【尚庭公寓SpringBoot + Vue 项目实战】移动端登录管理(二十)

【尚庭公寓SpringBoot + Vue 项目实战】移动端登录管理(二十)


文章目录

      • 【尚庭公寓SpringBoot + Vue 项目实战】移动端登录管理(二十)
        • 1、登录业务
        • 2、接口开发
          • 2.1、获取短信验证码
          • 2.2、登录和注册接口
          • 2.3、查询登录用户的个人信息

1、登录业务

登录管理共需三个接口,分别是获取短信验证码登录查询登录用户的个人信息。除此之外,同样需要编写HandlerInterceptor来为所有受保护的接口增加验证JWT的逻辑。移动端的具体登录流程如下图所示

image-20240620202741568

2、接口开发
2.1、获取短信验证码

前置条件

该接口需向登录手机号码发送短信验证码,各大云服务厂商都提供短信服务,本项目使用阿里云完成短信验证码功能,下面介绍具体配置。

  • 配置短信服务

    • 开通短信服务

      • 在阿里云官网,注册阿里云账号,并按照指引,完成实名认证(不认证,无法购买服务)

      • 找到短信服务,选择免费开通

      • 进入短信服务控制台,选择快速学习和测试

      • 找到发送测试下的API发送测试,绑定测试用的手机号(只有绑定的手机号码才能收到测试短信),然后配置短信签名和短信模版,这里选择**[专用]测试签名/模版**。

    • 创建AccessKey

      云账号 AccessKey 是访问阿里云 API 的密钥,没有AccessKey无法调用短信服务。点击页面右上角的头像,选择AccessKey管理,然后创建AccessKey

      image-20240620203300251

查看接口

image-20240620203128198

