Files
wucaixing-backend/docs/图片水印后缀异常兜底与补救方案.md
shihongwei 0adba6f5cb feat(photo-evidence): 实现图片水印异常修复全流程功能
抽离并重构原有图片水印生成逻辑,统一输出jpeg格式,避免因异常文件后缀产生坏数据
新增异常水印图片的分页查询、预览确认、单条修复、批量异步修复及任务状态查询接口
新增对应的控制器、业务服务类与数据传输对象
补充图片水印异常修复的完整方案文档
2026-07-01 23:02:00 +08:00

14 KiB
Raw Permalink Blame History

图片水印后缀异常兜底与补救方案

1. 背景

现有图片取证水印流程位于 PhotoEvidenceServiceImpl.java:L51-L149

当前已确认的问题链路如下:

  • 前端上传 OSS 时,部分原始图片没有正确的文件后缀名。
  • 原始 sys_oss.file_suffix 被写成了 23.2.3 这一类异常值。
  • 后端在生成水印图时,直接使用原始 file_suffix 作为 ImageIO.write 的输出格式。
  • 生成的临时文件可能为空文件,最终上传到 OSS 后变成 0B
  • 新生成的 sys_oss 记录中,ext1.fileSize=0contentType=null,页面预览和下载均异常。

示例数据可见 data.json:L1-L35

  • 原图记录 file_suffix=3,但 ext1.contentType=image/jpeg
  • 水印图记录 file_suffix=.3ext1.fileSize=0contentType=null

当前风险点位于 PhotoEvidenceServiceImpl.java:L137-L149 ,源码中也已经有 TODO 提示需要判断 mime

2. 根因分析

本问题的根因不是“没有后缀名”本身,而是“后端把不可信的后缀名继续当成图片编码格式使用”。

具体来说:

  • 原始图片是否可读,真正应该依据的是二进制内容和 MIME不应该只依赖文件名后缀。
  • 水印图不是前端上传文件,而是后端重新生成的系统产物,其输出格式应由后端统一决定。
  • 只要继续信任 sys_oss.file_suffix,就会持续受到历史脏数据和前端上传异常的影响。

因此,兜底思路必须从“猜正确后缀”改成“后端固定输出规范”。

3. 线上兜底方案

3.1 目标

阻断新增坏数据,确保后续所有水印图都能稳定生成、上传、展示和下载。

3.2 方案原则

  • 原图校验看内容,不看后缀。
  • 水印图输出格式由后端统一指定,不继承原图后缀。
  • 上传时必须带明确的 contentType
  • 入库前必须校验结果文件非空。

3.3 推荐方案

建议将“图片取证水印图”统一输出为 jpg/jpeg

原因:

当前水印结果图在 PhotoEvidenceServiceImpl.java:L261-L283 中已经固定为 BufferedImage.TYPE_INT_RGB,天然适合输出为 JPEG

  • 业务场景是现场拍照取证,统一输出 jpg 与实际使用习惯一致。
  • 可以彻底摆脱 23.2.3 这一类脏后缀的影响。
  • 后续历史修复和排障标准会非常统一。

3.4 兜底规则

建议按以下规则处理:

  1. 下载原始 OSS 文件后,先尝试按图片内容解析。
  2. 只要原图字节能正常解析成图片,就允许继续加水印。
  3. 水印结果图统一编码为 jpeg
  4. 临时文件统一使用 .jpg 后缀。
  5. 上传 OSS 时统一使用 contentType=image/jpeg
  6. 上传前必须校验:
    • ImageIO.write 返回成功
    • 临时文件存在
    • 临时文件大小大于 0
  7. 任一校验失败,直接报错,不创建 HotPhotoEvidence 记录,不创建新的水印 sys_oss 记录。

3.5 兜底后的收益

  • 前端即使继续上传无后缀文件,后端仍可稳定产出标准水印图。
  • 生成的水印图始终具备标准后缀和标准 contentType
  • 不再出现 .2.3 这类系统生成的异常文件。
  • 新增数据和历史补救数据将收敛到同一规范。

4. 历史补救总原则

4.1 必须满足的约束

本次历史补救必须满足以下硬约束:

  • 必须替换原有坏掉的 sys_oss 记录内容。
  • 必须保持原 oss_id 不变。
  • 不能只改 HotPhotoEvidence 表中的 watermarked_oss_id 指向。
  • 不能新增一条新的 sys_oss 记录再让业务表改引用。

原因:

  • 这个 oss_id 不仅存在于 HotPhotoEvidence,还可能被其他业务表、日志记录、导出记录、接口缓存、前端页面配置等位置复用。
  • 如果改成新 oss_id,补救范围会迅速扩散到未知引用方,风险不可控。
  • 保持原 oss_id 不变,才能把历史补救范围收敛在“文件对象替换 + 原记录修补”这一层。

4.2 正确的补救思路

