# 旧 RBAC 系统清理 - 完成总结

## 概览

本次清理工作完成了从旧的基于账号层级（`parent_id`）的数据权限模型到新的基于店铺层级、企业ID和客户ID的数据权限模型的迁移。

**完成时间**: 2026-01-10
**提案ID**: remove-legacy-rbac-cleanup
**依赖提案**:
- add-user-organization-model ✅
- add-role-permission-system ✅
- add-personal-customer-wechat ✅

---

## 核心变更

### 1. Account Store 清理

**文件**: `internal/store/postgres/account_store.go`

**移除的方法**:
- `GetSubordinateIDs(ctx, accountID)` - 基于 `parent_id` 的递归查询
- `ClearSubordinatesCache(ctx, accountID)` - 清除账号下级缓存
- `ClearSubordinatesCacheForParents(ctx, accountID)` - 递归清除上级缓存

**保留的方法**:
- `GetByShopID(ctx, shopID)` - 根据店铺 ID 查询账号列表
- `GetByEnterpriseID(ctx, enterpriseID)` - 根据企业 ID 查询账号列表

**移除的依赖**:
- 移除了 `time`、`constants`、`sonic` 包的导入（不再需要）

---

### 2. 数据权限过滤重构

**文件**: `pkg/gorm/callback.go`

**核心变更**:
从基于账号层级的过滤改为基于用户类型的多策略过滤。

#### 旧的过滤逻辑
```go
// 查询账号的所有下级ID
subordinateIDs := accountStore.GetSubordinateIDs(ctx, userID)
// 过滤: creator IN (下级账号ID)
tx.Where("creator IN ?", subordinateIDs)
```

#### 新的过滤逻辑

根据用户类型自动选择合适的过滤策略：

1. **超级管理员和平台用户**: 跳过过滤，查看所有数据
2. **代理用户**: 基于店铺层级过滤
   ```go
   subordinateShopIDs := shopStore.GetSubordinateShopIDs(ctx, shopID)
   tx.Where("shop_id IN ?", subordinateShopIDs)
   ```
3. **企业用户**: 基于企业ID过滤
   ```go
   tx.Where("enterprise_id = ?", enterpriseID)
   ```
4. **个人客户**: 基于客户ID或创建人过滤
   ```go
   tx.Where("customer_id = ?", customerID)
   // 或降级为
   tx.Where("creator = ?", userID)
   ```

**接口变更**:
```go
// 旧接口
type AccountStoreInterface interface {
    GetSubordinateIDs(ctx, accountID) ([]uint, error)
}
func RegisterDataPermissionCallback(db, accountStore)

// 新接口
type ShopStoreInterface interface {
    GetSubordinateShopIDs(ctx, shopID) ([]uint, error)
}
func RegisterDataPermissionCallback(db, shopStore)
```

---

### 3. 认证中间件增强

**文件**: `pkg/middleware/auth.go`

**新增字段支持**:

```go
// 旧的用户上下文
- userID
- userType
- shopID

// 新的用户上下文
type UserContextInfo struct {
    UserID       uint  // 用户ID
    UserType     int   // 用户类型
    ShopID       uint  // 店铺ID（代理用户）
    EnterpriseID uint  // 企业ID（企业用户）
    CustomerID   uint  // 客户ID（个人客户）
}
```

**API 变更**:

```go
// 旧API
func SetUserContext(ctx, userID, userType, shopID) context.Context
func SetUserToFiberContext(c, userID, userType, shopID)

// 新API
func SetUserContext(ctx, info *UserContextInfo) context.Context
func SetUserToFiberContext(c, info *UserContextInfo)

// 辅助函数（用于测试和兼容性）
func NewSimpleUserContext(userID, userType, shopID) *UserContextInfo
```

**新增辅助函数**:
```go
func GetEnterpriseIDFromContext(ctx) uint
func GetCustomerIDFromContext(ctx) uint
```

---

### 4. 常量清理

**文件**: `pkg/constants/constants.go` 和 `pkg/constants/redis.go`

**新增常量**:
```go
// Context 键
const (
    ContextKeyEnterpriseID = "enterprise_id"
    ContextKeyCustomerID   = "customer_id"
)

// 用户类型
const (
    UserTypePersonalCustomer = 5  // 个人客户（C端用户）
)
```

**移除常量**:
```go
// Redis 键生成函数（已废弃）
func RedisAccountSubordinatesKey(accountID) string
```

---

### 5. Bootstrap 初始化调整

**文件**: `internal/bootstrap/stores.go` 和 `internal/bootstrap/bootstrap.go`

