21 KiB
素材库未完成问题处理记录(2026-08-01)
当前代码基线:
origin/main@7c609a5ca891e95e23b7e1ad3a7fd6cfa680fb21记录原则:代码、生产运行状态和历史事件分开判断;没有完成真实验收的项目不标记为“已关闭”。
一、当前结论
主服务已经包含前一轮四项 P0(最高级别)代码修复。2026-08-02 00:06,生产机上的旧影子服务及其 FRP 映射已完成隔离;容器和数据均保留,正式服务未受影响。历史凭据是否失效、相关访问日志是否存在异常仍需继续核对,因此凭据事件本身尚未完全结案。
本轮已在隔离分支完成两项 P1(高优先级)代码修复:
- 关闭匿名网络测速 POST,避免无上限读取请求正文。
- 收紧媒体代理,阻止通过它访问服务器内网、回环地址、任意端口或恶意跳转地址。
这两项已提交并推送到 codex/audit-remediation-20260801,提交为 6b909fa;代码批次 2 已随 3495530 提交并推送。2026-08-03 又补充完成“选图时不再让浏览器直接预览原始大图”的修复,已随 62295a0 提交并推送,并通过 Linux Docker 隔离构建和 Chromium 完整上传验收;尚未合并 main 或部署生产。
二、生产现场阻断项
P0-R0:TrueNAS 宿主机可用内存过低,当前禁止部署
状态:生产服务正常,但部署门禁不通过
2026-08-03 只读核查结果:
- 正式应用仍运行在
main@776c6bafce9a,容器未发生 OOM,重启次数为 0,应用自身当前只占约 170MiB,健康接口、数据库和 Redis 均正常。 - 宿主机总内存约 15.5GiB、没有 Swap;本轮多次只读采样的可用内存在约 1.4~1.75GiB 之间,始终低于 2GiB。
- 主要占用不是素材库应用,而是 TrueNAS 的
middlewared.service,本轮采样约 8.1~8.8GB,历史峰值约 9.4GB。 - 蓝绿部署会短暂同时运行新旧两个应用容器;如果此时又发生图片或视频处理,存在再次拖垮整台主机的风险。
安全边界:
- 当前不重启、不终止
middlewared,不添加 Swap,也不重启 TrueNAS;这些属于 NAS 级高风险操作,需要单独维护窗口。 - 发布前建议宿主机
MemAvailable至少恢复到 4GiB,并确认没有正在执行的ffmpeg/图片计算任务。 - 发布过程中若可用内存低于 2GiB、出现新 OOM/连续 5xx,或应用内存异常上升,应立即停止并回滚。
- 图片和分片压力测试只能在隔离测试环境进行,禁止直接压测生产 NAS。
P0-R1:旧影子服务仍暴露旧漏洞
状态:运行入口已收口;历史凭据核查待完成
已确认事实:
- 影子容器
youyidian-3d-shadow使用旧提交57f46fca6f4b,不包含系统设置脱敏、上传路径、文件删除、白底图权限和图片内存修复。 - 影子端口
5320监听所有网卡;另有正在运行的 FRP 转发配置指向该端口,应按“可能从公网访问”处理。 - 未登录访问影子的
/api/system-settings实际返回200;返回内容包含非空的 S3 Access Key 和 Secret Key 字段。 - 上述接口泄露的数据库设置值与当前容器环境变量中使用的 S3 凭据不是同一对;旧值是否仍有效尚未验证。不能把它描述成“当前正式凭据已确认泄露”,但也不能在未确认失效前忽略。
- 影子服务代码目录由其运行用户拥有,旧版上传路径校验缺陷仍具备覆盖应用文件的运行条件。
- 影子服务虽然使用单独的数据库,但与正式环境共用 S3 存储桶和同一份生产
uploads目录,并且当前部署方式还复用了正式 Redis 等运行资源。
最小安全处理:
- 停止
youyidian-3d-shadow,将自动重启策略改为no;保留容器和数据,不删除。 - 停用指向
5320的 FRP 转发。 - 验证本机和外部入口均无法再访问影子接口。
- 核对泄露出来的旧 S3 凭据是否已经失效,并查阅 MinIO 审计记录;核对过程不得在日志或文档中输出密钥明文。
- 如果后续确实要恢复影子环境,必须先使用当前安全镜像,并隔离数据库、S3、Redis、
uploads和各类 Secret;端口只绑定本机地址。
2026-08-02 实际处理与验收:
youyidian-3d-shadow已停止,状态为exited,自动重启策略已从unless-stopped改为no;未删除容器、镜像、数据库或素材数据。- 宿主机
5320监听数为 0,本机访问影子/api/health和/api/system-settings均已不可达。 frpc-sc-entry同时承载正式4320与影子5320,因此没有停用整个 FRP 服务;仅精确删除sc-shadow-entry,保留sc-main-entry。- 新 FRP 配置已通过
frpc verify,服务重启成功并保持active + enabled;正式站点/api/health返回200,首页返回正常登录跳转307。 - 原 FRP 配置备份保留在生产机
/etc/frp/frpc-sc-entry.toml.codex-backup-20260802-000307,权限已收紧为root:root 0600,用于紧急回滚。 - 发布守门员已独立复验:外部旧
2322无法返回影子页面、健康接口或漏洞接口(5 秒内零字节),因此“影子应用已不可访问”通过。 - 入口机的
2322仍能完成 TCP 握手;由于当前没有入口机 SSH 权限,尚不能确认 FRPS 端是否存在残留监听。该现象不再能访问影子应用,但端口级收口仍需入口机运维核对。
P0-R2:生产 uploads 数据集根目录仍为 0777
状态:2026-08-02 用户决定暂缓处理;保留为已知风险
暂缓边界:当前不修改目录所有者、ACL 或 0777 权限,不递归处理任何素材文件,也不因此暂停上传。后续只有在用户重新授权并安排验证窗口后才恢复该修复项;在此之前不能把“运行时文件权限已收紧”标记为完成。
已确认事实:
- 容器中的
/app/uploads是可写挂载,实际指向宿主机/mnt/PoolOne/youyidian-uploads。 - 真实 ZFS 数据集根目录为
root:root 0777,NFSv4 ACL 等价为所有人可读、写、进入。 - Docker 镜像中的
0750会被宿主机可写挂载覆盖,因此镜像内权限正确不代表生产运行时正确。 - 正式和影子应用进程实际使用
uid=1001、gid=65533;不能只把目录改成root:root 0750,否则正式服务会失去写权限。
安全处理要求:
- 先停止/隔离影子服务,并短暂停止新上传。
- 对 uploads 数据集创建“改权限前”快照。
- 只修改数据集根目录本身,严禁对整棵素材目录递归
chmod/chown。 - 目录所有者需与正式应用运行身份匹配;权限调整后先做受控的创建、写入、读取、删除测试。
- 最后验收一次真实小文件上传、预览和下载,再恢复正常使用。
三、本轮已完成的代码修复
P1-1:匿名网络测速耗尽内存
状态:本地已修,待上线
处理:
- 保留首页自动判断内外网所需的
GET /api/network-status。 - GET 只返回
success、isIntranet和时间,不再返回客户端 IP、代理头和调试明细。 - 移除反射任意 Origin 且允许凭据的 CORS 响应。
- POST 不再读取正文,直接返回
405 Method Not Allowed。
验收:POST 测试证明即使传入带 arrayBuffer() 的请求对象,也不会调用正文读取。
P1-3:媒体代理可探测服务器内网
状态:本地已修,待上线
处理:
- 仅允许明确的 S3 域名,不再允许
localhost、127.0.0.1或任意子域名。 - 只允许配置中声明的端口或标准 80/443 端口;拒绝 URL 中的账号密码。
- 请求前解析全部 IPv4/IPv6 地址,只要其中一个是回环、内网、链路本地、保留地址或文档地址就拒绝。
- 关闭 fetch 自动跳转,最多手动跟随 3 次,并对每一跳重新检查域名、端口和 DNS 地址。
- 配置中的内网 S3 回源只能留在同一受信任源站,不能借跳转访问其它内网服务。
- 查询参数不再写入日志,避免临时签名 Token 被日志保存。
- 超时和用户取消会贯穿整个流式响应,不再只约束“收到响应头之前”的阶段。
已完成验证:
- 6 个直接相关测试套件、40 项测试全部通过。
- 全量 Jest 回归共 141 个测试套件、916 项测试全部通过。
- TypeScript 类型检查通过。
- 本轮文件 ESLint 检查通过,无 warning/error。
四、其余未完成问题
代码批次 2(2026-08-02 已提交并推送,待合并和部署)
本批原来的 5 项已完成代码修复:
- 首页不再把转码状态接口的
true/false写进视频 URL;首页收到的预览、封面、视频源和展示图也只保留非空字符串,异常布尔值、数字或对象不会挡住后续合法预览地址。 - 浏览器图片 Worker 已支持用户取消和 10 分钟防卡死超时。成功、Worker 异常、消息异常、超时和取消都会清理定时器、监听器并终止 Worker;用户取消不会误走服务器兜底,损坏文件和像素/宽度/切片超限仍直接报错。
- 图片和 PSD 在选择或拖拽时立即执行单组数量、图片总数、源文件总数和 1GB 总量门禁;超限文件不会创建 Blob 预览,合规文件会保留并给出明确处理提示。同步快照同时防止快速连续选图绕过限制。
- 上传状态轮询每次正常等待结束后都会移除 Abort listener,长任务不再逐轮累积监听器。
- 服务器兜底链路确认仍受单图 100MB、总量 1GB、60MP、切片数量、单用户/全站队列和全局图片并发门禁保护;同时把客户端预览与服务器源图的 Sharp 元数据读取纳入统一门禁。分片组装改为先核对物理总字节,再用固定 1MiB 缓冲区顺序复制,不再把整个分片一次性读入内存。
本批没有把正常文件上传并发降为 1:网络文件并发仍为 2,每个文件的分片并发策略保持不变;单槽位只用于浏览器图片解码和服务器图片计算。
已完成验证:
- 直接相关 10 个测试套件、88 项测试全部通过。
- 全量 Jest 回归共 146 个测试套件、933 项测试全部通过。
- TypeScript 类型检查通过;本批文件 ESLint 为 0 error,只有原文件已有的 Hook 与
<img>提示。 - Next.js 生产编译成功;Impeccable 前端检测没有发现新增问题。
- 本机隔离工作树通过 junction 复用原项目
node_modules,standalone 文件追踪阶段记录了 WindowsEPERM符号链接提示,因此不能把本机构建当成最终容器打包验收。
尚未验证:真实 Chrome/Edge 大图、断网/取消、线上 S3 上传,以及部署后的生产容器内存峰值和真实页面验收。这些必须在部署窗口完成后才能把本批标记为“生产已关闭”。
代码批次 2.1:选图预览内存峰值(2026-08-03 已完成代码修复和隔离验收,待生产发布)
用户反馈使用 Edge 选择图片时仍可能失败。复核发现,虽然正式上传处理已经转移到浏览器 Worker(后台线程),但选图界面此前仍把每张原始 File 直接作为 <img> 预览。压缩文件本身可能只有几 MB,解码后的 60MP 图片却可能占用约 229MiB;同时选择多张时,Edge 可能同时解码多张原图,造成页面内存骤升甚至崩溃。
本次处理:
- 原始图片不再直接进入页面
<img>。 - 使用单通道 Worker 严格一次只处理一张,生成最大 320×320 的 WebP 小缩略图后再展示。
- 多批快速选图仍共用同一条队列,不并行解码多张原图。
- 缩略图生成中、失败或浏览器能力不足时显示文件占位,不退回原图预览,也不阻断后续提交校验。
- 删除文件、开始提交、关闭组件都会终止当前缩略图任务;删除、上传成功和关闭组件都会回收临时 Blob URL。
验证结果:
- 4 个直接相关测试套件、34 项测试全部通过。
- 全量 Jest 回归共 146 个测试套件、942 项测试全部通过。
- TypeScript 类型检查通过,改动文件 ESLint 为 0 error,仅保留 2 个已知的动态 Blob
<img>提示。 git diff --check通过;Impeccable 检测没有发现新增界面问题。- 在独立构建机按提交
62295a0完成 Linux Docker 镜像构建,并启动独立 PostgreSQL、Redis、MinIO 和应用容器;应用健康检查通过,容器重启次数为 0,未发生 OOM。 - Chromium 隔离页面完成接近 60MP 的
6000×9999主图、1600×9000超长详情图和多批选图验收;选择后先显示缩略图生成状态,随后正常显示小缩略图,没有直接挂载原始大图。 8000×8000超过 60MP 的图片显示“预览不可用”占位,没有造成页面崩溃;处理中删除、立即提交和关闭弹窗均能终止当前缩略图任务。- 正式提交成功生成 1 条素材、7 条资源记录;后台上传任务状态为
completed、阶段为completed、进度为 100%,首页预览对象可正常读取。 - 浏览器进程组工作集由约 2.76GiB 短暂升至约 2.83GiB,任务结束后回落到约 2.77GiB;应用容器处理时约 105MiB,空闲后约 90MiB,未出现持续增长。
- 验收后已删除隔离容器、4 个专用测试卷、2 个专用测试镜像和远端临时工作树,并停止 3 条本地 SSH 转发;删除内容均为本轮合成测试数据,生产环境未改动。
Edge 补充边界:真实 Edge 已能登录隔离站点并正常显示已上传预览,但 Codex 的 Edge 扩展未开启“允许访问文件 URL”,自动化工具因此无法把本地测试图片交给网页;这是测试工具权限,不是素材库返回的上传失败。用户此前已经用自己的 Edge 完成人工上传。生产发布后仍需再做一次真实业务账号的小批量上传、预览和下载验收,才能把本项标记为“生产已关闭”。
代码批次 3.1:任务-页面空卡片与大文件下载背压(2026-08-03 本地完成并通过提交门禁,待发布)
本轮对生产数据库做了只读核查,没有修改或删除任何数据。生产共有 14 条未删除的“任务-页面”素材,其中 13 条同时满足“顶层预览为空、ZIP 路径为空、资源记录为 0”,只有 1 条具备真实预览和文件。这与首页截图中的 13 张“暂无预览图”空卡片和 1 张正常卡片完全一致。
已确认根因与处理:
- 初始化接口返回的是顶层
materialId,前端却按data.materialId读取;后端刚创建空素材记录,前端便立即报错,后续图片上传和完成接口都没有执行。前端现已按真实接口契约读取,并在素材编号缺失时给出明确错误。 - 新建记录会标记为
uploading;完成请求必须原子地把本人名下这条记录抢占为processing,只有一个请求能成功。重复点击或并发完成会返回 409,不能进入清理分支,也不能删除已经完成的素材。 - 完成接口增加素材类型校验,不能用“任务-页面”接口覆盖用户自己的其它素材。完成阶段失败时,临时文件、上传会话、S3 预览、ZIP 和素材会进入统一清理;数据库删除还必须再次满足
taskStatus=processing,不能只根据客户端传来的素材 ID 删除。 - 首页列表会从
assetType=preview的任务页面资源回退取封面;没有预览、没有 ZIP、也没有任何资源的任务页面空壳会从正常列表和筛选数量隐藏,但不会被删除,回收站查询也不受影响。 - 生产只读验证表明该过滤条件上线后,“任务-页面”的正常列表数量将从 14 变为 1;13 条历史空壳仍保留在数据库中,后续如需物理清理,必须先备份并单独授权。
- 任务页面前端和后端统一增加单图 100MB、最多 60 张、总量 1GB 的硬门禁;后端对用户拥有的上传会话去重,并以磁盘实际
stat大小为准,在打包和readFile()之前拒绝超限文件。图片解码阶段仍受 60MP、最大边长和全局单并发门禁保护。
同时修复了下载链路的真实内存风险:原实现虽然使用了 Web Stream,但通过 Node data 事件持续推送文件,用户网络慢或多人并发时无法向源文件施加背压,进程仍可能积压大量数据。现已统一为“下游每请求一块,Node 源流才读取一块”的转换器,并覆盖:
- 素材主下载与 Range 下载;
- SKU 单个模型下载和批量动态 ZIP;
- SKU 白底图动态 ZIP;
- 外部 Token 的本地文件、远程文件、Range、动态 ZIP 和展示图 ZIP。
SKU 白底图 ZIP 已从 JSZip 的“读取全部图片并生成完整内存 Buffer”改为 Archiver 流式压缩;动态 ZIP 不再先生成完整压缩包再返回。用户取消动态 ZIP 时会同时销毁显式远程输入流、调用 Archiver abort() 清空排队任务并销毁输出流;测试已验证源流销毁、archive 进入 aborted/destroyed 状态且队列归零。
验证结果:
- 任务页面、并发完成、条件清理、下载背压和 ZIP 取消的定向回归全部通过。
- 全量 Jest 回归共 150 个测试套件、962 项测试全部通过。
- TypeScript 类型检查通过;相关文件 ESLint 为 0 error,仅保留任务页面既有的动态 Blob
<img>性能提示。 - Next.js 生产编译成功;Impeccable 检测没有发现新增界面问题。
- 本机隔离工作树通过 junction 复用原项目
node_modules,standalone 文件复制阶段仍有 WindowsEPERM符号链接提示,因此本次构建只证明代码和页面编译通过,不能替代 Linux 镜像打包与部署后验收。
尚未完成:本批尚未部署生产;需要在安全发布窗口完成 Linux 镜像构建、真实“任务-页面”小文件上传、首页卡片预览、单文件/Range/动态 ZIP 下载,以及下载期间容器内存峰值验收后,才能标记为生产关闭。
后续 P1
- 普通主上传的小文件接口仍使用
formData(),SKU 模型上传仍会把单个 ZIP 完整放入内存;两者需要改为服务端强制限额或统一分片上传,不能只依赖前端约束。 - 下载 Token 有效期仍接近永久,且外部下载没有完成实时权限复核。
canShare权限配置未被分享接口真正执行;部分管理员接口角色口径不一致;素材所有者仍可能通过编辑接口转移素材归属。- 部门改名、移动层级和负责人角色更新仍缺完整事务。
- 任务号、SKU 等业务唯一值仍缺完整数据库唯一约束和生产数据清理门禁。
- 外部素材导入仍可能在业务请求过程中执行 DDL(建表/建索引/外键)。
- 部分管理员接口和外部自动上架接口仍返回过多字段,外部请求缺统一业务超时。
后续前端体验和性能
- 详情图排序缺少键盘和触屏替代操作,并且同一批图片重复渲染。
- 上传区域按钮语义、焦点样式、小删除按钮和 reduced-motion 支持仍不完整。
- 上传弹窗仍缺完整的 dialog 语义、焦点圈定和关闭后的焦点恢复;这是既有可访问性 P1,本次内存修复未扩大范围处理。
- 网络状态监听器、分享页视频重复预加载、音频进度引发全部卡片重渲染仍待处理。
- 上传弹窗、搜索框、登录注册标签和暗色模式等可访问性/一致性问题仍待处理。
后续凭据卫生
- 历史检查脚本中仍有一处疑似明文凭据。它不是本轮改动引入,也不会进入正式运行镜像,但不应继续保留在 Git 中。
- 下一步应先确认该凭据是否仍有效、是否被实际使用,并把脚本改为只从环境变量读取;只有确认凭据仍有效或存在异常使用证据时,才安排对应团队轮换,不能无依据扩大轮换范围。
五、处理顺序与上线门禁
- 已完成运行态收口:旧影子服务和 FRP 映射已停止;继续核对旧凭据是否失效及相关访问日志。
- 已暂缓:uploads 数据集根目录权限保持现状,等待用户后续重新授权。
- 代码批次 1:P1-1/P1-3 已提交并推送,待合并和部署;上线后验收网络状态 GET、POST 405、历史图片/视频预览和 Range 播放。
- 代码批次 2:已提交并推送,待合并、部署及真实业务验收。
- 代码批次 2.1:已随
62295a0提交并推送,Linux Docker 构建和 Chromium 大图上传验收通过;待生产门禁恢复后合并、部署,并补做真实业务账号验收。 - 生产发布门禁:先处理或等待 TrueNAS 可用内存恢复,禁止在当前不足 2GiB 余量下切换生产。
- 代码批次 3:继续处理上传/ZIP 内存、长期下载 Token、权限和数据库一致性。
- 代码批次 3.1:任务-页面上传/首页预览和下载背压已完成本地修复并通过独立提交门禁;生产发布前仍须完成 Linux 镜像和真实运行验收。
当前可以确认“旧影子漏洞入口已不可达”,但在旧凭据失效状态和访问日志核对完成前,不能把历史凭据事件标记为完全结案;uploads 的 0777 权限问题按用户决定暂缓,仍属于已知未关闭项。