Files
iotlcm/server/internal/logic/warningnotify/content.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

430 lines
9.9 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 (
"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)
}