news 2026/9/24 13:59:20

22 个审核页面怎么拆:Civitai 的模块边界与渐进式迁移复盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
22 个审核页面怎么拆:Civitai 的模块边界与渐进式迁移复盘

22 个审核页面怎么拆:Civitai 的模块边界与渐进式迁移复盘

【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai

在 Civitai 仓库中,22 个 moderator 审核页面(内容审核、扫描器审计、训练审核、CSAM、生成配置五个业务域)被要求搬进一个独立的卫星应用。拆分带着三条硬约束:新应用必须与主应用共享 Postgres / Redis / ClickHouse 连接;不能 fork 整个仓库;页面要能在新应用里直接构建。表面上这是个"怎么拆"的问题,真正的问题是模块边界画在哪里——哪些代码必须跨应用共享,哪些留在各应用内部,哪些要先重构才能动。

边界问题的分量,看三个数字就明白了。主应用最重的审核页src/pages/moderator/images.tsx有 34 个 import;tRPC 客户端~/utils/trpc被全部 22 个页面导入;枚举文件~/shared/utils/prisma/enums被 14 个页面导入。更关键的是底层:src/server/services/image.service.ts 当前实测 8012 行,直接导入 14 个兄弟服务,与post.service双向互导,是主应用内容图的枢纽。任何一个共享包如果不小心拖进这条链,包就不属于 22 个审核页面了,它属于整个主应用。

docs/moderator-app-shared-modules.md(下称"早期分析")是这场拆分的起点文档。它假设用 git submodule 承载共享代码,设想卫星应用是 Next.js。后来的 docs/moderator-app-package-boundary.md、docs/moderator-app-package-extraction-plan.md 与 docs/monorepo-bootstrap-handoff.md 逐条修订了这些假设,最终落地为 apps/moderator/ 这个 SvelteKit 应用。本文按"问题—规则—节奏—落地—方法论"的顺序复盘这个过程。

🧭 问题背景:拆 22 个审核页面的真实成本

早期分析对 22 个页面的全部导入做了闭包梳理,把依赖划成六组,并给每个页面打了可移植性分级:7 个 Easy、11 个 Medium、6 个 Hard。分级背后有一个规律:Hard 页面的难度几乎都来自单一耦合点auditor页只有 4 个 import,却因useCheckProfanity(要命中服务端端点)被标为 Hard;images页因 PromptHighlight 与 CSAM hook 两处耦合被标为 Hard。耦合解掉后,难度立刻降回 Medium。

这个分级直接决定了迁移策略:不能一把刀切下去,只能按可移植性分批推进。而分批之前,必须先回答"什么代码允许跨应用流动"。六组依赖的裁定,最终收敛为下面三条规则。