代码开发

  • 配置所需依赖

    如需调用阿里云的短信服务,需使用其提供的SDK,具体可参考官方文档。

    common模块的pom.xml文件中增加如下内容

    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>dysmsapi20170525</artifactId>
    </dependency>
    
  • 配置发送短信客户端

    • application.yml中增加如下内容

      aliyun:
        sms:
          access-key-id: <access-key-id>
          access-key-secret: <access-key-secret>
          endpoint: dysmsapi.aliyuncs.com
      

      注意

      上述access-key-idaccess-key-secret需根据实际情况进行修改。

    • common模块中创建com.atguigu.lease.common.sms.AliyunSMSProperties类,内容如下

      @Data
      @ConfigurationProperties(prefix = "aliyun.sms")
      public class AliyunSMSProperties {
      
          private String accessKeyId;
      
          private String accessKeySecret;
      
          private String endpoint;
      }
      
    • common模块中创建com.atguigu.lease.common.sms.AliyunSmsConfiguration类,内容如下

      @Configuration
      @EnableConfigurationProperties(AliyunSMSProperties.class)
      @ConditionalOnProperty(name = "aliyun.sms.endpoint")
      public class AliyunSMSConfiguration {
      
          @Autowired
          private AliyunSMSProperties properties;
      
          @Bean
          public Client smsClient() {
              Config config = new Config();
              config.setAccessKeyId(properties.getAccessKeyId());
              config.setAccessKeySecret(properties.getAccessKeySecret());
              config.setEndpoint(properties.getEndpoint());
              try {
                  return new Client(config);
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
      
          }
      }
      
  • 配置Redis连接参数

    spring: 
      data:
        redis:
          host: 192.168.10.101
          port: 6379
          database: 0
    
  • 编写Controller层逻辑

    LoginController中增加如下内容

    @GetMapping("login/getCode")
    @Operation(summary = "获取短信验证码")
    public Result getCode(@RequestParam String phone) {
        service.getSMSCode(phone);
        return Result.ok();
    }
    
  • 编写Service层逻辑

    • 编写发送短信逻辑

      • SmsService中增加如下内容

        void sendCode(String phone, String verifyCode);
        
      • SmsServiceImpl中增加如下内容

        @Override
        public void sendCode(String phone, String code) {
        
            SendSmsRequest smsRequest = new SendSmsRequest();
            smsRequest.setPhoneNumbers(phone);
            smsRequest.setSignName("阿里云短信测试");
            smsRequest.setTemplateCode("SMS_154950909");
            smsRequest.setTemplateParam("{\"code\":\"" + code + "\"}");
            try {
                client.sendSms(smsRequest);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        
    • 编写生成随机验证码逻辑

      common模块中创建com.atguigu.lease.common.utils.VerifyCodeUtil类,内容如下

      public class VerifyCodeUtil {
          public static String getVerifyCode(int length) {
              StringBuilder builder = new StringBuilder();
              Random random = new Random();
              for (int i = 0; i < length; i++) {
                  builder.append(random.nextInt(10));
              }
              return builder.toString();
          }
      }
      
    • 编写获取短信验证码逻辑

      • LoginServcie中增加如下内容

        void getSMSCode(String phone);
        
      • LoginServiceImpl中增加如下内容

        @Override
        public void getSMSCode(String phone) {
        
            //1. 检查手机号码是否为空
            if (!StringUtils.hasText(phone)) {
                throw new LeaseException(ResultCodeEnum.APP_LOGIN_PHONE_EMPTY);
            }
        
            //2. 检查Redis中是否已经存在该手机号码的key
            String key = RedisConstant.APP_LOGIN_PREFIX + phone;
            boolean hasKey = redisTemplate.hasKey(key);
            if (hasKey) {
                //若存在,则检查其存在的时间
                Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
                if (RedisConstant.APP_LOGIN_CODE_TTL_SEC - expire < RedisConstant.APP_LOGIN_CODE_RESEND_TIME_SEC) {
                    //若存在时间不足一分钟,响应发送过于频繁
                    throw new LeaseException(ResultCodeEnum.APP_SEND_SMS_TOO_OFTEN);
                }
            }
        
            //3.发送短信,并将验证码存入Redis
            String verifyCode = VerifyCodeUtil.getVerifyCode(6);
            smsService.sendCode(phone, verifyCode);
            redisTemplate.opsForValue().set(key, verifyCode, RedisConstant.APP_LOGIN_CODE_TTL_SEC, TimeUnit.SECONDS);
        }
        

        注意:需要注意防止频繁发送短信。

2.2、登录和注册接口

查看接口

image-20240620203512210

登录注册校验逻辑

  • 前端发送手机号码phone和接收到的短信验证码code到后端。
  • 首先校验phonecode是否为空,若为空,直接响应手机号码为空或者验证码为空,若不为空则进入下步判断。
  • 根据phone从Redis中查询之前保存的验证码,若查询结果为空,则直接响应验证码已过期 ,若不为空则进入下一步判断。
  • 比较前端发送的验证码和从Redis中查询出的验证码,若不同,则直接响应验证码错误,若相同则进入下一步判断。
  • 使用phone从数据库中查询用户信息,若查询结果为空,则创建新用户,并将用户保存至数据库,然后进入下一步判断。
  • 判断用户是否被禁用,若被禁,则直接响应账号被禁用,否则进入下一步。
  • 创建JWT并响应给前端。

代码开发

  • 接口实现

    • 编写Controller层逻辑

      LoginController中增加如下内容

      @PostMapping("login")
      @Operation(summary = "登录")
      public Result<String> login(LoginVo loginVo) {
          String token = service.login(loginVo);
          return Result.ok(token);
      }
      
    • 编写Service层逻辑

      • LoginService中增加如下内容

        String login(LoginVo loginVo);
        
      • LoginServiceImpl总增加如下内容

        @Override
        public String login(LoginVo loginVo) {
        
            //1.判断手机号码和验证码是否为空
            if (!StringUtils.hasText(loginVo.getPhone())) {
                throw new LeaseException(ResultCodeEnum.APP_LOGIN_PHONE_EMPTY);
            }
        
            if (!StringUtils.hasText(loginVo.getCode())) {
                throw new LeaseException(ResultCodeEnum.APP_LOGIN_CODE_EMPTY);
            }
        
            //2.校验验证码
            String key = RedisConstant.APP_LOGIN_PREFIX + loginVo.getPhone();
            String code = redisTemplate.opsForValue().get(key);
            if (code == null) {
                throw new LeaseException(ResultCodeEnum.APP_LOGIN_CODE_EXPIRED);
            }
        
            if (!code.equals(loginVo.getCode())) {
                throw new LeaseException(ResultCodeEnum.APP_LOGIN_CODE_ERROR);
            }
        
            //3.判断用户是否存在,不存在则注册(创建用户)
            LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UserInfo::getPhone, loginVo.getPhone());
            UserInfo userInfo = userInfoService.getOne(queryWrapper);
            if (userInfo == null) {
                userInfo = new UserInfo();
                userInfo.setPhone(loginVo.getPhone());
                userInfo.setStatus(BaseStatus.ENABLE);
                userInfo.setNickname("用户-"+loginVo.getPhone().substring(6));
                userInfoService.save(userInfo);
            }
        
            //4.判断用户是否被禁
            if (userInfo.getStatus().equals(BaseStatus.DISABLE)) {
                throw new LeaseException(ResultCodeEnum.APP_ACCOUNT_DISABLED_ERROR);
            }
        
            //5.创建并返回TOKEN
            return JwtUtil.createToken(userInfo.getId(), loginVo.getPhone());
        }
        
    • 编写HandlerInterceptor

      • 编写AuthenticationInterceptor

        web-app模块创建com.atguigu.lease.web.app.custom.interceptor.AuthenticationInterceptor,内容如下

        @Component
        public class AuthenticationInterceptor implements HandlerInterceptor {
        
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
                String token = request.getHeader("access-token");
        
                Claims claims = JwtUtil.parseToken(token);
                Long userId = claims.get("userId", Long.class);
                String username = claims.get("username", String.class);
                LoginUserHolder.setLoginUser(new LoginUser(userId, username));
        
                return true;
            }
        
            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                LoginUserHolder.clear();
            }
        }
        
      • 注册AuthenticationInterceptor

        web-app模块创建com.atguigu.lease.web.app.custom.config.WebMvcConfiguration,内容如下

        @Configuration
        public class WebMvcConfiguration implements WebMvcConfigurer {
        
            @Autowired
            private AuthenticationInterceptor authenticationInterceptor;
        
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(this.authenticationInterceptor).addPathPatterns("/app/**").excludePathPatterns("/app/login/**");
            }
        }
        
  • Knife4j增加认证相关配置

    在增加上述拦截器后,为方便继续调试其他接口,可以获取一个长期有效的Token,将其配置到Knife4j的全局参数中。