正确思路不是“替换原始图的 sys_oss 记录”,而是:

  • HotPhotoEvidence 为主表定位坏掉的水印记录。
  • 通过 original_oss_id 找回原始图片。
  • 重新生成一张标准的水印图。
  • 上传到 OSS 获得新的对象文件。
  • 然后覆盖原 watermarked_oss_id 对应的 sys_oss 记录内容。
  • oss_id 保持不变,只更新该记录指向的对象信息和元数据。

换句话说:

  • 替换的是“坏水印图对应的 sys_oss 内容”
  • 保留的是“该 sys_oss 的主键 oss_id

5. 历史补救识别范围

建议先做数据筛查,再执行修补。

5.1 强特征

以下记录可直接视为高风险坏数据:

  • sys_oss.ext1.fileSize = 0
  • sys_oss.file_suffix 为纯数字,如 23
  • sys_oss.file_suffix.数字,如 .2.3
  • sys_oss.original_namephoto_evidence_xxx.2photo_evidence_xxx.3
  • sys_oss.ext1.contentType is null

5.2 建议以 HotPhotoEvidence 为主表驱动

原因:

  • 该表已经保留了 originalOssIdwatermarkedOssId
  • 已经保留了 captureTimeClientcaptureTimeServer
  • 已经保留了 latitudelongitude
  • 已经保留了 addressTextServeraddressTextClientaddressSource

这些字段足够支撑“按历史状态重建水印图”,不需要重新依赖前端数据。

6. 历史补救执行方案

6.1 核心目标

在不改变 watermarked_oss_idoss_id 的前提下,修复坏掉的水印图对象和其 sys_oss 元数据。

6.2 推荐执行步骤

  1. 根据 HotPhotoEvidence.watermarked_oss_id 关联 sys_oss,筛出疑似坏水印记录。
  2. 按记录读取 original_oss_id 对应的原始图。
  3. 使用历史记录中的 captureTimeClient 作为水印时间。
  4. 水印地址按以下优先级确定:
    • addressSource=SERVER,取 addressTextServer
    • 否则取 addressTextClient
  5. 不重新调用逆地理接口,不重算地址。
  6. 按统一规范重新生成 jpg 水印图。
  7. 先上传到 OSS 新对象路径,确认对象可访问且大小正常。
  8. 用新对象信息覆盖旧的 sys_oss 记录:
    • file_name
    • url
    • file_suffix
    • original_name
    • ext1.fileSize
    • ext1.contentType
    • 如有必要同步更新时间和更新人
  9. oss_id 保持不变。
  10. 数据更新成功后,再删除旧的坏对象文件,或先保留待统一清理。

6.3 必须覆盖的字段

覆盖原 sys_oss 记录时,建议至少同步修正以下内容:

  • file_name:替换为新上传对象键
  • url:替换为新对象地址
  • file_suffix:统一改为 .jpg
  • original_name:统一规范化,例如 photo_evidence_{ossId}.jpg
  • ext1.fileSize:更新为真实大小
  • ext1.contentType:更新为 image/jpeg

如有以下字段被业务使用,也建议同步补齐:

  • ext1.source
  • ext1.remark
  • update_time
  • update_by

6.4 为什么不要重新生成新的 sys_oss 记录

  • 会引入新的 oss_id
  • 会导致其他业务表引用失效
  • 会让修复脚本必须跨全库改引用
  • 很难保证没有漏网表
  • 回滚和审计也会变复杂

因此,历史补救必须采用“新对象文件 + 原记录覆盖”的模式。

7. 前端可视化补救方案

为了让补救方案易于操作,建议将历史补救放到后台管理界面,而不是只做一次性脚本。

7.1 推荐入口

建议新增后台管理页面,例如:

  • 菜单名称:水印图片修复
  • 所属模块:系统管理附件管理
  • 页面定位:面向运维、实施、研发,不对普通业务用户开放

前端落点建议明确放到维护模块中,参考现有维护页 index.vue 的维护项列表能力。

建议方案:

  • 在前端维护模块 wucaixing-frontend/src/views/system/maintenance/index.vue 中新增一个维护项,例如 图片水印修复
  • 点击维护项后进入专门的修复弹窗或修复页面
  • 该页面承载异常筛选、预览比对、勾选记录、单条修复、批量修复、任务进度和结果查看

这样可以与现有 媒体资源元数据修复 的运维入口保持一致,便于统一权限、统一操作习惯、统一审计。

7.2 列表页建议字段

列表页建议展示以下信息,方便快速识别和人工核验:

  • evidenceId
  • watermarkedOssId
  • originalOssId
  • 原始图片缩略图
  • 当前水印图缩略图
  • sys_oss.file_suffix
  • ext1.fileSize
  • contentType
  • captureTimeClient
  • addressSource
  • 地址文本
  • 公司 / 业务类型 / 业务 ID
  • 创建时间
  • 修复状态
  • 修复结果说明

7.3 列表页筛选条件

建议支持以下筛选项:

  • 仅看异常记录
  • watermarkedOssId
  • originalOssId
  • 按公司
  • 按业务类型
  • 按时间范围
  • fileSize=0
  • contentType为空
  • file_suffix 异常
  • 按修复状态

7.4 页面操作按钮

