第八章 项目实战
四、后台功能开发
1. 用户模块开发
1.1 jwt 和 token 介绍
1.1.1 token 介绍
- 令牌(Token):在计算机领域,令牌是一种代表某种访问权限或身份认证信息的令牌。它可以是一串随机生成的字符或数字,用于验证用户的身份或授权用户对特定资源的访问。普通的令牌可能以各种形式出现,如访问令牌、身份令牌、刷新令牌等。
- 简单理解 : 每个用户生成的唯一字符串标识,可以进行用户识别和校验
- 类似技术: 天王盖地虎 , 小鸡炖蘑菇
- 优势: token 验证标识无法直接识别用户的信息,盗取 token 后也无法
登录
程序! 相对安全!
1.1.2 jwt 介绍
- Token 是一项规范和标准(接口)
- JWT(JSON Web Token)是具体可以生成,校验,解析等动作 Token 的技术(实现类)
1.1.3. jwt 工作流程
- 用户提供其凭据(通常是用户名和密码)进行身份验证。
- 服务器对这些凭据进行验证,并在验证成功后创建一个 JWT。
- 服务器将 JWT 发送给客户端,并客户端在后续的请求中将 JWT 附加在请求头或参数中。
- 服务器接收到请求后,验证 JWT 的签名和有效性,并根据 JWT 中的声明进行身份验证和授权操作
1.1.4 jwt 数据组成和包含信息
- JWT 由三部分组成: header(头部).payload(载荷).signature(签名)
- 我们需要理解的是, jwt 可以携带很多信息! 一般情况,需要加入:有效时间,签名秘钥,其他用户标识信息!
- 有效时间为了保证 token 的时效性,过期可以重新登录获取!
- 签名秘钥为了防止其他人随意解析和校验 token 数据!
- 用户信息为了我们自己解析的时候,知道 Token 对应的具体用户!
1.1.5 jwt 使用和测试
1.1.5.1 导入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
1.1.5.2 编写配置
- application.yaml
#jwt配置
jwt:
token:
tokenExpiration: 120 #有效时间,单位分钟
tokenSignKey: headline123456 #当前程序签名秘钥 自定义
1.1.5.3 导入工具类
- 封装 jwt 技术工具类
package com.alex.utils;
import com.alibaba.druid.util.StringUtils;
import io.jsonwebtoken.*;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
@Component
@ConfigurationProperties(prefix = "jwt.token")
public class JwtHelper {
private long tokenExpiration; //有效时间,单位毫秒 1000毫秒 == 1秒
private String tokenSignKey; //当前程序签名秘钥
//生成token字符串
public String createToken(Long userId) {
System.out.println("tokenExpiration = " + tokenExpiration);
System.out.println("tokenSignKey = " + tokenSignKey);
String token = Jwts.builder()
.setSubject("YYGH-USER")
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration*1000*60)) //单位分钟
.claim("userId", userId)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}
//从token字符串获取userid
public Long getUserId(String token) {
if(StringUtils.isEmpty(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
Integer userId = (Integer)claims.get("userId");
return userId.longValue();
}
//判断token是否有效
public boolean isExpiration(String token){
try {
boolean isExpire = Jwts.parser()
.setSigningKey(tokenSignKey)
.parseClaimsJws(token)
.getBody()
.getExpiration().before(new Date());
//没有过期,有效,返回false
return isExpire;
}catch(Exception e) {
//过期出现异常,返回true
return true;
}
}
}
1.1.5.4 使用和测试
@org.springframework.boot.test.context.SpringBootTest
public class SpringBootTest {
@Autowired
private JwtHelper jwtHelper;
@Test
public void test(){
//生成 传入用户标识
String token = jwtHelper.createToken(1L);
System.out.println("token = " + token);
//解析用户标识
int userId = jwtHelper.getUserId(token).intValue();
System.out.println("userId = " + userId);
//校验是否到期! false 未到期 true到期
boolean expiration = jwtHelper.isExpiration(token);
System.out.println("expiration = " + expiration);
}
}
1.2. 登录功能实现
1.2.1 需求描述
- 用户在客户端输入用户名密码并向后端提交,后端根据用户名和密码判断登录是否成功,用户有误或者密码有误响应不同的提示信息!
1.2.2 接口描述
-
url 地址: user/login
-
请求方式:POST
-
请求参数:
{
"username":"zhangsan", //用户名
"userPwd":"123456" //明文密码
}
-
响应数据:
- 成功
{ "code":"200", // 成功状态码 "message":"success" // 成功状态描述 "data":{ "token":"... ..." // 用户id的token } }
- 失败
{ "code":"501", "message":"用户名有误" "data":{} }
{ "code":"503", "message":"密码有误" "data":{} }
1.2.3 实现代码
1.2.3.1 controller
@RestController
@RequestMapping("user")
@CrossOrigin
public class UserController {
@Autowired
private UserService userService;
/**
* 登录需求
* 地址: /user/login
* 方式: post
* 参数:
* {
* "username":"zhangsan", //用户名
* "userPwd":"123456" //明文密码
* }
* 返回:
* {
* "code":"200", // 成功状态码
* "message":"success" // 成功状态描述
* "data":{
* "token":"... ..." // 用户id的token
* }
* }
*
* 大概流程:
* 1. 账号进行数据库查询 返回用户对象
* 2. 对比用户密码(md5加密)
* 3. 成功,根据userId生成token -> map key=token value=token值 - result封装
* 4. 失败,判断账号还是密码错误,封装对应的枚举错误即可
*/
@PostMapping("login")
public Result login(@RequestBody User user){
Result result = userService.login(user);
System.out.println("result = " + result);
return result;
}
}
1.2.3.2 service
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService{
@Autowired
private JwtHelper jwtHelper;
@Autowired
private UserMapper userMapper;
/**
* 登录业务实现
* @param user
* @return result封装
*/
@Override
public Result login(User user) {
//根据账号查询
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername,user.getUsername());
User loginUser = userMapper.selectOne(queryWrapper);
//账号判断
if (loginUser == null) {
//账号错误
return Result.build(null, ResultCodeEnum.USERNAME_ERROR);
}
//判断密码
if (!StringUtils.isEmpty(user.getUserPwd())
&& loginUser.getUserPwd().equals(MD5Util.encrypt(user.getUserPwd())))
{
//账号密码正确
//根据用户唯一标识生成token
String token = jwtHelper.createToken(Long.valueOf(loginUser.getUid()));
Map data = new HashMap();
data.put("token",token);
return Result.ok(data);
}
//密码错误
return Result.build(null,ResultCodeEnum.PASSWORD_ERROR);
}
}
1.3 根据 token 获取用户数据
1.3.1 需求描述
- 客户端发送请求,提交 token 请求头,后端根据 token 请求头获取登录用户的详细信息并响应给客户端进行存储
1.3.2 接口描述
-
url 地址:user/getUserInfo
-
请求方式:GET
-
请求头:
token: token内容
-
响应数据:
- 成功
{ "code": 200, "message": "success", "data": { "loginUser": { "uid": 1, "username": "zhangsan", "userPwd": "", "nickName": "张三" } } }
- 失败
{ "code": 504, "message": "notLogin", "data": null }
1.3.3 代码实现
1.3.3.1 controller
/**
* 地址: user/getUserInfo
* 方式: get
* 请求头: token = token内容
* 返回:
* {
* "code": 200,
* "message": "success",
* "data": {
* "loginUser": {
* "uid": 1,
* "username": "zhangsan",
* "userPwd": "",
* "nickName": "张三"
* }
* }
* }
*
* 大概流程:
* 1.获取token,解析token对应的userId
* 2.根据userId,查询用户数据
* 3.将用户数据的密码置空,并且把用户数据封装到结果中key = loginUser
* 4.失败返回504 (本次先写到当前业务,后期提取到拦截器和全局异常处理器)
*/
@GetMapping("getUserInfo")
public Result userInfo(@RequestHeader String token){
Result result = userService.getUserInfo(token);
return result;
}
1.3.3.2 service
/**
* 查询用户数据
* @param token
* @return result封装
*/
@Override
public Result getUserInfo(String token) {
//1.判定是否有效期
if (jwtHelper.isExpiration(token)) {
//true过期,直接返回未登录
return Result.build(null,ResultCodeEnum.NOTLOGIN);
}
//2.获取token对应的用户
int userId = jwtHelper.getUserId(token).intValue();
//3.查询数据
User user = userMapper.selectById(userId);
if (user != null) {
user.setUserPwd(null);
Map data = new HashMap();
data.put("loginUser",user);
return Result.ok(data);
}
return Result.build(null,ResultCodeEnum.NOTLOGIN);
}
1.4 注册用户名检查
1.4.1 需求描述
- 用户在注册时输入用户名时,立刻将用户名发送给后端,后端根据用户名查询用户名是否可用并做出响应
1.4.2 接口描述
- url 地址:user/checkUserName
- 请求方式:POST
- 请求参数:param 形式
username=zhangsan
-
响应数据:
- 成功
{ "code":"200", "message":"success" "data":{} }
- 失败
{ "code":"505", "message":"用户名占用" "data":{} }
1.4.3 代码实现
1.4.3.1 controller
/**
* url地址:user/checkUserName
* 请求方式:POST
* 请求参数:param形式
* username=zhangsan
* 响应数据:
* {
* "code":"200",
* "message":"success"
* "data":{}
* }
*
* 实现步骤:
* 1. 获取账号数据
* 2. 根据账号进行数据库查询
* 3. 结果封装
*/
@PostMapping("checkUserName")
public Result checkUserName(String username){
Result result = userService.checkUserName(username);
return result;
}
1.4.3.2 service
/**
* 检查账号是否可以注册
*
* @param username 账号信息
* @return
*/
@Override
public Result checkUserName(String username) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername,username);
User user = userMapper.selectOne(queryWrapper);
if (user != null){
return Result.build(null,ResultCodeEnum.USERNAME_USED);
}
return Result.ok(null);
}
1.5 用户注册功能
1.5.1 需求描述
- 客户端将新用户信息发送给服务端,服务端将新用户存入数据库,存入之前做用户名是否被占用校验,校验通过响应成功提示,否则响应失败提示
1.5.2 接口描述
-
url 地址:user/regist
-
请求方式:POST
-
请求参数:
{
"username":"zhangsan",
"userPwd":"123456",
"nickName":"张三"
}
-
响应数据:
- 成功
{ "code":"200", "message":"success" "data":{} }
- 失败
{ "code":"505", "message":"用户名占用" "data":{} }
1.5.3 代码实现
1.5.3.1 controller
/**
* url地址:user/regist
* 请求方式:POST
* 请求参数:
* {
* "username":"zhangsan",
* "userPwd":"123456",
* "nickName":"张三"
* }
* 响应数据:
* {
* "code":"200",
* "message":"success"
* "data":{}
* }
*
* 实现步骤:
* 1. 将密码加密
* 2. 将数据插入
* 3. 判断结果,成 返回200 失败 505
*/
@PostMapping("regist")
public Result regist(@RequestBody User user){
Result result = userService.regist(user);
return result;
}
1.5.3.2 service
@Override
public Result regist(User user) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername,user.getUsername());
Long count = userMapper.selectCount(queryWrapper);
if (count > 0){
return Result.build(null,ResultCodeEnum.USERNAME_USED);
}
user.setUserPwd(MD5Util.encrypt(user.getUserPwd()));
int rows = userMapper.insert(user);
System.out.println("rows = " + rows);
return Result.ok(null);
}