Files
workspace/code/fms/.qoder/repowiki/zh/content/文件管理系统/文件存储策略.md
T
2026-08-23 21:02:41 +08:00

15 KiB
Raw Blame History

文件存储策略

**本文引用的文件** - [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)

目录

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能考虑
  8. 故障排查指南
  9. 结论
  10. 附录

简介

本文件存储策略文档面向 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}"]

图表来源

章节来源

核心组件

  • 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 与对应文件目录
    • 校验文件完整性(大小、哈希)
    • 必要时重建索引与统计信息

章节来源