7.1.1、接口说明
参见YAPI接口地址:http://192.168.136.160:3000/project/19/interface/api/94
7.1.2、流程分析
客户端发送请求
服务端调用第三方组件发送验证码
验证码发送成功,存入redis
响应客户端,客户端跳转到输入验证码页面
7.1.3、代码实现
LoginController
@RestController
@RequestMapping("/user")
public class LoginController {
@Autowired
private UserService userService;
/**
* 获取登录验证码
* 请求参数:phone (Map)
* 响应:void
*/
@PostMapping("/login")
public ResponseEntity login(@RequestBody Map map){
String phone =(String) map.get("phone");
userService.sendMsg(phone);
return ResponseEntity.ok(null); //正常返回状态码200
}
}
UserService
@Service
public class UserService {
@Autowired
private SmsTemplate template;
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
* 发送短信验证码
* @param phone
*/
public void sendMsg(String phone) {
//1、随机生成6位数字
//String code = RandomStringUtils.randomNumeric(6);
String code = "123456";
//2、调用template对象,发送手机短信
//template.sendSms(phone,code);
//3、将验证码存入到redis
redisTemplate.opsForValue().set("CHECK_CODE_"+phone,code, Duration.ofMinutes(5));
}
}
7.2.1、简介
JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全
7.2.2、格式
7.2.3、流程
7.2.4、示例
导入依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
编写测试用例:
@Test
public void testCreateToken() {
//生成token
//1、准备数据
Map map = new HashMap();
map.put("id",1);
map.put("mobile","13800138000");
//2、使用JWT的工具类生成token
long now = System.currentTimeMillis();
String token = Jwts.builder()
.signWith(SignatureAlgorithm.HS512, "itcast") //指定加密算法
.setClaims(map) //写入数据
.setExpiration(new Date(now + 30000)) //失效时间
.compact();
System.out.println(token);
}
//解析token
/**
* SignatureException : token不合法
* ExpiredJwtException:token已过期
*/
@Test
public void testParseToken() {
String token = "eyJhbGciOiJIUzUxMiJ9.eyJtb2JpbGUiOiIxMzgwMDEzODAwMCIsImlkIjoxLCJleHAiOjE2MTgzOTcxOTV9.2lQiovogL5tJa0px4NC-DW7zwHFqZuwhnL0HPAZunieGphqnMPduMZ5TtH_mxDrgfiskyAP63d8wzfwAj-MIVw";
try {
Claims claims = Jwts.parser()
.setSigningKey("itcast")
.parseClaimsJws(token)
.getBody();
Object id = claims.get("id");
Object mobile = claims.get("mobile");
System.out.println(id + "--" + mobile);
}catch (ExpiredJwtException e) {
System.out.println("token已过期");
}catch (SignatureException e) {
System.out.println("token不合法");
}
}
通过解析Token得知,如果抛出SignatureException异常表示token不合法,如果抛出ExpiredJwtException异常表示token已过期
7.2.5 JWT工具类
public class JwtUtils {
// TOKEN的有效期1小时(S)
private static final int TOKEN_TIME_OUT = 1 * 3600;
// 加密KEY
private static final String TOKEN_SECRET = "itcast";
// 生成Token
public static String getToken(Map params){
long currentTime = System.currentTimeMillis();
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, TOKEN_SECRET) //加密方式
.setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
.addClaims(params)
.compact();
}
/**
* 获取Token中的claims信息
*/
public static Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(TOKEN_SECRET)
.parseClaimsJws(token).getBody();
}
/**
* 是否有效 true-有效,false-失效
*/
public static boolean verifyToken(String token) {
if(StringUtils.isEmpty(token)) {
return false;
}
try {
Claims claims = Jwts.parser()
.setSigningKey("itcast")
.parseClaimsJws(token)
.getBody();
}catch (Exception e) {
return false;
}
return true;
}
}
用户接收到验证码后,进行输入验证码,点击登录,前端系统将手机号以及验证码提交到服务端进行校验。
7.3.1、接口文档
YAPI接口地址:YApi-高效、易用、功能强大的可视化接口管理平台
7.3.2、LoginController
/**
* 检验登录
*/
@PostMapping("/loginVerification")
public ResponseEntity loginVerification(@RequestBody Map map) {
//1、调用map集合获取请求参数
String phone = (String) map.get("phone");
String code = (String) map.get("verificationCode");
//2、调用userService完成用户登录
Map retMap = userService.loginVerification(phone,code);
//3、构造返回
return ResponseEntity.ok(retMap);
}
7.3.3、UserService
/**
* 验证登录
* @param phone
* @param code
*/
public Map loginVerification(String phone, String code) {
//1、从redis中获取下发的验证码
String redisCode = redisTemplate.opsForValue().get("CHECK_CODE_" + phone);
//2、对验证码进行校验(验证码是否存在,是否和输入的验证码一致)
if(StringUtils.isEmpty(redisCode) || !redisCode.equals(code)) {
//验证码无效
throw new RuntimeException();
}
//3、删除redis中的验证码
redisTemplate.delete("CHECK_CODE_" + phone);
//4、通过手机号码查询用户
User user = userApi.findByMobile(phone);
boolean isNew = false;
//5、如果用户不存在,创建用户保存到数据库中
if(user == null) {
user = new User();
user.setMobile(phone);
user.setPassword(DigestUtils.md5Hex("123456"));
Long userId = userApi.save(user);
user.setId(userId);
isNew = true;
}
//6、通过JWT生成token(存入id和手机号码)
Map tokenMap = new HashMap();
tokenMap.put("id",user.getId());
tokenMap.put("mobile",phone);
String token = JwtUtils.getToken(tokenMap);
//7、构造返回值
Map retMap = new HashMap();
retMap.put("token",token);
retMap.put("isNew",isNew);
return retMap;
}
7.3.4、测试
为了简化实体类中created和updated字段,抽取BasePojo
@Data
public abstract class BasePojo implements Serializable {
@TableField(fill = FieldFill.INSERT) //自动填充
private Date created;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updated;
}
对于created和updated字段,每次操作都需要手动设置。为了解决这个问题,mybatis-plus支持自定义处理器的形式实现保存更新的自动填充
package com.tanhua.dubbo.server.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
Object created = getFieldValByName("created", metaObject);
if (null == created) {
//字段为空,可以进行填充
setFieldValByName("created", new Date(), metaObject);
}
Object updated = getFieldValByName("updated", metaObject);
if (null == updated) {
//字段为空,可以进行填充
setFieldValByName("updated", new Date(), metaObject);
}
}
@Override
public void updateFill(MetaObject metaObject) {
//更新数据时,直接更新字段
setFieldValByName("updated", new Date(), metaObject);
}
}