草庐IT

SpringBoot+Vue实现邮箱登录注册找回密码(附接口文档)

全栈小袁 2023-08-15 原文

🚀 花了几个小时做了一个SpringBoot+Vue实现邮箱登录注册找回密码的demo项目,项目已在Gitee上开源,Gitee开源地址(有接口文档):https://gitee.com/yuandewei/Yuan-SpringBoot/tree/master

跟着我的脚本一步一步实现代码,学会了你也能自己写出来 (或者根据接口文档自己写后端)✨

小袁有话说
今天的教程内容呢是实现邮箱注册登录账号,以及发送邮箱验证码校验验证码和找回密码等,效果图如下

文章底部有视频效果展示


话不多说,开始今天的详细教程

数据表的设计,没啥问题,邮箱、密码、加密盐,这里只是演示邮箱功能,所以字段就没有设计太多,需要的就自己额外设计了

🧸 Redis安装启动

redis本次案例用于存储请求权限码和邮箱验证码

复制链接下载windows解压版 https://user.xiaoyuan-boke.com/Redis-x64-5.0.14.zip

下载好直接解压就好,打开cmd命令控制台,来到刚刚解压的位置(我这里是F盘下的redis文件夹)

输入指令启动 redis-server.exe redis.windows.conf,显示下面这样则成功启动,窗口不能关闭,关闭了redis也跟着关闭了

🎃 创建项目,配置文件

我这里呢是直接创建一个SpringBoot的项目,启动类加上组件扫描映射文件扫描,同时创建基础结构目录,如图

项目如何创建这里就不一步一步教啦,相信之前看过几篇教学应该都会了

依赖

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.9.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	
	<properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!-- 数据库驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- lombok插件 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Redis依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- 处理JSON的 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <!-- 工具包 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <!-- MD5加密的依赖包 -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>
        <!-- 邮箱依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
    </dependencies>

配置文件

application 这个也没啥问题吧,大家应该都能看懂

server:
  port: 8081

spring:
  # 数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    username: xiaoyuan
    password: root
  redis:
    port: 6379
    host: localhost
  # 时间格式转换
  jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss
  profiles:
    # 引入application-email配置文件
    include: email

