221 lines
6.0 KiB
Go
221 lines
6.0 KiB
Go
package biz
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/go-kratos/kratos/v2/log"
|
|
"github.com/wechatpay-apiv3/wechatpay-go/services/cashcoupons"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"voucher/internal/biz/bo"
|
|
"voucher/internal/biz/do"
|
|
"voucher/internal/biz/vo"
|
|
)
|
|
|
|
func (this *VoucherBiz) Warning(ctx context.Context, batchNo string) error {
|
|
|
|
product, err := this.ProductRepo.GetByBatchNo(ctx, batchNo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return this.WarningBudget(ctx, product)
|
|
}
|
|
|
|
func (this *VoucherBiz) WarningBudgetIncr(ctx context.Context, key string, ttl time.Duration) (int64, error) {
|
|
|
|
// 增加发送计数
|
|
count, err := this.rdb.Rdb.IncrBy(ctx, key, 1).Result()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// 如果是第一次发送,设置 过期时间
|
|
if count == 1 {
|
|
if err = this.rdb.Rdb.Expire(ctx, key, ttl).Err(); err != nil {
|
|
return 0, fmt.Errorf("设置过期时间失败: %v", err)
|
|
}
|
|
}
|
|
|
|
// 如果发送次数超过 “指定” 条,清除再来
|
|
if count > 24 { // 大约2小时
|
|
return 0, this.WarningBudgetIncrDel(ctx, key)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (this *VoucherBiz) WarningBudgetIncrDel(ctx context.Context, key string) error {
|
|
|
|
// 检查键是否存在
|
|
exists, err := this.rdb.Rdb.Exists(ctx, key).Result()
|
|
if err != nil {
|
|
return fmt.Errorf("检查键存在性失败: %w", err)
|
|
}
|
|
|
|
// 如果键不存在,直接返回成功
|
|
if exists == 0 {
|
|
return nil
|
|
}
|
|
|
|
if _, err = this.rdb.Rdb.Del(ctx, key).Result(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (this *VoucherBiz) CronWarningBudget(ctx context.Context) {
|
|
|
|
uid := "warningBudget"
|
|
|
|
if b := this.Get(uid); b {
|
|
log.Warn("预警查询,上波还未执行完毕,此次暂不执行")
|
|
return
|
|
}
|
|
|
|
this.Add(uid)
|
|
defer this.Remove(uid)
|
|
|
|
start := time.Now()
|
|
log.Warnf("预警查询,执行开始: %s", start.Format(time.DateTime))
|
|
|
|
if err := this.cronWarningBudget(ctx); err != nil {
|
|
log.Errorf("预警查询,执行失败: %this", err)
|
|
}
|
|
|
|
end := time.Now()
|
|
elapsed := end.Sub(start)
|
|
log.Warnf("预警查询,开始执行时间%s,执行结束时间%s,代码块执行耗时: %s", start.Format(time.DateTime), end.Format(time.DateTime), elapsed)
|
|
|
|
return
|
|
}
|
|
|
|
func (this *VoucherBiz) cronWarningBudget(ctx context.Context) error {
|
|
|
|
return this.ProductRepo.FindWarningBudget(ctx, func(ctx context.Context, rows []*bo.ProductBo) error {
|
|
|
|
for _, row := range rows {
|
|
|
|
if err := this.WarningBudget(ctx, row); err != nil {
|
|
log.Context(ctx).Errorf("预警查询,处理失败: %this", err)
|
|
}
|
|
time.Sleep(time.Second * 2)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (this *VoucherBiz) WarningBudget(ctx context.Context, product *bo.ProductBo) error {
|
|
|
|
wxResp, err := this.WechatCpnRepo.QueryProduct(ctx, product.MchId, product.BatchNo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = this.Calculate(ctx, product, wxResp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (this *VoucherBiz) Calculate(ctx context.Context, product *bo.ProductBo, wxResp *cashcoupons.Stock) error {
|
|
|
|
w := this.WxResp(wxResp)
|
|
|
|
b := vo.WarningBudgetSendIncr.BuildCache([]string{product.BatchNo})
|
|
key := b.Key
|
|
ttl := b.TTL
|
|
|
|
if w.AllBudget > product.AllBudget {
|
|
if err := this.WarningBudgetIncrDel(ctx, key); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := this.ProductRepo.UpdateByWxResp(ctx, product.ID, w); err != nil {
|
|
return err
|
|
}
|
|
|
|
if product.WarningBudget >= w.AvailableBudget {
|
|
|
|
count, err2 := this.WarningBudgetIncr(ctx, key, ttl)
|
|
if err2 != nil {
|
|
return err2
|
|
}
|
|
|
|
if count == 1 {
|
|
return this.WarningSend(ctx, product, w)
|
|
} else {
|
|
log.Warnf("预警查询,当前达到预警第[%d]次,暂不做通知", count)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (this *VoucherBiz) WarningSend(ctx context.Context, product *bo.ProductBo, w *do.WxResp) error {
|
|
|
|
var warningPerson []*do.WarningPerson
|
|
if err := json.Unmarshal([]byte(product.WarningPerson), &warningPerson); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := this.DingMixRepo.SendMarkdownMessage(ctx, "券预算不足", formatAsCard(product, w)); err != nil {
|
|
return err
|
|
}
|
|
|
|
var mobileList []string
|
|
for _, person := range warningPerson {
|
|
mobileList = append(mobileList, person.Mobile)
|
|
}
|
|
|
|
return this.SmsMixRepo.Send(ctx, mobileList, buildTemplateParams(product, w))
|
|
}
|
|
|
|
func buildTemplateParams(product *bo.ProductBo, req *do.WxResp) map[string]string {
|
|
return map[string]string{
|
|
"stock_name": product.BatchName,
|
|
"stock_no": product.ProductNo,
|
|
"amount": strconv.Itoa(int(req.Amount)),
|
|
"all_budget": strconv.Itoa(int(req.AllBudget)),
|
|
"all_stock": strconv.Itoa(int(req.AllStock)),
|
|
"used_stock": strconv.Itoa(int(req.UsedStock)),
|
|
"used_budget": strconv.Itoa(int(req.UsedBudget)),
|
|
"available_budget": strconv.Itoa(int(req.AvailableBudget)),
|
|
"available_stock": strconv.Itoa(int(req.AvailableStock)),
|
|
"budget_usage_rate": fmt.Sprintf("%.1f", req.StockUsageRate),
|
|
}
|
|
}
|
|
|
|
func formatAsCard(product *bo.ProductBo, req *do.WxResp) string {
|
|
var card strings.Builder
|
|
|
|
card.WriteString("### " + product.BatchName + "\n\n")
|
|
|
|
// 基本信息
|
|
card.WriteString("#### 🎫 基本信息\n")
|
|
card.WriteString(fmt.Sprintf("- **批次号**: %s\n", product.BatchNo))
|
|
card.WriteString(fmt.Sprintf("- **活动号**: %s\n", product.ProductNo))
|
|
card.WriteString(fmt.Sprintf("- **预警值**: %d元\n", product.WarningBudget))
|
|
card.WriteString(fmt.Sprintf("- **面额**: %d元\n", req.Amount))
|
|
card.WriteString(fmt.Sprintf("- **总预算**: %d元\n", req.AllBudget))
|
|
card.WriteString(fmt.Sprintf("- **总库存**: %d张\n", req.AllStock))
|
|
card.WriteString("\n")
|
|
|
|
// 使用情况
|
|
card.WriteString("#### 📊 使用情况\n")
|
|
card.WriteString(fmt.Sprintf("- **已发券数**: %d张\n", req.UsedStock))
|
|
card.WriteString(fmt.Sprintf("- **已发券金额**: %d元\n", req.UsedBudget))
|
|
card.WriteString(fmt.Sprintf("- **剩余库存**: %d张\n", req.AvailableStock))
|
|
card.WriteString(fmt.Sprintf("- **剩余预算**: %d元\n", req.AvailableBudget))
|
|
card.WriteString("\n")
|
|
|
|
card.WriteString("#### 📈 使用率\n")
|
|
card.WriteString(fmt.Sprintf("- **使用率**: %.1f%%\n", req.StockUsageRate))
|
|
|
|
return card.String()
|
|
}
|