一文教你学会实现以邮件激活的注册账户代码

👏 Hi! 我是 Yumuing,一个技术的敲钟人

👨‍💻 每天分享技术文章,永远做技术的朝拜者

📚 欢迎关注我的博客:Yumuing’s blog

实现思路

通常,我们在进行各大平台进行注册账户时,都会在邮箱收到一封激活邮件,而在点击其中的激活链接之后,我们就能够激活账户,否则,我们将无法正常使用账户,这使得服务平台所拥有的激活用户的邮件信息真实性有了保证。如果为了服务平台的长久运行,这种激活方式必不可少。为了让大家更为理解这种激活方式,博主绘制了一份关于该种激活方式的时序图,可能存在一部分错漏,也希望能够收到大家的指正!以邮件激活的注册方式相关时序图如下:

在这份时序图中,我们可以看出,这种激活方式主要分为三大阶段,其相关的步骤信息如下:

访问注册页面

用户访问注册页面

后端返回动态页面数据

前端显示注册页面

提交注册数据

用户填写注册信息

前端校验注册信息格式,正确则发送注册数据给后端,否则要求用户填写注册数据直至格式正确

后端接收到注册数据,查询数据库信息,检验该注册数据中的用户名、邮箱信息是否已被注册过,如果没有被注册,生成账户基本状态信息,包括密码进行 MD5 加盐加密、生成账户激活码,之后,便是发送给注册数据中的邮箱一个独有激活链接,包含账户 ID 信息和激活码信息,否则发送给前端哪个数据已被注册,要求用户重新填写,提交数据,回到第二步。

激活用户账号

用户收到邮件,查看并点击激活邮件中的激活链接,自动跳转到默认浏览器,并由前端(浏览器)发送激活请求给予后端

后端查询对应 userID 信息,如果不存在,返回账户不存在信息,如果已被激活,发送重复激活信息,如果激活码错误,返回激活码不正确信息,最后返回中转页面八秒后跳转的页面地址:主页。如果激活成功,返回激活成功信息以及八秒后跳转的页面地址:登录。

前端接收到后端返回数据,显示中转页面信息,并在八秒后跳转到指定地址。

这便是实现以邮件激活的注册方式的基本实现思路,相信大家基本了解该如何去实现这样一个注册方式,接下来,博主将在 SpringBoot、Thymeleaf 以及 Mybatis 的运行环境下实现这种注册方式。

SpringBoot 实现

编写用户数据:用户包括用户 ID 、密码,加盐值、邮箱、激活状态、用户类型(用户身份:0-普通用户; 1-超级管理员; 2-版主)、用户激活状态、激活码、头像地址、创建时间。建表语句如下:

DROP TABLE IF EXISTS `user`;

/*!40101 SET @saved_cs_client = @@character_set_client */;

SET character_set_client = utf8mb4 ;

CREATE TABLE `user` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`username` varchar(50) DEFAULT NULL,

`password` varchar(50) DEFAULT NULL,

`salt` varchar(50) DEFAULT NULL,

`email` varchar(100) DEFAULT NULL,

`type` int(11) DEFAULT NULL COMMENT '0-普通用户; 1-超级管理员; 2-版主;',

`status` int(11) DEFAULT NULL COMMENT '0-未激活; 1-已激活;',

`activation_code` varchar(100) DEFAULT NULL,

`header_url` varchar(200) DEFAULT NULL,

`create_time` timestamp NULL DEFAULT NULL,

PRIMARY KEY (`id`),

KEY `index_username` (`username`(20)),

KEY `index_email` (`email`(20))

) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;

利用 Mybatis 生成 user.java、mapper.xml、mapper.java、userService.java、userServiceImpl.java 文件,并实现以下查询:

selectOneById(@Param(“id”) int id):以 ID 查询对应 user

selectOneByUsername(@Param(“username”) String username):以 userName 查询对应 user

selectOneByEmail(@Param(“email”) String email):以 Email 查询对应 user

