基于spring boot的邮箱验证码注册功能 - 后端代码

文章目录

  • 基于spring boot的邮箱验证码注册功能
    • 1. 后端项目
      • 1.1 数据库创建
    • 1.2 项目创建
    • 1.3 前期准备
      • 1.3.1 依赖安装
      • 1.3.2 工具类准备
        • 统一响应类+状态码枚举类
        • redis工具类+配置类
    • 1.4 代码编写
      • 1.4.1 user表相关代码
      • 1.4.2 控制层(controller)
      • 1.4.3 发送验证码功能实现
        • 1.4.3.1 获取授权码
        • 1.4.3.2 配置yml文件
        • 1.4.3.3 新建一个数据传输类`ToEmail`
        • 1.4.3.4 新建`EmailService`和其实现类
        • 1.4.3.5 修改EmailController
        • 1.4.3.6 测试
      • 1.4.4 注册功能实现
        • 1.4.4.1 新建一个请求数据接收类,接收注册数据
        • 1.4.4.2 UserController添加注册接口
        • 1.4.4.3 UserService及其实现类实现注册功能
        • 1.4.4.4 测试

基于spring boot的邮箱验证码注册功能

代码地址

1. 后端项目

1.1 数据库创建

CREATE TABLE `user`  (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称',
  `userAccount` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '登录账号',
  `avatarUrl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像',
  `gender` tinyint(1) NULL DEFAULT NULL COMMENT '性别',
  `userPassword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
  `phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '电话',
  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱',
  `userStatus` int NOT NULL DEFAULT 0 COMMENT '用户状态:0正常',
  `createTime` datetime NULL DEFAULT NULL COMMENT '创建时间,数据插入时间',
  `updateTime` datetime NULL DEFAULT NULL COMMENT '更新时间,数据更新时间',
  `isDelete` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除:0未删除',
  `role` int NOT NULL DEFAULT 0 COMMENT '0普通用户:1管理员',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

1.2 项目创建

基于spring boot的邮箱验证码注册功能 - 后端代码_第1张图片

基于spring boot的邮箱验证码注册功能 - 后端代码_第2张图片

这几个文件都可以删掉
基于spring boot的邮箱验证码注册功能 - 后端代码_第3张图片

1.3 前期准备

1.3.1 依赖安装


<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.30version>
dependency>

<dependency>
    <groupId>com.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
    <version>3.5.1version>
dependency>

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.1.16version>
dependency>


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>fastjsonartifactId>
    <version>1.2.33version>
dependency>


<dependency>
    <groupId>org.apache.commonsgroupId>
    <artifactId>commons-lang3artifactId>
    <version>3.12.0version>
dependency>

1.3.2 工具类准备

统一响应类+状态码枚举类
@Data
public class Result<T> implements Serializable {
    private Integer code;
    private String msg;
    private T data;

    public Result() {

    }

    public static Result ResultOk(Object data){
        Result result = setCodeEnum(CodeEnum.SUCCESS);
        if(data!=null) {
            result.setData(data);
        }
        return result;
    }
    public static Result ResultOk(){
        Result result = setCodeEnum(CodeEnum.SUCCESS);;
        return result;
    }
    public static Result error(){
        Result result = setCodeEnum(CodeEnum.ERROR);;
        return result;
    }
    public static Result error(String msg){
        Result result = setCodeEnum(CodeEnum.ERROR);
        result.setMsg(msg);
        return result;
    }

    public static Result setCodeEnum(CodeEnum enums){
        Result result = new  Result();
        result.setCode(enums.getCode());
        result.setMsg(enums.getMsg());
        return result;
    }
    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}
public enum CodeEnum {
    // 成功
    SUCCESS(200, "操作成功"),
    // 失败
    ERROR(400,"操作失败");
    int code;
    String msg;

    CodeEnum(int code, String errorMessage) {
        this.code = code;
        this.msg = errorMessage;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}
redis工具类+配置类

使用redis存放 邮箱验证码

@Configuration
public class RedisConfig {

    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
}
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public long deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 删除Hash中的数据
     * 
     * @param key
     * @param hkey
     */
    public void delCacheMapValue(final String key, final String hkey)
    {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key, hkey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }

    /**
     * 更新redis文章浏览量
     * @param key 哪一个哈希结构
     * @param hKey 哈希表里哪一个数据
     * @param v 更改值
     */
    public void incrementCacheMapValue(String key,String hKey,long v){
        redisTemplate.boundHashOps(key).increment(hKey, v);
    }
}
/**
 * Redis使用FastJson序列化
 */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>
{

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static
    {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }


    protected JavaType getJavaType(Class<?> clazz)
    {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

1.4 代码编写

1.4.1 user表相关代码

新建domain包,在domain包中新建entity包再在entity 中创建user实体类

/**
 * (User)表实体类
 * @author makejava
 * @since 2023-04-28 14:23:28
 */
@SuppressWarnings("serial")
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User  {
    // 用户id@TableId
    @TableId(value = "id" ,type = IdType.AUTO)
    private Long id;
    // 昵称
    private String username;
    // 登录账号
    private String useraccount;
    // 头像
    private String avatarurl;
    // 性别
    private Integer gender;
    // 密码
    private String userpassword;
    // 电话
    private String phone;
    // 邮箱
    private String email;
    // 用户状态:0正常
    private Integer userstatus;
    // 创建时间,数据插入时间
    private Date createtime;
    // 更新时间,数据更新时间
    private Date updatetime;
    // 是否删除:0未删除
    private Integer isdelete;
    // 0普通用户:1管理员
    private Integer role;
}

新建mapper包创建UserMapper

/**
 * (User)表数据库访问层
 *
 * @author makejava
 * @since 2023-04-28 14:23:25
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {

}

新建service和其实现类

/**
 * (User)表服务接口
 *
 * @author makejava
 * @since 2023-04-28 14:23:32
 */
public interface UserService extends IService<User> {
	
}
/**
 * (User)表服务实现类
 *
 * @author makejava
 * @since 2023-04-28 14:23:33
 */
@Service()
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

}

1.4.2 控制层(controller)

新建controller包新建UserController类,定义用户注册的请求

@RestController // 适用于编写restful风格的api,返回值默认为json类型
@RequestMapping("/user")
public class UserController {
   // 注册
}
@RestController // 适用于编写restful风格的api,返回值默认为json类型
@RequestMapping("/email")
public class EmailController {
    // 发送验证码
}

1.4.3 发送验证码功能实现

1.4.3.1 获取授权码

要实现邮箱服务需要邮箱授权码,QQ邮箱获取方式如下:
基于spring boot的邮箱验证码注册功能 - 后端代码_第4张图片

1.4.3.2 配置yml文件
server:
  port: 8888
spring:
  mail:
    host: smtp.qq.com
    protocol: smtp
    default-encoding: UTF-8
    username: [email protected]
    password: *********** # 填上面生成的授权码
    test-connection: true
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
    port: 587
1.4.3.3 新建一个数据传输类ToEmail

domain包下新建dto包,在dto包下创建ToEmail

package com.hzy.usercenter.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
 * @title: ToEmail
 * @Author zxwyhzy
 * @Date: 2023/4/28 14:47
 * @Version 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ToEmail implements Serializable {
    /**
     * 邮件接收方
     */
    private String to;
    /**
     * 邮件主题
     */
    private String subject;
    /**
     * 邮件内容
     */
    private String content;
}
1.4.3.4 新建EmailService和其实现类
public interface EmailService {
    /**
     *  发送验证码
     * @param toEmail 邮箱数据传输类
     * @return
     */
    Result commonEmail(ToEmail toEmail);
}
@Service
public class EmailServiceImpl implements EmailService {
    @Autowired
    private JavaMailSender mailSender;
    @Value("${spring.mail.username}")
    private String from;
    @Autowired
    private UserService userService;
    public Result commonEmail(ToEmail toEmail) {
        // userService.isEmailNull(toEmail) 判断该邮箱是否已注册
        if (userService.isEmailNull(toEmail)) {
            //创建简单邮件消息
            SimpleMailMessage message = new SimpleMailMessage();
            //谁发的
            message.setFrom(from);
            //谁要接收
            message.setTo(toEmail.getTo());
            //邮件标题
            message.setSubject(toEmail.getSubject());
            //邮件内容
            message.setText(toEmail.getContent());
            try {
                mailSender.send(message);

                return Result.ResultOk(toEmail.getTo());
            } catch (MailException e) {
                e.printStackTrace();
                return Result.error("发送失败");
            }
        }
        return Result.error("该邮箱已注册");
    }
}

实现isEmailNull()

public interface UserService extends IService<User> {
    /**
     *  判断邮箱是否注册
     * @return true|false
     */
    boolean isEmailNull(ToEmail toEmail);
}

    @Override
    public boolean isEmailNull(ToEmail toEmail) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getEmail,toEmail.getTo());
        long count = count(queryWrapper);
        if (count <1 )
            return true;
        return false;
    }
1.4.3.5 修改EmailController
@RestController
@RequestMapping("/mail")
public class EmailController {
    @Autowired
    private RedisCache redisCache;
    @Autowired
    EmailService emailService = new EmailServiceImpl();
    @PostMapping
    public Result SendMail(@RequestBody ToEmail toEmail){
        toEmail.setSubject("注册验证码");
        // RandomStringUtils  commons-lang3引进的工具类
        String yzm = RandomStringUtils.randomNumeric(6);
        toEmail.setContent(yzm);
        redisCache.setCacheObject(toEmail.getTo(),yzm);
        // 验证码6分钟内有效
        redisCache.expire(toEmail.getTo(),60*10);
        return emailService.commonEmail(toEmail);
    }
}
1.4.3.6 测试

基于spring boot的邮箱验证码注册功能 - 后端代码_第5张图片

基于spring boot的邮箱验证码注册功能 - 后端代码_第6张图片

数据库添加邮箱为[email protected]的数据在这里插入图片描述
基于spring boot的邮箱验证码注册功能 - 后端代码_第7张图片

1.4.4 注册功能实现

1.4.4.1 新建一个请求数据接收类,接收注册数据
/**
 *  接收前端注册参数
 * @title: UserRegisterRequest
 * @Author zxwyhzy
 * @Date: 2023/4/28 15:43
 * @Version 1.0
 */
@Data
public class UserRegisterRequest {
    // 用户名
    private String userAccount;
    // 密码
    private String userPassword;
    // 确认密码
    private String checkPassword;
    // 验证码
    private String code;
    // 邮箱
    private String email;
}
1.4.4.2 UserController添加注册接口
@RestController // 适用于编写restful风格的api,返回值默认为json类型
@RequestMapping("/user")
public class UserController {
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private UserService userService;

    @PostMapping("/register")
    public Result userRegister(@RequestBody UserRegisterRequest user){
        if (null == user) return Result.error("参数不能为空");
        String yzm = redisCache.getCacheObject(user.getEmail());
        if (user.getCode()!=null && user.getCode().equals(yzm)){
            return userService.userRegister(user);
        }
        return Result.error("验证码错误");
    }
}
1.4.4.3 UserService及其实现类实现注册功能
public interface UserService extends IService<User> {
    /**
     *  用户注册
     * @param user 注册参数
     * @return 是否注册功能 或失败原因
     */
    Result userRegister(UserRegisterRequest user);
}

@Service()
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    private static final String SALT = "zy";
    @Override
    public Result userRegister(UserRegisterRequest user) {
        // 虽然前端也会判断,但是有绕过前端直接发送请求的可能
        if (StringUtils.isAnyBlank(user.getUserAccount(),
                user.getUserPassword(),
                user.getCheckPassword())){
            return Result.error("用户名或密码为空");
        }
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUseraccount,user.getUserAccount());
        long count = count(queryWrapper);
        if (count > 0 ) return Result.error("用户名以存在");
        if (user.getUserPassword().equals(user.getCheckPassword())){
            User register = new User();
            register.setUseraccount(user.getUserAccount());
            // 加密
            String password = DigestUtils.md5DigestAsHex((SALT + user.getUserPassword()).getBytes());
            register.setUserpassword(password);
            boolean save = save(register);
            if (!save){
                return Result.error("注册失败");
            }
            return Result.ResultOk("注册成功");
        }
        return Result.error("两次输入的密码不一致");
    }
}
1.4.4.4 测试

在这里插入图片描述

基于spring boot的邮箱验证码注册功能 - 后端代码_第8张图片
基于spring boot的邮箱验证码注册功能 - 后端代码_第9张图片
基于spring boot的邮箱验证码注册功能 - 后端代码_第10张图片
在这里插入图片描述

你可能感兴趣的:(spring,boot,java,数据库)