refactor(account): 移除卡类型字段、优化账号列表查询和权限检查
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m18s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m18s
- 移除 IoT 卡和号卡的 card_type 字段(数据库迁移) - 优化账号列表查询,支持按店铺和企业筛选 - 账号响应增加店铺名称和企业名称字段 - 实现批量加载店铺和企业名称,避免 N+1 查询 - 更新权限检查中间件,完善权限验证逻辑 - 更新相关测试用例,确保功能正确性
This commit is contained in:
@@ -336,7 +336,7 @@ func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
}
|
||||
|
||||
// List 查询账号列表
|
||||
func (s *Service) List(ctx context.Context, req *dto.AccountListRequest) ([]*model.Account, int64, error) {
|
||||
func (s *Service) List(ctx context.Context, req *dto.AccountListRequest) ([]*dto.AccountResponse, int64, error) {
|
||||
opts := &store.QueryOptions{
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
@@ -362,8 +362,28 @@ func (s *Service) List(ctx context.Context, req *dto.AccountListRequest) ([]*mod
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
if req.ShopID != nil {
|
||||
filters["shop_id"] = *req.ShopID
|
||||
}
|
||||
if req.EnterpriseID != nil {
|
||||
filters["enterprise_id"] = *req.EnterpriseID
|
||||
}
|
||||
|
||||
return s.accountStore.List(ctx, opts, filters)
|
||||
accounts, total, err := s.accountStore.List(ctx, opts, filters)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
shopMap := s.loadShopNames(ctx, accounts)
|
||||
enterpriseMap := s.loadEnterpriseNames(ctx, accounts)
|
||||
|
||||
responses := make([]*dto.AccountResponse, 0, len(accounts))
|
||||
for _, acc := range accounts {
|
||||
resp := s.toAccountResponse(acc, shopMap, enterpriseMap)
|
||||
responses = append(responses, resp)
|
||||
}
|
||||
|
||||
return responses, total, nil
|
||||
}
|
||||
|
||||
// AssignRoles 为账号分配角色(支持空数组清空所有角色,超级管理员禁止分配)
|
||||
@@ -696,3 +716,78 @@ func (s *Service) CreateSystemAccount(ctx context.Context, account *model.Accoun
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadShopNames 批量加载店铺名称
|
||||
func (s *Service) loadShopNames(ctx context.Context, accounts []*model.Account) map[uint]string {
|
||||
shopIDs := make([]uint, 0)
|
||||
shopIDSet := make(map[uint]bool)
|
||||
|
||||
for _, acc := range accounts {
|
||||
if acc.ShopID != nil && *acc.ShopID > 0 && !shopIDSet[*acc.ShopID] {
|
||||
shopIDs = append(shopIDs, *acc.ShopID)
|
||||
shopIDSet[*acc.ShopID] = true
|
||||
}
|
||||
}
|
||||
|
||||
shopMap := make(map[uint]string)
|
||||
if len(shopIDs) > 0 {
|
||||
shops, err := s.shopStore.GetByIDs(ctx, shopIDs)
|
||||
if err == nil {
|
||||
for _, shop := range shops {
|
||||
shopMap[shop.ID] = shop.ShopName
|
||||
}
|
||||
}
|
||||
}
|
||||
return shopMap
|
||||
}
|
||||
|
||||
// loadEnterpriseNames 批量加载企业名称
|
||||
func (s *Service) loadEnterpriseNames(ctx context.Context, accounts []*model.Account) map[uint]string {
|
||||
enterpriseIDs := make([]uint, 0)
|
||||
enterpriseIDSet := make(map[uint]bool)
|
||||
|
||||
for _, acc := range accounts {
|
||||
if acc.EnterpriseID != nil && *acc.EnterpriseID > 0 && !enterpriseIDSet[*acc.EnterpriseID] {
|
||||
enterpriseIDs = append(enterpriseIDs, *acc.EnterpriseID)
|
||||
enterpriseIDSet[*acc.EnterpriseID] = true
|
||||
}
|
||||
}
|
||||
|
||||
enterpriseMap := make(map[uint]string)
|
||||
if len(enterpriseIDs) > 0 {
|
||||
enterprises, err := s.enterpriseStore.GetByIDs(ctx, enterpriseIDs)
|
||||
if err == nil {
|
||||
for _, ent := range enterprises {
|
||||
enterpriseMap[ent.ID] = ent.EnterpriseName
|
||||
}
|
||||
}
|
||||
}
|
||||
return enterpriseMap
|
||||
}
|
||||
|
||||
// toAccountResponse 组装账号响应,填充关联名称
|
||||
func (s *Service) toAccountResponse(acc *model.Account, shopMap map[uint]string, enterpriseMap map[uint]string) *dto.AccountResponse {
|
||||
resp := &dto.AccountResponse{
|
||||
ID: acc.ID,
|
||||
Username: acc.Username,
|
||||
Phone: acc.Phone,
|
||||
UserType: acc.UserType,
|
||||
ShopID: acc.ShopID,
|
||||
EnterpriseID: acc.EnterpriseID,
|
||||
Status: acc.Status,
|
||||
Creator: acc.Creator,
|
||||
Updater: acc.Updater,
|
||||
CreatedAt: acc.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: acc.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
if acc.ShopID != nil && *acc.ShopID > 0 {
|
||||
resp.ShopName = shopMap[*acc.ShopID]
|
||||
}
|
||||
|
||||
if acc.EnterpriseID != nil && *acc.EnterpriseID > 0 {
|
||||
resp.EnterpriseName = enterpriseMap[*acc.EnterpriseID]
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
@@ -37,6 +37,14 @@ func (m *MockShopStore) GetByID(ctx context.Context, id uint) (*model.Shop, erro
|
||||
return args.Get(0).(*model.Shop), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockShopStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.Shop, error) {
|
||||
args := m.Called(ctx, ids)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]*model.Shop), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockShopStore) GetSubordinateShopIDs(ctx context.Context, shopID uint) ([]uint, error) {
|
||||
args := m.Called(ctx, shopID)
|
||||
if args.Get(0) == nil {
|
||||
@@ -57,6 +65,14 @@ func (m *MockEnterpriseStore) GetByID(ctx context.Context, id uint) (*model.Ente
|
||||
return args.Get(0).(*model.Enterprise), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockEnterpriseStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.Enterprise, error) {
|
||||
args := m.Called(ctx, ids)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]*model.Enterprise), args.Error(1)
|
||||
}
|
||||
|
||||
func TestAccountService_Create_SuperAdminSuccess(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
|
||||
@@ -52,7 +52,6 @@ func TestAuthorizationService_BatchAuthorize_BoundCardRejected(t *testing.T) {
|
||||
|
||||
unboundCard := &model.IotCard{
|
||||
ICCID: "UNBOUND_CARD_001",
|
||||
CardType: "normal",
|
||||
CarrierID: carrier.ID,
|
||||
Status: 2,
|
||||
ShopID: &shop.ID,
|
||||
@@ -61,7 +60,6 @@ func TestAuthorizationService_BatchAuthorize_BoundCardRejected(t *testing.T) {
|
||||
|
||||
boundCard := &model.IotCard{
|
||||
ICCID: "BOUND_CARD_001",
|
||||
CardType: "normal",
|
||||
CarrierID: carrier.ID,
|
||||
Status: 2,
|
||||
ShopID: &shop.ID,
|
||||
@@ -132,7 +130,6 @@ func TestAuthorizationService_BatchAuthorize_BoundCardRejected(t *testing.T) {
|
||||
t.Run("混合卡列表中有绑定卡时整体拒绝", func(t *testing.T) {
|
||||
unboundCard2 := &model.IotCard{
|
||||
ICCID: "UNBOUND_CARD_002",
|
||||
CardType: "normal",
|
||||
CarrierID: carrier.ID,
|
||||
Status: 2,
|
||||
ShopID: &shop.ID,
|
||||
|
||||
@@ -93,7 +93,6 @@ func setupTestEnv(t *testing.T, prefix string) *testEnv {
|
||||
for i := 0; i < 4; i++ {
|
||||
cards[i] = &model.IotCard{
|
||||
ICCID: fmt.Sprintf("%s%04d", prefix, i+1),
|
||||
CardType: "normal",
|
||||
CarrierID: carrier.ID,
|
||||
Status: 2,
|
||||
ShopID: &shop.ID,
|
||||
@@ -775,7 +774,6 @@ func TestService_GetDeviceDetail_WithNetworkStatusOn(t *testing.T) {
|
||||
|
||||
card := &model.IotCard{
|
||||
ICCID: prefix + "0001",
|
||||
CardType: "normal",
|
||||
CarrierID: carrier.ID,
|
||||
Status: 2,
|
||||
NetworkStatus: 1,
|
||||
@@ -876,7 +874,6 @@ func TestService_ValidateCardOperation_RevokedDeviceAuth(t *testing.T) {
|
||||
|
||||
card := &model.IotCard{
|
||||
ICCID: prefix + "0001",
|
||||
CardType: "normal",
|
||||
CarrierID: carrier.ID,
|
||||
Status: 2,
|
||||
}
|
||||
|
||||
@@ -172,7 +172,6 @@ func (s *Service) toStandaloneResponse(card *model.IotCard, shopMap map[uint]str
|
||||
resp := &dto.StandaloneIotCardResponse{
|
||||
ID: card.ID,
|
||||
ICCID: card.ICCID,
|
||||
CardType: card.CardType,
|
||||
CardCategory: card.CardCategory,
|
||||
CarrierID: card.CarrierID,
|
||||
CarrierType: card.CarrierType,
|
||||
|
||||
@@ -56,9 +56,9 @@ func TestIotCardService_BatchSetSeriesBinding(t *testing.T) {
|
||||
|
||||
prefix := uniqueTestICCIDPrefix()
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "001", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: &shop.ID},
|
||||
{ICCID: prefix + "002", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: &shop.ID},
|
||||
{ICCID: prefix + "003", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil},
|
||||
{ICCID: prefix + "001", CarrierID: 1, Status: 1, ShopID: &shop.ID},
|
||||
{ICCID: prefix + "002", CarrierID: 1, Status: 1, ShopID: &shop.ID},
|
||||
{ICCID: prefix + "003", CarrierID: 1, Status: 1, ShopID: nil},
|
||||
}
|
||||
require.NoError(t, iotCardStore.CreateBatch(ctx, cards))
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ func createTestIotCard(t *testing.T, tx *gorm.DB, shopID *uint, seriesAllocation
|
||||
Updater: 1,
|
||||
},
|
||||
ICCID: fmt.Sprintf("89860%014d", timestamp%100000000000000),
|
||||
CardType: "流量卡",
|
||||
CardCategory: "normal",
|
||||
CarrierID: 1,
|
||||
CarrierType: "CMCC",
|
||||
|
||||
Reference in New Issue
Block a user