package warningnotify import ( "sort" "strings" ) type groupKey struct { Business string TenantID string DeptID int64 CreatedBy int64 Method string Recipients string Category string } func BuildBatches(events []Event) []Batch { if len(events) == 0 { return nil } grouped := make(map[groupKey]*Batch) for _, event := range events { event = normalizeEvent(event) for _, method := range methodsForEvent(event) { for _, recipients := range recipientGroupsForMethod(method, event) { if methodRequiresRecipient(method) && len(recipients) == 0 { continue } key := groupKeyForEvent(method, recipients, event) batch := grouped[key] if batch == nil { batch = &Batch{ Business: event.Business, Method: method, TenantID: event.TenantID, DeptID: event.DeptID, DeptName: event.DeptName, CreatedBy: event.CreatedBy, Category: event.Category, Recipients: recipients, Items: make([]Event, 0), } grouped[key] = batch } else if strings.TrimSpace(batch.DeptName) == "" { batch.DeptName = event.DeptName } batch.Items = append(batch.Items, event) } } } keys := make([]groupKey, 0, len(grouped)) for key := range grouped { keys = append(keys, key) } sort.Slice(keys, func(i, j int) bool { return compareGroupKey(keys[i], keys[j]) < 0 }) batches := make([]Batch, 0, len(keys)) for _, key := range keys { batch := grouped[key] sort.SliceStable(batch.Items, func(i, j int) bool { if batch.Items[i].WarnStage != batch.Items[j].WarnStage { return batch.Items[i].WarnStage > batch.Items[j].WarnStage } if batch.Items[i].ItemCode != batch.Items[j].ItemCode { return batch.Items[i].ItemCode < batch.Items[j].ItemCode } return batch.Items[i].EventID < batch.Items[j].EventID }) batch.WarnStage = highestStage(batch.Items) applyContent(batch) batches = append(batches, *batch) } return batches } func groupKeyForEvent(method string, recipients []string, event Event) groupKey { key := groupKey{ Business: event.Business, TenantID: event.TenantID, DeptID: event.DeptID, CreatedBy: event.CreatedBy, Method: method, Recipients: strings.Join(recipients, ","), Category: event.Category, } if method == MethodSMS || method == MethodEmail { key.DeptID = 0 key.CreatedBy = 0 } if method == MethodWechat { key.DeptID = 0 key.CreatedBy = 0 key.Recipients = "" } return key } func compareGroupKey(left, right groupKey) int { leftParts := []string{left.TenantID, left.Business, left.Method, left.Recipients, left.Category} rightParts := []string{right.TenantID, right.Business, right.Method, right.Recipients, right.Category} for i := range leftParts { if leftParts[i] < rightParts[i] { return -1 } if leftParts[i] > rightParts[i] { return 1 } } if left.DeptID != right.DeptID { if left.DeptID < right.DeptID { return -1 } return 1 } if left.CreatedBy != right.CreatedBy { if left.CreatedBy < right.CreatedBy { return -1 } return 1 } return 0 } func normalizeEvent(event Event) Event { event.Business = strings.TrimSpace(event.Business) if event.Business == "" { event.Business = BusinessSimWarning } event.TenantID = strings.TrimSpace(event.TenantID) event.DeptName = strings.TrimSpace(event.DeptName) event.Category = strings.TrimSpace(event.Category) if event.Category == "" { event.Category = "预警" } event.Kind = strings.TrimSpace(event.Kind) event.ItemCode = strings.TrimSpace(event.ItemCode) event.MSISDN = strings.TrimSpace(event.MSISDN) event.WarnMethods = strings.TrimSpace(event.WarnMethods) event.WarnPhones = strings.TrimSpace(event.WarnPhones) event.WarnEmails = strings.TrimSpace(event.WarnEmails) event.UnitText = strings.TrimSpace(event.UnitText) event.Remark = strings.TrimSpace(event.Remark) event.OperationText = strings.TrimSpace(event.OperationText) event.BatchNo = strings.TrimSpace(event.BatchNo) return event } func methodsForEvent(event Event) []string { values := splitList(event.WarnMethods) seen := map[string]struct{}{MethodPlatform: {}} result := []string{MethodPlatform} for _, value := range values { switch value { case MethodSMS, MethodEmail, MethodPhone, MethodPlatform: default: continue } if _, exists := seen[value]; exists { continue } seen[value] = struct{}{} result = append(result, value) } sort.SliceStable(result, func(i, j int) bool { return methodOrder(result[i]) < methodOrder(result[j]) }) return result } func methodOrder(method string) int { switch method { case MethodPlatform: return 1 case MethodEmail: return 2 case MethodSMS: return 3 case MethodPhone: return 4 case MethodWechat: return 5 default: return 99 } } func recipientGroupsForMethod(method string, event Event) [][]string { switch method { case MethodSMS: return singleRecipientGroups(splitList(event.WarnPhones)) case MethodEmail: return singleRecipientGroups(splitList(event.WarnEmails)) default: return [][]string{{}} } } func methodRequiresRecipient(method string) bool { return method == MethodSMS || method == MethodEmail } func singleRecipientGroups(recipients []string) [][]string { groups := make([][]string, 0, len(recipients)) for _, recipient := range recipients { recipient = strings.TrimSpace(recipient) if recipient == "" { continue } groups = append(groups, []string{recipient}) } return groups } func splitList(value string) []string { parts := strings.FieldsFunc(value, func(r rune) bool { switch r { case ',', ';', ',', ';', '|', '\n', '\r', '\t', ' ': return true default: return false } }) seen := make(map[string]struct{}, len(parts)) result := make([]string, 0, len(parts)) for _, part := range parts { part = strings.TrimSpace(part) if part == "" { continue } if _, exists := seen[part]; exists { continue } seen[part] = struct{}{} result = append(result, part) } sort.Strings(result) return result } func highestStage(items []Event) int { stage := 0 for _, item := range items { if item.WarnStage > stage { stage = item.WarnStage } } return stage }