项目4-注册
- 访问注册页面
- 点击顶部区域内的链接,打开注册页面。
- 提交注册数据
- 通过表单提交数据。
- 服务端验证账号是否已存在、邮箱是否已注册。
- 服务端发送激活邮件。
- 激活注册账号
- 点击邮件中的链接,访问服务端的激活服务
1. 访问注册页面
要求:点击顶部区域内的链接,打开注册页面。
因为访问页面只需要controller返回HTML页面即可,因此并不需要service层。
同时为了代码的复用将index的头部抽离出来,在register.html使用。
index.html
<header class="bg-dark sticky-top" th:fragment="header">
register.html
<!-- 头部 -->
<header class="bg-dark sticky-top" th:replace="index::header">
将index.html的首页和注册页面的超链接利用theafleaf进行管理
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" th:href="@{/index}">首页</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" th:href="@{/register}">注册</a>
</li>
LoginController
package com.guo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
//登录注册页面
@Controller
public class LoginController {
@RequestMapping(path = "/register", method = RequestMethod.GET)
public String getRegisterPage(){
return "/site/register";
}
}
此时,我们就可在首页点击注册跳转至注册页面。
2. 提交注册数据
首先导入一个常用的包 commons lang3
commons-lang3 是Apache提供的一个java.lang包的增强版本,Lang3为java.lang API提供了许多帮助程序实用程序,特别是字符串操作方法,基本数值方法,对象反射,并发,创建和序列化以及系统属性。此外,它还包含对java.util.Date的基本增强,以及一系列专用于构建方法的实用程序,例如hashCode,toString和equals。
- 主要是字符串判空等功能
在判断一个字符串是否为空时,有以下几种情况比较特殊
- 是否为"";
- 是否为“ ”;
- 是否为null
StringUtils的isBlank()方法可以一次性校验这三种情况,返回值都是true
StringUtils的isEmpty()方法
public static boolean isEmpty(CharSequence cs):null和空串都被定义为empty
StringUtils.isEmpty(null) = true
StringUtils.isEmpty("") = true
StringUtils.isEmpty(" ") = false //注意这里是false
StringUtils.isEmpty("java") = false
StringUtils.isEmpty(" java ") = false
public static boolean isBlank(CharSequence cs)
判断字符对象是不是空字符串
StringUtils.isBlank(null) = true
StringUtils.isBlank("") = true
StringUtils.isBlank(" ") = true //注意此处是null哦 这和isEmpty不一样的
StringUtils.isBlank("bob") = false
StringUtils.isBlank(" bob ") = false
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
添加配置信息
因为开发、上线域名不一样所以用户打开邮箱激活的链接也不一样所以给他弄成可变的
#community
community.path.domain=http://localhost:8080
UUID
UUID(Universally Unique Identifier):通用唯一识别码,是一种软件建构的标准。
-
UUID由以下几部分组合:
- 当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
- 时钟序列。
- 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
-
UUID的唯一缺陷在于生成的结果串会比较长。
标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)。32位
-
UUID.randomUUID()使用
直接生成的ID中有“-”存在,如果不需要,可以使用replace()方法去掉。
MD5
MD5消息摘要算法,属Hash算法一类。MD5算法对输入任意长度的消息进行运行,产生一个128位的消息摘要(32位的数字字母混合码)。
MD5主要特点:
不可逆,相同数据的MD5值肯定一样,不同数据的MD5值不一样
MD5的性质:
-
压缩性:任意长度的数据,算出的MD5值长度都是固定的(相当于超损压缩)。
-
容易计算:从原数据计算出MD5值很容易。
-
抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
-
弱抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
-
强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
虽说MD5有不可逆的特点
但是由于某些MD5破解网站,专门用来查询MD5码,其通过把常用的密码先MD5处理,并将数据存储起来,然后跟需要查询的MD5结果匹配,这时就有可能通过匹配的MD5得到明文,所以有些简单的MD5码是反查到加密前原文的。
为了让MD5码更加安全,涌现了很多其他方法,如加盐。 盐要足够长足够乱得到的MD5码就很难查到。
MD5用途:
- 防止被篡改:
1)比如发送一个电子文档,发送前,我先得到MD5的输出结果a。然后在对方收到电子文档后,对方也得到一个MD5的输出结果b。如果a与b一样就代表中途未被篡改。
2)比如我提供文件下载,为了防止不法分子在安装程序中添加木马,我可以在网站上公布由安装文件得到的MD5输出结果。
3)SVN在检测文件是否在CheckOut后被修改过,也是用到了MD5.
- 防止直接看到明文:
现在很多网站在数据库存储用户的密码的时候都是存储用户密码的MD5值。这样就算不法分子得到数据库的用户密码的MD5值,也无法知道用户的密码。(比如在UNIX系统中用户的密码就是以MD5(或其它类似的算法)经加密后存储在文件系统中。当用户登录的时候,系统把用户输入的密码计算成MD5值,然后再去和保存在文件系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这不但可以避免用户的密码被具有系统管理员权限的用户知道,而且还在一定程度上增加了密码被破解的难度。)
- 防止抵赖(数字签名):
这需要一个第三方认证机构。例如A写了一个文件,认证机构对此文件用MD5算法产生摘要信息并做好记录。若以后A说这文件不是他写的,权威机构只需对此文件重新产生摘要信息,然后跟记录在册的摘要信息进行比对,相同的话,就证明是A写的了。这就是所谓的“数字签名”。
MD5加密算法原理及实现:
MD5算法原理:
1、数据填充
对消息进行数据填充,使消息的长度对512取模得448,设消息长度为X,即满足X mod 512=448。根据此公式得出需要填充的数据长度。
填充方法:在消息后面进行填充,填充第一位为1,其余为0。
(此时消息长度为N*512+448)
2、添加消息长度
在第一步结果之后再填充上原消息的长度,可用来进行的存储长度为64位。如果消息长度大于264,则只使用其低64位的值,即(消息长度 对 264取模)。
在此步骤进行完毕后,最终消息长度就是512的整数倍。
(此时消息长度为(N+1)*512 )
3、数据处理
4、MD5运算:
由类似的64次循环构成,分成4轮,每轮16次。
写个工具类
- 生成随机字符串:用于验证码以及上传文件的名称
- MD5加密:MD5只能加密不能解密,通知由于密码有可能较为简单,需要将原串[原密码]加salt拼接新串加密防破解
package com.guo.util;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.DigestUtils;
import java.util.UUID;
//常用方法的工具类
public class CommunityUtil {
//1.生成随机字符串:用于验证码或者文件名称
public static String generateUUID(){
return UUID.randomUUID().toString().replace("-",""); //将-进行替换
}
//2.MD5加密算法:原字符串+salt 再进行加密
public static String md5(String key){
//当字符串为空时,不进行加密
if (StringUtils.isBlank(key)){
return null;
}
return DigestUtils.md5DigestAsHex(key.getBytes());
}
}
正式开发功能
1.Dao已经写完不弄了
2.弄Service
在userService中添加如下代码
package com.guo.service;
import com.guo.dao.UserMapper;
import com.guo.entity.User;
import com.guo.util.CommunityContant;
import com.guo.util.CommunityUtil;
import com.guo.util.MailClient;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@Service
public class UserService implements CommunityContant {
@Autowired
private UserMapper userMapper;
@Autowired
private MailClient mailClient;
@Autowired
TemplateEngine templateEngine;
@Value("${community.path.domain}")
private String domain; //域名
@Value("${server.servlet.context-path}")
private String contextPath; //项目名
public User findUserById(int id) {
return userMapper.selectById(id);
}
//处理用户注册的方法
//存入用户的信息
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","用户名不能为空");
}
//对密码进行判断
if (StringUtils.isBlank(user.getPassword())) {
//因为这不是异常需要返回给客户端
map.put("passwordMsg","密码不能为空");
}
//对邮箱进行判断
if (StringUtils.isBlank(user.getEmail())) {
//因为这不是异常需要返回给客户端
map.put("emailMsg","邮箱不能为空");
}
//对用户名进行判断,不能存在同名用户
User u = userMapper.selectByName(user.getUsername());
if (u != null) {
//因为这不是异常需要返回给客户端
map.put("usernameMsg","该账号已存在");
}
//对邮箱进行判断,不能存在同个邮箱的用户
u = userMapper.selectByEmail(user.getEmail());
if (u != null) {
//因为这不是异常需要返回给客户端
map.put("emailMsg","该邮箱已被注册");
}
//这时,已经判断该用户可以被注册
//设置salt
user.setSalt(CommunityUtil.generateUUID().substring(0,5));
//将密码进行加密后存到数据库
user.setPassword(CommunityUtil.md5(user.getPassword())+user.getSalt());
//'0-普通用户; 1-超级管理员; 2-版主;'
user.setType(0);
//'0-未激活; 1-已激活;'
user.setStatus(0);
//设置激活码
user.setActivationCode(CommunityUtil.generateUUID());
//头像地址0-1000
user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
//创建时间
user.setCreateTime(new Date());
//将用户添加至数据库
userMapper.insertUser(user);
//添加用户成功后,给用户发送邮件,让用户去激活账号
Context context = new Context();
context.setVariable("email",user.getEmail());
//返回给用户的网址链接为http://localhost:8080/community/activation/101/code
//#101-用户id,#code-激活码
String url = domain+contextPath+"/activation/"+user.getId()+"/"+user.getActivationCode();
context.setVariable("url", url);
//利用templateEngine发送HTML邮件
String html = templateEngine.process("/mail/activation", context);
mailClient.sendMail(user.getEmail(), "激活账号", html);
return map;
}
}
3.邮件模板内容
mail目录下activation.html
<!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="${url}">此链接</a>,
激活您的账号!
</p>
</div>
</body>
</html>
4.写controller
在LoginController类中增加如下内容
package com.guo.controller;
import com.guo.entity.User;
import com.guo.service.UserService;
import com.guo.util.CommunityContant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Map;
//登录注册页面
@Controller
public class LoginController implements CommunityContant {
//controller调用service
@Autowired
private UserService userService;
//这是访问注册页面的请求,方法为get
@RequestMapping(path = "/register",method = RequestMethod.GET)
public String getRegisterPage(){
return "/site/register";
}
@RequestMapping(path = "/login", method = RequestMethod.GET)
public String getLoginPage(Model model){
return "/site/login";
}
//当提交注册表单时的方法
@RequestMapping(path = "/register", method = RequestMethod.POST)
public String register(Model model, User user){
//从表单中获取user信息,再调用service的方法获取注册的相关信息
Map<String, Object> map = userService.register(user);
if (map == null || map.isEmpty()){
//当map中没有数据时表明用户注册成功,返回操作结果页面
model.addAttribute("msg","注册成功,我们已经向您的邮箱发送一封邮件,请查收并激活账号");
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";
}
}
}
register.html的内容部分进行修改
<!-- 内容 -->
<!--为了在用户注册失败时,能保留上次输入的内容,进行修改
th:class="|form-control| ${usernameMsg != null ? 'is-invalid':''}"
当usernameMsg不为空时,才显示具体的错误内容
th:value="${user!=null?user.username:''}"
保留上次输入的内容
-->
<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" method="post" th:action="@{/register}">
<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':''}|"
th:value="${user!=null?user.username:''}"
id="username" name="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':''}|"
th:value="${user!=null?user.password:''}"
id="password" name="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':''}|"
th:value="${user!=null?user.email:''}"
id="email" name="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>
6.注册成功的中间页面
site/operate-result.html
<!-- 内容 -->
<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>
3. 激活注册账号
- util包中定义几个激活状态常量
让UserService实现此接口
package com.hsw.community.util;
public interface CommunityContant {
//激活成功
int ACTIVATION_SUCCESS = 0;
//重复激活
int ACTIVATION_REPEAT = 1;
//激活失败
int ACTIVATION_FAILURE = 2;
}
业务层中UserService类增加新方法
//增加激活的方法:用户id和激活码
public int activation(int userId, String code) {
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;
}
}
LoginController类
//增加激活的方法
//url规定这么搞:http://localhost:8080/community/activation/101/code #101-用户id,#code-激活码
@RequestMapping(path="/activation/{userId}/{code}", method = RequestMethod.GET)
public String activation(Model model,
@PathVariable("userId")int userId, //@PathVariable从路径中取值
@PathVariable("code") String code){
//调用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","/login");
}else{
model.addAttribute("msg","激活失败,激活码不正确请重新注册");
model.addAttribute("target","/register");
}
return "site/operate-result";
}
此时,我们已经完成了简单的注册功能!!!