mybatis-plus:
  # mapper文件映射路径
  mapper-locations: classpath*:mapper/*.xml
  configuration:
    # 打印SQL语句
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
  level:
    com.shyroke.mapper: debug

application-email.yml 这个是配置邮箱信息的

spring:
  mail:
    host: smtp.qq.com
    username: 自己的QQ邮箱
    password: 授权码
    protocol: smtp
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
          ssl:
            enable: true

🎯 这里主要一点,要实现邮箱发送功能,得开启SMTP服务,打开自己的QQ邮箱,点击设置 -> 点击账户 -> 开启SMTP服务 -> 获取授权码,将授权码复制到上面的 application-email.yml 文件里

⚽ db

User 实体类,entity 包下

@Data
@TableName("t_user")
public class User {

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String email;

    private String password;

    private String salt;
}

UserMapper 数据访问接口,mapper 包下,继承MyBatis-Plus的 BaseMapper,内部封装了单表的大部分操作

@Repository
public interface UserMapper extends BaseMapper<User> {

}

⚾ constant静态变量

constant 包下新建 RedisConstant 类,

public interface RedisConstant {

    // Key
    String EMAIL = "EMAIL_"; // 邮箱缓存前缀
    String EMAIL_REQUEST_VERIFY = "EMAIL_REQUEST_VERIFY_"; // 邮箱请求的权限码

    // 缓存时间
    int EXPIRE_TEN_SECOND = 10; // 10s
    int EXPIRE_ONE_MINUTE = 60; // 1分钟
    int EXPIRE_FIVE_MINUTE = 5 * 60; // (五分钟)
    int EXPIRE_HALF_HOUR = 30 * 60; // 半小时(30分钟)
    int EXPIRE_ONE_DAY = 24 * 60 * 60; // (1天)
}

新建 HttpStatusEnum 枚举类

@Getter
public enum HttpStatusEnum {

    EMAIL_ERROR(4001, "邮箱格式不正确"),
    PARAM_ERROR(4002, "参数格式不正确"),
    CODE_ERROR(4002, "验证码不正确"),
    PASSWORD_ERROR(4003, "密码错误"),
    USER_NOT_EXIST(4004, "用户不存在"),
    EMAIL_ALREADY_EXIST(4005, "邮箱已被注册"),
    PASSWORD_INCONSISTENT(4006, "密码不一致"),
    PARAM_ILLEGAL(4007, "参数不合法"),

    INTERNAL_SERVER_ERROR(500, "服务器异常"),
    UNKNOWN_ERROR(66666, "未知异常, 联系管理员"),
    ILLEGAL_OPERATION(88888, "非法操作");

    private final int code;
    private final String msg;

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

🥎vo对象

统一结果返回

vo 包下新建 R 统一返回类

@Data
public class R {

    private Boolean success;

    private Integer code;

    private String message;

    private Map<String, Object> data = new HashMap<>();

    // 把构造方法私有化
    private R() {}

    // 成功静态方法
    public static R ok() {
        R r = new R();
        r.setSuccess(true);
        r.setCode(200);
        r.setMessage("成功");
        return r;
    }

    // 失败静态方法
    public static R error() {
        R r = new R();
        r.setSuccess(false);
        r.setCode(20001);
        r.setMessage("失败");
        return r;
    }

    // 失败静态方法
    public static R error(HttpStatusEnum httpStatus) {
        R r = new R();
        r.setSuccess(false);
        r.setCode(httpStatus.getCode());
        r.setMessage(httpStatus.getMsg());
        return r;
    }

    public R success(Boolean success){
        this.setSuccess(success);
        return this;
    }

    public R message(String message){
        this.setMessage(message);
        return this;
    }

    public R code(Integer code){
        this.setCode(code);
        return this;
    }

    public R data(String key, Object value){
        this.data.put(key, value);
        return this;
    }

    public R data(Map<String, Object> map){
        this.setData(map);
        return this;
    }
}

参数

新建param包,包下新建LoginParam

@Getter
public class LoginParam {
    
    private String email; // 邮箱
    
    private String password; // 密码
    
    private String passwordConfirm; // 确认密码
    
    private String code; // 验证码
}

🏀utils工具类

utils 包下新建StringUtil类,编写自定义的一些字符串工具类(如邮箱校验,验证码生成)

public class StringUtil {

    /**
     * 邮箱校验
     *
     * @param email 邮箱
     * @return true or false
     */
    public static boolean checkEmail(String email) {
        String check = "^([a-zA-Z]|[0-9])(\\w|\\-)+@[a-zA-Z0-9]+\\.([a-zA-Z]{2,4})$";
        Pattern regex = Pattern.compile(check);
        Matcher matcher = regex.matcher(email);
        return matcher.matches();
    }

    /**
     * 密码校验(长度 6-18,至少包含1个字母)
     * @param password
     * @return
     */
    public static boolean checkPassword(String password) {
        String check = "(?=.*[a-zA-Z])[a-zA-Z0-9]{6,18}";
        Pattern regex = Pattern.compile(check);
        Matcher matcher = regex.matcher(password);
        return matcher.matches();
    }

    /**
     * 随机生成六位数字验证码
     */
    public static String randomSixCode() {
        return String.valueOf(new Random().nextInt(899999) + 100000);
    }

    /**
     * 随机生成加密盐(4位随机字母 + 4位固定特殊字符)
     */
    public static String randomEncryptedSalt() {
        return RandomStringUtils.randomAlphabetic(4) + "#!$@";
    }
}

至此,基本完成了80%的工作,剩下的就是最重要的部分了,service业务层的设计,这部分我会详细介绍

🍊 邮箱业务

邮箱服务

service 包下创建 MailService 邮箱服务类,这里只列出了普通邮件和HTML邮件的代码,其他类型邮件网上也有的

javaMailSender Java内部封装好的邮箱发送类,只需要导入对应的依赖,直接注入即可

from 通过@Value注解读取 application-email 的username字段(也就是自己的邮箱)

message.setForm 中的三个参数,第一个是发件人(也就是自己),第二个是发件人昵称,也就是下面图片框出来的,第三个参数编码

