主题
文件存储 PRD
文档信息
| 项目 | 内容 |
|---|---|
| 产品名称 | PMForge - 文件存储 |
| 文档版本 | v2.0 |
| 创建日期 | 2026-09-17 |
| 最后更新 | 2026-09-24 |
| 文档状态 | 评审中 |
| 优先级 | P0 |
一、功能概述
1.1 功能定位
文件存储是 PMForge 平台的"文件仓库管理员"——所有需要上传、保存、下载、预览文件的业务场景(用户头像、商品图片、合同附件、导入 Excel 等),都通过它来统一管理。
它的核心价值在于**"一次接入、处处可用"**:运维人员只需配置好一种存储方案(比如阿里云 OSS),之后所有业务模块上传文件时都会自动存到 OSS 上,开发者不需要在每个模块里单独写上传逻辑。
同时支持 5 种存储方式——数据库、本地磁盘、FTP、SFTP、S3 兼容对象存储——覆盖了从"小型部署不想额外买存储"到"大规模生产用云存储"的全部场景。
1.2 目标用户
| 用户类型 | 核心诉求 | 典型操作 |
|---|---|---|
| 运维/系统管理员 | 配置好存储方案,确保文件存得稳、取得到 | 新建 S3 配置 → 测试连通 → 设为主配置 |
| 后台管理员 | 查看和管理系统中已有的文件,清理过期资源 | 按时间筛选 → 预览确认 → 批量删除 |
| 后台开发者 | 在业务代码中调用上传/下载接口,不关心底层存在哪 | 调用 uploadFile() 或 getFileUrl() |
| 前台用户 | 上传头像、业务图片,下载所需附件 | 点击上传 → 选择文件 → 看到预览 |
1.3 业务价值
- 降低开发成本:业务模块不需要关心文件存在本地还是云端,调用统一接口即可,换存储方案时业务代码零改动
- 节省服务器带宽:大文件通过"预签名 URL"直传到云存储,不经过后端服务器,100 个用户同时上传 50MB 文件也不会把服务器带宽打满
- 灵活切换存储方案:开发环境用本地磁盘(零成本),测试环境用 MinIO(自建对象存储),生产环境用阿里云 OSS——只需在界面上切换配置,不改代码
- 多租户安全隔离:每个租户的文件互不可见,数据不串台
- 文件全生命周期可追溯:谁上传的、什么时候上传的、存在哪个配置下,都有记录可查
1.4 功能范围
| 功能分类 | 后台管理端 | 前台用户端 | 优先级 |
|---|---|---|---|
| 文件列表查看与搜索 | ✅ | — | P0 |
| 文件详情查看 | ✅ | — | P0 |
| 文件删除(单个/批量) | ✅ | — | P0/P1 |
| 文件预览与下载 | ✅ | ✅ | P0 |
| 后端上传文件(小文件) | ✅ | ✅ | P0 |
| 前端直传(预签名 URL,大文件) | ✅ | ✅ | P0 |
| 存储配置管理(增删改查) | ✅ | — | P0 |
| 设置主配置 | ✅ | — | P0 |
| 配置连通性测试 | ✅ | — | P0 |
| 头像/图片上传 | — | ✅ | P0 |
二、用户场景
2.1 用户角色
| 角色 | 描述 | 核心诉求 |
|---|---|---|
| 运维工程师小陈 | 负责服务器和基础设施配置 | 快速配好存储方案,上线前验证通过 |
| 后台管理员小丽 | 管理系统资源和数据 | 方便地查看、清理文件,释放存储空间 |
| 后台开发者小韩 | 负责业务模块的后端开发 | 调用简单接口完成文件上传下载,不关心底层 |
| 前台用户小林 | 使用系统的普通用户 | 快速上传头像和业务图片,流畅不卡顿 |
2.2 使用场景
场景1:运维工程师配置阿里云 OSS 存储
- 用户:运维工程师小陈
- 前置条件:已在阿里云开通 OSS 服务,创建了 Bucket,拿到了 AccessKey 和 AccessSecret
- 操作步骤:
- 登录后台 → 进入"基础设施 → 文件存储 → 存储配置"页面
- 点击"新增"按钮 → 弹出配置表单
- 存储器类型选择"S3 兼容对象存储"
- 填写配置名:"阿里云 OSS - 生产环境"
- 填写 endpoint:
oss-cn-hangzhou.aliyuncs.com - 填写 bucket:
pmforge-prod - 填写 accessKey 和 accessSecret
- "是否公开访问"选"是"(图片需要外链直接访问)
- 点击"保存"
- 回到列表,点击该配置的"测试"按钮
- 系统提示"测试成功",并展示测试文件的访问 URL
- 点击"设为主配置" → 确认
- 期望:配置完成后,所有新上传的文件自动存到阿里云 OSS
- 异常处理:
- 如果测试失败,系统提示具体原因(如"连接超时""认证失败""Bucket 不存在"),小陈根据提示排查
- 如果忘了填 bucket,保存时表单校验直接拦截
场景2:管理员清理过期文件
- 用户:后台管理员小丽
- 场景:季度末清理系统中过期的导入文件和临时附件
- 操作步骤:
- 进入"文件管理"页面
- 在搜索栏选择创建时间范围:"2026-01-01 至 2026-06-30"
- 在文件类型中输入"xlsx"筛选 Excel 文件
- 浏览列表,勾选确认不再需要的文件
- 点击"批量删除" → 弹窗确认"确定删除选中的 23 个文件吗?"
- 点击"确定" → 提示"删除成功"
- 期望:文件记录删除的同时,存储介质中的实际文件也被清理,释放存储空间
- 异常处理:
- 如果某个文件在存储介质中已经不存在了(手动删过),系统只删除数据库记录,不报错
场景3:前台用户上传头像
- 用户:前台用户小林
- 场景:小林想换一张个人头像
- 操作步骤:
- 进入"个人中心" → 点击头像区域
- 弹出文件选择框 → 选择一张 jpg 图片(2MB)
- 系统自动裁剪为正方形 → 预览确认
- 点击"确定上传" → 进度条一闪而过
- 头像更新成功
- 期望:上传速度快(< 3 秒),头像立即可见
- 背后发生了什么:前端调用
/app-api/infra/file/upload接口 → 后端使用主配置(阿里云 OSS)存储 → 文件名自动改为基于内容哈希的名字(避免重名覆盖)→ 返回 OSS 上的访问 URL → 前端用这个 URL 展示新头像
场景4:前端直传大文件(预签名 URL 模式)
- 用户:系统自动 / 前端应用
- 场景:用户需要上传一个 200MB 的项目附件
- 为什么不用普通上传:200MB 文件如果先传到后端、后端再转存到 OSS,服务器带宽会被占满,其他用户操作会变卡
- 操作步骤:
- 前端先调用
/admin-api/infra/file/presigned-url?name=bigfile.zip→ 后端返回一个"预签名上传 URL" - 前端拿到这个 URL,直接把文件 PUT 到 OSS(不经过后端服务器)
- 上传完成后,前端调用
/admin-api/infra/file/create告诉后端"文件已经传好了,请记录一下" - 后端在
infra_file表中写入文件元信息(名称、大小、路径、URL)
- 前端先调用
- 期望:200MB 文件上传不占用后端带宽,上传速度取决于用户到 OSS 的网速
场景5:开发者在业务模块中集成文件上传
- 用户:后台开发者小韩
- 场景:小韩在开发"商品管理"模块,需要给商品加图片上传功能
- 操作步骤:
- 在商品 Service 中注入
FileClient(文件客户端) - 调用
fileClient.createFile(inputStream, fileName)→ 返回文件 URL - 把 URL 存到商品表的
imageUrl字段 - 前端展示商品图片时,直接用这个 URL
- 在商品 Service 中注入
- 期望:不需要关心文件存在哪里(本地?OSS?),换存储方案时商品模块代码零改动
- 为什么这样设计:所有存储细节(存在哪、怎么存、URL 怎么拼)都封装在
FileClient里,业务代码只需要一行调用
场景6:从本地存储迁移到云存储
- 用户:运维工程师小陈
- 场景:系统最初用本地磁盘存储,现在要迁移到阿里云 OSS
- 操作步骤:
- 新建一个 OSS 存储配置 → 测试通过 → 设为主配置
- 此后所有新上传的文件自动存到 OSS
- 历史文件仍然可以通过原来的本地配置访问(因为每个文件记录都关联了它上传时用的配置 ID)
- 期望:迁移过程平滑,历史文件不受影响,新文件自动走新通道
2.3 用户故事
| 编号 | 用户故事 | 优先级 | 验收标准 |
|---|---|---|---|
| US-01 | 作为管理员,我希望能查看系统中所有已上传文件的列表,以便管理文件资源 | P0 | ① 支持按文件路径模糊搜索 ② 支持按文件类型筛选 ③ 支持按创建时间范围筛选 ④ 默认按创建时间倒序 ⑤ 分页响应 < 500ms |
| US-02 | 作为管理员,我希望能配置多种存储后端,以便灵活选择存储方案 | P0 | ① 支持数据库/本地/FTP/SFTP/S3 五种类型 ② 选择类型后动态渲染对应的配置表单 ③ 保存后配置立即生效 |
| US-03 | 作为管理员,我希望能将某个存储配置设为主配置,以便控制默认的文件存储位置 | P0 | ① 系统中同时只有一个主配置 ② 设置新主配置后旧主配置自动取消 ③ 新上传的文件使用主配置 |
| US-04 | 作为管理员,我希望能测试存储配置是否正确,以便上线前验证可用性 | P0 | ① 点击测试后上传一个测试文件 ② 成功时返回测试文件 URL ③ 失败时返回具体错误原因 ④ 测试响应 < 5s |
| US-05 | 作为管理员,我希望能删除不需要的文件,以便释放存储空间 | P0 | ① 删除时同步删除存储介质中的实际文件 ② 删除前需二次确认 ③ 存储介质中不存在时仅删数据库记录 |
| US-06 | 作为管理员,我希望能批量删除文件,以便高效清理过期文件 | P1 | ① 支持勾选多个文件批量删除 ② 批量删除时逐个处理,单个失败不影响其他 |
| US-07 | 作为用户,我希望能通过后端接口上传文件,以便快速上传小文件 | P0 | ① 文件名自动生成(内容哈希),避免重名 ② 上传后返回文件访问 URL ③ < 5MB 文件上传响应 < 3s |
| US-08 | 作为用户,我希望能通过预签名 URL 直传大文件,以便不占用后端带宽 | P0 | ① 获取预签名 URL 响应 < 200ms ② 前端直传后需调用 create 接口记录元信息 ③ 直传文件可正常下载预览 |
| US-09 | 作为前台用户,我希望能上传头像,以便展示个人形象 | P0 | ① 支持 jpg/png 格式 ② 上传后立即可见新头像 ③ 建议限制 5MB 以内 |
| US-10 | 作为前台用户,我希望能下载和预览文件,以便查看所需内容 | P0 | ① 图片类型在浏览器中直接预览 ② 其他类型以附件形式下载 ③ 文件下载无需登录 ④ 首字节响应 < 500ms |
| US-11 | 作为系统,我需要确保文件目录参数不包含非法路径,以防止目录穿越攻击 | P0 | ① 目录参数不允许包含 .. ② 非法路径直接拒绝并返回错误 |
三、功能需求
3.1 后台管理端 - 文件管理
3.1.1 功能清单
| 功能 | 优先级 | 权限标识 | 说明 |
|---|---|---|---|
| 文件列表(分页) | P0 | infra:file:query | 分页展示文件,支持搜索筛选 |
| 文件详情 | P0 | infra:file:query | 查看单个文件详细信息 |
| 文件删除 | P0 | infra:file:delete | 删除单个文件(含存储介质) |
| 批量删除文件 | P1 | infra:file:delete | 批量删除多个文件 |
| 文件预览/下载 | P0 | 免登录 | 通过配置 ID 和路径访问文件 |
| 后端上传文件 | P0 | 登录即可 | 通过后端接口上传单个文件 |
| 获取预签名 URL | P0 | 登录即可 | 获取前端直传的预签名地址 |
| 创建文件记录 | P0 | 登录即可 | 前端直传后记录文件元信息 |
3.1.2 文件列表(分页)
页面描述:

业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 默认按创建时间倒序排列 | 最新上传的文件排在前面,方便管理员快速找到刚上传的文件 |
| R-02 | 支持按文件路径模糊搜索 | 存储文件名是系统生成的哈希值,但管理员可能通过路径中的目录名来定位 |
| R-03 | 支持按文件类型(MIME)模糊搜索 | 方便筛选特定类型的文件(如只找图片、只找 PDF) |
| R-04 | 支持按创建时间范围筛选 | 方便按时间段批量定位和清理文件 |
| R-05 | 列表不返回文件内容(content),仅返回元信息 | 列表页只需要展示信息,加载文件内容会严重拖慢性能 |
数据字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | Long | 文件编号 |
| configId | Long | 配置编号(关联存储配置) |
| name | String | 原文件名(用户上传时的文件名) |
| path | String | 文件路径(系统生成的存储文件名,含目录) |
| url | String | 文件访问地址(完整的 URL) |
| type | String | 文件 MIME 类型(如 image/png、application/pdf) |
| size | Long | 文件大小(字节) |
| createTime | LocalDateTime | 创建时间 |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 获取文件分页 | GET | /admin-api/infra/file/page | 获取文件分页列表 |
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| path | String | 否 | 文件路径(模糊匹配) |
| type | String | 否 | 文件类型(模糊匹配) |
| createTime | LocalDateTime[] | 否 | 创建时间范围 |
| pageNo | Integer | 是 | 页码 |
| pageSize | Integer | 是 | 每页条数 |
返回结果: 标准分页结构(list + total)
3.1.3 文件删除
业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 删除数据库中的文件记录 | 保持数据库记录与实际存储的一致性 |
| R-02 | 同时删除存储介质中的实际文件 | 避免产生"孤儿文件"占用存储空间 |
| R-03 | 删除前需二次确认 | 防止误操作,删除后文件不可恢复 |
| R-04 | 存储介质中文件不存在时,仅删除数据库记录,不报错 | 可能已被手动清理,不应阻塞数据库记录清理 |
| R-05 | 批量删除时逐个处理,单个失败不影响其他 | 避免因一个文件异常导致整批删除失败 |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 删除文件 | DELETE | /admin-api/infra/file/delete | 删除单个文件 |
| 批量删除文件 | DELETE | /admin-api/infra/file/delete-list | 批量删除文件 |
请求参数(删除):
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 文件编号 |
请求参数(批量删除):
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ids | Long[] | 是 | 文件编号列表 |
3.1.4 文件预览/下载
页面描述:
- 图片类型(jpg/png/gif/webp)→ 在浏览器中直接预览(Content-Type 返回对应的 image/* 类型)
- 其他类型 → 以附件形式下载(Content-Disposition: attachment)
- 该接口无需登录即可访问(PermitAll),且忽略租户隔离
为什么文件下载不需要登录:文件 URL 会嵌入在业务数据中(如商品图片、用户头像),如果每次加载图片都要登录态,前端展示会非常复杂且影响性能。文件 URL 本身包含的 configId + path 已经足够定位文件。
业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | URL 格式:/admin-api/infra/file/{configId}/get/{path} | 通过 configId 定位使用哪个存储配置,通过 path 定位具体文件 |
| R-02 | 无需登录即可访问 | 文件 URL 需要能在各种场景下直接使用(邮件、分享、前端 img 标签) |
| R-03 | 忽略租户隔离 | 文件 URL 不区分租户,确保跨租户场景下也能正常访问 |
| R-04 | 文件不存在时返回 404 | 标准的 HTTP 语义,前端和浏览器都能正确处理 |
| R-05 | 支持中文及特殊字符路径(自动 URL 解码) | 原文件名可能是中文,存储路径需要正确处理 |
| R-06 | 以附件形式返回,文件名取原文件名 | 下载时使用用户能理解的文件名,而非哈希值 |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 下载文件 | GET | /admin-api/infra/file/{configId}/get/** | 根据配置 ID 和路径下载文件 |
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| configId | Long | 是 | 存储配置编号 |
| path | String | 是 | 文件路径(通配路径) |
3.1.5 后端上传文件
页面描述:
- 通过表单方式(multipart/form-data)上传文件
- 可指定文件目录(directory),文件最终路径为
{directory}/{系统生成的文件名} - 上传成功后返回文件的访问 URL
两种上传模式对比:

业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 使用当前主配置(master=true)的存储配置进行上传 | 业务代码不需要指定存在哪,系统自动选择当前配置的存储方案 |
| R-02 | 文件名由系统自动生成(基于内容 SHA256 + 原始扩展名) | ① 避免文件名冲突(两个人上传同名文件不会覆盖)② 相同内容的文件只存一份(节省空间) |
| R-03 | 文件目录格式需合法,不允许包含 .. 等非法路径 | 防止目录穿越攻击(安全要求) |
| R-04 | 上传完成后在 infra_file 表中记录文件元信息 | 方便后续查询、管理、统计 |
| R-05 | 返回文件访问 URL | 前端拿到 URL 后可直接用于展示或存入业务表 |
接口设计(模式一:后端上传):
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 上传文件 | POST | /admin-api/infra/file/upload | 后端上传文件 |
请求参数(multipart/form-data):
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | MultipartFile | 是 | 文件附件 |
| directory | String | 否 | 文件目录(如 product/images) |
返回结果:
| 字段名 | 类型 | 说明 |
|---|---|---|
| data | String | 文件访问 URL |
接口设计(模式二:前端直传 - 步骤1 获取预签名 URL):
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 获取预签名地址 | GET | /admin-api/infra/file/presigned-url | 获取前端直传的预签名上传 URL |
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | String | 是 | 文件名称 |
| directory | String | 否 | 文件目录 |
返回结果:
| 字段名 | 类型 | 说明 |
|---|---|---|
| configId | Long | 配置编号 |
| uploadUrl | String | 预签名上传 URL(前端用这个 URL 直传文件到存储) |
| url | String | 文件访问 URL(上传完成后可通过此 URL 访问) |
| path | String | 文件路径(直传后需用此 path 调用 create 接口) |
接口设计(模式二:前端直传 - 步骤2 记录文件元信息):
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 创建文件记录 | POST | /admin-api/infra/file/create | 前端直传完成后记录文件元信息 |
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| configId | Long | 是 | 文件配置编号(取自预签名 URL 返回的 configId) |
| path | String | 是 | 文件路径(取自预签名 URL 返回的 path) |
| name | String | 是 | 原文件名 |
| url | String | 是 | 文件访问 URL |
| type | String | 否 | 文件 MIME 类型 |
| size | Long | 是 | 文件大小(字节) |
返回结果:
| 字段名 | 类型 | 说明 |
|---|---|---|
| data | Long | 新创建的文件记录 ID |
3.2 后台管理端 - 存储配置管理
3.2.1 功能清单
| 功能 | 优先级 | 权限标识 | 说明 |
|---|---|---|---|
| 配置列表(分页) | P0 | infra:file-config:query | 分页展示存储配置 |
| 配置详情 | P0 | infra:file-config:query | 查看单个配置详细信息 |
| 创建配置 | P0 | infra:file-config:create | 新增存储后端配置 |
| 更新配置 | P0 | infra:file-config:update | 修改存储配置 |
| 删除配置 | P0 | infra:file-config:delete | 删除存储配置 |
| 批量删除配置 | P1 | infra:file-config:delete | 批量删除多个配置 |
| 设置主配置 | P0 | infra:file-config:update | 将某个配置设为默认 |
| 配置测试 | P0 | infra:file-config:query | 测试配置是否可用 |
3.2.2 配置列表(分页)
页面描述:

业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 默认按创建时间倒序排列 | 最新创建的配置排在前面 |
| R-02 | 支持按配置名模糊搜索 | 配置多了之后方便快速定位 |
| R-03 | 支持按存储器类型筛选 | 方便查看某一类存储的配置 |
| R-04 | 主配置通过特殊标识(⭐)高亮显示 | 让管理员一眼看出当前文件存在哪里 |
数据字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | Long | 配置编号 |
| name | String | 配置名(如"阿里云 OSS - 生产环境") |
| storage | Integer | 存储器类型(见枚举表) |
| master | Boolean | 是否为主配置 |
| config | Object | 存储配置详情(不同存储器结构不同) |
| remark | String | 备注 |
| createTime | LocalDateTime | 创建时间 |
存储器类型枚举:
| 枚举值 | 存储器类型 | 适用场景 | 成本 |
|---|---|---|---|
| 1 | 数据库(DB) | 极小文件(头像图标),不想额外部署存储服务 | 高(占用数据库空间) |
| 10 | 本地磁盘(Local) | 开发环境、单机部署 | 低(但不好扩展) |
| 11 | FTP | 已有 FTP 服务器的传统企业 | 低 |
| 12 | SFTP | 需要安全传输的企业 | 低 |
| 20 | S3 兼容对象存储 | 生产环境推荐,支持阿里云 OSS / 腾讯云 COS / MinIO 等 | 按需付费,推荐 |
3.2.3 创建存储配置
页面描述:

各存储器配置参数:
数据库(storage=1):
| 配置项 | 类型 | 必填 | 说明 |
|---|---|---|---|
| domain | String(URL) | 是 | 自定义域名,用于生成文件访问 URL |
本地磁盘(storage=10):
| 配置项 | 类型 | 必填 | 说明 |
|---|---|---|---|
| basePath | String | 是 | 本地存储基础路径(如 /data/uploads) |
| domain | String(URL) | 是 | 自定义域名,用于生成文件访问 URL |
FTP(storage=11):
| 配置项 | 类型 | 必填 | 说明 |
|---|---|---|---|
| basePath | String | 是 | FTP 服务器上的基础路径 |
| domain | String(URL) | 是 | 自定义域名 |
| host | String | 是 | FTP 主机地址 |
| port | Integer | 是 | FTP 端口号 |
| username | String | 是 | FTP 用户名 |
| password | String | 是 | FTP 密码 |
| mode | String | 是 | 连接模式(Active/Passive) |
SFTP(storage=12):
| 配置项 | 类型 | 必填 | 说明 |
|---|---|---|---|
| basePath | String | 是 | SFTP 服务器上的基础路径 |
| domain | String(URL) | 是 | 自定义域名 |
| host | String | 是 | SFTP 主机地址 |
| port | Integer | 是 | SFTP 端口号 |
| username | String | 是 | SFTP 用户名 |
| password | String | 是 | SFTP 密码 |
S3 兼容对象存储(storage=20):
| 配置项 | 类型 | 必填 | 说明 |
|---|---|---|---|
| endpoint | String | 是 | 节点地址(如 oss-cn-hangzhou.aliyuncs.com) |
| domain | String(URL) | 否 | 自定义域名(七牛云必填) |
| bucket | String | 是 | 存储 Bucket 名称 |
| accessKey | String | 是 | 访问 Key |
| accessSecret | String | 是 | 访问 Secret |
| enablePathStyleAccess | Boolean | 是 | 是否启用 PathStyle(MinIO 通常为 true) |
| enablePublicAccess | Boolean | 是 | 是否公开访问 |
| region | String | 否 | 区域(AWS S3 必填) |
S3 兼容云厂商对照表:
| 云厂商 | endpoint 示例 | domain 说明 | region 说明 |
|---|---|---|---|
| 阿里云 OSS | oss-cn-hangzhou.aliyuncs.com | 绑定自定义域名 | 自动识别 |
| 腾讯云 COS | cos.ap-guangzhou.myqcloud.com | 绑定自定义域名 | 自动识别 |
| 七牛云 | s3.cn-south-1.qiniucs.com | 必须配置 domain | 自动识别 |
| MinIO | http://127.0.0.1:9000 | 通过 Nginx 配置 | 不需要 |
| 华为云 OBS | obs.cn-north-4.myhuaweicloud.com | 绑定自定义域名 | 自动识别 |
| 火山云 TOS | tos-s3-cn-beijing.volces.com | 绑定自定义域名 | 自动识别 |
| AWS S3 | s3.us-east-1.amazonaws.com | 绑定自定义域名 | 必须填写 |
为什么支持这么多云厂商:S3 是对象存储的事实标准协议,几乎所有云厂商的对象存储都兼容 S3 API。PMForge 统一使用 S3 协议对接,所以一套代码就能支持所有主流云存储。
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 创建配置 | POST | /admin-api/infra/file-config/create | 创建存储配置 |
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | String | 是 | 配置名 |
| storage | Integer | 是 | 存储器类型 |
| config | Map<String, Object> | 是 | 存储配置参数(不同存储器结构不同) |
| remark | String | 否 | 备注 |
返回结果:
| 字段名 | 类型 | 说明 |
|---|---|---|
| data | Long | 新创建的配置 ID |
3.2.4 更新存储配置
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 更新配置 | PUT | /admin-api/infra/file-config/update | 更新存储配置 |
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 配置编号 |
| name | String | 是 | 配置名 |
| storage | Integer | 是 | 存储器类型 |
| config | Map<String, Object> | 是 | 存储配置参数 |
| remark | String | 否 | 备注 |
3.2.5 删除存储配置
业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 删除前需二次确认 | 删除配置后,该配置下的历史文件将无法下载 |
| R-02 | 主配置不允许删除 | 必须有一个默认存储方案,需先取消主配置身份 |
| R-03 | 已被文件引用的配置删除后,相关历史文件将无法下载 | 文件记录中的 configId 指向已删除的配置,读取时会报错 |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 删除配置 | DELETE | /admin-api/infra/file-config/delete | 删除单个存储配置 |
| 批量删除配置 | DELETE | /admin-api/infra/file-config/delete-list | 批量删除存储配置 |
请求参数(删除):
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 配置编号 |
请求参数(批量删除):
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ids | Long[] | 是 | 配置编号列表 |
3.2.6 设置主配置
业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 系统中同一时刻只能有一个主配置 | 上传文件时需要明确知道存在哪里,不能有歧义 |
| R-02 | 设置新的主配置后,原主配置自动取消 | 保证唯一性,类似"默认地址"的概念 |
| R-03 | 文件上传默认使用主配置 | 业务代码不需要指定存储位置 |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 设为主配置 | PUT | /admin-api/infra/file-config/update-master | 将指定配置设为默认主配置 |
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 配置编号 |
3.2.7 配置测试
页面描述:
- 点击"测试"按钮后,系统使用该配置上传一个测试文件
- 测试成功 → 返回测试文件的访问 URL(可以点击验证)
- 测试失败 → 返回具体的错误原因
业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 上传一个测试文件到目标存储 | 只有真正上传成功才能证明配置正确 |
| R-02 | 测试成功返回测试文件的访问 URL | 管理员可以点击 URL 验证文件是否真的可访问 |
| R-03 | 测试失败返回具体的错误原因 | 方便管理员排查(连接失败?认证失败?Bucket 不存在?) |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 测试配置 | GET | /admin-api/infra/file-config/test | 测试存储配置是否可用 |
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 配置编号 |
返回结果:
| 字段名 | 类型 | 说明 |
|---|---|---|
| data | String | 测试文件的访问 URL(成功时返回) |
3.3 前台用户端
3.3.1 功能清单
| 功能 | 优先级 | 说明 |
|---|---|---|
| 上传文件(后端模式) | P0 | 通过后端接口上传文件(头像、图片等) |
| 获取预签名 URL | P0 | 获取前端直传的预签名地址 |
| 创建文件记录 | P0 | 前端直传完成后记录文件元信息 |
3.3.2 上传文件
业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 使用当前主配置的存储配置进行上传 | 前台用户不需要关心文件存在哪里 |
| R-02 | 文件名由系统自动生成 | 避免文件名冲突 |
| R-03 | 可指定文件目录(如 avatar、images) | 不同类型的文件分目录存储,方便管理 |
| R-04 | 文件目录不允许包含 .. 等非法路径 | 防止目录穿越攻击 |
| R-05 | 头像上传建议限制格式为 jpg/png,大小不超过 5MB | 头像不需要太大,限制大小节省存储空间 |
| R-06 | 前台上传接口当前为 PermitAll(免登录) | 生产环境需评估:如果只允许登录用户上传,应改为"登录即可" |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 上传文件 | POST | /app-api/infra/file/upload | 前台上传文件 |
| 获取预签名地址 | GET | /app-api/infra/file/presigned-url | 获取前端直传预签名 URL |
| 创建文件记录 | POST | /app-api/infra/file/create | 前端直传后记录文件元信息 |
请求参数(上传文件,multipart/form-data):
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | MultipartFile | 是 | 文件附件 |
| directory | String | 否 | 文件目录(如 avatar) |
返回结果(上传文件):
| 字段名 | 类型 | 说明 |
|---|---|---|
| data | String | 文件访问 URL |
请求参数(获取预签名地址):
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | String | 是 | 文件名称 |
| directory | String | 否 | 文件目录 |
返回结果(获取预签名地址):
| 字段名 | 类型 | 说明 |
|---|---|---|
| configId | Long | 配置编号 |
| uploadUrl | String | 预签名上传 URL |
| url | String | 文件访问 URL |
| path | String | 文件路径 |
请求参数(创建文件记录):
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| configId | Long | 是 | 文件配置编号 |
| path | String | 是 | 文件路径 |
| name | String | 是 | 原文件名 |
| url | String | 是 | 文件访问 URL |
| type | String | 否 | 文件 MIME 类型 |
| size | Long | 是 | 文件大小(字节) |
返回结果(创建文件记录):
| 字段名 | 类型 | 说明 |
|---|---|---|
| data | Long | 新创建的文件记录 ID |
3.3.3 文件下载/预览
前台用户的文件下载/预览复用后台管理端的下载接口(/admin-api/infra/file/{configId}/get/**),该接口无需鉴权且忽略租户隔离。
四、非功能需求
4.1 性能要求
| 指标 | 目标值 | 说明 |
|---|---|---|
| 文件分页查询 | < 500ms | 文件表数据量大时需关注索引优化 |
| 文件详情查询 | < 200ms | 单条记录查询 |
| 小文件上传(< 5MB) | < 3s | 包含网络传输时间 |
| 预签名 URL 获取 | < 200ms | 仅生成 URL,不涉及文件传输 |
| 文件下载首字节 | < 500ms | 用户感知"开始下载"的速度 |
| 配置测试 | < 5s | 包含上传测试文件到远端存储的时间 |
4.2 安全要求
| 安全项 | 实现方式 | 说明 |
|---|---|---|
| 目录穿越防护 | 文件目录参数校验,不允许包含 .. | 防止攻击者通过路径参数访问系统敏感文件 |
| 敏感信息保护 | accessKey、accessSecret、password 等加密存储 | 防止数据库泄露导致存储凭证泄露 |
| 权限控制 | 后台管理端接口需对应权限标识 | 防止未授权用户管理文件和配置 |
| 文件类型校验 | 上传时校验 MIME 类型 | 防止上传可执行文件等危险文件 |
| 前台接口鉴权评估 | 生产环境评估是否需要登录 | 避免匿名用户滥用上传功能 |
4.3 兼容性要求
| 端 | 要求 |
|---|---|
| PC 浏览器 | Chrome 80+、Firefox 75+、Safari 13+ |
| 移动端 | iOS Safari 12+、Android Chrome 80+ |
| 文件上传 | 支持主流浏览器的 File API 和 FormData |
4.4 可扩展性要求
| 要求 | 说明 |
|---|---|
| 存储后端扩展 | 新增存储后端只需实现 FileClient 接口和对应的 Config 类,并注册到 FileStorageEnum 枚举 |
| 配置参数动态化 | 存储配置参数使用 Map 动态接收,前端根据存储器类型渲染对应表单 |
| 未来可扩展方向 | ① 文件分片上传(超大文件)② 文件 CDN 加速集成 ③ 文件版本管理 ④ 存储空间配额管理 |
五、数据设计
5.1 数据模型
文件表(infra_file)
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | BIGINT | 是 | 文件编号(主键,自增) |
| config_id | BIGINT | 是 | 配置编号(关联 infra_file_config.id) |
| name | VARCHAR(256) | 是 | 原文件名 |
| path | VARCHAR(512) | 是 | 文件路径(系统生成的存储文件名) |
| url | VARCHAR(512) | 是 | 文件访问地址 |
| type | VARCHAR(128) | 否 | 文件 MIME 类型 |
| size | INT | 否 | 文件大小(字节) |
| creator | VARCHAR(64) | 否 | 创建者 |
| create_time | DATETIME | 否 | 创建时间 |
| updater | VARCHAR(64) | 否 | 更新者 |
| update_time | DATETIME | 否 | 更新时间 |
| deleted | BIT(1) | 是 | 删除标记(0-未删除 1-已删除) |
| tenant_id | BIGINT | 否 | 租户编号(该表忽略租户隔离) |
租户隔离说明:该表标注了
@TenantIgnore,即忽略租户隔离,所有租户共享文件记录。这是因为文件 URL 需要跨租户可访问(如系统级资源)。
索引设计:
| 索引名 | 字段 | 类型 | 说明 |
|---|---|---|---|
| idx_config_id | config_id | 普通索引 | 按配置查询文件 |
| idx_create_time | create_time | 普通索引 | 按时间范围筛选文件 |
文件配置表(infra_file_config)
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | BIGINT | 是 | 配置编号(主键,自增) |
| name | VARCHAR(64) | 是 | 配置名 |
| storage | INT | 是 | 存储器类型(1-DB 10-Local 11-FTP 12-SFTP 20-S3) |
| master | BIT(1) | 是 | 是否为主配置(0-否 1-是) |
| config | VARCHAR(4096) | 是 | 存储配置(JSON 格式) |
| remark | VARCHAR(256) | 否 | 备注 |
| creator | VARCHAR(64) | 否 | 创建者 |
| create_time | DATETIME | 否 | 创建时间 |
| updater | VARCHAR(64) | 否 | 更新者 |
| update_time | DATETIME | 否 | 更新时间 |
| deleted | BIT(1) | 是 | 删除标记 |
| tenant_id | BIGINT | 否 | 租户编号(该表忽略租户隔离) |
config 字段说明:JSON 格式,不同存储器的配置参数结构不同。例如 S3 类型存储 endpoint、bucket、accessKey 等;本地类型存储 basePath、domain 等。
索引设计:
| 索引名 | 字段 | 类型 | 说明 |
|---|---|---|---|
| uk_master | master | 唯一索引(partial) | 保证只有一个主配置(master=1 只有一条) |
文件内容表(infra_file_content)
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | BIGINT | 是 | 编号(主键,自增) |
| config_id | BIGINT | 是 | 配置编号 |
| path | VARCHAR(512) | 是 | 文件路径(与 infra_file.path 对应) |
| content | LONGBLOB | 是 | 文件二进制内容 |
| creator | VARCHAR(64) | 否 | 创建者 |
| create_time | DATETIME | 否 | 创建时间 |
| updater | VARCHAR(64) | 否 | 更新者 |
| update_time | DATETIME | 否 | 更新时间 |
| deleted | BIT(1) | 是 | 删除标记 |
| tenant_id | BIGINT | 否 | 租户编号(该表忽略租户隔离) |
仅用于数据库存储模式:当 storage=1(数据库存储)时,文件的二进制内容存储在此表的
content字段中。其他存储模式(本地/FTP/S3 等)不使用此表。
5.2 数据关系

5.3 数据字典
| 字典类型 | 字典值 | 说明 |
|---|---|---|
| infra_file_storage | 1-数据库、10-本地磁盘、11-FTP、12-SFTP、20-S3 对象存储 | 存储器类型 |
| infra_file_ftp_mode | Active-主动模式、Passive-被动模式 | FTP 连接模式 |
六、跨模块联动
6.1 联动关系总览
| 联动模块 | 联动方式 | 数据流向 | 说明 |
|---|---|---|---|
| 用户管理 | 头像 URL 存储 | 用户模块 → 文件存储 | 用户上传头像后,文件 URL 存入用户表的 avatar 字段 |
| 商品管理(商城模块) | 商品图片存储 | 商品模块 → 文件存储 | 商品图片上传后,URL 存入商品表的 image 字段 |
| 工作流(附件) | 流程附件存储 | 工作流模块 → 文件存储 | 审批流程中的附件通过文件存储上传 |
| 数据字典 | 存储器类型字典 | 字典模块 → 文件存储 | 提供存储器类型的字典数据 |
| 日志审计 | 操作日志记录 | 文件存储 → 日志模块 | 文件上传/删除等操作记录到操作日志 |
| 租户管理 | 租户隔离 | 租户模块 → 文件存储 | 文件存储忽略租户隔离(@TenantIgnore),但业务层按需控制 |
| 配置管理 | 系统参数 | 配置模块 → 文件存储 | 可通过参数配置上传文件大小限制等 |
6.2 关键联动流程
用户头像上传全流程
![]()
工作流附件上传流程

6.3 数据一致性要求
| 场景 | 一致性要求 | 处理方式 |
|---|---|---|
| 文件删除 | 数据库记录与存储介质同步删除 | 先删存储介质,再删数据库记录;介质不存在时仅删数据库 |
| 配置删除 | 历史文件可访问性 | 配置删除后,历史文件的 configId 失效,无法下载 → 删除前需二次确认 |
| 主配置切换 | 新旧文件共存 | 新文件用新主配置,旧文件仍用原配置 → 每个文件记录了自己的 configId |
七、附录
7.1 名词解释
| 术语 | 通俗解释 |
|---|---|
| 存储配置 | 告诉系统"文件存在哪里"的一份配置信息,就像快递系统的"仓库地址" |
| 主配置(Master) | 默认的存储配置。上传文件时如果不特别指定,就存在主配置指定的地方。类似手机的"默认 SIM 卡" |
| 预签名 URL | 一个"临时通行证"——后端生成一个带时效的 URL,前端拿着这个 URL 可以直接把文件传到云存储,不需要经过后端服务器。就像快递柜的取件码,不用去柜台就能存/取包裹 |
| S3 兼容存储 | S3 是亚马逊发明的对象存储协议,现在几乎所有云存储(阿里云 OSS、腾讯云 COS、MinIO 等)都支持这个协议。PMForge 用 S3 协议统一对接所有云存储 |
| MIME 类型 | 文件的"身份证类型",告诉浏览器这个文件是什么格式——image/png 是图片,application/pdf 是 PDF 文档 |
| PathStyle | S3 存储的一种 URL 格式:http://服务器地址/Bucket名/文件路径。MinIO 自建存储通常需要开启这个模式 |
| 目录穿越攻击 | 一种安全攻击方式——攻击者在文件路径中插入 ..(返回上级目录),试图访问系统敏感文件。比如 ../../etc/passwd 就能读到系统密码文件。PMForge 会拦截路径中的 .. 来防止这种攻击 |
| DB 存储模式 | 把文件二进制内容直接存在数据库里。优点是简单(不需要额外存储服务),缺点是数据库会变大变慢。适合存头像等极小文件 |
| SHA256 哈希命名 | 用文件内容的哈希值作为文件名。好处:① 同名文件不会覆盖 ② 相同内容的文件只存一份(去重) |
| Bucket | 对象存储中的"容器"概念,类似文件夹。一个 Bucket 可以装无数文件,每个文件有自己的路径 |
| FileClient | PMForge 内部的文件操作接口。业务代码通过这个接口上传/下载文件,不需要关心底层用的是哪种存储。就像用"快递 API"下单,不需要知道快递公司用的什么运输方式 |
| 前端直传 | 文件直接从浏览器传到云存储,不经过后端服务器。适合大文件场景,避免服务器带宽成为瓶颈 |
7.2 接口汇总
后台管理端 - 文件管理
| 接口名称 | 请求方式 | 接口路径 | 权限标识 | 说明 |
|---|---|---|---|---|
| 上传文件 | POST | /admin-api/infra/file/upload | 登录即可 | 后端上传文件 |
| 获取预签名地址 | GET | /admin-api/infra/file/presigned-url | 登录即可 | 获取前端直传预签名 URL |
| 创建文件记录 | POST | /admin-api/infra/file/create | 登录即可 | 前端直传后记录文件元信息 |
| 获取文件详情 | GET | /admin-api/infra/file/get | infra:file:query | 获取文件详细信息 |
| 获取文件分页 | GET | /admin-api/infra/file/page | infra:file:query | 获取文件分页列表 |
| 删除文件 | DELETE | /admin-api/infra/file/delete | infra:file:delete | 删除单个文件 |
| 批量删除文件 | DELETE | /admin-api/infra/file/delete-list | infra:file:delete | 批量删除文件 |
| 下载文件 | GET | /admin-api/infra/file/{configId}/get/** | 免登录 | 下载/预览文件 |
后台管理端 - 存储配置
| 接口名称 | 请求方式 | 接口路径 | 权限标识 | 说明 |
|---|---|---|---|---|
| 创建配置 | POST | /admin-api/infra/file-config/create | infra:file-config:create | 创建存储配置 |
| 更新配置 | PUT | /admin-api/infra/file-config/update | infra:file-config:update | 更新存储配置 |
| 删除配置 | DELETE | /admin-api/infra/file-config/delete | infra:file-config:delete | 删除单个配置 |
| 批量删除配置 | DELETE | /admin-api/infra/file-config/delete-list | infra:file-config:delete | 批量删除配置 |
| 获取配置详情 | GET | /admin-api/infra/file-config/get | infra:file-config:query | 获取配置详细信息 |
| 获取配置分页 | GET | /admin-api/infra/file-config/page | infra:file-config:query | 获取配置分页列表 |
| 设为主配置 | PUT | /admin-api/infra/file-config/update-master | infra:file-config:update | 设置默认主配置 |
| 测试配置 | GET | /admin-api/infra/file-config/test | infra:file-config:query | 测试配置可用性 |
前台用户端
| 接口名称 | 请求方式 | 接口路径 | 权限标识 | 说明 |
|---|---|---|---|---|
| 上传文件 | POST | /app-api/infra/file/upload | 免登录 | 前台上传文件 |
| 获取预签名地址 | GET | /app-api/infra/file/presigned-url | 登录即可 | 获取前端直传预签名 URL |
| 创建文件记录 | POST | /app-api/infra/file/create | 免登录 | 前端直传后记录文件元信息 |
| 下载文件 | GET | /admin-api/infra/file/{configId}/get/** | 免登录 | 下载/预览文件(复用后台接口) |
7.3 权限配置建议
| 权限标识 | 说明 | 推荐角色 |
|---|---|---|
| infra:file:query | 文件查询 | 后台管理员、系统管理员 |
| infra:file:delete | 文件删除 | 系统管理员 |
| infra:file-config:query | 存储配置查询 | 系统管理员、运维工程师 |
| infra:file-config:create | 存储配置创建 | 系统管理员 |
| infra:file-config:update | 存储配置更新 | 系统管理员 |
| infra:file-config:delete | 存储配置删除 | 系统管理员 |
7.4 注意事项
| 事项 | 说明 |
|---|---|
| 前台上传接口鉴权 | 当前前台上传接口配置为 PermitAll(免登录),生产环境应根据业务需要评估是否增加登录鉴权 |
| 文件删除策略 | 当前文件删除为物理删除(同时删除存储介质中的文件和数据库记录),如需保留历史可改为逻辑删除 |
| 大文件上传 | 对于 > 100MB 的文件,强烈建议使用前端直传(预签名 URL 模式),避免后端服务器带宽瓶颈 |
| 存储配置变更 | 修改存储配置后,已上传的历史文件仍可通过原配置访问,新上传的文件使用新的主配置 |
| 数据库存储限制 | 使用数据库存储模式时,文件内容存储在 LONGBLOB 字段中,不建议存储大文件(建议 < 1MB) |
7.5 变更记录
| 版本 | 日期 | 修改内容 | 修改人 |
|---|---|---|---|
| v1.0 | 2026-09-17 | 初始版本 | PM Team |
| v2.0 | 2026-09-19 | 全面增强:补充业务场景与人物画像、增加验收标准、新增 ASCII 页面原型、补充跨模块联动、新增名词解释、增加设计 rationale | PM Team |
本文档为文件存储模块 PRD v2.0,如有问题请联系产品负责人。