- 新增店铺角色管理 API 和数据模型 - 实现角色继承和权限检查逻辑 - 添加流程测试框架和集成测试 - 更新权限服务和账号管理逻辑 - 添加数据库迁移脚本 - 归档 OpenSpec 变更文档 Ultraworked with Sisyphus
13 KiB
店铺级角色继承功能实现任务清单
1. 数据库层实现
-
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成功
- 创建
-
1.2 创建数据库迁移回滚文件
migrations/YYYYMMDDHHMMSS_add_shop_role_table.down.sql- 删除
tb_shop_role表 - 验证:执行
migrate -path migrations -database "..." down成功
- 删除
2. Model 层实现
-
2.1 创建
internal/model/shop_role.go- 定义
ShopRole结构体,包含所有字段和 GORM 标签 - 实现
TableName()方法返回"tb_shop_role" - 验证:运行
go build ./internal/model/,无编译错误
- 定义
-
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 层实现
-
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/,无编译错误
- 实现
-
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 层实现
-
4.1 创建
internal/service/account/role_resolver.go- 实现
GetRoleIDsForAccount(ctx, accountID) ([]uint, error)方法 - 实现角色解析逻辑:
- 超级管理员返回空数组
- 查询账号级角色,如有则返回
- 代理账号且无账号级角色,查询店铺级角色并返回
- 其他用户类型返回空数组
- 验证:运行
go build ./internal/service/account/,无编译错误
- 实现
-
4.2 编写
GetRoleIDsForAccount单元测试- 测试文件:
internal/service/account/role_resolver_test.go - 测试场景:超级管理员返回空数组
- 测试场景:平台用户返回账号级角色
- 测试场景:代理账号有账号级角色,返回账号级角色(不继承)
- 测试场景:代理账号无账号级角色,继承店铺级角色
- 测试场景:代理账号无账号级角色且店铺无角色,返回空数组
- 测试场景:企业账号返回账号级角色
- 验证:运行
source .env.local && go test -v ./internal/service/account/ -run TestGetRoleIDsForAccount,测试覆盖率 ≥ 90%
- 测试文件:
-
4.3 修改
internal/service/permission/service.go- 修改
Service结构体,添加accountService *account.Service字段 - 修改
New()构造函数,接收accountService参数 - 修改
CheckPermission()方法,调用accountService.GetRoleIDsForAccount()替代直接查询accountRoleStore - 验证:运行
go build ./internal/service/permission/,无编译错误
- 修改
-
4.4 更新
Permission Service单元测试- 修改
internal/service/permission/service_test.go - 更新 mock accountService 或使用真实 accountService
- 验证所有现有测试仍然通过
- 新增测试:代理账号继承店铺角色的权限检查场景
- 验证:运行
source .env.local && go test -v ./internal/service/permission/ -run TestCheckPermission,所有测试通过
- 修改
-
4.5 修改
internal/service/account/service.go- 修改
Service结构体,添加shopRoleStore *postgres.ShopRoleStore字段(用于角色解析) - 修改
New()构造函数,接收shopRoleStore参数 - 验证:运行
go build ./internal/service/account/,无编译错误
- 修改
-
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/,无编译错误
- 实现
-
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 层实现
-
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. 路由注册和依赖注入
-
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/,无编译错误
- 注册
-
6.2 修改
internal/bootstrap/stores.go- 在
Stores结构体添加ShopRole *postgres.ShopRoleStore字段 - 在
initStores()中初始化ShopRole: postgres.NewShopRoleStore(deps.DB, deps.Redis) - 验证:运行
go build ./internal/bootstrap/,无编译错误
- 在
-
6.3 修改
internal/bootstrap/services.go- 修改 Account Service 初始化,传入
stores.ShopRole - 修改 Permission Service 初始化,传入
Account Service实例 - 验证:运行
go build ./internal/bootstrap/,无编译错误
- 修改 Account Service 初始化,传入
-
6.4 修改
internal/bootstrap/handlers.go- 在
Handlers结构体添加ShopRole *admin.ShopRoleHandler字段 - 在
initHandlers()中初始化ShopRole: admin.NewShopRoleHandler(services.Shop) - 验证:运行
go build ./internal/bootstrap/,无编译错误
- 在
-
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. 常量定义
-
7.1 检查是否需要新增错误码
- 检查
pkg/errors/codes.go是否已有所需错误码 - 如需新增,添加错误码常量和错误消息
- 验证:运行
go build ./pkg/errors/,无编译错误
- 检查
-
7.2 检查是否需要新增 Redis Key 生成函数
- 检查
pkg/constants/redis.go是否需要新增店铺角色相关的 Redis Key - 当前使用
RedisUserPermissionsKey(userID)已满足需求,无需新增 - 验证:确认缓存清理逻辑使用正确的 Key
- 检查
8. 端到端测试
-
8.1 测试完整的店铺角色继承流程
- 创建测试店铺和代理账号(无账号级角色)
- 为店铺分配角色
- 验证账号权限检查返回 true(继承店铺角色)
- 为账号分配账号级角色
- 验证账号权限检查使用账号级角色(不继承店铺角色)
- 删除账号级角色
- 验证账号权限检查恢复继承店铺角色
- 验证:手动测试或编写端到端测试脚本
-
8.2 测试缓存失效机制
- 为店铺分配角色,账号继承
- 触发一次权限检查(缓存写入)
- 修改店铺角色
- 再次触发权限检查,验证使用新角色(缓存已失效)
- 验证:手动测试或编写测试脚本
-
8.3 测试权限控制
- 使用平台用户操作任意店铺角色(应成功)
- 使用代理用户操作自己店铺角色(应成功)
- 使用代理用户操作下级店铺角色(应成功)
- 使用代理用户操作无关店铺角色(应失败 403)
- 使用企业用户操作店铺角色(应失败 403)
- 验证:手动测试或编写测试脚本
9. 代码质量和文档
-
9.1 运行 LSP 诊断检查所有修改的文件
- 运行
lsp_diagnostics检查所有新增和修改的 Go 文件 - 确保无错误、无警告
- 验证:所有文件通过 LSP 检查
- 运行
-
9.2 运行代码规范检查
- 运行
gofmt -w .格式化所有 Go 文件 - 运行
go vet ./...检查潜在问题 - 验证:无错误输出
- 运行
-
9.3 运行所有单元测试
- 运行
source .env.local && go test -v ./... - 确保所有测试通过,包括现有测试和新增测试
- 验证:测试通过率 100%,核心逻辑测试覆盖率 ≥ 90%
- 运行
-
9.4 运行所有集成测试
- 运行
source .env.local && go test -v ./tests/integration/ - 确保所有 API 测试通过
- 验证:测试通过率 100%
- 运行
-
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 接口正常工作
- 验证:功能完整、稳定、性能达标