# 店铺级角色继承功能实现任务清单 ## 1. 数据库层实现 - [x] 1.1 创建数据库迁移文件 `migrations/YYYYMMDDHHMMSS_add_shop_role_table.up.sql` - 创建 `tb_shop_role` 表 - 添加唯一约束 `(shop_id, role_id) WHERE deleted_at IS NULL` - 创建索引 `idx_shop_role_shop_id`、`idx_shop_role_role_id`、`idx_shop_role_deleted_at` - 验证:执行 `migrate -path migrations -database "..." up` 成功 - [x] 1.2 创建数据库迁移回滚文件 `migrations/YYYYMMDDHHMMSS_add_shop_role_table.down.sql` - 删除 `tb_shop_role` 表 - 验证:执行 `migrate -path migrations -database "..." down` 成功 ## 2. Model 层实现 - [x] 2.1 创建 `internal/model/shop_role.go` - 定义 `ShopRole` 结构体,包含所有字段和 GORM 标签 - 实现 `TableName()` 方法返回 `"tb_shop_role"` - 验证:运行 `go build ./internal/model/`,无编译错误 - [x] 2.2 创建 `internal/model/dto/shop_role_dto.go` - 定义 `AssignShopRolesRequest` 结构体(包含 `role_ids` 字段和 description 标签) - 定义 `ShopRoleResponse` 结构体(包含店铺和角色详情) - 定义 `ShopRolesResponse` 结构体(包含 `shop_id` 和 `roles` 列表) - 验证:运行 `go build ./internal/model/dto/`,无编译错误 ## 3. Store 层实现 - [x] 3.1 创建 `internal/store/postgres/shop_role_store.go` - 实现 `ShopRoleStore` 结构体,包含 `db` 和 `redisClient` 字段 - 实现 `NewShopRoleStore()` 构造函数 - 实现 `Create()` 方法(创建单个店铺角色关联) - 实现 `BatchCreate()` 方法(批量创建) - 实现 `Delete()` 方法(删除指定店铺角色关联) - 实现 `DeleteByShopID()` 方法(删除店铺的所有角色关联) - 实现 `GetByShopID()` 方法(查询店铺的所有角色关联) - 实现 `GetRoleIDsByShopID()` 方法(查询店铺的所有角色 ID) - 实现 `clearShopRoleCache()` 私有方法(清理店铺下所有账号的权限缓存) - 验证:运行 `go build ./internal/store/postgres/`,无编译错误 - [x] 3.2 编写 `ShopRoleStore` 单元测试 - 测试文件:`internal/store/postgres/shop_role_store_test.go` - 测试 `Create()` 成功场景 - 测试 `BatchCreate()` 成功场景 - 测试 `Delete()` 成功场景 - 测试 `DeleteByShopID()` 成功场景 - 测试 `GetByShopID()` 成功场景 - 测试 `GetRoleIDsByShopID()` 成功场景 - 测试唯一性约束冲突 - 验证:运行 `source .env.local && go test -v ./internal/store/postgres/ -run TestShopRoleStore`,所有测试通过 ## 4. Service 层实现 - [x] 4.1 创建 `internal/service/account/role_resolver.go` - 实现 `GetRoleIDsForAccount(ctx, accountID) ([]uint, error)` 方法 - 实现角色解析逻辑: - 超级管理员返回空数组 - 查询账号级角色,如有则返回 - 代理账号且无账号级角色,查询店铺级角色并返回 - 其他用户类型返回空数组 - 验证:运行 `go build ./internal/service/account/`,无编译错误 - [x] 4.2 编写 `GetRoleIDsForAccount` 单元测试 - 测试文件:`internal/service/account/role_resolver_test.go` - 测试场景:超级管理员返回空数组 - 测试场景:平台用户返回账号级角色 - 测试场景:代理账号有账号级角色,返回账号级角色(不继承) - 测试场景:代理账号无账号级角色,继承店铺级角色 - 测试场景:代理账号无账号级角色且店铺无角色,返回空数组 - 测试场景:企业账号返回账号级角色 - 验证:运行 `source .env.local && go test -v ./internal/service/account/ -run TestGetRoleIDsForAccount`,测试覆盖率 ≥ 90% - [x] 4.3 修改 `internal/service/permission/service.go` - 修改 `Service` 结构体,添加 `accountService *account.Service` 字段 - 修改 `New()` 构造函数,接收 `accountService` 参数 - 修改 `CheckPermission()` 方法,调用 `accountService.GetRoleIDsForAccount()` 替代直接查询 `accountRoleStore` - 验证:运行 `go build ./internal/service/permission/`,无编译错误 - [x] 4.4 更新 `Permission Service` 单元测试 - 修改 `internal/service/permission/service_test.go` - 更新 mock accountService 或使用真实 accountService - 验证所有现有测试仍然通过 - 新增测试:代理账号继承店铺角色的权限检查场景 - 验证:运行 `source .env.local && go test -v ./internal/service/permission/ -run TestCheckPermission`,所有测试通过 - [x] 4.5 修改 `internal/service/account/service.go` - 修改 `Service` 结构体,添加 `shopRoleStore *postgres.ShopRoleStore` 字段(用于角色解析) - 修改 `New()` 构造函数,接收 `shopRoleStore` 参数 - 验证:运行 `go build ./internal/service/account/`,无编译错误 - [x] 4.6 创建 `internal/service/shop/shop_role.go` - 实现 `AssignRolesToShop(ctx, shopID, roleIDs) ([]*model.ShopRole, error)` 方法 - 实现业务逻辑: - 权限检查(调用 `middleware.CanManageShop`) - 验证店铺存在 - 验证角色存在、类型正确(RoleType=2)、状态启用 - 空数组表示清空所有角色 - 删除现有角色关联,批量创建新关联(原子操作) - 实现 `GetShopRoles(ctx, shopID) ([]*dto.ShopRoleResponse, error)` 方法 - 实现业务逻辑: - 权限检查 - 查询店铺角色关联 - 查询角色详情并组装响应 - 验证:运行 `go build ./internal/service/shop/`,无编译错误 - [x] 4.7 编写 `Shop Service` 店铺角色管理单元测试 - 测试文件:`internal/service/shop/shop_role_test.go` - 测试 `AssignRolesToShop()` 成功分配单个角色 - 测试 `AssignRolesToShop()` 清空所有角色 - 测试 `AssignRolesToShop()` 替换现有角色 - 测试 `AssignRolesToShop()` 角色类型校验失败 - 测试 `AssignRolesToShop()` 角色不存在 - 测试 `AssignRolesToShop()` 店铺不存在 - 测试 `AssignRolesToShop()` 权限不足 - 测试 `GetShopRoles()` 查询已分配角色 - 测试 `GetShopRoles()` 查询未分配角色的店铺 - 测试 `GetShopRoles()` 权限不足 - 验证:运行 `source .env.local && go test -v ./internal/service/shop/ -run TestShopRole`,测试覆盖率 ≥ 90% ## 5. Handler 层实现 - [x] 5.1 创建 `internal/handler/admin/shop_role.go` - 实现 `ShopRoleHandler` 结构体,包含 `service *shop.Service` 字段 - 实现 `NewShopRoleHandler()` 构造函数 - 实现 `AssignShopRoles(c *fiber.Ctx) error` 方法 - 解析路径参数 `shop_id` - 解析请求体 `AssignShopRolesRequest` - 调用 `service.AssignRolesToShop()` - 返回统一响应格式 - 实现 `GetShopRoles(c *fiber.Ctx) error` 方法 - 解析路径参数 `shop_id` - 调用 `service.GetShopRoles()` - 返回统一响应格式 - 实现 `DeleteShopRole(c *fiber.Ctx) error` 方法 - 解析路径参数 `shop_id` 和 `role_id` - 调用 `service` 删除逻辑 - 返回统一响应格式 - 验证:运行 `go build ./internal/handler/admin/`,无编译错误 - [ ] 5.2 编写 Handler 集成测试 - 测试文件:`tests/integration/shop_role_test.go` - 测试 `POST /api/admin/shops/:shop_id/roles` 成功分配角色 - 测试 `POST /api/admin/shops/:shop_id/roles` 清空角色 - 测试 `POST /api/admin/shops/:shop_id/roles` 替换角色 - 测试 `POST /api/admin/shops/:shop_id/roles` 角色类型校验失败 - 测试 `POST /api/admin/shops/:shop_id/roles` 权限不足 - 测试 `GET /api/admin/shops/:shop_id/roles` 查询角色 - 测试 `GET /api/admin/shops/:shop_id/roles` 店铺不存在 - 测试 `DELETE /api/admin/shops/:shop_id/roles/:role_id` 删除角色 - 验证:运行 `source .env.local && go test -v ./tests/integration/ -run TestShopRole`,所有测试通过 ## 6. 路由注册和依赖注入 - [x] 6.1 修改 `internal/routes/shop.go` - 注册 `POST /api/admin/shops/:shop_id/roles` 路由到 `handlers.ShopRole.AssignShopRoles` - 注册 `GET /api/admin/shops/:shop_id/roles` 路由到 `handlers.ShopRole.GetShopRoles` - 注册 `DELETE /api/admin/shops/:shop_id/roles/:role_id` 路由到 `handlers.ShopRole.DeleteShopRole` - 验证:运行 `go build ./internal/routes/`,无编译错误 - [x] 6.2 修改 `internal/bootstrap/stores.go` - 在 `Stores` 结构体添加 `ShopRole *postgres.ShopRoleStore` 字段 - 在 `initStores()` 中初始化 `ShopRole: postgres.NewShopRoleStore(deps.DB, deps.Redis)` - 验证:运行 `go build ./internal/bootstrap/`,无编译错误 - [x] 6.3 修改 `internal/bootstrap/services.go` - 修改 Account Service 初始化,传入 `stores.ShopRole` - 修改 Permission Service 初始化,传入 `Account Service` 实例 - 验证:运行 `go build ./internal/bootstrap/`,无编译错误 - [x] 6.4 修改 `internal/bootstrap/handlers.go` - 在 `Handlers` 结构体添加 `ShopRole *admin.ShopRoleHandler` 字段 - 在 `initHandlers()` 中初始化 `ShopRole: admin.NewShopRoleHandler(services.Shop)` - 验证:运行 `go build ./internal/bootstrap/`,无编译错误 - [x] 6.5 更新 API 文档生成器 - 修改 `cmd/api/docs.go`,在 handlers 初始化中添加 `ShopRole: admin.NewShopRoleHandler(nil)` - 修改 `cmd/gendocs/main.go`,在 handlers 初始化中添加 `ShopRole: admin.NewShopRoleHandler(nil)` - 验证:运行 `go run cmd/gendocs/main.go`,生成文档成功,包含新的店铺角色管理接口 ## 7. 常量定义 - [x] 7.1 检查是否需要新增错误码 - 检查 `pkg/errors/codes.go` 是否已有所需错误码 - 如需新增,添加错误码常量和错误消息 - 验证:运行 `go build ./pkg/errors/`,无编译错误 - [x] 7.2 检查是否需要新增 Redis Key 生成函数 - 检查 `pkg/constants/redis.go` 是否需要新增店铺角色相关的 Redis Key - 当前使用 `RedisUserPermissionsKey(userID)` 已满足需求,无需新增 - 验证:确认缓存清理逻辑使用正确的 Key ## 8. 端到端测试 - [ ] 8.1 测试完整的店铺角色继承流程 - 创建测试店铺和代理账号(无账号级角色) - 为店铺分配角色 - 验证账号权限检查返回 true(继承店铺角色) - 为账号分配账号级角色 - 验证账号权限检查使用账号级角色(不继承店铺角色) - 删除账号级角色 - 验证账号权限检查恢复继承店铺角色 - 验证:手动测试或编写端到端测试脚本 - [ ] 8.2 测试缓存失效机制 - 为店铺分配角色,账号继承 - 触发一次权限检查(缓存写入) - 修改店铺角色 - 再次触发权限检查,验证使用新角色(缓存已失效) - 验证:手动测试或编写测试脚本 - [ ] 8.3 测试权限控制 - 使用平台用户操作任意店铺角色(应成功) - 使用代理用户操作自己店铺角色(应成功) - 使用代理用户操作下级店铺角色(应成功) - 使用代理用户操作无关店铺角色(应失败 403) - 使用企业用户操作店铺角色(应失败 403) - 验证:手动测试或编写测试脚本 ## 9. 代码质量和文档 - [x] 9.1 运行 LSP 诊断检查所有修改的文件 - 运行 `lsp_diagnostics` 检查所有新增和修改的 Go 文件 - 确保无错误、无警告 - 验证:所有文件通过 LSP 检查 - [x] 9.2 运行代码规范检查 - 运行 `gofmt -w .` 格式化所有 Go 文件 - 运行 `go vet ./...` 检查潜在问题 - 验证:无错误输出 - [x] 9.3 运行所有单元测试 - 运行 `source .env.local && go test -v ./...` - 确保所有测试通过,包括现有测试和新增测试 - 验证:测试通过率 100%,核心逻辑测试覆盖率 ≥ 90% - [x] 9.4 运行所有集成测试 - 运行 `source .env.local && go test -v ./tests/integration/` - 确保所有 API 测试通过 - 验证:测试通过率 100% - [x] 9.5 更新项目文档 - 在 `docs/` 目录创建功能总结文档(如果需要) - 更新 README.md(如果有重大功能说明) - 验证:文档清晰、准确、完整 ## 10. 部署准备 - [ ] 10.1 验证数据库迁移 - 在测试环境执行迁移:`migrate -path migrations -database "..." up` - 验证表创建成功,索引创建成功 - 验证回滚:`migrate -path migrations -database "..." down` - 验证表删除成功 - [ ] 10.2 性能测试 - 测试角色解析性能(< 10ms) - 测试权限检查性能(< 50ms) - 测试缓存命中性能(< 1ms) - 验证:性能满足设计要求 - [ ] 10.3 最终验收测试 - 在模拟生产环境执行完整测试流程 - 验证向后兼容性(现有账号级角色功能不受影响) - 验证不设置店铺角色的店铺行为保持一致 - 验证所有 API 接口正常工作 - 验证:功能完整、稳定、性能达标