chore: 并入 iotlcm-admin-svr 历史到 admin-svr/(原提交 49818c6)

This commit is contained in:
MiFaZhan
2026-11-02 13:27:59 +08:00
1250 changed files with 154111 additions and 0 deletions
@@ -0,0 +1,371 @@
package warningnotify
import (
"context"
"encoding/json"
"net/http"
"os"
"strconv"
"strings"
"time"
smspb "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/warningnotify/smspb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/google/uuid"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type warningSMSConfig struct {
Enabled bool `json:"enabled"`
URL string `json:"url"`
GRPCAddr string `json:"grpcAddr"`
Timeout string `json:"timeout"`
RouterID int64 `json:"routerId"`
ServiceType string `json:"serviceType"`
RegionID string `json:"regionId"`
AccessKeyID string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
SDKAppID string `json:"sdkAppId"`
SignName string `json:"signName"`
TemplateID string `json:"templateId"`
MsgContentTpl string `json:"msgContentTemplate"`
}
type smsSendConfig struct {
ServiceType string `json:"serviceType,omitempty"`
RegionID string `json:"regionId,omitempty"`
AccessKeyID string `json:"accessKeyId,omitempty"`
AccessKeySecret string `json:"accessKeySecret,omitempty"`
SDKAppID string `json:"sdkAppId,omitempty"`
SignName string `json:"signName,omitempty"`
TemplateID string `json:"templateId,omitempty"`
}
type smsSendParams struct {
PhoneNumber string `json:"phoneNumber"`
MsgContent string `json:"msgContent"`
}
type smsHTTPResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
func sendWarningSMS(ctx context.Context, batch Batch) error {
cfg, ok := loadWarningSMSConfig(ctx)
recipients := normalizeSMSRecipients(batch.Recipients)
if len(recipients) == 0 {
g.Log().Warningf(
ctx,
"WarningNotify SMS skipped, no valid recipients, business:%s, tenantId:%s, deptId:%d, category:%s, stage:%d, count:%d",
batch.Business,
batch.TenantID,
batch.DeptID,
batch.Category,
batch.WarnStage,
len(batch.Items),
)
return nil
}
if batch.SMSContent == "" {
applyContent(&batch)
}
if !ok || !cfg.Enabled || !smsConfigReady(cfg) {
g.Log().Infof(
ctx,
"WarningNotify SMS payload prepared, send skipped, reason:%s, business:%s, tenantId:%s, deptId:%d, category:%s, stage:%d, recipients:%s, count:%d, content:%s",
smsConfigSkipReason(ok, cfg),
batch.Business,
batch.TenantID,
batch.DeptID,
batch.Category,
batch.WarnStage,
strings.Join(recipients, ","),
len(batch.Items),
batch.SMSContent,
)
return nil
}
params, err := buildSMSRequest(batch, recipients, cfg)
if err != nil {
return err
}
if strings.TrimSpace(cfg.GRPCAddr) != "" {
if err = sendWarningSMSGRPC(ctx, cfg, params); err != nil {
return err
}
g.Log().Infof(
ctx,
"WarningNotify SMS sent by grpc, business:%s, tenantId:%s, deptId:%d, category:%s, stage:%d, recipients:%s, count:%d",
batch.Business,
batch.TenantID,
batch.DeptID,
batch.Category,
batch.WarnStage,
strings.Join(recipients, ","),
len(batch.Items),
)
return nil
}
if err = sendWarningSMSHTTP(ctx, cfg, params); err != nil {
return err
}
g.Log().Infof(
ctx,
"WarningNotify SMS sent by http, business:%s, tenantId:%s, deptId:%d, category:%s, stage:%d, recipients:%s, count:%d",
batch.Business,
batch.TenantID,
batch.DeptID,
batch.Category,
batch.WarnStage,
strings.Join(recipients, ","),
len(batch.Items),
)
return nil
}
func loadWarningSMSConfig(ctx context.Context) (warningSMSConfig, bool) {
var cfg warningSMSConfig
loaded := false
v, err := g.Cfg().Get(ctx, "warning.sms")
if err == nil && !v.IsNil() {
_ = v.Scan(&cfg)
loaded = true
}
if applyWarningSMSEnv(&cfg) {
loaded = true
}
cfg.URL = strings.TrimSpace(cfg.URL)
cfg.GRPCAddr = strings.TrimSpace(cfg.GRPCAddr)
cfg.ServiceType = strings.TrimSpace(cfg.ServiceType)
cfg.RegionID = strings.TrimSpace(cfg.RegionID)
cfg.AccessKeyID = strings.TrimSpace(cfg.AccessKeyID)
cfg.AccessKeySecret = strings.TrimSpace(cfg.AccessKeySecret)
cfg.SDKAppID = strings.TrimSpace(cfg.SDKAppID)
cfg.SignName = strings.TrimSpace(cfg.SignName)
cfg.TemplateID = strings.TrimSpace(cfg.TemplateID)
cfg.MsgContentTpl = strings.TrimSpace(cfg.MsgContentTpl)
return cfg, loaded
}
func smsConfigReady(cfg warningSMSConfig) bool {
return len(missingSMSConfigFields(cfg)) == 0
}
func smsConfigSkipReason(loaded bool, cfg warningSMSConfig) string {
if !loaded {
return "warning.sms config not found"
}
if !cfg.Enabled {
return "warning.sms disabled"
}
missing := missingSMSConfigFields(cfg)
if len(missing) > 0 {
return "warning.sms gateway config incomplete, missing: " + strings.Join(missing, ",")
}
return "unknown"
}
func missingSMSConfigFields(cfg warningSMSConfig) []string {
missing := make([]string, 0, 1)
if strings.TrimSpace(cfg.GRPCAddr) == "" && strings.TrimSpace(cfg.URL) == "" {
missing = append(missing, "grpcAddr/url")
}
return missing
}
func applyWarningSMSEnv(cfg *warningSMSConfig) bool {
if cfg == nil {
return false
}
loaded := false
if value := strings.TrimSpace(os.Getenv("IOTLCM_WARNING_SMS_ENABLED")); value != "" {
if enabled, err := strconv.ParseBool(value); err == nil {
cfg.Enabled = enabled
loaded = true
}
}
if value := strings.TrimSpace(os.Getenv("IOTLCM_WARNING_SMS_ROUTER_ID")); value != "" {
if routerID, err := strconv.ParseInt(value, 10, 64); err == nil {
cfg.RouterID = routerID
loaded = true
}
}
loaded = applySMSStringEnv(&cfg.URL, "IOTLCM_WARNING_SMS_URL") || loaded
loaded = applySMSStringEnv(&cfg.GRPCAddr, "IOTLCM_WARNING_SMS_GRPC_ADDR") || loaded
loaded = applySMSStringEnv(&cfg.Timeout, "IOTLCM_WARNING_SMS_TIMEOUT") || loaded
loaded = applySMSStringEnv(&cfg.ServiceType, "IOTLCM_WARNING_SMS_SERVICE_TYPE") || loaded
loaded = applySMSStringEnv(&cfg.RegionID, "IOTLCM_WARNING_SMS_REGION_ID") || loaded
loaded = applySMSStringEnv(&cfg.AccessKeyID, "IOTLCM_WARNING_SMS_ACCESS_KEY_ID") || loaded
loaded = applySMSStringEnv(&cfg.AccessKeySecret, "IOTLCM_WARNING_SMS_ACCESS_KEY_SECRET") || loaded
loaded = applySMSStringEnv(&cfg.SDKAppID, "IOTLCM_WARNING_SMS_SDK_APP_ID") || loaded
loaded = applySMSStringEnv(&cfg.SignName, "IOTLCM_WARNING_SMS_SIGN_NAME") || loaded
loaded = applySMSStringEnv(&cfg.TemplateID, "IOTLCM_WARNING_SMS_TEMPLATE_ID") || loaded
loaded = applySMSStringEnv(&cfg.MsgContentTpl, "IOTLCM_WARNING_SMS_MSG_CONTENT_TEMPLATE") || loaded
return loaded
}
func applySMSStringEnv(target *string, key string) bool {
value := strings.TrimSpace(os.Getenv(key))
if value == "" {
return false
}
*target = value
return true
}
func buildSMSRequest(batch Batch, recipients []string, cfg warningSMSConfig) (smsSendParams, error) {
content := strings.TrimSpace(batch.SMSContent)
if strings.TrimSpace(content) == "" {
return smsSendParams{}, gerror.New("warning SMS content is empty")
}
msgContent := renderSMSMsgContent(cfg.MsgContentTpl, content, batch)
return smsSendParams{
PhoneNumber: strings.Join(recipients, ","),
MsgContent: msgContent,
}, nil
}
func renderSMSMsgContent(template string, content string, batch Batch) string {
template = strings.TrimSpace(template)
if template == "" {
return content
}
contentJSON, _ := json.Marshal(content)
replacer := strings.NewReplacer(
"{content}", content,
"{contentJson}", string(contentJSON),
"{category}", batch.Category,
"{stage}", strconv.Itoa(batch.WarnStage),
"{count}", strconv.Itoa(len(batch.Items)),
"{firstItem}", firstSMSItemCode(batch),
)
return replacer.Replace(template)
}
func firstSMSItemCode(batch Batch) string {
if len(batch.Items) == 0 {
return ""
}
return strings.TrimSpace(batch.Items[0].ItemCode)
}
func buildSMSProviderConfig(cfg warningSMSConfig) smsSendConfig {
return smsSendConfig{
ServiceType: strings.TrimSpace(cfg.ServiceType),
RegionID: strings.TrimSpace(cfg.RegionID),
AccessKeyID: strings.TrimSpace(cfg.AccessKeyID),
AccessKeySecret: strings.TrimSpace(cfg.AccessKeySecret),
SDKAppID: strings.TrimSpace(cfg.SDKAppID),
SignName: strings.TrimSpace(cfg.SignName),
TemplateID: strings.TrimSpace(cfg.TemplateID),
}
}
func sendWarningSMSGRPC(ctx context.Context, cfg warningSMSConfig, params smsSendParams) error {
timeout := warningSMSTimeout(cfg)
callCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
conn, err := grpc.DialContext(
callCtx,
cfg.GRPCAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
)
if err != nil {
return gerror.Wrap(err, "connect warning SMS grpc failed")
}
defer conn.Close()
paramsBytes, err := json.Marshal(params)
if err != nil {
return err
}
configBytes, err := json.Marshal(buildSMSProviderConfig(cfg))
if err != nil {
return err
}
traceID := "warning-sms-" + uuid.NewString()
res, err := smspb.NewNoticeClient(conn).Send(callCtx, &smspb.SendReq{
Head: &smspb.HeadReq{
RouterId: cfg.RouterID,
TraceId: traceID,
},
Config: configBytes,
Params: paramsBytes,
})
if err != nil {
return gerror.Wrap(err, "send warning SMS grpc failed")
}
if res == nil || res.GetHead() == nil {
return gerror.New("send warning SMS grpc failed, empty response")
}
if res.GetHead().GetCode() != 0 {
return gerror.Newf("send warning SMS grpc failed, code:%d, message:%s", res.GetHead().GetCode(), res.GetHead().GetMessage())
}
return nil
}
func sendWarningSMSHTTP(ctx context.Context, cfg warningSMSConfig, params smsSendParams) error {
bodyBytes, err := json.Marshal(g.Map{
"config": buildSMSProviderConfig(cfg),
"params": params,
})
if err != nil {
return err
}
timeout := warningSMSTimeout(cfg)
client := g.Client().Timeout(timeout).SetHeader("Content-Type", "application/json")
resp, err := client.Post(ctx, cfg.URL, string(bodyBytes))
if err != nil {
return gerror.Wrap(err, "send warning SMS http failed")
}
defer resp.Close()
if resp.StatusCode != http.StatusOK {
return gerror.Newf("send warning SMS http failed, status code:%d", resp.StatusCode)
}
var result smsHTTPResponse
if err = json.Unmarshal(resp.ReadAll(), &result); err != nil {
return gerror.Wrap(err, "parse warning SMS http response failed")
}
if result.Code != 0 {
return gerror.Newf("send warning SMS http failed, code:%d, message:%s", result.Code, result.Message)
}
return nil
}
func normalizeSMSRecipients(values []string) []string {
seen := make(map[string]struct{}, len(values))
recipients := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" {
continue
}
if _, exists := seen[value]; exists {
continue
}
seen[value] = struct{}{}
recipients = append(recipients, value)
}
return recipients
}
func warningSMSTimeout(cfg warningSMSConfig) time.Duration {
if cfg.Timeout != "" {
if duration, err := time.ParseDuration(cfg.Timeout); err == nil && duration > 0 {
return duration
}
}
return 5 * time.Second
}