(仿牛客论坛项目)02 - 开发注册功能


1、访问注册页面

1.1 LoginController 层

  1. 获取注册页面,定义映射的请求路径和请求方法,跳转到注册页面 site/register
@Controller
public class LoginController {
    @RequestMapping(value = "/register",method = RequestMethod.GET)
    public String getRegisterPage(){
        return "site/register";
    }
}

1.2 修改 register.html 页面

  1. 修改引入的样式表路径为 thymeleaf 格式的,包括所有使用相对路径的 CSS、JS 文件都修改为绝对路径格式,例如: <link rel="stylesheet" th:href="@{/css/login.css}" />
  2. 同时,根据下面 index.html 文件修改中的提示,将所有文件都相同的头部文件定义一个,然后进行代码复用;th:replace="index::header"

在这里插入图片描述

1.3 修改 index.html 界面

  1. 修改首页跳转链接和注册页跳转链接:
<a class="nav-link" th:href="@{/index}">首页</a>
<a class="nav-link" th:href="@{/register}">注册</a>
  1. 将头部整个 header 标签进行复用,先取个名字:th:fragment="header" ,其他的文件复用这段代码:th:replace="index::header"

1.4 测试页面

  1. 输入访问链接 http://localhost:8080/community/register,可以使用;从首页访问,可以跳转;

在这里插入图片描述

2、提交注册数据

2.1 导包

  1. https://mvnrepository.com/commons long,这个包是判断字符串,是否存在空值等等,注册会用到:

在这里插入图片描述

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

2.2 配置域名

  1. 点击注册之后,发送激活链接到用户邮箱,这个激活链接在开发、测试和正式上线时是不一样的,所以要做成动态可配的,以后可以直接在配置文件中进行修改:
community.path.domain=http://localhost:8080

2.3 提供工具类:

  1. 生成随机字符串:激活码是随机字符串,上传头像文件命名也是随机字符串
  2. MD5 加密:给用户注册的密码进行加密,先加盐再加密
public class CommunityUtil {
    // 生成随机字符串
    public static String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
    
    /**
     * @param key ,用户输入的密码
     * @return 返回 MD5 加密的密码
     * 
     * MD5加密:hello -> abc123def456
     * 一般情况下我们会先加盐,再加密:hello + 3e4a8 -> abc123def456abc
     */
    public static String md5(String key) {
        if (StringUtils.isBlank(key)) {
            return null;
        }
        return DigestUtils.md5DigestAsHex(key.getBytes());//加密成16进制的字符串返回
    }
}

2.4 UserService 层

  1. 注入邮件属性和模板引擎属性:
@Autowired
private MailClient mailClient;
@Autowired
private TemplateEngine templateEngine;
  1. 将域名和项目名注入进来:
@Value("${server.servlet.context-path}")
private String contextPath;

@Value("${community.path.domain}")
private String domain;
  1. 实现 register 方法:
  • 返回密码不能为空,账号已存在等多种信息,所以我们要选择 Map<String,Obiject> 来接收,并在形参中传入 User 对象:
  • 空值处理:
    • User 为 null,抛出异常
    • User 的注册信息都不能为 null,封装到 map 容器中,响应给客户端错误信息
  • 验证账号:
    • 验证用户名和邮箱没有被注册,从数据库查信息之后不为空,则代表已经被注册过了,已经被注册过的话,给用户一个提示信息
  • 注册用户:
    • 设置除了用户 id 之外的所有用户信息
    • 设置随机头像的路径,牛客网上存了1000个随即头像,初始生成的用户头像从中取,http://images.nowcoder.com/head/%dt.png,其中 %d 填充位 0-1000 随机数;
    • 调用 DAO 层增加新用户方法

在这里插入图片描述

  • 激活邮件:
    • 新建 context 对象给模板传 context 参数
    • 将用户的邮箱和要点击的超链接设置进去
    • 超链接:http://localhost:8080/community/activation/用户id/激活码这里的用户id为什么不为空呢?因为我们 mybatis 配置中打开了自增主键配置,在向数据库插入数据之后自动将得到的自增 id 填充给了User类
    • 调用模板引擎生成动态网页
    • 给用户发送邮件
