Files
junhong_cmp_fiber/.sisyphus/plans/realname-trigger-priority-enhancement.md
huang 7308afe801
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m18s
归档
2026-04-13 15:03:02 +08:00

692 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# realname-trigger-priority-enhancement 执行计划
## TL;DR
> **快速摘要**修复手动触发去重TTL不对齐、日限制过低两个Bug并在C端实名链接接口中异步触发优先级检查减少用户等待时间。
>
> **交付物**
> - `pkg/config/` 新增 PollingAutoTriggerConfig 配置结构
> - `internal/service/polling/manual_trigger_service.go` 修复3处日限制(100→500)、2处TTL(1h→24h)、提取权限检查公共函数、增强错误日志
> - `internal/handler/app/client_realname.go` 新增异步触发逻辑含2个辅助方法提取
> - `internal/bootstrap/handlers.go`、`cmd/api/docs.go`、`cmd/gendocs/main.go` 依赖注入接线
> - `pkg/constants/redis.go` 注释同步更新
>
> **预计工作量**60-90 分钟
> **关键路径**:配置 → ManualTriggerService修复 → Handler集成 → 依赖注入接线 → 手动验证
---
## 上下文
### 原始需求
用户在C端主动获取实名跳转链接后需等待轮询系统定时检查30秒-5分钟间隔才能检测到实名状态变化。同时手动触发功能存在两个Bug去重key TTL1小时与日限制周期24小时不对齐以及日限制次数100次过低导致正常使用受限。
### 代码库现状(已验证)
| 项目 | 状态 |
|------|------|
| `constants.TaskTypePollingRealname` | ✅ 已存在:`"polling:realname"`constants.go:55 |
| `svc.PollingManualTrigger` in bootstrap | ✅ 已初始化services.go:210 |
| 日限制 `>= 100` 位置 | **3处**manual_trigger_service.go:58, 129, 238 |
| `Expire(..., time.Hour)` 位置 | **2处**manual_trigger_service.go:72TriggerSingle和 :192processBatchTrigger |
| `NewClientRealnameHandler` 当前参数数 | 7个 → 改后8个 |
| docs.go / gendocs/main.go 调用处 | 各1处7个nil → 8个nil |
| `GetRealnameLink` 当前行数 | **127行**已超100行限制需提取辅助方法 |
| 系统用户 | 暂用 SuperAdmin ID=1使用 UserTypePlatform 身份 |
### 关键设计决策
1. **goroutine 异步触发**:使用 `context.WithTimeout(context.Background(), 3s)` 独立 context**禁止**复用 `c.UserContext()`Fiber 请求 context 在 handler 返回后失效)
2. **系统用户身份**`UserTypePlatform`(非 UserTypeSuperAdmin既绕过卡归属权限检查又仍受500次/天限制约束
3. **goroutine 参数**只传标量值uint、string**绝对不捕获 `c *fiber.Ctx` 或任何指针**
4. **GetRealnameLink 行数**当前127行已超限需在 Task 4 中额外提取两个 helper已批准
5. **processBatchTrigger TTL**:两处都修(已确认)
6. **Commit 3 临时编译失败**Task 4 修改构造函数后、Task 5 接线前,全量编译会报错,只做文件级 LSP 检查
---
## Phase 0 — 现状分析
### Task 1.1 — LSP 基线诊断
**操作**:对以下两个文件运行 `lsp_diagnostics`
- `internal/handler/app/client_realname.go`
- `internal/service/polling/manual_trigger_service.go`
**目的**:建立干净基线,区分预存问题与本次引入的问题
**验收**:记录所有预存 warning确认无预存 error
---
### Task 1.2 — 依赖关系确认
**操作**
- 确认 `handler/app → service/polling` 无循环依赖polling service 不导入 handler 包)
- 确认 `internal/bootstrap/handlers.go` 已有 `pollingSvcPkg` import alias
**验收**:无循环依赖,`svc.PollingManualTrigger` 在 bootstrap 中可访问
---
## Phase 1 — 配置管理
### Task 2.1 — 新增配置结构体和字段
**文件**`pkg/config/config.go`
新增结构体(放在其他 Config 结构体附近):
```go
// PollingAutoTriggerConfig 轮询自动触发配置
type PollingAutoTriggerConfig struct {
// EnableAutoTrigger 是否启用C端实名自动触发
// 环境变量JUNHONG_POLLING_AUTO_TRIGGER_ENABLE_AUTO_TRIGGER
EnableAutoTrigger bool `mapstructure:"enable_auto_trigger"`
// AutoTriggerSystemUserID 自动触发使用的系统用户ID暂用 SuperAdmin ID=1
// 环境变量JUNHONG_POLLING_AUTO_TRIGGER_AUTO_TRIGGER_SYSTEM_USER_ID
AutoTriggerSystemUserID int `mapstructure:"auto_trigger_system_user_id"`
}
```
`Config` struct 中添加字段位置Gateway 字段之后):
```go
PollingAutoTrigger PollingAutoTriggerConfig `mapstructure:"polling_auto_trigger"`
```
**陷阱**:不要在 `Validate()` 中添加校验——零值false/0是合法默认值
**验收**结构体定义正确mapstructure tag 与 yaml key 对应
---
### Task 2.2 — 更新默认配置 YAML
**文件**`pkg/config/defaults/config.yaml`
在文件末尾添加(保留尾部换行):
```yaml
# 轮询自动触发配置
polling_auto_trigger:
enable_auto_trigger: true
auto_trigger_system_user_id: 1 # 暂用 SuperAdmin生产环境建议创建专用平台账号并更新此值
```
对应环境变量:
- `JUNHONG_POLLING_AUTO_TRIGGER_ENABLE_AUTO_TRIGGER`
- `JUNHONG_POLLING_AUTO_TRIGGER_AUTO_TRIGGER_SYSTEM_USER_ID`
**验收**config.Get().PollingAutoTrigger.EnableAutoTrigger 默认为 true
---
### Task 2.3 — LSP 检查配置
**操作**`lsp_diagnostics``pkg/config/config.go`
**验收**0 error
---
## Phase 2 — 手动触发服务 Bug 修复
### Task 3.1 — 日限制 100 → 5003处
**文件**`internal/service/polling/manual_trigger_service.go`
| 行号 | 修改内容 |
|------|---------|
| 58 | `todayCount >= 100``todayCount >= 500`,同步更新行内注释 `// 每日最多触发100次``// 每日最多触发500次` |
| 129 | `todayCount >= 100``todayCount >= 500`,补充注释 `// 每日最多触发500次` |
| 238 | `todayCount >= 100``todayCount >= 500`,补充注释 `// 每日最多触发500次` |
**验收**`grep -n "100" manual_trigger_service.go` 无日限制相关残留
---
### Task 3.2 — 去重 TTL 1h → 24h2处
**文件**`internal/service/polling/manual_trigger_service.go`
| 行号 | 修改内容 |
|------|---------|
| 71-72 | 注释改为 `// 设置去重 key 过期时间24小时与日限制周期对齐``time.Hour``24*time.Hour` |
| 191-192 | 同上processBatchTrigger 中,存在相同 Bug一并修复 |
**验收**`grep -n "time.Hour" manual_trigger_service.go` 无残留
---
### Task 3.3 — 同步 Redis 常量注释
**文件**`pkg/constants/redis.go`
找到 `RedisPollingManualDedupeKey` 函数注释,将:
```
// 过期时间1小时
```
改为:
```
// 过期时间24小时与日限制周期对齐
```
**验收**:注释与实际 TTL 一致
---
### Task 3.4 — 提取公共权限检查函数
**文件**`internal/service/polling/manual_trigger_service.go`
`canManageCard``canManageCards``applyShopPermissionFilter` 三处提取重复的用户类型检查,新增私有方法:
```go
// checkUserTypePermission 检查用户类型是否有手动触发权限
// 返回 skip=true 表示超级管理员/平台用户直接放行
// 返回 err!=nil 表示企业账号无权限
// 返回 skip=false, err=nil 表示代理账号需继续细粒度检查
func (s *ManualTriggerService) checkUserTypePermission(ctx context.Context) (skip bool, err error) {
userType := middleware.GetUserTypeFromContext(ctx)
if userType == constants.UserTypeSuperAdmin || userType == constants.UserTypePlatform {
return true, nil
}
if userType == constants.UserTypeEnterprise {
return false, errors.New(errors.CodeForbidden, "企业账号无权限手动触发轮询")
}
return false, nil
}
```
三个调用方改为:
```go
skip, err := s.checkUserTypePermission(ctx)
if err != nil || skip {
return err // canManageCard 签名
}
```
**陷阱**`applyShopPermissionFilter` 中超管分支有 filter 对象逻辑,只提取类型检查部分,不删除 filter 修改逻辑
**验收**三个函数各减少约5行重复代码行为不变
---
### Task 3.5 — 改进 TriggerSingle 错误日志
**文件**`internal/service/polling/manual_trigger_service.go`
`TriggerSingle` 每条 `return err` 路径前补充结构化日志,字段要求:
| 错误场景 | 必须包含字段 |
|---------|------------|
| 查询今日触发次数失败 | `triggered_by``task_type``error` |
| Redis去重操作失败 | `card_id``task_type``error` |
| 创建触发日志失败 | `card_id``triggered_by``error` |
| 写入队列失败 | `card_id``task_type``error` |
**验收**:每条 return err 路径有对应日志,级别为 Error
---
### Task 3.6 — LSP 检查
**操作**`lsp_diagnostics``internal/service/polling/manual_trigger_service.go`
**验收**0 error0 新增 warning
---
### Task 3.7 — 手动验证去重 + 限制
**操作**(通过 Redis CLI 或 PostgreSQL MCP
```bash
# 1. 触发某张卡,验证 TTL 为 24小时
redis-cli TTL polling:manual:dedupe:polling:realname
# 期望:≈ 86400
# 2. 再次触发同一张卡,验证被拒绝
# 期望返回:"该卡已在手动触发队列中"
# 3. 删除 dedupe key 后重新触发,验证成功
redis-cli DEL polling:manual:dedupe:polling:realname
```
```sql
-- 验证今日触发次数计数正确
SELECT COUNT(*) as today_count
FROM tb_polling_manual_trigger_log
WHERE DATE(triggered_at) = CURRENT_DATE;
```
**验收**TTL ≈ 86400重复触发被拒绝DB 计数正确
---
## Phase 3 — Handler 集成
### Task 4.1 — 结构体新增字段
**文件**`internal/handler/app/client_realname.go`
添加 import注意区分 `internal/service/polling``internal/polling` 是不同包):
```go
pollingSvc "github.com/xxx/junhong_cmp_fiber/internal/service/polling"
```
`ClientRealnameHandler` 结构体新增字段:
```go
manualTriggerSvc *pollingSvc.ManualTriggerService // 手动触发服务可为nilnil时跳过自动触发
```
**验收**结构体字段添加正确import 路径正确
---
### Task 4.2 — 修改构造函数第8个参数
**文件**`internal/handler/app/client_realname.go`
`NewClientRealnameHandler` 末尾新增参数:
```go
func NewClientRealnameHandler(
// ...现有7个参数保持不变...
manualTriggerSvc *pollingSvc.ManualTriggerService, // 可为nil
) *ClientRealnameHandler {
return &ClientRealnameHandler{
// ...现有字段初始化...
manualTriggerSvc: manualTriggerSvc,
}
}
```
**陷阱**:修改后 `docs.go``gendocs/main.go``bootstrap/handlers.go` 会出现编译错误(构造函数参数不一致),待 Task 5 修复。此阶段**只做文件级** LSP 检查,不跑全量 build
**验收**`client_realname.go` 本身 LSP 0 error
---
### Task 4.3 — 提取辅助方法(解决 GetRealnameLink 行数超限问题)
**文件**`internal/handler/app/client_realname.go`
**背景**:当前 `GetRealnameLink` 127行加 goroutine 代码约133行违反 ≤100行规范。需提取两个私有方法。
**提取1`resolveTargetCard`**(资产类型 switch 分支约26行
```go
// resolveTargetCard 根据资产类型和ICCID定位目标卡
// 支持三条路径:直接卡资产、设备+指定ICCID、设备取第一张绑定卡
func (h *ClientRealnameHandler) resolveTargetCard(c *fiber.Ctx, asset *assetService.ResolvedAsset, iccid string) (*model.IotCard, error) {
switch {
case asset.AssetType == "card":
// ...
case asset.AssetType == "device" && iccid != "":
// ...
case asset.AssetType == "device":
// ...
default:
return nil, errors.New(errors.CodeInvalidParam, "不支持的资产类型")
}
}
```
**提取2`buildRealnameResponse`**(运营商 URL 调度约32行
```go
// buildRealnameResponse 根据运营商实名链接类型构建响应
func (h *ClientRealnameHandler) buildRealnameResponse(ctx context.Context, card *model.IotCard, carrier *model.Carrier) (*dto.RealnimeLinkResponse, error) {
// switch carrier.RealnameLinkType { ... }
}
```
提取后 `GetRealnameLink` ≈70行加 goroutine 后 ≈76行 ✅
**陷阱**
- `resolveTargetCard` 需要 `c *fiber.Ctx`(子函数调用需要 Fiber context
- `buildRealnameResponse``ctx context.Context`(只调 gatewayClient
- 确认 `assetService.ResolvedAsset` 的实际类型名称(读文件确认后再写)
**验收**`GetRealnameLink` 函数体 ≤100行两个新方法编译正常逻辑不变
---
### Task 4.4 — 添加异步触发调用
**文件**`internal/handler/app/client_realname.go`
添加 import`"github.com/xxx/junhong_cmp_fiber/pkg/config"`
`GetRealnameLink` 中,成功构建响应之后、`response.Success(c, resp)` 之前插入:
```go
// 异步触发实名检查,提升检测优先级;失败不影响主流程
if h.manualTriggerSvc != nil && config.Get().PollingAutoTrigger.EnableAutoTrigger {
systemUserID := uint(config.Get().PollingAutoTrigger.AutoTriggerSystemUserID)
go h.triggerRealnameCheck(targetCard.ID, customerID, targetCard.ICCID, systemUserID)
}
```
**强制约束**goroutine 只传标量值uint、string**绝对不捕获 `c *fiber.Ctx` 或任何指针类型**
**验收**goroutine 启动代码不捕获 Fiber context 相关变量
---
### Task 4.5 — 实现 triggerRealnameCheck 私有方法
**文件**`internal/handler/app/client_realname.go`
添加 imports`"context"``"time"``"github.com/xxx/junhong_cmp_fiber/pkg/middleware"`
```go
// triggerRealnameCheck 异步触发单卡实名检查
// 在独立 goroutine 中调用,使用独立 context 避免 Fiber 请求 context 失效问题
// 参数全部为值类型,不捕获请求相关指针
func (h *ClientRealnameHandler) triggerRealnameCheck(cardID, customerID uint, iccid string, systemUserID uint) {
// 必须使用独立 context禁止复用 Fiber 请求 context请求返回后即失效
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
// 使用平台用户身份构建 context绕过卡归属权限检查
// 注意:使用 UserTypePlatform 而非 UserTypeSuperAdminSuperAdmin 不受日限制约束
sysCtx := middleware.SetUserContext(ctx, &middleware.UserContextInfo{
UserID: systemUserID,
UserType: constants.UserTypePlatform,
})
err := h.manualTriggerSvc.TriggerSingle(sysCtx, cardID, constants.TaskTypePollingRealname, systemUserID)
if err != nil {
h.logger.Warn("自动触发实名检查失败",
zap.Uint("customer_id", customerID),
zap.String("iccid", iccid),
zap.Uint("card_id", cardID),
zap.Error(err))
return
}
h.logger.Info("自动触发实名检查成功",
zap.Uint("customer_id", customerID),
zap.String("iccid", iccid),
zap.Uint("card_id", cardID))
}
```
**验收**:方法 ≤30行使用 `context.Background()`context 注入 UserTypePlatform日志字段完整
---
### Task 4.6 — 日志字段核查
**检查清单**
- [x] WARN 路径包含:`customer_id``iccid``card_id``error`
- [x] INFO 路径包含:`customer_id``iccid``card_id`
- [x] 无 nil panic 风险err 已检查)
- [x] goroutine 无 panic 风险manualTriggerSvc 已在调用前检查非 nil
**验收**:所有字段齐全,无潜在 panic
---
### Task 4.7 — LSP 检查 Handler 文件
**操作**`lsp_diagnostics``internal/handler/app/client_realname.go`
**注意**:此时 `bootstrap/handlers.go``docs.go``gendocs/main.go` 仍有编译错误(待 Task 5只关注 `client_realname.go` 自身
**验收**`client_realname.go` 自身 0 error
---
## Phase 4 — 依赖注入接线
### Task 5.1 — 更新 bootstrap/handlers.go
**文件**`internal/bootstrap/handlers.go`
找到 `ClientRealname` 初始化行,在末尾补充 `svc.PollingManualTrigger` 参数:
```go
// 修改前7个参数
ClientRealname: app.NewClientRealnameHandler(
svc.Asset, personalCustomerDeviceStore, iotCardStore,
deviceSimBindingStore, carrierStore, deps.GatewayClient, deps.Logger,
),
// 修改后8个参数
ClientRealname: app.NewClientRealnameHandler(
svc.Asset, personalCustomerDeviceStore, iotCardStore,
deviceSimBindingStore, carrierStore, deps.GatewayClient, deps.Logger,
svc.PollingManualTrigger,
),
```
**验收**`svc.PollingManualTrigger` 类型匹配(已在 services.go:210 初始化)
---
### Task 5.2 — 确认 ManualTriggerService 初始化(只读)
**操作**:确认 `internal/bootstrap/services.go:210` 已有:
```go
PollingManualTrigger: pollingSvc.NewManualTriggerService(...)
```
**无需变更**,仅确认
**验收**`svc.PollingManualTrigger` 非 nil
---
### Task 5.3 — 更新 docs.go 和 gendocs/main.go
**文件**`cmd/api/docs.go``cmd/gendocs/main.go`
两处均将 `NewClientRealnameHandler` 调用从 7个nil 改为 8个nil
```go
// 修改前
handlers.ClientRealname = apphandler.NewClientRealnameHandler(nil, nil, nil, nil, nil, nil, nil)
// 修改后
handlers.ClientRealname = apphandler.NewClientRealnameHandler(nil, nil, nil, nil, nil, nil, nil, nil)
```
**验收**:两个文件均编译通过
---
### Task 5.4 — LSP 检查依赖注入相关文件
**操作**`lsp_diagnostics` 对以下3个文件
- `internal/bootstrap/handlers.go`
- `cmd/api/docs.go`
- `cmd/gendocs/main.go`
**验收**:全部 0 error
---
## Phase 5 — 手动验证
### Task 6.1 — 接口调用验证 goroutine 启动
```bash
curl -H "Authorization: Bearer <token>" \
"http://localhost:3000/api/c/v1/realname/link?identifier=<iccid>"
```
**查看 app.log** 确认出现:
```json
{"level":"info","msg":"自动触发实名检查成功","customer_id":...,"iccid":"...","card_id":...}
```
**验收**响应正常返回实名链接日志在响应后3秒内出现
---
### Task 6.2 — DB 验证触发记录
```sql
SELECT triggered_by, task_type, total_count, status, created_at
FROM tb_polling_manual_trigger_log
WHERE triggered_by = 1
ORDER BY id DESC LIMIT 5;
```
**期望**`triggered_by = 1`SuperAdmin`task_type = 'polling:realname'``total_count = 1`
**验收**:记录存在且字段正确
---
### Task 6.3 — Redis 队列验证
```bash
redis-cli LRANGE polling:manual:polling:realname 0 -1
```
**期望**:卡 ID 存在于队列中
**验收**:队列条目可见
---
### Task 6.4 — 日限制计数验证
```sql
SELECT COUNT(*) as today_count
FROM tb_polling_manual_trigger_log
WHERE triggered_by = 1
AND DATE(triggered_at) = CURRENT_DATE;
```
**期望**计数正确未超过500
**验收**:计数准确
---
### Task 6.5 — Redis 去重 TTL 验证
```bash
redis-cli TTL polling:manual:dedupe:polling:realname
```
**期望**:≈ 8640024小时
**验收**TTL 约等于 86400
---
### Task 6.6 — 功能开关验证
设置 `JUNHONG_POLLING_AUTO_TRIGGER_ENABLE_AUTO_TRIGGER=false` 后重启服务,调用接口:
**期望**正常返回实名链接app.log 无自动触发相关日志DB 无新增触发记录,无 panic
**验收**:功能开关有效,主流程不受影响
---
## Phase 6 — 最终验收
### Task 7.1 — 全文件 LSP 最终扫描
对所有修改过的文件运行 `lsp_diagnostics`
- `internal/handler/app/client_realname.go`
- `internal/service/polling/manual_trigger_service.go`
- `internal/bootstrap/handlers.go`
- `pkg/constants/redis.go`
- `pkg/config/config.go`
- `cmd/api/docs.go`
- `cmd/gendocs/main.go`
**验收**:全部 0 error0 新增 warning
---
### Task 7.2 — 环境变量文档记录
确认以下环境变量已在部署文档中记录:
| 环境变量 | 默认值 | 说明 |
|---------|-------|------|
| `JUNHONG_POLLING_AUTO_TRIGGER_ENABLE_AUTO_TRIGGER` | `true` | 自动触发开关,可临时关闭 |
| `JUNHONG_POLLING_AUTO_TRIGGER_AUTO_TRIGGER_SYSTEM_USER_ID` | `1` | 暂用 SuperAdmin。**生产环境建议创建专用平台账号user_type=Platform并更新此值** |
**验收**:文档中有上述两个环境变量的说明
---
### Task 7.3 — GetRealnameLink 行数确认
统计 `GetRealnameLink` 函数体行数(从 `func` 到最后一个 `}`
**验收**:≤ 100 行
---
## 原子提交策略
| 提交顺序 | 涵盖文件 | Commit Message |
|---------|---------|---------------|
| Commit 1 | `pkg/config/config.go``pkg/config/defaults/config.yaml` | `feat: 新增轮询自动触发配置EnableAutoTrigger、AutoTriggerSystemUserID` |
| Commit 2 | `internal/service/polling/manual_trigger_service.go``pkg/constants/redis.go` | `fix: 修复手动触发去重TTL和日限制次数优化权限检查和错误日志` |
| Commit 3 | `internal/handler/app/client_realname.go` | `feat: ClientRealnameHandler 集成异步触发实名检查` |
| Commit 4 | `internal/bootstrap/handlers.go``cmd/api/docs.go``cmd/gendocs/main.go` | `feat: 更新依赖注入,传入 ManualTriggerService` |
> ⚠️ Commit 3 之后、Commit 4 完成之前,全量编译会报错(构造函数参数不一致)。只做文件级 LSP 检查,不跑全量 build。
---
## 风险清单
| 风险 | 级别 | 对策 |
|------|------|------|
| goroutine 捕获 `c *fiber.Ctx` 导致 use-after-free | **致命** | `triggerRealnameCheck` 只接受标量参数uint、string |
| GetRealnameLink 超 100 行 | 高 | Task 4.3 提取两个 helper已批准 |
| processBatchTrigger TTL 漏修 | 中 | Task 3.2 明确修复两处(已确认) |
| SuperAdmin(ID=1) 与其他系统进程共享日限额 | 低 | 生产环境创建专用账号后更新配置 |
| Commit 3 后临时全量编译失败 | 预期行为 | 文件级 LSP 即可Task 5 完成后全量恢复 |
| `assetService.ResolvedAsset` 实际类型名称不确定 | 低 | Task 4.3 执行前先读文件确认类型名 |
---
## 提案 tasks.md 更新规则
**文件**`openspec/changes/realname-trigger-priority-enhancement/tasks.md`
每完成一个 Phase 后,必须将对应条目从 `- [ ]` 改为 `- [x]`。对应关系如下:
| 本计划 Phase/Task | 提案 tasks.md 条目 |
|-----------------|------------------|
| Task 1.1 完成 | `1.1 使用 lsp_diagnostics 检查...` |
| Task 1.2 完成 | `1.2 分析现有 ClientRealnameHandler...` |
| Task 2.1 完成 | `2.1 在 pkg/config/ 中新增...` |
| Task 2.2 完成 | `2.2 更新 pkg/config/defaults/config.yaml...` |
| Task 2.3 完成 | `2.3 运行 lsp_diagnostics 检查配置...` |
| Task 3.1 完成 | `3.1 ...三处将 todayCount >= 100 改为...` |
| Task 3.2 完成 | `3.2 ...将 s.redis.Expire(ctx, dedupeKey, time.Hour) 改为...` |
| Task 3.3 完成 | `3.3 同步更新 pkg/constants/redis.go...` |
| Task 3.4 完成 | `3.4 优化 canManageCard 和 canManageCards...` |
| Task 3.5 完成 | `3.5 改进 TriggerSingle 的错误日志...` |
| Task 3.6 完成 | `3.6 运行 lsp_diagnostics 检查 ManualTriggerService...` |
| Task 3.7 完成 | `3.7 使用 PostgreSQL MCP 和 Redis CLI 手动验证...` |
| Task 4.14.2 完成 | `4.1 在 ClientRealnameHandler 结构体中新增...` + `4.2 修改 NewClientRealnameHandler...` |
| Task 4.3 完成(提取 helper | — (本计划新增步骤,提案无对应条目) |
| Task 4.44.5 完成 | `4.3 在 GetRealnameLink 成功返回...` + `4.4 goroutine 内部必须使用...` + `4.5 goroutine 内部构造系统用户 context...` |
| Task 4.6 完成 | `4.6 goroutine 执行失败时记录 WARN 日志...` |
| Task 4.7 完成 | `4.7 运行 lsp_diagnostics 检查 ClientRealnameHandler...` |
| Task 5.1 完成 | `5.1 在 internal/bootstrap/handlers.go 中更新...` |
| Task 5.2 完成 | `5.2 确认 ManualTriggerService 在 bootstrap/services.go 中已初始化...` |
| Task 5.3 完成 | `5.3 更新 cmd/api/docs.go 和 cmd/gendocs/main.go...` |
| Task 5.4 完成 | `5.4 运行 lsp_diagnostics 检查 bootstrap 和 docs.go...` |
| Task 6.1 完成 | `6.1 使用 curl 调用...` |
| Task 6.2 完成 | `6.2 通过 PostgreSQL MCP 查询 tb_polling_manual_trigger_log...` |
| Task 6.3 完成 | `6.3 通过 Redis CLI 检查 polling:manual:realname 队列...` |
| Task 6.4 完成 | `6.4 使用 PostgreSQL MCP 查询今日触发次数...` |
| Task 6.5 完成 | `6.5 使用 Redis CLI 确认 dedup key TTL 为 24 小时...` |
| Task 6.6 完成 | `6.6 模拟 manualTriggerSvc 不可用场景...` |
| Task 7.1 完成 | `7.1 运行 lsp_diagnostics 对所有修改过的文件...` |
| Task 7.2 完成 | `7.2 确认环境变量...已在部署文档中记录` |
| Task 7.3 完成 | `7.3 确认 GetRealnameLink handler 函数长度不超过 100 行` |
**更新方式**:直接编辑 `tasks.md`,将对应行的 `- [ ]` 替换为 `- [x]`。每个 Phase 执行完毕后集中更新该 Phase 所有条目,不要跨 Phase 提前勾选。