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 }