Compare commits
3 Commits
dfb79af4b1
...
b14cc119b4
| Author | SHA1 | Date | |
|---|---|---|---|
| b14cc119b4 | |||
| 1b9d301521 | |||
| 3ee35601e2 |
@ -115,184 +115,6 @@
|
||||
|
||||
**P1 末预检查清单**(P2 开工前必须验证):
|
||||
|
||||
| # | 检查项 | 来源 | 验证方式 |
|
||||
|---|--------|------|---------|
|
||||
| 1 | socialService 有 `LikeAsset` 方法(或等价的写 like_income_log 入口) | §2.2 事件类型 | `grep -r "like_income_log" backend/services/socialService/` |
|
||||
| 2 | galleryService 有 `PlaceAsset`(开始展出)/ `RemoveFromSlot`(结束展出)方法 | §2.2 | `grep -rn "^func.*PlaceAsset\|^func.*RemoveFromSlot" backend/services/galleryService/service/` |
|
||||
| 3 | taskService 有 `OnExhibitionCompleted` 方法(派发展览收益) | §2.2 | `grep -n "OnExhibitionCompleted" backend/services/taskService/service/revenue_service.go` |
|
||||
| 4 | assetService 有 `CreateMintOrder`(铸造)/ `CheckUpgrade` 或 `logLevelChange`(升级)方法 | §2.2 | `grep -n "CreateMintOrder\|CheckUpgrade\|logLevelChange" backend/services/assetService/service/{mint_service,asset_level_service}.go`;**实际等级变更点需 P1 末向 assetService 同学确认** |
|
||||
| 5 | userService 有 `UpdateCrystalBalance` 方法(水晶账本变动) | §2.2 | `grep -n "UpdateCrystalBalance" backend/services/userService/service/user_service.go` |
|
||||
| 6 | public 库的 `assets` 表有 `level` 字段、`status='active'` 软删除状态、`deleted_at IS NULL` 约定(MV4 假设) | §3.4 MV4 | `\d+ public.assets` 或 DBeaver 看 schema |
|
||||
| 7 | public 库的 `like_income_log` / `crystal_log` / `level_up_log` 表存在 | §3.4、§3.5 | `\dt public.*` |
|
||||
| 8 | Dubbo triple 端口 20009 未被占用 | §1.4 | `lsof -i :20009` |
|
||||
| 9 | 业务侧 5 个服务的 Dubbo 注册名(`tri://...:20000/20001/20002/20003/20006`)可达 | §1.2、§5.5 | `nc -zv` 或本地 e2e 调通 |
|
||||
| 10 | PostgreSQL `statistic` schema 创建权限 OK | §3.1 | 用业务账号 `CREATE SCHEMA statistic` 测试 |
|
||||
|
||||
**核对不过则不进 P2。**
|
||||
|
||||
#### P2 · 事件采集框架
|
||||
|
||||
**目标:** 服务能收事件、能落库、能维护预聚表;socialService 一个业务方联调通。
|
||||
|
||||
| 模块 | 涉及文件 | 备注 |
|
||||
|------|---------|------|
|
||||
| **model** | `model/event.go` | Event 结构 + JSONB 序列化 |
|
||||
| **repository** | `repository/event_repo.go` | 批量 INSERT ON CONFLICT DO NOTHING + event_id 去重 |
|
||||
| **repository** | `repository/metric_repo.go` | 3 个预聚表读写 |
|
||||
| **sink** | `sink/event_sink.go`(接口)+ `sink/channel_sink.go`(本期实现) | EventSink 接口预留 OLAP/实时/采样实现点 |
|
||||
| **service** | `service/event_service.go` | 校验(event_id 格式 / user_id 存在 / event_type 白名单 / properties < 1KB)+ 推入 channel |
|
||||
| **worker** | `worker/event_flusher.go` | 攒批 100 条/1s + 批量落库 + 触发 metric_recent_level_ups 同步更新 |
|
||||
| **worker** | `worker/metric_recent_level_ups_updater.go` | 同步更新(被 event_flusher 调用) |
|
||||
| **worker** | `worker/metric_upcoming_level_ups_updater.go` | 15min ticker(不依赖 event) |
|
||||
| **worker** | `worker/metric_weekly_user_income_updater.go` | 5min ticker,pg_try_advisory_lock 防多实例重复 |
|
||||
| **worker** | `worker/partitioner.go` | 启动时 + 每天 00:05 创建未来 7 天 / 每天 00:30 清理 30 天前 |
|
||||
| **proto handler** | `handler/track_event.go` | TrackEvent / BatchTrackEvent RPC 实现 |
|
||||
| **业务侧集成** | socialService 的 `like_income_log` 写入后 | 调用 `statisticClient.TrackEvent` 异步(fire-and-forget) |
|
||||
| **测试** | `service/event_service_test.go`、`worker/event_flusher_test.go`、`integration/track_event_test.go` | 单元 + 集成(dockertest 真实 PG) |
|
||||
|
||||
**EventSink 接口设计(关键):**
|
||||
|
||||
```go
|
||||
type EventSink interface {
|
||||
Submit(ctx context.Context, e *event.Event) error
|
||||
SubmitBatch(ctx context.Context, es []*event.Event) error
|
||||
Close() error
|
||||
}
|
||||
// 本期实现:ChannelEventSink(推到 channel,由 event_flusher 消费)
|
||||
// 未来扩展:KafkaEventSink / ClickHouseDualWriteSink / SamplingEventSink
|
||||
```
|
||||
|
||||
#### P3 · 看板 7 RPC
|
||||
|
||||
**目标:** 7 个看板端到端通,前端可切流量。
|
||||
|
||||
| 模块 | 涉及文件 | 备注 |
|
||||
|------|---------|------|
|
||||
| **model** | `model/metric.go` | 7 个响应结构对齐 proto |
|
||||
| **repository** | `repository/dashboard_repo.go` | 7 个聚合 SQL(读 MV/预聚表,公共 schema 关联 assets/exhibitions) |
|
||||
| **service** | `service/dashboard_service.go` | 7 个 RPC 业务逻辑 + Redis 5min TTL + 缓存穿透防护 + 跨服务调用降级(userService.crystal_balance 失败时用 stale 标记) |
|
||||
| **service** | `service/metric_service.go` | MV 刷新协调(被 materializer 调用) |
|
||||
| **worker** | `worker/materializer.go` | 4 个 MV 用 REFRESH MATERIALIZED VIEW CONCURRENTLY + pg_try_advisory_lock + 错开执行时间(避免 DB 瞬时压力) + refresh_log 记录 |
|
||||
| **cache** | `service/cache.go` | 封装 pkg/redis,cache key 格式 `dash:{rpc}:{star_id}:{user_id}` |
|
||||
| **gateway 路由** | `backend/gateway/router/router.go` | 7 个 GET 路由 |
|
||||
| **gateway controller** | `backend/gateway/controller/statistic_controller.go` | 7 个方法 + JWT 鉴权 + 粉丝身份校验(调 userService.fan_profile)+ 响应包装 `{code:200, data:resp}` |
|
||||
| **业务侧消费** | gateway 调 7 个 RPC,前端 dashboardApi 已固定 | 切流量前确认前端 mock 数据准确性 |
|
||||
| **测试** | `service/dashboard_service_test.go`(7 RPC 单测)、`integration/dashboard_rpc_test.go`(7 RPC dockertest)、`integration/gateway_test.go`(端到端) | 100% RPC 覆盖 |
|
||||
|
||||
**看板冷启动优化(启动 hook 预热):**
|
||||
|
||||
```go
|
||||
// main.go 启动时
|
||||
go func() {
|
||||
for _, starID := range getTopNStars(100) { // 取前 100 star
|
||||
for _, rpc := range dashboardRPCs {
|
||||
go callAndCache(rpc, starID, sampleUserID)
|
||||
}
|
||||
}
|
||||
}()
|
||||
```
|
||||
|
||||
#### P4 · 业务侧补全(4 个服务)
|
||||
|
||||
| 顺序 | 服务 | 事件类型 | 集成位置 |
|
||||
|------|------|---------|---------|
|
||||
| 1 | galleryService | `exhibition.start`、`exhibition.end` | `PlaceAsset`(开始)/ `RemoveFromSlot`(结束)后 |
|
||||
| 2 | taskService | `exhibition.revenue` | `OnExhibitionCompleted` 调用后 |
|
||||
| 3 | assetService | `asset.mint`、`asset.level_up` | `CreateMintOrder`(铸造)/ `CheckUpgrade`(升级)后 |
|
||||
| 4 | userService | `crystal.change` | `UpdateCrystalBalance` 调用后 |
|
||||
|
||||
**集成模式(统一封装):**
|
||||
|
||||
```go
|
||||
// 各服务引入 pkg/statistic 客户端
|
||||
type StatisticClient interface {
|
||||
TrackEvent(ctx context.Context, e *event.Event) error
|
||||
BatchTrackEvent(ctx context.Context, es []*event.Event) error
|
||||
}
|
||||
// 业务调用方用 defer + recover 保证 fire-and-forget 不影响主流程
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorf("statistic track panic: %v", r)
|
||||
}
|
||||
go statisticClient.TrackEvent(bgCtx, buildEvent(...))
|
||||
}()
|
||||
```
|
||||
|
||||
**每个服务必做:**
|
||||
|
||||
- 加 `statisticClient` 注入
|
||||
- 在指定业务方法后加 `defer go TrackEvent`
|
||||
- 单测验证 `TrackEvent` 被调用(用 mock client)
|
||||
- 联调验证事件能落库 + 看板数据能出现
|
||||
|
||||
---
|
||||
|
||||
## 〇·一、本期实施范围(2026-06-08 追加)
|
||||
|
||||
> **状态**: 本期聚焦看板 7 RPC + 事件采集框架两个范围,业务侧 5 服务集成在本期内,OLAP/SDK/实时/采样/多赛季/灰度发布策略等延后。本节不替换原设计,只标注"本期做 vs 不做",并给出阶段划分与特性清单,作为实施依据。
|
||||
|
||||
### 0.1.1 范围矩阵
|
||||
|
||||
| # | 类别 | 项目 | 本期 | 来源章节 |
|
||||
|---|------|------|------|----------|
|
||||
| 1 | **看板 7 RPC** | `GetTodayOverview` / `Get7DayIncomeCurve` / `GetExhibitionIncomeSummary` / `GetLikeIncomeByLevel` / `GetTopAssetsByEarning` / `GetAssetLevelDistribution` / `GetAssetUpgradeProgress` | ✅ | §2.3、§3.4-3.5、§4.2 |
|
||||
| 2 | **事件采集** | `TrackEvent` / `BatchTrackEvent` + EventSink 抽象 + Worker 批量落库 | ✅ | §2.2、§3.2、§4.3 |
|
||||
| 3 | **预聚合表** | `metric_weekly_user_income` / `metric_recent_level_ups` / `metric_upcoming_level_ups` | ✅ | §3.5 |
|
||||
| 4 | **物化视图** | `mv_daily_user_income` / `mv_daily_exhibition_revenue` / `mv_daily_like_income` / `mv_asset_level_distribution` | ✅ | §3.4 |
|
||||
| 5 | **分区管理** | events 表按日分区 + 7 天预创建 + 30 天清理 | ✅ | §3.3、§4.5 |
|
||||
| 6 | **Gateway 路由** | 7 个 `/api/v1/dashboard/*` 路由 + JWT 鉴权 + 粉丝身份校验 | ✅ | §2.4-2.6、§4.2 |
|
||||
| 7 | **业务侧集成** | 5 个服务改造(social/asset/gallery/task/user)逐步加 TrackEvent | ✅ | §5.7 |
|
||||
| 8 | **可观测性** | Prometheus 指标(§4.7 全量)+ healthz + refresh_log | ✅ | §4.7、§4.8 |
|
||||
| 9 | **OLAP 双写** | ClickHouse 集成 | ❌ | §3.10、§6.2 |
|
||||
| 10 | **实时通道** | 同步 TrackEvent 实时通道 | ❌ | §3.10、§6.2 |
|
||||
| 11 | **SDK 端点** | HTTP `/api/v1/dashboard/track` 暴露 | ❌ | §2.4、§3.10 |
|
||||
| 12 | **采样** | 客户端采样 + `EnableSampling` | ❌ | §3.8、§4.9 |
|
||||
| 13 | **多赛季** | `season_id` 字段 / 赛季 Tab | ❌ | §3.10、§6.2 |
|
||||
| 14 | **灰度发布** | 10%→100% 灰度策略 + Stage 1-6 步骤 | ❌(保留机制但简化执行) | §5.8 |
|
||||
| 15 | **完整告警实现** | 钉钉/飞书 webhook(10 条规则) | ❌(指标埋点保留,告警由监控平台处理) | §4.8 |
|
||||
| 16 | **Channel 满载 Level 2-5** | 加大缓冲 / Worker 并发 / 解耦预聚合 / 降级 / 采样 | ❌(仅 Level 1 监控) | §4.9 |
|
||||
|
||||
### 0.1.2 阶段划分(4 阶段 / 事件先看板后)
|
||||
|
||||
| 阶段 | 名称 | 周期(估) | 关键产出 | 关键验证 |
|
||||
|------|------|-----------|---------|---------|
|
||||
| **P1** | 服务骨架 | 2-3 天 | go.mod / proto / main.go / config / healthz / 启动接入 go.work / 10 个 SQL 迁移 | `go build` 通过 + `go test ./...` 通过 + 契约测试(proto json schema 校验)+ DB 迁移可重放 + P1 末预检查清单通过 |
|
||||
| **P2** | 事件采集 | 4-5 天 | events 表(已建)+ TrackEvent/BatchTrackEvent RPC + EventSink 接口 + event_flusher Worker + 3 个预聚表 + partitioner | 单元测试 + 集成测试(dockertest 真实 PG)+ 业务侧 socialService 一个服务联调通 |
|
||||
| **P3** | 看板 7 RPC | 4-5 天 | 4 个 MV + 7 个 RPC service + main.go 启动预热 7 cache 逻辑 + gateway 7 路由 + Redis 5min TTL | 集成测试(7 RPC 全覆盖)+ gateway 端到端 + 性能测试(k6/wrk 缓存命中/未命中 P99)+ 前端切流量 + cache hit rate > 80% |
|
||||
| **P4** | 业务侧补全 | 2-3 天 | 4 个服务集成(galleryService→taskService→assetService→userService)+ 各服务单元测试 + 联调 | 各服务 `go test` 通过 + 看板数据逐步出现 7 个事件类型 |
|
||||
|
||||
**关键依赖 / 决策:**
|
||||
|
||||
- **P2 不依赖 P3**:事件写入和看板读取在 service 层完全解耦(service 写 events,dashboard 读 MV),但都需要 PG schema + proto + config
|
||||
- **P3 启动时预热**:dashboard 7 个 RPC 各走独立 cache key + 启动 hook 预热 7 个 cache(防冷启动雪崩)
|
||||
- **P4 顺序**:galleryService(exhibition.start/end)→ taskService(exhibition.revenue)→ assetService(asset.mint/level_up)→ userService(crystal.change)。先做高频路径(gallery 展出是核心场景),后做低频路径
|
||||
|
||||
**对外接口冻结点:**
|
||||
|
||||
- P1 末:proto 文件定稿(7 RPC + 2 事件 RPC,事件 RPC 内部可用)
|
||||
- P2 末:TrackEvent RPC 内部可用,业务侧可接入
|
||||
- P3 末:前端可切流量
|
||||
|
||||
### 0.1.3 特性清单(每阶段具体做什么)
|
||||
|
||||
#### P1 · 服务骨架
|
||||
|
||||
**目标:** 起个空服务能跑起来,迁移能跑通。
|
||||
|
||||
| 任务 | 涉及文件 | 备注 |
|
||||
|------|---------|------|
|
||||
| go.mod 创建 | `backend/services/statisticService/go.mod` | 沿用 taskService 的依赖(pkg/database / pkg/redis / pkg/dubbo / pkg/metrics) |
|
||||
| go.work 集成 | `backend/go.work` | 加 `./services/statisticService` |
|
||||
| proto 定义 | `backend/proto/event.proto`、`backend/proto/statistic.proto` | **关键:冻结 proto**,7 RPC + 2 事件 RPC |
|
||||
| 配置 | `backend/services/statisticService/config/statistic_config.go` | 端口 20009 + schema=statistic + Redis + 刷新间隔 + Channel 配置 + 4 个 EnableXxx=false(OLAPDualWrite / RealtimeChannel / SDKEndpoint / Sampling) |
|
||||
| main.go | `backend/services/statisticService/main.go` | Dubbo triple 注册 + HTTP healthz + 启动 Worker hook |
|
||||
| DB 迁移 10 个 SQL | `backend/migrations/2026_06_08_001_statistic_events.sql` 等 10 个 | events 分区表 + 4 MV + 3 预聚表 + refresh_log + 初始 7 天分区 |
|
||||
| healthz 端点 | `backend/services/statisticService/handler/healthz.go` | 暴露 6 个关键指标(DB ping / Redis ping / Worker 状态 / 事件 channel 大小 / 预聚表行数) |
|
||||
| metrics 埋点骨架 | `backend/services/statisticService/metrics/metrics.go` | §4.7 全部 20+ 指标先声明,本期不全部埋数据 |
|
||||
| 启动验证 | 本地 + CI | `go build` + `go test ./...` + DB 迁移重放 + healthz 200 |
|
||||
|
||||
**P1 末预检查清单**(P2 开工前必须验证):
|
||||
|
||||
| # | 检查项 | 来源 | 验证方式 |
|
||||
|---|--------|------|---------|
|
||||
| 1 | socialService 有 `LikeAsset` 方法(或等价的写 asset_likes 入口) | §2.2 事件类型 | `grep -rn "asset_likes\|LikeAsset" backend/services/socialService/` |
|
||||
|
||||
@ -61,16 +61,20 @@ TopFans 是一个"明星粉丝平台"。不同明星的粉丝量差异极大:
|
||||
|
||||
### 1.3 即将到来的新服务
|
||||
|
||||
代码或 PRD 中已提及、未来需要独立部署的**全新系统**(非复用现有服务,各自独立代码库、独立 DB,只通过 API 互相调用):
|
||||
代码或 PRD 中已提及、未来需要独立部署的**新服务**。**与现有服务的关系各不相同**,明确如下:
|
||||
|
||||
| 服务 | 作用 | 关系 |
|
||||
| 服务 | 作用 | 与现有服务的关系 |
|
||||
|---|---|---|
|
||||
| `admin` | 后台管理平台(运营、客服) | **独立系统**,通过 API 调用 userservice / 其他服务 |
|
||||
| `review` | 审核工作流(UGC 内容审核) | **独立系统**,通过 API 调用 assetservice 读取待审内容,审核结果回写自己的 DB |
|
||||
| `ai-image-gen` | AI 图片生成(镭射卡) | **独立系统**,gateway 改为通过 API 调用它(原 MiniMax 调用逻辑迁过去) |
|
||||
| `ai-chat` | AI 对话(粉丝互动) | **独立系统**,前端通过 API 调用它(从 aichatservice 拆出来) |
|
||||
| `admin` | 后台管理平台(运营、客服) | **共享数据库**: 直接连同一个 PostgreSQL 实例(可能不同 schema,共享用户/订单/内容数据),不走 API 调用 |
|
||||
| `review` | 审核工作流(UGC 内容审核) | **被现有后端调用**: 现有后端(gateway / assetservice)调用 review 的 API 提交审核任务,review 异步审核后结果回写(可通过 callback / 消息 / 共享表) |
|
||||
| `ai-image-gen` | AI 图片生成(镭射卡) | **被现有后端调用**: gateway 改为调用 ai-image-gen 服务的 API(原 MiniMax 调用逻辑迁过去) |
|
||||
| `ai-chat` | AI 对话(粉丝互动) | **被现有后端/前端调用**: 现有后端/前端调用 ai-chat 服务的 API(它是新服务,不是从 aichatservice 拆出) |
|
||||
|
||||
> **关键澄清**: 这四个服务与现有 `userservice` / `assetservice` / `aichatservice` 等**没有代码级复用关系**,也没有共享 DB schema。它们是完全独立的新服务,通过 HTTP/gRPC API 互相调用。K8s 迁移任务只负责**为它们预留 namespace 位置和部署模板**,不涉及实现。
|
||||
> **关键澄清**:
|
||||
> - `admin` **不走 API**,通过共享 DB 直连,**权限和访问范围由 DB 账号/RBAC 控制**
|
||||
> - `review` / `ai-image-gen` / `ai-chat` 是**被调用方**,由现有后端(gateway / 前端)主动调用
|
||||
> - 这四个服务与现有 `userservice` / `assetservice` / `aichatservice` 等**没有代码级复用关系**
|
||||
> - K8s 迁移任务只负责**为它们预留 namespace 位置和部署模板**,不涉及实现
|
||||
|
||||
---
|
||||
|
||||
@ -649,12 +653,21 @@ spec:
|
||||
persistentvolumeclaims: "5"
|
||||
```
|
||||
|
||||
#### 11.4.3 跨 namespace Dubbo 调用 (新服务 ↔ 数据服务)
|
||||
#### 11.4.3 跨 namespace 调用模式 (新服务 ↔ 数据服务)
|
||||
|
||||
Phase 2 启动后,`admin` / `review` 部署在 `topfans-shared`,需要调用各组数据服务(如 review 审核 group-a 的 gallery):
|
||||
Phase 2 启动后,部署在 `topfans-shared` 的新服务与各组数据服务的交互方式各不相同:
|
||||
|
||||
**`admin` (共享 DB,不走 API)**:
|
||||
- 直接连同一个 PostgreSQL,按 RBAC 权限访问数据
|
||||
- **不需要**调用数据服务 API
|
||||
- K8s 部署上:`admin` Pod 内的 `DB_HOST=postgres-ext`(同 §4.2.2 的 ExternalName),与现有服务共用同一份 RDS
|
||||
|
||||
**`review` (被现有后端调用 + 需读数据)**:
|
||||
- 现有后端调用 review 提交审核任务(API 入站,不需要跨 ns)
|
||||
- review 审核时需要**读取各组**待审内容(如 group-a 的 gallery),需跨 ns 调数据服务
|
||||
- 跨 ns Dubbo 调用模式(可选方案):
|
||||
|
||||
```yaml
|
||||
# 未来 review 服务的环境变量 (在 topfans-shared namespace)
|
||||
# 方式 1: 静态全限定名(每组一个变量)
|
||||
DUBBO_GALLERY_SERVICE_URL_GROUP_A: tri://galleryservice.topfans-group-a.svc.cluster.local:20001
|
||||
DUBBO_GALLERY_SERVICE_URL_GROUP_B: tri://galleryservice.topfans-group-b.svc.cluster.local:20001
|
||||
@ -662,7 +675,11 @@ DUBBO_GALLERY_SERVICE_URL_GROUP_B: tri://galleryservice.topfans-group-b.svc.clus
|
||||
# 方式 2: 用配置中心或服务发现动态获取 (推荐)
|
||||
```
|
||||
|
||||
**应用层需支持 group 维度路由**。具体实现属于应用层设计,不在 K8s 范围。
|
||||
**`ai-image-gen` / `ai-chat` (被调用,无跨 ns 需求)**:
|
||||
- gateway / 前端调用它们时通过 Ingress 进 shared ns,不需要主动调数据服务
|
||||
- 可能间接调 userservice(获取用户信息),走短 DNS 名
|
||||
|
||||
**应用层需支持 group 维度路由**(针对 review)。具体实现属于应用层设计,不在 K8s 范围。
|
||||
|
||||
#### 11.4.4 应用层多租户改造 (数据层)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user