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

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


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());
      }
      
  • 49
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
### 回答1: springboot+vue项目实战是一种常见的开发模式,它将后端的业务逻辑和前端的用户界面分离开来,使得开发更加高效和灵活。在这种模式下,后端使用springboot框架进行开发,前端使用vue框架进行开发,两者通过RESTful API进行通信。这种模式具有易于维护、易于扩展、易于测试等优点,因此在实际项目中得到了广泛应用。 ### 回答2: SpringBootVue是现代web开发中非常流行的技术栈,他们分别代表了后端和前端的强大。结合起来,可以实现更加灵活和高效的web应用开发。本篇文章将介绍如何用SpringBootVue搭建一个完整的web应用,并展示如何运用他们的优势来提高开发效率和用户体验。 首先是SpringBoot后端的实现。使用SpringBoot可以快速搭建一个轻量级的后端架构,它包含了很多优秀的特性,例如自动配置,简单易用的API,以及集成了很多流行的依赖。开发者只需要一个简单的Maven或Gradle配置就可以开始编写Java代码了。 在实现中,我们使用了一个简单的用户管理系统,它包括了用户注册,登录,以及权限管理等基本功能。我们使用MySQL数据库存储用户信息,同时使用SpringSecurity来处理用户认证和授权。 Vue是一个非常强大的JavaScript框架,它拥有很多出色的特性,例如响应式页面设计,单页面应用等。结合Webpack,Vue可以使用诸如Vue Router,Vuex,Element UI等插件来加速开发。在本项目中,我们使用Vue来实现前端界面,同时使用Vue Router来处理页面路由,Vuex来处理状态管理,以及Element UI来提高视觉效果和交互体验。 最后我们将介绍如何使用SpringBootVue来组合一个完整的web应用。我们将使用axios来发起Ajax请求,同时使用SpringBoot提供的Restful API来处理请求,以及使用Vue显示数据和更新页面。我们也会展示如何使用SpringSecurity来保护API,并如何使用拦截器来控制用户权限。 综合起来,这个项目展示了如何使用SpringBootVue来构建一个完整的web应用,通过它你可以了解到SpringBootVue各自的优点,以及如何合理地结合它们来提高开发效率和用户体验。本项目代码开源可供下载、修改和使用。 ### 回答3: SpringBootVue.js是现在很热门的开发框架,它们都有自己的优势和特点,可以很好地实现前后端分离的开发模式。SpringBoot是一个快速开发框架,可以帮助我们快速搭建后端接口,而Vue.js则是一个轻量级的前端开发框架,可以帮助我们快速搭建前端页面和交互。 SpringBootVue.js可以非常完美地结合起来,形成一个完整的项目。在实践中,我们可以先使用SpringBoot搭建后端接口,然后使用Vue.js搭建前端页面和交互。在前后端分离的开发模式下,前后端的开发可以同时进行,互不干扰,提高了开发效率和代码质量。 具体项目实战中,我们可以根据需求来设计和实现项目。例如,我们可以使用SpringBoot来实现一个简单的登录注册接口,然后使用Vue.js来实现用户登录和注册页面。我们也可以使用SpringBoot实现一个简单的数据接口,然后使用Vue.js来实现数据的展示和交互功能。不管是哪种场景,SpringBootVue.js都可以帮助我们快速搭建一个完整的项目。 在项目实战中,我们还需要注意一些细节和技巧。例如,在使用SpringBoot时,我们应该合理地设计接口和参数,统一返回格式和错误码,便于前端调用和处理。在使用Vue.js时,我们应该注意组件的拆分和复用,尽量避免重复性的代码编写,提高代码的可维护性和可拓展性。同时,我们还应该注意前后端数据的交互和安全性,使用合适的加密和验证方式,避免数据泄露和攻击。 总之,SpringBootVue.js的结合是一个非常不错的选择,通过它们的协作,我们可以快速搭建高效、可维护、安全的项目。在实际项目中,我们需要结合具体场景和需求来设计和实现项目,同时注意细节和技巧,才能取得良好的效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小林学习编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值