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,429 @@
package warningnotify
import (
"fmt"
"html"
"strings"
"time"
)
const warningEmailHTMLSignature = `data-warning-email="iotlcm"`
func applyContent(batch *Batch) {
if batch == nil {
return
}
batch.SMSContent = buildSMSContent(*batch)
batch.EmailSubject = buildEmailSubject(*batch)
batch.EmailBody = buildEmailBody(*batch)
batch.PlatformTitle = buildPlatformTitle(*batch)
batch.PlatformContent = buildPlatformContent(*batch)
}
func buildSMSContent(batch Batch) string {
count := len(batch.Items)
if count == 0 {
return ""
}
summary := batchObjectSummary(batch)
title := warningTitle(batch)
action := ""
if hasAffectOperation(batch.Items) {
action = ",涉及停机或断网操作"
}
return fmt.Sprintf(
"【物联网】%s触发%s%s,请登录控制台查看。",
summary,
title,
action,
)
}
func buildEmailSubject(batch Batch) string {
count := len(batch.Items)
if count == 0 {
return "物联网预警提醒"
}
return fmt.Sprintf("%s(%d条)", warningTitle(batch), count)
}
func buildEmailBody(batch Batch) string {
count := len(batch.Items)
if count == 0 {
return ""
}
var builder strings.Builder
builder.WriteString(`<div ` + warningEmailHTMLSignature + ` style="font-family:Arial,'Microsoft YaHei',sans-serif;font-size:14px;line-height:1.6;color:#1f2937;">`)
fmt.Fprintf(&builder, `<p style="margin:0 0 12px;">%s,共 %d 条,最高级别:%s。</p>`, htmlEscape(warningTitle(batch)), count, htmlEscape(levelText(highestLevel(batch.Items))))
if hasAffectOperation(batch.Items) {
builder.WriteString(`<p style="margin:0 0 12px;color:#b42318;">本次预警包含停机或断网类操作,请重点确认。</p>`)
}
builder.WriteString(`<table style="width:100%;border-collapse:collapse;border:1px solid #d0d5dd;">`)
builder.WriteString(`<thead><tr style="background:#f2f4f7;">`)
builder.WriteString(emailHeaderCell(emailObjectHeader(batch)))
builder.WriteString(emailHeaderCell("预警类型"))
builder.WriteString(emailHeaderCell("预警原因"))
builder.WriteString(emailHeaderCell("预警时间"))
builder.WriteString(`</tr></thead><tbody>`)
for _, item := range batch.Items {
builder.WriteString(`<tr>`)
builder.WriteString(emailBodyCell(displayObjectCode(item)))
builder.WriteString(emailBodyCell(emailWarningType(item, batch)))
builder.WriteString(emailBodyCell(emailWarningReason(item)))
builder.WriteString(emailBodyCell(emailWarnTime(item)))
builder.WriteString(`</tr>`)
}
builder.WriteString(`</tbody></table></div>`)
return strings.TrimSpace(builder.String())
}
func buildPlatformTitle(batch Batch) string {
if len(batch.Items) == 0 {
return "预警提醒"
}
if title := expireDayPlatformTitle(batch); title != "" {
return title
}
return warningTitle(batch)
}
func buildPlatformContent(batch Batch) string {
count := len(batch.Items)
if count == 0 {
return ""
}
content := buildPlatformSummaryContent(batch)
if hasAffectOperation(batch.Items) {
content += "本次提醒包含停机或断网类操作,请及时确认。"
}
return content
}
func buildPlatformSummaryContent(batch Batch) string {
if isExpireDayBatch(batch) {
return fmt.Sprintf(
"%s有%d张卡临近到期,最高档位:%s,请登录控制台查看。",
firstObjectCode(batch),
len(batch.Items),
buildPlatformTitle(batch),
)
}
return fmt.Sprintf(
"%s触发%s,最高级别:%s,请登录控制台查看。",
batchObjectSummary(batch),
buildPlatformTitle(batch),
levelText(highestLevel(batch.Items)),
)
}
func warningTitle(batch Batch) string {
category := warningCategory(batch)
return categoryStageTitle(category, batch.WarnStage)
}
func warningCategory(batch Batch) string {
category := strings.TrimSpace(batch.Category)
if category == "" {
category = "预警"
}
return category
}
func categoryStageTitle(category string, stage int) string {
category = strings.TrimSpace(category)
if category == "" {
category = "预警"
}
if stage > 0 {
return category + stageText(stage)
}
if strings.HasSuffix(category, "提醒") {
return category
}
return category + "提醒"
}
func expireDayPlatformTitle(batch Batch) string {
if !isExpireDayBatch(batch) {
return ""
}
threshold := expireDayPlatformThreshold(batch)
if threshold <= 0 {
return "距离到期时间预警"
}
return fmt.Sprintf("距离到期时间小于%.0f天", threshold)
}
func isExpireDayBatch(batch Batch) bool {
category := strings.TrimSpace(batch.Category)
for _, item := range batch.Items {
if strings.TrimSpace(item.Kind) == "expire_day" {
return true
}
if strings.Contains(category, "到期") && strings.TrimSpace(item.UnitText) == "天" {
return true
}
}
return false
}
func expireDayPlatformThreshold(batch Batch) float64 {
for _, item := range batch.Items {
if item.WarnStage == batch.WarnStage && item.Threshold > 0 {
return item.Threshold
}
}
for _, item := range batch.Items {
if item.Threshold > 0 {
return item.Threshold
}
}
switch batch.WarnStage {
case 1:
return 30
case 2:
return 15
case 3:
return 7
default:
return 0
}
}
func itemDetailSummary(item Event) string {
return itemSummary(item)
}
func batchObjectSummary(batch Batch) string {
count := len(batch.Items)
if count == 0 {
return ""
}
code := firstObjectCode(batch)
if count == 1 {
return code
}
return fmt.Sprintf("%s等%d%s", code, count, objectCountUnit(batch.Items[0]))
}
func firstObjectCode(batch Batch) string {
if len(batch.Items) == 0 {
return "--"
}
return displayObjectCode(batch.Items[0])
}
func objectCountUnit(item Event) string {
switch item.ItemType {
case 2:
return "个流量池"
default:
return "张卡"
}
}
func metricSummary(item Event) string {
unit := strings.TrimSpace(item.UnitText)
switch unit {
case "天", "月":
parts := []string{
"剩余" + formatMetric(item.Actual, unit),
}
if item.Threshold > 0 {
parts = append(parts, "阈值"+formatMetric(item.Threshold, unit))
}
return strings.Join(parts, ",")
case "%":
parts := []string{
"阈值" + formatMetric(item.Threshold, unit),
"实际" + formatMetric(item.Actual, unit),
}
if shouldShowRemain(item) {
parts = append(parts, "剩余"+remainMetric(item))
}
if item.TotalFlowMB > 0 {
parts = append(parts, "总量"+formatMetric(item.TotalFlowMB, "MB"))
}
return strings.Join(parts, ",")
default:
parts := []string{
"阈值" + formatMetric(item.Threshold, item.UnitText),
"实际" + formatMetric(item.Actual, item.UnitText),
}
if shouldShowRemain(item) {
parts = append(parts, "剩余"+remainMetric(item))
}
if item.TotalFlowMB > 0 {
parts = append(parts, "总量"+formatMetric(item.TotalFlowMB, "MB"))
}
return strings.Join(parts, ",")
}
}
func itemSummary(item Event) string {
parts := make([]string, 0, 1)
parts = append(parts, fmt.Sprintf("%s:%s", itemLabel(item), displayObjectCode(item)))
return strings.Join(parts, ",")
}
func itemLabel(item Event) string {
switch item.ItemType {
case 2:
return "流量池编号"
default:
return "ICCID"
}
}
func displayObjectCode(item Event) string {
itemCode := strings.TrimSpace(item.ItemCode)
if itemCode == "" {
return "--"
}
return itemCode
}
func stageText(stage int) string {
if stage <= 0 {
return ""
}
return fmt.Sprintf("第%d次提醒", stage)
}
func levelText(level int) string {
switch level {
case 1:
return "提示"
case 2:
return "注意"
case 3:
return "警告"
case 4:
return "危险"
case 5:
return "紧急"
default:
return "提醒"
}
}
func highestLevel(items []Event) int {
level := 0
for _, item := range items {
if item.WarnLevel > level {
level = item.WarnLevel
}
}
return level
}
func hasAffectOperation(items []Event) bool {
for _, item := range items {
if item.AffectOperation {
return true
}
}
return false
}
func shouldShowRemain(item Event) bool {
if item.TotalFlowMB > 0 {
return true
}
unit := strings.TrimSpace(item.UnitText)
return unit == "MB" || unit == "%"
}
func remainMetric(item Event) string {
if !shouldShowRemain(item) {
return "--"
}
return formatMetric(item.RemainFlowMB, "MB")
}
func formatMetric(value float64, unit string) string {
unit = strings.TrimSpace(unit)
switch unit {
case "%":
return fmt.Sprintf("%.2f%%", value)
case "天", "月":
return fmt.Sprintf("%.0f%s", value, unit)
case "MB":
return fmt.Sprintf("%.3f MB", value)
default:
if unit == "" {
return fmt.Sprintf("%.3f", value)
}
return fmt.Sprintf("%.3f %s", value, unit)
}
}
func formatTime(value time.Time) string {
return value.Format("2006-01-02 15:04:05")
}
func emailObjectHeader(batch Batch) string {
if len(batch.Items) == 0 {
return "ICCID"
}
for _, item := range batch.Items {
if item.ItemType != 2 {
return "ICCID"
}
}
return "流量池编号"
}
func emailWarningType(item Event, batch Batch) string {
category := strings.TrimSpace(item.Category)
if category != "" {
return category
}
return warningCategory(batch)
}
func emailWarningReason(item Event) string {
parts := make([]string, 0, 3)
if item.WarnStage > 0 {
parts = append(parts, stageText(item.WarnStage))
}
if summary := metricSummary(item); summary != "" {
parts = append(parts, summary)
}
if item.OperationText != "" {
parts = append(parts, item.OperationText)
}
if len(parts) == 0 && item.Remark != "" {
parts = append(parts, item.Remark)
}
return strings.Join(parts, ",")
}
func emailWarnTime(item Event) string {
if item.WarnTime.IsZero() {
return "--"
}
return formatTime(item.WarnTime)
}
func emailHeaderCell(value string) string {
return `<th style="padding:8px 10px;border:1px solid #d0d5dd;text-align:left;font-weight:600;color:#344054;">` + htmlEscape(value) + `</th>`
}
func emailBodyCell(value string) string {
return `<td style="padding:8px 10px;border:1px solid #d0d5dd;vertical-align:top;color:#1f2937;">` + htmlEscape(value) + `</td>`
}
func htmlEscape(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return "--"
}
return html.EscapeString(value)
}