Files
junhong_cmp_fiber/internal/service/enterprise_card/service.go
huang 8f3a68a673
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m5s
新增上游的停机原因
2026-05-12 11:13:04 +08:00

381 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package enterprise_card
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"
"gorm.io/gorm"
)
type Service struct {
db *gorm.DB
enterpriseStore *postgres.EnterpriseStore
enterpriseCardAuthStore *postgres.EnterpriseCardAuthorizationStore
iotCardStore *postgres.IotCardStore
}
func New(
db *gorm.DB,
enterpriseStore *postgres.EnterpriseStore,
enterpriseCardAuthStore *postgres.EnterpriseCardAuthorizationStore,
iotCardStore *postgres.IotCardStore,
) *Service {
return &Service{
db: db,
enterpriseStore: enterpriseStore,
enterpriseCardAuthStore: enterpriseCardAuthStore,
iotCardStore: iotCardStore,
}
}
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
}
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, "未授权访问")
}
_, err := s.enterpriseStore.GetByID(ctx, enterpriseID)
if err != nil {
return nil, errors.New(errors.CodeEnterpriseNotFound, "企业不存在")
}
preview, err := s.AllocateCardsPreview(ctx, enterpriseID, &dto.AllocateCardsPreviewReq{ICCIDs: req.ICCIDs})
if err != nil {
return nil, err
}
resp := &dto.AllocateCardsResp{
FailedItems: preview.FailedItems,
FailCount: len(preview.FailedItems),
}
cardIDsToAllocate := make([]uint, 0)
for _, card := range preview.StandaloneCards {
cardIDsToAllocate = append(cardIDsToAllocate, card.IotCardID)
}
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 {
if err := s.enterpriseCardAuthStore.BatchCreate(ctx, auths); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "创建授权记录失败")
}
}
resp.SuccessCount = len(cardIDsToAllocate)
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, "未授权访问")
}
_, 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)
}
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)
for _, iccid := range req.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
}
cardIDsToRecall = append(cardIDsToRecall, card.ID)
}
if len(cardIDsToRecall) > 0 {
if err := s.enterpriseCardAuthStore.BatchUpdateStatus(ctx, enterpriseID, cardIDsToRecall, 0); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "回收授权失败")
}
}
resp.SuccessCount = len(cardIDsToRecall)
resp.FailCount = len(resp.FailedItems)
return resp, nil
}
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+"%")
}
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,
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,
})
}