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 个 importtRPC 客户端~/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要命中服务端端点被标为 Hardimages页因 PromptHighlight 与 CSAM hook 两处耦合被标为 Hard。耦合解掉后难度立刻降回 Medium。这个分级直接决定了迁移策略不能一把刀切下去只能按可移植性分批推进。而分批之前必须先回答什么代码允许跨应用流动。六组依赖的裁定最终收敛为下面三条规则。依赖分组代表 import裁定契约层枚举/schema/常量~/shared/utils/prisma/enums14 页、zod schema规则一共享胶水层tRPC 客户端、auth hooks~/utils/trpc22 页、useCurrentUser规则二各自持有审核域组件Csam/*、Moderation/*、select.store规则二单消费者留应用内通用 UI 词汇NextLink16 页、EdgeMedia/*规则二vendor-copy供应商式拷贝先行纯工具函数string-helpers、date-helpers规则二拷贝移植阻塞点image.service、dialog 系统规则三读/写分离拆解 边界判定的三条裁决规则规则一数据契约必须共享两个应用要说同一种话。枚举NsfwLevel、BlockedReason、BlocklistType、稳定常量、持久化 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.ts1258 行Prisma 生成器产物。这条规则同时否决了原方案的civitai-schema-commonsubmoduledocs/monorepo-bootstrap-handoff.md 明确记录五个基础包不是六个没有 civitai-schema-common理由是位标志解释browsingLevel.constants.ts这类领域常量不是基础设施暂留主应用src/shared/等卫星应用真正需要再评估。规则二单消费者代码留在应用内。全部 22 个页面都导入~/utils/trpc看起来是最共享的代码裁定却是每个应用持有自己的副本——因为 tRPC 客户端绑定的是本应用的 AppRouter 类型moderator 应用有自己的 router共享这份胶水只会造成类型错配。同理useCurrentUser、FeatureFlagsProvider这类 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.ts、CacheTTL抽到cache.ts。这条规则否决了早期分析提出的civitai-moderator-common子模块组件 页面 服务端代码三合一理由正是单消费者代码进应用不打包。规则三巨型服务按读自持、写选路拆解。这条规则的前置是一个测试边界文档称之为治理规则governing rule导入闭包测试——对每个拟共享文件问的不是它算不算 moderator 相关而是它完整的传递~/…导入闭包是否也可移动只要有一个叶子摸到 zustand store、tRPC 客户端、provider 或image.service整个候选即被阻塞。用这个测试量image.service结果是整体拉出它等于导入主应用的整个内容图feed 缓存失效、NSFW 重排队、化妆品、游戏不可行。解法是把审核面的服务端需求拆成两种形态读队列——自己拥有。审核队列本质是 SQLgetImageModerationReviewQueue、getDownleveledImages、strikes 榜单、CSAM 分页、训练队列只需dbRead selectors enums。把查询函数从image.service/report.service机械抽取出来SQL 一行不改。跨图写入——按动作选路。纯缓存失效的副作用如bustCachesForPosts归根到底是失效 Redis 键→ 复刻同一批civitai/redis键的失效包内完成真实编排漫画重排队、通知扇出、游戏状态→ 代理一次主应用 tRPC 调用接受网络跳数。六个隔离服务moderator.service128 行、blocklist.service、training.service、strike.service等逐一核实过服务间耦合可以直接原样抽取构成新应用自有 router 的骨架。 迁移节奏Phase 0 到 Phase 6 的推进逻辑早期分析给出的路线分七个阶段推进逻辑可以概括为契约先行、就地重构、先易后难、延迟提升阶段核心动作阻塞条件Phase 0发布数据契约枚举/schema/常量契约未就绪则卫星应用无法启动Phase 1主应用内就地重构 6 个移植阻塞点dialog 系统解耦是最高杠杆项Phase 2建立卫星应用vendor-copy UI 词汇与工具先迁 7 个 Easy 页面依赖 Phase 0/1Phase 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 46 个 Hard 页面被整体降级处理——auditor只需给useCheckProfanity配一个配套端点images需要把 PromptHighlight 抽成纯高亮器、CSAM hook 改写成依赖 dialog 接口而非主应用具体 store。更省事的是复核时发现部分阻塞点是误判PromptHighlight 的闭包实际自包含静态词表 字符串工具useCheckProfanity只依赖纯函数库而generation页随ModelVersionFlag.GenerationDisabled位旗标迁移整体移除。先易后难在这里不是排期上的偷懒而是因为 Hard 的成因是点状耦合解点即降级。另一个高杠杆项是 dialog 系统解耦FlaggedModelsList、CsamDetailsForm、UserBanModal、useReportCsamImages四个组件全部被dialogStoreuseDialogContext()卡住。方案是把纯 zustand 的dialogStore提为共享、弹窗组件改接收 props、卫星应用自建轻量DialogProvider——一次重构同时解锁四个组件。 落地实况文档假设与仓库现实的差距早期分析写在 monorepo 化之前它的载体假设git submodule Next.js 卫星应用没有一条被原样采纳。对照当前仓库修订与验证情况如下早期判断现状git submodule civitai-schema-common被否决改为 pnpm workspace五个基础设施包 civitai/db-schema契约层卫星应用用 Next.js被修订落地为 SvelteKitsveltejs/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.tsapps/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 仍以源码存在主应用先就地重构、卫星应用按需迁移的路线在代码层面是可追溯的。✅ 可复用的方法论清单从这条分析链里能带走的是六条可迁移到其他项目的原则先画依赖地图再谈拆分。把待迁移页面的全部导入归为契约、胶水、域组件、通用 UI、工具、阻塞点六组用多少个页面导入它量化每项的共享价值。用导入闭包测试做唯一裁决。对每个拟共享文件跑一遍传递导入闭包只要有一个叶子摸到全局状态、RPC 客户端或 provider整份候选标记为阻塞直到叶子被处理。契约按共享数据裁定代码按消费者数量裁定。双应用读写的枚举、常量、持久化 JSON 形状必须打包成单一来源单消费者代码即使看起来相关也留在应用内。巨型服务用读队列自持 写入逐动作选路拆解。查询函数机械抽取SQL 不变写操作按副作用分类选复刻缓存失效键或代理一次 RPC拒绝整体迁移。UI 原语 vendor-copy 先行提升延后。接受早期重复成本等视觉漂移真实造成摩擦再提升为共享包避免在拆分窗口内做全应用调用点重写。移动分两步提交。第一步纯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),仅供参考
