feat(t14-s345): 群迁移 + 消息迁移 + 关系链迁移

T14 S3/S4/S5 阶段:
- S3 群迁移:getAppidGroups/getGroupInfo(老密钥拉)+ createGroup/addGroupMember(主密钥导入)+ migrateGroups
- S4 消息迁移:importMsg/importGroupMsg + migrateMessages(会话清单+断点续传,v1 最近7天/100条)
- S5 关系链:importFriend/friendGetList(sns 系列)+ migrateRoster
- 后台 /admin/migrate/{groups,messages,roster} 触发 + migrate.ftl 三表单

S1/S2 完整联调通过;S3/S4/S5 代码完成+通路验证(腾讯返业务响应),
核心导入待真实老应用(10003 群组/60008 消息/60002 关系链)。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
yixiong
2026-07-09 12:12:26 +08:00
co-authored by Claude
parent 7b1385e160
commit 460a31b52f
5 changed files with 603 additions and 1 deletions
+64
View File
@@ -3,6 +3,9 @@
<#if msg?has_content>
<div class="alert">
<#if msg=='done'>✅ 迁移完成(成功导入 ${imported!'0'} 用户,详见任务记录)
<#elseif msg=='gdone'>✅ 群迁移完成(迁移 ${migrated!'0'} 群,详见任务记录)
<#elseif msg=='mdone'>✅ 消息迁移完成(导入 ${imported!'0'} 条,详见任务记录)
<#elseif msg=='rdone'>✅ 关系链迁移完成(导入 ${imported!'0'} 用户好友,详见任务记录)
<#elseif msg=='empty'>❌ 用户清单为空(每行一个 UserID,# 开头为注释)
<#elseif msg=='noapp'>❌ 请选择老应用
<#else>${msg}
@@ -31,6 +34,67 @@
</form>
</div>
<div class="stat-card" style="margin-bottom:20px">
<div class="modal-title">群迁移</div>
<p class="muted">从老应用拉取所有群(get_appid_groups)→ 逐群拉成员 → 加前缀建群(主应用)→ 导入成员 → 写 group_mapping。群清单由腾讯 API 拉取(无需文件);成员账号需已迁移(请先做用户迁移)。</p>
<form method="post" action="${basePath}/admin/migrate/groups" style="margin-top:14px">
<div class="modal-body">
<div class="form-group">
<label>老应用</label>
<select name="sourceAppId" required>
<option value="">请选择老应用</option>
<#list apps as a>
<option value="${a.id!}">${a.appName!}SDK ${a.sdkAppId!} → 租户 ${a.tenantId!}</option>
</#list>
</select>
</div>
</div>
<button type="submit" class="btn btn-primary">开始群迁移</button>
</form>
</div>
<div class="stat-card" style="margin-bottom:20px">
<div class="modal-title">单聊消息迁移</div>
<p class="muted">上传会话清单(每行 from,to 逗号或空格分隔,# 注释)或粘贴,选老应用后触发。getRoamMsg(老密钥)拉 → 加前缀 → importMsg(主密钥,不计未读)。v1 每会话拉最近 100 条,断点续传按会话索引。用户需已迁移。</p>
<form method="post" action="${basePath}/admin/migrate/messages" enctype="multipart/form-data" style="margin-top:14px">
<div class="modal-body">
<div class="form-group">
<label>老应用</label>
<select name="sourceAppId" required>
<option value="">请选择老应用</option>
<#list apps as a>
<option value="${a.id!}">${a.appName!}SDK ${a.sdkAppId!} → 租户 ${a.tenantId!}</option>
</#list>
</select>
</div>
<div class="form-group"><label>上传会话清单(每行 from,to</label><input type="file" name="file" style="padding:7px;border:1px solid #ddd;border-radius:6px;width:100%"></div>
<div class="form-group"><label>或粘贴会话列表(每行 from,to</label><textarea name="convText" rows="5" style="width:100%;padding:9px;border:1px solid #ddd;border-radius:6px;font-family:monospace;font-size:13px" placeholder="user_a,user_b&#10;user_c,user_d"></textarea></div>
</div>
<button type="submit" class="btn btn-primary">开始消息迁移</button>
</form>
</div>
<div class="stat-card" style="margin-bottom:20px">
<div class="modal-title">关系链迁移</div>
<p class="muted">上传用户清单(每行一个 UserID)或粘贴,选老应用后触发。逐用户 friendGetList(老密钥)拉好友 → 加前缀 → importFriend(主密钥)。用户与好友账号需已迁移。</p>
<form method="post" action="${basePath}/admin/migrate/roster" enctype="multipart/form-data" style="margin-top:14px">
<div class="modal-body">
<div class="form-group">
<label>老应用</label>
<select name="sourceAppId" required>
<option value="">请选择老应用</option>
<#list apps as a>
<option value="${a.id!}">${a.appName!}SDK ${a.sdkAppId!} → 租户 ${a.tenantId!}</option>
</#list>
</select>
</div>
<div class="form-group"><label>上传用户清单(每行一个 UserID</label><input type="file" name="file" style="padding:7px;border:1px solid #ddd;border-radius:6px;width:100%"></div>
<div class="form-group"><label>或粘贴用户 ID 列表</label><textarea name="userText" rows="4" style="width:100%;padding:9px;border:1px solid #ddd;border-radius:6px;font-family:monospace;font-size:13px"></textarea></div>
</div>
<button type="submit" class="btn btn-primary">开始关系链迁移</button>
</form>
</div>
<h3 style="margin:20px 0 10px;font-size:15px;color:#2c3e50">迁移任务记录</h3>
<#if tasks?has_content>
<table class="data-table">