chore: 并入 iotlcm-admin-svr 历史到 admin-svr/(原提交 49818c6)
This commit is contained in:
@@ -0,0 +1,385 @@
|
||||
package warningconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"mime"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
warnMethodSMS = "0"
|
||||
warnMethodEmail = "1"
|
||||
warnMethodPhone = "2"
|
||||
warnMethodPlatform = "3"
|
||||
warnEmailCodeTTLSeconds = 300
|
||||
warnEmailCodeRedisKeyScope = "iotlcm:warning:email-code"
|
||||
)
|
||||
|
||||
func SendEmailCode(ctx context.Context, in *model.TSimWarningConfigEmailCodeParam) error {
|
||||
if in == nil {
|
||||
return gerror.New("邮箱验证码参数不能为空")
|
||||
}
|
||||
email, err := normalizeSingleWarnEmail(in.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(in.CaptchaID) == "" || strings.TrimSpace(in.CaptchaValue) == "" {
|
||||
return gerror.New("图片验证码不能为空")
|
||||
}
|
||||
if err = service.SysCaptcha().VerifyCaptcha(ctx, in.CaptchaID, in.CaptchaValue); err != nil {
|
||||
return err
|
||||
}
|
||||
userID := contexts.GetUserId(ctx)
|
||||
tenantID := contexts.GetTenantId(ctx)
|
||||
if userID <= 0 || strings.TrimSpace(tenantID) == "" {
|
||||
return gerror.New("登录信息已过期")
|
||||
}
|
||||
code, err := generateWarnEmailCode()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "生成邮箱验证码失败")
|
||||
}
|
||||
if err = setWarnEmailCode(ctx, tenantID, userID, email, code); err != nil {
|
||||
return gerror.Wrap(err, "保存邮箱验证码失败,请稍后重试")
|
||||
}
|
||||
return sendWarnEmailCode(ctx, email, code)
|
||||
}
|
||||
|
||||
func ValidateEmailCode(ctx context.Context, options Options) error {
|
||||
return validateEmailCode(ctx, options, false)
|
||||
}
|
||||
|
||||
func ConsumeEmailCode(ctx context.Context, options Options) error {
|
||||
return validateEmailCode(ctx, options, true)
|
||||
}
|
||||
|
||||
func validateEmailCode(ctx context.Context, options Options, consume bool) error {
|
||||
if !hasEmailMethod(options.WarnMethods) {
|
||||
return nil
|
||||
}
|
||||
email, err := normalizeSingleWarnEmail(options.WarnEmails)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
code := strings.TrimSpace(options.WarnEmailCode)
|
||||
if code == "" {
|
||||
return gerror.New("请选择邮箱提醒时必须填写邮箱验证码")
|
||||
}
|
||||
userID := contexts.GetUserId(ctx)
|
||||
tenantID := contexts.GetTenantId(ctx)
|
||||
if userID <= 0 || strings.TrimSpace(tenantID) == "" {
|
||||
return gerror.New("登录信息已过期")
|
||||
}
|
||||
cacheCode, err := getWarnEmailCode(ctx, tenantID, userID, email)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "获取邮箱验证码失败,请稍后重试")
|
||||
}
|
||||
if cacheCode == "" {
|
||||
return gerror.New("邮箱验证码已失效")
|
||||
}
|
||||
if cacheCode != code {
|
||||
return gerror.New("邮箱验证码错误")
|
||||
}
|
||||
if consume {
|
||||
return deleteWarnEmailCode(ctx, tenantID, userID, email)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNotificationOptions(options *Options) error {
|
||||
if options == nil {
|
||||
return gerror.New("预警配置参数不能为空")
|
||||
}
|
||||
if hasSMSMethod(options.WarnMethods) {
|
||||
phones, err := normalizeWarnPhones(options.WarnPhones)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.WarnPhones = phones
|
||||
}
|
||||
if hasEmailMethod(options.WarnMethods) {
|
||||
emails, err := normalizeWarnEmails(options.WarnEmails)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.WarnEmails = emails
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasSMSMethod(methods string) bool {
|
||||
for _, method := range splitWarnList(methods) {
|
||||
if method == warnMethodSMS {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasEmailMethod(methods string) bool {
|
||||
for _, method := range splitWarnList(methods) {
|
||||
if method == warnMethodEmail {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeSingleWarnEmail(value string) (string, error) {
|
||||
emails := splitWarnList(value)
|
||||
if len(emails) == 0 {
|
||||
return "", gerror.New("请选择邮箱提醒时必须填写邮箱地址")
|
||||
}
|
||||
if len(emails) > 1 {
|
||||
return "", gerror.New("邮箱提醒验证码第一版仅支持一个邮箱地址")
|
||||
}
|
||||
address, err := mail.ParseAddress(emails[0])
|
||||
if err != nil || address == nil || strings.TrimSpace(address.Address) == "" {
|
||||
return "", gerror.New("邮箱格式不正确")
|
||||
}
|
||||
return strings.ToLower(strings.TrimSpace(address.Address)), nil
|
||||
}
|
||||
|
||||
func normalizeWarnEmails(value string) (string, error) {
|
||||
emails := splitWarnList(value)
|
||||
if len(emails) == 0 {
|
||||
return "", gerror.New("请选择邮箱提醒时必须填写邮箱地址")
|
||||
}
|
||||
result := make([]string, 0, len(emails))
|
||||
for _, email := range emails {
|
||||
address, err := mail.ParseAddress(email)
|
||||
if err != nil || address == nil || strings.TrimSpace(address.Address) == "" {
|
||||
return "", gerror.Newf("邮箱格式不正确:%s", email)
|
||||
}
|
||||
result = append(result, strings.ToLower(strings.TrimSpace(address.Address)))
|
||||
}
|
||||
return strings.Join(result, ","), nil
|
||||
}
|
||||
|
||||
func normalizeWarnPhones(value string) (string, error) {
|
||||
phones := splitWarnList(value)
|
||||
if len(phones) == 0 {
|
||||
return "", gerror.New("请选择短信提醒时必须填写短信号码")
|
||||
}
|
||||
for _, phone := range phones {
|
||||
if !isValidWarnPhone(phone) {
|
||||
return "", gerror.Newf("手机号格式不正确:%s", phone)
|
||||
}
|
||||
}
|
||||
return strings.Join(phones, ","), nil
|
||||
}
|
||||
|
||||
func isValidWarnPhone(phone string) bool {
|
||||
if len(phone) != 11 || phone[0] != '1' {
|
||||
return false
|
||||
}
|
||||
for _, r := range phone {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return phone[1] >= '3' && phone[1] <= '9'
|
||||
}
|
||||
|
||||
func splitWarnList(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
|
||||
}
|
||||
key := strings.ToLower(part)
|
||||
if _, exists := seen[key]; exists {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
result = append(result, part)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func warnEmailCodeKey(tenantID string, userID int64, email string) string {
|
||||
return fmt.Sprintf("%s:%s:%d:%s", warnEmailCodeRedisKeyScope, strings.TrimSpace(tenantID), userID, strings.ToLower(strings.TrimSpace(email)))
|
||||
}
|
||||
|
||||
func setWarnEmailCode(ctx context.Context, tenantID string, userID int64, email string, code string) error {
|
||||
_, err := g.Redis().Do(ctx, "SETEX", warnEmailCodeKey(tenantID, userID, email), warnEmailCodeTTLSeconds, code)
|
||||
return err
|
||||
}
|
||||
|
||||
func getWarnEmailCode(ctx context.Context, tenantID string, userID int64, email string) (string, error) {
|
||||
value, err := g.Redis().Get(ctx, warnEmailCodeKey(tenantID, userID, email))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if value.IsNil() {
|
||||
return "", nil
|
||||
}
|
||||
return value.String(), nil
|
||||
}
|
||||
|
||||
func deleteWarnEmailCode(ctx context.Context, tenantID string, userID int64, email string) error {
|
||||
_, err := g.Redis().Del(ctx, warnEmailCodeKey(tenantID, userID, email))
|
||||
return err
|
||||
}
|
||||
|
||||
func generateWarnEmailCode() (string, error) {
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%06d", n.Int64()), nil
|
||||
}
|
||||
|
||||
func sendWarnEmailCode(ctx context.Context, email string, code string) error {
|
||||
cfg, ok := loadWarnEmailCodeConfig(ctx)
|
||||
if !ok || strings.TrimSpace(cfg.Host) == "" {
|
||||
if strings.TrimSpace(cfg.Url) != "" {
|
||||
return callWarnEmailCodeHTTP(ctx, cfg.UserCodeHttpSenderConfig, email, code)
|
||||
}
|
||||
g.Log().Infof(ctx, "warning email config code, email:%s, code:%s", email, code)
|
||||
return nil
|
||||
}
|
||||
if cfg.Port <= 0 {
|
||||
cfg.Port = 25
|
||||
}
|
||||
if strings.TrimSpace(cfg.From) == "" {
|
||||
cfg.From = cfg.Username
|
||||
}
|
||||
if strings.TrimSpace(cfg.From) == "" {
|
||||
return gerror.New("预警邮箱验证码发件人为空")
|
||||
}
|
||||
addr := net.JoinHostPort(cfg.Host, gconv.String(cfg.Port))
|
||||
auth := smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
|
||||
msg := buildWarnEmailCodeMessage(cfg, email, code)
|
||||
|
||||
err := utility.SendSMTP(ctx, utility.SMTPOptions{
|
||||
Address: addr,
|
||||
Host: cfg.Host,
|
||||
Auth: auth,
|
||||
From: cfg.From,
|
||||
To: []string{email},
|
||||
Data: msg,
|
||||
SSL: cfg.SSL,
|
||||
Timeout: utility.ExternalRequestTimeout(cfg.Timeout),
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "send warning email config code failed, email:%s, err:%+v", email, err)
|
||||
return gerror.New("发送邮箱验证码失败,请稍后重试")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadWarnEmailCodeConfig(ctx context.Context) (model.UserCodeEmailSenderConfig, bool) {
|
||||
var cfg model.UserCodeEmailSenderConfig
|
||||
for _, key := range []string{"warning.email", "register.email"} {
|
||||
v, err := g.Cfg().Get(ctx, key)
|
||||
if err != nil || v.IsNil() {
|
||||
continue
|
||||
}
|
||||
_ = v.Scan(&cfg)
|
||||
if strings.TrimSpace(cfg.Url) != "" || strings.TrimSpace(cfg.Host) != "" {
|
||||
return cfg, true
|
||||
}
|
||||
}
|
||||
return cfg, false
|
||||
}
|
||||
|
||||
func buildWarnEmailCodeMessage(cfg model.UserCodeEmailSenderConfig, email string, code string) []byte {
|
||||
subject := strings.TrimSpace(cfg.Subject)
|
||||
if subject == "" || subject == "注册验证码" {
|
||||
subject = "预警邮箱验证码"
|
||||
}
|
||||
body := renderWarnEmailCodeTemplate(cfg.BodyTemplate, email, code)
|
||||
if body == "" || strings.Contains(body, "注册验证码") {
|
||||
body = fmt.Sprintf("您的预警邮箱验证码是:%s,5分钟内有效。", code)
|
||||
}
|
||||
from := strings.TrimSpace(cfg.From)
|
||||
if strings.TrimSpace(cfg.FromName) != "" {
|
||||
from = mime.QEncoding.Encode("UTF-8", cfg.FromName) + " <" + from + ">"
|
||||
}
|
||||
message := strings.Builder{}
|
||||
message.WriteString("From: " + from + "\r\n")
|
||||
message.WriteString("To: " + email + "\r\n")
|
||||
message.WriteString("Subject: " + mime.QEncoding.Encode("UTF-8", subject) + "\r\n")
|
||||
message.WriteString("MIME-Version: 1.0\r\n")
|
||||
message.WriteString("Content-Type: text/html; charset=UTF-8\r\n")
|
||||
message.WriteString("\r\n")
|
||||
message.WriteString(body)
|
||||
return []byte(message.String())
|
||||
}
|
||||
|
||||
func renderWarnEmailCodeTemplate(template string, contact string, code string) string {
|
||||
if strings.TrimSpace(template) == "" {
|
||||
return ""
|
||||
}
|
||||
replacer := strings.NewReplacer(
|
||||
"{contact}", contact,
|
||||
"{{contact}}", contact,
|
||||
"{email}", contact,
|
||||
"{{email}}", contact,
|
||||
"{code}", code,
|
||||
"{{code}}", code,
|
||||
)
|
||||
return replacer.Replace(template)
|
||||
}
|
||||
|
||||
func callWarnEmailCodeHTTP(ctx context.Context, cfg model.UserCodeHttpSenderConfig, email string, code string) error {
|
||||
method := strings.ToUpper(strings.TrimSpace(cfg.Method))
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
}
|
||||
successStatus := cfg.SuccessStatus
|
||||
if successStatus <= 0 {
|
||||
successStatus = 200
|
||||
}
|
||||
body := renderWarnEmailCodeTemplate(cfg.BodyTemplate, email, code)
|
||||
if body == "" {
|
||||
bodyBytes, _ := json.Marshal(g.Map{
|
||||
"contact": email,
|
||||
"email": email,
|
||||
"code": code,
|
||||
})
|
||||
body = string(bodyBytes)
|
||||
}
|
||||
headers := cfg.Headers
|
||||
if headers == nil {
|
||||
headers = map[string]string{}
|
||||
}
|
||||
if _, ok := headers["Content-Type"]; !ok {
|
||||
headers["Content-Type"] = "application/json"
|
||||
}
|
||||
resp, err := g.Client().Timeout(utility.ExternalRequestTimeout(cfg.Timeout)).SetHeaderMap(headers).DoRequest(ctx, method, cfg.Url, body)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "发送邮箱验证码失败,请稍后重试")
|
||||
}
|
||||
defer resp.Close()
|
||||
if resp.StatusCode != successStatus {
|
||||
return gerror.Newf("发送邮箱验证码失败,服务响应状态码:%d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package warningconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchinput/text"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
func ParseInputRows(ctx context.Context, input string, itemType, warnUnit int) ([]batchimport.Row, error) {
|
||||
lines, err := text.ParseNonEmptyLines(ctx, input)
|
||||
if err != nil {
|
||||
if errors.Is(err, text.ErrEmptyInput) {
|
||||
return nil, gerror.New("批量输入预警配置数据为空")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
rows := make([]batchimport.Row, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
row := &Row{RowNo: line.RowNo, ItemType: itemType}
|
||||
fields := parseInputFields(line.Text)
|
||||
if isCompleteInputRow(itemType, fields) {
|
||||
row.ConfigSet = true
|
||||
row.Config, err = parseInputConfig(itemType, warnUnit, fields[inputConfigOffset(itemType):])
|
||||
if err != nil {
|
||||
row.AddErrorReason(err.Error())
|
||||
}
|
||||
if itemType == warnItemTypeFlowPool {
|
||||
row.FlowPoolCode = strings.TrimSpace(fields[0])
|
||||
} else {
|
||||
row.Iccid = strings.TrimSpace(fields[0])
|
||||
row.Msisdn = strings.TrimSpace(fields[1])
|
||||
row.Imsi = strings.TrimSpace(fields[2])
|
||||
}
|
||||
rows = append(rows, row)
|
||||
continue
|
||||
}
|
||||
if itemType == warnItemTypeFlowPool {
|
||||
fields = strings.Fields(line.Text)
|
||||
if len(fields) == 1 {
|
||||
row.FlowPoolCode = fields[0]
|
||||
} else {
|
||||
row.AddErrorReason("格式错误,应为流量池编号")
|
||||
}
|
||||
rows = append(rows, row)
|
||||
continue
|
||||
}
|
||||
fields = strings.Fields(line.Text)
|
||||
switch len(fields) {
|
||||
case 1:
|
||||
row.Iccid = fields[0]
|
||||
case 3:
|
||||
row.Iccid = fields[0]
|
||||
row.Msisdn = fields[1]
|
||||
row.Imsi = fields[2]
|
||||
default:
|
||||
row.AddErrorReason("格式错误,应为 ICCID 或 ICCID MSISDN IMSI")
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func parseInputFields(value string) []string {
|
||||
value = strings.TrimSpace(value)
|
||||
if strings.Contains(value, "|") {
|
||||
parts := strings.Split(value, "|")
|
||||
for i := range parts {
|
||||
parts[i] = strings.TrimSpace(parts[i])
|
||||
}
|
||||
return parts
|
||||
}
|
||||
if strings.Contains(value, "\t") {
|
||||
parts := strings.Split(value, "\t")
|
||||
for i := range parts {
|
||||
parts[i] = strings.TrimSpace(parts[i])
|
||||
}
|
||||
return parts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func inputConfigOffset(itemType int) int {
|
||||
if itemType == warnItemTypeFlowPool {
|
||||
return 1
|
||||
}
|
||||
return 3
|
||||
}
|
||||
|
||||
func isCompleteInputRow(itemType int, fields []string) bool {
|
||||
if itemType == warnItemTypeFlowPool {
|
||||
return len(fields) == 9
|
||||
}
|
||||
return len(fields) == 12
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package warningconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchinput/excel"
|
||||
)
|
||||
|
||||
func ParseExcelFileRows(ctx context.Context, filePath string, itemType int) ([]batchimport.Row, error) {
|
||||
parsedRows, err := excel.ParseFile(ctx, filePath, excel.ParseOptions{
|
||||
RequiredHeaders: requiredHeaders(itemType),
|
||||
SingleSheet: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseOptions, err := NormalizeContextOptions(itemType, defaultWarnUnit(itemType))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows := make([]batchimport.Row, 0, len(parsedRows))
|
||||
for _, parsed := range parsedRows {
|
||||
if itemType == warnItemTypeFlowPool {
|
||||
row := &Row{
|
||||
RowNo: parsed.RowNo,
|
||||
ItemType: warnItemTypeFlowPool,
|
||||
FlowPoolCode: excel.Field(parsed, poolHeaderCode),
|
||||
Config: baseOptions,
|
||||
ConfigSet: true,
|
||||
}
|
||||
row.Config, err = parseExcelRowOptions(parsed, itemType, baseOptions.WarnUnit)
|
||||
if err != nil {
|
||||
row.AddErrorReason(err.Error())
|
||||
}
|
||||
rows = append(rows, row)
|
||||
continue
|
||||
}
|
||||
row := &Row{
|
||||
RowNo: parsed.RowNo,
|
||||
ItemType: warnItemTypeCard,
|
||||
Iccid: excel.Field(parsed, cardHeaderIccid),
|
||||
Msisdn: excel.Field(parsed, cardHeaderMsisdn),
|
||||
Imsi: excel.Field(parsed, cardHeaderImsi),
|
||||
Config: baseOptions,
|
||||
ConfigSet: true,
|
||||
}
|
||||
row.Config, err = parseExcelRowOptions(parsed, itemType, baseOptions.WarnUnit)
|
||||
if err != nil {
|
||||
row.AddErrorReason(err.Error())
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func defaultWarnUnit(itemType int) int {
|
||||
if itemType == warnItemTypeFlowPool {
|
||||
return warnUnitPercent
|
||||
}
|
||||
return warnUnitMb
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
package warningconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
const (
|
||||
warnItemTypeCard = 1
|
||||
warnItemTypeFlowPool = 2
|
||||
|
||||
warnUnitMb = 1
|
||||
warnUnitPercent = 2
|
||||
warnUnitDay = 3
|
||||
warnUnitMonth = 4
|
||||
|
||||
warnTypeCardTraffic = 1
|
||||
warnTypeExpireDay = 2
|
||||
warnTypeExpireMonth = 3
|
||||
warnTypeFlowPoolPercent = 4
|
||||
|
||||
warnAutoActionDataCut = 3
|
||||
warnMessageDefaultType = warnMethodPlatform
|
||||
)
|
||||
|
||||
type Processor struct {
|
||||
config batchimport.ProcessorConfig
|
||||
itemType int
|
||||
}
|
||||
|
||||
func NewProcessor(itemTypeInput ...int) *Processor {
|
||||
itemType := warnItemTypeCard
|
||||
if len(itemTypeInput) > 0 {
|
||||
itemType = normalizeItemType(itemTypeInput[0])
|
||||
}
|
||||
return &Processor{
|
||||
itemType: itemType,
|
||||
config: batchimport.ProcessorConfig{
|
||||
Code: "sim_warning_config",
|
||||
BatchType: consts.BatchTypeSimWarningConfig,
|
||||
BatchNoPrefix: consts.BatchNoPrefixSimWarningConfig,
|
||||
TaskName: consts.ResourceBatchDownloadTaskSimWarningConfig,
|
||||
ExportScene: consts.ResourceExportSceneSimWarningConfig,
|
||||
ImportDir: consts.SimWarningConfigSubDirImportExcel,
|
||||
ResultDir: consts.SimWarningConfigSubDirResultExcel,
|
||||
MaxFileSize: consts.SimWarningConfigMaxFileSize,
|
||||
TemplateHeaders: templateHeaders(itemType),
|
||||
RequiredHeaders: requiredHeaders(itemType),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) Config() batchimport.ProcessorConfig {
|
||||
return p.config
|
||||
}
|
||||
|
||||
func (p *Processor) ParseRows(ctx context.Context, filePath string) ([]batchimport.Row, error) {
|
||||
return ParseExcelFileRows(ctx, filePath, p.itemType)
|
||||
}
|
||||
|
||||
func (p *Processor) ValidateRows(ctx context.Context, rows []batchimport.Row) error {
|
||||
if p.itemType == warnItemTypeFlowPool {
|
||||
return p.validateFlowPoolRows(rows)
|
||||
}
|
||||
return p.validateCardRows(rows)
|
||||
}
|
||||
|
||||
func (p *Processor) validateCardRows(rows []batchimport.Row) error {
|
||||
firstByIccid := make(map[string]*Row, len(rows))
|
||||
for _, item := range rows {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
row, ok := item.(*Row)
|
||||
if !ok || row == nil {
|
||||
item.AddErrorReason("预警配置行类型错误")
|
||||
continue
|
||||
}
|
||||
row.ItemType = warnItemTypeCard
|
||||
row.Iccid = strings.TrimSpace(row.Iccid)
|
||||
row.Msisdn = strings.TrimSpace(row.Msisdn)
|
||||
row.Imsi = strings.TrimSpace(row.Imsi)
|
||||
if row.Iccid == "" {
|
||||
row.AddErrorReason("ICCID不能为空")
|
||||
continue
|
||||
}
|
||||
if first, exists := firstByIccid[row.Iccid]; exists {
|
||||
row.AddErrorReason("ICCID重复")
|
||||
first.AddErrorReason("ICCID重复")
|
||||
continue
|
||||
}
|
||||
firstByIccid[row.Iccid] = row
|
||||
if row.ConfigSet && row.Reason == "" {
|
||||
if err := validateOptions(&row.Config); err != nil {
|
||||
row.AddErrorReason(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Processor) validateFlowPoolRows(rows []batchimport.Row) error {
|
||||
firstByCode := make(map[string]*Row, len(rows))
|
||||
for _, item := range rows {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
row, ok := item.(*Row)
|
||||
if !ok || row == nil {
|
||||
item.AddErrorReason("预警配置行类型错误")
|
||||
continue
|
||||
}
|
||||
row.ItemType = warnItemTypeFlowPool
|
||||
row.FlowPoolCode = strings.TrimSpace(row.FlowPoolCode)
|
||||
if row.FlowPoolCode == "" {
|
||||
row.AddErrorReason("流量池编号不能为空")
|
||||
continue
|
||||
}
|
||||
if first, exists := firstByCode[row.FlowPoolCode]; exists {
|
||||
row.AddErrorReason("流量池编号重复")
|
||||
first.AddErrorReason("流量池编号重复")
|
||||
continue
|
||||
}
|
||||
firstByCode[row.FlowPoolCode] = row
|
||||
if row.ConfigSet && row.Reason == "" {
|
||||
if err := validateOptions(&row.Config); err != nil {
|
||||
row.AddErrorReason(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Processor) BuildBatchInfo(ctx context.Context, req batchimport.SubmitRequest, result *batchimport.Result, importOss *model.SysOssSaveContentModel, resultOss *model.SysOssSaveContentModel, downloadId int64) *gjson.Json {
|
||||
options, _ := batchInfoOptions(req.BizParams)
|
||||
scene := submitScene(req.BizParams)
|
||||
info := g.Map{
|
||||
"operation": "sim_warning_config",
|
||||
"configName": warningConfigName(options.ItemType, options.WarnUnit),
|
||||
"submitScene": scene,
|
||||
"itemType": options.ItemType,
|
||||
"warnUnit": options.WarnUnit,
|
||||
"firstWarn": options.FirstWarn,
|
||||
"secondWarn": options.SecondWarn,
|
||||
"thirdWarn": options.ThirdWarn,
|
||||
"warnMethods": options.WarnMethods,
|
||||
"warnPhones": options.WarnPhones,
|
||||
"warnEmails": options.WarnEmails,
|
||||
"autoAction": options.AutoAction,
|
||||
"isEnabled": options.IsEnabled,
|
||||
"remark": options.Remark,
|
||||
"rowConfig": scene == "excel" || scene == "input",
|
||||
"resultFile": g.Map{"status": "pending"},
|
||||
}
|
||||
if result != nil && result.ImportFileOssId > 0 {
|
||||
info["importFileOssId"] = result.ImportFileOssId
|
||||
} else if importOss != nil && importOss.OssId > 0 {
|
||||
info["importFileOssId"] = importOss.OssId
|
||||
}
|
||||
if resultOss != nil && resultOss.OssId > 0 {
|
||||
info["resultFileOssId"] = resultOss.OssId
|
||||
}
|
||||
if downloadId > 0 {
|
||||
info["resultDownloadId"] = downloadId
|
||||
}
|
||||
return gjson.New(info)
|
||||
}
|
||||
|
||||
func OptionsFromParams(params any) (Options, error) {
|
||||
switch in := params.(type) {
|
||||
case *model.TSimWarningConfigExcelSubmitParam:
|
||||
if in == nil {
|
||||
return Options{}, gerror.New("预警配置参数不能为空")
|
||||
}
|
||||
return NormalizeContextOptions(in.ItemType, in.WarnUnit)
|
||||
case *model.TSimWarningConfigInputSubmitParam:
|
||||
if in == nil {
|
||||
return Options{}, gerror.New("预警配置参数不能为空")
|
||||
}
|
||||
return NormalizeOptions(&in.TSimWarningConfigApplyOptions)
|
||||
case *model.TSimWarningConfigRangeSubmitParam:
|
||||
if in == nil {
|
||||
return Options{}, gerror.New("预警配置参数不能为空")
|
||||
}
|
||||
return NormalizeOptions(&in.TSimWarningConfigApplyOptions)
|
||||
default:
|
||||
return Options{}, gerror.New("预警配置参数不能为空")
|
||||
}
|
||||
}
|
||||
|
||||
func batchInfoOptions(params any) (Options, error) {
|
||||
if in, ok := params.(*model.TSimWarningConfigInputSubmitParam); ok && in != nil {
|
||||
contextOptions, err := NormalizeContextOptions(in.ItemType, in.WarnUnit)
|
||||
if err != nil {
|
||||
return Options{}, err
|
||||
}
|
||||
if in.FirstWarn <= 0 {
|
||||
return contextOptions, nil
|
||||
}
|
||||
}
|
||||
return OptionsFromParams(params)
|
||||
}
|
||||
|
||||
func ValidateSubmitOptions(params any) error {
|
||||
_, err := OptionsFromParams(params)
|
||||
return err
|
||||
}
|
||||
|
||||
func NormalizeOptions(in *model.TSimWarningConfigApplyOptions) (Options, error) {
|
||||
if in == nil {
|
||||
return Options{}, gerror.New("预警配置参数不能为空")
|
||||
}
|
||||
itemType := normalizeItemType(in.ItemType)
|
||||
options := Options{
|
||||
ItemType: itemType,
|
||||
WarnType: deriveWarnType(itemType, in.WarnUnit),
|
||||
WarnUnit: in.WarnUnit,
|
||||
FirstWarn: in.FirstWarn,
|
||||
SecondWarn: in.SecondWarn,
|
||||
ThirdWarn: in.ThirdWarn,
|
||||
WarnMethods: normalizeWarnMethods(in.WarnMethods),
|
||||
WarnPhones: strings.TrimSpace(in.WarnPhones),
|
||||
WarnEmails: strings.TrimSpace(in.WarnEmails),
|
||||
WarnEmailCode: strings.TrimSpace(in.WarnEmailCode),
|
||||
AutoAction: in.AutoAction,
|
||||
IsEnabled: 1,
|
||||
Remark: strings.TrimSpace(in.Remark),
|
||||
}
|
||||
if in.IsEnabled != nil {
|
||||
options.IsEnabled = *in.IsEnabled
|
||||
}
|
||||
if err := validateOptions(&options); err != nil {
|
||||
return Options{}, err
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func validateOptions(options *Options) error {
|
||||
if options == nil {
|
||||
return gerror.New("预警配置参数不能为空")
|
||||
}
|
||||
if options.ItemType != warnItemTypeCard && options.ItemType != warnItemTypeFlowPool {
|
||||
return gerror.New("预警对象类型无效")
|
||||
}
|
||||
if options.WarnUnit == warnUnitDay || options.WarnUnit == warnUnitMonth {
|
||||
return gerror.New("日期预警由系统默认规则处理,不支持手动配置")
|
||||
}
|
||||
if options.IsEnabled != 0 && options.IsEnabled != 1 {
|
||||
return gerror.New("启停状态无效")
|
||||
}
|
||||
if options.AutoAction != 0 && options.AutoAction != warnAutoActionDataCut {
|
||||
return gerror.New("第三次是否断网配置无效")
|
||||
}
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateWarnTypeUnit(options.ItemType, options.WarnType, options.WarnUnit); err != nil {
|
||||
return err
|
||||
}
|
||||
if options.ItemType == warnItemTypeFlowPool && options.AutoAction != 0 {
|
||||
return gerror.New("流量池预警不支持第三次断网")
|
||||
}
|
||||
if options.AutoAction == warnAutoActionDataCut && options.ThirdWarn <= 0 {
|
||||
return gerror.New("开启第三次断网时必须配置第三次提醒")
|
||||
}
|
||||
switch options.WarnType {
|
||||
case warnTypeCardTraffic:
|
||||
if options.WarnUnit == warnUnitPercent {
|
||||
options.FirstWarn = normalizePercentThreshold(options.FirstWarn)
|
||||
options.SecondWarn = normalizePercentThreshold(options.SecondWarn)
|
||||
options.ThirdWarn = normalizePercentThreshold(options.ThirdWarn)
|
||||
if options.FirstWarn > 100 || options.SecondWarn > 100 || options.ThirdWarn > 100 {
|
||||
return gerror.New("百分比阈值不能大于100")
|
||||
}
|
||||
}
|
||||
return validateTrafficThresholds(options.FirstWarn, options.SecondWarn, options.ThirdWarn)
|
||||
case warnTypeFlowPoolPercent:
|
||||
options.FirstWarn = normalizePercentThreshold(options.FirstWarn)
|
||||
options.SecondWarn = normalizePercentThreshold(options.SecondWarn)
|
||||
options.ThirdWarn = normalizePercentThreshold(options.ThirdWarn)
|
||||
if options.FirstWarn > 100 || options.SecondWarn > 100 || options.ThirdWarn > 100 {
|
||||
return gerror.New("百分比阈值不能大于100")
|
||||
}
|
||||
return validateTrafficThresholds(options.FirstWarn, options.SecondWarn, options.ThirdWarn)
|
||||
default:
|
||||
return gerror.New("预警类型无效")
|
||||
}
|
||||
}
|
||||
|
||||
func validateTrafficThresholds(first, second, third float64) error {
|
||||
if first <= 0 {
|
||||
return gerror.New("第一档阈值必须大于0")
|
||||
}
|
||||
if second < 0 || third < 0 {
|
||||
return gerror.New("预警阈值不能小于0")
|
||||
}
|
||||
if second > 0 && second <= first {
|
||||
return gerror.New("流量预警第二档阈值必须大于第一档")
|
||||
}
|
||||
if third > 0 {
|
||||
if second <= 0 {
|
||||
return gerror.New("配置第三档时必须先配置第二档")
|
||||
}
|
||||
if third <= second {
|
||||
return gerror.New("流量预警第三档阈值必须大于第二档")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizePercentThreshold(value float64) float64 {
|
||||
if value > 0 && value <= 1 {
|
||||
return value * 100
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func normalizeWarnMethods(methods string) string {
|
||||
selected := map[string]struct{}{
|
||||
warnMethodPlatform: {},
|
||||
}
|
||||
for _, method := range splitWarnList(methods) {
|
||||
switch strings.ToLower(method) {
|
||||
case warnMethodPlatform, "平台消息", "站内提醒", "站内消息":
|
||||
selected[warnMethodPlatform] = struct{}{}
|
||||
case warnMethodEmail, "邮箱":
|
||||
selected[warnMethodEmail] = struct{}{}
|
||||
case warnMethodSMS, "短信":
|
||||
selected[warnMethodSMS] = struct{}{}
|
||||
case warnMethodPhone, "电话":
|
||||
selected[warnMethodPhone] = struct{}{}
|
||||
}
|
||||
}
|
||||
ordered := []string{
|
||||
warnMethodPlatform,
|
||||
warnMethodEmail,
|
||||
warnMethodSMS,
|
||||
warnMethodPhone,
|
||||
}
|
||||
result := make([]string, 0, len(ordered))
|
||||
for _, method := range ordered {
|
||||
if _, ok := selected[method]; ok {
|
||||
result = append(result, method)
|
||||
}
|
||||
}
|
||||
return strings.Join(result, ",")
|
||||
}
|
||||
|
||||
func submitScene(params any) string {
|
||||
switch params.(type) {
|
||||
case *model.TSimWarningConfigExcelSubmitParam:
|
||||
return "excel"
|
||||
case *model.TSimWarningConfigInputSubmitParam:
|
||||
return "input"
|
||||
case *model.TSimWarningConfigRangeSubmitParam:
|
||||
return "range"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func warningConfigName(itemType, warnUnit int) string {
|
||||
warnType := deriveWarnType(itemType, warnUnit)
|
||||
switch warnType {
|
||||
case warnTypeFlowPoolPercent:
|
||||
return "流量池预警配置"
|
||||
case warnTypeCardTraffic:
|
||||
return "流量预警配置"
|
||||
case warnTypeExpireDay:
|
||||
return "日期预警(天)配置"
|
||||
case warnTypeExpireMonth:
|
||||
return "日期预警(月)配置"
|
||||
default:
|
||||
return "SIM卡预警配置"
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeItemType(itemType int) int {
|
||||
if itemType == 0 {
|
||||
return warnItemTypeCard
|
||||
}
|
||||
return itemType
|
||||
}
|
||||
|
||||
func deriveWarnType(itemType, warnUnit int) int {
|
||||
if itemType == warnItemTypeFlowPool {
|
||||
return warnTypeFlowPoolPercent
|
||||
}
|
||||
switch warnUnit {
|
||||
case warnUnitMb, warnUnitPercent:
|
||||
return warnTypeCardTraffic
|
||||
case warnUnitDay:
|
||||
return warnTypeExpireDay
|
||||
case warnUnitMonth:
|
||||
return warnTypeExpireMonth
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func validateWarnTypeUnit(itemType, warnType, warnUnit int) error {
|
||||
switch warnType {
|
||||
case warnTypeCardTraffic:
|
||||
if itemType != warnItemTypeCard {
|
||||
return gerror.New("单卡流量预警只能配置单卡对象")
|
||||
}
|
||||
if warnUnit != warnUnitMb && warnUnit != warnUnitPercent {
|
||||
return gerror.New("单卡流量预警仅支持MB或百分比单位")
|
||||
}
|
||||
case warnTypeExpireDay:
|
||||
return gerror.New("日期预警由系统默认规则处理,不支持手动配置")
|
||||
case warnTypeExpireMonth:
|
||||
return gerror.New("日期预警由系统默认规则处理,不支持手动配置")
|
||||
case warnTypeFlowPoolPercent:
|
||||
if itemType != warnItemTypeFlowPool {
|
||||
return gerror.New("流量池预警只能配置流量池对象")
|
||||
}
|
||||
if warnUnit != warnUnitPercent {
|
||||
return gerror.New("流量池预警仅支持百分比单位")
|
||||
}
|
||||
default:
|
||||
return gerror.New("预警类型无效")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package warningconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/simcardbatch/common/cardsource"
|
||||
)
|
||||
|
||||
const maxRangeRows = 100000
|
||||
|
||||
func BuildRangeRows(ctx context.Context, startIccid, endIccid, startMsisdn, endMsisdn, startImsi, endImsi string) ([]batchimport.Row, error) {
|
||||
return cardsource.BuildRangeRows(ctx, cardsource.RangeConfig{
|
||||
StartIccid: startIccid,
|
||||
EndIccid: endIccid,
|
||||
StartMsisdn: startMsisdn,
|
||||
EndMsisdn: endMsisdn,
|
||||
StartImsi: startImsi,
|
||||
EndImsi: endImsi,
|
||||
MaxRows: maxRangeRows,
|
||||
EmptyMessage: "连号预警配置数据为空",
|
||||
OverLimitMessage: "连号预警配置数量不能超过%d条",
|
||||
CountMismatchMessage: "ICCID号段数量(%d)、MSISDN号段数量(%d)、IMSI号段数量(%d)必须一致",
|
||||
}, func(rowNo int, iccid, msisdn, imsi string) *Row {
|
||||
return &Row{
|
||||
RowNo: rowNo,
|
||||
Iccid: iccid,
|
||||
Msisdn: msisdn,
|
||||
Imsi: imsi,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package warningconfig
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchinput/excel"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
const (
|
||||
cardHeaderIccid = "ICCID"
|
||||
cardHeaderMsisdn = "MSISDN"
|
||||
cardHeaderImsi = "IMSI"
|
||||
cardHeaderFirstWarn = "第一档阈值(MB)"
|
||||
cardHeaderSecondWarn = "第二档阈值(MB)"
|
||||
cardHeaderThirdWarn = "第三档阈值(MB)"
|
||||
cardHeaderAutoAction = "第三次是否断网"
|
||||
|
||||
poolHeaderCode = "流量池编号"
|
||||
poolHeaderFirstWarn = "第一档阈值(%)"
|
||||
poolHeaderSecondWarn = "第二档阈值(%)"
|
||||
poolHeaderThirdWarn = "第三档阈值(%)"
|
||||
|
||||
headerWarnMethods = "提醒方式"
|
||||
headerWarnPhones = "短信号码"
|
||||
headerWarnEmails = "邮箱地址"
|
||||
headerIsEnabled = "是否启用"
|
||||
headerRemark = "备注"
|
||||
)
|
||||
|
||||
func templateHeaders(itemType int) []string {
|
||||
if itemType == warnItemTypeFlowPool {
|
||||
return []string{
|
||||
poolHeaderCode,
|
||||
poolHeaderFirstWarn,
|
||||
poolHeaderSecondWarn,
|
||||
poolHeaderThirdWarn,
|
||||
headerWarnMethods,
|
||||
headerWarnPhones,
|
||||
headerWarnEmails,
|
||||
headerIsEnabled,
|
||||
headerRemark,
|
||||
}
|
||||
}
|
||||
return []string{
|
||||
cardHeaderIccid,
|
||||
cardHeaderMsisdn,
|
||||
cardHeaderImsi,
|
||||
cardHeaderFirstWarn,
|
||||
cardHeaderSecondWarn,
|
||||
cardHeaderThirdWarn,
|
||||
cardHeaderAutoAction,
|
||||
headerWarnMethods,
|
||||
headerWarnPhones,
|
||||
headerWarnEmails,
|
||||
headerIsEnabled,
|
||||
headerRemark,
|
||||
}
|
||||
}
|
||||
|
||||
func requiredHeaders(itemType int) []string {
|
||||
return templateHeaders(itemType)
|
||||
}
|
||||
|
||||
func NormalizeContextOptions(itemType, warnUnit int) (Options, error) {
|
||||
itemType = normalizeItemType(itemType)
|
||||
options := Options{
|
||||
ItemType: itemType,
|
||||
WarnType: deriveWarnType(itemType, warnUnit),
|
||||
WarnUnit: warnUnit,
|
||||
WarnMethods: warnMethodPlatform,
|
||||
IsEnabled: 1,
|
||||
}
|
||||
if err := validateWarnTypeUnit(itemType, options.WarnType, warnUnit); err != nil {
|
||||
return Options{}, err
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func parseExcelRowOptions(parsed excel.ParsedRow, itemType, warnUnit int) (Options, error) {
|
||||
if itemType == warnItemTypeFlowPool {
|
||||
return parseRowOptions(itemType, warnUnit, []string{
|
||||
excel.Field(parsed, poolHeaderFirstWarn),
|
||||
excel.Field(parsed, poolHeaderSecondWarn),
|
||||
excel.Field(parsed, poolHeaderThirdWarn),
|
||||
excel.Field(parsed, headerWarnMethods),
|
||||
excel.Field(parsed, headerWarnPhones),
|
||||
excel.Field(parsed, headerWarnEmails),
|
||||
excel.Field(parsed, headerIsEnabled),
|
||||
excel.Field(parsed, headerRemark),
|
||||
})
|
||||
}
|
||||
return parseRowOptions(itemType, warnUnit, []string{
|
||||
excel.Field(parsed, cardHeaderFirstWarn),
|
||||
excel.Field(parsed, cardHeaderSecondWarn),
|
||||
excel.Field(parsed, cardHeaderThirdWarn),
|
||||
excel.Field(parsed, cardHeaderAutoAction),
|
||||
excel.Field(parsed, headerWarnMethods),
|
||||
excel.Field(parsed, headerWarnPhones),
|
||||
excel.Field(parsed, headerWarnEmails),
|
||||
excel.Field(parsed, headerIsEnabled),
|
||||
excel.Field(parsed, headerRemark),
|
||||
})
|
||||
}
|
||||
|
||||
func parseRowOptions(itemType, warnUnit int, fields []string) (Options, error) {
|
||||
expected := 8
|
||||
if itemType == warnItemTypeCard {
|
||||
expected = 9
|
||||
}
|
||||
if len(fields) != expected {
|
||||
return Options{}, gerror.New("预警配置字段数量不正确")
|
||||
}
|
||||
|
||||
first, err := parseRequiredAmount(fields[0], "第一档阈值")
|
||||
if err != nil {
|
||||
return Options{}, err
|
||||
}
|
||||
second, err := parseOptionalAmount(fields[1], "第二档阈值")
|
||||
if err != nil {
|
||||
return Options{}, err
|
||||
}
|
||||
third, err := parseOptionalAmount(fields[2], "第三档阈值")
|
||||
if err != nil {
|
||||
return Options{}, err
|
||||
}
|
||||
|
||||
offset := 3
|
||||
autoAction := 0
|
||||
if itemType == warnItemTypeCard {
|
||||
if strings.TrimSpace(fields[offset]) != "" {
|
||||
enabled, parseErr := excel.ParseBool(fields[offset])
|
||||
if parseErr != nil {
|
||||
return Options{}, gerror.New("第三次是否断网必须填写是或否")
|
||||
}
|
||||
if enabled {
|
||||
autoAction = warnAutoActionDataCut
|
||||
}
|
||||
}
|
||||
offset++
|
||||
}
|
||||
|
||||
methods, err := parseWarnMethods(fields[offset])
|
||||
if err != nil {
|
||||
return Options{}, err
|
||||
}
|
||||
phones := strings.TrimSpace(fields[offset+1])
|
||||
emails := strings.TrimSpace(fields[offset+2])
|
||||
isEnabled := 1
|
||||
if strings.TrimSpace(fields[offset+3]) != "" {
|
||||
enabled, parseErr := excel.ParseBool(fields[offset+3])
|
||||
if parseErr != nil {
|
||||
return Options{}, gerror.New("是否启用必须填写是或否")
|
||||
}
|
||||
if !enabled {
|
||||
isEnabled = 0
|
||||
}
|
||||
}
|
||||
|
||||
options := Options{
|
||||
ItemType: itemType,
|
||||
WarnType: deriveWarnType(itemType, warnUnit),
|
||||
WarnUnit: warnUnit,
|
||||
FirstWarn: first,
|
||||
SecondWarn: second,
|
||||
ThirdWarn: third,
|
||||
WarnMethods: methods,
|
||||
WarnPhones: phones,
|
||||
WarnEmails: emails,
|
||||
AutoAction: autoAction,
|
||||
IsEnabled: isEnabled,
|
||||
Remark: strings.TrimSpace(fields[offset+4]),
|
||||
}
|
||||
if err = validateOptions(&options); err != nil {
|
||||
return Options{}, err
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func parseRequiredAmount(value, name string) (float64, error) {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return 0, gerror.Newf("%s不能为空", name)
|
||||
}
|
||||
parsed, err := excel.ParseAmount(value)
|
||||
if err != nil {
|
||||
return 0, gerror.Newf("%s必须是非负数字", name)
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func parseOptionalAmount(value, name string) (float64, error) {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return 0, nil
|
||||
}
|
||||
parsed, err := excel.ParseAmount(value)
|
||||
if err != nil {
|
||||
return 0, gerror.Newf("%s必须是非负数字", name)
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func parseWarnMethods(value string) (string, error) {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return "", gerror.New("提醒方式不能为空,至少填写站内提醒")
|
||||
}
|
||||
for _, method := range splitWarnList(value) {
|
||||
switch strings.ToLower(method) {
|
||||
case "0", "短信", "1", "邮箱", "2", "电话", "3", "平台消息", "站内提醒", "站内消息":
|
||||
default:
|
||||
return "", gerror.Newf("提醒方式无效:%s", method)
|
||||
}
|
||||
}
|
||||
return normalizeWarnMethods(value), nil
|
||||
}
|
||||
|
||||
func parseInputConfig(itemType, warnUnit int, fields []string) (Options, error) {
|
||||
return parseRowOptions(itemType, warnUnit, fields)
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package warningconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchmetadata"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/simcardbatch/common"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
func CreatePendingExcel(ctx context.Context, file *ghttp.UploadFile, in *model.TSimWarningConfigExcelSubmitParam) (*PendingResult, error) {
|
||||
if in == nil {
|
||||
in = &model.TSimWarningConfigExcelSubmitParam{}
|
||||
}
|
||||
options, err := NormalizeContextOptions(in.ItemType, in.WarnUnit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
processor := NewProcessor(options.ItemType)
|
||||
req := batchimport.SubmitRequest{
|
||||
SourceChannel: batchmetadata.Console,
|
||||
ExecutionMode: batchmetadata.Bulk,
|
||||
File: file,
|
||||
BizParams: in,
|
||||
SourceType: consts.BatchSourceTypeFile,
|
||||
Operator: common.CurrentOperator(ctx),
|
||||
}
|
||||
result, err := batchimport.CreatePendingUpload(ctx, req, processor, prepareOptions(options))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common.PublishBatchMessage(ctx, result, consts.QueueBatchExecute, "sim_warning_config")
|
||||
return pendingResultFromBatch(result), nil
|
||||
}
|
||||
|
||||
func CreatePendingInput(ctx context.Context, in *model.TSimWarningConfigInputSubmitParam) (*PendingResult, error) {
|
||||
if in == nil {
|
||||
in = &model.TSimWarningConfigInputSubmitParam{}
|
||||
}
|
||||
contextOptions, err := NormalizeContextOptions(in.ItemType, in.WarnUnit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := ParseInputRows(ctx, in.Input, contextOptions.ItemType, contextOptions.WarnUnit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options := contextOptions
|
||||
if rowsNeedSharedOptions(rows) {
|
||||
options, err = OptionsFromParams(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
processor := NewProcessor(options.ItemType)
|
||||
req := batchimport.SubmitRequest{
|
||||
SourceChannel: batchmetadata.Console,
|
||||
ExecutionMode: batchmetadata.Bulk,
|
||||
BizParams: in,
|
||||
SourceType: consts.BatchSourceTypeBatchInput,
|
||||
Operator: common.CurrentOperator(ctx),
|
||||
}
|
||||
result, err := batchimport.CreatePendingRows(ctx, req, processor, rows, prepareOptions(options))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common.PublishBatchMessage(ctx, result, consts.QueueBatchExecute, "sim_warning_config")
|
||||
return pendingResultFromBatch(result), nil
|
||||
}
|
||||
|
||||
func CreatePendingRange(ctx context.Context, in *model.TSimWarningConfigRangeSubmitParam) (*PendingResult, error) {
|
||||
if in == nil {
|
||||
in = &model.TSimWarningConfigRangeSubmitParam{}
|
||||
}
|
||||
options, err := OptionsFromParams(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.ItemType == warnItemTypeFlowPool {
|
||||
return nil, gerror.New("流量池预警配置不支持连号")
|
||||
}
|
||||
rows, err := BuildRangeRows(ctx, in.StartIccid, in.EndIccid, in.StartMsisdn, in.EndMsisdn, in.StartImsi, in.EndImsi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
processor := NewProcessor(options.ItemType)
|
||||
req := batchimport.SubmitRequest{
|
||||
SourceChannel: batchmetadata.Console,
|
||||
ExecutionMode: batchmetadata.Bulk,
|
||||
BizParams: in,
|
||||
SourceType: consts.BatchSourceTypeRange,
|
||||
Operator: common.CurrentOperator(ctx),
|
||||
}
|
||||
result, err := batchimport.CreatePendingRows(ctx, req, processor, rows, prepareOptions(options))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common.PublishBatchMessage(ctx, result, consts.QueueBatchExecute, "sim_warning_config")
|
||||
return pendingResultFromBatch(result), nil
|
||||
}
|
||||
|
||||
func prepareOptions(options Options) batchimport.PendingOptions {
|
||||
return batchimport.PendingOptions{
|
||||
PrepareRows: func(ctx context.Context, rows []batchimport.Row) error {
|
||||
if options.ItemType == warnItemTypeFlowPool {
|
||||
return prepareAuthorizedFlowPoolRows(ctx, rows, options)
|
||||
}
|
||||
if err := common.PrepareAuthorizedRows(ctx, rows, func(item batchimport.Row) (*Row, error) {
|
||||
row, ok := item.(*Row)
|
||||
if !ok || row == nil {
|
||||
return nil, gerror.New("预警配置行类型错误")
|
||||
}
|
||||
return row, nil
|
||||
}, "预警配置行类型错误"); err != nil {
|
||||
return err
|
||||
}
|
||||
applyConfigSnapshot(rows, options)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func prepareAuthorizedFlowPoolRows(ctx context.Context, rows []batchimport.Row, options Options) error {
|
||||
flowPoolRows := make([]*Row, 0, len(rows))
|
||||
codes := make([]string, 0, len(rows))
|
||||
for _, item := range rows {
|
||||
row, ok := item.(*Row)
|
||||
if !ok || row == nil {
|
||||
return gerror.New("预警配置行类型错误")
|
||||
}
|
||||
flowPoolRows = append(flowPoolRows, row)
|
||||
if code := strings.TrimSpace(row.FlowPoolCode); code != "" {
|
||||
codes = append(codes, code)
|
||||
}
|
||||
}
|
||||
if len(codes) == 0 {
|
||||
applyConfigSnapshot(rows, options)
|
||||
return nil
|
||||
}
|
||||
|
||||
var visible []struct {
|
||||
FlowPoolCode string `orm:"flow_pool_code"`
|
||||
}
|
||||
columns := dao.TSimFlowPool.Columns()
|
||||
err := handler.Model(dao.TSimFlowPool.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: true}).
|
||||
Fields(columns.FlowPoolCode, columns.TenantId, columns.DeptId, columns.CreatedDept, columns.CreatedBy).
|
||||
WhereIn(columns.FlowPoolCode, codes).
|
||||
Scan(&visible)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "计算流量池数据权限失败")
|
||||
}
|
||||
|
||||
authorized := make(map[string]struct{}, len(visible))
|
||||
for _, pool := range visible {
|
||||
authorized[strings.TrimSpace(pool.FlowPoolCode)] = struct{}{}
|
||||
}
|
||||
for _, row := range flowPoolRows {
|
||||
_, ok := authorized[strings.TrimSpace(row.FlowPoolCode)]
|
||||
row.SetAuthorized(ok)
|
||||
}
|
||||
applyConfigSnapshot(rows, options)
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyConfigSnapshot(rows []batchimport.Row, options Options) {
|
||||
for _, item := range rows {
|
||||
row, ok := item.(*Row)
|
||||
if !ok || row == nil {
|
||||
continue
|
||||
}
|
||||
if row.ConfigSet {
|
||||
continue
|
||||
}
|
||||
row.Config = options
|
||||
row.ConfigSet = true
|
||||
if row.ItemType == 0 {
|
||||
row.ItemType = options.ItemType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rowsNeedSharedOptions(rows []batchimport.Row) bool {
|
||||
for _, item := range rows {
|
||||
row, ok := item.(*Row)
|
||||
if ok && row != nil && !row.ConfigSet && row.Reason == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func pendingResultFromBatch(result *batchimport.Result) *PendingResult {
|
||||
res := &PendingResult{
|
||||
BatchType: consts.BatchTypeSimWarningConfig,
|
||||
BatchAsyncStatus: consts.BatchAsyncStatusPending,
|
||||
}
|
||||
if result == nil {
|
||||
return res
|
||||
}
|
||||
valid := result.ValidRows
|
||||
if result.TotalRows > 0 && valid == 0 {
|
||||
valid = result.TotalRows - result.ErrorRows
|
||||
if valid < 0 {
|
||||
valid = 0
|
||||
}
|
||||
}
|
||||
res.BatchNo = result.BatchNo
|
||||
res.BatchType = result.BatchType
|
||||
res.Total = result.TotalRows
|
||||
res.ValidCount = valid
|
||||
res.FailCount = result.ErrorRows
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package warningconfig
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type Row struct {
|
||||
RowNo int
|
||||
ItemType int
|
||||
Iccid string
|
||||
Msisdn string
|
||||
Imsi string
|
||||
FlowPoolCode string
|
||||
Authorized bool
|
||||
Reason string
|
||||
Config Options
|
||||
ConfigSet bool
|
||||
}
|
||||
|
||||
func (r *Row) GetRowNo() int {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
return r.RowNo
|
||||
}
|
||||
|
||||
func (r *Row) GetItemID() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
if r.ItemType == warnItemTypeFlowPool {
|
||||
return r.FlowPoolCode
|
||||
}
|
||||
return r.Iccid
|
||||
}
|
||||
|
||||
func (r *Row) GetErrorReasons() []string {
|
||||
if r == nil || r.Reason == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{r.Reason}
|
||||
}
|
||||
|
||||
func (r *Row) AddErrorReason(reason string) {
|
||||
if r == nil || r.Reason != "" || strings.TrimSpace(reason) == "" {
|
||||
return
|
||||
}
|
||||
r.Reason = strings.TrimSpace(reason)
|
||||
}
|
||||
|
||||
func (r *Row) GetIccid() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return r.Iccid
|
||||
}
|
||||
|
||||
func (r *Row) SetAuthorized(v bool) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
r.Authorized = v
|
||||
}
|
||||
|
||||
func (r *Row) Snapshot() *gjson.Json {
|
||||
if r == nil {
|
||||
return gjson.New(g.Map{})
|
||||
}
|
||||
return gjson.New(g.Map{
|
||||
"itemType": r.ItemType,
|
||||
"ICCID": r.Iccid,
|
||||
"MSISDN": r.Msisdn,
|
||||
"IMSI": r.Imsi,
|
||||
"flowPoolCode": r.FlowPoolCode,
|
||||
"authorized": r.Authorized,
|
||||
"configItemType": r.Config.ItemType,
|
||||
"configSet": r.ConfigSet,
|
||||
"warnUnit": r.Config.WarnUnit,
|
||||
"firstWarn": r.Config.FirstWarn,
|
||||
"secondWarn": r.Config.SecondWarn,
|
||||
"thirdWarn": r.Config.ThirdWarn,
|
||||
"warnMethods": r.Config.WarnMethods,
|
||||
"warnPhones": r.Config.WarnPhones,
|
||||
"warnEmails": r.Config.WarnEmails,
|
||||
"autoAction": r.Config.AutoAction,
|
||||
"isEnabled": r.Config.IsEnabled,
|
||||
"remark": r.Config.Remark,
|
||||
})
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
ItemType int
|
||||
WarnType int
|
||||
WarnUnit int
|
||||
FirstWarn float64
|
||||
SecondWarn float64
|
||||
ThirdWarn float64
|
||||
WarnMethods string
|
||||
WarnPhones string
|
||||
WarnEmails string
|
||||
WarnEmailCode string
|
||||
AutoAction int
|
||||
IsEnabled int
|
||||
Remark string
|
||||
}
|
||||
|
||||
type PendingResult struct {
|
||||
BatchNo string
|
||||
BatchType int
|
||||
BatchAsyncStatus int
|
||||
Total int
|
||||
ValidCount int
|
||||
FailCount int
|
||||
}
|
||||
Reference in New Issue
Block a user