chore: 初始化项目基础配置

- 添加Maven父POM配置,定义项目依赖版本
- 配置项目模块结构
- 添加.gitignore规则
- 添加项目文档和开发指南
This commit is contained in:
Leo 2025-10-08 02:00:43 +08:00
commit 19d3eccb5b
15 changed files with 10605 additions and 0 deletions

211
api/README.md Normal file
View File

@ -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): 初始版本发布
## 联系方式
如有问题,请联系开发团队。

View File

@ -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

View File

@ -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
<!-- 验证码显示区域 -->
<div class="captcha-container">
<img id="captchaImg" src="" alt="验证码" onclick="refreshCaptcha()">
<input type="hidden" id="codeKey" value="">
<input type="text" id="securityCode" placeholder="请输入验证码">
<button type="button" onclick="refreshCaptcha()">刷新</button>
</div>
```
```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. 支持自定义验证码样式和难度

File diff suppressed because it is too large Load Diff

View File

@ -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<SysRole> list() {
// 业务逻辑
}
```
### 2. 数据权限控制
```java
// 根据用户数据权限过滤数据
public List<SysRole> 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<String> 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. **定期清理**: 定期清理无效的角色和权限关联

View File

@ -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<ImageWriter> 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. **监控告警**: 监控存储空间和访问异常

View File

@ -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. **日志审计**: 记录所有文件操作日志

File diff suppressed because it is too large Load Diff

888
api/user/用户管理API.md Normal file
View File

@ -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. **数据完整性**: 删除用户前会检查关联数据

240
doc/oss/setup-env.sh Executable file
View File

@ -0,0 +1,240 @@
#!/bin/bash
# OSS环境变量快速设置脚本
# 用于设置阿里云OSS所需的环境变量
echo "🔧 OSS环境变量设置工具"
echo "=================================="
# 检测操作系统
OS_TYPE=$(uname -s)
SHELL_TYPE=$(basename "$SHELL")
echo "检测到操作系统: $OS_TYPE"
echo "检测到Shell: $SHELL_TYPE"
echo ""
# 获取当前配置文件中的值(仅用于显示)
CURRENT_KEY_ID="LTAI5t982gXi7A72gAa9yugE"
CURRENT_KEY_SECRET="Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30"
echo "📋 配置文件中的当前值:"
echo "OSS_ACCESS_KEY_ID: $CURRENT_KEY_ID"
echo "OSS_ACCESS_KEY_SECRET: ${CURRENT_KEY_SECRET:0:8}..."
echo ""
# 选择设置方式
echo "请选择设置方式:"
echo "1. 临时设置(当前终端会话有效)"
echo "2. 永久设置添加到Shell配置文件"
echo "3. 创建启动脚本"
echo "4. 创建.env文件"
echo "5. 显示手动设置命令"
echo ""
read -p "请输入选项 (1-5): " choice
case $choice in
1)
echo ""
echo "🔧 临时设置环境变量..."
export OSS_ACCESS_KEY_ID="$CURRENT_KEY_ID"
export OSS_ACCESS_KEY_SECRET="$CURRENT_KEY_SECRET"
echo "✅ 环境变量已设置(当前终端会话有效)"
echo ""
echo "验证设置:"
echo "OSS_ACCESS_KEY_ID: $OSS_ACCESS_KEY_ID"
echo "OSS_ACCESS_KEY_SECRET: ${OSS_ACCESS_KEY_SECRET:0:8}..."
echo ""
echo "⚠️ 注意:这些变量只在当前终端会话中有效"
echo " 关闭终端后需要重新设置"
;;
2)
echo ""
echo "🔧 永久设置环境变量..."
# 根据Shell类型选择配置文件
if [[ "$SHELL_TYPE" == "zsh" ]]; then
CONFIG_FILE="$HOME/.zshrc"
elif [[ "$SHELL_TYPE" == "bash" ]]; then
CONFIG_FILE="$HOME/.bashrc"
else
CONFIG_FILE="$HOME/.profile"
fi
echo "将添加到配置文件: $CONFIG_FILE"
# 检查是否已经存在
if grep -q "OSS_ACCESS_KEY_ID" "$CONFIG_FILE"; then
echo "⚠️ 配置文件中已存在OSS_ACCESS_KEY_ID是否覆盖 (y/N)"
read -p "" overwrite
if [[ "$overwrite" != "y" && "$overwrite" != "Y" ]]; then
echo "取消设置"
exit 0
fi
# 移除现有配置
sed -i.bak '/OSS_ACCESS_KEY_ID/d' "$CONFIG_FILE"
sed -i.bak '/OSS_ACCESS_KEY_SECRET/d' "$CONFIG_FILE"
fi
# 添加新配置
echo "" >> "$CONFIG_FILE"
echo "# OSS环境变量 - 由setup-env.sh添加" >> "$CONFIG_FILE"
echo "export OSS_ACCESS_KEY_ID=\"$CURRENT_KEY_ID\"" >> "$CONFIG_FILE"
echo "export OSS_ACCESS_KEY_SECRET=\"$CURRENT_KEY_SECRET\"" >> "$CONFIG_FILE"
echo "✅ 环境变量已添加到 $CONFIG_FILE"
echo ""
echo "请执行以下命令使配置生效:"
echo "source $CONFIG_FILE"
echo ""
echo "或者重新打开终端"
;;
3)
echo ""
echo "🔧 创建启动脚本..."
SCRIPT_FILE="start-app-with-oss.sh"
cat > "$SCRIPT_FILE" << 'EOF'
#!/bin/bash
# 应用程序启动脚本包含OSS环境变量
# 自动生成于 $(date)
echo "🚀 启动应用程序OSS模式..."
echo "=================================="
# 设置OSS环境变量
export OSS_ACCESS_KEY_ID="LTAI5t982gXi7A72gAa9yugE"
export OSS_ACCESS_KEY_SECRET="Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30"
echo "✅ OSS环境变量已设置"
echo "OSS_ACCESS_KEY_ID: $OSS_ACCESS_KEY_ID"
echo "OSS_ACCESS_KEY_SECRET: ${OSS_ACCESS_KEY_SECRET:0:8}..."
echo ""
# 切换到项目目录
PROJECT_DIR="/Users/leocoder/leocoder/develop/templates/coder-common-thin/coder-common-thin-backend"
if [ -d "$PROJECT_DIR" ]; then
cd "$PROJECT_DIR"
echo "📁 切换到项目目录: $PROJECT_DIR"
else
echo "❌ 项目目录不存在: $PROJECT_DIR"
echo "请修改脚本中的PROJECT_DIR变量"
exit 1
fi
# 检查JAR文件是否存在
JAR_FILE="coder-common-thin-web/target/coder-common-thin-web-1.0.0.jar"
if [ -f "$JAR_FILE" ]; then
echo "📦 找到JAR文件: $JAR_FILE"
else
echo "❌ JAR文件不存在: $JAR_FILE"
echo "请先编译项目: mvn clean package -DskipTests"
exit 1
fi
# 启动应用程序
echo ""
echo "🚀 启动Spring Boot应用程序..."
echo "访问地址: http://localhost:18099"
echo "API文档: http://localhost:18099/swagger-ui.html"
echo ""
echo "按 Ctrl+C 停止应用程序"
echo ""
java -jar "$JAR_FILE"
EOF
chmod +x "$SCRIPT_FILE"
echo "✅ 启动脚本已创建: $SCRIPT_FILE"
echo ""
echo "使用方法:"
echo "./$SCRIPT_FILE"
;;
4)
echo ""
echo "🔧 创建.env文件..."
ENV_FILE=".env"
cat > "$ENV_FILE" << EOF
# OSS环境变量配置
# 创建时间: $(date)
# 注意: 此文件包含敏感信息,不要提交到版本控制
OSS_ACCESS_KEY_ID=$CURRENT_KEY_ID
OSS_ACCESS_KEY_SECRET=$CURRENT_KEY_SECRET
EOF
echo "✅ .env文件已创建: $ENV_FILE"
echo ""
echo "使用方法:"
echo "1. 使用dotenv工具: dotenv java -jar app.jar"
echo "2. 手动加载: source .env && java -jar app.jar"
echo "3. 在IDE中配置Environment Variables"
echo ""
echo "⚠️ 重要提醒:"
echo "• 将.env添加到.gitignore文件中"
echo "• 不要将.env文件提交到版本控制"
# 检查并添加到.gitignore
if [ -f ".gitignore" ]; then
if ! grep -q "\.env" .gitignore; then
echo ".env" >> .gitignore
echo "✅ .env已添加到.gitignore"
fi
else
echo ".env" > .gitignore
echo "✅ 已创建.gitignore并添加.env"
fi
;;
5)
echo ""
echo "📋 手动设置命令:"
echo "=================================="
echo ""
echo "🐧 Linux/macOS (Bash):"
echo "export OSS_ACCESS_KEY_ID=\"$CURRENT_KEY_ID\""
echo "export OSS_ACCESS_KEY_SECRET=\"$CURRENT_KEY_SECRET\""
echo ""
echo "🐧 Linux/macOS (Zsh):"
echo "export OSS_ACCESS_KEY_ID=\"$CURRENT_KEY_ID\""
echo "export OSS_ACCESS_KEY_SECRET=\"$CURRENT_KEY_SECRET\""
echo ""
echo "🪟 Windows (CMD):"
echo "set OSS_ACCESS_KEY_ID=$CURRENT_KEY_ID"
echo "set OSS_ACCESS_KEY_SECRET=$CURRENT_KEY_SECRET"
echo ""
echo "🪟 Windows (PowerShell):"
echo "\$env:OSS_ACCESS_KEY_ID=\"$CURRENT_KEY_ID\""
echo "\$env:OSS_ACCESS_KEY_SECRET=\"$CURRENT_KEY_SECRET\""
echo ""
echo "🐳 Docker:"
echo "docker run -e OSS_ACCESS_KEY_ID=\"$CURRENT_KEY_ID\" -e OSS_ACCESS_KEY_SECRET=\"$CURRENT_KEY_SECRET\" your-app"
echo ""
echo "☕ Java启动命令:"
echo "OSS_ACCESS_KEY_ID=\"$CURRENT_KEY_ID\" OSS_ACCESS_KEY_SECRET=\"$CURRENT_KEY_SECRET\" java -jar app.jar"
;;
*)
echo "❌ 无效选项请选择1-5"
exit 1
;;
esac
echo ""
echo "🧪 验证环境变量设置:"
echo "--------------------------------"
echo "echo \$OSS_ACCESS_KEY_ID"
echo "echo \$OSS_ACCESS_KEY_SECRET"
echo ""
echo "📖 详细文档: doc/oss/环境变量设置指南.md"
echo "🚀 测试脚本: doc/oss/修复后验证测试.sh"

