【探花交友】day01—项目介绍与环境搭建(六)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 【探花交友】day01—项目介绍与环境搭建(六)

3.6.2、SSO短信接口服务

3.6.2.1、mock接口

地址:https://mock-java.itheima.net/project/35/interface/api/581

3.6.2.2、编写接口服务

编写ErrorResult,ErrorResult对象是与前端约定好的结构,如果发生错误需要返回该对象,如果未发生错误响应200即可。

1. package com.tanhua.sso.vo;
2. 
3. import lombok.Builder;
4. import lombok.Data;
5. 
6. @Data
7. @Builder
8. public class ErrorResult {
9. 
10. private String errCode;
11. private String errMessage;
12. }

SmsController:

1. package com.tanhua.sso.controller;
2. 
3. import com.tanhua.sso.service.SmsService;
4. import com.tanhua.sso.vo.ErrorResult;
5. import lombok.extern.slf4j.Slf4j;
6. import org.springframework.beans.factory.annotation.Autowired;
7. import org.springframework.http.HttpStatus;
8. import org.springframework.http.ResponseEntity;
9. import org.springframework.web.bind.annotation.PostMapping;
10. import org.springframework.web.bind.annotation.RequestBody;
11. import org.springframework.web.bind.annotation.RequestMapping;
12. import org.springframework.web.bind.annotation.RestController;
13. 
14. import java.util.Map;
15. 
16. @RestController
17. @RequestMapping("user")
18. @Slf4j
19. public class SmsController {
20. 
21. @Autowired
22. private SmsService smsService;
23. 
24. /**
25.      * 发送短信验证码接口
26.      *
27.      * @param param
28.      * @return
29.      */
30. @PostMapping("login")
31. public ResponseEntity<ErrorResult> sendCheckCode(@RequestBody Map<String, String> param) {
32. ErrorResult errorResult = null;
33. String phone = param.get("phone");
34. try {
35.             errorResult = this.smsService.sendCheckCode(phone);
36. if (null == errorResult) {
37. return ResponseEntity.ok(null);
38.             }
39.         } catch (Exception e) {
40.             log.error("发送短信验证码失败~ phone = " + phone, e);
41.             errorResult = ErrorResult.builder().errCode("000002").errMessage("短信验证码发送失败!").build();
42.         }
43. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResult);
44.     }
45. 
46. }

SmsService:

1. package com.tanhua.sso.service;
2. 
3. import cn.hutool.core.util.RandomUtil;
4. import cn.hutool.core.util.StrUtil;
5. import com.aliyun.dysmsapi20170525.Client;
6. import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
7. import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
8. import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
9. import com.aliyun.teaopenapi.models.Config;
10. import com.tanhua.sso.config.AliyunSMSConfig;
11. import com.tanhua.sso.vo.ErrorResult;
12. import lombok.extern.slf4j.Slf4j;
13. import org.springframework.beans.factory.annotation.Autowired;
14. import org.springframework.data.redis.core.RedisTemplate;
15. import org.springframework.stereotype.Service;
16. 
17. import java.time.Duration;
18. 
19. @Service
20. @Slf4j
21. public class SmsService {
22. 
23. @Autowired
24. private AliyunSMSConfig aliyunSMSConfig;
25. 
26. @Autowired
27. private RedisTemplate<String, String> redisTemplate;
28. 
29. /**
30.      * 发送短信验证码
31.      *
32.      * @param mobile
33.      * @return
34.      */
35. public String sendSms(String mobile) {
36. //随机生成6位数字验证码
37. String code = RandomUtil.randomNumbers(6);
38. try {
39. Config config = new Config()
40.                     .setAccessKeyId(this.aliyunSMSConfig.getAccessKeyId())
41.                     .setAccessKeySecret(this.aliyunSMSConfig.getAccessKeySecret())
42.                     .setEndpoint(this.aliyunSMSConfig.getDomain());
43. 
44. Client client = new Client(config);
45. SendSmsRequest sendSmsRequest = new SendSmsRequest()
46.                     .setPhoneNumbers(mobile)//目标手机号
47.                     .setSignName(this.aliyunSMSConfig.getSignName()) //签名名称
48.                     .setTemplateCode(this.aliyunSMSConfig.getTemplateCode()) //短信模板code
49.                     .setTemplateParam("{\"code\":\"" + code + "\"}"); //模板中变量替换
50. SendSmsResponse sendSmsResponse = client.sendSms(sendSmsRequest);
51. SendSmsResponseBody body = sendSmsResponse.getBody();
52. if (StrUtil.equals("OK", body.getCode())) {
53. return code;
54.             }
55.         } catch (Exception e) {
56.             log.error("发送短信验证码失败!" + mobile, e);
57.         }
58. return null;
59.     }
60. 
61. /**
62.      * 发送短信验证码
63.      * 实现:发送完成短信验证码后,需要将验证码保存到redis中
64.      * @param phone
65.      * @return
66.      */
67. public ErrorResult sendCheckCode(String phone) {
68. String redisKey = "CHECK_CODE_" + phone;
69. 
70. //先判断该手机号发送的验证码是否还未失效
71. if(this.redisTemplate.hasKey(redisKey)){
72. String msg = "上一次发送的验证码还未失效!";
73. return ErrorResult.builder().errCode("000001").errMessage(msg).build();
74.         }
75. 
76. String code = this.sendSms(phone);
77. if(StrUtil.isEmpty(code)){
78. String msg = "发送短信验证码失败!";
79. return ErrorResult.builder().errCode("000000").errMessage(msg).build();
80.         }
81. 
82. //短信发送成功,将验证码保存到redis中,有效期为5分钟
83. this.redisTemplate.opsForValue().set(redisKey, code, Duration.ofMinutes(5));
84. 
85. return null;
86.     }
87. }

