civitai 审核查询移植全景清单:主应用到 `@civitai/db-queries` 的收敛(Convergence)与 Net-new 实战指南
2026/9/18 16:31:51 网站建设 项目流程

civitai 审核查询移植全景清单:主应用到@civitai/db-queries的收敛(Convergence)与 Net-new 实战指南

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

导读

本文以仓库中的 docs/db-queries-moderation-port-checklist.md 为骨架,系统梳理 civitai 主应用(main app)尚未迁入共享包@civitai/db-queries的全部审核(moderation)数据库查询:一部分是已移植域在主应用中的"孪生实现"(Convergence,收敛项),另一部分是审核应用完全没有覆盖、需要全新写入共享包的能力(Net-new,全新项)。读完本文,你将掌握:主应用与共享查询包之间的边界划分规则、19 个已移植域与剩余待办域的一一对应关系、14 个 Net-new 域的取舍理由与优先级,以及 Prisma → Kysely 移植过程中被EXPLAIN测试网捕获的真实坑位(含strike.service.tsFOR UPDATE聚合陷阱等源码级证据)。


一、背景:为什么需要一份"审核查询移植清单"

1.1 共享数据访问层@civitai/db-queries

@civitai/db-queries是 monorepo 的共享数据访问层:以类型化的 Kysely 查询函数形式承载跨应用的数据库读写,主应用与各 SvelteKit 分支(spoke)共用同一套查询。Prisma 并不消失,而是降级为schema 来源、迁移工具与类型生成器(通过 prisma-kysely 生成@civitai/db-schema/kyselyDB类型),查询 API 全部由 Kysely 承担。该包的完整设计与迁移计划见 docs/db-queries-kysely-plan.md,作者约定与测试规范见 packages/civitai-db-queries/README.md。

1.2 现状:moderator 应用已先行,主应用只消费了一个函数

审核应用(moderator app)自身的查询已经完成移植,共 19 个域(reportsusersmodel3dcosmeticsmod-activitycomicsingestionblocklistimage-rating-reviewimage-reviewimage-moderationimage-moderation-effectsimage-tagsarticlesarticle-rating-reviewscannertags-on-imagesidebar-countsrewards),约 60 个查询函数,每个都配有编译 SQL + DB 支撑的EXPLAIN测试,以子路径@civitai/db-queries/<domain>导出。

而主应用侧目前只有report.service.ts一个消费点——bulkSetReportStatus委托共享包的setReportStatusMany。清单中bulkSetReportStatus一项已打勾([x]),其余全部是主应用本地实现,等待收敛或新写入。

1.3 黄金规则:纯查询进包,副作用留应用

这是整个移植工作的第一原则,清单开头原文即强调:

许多"convergence"项是签名分叉(signature-divergent)的:主应用函数在包暴露的窄原语之上叠加了多步编排(索引/缓存/申诉/通知等副作用)——收敛意味着在主应用侧基于包原语重组编排,而不是 1:1 互换。纯查询留在包内,副作用留在应用(与 moderator 移植采用的规则一致)。

从源码看这条规则的落地:bulkSetReportStatus(src/server/services/report.service.ts)先以findMany读取目标报告、再用$transaction批量更新状态,随后仍在应用侧完成trackModReports(审核行为审计)与reportAcceptedReward(举报奖励发放)等副作用——包内的setReportStatusMany只负责那一条原子UPDATE … WHERE id IN (…) RETURNING


二、Part A — 收敛项:18 个已移植域的孪生实现

收敛项的含义:该域在包中已有共享查询,主应用里存在同一能力的本地版本,目标是用共享实现替换本地实现(必要时重组编排)。以下按主文档顺序逐域展开,标注来源服务文件与包内对应原语。

