240 lines
6.0 KiB
Go
240 lines
6.0 KiB
Go
package biz
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/go-kratos/kratos/v2/log"
|
|
"github.com/wechatpay-apiv3/wechatpay-go/services/cashcoupons"
|
|
"strings"
|
|
"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))
|
|
|
|
if err := v.warningBudget(ctx); err != nil {
|
|
log.Errorf("预警查询,执行失败: %v", err)
|
|
}
|
|
|
|
end := time.Now()
|
|
elapsed := end.Sub(start)
|
|
log.Warnf("预警查询,开始执行时间%s,执行结束时间%s,代码块执行耗时: %s", start.Format(time.DateTime), end.Format(time.DateTime), elapsed)
|
|
|
|
return
|
|
}
|
|
|
|
func (v *VoucherBiz) warningBudget(ctx context.Context) error {
|
|
|
|
err := v.ProductRepo.FindWarningBudget(ctx, func(ctx context.Context, rows []*bo.ProductBo) error {
|
|
|
|
for _, row := range rows {
|
|
|
|
wxResp, err := v.WechatCpnRepo.QueryProduct(ctx, row.MchId, row.BatchNo)
|
|
if err != nil {
|
|
log.Context(ctx).Errorf("预警查询,查询微信券失败: %v", err)
|
|
} else {
|
|
if err = v.Calculate(ctx, row, wxResp); err != nil {
|
|
log.Context(ctx).Errorf("预警查询,处理失败: %v", err)
|
|
}
|
|
}
|
|
|
|
time.Sleep(time.Second * 2)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (v *VoucherBiz) GetWarningBudget(product *bo.ProductBo, wxResp *cashcoupons.Stock) (*do.WarningBudget, error) {
|
|
|
|
availableStock := *wxResp.StockUseRule.MaxCoupons - *wxResp.DistributedCoupons
|
|
couponAmount := *wxResp.StockUseRule.FixedNormalCoupon.CouponAmount / 100
|
|
|
|
remainingBudget := availableStock * couponAmount
|
|
|
|
stockUsageRate := float64(*wxResp.DistributedCoupons) / float64(*wxResp.StockUseRule.MaxCoupons) * 100
|
|
|
|
req := &do.WarningBudget{
|
|
StockName: product.Name,
|
|
StockId: product.BatchNo,
|
|
StockNo: product.ProductNo,
|
|
Amount: couponAmount,
|
|
AllBudget: *wxResp.StockUseRule.MaxAmount / 100,
|
|
AllStock: *wxResp.StockUseRule.MaxCoupons,
|
|
UsedStock: *wxResp.DistributedCoupons,
|
|
UsedBudget: *wxResp.DistributedCoupons * couponAmount,
|
|
AvailableStock: availableStock,
|
|
RemainingBudget: remainingBudget,
|
|
StockUsageRate: stockUsageRate,
|
|
}
|
|
|
|
inputFormat := time.RFC3339
|
|
|
|
if wxResp.AvailableBeginTime != nil {
|
|
availableBeginTime, _ := time.Parse(inputFormat, *wxResp.AvailableBeginTime)
|
|
req.StartTime = &availableBeginTime
|
|
}
|
|
|
|
if wxResp.AvailableEndTime != nil {
|
|
availableEndTime, _ := time.Parse(inputFormat, *wxResp.AvailableEndTime)
|
|
req.EndTime = &availableEndTime
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func (v *VoucherBiz) Calculate(ctx context.Context, product *bo.ProductBo, wxResp *cashcoupons.Stock) error {
|
|
|
|
req, err := v.GetWarningBudget(product, wxResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = v.ProductRepo.UpdateWarningBudget(ctx, product.ID, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if product.WarningBudget >= req.RemainingBudget {
|
|
|
|
count, err2 := v.WarningBudgetIncr(ctx, req.StockId)
|
|
if err2 != nil {
|
|
return err2
|
|
}
|
|
|
|
if count == 1 {
|
|
return v.WarningSend(ctx, formatAsCard(req))
|
|
} else {
|
|
log.Warnf("预警查询,当前达到预警第[%d]次,暂不做通知", count)
|
|
}
|
|
}
|
|
|
|
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("#### 🎫 基本信息\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))
|
|
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.RemainingBudget))
|
|
card.WriteString("\n")
|
|
|
|
card.WriteString("#### 📈 使用率\n")
|
|
card.WriteString(fmt.Sprintf("- **使用率**: %.1f%%\n", req.StockUsageRate))
|
|
|
|
return card.String()
|
|
}
|