diff --git a/internal/biz/alarm.go b/internal/biz/alarm.go index cf4ea72..99e2f27 100644 --- a/internal/biz/alarm.go +++ b/internal/biz/alarm.go @@ -62,7 +62,7 @@ func (v *VoucherBiz) alarmText(_ context.Context, order *bo.OrderBo, errMsg stri " \n" + "" + "不好了,订单发放发生异常了" + - "[%s]请尽快处理@相关人员。" + + "[%s]" + "" return fmt.Sprintf(msg, remarks) diff --git a/internal/biz/cron_notice.go b/internal/biz/cron_notice.go index 6197c74..a611bb4 100644 --- a/internal/biz/cron_notice.go +++ b/internal/biz/cron_notice.go @@ -89,7 +89,7 @@ func (v *VoucherBiz) timeSliceQuery(ctx context.Context, startTime, endTime time duration := 2 * time.Hour eg := new(errgroup.Group) - eg.SetLimit(8) + eg.SetLimit(10) for start := startTime; start.Before(endTime); start = start.Add(duration) { diff --git a/internal/biz/do/warning_budget.go b/internal/biz/do/warning_budget.go index 6313e48..d142ddd 100644 --- a/internal/biz/do/warning_budget.go +++ b/internal/biz/do/warning_budget.go @@ -1,5 +1,7 @@ package do +import "time" + type WarningBudget struct { StockName string // 券名称 StockId string // 券ID @@ -12,3 +14,9 @@ type WarningBudget struct { AvailableStock int64 // 可用库存 RemainingBudget int64 // 剩余预算 } + +type WarningBudgetLog struct { + WarningBudget *WarningBudget + Num int + LastTime time.Time +} diff --git a/internal/biz/vo/cache.go b/internal/biz/vo/cache.go index 038bb7e..2be695f 100644 --- a/internal/biz/vo/cache.go +++ b/internal/biz/vo/cache.go @@ -29,6 +29,10 @@ const ( ProductQueryLockKey CacheKey = "product_query_lock" ) +var ( + WarningBudgetSendIncr CacheKey = "warning_budget_send_incr" +) + var CacheKeyMap = map[CacheKey]time.Duration{ CmbOrderLockKey: 30 * time.Second, CmbQueryLockKey: 30 * time.Second, @@ -43,6 +47,8 @@ var CacheKeyMap = map[CacheKey]time.Duration{ ProductQueryKey: 30 * 86400 * time.Second, // 30天 ProductQueryLockKey: 30 * time.Second, + + WarningBudgetSendIncr: 1 * time.Hour, } type Cache struct { diff --git a/internal/biz/voucher.go b/internal/biz/voucher.go index 7c92587..734e2e1 100644 --- a/internal/biz/voucher.go +++ b/internal/biz/voucher.go @@ -3,6 +3,7 @@ package biz import ( "sync" "voucher/internal/biz/cmb" + "voucher/internal/biz/do" "voucher/internal/biz/mixrepos" "voucher/internal/biz/repo" "voucher/internal/biz/wechatrepo" @@ -24,8 +25,9 @@ type VoucherBiz struct { DingMixRepo mixrepos.DingMixRepo CmbMixRepo mixrepos.CmbMixRepo - mu sync.RWMutex - queryMap map[string]bool + mu sync.RWMutex + queryMap map[string]bool + warningBudgeMap map[string]*do.WarningBudgetLog } func NewVoucherBiz( @@ -56,7 +58,8 @@ func NewVoucherBiz( DingMixRepo: DingMixRepo, CmbMixRepo: CmbMixRepo, - queryMap: make(map[string]bool), + queryMap: make(map[string]bool), + warningBudgeMap: make(map[string]*do.WarningBudgetLog), } } diff --git a/internal/biz/warning_budget.go b/internal/biz/warning_budget.go index 4c36de4..37f514b 100644 --- a/internal/biz/warning_budget.go +++ b/internal/biz/warning_budget.go @@ -9,10 +9,93 @@ import ( "time" "voucher/internal/biz/bo" "voucher/internal/biz/do" + "voucher/internal/biz/vo" ) +func (s *VoucherBiz) WarningBudgetIncr(ctx context.Context, uid string) (int64, error) { + + v := vo.WarningBudgetSendIncr.BuildCache([]string{uid}) + + // 增加发送计数 + count, err := s.rdb.Rdb.IncrBy(ctx, v.Key, 1).Result() + if err != nil { + return 0, err + } + + // 如果是第一次发送,设置 过期时间 + if count == 1 { + if err = s.rdb.Rdb.Expire(ctx, v.Key, v.TTL).Err(); err != nil { + return 0, fmt.Errorf("设置过期时间失败: %v", err) + } + } + + // 如果发送次数超过 6 条,限制发送 + if count > 6 { + if _, err = s.rdb.Rdb.Del(ctx, v.Key).Result(); err != nil { + return 0, err + } + } + + return count, nil +} + +func (this *VoucherBiz) WarningBudgetGet(uid string) *do.WarningBudgetLog { + + if w, ok := this.warningBudgeMap[uid]; ok { + return w + } + + return nil +} + +func (this *VoucherBiz) WarningBudgetSet(req *do.WarningBudget) { + + this.warningBudgeMap[req.StockId] = &do.WarningBudgetLog{ + WarningBudget: req, + Num: 1, // 默认1 + LastTime: time.Now(), + } +} + +func (this *VoucherBiz) WarningBudgetAdd(req *do.WarningBudget) *do.WarningBudgetLog { + + this.mu.Lock() + defer this.mu.Unlock() + + w := this.WarningBudgetGet(req.StockId) + if w == nil { + this.WarningBudgetSet(req) + } else { + w.LastTime = time.Now() + w.Num += 1 + w.WarningBudget = req + } + + return w +} + +func (this *VoucherBiz) WarningBudgetRemove(uid string) { + + this.mu.Lock() + defer this.mu.Unlock() + + if _, ok := this.warningBudgeMap[uid]; ok { + delete(this.warningBudgeMap, uid) + } +} + func (v *VoucherBiz) WarningBudget(ctx context.Context) { + uid := "warningBudget" + + if b := v.Get(uid); b { + log.Warn("预警查询,上波还未执行完毕,此次暂不执行") + return + } + + v.Add(uid) + defer v.Remove(uid) + start := time.Now() log.Warnf("预警查询,执行开始: %s", start.Format(time.DateTime)) @@ -80,24 +163,40 @@ func (v *VoucherBiz) Calculate(ctx context.Context, product *bo.ProductBo, wxRes log.Warnf("预警查询,券预算明细,%s", str) if product.WarningBudget >= remainingBudget { - return v.WarningSend(ctx, str) + + count, err := v.WarningBudgetIncr(ctx, req.StockId) + if err != nil { + return err + } + + if count == 1 { + return v.WarningSend(ctx, str) + } else { + log.Warnf("预警查询,当前达到预警第[%d]次,暂不做通知", count) + } + + //w := v.WarningBudgetAdd(req) + //if w.Num == 1 { + // return v.WarningSend(ctx, str) + //} else if w.Num > 5 { + // v.WarningBudgetRemove(req.StockId) + //} } return nil } func (v *VoucherBiz) WarningSend(ctx context.Context, str string) error { - return v.DingMixRepo.SendMarkdownMessage(ctx, "券预算不足", str) } func formatAsCard(req *do.WarningBudget) string { var card strings.Builder - card.WriteString("### 🎫 " + req.StockName + "\n\n") + card.WriteString("### " + req.StockName + "\n\n") // 基本信息 - card.WriteString("#### 💰 基本信息\n") + card.WriteString("#### 🎫 基本信息\n") card.WriteString(fmt.Sprintf("- **批次号**: %s\n", req.StockId)) card.WriteString(fmt.Sprintf("- **活动号**: %s\n", req.StockNo)) card.WriteString(fmt.Sprintf("- **面额**: %d元\n", req.Amount)) diff --git a/internal/pkg/ding/ding.go b/internal/pkg/ding/ding.go index cfa9305..a2eacd6 100644 --- a/internal/pkg/ding/ding.go +++ b/internal/pkg/ding/ding.go @@ -117,11 +117,14 @@ func (c *TalkClient) SendLinkMessage(title, text, messageURL, picURL string, atM // SendMarkdownMessage 发送 Markdown 消息并可 @ 人员 func (c *TalkClient) SendMarkdownMessage(title, text string, atMobiles []string, isAtAll bool) error { - var atStr string - for _, mobile := range atMobiles { - atStr += fmt.Sprintf("@%s", mobile) + + if len(atMobiles) > 0 { + var atStr string + for _, mobile := range atMobiles { + atStr += fmt.Sprintf("@%s", mobile) + } + text += fmt.Sprintf("\n请尽快处理@相关人员%s", atStr) } - text += atStr message := map[string]interface{}{ "msgtype": "markdown", diff --git a/internal/pkg/ding/ding_test.go b/internal/pkg/ding/ding_test.go index c086bb7..494055b 100644 --- a/internal/pkg/ding/ding_test.go +++ b/internal/pkg/ding/ding_test.go @@ -142,14 +142,14 @@ func TestWarningBudgetText(t *testing.T) { markdownTitle := "批次预算预警" - //atMobiles := []string{"18666173766"} + atMobiles := []string{"18666173766"} webhookURL := "https://oapi.dingtalk.com/robot/send?access_token=5f10c2213cbf8168985cb2d061ebb1a5f70bd1dd47ec7cef58fa6fe545d52588" secret := "SEC77b63d70a9e22317144e712b4538ce1e0013db885c65f7f9bae283e8958b39eb" client := NewDingTalkClient(webhookURL, secret) - if err := client.SendMarkdownMessage(markdownTitle, str, nil, false); err != nil { + if err := client.SendMarkdownMessage(markdownTitle, str, atMobiles, false); err != nil { fmt.Println("Markdown 消息发送失败:", err) } else { fmt.Println("Markdown 消息发送成功") @@ -159,10 +159,10 @@ func TestWarningBudgetText(t *testing.T) { func formatAsCard(req do.WarningBudget) string { var card strings.Builder - card.WriteString("### 🎫 " + req.StockName + "\n\n") + card.WriteString("### " + req.StockName + "\n\n") // 基本信息 - card.WriteString("#### 💰 基本信息\n") + card.WriteString("#### 🎫 基本信息\n") card.WriteString(fmt.Sprintf("- **批次号**: %s\n", req.StockId)) card.WriteString(fmt.Sprintf("- **活动号**: %s\n", req.StockNo)) card.WriteString(fmt.Sprintf("- **面额**: %d元\n", req.Amount))