@Override
public Map<String, Object> register(User user) {
    Map<String, Object> map = new HashMap<>();
    //空值处理
    if (user == null) {
        throw new IllegalArgumentException("参数不能为空!");
    }
    if (StringUtils.isBlank(user.getUsername())) {
        map.put("usernameMsg", "用户名不能为空!");
        return map;
    }
    if (StringUtils.isBlank(user.getPassword())) {
        map.put("passwordMsg", "密码不能为空!");
        return map;
    }
    if (StringUtils.isBlank(user.getEmail())) {
        map.put("emailMsg", "邮箱不能为空!");
        return map;
    }

    //验证账号没有被注册过
    User uTemp = userMapper.selectByName(user.getUsername());
    if (uTemp != null) {
        map.put("usernameMsg", "用户名已经被注册过!");
        return map;
    }

    //验证邮箱没有被注册过
    uTemp = userMapper.selectByEmail(user.getEmail());
    if (uTemp != null) {
        map.put("emailMsg", "该邮箱已经被注册过!");
        return map;
    }

    //注册用户
    user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
    user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));
    user.setType(0);
    user.setStatus(0);
    user.setActivationCode(CommunityUtil.generateUUID());
    //设置头像路径为:http://images.nowcoder.com/head/%dt.png,其中%d填充位0-1000随机数
    user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", (int) (Math.random() * 1000)));
    user.setCreateTime(new Date());
    userMapper.insertUser(user);

    //发送激活邮件
    Context context = new Context();
    //这些是要替换html模板文件中的,设置邮箱
    context.setVariable("email", user.getEmail());
    //设置激活链接:http://localhost:8080/community/activation/用户id/激活码
    String activationUrl = "http://localhost:8080/community/activation/" + user.getId() + "/" + user.getActivationCode();
    context.setVariable("activationUrl", activationUrl);
    //调用模板引擎生成动态网页
    String content = templateEngine.process("mail/activation",context);
    mailClient.sendMail(user.getEmail(),"激活账号",content);

    return map;
}

注意:

  • 判断 map 容器为空的时候使用 StringUtils.isBlank() 这个方法,并且存在错误直接返回 map,结束当前方法;

2.5 设置激活邮件 activation.html 页面:

  1. 引入 themeleaf 模板:xmlns:th="http://www.thymeleaf.org"
  2. 邮箱替换掉:th:text="${email}"
  3. 激活链接替换掉:th:href="${activationUrl}"
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
    <title>牛客网-激活账号</title>
</head>
<body>
	<div>
		<p>
			<b th:text="${email}">xxx@xxx.com</b>, 您好!
		</p>
		<p>
			您正在注册牛客网, 这是一封激活邮件, 请点击 
			<a th:href="${activationUrl}">http://www.nowcoder.com/activation/abcdefg.html</a>,
			激活您的牛客账号!
		</p>
	</div>
</body>
</html>

2.6 LoginController 层

  1. 实现注册方法,只要你传送过来的请求中的值和 user 对象的属性匹配,就会自动给 user 对象对应属性赋值;同时由于 User 是实体类形参,MVC 会自动将这个参数注入到 model 中;
  2. 调用 UserService 层的 register(User user) 方法,返回一个map容器
  3. 判断如果 map 容器中没有值,代表注册信息可以使用,跳转到operate-result页面,告诉用户请尽快激活;
  4. 如果 map 有值,传给用户三种错误信息;
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String register(Model model, User user) {
    Map<String, Object> map = userService.register(user);
    if (map == null || map.isEmpty()) {
        model.addAttribute("msg", "注册成功,我们已经向您的邮箱发送了一封激活邮件,请尽快激活!");
        model.addAttribute("target", "/index");
        return "site/operate-result";
    }else{
        model.addAttribute("usernameMsg",map.get("usernameMsg"));
        model.addAttribute("passwordMsg",map.get("passwordMsg"));
        model.addAttribute("emailMsg",map.get("emailMsg"));
        return "/site/register";
    }
}

