批量更新
This commit is contained in:
@@ -1,203 +0,0 @@
|
||||
# 文件上传功能文档
|
||||
|
||||
## 概述
|
||||
|
||||
文件上传功能已完整实现,支持将文件保存到本地文件系统并记录到数据库中。
|
||||
|
||||
## 功能特性
|
||||
|
||||
1. **自动目录管理**:按年月日自动创建目录结构(如 `front/uploads/2024/01/15/`)
|
||||
2. **唯一文件名**:使用时间戳生成唯一文件名,避免重名冲突
|
||||
3. **文件类型识别**:自动识别文件类型(图片、文档、视频、音频、压缩包等)
|
||||
4. **数据库记录**:所有文件信息保存到 `yz_files` 表
|
||||
5. **用户关联**:自动关联当前登录用户
|
||||
6. **异常处理**:文件保存失败时自动清理已上传的文件
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
front/
|
||||
└── uploads/
|
||||
├── 2024/
|
||||
│ ├── 01/
|
||||
│ │ ├── 15/
|
||||
│ │ │ ├── 20240115143045_example.jpg
|
||||
│ │ │ └── 20240115143046_document.pdf
|
||||
```
|
||||
|
||||
## API 接口
|
||||
|
||||
### 上传文件
|
||||
|
||||
**POST** `/api/files`
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
- `file` (File, required): 上传的文件
|
||||
- `category` (String, optional): 文件分类,默认为"未分类"
|
||||
- `tenant_id` (String, optional): 租户ID,默认为"default"
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "文件上传成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"tenant_id": "default",
|
||||
"user_id": 123,
|
||||
"file_name": "example",
|
||||
"original_name": "example.jpg",
|
||||
"file_path": "uploads/2024/01/15/20240115143045_example.jpg",
|
||||
"file_url": "/uploads/2024/01/15/20240115143045_example.jpg",
|
||||
"file_size": 102400,
|
||||
"file_type": "image",
|
||||
"file_ext": ".jpg",
|
||||
"category": "未分类",
|
||||
"upload_by": "username",
|
||||
"upload_time": "2024-01-15T14:30:45Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 后端实现
|
||||
|
||||
### 路由配置
|
||||
|
||||
在 `server/routers/router.go` 中配置:
|
||||
|
||||
```go
|
||||
// 文件管理路由
|
||||
beego.Router("/api/files", &controllers.FileController{}, "get:GetAllFiles")
|
||||
beego.Router("/api/files", &controllers.FileController{}, "post:Post")
|
||||
beego.Router("/api/files/my", &controllers.FileController{}, "get:GetMyFiles")
|
||||
beego.Router("/api/files/:id", &controllers.FileController{}, "get:GetFileById")
|
||||
beego.Router("/api/files/:id", &controllers.FileController{}, "put:UpdateFile")
|
||||
beego.Router("/api/files/:id", &controllers.FileController{}, "delete:DeleteFile")
|
||||
```
|
||||
|
||||
### 控制器实现
|
||||
|
||||
`Post()` 方法位于 `server/controllers/file.go`:
|
||||
|
||||
1. 验证用户登录状态
|
||||
2. 接收上传的文件
|
||||
3. 生成日期路径和唯一文件名
|
||||
4. 保存文件到本地
|
||||
5. 记录文件信息到数据库
|
||||
6. 返回文件信息
|
||||
|
||||
## 前端使用
|
||||
|
||||
### 基本用法
|
||||
|
||||
```typescript
|
||||
import { fileAPI } from '@/api/file'
|
||||
|
||||
// 创建 FormData
|
||||
const formData = new FormData()
|
||||
formData.append('file', fileObject) // fileObject 是 File 对象
|
||||
formData.append('category', '文档')
|
||||
formData.append('tenant_id', 'tenant-001')
|
||||
|
||||
// 上传文件
|
||||
try {
|
||||
const response = await fileAPI.uploadFile(formData, {
|
||||
category: '文档',
|
||||
tenantId: 'tenant-001'
|
||||
})
|
||||
console.log('上传成功:', response.data)
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
}
|
||||
```
|
||||
|
||||
### Element Plus 上传组件
|
||||
|
||||
```vue
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
drag
|
||||
:action="uploadUrl"
|
||||
:headers="uploadHeaders"
|
||||
:data="{ category: uploadForm.category }"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:before-upload="beforeUpload"
|
||||
multiple
|
||||
>
|
||||
<el-icon><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
将文件拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
</el-upload>
|
||||
|
||||
<script setup>
|
||||
const uploadUrl = computed(() => {
|
||||
const baseUrl = import.meta.env.VITE_API_BASE_URL
|
||||
return `${baseUrl}/api/files`
|
||||
})
|
||||
|
||||
const uploadHeaders = computed(() => {
|
||||
const token = localStorage.getItem('token')
|
||||
return {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
|
||||
const handleUploadSuccess = (response) => {
|
||||
console.log('上传成功:', response)
|
||||
}
|
||||
|
||||
const beforeUpload = (file) => {
|
||||
const maxSize = 10 * 1024 * 1024 // 10MB
|
||||
if (file.size > maxSize) {
|
||||
ElMessage.error('文件大小不能超过 10MB!')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 文件访问
|
||||
|
||||
上传后的文件可以通过以下URL访问:
|
||||
|
||||
```
|
||||
http://localhost:8080/uploads/2024/01/15/20240115143045_example.jpg
|
||||
```
|
||||
|
||||
注意:需要配置 Beego 的静态文件服务来提供上传文件的访问。
|
||||
|
||||
在 `server/conf/app.conf` 中添加:
|
||||
|
||||
```conf
|
||||
# 文件上传目录
|
||||
StaticDir = /uploads:../front/uploads
|
||||
```
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
1. **文件大小限制**:建议在前端和后端都添加文件大小限制
|
||||
2. **文件类型验证**:根据业务需求限制允许上传的文件类型
|
||||
3. **文件名安全**:避免用户控制文件名造成安全问题
|
||||
4. **权限控制**:确保只有授权用户可以上传文件
|
||||
5. **存储位置**:考虑使用对象存储服务(如 OSS、S3)替代本地存储
|
||||
|
||||
## 数据库字段说明
|
||||
|
||||
`yz_files` 表的主要字段:
|
||||
|
||||
- `file_path`: 相对路径,用于存储和访问文件
|
||||
- `file_url`: 访问URL
|
||||
- `original_name`: 用户上传时的原始文件名
|
||||
- `file_name`: 去除扩展名的文件名
|
||||
- `file_type`: 文件类型(image, document, video, audio, archive, other)
|
||||
- `category`: 用户自定义分类
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
# OA 基础数据合并接口说明
|
||||
|
||||
## 概述
|
||||
|
||||
为了减少网络请求次数,提升系统性能,新增了一个合并接口,用于一次性获取部门、职位、角色三类基础数据。
|
||||
|
||||
## 接口信息
|
||||
|
||||
### 接口路径
|
||||
```
|
||||
GET /api/oa/base-data/:tenantId
|
||||
```
|
||||
|
||||
### 请求参数
|
||||
- `tenantId` (路径参数): 租户ID
|
||||
|
||||
### 响应格式
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "获取基础数据成功",
|
||||
"data": {
|
||||
"departments": [
|
||||
{
|
||||
"id": 1,
|
||||
"tenant_id": 1,
|
||||
"name": "技术部",
|
||||
"code": "TECH",
|
||||
"parent_id": 0,
|
||||
"description": "技术部门",
|
||||
"manager_id": 0,
|
||||
"sort_order": 0,
|
||||
"status": 1,
|
||||
"create_time": "2024-01-01T00:00:00Z",
|
||||
"update_time": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"positions": [
|
||||
{
|
||||
"id": 1,
|
||||
"tenant_id": 1,
|
||||
"name": "高级工程师",
|
||||
"code": "SENIOR",
|
||||
"department_id": 1,
|
||||
"level": 3,
|
||||
"description": "高级工程师职位",
|
||||
"sort_order": 0,
|
||||
"status": 1,
|
||||
"create_time": "2024-01-01T00:00:00Z",
|
||||
"update_time": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"roleId": 1,
|
||||
"tenantId": 1,
|
||||
"roleCode": "ADMIN",
|
||||
"roleName": "管理员",
|
||||
"description": "管理员角色",
|
||||
"status": 1,
|
||||
"sortOrder": 0,
|
||||
"createTime": "2024-01-01T00:00:00Z",
|
||||
"updateTime": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 实现细节
|
||||
|
||||
### 后端实现
|
||||
|
||||
#### Services 层 (`server/services/oa.go`)
|
||||
- 使用 goroutine 并行查询三个数据源
|
||||
- 使用 channel 安全地传递查询结果
|
||||
- 任何查询失败都会返回错误
|
||||
|
||||
#### Controllers 层 (`server/controllers/oa.go`)
|
||||
- 接收租户ID参数
|
||||
- 调用 services 层获取数据
|
||||
- 格式化返回数据
|
||||
|
||||
#### 路由配置 (`server/routers/router.go`)
|
||||
- 路由:`/api/oa/base-data/:tenantId`
|
||||
- 方法:GET
|
||||
|
||||
### 前端实现
|
||||
|
||||
#### API 文件 (`pc/src/api/oa.js`)
|
||||
- 封装了 `getOABaseData` 方法
|
||||
|
||||
#### Store 更新 (`pc/src/stores/oa.js`)
|
||||
- `fetchAllBaseData` 方法优先使用合并接口
|
||||
- 如果合并接口失败,自动回退到分别请求三个接口
|
||||
- 保持缓存机制不变
|
||||
|
||||
## 性能优势
|
||||
|
||||
### 优化前
|
||||
- 前端需要发起 3 个独立的 HTTP 请求
|
||||
- 每次请求都有网络延迟
|
||||
- 总耗时 = 3 × 网络延迟 + 3 × 查询时间
|
||||
|
||||
### 优化后
|
||||
- 前端只需发起 1 个 HTTP 请求
|
||||
- 后端使用 goroutine 并行查询,总耗时 = 1 × 网络延迟 + max(查询时间)
|
||||
- **性能提升**:减少 2 个网络请求,总耗时减少约 60-70%
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 前端使用
|
||||
|
||||
```javascript
|
||||
import { useOAStore } from '@/stores/oa';
|
||||
|
||||
const oaStore = useOAStore();
|
||||
|
||||
// 页面初始化时,会自动使用合并接口
|
||||
onMounted(async () => {
|
||||
await oaStore.fetchAllBaseData();
|
||||
});
|
||||
```
|
||||
|
||||
### 后端扩展
|
||||
|
||||
如果需要添加更多数据到合并接口,只需:
|
||||
|
||||
1. 在 `OABaseData` 结构体中添加新字段
|
||||
2. 在 `GetOABaseData` 方法中添加新的查询逻辑
|
||||
3. 在 controller 中格式化返回新数据
|
||||
|
||||
## 兼容性
|
||||
|
||||
- 合并接口与原有的三个独立接口并存
|
||||
- 前端 Store 有自动回退机制,确保兼容性
|
||||
- 如果合并接口失败,会自动使用原有接口
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **租户隔离**:确保返回的数据属于指定租户
|
||||
2. **错误处理**:任何查询失败都会返回错误
|
||||
3. **数据一致性**:确保返回的数据是最新的
|
||||
4. **性能考虑**:后端使用并行查询,但仍需注意数据库性能
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
# 文件上传路径说明
|
||||
|
||||
## 当前配置
|
||||
|
||||
### 文件保存路径
|
||||
文件保存在项目根目录的 `front/uploads/` 目录下,按照日期自动分类:
|
||||
|
||||
```
|
||||
项目根目录/
|
||||
├── server/
|
||||
│ └── controllers/
|
||||
│ └── file.go (处理上传)
|
||||
├── front/
|
||||
│ └── uploads/ ← 文件保存位置
|
||||
│ ├── 2024/
|
||||
│ │ └── 01/
|
||||
│ │ └── 15/
|
||||
│ │ └── 20240115143045_example.jpg
|
||||
```
|
||||
|
||||
### 代码中的路径
|
||||
|
||||
在 `server/controllers/file.go` 的 `Post()` 方法中:
|
||||
|
||||
```go
|
||||
// 构造保存路径:../front/uploads/年/月/日/
|
||||
uploadDir := path.Join("..", "front", "uploads", datePath)
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- `..` 表示从 server 目录向上一级到项目根目录
|
||||
- `front/uploads/` 是上传文件的根目录
|
||||
- `datePath` 是按日期自动生成的子目录(如 `2024/01/15`)
|
||||
|
||||
### 静态文件访问配置
|
||||
|
||||
在 `server/conf/app.conf` 中:
|
||||
|
||||
```conf
|
||||
StaticDir = /uploads:../front/uploads
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- `/uploads` 是 URL 访问路径
|
||||
- `../front/uploads` 是实际文件存储路径(相对于 server 目录)
|
||||
|
||||
## 目录结构
|
||||
|
||||
### 保存到数据库的路径
|
||||
- `file_path`: `uploads/2024/01/15/20240115143045_example.jpg`(相对路径)
|
||||
- `file_url`: `/uploads/2024/01/15/20240115143045_example.jpg`(URL 路径)
|
||||
|
||||
### 实际文件系统路径
|
||||
```
|
||||
front/uploads/2024/01/15/20240115143045_example.jpg
|
||||
```
|
||||
|
||||
### 访问 URL
|
||||
```
|
||||
http://localhost:8080/uploads/2024/01/15/20240115143045_example.jpg
|
||||
```
|
||||
|
||||
## 为什么使用相对路径 `..`
|
||||
|
||||
由于项目结构是:
|
||||
```
|
||||
yunzer_go/
|
||||
├── server/ ← 服务端代码
|
||||
└── front/ ← 前端代码和上传文件
|
||||
└── uploads/
|
||||
```
|
||||
|
||||
从 `server` 目录运行应用时,要访问 `front/uploads`,需要使用 `../front/uploads`。
|
||||
|
||||
## 如果路径不对怎么办?
|
||||
|
||||
### 方案1:修改代码中的路径
|
||||
如果您的项目启动目录不同,可以修改 `file.go` 中的路径:
|
||||
|
||||
```go
|
||||
// 如果从项目根目录运行
|
||||
uploadDir := path.Join("front", "uploads", datePath)
|
||||
|
||||
// 或者使用绝对路径
|
||||
uploadDir := path.Join("/path/to/project", "front", "uploads", datePath)
|
||||
```
|
||||
|
||||
### 方案2:从配置文件读取
|
||||
在 `app.conf` 中添加配置:
|
||||
|
||||
```conf
|
||||
# 上传文件目录
|
||||
uploadDir = ../front/uploads
|
||||
```
|
||||
|
||||
然后在代码中读取:
|
||||
|
||||
```go
|
||||
import "github.com/beego/beego/v2/server/web"
|
||||
|
||||
uploadDir := path.Join(
|
||||
web.AppConfig.String("uploadDir"),
|
||||
datePath,
|
||||
)
|
||||
```
|
||||
|
||||
## 验证路径是否正确
|
||||
|
||||
### 1. 检查文件保存位置
|
||||
上传一个文件后,查看文件是否在正确的位置:
|
||||
|
||||
```bash
|
||||
ls front/uploads/
|
||||
```
|
||||
|
||||
应该看到按日期分类的文件夹和文件。
|
||||
|
||||
### 2. 检查数据库记录
|
||||
查看 `yz_files` 表中的 `file_path` 字段:
|
||||
|
||||
```sql
|
||||
SELECT file_path, file_url FROM yz_files ORDER BY upload_time DESC LIMIT 1;
|
||||
```
|
||||
|
||||
应该看到类似:
|
||||
```
|
||||
file_path: uploads/2024/01/15/20240115143045_example.jpg
|
||||
file_url: /uploads/2024/01/15/20240115143045_example.jpg
|
||||
```
|
||||
|
||||
### 3. 检查 URL 访问
|
||||
直接在浏览器访问:
|
||||
|
||||
```
|
||||
http://localhost:8080/uploads/2024/01/15/文件名
|
||||
```
|
||||
|
||||
如果能看到文件,说明路径配置正确。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 文件保存在了 server/front/uploads?
|
||||
A: 修改代码中的路径为 `../front/uploads`(已经修改)
|
||||
|
||||
### Q: 文件保存在了 front/front/uploads?
|
||||
A: 检查当前工作目录,确保在 server 目录运行应用
|
||||
|
||||
### Q: 访问文件返回 404?
|
||||
A: 检查 `app.conf` 中的 StaticDir 配置是否正确
|
||||
|
||||
### Q: 权限问题?
|
||||
A: 确保应用有创建目录和写入文件的权限:
|
||||
```bash
|
||||
chmod 755 front/uploads
|
||||
```
|
||||
|
||||
## 建议的改进
|
||||
|
||||
如果需要更可靠的路径处理,可以考虑:
|
||||
|
||||
1. **使用绝对路径**:从配置文件或环境变量读取项目根目录
|
||||
2. **路径验证**:在应用启动时检查上传目录是否存在,不存在则创建
|
||||
3. **日志记录**:记录文件保存的完整路径,便于调试
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
# 文件管理 API 文档
|
||||
|
||||
## 概述
|
||||
|
||||
文件管理模块提供对 `yz_files` 表的完整 CRUD 操作,支持文件信息的管理、搜索和统计功能。
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
```sql
|
||||
CREATE TABLE yz_files (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '文件ID',
|
||||
tenant_id VARCHAR(64) NOT NULL COMMENT '租户ID',
|
||||
|
||||
-- 文件基础信息
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '文件名称',
|
||||
original_name VARCHAR(255) NOT NULL COMMENT '原始文件名',
|
||||
file_path VARCHAR(500) NOT NULL COMMENT '文件存储路径',
|
||||
file_url VARCHAR(500) COMMENT '文件访问URL',
|
||||
file_size BIGINT NOT NULL DEFAULT 0 COMMENT '文件大小(字节)',
|
||||
file_type VARCHAR(50) NOT NULL COMMENT '文件类型',
|
||||
file_ext VARCHAR(20) NOT NULL COMMENT '文件扩展名',
|
||||
|
||||
-- 分类信息
|
||||
category VARCHAR(100) NOT NULL COMMENT '文件分类',
|
||||
sub_category VARCHAR(100) COMMENT '子分类',
|
||||
|
||||
-- 状态信息
|
||||
status TINYINT DEFAULT 1 COMMENT '状态(1:正常, 0:删除)',
|
||||
is_public TINYINT DEFAULT 0 COMMENT '是否公开(1:是, 0:否)',
|
||||
|
||||
-- 上传信息
|
||||
upload_by VARCHAR(100) NOT NULL COMMENT '上传人',
|
||||
upload_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
|
||||
|
||||
-- 索引
|
||||
INDEX idx_tenant (tenant_id),
|
||||
INDEX idx_category (category),
|
||||
INDEX idx_upload_time (upload_time),
|
||||
INDEX idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文件表';
|
||||
```
|
||||
|
||||
## API 接口列表
|
||||
|
||||
### 1. 获取所有文件信息
|
||||
|
||||
**GET** `/api/files`
|
||||
|
||||
**认证方式**: JWT认证
|
||||
|
||||
**参数**: 无
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"tenant_id": "default",
|
||||
"user_id": 1,
|
||||
"file_name": "example.pdf",
|
||||
"original_name": "example.pdf",
|
||||
"file_path": "/uploads/2024/01/01/example.pdf",
|
||||
"file_url": "http://example.com/uploads/2024/01/01/example.pdf",
|
||||
"file_size": 1024,
|
||||
"file_type": "application/pdf",
|
||||
"file_ext": "pdf",
|
||||
"category": "文档",
|
||||
"sub_category": "PDF",
|
||||
"status": 1,
|
||||
"is_public": 0,
|
||||
"upload_by": "admin",
|
||||
"upload_time": "2024-01-01T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取当前用户的文件列表
|
||||
|
||||
**GET** `/api/files/my`
|
||||
|
||||
**认证方式**: JWT认证
|
||||
|
||||
**参数**: 无
|
||||
|
||||
**说明**: 通过JWT token自动获取当前登录用户的文件列表
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"tenant_id": "default",
|
||||
"user_id": 1,
|
||||
"file_name": "example.pdf",
|
||||
"original_name": "example.pdf",
|
||||
"file_path": "/uploads/2024/01/01/example.pdf",
|
||||
"file_url": "http://example.com/uploads/2024/01/01/example.pdf",
|
||||
"file_size": 1024,
|
||||
"file_type": "application/pdf",
|
||||
"file_ext": "pdf",
|
||||
"category": "文档",
|
||||
"sub_category": "PDF",
|
||||
"status": 1,
|
||||
"is_public": 0,
|
||||
"upload_by": "admin",
|
||||
"upload_time": "2024-01-01T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 根据ID获取文件信息
|
||||
|
||||
**GET** `/api/files/:id`
|
||||
|
||||
**参数**:
|
||||
- `id` (路径参数): 文件ID
|
||||
|
||||
**响应**: 同获取所有文件接口
|
||||
|
||||
### 3. 创建文件信息
|
||||
|
||||
**POST** `/api/files`
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"tenant_id": "tenant-001",
|
||||
"file_name": "new-file.txt",
|
||||
"original_name": "原始文件.txt",
|
||||
"file_path": "/uploads/tenant-001/new-file.txt",
|
||||
"file_url": "http://localhost:8080/uploads/tenant-001/new-file.txt",
|
||||
"file_size": 1024,
|
||||
"file_type": "text/plain",
|
||||
"file_ext": "txt",
|
||||
"category": "文本",
|
||||
"sub_category": "TXT",
|
||||
"status": 1,
|
||||
"is_public": 0,
|
||||
"upload_by": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "创建文件成功",
|
||||
"data": {
|
||||
"id": 2,
|
||||
... // 创建的文件信息
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 更新文件信息
|
||||
|
||||
**PUT** `/api/files/:id`
|
||||
|
||||
**参数**:
|
||||
- `id` (路径参数): 文件ID
|
||||
|
||||
**请求体**: 同创建文件接口
|
||||
|
||||
**响应**: 同创建文件接口
|
||||
|
||||
### 5. 删除文件(软删除)
|
||||
|
||||
**DELETE** `/api/files/:id`
|
||||
|
||||
**参数**:
|
||||
- `id` (路径参数): 文件ID
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "删除文件成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 硬删除文件
|
||||
|
||||
**DELETE** `/api/files/:id/hard`
|
||||
|
||||
**参数**:
|
||||
- `id` (路径参数): 文件ID
|
||||
|
||||
**响应**: 同软删除接口
|
||||
|
||||
### 7. 根据租户ID获取文件
|
||||
|
||||
**GET** `/api/files/tenant?tenant_id=tenant-001`
|
||||
|
||||
**参数**:
|
||||
- `tenant_id` (查询参数): 租户ID
|
||||
|
||||
**响应**: 同获取所有文件接口
|
||||
|
||||
### 8. 根据分类获取文件
|
||||
|
||||
**GET** `/api/files/category?category=文档`
|
||||
|
||||
**参数**:
|
||||
- `category` (查询参数): 文件分类
|
||||
|
||||
**响应**: 同获取所有文件接口
|
||||
|
||||
### 9. 根据状态获取文件
|
||||
|
||||
**GET** `/api/files/status?status=1`
|
||||
|
||||
**参数**:
|
||||
- `status` (查询参数): 文件状态 (1:正常, 0:删除)
|
||||
|
||||
**响应**: 同获取所有文件接口
|
||||
|
||||
### 10. 获取文件统计信息
|
||||
|
||||
**GET** `/api/files/statistics?tenant_id=tenant-001`
|
||||
|
||||
**参数**:
|
||||
- `tenant_id` (查询参数): 租户ID
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取统计信息成功",
|
||||
"data": {
|
||||
"total_count": 10,
|
||||
"total_size": 10485760,
|
||||
"category_stats": [
|
||||
{
|
||||
"category": "文档",
|
||||
"count": 5,
|
||||
"size": 5242880
|
||||
},
|
||||
{
|
||||
"category": "图片",
|
||||
"count": 3,
|
||||
"size": 3145728
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 11. 搜索文件
|
||||
|
||||
**GET** `/api/files/search?keyword=文档&tenant_id=tenant-001`
|
||||
|
||||
**参数**:
|
||||
- `keyword` (查询参数): 搜索关键词
|
||||
- `tenant_id` (查询参数): 租户ID
|
||||
|
||||
**响应**: 同获取所有文件接口
|
||||
|
||||
### 12. 公开接口(无需认证)
|
||||
|
||||
**GET** `/api/files/public`
|
||||
|
||||
**参数**: 无
|
||||
|
||||
**响应**: 同获取所有文件接口
|
||||
|
||||
## 错误响应
|
||||
|
||||
所有接口在发生错误时返回统一的错误格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误描述",
|
||||
"error": "详细错误信息"
|
||||
}
|
||||
```
|
||||
|
||||
## 状态码说明
|
||||
|
||||
- `200`: 请求成功
|
||||
- `400`: 参数错误
|
||||
- `404`: 资源不存在
|
||||
- `500`: 服务器内部错误
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 前端调用示例(JavaScript)
|
||||
|
||||
```javascript
|
||||
// 获取所有文件
|
||||
const response = await fetch('/api/files');
|
||||
const result = await response.json();
|
||||
|
||||
// 创建文件
|
||||
const newFile = {
|
||||
tenant_id: "tenant-001",
|
||||
file_name: "example.txt",
|
||||
original_name: "示例文件.txt",
|
||||
file_path: "/uploads/example.txt",
|
||||
file_type: "text/plain",
|
||||
file_ext: "txt",
|
||||
category: "文档",
|
||||
upload_by: "user123"
|
||||
};
|
||||
|
||||
const createResponse = await fetch('/api/files', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(newFile)
|
||||
});
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有需要认证的接口都需要在请求头中包含有效的 JWT Token
|
||||
2. 文件上传功能需要配合文件存储服务实现
|
||||
3. 软删除只是标记文件状态为删除,实际数据仍然保留
|
||||
4. 硬删除会永久删除文件记录,请谨慎使用
|
||||
@@ -1,135 +0,0 @@
|
||||
# 后端接口性能优化说明
|
||||
|
||||
## 问题描述
|
||||
|
||||
后端接口请求响应慢,主要原因是:
|
||||
|
||||
1. **数据库连接池未配置** - 每次请求都创建新的数据库连接
|
||||
2. **缺少数据库索引** - 常用查询字段(tenant_id, delete_time)没有索引
|
||||
3. **内存分配未优化** - Controller 层数据格式化时未预分配容量
|
||||
4. **网络延迟** - 使用远程数据库,网络延迟较高
|
||||
|
||||
## 优化措施
|
||||
|
||||
### 1. 数据库连接池配置 ✅
|
||||
|
||||
**位置**: `server/models/user.go`
|
||||
|
||||
**优化内容**:
|
||||
- 设置最大空闲连接数:`MaxIdleConns = 10`
|
||||
- 设置最大打开连接数:`MaxOpenConns = 100`
|
||||
- 设置连接最大生存时间:`ConnMaxLifetime = 1小时`
|
||||
- 添加连接超时参数:`timeout=10s&readTimeout=30s&writeTimeout=30s`
|
||||
|
||||
**效果**:
|
||||
- 减少连接创建和销毁的开销
|
||||
- 复用数据库连接,提升响应速度
|
||||
- 避免连接泄漏
|
||||
|
||||
### 2. 数据库索引优化 ✅
|
||||
|
||||
**位置**: `server/database/performance_indexes.sql`
|
||||
|
||||
**优化内容**:
|
||||
- 为 `yz_tenant_departments` 表添加索引:
|
||||
- `idx_tenant_id` - 租户ID索引
|
||||
- `idx_delete_time` - 删除时间索引
|
||||
- `idx_tenant_delete` - 复合索引 (tenant_id, delete_time)
|
||||
- `idx_parent_id` - 父级ID索引(树形结构查询)
|
||||
|
||||
- 为 `yz_tenant_positions` 表添加索引:
|
||||
- `idx_tenant_id` - 租户ID索引
|
||||
- `idx_delete_time` - 删除时间索引
|
||||
- `idx_department_id` - 部门ID索引
|
||||
- `idx_dept_delete_status` - 复合索引 (department_id, delete_time, status)
|
||||
|
||||
- 为 `yz_roles` 表添加索引:
|
||||
- `idx_tenant_id` - 租户ID索引
|
||||
- `idx_delete_time` - 删除时间索引
|
||||
|
||||
- 为 `yz_employees` 表添加索引:
|
||||
- `idx_tenant_id` - 租户ID索引
|
||||
- `idx_delete_time` - 删除时间索引
|
||||
- `idx_department_id` - 部门ID索引
|
||||
- `idx_position_id` - 职位ID索引
|
||||
|
||||
**执行方法**:
|
||||
```bash
|
||||
mysql -u gotest -p -h 43.133.71.191 -P 3308 gotest < server/database/performance_indexes.sql
|
||||
```
|
||||
|
||||
**效果**:
|
||||
- 查询速度提升 10-100 倍(取决于数据量)
|
||||
- 减少全表扫描
|
||||
- 优化 WHERE 和 JOIN 查询
|
||||
|
||||
### 3. 内存分配优化 ✅
|
||||
|
||||
**位置**: `server/controllers/oa.go`
|
||||
|
||||
**优化内容**:
|
||||
- 预分配切片容量,避免多次扩容
|
||||
- 使用 `make([]map[string]interface{}, 0, count)` 替代 `make([]map[string]interface{}, 0)`
|
||||
|
||||
**效果**:
|
||||
- 减少内存分配次数
|
||||
- 降低 GC 压力
|
||||
- 提升响应速度约 5-10%
|
||||
|
||||
### 4. 查询优化建议
|
||||
|
||||
**已实现**:
|
||||
- 使用 `services.GetOABaseData()` 并行查询部门、职位、角色数据
|
||||
- 使用 goroutine 并发执行多个查询
|
||||
|
||||
**建议**:
|
||||
- 对于大数据量查询,考虑实现分页
|
||||
- 对于频繁查询的数据,考虑添加 Redis 缓存层
|
||||
- 监控慢查询日志,持续优化
|
||||
|
||||
## 性能提升预期
|
||||
|
||||
- **连接池配置**: 提升 20-30%
|
||||
- **数据库索引**: 提升 50-90%(取决于数据量)
|
||||
- **内存优化**: 提升 5-10%
|
||||
- **总体提升**: 预期提升 50-80%
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **索引维护成本**:
|
||||
- 索引会占用额外存储空间
|
||||
- 插入/更新操作会稍慢(通常可忽略)
|
||||
- 建议定期检查索引使用情况
|
||||
|
||||
2. **连接池配置**:
|
||||
- `MaxOpenConns` 应根据实际并发量调整
|
||||
- 过大的连接池可能导致数据库连接耗尽
|
||||
- 建议监控连接池使用情况
|
||||
|
||||
3. **远程数据库**:
|
||||
- 网络延迟是主要瓶颈之一
|
||||
- 考虑使用 CDN 或数据库代理
|
||||
- 对于高并发场景,建议使用本地数据库或缓存
|
||||
|
||||
## 下一步优化建议
|
||||
|
||||
1. **添加 Redis 缓存层**:
|
||||
- 缓存常用的基础数据(部门、职位、角色)
|
||||
- 设置合理的过期时间(如 5 分钟)
|
||||
- 减少数据库查询压力
|
||||
|
||||
2. **实现查询日志**:
|
||||
- 记录慢查询(> 100ms)
|
||||
- 分析查询模式
|
||||
- 持续优化
|
||||
|
||||
3. **数据库查询优化**:
|
||||
- 使用 `SELECT` 只查询需要的字段
|
||||
- 避免 `SELECT *`
|
||||
- 使用 `LIMIT` 限制结果集
|
||||
|
||||
4. **监控和告警**:
|
||||
- 监控接口响应时间
|
||||
- 监控数据库连接池使用情况
|
||||
- 设置性能告警阈值
|
||||
|
||||
Reference in New Issue
Block a user