2026-03-13 17:07:27 +08:00
|
|
|
|
# convert-wecom-ids-gk
|
|
|
|
|
|
|
|
|
|
|
|
把第三方应用获取的:
|
|
|
|
|
|
|
|
|
|
|
|
- `external_userid`(服务商 external_userid)
|
|
|
|
|
|
- `open_userid`(密文 open_userid)
|
|
|
|
|
|
|
|
|
|
|
|
转换为自建应用/企业内部可用的:
|
|
|
|
|
|
|
|
|
|
|
|
- `external_userid`(企业 external_userid)
|
|
|
|
|
|
- `userid`(明文 userid)
|
|
|
|
|
|
|
2026-04-03 10:07:09 +08:00
|
|
|
|
并把转换后的数据按 `_id` 回写到原 MongoDB collection。
|
2026-03-13 17:07:27 +08:00
|
|
|
|
|
|
|
|
|
|
安全说明:
|
|
|
|
|
|
|
2026-04-03 10:07:09 +08:00
|
|
|
|
- 默认 dry-run:不写入任何 collection
|
2026-03-13 17:07:27 +08:00
|
|
|
|
- 只有 `--apply --yes` 才会写入
|
2026-04-03 10:07:09 +08:00
|
|
|
|
- `--apply` 会直接更新原 collection
|
|
|
|
|
|
- 回写时默认使用 **upsert**(按 `_id` 覆盖更新),因此报错后可直接重跑
|
2026-03-13 17:07:27 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 依赖与运行方式
|
|
|
|
|
|
|
|
|
|
|
|
- Node.js(建议 18+)
|
|
|
|
|
|
- 依赖:`mongodb`、`axios`(项目内已安装)
|
|
|
|
|
|
|
2026-04-03 10:07:09 +08:00
|
|
|
|
在目录 `c:\code\yk\gykq\transfer` 下运行:
|
2026-03-13 17:07:27 +08:00
|
|
|
|
|
|
|
|
|
|
```powershell
|
2026-04-03 10:07:09 +08:00
|
|
|
|
cd c:\code\yk\gykq\transfer
|
|
|
|
|
|
node transfer.js --help
|
2026-03-13 17:07:27 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-03 10:07:09 +08:00
|
|
|
|
脚本支持读取 `.env`、`.env.<NODE_ENV>`、`env.<NODE_ENV>`。当前仓库已可直接使用 `.env.gk`。
|
2026-03-13 17:07:27 +08:00
|
|
|
|
|
|
|
|
|
|
注:脚本文件为 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 做脱敏)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 典型用法
|
|
|
|
|
|
|
2026-04-03 10:07:09 +08:00
|
|
|
|
如果你已经准备好了 `.env.gk`,可直接运行:
|
|
|
|
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
|
|
node transfer.js --apply --yes
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
执行 `npm run build` 后,`.env.gk` 会被内嵌进 `dist/transfer.js`,因此发布产物是单文件。若你修改了 `.env.gk`,需要重新构建一次。
|
|
|
|
|
|
|
2026-03-13 17:07:27 +08:00
|
|
|
|
### 1) dry-run(推荐先跑)
|
|
|
|
|
|
|
2026-04-03 10:07:09 +08:00
|
|
|
|
只扫描统计,不写入任何表:
|
2026-03-13 17:07:27 +08:00
|
|
|
|
|
|
|
|
|
|
```powershell
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js `
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--mongoUri "mongodb://<user>:<pass>@<host>:27017/admin" `
|
|
|
|
|
|
--dbName corp `
|
|
|
|
|
|
--targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA `
|
|
|
|
|
|
--skipApi
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Linux / macOS(bash/zsh):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js \
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--mongoUri "mongodb://<user>:<pass>@<host>:27017/admin" \
|
|
|
|
|
|
--dbName corp \
|
|
|
|
|
|
--targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA \
|
|
|
|
|
|
--skipApi
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-03 10:07:09 +08:00
|
|
|
|
### 2) 实际回写原 collection
|
2026-03-13 17:07:27 +08:00
|
|
|
|
|
|
|
|
|
|
PowerShell:
|
|
|
|
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
|
|
$env:WECOM_SECRET='你的corpsecret'
|
|
|
|
|
|
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js `
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--apply --yes `
|
|
|
|
|
|
--mongoUri "mongodb://<user>:<pass>@<host>:27017/admin" `
|
|
|
|
|
|
--dbName corp `
|
|
|
|
|
|
--collections wechat-friends `
|
|
|
|
|
|
--targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA `
|
|
|
|
|
|
--sourceAgentId 1000076
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Linux / macOS(bash/zsh):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
export WECOM_SECRET='你的corpsecret'
|
|
|
|
|
|
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js \
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--apply --yes \
|
|
|
|
|
|
--mongoUri "mongodb://<user>:<pass>@<host>:27017/admin" \
|
|
|
|
|
|
--dbName corp \
|
|
|
|
|
|
--collections wechat-friends \
|
|
|
|
|
|
--targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA \
|
|
|
|
|
|
--sourceAgentId 1000076
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3) 只跑指定 collection(便于测试)
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js \
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--mongoUri "mongodb://<user>:<pass>@<host>:27017/admin" \
|
|
|
|
|
|
--dbName corp \
|
|
|
|
|
|
--collections wechat-friends,corp-member \
|
|
|
|
|
|
--targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA \
|
|
|
|
|
|
--skipApi
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4) wechat 表数据太多:只跑一部分
|
|
|
|
|
|
|
|
|
|
|
|
只跑 `wechat-friends` 前 1000 条(默认按 `_id` 升序,便于稳定分段):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js \
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--apply --yes \
|
|
|
|
|
|
--mongoUri "mongodb://<user>:<pass>@<host>:27017/admin" \
|
|
|
|
|
|
--dbName corp \
|
|
|
|
|
|
--collections wechat-friends \
|
|
|
|
|
|
--targetCorpId wpLgjyawAAeRkCPQMp9-z5q-xEzK64nA \
|
|
|
|
|
|
--limitDocs 1000
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
分段跑下一段(跳过前 1000 条,再跑 1000 条):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js \
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--apply --yes \
|
|
|
|
|
|
--mongoUri "mongodb://<user>:<pass>@<host>: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
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js \
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--preset gk-suite \
|
|
|
|
|
|
--dumpScope --scopeOnly
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
检查某些 userid 是否在可见范围内:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js \
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--preset gk-suite \
|
|
|
|
|
|
--dumpScope --scopeOnly \
|
|
|
|
|
|
--checkUserIds 23090101,23090102
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 参数说明
|
|
|
|
|
|
|
|
|
|
|
|
### MongoDB(任选一种方式)
|
|
|
|
|
|
|
|
|
|
|
|
- `--mongoUri "<mongodb uri>"`(或别名 `--mongoUrl "<mongodb uri>"`)
|
|
|
|
|
|
- 或拆分参数(也支持对应环境变量兜底):
|
|
|
|
|
|
- `--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 <db>`:只处理单个数据库(例如 `corp`;注意这是“数据库名”,不是 collection 名)
|
|
|
|
|
|
- `--targetCorpId <corpId>`:目标机构 corpId
|
|
|
|
|
|
- `--collections a,b,c`:只处理指定 collection(可选;例如 `corp-member,wechat-friends`;不传则扫描该库所有 collection)
|
|
|
|
|
|
|
|
|
|
|
|
### 指定 ID 转换(不跑数据库)
|
|
|
|
|
|
|
2026-04-03 10:07:09 +08:00
|
|
|
|
如果你只想把指定的 `open_userid`/`external_userid` 转换出来用于验证(不扫描 Mongo,也不写库),可以用:
|
2026-03-13 17:07:27 +08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js \
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--tokenMode corpSecret \
|
|
|
|
|
|
--tokenCorpId <corpId> \
|
|
|
|
|
|
--secret "<corpsecret>" \
|
|
|
|
|
|
--sourceAgentId 1000076 \
|
|
|
|
|
|
--openUserIds woXXXX,woYYYY \
|
|
|
|
|
|
--externalUserIds wmXXXX,wmYYYY
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
tokenMode=suite 也支持直接提供 token 所需参数(无需 Mongo):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js \
|
2026-03-13 17:07:27 +08:00
|
|
|
|
--tokenMode suite \
|
|
|
|
|
|
--suiteAccessToken "<suite_access_token>" \
|
|
|
|
|
|
--permanentCode "<permanent_code>" \
|
|
|
|
|
|
--targetCorpId <auth_corpid> \
|
|
|
|
|
|
--sourceAgentId 1000076 \
|
|
|
|
|
|
--openUserIds woXXXX
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 只跑部分数据(单表分段)
|
|
|
|
|
|
|
|
|
|
|
|
- `--limitDocs <n>`:每个 collection 最多处理 n 条
|
|
|
|
|
|
- `--skipDocs <n>`:每个 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 <corpId>`:用于 `gettoken` 的 corpId
|
|
|
|
|
|
- `--secret <corpsecret>`:corpsecret
|
|
|
|
|
|
- `--secretEnv <ENV_NAME>`:从环境变量读取 corpsecret(推荐)
|
|
|
|
|
|
- `--sourceAgentId <agentid>`:第三方应用的 `source_agentid`
|
|
|
|
|
|
- 不传时脚本会尝试从 `corp` collection 的 `auth_info.agent[0].agentid` 推断(推断不到会报错)
|
|
|
|
|
|
|
|
|
|
|
|
### 写入与安全开关
|
|
|
|
|
|
|
|
|
|
|
|
- `--apply`:执行写入(不传则 dry-run)
|
|
|
|
|
|
- `--yes`:`--apply` 时必须显式确认(避免误操作)
|
2026-04-03 10:07:09 +08:00
|
|
|
|
- `--insertOnly`:已不再支持;当前 apply 模式固定按 `_id` upsert 回写原表
|
2026-03-13 17:07:27 +08:00
|
|
|
|
- `--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
|
2026-04-03 10:07:09 +08:00
|
|
|
|
node transfer.js --listPresets
|
|
|
|
|
|
node transfer.js --preset gk-suite --collections wechat-friends --limitDocs 200 --apply --yes
|
2026-03-13 17:07:27 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
建议在 Linux 上把敏感信息放环境变量里:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
export GK_MONGO_URI='mongodb://<user>:<pass>@<host>:27017/admin'
|
|
|
|
|
|
export GK_CORP_SECRET='你的corpsecret' # 仅 tokenMode=corpSecret 时需要
|
|
|
|
|
|
```
|