insertAll(User user):插入对应 user

updateStatusById(@Param(“status”) Integer status, @Param(“id”) Integer id):更新对应 ID 的 user 所拥有的激活状态

id,username,password,

salt,email,type,

status,activation_code,header_url,

create_time

insert into user

(id, username, password,

salt, email, type,

status, activation_code, header_url,

create_time)

values (#{id,jdbcType=NUMERIC}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR},

#{salt,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{type,jdbcType=NUMERIC},

#{status,jdbcType=NUMERIC}, #{activationCode,jdbcType=VARCHAR}, #{headerUrl,jdbcType=VARCHAR},

#{createTime,jdbcType=TIMESTAMP})

update user

set status = #{status,jdbcType=NUMERIC}

where id = #{id,jdbcType=NUMERIC}

编写对应注册 html 代码,注意采用 post 提交表单,提交接口为 /register ,并且,为了保证重复填写注册信息,再进行验证的过程中,重复信息不用重复填写,使用th:value="${user!=null?user.username:''}"保证信息验证不通过后,输入框信息不变,进行修改信息直至正确即可。th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"接受验证信息,通过即用户不存在,不显示错误,否则显示错误,代码如下:

注  册

th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"

th:value="${user!=null?user.username:''}"

id="username" name="username" placeholder="请输入您的账号!" required>

该账号已存在!

th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|"

th:value="${user!=null?user.password:''}"

id="password" name="password" placeholder="请输入您的密码!" required>

密码长度不能小于8位!

th:value="${user!=null?user.password:''}"

id="confirm-password" placeholder="请再次输入密码!" required>

两次输入的密码不一致!

th:class="|form-control ${emailMsg!=null?'is-invalid':''}|"

th:value="${user!=null?user.email:''}"

id="email" name="email" placeholder="请输入您的邮箱!" required>

该邮箱已注册!

实现返回注册页面逻辑,用户访问时,使用的是 Get 请求,可与后续提交注册数据的 post 请求区别开,所以访问页面方法和验证注册数据方法都采用同一个路径:/register。返回主页、登录、注册代码类似,仅展示一种,代码如下:

// 注册页面

@RequestMapping(path = "/register", method = RequestMethod.GET)

public String getRegisterPage() {

return "/site/register";

}

完整代码可见:一文详解以邮件激活的注册方式 文章

第一阶段以解决,让我们开始实现第二阶段:提交注册数据

首先,我们得先实现三个工具类:

MailClient.java:发送邮件工具类

使用 Jakarta Mail 实现邮件发送操作

注入所需对象:邮件发送操作方法 JavaMailSender javaMailSender、邮件发送目标@Value("${spring.mail.username}")String mailFrom

创建邮件内容保存对象:MimeMessageHelper mimeMessageHelper

写入信息

// 发送主体

mimeMessageHelper.setFrom(mailFrom);

// 发送对象

mimeMessageHelper.setTo(to);

// 发送主题

mimeMessageHelper.setSubject(subject);

// 允许发送 html

mimeMessageHelper.setText(content, true);

发送邮件:javaMailSender.send(mimeMessageHelper.getMimeMessage());

捕获异常:

catch (MessagingException e){

logger.error("发送邮件失败!");

}

遇到相关 BUG 可看:springboot3 解决:Could not autowire. No beans of ‘JavaMailSender‘ type found

CommunityUtil.java:随机字符串、MD5 加盐加密工具方法

使用 java.util.UUID 的随机生成字符串功能,实现随机字符串生成:从生成的随机字符串中去除"-"符号

public static String generateUUID(){

return UUID.randomUUID().toString().replaceAll("-","");

}

MD5 加盐加密的原理就是密码都加上随机字符串,再进行 MD5 加密,提高安全性,因为 MD5 都不可解密,只能用 MD5 进行加密,并且每次加密后的字符一致,而简单密码的 MD5 加密后的字符容易获取,暴力破解密码存在可能,必须再进行加盐。

public static String md5(String key){

if (StringUtils.isBlank(key)){

return null;

}

return DigestUtils.md5DigestAsHex(key.getBytes(StandardCharsets.UTF_8));

}

CommunityConstant.java:记录激活状态常量,为抽象接口

// 激活成功

int ACTIVATION_SUCCESS = 0;

// 重复激活

int ACTIVATION_REPEAT = 1;

// 激活失败

int ACTIVATION_FAILURE = 2;

// 激活用户不存在

int ACTIVATION_NULL = 3;

利用以上工具类和查询代码,我们就可以实现校验注册数据是否已被注册了。

利用 commons.lang3 包内的工具类帮助我们实现校验注册数据

利用 HashMap 来存储后续返回给前端的校验错误信息,如果含有错误信息,前端报错,map 为空,则校验通过

校验注册信息分为空值校验、验证用户名是否存在、验证邮箱是否存在

空值校验主要是防止可能存在前端没有进行合理格式校验,或者绕过前端直接通过接口进行请求的情况,此方法较为简单,不展示。

验证用户名是否存在:通过 selectOneByUsername(username) 方法查询对应用户,不存在则开始进行邮箱验证是否存在,否则,存入 map 错误信息

验证邮箱是否存在:通过 selectOneByEmail(email) 方法查询对应用户,不存在则开始注册用户流程,否则,存入 map 错误信息

注册用户:将密码进行加盐加密,并构建用户基本状态,如:用户类型为普通用户、用户创建时间为XXXX,用户头像为随机头像,之后将激活状态设置为未激活,生成并设置账户激活码

发送激活邮件:将用户 ID 和激活码拼接成激活链接,并利用 MailClient.java 工具类发送激活邮件即可

Map 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 ExistedUser = userMapper.selectOneByUsername(user.getUsername());

System.out.println(ExistedUser != null);

if (ExistedUser != null){

map.put("usernameMsg","该账号已存在");

return map;

}

// 验证邮箱是否存在

ExistedUser = userMapper.selectOneByEmail(user.getEmail());

if (ExistedUser!=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());

user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png)",new Random().nextInt(1000)));

