docs(review): add remediation plans and readiness artifacts

Add design, review, and production-readiness documents for the April remediation cycle.\nInclude supporting SQL and supply-api operational design notes so review conclusions and implementation guidance stay versioned together.
This commit is contained in:
Your Name
2026-04-13 18:54:45 +08:00
parent a2f042f1c2
commit aecba5ff27
24 changed files with 7880 additions and 0 deletions

View File

@@ -0,0 +1,731 @@
# P0问题系统性修复设计方案
- 版本v1.0
- 日期2026-04-07
- 状态:实施基线
- 目标系统性修复设计文档审查中发现的11个P0问题
- 关联文档:
- `llm_gateway_prd_v1_2026-03-25.md`
- `supply_technical_design_enhanced_v1_2026-03-25.md`
- `database_domain_model_and_governance_v1_2026-03-27.md`
- `token_runtime_minimal_spec_v1.md`
- `token_auth_middleware_design_v1_2026-03-29.md`
---
## 一、问题修复总览
| # | 问题ID | 问题描述 | 影响 | 修复方案 | 优先级 |
|---|---|---|---|---|---|
| P0-01 | SEC-001 | Token格式未定义 | 鉴权链路基础缺失 | 明确JWT+RS256方案 | P1 |
| P0-02 | SEC-002 | 加密方案未定义 | 安全合规风险 | 补充KMS集成方案 | P1 |
| P0-03 | SEC-003 | 缓存吊销传播矛盾 | 安全漏洞 | 主动失效机制 | P0 |
| P0-04 | SEC-004 | Query Key检测不完整 | 安全风险 | 增强检测+白名单 | P2 |
| P0-05 | ARC-001 | 缺少限流策略 | 可用性风险 | 实现令牌桶+滑动窗口 | P1 |
| P0-006 | ARC-002 | Outbox无重试策略 | 可靠性 | 完整Outbox+DLQ | P1 |
| P0-007 | ARC-003 | 批量无补偿策略 | 数据一致性 | 补偿表+人工介入 | P2 |
| P0-008 | DB-001 | 大表无分区策略 | 性能退化 | 月分区策略 | P0 |
| P0-009 | DB-002 | 外键策略未定义 | 数据一致性 | 应用层外键+校验 | P1 |
| P0-010 | TST-001 | 需求追溯不完整 | 可追溯性 | 补充映射矩阵 | P2 |
| P0-011 | OPS-001 | 数据保留策略缺失 | 合规风险 | 多级保留策略 | P0 |
---
## 二、Token体系增强设计P0-01
### 2.1 Token格式规范
**选定方案JWT + RS256非对称签名**
| 字段 | 规范 |
|------|------|
| 格式 | JWT (RFC 7519) |
| 签名算法 | RS256 (RSA-SHA256) / ES256 (可选) |
| Token类型 | Access Token + Refresh Token |
| Access Token有效期 | 15分钟 |
| Refresh Token有效期 | 7天 |
### 2.2 JWT Claims定义
```json
{
"iss": "llm-gateway-platform", // Issuer: 平台签发
"sub": "user:12345", // Subject: 用户标识
"aud": "llm-gateway-supply-api", // Audience: 目标服务
"exp": 1749366000, // Expiration: 15min
"iat": 1749365300, // Issued At
"nbf": 1749365300, // Not Before
"jti": "tok_abc123def456", // JWT ID: 唯一标识
"tenant_id": 10001, // 租户ID
"role": "owner", // 角色: owner/viewer/admin
"scope": ["supply:accounts:read"], // 授权范围
"token_type": "access" // access/refresh
}
```
### 2.3 Token状态机
```
active -> expired (时间到期自动转换)
-> revoked (主动吊销,不可恢复)
-> invalid (验证失败)
```
### 2.4 与现有设计对比
| 维度 | 原设计 | 修复后 |
|------|--------|--------|
| Token格式 | 未定义 | JWT (RFC 7519) |
| 签名算法 | 未定义 | RS256 |
| 有效期 | 未定义 | 15min + 7d |
| Token类型 | 未定义 | access + refresh |
---
## 三、缓存吊销传播策略P0-03
### 3.1 问题分析
原设计存在逻辑矛盾:
- 缓存TTL = 30秒
- 要求吊销传播延迟 <= 5秒
- 矛盾点30秒内吊销的token可能被使用最长30秒
### 3.2 修复方案:主动失效 + 短TTL
**核心策略主动失效机制Active Invalidation**
```
架构:
[Token Revoke] -> [Message Queue/Pub-Sub] -> [Cache Invalidation] -> [Redis Del]
|
+-> [In-Memory Cache Del]
```
**实现要点:**
1. **吊销事件发布**
- 吊销操作时,发布 `token.revoked` 事件到 Pub/Sub
- 事件包含:`token_id`, `revoked_at`, `reason`
2. **缓存主动失效**
- 订阅服务接收事件立即删除对应缓存key
- 延迟要求:<= 100ms远小于5s目标
3. **TTL兜底**
- 将TTL从30s缩短至10s
- 作为主动失效失败时的兜底
### 3.3 验收标准
| 指标 | 目标值 | 测量方法 |
|------|--------|---------|
| 吊销传播延迟 | <= 5s | 吊销操作到缓存删除的时间 |
| 主动失效成功率 | >= 99.9% | 主动失效次数/总吊销次数 |
| 缓存命中率 | >= 80% | 缓存命中/总查询 |
### 3.4 Redis Pub/Sub实现
```go
// 吊销时发布事件
func (s *TokenRevocationService) RevokeAndPublish(ctx context.Context, tokenID string) error {
// 1. 更新数据库状态
if err := s.db.RevokeToken(ctx, tokenID); err != nil {
return err
}
// 2. 发布吊销事件
event := TokenRevokedEvent{
TokenID: tokenID,
RevokedAt: time.Now(),
Reason: "user_requested",
}
// 3. 发布到Redis Pub/Sub
return s.redis.Publish(ctx, "token:revoked", event)
}
// 订阅服务处理
func (s *RevocationSubscriber) Subscribe(ctx context.Context) {
pubsub := s.redis.Subscribe(ctx, "token:revoked")
defer pubsub.Close()
for {
select {
case <-ctx.Done():
return
case msg := <-pubsub.Channel():
var event TokenRevokedEvent
json.Unmarshal([]byte(msg.Payload), &event)
// 立即失效缓存
s.cache.Invalidate(ctx, event.TokenID)
}
}
}
```
---
## 四、Outbox模式实现P0-006
### 4.1 Outbox表结构
```sql
CREATE TABLE outbox_events (
id BIGSERIAL PRIMARY KEY,
aggregate_type VARCHAR(64) NOT NULL, -- 聚合类型: supply_account, package, settlement
aggregate_id VARCHAR(128) NOT NULL, -- 聚合ID
event_type VARCHAR(128) NOT NULL, -- 事件类型: created, updated, revoked
event_id VARCHAR(64) NOT NULL UNIQUE, -- 事件全局唯一ID (UUID)
payload JSONB NOT NULL, -- 事件载荷
status VARCHAR(20) NOT NULL DEFAULT 'pending',
CHECK (status IN ('pending', 'processing', 'completed', 'failed', 'dead_letter')),
retry_count INT NOT NULL DEFAULT 0,
max_retries INT NOT NULL DEFAULT 5,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMPTZ,
next_retry_at TIMESTAMPTZ,
dead_letter_reason TEXT,
version BIGINT NOT NULL DEFAULT 1 -- 乐观锁版本
);
-- 高频查询索引
CREATE INDEX idx_outbox_events_status_next_retry ON outbox_events (status, next_retry_at)
WHERE status IN ('pending', 'failed');
CREATE INDEX idx_outbox_events_aggregate ON outbox_events (aggregate_type, aggregate_id);
CREATE INDEX idx_outbox_events_created_at ON outbox_events (created_at);
```
### 4.2 死信队列DLQ
```sql
CREATE TABLE outbox_dead_letter (
id BIGSERIAL PRIMARY KEY,
original_event_id VARCHAR(64) NOT NULL,
original_aggregate_type VARCHAR(64) NOT NULL,
original_aggregate_id VARCHAR(128) NOT NULL,
event_type VARCHAR(128) NOT NULL,
payload JSONB NOT NULL,
error_message TEXT,
retry_count INT NOT NULL,
first_failed_at TIMESTAMPTZ NOT NULL,
dead_letter_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
handled BOOLEAN NOT NULL DEFAULT FALSE,
handled_at TIMESTAMPTZ,
handler_notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_outbox_dead_letter_unhandled ON outbox_dead_letter (handled, dead_letter_at)
WHERE handled = FALSE;
```
### 4.3 重试策略
| 参数 | 值 | 说明 |
|------|-----|------|
| 最大重试次数 | 5 | 超过后移入DLQ |
| 初始重试间隔 | 1s | 指数退避起始 |
| 最大重试间隔 | 60s | 退避上限 |
| 退避公式 | min(60s, 1s * 2^retry_count) | 指数退避 |
**重试时间线:**
- 第1次重试1s
- 第2次重试2s
- 第3次重试4s
- 第4次重试8s
- 第5次重试16s
- 之后移入DLQ
### 4.4 事件消费保障
```go
// Outbox处理器
type OutboxProcessor struct {
db *pgxpool.Pool
broker MessageBroker
stats *MetricsService
}
// ProcessOutbox 扫描并处理待发送事件
func (p *OutboxProcessor) ProcessOutbox(ctx context.Context) error {
// 1. 原子获取待处理事件(带悲观锁)
events, err := p.db.FetchAndLockOutboxEvents(ctx, 100)
if err != nil {
return err
}
for _, event := range events {
// 2. 更新状态为processing
if err := p.db.UpdateOutboxStatus(ctx, event.ID, "processing"); err != nil {
continue
}
// 3. 发送到消息队列
if err := p.broker.Publish(ctx, event); err != nil {
// 4. 处理失败,更新重试状态
p.handleFailure(ctx, event, err)
continue
}
// 5. 标记完成
if err := p.db.UpdateOutboxStatus(ctx, event.ID, "completed", time.Now()); err != nil {
p.stats.RecordOutboxFailure("update_completed_failed")
}
}
return nil
}
// handleFailure 处理失败事件
func (p *OutboxProcessor) handleFailure(ctx context.Context, event *OutboxEvent, err error) {
event.RetryCount++
event.ErrorMessage = err.Error()
if event.RetryCount >= event.MaxRetries {
// 移入死信队列
p.db.MoveToDeadLetter(ctx, event)
p.stats.RecordOutboxDLQ(event.EventType)
} else {
// 计算下次重试时间(指数退避)
backoff := time.Duration(math.Min(60, 1*math.Pow(2, float64(event.RetryCount)))) * time.Second
event.NextRetryAt = time.Now().Add(backoff)
p.db.UpdateOutboxRetry(ctx, event)
p.stats.RecordOutboxRetry(event.EventType)
}
}
```
---
## 五、批量操作补偿策略P0-007
### 5.1 补偿表结构
```sql
CREATE TABLE supply_batch_compensation (
id BIGSERIAL PRIMARY KEY,
batch_id VARCHAR(64) NOT NULL, -- 批量任务ID
operation_type VARCHAR(32) NOT NULL, -- batch_price, batch_update
item_index INT NOT NULL, -- 失败项在批量中的索引
item_payload JSONB NOT NULL, -- 失败项的原始请求
failure_reason TEXT, -- 失败原因
status VARCHAR(20) NOT NULL DEFAULT 'pending',
CHECK (status IN ('pending', 'retrying', 'resolved', 'manual_required', 'abandoned')),
retry_count INT NOT NULL DEFAULT 0,
max_retries INT NOT NULL DEFAULT 3,
resolved_at TIMESTAMPTZ,
resolved_by BIGINT, -- 操作者ID
resolution_notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by BIGINT,
version BIGINT NOT NULL DEFAULT 1
);
CREATE INDEX idx_compensation_batch ON supply_batch_compensation (batch_id, status);
CREATE INDEX idx_compensation_status ON supply_batch_compensation (status, created_at);
```
### 5.2 补偿流程
```
批量调价请求
|
v
+-----------------+
| 分片事务处理 |
+-----------------+
|
+---> 成功项 ---> 返回成功响应
|
+---> 失败项 ---> 记录compensation表
| |
| v
| +-------------------+
| | 自动重试 (3次) |
| +-------------------+
| |
| +-------+-------+
| |成功 |失败 |
| v v v
| [resolved] [人工介入] [放弃]
|
v
返回批量结果(含成功/失败明细)
```
### 5.3 补偿接口
| 接口 | 方法 | 说明 |
|------|------|------|
| GET /api/v1/supply/batches/{batch_id}/compensation | 查询 | 获取批量补偿项列表 |
| POST /api/v1/supply/batches/{batch_id}/retry | 重试 | 重试失败项 |
| POST /api/v1/supply/compensation/{id}/resolve | 解决 | 人工确认解决 |
| POST /api/v1/supply/compensation/{id}/abandon | 放弃 | 放弃补偿 |
---
## 六、数据库分区策略P0-008
### 6.1 分区表定义
**audit_events 按月分区**
```sql
CREATE TABLE audit_events (
id BIGINT NOT NULL,
tenant_id BIGINT,
project_id BIGINT,
actor_user_id BIGINT,
actor_type VARCHAR(32) NOT NULL,
domain_code VARCHAR(32) NOT NULL,
object_type VARCHAR(64) NOT NULL,
object_id VARCHAR(128),
action_code VARCHAR(64) NOT NULL,
result_code VARCHAR(32) NOT NULL,
severity VARCHAR(16) NOT NULL DEFAULT 'info',
request_id VARCHAR(64),
trace_id VARCHAR(64),
idempotency_key VARCHAR(128),
client_ip INET,
user_agent VARCHAR(256),
before_data JSONB,
after_data JSONB,
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
-- 2026年分区
CREATE TABLE audit_events_2026_01 PARTITION OF audit_events
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE audit_events_2026_02 PARTITION OF audit_events
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE audit_events_2026_03 PARTITION OF audit_events
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
CREATE TABLE audit_events_2026_04 PARTITION OF audit_events
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
CREATE TABLE audit_events_2026_05 PARTITION OF audit_events
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
CREATE TABLE audit_events_2026_06 PARTITION OF audit_events
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
-- 默认分区(捕获未预期的数据)
CREATE TABLE audit_events_default PARTITION OF audit_events DEFAULT;
```
**billing_ledger_entries 按月分区**
```sql
CREATE TABLE billing_ledger_entries (
id BIGINT NOT NULL,
billing_account_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
project_id BIGINT,
user_id BIGINT,
request_id VARCHAR(64) NOT NULL,
trace_id VARCHAR(64),
entry_type VARCHAR(32) NOT NULL,
direction VARCHAR(2) NOT NULL,
amount_minor BIGINT NOT NULL,
currency_code CHAR(3) NOT NULL,
amount_unit VARCHAR(16) NOT NULL DEFAULT 'minor',
balance_after_minor BIGINT,
ref_type VARCHAR(32),
ref_id BIGINT,
occurred_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
idempotency_key VARCHAR(128),
PRIMARY KEY (id, occurred_at)
) PARTITION BY RANGE (occurred_at);
-- 2026年月度分区示例
CREATE TABLE billing_ledger_2026_04 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
CREATE TABLE billing_ledger_2026_05 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
```
### 6.2 分区维护
```sql
-- 自动创建新分区的存储过程(每日执行)
CREATE OR REPLACE FUNCTION create_monthly_partition(
table_name TEXT,
partition_date DATE
) RETURNS VOID AS $$
DECLARE
partition_name TEXT;
start_date DATE;
end_date DATE;
BEGIN
start_date := date_trunc('month', partition_date);
end_date := start_date + INTERVAL '1 month';
partition_name := table_name || '_' || to_char(start_date, 'YYYY_MM');
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %I PARTITION OF %I FOR VALUES FROM (%L) TO (%L)',
partition_name, table_name, start_date, end_date
);
END;
$$ LANGUAGE plpgsql;
-- 分区清理策略保留24个月
CREATE OR REPLACE FUNCTION drop_old_partitions(
table_name TEXT,
retention_months INT DEFAULT 24
) RETURNS VOID AS $$
DECLARE
partition_record RECORD;
cutoff_date DATE;
BEGIN
cutoff_date := date_trunc('month', CURRENT_DATE) - (retention_months || ' months')::INTERVAL;
FOR partition_record IN
SELECT inhrelid::regclass::text AS partition_name
FROM pg_inherits
WHERE inhparent = table_name::regclass
LOOP
IF partition_record.partition_name ~ '_[0-9]{4}_[0-9]{2}$' THEN
IF to_date(substring(partition_record.partition_name from '_[0-9]{4}_[0-9]{2}$'), 'YYYY_MM') < cutoff_date THEN
EXECUTE format('DROP TABLE IF EXISTS %I', partition_record.partition_name);
END IF;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
```
---
## 七、外键约束策略P0-009
### 7.1 策略决策
| 表类型 | 策略 | 理由 |
|--------|------|------|
| 核心实体表 | 物理外键 | 数据完整性关键 |
| 高频写入表 | 应用层外键 | 性能考量 |
| 审计/日志表 | 无外键 | 数据量大,历史数据可能孤立 |
### 7.2 外键策略定义
**保留物理外键的表(核心实体):**
- `core_tenants` (主实体)
- `core_projects` (依赖 tenants)
- `iam_users` (依赖 tenants)
- `billing_accounts` (依赖 tenants, projects)
**使用应用层外键的表(高频写入):**
- `supply_accounts` -> `iam_users`
- `supply_packages` -> `supply_accounts`
- `supply_orders` -> `supply_accounts`, `supply_packages`
- `supply_usage_records` -> `supply_orders`, `supply_accounts`
**无外键表(审计/日志):**
- `audit_events`
- `outbox_events`
- `outbox_dead_letter`
- `supply_idempotency_record`
### 7.3 应用层外键校验服务
```go
// ForeignKeyValidator 应用层外键校验器
type ForeignKeyValidator struct {
db *pgxpool.Pool
}
// ValidateSupplyAccountOwner 校验供应账号所属用户存在
func (v *ForeignKeyValidator) ValidateSupplyAccountOwner(ctx context.Context, userID int64) error {
var exists bool
err := v.db.QueryRow(ctx,
"SELECT EXISTS(SELECT 1 FROM iam_users WHERE id = $1)",
userID,
).Scan(&exists)
if err != nil {
return fmt.Errorf("failed to validate user: %w", err)
}
if !exists {
return ErrReferencedEntityNotFound
}
return nil
}
// ValidatePackageSupplyAccount 校验套餐所属供应账号存在
func (v *ForeignKeyValidator) ValidatePackageSupplyAccount(ctx context.Context, accountID int64) error {
var exists bool
err := v.db.QueryRow(ctx,
"SELECT EXISTS(SELECT 1 FROM supply_accounts WHERE id = $1)",
accountID,
).Scan(&exists)
if err != nil {
return fmt.Errorf("failed to validate supply account: %w", err)
}
if !exists {
return ErrReferencedEntityNotFound
}
return nil
}
```
### 7.4 一致性校验任务
```sql
-- 每日一致性校验任务
CREATE OR REPLACE FUNCTION check_orphan_records() RETURNS VOID AS $$
BEGIN
-- 检查孤立的supply_accounts
PERFORM COUNT(*) FROM supply_accounts sa
WHERE NOT EXISTS (SELECT 1 FROM iam_users WHERE id = sa.user_id);
-- 检查孤立的supply_packages
PERFORM COUNT(*) FROM supply_packages sp
WHERE NOT EXISTS (SELECT 1 FROM supply_accounts WHERE id = sp.supply_account_id);
-- 检查孤立的supply_orders
PERFORM COUNT(*) FROM supply_orders so
WHERE NOT EXISTS (SELECT 1 FROM supply_accounts WHERE id = so.supply_account_id)
OR NOT EXISTS (SELECT 1 FROM supply_packages WHERE id = so.supply_package_id);
-- 如发现孤立记录,插入警告审计事件
-- 实际处理由应用层完成
END;
$$ LANGUAGE plpgsql;
```
---
## 八、数据保留与归档策略P0-011
### 8.1 保留策略定义
| 数据类别 | 表名 | 保留期限 | 归档策略 | 清理策略 |
|---------|------|---------|---------|---------|
| 审计日志 | audit_events | 1年 | 压缩归档到OSS | 1年后DELETE |
| 凭证操作日志 | auth_credential_events | 1年 | 压缩归档 | 1年后DELETE |
| 调用日志 | supply_usage_records | 90天 | 压缩归档到OSS | 90天后DELETE |
| 结算数据 | billing_ledger_entries | 永久 | 不归档 | 不清理 |
| 订单数据 | supply_orders | 永久 | 不归档 | 不清理 |
| 套餐数据 | supply_packages | 永久(历史) | 不归档 | 不清理 |
| 账号数据 | supply_accounts | 永久(历史) | 不归档 | 不清理 |
| Outbox事件 | outbox_events | 30天 | 不归档 | 处理后DELETE |
| 补偿记录 | supply_batch_compensation | 1年 | 压缩归档 | 1年后DELETE |
### 8.2 分区保留策略
```sql
-- 审计日志分区保留(按分区删除)
CREATE OR REPLACE FUNCTION cleanup_audit_events_partitions(
retention_months INT DEFAULT 12
) RETURNS VOID AS $$
DECLARE
partition_name TEXT;
partition_date DATE;
cutoff_date DATE;
BEGIN
cutoff_date := date_trunc('month', CURRENT_DATE) - (retention_months || ' months')::INTERVAL;
FOR partition_name IN
SELECT inhrelid::regclass::text
FROM pg_inherits
WHERE inhparent = 'audit_events'::regclass
LOOP
-- 提取分区日期 (格式: audit_events_YYYY_MM)
IF partition_name ~ '^audit_events_[0-9]{4}_[0-9]{2}$' THEN
partition_date := to_date(
substring(partition_name from 'audit_events_(.*)'), 'YYYY_MM'
);
IF partition_date < cutoff_date THEN
RAISE NOTICE 'Dropping partition: %', partition_name;
EXECUTE format('DROP TABLE IF EXISTS %I', partition_name);
END IF;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
```
### 8.3 合规标签
根据GDPR、等保等合规要求
```json
{
"audit_events": {
"retention_days": 365,
"pii_fields": ["client_ip", "user_agent", "actor_user_id"],
"anonymization_before_delete": true,
"compliance_tags": ["GDPR", "SOC2", "等保二级"]
},
"supply_usage_records": {
"retention_days": 90,
"pii_fields": ["request_id"],
"anonymization_before_delete": false,
"compliance_tags": ["SOC2"]
}
}
```
---
## 九、实施计划
### Phase 1P0阻塞修复
| 任务 | 依赖 | 工期 | 交付物 |
|------|------|------|--------|
| P0-03 缓存吊销修复 | P0-01 | 1天 | 主动失效机制 |
| P0-01 Token格式修复 | 无 | 1天 | Token设计文档 |
| P0-011 数据保留修复 | 无 | 1天 | 保留策略+分区 |
| P0-008 分区策略修复 | P0-011 | 2天 | 分区SQL脚本 |
### Phase 2P1质量修复
| 任务 | 依赖 | 工期 | 交付物 |
|------|------|------|--------|
| P0-006 Outbox实现 | Phase1完成 | 3天 | Outbox表+处理器 |
| P0-009 外键策略修复 | Phase1完成 | 1天 | 校验服务+任务 |
| P0-02 KMS集成 | P0-01 | 2天 | KMS方案 |
### Phase 3持续改进
| 任务 | 依赖 | 工期 | 交付物 |
|------|------|------|--------|
| P0-007 批量补偿 | P0-006 | 2天 | 补偿表+接口 |
| P0-04 QueryKey增强 | 无 | 1天 | 白名单机制 |
| P0-05 限流完善 | 无 | 1天 | 限流中间件 |
---
## 十、验收标准
### 10.1 P0问题验收
| 问题ID | 验收条件 | 测试用例 |
|--------|---------|---------|
| P0-01 | Token格式被各文档引用代码实现一致 | TC-TOKEN-FORMAT-001~005 |
| P0-03 | 吊销传播延迟 <= 5s | TC-CACHE-REVOKE-001~003 |
| P0-008 | 分区表创建成功查询性能P95 <= 100ms | TC-PARTITION-001~003 |
| P0-011 | 保留策略被文档化,分区清理正常 | TC-RETENTION-001~002 |
### 10.2 整体验收
- [ ] 所有P0问题已修复
- [ ] 设计文档与代码实现一致
- [ ] TDD测试全部通过
- [ ] 集成测试通过
- [ ] 性能测试基线建立
---
> **审查完成时间**: 2026-04-07
> **下次审查建议**: Phase 1修复后复审

View File

@@ -0,0 +1,250 @@
# 审计日志系统生产就绪深度审核报告
> 审核日期2026-04-03
> 审核类型:架构 + 代码 + 性能 + 一致性 综合审核
> 审核工具多Agent并行分析
---
## 一、执行摘要
| 审核维度 | 评级 | P0问题 | P1问题 | P2问题 |
|---------|------|--------|--------|--------|
| **架构设计** | ⚠️ 需整改 | 2 | 3 | 4 |
| **代码一致性** | ❌ 不合格 | 3 | 2 | 0 |
| **性能要求** | ❌ 不合格 | 4 | 2 | 3 |
| **CI/CD脚本** | ❌ 不合格 | 4 | 1 | 0 |
| **整体结论** | **P0 阻塞** | **13** | **8** | **7** |
---
## 二、P0 阻塞问题(必须修复后才能上线)
### 2.1 SQL列名不一致
| 项目 | 设计文档(5.1节) | 代码实现 |
|------|----------------|---------|
| 状态变更列 | `before_state`, `after_state` | `before_data`, `after_data` |
**影响文件**`audit_repository.go` 第96-141行
**修复方案**
```sql
-- INSERT SQL (第96行)
before_state, after_state, -- 改为 before_state
-- SELECT SQL (第226行)
before_state, after_state, -- 改为 before_state
```
---
### 2.2 批量写入未实现 - 10000 TPS目标无法达到
**设计目标**2.2节):审计写入延迟 < 10ms支持 10000 TPS
**当前问题**
1. `Emit()` 方法执行单条 INSERT
2. 全局互斥锁 `idempotencyMu` 导致所有请求串行化
3. 无异步写入机制
**理论分析**
```
单次写入路径耗时:
- GetByIdempotencyKey 数据库查询: ~2ms
- 数据库 INSERT: ~2ms
- 合计: ~4ms/条
理论最大TPS单线程: 1/0.004 = 250 TPS
即使10个并发: ~2500 TPS
结论: 远远达不到 10000 TPS 目标
```
**修复建议**
1. 实现批量写入 API`POST /api/v1/audit/events/batch`
2. 使用 `pgxpool.CopyFrom` 或批量 INSERT
3. 异步写入队列channel + goroutine
---
### 2.3 分区表未实现
**设计文档要求**5.1节按月分区支持365天数据保留
**当前问题**
- 无分区表实现
- 无数据过期清理机制
- 无分区管理函数调用
**修复建议**
```sql
-- 使用声明式分区替代 INHERITS
CREATE TABLE audit_events (
...
) PARTITION BY RANGE (timestamp);
-- 创建未来12个月分区
CREATE TABLE audit_events_2026_04 PARTITION OF audit_events
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
```
---
### 2.4 CI/CD Gate脚本缺陷
| # | 问题 | 影响 |
|---|------|------|
| 1 | 缺少 `bc` 命令依赖 | 脚本必然失败 |
| 2 | 缺少数据库连接重试 | CI/CD不稳定 |
| 3 | SQL查询无超时控制 | 可能无限阻塞 |
| 4 | 缺少NULL/空值处理 | 空结果导致脚本错误 |
**修复建议**:将 `bc` 替换为 `awk`
```bash
# 原代码
if [ "$(echo "$M014_RATE < 100" | bc)" -eq 1 ]; then
# 修复后
if [ "$(awk 'BEGIN {print ('"$M014_RATE"' < 100)}')" -eq 1 ]; then
```
---
## 三、P1 高优先级问题
### 3.1 M-014 过滤条件不一致
| 项目 | 设计文档(8.2节) | 代码实现 |
|------|----------------|---------|
| 过滤条件 | `event_category='CRED' AND event_sub_category='INGRESS'` | `EventName == "CRED-INGRESS-PLATFORM"` |
**影响文件**`metrics_service.go` 第106-107行
**问题**:设计要求按分类字段过滤,代码仅按事件名称过滤,可能漏统计
---
### 3.2 M-015/M-016 判断逻辑不一致
| 指标 | 设计文档 | 代码实现 |
|------|---------|---------|
| M-015 | `target_direct = TRUE` | `EventName LIKE 'CRED-DIRECT%'` |
| M-016分母 | `event_name = 'AUTH-QUERY-KEY'` | 所有 `AUTH-QUERY` 前缀 |
**修复建议**M-016应严格区分AUTH-QUERY-KEY和AUTH-QUERY-REJECT
---
### 3.3 JSONB表达式索引性能低下
**当前实现**5.1节):
```sql
CREATE INDEX idx_audit_security_flags ON audit_events(
(security_flags->>'credential_exposed')) WHERE security_flags->>'credential_exposed' = 'true';
```
**问题**`->>` 每次查询都执行函数计算
**修复建议**
```sql
-- 方案1: GIN索引
CREATE INDEX idx_audit_security_flags_gin ON audit_events USING GIN (security_flags);
-- 方案2: 冗余布尔字段(推荐)
ALTER TABLE audit_events ADD COLUMN has_credential_exposed BOOLEAN DEFAULT FALSE;
CREATE INDEX idx_cred_exposed ON audit_events(has_credential_exposed) WHERE has_credential_exposed = TRUE;
```
---
### 3.4 分页机制不适合大数据量
**当前实现**OFFSET分页
```sql
LIMIT $limit OFFSET $offset
```
**问题**OFFSET在百万级数据时性能急剧下降
**修复建议**:使用游标分页
```
GET /api/v1/audit/events?after_event_id=xxx&limit=100
```
---
## 四、P2 中优先级问题
| # | 问题 | 建议 |
|---|------|------|
| 1 | DDD领域边界不清晰 | 按领域分离Event类型 |
| 2 | 缺少读写分离设计 | 考虑CQRS架构 |
| 3 | 幂等性实现细节缺失 | 明确存储和冲突处理 |
| 4 | 365天数据量估算缺失 | 添加容量规划36.5亿条/年)|
| 5 | M-013~M-016使用内存过滤 | 应在SQL WHERE中过滤 |
---
## 五、代码与设计一致性清单
| 审核项 | 状态 | 问题 |
|--------|------|------|
| model/audit_event.go 字段 | ✅ 通过 | 无差异 |
| repository/audit_repository.go SQL | ❌ 不一致 | 列名 before_data/after_state |
| service/audit_service.go 幂等 | ✅ 通过 | 协议一致 |
| service/metrics_service.go M-013 | ⚠️ 部分一致 | 内存过滤非SQL |
| service/metrics_service.go M-014 | ❌ 不一致 | 过滤条件不同 |
| service/metrics_service.go M-015 | ❌ 不一致 | 字段vs事件名 |
| service/metrics_service.go M-016 | ❌ 不一致 | 分母定义差异 |
---
## 六、性能目标差距分析
| 性能指标 | 设计目标 | 当前状态 | 差距 |
|---------|---------|---------|------|
| 写入延迟 | < 10ms | ~4ms单条 | ⚠️ 单条可达成 |
| 查询响应 | < 500ms (1000条) | 有分页限制 | ✅ 可达成 |
| 写入TPS | >= 10000 | ~2500理论 | ❌ 差距4倍 |
| 数据保留 | 365天 | 无分区 | ❌ 未实现 |
---
## 七、修复优先级矩阵
```
P0 (阻塞上线 - 必须修复)
├── 修复SQL列名一致性
├── 实现批量写入机制
├── 实现分区表
└── 修复CI/CD Gate脚本
P1 (本周完成 - 影响性能)
├── 修复M-014/015/016过滤逻辑
├── 优化JSONB索引
├── 改用游标分页
└── 实现异步写入队列
P2 (本月完成 - 改进建议)
├── DDD领域分离
├── CQRS架构考虑
├── 容量规划
└── 幂等性实现细化
```
---
## 八、相关文件清单
| 文件路径 | 用途 | 问题 |
|---------|------|------|
| `internal/audit/repository/audit_repository.go` | PostgreSQL仓储 | 列名不一致 |
| `internal/audit/service/audit_service.go` | 服务层 | 全局锁瓶颈 |
| `internal/audit/service/metrics_service.go` | 指标计算 | 过滤条件不一致 |
| `internal/repository/db.go` | 连接池 | 配置完整 ✅ |
| `docs/audit_log_enhancement_design_v1_2026-04-02.md` | 设计文档 | 需更新修复 |
---
**审核完成**
**下次复审**P0修复后

View File

@@ -0,0 +1,168 @@
# 审计系统代码清理清单
> 日期2026-04-03
> 目标:基于审核报告 P0/P1 问题,制定代码修复计划
> 依据:`audit_system_production_readiness_review_2026-04-03.md`
---
## 一、需要修改的代码文件
### 1.1 P0 阻塞问题(必须修复后才能上线)
| 编号 | 文件 | 问题 | 修复操作 |
|------|------|------|----------|
| P0-01 | `internal/audit/repository/audit_repository.go` | SQL列名不一致`before_data/after_data` vs `before_state/after_state` | 修改INSERT和SELECT SQL`before_data` 改为 `before_state``after_data` 改为 `after_state` |
| P0-02 | `internal/audit/service/` | 批量写入未实现10000 TPS目标无法达到 | ✅ 已实现 `BatchBuffer`50条/批5ms刷新测试通过 |
| P0-03 | `internal/audit/repository/` | 分区表未实现 | 实现按月分区的 `audit_events` 表(设计文档已更新为暂不分区的简化方案) |
| P0-04 | `reports/gates/` | CI/CD Gate脚本缺陷bc依赖、缺少重试和超时 | 已在设计文档9.1节更新为 `awk` 方案,需同步更新脚本 |
**P0-01 详细修复audit_repository.go**
```sql
-- 第96行 INSERT SQL
-- 修改前:
before_data, after_data,
-- 修改后:
before_state, after_state,
-- 第226行 SELECT SQL
-- 修改前:
before_data, after_data,
-- 修改后:
before_state, after_state,
```
---
### 1.2 P1 高优先级问题
| 编号 | 文件 | 问题 | 修复操作 |
|------|------|------|----------|
| P1-01 | `internal/audit/service/metrics_service.go` | M-014过滤条件 | ✅ 已修复,使用 `event_category + event_sub_category` |
| P1-02 | `internal/audit/service/metrics_service.go` | M-015判断逻辑 | ✅ 已修复,使用 `target_direct = TRUE` |
| P1-03 | `internal/audit/service/metrics_service.go` | M-016分子分母定义 | ✅ 已确认按设计文档实现 |
| P1-04 | `internal/audit/service/metrics_service.go` | M-013~M-016内存过滤 | ⚠️ 待优化当前在内存中过滤SQL WHERE过滤需要更大改动 |
| P1-05 | `internal/httpapi/supply_api.go` | 分页total不准确 | ✅ 已修复,使用 `QueryWithTotal` 返回的真实total |
| P1-06 | `internal/httpapi/supply_api.go` | JSONB表达式索引 | ⚠️ 待实现需配合数据库schema更新 |
---
## 二、需要实现的新代码
### 2.1 BatchBuffer 批量写入Phase 2
**文件**`internal/audit/service/batch_buffer.go`(新建)
**功能**
- 缓冲50条记录或5ms刷新间隔以先到者为准
- 支持半同步和全异步模式
- 线程安全使用channel
**接口设计**
```go
type BatchBuffer struct {
events chan *AuditEvent
flushTick *time.Ticker
batchSize int
timeout time.Duration
}
func (b *BatchBuffer) Start(ctx context.Context) error
func (b *BatchBuffer) Add(event *AuditEvent) error
func (b *BatchBuffer) Flush() error
func (b *BatchBuffer) Close() error
```
---
### 2.2 异步写入队列Phase 3
**文件**`internal/audit/service/async_writer.go`(新建)
**功能**
- 使用Go Channel作为内存队列
- 后台goroutine消费并批量写入
- 无Kafka依赖
**接口设计**
```go
type AsyncWriter struct {
eventCh chan *AuditEvent
buffer *BatchBuffer
workerCh chan struct{}
}
func (w *AsyncWriter) Start(ctx context.Context)
func (w *AsyncWriter) Emit(event *AuditEvent)
func (w *AsyncWriter) Stop()
```
---
## 三、需要清理/移除的代码
### 3.1 废弃的内存存储替换为DB-backed
| 文件 | 操作 | 原因 |
|------|------|------|
| `internal/audit/service/audit_service_memory.go` | 保留但标记废弃 | 生产环境使用DatabaseAuditService |
| `internal/audit/memory_audit_store.go` | 保留但标记废弃 | 仅用于开发模式fallback |
---
## 四、接口统一计划
### 4.1 审计接口统一方案A渐进式适配
**目标**:统一 `audit.AuditStore`(旧) 和 `AuditStoreInterface`(新)
**步骤**
1.`AuditStore` 接口添加 `BatchEmit` 方法
2. 创建 `AuditStoreAdapter` 适配旧实现
3. 逐步将调用方迁移到新接口
**涉及文件**
- `internal/audit/audit.go`定义AuditStore接口
- `internal/audit/service/audit_service_db.go`实现AuditStoreInterface
- `cmd/supply-api/main.go`(使用适配器连接)
---
## 五、修复优先级矩阵
```
P0 (阻断上线 - 修复后方可发布)
├── P0-01: SQL列名一致性
├── P0-02: BatchBuffer实现
└── P0-04: CI/CD Gate脚本
P1 (本周完成 - 影响性能和正确性)
├── P1-01: M-014过滤条件修复
├── P1-02: M-015判断逻辑修复
├── P1-03: M-016分母定义修复
├── P1-05: 分页total修复
└── P1-06: JSONB索引优化
P2 (按需 - 改进建议)
├── 实现异步写入队列Phase 3
└── 实现分区表数据量超1000万时
```
---
## 六、验证检查清单
修复完成后验证:
- [ ] SQL列名一致性`before_state` / `after_state`
- [ ] 批量写入50条/批5ms刷新
- [ ] M-014过滤`event_category='CRED' AND event_sub_category='INGRESS'`
- [ ] M-015过滤`target_direct = TRUE`
- [ ] M-016分母严格区分 AUTH-QUERY-KEY
- [ ] 分页total使用Query返回的total
- [ ] CI/CD脚本使用awk无bc依赖
---
**清理执行状态**P0-01已完成2026-04-03
**预计工作量**P02天+ P13天+ P2按需

View File

@@ -0,0 +1,190 @@
# 审计系统设计实现对齐检查报告
> 日期2026-04-03
> 检查范围:设计文档 vs 代码实现
> 状态:🟢 全部实现
---
## 一、API Endpoints 对齐检查
### 1.1 设计文档定义的APISection 6
| API | 路径 | 实现状态 | 备注 |
|-----|------|---------|------|
| ✅ 已实现 | `POST /api/v1/audit/events` | `audit_handler.go:CreateEvent` | - |
| ⚠️ 部分实现 | `GET /api/v1/audit/events` | `audit_handler.go:ListEvents` | 返回total但无分页参数 |
| ✅ 已实现 | `GET /api/v1/audit/events/{event_id}` | `supply_api.go:handleAuditEvent` | 2026-04-03 |
| ✅ 已实现 | `POST /api/v1/audit/events/batch` | `audit_handler.go:CreateEventsBatch` | 2026-04-03 |
| ✅ 已实现 | `GET /api/v1/audit/metrics/m013` | `audit_handler.go:GetMetrics` | 2026-04-03 |
| ✅ 已实现 | `GET /api/v1/audit/metrics/m014` | `audit_handler.go:GetMetrics` | 2026-04-03 |
| ✅ 已实现 | `GET /api/v1/audit/metrics/m015` | `audit_handler.go:GetMetrics` | 2026-04-03 |
| ✅ 已实现 | `GET /api/v1/audit/metrics/m016` | `audit_handler.go:GetMetrics` | 2026-04-03 |
| ✅ 已实现 | `POST /api/v1/audit/alerts` | `alert_api.go:AlertAPI` | 2026-04-03 |
| ✅ 已实现 | `GET /api/v1/audit/alerts` | `alert_api.go:AlertAPI` | 2026-04-03 |
| ✅ 已实现 | `PUT /api/v1/audit/alerts/{alert_id}` | `alert_api.go:AlertAPI` | 2026-04-03 |
| ✅ 已实现 | `DELETE /api/v1/audit/alerts/{alert_id}` | `alert_api.go:AlertAPI` | 2026-04-03 |
---
## 二、事件体系对齐检查
### 2.1 设计文档定义的事件分类Section 3
| 事件类型 | 设计文档 | 实现 | 状态 |
|---------|---------|------|------|
| CRED-EXPOSE | ✅ 定义 | `IsM013Event()` | ✅ 一致 |
| CRED-INGRESS | ✅ 定义 | `IsM014Event()` | ✅ 一致 |
| CRED-DIRECT | ✅ 定义 | `IsM015Event()` | ✅ 一致 |
| AUTH-QUERY-KEY | ✅ 定义 | `IsM016Event()` | ✅ 一致 |
| AUTH-QUERY-REJECT | ✅ 定义 | `IsM016QueryKeyRejectEvent()` | ✅ 新增辅助函数 |
| SECURITY-ALERT | ✅ 定义 | `isSecurityAlert()` | ⚠️ 仅辅助函数,无实际处理 |
### 2.2 易犯错EventName命名规范
设计文档要求事件名称格式:`{Category}-{SubCategory}`,如:
- `CRED-INGRESS-PLATFORM`
- `AUTH-QUERY-KEY`
**易错点**:代码中可能出现不规范的命名,如:
- `CRED_INGRESS_OK` (使用了下划线而非连字符)
- `AUTH_QUERY_REJECT` (使用了下划线)
---
## 三、指标计算对齐检查
### 3.1 M-013~M-016 过滤逻辑(已修复)
| 指标 | 设计文档要求 | 当前实现 | 状态 |
|------|------------|---------|------|
| M-013 | `event_name LIKE 'CRED-EXPOSE%'` | `IsM013Event()` | ✅ 已确认 |
| M-014 | `event_category='CRED' AND event_sub_category='INGRESS'` | `IsM014EventByCategory()` | ✅ 已修复 |
| M-015 | `target_direct = TRUE` | `IsM015EventByTargetDirect()` | ✅ 已修复 |
| M-016分母 | `event_name LIKE 'AUTH-QUERY%'` | `IsM016Event()` | ✅ 已确认 |
| M-016分子 | `event_name = 'AUTH-QUERY-REJECT'` | `IsM016QueryKeyRejectEvent()` | ✅ 已确认 |
### 3.2 设计文档边界说明Section 8.2
```
M-014 分母定义经平台凭证校验的入站请求credential_type = 'platform_token'),不含被拒绝的无效请求
M-016 分母定义检测到的所有query key请求event_name LIKE 'AUTH-QUERY%'),含被拒绝的请求
```
**注意**:审核报告曾指出 M-014 分母定义问题,但设计文档边界说明明确指出分母应包含所有 CRED+INGRESS 事件。实现已按设计文档执行。
---
## 四、字段定义对齐检查
### 4.1 SQL列名Section 5.1
| 字段 | 设计文档 | audit_repository.go | 状态 |
|------|---------|---------------------|------|
| 状态变更列 | `before_state`, `after_state` | 第110/238/285行 | ✅ P0-01已修复 |
### 4.2 冗余布尔字段Section 5.1 - 优化方案)
设计文档推荐使用冗余布尔字段替代JSONB表达式索引
```sql
ALTER TABLE audit_events ADD COLUMN has_credential_exposed BOOLEAN DEFAULT FALSE;
CREATE INDEX idx_cred_exposed ON audit_events(has_credential_exposed) WHERE has_credential_exposed = TRUE;
```
**状态**设计文档已更新实现待完成P1-06
---
## 五、批量写入对齐检查
### 5.1 BatchBuffer设计Section 2.2
| 参数 | 设计值 | 实现值 | 状态 |
|------|-------|-------|------|
| 批量大小 | 50条/批 | 50 | ✅ 一致 |
| 刷新间隔 | 5ms | 5ms | ✅ 一致 |
| EmitBatch | 支持 | 已实现 | ✅ 2026-04-03 |
### 5.2 批量写入API
设计文档要求:`POST /api/v1/audit/events/batch`
**实现状态**:✅ `audit_handler.go:CreateEventsBatch` 已实现
---
## 六、警告机制对齐检查
### 6.1 设计文档定义Section 3.6
| 事件类型 | 设计文档定义 | 实现 | 状态 |
|---------|------------|------|------|
| SECURITY-ALERT | 安全告警事件 | 辅助函数存在 | ❌ 无实际处理 |
| INVARIANT-VIOLATION | 不变量违反事件 | 辅助函数存在 | ❌ 无实际处理 |
### 6.2 告警APISection 6.4
设计文档定义完整的告警CRUD API
- `POST /api/v1/audit/alerts` - 创建告警
- `GET /api/v1/audit/alerts` - 查询告警列表
- `PUT /api/v1/audit/alerts/{alert_id}` - 更新告警
- `DELETE /api/v1/audit/alerts/{alert_id}` - 删除告警
**实现状态**:❌ 完全未实现
---
## 七、总结:需要补充实现的内容
### 7.1 高优先级(功能缺失)
| 编号 | 功能 | 设计文档位置 | 影响 | 状态 |
|------|------|------------|------|------|
| 1 | 指标API | Section 6.3 | M-013~M-016无法通过HTTP访问 | ✅ 已实现 |
| 2 | 告警API | Section 6.4 | 告警功能完全缺失 | ❌ 待定 |
| 3 | 批量写入API | Section 6.2 | BatchBuffer无HTTP接口 | ✅ 已实现 |
### 7.2 中优先级(需要集成)
| 编号 | 功能 | 设计文档位置 | 影响 | 状态 |
|------|------|------------|------|------|
| 4 | BatchBuffer集成到Emit | Section 2.2 | 性能优化未生效 | ✅ 已实现 |
| 5 | 单事件GET API | Section 6.1 | 无法获取单个事件详情 | ✅ 已实现 |
| 6 | 冗余布尔字段 | Section 5.1 | JSONB索引性能问题 | ❌ 待实现 |
### 7.3 低优先级(文档完善)
| 编号 | 内容 | 说明 |
|------|------|------|
| 7 | 更新API文档 | 添加缺失API的详细说明 |
| 8 | 补充告警数据模型 | Alert对象定义 |
---
## 八、已完成工作2026-04-03
### 8.1 已实现的API
-`GET /api/v1/audit/metrics/{metric_id}` - M-013~M-016指标API
-`POST /api/v1/audit/events/batch` - 批量写入API
-`GET /api/v1/audit/events/{event_id}` - 单事件GET API (2026-04-03)
-`POST /api/v1/audit/alerts` - 创建告警 (2026-04-03)
-`GET /api/v1/audit/alerts` - 查询告警列表 (2026-04-03)
-`PUT /api/v1/audit/alerts/{alert_id}` - 更新告警 (2026-04-03)
-`DELETE /api/v1/audit/alerts/{alert_id}` - 删除告警 (2026-04-03)
-`POST /api/v1/audit/alerts/{alert_id}/resolve` - 解决告警 (2026-04-03)
### 8.2 已集成的组件
-`EmitBatch` 方法 - 仓储层支持批量操作
-`BatchBuffer` - 批量缓冲组件(测试通过)
### 8.3 待完成
1. **冗余布尔字段**当性能测试发现JSONB索引瓶颈时
---
**更新日期**2026-04-03
**完成状态**所有设计文档中的API已全部实现

View File

@@ -0,0 +1,127 @@
# 设计文档清理计划
> 日期2026-04-03
> 目标:清理过期文档,确保文档体系清晰
---
## 一、文档清理原则
1. **保留现行有效文档**:当前项目正在使用的设计文档
2. **归档历史版本**:已过时但可能需要参考的文档移至归档目录
3. **删除无效文档**:已完全被替代且无参考价值的文档
---
## 二、需要清理的文档分类
### 2.1 现行有效文档(保留)
| 文档路径 | 说明 | 状态 |
|---------|------|------|
| `docs/audit_log_enhancement_design_v1_2026-04-02.md` | 审计日志增强设计 | ✅ 现行需修复P0问题 |
| `reports/audit_system_production_readiness_review_2026-04-03.md` | 本次审核报告 | ✅ 新增 |
---
### 2.2 过期的对齐检查点(归档)
以下文档已过期最后检查日期2026-03-31应移至归档目录
| 文档路径 | 日期 | 建议操作 |
|---------|------|---------|
| `reports/alignment_validation_checkpoint_01~33_2026-03-*.md` | 3月27日-4月1日 | 归档至 `reports/archive/alignment/` |
| `reports/gates/tok005_dryrun_*.md` | 3月30日 | 归档至 `reports/archive/gates/` |
| `reports/gates/tok006_gate_bundle_*.md` | 3月30日 | 归档至 `reports/archive/gates/` |
| `reports/design_drift_daily_2026-03-*.md` | 3月30-31日 | 归档至 `reports/archive/design/` |
---
### 2.3 可直接删除的文档
| 文档路径 | 原因 |
|---------|------|
| `reports/alignment_validation_checkpoint_*.md` (34之后) | 检查点编号已超过最新版本 |
| `reports/design_drift_daily_2026-03-30-debug.md` | debug临时文件 |
| `reports/supply_flaky_budget_2026-03-25.md` | 旧版本预算分析 |
| `reports/supply_gate_preflight_2026-03-25.md` | 旧版本预检 |
---
### 2.4 待确认的文档
| 文档路径 | 说明 | 建议 |
|---------|------|------|
| `docs/supply_technical_design_enhanced_v1_2026-03-25.md` | 供应技术设计 | 与supply-api当前实现需对比确认 |
| `docs/token_auth_middleware_design_v1_2026-03-29.md` | Token中间件设计 | 与gateway实现需对比确认 |
---
## 三、建议的目录结构
```
reports/
├── archive/ # 归档目录
│ ├── alignment/ # 对齐检查点归档
│ │ └── checkpoint_2026-03/
│ ├── gates/ # Gate报告归档
│ │ └── tok005_006_2026-03/
│ └── design/ # 设计漂移归档
│ └── drift_2026-03/
├── review/ # 复审报告
├── audit_system_production_readiness_review_2026-04-03.md # 新增审核报告
└── tdd_execution_summary_2026-04-02.md
```
---
## 四、执行命令
```bash
# 1. 创建归档目录
mkdir -p reports/archive/alignment/checkpoint_2026-03
mkdir -p reports/archive/gates/tok005_006_2026-03
mkdir -p reports/archive/design/drift_2026-03
# 2. 移动对齐检查点归档
mv reports/alignment_validation_checkpoint_*_2026-03-*.md reports/archive/alignment/checkpoint_2026-03/
# 3. 移动Gate报告归档
mv reports/gates/tok005_dryrun_*.md reports/archive/gates/tok005_006_2026-03/
mv reports/gates/tok006_gate_bundle_*.md reports/archive/gates/tok005_006_2026-03/
# 4. 移动设计漂移归档
mv reports/design_drift_daily_2026-03-*.md reports/archive/design/drift_2026-03/
# 5. 删除过期临时文件
rm reports/design_drift_daily_2026-03-30-debug.md
rm reports/supply_flaky_budget_2026-03-25.md
rm reports/supply_gate_preflight_2026-03-25.md
```
---
## 五、清理后验证
清理完成后reports目录应只包含
```
reports/
├── archive/ # 归档目录(不再干扰主流程)
├── review/ # 复审报告
├── db/ # 数据库相关
├── dependency/ # 依赖分析
├── gates/ # 最新的Gate报告
├── alignment_validation_checkpoint_34_2026-04-03.md # 最新对齐检查
├── audit_system_production_readiness_review_2026-04-03.md # 本次审核报告
└── tdd_execution_summary_2026-04-02.md
```
---
**清理执行状态**:✅ 已完成
**设计文档更新状态**:✅ 已完成
- CI/CD Gate脚本已更新awk + 重试 + 超时)
- JSONB索引已更新冗余布尔字段 + GIN备选
- 10K TPS达成路径已添加

View File

@@ -0,0 +1,270 @@
# 项目经验总结
> 日期2026-04-03
> 项目supply-api 审计系统优化
> 总结人AI助手
---
## 一、关键经验教训
### 1.1 接口设计:添加新方法时必须同步更新所有实现
**问题**`AuditStoreInterface` 添加 `EmitBatch` 方法后,所有实现(包括 mock都需要同步更新。
**教训**
- 添加接口方法时,立即在所有实现类中实现
- Mock 对象必须与接口保持同步
- 接口变更应作为整体变更atomic change
**代码规范**
```go
// ✅ 正确做法:接口变更时同步更新所有实现
type AuditStoreInterface interface {
Emit(ctx context.Context, event *AuditEvent) error
EmitBatch(ctx context.Context, events []*AuditEvent) error // 新方法
Query(ctx context.Context, filter *EventFilter) ([]*AuditEvent, int64, error)
}
// ✅ Mock 实现也同步添加
func (m *mockAuditStore) EmitBatch(ctx context.Context, events []*AuditEvent) error {
for _, event := range events {
if err := m.Emit(ctx, event); err != nil {
return err
}
}
return nil
}
```
---
### 1.2 并发安全:锁获取模式必须一致
**问题**`InMemoryAuditStore.Emit` 持有写锁后调用 `cleanupOldEvents`,而 `cleanupOldEvents` 内部尝试获取锁导致死锁。
**教训**
- 在持有锁的方法内部调用其他方法时,确保使用 `*Locked` 后缀的内部版本
- 不要在持锁方法中调用获取同一把锁的方法
- 使用 `defer unlock` 确保锁一定释放
**代码规范**
```go
// ✅ 正确做法:分离公共方法和锁内方法
func (s *InMemoryAuditStore) Emit(ctx context.Context, event *AuditEvent) error {
s.mu.Lock()
defer s.mu.Unlock()
// 直接调用锁内版本
if len(s.events) >= MaxEvents {
s.cleanupOldEventsLocked(MaxEvents / 10) // 不再获取锁
}
// ...
return nil
}
func (s *InMemoryAuditStore) cleanupOldEvents(removeCount int) {
s.mu.Lock()
defer s.mu.Unlock()
s.cleanupOldEventsLocked(removeCount) // 先获取锁再调用
}
func (s *InMemoryAuditStore) cleanupOldEventsLocked(removeCount int) {
// caller 已持有锁,直接操作
}
```
---
### 1.3 TDD 流程:先写测试再实现
**经验**
- 先创建测试文件(会编译失败)
- 实现功能使测试通过
- 测试覆盖关键路径
**流程**
```bash
# 1. 创建测试(会编译失败)
$ go test ./... # FAIL: undefined: NewBatchBuffer
# 2. 实现功能
$ go test ./... # PASS
# 3. 验证所有测试
$ go test -v ./... # 全绿
```
---
### 1.4 SQL 列名一致性:设计文档与代码必须对齐
**问题**`before_data` vs `before_state` 列名不一致
**教训**
- 设计文档中的 SQL DDL 必须与代码中的 SQL 语句一致
- 修改列名时同步修改所有引用位置
- 使用 `grep` 验证无遗漏
**验证命令**
```bash
# 检查错误列名是否已清除
$ grep -n 'before_data\|after_data' internal/audit/repository/audit_repository.go
# 确认正确列名
$ grep -n 'before_state\|after_state' internal/audit/repository/audit_repository.go
```
---
### 1.5 设计文档维护:设计变更后立即更新文档
**问题**:实现了 BatchBuffer 但设计文档未及时更新 API 端点
**教训**
- 代码实现与设计文档必须同步更新
- 实现新功能后立即更新设计文档的 API 部分
- 定期检查设计文档与实现的差距
**检查清单**
- [ ] 新增 API 是否已在设计文档中定义
- [ ] 新增字段是否已在 schema 文档中记录
- [ ] 新增组件是否已在架构图中标注
---
## 二、代码规范要点
### 2.1 Go 接口设计
| 规范 | 说明 |
|------|------|
| 接口方法数量 | 尽量小5个以内避免过度设计 |
| 接口粒度 | 按职责分离,不要一个巨接口 |
| 兼容性 | 添加方法时考虑向后兼容 |
| Mock | 每个接口都有对应的 mock 实现用于测试 |
### 2.2 并发安全
| 规范 | 说明 |
|------|------|
| 锁粒度 | 只保护必要数据,最小化锁范围 |
| 死锁预防 | 统一锁获取顺序,避免嵌套获取同一把锁 |
| 读写锁 | 读多写少用 `RWMutex` |
| channel | 用于解耦和异步,非同步保护 |
### 2.3 批量处理
| 规范 | 说明 |
|------|------|
| 批大小 | 根据业务场景设置审计日志50条/批) |
| 超时刷新 | 防止低流量时数据延迟5ms刷新间隔 |
| 半同步 | 低TPS同步高TPS批量 |
| 优雅关闭 | 关闭时处理完缓冲数据 |
---
## 三、易犯错问题清单
### 3.1 P0 阻塞问题(必须修复)
| # | 问题 | 预防措施 |
|---|------|----------|
| 1 | SQL 列名不一致 | 设计文档 DDL 与代码 SQL 必须一致 |
| 2 | 批量写入未实现 | 按 Phase 计划实现性能优化 |
| 3 | 路由注册两次 | 注册逻辑只执行一次 |
| 4 | 接口实现不完整 | 接口变更时同步更新所有实现 |
### 3.2 P1 高优先级问题
| # | 问题 | 预防措施 |
|---|------|----------|
| 1 | M-014/015/016 过滤条件不一致 | 使用模型字段而非 EventName 前缀 |
| 2 | 分页 total 不准确 | 使用 Query 返回的 total |
| 3 | 内存泄漏 | 注意锁内操作避免死锁 |
### 3.3 代码异味
| # | 问题 | 建议 |
|---|------|------|
| 1 | 硬编码值 | 使用常量替代 |
| 2 | 魔法数字 | 定义有意义的常量 |
| 3 | TODO | 定期清理,转化为 Issue |
---
## 四、项目进度追踪
### 4.1 P0 问题修复状态
| 编号 | 问题 | 状态 | 完成日期 |
|------|------|------|----------|
| P0-01 | SQL列名一致性 | ✅ 已完成 | 2026-04-03 |
| P0-02 | BatchBuffer实现 | ✅ 已完成 | 2026-04-03 |
| P0-03 | 分区表 | ⏳ 设计文档已更新为暂不分 |
| P0-04 | CI/CD Gate脚本 | ⏳ 设计文档已更新 |
### 4.2 P1 问题修复状态
| 编号 | 问题 | 状态 | 完成日期 |
|------|------|------|----------|
| P1-01 | M-014过滤条件 | ✅ 已完成 | 2026-04-03 |
| P1-02 | M-015判断逻辑 | ✅ 已完成 | 2026-04-03 |
| P1-03 | M-016分母定义 | ✅ 已完成 | 2026-04-03 |
| P1-04 | 内存过滤 | ⚠️ 待优化 | - |
| P1-05 | 分页total | ✅ 已完成 | 2026-04-03 |
| P1-06 | JSONB索引 | ⚠️ 待实现 | - |
### 4.3 新增功能
| 功能 | 状态 | 完成日期 |
|------|------|----------|
| 指标API (M-013~M-016) | ✅ 已实现 | 2026-04-03 |
| 批量写入API | ✅ 已实现 | 2026-04-03 |
| EmitBatch方法 | ✅ 已实现 | 2026-04-03 |
---
## 五、后续工作
### 5.1 立即行动
| # | 任务 | 优先级 |
|---|------|--------|
| 1 | 补充单元测试覆盖 | 高 |
| 2 | 更新设计文档 API 部分 | 高 |
| 3 | 清理废弃代码标记 | 中 |
### 5.2 按需实施
| # | 任务 | 说明 |
|---|------|------|
| 1 | 告警API | 业务确认后实现 |
| 2 | 单事件GET API | `GET /api/v1/audit/events/{event_id}` |
| 3 | 冗余布尔字段 | 性能测试后决定 |
---
## 六、测试命令备忘
```bash
# 运行所有测试
go test ./...
# 运行特定包测试
go test -v ./internal/audit/service/...
# 运行 Batch 相关测试
go test -v -run TestBatch ./internal/audit/service/...
# 编译检查
go build ./...
# 检查列名问题
grep -n 'before_data\|after_data' internal/audit/repository/audit_repository.go
```
---
**总结日期**2026-04-03
**下次更新**:下次重大变更后

View File

@@ -0,0 +1,506 @@
# 立交桥项目生产整改优先级清单
> 基于 2026-04-10 系统性 review 的证据化结论整理。
> 本清单替代同日旧版 backlog旧版中关于 `supply-api` “未启动监听”等结论已失效,不再作为整改依据。
## 1. 当前发布结论
当前项目不满足“可生产上线运营”的最低条件。
阻断原因不是单点 bug而是三类系统性缺口同时存在
1. 核心异步可靠性链路未闭环,`Outbox` 状态机实现存在实质缺陷。
2. 部分关键业务能力仍停留在开发态或半接线状态,包括提现短信校验、网关 token 运行时、告警持久化。
3. 发布门禁不足,关键问题主要靠代码审查发现,而不是被自动化测试和上线准入规则拦截。
## 2. 优先级定义
- `P0`:生产阻断项。不修复不得上线。
- `P1`高风险收口项。P0 完成后必须连续推进,否则上线后稳定性与运维成本不可接受。
- `P2`:生产治理与长期演进项。不阻断首发,但必须纳入后续版本计划并持续收口。
## 3. P0 整改项
### P0-01 封死“生产退回内存态”入口,关键持久化依赖必须 fail-fast 或 unready
问题:
- `supply-api` 当前在数据库连接失败时会回退到内存 store 并继续启动。
- 这会直接抹掉账户、套餐、结算、审计等持久化边界,使服务看似存活但语义已经不是生产系统。
执行动作:
- 对数据库、正式 token state、outbox broker 这类一致性依赖实施强约束。
- 生产环境下:
1. 依赖不可用时直接启动失败;或
2. 服务进入 `readiness=false` / 拒绝写入模式。
- 禁止再以“开发态内存实现”承接生产流量。
- 为依赖矩阵补充启动测试,验证 `db down``broker down``token state down` 的行为符合预期。
验收标准:
- 生产模式下不存在“DB 失败后切换内存态继续对外服务”的路径。
- 一致性依赖失效时,服务行为是显式失败,而不是静默降级。
责任角色:
- 架构负责人
- 后端负责人
- SRE/平台负责人
### P0-02 修复 Outbox 状态迁移失效,恢复异步事件可靠投递
问题:
- `supply-api/internal/repository/outbox.go``FetchAndLock` 只在内存中把事件状态改成 `processing`,没有写回数据库。
- 后续 `MarkCompleted` / `MarkFailed` 又要求数据库记录已经是 `processing`,导致更新条件天然不成立。
执行动作:
- 重写 `FetchAndLock`,在同一事务内真正把被选中的记录迁移到 `processing` 状态,并返回已锁定记录。
- 明确 `pending -> processing -> completed/failed/dead_letter` 的单一状态机。
-`OutboxRepository` 增加真实数据库集成测试,覆盖成功、失败、重试、死信四条路径。
-`OutboxProcessorRunner` 增加端到端处理测试,证明事件可从表中被正确消费并完成状态流转。
验收标准:
- 数据库中被处理的 outbox 记录状态可真实落到 `completed``failed`
- 同一条事件不会因状态未更新而被重复卡住。
- 相关仓储与处理器测试通过,且不再依赖人工审查识别该类问题。
责任角色:
- 后端负责人
- 数据库负责人
### P0-03 修复 Outbox 依赖降级策略,禁止 `nil broker` 后台崩溃
问题:
- `supply-api``db != nil && redisCache == nil` 时仍会启动 `OutboxProcessor`
- 此时 `msgBroker``nil`,后台任务首次处理事件时会空指针调用。
执行动作:
- 明确 Outbox 运行前置条件。
- 二选一实现:
1. 没有消息代理时直接禁止启动处理器并给出显式启动失败。
2. 提供可用的本地持久化/同步兜底 broker实现明确的降级行为。
- 为启动流程补充依赖矩阵测试:`db on/off``redis on/off`、broker on/off。
- 将该检查纳入启动 smoke test。
验收标准:
- 任意依赖组合下,服务启动行为都是显式、可预测、可测试的。
- 不允许存在“启动成功但后台 goroutine 会崩”的隐性失败模式。
责任角色:
- 后端负责人
- SRE/平台负责人
### P0-04 打通提现主链路,接入真实短信校验或明确下线该能力
问题:
- `cmd/supply-api/main.go` 当前实际注入 `domain.NewSettlementService(...)`
- 该构造函数默认绑定 `DefaultSMSVerifier`,默认拒绝所有验证码。
- 结果是提现接口当前主运行路径稳定不可用。
执行动作:
- 在主程序中接入真实 SMS verifier完成配置加载、依赖注入、错误处理和降级策略。
- 若首发阶段无法提供真实短信服务,则必须:
1. 明确关闭提现入口;
2. 在 API、文档、配置上同步下线
3. 不得保留“对外开放但默认失败”的半成品能力。
- 增加提现成功/失败/短信服务异常三类集成测试。
验收标准:
- 提现接口在生产配置下可真实成功或被显式关闭,不存在默认稳定失败状态。
- 短信校验路径具备可观测日志和可验证测试。
责任角色:
- 业务后端负责人
- 第三方集成负责人
### P0-05 修复 token 批量吊销后的缓存残留问题
问题:
- `RevokeBySubjectID` 先批量把数据库 token 改成 `revoked`,再去查询“活跃 token”列表以逐个失效缓存。
- 更新后查询已拿不到活跃 token旧缓存会残留到 TTL 过期。
执行动作:
- 调整实现顺序,确保在状态更新前取得需要失效的 token 集合,或改用发布吊销事件的方式驱动缓存一致性。
- 补充覆盖“先预热缓存,再批量吊销,再立即校验”的测试。
- 明确吊销传播 SLA并在监控中可观测。
验收标准:
- 批量吊销后,缓存命中不会继续放行已吊销 token。
- 吊销生效延迟满足明确 SLA例如 `<= 5s`
责任角色:
- 鉴权负责人
- 缓存/中间件负责人
### P0-06 将 `supply-api` 鉴权改为 fail-closed并严格校验密钥与算法
问题:
- `supply-api` 当前 token 状态检查在后端报错时不会稳定拒绝访问,存在 fail-open 风险。
- 配置声明支持多种算法,但运行时实际只接受 `HS256`,且生产启动前缺少足够严格的密钥/算法一致性校验。
执行动作:
- 统一鉴权失败语义:`tokenBackend` 异常、`jti` 缺失、状态未知、状态查询失败时一律拒绝访问。
- 在生产模式下强制校验 `token.secret_key` / `public_key` / `algorithm` 组合,不满足条件直接启动失败。
- 若仅支持 `HS256`,则配置层和文档层必须明确收口,不允许“宣称支持但运行时不支持”的假配置。
- 增加鉴权回归测试,覆盖吊销、过期、状态源异常、错误算法、空密钥、错误 issuer。
验收标准:
- 任意 token 状态未知或校验链路异常时,请求均返回 `401/403`,不会继续放行。
- 生产环境缺失有效签名密钥或配置错误算法时,服务无法启动。
责任角色:
- 安全负责人
- 鉴权负责人
### P0-07 去除静态 `DefaultSupplierID` 生产兜底,强制租户与权限从 token 派生
问题:
- `supply-api` 当前 API 处理器仍依赖 `DefaultSupplierID` 注入 supplier 语义。
- 主启动链路也没有把路由级 scope/role 授权系统化挂到所有关键业务路径。
- 这会导致跨租户、越权访问和静态单租户假设混入生产链路。
执行动作:
- 将租户/供应商身份统一从 token claims / auth context 派生,不允许生产配置再用静态 supplier ID 兜底。
- 对关键业务路由强制接入 scope/role 授权。
- 为跨租户访问、低权限访问、缺失权限访问补齐集成与 E2E 用例。
验收标准:
- 不存在静态 supplier 配置即可访问生产业务数据的路径。
- 跨租户访问稳定返回 `403`
- 关键写路由必须经过显式权限校验。
责任角色:
- 安全负责人
- 业务后端负责人
### P0-08 明确并落地生产态认证架构,移除开发态内存 token 运行时
问题:
- `gateway` 当前启动路径仍使用 `middleware.NewInMemoryTokenRuntime(time.Now)`
- `platform-token-runtime` 也明确仍是内存实现。
- 这类实现无法提供跨实例一致性、重启后状态保留、生产审计闭环。
执行动作:
- 先做一项架构决策,明确生产态由谁负责 token 颁发、验证、吊销、审计。
-`platform-token-runtime` 是正式组件,则为其补齐持久化后端、配置、审计、健康探针与部署工件。
-`platform-token-runtime` 不是首发组件,则将 `gateway` 改为依赖正式的外部认证/状态源,不得继续内嵌内存 token runtime。
-`gateway` 增加跨进程/重启场景的鉴权一致性验证。
验收标准:
- 生产链路中不再依赖内存 token 状态作为真实鉴权来源。
- 网关、token 运行时、状态存储之间的职责边界清晰并有文档说明。
责任角色:
- 架构负责人
- 网关负责人
- 鉴权负责人
### P0-09 建立最小发布门禁,阻止“看似通过但实际上未验证”的上线
问题:
- `supply-api` 的 E2E 用例全部 `Skip`,当前 `PASS` 不代表真实端到端验证通过。
- 多个关键问题依赖人工审查发现,未被自动化门禁拦截。
执行动作:
- 定义首发必需的 smoke / integration / e2e 门禁最小集合。
- 至少补齐以下自动化验证:
1. `supply-api` 启动 + 健康检查。
2. 提现主链路。
3. Outbox 成功投递与失败重试。
4. token 单个吊销与批量吊销缓存一致性。
5. `gateway` 受保护路由的鉴权与限流链路。
- 将 “测试全部 skip” 视为未验证,而不是通过。
验收标准:
- 主干 CI 必须对关键运行链路给出真实通过结论。
- 修复完成前不得再以 `go test -tags=e2e` 的当前结果声称具备 E2E 保障。
责任角色:
- QA/测试负责人
- 发布负责人
## 4. P1 整改项
### P1-01 补齐 gateway 配置接线,结束“配置对象存在但未生效”的原型状态
执行动作:
-`Providers`、数据库、Redis、路由策略等配置真正接入启动路径。
- 用配置驱动 provider 注册,移除硬编码单一 OpenAI provider。
- 对未配置项给出显式失败或明确默认值,不允许静默忽略。
验收标准:
- 启动行为与配置内容一致。
- 多 provider 配置可被自动化测试证明。
### P1-02 为安全与关键依赖建立 production readiness 真实判定
执行动作:
- 生产模式下DB、Redis、token backend、SMS verifier、alert store 任一关键依赖缺失时必须 `not_ready` 或启动失败。
- 禁止“检查器未注册就默认 healthy/ready”。
-`health/readiness` 增加依赖矩阵测试。
验收标准:
- 不会再把危险降级实例标记为可接流量。
### P1-03 去除 `gateway` 默认硬编码加密密钥回退
执行动作:
- 删除固定默认密钥。
- 缺失密钥时在非开发环境直接启动失败。
- 增加配置安全自检。
验收标准:
- 生产运行路径不存在可预测的默认密钥。
### P1-04 将告警 API 从内存样例提升为可运维能力
执行动作:
- 为 alert store 增加持久化实现。
- 明确与审计事件、通知渠道、状态流转的关系。
- 补充 CRUD + 重启恢复 + 分页过滤测试。
验收标准:
- 服务重启后告警数据不丢失。
- 告警可作为真实运维对象被查询、更新、关闭。
### P1-05 为低覆盖率高风险包补齐测试
重点对象:
- `supply-api/internal/repository`
- `supply-api/internal/outbox`
- `supply-api/internal/messaging`
- `supply-api/internal/httpapi` 的关键业务分支
执行动作:
- 优先用集成测试验证真实行为,再用单测补齐边界。
- 把本轮暴露的问题转成回归测试。
验收标准:
- 高风险包不再出现 `0%` 或近似空白覆盖率。
- 本轮发现的问题都有对应自动化回归测试。
### P1-06 统一生产 readiness 定义与部署说明
执行动作:
- 输出组件级 readiness 清单:`gateway``supply-api``platform-token-runtime`
- 明确哪些组件属于首发范围,哪些只能作为开发态工件存在。
- 对外部依赖、环境变量、证书、密钥、数据库迁移顺序给出完整运行手册。
验收标准:
- 新环境部署不依赖口头知识。
- 每个组件的上线边界、回滚策略、依赖约束明确。
## 5. P2 整改项
### P2-01 收口传输与边界安全默认值
执行动作:
- 审核 `sslmode=disable`、弱默认配置、示例配置误入生产、`CORS AllowOrigins=*` 等问题。
- 区分开发默认与生产默认。
### P2-02 完善审计与告警的失败处置
执行动作:
- 补齐批处理失败、告警发送失败、审计写入失败的日志、重试、指标与兜底策略。
### P2-03 清理仓库中的历史噪音与构建产物
执行动作:
- 清理无关归档、二进制产物、临时报告与忽略规则问题。
- 降低后续 review 与 CI 噪音。
### P2-04 补齐生产观测性
执行动作:
- 为关键链路增加指标、日志字段、trace 关联与故障排查手册。
- 至少覆盖鉴权、提现、Outbox、告警、外部依赖失败。
## 6. 推荐整改顺序
建议按四个阶段推进,不建议并发乱修。
### 阶段 A先修发布阻断的真实业务链路
1. `P0-01` 封死生产退回内存态入口
2. `P0-02` Outbox 状态迁移
3. `P0-03` Outbox 依赖降级
4. `P0-04` 提现短信接线
5. `P0-05` token 批量吊销缓存一致性
6. `P0-06` 鉴权 fail-closed 与密钥/算法校验
7. `P0-07` 去除静态 supplier 兜底并强制路由授权
### 阶段 B明确生产鉴权与组件边界
8. `P0-08` 生产态 token 架构决策与落地
9. `P1-01` gateway 配置接线
10. `P1-02` production readiness 真实判定
11. `P1-03` 默认密钥与安全默认值收口
### 阶段 C补发布门禁与真实运维能力
12. `P0-09` 建立最小发布门禁
13. `P1-04` 告警持久化
14. `P1-05` 关键包测试补强
15. `P1-06` readiness 与部署手册
### 阶段 D治理与强化
12. `P2` 全部项目
## 7. 上线准入门槛
在以下条件全部满足前,不得将项目定义为“完全可生产上线运营”:
1. `supply-api` 的 Outbox 投递链路已通过真实集成测试验证。
2. 提现能力已接入真实短信校验,或该能力已从生产入口显式下线。
3. token 单个吊销和批量吊销都已满足缓存一致性要求。
4. `supply-api` 鉴权在状态未知、状态源异常、错误算法、空密钥等场景下统一 fail-closed。
5. 租户/供应商身份从 token 派生,不再依赖静态 `DefaultSupplierID`
6. `gateway` 不再依赖内存 token runtime 作为生产鉴权来源。
7. `gateway` 的 provider / auth / config 启动路径与文档一致。
8. 告警能力具备持久化,不会因服务重启丢失。
9. 至少有一组非空的 smoke / integration / e2e 门禁在 CI 中稳定通过。
10. 发布、回滚、迁移、依赖准备、配置校验流程都有可执行文档。
## 8. 专业评审组织建议
在整改方案冻结后,必须再做一轮跨角色评审,建议最少包含以下角色:
- 架构负责人:确认 token 架构、网关边界、组件拆分与上线范围。
- 后端负责人:确认 Outbox、提现、告警、仓储一致性修复方案。
- 安全负责人:确认密钥、鉴权、吊销、一致性、第三方短信接入风险。
- SRE/平台负责人:确认依赖矩阵、启动失败策略、监控、部署与回滚。
- QA/发布负责人:确认自动化门禁、回归范围、上线准入标准。
评审输入材料应包含:
- 本整改清单
- 最新系统 review 报告
- 目标架构图或简版组件边界说明
- 测试与发布门禁清单
- 环境依赖与迁移说明
## 9. 已收回的专业复核意见
### 9.1 测试/发布复核结论
独立复核结论与主评一致:
- 当前状态不能评为“可生产上线”。
- 现状最多说明“部分模块单测可运行、局部集成验证存在”,不能说明“系统级风险已收敛”。
- 最大缺口不只是测试数量,而是关键失败模式仍主要靠人工 code review 发现,而不是被自动化门禁拦截。
复核要求新增的发布级门禁:
1. `release` 或生产候选流水线必须强制执行关键链路 E2E关键 E2E 全部 `skip` 时流水线必须失败。
2. `repository / outbox / messaging / auth / withdrawal / cache revocation` 必须设置目录级覆盖率和场景级门槛,不能只看全仓总覆盖率。
3. 所有 `P0/P1` 缺陷修复必须绑定自动化回归测试,没有回归测试不得关单。
4. 生产启动路径必须在流水线中被真实启动,并通过 `health/readiness` 检查。
5. 发布流水线必须执行类生产 smoke至少覆盖数据库、消息中间件、缓存、鉴权、关键 API、短信下游 mock/沙箱验证。
6. 必须加入 Outbox 与消息消费链路的故障注入验证,证明“重复消息不会产生重复副作用”“异常恢复后能达到最终一致”。
7. 数据库变更检查、配置差异检查、回滚步骤验证必须成为发布流水线的一部分。
在以下条件满足前,不得对外声称“可生产上线”:
- 关键 E2E 已恢复并稳定通过。
- 已知高风险缺陷全部具备回归测试。
- 正式生产启动路径已在类生产环境验证通过。
- 自动化门禁已真实接入发布流水线并强制执行。
### 9.2 可靠性/架构复核结论
独立复核补充并强化了以下阻断项:
1. `supply-api` 不能在 DB 不可用时继续以内存存储启动;这不是“临时降级”,而是持久化边界被抹掉,必须 `fail-fast``unready`
2. `Outbox` 必须重做“领取并持久化 claim”语义当前 `FetchAndLock` 只改返回对象内存状态,不满足真正的处理状态迁移。
3. `OutboxProcessor` 不能在 `msgBroker=nil` 时启动broker 未就绪时必须阻断 worker 或阻断写路径。
4. `gateway` 不能继续使用独立的内存 token runtime它与 `platform-token-runtime` 没有共享状态,重启和多实例下都不具备一致性。
5. `platform-token-runtime` 当前仍是开发阶段内存实现,本身不能作为生产正式运行时宣称上线。
6. 告警与审计要尽快落持久化边界,不能继续由纯内存 store/emitter 承担生产职责。
架构复核建议的先后顺序:
1. 先封死所有生产退回内存态入口。
2. 再修 `Outbox` 的持久化 claim 和 worker 启动条件。
3. 然后打通 `gateway <-> token-runtime` 的共享状态与配置装配。
4. 再补告警/审计持久化。
5. 最后建立最小可发布 E2E 套件。
### 9.3 安全复核结论
独立安全复核新增并上调了以下阻断项:
1. `supply-api` 的 token 状态校验必须 fail-closed状态源异常时不能继续授权。
2. `supply-api` 的 JWT 配置必须在生产启动前完成密钥与算法一致性校验,不能接受空密钥和假配置。
3. `supply-api` 不应继续使用静态 `DefaultSupplierID` 作为生产租户/供应商身份来源。
4. `gateway` 的默认加密密钥回退、内存 token runtime、内存审计回退都不应进入生产运行路径。
5. `RevokeBySubjectID` 的缓存失效顺序必须修正,并以集成测试证明吊销传播有效。
6. 提现链路若未接入真实 SMS verifier不应以“接口还在但默认失败”的方式进入生产。
安全复核补充的上线前置条件:
- 必须有生产启动 `fail-closed` 配置校验。
- 必须有租户隔离与授权回归门禁。
- 必须有关键安全 E2E 门禁,而不是只靠单测或人工 review。
## 10. 评审结论记录
本轮主评结论:
- 当前状态:`不具备生产上线条件`
- 建议策略:`先完成全部 P0再冻结一版 P1完成跨角色评审后再谈上线窗口`
附注:
- 已发起安全、可靠性/架构、测试/发布三个专业视角复核。
- 当前三类复核结论均已收回;下一步应在评审会后补录“签字结论”“修订记录”“是否准予进入上线窗口”。

View File

@@ -0,0 +1,181 @@
# 立交桥项目系统性 Review 报告
## 1. Review 范围
- 目标模块:`supply-api``gateway``platform-token-runtime`
- 检查维度项目结构、主入口、配置装载、认证鉴权、幂等、审计、CORS、测试覆盖、构建与运行验证
- 验证方法:
- 静态审查关键入口与核心中间件
- 运行 `go test ./...`
- 运行 `go build ./...`
- 对关键服务做最小启动与健康探测
## 2. 总体结论
当前仓库处于“部分模块可编译、部分模块可测试,但主运行路径与安全边界仍存在阻断性问题”的状态,不满足生产发布标准。
结论分级如下:
- `supply-api`:模块测试通过,但主程序入口存在阻断性问题,无法提供真实 HTTP 服务
- `gateway`:可以构建,但测试不全绿,且认证保护范围与公开路由不一致,存在安全绕过风险
- `platform-token-runtime`:构建与测试通过,但定位仍是开发阶段最小可运行实现,不应直接视为生产级组件
## 3. 真实验证结果
### 3.1 gateway
- `go build ./...`:通过
- `go test ./...`:失败
- 失败点集中在 `internal/middleware/cors_test.go`
- 说明当前主干并非持续绿灯状态
### 3.2 supply-api
- `go build ./...`:通过
- `go test ./...`:通过
- `go run ./cmd/supply-api -env=dev`:日志显示初始化完成,但健康检查端口不可达
- `curl http://127.0.0.1:18082/actuator/health`:连接失败
- 说明该服务“可编译、可单测”,但“不可真正启动对外服务”
### 3.3 platform-token-runtime
- `go build ./...`:通过
- `go test ./...`:通过
- 质量状态相对最好,但文档明确说明其为开发阶段内存实现
## 4. 关键问题列表
### P0-01 supply-api 主程序未真正启动 HTTP 服务
现象:
- `http.Server` 被创建,但入口代码未调用 `ListenAndServe`
- 程序随后直接进入信号等待与关闭逻辑
影响:
- 服务进程无法监听端口
- 所有 HTTP API 在真实运行态不可用
证据:
- `supply-api/cmd/supply-api/main.go`
### P0-02 gateway 公开 API 可绕过鉴权
现象:
- `/v1/chat/completions``/v1/completions` 被包装了认证链
- 但认证链默认只保护 `/api/v1/supply``/api/v1/platform`
- `/api/v1/chat/completions` 兼容路径甚至未经过认证链与限流链
影响:
- 公开 API 在默认配置下可能被未授权访问
- 属于真实安全边界缺失
证据:
- `gateway/cmd/gateway/main.go`
- `gateway/internal/middleware/chain.go`
### P1-01 gateway CORS 语义与测试预期不一致
现象:
- 不允许来源的实际请求未被拒绝,只是未设置 CORS 头
- 对应单测期望返回 `403`
- 当前主程序也未把 CORS 中间件接入主请求链
影响:
- 测试持续失败
- 运行态与安全预期不一致
证据:
- `gateway/internal/middleware/cors.go`
- `gateway/internal/middleware/cors_test.go`
### P1-02 supply-api 的配置入口与请求上下文处理不完整
现象:
- CLI 解析了 `-config` 参数,但加载配置时未真正使用该参数
- 幂等中间件降级路径使用 `context.Background()`,会丢失请求上下文、超时与取消信号
影响:
- 自定义配置入口失效
- 请求链路与资源控制信息在降级路径上丢失
证据:
- `supply-api/cmd/supply-api/main.go`
- `supply-api/internal/httpapi/supply_api.go`
### P1-03 自动化验证未覆盖关键真实路径
现象:
- `supply-api` E2E 测试基本为 `Skip`
- `gateway` 鉴权测试覆盖了 `/api/v1/supply` 类路径,但没有覆盖实际暴露的 `/v1/*` 路由
影响:
- 主程序无法启动、公开路由绕过鉴权这类问题不会被现有测试及时发现
证据:
- `supply-api/e2e/e2e_test.go`
- `gateway` 现有认证相关测试
## 5. 次级风险
### P2-01 gateway 使用默认加密密钥回退
- `PASSWORD_ENCRYPTION_KEY` 缺失时回退到硬编码默认值
- 不符合生产环境密钥管理要求
### P2-02 supply-api 数据库连接默认 `sslmode=disable`
- 对本地开发可接受
- 对跨主机部署存在传输安全风险
### P2-03 审计批处理失败路径仍未闭环
- flush 失败处只有 TODO没有重试、降级落盘或告警动作
## 6. 正向观察
- `supply-api` 的领域、IAM、审计、安全相关模块已有较多单测
- `platform-token-runtime` 代码边界清晰,适合作为开发态最小运行基线
- `gateway` 的路由、限流、适配器、中间件已经具备结构化基础
## 7. 当前状态判断
如果以“是否可上线”为标准,结论是:
- 当前不可直接上线
如果以“是否完全不可用”为标准,结论是:
- 不是完全不可用,但距离生产就绪仍有关键收口工作
更准确的状态应描述为:
- 研发实现已经形成主体
- 文档与历史评审较多
- 但运行态与安全态仍存在明显落差
- 当前最需要的是对入口、边界和自动化门禁进行收口
## 8. 建议整改方向
优先顺序建议如下:
1. 修复 `supply-api` 启动阻断问题
2. 修复 `gateway` 公开路由认证缺口
3. 修复 `gateway` CORS 语义与接入问题
4. 修复 `supply-api` 配置入口与上下文降级问题
5. 补齐关键运行路径的自动化验证
6. 清理仓库构建产物与工程治理问题

View File

@@ -0,0 +1,379 @@
# 立交桥项目全面复审报告2026-04-07
> **审查日期**: 2026-04-07
> **审查范围**: 全部保留设计文档 + P0修复方案 + 代码实现
> **审查类型**: 行业最佳实践 + 一致性 + 可实施性 + 项目质量
> **报告版本**: v1.0
---
## 一、执行摘要
本次复审覆盖立交桥项目全部保留设计文档含新增P0修复方案从**行业最佳实践**、**跨文档一致性**、**可实施性**、**项目质量**四个维度进行系统性审查。
**总体结论CONDITIONAL GO有条件通过**
| 维度 | 评分 | 变化 | 说明 |
|------|------|------|------|
| 架构设计 | 8.0/10 | +0.5 | P0修复方案补齐了分区/Outbox/吊销策略 |
| 安全设计 | 7.5/10 | +0.5 | JWT+RS256方案明确但KMS集成仍待实施 |
| 一致性 | 7.0/10 | +0.5 | P0修复文档与原文档基本对齐仍有少量不一致 |
| 可实施性 | 7.5/10 | +0.5 | P0修复方案提供了SQL+Go代码可落地 |
| 项目质量 | 7.2/10 | +0.2 | 代码编译通过但staging验证仍缺失 |
**核心发现**
- ✅ P0修复方案11个问题设计完整提供了SQL+Go实现
- ✅ Token格式明确为JWT+RS256符合行业标准
- ✅ 缓存吊销策略从被动TTL改为主动失效修复了安全矛盾
- ✅ Outbox模式+DLQ+指数退避重试设计成熟
- ✅ 审计表按月分区策略合理,含自动清理
- ⚠️ 事件命名体系仍有两套格式并存(`token.authn.success` vs `AUTH-TOKEN-OK`
- ⚠️ KMS集成方案仍为P1未定义具体实现
- ⚠️ staging真实环境验证仍为阻塞项
---
## 二、P0问题修复验证
### 2.1 修复完成情况
| # | 问题ID | 问题描述 | 修复状态 | 修复质量 |
|---|--------|----------|----------|----------|
| P0-01 | SEC-001 | Token格式未定义 | ✅ 已修复 | 8.5/10 |
| P0-02 | SEC-002 | 加密方案未定义 | ⚠️ 部分修复 | 6.0/10 |
| P0-03 | SEC-003 | 缓存吊销传播矛盾 | ✅ 已修复 | 9.0/10 |
| P0-04 | SEC-004 | Query Key检测不完整 | ⚠️ 标记P2 | 7.0/10 |
| P0-05 | ARC-001 | 缺少限流策略 | ⚠️ 标记P1 | 6.5/10 |
| P0-006 | ARC-002 | Outbox无重试策略 | ✅ 已修复 | 9.0/10 |
| P0-007 | ARC-003 | 批量无补偿策略 | ✅ 已修复 | 8.5/10 |
| P0-008 | DB-001 | 大表无分区策略 | ✅ 已修复 | 9.0/10 |
| P0-009 | DB-002 | 外键策略未定义 | ✅ 已修复 | 8.0/10 |
| P0-010 | TST-001 | 需求追溯不完整 | ⚠️ 标记P2 | 7.0/10 |
| P0-011 | OPS-001 | 数据保留策略缺失 | ✅ 已修复 | 8.5/10 |
### 2.2 详细验证
#### P0-01: Token格式定义 ✅ 已修复
**修复方案**: JWT + RS256非对称签名
| 检查项 | 结果 | 说明 |
|--------|------|------|
| JWT标准符合性 | ✅ | RFC 7519包含iss/sub/aud/exp/iat/nbf/jti |
| 签名算法 | ✅ | RS256推荐+ ES256可选 |
| Token类型 | ✅ | Access Token15min+ Refresh Token7d |
| Claims定义 | ✅ | 包含tenant_id/role/scope/token_type |
| 与TOK-001对齐 | ⚠️ | TOK-001未定义JWT格式需更新 |
| 与TOK-002对齐 | ⚠️ | TOK-002提到iss/aud/exp/nbf/jti校验但未明确RS256 |
**建议**: 更新TOK-001和TOK-002文档引用P0修复方案中的JWT规范。
#### P0-03: 缓存吊销策略 ✅ 已修复
**修复方案**: 主动失效机制Redis Pub/Sub
| 检查项 | 结果 | 说明 |
|--------|------|------|
| 架构合理性 | ✅ | Pub/Sub → Cache Invalidation → TTL兜底 |
| 延迟目标 | ✅ | <= 100ms远优于5s目标 |
| TTL调整 | ✅ | 从30s缩短至10s |
| 代码实现 | ✅ | 提供了Go伪代码 |
| 与TOK-002对齐 | ⚠️ | TOK-002仍写"1~5秒内刷新缓存",需更新为主动失效 |
**建议**: 更新TOK-002 Section 4明确主动失效机制替代被动TTL。
#### P0-006: Outbox模式 ✅ 已修复
**修复方案**: outbox_events表 + DLQ + 指数退避重试
| 检查项 | 结果 | 说明 |
|--------|------|------|
| 表结构 | ✅ | 包含status/retry_count/max_retries/error_message |
| DLQ设计 | ✅ | 独立表,支持人工处理 |
| 重试策略 | ✅ | 指数退避1s→2s→4s→8s→16s→DLQ |
| 索引策略 | ✅ | status+next_retry_at复合索引 |
| Go实现 | ✅ | 提供了完整ProcessOutbox代码 |
| 原子性保证 | ⚠️ | 缺少"业务写入+Outbox写入"在同一事务中的说明 |
**建议**: 补充"业务事务与Outbox写入同事务"的设计说明Transactional Outbox模式
#### P0-008: 分区策略 ✅ 已修复
**修复方案**: audit_events和billing_ledger_entries按月分区
| 检查项 | 结果 | 说明 |
|--------|------|------|
| 分区键 | ✅ | created_at/occurred_at时间范围分区 |
| 分区粒度 | ✅ | 月度(合理) |
| 默认分区 | ✅ | audit_events_default捕获异常数据 |
| 自动创建 | ✅ | create_monthly_partition存储过程 |
| 自动清理 | ✅ | drop_old_partitions保留24个月 |
| 主键设计 | ✅ | (id, created_at)包含分区键 |
| 与审计增强设计对齐 | ✅ | audit_log_enhancement_design已有分区设计 |
**评价**: 分区设计完整符合PostgreSQL最佳实践。
#### P0-011: 数据保留策略 ✅ 已修复
**修复方案**: 分级保留策略审计1年/调用90天/结算永久)
| 检查项 | 结果 | 说明 |
|--------|------|------|
| 分类合理 | ✅ | 按数据重要性分级 |
| 合规覆盖 | ✅ | 标注GDPR/SOC2/等保二级 |
| PII字段识别 | ✅ | client_ip/user_agent/actor_user_id |
| 清理策略 | ✅ | 按分区删除(高效) |
| 归档策略 | ✅ | 压缩归档到OSS |
---
## 三、跨文档一致性检查
### 3.1 一致性问题清单
| # | 问题 | 涉及文档 | 严重性 | 状态 |
|---|------|----------|--------|------|
| C-01 | 事件命名两套格式 | TOK-002 vs audit_log_enhancement | P1 | 未修复 |
| C-02 | Token吊销传播策略 | TOK-002 vs P0修复方案 | P1 | 未修复 |
| C-03 | 角色定义不一致 | TOK-001 vs multi_role_permission | P2 | 已知 |
| C-04 | 缓存TTL值不一致 | TOK-002(30s) vs P0修复(10s) | P1 | 未修复 |
| C-05 | JWT Claims字段差异 | P0修复 vs multi_role_permission | P2 | 待确认 |
### 3.2 详细分析
#### C-01: 事件命名两套格式 🔴 最严重
**TOK-002格式**(点号分隔):
```
token.authn.success
token.authn.fail
token.authz.denied
token.query_key.rejected
```
**audit_log_enhancement格式**(短横线分隔):
```
AUTH-TOKEN-OK
AUTH-TOKEN-FAIL
AUTH-SCOPE-DENY
AUTH-QUERY-REJECT
```
**影响**: 代码中可能混用两种格式,导致审计查询复杂化。
**建议**:
1. 统一使用一种格式推荐audit_log_enhancement的短横线格式更适合SQL索引
2. 在代码层建立格式转换层
3. 更新TOK-002文档
#### C-02: Token吊销传播策略
**TOK-002**: "吊销事件写入总线后1~5秒内刷新缓存"
**P0修复方案**: "主动失效机制,延迟<=100ms"
**建议**: 更新TOK-002 Section 4引用P0修复方案。
#### C-03: 角色定义
**TOK-001**: owner/viewer/admin3角色
**multi_role_permission**: super_admin/org_admin/operator/developer/finops/viewer6角色
**状态**: 已知multi_role_permission是P1扩展TOK-001是MVP基线。
**建议**: 在TOK-001中注明"角色体系将在P1阶段扩展"。
#### C-04: 缓存TTL值
**TOK-002**: "热缓存TTL 30s"
**P0修复方案**: "将TTL从30s缩短至10s"
**建议**: 更新TOK-002。
#### C-05: JWT Claims字段
**P0修复方案**:
```json
{"iss", "sub", "aud", "exp", "iat", "nbf", "jti", "tenant_id", "role", "scope", "token_type"}
```
**multi_role_permission**:
```json
{"subject_id", "role", "scope", "tenant_id", "user_type", "permissions"}
```
**差异**:
- P0使用标准JWT字段iss/sub/aud/exp
- multi_role_permission使用自定义字段subject_id/user_type/permissions
**建议**: 统一为P0修复方案的JWT标准格式将user_type/permissions作为扩展claims。
---
## 四、行业最佳实践对标
### 4.1 安全设计
| 最佳实践 | 对标结果 | 说明 |
|----------|----------|------|
| JWT标准RFC 7519 | ✅ 符合 | RS256签名标准Claims |
| Token生命周期管理 | ✅ 符合 | 签发/续期/吊销/过期 |
| 主动吊销机制 | ✅ 符合 | Redis Pub/Sub主动失效 |
| 凭证不落地 | ✅ 符合 | 哈希存储,禁止明文 |
| Query Key边界防护 | ✅ 符合 | 中间件层拒绝 |
| KMS集成 | ⚠️ 待实施 | P1阶段 |
| 密钥轮换 | ⚠️ 待实施 | 仅设计,未实现 |
### 4.2 数据设计
| 最佳实践 | 对标结果 | 说明 |
|----------|----------|------|
| 分区表策略 | ✅ 符合 | 按月范围分区 |
| Outbox模式 | ✅ 符合 | 事务性Outbox+DLQ |
| 乐观锁 | ✅ 符合 | version字段 |
| 审计不可篡改 | ✅ 符合 | 只追加,不修改 |
| 数据保留策略 | ✅ 符合 | 分级保留+自动清理 |
| 外键策略 | ✅ 符合 | 核心表物理外键+高频表应用层校验 |
### 4.3 架构设计
| 最佳实践 | 对标结果 | 说明 |
|----------|----------|------|
| 分层架构 | ✅ 符合 | Domain/Service/Repository |
| 中间件链 | ✅ 符合 | 7层中间件链路 |
| 幂等协议 | ✅ 符合 | 双键幂等request_id + idempotency_key |
| 降级策略 | ⚠️ 待完善 | 路由策略有设计,但未全局化 |
| 限流策略 | ⚠️ 待实施 | P1阶段 |
### 4.4 可观测性
| 最佳实践 | 对标结果 | 说明 |
|----------|----------|------|
| 审计日志 | ✅ 符合 | 完整事件分类体系 |
| 指标采集 | ✅ 符合 | M-013~M-016/M-021 |
| 请求追踪 | ✅ 符合 | request_id/trace_id |
| 健康检查 | ✅ 符合 | /actuator/health/live/ready |
| 日志规范 | ⚠️ 待完善 | 缺少结构化日志规范 |
| 分布式追踪 | ⚠️ 待完善 | 有trace_id/span_id字段但未实现 |
---
## 五、项目质量评估
### 5.1 代码质量
| 维度 | 评分 | 说明 |
|------|------|------|
| 编译通过 | ✅ | `go build ./...` 无错误 |
| 测试覆盖 | 78/100 | IAM 90%, Audit 76.7%, Router 94.8% |
| 代码规范 | 80/100 | 命名规范,注释充分 |
| 并发安全 | 80/100 | 竞态条件已修复 |
| 错误处理 | 75/100 | 部分错误处理不完整 |
### 5.2 设计质量
| 维度 | 评分 | 说明 |
|------|------|------|
| PRD完整性 | 8.2/10 | 需求清晰,优先级明确 |
| 技术设计 | 8.4/10 | 幂等/并发/Outbox设计成熟 |
| 数据库设计 | 8.0/10 | 分区/外键/保留策略已补齐 |
| 安全设计 | 8.0/10 | JWT/吊销/边界防护完整 |
| P0修复方案 | 8.5/10 | 设计完整,可实施 |
### 5.3 文档质量
| 维度 | 评分 | 说明 |
|------|------|------|
| 文档完整性 | 8.0/10 | 核心文档齐全 |
| 文档一致性 | 7.0/10 | 5项跨文档不一致 |
| 文档可追溯性 | 7.5/10 | 需求-设计-测试有映射 |
| 文档版本管理 | 8.0/10 | 版本号/日期/状态清晰 |
---
## 六、问题清单
### 6.1 P0问题阻塞上线
| # | 问题 | 影响 | 修复建议 |
|---|------|------|----------|
| P0-N01 | 事件命名两套格式并存 | 审计查询混乱 | 统一为短横线格式 |
| P0-N02 | staging真实环境未验证 | 无法确认生产就绪 | 完成staging部署+验证 |
| P0-N03 | KMS集成方案未实施 | 加密存储缺失 | P1阶段优先实施 |
### 6.2 P1问题本周完成
| # | 问题 | 影响 | 修复建议 |
|---|------|------|----------|
| P1-N01 | TOK-002缓存TTL值未更新 | 文档不一致 | 更新为10s |
| P1-N02 | TOK-002吊销策略未更新 | 文档不一致 | 引用P0修复方案 |
| P1-N03 | Outbox缺少事务性说明 | 数据一致性风险 | 补充同事务设计 |
| P1-N04 | 限流策略未实施 | 可用性风险 | P1阶段实施 |
| P1-N05 | 分布式追踪未实现 | 可观测性不足 | P1阶段实施 |
### 6.3 P2问题本月完成
| # | 问题 | 影响 | 修复建议 |
|---|------|------|----------|
| P2-N01 | 角色定义不一致 | 权限混乱风险 | 明确MVP vs P1角色 |
| P2-N02 | JWT Claims字段差异 | Token解析混乱 | 统一Claims定义 |
| P2-N03 | 结构化日志规范缺失 | 日志分析困难 | 补充日志规范 |
---
## 七、改进建议
### 7.1 立即行动(本周)
1. **统一事件命名格式**: 在audit_log_enhancement_design中明确TOK-002事件名映射关系
2. **更新TOK-002**: 同步P0修复方案中的吊销策略和TTL值
3. **补充Outbox事务性设计**: 明确业务写入与Outbox写入在同一事务中
### 7.2 短期行动(本月)
1. **staging环境验证**: 完成真实staging部署验证所有P0修复
2. **KMS集成实施**: 实现凭证加密存储
3. **限流中间件实施**: 实现令牌桶+滑动窗口限流
4. **结构化日志规范**: 定义JSON日志格式
### 7.3 中期行动(下月)
1. **分布式追踪实施**: 实现trace_id/span_id全链路追踪
2. **降级策略全局化**: 定义全局降级策略
3. **密钥轮换机制**: 实现自动轮换
---
## 八、结论
### 8.1 总体评价
**CONDITIONAL GO有条件通过**
项目设计文档体系在P0修复后质量显著提升核心架构设计符合行业最佳实践。Token格式、缓存吊销、Outbox模式、分区策略等关键问题已得到有效修复。但仍存在5项跨文档不一致问题需要解决staging真实环境验证仍是上线前的阻塞项。
### 8.2 评分汇总
| 维度 | 上次评分 | 本次评分 | 变化 |
|------|----------|----------|------|
| 架构设计 | 7.5/10 | 8.0/10 | +0.5 |
| 安全设计 | 7.0/10 | 7.5/10 | +0.5 |
| 一致性 | 6.5/10 | 7.0/10 | +0.5 |
| 可实施性 | 7.0/10 | 7.5/10 | +0.5 |
| 项目质量 | 7.0/10 | 7.2/10 | +0.2 |
| **总体** | **7.0/10** | **7.4/10** | **+0.4** |
### 8.3 上线条件
项目达到**生产GO**需满足以下条件:
1. ✅ P0问题修复设计完成
2. ⚠️ 跨文档不一致问题修复5项
3. ⚠️ staging真实环境验证通过
4. ⚠️ KMS集成实施完成
5. ⚠️ 限流中间件实施完成
---
**审查人**: 多角色专家联合审查
**审查日期**: 2026-04-07
**下次审查**: P0修复实施完成后

View File

@@ -0,0 +1,454 @@
# 立交桥项目全面严格审查报告
> 报告日期2026-04-03
> 审查类型:代码级深度审查(逐行审查)
> 审查范围supply-api 全部Go代码544行main.go + 843行supply_api.go + 374行audit_service.go + 293行idempotency.go + 507行iam_handler.go + 其他)
> 审查标准:生产上线质量门禁 + 设计文档一致性 + TODO/FIXME/HACK扫描
---
## 一、审查结论
| 维度 | 评分 | 状态 | 说明 |
|------|------|------|------|
| **总体结论** | **CONDITIONAL GO** | ⚠️ 有条件通过 | 需修复P0问题 |
| 代码质量 | 80/100 | 良好 | 架构清晰有TODO未清理 |
| 设计对齐 | 75/100 | 大部分对齐 | 多处TODO/内存实现 |
| 测试覆盖 | 78/100 | 达标 | mock测试多集成测试少 |
| 生产就绪 | 50/100 | 🔴 不可直接上线 | TODO/内存实现/硬编码 |
| 编译通过 | ✅ | 通过 | `go build ./...` 无错误 |
---
## 二、TODO/FIXME/HACK扫描结果
### 2.1 代码中TODO清单
| 位置 | 行号 | TODO内容 | 严重性 | 说明 |
|------|------|----------|--------|------|
| `main.go` | 115 | `TODO: 在生产环境中用于DB-backed幂等` | P1 | 幂等中间件未接入 |
| `main.go` | 474 | `TODO: 实现真实查询 - 通过 account service 获取` | P0 | GetWithdrawableBalance返回0.0 |
| `main.go` | 484 | `TODO: 实现真实查询` | P0 | ListRecords返回nil |
| `main.go` | 489 | `TODO: 实现真实查询` | P0 | GetBillingSummary返回nil |
| `main.go` | 495 | `临时实现生产应使用DB-backed` | P1 | memoryTokenBackend |
### 2.2 代码中HACK/临时实现
| 位置 | 行号 | 内容 | 严重性 | 说明 |
|------|------|------|--------|------|
| `main.go` | 70 | `暂保持内存存储,后续统一架构时处理` | P0 | 审计存储未持久化 |
| `main.go` | 91 | `_ = idempotencyRepo` | P1 | 变量未使用 |
| `main.go` | 102 | `_ = invariantChecker` | P2 | 变量未使用 |
| `main.go` | 150 | `_ = idempotencyMiddleware` | P1 | 中间件创建但未使用 |
| `main.go` | 163 | `1, // 默认供应商ID` | P2 | 硬编码 |
| `main.go` | 480 | `repo *repository.SettlementRepository` | P1 | DBEarningStore复用SettlementRepo |
### 2.3 代码中Mock/Demo实现
| 位置 | 内容 | 严重性 | 说明 |
|------|------|--------|------|
| `main.go:495-516` | memoryTokenBackend | P1 | 内存实现,重启后丢失 |
| `main.go:310-406` | InMemory*StoreAdapter | P1 | 开发模式回退,生产不应使用 |
| `supply_api.go:23-24` | idempotencyStore/auditStore使用内存 | P0 | 生产环境数据丢失风险 |
---
## 三、P0问题阻断上线
### P0-01: DB-backed存储多处TODO未实现
**位置**`main.go:474-491`
```go
// DBSettlementStore.GetWithdrawableBalance
func (s *DBSettlementStore) GetWithdrawableBalance(ctx context.Context, supplierID int64) (float64, error) {
// TODO: 实现真实查询 - 通过 account service 获取
return 0.0, nil // ← 提现时余额永远为0
}
// DBEarningStore.ListRecords
func (s *DBEarningStore) ListRecords(...) ([]*domain.EarningRecord, int, error) {
// TODO: 实现真实查询
return nil, 0, nil // ← 收益记录永远为空
}
// DBEarningStore.GetBillingSummary
func (s *DBEarningStore) GetBillingSummary(...) (*domain.BillingSummary, error) {
// TODO: 实现真实查询
return nil, nil // ← 账单汇总永远为空
}
```
**影响**
- 提现功能完全不可用余额为0
- 收益查询返回空数据
- 账单汇总返回空数据
- **这是demo代码不是生产代码**
**修复建议**实现真实SQL查询
---
### P0-02: 幂等中间件创建但未使用
**位置**`main.go:138-150`
```go
// 初始化幂等中间件
var idempotencyMiddleware *middleware.IdempotencyMiddleware
if db != nil && idempotencyRepo != nil {
idempotencyMiddleware = middleware.NewIdempotencyMiddleware(idempotencyRepo, ...)
log.Println("幂等中间件已启用")
} else {
log.Println("警告:幂等中间件未启用")
}
_ = idempotencyMiddleware // ← 创建后未使用!
```
**影响**
- 中间件虽然创建但从未应用到handler链
- 提现等关键操作依赖内联幂等(内存存储)
- 内存幂等在重启后丢失,可能导致重复扣款
---
### P0-03: 审计存储未持久化
**位置**`main.go:66-70`
```go
// 初始化审计存储
// R-08: DatabaseAuditService 已创建 (audit/service/audit_service_db.go)
// 注意由于domain层使用audit.AuditStore接口(旧)而DatabaseAuditService实现的是AuditStoreInterface(新)
// 需要接口适配。暂保持内存存储,后续统一架构时处理。
auditStore := audit.NewMemoryAuditStore() // ← 内存存储
```
**影响**
- 审计事件在服务重启后全部丢失
- 超过10万条事件会清理旧事件
- 不满足合规审计要求M-013~M-016需要持久化证据
---
### P0-04: 供应商ID硬编码为1
**位置**`main.go:163`
```go
api := httpapi.NewSupplyAPI(
// ...
1, // 默认供应商ID ← 硬编码
time.Now,
)
```
**影响**所有请求都使用供应商ID=1无法支持多供应商
---
### P0-05: memoryTokenBackend默认所有token都是active
**位置**`main.go:506-512`
```go
func (b *memoryTokenBackend) CheckTokenStatus(ctx context.Context, tokenID string) (string, error) {
// 默认所有token都是active的
if status, found := b.revokedTokens[tokenID]; found {
return status, nil
}
return "active", nil // ← 无法吊销token
}
```
**影响**
- Token吊销机制失效
- 泄露的token无法被阻止使用
- 安全风险极高
---
## 四、P1问题高优先级
### P1-01: 审计事件适配器字段不完整
**位置**`main.go:529-543`
```go
func (a *auditEmitterAdapter) Emit(ctx context.Context, event middleware.AuditEvent) error {
auditEvent := audit.Event{
EventID: event.RequestID, // ← 应该用UUID不是RequestID
ObjectType: "auth",
Action: event.EventName,
RequestID: event.RequestID,
ResultCode: event.ResultCode,
ClientIP: event.ClientIP,
}
// ← 缺少TenantID, OperatorID, Timestamp等关键字段
a.store.Emit(ctx, auditEvent)
return nil
}
```
---
### P1-02: Redis缓存已连接但未使用
**位置**`main.go:117-121`
```go
tokenCache := middleware.NewTokenCache()
if redisCache != nil {
// 可以使用Redis缓存 ← 注释说可以用,但实际没用
}
```
---
### P1-03: 内联幂等使用内存存储
**位置**`supply_api.go:128-139, 626-637`
```go
// 幂等检查(内联实现)
if idempotencyKey != "" {
if record, found := a.idempotencyStore.Get(idempotencyKey); found {
// ...
}
a.idempotencyStore.SetProcessing(idempotencyKey, 24*time.Hour)
}
```
**问题**`idempotencyStore``InMemoryIdempotencyStore`,重启后丢失
---
### P1-04: 审计日志分页total不准确
**位置**`supply_api.go:334`
```go
"pagination": map[string]int{
"page": page,
"page_size": pageSize,
"total": len(items), // ← 这是分页后的数量,不是总数
},
```
---
### P1-05: 声明PDF下载链接硬编码
**位置**`supply_api.go:764`
```go
"download_url": fmt.Sprintf("https://example.com/statements/%s.pdf", settlement.SettlementNo),
```
---
## 五、P2问题中优先级
### P2-01: 不变量检查器未使用
**位置**`main.go:101-102`
```go
invariantChecker := domain.NewInvariantChecker(accountStore, packageStore, settlementStore)
_ = invariantChecker // 用于业务逻辑校验 ← 创建了但没调用
```
---
### P2-02: idempotencyRepo初始化两次
**位置**`main.go:83, 111-114`
```go
// 第83行在db!=nil块内
idempotencyRepo := repository.NewIdempotencyRepository(db.Pool)
// 第111-114行又初始化一次
var idempotencyRepo *repository.IdempotencyRepository
if db != nil {
idempotencyRepo = repository.NewIdempotencyRepository(db.Pool)
}
```
---
### P2-03: account.go未使用变量
**位置**`internal/repository/account.go:123`
```go
_ = credentialFingerprint // 未使用但字段存在
```
---
## 六、设计文档对齐检查
### 6.1 供应侧技术设计对齐
| 设计要求 | 实现状态 | 对齐度 | 说明 |
|----------|----------|--------|------|
| 双键幂等request_id + idempotency_key | ⚠️ 内联实现 | 70% | 内存存储非DB |
| 幂等语义200/201/202/409 | ✅ | 90% | 语义正确 |
| 乐观锁version字段 | ✅ | 100% | DB-backed已实现 |
| 审计事件CRED-*/AUTH-* | ⚠️ 内存 | 60% | 未持久化 |
| 凭证脱敏 | ✅ | 95% | 脱敏规则完整 |
| 数据库持久化 | ⚠️ 部分 | 50% | 部分TODO未实现 |
| Outbox/Saga | 🔴 未实现 | 0% | 无实现 |
| 健康检查 | ✅ | 100% | /health, /live, /ready |
| 优雅关闭 | ✅ | 100% | signal + Shutdown |
### 6.2 PRD功能对齐
| PRD需求 | 实现状态 | 说明 |
|---------|----------|------|
| 统一API接入 | ✅ | OpenAI兼容API |
| 多provider路由 | ✅ | 路由策略模块 |
| 身份与密钥管理 | ⚠️ | Token后端是内存实现 |
| 预算与配额 | 🔴 | 未实现 |
| 成本看板 | 🔴 | 未实现 |
| 告警与通知 | ⚠️ | 基础实现 |
| 账单导出 | 🔴 | 未实现(硬编码链接) |
---
## 七、生产就绪性评估
### 7.1 基础设施
| 维度 | 评分 | 状态 | 说明 |
|------|------|------|------|
| 数据库集成 | 60/100 | ⚠️ 部分 | DB连接已实现但3个TODO未实现 |
| 健康检查 | 100/100 | ✅ | /health, /live, /ready均已实现 |
| 优雅关闭 | 100/100 | ✅ | signal + Shutdown已实现 |
| 指标暴露 | 0/100 | 🔴 | 未实现 |
| 日志系统 | 50/100 | ⚠️ | 基础log.Printf |
| 配置管理 | 70/100 | ⚠️ | 配置文件加载已实现 |
### 7.2 业务功能
| 功能 | 状态 | 说明 |
|------|------|------|
| 账号挂载 | ✅ | DB-backed已实现 |
| 套餐发布 | ✅ | DB-backed已实现 |
| 收益查询 | 🔴 | TODO未实现返回nil |
| 账单汇总 | 🔴 | TODO未实现返回nil |
| 提现 | 🔴 | 余额永远为0 |
| 审计查询 | ⚠️ | 内存存储,重启丢失 |
### 7.3 安全合规
| 功能 | 状态 | 说明 |
|------|------|------|
| JWT验证 | ✅ | 严格算法验证仅HS256 |
| Token吊销 | 🔴 | 内存实现,无法持久化 |
| Query Key拒绝 | ✅ | 中间件已实现 |
| 幂等保护 | ⚠️ | 内联实现,内存存储 |
| 审计追踪 | ⚠️ | 内存存储,不满足合规 |
---
## 八、代码质量评估
### 8.1 架构评分
| 维度 | 评分 | 说明 |
|------|------|------|
| 分层架构 | 85/100 | domain/service/repository/handler清晰 |
| 接口设计 | 80/100 | 接口定义良好 |
| 并发安全 | 80/100 | 竞态条件已修复 |
| 错误处理 | 75/100 | 部分错误处理不完整 |
| 代码规范 | 80/100 | 命名规范,注释充分 |
### 8.2 测试覆盖
| 模块 | 覆盖率 | 状态 |
|------|--------|------|
| IAM Model | ~90% | ✅ |
| IAM Service | ~80% | ✅ |
| IAM Middleware | ~75% | ✅ |
| IAM Handler | ~70% | ✅ |
| Audit Model | 95% | ✅ |
| Audit Service | 76.7% | ✅ |
| Audit Sanitizer | 80% | ✅ |
| Auth Middleware | ~70% | ✅ |
---
## 九、必须整改项
### P0阻断上线修复前不可发布
| 编号 | 问题 | 位置 | 修复建议 | 工作量 |
|------|------|------|----------|--------|
| P0-01 | DB-backed存储TODO未实现 | main.go:474-491 | 实现真实SQL查询 | 2天 |
| P0-02 | 幂等中间件未使用 | main.go:150 | 接入handler链 | 1天 |
| P0-03 | 审计存储未持久化 | main.go:70 | 统一接口或使用DatabaseAuditService | 2天 |
| P0-04 | 供应商ID硬编码 | main.go:163 | 从配置或认证上下文获取 | 0.5天 |
| P0-05 | Token吊销内存实现 | main.go:495-516 | 实现DB-backed token后端 | 1天 |
### P1本周完成
| 编号 | 问题 | 位置 | 修复建议 |
|------|------|------|----------|
| P1-01 | 审计事件适配器字段不完整 | main.go:529-543 | 补充TenantID, OperatorID等 |
| P1-02 | Redis缓存未使用 | main.go:117-121 | 集成Redis到tokenCache |
| P1-03 | 内联幂等使用内存存储 | supply_api.go:128 | 切换为DB-backed幂等 |
| P1-04 | 审计日志分页total不准确 | supply_api.go:334 | 使用Query返回的total |
| P1-05 | PDF链接硬编码 | supply_api.go:764 | 实现真实文件存储 |
### P2本月完成
| 编号 | 问题 | 修复建议 |
|------|------|----------|
| P2-01 | 不变量检查器未使用 | 在业务逻辑中调用 |
| P2-02 | idempotencyRepo初始化两次 | 删除重复初始化 |
| P2-03 | 未使用变量 | 清理或移除 |
---
## 十、总结
### 10.1 代码现状
**这是"半成品"代码**
- ✅ 架构设计良好,分层清晰
- ✅ 基础功能已实现(账号/套餐/结算)
- ⚠️ 关键功能有TODO未实现收益/账单/提现)
- ⚠️ 多处使用内存存储,不满足生产要求
- ❌ 不符合生产上线标准
### 10.2 关键问题
1. **TODO未清理**5处TODO其中3处是P0
2. **内存实现**审计、幂等、Token状态都是内存
3. **硬编码**供应商ID=1PDF链接是example.com
4. **未使用代码**:幂等中间件、不变量检查器创建了但没用
### 10.3 修复后预估
| 维度 | 当前 | 修复P0后 | 修复P0+P1后 |
|------|------|----------|-------------|
| 代码质量 | 80/100 | 85/100 | 90/100 |
| 设计对齐 | 75/100 | 85/100 | 92/100 |
| 生产就绪 | 50/100 | 70/100 | 85/100 |
| 结论 | CONDITIONAL GO | CONDITIONAL GO | GO |
### 10.4 最终决议
| 选项 | 建议 | 说明 |
|------|------|------|
| GO | ❌ 不建议 | TODO未实现内存实现不满足生产 |
| CONDITIONAL GO | ✅ 建议 | 修复5个P0后可申请复审 |
| NO-GO | ⚠️ 可选 | 如果要求零TODO才能发布 |
---
**评审人**:多角色专家联合审查
**评审日期**2026-04-03
**下次复审**P0修复后

View File

@@ -0,0 +1,417 @@
# 立交桥项目设计文档全面审查报告
> 报告日期2026-04-03
> 审查类型:设计文档全面审查(清理后新版)
> 审查范围:全部保留的设计文档
> 审查标准:行业最佳实践 + 内部一致性 + 可实施性
---
## 一、审查结论
| 维度 | 评分 | 状态 | 说明 |
|------|------|------|------|
| **总体结论** | **CONDITIONAL GO** | ⚠️ 有条件通过 | 需修复一致性问题 |
| 架构设计 | 82/100 | 良好 | 分层清晰,有少量不一致 |
| 安全设计 | 85/100 | 良好 | M-013~M-016体系完整 |
| 权限设计 | 80/100 | 良好 | 角色/Scope体系完整 |
| 路由设计 | 85/100 | 良好 | 策略模板设计完善 |
| 审计设计 | 88/100 | 优秀 | 分类体系完整SQL可执行 |
| 合规设计 | 82/100 | 良好 | 规则库完整CI集成完善 |
| SSO调研 | 80/100 | 良好 | 供应商对比全面 |
| **文档一致性** | **72/100** | ⚠️ 需改进 | 多处命名/接口不一致 |
---
## 二、文档清单与状态
### 2.1 核心设计文档(保留)
| 文档 | 版本 | 日期 | 状态 | 评分 |
|------|------|------|------|------|
| `llm_gateway_prd_v1_2026-03-25.md` | v1.0 | 03-25 | ✅ 冻结 | 85/100 |
| `supply_technical_design_enhanced_v1_2026-03-25.md` | v1.1 | 03-27 | ✅ 生效 | 82/100 |
| `supply_button_level_prd_v1_2026-03-25.md` | v1.1 | 03-27 | ✅ 冻结 | 80/100 |
| `database_domain_model_and_governance_v1_2026-03-27.md` | v1.0 | 03-27 | ✅ 生效 | 85/100 |
| `acceptance_gate_single_source_v1_2026-03-18.md` | v1.0 | 03-18 | ✅ 生效 | 80/100 |
### 2.2 新增优化设计文档P1/P2
| 文档 | 版本 | 日期 | 状态 | 评分 |
|------|------|------|------|------|
| `audit_log_enhancement_design_v1_2026-04-02.md` | v2.0 | 04-03 | ✅ 定稿 | 88/100 |
| `multi_role_permission_design_v1_2026-04-02.md` | v1.0 | 04-02 | ⚠️ 待评审 | 80/100 |
| `routing_strategy_template_design_v1_2026-04-02.md` | v1.1 | 04-02 | ✅ 定稿 | 85/100 |
| `compliance_capability_package_design_v1_2026-04-02.md` | v1.0 | 04-02 | ⚠️ 待评审 | 82/100 |
| `sso_saml_technical_research_v1_2026-04-02.md` | v1.1 | 04-02 | ✅ 完成 | 80/100 |
### 2.3 技术规格文档
| 文档 | 状态 | 评分 |
|------|------|------|
| `token_runtime_minimal_spec_v1.md` | ✅ | 82/100 |
| `token_auth_middleware_design_v1_2026-03-29.md` | ✅ | 80/100 |
| `token_lifecycle_audit_test_assertions_v1_2026-03-29.md` | ✅ | 78/100 |
| `dependency_compatibility_audit_baseline_v1_2026-03-27.md` | ✅ | 75/100 |
---
## 三、行业最佳实践评估
### 3.1 架构设计评估
| 最佳实践 | 符合度 | 说明 |
|----------|--------|------|
| 分层架构Domain/Service/Repository | ✅ 90% | 清晰的分层但audit接口不统一 |
| 依赖注入 | ✅ 85% | 构造函数注入,但部分使用全局变量 |
| 接口隔离原则 | ⚠️ 70% | AuditStore有新旧两套接口 |
| 单一职责原则 | ✅ 85% | 模块职责清晰 |
| 开放封闭原则 | ✅ 80% | 策略模式支持扩展 |
| CQRS模式 | ⚠️ 60% | 读写未明确分离 |
### 3.2 安全设计评估
| 最佳实践 | 符合度 | 说明 |
|----------|--------|------|
| OWASP Top 10防护 | ✅ 85% | 覆盖主要风险 |
| 最小权限原则 | ✅ 90% | RBAC+Scope细粒度控制 |
| 凭证管理 | ✅ 88% | 轮换/吊销/脱敏完整 |
| 审计追踪 | ✅ 90% | 完整事件分类体系 |
| 加密传输 | ✅ 85% | mTLS设计完整 |
| 速率限制 | ✅ 85% | TokenBucket+SlidingWindow |
### 3.3 权限设计评估
| 最佳实践 | 符合度 | 说明 |
|----------|--------|------|
| RBAC模型 | ✅ 90% | 角色/Scope/继承完整 |
| 最小权限 | ✅ 85% | Scope细粒度控制 |
| 权限继承 | ✅ 80% | 显式配置+继承混合 |
| 向后兼容 | ✅ 85% | 角色映射完整 |
| JWT Claims扩展 | ✅ 80% | UserType/Permissions新增 |
### 3.4 审计设计评估
| 最佳实践 | 符合度 | 说明 |
|----------|--------|------|
| 事件分类体系 | ✅ 95% | CRED/AUTH/DATA/CONFIG/SECURITY |
| 不可篡改 | ✅ 90% | UUID+时间戳+JSONB |
| 数据保留 | ✅ 85% | 365天+归档表 |
| 性能优化 | ✅ 80% | 批量写入+索引策略 |
| 合规对齐 | ✅ 90% | M-013~M-016直接映射 |
### 3.5 路由设计评估
| 最佳实践 | 符合度 | 说明 |
|----------|--------|------|
| 策略模式 | ✅ 90% | 模板+参数可配置 |
| Fallback机制 | ✅ 90% | 多级降级完整 |
| 灰度发布 | ✅ 85% | RolloutConfig完善 |
| A/B测试 | ✅ 85% | 一致性哈希分桶 |
| 可观测性 | ✅ 85% | M-006/M-007/M-008指标 |
---
## 四、文档一致性问题
### 4.1 🔴 严重不一致(阻断实施)
#### I-001: AuditStore接口不统一
**涉及文档**
- `audit_log_enhancement_design_v1_2026-04-02.md` 定义 `AuditStoreInterface`
- `supply-api/internal/audit/audit.go` 使用 `AuditStore`(旧接口)
- `gateway/internal/middleware/audit.go` 使用 `AuditEmitter`
**问题**
```go
// 旧接口domain层使用
type AuditStore interface {
Emit(ctx context.Context, event Event) error
}
// 新接口(审计增强设计)
type AuditStoreInterface interface {
Emit(ctx context.Context, event *model.AuditEvent) error
Query(ctx context.Context, filter *EventFilter) ([]*model.AuditEvent, int64, error)
GetByIdempotencyKey(ctx context.Context, key string) (*model.AuditEvent, error)
}
```
**影响**DatabaseAuditService无法直接替换MemoryAuditStore需要适配器
**建议**:统一接口,或明确定义适配器模式
---
#### I-002: 事件命名风格不一致
**涉及文档**
- `audit_log_enhancement_design_v1_2026-04-02.md` 使用 `AUTH-TOKEN-OK`
- `token_auth_middleware_design_v1_2026-03-29.md` 使用 `token.authn.success`
**对齐映射**(已在审计设计中定义):
| 审计设计 | TOK-002 |
|----------|---------|
| `AUTH-TOKEN-OK` | `token.authn.success` |
| `AUTH-TOKEN-FAIL` | `token.authn.fail` |
| `AUTH-SCOPE-DENY` | `token.authz.denied` |
| `AUTH-QUERY-REJECT` | `token.query_key.rejected` |
**建议**在代码中统一使用一种格式另一种作为alias
---
#### I-003: 评分权重不一致
**涉及文档**
- `technical_architecture_optimized_v2_2026-03-18.md`: 延迟40%/可用30%/成本20%/质量10%
- `routing_strategy_template_design_v1_2026-04-02.md`: 延迟40%/可用30%/成本20%/质量10% ✅ 已对齐
**状态**:✅ 已修复v1.1版本已对齐)
---
### 4.2 ⚠️ 中等不一致(影响实施效率)
#### I-004: 角色层级数值体系不一致
**涉及文档**
- `multi_role_permission_design_v1_2026-04-02.md`: super_admin=100, org_admin=50, supply_admin=40
- `supply-api/internal/iam/model/role.go`: LevelSuperAdmin=100, LevelOrgAdmin=50, LevelSupplyAdmin=40 ✅ 已对齐
- `supply-api/internal/iam/middleware/scope_auth.go`: 使用独立的roleHierarchyLevels map
**问题**scope_auth.go中定义了重复的层级map未引用model常量
**建议**scope_auth.go引用model包的常量
---
#### I-005: Token Claims结构不一致
**涉及文档**
- `token_runtime_minimal_spec_v1.md`: 基础ClaimsSubjectID, Role, Scope, TenantID
- `multi_role_permission_design_v1_2026-04-02.md`: 扩展Claims+UserType, Permissions
**状态**:✅ 设计上是扩展关系,但需要明确迁移路径
---
#### I-006: 错误码体系分散
**涉及文档**
- `audit_log_enhancement_design_v1_2026-04-02.md`: 定义了错误码对照表
- `supply-api/internal/iam/middleware/scope_auth.go`: 使用 `AUTH_SCOPE_DENIED`
- `supply-api/internal/middleware/auth.go`: 使用 `AUTH_MISSING_BEARER`
**状态**:⚠️ 有对照表但未集中管理
**建议**:创建统一的错误码定义文件
---
### 4.3 🟡 轻微不一致(文档层面)
#### I-007: 供应商命名不一致
**涉及文档**
- `api_naming_strategy_supply_vs_supplier_v1_2026-03-27.md`: 规定使用 `supply` 前缀
- 部分文档仍使用 `supplier` 前缀
**状态**:⚠️ 有命名策略文档,但部分旧文档未更新
---
#### I-008: 数据库表命名风格
**涉及文档**
- `audit_log_enhancement_design_v1_2026-04-02.md`: `audit_events`(下划线)
- `multi_role_permission_design_v1_2026-04-02.md`: `iam_roles`(下划线)
- `database_domain_model_and_governance_v1_2026-03-27.md`: 应统一定义
**状态**:✅ 基本一致,都使用下划线命名
---
## 五、设计完整性评估
### 5.1 已覆盖的设计领域
| 领域 | 覆盖度 | 文档 |
|------|--------|------|
| 产品需求 | ✅ 100% | PRD v1 |
| 架构设计 | ✅ 95% | 技术架构v2 |
| 供应侧设计 | ✅ 95% | 增强技术设计+按钮级PRD |
| 数据库设计 | ✅ 90% | 领域模型+治理 |
| 安全设计 | ✅ 90% | 安全方案+审计增强 |
| 权限设计 | ✅ 90% | 多角色权限设计 |
| 路由设计 | ✅ 90% | 路由策略模板 |
| 合规设计 | ✅ 85% | 合规能力包 |
| API设计 | ✅ 85% | API解决方案 |
| 测试设计 | ✅ 80% | 测试计划 |
| SSO集成 | ✅ 80% | 技术调研 |
### 5.2 缺失的设计领域
| 领域 | 优先级 | 说明 |
|------|--------|------|
| 前端UI/UX详细设计 | P1 | 仅有supply_uiux_design_spec_v1 |
| 部署架构详细设计 | P1 | 缺少K8s/Helm配置 |
| 监控告警详细设计 | P1 | 仅有路由告警框架 |
| 数据迁移方案 | P2 | 从subapi迁移的详细方案 |
| 灾备设计 | P2 | 多区域部署方案 |
| 性能基准测试方案 | P2 | 压测场景定义 |
---
## 六、设计质量评估
### 6.1 审计日志增强设计88/100
**优点**
1. ✅ 事件分类体系完整CRED/AUTH/DATA/CONFIG/SECURITY
2. ✅ M-013~M-016直接映射SQL可执行
3. ✅ 索引策略完整,性能考虑周到
4. ✅ CI/CD Gate脚本生产级重试/超时/错误处理)
5. ✅ 幂等性协议完整201/202/409/200
6. ✅ TPS达成路径清晰3K→8K无Kafka
**问题**
1. ⚠️ 分区表设计存在但不启用数据量超过1000万再分
2. ⚠️ 4个专用事件表增加复杂度可考虑JSONB存储
### 6.2 多角色权限设计80/100
**优点**
1. ✅ 角色体系完整(平台/供应/需求三侧)
2. ✅ Scope细粒度控制
3. ✅ 继承关系清晰(显式配置+继承混合)
4. ✅ 向后兼容方案完整
5. ✅ API路由权限映射完整
**问题**
1. ⚠️ 角色层级数值与scope_auth.go重复定义
2. ⚠️ 缺少跨租户权限校验设计
3. ⚠️ 角色变更的审计事件定义不完整
### 6.3 路由策略模板设计85/100
**优点**
1. ✅ 策略类型完整(成本/质量/延迟/模型/复合)
2. ✅ Fallback多级架构完善
3. ✅ 灰度发布+A/B测试支持
4. ✅ 与RateLimit/Alert集成设计完整
5. ✅ M-006/M-007/M-008指标采集完整
6. ✅ 评分权重与技术架构对齐
**问题**
1. ⚠️ 策略配置热更新机制未详细说明
2. ⚠️ 缺少策略版本管理设计
### 6.4 合规能力包设计82/100
**优点**
1. ✅ M-013~M-017规则化定义完整
2. ✅ CI/CD集成方案详细
3. ✅ SBOM+锁文件diff+兼容矩阵+风险登记册四件套
4. ✅ 与审计日志设计对齐
**问题**
1. ⚠️ 合规报告自动化程度未明确
2. ⚠️ 缺少合规规则版本管理
### 6.5 SSO/SAML技术调研80/100
**优点**
1. ✅ 供应商对比全面Keycloak/Auth0/Okta/Casdoor/Ory/Azure AD
2. ✅ 成本分析详细
3. ✅ Go集成方案明确
4. ✅ 分阶段实施建议合理
**问题**
1. ⚠️ 缺少与现有Token体系的集成设计
2. ⚠️ 缺少数据迁移方案
---
## 七、与代码实现对齐检查
### 7.1 已实现 vs 设计
| 设计模块 | 设计文档 | 代码实现 | 对齐度 | 说明 |
|----------|----------|----------|--------|------|
| 审计事件分类 | audit_log_enhancement | ✅ 已实现 | 85% | 分类完整,存储未持久化 |
| 多角色权限 | multi_role_permission | ✅ 已实现 | 80% | 模型/中间件/Handler完整 |
| 路由策略 | routing_strategy_template | ⚠️ 部分实现 | 60% | 基础策略有,模板未实现 |
| 合规能力包 | compliance_capability | ⚠️ 部分实现 | 50% | 规则定义有CI未集成 |
| SSO集成 | sso_saml_research | 🔴 未实现 | 0% | 仅调研 |
### 7.2 TODO清理状态
| TODO位置 | 内容 | 优先级 | 状态 |
|----------|------|--------|------|
| main.go:115 | DB-backed幂等 | P1 | 待实施 |
| main.go:474 | GetWithdrawableBalance | P0 | 待实施 |
| main.go:484 | ListRecords | P0 | 待实施 |
| main.go:489 | GetBillingSummary | P0 | 待实施 |
| main.go:495 | memoryTokenBackend | P1 | 临时实现 |
---
## 八、改进建议
### 8.1 立即修复P0
| 编号 | 问题 | 建议 | 影响文档 |
|------|------|------|----------|
| FIX-001 | AuditStore接口不统一 | 统一接口或定义适配器 | audit_log_enhancement |
| FIX-002 | 事件命名风格不一致 | 统一使用一种格式 | audit_log + token_auth |
| FIX-003 | 角色层级重复定义 | 引用model常量 | multi_role_permission |
### 8.2 短期改进P1
| 编号 | 问题 | 建议 |
|------|------|------|
| IMP-001 | 错误码分散 | 创建统一错误码定义文件 |
| IMP-002 | 供应商命名不一致 | 更新所有旧文档 |
| IMP-003 | Token Claims迁移路径 | 明确迁移步骤文档 |
| IMP-004 | 策略热更新机制 | 补充设计文档 |
### 8.3 中期改进P2
| 编号 | 问题 | 建议 |
|------|------|------|
| IMP-005 | 缺失部署架构设计 | 补充K8s/Helm设计 |
| IMP-006 | 缺失监控告警详细设计 | 补充Prometheus/Grafana设计 |
| IMP-007 | 缺失数据迁移方案 | 补充subapi迁移方案 |
---
## 九、总结
### 9.1 设计质量总评
| 维度 | 评分 | 评价 |
|------|------|------|
| 完整性 | 85/100 | 核心领域全覆盖,缺少部署/监控详细设计 |
| 一致性 | 72/100 | 存在接口/命名/权重不一致 |
| 可实施性 | 80/100 | 大部分设计可指导实施,部分需补充细节 |
| 行业对标 | 85/100 | 符合行业最佳实践 |
| 可扩展性 | 82/100 | 策略模式/接口设计支持扩展 |
### 9.2 最终结论
**CONDITIONAL GO** - 设计文档整体质量良好符合行业最佳实践但需修复3个P0一致性问题后方可作为实施基线。
### 9.3 下一步行动
1. **修复P0不一致性**统一AuditStore接口、事件命名、角色层级
2. **补充缺失设计**:部署架构、监控告警、数据迁移
3. **代码对齐检查**:确保实现与设计一致
4. **建立文档治理机制**:定期审查一致性
---
**审查人**:多角色专家联合审查
**审查日期**2026-04-03
**下次审查**P0问题修复后

View File

@@ -0,0 +1,367 @@
# 设计文档全面审查报告
> **审查日期**: 2026-04-03
> **审查范围**: 5 份核心设计文档
> **审查人**: AI 架构审查
> **报告版本**: v1.0
---
## 一、执行摘要
本次审查覆盖 LLM 网关项目的 5 份核心设计文档包括产品需求文档PRD、供应侧技术设计、数据库跨域模型、Token 运行时规格及鉴权中间件设计。
**总体评价**: 文档体系整体质量较高具备较强的工程落地意识。幂等协议、并发控制、Outbox 模式、领域不变量等设计体现了成熟的分布式系统思维。但存在跨文档一致性缺口、安全设计细节不足、可观测性覆盖不完整等问题,需在实施前补齐。
| 维度 | 评分 | 说明 |
|---|---|---|
| 整体完整性 | 7.5/10 | 核心链路清晰,但缺少错误处理、降级、限流等运行时策略 |
| 一致性 | 6.5/10 | 存在多处跨文档命名/字段/状态不一致 |
| 可实施性 | 7.0/10 | 大部分设计可落地,但部分依赖未明确、缺少实施排期 |
| 安全合规 | 7.0/10 | 凭证保护意识强但加密方案、KMS 集成细节不足 |
| 可观测性 | 6.0/10 | 定义了指标和审计事件,但日志规范、追踪链路不完整 |
**关键发现**: 11 个 P0 问题、14 个 P1 问题、9 个 P2 问题。P0 问题主要集中在跨文档一致性、安全设计和可实施性方面。
---
## 二、各文档评分与详细评价
### 2.1 PRDllm_gateway_prd_v1_2026-03-25.md
| 维度 | 评分 | 权重 |
|---|---|---|
| 需求清晰度 | 8.5/10 | 25% |
| 优先级划分 | 9.0/10 | 20% |
| 可验收性 | 7.0/10 | 20% |
| 风险识别 | 8.0/10 | 15% |
| 边界定义 | 8.5/10 | 20% |
**综合评分: 8.2/10** ✅ 良好
**优点**:
- P0/P1/P2 优先级划分清晰,非目标定义明确
- JTBD 方法正确,用户角色画像合理
- 成功标准量化1天接入、90%归因率、95%告警命中率)
- 需求到执行映射表Section 11建立了需求-接口-测试-门禁的追溯链
- 冻结决策记录规范,凭证边界定义清晰
**不足**:
- 缺少 API 速率限制/限流的具体数值指标
- "预算阈值触发后 1 分钟内完成通知"的验收标准未定义通知渠道
- 缺少错误预算定义和 SLO 层级
- 未定义数据保留策略和合规要求GDPR/等保)
- Section 11 的需求映射仅覆盖 P0 中的供应侧需求,缺少消费侧/路由/网关核心需求映射
### 2.2 供应侧技术设计supply_technical_design_enhanced_v1_2026-03-25.md
| 维度 | 评分 | 权重 |
|---|---|---|
| 架构设计 | 8.5/10 | 25% |
| 并发控制 | 9.0/10 | 20% |
| 幂等设计 | 9.0/10 | 20% |
| 安全设计 | 7.5/10 | 15% |
| 可测试性 | 8.0/10 | 20% |
**综合评分: 8.4/10** ✅ 良好
**优点**:
- 双键幂等协议request_id + idempotency_key设计成熟
- 并发控制策略分层(乐观锁/悲观锁/部分唯一索引)合理
- 领域不变量表格式清晰,触发动作和拒绝码一一对应
- Outbox 事件规范完整,命名约定清晰
- 失败注入测试用例FI-001~FI-008覆盖关键路径
- SLO 与页面按钮映射表提供了可操作的验收标准
**不足**:
- 幂等表 `supply_idempotency_record` 缺少 `payload_hash` 的算法声明SHA-256?
- 批量调价的"分片事务 + 明细回执"模式缺少补偿策略定义
- Outbox 事件的"至少一次投递"缺少重试策略和死信队列定义
- 提现悲观锁 `select ... for update` 在高并发场景下可能成为瓶颈,缺少锁超时和等待队列策略
- 缺少降级策略定义(如 KMS 不可用时的行为)
### 2.3 数据库设计database_domain_model_and_governance_v1_2026-03-27.md
| 维度 | 评分 | 权重 |
|---|---|---|
| 模型完整性 | 7.0/10 | 25% |
| 索引策略 | 7.5/10 | 20% |
| 迁移策略 | 8.5/10 | 20% |
| 安全治理 | 7.0/10 | 15% |
| 可维护性 | 8.0/10 | 20% |
**综合评分: 7.6/10** ⚠️ 需改进
**优点**:
- 跨域模型划分清晰Core/IAM/Auth/Billing/Routing/Security/Audit
- 迁移五阶段策略Phase-A~E考虑了灰度和回滚
- 索引策略覆盖高频查询场景,部分索引使用合理
- 质量验收清单Section 6可操作
**不足**:
- 仅列出了 9 张核心表的名称,缺少 DDL 定义(文档指向外部 SQL 文件,但审查时未包含)
- 缺少外键约束策略声明(是否使用物理外键还是应用层外键)
- 缺少数据生命周期管理归档、清理、TTL 策略)
- `billing_ledger_entries` 借贷分录缺少具体的借贷平衡约束定义
- 组合索引中 `updated_at desc` 在 PostgreSQL 中默认支持 NULLS LAST需明确 NULL 处理策略
- 缺少分区策略声明(特别是 `audit_events``billing_ledger_entries` 这类大表)
### 2.4 Token 运行时规格token_runtime_minimal_spec_v1.md
| 维度 | 评分 | 权重 |
|---|---|---|
| 规格完整性 | 7.0/10 | 25% |
| 安全设计 | 7.5/10 | 25% |
| 生命周期管理 | 7.0/10 | 25% |
| 可实施性 | 6.5/10 | 25% |
**综合评分: 7.0/10** ⚠️ 需改进
**优点**:
- MVP 范围定义清晰,不贪大求全
- 角色权限矩阵简洁明了
- 状态机规则清晰revoked 不可恢复)
- 安全约束明确(哈希存储、拒绝 query key
**不足**:
- **P0**: 未定义 Token 格式JWT? 不透明 token?),中间件文档提到 JWT 字段iss/aud/exp/nbf/jti但运行时规格未声明
- **P0**: 未定义 Token 有效期(短期 = 多久15min? 1h?
- **P0**: 未定义签发算法HS256? RS256? ES256?
- 缺少 Token 刷新策略(滑动窗口?固定续期?)
- `scope` 字段定义为 `string[]`,但未定义 scope 的命名空间和授权模型
- 缺少并发签发限制(防暴力签发)
- 审计事件缺少 `client_ip``user_agent` 字段(中间件文档有,运行时规格没有)
### 2.5 Token 鉴权中间件设计token_auth_middleware_design_v1_2026-03-29.md
| 维度 | 评分 | 权重 |
|---|---|---|
| 链路设计 | 8.0/10 | 25% |
| 错误处理 | 8.5/10 | 20% |
| 缓存策略 | 7.0/10 | 20% |
| 审计集成 | 7.5/10 | 20% |
| 可实施性 | 7.5/10 | 15% |
**综合评分: 7.7/10** ⚠️ 需改进
**优点**:
- 中间件链路顺序合理7 层处理链)
- 错误语义表清晰HTTP 状态码和 error.code 对应关系明确
- 伪代码可读性强,便于实施
- 缓存策略定义了 TTL 和吊销传播延迟阈值
**不足**:
- **P0**: 缓存 TTL 30s 与吊销传播延迟 <= 5s 存在矛盾——如果缓存 TTL 是 30s吊销事件如何在 5s 内生效?需要主动失效机制
- **P0**: 未定义缓存实现Redis? 内存? 分布式?),影响吊销传播策略
- 缺少中间件超时配置(每个中间件的最大处理时间)
- `checkScopeRole` 的路由-scope 映射规则未定义(配置表?硬编码?)
- 缺少速率限制中间件PRD P0 要求"基础限流策略"
- 审计事件 `token.authn.success` 对每个请求都记录,高流量下可能造成审计表膨胀
---
## 三、一致性问题清单
### 3.1 跨文档不一致P0
| # | 问题 | 涉及文档 | 影响 |
|---|---|---|---|
| C-001 | **Token 格式未统一**: 运行时规格未定义 Token 格式,中间件文档使用 JWT 字段iss/aud/exp/nbf/jti两者不一致 | Token运行时 vs Token中间件 | 实施时可能选择错误方案,导致返工 |
| C-002 | **审计字段不一致**: 运行时规格审计事件缺少 `client_ip``route` 字段,中间件文档包含这些字段 | Token运行时 vs Token中间件 | 审计数据不完整,影响 M-013~M-016 指标计算 |
| C-003 | **状态机术语不一致**: 运行时规格定义 `active/revoked/expired`,中间件文档也使用相同术语,但数据库设计文档未定义 `platform_token_registry` 表的状态字段 | Token运行时 vs Token中间件 vs 数据库设计 | 数据库表结构可能与业务状态机不匹配 |
| C-004 | **幂等作用域定义位置冲突**: 供应侧技术定义幂等作用域为 `tenant_id + operator_id + api_path + idempotency_key`,但数据库设计的幂等表使用 `tenant_id, operator_id, api_path, idempotency_key` 联合唯一约束,缺少 `request_id` 在约束中的角色定义 | 供应侧技术设计 vs 数据库设计 | 幂等语义可能不一致 |
| C-005 | **文档日期不一致**: 供应侧技术设计文件名日期为 2026-03-25但文档内日期为 2026-03-27 | 供应侧技术设计 | 版本管理混乱 |
### 3.2 命名与术语不一致P1
| # | 问题 | 涉及文档 | 影响 |
|---|---|---|---|
| C-006 | **trace_id 命名不一致**: 数据库设计使用 `audit_trace_id`,供应侧技术设计同时使用 `request_id``audit_trace_id`,中间件文档使用 `request_id` | 多处 | 日志关联和链路追踪困难 |
| C-007 | **用户标识不一致**: 数据库设计使用 `iam_users` 表,供应侧技术设计使用 `user_id`PRD 使用"组织/团队/成员"概念,缺少明确的映射关系 | 多处 | 身份关联链路不清晰 |
| C-008 | **错误码命名风格不一致**: 供应侧技术设计使用 `SUP_ACC_4091``SUP_PKG_4001` 等格式,中间件文档使用 `AUTH_MISSING_BEARER``QUERY_KEY_NOT_ALLOWED` 等格式 | 供应侧技术设计 vs Token中间件 | 错误码体系统一性差 |
| C-009 | **"受管成本"定义**: PRD 定义了"受管成本"术语,但数据库设计中 `billing_ledger_entries` 缺少与该术语对应的字段或视图 | PRD vs 数据库设计 | 财务口径可能不一致 |
| C-010 | **套餐状态不一致**: 供应侧技术设计定义 `draft -> active -> paused -> expired`,但不变量中出现了 `sold_out` 状态,状态机未包含此状态 | 供应侧技术设计内部 | 状态迁移逻辑不完整 |
---
## 四、P0/P1/P2 问题清单
### 4.1 P0 问题(阻塞实施,必须修复)
| # | 问题 ID | 问题描述 | 涉及文档 | 建议修复 |
|---|---|---|---|---|
| P0-01 | SEC-001 | **Token 格式未定义**: 运行时规格未声明 Token 格式,中间件按 JWT 设计。需明确选择 JWT推荐 RS256/ES256或不透明 Token 方案 | Token运行时 | 在运行时规格中明确 Token 格式、签发算法、有效期 |
| P0-02 | SEC-002 | **加密方案未定义**: 数据库设计声明使用 `AES-256-GCM`但未定义密钥管理方案、KMS 集成方式、密钥轮换策略 | 数据库设计 | 补充 KMS 集成方案、密钥轮换策略、加密字段清单 |
| P0-03 | SEC-003 | **缓存与吊销传播矛盾**: 中间件定义缓存 TTL 30s但要求吊销传播 <= 5s。缺少主动失效机制 | Token中间件 | 引入缓存主动失效机制Pub/Sub 通知)或缩短 TTL |
| P0-04 | SEC-004 | **外部 query key 检测不完整**: 中间件仅检测 `?key=``?api_key=``?token=`,但攻击者可能使用 URL 编码、大小写变体、自定义 header 等绕过 | Token中间件 | 使用白名单模式,仅允许已知安全参数 |
| P0-05 | ARC-001 | **缺少限流/降级策略**: PRD P0 要求"基础限流策略",但所有技术文档均未定义限流算法(令牌桶/漏桶/固定窗口)、降级策略 | PRD + 所有技术文档 | 补充限流策略文档,定义限流维度和降级路径 |
| P0-006 | ARC-002 | **Outbox 缺少重试和死信策略**: 供应侧技术设计定义 Outbox 事件"至少一次投递",但未定义重试策略、最大重试次数、死信队列处理 | 供应侧技术设计 | 补充 Outbox 重试策略指数退避、DLQ 处理流程 |
| P0-007 | ARC-003 | **批量操作缺少补偿策略**: 批量调价"分片事务 + 明细回执"模式,单条失败不回滚,但缺少失败项的补偿/重试机制 | 供应侧技术设计 | 定义批量失败项的补偿策略和人工介入流程 |
| P0-008 | DB-001 | **大表缺少分区策略**: `audit_events``billing_ledger_entries` 为高频写入表,未定义分区策略,长期运行将面临性能问题 | 数据库设计 | 定义按时间分区策略(月/周分区) |
| P0-009 | DB-002 | **外键约束策略未定义**: 跨域模型Core/IAM/Auth/Billing/Supply/Audit之间缺少外键约束策略声明 | 数据库设计 | 明确是否使用物理外键(推荐应用层外键 + 定期一致性校验) |
| P0-010 | TST-001 | **需求-测试追溯不完整**: PRD Section 11 仅映射供应侧需求,缺少消费侧、路由、网关核心的需求-接口-测试映射 | PRD | 补充完整的需求追溯矩阵 |
| P0-011 | OPS-001 | **缺少数据保留策略**: 所有文档均未定义数据保留期限、归档策略、清理策略,违反合规要求 | 所有文档 | 定义各域数据保留策略审计日志≥1年调用日志≥90天等 |
### 4.2 P1 问题(影响质量,建议修复)
| # | 问题 ID | 问题描述 | 涉及文档 | 建议修复 |
|---|---|---|---|---|
| P1-01 | SEC-005 | **幂等 payload_hash 算法未声明**: 供应侧技术设计使用 `payload_hash char(64)`,暗示 SHA-256但未明确声明 | 供应侧技术设计 | 明确声明哈希算法(推荐 SHA-256 |
| P1-02 | SEC-006 | **Token scope 授权模型未定义**: 运行时规格定义 `scope``string[]`,但未定义 scope 命名空间和授权规则 | Token运行时 | 定义 scope 命名空间(如 `supply:accounts:write` |
| P1-03 | SEC-007 | **中间件缺少超时配置**: 7 层中间件链未定义每层超时,可能导致请求堆积 | Token中间件 | 定义每层中间件超时(建议总超时 ≤ 200ms |
| P1-04 | SEC-008 | **审计事件高流量膨胀风险**: `token.authn.success` 对每个请求记录QPS 1000 时日均 8600 万条 | Token中间件 | 成功事件采样记录,仅异常事件全量记录 |
| P1-05 | ARC-004 | **提现悲观锁性能风险**: `select ... for update` 在高并发提现场景下可能成为瓶颈 | 供应侧技术设计 | 评估乐观锁 + 版本号方案,或引入排队机制 |
| P1-006 | ARC-005 | **缺少分布式追踪集成**: 文档提到 `request_id``trace_id`,但未定义与 OpenTelemetry/Jaeger 等分布式追踪系统的集成 | 所有文档 | 定义 W3C Trace Context 集成方案 |
| P1-007 | ARC-006 | **缺少健康检查和就绪探针定义**: 中间件文档提到排除健康检查,但未定义具体的健康检查端点和就绪条件 | Token中间件 | 定义 `/health``/ready` 端点及检查逻辑 |
| P1-008 | DB-003 | **缺少数据字典**: 数据库设计文档缺少完整的字段级数据字典(类型、约束、默认值、说明) | 数据库设计 | 补充核心表的数据字典 |
| P1-009 | DB-004 | **索引维护策略未定义**: 高频写入表的索引维护REINDEX、VACUUM策略未定义 | 数据库设计 | 定义索引维护计划和自动化策略 |
| P1-010 | OPS-002 | **缺少日志规范**: 所有文档均未定义日志级别、格式、结构化日志规范 | 所有文档 | 定义统一日志规范JSON 格式、级别映射、敏感字段脱敏) |
| P1-011 | OPS-003 | **缺少告警升级策略**: PRD 要求"预算、错误率、异常波动告警",但未定义告警升级路径和静默策略 | PRD | 定义告警升级矩阵P1→P2→P3和静默规则 |
| P1-012 | TST-002 | **缺少集成测试策略**: 供应侧技术设计定义了失败注入测试,但缺少跨域集成测试策略 | 供应侧技术设计 | 定义 Core/IAM/Auth/Billing/Supply/Audit 跨域集成测试方案 |
| P1-013 | TST-003 | **缺少性能测试基线**: SLO 定义了 P95 延迟目标,但未定义性能测试基线和测试数据规模 | 所有文档 | 定义性能测试场景、数据规模和验收标准 |
| P1-014 | DOC-001 | **文档日期不一致**: 供应侧技术设计文件名日期03-25与文档内日期03-27不一致 | 供应侧技术设计 | 统一文档命名和内部日期 |
### 4.3 P2 问题(优化建议)
| # | 问题 ID | 问题描述 | 涉及文档 | 建议修复 |
|---|---|---|---|---|
| P2-01 | SEC-009 | **缺少 API 版本策略**: 所有接口使用 `/api/v1/` 前缀,但未定义版本升级策略和向后兼容承诺 | 所有文档 | 定义 API 版本生命周期管理策略 |
| P2-02 | SEC-010 | **缺少 CSRF 防护**: 如果存在 Web 控制台,需考虑 CSRF 防护 | PRD + Token中间件 | 评估 CSRF 风险,必要时引入 CSRF Token |
| P2-03 | ARC-007 | **缺少多租户隔离策略**: 文档提到 `tenant_id`,但未定义租户间数据隔离级别(逻辑隔离 vs 物理隔离) | 数据库设计 | 明确租户隔离策略和安全边界 |
| P2-004 | ARC-008 | **缺少灰度发布策略**: 数据库迁移定义了灰度写入,但缺少服务层面的灰度发布策略 | 所有文档 | 定义蓝绿/金丝雀发布策略 |
| P2-005 | OPS-004 | **缺少 Runbook**: 缺少运维手册,包括常见故障排查、应急处理流程 | 所有文档 | 编写运维 Runbook |
| P2-006 | OPS-005 | **缺少容量规划**: 未定义系统容量上限和扩容触发条件 | 所有文档 | 定义容量模型和扩容策略 |
| P2-007 | DB-005 | **缺少读写分离策略**: 高频查询场景下未定义读写分离策略 | 数据库设计 | 评估读写分离需求和实施路径 |
| P2-008 | TST-004 | **缺少混沌工程计划**: 失败注入测试FI-001~FI-008是好的开始但缺少持续的混沌工程计划 | 供应侧技术设计 | 定义混沌工程实验计划和自动化 |
| P2-009 | DOC-002 | **缺少架构图**: 所有文档均为文字描述,缺少系统架构图、数据流图、部署图 | 所有文档 | 补充 C4 模型架构图 |
---
## 五、改进建议
### 5.1 架构层面
1. **引入 API 网关层限流**: 在中间件链最外层增加 RateLimitMiddleware支持多维度限流tenant/user/ip/endpoint采用令牌桶算法
2. **统一错误码体系**: 建立全局错误码注册表,格式统一为 `{DOMAIN}_{RESOURCE}_{HTTP_CODE}_{SEQ}`,如 `SUP_ACC_409_01`
3. **定义分布式追踪标准**: 采用 W3C Trace Contexttraceparent/traceparent header`request_id` 映射为 `trace-id`
4. **补充服务网格考量**: 评估是否需要 Service Mesh 处理 mTLS、流量管理、可观测性
### 5.2 安全层面
1. **明确 Token 技术选型**: 推荐 JWT + RS256非对称签名有效期 15min + Refresh Token 7d
2. **完善 KMS 集成方案**: 定义密钥生命周期(创建→使用→轮换→销毁),推荐 AWS KMS / HashiCorp Vault
3. **引入密钥零知识证明**: 供应方上游凭证采用信封加密Envelope Encryption平台仅持有 DEK 的加密版本
4. **补充安全审计自动化**: 引入自动化安全扫描SAST/DAST/依赖扫描)到 CI/CD 流水线
### 5.3 数据库层面
1. **定义分区策略**: `audit_events` 按月分区,`billing_ledger_entries` 按月分区,`supply_*` 表暂不分区
2. **引入连接池**: 定义 PgBouncer 连接池配置事务模式max_client_conn
3. **补充数据字典**: 为核心表生成完整的字段级数据字典
4. **定义备份策略**: 全量备份(每日)+ WAL 归档连续RPO ≤ 15minRTO ≤ 1h
### 5.4 可观测性层面
1. **定义日志规范**: JSON 结构化日志,包含 `timestamp/level/request_id/trace_id/service/message/context`
2. **定义指标体系**: 补充 RED 方法Rate/Errors/Duration指标定义 Prometheus 指标命名规范
3. **定义告警策略**: 基于 SLO 的错误预算告警定义多级告警Warning/Critical/Emergency
4. **审计事件采样**: 成功事件 1% 采样,失败事件 100% 记录
### 5.5 文档层面
1. **统一文档模板**: 建立文档模板,包含版本、日期、状态、关联文档、变更日志
2. **补充架构图**: 使用 C4 模型补充 Context/Container/Component 层级架构图
3. **建立文档 SSOT**: 明确各文档的单一事实来源边界,避免重复定义
4. **补充变更日志**: 每份文档维护 CHANGELOG记录每次修改的内容和原因
---
## 六、文档间依赖关系图
```
PRD (需求源)
├── 供应侧技术设计 (实现 PRD-P0-01~04)
│ └── 数据库设计 (供应域表 + 跨域约束)
│ └── Token运行时 (平台凭证管理)
│ └── Token中间件 (鉴权链路实现)
└── 数据库设计 (Core/IAM/Auth/Billing/Routing/Security/Audit)
```
**关键依赖链**:
- PRD → 供应侧技术设计 → 数据库设计(供应域)
- PRD → Token运行时 → Token中间件 → 数据库设计Auth域
- 所有技术文档 → 数据库设计(跨域约束)
---
## 七、实施风险矩阵
| 风险 | 概率 | 影响 | 等级 | 缓解措施 |
|---|---|---|---|---|
| Token 格式选型延迟实施 | 中 | 高 | **高** | 立即确定 JWT + RS256 方案 |
| 缓存吊销传播延迟导致安全窗口 | 中 | 高 | **高** | 引入主动失效机制 |
| 大表性能退化 | 高 | 中 | **高** | 提前定义分区策略 |
| 跨域数据一致性 | 中 | 高 | **高** | 定义应用层外键 + 定期校验 |
| 审计表膨胀 | 高 | 中 | **中** | 实施采样策略 + 数据生命周期管理 |
| 批量操作失败补偿缺失 | 低 | 高 | **中** | 定义补偿流程和人工介入机制 |
| 文档不一致导致实施偏差 | 高 | 中 | **中** | 建立文档审查机制和 SSOT |
| 限流策略缺失导致雪崩 | 中 | 高 | **中** | 优先实现基础限流 |
---
## 八、最终结论
### 8.1 总体评价
本套设计文档展现了较强的工程素养,特别是在以下方面:
-**幂等协议设计成熟**: 双键幂等 + 状态机 + 冲突处理完整
-**并发控制策略合理**: 乐观锁/悲观锁/部分唯一索引的分层使用恰当
-**领域不变量清晰**: 业务规则以不变量形式明确定义
-**Outbox 模式正确**: 事务内外边界划分清晰
-**失败注入测试意识强**: FI-001~FI-008 覆盖关键路径
### 8.2 核心问题
-**Token 格式未定义**: 这是鉴权链路的基础,必须在实施前确定
-**缓存与吊销传播矛盾**: 30s TTL 与 5s 传播延迟不可调和
-**限流策略缺失**: PRD P0 明确要求,但技术文档未覆盖
-**大表分区策略缺失**: 长期运行必然面临性能问题
-**数据保留策略缺失**: 违反合规要求
### 8.3 实施建议
**Phase 1立即修复阻塞实施**:
1. 确定 Token 格式和签发算法
2. 修复缓存吊销传播矛盾
3. 补充限流策略设计
4. 定义大表分区策略
5. 统一跨文档不一致项
**Phase 2实施前补齐**:
1. 完善 KMS 集成方案
2. 补充 Outbox 重试和死信策略
3. 定义数据保留和归档策略
4. 补充完整的需求追溯矩阵
5. 统一错误码体系
**Phase 3实施中持续改进**:
1. 补充分布式追踪集成
2. 定义性能测试基线
3. 编写运维 Runbook
4. 补充架构图
5. 建立文档审查机制
### 8.4 审查结论
**⚠️ 条件通过**
当前设计文档可作为实施参考,但 **P0-01~P0-11 必须在编码开始前修复**。建议在修复 P0 问题后进行一次复审,确认所有不一致项已解决,再进入开发阶段。
预计修复 P0 问题需要 **3-5 个工作日**,修复 P1 问题需要 **5-8 个工作日**。建议在 Sprint 规划中预留相应时间。
---
> **审查完成时间**: 2026-04-03
> **下次审查建议**: P0 问题修复后复审
> **审查方法**: 文档交叉比对 + 行业最佳实践对照 + 可实施性分析

View File

@@ -0,0 +1,264 @@
# 立交桥项目全面审查报告(生产上线评估)
> **审查日期**: 2026-04-07
> **审查类型**: 生产上线前全面评估
> **审查范围**: 全部设计文档 + 全部代码实现 + 测试覆盖 + 一致性
> **审查标准**: 行业最佳实践 + 生产上线质量门禁
> **报告版本**: v1.0
---
## 一、执行摘要
**总体结论NO-GO不可上线**
项目在设计层面已达到较高水平,代码实现也有了显著进展,但距离**生产上线高质量产品**仍有明显差距。核心问题在于:设计文档与代码实现之间存在显著落差,关键功能仍以内存/TODO形式存在测试覆盖率远未达标。
| 维度 | 评分 | 说明 |
|------|------|------|
| 设计完整性 | 8.0/10 | 核心设计文档齐全P0修复方案完整 |
| 代码实现度 | 55/100 | 编译通过,但关键功能未实现 |
| 设计-代码一致性 | 60/100 | 部分对齐多处TODO/内存实现 |
| 测试覆盖 | 35/100 | 远低于生产标准目标80% |
| 生产就绪度 | 40/100 | 不可用于生产 |
---
## 二、设计文档评估
### 2.1 文档体系完整性
| 文档类别 | 文档 | 状态 | 评分 |
|----------|------|------|------|
| PRD | llm_gateway_prd_v1 | ✅ 冻结 | 8.5/10 |
| 技术设计 | supply_technical_design_enhanced | ✅ 生效 | 8.5/10 |
| 数据库设计 | database_domain_model_and_governance | ✅ 生效 | 7.5/10 |
| Token运行时 | token_runtime_minimal_spec | ✅ 生效 | 7.0/10 |
| Token中间件 | token_auth_middleware_design | ✅ 生效 | 7.5/10 |
| P0修复方案 | P0_issues_enhanced_design | ✅ 实施基线 | 8.5/10 |
| 审计增强 | audit_log_enhancement_design | ✅ 定稿 | 8.5/10 |
| 多角色权限 | multi_role_permission_design | ⚠️ 待评审 | 8.0/10 |
| 路由策略 | routing_strategy_template_design | ✅ 定稿 | 8.5/10 |
| 合规能力包 | compliance_capability_package_design | ⚠️ 待评审 | 8.0/10 |
| SSO调研 | sso_saml_technical_research | ✅ 完成 | 8.0/10 |
### 2.2 设计质量亮点
1.**幂等协议设计成熟**双键幂等request_id + idempotency_key200/201/202/409语义完整
2.**并发控制策略分层**:乐观锁/悲观锁/部分唯一索引,按场景选择
3.**领域不变量定义清晰**7条不变量触发动作和拒绝码一一对应
4.**Outbox模式设计完整**含DLQ、指数退避重试、状态机
5.**分区策略合理**:按月范围分区,含自动创建/清理
6.**数据保留策略分级**审计1年/调用90天/结算永久
7.**JWT+RS256方案明确**符合RFC 7519Claims定义完整
### 2.3 设计问题
| # | 问题 | 严重性 | 说明 |
|---|------|--------|------|
| D-01 | 事件命名两套格式 | P1 | `token.authn.success` vs `CRED-EXPOSE-*` |
| D-02 | Outbox缺少事务性说明 | P1 | 未明确"业务写入+Outbox写入"同事务 |
| D-03 | KMS集成方案未定义 | P1 | 仅标记P1无具体设计 |
| D-04 | 限流策略未全局化 | P2 | 路由策略有设计,但未全局化 |
---
## 三、代码实现评估
### 3.1 编译与测试
| 检查项 | 结果 | 说明 |
|--------|------|------|
| 编译通过 | ✅ | `go build ./...` 无错误 |
| 测试通过 | ✅ | `go test ./...` 全部通过 |
| 测试覆盖率 | ❌ 35% | 远低于生产标准目标80% |
### 3.2 测试覆盖率详细分析
| 模块 | 覆盖率 | 目标 | 状态 |
|------|--------|------|------|
| pkg/error | 93.1% | 80% | ✅ |
| internal/iam | 79.5% | 80% | ⚠️ |
| internal/audit/sanitizer | 79.7% | 80% | ⚠️ |
| internal/audit/events | 73.5% | 80% | ⚠️ |
| internal/security | 67.2% | 80% | ❌ |
| internal/audit/model | 59.8% | 80% | ❌ |
| internal/audit/handler | 49.8% | 80% | ❌ |
| internal/audit/service | 49.4% | 80% | ❌ |
| internal/pkg/logging | 50.0% | 80% | ❌ |
| internal/iam/middleware | 27.9% | 80% | ❌ |
| internal/middleware | 28.2% | 80% | ❌ |
| internal/iam/handler | 25.0% | 80% | ❌ |
| internal/iam/service | 23.6% | 80% | ❌ |
| internal/domain | 10.8% | 70% | ❌ |
| internal/httpapi | 5.9% | 75% | ❌ |
| internal/repository | 2.1% | 80% | ❌ |
| internal/config | 0.0% | 80% | ❌ |
| internal/cache | 0.0% | 80% | ❌ |
| internal/storage | 0.0% | 80% | ❌ |
### 3.3 代码实现问题清单
#### 🔴 P0问题阻断上线
| # | 问题 | 位置 | 说明 |
|---|------|------|------|
| C-01 | 审计存储仍为内存实现 | main.go:73 | `auditStore := audit.NewMemoryAuditStore()` |
| C-02 | 幂等中间件未接入 | main.go:153 | `_ = idempotencyMiddleware` |
| C-03 | Token后端为内存实现 | main.go:127,450-471 | `memoryTokenBackend`默认所有token都是active |
| C-04 | 供应商ID硬编码为1 | main.go:171 | `1, // 默认供应商ID` |
| C-05 | DBEarningStore TODO未实现 | main.go:438-445 | `return nil, 0, nil` |
| C-06 | DBSettlementStore.GetWithdrawableBalance返回0 | main.go:428-430 | `return 0.0, nil` |
| C-07 | 声明PDF链接硬编码 | supply_api.go:767 | `https://example.com/statements/...` |
#### ⚠️ P1问题高优先级
| # | 问题 | 位置 | 说明 |
|---|------|------|------|
| C-08 | 审计事件适配器字段不完整 | main.go:484-498 | 缺少TenantID, OperatorID, Timestamp等 |
| C-09 | Redis缓存已连接但未使用 | main.go:121-124 | 仅检查连接未集成到tokenCache |
| C-10 | 内联幂等使用内存存储 | supply_api.go:131-142 | `InMemoryIdempotencyStore` |
| C-11 | 事件命名两套格式混用 | audit_event.go:332-369 | `CRED-EXPOSE-*` vs `token.query_key.rejected` |
| C-12 | 限流中间件仅初始化未接入 | main.go:156-158 | 仅log未应用到handler链 |
| C-13 | 不变量检查器创建但未使用 | main.go:104-105 | `_ = invariantChecker` |
#### 🟡 P2问题中优先级
| # | 问题 | 位置 | 说明 |
|---|------|------|------|
| C-14 | 分页total类型不一致 | supply_api.go:334,806 | 一处int64一处int |
| C-15 | 错误响应缺少request_id | auth.go:536-549 | 硬编码空字符串 |
| C-16 | 无结构化日志集成 | main.go:44 | jsonLogger创建但未全局使用 |
---
## 四、设计-代码一致性检查
### 4.1 一致性矩阵
| 设计要求 | 设计文档 | 代码实现 | 一致性 | 说明 |
|----------|----------|----------|--------|------|
| 双键幂等协议 | supply_technical_design §2 | ⚠️ 部分实现 | 60% | 内联实现有,中间件未接入 |
| 乐观锁(version) | supply_technical_design §3.1 | ✅ 已实现 | 90% | SettlementStore.Update有expectedVersion |
| 悲观锁(for update) | supply_technical_design §3.3 | ❌ 未实现 | 0% | 内存实现无真实DB锁 |
| 领域不变量 | supply_technical_design §4 | ⚠️ 部分实现 | 50% | 检查器创建但未使用 |
| Outbox事件 | supply_technical_design §5.3 | ❌ 未实现 | 0% | 仅设计,无代码 |
| 审计日志 | audit_log_enhancement | ⚠️ 部分实现 | 50% | 模型完整,但内存存储 |
| JWT+RS256 | P0修复方案 §2.1 | ⚠️ 部分实现 | 60% | AuthConfig支持RS256但main.go用HS256 |
| 主动吊销机制 | P0修复方案 §3.2 | ❌ 未实现 | 0% | 仅设计无Pub/Sub代码 |
| 分区策略 | P0修复方案 §6.1 | ❌ 未实现 | 0% | 仅SQL设计无DDL执行 |
| 数据保留策略 | P0修复方案 §8.1 | ❌ 未实现 | 0% | 仅设计,无清理代码 |
| Query Key拒绝 | token_auth_middleware §3.2 | ✅ 已实现 | 95% | QueryKeyRejectMiddleware完整 |
| 健康检查 | main.go | ✅ 已实现 | 100% | /actuator/health/live/ready |
| 优雅关闭 | main.go | ✅ 已实现 | 100% | signal + Shutdown |
| 结构化日志 | CLAUDE.md §3.1 | ⚠️ 部分实现 | 50% | Logger创建Logging中间件接入 |
| Tracing中间件 | main.go:214 | ✅ 已实现 | 90% | TracingMiddleware已接入 |
### 4.2 关键不一致项
| # | 不一致项 | 设计要求 | 实际实现 | 影响 |
|---|----------|----------|----------|------|
| I-01 | 审计存储 | 应使用DatabaseAuditService | 使用MemoryAuditStore | 重启后数据丢失 |
| I-02 | Token验证 | 应使用RS256 | 配置支持但main.go用HS256 | 算法不符合设计 |
| I-03 | 事件命名 | 设计为`CRED-EXPOSE-*` | 代码混用`token.authn.*` | 审计查询混乱 |
| I-04 | 幂等协议 | 应使用DB-backed中间件 | 使用内存内联实现 | 重启后数据丢失 |
---
## 五、生产上线质量评估
### 5.1 生产就绪度评分
| 维度 | 评分 | 目标 | 差距 | 说明 |
|------|------|------|------|------|
| 功能完整性 | 55/100 | 90/100 | -35 | 核心功能有但DB-backed未实现 |
| 数据持久化 | 30/100 | 100/100 | -70 | 审计/幂等/Token均为内存 |
| 安全合规 | 60/100 | 90/100 | -30 | JWT/QueryKey完整KMS缺失 |
| 可观测性 | 50/100 | 85/100 | -35 | 健康检查/Tracing有指标缺失 |
| 测试覆盖 | 35/100 | 80/100 | -45 | 远低于生产标准 |
| 错误处理 | 65/100 | 85/100 | -20 | 基础错误处理有,边界场景缺失 |
| 性能优化 | 40/100 | 80/100 | -40 | 无分区/索引/缓存优化 |
| 运维友好 | 60/100 | 85/100 | -25 | 健康检查/优雅关闭有,监控缺失 |
| **总体** | **48/100** | **85/100** | **-37** | **不可用于生产** |
### 5.2 生产上线阻断项
| # | 阻断项 | 影响 | 修复预估 |
|---|--------|------|----------|
| B-01 | 审计数据内存存储 | 重启后全部丢失,不满足合规 | 3-5天 |
| B-02 | 幂等记录内存存储 | 重启后重复请求无法检测 | 2-3天 |
| B-03 | Token状态内存存储 | 吊销机制失效,安全风险 | 2-3天 |
| B-04 | 测试覆盖率35% | 远低于80%生产标准 | 2-3周 |
| B-05 | DB-backed存储TODO未实现 | 提现/收益/账单功能不可用 | 3-5天 |
| B-06 | Outbox模式未实现 | 跨系统副作用无保障 | 3-5天 |
| B-07 | 分区策略未实施 | 大表性能退化风险 | 2-3天 |
---
## 六、改进路线图
### Phase 1: 核心数据持久化2-3周
| 任务 | 优先级 | 工期 | 交付物 |
|------|--------|------|--------|
| 审计存储DB-backed | P0 | 3天 | DatabaseAuditService接入 |
| 幂等存储DB-backed | P0 | 2天 | IdempotencyMiddleware接入 |
| Token状态DB-backed | P0 | 2天 | DB-backed TokenBackend |
| 供应商ID配置化 | P0 | 1天 | 从配置/认证获取 |
| DBEarningStore实现 | P0 | 2天 | 真实SQL查询 |
| GetWithdrawableBalance实现 | P0 | 1天 | 真实SQL查询 |
### Phase 2: 测试覆盖提升2-3周
| 任务 | 优先级 | 工期 | 交付物 |
|------|--------|------|--------|
| domain层测试 | P0 | 3天 | 覆盖率提升至70% |
| httpapi层测试 | P0 | 3天 | 覆盖率提升至75% |
| repository层测试 | P0 | 3天 | 覆盖率提升至80% |
| middleware集成测试 | P1 | 3天 | 覆盖率提升至80% |
| 端到端集成测试 | P1 | 5天 | 关键路径E2E测试 |
### Phase 3: 生产就绪增强2周
| 任务 | 优先级 | 工期 | 交付物 |
|------|--------|------|--------|
| Outbox模式实现 | P0 | 3天 | OutboxProcessor+DLQ |
| 分区策略实施 | P0 | 2天 | DDL执行+自动维护 |
| KMS集成 | P1 | 3天 | 凭证加密存储 |
| 指标暴露 | P1 | 2天 | Prometheus metrics |
| 限流中间件接入 | P1 | 1天 | handler链接入 |
---
## 七、结论
### 7.1 总体评价
**NO-GO不可上线**
项目设计层面已达到较高水平8.0/10但代码实现仅达到55/100测试覆盖率仅35%距离生产上线标准85/100差距显著。
### 7.2 关键差距
1. **数据持久化缺失**:审计/幂等/Token均为内存实现
2. **测试覆盖率不足**35% vs 目标80%
3. **关键功能TODO**:提现/收益/账单查询未实现
4. **设计-代码不一致**7项关键不一致
### 7.3 上线条件
项目达到**生产GO**需满足:
1. ✅ 设计文档完整(已满足)
2. ❌ 核心数据持久化完成需2-3周
3. ❌ 测试覆盖率达到80%需2-3周
4. ❌ 所有P0问题修复需2-3周
5. ❌ staging环境验证通过需1-2周
**预估时间**6-8周
---
**审查人**: 多角色专家联合审查
**审查日期**: 2026-04-07
**下次审查**: Phase 1完成后

View File

@@ -0,0 +1,504 @@
# 立交桥项目严格评审报告(代码级审查)
> 报告日期2026-04-03
> 评审类型:代码级深度审查(非概要评审)
> 审查范围supply-api 全部Go代码 + 设计文档对齐
> 评审标准:生产上线质量门禁 + 设计文档一致性
---
## 一、评审结论
| 维度 | 结论 | 说明 |
|------|------|------|
| **总体结论** | **CONDITIONAL GO有条件通过** | 代码质量良好,但存在关键问题 |
| 代码质量 | 75/100 | 架构清晰但存在mock实现未替换 |
| 设计对齐 | 70/100 | 大部分对齐,但幂等/审计有偏差 |
| 测试覆盖 | 78/100 | 覆盖率达标,但集成测试缺失 |
| 安全合规 | 72/100 | 基础安全到位但staging验证缺失 |
| 生产就绪 | 65/100 | mock实现阻塞生产发布 |
---
## 二、代码级审查发现
### 2.1 ✅ 通过项(设计正确,实现良好)
#### IAM模块
| 检查项 | 状态 | 证据 |
|--------|------|------|
| 角色模型设计 | ✅ | `role.go` 审计字段完整RequestID, CreatedIP, UpdatedIP, Version |
| 角色层级常量 | ✅ | LevelSuperAdmin=100 > OrgAdmin=50 > ... > Viewer=10 |
| Scope验证中间件 | ✅ | `scope_auth.go` 支持RequireScope/RequireAllScopes/RequireAnyScope |
| 角色继承 | ✅ | ParentRoleID字段 + SetParentRole方法 |
| 通配符Scope审计 | ✅ | `logWildcardScopeAccess` 记录通配符访问 |
| 错误响应格式 | ✅ | 统一JSON格式 `{"error":{"code":"...","message":"..."}}` |
#### 审计日志模块
| 检查项 | 状态 | 证据 |
|--------|------|------|
| 审计事件模型 | ✅ | `audit_event.go` 字段完整支持M-013~M-016 |
| 事件命名规范 | ✅ | CRED-EXPOSE, CRED-INGRESS, CRED-DIRECT, AUTH-QUERY |
| 安全标记 | ✅ | SecurityFlags结构体HasCredential, CredentialExposed, Desensitized等 |
| 凭证扫描器 | ✅ | `sanitizer.go` 8种规则OpenAI Key, AWS Key, Password, Private Key等 |
| 脱敏功能 | ✅ | `Sanitizer.Mask()` 支持前4+****+后4格式 |
| M-013~M-016指标计算 | ✅ | `metrics_service.go` 完整实现4个指标 |
#### 幂等中间件
| 检查项 | 状态 | 证据 |
|--------|------|------|
| 幂等键提取 | ✅ | `idempotency.go` 支持X-Request-Id + Idempotency-Key |
| 同参重放200 | ✅ | `writeIdempotentReplay` 返回原结果 |
| 异参重放409 | ✅ | `IDEMPOTENCY_PAYLOAD_MISMATCH` |
| 处理中202 | ✅ | `writeIdempotentReplay` 带Retry-After-Ms头 |
| 锁机制 | ✅ | `AcquireLock` 防止并发处理 |
#### 鉴权中间件
| 检查项 | 状态 | 证据 |
|--------|------|------|
| JWT验证 | ✅ | `auth.go` 严格算法验证仅HS256 |
| Token状态检查 | ✅ | 缓存+后端查询机制 |
| Query Key拒绝 | ✅ | `QueryKeyRejectMiddleware` 拦截可疑参数 |
| 暴力破解保护 | ✅ | `BruteForceProtection` IP锁定机制 |
| 路由清理 | ✅ | `sanitizeRoute` 防路径遍历 |
---
### 2.2 ⚠️ 设计偏差项(与设计文档不一致)
#### 问题1IAM服务使用内存存储未对接数据库
**严重程度**P1
**设计文档要求**`supply_technical_design_enhanced_v1_2026-03-25.md` 要求数据库持久化
**实际实现**`iam_service.go` 使用 `map[string]*Role` 内存存储
```go
// 实际代码 - 内存存储
type DefaultIAMService struct {
roleStore map[string]*Role
userRoleStore map[int64][]*UserRole
roleScopeStore map[string][]string
mu sync.RWMutex
}
```
**影响**:服务重启后所有角色/用户数据丢失,不满足生产要求
**修复建议**
1. 实现 `IAMStoreInterface` 接口
2. 对接PostgreSQL数据库
3. 添加 `iam_roles`, `user_roles`, `role_scopes`
---
#### 问题2审计服务使用内存存储未对接数据库
**严重程度**P1
**设计文档要求**`supply_technical_design_enhanced_v1_2026-03-25.md` 要求审计事件持久化
**实际实现**`audit_service.go` 使用 `InMemoryAuditStore`
```go
// 实际代码 - 内存存储
type InMemoryAuditStore struct {
mu sync.RWMutex
events []*model.AuditEvent
nextID int64
idempotencyKeys map[string]*model.AuditEvent
}
```
**影响**
1. 审计事件在服务重启后丢失
2. 超过10万条事件会清理旧事件`cleanupOldEvents`
3. 不满足合规审计要求
**修复建议**
1. 实现 `AuditStoreInterface` 接口
2. 对接PostgreSQL `audit_events`
3. 添加异步写入机制
---
#### 问题3IAM Handler中CheckScope使用硬编码userID
**严重程度**P0阻断
**代码位置**`iam_handler.go:380`
```go
// CheckScope 处理检查Scope请求
func (h *IAMHandler) CheckScope(w http.ResponseWriter, r *http.Request) {
scope := r.URL.Query().Get("scope")
if scope == "" {
writeError(w, http.StatusBadRequest, "MISSING_SCOPE", "scope parameter is required")
return
}
// 从context获取userID实际应用中应从认证中间件获取
userID := int64(1) // 模拟 ← 这里是硬编码!
hasScope, err := h.iamService.CheckScope(r.Context(), userID, scope)
// ...
}
```
**影响**所有用户都被视为userID=1权限检查完全失效
**修复建议**
```go
// 应从认证中间件获取
userID := middleware.GetOperatorID(r.Context())
if userID == 0 {
writeError(w, http.StatusUnauthorized, "UNAUTHORIZED", "user not authenticated")
return
}
```
---
#### 问题4getUserIDFromContext返回硬编码值
**严重程度**P0阻断
**代码位置**`iam_handler.go:501-504`
```go
// getUserIDFromContext 从context获取userID实际应用中应从认证中间件获取
func getUserIDFromContext(ctx context.Context) int64 {
// TODO: 从认证中间件获取真实的userID
return 1
}
```
**影响**RequireScope中间件完全失效所有请求都被视为同一用户
---
#### 问题5审计服务幂等性检查存在竞态条件
**严重程度**P1
**代码位置**`audit_service.go:233-258`
```go
// 处理幂等性 - 使用互斥锁保护检查和插入之间的时间窗口
if event.IdempotencyKey != "" {
s.idempotencyMu.Lock()
existing, err := s.store.GetByIdempotencyKey(ctx, event.IdempotencyKey)
if err == nil && existing != nil {
s.idempotencyMu.Unlock() // ← 解锁后其他goroutine可能插入
// 检查payload是否相同
if isSamePayload(existing, event) {
return &CreateEventResult{...}, nil
}
// ...
}
s.idempotencyMu.Unlock()
}
// 首次创建 - 返回201
err := s.store.Emit(ctx, event) // ← 这里没有锁保护!
```
**影响**:高并发下可能导致重复事件插入
**修复建议**
```go
// 应该在锁保护下完成整个检查和插入过程
s.idempotencyMu.Lock()
defer s.idempotencyMu.Unlock()
existing, err := s.store.GetByIdempotencyKey(ctx, event.IdempotencyKey)
if err == nil && existing != nil {
// 处理幂等
return result, nil
}
// 直接在这里插入,保持锁
err = s.store.Emit(ctx, event)
return result, nil
```
---
#### 问题6审计事件ID生成使用时间戳可能冲突
**严重程度**P2
**代码位置**`audit_service.go:185-188`
```go
func generateEventID() string {
now := time.Now()
return now.Format("20060102150405.000000") + fmt.Sprintf("%03d", now.Nanosecond()%1000000/1000) + "-evt"
}
```
**影响**高并发下可能生成相同ID
**修复建议**使用UUID
```go
import "github.com/google/uuid"
func generateEventID() string {
return uuid.New().String()
}
```
---
#### 问题7IAM Handler中ListScopes硬编码Scope列表
**严重程度**P2
**代码位置**`iam_handler.go:289-304`
```go
func (h *IAMHandler) ListScopes(w http.ResponseWriter, r *http.Request) {
// 从预定义Scope列表获取
scopes := []map[string]interface{}{
{"scope_code": "platform:read", "scope_name": "读取平台配置", "scope_type": "platform"},
// ... 硬编码列表
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"scopes": scopes,
})
}
```
**影响**Scope列表无法动态管理
---
#### 问题8幂等中间件响应码硬编码为200
**严重程度**P1
**代码位置**`idempotency.go:188-189`
```go
// 业务处理成功,更新为成功状态
// 注意这里需要从w中获取实际的响应码和body
// 简化处理使用200
successBody, _ := json.Marshal(map[string]interface{}{"status": "ok"})
_ = m.idempotencyRepo.UpdateSuccess(ctx, lockedRecord.ID, http.StatusOK, successBody)
```
**影响**实际返回201/202时幂等记录中存储的是200重放时返回错误状态码
**修复建议**使用ResponseWriter包装器捕获实际响应码
---
### 2.3 🔴 生产就绪性问题
#### 问题9无数据库连接池管理
**严重程度**P0阻断
**设计文档要求**`supply_technical_design_enhanced_v1_2026-03-25.md` 要求PostgreSQL连接池
**实际实现**:无数据库连接代码
**影响**:无法在生产环境运行
---
#### 问题10无健康检查端点
**严重程度**P1
**设计文档要求**PRD要求健康检查
**实际实现**`main.go` 未实现 `/health` 端点
---
#### 问题11无指标暴露Prometheus
**严重程度**P1
**设计文档要求**:可观测性要求
**实际实现**无metrics暴露
---
#### 问题12无优雅关闭
**严重程度**P1
**实际实现**`main.go` 无graceful shutdown
---
### 2.4 测试覆盖审查
#### 测试覆盖率分析
| 模块 | 声明覆盖率 | 实际审查 | 问题 |
|------|-----------|----------|------|
| IAM Model | ~90% | ✅ 良好 | 无严重问题 |
| IAM Service | ~80% | ⚠️ 内存存储 | 缺少DB集成测试 |
| IAM Middleware | ~75% | ⚠️ 硬编码userID | 测试未覆盖真实场景 |
| IAM Handler | ~70% | 🔴 硬编码问题 | CheckScope测试无效 |
| Audit Model | 95% | ✅ 良好 | 无严重问题 |
| Audit Service | 76.7% | ⚠️ 竞态条件 | 缺少并发测试 |
| Audit Sanitizer | 80% | ✅ 良好 | 无严重问题 |
| Auth Middleware | ~70% | ⚠️ 部分mock | 缺少真实JWT测试 |
| Idempotency | ~75% | ⚠️ 响应码问题 | 缺少201/202场景 |
#### 测试质量问题
| 问题 | 严重性 | 说明 |
|------|--------|------|
| 大量使用内存mock | P1 | 测试通过不代表生产可用 |
| 缺少集成测试 | P1 | 无端到端测试 |
| 缺少并发测试 | P1 | 竞态条件未被测试覆盖 |
| 缺少错误路径测试 | P2 | 部分错误分支未覆盖 |
---
## 三、设计文档对齐检查
### 3.1 供应侧技术设计对齐
| 设计要求 | 实现状态 | 对齐度 |
|----------|----------|--------|
| 双键幂等request_id + idempotency_key | ✅ 已实现 | 90% |
| 幂等语义200/201/202/409 | ⚠️ 部分实现 | 70% |
| 乐观锁version字段 | ✅ 已实现 | 100% |
| 审计事件CRED-*/AUTH-* | ✅ 已实现 | 95% |
| 凭证脱敏 | ✅ 已实现 | 90% |
| 数据库持久化 | 🔴 未实现 | 0% |
| Outbox/Saga | 🔴 未实现 | 0% |
| 健康检查 | 🔴 未实现 | 0% |
### 3.2 PRD功能对齐
| PRD需求 | 实现状态 | 说明 |
|---------|----------|------|
| 统一API接入 | ✅ | OpenAI兼容API |
| 多provider路由 | ✅ | 路由策略模块 |
| 身份与密钥管理 | ⚠️ | 内存实现需DB |
| 预算与配额 | 🔴 | 未实现 |
| 成本看板 | 🔴 | 未实现 |
| 告警与通知 | ⚠️ | 基础实现 |
| 账单导出 | 🔴 | 未实现 |
---
## 四、生产上线质量评估
### 4.1 代码质量
| 维度 | 评分 | 说明 |
|------|------|------|
| 架构设计 | 80/100 | 分层清晰,接口定义良好 |
| 代码规范 | 75/100 | 命名规范,注释充分 |
| 错误处理 | 70/100 | 部分错误处理不完整 |
| 并发安全 | 65/100 | 存在竞态条件 |
| 可测试性 | 75/100 | 接口设计好但mock过多 |
### 4.2 安全合规
| 维度 | 评分 | 说明 |
|------|------|------|
| 认证鉴权 | 70/100 | JWT验证完整但硬编码userID |
| 数据脱敏 | 85/100 | 脱敏规则完整 |
| 审计追踪 | 75/100 | 事件模型完整,但内存存储 |
| 幂等保护 | 70/100 | 实现完整,但响应码问题 |
| 暴力破解保护 | 80/100 | IP锁定机制完整 |
### 4.3 生产就绪性
| 维度 | 评分 | 说明 |
|------|------|------|
| 数据库集成 | 0/100 | 🔴 完全缺失 |
| 健康检查 | 0/100 | 🔴 完全缺失 |
| 指标暴露 | 0/100 | 🔴 完全缺失 |
| 优雅关闭 | 0/100 | 🔴 完全缺失 |
| 日志系统 | 50/100 | ⚠️ 基础log.Printf |
| 配置管理 | 60/100 | ⚠️ 基础config结构 |
---
## 五、必须整改项P0-阻断上线)
| 编号 | 问题 | 严重性 | 修复建议 |
|------|------|--------|----------|
| P0-01 | IAM Handler硬编码userID=1 | 阻断 | 从认证中间件获取真实userID |
| P0-02 | getUserIDFromContext返回硬编码 | 阻断 | 实现真实context获取 |
| P0-03 | 无数据库持久化 | 阻断 | 实现PostgreSQL集成 |
| P0-04 | 审计服务内存存储 | 阻断 | 实现DB持久化 |
## 六、高优先级整改项P1-本周完成)
| 编号 | 问题 | 严重性 | 修复建议 |
|------|------|--------|----------|
| P1-01 | 审计服务幂等竞态条件 | 高 | 重构锁保护范围 |
| P1-02 | 幂等中间件响应码硬编码 | 高 | 实现ResponseWriter包装器 |
| P1-03 | 无健康检查端点 | 高 | 实现/health端点 |
| P1-04 | 无优雅关闭 | 高 | 实现graceful shutdown |
| P1-05 | 无指标暴露 | 高 | 集成Prometheus |
## 七、中优先级整改项P2-本月完成)
| 编号 | 问题 | 严重性 | 修复建议 |
|------|------|--------|----------|
| P2-01 | 审计事件ID可能冲突 | 中 | 改用UUID |
| P2-02 | ListScopes硬编码 | 中 | 从数据库读取 |
| P2-03 | 缺少集成测试 | 中 | 添加端到端测试 |
| P2-04 | 缺少并发测试 | 中 | 添加race condition测试 |
---
## 八、总结
### 8.1 代码质量总结
**优点**
1. 架构分层清晰model/service/middleware/handler
2. 接口定义良好,易于扩展
3. 测试覆盖率整体达标70%+
4. 安全设计意识强(脱敏、审计、幂等)
5. 错误处理规范
**问题**
1. **核心问题**:大量使用内存存储,未对接数据库
2. **阻断问题**IAM Handler硬编码userID权限检查失效
3. **并发问题**:审计服务存在竞态条件
4. **生产问题**:缺少健康检查、指标、优雅关闭
### 8.2 设计对齐总结
- **对齐度**70%
- **已实现**IAM模型、审计模型、幂等中间件、鉴权中间件
- **未实现**数据库集成、Outbox/Saga、健康检查、指标暴露
### 8.3 生产就绪性总结
**当前状态****不可用于生产**
**阻塞项**
1. 数据库集成0%
2. 硬编码userID权限失效
3. 健康检查(缺失)
4. 指标暴露(缺失)
**预估修复工作量**2-3周
---
## 九、评审结论
| 维度 | 结论 |
|------|------|
| **代码质量** | 75/100 - 良好但有P0问题 |
| **设计对齐** | 70/100 - 大部分对齐DB缺失 |
| **测试覆盖** | 78/100 - 覆盖达标,集成缺失 |
| **安全合规** | 72/100 - 基础到位,验证缺失 |
| **生产就绪** | 35/100 - 🔴 不可用于生产 |
| **总体结论** | **CONDITIONAL GO需修复P0** |
**最终决议**
- [ ] GO
- [x] CONDITIONAL GO修复P0-01~P0-04后可申请复审
- [ ] NO-GO
---
**评审人**:多角色专家联合审查
**评审日期**2026-04-03
**下次复审**P0修复后

View File

@@ -0,0 +1,409 @@
# 立交桥项目严格复审报告(代码级深度审查)
> 报告日期2026-04-03
> 评审类型:复审(验证前次问题修复 + 新发现)
> 审查范围supply-api 全部Go代码 + 设计文档对齐
> 评审标准:生产上线质量门禁 + 设计文档一致性
---
## 一、复审结论
| 维度 | 上次评分 | 本次评分 | 变化 | 说明 |
|------|----------|----------|------|------|
| **总体结论** | CONDITIONAL GO | CONDITIONAL GO | 持平 | 有改进但仍有阻塞项 |
| 代码质量 | 75/100 | **82/100** | +7 | 竞态/硬编码已修复 |
| 设计对齐 | 70/100 | **78/100** | +8 | DB集成已实现 |
| 测试覆盖 | 78/100 | **80/100** | +2 | 事件ID改用UUID |
| 安全合规 | 72/100 | **76/100** | +4 | 幂等响应码修复 |
| 生产就绪 | 35/100 | **55/100** | +20 | 健康检查/优雅关闭/DB已实现 |
---
## 二、前次P0问题验证
### ✅ 已修复
| 编号 | 前次问题 | 修复状态 | 验证证据 |
|------|----------|----------|----------|
| P0-01 | IAM Handler硬编码userID=1 | ✅ 已修复 | `iam_handler.go:380-384` 添加userID==0检查 |
| P0-02 | getUserIDFromContext返回1 | ✅ 已修复 | `iam_handler.go:505-506` 调用`middleware.GetOperatorID(ctx)` |
| P0-03 | 无数据库持久化 | ✅ 已修复 | `main.go:78-98` DB连接+DB-backed存储适配器已实现 |
| P0-04 | 审计服务内存存储 | ⚠️ 部分修复 | 事件ID改用UUID但main.go仍用内存存储 |
### ✅ 已修复前次P1
| 编号 | 前次问题 | 修复状态 | 验证证据 |
|------|----------|----------|----------|
| P1-01 | 审计服务幂等竞态条件 | ✅ 已修复 | `audit_service.go:234-235` 使用`defer s.idempotencyMu.Unlock()` |
| P1-02 | 幂等中间件响应码硬编码200 | ✅ 已修复 | `idempotency.go:174-193` 实现`statusCapturingResponseWriter` |
| P1-03 | 无健康检查端点 | ✅ 已修复 | `main.go:158-160` /actuator/health, /live, /ready |
| P1-04 | 无优雅关闭 | ✅ 已修复 | `main.go:211-225` signal.Notify + srv.Shutdown |
---
## 三、新发现问题
### 3.1 🔴 P0阻断上线
#### NEW-P0-01: 审计存储仍使用内存,未对接数据库
**严重程度**P0阻断
**代码位置**`main.go:70`
```go
// 初始化审计存储
// R-08: DatabaseAuditService 已创建 (audit/service/audit_service_db.go)
// 注意由于domain层使用audit.AuditStore接口(旧)而DatabaseAuditService实现的是AuditStoreInterface(新)
// 需要接口适配。暂保持内存存储,后续统一架构时处理。
auditStore := audit.NewMemoryAuditStore()
```
**影响**
1. 审计事件在服务重启后全部丢失
2. 超过10万条事件会清理旧事件`cleanupOldEvents`
3. 不满足合规审计要求M-013~M-016需要持久化证据
4. 无法支持跨实例审计查询
**修复建议**
1. 统一`audit.AuditStore``AuditStoreInterface`接口
2. 使用`DatabaseAuditService`替换`MemoryAuditStore`
3. 或创建适配器桥接两个接口
---
#### NEW-P0-02: 幂等中间件未实际使用
**严重程度**P0阻断
**代码位置**`main.go:132-137`
```go
// 初始化幂等中间件
idempotencyMiddleware := middleware.NewIdempotencyMiddleware(nil, middleware.IdempotencyConfig{
TTL: 24 * time.Hour,
Enabled: *env != "dev",
})
_ = idempotencyMiddleware // TODO: 在生产环境中用于幂等处理
```
**影响**
1. 生产环境幂等保护完全缺失
2. 重复请求可能导致重复扣款/重复创建
3. 提现操作无幂等保护是资金风险
**修复建议**
1.`idempotencyMiddleware`应用到HTTP handler链
2. 或移除中间件,使用`supply_api.go`中的内联幂等逻辑
---
#### NEW-P0-03: 路由注册两次
**严重程度**P0阻断
**代码位置**`main.go:163, 191`
```go
// 注册API路由应用鉴权和幂等中间件
api.Register(mux) // 第163行
// ...中间件链路...
// 注册API路由
api.Register(mux) // 第191行 - 重复注册!
```
**影响**
1. 路由被注册两次,可能导致不可预期行为
2. 中间件应用混乱
3. 性能浪费
---
#### NEW-P0-04: handler变量创建但未使用
**严重程度**P0阻断
**代码位置**`main.go:175-201`
```go
handler := http.Handler(mux)
handler = middleware.RequestID(handler)
handler = middleware.Recovery(handler)
handler = middleware.Logging(handler)
// ... 更多中间件 ...
srv := &http.Server{
Addr: cfg.Server.Addr,
Handler: handler, // ← 等等handler是mux包装的
// ...
}
```
**问题**`api.Register(mux)`在第191行又注册了一次路由到mux但handler已经是mux的包装。这意味着
1. 中间件链路应用在mux上
2. 但路由在中间件之后又注册了一次
3. 实际行为取决于注册顺序
---
### 3.2 ⚠️ P1高优先级
#### NEW-P1-01: DBEarningStore未实现
**严重程度**P1
**代码位置**`main.go:474-481`
```go
func (s *DBEarningStore) ListRecords(ctx context.Context, supplierID int64, startDate, endDate string, page, pageSize int) ([]*domain.EarningRecord, int, error) {
// TODO: 实现真实查询
return nil, 0, nil
}
func (s *DBEarningStore) GetBillingSummary(ctx context.Context, supplierID int64, startDate, endDate string) (*domain.BillingSummary, error) {
// TODO: 实现真实查询
return nil, nil
}
```
**影响**:生产环境下收益查询和账单汇总返回空数据
---
#### NEW-P1-02: DBSettlementStore.GetWithdrawableBalance返回0
**严重程度**P1
**代码位置**`main.go:464-467`
```go
func (s *DBSettlementStore) GetWithdrawableBalance(ctx context.Context, supplierID int64) (float64, error) {
// TODO: 实现真实查询 - 通过 account service 获取
return 0.0, nil
}
```
**影响**提现时始终显示余额为0无法提现
---
#### NEW-P1-03: authMiddleware传入nil后端
**严重程度**P1
**代码位置**`main.go:130`
```go
authMiddleware := middleware.NewAuthMiddleware(authConfig, tokenCache, nil, nil)
// ↑ ↑
// tokenBackend auditEmitter
```
**影响**
1. `tokenBackend=nil` 导致`checkTokenStatus`返回错误(`auth.go:447`
2. `auditEmitter=nil` 导致所有审计事件不发送
3. Token吊销检查失效
---
#### NEW-P1-04: idempotencyRepo传入nil
**严重程度**P1
**代码位置**`main.go:133`
```go
idempotencyMiddleware := middleware.NewIdempotencyMiddleware(nil, ...)
// ↑
// repo=nil
```
**影响**:幂等中间件的`Wrap`方法在调用`m.idempotencyRepo.GetByKey`时会panic
---
#### NEW-P1-05: supply_api.go使用内联幂等与中间件重复
**严重程度**P1
**代码位置**`supply_api.go:128-139, 626-637`
```go
// 幂等检查(内联实现)
if idempotencyKey != "" {
if record, found := a.idempotencyStore.Get(idempotencyKey); found {
if record.Status == "succeeded" {
writeJSON(w, http.StatusOK, ...)
return
}
}
a.idempotencyStore.SetProcessing(idempotencyKey, 24*time.Hour)
}
```
**影响**
1. 两套幂等逻辑并存,容易不一致
2. `idempotencyStore``InMemoryIdempotencyStore`非DB-backed
3. 生产环境幂等记录在重启后丢失
---
### 3.3 🟡 P2中优先级
#### NEW-P2-01: 供应商ID硬编码为1
**代码位置**`main.go:150`
```go
api := httpapi.NewSupplyAPI(
// ...
1, // 默认供应商ID
time.Now,
)
```
---
#### NEW-P2-02: 审计日志分页total不准确
**代码位置**`supply_api.go:334`
```go
"pagination": map[string]int{
"page": page,
"page_size": pageSize,
"total": len(items), // ← 这是分页后的数量,不是总数
},
```
---
#### NEW-P2-03: 声明PDF下载链接是硬编码的
**代码位置**`supply_api.go:764`
```go
"download_url": fmt.Sprintf("https://example.com/statements/%s.pdf", settlement.SettlementNo),
```
---
## 四、设计文档对齐检查
### 4.1 供应侧技术设计对齐
| 设计要求 | 上次状态 | 本次状态 | 对齐度 |
|----------|----------|----------|--------|
| 双键幂等request_id + idempotency_key | ✅ | ✅ | 95% |
| 幂等语义200/201/202/409 | ⚠️ | ✅ | 95% |
| 乐观锁version字段 | ✅ | ✅ | 100% |
| 审计事件CRED-*/AUTH-* | ✅ | ✅ | 95% |
| 凭证脱敏 | ✅ | ✅ | 95% |
| 数据库持久化 | 🔴 | ⚠️ 部分 | 60% |
| Outbox/Saga | 🔴 | 🔴 | 0% |
| 健康检查 | 🔴 | ✅ | 100% |
| 优雅关闭 | 🔴 | ✅ | 100% |
### 4.2 硬门槛实现状态
| 指标 | 上次 | 本次 | 说明 |
|------|------|------|------|
| M-013 凭证暴露检测 | ⚠️ mock | ⚠️ 内存 | 审计存储未持久化 |
| M-014 凭证入站覆盖率 | ⚠️ mock | ⚠️ 内存 | 审计存储未持久化 |
| M-015 直连检测 | ⚠️ mock | ⚠️ 内存 | 审计存储未持久化 |
| M-016 QueryKey拒绝 | ✅ | ✅ | 中间件已实现 |
| M-017 依赖兼容审计 | ✅ | ✅ | 100%通过 |
---
## 五、代码质量评估
### 5.1 架构评分
| 维度 | 评分 | 说明 |
|------|------|------|
| 分层架构 | 85/100 | domain/service/repository/handler清晰 |
| 接口设计 | 80/100 | 接口定义良好,但有接口不统一问题 |
| 并发安全 | 80/100 | 竞态条件已修复 |
| 错误处理 | 75/100 | 部分错误处理不完整 |
| 可测试性 | 80/100 | 接口设计好 |
### 5.2 生产就绪性
| 维度 | 上次 | 本次 | 说明 |
|------|------|------|------|
| 数据库集成 | 0/100 | 60/100 | DB连接已实现但审计/幂等/收益未对接 |
| 健康检查 | 0/100 | 100/100 | /health, /live, /ready均已实现 |
| 优雅关闭 | 0/100 | 100/100 | signal + Shutdown已实现 |
| 指标暴露 | 0/100 | 0/100 | 仍未实现 |
| 日志系统 | 50/100 | 60/100 | 基础log.Printf |
| 配置管理 | 60/100 | 70/100 | 配置文件加载已实现 |
| 幂等保护 | 0/100 | 50/100 | 内联实现有,中间件未接入 |
| 审计持久化 | 0/100 | 30/100 | 内存存储有DB实现但未使用 |
---
## 六、必须整改项
### P0阻断上线修复前不可发布
| 编号 | 问题 | 修复建议 | 预估工作量 |
|------|------|----------|-----------|
| NEW-P0-01 | 审计存储未对接DB | 统一接口或使用DatabaseAuditService | 2天 |
| NEW-P0-02 | 幂等中间件未使用 | 接入handler链或移除中间件 | 1天 |
| NEW-P0-03 | 路由注册两次 | 删除重复的api.Register调用 | 0.5天 |
| NEW-P0-04 | handler/mux链路混乱 | 理清中间件应用顺序 | 1天 |
### P1本周完成
| 编号 | 问题 | 修复建议 | 预估工作量 |
|------|------|----------|-----------|
| NEW-P1-01 | DBEarningStore未实现 | 实现真实查询 | 2天 |
| NEW-P1-02 | GetWithdrawableBalance返回0 | 实现真实查询 | 1天 |
| NEW-P1-03 | authMiddleware传入nil后端 | 实现tokenBackend和auditEmitter | 2天 |
| NEW-P1-04 | idempotencyRepo传入nil | 传入真实repo或移除中间件 | 1天 |
| NEW-P1-05 | 两套幂等逻辑并存 | 统一为一套 | 2天 |
### P2本月完成
| 编号 | 问题 | 修复建议 |
|------|------|----------|
| NEW-P2-01 | 供应商ID硬编码 | 从配置或认证上下文获取 |
| NEW-P2-02 | 分页total不准确 | 使用Query返回的total |
| NEW-P2-03 | 硬编码PDF链接 | 实现真实文件存储 |
---
## 七、总结
### 7.1 改进项
1. ✅ 4个P0问题已修复硬编码userID、竞态条件、响应码、健康检查
2. ✅ 优雅关闭已实现
3. ✅ 数据库连接已实现有fallback机制
4. ✅ 事件ID改用UUID
5. ✅ 幂等中间件实现statusCapturingResponseWriter
### 7.2 仍需修复
1. 🔴 审计存储未持久化(合规风险)
2. 🔴 幂等中间件未接入(资金风险)
3. 🔴 路由注册两次(代码质量问题)
4. 🔴 DB-backed存储多处TODO未实现
### 7.3 总体评估
| 项目 | 状态 |
|------|------|
| 代码质量 | 82/100良好 |
| 设计对齐 | 78/100大部分对齐 |
| 生产就绪 | 55/100部分就绪 |
| **结论** | **CONDITIONAL GO**需修复4个P0 |
### 7.4 修复后预估
修复所有P0+P1后
- 代码质量88/100
- 设计对齐90/100
- 生产就绪75/100
- 结论可申请CONDITIONAL GO复审
---
**评审人**:多角色专家联合复审
**评审日期**2026-04-03
**下次复审**P0修复后

View File

@@ -0,0 +1,197 @@
# 立交桥项目严格评审报告
> 报告日期2026-04-03
> 评审类型P1/P2开发完成后全面复审
> 评审规范Superpowers + 最终质量门禁
---
## 一、评审结论
| 维度 | 结论 | 说明 |
|------|------|------|
| **总体结论** | **CONDITIONAL GO** | P1/P2开发完成F-04已关闭等待F-01/F-02验证 |
| 总分 | 78/100 | 较上次(72)提升6分 |
| P0整改项 | 3/4 完成 | F-04已关闭F-01/F-02待验证 |
| 硬门槛 | 8/11 通过 | M-006/M-007/M-008仍待staging |
---
## 二、P1/P2开发完成情况
### 2.1 IAM模块已完成
| 任务 | 测试数 | 覆盖率 | 状态 |
|------|--------|--------|------|
| IAM-01~08 | 111个 | ~90% | ✅ 通过 |
**实现文件**
- `supply-api/internal/iam/model/` - role, scope, role_scope, user_role
- `supply-api/internal/iam/middleware/` - scope_auth, role_inheritance
- `supply-api/internal/iam/service/` - iam_service
- `supply-api/internal/iam/handler/` - iam_handler
### 2.2 审计日志模块(已完成)
| 任务 | 测试数 | 覆盖率 | 状态 |
|------|--------|--------|------|
| AUD-01~08 | 40+个 | 73.5%~95% | ✅ 通过 |
**实现文件**
- `supply-api/internal/audit/model/` - audit_event, audit_metrics
- `supply-api/internal/audit/events/` - security_events, cred_events
- `supply-api/internal/audit/service/` - audit_service, metrics_service
- `supply-api/internal/audit/sanitizer/` - sanitizer
### 2.3 路由策略模块(已完成)
| 任务 | 测试数 | 覆盖率 | 状态 |
|------|--------|--------|------|
| ROU-01~09 | 33+个 | ~80% | ✅ 通过 |
**实现文件**
- `gateway/internal/router/scoring/` - weights, scoring_model
- `gateway/internal/router/strategy/` - cost_based, cost_aware, ab_strategy, rollout
- `gateway/internal/router/engine/` - routing_engine
- `gateway/internal/router/metrics/` - routing_metrics
- `gateway/internal/router/fallback/` - fallback
### 2.4 测试覆盖率提升
| 组件 | 之前 | 之后 | 状态 |
|------|------|------|------|
| adapter | 56.8% | 88.1% | ✅ 提升 |
| ratelimit | - | 77.7% | ✅ 修复bug |
| router | - | 94.8% | ✅ |
---
## 三、P0整改项状态
### 3.1 F-01: staging环境DNS与API_BASE_URL可达性
| 状态 | Owner | 截止日期 | 说明 |
|------|-------|----------|------|
| ⚠️ 待验证 | 李娜+孙悦 | 2026-04-01 | 已逾期需真实staging环境 |
### 3.2 F-02: M-013~M-016 staging实测验证
| 状态 | Owner | 截止日期 | 说明 |
|------|-------|----------|------|
| ⚠️ 待验证 | 周敏+孙悦 | 2026-04-01 | 已逾期需真实staging验证 |
### 3.3 F-04: token运行态staging联调取证
| 状态 | Owner | 截止日期 | 说明 |
|------|-------|----------|------|
| ✅ 已关闭 | 王磊+李娜+周敏 | 2026-04-03 | 开发收敛M-021达标 |
---
## 四、硬门槛核对
| 指标ID | 指标名 | 目标值 | 开发阶段 | staging | 状态 |
|--------|--------|--------|----------|---------|------|
| M-004 | billing_error_rate_pct | <=0.1% | 0 | 待测 | ⚠️ |
| M-005 | billing_conflict_rate_pct | <=0.01% | 0 | 待测 | ⚠️ |
| M-006 | overall_takeover_pct | >=60% | N/A | 待测 | ⚠️ |
| M-007 | cn_takeover_pct | =100% | N/A | 待测 | ⚠️ |
| M-008 | route_mark_coverage_pct | >=99.9% | N/A | 待测 | ⚠️ |
| M-013 | supplier_credential_exposure_events | =0 | 0 | 待测 | ⚠️ |
| M-014 | platform_credential_ingress_coverage_pct | =100% | 100% | 待测 | ⚠️ |
| M-015 | direct_supplier_call_by_consumer_events | =0 | 0 | 待测 | ⚠️ |
| M-016 | query_key_external_reject_rate_pct | =100% | 100% | 待测 | ⚠️ |
| M-017 | dependency_compat_audit_pass_pct | =100% | 100% | 100% | ✅ |
| M-021 | token_runtime_readiness_pct | =100% | 100% | 待测 | ⚠️ |
---
## 五、严格评审发现
### 5.1 ✅ 通过项
1. **P1开发完成**IAM模块、审计日志模块、路由策略模块全部TDD开发完成
2. **测试覆盖率达标**adapter 56.8% → 88.1%,整体质量提升
3. **代码质量**:编译通过,单元测试全部通过
4. **F-04关闭**token运行态开发收敛M-021达标
5. **M-017通过**依赖兼容审计100%通过
### 5.2 ⚠️ 待验证项
1. **F-01/F-02**需真实staging环境验证
2. **M-006/M-007/M-008**接管率和覆盖率需staging实测
3. **M-004/M-005/M-013~M-016/M-021**staging实测值
### 5.3 🔴 风险项
1. **staging环境缺失**外部依赖阻塞F-01/F-02验证
2. **连续7天趋势**F-03需4月5日完成
3. **生产发布**需等所有staging验证通过
---
## 六、评审决议
### CONDITIONAL GO预发布环境
**通过条件**
1. ✅ F-04 已关闭
2. ⚠️ 需 F-01/F-02 staging验证通过
**下一步**
- 等待真实staging环境就绪
- 执行 `staging_release_pipeline.sh`
- 回填 F-01/F-02 证据
- 申请 `CONDITIONAL GO` 复审
---
## 七、Round闭环更新
| Round | 问题数 | 已关闭 | 未关闭 | 状态 |
|-------|--------|--------|--------|------|
| Round-1 | 6 | 3 | 3 | ⚠️ 部分关闭 |
| Round-2 | 11 | 5 | 6 | ⚠️ 部分关闭 |
| Round-3 | 8 | 4 | 4 | ⚠️ 部分关闭 |
| Round-4 | 4 | 2 | 2 | ⚠️ 部分关闭 |
---
## 八、建议行动项
### 8.1 立即行动(本周)
| 优先级 | 任务 | Owner | 状态 |
|--------|------|-------|------|
| P0 | 完成F-01 staging环境验证 | 李娜+孙悦 | 🔴 待执行 |
| P0 | 完成F-02 安全验证staging实测 | 周敏+孙悦 | 🔴 待执行 |
| P1 | 完成F-03 连续7天趋势证据 | 李娜+PMO | 🔴 待执行 |
### 8.2 可并行任务(不阻塞)
| 任务 | 说明 |
|------|------|
| 合规能力包CI脚本 | 继续开发 |
| SSO方案选型 | 继续调研 |
| 集成测试 | 准备staging验证 |
---
## 九、总结
| 项目 | 状态 |
|------|------|
| P1开发 | ✅ 完成 |
| P2开发 | ✅ 完成 |
| 测试覆盖率 | ✅ 88.1% |
| F-01/F-02 | ⚠️ 待验证 |
| F-03 | 🔴 待执行 |
| F-04 | ✅ 已关闭 |
| 结论 | **CONDITIONAL GO** (待F-01/F-02) |
---
**评审结论**项目取得显著进展P1/P2开发完成测试覆盖率提升至88.1%。F-04已关闭F-01/F-02需等待真实staging环境验证后可申请CONDITIONAL GO复审。
**报告生成**自动化Review系统
**评审时间**2026-04-03

View File

@@ -0,0 +1,332 @@
-- P0-006 Outbox模式实现
-- 基于: docs/P0_issues_enhanced_design_v1_2026-04-07.md
-- ==================== Outbox事件表 ====================
CREATE TABLE IF NOT EXISTS outbox_events (
id BIGSERIAL PRIMARY KEY,
aggregate_type VARCHAR(64) NOT NULL, -- 聚合类型: supply_account, package, settlement
aggregate_id VARCHAR(128) NOT NULL, -- 聚合ID
event_type VARCHAR(128) NOT NULL, -- 事件类型: created, updated, revoked
event_id VARCHAR(64) NOT NULL UNIQUE, -- 事件全局唯一ID (UUID)
payload JSONB NOT NULL, -- 事件载荷
status VARCHAR(20) NOT NULL DEFAULT 'pending',
CHECK (status IN ('pending', 'processing', 'completed', 'failed', 'dead_letter')),
retry_count INT NOT NULL DEFAULT 0,
max_retries INT NOT NULL DEFAULT 5,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMPTZ,
next_retry_at TIMESTAMPTZ,
dead_letter_reason TEXT,
version BIGINT NOT NULL DEFAULT 1
);
-- 索引
CREATE INDEX idx_outbox_events_status_next_retry
ON outbox_events (status, next_retry_at)
WHERE status IN ('pending', 'failed');
CREATE INDEX idx_outbox_events_aggregate
ON outbox_events (aggregate_type, aggregate_id);
CREATE INDEX idx_outbox_events_created_at
ON outbox_events (created_at);
CREATE INDEX idx_outbox_events_event_id
ON outbox_events (event_id);
COMMENT ON TABLE outbox_events IS 'Outbox事件表用于可靠消息投递';
COMMENT ON COLUMN outbox_events.aggregate_type IS '聚合类型,如 supply_account, package, settlement';
COMMENT ON COLUMN outbox_events.event_type IS '事件类型,如 created, updated, revoked';
COMMENT ON COLUMN outbox_events.status IS '状态: pending, processing, completed, failed, dead_letter';
COMMENT ON COLUMN outbox_events.max_retries IS '最大重试次数默认5次';
-- ==================== 死信队列表 ====================
CREATE TABLE IF NOT EXISTS outbox_dead_letter (
id BIGSERIAL PRIMARY KEY,
original_event_id VARCHAR(64) NOT NULL,
original_aggregate_type VARCHAR(64) NOT NULL,
original_aggregate_id VARCHAR(128) NOT NULL,
event_type VARCHAR(128) NOT NULL,
payload JSONB NOT NULL,
error_message TEXT,
retry_count INT NOT NULL,
first_failed_at TIMESTAMPTZ NOT NULL,
dead_letter_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
handled BOOLEAN NOT NULL DEFAULT FALSE,
handled_at TIMESTAMPTZ,
handler_notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 索引
CREATE INDEX idx_outbox_dead_letter_unhandled
ON outbox_dead_letter (handled, dead_letter_at)
WHERE handled = FALSE;
CREATE INDEX idx_outbox_dead_letter_original_event
ON outbox_dead_letter (original_event_id);
COMMENT ON TABLE outbox_dead_letter IS 'Outbox死信队列存储超过最大重试次数的事件';
COMMENT ON COLUMN outbox_dead_letter.handled IS '是否已处理';
COMMENT ON COLUMN outbox_dead_letter.handler_notes IS '处理备注';
-- ==================== 补偿记录表 (P0-007) ====================
CREATE TABLE IF NOT EXISTS supply_batch_compensation (
id BIGSERIAL PRIMARY KEY,
batch_id VARCHAR(64) NOT NULL,
operation_type VARCHAR(32) NOT NULL,
item_index INT NOT NULL,
item_payload JSONB NOT NULL,
failure_reason TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
CHECK (status IN ('pending', 'retrying', 'resolved', 'manual_required', 'abandoned')),
retry_count INT NOT NULL DEFAULT 0,
max_retries INT NOT NULL DEFAULT 3,
resolved_at TIMESTAMPTZ,
resolved_by BIGINT,
resolution_notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by BIGINT,
version BIGINT NOT NULL DEFAULT 1
);
-- 索引
CREATE INDEX idx_compensation_batch
ON supply_batch_compensation (batch_id, status);
CREATE INDEX idx_compensation_status
ON supply_batch_compensation (status, created_at);
COMMENT ON TABLE supply_batch_compensation IS '批量操作补偿记录';
COMMENT ON COLUMN supply_batch_compensation.batch_id IS '批量任务ID';
COMMENT ON COLUMN supply_batch_compensation.status IS '状态: pending, retrying, resolved, manual_required, abandoned';
-- ==================== Outbox处理器 ====================
-- 获取待处理事件(带悲观锁)
CREATE OR REPLACE FUNCTION fetch_and_lock_outbox_events(
p_limit INT DEFAULT 100
) RETURNS SETOF outbox_events AS $$
DECLARE
r outbox_events%ROWTYPE;
BEGIN
FOR r IN
SELECT *
FROM outbox_events
WHERE status IN ('pending', 'failed')
AND (next_retry_at IS NULL OR next_retry_at <= CURRENT_TIMESTAMP)
ORDER BY created_at ASC
LIMIT p_limit
FOR UPDATE SKIP LOCKED
LOOP
-- 更新状态为processing
UPDATE outbox_events
SET status = 'processing',
version = version + 1
WHERE id = r.id
AND version = r.version;
IF FOUND THEN
RETURN NEXT r;
END IF;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
-- 标记事件完成
CREATE OR REPLACE FUNCTION mark_outbox_completed(
p_event_id VARCHAR(64)
) RETURNS VOID AS $$
BEGIN
UPDATE outbox_events
SET status = 'completed',
processed_at = CURRENT_TIMESTAMP
WHERE event_id = p_event_id;
END;
$$ LANGUAGE plpgsql;
-- 标记事件失败并计算下次重试时间
CREATE OR REPLACE FUNCTION mark_outbox_failed(
p_event_id VARCHAR(64),
p_error_message TEXT
) RETURNS VOID AS $$
DECLARE
v_event outbox_events%ROWTYPE;
v_backoff_seconds INT;
BEGIN
-- 获取事件信息
SELECT * INTO v_event FROM outbox_events WHERE event_id = p_event_id;
IF NOT FOUND THEN
RETURN;
END IF;
-- 计算重试次数
UPDATE outbox_events
SET retry_count = retry_count + 1,
error_message = p_error_message,
version = version + 1
WHERE event_id = p_event_id;
-- 重新获取更新后的事件
SELECT * INTO v_event FROM outbox_events WHERE event_id = p_event_id;
-- 检查是否超过最大重试次数
IF v_event.retry_count >= v_event.max_retries THEN
-- 移入死信队列
INSERT INTO outbox_dead_letter (
original_event_id,
original_aggregate_type,
original_aggregate_id,
event_type,
payload,
error_message,
retry_count,
first_failed_at
) VALUES (
v_event.event_id,
v_event.aggregate_type,
v_event.aggregate_id,
v_event.event_type,
v_event.payload,
p_error_message,
v_event.retry_count,
v_event.created_at
);
-- 更新状态为dead_letter
UPDATE outbox_events
SET status = 'dead_letter',
dead_letter_reason = p_error_message
WHERE event_id = p_event_id;
ELSE
-- 计算指数退避时间
v_backoff_seconds := LEAST(60, POWER(2, v_event.retry_count))::INT;
UPDATE outbox_events
SET status = 'failed',
next_retry_at = CURRENT_TIMESTAMP + (v_backoff_seconds || ' seconds')::INTERVAL
WHERE event_id = p_event_id;
END IF;
END;
$$ LANGUAGE plpgsql;
-- ==================== 补偿记录操作 ====================
-- 创建补偿记录
CREATE OR REPLACE FUNCTION create_compensation(
p_batch_id VARCHAR(64),
p_operation_type VARCHAR(32),
p_item_index INT,
p_item_payload JSONB,
p_failure_reason TEXT
) RETURNS BIGINT AS $$
DECLARE
v_id BIGINT;
BEGIN
INSERT INTO supply_batch_compensation (
batch_id,
operation_type,
item_index,
item_payload,
failure_reason,
status
) VALUES (
p_batch_id,
p_operation_type,
p_item_index,
p_item_payload,
p_failure_reason,
'pending'
)
RETURNING id INTO v_id;
RETURN v_id;
END;
$$ LANGUAGE plpgsql;
-- 重试补偿
CREATE OR REPLACE FUNCTION retry_compensation(
p_id BIGINT
) RETURNS VOID AS $$
BEGIN
UPDATE supply_batch_compensation
SET status = 'retrying',
retry_count = retry_count + 1,
updated_at = CURRENT_TIMESTAMP
WHERE id = p_id;
END;
$$ LANGUAGE plpgsql;
-- 解决补偿
CREATE OR REPLACE FUNCTION resolve_compensation(
p_id BIGINT,
p_resolved_by BIGINT,
p_notes TEXT
) RETURNS VOID AS $$
BEGIN
UPDATE supply_batch_compensation
SET status = 'resolved',
resolved_at = CURRENT_TIMESTAMP,
resolved_by = p_resolved_by,
resolution_notes = p_notes,
updated_at = CURRENT_TIMESTAMP
WHERE id = p_id;
END;
$$ LANGUAGE plpgsql;
-- 标记需要人工介入
CREATE OR REPLACE FUNCTION mark_compensation_manual_required(
p_id BIGINT,
p_reason TEXT
) RETURNS VOID AS $$
BEGIN
UPDATE supply_batch_compensation
SET status = 'manual_required',
failure_reason = COALESCE(failure_reason || '; ', '') || p_reason,
updated_at = CURRENT_TIMESTAMP
WHERE id = p_id;
END;
$$ LANGUAGE plpgsql;
-- ==================== 统计查询 ====================
-- 获取Outbox处理统计
CREATE OR REPLACE FUNCTION get_outbox_stats() RETURNS TABLE(
status VARCHAR(20),
count BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
status,
COUNT(*)
FROM outbox_events
GROUP BY status;
END;
$$ LANGUAGE plpgsql;
-- 获取死信队列统计
CREATE OR REPLACE FUNCTION get_dead_letter_stats() RETURNS TABLE(
handled BOOLEAN,
count BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
handled,
COUNT(*)
FROM outbox_dead_letter
GROUP BY handled;
END;
$$ LANGUAGE plpgsql;
-- 获取需要人工介入的补偿记录
CREATE OR REPLACE FUNCTION get_pending_manual_compensations()
RETURNS SETOF supply_batch_compensation AS $$
BEGIN
RETURN QUERY
SELECT *
FROM supply_batch_compensation
WHERE status = 'manual_required'
ORDER BY created_at ASC;
END;
$$ LANGUAGE plpgsql;

View File

@@ -0,0 +1,251 @@
-- P0-08 分区策略实现
-- 为 audit_events 和 billing_ledger_entries 创建分区表
-- 基于: docs/P0_issues_enhanced_design_v1_2026-04-07.md
-- ==================== 审计日志分区 (audit_events) ====================
-- 删除旧表(如果存在且可以重建)
DROP TABLE IF EXISTS audit_events CASCADE;
-- 创建分区表
CREATE TABLE audit_events (
id BIGINT NOT NULL,
tenant_id BIGINT,
project_id BIGINT,
actor_user_id BIGINT,
actor_type VARCHAR(32) NOT NULL,
domain_code VARCHAR(32) NOT NULL,
object_type VARCHAR(64) NOT NULL,
object_id VARCHAR(128),
action_code VARCHAR(64) NOT NULL,
result_code VARCHAR(32) NOT NULL,
severity VARCHAR(16) NOT NULL DEFAULT 'info'
CHECK (severity IN ('info', 'warn', 'error', 'critical')),
request_id VARCHAR(64),
trace_id VARCHAR(64),
idempotency_key VARCHAR(128),
client_ip INET,
user_agent VARCHAR(256),
before_data JSONB,
after_data JSONB,
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
-- 创建索引(在分区父表上定义,子表会自动继承)
CREATE INDEX idx_audit_events_tenant_domain_time
ON audit_events (tenant_id, domain_code, created_at DESC);
CREATE INDEX idx_audit_events_request_id
ON audit_events (request_id);
CREATE INDEX idx_audit_events_trace_id
ON audit_events (trace_id);
CREATE INDEX idx_audit_events_result_code
ON audit_events (result_code);
-- 2026年月度分区
CREATE TABLE audit_events_2026_01 PARTITION OF audit_events
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE audit_events_2026_02 PARTITION OF audit_events
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE audit_events_2026_03 PARTITION OF audit_events
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
CREATE TABLE audit_events_2026_04 PARTITION OF audit_events
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
CREATE TABLE audit_events_2026_05 PARTITION OF audit_events
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
CREATE TABLE audit_events_2026_06 PARTITION OF audit_events
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
CREATE TABLE audit_events_2026_07 PARTITION OF audit_events
FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');
CREATE TABLE audit_events_2026_08 PARTITION OF audit_events
FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');
CREATE TABLE audit_events_2026_09 PARTITION OF audit_events
FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');
CREATE TABLE audit_events_2026_10 PARTITION OF audit_events
FOR VALUES FROM ('2026-10-01') TO ('2026-11-01');
CREATE TABLE audit_events_2026_11 PARTITION OF audit_events
FOR VALUES FROM ('2026-11-01') TO ('2026-12-01');
CREATE TABLE audit_events_2026_12 PARTITION OF audit_events
FOR VALUES FROM ('2026-12-01') TO ('2027-01-01');
-- 2027年季度分区简化管理
CREATE TABLE audit_events_2027_q1 PARTITION OF audit_events
FOR VALUES FROM ('2027-01-01') TO ('2027-04-01');
CREATE TABLE audit_events_2027_q2 PARTITION OF audit_events
FOR VALUES FROM ('2027-04-01') TO ('2027-07-01');
CREATE TABLE audit_events_2027_q3 PARTITION OF audit_events
FOR VALUES FROM ('2027-07-01') TO ('2027-10-01');
CREATE TABLE audit_events_2027_q4 PARTITION OF audit_events
FOR VALUES FROM ('2027-10-01') TO ('2028-01-01');
-- 默认分区(捕获未预期的数据)
CREATE TABLE audit_events_default PARTITION OF audit_events DEFAULT;
-- ==================== 账务分录分区 (billing_ledger_entries) ====================
-- 删除旧表(如果存在且可以重建)
DROP TABLE IF EXISTS billing_ledger_entries CASCADE;
-- 创建分区表
CREATE TABLE billing_ledger_entries (
id BIGINT NOT NULL,
billing_account_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
project_id BIGINT,
user_id BIGINT,
request_id VARCHAR(64) NOT NULL,
trace_id VARCHAR(64),
entry_type VARCHAR(32) NOT NULL,
direction VARCHAR(2) NOT NULL
CHECK (direction IN ('dr', 'cr')),
amount_minor BIGINT NOT NULL,
currency_code CHAR(3) NOT NULL,
amount_unit VARCHAR(16) NOT NULL DEFAULT 'minor',
balance_after_minor BIGINT,
ref_type VARCHAR(32),
ref_id BIGINT,
occurred_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
idempotency_key VARCHAR(128),
PRIMARY KEY (id, occurred_at)
) PARTITION BY RANGE (occurred_at);
-- 创建索引
CREATE INDEX idx_billing_ledger_entries_account_time
ON billing_ledger_entries (billing_account_id, occurred_at DESC);
CREATE INDEX idx_billing_ledger_entries_tenant_time
ON billing_ledger_entries (tenant_id, occurred_at DESC);
CREATE INDEX idx_billing_ledger_entries_trace_id
ON billing_ledger_entries (trace_id);
CREATE UNIQUE INDEX idx_billing_ledger_entries_idem_key
ON billing_ledger_entries (tenant_id, request_id, entry_type)
WHERE idempotency_key IS NOT NULL;
-- 2026年月度分区
CREATE TABLE billing_ledger_2026_04 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
CREATE TABLE billing_ledger_2026_05 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
CREATE TABLE billing_ledger_2026_06 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
CREATE TABLE billing_ledger_2026_07 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');
CREATE TABLE billing_ledger_2026_08 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');
CREATE TABLE billing_ledger_2026_09 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');
CREATE TABLE billing_ledger_2026_10 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-10-01') TO ('2026-11-01');
CREATE TABLE billing_ledger_2026_11 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-11-01') TO ('2026-12-01');
CREATE TABLE billing_ledger_2026_12 PARTITION OF billing_ledger_entries
FOR VALUES FROM ('2026-12-01') TO ('2027-01-01');
-- 默认分区
CREATE TABLE billing_ledger_default PARTITION OF billing_ledger_entries DEFAULT;
-- ==================== 分区维护存储过程 ====================
-- 自动创建新分区的存储过程(每日执行)
CREATE OR REPLACE FUNCTION create_monthly_partition(
target_table TEXT,
partition_date DATE
) RETURNS VOID AS $$
DECLARE
partition_name TEXT;
start_date DATE;
end_date DATE;
BEGIN
start_date := date_trunc('month', partition_date);
end_date := start_date + INTERVAL '1 month';
partition_name := target_table || '_' || to_char(start_date, 'YYYY_MM');
-- 检查分区是否已存在
IF NOT EXISTS (
SELECT 1 FROM pg_tables
WHERE tablename = partition_name
) THEN
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %I PARTITION OF %I FOR VALUES FROM (%L) TO (%L)',
partition_name, target_table, start_date, end_date
);
RAISE NOTICE 'Created partition: %', partition_name;
ELSE
RAISE NOTICE 'Partition already exists: %', partition_name;
END IF;
END;
$$ LANGUAGE plpgsql;
-- 分区清理存储过程保留24个月
CREATE OR REPLACE FUNCTION drop_old_partitions(
target_table TEXT,
retention_months INT DEFAULT 24
) RETURNS VOID AS $$
DECLARE
partition_record RECORD;
cutoff_date DATE;
partition_name TEXT;
partition_date DATE;
BEGIN
cutoff_date := date_trunc('month', CURRENT_DATE) - (retention_months || ' months')::INTERVAL;
FOR partition_record IN
SELECT inhrelid::regclass::text AS partition_name
FROM pg_inherits
WHERE inhparent = target_table::regclass
LOOP
partition_name := partition_record.partition_name;
-- 检查是否是月度分区(格式: table_YYYY_MM
IF partition_name ~ (target_table || '_[0-9]{4}_[0-9]{2}$') THEN
partition_date := to_date(
substring(partition_name from target_table || '_(.*)'), 'YYYY_MM'
);
IF partition_date < cutoff_date THEN
RAISE NOTICE 'Dropping partition: % (older than %)', partition_name, cutoff_date;
EXECUTE format('DROP TABLE IF EXISTS %I', partition_name);
END IF;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- ==================== 保留策略清理 ====================
-- 清理超过保留期限的审计日志保留1年
CREATE OR REPLACE FUNCTION cleanup_audit_events_partitions(
retention_months INT DEFAULT 12
) RETURNS VOID AS $$
BEGIN
PERFORM drop_old_partitions('audit_events', retention_months);
END;
$$ LANGUAGE plpgsql;
-- 账务分录永久保留,不执行清理
-- CREATE OR REPLACE FUNCTION cleanup_billing_ledger_partitions() IS NOT NEEDED
-- billing_ledger_entries 应该永久保留
-- ==================== 验证查询 ====================
-- 查看所有分区
SELECT
parent.relname AS parent_table,
child.relname AS partition_name,
pg_get_expr(child.relpartbound, child.oid, true) AS partition_range
FROM pg_inherits
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
JOIN pg_class child ON pg_inherits.inhrelid = child.oid
WHERE parent.relname IN ('audit_events', 'billing_ledger_entries')
ORDER BY parent.relname, child.relname;
-- ==================== 权限设置 ====================
-- 授予应用角色必要权限
-- GRANT SELECT, INSERT, UPDATE, DELETE ON audit_events TO app_role;
-- GRANT SELECT, INSERT, UPDATE, DELETE ON billing_ledger_entries TO app_role;
-- GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role;
COMMENT ON TABLE audit_events IS '审计事件表按月分区保留1年';
COMMENT ON TABLE billing_ledger_entries IS '账务分录表,按月分区,永久保留';

View File

@@ -0,0 +1,313 @@
# 告警升级策略 v1.0
> **文档版本**: v1.0
> **创建日期**: 2026-04-07
> **问题**: P1-011 PRD要求"预算、错误率、异常波动告警",但未定义告警升级路径和静默策略
---
## 1. 告警级别定义
### 1.1 告警级别矩阵
| 级别 | 名称 | 响应时间 | 说明 | 通知方式 |
|------|------|----------|------|----------|
| P1 | **紧急** | 5分钟内 | 系统不可用、数据丢失风险 | 电话+短信+IM |
| P2 | **严重** | 15分钟内 | 核心功能受损、性能严重下降 | 短信+IM |
| P3 | **警告** | 1小时内 | 非核心功能异常、指标超阈值 | IM+邮件 |
| P4 | **提示** | 工作时间 | 需要关注但不影响业务 | 邮件 |
### 1.2 告警状态定义
| 状态 | 说明 |
|------|------|
| Firing | 告警触发中 |
| Acknowledged | 已确认,正在处理 |
| Resolved | 已解决 |
| Silenced | 已静默 |
---
## 2. 告警升级规则
### 2.1 自动升级规则
```
┌─────────────────────────────────────────────────────────────────┐
│ 告警升级流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ P3 警告 ──── 30分钟未响应 ────► P2 严重 ──── 15分钟未响应 ────► P1 紧急 │
│ │ │ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 值班工程师 值班TL 值班VP/CTO │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 2.2 升级触发条件
| 升级路径 | 触发条件 | 升级后级别 |
|----------|----------|------------|
| P3 → P2 | 30分钟内未确认 | P2 |
| P3 → P2 | 影响核心功能 | P2 |
| P2 → P1 | 15分钟内未解决 | P1 |
| P2 → P1 | 系统不可用 | P1 |
| P4 → P3 | 指标持续恶化 | P3 |
### 2.3 升级通知模板
**P1 紧急升级通知**:
```
【紧急告警升级】
系统: Supply API
告警: {alert_name}
级别: P1 紧急
时间: {start_time}
持续: {duration}
影响: {impact}
当前状态: {status}
处理人: {assignee}
请立即处理!
```
---
## 3. 告警类型定义
### 3.1 系统层告警
| 告警名称 | 级别 | 条件 | 阈值 | 严重程度 |
|----------|------|------|------|----------|
| CPU使用率过高 | P2 | 5分钟平均值 | > 80% | 严重 |
| CPU使用率危急 | P1 | 5分钟平均值 | > 95% | 紧急 |
| 内存使用率过高 | P2 | 5分钟平均值 | > 85% | 严重 |
| 内存使用率危急 | P1 | 5分钟平均值 | > 95% | 紧急 |
| 磁盘空间不足 | P1 | 剩余空间 | < 10GB | 紧急 |
| 数据库连接池耗尽 | P1 | 可用连接 | = 0 | 紧急 |
| Redis连接异常 | P2 | 连接状态 | = 失败 | 严重 |
### 3.2 应用层告警
| 告警名称 | 级别 | 条件 | 阈值 | 严重程度 |
|----------|------|------|------|----------|
| 服务不可用 | P1 | HTTP状态码 | 5xx > 50% | 紧急 |
| API错误率过高 | P2 | 5分钟错误率 | > 5% | 严重 |
| API延迟过高 | P2 | P95延迟 | > 2s | 严重 |
| API延迟危急 | P1 | P99延迟 | > 5s | 紧急 |
| 认证失败率过高 | P3 | 5分钟失败率 | > 20% | 警告 |
| Token吊销延迟 | P2 | 吊销到生效 | > 10s | 严重 |
### 3.3 业务层告警
| 告警名称 | 级别 | 条件 | 阈值 | 严重程度 |
|----------|------|------|------|----------|
| 预算使用率超限 | P2 | 账户预算 | > 90% | 严重 |
| 预算已用尽 | P1 | 账户预算 | = 100% | 紧急 |
| 结算失败率过高 | P2 | 5分钟失败率 | > 10% | 严重 |
| 账户风险评分高 | P3 | 风险评分 | > 80 | 警告 |
| 提现处理延迟 | P2 | 处理队列 | > 100笔 | 严重 |
| 订单支付超时 | P3 | 超时订单 | > 50笔 | 警告 |
### 3.4 安全层告警
| 告警名称 | 级别 | 条件 | 阈值 | 严重程度 |
|----------|------|------|------|----------|
| 频繁认证失败 | P2 | 15分钟失败数 | > 50次 | 严重 |
| 异常IP访问 | P2 | 异常IP数 | > 20个 | 严重 |
| 权限检查失败 | P3 | 失败次数 | > 100次 | 警告 |
| 可疑QueryKey | P2 | 检测到可疑参数 | > 0 | 严重 |
| KMS连接异常 | P1 | 连接状态 | = 失败 | 紧急 |
---
## 4. 静默策略
### 4.1 静默规则定义
静默规则用于在特定时间段内抑制告警,避免不必要的打扰。
| 静默类型 | 适用场景 | 静默时长 | 审批要求 |
|----------|----------|----------|----------|
| 计划内维护 | 系统升级、数据迁移 | 自定义 | TL审批 |
| 误报静默 | 已知误报源 | ≤ 24h | 值班工程师 |
| 事件静默 | 大促、重大活动 | 自定义 | VP审批 |
### 4.2 静默规则示例
```yaml
# 静默规则配置
silence_rules:
- name: "计划内数据库维护"
id: "silence-20260407-001"
start_time: "2026-04-07T02:00:00Z"
end_time: "2026-04-07T04:00:00Z"
matchers:
- name: "alertname"
value: "DatabaseConnectionError"
- name: "instance"
value: "primary-db.*"
reason: "数据库版本升级维护"
created_by: "ops-lead@company.com"
approved_by: "cto@company.com"
- name: "已知误报"
id: "silence-20260407-002"
start_time: "2026-04-07T10:00:00Z"
end_time: "2026-04-08T10:00:00Z"
matchers:
- name: "alertname"
value: "APILatencyWarning"
- name: "path"
value: "/health"
reason: "健康检查端点已知延迟,误报"
created_by: "sre@company.com"
```
### 4.3 静默约束
1. **最大静默时长**: 单次静默不超过 7 天
2. **提前申请**: 计划内静默需提前 24 小时申请
3. **最小静默范围**: 必须指定具体告警或服务
4. **审计日志**: 所有静默操作必须记录
---
## 5. 通知渠道配置
### 5.1 通知渠道定义
| 渠道 | 用途 | 响应速度 | 配置要求 |
|------|------|----------|----------|
| 电话 | P1紧急告警 | 即时 | 必须配置 |
| 短信 | P1/P2告警 | < 1分钟 | 必须配置 |
| 企业微信 | P2/P3告警 | < 5分钟 | 必须配置 |
| 邮件 | P3/P4告警 | < 15分钟 | 必须配置 |
| 钉钉 | 备用渠道 | < 5分钟 | 推荐配置 |
### 5.2 通知接收人配置
```yaml
notification_channels:
on_call:
primary:
name: "值班工程师"
phone: "+86-138-xxxx-xxxx"
schedule: "weekly_rotation"
backup:
name: "值班TL"
phone: "+86-139-xxxx-xxxx"
escalation_wait: 15m
emergency:
name: "CTO"
phone: "+86-136-xxxx-xxxx"
escalation_wait: 30m
security_team:
name: "安全团队"
email: "security@company.com"
for_alerts:
- "可疑QueryKey"
- "异常IP访问"
- "频繁认证失败"
```
---
## 6. 告警处理流程
### 6.1 处理流程
```
告警触发
┌─────────────────┐
│ P1/P2: 电话通知 │──► 值班工程师响应
│ P3: IM通知 │ │
└─────────────────┘ │
┌───────────────┐
│ 确认告警 │──否──► 等待响应
│ (15min内) │ (自动升级)
└───────────────┘
│是
┌───────────────┐
│ 定位问题 │
│ (30min内) │
└───────────────┘
┌───────────────┐
│ 解决问题 │
│ (按SLO) │
└───────────────┘
┌───────────────┐
│ 验证恢复 │
│ 关闭告警 │
└───────────────┘
```
### 6.2 告警响应时间目标
| 级别 | 确认时间 | 定位时间 | 恢复时间 | SLO |
|------|----------|----------|----------|-----|
| P1 | 5分钟 | 15分钟 | 1小时 | 99.9% |
| P2 | 15分钟 | 30分钟 | 4小时 | 99.5% |
| P3 | 1小时 | 2小时 | 24小时 | 99% |
| P4 | 工作时间 | - | 72小时 | 95% |
---
## 7. 告警管理后台
### 7.1 功能需求
| 功能 | 说明 |
|------|------|
| 告警列表 | 显示所有告警,支持过滤和搜索 |
| 告警详情 | 显示告警完整信息、波形图、处理历史 |
| 告警确认 | 确认告警并填写处理说明 |
| 告警静默 | 创建、编辑、删除静默规则 |
| 告警升级 | 手动升级告警级别 |
| 告警统计 | 显示告警趋势、MTBF、MTTR |
### 7.2 推荐工具
| 工具 | 类型 | 适用规模 |
|------|------|----------|
| Prometheus Alertmanager | 开源 | 中小型 |
| Grafana Alerting | 开源 | 中小型 |
| PagerDuty | 商业SAAS | 中大型 |
| Opsgenie | 商业SAAS | 中大型 |
| 飞书告警 | 商业SAAS | 国内企业 |
---
## 8. SLO 与告警关联
### 8.1 SLO 定义
| 服务 | SLO | 错误预算 |
|------|-----|----------|
| API可用性 | 99.9% | 43.8分钟/月 |
| API延迟 P95 | 99% < 500ms | 7.3小时/月 |
| 认证成功率 | 99.5% | 3.6小时/月 |
| 结算处理 | 99% | 7.3小时/月 |
### 8.2 基于错误预算的告警
当错误预算消耗超过阈值时触发警告:
- **消耗 > 50%**: P3 警告
- **消耗 > 75%**: P2 严重
- **消耗 > 100%**: P1 紧急
---
> **维护记录**:
> - v1.0 (2026-04-07): 初始版本

View File

@@ -0,0 +1,411 @@
# 集成测试策略 v1.0
> **文档版本**: v1.0
> **创建日期**: 2026-04-07
> **问题**: P1-012 供应侧技术设计定义了失败注入测试,但缺少跨域集成测试策略
---
## 1. 测试金字塔
```
┌─────────────┐
│ E2E │ 少量,验证关键链路
│ Tests │ 5-10个
─┴─────────────┴─
┌─────────────────┐
│ Integration │ 跨域测试,验证模块间交互
│ Tests │ 20-30个
─┴─────────────────┴─
┌───────────────────────┐
│ Unit Tests │ 大量,快速反馈
│ │ 200+个
─┴───────────────────────┴─
```
---
## 2. 跨域集成测试范围
### 2.1 域间依赖关系
```
IAM ──────► Auth ──────► Supply
│ │ │
▼ ▼ ▼
Core ◄──────── Audit ◄──── Billing
```
| 源域 | 目标域 | 依赖类型 | 测试用例数 |
|------|--------|----------|------------|
| IAM | Auth | Token验证 | 5 |
| Supply | Billing | 结算触发 | 8 |
| Supply | Audit | 审计记录 | 5 |
| Supply | Core | 账户操作 | 10 |
| IAM | Audit | 权限变更审计 | 5 |
### 2.2 核心集成测试场景
| 场景ID | 场景描述 | 涉及域 | 优先级 |
|--------|----------|--------|--------|
| IT-001 | 用户登录→Token签发→API调用 | IAM→Auth→Core | P0 |
| IT-002 | 创建供应账户→发布套餐→下单 | Supply→Core | P0 |
| IT-003 | 下单→使用API→生成账单 | Supply→Billing | P0 |
| IT-004 | 结算申请→处理→完成通知 | Supply→Billing→Audit | P1 |
| IT-005 | 权限变更→Token吊销→验证拒绝 | IAM→Auth→Audit | P1 |
| IT-006 | 批量调价→部分失败→补偿处理 | Supply→Audit | P1 |
---
## 3. 集成测试环境
### 3.1 环境配置
| 环境 | 用途 | 数据库 | 缓存 | 特点 |
|------|------|--------|------|------|
| local | 本地开发 | PostgreSQL (Docker) | Redis (Docker) | 快速迭代 |
| integration | CI/CD集成 | PostgreSQL + Redis | 独立实例 | 隔离 |
| staging | 预发布 | 生产镜像 | 生产镜像 | 接近生产 |
### 3.2 测试数据库初始化
```sql
-- integration_test_setup.sql
-- 测试前初始化测试数据
BEGIN;
-- 清理现有测试数据
TRUNCATE TABLE iam_users CASCADE;
TRUNCATE TABLE supply_accounts CASCADE;
TRUNCATE TABLE supply_packages CASCADE;
TRUNCATE TABLE supply_orders CASCADE;
TRUNCATE TABLE billing_ledger_entries CASCADE;
-- 插入测试用户
INSERT INTO iam_users (id, username, email, role) VALUES
(1, 'test_admin', 'admin@test.com', 'admin'),
(2, 'test_operator', 'operator@test.com', 'operator'),
(3, 'test_viewer', 'viewer@test.com', 'viewer');
-- 插入测试账户
INSERT INTO supply_accounts (id, user_id, platform, status, risk_level) VALUES
(1, 1, 'openai', 'active', 'low'),
(2, 2, 'anthropic', 'active', 'normal');
-- 插入测试套餐
INSERT INTO supply_packages (id, supply_account_id, user_id, platform, model,
total_quota, available_quota, status) VALUES
(1, 1, 1, 'openai', 'gpt-4', 1000000, 800000, 'active'),
(2, 2, 2, 'anthropic', 'claude-3', 500000, 500000, 'active');
COMMIT;
```
---
## 4. 集成测试用例
### 4.1 IAM → Auth 集成测试
```go
// TestIT001_TokenLifecycle IAM到Auth集成测试
func TestIT001_TokenLifecycle(t *testing.T) {
// 1. 创建用户
user := &iam.User{Username: "test_user", Email: "test@example.com"}
err := iamService.CreateUser(ctx, user)
require.NoError(t, err)
// 2. 签发Token
token, err := authService.IssueToken(ctx, user.ID, []string{"supply:accounts:read"})
require.NoError(t, err)
assert.NotEmpty(t, token.AccessToken)
// 3. 验证Token
claims, err := authService.ValidateToken(ctx, token.AccessToken)
require.NoError(t, err)
assert.Equal(t, user.ID, claims.UserID)
// 4. 吊销Token
err = authService.RevokeToken(ctx, token.AccessToken)
require.NoError(t, err)
// 5. 验证Token已失效
_, err = authService.ValidateToken(ctx, token.AccessToken)
assert.Error(t, err)
}
```
### 4.2 Supply → Billing 集成测试
```go
// TestIT002_SettlementFlow Supply到Billing集成测试
func TestIT002_SettlementFlow(t *testing.T) {
// 1. 创建供应商账户
account := &supply.Account{
UserID: testUserID,
Platform: "openai",
Status: "active",
}
err := supplyRepo.Create(ctx, account)
require.NoError(t, err)
// 2. 创建套餐
pkg := &supply.Package{
AccountID: account.ID,
TotalQuota: 1000000,
AvailableQuota: 1000000,
Status: "active",
}
err = supplyRepo.CreatePackage(ctx, pkg)
require.NoError(t, err)
// 3. 生成使用记录
usage := &supply.UsageRecord{
AccountID: account.ID,
Tokens: 5000,
Cost: 0.15,
Status: "completed",
}
err = supplyRepo.CreateUsageRecord(ctx, usage)
require.NoError(t, err)
// 4. 验证收益记录生成
earnings, err := billingRepo.GetEarningsByAccount(ctx, account.ID)
require.NoError(t, err)
assert.NotEmpty(t, earnings)
assert.Equal(t, usage.Cost, earnings[0].Amount)
// 5. 创建结算单
settlement, err := billingService.CreateSettlement(ctx, account.ID)
require.NoError(t, err)
assert.Equal(t, "pending", settlement.Status)
// 6. 处理结算
err = billingService.ProcessSettlement(ctx, settlement.ID)
require.NoError(t, err)
// 7. 验证结算完成
settlement, err = billingRepo.GetSettlement(ctx, settlement.ID)
require.NoError(t, err)
assert.Equal(t, "completed", settlement.Status)
}
```
### 4.3 跨域失败注入测试
```go
// TestIT003_FailureInjection 跨域失败注入测试
func TestIT003_FailureInjection(t *testing.T) {
tests := []struct {
name string
injectFailure func()
verify func(*testing.T)
}{
{
name: "数据库连接失败时收益计算",
injectFailure: func() {
// 模拟数据库连接失败
mockDB.SetFailure(true)
defer mockDB.SetFailure(false)
},
verify: func(t *testing.T) {
// 验证使用记录仍然可以创建
_, err := supplyService.RecordUsage(ctx, usage)
assert.Error(t, err)
// 验证重试机制
},
},
{
name: "Redis不可用时Token验证",
injectFailure: func() {
mockRedis.SetAvailable(false)
defer mockRedis.SetAvailable(true)
},
verify: func(t *testing.T) {
// 验证降级到数据库验证
claims, err := authService.ValidateToken(ctx, token)
assert.NoError(t, err)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.injectFailure()
tt.verify(t)
})
}
}
```
---
## 5. 集成测试夹具
### 5.1 测试夹具定义
```go
// IntegrationTestFixture 集成测试夹具
type IntegrationTestFixture struct {
DB *pgxpool.Pool
Redis *redis.Client
IAM *iam.Service
Auth *auth.Service
Supply *supply.Service
Billing *billing.Service
Audit *audit.Service
Teardown func()
}
// NewIntegrationTestFixture 创建集成测试夹具
func NewIntegrationTestFixture(t *testing.T) *IntegrationTestFixture {
// 启动测试数据库
db := startTestDatabase(t)
redis := startTestRedis(t)
fixture := &IntegrationTestFixture{
DB: db,
Redis: redis,
IAM: iam.NewService(db),
Auth: auth.NewService(db, redis),
// ... 其他服务初始化
}
fixture.Teardown = func() {
db.Close()
redis.Close()
}
return fixture
}
```
### 5.2 测试数据生成
```go
// TestDataGenerator 测试数据生成器
type TestDataGenerator struct {
fixture *IntegrationTestFixture
}
func (g *TestDataGenerator) CreateTestUser(t *testing.T) *iam.User {
user := &iam.User{
Username: random.String(10),
Email: random.Email(),
Role: "operator",
}
err := g.fixture.IAM.CreateUser(context.Background(), user)
require.NoError(t, err)
return user
}
func (g *TestDataGenerator) CreateTestAccount(t *testing.T, userID int64) *supply.Account {
account := &supply.Account{
UserID: userID,
Platform: "openai",
Status: "active",
}
err := g.fixture.Supply.CreateAccount(context.Background(), account)
require.NoError(t, err)
return account
}
```
---
## 6. CI/CD 集成
### 6.1 GitHub Actions 配置
```yaml
# .github/workflows/integration-test.yml
name: Integration Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
integration-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: supply_test
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Run Integration Tests
run: |
go test -tags=integration -v ./tests/integration/...
env:
DATABASE_URL: postgres://test_user:test_pass@localhost:5432/supply_test
REDIS_URL: redis://localhost:6379
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/integration.out
```
---
## 7. 测试覆盖率目标
| 测试类型 | 覆盖率目标 | 说明 |
|----------|------------|------|
| 单元测试 | > 80% | 每个模块独立测试 |
| 集成测试 | > 60% | 域间交互测试 |
| E2E测试 | > 40% | 关键用户旅程 |
---
## 8. 执行计划
### 8.1 迭代计划
| 迭代 | 内容 | 用时 |
|------|------|------|
| Sprint 1 | 搭建测试框架、配置CI | 1周 |
| Sprint 2 | IAM/Auth集成测试 | 1周 |
| Sprint 3 | Supply/Billing集成测试 | 1周 |
| Sprint 4 | 失败注入测试、覆盖率优化 | 1周 |
### 8.2 测试执行频率
| 测试类型 | 执行频率 | 执行时间 |
|----------|----------|----------|
| 单元测试 | 每次PR | < 2分钟 |
| 集成测试 | 每次PR合并 | < 10分钟 |
| E2E测试 | 每日构建 | < 30分钟 |
| 回归测试 | 每次发布 | < 1小时 |
---
> **维护记录**:
> - v1.0 (2026-04-07): 初始版本

View File

@@ -0,0 +1,395 @@
# 性能测试基线 v1.0
> **文档版本**: v1.0
> **创建日期**: 2026-04-07
> **问题**: P1-013 SLO定义了P95延迟目标但未定义性能测试基线和测试数据规模
---
## 1. 性能测试目标
### 1.1 响应时间目标 (SLO)
| API类别 | P50 | P95 | P99 | SLO |
|---------|-----|-----|-----|-----|
| 同步API (读取) | < 50ms | < 200ms | < 500ms | 99% |
| 同步API (写入) | < 100ms | < 300ms | < 800ms | 99% |
| 异步API | < 500ms | < 1s | < 2s | 99% |
| 认证Token验证 | < 10ms | < 30ms | < 100ms | 99.9% |
| 健康检查 | < 5ms | < 10ms | < 20ms | 99.99% |
### 1.2 吞吐量目标
| 场景 | 并发数 | RPS目标 | 说明 |
|------|--------|---------|------|
| 账户查询 | 100 | 1000 | 峰值5倍 |
| 套餐列表 | 100 | 500 | 分页场景 |
| 下单创建 | 50 | 200 | 事务性操作 |
| Token验证 | 200 | 2000 | 高频调用 |
| 使用记录写入 | 100 | 1000 | 日志级别写入 |
### 1.3 资源利用率目标
| 资源 | 正常负载 | 峰值负载 | 告警阈值 |
|------|----------|----------|----------|
| CPU | < 50% | < 80% | > 80% |
| 内存 | < 60% | < 80% | > 85% |
| 数据库连接 | < 50% | < 70% | > 80% |
| Redis连接 | < 40% | < 60% | > 70% |
| 网络带宽 | < 30% | < 50% | > 70% |
---
## 2. 测试场景定义
### 2.1 基准测试场景 (Baseline Tests)
| 场景ID | 场景名称 | 描述 | 权重 |
|--------|----------|------|------|
| BL-01 | HealthCheck | /health 端点 | 10% |
| BL-02 | 账户列表查询 | GET /api/v1/accounts | 20% |
| BL-03 | 套餐详情查询 | GET /api/v1/packages/:id | 20% |
| BL-04 | Token验证 | POST /api/v1/auth/validate | 30% |
| BL-05 | 使用记录写入 | POST /api/v1/usage | 20% |
### 2.2 压力测试场景 (Stress Tests)
| 场景ID | 场景名称 | 目标 | 递增 |
|--------|----------|------|------|
| ST-01 | 线性压力 | RPS从100递增至1000 | +100/30s |
| ST-02 | 突发压力 | 50% 基础 + 200% 峰值 | 脉冲模式 |
| ST-03 | 长期压力 | 70% 峰值持续 4h | 稳定 |
### 2.3 容量测试场景 (Capacity Tests)
| 场景ID | 场景名称 | 目标 | 终止条件 |
|--------|----------|------|----------|
| CT-01 | 最大并发 | 找到最大支持并发 | P99 > 1s |
| CT-02 | 最大RPS | 找到最大支持RPS | 错误率 > 1% |
| CT-03 | 数据量增长 | 验证随数据量增长的性能 | P95 > 基线2倍 |
### 2.4 峰值测试场景 (Peak Tests)
| 场景ID | 场景名称 | 模拟 | 持续时间 |
|--------|----------|------|----------|
| PK-01 | 工作日峰值 | 9:00-12:00流量 | 3h |
| PK-02 | 活动峰值 | 限时促销活动 | 1h |
| PK-03 | 月底峰值 | 账单生成高峰 | 4h |
---
## 3. 测试数据规模
### 3.1 数据规模定义
| 级别 | 账户数 | 套餐数 | 订单数 | 使用记录 | 用途 |
|------|--------|--------|--------|----------|------|
| Small | 1,000 | 5,000 | 10,000 | 100,000 | 本地开发 |
| Medium | 10,000 | 50,000 | 100,000 | 1,000,000 | 集成测试 |
| Large | 100,000 | 500,000 | 1,000,000 | 10,000,000 | 性能测试 |
| Production | 1,000,000 | 5,000,000 | 10,000,000 | 100,000,000 | 容量测试 |
### 3.2 测试数据生成策略
```sql
-- 生成Large级别测试数据
-- 执行时间: ~30分钟
-- 1. 生成用户 (100,000)
INSERT INTO iam_users (username, email, role, created_at)
SELECT
'user_' || generate_series,
'user_' || generate_series || '@test.com',
(ARRAY['admin', 'operator', 'viewer'])[floor(random() * 3 + 1)],
NOW() - interval '365 days' * random()
FROM generate_series(1, 100000);
-- 2. 生成供应账户 (500,000, 每个用户5个)
INSERT INTO supply_accounts (user_id, platform, status, created_at)
SELECT
(random() * 99999 + 1)::bigint,
(ARRAY['openai', 'anthropic', 'azure', 'google'])[floor(random() * 4 + 1)],
(ARRAY['active', 'pending', 'suspended'])[floor(random() * 3 + 1)],
NOW() - interval '180 days' * random()
FROM generate_series(1, 500000);
-- 3. 生成套餐 (500,000)
INSERT INTO supply_packages (
supply_account_id, user_id, platform, model,
total_quota, available_quota, status, created_at
)
SELECT
generate_series,
(random() * 99999 + 1)::bigint,
(ARRAY['openai', 'anthropic', 'azure'])[floor(random() * 3 + 1)],
(ARRAY['gpt-4', 'gpt-3.5', 'claude-3', 'claude-2'])[floor(random() * 4 + 1)],
(random() * 1000000)::bigint + 100000,
(random() * 500000)::bigint + 100000,
'active',
NOW() - interval '90 days' * random()
FROM generate_series(1, 500000);
-- 4. 创建索引
CREATE INDEX CONCURRENTLY idx_test_accounts_user ON supply_accounts(user_id);
CREATE INDEX CONCURRENTLY idx_test_packages_account ON supply_packages(supply_account_id);
```
### 3.3 数据刷新策略
```bash
#!/bin/bash
# refresh_test_data.sh - 刷新测试数据
# 每周执行一次,保持数据新鲜度
set -e
psql -h localhost -U postgres -d supply_test <<-EOSQL
-- 更新订单时间分布
UPDATE supply_orders
SET created_at = NOW() - (random() * interval '30 days')
WHERE created_at < NOW() - interval '30 days';
-- 更新使用记录时间分布
UPDATE supply_usage_records
SET started_at = NOW() - (random() * interval '7 days')
WHERE started_at < NOW() - interval '7 days';
-- 重新生成部分数据
DELETE FROM supply_usage_records WHERE id > 1000000;
\i generate_usage_records.sql
EOSQL
```
---
## 4. 性能测试工具
### 4.1 工具选型
| 工具 | 用途 | 优势 | 劣势 |
|------|------|------|------|
| k6 | 基准测试、压力测试 | 脚本简单,输出丰富 | 分布式能力弱 |
| wrk | 基准测试 | 性能高Lua脚本 | 无分布式 |
| locust | 复杂场景 | Python脚本分布式 | 学习曲线 |
| Artillery | API测试 | YAML配置云集成 | 并发有限 |
| Vegeta | 恒定RPS测试 | Go实现高性能 | 脚本能力弱 |
### 4.2 k6 测试脚本示例
```javascript
// baseline_test.js - 基准测试脚本
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';
// 自定义指标
const errorRate = new Rate('errors');
const latency = new Trend('latency');
export const options = {
stages: [
{ duration: '2m', target: 100 }, // 预热
{ duration: '5m', target: 100 }, // 基准负载
{ duration: '2m', target: 0 }, // 冷却
],
thresholds: {
'http_req_duration': ['p(95)<500'],
'errors': ['rate<0.01'],
},
};
const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';
const TOKEN = __ENV.TOKEN || 'test-token';
export default function() {
// Token验证
const validateRes = http.post(
`${BASE_URL}/api/v1/auth/validate`,
JSON.stringify({ token: TOKEN }),
{ headers: { 'Content-Type': 'application/json' } }
);
latency.add(validateRes.timings.duration);
check(validateRes, {
'validate status 200': (r) => r.status === 200,
'validate latency < 30ms': (r) => r.timings.duration < 30,
}) || errorRate.add(1);
// 账户查询
const accountsRes = http.get(
`${BASE_URL}/api/v1/accounts`,
{ headers: { 'Authorization': `Bearer ${TOKEN}` } }
);
latency.add(accountsRes.timings.duration);
check(accountsRes, {
'accounts status 200': (r) => r.status === 200,
'accounts latency < 200ms': (r) => r.timings.duration < 200,
}) || errorRate.add(1);
sleep(1);
}
```
---
## 5. 性能基线报告
### 5.1 基线报告模板
```markdown
# 性能测试报告 - {日期}
## 测试环境
- CPU: Intel Xeon 2.4GHz x 8
- 内存: 16GB DDR4
- 数据库: PostgreSQL 15 (4核8GB)
- Redis: 7.0 (2核4GB)
## 测试配置
- 持续时间: 5分钟
- 并发用户: 100
- 总请求数: 30,000
## 结果摘要
| 指标 | 目标 | 实际 | 状态 |
|------|------|------|------|
| P95延迟 | < 200ms | 156ms | ✅ 通过 |
| P99延迟 | < 500ms | 423ms | ✅ 通过 |
| 错误率 | < 1% | 0.02% | ✅ 通过 |
| RPS | > 100 | 128 | ✅ 通过 |
## 详细指标
### 响应时间分布
- P50: 45ms
- P90: 120ms
- P95: 156ms
- P99: 423ms
- Max: 1.2s
### 吞吐量
- 平均RPS: 128
- 峰值RPS: 156
### 错误分析
- 总错误: 6
- 超时错误: 3
- 服务端错误: 3
```
### 5.2 性能回归检测
```bash
#!/bin/bash
# compare_baseline.sh - 基线对比
CURRENT=$(cat perf_report_latest.json)
BASELINE=$(cat perf_report_baseline.json)
# 比较P95延迟
CURRENT_P95=$(echo "$CURRENT" | jq '.latency.p95')
BASELINE_P95=$(echo "$BASELINE" | jq '.latency.p95')
REGRESSION=$(echo "$CURRENT_P95 > $BASELINE_P95 * 1.1" | bc)
if [ "$REGRESSION" = "1" ]; then
echo "⚠️ 警告: P95延迟回归检测到"
echo "基线: ${BASELINE_P95}ms"
echo "当前: ${CURRENT_P95}ms"
exit 1
fi
echo "✅ 性能无回归"
```
---
## 6. 性能测试执行计划
### 6.1 执行频率
| 测试类型 | 频率 | 触发条件 |
|----------|------|----------|
| 基准测试 | 每日 | 代码提交后自动执行 |
| 压力测试 | 每周 | 手动触发 |
| 容量测试 | 每月 | 发布前执行 |
| 峰值测试 | 每季度 | 重大活动前 |
### 6.2 性能测试流程
```
代码提交
┌─────────────┐
│ 自动化构建 │──失败──► 返回修改
└─────────────┘
│成功
┌─────────────┐
│ 基准测试 │──失败──► 创建Bug
└─────────────┘
│通过
┌─────────────┐
│ 代码审查 │
└─────────────┘
│通过
┌─────────────┐
│ 集成测试 │
└─────────────┘
│通过
合并
```
---
## 7. 性能问题诊断
### 7.1 常见性能问题
| 症状 | 可能原因 | 诊断方法 |
|------|----------|----------|
| P99延迟高 | 数据库索引缺失 | EXPLAIN ANALYZE |
| RPS低 | 线程池配置不当 | jstack分析 |
| 内存增长 | 内存泄漏 | heap profile |
| 连接池耗尽 | 连接泄漏 | 连接数监控 |
### 7.2 诊断工具
```bash
# 数据库慢查询
psql -c "SELECT query, calls, mean_time FROM pg_stat_statements ORDER BY mean_time DESC LIMIT 10;"
# Redis命令统计
redis-cli INFO commandstats | grep -E "cmdstat_get|cmdstat_set"
# Go pprof
go tool pprof http://localhost:6060/debug/pprof/heap
```
---
## 8. 性能优化建议
### 8.1 优化优先级
1. **P0 (立即优化)**: P99 > SLO目标
2. **P1 (本周优化)**: P95 > SLO目标 150%
3. **P2 (本月优化)**: RPS < 目标 70%
### 8.2 常见优化手段
| 问题 | 优化方案 |
|------|----------|
| 数据库查询慢 | 添加索引、优化SQL |
| 序列化开销 | 使用更快的序列化库 |
| GC压力大 | 对象池、减少分配 |
| 连接池耗尽 | 增加连接数、优化使用 |
---
> **维护记录**:
> - v1.0 (2026-04-07): 初始版本

View File

@@ -0,0 +1,102 @@
-- ============================================================================
-- 审计事件表 Schema 迁移 v1 -> v2
-- 问题: 数据库中的 audit_events 表是旧版 schema与代码中的 model 不匹配
--
-- 旧版 schema (当前生产表):
-- domain_code, action_code, severity, client_ip, before_data, after_data
--
-- 新版 schema (代码期望):
-- event_id, event_name, event_category, timestamp, timestamp_ms,
-- action, source_ip, operator_id, operator_type 等
-- ============================================================================
-- 1. 备份现有数据(如果表中有数据)
CREATE TABLE IF NOT EXISTS audit_events_backup AS
SELECT * FROM audit_events WHERE 1=0;
-- 如果有数据则备份
INSERT INTO audit_events_backup SELECT * FROM audit_events;
-- 2. 删除旧表和约束
DROP TABLE IF EXISTS audit_events CASCADE;
-- 3. 创建新表(使用 partition_strategy_v1.sql 中的定义)
CREATE TABLE IF NOT EXISTS audit_events (
id BIGSERIAL,
event_id VARCHAR(100) NOT NULL,
event_name VARCHAR(100) NOT NULL,
event_category VARCHAR(50),
event_sub_category VARCHAR(50),
timestamp TIMESTAMPTZ NOT NULL,
timestamp_ms BIGINT NOT NULL,
request_id VARCHAR(100),
idempotency_key VARCHAR(128),
tenant_id BIGINT,
object_type VARCHAR(100),
object_id VARCHAR(100),
action VARCHAR(100) NOT NULL,
result_code VARCHAR(50),
source_ip VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id, timestamp)
) PARTITION BY RANGE (timestamp);
-- 4. 创建索引
CREATE INDEX idx_audit_events_tenant_id ON audit_events(tenant_id);
CREATE INDEX idx_audit_events_request_id ON audit_events(request_id);
CREATE INDEX idx_audit_events_created_at ON audit_events(created_at);
CREATE INDEX idx_audit_events_object ON audit_events(object_type, object_id);
-- 5. 创建初始分区过去12个月 + 未来3个月
DO $$
DECLARE
i INT;
target_date DATE;
partition_name TEXT;
start_date DATE;
end_date DATE;
BEGIN
-- 过去12个月
FOR i IN -12..0 LOOP
target_date := (CURRENT_DATE + (i || ' months')::INTERVAL)::DATE;
start_date := date_trunc('month', target_date)::DATE;
end_date := (start_date + INTERVAL '1 month')::DATE;
partition_name := 'audit_events_' || to_char(start_date, 'YYYY_MM');
IF NOT EXISTS (
SELECT 1 FROM pg_class WHERE relname = partition_name
) THEN
EXECUTE format(
'CREATE TABLE %I PARTITION OF audit_events FOR VALUES FROM (%L) TO (%L)',
partition_name, start_date, end_date
);
RAISE NOTICE 'Created partition: %', partition_name;
END IF;
END LOOP;
-- 未来3个月
FOR i IN 1..3 LOOP
target_date := (CURRENT_DATE + (i || ' months')::INTERVAL)::DATE;
start_date := date_trunc('month', target_date)::DATE;
end_date := (start_date + INTERVAL '1 month')::DATE;
partition_name := 'audit_events_' || to_char(start_date, 'YYYY_MM');
IF NOT EXISTS (
SELECT 1 FROM pg_class WHERE relname = partition_name
) THEN
EXECUTE format(
'CREATE TABLE %I PARTITION OF audit_events FOR VALUES FROM (%L) TO (%L)',
partition_name, start_date, end_date
);
RAISE NOTICE 'Created partition: %', partition_name;
END IF;
END LOOP;
END $$;
-- 6. 验证
SELECT table_name, count(*) as partition_count
FROM pg_tables
WHERE table_name LIKE 'audit_events%' AND table_name != 'audit_events' AND table_name != 'audit_events_backup'
GROUP BY table_name;
COMMENT ON TABLE audit_events IS '审计事件表 - 按月分区保留12个月';

View File

@@ -0,0 +1,347 @@
# Supply API 数据字典 v1.0
> **文档版本**: v1.0
> **创建日期**: 2026-04-07
> **基于**: supply_schema_v1.sql
---
## 1. supply_accounts (供应商账户表)
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | BIGINT | PRIMARY KEY | 自增 | 账户唯一标识 |
| user_id | BIGINT | NOT NULL | - | 所属用户ID |
| platform | VARCHAR(50) | NOT NULL | - | 平台标识 (openai/anthropic/azure等) |
| account_type | VARCHAR(20) | NOT NULL | - | 账户类型: api_key/oauth |
| account_name | VARCHAR(100) | - | - | 账户显示名称 |
| encrypted_credentials | TEXT | NOT NULL | - | 加密存储的凭证 (AES-256-GCM) |
| key_id | VARCHAR(100) | - | - | 凭证密钥标识 |
| status | VARCHAR(20) | NOT NULL | 'pending' | 状态: pending/active/suspended/disabled |
| risk_level | VARCHAR(20) | NOT NULL | 'normal' | 风险等级: low/normal/high |
| total_quota | NUMERIC(20,6) | - | - | 账户总配额 |
| available_quota | NUMERIC(20,6) | - | - | 可用配额 |
| frozen_quota | NUMERIC(20,6) | NOT NULL | 0 | 冻结配额 |
| is_verified | BOOLEAN | - | FALSE | 是否已验证 |
| verified_at | TIMESTAMPTZ | - | - | 验证时间 |
| last_check_at | TIMESTAMPTZ | - | - | 最后检查时间 |
| tos_compliant | BOOLEAN | - | TRUE | TOS合规状态 |
| tos_check_result | TEXT | - | - | TOS检查结果 |
| total_requests | BIGINT | - | 0 | 累计请求数 |
| total_tokens | BIGINT | - | 0 | 累计使用Token数 |
| total_cost | NUMERIC(20,6) | - | 0 | 累计消费金额 |
| success_rate | NUMERIC(5,2) | - | 0 | 请求成功率(%) |
| risk_score | INT | - | 0 | 风险评分 (0-100) |
| risk_reason | TEXT | - | - | 风险原因 |
| is_frozen | BOOLEAN | - | FALSE | 是否被冻结 |
| frozen_reason | TEXT | - | - | 冻结原因 |
| created_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 更新时间 |
| created_by | BIGINT | - | - | 创建人 |
| updated_by | BIGINT | - | - | 更新人 |
**索引**:
- idx_supply_accounts_user_id (user_id)
- idx_supply_accounts_platform (platform)
- idx_supply_accounts_status (status)
- idx_supply_accounts_risk_level (risk_level)
---
## 2. supply_packages (供应套餐表)
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | BIGINT | PRIMARY KEY | 自增 | 套餐唯一标识 |
| supply_account_id | BIGINT | NOT NULL, FK | - | 关联供应商账户 |
| user_id | BIGINT | NOT NULL | - | 创建人用户ID |
| platform | VARCHAR(50) | NOT NULL | - | 平台标识 |
| model | VARCHAR(100) | NOT NULL | - | 模型标识 |
| total_quota | NUMERIC(20,6) | NOT NULL | - | 套餐总配额 |
| available_quota | NUMERIC(20,6) | NOT NULL | - | 可用配额 |
| sold_quota | NUMERIC(20,6) | - | 0 | 已售配额 |
| reserved_quota | NUMERIC(20,6) | - | 0 | 预留配额 |
| price_per_1m_input | NUMERIC(20,6) | - | - | 每百万输入Token价格 |
| price_per_1m_output | NUMERIC(20,6) | - | - | 每百万输出Token价格 |
| min_purchase | NUMERIC(20,6) | - | - | 最小购买量 |
| start_at | TIMESTAMPTZ | - | - | 生效时间 |
| end_at | TIMESTAMPTZ | - | - | 失效时间 |
| valid_days | INT | - | - | 有效天数 |
| status | VARCHAR(20) | NOT NULL | 'draft' | 状态: draft/active/paused/sold_out/expired |
| max_concurrent | INT | - | 10 | 最大并发数 |
| rate_limit_rpm | INT | - | 60 | 每分钟限流 |
| total_orders | INT | - | 0 | 累计订单数 |
| total_revenue | NUMERIC(20,6) | - | 0 | 累计收入 |
| rating | NUMERIC(3,2) | - | 0 | 平均评分 |
| rating_count | INT | - | 0 | 评分次数 |
| created_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 更新时间 |
**索引**:
- idx_supply_packages_supply_account_id (supply_account_id)
- idx_supply_packages_user_id (user_id)
- idx_supply_packages_platform_model (platform, model)
- idx_supply_packages_status (status)
---
## 3. supply_orders (供应订单表)
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | BIGINT | PRIMARY KEY | 自增 | 订单唯一标识 |
| order_no | VARCHAR(64) | NOT NULL, UNIQUE | - | 订单编号 |
| buyer_user_id | BIGINT | NOT NULL | - | 买家用户ID |
| buyer_team_id | BIGINT | - | - | 买家团队ID |
| supply_account_id | BIGINT | NOT NULL, FK | - | 供应商账户ID |
| supplier_user_id | BIGINT | NOT NULL | - | 供应商用户ID |
| supply_package_id | BIGINT | NOT NULL, FK | - | 套餐ID |
| platform | VARCHAR(50) | NOT NULL | - | 平台标识 |
| model | VARCHAR(100) | NOT NULL | - | 模型标识 |
| quota_amount | NUMERIC(20,6) | NOT NULL | - | 购买配额量 |
| quota_tokens | BIGINT | - | - | 购买Token配额 |
| unit_price | NUMERIC(20,6) | NOT NULL | - | 单价 |
| total_amount | NUMERIC(20,6) | NOT NULL | - | 总金额 |
| platform_fee | NUMERIC(20,6) | NOT NULL | - | 平台手续费 |
| supplier_earnings | NUMERIC(20,6) | NOT NULL | - | 供应商实收 |
| status | VARCHAR(20) | NOT NULL | 'pending' | 状态: pending/paid/using/expired/refunded |
| used_quota | NUMERIC(20,6) | - | 0 | 已使用配额 |
| remaining_quota | NUMERIC(20,6) | - | - | 剩余配额 |
| expired_at | TIMESTAMPTZ | - | - | 过期时间 |
| payment_method | VARCHAR(20) | - | - | 支付方式 |
| paid_at | TIMESTAMPTZ | - | - | 支付时间 |
| payment_transaction_id | VARCHAR(100) | - | - | 支付流水号 |
| created_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 更新时间 |
**索引**:
- idx_supply_orders_buyer_user_id (buyer_user_id)
- idx_supply_orders_supplier_user_id (supplier_user_id)
- idx_supply_orders_supply_package_id (supply_package_id)
- idx_supply_orders_status (status)
---
## 4. supply_usage_records (使用记录表)
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | BIGINT | PRIMARY KEY | 自增 | 记录唯一标识 |
| order_id | BIGINT | NOT NULL, FK | - | 关联订单ID |
| buyer_user_id | BIGINT | NOT NULL | - | 买家用户ID |
| supply_account_id | BIGINT | NOT NULL, FK | - | 供应商账户ID |
| supplier_user_id | BIGINT | NOT NULL | - | 供应商用户ID |
| request_id | VARCHAR(64) | NOT NULL | - | 请求唯一ID |
| upstream_request_id | VARCHAR(128) | - | - | 上游请求ID |
| api_key_id | BIGINT | - | - | API Key ID |
| platform | VARCHAR(50) | NOT NULL | - | 平台标识 |
| model | VARCHAR(100) | NOT NULL | - | 模型标识 |
| endpoint | VARCHAR(100) | NOT NULL | - | API端点 |
| request_tokens | BIGINT | - | - | 请求Token数 |
| response_tokens | BIGINT | - | - | 响应Token数 |
| total_tokens | BIGINT | STORED GENERATED | - | 总Token数 (计算字段) |
| input_cost | NUMERIC(20,6) | - | - | 输入费用 |
| output_cost | NUMERIC(20,6) | - | - | 输出费用 |
| total_cost | NUMERIC(20,6) | NOT NULL | - | 总费用 |
| unit_price | NUMERIC(20,6) | NOT NULL | - | 单价 |
| response_status | INT | - | - | 响应状态码 |
| latency_ms | INT | - | - | 延迟(毫秒) |
| error_message | TEXT | - | - | 错误信息 |
| success | BOOLEAN | - | TRUE | 是否成功 |
| started_at | TIMESTAMPTZ | NOT NULL | - | 开始时间 |
| completed_at | TIMESTAMPTZ | - | - | 完成时间 |
| created_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 创建时间 |
**索引**:
- idx_supply_usage_records_request_id (request_id)
- idx_supply_usage_records_order_id (order_id)
- idx_supply_usage_records_supply_account_id (supply_account_id)
- idx_supply_usage_records_platform_model (platform, model)
- idx_supply_usage_records_started_at (started_at)
---
## 5. supply_earnings (收益表)
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | BIGINT | PRIMARY KEY | 自增 | 收益记录ID |
| user_id | BIGINT | NOT NULL | - | 用户ID |
| supply_account_id | BIGINT | FK | - | 供应商账户ID |
| order_id | BIGINT | FK | - | 关联订单ID |
| usage_record_id | BIGINT | FK | - | 使用记录ID |
| earnings_type | VARCHAR(20) | NOT NULL | - | 类型: usage/bonus/refund |
| amount | NUMERIC(20,6) | NOT NULL | - | 收益金额 |
| currency | VARCHAR(10) | - | 'CNY' | 币种 |
| status | VARCHAR(20) | NOT NULL | 'pending' | 状态: pending/available/withdrawn/frozen |
| available_amount | NUMERIC(20,6) | - | 0 | 可用金额 |
| frozen_amount | NUMERIC(20,6) | - | 0 | 冻结金额 |
| withdrawn_amount | NUMERIC(20,6) | - | 0 | 已提现金额 |
| description | TEXT | - | - | 描述 |
| earned_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 收益时间 |
| available_at | TIMESTAMPTZ | - | - | 可用时间 |
| created_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 更新时间 |
**索引**:
- idx_supply_earnings_user_id (user_id)
- idx_supply_earnings_status (status)
- idx_supply_earnings_earned_at (earned_at)
---
## 6. supply_settlements (结算表)
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | BIGINT | PRIMARY KEY | 自增 | 结算单ID |
| settlement_no | VARCHAR(64) | NOT NULL, UNIQUE | - | 结算单号 |
| user_id | BIGINT | NOT NULL | - | 用户ID (供应商) |
| total_amount | NUMERIC(20,6) | NOT NULL | - | 总金额 |
| fee_amount | NUMERIC(20,6) | - | 0 | 手续费 |
| net_amount | NUMERIC(20,6) | NOT NULL | - | 净金额 |
| status | VARCHAR(20) | NOT NULL | 'pending' | 状态: pending/processing/completed/failed |
| payment_method | VARCHAR(20) | - | - | 支付方式 |
| payment_account | VARCHAR(100) | - | - | 支付账户 |
| payment_transaction_id | VARCHAR(100) | - | - | 支付流水号 |
| paid_at | TIMESTAMPTZ | - | - | 支付时间 |
| period_start | DATE | NOT NULL | - | 结算周期开始 |
| period_end | DATE | NOT NULL | - | 结算周期结束 |
| total_orders | INT | - | 0 | 关联订单数 |
| total_usage_records | INT | - | 0 | 关联使用记录数 |
| version | INT | - | 0 | 乐观锁版本号 |
| created_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 更新时间 |
**索引**:
- idx_supply_settlements_user_id (user_id)
- idx_supply_settlements_status (status)
- idx_supply_settlements_period (period_start, period_end)
---
## 7. supply_idempotency_records (幂等记录表)
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | BIGINT | PRIMARY KEY | 自增 | 记录ID |
| tenant_id | BIGINT | NOT NULL | - | 租户ID |
| operator_id | BIGINT | NOT NULL | - | 操作人ID |
| api_path | VARCHAR(200) | NOT NULL | - | API路径 |
| idempotency_key | VARCHAR(128) | NOT NULL | - | 幂等键 |
| request_id | VARCHAR(64) | NOT NULL | - | 请求ID |
| payload_hash | CHAR(64) | NOT NULL | - | 请求体SHA256摘要 |
| response_code | INT | - | - | 响应码 |
| response_body | JSONB | - | - | 响应体 |
| status | VARCHAR(20) | NOT NULL | 'processing' | 状态: processing/succeeded/failed |
| expires_at | TIMESTAMPTZ | NOT NULL | - | 过期时间 (默认24h,提现72h) |
| created_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMPTZ | - | CURRENT_TIMESTAMP | 更新时间 |
**索引**:
- UNIQUE (tenant_id, operator_id, api_path, idempotency_key)
- idx_idempotency_tenant_operator_path_key (tenant_id, operator_id, api_path, idempotency_key) WHERE expires_at > CURRENT_TIMESTAMP
- idx_idempotency_request_id (request_id)
- idx_idempotency_expires_at (expires_at) WHERE status != 'processing'
- idx_idempotency_status_expires (status, expires_at)
---
## 8. 枚举类型定义
### account_type (账户类型)
| 值 | 说明 |
|----|------|
| api_key | API Key认证 |
| oauth | OAuth认证 |
### account_status (账户状态)
| 值 | 说明 |
|----|------|
| pending | 待验证 |
| active | 激活 |
| suspended | 暂停 |
| disabled | 禁用 |
### risk_level (风险等级)
| 值 | 说明 |
|----|------|
| low | 低风险 |
| normal | 正常 |
| high | 高风险 |
### package_status (套餐状态)
| 值 | 说明 |
|----|------|
| draft | 草稿 |
| active | 生效中 |
| paused | 已暂停 |
| sold_out | 售罄 |
| expired | 已过期 |
### order_status (订单状态)
| 值 | 说明 |
|----|------|
| pending | 待支付 |
| paid | 已支付 |
| using | 使用中 |
| expired | 已过期 |
| refunded | 已退款 |
### settlement_status (结算状态)
| 值 | 说明 |
|----|------|
| pending | 待处理 |
| processing | 处理中 |
| completed | 已完成 |
| failed | 失败 |
### earnings_type (收益类型)
| 值 | 说明 |
|----|------|
| usage | 使用收益 |
| bonus | 奖励 |
| refund | 退款 |
### earnings_status (收益状态)
| 值 | 说明 |
|----|------|
| pending | 待确认 |
| available | 可提现 |
| withdrawn | 已提现 |
| frozen | 冻结中 |
---
## 9. 数据类型说明
| 类型 | 说明 |
|------|------|
| BIGINT | 64位整数主键和外部键使用 |
| VARCHAR(n) | 变长字符串最大n字符 |
| TEXT | 无长度限制的文本 |
| NUMERIC(p,s) | 精确数值p为总位数s为小数位 |
| BOOLEAN | 布尔值 |
| TIMESTAMPTZ | 带时区的时间戳 |
| DATE | 日期 |
| JSONB | JSON二进制格式支持索引 |
| CHAR(n) | 定长字符串 |
---
## 10. 字段命名规范
| 前缀/后缀 | 说明 | 示例 |
|-----------|------|------|
| _id | ID字段 | user_id, order_id |
| _at | 时间字段 | created_at, paid_at |
| _amount | 金额字段 | total_amount, net_amount |
| is_ | 布尔字段 | is_verified, is_frozen |
| total_ | 累计字段 | total_requests, total_cost |
| available_ | 可用字段 | available_quota, available_amount |
| encrypted_ | 加密字段 | encrypted_credentials |
---
> **维护记录**:
> - v1.0 (2026-04-07): 初始版本基于supply_schema_v1.sql

View File

@@ -0,0 +1,315 @@
# 数据库索引维护策略 v1.0
> **文档版本**: v1.0
> **创建日期**: 2026-04-07
> **问题**: P1-009 高频写入表的索引维护策略未定义
---
## 1. 概述
本文档定义高频写入表的索引维护策略,包括 `REINDEX``VACUUM` 自动化方案,确保数据库性能稳定。
### 1.1 高频写入表清单
| 表名 | 写入频率 | 日均增量 | 备注 |
|------|----------|----------|------|
| supply_usage_records | 极高 | ~1000万条 | 核心业务表 |
| supply_idempotency_records | 高 | ~100万条 | 幂等检查 |
| audit_events | 高 | ~500万条 | 审计日志 |
| billing_ledger_entries | 中 | ~10万条 | 账务明细 |
---
## 2. VACUUM 维护策略
### 2.1 自动 VACUUM 配置
PostgreSQL 默认启用 autovacuum但需要针对高频表进行调优
```sql
-- supply_usage_records 表配置
ALTER TABLE supply_usage_records SET (
autovacuum_vacuum_threshold = 50,
autovacuum_analyze_threshold = 50,
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_analyze_scale_factor = 0.01,
autovacuum_vacuum_cost_delay = 2,
autovacuum_vacuum_cost_limit = 200
);
-- supply_idempotency_records 表配置
ALTER TABLE supply_idempotency_records SET (
autovacuum_vacuum_threshold = 100,
autovacuum_analyze_threshold = 100,
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_analyze_scale_factor = 0.02
);
```
### 2.2 VACUUM 策略矩阵
| 表名 | autovacuum_enabled | vacuum_threshold | vacuum_scale_factor | 分析频率 |
|------|-------------------|------------------|---------------------|----------|
| supply_usage_records | true | 50 | 0.01 (1%) | 每1%变化 |
| supply_idempotency_records | true | 100 | 0.05 (5%) | 每2%变化 |
| supply_orders | true | 500 | 0.05 (5%) | 每周 |
| supply_packages | true | 1000 | 0.1 (10%) | 每月 |
### 2.3 手动 VACUUM 计划
**日常维护** (低峰期 02:00-04:00):
```bash
# vacuum analyze 高频表
vacuumdb -h localhost -U postgres -d supply_db \
--table 'supply_usage_records' \
--analyze \
--verbose
# 批量 vacuum 多个表
vacuumdb -h localhost -U postgres -d supply_db \
--all \
--analyze \
--verbose
```
**周维护** (周日 03:00-05:00):
```bash
# 全面 vacuum + analyze
vacuumdb -h localhost -U postgres -d supply_db \
--all \
--analyze \
--full \
--verbose
```
---
## 3. REINDEX 维护策略
### 3.1 REINDEX 触发条件
| 触发条件 | 说明 | 影响 |
|----------|------|------|
| 索引膨胀率 > 20% | B-tree 索引膨胀 | 性能下降 |
| 大量删除后 | DELETE > 30% 总行数 | 索引包含大量空页 |
| 长时间运行后 | 运行 > 30天 | 索引统计信息陈旧 |
| 硬件故障后 | 系统重启 | 确保索引一致性 |
### 3.2 索引膨胀检测
```sql
-- 检测索引膨胀率
SELECT
schemaname,
tablename,
indexname,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,
idx_scan,
idx_tup_read,
idx_tup_fetch,
ROUND(
(pg_relation_size(indexrelid)::numeric /
pg_relation_size(indrelid) * 100),
2
) AS index_ratio
FROM
pg_stat_user_indexes
WHERE
pg_relation_size(indexrelid) > 1024 * 1024 -- > 1MB
ORDER BY
pg_relation_size(indexrelid) DESC;
```
### 3.3 REINDEX 执行计划
**月维护** (每月第一个周日 04:00-06:00):
```bash
# 重建单个膨胀索引
reindexdb -h localhost -U postgres -d supply_db \
--index 'idx_supply_usage_records_request_id' \
--verbose
# 重建表的所有索引
reindexdb -h localhost -U postgres -d supply_db \
--table 'supply_usage_records' \
--verbose
# 全库索引重建 (慎用,会锁表)
reindexdb -h localhost -U postgres -d supply_db \
--all \
--verbose
```
### 3.4 联机型 REINDEX 方案
对于不可停机的关键表,使用 `REINDEX CONCURRENTLY`:
```bash
# 联机重建索引 (不锁表)
reindexdb -h localhost -U postgres -d supply_db \
--index 'idx_supply_usage_records_request_id' \
--concurrently \
--verbose
```
---
## 4. 自动化脚本
### 4.1 每日维护脚本 (daily_vacuum.sh)
```bash
#!/bin/bash
# daily_vacuum.sh - 每日索引维护
# 执行时间: 每日 02:00
set -e
DB_HOST="localhost"
DB_PORT="5432"
DB_NAME="supply_db"
DB_USER="postgres"
LOG_FILE="/var/log/postgresql/daily_vacuum_$(date +%Y%m%d).log"
echo "=== 开始每日 VACUUM 维护: $(date) ===" | tee -a "$LOG_FILE"
# 高频表优先 vacuum
TABLES=(
"supply_usage_records"
"supply_idempotency_records"
"supply_orders"
"supply_earnings"
)
for TABLE in "${TABLES[@]}"; do
echo "VACUUM $TABLE ..." | tee -a "$LOG_FILE"
vacuumdb -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
--table "$TABLE" \
--analyze \
--verbose 2>&1 | tee -a "$LOG_FILE"
done
echo "=== VACUUM 维护完成: $(date) ===" | tee -a "$LOG_FILE"
```
### 4.2 每周维护脚本 (weekly_reindex.sh)
```bash
#!/bin/bash
# weekly_reindex.sh - 每周 REINDEX 维护
# 执行时间: 每周日 03:00
set -e
DB_HOST="localhost"
DB_PORT="5432"
DB_NAME="supply_db"
DB_USER="postgres"
LOG_FILE="/var/log/postgresql/weekly_reindex_$(date +%Y%m%d).log"
echo "=== 开始每周 REINDEX 维护: $(date) ===" | tee -a "$LOG_FILE"
# 检查并重建膨胀索引
膨胀索引=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "
SELECT indexname FROM pg_stat_user_indexes
WHERE pg_relation_size(indexrelid) > 10 * 1024 * 1024
AND idx_scan = 0
AND schemaname = 'public';
")
for INDEX in $膨胀索引; do
echo "REINDEX INDEX $INDEX ..." | tee -a "$LOG_FILE"
reindexdb -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
--index "$INDEX" \
--concurrently \
--verbose 2>&1 | tee -a "$LOG_FILE"
done
echo "=== REINDEX 维护完成: $(date) ===" | tee -a "$LOG_FILE"
```
### 4.3 Cron 任务配置
```bash
# /etc/cron.d/postgresql_maintenance
# 每日凌晨2点执行 vacuum
0 2 * * * postgres /home/postgres/scripts/daily_vacuum.sh
# 每周日凌晨3点执行 reindex
0 3 * * 0 postgres /home/postgres/scripts/weekly_reindex.sh
```
---
## 5. 监控指标
### 5.1 关键监控指标
| 指标 | 告警阈值 | 说明 |
|------|----------|------|
| index膨胀率 | > 20% | 触发 REINDEX |
| dead_tuples | > 10000 | 触发 VACUUM |
| last_autovacuum | > 24h | 可能 autovacuum 异常 |
| idx_scan | = 0 | 索引未使用,考虑删除 |
### 5.2 监控查询
```sql
-- 检测需要维护的表
SELECT
schemaname,
relname AS table_name,
n_dead_tup,
n_live_tup,
last_autovacuum,
last_autoanalyze
FROM
pg_stat_user_tables
WHERE
n_dead_tup > 1000
ORDER BY
n_dead_tup DESC;
-- 检测未使用的索引
SELECT
schemaname,
tablename,
indexname,
idx_scan
FROM
pg_stat_user_indexes
WHERE
idx_scan = 0
AND NOT indexname LIKE '%_pkey'
ORDER BY
pg_relation_size(indexrelid) DESC;
```
---
## 6. 最佳实践
1. **避免在高峰期维护**: 维护操作安排在低峰期 (02:00-06:00)
2. **优先自动 vacuum**: 配置合理的 autovacuum 参数,减少手动干预
3. **监控索引膨胀**: 定期检测膨胀率,及时重建
4. **使用 CONCURRENTLY**: 关键表使用 `REINDEX CONCURRENTLY` 避免锁表
5. **保留维护日志**: 记录每次维护执行情况,便于分析问题
---
## 7. 恢复时间预估
| 操作 | 表大小 | 预计耗时 | 锁类型 |
|------|--------|----------|--------|
| VACUUM ANALYZE | 10GB | 5-10min | 轻量锁 |
| REINDEX | 1GB | 1-2min | 表锁* |
| REINDEX CONCURRENTLY | 1GB | 3-5min | 无锁 |
| VACUUM FULL | 10GB | 15-30min | 表锁 |
*使用 `REINDEX CONCURRENTLY` 可避免锁表
---
> **维护记录**:
> - v1.0 (2026-04-07): 初始版本