package models import ( "bytes" "encoding/json" "errors" "fmt" utils "go-admin/app/admin/models/tools" orm "go-admin/common/global" "go-admin/logger" "go-admin/tools/config" "io/ioutil" "log" "net/http" "net/url" "strings" "time" ) const ( ErpCouponUnStarted = 1 // 未开始 ErpCouponSending = 2 // 发放中 ErpCouponInProgress = 3 // 进行中 ErpCouponFinished = 4 // 已结束 ErpCouponSendFailed = 5 // 发放失败 TaskInProgress = "in_progress" // 执行中 TaskCompleted = "completed" // 已完成 TaskFailed = "failed" // 发送失败 AccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token" WXAppID = "wx806c079463b5b56c" WXSecret = "cb125688bf4e482f66e8c46062d568fc" WxSubscribeMessage = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" TemplateId = "aP3c503T3wn-tJtpDccvxtNi7b1qjrwavFFfo-k2SAQ" WxJumpUrl = "/pages/voucher/voucher" // 跳转微信小程序的页面路径 WXGenerateSchema = "https://api.weixin.qq.com/wxa/generatescheme?access_token=" SMSHead = "【go2ns】" ) // WxAccessToken 微信access_token type WxAccessToken struct { Model Appid string `json:"appid"` // 小程序appid AccessToken string `json:"access_token"` // 获取到的凭证 ExpiresTime time.Time `json:"expires_time"` // 凭证有效时间 } // CouponIssuanceTask 优惠券发放任务表 type CouponIssuanceTask struct { Model ErpCouponId uint32 `json:"erp_coupon_id"` // 优惠券id ErpCouponName string `json:"erp_coupon_name"` // 优惠券名称 Status string `json:"status"` // 任务状态:in_progress(执行中)、completed(已完成)、failed(发送失败) LastUserID uint32 `json:"last_user_id"` // 上次处理的用户ID ErrorMessage string `json:"error_message" gorm:"type:text"` // 错误信息,若任务失败时记录 } // ErpCoupon 营销管理-优惠券 type ErpCoupon struct { Model Name string `json:"name" gorm:"index"` // 优惠券名称 Remark string `json:"remark"` // 备注 Describe string `json:"describe" gorm:"type:text"` // 优惠券简介 Rule string `json:"rule" gorm:"type:text"` // 优惠券使用规则 CategoryNumber string `json:"category_number"` // 可以使用该优惠券的商品分类,如果为空则表示没限制 ActiveDate uint32 `json:"active_date"` // 有效期(天) Amount uint32 `json:"amount"` // 金额(元) UserType uint32 `json:"user_type"` // 领取人限制:1-所有人 2-未付费用户 3-已付费用户 4-尊享会员 Limit uint32 `json:"limit"` // 优惠券叠加限制 0-不限制;1-仅限原价购买时使用 StartTime *time.Time `json:"start_time"` // 启动时间 State uint32 `json:"state" gorm:"index"` // 当前状态 1-未开始;2-发放中;3-进行中;4-已结束;5-发放失败 SendCount uint32 `json:"send_count"` // 已发放 UsedCount uint32 `json:"used_count"` // 已使用 TotalPayAmount float64 `json:"total_pay_amount"` // 支付金额(元) PerCustomerAmount float64 `json:"per_customer_amount"` // 客单价(元) SmsContent string `json:"sms_content" gorm:"type:text"` // 短信提示内容 } // ErpMarketingCouponListReq 优惠券列表入参 type ErpMarketingCouponListReq struct { Name string `json:"name" gorm:"index"` // 优惠券名称 State uint32 `json:"state" gorm:"index"` // 当前状态 1-未开始;2-发放中;3-进行中;4-已结束 CreatedTimeStart string `json:"created_time_start"` // 创建开始时间 CreatedTimeEnd string `json:"created_time_end"` // 创建结束时间 PageIndex int `json:"pageIndex"` // 页码 PageSize int `json:"pageSize"` // 页面条数 IsExport uint32 `json:"is_export"` // 1-导出 } // ErpMarketingCouponListResp 优惠券列表出参 type ErpMarketingCouponListResp struct { List []ErpCoupon `json:"list"` Total int `json:"total"` // 总条数 PageIndex int `json:"pageIndex"` // 页码 PageSize int `json:"pageSize"` // 页面条数 ExportUrl string `json:"export_url"` // 导出excel路径 } // ErpMarketingCouponCreateReq 新增优惠券入参 type ErpMarketingCouponCreateReq struct { Name string `json:"name" validate:"required"` // 优惠券名称 Remark string `json:"remark"` // 备注 Describe string `json:"describe" gorm:"type:text"` // 优惠券简介 Rule string `json:"rule" gorm:"type:text"` // 优惠券使用规则 CategoryNumber string `json:"category_number" validate:"required"` // 可以使用该优惠券的商品分类编号,如果为空则表示没限制 ActiveDate uint32 `json:"active_date" validate:"required"` // 有效期(天) Amount uint32 `json:"amount" validate:"required"` // 金额(元) UserType uint32 `json:"user_type" validate:"required"` // 领取人限制:1-所有人 2-未付费用户 3-已付费用户 4-尊享会员 Limit uint32 `json:"limit"` // 优惠券叠加限制 0-不限制;1-仅限原价购买时使用 SmsContent string `json:"sms_content" validate:"required"` // 短信提示内容 } // ErpMarketingCouponEditReq 编辑优惠券入参 type ErpMarketingCouponEditReq struct { ErpCouponId uint32 `json:"erp_coupon_id" validate:"required"` // 优惠券id Name string `json:"name" validate:"required"` // 优惠券名称 Remark string `json:"remark"` // 名称备注 Describe string `json:"describe" gorm:"type:text"` // 优惠券简介 Rule string `json:"rule" gorm:"type:text"` // 优惠券使用规则 CategoryNumber string `json:"category_number" validate:"required"` // 可以使用该优惠券的商品分类,如果为空则表示没限制 ActiveDate uint32 `json:"active_date" validate:"required"` // 有效期(天) Amount uint32 `json:"amount" validate:"required"` // 金额(元) UserType uint32 `json:"user_type" validate:"required"` // 领取人限制:1-所有人 2-未付费用户 3-已付费用户 4-尊享会员 Limit uint32 `json:"limit"` // 优惠券叠加限制 0-不限制;1-仅限原价购买时使用 SmsContent string `json:"sms_content" validate:"required"` // 短信提示内容 } // ErpMarketingCouponDetailReq 优惠券详情入参 type ErpMarketingCouponDetailReq struct { ErpCouponId uint32 `json:"erp_coupon_id" validate:"required"` // 优惠券id } // ErpMarketingCouponDeleteReq 删除优惠券入参 type ErpMarketingCouponDeleteReq struct { ErpCouponId uint32 `json:"erp_coupon_id" validate:"required"` // 优惠券id } // ErpMarketingCouponStartReq 启动优惠券发放 type ErpMarketingCouponStartReq struct { ErpCouponId []uint32 `json:"erp_coupon_id" validate:"required"` // 优惠券id Remark string `json:"remark" validate:"required"` // 活动名称备注 SmsContent string `json:"sms_content" validate:"required"` // 短信提示内容 } // ErpMarketingCouponDataReq 优惠券数据入参 type ErpMarketingCouponDataReq struct { ErpCouponId uint32 `json:"erp_coupon_id" validate:"required"` // 优惠券id } // ErpMarketingCouponDataResp 优惠券数据出参 type ErpMarketingCouponDataResp struct { TotalAmount float64 `json:"total_amount"` // 用券总成交额:使用该优惠券的订单付款总金额 TotalDiscount float64 `json:"total_discount"` // 优惠总金额:使用该优惠券优惠的总金额 CostEffectiveness float64 `json:"cost_effectiveness"` // 费效比:优惠总金额 / 用券总成交额 OrderCount int `json:"order_count"` // 订单数:使用该优惠券的付款订单数 UnitPrice float64 `json:"unit_price"` // 用券笔单价:用券总成交额 / 使用该优惠券的付款订单数 CustomerCount int `json:"customer_count"` // 用券客户数:使用该优惠券的成交客户数 ProductCount int `json:"product_count"` // 购买商品件数:使用该优惠券购买的商品数量 Products []ProductInfo `json:"products"` // 商品信息:包含每个商品的ID、名称、付款件数、付款人数等信息 } // ProductInfo 商品信息结构体 type ProductInfo struct { ProductID int `json:"product_id"` // 商品ID ProductName string `json:"product_name"` // 商品名称 PayCount int `json:"pay_count"` // 付款件数:使用该优惠券购买该商品的付款件数 PayCustomer int `json:"pay_customer"` // 付款人数:购买该商品并使用优惠券的客户人数 } type GenerateSchemeReq struct { Path string `json:"path" validate:"required"` // 跳转小程序页面 EnvVersion string `json:"env_version"` // 默认值"release"。要打开的小程序版本。正式版为"release",体验版为"trial",开发版为"develop" } type GenerateSchemeResp struct { Openlink string `json:"openlink"` // 小程序跳转链接 } type WXGenerateSchemeResponse struct { Errcode int `json:"errcode"` Errmsg string `json:"errmsg"` Openlink string `json:"openlink"` } // List 查询优惠券列表 func (m *ErpMarketingCouponListReq) List() (*ErpMarketingCouponListResp, error) { resp := &ErpMarketingCouponListResp{ PageIndex: m.PageIndex, PageSize: m.PageSize, } page := m.PageIndex - 1 if page < 0 { page = 0 } if m.PageSize == 0 { m.PageSize = 10 } qs := orm.Eloquent.Table("erp_coupon") if m.Name != "" { qs = qs.Where("name = ?", m.Name) } if m.State != 0 { qs = qs.Where("state = ?", m.State) } if m.CreatedTimeStart != "" { parse, err := time.Parse(QueryTimeFormat, m.CreatedTimeStart) if err != nil { logger.Errorf("erpPurchaseOrderList err:", err) return nil, err } qs = qs.Where("created_at > ?", parse) } if m.CreatedTimeEnd != "" { parse, err := time.Parse(QueryTimeFormat, m.CreatedTimeEnd) if err != nil { logger.Errorf("erpPurchaseOrderList err:", err) return nil, err } qs = qs.Where("created_at < ?", parse) } var count int64 err := qs.Count(&count).Error if err != nil { logger.Error("count err:", logger.Field("err", err)) return resp, err } resp.Total = int(count) var couponList []ErpCoupon err = qs.Order("id DESC").Offset(page * m.PageSize).Limit(m.PageSize).Find(&couponList).Error if err != nil && err != RecordNotFound { logger.Error("erp_coupon list err:", logger.Field("err", err)) return resp, err } for i, v := range couponList { couponList[i].Amount = v.Amount / 100 } resp.List = couponList return resp, nil } // CreateErpMarketingCoupon 新增优惠券 func CreateErpMarketingCoupon(req *ErpMarketingCouponCreateReq) error { erpCoupon := &ErpCoupon{ Name: req.Name, Remark: req.Remark, Describe: req.Describe, Rule: req.Rule, CategoryNumber: req.CategoryNumber, ActiveDate: req.ActiveDate, Amount: req.Amount * 100, UserType: req.UserType, Limit: req.Limit, State: ErpCouponUnStarted, SmsContent: req.SmsContent, } err := orm.Eloquent.Create(erpCoupon).Error if err != nil { logger.Error("create purchase order err:", logger.Field("err", err)) return err } coupon := &Coupon{ Name: req.Name, Describe: req.Describe, Rule: req.Rule, CouponType: "deduction", Value: req.Amount * 100, CategoryNumber: req.CategoryNumber, ActivityType: 666, ErpCouponId: erpCoupon.ID, ActiveStart: Now(), ActiveEnd: Now().AddDate(0, 0, int(req.ActiveDate)), Limit: req.Limit, } err = orm.Eloquent.Create(coupon).Error if err != nil { logger.Error("create coupon order err:", logger.Field("err", err)) return err } return nil } // EditErpMarketingCoupon 编辑优惠券 func EditErpMarketingCoupon(req *ErpMarketingCouponEditReq) error { // 查询订单信息 var erpCoupon ErpCoupon err := orm.Eloquent.Table("erp_coupon").Where("id=?", req.ErpCouponId).Find(&erpCoupon).Error if err != nil { logger.Error("query erp_coupon err:", logger.Field("err", err)) return err } if erpCoupon.ID == 0 { logger.Error("delete err, erpCoupon ID is:", logger.Field("erpCoupon.ID", req.ErpCouponId)) return errors.New(fmt.Sprintf("编辑失败:未查询到优惠券id[%d]", req.ErpCouponId)) } // 1-更新优惠券信息 erpCoupon.Name = req.Name erpCoupon.Remark = req.Remark erpCoupon.Rule = req.Rule erpCoupon.Describe = req.Describe erpCoupon.CategoryNumber = req.CategoryNumber erpCoupon.ActiveDate = req.ActiveDate erpCoupon.Amount = req.Amount * 100 erpCoupon.UserType = req.UserType erpCoupon.Limit = req.Limit erpCoupon.SmsContent = req.SmsContent begin := orm.Eloquent.Begin() err = begin.Model(&ErpCoupon{}).Where("id = ?", req.ErpCouponId). Omit("created_at").Save(erpCoupon).Error if err != nil { begin.Rollback() logger.Error("update erp_coupon err:", logger.Field("err", err)) return err } // 查询订单信息 var coupon Coupon err = orm.Eloquent.Table("coupon").Where("erp_coupon_id=?", req.ErpCouponId).Find(&coupon).Error if err != nil { begin.Rollback() logger.Error("query coupon err:", logger.Field("err", err)) return err } coupon.Name = req.Name coupon.Describe = req.Describe coupon.Rule = req.Rule coupon.Value = req.Amount * 100 coupon.ActiveStart = Now() coupon.ActiveEnd = Now().AddDate(0, 0, int(req.ActiveDate)) err = begin.Model(&Coupon{}).Where("erp_coupon_id = ?", req.ErpCouponId). Omit("created_at").Save(coupon).Error if err != nil { begin.Rollback() logger.Error("update coupon err:", logger.Field("err", err)) return err } err = begin.Commit().Error if err != nil { begin.Rollback() logger.Error("commit update erp_coupon err:", logger.Field("err", err)) return err } return nil } // DeleteErpMarketingCoupon 删除优惠券 func DeleteErpMarketingCoupon(req *ErpMarketingCouponDeleteReq) error { // 查询订单信息 var erpCoupon ErpCoupon err := orm.Eloquent.Table("erp_coupon").Where("id=?", req.ErpCouponId).Find(&erpCoupon).Error if err != nil { logger.Error("purchase order err:", logger.Field("err", err)) return err } if erpCoupon.ID == 0 { logger.Error("delete err, erpCoupon ID is:", logger.Field("erpCoupon.ID", req.ErpCouponId)) return errors.New(fmt.Sprintf("删除失败:未查询到优惠券id[%d]", req.ErpCouponId)) } // 仅未开始和已结束的订单可删除 if erpCoupon.State != ErpCouponUnStarted && erpCoupon.State != ErpCouponFinished { logger.Error("delete err, erpCoupon.State is:", logger.Field("erpCoupon.State", erpCoupon.State)) return errors.New("删除失败:仅未开始和已结束的优惠券可删除") } // 删除优惠订单 begin := orm.Eloquent.Begin() err = begin.Delete(erpCoupon).Error if err != nil { begin.Rollback() logger.Error("erp_coupon delete err:", logger.Field("err", err)) return err } err = begin.Table("coupon").Where("erp_coupon_id", req.ErpCouponId).Delete(&Coupon{}).Error if err != nil { begin.Rollback() logger.Error("coupon delete err:", logger.Field("err", err)) return err } err = begin.Commit().Error if err != nil { begin.Rollback() logger.Error("commit erp_coupon delete err:", logger.Field("err", err)) return err } return nil } // StartCouponIssuanceTask 启动发放优惠券的任务 func StartCouponIssuanceTask(req *ErpMarketingCouponStartReq, taskList []CouponIssuanceTask) error { var erpCouponList []ErpCoupon err := orm.Eloquent.Table("erp_coupon").Where("id in ?", req.ErpCouponId).Find(&erpCouponList).Error if err != nil { logger.Error("purchase order err:", logger.Field("err", err)) return err } if len(taskList) < len(erpCouponList) { for _, erpCoupon := range erpCouponList { exitFlag := false for _, task := range taskList { if erpCoupon.ID == task.ErpCouponId { exitFlag = true break } else { continue } } if !exitFlag { // 新建任务 taskInfo := CouponIssuanceTask{ ErpCouponId: erpCoupon.ID, ErpCouponName: erpCoupon.Name, Status: TaskInProgress, LastUserID: 0, ErrorMessage: "", } err := orm.Eloquent.Create(&taskInfo).Error if err != nil { logger.Error("create CouponIssuanceTask err:", logger.Field("err", err)) return err } taskList = append(taskList, taskInfo) } } } // 遍历taskList,获取最小的lastUserID var lastUserID uint32 for _, task := range taskList { if lastUserID == 0 { lastUserID = task.LastUserID } if lastUserID > task.LastUserID { lastUserID = task.LastUserID } } var nTotalCount int // 处理任务,从上次中断的用户ID开始 for { // 查询用户ID大于 lastUserID 的用户 users, err := getUsersAfterID(lastUserID, erpCouponList[0].UserType) if err != nil || len(users) == 0 { log.Println("没有更多用户,任务结束") break } // 发放优惠券 count, err := issueCouponsToUsers(users, req.ErpCouponId) if err != nil { log.Printf("发放优惠券失败: %v", err) return err } // 更新任务进度:记录处理到的最后一个用户ID lastUserID = users[len(users)-1].ID nTotalCount += count //err = updateTaskProgress(req.ErpCouponId, lastUserID, err.Error()) //if err != nil { // log.Printf("更新任务进度失败: %v", err) // break //} } // 任务完成,更新状态 err = markTaskAsCompleted(req.ErpCouponId, nTotalCount) if err != nil { return nil } return nil } // CheckUserType 校验批量选择的优惠券使用人群是否相同 func CheckUserType(erpCouponId []uint32) bool { var erpCouponList []ErpCoupon err := orm.Eloquent.Table("erp_coupon").Where("id in ?", erpCouponId).Find(&erpCouponList).Error if err != nil || err == RecordNotFound { logger.Error("query erp_coupon err:", logger.Field("err", err)) return false } var userType uint32 for _, erpCoupon := range erpCouponList { if userType == 0 { userType = erpCoupon.UserType } if userType != erpCoupon.UserType { return false } } return true } // GetTaskProgress 查询优惠券任务执行情况 func GetTaskProgress(erpCouponId []uint32) ([]CouponIssuanceTask, error) { var task []CouponIssuanceTask err := orm.Eloquent.Table("coupon_issuance_task").Where("erp_coupon_id in ?", erpCouponId). Order("updated_at desc").First(&task).Error if err != nil && err.Error() != "record not found" { return nil, err } return task, nil } func getUsersAfterID(lastUserID, userType uint32) ([]UserInfo, error) { var err error var users []UserInfo // 获取尊享会员信息 var privilegeUserList []PrivilegeMember switch userType { case 2: // 2-未付费用户:MemberLevel 不能是 (2, 3, 4, 5),不包含已过期的租卡用户 err = orm.Eloquent.Model(&UserInfo{}).Where("id > ? and member_level not in ? and member_expire = ?", lastUserID, []uint32{MemberLevelGold, MemberLevelPeriod, MemberLevelPlatinum, MemberLevelBlackGold}, "0000-00-00 00:00:00").Order("id asc").Limit(100).Find(&users).Error case 3: // 3-已付费用户:MemberLevel 必须是 (2, 3, 4, 5) err = orm.Eloquent.Model(&UserInfo{}).Where("id > ? and member_level in ?", lastUserID, []uint32{ MemberLevelGold, MemberLevelPeriod, MemberLevelPlatinum, MemberLevelBlackGold}). Order("id asc").Limit(100).Find(&users).Error case 4: // 尊享会员 err = orm.Eloquent.Model(&PrivilegeMember{}).Where("id > ? and member_level = ?", lastUserID, MemberLevelPrivilege).Order("id asc").Limit(100).Find(&privilegeUserList).Error if err != nil { return nil, err } for _, privilegeUser := range privilegeUserList { user, _ := GetUserInfoByUid(privilegeUser.Uid) if user.ID == 0 { // 没查到数据 user.Uid = privilegeUser.Uid user.Tel = privilegeUser.Tel } user.ID = privilegeUser.ID user.MemberLevel = MemberLevelPrivilege users = append(users, user) } case 5: // 测试用户 if lastUserID == 0 { users, err = GetTestUserConfig() if err != nil { return nil, err } } case 1: // 1-所有人:不限制会员等级,所有人均可领取 fallthrough default: err = orm.Eloquent.Model(&UserInfo{}).Where("id > ?", lastUserID). Order("id asc").Limit(100).Find(&users).Error } if err != nil { return nil, err } return users, nil } func updateTaskProgress(erpCouponId []uint32, lastUserID uint32, errMsg string) error { err := orm.Eloquent.Table("coupon_issuance_task").Where("erp_coupon_id in ?", erpCouponId). Updates(map[string]interface{}{ "last_user_id": lastUserID, "error_message": errMsg, "updated_at": time.Now(), }).Error if err != nil { return err } return nil } func markTaskAsCompleted(erpCouponId []uint32, nTotalCount int) error { err := orm.Eloquent.Table("coupon_issuance_task").Where("erp_coupon_id in ?", erpCouponId). Updates(map[string]interface{}{ "Status": TaskCompleted, "updated_at": time.Now(), }).Error if err != nil { log.Printf("更新任务完成状态失败: %v", err) return err } err = orm.Eloquent.Table("erp_coupon").Where("id in ?", erpCouponId). Updates(map[string]interface{}{ "state": ErpCouponInProgress, "send_count": nTotalCount, "updated_at": time.Now(), }).Error if err != nil { log.Printf("更新任务完成状态失败: %v", err) return err } log.Println("任务完成") return nil } // 校验用户是否符合领取优惠券的条件 func isEligibleForCoupon(user UserInfo, erpCoupon ErpCoupon) bool { switch erpCoupon.UserType { case 1: // 1-所有人:不限制会员等级,所有人均可领取 return true case 2: // 2-未付费用户:MemberLevel 不能是 (2, 3, 4, 5) if user.MemberLevel == MemberLevelGold || user.MemberLevel == MemberLevelPeriod || user.MemberLevel == MemberLevelPlatinum || user.MemberLevel == MemberLevelBlackGold { return false } if IsValidPrivilegeMember(user.Uid) { // 如果是尊享会员 return false } return true case 3: // 3-已付费用户:MemberLevel 必须是 (2, 3, 4, 5) if user.MemberLevel == MemberLevelGold || user.MemberLevel == MemberLevelPeriod || user.MemberLevel == MemberLevelPlatinum || user.MemberLevel == MemberLevelBlackGold { return true } if IsValidPrivilegeMember(user.Uid) { // 如果是尊享会员 return true } return false case 4: // 尊享会员 if user.MemberLevel == MemberLevelPrivilege { return true } if IsValidPrivilegeMember(user.Uid) { // 如果是尊享会员 return true } return false case 5: // 测试用户 if user.MemberLevel == MemberLevelTest { return true } return false default: // 未知类型,不发放 return false } } func issueCouponsToUsers(users []UserInfo, erpCouponId []uint32) (int, error) { var erpCoupon []ErpCoupon err := orm.Eloquent.Table("erp_coupon").Where("id in ?", erpCouponId).Find(&erpCoupon).Error if err != nil || err == RecordNotFound { logger.Error("query erp_coupon err:", logger.Field("err", err)) return 0, err } var nSendCount int for _, user := range users { // 根据条件发放优惠券 if isEligibleForCoupon(user, erpCoupon[0]) { // 发放优惠券 var coupons []Coupon err = orm.Eloquent.Table("coupon").Where("erp_coupon_id in ?", erpCouponId).Find(&coupons).Error if err != nil { logger.Error("query coupon err:", logger.Field("err", err)) return 0, err } for _, coupon := range coupons { duration := coupon.ActiveEnd.Sub(coupon.ActiveStart).Hours() / 24 couponCode, _ := utils.GenerateRandomNumber19() userCoupon := &UserCoupon{ Uid: user.Uid, CouponId: coupon.ID, CouponType: coupon.CouponType, Value: coupon.Value, State: 1, ActiveStart: time.Now(), ActiveEnd: time.Now().Add(time.Duration(duration) * 24 * time.Hour), RedeemCode: "", CategoryNumber: coupon.CategoryNumber, Code: couponCode, ActivityType: coupon.ActivityType, Limit: coupon.Limit, } err = orm.Eloquent.Create(userCoupon).Error if err != nil { logger.Error("create user coupon err:", logger.Field("err", err)) err = updateTaskProgress(erpCouponId, user.ID, err.Error()) if err != nil { log.Printf("更新任务进度失败: %v", err) } return 0, err } } // 发送订阅通知 var wxToken WxAccessToken err = orm.Eloquent.Table("wx_access_token").Where("appid = ?", WXAppID).First(&wxToken).Error if err != nil { logger.Error("query wx_access_token err:", logger.Field("err", err)) } if wxToken.ID == 0 || wxToken.ExpiresTime.Before(time.Now()) { CheckAccessToken() err = orm.Eloquent.Table("wx_access_token").Where("appid = ?", WXAppID).First(&wxToken).Error if err != nil { logger.Error("query wx_access_token err:", logger.Field("err", err)) } } toUserOpenid := user.WxOpenID templateId := TemplateId data := map[string]interface{}{ "thing1": map[string]interface{}{"value": coupons[0].Name}, "thing5": map[string]interface{}{"value": coupons[0].Describe}, "date3": map[string]interface{}{"value": coupons[0].ActiveEnd.Format("2006年01月02日")}, "thing4": map[string]interface{}{"value": coupons[0].Rule}, } //developer为开发版;trial为体验版;formal为正式版;默认为正式版 miniProgramState := config.MessageConfig.MiniProgramState _, err = SendMessage(wxToken.AccessToken, toUserOpenid, templateId, data, WxJumpUrl, miniProgramState) if err != nil { logger.Errorf("SendMessage err:", err) // 如果订阅通知发送失败,则发送短信 err = GtSendMessage([]string{user.Tel}, ComposeSMSContent(erpCoupon[0].SmsContent)) if err != nil { logger.Error(err.Error()) } } nSendCount++ } } return nSendCount, nil } func ComposeSMSContent(content string) string { // 1. 判断短信内容开头是否有【go2ns】,没有的话添加上 if !strings.HasPrefix(content, SMSHead) { content = SMSHead + content } // 2. 判断短信内容中是否有 http 开头的链接,没有的话在最后加上 if !strings.Contains(content, "http") { content += " 详情请点击:" + config.MessageConfig.SmsUrl } return content } // Response 请求微信返回基础数据 type Response struct { Errcode int `json:"errcode"` Errmsg string `json:"errmsg"` } // AccessTokenResponse 返回给用户的数据 type AccessTokenResponse struct { AccessToken string `json:"access_token"` // 获取到的凭证 ExpiresIn int64 `json:"expires_in"` // 凭证有效时间,单位:秒。目前是7200秒之内的值。 } type accessTokenResponse struct { Response AccessTokenResponse } // CheckAccessToken 定期更新access_token func CheckAccessToken() { accessToken, err := GetWxAccessToken() if err != nil { logger.Error("query wx_access_token err:", logger.Field("err", err)) } else { now := time.Now() twoHoursLater := now.Add(2 * time.Hour) // 查询access_token var wxToken WxAccessToken err = orm.Eloquent.Table("wx_access_token").Where("appid = ?", WXAppID).First(&wxToken).Error if err != nil { logger.Error("query wx_access_token err:", logger.Field("err", err)) } if wxToken.ID != 0 { // 更新 // 更新access_token err = orm.Eloquent.Table("wx_access_token").Where("appid = ?", WXAppID). Updates(map[string]interface{}{ "access_token": accessToken.AccessToken, "expires_time": twoHoursLater, }).Error if err != nil { logger.Errorf("更新wx_access_token失败: %v", err) } } else { // 插入 tempToken := &WxAccessToken{ Appid: WXAppID, AccessToken: accessToken.AccessToken, ExpiresTime: twoHoursLater, } err = orm.Eloquent.Create(tempToken).Error if err != nil { logger.Error("create wx_access_token err:", logger.Field("err", err)) } } } } // GetWxAccessToken 获取小程序的access_token func GetWxAccessToken() (lures AccessTokenResponse, err error) { api, err := code2url(WXAppID, WXSecret) if err != nil { return } res, err := http.Get(api) if err != nil { return } defer res.Body.Close() if res.StatusCode != 200 { err = errors.New("微信服务器发生错误") return } var data accessTokenResponse err = json.NewDecoder(res.Body).Decode(&data) if err != nil { return } if data.Errcode != 0 { err = errors.New(data.Errmsg) return } lures = data.AccessTokenResponse return } // 拼接 获取 session_key 的 URL func code2url(appID, secret string) (string, error) { url, err := url.Parse(AccessTokenUrl) if err != nil { return "", err } query := url.Query() query.Set("appid", appID) query.Set("secret", secret) query.Set("grant_type", "client_credential") url.RawQuery = query.Encode() return url.String(), nil } // SendMessageReq 消息订阅请求参数 type SendMessageReq struct { TemplateId string `json:"template_id"` Page string `json:"page"` Touser string `json:"touser"` Data interface{} `json:"data"` MiniprogramState string `json:"miniprogram_state"` Lang string `json:"lang"` } // SendMessage 发送订阅消息 func SendMessage(accessToken, toUserOpenid, templateId string, data interface{}, page, miniProgramState string) ( sendMessageRsp *Response, err error) { url := fmt.Sprintf("%s%s", WxSubscribeMessage, accessToken) body := SendMessageReq{ TemplateId: templateId, Page: page, Touser: toUserOpenid, Data: data, MiniprogramState: miniProgramState, Lang: "zh_CN", } respRaw, err := HttpRequest(http.MethodPost, url, body) rsp := Response{} if err = json.Unmarshal(respRaw, &rsp); err != nil { return nil, err } if rsp.Errcode != 0 { return nil, errors.New(rsp.Errmsg) } return &rsp, err } func HttpRequest(method, url string, reqBody interface{}) ([]byte, error) { client := http.Client{} reqBodyBytes, err := json.Marshal(reqBody) if err != nil { return nil, err } reqBodyReader := bytes.NewReader(reqBodyBytes) req, err := http.NewRequest(method, url, reqBodyReader) if err != nil { return nil, err } rsp, err := client.Do(req) if err != nil { return nil, err } defer rsp.Body.Close() if rsp.StatusCode != 200 { return nil, errors.New(fmt.Sprintf("response Status err:%d", rsp.StatusCode)) } rspBody, err := ioutil.ReadAll(rsp.Body) if err != nil { return nil, err } return rspBody, nil } // GetUserCouponIdsByErpCouponId 通过erp的优惠券id查找用户的优惠券id列表 func GetUserCouponIdsByErpCouponId(erpCouponId uint32) ([]uint32, error) { // 先查询 Coupon 表,根据 ErpCouponId 获取 coupon_id var coupon Coupon err := orm.Eloquent.Where("erp_coupon_id = ?", erpCouponId).First(&coupon).Error if err != nil { // 如果未找到对应优惠券ID,返回错误 return nil, fmt.Errorf("优惠券ID不存在或查询失败") } // 使用 coupon_id 查找 UserCoupon 表中的记录,获取所有符合条件的记录 var userCoupons []UserCoupon err = orm.Eloquent.Where("coupon_id = ?", coupon.ID).Find(&userCoupons).Error if err != nil { // 如果查询失败,返回错误 return nil, fmt.Errorf("查询 UserCoupon 记录失败") } // 提取所有找到的 userCoupon 的 ID var userCouponIds []uint32 for _, userCoupon := range userCoupons { userCouponIds = append(userCouponIds, userCoupon.ID) } // 返回所有 UserCoupon ID return userCouponIds, nil } // WXGenerateScheme 获取小程序跳转链接 func WXGenerateScheme(req *GenerateSchemeReq) (*GenerateSchemeResp, error) { var rsp WXGenerateSchemeResponse var wxToken WxAccessToken err := orm.Eloquent.Table("wx_access_token").Where("appid = ?", WXAppID).First(&wxToken).Error if err != nil { logger.Error("query wx_access_token err:", logger.Field("err", err)) } if wxToken.ID == 0 || wxToken.ExpiresTime.Before(time.Now()) { CheckAccessToken() err = orm.Eloquent.Table("wx_access_token").Where("appid = ?", WXAppID).First(&wxToken).Error if err != nil { logger.Error("query wx_access_token err:", logger.Field("err", err)) } } url := WXGenerateSchema + wxToken.AccessToken /* { "jump_wxa": { "path": "/pages/startPage/index", "query": "", "env_version": "release" }, "expire_type": 1, "expire_interval": 30 } */ reqScheme := map[string]interface{}{ "jump_wxa": map[string]string{ "path": req.Path, "env_version": req.EnvVersion, }, "expire_type": 1, //小程序 URL Link 失效类型,时间戳:0,间隔天数:1 "expire_interval": 30, //最长间隔天数为30天。expire_type 为 1 必填 } data, err := HttpRequest(http.MethodPost, url, reqScheme) /* { "errcode": 0, //成功返回 "errmsg": "ok", "url_link": "https://wxaurl.cn/pjKMfcQsULj" } */ if err = json.Unmarshal(data, &rsp); err != nil { return nil, err } if rsp.Errcode != 0 { return nil, errors.New(fmt.Sprintf("生成小程序跳转链接失败:errcode %d,errmsg %s", rsp.Errcode, rsp.Errmsg)) } resp := GenerateSchemeResp{ Openlink: rsp.Openlink, } return &resp, nil }