init: 提交 files 服务初始代码

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zxr
2026-08-03 23:51:21 +08:00
commit d29693343b
33 changed files with 2067 additions and 0 deletions

244
README.md Normal file
View File

@@ -0,0 +1,244 @@
# Files 文件服务
Files 是 OPS 的公共文件服务,统一管理文件元数据,并通过阿里云 OSS 预签名地址让调用方直传文件。调用方只选择已配置的命名空间,不能指定 OSS 厂商、Bucket 或对象路径。
当前仅支持阿里云 OSS。服务不在本地保存文件已完成上传的文件持续保留临时文件由调用方在使用结束后主动删除只有超时且未完成的上传会被后台任务自动清理。
## 主要能力
- 生成 OSS 预签名 PUT 地址,文件不经过 Files 服务中转。
- 校验命名空间、扩展名、文件大小和上传后的对象大小。
- 记录文件归属、状态、对象路径、ETag 和公开访问地址。
- 支持 JWT 用户调用和内部服务调用,二者数据相互隔离。
- 支持查询与删除本人或本服务创建的文件。
- 自动清理超过预签名有效期但未完成的上传。
## 运行依赖
- Go 1.25.1
- PostgreSQL
- Redis
- 阿里云 OSS Bucket
## 配置
配置文件位于 `etc`
```text
etc/files_dev.yaml
etc/files_test.yaml
etc/files_prod.yaml
```
运行环境由 `BSM_RuntimeMode` 决定,默认值为 `dev`。服务读取 `${BSM_Prefix}/etc/files_<mode>.yaml`;开发模式未设置 `BSM_Prefix` 时,使用当前工作目录。
核心配置示例:
```yaml
ServiceClients:
assets: <assets 调用密钥>
dc-control: <dc-control 调用密钥>
visual: <visual 调用密钥>
ObjectStorage:
Provider: aliyun
Endpoint: https://oss-cn-beijing.aliyuncs.com
Region: cn-beijing
Bucket: ops-app
PublicBaseURL: https://ops-app.oss-cn-beijing.aliyuncs.com
AccessKeyID: ${FILES_OSS_ACCESS_KEY_ID}
AccessKeySecret: ${FILES_OSS_ACCESS_KEY_SECRET}
PresignTTLSeconds: 600
Namespaces:
reports:
Prefix: reports
MaxSizeMB: 512
AllowedExtensions: [.csv, .xlsx, .json, .pdf, .zip]
Cleanup:
IntervalSeconds: 600
```
配置说明:
- `ServiceClients`:允许调用内部接口的服务名和密钥,服务名必须与请求头 `Service-Name` 完全一致。
- `Provider`:当前只能填写 `aliyun`
- `Endpoint`OSS SDK 请求地址,不包含 Bucket 名称。
- `PublicBaseURL`:完成上传后返回给调用方的公开访问地址前缀。
- `PresignTTLSeconds`:上传地址有效期,允许范围为 603600 秒。
- `Namespaces`:调用方可使用的命名空间,以及对应的目录、大小和扩展名限制。
- `Cleanup.IntervalSeconds`:扫描并清理超时未完成上传的时间间隔。
OSS 凭证必须通过环境变量提供:
```powershell
$env:FILES_OSS_ACCESS_KEY_ID = '<AccessKey ID>'
$env:FILES_OSS_ACCESS_KEY_SECRET = '<AccessKey Secret>'
```
使用用户接口时,还要保证 Files 与 JWT 签发方使用相同密钥:
```powershell
$env:BSM_JwtSecretKey = '<与 JWT 签发方一致的密钥>'
```
不要将 AccessKey、数据库密码或生产环境服务密钥写入 README 或提交到代码仓库。
## 本地启动
`files` 目录执行:
```powershell
$env:BSM_RuntimeMode = 'dev'
go run ./cmd/main/main.go
```
默认监听配置文件中的 `12452` 端口。启动时会自动创建或更新 `files_object` 表。
如需只执行表结构迁移:
```powershell
go run ./cmd/cli/main.go migrate
```
健康检查:
```text
HEAD /
GET /Files/v1/ping/hello
```
## 构建
构建当前 Windows 环境程序:
```powershell
go build -o build/files.exe ./cmd/main/main.go
```
打包 Linux amd64 程序:
```powershell
powershell -ExecutionPolicy Bypass -File ./scripts/pack.ps1
```
产物为 `build/ops-files`
## 调用鉴权
文件接口提供两套路径,功能和请求结构相同。
用户接口使用 JWT
```text
Authorization: <JWT 原始字符串>
```
JWT 直接放入 `Authorization`,不要添加 `Bearer ` 前缀。
内部服务接口使用服务名和独立密钥:
```text
Service-Name: assets
Secret-Key: <与 ServiceClients.assets 一致的值>
```
服务密钥只用于服务之间调用,不是阿里云 AccessKey。调用方不能通过请求选择 OSS 厂商或 Bucket。
## 文件接口
| 操作 | 用户接口 | 内部服务接口 |
| --- | --- | --- |
| 初始化上传 | `POST /Files/v1/uploads/init` | `POST /Files/v1/internal/uploads/init` |
| 确认上传完成 | `POST /Files/v1/uploads/:file_id/complete` | `POST /Files/v1/internal/uploads/:file_id/complete` |
| 查询文件 | `GET /Files/v1/files/:file_id` | `GET /Files/v1/internal/files/:file_id` |
| 删除文件 | `DELETE /Files/v1/files/:file_id` | `DELETE /Files/v1/internal/files/:file_id` |
同一个文件只能由创建它的用户或内部服务访问。用户接口和内部服务接口之间不能交叉访问文件。
### 上传流程
1. 调用初始化接口:
```http
POST /Files/v1/internal/uploads/init
Content-Type: application/json
Service-Name: assets
Secret-Key: <>
{
"namespace": "assets",
"filename": "example.png",
"size": 102400,
"content_type": "image/png"
}
```
成功结果的 `details` 中包含:
```json
{
"file_id": "文件标识",
"object_key": "assets/年/月/文件标识.png",
"upload": {
"method": "PUT",
"url": "OSS 预签名地址",
"headers": {
"接口返回的请求头": "接口返回的值"
},
"expires_at": "过期时间"
}
}
```
2. 使用 `upload.method``upload.url` 将文件内容直接上传到 OSS并原样携带 `upload.headers`。此请求不携带 Files 的 JWT、`Service-Name``Secret-Key`
3. OSS 上传成功后调用确认接口:
```http
POST /Files/v1/internal/uploads/<file_id>/complete
Service-Name: assets
Secret-Key: <>
```
确认接口会检查 OSS 对象是否存在以及实际大小是否与初始化请求一致。成功后返回文件信息,其中 `url` 是公开访问地址。
### 查询和删除
```http
GET /Files/v1/internal/files/<file_id>
DELETE /Files/v1/internal/files/<file_id>
```
删除接口同时删除 OSS 对象和文件元数据。处于 `ready` 状态的临时文件不会自动删除,调用方必须在业务使用结束后主动调用删除接口。
## 响应约定
业务接口使用统一响应结构:
```json
{
"code": 0,
"message": "",
"details": {},
"timeseq": 0
}
```
- `code = 0` 表示成功。
- 业务错误通常仍返回 HTTP 200通过非零 `code` 判断失败。
- JWT 或内部服务鉴权失败返回 HTTP 401。
## OSS 前置设置
当前 `PublicBaseURL` 使用 OSS Bucket 公网域名,因此 Bucket 需要设置为“公共读”,不能设置为“公共读写”。如果由浏览器直接上传,还需要给 Bucket 配置 CORS
```text
来源:实际前端域名
MethodsPUT、GET、HEAD
允许 Headers*
暴露 HeadersETag、x-oss-request-id
```
生产环境应使用 RAM 用户的 AccessKey并只授予目标 Bucket 所需的对象上传、查询和删除权限。