2.7 修改 operate-result.html 页面

  1. 静态资源路径修改为绝对路径:th:href="@{/css/global.css}"
  2. 修改头部代码复用:th:replace="index::header"
<!-- 内容 -->
<div class="main">
	<div class="container mt-5">
		<div class="jumbotron">
			<p class="lead" th:text="${msg}">您的账号已经激活成功,可以正常使用了!</p>
			<hr class="my-4">
			<p>
				系统会在 <span id="seconds" class="text-danger">8</span> 秒后自动跳转,
				您也可以点此 <a id="target" th:href="@{${target}}" class="text-primary">链接</a>, 手动跳转!
			</p>
		</div>
	</div>
</div>

注意:

  • th:href="@{${target}}",注意这里里面代表一个链接,所以要从绝对路径开始;

2.8 修改 register.html 页面(修改内容部分)

  1. 表单提交方式,提交给谁:th:action="@{/register}" method="post"
  2. 设置账号、密码、邮箱的 name 属性,一定要和 user 实体类中的属性名字一字不差的对应,否则就获取不到;例如:name="username"

在这里插入图片描述

  1. 如果没有注册成功回到 register 页面的话
  • 那么用户之前输入的信息都还要在,即设置默认填充值即可,没有就显示空:th:value="${user!=null?user.username:''}"
  • 给用户提示错误消息:th:text="${usernameMsg}"
  • 动态显示错误消息的样式:th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"
<!-- 内容 -->
<div class="main">
	<div class="container pl-5 pr-5 pt-3 pb-3 mt-3 mb-3">
		<h3 class="text-center text-info border-bottom pb-3">&nbsp;&nbsp;</h3>
		<form class="mt-5" th:action="@{/register}" method="post">
			<div class="form-group row">
				<label for="username" class="col-sm-2 col-form-label text-right">账号:</label>
				<div class="col-sm-10">
					<input type="text" th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"
						   name="username" th:value="${user!=null?user.username:''}"
						   id="username" placeholder="请输入您的账号!" required>
					<div class="invalid-feedback" th:text="${usernameMsg}">
						该账号已存在!
					</div>
				</div>
			</div>
			<div class="form-group row mt-4">
				<label for="password" class="col-sm-2 col-form-label text-right">密码:</label>
				<div class="col-sm-10">
					<input type="password" th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|"
						   name="password" th:value="${user!=null?user.password:''}"
						   id="password" placeholder="请输入您的密码!" required>
					<div class="invalid-feedback" th:text="${passwordMsg}">
						密码长度不能小于8位!
					</div>							
				</div>
			</div>
			<div class="form-group row mt-4">
				<label for="confirm-password" class="col-sm-2 col-form-label text-right">确认密码:</label>
				<div class="col-sm-10">
					<input type="password" class="form-control"
						   th:value="${user!=null?user.password:''}"
						   id="confirm-password" placeholder="请再次输入密码!" required>
					<div class="invalid-feedback">
						两次输入的密码不一致!
					</div>
				</div>
			</div>
			<div class="form-group row">
				<label for="email" class="col-sm-2 col-form-label text-right">邮箱:</label>
				<div class="col-sm-10">
					<input type="email" th:class="|form-control ${emailMsg!=null?'is-invalid':''}|"
						   name="email" th:value="${user!=null?user.email:''}"
						   id="email" placeholder="请输入您的邮箱!" required>
					<div class="invalid-feedback" th:text="${emailMsg}">
						该邮箱已注册!
					</div>
				</div>
			</div>
			<div class="form-group row mt-4">
				<div class="col-sm-2"></div>
				<div class="col-sm-10 text-center">
					<button type="submit" class="btn btn-info text-white form-control">立即注册</button>
				</div>
			</div>
		</form>				
	</div>
</div>

2.9 测试页面

  1. 注册一个已经存在的用户名:

在这里插入图片描述

  1. 注册一个已经存在的邮箱:

在这里插入图片描述

  1. 注册一个都不重复的用户名和邮箱,看看邮箱是否收到邮件,点击链接之后跳转到了首页:

在这里插入图片描述
邮箱收到了激活邮件:

