山东聊城做网站,初期做网站,桂林两江四湖夜游时间,龙岩代理记账公司【尚庭公寓SpringBoot Vue 项目实战】移动端登录管理#xff08;二十#xff09; 文章目录 【尚庭公寓SpringBoot Vue 项目实战】移动端登录管理#xff08;二十#xff09;1、登录业务2、接口开发2.1、获取短信验证码2.2、登录和注册接口2.3、查询登录用户的个人信息 1、…【尚庭公寓SpringBoot Vue 项目实战】移动端登录管理二十 文章目录 【尚庭公寓SpringBoot Vue 项目实战】移动端登录管理二十1、登录业务2、接口开发2.1、获取短信验证码2.2、登录和注册接口2.3、查询登录用户的个人信息 1、登录业务
登录管理共需三个接口分别是获取短信验证码、登录、查询登录用户的个人信息。除此之外同样需要编写HandlerInterceptor来为所有受保护的接口增加验证JWT的逻辑。移动端的具体登录流程如下图所示 2、接口开发
2.1、获取短信验证码 前置条件 该接口需向登录手机号码发送短信验证码各大云服务厂商都提供短信服务本项目使用阿里云完成短信验证码功能下面介绍具体配置。 配置短信服务 开通短信服务 在阿里云官网注册阿里云账号并按照指引完成实名认证不认证无法购买服务 找到短信服务选择免费开通 进入短信服务控制台选择快速学习和测试 找到发送测试下的API发送测试绑定测试用的手机号只有绑定的手机号码才能收到测试短信然后配置短信签名和短信模版这里选择**[专用]测试签名/模版**。 创建AccessKey 云账号 AccessKey 是访问阿里云 API 的密钥没有AccessKey无法调用短信服务。点击页面右上角的头像选择AccessKey管理然后创建AccessKey。 查看接口 代码开发 配置所需依赖 如需调用阿里云的短信服务需使用其提供的SDK具体可参考官方文档。 在common模块的pom.xml文件中增加如下内容 dependencygroupIdcom.aliyun/groupIdartifactIddysmsapi20170525/artifactId
/dependency配置发送短信客户端 在application.yml中增加如下内容 aliyun:sms:access-key-id: access-key-idaccess-key-secret: access-key-secretendpoint: dysmsapi.aliyuncs.com注意 上述access-key-id、access-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、登录和注册接口 查看接口 登录注册校验逻辑
前端发送手机号码phone和接收到的短信验证码code到后端。首先校验phone和code是否为空若为空直接响应手机号码为空或者验证码为空若不为空则进入下步判断。根据phone从Redis中查询之前保存的验证码若查询结果为空则直接响应验证码已过期 若不为空则进入下一步判断。比较前端发送的验证码和从Redis中查询出的验证码若不同则直接响应验证码错误若相同则进入下一步判断。使用phone从数据库中查询用户信息若查询结果为空则创建新用户并将用户保存至数据库然后进入下一步判断。判断用户是否被禁用若被禁则直接响应账号被禁用否则进入下一步。创建JWT并响应给前端。 代码开发 接口实现 编写Controller层逻辑 在LoginController中增加如下内容 PostMapping(login)
Operation(summary 登录)
public ResultString 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.判断用户是否存在,不存在则注册创建用户LambdaQueryWrapperUserInfo 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、查询登录用户的个人信息 查看接口 代码开发 查看响应数据结构 查看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 ResultUserInfoVo 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());
}