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
+82
View File
@@ -0,0 +1,82 @@
<!-- Last updated: 2026-07-09 | Commit: b679d47 -->
# tencent-im-util
腾讯 IM 回调分发工具:接收腾讯 IM 的消息回调,落库后按租户/源应用分发。配套管理后台、用量统计、关系链/消息迁移等。
> 本文件为**项目级技术上下文**(行为准则见全局 `~/.claude/CLAUDE.md`)。密钥一律不写在此处,配置项以 `app.yml` 为准。
## 技术栈
- **框架**Solon`solon-parent`,非 Spring),Java 21`solon-web`
- **鉴权**Sa-Token`sa-token-solon-plugin`
- **持久层**MyBatis-Plus`mybatis-plus-extension-solon-plugin`+ PostgreSQL + HikariCP
- **缓存/队列**RedisJedis+ Caffeine 本地缓存
- **视图**FreeMarker`solon-view-freemarker`
- **日志**logback`solon-logging-logback-jakarta`
- **腾讯 IM**`tls-sig-api-v2`UserSig 签名)+ 自封装 `TencentImClient`REST API
- **定时**`solon-scheduling-simple`**工作线程**:自实现 `worker/DispatchWorker`
## 构建与运行
```bash
# 打包(离线模式,跳过测试)
mvn -o package -DskipTests
# 运行
java -jar target/tencent-im-util.jar
```
> ⚠️ **不要用 `mvn solon:solon`**(本项目下不可用,会失败)。详见 `.claude/memory/run-app-jar.md`。
> 端口 `8092`contextPath `/imutil`(即所有路由前缀 `/imutil`)。控制台中文需 UTF-8 终端(已在 `logback.xml` 修正 charset)。
## 目录结构(`src/main/java/com/imutil`
| 包 | 职责 |
|---|---|
| `controller` | `AdminController`(`/admin/*` 后台)、`CallbackController`(`/callback/im` 回调入口)、`HealthController``SigController`(UserSig 下发) |
| `service` + `service/impl` | 业务逻辑(接口 + 实现) |
| `entity` | MyBatis-Plus 实体:`Tenant`/`SourceApp`/`ImMessage`/`DistQueue`/`UsageStat`/`MigrateTask`/`UserMapping`/`GroupMapping`/`Recording`/`TrtcRoom`/`CrossTenantGrant`/`CrossTenantAudit`/`PullWatermark`/`ApiCallLog`/`AdminUser` |
| `mapper` | MyBatis 映射,含 `PartitionMapper`PG 分区管理) |
| `filter` | `AdminAuthFilter`(后台鉴权)、`TenantAuthFilter`(租户鉴权)、`GlobalExceptionFilter`(全局异常) |
| `task` | 定时任务:`DispatchRecoverTask`(死信重投)、`PartitionCreateTask`(分区创建)、`PullCheckTask``UsageStatTask` |
| `tencent` | `TencentImClient`REST API + 429 限速重试)、`TencentCallbackSign`(回调签名校验)、`UserSigUtil` |
| `worker` | `DispatchWorker`(消费分发队列) |
| `common` | 工具类:`PasswordUtil`(PBKDF2)、`RedisService``LocalCache``RateLimiter``Jsons``Httpx``Ids``MsgKeys``TenantContext``HealthService``BizException` |
| `model` | `Result`(统一响应体) |
## 核心调用链
1. **回调分发(主链路)**:腾讯服务器 → `CallbackController#callback`(`/imutil/callback/im`) → `CallbackServiceImpl`(验签 + 落 `ImMessage` + 入 `DistQueue`)→ `DispatchWorker` 消费 → 分发到目标租户/源应用;失败由 `DispatchRecoverTask` 重投。
2. **管理后台**`AdminController`Sa-Token 会话)+ FreeMarker 页面(`resources/templates/*.ftl`):login / home / tenant / sourceapp / grant / queue / usage / password / messages / migrate。
3. **数据迁移**`MigrateService`C2C 全量分页 + 群消息,`msgLookbackDays` 回溯天数,429 自动重试)。
4. **跨租户授权**`CrossTenantService``CrossTenantGrant` 授权 + `CrossTenantAudit` 审计)。
5. **分区与统计**`PartitionService`(按 `msg_time` 分区裁剪)+ `PartitionCreateTask``UsageStatService` + `UsageStatTask`
## 项目约定(务必遵守)
- **Long ID → String**:雪花 ID 等 `Long` 序列化给前端**必须** `String.valueOf()`,否则 JS 精度丢失(全局规则 10.1)。
- **FreeMarker + java.time 坑**ftl 里**禁止**对 `LocalDateTime`/`OffsetDateTime``?string(pattern)`(抛 `NonMethodException`)。改为 Controller 端 `DateTimeFormatter.format` 预格式化成 `xxxStr` 字段,ftl 用 `${obj.xxxStr!}`。详见 `.claude/memory/freemarker-java-time.md`
- **LocalDate / DateTimeFormatter**pattern 含 `H/m/s` 时只能用 `LocalDateTime`,禁用 `LocalDate.now()`(全局规则 10.2)。
- **中文注释**:所有注释用简体中文,标识符保持英文(全局规则 9)。
- **密码**`PasswordUtil` PBKDF2 哈希存 `admin_user.password_hash``iterations:salt:hash`);改密走 `/admin/password`
- **配置不提交**`app.yml` 含密钥(Redis 密码 / `tencent.secretKey` / admin 默认密码),长期处于未提交状态,**勿提交**;如需本地覆盖敏感值用 `app-env.yml`(已 gitignore)。
## 关键配置(`src/main/resources/app.yml`,值为准)
- `server.port` / `server.contextPath`
- `solon.dataSources.db1`PostgreSQL 连接)
- `imutil.redis`host/port/password/database
- `imutil.tencent``sdkAppId`/`secretKey`/`adminUserId`/`apiHost`/`usersigExpireDays`/`callbackToken`——`callbackToken` 留空则跳过回调签名校验,仅联调用)
- `imutil.admin``defaultUsername`/`defaultPassword`——仅首次初始化 `admin_user`
- `imutil.migrate``msgLookbackDays` 等)
- `sa-token.*`
## 文档与外部资源
- **实施记录与测试用例**Obsidian 库 `8 腾讯IM&音视频分发\实施记录与测试用例.md`(项目外,按 T1–T16 任务编号组织;**不进 git**)。
- **项目记忆**`.claude/memory/`(已 gitignore,含账号/密码/密钥提醒等;索引见其 `MEMORY.md`)。召回失效时跑 `/memory-sync` 重新同步到会话目录。
- **数据库操作**:本机无 `psql`,用 DBX MCP`192.168.10.118-pg`database `tencen_im`)查改数据。详见 `.claude/memory/pg-via-dbx.md`
- **回调测试**:端点 `/imutil/callback/im`,本地可用 curl 模拟(见 `.claude/memory/tencent-callback-test.md`)。
## 当前进度
核心功能 T1T15 已完成(T12/T13 TRTC 音视频按需暂缓),T14 消息迁移 V1–V4、T16 消息记录查看已完成。详见实施记录文档与 `.claude/memory/git-author-and-progress.md`