Selaa lähdekoodia

添加功能详细文档和数据结构

gengjunfang 1 kuukausi sitten
vanhempi
commit
7c19e26c63

+ 2 - 8
.gitignore

@@ -1,11 +1,5 @@
 /target/
 /.idea/
 /.fastRequest/
-/logs/log_debug.log
-/logs/log_error.log
-/logs/log_info.log
-/logs/log_warn.log
-/logs/debug/log-debug-2025-10-28.0.log
-/logs/error/log-error-2025-10-28.0.log
-/logs/info/log-info-2025-10-28.0.log
-/logs/warn/log-warn-2025-10-28.0.log
+/logs/*
+/.vscode/

+ 1883 - 0
doc/Spring_Security_JWT权限认证完整指南.md

@@ -0,0 +1,1883 @@
+# Spring Security + JWT 权限认证完整指南
+
+## 目录
+- [1. 系统架构概述](#1-系统架构概述)
+- [2. 数据库表设计](#2-数据库表设计)
+- [3. Spring Security认证流程](#3-spring-security认证流程)
+- [4. 核心组件详解](#4-核心组件详解)
+- [5. 开发者实现清单](#5-开发者实现清单)
+- [6. 代码示例](#6-代码示例)
+- [7. 测试指南](#7-测试指南)
+- [8. 常见问题](#8-常见问题)
+
+---
+
+## 1. 系统架构概述
+
+### 1.1 技术栈
+- **Spring Boot 2.2.7**
+- **Spring Security 5.x**
+- **JWT (JSON Web Token)**
+- **Redis** - 存储Token
+- **MySQL 8.0** - 存储用户、角色、权限数据
+- **MyBatis-Plus** - 数据库操作
+
+### 1.2 权限控制级别
+本系统实现了**三级权限控制**:
+1. **接口级别** - URL路径访问控制
+2. **方法级别** - 使用 `@PreAuthorize` 注解
+3. **按钮级别** - 前端根据权限标识显示/隐藏按钮
+
+### 1.3 认证流程架构图
+```
+┌─────────────┐      ┌──────────────┐      ┌────────────────┐
+│   客户端    │─────>│  登录接口    │─────>│ Authentication │
+│             │      │ /auth/login  │      │    Manager     │
+└─────────────┘      └──────────────┘      └────────────────┘
+                            ↓                       ↓
+                            ↓              ┌────────────────┐
+                            ↓              │ UserDetails    │
+                            ↓              │    Service     │
+                            ↓              └────────────────┘
+                            ↓                       ↓
+                     ┌──────────────┐              ↓
+                     │  生成JWT     │      ┌────────────────┐
+                     │  Token       │      │  查询用户      │
+                     └──────────────┘      │  角色+权限     │
+                            ↓              └────────────────┘
+                            ↓                       ↓
+                     ┌──────────────┐              ↓
+                     │ 存储Token到  │<─────────────┘
+                     │   Redis      │
+                     └──────────────┘
+                            ↓
+                     ┌──────────────┐
+                     │ 返回Token给  │
+                     │   客户端     │
+                     └──────────────┘
+```
+
+---
+
+## 2. 数据库表设计
+
+### 2.1 RBAC权限模型
+本系统采用经典的 **RBAC (Role-Based Access Control)** 模型:
+- 用户表 (sys_user)
+- 角色表 (sys_role)
+- 菜单/权限表 (sys_menu)
+- 用户-角色关联表 (sys_user_role)
+- 角色-菜单关联表 (sys_role_menu)
+
+### 2.2 完整SQL脚本
+
+```sql
+-- ====================================
+-- 用户表
+-- ====================================
+CREATE TABLE `sys_user` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+  `username` VARCHAR(50) NOT NULL COMMENT '用户名',
+  `password` VARCHAR(100) NOT NULL COMMENT '密码(BCrypt加密)',
+  `nickname` VARCHAR(50) DEFAULT NULL COMMENT '昵称',
+  `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
+  `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号',
+  `avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像',
+  `status` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
+  `create_by` VARCHAR(50) DEFAULT NULL COMMENT '创建人',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_by` VARCHAR(50) DEFAULT NULL COMMENT '更新人',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `del_flag` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志:0-正常,1-删除',
+  `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_username` (`username`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';
+
+-- ====================================
+-- 角色表
+-- ====================================
+CREATE TABLE `sys_role` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
+  `role_name` VARCHAR(50) NOT NULL COMMENT '角色名称',
+  `role_key` VARCHAR(50) NOT NULL COMMENT '角色权限字符串',
+  `role_sort` INT(4) NOT NULL DEFAULT 0 COMMENT '显示顺序',
+  `status` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
+  `create_by` VARCHAR(50) DEFAULT NULL COMMENT '创建人',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_by` VARCHAR(50) DEFAULT NULL COMMENT '更新人',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `del_flag` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志:0-正常,1-删除',
+  `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_role_key` (`role_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统角色表';
+
+-- ====================================
+-- 菜单/权限表(支持2级菜单 + 按钮权限)
+-- ====================================
+CREATE TABLE `sys_menu` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
+  `menu_name` VARCHAR(50) NOT NULL COMMENT '菜单名称',
+  `parent_id` BIGINT(20) NOT NULL DEFAULT 0 COMMENT '父菜单ID,0表示顶级菜单',
+  `menu_type` CHAR(1) NOT NULL COMMENT '菜单类型:M-目录,C-菜单,F-按钮',
+  `path` VARCHAR(200) DEFAULT NULL COMMENT '路由地址',
+  `component` VARCHAR(255) DEFAULT NULL COMMENT '组件路径',
+  `perms` VARCHAR(100) DEFAULT NULL COMMENT '权限标识(如:system:user:add)',
+  `icon` VARCHAR(100) DEFAULT NULL COMMENT '菜单图标',
+  `order_num` INT(4) NOT NULL DEFAULT 0 COMMENT '显示顺序',
+  `visible` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否可见:0-隐藏,1-显示',
+  `status` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
+  `create_by` VARCHAR(50) DEFAULT NULL COMMENT '创建人',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_by` VARCHAR(50) DEFAULT NULL COMMENT '更新人',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `del_flag` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志:0-正常,1-删除',
+  `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`),
+  KEY `idx_parent_id` (`parent_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统菜单表';
+
+-- ====================================
+-- 用户-角色关联表
+-- ====================================
+CREATE TABLE `sys_user_role` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
+  `role_id` BIGINT(20) NOT NULL COMMENT '角色ID',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_user_role` (`user_id`, `role_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
+
+-- ====================================
+-- 角色-菜单关联表
+-- ====================================
+CREATE TABLE `sys_role_menu` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `role_id` BIGINT(20) NOT NULL COMMENT '角色ID',
+  `menu_id` BIGINT(20) NOT NULL COMMENT '菜单ID',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_role_menu` (`role_id`, `menu_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色菜单关联表';
+
+-- ====================================
+-- 初始化数据
+-- ====================================
+-- 插入超级管理员用户(密码: admin123,已BCrypt加密)
+INSERT INTO `sys_user` (`id`, `username`, `password`, `nickname`, `status`)
+VALUES (1, 'admin', '$2a$10$N.ZOn9G6/YLFixAOPMg/h.z7pCu6v2XyFDtC4q.jeeGm/TEZySmDu', '超级管理员', 1);
+
+-- 插入普通用户(密码: user123,已BCrypt加密)
+INSERT INTO `sys_user` (`id`, `username`, `password`, `nickname`, `status`)
+VALUES (2, 'user', '$2a$10$N.ZOn9G6/YLFixAOPMg/h.z7pCu6v2XyFDtC4q.jeeGm/TEZySmDu', '普通用户', 1);
+
+-- 插入角色
+INSERT INTO `sys_role` (`id`, `role_name`, `role_key`, `role_sort`)
+VALUES (1, '超级管理员', 'admin', 1);
+
+INSERT INTO `sys_role` (`id`, `role_name`, `role_key`, `role_sort`)
+VALUES (2, '普通用户', 'user', 2);
+
+-- 插入一级菜单(目录)
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `icon`, `order_num`, `perms`)
+VALUES (1, '系统管理', 0, 'M', '/system', 'system', 1, NULL);
+
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `icon`, `order_num`, `perms`)
+VALUES (2, '用户中心', 0, 'M', '/user', 'user', 2, NULL);
+
+-- 插入二级菜单
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `order_num`, `perms`)
+VALUES (101, '用户管理', 1, 'C', '/system/user', 'system/user/index', 1, 'system:user:list');
+
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `order_num`, `perms`)
+VALUES (102, '角色管理', 1, 'C', '/system/role', 'system/role/index', 2, 'system:role:list');
+
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `order_num`, `perms`)
+VALUES (103, '菜单管理', 1, 'C', '/system/menu', 'system/menu/index', 3, 'system:menu:list');
+
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `order_num`, `perms`)
+VALUES (201, '个人信息', 2, 'C', '/user/profile', 'user/profile/index', 1, 'user:profile:view');
+
+-- 插入按钮权限(F类型 - 按钮级别权限控制)
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `order_num`, `perms`)
+VALUES (1011, '用户新增', 101, 'F', 1, 'system:user:add');
+
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `order_num`, `perms`)
+VALUES (1012, '用户修改', 101, 'F', 2, 'system:user:edit');
+
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `order_num`, `perms`)
+VALUES (1013, '用户删除', 101, 'F', 3, 'system:user:remove');
+
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `order_num`, `perms`)
+VALUES (1021, '角色新增', 102, 'F', 1, 'system:role:add');
+
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `order_num`, `perms`)
+VALUES (1022, '角色修改', 102, 'F', 2, 'system:role:edit');
+
+INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `order_num`, `perms`)
+VALUES (1023, '角色删除', 102, 'F', 3, 'system:role:remove');
+
+-- 用户-角色关联
+INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (1, 1);
+INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (2, 2);
+
+-- 角色-菜单关联(超级管理员拥有所有权限)
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+VALUES
+(1, 1), (1, 2), (1, 101), (1, 102), (1, 103), (1, 201),
+(1, 1011), (1, 1012), (1, 1013), (1, 1021), (1, 1022), (1, 1023);
+
+-- 普通用户只有查看权限
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+VALUES (2, 2), (2, 201);
+```
+
+### 2.3 权限标识命名规范
+
+权限标识(perms字段)采用 **模块:功能:操作** 三段式命名:
+
+| 权限标识 | 说明 | 示例 |
+|---------|------|------|
+| `system:user:list` | 用户列表查看 | 查询用户列表接口 |
+| `system:user:add` | 用户新增 | 新增用户按钮 |
+| `system:user:edit` | 用户编辑 | 编辑用户按钮 |
+| `system:user:remove` | 用户删除 | 删除用户按钮 |
+| `system:user:export` | 用户导出 | 导出Excel按钮 |
+
+---
+
+## 3. Spring Security认证流程
+
+### 3.1 完整认证流程图
+
+```
+用户登录流程:
+┌──────────────────────────────────────────────────────────────────┐
+│                         1. 用户发起登录请求                       │
+│                    POST /auth/login                               │
+│                    { username, password }                         │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│                    2. Controller接收请求                          │
+│              SysUserController.login()                            │
+│         创建 UsernamePasswordAuthenticationToken                  │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│                 3. AuthenticationManager 认证                     │
+│         调用 authenticate(authenticationToken)                    │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│              4. DaoAuthenticationProvider 处理                    │
+│    - 调用 UserDetailsService.loadUserByUsername()                │
+│    - 获取用户详情(包含密码、角色、权限)                        │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│           5. UserDetailsServiceImpl.loadUserByUsername()          │
+│    ┌──────────────────────────────────────┐                      │
+│    │ a. 查询用户基本信息                  │                      │
+│    │    getUserWithRolesAndPermissions()   │                      │
+│    └──────────────────┬───────────────────┘                      │
+│                       ↓                                           │
+│    ┌──────────────────────────────────────┐                      │
+│    │ b. 查询用户权限列表                  │                      │
+│    │    getPermissionsByUserId()           │                      │
+│    └──────────────────┬───────────────────┘                      │
+│                       ↓                                           │
+│    ┌──────────────────────────────────────┐                      │
+│    │ c. 构建 LoginUser 对象              │                      │
+│    │    包含:用户信息 + 权限列表         │                      │
+│    └──────────────────────────────────────┘                      │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│                      6. 密码校验                                  │
+│    PasswordEncoder.matches(rawPassword, encodedPassword)          │
+│    ✓ 匹配成功 → 继续                                             │
+│    ✗ 匹配失败 → 抛出 BadCredentialsException                     │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│                   7. 认证成功,生成Token                          │
+│    ┌──────────────────────────────────────┐                      │
+│    │ a. 生成 JWT Token                    │                      │
+│    │    JwtUtil.generateToken()            │                      │
+│    │    包含:userId, username, 过期时间   │                      │
+│    └──────────────────┬───────────────────┘                      │
+│                       ↓                                           │
+│    ┌──────────────────────────────────────┐                      │
+│    │ b. 将Token存储到Redis                │                      │
+│    │    key: token:userId                  │                      │
+│    │    value: token                       │                      │
+│    │    expire: 24小时                     │                      │
+│    └──────────────────────────────────────┘                      │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│                    8. 返回Token给客户端                           │
+│    { success: true, token: "xxx", userInfo: {...} }              │
+└──────────────────────────────────────────────────────────────────┘
+
+
+后续请求认证流程:
+┌──────────────────────────────────────────────────────────────────┐
+│                   1. 客户端发起业务请求                           │
+│            Header: Authorization: Bearer {token}                  │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│                2. JwtAuthenticationFilter 拦截                    │
+│    ┌──────────────────────────────────────┐                      │
+│    │ a. 从请求头获取Token                 │                      │
+│    │    Authorization: Bearer {token}      │                      │
+│    └──────────────────┬───────────────────┘                      │
+│                       ↓                                           │
+│    ┌──────────────────────────────────────┐                      │
+│    │ b. 验证Token有效性                   │                      │
+│    │    JwtUtil.validateToken()            │                      │
+│    └──────────────────┬───────────────────┘                      │
+│                       ↓                                           │
+│    ┌──────────────────────────────────────┐                      │
+│    │ c. 从Redis获取LoginUser              │                      │
+│    │    TokenService.getLoginUser()        │                      │
+│    └──────────────────┬───────────────────┘                      │
+│                       ↓                                           │
+│    ┌──────────────────────────────────────┐                      │
+│    │ d. 将LoginUser设置到SecurityContext  │                      │
+│    │    包含用户信息和权限列表             │                      │
+│    └──────────────────────────────────────┘                      │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│                  3. FilterSecurityInterceptor                     │
+│             检查用户是否有访问当前URL的权限                       │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│                    4. 方法级别权限验证                            │
+│              @PreAuthorize("hasAuthority('xxx')")                │
+│              检查用户是否有执行该方法的权限                       │
+└────────────────────────┬─────────────────────────────────────────┘
+                         ↓
+┌──────────────────────────────────────────────────────────────────┐
+│                  5. 执行业务逻辑,返回结果                        │
+└──────────────────────────────────────────────────────────────────┘
+```
+
+### 3.2 核心过滤器链顺序
+
+Spring Security的过滤器链按以下顺序执行:
+
+```
+1. SecurityContextPersistenceFilter      - 创建SecurityContext
+2. HeaderWriterFilter                    - 添加安全响应头
+3. LogoutFilter                          - 处理登出请求
+4. JwtAuthenticationFilter              - JWT认证过滤器(自定义)
+5. RequestCacheAwareFilter               - 恢复被中断的请求
+6. SecurityContextHolderAwareRequestFilter - 包装请求对象
+7. AnonymousAuthenticationFilter         - 匿名认证
+8. SessionManagementFilter               - Session管理
+9. ExceptionTranslationFilter            - 异常转换
+10. FilterSecurityInterceptor            - 权限验证
+```
+
+---
+
+## 4. 核心组件详解
+
+### 4.1 SecurityConfig(安全配置类)
+
+**作用**:Spring Security的核心配置类
+
+**关键配置项**:
+
+```java
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    // 1. 配置密码编码器(BCrypt)
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    // 2. 暴露认证管理器
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+    // 3. 配置认证方式
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+        auth.userDetailsService(userDetailsService)
+            .passwordEncoder(passwordEncoder());
+    }
+
+    // 4. 配置HTTP安全策略
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http
+            .csrf().disable()  // 禁用CSRF(使用JWT不需要)
+            .sessionManagement()
+                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态
+            .and()
+            .authorizeRequests()
+                .antMatchers("/auth/login", "/auth/logout").permitAll() // 公开接口
+                .anyRequest().authenticated(); // 其他接口需要认证
+
+        // 添加JWT过滤器
+        http.addFilterBefore(jwtAuthenticationFilter,
+                           UsernamePasswordAuthenticationFilter.class);
+    }
+}
+```
+
+**重要说明**:
+- `permitAll()` vs `anonymous()`:
+  - `permitAll()`:允许所有人访问(包括已认证和未认证用户)
+  - `anonymous()`:只允许匿名用户访问(已认证用户会被拒绝)
+  - **登录接口必须使用 `permitAll()`**
+
+### 4.2 UserDetailsServiceImpl(用户详情服务)
+
+**作用**:加载用户信息、角色和权限
+
+**实现要点**:
+
+```java
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+
+    @Autowired
+    private SysUserService userService;
+
+    @Override
+    public UserDetails loadUserByUsername(String username)
+            throws UsernameNotFoundException {
+
+        // 1. 查询用户(包含角色信息)
+        SysUser user = userService.getUserWithRolesAndPermissions(username);
+        if (user == null) {
+            throw new UsernameNotFoundException("用户不存在");
+        }
+
+        // 2. 查询用户权限列表
+        List<String> permissions = userService.getPermissionsByUserId(user.getId());
+
+        // 3. 构建LoginUser对象(实现UserDetails接口)
+        return new LoginUser(user, permissions);
+    }
+}
+```
+
+**SQL查询示例**:
+
+```xml
+<!-- 查询用户及其角色 -->
+<select id="selectUserWithRolesAndPermissions" resultMap="UserWithRolesMap">
+    SELECT
+        u.id, u.username, u.password, u.nickname, u.status,
+        r.id AS role_id, r.role_name, r.role_key
+    FROM sys_user u
+    LEFT JOIN sys_user_role ur ON u.id = ur.user_id
+    LEFT JOIN sys_role r ON ur.role_id = r.id
+    WHERE u.username = #{username}
+      AND u.del_flag = 0
+      AND (r.del_flag = 0 OR r.id IS NULL)
+</select>
+
+<!-- 查询用户权限 -->
+<select id="selectPermissionsByUserId" resultType="java.lang.String">
+    SELECT DISTINCT m.perms
+    FROM sys_user_role ur
+    LEFT JOIN sys_role_menu rm ON ur.role_id = rm.role_id
+    LEFT JOIN sys_menu m ON rm.menu_id = m.id
+    WHERE ur.user_id = #{userId}
+      AND m.perms IS NOT NULL
+      AND m.perms != ''
+      AND m.status = 1
+      AND m.del_flag = 0
+</select>
+```
+
+### 4.3 LoginUser(用户认证信息)
+
+**作用**:封装用户详情和权限,实现UserDetails接口
+
+```java
+@Data
+@NoArgsConstructor
+public class LoginUser implements UserDetails {
+
+    private SysUser user;               // 用户信息
+    private List<String> permissions;   // 权限列表
+
+    // 构造方法
+    public LoginUser(SysUser user, List<String> permissions) {
+        this.user = user;
+        this.permissions = permissions;
+    }
+
+    // 将权限字符串转换为GrantedAuthority
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return permissions.stream()
+                .map(SimpleGrantedAuthority::new)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public String getPassword() {
+        return user.getPassword();
+    }
+
+    @Override
+    public String getUsername() {
+        return user.getUsername();
+    }
+
+    // 账户是否启用(检查status字段)
+    @Override
+    public boolean isEnabled() {
+        return user.getStatus() == 1;
+    }
+
+    // 其他方法返回true
+    @Override
+    public boolean isAccountNonExpired() { return true; }
+
+    @Override
+    public boolean isAccountNonLocked() { return true; }
+
+    @Override
+    public boolean isCredentialsNonExpired() { return true; }
+}
+```
+
+### 4.4 JwtAuthenticationFilter(JWT认证过滤器)
+
+**作用**:拦截所有请求,验证JWT Token
+
+```java
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private JwtUtil jwtUtil;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request,
+                                   HttpServletResponse response,
+                                   FilterChain chain)
+            throws ServletException, IOException {
+
+        // 1. 从请求头获取token
+        String token = getTokenFromRequest(request);
+
+        // 2. 验证token
+        if (token != null && jwtUtil.validateToken(token)) {
+
+            // 3. 从Redis获取用户信息
+            LoginUser loginUser = tokenService.getLoginUser(token);
+
+            if (loginUser != null) {
+                // 4. 刷新token有效期
+                tokenService.verifyToken(loginUser);
+
+                // 5. 创建认证对象
+                UsernamePasswordAuthenticationToken authentication =
+                    new UsernamePasswordAuthenticationToken(
+                        loginUser, null, loginUser.getAuthorities());
+
+                // 6. 设置到SecurityContext
+                SecurityContextHolder.getContext()
+                    .setAuthentication(authentication);
+            }
+        }
+
+        // 7. 继续过滤器链
+        chain.doFilter(request, response);
+    }
+
+    // 从请求头提取token
+    private String getTokenFromRequest(HttpServletRequest request) {
+        String bearerToken = request.getHeader("Authorization");
+        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
+            return bearerToken.substring(7);
+        }
+        return null;
+    }
+}
+```
+
+### 4.5 JwtUtil(JWT工具类)
+
+**作用**:生成和解析JWT Token
+
+```java
+@Component
+public class JwtUtil {
+
+    // 密钥(生产环境应从配置文件读取)
+    private String secret = "your-secret-key-must-be-very-long";
+
+    // 过期时间(24小时)
+    private long expiration = 86400000;
+
+    // 生成Token
+    public String generateToken(Map<String, Object> claims) {
+        return Jwts.builder()
+                .setClaims(claims)
+                .setExpiration(new Date(System.currentTimeMillis() + expiration))
+                .signWith(SignatureAlgorithm.HS512, secret)
+                .compact();
+    }
+
+    // 从Token中获取用户ID
+    public Long getUserIdFromToken(String token) {
+        Claims claims = parseToken(token);
+        return claims.get("userId", Long.class);
+    }
+
+    // 从Token中获取用户名
+    public String getUsernameFromToken(String token) {
+        Claims claims = parseToken(token);
+        return claims.get("username", String.class);
+    }
+
+    // 验证Token
+    public boolean validateToken(String token) {
+        try {
+            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    // 解析Token
+    private Claims parseToken(String token) {
+        return Jwts.parser()
+                .setSigningKey(secret)
+                .parseClaimsJws(token)
+                .getBody();
+    }
+}
+```
+
+### 4.6 TokenService(Token服务)
+
+**作用**:管理Token的存储和刷新
+
+```java
+@Service
+public class TokenService {
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private JwtUtil jwtUtil;
+
+    // Token在Redis中的前缀
+    private static final String LOGIN_TOKEN_KEY = "login_tokens:";
+
+    // 刷新时间(20分钟)
+    private static final long REFRESH_TIME = 20 * 60 * 1000;
+
+    // 过期时间(24小时)
+    private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000;
+
+    /**
+     * 创建Token
+     */
+    public String createToken(LoginUser loginUser) {
+        String uuid = UUID.randomUUID().toString();
+        loginUser.setToken(uuid);
+        loginUser.setLoginTime(System.currentTimeMillis());
+        loginUser.setExpireTime(loginUser.getLoginTime() + EXPIRE_TIME);
+
+        // 生成JWT
+        Map<String, Object> claims = new HashMap<>();
+        claims.put("userId", loginUser.getUser().getId());
+        claims.put("username", loginUser.getUsername());
+        String token = jwtUtil.generateToken(claims);
+
+        // 存储到Redis
+        String tokenKey = getTokenKey(token);
+        redisCache.setCacheObject(tokenKey, loginUser, EXPIRE_TIME, TimeUnit.MILLISECONDS);
+
+        return token;
+    }
+
+    /**
+     * 获取用户信息
+     */
+    public LoginUser getLoginUser(String token) {
+        String tokenKey = getTokenKey(token);
+        return redisCache.getCacheObject(tokenKey);
+    }
+
+    /**
+     * 刷新Token有效期
+     */
+    public void verifyToken(LoginUser loginUser) {
+        long currentTime = System.currentTimeMillis();
+        long expireTime = loginUser.getExpireTime();
+
+        // 距离过期时间小于20分钟,自动刷新
+        if (expireTime - currentTime <= REFRESH_TIME) {
+            String tokenKey = getTokenKey(loginUser.getToken());
+            redisCache.expire(tokenKey, EXPIRE_TIME, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    /**
+     * 删除Token
+     */
+    public void deleteToken(String token) {
+        String tokenKey = getTokenKey(token);
+        redisCache.deleteObject(tokenKey);
+    }
+
+    private String getTokenKey(String token) {
+        return LOGIN_TOKEN_KEY + token;
+    }
+}
+```
+
+### 4.7 全局异常处理器
+
+**作用**:统一处理认证和授权异常
+
+```java
+@RestController
+@ControllerAdvice
+public class ServiceExceptionHandler {
+
+    /**
+     * 处理认证异常(用户名或密码错误)
+     */
+    @ExceptionHandler(AuthenticationException.class)
+    @ResponseBody
+    public RestResult<?> handle(AuthenticationException ex) {
+        ex.printStackTrace();
+        return RestResult.error("用户名或密码错误");
+    }
+
+    /**
+     * 处理授权异常(权限不足)
+     */
+    @ExceptionHandler(AccessDeniedException.class)
+    @ResponseBody
+    public RestResult<?> handle(AccessDeniedException ex) {
+        ex.printStackTrace();
+        return RestResult.error("没有访问权限");
+    }
+
+    /**
+     * 处理其他异常
+     */
+    @ExceptionHandler(Exception.class)
+    @ResponseBody
+    public RestResult<?> handle(Exception ex) {
+        ex.printStackTrace();
+        return RestResult.error("请求异常, 请稍后重试!" + ex.getMessage());
+    }
+}
+```
+
+---
+
+## 5. 开发者实现清单
+
+### 5.1 必须实现的组件(7个核心类)
+
+#### ✅ 1. SecurityConfig(安全配置类)
+**位置**:`config/SecurityConfig.java`
+
+**必须实现的方法**:
+- `passwordEncoder()` - 配置密码编码器
+- `authenticationManagerBean()` - 暴露认证管理器
+- `configure(AuthenticationManagerBuilder)` - 配置认证方式
+- `configure(HttpSecurity)` - 配置HTTP安全策略
+
+**关键点**:
+- 禁用CSRF和Session
+- 配置公开接口(登录、登出等)
+- 添加JWT过滤器
+- 启用方法级权限注解 `@EnableGlobalMethodSecurity`
+
+---
+
+#### ✅ 2. UserDetailsServiceImpl(用户详情服务)
+**位置**:`security/UserDetailsServiceImpl.java`
+
+**必须实现的方法**:
+- `loadUserByUsername(String username)` - 加载用户信息
+
+**关键点**:
+- 查询用户基本信息(包含密码)
+- 查询用户权限列表
+- 返回LoginUser对象
+- 用户不存在时抛出 `UsernameNotFoundException`
+
+---
+
+#### ✅ 3. LoginUser(用户认证信息)
+**位置**:`security/LoginUser.java`
+
+**必须实现的方法**(实现UserDetails接口):
+- `getAuthorities()` - 返回权限列表
+- `getPassword()` - 返回密码
+- `getUsername()` - 返回用户名
+- `isEnabled()` - 是否启用(检查status字段)
+- `isAccountNonExpired()` - 账户是否过期
+- `isAccountNonLocked()` - 账户是否锁定
+- `isCredentialsNonExpired()` - 密码是否过期
+
+**关键点**:
+- 封装用户信息和权限列表
+- 将权限字符串转换为 `GrantedAuthority`
+
+---
+
+#### ✅ 4. JwtAuthenticationFilter(JWT认证过滤器)
+**位置**:`security/JwtAuthenticationFilter.java`
+
+**必须实现的方法**:
+- `doFilterInternal()` - 过滤器逻辑
+
+**关键点**:
+- 从请求头提取Token
+- 验证Token有效性
+- 从Redis获取用户信息
+- 设置认证信息到SecurityContext
+- 继续过滤器链
+
+---
+
+#### ✅ 5. JwtUtil(JWT工具类)
+**位置**:`utils/JwtUtil.java`
+
+**必须实现的方法**:
+- `generateToken(Map<String, Object> claims)` - 生成Token
+- `getUserIdFromToken(String token)` - 从Token获取用户ID
+- `getUsernameFromToken(String token)` - 从Token获取用户名
+- `validateToken(String token)` - 验证Token
+- `parseToken(String token)` - 解析Token
+
+**关键点**:
+- 使用HMAC-SHA512算法签名
+- 设置过期时间
+- 异常处理(ExpiredJwtException等)
+
+---
+
+#### ✅ 6. TokenService(Token服务)
+**位置**:`security/TokenService.java`
+
+**必须实现的方法**:
+- `createToken(LoginUser loginUser)` - 创建Token并存储到Redis
+- `getLoginUser(String token)` - 从Redis获取用户信息
+- `verifyToken(LoginUser loginUser)` - 刷新Token有效期
+- `deleteToken(String token)` - 删除Token(登出时使用)
+
+**关键点**:
+- Token存储在Redis中,key为 `login_tokens:{token}`
+- 自动刷新机制:距离过期时间小于20分钟时自动续期
+- 默认过期时间24小时
+
+---
+
+#### ✅ 7. 登录/登出Controller
+**位置**:`modules/system/controller/SysUserController.java`
+
+**必须实现的方法**:
+- `login(LoginBody loginBody)` - 登录接口
+- `logout()` - 登出接口
+
+**关键点**:
+- 使用 `AuthenticationManager.authenticate()` 进行认证
+- 认证成功后生成Token
+- 将Token存储到Redis
+- 返回Token和用户信息给客户端
+- 登出时删除Redis中的Token
+
+---
+
+### 5.2 数据访问层(Mapper)
+
+#### ✅ 8. SysUserMapper
+**位置**:`modules/system/mapper/SysUserMapper.java`
+
+**必须实现的方法**:
+```java
+// 根据用户名查询用户(包含角色)
+SysUser selectUserWithRolesAndPermissions(@Param("username") String username);
+
+// 根据用户ID查询权限列表
+List<String> selectPermissionsByUserId(@Param("userId") Long userId);
+```
+
+#### ✅ 9. SysUserMapper.xml
+**位置**:`modules/system/mapper/dao/SysUserMapper.xml`
+
+**必须实现的SQL**:
+- 用户+角色关联查询
+- 用户权限查询(用户→角色→菜单)
+
+---
+
+### 5.3 服务层(Service)
+
+#### ✅ 10. SysUserService
+**位置**:`modules/system/service/SysUserService.java`
+
+**必须实现的方法**:
+```java
+// 根据用户名查询用户(包含角色和权限)
+SysUser getUserWithRolesAndPermissions(String username);
+
+// 根据用户ID查询权限列表
+List<String> getPermissionsByUserId(Long userId);
+
+// 根据用户名查询用户(不含角色)
+SysUser getUserByUsername(String username);
+
+// 验证密码是否匹配
+boolean matchesPassword(String rawPassword, String encodedPassword);
+```
+
+---
+
+### 5.4 实体类(Entity)
+
+#### ✅ 11. SysUser
+**位置**:`modules/system/entity/SysUser.java`
+
+**必须包含的字段**:
+- `id` - 用户ID
+- `username` - 用户名
+- `password` - 密码(BCrypt加密)
+- `status` - 状态(0-禁用,1-正常)
+- `roles` - 角色列表(非数据库字段)
+- `permissions` - 权限列表(非数据库字段)
+
+**关键点**:
+- `password` 字段添加 `@JsonIgnore` 注解,防止返回给前端
+
+---
+
+### 5.5 配置文件
+
+#### ✅ 12. pom.xml
+**必须添加的依赖**:
+```xml
+<!-- Spring Security -->
+<dependency>
+    <groupId>org.springframework.boot</groupId>
+    <artifactId>spring-boot-starter-security</artifactId>
+</dependency>
+
+<!-- JWT -->
+<dependency>
+    <groupId>io.jsonwebtoken</groupId>
+    <artifactId>jjwt</artifactId>
+    <version>0.9.1</version>
+</dependency>
+
+<!-- JAXB API(JDK 11+必需) -->
+<dependency>
+    <groupId>javax.xml.bind</groupId>
+    <artifactId>jaxb-api</artifactId>
+    <version>2.3.1</version>
+</dependency>
+<dependency>
+    <groupId>org.glassfish.jaxb</groupId>
+    <artifactId>jaxb-runtime</artifactId>
+    <version>2.3.1</version>
+</dependency>
+
+<!-- Redis -->
+<dependency>
+    <groupId>org.springframework.boot</groupId>
+    <artifactId>spring-boot-starter-data-redis</artifactId>
+</dependency>
+```
+
+#### ✅ 13. application.yaml
+**必须配置的项**:
+```yaml
+spring:
+  redis:
+    host: 127.0.0.1
+    port: 6379
+    password: your-password
+    database: 0
+
+logging:
+  level:
+    org.springframework.security: DEBUG  # 开发时开启,便于调试
+```
+
+---
+
+### 5.6 完整实现清单总结
+
+| 序号 | 组件名称 | 位置 | 类型 | 优先级 |
+|-----|---------|------|------|-------|
+| 1 | SecurityConfig | config/ | 配置类 | 🔴 必须 |
+| 2 | UserDetailsServiceImpl | security/ | 服务类 | 🔴 必须 |
+| 3 | LoginUser | security/ | 实体类 | 🔴 必须 |
+| 4 | JwtAuthenticationFilter | security/ | 过滤器 | 🔴 必须 |
+| 5 | JwtUtil | utils/ | 工具类 | 🔴 必须 |
+| 6 | TokenService | security/ | 服务类 | 🔴 必须 |
+| 7 | SysUserController | controller/ | 控制器 | 🔴 必须 |
+| 8 | SysUserMapper | mapper/ | Mapper接口 | 🔴 必须 |
+| 9 | SysUserMapper.xml | mapper/dao/ | MyBatis XML | 🔴 必须 |
+| 10 | SysUserService | service/ | 服务接口 | 🔴 必须 |
+| 11 | SysUser | entity/ | 实体类 | 🔴 必须 |
+| 12 | ServiceExceptionHandler | exception/ | 异常处理 | 🟡 推荐 |
+| 13 | RedisCache | utils/ | Redis工具类 | 🟡 推荐 |
+
+---
+
+## 6. 代码示例
+
+### 6.1 登录接口完整实现
+
+```java
+@RestController
+@RequestMapping("/auth")
+public class SysUserController {
+
+    @Resource
+    private AuthenticationManager authenticationManager;
+
+    @Resource
+    private JwtUtil jwtUtil;
+
+    @Resource
+    private SysUserService sysUserService;
+
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+
+    /**
+     * 用户名密码登录
+     */
+    @PostMapping("/login")
+    public RestResult<?> login(@RequestBody LoginBody loginBody) {
+        try {
+            // 1. 使用Spring Security进行认证
+            Authentication authentication = authenticationManager.authenticate(
+                new UsernamePasswordAuthenticationToken(
+                    loginBody.getUsername(),
+                    loginBody.getPassword()
+                )
+            );
+
+            // 2. 认证成功,设置认证信息
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
+            // 3. 获取用户信息
+            SysUser user = sysUserService.getUserByUsername(loginBody.getUsername());
+
+            // 4. 生成JWT Token
+            Map<String, Object> claims = new HashMap<>();
+            claims.put("username", user.getUsername());
+            claims.put("userId", user.getId());
+            String token = jwtUtil.generateToken(claims);
+
+            // 5. 将Token存储到Redis(24小时过期)
+            redisTemplate.opsForValue().set(
+                "token:" + user.getId(),
+                token,
+                24,
+                TimeUnit.HOURS
+            );
+
+            // 6. 返回结果
+            Map<String, Object> result = new HashMap<>();
+            result.put("token", token);
+            result.put("userInfo", user);
+
+            return RestResult.ok("登录成功", result);
+
+        } catch (AuthenticationException e) {
+            // 认证失败
+            return RestResult.error("用户名或密码错误");
+        }
+    }
+
+    /**
+     * 退出登录
+     */
+    @PostMapping("/logout")
+    public RestResult<?> logout() {
+        // 1. 获取当前用户
+        Authentication authentication = SecurityContextHolder.getContext()
+            .getAuthentication();
+
+        if (authentication != null) {
+            // 2. 删除Redis中的Token
+            String username = authentication.getName();
+            SysUser user = sysUserService.getUserByUsername(username);
+            if (user != null) {
+                redisTemplate.delete("token:" + user.getId());
+            }
+        }
+
+        // 3. 清空认证信息
+        SecurityContextHolder.clearContext();
+
+        return RestResult.ok("退出成功");
+    }
+}
+```
+
+### 6.2 方法级权限控制
+
+```java
+@RestController
+@RequestMapping("/system/user")
+public class UserController {
+
+    @Autowired
+    private SysUserService userService;
+
+    /**
+     * 查询用户列表
+     * 需要 system:user:list 权限
+     */
+    @GetMapping("/list")
+    @PreAuthorize("hasAuthority('system:user:list')")
+    public RestResult<?> list() {
+        List<SysUser> users = userService.list();
+        return RestResult.ok(users);
+    }
+
+    /**
+     * 新增用户
+     * 需要 system:user:add 权限
+     */
+    @PostMapping("/add")
+    @PreAuthorize("hasAuthority('system:user:add')")
+    public RestResult<?> add(@RequestBody SysUser user) {
+        userService.save(user);
+        return RestResult.ok("新增成功");
+    }
+
+    /**
+     * 修改用户
+     * 需要 system:user:edit 权限
+     */
+    @PutMapping("/edit")
+    @PreAuthorize("hasAuthority('system:user:edit')")
+    public RestResult<?> edit(@RequestBody SysUser user) {
+        userService.updateById(user);
+        return RestResult.ok("修改成功");
+    }
+
+    /**
+     * 删除用户
+     * 需要 system:user:remove 权限
+     */
+    @DeleteMapping("/remove/{id}")
+    @PreAuthorize("hasAuthority('system:user:remove')")
+    public RestResult<?> remove(@PathVariable Long id) {
+        userService.removeById(id);
+        return RestResult.ok("删除成功");
+    }
+
+    /**
+     * 组合权限判断
+     * 需要同时拥有 system:user:list 和 system:user:export 权限
+     */
+    @GetMapping("/export")
+    @PreAuthorize("hasAuthority('system:user:list') and hasAuthority('system:user:export')")
+    public RestResult<?> export() {
+        // 导出Excel逻辑
+        return RestResult.ok("导出成功");
+    }
+
+    /**
+     * 角色判断
+     * 需要admin角色
+     */
+    @GetMapping("/admin-only")
+    @PreAuthorize("hasRole('admin')")
+    public RestResult<?> adminOnly() {
+        return RestResult.ok("管理员专属功能");
+    }
+}
+```
+
+### 6.3 前端按钮级权限控制
+
+#### Vue示例(使用自定义指令)
+
+```javascript
+// permission.js - 自定义权限指令
+import store from '@/store'
+
+export default {
+  install(Vue) {
+    // v-hasPermission 指令
+    Vue.directive('hasPermission', {
+      inserted(el, binding, vnode) {
+        const { value } = binding
+        const permissions = store.getters.permissions
+
+        if (value && value instanceof Array && value.length > 0) {
+          const hasPermission = permissions.some(permission => {
+            return value.includes(permission)
+          })
+
+          if (!hasPermission) {
+            // 没有权限,移除按钮
+            el.parentNode && el.parentNode.removeChild(el)
+          }
+        }
+      }
+    })
+  }
+}
+```
+
+```vue
+<!-- UserList.vue -->
+<template>
+  <div>
+    <el-table :data="userList">
+      <el-table-column prop="username" label="用户名"/>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <!-- 新增按钮 - 需要 system:user:add 权限 -->
+          <el-button
+            v-hasPermission="['system:user:add']"
+            @click="handleAdd()">
+            新增
+          </el-button>
+
+          <!-- 编辑按钮 - 需要 system:user:edit 权限 -->
+          <el-button
+            v-hasPermission="['system:user:edit']"
+            @click="handleEdit(scope.row)">
+            编辑
+          </el-button>
+
+          <!-- 删除按钮 - 需要 system:user:remove 权限 -->
+          <el-button
+            v-hasPermission="['system:user:remove']"
+            @click="handleDelete(scope.row)">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+```
+
+#### React示例(使用Hook)
+
+```javascript
+// usePermission.js
+import { useSelector } from 'react-redux'
+
+export const usePermission = () => {
+  const permissions = useSelector(state => state.user.permissions)
+
+  const hasPermission = (permission) => {
+    if (Array.isArray(permission)) {
+      return permission.some(p => permissions.includes(p))
+    }
+    return permissions.includes(permission)
+  }
+
+  return { hasPermission }
+}
+
+// UserList.jsx
+import { usePermission } from '@/hooks/usePermission'
+
+const UserList = () => {
+  const { hasPermission } = usePermission()
+
+  return (
+    <div>
+      <Table dataSource={userList}>
+        <Column title="用户名" dataIndex="username" />
+        <Column
+          title="操作"
+          render={(text, record) => (
+            <>
+              {/* 新增按钮 */}
+              {hasPermission('system:user:add') && (
+                <Button onClick={() => handleAdd()}>新增</Button>
+              )}
+
+              {/* 编辑按钮 */}
+              {hasPermission('system:user:edit') && (
+                <Button onClick={() => handleEdit(record)}>编辑</Button>
+              )}
+
+              {/* 删除按钮 */}
+              {hasPermission('system:user:remove') && (
+                <Button onClick={() => handleDelete(record)}>删除</Button>
+              )}
+            </>
+          )}
+        />
+      </Table>
+    </div>
+  )
+}
+```
+
+---
+
+## 7. 测试指南
+
+### 7.1 登录测试
+
+```bash
+# 1. 登录获取Token
+curl -X POST http://localhost:8080/auth/login \
+  -H "Content-Type: application/json" \
+  -d '{
+    "username": "admin",
+    "password": "admin123"
+  }'
+
+# 响应示例
+{
+  "success": true,
+  "message": "登录成功",
+  "code": 200,
+  "result": {
+    "token": "eyJhbGciOiJIUzUxMiJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJleHAiOjE3MDAwMDAwMDB9.xxx",
+    "userInfo": {
+      "id": 1,
+      "username": "admin",
+      "nickname": "超级管理员"
+    }
+  },
+  "timestamp": 1700000000000
+}
+```
+
+### 7.2 携带Token访问受保护接口
+
+```bash
+# 2. 使用Token访问受保护的接口
+curl -X GET http://localhost:8080/system/user/list \
+  -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.xxx"
+
+# 有权限 - 返回数据
+{
+  "success": true,
+  "code": 200,
+  "result": [...]
+}
+
+# 无权限 - 返回403
+{
+  "success": false,
+  "message": "没有访问权限",
+  "code": 403
+}
+```
+
+### 7.3 测试不同权限级别
+
+```bash
+# 测试超级管理员(拥有所有权限)
+# 1. 以admin身份登录
+TOKEN_ADMIN="eyJhbGciOiJIUzUxMiJ9.xxx"
+
+# 2. 测试新增用户(需要 system:user:add 权限)
+curl -X POST http://localhost:8080/system/user/add \
+  -H "Authorization: Bearer $TOKEN_ADMIN" \
+  -H "Content-Type: application/json" \
+  -d '{
+    "username": "test",
+    "password": "123456",
+    "nickname": "测试用户"
+  }'
+# 预期:成功
+
+
+# 测试普通用户(权限有限)
+# 1. 以user身份登录
+TOKEN_USER="eyJhbGciOiJIUzUxMiJ9.yyy"
+
+# 2. 测试新增用户(普通用户没有此权限)
+curl -X POST http://localhost:8080/system/user/add \
+  -H "Authorization: Bearer $TOKEN_USER" \
+  -H "Content-Type: application/json" \
+  -d '{...}'
+# 预期:403 没有访问权限
+```
+
+### 7.4 退出登录测试
+
+```bash
+# 3. 退出登录
+curl -X POST http://localhost:8080/auth/logout \
+  -H "Authorization: Bearer $TOKEN_ADMIN"
+
+# 响应
+{
+  "success": true,
+  "message": "退出成功",
+  "code": 200
+}
+
+# 4. 退出后再次访问受保护接口
+curl -X GET http://localhost:8080/system/user/list \
+  -H "Authorization: Bearer $TOKEN_ADMIN"
+
+# 预期:401 未认证(因为Token已从Redis中删除)
+```
+
+### 7.5 Postman测试集合
+
+创建Postman环境变量:
+```json
+{
+  "base_url": "http://localhost:8080",
+  "token": ""
+}
+```
+
+测试脚本(自动保存Token):
+```javascript
+// 在登录接口的 Tests 标签页添加
+if (pm.response.code === 200) {
+    var jsonData = pm.response.json();
+    if (jsonData.success && jsonData.result.token) {
+        pm.environment.set("token", jsonData.result.token);
+        console.log("Token已保存:", jsonData.result.token);
+    }
+}
+```
+
+---
+
+## 8. 常见问题
+
+### 8.1 认证问题
+
+#### Q1: 登录时提示"Bad credentials",但密码确实正确?
+
+**原因**:
+1. 数据库中的密码不是正确的BCrypt加密值
+2. `UserDetailsService.loadUserByUsername()` 返回的密码为null
+3. 密码编码器配置不正确
+
+**解决方法**:
+```bash
+# 1. 测试密码是否匹配
+curl -X POST "http://localhost:8080/auth/test-password" \
+  -d "username=admin&password=admin123"
+
+# 2. 生成正确的BCrypt密码
+curl -X POST "http://localhost:8080/auth/generate-password" \
+  -d "password=admin123"
+
+# 3. 更新数据库密码
+UPDATE sys_user SET password = '$2a$10$xxx' WHERE username = 'admin';
+```
+
+#### Q2: SecurityConfig中应该使用 `permitAll()` 还是 `anonymous()`?
+
+**区别**:
+- `permitAll()`:允许所有人访问(已认证 + 未认证)
+- `anonymous()`:只允许匿名用户访问(已认证用户会被拒绝)
+
+**登录接口必须使用 `permitAll()`**,否则认证过程会失败!
+
+```java
+// ✓ 正确
+.antMatchers("/auth/login", "/auth/logout").permitAll()
+
+// ✗ 错误(会导致认证失败)
+.antMatchers("/auth/login", "/auth/logout").anonymous()
+```
+
+### 8.2 JWT问题
+
+#### Q3: JDK 11运行时报错:`NoClassDefFoundError: javax/xml/bind/DatatypeConverter`
+
+**原因**:Java 11移除了 `javax.xml.bind` 包
+
+**解决方法**:在 `pom.xml` 中添加JAXB依赖
+
+```xml
+<dependency>
+    <groupId>javax.xml.bind</groupId>
+    <artifactId>jaxb-api</artifactId>
+    <version>2.3.1</version>
+</dependency>
+<dependency>
+    <groupId>org.glassfish.jaxb</groupId>
+    <artifactId>jaxb-runtime</artifactId>
+    <version>2.3.1</version>
+</dependency>
+```
+
+#### Q4: Token验证失败,但Token确实有效?
+
+**检查项**:
+1. JWT密钥是否一致(secret)
+2. Token是否过期
+3. Redis中是否存在该Token
+4. Token格式是否正确(Bearer + 空格 + token)
+
+```bash
+# 正确的请求头格式
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.xxx
+
+# 错误示例
+Authorization: eyJhbGciOiJIUzUxMiJ9.xxx  # 缺少Bearer前缀
+Authorization: BearereyJhbGciOiJIUzUxMiJ9.xxx  # 缺少空格
+```
+
+### 8.3 权限问题
+
+#### Q5: @PreAuthorize 注解不生效?
+
+**原因**:未开启方法级权限验证
+
+**解决方法**:在SecurityConfig上添加注解
+
+```java
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+    // ...
+}
+```
+
+#### Q6: 用户明明有权限,但还是返回403?
+
+**检查步骤**:
+1. 确认权限标识是否正确(区分大小写)
+2. 确认数据库中用户-角色-权限关联是否正确
+3. 确认 `getPermissionsByUserId()` 是否正确返回权限列表
+4. 确认LoginUser的 `getAuthorities()` 方法是否正确转换权限
+
+```java
+// 调试:打印用户权限
+@GetMapping("/debug/permissions")
+public RestResult<?> getMyPermissions() {
+    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+    LoginUser loginUser = (LoginUser) auth.getPrincipal();
+    return RestResult.ok(loginUser.getPermissions());
+}
+```
+
+### 8.4 Redis问题
+
+#### Q7: Token生成成功,但后续请求获取不到用户信息?
+
+**原因**:Redis连接或序列化问题
+
+**检查项**:
+1. Redis服务是否正常运行
+2. Redis连接配置是否正确
+3. RedisTemplate序列化配置
+
+```java
+@Configuration
+public class RedisConfig {
+
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(
+            RedisConnectionFactory factory) {
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(factory);
+
+        // 使用Jackson序列化
+        Jackson2JsonRedisSerializer<Object> serializer =
+            new Jackson2JsonRedisSerializer<>(Object.class);
+
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(serializer);
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        return template;
+    }
+}
+```
+
+### 8.5 跨域问题
+
+#### Q8: 前后端分离时,登录接口报跨域错误?
+
+**解决方法**:在SecurityConfig中配置CORS
+
+```java
+@Override
+protected void configure(HttpSecurity http) throws Exception {
+    http
+        .cors()  // 启用CORS
+        .and()
+        .csrf().disable()
+        // ...
+}
+
+// 配置CORS规则
+@Bean
+public CorsConfigurationSource corsConfigurationSource() {
+    CorsConfiguration configuration = new CorsConfiguration();
+    configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
+    configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
+    configuration.setAllowedHeaders(Arrays.asList("*"));
+    configuration.setAllowCredentials(true);
+
+    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+    source.registerCorsConfiguration("/**", configuration);
+    return source;
+}
+```
+
+---
+
+## 9. 最佳实践
+
+### 9.1 密码安全
+
+1. **永远使用BCrypt加密密码**
+   ```java
+   // ✓ 正确
+   String encodedPassword = passwordEncoder.encode(rawPassword);
+
+   // ✗ 错误
+   String encodedPassword = MD5(rawPassword);  // MD5不安全
+   ```
+
+2. **密码字段添加@JsonIgnore**
+   ```java
+   @JsonIgnore
+   private String password;
+   ```
+
+3. **密码复杂度要求**
+   - 最小长度8位
+   - 包含大小写字母、数字、特殊字符
+   - 定期更换密码
+
+### 9.2 Token安全
+
+1. **Token存储位置**
+   - ✓ 前端:localStorage 或 sessionStorage
+   - ✗ 前端:Cookie(容易被CSRF攻击)
+   - ✓ 后端:Redis(可快速失效)
+
+2. **Token刷新策略**
+   ```java
+   // 距离过期时间小于20分钟时自动刷新
+   if (expireTime - currentTime <= 20 * 60 * 1000) {
+       redisCache.expire(tokenKey, 24 * 60 * 60 * 1000);
+   }
+   ```
+
+3. **Token密钥管理**
+   ```yaml
+   # application.yaml - 生产环境从环境变量读取
+   jwt:
+     secret: ${JWT_SECRET:your-default-secret-key}
+     expiration: 86400000  # 24小时
+   ```
+
+### 9.3 权限设计
+
+1. **权限粒度**
+   - 粗粒度:模块级(system:user)
+   - 中粒度:功能级(system:user:list)
+   - 细粒度:按钮级(system:user:add)
+
+2. **权限命名规范**
+   ```
+   模块:功能:操作
+   system:user:add      # 系统管理-用户管理-新增
+   system:user:edit     # 系统管理-用户管理-编辑
+   report:sales:export  # 报表管理-销售报表-导出
+   ```
+
+3. **数据权限控制**
+   ```java
+   // 根据用户所属部门过滤数据
+   @DataScope(deptAlias = "d", userAlias = "u")
+   @PreAuthorize("hasAuthority('system:user:list')")
+   public List<SysUser> list() {
+       return userService.selectUserList();
+   }
+   ```
+
+### 9.4 日志记录
+
+```java
+@Aspect
+@Component
+public class LoginLogAspect {
+
+    @Around("@annotation(com.xxx.annotation.LoginLog)")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        long startTime = System.currentTimeMillis();
+
+        try {
+            Object result = joinPoint.proceed();
+            long time = System.currentTimeMillis() - startTime;
+
+            // 记录登录成功日志
+            saveLog(joinPoint, time, "SUCCESS");
+
+            return result;
+        } catch (Exception e) {
+            // 记录登录失败日志
+            saveLog(joinPoint, 0, "FAIL");
+            throw e;
+        }
+    }
+}
+```
+
+---
+
+## 10. 性能优化
+
+### 10.1 Redis优化
+
+1. **使用Redis Pipeline批量操作**
+   ```java
+   redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
+       connection.set("key1".getBytes(), "value1".getBytes());
+       connection.set("key2".getBytes(), "value2".getBytes());
+       return null;
+   });
+   ```
+
+2. **合理设置过期时间**
+   - Token:24小时
+   - 验证码:5分钟
+   - 短期缓存:1小时
+
+### 10.2 数据库优化
+
+1. **为常用查询添加索引**
+   ```sql
+   -- 用户名索引
+   CREATE UNIQUE INDEX idx_username ON sys_user(username);
+
+   -- 用户-角色关联索引
+   CREATE INDEX idx_user_id ON sys_user_role(user_id);
+   CREATE INDEX idx_role_id ON sys_user_role(role_id);
+   ```
+
+2. **使用MyBatis二级缓存**
+   ```xml
+   <!-- SysUserMapper.xml -->
+   <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
+   ```
+
+---
+
+## 11. 附录
+
+### 11.1 完整项目结构
+
+```
+pacsonline_new/
+├── src/main/java/com/zskk/pacsonline/
+│   ├── config/
+│   │   ├── SecurityConfig.java          # Security配置
+│   │   └── RedisConfig.java             # Redis配置
+│   ├── security/
+│   │   ├── UserDetailsServiceImpl.java  # 用户详情服务
+│   │   ├── LoginUser.java               # 用户认证信息
+│   │   ├── JwtAuthenticationFilter.java # JWT过滤器
+│   │   └── TokenService.java            # Token服务
+│   ├── utils/
+│   │   ├── JwtUtil.java                 # JWT工具类
+│   │   └── RedisCache.java              # Redis工具类
+│   ├── modules/system/
+│   │   ├── controller/
+│   │   │   └── SysUserController.java   # 用户控制器
+│   │   ├── service/
+│   │   │   ├── SysUserService.java      # 用户服务接口
+│   │   │   └── impl/
+│   │   │       └── SysUserServiceImpl.java
+│   │   ├── mapper/
+│   │   │   ├── SysUserMapper.java       # 用户Mapper接口
+│   │   │   └── dao/
+│   │   │       └── SysUserMapper.xml    # MyBatis XML
+│   │   └── entity/
+│   │       ├── SysUser.java             # 用户实体
+│   │       ├── SysRole.java             # 角色实体
+│   │       └── SysMenu.java             # 菜单实体
+│   └── component/
+│       └── exception/
+│           └── ServiceExceptionHandler.java  # 全局异常处理
+├── src/main/resources/
+│   ├── application.yaml                 # 配置文件
+│   └── mapper/
+└── doc/
+    └── security_tables.sql              # 数据库表SQL
+```
+
+### 11.2 参考资料
+
+- [Spring Security官方文档](https://docs.spring.io/spring-security/reference/)
+- [JWT官方网站](https://jwt.io/)
+- [BCrypt密码加密](https://www.baeldung.com/spring-security-registration-password-encoding-bcrypt)
+- [Spring Security架构](https://spring.io/guides/topicals/spring-security-architecture/)
+
+---
+
+## 12. 总结
+
+本文档详细介绍了基于Spring Security + JWT的权限认证系统的完整实现方案,涵盖了从数据库设计到代码实现的所有关键环节。
+
+**核心要点回顾**:
+
+1. **7个核心类必须实现**:SecurityConfig、UserDetailsServiceImpl、LoginUser、JwtAuthenticationFilter、JwtUtil、TokenService、登录Controller
+
+2. **RBAC权限模型**:用户-角色-权限三级关联
+
+3. **三级权限控制**:接口级(URL)、方法级(@PreAuthorize)、按钮级(前端控制)
+
+4. **关键配置**:
+   - 登录接口使用 `permitAll()` 而非 `anonymous()`
+   - JDK 11+ 需要添加JAXB依赖
+   - 密码必须使用BCrypt加密
+   - Token存储在Redis中,支持自动刷新
+
+5. **安全最佳实践**:
+   - 密码加@JsonIgnore注解
+   - Token密钥从环境变量读取
+   - 记录登录日志
+   - 合理设置Token过期时间
+
+遵循本文档的实现方案,可以快速搭建一个安全、可靠、易扩展的权限认证系统。
+
+---
+
+**文档版本**:v1.0
+**最后更新**:2025-10-29
+**作者**:gengjunfang
+**项目地址**:pacsonline_new

+ 14 - 0
doc/security_tables.sql

@@ -1,3 +1,17 @@
+
+-- 短信验证码记录表
+CREATE TABLE IF NOT EXISTS `sys_sms_code` (
+  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+  `phone` VARCHAR(20) NOT NULL,
+  `code` VARCHAR(10) NOT NULL,
+  `expire_time` DATETIME NOT NULL,
+  `used` TINYINT NOT NULL DEFAULT 0,
+  `used_time` DATETIME DEFAULT NULL,
+  `request_ip` VARCHAR(64) DEFAULT NULL,
+  `send_status` TINYINT NOT NULL DEFAULT 1,
+  `send_msg` VARCHAR(255) DEFAULT NULL,
+  `create_time` DATETIME NOT NULL
+) COMMENT='短信验证码记录';
 -- 用户表
 CREATE TABLE `sys_user` (
   `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',

