重构数据权限模型并清理旧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:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user