package enterprise_device import ( "context" "time" "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" "go.uber.org/zap" "gorm.io/gorm" ) type Service struct { db *gorm.DB enterpriseStore *postgres.EnterpriseStore deviceStore *postgres.DeviceStore deviceSimBindingStore *postgres.DeviceSimBindingStore enterpriseDeviceAuthStore *postgres.EnterpriseDeviceAuthorizationStore enterpriseCardAuthStore *postgres.EnterpriseCardAuthorizationStore logger *zap.Logger } func New( db *gorm.DB, enterpriseStore *postgres.EnterpriseStore, deviceStore *postgres.DeviceStore, deviceSimBindingStore *postgres.DeviceSimBindingStore, enterpriseDeviceAuthStore *postgres.EnterpriseDeviceAuthorizationStore, enterpriseCardAuthStore *postgres.EnterpriseCardAuthorizationStore, logger *zap.Logger, ) *Service { return &Service{ db: db, enterpriseStore: enterpriseStore, deviceStore: deviceStore, deviceSimBindingStore: deviceSimBindingStore, enterpriseDeviceAuthStore: enterpriseDeviceAuthStore, enterpriseCardAuthStore: enterpriseCardAuthStore, logger: logger, } } // AllocateDevices 授权设备给企业 func (s *Service) AllocateDevices(ctx context.Context, enterpriseID uint, req *dto.AllocateDevicesReq) (*dto.AllocateDevicesResp, 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, "企业不存在") } // 查询所有设备 var devices []model.Device if err := s.db.WithContext(ctx).Where("device_no IN ?", req.DeviceNos).Find(&devices).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备信息失败") } deviceMap := make(map[string]*model.Device) deviceIDs := make([]uint, 0, len(devices)) for i := range devices { deviceMap[devices[i].DeviceNo] = &devices[i] deviceIDs = append(deviceIDs, devices[i].ID) } // 获取当前用户的店铺ID(用于验证设备所有权) currentShopID := middleware.GetShopIDFromContext(ctx) userType := middleware.GetUserTypeFromContext(ctx) // 检查已授权的设备 existingAuths, err := s.enterpriseDeviceAuthStore.GetActiveAuthsByDeviceIDs(ctx, enterpriseID, deviceIDs) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询已有授权失败") } resp := &dto.AllocateDevicesResp{ FailedItems: make([]dto.FailedDeviceItem, 0), AuthorizedDevices: make([]dto.AuthorizedDeviceItem, 0), } devicesToAllocate := make([]*model.Device, 0) for _, deviceNo := range req.DeviceNos { device, exists := deviceMap[deviceNo] if !exists { resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{ DeviceNo: deviceNo, Reason: "设备不存在", }) continue } // 验证设备状态(必须是"已分销"状态) if device.Status != 2 { resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{ DeviceNo: deviceNo, Reason: "设备状态不正确,必须是已分销状态", }) continue } // 验证设备所有权(除非是超级管理员或平台用户) if userType == constants.UserTypeAgent { if device.ShopID == nil || *device.ShopID != currentShopID { resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{ DeviceNo: deviceNo, Reason: "无权操作此设备", }) continue } } // 检查是否已授权 if existingAuths[device.ID] { resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{ DeviceNo: deviceNo, Reason: "设备已授权给此企业", }) continue } devicesToAllocate = append(devicesToAllocate, device) } // 在事务中处理授权 if len(devicesToAllocate) > 0 { err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { now := time.Now() authorizerType := userType // 1. 创建设备授权记录 deviceAuths := make([]*model.EnterpriseDeviceAuthorization, 0, len(devicesToAllocate)) for _, device := range devicesToAllocate { deviceAuths = append(deviceAuths, &model.EnterpriseDeviceAuthorization{ EnterpriseID: enterpriseID, DeviceID: device.ID, AuthorizedBy: currentUserID, AuthorizedAt: now, AuthorizerType: authorizerType, Remark: req.Remark, }) } if err := tx.Create(deviceAuths).Error; err != nil { return errors.Wrap(errors.CodeInternalError, err, "创建设备授权记录失败") } // 构建设备ID到授权ID的映射 deviceAuthIDMap := make(map[uint]uint) for _, auth := range deviceAuths { deviceAuthIDMap[auth.DeviceID] = auth.ID } // 2. 查询所有设备绑定的卡 deviceIDsToQuery := make([]uint, 0, len(devicesToAllocate)) for _, device := range devicesToAllocate { deviceIDsToQuery = append(deviceIDsToQuery, device.ID) } var bindings []model.DeviceSimBinding if err := tx.Where("device_id IN ? AND bind_status = 1", deviceIDsToQuery).Find(&bindings).Error; err != nil { return errors.Wrap(errors.CodeInternalError, err, "查询设备绑定卡失败") } // 3. 为每张绑定的卡创建授权记录 if len(bindings) > 0 { cardAuths := make([]*model.EnterpriseCardAuthorization, 0, len(bindings)) for _, binding := range bindings { deviceAuthID := deviceAuthIDMap[binding.DeviceID] cardAuths = append(cardAuths, &model.EnterpriseCardAuthorization{ EnterpriseID: enterpriseID, CardID: binding.IotCardID, DeviceAuthID: &deviceAuthID, AuthorizedBy: currentUserID, AuthorizedAt: now, AuthorizerType: authorizerType, Remark: req.Remark, }) } if err := tx.Create(cardAuths).Error; err != nil { return errors.Wrap(errors.CodeInternalError, err, "创建卡授权记录失败") } } // 4. 统计每个设备的绑定卡数量 deviceCardCount := make(map[uint]int) for _, binding := range bindings { deviceCardCount[binding.DeviceID]++ } // 5. 构建响应 for _, device := range devicesToAllocate { resp.AuthorizedDevices = append(resp.AuthorizedDevices, dto.AuthorizedDeviceItem{ DeviceID: device.ID, DeviceNo: device.DeviceNo, CardCount: deviceCardCount[device.ID], }) } return nil }) if err != nil { return nil, err } } resp.SuccessCount = len(devicesToAllocate) resp.FailCount = len(resp.FailedItems) return resp, nil } // RecallDevices 撤销设备授权 func (s *Service) RecallDevices(ctx context.Context, enterpriseID uint, req *dto.RecallDevicesReq) (*dto.RecallDevicesResp, 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, "企业不存在") } // 查询设备 var devices []model.Device if err := s.db.WithContext(ctx).Where("device_no IN ?", req.DeviceNos).Find(&devices).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备信息失败") } deviceMap := make(map[string]*model.Device) deviceIDs := make([]uint, 0, len(devices)) for i := range devices { deviceMap[devices[i].DeviceNo] = &devices[i] deviceIDs = append(deviceIDs, devices[i].ID) } // 检查授权状态 existingAuths, err := s.enterpriseDeviceAuthStore.GetActiveAuthsByDeviceIDs(ctx, enterpriseID, deviceIDs) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询授权状态失败") } resp := &dto.RecallDevicesResp{ FailedItems: make([]dto.FailedDeviceItem, 0), } deviceAuthsToRevoke := make([]uint, 0) for _, deviceNo := range req.DeviceNos { device, exists := deviceMap[deviceNo] if !exists { resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{ DeviceNo: deviceNo, Reason: "设备不存在", }) continue } if !existingAuths[device.ID] { resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{ DeviceNo: deviceNo, Reason: "设备未授权给此企业", }) continue } // 获取授权记录ID auth, err := s.enterpriseDeviceAuthStore.GetByDeviceID(ctx, device.ID) if err != nil || auth.EnterpriseID != enterpriseID { resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{ DeviceNo: deviceNo, Reason: "授权记录不存在", }) continue } deviceAuthsToRevoke = append(deviceAuthsToRevoke, auth.ID) } // 在事务中处理撤销 if len(deviceAuthsToRevoke) > 0 { err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 1. 撤销设备授权 if err := s.enterpriseDeviceAuthStore.RevokeByIDs(ctx, deviceAuthsToRevoke, currentUserID); err != nil { return errors.Wrap(errors.CodeInternalError, err, "撤销设备授权失败") } // 2. 级联撤销卡授权 for _, authID := range deviceAuthsToRevoke { if err := s.enterpriseCardAuthStore.RevokeByDeviceAuthID(ctx, authID, currentUserID); err != nil { return errors.Wrap(errors.CodeInternalError, err, "撤销卡授权失败") } } return nil }) if err != nil { return nil, err } } resp.SuccessCount = len(deviceAuthsToRevoke) resp.FailCount = len(resp.FailedItems) return resp, nil } // ListDevices 查询企业授权设备列表(后台管理) func (s *Service) ListDevices(ctx context.Context, enterpriseID uint, req *dto.EnterpriseDeviceListReq) (*dto.EnterpriseDeviceListResp, error) { // 验证企业存在 _, err := s.enterpriseStore.GetByID(ctx, enterpriseID) if err != nil { return nil, errors.New(errors.CodeEnterpriseNotFound, "企业不存在") } // 查询授权记录 opts := postgres.DeviceAuthListOptions{ EnterpriseID: &enterpriseID, IncludeRevoked: false, Page: req.Page, PageSize: req.PageSize, } auths, total, err := s.enterpriseDeviceAuthStore.ListByEnterprise(ctx, opts) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询授权记录失败") } if len(auths) == 0 { return &dto.EnterpriseDeviceListResp{ List: make([]dto.EnterpriseDeviceItem, 0), Total: 0, }, nil } // 收集设备ID deviceIDs := make([]uint, 0, len(auths)) authMap := make(map[uint]*model.EnterpriseDeviceAuthorization) for _, auth := range auths { deviceIDs = append(deviceIDs, auth.DeviceID) authMap[auth.DeviceID] = auth } // 查询设备信息 var devices []model.Device query := s.db.WithContext(ctx).Where("id IN ?", deviceIDs) if req.DeviceNo != "" { query = query.Where("device_no LIKE ?", "%"+req.DeviceNo+"%") } if err := query.Find(&devices).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备信息失败") } // 统计每个设备的绑定卡数量 var bindings []model.DeviceSimBinding if err := s.db.WithContext(ctx). Where("device_id IN ? AND bind_status = 1", deviceIDs). Find(&bindings).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备绑定卡失败") } cardCountMap := make(map[uint]int) for _, binding := range bindings { cardCountMap[binding.DeviceID]++ } // 构建响应 items := make([]dto.EnterpriseDeviceItem, 0, len(devices)) for _, device := range devices { auth := authMap[device.ID] items = append(items, dto.EnterpriseDeviceItem{ DeviceID: device.ID, DeviceNo: device.DeviceNo, DeviceName: device.DeviceName, DeviceModel: device.DeviceModel, CardCount: cardCountMap[device.ID], AuthorizedAt: auth.AuthorizedAt, }) } return &dto.EnterpriseDeviceListResp{ List: items, Total: total, }, nil } // ListDevicesForEnterprise 查询企业授权设备列表(H5企业用户) func (s *Service) ListDevicesForEnterprise(ctx context.Context, req *dto.EnterpriseDeviceListReq) (*dto.EnterpriseDeviceListResp, error) { enterpriseID := middleware.GetEnterpriseIDFromContext(ctx) if enterpriseID == 0 { return nil, errors.New(errors.CodeUnauthorized, "未授权访问") } opts := postgres.DeviceAuthListOptions{ EnterpriseID: &enterpriseID, IncludeRevoked: false, Page: req.Page, PageSize: req.PageSize, } auths, total, err := s.enterpriseDeviceAuthStore.ListByEnterprise(ctx, opts) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询授权记录失败") } if len(auths) == 0 { return &dto.EnterpriseDeviceListResp{ List: make([]dto.EnterpriseDeviceItem, 0), Total: 0, }, nil } deviceIDs := make([]uint, 0, len(auths)) authMap := make(map[uint]*model.EnterpriseDeviceAuthorization) for _, auth := range auths { deviceIDs = append(deviceIDs, auth.DeviceID) authMap[auth.DeviceID] = auth } var devices []model.Device query := s.db.WithContext(ctx).Where("id IN ?", deviceIDs) if req.DeviceNo != "" { query = query.Where("device_no LIKE ?", "%"+req.DeviceNo+"%") } if err := query.Find(&devices).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备信息失败") } var bindings []model.DeviceSimBinding if err := s.db.WithContext(ctx). Where("device_id IN ? AND bind_status = 1", deviceIDs). Find(&bindings).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备绑定卡失败") } cardCountMap := make(map[uint]int) for _, binding := range bindings { cardCountMap[binding.DeviceID]++ } items := make([]dto.EnterpriseDeviceItem, 0, len(devices)) for _, device := range devices { auth := authMap[device.ID] items = append(items, dto.EnterpriseDeviceItem{ DeviceID: device.ID, DeviceNo: device.DeviceNo, DeviceName: device.DeviceName, DeviceModel: device.DeviceModel, CardCount: cardCountMap[device.ID], AuthorizedAt: auth.AuthorizedAt, }) } return &dto.EnterpriseDeviceListResp{ List: items, Total: total, }, nil } // GetDeviceDetail 获取设备详情(H5企业用户) func (s *Service) GetDeviceDetail(ctx context.Context, deviceID uint) (*dto.EnterpriseDeviceDetailResp, error) { enterpriseID := middleware.GetEnterpriseIDFromContext(ctx) if enterpriseID == 0 { return nil, errors.New(errors.CodeUnauthorized, "未授权访问") } auth, err := s.enterpriseDeviceAuthStore.GetByDeviceID(ctx, deviceID) if err != nil || auth.EnterpriseID != enterpriseID || auth.RevokedAt != nil { return nil, errors.New(errors.CodeDeviceNotAuthorized, "设备未授权给此企业") } var device model.Device if err := s.db.WithContext(ctx).Where("id = ?", deviceID).First(&device).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备信息失败") } var bindings []model.DeviceSimBinding if err := s.db.WithContext(ctx). Where("device_id = ? AND bind_status = 1", deviceID). Find(&bindings).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备绑定卡失败") } cardIDs := make([]uint, 0, len(bindings)) for _, binding := range bindings { cardIDs = append(cardIDs, binding.IotCardID) } var cards []model.IotCard cardInfos := make([]dto.DeviceCardInfo, 0) if len(cardIDs) > 0 { if err := s.db.WithContext(ctx).Where("id IN ?", cardIDs).Find(&cards).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡信息失败") } carrierIDs := make([]uint, 0, len(cards)) for _, card := range cards { carrierIDs = append(carrierIDs, card.CarrierID) } var carriers []model.Carrier carrierMap := make(map[uint]string) if len(carrierIDs) > 0 { if err := s.db.WithContext(ctx).Where("id IN ?", carrierIDs).Find(&carriers).Error; err == nil { for _, carrier := range carriers { carrierMap[carrier.ID] = carrier.CarrierName } } } for _, card := range cards { cardInfos = append(cardInfos, dto.DeviceCardInfo{ CardID: card.ID, ICCID: card.ICCID, MSISDN: card.MSISDN, CarrierName: carrierMap[card.CarrierID], NetworkStatus: card.NetworkStatus, NetworkStatusName: getNetworkStatusName(card.NetworkStatus), }) } } return &dto.EnterpriseDeviceDetailResp{ Device: dto.EnterpriseDeviceInfo{ DeviceID: device.ID, DeviceNo: device.DeviceNo, DeviceName: device.DeviceName, DeviceModel: device.DeviceModel, DeviceType: device.DeviceType, AuthorizedAt: auth.AuthorizedAt, }, Cards: cardInfos, }, nil } func (s *Service) SuspendCard(ctx context.Context, deviceID, cardID uint, req *dto.DeviceCardOperationReq) (*dto.DeviceCardOperationResp, error) { if err := s.validateCardOperation(ctx, deviceID, cardID); err != nil { return nil, err } if err := s.db.WithContext(ctx).Model(&model.IotCard{}). Where("id = ?", cardID). Update("network_status", 0).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "停机操作失败") } return &dto.DeviceCardOperationResp{ Success: true, Message: "停机成功", }, nil } func (s *Service) ResumeCard(ctx context.Context, deviceID, cardID uint, req *dto.DeviceCardOperationReq) (*dto.DeviceCardOperationResp, error) { if err := s.validateCardOperation(ctx, deviceID, cardID); err != nil { return nil, err } if err := s.db.WithContext(ctx).Model(&model.IotCard{}). Where("id = ?", cardID). Update("network_status", 1).Error; err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "复机操作失败") } return &dto.DeviceCardOperationResp{ Success: true, Message: "复机成功", }, nil } func (s *Service) validateCardOperation(ctx context.Context, deviceID, cardID uint) error { enterpriseID := middleware.GetEnterpriseIDFromContext(ctx) if enterpriseID == 0 { return errors.New(errors.CodeUnauthorized, "未授权访问") } auth, err := s.enterpriseDeviceAuthStore.GetByDeviceID(ctx, deviceID) if err != nil || auth.EnterpriseID != enterpriseID || auth.RevokedAt != nil { return errors.New(errors.CodeDeviceNotAuthorized, "设备未授权给此企业") } var binding model.DeviceSimBinding if err := s.db.WithContext(ctx). Where("device_id = ? AND iot_card_id = ? AND bind_status = 1", deviceID, cardID). First(&binding).Error; err != nil { return errors.New(errors.CodeForbidden, "卡不属于该设备") } var cardAuth model.EnterpriseCardAuthorization if err := s.db.WithContext(ctx). Where("enterprise_id = ? AND card_id = ? AND device_auth_id IS NOT NULL AND revoked_at IS NULL", enterpriseID, cardID). First(&cardAuth).Error; err != nil { return errors.New(errors.CodeForbidden, "无权操作此卡") } return nil } func getNetworkStatusName(status int) string { if status == 1 { return "开机" } return "停机" }