feat: 迁移预警处理审核与备注附件流程
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"kra/internal/biz/business"
|
||||
"kra/internal/service/dto"
|
||||
)
|
||||
|
||||
type AlertRecordUsecase interface {
|
||||
List(context.Context, business.AlertRecordFilter, ...business.ListOption) (*business.AlertRecordPage, error)
|
||||
Detail(context.Context, uint64) (*business.AlertRecordDetail, error)
|
||||
Process(context.Context, uint64, business.AlertAction, string) error
|
||||
AddNote(context.Context, uint64, string, []string) error
|
||||
}
|
||||
|
||||
type AlertRecordService struct{ uc AlertRecordUsecase }
|
||||
|
||||
func NewAlertRecordService(uc *business.AlertRecordUsecase) *AlertRecordService {
|
||||
return &AlertRecordService{uc: uc}
|
||||
}
|
||||
|
||||
func alertRecordResponse(r *business.AlertRecord) *dto.AlertRecordResponse {
|
||||
metricLabel := ""
|
||||
if r.MonitorMetric != nil {
|
||||
metricLabel = business.AlertMetricLabel(*r.MonitorMetric)
|
||||
}
|
||||
return &dto.AlertRecordResponse{ID: r.ID, RuleID: r.RuleID, PlatformCode: r.PlatformCode, PlatformName: r.PlatformName, RuleName: r.RuleName, RuleType: r.RuleType, RuleTypeLabel: business.AlertRuleTypeLabel(r.RuleType), MonitorMetric: r.MonitorMetric, MonitorMetricLabel: metricLabel, TargetPromoteID: r.TargetPromoteID, TargetPromoteName: r.TargetPromoteName, BusinessID: r.BusinessID, BusinessName: r.BusinessName, AlertContent: r.AlertContent, Status: r.Status, StatusLabel: business.AlertStatusLabel(r.Status), CreatedAt: localTime(&r.CreatedAt), UpdatedAt: localTime(&r.UpdatedAt)}
|
||||
}
|
||||
|
||||
func (s *AlertRecordService) List(ctx context.Context, req *dto.AlertRecordQuery) (*dto.AlertRecordPage, error) {
|
||||
if req == nil {
|
||||
return nil, business.ErrAlertRecordInput
|
||||
}
|
||||
if req.MonitorMetric != nil && (*req.MonitorMetric < 1 || *req.MonitorMetric > 3) {
|
||||
return nil, business.ErrAlertRecordInput
|
||||
}
|
||||
if req.Status != nil && (*req.Status < 1 || *req.Status > 5) {
|
||||
return nil, business.ErrAlertRecordInput
|
||||
}
|
||||
page, size := req.Page, req.PageSize
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
if size > 500 {
|
||||
size = 500
|
||||
}
|
||||
if page > int(^uint(0)>>1)/size {
|
||||
return nil, business.ErrAlertRecordInput
|
||||
}
|
||||
filter := business.AlertRecordFilter{TargetPromoteID: req.TargetPromoteID, BusinessID: req.BusinessID, TargetPromoteName: strings.TrimSpace(req.TargetPromoteName), RuleName: strings.TrimSpace(req.RuleName), RuleType: strings.TrimSpace(req.RuleType), BusinessName: strings.TrimSpace(req.BusinessName), MonitorMetric: req.MonitorMetric, Status: req.Status}
|
||||
seen := map[string]bool{}
|
||||
for _, code := range strings.Split(req.PlatformCodes, ",") {
|
||||
code = strings.TrimSpace(code)
|
||||
if code != "" && !seen[code] {
|
||||
filter.PlatformCodes = append(filter.PlatformCodes, code)
|
||||
seen[code] = true
|
||||
}
|
||||
}
|
||||
result, err := s.uc.List(ctx, filter, business.ListOffset((page-1)*size), business.ListLimit(size))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &dto.AlertRecordPage{PageData: dto.AlertRecordRows{Rows: []*dto.AlertRecordResponse{}, Total: result.Total}, StatusCount: result.StatusCount, Available: result.Available}
|
||||
for _, row := range result.Rows {
|
||||
out.PageData.Rows = append(out.PageData.Rows, alertRecordResponse(row))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *AlertRecordService) Detail(ctx context.Context, id uint64) (*dto.AlertRecordDetail, error) {
|
||||
if id == 0 {
|
||||
return nil, business.ErrAlertRecordInput
|
||||
}
|
||||
detail, err := s.uc.Detail(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &dto.AlertRecordDetail{AlertRecord: alertRecordResponse(detail.Record), OperationLogs: []*dto.AlertLogResponse{}, CanAudit: detail.CanAudit}
|
||||
for _, l := range detail.Logs {
|
||||
out.OperationLogs = append(out.OperationLogs, &dto.AlertLogResponse{ID: l.ID, AlertID: l.AlertID, OperatorID: l.OperatorID, OperatorName: l.OperatorName, LogType: l.LogType, LogTypeLabel: business.AlertLogTypeLabel(l.LogType), ActionDetail: l.ActionDetail, NoteContent: l.NoteContent, Attachments: l.Attachments, CreatedAt: localTime(&l.CreatedAt)})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *AlertRecordService) Process(ctx context.Context, req *dto.AlertProcessRequest, action business.AlertAction) error {
|
||||
if req == nil || len(req.Note) > 65535 {
|
||||
return business.ErrAlertRecordInput
|
||||
}
|
||||
id, err := strconv.ParseUint(req.AlertID.String(), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
return business.ErrAlertRecordInput
|
||||
}
|
||||
return s.uc.Process(ctx, id, action, req.Note)
|
||||
}
|
||||
|
||||
func (s *AlertRecordService) AddNote(ctx context.Context, req *dto.AlertNoteRequest) error {
|
||||
if req == nil || len(req.NoteContent) > 65535 {
|
||||
return business.ErrAlertRecordInput
|
||||
}
|
||||
id, err := strconv.ParseUint(req.AlertID.String(), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
return business.ErrAlertRecordInput
|
||||
}
|
||||
if strings.TrimSpace(req.NoteContent) == "" {
|
||||
return business.ErrAlertNoteEmpty
|
||||
}
|
||||
values := make([]string, 0, len(req.AttachmentIDs))
|
||||
for _, raw := range req.AttachmentIDs {
|
||||
var value string
|
||||
if json.Unmarshal(raw, &value) != nil {
|
||||
var number json.Number
|
||||
if json.Unmarshal(raw, &number) != nil {
|
||||
return business.ErrAlertRecordInput
|
||||
}
|
||||
if _, err := number.Int64(); err != nil {
|
||||
return business.ErrAlertRecordInput
|
||||
}
|
||||
value = number.String()
|
||||
}
|
||||
if value == "" {
|
||||
return business.ErrAlertRecordInput
|
||||
}
|
||||
values = append(values, value)
|
||||
}
|
||||
return s.uc.AddNote(ctx, id, req.NoteContent, values)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"kra/internal/biz/business"
|
||||
"kra/internal/service/dto"
|
||||
)
|
||||
|
||||
type alertRecordUsecaseFake struct {
|
||||
AlertRecordUsecase
|
||||
id uint64
|
||||
filter business.AlertRecordFilter
|
||||
attachments []string
|
||||
}
|
||||
|
||||
func (f *alertRecordUsecaseFake) Process(_ context.Context, id uint64, _ business.AlertAction, _ string) error {
|
||||
f.id = id
|
||||
return nil
|
||||
}
|
||||
func (f *alertRecordUsecaseFake) AddNote(_ context.Context, id uint64, _ string, attachments []string) error {
|
||||
f.id = id
|
||||
f.attachments = attachments
|
||||
return nil
|
||||
}
|
||||
func (f *alertRecordUsecaseFake) List(_ context.Context, filter business.AlertRecordFilter, _ ...business.ListOption) (*business.AlertRecordPage, error) {
|
||||
f.filter = filter
|
||||
return &business.AlertRecordPage{Rows: []*business.AlertRecord{}, StatusCount: map[int]int64{}, Available: true}, nil
|
||||
}
|
||||
|
||||
func TestAlertRecordServiceAcceptsExactIDsAndAttachmentFormats(t *testing.T) {
|
||||
f := &alertRecordUsecaseFake{}
|
||||
s := &AlertRecordService{uc: f}
|
||||
for _, raw := range []string{`{"alertId":"9007199254740993"}`, `{"alertId":9007199254740993}`} {
|
||||
var req dto.AlertProcessRequest
|
||||
if err := json.Unmarshal([]byte(raw), &req); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Process(context.Background(), &req, business.AlertStart); err != nil || f.id != 9007199254740993 {
|
||||
t.Fatal("预警 ID 在 DTO 边界失真")
|
||||
}
|
||||
}
|
||||
var req dto.AlertNoteRequest
|
||||
if err := json.Unmarshal([]byte(`{"alertId":"10","noteContent":"备注","attachmentIds":[9007199254740993,"2","/files/a.png"]}`), &req); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.AddNote(context.Background(), &req); err != nil || !reflect.DeepEqual(f.attachments, []string{"9007199254740993", "2", "/files/a.png"}) {
|
||||
t.Fatalf("附件解析错误: %+v %v", f.attachments, err)
|
||||
}
|
||||
for _, raw := range []string{`null`, `1.5`, `true`, `{"url":"bad"}`} {
|
||||
req.AttachmentIDs = []json.RawMessage{json.RawMessage(raw)}
|
||||
if err := s.AddNote(context.Background(), &req); err == nil {
|
||||
t.Fatalf("错误附件值被接受: %s", raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertRecordServiceQueryNormalizationAndIDResponse(t *testing.T) {
|
||||
f := &alertRecordUsecaseFake{}
|
||||
s := &AlertRecordService{uc: f}
|
||||
_, err := s.List(context.Background(), &dto.AlertRecordQuery{PlatformCodes: " lx, hw,lx, ", RuleName: " 注册 "})
|
||||
if err != nil || !reflect.DeepEqual(f.filter.PlatformCodes, []string{"lx", "hw"}) || f.filter.RuleName != "注册" {
|
||||
t.Fatal("查询参数未归一化")
|
||||
}
|
||||
raw, err := json.Marshal(alertRecordResponse(&business.AlertRecord{ID: 9007199254740993, RuleID: 9007199254740995, Status: 3}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var value map[string]any
|
||||
if err = json.Unmarshal(raw, &value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if value["id"] != "9007199254740993" || value["ruleId"] != "9007199254740995" || value["statusLabel"] != "待审核" || value["warningColor"] != nil {
|
||||
t.Fatalf("响应丢失精度或改变快照语义: %s", raw)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package dto
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type AlertRecordQuery struct {
|
||||
Page int `form:"pageNum"`
|
||||
PageSize int `form:"pageSize"`
|
||||
PlatformCodes string `form:"platformCodes"`
|
||||
TargetPromoteID *uint64 `form:"targetPromoteId"`
|
||||
BusinessID *uint `form:"businessId"`
|
||||
TargetPromoteName string `form:"targetPromoteName"`
|
||||
RuleName string `form:"ruleName"`
|
||||
RuleType string `form:"ruleType"`
|
||||
BusinessName string `form:"businessName"`
|
||||
MonitorMetric *int `form:"monitorMetric"`
|
||||
Status *int `form:"status"`
|
||||
}
|
||||
|
||||
type AlertProcessRequest struct {
|
||||
AlertID json.Number `json:"alertId"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
type AlertNoteRequest struct {
|
||||
AlertID json.Number `json:"alertId"`
|
||||
NoteContent string `json:"noteContent"`
|
||||
AttachmentIDs []json.RawMessage `json:"attachmentIds"`
|
||||
}
|
||||
|
||||
type AlertRecordResponse struct {
|
||||
ID uint64 `json:"id,string"`
|
||||
PlatformCode string `json:"platformCode"`
|
||||
PlatformName string `json:"platformName"`
|
||||
RuleID uint64 `json:"ruleId,string"`
|
||||
RuleName string `json:"ruleName"`
|
||||
RuleType string `json:"ruleType"`
|
||||
RuleTypeLabel string `json:"ruleTypeLabel"`
|
||||
MonitorMetric *int `json:"monitorMetric"`
|
||||
MonitorMetricLabel string `json:"monitorMetricLabel"`
|
||||
// 旧视图声明了颜色,但记录没有对应快照字段,保留空值。
|
||||
WarningColor *string `json:"warningColor"`
|
||||
TargetPromoteID uint64 `json:"targetPromoteId"`
|
||||
TargetPromoteName string `json:"targetPromoteName"`
|
||||
BusinessID uint `json:"businessId"`
|
||||
BusinessName string `json:"businessName"`
|
||||
AlertContent string `json:"alertContent"`
|
||||
Status int `json:"status"`
|
||||
StatusLabel string `json:"statusLabel"`
|
||||
CreatedAt *string `json:"createdAt"`
|
||||
UpdatedAt *string `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type AlertLogResponse struct {
|
||||
ID uint64 `json:"id,string"`
|
||||
AlertID uint64 `json:"alertId,string"`
|
||||
OperatorID uint `json:"operatorId"`
|
||||
OperatorName string `json:"operatorName"`
|
||||
LogType int `json:"logType"`
|
||||
LogTypeLabel string `json:"logTypeLabel"`
|
||||
ActionDetail string `json:"actionDetail"`
|
||||
NoteContent string `json:"noteContent"`
|
||||
Attachments []string `json:"attachments"`
|
||||
CreatedAt *string `json:"createdAt"`
|
||||
}
|
||||
|
||||
type AlertRecordRows struct {
|
||||
Rows []*AlertRecordResponse `json:"rows"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type AlertRecordPage struct {
|
||||
PageData AlertRecordRows `json:"pageData"`
|
||||
StatusCount map[int]int64 `json:"statusCount"`
|
||||
Available bool `json:"available"`
|
||||
}
|
||||
|
||||
type AlertRecordDetail struct {
|
||||
AlertRecord *AlertRecordResponse `json:"alertRecord"`
|
||||
OperationLogs []*AlertLogResponse `json:"operationLogs"`
|
||||
CanAudit bool `json:"canAudit"`
|
||||
}
|
||||
@@ -18,6 +18,7 @@ var ProviderSet = wire.NewSet(
|
||||
businessservice.NewPasswordResetService,
|
||||
businessservice.NewBusinessPromoteService,
|
||||
businessservice.NewAlertRuleService,
|
||||
businessservice.NewAlertRecordService,
|
||||
systemservice.ProviderSet,
|
||||
paymentservice.ProviderSet,
|
||||
integrationservice.ProviderSet,
|
||||
|
||||
Reference in New Issue
Block a user