@Component
public class MailService  {

    @Resource
    private JavaMailSender javaMailSender;

    @Value("${spring.mail.username}")
    private String from;

    /**
     * 发送简单的邮箱
     *
     * @param to 收件人
     * @param theme 标题
     * @param content 正文内容
     * @param cc 抄送
     */
    public void sendSimpleMail(String to, String theme, String content, String... cc) {
        // 创建邮件对象
        SimpleMailMessage message = new SimpleMailMessage();
        
        try {
            message.setFrom(String.valueOf(new InternetAddress(from, "小袁博客平台", "UTF-8")));      // 发件人
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        message.setTo(to);          // 收件人
        message.setSubject(theme);  // 标题
        message.setText(content);   // 内容
        
        if (ArrayUtils.isNotEmpty(cc)) {
            message.setCc(cc);
        }

        // 发送
        javaMailSender.send(message);
    }

    /**
     * 发送HTML邮件
     *
     * @param to      收件人地址
     * @param subject 邮件主题
     * @param content 邮件内容
     * @param cc      抄送地址
     * @throws MessagingException 邮件发送异常
     */
    public void sendHtmlMail(String to, String subject, String content, String... cc) throws MessagingException {
        MimeMessage message = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true);
        helper.setFrom(from);
        helper.setTo(to);
        helper.setSubject(subject);
        helper.setText(content, true);
        if (ArrayUtils.isNotEmpty(cc)) {
            helper.setCc(cc);
        }
        javaMailSender.send(message);
    }
}

线程池配置

先在主目录创建 config 包,包下新建 ThreadPoolConfig 类,这是线程池的配置类,帮助统一管理线程,线程池的其他作用就不一一介绍啦,挺多的,可以B站找视频学习

@Configuration
@EnableAsync // 开启线程池
public class ThreadPoolConfig {

    @Bean("taskExecutor")
    public Executor asyncServiceExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        // 设置核兴线程数
        executor.setCorePoolSize(5);
        // 设置最大线程数
        executor.setMaxPoolSize(20);
        // 设置队列大小
        executor.setQueueCapacity(Integer.MAX_VALUE);
        // 设置线程活跃时间 60s
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("小袁博客平台");
        // 是否所有任务执行完毕后关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 执行初始化
        executor.initialize();
        return executor;
    }
}

线程服务

service 包下新建 threadService 线程服务类

发送邮箱是一件很耗时的操作,如果不开辟线程去执行在主线程执行的话,会等待很久导致请求超时,而且我们发送验证码也不需要等到邮件到了才返回给前端提示

所以我们开辟线程去执行邮箱发送操作,不占用主线程运行

@Component
public class ThreadService {

    @Autowired
    private MailService mailService;

    /**
     * 发送邮箱
     * @param to 收件人
     * @param theme 主题
     * @param content 内容
     */
    @Async("taskExecutor")
    public void sendSimpleMail(String to, String theme, String content) {
        mailService.sendSimpleMail(to, theme, content);
    }
}

公共服务

新建 CommonService 公共服务接口类,注意是接口,这里讲解一下发送验证码的设计


使用权限码校验防止故意重复使用该接口,每次发送验证码请求前都先请求一个随机的权限码(有效时间越短越好,当然也不要太短,5 - 10s即可)

public interface CommonService {

    /**
     * 获取请求权限码
     * @param emailJson 邮箱
     * @return
     */
    R getRequestPermissionCode(String emailJson);

    /**
     * 发送邮箱验证码
     * @param loginParam (邮箱和权限码)
     * @return
     */
    R sendEmailCode(LoginParam loginParam);
}

新建 CommonServiceImpl 类实现 CommonService 接口

代码每行都写有注释,内容不难,参数校验 -> 邮箱校验 -> 权限码校验 -> 发送验证码