建议至少提供以下操作:

  • 预览原图
  • 预览当前水印图
  • 生成修复预览
  • 勾选当前记录
  • 批量勾选筛选结果
  • 清空勾选
  • 执行单条修复
  • 批量修复已勾选记录
  • 导出异常清单
  • 查看修复日志

补充要求:

  • 不允许用户在未查看修复前后效果的情况下直接发起批量修复
  • 批量修复应以“用户已勾选且已完成预览确认的记录”为执行范围,而不是直接对整个筛选结果无差别执行

7.5 单条修复建议流程

单条修复推荐流程如下:

  1. 用户点开一条异常记录。
  2. 页面展示:
    • 原图预览
    • 当前坏水印图预览
    • 拟修复后的新水印预览
    • 修复前后元数据对比
  3. 用户点击 确认修复
  4. 后端执行:
    • 读取原图
    • 按历史时间与地址重新加水印
    • 上传新 OSS 对象
    • 覆盖原 sys_oss 记录
    • 记录操作日志
  5. 页面回显修复结果。

这个流程适合先做试点修复和人工验证。

7.6 批量修复建议流程

批量修复推荐流程如下:

  1. 用户先通过筛选条件锁定异常数据范围。
  2. 页面展示总数、可修复数、疑似失败风险数,并支持逐条勾选待修复记录。
  3. 用户先对准备修复的记录执行预览,页面必须展示:
    • 原图
    • 当前坏水印图
    • 拟修复后的新水印图
    • 修复前后元数据差异
  4. 用户确认预览效果无误后,勾选本次真正要修复的记录。
  5. 用户点击 批量修复已勾选记录 前,系统弹出二次确认,并明确显示本次修复条数。
  6. 后端采用任务方式异步执行,页面可轮询查看进度。
  7. 每条记录输出修复结果:
    • 成功
    • 原图不存在
    • 原图无法解析
    • 新水印生成失败
    • OSS 上传失败
    • sys_oss 覆盖失败
  8. 任务完成后支持导出结果明细。

这里的关键要求是:

  • 批量修复之前必须先有“预览确认”步骤
  • 批量修复的对象必须是“用户主动勾选的记录”
  • 不能仅凭筛选条件直接全量执行修复任务

7.7 为什么前端界面比一次性脚本更适合

  • 更适合先小批量验证,再逐步扩大范围
  • 操作人员无需直接跑脚本和改 SQL
  • 每条记录可预览、可追踪、可重试
  • 更方便做权限控制和操作审计
  • 后续若再出现新脏数据,也可以复用同一页面处理

8. 后端接口建议

为支撑前端可视化操作,建议至少提供以下几类接口:

8.1 异常列表接口

用途:

  • 按筛选条件查询疑似异常水印记录
  • 返回分页列表和异常原因标签

8.2 预览接口

用途:

  • 根据 evidenceIdwatermarkedOssId 生成“拟修复水印图”的临时预览
  • 不落库,不覆盖 sys_oss

8.3 单条修复接口

用途:

  • 对单条异常记录执行正式修复
  • 修复成功后覆盖原 sys_oss

8.4 批量修复任务接口

用途:

  • 创建批量修复任务
  • 入参必须是前端勾选确认后的记录 ID 列表,而不是只有筛选条件
  • 后台异步执行
  • 支持查询进度、成功数、失败数、失败原因

8.5 操作日志接口

用途:

  • 记录谁在什么时间修复了哪条记录
  • 保留修复前后对象信息,便于追溯

9. 操作审计与回滚建议

9.1 建议记录的审计信息

每次修复建议记录:

  • 操作人
  • 操作时间
  • evidenceId
  • watermarkedOssId
  • file_name
  • file_name
  • url
  • url
  • ext1
  • ext1
  • 修复结果
  • 错误信息

9.2 回滚方式

由于要求保持原 oss_id 不变,回滚方案建议也按“对象替换”处理:

  1. 修复前先记录旧对象信息。
  2. 如修复后发现异常,可重新把旧对象重新上传或恢复。
  3. 再次覆盖同一个 sys_oss 记录,使其回到修复前状态。

也就是说,回滚不是改回旧 oss_id,而是把同一个 oss_id 再次指回旧对象。

10. 推荐实施顺序

建议按以下顺序落地:

  1. 先完成线上兜底,阻断新增坏数据。
  2. 再开发后台可视化补救页面和补救接口。
  3. 先选少量样本做单条修复验证。
  4. 确认预览、下载、页面展示全部正常后,再进行批量修复。
  5. 批量修复完成后,统一清理被替换掉的坏 OSS 对象。

11. 最终结论

本问题的正确处理方式是:

  • 线上新增场景:后端不再信任原始上传后缀,水印图统一输出为 jpg/jpeg
  • 历史补救场景:以 HotPhotoEvidence 为主表重建水印图,但不能改 oss_id,必须覆盖原坏水印图对应的 sys_oss 记录。
  • 落地形式:优先做成后台管理可视化能力,支持异常筛选、预览、单条修复、批量修复、日志追踪。

该方案既能快速止血,也能在不扩散引用风险的前提下,稳妥完成历史数据修复。