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

第八章 项目实战

四、后台功能开发

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 导入依赖
xml 复制代码
<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
yaml 复制代码
#jwt配置
jwt:
  token:
    tokenExpiration: 120 #有效时间,单位分钟
    tokenSignKey: headline123456  #当前程序签名秘钥 自定义
1.1.5.3 导入工具类
  • 封装 jwt 技术工具类
java 复制代码
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 使用和测试
java 复制代码
@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

  • 请求参数:

java 复制代码
{
    "username":"zhangsan", //用户名
    "userPwd":"123456"     //明文密码
}
  • 响应数据:

    • 成功
    java 复制代码
    {
       "code":"200",         // 成功状态码
       "message":"success"   // 成功状态描述
       "data":{
        "token":"... ..." // 用户id的token
      }
    }
    • 失败
    java 复制代码
    {
       "code":"501",
       "message":"用户名有误"
       "data":{}
    }
    java 复制代码
    {
       "code":"503",
       "message":"密码有误"
       "data":{}
    }
1.2.3 实现代码
1.2.3.1 controller
java 复制代码
@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
java 复制代码
@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

  • 请求头:

    java 复制代码
    token: token内容
  • 响应数据:

    • 成功
    java 复制代码
    {
        "code": 200,
        "message": "success",
        "data": {
            "loginUser": {
                "uid": 1,
                "username": "zhangsan",
                "userPwd": "",
                "nickName": "张三"
            }
        }
    }
    • 失败
    java 复制代码
    {
        "code": 504,
        "message": "notLogin",
        "data": null
    }
1.3.3 代码实现
1.3.3.1 controller
java 复制代码
/**
 * 地址: 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
java 复制代码
/**
 * 查询用户数据
 * @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 形式
java 复制代码
username=zhangsan
  • 响应数据:

    • 成功
    java 复制代码
    {
       "code":"200",
       "message":"success"
       "data":{}
    }
    • 失败
    java 复制代码
    {
       "code":"505",
       "message":"用户名占用"
       "data":{}
    }
1.4.3 代码实现
1.4.3.1 controller
java 复制代码
/**
 * 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
java 复制代码
/**
 * 检查账号是否可以注册
 *
 * @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

  • 请求参数:

java 复制代码
{
    "username":"zhangsan",
    "userPwd":"123456",
    "nickName":"张三"
}
  • 响应数据:

    • 成功
    java 复制代码
    {
       "code":"200",
       "message":"success"
       "data":{}
    }
    • 失败
    java 复制代码
    {
       "code":"505",
       "message":"用户名占用"
       "data":{}
    }
1.5.3 代码实现
1.5.3.1 controller
java 复制代码
/**
* 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
java 复制代码
@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);
}
相关推荐
稚辉君.MCA_P8_Java几秒前
DeepSeek Java 单例模式详解
java·spring boot·微服务·单例模式·kubernetes
疯癫的老码农15 分钟前
【小白入门docker】创建Spring Boot Hello World应用制作Docker镜像并运行
java·spring boot·分布式·docker·微服务
Mr.Entropy23 分钟前
Hibernate批量查询方法全面解析
java·后端·hibernate
绝顶少年39 分钟前
Spring 框架中 RestTemplate 的使用方法
java·后端·spring
信安成长日记43 分钟前
golang 写路由的时候要注意
开发语言·后端·golang
Lojarro1 小时前
GO学习2:基本数据类型 与 转换
后端·学习·golang
闲人编程1 小时前
2025年,如何选择Python Web框架:Django, Flask还是FastAPI?
前端·后端·python·django·flask·fastapi·web
karry_k1 小时前
Callable
后端
thginWalker2 小时前
深入剖析 MyBatis 核心原理-模块三:核心处理层(下)
mybatis
golang学习记2 小时前
从0死磕全栈之Next.js App Router 入门实战:5 分钟搭建一个待办事项(Todo List)应用
后端