15 KiB
15 KiB
文件存储策略
**本文引用的文件** - [application.yaml](file://fms-api/src/main/resources/application.yaml) - [FileStorageProperties.java](file://fms-api/src/main/java/cn/g3soft/fmsapi/config/FileStorageProperties.java) - [FileController.java](file://fms-api/src/main/java/cn/g3soft/fmsapi/controller/FileController.java) - [FileService.java](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/FileService.java) - [100_initialize_fms_new.sql](file://fms-api/config/migrations/100_initialize_fms_new.sql)目录
简介
本文件存储策略文档面向 FMS 系统的文件上传、存储与访问能力,覆盖以下主题:
- 支持的存储模式:本地文件系统与云存储(当前实现为通过 g3oss 代理的 OSS)
- 存储路径规则、文件命名策略与目录结构设计
- 文件元数据管理:表结构、关联关系与持久化策略
- 文件访问 URL 生成机制与安全访问控制
- 不同环境的配置示例与调优建议
- 备份、迁移与灾难恢复策略
项目结构
FMS 后端采用 Spring Boot 模块组织,文件相关能力集中在 fms-api 模块中:
- 配置层:application.yaml 提供全局配置;FileStorageProperties 将 fms.file.* 映射到 Java 对象
- 控制器层:FileController 暴露 /file/* 接口,负责上传、凭证获取、记录保存、删除与静态资源访问
- 服务层:FileService 封装本地/OSS 上传、删除、DB 写入等核心逻辑
- 数据层:bf_files 表用于持久化文件元数据;迁移脚本定义系统基础表结构
graph TB
A["前端/调用方"] --> B["FileController<br/>/file/*"]
B --> C["FileService<br/>业务逻辑"]
C --> D["FileStorageProperties<br/>配置读取"]
C --> E["数据库 bf_files<br/>元数据持久化"]
C --> F["本地文件系统<br/>./data/files/{orgId}/{yyyy}/{MM}/{dd}/..."]
C --> G["g3oss 代理服务<br/>OSS 凭证/删除"]
B --> H["静态文件访问<br/>/file/static/{path}"]
图表来源
- FileController.java:28-113
- FileService.java:34-268
- FileStorageProperties.java:5-69
- application.yaml:10-34
章节来源
- application.yaml:1-34
- FileStorageProperties.java:1-69
- FileController.java:1-113
- FileService.java:1-268
核心组件
- FileStorageProperties:集中管理存储类型与具体实现参数(本地存储目录、URL前缀;OSS 地址)
- FileController:对外暴露文件上传、凭证获取、记录保存、删除与静态访问接口
- FileService:实现本地直传、OSS 凭证获取、删除(本地或 OSS)、以及 bf_files 元数据写入
- 数据库 bf_files:存储文件元数据,包括原始文件名、大小、扩展名、映射路径、所属模块与分类等
章节来源
架构总览
下图展示了从请求到存储与数据库的完整流程,涵盖本地与 OSS 两种模式。
sequenceDiagram
participant FE as "前端"
participant FC as "FileController"
participant FS as "FileService"
participant DB as "数据库(bf_files)"
participant FSYS as "本地文件系统"
participant GOSS as "g3oss 代理服务"
Note over FE,FC : 上传流程
FE->>FC : POST /file/upload (files, father, moduleId, cateId)
FC->>FS : localUpload(...)
FS->>FSYS : 写入 ./data/files/{orgId}/{yyyy}/{MM}/{dd}/{uuid.ext}
FS->>DB : INSERT bf_files(元数据)
FS-->>FC : 返回元数据列表
FC-->>FE : 成功响应
Note over FE,GOSS : OSS 直传流程
FE->>FC : POST /file/upload-credential
FC->>FS : ossCredential()
FS->>GOSS : 获取上传凭证(bucket=orgId)
GOSS-->>FS : 凭证信息
FS-->>FC : 凭证
FC-->>FE : 凭证(前端直传到g3oss)
FE->>FC : POST /file/save-record(回调)
FC->>FS : saveRecord(meta)
FS->>DB : INSERT bf_files(元数据)
FC-->>FE : 成功响应
图表来源
详细组件分析
存储模式与配置
- 存储类型:通过 fms.file.storage-type 切换(当前支持 local 与 aliyun-oss 分支逻辑)
- 本地存储:
- 存储目录:fms.file.local.storage-dir(默认 ./data/files)
- URL 前缀:fms.file.local.url-prefix(默认 /api/file/static)
- OSS:
- g3oss 地址:fms.file.oss.g3oss-url(用于获取凭证与删除文件)
章节来源
存储路径规则与目录结构
- 机构隔离:以 orgId 作为根目录,确保多租户数据隔离
- 时间分片:按年/月/日建立子目录,便于归档与清理
- 文件命名:使用 UUID 去除连字符并保留原扩展名,避免冲突且利于缓存
- 最终路径:{storageDir}/{orgId}/{yyyy}/{MM}/{dd}/{uuid.ext}
- 访问 URL:{urlPrefix}/{orgId}/{yyyy}/{MM}/{dd}/{uuid.ext}
flowchart TD
Start(["开始"]) --> Org["解析 orgId"]
Org --> Date["计算 yyyy/MM/dd"]
Date --> Name["生成 uuid.ext"]
Name --> Path["拼接路径 {orgId}/{yyyy}/{MM}/{dd}/{uuid.ext}"]
Path --> Write{"本地还是OSS?"}
Write --> |本地| LocalWrite["写入本地磁盘"]
Write --> |OSS| OssCred["获取g3oss凭证并直传"]
LocalWrite --> Meta["写入bf_files元数据"]
OssCred --> Callback["前端回调save-record"]
Callback --> Meta
Meta --> End(["结束"])
图表来源
文件元数据管理与持久化
- 表名:bf_files
- 关键字段(根据代码推断):
- subid:雪花主键,唯一标识文件记录
- father:父级 ID(可为空)
- mx_filename:原始文件名
- mx_filesize:文件大小
- mx_fileext:扩展名
- mx_mapfilename:映射路径或访问 URL
- mx_moduleid:所属模块 ID(可为空)
- mx_cate_id:分类 ID(可为空)
- mx_xh:排序字段(默认 0)
- mx_sh:审核状态(默认 0)
- mx_inputuser_id:录入用户 ID
- mx_inputdatetime:录入时间
- 插入时机:
- 本地上传:写盘成功后立即写入 bf_files
- OSS 直传:前端直传完成后回调 /file/save-record 写入 bf_files
- 删除时同步:
- 先删存储(本地文件或 OSS),再删除 bf_files 行
章节来源
文件访问 URL 生成与安全访问
- 本地访问:
- 通过 /file/static/{fileName} 提供下载/预览
- 服务端校验路径穿越,仅允许在 storageDir 下访问
- 设置 Content-Disposition 为 inline,便于浏览器直接预览
- OSS 访问:
- 当前未实现服务端签名 URL 生成;前端直传后由 g3oss 提供服务端访问
- 可通过后续扩展增加签名 URL 能力(如带过期时间的临时链接)
章节来源
删除流程与一致性
- 删除入口:POST /file/delete
- 删除步骤:
- 查询 bf_files 获取 mx_mapfilename
- 根据存储类型选择删除本地文件或调用 g3oss 删除
- 删除 bf_files 记录
- 异常处理:
- 若 OSS 删除失败,抛出业务异常并回滚上层事务(如有)
sequenceDiagram
participant FE as "前端"
participant FC as "FileController"
participant FS as "FileService"
participant DB as "数据库"
participant FSYS as "本地文件系统"
participant GOSS as "g3oss"
FE->>FC : POST /file/delete({subid})
FC->>FS : delete(subid)
FS->>DB : 查询 bf_files(mx_mapfilename)
alt 本地存储
FS->>FSYS : 删除对应文件
else OSS 存储
FS->>GOSS : 调用删除接口(objectName)
end
FS->>DB : DELETE FROM bf_files WHERE subid=?
FS-->>FC : 成功
FC-->>FE : 成功响应
图表来源
依赖关系分析
- FileController 依赖 FileStorageProperties 与 FileService
- FileService 依赖 FileStorageProperties、DbUtils、DataSource、OrgContext
- FileService 与外部系统交互:
- 本地文件系统:NIO Files 操作
- g3oss 代理服务:HTTP 客户端发起凭证获取与删除请求
- 数据库:bf_files 表用于元数据持久化
classDiagram
class FileController {
+config()
+upload()
+uploadCredential()
+saveRecord()
+delete()
+serveFile()
}
class FileService {
+localUpload()
+saveRecord()
+ossCredential()
+delete()
-insertFileRecord()
-deleteLocalFile()
-deleteOssFile()
}
class FileStorageProperties {
+getStorageType()
+getLocal()
+getOss()
}
class DataSource
class DbUtils
class OrgContext
FileController --> FileService : "调用"
FileService --> FileStorageProperties : "读取配置"
FileService --> DataSource : "连接数据库"
FileService --> DbUtils : "通用查询"
FileService --> OrgContext : "获取orgId/userId"
图表来源
章节来源
性能考虑
- 连接池与超时:
- application.yaml 中定义了数据库连接池参数(初始大小、最小空闲、最大活跃、等待超时、验证查询等)
- HTTP 客户端连接超时设置为 10 秒,避免阻塞
- 本地 I/O:
- 使用 NIO Files.copy 进行高效写入
- 路径规范化与存在性检查减少无效 IO
- OSS 直传:
- 前端直传减轻服务器带宽压力
- 凭证获取与回调分离,提高并发能力
- 建议:
- 在高并发场景下,适当增大连接池与线程池
- 对热点文件启用 CDN 加速(结合 OSS)
- 定期归档历史日期目录,降低单目录文件数量
章节来源
故障排查指南
- 上传失败:
- 检查 storageDir 是否存在且可写
- 确认 orgId 上下文是否正确注入
- 查看日志中的 IOException 与 SQL 异常
- OSS 凭证获取失败:
- 核对 g3ossUrl 与网络连通性
- 检查返回码是否为 200
- 删除不一致:
- 若存储删除成功但 DB 删除失败,需补偿删除 DB 记录
- 若 DB 删除成功但存储残留,需手动清理或重试删除
- 静态访问 404:
- 确认请求路径是否包含正确的 orgId/日期/文件名
- 检查路径穿越防护是否误拦截
章节来源
结论
本系统实现了灵活的文件存储策略,支持本地与 OSS 两种模式,并通过 bf_files 表统一管理文件元数据。通过时间分目录与 UUID 命名保障可扩展性与唯一性,配合安全的路径校验与可选的 OSS 直传,满足常见业务需求。后续可进一步增强签名 URL、访问审计与跨域策略。
附录
环境变量与配置示例
- 应用端口与上下文:server.port、server.servlet.context-path
- JWT 密钥与过期:fms.auth.jwt-secret、fms.auth.jwt-expiration
- 数据库连接池:fms.database.pool.*
- 文件存储:
- fms.file.storage-type: local 或 aliyun-oss
- fms.file.local.storage-dir: 本地存储根目录
- fms.file.local.url-prefix: 静态访问前缀
- fms.file.oss.g3oss-url: g3oss 服务地址
章节来源
备份、迁移与灾难恢复策略
- 本地文件备份:
- 定期打包 storageDir 下的 orgId 目录
- 建议使用增量备份与异地容灾
- 元数据备份:
- 对 bf_files 表执行常规备份(数据库层面)
- 迁移:
- 通过迁移脚本维护表结构与数据一致性
- 在变更存储策略时,优先保证 bf_files 与物理文件的一致性
- 灾难恢复:
- 恢复 bf_files 与对应文件目录
- 校验文件完整性(大小、哈希)
- 必要时重建索引与统计信息
章节来源