• 仿牛客论坛项目


    个人记录一下觉得有意思的功能模块

    目录

    1.发送邮件(包括html页面)

    1.1 导入maven依赖

    1.2 修改配置文件

    1.3 编写邮件发送工具类

    1.4 工具类测试

    2.登陆注册功能

    2.1 对用户的信息进行加密处理

    2.2 用户重复注册问题和用户未激活问题

    2.3 确定邮件发送的具体网址信息

    1.发送邮件(包括html页面)

    1.1 导入maven依赖

    发送邮件的功能封装在spring中,可以直接调用,首先导入相应的依赖

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-mailartifactId>
    4. <version>2.1.5.RELEASEversion>
    5. dependency>

    1.2 修改配置文件

    再修改一下application.properties配置文件,加入以下内容

    1. # MailProperties
    2. spring.mail.host=smtp.sina.com
    3. spring.mail.port=465
    4. spring.mail.username=你自己的邮箱地址
    5. spring.mail.password=邮箱的授权码
    6. spring.mail.protocol=smtps
    7. spring.mail.properties.mail.smtp.ssl.enable=true

    1.3 编写邮件发送工具类

    好了,接下来我们开始写一下发送邮件的工具类MailClient

    1. @Component
    2. public class MailClient {
    3. private static final Logger logger = LoggerFactory.getLogger(MailClient.class);
    4. @Autowired
    5. private JavaMailSender mailSender;
    6. @Value("${spring.mail.username}")
    7. private String from;
    8. public void sendMail(String to, String subject, String content) {
    9. try {
    10. MimeMessage message = mailSender.createMimeMessage();
    11. MimeMessageHelper helper = new MimeMessageHelper(message);
    12. helper.setFrom(from);
    13. helper.setTo(to);
    14. helper.setSubject(subject);
    15. helper.setText(content, true);
    16. mailSender.send(helper.getMimeMessage());
    17. } catch (MessagingException e) {
    18. logger.error("发送邮件失败:" + e.getMessage());
    19. }
    20. }
    21. }

    1.4 工具类测试

    接下来我们测试一下这个工具类

    1. package com.nowcoder.community;
    2. import com.nowcoder.community.util.MailClient;
    3. import org.junit.Test;
    4. import org.junit.runner.RunWith;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.boot.test.context.SpringBootTest;
    7. import org.springframework.test.context.ContextConfiguration;
    8. import org.springframework.test.context.junit4.SpringRunner;
    9. import org.thymeleaf.TemplateEngine;
    10. import org.thymeleaf.context.Context;
    11. @RunWith(SpringRunner.class)
    12. @SpringBootTest
    13. @ContextConfiguration(classes = CommunityApplication.class)
    14. public class MailTests {
    15. @Autowired
    16. private MailClient mailClient;
    17. @Autowired
    18. private TemplateEngine templateEngine;
    19. //发送文字消息
    20. @Test
    21. public void testTextMail() {
    22. mailClient.sendMail("949464367@qq.com", "TEST", "Welcome.");
    23. }
    24. //发送网页(需要事先准备好网页资源)
    25. @Test
    26. public void testHtmlMail() {
    27. //Context为网页正文内容
    28. Context context = new Context();
    29. //为正文内容的变量进行赋值
    30. context.setVariable("username", "sunday");
    31. //静态资源路径 template/context
    32. String content = templateEngine.process("/mail/demo", context);
    33. System.out.println(content);
    34. //发送邮件
    35. mailClient.sendMail("949464367@qq.com", "HTML", content);
    36. }
    37. }

    网页版测试结果

     特别给力,这个方法可以用到后面的账号注册激活功能模块里面

    2.登陆注册功能

    通过注册页面,输入账号,密码,邮箱,点击注册后,工程会往注册邮箱发送一份html,点击html中的链接,以实现用户的激活。

    2.1 对用户的信息进行加密处理

    用户的账号,密码的注册,为安全性考虑,不能直接存储进数据库,下场就是学习通,需要进行加密存储。需要编写一个加密工具类,对用户信息进行加密。

    采用MD5加密方法,为防止密码过于简单,导致即便是加密后也容易破解的问题,设置了加密盐,也就是在原密码后,生成n位随机字符串,再进行整体的一个加密。

    工具类如下所示

    1. package com.nowcoder.community.util;
    2. import org.apache.commons.lang3.StringUtils;
    3. import org.springframework.util.DigestUtils;
    4. import java.util.UUID;
    5. public class CommunityUtil {
    6. // 生成随机字符串
    7. public static String generateUUID() {
    8. return UUID.randomUUID().toString().replaceAll("-", "");
    9. }
    10. // MD5加密
    11. // hello -> abc123def456,这样的弊端就是简单的密码加密之后依旧是容易破解的
    12. //在原账号的基础上加上随机的字符串,再进行加密,这样不容易被破解
    13. // hello + 3e4a8 -> abc123def456abc
    14. public static String md5(String key) {
    15. //空值不处理
    16. if (StringUtils.isBlank(key)) {
    17. return null;
    18. }
    19. //spring自带的mp5加密
    20. return DigestUtils.md5DigestAsHex(key.getBytes());
    21. }
    22. }

    2.2 用户重复注册问题和用户未激活问题

    注册用户的账号,邮箱不能重复注册,账号需要激活以后才能使用,账号不能重复激活。

    不能重复注册实现方式,即去数据库找是否已存在,账号需要激活,不能重复激活,通过定义一组常量,通过它属性的变化,给user赋予相应的值

    1. package com.nowcoder.community.util;
    2. public interface CommunityConstant {
    3. /**
    4. * 激活成功
    5. */
    6. int ACTIVATION_SUCCESS = 0;
    7. /**
    8. * 重复激活
    9. */
    10. int ACTIVATION_REPEAT = 1;
    11. /**
    12. * 激活失败
    13. */
    14. int ACTIVATION_FAILURE = 2;
    15. }

    user类为如下所示

    1. package com.nowcoder.community.entity;
    2. import java.util.Date;
    3. public class User {
    4. private int id;
    5. //用户名
    6. private String username;
    7. //密码
    8. private String password;
    9. //加密盐
    10. private String salt;
    11. //邮箱
    12. private String email;
    13. //用户种类
    14. private int type;
    15. //用户状态 0为未激活,1为激活
    16. private int status;
    17. //用户的激活码
    18. private String activationCode;
    19. //用户的头像地址,获取头像
    20. private String headerUrl;
    21. //用户的注册时间
    22. private Date createTime;
    23. public int getId() {
    24. return id;
    25. }
    26. public void setId(int id) {
    27. this.id = id;
    28. }
    29. public String getUsername() {
    30. return username;
    31. }
    32. public void setUsername(String username) {
    33. this.username = username;
    34. }
    35. public String getPassword() {
    36. return password;
    37. }
    38. public void setPassword(String password) {
    39. this.password = password;
    40. }
    41. public String getSalt() {
    42. return salt;
    43. }
    44. public void setSalt(String salt) {
    45. this.salt = salt;
    46. }
    47. public String getEmail() {
    48. return email;
    49. }
    50. public void setEmail(String email) {
    51. this.email = email;
    52. }
    53. public int getType() {
    54. return type;
    55. }
    56. public void setType(int type) {
    57. this.type = type;
    58. }
    59. public int getStatus() {
    60. return status;
    61. }
    62. public void setStatus(int status) {
    63. this.status = status;
    64. }
    65. public String getActivationCode() {
    66. return activationCode;
    67. }
    68. public void setActivationCode(String activationCode) {
    69. this.activationCode = activationCode;
    70. }
    71. public String getHeaderUrl() {
    72. return headerUrl;
    73. }
    74. public void setHeaderUrl(String headerUrl) {
    75. this.headerUrl = headerUrl;
    76. }
    77. public Date getCreateTime() {
    78. return createTime;
    79. }
    80. public void setCreateTime(Date createTime) {
    81. this.createTime = createTime;
    82. }
    83. @Override
    84. public String toString() {
    85. return "User{" +
    86. "id=" + id +
    87. ", username='" + username + '\'' +
    88. ", password='" + password + '\'' +
    89. ", salt='" + salt + '\'' +
    90. ", email='" + email + '\'' +
    91. ", type=" + type +
    92. ", status=" + status +
    93. ", activationCode='" + activationCode + '\'' +
    94. ", headerUrl='" + headerUrl + '\'' +
    95. ", createTime=" + createTime +
    96. '}';
    97. }
    98. }

    2.3 确定邮件发送的具体网址信息

    注册成功以后,工程会向用户发送激活邮件,点击里面的链接才能成功对用户进行激活。

    加入配置项

    1. # community域名
    2. community.path.domain=http://localhost:8088

    编写service层,实现注册的具体功能,以及邮件收发,UserService

    1. package com.nowcoder.community.service;
    2. import com.nowcoder.community.dao.UserMapper;
    3. import com.nowcoder.community.entity.User;
    4. import com.nowcoder.community.util.CommunityConstant;
    5. import com.nowcoder.community.util.CommunityUtil;
    6. import com.nowcoder.community.util.MailClient;
    7. import org.apache.commons.lang3.StringUtils;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.beans.factory.annotation.Value;
    10. import org.springframework.stereotype.Service;
    11. import org.thymeleaf.TemplateEngine;
    12. import org.thymeleaf.context.Context;
    13. import java.util.Date;
    14. import java.util.HashMap;
    15. import java.util.Map;
    16. import java.util.Random;
    17. @Service
    18. public class UserService implements CommunityConstant {
    19. @Autowired
    20. private UserMapper userMapper;
    21. @Autowired
    22. private MailClient mailClient;
    23. @Autowired
    24. private TemplateEngine templateEngine;
    25. //@Value注解,从配置文件中提取值
    26. @Value("${community.path.domain}")
    27. private String domain;
    28. //@Value注解,从配置文件中提取值
    29. @Value("${server.servlet.context-path}")
    30. private String contextPath;
    31. public User findUserById(int id) {
    32. return userMapper.selectById(id);
    33. }
    34. public Map register(User user) {
    35. Map map = new HashMap<>();
    36. // 空值处理
    37. if (user == null) {
    38. throw new IllegalArgumentException("参数不能为空!");
    39. }
    40. if (StringUtils.isBlank(user.getUsername())) {
    41. map.put("usernameMsg", "账号不能为空!");
    42. return map;
    43. }
    44. if (StringUtils.isBlank(user.getPassword())) {
    45. map.put("passwordMsg", "密码不能为空!");
    46. return map;
    47. }
    48. if (StringUtils.isBlank(user.getEmail())) {
    49. map.put("emailMsg", "邮箱不能为空!");
    50. return map;
    51. }
    52. // 验证账号
    53. User u = userMapper.selectByName(user.getUsername());
    54. if (u != null) {
    55. map.put("usernameMsg", "该账号已存在!");
    56. return map;
    57. }
    58. // 验证邮箱
    59. u = userMapper.selectByEmail(user.getEmail());
    60. if (u != null) {
    61. map.put("emailMsg", "该邮箱已被注册!");
    62. return map;
    63. }
    64. // 注册用户
    65. //加密盐
    66. user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
    67. //原注册密码加上加密盐,再用md5进行加密,这样的密码难以破解
    68. user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));
    69. //用户种类
    70. user.setType(0);
    71. //是否被激活
    72. user.setStatus(0);
    73. //激活码
    74. user.setActivationCode(CommunityUtil.generateUUID());
    75. //生成随机头像,参数是网址格式+(图片序号)
    76. user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
    77. user.setCreateTime(new Date());
    78. userMapper.insertUser(user);
    79. // 激活邮件
    80. Context context = new Context();
    81. context.setVariable("email", user.getEmail());
    82. // http://localhost:8080/community/activation/101/code 项目的访问路径,展示在网页上
    83. String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
    84. context.setVariable("url", url);
    85. //将正文内容放到模板引擎中,即可跳转访问template文件夹下mail文件夹下的activation.html资源
    86. String content = templateEngine.process("/mail/activation", context);
    87. mailClient.sendMail(user.getEmail(), "激活账号", content);
    88. return map;
    89. }
    90. public int activation(int userId, String code) {
    91. User user = userMapper.selectById(userId);
    92. //重复激活
    93. if (user.getStatus() == 1) {
    94. return ACTIVATION_REPEAT;
    95. }
    96. //成功激活
    97. else if (user.getActivationCode().equals(code)) {
    98. userMapper.updateStatus(userId, 1);
    99. return ACTIVATION_SUCCESS;
    100. }
    101. //激活失败
    102. else {
    103. return ACTIVATION_FAILURE;
    104. }
    105. }
    106. }

    以及对应的Controller层 LoginController

    1. package com.nowcoder.community.controller;
    2. import com.nowcoder.community.entity.User;
    3. import com.nowcoder.community.service.UserService;
    4. import com.nowcoder.community.util.CommunityConstant;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.stereotype.Controller;
    7. import org.springframework.ui.Model;
    8. import org.springframework.web.bind.annotation.PathVariable;
    9. import org.springframework.web.bind.annotation.RequestMapping;
    10. import org.springframework.web.bind.annotation.RequestMethod;
    11. import java.util.Map;
    12. @Controller
    13. public class LoginController implements CommunityConstant {
    14. @Autowired
    15. private UserService userService;
    16. @RequestMapping(path = "/register", method = RequestMethod.GET)
    17. public String getRegisterPage() {
    18. return "/site/register";
    19. }
    20. @RequestMapping(path = "/login", method = RequestMethod.GET)
    21. public String getLoginPage() {
    22. return "/site/login";
    23. }
    24. //注册触发的函数,成功则发送一封邮件
    25. @RequestMapping(path = "/register", method = RequestMethod.POST)
    26. public String register(Model model, User user) {
    27. Map map = userService.register(user);
    28. //成功
    29. if (map == null || map.isEmpty()) {
    30. model.addAttribute("msg", "注册成功,我们已经向您的邮箱发送了一封激活邮件,请尽快激活!");
    31. model.addAttribute("target", "/index");
    32. //页面跳转
    33. return "/site/operate-result";
    34. }
    35. //失败
    36. else {
    37. model.addAttribute("usernameMsg", map.get("usernameMsg"));
    38. model.addAttribute("passwordMsg", map.get("passwordMsg"));
    39. model.addAttribute("emailMsg", map.get("emailMsg"));
    40. return "/site/register";
    41. }
    42. }
    43. //发送激活邮件,通过点击链接触发的函数,通过读取请求地址里面的参数,判定激活码是否有效,判定用户是否成功激活
    44. // http://localhost:8080/community/activation/101/code
    45. @RequestMapping(path = "/activation/{userId}/{code}", method = RequestMethod.GET)
    46. public String activation(Model model, @PathVariable("userId") int userId, @PathVariable("code") String code) {
    47. int result = userService.activation(userId, code);
    48. if (result == ACTIVATION_SUCCESS) {
    49. model.addAttribute("msg", "激活成功,您的账号已经可以正常使用了!");
    50. model.addAttribute("target", "/login");
    51. } else if (result == ACTIVATION_REPEAT) {
    52. model.addAttribute("msg", "无效操作,该账号已经激活过了!");
    53. model.addAttribute("target", "/index");
    54. } else {
    55. model.addAttribute("msg", "激活失败,您提供的激活码不正确!");
    56. model.addAttribute("target", "/index");
    57. }
    58. return "/site/operate-result";
    59. }
    60. }

    注册界面为register.html

    1. html>
    2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
    3. <head>
    4. <meta charset="utf-8">
    5. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    6. <link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
    7. <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
    8. <link rel="stylesheet" th:href="@{/css/global.css}" />
    9. <link rel="stylesheet" th:href="@{/css/login.css}" />
    10. <title>牛客网-注册title>
    11. head>
    12. <body>
    13. <div class="nk-container">
    14. <header class="bg-dark sticky-top" th:replace="index::header">
    15. <div class="container">
    16. <nav class="navbar navbar-expand-lg navbar-dark">
    17. <a class="navbar-brand" href="#">a>
    18. <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
    19. <span class="navbar-toggler-icon">span>
    20. button>
    21. <div class="collapse navbar-collapse" id="navbarSupportedContent">
    22. <ul class="navbar-nav mr-auto">
    23. <li class="nav-item ml-3 btn-group-vertical">
    24. <a class="nav-link" href="../index.html">首页a>
    25. li>
    26. <li class="nav-item ml-3 btn-group-vertical">
    27. <a class="nav-link position-relative" href="letter.html">消息<span class="badge badge-danger">12span>a>
    28. li>
    29. <li class="nav-item ml-3 btn-group-vertical">
    30. <a class="nav-link" href="register.html">注册a>
    31. li>
    32. <li class="nav-item ml-3 btn-group-vertical">
    33. <a class="nav-link" href="login.html">登录a>
    34. li>
    35. <li class="nav-item ml-3 btn-group-vertical dropdown">
    36. <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
    37. <img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
    38. a>
    39. <div class="dropdown-menu" aria-labelledby="navbarDropdown">
    40. <a class="dropdown-item text-center" href="profile.html">个人主页a>
    41. <a class="dropdown-item text-center" href="setting.html">账号设置a>
    42. <a class="dropdown-item text-center" href="login.html">退出登录a>
    43. <div class="dropdown-divider">div>
    44. <span class="dropdown-item text-center text-secondary">nowcoderspan>
    45. div>
    46. li>
    47. ul>
    48. <form class="form-inline my-2 my-lg-0" action="search.html">
    49. <input class="form-control mr-sm-2" type="search" aria-label="Search" />
    50. <button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索button>
    51. form>
    52. div>
    53. nav>
    54. div>
    55. header>
    56. <div class="main">
    57. <div class="container pl-5 pr-5 pt-3 pb-3 mt-3 mb-3">
    58. <h3 class="text-center text-info border-bottom pb-3">  h3>
    59. <form class="mt-5" method="post" th:action="@{/register}">
    60. <div class="form-group row">
    61. <label for="username" class="col-sm-2 col-form-label text-right">账号:label>
    62. <div class="col-sm-10">
    63. <input type="text"
    64. th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"
    65. th:value="${user!=null?user.username:''}"
    66. id="username" name="username" placeholder="请输入您的账号!" required>
    67. <div class="invalid-feedback" th:text="${usernameMsg}">
    68. 该账号已存在!
    69. div>
    70. div>
    71. div>
    72. <div class="form-group row mt-4">
    73. <label for="password" class="col-sm-2 col-form-label text-right">密码:label>
    74. <div class="col-sm-10">
    75. <input type="password"
    76. th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|"
    77. th:value="${user!=null?user.password:''}"
    78. id="password" name="password" placeholder="请输入您的密码!" required>
    79. <div class="invalid-feedback" th:text="${passwordMsg}">
    80. 密码长度不能小于8位!
    81. div>
    82. div>
    83. div>
    84. <div class="form-group row mt-4">
    85. <label for="confirm-password" class="col-sm-2 col-form-label text-right">确认密码:label>
    86. <div class="col-sm-10">
    87. <input type="password" class="form-control"
    88. th:value="${user!=null?user.password:''}"
    89. id="confirm-password" placeholder="请再次输入密码!" required>
    90. <div class="invalid-feedback">
    91. 两次输入的密码不一致!
    92. div>
    93. div>
    94. div>
    95. <div class="form-group row">
    96. <label for="email" class="col-sm-2 col-form-label text-right">邮箱:label>
    97. <div class="col-sm-10">
    98. <input type="email"
    99. th:class="|form-control ${emailMsg!=null?'is-invalid':''}|"
    100. th:value="${user!=null?user.email:''}"
    101. id="email" name="email" placeholder="请输入您的邮箱!" required>
    102. <div class="invalid-feedback" th:text="${emailMsg}">
    103. 该邮箱已注册!
    104. div>
    105. div>
    106. div>
    107. <div class="form-group row mt-4">
    108. <div class="col-sm-2">div>
    109. <div class="col-sm-10 text-center">
    110. <button type="submit" class="btn btn-info text-white form-control">立即注册button>
    111. div>
    112. div>
    113. form>
    114. div>
    115. div>
    116. <footer class="bg-dark">
    117. <div class="container">
    118. <div class="row">
    119. <div class="col-4 qrcode">
    120. <img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
    121. div>
    122. <div class="col-8 detail-info">
    123. <div class="row">
    124. <div class="col">
    125. <ul class="nav">
    126. <li class="nav-item">
    127. <a class="nav-link text-light" href="#">关于我们a>
    128. li>
    129. <li class="nav-item">
    130. <a class="nav-link text-light" href="#">加入我们a>
    131. li>
    132. <li class="nav-item">
    133. <a class="nav-link text-light" href="#">意见反馈a>
    134. li>
    135. <li class="nav-item">
    136. <a class="nav-link text-light" href="#">企业服务a>
    137. li>
    138. <li class="nav-item">
    139. <a class="nav-link text-light" href="#">联系我们a>
    140. li>
    141. <li class="nav-item">
    142. <a class="nav-link text-light" href="#">免责声明a>
    143. li>
    144. <li class="nav-item">
    145. <a class="nav-link text-light" href="#">友情链接a>
    146. li>
    147. ul>
    148. div>
    149. div>
    150. <div class="row">
    151. <div class="col">
    152. <ul class="nav btn-group-vertical company-info">
    153. <li class="nav-item text-white-50">
    154. 公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
    155. li>
    156. <li class="nav-item text-white-50">
    157. 联系方式:010-60728802(电话)    admin@nowcoder.com
    158. li>
    159. <li class="nav-item text-white-50">
    160. 牛客科技©2018 All rights reserved
    161. li>
    162. <li class="nav-item text-white-50">
    163. 京ICP备14055008号-4     
    164. <img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
    165. 京公网安备 11010502036488号
    166. li>
    167. ul>
    168. div>
    169. div>
    170. div>
    171. div>
    172. div>
    173. footer>
    174. div>
    175. <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous">script>
    176. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous">script>
    177. <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous">script>
    178. <script th:src="@{/js/global.js}">script>
    179. <script th:src="@{/js/register.js}">script>
    180. body>
    181. html>

    激活界面activation.html

  • 相关阅读:
    C# OpenCvSharp 基于直线检测的文本图像倾斜校正
    C++初阶 入门
    【mybatis注解开发+二级缓存】
    常见的噪声:高斯、泊松和椒盐噪声
    python curl2pyreqs 生成接口脚本
    从HashMap的执行流程开始 揭开HashMap底层实现
    【数据结构】队列的基本操作——基本实现 | 初始化 | 出入队列
    程序员怎样才能学好算法? 这本书送几本给大家!
    大语言模型量化方法对比:GPTQ、GGUF、AWQ
    第十一章·组合模式
  • 原文地址:https://blog.csdn.net/WC949464367/article/details/126087138