Repository navigation
Conversation
- 新增 CoverBackgroundService:上传即归一化为 1200×1600(3:4),按内容哈希命名天然去重,图库目录短缓存 - generate_cover 在背景库非空时按书名稳定选图铺底,照片背景自动给书名加描边并同步加大行距 - 修复 bk_img_path 分支输出尺寸跟随原图尺寸的问题,统一 ImageOps.fit 到请求尺寸 - 设置页「阅读」组新增「封面背景图」区块:开关、上传/删除、按书名预览效果 - 默认开启;图库为空时行为与原来一致,已有真实封面不会被任何自动路径覆盖
- 二进制接口改用 is_admin():admin_user 只在 get_current_user() 里赋值, prepare 未触发登录态解析时管理员也会恒 403 - 去掉 lockOn:它的语义是「依赖项为真时禁用本项」,与「动态封面关闭则本项无效」相反, 依赖关系改由提示文案说明 - 上传格式收敛为 JPEG/PNG/WEBP 并加 5000 万像素上限,与前端 accept 对齐 - 按 EXIF Orientation 转正手机竖拍照片 - 背景图解码加 draft 预降采样,预览生成下放线程池 - 图库缩略图改 Cache-Control: private - 补 4 条测试:EXIF 转正、像素上限、格式收敛、handler 权限必须走 is_admin
MAX_BACKGROUNDS 是模块常量而非类属性,_payload() 读取时抛 AttributeError, 列表与上传接口全部 500。改为读模块常量,并补 _payload 的单元测试。
mdi-image-refresh 不在 app/src/assets/css/mdi-subset.css 里,构建会顺带重新生成 子集 css 与 woff2(还夹带 develop 既有的 mdi-stop-circle-outline 缺失)。 预览按钮换成子集内已有的 mdi-image-sync-outline,保持生成物零变更。
- 路径穿越(CodeQL ×3 / Sourcery #1):read/delete 不再用原始 name 拼路径, _safe_name 对带路径成分的输入整条拒绝;补能复现旧漏洞的测试 - 并发上传超限:计数复查与写入并入同一把锁,锁内绕过缓存做真实扫描 - 上传 handler 改 async,解码/重采样/JPEG 编码下放线程池 - list_backgrounds 区分目录不存在与读取失败,后者记 warning,避免静默退回渐变 - 前端加请求序号,防止乱序响应回滚图库状态 - 删除确认改用 AppDialog(type=confirm, color=error),符合全站对话框规范 - 补 seed 变体测试,证明选图确实随书名变化而非固定第一张
- 图库挂到「启用动态封面」下方,勾选后才展开(同图章上传),删除独立卡片 - 移除 USE_COVER_BACKGROUND 总开关,由每张图的勾选状态决定候选 - 候选池加入内置「默认彩色封面」,命中时沿用原渐变/纯色生成 - 选中集合内仍按书名哈希稳定选图;上传自动选中、删除自动移出
- 勾选先乐观更新本地并在请求期间禁用其它勾选,失败或网络异常时回退 - selection() 增加代次校验,避免写操作期间旧读把状态回写缓存 - 选择状态写入失败包装为业务错误;上传/删除仅记日志,不整体失败 - 文案明确内置默认项沿用原来的渐变/纯色生成
shiningsprk-arch
marked this pull request as ready for review
November 3, 2026 11:31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
动态封面目前只有随机渐变 / 纯色底 + 白色书名。希望让管理员上传自己的图片作为底图,书名仍沿用现有排版逻辑绘制。
实现
1. 只改
generate_cover()一处,8 条生成路径自动全部生效ImageGenerator.generate_cover()原本已有一个bk_img_path背景图参数,但从它被引入的那次提交起,全仓库没有任何调用方传入过。本次把它接到背景图库,于是static_files.py、book.py(4 处)、scan_service.py、batch_generate_cover.py的调用点一行都不用改。2. 背景图库
webserver/base/cover_background.py/data/books/resources/cover_backgrounds/,与现成的resources/author_avatar/同族,已在/data卷内,无需改 Dockerfile。刻意不放upload_path——那里会被TrashManager.clear_trashs()清空。selection.json,线程锁内原子写入。没有该文件时默认全选。上传自动选中,删除自动移出,已被删掉的旧名字读取时自动忽略。md5(书名) % 候选数,同一书名永远命中同一张。因此不需要为每本书记录「用了哪张背景」,也不新增 Calibre 自定义列。3. 两处底图相关的行为修正
bk_img_path分支的输出尺寸等于背景图原始尺寸,用户传横图就会生成横版封面。现在统一ImageOps.fit到请求尺寸。stroke = max(1, 字号 ÷ 24)随字号等比(1200×1600 上 5px、240×320 缩略图上 1px),行距同步加stroke × 2防止多行文字贴挤。渐变 / 纯色底完全不描边——stroke_width = 0时绘制调用与行距计算和改动前逐字符一致。4. 不改变任何「是否覆盖封面」的既有语义
逐条核对了 8 条生成路径:
cover.jpg实时生成 / 重置元数据 / 网页上传 / 分片上传 / 扫描导入#dynamic_cover = 1已有真实封面(
#dynamic_cover = 0)的书不会被任何自动路径换成背景图封面。上传 / 删除背景图也不触发任何重生成,要生效需管理员主动点「更新所有动态封面」。5. 管理接口与设置页
main.py的static_files.routes()(含 catch-allr"/(.*)")之前。USE_COVER_BACKGROUND总开关(前端字段与后端默认值 / 持久化白名单一并删除):是否使用背景图完全由勾选集合决定,清空候选即回退渐变 / 纯色。library=0生成一张原彩色封面作为预览。测试
tests/test_cover_background.py覆盖:空库回退、3:4 居中裁剪、同内容去重、选图稳定性、仅勾选项参与、内置默认项参与分配、取消全部候选回退、勾选持久化 / 缓存失效、上传自动选中、删除移出选择、非法 / 损坏选择状态、selection.json不被列表 / 读取接口暴露、状态写入失败时上传仍成功、非图片 / 超大字节 / 超像素 / 不支持格式拒绝、库满与并发不超限、透明 PNG 垫白、EXIF 转正、带路径成分的名字整条拒绝(../、./、sub/、..\)、输出尺寸不受原图影响、白底描边可见性、显式bk_img_path优先于图库、use_library=False跳过图库、_payload字段、两个二进制接口的权限必须走is_admin()。~/mbvenv(calibre 7.6):test_cover_background.py+test_thumbnail.py+test_locale_i18n_keys.py+test_perf_settings.py+test_scan_p0.py共 134 passed;唯一 warning 来自social_sqlalchemy的既有 identity-map 提示,与本次改动无关test_setting_saver.py::test_no_duplicate_admin_keys(MYNEXUS 键重复)、test_sidebar_items_migration.py::TestSidebarItemsServedToInfo(主克隆 develop 同样失败)vue-template-compiler编译通过;npm run build-spa通过。构建重写的 mdi 子集已还原,不夹带无关的mdi-stop-circle-outline缺口审查过程中发现并修掉的问题
self.admin_user判权限,而该属性只在get_current_user()里赋值,默认REQUIRE_LOGIN=False时prepare()不会触发解析 → 管理员也恒 403。改用self.is_admin()并加测试防回退。read/delete原本用原始请求名拼路径,../x/bg_<16hex>.jpg可命中库外同名文件。现在带路径成分的输入整条拒绝。MAX_BACKGROUNDS是模块常量却被当类属性读取,列表与上传接口全部 500。mdi-image-refresh不在已提交的图标子集内,按钮图标会渲染成空白。改用子集内已有的mdi-image-sync-outline。Cache-Control从public改private(admin-only 资源);背景图解码加Image.draft()预降采样。二次 review 修复
inputValuewatcher 只在 prop 变化时回写。现在勾选先乐观更新本地,请求期间禁用其它勾选;失败或网络异常时把本地值改回旧值,勾选框才会跟着回退。selection()增加代次校验。封面生成在 executor 线程,写操作的invalidate_cache()可能与读缓存交错;旧读不再把状态回写缓存,避免最长 1 秒的脏读。CoverBackgroundError,避免@js把裸OSError的整段 traceback 回给前端;上传 / 删除路径只记日志,图片操作不因勾选状态写失败而整体失败。已知取舍
% len(pool)会让多数书名换图。这是「全局候选池」的固有特征——重生成时重新分配即可,不引入一致性哈希的复杂度。book_id:新书入库生成封面时book_id还不存在,书名是唯一无状态可用的种子。代价是改书名会换背景。ENABLE_GRADIENT自动降级机制)。xsrf_cookies默认关闭,所有管理接口一致只依赖会话 cookie)。如需要可作为全站改造单独进行。未包含
BookCover.post()至今没有书籍归属校验(任何登录用户可改任意书籍的封面),属既有问题,本 PR 未触碰,如需要可单独提。