主体(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>
83 lines
6.0 KiB
Markdown
83 lines
6.0 KiB
Markdown
<!-- 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
|
||
- **缓存/队列**:Redis(Jedis)+ 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`)。
|
||
|
||
## 当前进度
|
||
|
||
核心功能 T1–T15 已完成(T12/T13 TRTC 音视频按需暂缓),T14 消息迁移 V1–V4、T16 消息记录查看已完成。详见实施记录文档与 `.claude/memory/git-author-and-progress.md`。
|