用户注册时要发送验证码到邮箱,以此验证邮箱是否有效。因此该服务分为两部分,首先是发送邮箱验证码,并放入Redis缓存,其次是新增邮箱账户信息。

邮箱验证码

接口信息

使用字节流的方式写回前端

路径地址 http://localhost:7090/api/sendEmailCode
请求方式 POST
请求参数 email、checkCode、type
返回结果

定义Mapper

将验证码置为无效

EmailCodeMapper
public interface EmailCodeMapper extends BaseMapper<EmailCode> {

void disableEmailCode(@Param("email") String email);
}

在 EmailCodeMapper.xml 中实现接口,使用乐观锁

EmailCodeMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.swx.easypan.mapper.EmailCodeMapper">
<select id="disableEmailCode">
update email_code
set status = 1
where email = #{email}
and status = 0
</select>
</mapper>

定义Service

找到 EmailCodeService,定义发送验证码方法:

public interface EmailCodeService extends IService<EmailCode> {
void sendEmailCode(String email, Integer type);
}

在 EmailCodeServiceImpl 实现该方法,发送的同时将验证码写入数据库

EmailCodeServiceImpl
/**
* <p>
* 邮箱验证码服务实现类
* </p>
*
* @author sw-code
* @since 2023-05-17
*/
@Service
public class EmailCodeServiceImpl extends ServiceImpl<EmailCodeMapper, EmailCode> implements EmailCodeService {

private final JavaMailSender javaMailSender;

private final RedisComponent redisComponent;

@Resource
private AppConfig appConfig;

public EmailCodeServiceImpl(JavaMailSender javaMailSender, RedisComponent redisComponent) {
this.javaMailSender = javaMailSender;
this.redisComponent = redisComponent;
}

@Override
@Transactional(rollbackFor = Exception.class)
public void sendEmailCode(String email, Integer type) {
String code = StringTools.getRandomNumber(Constants.LENGTH_5);
// 发送验证码
sendMailCode(email, code);
// 将之前的验证码置为无效
baseMapper.disableEmailCode(email);

EmailCode emailCode = new EmailCode();
emailCode.setCode(code);
emailCode.setEmail(email);
emailCode.setStatus(Constants.ZERO);
this.save(emailCode);
}

private void sendMailCode(String toEmail, String code) {
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(appConfig.getSendUsername());
helper.setTo(toEmail);

SysSettingsDTO sysSettingsDto = redisComponent.getSysSettingDto();

helper.setSubject(sysSettingsDto.getRegisterMailTitle());
helper.setText(String.format(sysSettingsDto.getRegisterEmailContent(), code));
helper.setSentDate(new Date());
javaMailSender.send(message);
} catch (Exception e) {
throw new BizException("邮件发送失败");
}
}
}

定义Controller

在 UserInfoController 下定义接口

UserInfoController
@RestController("userInfoController")
@ResponseResult
@Validated
public class UserInfoController {

private final UserInfoService userInfoService;
private final EmailCodeService emailCodeService;

@Resource
AppConfig appConfig;

public UserInfoController(UserInfoService userInfoService, EmailCodeService emailCodeService) {
this.userInfoService = userInfoService;
this.emailCodeService = emailCodeService;
}

@PostMapping("/sendEmailCode")
public void sendEmailCode(HttpSession session,
@NotNull String email,
@NotNull String checkCode,
@NotNull Integer type) {
try {
if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY_EMAIL))) {
throw new BizException("图片验证码错误");
}
// 0:注册 1:找回
if (type == 0) {
UserInfo userInfo = userInfoService.getOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getEmail, email));
if (null != userInfo) {
throw new BizException("邮箱已存在");
}
}
emailCodeService.sendEmailCode(email, type);
} finally {
session.removeAttribute(Constants.CHECK_CODE_KEY_EMAIL);
}
}
}

用户注册服务

接口信息

路径地址 http://localhost:7090/api/register
请求方式 POST
请求参数 RegisterDTO
返回结果

图片验证码DTO

在包com.swx.easypan.entity.dto,下创建 RegisterDTO 实体类,将下面代码放入:

@Data
public class RegisterDTO {

@Email
private String email;
@NotNull
private String nickname;

@NotNull
@Size(min = 8, max = 18, message = "密码长度在8-18之间")
private String password;

@NotNull
private String checkCode;

@NotNull
private String emailCode;

}

定义Mapper

将验证码置为无效

EmailCodeMapper
public interface EmailCodeMapper extends BaseMapper<EmailCode> {

void disableEmailCode(@Param("email") String email);
}

在 EmailCodeMapper.xml 中实现接口,使用乐观锁

EmailCodeMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.swx.easypan.mapper.EmailCodeMapper">
<select id="disableEmailCode">
update email_code
set status = 1
where email = #{email}
and status = 0
</select>
</mapper>

定义Service

找到 UserInfoService接口,在其中添加:

UserInfoService
/**
* 注册
*
* @param registerDto 注册信息
*/
void register(RegisterDTO registerDto);

在 UserInfoServiceImpl 中实现该方法,首先校验用户是否已经注册,然后校验其邮箱验证码,没有问题,则创建新用户,并初始化基础信息。

UserInfoServiceImpl
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

private final EmailCodeService emailCodeService;
private final RedisComponent redisComponent;

@Resource
private AppConfig appConfig;

public UserInfoServiceImpl(EmailCodeService emailCodeService, RedisComponent redisComponent) {
this.emailCodeService = emailCodeService;
this.redisComponent = redisComponent;
}

/**
* 注册
*
* @param registerDto 注册信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void register(RegisterDTO registerDto) {
UserInfo emailUser = this.getOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getEmail, registerDto.getEmail()));
if (null != emailUser) {
throw new BizException("邮箱账号已经存在");
}
UserInfo nickNameUser = this.getOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getNickname, registerDto.getNickname()));
if (null != nickNameUser) {
throw new BizException("邮箱昵称已经存在");
}
// 校验邮箱验证码
emailCodeService.checkCode(registerDto.getEmail(), registerDto.getEmailCode());

String userId = StringTools.getRandomNumber(Constants.LENGTH_10);
UserInfo userInfo = new UserInfo();
userInfo.setId(userId);
userInfo.setNickname(registerDto.getNickname());
userInfo.setEmail(registerDto.getEmail());
userInfo.setPassword(MD5.encrypt(registerDto.getPassword()));
userInfo.setUseSpace(0L);
SysSettingsDTO sysSettingDto = redisComponent.getSysSettingDto();
userInfo.setTotalSpace(sysSettingDto.getUserInitSpace() * Constants.MB);
this.save(userInfo);
}
}

定义Controller

在 UserInfoController 下定义接口

UserInfoController
@RestController("userInfoController")
@ResponseResult
@Validated
public class UserInfoController {

private final UserInfoService userInfoService;

public UserInfoController(UserInfoService userInfoService) {
this.userInfoService = userInfoService;
}

@PostMapping("/register")
public void register(HttpSession session, @Valid @RequestBody RegisterDTO registerDto) {
try {
if (!registerDto.getCheckCode().equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY))) {
throw new BizException("图片验证码错误");
}
userInfoService.register(registerDto);
} finally {
session.removeAttribute(Constants.CHECK_CODE_KEY);
}
}
}