Files
junhong_cmp_fiber/internal/service/carrier/service.go
break 88cc5e96ec
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m32s
暂存
2026-08-06 09:35:00 +08:00

324 lines
11 KiB
Go

package carrier
import (
"context"
"strconv"
"time"
"gorm.io/gorm"
systemconfigapp "github.com/break/junhong_cmp_fiber/internal/application/systemconfig"
"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"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/auditfailure"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
)
type Service struct {
carrierStore *postgres.CarrierStore
audit systemconfigapp.AuditWriter
}
func New(carrierStore *postgres.CarrierStore, audit systemconfigapp.AuditWriter) *Service {
return &Service{carrierStore: carrierStore, audit: audit}
}
func (s *Service) Create(ctx context.Context, req *dto.CreateCarrierRequest) (*dto.CarrierResponse, error) {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
existing, _ := s.carrierStore.GetByCode(ctx, req.CarrierCode)
if existing != nil {
s.recordDenied(ctx, constants.AuditOperationCarrierCreate, "拒绝创建重复运营商配置", existing, errors.CodeCarrierCodeExists)
return nil, errors.New(errors.CodeCarrierCodeExists, "运营商编码已存在")
}
if s.audit == nil {
return nil, errors.New(errors.CodeInvalidStatus, "运营商配置审计接缝未配置")
}
carrier := &model.Carrier{
CarrierCode: req.CarrierCode,
CarrierName: req.CarrierName,
CarrierType: req.CarrierType,
Description: req.Description,
Status: constants.StatusEnabled,
DataResetDay: 1,
}
if req.DataResetDay != nil {
carrier.DataResetDay = *req.DataResetDay
}
if req.RealnameLinkType != nil {
carrier.RealnameLinkType = *req.RealnameLinkType
}
if req.RealnameLinkTemplate != nil {
carrier.RealnameLinkTemplate = *req.RealnameLinkTemplate
}
carrier.Creator = currentUserID
err := s.carrierStore.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := s.carrierStore.WithTx(tx).Create(ctx, carrier); err != nil {
return err
}
return s.writeAudit(ctx, tx, constants.AuditOperationCarrierCreate, "创建运营商配置", nil, carrier)
})
if err != nil {
s.recordFailure(ctx, constants.AuditOperationCarrierCreate, "创建运营商配置失败", carrier)
return nil, errors.Wrap(errors.CodeInternalError, err, "创建运营商失败")
}
return s.toResponse(carrier), nil
}
func (s *Service) Get(ctx context.Context, id uint) (*dto.CarrierResponse, error) {
carrier, err := s.carrierStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeCarrierNotFound, "运营商不存在")
}
return nil, errors.Wrap(errors.CodeInternalError, err, "获取运营商失败")
}
return s.toResponse(carrier), nil
}
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateCarrierRequest) (*dto.CarrierResponse, error) {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
carrier, err := s.carrierStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeCarrierNotFound, "运营商不存在")
}
return nil, errors.Wrap(errors.CodeInternalError, err, "获取运营商失败")
}
before := *carrier
if req.CarrierName != nil {
carrier.CarrierName = *req.CarrierName
}
if req.Description != nil {
carrier.Description = *req.Description
}
if req.DataResetDay != nil {
carrier.DataResetDay = *req.DataResetDay
}
if req.RealnameLinkType != nil {
carrier.RealnameLinkType = *req.RealnameLinkType
}
if req.RealnameLinkTemplate != nil {
carrier.RealnameLinkTemplate = *req.RealnameLinkTemplate
}
if carrier.RealnameLinkType == "template" && carrier.RealnameLinkTemplate == "" {
s.recordDenied(ctx, constants.AuditOperationCarrierUpdate, "拒绝保存非法运营商实名链接配置", &before, errors.CodeInvalidParam)
return nil, errors.New(errors.CodeInvalidParam, "模板URL类型必须提供实名链接模板")
}
carrier.Updater = currentUserID
err = s.carrierStore.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := s.carrierStore.WithTx(tx).Update(ctx, carrier); err != nil {
return err
}
return s.writeAudit(ctx, tx, constants.AuditOperationCarrierUpdate, "更新运营商配置", &before, carrier)
})
if err != nil {
s.recordFailure(ctx, constants.AuditOperationCarrierUpdate, "更新运营商配置失败", carrier)
return nil, errors.Wrap(errors.CodeInternalError, err, "更新运营商失败")
}
return s.toResponse(carrier), nil
}
func (s *Service) Delete(ctx context.Context, id uint) error {
carrier, err := s.carrierStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeCarrierNotFound, "运营商不存在")
}
return errors.Wrap(errors.CodeInternalError, err, "获取运营商失败")
}
if s.audit == nil {
return errors.New(errors.CodeInvalidStatus, "运营商配置审计接缝未配置")
}
err = s.carrierStore.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := s.carrierStore.WithTx(tx).Delete(ctx, id); err != nil {
return err
}
return s.writeAudit(ctx, tx, constants.AuditOperationCarrierDelete, "删除运营商配置", carrier, nil)
})
if err != nil {
s.recordFailure(ctx, constants.AuditOperationCarrierDelete, "删除运营商配置失败", carrier)
return errors.Wrap(errors.CodeInternalError, err, "删除运营商失败")
}
return nil
}
func (s *Service) List(ctx context.Context, req *dto.CarrierListRequest) ([]*dto.CarrierResponse, int64, error) {
opts := &store.QueryOptions{
Page: req.Page,
PageSize: req.PageSize,
OrderBy: "id DESC",
}
if opts.Page == 0 {
opts.Page = 1
}
if opts.PageSize == 0 {
opts.PageSize = constants.DefaultPageSize
}
filters := make(map[string]interface{})
if req.CarrierType != nil {
filters["carrier_type"] = *req.CarrierType
}
if req.CarrierName != nil {
filters["carrier_name"] = *req.CarrierName
}
if req.Status != nil {
filters["status"] = *req.Status
}
carriers, total, err := s.carrierStore.List(ctx, opts, filters)
if err != nil {
return nil, 0, errors.Wrap(errors.CodeInternalError, err, "查询运营商列表失败")
}
responses := make([]*dto.CarrierResponse, len(carriers))
for i, c := range carriers {
responses[i] = s.toResponse(c)
}
return responses, total, nil
}
func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return errors.New(errors.CodeUnauthorized, "未授权访问")
}
carrier, err := s.carrierStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeCarrierNotFound, "运营商不存在")
}
return errors.Wrap(errors.CodeInternalError, err, "获取运营商失败")
}
if s.audit == nil {
return errors.New(errors.CodeInvalidStatus, "运营商配置审计接缝未配置")
}
before := *carrier
carrier.Status = status
carrier.Updater = currentUserID
err = s.carrierStore.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := s.carrierStore.WithTx(tx).Update(ctx, carrier); err != nil {
return err
}
return s.writeAudit(ctx, tx, constants.AuditOperationCarrierStatusUpdate, "更新运营商配置状态", &before, carrier)
})
if err != nil {
s.recordFailure(ctx, constants.AuditOperationCarrierStatusUpdate, "更新运营商配置状态失败", carrier)
return errors.Wrap(errors.CodeInternalError, err, "更新运营商状态失败")
}
return nil
}
func (s *Service) writeAudit(ctx context.Context, tx *gorm.DB, operation, description string, before, after *model.Carrier) error {
carrier := after
if carrier == nil {
carrier = before
}
resourceID := strconv.FormatUint(uint64(carrier.ID), 10)
requestID := ""
if value := middleware.GetRequestIDFromContext(ctx); value != nil {
requestID = *value
}
return s.audit.WriteConfigChange(ctx, tx, systemconfigapp.ChangeAudit{
OperatorID: middleware.GetUserIDFromContext(ctx), OperationType: operation, Description: description,
ConfigKey: "carrier." + carrier.CarrierCode, Module: "carrier", ResourceID: &resourceID,
DisplayName: carrier.CarrierName, Identity: carrierIdentity(carrier),
BeforeData: carrierAuditSnapshot(before), AfterData: carrierAuditSnapshot(after),
RequestID: requestID, CorrelationID: requestID,
})
}
func (s *Service) recordDenied(ctx context.Context, operation, description string, carrier *model.Carrier, code int) {
s.recordAuditResult(ctx, operation, description, carrier, constants.AuditResultDenied, code)
}
func (s *Service) recordFailure(ctx context.Context, operation, description string, carrier *model.Carrier) {
s.recordAuditResult(ctx, operation, description, carrier, constants.AuditResultFailed, errors.CodeDatabaseError)
}
func (s *Service) recordAuditResult(ctx context.Context, operation, description string, carrier *model.Carrier, result string, code int) {
if s.audit == nil || carrier == nil || s.carrierStore == nil || s.carrierStore.DB() == nil {
return
}
resourceID := strconv.FormatUint(uint64(carrier.ID), 10)
requestID := ""
if value := middleware.GetRequestIDFromContext(ctx); value != nil {
requestID = *value
}
audit := systemconfigapp.ChangeAudit{
OperatorID: middleware.GetUserIDFromContext(ctx), OperationType: operation, Description: description,
ConfigKey: "carrier." + carrier.CarrierCode, Module: "carrier", ResourceID: &resourceID,
DisplayName: carrier.CarrierName, Identity: carrierIdentity(carrier), BeforeData: carrierAuditSnapshot(carrier),
Result: result, ErrorCode: strconv.Itoa(code), ErrorSummary: description,
RequestID: requestID, CorrelationID: requestID,
}
if err := s.carrierStore.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
return s.audit.WriteConfigChange(ctx, tx, audit)
}); err != nil {
auditfailure.RecordSecondaryWriteFailure(operation, audit.ConfigKey, requestID, requestID, audit.ErrorCode, err)
}
}
func carrierIdentity(carrier *model.Carrier) map[string]any {
if carrier == nil {
return nil
}
return map[string]any{
"id": carrier.ID, "carrier_code": carrier.CarrierCode, "carrier_name": carrier.CarrierName,
"carrier_type": carrier.CarrierType, "status": carrier.Status,
}
}
func carrierAuditSnapshot(carrier *model.Carrier) map[string]any {
if carrier == nil {
return nil
}
return map[string]any{
"id": carrier.ID, "carrier_code": carrier.CarrierCode, "carrier_name": carrier.CarrierName,
"carrier_type": carrier.CarrierType, "description": carrier.Description, "status": carrier.Status,
"realname_link_type": carrier.RealnameLinkType, "realname_link_template": carrier.RealnameLinkTemplate,
"data_reset_day": carrier.DataResetDay,
}
}
func (s *Service) toResponse(c *model.Carrier) *dto.CarrierResponse {
return &dto.CarrierResponse{
ID: c.ID,
CarrierCode: c.CarrierCode,
CarrierName: c.CarrierName,
CarrierType: c.CarrierType,
Description: c.Description,
DataResetDay: c.DataResetDay,
RealnameLinkType: c.RealnameLinkType,
RealnameLinkTemplate: c.RealnameLinkTemplate,
Status: c.Status,
CreatedAt: c.CreatedAt.Format(time.RFC3339),
UpdatedAt: c.UpdatedAt.Format(time.RFC3339),
}
}