7、用户登录

7.1、登录验证码

7.1.1、接口说明

参见YAPI接口地址:http://192.168.136.160:3000/project/19/interface/api/94

7.1.2、流程分析

客户端发送请求

服务端调用第三方组件发送验证码

验证码发送成功,存入redis

响应客户端,客户端跳转到输入验证码页面

7.1.3、代码实现

LoginController

1. @RestController
2. @RequestMapping("/user")
3. public class LoginController {
4. 
5. @Autowired
6. private UserService userService;
7. 
8. /**
9.      * 获取登录验证码
10.      *   请求参数:phone (Map)
11.      *   响应:void
12.      */
13. @PostMapping("/login")
14. public ResponseEntity login(@RequestBody Map map){
15. String phone =(String) map.get("phone");
16.         userService.sendMsg(phone);
17. return ResponseEntity.ok(null); //正常返回状态码200
18.     }
19. }

UserService

1. @Service
2. public class UserService {
3. 
4. @Autowired
5. private SmsTemplate template;
6. 
7. @Autowired
8. private RedisTemplate<String,String> redisTemplate;
9. 
10. /**
11.      * 发送短信验证码
12.      * @param phone
13.      */
14. public void sendMsg(String phone) {
15. //1、随机生成6位数字
16. //String code = RandomStringUtils.randomNumeric(6);
17. String code = "123456";
18. //2、调用template对象,发送手机短信
19. //template.sendSms(phone,code);
20. //3、将验证码存入到redis
21.         redisTemplate.opsForValue().set("CHECK_CODE_"+phone,code, Duration.ofMinutes(5));
22.     }
23.  }

7.2、JWT

7.2.1、简介

JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全

7.2.2、格式

  • JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:A.B.C
  • A由JWT头部信息header加密得到
  • B由JWT用到的身份验证信息json数据加密得到
  • C由A和B加密得到,是校验部分

7.2.3、流程

7.2.4、示例

导入依赖:

1. <dependency>
2. <groupId>io.jsonwebtoken</groupId>
3. <artifactId>jjwt</artifactId>
4. <version>0.9.1</version>
5. </dependency>

编写测试用例:

1. @Test
2. public void testCreateToken() {
3. //生成token
4. //1、准备数据
5. Map map = new HashMap();
6.         map.put("id",1);
7.         map.put("mobile","13800138000");
8. //2、使用JWT的工具类生成token
9. long now = System.currentTimeMillis();
10. String token = Jwts.builder()
11.                 .signWith(SignatureAlgorithm.HS512, "itcast") //指定加密算法
12.                 .setClaims(map) //写入数据
13.                 .setExpiration(new Date(now + 30000)) //失效时间
14.                 .compact();
15.         System.out.println(token);
16.     }
17. 
18. //解析token
19. 
20. /**
21.      * SignatureException : token不合法
22.      * ExpiredJwtException:token已过期
23.      */
24. @Test
25. public void testParseToken() {
26. String token = "eyJhbGciOiJIUzUxMiJ9.eyJtb2JpbGUiOiIxMzgwMDEzODAwMCIsImlkIjoxLCJleHAiOjE2MTgzOTcxOTV9.2lQiovogL5tJa0px4NC-DW7zwHFqZuwhnL0HPAZunieGphqnMPduMZ5TtH_mxDrgfiskyAP63d8wzfwAj-MIVw";
27. try {
28. Claims claims = Jwts.parser()
29.                     .setSigningKey("itcast")
30.                     .parseClaimsJws(token)
31.                     .getBody();
32. Object id = claims.get("id");
33. Object mobile = claims.get("mobile");
34.             System.out.println(id + "--" + mobile);
35.         }catch (ExpiredJwtException e) {
36.             System.out.println("token已过期");
37.         }catch (SignatureException e) {
38.             System.out.println("token不合法");
39.         }
40. 
41.     }