@Component
public class CommonServiceImpl implements CommonService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private ThreadService threadService;

    @Override
    public R getRequestPermissionCode(String emailJson) {
        // 非空校验
        if (StringUtils.isBlank(emailJson)) return R.error(HttpStatusEnum.PARAM_ILLEGAL);

        // JSON转换,提取email的值
        String email = JSON.parseObject(emailJson).getString("email").trim();
        // 邮箱校验
        if (!StringUtil.checkEmail(email)) {
            return R.error(HttpStatusEnum.EMAIL_ERROR);
        }

        // 随机生成权限码
        String permissionCode = UUID.randomUUID().toString();

        // 存入redis,缓存10s
        redisTemplate.opsForValue().set(RedisConstant.EMAIL_REQUEST_VERIFY + email, permissionCode, RedisConstant.EXPIRE_TEN_SECOND, TimeUnit.SECONDS);
        return R.ok().data("permissionCode", permissionCode);
    }

    @Override
    public R sendEmailCode(LoginParam loginParam) {
        if (loginParam == null) return R.error(HttpStatusEnum.PARAM_ILLEGAL);

        // 获取权限码和邮箱
        String email = loginParam.getEmail();
        String permissionCode = loginParam.getCode();
        // 参数校验
        if (StringUtils.isAnyBlank(email, permissionCode)) {
            return R.error(HttpStatusEnum.PARAM_ILLEGAL);
        }else if (!StringUtil.checkEmail(email)) {
            // 邮箱校验
            return R.error(HttpStatusEnum.EMAIL_ERROR);
        }else {
            // 权限码比对
            String rightCode = redisTemplate.opsForValue().get(RedisConstant.EMAIL_REQUEST_VERIFY + email);
            if (!permissionCode.equals(rightCode)) {
                // 不通过
                return R.error(HttpStatusEnum.ILLEGAL_OPERATION);
            }
        }

        // 全部通过

        // 随机生成6位数字验证码
        String code = StringUtil.randomSixCode();

        // 正文内容
        String content = "亲爱的用户:\n" +
                "您此次的验证码为:\n\n" +
                code + "\n\n" +
                "此验证码5分钟内有效,请立即进行下一步操作。 如非你本人操作,请忽略此邮件。\n" +
                "感谢您的使用!";

        // 发送验证码
        threadService.sendSimpleMail(email, "您此次的验证码为:" + code, content);
        // 丢入缓存,设置5分钟过期
        redisTemplate.opsForValue().set(RedisConstant.EMAIL + email, code, RedisConstant.EXPIRE_FIVE_MINUTE, TimeUnit.SECONDS);
        return R.ok();
    }
}

邮箱服务器接口

邮箱服务已经设计好了,接下来写邮箱服务的接口,然后前端运行测试一下,邮箱功能是否正常

🎯 接口路径如果不和我这个一样的话,前端api下的请求记得也要改路径

@RestController
@RequestMapping("/common")
@CrossOrigin
public class CommonController {

    @Autowired
    private CommonService commonService;

    // 权限码请求接口
    @PostMapping("code/request")
    public R getRequestPermissionCode(@RequestBody String emailJson) {
        return commonService.getRequestPermissionCode(emailJson);
    }

    // 邮箱验证码接口
    @PostMapping("code/email")
    public R sendEmailCode(@RequestBody LoginParam loginParam) {
        return commonService.sendEmailCode(loginParam);
    }
}

启动后端项目,启动前端项目,成功发送邮箱验证码,没有问题

🍋 登录注册业务

新建 UserService 接口类

public interface UserService extends IService<User> {

    /**
     * 登录
     * @param loginParam (邮箱和密码)
     * @return
     */
    R login(LoginParam loginParam);

    /**
     * 注册
     * @param loginParam (邮箱、密码、确认密码、验证码)
     * @return
     */
    R register(LoginParam loginParam);

    /**
     * 找回密码
     * @param loginParam (邮箱、密码、验证码)
     * @return
     */
    R findPassword(LoginParam loginParam);
}

新建 UserServiceImpl 类实现 UserService 接口

@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Override
    public R login(LoginParam loginParam) {
        return null;
    }

    @Override
    public R register(LoginParam loginParam) {
    	return null;
    }

    @Override
    public R findPassword(LoginParam loginParam) {
        return null;
    }
}

登录注册找回密码我一个一个来介绍

