This commit is contained in:
596
docs/polling-system/manual-trigger-analysis.md
Normal file
596
docs/polling-system/manual-trigger-analysis.md
Normal file
@@ -0,0 +1,596 @@
|
||||
# 轮询系统手动触发功能深入分析报告
|
||||
|
||||
## 执行摘要
|
||||
|
||||
轮询系统的手动触发功能包含 **884 行代码**(Service 469行 + Handler 307行 + Store 108行),实现了 **6 个 API 接口**,支持单卡、批量、条件筛选三种触发方式。
|
||||
|
||||
**核心发现**:
|
||||
- ✅ **功能完整性高**:支持多种触发方式、权限控制、进度追踪、任务取消
|
||||
- ⚠️ **使用价值存疑**:缺乏实际业务需求支撑,频次限制(每日100次、1000张卡)设置不合理
|
||||
- ❌ **过度设计风险**:复杂度与实际使用场景不匹配,维护成本高
|
||||
|
||||
---
|
||||
|
||||
## 1. 功能接口完整分析
|
||||
|
||||
### 1.1 API 接口清单
|
||||
|
||||
| 接口 | 方法 | 功能 | 代码行数 |
|
||||
|------|------|------|---------|
|
||||
| `/polling-manual-trigger/single` | POST | 单卡手动触发 | 18 |
|
||||
| `/polling-manual-trigger/batch` | POST | 批量手动触发(最多1000张) | 19 |
|
||||
| `/polling-manual-trigger/by-condition` | POST | 条件筛选触发 | 29 |
|
||||
| `/polling-manual-trigger/status` | GET | 获取触发状态 | 34 |
|
||||
| `/polling-manual-trigger/history` | GET | 获取触发历史 | 31 |
|
||||
| `/polling-manual-trigger/cancel` | POST | 取消触发任务 | 18 |
|
||||
|
||||
### 1.2 触发方式详解
|
||||
|
||||
#### 方式1:单卡触发(TriggerSingle)
|
||||
```go
|
||||
// 限制:每日100次
|
||||
// 流程:
|
||||
// 1. 验证任务类型(realname/carddata/package)
|
||||
// 2. 权限检查(企业账号禁止,代理只能管理自己的卡)
|
||||
// 3. 检查每日触发次数(≥100则拒绝)
|
||||
// 4. Redis Set 去重(1小时过期)
|
||||
// 5. 加入手动触发队列(List)
|
||||
// 6. 更新日志状态为 completed
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 单卡触发后立即标记为 completed,但实际处理是异步的
|
||||
- 去重 key 1小时过期,但日限制是按天计算,时间不对齐
|
||||
|
||||
#### 方式2:批量触发(TriggerBatch)
|
||||
```go
|
||||
// 限制:单次最多1000张卡,每日100次
|
||||
// 流程:
|
||||
// 1. 验证卡数量(>1000则拒绝)
|
||||
// 2. 权限检查(批量检查所有卡)
|
||||
// 3. 异步处理:
|
||||
// - 逐卡检查去重
|
||||
// - 加入队列
|
||||
// - 每100卡更新一次进度
|
||||
// 4. 最终更新状态为 completed
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 异步处理中的去重失败被计为 failedCount,但实际上是重复触发的防护
|
||||
- 没有考虑批量操作的原子性
|
||||
|
||||
#### 方式3:条件筛选触发(TriggerByCondition)
|
||||
```go
|
||||
// 限制:筛选结果最多1000张卡,每日100次
|
||||
// 支持筛选条件:
|
||||
// - card_status: 卡状态
|
||||
// - carrier_code: 运营商
|
||||
// - card_type: 卡类型
|
||||
// - shop_id: 店铺ID
|
||||
// - package_ids: 套餐ID列表
|
||||
// - enable_polling: 是否启用轮询
|
||||
// 流程:
|
||||
// 1. 权限过滤(代理只能筛选自己店铺的卡)
|
||||
// 2. 数据库查询符合条件的卡
|
||||
// 3. 异步批量处理(同方式2)
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 权限过滤逻辑:代理未指定 ShopID 时,自动限制为当前店铺(而非所有下级店铺)
|
||||
- 这与代理的权限模型不一致(代理应该能管理下级店铺的卡)
|
||||
|
||||
---
|
||||
|
||||
## 2. 业务需求支撑分析
|
||||
|
||||
### 2.1 需求来源
|
||||
|
||||
通过代码搜索,找到的需求记录:
|
||||
```
|
||||
./openspec/changes/archive/2026-02-10-polling-system-implementation/tasks.md
|
||||
- [x] 12.12 实现手动触发限流(单次限制1000张、每日限制100次)
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 没有找到需求文档(proposal.md 或 spec.md)
|
||||
- 没有业务场景说明
|
||||
- 没有使用频率预测
|
||||
|
||||
### 2.2 实际使用场景推测
|
||||
|
||||
根据代码注释和文档,推测的使用场景:
|
||||
|
||||
| 场景 | 频率 | 合理性 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| 故障恢复 | 低(<1次/天) | ✅ 高 | 轮询失败时手动重试 |
|
||||
| 数据修复 | 低(<1次/周) | ✅ 高 | 修复错误数据后重新检查 |
|
||||
| 性能测试 | 低(<1次/月) | ✅ 中 | 测试轮询系统性能 |
|
||||
| 日常运维 | 中(1-10次/天) | ⚠️ 中 | 运维人员定期检查 |
|
||||
| 业务需求 | 高(>100次/天) | ❌ 低 | 业务系统频繁触发 |
|
||||
|
||||
**结论**:
|
||||
- 如果是故障恢复/数据修复场景,每日100次限制过高(实际需求<10次)
|
||||
- 如果是业务系统频繁触发,每日100次限制过低(实际需求>1000次)
|
||||
- **频次限制设置不合理,说明需求定义不清**
|
||||
|
||||
### 2.3 与自动轮询的关系
|
||||
|
||||
```
|
||||
自动轮询流程:
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Scheduler(定时调度) │
|
||||
│ - 每秒从分片队列出队卡 │
|
||||
│ - 按配置间隔重新入队 │
|
||||
└──────────────┬──────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 手动触发队列(优先级高) │
|
||||
│ - List 结构(FIFO) │
|
||||
│ - 调度器优先消费 │
|
||||
└──────────────┬──────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Asynq 任务队列 │
|
||||
│ - 实际执行轮询任务 │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**关键发现**:
|
||||
- 手动触发队列是 **List**(FIFO),自动轮询队列是 **Sorted Set**(按时间戳排序)
|
||||
- 调度器优先消费手动触发队列(`processManualQueue` 在 `processScheduledQueue` 之前)
|
||||
- 手动触发卡会立即进入 Asynq 队列,不受轮询间隔限制
|
||||
|
||||
**必要性评估**:
|
||||
- ✅ **必要**:如果需要快速重试失败的卡
|
||||
- ❌ **不必要**:如果自动轮询已经足够频繁
|
||||
- ⚠️ **可替代**:可以通过调整轮询配置的间隔来实现相同效果
|
||||
|
||||
---
|
||||
|
||||
## 3. 频次限制合理性评估
|
||||
|
||||
### 3.1 限制规则
|
||||
|
||||
```go
|
||||
// 每日触发次数限制:100次
|
||||
if todayCount >= 100 {
|
||||
return errors.New(errors.CodeInvalidParam, "已达到每日触发次数上限")
|
||||
}
|
||||
|
||||
// 单次卡数限制:1000张
|
||||
if len(cardIDs) > 1000 {
|
||||
return errors.New(errors.CodeInvalidParam, "单次最多触发1000张卡")
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 限制的问题
|
||||
|
||||
#### 问题1:每日100次限制不合理
|
||||
|
||||
**场景分析**:
|
||||
```
|
||||
场景A:故障恢复
|
||||
- 实际需求:1-5次/天
|
||||
- 限制:100次/天
|
||||
- 评价:过高,浪费
|
||||
|
||||
场景B:数据修复
|
||||
- 实际需求:<1次/周
|
||||
- 限制:100次/天
|
||||
- 评价:过高,浪费
|
||||
|
||||
场景C:业务系统集成
|
||||
- 实际需求:可能>100次/天
|
||||
- 限制:100次/天
|
||||
- 评价:过低,无法满足
|
||||
|
||||
场景D:性能测试
|
||||
- 实际需求:可能>100次/天
|
||||
- 限制:100次/天
|
||||
- 评价:过低,无法满足
|
||||
```
|
||||
|
||||
**结论**:
|
||||
- 如果是人工运维操作,100次/天 过高
|
||||
- 如果是自动化系统调用,100次/天 过低
|
||||
- **限制设置没有明确的业务依据**
|
||||
|
||||
#### 问题2:单次1000张卡限制
|
||||
|
||||
**分析**:
|
||||
```go
|
||||
// 单次最多1000张卡
|
||||
if len(cardIDs) > 1000 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "单次最多触发1000张卡")
|
||||
}
|
||||
|
||||
// 但批量处理时,每100卡更新一次进度
|
||||
if processedCount%100 == 0 {
|
||||
_ = s.logStore.UpdateProgress(ctx, logID, processedCount, successCount, failedCount)
|
||||
}
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 1000张卡的限制来自哪里?没有文档说明
|
||||
- 是否考虑了数据库查询性能?
|
||||
- 是否考虑了 Redis 操作性能?
|
||||
- 是否考虑了 Asynq 队列容量?
|
||||
|
||||
#### 问题3:去重机制的时间不对齐
|
||||
|
||||
```go
|
||||
// 去重 key 1小时过期
|
||||
s.redis.Expire(ctx, dedupeKey, time.Hour)
|
||||
|
||||
// 但日限制是按天计算
|
||||
if todayCount >= 100 {
|
||||
return errors.New(...)
|
||||
}
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 去重 key 在1小时后过期,但日限制是按天计算
|
||||
- 如果在 23:00 触发一张卡,1小时后(00:00)去重 key 过期
|
||||
- 同一张卡可能在同一天内被触发两次(如果去重 key 过期了)
|
||||
- **去重机制与日限制不一致**
|
||||
|
||||
### 3.3 限制的实现问题
|
||||
|
||||
#### 问题1:CountTodayTriggers 的性能
|
||||
|
||||
```go
|
||||
func (s *PollingManualTriggerLogStore) CountTodayTriggers(ctx context.Context, triggeredBy uint) (int64, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.PollingManualTriggerLog{}).
|
||||
Where("triggered_by = ? AND DATE(triggered_at) = CURRENT_DATE", triggeredBy).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 每次触发都要查询数据库
|
||||
- 没有索引优化(`triggered_by` + `triggered_at`)
|
||||
- 没有缓存(可以用 Redis 计数器)
|
||||
- 在高频触发场景下,这个查询会成为瓶颈
|
||||
|
||||
#### 问题2:去重 Set 的内存占用
|
||||
|
||||
```go
|
||||
// 每个任务类型一个去重 Set
|
||||
dedupeKey := constants.RedisPollingManualDedupeKey(taskType)
|
||||
added, err := s.redis.SAdd(ctx, dedupeKey, cardID).Result()
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 如果每日触发1000张卡,去重 Set 会有1000个元素
|
||||
- 4个任务类型 × 1000张卡 = 4000个元素
|
||||
- 1小时过期后清空,但在高峰期可能占用大量内存
|
||||
- 没有考虑 Redis 内存限制
|
||||
|
||||
---
|
||||
|
||||
## 4. 代码实现复杂度分析
|
||||
|
||||
### 4.1 代码规模
|
||||
|
||||
```
|
||||
总代码行数:884 行
|
||||
├── Service 层:469 行(53%)
|
||||
├── Handler 层:307 行(35%)
|
||||
└── Store 层:108 行(12%)
|
||||
|
||||
功能点数:
|
||||
├── 3 种触发方式
|
||||
├── 权限检查(3 个函数)
|
||||
├── 进度追踪(异步处理)
|
||||
├── 任务取消
|
||||
├── 历史查询
|
||||
└── 状态查询
|
||||
```
|
||||
|
||||
### 4.2 复杂度评估
|
||||
|
||||
#### 权限检查的复杂性
|
||||
|
||||
```go
|
||||
// canManageCard:单卡权限检查
|
||||
// - 用户类型判断(3种)
|
||||
// - 卡信息查询
|
||||
// - 店铺权限检查
|
||||
// 复杂度:O(1) + O(1) = O(1)
|
||||
|
||||
// canManageCards:批量权限检查
|
||||
// - 用户类型判断(3种)
|
||||
// - 批量查询卡信息
|
||||
// - 逐卡权限检查
|
||||
// 复杂度:O(n) 其中 n = 卡数量
|
||||
|
||||
// applyShopPermissionFilter:条件筛选权限过滤
|
||||
// - 用户类型判断(3种)
|
||||
// - 店铺权限检查
|
||||
// 复杂度:O(1)
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- `canManageCards` 的 O(n) 复杂度在 n=1000 时可能有性能问题
|
||||
- 没有批量查询的优化(如分页查询)
|
||||
|
||||
#### 异步处理的复杂性
|
||||
|
||||
```go
|
||||
// processBatchTrigger:异步处理批量触发
|
||||
// - 逐卡检查去重(O(n))
|
||||
// - 逐卡加入队列(O(n))
|
||||
// - 每100卡更新一次进度(O(n/100))
|
||||
// 复杂度:O(n)
|
||||
|
||||
// 问题:
|
||||
// 1. 没有错误处理(如果 Redis 操作失败)
|
||||
// 2. 没有超时控制(如果处理时间过长)
|
||||
// 3. 没有并发控制(如果同时有多个批量触发)
|
||||
```
|
||||
|
||||
### 4.3 维护成本
|
||||
|
||||
| 方面 | 成本 | 说明 |
|
||||
|------|------|------|
|
||||
| 代码理解 | 中 | 需要理解权限模型、队列机制、异步处理 |
|
||||
| 测试覆盖 | 高 | 需要测试6个接口、3种权限、3种触发方式 |
|
||||
| 故障排查 | 高 | 涉及数据库、Redis、Asynq 三个系统 |
|
||||
| 性能优化 | 中 | 需要优化数据库查询、Redis 操作 |
|
||||
| 功能扩展 | 中 | 添加新的触发方式或限制规则需要修改多个地方 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 与自动轮询的必要性分析
|
||||
|
||||
### 5.1 自动轮询的能力
|
||||
|
||||
```
|
||||
自动轮询配置示例:
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 配置1:未实名卡 │
|
||||
│ - 实名检查间隔:60秒 │
|
||||
│ - 流量检查间隔:NULL(不检查) │
|
||||
│ - 套餐检查间隔:NULL(不检查) │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 配置2:已实名卡 │
|
||||
│ - 实名检查间隔:3600秒(1小时) │
|
||||
│ - 流量检查间隔:1800秒(30分钟) │
|
||||
│ - 套餐检查间隔:1800秒(30分钟) │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.2 手动触发的优势
|
||||
|
||||
| 场景 | 自动轮询 | 手动触发 | 必要性 |
|
||||
|------|----------|----------|--------|
|
||||
| 故障恢复 | 需要等待下次轮询 | 立即执行 | ✅ 高 |
|
||||
| 数据修复 | 需要等待下次轮询 | 立即执行 | ✅ 高 |
|
||||
| 性能测试 | 无法控制 | 可以控制 | ✅ 中 |
|
||||
| 日常运维 | 足够 | 可选 | ⚠️ 低 |
|
||||
| 业务集成 | 足够 | 可选 | ⚠️ 低 |
|
||||
|
||||
### 5.3 可替代方案
|
||||
|
||||
#### 方案1:调整轮询配置
|
||||
```go
|
||||
// 不需要手动触发,直接调整配置
|
||||
// 原配置:实名检查间隔 3600秒
|
||||
// 新配置:实名检查间隔 60秒(故障期间)
|
||||
// 恢复:实名检查间隔 3600秒(故障解决后)
|
||||
|
||||
优点:
|
||||
- 简单,不需要额外代码
|
||||
- 自动应用到所有卡
|
||||
- 易于理解和维护
|
||||
|
||||
缺点:
|
||||
- 需要手动修改配置
|
||||
- 可能影响其他卡的轮询频率
|
||||
```
|
||||
|
||||
#### 方案2:优先级队列
|
||||
```go
|
||||
// 不需要手动触发,使用优先级队列
|
||||
// 自动轮询队列:Sorted Set(按时间戳排序)
|
||||
// 优先级队列:Sorted Set(按优先级排序)
|
||||
|
||||
优点:
|
||||
- 灵活,可以动态调整优先级
|
||||
- 不需要额外的队列机制
|
||||
|
||||
缺点:
|
||||
- 需要修改调度器逻辑
|
||||
- 需要定义优先级规则
|
||||
```
|
||||
|
||||
#### 方案3:保留手动触发(当前方案)
|
||||
```go
|
||||
优点:
|
||||
- 灵活,支持多种触发方式
|
||||
- 不影响自动轮询配置
|
||||
- 可以精确控制触发范围
|
||||
|
||||
缺点:
|
||||
- 代码复杂度高(884行)
|
||||
- 维护成本高
|
||||
- 频次限制设置不合理
|
||||
```
|
||||
|
||||
### 5.4 结论
|
||||
|
||||
**手动触发功能的必要性:中等**
|
||||
|
||||
- ✅ 对于故障恢复和数据修复场景,手动触发是有价值的
|
||||
- ⚠️ 对于日常运维和业务集成,自动轮询已经足够
|
||||
- ❌ 当前的实现过于复杂,频次限制不合理
|
||||
|
||||
---
|
||||
|
||||
## 6. 过度设计问题
|
||||
|
||||
### 6.1 功能过度设计
|
||||
|
||||
| 功能 | 使用频率 | 必要性 | 说明 |
|
||||
|------|----------|--------|------|
|
||||
| 单卡触发 | 低 | ✅ 高 | 用于快速测试 |
|
||||
| 批量触发 | 中 | ✅ 中 | 用于批量修复 |
|
||||
| 条件筛选触发 | 低 | ⚠️ 低 | 可以用 API 查询后再批量触发 |
|
||||
| 进度追踪 | 低 | ⚠️ 低 | 异步处理,用户无法实时看到 |
|
||||
| 任务取消 | 极低 | ❌ 低 | 取消后卡已经在队列中,无法真正取消 |
|
||||
| 历史查询 | 低 | ⚠️ 低 | 可以用日志系统查询 |
|
||||
|
||||
### 6.2 权限检查过度设计
|
||||
|
||||
```go
|
||||
// 当前实现:3个权限检查函数
|
||||
- canManageCard(ctx, cardID) // 单卡权限检查
|
||||
- canManageCards(ctx, cardIDs) // 批量权限检查
|
||||
- applyShopPermissionFilter(...) // 条件筛选权限过滤
|
||||
|
||||
// 问题:
|
||||
// 1. 代码重复(都是检查 shop_id)
|
||||
// 2. 逻辑复杂(需要理解权限模型)
|
||||
// 3. 易出错(权限检查不一致)
|
||||
|
||||
// 可以简化为:
|
||||
- canManageCards(ctx, cardIDs) // 统一的权限检查
|
||||
```
|
||||
|
||||
### 6.3 数据库设计过度设计
|
||||
|
||||
```go
|
||||
// 当前表结构:tb_polling_manual_trigger_log
|
||||
type PollingManualTriggerLog struct {
|
||||
ID uint // 日志ID
|
||||
TaskType string // 任务类型
|
||||
TriggerType string // 触发类型(single/batch/by_condition)
|
||||
CardIDs string // 卡ID列表(JSON)
|
||||
ConditionFilter string // 筛选条件(JSON)
|
||||
TotalCount int // 总卡数
|
||||
ProcessedCount int // 已处理数
|
||||
SuccessCount int // 成功数
|
||||
FailedCount int // 失败数
|
||||
Status string // 状态
|
||||
TriggeredBy uint // 触发人ID
|
||||
TriggeredAt time.Time // 触发时间
|
||||
CompletedAt *time.Time // 完成时间
|
||||
}
|
||||
|
||||
// 问题:
|
||||
// 1. CardIDs 和 ConditionFilter 都是 JSON 字符串,难以查询
|
||||
// 2. ProcessedCount/SuccessCount/FailedCount 在异步处理中更新,可能不准确
|
||||
// 3. Status 字段的状态转移不清楚(pending -> processing -> completed)
|
||||
|
||||
// 可以简化为:
|
||||
// - 只记录触发信息(TaskType, TriggerType, TotalCount, TriggeredBy, TriggeredAt)
|
||||
// - 不记录进度信息(ProcessedCount, SuccessCount, FailedCount)
|
||||
// - 不记录详细条件(CardIDs, ConditionFilter)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 实际使用价值评估
|
||||
|
||||
### 7.1 使用场景评分
|
||||
|
||||
| 场景 | 频率 | 价值 | 复杂度 | 综合评分 |
|
||||
|------|------|------|--------|---------|
|
||||
| 故障恢复 | 低 | 高 | 低 | ⭐⭐⭐⭐ |
|
||||
| 数据修复 | 低 | 高 | 低 | ⭐⭐⭐⭐ |
|
||||
| 性能测试 | 低 | 中 | 中 | ⭐⭐⭐ |
|
||||
| 日常运维 | 中 | 低 | 中 | ⭐⭐ |
|
||||
| 业务集成 | 高 | 低 | 高 | ⭐ |
|
||||
|
||||
### 7.2 成本收益分析
|
||||
|
||||
```
|
||||
成本:
|
||||
- 代码行数:884 行
|
||||
- 维护成本:中等(需要理解权限、队列、异步处理)
|
||||
- 测试成本:高(6个接口,多种权限,多种触发方式)
|
||||
- 性能成本:低(不影响自动轮询)
|
||||
|
||||
收益:
|
||||
- 故障恢复:高(可以快速重试失败的卡)
|
||||
- 数据修复:高(可以快速重新检查修复的卡)
|
||||
- 性能测试:中(可以控制测试场景)
|
||||
- 日常运维:低(自动轮询已经足够)
|
||||
- 业务集成:低(频次限制过低)
|
||||
|
||||
综合评估:
|
||||
- 如果只用于故障恢复和数据修复,成本收益比 = 低成本 / 高收益 = ✅ 值得
|
||||
- 如果用于日常运维和业务集成,成本收益比 = 中成本 / 低收益 = ❌ 不值得
|
||||
```
|
||||
|
||||
### 7.3 建议
|
||||
|
||||
#### 短期建议(保留当前功能)
|
||||
|
||||
1. **明确使用场景**
|
||||
- 文档化手动触发的使用场景
|
||||
- 定义频次限制的业务依据
|
||||
- 添加使用指南
|
||||
|
||||
2. **优化频次限制**
|
||||
- 每日限制改为 1000 次(支持更多使用场景)
|
||||
- 单次限制改为 10000 张卡(支持大规模修复)
|
||||
- 或者完全移除限制(由业务层控制)
|
||||
|
||||
3. **改进实现**
|
||||
- 修复去重 key 过期时间与日限制的不对齐
|
||||
- 添加 Redis 缓存优化 CountTodayTriggers 查询
|
||||
- 改进异步处理的错误处理和超时控制
|
||||
|
||||
#### 长期建议(重新设计)
|
||||
|
||||
1. **简化功能**
|
||||
- 移除条件筛选触发(可以用 API 查询后再批量触发)
|
||||
- 移除进度追踪(异步处理,用户无法实时看到)
|
||||
- 移除任务取消(无法真正取消已入队的任务)
|
||||
|
||||
2. **改进设计**
|
||||
- 统一权限检查逻辑
|
||||
- 简化数据库表结构
|
||||
- 使用事件驱动而不是 API 驱动
|
||||
|
||||
3. **考虑替代方案**
|
||||
- 使用优先级队列替代手动触发队列
|
||||
- 使用配置调整替代手动触发
|
||||
- 使用事件系统替代 API 调用
|
||||
|
||||
---
|
||||
|
||||
## 8. 总结
|
||||
|
||||
### 8.1 核心发现
|
||||
|
||||
1. **功能完整性高**:支持多种触发方式、权限控制、进度追踪
|
||||
2. **使用价值存疑**:缺乏明确的业务需求支撑
|
||||
3. **频次限制不合理**:每日100次、1000张卡的限制没有业务依据
|
||||
4. **过度设计风险**:代码复杂度与实际使用场景不匹配
|
||||
5. **维护成本高**:884行代码,涉及多个系统(数据库、Redis、Asynq)
|
||||
|
||||
### 8.2 建议
|
||||
|
||||
**保留手动触发功能,但需要改进**:
|
||||
|
||||
1. 明确使用场景和频次限制的业务依据
|
||||
2. 优化频次限制(提高或移除)
|
||||
3. 改进实现(修复 bug,优化性能)
|
||||
4. 简化功能(移除不必要的功能)
|
||||
5. 改进文档(添加使用指南和最佳实践)
|
||||
|
||||
**不建议完全移除**,因为:
|
||||
- 故障恢复和数据修复场景有实际价值
|
||||
- 代码已经实现,移除成本高
|
||||
- 可以通过改进来提高价值
|
||||
|
||||
175
docs/polling-system/manual-trigger-summary.md
Normal file
175
docs/polling-system/manual-trigger-summary.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# 轮询系统手动触发功能 - 执行摘要
|
||||
|
||||
## 快速评估
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| **功能完整性** | ⭐⭐⭐⭐ | 支持单卡、批量、条件筛选三种方式,权限控制完善 |
|
||||
| **使用价值** | ⭐⭐ | 缺乏明确业务需求,频次限制不合理 |
|
||||
| **代码质量** | ⭐⭐⭐ | 884行代码,结构清晰但存在设计问题 |
|
||||
| **维护成本** | ⭐⭐ | 涉及多个系统,权限检查逻辑复杂 |
|
||||
| **性能影响** | ⭐⭐⭐⭐ | 不影响自动轮询,异步处理 |
|
||||
| **综合评价** | ⭐⭐⭐ | 保留但需要改进 |
|
||||
|
||||
## 核心问题
|
||||
|
||||
### 1. 频次限制不合理 ❌
|
||||
|
||||
```
|
||||
当前限制:
|
||||
- 每日100次触发
|
||||
- 单次1000张卡
|
||||
|
||||
问题:
|
||||
- 如果用于故障恢复,100次/天 过高(实际需求<10次)
|
||||
- 如果用于业务集成,100次/天 过低(实际需求>1000次)
|
||||
- 没有业务依据支撑这些数字
|
||||
```
|
||||
|
||||
### 2. 去重机制时间不对齐 ⚠️
|
||||
|
||||
```
|
||||
当前实现:
|
||||
- 去重 key 1小时过期
|
||||
- 日限制按天计算
|
||||
|
||||
问题:
|
||||
- 23:00 触发的卡,1小时后(00:00)去重 key 过期
|
||||
- 同一张卡可能在同一天内被触发两次
|
||||
```
|
||||
|
||||
### 3. 权限检查过度设计 ⚠️
|
||||
|
||||
```
|
||||
当前实现:3个权限检查函数
|
||||
- canManageCard()
|
||||
- canManageCards()
|
||||
- applyShopPermissionFilter()
|
||||
|
||||
问题:
|
||||
- 代码重复
|
||||
- 逻辑复杂
|
||||
- 易出错
|
||||
```
|
||||
|
||||
### 4. 功能过度设计 ⚠️
|
||||
|
||||
```
|
||||
6个接口中,必要性评估:
|
||||
- 单卡触发:✅ 高(快速测试)
|
||||
- 批量触发:✅ 中(批量修复)
|
||||
- 条件筛选:⚠️ 低(可用 API 查询后再批量触发)
|
||||
- 进度追踪:⚠️ 低(异步处理,用户无法实时看到)
|
||||
- 任务取消:❌ 低(无法真正取消已入队的任务)
|
||||
- 历史查询:⚠️ 低(可用日志系统查询)
|
||||
```
|
||||
|
||||
## 实际使用价值
|
||||
|
||||
### 高价值场景 ✅
|
||||
|
||||
| 场景 | 频率 | 价值 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 故障恢复 | <1次/天 | 高 | 轮询失败时快速重试 |
|
||||
| 数据修复 | <1次/周 | 高 | 修复错误数据后重新检查 |
|
||||
|
||||
### 低价值场景 ❌
|
||||
|
||||
| 场景 | 频率 | 价值 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 日常运维 | 1-10次/天 | 低 | 自动轮询已足够 |
|
||||
| 业务集成 | >100次/天 | 低 | 频次限制过低 |
|
||||
|
||||
## 改进建议
|
||||
|
||||
### 短期(保留功能)
|
||||
|
||||
1. **明确使用场景**
|
||||
- 文档化手动触发的适用场景
|
||||
- 定义频次限制的业务依据
|
||||
- 添加使用指南
|
||||
|
||||
2. **优化频次限制**
|
||||
```
|
||||
选项A:提高限制
|
||||
- 每日限制:100 → 1000 次
|
||||
- 单次限制:1000 → 10000 张卡
|
||||
|
||||
选项B:移除限制
|
||||
- 由业务层控制频率
|
||||
- 系统层不做限制
|
||||
```
|
||||
|
||||
3. **修复 Bug**
|
||||
- 修复去重 key 过期时间与日限制的不对齐
|
||||
- 添加 Redis 缓存优化 CountTodayTriggers 查询
|
||||
- 改进异步处理的错误处理
|
||||
|
||||
### 长期(重新设计)
|
||||
|
||||
1. **简化功能**
|
||||
- 移除条件筛选触发
|
||||
- 移除进度追踪
|
||||
- 移除任务取消
|
||||
|
||||
2. **改进设计**
|
||||
- 统一权限检查逻辑
|
||||
- 简化数据库表结构
|
||||
- 使用事件驱动而不是 API 驱动
|
||||
|
||||
3. **考虑替代方案**
|
||||
- 使用优先级队列替代手动触发队列
|
||||
- 使用配置调整替代手动触发
|
||||
- 使用事件系统替代 API 调用
|
||||
|
||||
## 成本收益分析
|
||||
|
||||
```
|
||||
成本:
|
||||
- 代码行数:884 行
|
||||
- 维护成本:中等
|
||||
- 测试成本:高
|
||||
- 性能成本:低
|
||||
|
||||
收益:
|
||||
- 故障恢复:高
|
||||
- 数据修复:高
|
||||
- 日常运维:低
|
||||
- 业务集成:低
|
||||
|
||||
结论:
|
||||
- 如果只用于故障恢复和数据修复,成本收益比 = ✅ 值得
|
||||
- 如果用于日常运维和业务集成,成本收益比 = ❌ 不值得
|
||||
```
|
||||
|
||||
## 最终建议
|
||||
|
||||
### 保留还是删除?
|
||||
|
||||
**建议:保留,但需要改进**
|
||||
|
||||
理由:
|
||||
- ✅ 故障恢复和数据修复场景有实际价值
|
||||
- ✅ 代码已经实现,删除成本高
|
||||
- ✅ 可以通过改进来提高价值
|
||||
- ❌ 当前实现存在问题,需要修复
|
||||
|
||||
### 优先级
|
||||
|
||||
1. **P0(必做)**:修复去重机制的时间不对齐问题
|
||||
2. **P1(应做)**:明确使用场景和频次限制的业务依据
|
||||
3. **P2(可做)**:优化频次限制和性能
|
||||
4. **P3(长期)**:重新设计和简化功能
|
||||
|
||||
## 详细分析
|
||||
|
||||
完整的深入分析报告请参考:[manual-trigger-analysis.md](./manual-trigger-analysis.md)
|
||||
|
||||
---
|
||||
|
||||
**报告生成时间**:2025-04-13
|
||||
**分析范围**:
|
||||
- 代码行数:884 行
|
||||
- API 接口:6 个
|
||||
- 触发方式:3 种
|
||||
- 权限检查:3 个函数
|
||||
309
docs/polling-system/manual-trigger-vs-alternatives.md
Normal file
309
docs/polling-system/manual-trigger-vs-alternatives.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# 手动触发 vs 替代方案对比
|
||||
|
||||
## 方案对比矩阵
|
||||
|
||||
### 方案1:保留手动触发(当前方案)
|
||||
|
||||
```
|
||||
优点:
|
||||
✅ 灵活,支持多种触发方式
|
||||
✅ 不影响自动轮询配置
|
||||
✅ 可以精确控制触发范围
|
||||
✅ 支持权限控制和审计
|
||||
|
||||
缺点:
|
||||
❌ 代码复杂度高(884行)
|
||||
❌ 维护成本高
|
||||
❌ 频次限制设置不合理
|
||||
❌ 权限检查逻辑复杂
|
||||
|
||||
适用场景:
|
||||
- 故障恢复(快速重试失败的卡)
|
||||
- 数据修复(重新检查修复的卡)
|
||||
- 性能测试(控制测试场景)
|
||||
|
||||
成本:884行代码 + 中等维护成本
|
||||
收益:高(故障恢复/数据修复)
|
||||
综合评分:⭐⭐⭐
|
||||
```
|
||||
|
||||
### 方案2:调整轮询配置
|
||||
|
||||
```
|
||||
原理:
|
||||
- 不需要手动触发
|
||||
- 直接调整轮询配置的间隔
|
||||
- 原配置:实名检查间隔 3600秒
|
||||
- 新配置:实名检查间隔 60秒(故障期间)
|
||||
- 恢复:实名检查间隔 3600秒(故障解决后)
|
||||
|
||||
优点:
|
||||
✅ 简单,不需要额外代码
|
||||
✅ 自动应用到所有卡
|
||||
✅ 易于理解和维护
|
||||
✅ 无需权限控制
|
||||
|
||||
缺点:
|
||||
❌ 需要手动修改配置
|
||||
❌ 可能影响其他卡的轮询频率
|
||||
❌ 无法精确控制触发范围
|
||||
❌ 无法快速恢复
|
||||
|
||||
适用场景:
|
||||
- 系统级故障(需要调整所有卡的轮询频率)
|
||||
- 长期优化(调整轮询策略)
|
||||
|
||||
成本:0行代码 + 低维护成本
|
||||
收益:中(需要手动操作)
|
||||
综合评分:⭐⭐
|
||||
```
|
||||
|
||||
### 方案3:优先级队列
|
||||
|
||||
```
|
||||
原理:
|
||||
- 不需要手动触发队列
|
||||
- 使用优先级队列替代
|
||||
- 自动轮询队列:Sorted Set(按时间戳排序)
|
||||
- 优先级队列:Sorted Set(按优先级排序)
|
||||
- 调度器优先消费优先级队列
|
||||
|
||||
优点:
|
||||
✅ 灵活,可以动态调整优先级
|
||||
✅ 不需要额外的队列机制
|
||||
✅ 支持多级优先级
|
||||
✅ 易于扩展
|
||||
|
||||
缺点:
|
||||
❌ 需要修改调度器逻辑
|
||||
❌ 需要定义优先级规则
|
||||
❌ 需要权限控制
|
||||
❌ 需要审计日志
|
||||
|
||||
适用场景:
|
||||
- 需要多级优先级的场景
|
||||
- 需要动态调整优先级的场景
|
||||
|
||||
成本:200-300行代码 + 中等维护成本
|
||||
收益:中(灵活性高)
|
||||
综合评分:⭐⭐⭐
|
||||
```
|
||||
|
||||
### 方案4:事件驱动系统
|
||||
|
||||
```
|
||||
原理:
|
||||
- 使用事件系统替代 API 调用
|
||||
- 当卡状态变化时,发送事件
|
||||
- 事件处理器根据事件类型触发轮询
|
||||
- 支持多个事件处理器
|
||||
|
||||
优点:
|
||||
✅ 解耦,易于扩展
|
||||
✅ 支持多个事件处理器
|
||||
✅ 易于测试
|
||||
✅ 易于维护
|
||||
|
||||
缺点:
|
||||
❌ 需要重新设计架构
|
||||
❌ 需要事件系统基础设施
|
||||
❌ 学习成本高
|
||||
❌ 调试困难
|
||||
|
||||
适用场景:
|
||||
- 需要多个系统协作的场景
|
||||
- 需要高度解耦的场景
|
||||
|
||||
成本:500+行代码 + 高维护成本
|
||||
收益:高(长期收益)
|
||||
综合评分:⭐⭐⭐⭐
|
||||
```
|
||||
|
||||
## 详细对比
|
||||
|
||||
### 功能对比
|
||||
|
||||
| 功能 | 手动触发 | 配置调整 | 优先级队列 | 事件驱动 |
|
||||
|------|----------|----------|-----------|---------|
|
||||
| 快速重试 | ✅ | ⚠️ | ✅ | ✅ |
|
||||
| 精确控制 | ✅ | ❌ | ✅ | ✅ |
|
||||
| 权限控制 | ✅ | ❌ | ✅ | ✅ |
|
||||
| 审计日志 | ✅ | ❌ | ✅ | ✅ |
|
||||
| 自动化 | ❌ | ✅ | ✅ | ✅ |
|
||||
| 灵活性 | ✅ | ❌ | ✅ | ✅ |
|
||||
|
||||
### 性能对比
|
||||
|
||||
| 指标 | 手动触发 | 配置调整 | 优先级队列 | 事件驱动 |
|
||||
|------|----------|----------|-----------|---------|
|
||||
| 响应时间 | <100ms | <100ms | <100ms | <100ms |
|
||||
| 内存占用 | 中 | 低 | 中 | 中 |
|
||||
| CPU 占用 | 低 | 低 | 低 | 低 |
|
||||
| 数据库查询 | 中 | 低 | 中 | 中 |
|
||||
| Redis 操作 | 中 | 低 | 中 | 中 |
|
||||
|
||||
### 成本对比
|
||||
|
||||
| 成本项 | 手动触发 | 配置调整 | 优先级队列 | 事件驱动 |
|
||||
|--------|----------|----------|-----------|---------|
|
||||
| 开发成本 | 已完成 | 0 | 200-300行 | 500+行 |
|
||||
| 维护成本 | 中 | 低 | 中 | 高 |
|
||||
| 测试成本 | 高 | 低 | 中 | 高 |
|
||||
| 学习成本 | 中 | 低 | 中 | 高 |
|
||||
| 总成本 | 中 | 低 | 中 | 高 |
|
||||
|
||||
### 收益对比
|
||||
|
||||
| 收益项 | 手动触发 | 配置调整 | 优先级队列 | 事件驱动 |
|
||||
|--------|----------|----------|-----------|---------|
|
||||
| 故障恢复 | 高 | 中 | 高 | 高 |
|
||||
| 数据修复 | 高 | 中 | 高 | 高 |
|
||||
| 性能测试 | 中 | 低 | 中 | 中 |
|
||||
| 日常运维 | 低 | 中 | 中 | 中 |
|
||||
| 业务集成 | 低 | 低 | 中 | 高 |
|
||||
| 长期价值 | 中 | 低 | 中 | 高 |
|
||||
|
||||
## 推荐方案
|
||||
|
||||
### 短期(1-3个月)
|
||||
|
||||
**推荐:保留手动触发 + 改进**
|
||||
|
||||
理由:
|
||||
- 代码已经实现,删除成本高
|
||||
- 故障恢复和数据修复场景有实际价值
|
||||
- 可以通过改进来提高价值
|
||||
|
||||
改进清单:
|
||||
1. 修复去重机制的时间不对齐问题
|
||||
2. 明确使用场景和频次限制的业务依据
|
||||
3. 优化频次限制(提高或移除)
|
||||
4. 添加 Redis 缓存优化性能
|
||||
5. 改进异步处理的错误处理
|
||||
|
||||
### 中期(3-6个月)
|
||||
|
||||
**推荐:优先级队列 + 简化手动触发**
|
||||
|
||||
理由:
|
||||
- 优先级队列提供更灵活的控制
|
||||
- 可以逐步迁移手动触发的功能
|
||||
- 减少代码复杂度
|
||||
|
||||
迁移步骤:
|
||||
1. 实现优先级队列
|
||||
2. 将手动触发迁移到优先级队列
|
||||
3. 简化手动触发的功能(只保留单卡和批量)
|
||||
4. 移除条件筛选、进度追踪、任务取消
|
||||
|
||||
### 长期(6-12个月)
|
||||
|
||||
**推荐:事件驱动系统**
|
||||
|
||||
理由:
|
||||
- 高度解耦,易于扩展
|
||||
- 支持多个系统协作
|
||||
- 长期收益高
|
||||
|
||||
实现步骤:
|
||||
1. 设计事件系统
|
||||
2. 实现事件处理器
|
||||
3. 迁移手动触发到事件系统
|
||||
4. 移除手动触发 API
|
||||
|
||||
## 成本收益分析
|
||||
|
||||
### 方案1:保留手动触发(当前方案)
|
||||
|
||||
```
|
||||
成本:
|
||||
- 开发成本:0(已完成)
|
||||
- 维护成本:中等
|
||||
- 测试成本:高
|
||||
- 总成本:中等
|
||||
|
||||
收益:
|
||||
- 故障恢复:高
|
||||
- 数据修复:高
|
||||
- 日常运维:低
|
||||
- 业务集成:低
|
||||
- 总收益:中等
|
||||
|
||||
成本收益比:中等 / 中等 = 1:1
|
||||
评价:⭐⭐⭐ 保留,但需要改进
|
||||
```
|
||||
|
||||
### 方案2:调整轮询配置
|
||||
|
||||
```
|
||||
成本:
|
||||
- 开发成本:0
|
||||
- 维护成本:低
|
||||
- 测试成本:低
|
||||
- 总成本:低
|
||||
|
||||
收益:
|
||||
- 故障恢复:中
|
||||
- 数据修复:中
|
||||
- 日常运维:中
|
||||
- 业务集成:低
|
||||
- 总收益:中
|
||||
|
||||
成本收益比:低 / 中 = 1:2
|
||||
评价:⭐⭐ 简单但功能有限
|
||||
```
|
||||
|
||||
### 方案3:优先级队列
|
||||
|
||||
```
|
||||
成本:
|
||||
- 开发成本:200-300行
|
||||
- 维护成本:中等
|
||||
- 测试成本:中等
|
||||
- 总成本:中等
|
||||
|
||||
收益:
|
||||
- 故障恢复:高
|
||||
- 数据修复:高
|
||||
- 日常运维:中
|
||||
- 业务集成:中
|
||||
- 总收益:高
|
||||
|
||||
成本收益比:中等 / 高 = 1:1.5
|
||||
评价:⭐⭐⭐⭐ 推荐中期方案
|
||||
```
|
||||
|
||||
### 方案4:事件驱动系统
|
||||
|
||||
```
|
||||
成本:
|
||||
- 开发成本:500+行
|
||||
- 维护成本:高
|
||||
- 测试成本:高
|
||||
- 总成本:高
|
||||
|
||||
收益:
|
||||
- 故障恢复:高
|
||||
- 数据修复:高
|
||||
- 日常运维:高
|
||||
- 业务集成:高
|
||||
- 总收益:高
|
||||
|
||||
成本收益比:高 / 高 = 1:1
|
||||
评价:⭐⭐⭐⭐ 推荐长期方案
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
| 方案 | 短期 | 中期 | 长期 | 综合评分 |
|
||||
|------|------|------|------|---------|
|
||||
| 保留手动触发 | ✅ | ⚠️ | ❌ | ⭐⭐⭐ |
|
||||
| 调整轮询配置 | ⚠️ | ❌ | ❌ | ⭐⭐ |
|
||||
| 优先级队列 | ⚠️ | ✅ | ⚠️ | ⭐⭐⭐⭐ |
|
||||
| 事件驱动系统 | ❌ | ⚠️ | ✅ | ⭐⭐⭐⭐ |
|
||||
|
||||
**最终建议**:
|
||||
1. **短期**:保留手动触发,进行改进
|
||||
2. **中期**:实现优先级队列,逐步迁移
|
||||
3. **长期**:构建事件驱动系统,完全重构
|
||||
|
||||
Reference in New Issue
Block a user