通过解析Token得知,如果抛出SignatureException异常表示token不合法,如果抛出ExpiredJwtException异常表示token已过期

7.2.5 JWT工具类

1. public class JwtUtils {
2. 
3. // TOKEN的有效期1小时(S)
4. private static final int TOKEN_TIME_OUT = 1 * 3600;
5. 
6. // 加密KEY
7. private static final String TOKEN_SECRET = "itcast";
8. 
9. 
10. // 生成Token
11. public static String getToken(Map params){
12. long currentTime = System.currentTimeMillis();
13. return Jwts.builder()
14.                 .signWith(SignatureAlgorithm.HS512, TOKEN_SECRET) //加密方式
15.                 .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
16.                 .addClaims(params)
17.                 .compact();
18.     }
19. 
20. 
21. /**
22.      * 获取Token中的claims信息
23.      */
24. public static Claims getClaims(String token) {
25. return Jwts.parser()
26.                 .setSigningKey(TOKEN_SECRET)
27.                 .parseClaimsJws(token).getBody();
28.     }
29. 
30. 
31. /**
32.      * 是否有效 true-有效,false-失效
33.      */
34. public static boolean verifyToken(String token) {
35. 
36. if(StringUtils.isEmpty(token)) {
37. return false;
38.         }
39. 
40. try {
41. Claims claims = Jwts.parser()
42.                     .setSigningKey("itcast")
43.                     .parseClaimsJws(token)
44.                     .getBody();
45.         }catch (Exception e) {
46. return false;
47.         }
48. 
49. return true;
50.     }
51. }

7.3、用户登录

用户接收到验证码后,进行输入验证码,点击登录,前端系统将手机号以及验证码提交到服务端进行校验。

7.3.1、接口文档

YAPI接口地址:https://mock-java.itheima.net/project/164/interface/api/12593

7.3.2、LoginController

1. /**
2.      * 检验登录
3.      */
4. @PostMapping("/loginVerification")
5. public ResponseEntity loginVerification(@RequestBody Map map) {
6. //1、调用map集合获取请求参数
7. String phone = (String) map.get("phone");
8. String code = (String) map.get("verificationCode");
9. //2、调用userService完成用户登录
10. Map retMap = userService.loginVerification(phone,code);
11. //3、构造返回
12. return ResponseEntity.ok(retMap);
13.     }

7.3.3、UserService

1. /**
2.      * 验证登录
3.      * @param phone
4.      * @param code
5.      */
6. public Map loginVerification(String phone, String code) {
7. //1、从redis中获取下发的验证码
8. String redisCode = redisTemplate.opsForValue().get("CHECK_CODE_" + phone);
9. //2、对验证码进行校验(验证码是否存在,是否和输入的验证码一致)
10. if(StringUtils.isEmpty(redisCode) || !redisCode.equals(code)) {
11. //验证码无效
12. throw new RuntimeException();
13.         }
14. //3、删除redis中的验证码
15.         redisTemplate.delete("CHECK_CODE_" + phone);
16. //4、通过手机号码查询用户
17. User user = userApi.findByMobile(phone);
18. boolean isNew = false;
19. //5、如果用户不存在,创建用户保存到数据库中
20. if(user == null) {
21.             user = new User();
22.             user.setMobile(phone);
23.             user.setPassword(DigestUtils.md5Hex("123456"));
24. Long userId = userApi.save(user);
25.             user.setId(userId);
26.             isNew = true;
27.         }
28. //6、通过JWT生成token(存入id和手机号码)
29. Map tokenMap = new HashMap();
30.         tokenMap.put("id",user.getId());
31.         tokenMap.put("mobile",phone);
32. String token = JwtUtils.getToken(tokenMap);
33. //7、构造返回值
34. Map retMap = new HashMap();
35.         retMap.put("token",token);
36.         retMap.put("isNew",isNew);
37. 
38. return retMap;
39.     }

