# convert-wecom-ids-gk 把第三方应用获取的: - `external_userid`(服务商 external_userid) - `open_userid`(密文 open_userid) 转换为自建应用/企业内部可用的: - `external_userid`(企业 external_userid) - `userid`(明文 userid) 并把转换后的数据按 `_id` 回写到原 MongoDB collection。 安全说明: - 默认 dry-run:不写入任何 collection - 只有 `--apply --yes` 才会写入 - `--apply` 会直接更新原 collection - 回写时默认使用 **upsert**(按 `_id` 覆盖更新),因此报错后可直接重跑 --- ## 依赖与运行方式 - Node.js(建议 18+) - 依赖:`mongodb`、`axios`(项目内已安装) 在目录 `c:\code\yk\gykq\transfer` 下运行: ```powershell cd c:\code\yk\gykq\transfer node transfer.js --help ``` 脚本支持读取 `.env`、`.env.`、`env.`。当前仓库已可直接使用 `.env.gk`。 注:脚本文件为 UTF-8 编码;在 Windows PowerShell 里如果直接 `Get-Content` 看到中文乱码,请用 `Get-Content -Encoding UTF8 scripts/convert-wecom-ids-gk.js` 查看。 --- ## 企业微信接口(脚本内部调用) - 获取 `access_token`:`/cgi-bin/gettoken` - open_userid -> userid:`/cgi-bin/batch/openuserid_to_userid` - 服务商 external_userid -> 企业 external_userid:`/cgi-bin/externalcontact/from_service_external_userid` 若遇到 `errcode=60020 not allow to access from your ip`,需要把运行机器出口 IP 加到企业微信白名单后再执行。 补充:文档里很多地址写成 `.../xxx?access_token=ACCESS_TOKEN`。脚本里使用 `axios` 的 `params` 传参,最终效果仍然是拼到 URL query string(日志里会以 `fullUrl` 字段打印出来,并对 token 做脱敏)。 --- ## 典型用法 如果你已经准备好了 `.env.gk`,可直接运行: ```powershell node transfer.js --apply --yes ``` 执行 `npm run build` 后,`.env.gk` 会被内嵌进 `dist/transfer.js`,因此发布产物是单文件。若你修改了 `.env.gk`,需要重新构建一次。 ### 1) dry-run(推荐先跑) 只扫描统计,不写入任何表: ```powershell node transfer.js ` --mongoUri "mongodb://:@:27017/admin" ` --dbName corp ` --targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA ` --skipApi ``` Linux / macOS(bash/zsh): ```bash node transfer.js \ --mongoUri "mongodb://:@:27017/admin" \ --dbName corp \ --targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA \ --skipApi ``` ### 2) 实际回写原 collection PowerShell: ```powershell $env:WECOM_SECRET='你的corpsecret' node transfer.js ` --apply --yes ` --mongoUri "mongodb://:@:27017/admin" ` --dbName corp ` --collections wechat-friends ` --targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA ` --sourceAgentId 1000076 ``` Linux / macOS(bash/zsh): ```bash export WECOM_SECRET='你的corpsecret' node transfer.js \ --apply --yes \ --mongoUri "mongodb://:@:27017/admin" \ --dbName corp \ --collections wechat-friends \ --targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA \ --sourceAgentId 1000076 ``` ### 3) 只跑指定 collection(便于测试) ```bash node transfer.js \ --mongoUri "mongodb://:@:27017/admin" \ --dbName corp \ --collections wechat-friends,corp-member \ --targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA \ --skipApi ``` ### 4) wechat 表数据太多:只跑一部分 只跑 `wechat-friends` 前 1000 条(默认按 `_id` 升序,便于稳定分段): ```bash node transfer.js \ --apply --yes \ --mongoUri "mongodb://:@:27017/admin" \ --dbName corp \ --collections wechat-friends \ --targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA \ --limitDocs 1000 ``` 分段跑下一段(跳过前 1000 条,再跑 1000 条): ```bash node transfer.js \ --apply --yes \ --mongoUri "mongodb://:@:27017/admin" \ --dbName corp \ --collections wechat-friends \ --targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA \ --skipDocs 1000 \ --limitDocs 1000 ``` --- ## 可见范围/成员列表排查(推荐) 当你发现 `open_userid -> userid` 大面积 `invalid_open_userid_list` 时,最常见原因不是“参数写错”,而是:当前 `access_token` 所代表的应用,在通讯录侧可见的成员范围很小(可见范围/权限交集)。 脚本提供 `--dumpScope`,会调用: - `agent/get`:查看应用的 allow_user/allow_party/allow_tag - `user/list_id`(文档 path=96067):拉取“当前 token 可见的成员 userid 列表” 只做排查、不跑转换: ```bash node transfer.js \ --preset gk-suite \ --dumpScope --scopeOnly ``` 检查某些 userid 是否在可见范围内: ```bash node transfer.js \ --preset gk-suite \ --dumpScope --scopeOnly \ --checkUserIds 23090101,23090102 ``` ## 参数说明 ### MongoDB(任选一种方式) - `--mongoUri ""`(或别名 `--mongoUrl ""`) - 或拆分参数(也支持对应环境变量兜底): - `--mongoHost`(或 `MONGO_HOST` / `CONFIG_DB_HOST`) - `--mongoPort`(或 `MONGO_PORT` / `CONFIG_DB_PORT`,默认 `27017`) - `--mongoUser`(或 `MONGO_USER` / `CONFIG_DB_USERNAME`) - `--mongoPass`(或 `MONGO_PASS` / `CONFIG_DB_PASSWORD`) - `--mongoAuthDb`(或 `MONGO_AUTH_DB`,默认 `admin`) ### 必填/常用 - `--dbName `:只处理单个数据库(例如 `corp`;注意这是“数据库名”,不是 collection 名) - `--targetCorpId `:目标机构 corpId - `--collections a,b,c`:只处理指定 collection(可选;例如 `corp-member,wechat-friends`;不传则扫描该库所有 collection) ### 指定 ID 转换(不跑数据库) 如果你只想把指定的 `open_userid`/`external_userid` 转换出来用于验证(不扫描 Mongo,也不写库),可以用: ```bash node transfer.js \ --tokenMode corpSecret \ --tokenCorpId \ --secret "" \ --sourceAgentId 1000076 \ --openUserIds woXXXX,woYYYY \ --externalUserIds wmXXXX,wmYYYY ``` tokenMode=suite 也支持直接提供 token 所需参数(无需 Mongo): ```bash node transfer.js \ --tokenMode suite \ --suiteAccessToken "" \ --permanentCode "" \ --targetCorpId \ --sourceAgentId 1000076 \ --openUserIds woXXXX ``` ### 只跑部分数据(单表分段) - `--limitDocs `:每个 collection 最多处理 n 条 - `--skipDocs `:每个 collection 跳过前 n 条 - `--noSortById`:不按 `_id` 排序(默认按 `_id` 升序,分段更稳定) ### 企微转换相关(`--skipApi` 关闭时需要) - access_token 获取方式(2 选 1): - `--tokenMode suite`(推荐):使用第三方应用的 suite_token + 目标机构 permanent_code 获取该机构的 corp access_token - 依赖:MongoDB 的 `corp.weComToken(type=suiteToken).suite_token`、以及 `corp.corp.permanent_code` - 无需传 `--tokenCorpId/--secret` - `--tokenMode corpSecret`(默认):使用 `gettoken`(仅适用于自建应用/企业内部应用场景) - `--tokenCorpId `:用于 `gettoken` 的 corpId - `--secret `:corpsecret - `--secretEnv `:从环境变量读取 corpsecret(推荐) - `--sourceAgentId `:第三方应用的 `source_agentid` - 不传时脚本会尝试从 `corp` collection 的 `auth_info.agent[0].agentid` 推断(推断不到会报错) ### 写入与安全开关 - `--apply`:执行写入(不传则 dry-run) - `--yes`:`--apply` 时必须显式确认(避免误操作) - `--insertOnly`:已不再支持;当前 apply 模式固定按 `_id` upsert 回写原表 - `--skipApi`:只扫描统计,不调企业微信接口(仅 dry-run 可用) ### 扫描/性能参数(可选) - `--corpIdFields corpId,corpid,corp_id`:用于判断该 collection 是否属于目标 corp - `--batchDocs 200`:Mongo 游标批大小 - `--batchOpenIds 100`:`open_userid` 批量转换每次请求数量 - `--concurrencyExternal 8`:external_userid 单条转换并发数 - `--aggressive`:更激进地识别字段/字符串(可能增加 API 调用量) ### 企微接口日志 - 默认会输出每一次企微接口调用的完整响应(成功/失败都会输出) - 如需关闭:`--noWecomDebug` --- ## 预设(减少命令行参数) 脚本内置了少量预设(不包含任何明文账号/密码/secret),用法: ```bash node transfer.js --listPresets node transfer.js --preset gk-suite --collections wechat-friends --limitDocs 200 --apply --yes ``` 建议在 Linux 上把敏感信息放环境变量里: ```bash export GK_MONGO_URI='mongodb://:@:27017/admin' export GK_CORP_SECRET='你的corpsecret' # 仅 tokenMode=corpSecret 时需要 ```