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

news2024/11/28 20:52:26

【尚庭公寓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.coloradmin.cn/o/1844667.html

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

相关文章

SFF1006A-ASEMI无人机专用SFF1006A

编辑&#xff1a;ll SFF1006A-ASEMI无人机专用SFF1006A 型号&#xff1a;SFF1006A 品牌&#xff1a;ASEMI 封装&#xff1a;TO-220F 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;10A 最大循环峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;600V 最大…

react实现窗口悬浮框,可拖拽、折叠、滚动

1、效果如下 2、如下两个文件不需要修改 drag.js import React from "react"; import PropTypes from "prop-types";export default class DragM extends React.Component {static propTypes {children: PropTypes.element.isRequired};static defaultP…

神经网络 #数据挖掘 #Python

神经网络是一种受生物神经元系统启发的人工计算模型&#xff0c;用于模仿人脑的学习和决策过程。它由大量互相连接的节点&#xff08;称为神经元&#xff09;组成&#xff0c;这些节点处理和传递信息。神经网络通常包含输入层、隐藏层&#xff08;可有多个&#xff09;和输出层…

MySQL安装教程,包含root账户密码的修改(绿色版安装)---超简单好用

1、下载(mysql-8.0.27-winx64) 2、下载地址:https://dev.mysql.com/downloads/mysql/ 3、已经将 mysql-8.0.27-winx64 文件放在了文章最后,有需要的自取(解压配置即可用)。 4、配置环境变量: 右键点击我的电脑->属性->高级系统设置->高级->环境变量->系…

PostgreSQL的学习心得和知识总结(一百四十五)|深入理解PostgreSQL数据库之ShowTransactionState的使用及父子事务有限状态机

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

Windows11平台C++在VS2022中安装和使用Matplot++绘图库的时候出现的问题和解决方法

Matplot 是一个基于 C 的绘图库&#xff0c;专门用于绘制高质量的数据图表。它提供了一个简洁而强大的接口&#xff0c;使得用户能够轻松地创建各种类型的图表&#xff0c;包括线图、散点图、柱状图、饼图等。Matplot 的设计目标是提供与 MATLAB 相似的绘图体验&#xff0c;同时…

apache-tomcat、apache-maven、apache-zookeeper等的本地环境配置

一、介绍 1.apache-tomcat apache-tomcat充当了一个Web服务器和一个Java应用程序服务器的角色&#xff0c;可以用来部署和运行Java Web应用程序&#xff0c;使开发者能够轻松地部署和管理Java Web应用程序。 2.apache-maven apache-maven是一个项目管理工具&#xff0c;主要…

助力草莓智能自动化采摘,基于YOLOv8全系列【n/s/m/l/x】参数模型开发构建果园种植采摘场景下草莓成熟度智能检测识别系统

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术已经渗透到我们生活的方方面面&#xff0c;从智能家居到自动驾驶&#xff0c;再到医疗健康&#xff0c;其影响力无处不在。然而&#xff0c;当我们把目光转向中国的农业领域时&#xff0c;一个令人惊讶的…

【调试笔记-20240620-Windows- Tauri + Vue 中实现部分区域滚动】

调试笔记-系列文章目录 调试笔记-20240620-Windows- Tauri Vue 中实现部分区域滚动 文章目录 调试笔记-系列文章目录调试笔记-20240620-Windows- Tauri Vue 中实现部分区域滚动 前言一、调试环境操作系统&#xff1a;Windows 10 专业版调试环境调试目标 二、调试步骤搜索相似…

力扣144A

文章目录 1. 题目链接2. 题目代码3. 题目总结4. 代码分析 1. 题目链接 Arrival of the General 2. 题目代码 #include<iostream> using namespace std;int heightOfSoldier[110];int main(){int numberOfSoldier;cin >> numberOfSoldier;int maxHeight -1;int mi…

IT入门知识第七部分《移动开发》(7/10)

目录 第七部分&#xff1a;移动开发 —— 触手可及的未来 引言 1. 移动开发平台 1.1 Android 1.2 iOS 2. 跨平台开发 2.1 React Native 2.2 Flutter 3.移动开发的重要性 3.1 用户体验 3.2 可访问性 3.3 市场机会 4.移动开发面临的挑战 4.1 设备多样性 4.2 系统更…

【uni-app学习手札】

uni-app&#xff08;vue3&#xff09;编写微信小程序 编写uni-app不必拘泥于HBuilder-X编辑器&#xff0c;可用vscode进行编写&#xff0c;在《微信开发者工具》中进行热加载预览&#xff0c; 主要记录使用uni-app过程中自我备忘一些api跟语法&#xff0c;方便以后编写查找使用…

HTML静态网页成品作业(HTML+CSS)——手机电子商城网页(4个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有4个页面。 二、作品演示 三、代…

前端vue实战项目结构、常用编辑器vs code 配置

5.Complete JSDoc Tags 6.Custom CSS and JS Loader 7.Debugger for Chrome 8.EditorConfig for VS Code 9.ESLint ☆☆☆ 10.gitignore 11.GitLens — Git supercharged 12.npm 13.PostCSS syntax !important 14.Vetur ☆ 15.vscode-icons 16.vue-i18n 17.Markdow…

【C语言】排序算法 -------- 计数排序

个人主页 创作不易&#xff0c;感谢大家的关注&#xff01; 文章目录 1. 计数排序的概念2. 计数排序使用场景3. 计数排序思想4. 计数排序实现过程5. 计数排序的效率6. 总结&#xff08;附源代码&#xff09; 1. 计数排序的概念 计数排序是一种非比较的排序算法&#xff0c;其…

雷达标定与解析

融合雷达与解析雷达数据的相关代码。感谢开源社区的贡献。以下代码继承了很多人的工作。 如果是单雷达&#xff1a; 直接进行标定&#xff0c;所以就是接收相关的话题然后发布。 lidar_calibration_params.yaml&#xff1a; calibration:在这个接口里面x_offset: 0.0y_offset:…

前端 CSS 经典:clip-path 裁剪

前言&#xff1a;clip-path 可以把一个元素裁剪成任意你想要的形状。clip-path 裁剪路径生成器&#xff0c;通过改变数值&#xff0c;还可以做出有趣的动画。 效果&#xff1a; 代码实现&#xff1a; <!DOCTYPE html> <html lang"en"><head><…

创新实训2024.06.17日志:大模型微调总结

前段时间其实我们已经部署了大模型&#xff0c;并开放了对外的web接口。不过由于之前某几轮微调实验的大模型在对话时会有异常表现&#xff08;例如响应难以被理解&#xff09;&#xff0c;因此我在项目上线后&#xff0c;监控了数据库里存储的对话记录。确定了最近一段时间部署…

『 Linux 』 进程间通信概述

文章目录 什么是进程间通信为什么要有进程间通信如何进行进程间通信 什么是进程间通信 进程间通信(IPC)指的是在操作系统重,允许两个或者多个进程之间传递信息或者数据的机制; 进程是操作系统重独立运行的实体,即进程间具有独立性,存在自己的地址空间; 因此进程间默认无法直接访…

多模块开发

简介 Git 通过子模块来解决复用模块的问题。 submodule允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中&#xff0c;同时还保持提交的独立。而subtree可以将子模块合并到主模块由主模块完全管理。 git subModule Git地址&#…