7.3.4、测试

8、代码优化

8.1 抽取BasePojo

为了简化实体类中created和updated字段,抽取BasePojo

1. @Data
2. public abstract class BasePojo implements Serializable {
3. 
4. @TableField(fill = FieldFill.INSERT) //自动填充
5. private Date created;
6. @TableField(fill = FieldFill.INSERT_UPDATE)
7. private Date updated;
8. 
9. }

8.2 自动填充

对于created和updated字段,每次操作都需要手动设置。为了解决这个问题,mybatis-plus支持自定义处理器的形式实现保存更新的自动填充

1. ```java
2. package com.tanhua.dubbo.server.handler;
3. 
4. import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
5. import org.apache.ibatis.reflection.MetaObject;
6. import org.springframework.stereotype.Component;
7. 
8. import java.util.Date;
9. 
10. @Component
11. public class MyMetaObjectHandler implements MetaObjectHandler {
12. 
13. @Override
14. public void insertFill(MetaObject metaObject) {
15. Object created = getFieldValByName("created", metaObject);
16. if (null == created) {
17. //字段为空,可以进行填充
18.             setFieldValByName("created", new Date(), metaObject);
19.         }
20. 
21. Object updated = getFieldValByName("updated", metaObject);
22. if (null == updated) {
23. //字段为空,可以进行填充
24.             setFieldValByName("updated", new Date(), metaObject);
25.         }
26.     }
27. 
28. @Override
29. public void updateFill(MetaObject metaObject) {
30. //更新数据时,直接更新字段
31.         setFieldValByName("updated", new Date(), metaObject);
32.     }
33. }
34.
相关文章
|
NoSQL Java 关系型数据库
【精选】六款JavaWeb项目源码下载
【精选】六款JavaWeb项目源码下载
【精选】六款JavaWeb项目源码下载
|
5月前
|
存储 移动开发 应用服务中间件
H5漂流瓶交友源码|社交漂流瓶H5源码 附安装教程
H5漂流瓶交友源码|社交漂流瓶H5源码 附安装教程
44 2
|
6月前
|
Java
POETIZE个人博客系统源码 | 最美博客
这是一个 SpringBoot + Vue2 + Vue3 的产物,支持移动端自适应,配有完备的前台和后台管理功能。 网站分两个模块: 博客系统:具有文章,表白墙,图片墙,收藏夹,乐曲,视频播放,留言,友链,时间线,后台管理等功能。 聊天室系统:具有朋友圈(时间线),好友,群等功能。
122 5
POETIZE个人博客系统源码 | 最美博客
|
6月前
|
存储 运维 安全
年终总结:官网搭建知多少
官网搭建的核心工作可以分为以下5个部分,找到每一步的最佳解决方案,专属企业官网就近在眼前啦!
128 1
|
架构师 Java 大数据
美团特供IDEA入门实战笔记CSDN显踪,竟无良程序员白嫖后举报下架
《IntelliJ IDEA入门与实战》蕴含的知识体系甚广。 主要基于IntelliJ IDEA官方文档以及作者实际工作经验为广大读者深入挖掘IDEA不为人知的功能。 是一本理论和实践相结合的图书,将非常完善地介绍IntelliJ IDEA所涵盖的方方面面的知识,并通过大量生动形象的图片以及实战案例加深读者对IntelliJ IDEA的理解,相信读者必会受益匪浅。
|
存储 分布式计算 NoSQL
【探花交友】day01—项目介绍与环境搭建(二)
【探花交友】day01—项目介绍与环境搭建(二)
138 0
|
云安全 安全 Java
【探花交友】day01—项目介绍与环境搭建(五)
【探花交友】day01—项目介绍与环境搭建(五)
159 0
【探花交友】day01—项目介绍与环境搭建(四)
【探花交友】day01—项目介绍与环境搭建(四)
91 0
|
Web App开发 Dubbo NoSQL
【探花交友】day01—项目介绍与环境搭建(三)
【探花交友】day01—项目介绍与环境搭建(三)
257 0
|
搜索推荐 算法 大数据
【探花交友】day01—项目介绍与环境搭建(一)
【探花交友】day01—项目介绍与环境搭建
192 0