【尚庭公寓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 {@Autowiredprivate AliyunSMSProperties properties;@Beanpublic 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.101port: 6379database: 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中是否已经存在该手机号码的keyString 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.发送短信,并将验证码存入RedisString 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.创建并返回TOKENreturn JwtUtil.createToken(userInfo.getId(), loginVo.getPhone());
        }
        
    • 编写HandlerInterceptor

      • 编写AuthenticationInterceptor

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

        @Component
        public class AuthenticationInterceptor implements HandlerInterceptor {@Overridepublic 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;}@Overridepublic 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 {@Autowiredprivate AuthenticationInterceptor authenticationInterceptor;@Overridepublic 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.mzph.cn/news/856473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【BES2500x系列 -- RTX5操作系统】CMSIS-RTOS RTX -- 实时操作系统的核心,为嵌入式系统注入活力 --(一)

&#x1f48c; 所属专栏&#xff1a;【BES2500x系列】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f49…

动态住宅代理IP:多账号矩阵管理的使用

如果您要处理多个在线帐户&#xff0c;选择正确的代理类型对于实现流畅的性能至关重要。但最适合这项工作的代理类型是什么&#xff1f; 为了更好地管理不同平台上的多个账户并优化成本&#xff0c;动态住宅代理IP通常作用在此。 一、什么是轮换代理&#xff1f; 轮换代理充当…

数据仓库与数据库的区别

在数据管理和分析的过程中&#xff0c;我们常常会听到“数据库”和“数据仓库”这两个术语。 虽然它们看起来相似&#xff0c;但实际上它们在设计目的、结构和使用场景上都有显著的区别。 数据库是什么&#xff1f; 数据库&#xff08;Database&#xff09;是一个用于存储和管…

idea2023开发插件入门

idea2023开发插件入门 创建工程 通过 idea plugin 来创建工程 修改 开发语言 默认创建的工程是用scala开发的&#xff0c;但是我不会&#xff0c;就会java,所以改成java创建 build.gradle.kt 为 build.gradlesettings.gradle.kt 为 settings.gradle build.gradle修改为以…

英语恶补ing

ing的词组都有停下来做某事的感觉了。 second hand是形容词了。 wouldnt buy这里的would是情态动词&#xff0c;也是助动词 助动词不能单独使用&#xff0c;要搭配实义动词&#xff0c;这样才能构成谓语 情态动词&#xff08;modals&#xff09;在英语中有多种作用&#xff…

随机森林算法详解

随机森林算法详解 随机森林&#xff08;Random Forest&#xff09;是一种集成学习方法&#xff0c;通过构建多个决策树并将它们的预测结果结合起来&#xff0c;来提高模型的准确性和稳定性。随机森林在分类和回归任务中都表现出色&#xff0c;广泛应用于各类机器学习问题。本文…

【机器学习】基于稀疏识别方法的洛伦兹混沌系统预测

1. 引言 1.1. DNN模型的来由 从数据中识别非线性动态学意味着什么&#xff1f; 假设我们有时间序列数据&#xff0c;这些数据来自一个&#xff08;非线性&#xff09;动态学系统。 识别一个系统意味着基于数据推断该系统的控制方程。换句话说&#xff0c;就是找到动态系统方…

XXL-Job实战(一)

​需求介绍&#xff1a;构建一个分布式短信发送系统&#xff0c;应对双十一活动需向1000万用户快速推送营销短信的挑战&#xff0c;每条数据的业务处理逻辑为0.1s。对于普通任务来说&#xff0c;只有一个线程来处理 可能需要10万秒才能处理完&#xff0c;业务则严重受影响。 常…

5款堪称变态的AI神器,焊死在电脑上永不删除!

一 、AI视频合成工具——Runway&#xff1a; 第一款RunWay&#xff0c;你只需要轻轻一抹&#xff0c;视频中的元素就会被擦除&#xff0c;再来轻轻一抹&#xff0c;直接擦除&#xff0c;不喜欢这个人直接擦除&#xff0c;一点痕迹都看不出来。 除了视频擦除功能外&#xff0c;…

【AI大模型】Transformers大模型库(十):repetition_penalty惩罚系数

目录​​​​​​​ 一、引言 二、惩罚系数repetition_penalty 2.1 概述 2.2 使用说明 2.3 使用示例 三、总结 一、引言 这里的Transformers指的是huggingface开发的大模型库&#xff0c;为huggingface上数以万计的预训练大模型提供预测、训练等服务。 &#x1f917; T…

韩顺平0基础学Java——第27天

p548-568 明天开始坦克大战 Entry 昨天没搞明白的Map、Entry、EntrySet&#xff1a;//GPT教的 Map 和 Entry 的关系 1.Map 接口&#xff1a;它定义了一些方法来操作键值对集合。常用的实现类有 HashMap、TreeMap 等。 2. Entry接口&#xff1a;Entry 是 Map 接口的一个嵌…

vivado TILE

TILE是包含一个或多个SITE对象的设备对象。可编程逻辑TILE 包括各种各样的对象&#xff0c;如SLICE/CLB、BRAM、DSP、I/O块、时钟资源&#xff0c;以及 GT块。从结构上讲&#xff0c;每个瓦片都有许多输入和输出&#xff0c;并且可编程 互连以将瓦片的输入和输出连接到任何其他…

实现一个简易动态线程池

项目完整代码&#xff1a;https://github.com/YYYUUU42/Yu-dynamic-thread-pool 如果该项目对你有帮助&#xff0c;可以在 github 上点个 ⭐ 喔 &#x1f970;&#x1f970; 1. 线程池概念 2. ThreadPoolExecutor 介绍 2.1. ThreadPoolExecutor是如何运行&#xff0c;如何同时…

elementUI的el-table自定义表头

<el-table-column label"昨日仪表里程(KM)" align"left" min-width"190" :render-header"(h, obj) > renderHeader(h, obj, 参数)" > <template slot-scope"scope"> <span>{{ scope.row.firstStartMil…

流程图工具评测:十大热门软件对比

流程图是一种用图形符号和箭头表示工作流程的图形表示方法。它展示了一系列相互关联的步骤&#xff0c;以显示过程中数据或物质的流动、决策点和操作步骤。流程图广泛用于各种领域&#xff0c;包括业务流程、软件开发、工程等&#xff0c;以帮助人们更好地理解和分析工作流程。…

大模型应用开发实践:RAG与Agent

RAG planning是任务拆解的一些方法。 Agent RAG现在基本上推荐LangChain开发框架。而Agent目前没有一个通用的好的开发框架/范式。 学习路径

达梦8 兼容MySQL语法支持非分组项作为查询列

MySQL 数据库迁移到达梦后&#xff0c;部分GROUP BY语句执行失败&#xff0c;报错如下&#xff1a; 问题原因&#xff1a; 对于Oracle数据库&#xff0c;使用GROUP BY时&#xff0c;SELECT中的非聚合列必须出现在GROUP BY后面&#xff0c;否则就会报上面的错误&#xff0c;达梦…

使用宝塔面板搭建Flask项目保姆级喂饭教程

目录 零.前言 一.准备工作 1.1创建requirements.txt文件 1.2将项目打包为压缩文件 1.3租一台服务器 1.4部署宝塔面板 二.宝塔面板(服务器)上的操作 2.1将本地Flask项目上传到服务器 2.2添加Python项目 2.3配置Python项目 2.4配置Nginx 2.5宝塔面板放行端口 2.6在服…

首个AI高考评测结果出炉,GPT-4o排名第二

近日&#xff0c;上海人工智能实验室利用其自主研发的“司南”评测体系OpenCompass&#xff0c;对国内外多个知名大模型进行了一场特殊的“高考”。这些来自阿里巴巴、智谱AI、Mistral等机构&#xff0c;以及OpenAI的GPT-4o等“考生”&#xff0c;接受了新课标I卷“语数外”的全…

百万级 QPS 接入层网关架构方案演进

文章目录 前言1、单机架构2、DNS 轮询3、Nginx 单机4、Nginx 主备 Keepalived5、LVS 主备 Keepalived Nginx 集群6、LVS 主备 Keepalived Nginx 集群 DNS 轮询 前言 随着PC、移动互联网的快速发展&#xff0c;越来越多的人通过手机、电脑、平板等设备访问各种各样APP、网…