2.3、查询登录用户的个人信息

查看接口

image-20240620203705812

代码开发

  • 查看响应数据结构

    查看web-app模块下的com.atguigu.lease.web.app.vo.user.UserInfoVo,内容如下

    @Schema(description = "用户基本信息")
    @Data
    @AllArgsConstructor
    public class UserInfoVo {
    
        @Schema(description = "用户昵称")
        private String nickname;
    
        @Schema(description = "用户头像")
        private String avatarUrl;
    }
    
  • 编写Controller层逻辑

    LoginController中增加如下内容

    @GetMapping("info")
    @Operation(summary = "获取登录用户信息")
    public Result<UserInfoVo> info() {
        UserInfoVo info = service.getUserInfoById(LoginUserHolder.getLoginUser().getUserId());
        return Result.ok(info);
    }
    
  • 编写Service层逻辑

    • LoginService中增加如下内容

      UserInfoVo getUserInfoId(Long id);
      
    • LoginServiceImpl中增加如下内容

      @Override
      public UserInfoVo getUserInfoId(Long id) {
          UserInfo userInfo = userInfoService.getById(id);
          return new UserInfoVo(userInfo.getNickname(), userInfo.getAvatarUrl());
      }
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/750334.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

gbase 8c分布式升级步骤

GBase 8c 多模多态企业级分布式数据库具备高性能、高可用、弹性伸缩、高安全性等特性&#xff0c;可以部署在物理机、虚拟机、容器、私有云和公有云&#xff0c;为关键行业核心系统、互联网业务系统和政企业务系统提供安全、稳定、可靠的数据存储和管理服务。GBase 8c支持行存、…

