附件管理基础设施的设计与实践:从旧版组件到 lcxm-attachment
最后更新:2026-09-04
一、背景与演进
之前写过一个 attachment 组件,支持两种使用方式:
- 一种是 client mode,由业务应用通过 starter 调用独立附件服务;
- 另一种是 server mode,由业务应用自己提供附件服务并完成本地存储。
旧版本已经解决了基本上传问题,但图片压缩、缩略图、访问鉴权和生命周期管理比较分散。现在的主要使用场景是微信小程序和多个业务应用,因此重新整理了文件资源、业务关系、访问权限、状态流转和部署边界。
当前版本优先采用“独立 attachment-server + 业务 starter”的 client mode。
业务应用不代理文件流,附件服务只负责文件资源;server mode 暂不放入主流程,后续如果出现离线部署或单体应用场景,再通过独立 adapter 评估。
二、总体认识
2.1 参与者和边界
text
+------------+ +----------------------+ +----------------------+
| Client | | Business Application | | Attachment Server |
| Mini App | | API + Starter | | Upload/Access/Tasks |
+-----+------+ +----------+-----------+ +----------+-----------+
| | |
| upload-info/form | relation/status |
+---------------------->| |
| | direct upload |
+-------------------------------------------------->|
| | |
| v v
| +-------------+ +-------------+
| | Business DB | | Server DB |
| | relations | | attachments |
| +-------------+ | variants |
| +------+------+
| |
| v
| +-------------+
| | File Volume |
| +-------------+
客户端负责选择上传策略、直传文件和提交附件关系。
业务应用及 starter 负责用户权限、业务事务、关系维护和详情回填。attachment-server 负责上传校验、文件存储、图片处理、访问鉴权、派生资源和清理任务。
业务库只保存业务关系,server 库保存附件元数据和派生资源状态。
2.2 一条附件的完整生命周期
text
获取 upload-info
-> 客户端携带 JWT 直传
-> server 校验路径、应用、MIME 和图片内容
-> 按策略写入主文件,状态 INIT
-> 业务表单提交附件关系
-> 业务事务提交后同步为 USED
-> 按需创建或生成 THUMBNAIL_320
-> 详情根据场景返回主图或缩略图访问地址
-> 关系移除后同步为 DELETED
-> 清理任务删除主文件、派生文件和元数据
三、端到端流程
3.1 上传和绑定
text
CLIENT BUSINESS APP/STARTER ATTACHMENT SERVER BUSINESS DB
| | | |
|-- upload-info --------->| | |
|<-- uploadUrl/token -----| | |
| | | |
|-- multipart + JWT + policy ---------------------->| |
| | |-- verify JWT |
| | |-- verify app/path |
| | |-- inspect MIME |
| | |-- compress/resize |
| | |-- write primary |
| | |-- insert INIT |
|<-- attachmentId/key ------------------------------| |
| | | |
|-- submit business form -->| | |
| |-- save entity + relation ------------------->|
| |<-- transaction committed --------------------|
| |-- sync INIT -> USED ---->| |
上传信息中可以指定 COMPRESS_720 或 ORIGINAL。客户端默认策略由 starter 配置决定,server 在策略为空时默认使用 COMPRESS_720。上传阶段先保存主文件并登记 INIT,只有业务关系成功提交后才转为 USED,避免未绑定文件长期占用空间。
3.2 访问和派生资源
text
CLIENT BUSINESS APP/STARTER ATTACHMENT SERVER SERVER DB/FILES
| | | |
|-- request detail ------>| | |
| |-- query relation | |
| |-- choose PRIMARY/THUMB | |
|<-- accessUrl -----------| | |
| | | |
|-- private URL --------->| | |
| |-- check user permission | |
| |-- issue access JWT ---->| |
|<-- redirect URL --------| | |
|-------------------------------------------------->| |
| | |-- verify JWT |
| | |-- resolve objectKey|
| | |-- find variant |
| | |-- claim PROCESSING |
| | |-- generate if miss |
| | |-- mark READY |
|<--------------------------------------------------|-- file response ---|
public 附件可以直接访问;private 附件必须先经过业务应用的用户权限判断,再使用短期 JWT 访问地址。缩略图采用确定性 objectKey,例如 abc.jpg 对应 abc_thumbnail_320.jpg。历史附件没有缩略图记录时,可以在访问或低频任务中延迟生成。
3.3 删除和清理
text
BUSINESS APP/STARTER BUSINESS DB ATTACHMENT SERVER CLEANER
| | | |
|-- remove relation ----->| | |
|<-- transaction commit --| | |
|-- sync USED -> DELETED -------------------------->| |
| | | |
| | |<-- scheduled scan -|
| | |-- verify state/path|
| | |-- delete variants -|
| | |-- delete primary --|
| | |-- delete metadata -|
清理任务低频运行,用于兜底处理超时 INIT 和 DELETED 数据;派生文件和主文件在同一生命周期内清理。删除关系不需要先填充详情,业务端直接根据表单中的关系差异处理即可。
四、模块职责
text
lcxm-attachment-core shared DTO, entity, enum, exception, utility
lcxm-attachment-server upload, storage, auth, image, variant, cleanup
lcxm-attachment-spring-boot-starter relation, status sync, fill, private redirect
业务端通过 AttachmentOperationHelper 维护新增、保留和移除关系,通过 AttachmentFillHelper 在详情返回阶段按需填充访问地址。accessUrl 只用于响应,不写入数据库。
五、图片处理策略
当前主图策略包括:
COMPRESS_720:支持的图片按比例缩放,使最大边不超过 720;JPEG 通过质量迭代和尺寸回退控制体积,目标约 300KB、硬上限约 400KB。ORIGINAL:保留客户端文件内容,但仍执行真实格式、MIME、尺寸和路径安全校验。THUMBNAIL_320:按比例生成最大边 320 的列表和时间轴缩略图。
PNG 不使用 JPEG 的质量参数,会按比例缩放并保留透明通道。压缩结果与图片内容有关,一次实际测试中,原图 2.72MB 处理为 43.4KB 主图,THUMBNAIL_320 为 12.8KB;这不是固定压缩比例。
text
upload stream
-> controlled temporary file
-> detect real image format and dimensions
-> validate request MIME and actual MIME
-> resize/compress according to enum policy
-> write primary objectKey
-> create pending variant metadata when needed
-> remove temporary file
派生资源记录使用 PENDING -> PROCESSING -> READY/FAILED 状态。多个实例同时处理时先原子领取任务,只有领取成功的实例可以生成文件;生成完成后再更新状态,失败则保留可重试信息。
六、JWT、访问安全与业务关系
JWT 用于证明调用方的应用身份、上传参数或私有资源访问范围,不替代业务用户权限。server 会校验 token 中的 appCode、路径中的 appCode、客户端配置和请求参数是否一致,所有 objectKey 入口都进行路径安全校验。
text
业务用户权限判断(Business App)
-> 生成短期访问 JWT(Starter)
-> server 校验 token、appCode、objectKey 和 variant
-> 返回文件或重定向
附件资源与业务数据采用最终一致模型:业务库保存 t_attachment_relation,server 库保存 t_attachment、t_attachment_variant 和 t_client_config,两侧通过 attachmentId/objectKey 关联,不建立跨库强事务或外键。
七、部署与 CI/CD
text
Cloud production : prod / lcxm_attachment /xqd/attachments/prod
Home test : test / lcxm_attachment_test /xqd/attachments/test
Local development: dev
微信小程序正式访问要求 HTTPS 和合法域名,因此生产附件服务部署在云主机更合适;小主机保留 Docker 测试环境。Docker 只是运行方式,不是 Spring profile。Jenkins 在小主机构建镜像,默认发布 test;将 DEPLOY_CLOUD=true 时,再通过 docker save、scp 和云端 docker load 发布 prod。数据库、文件卷和备份必须保持同一环境边界,避免测试数据覆盖生产资源。
带宽和流量限制需要结合访问量评估。主图统一压缩、缩略图用于列表、private 资源经过权限校验,可以显著降低小程序访问流量;后续接入 CDN 或对象存储时,再补充缓存和跨地域同步策略。
八、当前未完成能力
当前 server 使用本地文件存储。MinIO、Cloudflare R2、OSS 等对象存储同步,以及同步重试、幂等、完整性校验、恢复策略尚未完成。文件 hash/秒传、分片上传、CDN 和管理后台属于后续扩展。
总结
text
业务应用负责业务数据和用户权限
starter 负责业务关系、状态同步和详情填充
attachment-server 负责文件资源、图片处理、鉴权和清理
当前有效配置、接口和接入步骤以项目 docs/ 下的正式文档为准。
附录:代码与相关文档
A.1 代码与基础设施
- 旧版附件组件:我之前实现的 attachment starter,支持 client mode 和 server mode。
- lcxm-attachment:当前附件管理基础设施。
- lcxm-cas-sso:统一登录与 SSO 基础组件。
- lcxm-springboot-basic-support:Spring Boot 基础框架依赖。
A.2 当前项目文档
- 项目总览:模块边界、快速认识和文档入口。
- 服务端设计与配置:上传、访问鉴权、图片处理、派生资源和清理任务。
- Starter 设计:业务关系、状态同步、详情填充和私有访问重定向。
- Starter 使用手册:业务应用的 properties 配置、后端调用和前端上传流程。
- 数据库设计:数据库职责、表结构和状态关系。
- 部署说明:云主机、小主机、Docker、Jenkins、域名和备份规划。
- 待办与预留能力:对象存储同步及其他尚未完成的扩展。
GitHub 登录
Gitee 登录
QQ 登录