diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..52ec97f --- /dev/null +++ b/api/README.md @@ -0,0 +1,211 @@ +# Coder Common Thin Backend API 文档 + +## 项目概述 + +Coder Common Thin Backend 是一个基于Spring Boot 3.5.0的企业级开发框架,采用插件化架构设计,提供了完整的后台管理系统功能。 + +## 技术栈 + +- **后端框架**: Spring Boot 3.5.0 + Java 17 +- **数据库**: MySQL 8 + MyBatis Plus 3.5.12 +- **缓存**: Redis (Spring Data Redis) +- **安全认证**: Sa-Token 1.43.0 +- **权限控制**: RBAC (Role-Based Access Control) +- **API文档**: OpenAPI 3.0 (Swagger) +- **工具库**: Hutool 5.8.38, Fastjson2 2.0.57, Guava 33.4.8 + +## 服务器信息 + +- **默认端口**: 18099 +- **基础URL**: http://localhost:18099 +- **API前缀**: /coder (除登录认证接口外) + +## 认证方式 + +系统使用Sa-Token进行身份认证和权限管理: +- **Token类型**: Bearer Token +- **Token位置**: 请求头 `Authorization` 字段 +- **Token过期**: 支持自定义过期时间 +- **权限验证**: 基于注解的权限验证 + +## 统一响应格式 + +所有接口均采用统一的JSON响应格式: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + // 具体数据 + }, + "traceId": "trace-id-value" +} +``` + +### 状态码说明 + +- **200**: 成功 +- **400**: 请求参数错误 +- **401**: 认证失败/未登录 +- **403**: 权限不足 +- **500**: 服务器内部错误 + +## 分页查询格式 + +分页查询请求参数: +```json +{ + "pageNo": 1, + "pageSize": 10, + "params": { + // 查询参数 + } +} +``` + +分页查询响应格式: +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "records": [ + // 数据记录 + ], + "total": 100, + "size": 10, + "current": 1, + "pages": 10, + "searchCount": true + }, + "traceId": "trace-id-value" +} +``` + +## 权限系统 + +系统采用RBAC权限模型: +- **用户 (User)**: 系统使用者 +- **角色 (Role)**: 权限的集合 +- **权限 (Permission)**: 具体的操作权限 +- **菜单 (Menu)**: 系统菜单和按钮权限 + +### 权限验证流程 + +1. 用户登录获取Token +2. 请求接口时携带Token +3. 系统验证Token有效性 +4. 根据用户角色验证权限 +5. 允许或拒绝访问 + +## API模块分类 + +### 1. 认证模块 (Authentication) +- [登录认证API](./authentication/登录认证API) +- [验证码API](./authentication/验证码API) + +### 2. 用户管理模块 (User Management) +- [用户管理API](./user/用户管理API) + +### 3. 权限管理模块 (Permission Management) +- [菜单管理API](./permission/菜单管理API) +- [角色管理API](./permission/角色管理API) + +### 4. 系统管理模块 (System Management) +- [文件管理API](./system/文件管理API) +- [图片管理API](./system/图片管理API) +- [登录日志API](./system/登录日志API) + +## 错误处理 + +系统提供了完善的错误处理机制: + +### 常见错误类型 + +1. **业务异常**: 自定义业务逻辑错误 +2. **认证异常**: 登录认证相关错误 +3. **权限异常**: 权限验证失败 +4. **参数异常**: 请求参数验证失败 +5. **系统异常**: 服务器内部错误 + +### 错误响应格式 + +```json +{ + "status": 400, + "msg": "具体错误信息", + "data": null, + "traceId": "trace-id-value" +} +``` + +## 开发环境配置 + +### 数据库配置 +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/coder_common_thin + username: root + password: your_password +``` + +### Redis配置 +```yaml +spring: + redis: + host: localhost + port: 6379 + password: your_password +``` + +### Sa-Token配置 +```yaml +sa-token: + token-name: Authorization + timeout: 2592000 + activity-timeout: -1 + is-concurrent: true + is-share: false + is-read-head: true + is-read-cookie: false +``` + +## 接口调用示例 + +### 登录接口调用 +```bash +curl -X POST \ + http://localhost:18099/auth/login \ + -H 'Content-Type: application/json' \ + -d '{ + "loginName": "admin", + "password": "123456", + "codeKey": "uuid-key", + "securityCode": "1234" + }' +``` + +### 携带Token调用接口 +```bash +curl -X GET \ + http://localhost:18099/coder/sysLoginUser/getLoginUserInformation \ + -H 'Authorization: Bearer your-token-value' +``` + +## 注意事项 + +1. 所有接口都需要进行认证,除了登录接口和验证码接口 +2. 权限验证基于角色和权限码进行 +3. 系统支持多设备登录 +4. 敏感操作会记录操作日志 +5. 建议在生产环境中启用HTTPS + +## 更新日志 + +- **v1.0.0** (2025-07-05): 初始版本发布 + +## 联系方式 + +如有问题,请联系开发团队。 \ No newline at end of file diff --git a/api/authentication/登录认证API.md b/api/authentication/登录认证API.md new file mode 100644 index 0000000..5f2be0b --- /dev/null +++ b/api/authentication/登录认证API.md @@ -0,0 +1,255 @@ +# 登录认证API + +## 概述 + +登录认证模块提供用户登录、退出、注册等功能,使用Sa-Token进行身份认证和会话管理。 + +## 接口列表 + +### 1. 用户登录 + +**接口地址**: `POST /auth/login` + +**接口描述**: 用户使用账号密码登录系统 + +**是否需要认证**: 否 + +**请求参数**: + +```json +{ + "loginName": "admin", + "password": "123456", + "codeKey": "uuid-key", + "securityCode": "1234", + "rememberMe": false +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| loginName | String | 是 | 登录账号 | 长度3-16位,只能包含字母和数字 | +| password | String | 是 | 登录密码 | 不能为空 | +| codeKey | String | 是 | 验证码UUID | 不能为空 | +| securityCode | String | 是 | 验证码 | 不能为空 | +| rememberMe | Boolean | 否 | 记住登录 | 默认false | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "tokenName": "Authorization", + "tokenValue": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." + }, + "traceId": "trace-123456" +} +``` + +**响应参数说明**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| tokenName | String | Token名称 | +| tokenValue | String | Token值 | + +**错误码**: + +| 状态码 | 错误信息 | 说明 | +|--------|----------|------| +| 400 | 账号长度为 3-16 位 | 登录账号长度不符合要求 | +| 400 | 账号格式为数字以及字母 | 登录账号格式不正确 | +| 400 | 请输入登录名 | 登录名为空 | +| 400 | 请输入密码 | 密码为空 | +| 400 | 验证码已失效 | 验证码Key无效 | +| 400 | 请输入验证码 | 验证码为空 | +| 400 | 验证码错误 | 验证码不正确 | +| 400 | 用户不存在 | 用户账号不存在 | +| 400 | 密码错误 | 密码不正确 | +| 400 | 账号已被停用 | 用户账号被禁用 | +| 400 | 账号已被锁定 | 用户账号被锁定 | + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/auth/login \ + -H 'Content-Type: application/json' \ + -d '{ + "loginName": "admin", + "password": "123456", + "codeKey": "550e8400-e29b-41d4-a716-446655440000", + "securityCode": "1234", + "rememberMe": false + }' +``` + +--- + +### 2. 用户退出 + +**接口地址**: `GET /auth/logout` + +**接口描述**: 用户退出登录,清除会话信息 + +**是否需要认证**: 是 + +**请求头**: + +``` +Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9... +``` + +**请求参数**: 无 + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "退出成功", + "traceId": "trace-123456" +} +``` + +**错误码**: + +| 状态码 | 错误信息 | 说明 | +|--------|----------|------| +| 401 | 当前会话未登录 | 用户未登录或Token无效 | + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/auth/logout \ + -H 'Authorization: your-token-value' +``` + +--- + +### 3. 用户注册 + +**接口地址**: `POST /auth/register` + +**接口描述**: 新用户注册账号 + +**是否需要认证**: 否 + +**请求参数**: + +```json +{ + "loginName": "newuser", + "password": "123456", + "userName": "新用户", + "codeKey": "uuid-key", + "securityCode": "1234" +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| loginName | String | 是 | 登录账号 | 长度3-16位,只能包含字母和数字 | +| password | String | 是 | 登录密码 | 不能为空 | +| userName | String | 是 | 用户姓名 | 不能为空 | +| codeKey | String | 是 | 验证码UUID | 不能为空 | +| securityCode | String | 是 | 验证码 | 不能为空 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "注册成功", + "traceId": "trace-123456" +} +``` + +**错误码**: + +| 状态码 | 错误信息 | 说明 | +|--------|----------|------| +| 400 | 账号长度为 3-16 位 | 登录账号长度不符合要求 | +| 400 | 账号格式为数字以及字母 | 登录账号格式不正确 | +| 400 | 请输入登录名 | 登录名为空 | +| 400 | 请输入密码 | 密码为空 | +| 400 | 请输入用户姓名 | 用户姓名为空 | +| 400 | 验证码已失效 | 验证码Key无效 | +| 400 | 请输入验证码 | 验证码为空 | +| 400 | 验证码错误 | 验证码不正确 | +| 400 | 账号已存在 | 登录账号已被注册 | + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/auth/register \ + -H 'Content-Type: application/json' \ + -d '{ + "loginName": "newuser", + "password": "123456", + "userName": "新用户", + "codeKey": "550e8400-e29b-41d4-a716-446655440000", + "securityCode": "1234" + }' +``` + +--- + +## 认证流程说明 + +### 1. 登录流程 + +1. 用户获取验证码(调用验证码接口) +2. 用户输入账号、密码、验证码 +3. 系统验证验证码有效性 +4. 系统验证用户账号和密码 +5. 系统检查用户状态(是否禁用/锁定) +6. 生成Token并返回给用户 +7. 用户后续请求携带Token + +### 2. Token使用 + +- Token需要在请求头中携带:`Authorization: token-value` +- Token有过期时间,过期后需要重新登录 +- 系统支持多设备登录 +- 管理员可以强制用户下线 + +### 3. 权限验证 + +- 登录后系统会根据用户角色加载权限 +- 每个接口都会验证用户是否有相应权限 +- 超级管理员拥有所有权限 + +### 4. 会话管理 + +- 用户登录信息存储在Redis中 +- 支持会话延长 +- 支持记住登录功能 +- 系统会记录用户登录日志 + +## 安全特性 + +1. **密码加密**: 使用盐值加密存储密码 +2. **验证码保护**: 防止暴力破解 +3. **账号锁定**: 连续错误3次自动锁定 +4. **IP限制**: 可配置IP白名单/黑名单 +5. **会话安全**: Token有效期管理 +6. **日志记录**: 记录所有登录操作 + +## 注意事项 + +1. 验证码有效期为5分钟 +2. Token默认有效期为30天 +3. 账号锁定时间为10分钟 +4. 注册功能可通过配置开启/关闭 +5. 建议在生产环境中启用HTTPS \ No newline at end of file diff --git a/api/authentication/验证码API.md b/api/authentication/验证码API.md new file mode 100644 index 0000000..c25b3f9 --- /dev/null +++ b/api/authentication/验证码API.md @@ -0,0 +1,397 @@ +# 验证码API + +## 概述 + +验证码模块提供图形验证码生成功能,支持PNG和GIF两种格式,用于防止恶意攻击和机器人注册。 + +## 接口列表 + +### 1. 生成PNG验证码 + +**接口地址**: `GET /captcha/png` + +**接口描述**: 生成PNG格式的图形验证码 + +**是否需要认证**: 否 + +**请求参数**: 无 + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "uuid": "550e8400-e29b-41d4-a716-446655440000", + "base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAkCAYAAAB...", + "captchaText": "1234" + }, + "traceId": "trace-123456" +} +``` + +**响应参数说明**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| uuid | String | 验证码唯一标识,用于后续验证 | +| base64 | String | 验证码图片Base64编码 | +| captchaText | String | 验证码文本(开发环境返回,生产环境不返回) | + +**特性说明**: + +- 验证码长度:4位数字 +- 图片尺寸:102x38像素 +- 有效期:5分钟 +- 字体:随机字体 +- 背景:随机背景色 +- 干扰线:随机干扰线 + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/captcha/png \ + -H 'Content-Type: application/json' +``` + +**前端使用示例**: + +```javascript +// 获取验证码 +function getCaptcha() { + fetch('/captcha/png') + .then(response => response.json()) + .then(data => { + if (data.status === 200) { + // 显示验证码图片 + document.getElementById('captchaImg').src = data.data.base64; + // 保存验证码UUID,用于登录时提交 + document.getElementById('codeKey').value = data.data.uuid; + } + }); +} + +// 点击刷新验证码 +document.getElementById('captchaImg').onclick = getCaptcha; +``` + +--- + +### 2. 生成GIF验证码 + +**接口地址**: `GET /captcha/gif` + +**接口描述**: 生成GIF动画格式的图形验证码 + +**是否需要认证**: 否 + +**请求参数**: 无 + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "uuid": "550e8400-e29b-41d4-a716-446655440001", + "base64": "data:image/gif;base64,R0lGODlhZgAyAPcAAAAAAP///wAAAP...", + "captchaText": "5678" + }, + "traceId": "trace-123456" +} +``` + +**响应参数说明**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| uuid | String | 验证码唯一标识,用于后续验证 | +| base64 | String | 验证码图片Base64编码 | +| captchaText | String | 验证码文本(开发环境返回,生产环境不返回) | + +**特性说明**: + +- 验证码长度:4位数字 +- 图片尺寸:102x38像素 +- 有效期:5分钟 +- 动画效果:字符摆动动画 +- 帧数:10帧 +- 动画速度:100ms/帧 + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/captcha/gif \ + -H 'Content-Type: application/json' +``` + +**前端使用示例**: + +```javascript +// 获取GIF验证码 +function getGifCaptcha() { + fetch('/captcha/gif') + .then(response => response.json()) + .then(data => { + if (data.status === 200) { + // 显示验证码图片 + document.getElementById('captchaImg').src = data.data.base64; + // 保存验证码UUID,用于登录时提交 + document.getElementById('codeKey').value = data.data.uuid; + } + }); +} +``` + +--- + +## 验证码配置 + +### 1. 验证码参数配置 + +```yaml +# application.yml +captcha: + # 验证码长度 + length: 4 + # 验证码宽度 + width: 102 + # 验证码高度 + height: 38 + # 验证码有效期(分钟) + timeout: 5 + # 字体大小 + font-size: 25 + # 干扰线数量 + line-count: 100 + # 是否开启验证码 + enabled: true +``` + +### 2. 验证码存储 + +- 验证码使用Redis存储 +- Key格式:`captcha:uuid` +- 过期时间:5分钟自动删除 +- 值存储:验证码文本(忽略大小写) + +### 3. 验证码验证 + +```java +// 验证码验证逻辑 +public boolean validateCaptcha(String uuid, String inputCode) { + // 从Redis获取验证码 + String correctCode = redisTemplate.opsForValue().get("captcha:" + uuid); + + if (correctCode == null) { + throw new BusinessException("验证码已过期"); + } + + // 不区分大小写验证 + if (!correctCode.equalsIgnoreCase(inputCode)) { + throw new BusinessException("验证码错误"); + } + + // 验证成功后立即删除验证码(防止重复使用) + redisTemplate.delete("captcha:" + uuid); + + return true; +} +``` + +--- + +## 安全特性 + +### 1. 防暴力破解 + +- 验证码一次性使用 +- 验证后立即删除 +- 5分钟自动过期 +- 支持IP限制 + +### 2. 防机器识别 + +- 随机字体和颜色 +- 随机背景和干扰线 +- 字符位置随机偏移 +- GIF动画增加识别难度 + +### 3. 防重放攻击 + +- 每次验证码都有唯一UUID +- 验证码使用后立即失效 +- 不允许重复使用 + +## 错误处理 + +### 常见错误情况 + +| 错误场景 | 错误码 | 错误信息 | +|----------|--------|----------| +| 验证码过期 | 400 | 验证码已失效 | +| 验证码错误 | 400 | 验证码错误 | +| 验证码为空 | 400 | 请输入验证码 | +| UUID无效 | 400 | 验证码已失效 | +| 系统异常 | 500 | 验证码生成失败 | + +### 错误响应示例 + +```json +{ + "status": 400, + "msg": "验证码已失效", + "data": null, + "traceId": "trace-123456" +} +``` + +--- + +## 使用建议 + +### 1. 前端集成 + +```html + +
+ 验证码 + + + +
+``` + +```javascript +// 初始化验证码 +function initCaptcha() { + fetch('/captcha/png') + .then(response => response.json()) + .then(data => { + if (data.status === 200) { + document.getElementById('captchaImg').src = data.data.base64; + document.getElementById('codeKey').value = data.data.uuid; + } + }); +} + +// 刷新验证码 +function refreshCaptcha() { + initCaptcha(); +} + +// 页面加载时获取验证码 +window.onload = initCaptcha; +``` + +### 2. 表单提交 + +```javascript +// 登录表单提交 +function login() { + const formData = { + loginName: document.getElementById('loginName').value, + password: document.getElementById('password').value, + codeKey: document.getElementById('codeKey').value, + securityCode: document.getElementById('securityCode').value + }; + + fetch('/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }) + .then(response => response.json()) + .then(data => { + if (data.status === 200) { + // 登录成功 + localStorage.setItem('token', data.data.tokenValue); + window.location.href = '/dashboard'; + } else { + // 登录失败,刷新验证码 + alert(data.msg); + refreshCaptcha(); + } + }); +} +``` + +### 3. 移动端适配 + +```css +/* 移动端验证码样式 */ +@media (max-width: 768px) { + .captcha-container { + display: flex; + align-items: center; + gap: 10px; + } + + #captchaImg { + width: 102px; + height: 38px; + cursor: pointer; + } + + #securityCode { + width: 100px; + height: 38px; + } +} +``` + +## 性能优化 + +### 1. 缓存策略 + +- 验证码图片不缓存 +- 验证码文本缓存5分钟 +- 使用Redis集群提高性能 + +### 2. 并发控制 + +- 支持高并发验证码生成 +- 每秒可生成1000+验证码 +- 使用连接池管理Redis连接 + +### 3. 资源优化 + +- 图片大小优化(小于2KB) +- 内存使用优化 +- 垃圾回收优化 + +## 监控和日志 + +### 1. 指标监控 + +- 验证码生成次数 +- 验证码验证次数 +- 验证码成功率 +- 验证码过期率 + +### 2. 日志记录 + +- 验证码生成日志 +- 验证码验证日志 +- 异常错误日志 +- 性能监控日志 + +### 3. 告警机制 + +- 验证码成功率过低告警 +- 验证码生成异常告警 +- Redis连接异常告警 + +## 注意事项 + +1. 生产环境不返回验证码文本 +2. 验证码区分大小写(可配置) +3. 验证码只能使用一次 +4. 建议定期清理过期验证码 +5. 支持自定义验证码样式和难度 \ No newline at end of file diff --git a/api/permission/菜单管理API.md b/api/permission/菜单管理API.md new file mode 100644 index 0000000..2a699f2 --- /dev/null +++ b/api/permission/菜单管理API.md @@ -0,0 +1,1315 @@ +# 菜单管理API + +## 概述 + +菜单管理模块是权限系统的核心组成部分,负责管理系统菜单、权限配置和路由生成。支持无限级菜单嵌套,提供目录、菜单、按钮三种类型的权限控制。 + +## 权限说明 + +菜单管理接口需要相应的权限才能访问: + +| 操作 | 权限码 | 说明 | +|------|--------|------| +| 查询菜单列表 | `system:menu:list` | 查看菜单列表权限 | +| 新增菜单 | `system:menu:add` | 新增菜单权限 | +| 修改菜单 | `system:menu:edit` | 修改菜单信息权限 | +| 删除菜单 | `system:menu:remove` | 删除菜单权限 | +| 分配权限 | `system:menu:assign` | 分配菜单权限 | + +## 菜单类型说明 + +| 类型 | 值 | 说明 | 用途 | +|------|---|------|------| +| 目录 | 1 | 菜单目录 | 用于菜单分组,不对应具体页面 | +| 菜单 | 2 | 菜单项 | 对应具体的页面路由 | +| 按钮 | 3 | 按钮权限 | 用于控制页面内的按钮显示 | + +## 接口列表 + +### 1. 分页查询菜单列表 + +**接口地址**: `GET /coder/sysMenu/listPage` + +**接口描述**: 分页查询系统菜单列表 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:list` + +**请求头**: +``` +Authorization: your-token-value +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| pageNo | Integer | 否 | 页码 | 1 | +| pageSize | Integer | 否 | 每页大小 | 10 | +| menuName | String | 否 | 菜单名称 | 用户管理 | +| menuStatus | String | 否 | 菜单状态 | 0 | +| auth | String | 否 | 权限标识 | system:user:list | + +**响应示例**: + +```json +{ + "code": 1, + "success": true, + "msg": "操作成功", + "data": [ + { + "menuId": 1, + "menuName": "系统管理", + "enName": "System Manage", + "parentId": 0, + "menuType": "1", + "name": "systemPage", + "path": "/system", + "component": "", + "icon": "Tools", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "/system/user", + "activeMenu": null + }, + { + "menuId": 2, + "menuName": "用户管理", + "enName": "User Manage", + "parentId": 1, + "menuType": "2", + "name": "userPage", + "path": "/system/user", + "component": "system/user/index", + "icon": "UserFilled", + "isHide": "1", + "isLink": "", + "isKeepAlive": "1", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": null + }, + { + "menuId": 12, + "menuName": "角色管理", + "enName": "Role Manage", + "parentId": 1, + "menuType": "2", + "name": "rolePage", + "path": "/system/role", + "component": "system/role/index", + "icon": "Avatar", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": null + }, + { + "menuId": 72, + "menuName": "文件管理", + "enName": "Files Manage", + "parentId": 70, + "menuType": "2", + "name": "filePage", + "path": "/tools/file", + "component": "system/file/index", + "icon": "FolderOpened", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": "" + }, + { + "menuId": 19, + "menuName": "菜单管理", + "enName": "Menu Manage", + "parentId": 1, + "menuType": "2", + "name": "menuPage", + "path": "/system/menu", + "component": "system/menu/index", + "icon": "Grid", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": null + }, + { + "menuId": 77, + "menuName": "图库管理", + "enName": "Pictures Manage", + "parentId": 70, + "menuType": "2", + "name": "picturePage", + "path": "/tools/picture", + "component": "system/picture/index", + "icon": "Picture", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": "" + }, + { + "menuId": 39, + "menuName": "登录日志", + "enName": "Login Logs", + "parentId": 1, + "menuType": "2", + "name": "loginlogPage", + "path": "/system/loginlog", + "component": "system/loginlog/index", + "icon": "Calendar", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": null + }, + { + "menuId": 42, + "menuName": "操作日志", + "enName": "Operate Logs", + "parentId": 1, + "menuType": "2", + "name": "operlogPage", + "path": "/system/operlog", + "component": "system/operlog/index", + "icon": "Notebook", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": null + }, + { + "menuId": 50, + "menuName": "个人中心", + "enName": "Personage Center", + "parentId": 1, + "menuType": "2", + "name": "personagePage", + "path": "/system/personage", + "component": "system/personage/index", + "icon": "User", + "isHide": "0", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": null, + "activeMenu": null + }, + { + "menuId": 166, + "menuName": "外部链接", + "enName": "External Link", + "parentId": 0, + "menuType": "1", + "name": "linkPage", + "path": "/link", + "component": "", + "icon": "Link", + "isHide": "0", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": null, + "activeMenu": null + } + ] +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysMenu/listPage?pageNo=1&pageSize=10&menuName=系统管理" \ + -H "Authorization: your-token-value" +``` + +--- + +### 2. 查询菜单列表 + +**接口地址**: `GET /coder/sysMenu/list` + +**接口描述**: 查询系统菜单列表(树形结构,不分页) + +**是否需要认证**: 是 + +**权限要求**: `system:menu:list` + +**请求参数**: 同分页查询(除pageNo、pageSize外) + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [ + { + "menuId": 1, + "menuName": "系统管理", + "enName": "System", + "parentId": 0, + "menuType": "1", + "path": "/system", + "name": "system", + "component": "Layout", + "icon": "system", + "auth": "", + "menuStatus": "0", + "activeMenu": "", + "isHide": "1", + "isLink": "1", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "isSpread": "0", + "sorted": 1, + "createBy": "admin", + "createTime": "2024-01-01 10:00:00", + "updateBy": "admin", + "updateTime": "2024-01-01 10:00:00", + "children": [ + // 子菜单... + ] + } + ], + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysMenu/list" \ + -H "Authorization: your-token-value" +``` + +--- + +### 3. 根据ID查询菜单 + +**接口地址**: `GET /coder/sysMenu/getById/{id}` + +**接口描述**: 根据菜单ID查询菜单详细信息 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:list` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 菜单ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "menuId": 1, + "menuName": "系统管理", + "enName": "System", + "parentId": 0, + "menuType": "1", + "path": "/system", + "name": "system", + "component": "Layout", + "icon": "system", + "auth": "", + "menuStatus": "0", + "activeMenu": "", + "isHide": "1", + "isLink": "1", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "isSpread": "0", + "sorted": 1, + "createBy": "admin", + "createTime": "2024-01-01 10:00:00", + "updateBy": "admin", + "updateTime": "2024-01-01 10:00:00" + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysMenu/getById/1 \ + -H "Authorization: your-token-value" +``` + +--- + +### 4. 新增菜单 + +**接口地址**: `POST /coder/sysMenu/add` + +**接口描述**: 新增系统菜单 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:add` + +**请求参数**: + +```json +{ + "menuName": "用户管理", + "enName": "User Management", + "parentId": 1, + "menuType": "2", + "path": "/system/user", + "name": "user", + "component": "/system/user/index", + "icon": "user", + "auth": "system:user:list", + "menuStatus": "0", + "activeMenu": "", + "isHide": "1", + "isLink": "1", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "isSpread": "0", + "sorted": 1 +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| menuName | String | 是 | 菜单名称 | 不能为空 | +| enName | String | 否 | 英文名称 | 可为空 | +| parentId | Long | 是 | 父菜单ID | 不能为空,0表示根菜单 | +| menuType | String | 是 | 菜单类型 | 1-目录 2-菜单 3-按钮 | +| path | String | 否 | 路由地址 | 菜单类型为2时必填 | +| name | String | 否 | 路由名称 | 菜单类型为2时必填 | +| component | String | 否 | 组件路径 | 菜单类型为2时必填 | +| icon | String | 否 | 菜单图标 | 可为空 | +| auth | String | 否 | 权限标识 | 按钮类型时必填 | +| menuStatus | String | 是 | 菜单状态 | 0-启用 1-停用 | +| activeMenu | String | 否 | 选中路由 | 可为空 | +| isHide | String | 是 | 是否隐藏 | 0-隐藏 1-显示 | +| isLink | String | 否 | 是否外链 | 0-是 1-否 | +| isKeepAlive | String | 否 | 是否缓存 | 0-是 1-否 | +| isFull | String | 否 | 是否全屏 | 0-是 1-否 | +| isAffix | String | 否 | 是否固定 | 0-是 1-否 | +| isSpread | String | 否 | 是否展开 | 0-是 1-否 | +| sorted | Integer | 是 | 显示顺序 | 不能为空 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "新增成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysMenu/add \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "menuName": "用户管理", + "enName": "User Management", + "parentId": 1, + "menuType": "2", + "path": "/system/user", + "name": "user", + "component": "/system/user/index", + "icon": "user", + "auth": "system:user:list", + "menuStatus": "0", + "activeMenu": "", + "isHide": "1", + "isLink": "1", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "isSpread": "0", + "sorted": 1 + }' +``` + +--- + +### 5. 修改菜单信息 + +**接口地址**: `POST /coder/sysMenu/update` + +**接口描述**: 修改系统菜单信息 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:edit` + +**请求参数**: + +```json +{ + "menuId": 1, + "menuName": "用户管理", + "enName": "User Management", + "parentId": 1, + "menuType": "2", + "path": "/system/user", + "name": "user", + "component": "/system/user/index", + "icon": "user", + "auth": "system:user:list", + "menuStatus": "0", + "activeMenu": "", + "isHide": "1", + "isLink": "1", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "isSpread": "0", + "sorted": 1 +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| menuId | Long | 是 | 菜单ID | 必须是有效的菜单ID | +| 其他参数 | - | - | 同新增菜单 | - | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysMenu/update \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "menuId": 1, + "menuName": "用户管理", + "parentId": 1, + "menuType": "2", + "menuStatus": "0", + "sorted": 1 + }' +``` + +--- + +### 6. 删除菜单 + +**接口地址**: `POST /coder/sysMenu/deleteById/{id}` + +**接口描述**: 根据ID删除菜单 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:remove` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 菜单ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysMenu/deleteById/1 \ + -H "Authorization: your-token-value" +``` + +--- + +### 7. 批量删除菜单 + +**接口地址**: `POST /coder/sysMenu/batchDelete` + +**接口描述**: 批量删除菜单 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:remove` + +**请求参数**: + +```json +{ + "ids": [1, 2, 3] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | Long[] | 是 | 菜单ID数组 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysMenu/batchDelete \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "ids": [1, 2, 3] + }' +``` + +--- + +### 8. 修改菜单状态 + +**接口地址**: `POST /coder/sysMenu/updateStatus/{id}/{menuStatus}` + +**接口描述**: 修改菜单状态(启用/停用) + +**是否需要认证**: 是 + +**权限要求**: `system:menu:edit` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 菜单ID | +| menuStatus | String | 是 | 菜单状态(0-启用 1-停用) | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysMenu/updateStatus/1/0 \ + -H "Authorization: your-token-value" +``` + +--- + +### 9. 修改菜单展开状态 + +**接口地址**: `POST /coder/sysMenu/updateSpread/{id}/{isSpread}` + +**接口描述**: 修改菜单展开状态 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:edit` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 菜单ID | +| isSpread | String | 是 | 展开状态(0-是 1-否) | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysMenu/updateSpread/1/0 \ + -H "Authorization: your-token-value" +``` + +--- + +### 10. 菜单级联下拉框 + +**接口地址**: `GET /coder/sysMenu/cascaderList` + +**接口描述**: 获取菜单级联下拉框数据 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:list` + +**请求参数**: 无 + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [ + { + "label": "系统管理", + "value": 1, + "parentId": "0", + "children": [ + { + "label": "用户管理", + "value": 2, + "parentId": "1" + }, + { + "label": "角色管理", + "value": 3, + "parentId": "1" + } + ] + } + ], + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysMenu/cascaderList \ + -H "Authorization: your-token-value" +``` + +--- + +### 11. 生成用户菜单路由 + +**接口地址**: `GET /coder/sysMenu/listRouters` + +**接口描述**: 根据当前用户权限生成前端菜单路由 + +**是否需要认证**: 是 + +**权限要求**: 无(已登录用户可访问) + +**请求参数**: 无 + +**响应示例**: + +```json +{ + "code": 1, + "success": true, + "msg": "操作成功", + "data": [ + { + "menuId": 1, + "menuName": "系统管理", + "enName": "System Manage", + "parentId": 0, + "menuType": "1", + "name": "systemPage", + "path": "/system", + "component": "", + "icon": "Tools", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "/system/user", + "activeMenu": null + }, + { + "menuId": 2, + "menuName": "用户管理", + "enName": "User Manage", + "parentId": 1, + "menuType": "2", + "name": "userPage", + "path": "/system/user", + "component": "system/user/index", + "icon": "UserFilled", + "isHide": "1", + "isLink": "", + "isKeepAlive": "1", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": null + }, + { + "menuId": 70, + "menuName": "系统工具", + "enName": "System Tools", + "parentId": 0, + "menuType": "1", + "name": "toolsPage", + "path": "/tools", + "component": "", + "icon": "Tools", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "/tools/file", + "activeMenu": null + }, + { + "menuId": 12, + "menuName": "角色管理", + "enName": "Role Manage", + "parentId": 1, + "menuType": "2", + "name": "rolePage", + "path": "/system/role", + "component": "system/role/index", + "icon": "Avatar", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": null + }, + { + "menuId": 72, + "menuName": "文件管理", + "enName": "Files Manage", + "parentId": 70, + "menuType": "2", + "name": "filePage", + "path": "/tools/file", + "component": "system/file/index", + "icon": "FolderOpened", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": "" + }, + { + "menuId": 19, + "menuName": "菜单管理", + "enName": "Menu Manage", + "parentId": 1, + "menuType": "2", + "name": "menuPage", + "path": "/system/menu", + "component": "system/menu/index", + "icon": "Grid", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": null + }, + { + "menuId": 77, + "menuName": "图库管理", + "enName": "Pictures Manage", + "parentId": 70, + "menuType": "2", + "name": "picturePage", + "path": "/tools/picture", + "component": "system/picture/index", + "icon": "Picture", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": "" + }, + { + "menuId": 39, + "menuName": "登录日志", + "enName": "Login Logs", + "parentId": 1, + "menuType": "2", + "name": "loginlogPage", + "path": "/system/loginlog", + "component": "system/loginlog/index", + "icon": "Calendar", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": null + }, + { + "menuId": 42, + "menuName": "操作日志", + "enName": "Operate Logs", + "parentId": 1, + "menuType": "2", + "name": "operlogPage", + "path": "/system/operlog", + "component": "system/operlog/index", + "icon": "Notebook", + "isHide": "1", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": "", + "activeMenu": null + }, + { + "menuId": 50, + "menuName": "个人中心", + "enName": "Personage Center", + "parentId": 1, + "menuType": "2", + "name": "personagePage", + "path": "/system/personage", + "component": "system/personage/index", + "icon": "User", + "isHide": "0", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": null, + "activeMenu": null + }, + { + "menuId": 166, + "menuName": "外部链接", + "enName": "External Link", + "parentId": 0, + "menuType": "1", + "name": "linkPage", + "path": "/link", + "component": "", + "icon": "Link", + "isHide": "0", + "isLink": "", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "redirect": null, + "activeMenu": null + } + ] +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysMenu/listRouters \ + -H "Authorization: your-token-value" +``` + +--- + +### 12. 查询正常菜单列表 + +**接口地址**: `GET /coder/sysMenu/listMenuNormal` + +**接口描述**: 查询状态正常的菜单列表 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:list` + +**请求参数**: 无 + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [ + { + "menuId": 1, + "menuName": "系统管理", + "enName": "System", + "parentId": 0, + "menuType": "1", + "path": "/system", + "name": "system", + "component": "Layout", + "icon": "system", + "auth": "", + "menuStatus": "0", + "activeMenu": "", + "isHide": "1", + "isLink": "1", + "isKeepAlive": "0", + "isFull": "1", + "isAffix": "1", + "isSpread": "0", + "sorted": 1, + "children": [ + // 子菜单... + ] + } + ], + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysMenu/listMenuNormal \ + -H "Authorization: your-token-value" +``` + +--- + +### 13. 根据角色ID查询菜单 + +**接口地址**: `GET /coder/sysMenu/listMenuIdsByRoleId/{roleId}` + +**接口描述**: 根据角色ID查询该角色拥有的菜单ID列表 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:list` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| roleId | Long | 是 | 角色ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [1, 2, 3, 4, 5], + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysMenu/listMenuIdsByRoleId/1 \ + -H "Authorization: your-token-value" +``` + +--- + +### 14. 保存角色菜单权限 + +**接口地址**: `POST /coder/sysMenu/saveRoleMenu/{roleId}/{menuIds}` + +**接口描述**: 保存角色的菜单权限 + +**是否需要认证**: 是 + +**权限要求**: `system:menu:assign` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| roleId | Long | 是 | 角色ID | +| menuIds | String | 是 | 菜单ID列表(逗号分隔) | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "保存成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysMenu/saveRoleMenu/1/1,2,3,4,5 \ + -H "Authorization: your-token-value" +``` + +--- + +## 菜单字段说明 + +### 基础字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| menuId | Long | 菜单ID | 1 | +| menuName | String | 菜单名称 | 系统管理 | +| enName | String | 英文名称 | System | +| parentId | Long | 父菜单ID | 0(根菜单) | +| menuType | String | 菜单类型 | 1-目录 2-菜单 3-按钮 | +| sorted | Integer | 显示顺序 | 1 | + +### 路由字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| path | String | 路由地址 | /system/user | +| name | String | 路由名称 | user | +| component | String | 组件路径 | /system/user/index | +| redirect | String | 重定向地址 | /system/user | +| activeMenu | String | 选中路由 | /system/user | + +### 权限字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| auth | String | 权限标识 | system:user:list | +| menuStatus | String | 菜单状态 | 0-启用 1-停用 | + +### 显示字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| icon | String | 菜单图标 | user | +| isHide | String | 是否隐藏 | 0-隐藏 1-显示 | +| isLink | String | 是否外链 | 0-是 1-否 | +| isKeepAlive | String | 是否缓存 | 0-是 1-否 | +| isFull | String | 是否全屏 | 0-是 1-否 | +| isAffix | String | 是否固定 | 0-是 1-否 | +| isSpread | String | 是否展开 | 0-是 1-否 | + +--- + +## 前端路由配置 + +### Vue Router 配置 + +```javascript +// 根据后端返回的菜单数据生成路由 +function generateRoutes(menuData) { + const routes = []; + + menuData.forEach(menu => { + const route = { + path: menu.path, + name: menu.name, + component: () => import(`@/views${menu.component}.vue`), + meta: { + title: menu.menuName, + icon: menu.icon, + isHide: menu.isHide === '0', + isLink: menu.isLink === '0', + isKeepAlive: menu.isKeepAlive === '0', + isFull: menu.isFull === '0', + isAffix: menu.isAffix === '0', + activeMenu: menu.activeMenu + } + }; + + if (menu.children && menu.children.length > 0) { + route.children = generateRoutes(menu.children); + } + + if (menu.redirect) { + route.redirect = menu.redirect; + } + + routes.push(route); + }); + + return routes; +} +``` + +### 权限控制 + +```javascript +// 页面权限控制 +router.beforeEach((to, from, next) => { + const userPermissions = store.getters.permissions; + + if (to.meta.auth) { + if (userPermissions.includes(to.meta.auth) || userPermissions.includes('*:*:*')) { + next(); + } else { + next('/403'); + } + } else { + next(); + } +}); + +// 按钮权限控制 +Vue.directive('permission', { + inserted(el, binding) { + const { value } = binding; + const permissions = store.getters.permissions; + + if (value) { + const hasPermission = permissions.includes(value) || permissions.includes('*:*:*'); + if (!hasPermission) { + el.parentNode && el.parentNode.removeChild(el); + } + } + } +}); +``` + +--- + +## 错误码说明 + +| 错误码 | 错误信息 | 说明 | +|--------|----------|------| +| 400 | 菜单名称不能为空 | 菜单名称为空 | +| 400 | 上级菜单不能为空 | 父菜单ID为空 | +| 400 | 菜单类型不能为空 | 菜单类型为空 | +| 400 | 菜单状态不能为空 | 菜单状态为空 | +| 400 | 是否隐藏菜单不能为空 | 隐藏状态为空 | +| 400 | 显示顺序不能为空 | 排序值为空 | +| 400 | 菜单不存在 | 菜单ID不存在 | +| 400 | 存在子菜单,不允许删除 | 菜单有子菜单时不能删除 | +| 400 | 菜单已分配,不允许删除 | 菜单已分配给角色时不能删除 | +| 400 | 不能选择自己作为父菜单 | 父菜单不能是自己 | +| 400 | 路由地址不能为空 | 菜单类型为菜单时路由地址必填 | +| 400 | 组件路径不能为空 | 菜单类型为菜单时组件路径必填 | +| 400 | 权限标识不能为空 | 菜单类型为按钮时权限标识必填 | +| 401 | 当前会话未登录 | 未登录或Token无效 | +| 403 | 权限不足 | 没有相应的操作权限 | +| 500 | 系统异常 | 服务器内部错误 | + +--- + +## 使用建议 + +### 1. 菜单设计规范 + +- **层级结构**: 建议不超过3级菜单 +- **命名规范**: 使用有意义的英文名称作为路由名称 +- **权限标识**: 使用模块:功能:操作的格式 +- **图标使用**: 统一使用Element UI或其他图标库 + +### 2. 权限设计 + +- **粒度控制**: 按钮级别的权限控制 +- **角色分离**: 不同角色分配不同的菜单权限 +- **继承关系**: 子菜单权限依赖于父菜单权限 +- **缓存策略**: 用户权限信息缓存到前端 + +### 3. 性能优化 + +- **懒加载**: 菜单组件使用懒加载 +- **缓存机制**: 菜单数据缓存到本地存储 +- **权限缓存**: 权限信息缓存到内存中 +- **树形结构**: 使用高效的树形数据结构 + +### 4. 安全考虑 + +- **权限验证**: 前后端都要进行权限验证 +- **敏感操作**: 重要操作需要二次确认 +- **日志记录**: 记录权限变更操作日志 +- **最小权限**: 遵循最小权限原则 + +--- + +## 注意事项 + +1. **菜单删除**: 删除菜单前需要检查是否有子菜单和角色关联 +2. **权限继承**: 子菜单的权限依赖于父菜单 +3. **缓存更新**: 菜单权限变更后需要清理相关缓存 +4. **前端同步**: 菜单结构变更后前端需要同步更新 +5. **权限验证**: 前后端都需要进行权限验证 +6. **状态管理**: 菜单状态变更会影响用户访问 +7. **排序规则**: 菜单按照sorted字段升序排列 +8. **数据完整性**: 保证菜单数据的完整性和一致性 \ No newline at end of file diff --git a/api/permission/角色管理API.md b/api/permission/角色管理API.md new file mode 100644 index 0000000..673a18f --- /dev/null +++ b/api/permission/角色管理API.md @@ -0,0 +1,882 @@ +# 角色管理API + +## 概述 + +角色管理模块是权限系统的重要组成部分,负责管理系统角色、角色权限分配和用户角色关联。基于RBAC权限模型,实现了灵活的角色权限管理机制。 + +## 权限说明 + +角色管理接口需要相应的权限才能访问: + +| 操作 | 权限码 | 说明 | +|------|--------|------| +| 查询角色列表 | `system:role:list` | 查看角色列表权限 | +| 新增角色 | `system:role:add` | 新增角色权限 | +| 修改角色 | `system:role:edit` | 修改角色信息权限 | +| 删除角色 | `system:role:remove` | 删除角色权限 | +| 分配权限 | `system:role:assign` | 分配角色权限 | +| 用户授权 | `system:user:role` | 分配用户角色权限 | + +## 接口列表 + +### 1. 分页查询角色列表 + +**接口地址**: `GET /coder/sysRole/listPage` + +**接口描述**: 分页查询系统角色列表 + +**是否需要认证**: 是 + +**权限要求**: `system:role:list` + +**请求头**: +``` +Authorization: your-token-value +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| pageNo | Integer | 否 | 页码 | 1 | +| pageSize | Integer | 否 | 每页大小 | 10 | +| roleName | String | 否 | 角色名称 | 管理员 | +| roleCode | String | 否 | 角色编码 | admin | +| roleStatus | String | 否 | 角色状态 | 0 | +| beginTime | String | 否 | 开始时间 | 2024-01-01 | +| endTime | String | 否 | 结束时间 | 2024-12-31 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "records": [ + { + "roleId": 1, + "roleName": "超级管理员", + "roleCode": "admin", + "roleStatus": "0", + "dataScope": "1", + "deptCheckStrictly": "1", + "menuCheckStrictly": "1", + "sorted": 1, + "remark": "超级管理员", + "createBy": "admin", + "createTime": "2024-01-01 10:00:00", + "updateBy": "admin", + "updateTime": "2024-01-01 10:00:00", + "menuIds": [1, 2, 3, 4, 5], + "deptIds": [100, 101, 102] + } + ], + "total": 1, + "size": 10, + "current": 1, + "pages": 1 + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysRole/listPage?pageNo=1&pageSize=10&roleName=管理员" \ + -H "Authorization: your-token-value" +``` + +--- + +### 2. 查询所有角色 + +**接口地址**: `GET /coder/sysRole/list` + +**接口描述**: 查询所有系统角色(不分页) + +**是否需要认证**: 是 + +**权限要求**: `system:role:list` + +**请求参数**: 同分页查询(除pageNo、pageSize外) + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [ + { + "roleId": 1, + "roleName": "超级管理员", + "roleCode": "admin", + "roleStatus": "0", + "dataScope": "1", + "deptCheckStrictly": "1", + "menuCheckStrictly": "1", + "sorted": 1, + "remark": "超级管理员", + "createBy": "admin", + "createTime": "2024-01-01 10:00:00", + "updateBy": "admin", + "updateTime": "2024-01-01 10:00:00" + } + ], + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysRole/list" \ + -H "Authorization: your-token-value" +``` + +--- + +### 3. 根据ID查询角色 + +**接口地址**: `GET /coder/sysRole/getById/{id}` + +**接口描述**: 根据角色ID查询角色详细信息 + +**是否需要认证**: 是 + +**权限要求**: `system:role:list` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 角色ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "roleId": 1, + "roleName": "超级管理员", + "roleCode": "admin", + "roleStatus": "0", + "dataScope": "1", + "deptCheckStrictly": "1", + "menuCheckStrictly": "1", + "sorted": 1, + "remark": "超级管理员", + "createBy": "admin", + "createTime": "2024-01-01 10:00:00", + "updateBy": "admin", + "updateTime": "2024-01-01 10:00:00", + "menuIds": [1, 2, 3, 4, 5], + "deptIds": [100, 101, 102] + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysRole/getById/1 \ + -H "Authorization: your-token-value" +``` + +--- + +### 4. 新增角色 + +**接口地址**: `POST /coder/sysRole/add` + +**接口描述**: 新增系统角色 + +**是否需要认证**: 是 + +**权限要求**: `system:role:add` + +**请求参数**: + +```json +{ + "roleName": "普通用户", + "roleCode": "user", + "roleStatus": "0", + "dataScope": "2", + "deptCheckStrictly": "1", + "menuCheckStrictly": "1", + "sorted": 2, + "remark": "普通用户角色", + "menuIds": [1, 2, 3], + "deptIds": [100, 101] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| roleName | String | 是 | 角色名称 | 不能为空,最长30字符 | +| roleCode | String | 是 | 角色编码 | 不能为空,最长100字符,唯一 | +| roleStatus | String | 是 | 角色状态 | 0-正常 1-停用 | +| dataScope | String | 否 | 数据范围 | 1-全部数据 2-自定义数据 3-本部门数据 4-本部门及以下数据 5-仅本人数据 | +| deptCheckStrictly | String | 否 | 部门树选择项是否关联显示 | 0-父子不互相关联显示 1-父子互相关联显示 | +| menuCheckStrictly | String | 否 | 菜单树选择项是否关联显示 | 0-父子不互相关联显示 1-父子互相关联显示 | +| sorted | Integer | 是 | 显示顺序 | 不能为空 | +| remark | String | 否 | 备注信息 | 最长200字符 | +| menuIds | Long[] | 否 | 菜单ID数组 | 有效的菜单ID | +| deptIds | Long[] | 否 | 部门ID数组 | 有效的部门ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "新增成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysRole/add \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "roleName": "普通用户", + "roleCode": "user", + "roleStatus": "0", + "dataScope": "2", + "deptCheckStrictly": "1", + "menuCheckStrictly": "1", + "sorted": 2, + "remark": "普通用户角色", + "menuIds": [1, 2, 3], + "deptIds": [100, 101] + }' +``` + +--- + +### 5. 获取最新排序号 + +**接口地址**: `GET /coder/sysRole/getSorted` + +**接口描述**: 获取角色的最新排序号 + +**是否需要认证**: 是 + +**权限要求**: `system:role:add` + +**请求参数**: 无 + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": 10, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysRole/getSorted \ + -H "Authorization: your-token-value" +``` + +--- + +### 6. 修改角色信息 + +**接口地址**: `POST /coder/sysRole/update` + +**接口描述**: 修改系统角色信息 + +**是否需要认证**: 是 + +**权限要求**: `system:role:edit` + +**请求参数**: + +```json +{ + "roleId": 1, + "roleName": "超级管理员", + "roleCode": "admin", + "roleStatus": "0", + "dataScope": "1", + "deptCheckStrictly": "1", + "menuCheckStrictly": "1", + "sorted": 1, + "remark": "超级管理员", + "menuIds": [1, 2, 3, 4, 5], + "deptIds": [100, 101, 102] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| roleId | Long | 是 | 角色ID | 必须是有效的角色ID | +| 其他参数 | - | - | 同新增角色 | - | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysRole/update \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "roleId": 1, + "roleName": "超级管理员", + "roleCode": "admin", + "roleStatus": "0", + "sorted": 1 + }' +``` + +--- + +### 7. 删除角色 + +**接口地址**: `POST /coder/sysRole/deleteById/{id}` + +**接口描述**: 根据ID删除角色 + +**是否需要认证**: 是 + +**权限要求**: `system:role:remove` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 角色ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysRole/deleteById/1 \ + -H "Authorization: your-token-value" +``` + +--- + +### 8. 批量删除角色 + +**接口地址**: `POST /coder/sysRole/batchDelete` + +**接口描述**: 批量删除角色 + +**是否需要认证**: 是 + +**权限要求**: `system:role:remove` + +**请求参数**: + +```json +{ + "ids": [1, 2, 3] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | Long[] | 是 | 角色ID数组 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysRole/batchDelete \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "ids": [1, 2, 3] + }' +``` + +--- + +### 9. 修改角色状态 + +**接口地址**: `POST /coder/sysRole/updateStatus/{roleId}/{roleStatus}` + +**接口描述**: 修改角色状态(正常/停用) + +**是否需要认证**: 是 + +**权限要求**: `system:role:edit` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| roleId | Long | 是 | 角色ID | +| roleStatus | String | 是 | 角色状态(0-正常 1-停用) | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysRole/updateStatus/1/0 \ + -H "Authorization: your-token-value" +``` + +--- + +### 10. 查询正常角色穿梭框 + +**接口地址**: `GET /coder/sysRole/listNormalRole/{userId}` + +**接口描述**: 查询正常状态的角色用于穿梭框显示 + +**是否需要认证**: 是 + +**权限要求**: `system:user:role` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| userId | Long | 是 | 用户ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [ + { + "label": "超级管理员", + "value": 1, + "parentId": "" + }, + { + "label": "普通用户", + "value": 2, + "parentId": "" + } + ], + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysRole/listNormalRole/1 \ + -H "Authorization: your-token-value" +``` + +--- + +### 11. 分配用户角色 + +**接口地址**: `GET /coder/sysRole/assignUserRole/{userId}/{roleIds}` + +**接口描述**: 为用户分配角色 + +**是否需要认证**: 是 + +**权限要求**: `system:user:role` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| userId | Long | 是 | 用户ID | +| roleIds | String | 是 | 角色ID列表(逗号分隔) | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "分配成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysRole/assignUserRole/1/1,2,3 \ + -H "Authorization: your-token-value" +``` + +--- + +### 12. 获取角色下拉框 + +**接口地址**: `GET /coder/sysRole/listRoleElSelect` + +**接口描述**: 获取角色下拉框数据 + +**是否需要认证**: 是 + +**权限要求**: `system:role:list` + +**请求参数**: 无 + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [ + { + "label": "超级管理员", + "value": 1 + }, + { + "label": "普通用户", + "value": 2 + } + ], + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysRole/listRoleElSelect \ + -H "Authorization: your-token-value" +``` + +--- + +## 角色字段说明 + +### 基础字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| roleId | Long | 角色ID | 1 | +| roleName | String | 角色名称 | 超级管理员 | +| roleCode | String | 角色编码 | admin | +| roleStatus | String | 角色状态 | 0-正常 1-停用 | +| sorted | Integer | 显示顺序 | 1 | +| remark | String | 备注信息 | 超级管理员 | + +### 权限字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| dataScope | String | 数据权限范围 | 1-全部数据 2-自定义数据 3-本部门数据 4-本部门及以下数据 5-仅本人数据 | +| deptCheckStrictly | String | 部门树选择项是否关联显示 | 0-父子不互相关联显示 1-父子互相关联显示 | +| menuCheckStrictly | String | 菜单树选择项是否关联显示 | 0-父子不互相关联显示 1-父子互相关联显示 | + +### 关联字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| menuIds | Long[] | 菜单ID数组 | [1, 2, 3, 4, 5] | +| deptIds | Long[] | 部门ID数组 | [100, 101, 102] | + +--- + +## 数据字典 + +### 角色状态 (roleStatus) + +| 值 | 说明 | +|----|------| +| 0 | 正常 | +| 1 | 停用 | + +### 数据权限范围 (dataScope) + +| 值 | 说明 | +|----|------| +| 1 | 全部数据权限 | +| 2 | 自定数据权限 | +| 3 | 本部门数据权限 | +| 4 | 本部门及以下数据权限 | +| 5 | 仅本人数据权限 | + +### 树选择项关联显示 + +| 值 | 说明 | +|----|------| +| 0 | 父子不互相关联显示 | +| 1 | 父子互相关联显示 | + +--- + +## 权限验证机制 + +### 1. 角色权限验证 + +```java +// Sa-Token权限验证 +@SaCheckPermission("system:role:list") +public List list() { + // 业务逻辑 +} +``` + +### 2. 数据权限控制 + +```java +// 根据用户数据权限过滤数据 +public List getRolesByDataScope(Long userId) { + SysUser user = getCurrentUser(); + String dataScope = user.getDataScope(); + + switch (dataScope) { + case "1": // 全部数据权限 + return getAllRoles(); + case "2": // 自定义数据权限 + return getRolesByDeptIds(user.getDeptIds()); + case "3": // 本部门数据权限 + return getRolesByDept(user.getDeptId()); + case "4": // 本部门及以下数据权限 + return getRolesByDeptAndChildren(user.getDeptId()); + case "5": // 仅本人数据权限 + return getRolesByUserId(userId); + default: + return new ArrayList<>(); + } +} +``` + +### 3. 角色权限缓存 + +```java +// 缓存用户角色权限 +@Cacheable(value = "userRoles", key = "#userId") +public List getRolesByUserId(Long userId) { + return roleMapper.selectRolePermissionByUserId(userId); +} + +// 清除角色权限缓存 +@CacheEvict(value = "userRoles", key = "#userId") +public void clearUserRoleCache(Long userId) { + // 角色变更时清除缓存 +} +``` + +--- + +## 错误码说明 + +| 错误码 | 错误信息 | 说明 | +|--------|----------|------| +| 400 | 角色名称不能为空 | 角色名称为空 | +| 400 | 角色权限编码不能为空 | 角色编码为空 | +| 400 | 显示顺序不能为空 | 排序值为空 | +| 400 | 角色不存在 | 角色ID不存在 | +| 400 | 角色编码已存在 | 角色编码重复 | +| 400 | 角色已分配用户,不允许删除 | 角色已分配给用户时不能删除 | +| 400 | 不能删除超级管理员角色 | 超级管理员角色不能删除 | +| 400 | 不能停用超级管理员角色 | 超级管理员角色不能停用 | +| 400 | 用户不存在 | 用户ID不存在 | +| 400 | 角色ID列表不能为空 | 分配角色时角色ID为空 | +| 401 | 当前会话未登录 | 未登录或Token无效 | +| 403 | 权限不足 | 没有相应的操作权限 | +| 500 | 系统异常 | 服务器内部错误 | + +--- + +## RBAC权限模型 + +### 1. 基本概念 + +- **用户 (User)**: 系统的使用者 +- **角色 (Role)**: 权限的集合,连接用户和权限的桥梁 +- **权限 (Permission)**: 对系统资源的操作权限 +- **会话 (Session)**: 用户与系统的一次交互过程 + +### 2. 关系模型 + +``` +用户 ←→ 用户角色关系 ←→ 角色 ←→ 角色权限关系 ←→ 权限 +``` + +### 3. 数据库设计 + +```sql +-- 用户表 +CREATE TABLE sys_login_user ( + user_id BIGINT PRIMARY KEY, + login_name VARCHAR(32) UNIQUE NOT NULL, + user_name VARCHAR(32) NOT NULL, + -- 其他字段... +); + +-- 角色表 +CREATE TABLE sys_role ( + role_id BIGINT PRIMARY KEY, + role_name VARCHAR(32) NOT NULL, + role_code VARCHAR(32) UNIQUE NOT NULL, + role_status CHAR(1) DEFAULT '0', + -- 其他字段... +); + +-- 权限表(菜单表) +CREATE TABLE sys_menu ( + menu_id BIGINT PRIMARY KEY, + menu_name VARCHAR(64) NOT NULL, + auth VARCHAR(128), + -- 其他字段... +); + +-- 用户角色关联表 +CREATE TABLE sys_user_role ( + user_id BIGINT NOT NULL, + role_id BIGINT NOT NULL, + PRIMARY KEY (user_id, role_id) +); + +-- 角色权限关联表 +CREATE TABLE sys_role_menu ( + role_id BIGINT NOT NULL, + menu_id BIGINT NOT NULL, + PRIMARY KEY (role_id, menu_id) +); +``` + +--- + +## 使用建议 + +### 1. 角色设计原则 + +- **最小权限原则**: 每个角色只分配必要的权限 +- **职责分离**: 不同职责的操作分配给不同角色 +- **角色层次**: 建立角色层次结构,高级角色继承低级角色权限 +- **定期审查**: 定期审查角色权限,及时调整 + +### 2. 权限分配策略 + +- **默认无权限**: 新建角色默认无任何权限 +- **逐步授权**: 根据业务需要逐步授权 +- **权限组合**: 通过多个角色组合实现复杂权限需求 +- **临时授权**: 支持临时权限授权机制 + +### 3. 数据权限控制 + +- **部门数据权限**: 基于用户所属部门控制数据访问范围 +- **自定义数据权限**: 支持自定义数据访问范围 +- **数据隔离**: 确保不同用户只能访问授权范围内的数据 +- **审计日志**: 记录数据访问日志 + +### 4. 性能优化 + +- **权限缓存**: 将用户权限信息缓存到Redis +- **批量操作**: 支持批量分配和撤销权限 +- **索引优化**: 对关联表建立合适的索引 +- **分页查询**: 大数据量时使用分页查询 + +--- + +## 安全考虑 + +### 1. 权限验证 + +- **双重验证**: 前后端都要进行权限验证 +- **接口鉴权**: 每个接口都要验证权限 +- **数据鉴权**: 数据层面的权限验证 +- **操作鉴权**: 敏感操作的二次验证 + +### 2. 权限变更 + +- **变更日志**: 记录所有权限变更操作 +- **审批流程**: 重要权限变更需要审批 +- **通知机制**: 权限变更及时通知相关人员 +- **回滚机制**: 支持权限变更回滚 + +### 3. 会话安全 + +- **会话超时**: 设置会话超时时间 +- **单点登录**: 防止重复登录 +- **强制下线**: 支持强制用户下线 +- **设备绑定**: 支持设备级别的访问控制 + +--- + +## 注意事项 + +1. **角色删除**: 删除角色前需要检查是否有用户关联 +2. **权限继承**: 考虑角色之间的权限继承关系 +3. **缓存一致性**: 角色权限变更后需要清理相关缓存 +4. **数据完整性**: 保证角色权限数据的完整性 +5. **超级管理员**: 超级管理员角色不能删除和停用 +6. **权限验证**: 前后端都需要进行权限验证 +7. **审计日志**: 记录所有角色操作日志 +8. **性能考虑**: 大量用户时注意权限验证性能 +9. **数据权限**: 正确配置数据权限范围 +10. **定期清理**: 定期清理无效的角色和权限关联 \ No newline at end of file diff --git a/api/system/图片管理API.md b/api/system/图片管理API.md new file mode 100644 index 0000000..67339a6 --- /dev/null +++ b/api/system/图片管理API.md @@ -0,0 +1,871 @@ +# 图片管理API + +## 概述 + +图片管理模块专门用于管理系统中的图片资源,提供图片的上传、存储、展示和管理功能。支持多种图片格式,具备图片压缩、水印、缩略图生成等高级功能。 + +## 权限说明 + +图片管理接口需要相应的权限才能访问: + +| 操作 | 权限码 | 说明 | +|------|--------|------| +| 查询图片列表 | `system:picture:list` | 查看图片列表权限 | +| 上传图片 | `system:picture:upload` | 上传图片权限 | +| 删除图片 | `system:picture:remove` | 删除图片权限 | +| 编辑图片 | `system:picture:edit` | 编辑图片信息权限 | + +## 接口列表 + +### 1. 分页查询图片列表 + +**接口地址**: `GET /coder/sysPicture/listPage` + +**接口描述**: 分页查询系统图片列表 + +**是否需要认证**: 是 + +**权限要求**: `system:picture:list` + +**请求头**: +``` +Authorization: Bearer your-token-value +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| pageNo | Integer | 否 | 页码 | 1 | +| pageSize | Integer | 否 | 每页大小 | 10 | +| pictureName | String | 否 | 图片名称 | banner.jpg | +| pictureType | String | 否 | 图片类型 | jpg | +| albumName | String | 否 | 相册名称 | banner | +| pictureStatus | String | 否 | 图片状态 | 0 | +| beginTime | String | 否 | 开始时间 | 2024-01-01 | +| endTime | String | 否 | 结束时间 | 2024-12-31 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "records": [ + { + "pictureId": 1, + "pictureName": "banner_20240705_001.jpg", + "originalName": "主页横幅.jpg", + "picturePath": "/upload/pictures/banner/2024/07/05/banner_20240705_001.jpg", + "pictureUrl": "http://localhost:18099/upload/pictures/banner/2024/07/05/banner_20240705_001.jpg", + "thumbnailUrl": "http://localhost:18099/upload/pictures/banner/2024/07/05/thumb_banner_20240705_001.jpg", + "albumName": "banner", + "pictureSize": 2048000, + "pictureType": "jpg", + "pictureFormat": "JPEG", + "width": 1920, + "height": 1080, + "pictureStatus": "0", + "isWatermark": "0", + "uploadUserId": 1, + "uploadUserName": "admin", + "description": "主页横幅图片", + "tags": "横幅,主页,展示", + "viewCount": 100, + "downloadCount": 10, + "remark": "网站主页横幅", + "createBy": "admin", + "createTime": "2024-07-05 10:00:00", + "updateBy": "admin", + "updateTime": "2024-07-05 10:00:00" + } + ], + "total": 1, + "size": 10, + "current": 1, + "pages": 1 + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysPicture/listPage?pageNo=1&pageSize=10&albumName=banner" \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 2. 查询所有图片 + +**接口地址**: `GET /coder/sysPicture/list` + +**接口描述**: 查询所有系统图片(不分页) + +**是否需要认证**: 是 + +**权限要求**: `system:picture:list` + +**请求参数**: 同分页查询(除pageNo、pageSize外) + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [ + { + "pictureId": 1, + "pictureName": "banner_20240705_001.jpg", + "originalName": "主页横幅.jpg", + "picturePath": "/upload/pictures/banner/2024/07/05/banner_20240705_001.jpg", + "pictureUrl": "http://localhost:18099/upload/pictures/banner/2024/07/05/banner_20240705_001.jpg", + "thumbnailUrl": "http://localhost:18099/upload/pictures/banner/2024/07/05/thumb_banner_20240705_001.jpg", + "albumName": "banner", + "pictureSize": 2048000, + "pictureType": "jpg", + "pictureFormat": "JPEG", + "width": 1920, + "height": 1080, + "pictureStatus": "0", + "isWatermark": "0", + "uploadUserId": 1, + "uploadUserName": "admin", + "description": "主页横幅图片", + "tags": "横幅,主页,展示", + "viewCount": 100, + "downloadCount": 10, + "remark": "网站主页横幅", + "createBy": "admin", + "createTime": "2024-07-05 10:00:00", + "updateBy": "admin", + "updateTime": "2024-07-05 10:00:00" + } + ], + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysPicture/list?albumName=banner" \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 3. 根据ID查询图片 + +**接口地址**: `GET /coder/sysPicture/getById/{id}` + +**接口描述**: 根据图片ID查询图片详细信息 + +**是否需要认证**: 是 + +**权限要求**: `system:picture:list` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 图片ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "pictureId": 1, + "pictureName": "banner_20240705_001.jpg", + "originalName": "主页横幅.jpg", + "picturePath": "/upload/pictures/banner/2024/07/05/banner_20240705_001.jpg", + "pictureUrl": "http://localhost:18099/upload/pictures/banner/2024/07/05/banner_20240705_001.jpg", + "thumbnailUrl": "http://localhost:18099/upload/pictures/banner/2024/07/05/thumb_banner_20240705_001.jpg", + "albumName": "banner", + "pictureSize": 2048000, + "pictureType": "jpg", + "pictureFormat": "JPEG", + "width": 1920, + "height": 1080, + "pictureStatus": "0", + "isWatermark": "0", + "uploadUserId": 1, + "uploadUserName": "admin", + "description": "主页横幅图片", + "tags": "横幅,主页,展示", + "viewCount": 100, + "downloadCount": 10, + "remark": "网站主页横幅", + "createBy": "admin", + "createTime": "2024-07-05 10:00:00", + "updateBy": "admin", + "updateTime": "2024-07-05 10:00:00" + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysPicture/getById/1 \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 4. 新增图片记录 + +**接口地址**: `POST /coder/sysPicture/add` + +**接口描述**: 新增系统图片记录 + +**是否需要认证**: 是 + +**权限要求**: `system:picture:upload` + +**请求参数**: + +```json +{ + "pictureName": "product_20240705_001.jpg", + "originalName": "产品展示图.jpg", + "picturePath": "/upload/pictures/product/2024/07/05/product_20240705_001.jpg", + "pictureUrl": "http://localhost:18099/upload/pictures/product/2024/07/05/product_20240705_001.jpg", + "thumbnailUrl": "http://localhost:18099/upload/pictures/product/2024/07/05/thumb_product_20240705_001.jpg", + "albumName": "product", + "pictureSize": 1536000, + "pictureType": "jpg", + "pictureFormat": "JPEG", + "width": 1200, + "height": 800, + "pictureStatus": "0", + "isWatermark": "1", + "uploadUserId": 1, + "uploadUserName": "admin", + "description": "产品展示图片", + "tags": "产品,展示,商品", + "remark": "商品详情页图片" +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| pictureName | String | 是 | 存储图片名 | 不能为空 | +| originalName | String | 是 | 原始图片名 | 不能为空 | +| picturePath | String | 是 | 图片相对路径 | 不能为空 | +| pictureUrl | String | 是 | 图片访问URL | 不能为空 | +| thumbnailUrl | String | 否 | 缩略图URL | 可为空 | +| albumName | String | 是 | 相册名称 | 不能为空 | +| pictureSize | Long | 是 | 图片大小 | 必须大于0 | +| pictureType | String | 是 | 图片类型 | 不能为空 | +| pictureFormat | String | 是 | 图片格式 | 不能为空 | +| width | Integer | 否 | 图片宽度 | 大于0 | +| height | Integer | 否 | 图片高度 | 大于0 | +| pictureStatus | String | 是 | 图片状态 | 0-正常 1-删除 | +| isWatermark | String | 否 | 是否水印 | 0-否 1-是 | +| uploadUserId | Long | 否 | 上传用户ID | 有效的用户ID | +| uploadUserName | String | 否 | 上传用户名 | 可为空 | +| description | String | 否 | 图片描述 | 最长500字符 | +| tags | String | 否 | 图片标签 | 逗号分隔 | +| remark | String | 否 | 备注信息 | 最长200字符 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "新增成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysPicture/add \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-token-value" \ + -d '{ + "pictureName": "product_20240705_001.jpg", + "originalName": "产品展示图.jpg", + "picturePath": "/upload/pictures/product/2024/07/05/product_20240705_001.jpg", + "pictureUrl": "http://localhost:18099/upload/pictures/product/2024/07/05/product_20240705_001.jpg", + "albumName": "product", + "pictureSize": 1536000, + "pictureType": "jpg", + "pictureFormat": "JPEG", + "width": 1200, + "height": 800, + "pictureStatus": "0", + "description": "产品展示图片", + "tags": "产品,展示,商品" + }' +``` + +--- + +### 5. 修改图片信息 + +**接口地址**: `POST /coder/sysPicture/update` + +**接口描述**: 修改系统图片记录信息 + +**是否需要认证**: 是 + +**权限要求**: `system:picture:edit` + +**请求参数**: + +```json +{ + "pictureId": 1, + "pictureName": "product_20240705_001.jpg", + "originalName": "产品展示图.jpg", + "picturePath": "/upload/pictures/product/2024/07/05/product_20240705_001.jpg", + "pictureUrl": "http://localhost:18099/upload/pictures/product/2024/07/05/product_20240705_001.jpg", + "thumbnailUrl": "http://localhost:18099/upload/pictures/product/2024/07/05/thumb_product_20240705_001.jpg", + "albumName": "product", + "pictureSize": 1536000, + "pictureType": "jpg", + "pictureFormat": "JPEG", + "width": 1200, + "height": 800, + "pictureStatus": "0", + "isWatermark": "1", + "uploadUserId": 1, + "uploadUserName": "admin", + "description": "产品展示图片(已更新)", + "tags": "产品,展示,商品,新品", + "remark": "商品详情页图片(已更新)" +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| pictureId | Long | 是 | 图片ID | 必须是有效的图片ID | +| 其他参数 | - | - | 同新增图片记录 | - | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysPicture/update \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-token-value" \ + -d '{ + "pictureId": 1, + "description": "产品展示图片(已更新)", + "tags": "产品,展示,商品,新品" + }' +``` + +--- + +### 6. 删除图片 + +**接口地址**: `POST /coder/sysPicture/deleteById/{id}` + +**接口描述**: 根据ID删除图片记录和物理文件 + +**是否需要认证**: 是 + +**权限要求**: `system:picture:remove` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 图片ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysPicture/deleteById/1 \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 7. 批量删除图片 + +**接口地址**: `POST /coder/sysPicture/batchDelete` + +**接口描述**: 批量删除图片记录和物理文件 + +**是否需要认证**: 是 + +**权限要求**: `system:picture:remove` + +**请求参数**: + +```json +{ + "ids": [1, 2, 3] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | Long[] | 是 | 图片ID数组 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysPicture/batchDelete \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-token-value" \ + -d '{ + "ids": [1, 2, 3] + }' +``` + +--- + +## 图片字段说明 + +### 基础字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| pictureId | Long | 图片ID | 1 | +| pictureName | String | 存储图片名 | banner_20240705_001.jpg | +| originalName | String | 原始图片名 | 主页横幅.jpg | +| picturePath | String | 图片相对路径 | /upload/pictures/banner/2024/07/05/banner_20240705_001.jpg | +| pictureUrl | String | 图片访问URL | http://localhost:18099/upload/pictures/banner/2024/07/05/banner_20240705_001.jpg | +| thumbnailUrl | String | 缩略图URL | http://localhost:18099/upload/pictures/banner/2024/07/05/thumb_banner_20240705_001.jpg | + +### 分类字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| albumName | String | 相册名称 | banner | +| pictureType | String | 图片扩展名 | jpg | +| pictureFormat | String | 图片格式 | JPEG | +| pictureSize | Long | 图片大小(字节) | 2048000 | + +### 尺寸字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| width | Integer | 图片宽度(像素) | 1920 | +| height | Integer | 图片高度(像素) | 1080 | + +### 状态字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| pictureStatus | String | 图片状态 | 0-正常 1-删除 | +| isWatermark | String | 是否有水印 | 0-否 1-是 | +| uploadUserId | Long | 上传用户ID | 1 | +| uploadUserName | String | 上传用户名 | admin | + +### 描述字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| description | String | 图片描述 | 主页横幅图片 | +| tags | String | 图片标签 | 横幅,主页,展示 | +| remark | String | 备注信息 | 网站主页横幅 | + +### 统计字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| viewCount | Integer | 查看次数 | 100 | +| downloadCount | Integer | 下载次数 | 10 | + +--- + +## 数据字典 + +### 图片状态 (pictureStatus) + +| 值 | 说明 | +|----|------| +| 0 | 正常 | +| 1 | 已删除 | + +### 是否水印 (isWatermark) + +| 值 | 说明 | +|----|------| +| 0 | 无水印 | +| 1 | 有水印 | + +### 支持的图片格式 + +| 格式 | 扩展名 | MIME类型 | 说明 | +|------|--------|----------|------| +| JPEG | jpg, jpeg | image/jpeg | 有损压缩,适合照片 | +| PNG | png | image/png | 无损压缩,支持透明 | +| GIF | gif | image/gif | 支持动画 | +| WebP | webp | image/webp | 现代格式,压缩率高 | +| BMP | bmp | image/bmp | 位图格式 | +| TIFF | tiff, tif | image/tiff | 高质量图像 | + +### 相册分类 + +| 相册名称 | 说明 | 用途 | +|----------|------|------| +| banner | 横幅图片 | 网站横幅展示 | +| product | 产品图片 | 商品展示 | +| avatar | 头像图片 | 用户头像 | +| gallery | 图库图片 | 相册展示 | +| article | 文章图片 | 文章配图 | +| icon | 图标图片 | 小图标 | +| background | 背景图片 | 页面背景 | + +--- + +## 图片处理功能 + +### 1. 图片上传处理 + +```java +@Service +public class PictureUploadService { + + /** + * 上传图片 + */ + public PictureUploadResult uploadPicture(MultipartFile file, String album) { + // 1. 图片校验 + validatePicture(file); + + // 2. 生成文件名 + String fileName = generatePictureName(file.getOriginalFilename()); + + // 3. 保存原图 + String originalPath = savePicture(file, album, fileName); + + // 4. 生成缩略图 + String thumbnailPath = generateThumbnail(originalPath, album, fileName); + + // 5. 添加水印 + if (needWatermark(album)) { + addWatermark(originalPath); + } + + // 6. 获取图片信息 + BufferedImage image = ImageIO.read(new File(originalPath)); + int width = image.getWidth(); + int height = image.getHeight(); + + // 7. 生成访问URL + String pictureUrl = generatePictureUrl(album, fileName); + String thumbnailUrl = generateThumbnailUrl(album, fileName); + + // 8. 返回结果 + return PictureUploadResult.builder() + .pictureName(fileName) + .originalName(file.getOriginalFilename()) + .picturePath(originalPath) + .pictureUrl(pictureUrl) + .thumbnailUrl(thumbnailUrl) + .pictureSize(file.getSize()) + .pictureType(getFileExtension(fileName)) + .pictureFormat(getImageFormat(file)) + .width(width) + .height(height) + .build(); + } + + /** + * 生成缩略图 + */ + private String generateThumbnail(String originalPath, String album, String fileName) { + try { + BufferedImage originalImage = ImageIO.read(new File(originalPath)); + + // 计算缩略图尺寸 + int thumbnailWidth = 200; + int thumbnailHeight = 150; + + // 保持宽高比 + double ratio = Math.min((double) thumbnailWidth / originalImage.getWidth(), + (double) thumbnailHeight / originalImage.getHeight()); + int width = (int) (originalImage.getWidth() * ratio); + int height = (int) (originalImage.getHeight() * ratio); + + // 生成缩略图 + BufferedImage thumbnailImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = thumbnailImage.createGraphics(); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2d.drawImage(originalImage, 0, 0, width, height, null); + g2d.dispose(); + + // 保存缩略图 + String thumbnailPath = getThumbnailPath(album, fileName); + ImageIO.write(thumbnailImage, "jpg", new File(thumbnailPath)); + + return thumbnailPath; + } catch (IOException e) { + throw new BusinessException("生成缩略图失败", e); + } + } + + /** + * 添加水印 + */ + private void addWatermark(String imagePath) { + try { + BufferedImage originalImage = ImageIO.read(new File(imagePath)); + Graphics2D g2d = originalImage.createGraphics(); + + // 设置水印属性 + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); + g2d.setColor(Color.LIGHT_GRAY); + g2d.setFont(new Font("Arial", Font.BOLD, 20)); + + // 计算水印位置 + FontMetrics fm = g2d.getFontMetrics(); + String watermarkText = "© Your Company"; + int x = originalImage.getWidth() - fm.stringWidth(watermarkText) - 10; + int y = originalImage.getHeight() - fm.getHeight() + fm.getAscent() - 10; + + // 绘制水印 + g2d.drawString(watermarkText, x, y); + g2d.dispose(); + + // 保存带水印的图片 + ImageIO.write(originalImage, "jpg", new File(imagePath)); + } catch (IOException e) { + throw new BusinessException("添加水印失败", e); + } + } +} +``` + +### 2. 图片压缩 + +```java +/** + * 图片压缩 + */ +public void compressPicture(String inputPath, String outputPath, float quality) { + try { + BufferedImage image = ImageIO.read(new File(inputPath)); + + // 创建输出流 + FileOutputStream fos = new FileOutputStream(outputPath); + + // 获取JPEG写入器 + Iterator writers = ImageIO.getImageWritersByFormatName("jpg"); + ImageWriter writer = writers.next(); + + // 设置输出 + ImageOutputStream ios = ImageIO.createImageOutputStream(fos); + writer.setOutput(ios); + + // 设置压缩参数 + ImageWriteParam param = writer.getDefaultWriteParam(); + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionQuality(quality); // 压缩质量 0.0-1.0 + + // 写入图片 + writer.write(null, new IIOImage(image, null, null), param); + + // 清理资源 + ios.close(); + fos.close(); + writer.dispose(); + } catch (IOException e) { + throw new BusinessException("图片压缩失败", e); + } +} +``` + +### 3. 图片格式转换 + +```java +/** + * 图片格式转换 + */ +public void convertPictureFormat(String inputPath, String outputPath, String format) { + try { + BufferedImage image = ImageIO.read(new File(inputPath)); + + // 如果转换为JPEG,需要处理透明背景 + if ("jpg".equalsIgnoreCase(format) || "jpeg".equalsIgnoreCase(format)) { + BufferedImage jpegImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = jpegImage.createGraphics(); + g2d.setColor(Color.WHITE); + g2d.fillRect(0, 0, image.getWidth(), image.getHeight()); + g2d.drawImage(image, 0, 0, null); + g2d.dispose(); + image = jpegImage; + } + + // 保存转换后的图片 + ImageIO.write(image, format, new File(outputPath)); + } catch (IOException e) { + throw new BusinessException("图片格式转换失败", e); + } +} +``` + +--- + +## 错误码说明 + +| 错误码 | 错误信息 | 说明 | +|--------|----------|------| +| 400 | 图片不能为空 | 上传图片为空 | +| 400 | 图片大小不能超过{size}MB | 图片大小超过限制 | +| 400 | 不支持的图片格式 | 图片格式不支持 | +| 400 | 图片尺寸超过限制 | 图片宽高超过限制 | +| 400 | 图片名称不能为空 | 图片名称为空 | +| 400 | 相册名称不能为空 | 相册名称为空 | +| 400 | 图片不存在 | 图片ID不存在 | +| 400 | 图片已被删除 | 图片状态为已删除 | +| 401 | 当前会话未登录 | 未登录或Token无效 | +| 403 | 权限不足 | 没有相应的操作权限 | +| 500 | 图片上传失败 | 图片保存到磁盘失败 | +| 500 | 缩略图生成失败 | 缩略图生成过程失败 | +| 500 | 水印添加失败 | 水印添加过程失败 | +| 500 | 图片压缩失败 | 图片压缩过程失败 | +| 500 | 图片删除失败 | 物理文件删除失败 | + +--- + +## 安全特性 + +### 1. 图片验证 + +- **格式验证**: 验证图片格式和MIME类型 +- **尺寸验证**: 验证图片宽高限制 +- **内容验证**: 验证图片内容安全性 +- **病毒扫描**: 对上传图片进行病毒扫描 + +### 2. 访问控制 + +- **权限验证**: 验证用户访问权限 +- **防盗链**: 防止图片被盗链 +- **水印保护**: 添加水印保护版权 +- **下载限制**: 限制图片下载次数 + +### 3. 存储安全 + +- **路径安全**: 防止路径遍历攻击 +- **文件重命名**: 重命名避免冲突 +- **备份策略**: 重要图片定期备份 +- **权限设置**: 设置合适的文件权限 + +--- + +## 性能优化 + +### 1. 图片处理优化 + +- **异步处理**: 图片压缩和水印添加异步处理 +- **批量处理**: 支持批量图片处理 +- **缓存策略**: 缓存处理后的图片 +- **CDN加速**: 使用CDN加速图片访问 + +### 2. 存储优化 + +- **分目录存储**: 按相册和日期分目录存储 +- **格式优化**: 自动选择最优图片格式 +- **压缩存储**: 自动压缩降低存储空间 +- **重复检测**: 检测重复图片避免冗余 + +### 3. 访问优化 + +- **懒加载**: 图片懒加载技术 +- **响应式图片**: 根据设备提供不同尺寸 +- **WebP支持**: 支持WebP格式提高性能 +- **预加载**: 关键图片预加载 + +--- + +## 使用建议 + +### 1. 图片命名规范 + +- **唯一性**: 确保图片名唯一 +- **时间戳**: 包含时间戳信息 +- **分类标识**: 包含相册分类信息 +- **版本控制**: 支持图片版本管理 + +### 2. 相册管理 + +- **分类管理**: 按用途分类管理图片 +- **权限控制**: 不同相册设置不同权限 +- **容量限制**: 设置相册容量限制 +- **定期清理**: 定期清理无用图片 + +### 3. 图片优化 + +- **尺寸适配**: 根据用途选择合适尺寸 +- **格式选择**: 根据内容选择最优格式 +- **质量平衡**: 平衡图片质量和文件大小 +- **缓存策略**: 设置合适的缓存时间 + +--- + +## 注意事项 + +1. **图片安全**: 严格验证上传图片格式和内容 +2. **版权保护**: 添加水印保护图片版权 +3. **存储管理**: 定期清理无效和重复图片 +4. **性能考虑**: 大图片上传时注意性能影响 +5. **备份策略**: 重要图片需要备份 +6. **访问控制**: 合理设置图片访问权限 +7. **格式支持**: 根据需要支持不同图片格式 +8. **缩略图**: 为提高加载速度生成缩略图 +9. **压缩处理**: 适当压缩减少存储空间 +10. **监控告警**: 监控存储空间和访问异常 \ No newline at end of file diff --git a/api/system/文件管理API.md b/api/system/文件管理API.md new file mode 100644 index 0000000..cde6262 --- /dev/null +++ b/api/system/文件管理API.md @@ -0,0 +1,867 @@ +# 文件管理API + +## 概述 + +文件管理模块提供文件上传、下载、存储管理等功能。支持多种文件类型,提供文件记录管理和批量操作功能。分为通用文件上传接口和系统文件记录管理两部分。 + +## 权限说明 + +文件管理接口需要相应的权限才能访问: + +| 操作 | 权限码 | 说明 | +|------|--------|------| +| 查询文件列表 | `system:file:list` | 查看文件列表权限 | +| 上传文件 | `system:file:upload` | 上传文件权限 | +| 下载文件 | `system:file:download` | 下载文件权限 | +| 删除文件 | `system:file:remove` | 删除文件权限 | + +## 文件上传接口 + +### 1. 上传文件(需认证) + +**接口地址**: `POST /coder/file/uploadFile/{fileSize}/{folderName}/{fileParam}` + +**接口描述**: 上传文件到服务器(需要登录认证) + +**是否需要认证**: 是 + +**权限要求**: `system:file:upload` + +**请求头**: +``` +Authorization: Bearer your-token-value +Content-Type: multipart/form-data +``` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| fileSize | Integer | 是 | 文件大小限制(MB) | 10 | +| folderName | String | 是 | 存储文件夹名称 | avatar | +| fileParam | String | 是 | 文件参数名 | file | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| file | File | 是 | 上传的文件 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "fileName": "avatar.jpg", + "originalName": "my-avatar.jpg", + "filePath": "/upload/avatar/2024/07/05/avatar_20240705_001.jpg", + "fileUrl": "http://localhost:18099/upload/avatar/2024/07/05/avatar_20240705_001.jpg", + "fileSize": 1024000, + "fileType": "jpg", + "uploadTime": "2024-07-05 10:00:00" + }, + "traceId": "trace-123456" +} +``` + +**响应参数说明**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| fileName | String | 存储文件名 | +| originalName | String | 原始文件名 | +| filePath | String | 文件相对路径 | +| fileUrl | String | 文件访问URL | +| fileSize | Long | 文件大小(字节) | +| fileType | String | 文件类型 | +| uploadTime | String | 上传时间 | + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/file/uploadFile/10/avatar/file \ + -H "Authorization: Bearer your-token-value" \ + -F "file=@avatar.jpg" +``` + +**前端使用示例**: + +```javascript +// 使用FormData上传文件 +function uploadFile(file, folder, maxSize) { + const formData = new FormData(); + formData.append('file', file); + + fetch(`/coder/file/uploadFile/${maxSize}/${folder}/file`, { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + localStorage.getItem('token') + }, + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.status === 200) { + console.log('上传成功:', data.data.fileUrl); + } else { + console.error('上传失败:', data.msg); + } + }); +} +``` + +--- + +### 2. 匿名上传文件 + +**接口地址**: `POST /coder/file/uploadAnyFile/{fileSize}/{folderName}/{fileParam}` + +**接口描述**: 匿名上传文件到服务器(无需登录认证) + +**是否需要认证**: 否 + +**权限要求**: 无 + +**路径参数**: 同上传文件接口 + +**请求参数**: 同上传文件接口 + +**响应示例**: 同上传文件接口 + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/file/uploadAnyFile/5/temp/file \ + -F "file=@document.pdf" +``` + +**使用场景**: +- 用户注册时上传头像 +- 访客上传临时文件 +- 公开资源上传 + +--- + +## 系统文件记录管理接口 + +### 1. 分页查询文件列表 + +**接口地址**: `GET /coder/sysFile/listPage` + +**接口描述**: 分页查询系统文件记录列表 + +**是否需要认证**: 是 + +**权限要求**: `system:file:list` + +**请求头**: +``` +Authorization: Bearer your-token-value +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| pageNo | Integer | 否 | 页码 | 1 | +| pageSize | Integer | 否 | 每页大小 | 10 | +| fileName | String | 否 | 文件名称 | avatar.jpg | +| fileType | String | 否 | 文件类型 | jpg | +| folderName | String | 否 | 文件夹名称 | avatar | +| beginTime | String | 否 | 开始时间 | 2024-01-01 | +| endTime | String | 否 | 结束时间 | 2024-12-31 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "records": [ + { + "fileId": 1, + "fileName": "avatar_20240705_001.jpg", + "originalName": "my-avatar.jpg", + "filePath": "/upload/avatar/2024/07/05/avatar_20240705_001.jpg", + "fileUrl": "http://localhost:18099/upload/avatar/2024/07/05/avatar_20240705_001.jpg", + "folderName": "avatar", + "fileSize": 1024000, + "fileType": "jpg", + "contentType": "image/jpeg", + "fileStatus": "0", + "uploadUserId": 1, + "uploadUserName": "admin", + "remark": "用户头像", + "createBy": "admin", + "createTime": "2024-07-05 10:00:00", + "updateBy": "admin", + "updateTime": "2024-07-05 10:00:00" + } + ], + "total": 1, + "size": 10, + "current": 1, + "pages": 1 + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysFile/listPage?pageNo=1&pageSize=10&fileName=avatar" \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 2. 查询所有文件 + +**接口地址**: `GET /coder/sysFile/list` + +**接口描述**: 查询所有系统文件记录(不分页) + +**是否需要认证**: 是 + +**权限要求**: `system:file:list` + +**请求参数**: 同分页查询(除pageNo、pageSize外) + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [ + { + "fileId": 1, + "fileName": "avatar_20240705_001.jpg", + "originalName": "my-avatar.jpg", + "filePath": "/upload/avatar/2024/07/05/avatar_20240705_001.jpg", + "fileUrl": "http://localhost:18099/upload/avatar/2024/07/05/avatar_20240705_001.jpg", + "folderName": "avatar", + "fileSize": 1024000, + "fileType": "jpg", + "contentType": "image/jpeg", + "fileStatus": "0", + "uploadUserId": 1, + "uploadUserName": "admin", + "remark": "用户头像", + "createBy": "admin", + "createTime": "2024-07-05 10:00:00", + "updateBy": "admin", + "updateTime": "2024-07-05 10:00:00" + } + ], + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysFile/list?fileType=jpg" \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 3. 根据ID查询文件 + +**接口地址**: `GET /coder/sysFile/getById/{id}` + +**接口描述**: 根据文件ID查询文件详细信息 + +**是否需要认证**: 是 + +**权限要求**: `system:file:list` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 文件ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "fileId": 1, + "fileName": "avatar_20240705_001.jpg", + "originalName": "my-avatar.jpg", + "filePath": "/upload/avatar/2024/07/05/avatar_20240705_001.jpg", + "fileUrl": "http://localhost:18099/upload/avatar/2024/07/05/avatar_20240705_001.jpg", + "folderName": "avatar", + "fileSize": 1024000, + "fileType": "jpg", + "contentType": "image/jpeg", + "fileStatus": "0", + "uploadUserId": 1, + "uploadUserName": "admin", + "remark": "用户头像", + "createBy": "admin", + "createTime": "2024-07-05 10:00:00", + "updateBy": "admin", + "updateTime": "2024-07-05 10:00:00" + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysFile/getById/1 \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 4. 新增文件记录 + +**接口地址**: `POST /coder/sysFile/add` + +**接口描述**: 新增系统文件记录 + +**是否需要认证**: 是 + +**权限要求**: `system:file:upload` + +**请求参数**: + +```json +{ + "fileName": "document_20240705_001.pdf", + "originalName": "重要文档.pdf", + "filePath": "/upload/documents/2024/07/05/document_20240705_001.pdf", + "fileUrl": "http://localhost:18099/upload/documents/2024/07/05/document_20240705_001.pdf", + "folderName": "documents", + "fileSize": 2048000, + "fileType": "pdf", + "contentType": "application/pdf", + "fileStatus": "0", + "uploadUserId": 1, + "uploadUserName": "admin", + "remark": "重要文档" +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| fileName | String | 是 | 存储文件名 | 不能为空 | +| originalName | String | 是 | 原始文件名 | 不能为空 | +| filePath | String | 是 | 文件相对路径 | 不能为空 | +| fileUrl | String | 是 | 文件访问URL | 不能为空 | +| folderName | String | 是 | 文件夹名称 | 不能为空 | +| fileSize | Long | 是 | 文件大小 | 必须大于0 | +| fileType | String | 是 | 文件类型 | 不能为空 | +| contentType | String | 是 | MIME类型 | 不能为空 | +| fileStatus | String | 是 | 文件状态 | 0-正常 1-删除 | +| uploadUserId | Long | 否 | 上传用户ID | 有效的用户ID | +| uploadUserName | String | 否 | 上传用户名 | 可为空 | +| remark | String | 否 | 备注信息 | 最长200字符 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "新增成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysFile/add \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-token-value" \ + -d '{ + "fileName": "document_20240705_001.pdf", + "originalName": "重要文档.pdf", + "filePath": "/upload/documents/2024/07/05/document_20240705_001.pdf", + "fileUrl": "http://localhost:18099/upload/documents/2024/07/05/document_20240705_001.pdf", + "folderName": "documents", + "fileSize": 2048000, + "fileType": "pdf", + "contentType": "application/pdf", + "fileStatus": "0", + "uploadUserId": 1, + "uploadUserName": "admin", + "remark": "重要文档" + }' +``` + +--- + +### 5. 修改文件信息 + +**接口地址**: `POST /coder/sysFile/update` + +**接口描述**: 修改系统文件记录信息 + +**是否需要认证**: 是 + +**权限要求**: `system:file:edit` + +**请求参数**: + +```json +{ + "fileId": 1, + "fileName": "document_20240705_001.pdf", + "originalName": "重要文档.pdf", + "filePath": "/upload/documents/2024/07/05/document_20240705_001.pdf", + "fileUrl": "http://localhost:18099/upload/documents/2024/07/05/document_20240705_001.pdf", + "folderName": "documents", + "fileSize": 2048000, + "fileType": "pdf", + "contentType": "application/pdf", + "fileStatus": "0", + "uploadUserId": 1, + "uploadUserName": "admin", + "remark": "重要文档(已更新)" +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| fileId | Long | 是 | 文件ID | 必须是有效的文件ID | +| 其他参数 | - | - | 同新增文件记录 | - | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysFile/update \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-token-value" \ + -d '{ + "fileId": 1, + "fileName": "document_20240705_001.pdf", + "originalName": "重要文档.pdf", + "remark": "重要文档(已更新)" + }' +``` + +--- + +### 6. 删除文件 + +**接口地址**: `POST /coder/sysFile/deleteById/{id}` + +**接口描述**: 根据ID删除文件记录和物理文件 + +**是否需要认证**: 是 + +**权限要求**: `system:file:remove` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 文件ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysFile/deleteById/1 \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 7. 批量删除文件 + +**接口地址**: `POST /coder/sysFile/batchDelete` + +**接口描述**: 批量删除文件记录和物理文件 + +**是否需要认证**: 是 + +**权限要求**: `system:file:remove` + +**请求参数**: + +```json +{ + "ids": [1, 2, 3] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | Long[] | 是 | 文件ID数组 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysFile/batchDelete \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-token-value" \ + -d '{ + "ids": [1, 2, 3] + }' +``` + +--- + +## 文件字段说明 + +### 基础字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| fileId | Long | 文件ID | 1 | +| fileName | String | 存储文件名 | avatar_20240705_001.jpg | +| originalName | String | 原始文件名 | my-avatar.jpg | +| filePath | String | 文件相对路径 | /upload/avatar/2024/07/05/avatar_20240705_001.jpg | +| fileUrl | String | 文件访问URL | http://localhost:18099/upload/avatar/2024/07/05/avatar_20240705_001.jpg | + +### 分类字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| folderName | String | 文件夹名称 | avatar | +| fileType | String | 文件扩展名 | jpg | +| contentType | String | MIME类型 | image/jpeg | +| fileSize | Long | 文件大小(字节) | 1024000 | + +### 状态字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| fileStatus | String | 文件状态 | 0-正常 1-删除 | +| uploadUserId | Long | 上传用户ID | 1 | +| uploadUserName | String | 上传用户名 | admin | +| remark | String | 备注信息 | 用户头像 | + +--- + +## 数据字典 + +### 文件状态 (fileStatus) + +| 值 | 说明 | +|----|------| +| 0 | 正常 | +| 1 | 已删除 | + +### 常见文件类型 + +| 文件类型 | 扩展名 | MIME类型 | 说明 | +|----------|--------|----------|------| +| 图片 | jpg, jpeg, png, gif, bmp | image/* | 图片文件 | +| 文档 | pdf, doc, docx, xls, xlsx, ppt, pptx | application/* | 办公文档 | +| 音频 | mp3, wav, flac, aac | audio/* | 音频文件 | +| 视频 | mp4, avi, mov, wmv | video/* | 视频文件 | +| 压缩包 | zip, rar, 7z, tar, gz | application/* | 压缩文件 | +| 文本 | txt, md, csv | text/* | 文本文件 | + +--- + +## 文件配置 + +### 1. 文件存储配置 + +```yaml +# application-dev.yml +coder: + # 文件存储路径 + filePath: /data/upload/ + + # 文件访问域名 + domain: http://localhost:18099 + + # 文件大小限制(MB) + maxFileSize: 100 + + # 允许的文件类型 + allowedTypes: + - jpg + - jpeg + - png + - gif + - pdf + - doc + - docx + - xls + - xlsx + + # 禁止的文件类型 + forbiddenTypes: + - exe + - bat + - sh + - jsp + - php +``` + +### 2. 文件夹分类 + +```java +// 文件夹类型枚举 +public enum FolderType { + AVATAR("avatar", "用户头像"), + DOCUMENT("document", "文档文件"), + IMAGE("image", "图片文件"), + TEMP("temp", "临时文件"), + EXPORT("export", "导出文件"); + + private final String code; + private final String name; +} +``` + +### 3. 文件上传工具类 + +```java +@Component +public class FileUploadUtil { + + /** + * 上传文件 + */ + public FileUploadResult uploadFile(MultipartFile file, String folder, int maxSize) { + // 1. 文件校验 + validateFile(file, maxSize); + + // 2. 生成文件名 + String fileName = generateFileName(file.getOriginalFilename()); + + // 3. 创建目录 + String datePath = DateUtil.format(new Date(), "yyyy/MM/dd"); + String dirPath = filePath + folder + "/" + datePath + "/"; + FileUtil.createDirs(dirPath); + + // 4. 保存文件 + String filePath = dirPath + fileName; + file.transferTo(new File(filePath)); + + // 5. 生成访问URL + String fileUrl = domain + "/upload/" + folder + "/" + datePath + "/" + fileName; + + // 6. 返回结果 + return FileUploadResult.builder() + .fileName(fileName) + .originalName(file.getOriginalFilename()) + .filePath(filePath) + .fileUrl(fileUrl) + .fileSize(file.getSize()) + .fileType(FileUtil.getExtension(fileName)) + .uploadTime(new Date()) + .build(); + } + + /** + * 文件校验 + */ + private void validateFile(MultipartFile file, int maxSize) { + if (file.isEmpty()) { + throw new BusinessException("文件不能为空"); + } + + if (file.getSize() > maxSize * 1024 * 1024) { + throw new BusinessException("文件大小不能超过" + maxSize + "MB"); + } + + String extension = FileUtil.getExtension(file.getOriginalFilename()); + if (!allowedTypes.contains(extension.toLowerCase())) { + throw new BusinessException("不支持的文件类型:" + extension); + } + + if (forbiddenTypes.contains(extension.toLowerCase())) { + throw new BusinessException("禁止上传的文件类型:" + extension); + } + } + + /** + * 生成文件名 + */ + private String generateFileName(String originalName) { + String extension = FileUtil.getExtension(originalName); + String baseName = FileUtil.getBaseName(originalName); + String timestamp = DateUtil.format(new Date(), "yyyyMMdd_HHmmss"); + String random = RandomUtil.randomString(3); + return baseName + "_" + timestamp + "_" + random + "." + extension; + } +} +``` + +--- + +## 错误码说明 + +| 错误码 | 错误信息 | 说明 | +|--------|----------|------| +| 400 | 文件不能为空 | 上传文件为空 | +| 400 | 文件大小不能超过{size}MB | 文件大小超过限制 | +| 400 | 不支持的文件类型 | 文件类型不在允许列表中 | +| 400 | 禁止上传的文件类型 | 文件类型在禁止列表中 | +| 400 | 文件名不能为空 | 文件名为空 | +| 400 | 文件路径不能为空 | 文件路径为空 | +| 400 | 文件不存在 | 文件ID不存在 | +| 400 | 文件已被删除 | 文件状态为已删除 | +| 401 | 当前会话未登录 | 未登录或Token无效 | +| 403 | 权限不足 | 没有相应的操作权限 | +| 500 | 文件上传失败 | 文件保存到磁盘失败 | +| 500 | 文件删除失败 | 物理文件删除失败 | +| 500 | 磁盘空间不足 | 服务器磁盘空间不足 | + +--- + +## 安全特性 + +### 1. 文件类型验证 + +- **扩展名校验**: 验证文件扩展名 +- **MIME类型校验**: 验证文件MIME类型 +- **文件内容校验**: 验证文件真实类型 +- **黑名单过滤**: 禁止上传危险文件类型 + +### 2. 文件大小限制 + +- **单文件大小限制**: 限制单个文件大小 +- **总文件大小限制**: 限制用户总文件大小 +- **磁盘空间检查**: 检查服务器磁盘空间 + +### 3. 访问控制 + +- **权限验证**: 验证用户上传和访问权限 +- **路径防护**: 防止路径遍历攻击 +- **防盗链**: 防止文件被盗链 + +### 4. 病毒扫描 + +- **文件扫描**: 上传文件病毒扫描 +- **隔离机制**: 可疑文件隔离处理 +- **定期扫描**: 定期扫描存储文件 + +--- + +## 性能优化 + +### 1. 文件存储 + +- **分目录存储**: 按日期和类型分目录存储 +- **CDN加速**: 使用CDN加速文件访问 +- **压缩存储**: 自动压缩图片文件 +- **缓存策略**: 设置合适的缓存策略 + +### 2. 上传优化 + +- **分片上传**: 大文件分片上传 +- **断点续传**: 支持断点续传功能 +- **并发上传**: 支持多文件并发上传 +- **进度显示**: 实时显示上传进度 + +### 3. 存储优化 + +- **重复文件检测**: 检测并避免重复文件 +- **文件压缩**: 自动压缩文件 +- **定期清理**: 清理临时文件和无效文件 +- **存储监控**: 监控存储空间使用情况 + +--- + +## 使用建议 + +### 1. 文件命名规范 + +- **唯一性**: 确保文件名唯一 +- **时间戳**: 包含时间戳信息 +- **随机性**: 添加随机字符 +- **可读性**: 保持一定的可读性 + +### 2. 文件分类管理 + +- **按类型分类**: 不同类型文件存储在不同目录 +- **按用户分类**: 不同用户文件分开存储 +- **按时间分类**: 按年月日创建目录结构 +- **按业务分类**: 按业务模块分类存储 + +### 3. 安全防护 + +- **输入验证**: 严格验证上传文件 +- **权限控制**: 细粒度的权限控制 +- **日志记录**: 记录所有文件操作 +- **备份策略**: 重要文件定期备份 + +### 4. 监控告警 + +- **存储监控**: 监控存储空间使用 +- **性能监控**: 监控上传下载性能 +- **安全监控**: 监控异常文件操作 +- **容量告警**: 存储空间不足告警 + +--- + +## 注意事项 + +1. **文件安全**: 严格验证上传文件类型和内容 +2. **存储路径**: 确保文件存储路径安全 +3. **权限控制**: 合理设置文件访问权限 +4. **磁盘空间**: 定期清理无效文件,避免磁盘满 +5. **备份策略**: 重要文件需要备份 +6. **访问控制**: 防止文件被恶意访问 +7. **性能考虑**: 大文件上传时注意性能影响 +8. **并发控制**: 处理并发上传时的文件冲突 +9. **错误处理**: 完善的错误处理和回滚机制 +10. **日志审计**: 记录所有文件操作日志 \ No newline at end of file diff --git a/api/system/登录日志API.md b/api/system/登录日志API.md new file mode 100644 index 0000000..8f5cc29 --- /dev/null +++ b/api/system/登录日志API.md @@ -0,0 +1,1017 @@ +# 登录日志API + +## 概述 + +登录日志模块用于记录和管理系统用户的登录行为,包括登录成功、登录失败、退出登录等操作的详细记录。提供日志查询、统计分析和安全监控功能,是系统安全审计的重要组成部分。 + +## 权限说明 + +登录日志接口需要相应的权限才能访问: + +| 操作 | 权限码 | 说明 | +|------|--------|------| +| 查询登录日志 | `system:loginlog:list` | 查看登录日志权限 | +| 删除登录日志 | `system:loginlog:remove` | 删除登录日志权限 | +| 导出登录日志 | `system:loginlog:export` | 导出登录日志权限 | + +## 接口列表 + +### 1. 分页查询登录日志 + +**接口地址**: `GET /coder/sysLoginLog/listPage` + +**接口描述**: 分页查询系统登录日志列表 + +**是否需要认证**: 是 + +**权限要求**: `system:loginlog:list` + +**请求头**: +``` +Authorization: Bearer your-token-value +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| pageNo | Integer | 否 | 页码 | 1 | +| pageSize | Integer | 否 | 每页大小 | 10 | +| loginName | String | 否 | 登录账号 | admin | +| loginStatus | String | 否 | 登录状态 | 0 | +| clientType | String | 否 | 客户端类型 | WEB | +| loginIp | String | 否 | 登录IP | 127.0.0.1 | +| loginAddress | String | 否 | 登录地址 | 本地登录 | +| browser | String | 否 | 浏览器类型 | Chrome | +| os | String | 否 | 操作系统 | Windows 10 | +| beginTime | String | 否 | 开始时间 | 2024-01-01 | +| endTime | String | 否 | 结束时间 | 2024-12-31 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "records": [ + { + "infoId": 1, + "loginName": "admin", + "userName": "管理员", + "userId": 1, + "loginStatus": "0", + "clientType": "WEB", + "deviceName": "Windows PC", + "loginIp": "127.0.0.1", + "loginAddress": "本地登录", + "browser": "Chrome 120.0.0.0", + "os": "Windows 10", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "loginTime": "2024-07-05 10:00:00", + "logoutTime": "2024-07-05 11:00:00", + "sessionDuration": 3600, + "failureReason": "", + "remark": "登录成功", + "createBy": "system", + "createTime": "2024-07-05 10:00:00", + "updateBy": "system", + "updateTime": "2024-07-05 11:00:00" + } + ], + "total": 1, + "size": 10, + "current": 1, + "pages": 1 + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysLoginLog/listPage?pageNo=1&pageSize=10&loginName=admin&loginStatus=0" \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 2. 查询所有登录日志 + +**接口地址**: `GET /coder/sysLoginLog/list` + +**接口描述**: 查询所有系统登录日志(不分页) + +**是否需要认证**: 是 + +**权限要求**: `system:loginlog:list` + +**请求参数**: 同分页查询(除pageNo、pageSize外) + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [ + { + "infoId": 1, + "loginName": "admin", + "userName": "管理员", + "userId": 1, + "loginStatus": "0", + "clientType": "WEB", + "deviceName": "Windows PC", + "loginIp": "127.0.0.1", + "loginAddress": "本地登录", + "browser": "Chrome 120.0.0.0", + "os": "Windows 10", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "loginTime": "2024-07-05 10:00:00", + "logoutTime": "2024-07-05 11:00:00", + "sessionDuration": 3600, + "failureReason": "", + "remark": "登录成功", + "createBy": "system", + "createTime": "2024-07-05 10:00:00", + "updateBy": "system", + "updateTime": "2024-07-05 11:00:00" + } + ], + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysLoginLog/list?loginStatus=1&beginTime=2024-07-01" \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 3. 根据ID查询登录日志 + +**接口地址**: `GET /coder/sysLoginLog/getById/{id}` + +**接口描述**: 根据日志ID查询登录日志详细信息 + +**是否需要认证**: 是 + +**权限要求**: `system:loginlog:list` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 日志ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "infoId": 1, + "loginName": "admin", + "userName": "管理员", + "userId": 1, + "loginStatus": "0", + "clientType": "WEB", + "deviceName": "Windows PC", + "loginIp": "127.0.0.1", + "loginAddress": "本地登录", + "browser": "Chrome 120.0.0.0", + "os": "Windows 10", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "loginTime": "2024-07-05 10:00:00", + "logoutTime": "2024-07-05 11:00:00", + "sessionDuration": 3600, + "failureReason": "", + "remark": "登录成功", + "createBy": "system", + "createTime": "2024-07-05 10:00:00", + "updateBy": "system", + "updateTime": "2024-07-05 11:00:00" + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysLoginLog/getById/1 \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 4. 新增登录日志 + +**接口地址**: `POST /coder/sysLoginLog/add` + +**接口描述**: 新增系统登录日志记录 + +**是否需要认证**: 是 + +**权限要求**: `system:loginlog:add` + +**请求参数**: + +```json +{ + "loginName": "admin", + "userName": "管理员", + "userId": 1, + "loginStatus": "0", + "clientType": "WEB", + "deviceName": "Windows PC", + "loginIp": "127.0.0.1", + "loginAddress": "本地登录", + "browser": "Chrome 120.0.0.0", + "os": "Windows 10", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "loginTime": "2024-07-05 10:00:00", + "failureReason": "", + "remark": "登录成功" +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| loginName | String | 是 | 登录账号 | 不能为空 | +| userName | String | 否 | 用户姓名 | 可为空 | +| userId | Long | 否 | 用户ID | 有效的用户ID | +| loginStatus | String | 是 | 登录状态 | 0-成功 1-失败 | +| clientType | String | 是 | 客户端类型 | WEB, MOBILE, API | +| deviceName | String | 否 | 设备名称 | 可为空 | +| loginIp | String | 是 | 登录IP | 有效的IP地址 | +| loginAddress | String | 否 | 登录地址 | 可为空 | +| browser | String | 否 | 浏览器信息 | 可为空 | +| os | String | 否 | 操作系统 | 可为空 | +| userAgent | String | 否 | 用户代理 | 可为空 | +| loginTime | String | 是 | 登录时间 | 时间格式 | +| failureReason | String | 否 | 失败原因 | 登录失败时必填 | +| remark | String | 否 | 备注信息 | 最长500字符 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "新增成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginLog/add \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-token-value" \ + -d '{ + "loginName": "admin", + "userName": "管理员", + "userId": 1, + "loginStatus": "0", + "clientType": "WEB", + "loginIp": "127.0.0.1", + "loginTime": "2024-07-05 10:00:00", + "remark": "登录成功" + }' +``` + +--- + +### 5. 修改登录日志 + +**接口地址**: `POST /coder/sysLoginLog/update` + +**接口描述**: 修改系统登录日志信息(通常用于更新退出时间) + +**是否需要认证**: 是 + +**权限要求**: `system:loginlog:edit` + +**请求参数**: + +```json +{ + "infoId": 1, + "loginName": "admin", + "userName": "管理员", + "userId": 1, + "loginStatus": "0", + "clientType": "WEB", + "deviceName": "Windows PC", + "loginIp": "127.0.0.1", + "loginAddress": "本地登录", + "browser": "Chrome 120.0.0.0", + "os": "Windows 10", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "loginTime": "2024-07-05 10:00:00", + "logoutTime": "2024-07-05 11:00:00", + "sessionDuration": 3600, + "failureReason": "", + "remark": "正常退出" +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| infoId | Long | 是 | 日志ID | 必须是有效的日志ID | +| logoutTime | String | 否 | 退出时间 | 时间格式 | +| sessionDuration | Integer | 否 | 会话时长(秒) | 大于等于0 | +| 其他参数 | - | - | 同新增日志 | - | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginLog/update \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-token-value" \ + -d '{ + "infoId": 1, + "logoutTime": "2024-07-05 11:00:00", + "sessionDuration": 3600, + "remark": "正常退出" + }' +``` + +--- + +### 6. 删除登录日志 + +**接口地址**: `POST /coder/sysLoginLog/deleteById/{id}` + +**接口描述**: 根据ID删除登录日志 + +**是否需要认证**: 是 + +**权限要求**: `system:loginlog:remove` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 日志ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginLog/deleteById/1 \ + -H "Authorization: Bearer your-token-value" +``` + +--- + +### 7. 批量删除登录日志 + +**接口地址**: `POST /coder/sysLoginLog/batchDelete` + +**接口描述**: 批量删除登录日志 + +**是否需要认证**: 是 + +**权限要求**: `system:loginlog:remove` + +**请求参数**: + +```json +{ + "ids": [1, 2, 3] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | Long[] | 是 | 日志ID数组 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginLog/batchDelete \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-token-value" \ + -d '{ + "ids": [1, 2, 3] + }' +``` + +--- + +## 登录日志字段说明 + +### 基础字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| infoId | Long | 日志ID | 1 | +| loginName | String | 登录账号 | admin | +| userName | String | 用户姓名 | 管理员 | +| userId | Long | 用户ID | 1 | + +### 状态字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| loginStatus | String | 登录状态 | 0-成功 1-失败 | +| clientType | String | 客户端类型 | WEB, MOBILE, API | +| deviceName | String | 设备名称 | Windows PC | + +### 网络字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| loginIp | String | 登录IP地址 | 127.0.0.1 | +| loginAddress | String | 登录地理位置 | 本地登录 | +| userAgent | String | 用户代理字符串 | Mozilla/5.0... | + +### 环境字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| browser | String | 浏览器信息 | Chrome 120.0.0.0 | +| os | String | 操作系统 | Windows 10 | + +### 时间字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| loginTime | String | 登录时间 | 2024-07-05 10:00:00 | +| logoutTime | String | 退出时间 | 2024-07-05 11:00:00 | +| sessionDuration | Integer | 会话时长(秒) | 3600 | + +### 其他字段 + +| 字段名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| failureReason | String | 失败原因 | 密码错误 | +| remark | String | 备注信息 | 登录成功 | + +--- + +## 数据字典 + +### 登录状态 (loginStatus) + +| 值 | 说明 | +|----|------| +| 0 | 登录成功 | +| 1 | 登录失败 | + +### 客户端类型 (clientType) + +| 值 | 说明 | +|----|------| +| WEB | 网页端 | +| MOBILE | 移动端 | +| API | 接口调用 | +| DESKTOP | 桌面应用 | +| WECHAT | 微信小程序 | + +### 常见失败原因 + +| 失败原因 | 说明 | +|----------|------| +| 用户不存在 | 登录账号不存在 | +| 密码错误 | 登录密码不正确 | +| 验证码错误 | 验证码输入错误 | +| 账号被禁用 | 用户账号被禁用 | +| 账号被锁定 | 用户账号被锁定 | +| IP被限制 | 登录IP被限制 | +| 设备被限制 | 登录设备被限制 | +| 会话过期 | 用户会话已过期 | + +--- + +## 日志记录机制 + +### 1. 自动记录 + +```java +@Component +@Slf4j +public class LoginLogService { + + /** + * 记录登录成功日志 + */ + public void recordLoginSuccess(String loginName, HttpServletRequest request) { + try { + SysLoginLog loginLog = new SysLoginLog(); + + // 基础信息 + loginLog.setLoginName(loginName); + loginLog.setUserId(getCurrentUserId(loginName)); + loginLog.setUserName(getCurrentUserName(loginName)); + loginLog.setLoginStatus("0"); + loginLog.setLoginTime(LocalDateTime.now()); + + // 网络信息 + loginLog.setLoginIp(getClientIP(request)); + loginLog.setLoginAddress(getAddressByIP(loginLog.getLoginIp())); + + // 客户端信息 + loginLog.setClientType(getClientType(request)); + loginLog.setUserAgent(request.getHeader("User-Agent")); + loginLog.setBrowser(getBrowserInfo(request)); + loginLog.setOs(getOSInfo(request)); + loginLog.setDeviceName(getDeviceName(request)); + + // 其他信息 + loginLog.setRemark("登录成功"); + loginLog.setCreateBy("system"); + loginLog.setCreateTime(LocalDateTime.now()); + + // 保存日志 + sysLoginLogMapper.insert(loginLog); + + } catch (Exception e) { + log.error("记录登录成功日志失败", e); + } + } + + /** + * 记录登录失败日志 + */ + public void recordLoginFailure(String loginName, String failureReason, HttpServletRequest request) { + try { + SysLoginLog loginLog = new SysLoginLog(); + + // 基础信息 + loginLog.setLoginName(loginName); + loginLog.setLoginStatus("1"); + loginLog.setLoginTime(LocalDateTime.now()); + loginLog.setFailureReason(failureReason); + + // 网络信息 + loginLog.setLoginIp(getClientIP(request)); + loginLog.setLoginAddress(getAddressByIP(loginLog.getLoginIp())); + + // 客户端信息 + loginLog.setClientType(getClientType(request)); + loginLog.setUserAgent(request.getHeader("User-Agent")); + loginLog.setBrowser(getBrowserInfo(request)); + loginLog.setOs(getOSInfo(request)); + loginLog.setDeviceName(getDeviceName(request)); + + // 其他信息 + loginLog.setRemark("登录失败:" + failureReason); + loginLog.setCreateBy("system"); + loginLog.setCreateTime(LocalDateTime.now()); + + // 保存日志 + sysLoginLogMapper.insert(loginLog); + + } catch (Exception e) { + log.error("记录登录失败日志失败", e); + } + } + + /** + * 记录退出登录日志 + */ + public void recordLogout(String loginName, LocalDateTime loginTime) { + try { + // 查找对应的登录日志 + SysLoginLog loginLog = sysLoginLogMapper.selectOne( + new LambdaQueryWrapper() + .eq(SysLoginLog::getLoginName, loginName) + .eq(SysLoginLog::getLoginTime, loginTime) + .eq(SysLoginLog::getLoginStatus, "0") + .isNull(SysLoginLog::getLogoutTime) + .last("ORDER BY create_time DESC LIMIT 1") + ); + + if (loginLog != null) { + LocalDateTime logoutTime = LocalDateTime.now(); + long sessionDuration = Duration.between(loginLog.getLoginTime(), logoutTime).getSeconds(); + + loginLog.setLogoutTime(logoutTime); + loginLog.setSessionDuration((int) sessionDuration); + loginLog.setRemark("正常退出"); + loginLog.setUpdateBy("system"); + loginLog.setUpdateTime(LocalDateTime.now()); + + sysLoginLogMapper.updateById(loginLog); + } + + } catch (Exception e) { + log.error("记录退出登录日志失败", e); + } + } + + /** + * 获取客户端IP + */ + private String getClientIP(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return ip; + } + + /** + * 解析浏览器信息 + */ + private String getBrowserInfo(HttpServletRequest request) { + String userAgent = request.getHeader("User-Agent"); + if (userAgent == null) { + return "Unknown"; + } + + if (userAgent.contains("Chrome")) { + return "Chrome"; + } else if (userAgent.contains("Firefox")) { + return "Firefox"; + } else if (userAgent.contains("Safari")) { + return "Safari"; + } else if (userAgent.contains("Edge")) { + return "Edge"; + } else if (userAgent.contains("IE")) { + return "Internet Explorer"; + } else { + return "Other"; + } + } + + /** + * 解析操作系统信息 + */ + private String getOSInfo(HttpServletRequest request) { + String userAgent = request.getHeader("User-Agent"); + if (userAgent == null) { + return "Unknown"; + } + + if (userAgent.contains("Windows NT 10.0")) { + return "Windows 10"; + } else if (userAgent.contains("Windows NT 6.3")) { + return "Windows 8.1"; + } else if (userAgent.contains("Windows NT 6.2")) { + return "Windows 8"; + } else if (userAgent.contains("Windows NT 6.1")) { + return "Windows 7"; + } else if (userAgent.contains("Mac OS X")) { + return "Mac OS"; + } else if (userAgent.contains("Linux")) { + return "Linux"; + } else if (userAgent.contains("Android")) { + return "Android"; + } else if (userAgent.contains("iPhone") || userAgent.contains("iPad")) { + return "iOS"; + } else { + return "Other"; + } + } +} +``` + +### 2. 异步处理 + +```java +@Service +public class AsyncLoginLogService { + + @Async("taskExecutor") + public void recordLoginLogAsync(SysLoginLog loginLog) { + try { + // 异步记录登录日志 + sysLoginLogMapper.insert(loginLog); + + // 更新用户登录信息 + updateUserLoginInfo(loginLog); + + // 检查异常登录 + checkAbnormalLogin(loginLog); + + } catch (Exception e) { + log.error("异步记录登录日志失败", e); + } + } + + /** + * 检查异常登录 + */ + private void checkAbnormalLogin(SysLoginLog loginLog) { + // 检查IP异常 + if (isAbnormalIP(loginLog.getLoginIp(), loginLog.getLoginName())) { + sendSecurityAlert("检测到异常IP登录", loginLog); + } + + // 检查设备异常 + if (isAbnormalDevice(loginLog.getUserAgent(), loginLog.getLoginName())) { + sendSecurityAlert("检测到异常设备登录", loginLog); + } + + // 检查时间异常 + if (isAbnormalTime(loginLog.getLoginTime(), loginLog.getLoginName())) { + sendSecurityAlert("检测到异常时间登录", loginLog); + } + } +} +``` + +--- + +## 统计分析功能 + +### 1. 登录统计 + +```java +/** + * 登录统计服务 + */ +@Service +public class LoginStatisticsService { + + /** + * 获取登录成功率统计 + */ + public LoginSuccessRateVO getLoginSuccessRate(String beginTime, String endTime) { + // 查询总登录次数 + long totalCount = sysLoginLogMapper.selectCount( + new LambdaQueryWrapper() + .between(SysLoginLog::getLoginTime, beginTime, endTime) + ); + + // 查询成功登录次数 + long successCount = sysLoginLogMapper.selectCount( + new LambdaQueryWrapper() + .eq(SysLoginLog::getLoginStatus, "0") + .between(SysLoginLog::getLoginTime, beginTime, endTime) + ); + + // 计算成功率 + double successRate = totalCount > 0 ? (double) successCount / totalCount * 100 : 0; + + return LoginSuccessRateVO.builder() + .totalCount(totalCount) + .successCount(successCount) + .failureCount(totalCount - successCount) + .successRate(successRate) + .build(); + } + + /** + * 获取每日登录统计 + */ + public List getDailyLoginStat(String beginTime, String endTime) { + return sysLoginLogMapper.selectDailyLoginStat(beginTime, endTime); + } + + /** + * 获取客户端类型统计 + */ + public List getClientTypeStat(String beginTime, String endTime) { + return sysLoginLogMapper.selectClientTypeStat(beginTime, endTime); + } +} +``` + +### 2. 安全监控 + +```java +/** + * 安全监控服务 + */ +@Service +public class SecurityMonitorService { + + /** + * 检测暴力破解 + */ + public void detectBruteForce() { + // 查询5分钟内失败次数超过5次的IP + List suspiciousIPs = sysLoginLogMapper.selectSuspiciousIPs(); + + for (String ip : suspiciousIPs) { + // 加入黑名单 + addToBlacklist(ip); + + // 发送告警 + sendSecurityAlert("检测到暴力破解攻击", ip); + } + } + + /** + * 检测异地登录 + */ + public void detectRemoteLogin() { + // 查询用户最近登录地址 + List recentLogins = sysLoginLogMapper.selectRecentLogins(); + + for (SysLoginLog loginLog : recentLogins) { + String lastLoginAddress = getLastLoginAddress(loginLog.getLoginName()); + + if (!loginLog.getLoginAddress().equals(lastLoginAddress)) { + // 发送异地登录通知 + sendRemoteLoginNotification(loginLog); + } + } + } +} +``` + +--- + +## 错误码说明 + +| 错误码 | 错误信息 | 说明 | +|--------|----------|------| +| 400 | 登录账号不能为空 | 登录账号为空 | +| 400 | 登录状态不能为空 | 登录状态为空 | +| 400 | 登录时间不能为空 | 登录时间为空 | +| 400 | 登录IP不能为空 | 登录IP为空 | +| 400 | 日志不存在 | 日志ID不存在 | +| 400 | 时间格式错误 | 时间格式不正确 | +| 401 | 当前会话未登录 | 未登录或Token无效 | +| 403 | 权限不足 | 没有相应的操作权限 | +| 500 | 日志记录失败 | 日志保存失败 | +| 500 | 系统异常 | 服务器内部错误 | + +--- + +## 日志清理策略 + +### 1. 定时清理 + +```java +@Component +public class LoginLogCleanupTask { + + /** + * 每天凌晨2点清理30天前的登录日志 + */ + @Scheduled(cron = "0 0 2 * * ?") + public void cleanupOldLogs() { + try { + LocalDateTime cutoffTime = LocalDateTime.now().minusDays(30); + + int deletedCount = sysLoginLogMapper.delete( + new LambdaQueryWrapper() + .lt(SysLoginLog::getCreateTime, cutoffTime) + ); + + log.info("清理登录日志完成,删除{}条记录", deletedCount); + + } catch (Exception e) { + log.error("清理登录日志失败", e); + } + } + + /** + * 归档登录日志 + */ + @Scheduled(cron = "0 0 1 1 * ?") // 每月1号凌晨1点执行 + public void archiveLogs() { + try { + LocalDateTime lastMonth = LocalDateTime.now().minusMonths(1); + + // 归档上个月的日志到历史表 + sysLoginLogMapper.archiveLogsToHistory(lastMonth); + + log.info("归档登录日志完成"); + + } catch (Exception e) { + log.error("归档登录日志失败", e); + } + } +} +``` + +### 2. 日志配置 + +```yaml +# application.yml +logging: + login-log: + # 是否启用登录日志 + enabled: true + # 日志保留天数 + retention-days: 90 + # 是否记录成功登录 + log-success: true + # 是否记录失败登录 + log-failure: true + # 是否异步记录 + async: true + # 是否启用IP地址解析 + resolve-address: true + # 是否启用安全监控 + security-monitor: true +``` + +--- + +## 使用建议 + +### 1. 日志管理 + +- **合理保留期**: 根据业务需要设置日志保留期 +- **定期清理**: 定期清理过期日志释放存储空间 +- **分表存储**: 大量日志可考虑按月分表存储 +- **归档备份**: 重要日志定期归档备份 + +### 2. 安全监控 + +- **实时监控**: 实时监控异常登录行为 +- **告警机制**: 建立完善的安全告警机制 +- **自动响应**: 对可疑行为自动采取防护措施 +- **定期分析**: 定期分析登录日志发现安全趋势 + +### 3. 性能优化 + +- **异步记录**: 使用异步方式记录日志避免影响登录性能 +- **批量处理**: 批量处理日志数据提高效率 +- **索引优化**: 为查询字段建立合适索引 +- **分页查询**: 大数据量查询使用分页 + +### 4. 隐私保护 + +- **敏感信息**: 不记录密码等敏感信息 +- **数据脱敏**: 对部分信息进行脱敏处理 +- **访问控制**: 严格控制日志访问权限 +- **合规要求**: 遵守相关数据保护法规 + +--- + +## 注意事项 + +1. **日志完整性**: 确保登录日志记录的完整性和准确性 +2. **性能影响**: 日志记录不应影响用户登录体验 +3. **存储空间**: 注意日志存储空间的管理和清理 +4. **安全防护**: 防止日志被恶意篡改或删除 +5. **隐私保护**: 不记录用户密码等敏感信息 +6. **异常处理**: 日志记录失败不应影响正常业务 +7. **监控告警**: 建立异常登录的监控告警机制 +8. **数据备份**: 重要日志数据需要备份 +9. **合规要求**: 遵守相关法律法规对日志的要求 +10. **访问审计**: 对日志访问行为也要进行审计 \ No newline at end of file diff --git a/api/user/用户管理API.md b/api/user/用户管理API.md new file mode 100644 index 0000000..efd4fa5 --- /dev/null +++ b/api/user/用户管理API.md @@ -0,0 +1,888 @@ +# 用户管理API + +## 概述 + +用户管理模块提供系统用户的增删改查功能,包括用户信息管理、状态控制、密码管理、角色分配等核心功能。 + +## 权限说明 + +用户管理接口需要相应的权限才能访问: + +| 操作 | 权限码 | 说明 | +|------|--------|------| +| 查询用户列表 | `system:user:list` | 查看用户列表权限 | +| 新增用户 | `system:user:add` | 新增用户权限 | +| 修改用户 | `system:user:edit` | 修改用户信息权限 | +| 删除用户 | `system:user:remove` | 删除用户权限 | +| 重置密码 | `system:user:resetPwd` | 重置用户密码权限 | +| 修改状态 | `system:user:status` | 修改用户状态权限 | +| 分配角色 | `system:user:role` | 分配用户角色权限 | +| 导入用户 | `system:user:import` | 导入用户数据权限 | +| 导出用户 | `system:user:export` | 导出用户数据权限 | + +## 接口列表 + +### 1. 分页查询用户列表 + +**接口地址**: `GET /coder/sysLoginUser/listPage` + +**接口描述**: 分页查询系统用户列表 + +**是否需要认证**: 是 + +**权限要求**: `system:user:list` + +**请求头**: +``` +Authorization: your-token-value +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| pageNo | Integer | 否 | 页码 | 1 | +| pageSize | Integer | 否 | 每页大小 | 10 | +| loginName | String | 否 | 登录账号 | admin | +| userName | String | 否 | 用户姓名 | 管理员 | +| userType | String | 否 | 用户类型 | 1 | +| phone | String | 否 | 手机号码 | 13800138000 | +| sex | String | 否 | 用户性别 | 1 | +| userStatus | String | 否 | 用户状态 | 0 | +| beginTime | String | 否 | 开始时间 | 2024-01-01 | +| endTime | String | 否 | 结束时间 | 2024-12-31 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "records": [ + { + "userId": 1, + "loginName": "admin", + "userName": "管理员", + "userType": "1", + "email": "admin@example.com", + "phone": "13800138000", + "sex": "1", + "avatar": "/upload/avatar/admin.jpg", + "userStatus": "0", + "loginIp": "127.0.0.1", + "loginTime": "2024-01-01 10:00:00", + "pwdUpdateTime": "2024-01-01 10:00:00", + "remark": "系统管理员", + "createBy": "admin", + "createTime": "2024-01-01 10:00:00", + "updateBy": "admin", + "updateTime": "2024-01-01 10:00:00", + "roleIds": [1, 2] + } + ], + "total": 1, + "size": 10, + "current": 1, + "pages": 1 + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysLoginUser/listPage?pageNo=1&pageSize=10&loginName=admin" \ + -H "Authorization: your-token-value" +``` + +--- + +### 2. 查询所有用户 + +**接口地址**: `GET /coder/sysLoginUser/list` + +**接口描述**: 查询所有系统用户(不分页) + +**是否需要认证**: 是 + +**权限要求**: `system:user:list` + +**请求参数**: 同分页查询(除pageNo、pageSize外) + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": [ + { + "userId": 1, + "loginName": "admin", + "userName": "管理员", + // 其他字段... + } + ], + "traceId": "trace-123456" +} +``` + +--- + +### 3. 根据ID查询用户 + +**接口地址**: `GET /coder/sysLoginUser/getById/{id}` + +**接口描述**: 根据用户ID查询用户详细信息 + +**是否需要认证**: 是 + +**权限要求**: `system:user:list` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 用户ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "userId": 1, + "loginName": "admin", + "userName": "管理员", + "userType": "1", + "email": "admin@example.com", + "phone": "13800138000", + "sex": "1", + "avatar": "/upload/avatar/admin.jpg", + "userStatus": "0", + "loginIp": "127.0.0.1", + "loginTime": "2024-01-01 10:00:00", + "pwdUpdateTime": "2024-01-01 10:00:00", + "remark": "系统管理员", + "createBy": "admin", + "createTime": "2024-01-01 10:00:00", + "updateBy": "admin", + "updateTime": "2024-01-01 10:00:00", + "roleIds": [1, 2] + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysLoginUser/getById/1 \ + -H "Authorization: your-token-value" +``` + +--- + +### 4. 新增用户 + +**接口地址**: `POST /coder/sysLoginUser/add` + +**接口描述**: 新增系统用户 + +**是否需要认证**: 是 + +**权限要求**: `system:user:add` + +**请求参数**: + +```json +{ + "loginName": "newuser", + "userName": "新用户", + "password": "123456", + "userType": "1", + "email": "newuser@example.com", + "phone": "13800138001", + "sex": "1", + "userStatus": "0", + "remark": "新用户备注", + "roleIds": [2] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| loginName | String | 是 | 登录账号 | 3-16位字母数字 | +| userName | String | 是 | 用户姓名 | 不能为空 | +| password | String | 是 | 登录密码 | 不能为空 | +| userType | String | 是 | 用户类型 | 1-系统用户 2-注册用户 | +| email | String | 否 | 邮箱地址 | 邮箱格式验证 | +| phone | String | 否 | 手机号码 | 手机号格式验证 | +| sex | String | 否 | 用户性别 | 1-男 2-女 3-未知 | +| userStatus | String | 是 | 用户状态 | 0-启用 1-停用 | +| remark | String | 否 | 备注信息 | 最长200字符 | +| roleIds | Long[] | 否 | 角色ID数组 | 有效的角色ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "新增成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginUser/add \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "loginName": "newuser", + "userName": "新用户", + "password": "123456", + "userType": "1", + "email": "newuser@example.com", + "phone": "13800138001", + "sex": "1", + "userStatus": "0", + "remark": "新用户备注", + "roleIds": [2] + }' +``` + +--- + +### 5. 修改用户信息 + +**接口地址**: `POST /coder/sysLoginUser/update` + +**接口描述**: 修改系统用户信息 + +**是否需要认证**: 是 + +**权限要求**: `system:user:edit` + +**请求参数**: + +```json +{ + "userId": 1, + "loginName": "admin", + "userName": "管理员", + "userType": "1", + "email": "admin@example.com", + "phone": "13800138000", + "sex": "1", + "userStatus": "0", + "remark": "系统管理员", + "roleIds": [1, 2] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| userId | Long | 是 | 用户ID | 必须是有效的用户ID | +| loginName | String | 是 | 登录账号 | 3-16位字母数字 | +| userName | String | 是 | 用户姓名 | 不能为空 | +| userType | String | 是 | 用户类型 | 1-系统用户 2-注册用户 | +| email | String | 否 | 邮箱地址 | 邮箱格式验证 | +| phone | String | 否 | 手机号码 | 手机号格式验证 | +| sex | String | 否 | 用户性别 | 1-男 2-女 3-未知 | +| userStatus | String | 是 | 用户状态 | 0-启用 1-停用 | +| remark | String | 否 | 备注信息 | 最长200字符 | +| roleIds | Long[] | 否 | 角色ID数组 | 有效的角色ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginUser/update \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "userId": 1, + "loginName": "admin", + "userName": "管理员", + "userType": "1", + "email": "admin@example.com", + "phone": "13800138000", + "sex": "1", + "userStatus": "0", + "remark": "系统管理员", + "roleIds": [1, 2] + }' +``` + +--- + +### 6. 删除用户 + +**接口地址**: `POST /coder/sysLoginUser/deleteById/{id}` + +**接口描述**: 根据ID删除用户 + +**是否需要认证**: 是 + +**权限要求**: `system:user:remove` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 用户ID | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginUser/deleteById/1 \ + -H "Authorization: your-token-value" +``` + +--- + +### 7. 批量删除用户 + +**接口地址**: `POST /coder/sysLoginUser/batchDelete` + +**接口描述**: 批量删除用户 + +**是否需要认证**: 是 + +**权限要求**: `system:user:remove` + +**请求参数**: + +```json +{ + "ids": [1, 2, 3] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | Long[] | 是 | 用户ID数组 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "删除成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginUser/batchDelete \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "ids": [1, 2, 3] + }' +``` + +--- + +### 8. 修改用户状态 + +**接口地址**: `POST /coder/sysLoginUser/updateStatus/{userId}/{userStatus}` + +**接口描述**: 修改用户状态(启用/停用) + +**是否需要认证**: 是 + +**权限要求**: `system:user:status` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| userId | Long | 是 | 用户ID | +| userStatus | String | 是 | 用户状态(0-启用 1-停用) | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginUser/updateStatus/1/0 \ + -H "Authorization: your-token-value" +``` + +--- + +### 9. 获取登录用户信息 + +**接口地址**: `GET /coder/sysLoginUser/getLoginUserInformation` + +**接口描述**: 获取当前登录用户的详细信息 + +**是否需要认证**: 是 + +**权限要求**: 无(已登录用户可访问) + +**请求参数**: 无 + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "userId": 1, + "loginName": "admin", + "userName": "管理员", + "userType": "1", + "email": "admin@example.com", + "phone": "13800138000", + "sex": "1", + "avatar": "/upload/avatar/admin.jpg", + "userStatus": "0", + "loginIp": "127.0.0.1", + "loginTime": "2024-01-01 10:00:00", + "pwdUpdateTime": "2024-01-01 10:00:00", + "remark": "系统管理员", + "createBy": "admin", + "createTime": "2024-01-01 10:00:00", + "updateBy": "admin", + "updateTime": "2024-01-01 10:00:00", + "roleIds": [1, 2], + "roles": [ + { + "roleId": 1, + "roleName": "超级管理员", + "roleCode": "admin" + } + ], + "permissions": [ + "*:*:*" + ] + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysLoginUser/getLoginUserInformation \ + -H "Authorization: your-token-value" +``` + +--- + +### 10. 获取个人资料 + +**接口地址**: `GET /coder/sysLoginUser/getPersonalData` + +**接口描述**: 获取当前用户的个人资料 + +**是否需要认证**: 是 + +**权限要求**: 无(已登录用户可访问) + +**请求参数**: 无 + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "userId": 1, + "loginName": "admin", + "userName": "管理员", + "email": "admin@example.com", + "phone": "13800138000", + "sex": "1", + "avatar": "/upload/avatar/admin.jpg", + "remark": "系统管理员" + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysLoginUser/getPersonalData \ + -H "Authorization: your-token-value" +``` + +--- + +### 11. 修改个人资料 + +**接口地址**: `POST /coder/sysLoginUser/updateBasicData` + +**接口描述**: 修改当前用户的个人资料 + +**是否需要认证**: 是 + +**权限要求**: 无(已登录用户可访问) + +**请求参数**: + +```json +{ + "userName": "管理员", + "email": "admin@example.com", + "phone": "13800138000", + "sex": "1", + "avatar": "/upload/avatar/admin.jpg", + "remark": "系统管理员" +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| userName | String | 是 | 用户姓名 | 不能为空 | +| email | String | 否 | 邮箱地址 | 邮箱格式验证 | +| phone | String | 否 | 手机号码 | 手机号格式验证 | +| sex | String | 否 | 用户性别 | 1-男 2-女 3-未知 | +| avatar | String | 否 | 头像地址 | 有效的文件路径 | +| remark | String | 否 | 备注信息 | 最长200字符 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginUser/updateBasicData \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "userName": "管理员", + "email": "admin@example.com", + "phone": "13800138000", + "sex": "1", + "avatar": "/upload/avatar/admin.jpg", + "remark": "系统管理员" + }' +``` + +--- + +### 12. 修改登录密码 + +**接口地址**: `POST /coder/sysLoginUser/updateUserPwd` + +**接口描述**: 修改当前用户的登录密码 + +**是否需要认证**: 是 + +**权限要求**: 无(已登录用户可访问) + +**请求参数**: + +```json +{ + "oldPassword": "123456", + "newPassword": "654321", + "confirmPassword": "654321" +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| oldPassword | String | 是 | 原密码 | 不能为空 | +| newPassword | String | 是 | 新密码 | 不能为空 | +| confirmPassword | String | 是 | 确认密码 | 必须与新密码一致 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "修改成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginUser/updateUserPwd \ + -H "Content-Type: application/json" \ + -H "Authorization: your-token-value" \ + -d '{ + "oldPassword": "123456", + "newPassword": "654321", + "confirmPassword": "654321" + }' +``` + +--- + +### 13. 重置用户密码 + +**接口地址**: `POST /coder/sysLoginUser/resetPwd/{id}/{password}` + +**接口描述**: 重置指定用户的密码 + +**是否需要认证**: 是 + +**权限要求**: `system:user:resetPwd` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 用户ID | +| password | String | 是 | 新密码 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": "重置成功", + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginUser/resetPwd/1/123456 \ + -H "Authorization: your-token-value" +``` + +--- + +### 14. 下载用户导入模板 + +**接口地址**: `GET /coder/sysLoginUser/downloadExcelTemplate` + +**接口描述**: 下载用户批量导入的Excel模板 + +**是否需要认证**: 是 + +**权限要求**: `system:user:import` + +**请求参数**: 无 + +**响应**: Excel文件下载 + +**调用示例**: + +```bash +curl -X GET \ + http://localhost:18099/coder/sysLoginUser/downloadExcelTemplate \ + -H "Authorization: your-token-value" \ + -o user_template.xlsx +``` + +--- + +### 15. 导出用户数据 + +**接口地址**: `GET /coder/sysLoginUser/exportExcelData` + +**接口描述**: 导出用户数据到Excel + +**是否需要认证**: 是 + +**权限要求**: `system:user:export` + +**请求参数**: 同查询参数 + +**响应**: Excel文件下载 + +**调用示例**: + +```bash +curl -X GET \ + "http://localhost:18099/coder/sysLoginUser/exportExcelData?userStatus=0" \ + -H "Authorization: your-token-value" \ + -o users.xlsx +``` + +--- + +### 16. 导入用户数据 + +**接口地址**: `POST /coder/sysLoginUser/importExcelData` + +**接口描述**: 从Excel文件导入用户数据 + +**是否需要认证**: 是 + +**权限要求**: `system:user:import` + +**请求参数**: 文件上传 + +**Content-Type**: `multipart/form-data` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| file | File | 是 | Excel文件 | +| updateSupport | Boolean | 否 | 是否更新已存在的用户 | + +**响应示例**: + +```json +{ + "status": 200, + "msg": "SUCCESS", + "data": { + "total": 100, + "success": 95, + "failed": 5, + "message": "导入成功95条,失败5条" + }, + "traceId": "trace-123456" +} +``` + +**调用示例**: + +```bash +curl -X POST \ + http://localhost:18099/coder/sysLoginUser/importExcelData \ + -H "Authorization: your-token-value" \ + -F "file=@users.xlsx" \ + -F "updateSupport=true" +``` + +--- + +## 数据字典 + +### 用户类型 (userType) + +| 值 | 说明 | +|----|------| +| 1 | 系统用户 | +| 2 | 注册用户 | +| 3 | 微信用户 | + +### 用户性别 (sex) + +| 值 | 说明 | +|----|------| +| 1 | 男 | +| 2 | 女 | +| 3 | 未知 | + +### 用户状态 (userStatus) + +| 值 | 说明 | +|----|------| +| 0 | 启用 | +| 1 | 停用 | + +--- + +## 错误码说明 + +| 错误码 | 错误信息 | 说明 | +|--------|----------|------| +| 400 | 账号长度为 3-16 位 | 登录账号长度不符合要求 | +| 400 | 账号格式为数字以及字母 | 登录账号格式不正确 | +| 400 | 登录账号不能为空 | 登录账号为空 | +| 400 | 用户真实姓名不能为空 | 用户姓名为空 | +| 400 | 用户不存在 | 用户ID不存在 | +| 400 | 账号已存在 | 登录账号已被使用 | +| 400 | 手机号已存在 | 手机号已被使用 | +| 400 | 邮箱已存在 | 邮箱已被使用 | +| 400 | 不能删除当前用户 | 不能删除自己的账号 | +| 400 | 不能停用当前用户 | 不能停用自己的账号 | +| 400 | 原密码错误 | 修改密码时原密码不正确 | +| 400 | 新密码不能与原密码相同 | 新密码与原密码一致 | +| 400 | 两次输入的密码不一致 | 确认密码与新密码不一致 | +| 401 | 当前会话未登录 | 未登录或Token无效 | +| 403 | 权限不足 | 没有相应的操作权限 | +| 500 | 系统异常 | 服务器内部错误 | + +--- + +## 注意事项 + +1. **权限验证**: 所有用户管理操作都需要相应的权限 +2. **数据校验**: 新增和修改用户时会进行数据格式校验 +3. **唯一性约束**: 登录账号、手机号、邮箱必须唯一 +4. **密码安全**: 密码使用盐值加密存储 +5. **操作日志**: 所有用户操作都会记录日志 +6. **批量操作**: 批量删除时会跳过超级管理员账号 +7. **Excel导入**: 支持Excel模板导入用户数据 +8. **角色关联**: 用户删除时会自动删除角色关联关系 +9. **会话管理**: 用户状态变更时会影响其登录会话 +10. **数据完整性**: 删除用户前会检查关联数据 \ No newline at end of file