package enterprise_card import ( "context" stderrors "errors" "time" accessauditapp "github.com/break/junhong_cmp_fiber/internal/application/accessaudit" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" "gorm.io/gorm" "gorm.io/gorm/clause" ) type Service struct { db *gorm.DB enterpriseStore *postgres.EnterpriseStore enterpriseCardAuthStore *postgres.EnterpriseCardAuthorizationStore iotCardStore *postgres.IotCardStore accessAudit accessauditapp.Writer } func New( db *gorm.DB, enterpriseStore *postgres.EnterpriseStore, enterpriseCardAuthStore *postgres.EnterpriseCardAuthorizationStore, iotCardStore *postgres.IotCardStore, accessAudit accessauditapp.Writer, ) *Service { return &Service{ db: db, enterpriseStore: enterpriseStore, enterpriseCardAuthStore: enterpriseCardAuthStore, iotCardStore: iotCardStore, accessAudit: accessAudit, } } func (s *Service) AllocateCardsPreview(ctx context.Context, enterpriseID uint, req *dto.AllocateCardsPreviewReq) (*dto.AllocateCardsPreviewResp, error) { currentUserID := middleware.GetUserIDFromContext(ctx) if currentUserID == 0 { return nil, errors.New(errors.CodeUnauthorized, "未授权访问") } _, err := s.enterpriseStore.GetByID(ctx, enterpriseID) if err != nil { return nil, errors.New(errors.CodeEnterpriseNotFound, "企业不存在") } iotCardPtrs, err := s.iotCardStore.GetByICCIDs(ctx, req.ICCIDs) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡信息失败") } cardMap := make(map[string]*model.IotCard) cardIDMap := make(map[uint]*model.IotCard) cardIDs := make([]uint, 0, len(iotCardPtrs)) for _, c := range iotCardPtrs { cardMap[c.ICCID] = c cardIDMap[c.ID] = c cardIDs = append(cardIDs, c.ID) } var bindings []model.DeviceSimBinding if len(cardIDs) > 0 { s.db.WithContext(ctx).Where("iot_card_id IN ? AND bind_status = 1", cardIDs).Find(&bindings) } cardToDevice := make(map[uint]bool) for _, binding := range bindings { cardToDevice[binding.IotCardID] = true } resp := &dto.AllocateCardsPreviewResp{ StandaloneCards: make([]dto.StandaloneCard, 0), FailedItems: make([]dto.FailedItem, 0), } for _, iccid := range req.ICCIDs { card, exists := cardMap[iccid] if !exists { resp.FailedItems = append(resp.FailedItems, dto.FailedItem{ ICCID: iccid, Reason: "卡不存在", }) continue } if cardToDevice[card.ID] { resp.FailedItems = append(resp.FailedItems, dto.FailedItem{ ICCID: iccid, Reason: "该卡已绑定设备,请使用设备授权功能", }) continue } resp.StandaloneCards = append(resp.StandaloneCards, dto.StandaloneCard{ ICCID: card.ICCID, IotCardID: card.ID, MSISDN: card.MSISDN, CarrierID: card.CarrierID, StatusName: constants.GetIotCardStatusName(card.Status), }) } resp.Summary = dto.AllocatePreviewSummary{ StandaloneCardCount: len(resp.StandaloneCards), DeviceCount: 0, DeviceCardCount: 0, TotalCardCount: len(resp.StandaloneCards), FailedCount: len(resp.FailedItems), } return resp, nil } // resolveICCIDsForAllocate 根据选取模式解析待授权的 ICCID 列表 func (s *Service) resolveICCIDsForAllocate(ctx context.Context, req *dto.AllocateCardsReq) ([]string, error) { switch req.SelectionType { case "list": return req.ICCIDs, nil case "range": cards, err := s.iotCardStore.GetStandaloneByICCIDRangeForAuth(ctx, req.ICCIDStart, req.ICCIDEnd) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "按号段查询卡失败") } iccids := make([]string, 0, len(cards)) for _, c := range cards { iccids = append(iccids, c.ICCID) } return iccids, nil case "filter": filters := map[string]any{} if req.ICCID != "" { filters["iccid"] = req.ICCID } if req.BatchNo != "" { filters["batch_no"] = req.BatchNo } if req.CarrierID != nil { filters["carrier_id"] = *req.CarrierID } if req.ShopID != nil { filters["shop_id"] = *req.ShopID } if len(req.ShopIDs) > 0 { filters["shop_ids"] = req.ShopIDs } cards, err := s.iotCardStore.GetStandaloneByAuthFilters(ctx, filters) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "按条件查询卡失败") } iccids := make([]string, 0, len(cards)) for _, c := range cards { iccids = append(iccids, c.ICCID) } return iccids, nil default: return nil, errors.New(errors.CodeInvalidParam, "无效的选取模式") } } // resolveICCIDsForRecall 根据选取模式解析待回收的 ICCID 列表 func (s *Service) resolveICCIDsForRecall(ctx context.Context, enterpriseID uint, req *dto.RecallCardsReq) ([]string, error) { switch req.SelectionType { case "list": return req.ICCIDs, nil case "range": cards, err := s.iotCardStore.GetAuthorizedStandaloneByICCIDRange(ctx, enterpriseID, req.ICCIDStart, req.ICCIDEnd) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "按号段查询已授权卡失败") } iccids := make([]string, 0, len(cards)) for _, c := range cards { iccids = append(iccids, c.ICCID) } return iccids, nil case "filter": filters := map[string]any{} if req.ICCID != "" { filters["iccid"] = req.ICCID } if req.BatchNo != "" { filters["batch_no"] = req.BatchNo } if req.CarrierID != nil { filters["carrier_id"] = *req.CarrierID } cards, err := s.iotCardStore.GetAuthorizedStandaloneByFilters(ctx, enterpriseID, filters) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "按条件查询已授权卡失败") } iccids := make([]string, 0, len(cards)) for _, c := range cards { iccids = append(iccids, c.ICCID) } return iccids, nil default: return nil, errors.New(errors.CodeInvalidParam, "无效的选取模式") } } func (s *Service) AllocateCards(ctx context.Context, enterpriseID uint, req *dto.AllocateCardsReq) (*dto.AllocateCardsResp, error) { currentUserID := middleware.GetUserIDFromContext(ctx) if currentUserID == 0 { return nil, errors.New(errors.CodeUnauthorized, "未授权访问") } if s.db == nil || s.accessAudit == nil { return nil, errors.New(errors.CodeInvalidStatus, "企业卡授权审计接缝未配置") } if err := validateEnterpriseCardActor(ctx); err != nil { return nil, err } if err := middleware.CanManageEnterprise(ctx, enterpriseID, s.enterpriseStore); err != nil { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } enterprise, err := s.enterpriseStore.GetByID(ctx, enterpriseID) if err != nil { return nil, errors.New(errors.CodeEnterpriseNotFound, "企业不存在") } ownerShop := s.loadOwnerShop(ctx, enterprise.OwnerShopID) iccids, err := s.resolveICCIDsForAllocate(ctx, req) if err != nil { return nil, err } preview, err := s.AllocateCardsPreview(ctx, enterpriseID, &dto.AllocateCardsPreviewReq{ICCIDs: iccids}) if err != nil { return nil, err } resp := &dto.AllocateCardsResp{ FailedItems: preview.FailedItems, FailCount: len(preview.FailedItems), } // 构建 cardID -> ICCID 映射,用于组装失败原因 cardIDToICCID := make(map[uint]string, len(preview.StandaloneCards)) allCandidateIDs := make([]uint, 0, len(preview.StandaloneCards)) for _, card := range preview.StandaloneCards { cardIDToICCID[card.IotCardID] = card.ICCID allCandidateIDs = append(allCandidateIDs, card.IotCardID) } auditCardList, err := s.iotCardStore.GetByIDs(ctx, allCandidateIDs) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡审计快照失败") } cardIDMap := make(map[uint]*model.IotCard, len(auditCardList)) for _, card := range auditCardList { cardIDMap[card.ID] = card } // 检测已被其他企业授权的卡,阻止重复授权 conflictAuths, err := s.enterpriseCardAuthStore.GetConflictingAuthsByCardIDs(ctx, enterpriseID, allCandidateIDs) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询冲突授权失败") } cardIDsToAllocate := make([]uint, 0, len(allCandidateIDs)) seenAllocate := make(map[uint]struct{}, len(allCandidateIDs)) for _, cardID := range allCandidateIDs { if _, seen := seenAllocate[cardID]; seen { continue } seenAllocate[cardID] = struct{}{} if _, conflict := conflictAuths[cardID]; conflict { resp.FailedItems = append(resp.FailedItems, dto.FailedItem{ ICCID: cardIDToICCID[cardID], Reason: "卡已授权给其他企业,请先收回", }) resp.FailCount++ continue } cardIDsToAllocate = append(cardIDsToAllocate, cardID) } existingAuths, err := s.enterpriseCardAuthStore.GetActiveAuthsByCardIDs(ctx, enterpriseID, cardIDsToAllocate) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询已有授权失败") } now := time.Now() userType := middleware.GetUserTypeFromContext(ctx) auths := make([]*model.EnterpriseCardAuthorization, 0) for _, cardID := range cardIDsToAllocate { if existingAuths[cardID] { continue } auths = append(auths, &model.EnterpriseCardAuthorization{ EnterpriseID: enterpriseID, CardID: cardID, AuthorizedBy: currentUserID, AuthorizedAt: now, AuthorizerType: userType, Remark: req.Remark, }) } if len(auths) > 0 { auditResult := constants.AuditResultSuccess if resp.FailCount > 0 { auditResult = constants.AuditResultPartial } if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.CreateInBatches(auths, 100).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "创建授权记录失败") } cards := make([]accessauditapp.IotCardChange, 0, len(auths)) authorizations := make([]accessauditapp.EnterpriseCardAuthorizationChange, 0, len(auths)) for _, auth := range auths { card := cardIDMap[auth.CardID] cards = append(cards, accessauditapp.IotCardChange{ Card: card, BeforeData: map[string]any{"enterprise_id": nil, "authorized": false}, AfterData: map[string]any{"enterprise_id": enterprise.ID, "authorization_id": auth.ID, "authorized": true}, SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "卡已授权给企业", }) authorizations = append(authorizations, accessauditapp.EnterpriseCardAuthorizationChange{ Authorization: auth, AfterData: map[string]any{"authorized": true, "remark": auth.Remark}, }) } return s.accessAudit.WriteAccessChange(ctx, tx, accessauditapp.ChangeAudit{ ActionCode: constants.AuditActionEnterpriseCardsAllocated, Summary: "向企业授权卡", Result: auditResult, OperatorID: currentUserID, Enterprise: enterprise, Shop: ownerShop, Cards: cards, CardAuthorizations: authorizations, BeforeData: map[string]any{"authorized_card_count": 0}, AfterData: map[string]any{"authorized_card_count": len(auths)}, }) }); err != nil { s.recordFailure(ctx, constants.AuditActionEnterpriseCardsAllocated, "向企业授权卡失败", enterprise, ownerShop, cardChanges(cardIDMap, allCandidateIDs), err) return nil, err } } resp.SuccessCount = len(auths) return resp, nil } func (s *Service) RecallCards(ctx context.Context, enterpriseID uint, req *dto.RecallCardsReq) (*dto.RecallCardsResp, error) { currentUserID := middleware.GetUserIDFromContext(ctx) if currentUserID == 0 { return nil, errors.New(errors.CodeUnauthorized, "未授权访问") } if s.db == nil || s.accessAudit == nil { return nil, errors.New(errors.CodeInvalidStatus, "企业卡授权审计接缝未配置") } if err := validateEnterpriseCardActor(ctx); err != nil { return nil, err } if err := middleware.CanManageEnterprise(ctx, enterpriseID, s.enterpriseStore); err != nil { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } enterprise, err := s.enterpriseStore.GetByID(ctx, enterpriseID) if err != nil { return nil, errors.New(errors.CodeEnterpriseNotFound, "企业不存在") } ownerShop := s.loadOwnerShop(ctx, enterprise.OwnerShopID) iccids, err := s.resolveICCIDsForRecall(ctx, enterpriseID, req) if err != nil { return nil, err } iotCardPtrs, err := s.iotCardStore.GetByICCIDs(ctx, iccids) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡信息失败") } cardMap := make(map[string]*model.IotCard) cardIDMap := make(map[uint]*model.IotCard) cardIDs := make([]uint, 0, len(iotCardPtrs)) for _, c := range iotCardPtrs { cardMap[c.ICCID] = c cardIDMap[c.ID] = c cardIDs = append(cardIDs, c.ID) } existingAuths, err := s.enterpriseCardAuthStore.GetActiveAuthsByCardIDs(ctx, enterpriseID, cardIDs) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询已有授权失败") } resp := &dto.RecallCardsResp{ FailedItems: make([]dto.FailedItem, 0), RecalledDevices: make([]dto.RecalledDevice, 0), } cardIDsToRecall := make([]uint, 0) seenRecall := make(map[uint]struct{}, len(iccids)) for _, iccid := range iccids { card, exists := cardMap[iccid] if !exists { resp.FailedItems = append(resp.FailedItems, dto.FailedItem{ ICCID: iccid, Reason: "卡不存在", }) continue } if !existingAuths[card.ID] { resp.FailedItems = append(resp.FailedItems, dto.FailedItem{ ICCID: iccid, Reason: "该卡未授权给此企业", }) continue } if _, seen := seenRecall[card.ID]; seen { continue } seenRecall[card.ID] = struct{}{} cardIDsToRecall = append(cardIDsToRecall, card.ID) } if len(cardIDsToRecall) > 0 { var recalled []*model.EnterpriseCardAuthorization if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("enterprise_id = ? AND card_id IN ? AND revoked_at IS NULL", enterpriseID, cardIDsToRecall). Find(&recalled).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询有效卡授权失败") } if len(recalled) == 0 { return nil } now := time.Now() ids := make([]uint, 0, len(recalled)) cards := make([]accessauditapp.IotCardChange, 0, len(recalled)) authorizations := make([]accessauditapp.EnterpriseCardAuthorizationChange, 0, len(recalled)) for _, auth := range recalled { ids = append(ids, auth.ID) before := *auth auth.RevokedBy = ¤tUserID auth.RevokedAt = &now cards = append(cards, accessauditapp.IotCardChange{ Card: cardIDMap[auth.CardID], BeforeData: map[string]any{"enterprise_id": enterprise.ID, "authorization_id": auth.ID, "authorized": true}, AfterData: map[string]any{"enterprise_id": enterprise.ID, "authorization_id": auth.ID, "authorized": false}, SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "卡授权已回收", }) authorizations = append(authorizations, accessauditapp.EnterpriseCardAuthorizationChange{ Authorization: auth, BeforeData: map[string]any{"revoked_by": before.RevokedBy, "revoked_at": before.RevokedAt}, AfterData: map[string]any{"revoked_by": currentUserID, "revoked_at": now}, }) } if err := tx.Model(&model.EnterpriseCardAuthorization{}).Where("id IN ? AND revoked_at IS NULL", ids). Updates(map[string]any{"revoked_by": currentUserID, "revoked_at": now}).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "回收卡授权失败") } result := constants.AuditResultSuccess if len(resp.FailedItems) > 0 { result = constants.AuditResultPartial } return s.accessAudit.WriteAccessChange(ctx, tx, accessauditapp.ChangeAudit{ ActionCode: constants.AuditActionEnterpriseCardsRecalled, Summary: "回收企业卡授权", Result: result, OperatorID: currentUserID, Enterprise: enterprise, Shop: ownerShop, Cards: cards, CardAuthorizations: authorizations, BeforeData: map[string]any{"authorized_card_count": len(recalled)}, AfterData: map[string]any{"authorized_card_count": 0}, }) }); err != nil { s.recordFailure(ctx, constants.AuditActionEnterpriseCardsRecalled, "回收企业卡授权失败", enterprise, ownerShop, cardChanges(cardIDMap, cardIDsToRecall), err) return nil, err } resp.SuccessCount = len(recalled) } resp.FailCount = len(resp.FailedItems) return resp, nil } func validateEnterpriseCardActor(ctx context.Context) error { switch middleware.GetUserTypeFromContext(ctx) { case constants.UserTypeSuperAdmin, constants.UserTypePlatform, constants.UserTypeAgent: return nil default: return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } } func (s *Service) loadOwnerShop(ctx context.Context, ownerShopID *uint) *model.Shop { if ownerShopID == nil { return nil } shop, err := postgres.NewShopStore(s.db, nil).GetByID(ctx, *ownerShopID) if err != nil { return nil } return shop } func cardChanges(cardMap map[uint]*model.IotCard, ids []uint) []accessauditapp.IotCardChange { changes := make([]accessauditapp.IotCardChange, 0, len(ids)) for _, id := range ids { if card := cardMap[id]; card != nil { changes = append(changes, accessauditapp.IotCardChange{Card: card}) } } return changes } func (s *Service) recordFailure( ctx context.Context, actionCode, summary string, enterprise *model.Enterprise, ownerShop *model.Shop, cards []accessauditapp.IotCardChange, originalErr error, ) { accessauditapp.RecordFailure(ctx, s.db, s.accessAudit, accessauditapp.ChangeAudit{ ActionCode: actionCode, Summary: summary, Result: enterpriseCardFailureResult(originalErr), OperatorID: middleware.GetUserIDFromContext(ctx), Enterprise: enterprise, Shop: ownerShop, Cards: cards, SubjectVisibility: constants.AuditSubjectInternalOnly, }, originalErr) } func enterpriseCardFailureResult(err error) string { var appErr *errors.AppError if stderrors.As(err, &appErr) { switch appErr.Code { case errors.CodeForbidden, errors.CodeInvalidParam, errors.CodeNotFound, errors.CodeEnterpriseNotFound, errors.CodeIotCardNotFound, errors.CodeIotCardStatusNotAllowed, errors.CodeCannotAuthorizeToOthersEnterprise, errors.CodeCannotAuthorizeOthersCard, errors.CodeCannotAuthorizeBoundCard, errors.CodeCardAlreadyAuthorized, errors.CodeCardNotAuthorized, errors.CodeCannotRevokeOthersAuthorization: return constants.AuditResultDenied } } return constants.AuditResultFailed } func (s *Service) ListCards(ctx context.Context, enterpriseID uint, req *dto.EnterpriseCardListReq) (*dto.EnterpriseCardPageResult, error) { _, err := s.enterpriseStore.GetByID(ctx, enterpriseID) if err != nil { return nil, errors.New(errors.CodeEnterpriseNotFound, "企业不存在") } cardIDs, err := s.enterpriseCardAuthStore.ListCardIDsByEnterprise(ctx, enterpriseID) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询授权卡ID失败") } if len(cardIDs) == 0 { return &dto.EnterpriseCardPageResult{ Items: make([]dto.EnterpriseCardItem, 0), Total: 0, Page: req.Page, Size: req.PageSize, }, nil } page := req.Page pageSize := req.PageSize if page == 0 { page = 1 } if pageSize == 0 { pageSize = constants.DefaultPageSize } query := s.db.WithContext(ctx).Model(&model.IotCard{}).Where("id IN ?", cardIDs) if req.Status != nil { query = query.Where("status = ?", *req.Status) } if req.CarrierID != nil { query = query.Where("carrier_id = ?", *req.CarrierID) } if req.ICCID != "" { query = query.Where("iccid LIKE ?", "%"+req.ICCID+"%") } if req.VirtualNo != "" { query = query.Where("virtual_no LIKE ?", "%"+req.VirtualNo+"%") } if req.MSISDN != "" { query = query.Where("msisdn LIKE ?", "%"+req.MSISDN+"%") } var total int64 if err := query.Count(&total).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "统计卡数量失败") } var cards []model.IotCard offset := (page - 1) * pageSize if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&cards).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡列表失败") } // 批量查询各卡当前生效的主套餐名称(取 package_name 快照,避免 JOIN tb_package) pageCardIDs := make([]uint, 0, len(cards)) for _, card := range cards { pageCardIDs = append(pageCardIDs, card.ID) } packageNameMap := s.buildPackageNameMap(ctx, pageCardIDs) items := make([]dto.EnterpriseCardItem, 0, len(cards)) for _, card := range cards { items = append(items, dto.EnterpriseCardItem{ ID: card.ID, ICCID: card.ICCID, MSISDN: card.MSISDN, VirtualNo: card.VirtualNo, CarrierID: card.CarrierID, CarrierName: card.CarrierName, PackageName: packageNameMap[card.ID], Status: card.Status, StatusName: constants.GetIotCardStatusName(card.Status), NetworkStatus: card.NetworkStatus, NetworkStatusName: constants.GetNetworkStatusName(card.NetworkStatus), GatewayExtend: card.GatewayExtend, }) } return &dto.EnterpriseCardPageResult{ Items: items, Total: total, Page: page, Size: pageSize, }, nil } // buildPackageNameMap 批量查询卡列表当前生效主套餐名称,返回 map[iotCardID]packageName func (s *Service) buildPackageNameMap(ctx context.Context, cardIDs []uint) map[uint]string { result := make(map[uint]string, len(cardIDs)) if len(cardIDs) == 0 { return result } type usageRow struct { IotCardID uint `gorm:"column:iot_card_id"` PackageName string `gorm:"column:package_name"` } // 每张卡取优先级最低(数字最小)的生效中主套餐,使用 DISTINCT ON 保证每卡仅一条 var rows []usageRow s.db.WithContext(ctx).Raw(` SELECT DISTINCT ON (iot_card_id) iot_card_id, package_name FROM tb_package_usage WHERE iot_card_id IN ? AND status = 1 AND master_usage_id IS NULL AND deleted_at IS NULL ORDER BY iot_card_id, priority ASC `, cardIDs).Scan(&rows) for _, row := range rows { result[row.IotCardID] = row.PackageName } return result } func (s *Service) SuspendCard(ctx context.Context, enterpriseID, cardID uint) error { return s.updateCardNetworkStatus(ctx, enterpriseID, cardID, 0) } func (s *Service) ResumeCard(ctx context.Context, enterpriseID, cardID uint) error { return s.updateCardNetworkStatus(ctx, enterpriseID, cardID, 1) } func (s *Service) updateCardNetworkStatus(ctx context.Context, enterpriseID, cardID uint, networkStatus int) error { currentUserID := middleware.GetUserIDFromContext(ctx) if currentUserID == 0 { return errors.New(errors.CodeUnauthorized, "未授权访问") } _, err := s.enterpriseStore.GetByID(ctx, enterpriseID) if err != nil { return errors.New(errors.CodeEnterpriseNotFound, "企业不存在") } auth, err := s.enterpriseCardAuthStore.GetByEnterpriseAndCard(ctx, enterpriseID, cardID) if err != nil || auth.RevokedAt != nil { return errors.New(errors.CodeForbidden, "无权限操作此卡") } return s.iotCardStore.UpdateFields(ctx, cardID, map[string]any{ "network_status": networkStatus, }) }