WeKan 外部工具迁移实战:NextCloud Deck、OpenProject、GitHub、GitLab、Gitea、Forgejo 的导入与导出机制

发布时间:2026/9/14 17:43:03
WeKan 外部工具迁移实战:NextCloud Deck、OpenProject、GitHub、GitLab、Gitea、Forgejo 的导入与导出机制 WeKan 外部工具迁移实战NextCloud Deck、OpenProject、GitHub、GitLab、Gitea、Forgejo 的导入与导出机制【免费下载链接】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/wekanWeKan 通过一套通用的解析器 格式化器架构支持从 NextCloud Deck、OpenProject、GitHub、GitLab、Gitea、Forgejo、Asana、ZenKit 等工具导入看板也支持反向导出为这些工具的原生 JSON 结构。本文基于 外部工具导入导出文档 与仓库源码完整梳理每种工具的字段映射规则、损失报告机制、REST API 端点和 api.py 命令行脚本的用法读完你可以用界面或脚本完成单看板的批量迁移。支持的工具与 JSON 形态对照导入入口在界面菜单All Boards → New → Import导出入口在Board Settings → Export。每种源工具有一个小型解析器parser负责把该工具的 JSON 归一化为通用形状每个目标格式有一个格式化器formatter负责输出该工具的 JSON。两者共享同一个导入引擎和同一个导出收集器。原文档给出的各工具 JSON 形态如下工具导入时粘贴的 JSON 形态导出格式Trello看板导出 JSON来自 Trello 的.json{ name, lists:[…], cards:[…], labels:[…] }Jiraissue 搜索 JSONGET /rest/api/2/search{ board:{name}, issues:[{key, fields:{summary,status,labels}}] }NextCloud Deck带stacks的看板每个 stack 携带cards{ title, stacks:[{title, cards:[…]}] }OpenProjectwork-packages 集合GET /api/v3/work_packages{ _embedded:{ elements:[{subject, _links:{status}}] } }GitHubissues 数组GET /repos/OWNER/REPO/issuesissues 数组[{title, body, state, labels}]GitLabissues 数组GET /projects/ID/issuesissues 数组[{title, description, state, labels}]Giteaissues 数组GET /repos/OWNER/REPO/issuesissues 数组与 GitHub 相似Forgejoissues 数组与 Gitea 相同 APIissues 数组与 GitHub 相似Asana任务导出{ data:[{name, notes, memberships:[{section}], tags, due_on}] }{ data:[{name, notes, completed, due_on, memberships, tags}] }ZenKit列表导出{ title, stages:[{name}], items:[{title, description, stage_name, due, tags}] }{ title, stages:[…], items:[…] }从源码结构看api.py的语法说明还列出kanboard、csv、excel、wekan、markdown等源/格式覆盖范围比上表更宽导入源为trello/wekan/csv/jira/kanboard/excel/deck/openproject/github/gitlab/gitea/forgejo/asana/zenkit导出格式为kanboard/trello/jira/deck/openproject/github/gitlab/gitea/forgejo/asana/zenkit见 api.py 的语法段。通用架构解析器归一化 格式化器发射导入侧的所有解析器集中在 models/lib/externalParsers.js。每个解析器把源 JSON 归一化为统一的 Kanboard 形状// 归一化后的任务形状来自 externalParsers.js 文件头注释 { title, description, column_name, swimlane_name, date_due, owner_username, tags: [string] } // 以及看板级: { board: { name }, columns: [{title}], swimlanes: [{name}], tasks: [...] }解析器按源名注册在EXTERNAL_PARSERS映射表中externalParsers.js#L356-L367deck、openproject、github、gitlab、gitea、forgejo、asana、zenkit、markdown、jira。注意 Gitea 与 Forgejo 共用同一个解析器parseGitea因为它们共享相同的 issue API 形状。导出侧集中在 models/lib/externalExporters.js一个共享的collect()函数先把 WeKan 看板列表、泳道、卡片、标签收集成中性中间结构再由formatters映射表中对应格式的格式化器发射目标 JSONexternalExporters.js#L63-L166。这正好对应文档说的一个导入引擎 一个导出收集器。各工具的导入字段映射NextCloud Deckstacks 到列表parseNextcloudDeckexternalParsers.js#L15-L44接受 Deck 看板对象Deck REST APIGET /boards/{id}/stacks的返回形态映射规则stacks → 列表stacks数组逐个映射为columnscard → 卡片card.title→ 标题card.description→ 描述labels → 标签card.labels字符串或{title}对象均可归一为tagsassigned user → 卡片成员取card.assignedUsers[0]优先participant.uid回退uid再回退card.ownerdue datecard.duedate或card.dueDate→date_due。OpenProjectstatuses 到列表parseOpenProjectexternalParsers.js#L50-L73接受 work-packages 集合GET /api/v3/work_packages的{ _embedded: { elements: [...] } }形态也兼容裸数组work package → 卡片subject或name→ 标题description.raw回退description.html→ 描述status → 列表_links.status.title作为column_name所有出现过的 status 去重后生成columnsdue datedueDate或due_dateassignee_links.assignee.title→owner_usernametype → 标签_links.type.title作为唯一的 tag。GitHub / Gitea / Forgejo共享的 issue 解析器三个工具共用parseIssuesArrayexternalParsers.js#L92-L163行为如下接受 issues 数组GET /repos/{o}/{r}/issues也接受把多页分页结果拼接成的数组——源码注释明确指出补全分页是 API 客户端的事不是解析器的事它只处理拿到的 JSONpull request 被跳过issues.filter(issue !issue.pull_request)issue state → Open / Closed 列表state closed进Closed否则进Open两个列表固定生成labels → 标签milestone 追加为milestone:title标签assignee → 卡片成员只取第一个 assigneelogin或username成为 task Owner多余 assignee 以assignee:login标签保留并写入unsupported损失记录state_reason非completed的 state reason 以state_reason:reason标签保留来源追溯卡片描述末尾自动追加Source: #number和 issue 的html_url便于回查原始 issue评论若导出内嵌了comments_data数组则以Comments:段落追加进描述若只有comments计数而无内嵌数据则记录 unsupported 说明上游存在 N 条评论但未嵌入本次导出externalIdissue number 作为同步匹配键详见下文周期性同步requested_byissue 创建者user.login/author映射为WeKan 的 Requested By即使该用户在本看板没有账号也能以纯文本保留。GitLab 由独立的parseGitlabexternalParsers.js#L177-L196处理GitLab 的 state 是opened/closed而非 GitHub 的open/closedlabels 是字符串数组assignee 用username字段同步键取iid。文档中Member mapping is skipped for these (map members afterwards)的说法对应的正是这一机制issue 的 assignee 被解析为owner_username自由文本而不是直接授权板成员——导入不会仅仅因为源文件里出现了一个用户名就授予板访问权限这也是 Format-Coverage 契约 中明确的安全约束因此导入后需要在界面上手动把成员映射到实际的板成员。Asana 与 ZenKitparseAsanaexternalParsers.js#L201-L224memberships[0].section.name决定列表无 section 时按completed归入Done/In Progressdue_on/due_at→ 截止日assignee.email或assignee.name→ Ownertags归一为标签。parseZenkitexternalParsers.js#L229-L248items中每项的stage_name或stageName/list→ 列表缺省Inbox顶层stages若存在则优先生成列否则从任务推导。导出WeKan 看板到工具 JSON导出由buildExternalExport完成externalExporters.js#L170-L177collect()读取看板的未归档列表、泳道、卡片按sort排序及标签名构建中间结构后交给格式化器。关键规则已完成列表的判定。isClosed用正则/done|closed|complete|archiv|finished/i匹配列表名externalExporters.js#L50-L52。列表名命中 Done/Closed/Complete/Archived 等终结性词汇时其中的卡片导出为 closed 状态的 issue——这就是原文档where a list namedDone/Closed/Complete/Archivedmaps to a closed issue的实现。各格式化器的输出形状externalExporters.js#L63-L166github/gitea/forgejo共用githubLike{ title, body, state: open|closed, labels: [{name}], due_date }gitlabstate用opened/closedlabels 为字符串数组deck{ title, stacks: [{title, cards: [{title, description, duedate, labels:[{title}]}]}] }openproject{ _embedded: { elements: [{subject, description:{raw}, dueDate, _links:{status:{title}}}] } }asana{ data: [{name, notes, completed, due_on, memberships:[{section:{name}}], tags:[{name}]}] }zenkit{ title, stages:[{name}], items:[{title, description, stage_name, due, tags}] }trello完整看板 JSONname/prefs/lists/cards/labels/checklists/actions可与 WeKan 的 Trello 导入往返jira{ board:{name}, issues:[{key: WEKAN-n, fields:{summary, description, status:{name}, labels, duedate}}] }与 WeKan 的 Jira 导入往返。导出结果最后统一经过/server/lib/secureTransfer的出站校验非有限数字、非法日期、不安全 URL、意外密钥、循环引用等即使数据库行早于当前导入校验逻辑存在也能兜底。损失报告{ normalized, warnings, unsupported }契约解析器从不静默丢弃源字段。以 issue 解析器为例归一化形状之外的信息第二个 assignee、state reason、未内嵌的评论数会作为顶层的warnings/unsupported键附加在结果上——这是 Format-Coverage 设计 规定的{ normalized, warnings, unsupported }契约unsupported携带有界 JSON-pointer 风格路径和原因不含机密值最终在 WeKan 的 Problems → Recovery 界面展示。因此一个带 unsupported 字段的导入结果是completed-with-warnings而非静默的completed。导出侧同理当目标工具没有 WeKan 某字段的等价物时格式化器在 JSON 允许的位置输出x-wekan扩展块并把字段记入_wekan.losses消费者可忽略扩展而 WeKan 后续导入会利用它们恢复无损往返。字段级完整覆盖矩阵每种格式的权威形状、必须覆盖的字段、验证矩阵以 Format-Coverage.md 为合同它声明文档声称的映射若不存在于代码中字段清单测试必须失败。REST API 端点导入POST /api/boards/import/:source服务端路由在 server/models/boards.js#L1063-L1081。要点:source取值trello、wekan、csv、jira、kanboard、excel、deck、openproject、github、gitlab、gitea、forgejo、asana、zenkit请求体是该工具的导出 JSON直接发送或包成{ board: export }均可可选membersMapping{ 源userId: 本地userId }用于成员映射路由内部复用与 UI 相同的importBoardMeteor method保证一种鉴权、校验、净化与超时边界覆盖所有传输通道成功返回{ _id: 新看板ID }。导出GET /api/boards/:boardId/export/:format统一处理函数serveExternalExport在 models/export.js#L428-L479每个格式一条路由、共享一个鉴权处理器公共看板可匿名导出私有看板通过登录态或?authTokentoken登录 token超长会 400鉴权最终权限由exporter.canExport()按看板可见性判定。支持fields查询参数做导出选择只导出 description/labels/dates 中指定部分。除 Markdown 格式以text/markdown直接返回纯文本外其余格式均返回 JSON。用 api.py 脚本批量迁移两个方向都可通过 REST API 脚本化因此可以批量迁移所有看板。仓库根目录的 api.py 是配套的 Python CLI# 从某工具的导出文件导入 (SOURCE deck/openproject/github/gitlab/gitea/forgejo/asana/zenkit/trello/jira/…) python3 api.py importboardfrom github issues.json # → POST /api/boards/import/github (body: 该工具的导出 JSON) # 把看板导出为某工具的 JSON 形状 (FORMAT trello/jira/deck/openproject/github/gitlab/gitea/forgejo/asana/zenkit/kanboard) python3 api.py exportboardformat BOARDID deck deck-board.json # → GET /api/boards/:boardId/export/deck?authToken:tokenimportboardfrom的实现api.py#L2070-L2078读取本地 JSON 文件后以{board: 内容}包装 POST 到import/sourceexportboardformatapi.py#L2080-L2089GET 对应格式路由并把响应写入输出文件。使用前需要修改脚本顶部的 SETTINGS 段api.py#L172-L183# Username is your Wekan username or email address. # OIDC/OAuth2 etc uses email address as username. username testtest password testtest wekanurl http://localhost:4000/脚本启动时先POST users/login换取 token之后所有请求以Authorization: Bearer token头携带。注意它依赖requests库且凭据以明文写在脚本中生产环境使用时请自行管理权限。周期性同步导入之外的增量能力EXTERNAL_PARSERS中有一个刻意更小的子集SYNC_CAPABLE_SOURCES [jira, github, gitlab, gitea, forgejo]externalParsers.js#L369-L374。这些解析器会在归一化任务上输出externalIdissue key / number / iid使models/lib/listSyncReconcile.js能把再次抓取到的 issue 匹配回已创建的卡片从而支持周期性同步配合server/listSync.js与 models/listSyncCredentials.jsdeck/openproject/asana/zenkit/markdown 解析器尚未输出externalId源码注释明确说明若加入同步列表会导致每次同步都重复建卡故有意排除。相关文档Kanboard 导入导出Jira 迁移Trello 迁移CSV/TSV 导入导出Excel 导入导出从另一 WeKan 迁移所有看板【免费下载链接】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),仅供参考