View File

@ -0,0 +1,271 @@
# 环境变量设置指南
## 📋 需要设置的环境变量
根据你的配置文件,需要设置以下环境变量:
```bash
OSS_ACCESS_KEY_ID=your_access_key_id
OSS_ACCESS_KEY_SECRET=your_access_key_secret
```
## 🖥️ 不同操作系统的设置方法
### 1. macOS / Linux
#### 方法1临时设置当前终端会话有效
```bash
export OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE
export OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30
```
#### 方法2永久设置添加到配置文件
**对于 Bash 用户:**
```bash
# 编辑 ~/.bashrc 或 ~/.bash_profile
echo 'export OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE' >> ~/.bashrc
echo 'export OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30' >> ~/.bashrc
# 重新加载配置
source ~/.bashrc
```
**对于 Zsh 用户macOS 默认):**
```bash
# 编辑 ~/.zshrc
echo 'export OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE' >> ~/.zshrc
echo 'export OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30' >> ~/.zshrc
# 重新加载配置
source ~/.zshrc
```
### 2. Windows
#### 方法1命令行临时设置
**CMD**
```cmd
set OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE
set OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30
```
**PowerShell**
```powershell
$env:OSS_ACCESS_KEY_ID="LTAI5t982gXi7A72gAa9yugE"
$env:OSS_ACCESS_KEY_SECRET="Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30"
```
#### 方法2系统环境变量设置
1. 右击"此电脑" → "属性"
2. 点击"高级系统设置"
3. 点击"环境变量"
4. 在"系统变量"中点击"新建"
5. 变量名:`OSS_ACCESS_KEY_ID`,变量值:`LTAI5t982gXi7A72gAa9yugE`
6. 重复步骤4-5设置 `OSS_ACCESS_KEY_SECRET`
## 🚀 启动应用程序的方法
### 1. 直接在终端启动
```bash
# 设置环境变量
export OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE
export OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30
# 启动应用程序
cd /Users/leocoder/leocoder/develop/templates/coder-common-thin/coder-common-thin-backend
java -jar coder-common-thin-web/target/coder-common-thin-web-1.0.0.jar
```
### 2. 一行命令启动
```bash
OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30 java -jar coder-common-thin-web/target/coder-common-thin-web-1.0.0.jar
```
### 3. 使用启动脚本
创建一个启动脚本:
```bash
#!/bin/bash
# 文件名: start-app.sh
# 设置环境变量
export OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE
export OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30
# 切换到项目目录
cd /Users/leocoder/leocoder/develop/templates/coder-common-thin/coder-common-thin-backend
# 启动应用程序
java -jar coder-common-thin-web/target/coder-common-thin-web-1.0.0.jar
```
给脚本添加执行权限并运行:
```bash
chmod +x start-app.sh
./start-app.sh
```
## 🐳 Docker 环境
### 1. docker run 命令
```bash
docker run -d \
-e OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE \
-e OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30 \
-p 18099:18099 \
your-app-image
```
### 2. docker-compose.yml
```yaml
version: '3.8'
services:
app:
image: your-app-image
ports:
- "18099:18099"
environment:
- OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE
- OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30
```
### 3. 使用 .env 文件
```bash
# 创建 .env 文件
echo "OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE" > .env
echo "OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30" >> .env
# docker-compose 会自动读取
docker-compose up -d
```
## 🔧 IDE 环境配置
### 1. IntelliJ IDEA
1. 打开 Run Configuration
2. 选择你的 Spring Boot 应用
3. 在 "Environment Variables" 中添加:
- `OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE`
- `OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30`
### 2. VS Code
`.vscode/launch.json` 中配置:
```json
{
"type": "java",
"name": "Spring Boot App",
"request": "launch",
"mainClass": "org.leocoder.thin.web.CoderApplication",
"env": {
"OSS_ACCESS_KEY_ID": "LTAI5t982gXi7A72gAa9yugE",
"OSS_ACCESS_KEY_SECRET": "Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30"
}
}
```
### 3. Eclipse
1. 右击项目 → Run As → Run Configurations
2. 选择你的 Java Application
3. 在 "Environment" 选项卡中添加变量
## 🔐 安全最佳实践
### 1. 使用 .env 文件(推荐)
创建 `.env` 文件(不要提交到版本控制):
```bash
# .env 文件
OSS_ACCESS_KEY_ID=LTAI5t982gXi7A72gAa9yugE
OSS_ACCESS_KEY_SECRET=Mi9ZsSWLGkvFoMiLNiZ71hHFzVso30
```
`.gitignore` 中添加:
```
.env
```
### 2. 使用系统密钥管理
**macOS Keychain:**
```bash
# 存储到 Keychain
security add-generic-password -s "oss-access-key" -a "your-app" -w "LTAI5t982gXi7A72gAa9yugE"
# 从 Keychain 读取
OSS_ACCESS_KEY_ID=$(security find-generic-password -s "oss-access-key" -a "your-app" -w)
```
**Linux Secret Service:**
```bash
# 使用 secret-tool
secret-tool store --label="OSS Access Key" service oss-access-key LTAI5t982gXi7A72gAa9yugE
```
### 3. 配置文件最佳实践
修改 `application-dev.yml`
```yaml
coder:
oss:
# 只从环境变量读取,不设置默认值
access-key-id: ${OSS_ACCESS_KEY_ID}
access-key-secret: ${OSS_ACCESS_KEY_SECRET}
```
## ✅ 验证环境变量
### 1. 检查环境变量是否设置成功
```bash
# Linux/macOS
echo $OSS_ACCESS_KEY_ID
echo $OSS_ACCESS_KEY_SECRET
# Windows CMD
echo %OSS_ACCESS_KEY_ID%
echo %OSS_ACCESS_KEY_SECRET%
# Windows PowerShell
echo $env:OSS_ACCESS_KEY_ID
echo $env:OSS_ACCESS_KEY_SECRET
```
### 2. 在应用程序中验证
在 Java 代码中临时添加日志:
```java
@PostConstruct
public void logOssConfig() {
log.info("OSS_ACCESS_KEY_ID: {}", System.getenv("OSS_ACCESS_KEY_ID"));
log.info("OSS_ACCESS_KEY_SECRET: {}",
System.getenv("OSS_ACCESS_KEY_SECRET") != null ? "已设置" : "未设置");
}
```
## 🚨 注意事项
1. **不要在版本控制中提交真实的密钥**
2. **定期轮换访问密钥**
3. **使用最小权限原则配置OSS权限**
4. **在生产环境中使用更安全的密钥管理方案**
5. **重启应用程序后环境变量才会生效**
## 📝 快速设置脚本
我已经为你准备了一个快速设置脚本,运行即可:
```bash
# 当前目录下创建 setup-env.sh
chmod +x setup-env.sh
./setup-env.sh
```
按照这个指南设置环境变量后你的OSS功能就可以正常使用了

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,727 @@
# WebSocket权限实时推送技术方案设计
## 📋 项目背景
在当前的权限管理系统中管理员修改用户权限后用户需要重新登录才能获得最新权限这严重影响了用户体验。为了解决这个问题我们设计了基于WebSocket的权限实时推送方案让权限变更能够立即生效。
## 🎯 整体架构设计
### 系统架构图
```
┌─────────────────┐ WebSocket ┌─────────────────┐ 数据库操作 ┌─────────────────┐
│ 前端应用 │ ←──────────→ │ Spring Boot │ ←──────────→ │ MySQL数据库 │
│ │ │ 后端服务 │ │ │
│ - 权限缓存 │ │ - WebSocket服务 │ │ - 用户权限表 │
│ - 实时更新 │ │ - 权限管理 │ │ - 权限变更记录 │
│ - 用户界面 │ │ - 消息推送 │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### 核心目标
- ✅ 权限变更实时生效,无需重新登录
- ✅ 支持多标签页同步更新
- ✅ 安全可靠的消息推送机制
- ✅ 良好的用户体验和性能表现
## 🔧 后端技术方案设计
### 1. WebSocket服务架构
#### 1.1 技术栈选择
```
Spring Boot + Spring WebSocket + SaToken + Redis + MySQL
```
#### 1.2 核心组件设计
```
┌── WebSocket管理层
│ ├── WebSocketConfig (配置)
│ ├── WebSocketHandler (连接处理)
│ └── WebSocketInterceptor (权限验证)
├── 权限推送服务层
│ ├── PermissionPushService (权限推送核心服务)
│ ├── UserSessionManager (用户会话管理)
│ └── MessageBroadcaster (消息广播器)
├── 权限监听层
│ ├── PermissionChangeListener (权限变更监听)
│ ├── RoleChangeListener (角色变更监听)
│ └── MenuChangeListener (菜单变更监听)
└── 数据存储层
├── Redis (会话存储 + 消息队列)
└── MySQL (权限数据 + 变更记录)
```
### 2. 核心服务设计
#### 2.1 WebSocket连接管理
```java
// 连接管理策略
- 用户登录后自动建立WebSocket连接
- 一个用户可以有多个连接(多标签页支持)
- 连接断开后自动重连机制
- 连接状态持久化到Redis
// 会话存储结构
Key: "websocket:user:{userId}"
Value: {
"connections": [
{
"sessionId": "session-123",
"connectTime": 1640995200000,
"lastHeartbeat": 1640995800000,
"browser": "Chrome",
"ip": "192.168.1.100"
}
]
}
```
#### 2.2 权限变更监听机制
```java
// 监听触发点
1. 用户角色分配/取消
2. 角色权限修改
3. 菜单权限调整
4. 用户状态变更
// 变更事件设计
@EventListener
public class PermissionChangeListener {
// 用户角色变更
@Async
public void handleUserRoleChange(UserRoleChangeEvent event)
// 角色权限变更
@Async
public void handleRolePermissionChange(RolePermissionChangeEvent event)
// 菜单权限变更
@Async
public void handleMenuPermissionChange(MenuPermissionChangeEvent event)
}
```
#### 2.3 消息推送策略
```java
// 消息类型设计
enum MessageType {
PERMISSION_UPDATE, // 权限更新
ROLE_CHANGE, // 角色变更
FORCE_LOGOUT, // 强制退出
SYSTEM_NOTICE // 系统通知
}
// 推送策略
1. 精准推送:只推送给受影响的用户
2. 批量推送:角色权限变更时推送给该角色的所有用户
3. 广播推送:系统级权限调整时推送给所有在线用户
```
### 3. 数据库设计扩展
#### 3.1 权限变更记录表
```sql
CREATE TABLE sys_permission_change_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
change_type VARCHAR(50) NOT NULL, -- USER_ROLE, ROLE_PERMISSION, MENU_PERMISSION
target_user_id BIGINT, -- 目标用户ID如果是用户级变更
target_role_id BIGINT, -- 目标角色ID如果是角色级变更
operator_id BIGINT NOT NULL, -- 操作者ID
change_detail JSON, -- 变更详情
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_time (target_user_id, create_time),
INDEX idx_role_time (target_role_id, create_time)
);
```
#### 3.2 WebSocket会话表
```sql
CREATE TABLE sys_websocket_session (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
session_id VARCHAR(100) NOT NULL UNIQUE,
connect_time DATETIME NOT NULL,
disconnect_time DATETIME,
client_info JSON, -- 客户端信息
status TINYINT DEFAULT 1, -- 1:连接中 0:已断开
INDEX idx_user_status (user_id, status)
);
```
### 4. 后端核心接口设计
#### 4.1 WebSocket端点配置
```java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new PermissionWebSocketHandler(), "/ws/permission")
.setAllowedOrigins("*")
.addInterceptors(new WebSocketAuthInterceptor());
}
}
```
#### 4.2 权限推送服务接口
```java
@Component
public class PermissionPushService {
/**
* 推送权限更新消息给指定用户
*/
public void pushPermissionUpdate(Long userId, List<String> newPermissions);
/**
* 推送角色变更消息给指定用户
*/
public void pushRoleChange(Long userId, List<String> newRoles);
/**
* 批量推送权限更新(角色权限变更时)
*/
public void batchPushPermissionUpdate(Long roleId, List<String> newPermissions);
/**
* 强制用户下线
*/
public void forceUserLogout(Long userId, String reason);
}
```
#### 4.3 消息格式定义
```java
@Data
public class WebSocketMessage {
private String type; // 消息类型
private Long userId; // 目标用户ID
private Long timestamp; // 时间戳
private Object data; // 消息数据
private String messageId; // 消息ID
private String operator; // 操作者
}
@Data
public class PermissionUpdateData {
private List<String> permissions; // 新权限列表
private List<String> roles; // 新角色列表
private String updateType; // 更新类型ADD, REMOVE, REPLACE
private String reason; // 变更原因
}
```
## 🌐 前端技术方案设计
### 1. WebSocket客户端架构
#### 1.1 技术栈
```
Vue3 + TypeScript + Pinia + WebSocket API
```
#### 1.2 组件设计
```
┌── WebSocket管理层
│ ├── WebSocketService (核心WebSocket服务)
│ ├── ConnectionManager (连接管理器)
│ └── MessageHandler (消息处理器)
├── 权限更新层
│ ├── PermissionUpdateService (权限更新服务)
│ ├── PermissionSyncManager (权限同步管理)
│ └── PermissionNotification (权限通知)
└── 用户界面层
├── PermissionUpdateNotify (权限更新提示组件)
├── ConnectionStatus (连接状态组件)
└── WebSocketDebugPanel (调试面板)
```
### 2. WebSocket服务设计
#### 2.1 连接管理策略
```typescript
class WebSocketService {
// 连接策略
- 用户登录成功后自动连接
- 连接断开后指数退避重连
- 页面可见性变化时管理连接
- 网络状态变化时重连
// 连接状态管理
enum ConnectionState {
DISCONNECTED = 'disconnected',
CONNECTING = 'connecting',
CONNECTED = 'connected',
RECONNECTING = 'reconnecting',
ERROR = 'error'
}
}
```
#### 2.2 消息处理机制
```typescript
// 消息类型定义
interface WebSocketMessage {
type: 'PERMISSION_UPDATE' | 'ROLE_CHANGE' | 'FORCE_LOGOUT' | 'SYSTEM_NOTICE'
userId: number
timestamp: number
data: any
messageId: string
}
// 消息处理器
class MessageHandler {
handlePermissionUpdate() // 处理权限更新
handleRoleChange() // 处理角色变更
handleForceLogout() // 处理强制退出
handleSystemNotice() // 处理系统通知
}
```
### 3. 权限同步机制
#### 3.1 同步策略
```typescript
class PermissionSyncManager {
// 同步时机
1. 收到权限更新消息时立即同步
2. 连接重建后检查权限版本
3. 页面激活时检查权限一致性
// 同步方式
async syncPermissions(updateType: string) {
// 1. 请求最新权限数据
// 2. 更新本地权限缓存
// 3. 触发UI重新渲染
// 4. 显示权限更新通知
}
}
```
#### 3.2 冲突处理
```typescript
// 权限冲突处理策略
1. 权限被收回立即隐藏相关UI显示权限不足提示
2. 权限被授予:立即显示新的功能按钮,显示权限获得提示
3. 强制退出:清理本地数据,跳转到登录页
4. 操作中断:保存用户操作状态,权限恢复后继续
```
### 4. 前端核心服务设计
#### 4.1 WebSocket服务接口
```typescript
export interface IWebSocketService {
// 连接管理
connect(): Promise<void>
disconnect(): void
reconnect(): Promise<void>
// 消息发送
sendMessage(message: WebSocketMessage): void
// 事件监听
onMessage(callback: (message: WebSocketMessage) => void): void
onConnected(callback: () => void): void
onDisconnected(callback: () => void): void
onError(callback: (error: Error) => void): void
// 状态查询
isConnected(): boolean
getConnectionState(): ConnectionState
}
```
#### 4.2 权限更新服务接口
```typescript
export interface IPermissionUpdateService {
// 权限同步
syncPermissions(): Promise<void>
updateLocalPermissions(permissions: string[]): void
// 通知管理
showPermissionUpdateNotification(updateInfo: PermissionUpdateInfo): void
showPermissionRevokedWarning(revokedPermissions: string[]): void
// 权限检查
checkPermissionChange(): Promise<boolean>
validateCurrentPermissions(): boolean
}
```
## 🔐 安全性设计
### 1. 连接安全
```java
// Token验证
- WebSocket握手时验证JWT Token
- 定期验证Token有效性
- Token过期时自动断开连接
// 权限验证
- 连接建立时验证用户权限
- 消息发送前验证操作权限
- 防止权限越权操作
// 示例WebSocket拦截器
@Component
public class WebSocketAuthInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map<String, Object> attributes) {
// 1. 提取Token
String token = extractTokenFromRequest(request);
// 2. 验证Token有效性
if (!saTokenUtil.isValidToken(token)) {
return false;
}
// 3. 获取用户信息
Long userId = saTokenUtil.getUserIdFromToken(token);
attributes.put("userId", userId);
return true;
}
}
```
### 2. 消息安全
```java
// 消息加密
- 敏感消息内容加密传输
- 消息完整性校验
- 防止消息重放攻击
// 频率限制
- 连接频率限制
- 消息发送频率限制
- 异常连接自动断开
// 示例:消息安全处理
@Component
public class MessageSecurityHandler {
public WebSocketMessage encryptMessage(WebSocketMessage message) {
// 对敏感数据进行加密
if (message.getType().equals("PERMISSION_UPDATE")) {
String encryptedData = aesUtil.encrypt(message.getData().toString());
message.setData(encryptedData);
}
return message;
}
public boolean validateMessageIntegrity(WebSocketMessage message) {
// 验证消息完整性
String expectedHash = calculateMessageHash(message);
return expectedHash.equals(message.getHash());
}
}
```
### 3. 访问控制
```java
// 用户隔离
- 确保用户只能接收自己的权限变更消息
- 防止跨用户信息泄露
- 管理员权限特殊处理
// 示例:消息权限验证
@Component
public class MessagePermissionValidator {
public boolean canReceiveMessage(Long userId, WebSocketMessage message) {
// 1. 检查消息是否发给该用户
if (!message.getUserId().equals(userId)) {
return false;
}
// 2. 检查用户是否有权限接收该类型消息
return hasPermissionToReceiveMessageType(userId, message.getType());
}
}
```
## 🚀 性能优化方案
### 1. 连接优化
```java
// 连接池管理
- 合理设置连接数上限
- 空闲连接自动清理
- 连接状态监控
// 内存优化
- 及时清理断开的连接
- 消息队列大小限制
- 定期清理过期数据
// 示例:连接池配置
@Configuration
public class WebSocketPoolConfig {
@Bean
public WebSocketConnectionPool connectionPool() {
return WebSocketConnectionPool.builder()
.maxConnections(10000) // 最大连接数
.maxConnectionsPerUser(5) // 每用户最大连接数
.idleTimeout(Duration.ofMinutes(30)) // 空闲超时
.cleanupInterval(Duration.ofMinutes(5)) // 清理间隔
.build();
}
}
```
### 2. 推送优化
```java
// 批量推送
- 相同类型消息合并推送
- 延迟推送策略
- 推送优先级管理
// 缓存优化
- Redis缓存权限数据
- 权限变更增量推送
- 本地权限缓存
// 示例:批量推送实现
@Component
public class BatchMessagePusher {
private final Map<String, List<WebSocketMessage>> messageBatches = new ConcurrentHashMap<>();
@Scheduled(fixedDelay = 1000) // 每秒批量推送一次
public void flushMessageBatches() {
messageBatches.forEach((batchKey, messages) -> {
WebSocketMessage batchMessage = mergeMess
ages(messages);
webSocketHandler.broadcast(batchMessage);
});
messageBatches.clear();
}
}
```
### 3. 数据库优化
```sql
-- 权限查询优化
CREATE INDEX idx_user_permission_version ON sys_login_user(user_id, permission_version);
CREATE INDEX idx_role_permission_update ON sys_role_menu(role_id, update_time);
-- 会话查询优化
CREATE INDEX idx_websocket_user_status ON sys_websocket_session(user_id, status, connect_time);
-- 变更日志查询优化
CREATE INDEX idx_permission_log_target ON sys_permission_change_log(target_user_id, target_role_id, create_time);
```
## 📊 监控和日志
### 1. 连接监控
```java
// 监控指标
- 当前连接数
- 连接成功率
- 连接断开原因统计
- 消息推送成功率
// 示例:监控服务
@Component
public class WebSocketMonitorService {
private final MeterRegistry meterRegistry;
public void recordConnection(String result) {
Counter.builder("websocket.connections")
.tag("result", result)
.register(meterRegistry)
.increment();
}
public void recordMessagePush(String type, String result) {
Counter.builder("websocket.messages")
.tag("type", type)
.tag("result", result)
.register(meterRegistry)
.increment();
}
}
```
### 2. 错误处理和日志
```java
// 日志记录
- 连接建立/断开日志
- 消息推送日志
- 错误异常日志
- 性能统计日志
// 示例:日志配置
@Slf4j
@Component
public class WebSocketLogger {
public void logConnection(Long userId, String action, String result) {
log.info("WebSocket连接 - 用户:{}, 操作:{}, 结果:{}", userId, action, result);
}
public void logMessagePush(Long userId, String messageType, String result) {
log.info("消息推送 - 用户:{}, 类型:{}, 结果:{}", userId, messageType, result);
}
public void logError(String operation, Exception e) {
log.error("WebSocket错误 - 操作:{}, 异常:", operation, e);
}
}
```
## 📋 实施步骤
### 第一阶段基础WebSocket服务1-2周
1. **后端WebSocket服务搭建**
- 创建WebSocket配置类
- 实现WebSocket处理器
- 添加权限验证拦截器
- 基础连接管理功能
2. **前端WebSocket客户端**
- 创建WebSocket服务类
- 实现连接管理逻辑
- 添加重连机制
- 基础消息收发功能
3. **基础测试**
- 连接建立测试
- 消息收发测试
- 断线重连测试
### 第二阶段权限推送核心功能2-3周
1. **权限变更监听**
- 实现用户角色变更监听
- 实现角色权限变更监听
- 实现菜单权限变更监听
- 创建权限变更事件
2. **消息推送逻辑**
- 实现权限更新消息推送
- 实现角色变更消息推送
- 实现批量推送逻辑
- 添加消息去重机制
3. **前端权限同步**
- 实现权限数据更新
- 实现UI实时刷新
- 添加权限变更通知
- 处理权限冲突场景
### 第三阶段高级功能和优化2-3周
1. **安全性增强**
- 添加消息加密
- 实现访问控制
- 添加频率限制
- 防止攻击机制
2. **性能优化**
- 实现连接池管理
- 添加消息批量处理
- 优化数据库查询
- 添加缓存机制
3. **用户体验优化**
- 完善重连策略
- 优化通知交互
- 添加调试面板
- 处理边界情况
### 第四阶段生产环境适配1-2周
1. **集群部署支持**
- Redis消息队列
- 负载均衡配置
- 会话共享机制
2. **监控和运维**
- 添加监控指标
- 完善日志记录
- 配置告警机制
- 制定运维手册
3. **测试和部署**
- 压力测试
- 兼容性测试
- 生产环境部署
- 回滚方案准备
## 🔍 风险评估和应对方案
### 1. 技术风险
| 风险项 | 影响度 | 概率 | 应对方案 |
|--------|--------|------|----------|
| WebSocket连接不稳定 | 高 | 中 | 完善重连机制,降级到轮询 |
| 消息推送延迟 | 中 | 低 | 优化推送逻辑,添加超时机制 |
| 内存泄漏 | 高 | 低 | 定期清理,添加监控 |
| 安全漏洞 | 高 | 低 | 安全审计,权限校验 |
### 2. 业务风险
| 风险项 | 影响度 | 概率 | 应对方案 |
|--------|--------|------|----------|
| 权限同步失败 | 高 | 中 | 手动刷新机制,错误提示 |
| 用户体验下降 | 中 | 低 | 渐进式升级,用户反馈 |
| 系统复杂度增加 | 中 | 高 | 完善文档,团队培训 |
### 3. 运维风险
| 风险项 | 影响度 | 概率 | 应对方案 |
|--------|--------|------|----------|
| 服务器压力增加 | 中 | 中 | 性能监控,扩容预案 |
| 故障排查困难 | 中 | 中 | 详细日志,监控告警 |
| 部署复杂度增加 | 低 | 高 | 自动化部署,回滚机制 |
## 📈 预期效果
### 1. 用户体验提升
- ✅ 权限变更即时生效,无需重新登录
- ✅ 多标签页权限状态同步
- ✅ 清晰的权限变更通知
### 2. 系统性能
- ✅ 减少不必要的接口调用
- ✅ 提高权限检查效率
- ✅ 降低服务器负载
### 3. 管理效率
- ✅ 权限管理操作即时生效
- ✅ 减少用户投诉和支持工作
- ✅ 提高系统管理效率
## 📚 相关技术文档
1. [Spring WebSocket官方文档](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket)
2. [Vue3 WebSocket最佳实践](https://vuejs.org/guide/extras/web-components.html)
3. [SaToken权限认证文档](https://sa-token.dev33.cn/)
4. [Redis消息队列使用指南](https://redis.io/docs/manual/pubsub/)
## 👥 团队分工建议
| 角色 | 职责 | 时间投入 |
|------|------|----------|
| 后端开发 | WebSocket服务、权限监听、消息推送 | 60% |
| 前端开发 | WebSocket客户端、权限同步、UI更新 | 40% |
| 测试工程师 | 功能测试、性能测试、安全测试 | 全程参与 |
| 运维工程师 | 部署配置、监控告警、性能调优 | 后期参与 |
---
**文档版本**: v1.0
**创建时间**: 2025-01-07
**更新时间**: 2025-01-07
**负责人**: 系统架构团队
**审核人**: 技术负责人

242
script/README.md Normal file
View File

@ -0,0 +1,242 @@
# 项目模板重构脚本
这是一个用于将 `heritage-backend` 项目重构为新项目模板的自动化脚本。
## 功能特性
🚀 **全自动重构**:一键完成整个项目的重构过程
📁 **目录重命名**:自动重命名所有模块目录和子模块目录
📦 **Maven配置**:更新所有 pom.xml 文件中的 groupId、artifactId 和模块名
**Java包结构**:重构所有 Java 文件的包名和 import 语句
⚙️ **配置文件**:更新所有配置文件中的包名引用
🗄️ **数据库配置**:重命名 SQL 文件和更新数据库名引用
🔄 **多层级支持**:支持插件模块等多层级子模块结构
## 使用方法
### 1. 进入脚本目录
```bash
cd /Users/leocoder/leocoder/develop/frameworks/heritage/heritage-backend/script
```
### 2. 执行重构脚本
```bash
./project-template-refactor.sh
```
### 3. 按提示输入新项目信息
脚本会提示你输入以下信息:
- **新项目名称**:例如 `my-project-backend`
- **新的GroupId**:例如 `com.company.project`
- **新的包名**:例如 `com.company.project`
- **新的模块前缀**:例如 `my-project`
- **新的数据库名**:例如 `my_project_db`
### 4. 确认配置并开始重构
脚本会显示所有配置信息供你确认,输入 `y` 开始重构。
## 重构内容详解
### 📁 目录结构重构
**原始结构:**
```
/parent-directory/
└── heritage-backend/ ← 项目根目录
├── heritage-web/
├── heritage-common/
├── heritage-model/
├── heritage-mybatisplus/
├── heritage-modules/
├── heritage-plugins/
│ ├── heritage-easyexcel/
│ ├── heritage-oss/
│ ├── heritage-sa-token/
│ └── ...其他插件模块
└── sql/
└── heritage.sql
```
**重构后结构(以 my-project-backend 为例):**
```
/parent-directory/
├── heritage-backend_backup_* ← 自动创建的备份
└── my-project-backend/ ← 重命名后的项目根目录
├── my-project-web/
├── my-project-common/
├── my-project-model/
├── my-project-mybatisplus/
├── my-project-modules/
├── my-project-plugins/
│ ├── my-project-easyexcel/
│ ├── my-project-oss/
│ ├── my-project-sa-token/
│ └── ...其他插件模块
└── sql/
└── my_project_db.sql
```
**v1.2.0 新特性**:脚本现在会自动重命名项目根目录,无需手动操作!
### 📦 Maven 配置重构
- **groupId**`org.leocoder.heritage` → `com.company.project`
- **artifactId**`heritage-*` → `my-project-*`
- **name**:相应模块名称更新
- **依赖引用**:所有模块间依赖的 groupId 和 artifactId
### ☕ Java 包结构重构
- **包声明**`package org.leocoder.heritage.*` → `package com.company.project.*`
- **import语句**`import org.leocoder.heritage.*` → `import com.company.project.*`
- **目录结构**`src/main/java/org.leocoder.heritage/` → `src/main/java/com/company/project/`
### ⚙️ 配置文件重构
- **application.yml / application-dev.yml / application-local.yml**
- `packages-to-scan: org.leocoder.heritage``packages-to-scan: com.company.project`
- `projectName: CORDER-ADMIN-THIN``projectName: MY-PROJECT-ADMIN`
- `pool-name: CORDER-HIKARI-DEV``pool-name: MY-PROJECT-HIKARI-DEV`
- `name: coder-web``name: my-project-web`
- `jdbc:mysql://localhost:3306/heritage``jdbc:mysql://localhost:3306/new-db-name`
- `jdbc:mysql://localhost:3306/heritage-backup``jdbc:mysql://localhost:3306/new-db-name-backup`
- `filePath: /path/heritage-backend/``filePath: /path/new-project-backend/`
- **logback配置文件**logback-spring*.xml
- `<contextName>heritage-logback</contextName>``<contextName>my-project-logback</contextName>`
- `<property name="CORDER_ADMIN_LOGS" value="./logs"/>``<property name="MY_PROJECT_ADMIN_LOGS" value="./logs"/>`
- 所有日志路径引用:`${CORDER_ADMIN_LOGS}` → `${MY_PROJECT_ADMIN_LOGS}`
- **其他配置文件**:包名和项目相关引用更新
### 🗄️ SQL 文件重构
- **文件重命名**`heritage.sql` → `my_project_db.sql`
- **数据库名更新**SQL文件中的数据库名引用更新
## 安全特性
### 🔒 自动备份
脚本执行前会自动创建项目备份:
```
heritage-backend_backup_20250922_143022/
```
### ✅ 验证检查
重构完成后自动验证是否还有旧的引用残留
### 🚫 错误处理
遇到错误时立即停止执行,保护项目完整性
## 重构后操作
重构完成后请执行以下步骤:
### 1. 验证编译
```bash
mvn clean compile
```
### 2. 更新数据库配置
手动检查并更新以下配置文件中的数据库连接信息:
- `*/src/main/resources/application-dev.yml`
- `*/src/main/resources/application-local.yml`
### 3. 验证功能
- 启动应用程序验证功能正常
- 检查所有插件模块是否正常加载
### 4. IDE配置
- 重新导入Maven项目
- 检查项目结构和依赖
### 5. Git管理
```bash
git add .
git commit -m "refactor: 重构项目为新模板"
```
## 注意事项
⚠️ **重要提醒:**
1. 重构前请确保代码已提交到Git仓库
2. 脚本会自动创建备份,但建议额外手动备份重要数据
3. 重构过程不可逆,请谨慎操作
4. 重构后需要手动更新数据库连接配置
5. 如果项目中有自定义的特殊配置,可能需要手动调整
## 故障排除
### 常见问题
1. **权限错误**:确保脚本有可执行权限 `chmod +x project-template-refactor.sh`
2. **路径错误**:确保在正确的项目根目录下执行脚本
3. **Maven错误**:重构后如果编译失败,检查依赖配置是否正确
4. **包名冲突**:如果新包名已存在,选择不同的包名
5. **Bash版本兼容性**脚本已修复了bash版本兼容性问题
6. **包名未正确替换**:使用修复脚本 `./fix-existing-project.sh`
7. **多余src目录**:使用修复脚本自动清理
### 项目修复工具
#### 1. 快速修复目录名反斜杠问题
如果目录名中出现反斜杠(如 `leocoder\`
```bash
./quick-fix-backslash.sh
```
#### 2. 修复遗漏的modules子模块
如果modules目录下的子模块没有被重命名
```bash
./fix-missing-modules.sh
```
#### 3. 修复已有问题的项目
如果重构过程中出现包名未正确替换或目录结构问题:
```bash
./fix-existing-project.sh
```
#### 4. 检查项目状态
检查项目重构后的整体状态:
```bash
./project-status-check.sh
```
#### 5. 演示logback配置处理
查看logback配置处理效果
```bash
./logback-demo.sh
```
#### 6. 测试反斜杠修复功能
验证反斜杠问题修复效果:
```bash
./test-backslash-fix.sh
```
#### 7. 测试配置文件修复功能
验证数据库连接字符串和文件路径配置修复效果:
```bash
./test-config-fix.sh
```
### 回滚操作
如果重构出现问题,可以:
1. 删除重构后的项目目录
2. 从备份目录恢复:`cp -r heritage-backend_backup_* heritage-backend`
3. 或使用修复脚本进行增量修复
### 脚本版本信息
- **v1.0.0**:初始版本
- **v1.1.0**修复bash兼容性问题改进Java包结构迁移逻辑增加修复工具
- **v1.1.1**修复sed正则表达式导致的目录名反斜杠问题增加快速修复工具
- **v1.2.0****重大更新** - 完善反斜杠自动清理,增加项目根目录自动重命名功能
- **v1.2.1****关键修复** - 彻底解决反斜杠问题增强MyBatis XML处理改进验证逻辑
- **v1.2.2****模块修复** - 修复modules子模块重命名遗漏问题支持多层级子模块处理
- **v1.2.3****配置修复** - 修复数据库连接字符串和文件路径配置未更新问题
## 技术支持
如果在使用过程中遇到问题,请:
1. 检查脚本执行日志
2. 查看备份目录是否完整
3. 联系开发团队获取支持
---
**版本**v1.2.3
**作者**Leocoder
**更新时间**2025-09-22

View File

@ -0,0 +1,567 @@
#!/bin/bash
#=============================================================================
# 项目模板重构脚本
# 用于将coder-common-thin-backend项目重构为新的项目模板
#
# 功能:
# 1. 重构项目名称和模块名
# 2. 修改包名和groupId
# 3. 更新配置文件中的包名引用
# 4. 重命名SQL文件和数据库名
# 5. 处理多层级子模块结构
# 6. 更新数据库连接字符串
# 7. 更新文件路径配置
#
# 作者: Leocoder
# 版本: 1.2.3
#=============================================================================
set -e # 遇到错误立即退出
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# 当前项目信息
CURRENT_PROJECT_NAME="coder-common-thin-backend"
CURRENT_GROUP_ID="org.leocoder.thin"
CURRENT_PACKAGE_NAME="org.leocoder.thin"
CURRENT_MODULE_PREFIX="coder-common-thin"
CURRENT_DB_NAME="coder-common-thin"
# 脚本目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
log_info "=== 项目模板重构脚本 v1.2.3 ==="
log_info "项目根目录: $PROJECT_ROOT"
# 验证当前目录
if [[ ! -f "$PROJECT_ROOT/pom.xml" ]]; then
log_error "当前目录不是有效的Maven项目根目录"
exit 1
fi
# 获取用户输入
echo ""
log_step "请输入新项目的配置信息:"
read -p "新项目名称 (例如: my-project-backend): " NEW_PROJECT_NAME
read -p "新的GroupId (例如: com.company.project): " NEW_GROUP_ID
read -p "新的包名 (例如: com.company.project): " NEW_PACKAGE_NAME
read -p "新的模块前缀 (例如: my-project): " NEW_MODULE_PREFIX
read -p "新的数据库名 (例如: my_project_db): " NEW_DB_NAME
# 验证输入
if [[ -z "$NEW_PROJECT_NAME" || -z "$NEW_GROUP_ID" || -z "$NEW_PACKAGE_NAME" || -z "$NEW_MODULE_PREFIX" || -z "$NEW_DB_NAME" ]]; then
log_error "所有参数都不能为空"
exit 1
fi
# 显示配置信息确认
echo ""
log_info "=== 重构配置确认 ==="
echo "项目名称: $CURRENT_PROJECT_NAME -> $NEW_PROJECT_NAME"
echo "GroupId: $CURRENT_GROUP_ID -> $NEW_GROUP_ID"
echo "包名: $CURRENT_PACKAGE_NAME -> $NEW_PACKAGE_NAME"
echo "模块前缀: $CURRENT_MODULE_PREFIX -> $NEW_MODULE_PREFIX"
echo "数据库名: $CURRENT_DB_NAME -> $NEW_DB_NAME"
echo ""
read -p "确认开始重构?[y/N]: " CONFIRM
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
log_info "重构已取消"
exit 0
fi
# 备份项目
BACKUP_DIR="${PROJECT_ROOT}_backup_$(date +%Y%m%d_%H%M%S)"
log_step "创建项目备份: $BACKUP_DIR"
cp -r "$PROJECT_ROOT" "$BACKUP_DIR"
log_info "备份完成"
# 重构函数
# 1. 重命名模块目录
rename_module_directories() {
log_step "重命名模块目录..."
cd "$PROJECT_ROOT"
# 主模块重命名
local modules=(
"$CURRENT_MODULE_PREFIX-web"
"$CURRENT_MODULE_PREFIX-common"
"$CURRENT_MODULE_PREFIX-model"
"$CURRENT_MODULE_PREFIX-mybatisplus"
"$CURRENT_MODULE_PREFIX-modules"
"$CURRENT_MODULE_PREFIX-plugins"
)
for module in "${modules[@]}"; do
if [[ -d "$module" ]]; then
new_module="${module/$CURRENT_MODULE_PREFIX/$NEW_MODULE_PREFIX}"
log_info "重命名: $module -> $new_module"
mv "$module" "$new_module"
fi
done
# 递归重命名所有子模块modules、plugins等
local parent_modules=(
"$NEW_MODULE_PREFIX-modules"
"$NEW_MODULE_PREFIX-plugins"
)
for parent_module in "${parent_modules[@]}"; do
if [[ -d "$parent_module" ]]; then
log_info "处理子模块目录: $parent_module"
cd "$parent_module"
# 重命名当前目录下的所有子模块
for sub_dir in $CURRENT_MODULE_PREFIX-*; do
if [[ -d "$sub_dir" ]]; then
new_sub_dir="${sub_dir/$CURRENT_MODULE_PREFIX/$NEW_MODULE_PREFIX}"
log_info "重命名子模块: $sub_dir -> $new_sub_dir"
mv "$sub_dir" "$new_sub_dir"
# 递归处理子模块中的子子模块(如果存在)
if [[ -d "$new_sub_dir" ]]; then
cd "$new_sub_dir"
for sub_sub_dir in $CURRENT_MODULE_PREFIX-*; do
if [[ -d "$sub_sub_dir" ]]; then
new_sub_sub_dir="${sub_sub_dir/$CURRENT_MODULE_PREFIX/$NEW_MODULE_PREFIX}"
log_info "重命名子子模块: $sub_sub_dir -> $new_sub_sub_dir"
mv "$sub_sub_dir" "$new_sub_sub_dir"
fi
done
cd ".."
fi
fi
done
cd "$PROJECT_ROOT"
fi
done
log_info "模块目录重命名完成"
}
# 2. 更新POM文件
update_pom_files() {
log_step "更新POM文件..."
# 查找所有pom.xml文件
find "$PROJECT_ROOT" -name "pom.xml" -type f | while read pom_file; do
log_info "更新POM: $pom_file"
# 使用sed进行替换
sed -i.bak \
-e "s|<groupId>$CURRENT_GROUP_ID</groupId>|<groupId>$NEW_GROUP_ID</groupId>|g" \
-e "s|<artifactId>$CURRENT_PROJECT_NAME</artifactId>|<artifactId>$NEW_PROJECT_NAME</artifactId>|g" \
-e "s|<name>$CURRENT_PROJECT_NAME</name>|<name>$NEW_PROJECT_NAME</name>|g" \
-e "s|$CURRENT_MODULE_PREFIX-|$NEW_MODULE_PREFIX-|g" \
"$pom_file"
# 删除备份文件
rm -f "${pom_file}.bak"
done
log_info "POM文件更新完成"
}
# 3. 更新Java包结构
update_java_packages() {
log_step "更新Java包结构..."
# 转换包名路径
local current_package_path="${CURRENT_PACKAGE_NAME//./\/}"
local new_package_path="${NEW_PACKAGE_NAME//./\/}"
log_info "包路径转换: $current_package_path -> $new_package_path"
# 查找所有Java文件并更新包名和import语句
find "$PROJECT_ROOT" -name "*.java" -type f | while read java_file; do
log_info "更新Java文件内容: $java_file"
# 更新package声明和import语句使用更精确的匹配避免转义字符问题
sed -i.bak \
-e "s|^package ${CURRENT_PACKAGE_NAME};|package ${NEW_PACKAGE_NAME};|g" \
-e "s|^package ${CURRENT_PACKAGE_NAME}[.]|package ${NEW_PACKAGE_NAME}.|g" \
-e "s|^import ${CURRENT_PACKAGE_NAME};|import ${NEW_PACKAGE_NAME};|g" \
-e "s|^import ${CURRENT_PACKAGE_NAME}[.]|import ${NEW_PACKAGE_NAME}.|g" \
-e "s|${CURRENT_PACKAGE_NAME}[.]|${NEW_PACKAGE_NAME}.|g" \
"$java_file"
rm -f "${java_file}.bak"
done
# 重构Java包目录结构
log_info "开始重构Java包目录结构..."
# 查找所有包含当前包路径的java源码目录
find "$PROJECT_ROOT" -path "*/src/main/java/$current_package_path" -type d | while read old_package_dir; do
log_info "处理包目录: $old_package_dir"
# 获取java源码根目录
local java_root="${old_package_dir%/$current_package_path}"
local new_package_dir="$java_root/$new_package_path"
log_info " 源目录: $old_package_dir"
log_info " 目标目录: $new_package_dir"
# 创建新的包目录结构
mkdir -p "$(dirname "$new_package_dir")"
# 移动整个包目录到新位置
if [[ -d "$old_package_dir" && "$old_package_dir" != "$new_package_dir" ]]; then
mv "$old_package_dir" "$new_package_dir"
log_info " 已移动包目录"
# 清理空的父级目录
local old_parent="$(dirname "$old_package_dir")"
while [[ "$old_parent" != "$java_root" && -d "$old_parent" ]]; do
if [[ -z "$(ls -A "$old_parent" 2>/dev/null)" ]]; then
rmdir "$old_parent" 2>/dev/null && log_info " 清理空目录: $old_parent"
old_parent="$(dirname "$old_parent")"
else
break
fi
done
fi
done
log_info "Java包结构更新完成"
}
# 4. 清理目录名中的反斜杠(独立函数)
cleanup_backslash_directories() {
log_step "清理目录名中的反斜杠..."
local fixed_count=0
local attempts=0
# 重复检查和修复,直到没有反斜杠目录或达到最大尝试次数
while [[ $attempts -lt 5 ]]; do
local found_backslash=false
# 查找包含反斜杠的目录
for dir_path in $(find "$PROJECT_ROOT" -path "*/src/main/java/*" -type d 2>/dev/null); do
if [[ ! -d "$dir_path" ]]; then
continue
fi
dir_name=$(basename "$dir_path")
# 检查目录名是否包含反斜杠
if [[ "$dir_name" == *"\\"* ]]; then
found_backslash=true
# 清理反斜杠,获取正确的目录名
clean_dir_name="${dir_name//\\/}"
parent_dir=$(dirname "$dir_path")
new_dir_path="$parent_dir/$clean_dir_name"
log_info "修复目录名: '$dir_name' -> '$clean_dir_name'"
# 重命名目录
if [[ "$dir_path" != "$new_dir_path" && ! -e "$new_dir_path" ]]; then
if mv "$dir_path" "$new_dir_path" 2>/dev/null; then
log_info " ✓ 目录重命名成功: $(basename "$new_dir_path")"
((fixed_count++))
else
log_warn " ✗ 目录重命名失败: $dir_path"
fi
fi
fi
done
# 如果没有找到反斜杠目录,退出循环
if [[ "$found_backslash" == false ]]; then
break
fi
((attempts++))
sleep 0.1 # 短暂等待,确保文件系统操作完成
done
if [[ $fixed_count -gt 0 ]]; then
log_info "总共修复了 $fixed_count 个包含反斜杠的目录(用了 $attempts 轮处理)"
else
log_info "未发现包含反斜杠的目录"
fi
log_info "反斜杠清理完成"
}
# 5. 更新配置文件
update_config_files() {
log_step "更新配置文件..."
# 更新application配置文件
find "$PROJECT_ROOT" -name "application*.yml" -o -name "application*.yaml" -o -name "application*.properties" | while read config_file; do
log_info "更新配置文件: $config_file"
# 计算新的配置名称兼容各种bash版本
local new_pool_name="$(echo "$NEW_MODULE_PREFIX" | tr '[:lower:]' '[:upper:]')-HIKARI-DEV"
local new_project_name="$(echo "$NEW_MODULE_PREFIX" | tr '[:lower:]' '[:upper:]')-ADMIN"
sed -i.bak \
-e "s|packages-to-scan: $CURRENT_PACKAGE_NAME|packages-to-scan: $NEW_PACKAGE_NAME|g" \
-e "s|$CURRENT_PACKAGE_NAME|$NEW_PACKAGE_NAME|g" \
-e "s|name: coder-web|name: ${NEW_MODULE_PREFIX}-web|g" \
-e "s|projectName: CORDER-ADMIN-THIN|projectName: $new_project_name|g" \
-e "s|pool-name: CORDER-HIKARI-DEV|pool-name: $new_pool_name|g" \
-e "s|jdbc:mysql://localhost:3306/$CURRENT_DB_NAME|jdbc:mysql://localhost:3306/$NEW_DB_NAME|g" \
-e "s|jdbc:mysql://localhost:3306/$CURRENT_DB_NAME-backup|jdbc:mysql://localhost:3306/$NEW_DB_NAME-backup|g" \
-e "s|/$CURRENT_PROJECT_NAME/|/$NEW_PROJECT_NAME/|g" \
-e "s|$CURRENT_MODULE_PREFIX|$NEW_MODULE_PREFIX|g" \
"$config_file"
rm -f "${config_file}.bak"
done
# 更新logback配置文件专门处理logback特有的配置
find "$PROJECT_ROOT" -name "logback*.xml" | while read logback_file; do
log_info "更新Logback配置: $logback_file"
# 计算新的日志配置名称兼容各种bash版本
local new_context_name="${NEW_MODULE_PREFIX}-logback"
local new_property_name="$(echo "$NEW_MODULE_PREFIX" | tr '[:lower:]' '[:upper:]')_ADMIN_LOGS"
sed -i.bak \
-e "s|$CURRENT_PACKAGE_NAME|$NEW_PACKAGE_NAME|g" \
-e "s|<contextName>$CURRENT_MODULE_PREFIX-logback</contextName>|<contextName>$new_context_name</contextName>|g" \
-e "s|CORDER_ADMIN_LOGS|$new_property_name|g" \
-e "s|name=\"CORDER_ADMIN_LOGS\"|name=\"$new_property_name\"|g" \
"$logback_file"
rm -f "${logback_file}.bak"
done
# 更新MyBatis XML文件处理namespace和resultType
find "$PROJECT_ROOT" -name "*.xml" -path "*/mapper/*" | while read mapper_file; do
log_info "更新MyBatis配置: $mapper_file"
sed -i.bak \
-e "s|namespace=\"$CURRENT_PACKAGE_NAME|namespace=\"$NEW_PACKAGE_NAME|g" \
-e "s|resultType=\"$CURRENT_PACKAGE_NAME|resultType=\"$NEW_PACKAGE_NAME|g" \
-e "s|parameterType=\"$CURRENT_PACKAGE_NAME|parameterType=\"$NEW_PACKAGE_NAME|g" \
"$mapper_file"
rm -f "${mapper_file}.bak"
done
log_info "配置文件更新完成"
}
# 6. 更新SQL文件
update_sql_files() {
log_step "更新SQL文件..."
local sql_dir="$PROJECT_ROOT/sql"
if [[ -d "$sql_dir" ]]; then
# 重命名SQL文件
local current_sql_file="$sql_dir/$CURRENT_DB_NAME.sql"
local new_sql_file="$sql_dir/$NEW_DB_NAME.sql"
if [[ -f "$current_sql_file" ]]; then
log_info "重命名SQL文件: $CURRENT_DB_NAME.sql -> $NEW_DB_NAME.sql"
mv "$current_sql_file" "$new_sql_file"
# 更新SQL文件内容中的数据库名引用
sed -i.bak \
-e "s|Source Schema.*: $CURRENT_DB_NAME|Source Schema : $NEW_DB_NAME|g" \
-e "s|$CURRENT_DB_NAME|$NEW_DB_NAME|g" \
"$new_sql_file"
rm -f "${new_sql_file}.bak"
fi
fi
log_info "SQL文件更新完成"
}
# 7. 更新其他文件
update_other_files() {
log_step "更新其他相关文件..."
# 更新README文件
find "$PROJECT_ROOT" -name "README*" -type f | while read readme_file; do
log_info "更新README: $readme_file"
sed -i.bak \
-e "s|$CURRENT_PROJECT_NAME|$NEW_PROJECT_NAME|g" \
-e "s|$CURRENT_MODULE_PREFIX|$NEW_MODULE_PREFIX|g" \
-e "s|$CURRENT_PACKAGE_NAME|$NEW_PACKAGE_NAME|g" \
"$readme_file"
rm -f "${readme_file}.bak"
done
# 更新CLAUDE.md文件
if [[ -f "$PROJECT_ROOT/CLAUDE.md" ]]; then
log_info "更新CLAUDE.md"
sed -i.bak \
-e "s|$CURRENT_PROJECT_NAME|$NEW_PROJECT_NAME|g" \
-e "s|$CURRENT_MODULE_PREFIX|$NEW_MODULE_PREFIX|g" \
-e "s|$CURRENT_PACKAGE_NAME|$NEW_PACKAGE_NAME|g" \
"$PROJECT_ROOT/CLAUDE.md"
rm -f "$PROJECT_ROOT/CLAUDE.md.bak"
fi
# 更新其他配置文件(如果存在)
find "$PROJECT_ROOT" -name "*.properties" -o -name "*.yml" -o -name "*.yaml" | while read prop_file; do
if [[ "$prop_file" != *"application"* ]]; then
log_info "更新属性文件: $prop_file"
sed -i.bak \
-e "s|$CURRENT_PACKAGE_NAME|$NEW_PACKAGE_NAME|g" \
-e "s|$CURRENT_MODULE_PREFIX|$NEW_MODULE_PREFIX|g" \
"$prop_file"
rm -f "${prop_file}.bak"
fi
done
log_info "其他文件更新完成"
}
# 8. 清理和验证
cleanup_and_verify() {
log_step "清理临时文件和验证..."
# 清理备份文件
find "$PROJECT_ROOT" -name "*.bak" -delete
# 验证重构结果
log_info "验证重构结果..."
# 检查是否还有旧的引用(排除脚本文件、日志文件、备份等)
local old_refs=$(grep -r "$CURRENT_PACKAGE_NAME" "$PROJECT_ROOT" \
--exclude-dir=".git" \
--exclude-dir="target" \
--exclude-dir=".idea" \
--exclude-dir="script" \
--exclude="*.log" \
--exclude="spy.log" \
--exclude="*.bak" \
--exclude="*.sh" \
2>/dev/null | wc -l)
if [[ $old_refs -gt 0 ]]; then
log_warn "发现 $old_refs 处旧包名引用,请手动检查"
grep -r "$CURRENT_PACKAGE_NAME" "$PROJECT_ROOT" \
--exclude-dir=".git" \
--exclude-dir="target" \
--exclude-dir=".idea" \
--exclude-dir="script" \
--exclude="*.log" \
--exclude="spy.log" \
--exclude="*.bak" \
--exclude="*.sh" \
2>/dev/null | head -10
else
log_info "✓ 验证通过:未发现旧包名引用"
fi
log_info "清理完成"
}
# 9. 重命名项目根目录
rename_project_directory() {
log_step "重命名项目根目录..."
# 获取当前项目目录名
local current_dir_name=$(basename "$PROJECT_ROOT")
local parent_dir=$(dirname "$PROJECT_ROOT")
local new_project_dir="$parent_dir/$NEW_PROJECT_NAME"
log_info "项目目录重命名: $current_dir_name -> $NEW_PROJECT_NAME"
# 检查目标目录是否已存在
if [[ -d "$new_project_dir" && "$PROJECT_ROOT" != "$new_project_dir" ]]; then
log_warn "目标目录已存在: $new_project_dir"
log_warn "跳过项目目录重命名"
return
fi
# 重命名项目目录
if [[ "$PROJECT_ROOT" != "$new_project_dir" ]]; then
log_info "移动项目目录: $PROJECT_ROOT -> $new_project_dir"
# 切换到父目录
cd "$parent_dir"
# 重命名目录
if mv "$current_dir_name" "$NEW_PROJECT_NAME" 2>/dev/null; then
log_info "✓ 项目目录重命名成功"
log_info "新的项目路径: $new_project_dir"
# 更新PROJECT_ROOT变量为新路径
PROJECT_ROOT="$new_project_dir"
else
log_warn "✗ 项目目录重命名失败,请手动重命名"
fi
else
log_info "项目目录名已正确,无需重命名"
fi
log_info "项目目录处理完成"
}
# 执行重构
echo ""
log_info "=== 开始执行重构 ==="
rename_module_directories
update_pom_files
update_java_packages
cleanup_backslash_directories
update_config_files
update_sql_files
update_other_files
cleanup_and_verify
rename_project_directory
echo ""
log_info "=== 重构完成 ==="
log_info "新项目名称: $NEW_PROJECT_NAME"
log_info "新的GroupId: $NEW_GROUP_ID"
log_info "新的包名: $NEW_PACKAGE_NAME"
log_info "新的模块前缀: $NEW_MODULE_PREFIX"
log_info "新的数据库名: $NEW_DB_NAME"
echo ""
log_info "项目备份位置: $BACKUP_DIR"
log_info "新项目位置: $PROJECT_ROOT"
echo ""
log_warn "重构完成后请执行以下操作:"
echo "1. 进入新项目目录: cd \"$PROJECT_ROOT\""
echo "2. 验证项目能否正常编译: mvn clean compile"
echo "3. 更新数据库连接配置中的数据库名"
echo "4. 在IDE中重新导入项目检查包结构"
echo "5. 提交代码到新的Git仓库"
echo ""
log_info "感谢使用项目模板重构脚本!"