视频号的视频怎么提取文案?详细教程来啦!

很多人不知道视频号的视频怎么提取文案&#xff0c;今天就和大家详细说说视频号视频提取文案的方法&#xff01; 众所众知视频号的视频无法下载&#xff0c;我们该怎么提取视频号的视频呢&#xff1f; 关于下载视频号的视频&#xff0c;首先给大家两种方案&#xff0c;一种就是…

软考在事业单位可以评高级职称的吗?

导言&#xff1a; 我本人计算机专业&#xff0c;就业于一个事业单位&#xff0c;单位有一个副高级职称数&#xff0c;若干个中级职称数&#xff0c;我可不可以通过软考拿到的中级证书&#xff0c;去找领导申请单位聘我的中级职称&#xff0c;高级职称呢&#xff1f; 以下&…

C++之STL(九)

1、函数对象 什么适合推荐使用函数对象&#xff1f; 需要状态的函数调用: 需要状态的函数调用: 函数对象可以包含成员变量&#xff0c;可以在多次调用中保持状态。这在某些算法中非常有用。 提高性能: 编译器可以更好地优化函数对象&#xff0c;因为它们是具体的类型&#xf…

【路由交换技术】Cisco Packet Tracer基础入门教程(四)

Hello各位&#xff0c;好久不见&#xff0c;第四期我准备讲一下Packet Tracer中DHCP的配置&#xff0c;使用方法。 本章实验我们将拓扑中的某个路由器作为DHCP服务器&#xff08;它仍然可作为路由器使用&#xff09;&#xff0c;通过命令配置DHCP服务。独立的服务器可通过图形化…

基于VUE3+VITE+SpringBoot+Nginx部署项目之跨域配置等问题

前言&#xff1a;遇到问题&#xff0c;解决问题。 第一部分&#xff1a;VUE 配置 1、vite.config.js 文件 server: {proxy: {/api: {target: env.VITE_BASE_URL,changeOrigin: true,secure: false,rewrite: path > path.replace(/^\/api/, )}}}, 2、.env 文件 VITE_BAS…

JavaScript算法之龟兔赛跑

简介:龟兔赛跑算法,又称弗洛伊德循环检测算法,是一种在链表中非常常用的算法。它基于运动学和直觉的基本定律。本文旨在向您简要介绍该算法,并帮助您了解这个看似神奇的算法。 假设高速公路上有两辆车。其中一辆的速度为 x,另一辆的速度为 2x。它们唯一能相遇的条件是它们…

UE4 Unlua的快速使用

目录 Unlua的使用前言下载Unlua插件插件安装快速入门语法汇总模块导入多行字符串官方静态方法调用蓝图方法调用重载蓝图中的方法主动调用被重载的蓝图方法输入绑定动态绑定Lua脚本委托容器使用 延迟与协程的使用C 调用Lua 静态导出自定义类型到Lua使用网络UMG资源释放自定义加载…

如何寻找强势货币和弱势货币?

外汇交易的独特之处在于&#xff0c;它融合了两种货币的价值&#xff0c;其中一种货币的价值通过另一种货币来体现。举例来说&#xff0c;USDJPY外汇反映了美元与日元之间的价值关系&#xff0c;而EURUSD则代表了欧元与美元的价值对比。 通过开仓操作&#xff0c;我们预测一种…

继续捡钱,每天几百块!

每日操作计划&#xff1a; 标普信息科技(161128)&#xff0c;溢价8.5%&#xff0c;限购100&#xff0c;一拖七&#xff0c;单户每天700*8.5%59元 印度基金LOF(164824)&#xff0c;溢价2.6%&#xff0c;限购100&#xff0c;一拖七&#xff0c;单户每天700*2.6%18元 美元债LOF(…

