You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

422 lines
12 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package notify
import (
"context"
"joybuywork/internal/app"
"joybuywork/internal/consts"
"joybuywork/internal/consts/ckey"
"joybuywork/internal/plugins/hashid"
"joybuywork/internal/plugins/jsonm"
"joybuywork/internal/repository/cache"
"joybuywork/internal/repository/dao"
"joybuywork/internal/repository/dbcheck"
"joybuywork/internal/repository/model"
"joybuywork/internal/repository/mtype"
"joybuywork/internal/repository/withdefault"
"joybuywork/internal/service/queue/producer"
"joybuywork/internal/service/queue/types"
"joybuywork/internal/utils"
"fmt"
"strings"
"time"
"gits.branchcn.com/backend/microcode/plugins/derivative/lock"
"gorm.io/gen/field"
)
var delaySecMap = map[int32]int{
0: 15, 1: 15,
2: 30,
3: 180,
4: 600,
5: 1200,
6: 1800, 7: 1800, 8: 1800,
9: 3600,
10: 3600 * 3, 11: 3600 * 3, 12: 3600 * 3,
13: 3600 * 6, 14: 3600 * 6,
}
type Reward struct {
//非传输数据
CampaignId int64 `json:"campaign_id"` //活动id
RewardId int64 `json:"reward_id"` //领取的用户奖品记录id(fission_reward_record表主键)
UserId int64 `json:"user_id"` //用户id
ParentId int64 `json:"parent_id"` //上级用户id
//传输数据
CampaignHashId string `json:"campaign_hash_id"` //活动hash_id(fission_campaign表)
RewardHashId string `json:"reward_hash_id"` //奖品hash_id(fission_reward_record表)
KvData []*mtype.KV `json:"kv_data"` //奖品设置的回调的键值数据
RewardType int8 `json:"reward_type"` //奖品类型
RewardTitle string `json:"reward_title"` //奖品名称
RewardNum int32 `json:"reward_num"` //奖品发放数量
SubUserId string `json:"sub_user_id"` //活动提供的用户id,该值为第三方提供的唯一标识
ParentSubUserId string `json:"parent_sub_user_id"` //活用户的上级用户id
Lang string `json:"lang"` //用户绑定的语种标识
Title string `json:"title"` //活动标题
MallRecordId int64 `json:"mall_record_id"` //关联的商城记录id(redeem_mall_record表主键)
}
func (s Reward) Send(ctx context.Context, notifyUrl, token string, pushLadder int8, headers []*mtype.Header) (err error) {
// 防止一些并发操作,导致同时推送到客户端
lc := app.NewRedisLock(ctx, ckey.FissionAcceptRewardOnce(s.CampaignId, s.RewardHashId), lock.SetExpire(timeOut))
defer lc.Release()
if !lc.SoftLock() {
return
}
//回调记录
taskId := hashid.Id()
m := dao.FissionNotify
fn, err := m.WithContext(ctx).Where(m.CampaignID.Eq(s.CampaignId), m.RecordID.Eq(s.RewardId)).Take()
if err = dbcheck.DbError(err); err != nil {
return
}
if fn != nil {
taskId = fn.HashID
}
// 回调成功不处理
if fn != nil && (fn.IsOk == mtype.Yes || s.hasTask(ctx, fn.ID)) {
return
}
body, _ := jsonm.Marshal(s.body(ctx, taskId))
app.Logger.Info(ctx, fmt.Sprintf("奖品推送: %s", body.ToString()))
reqStart := time.Now()
if fn == nil {
fn = withdefault.FissionNotify(&model.FissionNotify{
HashID: taskId,
CampaignID: s.CampaignId,
RecordID: s.RewardId,
NotifyURL: notifyUrl,
NotifyType: mtype.FissionNotifyTypeServer,
Token: token,
Body: body.ToString(),
PushLadder: pushLadder,
})
fn.Headers, _ = jsonm.ToSliceString(headers)
_ = m.WithContext(ctx).Create(fn)
}
var end bool
delaySec, hasDelay := delaySecMap[fn.PushNum]
if !hasDelay {
end = true
}
var respBody string
var code int
// 是否转发推送
if pushLadder == mtype.No {
respBody, code, err = normalReq(ctx, body, notifyUrl, token, s.Lang, headers)
} else {
respBody, code, err = proxyReq(ctx, body, notifyUrl, token, s.Lang, headers)
}
defer func() { // 只有在重推任务结束的时候,才返回错误信息出去
if err != nil && end {
return
}
err = nil
}()
//处理回调任务
var ok bool
if err == nil && code == 200 && strings.HasPrefix(respBody, "success") {
ok = true
}
var ntfUp = []field.AssignExpr{
m.PushNum.Add(1),
m.IsOk.Value(utils.Ternary[int8](ok, mtype.Yes, mtype.No)),
m.Body.Value(body.ToString()),
}
if fn.NotifyURL != notifyUrl {
ntfUp = append(ntfUp, m.NotifyURL.Value(notifyUrl))
}
// 更新状态
_, _ = m.WithContext(ctx).Where(m.ID.Eq(fn.ID)).UpdateSimple(ntfUp...)
var cliResp = respBody
if err != nil && cliResp == "" {
cliResp = fmt.Sprintf("系统错误:%v", err)
} else if cliResp == "" {
cliResp = "如果处理成功请返回字符串success,当前接收到的信息为空字符串,无法判断当前回调是否正确"
}
//回调结果
fnrM := dao.FissionNotifyResp
_ = fnrM.WithContext(ctx).Create(withdefault.FissionNotifyResp(&model.FissionNotifyResp{
CreatedAt: reqStart.Unix(),
NotifyID: fn.ID,
Resp: cliResp,
}))
//如果没有发送成功并且不是最后一次发送,就创建一个任务
if !ok && !end {
s.createTask(ctx, fn.ID, delaySec)
return
}
recordM := dao.FissionRewardRecord
recordSM := dao.FissionRewardRecord.SubTable(s.CampaignId)
mallRecordM := dao.RedeemMallRecord
if ok {
_, nd, _ := strings.Cut(respBody, ":")
_, _ = recordM.WithContext(ctx).Where(recordM.ID.Eq(s.RewardId)).UpdateSimple(
recordM.Status.Value(mtype.FissionRewardRecordStatusSuccess),
recordM.FailType.Value(""),
recordM.FailMsg.Value(""),
recordM.CustomRewardVal.Value(nd),
)
_, _ = recordSM.WithContext(ctx).Where(recordSM.ID.Eq(s.RewardId)).UpdateSimple(
recordSM.Status.Value(mtype.FissionRewardRecordStatusSuccess),
recordSM.FailType.Value(""),
recordSM.FailMsg.Value(""),
recordSM.CustomRewardVal.Value(nd),
)
if s.MallRecordId > 0 {
_, _ = mallRecordM.WithContext(ctx).Where(mallRecordM.ID.Eq(s.MallRecordId)).UpdateSimple(
mallRecordM.Status.Value(mtype.RedeemMallRecordStatusOk),
mallRecordM.FailType.Value(""),
mallRecordM.FailMsg.Value(""),
mallRecordM.CustomRewardVal.Value(nd),
)
}
return
}
var up = []field.AssignExpr{
recordM.Status.Value(mtype.FissionRewardRecordStatusFail),
}
var ups = []field.AssignExpr{
recordSM.Status.Value(mtype.FissionRewardRecordStatusFail),
}
var mup = []field.AssignExpr{
mallRecordM.Status.Value(mtype.RedeemMallRecordStatusFail),
}
if err != nil {
val := fmt.Sprintf("%v", err)
if strings.Contains(val, "missing port in address") {
up = append(up,
recordM.FailType.Value(mtype.FissionRewardRecordFailTypeCli),
recordM.FailMsg.Value("回调地址无法正常访问"),
)
ups = append(ups,
recordSM.FailType.Value(mtype.FissionRewardRecordFailTypeCli),
recordSM.FailMsg.Value("回调地址无法正常访问"),
)
mup = append(mup,
recordSM.FailType.Value(mtype.RewardPushFailTypeCli),
recordSM.FailMsg.Value("回调地址无法正常访问"),
)
} else {
up = append(up,
recordM.FailType.Value(mtype.FissionRewardRecordFailTypeSys),
recordM.FailMsg.Value(val),
)
ups = append(ups,
recordSM.FailType.Value(mtype.FissionRewardRecordFailTypeSys),
recordSM.FailMsg.Value(val),
)
mup = append(mup,
recordSM.FailType.Value(mtype.RewardPushFailTypeSys),
recordSM.FailMsg.Value(val),
)
}
} else if !strings.HasPrefix(respBody, "success") {
up = append(up,
recordM.FailType.Value(mtype.FissionRewardRecordFailTypeCli),
recordM.FailMsg.Value("返回无法识别值:"+cliResp),
)
ups = append(ups,
recordSM.FailType.Value(mtype.FissionRewardRecordFailTypeCli),
recordSM.FailMsg.Value("返回无法识别值:"+cliResp),
)
mup = append(mup,
recordSM.FailType.Value(mtype.RewardPushFailTypeCli),
recordSM.FailMsg.Value("返回无法识别值:"+cliResp),
)
} else {
up = append(up,
recordM.FailType.Value(mtype.FissionRewardRecordFailTypeUndefined),
)
ups = append(ups,
recordSM.FailType.Value(mtype.FissionRewardRecordFailTypeUndefined),
)
mup = append(mup,
recordSM.FailType.Value(mtype.RewardPushFailTypeUndefined),
)
}
_, _ = recordM.WithContext(ctx).
Where(recordM.ID.Eq(s.RewardId)).
UpdateSimple(up...)
_, _ = recordSM.WithContext(ctx).
Where(recordSM.ID.Eq(s.RewardId)).
UpdateSimple(ups...)
if s.MallRecordId > 0 {
_, _ = mallRecordM.WithContext(ctx).
Where(mallRecordM.ID.Eq(s.MallRecordId)).
UpdateSimple(mup...)
}
return
}
type InviteList struct {
UserId string `json:"user_id"` // 活动提供的用户id,该值为第三方提供的唯一用户标识
DeviceId string `json:"device_id"` // 活动提供的用户id,该值为第三方提供的唯一设备标识
InviteTime int64 `json:"invite_time"` // 邀请时间
Platform string `json:"platform"` // 平台(ios, android, web, other)
}
func (s Reward) body(ctx context.Context, taskId string) map[string]interface{} {
inviteM := dao.FissionUser
// 找下级用户数据
var list []*model.FissionUser
if s.UserId > 0 {
list, _ = inviteM.WithContext(ctx).Where(inviteM.CampaignID.Eq(s.CampaignId), inviteM.ParentID.Eq(s.UserId)).Find()
}
var ids []int64
for _, v := range list {
ids = append(ids, v.ID)
}
var attrMap = make(map[int64]string)
if len(ids) > 0 {
m := dao.FissionUserAttr
attrs, _ := m.WithContext(ctx).Where(m.UserID.In(ids...), m.Attr.Eq(mtype.FissionUserAttrMarkPlatform)).Find()
for _, v := range attrs {
attrMap[v.UserID] = v.Value
}
}
var il = make([]*InviteList, 0, len(list))
for _, v := range list {
il = append(il, &InviteList{
UserId: v.SubUserID,
DeviceId: v.DeviceID,
InviteTime: v.CreatedAt,
Platform: utils.Ternary[string](attrMap[v.ID] == "", consts.PlatformOther, attrMap[v.ID]),
})
}
var kvData = make(map[string]string, len(s.KvData))
for _, v := range s.KvData {
kvData[v.Key] = v.Value
}
return map[string]interface{}{
"task_id": taskId,
"source": "campaign",
"campaign_id": s.CampaignHashId,
"reward_id": s.RewardHashId,
"reward_type": s.RewardType,
"reward_title": s.RewardTitle,
"reward_num": s.RewardNum,
"user_id": s.SubUserId,
"user_platform": s._getPlatform(ctx, s.UserId),
"user_pid": s.ParentSubUserId,
"parent_platform": s._getPlatform(ctx, s.ParentId),
"kv_data": kvData,
"invite_list": il,
"lang": s.Lang,
"title": s.Title,
"custom_param": s._getCustomParam(ctx, s.UserId),
}
}
// 获取平台信息
func (s Reward) _getPlatform(ctx context.Context, userId int64) (platform string) {
if userId == 0 {
return
}
attr, err := cache.NewFissionUserAttr(ctx).FindByUniqIdx(userId, mtype.FissionUserAttrMarkPlatform)
if err != nil {
return
}
if attr != nil {
platform = attr.Value
}
if platform == "" {
platform = consts.PlatformOther
}
return
}
func (s Reward) createTask(ctx context.Context, fnId int64, sec int) {
exp := time.Duration(sec) * time.Second
// 防止一些并发操作,导致同时推送到客户端
lc := app.NewRedisLock(ctx, ckey.FissionRewardPushTaskOnce(fnId), lock.SetExpire(exp-time.Second))
if !lc.SoftLock() {
return
}
reward, _ := jsonm.MarshalToString(s)
fntM := dao.FissionNotifyTask
task := withdefault.FissionNotifyTask(&model.FissionNotifyTask{
DoTime: time.Now().Add(exp).Unix(),
NotifyID: fnId,
Reward: reward,
})
_ = fntM.WithContext(ctx).Create(task)
producer.RewardPushPub(ctx, sec, types.DelayCommonRewardPush{TaskId: task.ID})
}
func (s Reward) hasTask(ctx context.Context, fnId int64) bool {
return app.Redis.Cache.Exists(ctx, ckey.FissionRewardPushTaskOnce(fnId)).Val() > 0
}
func (s Reward) _getCustomParam(ctx context.Context, userId int64) (data []*mtype.KV) {
if userId == 0 {
return
}
attr, err := cache.NewFissionUserJattr(ctx).FindByUniqIdx(userId, mtype.FissionUserAttrMarkCustomParam)
if err != nil {
return
}
if attr == nil {
return make([]*mtype.KV, 0)
}
var params = strings.Split(attr.Value, ",")
data = make([]*mtype.KV, 0, len(params))
for _, v := range params {
key, val, _ := strings.Cut(v, "$")
data = append(data, &mtype.KV{Key: key, Value: val})
}
return
}