全栈开发

springboot + mybatis plus 示例

需求jdk 17,Ideaj插件:maven-search、MybatisX

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.5</version>
    </parent>

    <groupId>com.learn</groupId>
    <artifactId>springboot-headline-part</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.27</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
        </dependency>
        <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>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

MybatisX插件根据数据库表生成:Mapper、Pojo、Service

resources/application.yml

server:
  port: 8080
  servlet:
    context-path: /

# 连接配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://xxx:3306/datacenter
    username: xxx
    # 纯数字需要两侧添加`12345`
    password: xxx
    driver-class-name: com.mysql.cj.jdbc.Driver
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20


mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: com.learn.pojo
  global-config:
    db-config:
      logic-delete-field: isDeleted # 全局逻辑删除实体属性名
      logic-delete-value: 1 # 逻辑删除
      logic-not-delete-value: 0  # 逻辑未删除
      id-type: auto  # 主键策略自增长
      table-prefix: news_  # 设置表的前缀


jwt:
  token:
    tokenExpiration: 120 # 有效时间 分钟
    tokenSignKey: headline123456 # 当前程序签名密钥 自定义

resources/com/learn/mapper

HeadlineMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.mapper.HeadlineMapper">

    <resultMap id="BaseResultMap" type="com.learn.pojo.Headline">
        <id property="hid" column="hid"/>
        <result property="title" column="title"/>
        <result property="article" column="article"/>
        <result property="type" column="type"/>
        <result property="publisher" column="publisher"/>
        <result property="pageViews" column="page_views"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
        <result property="version" column="version"/>
        <result property="isDeleted" column="is_deleted"/>
    </resultMap>

    <sql id="Base_Column_List">
        hid
        ,title,article,type,publisher,page_views,
        create_time,update_time,version,is_deleted
    </sql>

    <!--    IPage<Map> selectMyPage(IPage page, @Param("portalVo") PortalVo portalVo);-->
    <select id="selectMyPage" resultType="map">
        SELECT hid,
        title,
        page_views pageViews,
        TIMESTAMPDIFF(HOUR, create_time, NOW()) pastHours,
        publisher
        FROM news_headline
        WHERE is_deleted = 0
        <if test="portalVo.keyWords != null and portalVo.keywords.length() > 0">
            AND title LIKE concat('%', #{portalVo.keyWords}, '%')
        </if>
        <if test="portalVo.type != 0">
            AND type = #{portalVo.type}
        </if>
    </select>

    <!--    Map queryDetailMap(Integer hid);-->
    <select id="queryDetailMap" resultType="map">
        SELECT hid,
               title,
               article,
               h.version,
               tname as typeName,
               page_views as pageViews,
               TIMESTAMPDIFF(HOUR, create_time, now()) as pastHours,
               publisher,
               nick_name as author
        FROM news_headline h
        LEFT JOIN news_type t ON h.type = t.tid
        LEFT JOIN news_user u ON h.publisher = u.uid
        WHERE hid = #{hid}
    </select>
</mapper>

TypeMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.mapper.TypeMapper">

    <resultMap id="BaseResultMap" type="com.learn.pojo.Type">
            <id property="tid" column="tid" />
            <result property="tname" column="tname" />
            <result property="version" column="version" />
            <result property="isDeleted" column="is_deleted" />
    </resultMap>

    <sql id="Base_Column_List">
        tid,tname,version,is_deleted
    </sql>
</mapper>

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.mapper.UserMapper">

    <resultMap id="BaseResultMap" type="com.learn.pojo.User">
            <id property="uid" column="uid" />
            <result property="username" column="username" />
            <result property="userPwd" column="user_pwd" />
            <result property="nickName" column="nick_name" />
            <result property="version" column="version" />
            <result property="isDeleted" column="is_deleted" />
    </resultMap>

    <sql id="Base_Column_List">
        uid,username,user_pwd,nick_name,version,is_deleted
    </sql>
</mapper>

pojo

Headline.java

package com.learn.pojo;

import com.baomidou.mybatisplus.annotation.*;

import java.util.Date;
import lombok.Data;

/**
 * @TableName news_headline
 */
@TableName(value ="news_headline")
@Data
public class Headline {
    // 全局配置id auto自增
    @TableId
    private Integer hid;

    private String title;

    private String article;

    private Integer type;

    private Integer publisher;

    private Integer pageViews;

    private Date createTime;

    private Date updateTime;
    @Version
    private Integer version;
    // 全局配置 指定逻辑删除字段属性名
    private Integer isDeleted;
}

Type.java

package com.learn.pojo;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

/**
 * @TableName news_type
 */
@TableName(value ="news_type")
@Data
public class Type {
    @TableId
    private Integer tid;

    private String tname;
    @Version
    private Integer version;

    private Integer isDeleted;
}

User.java

package com.learn.pojo;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

/**
 * @TableName news_user
 */
@TableName(value ="news_user")
@Data
public class User {
    @TableId
    private Integer uid;

    private String username;

    private String userPwd;

    private String nickName;
    @Version
    private Integer version;

    private Integer isDeleted;
}

pojo/vo/PortalVo.java

package com.learn.pojo.vo;

import lombok.Data;

// 定义vo(Value Object)接收前端传过来的参数
// vo 常用于接收前端表单提交、JSON请求体等
// vo 与DTO或Entity区分开,VO通常只包含当前接口需要的字段,避免直接暴露数据库实体
@Data
public class PortalVo {
    private String keyWords;
    private Integer type = 0;
    private Integer pageNum = 1;
    private Integer pageSize = 10;
}

mapper

HeadlineMapper.java

package com.learn.mapper;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.learn.pojo.Headline;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.learn.pojo.vo.PortalVo;
import org.apache.ibatis.annotations.Param;

import java.util.Map;

/**
 * @author DELL
 * @description 针对表【news_headline】的数据库操作Mapper
 * @createDate 2025-10-25 18:06:42
 * @Entity com.learn.pojo.Headline
 */
public interface HeadlineMapper extends BaseMapper<Headline> {
    // 自定义查询头条新闻
    IPage<Map> selectMyPage(IPage page, @Param("portalVo") PortalVo portalVo);

    Map queryDetailMap(Integer hid);
}

TypeMapper.java

package com.learn.mapper;

import com.learn.pojo.Type;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
* @author DELL
* @description 针对表【news_type】的数据库操作Mapper
* @createDate 2025-10-25 18:06:42
* @Entity com.learn.pojo.Type
*/
public interface TypeMapper extends BaseMapper<Type> {

}

UserMapper.java

package com.learn.mapper;

import com.learn.pojo.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
* @author DELL
* @description 针对表【news_user】的数据库操作Mapper
* @createDate 2025-10-25 18:06:42
* @Entity com.learn.pojo.User
*/
public interface UserMapper extends BaseMapper<User> {

}

service

HeadlineService.java

package com.learn.service;

import com.learn.pojo.Headline;
import com.baomidou.mybatisplus.extension.service.IService;
import com.learn.pojo.vo.PortalVo;
import com.learn.utils.Result;

/**
* @author DELL
* @description 针对表【news_headline】的数据库操作Service
* @createDate 2025-10-25 18:06:42
*/
public interface HeadlineService extends IService<Headline> {

    /**
     * 首页数据查询
     * @param portalVo
     * @return
     */
    Result findNewsPage(PortalVo portalVo);

    /**
     * 根据id查询详情
     * @param hid
     * @return
     */
    Result showHeadlineDetail(Integer hid);

    /**
     * 发布头条
     * @param headline
     * @return
     */
    Result publish(Headline headline, String token);

    /**
     * 修改头条数据
     * @param headline
     * @return
     */
    Result updateData(Headline headline);
}

TypeService.java

package com.learn.service;

import com.learn.pojo.Type;
import com.baomidou.mybatisplus.extension.service.IService;
import com.learn.utils.Result;

/**
* @author DELL
* @description 针对表【news_type】的数据库操作Service
* @createDate 2025-10-25 18:06:42
*/
public interface TypeService extends IService<Type> {

    /**
     * 获取所有类型
     * @return
     */
    Result findAllTypes();
}

UserService.java

package com.learn.service;

import com.learn.pojo.User;
import com.baomidou.mybatisplus.extension.service.IService;
import com.learn.utils.Result;

/**
* @author DELL
* @description 针对表【news_user】的数据库操作Service
* @createDate 2025-10-25 18:06:42
*/
public interface UserService extends IService<User> {
    /**
     * 登录业务
     * @param user
     * @return
     */
    Result login(User user);

    /**
     * 根据token获取用户数据
     * @param token
     * @return
     */
    Result getUserInfo(String token);

    /**
     * 检查用户名是否可用
     * @param username
     * @return
     */
    Result checkUserName(String username);

    /**
     * 用户注册
     * @param user
     * @return
     */
    Result regist(User user);
}

service/impl

HeadlineServiceImpl.java

package com.learn.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.learn.pojo.Headline;
import com.learn.pojo.vo.PortalVo;
import com.learn.service.HeadlineService;
import com.learn.mapper.HeadlineMapper;
import com.learn.utils.JwtHelper;
import com.learn.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author DELL
 * @description 针对表【news_headline】的数据库操作Service实现
 * @createDate 2025-10-25 18:06:42
 */
@Service
public class HeadlineServiceImpl extends ServiceImpl<HeadlineMapper, Headline>
        implements HeadlineService {

    @Autowired
    private HeadlineMapper headlineMapper;

    @Autowired
    private JwtHelper jwtHelper;

    /**
     * 首页数据查询
     * 1. 进行分页数据查询
     * 2. 分页数据拼接到Result
     * <p>
     * 注意1:插叙需要自定义语句 自定义mapper方法 携带分页
     * 注意2:返回的结果List<Map>
     *
     * @param portalVo
     * @return
     */
    @Override
    public Result findNewsPage(PortalVo portalVo) {
        Page<Map> page = new Page<>(portalVo.getPageNum(), portalVo.getPageSize());
        headlineMapper.selectMyPage(page, portalVo);
        List<Map> records = page.getRecords();
        Map data = new HashMap();
        data.put("pageData", records);

        Map pageInfo = new HashMap();
        pageInfo.put("pageData", data);
        pageInfo.put("pageNum", page.getCurrent());
        pageInfo.put("pageSize", page.getSize());
        pageInfo.put("totalPage", page.getPages());
        pageInfo.put("totalSize", page.getTotal());
        return Result.ok(pageInfo);
    }

    /**
     * 根据id查询详情
     * 1. 查询对应的数据:多表查询 自定义方法 没有对应实体类返回Map
     * 2. 修改阅读量 + 1 version乐观锁 当前数据对应的版本
     *
     * @param hid
     * @return
     */
    @Override
    public Result showHeadlineDetail(Integer hid) {
        Map data = headlineMapper.queryDetailMap(hid);
        Map headlineMap = new HashMap();
        headlineMap.put("headline", data);

        // 修改阅读量 + 1
        Headline headline = new Headline();
        headline.setHid((Integer) data.get("hid"));
        headline.setVersion((Integer) data.get("version"));
        headline.setPageViews((Integer) data.get("pageViews") + 1);
        headlineMapper.updateById(headline);
        return Result.ok(headlineMap);
    }

    /**
     * 发布头条
     * 1. 补全数据
     *
     * @param headline
     * @return
     */
    @Override
    public Result publish(Headline headline, String token) {
        // 根据token获取用户id
        int userId = jwtHelper.getUserId(token).intValue();
        // 数据装配
        headline.setPublisher(userId);
        headline.setPageViews(0);
        headline.setCreateTime(new Date());
        headline.setUpdateTime(new Date());
        headlineMapper.insert(headline);
        return Result.ok(null);
    }

    /**
     * 修改头条数据
     * 1. hid查询数据的最新version
     * 2. 修改数据的修改时间为当前时间
     *
     * @param headline
     * @return
     */
    @Override
    public Result updateData(Headline headline) {
        Integer version = headlineMapper.selectById(headline.getHid()).getVersion();
        headline.setVersion(version); // 乐观锁
        headline.setUpdateTime(new Date());
        headlineMapper.updateById(headline);
        return Result.ok(null);
    }
}

TypeServiceImpl.java

package com.learn.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.learn.pojo.Type;
import com.learn.service.TypeService;
import com.learn.mapper.TypeMapper;
import com.learn.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* @author DELL
* @description 针对表【news_type】的数据库操作Service实现
* @createDate 2025-10-25 18:06:42
*/
@Service
public class TypeServiceImpl extends ServiceImpl<TypeMapper, Type>
    implements TypeService{

    @Autowired
    private TypeMapper typeMapper;

    /**
     * 查找所有分类
     * @return
     */
    @Override
    public Result findAllTypes() {
        List<Type> types = typeMapper.selectList(null);
        return Result.ok(types);
    }
}

UserServiceImpl.java

package com.learn.service.impl;

import com.alibaba.druid.util.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.learn.pojo.User;
import com.learn.service.UserService;
import com.learn.mapper.UserMapper;
import com.learn.utils.JwtHelper;
import com.learn.utils.MD5Util;
import com.learn.utils.Result;
import com.learn.utils.ResultCodeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/**
 * @author DELL
 * @description 针对表【news_user】的数据库操作Service实现
 * @createDate 2025-10-25 18:06:42
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private JwtHelper jwtHelper;

    /**
     * 登录业务
     * 1. 根据账号查询用户对象
     * 2. 如果用户对象为null,查询失败,账号错误 501
     * 3. 对比密码,失败 返回503
     * 4. 更具用户id生成token token -> result 返回
     *
     * @param user
     * @return
     */
    @Override
    public Result login(User user) {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getUsername, user.getUsername());
        User loginUser = userMapper.selectOne(lambdaQueryWrapper);

        if (loginUser == null) {
            return Result.build(null, ResultCodeEnum.USERNAME_ERROR);
        }

        // 对比密码
        if (!StringUtils.isEmpty(user.getUserPwd())
                && MD5Util.encrypt(user.getUserPwd()).equals(loginUser.getUserPwd())) {
            // 登录成功
            // 根据有用户id生成token
            // 将token封装到result返回
            String token = jwtHelper.createToken(loginUser.getUid().longValue());
            Map data = new HashMap();
            data.put("token", token);
            return Result.ok(data);
        }
        // 密码错误
        return Result.build(null, ResultCodeEnum.PASSWORD_ERROR);
    }

    /**
     * 根据token获取用户数据
     * 1. 校验token有效性 是否在有效期
     * 2. 根据token解析用户id
     * 3. 根据用户id获取用户数据
     * 4. 去掉密码 封装result结果返回
     */
    @Override
    public Result getUserInfo(String token) {
        // token是否过期
        boolean expiration = jwtHelper.isExpiration(token);
        if (expiration) {
            return Result.build(null, ResultCodeEnum.NOTLOGIN);
        }
        int userId = jwtHelper.getUserId(token).intValue();
        User user = userMapper.selectById(userId);
        user.setUserPwd("");
        Map data = new HashMap();
        data.put("loginUser", user);
        return Result.ok(data);
    }

    /**
     * 检查用户名是否可用
     * 1. 检查账号 进行count查询
     * 2. count = 0 可用
     * 3. count > 0 不可用
     */
    @Override
    public Result checkUserName(String username) {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getUsername, username);
        Long count = userMapper.selectCount(lambdaQueryWrapper);
        if (count == 0) {
            return Result.ok(null);
        }
        return Result.build(null, ResultCodeEnum.USERNAME_USED);
    }

    /**
     * 用户注册
     * 1. 检查用户是否注册
     * 2. 密码加密
     * 3. 账号数据保存
     * 4. 返回结果
     * @param user
     * @return
     */
    @Override
    public Result regist(User user) {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getUsername, user.getUsername());
        Long count = userMapper.selectCount(lambdaQueryWrapper);
        if (count > 0) {
            return Result.build(null, ResultCodeEnum.USERNAME_USED);
        }

        user.setUserPwd(MD5Util.encrypt(user.getUserPwd()));
        userMapper.insert(user);
        return Result.ok(null);
    }
}

controller

HeadlineController.java

package com.learn.controller;

import com.learn.pojo.Headline;
import com.learn.service.HeadlineService;
import com.learn.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/headline")
@CrossOrigin
public class HeadlineController {

    @Autowired
    private HeadlineService headlineService;

    // 登录后才可以访问
    @PostMapping("/publish")
    public Result publish(@RequestBody Headline headline, @RequestHeader String token) {
        Result result = headlineService.publish(headline, token);
        return result;
    }

    @PostMapping("/findHeadlineById")
    public Result findHeadlineById(Integer hid) {
        Headline headline = headlineService.getById(hid);
        Map data = new HashMap();
        data.put("headline", headline);
        return Result.ok(data);
    }

    @PostMapping("/update")
    public Result update(@RequestBody Headline headline) {
        Result result = headlineService.updateData(headline);
        return result;
    }

    @PostMapping("/removeByHid")
    public Result removeByHid(Integer hid) {
        headlineService.removeById(hid);
        return Result.ok(null);
    }
}

PortalController.java

package com.learn.controller;

import com.learn.pojo.vo.PortalVo;
import com.learn.service.HeadlineService;
import com.learn.service.TypeService;
import com.learn.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/portal")
@CrossOrigin
public class PortalController {

    @Autowired
    private TypeService typeService;

    @Autowired
    private HeadlineService headlineService;

    @GetMapping("/findAllTypes")
    public Result findAllTypes() {
        Result result = typeService.findAllTypes();
        return result;
    }

    @PostMapping("/findNewsPage")
    public Result findNewsPage(@RequestBody PortalVo portalVo) {
        Result result = headlineService.findNewsPage(portalVo);
        return result;
    }

    @PostMapping("/showHeadlineDetail")
    public Result showHeadlineDetail(Integer hid) {
        Result result = headlineService.showHeadlineDetail(hid);
        return result;
    }
}

UserController.java

package com.learn.controller;

import com.learn.pojo.User;
import com.learn.service.UserService;
import com.learn.utils.JwtHelper;
import com.learn.utils.Result;
import com.learn.utils.ResultCodeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
@CrossOrigin // 跨域
public class UserController {

    @Autowired
    private JwtHelper jwtHelper;

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Result login(@RequestBody User user) {
        Result result = userService.login(user);
        return result;
    }

    @GetMapping("/getUserInfo")
    public Result getUserInfo(@RequestHeader String token) {
        Result result = userService.getUserInfo(token);
        return result;
    }

    @PostMapping("/checkUserName")
    public Result checkUserName(String username) {
        Result result = userService.checkUserName(username);
        return result;
    }

    @PostMapping("/regist")
    public Result regist(@RequestBody User user) {
        Result result = userService.regist(user);
        return result;
    }

    @GetMapping("/checkLogin")
    public Result checkLogin(@RequestHeader String token) {
        boolean expiration = jwtHelper.isExpiration(token);
        if (expiration) {
            return Result.build(null, ResultCodeEnum.NOTLOGIN);
        }
        return Result.ok(null);
    }
}

interceptors

LoginProtectedInterceptor.java

package com.learn.interceptors;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.learn.utils.JwtHelper;
import com.learn.utils.Result;
import com.learn.utils.ResultCodeEnum;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

/**
 * 登录保护拦截器,检查请求头是否包含有效token
 * 有效,放行
 * 无效,504
 */
@Component
public class LoginProtectedInterceptor implements HandlerInterceptor {
    @Autowired
    private JwtHelper jwtHelper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从请求头中获取token
        String token = request.getHeader("token");
        // 检查是否有效
        boolean expiration = jwtHelper.isExpiration(token);
        // 有效放行
        if (!expiration) {
            return true;
        }
        // 无效返回504
        Result result = Result.build(null, ResultCodeEnum.NOTLOGIN);
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(result);
        response.getWriter().print(json);
        return false;
    }
}

config

WebMVCConfig.java

