SSM - Springboot - MyBatis-Plus 全栈体系(三十四)

第八章 项目实战

四、后台功能开发

1. 用户模块开发

1.1 jwt 和 token 介绍
1.1.1 token 介绍
  • 令牌(Token):在计算机领域,令牌是一种代表某种访问权限或身份认证信息的令牌。它可以是一串随机生成的字符或数字,用于验证用户的身份或授权用户对特定资源的访问。普通的令牌可能以各种形式出现,如访问令牌、身份令牌、刷新令牌等。

SSM - Springboot - MyBatis-Plus 全栈体系(三十四)_第1张图片

  • 简单理解 : 每个用户生成的唯一字符串标识,可以进行用户识别和校验
  • 类似技术: 天王盖地虎 , 小鸡炖蘑菇
  • 优势: token 验证标识无法直接识别用户的信息,盗取 token 后也无法登录程序! 相对安全!
1.1.2 jwt 介绍
  • Token 是一项规范和标准(接口)
  • JWT(JSON Web Token)是具体可以生成,校验,解析等动作 Token 的技术(实现类)

SSM - Springboot - MyBatis-Plus 全栈体系(三十四)_第2张图片

1.1.3. jwt 工作流程
  • 用户提供其凭据(通常是用户名和密码)进行身份验证。
  • 服务器对这些凭据进行验证,并在验证成功后创建一个 JWT。
  • 服务器将 JWT 发送给客户端,并客户端在后续的请求中将 JWT 附加在请求头或参数中。
  • 服务器接收到请求后,验证 JWT 的签名和有效性,并根据 JWT 中的声明进行身份验证和授权操作
1.1.4 jwt 数据组成和包含信息
  • JWT 由三部分组成: header(头部).payload(载荷).signature(签名)

SSM - Springboot - MyBatis-Plus 全栈体系(三十四)_第3张图片

  • 我们需要理解的是, jwt 可以携带很多信息! 一般情况,需要加入:有效时间,签名秘钥,其他用户标识信息!
  • 有效时间为了保证 token 的时效性,过期可以重新登录获取!
  • 签名秘钥为了防止其他人随意解析和校验 token 数据!
  • 用户信息为了我们自己解析的时候,知道 Token 对应的具体用户!
1.1.5 jwt 使用和测试
1.1.5.1 导入依赖
<dependency>
    <groupId>io.jsonwebtokengroupId>
    <artifactId>jjwtartifactId>
    <version>0.9.1version>
dependency>

<dependency>
    <groupId>javax.xml.bindgroupId>
    <artifactId>jaxb-apiartifactId>
    <version>2.3.0version>
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 需求描述

SSM - Springboot - MyBatis-Plus 全栈体系(三十四)_第4张图片

  • 用户在客户端输入用户名密码并向后端提交,后端根据用户名和密码判断登录是否成功,用户有误或者密码有误响应不同的提示信息!
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 需求描述

SSM - Springboot - MyBatis-Plus 全栈体系(三十四)_第5张图片

  • 用户在注册时输入用户名时,立刻将用户名发送给后端,后端根据用户名查询用户名是否可用并做出响应
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 需求描述

SSM - Springboot - MyBatis-Plus 全栈体系(三十四)_第6张图片

  • 客户端将新用户信息发送给服务端,服务端将新用户存入数据库,存入之前做用户名是否被占用校验,校验通过响应成功提示,否则响应失败提示
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);
}

你可能感兴趣的:(SSM+全栈体系,spring,boot,mybatis,后端)