corp-transfer/docs/OPERATION_GUIDE.md
2026-04-03 10:26:45 +08:00

325 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 企业微信 ID 转换与数据迁移操作指南
本文档介绍如何编译打包、运行企业微信数据迁移任务(`transfer.js` 等脚本),以及执行完毕后的数据验证方式。
## 1. 整体流程
迁移主要包含三个步骤:
1. **构建打包(可选但推荐)**:打包脚本,将配置信息直接注入到单产物中,方便分发到不同机器(如接收端机器),不依赖外部 `node_modules`
2. **测试评估Dry-run**:获取 API 数据,扫描相关表进行日志打印统计,但不做实际写入。
3. **真实修改Apply**:将转换后的用户 ID 写回原有的 MongoDB 集合中。
## 2. 构建与打包准备 (Build)
如果需要在生产机上直接运行而不需要安装库依赖,可使用已配置好的 `esbuild` 进行单文件打包。
1. 进入项目目录并安装依赖(如尚未安装):
```bash
cd c:\code\yk\gykq\transfer
npm install
```
2. 准备环境变量文件(如 `.env.gk`)。打包脚本会自动将其读取并注入到构建结果中。
3. 执行打包命令:
```bash
npm run build
```
*产物将生成在 `dist/` 目录下(如 `dist/transfer.js`、`dist/corpid.js`),你可以把这个单文件发给对方,对端机器只需安装了 Node.js 就能直接跑。*
## 3. 运行指南 (Run)
以迁移企业微信 `open_userid` 及 `external_userid` 为例,核心使用 `transfer.js`。
如果已经使用内置 env例如 `.env.gk`,或打包后的 `dist/transfer.js`),通常不需要再手动传 `--mongoUri`、`--dbName`、`--targetCorpId`、`--sourceAgentId` 这类基础参数;脚本会直接读取内置配置。只有临时覆盖某个值时,才需要显式加参数。
### 3.1 步骤一预演空跑Dry-Run
强烈建议在实际写入生产数据前,执行一次空测试。该模式仅读取数据和调用查询 API不会修写数据库。
**内置 env 直接执行:**
```bash
node transfer.js --skipApi
```
**或者执行打包后的产物:**
```bash
node dist/transfer.js --skipApi
```
*注:可通过日志查看预计会有多少条数据被改变、读取的数据形态是否符合预期。*
### 3.2 步骤二实际执行转换并写入Apply
在确认空跑日志报错和逻辑都没有问题后,带上 `--apply --yes` 标志位做真实写入。
```bash
# 需保证服务器外网出口 IP 已经在企业微信开发平台的白名单中
node dist/transfer.js --apply --yes
```
如果只是想临时覆盖某个默认值,可以继续追加参数,例如:
```bash
node dist/transfer.js --apply --yes --collections wechat-friends
```
> **提示**:脚本设计为使用 MongoDB 的 `upsert` 或根据 `_id` 覆盖写入。如果在中途网络断开或因接口限制(如并发 / token 过期)导致报错退出,直接原样重启脚本即可,不会造成脏数据重复写入。
## 4. 如何验证 (Verify)
转换脚本执行完毕后,可通过以下三种方式进行验证:
### 4.1 日志验证
检查终端输出的最终 Summary 日志。
### 4.2 数据库数据验证
使用 MongoDB 客户端(如 MongoDB Compass、Navicat 或 mosh连接数据库
1. 打开被修改的数据表(例如 `wechat-friends` 或具体对应业务的 collection
2. 核对被转换的字段(如 `open_userid` 是否已变更为了 `userid`,服务商的 `external_userid` 是否变更为企业的 `external_userid`)。
3. 检查原数据文档的其他字段是否存在丢失。得益于 `_id` 覆盖更新机制,确保只变更多出的或改写的字段。
### 4.3 业务功能验证
登录实际业务系统或者企业微信应用端:
1. **身份鉴权测试**:尝试调用依赖当前企微系统的登录模块或回调模块,验证基于新 `userid` 是否能正常查到员工权限和身份。
2. **外部联系人服务测试**:给发生转换的用户发送测试企微消息或打标签,确认企业微信后台或第三方系统能由转换后的 `external_userid` 成功匹配到对应客户,不报“不是企业客户”之类的错误。
---
## 5. 单独替换 corpId
### 5.1 默认运行方式
`corpid.js` 已经会默认走 `.env.gk`,所以如果环境变量已准备好,直接跑就行:
```powershell
node corpid.js
```
实际写入:
```powershell
node corpid.js --apply --yes
```
### 5.2 常见定向运行
只处理某个数据库:
```powershell
node corpid.js --dbName corp
```
只处理指定 collection
```powershell
node corpid.js --dbName corp --collections corp-member,wechat-friends
```
显式指定旧 corpId 和新 corpId
```powershell
node corpid.js --sourceCorpId <旧corpId> --newCorpId <新corpId>
```
补充说明:
- 默认旧 corpId 从 `TARGET_CORP_ID` 取
- 默认新 corpId 从 `TOKEN_CORP_ID` 取
- `corp` collection 里会额外维护 `internal_agentid`
### 5.3 重复字段和重复执行
这个脚本的“重复”有两层含义:
- 同一文档里 `corpId`、`corpid`、`corp_id` 可能同时出现,脚本会全部替换
- 你重复执行同一命令dry-run 不会落库apply 模式按原 `_id` 回写,重复跑不会造重复文档
---
## 6. 数据库迁移
### 6.1 运行顺序
迁移必须先起 receiver再跑 sender。
1. 在目标环境启动 receiver
2. 在源环境启动 sender把数据推过去
### 6.2 启动 receiver
receiver 默认监听 8081默认参数已经写在脚本里。内网/目标机一般直接运行即可:
```powershell
node migrate-corpid-receiver.js
```
如果你要改端口或目标 MongoDB可以覆盖参数
```powershell
node migrate-corpid-receiver.js --port 8081 --targetMongoUri "mongodb://<user>:<pass>@<host>:27017/admin" --token "<token>"
```
### 6.3 启动 sender
sender 默认也带有内置配置,常规情况下直接运行即可:
```powershell
node migrate-corpid-sender.js
```
如果要覆盖源库、receiver 地址或 token
```powershell
node migrate-corpid-sender.js --sourceMongoUri "mongodb://<user>:<pass>@<host>:27017/admin" --receiverUrl "http://127.0.0.1:8081" --token "<token>" --corpId "<corpId>"
```
### 6.4 迁移范围和重复执行
sender 会扫描源库的业务数据库和 collection默认过滤 `admin`、`config`、`local` 以外的范围。它会:
- 先把 collection 的索引发给 receiver
- 再按批发送文档
- receiver 用 `_id` 做 upsert 写入
迁移脚本适合重跑:
- 索引有去重缓存
- 文档按 `_id` 覆盖写入
- 中途失败后可以重新执行 sender
---
## 7. 怎么验证
### 7.1 先看日志
每个脚本结束时都应该看最后的 summary
- `transfer.js`看扫描数、命中文档数、写入数、invalid 数
- `corpid.js`看扫描的数据库数、collection 数、corpId 替换次数、`internal_agentid` 更新次数
- `migrate-corpid-sender.js`:看迁移完成 summary确认 migratedDocs 数量正常
### 7.2 mongosh 验证命令
下面给的是可以直接复制的 `mongosh` 命令。把 `<MONGO_URI>`、`<DB_NAME>`、`<COLLECTION>`、`<OLD_CORP_ID>`、`<NEW_CORP_ID>` 替换成你的真实值即可。
#### 验证 corpId 替换
先看旧 corpId 还剩多少:
```bash
mongosh "<MONGO_URI>" --quiet --eval '
const dbx = db.getSiblingDB("<DB_NAME>");
const col = dbx.getCollection("<COLLECTION>");
const oldCorpId = "<OLD_CORP_ID>";
print("old corpId count:", col.countDocuments({ $or: [ { corpId: oldCorpId }, { corpid: oldCorpId }, { corp_id: oldCorpId } ] }));
'
```
再看新 corpId 命中多少:
```bash
mongosh "<MONGO_URI>" --quiet --eval '
const dbx = db.getSiblingDB("<DB_NAME>");
const col = dbx.getCollection("<COLLECTION>");
const newCorpId = "<NEW_CORP_ID>";
print("new corpId count:", col.countDocuments({ $or: [ { corpId: newCorpId }, { corpid: newCorpId }, { corp_id: newCorpId } ] }));
printjson(col.findOne({ $or: [ { corpId: newCorpId }, { corpid: newCorpId }, { corp_id: newCorpId } ] }, { projection: { corpId: 1, corpid: 1, corp_id: 1, internal_agentid: 1 } }));
'
```
如果你想检查整个数据库里是否还有旧 corpId可以在 mongosh 里循环所有 collection
```bash
mongosh "<MONGO_URI>" --quiet --eval '
const dbx = db.getSiblingDB("<DB_NAME>");
const oldCorpId = "<OLD_CORP_ID>";
for (const name of dbx.getCollectionNames()) {
const col = dbx.getCollection(name);
const count = col.countDocuments({ $or: [ { corpId: oldCorpId }, { corpid: oldCorpId }, { corp_id: oldCorpId } ] });
if (count) print(name + ": " + count);
}
'
```
#### 验证企微 ID 转换
如果你知道要查的 collection可以先抽样看一条文档
```bash
mongosh "<MONGO_URI>" --quiet --eval '
const dbx = db.getSiblingDB("<DB_NAME>");
const col = dbx.getCollection("<COLLECTION>");
printjson(col.findOne({}, { projection: { open_userid: 1, userid: 1, external_userid: 1, corpId: 1, corpid: 1, corp_id: 1 } }));
'
```
如果你想确认旧格式值是否还残留,可以针对你已知字段做计数。比如常见的 open_userid / external_userid 形式:
```bash
mongosh "<MONGO_URI>" --quiet --eval '
const dbx = db.getSiblingDB("<DB_NAME>");
const col = dbx.getCollection("<COLLECTION>");
print("open_userid-like count:", col.countDocuments({ open_userid: /^wo/ }));
print("external_userid-like count:", col.countDocuments({ external_userid: /^wm/ }));
'
```
如果你的数据字段不是顶层字段,而是嵌套在对象里,那么更实用的办法是:
1. 先从日志里拿到处理过的 collection 名
2. 用 `findOne` 抽样看字段
3. 再针对具体字段写 `countDocuments` 或 `find` 验证
#### 验证迁移前后数据量
迁移时最直接的验证方式,是在源库和目标库分别看同一个 collection 的数量是否一致。
源库:
```bash
mongosh "<SOURCE_MONGO_URI>" --quiet --eval '
const dbx = db.getSiblingDB("<DB_NAME>");
print("source count:", dbx.getCollection("<COLLECTION>").estimatedDocumentCount());
'
```
目标库:
```bash
mongosh "<TARGET_MONGO_URI>" --quiet --eval '
const dbx = db.getSiblingDB("<DB_NAME>");
print("target count:", dbx.getCollection("<COLLECTION>").estimatedDocumentCount());
'
```
如果要更严谨一点,可以再比对同一批 `_id` 是否能查到:
```bash
mongosh "<TARGET_MONGO_URI>" --quiet --eval '
const dbx = db.getSiblingDB("<DB_NAME>");
const col = dbx.getCollection("<COLLECTION>");
printjson(col.find({}, { projection: { _id: 1 } }).limit(5).toArray());
'
```
### 7.3 验证 receiver 是否在工作
这个不是 mongosh 命令,但很适合一起检查:
```powershell
Invoke-RestMethod http://127.0.0.1:8081/health
```
如果返回 `success: true`,说明 receiver 已经在监听。
---
## 8. 推荐执行顺序
最稳妥的顺序是:
1. 先跑 `transfer.js --skipApi` 做 dry-run
2. 用 `mongosh` 抽样看数据形态
3. 再跑 `transfer.js --apply --yes`
4. 跑 `corpid.js` 做 corpId 替换
5. 最后做 sender / receiver 迁移
如果只是要迁移其中一类数据,就只跑对应脚本,不必全跑。