2.1 reports(services/report.service.ts

主应用函数包内对应备注
getReportsgetReports审核队列读取
getReportById/getReportByIds批量 id 读取
updateReportById(notes/status)updateReportNotes/setReportStatus状态转移窄原语
updateImageReportStatusByReason(在image.service.ts按原因批量更新图片报告
createReport尚未入包含 Report + 各类型 join + ImageRatingRequest / tag-vote / article 分支,是净新增工作
bulkSetReportStatussetReportStatusMany已委托(唯一已收敛项)

2.2 image-review(services/image.service.ts

  • getImageModerationReviewQueue:主应用的"巨石"队列查询,内含 needsReview / tagReview / reportReview 三个 CTE。注意翻译注记中的警告:其tagReview分支TagsOnImageNew.attributes位掩码 CTE)在包内所有 image-review 函数中均无对应实现,收敛时必须补移植。
  • getImageModerationCounts:按桶 + reported 的计数,与包内getImageReviewCounts语义不同(见下文调和注记)。
  • getImagesModRules/bustImageModRulesCache→ 包getModerationRuleDefinitions
  • getModeratorPOITags:POI 标签汇总,包内无精确孪生,接近 Net-new。

2.3 image-moderation(services/image.service.ts,基于包原语重组)

主应用函数包原语
handleBlockImagessetImageBlocked(phash/通知/mod-activity 副作用留应用)
handleUnblockImagessetImageAccepted(申诉/索引/标签副作用留应用)
moderateImages调度器(dispatcher),自身不迁移,重组调用
updateNsfwLevelrecomputeImageNsfwLevel
updateImageNsfwLevel(6779)setImageNsfwLevel
updateImageAcceptableMinortoggleImageFlag/updateImagesFlag
reportCsamImages(image 分支)报告分支归 reports 域
resolveEntityAppealimage 分支setImageAppealStatus/-Restored/-RejectedgetPendingImageAppealAppellants

2.4 image-moderation-effects(services/image.service.ts

  • phash 的 ClickHouse 写入:addBlockedImage/bulkAddBlockedImages/bulkRemoveBlockedImages——大概率留在应用侧(ClickHouse 不在包章程范围内)。
  • 搜索索引/缓存扇出辅助函数(queueImageSearchIndexUpdate):只是副作用辅助,不迁移。

2.5 image-tags(services/tag.service.tsservices/image-review.service.ts

  • getTagsForReview→ 包getImageTagReviewQueue/getImageTagReviewTags
  • moderateTagsdisableTags:标签禁用核心。注意disableTags的 model 分支引用了一个TagsOnModels不存在的disabled(源实现自身就带TODO.fix,是死分支),移植时如实照搬并在注释中标注。

2.6 tags-on-image(services/tagsOnImageNew.service.ts

  • insertTagsOnImageNew/upsertTagsOnImageNew/deleteTagsOnImageNew:存储过程写入 →upsert_tag_on_image
  • applyTagRules/getTagRules

2.7 blocklist(services/blocklist.service.ts

  • upsertBlocklistgetBlocklistDTO/getBlocklistDataremoveBlocklistItems
  • 执法读取:throwOnBlockedLinkDomainthrowOnBlockedMessagePatterngetBlockedEmailDomainsstripBenignPhrases
  • 边界澄清moderation-utils.ts中的 blocklist 是Redis 支撑而非 DB 表,不属于移植目标——清单特意标注,防止把 Redis 逻辑误当 PG 查询迁入。

2.8 ingestion(services/image.service.tsjobs/image-ingestion.ts

  • getImagesPendingIngestiongetIngestionErrorImagesresolveIngestionErrorgetIngestionResults
  • ingestImages作业的读写(JobQueue 驱动)。

2.9 scanner(services/scanner-audit.service.ts

  • recordImageScanscanner_label_results表 → 包upsertScannerLabelVerdict/insertScannerContentSnapshot

2.10 image-rating-review(services/image.service.ts

  • getImageRatingRequests:包getImageRatingRequests的主应用孪生。

2.11 articles(services/article.service.tsnsfwLevels.service.ts

  • unpublishArticleByIdrestoreArticleByIdgetModeratorArticlesrescanArticle
  • recomputeArticleIngestion/recomputeArticleIngestionInTx:这两处源实现特意用原生$executeRaw绕过@updatedAt自增——Kysely 移植版天然规避(类型化 builder 的set中不含updatedAt)。
  • updateArticleNsfwLevels
  • articleModerationAdapter:EntityModeration 管线钩子,与 Net-new #5 联动。

2.12 article-rating-review(services/article.service.tsservices/article-rating-review.helpers.ts

  • getArticleRatingReviewsgetArticleRatingReviewCountsresolveArticleRatingReviewcreateArticleRatingReviewgetArticleRatingReviewForOwner
  • 辅助函数:computeArticleDerivedNsfwLevelevaluateAutoApproveGateautoResolveArticleRatingReviewmaybeAutoResolveDisputeAfterScan

2.13 model3d(services/model3d.service.tsnsfwLevels.service.ts

  • unpublishModel3DdeleteModel3DrestoreModel3DsetModel3DNsfwLeveltoggleModel3DFlag
  • updateModel3DNsfwLevels/updateModel3DNsfwLevelForThumbnailImage(后者在翻译注记中有一个 CTE 优化:直接以thumbnailImageId过滤,替代"先读 id 再IN(...)"的两趟往返)。
  • 范围确认待办model3d-review.service.ts(用户评价)与model3d-report.service.ts(用户举报)是否属于已移植的 "model3d" 域,需要与包维护者确认。

2.14 comics(routers/comics.router.ts内联实现)

  • setTosViolationsetProjectNsfwLevelsetChapterNsfwLevelgetModReviewQueuemoderatorUnpublishChapter
  • updateComicChapterNsfwLevels/updateComicProjectNsfwLevels/updateComicNsfwLevels(ForImage)
  • 关键前置:comic 审核逻辑内联在comics.router.ts里(没有comic.service.ts),必须先抽取为 service 函数再移植(见"提取后再移植"原则)。

2.15 cosmetics(services/cosmetic.service.ts

  • grantCosmeticsToUsers+grantCosmetics→ 包grantCosmeticsToUsers/insertUserCosmeticGrant
  • getPaginatedCosmetics已移植,只需替换调用方。

2.16 mod-activity(services/moderator.service.ts

  • trackModActivity→ 包recordModActivity。调用方分布在 report / image 服务与 entity-moderation 作业中。

2.17 rewards(部分,services/rewards-bonus-event.service.ts

  • getGlobalRewardsBonus✅ 已移植。
  • 扩展项:getActiveRewardsBonusEvent+RewardsBonusEventCRUD(见 Net-new #12)。

2.18 sidebar-counts(部分)

  • 包内已有 report / appeal / tag-review 计数,但主应用仪表盘计数不同。需调和:getImageModerationCounts、CSAM 的getCsamReportStats、申诉的getAppealCount与包内计数对齐。

三、Part B — 14 个 Net-new 域:moderator 应用没有对应物

这些是共享包完全未覆盖、需全新写入的能力。按主文档编号逐一说明。

#1 model / modelVersion 审核(services/model.service.tsmodel-version.service.ts

unpublishModelByIdunpublishModelVersionByIddeleteModelByIdrestoreModelByIdpermaDeleteModelById(事务组合器)、toggleLockModeltoggleLockCommentssetModelsCategory;poi/nsfw/minor/sfwOnly +lockedPropertiesupsertModel/updateModelByIdgetTrainingModelsForModeratorsupdateModelNsfwLevels/updateModelVersionNsfwLevelsnsfwLevels.service.ts)。

#2 model-flag 自动扫描队列(services/model-flag.service.tsmodel-file-scan.service.ts

upsertModelFlaggetFlaggedModelsresolveFlaggedModelunpublishBlockedModel。注意:源实现upsertModelFlag的原生 upsert SQL缺少逗号(无法执行),移植时已修正,保证每个扫描列都能回写;且ModelFlag.sfwOnly列在schema.prisma和生成的 Kysely 类型中存在,但线上表尚无该列(未应用的迁移),EXPLAIN因此失败,移植版已移除该列写入(isFlagged决策仍使用该字段),待迁移落地后恢复。

#3 collection 审核(services/collection.service.tsnsfwLevels.service.ts

updateCollectionItemsStatussetCollectionItemNsfwLevelupdateCollectionsNsfwLevels

#4 post 审核(services/post.service.tsnsfwLevels.service.ts

deletePost(mod 路径,事务组合器)、updatePostNsfwLevel/updatePostNsfwLevels

#5 entity / 文本审核(XGuard / Clavata)(services/entity-moderation.service.tsjobs/entity-moderation.ts

  • upsertEntityModerationPendingrecordEntityModerationSuccessrecordEntityModerationFailuregetEntityModerationWithImageNsfwLevel
  • 作业编排:runModQueuerunModChatclearAutomatedReportsautoMuteIfScamAccount(+ReportAutomated、JobQueue)。
  • 决策点:主应用已有ModerationAdapter注册表(src/server/services/moderation-adapters.ts,目前注册 Article / Challenge / Model / WildcardSetCategory 四个适配器,getModerationAdapter(entityType)按实体类型取用),需决定包是否将其标准化。适配器接口定义在 src/server/services/entity-moderation.service.ts,核心是resolveContent(ids)(批量取文本内容)与submit({entityId, content})(提交审核,失败时写 EM 行为 Failed 供重试作业拾取)。

#6 申诉生命周期(超出图片状态)(services/report.service.ts

createEntityAppealgetRecentAppealsByUserIdgetAppealCountgetAppealDetailsresolveEntityAppeal(非 image 分支)。

#7 用户审核 / 执法(services/user.service.tsjobs/confirm-mutes.ts

  • toggleBantoggleContestBansetUserMutedsetUserModeratorupdateUserById
  • softDeleteUser(CSAM)、deleteUserremoveAllContent(约 15 张表的清空)。
  • bulkUnpublishModelsForBannedUsersetLeaderboardEligibility
  • getUsers(mod 查找,含 banned/muted/deleted 状态、原生前缀搜索)——与包内searchUsers需要先核实 Meili 还是 SQL再决定取舍。
  • confirmMutes作业。

#8 strike / 惩罚系统(services/strike.service.ts)——纯 Net-new,raw-SQL 重

  • 读:shouldRateLimitStrikegetActiveStrikePointsgetStrikeSummarygetStrikesForUsergetStrikeHistoryForModgetStrikesForModgetUserStandings(动态 SQL)。
  • 写:evaluateStrikeEscalationFOR UPDATE事务)、createStrikevoidStrikeexpireStrikesprocessTimedUnmutes

源码证据表明该域为何是 raw-SQL 重灾区:evaluateStrikeEscalation(src/server/services/strike.service.ts)在源实现中直接用SELECT SUM(points) … FOR UPDATE,而 Postgres 明确拒绝聚合查询带FOR UPDATE(错误码 0A000),移植版把行锁下推进WITH locked AS MATERIALIZED (SELECT points … FOR UPDATE) SELECT SUM(points) FROM locked子查询——MATERIALIZED是关键,内联 CTE 会把两个层级塌缩回一个。该函数还演示了正确的边界划分:点数和禁言状态写入是原子单元(锁必须与写入在同一事务内),而通知、会话失效等 I/O 全部留在事务外。

#9 用户限制(生成执法)(routers/user-restriction.router.tsorchestrator/promptAuditing.ts

  • getAll(mod 队列)、resolve(uphold/overturn)、submitContext(PromptAllowlist upsert)、backfillTriggers
  • auditPromptServer(拦截提示词 → 自动禁言 + UserRestriction)、getCachedPromptAllowlist/bustPromptAllowlistCache

#10 CSAM / NCMEC(services/csam.service.ts+csam.service-new.ts

  • createCsamReportcreateExternalCsamReportuploadExternalCsamEvidence
  • getCsamReportsPagedgetCsamReportStatsgetCsamsToReport/getCsamsToArchive/getCsamsToRemoveContent
  • processCsamReportarchiveCsamDataForReportgetUserIpInfo(ClickHouse + PG 混合)、getImageResources
  • 先调和再移植:两个 CSAM 实现需先合并(csam.service-new.ts是超集,旧csam.service.ts是其严格子集),翻译注记明确 CSAM 以-new.ts为移植源。

#11 装扮撤销 / 定向发放(services/cosmetic.service.ts

assignCosmeticByTarget(collection/userIds,支持 dry-run)、unassignCosmeticequipCosmeticToEntity/unequipCosmetic

#12 RewardsBonusEvent CRUD(services/rewards-bonus-event.service.ts

getActiveRewardsBonusEventupsertRewardsBonusEventdeleteRewardsBonusEventgetRewardsBonusEventByIdgetRewardsBonusEventsPaged

#13 knights "降级" 复核(services/image.service.ts

getDownleveledImages(ClickHouseknights_new_order_downleveled+ PG join)+addToNewOrderQueue

#14 扫描结果摄取管线内部(services/image-scan-result.service.ts

resolveScanOutcomeauditScanResultsmarkImageScanErrorblockImageFromRatinggetAssociatedEntitiesevaluateImageModRulesisExemptFromAiVerificationprocessTags


四、排序原则与执行策略(Sequencing notes)

主文档给出的四条推进纪律,是执行这份清单时避免返工的关键:

  1. ClickHouse 一律排除在包外(按当前包章程):phashblocked_images、降级复核(down-leveled review)、getUserIpInfo的 CH 读取、prohibited-request 计数等均只移植 PG 部分,CH 部分按 moderator 移植先例标注留在应用。这也与 docs/db-queries-kysely-plan.md 的"Out of scope — ClickHouse (deliberately not ported)"一致:@civitai/db-queries是 Postgres/Kysely-only。
  2. 先调和再移植(Reconcile-before-port):两个 CSAM 实现(csam.service.tsvs-new.ts)与 Meili-vs-SQL 用户搜索,必须在移植前拍板以哪个为准。
  3. 先提取再移植(Extract-before-port):comic 审核内联在comics.router.ts,先抽出 service 函数再动查询。
  4. 最高 Net-new 价值排序:strike 系统、用户 ban/mute/delete 执法、CSAM/NCMEC、entity/文本审核——这四类体积大、raw-SQL 密集,且今天没有共享等价物,值得优先投入。

五、移植工程底座:Prisma → Kysely 的翻译纪律与验证体系

清单落地所依托的翻译纪律与测试体系(完整记录见 docs/db-queries-moderation-translation-notes.md 与 packages/civitai-db-queries/README.md),是理解清单各条目"为什么这么列"的底层背景。

5.1EXPLAIN测试网捕获的源实现缺陷

每个移植查询都配有 DB 支撑的EXPLAIN测试(对实时 schema 解析+规划、不执行),借此发现了多处"源实现本身就是坏的"问题——这些问题与迁移无关,主应用也值得修复:

位置源问题移植处理
strike.service.tsevaluateStrikeEscalationSUM(points) … FOR UPDATE被 Postgres 拒绝行锁下推子查询,外层求和,锁意图一致
user.service.tsgetUsersORDER BY LENGTH(username)夹在两个WHERE AND之间(query 设置时 SQL 非法);头像读取用了未加引号的i.nsfwLevel(折叠成不存在的nsfwlevel)且以文本'None'默认值对 int 列ORDER BY移到WHERE之后;加引号"nsfwLevel"且默认值改0
user.service.tsremoveAllContentPrisma{ OR: [...] }实际编译为单个 AND 谓词(只清自己)忠实照搬并注释标注
model-flag.service.tsupsertModelFlag原生 upsert SQL 缺逗号,无法执行修正,保证列完整回写
ModelFlag.sfwOnlyschema 与类型有、线上表无此列移除写入,isFlagged仍用,注释待迁移恢复
tag.service.tsdisableTags(model 分支)引用不存在的disabled列,死分支忠实移植并注释标注,排除出 EXPLAIN

5.2 语义翻译选择(行为等价,非字节一致)

典型如cannotPublish过滤器从 Prisma 的{ not: { path: ['cannotPublish'], equals: true } }译为meta -> 'cannotPublish' IS DISTINCT FROM 'true'::jsonb(null/false 均视为"可发布",与 Prisma 对 null 键的NOT(x = true)略有差异);toggleImageFlag把源实现的"读后取反写"两条语句折叠为一条无竞态的SET flag = NOT flagupdateModel3DNsfwLevelForThumbnailImagethumbnailImageId直接过滤 CTE,省一次往返。@updatedAt是系统性问题:Prisma 客户端自动 bump,Kysely 不 bump 且无 DB 触发器——Prisma-builder 类 UPDATE 显式写updatedAt,raw-SQL 类 UPDATE 刻意不写(源 raw SQL 也不写)。

5.3 收敛调和注记(Convergence reconcile)

主应用函数与已移植的 moderator 版本语义分叉的 5 处,采用"不重复、先调和"策略:

  • getImageReviewCounts(包)vsgetImageModerationCounts(主应用):后者计appeal(无摄取门禁)且追加 reported-image 的 UNION,语义不同。
  • getImageReviewQueue(包)vsgetImageModerationReviewQueue(巨石):monolith 的tagReview分支无对应物,需补移植。
  • getArticlesForModeration(包)vsgetModeratorArticles(主应用):后者更丰富(id游标分页 + 更宽嵌套 select),未移植。
  • unpublishModel3d(包)vsunpublishModel3D(主应用):写入核心相同,保留已有实现。
  • getComicReviewQueue(包)vsgetModReviewQueue(主应用):谓词相同,仅结果形状不同(router 返回 Prisma 嵌套对象,移植版返回扁平行)。

5.4 三层测试体系

  1. 编译 SQL 测试(必需、免 DB):断言函数编译出的精确 SQL 与参数,用离线 harness(src/test/harness.tscompileHarness(),基于 KyselyDummyDriver)——能捕获IN ()空数组守卫缺失、where丢失、set乱序等回归。执行:pnpm --filter @civitai/db-queries test
  2. 行为 + 执行计划测试(热路径必需、需活库):explainHarness()对编译 SQL 做EXPLAIN(不 ANALYZE),列/join/类型/存储过程签名解析失败会在此暴露;环境门控(TEST_DATABASE_URL或本地根.envDATABASE_URL),无库时describe.skipIf(!h.hasDb)跳过。
  3. Prisma-vs-Kysely 行为等价(移植自 Prisma 的热路径):双实现跑同一批行、深比较结果,防谓词丢失;位于消费应用内<civitai>/src/server/db/__tests__/kysely-prisma-parity.test.ts,通过KYSELY_PARITY_DATABASE_URL开启且刻意不回退DATABASE_URL(它要写 fixture,需要一次性数据库)。

正确性硬规则还包括:in ([])前的空数组守卫(Kysely 编译出IN ()是语法错误,Prisma 静默 no-op);jsonb 写入必须toJson()(node-postgres 会把 JS 数组误序列化成 Postgres 数组字面量);Postgres 枚举数组列需注册registerEnumArrayTypeParsers(动态 oid 无默认解析器,否则读出{a,b}字面量)。事务组合器(如unpublishModelByIdpermaDeleteModelByIddeletePostunpublishBlockedModelsetModelsCategoryautoResolveArticleRatingReview)无法在离线 harness 上EXPLAIN,只对其组成语句函数做编译与 EXPLAIN,组合器本身保持薄封装。


六、相关文档导航

  • docs/db-queries-moderation-port-checklist.md —— 本文主体,主应用剩余审核查询的全量清单(Convergence + Net-new)。
  • docs/db-queries-moderation-translation-notes.md —— Prisma→Kysely 翻译偏差逐条记录(按风险分组)。
  • docs/db-queries-kysely-plan.md —— 包的整体迁移计划:执行器注入、事务组合、lag 感知、分阶段推进。
  • packages/civitai-db-queries/README.md —— 包结构与作者约定(命名、签名、jsonb/@updatedAt/枚举数组处理、三层测试)。
  • docs/moderator-app-package-boundary.md —— moderator 应用包边界分析,"纯查询入包、副作用留应用"规则的由来(读队列自持、跨图写入按动作选 Redis 缓存失效或代理回主应用)。
  • 源码参照:src/server/services/report.service.ts(已收敛的bulkSetReportStatus)、src/server/services/strike.service.ts(FOR UPDATE聚合陷阱)、src/server/services/moderation-adapters.ts(实体审核适配器注册表)、packages/civitai-db-queries/src/model.db.ts(包内查询函数的作者风格范例,含空数组守卫与枚举类型说明)。

这份清单的执行边界清晰:包负责类型化、可共享、可EXPLAIN验证的纯查询;主应用保留编排、缓存、通知、ClickHouse、计费等一切副作用。按"ClickHouse 排除 → 先调和 → 先提取 → 高价值优先"四条纪律推进,即可逐步把主应用审核查询全部收敛到@civitai/db-queries,与已移植的 19 个 moderator 域最终合流。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询