在这里插入图片描述

3、激活注册账号

3.1 激活的三种状态

①成功没问题 ②重复激活 ③激活码伪造
创建一个接口表示上述三种状态

public interface CommunityConstant {
    /**
     * 激活成功
     */
    int ACTIVATION_SUCCESS = 0;

    /**
     * 重复激活
     */
    int ACTIVATION_REPEAT = 1;

    /**
     * 激活失败
     */
    int ACTIVATION_FAILURE = 2;

    /**
     * 默认状态的登录凭证的超时时间,12h
     */
    int DEFAULT_EXPIRED_SECONDS = 3600 * 12;

    /**
     * 记住状态的登录凭证超时时间,100天
     */
    int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;
}

3.2 UserService 层

  1. UserService 层要实现 CommunityConstant 接口,因为要用到其中定义的常量;
  2. 实现激活方法,点击激活链接,会传进来用户id和激活码
  3. 判断目前的状态是三种状态码中的哪一种,并返回给表现层:
    1. 如果状态码未激活且传进来的激活码和保存在数据库的一致,则修改当前用户状态码并返回激活成功
    2. 如果状态码已激活,则返回已激活状态
    3. 其他情况,返回激活失败
//①成功没问题 ②重复激活 ③激活码伪造
@Override
public int activation(int userId, String code) {
    int status = userMapper.selectById(userId).getStatus();
    String activationCode = userMapper.selectById(userId).getActivationCode();
    if (status == 0 && activationCode.equals(code)){
        return ACTIVATION_SUCCESS;
    }else if (status == 1){
        return ACTIVATION_REPEAT;
    }else {
        return ACTIVATION_FAILURE;
    }
}

3.3 LoginController 层

  1. 实现 CommunityConstant 接口;
  2. 实现 activation 方法,设置映射的跳转路径为我们之前规定好的激活超链接http://localhost:8080/community/activation/用户id/激活码
  3. 从超链接中获取用户id 和激活码信息(Restful 风格代码,通过 @PathVariable 注解将请求路径中的信息与形参信息对应起来),调用 UserService 层判断激活状态码;
  4. 根据激活状态码修改 result 页面的信息,即最终跳转到 operate-result 页面;即根据不同的激活状态码向 model 中放置不同的 msg 和 target 信息;

在这里插入图片描述

//超链接http://localhost:8080/community/activation/用户id/激活码
@RequestMapping(path = "/activation/{userId}/{code}",method = RequestMethod.GET)
public String activation(Model model,
        @PathVariable("userId") int userId,@PathVariable("code") String code){
    int result = userService.activation(userId, code);
    if (result == ACTIVATION_SUCCESS) {
        model.addAttribute("msg", "激活成功,您的账号已经可以正常使用了!");
        model.addAttribute("target", "/login");
    } else if (result == ACTIVATION_REPEAT) {
        model.addAttribute("msg", "无效操作,该账号已经激活过了!");
        model.addAttribute("target", "/index");
    } else {
        model.addAttribute("msg", "激活失败,您提供的激活码不正确!");
        model.addAttribute("target", "/index");
    }
    return "/site/operate-result";
}
  1. 激活成功会跳转到登陆页面,所以需要一个 getLoginPage 登陆页面的方法;
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String getLoginPage(){
    return "site/login";
}

3.4 修改 login.html 页面(登录功能)

  1. 顶部加 thymeleaf 声明:xmlns:th="http://www.thymeleaf.org"
  2. 静态资源路径修改为绝对路径:th:href="@{/css/global.css}"
  3. 头部代码复用:th:replace="index::header"
  4. 验证码图片资源改成动态路径:th:src="@{/img/captcha.png}"

3.5 修改 index.html 页面(头部代码 - 登录功能)

  1. 修改登录超链接:th:href="@{/login}"

3.6 测试激活链接是否可用

  1. 点击激活链接之后:
    在这里插入图片描述

  2. 激活成功之后跳转到登陆页面:
    在这里插入图片描述

  3. 数据库该用户的状态码 status 由 0 变为 1;

  4. 之后再次点击激活链接显示页面:

在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值