user.setCreateTime(new Date());

userMapper.insertAll(user);

// 发送激活邮件

Context context = new Context();

context.setVariable("email",user.getEmail());

// http://localhost:8888/activation/userId(变量)/激活码(变量)

String url = domain + "/activation/" + user.getId() + "/" + user.getActivationCode();

System.out.println(url);

context.setVariable("url",url);

String content = templateEngine.process("/mail/activation",context);

mailClient.sendMail(user.getEmail(),"激活账号",content);

return map;

前端部分代码较为繁琐,庞大,完整代码可见:一文详解以邮件激活的注册方式 文章资源

接下来,就是最后一个阶段的编写:激活邮件,其实,也很简单,用户点击激活邮件后,会自动跳转到浏览器并自动发送 Get 请求,后端收到请求之后,就要验证 userID 和激活码信息,验证无误后,发送给前端正确信息,并修改数据库中的用户状态为激活即可,否则,就要返回前端错误信息。

为了提高代码可读性,采用验证激活数据常量来辅助代码编写,它们存储在 CommunityConstant.java 中,需要使用 extends CommunityConstant 来让 UserService 继承它,再进行激活操作。

@RequestMapping(path = "/activation/{userId}/{code}",method = RequestMethod.GET)

public String activation(Model model, @PathVariable("userId") int userId, @PathVariable("code") String code){

int result = userServiceImpl.activation(userId,code);

System.out.println("激活返回结果为:"+result);

if (result == ACTIVATION_NULL){

model.addAttribute("msg","无效操作!该账号不存在!");

model.addAttribute("target","/index");

} else 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";

}

前端部分代码较为繁琐,庞大,完整代码可见:一文详解以邮件激活的注册方式 文章资源