依赖分组代表 import裁定
契约层(枚举/schema/常量)~/shared/utils/prisma/enums(14 页)、zod schema规则一:共享
胶水层(tRPC 客户端、auth hooks)~/utils/trpc(22 页)、useCurrentUser规则二:各自持有
审核域组件Csam/*Moderation/*select.store规则二:单消费者留应用内
通用 UI 词汇NextLink(16 页)、EdgeMedia/*规则二:vendor-copy(供应商式拷贝)先行
纯工具函数string-helpersdate-helpers规则二:拷贝
移植阻塞点image.service、dialog 系统规则三:读/写分离拆解

📐 边界判定的三条裁决规则

规则一:数据契约必须共享,两个应用要"说同一种话"。枚举(NsfwLevelBlockedReasonBlocklistType)、稳定常量、持久化 JSON 形状,属于双应用共同读写数据库列的契约,漂移就是数据损坏,必须收敛到单一来源。仓库里的佐证是 src/shared/utils/prisma/enums.ts——它现在只剩三行:

// Re-export shim: this module moved to the @civitai/db-schema package. export * from '@civitai/db-schema/enums';

所有 14 个页面的调用点一行不改,枚举实体则住在 packages/civitai-db-schema/src/enums.ts(1258 行,Prisma 生成器产物)。这条规则同时否决了原方案的civitai-schema-commonsubmodule:docs/monorepo-bootstrap-handoff.md 明确记录"五个基础包,不是六个,没有 civitai-schema-common",理由是位标志解释(browsingLevel.constants.ts)这类领域常量不是基础设施,暂留主应用src/shared/,等卫星应用真正需要再评估。

规则二:单消费者代码留在应用内。全部 22 个页面都导入~/utils/trpc,看起来是"最共享"的代码,裁定却是每个应用持有自己的副本——因为 tRPC 客户端绑定的是本应用的 AppRouter 类型,moderator 应用有自己的 router,共享这份胶水只会造成类型错配。同理,useCurrentUserFeatureFlagsProvider这类 per-app 胶水(应用私有胶水层)直接 vendor-copy。docs/moderator-app-package-extraction-plan.md 把这条规则推向极致:moderator routers、页面、providers、hooks 一律"one consumer →apps/moderator/",强制抽取的共享包被收紧到一个@civitai/domain契约包(8 个整文件移动 + 2 个成员级抽取:ReportEntity并入enums.tsCacheTTL抽到cache.ts)。这条规则否决了早期分析提出的civitai-moderator-common子模块(组件 + 页面 + 服务端代码三合一),理由正是"单消费者代码进应用,不打包"。

规则三:巨型服务按"读自持、写选路"拆解。这条规则的前置是一个测试,边界文档称之为治理规则(governing rule):导入闭包测试——对每个拟共享文件,问的不是"它算不算 moderator 相关",而是"它完整的传递~/…导入闭包是否也可移动?"只要有一个叶子摸到 zustand store、tRPC 客户端、provider 或image.service,整个候选即被阻塞。用这个测试量image.service,结果是:整体拉出它等于导入主应用的整个内容图(feed 缓存失效、NSFW 重排队、化妆品、游戏),不可行。解法是把审核面的服务端需求拆成两种形态:

  • 读队列——自己拥有。审核队列本质是 SQL(getImageModerationReviewQueuegetDownleveledImages、strikes 榜单、CSAM 分页、训练队列),只需dbRead+ selectors + enums。把查询函数从image.service/report.service机械抽取出来,SQL 一行不改。
  • 跨图写入——按动作选路。纯缓存失效的副作用(如bustCachesForPosts归根到底是失效 Redis 键)→ 复刻同一批@civitai/redis键的失效,包内完成;真实编排(漫画重排队、通知扇出、游戏状态)→ 代理一次主应用 tRPC 调用,接受网络跳数。

六个隔离服务(moderator.service128 行、blocklist.servicetraining.servicestrike.service等,逐一核实过服务间耦合)可以直接原样抽取,构成新应用自有 router 的骨架。

🔪 迁移节奏:Phase 0 到 Phase 6 的推进逻辑

早期分析给出的路线分七个阶段,推进逻辑可以概括为"契约先行、就地重构、先易后难、延迟提升":

阶段核心动作阻塞条件
Phase 0发布数据契约(枚举/schema/常量)契约未就绪则卫星应用无法启动
Phase 1主应用内就地重构 6 个移植阻塞点dialog 系统解耦是最高杠杆项
Phase 2建立卫星应用;vendor-copy UI 词汇与工具;先迁 7 个 Easy 页面依赖 Phase 0/1
Phase 3迁移 Medium 页面(11 个)阻塞点重构完成
Phase 4迁移 Hard 页面(6 个)各页面单一耦合点解除
Phase 5/6通用 UI 词汇提升为共享包漂移真实造成摩擦才启动

两个转折时刻值得展开。第一个在 Phase 2:先迁 7 个 Easy 页面(to-ingest、rating-review、downleveled、ingestion-error、comics-review、strikes、training-data/index)作为概念验证(proof-of-concept),用最小成本验证"自有 router + 共享 DB"的技术栈假设,再谈批量迁移。第二个在 Phase 4:6 个 Hard 页面被整体降级处理——auditor只需给useCheckProfanity配一个配套端点;images需要把 PromptHighlight 抽成纯高亮器、CSAM hook 改写成依赖 dialog 接口而非主应用具体 store。更省事的是,复核时发现部分"阻塞点"是误判:PromptHighlight 的闭包实际自包含(静态词表 + 字符串工具),useCheckProfanity只依赖纯函数库,而generation页随ModelVersionFlag.GenerationDisabled位旗标迁移整体移除。"先易后难"在这里不是排期上的偷懒,而是因为 Hard 的成因是点状耦合,解点即降级。

另一个高杠杆项是 dialog 系统解耦:FlaggedModelsListCsamDetailsFormUserBanModaluseReportCsamImages四个组件全部被dialogStore+useDialogContext()卡住。方案是把纯 zustand 的dialogStore提为共享、弹窗组件改接收 props、卫星应用自建轻量DialogProvider——一次重构同时解锁四个组件。

🪞 落地实况:文档假设与仓库现实的差距

早期分析写在 monorepo 化之前,它的载体假设(git submodule + Next.js 卫星应用)没有一条被原样采纳。对照当前仓库,修订与验证情况如下:

早期判断现状
git submodule +civitai-schema-common被否决:改为 pnpm workspace,五个基础设施包 +@civitai/db-schema契约层
卫星应用用 Next.js被修订:落地为 SvelteKit(@sveltejs/kit),见 apps/moderator/package.json
六层依赖分级 + 7/11/6 可移植性裁定被验证:分级、分级理由、迁移顺序在后续文档中被直接沿用

修订的直接证据在依赖清单里:apps/moderator/package.json 依赖@civitai/auth@civitai/db@civitai/db-schema@civitai/clickhouse@civitai/redis@civitai/mod-utils@civitai/moderation@civitai/shared@civitai/ui,全部是workspace:*引用——"不 fork 仓库、共享连接"的目标兑现为一组 monorepo 拆分中的 workspace 包。应用内部还保留了独立的 apps/moderator/prisma/schema.prisma,是 moderation 专属数据(笔记、strikes、帮助请求)从数据库 introspect(内省生成)而来,从不手写。

路由目录显示迁移范围与早期清单高度吻合,apps/moderator/src/routes/ 下已包含 images、articles、models、reports、blocklists、comics-review、audit、users、xguard、abuse 等审核面:

apps/moderator/src/routes/ ├── images/ articles/ models/ reports/ ├── blocklists/ comics-review/ audit/ ├── users/ xguard/ abuse/ admin/ └── 页面访问集中门控于 hooks.server.ts

apps/moderator/CLAUDE.md 记录了这套拆分之后长出来的治理规则:页面授权与动作授权是两条独立轴(页面 grant 管"能打开什么",permission 管"能做什么"),权限 id 是持久化值、改一个 id 等于改一列列名,新页面在授权前默认不可达。回头看,这些规则在早期分析里只以五个"开放问题"的形式存在(认证怎么做、CSAM 是否迁移、过渡期页面归属等)——文档留下了问题,落地过程补上了答案。主应用侧的对照也符合渐进式迁移预期:Csam/*Moderation/*、src/store/select.store.ts、src/server/services/image.service.ts、src/server/common/moderation-helpers.ts 仍以源码存在,"主应用先就地重构、卫星应用按需迁移"的路线在代码层面是可追溯的。

✅ 可复用的方法论清单

从这条分析链里能带走的,是六条可迁移到其他项目的原则:

  1. 先画依赖地图,再谈拆分。把待迁移页面的全部导入归为契约、胶水、域组件、通用 UI、工具、阻塞点六组,用"多少个页面导入它"量化每项的共享价值。
  2. 用导入闭包测试做唯一裁决。对每个拟共享文件跑一遍传递导入闭包:只要有一个叶子摸到全局状态、RPC 客户端或 provider,整份候选标记为阻塞,直到叶子被处理。
  3. 契约按共享数据裁定,代码按消费者数量裁定。双应用读写的枚举、常量、持久化 JSON 形状必须打包成单一来源;单消费者代码即使"看起来相关",也留在应用内。
  4. 巨型服务用"读队列自持 + 写入逐动作选路"拆解。查询函数机械抽取(SQL 不变);写操作按副作用分类,选"复刻缓存失效键"或"代理一次 RPC",拒绝整体迁移。
  5. UI 原语 vendor-copy 先行,提升延后。接受早期重复成本,等视觉漂移真实造成摩擦再提升为共享包,避免在拆分窗口内做全应用调用点重写。
  6. 移动分两步提交。第一步纯git mv重命名(保持 R100 相似度以保住--follow历史),第二步才加 re-export shim(重导出垫片)与 import 改写,让数百个调用点保持不动。

收尾:这份早期分析真正值钱的地方

git submodule 没了,civitai-schema-common没了,三个子模块收敛成一个@civitai/domain契约包,Next.js 换成了 SvelteKit。但六层依赖分级、7/11/6 的可移植性裁定、导入闭包测试、读/写分离解法,在后续每一份修订文档里都被原样继承,并最终兑现为apps/moderator与一组@civitai/*workspace 包。当 monorepo 面对"代码怎么拆"时,答案很少躺在某一份方案里,而是躺在一串互相修订的文档、以及修订链尽头落定的代码里。

【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 13:58:32

从零到跑通:Flink CDC 安装教程与 MySQL 到 Doris 实时同步完整指南

从零到跑通:Flink CDC 安装教程与 MySQL 到 Doris 实时同步完整指南 【免费下载链接】flink-cdc Flink CDC is a streaming data integration tool 项目地址: https://gitcode.com/GitHub_Trending/flin/flink-cdc 想把手里业务库的 MySQL 数据实时搬进数据仓…

作者头像 李华
网站建设 2026/9/24 13:56:07

10分钟解决99%的PSLab for ExpEYES实验故障:工程师私藏排错指南

10分钟解决99%的PSLab for ExpEYES实验故障:工程师私藏排错指南 【免费下载链接】pslab-expeyes PSLab for ExpEYES - Science Experiments and Data Acquisition for Physics Education https://pslab.io 项目地址: https://gitcode.com/gh_mirrors/ps/pslab-exp…

作者头像 李华
网站建设 2026/9/24 13:53:27

车载测试不是点点点:从CAN总线到ISO 26262的系统能力构建

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 13:53:03

Chat2DB 上手指南:自然语言生成 SQL,统一管理 16+ 数据库

Chat2DB 上手指南:自然语言生成 SQL,统一管理 16 数据库 【免费下载链接】Chat2DB Chat2DB is a free, cross-platform, local-first database client and SQL workspace for developers, DBAs, analysts, and data teams. Connect to 40 databases, man…

作者头像 李华