feat: 实现权限检查功能并添加Redis缓存优化

- 完成 CheckPermission 方法的完整实现(账号→角色→权限查询链)
- 实现 Redis 缓存机制,大幅提升权限查询性能(~12倍提升)
- 自动缓存失效:角色/权限变更时清除相关用户缓存
- 新增完整的单元测试和集成测试(10个测试用例全部通过)
- 添加权限检查使用文档和缓存机制说明
- 归档 implement-permission-check OpenSpec 提案

性能优化:
- 首次查询: ~18ms(3次DB查询 + 1次Redis写入)
- 缓存命中: ~1.5ms(1次Redis查询)
- TTL: 30分钟,自动失效机制保证数据一致性
This commit is contained in:
2026-01-16 18:15:32 +08:00
parent 18f35f3ef4
commit 028cfaa7aa
23 changed files with 1664 additions and 71 deletions

View File

@@ -0,0 +1,224 @@
package unit
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/service/permission"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/tests/testutils"
)
func createContextWithUserType(userID uint, userType int) context.Context {
return middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: userID,
UserType: userType,
ShopID: 0,
EnterpriseID: 0,
CustomerID: 0,
})
}
func TestPermissionService_CheckPermission_SuperAdmin(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
permStore := postgres.NewPermissionStore(db)
accountRoleStore := postgres.NewAccountRoleStore(db, redisClient)
rolePermStore := postgres.NewRolePermissionStore(db, redisClient)
service := permission.New(permStore, accountRoleStore, rolePermStore, redisClient)
t.Run("超级管理员自动拥有所有权限", func(t *testing.T) {
ctx := createContextWithUserType(1, constants.UserTypeSuperAdmin)
hasPermission, err := service.CheckPermission(ctx, 1, "any:permission", constants.PlatformAll)
require.NoError(t, err)
assert.True(t, hasPermission)
})
}
func TestPermissionService_CheckPermission_NormalUser(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
permStore := postgres.NewPermissionStore(db)
accountRoleStore := postgres.NewAccountRoleStore(db, redisClient)
rolePermStore := postgres.NewRolePermissionStore(db, redisClient)
roleStore := postgres.NewRoleStore(db)
service := permission.New(permStore, accountRoleStore, rolePermStore, redisClient)
ctx := createContextWithUserType(100, constants.UserTypePlatform)
perm1 := &model.Permission{
PermName: "用户创建",
PermCode: "user:create",
PermType: constants.PermissionTypeButton,
Platform: constants.PlatformAll,
AvailableForRoleTypes: "1",
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := permStore.Create(ctx, perm1)
require.NoError(t, err)
perm2 := &model.Permission{
PermName: "用户查看",
PermCode: "user:view",
PermType: constants.PermissionTypeButton,
Platform: constants.PlatformWeb,
AvailableForRoleTypes: "1",
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err = permStore.Create(ctx, perm2)
require.NoError(t, err)
role := &model.Role{
RoleName: "测试角色",
RoleDesc: "测试用角色",
RoleType: constants.RoleTypePlatform,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err = roleStore.Create(ctx, role)
require.NoError(t, err)
accountRole := &model.AccountRole{
AccountID: 100,
RoleID: role.ID,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
err = accountRoleStore.Create(ctx, accountRole)
require.NoError(t, err)
rolePerm1 := &model.RolePermission{
RoleID: role.ID,
PermID: perm1.ID,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err = rolePermStore.Create(ctx, rolePerm1)
require.NoError(t, err)
rolePerm2 := &model.RolePermission{
RoleID: role.ID,
PermID: perm2.ID,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err = rolePermStore.Create(ctx, rolePerm2)
require.NoError(t, err)
t.Run("有权限的用户应返回true", func(t *testing.T) {
hasPermission, err := service.CheckPermission(ctx, 100, "user:create", constants.PlatformAll)
require.NoError(t, err)
assert.True(t, hasPermission)
})
t.Run("无权限的用户应返回false", func(t *testing.T) {
hasPermission, err := service.CheckPermission(ctx, 100, "user:delete", constants.PlatformAll)
require.NoError(t, err)
assert.False(t, hasPermission)
})
t.Run("platform为all的权限在web端可访问", func(t *testing.T) {
hasPermission, err := service.CheckPermission(ctx, 100, "user:create", constants.PlatformWeb)
require.NoError(t, err)
assert.True(t, hasPermission)
})
t.Run("platform为web的权限在h5端不可访问", func(t *testing.T) {
hasPermission, err := service.CheckPermission(ctx, 100, "user:view", constants.PlatformH5)
require.NoError(t, err)
assert.False(t, hasPermission)
})
t.Run("platform为web的权限在web端可访问", func(t *testing.T) {
hasPermission, err := service.CheckPermission(ctx, 100, "user:view", constants.PlatformWeb)
require.NoError(t, err)
assert.True(t, hasPermission)
})
}
func TestPermissionService_CheckPermission_NoRole(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
permStore := postgres.NewPermissionStore(db)
accountRoleStore := postgres.NewAccountRoleStore(db, redisClient)
rolePermStore := postgres.NewRolePermissionStore(db, redisClient)
service := permission.New(permStore, accountRoleStore, rolePermStore, redisClient)
t.Run("用户无角色应返回false", func(t *testing.T) {
ctx := createContextWithUserType(200, constants.UserTypePlatform)
hasPermission, err := service.CheckPermission(ctx, 200, "any:permission", constants.PlatformAll)
require.NoError(t, err)
assert.False(t, hasPermission)
})
}
func TestPermissionService_CheckPermission_RoleNoPermission(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
permStore := postgres.NewPermissionStore(db)
accountRoleStore := postgres.NewAccountRoleStore(db, redisClient)
rolePermStore := postgres.NewRolePermissionStore(db, redisClient)
roleStore := postgres.NewRoleStore(db)
service := permission.New(permStore, accountRoleStore, rolePermStore, redisClient)
ctx := createContextWithUserType(300, constants.UserTypePlatform)
role := &model.Role{
RoleName: "空角色",
RoleDesc: "无权限的角色",
RoleType: constants.RoleTypePlatform,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := roleStore.Create(ctx, role)
require.NoError(t, err)
accountRole := &model.AccountRole{
AccountID: 300,
RoleID: role.ID,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
err = accountRoleStore.Create(ctx, accountRole)
require.NoError(t, err)
t.Run("角色无权限应返回false", func(t *testing.T) {
hasPermission, err := service.CheckPermission(ctx, 300, "any:permission", constants.PlatformAll)
require.NoError(t, err)
assert.False(t, hasPermission)
})
}