0


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

  • 🚀 注重版权,转载请注明原作者和原文链接
  • 🥭 作者:Yuan-Programmer
  • 🍎 个人博客:https://www.xiaoyuan-boke.com 正在持续完善中
  • 🍉 进来的小伙伴点点赞呀

🚀 花了几个小时做了一个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")publicclassUser{@TableId(value ="id", type =IdType.AUTO)privateInteger id;privateString email;privateString password;privateString salt;}

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

@RepositorypublicinterfaceUserMapperextendsBaseMapper<User>{}

⚾ constant静态变量

constant 包下新建 RedisConstant 类,

publicinterfaceRedisConstant{// KeyString EMAIL ="EMAIL_";// 邮箱缓存前缀String EMAIL_REQUEST_VERIFY ="EMAIL_REQUEST_VERIFY_";// 邮箱请求的权限码// 缓存时间int EXPIRE_TEN_SECOND =10;// 10sint 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 枚举类

@GetterpublicenumHttpStatusEnum{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,"非法操作");privatefinalint code;privatefinalString msg;HttpStatusEnum(int code,String msg){this.code = code;this.msg = msg;}}

🥎vo对象

统一结果返回

vo 包下新建 R 统一返回类

@DatapublicclassR{privateBoolean success;privateInteger code;privateString message;privateMap<String,Object> data =newHashMap<>();// 把构造方法私有化privateR(){}// 成功静态方法publicstaticRok(){R r =newR();
        r.setSuccess(true);
        r.setCode(200);
        r.setMessage("成功");return r;}// 失败静态方法publicstaticRerror(){R r =newR();
        r.setSuccess(false);
        r.setCode(20001);
        r.setMessage("失败");return r;}// 失败静态方法publicstaticRerror(HttpStatusEnum httpStatus){R r =newR();
        r.setSuccess(false);
        r.setCode(httpStatus.getCode());
        r.setMessage(httpStatus.getMsg());return r;}publicRsuccess(Boolean success){this.setSuccess(success);returnthis;}publicRmessage(String message){this.setMessage(message);returnthis;}publicRcode(Integer code){this.setCode(code);returnthis;}publicRdata(String key,Object value){this.data.put(key, value);returnthis;}publicRdata(Map<String,Object> map){this.setData(map);returnthis;}}

参数

新建param包,包下新建LoginParam

