feat(shop-role): 实现店铺角色继承功能和权限检查优化
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m39s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m39s
- 新增店铺角色管理 API 和数据模型 - 实现角色继承和权限检查逻辑 - 添加流程测试框架和集成测试 - 更新权限服务和账号管理逻辑 - 添加数据库迁移脚本 - 归档 OpenSpec 变更文档 Ultraworked with Sisyphus
This commit is contained in:
101
internal/store/postgres/shop_role_store.go
Normal file
101
internal/store/postgres/shop_role_store.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ShopRoleStore struct {
|
||||
db *gorm.DB
|
||||
redisClient *redis.Client
|
||||
}
|
||||
|
||||
func NewShopRoleStore(db *gorm.DB, redisClient *redis.Client) *ShopRoleStore {
|
||||
return &ShopRoleStore{
|
||||
db: db,
|
||||
redisClient: redisClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ShopRoleStore) Create(ctx context.Context, sr *model.ShopRole) error {
|
||||
if err := s.db.WithContext(ctx).Create(sr).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
s.clearShopRoleCache(ctx, sr.ShopID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ShopRoleStore) BatchCreate(ctx context.Context, srs []*model.ShopRole) error {
|
||||
if len(srs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Create(&srs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
s.clearShopRoleCache(ctx, srs[0].ShopID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ShopRoleStore) Delete(ctx context.Context, shopID, roleID uint) error {
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("shop_id = ? AND role_id = ?", shopID, roleID).
|
||||
Delete(&model.ShopRole{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
s.clearShopRoleCache(ctx, shopID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ShopRoleStore) DeleteByShopID(ctx context.Context, shopID uint) error {
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("shop_id = ?", shopID).
|
||||
Delete(&model.ShopRole{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
s.clearShopRoleCache(ctx, shopID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ShopRoleStore) GetByShopID(ctx context.Context, shopID uint) ([]*model.ShopRole, error) {
|
||||
var srs []*model.ShopRole
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("shop_id = ?", shopID).
|
||||
Find(&srs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return srs, nil
|
||||
}
|
||||
|
||||
func (s *ShopRoleStore) GetRoleIDsByShopID(ctx context.Context, shopID uint) ([]uint, error) {
|
||||
var roleIDs []uint
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.ShopRole{}).
|
||||
Where("shop_id = ?", shopID).
|
||||
Pluck("role_id", &roleIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return roleIDs, nil
|
||||
}
|
||||
|
||||
func (s *ShopRoleStore) clearShopRoleCache(ctx context.Context, shopID uint) {
|
||||
if s.redisClient == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var accountIDs []uint
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.Account{}).
|
||||
Where("shop_id = ?", shopID).
|
||||
Pluck("id", &accountIDs).Error; err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, accountID := range accountIDs {
|
||||
key := constants.RedisUserPermissionsKey(accountID)
|
||||
s.redisClient.Del(ctx, key)
|
||||
}
|
||||
}
|
||||
171
internal/store/postgres/shop_role_store_test.go
Normal file
171
internal/store/postgres/shop_role_store_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestShopRoleStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
sr := &model.ShopRole{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
}
|
||||
|
||||
err := store.Create(ctx, sr)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, sr.ID)
|
||||
}
|
||||
|
||||
func TestShopRoleStore_BatchCreate(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
srs := []*model.ShopRole{
|
||||
{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := store.BatchCreate(ctx, srs)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, srs[0].ID)
|
||||
}
|
||||
|
||||
func TestShopRoleStore_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
sr := &model.ShopRole{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, sr))
|
||||
|
||||
err := store.Delete(ctx, 1, 5)
|
||||
require.NoError(t, err)
|
||||
|
||||
results, err := store.GetByShopID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, results)
|
||||
}
|
||||
|
||||
func TestShopRoleStore_DeleteByShopID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
srs := []*model.ShopRole{
|
||||
{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
{
|
||||
ShopID: 1,
|
||||
RoleID: 6,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
require.NoError(t, store.BatchCreate(ctx, srs))
|
||||
|
||||
err := store.DeleteByShopID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
results, err := store.GetByShopID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, results)
|
||||
}
|
||||
|
||||
func TestShopRoleStore_GetByShopID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("查询已分配角色", func(t *testing.T) {
|
||||
sr := &model.ShopRole{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, sr))
|
||||
|
||||
results, err := store.GetByShopID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, results, 1)
|
||||
assert.Equal(t, uint(1), results[0].ShopID)
|
||||
assert.Equal(t, uint(5), results[0].RoleID)
|
||||
})
|
||||
|
||||
t.Run("查询未分配角色的店铺", func(t *testing.T) {
|
||||
results, err := store.GetByShopID(ctx, 999)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, results)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopRoleStore_GetRoleIDsByShopID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("查询已分配角色的店铺", func(t *testing.T) {
|
||||
sr := &model.ShopRole{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, sr))
|
||||
|
||||
roleIDs, err := store.GetRoleIDsByShopID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []uint{5}, roleIDs)
|
||||
})
|
||||
|
||||
t.Run("查询未分配角色的店铺", func(t *testing.T) {
|
||||
roleIDs, err := store.GetRoleIDsByShopID(ctx, 999)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, roleIDs)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user