Files
iotlcm/server/internal/logic/warningnotify/aggregate.go
T
MiFaZhan 59eb34976a chore: 初始开源发布
iotlcm-admin-svr:管理后台服务。

- Go module:iotlcm → git.5iot.com/iotlcm-public/iotlcm-admin-svr(729 文件)
- 凭据与地址脱敏:JWT signingKey(含代码里的 MustGet 默认值)、
  MySQL DSN、Redis 口令、告警邮件发件人邮箱全部改为占位;
  私网 IP → 文档地址段;库名 xiujie_admin → iotlcm
- 真实手机号/邮箱 → 合法 11 位编造号 / example.com
- 移除内部资产:CI 触发器、过时 DDL(sql/db_iotlcm.sql)、
  私有依赖 go get 行、明文 registry 登录口令
- 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名
- Dockerfile 改用公共基础镜像
- 拼错的 RADEME.md → README.md,README 重写
- 需要真实数据库的用例改为可用 IOTLCM_TEST_WITH_DB=1 显式启用
- 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes

数据库 schema 不随本仓发布(生产 DDL 已过时),README 已说明。
2026-10-23 14:48:30 +08:00

257 lines
6.2 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 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)
}
if event.AppNotifyEnabled {
result = append(result, MethodWechat)
}
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
}