@GetterpublicclassLoginParam{privateString email;// 邮箱privateString password;// 密码privateString passwordConfirm;// 确认密码privateString code;// 验证码}

🏀utils工具类

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

publicclassStringUtil{/**
     * 邮箱校验
     *
     * @param email 邮箱
     * @return true or false
     */publicstaticbooleancheckEmail(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
     */publicstaticbooleancheckPassword(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();}/**
     * 随机生成六位数字验证码
     */publicstaticStringrandomSixCode(){returnString.valueOf(newRandom().nextInt(899999)+100000);}/**
     * 随机生成加密盐(4位随机字母 + 4位固定特殊字符)
     */publicstaticStringrandomEncryptedSalt(){returnRandomStringUtils.randomAlphabetic(4)+"#!$@";}}

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

🍊 邮箱业务

邮箱服务

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

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

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

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

在这里插入图片描述

@ComponentpublicclassMailService{@ResourceprivateJavaMailSender javaMailSender;@Value("${spring.mail.username}")privateString from;/**
     * 发送简单的邮箱
     *
     * @param to 收件人
     * @param theme 标题
     * @param content 正文内容
     * @param cc 抄送
     */publicvoidsendSimpleMail(Stringto,String theme,String content,String... cc){// 创建邮件对象SimpleMailMessage message =newSimpleMailMessage();try{
            message.setFrom(String.valueOf(newInternetAddress(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 邮件发送异常
     */publicvoidsendHtmlMail(Stringto,String subject,String content,String... cc)throwsMessagingException{MimeMessage message = javaMailSender.createMimeMessage();MimeMessageHelper helper =newMimeMessageHelper(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// 开启线程池publicclassThreadPoolConfig{@Bean("taskExecutor")publicExecutorasyncServiceExecutor(){ThreadPoolTaskExecutor executor =newThreadPoolTaskExecutor();// 设置核兴线程数
        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 线程服务类

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

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

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

公共服务

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

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

publicinterfaceCommonService{/**
     * 获取请求权限码
     * @param emailJson 邮箱
     * @return
     */RgetRequestPermissionCode(String emailJson);/**
     * 发送邮箱验证码
     * @param loginParam (邮箱和权限码)
     * @return
     */RsendEmailCode(LoginParam loginParam);}

新建 CommonServiceImpl 类实现 CommonService 接口

代码每行都写有注释,内容不难,

参数校验 -> 邮箱校验 -> 权限码校验 -> 发送验证码
@ComponentpublicclassCommonServiceImplimplementsCommonService{@AutowiredprivateRedisTemplate<String,String> redisTemplate;@AutowiredprivateThreadService threadService;@OverridepublicRgetRequestPermissionCode(String emailJson){// 非空校验if(StringUtils.isBlank(emailJson))returnR.error(HttpStatusEnum.PARAM_ILLEGAL);// JSON转换,提取email的值String email = JSON.parseObject(emailJson).getString("email").trim();// 邮箱校验if(!StringUtil.checkEmail(email)){returnR.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);returnR.ok().data("permissionCode", permissionCode);}@OverridepublicRsendEmailCode(LoginParam loginParam){if(loginParam ==null)returnR.error(HttpStatusEnum.PARAM_ILLEGAL);// 获取权限码和邮箱String email = loginParam.getEmail();String permissionCode = loginParam.getCode();// 参数校验if(StringUtils.isAnyBlank(email, permissionCode)){returnR.error(HttpStatusEnum.PARAM_ILLEGAL);}elseif(!StringUtil.checkEmail(email)){// 邮箱校验returnR.error(HttpStatusEnum.EMAIL_ERROR);}else{// 权限码比对String rightCode = redisTemplate.opsForValue().get(RedisConstant.EMAIL_REQUEST_VERIFY + email);if(!permissionCode.equals(rightCode)){// 不通过returnR.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);returnR.ok();}}

邮箱服务器接口

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

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

@RestController@RequestMapping("/common")@CrossOriginpublicclassCommonController{@AutowiredprivateCommonService commonService;// 权限码请求接口@PostMapping("code/request")publicRgetRequestPermissionCode(@RequestBodyString emailJson){return commonService.getRequestPermissionCode(emailJson);}// 邮箱验证码接口@PostMapping("code/email")publicRsendEmailCode(@RequestBodyLoginParam loginParam){return commonService.sendEmailCode(loginParam);}}

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

在这里插入图片描述

在这里插入图片描述

🍋 登录注册业务

新建 UserService 接口类

publicinterfaceUserServiceextendsIService<User>{/**
     * 登录
     * @param loginParam (邮箱和密码)
     * @return
     */Rlogin(LoginParam loginParam);/**
     * 注册
     * @param loginParam (邮箱、密码、确认密码、验证码)
     * @return
     */Rregister(LoginParam loginParam);/**
     * 找回密码
     * @param loginParam (邮箱、密码、验证码)
     * @return
     */RfindPassword(LoginParam loginParam);}

新建 UserServiceImpl 类实现 UserService 接口

@Service@TransactionalpublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{@OverridepublicRlogin(LoginParam loginParam){returnnull;}@OverridepublicRregister(LoginParam loginParam){returnnull;}@OverridepublicRfindPassword(LoginParam loginParam){returnnull;}}

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

参数校验 -> 用户是否存在 -> 密码是否正确 -> 登录成功
@OverridepublicRlogin(LoginParam loginParam){if(loginParam ==null)returnR.error(HttpStatusEnum.PARAM_ILLEGAL);// 获取参数String email = loginParam.getEmail();String password = loginParam.getPassword();if(StringUtils.isAnyBlank(email, password)){// 非空returnR.error(HttpStatusEnum.PARAM_ILLEGAL);}elseif(!StringUtil.checkEmail(email)){// 邮箱格式校验returnR.error(HttpStatusEnum.EMAIL_ERROR);}elseif(!StringUtil.checkPassword(password)){// 密码格式returnR.error(HttpStatusEnum.PASSWORD_ERROR);}// 构件条件对象 select salt from user where email = #{email} limit 1QueryWrapper<User> wrapper =newQueryWrapper<>();
        wrapper.select("salt");
        wrapper.eq("email", email);
        wrapper.last("limit 1");// 查询结果User user =this.baseMapper.selectOne(wrapper);if(user ==null){// 用户不存在returnR.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验证码 -> 生成加密盐 -> 加密密码 -> 注册用户
@OverridepublicRregister(LoginParam loginParam){if(loginParam ==null)returnR.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)){// 非空returnR.error(HttpStatusEnum.PARAM_ILLEGAL);}elseif(!StringUtil.checkEmail(email)){// 邮箱格式校验returnR.error(HttpStatusEnum.EMAIL_ERROR);}elseif(!password.equals(passwordConfirm)){// 密码一致校验returnR.error(HttpStatusEnum.PASSWORD_INCONSISTENT);}elseif(!StringUtil.checkPassword(password)|| code.length()!=6){// 密码格式和验证码长度校验returnR.error(HttpStatusEnum.PARAM_ILLEGAL);}// 构造查询条件对象QueryWrapper<User> wrapper =newQueryWrapper<>();
        wrapper.select("id");
        wrapper.eq("email", email);
        wrapper.last("limit 1");// 查询用户,是否存在if(this.baseMapper.selectOne(wrapper)!=null){returnR.error(HttpStatusEnum.EMAIL_ALREADY_EXIST);}// 获取正确的验证码String rightCode = redisTemplate.opsForValue().get(RedisConstant.EMAIL + email);if(!code.equals(rightCode)){// 验证码比对returnR.error(HttpStatusEnum.CODE_ERROR);}// 删除验证码
        redisTemplate.delete(RedisConstant.EMAIL + email);// 注册用户User user =newUser();// 获取加密盐String salt =StringUtil.randomEncryptedSalt();// 邮箱
        user.setEmail(email);// 密码加密(原明文密码 + 随机加密盐) md5加密
        user.setPassword(DigestUtils.md5Hex(password + salt));// 加密盐
        user.setSalt(salt);// 插入数据returnthis.baseMapper.insert(user)==0?R.error(HttpStatusEnum.UNKNOWN_ERROR):R.ok();}
参数校验 -> 用户是否存在 -> 验证码校验 -> 删除redis验证码 -> 覆盖密码
@OverridepublicRfindPassword(LoginParam loginParam){if(loginParam ==null)returnR.error(HttpStatusEnum.PARAM_ILLEGAL);// 获取参数String email = loginParam.getEmail();String password = loginParam.getPassword();String code = loginParam.getCode();if(StringUtils.isAnyBlank(email, password, code)){// 非空returnR.error(HttpStatusEnum.PARAM_ILLEGAL);}elseif(!StringUtil.checkEmail(email)){// 邮箱格式校验returnR.error(HttpStatusEnum.EMAIL_ERROR);}elseif(!StringUtil.checkPassword(password)|| code.length()!=6){// 密码格式和验证码长度校验returnR.error(HttpStatusEnum.PARAM_ILLEGAL);}// 构造查询条件对象QueryWrapper<User> wrapper =newQueryWrapper<>();
        wrapper.select("id","salt");
        wrapper.eq("email", email);
        wrapper.last("limit 1");// 查询用户,是否存在User user =this.baseMapper.selectOne(wrapper);if(user ==null){returnR.error(HttpStatusEnum.USER_NOT_EXIST);}// 获取正确的验证码String rightCode = redisTemplate.opsForValue().get(RedisConstant.EMAIL + email);if(!code.equals(rightCode)){// 验证码比对returnR.error(HttpStatusEnum.CODE_ERROR);}// 删除验证码
        redisTemplate.delete(RedisConstant.EMAIL + email);// 修改密码User user1 =newUser();
        user1.setId(user.getId());
        user1.setPassword(DigestUtils.md5Hex(password + user.getSalt()));// 修改returnthis.baseMapper.updateById(user1)==0?R.error(HttpStatusEnum.UNKNOWN_ERROR):R.ok();}

接口

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

@RestController@RequestMapping("/user")@CrossOriginpublicclassUserController{@AutowiredprivateUserService userService;// 登录@PostMapping("login")publicRlogin(@RequestBodyLoginParam loginParam){return userService.login(loginParam);}// 注册@PostMapping("register")publicRregister(@RequestBodyLoginParam loginParam){return userService.register(loginParam);}// 找回密码@PostMapping("findPassword")publicRfindPassword(@RequestBodyLoginParam loginParam){return userService.findPassword(loginParam);}}

🥭 视频演示

最后看看视频演示的效果

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

🌹 结束语

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

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

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

  • 都看到这里啦,点点赞呀 😋
  • 感谢阅读 😘
标签: java springboot redis

本文转载自: https://blog.csdn.net/weixin_47971206/article/details/124980404
版权归原作者 Yuan-Programmer 所有, 如有侵权,请联系我们删除。

“SpringBoot+Vue实现邮箱登录注册找回密码(附接口文档)”的评论:

还没有评论