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:
@@ -0,0 +1,379 @@
|
||||
package simwarning
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
func evaluateConfig(cfg warnConfig, card entity.TSimCardInfo, runTime time.Time) (*warningHit, string) {
|
||||
cfg.WarnType = normalizeWarnType(cfg.ItemType, cfg.WarnUnit)
|
||||
switch cfg.WarnType {
|
||||
case warnTypeCardTraffic:
|
||||
return evaluateTraffic(cfg, card, runTime)
|
||||
default:
|
||||
return nil, "unsupported warn type"
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateTraffic(cfg warnConfig, card entity.TSimCardInfo, runTime time.Time) (*warningHit, string) {
|
||||
cfg.WarnType = normalizeWarnType(warnItemTypeCard, cfg.WarnUnit)
|
||||
if cfg.WarnType != warnTypeCardTraffic {
|
||||
return nil, "unsupported traffic warn type"
|
||||
}
|
||||
if cfg.WarnUnit != warnUnitMb && cfg.WarnUnit != warnUnitPercent {
|
||||
return nil, "unsupported traffic warn unit"
|
||||
}
|
||||
if reason := validCommonCard(card); reason != "" {
|
||||
return nil, reason
|
||||
}
|
||||
if card.PackageTotalFlowMb <= 0 {
|
||||
return nil, "package total flow is empty"
|
||||
}
|
||||
|
||||
usedFlowMb := card.UsedFlowMb
|
||||
if usedFlowMb < 0 {
|
||||
usedFlowMb = 0
|
||||
}
|
||||
totalFlowMb := card.PackageTotalFlowMb
|
||||
remainFlowMb := totalFlowMb - usedFlowMb
|
||||
if remainFlowMb < 0 {
|
||||
remainFlowMb = 0
|
||||
}
|
||||
usedPercent := usedFlowMb / totalFlowMb * 100
|
||||
|
||||
var (
|
||||
stage int
|
||||
threshold float64
|
||||
actual float64
|
||||
kind string
|
||||
)
|
||||
if cfg.WarnUnit == warnUnitPercent {
|
||||
first := normalizePercentThreshold(cfg.FirstWarn)
|
||||
second := normalizePercentThreshold(cfg.SecondWarn)
|
||||
third := normalizePercentThreshold(cfg.ThirdWarn)
|
||||
stage, threshold = matchGreaterStage(usedPercent, first, second, third)
|
||||
actual = usedPercent
|
||||
kind = warnKindTrafficPercent
|
||||
} else {
|
||||
stage, threshold = matchGreaterStage(usedFlowMb, cfg.FirstWarn, cfg.SecondWarn, cfg.ThirdWarn)
|
||||
actual = usedFlowMb
|
||||
kind = warnKindTrafficMb
|
||||
}
|
||||
if stage == 0 {
|
||||
return nil, "traffic threshold not reached"
|
||||
}
|
||||
|
||||
level := warnLevelByStage(stage)
|
||||
if cfg.WarnUnit == warnUnitPercent && usedPercent >= 100 {
|
||||
level = warnLevelUrgent
|
||||
}
|
||||
remark := trafficRemark(cfg.WarnUnit, stage, threshold, usedFlowMb, usedPercent, totalFlowMb, remainFlowMb)
|
||||
|
||||
hit := baseHit(cfg, card, kind)
|
||||
hit.WarnStage = stage
|
||||
hit.WarnLevel = level
|
||||
hit.ThresholdValue = threshold
|
||||
hit.ActualValue = round(actual, 3)
|
||||
hit.TotalFlowMb = round(totalFlowMb, 3)
|
||||
hit.RemainFlowMb = round(remainFlowMb, 3)
|
||||
hit.Remark = remark
|
||||
setTrafficDedupeWindow(&hit, runTime)
|
||||
return &hit, ""
|
||||
}
|
||||
|
||||
func evaluatePoolTrafficPercent(cfg warnConfig, pool entity.TSimFlowPool, runTime time.Time) (*warningHit, string) {
|
||||
cfg.WarnType = normalizeWarnType(warnItemTypeFlowPool, cfg.WarnUnit)
|
||||
if cfg.WarnType != warnTypeFlowPoolPercent {
|
||||
return nil, "unsupported flow pool warn type"
|
||||
}
|
||||
if cfg.WarnUnit != warnUnitPercent {
|
||||
return nil, "flow pool only supports percent warning"
|
||||
}
|
||||
if pool.PoolStatus != 0 {
|
||||
return nil, "flow pool is not normal"
|
||||
}
|
||||
if pool.TotalFlowMb <= 0 {
|
||||
return nil, "flow pool total flow is empty"
|
||||
}
|
||||
|
||||
usedFlowMb := pool.UsedFlowMb
|
||||
if usedFlowMb < 0 {
|
||||
usedFlowMb = 0
|
||||
}
|
||||
totalFlowMb := pool.TotalFlowMb
|
||||
remainFlowMb := totalFlowMb - usedFlowMb
|
||||
if remainFlowMb < 0 {
|
||||
remainFlowMb = 0
|
||||
}
|
||||
usedPercent := usedFlowMb / totalFlowMb * 100
|
||||
|
||||
first := normalizePercentThreshold(cfg.FirstWarn)
|
||||
second := normalizePercentThreshold(cfg.SecondWarn)
|
||||
third := normalizePercentThreshold(cfg.ThirdWarn)
|
||||
stage, threshold := matchGreaterStage(usedPercent, first, second, third)
|
||||
if stage == 0 {
|
||||
return nil, "flow pool traffic threshold not reached"
|
||||
}
|
||||
|
||||
level := warnLevelByStage(stage)
|
||||
if usedPercent >= 100 {
|
||||
level = warnLevelUrgent
|
||||
}
|
||||
remark := fmt.Sprintf(
|
||||
"流量池流量已达到第%d档阈值,阈值:%.2f%%,实际使用率:%.2f%%,实际已用:%.3f MB,剩余:%.3f MB,总量:%.3f MB",
|
||||
stage,
|
||||
threshold,
|
||||
usedPercent,
|
||||
usedFlowMb,
|
||||
remainFlowMb,
|
||||
totalFlowMb,
|
||||
)
|
||||
if usedPercent >= 100 {
|
||||
remark += ",已超出流量池总量"
|
||||
}
|
||||
|
||||
hit := basePoolHit(cfg, pool, warnKindPoolTrafficPercent)
|
||||
hit.WarnStage = stage
|
||||
hit.WarnLevel = level
|
||||
hit.ThresholdValue = threshold
|
||||
hit.ActualValue = round(usedPercent, 3)
|
||||
hit.TotalFlowMb = round(totalFlowMb, 3)
|
||||
hit.RemainFlowMb = round(remainFlowMb, 3)
|
||||
hit.Remark = remark
|
||||
setTrafficDedupeWindow(&hit, runTime)
|
||||
return &hit, ""
|
||||
}
|
||||
|
||||
func evaluateSystemExpireDay(cfg warnConfig, card entity.TSimCardInfo, runTime time.Time) (*warningHit, string) {
|
||||
cfg.WarnType = normalizeWarnType(warnItemTypeCard, cfg.WarnUnit)
|
||||
if cfg.WarnType != warnTypeExpireDay {
|
||||
return nil, "unsupported expire day warn type"
|
||||
}
|
||||
if cfg.WarnUnit != warnUnitDay {
|
||||
return nil, "unsupported expire day warn unit"
|
||||
}
|
||||
if reason := validCommonCard(card); reason != "" {
|
||||
return nil, reason
|
||||
}
|
||||
if card.FlowPkgEndAt == nil || card.FlowPkgEndAt.IsZero() {
|
||||
return nil, "package end time is empty"
|
||||
}
|
||||
|
||||
endTime := card.FlowPkgEndAt.Time
|
||||
remainDays := diffDateDays(runTime, endTime)
|
||||
if remainDays < 0 {
|
||||
return nil, "package expired"
|
||||
}
|
||||
|
||||
stage, threshold := matchLessStage(float64(remainDays), cfg.FirstWarn, cfg.SecondWarn, cfg.ThirdWarn)
|
||||
if stage == 0 {
|
||||
return nil, "expire day threshold not reached"
|
||||
}
|
||||
|
||||
hit := baseHit(cfg, card, warnKindExpireDay)
|
||||
hit.WarnStage = stage
|
||||
hit.WarnLevel = warnLevelByStage(stage)
|
||||
hit.ThresholdValue = threshold
|
||||
hit.ActualValue = float64(remainDays)
|
||||
hit.TotalFlowMb = 0
|
||||
hit.RemainFlowMb = 0
|
||||
hit.Remark = fmt.Sprintf("套餐将在 %d 天后到期", remainDays)
|
||||
setExpireDayDedupeWindow(&hit, runTime)
|
||||
return &hit, ""
|
||||
}
|
||||
|
||||
func validCommonCard(card entity.TSimCardInfo) string {
|
||||
if card.PackageStatus != consts.PackageStatusEffective {
|
||||
return "package is not effective"
|
||||
}
|
||||
if card.IsFlowPool != 0 {
|
||||
return "flow pool card is not supported"
|
||||
}
|
||||
if card.CurrentPackageCode == "" {
|
||||
return "current package code is empty"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func baseHit(cfg warnConfig, card entity.TSimCardInfo, kind string) warningHit {
|
||||
hit := warningHit{
|
||||
Config: cfg,
|
||||
Card: card,
|
||||
Kind: kind,
|
||||
}
|
||||
if card.FlowPkgBeginAt != nil && !card.FlowPkgBeginAt.IsZero() {
|
||||
hit.PeriodBegin = card.FlowPkgBeginAt.Time
|
||||
}
|
||||
if card.FlowPkgEndAt != nil && !card.FlowPkgEndAt.IsZero() {
|
||||
hit.PeriodEnd = card.FlowPkgEndAt.Time
|
||||
}
|
||||
if hit.PeriodBegin.IsZero() && !hit.PeriodEnd.IsZero() {
|
||||
hit.PeriodBegin = warnPeriodFallbackBegin(hit.PeriodEnd.Location())
|
||||
}
|
||||
return hit
|
||||
}
|
||||
|
||||
func basePoolHit(cfg warnConfig, pool entity.TSimFlowPool, kind string) warningHit {
|
||||
return warningHit{
|
||||
Config: cfg,
|
||||
Pool: pool,
|
||||
Kind: kind,
|
||||
}
|
||||
}
|
||||
|
||||
func warnLevelByStage(stage int) int {
|
||||
switch stage {
|
||||
case warnStageFirst:
|
||||
return warnLevelNotice
|
||||
case warnStageSecond:
|
||||
return warnLevelWarning
|
||||
case warnStageThird:
|
||||
return warnLevelDanger
|
||||
default:
|
||||
return warnLevelNotice
|
||||
}
|
||||
}
|
||||
|
||||
func trafficRemark(warnUnit, stage int, threshold, usedFlowMb, usedPercent, totalFlowMb, remainFlowMb float64) string {
|
||||
if warnUnit == warnUnitPercent {
|
||||
remark := fmt.Sprintf(
|
||||
"单卡流量已达到第%d档阈值,阈值:%.2f%%,实际使用率:%.2f%%,实际已用:%.3f MB,剩余:%.3f MB,总量:%.3f MB",
|
||||
stage,
|
||||
threshold,
|
||||
usedPercent,
|
||||
usedFlowMb,
|
||||
remainFlowMb,
|
||||
totalFlowMb,
|
||||
)
|
||||
if usedPercent >= 100 {
|
||||
return remark + ",已超出套餐流量"
|
||||
}
|
||||
return remark
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"单卡流量已达到第%d档阈值,阈值:%.3f MB,实际已用:%.3f MB,剩余:%.3f MB,总量:%.3f MB",
|
||||
stage,
|
||||
threshold,
|
||||
usedFlowMb,
|
||||
remainFlowMb,
|
||||
totalFlowMb,
|
||||
)
|
||||
}
|
||||
|
||||
func matchGreaterStage(actual, first, second, third float64) (int, float64) {
|
||||
if third > 0 && actual >= third {
|
||||
return warnStageThird, third
|
||||
}
|
||||
if second > 0 && actual >= second {
|
||||
return warnStageSecond, second
|
||||
}
|
||||
if first > 0 && actual >= first {
|
||||
return warnStageFirst, first
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func matchLessStage(actual, first, second, third float64) (int, float64) {
|
||||
if third > 0 && actual <= third {
|
||||
return warnStageThird, third
|
||||
}
|
||||
if second > 0 && actual <= second {
|
||||
return warnStageSecond, second
|
||||
}
|
||||
if first > 0 && actual <= first {
|
||||
return warnStageFirst, first
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func normalizePercentThreshold(value float64) float64 {
|
||||
if value > 0 && value <= 1 {
|
||||
return value * 100
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func diffDateDays(runTime, endTime time.Time) int {
|
||||
location := runTime.Location()
|
||||
runDate := time.Date(runTime.In(location).Year(), runTime.In(location).Month(), runTime.In(location).Day(), 0, 0, 0, 0, location)
|
||||
endLocal := endTime.In(location)
|
||||
endDate := time.Date(endLocal.Year(), endLocal.Month(), endLocal.Day(), 0, 0, 0, 0, location)
|
||||
return int(endDate.Sub(runDate).Hours() / 24)
|
||||
}
|
||||
|
||||
func round(value float64, places int) float64 {
|
||||
pow := math.Pow10(places)
|
||||
return math.Round(value*pow) / pow
|
||||
}
|
||||
|
||||
func warnPeriodFallbackBegin(location *time.Location) time.Time {
|
||||
if location == nil {
|
||||
location = time.Local
|
||||
}
|
||||
return time.Date(1970, 1, 1, 0, 0, 0, 0, location)
|
||||
}
|
||||
|
||||
func setTrafficDedupeWindow(hit *warningHit, runTime time.Time) {
|
||||
monthBegin, monthEnd := calendarMonthWindow(runTime)
|
||||
hit.WindowBegin = monthBegin
|
||||
hit.WindowEnd = monthEnd
|
||||
hit.DedupeExpireAt = monthEnd
|
||||
}
|
||||
|
||||
func calendarMonthWindow(runTime time.Time) (time.Time, time.Time) {
|
||||
if runTime.IsZero() {
|
||||
runTime = time.Now()
|
||||
}
|
||||
location := runTime.Location()
|
||||
local := runTime.In(location)
|
||||
begin := time.Date(local.Year(), local.Month(), 1, 0, 0, 0, 0, location)
|
||||
return begin, begin.AddDate(0, 1, 0)
|
||||
}
|
||||
|
||||
func setExpireDayDedupeWindow(hit *warningHit, runTime time.Time) {
|
||||
if hit.PeriodEnd.IsZero() {
|
||||
return
|
||||
}
|
||||
stageBegin := expireDayStageBegin(runTime, hit.PeriodEnd, hit.ThresholdValue)
|
||||
stageEnd := expireDayStageEnd(runTime, *hit)
|
||||
if !stageEnd.After(stageBegin) {
|
||||
stageEnd = stageBegin.Add(24 * time.Hour)
|
||||
}
|
||||
hit.WindowBegin = stageBegin
|
||||
hit.WindowEnd = stageEnd
|
||||
hit.DedupeExpireAt = stageEnd
|
||||
}
|
||||
|
||||
func expireDayStageBegin(runTime, endTime time.Time, threshold float64) time.Time {
|
||||
location := runTime.Location()
|
||||
endLocal := endTime.In(location)
|
||||
endDate := time.Date(endLocal.Year(), endLocal.Month(), endLocal.Day(), 0, 0, 0, 0, location)
|
||||
return endDate.AddDate(0, 0, -int(threshold))
|
||||
}
|
||||
|
||||
func expireDayStageEnd(runTime time.Time, hit warningHit) time.Time {
|
||||
if next, ok := nextExpireThreshold(hit); ok {
|
||||
return expireDayStageBegin(runTime, hit.PeriodEnd, next)
|
||||
}
|
||||
location := runTime.Location()
|
||||
endLocal := hit.PeriodEnd.In(location)
|
||||
endDate := time.Date(endLocal.Year(), endLocal.Month(), endLocal.Day(), 0, 0, 0, 0, location)
|
||||
return endDate.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
func nextExpireThreshold(hit warningHit) (float64, bool) {
|
||||
switch hit.WarnStage {
|
||||
case warnStageFirst:
|
||||
if hit.Config.SecondWarn > 0 {
|
||||
return hit.Config.SecondWarn, true
|
||||
}
|
||||
case warnStageSecond:
|
||||
if hit.Config.ThirdWarn > 0 {
|
||||
return hit.Config.ThirdWarn, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
Reference in New Issue
Block a user