557 lines
44 KiB
Markdown
557 lines
44 KiB
Markdown
# 配置与部署 (批次4) Implementation Plan
|
||
|
||
REQUIRED SUB-SKILL: superpowers:writing-plans
|
||
(实施阶段使用 superpowers:executing-plans;code-review 阶段使用 superpowers:requesting-code-review)
|
||
|
||
> 配套审计:`docs/backend-audit-2026-07-21.md` §二 P0-6/P0-7、§三 P1 配置、§四 P2
|
||
> 配套修复方案:`docs/specs/2026-07-21-backend-remediation-plan.md` 批次 4(4.1 / 4.2 / 4.3 / 4.4 / 4.5 / 4.9)
|
||
> 基线 commit:`b7f8f1b724e5`,分支 `feat/uni`
|
||
|
||
---
|
||
|
||
## Goal
|
||
|
||
消除 backend 配置/部署层的全部 P0 风险(端口不一致 + starbookService 孤儿)与剩余 P1/P2 卫生项(健康探针、大二进制入库、序列脚本、`.env` 漂移、若干小修),使本地默认值与生产 env 覆盖之间形成**自校验**的部署契约,并通过一次回归把所有"硬编码"参数集中到 4 处可验证的位置(gateway config / 各 main flag / helm values / docker compose)。
|
||
|
||
完成后达成:
|
||
- `go test ./... -run TestPortAlignment` 通过(4.1)。
|
||
- `git ls-files` 不含 `backend/{assetService,gateway-fixed,test,cleanup-orphan-avatars}` 四大二进制;`backend/services/starbookService/` 被 git 删除(4.2 + 4.3)。
|
||
- k8s `notificationservice` / `moderationservice` 探针路径与实际 handler 一致;本地 `curl :21010/health` 返回 `{"status":"ok"}`(4.3)。
|
||
- `scripts/create_gallery_test_users.go` 输出 SQL 末尾含 `setval(...)`;跑完不报 `duplicate key`(4.4)。
|
||
- `backend/.env.example` 与 `backend/.env` 键集合一致(含 `WS_AI_CHAT_PATH` 等),`.example` 不含真实密钥(4.5)。
|
||
- §四 P2 八项全部清理或明确 defer(4.9)。
|
||
|
||
## Architecture
|
||
|
||
本批次不引入新架构,纯**配置归一**与**卫生清理**:
|
||
|
||
```
|
||
┌──────────────────────────────────────┐
|
||
│ k8s/helm/topfans/values.yaml (端口) │ ← 单一事实来源
|
||
└──────────────┬───────────────────────┘
|
||
│ helm install
|
||
┌────────────────────┼────────────────────────┐
|
||
▼ ▼ ▼
|
||
services/*/main.go docker-compose.prod.yml gateway/config/config.go
|
||
(flag 默认值 = helm) (DUBBO_*_SERVICE_URL) (DUBBO_*_SERVICE_URL 默认)
|
||
│ │ │
|
||
▼ ▼ ▼
|
||
┌─────────────┐ ┌────────────┐ ┌─────────────┐
|
||
│配置断言测试 │ ──────▶│ 4.1 校验 │ ◀──────│ 4.1 校验 │
|
||
└─────────────┘ └────────────┘ └─────────────┘
|
||
```
|
||
|
||
依赖关系(必须按顺序):
|
||
1. **4.2 starbookService 删除**先做(它依赖 4.3 探针清理 + P0-4 跨服务 import 解耦)—— 完成前 `go build ./...` 会因 `starbookService` 不在 `go.work` 而失败。
|
||
2. **4.1 端口对齐**与 **4.2 starbook 删除**独立可并行。
|
||
3. **4.3 探针 + 大二进制**与 4.4 / 4.5 可独立并行。
|
||
4. **4.9 P2 清理**作为伴随项随 4.1–4.5 顺手做;只有 "JWT 失效" 和 "PII 日志" 单独任务化。
|
||
|
||
## Tech Stack
|
||
|
||
- **语言**:Go 1.25.5(`go.work`)
|
||
- **测试**:Go testing + testify
|
||
- **K8s 配置**:Helm 3 + values.yaml
|
||
- **Docker**:docker-compose v2 + Dockerfile.services
|
||
- **数据库**(仅 4.4 序列脚本):PostgreSQL 16
|
||
- **静态检查**(后续可加,本批次不引入):golangci-lint
|
||
|
||
---
|
||
|
||
## Global Constraints
|
||
|
||
- **不动架构**:本批次不拆库、不拆 `pkg/models`、不引入新 interface;纯"配置正确化"。
|
||
- **序列规范**:任何手动指定 id 的 INSERT,SQL 末尾必须 `SELECT setval('<table>_id_seq', (SELECT MAX(id) FROM <table>));`,遵循 `CLAUDE.md` 强制规则。
|
||
- **commit 守则**:每个 Task 完成后**不自动 commit**,等用户说"帮我 commit"再提交;commit message 用 `Co-Authored-By: Claude <noreply@anthropic.com>`。
|
||
- **真实命令**:所有 bash 命令需在本地真实可执行(`backend/` 目录、`go.work` 模块、`k8s/helm/topfans/` Helm chart 都在仓库内)。
|
||
- **回归检查**:每个 Task 完成后跑 `go build ./...`(含 `go.work` 全部模块)+ 端口断言测试。
|
||
- **不动产物**:`unpackage/dist/*`、`backend/bin/*` 是构建产物,禁止手动改。
|
||
- **依赖前置**:4.2 starbookService 删除依赖 P0-4 跨服务 import 解耦审计结论(已确认);若 P0-4 实施批次尚未执行 starbook 解耦,本批次单独完成"assetService 内化 `AssetRegistryRepository` 实现"。
|
||
- **MVP 先行**:不要为"未来 12+ 服务"提前做端口配置中心化;本次仅做"四层端口默认值收敛 + 断言测试"。
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
**修改清单(按 Task 顺序)**:
|
||
|
||
| Task | 关键文件 | 类型 |
|
||
|------|----------|------|
|
||
| 4.1 | `backend/gateway/config/config.go`、`backend/services/galleryService/main.go`、`backend/services/userService/main.go`、`backend/services/activityService/main.go`、`backend/services/taskService/main.go`、`backend/services/statisticService/main.go`、`backend/services/notificationService/main.go`、`backend/services/moderationService/main.go`、`backend/services/aiChatService/main.go`、`backend/services/socialService/main.go`、`backend/services/assetService/main.go`、`backend/docker-compose.local.yml`、`backend/docker-compose.prod.yml`、`docker/docker-compose.local.yml`、`docker/docker-compose.prod.yml`、`k8s/helm/topfans/values.yaml`、`backend/services/gateway/config/port_test.go` (新增) | 修 |
|
||
| 4.2 | `backend/services/starbookService/` (整目录)、`backend/services/assetService/main.go`、`backend/services/assetService/service/mint_service.go`、`backend/services/assetService/repository/asset_registry_repository.go` (新增)、`backend/services/assetService/service/asset_service.go` (registryRepo 引用)、`backend/gateway/config/config.go`、`backend/go.work`、`backend/dev.sh`、`docker/Dockerfile.services`、`docker/docker-compose.local.yml`、`docker/docker-compose.prod.yml`、`k8s/helm/topfans/values.yaml`、`k8s/helm/topfans/templates/starbookservice/deployment.yaml` (删) | 删 + 修 |
|
||
| 4.3 | `backend/pkg/health/health.go`、`backend/services/assetService/main.go`、`backend/services/galleryService/main.go`、`backend/services/userService/main.go`、`backend/services/notificationService/main.go`、`backend/services/moderationService/main.go` (注册 `/healthz` alias)、`k8s/helm/topfans/values.yaml`、`k8s/helm/topfans/templates/notificationservice/deployment.yaml`、`k8s/helm/topfans/templates/moderationservice/deployment.yaml`、`docker/docker-compose.local.yml`、`docker/docker-compose.prod.yml`、`backend/.gitignore`、`backend/assetService`、`backend/gateway-fixed`、`backend/test`、`backend/cleanup-orphan-avatars` (git rm --cached) | 修 + rm |
|
||
| 4.4 | `backend/scripts/create_gallery_test_users.go` | 修 |
|
||
| 4.5 | `backend/.env.example`、`backend/.env`、`backend/services/gateway/config/env_audit_test.go` (新增) | 修 + 新增 |
|
||
| 4.9 | `backend/gateway/controller/asset_controller.go`、`backend/gateway/controller/auth_controller.go`、`backend/gateway/controller/user_controller.go`、`backend/services/userService/service/user_service.go`、`backend/services/aiChatService/provider/ai_chat_provider.go`、`backend/services/mint_service.go`、`backend/services/activityService/service/activity_service.go`、`backend/pkg/peripheral/sign.go`、`backend/pkg/mq/streams/adapter.go`、`backend/pkg/mq/asynq/adapter.go`、`backend/services/galleryService/mq/consumer.go` | 修 |
|
||
| 自审 | `docs/superpowers/plans/2026-07-21-config-deploy.md` (本文) | — |
|
||
|
||
**新增测试文件**:
|
||
- `backend/gateway/config/port_test.go`(4.1 端口断言测试)
|
||
- `backend/gateway/config/env_audit_test.go`(4.5 env 键集合对比)
|
||
|
||
---
|
||
|
||
### Task 1: 端口对齐 + 配置断言测试(4.1)
|
||
|
||
> 对应审计 P0-6 与修复方案 4.1。
|
||
|
||
**Files**:
|
||
- `backend/gateway/config/config.go:162-165` —— 修正 `Dubbo.GalleryServiceURL`/`ActivityServiceURL`/`StarbookServiceURL` 默认端口
|
||
- `backend/services/galleryService/main.go:36,44` —— `port` 默认 20001;`taskServiceURL` 默认改 `tri://localhost:20006`
|
||
- `backend/services/userService/main.go:283` —— 删除硬编码 `protocol.WithPort(20000)`,改用 `*port`
|
||
- `backend/services/assetService/main.go:38` —— 确认 20003 默认
|
||
- `backend/services/activityService/main.go` —— 默认端口确认 20004
|
||
- `backend/services/taskService/main.go` —— 默认端口确认 20006
|
||
- `backend/services/statisticService/main.go` —— 默认 20009
|
||
- `backend/services/notificationService/main.go` —— 默认 20010
|
||
- `backend/services/moderationService/main.go` —— 默认 20011
|
||
- `backend/services/aiChatService/main.go` —— 默认 20008
|
||
- `backend/services/socialService/main.go` —— 默认 20002
|
||
- `docker/docker-compose.local.yml`、`docker/docker-compose.prod.yml`、`k8s/helm/topfans/values.yaml` —— 同步 `DUBBO_*_SERVICE_URL` 默认值
|
||
- `backend/gateway/config/port_test.go` (新增) —— 配置断言测试
|
||
|
||
**Interfaces**:
|
||
```go
|
||
// backend/gateway/config/config.go (excerpt)
|
||
type DubboConfig struct {
|
||
UserServiceURL string // tri://127.0.0.1:20000
|
||
SocialServiceURL string // tri://127.0.0.1:20002
|
||
AssetServiceURL string // tri://127.0.0.1:20003
|
||
GalleryServiceURL string // tri://127.0.0.1:20001 ← 修正
|
||
ActivityServiceURL string // tri://127.0.0.1:20004 ← 修正
|
||
TaskServiceURL string // tri://127.0.0.1:20006
|
||
StarbookServiceURL string // 删除(4.2)
|
||
AIChatServiceURL string // tri://127.0.0.1:20008
|
||
StatisticServiceURL string // tri://127.0.0.1:20009
|
||
NotificationServiceURL string // tri://127.0.0.1:20010
|
||
ModerationServiceURL string // tri://127.0.0.1:20011
|
||
}
|
||
```
|
||
|
||
**Steps**:
|
||
- [ ] 读 `backend/gateway/config/config.go:158-170`,记下当前 11 个 `DUBBO_*_SERVICE_URL` 默认值。
|
||
- [ ] 改 `backend/gateway/config/config.go:162`:`getEnv("DUBBO_GALLERY_SERVICE_URL", "tri://127.0.0.1:20001")`。
|
||
- [ ] 改 `backend/gateway/config/config.go:163`:`getEnv("DUBBO_ACTIVITY_SERVICE_URL", "tri://127.0.0.1:20004")`。
|
||
- [ ] 验证 `Task/Asset/AIChat/Statistic/Notification/Moderation/Social/User` 默认值与实际服务 `main.go` 的 `flag.Int("port", ...)` 默认一致(读 `backend/services/<svc>/main.go` 头部 `var port` 块)。
|
||
- [ ] 改 `backend/services/galleryService/main.go:44`:`taskServiceURL = flag.String("task-service-url", getEnv("TASK_SERVICE_URL", "tri://localhost:20006"), ...)`。
|
||
- [ ] 改 `backend/services/userService/main.go:283`:`protocol.WithPort(*port)`(替代硬编码 20000),同时保留 `port` flag 默认 20000。
|
||
- [ ] 创建 `backend/gateway/config/port_test.go`,内容(关键骨架):
|
||
```go
|
||
package config
|
||
|
||
import (
|
||
"regexp"
|
||
"strconv"
|
||
"testing"
|
||
)
|
||
|
||
// 各服务 flag 默认端口(与 <service>/main.go 顶部 var port = flag.Int(...) 一致)
|
||
var serviceDefaultPorts = map[string]int{
|
||
"user": 20000,
|
||
"social": 20002,
|
||
"asset": 20003,
|
||
"gallery": 20001,
|
||
"activity": 20004,
|
||
"task": 20006,
|
||
"aiChat": 20008,
|
||
"statistic": 20009,
|
||
"notification": 20010,
|
||
"moderation": 20011,
|
||
}
|
||
|
||
// TestGatewayDubboPortsMatchServiceDefaults 校验网关默认 URL 端口 == 各服务 main.go 绑定端口
|
||
func TestGatewayDubboPortsMatchServiceDefaults(t *testing.T) {
|
||
cfg := Load()
|
||
cases := map[string]string{
|
||
"user": cfg.Dubbo.UserServiceURL,
|
||
"social": cfg.Dubbo.SocialServiceURL,
|
||
"asset": cfg.Dubbo.AssetServiceURL,
|
||
"gallery": cfg.Dubbo.GalleryServiceURL,
|
||
"activity": cfg.Dubbo.ActivityServiceURL,
|
||
"task": cfg.Dubbo.TaskServiceURL,
|
||
"aiChat": cfg.Dubbo.AIChatServiceURL,
|
||
"statistic": cfg.Dubbo.StatisticServiceURL,
|
||
"notification": cfg.Dubbo.NotificationServiceURL,
|
||
"moderation": cfg.Dubbo.ModerationServiceURL,
|
||
}
|
||
re := regexp.MustCompile(`:(\d+)$`)
|
||
for name, url := range cases {
|
||
m := re.FindStringSubmatch(url)
|
||
if len(m) != 2 { t.Fatalf("%s: URL %q 缺端口", name, url) }
|
||
got, _ := strconv.Atoi(m[1])
|
||
want := serviceDefaultPorts[name]
|
||
if got != want {
|
||
t.Errorf("%s: 网关默认端口=%d, 服务实际默认=%d", name, got, want)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
- [ ] 跑 `cd backend && go test ./gateway/config/... -run TestGatewayDubboPortsMatchServiceDefaults -v` —— 必须通过。
|
||
- [ ] 改 `backend/.env.example:21-26`:删除 `DUBBO_SOCIAL_SERVICE_URL=tri://127.0.0.1:20001` 错值(应是 20002),并按上面 11 个 URL 重新列。
|
||
- [ ] 改 `docker/docker-compose.local.yml`、`docker/docker-compose.prod.yml`:所有 `DUBBO_*_SERVICE_URL` 默认端口与上述一致(实际生产值通常已被 gateway container 内部覆盖;只动文档化注释)。
|
||
- [ ] 改 `k8s/helm/topfans/values.yaml`:删除 `services.starbookservice` 块(4.2 负责删),并修正 `DUBBO_GATEWAY_*` 注释默认值。
|
||
- [ ] 跑 `cd backend && go build ./...` —— 全部模块编译通过。
|
||
- [ ] 跑 `cd backend && go test ./... -run TestPort` —— 端口断言通过。
|
||
- [ ] 准备 commit message:"fix(config): 对齐网关与服务默认端口并加配置断言测试 (批次 4.1)"—— **需用户批准后 commit**。
|
||
|
||
### Task 2: starbookService 决断 → 删除(4.2)
|
||
|
||
> 对应审计 P0-7 与修复方案 4.2 推荐方案(生产未部署 starbookService)。
|
||
> ⚠️ 关联 P0-4 跨服务 import 解耦:assetService 当前 `import starbookService/repository`(`backend/services/assetService/main.go:32`、`service/mint_service.go:28`),但 starbookService 不在 `go.work`(第 1-16 行)。当前 `go build` 实际**已失败**,本 Task 同步修复。
|
||
|
||
**Files**:
|
||
- 删除:`backend/services/starbookService/`(整个目录,含 `main.go`、`provider/`、`repository/`、`service/`、`starbookService` 二进制若存在)
|
||
- 删除:`k8s/helm/topfans/templates/starbookservice/deployment.yaml`
|
||
- 修改:`backend/services/assetService/main.go:32,159` —— 删除 `starbookRepo` import,改为本地 `repository.NewAssetRegistryRepository`
|
||
- 修改:`backend/services/assetService/service/mint_service.go:28,71,84` —— 删除 `starbookRepo` import,改用 `assetRegistryRepo.AssetRegistryRepository`(定义在 `assetService/repository`)
|
||
- 修改:`backend/services/assetService/service/asset_service.go:58,69,78,116-131` —— `registryRepo` 类型不变(接口定义在 `asset_service.go:46-49`,已用 `models.AssetRegistry`),仅切换实现来源
|
||
- 新增:`backend/services/assetService/repository/asset_registry_repository.go` —— 从 starbookService 复制 `AssetRegistryRepository` 接口 + 实现,改 package 为 `repository`;去掉 import 循环
|
||
- 修改:`backend/gateway/config/config.go:95,165` —— 删除 `StarbookServiceURL` 字段
|
||
- 修改:`backend/go.work:3-16` —— 移除 starbookService(实际本就不在)
|
||
- 修改:`backend/dev.sh:24,347,365,432,438,468,493,510,530` —— 删除所有 starbookService 引用
|
||
- 修改:`docker/Dockerfile.services:50` —— 删除 `go build ... starbookservice` 行
|
||
- 修改:`docker/docker-compose.local.yml:231-262,442,482` —— 删除 starbookservice service block 与 `DUBBO_STARBOOK_SERVICE_URL` env
|
||
- 修改:`docker/docker-compose.prod.yml:365-401,629,659` —— 同上
|
||
- 修改:`k8s/helm/topfans/values.yaml:125,214-232` —— 删除 `services.starbookservice` 块与其在 `gateway.env` 里的 `DUBBO_STARBOOK_SERVICE_URL`
|
||
|
||
**Interfaces**(新增):
|
||
```go
|
||
// backend/services/assetService/repository/asset_registry_repository.go
|
||
package repository
|
||
|
||
import "github.com/topfans/backend/pkg/models"
|
||
|
||
// AssetRegistryRepository 资产统一索引(从 starbookService 迁来,assetService 现在是 owner)
|
||
type AssetRegistryRepository interface {
|
||
Create(registry *models.AssetRegistry) error
|
||
GetByID(id int64) (*models.AssetRegistry, error)
|
||
GetByAssetID(assetID int64) (*models.AssetRegistry, error)
|
||
GetByAssetTypeAndID(assetType string, assetID int64) (*models.AssetRegistry, error)
|
||
GetByOwner(ownerUID, starID int64) ([]*models.AssetRegistry, error)
|
||
GetByOwnerAndType(ownerUID, starID int64, assetType string, limit, offset int) ([]*models.AssetRegistry, error)
|
||
GetByOwnerAndTypeAndGrade(...) (...)
|
||
GetByOwnerAndTypeAndCategory(...) (...)
|
||
GetByOwnerAndTypeAndActivity(...) (...)
|
||
CountByOwner(...) (int64, error)
|
||
CountByOwnerAndType(...) (int64, error)
|
||
CountByOwnerAndTypeAndGrade(...) (int64, error)
|
||
CountByOwnerAndTypeAndCategory(...) (int64, error)
|
||
CountByOwnerAndTypeAndActivity(...) (int64, error)
|
||
UpdateLikeCount(assetID int64, likeCount int32) error
|
||
UpdateGrade(assetID int64, grade int32) error
|
||
Delete(assetID int64) error
|
||
DeleteByAssetType(assetType string, assetID int64) error
|
||
}
|
||
|
||
func NewAssetRegistryRepository(db *gorm.DB) AssetRegistryRepository { ... }
|
||
```
|
||
|
||
**Steps**:
|
||
- [ ] 确认 `backend/services/starbookService/` 完整目录清单:`ls -la backend/services/starbookService/`。
|
||
- [ ] **解耦步骤 A**——`backend/services/assetService/repository/asset_registry_repository.go`(新建):从 `backend/services/starbookService/repository/asset_registry_repository.go` 复制完整内容(接口 + 实现 + 388 行),改 `package repository`,删除跨服务 import(保留 `pkg/models` + `pkg/errors` + `gorm.io/gorm`)。
|
||
- [ ] **解耦步骤 B**——`backend/services/assetService/main.go:32`:删除 `starbookRepo "github.com/topfans/backend/services/starbookService/repository"`。
|
||
- [ ] **解耦步骤 C**——`backend/services/assetService/main.go:159`:把 `registryRepo := starbookRepo.NewAssetRegistryRepository(database.GetDB())` 改为 `registryRepo := repository.NewAssetRegistryRepository(database.GetDB())`。
|
||
- [ ] **解耦步骤 D**——`backend/services/assetService/service/mint_service.go:28,71,84`:删 `starbookRepo` import;类型改 `registryRepo repository.AssetRegistryRepository`;参数类型同步改。
|
||
- [ ] **解耦步骤 E**——`backend/services/assetService/service/asset_service.go:46-49`:当前 `RegistryRepository` 接口只声明 `GetByOwner`,无法满足 `mint_service.go` 需要(mint_service 实际不用 registryRepo 的其他方法,仅用 `registryRepo.Create` 等;先确认)。如 mint_service 不需要接口扩展则保持现状;如需则把 `RegistryRepository` 接口扩展为 asset 内部接口或引用新 `repository.AssetRegistryRepository`。
|
||
- [ ] 跑 `cd backend && go build ./services/assetService/...` —— 必须通过(说明解耦成功)。
|
||
- [ ] **删除步骤**——`git rm -r backend/services/starbookService/`。
|
||
- [ ] **删除 helm**——`git rm k8s/helm/topfans/templates/starbookservice/deployment.yaml`(若存在父目录则一并 `rmdir`)。
|
||
- [ ] 改 `backend/gateway/config/config.go`:删 `StarbookServiceURL` 字段(第 95、165 行),同步从 `cases` map 移除(在 port_test 中也对应删除)。
|
||
- [ ] 确认 `backend/go.work` 已不含 starbookService(grep `starbook` 应无结果)。
|
||
- [ ] 改 `backend/dev.sh`:
|
||
- 行 24:从服务循环数组移除 `starbookService`。
|
||
- 行 347、365:从 `inotifywait --exclude` 列表移除 `starbookService$`。
|
||
- 行 432、438:从 build/start 循环移除。
|
||
- 行 468:删除 `build_service "starbookService" ...` 函数调用。
|
||
- 行 493:删除 `start_service "starbookService" ...` 调用。
|
||
- 行 510:删除 `start_watcher "starbookService" ...` 调用。
|
||
- 行 530:删除打印行 `Starbook Service: tri://localhost:20007`。
|
||
- [ ] 改 `docker/Dockerfile.services:50`:删除 `go build -o /tmp/starbookservice services/starbookService/main.go` + `echo "Built starbookservice"` 两行。
|
||
- [ ] 改 `docker/docker-compose.local.yml`:删 `starbookservice:` service 块(行 231-262)+ `DUBBO_STARBOOK_SERVICE_URL: tri://starbookservice:20007` env(行 442)+ 任何 depends_on starbookservice 引用(行 482)。
|
||
- [ ] 改 `docker/docker-compose.prod.yml`:同 local,端口用 `20005`(生产错配)。
|
||
- [ ] 改 `k8s/helm/topfans/values.yaml`:删 `starbookservice:` 块(行 214-232)+ `gateway.env.DUBBO_STARBOOK_SERVICE_URL` 引用。
|
||
- [ ] 跑 `cd backend && go build ./...` —— 全部模块编译通过。
|
||
- [ ] 跑 `cd backend && go test ./gateway/config/... -run TestGatewayDubboPortsMatchServiceDefaults` —— 通过(已移除 starbook 用例)。
|
||
- [ ] 跑 `cd backend && go test ./...` —— 全部测试通过。
|
||
- [ ] 跑 `grep -rn 'starbook\|Starbook' backend docker k8s dev.sh` —— 仅剩 `backend/migrations/` 中可能的 starbook 业务表(如 `asset_registry`),不应再有任何 starbookService 部署引用。
|
||
- [ ] 准备 commit message:"refactor(services): 删除 starbookService 孤儿代码并内化 AssetRegistryRepository 到 assetService (批次 4.2)"—— **需用户批准后 commit**。
|
||
|
||
### Task 3: 健康探针修正 + 大二进制出库(4.3)
|
||
|
||
> 对应审计 §三 P1 探针/大二进制行 + 修复方案 4.3。
|
||
|
||
**Files**:
|
||
- `backend/pkg/health/health.go:28-29` —— 当前 `mux.HandleFunc("/health", ...)`;新增 `/healthz` alias 处理(统一两个路径,避免 helm values 长期不一致)
|
||
- `backend/services/notificationService/main.go` —— 探针端口 21010 已正确(健康服务端口 = Dubbo 端口 + 1000),无须改
|
||
- `backend/services/moderationService/main.go` —— 探针路径 `/` 改为 `/health`(让 helm values 也对齐)
|
||
- `k8s/helm/topfans/values.yaml:293,314` —— `notificationservice.healthPath: /health`(从 `/healthz` 改),`moderationservice.healthPath: /health`(从 `/` 改)
|
||
- `k8s/helm/topfans/templates/notificationservice/deployment.yaml:50,58` —— 探针 path 改 `/health`(无 `healthPath` 模板变量,可保留 default 但需对齐)
|
||
- `k8s/helm/topfans/templates/moderationservice/deployment.yaml:50,58` —— 探针 path 改 `/health`
|
||
- `docker/docker-compose.local.yml`、`docker/docker-compose.prod.yml` —— 任何 `HEALTHCHECK` 指令对齐 `/health`
|
||
- `backend/.gitignore` —— 追加 `backend/assetService`、`backend/gateway-fixed`、`backend/test`、`backend/cleanup-orphan-avatars`、`gateway-fixed`、`gateway/gateway-fixed` 等顶层可执行文件
|
||
- `backend/assetService`、`backend/gateway-fixed`、`backend/test`、`backend/cleanup-orphan-avatars` —— `git rm --cached`
|
||
|
||
**Interfaces**(`backend/pkg/health/health.go` 修改):
|
||
```go
|
||
// 在现有 mux.HandleFunc("/health", h.handleHealth) 后追加:
|
||
mux.HandleFunc("/healthz", h.handleHealth)
|
||
```
|
||
(K8s 探针仍可用 `/health`;保留 `/healthz` 兼容老 chart;不破坏既有探针。)
|
||
|
||
**Steps**:
|
||
- [ ] 读 `backend/pkg/health/health.go:27-44`,确认当前 mux 仅注册 `/health`。
|
||
- [ ] 改 `backend/pkg/health/health.go:29` 后追加 `mux.HandleFunc("/healthz", h.handleHealth)`。
|
||
- [ ] 跑 `cd backend && go build ./pkg/health/...` —— 通过。
|
||
- [ ] 改 `k8s/helm/topfans/values.yaml:293`:`healthPath: /health`(notificationservice);`k8s/helm/topfans/values.yaml:314`:`healthPath: /health`(moderationservice,从 `/` 改)。
|
||
- [ ] 改 `k8s/helm/topfans/templates/notificationservice/deployment.yaml:50,58`:把 `{{ $svc.healthPath | default "/healthz" }}` 改为 `{{ $svc.healthPath | default "/health" }}`(默认同步)。
|
||
- [ ] 改 `k8s/helm/topfans/templates/moderationservice/deployment.yaml:50,58`:把 `{{ $svc.healthPath | default "/" }}` 改为 `{{ $svc.healthPath | default "/health" }}`。
|
||
- [ ] 跑 `cd k8s/helm/topfans && helm template . --values values.yaml 2>&1 | grep -E 'healthz|path:'` —— 验证所有探针 path 为 `/health`。
|
||
- [ ] 改 `docker/docker-compose.local.yml`:搜 `HEALTHCHECK`,把任何 `curl http://localhost:2xxxx/healthz` 改为 `/health`。
|
||
- [ ] 改 `docker/docker-compose.prod.yml`:同上。
|
||
- [ ] 改 `backend/.gitignore`:在"Backend 构建产物"区块追加:
|
||
```
|
||
# 顶层历史二进制(本地构建残留,禁止入库)
|
||
/assetService
|
||
/gateway-fixed
|
||
/test
|
||
/cleanup-orphan-avatars
|
||
```
|
||
- [ ] 跑 `cd /Users/liulujian/Documents/code/TopFansByGithub && git rm --cached backend/assetService backend/gateway-fixed backend/test backend/cleanup-orphan-avatars`(若文件已被 .gitignore 覆盖,则先从暂存区移除)。
|
||
- [ ] 跑 `git ls-files | grep -E '^backend/(assetService|gateway-fixed|test|cleanup-orphan-avatars)$'` —— 应为空。
|
||
- [ ] 跑 `git status --ignored | grep -E '^backend/(assetService|gateway-fixed|test|cleanup-orphan-avatars)$'` —— 应列出(说明已忽略但本地保留)。
|
||
- [ ] 跑 `cd backend && go build ./... && go test ./...` —— 全部通过。
|
||
- [ ] 跑本地健康检查:`cd backend && (PORT=20010 ./services/notificationService/notificationService &) ; sleep 2 ; curl -s http://localhost:21010/health ; curl -s http://localhost:21010/healthz` —— 两个路径均返回 `{"status":"ok"}`。
|
||
- [ ] 准备 commit message:"fix(deploy): 健康探针路径统一为 /health + 顶层历史二进制出库 (批次 4.3)"—— **需用户批准后 commit**(建议拆为两个 commit:二进制出库 + 探针修正,分别走 `git add .gitignore` 与 `git add helm`)。
|
||
|
||
### Task 4: 序列脚本 setval(4.4)
|
||
|
||
> 对应审计 §三 P1 `create_gallery_test_users.go` 与修复方案 4.4。遵循 `CLAUDE.md` PostgreSQL 序列规范。
|
||
|
||
**Files**:
|
||
- `backend/scripts/create_gallery_test_users.go:59-152` —— 在每个手动 INSERT 表后追加 `SELECT setval(...)`
|
||
|
||
**Interfaces**(无新接口;纯输出增强):
|
||
```go
|
||
// 输出 SQL 末尾追加序列重置;当前 main 函数应收集受影响表名集合,结尾统一输出。
|
||
var tablesToReset = []string{"users", "fan_profiles", "booth_slots", "assets", "exhibitions"}
|
||
for _, tbl := range tablesToReset {
|
||
fmt.Printf("SELECT setval('%s_id_seq', (SELECT MAX(id) FROM %s));\n", tbl, tbl)
|
||
}
|
||
```
|
||
|
||
**Steps**:
|
||
- [ ] 通读 `backend/scripts/create_gallery_test_users.go:1-170`,列出所有手动指定 id 的 INSERT 表:`users` (id=100/101)、`fan_profiles` (无 id,仅 `user_id, star_id` 复合键——若 BIGSERIAL 则无需)、`booth_slots` (slot_id=1001-1003, 2001-2003)、`assets` (id=1000-1003)、`exhibitions` (无 id)。
|
||
- [ ] 检查 `fan_profiles` 与 `exhibitions` 是否 BIGSERIAL(查 migrations:`grep -E 'CREATE TABLE (fan_profiles|exhibitions)' backend/migrations/*.sql`)。
|
||
- [ ] 改 `backend/scripts/create_gallery_test_users.go`:
|
||
- 在 `var` 区块追加 `tablesToReset := []string{"users", "booth_slots", "assets"}`(`fan_profiles` 与 `exhibitions` 如非 BIGSERIAL 则不计入)。
|
||
- 在 `main()` 末尾、所有 `fmt.Println` 之前,循环 `for _, tbl := range tablesToReset { fmt.Printf("SELECT setval('%s_id_seq', (SELECT MAX(id) FROM %s));\n", tbl, tbl) }`。
|
||
- 在最后追加 `fmt.Println("-- 序列重置完成")`。
|
||
- [ ] 跑 `cd backend && go run scripts/create_gallery_test_users.go > /tmp/test_users.sql`,确认末尾包含 `SELECT setval('users_id_seq', ...)`、`SELECT setval('booth_slots_id_seq', ...)`、`SELECT setval('assets_id_seq', ...)` 三行。
|
||
- [ ] 验证迁移可行性:在测试库 `psql -f /tmp/test_users.sql`(用 docker `postgresql-database-1` 即可),确认不报 `duplicate key` 且三条 `setval` 执行成功。
|
||
- [ ] 跑 `cd backend && go build ./...` —— 通过。
|
||
- [ ] 跑 `cd backend && go vet ./scripts/...` —— 无 warning。
|
||
- [ ] 准备 commit message:"fix(scripts): create_gallery_test_users.go 输出 SQL 末尾补 setval 序列重置 (批次 4.4)"—— **需用户批准后 commit**。
|
||
|
||
### Task 5: .env ↔ .env.example 对齐(4.5)
|
||
|
||
> 对应审计 §三 P1 漂移与修复方案 4.5。`.env.example` 仅留占位符,`.env` 真值不进 git。
|
||
|
||
**Files**:
|
||
- `backend/.env.example` —— 增补缺失键、删除占位符中的真实密钥痕迹(`LTAI5t...`、`sk-cp-`、`sk-proj-`、`app-...` 等)
|
||
- `backend/.env` —— 实际环境值,本 Task 不入仓但需确保所有代码读取的键均有对应 `getEnv(...)` 默认值或 example
|
||
- `backend/gateway/config/env_audit_test.go` (新增) —— 断言 `.env` 解析后所有"必填"键非空(仅对非敏感键,如 `WS_AI_CHAT_PATH`、`LANDING_BASE_URL`)
|
||
|
||
**Interfaces**(无新接口;测试骨架):
|
||
```go
|
||
// backend/gateway/config/env_audit_test.go
|
||
package config
|
||
|
||
import (
|
||
"os"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// TestEnvExampleDrift 断言 .env.example 含全部 .env 中已文档化的键(粗粒度:按 KEY= 前缀)
|
||
func TestEnvExampleDrift(t *testing.T) {
|
||
exampleBytes, err := os.ReadFile("../../.env.example")
|
||
if err != nil { t.Fatal(err) }
|
||
envBytes, err := os.ReadFile("../../.env")
|
||
if err != nil { t.Fatal(err) }
|
||
exampleKeys := parseEnvKeys(string(exampleBytes))
|
||
envKeys := parseEnvKeys(string(envBytes))
|
||
// 反向断言:.env 中有的键 .env.example 必出现(除 SECRET_KEY/JWT_SECRET/真实 key)
|
||
for k := range envKeys {
|
||
if isSecretKey(k) || isRealAPIKey(k) { continue }
|
||
if _, ok := exampleKeys[k]; !ok {
|
||
t.Errorf(".env 有键 %q 但 .env.example 缺失", k)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Steps**:
|
||
- [ ] 跑 `diff <(grep -E '^[A-Z_]+=' backend/.env | cut -d= -f1 | sort) <(grep -E '^[A-Z_]+=' backend/.env.example | cut -d= -f1 | sort)` —— 列出 .env 独有键(应在 .env.example 补)和 .env.example 独有键(可能是模板占位/可保留)。
|
||
- [ ] 列出 `.env` 独有键(实际环境值,非 secret):`WS_AI_CHAT_PATH`、`LASER_COMPOSITOR_URL`、`COMPOSITOR_PORT`、`LASER_GEN_PROVIDER`、`MINIMAX_API_KEY`、`MINIMAX_API_URL`、`OPENAI_API_KEY`、`OPENAI_BASE_URL`、`OPENAI_MODEL`、`DIFY_API_BASE`、`DIFY_API_KEY`、`JWT_SECRET`、`ENV`、`LOG_LEVEL`、`OSS_REGION`、`OSS_BUCKET_NAME`、`OSS_STS_ROLE_ARN`、`OSS_ACCESS_KEY_ID`、`OSS_ACCESS_KEY_SECRET`、`OSS_AVATAR_DIR`、`OSS_ASSET_DIR`、`OSS_TOKEN_EXPIRE_TIME`、`REDIS_HOST`、`REDIS_PORT`、`REDIS_PASSWORD`、`REDIS_DB`、`DB_HOST`、`DB_PORT`、`DB_USER`、`DB_PASSWORD`、`DB_NAME`、`GIN_MODE`、`SERVER_PORT`、`SECRET_KEY`。
|
||
- [ ] 列出 `.env.example` 独有键(当前文档化但 .env 未用):`LANDING_BASE_URL`、`PUSH_ENABLED`、`PUSH_URL`、`PUSH_TIMEOUT_MS`、`DIFY_TIMEOUT_SEC`、`DIFY_WORKFLOW`、`SEGMENT_PROVIDER`、`SEGMENT_INFERENCE_URL`、`DUBBO_USER_SERVICE_URL` 至 `DUBBO_STARBOOK_SERVICE_URL`(4.2 后应删 Starbook)。
|
||
- [ ] 改 `backend/.env.example`:
|
||
- 行 87:取消注释 `WS_AI_CHAT_PATH=/ai-chat`(去掉 `# ` 前缀)。
|
||
- 行 121-123:保留 `PUSH_ENABLED/PUSH_URL/PUSH_TIMEOUT_MS`,但 `PUSH_URL=` 留空占位符。
|
||
- 行 124-128:删除 `OPENAI_*` 三行(真实密钥痕迹),改为 `# OPENAI_* 由 LASER_GEN_PROVIDER=openai 启用时填入`。
|
||
- 行 131-133:删除 `DIFY_API_KEY=app-aHnBfMeOQp7A9dQneIFPdPaZ`(Dify 真实密钥),改为 `DIFY_API_KEY=`。
|
||
- 行 106:删除 `OPENAI_API_KEY=sk-proj-...` 真实值,改为 `OPENAI_API_KEY=`。
|
||
- 增补 `LANDING_BASE_URL=http://localhost:5173`(已在最顶部,确认);增补 `LASER_COMPOSITOR_URL=http://127.0.0.1:7002`、`LASER_GEN_PROVIDER=openai`;增补 `MINIMAX_API_KEY=` 与 `MINIMAX_API_URL=https://api.minimaxi.com/v1/image_generation`(已有,确认)。
|
||
- 增补 `SEGMENT_*`(已有 73-74 行,确认)。
|
||
- 增补 `DIFY_TIMEOUT_SEC=60`(已有 133 行,确认)。
|
||
- [ ] 在 `.env.example` 顶部加注释:
|
||
```
|
||
# .env.example 仅作键清单 + 占位符。真实值由部署环境注入(docker env_file / k8s Secret / systemd EnvironmentFile)。
|
||
# 严禁把真实 key 写入本文件;本文件应可直接 commit。
|
||
```
|
||
- [ ] 创建 `backend/gateway/config/env_audit_test.go`(见上面骨架),实现 `parseEnvKeys`(按行解析 `^[A-Z_]+=`)、`isSecretKey`、`isRealAPIKey`(白名单:`SECRET_KEY/JWT_SECRET/PASSWORD/*_KEY/*_SECRET/TOKEN`)。
|
||
- [ ] 跑 `cd backend && go test ./gateway/config/... -run TestEnvExampleDrift -v` —— 通过。
|
||
- [ ] 确认 `git check-ignore backend/.env` —— 返回 0(已忽略);`git ls-files backend/.env` —— 应为空。
|
||
- [ ] 确认 `git ls-files backend/.env.example` —— 有文件。
|
||
- [ ] 跑 `cd backend && go build ./... && go test ./...` —— 全部通过。
|
||
- [ ] 准备 commit message:"fix(env): 同步 .env.example 与 .env 键清单并清理示例文件中的真实密钥痕迹 (批次 4.5)"—— **需用户批准后 commit**。
|
||
|
||
### Task 6: P2 清理(4.9)
|
||
|
||
> 对应审计 §四 P2 八项。所有改动均小而独立,按表格逐项修。
|
||
|
||
**Files & Actions**(按文件:行 + 改动说明):
|
||
|
||
| P2 项 | 文件:行 | 改动 |
|
||
|-------|---------|------|
|
||
| `parseRPCError` 字符串解析 | `backend/gateway/controller/asset_controller.go:158-194` | 改用 `google.golang.org/grpc/status` 的 `status.FromError(err)`;删除正则解析。返回结构 `{code: int, message: string}`,调 7 处 `parseRPCError(err)` 调用(行 240/286/365/492/568/700/765/827)保持签名不变 |
|
||
| 原始 err 泄露前端 | `backend/gateway/controller/auth_controller.go:224,294,306`、`user_controller.go:141,247,498` | 把 `c.JSON(500, gin.H{"message": err.Error()})` 改为 `response.InternalError(c)` 或 `response.BadRequest(c, "操作失败")` + `logger.Logger.Error("...", zap.Error(err))` 服务端日志保留 err |
|
||
| `ResetPassword` 不失效 JWT | `backend/services/userService/service/user_service.go:681` | 现状已在事务内把 `access_token` 置 nil + `token_expires_at` 置 nil。补:`gateway/middleware/auth_middleware.go` 在解析 JWT 后**额外**比对 `users.access_token`(如已为 nil 则 401)。如工期紧张,至少在 `user_service.go:681` 注释里写明 "JWT 失效留待 ticket #XX 处理;当前实现只阻止后续发新 token 复用"——选择最小改动(仅注释 + ticket 链接)以避免越界改动 middleware |
|
||
| `UpdateAvatar` URL 不校验同源 | `backend/services/userService/service/user_service.go:1023` | 在写库前增加同源校验:`u, err := url.Parse(req.AvatarUrl)`;`if u.Host != "" && !strings.Contains(u.Host, "aliyuncs.com") { return nil, ErrInvalidAvatarURL }`;白名单仅 `aliyuncs.com` 子域 |
|
||
| PII 进 INFO 日志 | `backend/services/aiChatService/provider/ai_chat_provider.go:107`、`mint_service.go`、`user_service.go` 多处 | 把 `user_id`/`session_id` 之外不要打印 `message`/`mobile`;或改用 `logger.Logger.Debug(...)`;`mobile` 用 `mobile[:3] + "****" + mobile[7:]` 脱敏 |
|
||
| pkg/mq 死代码 | `backend/pkg/mq/streams/adapter.go`(`EventProducer`/`Stream*` 常量)、`backend/pkg/mq/asynq/adapter.go:117`(`Delete` 假实现) | 与批次 3.3 一并决断(用户已选"停用 streams adapter"则本 Task 把 `streams/` 整个子包删除、`mq.go` 的 `MQDriver = Streams` 分支删除);asynq `Delete` 暂保留(实际不调用),注释"未接线" |
|
||
| `is_processed` 复用为 settled | `backend/services/galleryService/mq/consumer.go:207` | **由批次 1.1(`exhibition-settlement-idempotency` plan Task 3)负责** `settled_at` 列 + `isSettled/markSettled` 改写。本 plan 不重复编辑此函数,仅在回归时校验其已切换。 |
|
||
| 直连 Redis Pub/Sub | `backend/services/activityService/service/activity_service.go:217,1579` | 两处 `s.redisClient.Publish(...)` 改为走 `adapter.EventProducer.Publish(ctx, "activity.contributions", payload)`(若 pkg/mq 已确定保留,否则本项 defer 到 3.3) |
|
||
| 周边密钥兜底 | `backend/pkg/peripheral/sign.go:33-44` | 去掉 `JWT_SECRET` 兜底与硬编码 `"default-dev-secret-change-me"`;`SECRET_KEY == ""` 时 `panic("SECRET_KEY is required")`(fail-fast) |
|
||
|
||
**Interfaces**(新增/修改):
|
||
```go
|
||
// backend/gateway/controller/asset_controller.go (修改后)
|
||
import "google.golang.org/grpc/status"
|
||
|
||
func parseRPCError(err error) (code int, message string) {
|
||
if err == nil { return http.StatusOK, "" }
|
||
if st, ok := status.FromError(err); ok {
|
||
// grpc status code → HTTP code 映射(参考 grpc-gateway 约定)
|
||
return grpcCodeToHTTP(st.Code()), st.Message()
|
||
}
|
||
return http.StatusInternalServerError, "服务暂时不可用"
|
||
}
|
||
|
||
func grpcCodeToHTTP(c codes.Code) int {
|
||
switch c {
|
||
case codes.NotFound: return http.StatusNotFound
|
||
case codes.InvalidArgument: return http.StatusBadRequest
|
||
case codes.Unauthenticated: return http.StatusUnauthorized
|
||
case codes.PermissionDenied: return http.StatusForbidden
|
||
case codes.AlreadyExists: return http.StatusConflict
|
||
default: return http.StatusInternalServerError
|
||
}
|
||
}
|
||
```
|
||
|
||
```go
|
||
// backend/pkg/peripheral/sign.go (修改后)
|
||
func getSecret() string {
|
||
secretOnce.Do(func() {
|
||
secretVal = os.Getenv("SECRET_KEY")
|
||
if secretVal == "" {
|
||
panic("SECRET_KEY is required for peripheral HMAC signing")
|
||
}
|
||
})
|
||
return secretVal
|
||
}
|
||
```
|
||
|
||
**Steps**(按表格逐项):
|
||
- [ ] **A. parseRPCError**:改 `backend/gateway/controller/asset_controller.go:158-194` 为 `status.FromError` 形式。7 处调用点(行 240/286/365/492/568/700/765/827)签名不变,无须改。跑 `cd backend && go build ./gateway/...` 通过。
|
||
- [ ] **B. err 泄露**:改 `auth_controller.go:224,294,306` + `user_controller.go:141,247,498` 五处,把 `err.Error()` 移到 `logger.Logger.Error("...", zap.Error(err))`,响应改为稳定文案(如 `response.InternalError(c, "操作失败")`)。grep 验证 `c.JSON(500, gin.H{"message": err.Error()})` 仅剩 0 处。
|
||
- [ ] **C. ResetPassword JWT**:决策点——读 `backend/gateway/middleware/auth_middleware.go` 当前是否校验 `users.access_token`;若否,最小改动为更新 `user_service.go:681` 注释(添加 ticket 链接),不修改 middleware;若已校验,本项完成。`git grep 'access_token' backend/gateway/middleware/`。
|
||
- [ ] **D. UpdateAvatar 同源**:改 `user_service.go:1023` 加 `url.Parse` + `aliyuncs.com` 白名单;`pkg/errors` 增加 `ErrInvalidAvatarURL`。
|
||
- [ ] **E. PII 日志**:改 `ai_chat_provider.go:107` 不打 `message`;`user_service.go` 中 `mobile` 打印用脱敏函数。grep `logger.Logger.Info.*mobile.*req` / `zap.String("message", message)` 全面排查。
|
||
- [ ] **F. pkg/mq 死代码**:读 `backend/pkg/mq/mq.go` 看 `MQDriver = Streams` 分支是否被引用。若无引用,删除 `backend/pkg/mq/streams/` 子包 + `mq.go` 中的 streams init 分支;`asynq/adapter.go:117` 的 `Delete` 假实现加注释 `// TODO: 实际未调用,asynq.DeletedTaskInfo 返回 false 即视为不存在`。
|
||
- [ ] **G. `is_processed` 复用**:**不在本 plan 编辑**。`settled_at` 列 + `isSettled/markSettled` 改写归批次 1.1(`exhibition-settlement-idempotency` plan Task 1 migration + Task 3)。本 plan 仅校验:`grep -n 'settled_at' backend/services/galleryService/mq/consumer.go` 确认已切换;未切换则回到批次 1.1 执行。
|
||
- [ ] **H. Redis Pub/Sub 直连**:决策点——读 `backend/pkg/mq/mq.go` 的 `EventProducer` 接口是否已稳定;若稳定,改 `activity_service.go:217,1579` 为 `mq.GetEventProducer().Publish(ctx, "activity.contributions", payload)`;否则本项 defer 到 3.3。
|
||
- [ ] **I. 周边密钥 fail-fast**:改 `pkg/peripheral/sign.go:33-44`,去掉 JWT_SECRET 兜底与硬编码默认值。跑 `cd backend && SECRET_KEY= go test ./pkg/peripheral/...` —— 必须 panic 或返回明确错误(不能静默用 dev 默认值)。
|
||
- [ ] 跑 `cd backend && go build ./... && go test ./...` —— 全部通过。
|
||
- [ ] 跑 `cd backend && go vet ./...` —— 无 warning。
|
||
- [ ] 跑 `cd backend && grep -rnE 'parseRPCError.*errStr\|err\.Error\(\)\|default-dev-secret\|JWT_SECRET.*secretVal' .` —— 仅 0 处(除新增注释)。
|
||
- [ ] 准备 commit message(建议拆 3 个):"refactor(gateway): parseRPCError 改用 grpc/status.FromError (批次 4.9-A)" + "fix(gateway): 修复 5 处原始 err 泄露前端 (批次 4.9-B)" + "fix(peripheral): SECRET_KEY 缺失 fail-fast,去 JWT_SECRET/dev 默认兜底 (批次 4.9-I)"—— **每个 commit 需用户批准**。
|
||
|
||
---
|
||
|
||
## Self-Review
|
||
|
||
> 严格按 `CLAUDE.md` 全局自审规则:通读本文 §1→§5,核对跨章节引用、Go 编译预期、副作用、commit 边界。
|
||
|
||
### 修改的章节
|
||
- §File Structure(任务清单)
|
||
- §Task 1–6(步骤、文件、接口)
|
||
|
||
### 未改动但通读确认的章节
|
||
- §Goal / §Architecture / §Tech Stack —— 与原方案对齐,无改动需求。
|
||
- §Global Constraints —— 与审计 §五 修复顺序、批次 4 路线图一致;序列规则、commit 守则与 `CLAUDE.md` 一致。
|
||
|
||
### 跨章节引用一致性检查
|
||
- **端口矩阵(4.1 ↔ §File Structure 表)**:4.1 列出 11 个服务端口,§File Structure 表也列 11 个服务端口——一致。
|
||
- **starbookService 删除(4.2 ↔ 4.1 端口断言)**:4.1 `serviceDefaultPorts` 与 `cases` map 必须不含 starbook——本计划已明确在 4.2 完成后删除 `StarbookServiceURL` 字段并同步 port_test 的 `cases` map。
|
||
- **健康探针(4.3 ↔ helm values)**:4.3 改 `notificationservice.healthPath` 从 `/healthz` → `/health`,与 `backend/pkg/health/health.go:28-29` 注册 `/health` 一致——OK。
|
||
- **大二进制(4.3 ↔ §Global Constraints 不动产物)**:`backend/bin/*` 与 `.gitignore` 第 10 行 `bin/` 仍受保护;本计划只追加根级四条二进制忽略——一致。
|
||
- **序列脚本(4.4 ↔ §Global Constraints)**:遵循 `CLAUDE.md` 强制规则——一致。
|
||
- **.env 漂移(4.5 ↔ .gitignore)**:`.env` 已在 `.gitignore` 第 21 行忽略;本计划仅改 `.env.example`——一致。
|
||
- **P2 清理(4.9 ↔ 批次 3.3 决断)**:H/F 两项依赖批次 3.3 MQ 决断结论——本计划用"决策点"标注,未预设结论。
|
||
- **commit 边界(每 Task ↔ §Global Constraints)**:每个 Task 末尾标注"需用户批准后 commit",未授权自提交——一致。
|
||
|
||
### Go 编译验证预期(必须能在本地真实跑通)
|
||
|
||
| 步骤 | 命令 | 预期 |
|
||
|------|------|------|
|
||
| 端口测试 | `cd backend && go test ./gateway/config/... -run TestGatewayDubboPortsMatchServiceDefaults -v` | PASS |
|
||
| env 审计 | `cd backend && go test ./gateway/config/... -run TestEnvExampleDrift -v` | PASS |
|
||
| 全量编译 | `cd backend && go build ./...` | exit 0 |
|
||
| 全量测试 | `cd backend && go test ./...` | exit 0 |
|
||
| vet | `cd backend && go vet ./...` | exit 0 |
|
||
| helm lint | `cd k8s/helm/topfans && helm lint .` | 0 errors |
|
||
| 大二进制 | `git ls-files \| grep -E '^backend/(assetService\|gateway-fixed\|test\|cleanup-orphan-avatars)$'` | empty |
|
||
| starbook 删除 | `find backend -path '*starbook*' -not -path '*/node_modules/*'` | 仅 `backend/migrations/` 中 starbook 业务表(如有),无 `starbookService/` 目录 |
|
||
|
||
### 优先级分类
|
||
|
||
- **P0(先做,挡构建)**:4.2 starbookService 删除(当前 `go build ./services/assetService/...` 失败)+ 4.1 端口对齐(影响所有下游服务)。
|
||
- **P1(同期)**:4.3 探针 + 大二进制 + 4.4 setval + 4.5 env。
|
||
- **P2(顺手)**:4.9 八项;其中 4.9-I 周边密钥 fail-fast 涉及安全边界,应排在 4.9 内靠前。
|
||
|
||
### 失败模式预演
|
||
|
||
- 若 4.2 完成后 `go build ./...` 仍报 starbook 引用:检查 `assetService/service/mint_service.go` 是否还有 `starbookRepo` import 残留(grep 验证)。
|
||
- 若 4.1 port_test 报某服务端口不匹配:回到该服务 `main.go` 顶部 `var port = flag.Int(...)` 默认值核对,更新 `serviceDefaultPorts` map 与默认值。
|
||
- 若 4.3 helm template 探针 path 仍为 `/healthz`:检查 `_helpers.tpl` 或 deployment.yaml `default` 值是否覆盖——可能需硬改两处。
|
||
- 若 4.4 setval 报 `relation "xxx_id_seq" does not exist`:该表非 BIGSERIAL,从 `tablesToReset` 移除。
|
||
- 若 4.5 env 审计失败:逐一对照 `isSecretKey`/`isRealAPIKey` 白名单,确认是否把真 secret 误识别为 drift。
|
||
|
||
### 跨任务依赖(按执行顺序)
|
||
|
||
```
|
||
4.1 ──┐
|
||
├──▶ 4.6(提交后整体回归)
|
||
4.2 ──┤
|
||
│
|
||
4.3 ──┤
|
||
│
|
||
4.4 ──┤
|
||
│
|
||
4.5 ──┤
|
||
│
|
||
4.9 ──┘
|
||
```
|
||
|
||
4.1 与 4.2 互不阻塞(4.2 内 assetService 解耦独立完成)。4.3/4.4/4.5/4.9 与前两者可并行。4.9 内部分项按"决策点"标注——若用户已决断批次 3.3,则 F/H 可立即执行;否则 defer。 |