mh_goadmin_server/app/admin/models/marketing.go

1054 lines
33 KiB
Go
Raw Normal View History

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"` // 短信提示内容
}
2024-12-06 03:23:00 +00:00
// 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
}