目录
1.controller层接口
controller传递前端返回的参数合调用service层接口,方便管理
@ApiOperation("用户注册")
@PostMapping("/register")
public JsonData register(@ApiParam(value = "用户注册对象")
@RequestBody UserRegister userRegister) {
JsonData register = userService.register(userRegister);
return JsonData.buildSuccess(register);
}
2.service层接口实现
service对注册用户进行处理:
- 1.密码加密:如果使用简单的MD5或其他加密方式很容易,例如(cmd5.com解密网站),当在MD5后在拼接上盐,则可以加大破解难度
- 2.头像:有专门的oss对象存储接口,返回图片地址然后在存储到数据库中
- 3.账号唯一性:在大高并发下,很难保证账号的唯一性,我们直接在数据库中使用唯一索引
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private NotifyService notifyService;
@Override
public UserDO getById(Long id) {
return userMapper.selectById(id);
}
/**
* 用户注册
*
* @param userRegister
* @return
*/
@Override
public JsonData register(UserRegister userRegister) {
Boolean checkCode = false;
//判断邮箱是否合规
if (StringUtils.isNotBlank(userRegister.getMail())) {
//调用接口,校验验证码
checkCode = notifyService.checkCode(SendCodeEnum.USER_REGISTER, userRegister.getMail(), userRegister.getCode());
}
//验证错误,直接返回
if (!checkCode) {
return JsonData.buildResult(CodeEnum.CODE_ERROR);
}
//创建UserDo数据模型
UserDO userDO = new UserDO();
//将UserRegister的属性拷贝到数据模型userDO中
BeanUtils.copyProperties(userRegister, userDO);
//设置创建日期
userDO.setCreateTime(new Date());
//设置个性签名
userDO.setSlogan(userRegister.getSlogan());
//设置头像
userDO.setHeadImg(userRegister.getHeadImage());
userDO.setSex(userRegister.getSex());
//TODO 设置密码,md5加密
//生成密钥盐
userDO.setSecret("$1$" + CommonUtil.getStringNumRandom(8));
//密码+盐处理
String password = Md5Crypt.md5Crypt(userRegister.getPwd().getBytes(), userDO.getSecret());
userDO.setPwd(password);
//TODO 账号唯一性检查
if (checkUnique(userDO.getMail())) {
int rows = userMapper.insert(userDO);
log.info("rows:{},注册成功:{}", rows, userDO.toString());
//TODO 初始化信息,发放福利
//保存到数据库
userRegisterInitTask(userDO);
return JsonData.buildSuccess();
} else {
return JsonData.buildResult(CodeEnum.ACCOUNT_REPEAT);
}
}
/**
* 校验账号是否唯一
*
* @param mail
* @return
*/
private boolean checkUnique(String mail) {
List<UserDO> mailCheck = userMapper.selectList(new QueryWrapper<UserDO>().eq("mail", mail));
return mailCheck.size() > 0 ? false : true;
}
}
疑惑解答:
对于为什么不直接使用UserDo传参,而是重新建个含部分UserDo属性的对象?
- 我也很疑惑,后来请教了一下大佬,他说:dao层不用打包出去,避免数据库暴露,api模块和orm操作模块衍生出的,单体项目没必要!!!
- 豁然开朗后,我的理解:以后dao只作为与数据库连接的桥梁,而每当用到部分属性作为参数时,重新构建实体类,然后在将构建好的实体类,利用(BeanUtils.copyProperties)拷贝到dao层上,在设置相关属性
3.验证码的实现类
发送验证码思路分析:
- 1.指定key的名称
- 2.根据key获取value(验证码)
- 3.将随机数拼接上时间戳(方便校验时间),存入缓存中并设置过期时间
- 4.判断邮箱是否合规
- 5.调用邮箱发送接口,将验证码发送给邮箱
- 6.60秒内不能重复发送
- 获取的value如果不为空,通过分割字符换获取时间戳
- 当前时间-分割字符串获取的时间<60秒,直接返回不能重复发送
校验验证码思路分析:
- 1.根据指定规则获取key
- 2.根据key获取value(验证码)
- 3. 判断获取的value(验证码)是否为空
- 4.根据获取的value利用字符串分割,取出时间戳,获取真正的验证码
- 5.判断获取的真正验证码是否与用户传递过来的验证码相等
- 相等,返回true
- 否则,返回false
@Slf4j
@Service
public class NotifyServiceImpl implements NotifyService {
@Autowired
private MailService mailService;
@Autowired
private StringRedisTemplate redisTemplate;
private static final String SUBJECT = "欢迎查收小张的邮箱~";
private static final String CONTENT = "你的验证码%s,有效时间60秒,悄悄地哦";
//过期时间10分钟
private static final int CODE_EXPIRED = 60 * 1000 * 10;
/**
* 发送验证码
*
* @param sendCodeEnum:发送类型
* @param to:收件人邮箱
* @return
*/
@Override
public JsonData sendCode(SendCodeEnum sendCodeEnum, String to) {
//获取缓存key
String cacheKey = String.format(CaptchaKey.CAPTCHA_CODE_KEY, sendCodeEnum.name(), to);
//根据key获取value
String cacheValue = redisTemplate.opsForValue().get(cacheKey);
//如果不为空,则判断60秒内重复发送
if (StringUtils.isNotBlank(cacheValue)) {
long ttl = Long.parseLong(cacheValue.split("_")[1]);
//当前时间戳-验证码发送时间错,如果小于60,不重复发送
if (CommonUtil.getCurrentTimestamp() - ttl < 1000 * 60) {
log.info("重复发送验证码,时间间隔:{}秒", (CommonUtil.getCurrentTimestamp() - ttl) / 1000);
return JsonData.buildResult(CodeEnum.CODE_LIMITED);
}
}
//获取随机验证码
String randomCode = CommonUtil.getRandomCode(6);
//获取到的验证码拼接上时间戳
String value = randomCode + "_" + CommonUtil.getCurrentTimestamp();
//将拼接后的验证码存到redis中
redisTemplate.opsForValue().set(cacheKey, value, CODE_EXPIRED, TimeUnit.MILLISECONDS);
//1.判断是否合规
if (CheckUtil.isEmail(to)) {
//指定邮箱验证的位数
//发送随机验证码
mailService.sendMail(to, SUBJECT, String.format(CONTENT, randomCode));
log.info("验证码发送成功:{}",randomCode);
return JsonData.buildSuccess("发送随机验证成功~");
} else {
return JsonData.buildResult(CodeEnum.CODE_TO_ERROR);
}
}
/**
* 校验验证码
*
* @param sendCodeEnum:类型
* @param to:接受验证码的人
* @param code:验证码
* @return
*/
@Override
public boolean checkCode(SendCodeEnum sendCodeEnum, String to, String code) {
//获取key
String cacheKey = String.format(CaptchaKey.CAPTCHA_CODE_KEY, sendCodeEnum.name(), to);
//根据key获取验证码
String cacheValue = redisTemplate.opsForValue().get(cacheKey);
//判断验证码是否为空
if (StringUtils.isNotBlank(cacheValue)) {
//因为存储的时候是3123_312351521的形式,所以进行切割,获取真正的验证码
String cacheCode = cacheValue.split("_")[0];
//校验验证码是否相等
if (cacheCode.equals(code)) {
//相等的话,删除key,防止重复
redisTemplate.delete(cacheKey);
return true;
}
}
return false;
}
}