1、访问注册页面
1.1 LoginController 层
- 获取注册页面,定义映射的请求路径和请求方法,跳转到注册页面
site/register
;
@Controller
public class LoginController {
@RequestMapping(value = "/register",method = RequestMethod.GET)
public String getRegisterPage(){
return "site/register";
}
}
1.2 修改 register.html 页面
- 修改引入的样式表路径为 thymeleaf 格式的,包括所有使用相对路径的 CSS、JS 文件都修改为绝对路径格式,例如:
<link rel="stylesheet" th:href="@{/css/login.css}" />
; - 同时,根据下面 index.html 文件修改中的提示,将所有文件都相同的头部文件定义一个,然后进行代码复用;
th:replace="index::header"
1.3 修改 index.html 界面
- 修改首页跳转链接和注册页跳转链接:
<a class="nav-link" th:href="@{/index}">首页</a>
<a class="nav-link" th:href="@{/register}">注册</a>
- 将头部整个 header 标签进行复用,先取个名字:
th:fragment="header"
,其他的文件复用这段代码:th:replace="index::header"
1.4 测试页面
- 输入访问链接
http://localhost:8080/community/register
,可以使用;从首页访问,可以跳转;
2、提交注册数据
2.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 配置域名
- 点击注册之后,发送激活链接到用户邮箱,这个激活链接在开发、测试和正式上线时是不一样的,所以要做成动态可配的,以后可以直接在配置文件中进行修改:
community.path.domain=http://localhost:8080
2.3 提供工具类:
- 生成随机字符串:激活码是随机字符串,上传头像文件命名也是随机字符串
- 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 层
- 注入邮件属性和模板引擎属性:
@Autowired
private MailClient mailClient;
@Autowired
private TemplateEngine templateEngine;
- 将域名和项目名注入进来:
@Value("${server.servlet.context-path}")
private String contextPath;
@Value("${community.path.domain}")
private String domain;
- 实现
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 页面:
- 引入 themeleaf 模板:
xmlns:th="http://www.thymeleaf.org"
- 邮箱替换掉:
th:text="${email}"
- 激活链接替换掉:
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 层
- 实现注册方法,只要你传送过来的请求中的值和 user 对象的属性匹配,就会自动给 user 对象对应属性赋值;同时由于 User 是实体类形参,MVC 会自动将这个参数注入到 model 中;
- 调用 UserService 层的
register(User user)
方法,返回一个map容器 - 判断如果 map 容器中没有值,代表注册信息可以使用,跳转到
operate-result
页面,告诉用户请尽快激活; - 如果 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 页面
- 静态资源路径修改为绝对路径:
th:href="@{/css/global.css}"
- 修改头部代码复用:
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 页面(修改内容部分)
- 表单提交方式,提交给谁:
th:action="@{/register}" method="post"
- 设置账号、密码、邮箱的 name 属性,一定要和 user 实体类中的属性名字一字不差的对应,否则就获取不到;例如:
name="username"
- 如果没有注册成功回到 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">注 册</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 测试页面
- 注册一个已经存在的用户名:
- 注册一个已经存在的邮箱:
- 注册一个都不重复的用户名和邮箱,看看邮箱是否收到邮件,点击链接之后跳转到了首页:
邮箱收到了激活邮件:
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 层
- UserService 层要实现
CommunityConstant
接口,因为要用到其中定义的常量; - 实现激活方法,点击激活链接,会传进来用户id和激活码;
- 判断目前的状态是三种状态码中的哪一种,并返回给表现层:
- 如果状态码未激活且传进来的激活码和保存在数据库的一致,则修改当前用户状态码并返回激活成功
- 如果状态码已激活,则返回已激活状态
- 其他情况,返回激活失败
//①成功没问题 ②重复激活 ③激活码伪造
@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 层
- 实现
CommunityConstant
接口; - 实现
activation
方法,设置映射的跳转路径为我们之前规定好的激活超链接http://localhost:8080/community/activation/用户id/激活码
; - 从超链接中获取用户id 和激活码信息(Restful 风格代码,通过
@PathVariable
注解将请求路径中的信息与形参信息对应起来),调用 UserService 层判断激活状态码; - 根据激活状态码修改 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";
}
- 激活成功会跳转到登陆页面,所以需要一个 getLoginPage 登陆页面的方法;
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String getLoginPage(){
return "site/login";
}
3.4 修改 login.html 页面(登录功能)
- 顶部加 thymeleaf 声明:
xmlns:th="http://www.thymeleaf.org"
- 静态资源路径修改为绝对路径:
th:href="@{/css/global.css}"
- 头部代码复用:
th:replace="index::header"
- 验证码图片资源改成动态路径:
th:src="@{/img/captcha.png}"
3.5 修改 index.html 页面(头部代码 - 登录功能)
- 修改登录超链接:
th:href="@{/login}"
3.6 测试激活链接是否可用
-
点击激活链接之后:
-
激活成功之后跳转到登陆页面:
-
数据库该用户的状态码 status 由 0 变为 1;
-
之后再次点击激活链接显示页面: