SpringBoot 整合 Sa-Token

SpringBoot 整合 Sa-Token

一、前言 & 技术简介

1.1 什么是 Sa-Token

官网:https://sa-token.cc/index.html

Sa-Token 是一款轻量级 Java 权限认证框架,主打:登录认证、权限认证、角色认证、单点登录、会话管理,相比 SpringSecurity、Shiro 更加轻量、开箱即用、代码极简、学习成本极低。

1.2 RBAC 权限模型

RBAC 权限模型(用户-角色-权限)

  • 用户(User):具体操作者(admin、test)

  • 角色(Role):权限集合载体(超级管理员、运营人员)

  • 权限(Permission):接口粒度权限标识(user:add、user:delete、user:edit)

核心关系:用户分配角色 → 角色绑定权限 → 用户拥有对应权限

1.3 本项目实现功能

  • 账号密码 BCrypt 加密登录、退出登录、获取当前登录用户

  • 基于 注解鉴权:角色鉴权、权限鉴权

  • 基于 路由拦截鉴权:全局登录拦截、放行指定接口

  • 完整用户 CRUD 接口(分页查询、新增、修改、删除、批量删除、状态修改)

  • 全局异常统一处理(401未登录、403无权限)

  • 密码加密、防重复用户名、禁止删除超级管理员

  • 前后端分离跨域配置、Token 会话管理

1.4 技术栈

  • SpringBoot 2.7.x

  • Sa-Token 1.37.0(最新稳定版)

  • MyBatis-Plus 3.5.x

  • MySQL 8.0

  • Lombok、Hutool

二、数据库设计

标准 RBAC 五张表:用户表、角色表、用户角色关联表、权限表、角色权限关联表

-- 创建数据库
CREATE DATABASE IF NOT EXISTS springboot_satoken_demo DEFAULT CHARACTER SET utf8mb4;
USE springboot_satoken_demo;

-- 1. 用户表
CREATE TABLE `sys_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '登录账号',
  `password` varchar(100) NOT NULL COMMENT '加密密码',
  `nickname` varchar(50) DEFAULT NULL COMMENT '用户昵称',
  `status` tinyint DEFAULT 1 COMMENT '状态 1正常 0禁用',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY uk_username (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';

-- 2. 角色表
CREATE TABLE `sys_role` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(50) NOT NULL COMMENT '角色名称',
  `role_key` varchar(50) NOT NULL COMMENT '角色标识',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY uk_role_key (`role_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

-- 3. 用户角色关联表
CREATE TABLE `sys_user_role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY uk_user_role (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';

-- 4. 权限表
CREATE TABLE `sys_permission` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `perm_key` varchar(100) NOT NULL COMMENT '权限标识',
  `perm_name` varchar(50) NOT NULL COMMENT '权限名称',
  PRIMARY KEY (`id`),
  UNIQUE KEY uk_perm_key (`perm_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';

-- 5. 角色权限关联表
CREATE TABLE `sys_role_perm` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `role_id` bigint NOT NULL,
  `perm_id` bigint NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY uk_role_perm (`role_id`,`perm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';

-- ===================== 初始化测试数据 =====================
-- 密码统一:123456
insert  into `sys_user`(`id`,`username`,`password`,`nickname`,`status`,`create_time`) values 
(1,'admin','$2a$10$uxLWDO3ZUObjeXaIsM7vc.8k6nxVHKgx0h8YTEQzh6iJD5pxmO.em','超级管理员',1,'2026-07-06 14:55:08'),
(2,'test','$2a$10$uxLWDO3ZUObjeXaIsM7vc.8k6nxVHKgx0h8YTEQzh6iJD5pxmO.em','运营员工',1,'2026-07-06 14:55:08');
-- 角色
INSERT INTO sys_role(role_name,role_key) VALUES
('超级管理员','admin'),
('运营人员','operator');

-- 用户分配角色
INSERT INTO sys_user_role(user_id,role_id) VALUES (1,1),(2,2);

-- 权限(适配本项目全部接口)
INSERT INTO sys_permission(perm_key,perm_name) VALUES
('sys:dashboard','查看首页'),
('user:list','用户列表查询'),
('user:add','新增用户'),
('user:edit','编辑用户'),
('user:delete','删除用户');

-- admin 拥有所有权限
INSERT INTO sys_role_perm(role_id,perm_id) VALUES
(1,1),(1,2),(1,3),(1,4),(1,5),
-- operator 仅拥有查询权限
(2,1),(2,2);

三、项目创建 & 依赖配置

image-20260708162607364

3.1 pom.xml 核心依赖

<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>2.7.4</version>
        <relativePath/>
    </parent>

    <groupId>com.yqd</groupId>
    <artifactId>SpringBoot-SaToken-Demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>SpringBoot-SaToken-Demo</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- Spring Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Sa-Token SpringBoot 整合包 -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>1.37.0</version>
        </dependency>

        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Hutool工具 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.22</version>
        </dependency>

        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.2 application.yml 配置(修复所有过期警告)

已修复:activity-timeout 过期、端口冲突规避、前后端分离配置

# 服务端口
server:
  port: 8080

# 数据库配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/springboot_satoken_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456

# MyBatis-Plus配置
mybatis-plus:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.yqd.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# Sa-Token 全局配置
sa-token:
  # token名称
  token-name: satoken
  # token有效期 单位s 30分钟
  timeout: 1800
  # 临时有效期 无操作自动续期
  active-timeout: 1200
  # 单设备登录
  is-single-login: true
  # 关闭cookie自动注入(前后端分离推荐)
  is-read-cookie: false
  # 打印日志
  is-print: true

四、核心代码实现

4.1 统一返回结果 Result

package com.yqd.common;

import lombok.Data;

@Data
public class Result<T> {
    private Integer code;
    private String msg;
    private T data;

    public static <T> Result<T> ok(T data) {
        Result<T> r = new Result<>();
        r.setCode(200);
        r.setMsg("操作成功");
        r.setData(data);
        return r;
    }

    public static <T> Result<T> fail(Integer code, String msg) {
        Result<T> r = new Result<>();
        r.setCode(code);
        r.setMsg(msg);
        return r;
    }
}

4.2 全局异常处理器

package com.yqd.common;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 未登录
    @ExceptionHandler(NotLoginException.class)
    public Result<?> notLogin(NotLoginException e) {
        return Result.fail(401, "用户未登录,请先登录");
    }

    // 无角色
    @ExceptionHandler(NotRoleException.class)
    public Result<?> noRole(NotRoleException e) {
        return Result.fail(403, "当前账号无访问角色权限:" + e.getRole());
    }

    // 无权限
    @ExceptionHandler(NotPermissionException.class)
    public Result<?> noPerm(NotPermissionException e) {
        return Result.fail(403, "当前账号无操作权限:" + e.getPermission());
    }

    // 其他异常
    @ExceptionHandler(Exception.class)
    public Result<?> otherError(Exception e) {
        e.printStackTrace();
        return Result.fail(500, "系统异常:" + e.getMessage());
    }
}

4.3 实体类

SysUser

package com.yqd.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("sys_user")
public class SysUser {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private String nickname;
    private Integer status;
    private LocalDateTime createTime;
}

SysRole

package com.yqd.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("sys_role")
public class SysRole {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String roleName;
    private String roleKey;
    private LocalDateTime createTime;
}

4.4 Mapper 层

package com.yqd.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yqd.entity.SysUser;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;

public interface SysUserMapper extends BaseMapper<SysUser> {

    // 根据用户ID查询角色标识集合
    @Select("SELECT r.role_key FROM sys_role r " +
            "LEFT JOIN sys_user_role ur ON r.id = ur.role_id " +
            "WHERE ur.user_id = #{userId}")
    List<String> getRoleKeysByUserId(@Param("userId") Long userId);

    // 根据用户ID查询权限标识集合
    @Select("SELECT p.perm_key FROM sys_permission p " +
            "LEFT JOIN sys_role_perm rp ON p.id = rp.perm_id " +
            "LEFT JOIN sys_user_role ur ON rp.role_id = ur.role_id " +
            "WHERE ur.user_id = #{userId}")
    List<String> getPermKeysByUserId(@Param("userId") Long userId);
}

4.5 Service 层

package com.yqd.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yqd.entity.SysUser;
import com.yqd.mapper.SysUserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class UserService extends ServiceImpl<SysUserMapper, SysUser> {

    @Resource
    private SysUserMapper userMapper;

    // 根据用户名查询用户
    public SysUser getUserByUsername(String username) {
        return this.lambdaQuery().eq(SysUser::getUsername, username).one();
    }

    // 获取用户角色标识
    public List<String> getUserRoleList(Long userId) {
        return userMapper.getRoleKeysByUserId(userId);
    }

    // 获取用户权限标识
    public List<String> getUserPermList(Long userId) {
        return userMapper.getPermKeysByUserId(userId);
    }
}

4.6配置类

Sa-Token 核心配置类

package com.yqd.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import com.yqd.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;
import java.util.List;

/**
 * Sa-Token 权限自定义实现 + 路由拦截配置
 */
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {

    @Resource
    private UserService userService;

    // ===================== 1. 自定义权限数据源(核心RBAC) =====================
    @Bean
    public StpInterface stpInterface() {
        return new StpInterface() {
            /**
             * 获取当前登录用户的权限集合
             */
            @Override
            public List<String> getPermissionList(Object loginId, String loginType) {
                Long userId = Long.parseLong(loginId.toString());
                return userService.getUserPermList(userId);
            }

            /**
             * 获取当前登录用户的角色集合
             */
            @Override
            public List<String> getRoleList(Object loginId, String loginType) {
                Long userId = Long.parseLong(loginId.toString());
                return userService.getUserRoleList(userId);
            }
        };
    }

    // ===================== 2. 注册拦截器,配置路由鉴权 =====================
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaInterceptor(handler -> {
                    // 只写权限校验,不用处理登录放行
                    SaRouter.match("/user/delete", r -> StpUtil.checkRole("admin"));
                    SaRouter.match("/user/add", r -> StpUtil.checkPermission("user:add"));
                    // 全局强制登录校验
                    SaRouter.match("/**", r -> StpUtil.checkLogin());
                }))
                .addPathPatterns("/**")
                // 此处统一放行登录接口,框架自动跳过校验
                .excludePathPatterns("/auth/login");
    }

    // ===================== 3. 全局跨域、统一返回过滤器(前后端分离必备) =====================
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
                .addInclude("/**")
                .setAuth(obj -> {
                    // 无需额外处理,拦截器已鉴权
                })
                .setBeforeAuth(obj -> {
                    // 跨域配置
                    SaHolder.getResponse()
                            .setHeader("Access-Control-Allow-Origin", "*")
                            .setHeader("Access-Control-Allow-Methods", "*")
                            .setHeader("Access-Control-Max-Age", "3600")
                            .setHeader("Access-Control-Allow-Headers", "*");
                });
    }
}

MyBatisPlus配置类

package com.yqd.common;

import org.springframework.context.annotation.Configuration;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;

@Configuration
public class MyBatisPlusConfig {
    // MyBatis-Plus分页插件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

4.7 启动类

package com.yqd;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

4.8 权限控制器 AuthController

package com.yqd.controller;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import com.yqd.common.Result;
import com.yqd.entity.SysUser;
import com.yqd.service.UserService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/auth")
public class AuthController {

    @Resource
    private UserService userService;

    /**
     * 登录接口
     */
    @PostMapping("/login")
    public Result<?> login(@RequestParam String username, @RequestParam String password) {
        // 1. 查询用户
        SysUser user = userService.getUserByUsername(username);
        if (user == null) {
            return Result.fail(400, "账号不存在");
        }
        if (user.getStatus() == 0) {
            return Result.fail(400, "账号已禁用");
        }
        // 2. 密码校验 BCrypt
        if (!BCrypt.checkpw(password, user.getPassword())) {
            return Result.fail(400, "密码错误");
        }
        // 3. Sa-Token 登录,传入用户ID作为登录标识
        StpUtil.login(user.getId());
        // 4. 返回token
        Map<String, Object> map = new HashMap<>();
        map.put("token", StpUtil.getTokenValue());
        map.put("userInfo", user);
        return Result.ok(map);
    }

    /**
     * 退出登录
     */
    @PostMapping("/logout")
    public Result<?> logout() {
        StpUtil.logout();
        return Result.ok("退出成功");
    }

    /**
     * 获取当前登录用户信息
     */
    @RequestMapping("/current")
    public Result<?> current() {
        Long userId = StpUtil.getLoginIdAsLong();
        SysUser user = userService.getById(userId);
        return Result.ok(user);
    }
}

4.9 用户完整CRUD控制器 UserController

package com.yqd.controller;

import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yqd.common.Result;
import com.yqd.entity.SysUser;
import com.yqd.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    /**
     * 分页查询用户列表(仅登录即可访问)
     *
     * @param pageNum  当前页
     * @param pageSize 每页条数
     * @param username 用户名模糊查询
     */
    @GetMapping("/list")
    public Result<Page<SysUser>> userList(
            @RequestParam(defaultValue = "1") Long pageNum,
            @RequestParam(defaultValue = "10") Long pageSize,
            @RequestParam(required = false) String username
    ) {
        Page<SysUser> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        // 用户名模糊查询
        if (StrUtil.isNotBlank(username)) {
            wrapper.like(SysUser::getUsername, username);
        }
        // 不返回密码明文
        wrapper.select(SysUser::getId, SysUser::getUsername, SysUser::getNickname, SysUser::getStatus, SysUser::getCreateTime);
        Page<SysUser> pageData = userService.page(page, wrapper);
        return Result.ok(pageData);
    }

    /**
     * 根据ID查询单条用户详情
     */
    @GetMapping("/{id}")
    public Result<SysUser> getInfo(@PathVariable Long id) {
        SysUser user = userService.getById(id);
        // 清空密码,避免明文返回前端
        if (user != null) {
            user.setPassword(null);
        }
        return Result.ok(user);
    }

    /**
     * 新增用户:需要 user:add 权限
     */
    @PostMapping
    @SaCheckPermission("user:add")
    public Result<String> addUser(@RequestBody SysUser user) {
        // 参数简单校验
        if (StrUtil.isBlank(user.getUsername())) {
            return Result.fail(400, "用户名不能为空");
        }
        if (StrUtil.isBlank(user.getPassword())) {
            return Result.fail(400, "密码不能为空");
        }
        // 判断用户名是否重复
        SysUser existUser = userService.getUserByUsername(user.getUsername());
        if (existUser != null) {
            return Result.fail(400, "该用户名已存在");
        }
        // BCrypt加密密码
        user.setPassword(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()));
        // 默认启用状态
        if (user.getStatus() == null) {
            user.setStatus(1);
        }
        userService.save(user);
        return Result.ok("新增用户成功");
    }

    /**
     * 修改用户:需要 user:edit 权限
     */
    @PutMapping
    @SaCheckPermission("user:edit")
    public Result<String> editUser(@RequestBody SysUser user) {
        if (user.getId() == null) {
            return Result.fail(400, "用户ID不能为空");
        }
        // 查询原有用户
        SysUser dbUser = userService.getById(user.getId());
        if (dbUser == null) {
            return Result.fail(400, "用户不存在");
        }
        // 如果前端传了新密码,则重新加密;不传则保留原密码
        if (StrUtil.isNotBlank(user.getPassword())) {
            user.setPassword(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()));
        } else {
            user.setPassword(null);
        }
        userService.updateById(user);
        return Result.ok("修改用户成功");
    }

    /**
     * 删除单个用户:需要 admin角色 + user:delete权限
     */
    @DeleteMapping("/{id}")
    @SaCheckRole("admin")
    @SaCheckPermission("user:delete")
    public Result<String> deleteUser(@PathVariable Long id) {
        // 禁止删除超级管理员id=1
        if (id == 1) {
            return Result.fail(400, "不允许删除超级管理员账号");
        }
        userService.removeById(id);
        return Result.ok("删除用户成功");
    }

    /**
     * 批量删除用户
     */
    @DeleteMapping("/batch")
    @SaCheckRole("admin")
    @SaCheckPermission("user:delete")
    public Result<String> batchDelete(@RequestParam List<Long> ids) {
        // 过滤掉超级管理员ID
        if (ids.contains(1L)) {
            return Result.fail(400, "不允许删除超级管理员账号");
        }
        userService.removeByIds(ids);
        return Result.ok("批量删除成功");
    }

    /**
     * 修改用户状态(启用/禁用)
     */
    @PutMapping("/status")
    @SaCheckPermission("user:edit")
    public Result<String> changeStatus(@RequestParam Long id, @RequestParam Integer status) {
        if (status != 0 && status != 1) {
            return Result.fail(400, "状态参数非法");
        }
        SysUser user = new SysUser();
        user.setId(id);
        user.setStatus(status);
        userService.updateById(user);
        return Result.ok("状态修改成功");
    }
}

五、完整测试流程

5.1 测试账号

  • 超级管理员:admin / 123456 (admin角色,全部权限)

  • 运营账号:test / 123456 (operator角色,仅查询权限)

5.2 测试前置

  • 项目启动端口:8081

  • 所有业务接口请求头携带:satoken: 登录返回的token

5.3 测试步骤

1、未登录访问任意接口

GET/user/list → 返回 401 未登录

2、登录获取Token

POST /auth/login 传参 username、password

3、admin 账号全权限测试

所有 CRUD 接口全部正常通过

4、test 账号权限拦截测试

  • ✅ /user/list、/user/{id} 正常访问

  • ❌ 新增、修改、删除 全部 403 无权限

5、退出登录

POST /auth/logout,旧Token立即失效

posted @ 2026-07-08 16:34  碧水云天4  阅读(20)  评论(0)    收藏  举报