feat(通道流量阈值): AUG26-011 运营商通道流量阈值达量停机与周期复机
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 12m59s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 12m59s
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
|
||||
systemconfigapp "github.com/break/junhong_cmp_fiber/internal/application/systemconfig"
|
||||
carrierthreshold "github.com/break/junhong_cmp_fiber/internal/domain/carrierthreshold"
|
||||
"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"
|
||||
@@ -33,6 +34,25 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateCarrierRequest) (*d
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
// 阈值字段只由超级管理员与平台账号配置:非平台账号提交即整体拒绝,不落任何配置。
|
||||
if req.HasTrafficThresholdFields() && !canManageThreshold(ctx) {
|
||||
s.recordDenied(ctx, constants.AuditOperationCarrierCreate, "拒绝非平台账号配置运营商通道流量阈值",
|
||||
thresholdCreateProbe(req), errors.CodeForbidden)
|
||||
return nil, errors.New(errors.CodeForbidden, constants.PlatformManagementForbiddenMessage)
|
||||
}
|
||||
thresholdEnabled, thresholdValue, thresholdUnit := 0, req.TrafficThresholdValue, ""
|
||||
if req.TrafficThresholdEnabled != nil {
|
||||
thresholdEnabled = *req.TrafficThresholdEnabled
|
||||
}
|
||||
if req.TrafficThresholdUnit != nil {
|
||||
thresholdUnit = *req.TrafficThresholdUnit
|
||||
}
|
||||
if err := validateThresholdConfig(thresholdEnabled, thresholdValue, thresholdUnit); err != nil {
|
||||
s.recordDenied(ctx, constants.AuditOperationCarrierCreate, "拒绝保存非法运营商通道流量阈值配置",
|
||||
thresholdCreateProbe(req), errors.CodeInvalidParam)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing, _ := s.carrierStore.GetByCode(ctx, req.CarrierCode)
|
||||
if existing != nil {
|
||||
s.recordDenied(ctx, constants.AuditOperationCarrierCreate, "拒绝创建重复运营商配置", existing, errors.CodeCarrierCodeExists)
|
||||
@@ -49,6 +69,10 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateCarrierRequest) (*d
|
||||
Description: req.Description,
|
||||
Status: constants.StatusEnabled,
|
||||
DataResetDay: 1,
|
||||
|
||||
TrafficThresholdEnabled: thresholdEnabled,
|
||||
TrafficThresholdValue: thresholdValue,
|
||||
TrafficThresholdUnit: thresholdUnit,
|
||||
}
|
||||
if req.DataResetDay != nil {
|
||||
carrier.DataResetDay = *req.DataResetDay
|
||||
@@ -72,7 +96,7 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateCarrierRequest) (*d
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "创建运营商失败")
|
||||
}
|
||||
|
||||
return s.toResponse(carrier), nil
|
||||
return s.toResponse(ctx, carrier), nil
|
||||
}
|
||||
|
||||
func (s *Service) Get(ctx context.Context, id uint) (*dto.CarrierResponse, error) {
|
||||
@@ -83,7 +107,7 @@ func (s *Service) Get(ctx context.Context, id uint) (*dto.CarrierResponse, error
|
||||
}
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "获取运营商失败")
|
||||
}
|
||||
return s.toResponse(carrier), nil
|
||||
return s.toResponse(ctx, carrier), nil
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateCarrierRequest) (*dto.CarrierResponse, error) {
|
||||
@@ -101,6 +125,32 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateCarrierReq
|
||||
}
|
||||
before := *carrier
|
||||
|
||||
// 阈值字段只由超级管理员与平台账号修改:非平台账号提交即整体拒绝。
|
||||
if req.HasTrafficThresholdFields() && !canManageThreshold(ctx) {
|
||||
s.recordDenied(ctx, constants.AuditOperationCarrierUpdate, "拒绝非平台账号配置运营商通道流量阈值",
|
||||
&before, errors.CodeForbidden)
|
||||
return nil, errors.New(errors.CodeForbidden, constants.PlatformManagementForbiddenMessage)
|
||||
}
|
||||
// 阈值按「请求覆盖既有配置」后的结果校验,避免保存出启用但缺数值/单位的半配置。
|
||||
thresholdEnabled, thresholdValue, thresholdUnit := carrier.TrafficThresholdEnabled, carrier.TrafficThresholdValue, carrier.TrafficThresholdUnit
|
||||
if req.TrafficThresholdEnabled != nil {
|
||||
thresholdEnabled = *req.TrafficThresholdEnabled
|
||||
}
|
||||
if req.TrafficThresholdValue != nil {
|
||||
thresholdValue = req.TrafficThresholdValue
|
||||
}
|
||||
if req.TrafficThresholdUnit != nil {
|
||||
thresholdUnit = *req.TrafficThresholdUnit
|
||||
}
|
||||
if err := validateThresholdConfig(thresholdEnabled, thresholdValue, thresholdUnit); err != nil {
|
||||
s.recordDenied(ctx, constants.AuditOperationCarrierUpdate, "拒绝保存非法运营商通道流量阈值配置",
|
||||
&before, errors.CodeInvalidParam)
|
||||
return nil, err
|
||||
}
|
||||
carrier.TrafficThresholdEnabled = thresholdEnabled
|
||||
carrier.TrafficThresholdValue = thresholdValue
|
||||
carrier.TrafficThresholdUnit = thresholdUnit
|
||||
|
||||
if req.CarrierName != nil {
|
||||
carrier.CarrierName = *req.CarrierName
|
||||
}
|
||||
@@ -133,7 +183,7 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateCarrierReq
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "更新运营商失败")
|
||||
}
|
||||
|
||||
return s.toResponse(carrier), nil
|
||||
return s.toResponse(ctx, carrier), nil
|
||||
}
|
||||
|
||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
@@ -193,7 +243,7 @@ func (s *Service) List(ctx context.Context, req *dto.CarrierListRequest) ([]*dto
|
||||
|
||||
responses := make([]*dto.CarrierResponse, len(carriers))
|
||||
for i, c := range carriers {
|
||||
responses[i] = s.toResponse(c)
|
||||
responses[i] = s.toResponse(ctx, c)
|
||||
}
|
||||
|
||||
return responses, total, nil
|
||||
@@ -303,11 +353,56 @@ func carrierAuditSnapshot(carrier *model.Carrier) map[string]any {
|
||||
"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,
|
||||
// 通道流量阈值的新增、修改、启用与停用都并入既有运营商更新审计的前后值快照,不另建审计动作。
|
||||
"traffic_threshold_enabled": carrier.TrafficThresholdEnabled,
|
||||
"traffic_threshold_value": carrier.TrafficThresholdValue,
|
||||
"traffic_threshold_unit": carrier.TrafficThresholdUnit,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) toResponse(c *model.Carrier) *dto.CarrierResponse {
|
||||
return &dto.CarrierResponse{
|
||||
// canManageThreshold 判断当前账号是否具备通道流量阈值字段的读写权限。
|
||||
// 只有超级管理员与平台账号可见可写,与 requirePlatformManagement 的判定一致(引用同一用户类型枚举)。
|
||||
func canManageThreshold(ctx context.Context) bool {
|
||||
userType := middleware.GetUserTypeFromContext(ctx)
|
||||
return userType == constants.UserTypeSuperAdmin || userType == constants.UserTypePlatform
|
||||
}
|
||||
|
||||
// validateThresholdConfig 校验通道流量阈值配置:启停仅 0/1、数值必须为正、单位仅 MB/GB、配置齐备。
|
||||
// 「启用但无数值/单位」与「只给数值不给单位」都会让达量判定失去唯一口径;
|
||||
// 非 0/1 的启停取值会被判定端按「未启用」静默处理,因此必须显式拒绝。
|
||||
func validateThresholdConfig(enabled int, value *float64, unit string) error {
|
||||
if enabled != constants.StatusDisabled && enabled != constants.StatusEnabled {
|
||||
return errors.New(errors.CodeInvalidParam, "通道流量阈值是否启用仅支持 0 或 1")
|
||||
}
|
||||
if value != nil && *value <= 0 {
|
||||
return errors.New(errors.CodeInvalidParam, "通道流量阈值数值必须为正数")
|
||||
}
|
||||
if unit != "" && unit != carrierthreshold.UnitMB && unit != carrierthreshold.UnitGB {
|
||||
return errors.New(errors.CodeInvalidParam, "通道流量阈值单位仅支持 MB 或 GB")
|
||||
}
|
||||
configured := value != nil || unit != ""
|
||||
if configured && (value == nil || unit == "") {
|
||||
return errors.New(errors.CodeInvalidParam, "通道流量阈值数值与流量单位必须同时提供")
|
||||
}
|
||||
if enabled == constants.StatusEnabled && !configured {
|
||||
return errors.New(errors.CodeInvalidParam, "启用通道流量阈值必须同时提供正的阈值数值与流量单位")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// thresholdCreateProbe 构造仅供拒绝审计使用的运营商身份,不写库。
|
||||
func thresholdCreateProbe(req *dto.CreateCarrierRequest) *model.Carrier {
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
return &model.Carrier{
|
||||
CarrierCode: req.CarrierCode, CarrierName: req.CarrierName, CarrierType: req.CarrierType,
|
||||
}
|
||||
}
|
||||
|
||||
// toResponse 把运营商事实投影为响应;阈值字段只对超级管理员与平台账号返回。
|
||||
func (s *Service) toResponse(ctx context.Context, c *model.Carrier) *dto.CarrierResponse {
|
||||
result := &dto.CarrierResponse{
|
||||
ID: c.ID,
|
||||
CarrierCode: c.CarrierCode,
|
||||
CarrierName: c.CarrierName,
|
||||
@@ -320,4 +415,15 @@ func (s *Service) toResponse(c *model.Carrier) *dto.CarrierResponse {
|
||||
CreatedAt: c.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: c.UpdatedAt.Format(time.RFC3339),
|
||||
}
|
||||
if !canManageThreshold(ctx) {
|
||||
return result
|
||||
}
|
||||
enabled := c.TrafficThresholdEnabled
|
||||
result.TrafficThresholdEnabled = &enabled
|
||||
result.TrafficThresholdValue = c.TrafficThresholdValue
|
||||
if c.TrafficThresholdUnit != "" {
|
||||
unit := c.TrafficThresholdUnit
|
||||
result.TrafficThresholdUnit = &unit
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user