package com.learn.config;


import com.learn.interceptors.LoginProtectedInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {

    @Autowired
    private LoginProtectedInterceptor loginProtectedInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginProtectedInterceptor).addPathPatterns("/headline/**");
    }
}

utils

JwtHelper.java

package com.learn.utils;

import com.alibaba.druid.util.StringUtils;
import io.jsonwebtoken.*;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;

@Data
@Component // 被此注解修饰的类会被自动检测并注册为Spring bean
@ConfigurationProperties(prefix = "jwt.token")
public class JwtHelper {
    private long tokenExpiration; // 有效时间单位毫秒
    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) {
            return true;
        }
    }
}

MD5Util.java

package com.learn.utils;

import org.springframework.stereotype.Component;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * MD5 加密工具类
 * 用于对用户密码等敏感信息进行不可逆加密
 */
@Component
public class MD5Util {

    /**
     * 对输入字符串进行 MD5 加密
     *
     * @param strSrc 待加密的原始字符串(如用户密码)
     * @return 加密后的 32 位小写十六进制字符串
     */
    public static String encrypt(String strSrc) {
        if (strSrc == null || strSrc.isEmpty()) {
            throw new IllegalArgumentException("输入不能为空");
        }

        try {
            // 获取 MD5 消息摘要实例
            MessageDigest md = MessageDigest.getInstance("MD5");
            // 将输入字符串转换为字节数组并进行摘要计算
            byte[] digest = md.digest(strSrc.getBytes());
            // 将字节数组转换为十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : digest) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString(); // 返回 32 位小写 MD5 字符串
        } catch (NoSuchAlgorithmException e) {
            // 理论上不会发生,因为 MD5 是标准算法
            throw new RuntimeException("MD5加密出错!!", e);
        }
    }