参数校验 -> 用户是否存在 -> 密码是否正确 -> 登录成功

	@Override
    public R login(LoginParam loginParam) {
        if (loginParam == null) return R.error(HttpStatusEnum.PARAM_ILLEGAL);

        // 获取参数
        String email = loginParam.getEmail();
        String password = loginParam.getPassword();

        if (StringUtils.isAnyBlank(email, password)) {
            // 非空
            return R.error(HttpStatusEnum.PARAM_ILLEGAL);
        }else if (!StringUtil.checkEmail(email)) {
            // 邮箱格式校验
            return R.error(HttpStatusEnum.EMAIL_ERROR);
        }else if (!StringUtil.checkPassword(password)) {
            // 密码格式
            return R.error(HttpStatusEnum.PASSWORD_ERROR);
        }

        // 构件条件对象 select salt from user where email = #{email} limit 1
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.select("salt");
        wrapper.eq("email", email);
        wrapper.last("limit 1");

        // 查询结果
        User user = this.baseMapper.selectOne(wrapper);
        if (user == null) {
            // 用户不存在
            return R.error(HttpStatusEnum.USER_NOT_EXIST);
        }

        // 获取加密盐
        String salt = user.getSalt();
        // 重新设置条件 select id from user where email = #{email} and password #{password} limit 1
        wrapper.clear();
        wrapper.select("id");
        wrapper.eq("email", email);
        wrapper.eq("password", DigestUtils.md5Hex(password + salt));
        wrapper.last("limit 1");
        // 查询用户
        user = this.baseMapper.selectOne(wrapper);

        return user == null ? R.error(HttpStatusEnum.PASSWORD_ERROR) : R.ok();
    }

参数校验 -> 邮箱是否被注册 -> 验证码比对-> 删除redis验证码 -> 生成加密盐 -> 加密密码 -> 注册用户

	@Override
    public R register(LoginParam loginParam) {
        if (loginParam == null) return R.error(HttpStatusEnum.PARAM_ILLEGAL);

        // 获取参数
        String email = loginParam.getEmail();
        String password = loginParam.getPassword();
        String passwordConfirm = loginParam.getPasswordConfirm();
        String code = loginParam.getCode();

        if (StringUtils.isAnyBlank(email, password, passwordConfirm, code)) {
            // 非空
            return R.error(HttpStatusEnum.PARAM_ILLEGAL);
        }else if (!StringUtil.checkEmail(email)) {
            // 邮箱格式校验
            return R.error(HttpStatusEnum.EMAIL_ERROR);
        }else if (!password.equals(passwordConfirm)) {
            // 密码一致校验
            return R.error(HttpStatusEnum.PASSWORD_INCONSISTENT);
        }else if (!StringUtil.checkPassword(password) || code.length() != 6) {
            // 密码格式和验证码长度校验
            return R.error(HttpStatusEnum.PARAM_ILLEGAL);
        }

        // 构造查询条件对象
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.select("id");
        wrapper.eq("email", email);
        wrapper.last("limit 1");

        // 查询用户,是否存在
        if (this.baseMapper.selectOne(wrapper) != null) {
            return R.error(HttpStatusEnum.EMAIL_ALREADY_EXIST);
        }

        // 获取正确的验证码
        String rightCode = redisTemplate.opsForValue().get(RedisConstant.EMAIL + email);
        if (!code.equals(rightCode)) {
            // 验证码比对
            return R.error(HttpStatusEnum.CODE_ERROR);
        }

        // 删除验证码
        redisTemplate.delete(RedisConstant.EMAIL + email);

        // 注册用户
        User user = new User();
        // 获取加密盐
        String salt = StringUtil.randomEncryptedSalt();
        // 邮箱
        user.setEmail(email);
        // 密码加密(原明文密码 + 随机加密盐) md5加密
        user.setPassword(DigestUtils.md5Hex(password + salt));
        // 加密盐
        user.setSalt(salt);

        // 插入数据
        return this.baseMapper.insert(user) == 0 ? R.error(HttpStatusEnum.UNKNOWN_ERROR) : R.ok();
    }

