注册后端流程
流程如下图所示:
-
1. 用户点击注册提交。根据 URL 映射到具体的 Controller 的具体方法。
-
2. 后台再次判断验证码是否正确,错误则返回注册页面,提示错误。
-
3. 验证码正确后:
- 3.1 将 MD5 随机生成的激活码保存到 Redis 中,key 为 email,value 为激活码,并且设置保存时间为24小时;
- 3.2 将用户的信息封装到 user 对象以后保存到 MySQL 数据库中;
- 3.3 然后给注册用户发送激活邮件,并跳转到注册成功页面。
- 4. 用户激活:
- 4.1 在24小时内激活,跳转到激活成功页面;
- 4.2 在24小时内激活,激活码错误跳转到激活失败页面(比如网页链接上自行修改激活码测试);
- 4.3 超过24小时,Redis 删除用户激活码信息。
- 4.4 超过24小时激活,激活失败,MySQL 删除用户未激活注册信息。
根据流程可知需要用到 Redis 和发送邮件功能,这里先简单介绍下 Redis 及如何开通邮箱的 POP3/SMTP 服务。
Redis 简介
Redis 是一个高性能的 key-value 型数据库。Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis 不仅仅支持简单的 key-value 类型的数据,同时还提供 List、Set、Zset、Hash等数据结构的存储。
Redis 支持数据的备份,即 master-slave 模式的数据备份。
我已将 Redis 压缩包放在了文末的百度网盘链接中,也可自己下载。
Redis 压缩包解压后,进入目录,点击 redis-server.exe,启动 Redis,然后在同目录下启动 redis-cli.exe 从而启动 Redis 客户端,命令如下:
输入:
set name wly
通过:
get name
即可获得存入 name 中的值,如图:
Java 中是通过 Redis 模板操作 Redis 的,pom.xml 中已经导入了依赖包。这里主要将 Spring 和 Redis 的整合配置文件 applicationContext-redis.xml
和 Redis 的配置文件 redis.properties
进行配置。
1.applicationContext-redis.xml 配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="300" />
<property name="maxWaitMillis" value="3000" />
<property name="testOnBorrow" value="true" />
</bean>
<!-- 从外部配置文件获取redis相关信息 -->
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.ip}" />
<property name="port" value="${redis.port}" />
<property name="database" value="${redis.database}" />
<property name="poolConfig" ref="poolConfig"/>
</bean>
<!-- redis模板配置 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="redisConnectionFactory"></property>
<!-- 对于中文的存储 需要进行序列化操作存储 -->
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer">
</bean>
</property>
</bean>
</beans>
代码解读如下:
(1)连接池的配置。
获取最大空闲连接数:
<property name="maxIdle" value="300" />
获取连接时的最大等待毫秒数:
<property name="maxWaitMillis" value="3000" />
在获取连接的时候检查有效性:
<property name="testOnBorrow" value="true" />
(2)Redis 连接工厂配置。
主机名:
<property name="hostName" value="${redis.ip}" />
端口号:
<property name="port" value="${redis.port}" />
选用的数据库:
<property name="database" value="${redis.database}" />
连接池名称,引用上面配置的连接池:
<property name="poolConfig" ref="poolConfig"/>
(3)redisTemplate 模板配置,主要是将 Redis 模板交给 Spring 管理、引入上面配置的 Redis 连接工厂,对中文存储进行序列化操作等。
2.redis.properties 配置如下:
redis.pool.maxActive=1024
redis.pool.maxIdle=200
redis.pool.maxWait=1000
redis.ip=localhost
redis.port=6379
redis.database=2
redis.properties 就是属性配置文件,applicationContext-redis.xm
中连接池的配置就是从这里取的值,其中 redis.database=2
代表选用第二个数据库。
3.Redis 相关配置文件配置好以后,引入项目中:
web.xml中引入applicationContext-redis.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:spring-mybatis.xml,
classpath*:applicationContext-redis.xml
</param-value>
</context-param>
spring-mybatis.xml 中引入 redis.properties:
<bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
<value>classpath:redis.properties</value>
</list>
</property>
<property name="fileEncoding" value="UTF-8"/>
</bean>
classpath 表示只会到你的 class 路径中查找文件。
classpath*
:不仅在 class 路径,还会在 jar 文件中(class 路径)进行查找。
所以 classpath*
的加载速度要慢,通常情况下使用 classpath,找不到文件的情况时才使用 classpath*
。
开通邮箱的 POP3/SMTP 服务
这里以网易163邮箱为例。
登入163邮箱后,点击设置中的 POP3/SMTP/IMAP
开通 POP3/SMTP
服务,然后点击客户端授权密码,获取一个授权密码,之后的发送邮件功能中需要用到,如下图:
项目中还会用到 MD5 加密,在 common 包下引入 MD5Util 工具类,该工具类我也放在了百度网盘链接中。
注册后台代码
在 RegisterController.java 中创建映射 URL 为 /doRegister
的方法:
@Autowired// redis数据库操作模板
private RedisTemplate<String, String> redisTemplate;
@RequestMapping("/doRegister")
public String doRegister(Model model, @RequestParam(value = "email", required = false) String email,
@RequestParam(value = "password", required = false) String password,
@RequestParam(value = "phone", required = false) String phone,
@RequestParam(value = "nickName", required = false) String nickname,
@RequestParam(value = "code", required = false) String code) {
log.debug("注册...");
if (StringUtils.isBlank(code)) {
model.addAttribute("error", "非法注册,请重新注册!");
return "../register";
}
int b = checkValidateCode(code);
if (b == -1) {
model.addAttribute("error", "验证码超时,请重新注册!");
return "../register";
} else if (b == 0) {
model.addAttribute("error", "验证码不正确,请重新输入!");
return "../register";
}
User user = userService.findByEmail(email);
if (user != null) {
model.addAttribute("error", "该用户已经被注册!");
return "../register";
} else {
user = new User();
user.setNickName(nickname);
user.setPassword(MD5Util.encodeToHex("salt"+password));
user.setPhone(phone);
user.setEmail(email);
user.setState("0");
user.setEnable("0");
user.setImgUrl("/images/icon_m.jpg");
//邮件激活码
String validateCode = MD5Util.encodeToHex("salt"+email + password);
redisTemplate.opsForValue().set(email, validateCode, 24, TimeUnit.HOURS);// 24小时 有效激活 redis保存激活码
userService.regist(user);
log.info("注册成功");
SendEmail.sendEmailMessage(email, validateCode);
String message = email + "," + validateCode;
model.addAttribute("message", message);
return "/regist/registerSuccess";
}
}
// 匹对验证码的正确性
public int checkValidateCode(String code) {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
Object vercode = attrs.getRequest().getSession().getAttribute("VERCODE_KEY");
if (null == vercode) {
return -1;
}
if (!code.equalsIgnoreCase(vercode.toString())) {
return 0;
}
return 1;
}
代码解读如下:
(1)通过 @Autowired
注解注入 RedisTemplate 模板,用于操作 Redis。
(2)判断验证码是否为空,如果为空,通过 model.addAttribute
方法将错误信息添加到 model 中,前台可通过键“error”获取对应的值,并返回到注册页面。
这里return "../register"
是根据之前 spring-mvc.xml 中配置的规则转发的,如下:
<!-- 定义跳转的文件的前后缀 ,视图模式配置 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/" />
<property name="suffix" value=".jsp"/>
</bean>
前缀为/WEB-INF/
,../
代表退回到上一层,即到了 /webapp/register
下,后缀是.jsp
。
那么最终结果就是 return 到了 /webapp/register.jsp
注册页面。
(3)这里封装了匹配验证码正确性的方法 checkValidateCode,如果 Session 中不存在验证码,则返回-1,提示超时错误,如果存在验证码但是和前台传入的验证码不一致则返回0,提示验证码错误,否则返回1,验证码正确,不提示。
(4)根据前台传来的 email 查询用户是否存在,如果存在说明已经注册,否则给予错误提示,返回注册页面。
(5)用户不存在,这里新创建一个 User,将前台传来的信息赋值给 User,通过 MD5 工具类对密码进行加密,其中 salt 用来增加密码的复杂度,将 state 和 enable 赋初始值0,state 用于用户的激活,激活后变为1,enable 属性以后会说,给用户设置一个默认的头像。
(6)通过 MD5 加密工具生成唯一的邮件激活码赋值给 validateCode。
(7)通过 redisTemplate.opsForValue().set
方法将激活码 validateCode 存入 Redis,以 email 为键,validateCode 为值,设置有效时间为24,单位为小时,即激活码时效为一天。
(8)调用 userService 的 regist 方法将用户插入数据库。
(9)调用发送邮件的方法发送邮件,参数是发送邮件的目的地和激活码。
(10)将邮箱和激活码用 ,
号拼接后赋值给 message,将 message 添加到 model 中。
(11)返回注册成功页面 registerSuccess.jsp。
发送邮件类
在 wang.dreamland.www 路径下新建 mail 包并引入发送邮件的相关类,mail 包我也放在了文末的百度网盘链接中,主要有三个类:
- MailExample.java 用于测试;
- MyAuthenticator.java 用于验证邮箱和授权码的正确性;
- SendEmail.java 用于发送邮件的,主要注意将发件人的邮箱改成你的邮箱,还有输入你的授权码。
注意: 启动项目前如果 Redis 没有启动,请先启动 redis-server.exe。
重新启动项目,注册一个账号,注册成功后跳转到注册成功页面,如下图:
激活流程
1.点击立即查看邮箱,可根据不同邮箱跳转到不同邮箱主页。
<div class="register-active">
<span style="font-size: 15px;font-weight: bold;padding: 20px;line-height: 100px">激活邮件已经发送到您的注册邮箱${message.split(",")[0]},点击邮件里的链接即可激活账号。</span><br/>
<button style="margin-left: 20px;" class="btn btn-primary" type="button" onclick="lookEmail('${message}');">立即查看邮件</button>
</div>
根据键 message 取出后台存入 model 中的值,然后根据 ,
号切割,取第一个元素就是邮箱了。
查看邮箱的点击事件 lookEmail 方法如下:
function lookEmail(message) {
var arr = message.split(",");
var email = arr[0];
var opt = email.split("@")[1];
if("qq.com"==opt){
location.href = "https://mail.qq.com/";
}else if("163.com"==opt){
location.href = "https://mail.163.com/";
}else if("162.com"==opt){
location.href = "https://mail.162.com/";
}else if("sina.com"==opt){
location.href = "http://mail.sina.com.cn/";
}else if("sohu"==opt){
location.href = "https://mail.sohu.com";
}
}
代码解读如下:
(1)参数 message 是邮箱和激活码通过 ,
号拼接的字符串,根据 ,
号切割取出邮箱。
(2)根据 @
切割取出邮箱 @
后面部分,主要根据 @
后面的信息进行跳转。
(3)如果后面部分是 qq.com 则链接到 QQ 邮箱,其他同理。
2.点击重新发送邮件,可重新发送激活邮件。
onclick="reSendEmail('${message}')"
点击事件 reSendEmail 方法如下:
function reSendEmail(message) {
var arr = message.split(",");
var email = arr[0];
var code = arr[1];
$.ajax({
type:'post',
url:'/sendEmail',
data: {"email":email,"validateCode":code},
dataType:'json',
success:function(data){
//alert(data["success"])
var s = data["success"];
if(s=="success"){
alert("发送成功!");
}
}
});
}
代码解读如下:
(1)通过 ,
号切割分别拿到邮箱和激活码。
(2)发送 AJAX 请求,映射 URL 为 /sendEmail
,请求参数 data 是邮箱和激活码。
(3)success 回调函数返回后台处理结果。如果是“success”代表发送成功,给予提示。
后台 RegisterController.java 中映射 URL 为 /sendEmail
的方法如下:
@RequestMapping("/sendEmail")
@ResponseBody
public Map<String,Object> sendEmail(Model model) {
Map map = new HashMap<String,Object>( );
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String validateCode = attrs.getRequest().getParameter( "validateCode" );
String email = attrs.getRequest().getParameter( "email" );
SendEmail.sendEmailMessage(email,validateCode);
map.put( "success","success" );
return map;
}
代码解读如下:
(1)这里我使用的是另一种获取参数的方式,通过 request.getParameter() 方法也可以获取前台传来的参数,参数是要获取的参数名称。
(2)调用发送邮件的方法即可,然后将“success”放入 map 集合,返回给前台。
3.重新注册,跳转到注册页面。
点击事件 reRegist 方法如下:
function reRegist() {
location.href = "../register.jsp";
}
通过 location.href 属性链接到 register.jsp 页面。
也可以通过后台 Controller 跳转:
function reRegist() {
location.href = "${ctx}/register";
}
RegisterController.java 写上对应 URL 的方法:
@RequestMapping("/register")
public String register(Model model) {
log.info("进入注册页面");
return "../register";
}
4.进入邮箱进行激活。
(1)登录邮箱打开收到的激活邮件,点击“请于24小时内点击激活”链接:
点击后获得的链接也就是我们发送邮件方法里面写的链接:
message.setContent( "<a href=\"http://localhost:8080/activecode?email="+email+"&validateCode="+validateCode+"\" target=\"_blank\">请于24小时内点击激活</a>","text/html;charset=gb2312");
在 RegisterController.java 创建映射 URL 为 /activecode
的方法如下:
@Autowired
private RoleUserService roleUserService;
@RequestMapping("/activecode")
public String active(Model model) {
log.info( "==============激活验证==================" );
//判断 激活有无过期 是否正确
//validateCode=
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String validateCode = attrs.getRequest().getParameter( "validateCode" );
String email = attrs.getRequest().getParameter( "email" );
String code = redisTemplate.opsForValue().get( email );
log.info( "验证邮箱为:"+email+",邮箱激活码为:"+code+",用户链接的激活码为:"+validateCode );
//判断是否已激活
User userTrue = userService.findByEmail( email );
if(userTrue!=null && "1".equals( userTrue.getState() )){
//已激活
model.addAttribute( "success","您已激活,请直接登录!" );
return "../login";
}
if(code==null){
//激活码过期
model.addAttribute( "fail","您的激活码已过期,请重新注册!" );
userService.deleteByEmail( email );
return "/regist/activeFail";
}
if(StringUtils.isNotBlank( validateCode ) && validateCode.equals( code )){
//激活码正确
userTrue.setEnable( "1" );
userTrue.setState( "1" );
userService.update( userTrue );
model.addAttribute( "email",email );
return "/regist/activeSuccess";
}else {
//激活码错误
model.addAttribute( "fail","您的激活码错误,请重新激活!" );
return "/regist/activeFail";
}
}
代码解读如下:
A. 这里通过 request.getParameter 方法获取请求参数:邮箱和激活码。
B. 通过键 email 去 Redis 中取出之前存入 Redis 中的激活码,赋值给 code。
C. 根据邮箱查询用户,如果用户不为 null 并且激活状态 state=1
说明该用户已经激活,将“success”添加到 model 中并跳转到登录页面。
D. 如果从 Redis 中取出的激活码为 null,说明激活码已经超过24小时,将“fail”添加到 model 中,然后 根据用户 email 删除用户,返回注册页面。
E. 如果前台传来的激活码不为空并且和 Redis 中取出的激活码相同说明激活码正确,更新用户激活状态 state 和 enable 为1,将 email 添加到 model 中,返回到激活成功页面,否则将“fail”添加到 model 中并返回到激活失败页面。
重新启动项目,注册 -> 注册成功 -> 进入邮箱点击激活链接。
(2)激活成功页面效果如下:
(3)激活失败页面效果如下:
注意:
激活成功后,发现数据库中 state 和 enable 字段并未更新,因为 User 实体类的 id 我们没有给它标识为主键,所以没有根据主键来更新字段。
在 id 字段上加上以下注解:
@Id//标识主键
@GeneratedValue(strategy = GenerationType.IDENTITY) //自增长策略
顺便将其他实体类一并加上这两个注解,然后再启动项目,点击激活链接,查看数据库,字段已更新!
-------------
這一段,純抄,不會就是這麽乾脆。