**变更内容**:
```go
// 在 stores 结构体中添加 Shop
type stores struct {
    Account  *postgres.AccountStore
    Shop     *postgres.ShopStore  // 新增
    Role     *postgres.RoleStore
    // ...
}

// 初始化 Shop Store
Shop: postgres.NewShopStore(deps.DB, deps.Redis)

// 数据权限回调改为使用 ShopStore
pkgGorm.RegisterDataPermissionCallback(deps.DB, stores.Shop)
```

---

## 架构改进

### 数据权限过滤逻辑对比

| 维度 | 旧设计（基于账号层级） | 新设计（基于用户类型） |
|------|----------------------|---------------------|
| **核心依赖** | Account.parent_id | Shop.parent_id + Enterprise.id + Customer.id |
| **递归查询** | 账号的下级账号 | 店铺的下级店铺 |
| **适用范围** | 仅代理账号 | 代理、企业、个人客户 |
| **过滤字段** | creator | shop_id / enterprise_id / customer_id / creator |
| **可扩展性** | 低（单一策略） | 高（多策略，根据用户类型） |
| **缓存键** | account:subordinates:{id} | shop:subordinates:{id} |

### 新的数据权限模型优势

1. **更清晰的职责分离**: 账号不再承担组织结构的职责，组织结构完全由 Shop 和 Enterprise 维护
2. **更灵活的过滤策略**: 根据用户类型自动选择合适的过滤字段
3. **更好的扩展性**: 新增用户类型时只需添加对应的过滤逻辑
4. **更符合业务模型**: B端（代理/企业）和C端（个人客户）使用不同的过滤策略

---

## 破坏性变更

### API 签名变更

以下 API 的签名已变更，需要调用方更新：

#### 1. pkg/middleware 包

```go
// ❌ 旧API（已移除）
middleware.SetUserContext(ctx, userID, userType, shopID)
middleware.SetUserToFiberContext(c, userID, userType, shopID)

// ✅ 新API
info := &middleware.UserContextInfo{
    UserID:       userID,
    UserType:     userType,
    ShopID:       shopID,
    EnterpriseID: enterpriseID,
    CustomerID:   customerID,
}
middleware.SetUserContext(ctx, info)
middleware.SetUserToFiberContext(c, info)

// ✅ 兼容性辅助函数（仅用于基本场景）
info := middleware.NewSimpleUserContext(userID, userType, shopID)
middleware.SetUserContext(ctx, info)
```

#### 2. AuthConfig 配置

```go
// ❌ 旧API
type AuthConfig struct {
    TokenValidator func(token string) (userID uint, userType int, shopID uint, err error)
}

// ✅ 新API
type AuthConfig struct {
    TokenValidator func(token string) (*middleware.UserContextInfo, error)
}
```

#### 3. GORM Callback 注册

```go
// ❌ 旧API
gorm.RegisterDataPermissionCallback(db, accountStore)

// ✅ 新API
gorm.RegisterDataPermissionCallback(db, shopStore)
```

---

## 迁移指南

### 对于业务代码

如果你的代码直接调用了以下方法，需要迁移：

#### 1. AccountStore 方法调用

```go
// ❌ 旧代码
ids, err := accountStore.GetSubordinateIDs(ctx, userID)
accountStore.ClearSubordinatesCache(ctx, userID)

// ✅ 新代码
// 不再需要查询账号下级，数据权限过滤会自动处理
// 如果需要查询店铺下级：
shopIDs, err := shopStore.GetSubordinateShopIDs(ctx, shopID)
```

#### 2. 认证中间件配置

```go
// ❌ 旧代码
auth.Auth(auth.AuthConfig{
    TokenValidator: func(token string) (uint, int, uint, error) {
        // 解析 token
        return userID, userType, shopID, nil
    },
})

// ✅ 新代码
auth.Auth(auth.AuthConfig{
    TokenValidator: func(token string) (*middleware.UserContextInfo, error) {
        // 解析 token
        return &middleware.UserContextInfo{
            UserID:       userID,
            UserType:     userType,
            ShopID:       shopID,
            EnterpriseID: enterpriseID,
            CustomerID:   customerID,
        }, nil
    },
})
```

#### 3. 设置用户上下文

```go
// ❌ 旧代码
middleware.SetUserToFiberContext(c, userID, userType, shopID)

// ✅ 新代码
info := &middleware.UserContextInfo{
    UserID:   userID,
    UserType: userType,
    ShopID:   shopID,
}
middleware.SetUserToFiberContext(c, info)

// ✅ 或者使用辅助函数（仅适用于简单场景）
info := middleware.NewSimpleUserContext(userID, userType, shopID)
middleware.SetUserToFiberContext(c, info)
```

