Skip to content

feat(theme): 上传图片自动压缩并转换为 WebP (#130)#132

Merged
Tespera merged 2 commits into
Gridea-Pro:mainfrom
Bcy2020:feat/issue-130-webp
Jun 13, 2026
Merged

feat(theme): 上传图片自动压缩并转换为 WebP (#130)#132
Tespera merged 2 commits into
Gridea-Pro:mainfrom
Bcy2020:feat/issue-130-webp

Conversation

@Bcy2020

@Bcy2020 Bcy2020 commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

变更说明

上传图片时自动压缩并转换为 WebP 格式,减少网站加载卡顿。

  • 新增 webpconvert 包,基于 chai2010/webp 实现图片转 WebP(纯 Go,无系统依赖)
  • 新增 ImageOptimizeSetting 域模型 + JSON 仓储 + Wails 门面,设置存储于 config/image_optimize_setting.json
  • 修改 MediaRepository.SaveImages() 和 PostRepository.save(),在文件复制前按需转换
  • 前端 PersonalizationSetting.vue 新增 WebP 转换开关,独立于 ThemeConfig 保存
  • 12 个 locale 补全 webpConvert / webpConvertDesc 翻译

功能默认关闭,用户手动开启后生效。

关联 Issue

Closes #130

自查清单

  • 本地 wails dev 跑通并自测过改动
  • Go 代码已 gofmt -w 格式化,go vet ./...、go test ./backend/... 通过
  • 前端 npm run lint 无新增 error(models.ts 有 4 个预存 error,非本次引入)
  • 新增/修改的 UI 文案已补全全部 12 个语言,check_i18n.py 通过,en.json 无中文
  • 未夹带与本 PR 无关的改动

新增图片优化设置模块,用户可在主题个性化设置中开启 WebP 自动转换。
开启后,编辑器内联图片和文章封面图在保存时自动转为 WebP 格式。

- 新增 webpconvert 包:独立的图片转 WebP 能力(chai2010/webp)
- 新增 ImageOptimizeSetting 域模型、仓储和门面
- 注入 media_repo 和 post_repo 的图片保存流程
- 前端 PersonalizationSetting 新增 WebP 开关
- 12 个 locale 补全 webpConvert / webpConvertDesc 翻译
@Bcy2020 Bcy2020 requested a review from Tespera as a code owner June 13, 2026 12:51
@Tespera

Tespera commented Jun 13, 2026

Copy link
Copy Markdown
Member

@claude review 代码,重点看下配置信息的存放问题。

@claude

claude Bot commented Jun 13, 2026

Copy link
Copy Markdown

Claude finished @Tespera's task in 5m 16s —— View job


代码评审


1. 总体结论

⚠️ 建议修改后合并

WebP 转换的核心逻辑(webpconvert 包 + 仓储层集成 + Wails 绑定 + i18n)整体设计清晰,双检锁、临时文件原子写入、nil-safe 的 MCP 降级路径都处理得不错。配置存放方案与现有架构一致,但有几处值得修复的中等问题,特别是动画 GIF 静默丢失动画quality 字段名实不符,建议在合并前处理。


2. 问题清单

🟡 建议修改

[1] 动画 GIF 转 WebP 静默丢失动画

  • backend/internal/webpconvert/convert.go:26.gif 列在 convertibleExts 中,会被转换
  • image.Decode() 只解码 GIF 第一帧;chai2010/webp 编码的也是单帧静态 WebP
  • 用户上传动画 GIF 后,图片会静默变成静态图。没有任何错误提示,用户无法发现。
  • 建议:从 convertibleExts 中移除 .gif(最简单安全),或在 webpConvertDesc 的 UI 文案里明确告知用户动画 GIF 不会保留动画效果。

Fix this →


[2] quality 字段名实不符——前端始终硬编码为 80

  • frontend/src/views/theme/components/PersonalizationSetting.vue:289quality: 80 硬编码
  • Domain 层注释写了"1-100,默认 80",给人可配置的预期;但前端永远存 80,UI 也没有质量控制器
  • 这让 Quality 字段成了一个永远是 80 的占位符,会让后续维护者疑惑"这个字段到底有没有被用"
  • 建议:若质量调整推迟到后续版本,把 domain 注释改为"保留字段,暂固定为 80,后续开放"并在 Facade 中直接忽略传入的 quality,强制用 GetQuality() 返回默认值;或者直接删掉 Quality 字段(彻底简化),等 UI 准备好再加回来。

Fix this →


[3] loadIfNeeded 静默吞掉文件格式错误

  • backend/internal/repository/image_optimize_setting_repo.go:44-48
if err := LoadJSONFile(settingPath, &setting); err != nil {
    r.cache = &domain.ImageOptimizeSetting{} // 文件不存在 → 零值(功能关闭)
    r.loaded = true
    return nil  // ← 无论是 "文件不存在" 还是 "JSON 损坏" 都走这里
}
  • 文件不存在时返回零值是正确行为(首次运行);但配置文件被手工编辑损坏时,错误也被静默吞掉,WebP 功能会悄悄关闭,用户不会得到任何提示。
  • 建议:区分 os.IsNotExist 和其他 IO/JSON 错误,后者至少打一条 log.Printf(与 post_repo.go 里的错误日志风格一致):
if err := LoadJSONFile(settingPath, &setting); err != nil {
    if !os.IsNotExist(err) {
        log.Printf("[imageOptRepo] 读取配置失败,使用默认值: %v", err)
    }
    r.cache = &domain.ImageOptimizeSetting{}
    r.loaded = true
    return nil
}

Fix this →


[4] getWebpSetting 在两个 Repository 中重复实现

  • backend/internal/repository/media_repo.go:62-72
  • backend/internal/repository/post_repo.go:532-542
  • 两段逻辑完全一样,是典型的复制粘贴。评审约定"发现重复造轮子要指出"。
  • 建议:提取到 helper.go 或新建 webp_helper.go,暴露一个包级函数 GetWebpSetting(repo domain.ImageOptimizeSettingRepository, ctx context.Context) (bool, int)

[5] post_repo.go 中有重复注释行

  • backend/internal/repository/post_repo.go:531-532
// getWebpSetting 获取 WebP 转换设置。设置不可用时返回关闭状态。
// getWebpSetting 获取 WebP 转换设置。imageOptRepo 为 nil 或读取失败时返回关闭状态。

连续两行函数注释,应只保留第二行(内容更准确)。


🟢 可选优化

[6] Facade 层错误未包装为中文

  • backend/internal/facade/image_optimize_setting_facade.go:29 — 直接透传 repo 错误
  • 按架构约定 Facade 层应将内部错误转为用户友好提示。前端有统一的 toast.error(t('settings.theme.saveFailed')) 兜底,实际影响很小。可在后续统一规范时处理。

3. 重点维度核查

维度 4 — Wails 绑定同步
frontend/src/wailsjs/ 目录在 .gitignore 中,由 wails dev/wails build 自动生成,无需手工维护。PR 作者已确认本地 wails dev 跑通。ImageOptimizeSettingFacade 已在 boot.go:285 正确 Bind,boot.go 的变更(新增 services.ImageOptimizeSetting)也已包含在 diff 里。models.tsdomain.ImageOptimizeSetting 类(被前端 new domain.ImageOptimizeSetting({...}) 调用)会在构建时自动生成。✅ 已正确处理。

关于配置存放(触发点)

当前实现将 image_optimize_setting.json 存于 站点目录 {appDir}/config/,与 seo_setting.jsoncdn_setting.jsonpwa_setting.jsonimage_hosting.json 的存放位置完全一致,架构上合规、实现上无技术缺陷(WriteFileAtomic 内部已调用 os.MkdirAll,不会因目录不存在报错)。

需要确认一个设计选择:WebP 转换是"工具级偏好"(所有站点共享同一开关)还是"站点级配置"(每个站点独立)?

  • 若希望所有站点共享(用户不管在哪个站点都用同一个 WebP 开关),应改用 config.NewConfigManager().AppConfigDir() 存储,类似 AI 设置的做法(NewAISettingRepository(cm)
  • 若希望每站点独立配置(当前实现),则现有设计完全正确,切换站点时 WebP 设置随站点切换也是预期行为

目前代码在 UpdateAppDir 中已正确更新 s.ImageOptimizeSetting.repo,站点切换后设置会正确跟随新站点——若设计意图就是站点级,这是 ✅。

维度 5 — i18n
12 个语言文件全部补全 webpConvert / webpConvertDesc 两个 key,组件内无硬编码字符串。✅ 已正确处理。

维度 6 — 双渲染器一致性
本 PR 不涉及 Markdown 渲染,仅影响图片文件处理流程。✅ 不涉及。

PR 描述核查:合格(描述完整覆盖了主要模块变更,功能默认关闭等行为均已说明)


- 移除 .gif 的 WebP 转换支持,避免动画 GIF 静默丢失动画
- Quality 字段注释改为"保留字段,暂固定为 80,后续版本开放"
- loadIfNeeded 区分文件不存在与 JSON 损坏,后者打 log
- 提取 getWebpSetting 为包级公共函数,消除 media_repo/post_repo 重复
- 删除 post_repo.go 重复注释行
- 同步更新 webpconvert 测试用例
@Bcy2020

Bcy2020 commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

@Tespera 已经修改,麻烦再次审核,感谢!

@Tespera

Tespera commented Jun 13, 2026

Copy link
Copy Markdown
Member

@claude 再次审查代码。

@claude

claude Bot commented Jun 13, 2026

Copy link
Copy Markdown

Claude finished @Tespera's task in 2m 26s —— View job


代码评审(第二轮)

  • 读取评审规范
  • 阅读当前代码文件
  • 核查上轮 5 项问题的修复情况
  • 输出评审结论

1. 总体结论

可合并

上轮提出的全部 5 项问题均已正确修复,修复质量整体不错,没有引入新问题。


2. 问题清单

上轮问题核查

# 问题 状态 说明
1 动画 GIF 静默丢失动画 ✅ 已修 .gifconvertibleExts 移除,加入 skipExts 并附明确注释
2 quality 字段名实不符 ✅ 已修 domain 注释已改为「保留字段,暂固定为 80,后续版本开放可配置」,意图清晰
3 loadIfNeeded 静默吞错误 ✅ 已修 加入 os.IsNotExist 区分判断,非 not-exist 错误已走 log.Printf
4 getWebpSetting 重复实现 ✅ 已修 提取为 webp_helper.go:GetWebpSetting,两个 repo 均改为调用共享函数
5 post_repo.go 重复注释 ✅ 已修 私有 getWebpSetting 整体被移除,重复注释随之消失

🟢 新发现(可选)

convert.go_ "image/gif" 为死代码

convert.go:7 引入了 _ "image/gif" 以注册 GIF 解码器,但修复后 .gif 已进入 skipExtsimage.Decode() 永远不会被 GIF 文件调用到,这个 blank import 成了死代码。不影响功能和编译,但可按需移除以保持 import 整洁。


3. 重点维度核查

维度 4 — Wails 绑定同步
无新增 domain 结构体字段或 Facade 方法变更,本轮修复均为逻辑层调整。webp_helper.go 是后端内部工具,不需要前端 stub。✅ 不涉及。

维度 5 — i18n
本轮修复无 UI 文案变更。✅ 不涉及。

维度 6 — 双渲染器一致性
本 PR 不涉及 Markdown 渲染,仅影响图片文件处理流程。✅ 不涉及。

PR 描述核查:合格(修复提交的 commit message 已简要列出 5 项修复内容)


@Tespera Tespera merged commit cdf98a9 into Gridea-Pro:main Jun 13, 2026
4 checks passed
@Bcy2020 Bcy2020 deleted the feat/issue-130-webp branch June 14, 2026 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]

3 participants