245 lines
6.7 KiB
Markdown
245 lines
6.7 KiB
Markdown
# 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`:上传地址有效期,允许范围为 60~3600 秒。
|
||
- `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
|
||
来源:实际前端域名
|
||
Methods:PUT、GET、HEAD
|
||
允许 Headers:*
|
||
暴露 Headers:ETag、x-oss-request-id
|
||
```
|
||
|
||
生产环境应使用 RAM 用户的 AccessKey,并只授予目标 Bucket 所需的对象上传、查询和删除权限。
|