---

## 测试影响

### 需要更新的测试

由于 API 签名变更，以下测试需要更新：

1. **认证中间件测试** (`pkg/middleware/auth_test.go`)
2. **GORM Callback 测试** (`pkg/gorm/callback_test.go`)
3. **业务集成测试** (`tests/integration/admin/*_test.go`)
4. **权限过滤测试** (`tests/integration/admin/permission_platform_filter_test.go`)

### 测试迁移模式

```go
// ❌ 旧测试代码
ctx := middleware.SetUserContext(context.Background(), 1, constants.UserTypeAgent, 100)

// ✅ 新测试代码
ctx := middleware.SetUserContext(context.Background(), middleware.NewSimpleUserContext(1, constants.UserTypeAgent, 100))
```

---

## 遗留问题

### 1. 企业用户和个人客户的 Token 生成

**问题**: 当前 Token 验证器需要返回 `enterprise_id` 和 `customer_id`，但现有的 Token 生成逻辑可能还没有包含这些字段。

**影响**: 企业用户和个人客户的数据权限过滤可能无法正常工作。

**解决方案**:
1. 更新 JWT Token 的 Claims 结构，添加 `enterprise_id` 和 `customer_id` 字段
2. 在登录时根据用户类型生成包含对应字段的 Token

### 2. 测试兼容性

**问题**: 大量测试代码需要更新以适配新的 API 签名。

**影响**: 测试编译失败。

**解决方案**:
1. 批量更新测试代码，使用 `middleware.NewSimpleUserContext` 辅助函数
2. 为需要完整上下文的测试创建完整的 `UserContextInfo` 实例

---

## 验收清单

- [x] 0.1 确认 add-user-organization-model 提案已完成
- [x] 0.2 确认 add-role-permission-system 提案已完成
- [x] 0.3 确认 add-personal-customer-wechat 提案已完成
- [x] 1.1 移除 `GetSubordinateIDs` 方法
- [x] 1.2 移除相关的 Redis 缓存逻辑
- [x] 1.3 更新 `account_store.go` 中所有引用 `parent_id` 的代码
- [x] 1.4 添加新的查询方法：`GetByShopID`、`GetByEnterpriseID`
- [x] 2.1 创建/更新数据权限过滤逻辑
  - [x] 2.1.1 改为从 context 获取 shop_id（而非 user_id）
  - [x] 2.1.2 调用 `shop_store.GetSubordinateShopIDs` 获取下级店铺
  - [x] 2.1.3 生成 `WHERE shop_id IN (...)` 过滤条件
- [x] 2.2 更新 Store 层的 List 方法
- [x] 2.3 处理企业账号的过滤逻辑
- [x] 2.4 处理平台用户跳过过滤的逻辑
- [x] 3.1 更新认证中间件
  - [x] 3.1.1 在 context 中设置 enterprise_id 和 customer_id
  - [x] 3.1.2 根据用户类型设置数据权限过滤标记
- [x] 4.1 权限校验中间件（无需修改）
- [x] 5.1 移除旧的 Redis key 常量
- [x] 5.2 确保所有代码使用新定义的用户类型常量
- [x] 5.3 确保所有代码使用新定义的角色类型常量
- [x] 6.1 日志和埋点更新（无需额外修改，context 已包含完整信息）
- [ ] 7.x 测试更新（待完成）
- [x] 8.1 创建清理总结文档

---

## 性能影响

### 缓存使用

- **旧系统**: `account:subordinates:{account_id}`（缓存账号下级）
- **新系统**: `shop:subordinates:{shop_id}`（缓存店铺下级）

### 查询性能

- **代理用户**: 查询性能保持不变，从递归查询账号改为递归查询店铺
- **企业用户**: 查询性能提升，直接根据 `enterprise_id` 过滤
- **个人客户**: 查询性能提升，直接根据 `customer_id` 或 `creator` 过滤

---

## 后续工作

1. **完成测试修复**: 批量更新测试代码以适配新的 API 签名
2. **Token 生成逻辑更新**: 在 JWT Token 中添加 `enterprise_id` 和 `customer_id` 字段
3. **监控和日志**: 验证新的数据权限过滤逻辑是否正常工作
4. **性能测试**: 验证新的过滤逻辑性能是否符合预期

---

## 参考文档

- [提案文档](../../openspec/changes/archive/remove-legacy-rbac-cleanup/proposal.md)
- [用户组织模型](../add-user-organization-model/功能总结.md)
- [角色权限体系](../add-role-permission-system/功能总结.md)
