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 已说明。
This commit is contained in:
MiFaZhan
2026-10-23 14:46:05 +08:00
commit 59eb34976a
1132 changed files with 138197 additions and 0 deletions
@@ -0,0 +1,256 @@
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
}