ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

WeKan Schema 迁移体系全解析:从已移除的 cron 迁移系统到启动自愈升级与 MongoDB/FerretDB 数据迁移

2026/9/13 12:32:13 拓冰建站 浏览量
WeKan Schema 迁移体系全解析:从已移除的 cron 迁移系统到启动自愈升级与 MongoDB/FerretDB 数据迁移 WeKan Schema 迁移体系全解析从已移除的 cron 迁移系统到启动自愈升级与 MongoDB/FerretDB 数据迁移【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan本文围绕 WeKan 仓库 docs/Databases/Migrations 目录下的迁移文档展开系统梳理 WeKan 自有 schema 迁移把既有看板、列表、卡片、用户数据结构升级到新版本期望的形态的完整演进脉络旧 cron 驱动迁移系统的设计缺陷与修复过程、为何在 issue #6521 中被整体移除、以及当前版本采用的启动时版本门控 schema 升级 看板打开自愈修复 MongoDB/FerretDB 文本数据迁移三套体系。读完本文你将理解 WeKan 数据从旧版本升级到新版本时发生了什么、迁移进度如何被真实计算与持久化、哪些环境变量可以控制升级行为以及如何在管理员面板和命令行中观测迁移状态。一、WeKan 中的两类迁移与 Migrations 文档目录的定位在 docs/Databases/README.md 中数据库文档被划分为 FerretDB、MongoDB、Migrations、ToroDB 四个子目录。其中 Migrations 目录描述的不是数据库之间的切换而是WeKan 自身的 schema 迁移WeKans own schema migrations: the ones that move existing boards, lists, cards and users to the shape a newer WeKan expects. This is not about migrating between databases.两类迁移的边界必须分清迁移类型做什么文档位置数据库间迁移在 MongoDB 与 FerretDBSQLite/PostgreSQL 等之间搬迁数据docs/Databases/MongoDB、docs/Databases/FerretDBSchema 结构迁移把旧版本遗留的 boards / lists / cards / users 数据结构升级到新版本代码期望的形态docs/Databases/Migrations值得注意的是docs/Databases/Migrations/README.md 本身明确声明Everything in this directory is a historical record.It documents the old cron-driven migration system and the per-swimlane-lists migration, both of which have since been REMOVED; each file says so at its top. They are kept because they explain why the current system looks the way it does.也就是说该目录是一份历史档案它记录的是已经被删除的旧 cron 驱动迁移系统和 per-swimlane-lists 迁移保留这些文件的目的是解释当前系统为什么长成现在这个样子。目录内的文件分工如下文件内容MIGRATION_SYSTEM_IMPROVEMENTS.md旧系统做得不好的地方以及改动内容CODE_CHANGES_SUMMARY.md这些改进所触及的代码MIGRATION_SYSTEM_REVIEW_COMPLETE.md配套进行的系统评审SESSION_SUMMARY.md该评审会话的总结verify-migrations.sh用于验证旧系统的脚本当前 WeKan 实际运行的迁移体系则记录在 docs/Features/Admin-Panel/Problems/Migrations.md。下面先完整还原历史档案中的旧系统再介绍现状。二、历史档案一旧 cron 驱动迁移系统的问题与修复2.1 问题陈述旧迁移系统核心文件为已删除的server/cronMigrationManager.js在长期运行后暴露出三类问题模拟进度Simulated Progress大量迁移显示的是模拟进度而不是真实数据库变更产生的进度误报False Positives全新安装的 WeKan 也会无谓地执行迁移——因为数据库里根本没有旧数据需要迁移检查缺失Missing Checks部分迁移类型没有显式的是否需要迁移needs migration判定。2.2 修复一isMigrationNeeded()默认分支从 true 改为 false旧系统在isMigrationNeeded()的 switch 语句里有一个危险默认分支// BEFORE: default: return true; // 导致所有未知迁移都被执行 // AFTER: default: return false; // 只有显式检查过的迁移才被认为是需要这一行改动的影响在于全新安装不会再触发任何幽灵迁移只有拥有显式检测逻辑的迁移类型才会被判定为需要执行未知迁移 ID 一律视为不需要避免对不存在的旧数据结构做无用功。2.3 修复二为全部 13 类迁移补充显式检测旧系统为 13 类迁移在isMigrationNeeded()中逐一增加了基于真实数据库查询的检测逻辑原文档标注的旧文件行号见下表迁移 ID检测逻辑旧文件行号lowercase-board-permission检查是否存在permission字段为大写值的看板404-407change-attachments-type-for-non-images检查是否存在缺少type字段的附件408-412card-covers检查是否存在带coverId字段的卡片413-417use-css-class-for-boards-colors检查是否存在带color字段的看板418-421denormalize-star-number-per-board检查是否存在profile.starredBoards的用户422-428add-member-isactive-field检查是否存在没有isActive的看板成员429-437ensure-valid-swimlane-ids检查是否存在swimlaneId非法的卡片438-448add-swimlanes检查泳道结构是否存在449-457add-checklist-items检查是否存在没有items数组的清单458-462add-card-types检查是否存在没有type字段的卡片463-469migrate-attachments-collectionFS-to-ostrioFiles直接返回 false全新安装使用 Meteor-Files470-473migrate-avatars-collectionFS-to-ostrioFiles直接返回 false全新安装使用 Meteor-Files474-477migrate-lists-to-per-swimlane检查看板是否需要 per-swimlane 迁移478-481以几个典型 case 为例检测的本质是对旧数据结构的存在性探测见 CODE_CHANGES_SUMMARY.mdcase lowercase-board-permission: return !!Boards.findOne({ $or: [ { permission: PUBLIC }, { permission: Private }, { permission: PRIVATE } ] }); case change-attachments-type-for-non-images: return !!Attachments.findOne({ $or: [ { type: { $exists: false } }, { type: null }, { type: } ] }); case card-covers: return !!Cards.findOne({ coverId: { $exists: true, $ne: null } }); case denormalize-star-number-per-board: return !!Users.findOne({ profile.starredBoards: { $exists: true, $ne: [] } }); case add-member-isactive-field: return !!Boards.findOne({ members: { $elemMatch: { isActive: { $exists: false } } } }); case ensure-valid-swimlane-ids: return !!Cards.findOne({ $or: [ { swimlaneId: { $exists: false } }, { swimlaneId: null }, { swimlaneId: } ] }); case add-checklist-items: return !!Checklists.findOne({ $or: [ { items: { $exists: false } }, { items: null } ] }); default: return false; // ✅ 修复后只运行显式检查过的迁移两个 CollectionFS 迁移 case 直接返回false是有意为之全新安装的数据库由 models/ 目录按新 schema 创建附件与头像只使用 Meteor-Files不存在需要迁移的旧结构。2.4 修复三所有迁移改用真实进度跟踪旧系统大量迁移通过cronJobStorage.saveJobStep上报的进度是模拟的——按固定步数循环递增而不是根据真实数据库变更计数。修复后每个迁移执行方法都先用真实查询找出确实需要迁移的记录再逐条更新并实时计算百分比。以看板颜色迁移executeBoardColorMigration为例// 真实检查——找出确实需要迁移的看板 const boardsNeedingMigration Boards.find({ $or: [ { color: { $exists: true, $ne: null } }, { color: { $regex: /^(?!css-)/ } } ] }, { fields: { _id: 1 } }).fetch(); // 真实进度跟踪 for (const board of boardsNeedingMigration) { Boards.update(board._id, { $set: { colorClass: css-${board.color} } }); updated; const progress Math.round((updated / total) * 100); cronJobStorage.saveJobStep(jobId, stepIndex, { progress, currentAction: Migrating board colors: ${updated}/${total} }); }类似地executeChecklistItemsMigration会先查询所有items缺失或为null的清单逐条$set: { items: [] }并上报Initializing checklists: ${updated}/${total}若查询结果为空则直接返回All checklists properly configured. No migration needed.不做任何无谓写入。2.5 修复四移除模拟执行回退旧系统对未知迁移类型有一段模拟 10 步进度的回退代码会在duration时间内循环递增伪造进度。修复后该回退被删除替换为显式告警并立即标记完成// BEFORE: Simulated 10-step progress for unknown migrations // AFTER: console.warn(Unknown migration step: ${stepId} - no handler found.); cronJobStorage.saveJobStep(jobId, stepIndex, { progress: 100, currentAction: Migration skipped: No handler for ${stepId} });这一改动带来三个直接收益未知迁移不再伪造工作量、日志能清晰暴露无法识别的迁移类型、所有迁移要么展示真实进度要么如实报告不需要。2.6 修复五补齐缺失的模型导入add-checklist-items迁移在旧代码中使用了 Checklists 模型却未导入导致该迁移无法正常工作。修复后在文件顶部补充import Checklists from /models/checklists;2.7 旧系统的三层架构MIGRATION_SYSTEM_REVIEW_COMPLETE.md 把旧系统归纳为三层架构检测层isMigrationNeeded()旧文件 402-487 行每种迁移一个 case查询数据库中是否存在旧/不完整数据结构返回true/false路由层executeMigrationStep()旧文件 494-570 行根据stepId分发到各自的 execute 方法分发后立即 return 防止穿透实现层execute 方法旧文件 583-1485 行查询需要迁移的记录 → 通过cronJobStorage上报进度 → 逐条更新并统计真实计数 → 带上下文记录错误 → 汇报迁移总数。旧系统共新增/更新了 13 个执行方法包括executeLowercasePermission、executeAttachmentTypeStandardization、executeCardCoversMigration、executeMemberActivityMigration、executeAddSwimlanesIdMigration、executeAddCardTypesMigration、executeAttachmentMigration、executeAvatarMigration、executeBoardColorMigration、executeDenormalizeStarCount、executeEnsureValidSwimlaneIds、executeChecklistItemsMigration、executeComprehensiveBoardMigration。2.8 新装与旧库的行为对比全新安装isMigrationNeeded()对旧数据结构的探测全部落空 → 全部返回false→ 迁移被跳过零额外数据库写入日志形如All checklists properly configured. No migration needed.旧数据库升级探测命中旧结构 → 返回true→ 执行迁移并上报真实计数进度形如Migrating board colors: 45/120。2.9 验证脚本 verify-migrations.sh历史档案还保留了用于验证旧系统的 verify-migrations.sh。该脚本对当时的server/cronMigrationManager.js做 6 项检查isMigrationNeeded()默认分支返回false13 类迁移全部拥有isMigrationNeeded()case 检查13 类迁移全部拥有async executeXxx()处理器Checklists 模型已导入模拟执行回退代码已移除存在真实数据库实现探测Boards.find({、Cards.find({、Users.find({、Checklists.find({等查询。运行方式为bash verify-migrations.sh脚本会逐项输出 PASS/FAIL 并给出汇总。需要强调的是该脚本同样被标注为 OBSOLETE历史档案因为目标文件server/cronMigrationManager.js已被删除。三、为什么旧系统被整体移除per-swimlane-lists 并不存在所有历史档案文件顶部都带有一段相同的 OBSOLETE 声明说明了旧系统被移除的根本原因The cron migration subsystem (server/cronMigrationManager.js) was deleted, and the per-swimlane-lists board migrations (comprehensiveBoardMigration,fixMissingListsMigration,restoreLostCards,restoreAllArchived) were removed in issue #6521.WeKan lists are board-wide — the same lists appear in every swimlane; there is no per-swimlane lists model.这段话包含两层关键事实server/cronMigrationManager.js已被删除——在当前的仓库源码树中已无法找到该文件可在server/目录下搜索确认这与历史档案的声明一致per-swimlane-lists 迁移被整体移除——WeKan 的列表模型是看板级共享的同一个列表出现在每一个泳道swimlane中根本不存在每个泳道各自拥有一套列表的数据模型。因此comprehensiveBoardMigration、fixMissingListsMigration、restoreLostCards、restoreAllArchived这四个围绕按泳道迁移列表构建的迁移其前提本身就是不成立的最终在 issue #6521 中被移除。旧系统遗留下的数据问题例如某些旧版本曾把列表盖章到默认泳道、或按泳道复制出重复列表现在由两套机制兜底详见下文第四、五节。四、当前 schema 升级体系启动时的版本门控自愈升级4.1 设计动机与整体思路当前的核心实现位于 server/lib/schemaUpgradeSteps.js纯逻辑、无 Meteor 依赖可被 tests/schemaUpgradeSteps.test.cjs 用普通 Node 直接单测以及其 Meteor 包装层 server/startupSchemaUpgrade.js。该模块的文件头注释交代了完整的历史脉络与设计动机详见源码第 1-62 行WeKan v0.9–v8.00 时代在启动时运行Migrations.add(...)步骤v8.01 因大数据库会导致长时间停机而禁用了它们改用读时兼容read-time compatibility但读时兼容只覆盖泳道时代之后的结构从旧版本或旧备份/Sandstorm grain直接跳到当前版本的数据仍可能带着迁移前的旧形态——文本数据还在数据库里但新代码渲染不出来不可见该模块在不重新引入停机问题的前提下恢复了安全网版本门控VERSION-GATED_wekan_migration标记集合记录上次成功复查的 WeKan 版本与时间版本未变时一次启动只花一次findOne仅在新版本发布后或设置WEKAN_FORCE_SCHEMA_UPGRADEtrue才强制完整复查只迁移缺失部分复查后只对缺失的步骤执行迁移且使用对大数据库友好的查询形态——有界的findOne/countDocuments存在性探测、用distinct()连接替代全表扫描、用服务端updateMany批量更新替代逐文档往返幂等中断后重启可安全续跑单个步骤失败不会阻塞 WeKan 启动进度可观测通过getUpgradeState发布进度供迁移仪表盘展示。4.2 启动接入与进度仪表盘server/startupSchemaUpgrade.js 在Meteor.startup中触发升级第 69-96 行并注册了/schema-upgrade-statusHTTP 端点第 32-67 行同时支持 HTML 仪表盘与?json原始状态输出。关键行为升级在后台运行WeKan 立即开始服务不阻塞启动仪表盘逐步骤显示 status / fixed / unresolved / error页面每 3 秒自动刷新meta http-equivrefresh content3版本未变时显示Already re-checked for version X — nothing to do环境变量开关WEKAN_FORCE_SCHEMA_UPGRADEtrue强制完整复查WEKAN_SKIP_SCHEMA_UPGRADEtrue完全跳过升级日志输出[schema-upgrade] skipped。4.3 升级步骤清单继承自 v8.00 的server/migrations.js从 server/lib/schemaUpgradeSteps.js 第 181 行起的steps数组可以看到当前全部升级步骤每步对应一个历史版本遗留的数据形态问题步骤名解决的问题对应历史迁移archived-flag-backfill旧文档缺少archived字段永远匹配不上archived:false的视图查询 → 卡片不可见swimlane-structure补建泳道结构对应 add-swimlanes、挽救 dangling listId、以及 #1959/#1971 泳道视图可见性救援checklist-items-embedded内嵌checklist.items[]→ ChecklistItems 集合对应 add-checklist-itemscustomfields-boardIds标量boardId→boardIds数组对应 mutate-boardIds-in-customfieldsboard-allows-defaults缺失allows*标志会以 undefinedfalse 隐藏已有描述/清单/评论文本对应 add-*-allowed 系列步骤board-members-isactive成员缺isActive会被isActiveMember()拒绝访问对应 add-member-isactive-fieldboard-permission-lowercasePUBLIC大写权限会让看板静默变成仅成员可见对应 lowercase-board-permissionnonfinite-sort-repair旧版本遗留的 ±Infinity/NaNsort值会破坏卡片排序看板卡在 spinner并被 FerretDB 拒绝中止 MongoDB→FerretDB 迁移 → 重置为有限值 0fs-path-heal文件系统附件/头像记录的路径早于当前WRITABLE_PATH布局v6.10-18 的uploads/coll、v6.19-v8.4x 的WRITABLE_PATH/coll、CFS→ostrio 临时文件→ 定位二进制并重指向/复制merge-per-swimlane-lists处理 v7.98 per-swimlane lists 时代遗留的列表复制问题详见下文checklist-minicard-unset一次性的清空步骤使用独立于版本标记的专用 markerchecklist-minicard-unset4.4 详解merge-per-swimlane-lists把按泳道复制的列表合并回共享列表这是与历史档案直接相关、目前仍承担善后职责的关键步骤server/lib/schemaUpgradeSteps.js。其注释完整还原了历史v7.98 时代的migrate-lists-to-per-swimlane步骤把每个列表盖章到默认泳道导致列表在其他泳道中不可见v8.07–v8.19 的看板打开时修复按泳道复制列表又在今天的看板级共享列表视图中渲染出重复列当前 WeKan 以swimlaneId: 的共享列表在每个泳道中渲染因此该步骤通过时代的遗留标记fixMissingListsCompleted/comprehensiveMigrationCompleted、重复标题、或卡片与其被盖章泳道不一致来识别受影响看板把同标题的未归档列表副本合并回一个规范共享列表——卡片保留自己的swimlaneId保住泳道分组与顺序只有列指针listId被重指向跨泳道拖拽继续可用把遗留的swimlaneId盖章清空为并移除时代标记使按需修复工具可重新使用用户在不同泳道里重命名成不同标题的列表会被刻意保留为独立列表强行合并会丢失不同名称健康的原生看板完全不动。五、看板数据自愈repairBoardData 与三种触发时机与 schema 升级并行的另一套兜底机制是看板数据自愈修复完整说明见 docs/Features/Admin-Panel/Problems/Repairs.md。所有修复都是幂等的干净看板不会有任何变更、按看板作用域的问题症状修复动作#6484 绑定列表看板级共享列表被拖进某个泳道后绑定到该泳道从其他泳道消失把列表swimlaneId清回null恢复看板级共享缺泳道卡片没有swimlaneIdnull//缺失的卡片在泳道视图不渲染指派到看板第一个泳道没有泳道则创建Default泳道孤儿卡片swimlaneId指向已删除泳道的卡片不可见重新指派到看板第一个泳道指向已归档泳道不算孤儿修复决策由纯函数规划器models/lib/boardRepair.js的planBoardRepair完成有单测服务端由server/lib/repairBoardData.js应用。修复在三个时机运行不依赖用户停留在进度页面服务启动时server/startup/repairBoardsOnStartup.js启动约 30 秒后START_DELAY_MS在后台对全部看板跑一遍版本门控REPAIR_VERSION当前为 1变更时才会再跑一次可用WEKAN_SKIP_STARTUP_REPAIRtrue跳过进度持久化每 25 个看板写一次供 Problems → Status 与snap run wekan.problems观测看板打开时客户端检查boardRepairNeeded若需修复且查看者是看板管理员则调用repairBoardData方法修复该看板——即使浏览器离开页面方法也会在服务端完成可捕获启动扫描之后新损坏的数据数据库迁移期间MongoDB ↔ SQLite 文本数据迁移会先在源数据库上执行修复再拷贝保证迁出的副本是干净的见下文第六节。六、数据库之间的文本数据迁移MongoDB ↔ FerretDB当前 WeKan 实际运行的迁移与 schema 升级不同是数据库之间的文本数据迁移官方说明见 docs/Features/Admin-Panel/Problems/Migrations.md相关实现包括server/methods/migrateTextDatabase.js、server/lib/repairBoardData.js、server/lib/systemStatus.js、models/textMigrationStatus.js以及client/components/settings/migrationProgress.*。要点如下迁移范围只迁移文本数据boards、lists、cards、users 等除附件/头像外的一切附件与头像留在文件系统双向在 MongoDB 与 FerretDB v1SQLite之间任一方向迁移入口在Admin Panel → Attachments机制两个数据库都讲 MongoDB wire protocol用同一套驱动按集合逐个拷贝、按_idupsert——幂等重复运行安全两个阶段①Repairing——先在在线数据库上运行共享的看板数据修复让拷贝携带干净数据②Migrating——把每个非文件集合分批拷贝到目标数据库后台运行迁移是服务端后台任务不依赖管理员停留在 Attachments 页面关闭页面后迁移仍会跑到完成看到的进度只是它的一个视图进度持久化迁移与启动修复把进度持久化到text_migration_status集合因此无浏览器环境也能观测——Admin Panel → Problems → Status 会列在 In progresssnap run wekan.problems migrations以文本打印迁移后把MONGO_URL指向目标数据库并重启snap 版自动切换见 docs/Features/Admin-Panel/Problems/Migrations.md 中对 Snap 平台的说明。七、遗留的迁移脚本与单次迁移示例除上述系统化迁移外仓库中还保留了两类单点迁移工具可作为理解 WeKan 数据形态演进的补充证据。7.1 server/migrations 下的维护脚本server/migrations/目录保留了若干聚焦脚本从文件名即可看出它们处理的旧数据形态migrateAttachments.js—— 把旧 CollectionFS 附件转换为新 Meteor-Files 结构correctFileExtensions.js、fixAllFileUrls.js、fixAvatarUrls.js—— 文件 URL / 扩展名 / 头像 URL 修正deleteDuplicateEmptyLists.js—— 删除重复空列表ensureValidSwimlaneIds.js—— 校验泳道 ID。以 server/migrations/migrateAttachments.js 为例它通过三个 Meteor 方法暴露能力对应历史档案中第 11、12 类 CollectionFS 迁移的按需手动版migrateAttachment(attachmentId)校验登录态 → 通过getOldAttachmentData读取旧附件 →校验调用者对附件所属看板的访问权限防止任意用户迁移无权看板的附件→ 从 GridFS 读取二进制getOldAttachmentDataBuffer→ 用Attachments.insertAsync以新结构重新插入并返回新旧 ID 映射migrateCardAttachments(cardId)遍历某卡片下全部旧附件逐个迁移返回成功/失败计数与错误列表getAttachmentMigrationStatus(cardId)查询迁移状态。7.2 单次数据修复迁移示例009_fix_v795_due_dates仓库根目录migrations/009_fix_v795_due_dates.js是一个典型的一次性数据修复迁移展示了 WeKan 处理跨版本字段改名 关联引用修复的常规写法对应 issue #6069v7.95 的dueDate→ v8.20 的dueAt// Migration to fix due dates from v7.95 format to v8.20 // Issue: https://github.com/wekan/wekan/issues/6069 export default async function migrate() { console.log( Fixing v7.95 due date migration (Issue #6069) ); // Get current user (assumes single user migration) const currentUser await db.users.findOne({}, { _id: 1 }); if (!currentUser) { console.error(ERROR: No users found. Please ensure at least one user exists.); return; } // Get default board for reference fixing const defaultBoard await db.boards.findOne({ type: { $ne: template-container } }); const defaultList defaultBoard ? await db.lists.findOne({ boardId: defaultBoard._id }) : null; const defaultSwimlane defaultBoard ? await db.swimlanes.findOne({ boardId: defaultBoard._id }) : null; // Fix cards with old dueDate format const cardsToFix await db.cards.find({ dueDate: { $exists: true } }).toArray(); console.log(Found ${cardsToFix.length} cards to migrate); let fixed 0; for (const card of cardsToFix) { const updateDoc { $rename: { dueDate: dueAt }, $set: { dueAt: new Date(card.dueDate), userId: currentUser._id, members: [currentUser._id], modifiedAt: new Date() } }; // Fix invalid board reference const boardExists await db.boards.findOne({ _id: card.boardId }); if (!boardExists defaultBoard) { updateDoc.$set.boardId defaultBoard._id; if (defaultList) updateDoc.$set.listId defaultList._id; if (defaultSwimlane) updateDoc.$set.swimlaneId defaultSwimlane._id; } // Add swimlane if missing if (!card.swimlaneId defaultSwimlane) { updateDoc.$set.swimlaneId defaultSwimlane._id; } await db.cards.updateOne({ _id: card._id }, updateDoc); fixed; } // Fix cards missing userId const cardsMissingUser await db.cards.find({ dueAt: { $exists: true }, userId: { $exists: false } }).toArray(); console.log(Found ${cardsMissingUser.length} cards missing userId); for (const card of cardsMissingUser) { await db.cards.updateOne( { _id: card._id }, { $set: { userId: currentUser._id, members: [currentUser._id] } } ); } const finalCount await db.cards.countDocuments({ dueAt: { $exists: true, $type: date }, userId: { $exists: true } }); console.log(✅ Migration complete! ${finalCount} cards now have properly formatted due dates.); console.log( Fixed ${fixed} cards with old dueDate format.); }该示例展示了此类迁移的四个典型动作字段改名$rename、默认值回填userId/members、悬空引用修复boardId/listId/swimlaneId 回指默认看板、以及用countDocuments做迁移完成校验。八、实践指引如何观测与控制迁移综合当前文档与源码运维与开发者可用的观测/控制手段汇总如下手段位置 / 命令作用Schema 升级仪表盘GET /schema-upgrade-statusHTML与?json查看每个升级步骤的 status / fixed / unresolved / error强制复查环境变量WEKAN_FORCE_SCHEMA_UPGRADEtrue新版本发布之外强制完整复查一次跳过升级环境变量WEKAN_SKIP_SCHEMA_UPGRADEtrue完全关闭启动 schema 升级跳过启动修复环境变量WEKAN_SKIP_STARTUP_REPAIRtrue关闭启动时的全看板自愈扫描文本数据迁移Admin Panel → AttachmentsMongoDB ↔ FerretDB 双向迁移后台运行、幂等、进度持久化迁移/修复状态Admin Panel → Problems → Statussnap run wekan.problems migrations查看正在进行的迁移/修复历史迁移档案docs/Databases/Migrations理解旧 cron 迁移系统为何被移除、当前系统为何如此设计九、总结WeKan 的迁移体系经历了一次完整的否定之否定旧 cron 驱动迁移系统已删除的server/cronMigrationManager.js曾被大幅改进——默认分支改为false、13 类迁移全部补上显式检测、用真实数据库计数替换模拟进度、移除模拟执行回退——这些改进的正确方向只在需要时迁移、只展示真实进度至今仍是设计原则但其中第 13 类migrate-lists-to-per-swimlane建立在一个错误的前提上——WeKan 列表是看板级共享的不存在 per-swimlane lists 模型相关迁移在 issue #6521 中被整体删除当前体系由三套机制组成启动时的版本门控 schema 升级server/lib/schemaUpgradeSteps.js含merge-per-swimlane-lists善后步骤、看板数据自愈修复server/startup/repairBoardsOnStartup.js 与看板打开时的repairBoardData、以及MongoDB ↔ FerretDB 文本数据迁移docs/Features/Admin-Panel/Problems/Migrations.md。三者共同保证旧版本数据升级后可见、可访问、不重复且迁移/修复全程透明可观测。【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考