    public static void main(String[] args) {
        String encrypt = encrypt("12345");
        System.out.println(encrypt);
    }
}

Result.java

package com.learn.utils;


/**
 * 全局统一返回结果类
 */
public class Result<T> {
    // 返回码
    private Integer code;
    // 返回消息
    private String message;
    // 返回数据
    private T data;

    public Result() {
    }

    // 返回数据
    protected static <T> Result<T> build(T data) {
        Result<T> result = new Result<>();
        if (data != null) {
            result.setData(data);
        }
        return result;
    }

    public static <T> Result<T> build(T body, Integer code, String message) {
        Result<T> result = build(body);
        result.setCode(code);
        result.setMessage(message);
        return result;
    }

    public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
        Result<T> result = build(body);
        result.setCode(resultCodeEnum.getCode());
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }

    public static <T> Result<T> ok(T data) {
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.SUCCESS);
    }

    public Result<T> message(String msg) {
        this.setMessage(msg);
        return this;
    }

    public Result<T> code(Integer code) {
        this.setCode(code);
        return this;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

ResultCodeEnum.java

package com.learn.utils;

public enum ResultCodeEnum {
    SUCCESS(200, "success"),
    USERNAME_ERROR(501, "usernameError"),
    PASSWORD_ERROR(503, "passwordError"),
    NOTLOGIN(504, "notLogin"),
    USERNAME_USED(505, "usernameUsed");

    private Integer code;
    private String message;

    private ResultCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

Main.java

package com.learn;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@MapperScan("com.learn.mapper")
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 防止全表删除、更新
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}