重构数据权限模型并清理旧RBAC代码

核心变更:
- 数据权限过滤从基于账号层级改为基于用户类型的多策略过滤
- 移除 AccountStore 中的 GetSubordinateIDs 等旧方法
- 重构认证中间件,支持 enterprise_id 和 customer_id
- 更新 GORM Callback,根据用户类型自动选择过滤策略(代理/企业/个人客户)
- 更新所有集成测试以适配新的 API 签名
- 添加功能总结文档和 OpenSpec 归档

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-10 15:08:11 +08:00
parent 9c6d4a3bd4
commit 743db126f7
26 changed files with 1292 additions and 322 deletions

View File

@@ -8,12 +8,23 @@ import (
"github.com/gofiber/fiber/v2"
)
// UserContextInfo 用户上下文信息
type UserContextInfo struct {
UserID uint
UserType int
ShopID uint
EnterpriseID uint
CustomerID uint
}
// SetUserContext 将用户信息设置到 context 中
// 在 Auth 中间件认证成功后调用
func SetUserContext(ctx context.Context, userID uint, userType int, shopID uint) context.Context {
ctx = context.WithValue(ctx, constants.ContextKeyUserID, userID)
ctx = context.WithValue(ctx, constants.ContextKeyUserType, userType)
ctx = context.WithValue(ctx, constants.ContextKeyShopID, shopID)
func SetUserContext(ctx context.Context, info *UserContextInfo) context.Context {
ctx = context.WithValue(ctx, constants.ContextKeyUserID, info.UserID)
ctx = context.WithValue(ctx, constants.ContextKeyUserType, info.UserType)
ctx = context.WithValue(ctx, constants.ContextKeyShopID, info.ShopID)
ctx = context.WithValue(ctx, constants.ContextKeyEnterpriseID, info.EnterpriseID)
ctx = context.WithValue(ctx, constants.ContextKeyCustomerID, info.CustomerID)
return ctx
}
@@ -53,6 +64,30 @@ func GetShopIDFromContext(ctx context.Context) uint {
return 0
}
// GetEnterpriseIDFromContext 从 context 中提取企业 ID
// 如果未设置,返回 0
func GetEnterpriseIDFromContext(ctx context.Context) uint {
if ctx == nil {
return 0
}
if enterpriseID, ok := ctx.Value(constants.ContextKeyEnterpriseID).(uint); ok {
return enterpriseID
}
return 0
}
// GetCustomerIDFromContext 从 context 中提取个人客户 ID
// 如果未设置,返回 0
func GetCustomerIDFromContext(ctx context.Context) uint {
if ctx == nil {
return 0
}
if customerID, ok := ctx.Value(constants.ContextKeyCustomerID).(uint); ok {
return customerID
}
return 0
}
// IsRootUser 检查当前用户是否为 root 用户
// root 用户跳过数据权限过滤
func IsRootUser(ctx context.Context) bool {
@@ -62,14 +97,16 @@ func IsRootUser(ctx context.Context) bool {
// SetUserToFiberContext 将用户信息设置到 Fiber context 的 Locals 中
// 同时也设置到标准 context 中,便于 GORM 查询使用
func SetUserToFiberContext(c *fiber.Ctx, userID uint, userType int, shopID uint) {
func SetUserToFiberContext(c *fiber.Ctx, info *UserContextInfo) {
// 设置到 Fiber Locals
c.Locals(constants.ContextKeyUserID, userID)
c.Locals(constants.ContextKeyUserType, userType)
c.Locals(constants.ContextKeyShopID, shopID)
c.Locals(constants.ContextKeyUserID, info.UserID)
c.Locals(constants.ContextKeyUserType, info.UserType)
c.Locals(constants.ContextKeyShopID, info.ShopID)
c.Locals(constants.ContextKeyEnterpriseID, info.EnterpriseID)
c.Locals(constants.ContextKeyCustomerID, info.CustomerID)
// 设置到标准 context用于 GORM 数据权限过滤)
ctx := SetUserContext(c.UserContext(), userID, userType, shopID)
ctx := SetUserContext(c.UserContext(), info)
c.SetUserContext(ctx)
}
@@ -80,9 +117,9 @@ type AuthConfig struct {
TokenExtractor func(c *fiber.Ctx) string
// TokenValidator token 验证函数
// 验证成功返回用户 ID、用户类型、店铺 ID
// 验证成功返回用户上下文信息
// 验证失败返回 error
TokenValidator func(token string) (userID uint, userType int, shopID uint, err error)
TokenValidator func(token string) (*UserContextInfo, error)
// SkipPaths 跳过认证的路径列表
SkipPaths []string
@@ -119,7 +156,7 @@ func Auth(config AuthConfig) fiber.Handler {
return errors.New(errors.CodeInternalError, "认证验证器未配置")
}
userID, userType, shopID, err := config.TokenValidator(token)
userInfo, err := config.TokenValidator(token)
if err != nil {
// 如果验证器返回的是 AppError直接返回
if appErr, ok := err.(*errors.AppError); ok {
@@ -130,7 +167,7 @@ func Auth(config AuthConfig) fiber.Handler {
}
// 将用户信息设置到 context
SetUserToFiberContext(c, userID, userType, shopID)
SetUserToFiberContext(c, userInfo)
return c.Next()
}
@@ -144,3 +181,16 @@ func extractBearerToken(c *fiber.Ctx) string {
}
return ""
}
// NewSimpleUserContext 创建简单的用户上下文信息(仅包含基本字段)
// 这是一个兼容性辅助函数,用于快速创建只包含 userID, userType, shopID 的上下文
// 适用于测试代码和不需要完整上下文信息的场景
func NewSimpleUserContext(userID uint, userType int, shopID uint) *UserContextInfo {
return &UserContextInfo{
UserID: userID,
UserType: userType,
ShopID: shopID,
EnterpriseID: 0,
CustomerID: 0,
}
}