参数校验 -> 用户是否存在 -> 验证码校验 -> 删除redis验证码 -> 覆盖密码

	@Override
    public R findPassword(LoginParam loginParam) {
        if (loginParam == null) return R.error(HttpStatusEnum.PARAM_ILLEGAL);

        // 获取参数
        String email = loginParam.getEmail();
        String password = loginParam.getPassword();
        String code = loginParam.getCode();

        if (StringUtils.isAnyBlank(email, password, code)) {
            // 非空
            return R.error(HttpStatusEnum.PARAM_ILLEGAL);
        }else if (!StringUtil.checkEmail(email)) {
            // 邮箱格式校验
            return R.error(HttpStatusEnum.EMAIL_ERROR);
        }else if (!StringUtil.checkPassword(password) || code.length() != 6) {
            // 密码格式和验证码长度校验
            return R.error(HttpStatusEnum.PARAM_ILLEGAL);
        }

        // 构造查询条件对象
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.select("id", "salt");
        wrapper.eq("email", email);
        wrapper.last("limit 1");

        // 查询用户,是否存在
        User user = this.baseMapper.selectOne(wrapper);
        if (user == null) {
            return R.error(HttpStatusEnum.USER_NOT_EXIST);
        }

        // 获取正确的验证码
        String rightCode = redisTemplate.opsForValue().get(RedisConstant.EMAIL + email);
        if (!code.equals(rightCode)) {
            // 验证码比对
            return R.error(HttpStatusEnum.CODE_ERROR);
        }

        // 删除验证码
        redisTemplate.delete(RedisConstant.EMAIL + email);
        
        // 修改密码
        User user1 = new User();
        user1.setId(user.getId());
        user1.setPassword(DigestUtils.md5Hex(password + user.getSalt()));
        
        // 修改
        return this.baseMapper.updateById(user1) == 0 ? R.error(HttpStatusEnum.UNKNOWN_ERROR) : R.ok();
    }

接口

🎯 接口路径如果不和我这个一样的话,前端api下的请求记得也要改路径

@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {

    @Autowired
    private UserService userService;

    // 登录
    @PostMapping("login")
    public R login(@RequestBody LoginParam loginParam) {
        return userService.login(loginParam);
    }

    // 注册
    @PostMapping("register")
    public R register(@RequestBody LoginParam loginParam) {
        return userService.register(loginParam);
    }

    // 找回密码
    @PostMapping("findPassword")
    public R findPassword(@RequestBody LoginParam loginParam) {
        return userService.findPassword(loginParam);
    }
}

🥭 视频演示

最后看看视频演示的效果

SpringBoot+Vue实现邮箱登录注册找回密码

🌹 结束语

好了,整篇的教程呢到这也就结束,整篇教程即为原创一字一字手敲,也花了心思想怎么写怎么设计才能更好的直观简洁展示给大家,让大家能看懂

最后,关于教程还有什么不懂的可以评论区留言,我一定会回复的,或者有什么更好的建议和想法也可以在评论区留言,看到好的我会一一采纳,感谢大家的支持

再一次附上Gitee开源地址:https://gitee.com/yuandewei/Yuan-SpringBoot/tree/master 不用大伙翻上去复制了

  • 都看到这里啦,点点赞呀 😋
  • 感谢阅读 😘

有关SpringBoot+Vue实现邮箱登录注册找回密码(附接口文档)的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. Matlab imread()读到了什么 (浅显 当复习文档了) - 2

    matlab打开matlab,用最简单的imread方法读取一个图像clcclearimg_h=imread('hua.jpg');返回一个数组(矩阵),往往是a*b*cunit8类型解释一下这个三维数组的意思,行数、数和层数,unit8:指数据类型,无符号八位整形,可理解为0~2^8的数三个层数分别代表RGB三个通道图像rgb最常用的是24-位实现方法,即RGB每个通道有256色阶(2^8)。基于这样的24-位RGB模型的色彩空间可以表现256×256×256≈1670万色当imshow传入了一个二维数组,它将以灰度方式绘制;可以把图像拆分为rgb三层,可以以灰度的方式观察它figure(1

  3. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  6. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  7. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  8. 阿里云国际版免费试用:如何注册以及注意事项 - 2

    作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。​关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐

  9. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  10. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

随机推荐