文章目录
- 整体功能实现
- 1. 访问注册页面
- 功能需求:
- 功能实现
- ==th:href="@{/index}==
- ==th:fragment="header"==
- ==th:replace="index::header"==
- 2. 提交注册页面
- 功能需求
- 功能实现
- 2.1 Service层处理用户提交的表单数据
- 环境、工具类准备
- ==commons-lang3包导入==-==StringUtils.isBlank(key)==
- 配置域名
- 设计注册工具类-==UUID,MD5==
- server层处理业务逻辑
- 2.1.验证信息
- 2.2 信息入库
- 2.3 发送激活邮件
- 2.2 Controller层处理注册表单提交与信息反馈
- 请求方式、接受参数
- 对请求的接收与响应
- 2.3 View视图模板页面处理
- 中间提示跳转页面模板
- ==th:href="@{${target}}==
- 注册表单页面模板
- ==th:action="@{/register}==
- ==name="userName"==
- ==th:text="${usernameMsg}"==
- ==is-invalid==
- ==th:value=""==
- 邮件模板页面
- 测试
- 3. 激活注册用户
- 功能需求
- 功能实现
- 3.1 service层处理激活业务
- 设计静态常量接口
- 设计激活业务
- 3.2 Controller层接收和响应请求
- ==path = "/activate/{userId}/{code}"==
- ==@PathVariable("userId") int userId==
- 请求接收响应处理逻辑
- 3.2 View层处理模板页面
- 测试:
参考牛客网高级项目教程
整体功能实现
- 对于业务需求比较大的网页,可以按照功能进行拆分设计
- 也是按照请求来拆解设计
1. 访问注册页面
功能需求:
- 点击顶部区域内的链接,打开注册页面。
- 注册页面头部和尾部复用主页的页面
- 点击首页,能够再次跳转回首页
功能实现
- 只是为了显示表单信息,只需在视图层处理即可,没有modle需要封装
- 需要处理以下信息:
- Controller处理:点击访问url,直接返回模板页面,因此可以设定为GET请求
@Controller
public class LoginController {
// 注册页面的显示
@RequestMapping(path = "/register", method = RequestMethod.GET)
public String getRegisterPage() {
return "/site/register";
}
}
th:href="@{/index}
th:fragment=“header”
th:replace=“index::header”
- View处理: 将主页的头部和尾部复用,使用th引擎处理
- url点击位置在index主页上,因此需要处理主页页面的url链接,用th控制
- th:href="@{/index}
- th:href="@{/register}"
- 定义复用片段的别名:th:fragment=“header”
- 替换要复用的片段:th:replace=“index::header”
2. 提交注册页面
功能需求
- 用户通过表单提交数据。
- 服务端验证账号是否已存在、邮箱是否已注册。
- 注册成功后,
- 将用户信息添加进数据库
- 注册成功后,服务端向用户注册邮箱发送激活邮件
- 注册失败:
- 将错误信息封装,并在注册表单页面上显示错误提示信息
功能实现
2.1 Service层处理用户提交的表单数据
环境、工具类准备
commons-lang3包导入-StringUtils.isBlank(key)
- 提供判断字符串、集合常用数据结构空值等其他情况
- null,空字符串、空格均被判为空值
<!--提供判断字符串、集合常用数据结构空值情况-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
配置域名
- 注册邮箱时,需要向用户邮箱发送激活码,激活码中要拼接带有用户id,激活码信息
- 因此需要将当前项目域名、项目名、功能名拼接进去
- 提前在配置类中配置好,以便复用
# community,域名
community.path.domain=http://localhost:8080
# url中的项目名
server.servlet.context-path=/community
设计注册工具类-UUID,MD5
可以定义成静态方法,可以直接使用,无需交给SpringIOC容器管理
- 1.生成随机字符串
- 将"-" 替换成""
// 生成随机字符串
public static String generateUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
- 2.MD5加密
- MD5实质上只能加密,不能解密,
- 但简单字符串加密后可以破解,是查询了MD5密码库
- 所以,为了增加安全性,将用户密码拼接上一个salt(随机字符串),再MD5加密,库中就查不到了
- 调用Spring中自带的MD5加密16进制字符串方法
- 不过,接受的参数为byte数组类型,需将字符串类型转换
// MD5加密
// hello -> abc123def456
// hello + 3e4a8 -> abc123def456abc
public static String md5(String key) {
// 判空
if(StringUtils.isBlank(key)) {
return null;
}
// 调用Spring的工具类
return DigestUtils.md5DigestAsHex(key.getBytes())
}
server层处理业务逻辑
- 数据属性注入
- 发邮件工具类
- 模板引擎
- url的域名、项目名,注意变量名不要用关键字context
@Autowired
private MailClient mailClient;
@Autowired
private TemplateEngine templateEngine;
@Value("${community.path.domain}")
private String domain;
@Value("${server.servlet.context-path}")
private String contextPth; //
- 注册业务方法
- 返回值:user,用户信息
- 接受参数:返回的注册错误信息
/**
* 注册业务
* @param user,用户信息
* @return 返回的注册错误信息
*/
public Map<String, Object> register(User user) {
Map<String, Object> falseMap = new HashMap<>();
...
return falseMap;
}
- 内部逻辑:
2.1.验证信息
- 空值处理
- 账户、密码、邮箱
- 与数据库比对验证
- 账户不能重复
- 邮箱不能重复
Map<String, Object> falseMap = new HashMap<>();
// 1.验证用户输入内容是否有问题
// 用户对象不能为null
if(user == null) {
throw new IllegalArgumentException("注册用户不能为空");
}
// 内容不能为空
// 用户名
if(StringUtils.isBlank(user.getUsername())) {
falseMap.put("userNameMsg", "用户名不能为空!");
return falseMap;
}
// 密码
if(StringUtils.isBlank(user.getPassword())) {
falseMap.put("passwordMsg", "密码不能为空!");
return falseMap;
}
// 邮箱
if(StringUtils.isBlank(user.getEmail())) {
falseMap.put("emailMsg", "邮箱不能为空!");
return falseMap;
}
2.2 信息入库
- 验证无误,将用户信息入库,并向用户发送激活链接
// 先将密码进行MD5加密
String salt = CommunityUtil.generateUUID().substring(0, 5);
user.setSalt(salt);
user.setPassword(CommunityUtil.md5(user.getPassword() + salt));
// 其他状态设置
user.setType(0); // 普通用户
user.setStatus(0); // 未激活
String code = CommunityUtil.generateUUID();
user.setActivationCode(code); // 激活码
user.setCreateTime(new Date()); // 创建时间
user.setHeaderUrl(String.format( // 用户头像,随机生成
"http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
// 入库
userMapper.insertUser(user);
2.3 发送激活邮件
- 向注册用户邮箱发邮件
// 创建封装数据的Context,封装数据
Context context = new Context(); // thymeleaf模板中的Context,相当于model
context.setVariable("toMsg", user.getEmail());
// 拼接激活链接url,http://localhost:8080/community/activation/101/code
String url = domain + contextPth + "/activate" + "/" + user.getId() + "/" + code;
context.setVariable("urlMsg", url);
// 发送HTML邮件
// 利用模板生成动态网页,需将模板视图传过去
// 模板引擎自动识别context传入的参数,然后动态加载到网页中,
// 将网页的动态变量进行替换,并将网页内容加载到content中
String content = templateEngine.process("/mail/activation", context);
mailClient.sendMail(user.getEmail(), "交流网用户注册激活链接", content);
- 处理激活邮件模板页面
- 渲染链接:
- 1.th:href="@{${urlMsg}}"
- 2.th:href="${urlMsg}" 都行,但通过Controller传过去数据只能用第二种
<div>
<p>
<b th:text="${toMsg}">xxx@xxx.com</b>, 您好!
</p>
<p>
您正在注册交流网, 这是一封激活邮件, 请点击
<a th:href="@{${urlMsg}}">此链接</a>,
激活您的社交账号!
</p>
</div>
2.2 Controller层处理注册表单提交与信息反馈
请求方式、接受参数
- 提交表单数据,用Post请求,路径与GET请求一样,但,请求方式不同,处理方式不同
- 直接用User类接收表单数据,
- 属性名相同,Spring会自动将表单中对应属性值userName,password,email等属性值注入到user类中
// 注册页面表单提交请求
@RequestMapping(path = "/register", method = RequestMethod.POST)
public String register(Model model, User user) {
...
return "";
}
对请求的接收与响应
接收提交表单数据,交给service处理后,返回给表单
- 将接收的user类中表单输入信息交给service层处理后,获取返回的map信息
- 处理map信息,
- 成功,封装激活中间页面需要的model信息:
- 页面显示的提示信息和最终跳转的链接:
- msg:提示注册成功,已经向用户发送了激活邮件,提醒用户尽快激活账户
- target:一定时间后,自动跳转或立即手动跳转的指定链接
- 跳转到激活中间页面
- 失败,封装map错误信息,
- 重新返回注册页面,并显示错误提示信息
// 注册页面表单提交请求
@RequestMapping(path = "/register", method = RequestMethod.POST)
public String register(Model model, User user) {
// 将接收的user类中表单输入信息交给service层处理后,获取返回的map信息
Map<String, Object> falseMap = userService.register(user);
// 前端显示处理这些信息
if(falseMap == null || falseMap.isEmpty()) {
return "/site/operate-result"; // 注册成功跳转到激活提示激活页面
} else {
model.addAttribute("userNameMsg", falseMap.get("userNameMsg"));
model.addAttribute("passwordMsg", falseMap.get("passwordMsg"));
model.addAttribute("emailMsg", falseMap.get("emailMsg"));
return "/site/register"; // 返回注册表单页面
}
}
2.3 View视图模板页面处理
中间提示跳转页面模板
th:href="@{${target}}
- thymeleaf自动识别并动态填入链接的url值
<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>
注册表单页面模板
th:action="@{/register}
- 处理表头:method=“post” th:action="@{/register}
<form class="mt-5" method="post" th:action="@{/register}">
name=“userName”
- 处理表单中的每行标签内容:html中Input标签中,属性名一定要和User类中的属性名一致,
- 这样,Spring才能自动识别并注入数据到类中
th:text="${usernameMsg}"
is-invalid
- 显示错误提示信息,将controller返回的model封装的错误信息渲染处理
- 如果没有msg错误信息,则不显示,Bootstrap前端框架处理是在input标签的class选择器中处理
- 因此需要"|静态 动态|"拼接,显示样式均交给css处理,放在class选择器中
- Bootstrap中判断:is-invalid,输入内容非法,就将错误提示框显示出来,反之不显示
<input type="text"
th:class="|form-control ${usernameMsg != null ? 'is-invalid' : ''}|"
th:value="${user != null ? user.userName : ''}"
id="username" name="userName" placeholder="请输入您的账号!" required>
<div class="invalid-feedback" th:text="${userNameMsg}">
该账号已存在!
</div>
th:value=""
- 默认值的显示,如果反馈注册信息,将之前填入的值自动填入
- 要动态判断,如果第一次访问没有默认值,
- user为null,再调用user的属性方法,会异常,故要判空处理
th:value="${user != null ? user.userName : ''}"
邮件模板页面
- 用thymeleaf模板引擎处理的,因此在service层处理完业务后,就处理模板页面,免得遗忘了
<div>
<p>
<b th:text="${toMsg}">xxx@xxx.com</b>, 您好!
</p>
<p>
您正在注册交流网, 这是一封激活邮件, 请点击
<a th:href="@{${urlMsg}}">此链接</a>,
激活您的社交账号!
</p>
</div>
测试
- 结果显示
3. 激活注册用户
功能需求
- 点击邮件中的链接,访问服务端的激活服务
- 将用户激活状态改变,并在页面显示激活的结果
功能实现
3.1 service层处理激活业务
设计静态常量接口
- 设置项目中的静态常量接口,可以直接使用静态常量来表示激活信号
public interface CommunityConstant {
/**
* 激活成功
*/
int ACTIVATION_SUCCESS = 0;
/**
* 重复激活
*/
int ACTIVATION_REPEAT = 1;
/**
* 激活失败
*/
int ACTIVATION_FAILURE = 2;
}
设计激活业务
- 将从controller接收的用户id从库中查询指定用户
- 根据指定用户的激活码与传入的激活码比对,处理不同结果
- 激活成功:返回成功信号,并将指定用户的状态更新为已激活状态,static=1
- 激活失败:
- 用户已经激活,重复激活
- 激活码不对,不能激活
/**
* 处理用户激活业务
* @param userId 接收Controller解析url中的userID,定位指定用户
* @param code 接收Controller解析url中的激活码
* @return 返回激活状态,用静态变量展示
*/
public int activation(int userId, String code) {
// controller接收的用户id从库中查询指定用户
User user = userMapper.selectById(userId);
// 指定用户的激活码与传入的激活码比对
if (user.getStatus() == 1) { // 重复激活
return ACTIVATION_REPEAT;
} else if (user.getActivationCode().equals(code)) { // 激活码匹配成功
userMapper.updateStatus(userId, 1); // 更新用户状态,激活成功
return ACTIVATION_SUCCESS;
} else { // 激活码不匹配,不能激活
return ACTIVATION_FAILURE;
}
}
3.2 Controller层接收和响应请求
path = “/activate/{userId}/{code}”
- 请求路径:为之前发送到邮件的激活链接url,RestFul风格拼接的字符串
- 请求方式为Get请求,浏览器直接访问POST请求不被支持
@PathVariable(“userId”) int userId
- 解析RestFul风格拼接的字符串中后面拼接的路径名的值
// http://localhost:8080/community/activation/101/code
@RequestMapping(path = "/activate/{userId}/{code}", method = RequestMethod.GET)
public String activation(Model model,
@PathVariable("userId") int userId,
@PathVariable("code") String code) {
...
return "/site/operate-result";
}
请求接收响应处理逻辑
- 接收用户传入的url,并将路径解析,
- 将userId,code解析出来后交给service处理
- 接收service反馈的信息,
- 将信息转为跳转中间页面需要的msg、target封装,交给中间页面渲染显示给用户浏览器
// http://localhost:8080/community/activation/101/code
@RequestMapping(path = "/activate/{userId}/{code}", method = RequestMethod.GET)
public String activation(Model model,
@PathVariable("userId") int userId,
@PathVariable("code") String code) {
// 将userId,code解析出来后交给service处理,
// 接收service反馈的信息
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";
}
3.2 View层处理模板页面
- 主要处理激活成功后要跳转的登录模板页面
- 加上thymeleaf模板引用即可
- 中间跳转模板页面,前面注册时已经处理好了
测试: