# Change: Service 层错误语义统一 - 核心业务模块

## Why

完成核心业务模块的错误语义统一，确保订单、套餐、分佣等关键流程的错误处理一致性，避免业务错误被错误归类为 500 导致的用户体验问题。

**当前问题**：
- 核心业务模块（订单、套餐、分佣、店铺、企业）使用 `fmt.Errorf` 返回业务错误
- 全局 ErrorHandler 将这些错误归类为 500（Internal Server Error）
- 客户端无法区分业务错误（如状态不允许）和系统错误（如数据库故障）
- 错误消息缺少结构化错误码，难以做错误分类处理

**影响范围**：
- `package/service.go` (14 处)
- `package_series/service.go` (9 处)
- `commission_withdrawal/service.go` (7 处)
- `commission_stats/service.go` (3 处)
- `my_commission/service.go` (9 处)
- `shop/service.go` (8 处)
- `enterprise/service.go` (7 处)
- `shop_account/service.go` (11 处)
- `customer_account/service.go` (6 处)

**总计**：9 个文件，约 70-74 处 `fmt.Errorf` 待替换

## What Changes

### 错误处理统一规则

#### 1. 业务校验错误（4xx）

使用 `errors.New(Code4xx, msg)`：

```go
// ❌ 当前
if order.Status == StatusCanceled {
    return fmt.Errorf("订单已取消，无法修改")
}

// ✅ 修复后
if order.Status == StatusCanceled {
    return errors.New(errors.CodeOrderCanceled, "订单已取消，无法修改")
}
```

**适用场景**：
- 资源不存在（CodeNotFound）
- 状态不允许（CodeInvalidStatus）
- 参数错误（CodeInvalidParam）
- 权限不足（CodeForbidden）
- 重复操作（CodeDuplicate）

#### 2. 系统依赖错误（5xx）

使用 `errors.Wrap(Code5xx, err, msg)`：

```go
// ❌ 当前
if err := s.store.Order.Create(ctx, order); err != nil {
    return fmt.Errorf("创建订单失败: %w", err)
}

// ✅ 修复后
if err := s.store.Order.Create(ctx, order); err != nil {
    return errors.Wrap(errors.CodeInternalError, err, "创建订单失败")
}
```

**适用场景**：
- 数据库操作失败
- Redis 操作失败
- 队列提交失败
- 外部服务调用失败

### 修改清单

#### 订单与套餐管理
- [ ] `package/service.go` (14 处)
  - 套餐不存在 → `CodeNotFound`
  - 套餐状态不允许 → `CodeInvalidStatus`
  - 数据库错误 → `Wrap(CodeInternalError, err)`

- [ ] `package_series/service.go` (9 处)
  - 套餐系列不存在 → `CodeNotFound`
  - 套餐系列已存在 → `CodeDuplicate`
  - 数据库错误 → `Wrap(CodeInternalError, err)`

#### 分佣系统
- [ ] `commission_withdrawal/service.go` (7 处)
  - 余额不足 → `CodeInsufficientBalance`
  - 提现状态不允许 → `CodeInvalidStatus`
  - 数据库/队列错误 → `Wrap(CodeInternalError, err)`

- [ ] `commission_stats/service.go` (3 处)
  - 统计数据计算失败 → `Wrap(CodeInternalError, err)`

- [ ] `my_commission/service.go` (9 处)
  - 分佣记录不存在 → `CodeNotFound`
  - 数据库错误 → `Wrap(CodeInternalError, err)`

#### 店铺与企业
- [ ] `shop/service.go` (8 处)
  - 店铺不存在 → `CodeNotFound`
  - 店铺代码重复 → `CodeDuplicate`
  - 层级超过限制 → `CodeInvalidParam`
  - 数据库错误 → `Wrap(CodeInternalError, err)`

- [ ] `enterprise/service.go` (7 处)
  - 企业不存在 → `CodeNotFound`
  - 企业代码重复 → `CodeDuplicate`
  - 数据库错误 → `Wrap(CodeInternalError, err)`

- [ ] `shop_account/service.go` (11 处)
  - 账号不存在 → `CodeNotFound`
  - 用户名重复 → `CodeDuplicate`
  - 密码错误 → `CodeInvalidPassword`
  - 数据库错误 → `Wrap(CodeInternalError, err)`

- [ ] `customer_account/service.go` (6 处)
  - 客户不存在 → `CodeNotFound`
  - 数据库错误 → `Wrap(CodeInternalError, err)`

## Decisions

### 错误码映射

| 场景 | 错误码 | HTTP 状态码 |
|-----|-------|-----------|
| 资源不存在 | `CodeNotFound` | 404 |
| 状态不允许 | `CodeInvalidStatus` | 400 |
| 参数错误 | `CodeInvalidParam` | 400 |
| 重复操作 | `CodeDuplicate` | 409 |
| 余额不足 | `CodeInsufficientBalance` | 400 |
| 数据库错误 | `CodeInternalError` | 500 |
| 队列错误 | `CodeInternalError` | 500 |

### 执行策略

1. **按模块分批**：每完成 2-3 个文件运行相关测试
2. **错误码优先**：优先使用已有错误码，确需新增时添加到 `pkg/errors/codes.go`
3. **保持向后兼容**：错误消息保持中文描述，便于日志排查
4. **补充测试**：为每个模块补充错误场景单元测试

## Impact

### Breaking Changes

- 部分接口错误码从 500 调整为 4xx（如订单状态不允许、套餐不存在等）
- 客户端需要处理新的错误码分类

### Testing Requirements

每个模块补充以下测试：

```go
func TestService_ErrorHandling(t *testing.T) {
    t.Run("资源不存在返回 404", func(t *testing.T) {
        err := service.GetPackage(ctx, 99999)
        assert.Error(t, err)
        assert.Equal(t, errors.CodeNotFound, errors.GetCode(err))
    })
    
    t.Run("状态不允许返回 400", func(t *testing.T) {
        err := service.CancelOrder(ctx, canceledOrderID)
        assert.Error(t, err)
        assert.Equal(t, errors.CodeInvalidStatus, errors.GetCode(err))
    })
    
    t.Run("数据库错误返回 500", func(t *testing.T) {
        // Mock 数据库故障
        err := service.CreatePackage(ctx, pkg)
        assert.Error(t, err)
        assert.Equal(t, errors.CodeInternalError, errors.GetCode(err))
    })
}
```

### Documentation Updates

- 更新 API 文档中的错误码说明
- 补充 `docs/003-error-handling/使用指南.md` 中的实际案例
- 在 Code Review 时参考错误处理规范

## Affected Specs

- **UPDATE**: `openspec/specs/error-handling/spec.md`
  - 补充 Service 层错误处理规范
  - 添加错误码映射表

## Verification Checklist

### 编译检查
```bash
go build -o /tmp/test_api ./cmd/api
go build -o /tmp/test_worker ./cmd/worker
```

### 单元测试
```bash
source .env.local && go test -v ./internal/service/package/...
source .env.local && go test -v ./internal/service/shop/...
source .env.local && go test -v ./internal/service/commission_withdrawal/...
```

### 错误码验证

手动测试关键接口，确认：
- ✅ 套餐不存在返回 404
- ✅ 订单状态不允许返回 400
- ✅ 余额不足返回 400
- ✅ 数据库错误返回 500
- ✅ 错误消息保持中文描述

### 日志验证

检查 `logs/app.log` 确认：
- 业务错误（4xx）记录为 `WARN` 级别
- 系统错误（5xx）记录为 `ERROR` 级别
- 错误日志包含完整的堆栈信息（仅 5xx）

## Estimated Effort

- **修改时间**：2-3 小时
- **测试时间**：1 小时
- **文档更新**：0.5 小时

**总计**：约 3.5-4.5 小时