+ 33 - 0
doc/sys_log_table.sql

@@ -0,0 +1,33 @@
+-- ====================================
+-- 系统操作日志表
+-- ====================================
+CREATE TABLE `sys_log` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID',
+  `user_id` BIGINT(20) DEFAULT NULL COMMENT '用户ID',
+  `username` VARCHAR(50) DEFAULT NULL COMMENT '用户名',
+  `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名',
+  `log_type` VARCHAR(20) NOT NULL COMMENT '日志类型:登录日志、操作日志',
+  `operate_type` VARCHAR(50) DEFAULT NULL COMMENT '操作类型:增删改查等',
+  `detail` VARCHAR(500) DEFAULT NULL COMMENT '日志详情描述',
+  `controller` VARCHAR(100) DEFAULT NULL COMMENT '控制器名称',
+  `method` VARCHAR(100) DEFAULT NULL COMMENT '方法名称',
+  `request_url` VARCHAR(255) DEFAULT NULL COMMENT '请求URL',
+  `request_method` VARCHAR(10) DEFAULT NULL COMMENT '请求方式:GET/POST/PUT/DELETE',
+  `request_params` TEXT DEFAULT NULL COMMENT '请求参数',
+  `operate_ip` VARCHAR(50) DEFAULT NULL COMMENT '操作IP地址',
+  `operate_location` VARCHAR(100) DEFAULT NULL COMMENT '操作地点',
+  `operate_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
+  `use_time` BIGINT(20) DEFAULT NULL COMMENT '执行时长(毫秒)',
+  `status` TINYINT(1) DEFAULT 1 COMMENT '状态:0-失败,1-成功',
+  `error_msg` TEXT DEFAULT NULL COMMENT '错误信息',
+  `browser` VARCHAR(100) DEFAULT NULL COMMENT '浏览器',
+  `os` VARCHAR(100) DEFAULT NULL COMMENT '操作系统',
+  PRIMARY KEY (`id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_operate_time` (`operate_time`),
+  KEY `idx_log_type` (`log_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
+
+-- 创建索引优化查询
+CREATE INDEX idx_username ON sys_log(username);
+CREATE INDEX idx_log_type_time ON sys_log(log_type, operate_time);

+ 466 - 0
doc/操作日志使用说明.md

@@ -0,0 +1,466 @@
+# 操作日志使用说明
+
+## 1. 功能概述
+
+系统集成了完整的操作日志记录功能,自动记录用户的所有重要操作,包括:
+- **登录日志**:用户登录、登出操作
+- **操作日志**:用户的增删改查等业务操作
+
+## 2. 数据库表结构
+
+### 2.1 sys_log 表
+
+```sql
+CREATE TABLE `sys_log` (
+  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID',
+  `user_id` BIGINT(20) DEFAULT NULL COMMENT '用户ID',
+  `username` VARCHAR(50) DEFAULT NULL COMMENT '用户名',
+  `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名',
+  `log_type` VARCHAR(20) NOT NULL COMMENT '日志类型:登录日志、操作日志',
+  `operate_type` VARCHAR(50) DEFAULT NULL COMMENT '操作类型:增删改查等',
+  `detail` VARCHAR(500) DEFAULT NULL COMMENT '日志详情描述',
+  `controller` VARCHAR(100) DEFAULT NULL COMMENT '控制器名称',
+  `method` VARCHAR(100) DEFAULT NULL COMMENT '方法名称',
+  `request_url` VARCHAR(255) DEFAULT NULL COMMENT '请求URL',
+  `request_method` VARCHAR(10) DEFAULT NULL COMMENT '请求方式:GET/POST/PUT/DELETE',
+  `request_params` TEXT DEFAULT NULL COMMENT '请求参数',
+  `operate_ip` VARCHAR(50) DEFAULT NULL COMMENT '操作IP地址',
+  `operate_location` VARCHAR(100) DEFAULT NULL COMMENT '操作地点',
+  `operate_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
+  `use_time` BIGINT(20) DEFAULT NULL COMMENT '执行时长(毫秒)',
+  `status` TINYINT(1) DEFAULT 1 COMMENT '状态:0-失败,1-成功',
+  `error_msg` TEXT DEFAULT NULL COMMENT '错误信息',
+  `browser` VARCHAR(100) DEFAULT NULL COMMENT '浏览器',
+  `os` VARCHAR(100) DEFAULT NULL COMMENT '操作系统',
+  PRIMARY KEY (`id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_operate_time` (`operate_time`),
+  KEY `idx_log_type` (`log_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
+```
+
+## 3. 使用方法
+
+### 3.1 在Controller方法上添加注解
+
+在需要记录日志的方法上添加 `@SystemLogHandler` 注解:
+
+```java
+@RestController
+@RequestMapping("/system/user")
+public class UserController {
+
+    /**
+     * 新增用户
+     * 注解格式:@SystemLogHandler("操作描述|操作类型")
+     */
+    @SystemLogHandler("新增用户|新增")
+    @PostMapping("/add")
+    public RestResult<?> add(@RequestBody SysUser user) {
+        userService.save(user);
+        return RestResult.ok("新增成功");
+    }
+
+    /**
+     * 修改用户
+     */
+    @SystemLogHandler("修改用户|修改")
+    @PutMapping("/edit")
+    public RestResult<?> edit(@RequestBody SysUser user) {
+        userService.updateById(user);
+        return RestResult.ok("修改成功");
+    }
+
+    /**
+     * 删除用户
+     */
+    @SystemLogHandler("删除用户|删除")
+    @DeleteMapping("/remove/{id}")
+    public RestResult<?> remove(@PathVariable Long id) {
+        userService.removeById(id);
+        return RestResult.ok("删除成功");
+    }
+
+    /**
+     * 查询用户列表
+     */
+    @SystemLogHandler("查询用户列表|查询")
+    @GetMapping("/list")
+    public RestResult<?> list() {
+        List<SysUser> users = userService.list();
+        return RestResult.ok(users);
+    }
+
+    /**
+     * 导出用户数据
+     */
+    @SystemLogHandler("导出用户数据|导出")
+    @GetMapping("/export")
+    public void export(HttpServletResponse response) {
+        // 导出逻辑
+    }
+}
+```
+
+### 3.2 注解格式说明
+
+```java
+@SystemLogHandler("操作描述|操作类型")
+```
+
+- **操作描述**:中文描述,说明这个操作做了什么(如:新增用户、修改角色、删除菜单)
+- **操作类型**:操作分类(如:新增、修改、删除、查询、导出、登录、登出)
+- 使用 `|` 分隔两部分
+
+**特殊处理**:
+- 如果操作类型是 `登录` 或 `登出`,日志类型自动标记为 `登录日志`
+- 其他操作类型,日志类型自动标记为 `操作日志`
+
+### 3.3 示例:登录/登出
+
+```java
+@RestController
+@RequestMapping("/auth")
+public class SysUserController {
+
+    /**
+     * 用户登录
+     */
+    @SystemLogHandler("用户登录|登录")
+    @PostMapping("/login")
+    public RestResult<?> login(@RequestBody LoginBody loginBody) {
+        // 登录逻辑
+    }
+
+    /**
+     * 用户登出
+     */
+    @SystemLogHandler("用户登出|登出")
+    @PostMapping("/logout")
+    public RestResult<?> logout() {
+        // 登出逻辑
+    }
+
+    /**
+     * 手机号验证码登录
+     */
+    @SystemLogHandler("手机号验证码登录|登录")
+    @PostMapping("/loginByPhone")
+    public RestResult<?> loginByPhone(@RequestBody LoginBody loginBody) {
+        // 登录逻辑
+    }
+}
+```
+
+## 4. 自动记录的信息
+
+### 4.1 用户信息(从SecurityContext获取)
+- 用户ID
+- 用户名
+- 真实姓名(昵称)
+
+### 4.2 操作信息
+- 日志类型(登录日志/操作日志)
+- 操作类型(新增/修改/删除等)
+- 操作描述
+- 控制器名称
+- 方法名称
+
+### 4.3 请求信息
+- 请求URL
+- 请求方式(GET/POST/PUT/DELETE)
+- 请求参数(自动过滤密码字段)
+
+### 4.4 环境信息
+- 操作IP地址
+- 浏览器类型
+- 操作系统
+
+### 4.5 执行信息
+- 操作时间
+- 执行时长(毫秒)
+- 执行状态(成功/失败)
+- 错误信息(如果失败)
+
+## 5. 敏感信息处理
+
+### 5.1 密码字段自动过滤
+
+系统自动将请求参数中的 `password` 字段替换为 `******`:
+
+```json
+// 原始参数
+{
+  "username": "admin",
+  "password": "admin123"
+}
+
+// 记录到日志
+{
+  "username": "admin",
+  "password": "******"
+}
+```
+
+### 5.2 参数长度限制
+
+请求参数超过2000字符时,自动截断并添加 `...` 后缀。
+
+## 6. 性能优化
+
+### 6.1 异步保存
+
+日志保存使用 `@Async` 异步执行,不影响主业务性能:
+
+```java
+@Async
+public void saveLogAsync(SysLog log) {
+    save(log);
+}
+```
+
+### 6.2 异常隔离
+
+日志保存失败不会影响主业务:
+
+```java
+try {
+    saveLog(jp, request, startTime, exception);
+} catch (Exception e) {
+    logger.error("保存操作日志失败", e);
+}
+```
+
+## 7. 查询日志
+
+### 7.1 按日志类型查询
+
+```sql
+-- 查询登录日志
+SELECT * FROM sys_log WHERE log_type = '登录日志' ORDER BY operate_time DESC;
+
+-- 查询操作日志
+SELECT * FROM sys_log WHERE log_type = '操作日志' ORDER BY operate_time DESC;
+```
+
+### 7.2 按用户查询
+
+```sql
+-- 查询某个用户的所有操作
+SELECT * FROM sys_log WHERE user_id = 1 ORDER BY operate_time DESC;
+
+-- 查询某个用户的登录记录
+SELECT * FROM sys_log
+WHERE user_id = 1 AND log_type = '登录日志'
+ORDER BY operate_time DESC;
+```
+
+### 7.3 按时间范围查询
+
+```sql
+-- 查询今天的操作日志
+SELECT * FROM sys_log
+WHERE DATE(operate_time) = CURDATE()
+ORDER BY operate_time DESC;
+
+-- 查询最近7天的操作日志
+SELECT * FROM sys_log
+WHERE operate_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
+ORDER BY operate_time DESC;
+```
+
+### 7.4 按操作类型查询
+
+```sql
+-- 查询所有删除操作
+SELECT * FROM sys_log WHERE operate_type = '删除' ORDER BY operate_time DESC;
+
+-- 查询所有失败的操作
+SELECT * FROM sys_log WHERE status = 0 ORDER BY operate_time DESC;
+```
+
+### 7.5 性能分析
+
+```sql
+-- 查询执行时间超过1秒的操作
+SELECT * FROM sys_log WHERE use_time > 1000 ORDER BY use_time DESC;
+
+-- 统计各操作类型的平均执行时间
+SELECT
+    operate_type,
+    COUNT(*) as count,
+    AVG(use_time) as avg_time,
+    MAX(use_time) as max_time
+FROM sys_log
+GROUP BY operate_type
+ORDER BY avg_time DESC;
+```
+
+## 8. 日志分析示例
+
+### 8.1 用户活跃度统计
+
+```sql
+-- 统计每个用户的操作次数
+SELECT
+    username,
+    real_name,
+    COUNT(*) as operate_count,
+    MAX(operate_time) as last_operate_time
+FROM sys_log
+WHERE log_type = '操作日志'
+GROUP BY user_id, username, real_name
+ORDER BY operate_count DESC;
+```
+
+### 8.2 登录统计
+
+```sql
+-- 统计每个用户的登录次数
+SELECT
+    username,
+    COUNT(*) as login_count,
+    MAX(operate_time) as last_login_time
+FROM sys_log
+WHERE log_type = '登录日志' AND operate_type = '登录'
+GROUP BY username
+ORDER BY login_count DESC;
+
+-- 统计每天的登录次数
+SELECT
+    DATE(operate_time) as date,
+    COUNT(*) as login_count
+FROM sys_log
+WHERE log_type = '登录日志' AND operate_type = '登录'
+GROUP BY DATE(operate_time)
+ORDER BY date DESC;
+```
+
+### 8.3 异常操作统计
+
+```sql
+-- 统计失败的操作
+SELECT
+    username,
+    detail,
+    error_msg,
+    operate_time
+FROM sys_log
+WHERE status = 0
+ORDER BY operate_time DESC
+LIMIT 100;
+
+-- 统计失败次数最多的操作
+SELECT
+    detail,
+    COUNT(*) as fail_count
+FROM sys_log
+WHERE status = 0
+GROUP BY detail
+ORDER BY fail_count DESC;
+```
+
+## 9. 注意事项
+
+### 9.1 性能考虑
+
+1. **异步保存**:日志使用异步方式保存,不影响主业务性能
+2. **批量查询**:查询大量日志时,建议添加时间范围限制
+3. **定期清理**:建议定期清理历史日志(如保留最近6个月)
+
+### 9.2 安全考虑
+
+1. **敏感信息**:密码字段自动过滤,不会记录到日志
+2. **权限控制**:日志查询接口需要添加权限控制
+3. **数据脱敏**:如果日志包含其他敏感信息,需要手动脱敏
+
+### 9.3 最佳实践
+
+1. **合理使用注解**:只在重要操作上添加 `@SystemLogHandler` 注解
+2. **清晰的描述**:操作描述要清晰明了,便于日后追踪
+3. **统一命名**:操作类型使用统一的命名(新增、修改、删除、查询、导出等)
+4. **及时清理**:定期清理历史日志,保持数据库性能
+
+## 10. 常见问题
+
+### Q1: 为什么有些方法没有记录日志?
+
+A: 只有添加了 `@SystemLogHandler` 注解的方法才会记录日志。请检查方法是否添加了该注解。
+
+### Q2: 日志中的用户信息为空?
+
+A: 可能的原因:
+- 用户未登录(匿名访问)
+- SecurityContext中没有认证信息
+- 登录接口因为在认证前执行,可能获取不到用户信息
+
+### Q3: 如何关闭某个接口的日志记录?
+
+A: 移除该方法上的 `@SystemLogHandler` 注解即可。
+
+### Q4: 日志保存失败怎么办?
+
+A: 日志保存失败不会影响主业务,但会在控制台输出错误日志。请检查:
+- 数据库连接是否正常
+- sys_log 表是否存在
+- 字段类型是否匹配
+
+### Q5: 如何自定义日志内容?
+
+A: 可以修改 `ControllerAop.java` 中的 `saveLog` 方法,添加自定义字段。
+
+## 11. 扩展功能
+
+### 11.1 添加IP地理位置解析
+
+可以集成IP地址库,将IP地址解析为具体地理位置:
+
+```java
+// 在 IpUtils 中添加方法
+public static String getIpLocation(String ip) {
+    // 调用IP地址库API,解析地理位置
+    // 返回如:"北京市 联通"
+}
+
+// 在 ControllerAop 中使用
+log.setOperateLocation(IpUtils.getIpLocation(ipAddr));
+```
+
+### 11.2 添加数据变更记录
+
+对于重要数据的修改,可以记录修改前后的值:
+
+```java
+@SystemLogHandler("修改用户|修改")
+@PutMapping("/edit")
+public RestResult<?> edit(@RequestBody SysUser user) {
+    // 查询修改前的数据
+    SysUser oldUser = userService.getById(user.getId());
+
+    // 执行修改
+    userService.updateById(user);
+
+    // 可以将修改前后的对比记录到日志的备注字段
+    return RestResult.ok("修改成功");
+}
+```
+
+### 11.3 实时日志推送
+
+可以集成WebSocket,将重要操作实时推送到管理员:
+
+```java
+@Autowired
+private WebSocketService webSocketService;
+
+// 在保存日志后推送
+if ("删除".equals(log.getOperateType())) {
+    webSocketService.sendToAdmin("用户执行了删除操作", log);
+}
+```
+
+---
+
+**文档版本**:v1.0
+**最后更新**:2025-10-29
+**维护者**:开发团队