feat(t17): 数据同步——只读拉取群组/群成员/群消息到本地表,按租户查看

主体(T17 数据同步,/admin/sync 三个独立按钮):
- 同步群组:get_appid_group_list 全量 Next 分页 + 逐群 get_group_info → group_mapping
- 同步群成员(→用户):遍历该租户群取 MemberList → user_mapping(腾讯无全量用户API,靠群成员反推)
- 同步群消息:遍历该租户群 getGroupMsg + IsFinished 滚动全量 → im_message(source=SYNC)
- DB: group_mapping 加 name/owner_account/member_count/last_synced_at;user_mapping 加 nick/last_synced_at
  (init.sql 建表 + ADD COLUMN IF NOT EXISTS 老库升级补丁,幂等)
- TencentImClient: 新增 getAppidGroupList(limit,next[,sdkAppId,secretKey])
- 已知限制:C2C单聊无全量会话API;超大群成员需换 get_group_member_info 分页

附带收尾此前未提交的改动:
- 回调字段名修正(FromAccount→From_Account 等腾讯标准字段) + pickMsgRandom/Time/Type 兼容字段差异
- 租户识别重构:前缀经 TenantService.getByPrefixCode 反查 tenantId(主键雪花化与前缀解耦)
- FreeMarker java.time ?string 坑修复(usage/queue Controller 预格式化) + 消息记录"全部"状态修复
- 新增项目 CLAUDE.md + .claude/memory 基建(gitignore 含密钥记忆,不进 git)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
连龙刚
2026-07-10 08:40:46 +08:00
co-authored by Claude
parent b679d47adb
commit fdbb7724fa
19 changed files with 1120 additions and 52 deletions
@@ -341,6 +341,36 @@ public class TencentImClient {
return callApiAs("group_open_http_svc/get_appid_groups", Jsons.stringify(body), srcSdkAppId, srcSecretKey);
}
/**
* 全量分页列出 SDKAppId 下所有群(T17 数据同步,主应用密钥)
* <p>
* 命令字 group_open_http_svc/get_appid_group_list,支持 Next 游标分页:
* 首次 Next 传空串,循环把上次返回的 Next 再传入,直到返回 Next 为空。
* 与 {@link #getAppidGroups} 的区别:标准接口、支持分页、GroupIdList 元素为纯字符串。
*
* @param limit 每页数量(建议 100
* @param next 分页游标,首次传空串
* @return 腾讯响应原始 JSON(含 Next/Total/GroupIdList),由调用方解析
*/
public String getAppidGroupList(int limit, String next) {
Map<String, Object> body = new HashMap<>();
body.put("Limit", limit);
body.put("Next", next == null ? "" : next);
return callApi("group_open_http_svc/get_appid_group_list", Jsons.stringify(body));
}
/**
* 全量分页列出 SDKAppId 下所有群(T17 数据同步,指定应用密钥,用于老应用)
*
* @see #getAppidGroupList(int, String)
*/
public String getAppidGroupList(int limit, String next, long sdkAppId, String secretKey) {
Map<String, Object> body = new HashMap<>();
body.put("Limit", limit);
body.put("Next", next == null ? "" : next);
return callApiAs("group_open_http_svc/get_appid_group_list", Jsons.stringify(body), sdkAppId, secretKey);
}
/**
* 查群资料 + 成员列表(老应用拉群详情,用老密钥)
* <p>