如何解决Oracle中PL Developer过期

如果长时间不使用PL Deveploer&#xff0c;再次打开有可能会出现以下页面&#xff1a; 上方页面说明此软件已经过期&#xff0c;有两种方法可以解决上述问题&#xff0c;第一种&#xff1a; 操作注册表&#xff1a; WinR 输入指令“regedit”打开注册表&#xff0c;出现下方页…

List常用操作比for循环更优雅的写法

private String name; //姓名 private Integer age; //年龄 private Integer departId; //所属部门id } List list new ArrayList<>(); 复制代码 简单遍历 使用lamada表达式之前&#xff0c;如果需要遍历list时&#xff0c;一般使用增强for循环&#xff0c;代码如…

利用圆上两点和圆半径求解圆心坐标

已知圆上两点P1&#xff0c;P2&#xff0c;坐标依次为 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1​,y1​),(x2​,y2​)&#xff0c;圆的半径为 r r r&#xff0c;求圆心的坐标。 假定P1&#xff0c;P2为任意两点&#xff0c;则两点连成线段的中点坐标是 x m i …

Python学习笔记24:进阶篇(十三)常见标准库使用之数据压缩功能模块zlib,gzip,bz2,lzma的学习使用

前言 本文是根据python官方教程中标准库模块的介绍&#xff0c;自己查询资料并整理&#xff0c;编写代码示例做出的学习笔记。 根据模块知识&#xff0c;一次讲解单个或者多个模块的内容。 教程链接&#xff1a;https://docs.python.org/zh-cn/3/tutorial/index.html 数据压缩…

活动|华院计算受邀参加2024全球人工智能技术大会(GAITC),探讨法律大模型如何赋能社会治理

6月22至23日&#xff0c;备受瞩目的2024全球人工智能技术大会&#xff08;GAITC&#xff09;在杭州市余杭区未来科技城隆重举行。本届大会以“交叉、融合、相生、共赢”为主题&#xff0c;集“会、展、赛”为一体&#xff0c;聚“产、学、研”于一堂。值得一提的是&#xff0c;…

如何在线上快速定位bug(干货)

想必有许多人都想我刚进公司一样不会快速定位线上bug吧&#xff0c;不会快速定位bug会大大降低我们的开发效率&#xff0c;随之而来的就是工作质量下降、业绩下滑。 我总结了一些我常用的线上定位技巧&#xff0c;希望能帮助到大家&#xff01; 我这里以使用阿里云日志分析作…

【电路笔记】-MOSFET放大器

MOSFET放大器 文章目录 MOSFET放大器1、概述2、电路图3、电气特性3.1 ** I D = F ( V G S ) I_D=F(V_{GS}) ID​=F(VGS​)**特性3.2 I D = F ( V D S ) I_D=F(V_{DS}) ID​=F(VDS​)特性4、MOSFET放大器5、输入和输出电压6、电压增益7、总结1、概述 在前面的文章中,我们已经…

ThreadLocal 源码浅析

前言 多线程在访问同一个共享变量时很可能会出现并发问题&#xff0c;特别是在多线程对共享变量进行写入时&#xff0c;那么除了加锁还有其他方法避免并发问题吗&#xff1f;本文将详细讲解 ThreadLocal 的使用及其源码。 一、什么是 ThreadLocal&#xff1f; ThreadLocal 是 …

多电商账户为什么要用指纹浏览器?

随着电子商务的蓬勃发展&#xff0c;越来越多的商家选择开设多店来扩大经营规模。然而多店运营也带来了一系列的挑战&#xff0c;其中之一就是账号安全。 1. 了解反检测浏览器和代理服务器 在我们开始讨论如何有效地使用反检测浏览器之前&#xff0c;我们首先需要了解这两个工…

第三十四篇——幸存者偏差:如何避免被已知信息误导?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 人类顶层智慧的总结&#xff0c;一定会让我们在做事的过程中产生降维打击…