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.

593 lines
15 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 validevents
import (
"joybuywork/internal/app"
"joybuywork/internal/consts/ckey"
"joybuywork/internal/plugins/jsonm"
"joybuywork/internal/repository/cache"
"joybuywork/internal/repository/model"
"joybuywork/internal/repository/mtype"
"joybuywork/internal/repository/withdefault"
"joybuywork/internal/service/provider"
"joybuywork/internal/service/provider/cachecurd"
"joybuywork/internal/service/provider/fissionaction"
"joybuywork/internal/service/queue/types"
"joybuywork/internal/utils"
"joybuywork/internal/utils/lockdo"
"joybuywork/internal/utils/logic"
"joybuywork/internal/utils/timez"
"joybuywork/internal/utils/verify"
"fmt"
"math/rand"
"strconv"
"time"
"gorm.io/gen/field"
)
// 安装、注册事件是否有效处理
type taskEffectDo func() (err error)
// 事件完成任务回调
type taskDoneCallback func(user *model.FissionUser, tasks []*model.FissionCampaignTask) (err error)
type eventData struct {
props map[string]int32 // 道具对应的发放数量,如: {"chance": 10, "fixed_roll": 1} (如果是大富翁表示,骰子个数获得10个,固定骰子获得1个)
targetNum int32 // 当前推送的事件计算完毕后,总共完成了几次自定义事件
}
type base struct {
*logic.Logic
Req *types.ValidEvents
effectNum, taskValidNum int64
cpn *model.FissionCampaign
tOffset int32 // 时区
reportType int8 // 报表类型
spLang []string // 支持的语种
condFinishData []*condFinishOne
condTotalData []*condTotalOne
}
func (s *base) Event(user *model.FissionUser) (targetNum int32, setHelp bool, err error) {
return
}
func (s *base) Register(helper, beHelper *model.FissionUser) (setHelp bool, err error) {
return
}
func (s *base) Install(helper, beHelper *model.FissionUser) (setHelp bool, err error) {
return
}
func (s *base) Open(helper, beHelper *model.FissionUser) (setHelp bool, err error) {
return
}
func (s *base) Click(userMark string, beHelper *model.FissionUser) (err error) {
return
}
// 条件结果处理
func (s *base) _event(user *model.FissionUser, callbacks ...taskDoneCallback) (data eventData, err error) {
e := event{base: s}
defer func() {
if err == nil {
err = e.dataSync()
}
}()
return e.event(user, callbacks...)
}
// 任务是否有效完成
func (s *base) _effect(user *model.FissionUser, task *model.FissionCampaignTask, ideq ...string) (ok bool, err error) {
taskLimit, chance, chanceLimit, targetNum := int64(task.TaskLimit), int64(task.Chance), int64(task.ChanceLimit), int64(task.Num)
// 完成的任务
ftNum, err := s._taskFinishNum(user.ID, task.ID)
if err != nil {
return
}
// 统计有效邀请人数
var num int64
if task.PropMark == mtype.FissionCampaignTaskPropMarkChance {
num, err = s._statDirectNum(user.CampaignID, user.ID)
if err != nil {
return
}
} else {
num, err = s._statTaskValidNum(user.CampaignID, user.ID, task.ID, ideq...)
if err != nil {
return
}
}
var indicatorData *mtype.IndicatorData
switch s.Req.NoticeType {
case types.NoticeTypeInstall:
indicatorData = &mtype.IndicatorData{
InstallNum: num,
}
case types.NoticeTypeRegister:
indicatorData = &mtype.IndicatorData{
RegisterNum: num,
}
default:
}
// 超过限制不能完成任务了
if (task.TaskLimit != 0 && ftNum >= taskLimit) ||
(task.ChanceLimit != 0 && (ftNum*chance) >= chanceLimit) {
return false, s._taskRecord(user, task, indicatorData, mtype.Yes)
}
// 有效计算次数
//总邀请人数 减去 当前任务完成次数 * 任务目标次数
efNum := num - (ftNum * targetNum)
if efNum < targetNum {
return
}
return true, s._taskRecord(user, task, indicatorData, mtype.No)
}
// 任务有效就执行任务有效处理
func (s *base) _taskEffectDo(user *model.FissionUser, task *model.FissionCampaignTask, do taskEffectDo, ideq ...string) (err error) {
ok, err := s._effect(user, task, ideq...)
if err != nil {
return
}
if !ok {
return
}
return do()
}
// 统计有效邀请人数
func (s *base) _statDirectNum(cpnId, userId int64) (num int64, err error) {
// 如果已经统计过了,就直接返回
if s.effectNum > 0 {
return s.effectNum, nil
}
key := ckey.FissionUserInviteStat(userId)
_, err = lockdo.Int64(s.Ctx, key, func() (num int64, err error) {
m := s.GetDao().FissionUser.SubTable(cpnId)
return m.WithContext(s.Ctx).Where(
m.CampaignID.Eq(cpnId),
m.ParentID.Eq(userId),
m.IsHelp.Eq(mtype.Yes),
).Count()
})
if err != nil {
err = s.BE().CacheError(err)
return
}
app.Redis.Cache.Expire(s.Ctx, key, 2*time.Hour)
s.effectNum, err = app.Redis.Cache.Incr(s.Ctx, key).Result()
if err != nil {
err = s.BE().CacheError(err)
return
}
return s.effectNum, nil
}
// 统计任务有效数
func (s *base) _statTaskValidNum(cpnId, userId, taskId int64, ideq ...string) (num int64, err error) {
// 如果已经统计过了,就直接返回
if s.taskValidNum > 0 {
return s.taskValidNum, nil
}
key := ckey.FissionUserTaskValidStat(userId, taskId)
_, err = lockdo.Int64(s.Ctx, key, func() (num int64, err error) {
m := s.GetDao().FissionUserTaskValid
return m.WithContext(s.Ctx).Where(
m.CampaignID.Eq(cpnId),
m.UserID.Eq(userId),
m.TaskID.Eq(taskId),
).Count()
})
if err != nil {
err = s.BE().CacheError(err)
return
}
valid := withdefault.FissionUserTaskValid(&model.FissionUserTaskValid{
CampaignID: cpnId,
UserID: userId,
TaskID: taskId,
})
valid.SetIdeq(ideq...)
m := s.GetDao().FissionUserTaskValid
has, err := m.WithContext(s.Ctx).Where(m.Ideq.Eq(valid.Ideq)).Count()
if err != nil {
err = s.BE().MysqlError(err)
return
}
if has > 1 {
return
}
if err = m.WithContext(s.Ctx).Create(valid); err != nil {
err = s.BE().MysqlError(err)
return
}
app.Redis.Cache.Expire(s.Ctx, key, 2*time.Hour)
s.taskValidNum, err = app.Redis.Cache.Incr(s.Ctx, key).Result()
if err != nil {
err = s.BE().CacheError(err)
return
}
return s.taskValidNum, nil
}
// 任务完成数
func (s *base) _taskFinishNum(userId, taskId int64) (num int64, err error) {
key := ckey.FissionUserTaskCpnLimit(userId, taskId)
num, err = lockdo.Int64(s.Ctx, key, func() (num int64, err error) {
m := s.GetDao().FissionUserTaskRecord
return m.WithContext(s.Ctx).Where(m.UserID.Eq(userId), m.TaskID.Eq(taskId)).Count()
})
if err != nil {
err = s.BE().CacheError(err)
return
}
return
}
// 任务完成次数刷新缓存
func (s *base) _addTaskFinish(user *model.FissionUser, task *model.FissionCampaignTask, chance int64) (err error) {
if task.TaskLimit > 0 || task.ChanceLimit > 0 {
key := ckey.FissionUserTaskCpnLimit(user.ID, task.ID)
err = lockdo.ExistDo(s.Ctx, key, func(exist bool) (err error) {
if !exist {
var num int64
m := s.GetDao().FissionUserTaskRecord
num, err = m.WithContext(s.Ctx).Where(m.UserID.Eq(user.ID), m.TaskID.Eq(task.ID)).Count()
if err != nil {
return
}
app.Redis.Cache.IncrBy(s.Ctx, key, num)
}
app.Redis.Cache.IncrBy(s.Ctx, key, chance)
app.Redis.Cache.Expire(s.Ctx, key, time.Duration(rand.Intn(60)+60)*time.Minute)
return
})
if err != nil {
err = s.BE().CacheError(err)
return
}
}
if task.DayLimit > 0 {
day, start, end := timez.GMTStartAndEndTs(s.tOffset, s.Req.BehaviorTime)
key := ckey.FissionUserTaskDayLimit(day, user.ID, task.ID)
err = lockdo.ExistDo(s.Ctx, key, func(exist bool) (err error) {
if !exist {
var num int64
m := s.GetDao().FissionUserTaskRecord
num, err = m.WithContext(s.Ctx).Where(
m.UserID.Eq(user.ID),
m.TaskID.Eq(task.ID),
m.CreatedAt.Gte(start),
m.CreatedAt.Lte(end),
).Count()
if err != nil {
return
}
app.Redis.Cache.IncrBy(s.Ctx, key, num)
}
app.Redis.Cache.IncrBy(s.Ctx, key, chance)
app.Redis.Cache.Expire(s.Ctx, key, time.Duration(rand.Intn(60)+60)*time.Minute)
return
})
if err != nil {
err = s.BE().CacheError(err)
return
}
}
if task.WeekLimit > 0 {
start, end := timez.GetWeekEnd(s.tOffset, int32(task.WeekNum))
key := ckey.FissionUserWeekLimit(start, user.ID, task.ID)
err = lockdo.ExistDo(s.Ctx, key, func(exist bool) (err error) {
if !exist {
var num int64
m := s.GetDao().FissionUserTaskRecord
num, err = m.WithContext(s.Ctx).Where(
m.UserID.Eq(user.ID),
m.TaskID.Eq(task.ID),
m.CreatedAt.Between(start, end),
).Count()
if err != nil {
return
}
app.Redis.Cache.IncrBy(s.Ctx, key, num)
}
app.Redis.Cache.IncrBy(s.Ctx, key, chance)
app.Redis.Cache.Expire(s.Ctx, key, time.Duration(rand.Intn(60)+60)*time.Minute)
return
})
if err != nil {
err = s.BE().CacheError(err)
return
}
}
return
}
// 一个任务完成了
func (s *base) _taskRecord(user *model.FissionUser, task *model.FissionCampaignTask, data *mtype.IndicatorData, limit int8) (err error) {
record := withdefault.FissionUserTaskRecord(&model.FissionUserTaskRecord{
CampaignID: user.CampaignID,
UserID: user.ID,
TaskID: task.ID,
IndicatorData: "",
Chance: task.Chance,
LimitProp: limit,
})
record.SetIndicatorData(data)
err = s._addUserTaskRecord(record)
if err != nil {
return
}
return s._addTaskFinish(user, task, 1)
}
// 添加任务完成记录
func (s *base) _addUserTaskRecord(records ...*model.FissionUserTaskRecord) (err error) {
if len(records) == 0 {
return
}
m := s.GetDao().FissionUserTaskRecord
if err = m.WithContext(s.Ctx).Create(records...); err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
// 完成事件统计推送
func (s *base) _condFinish(user *model.FissionUser, condId int64, total string, first bool) {
val, _ := strconv.ParseFloat(total, 64)
s.condFinishData = append(s.condFinishData, &condFinishOne{
condId: condId,
num: 1,
userNum: utils.Ternary[int64](first, 1, 0),
})
if s.reportType == mtype.FissionCampaignReportTypeNew {
s.MP().InApp(user).FinishCond(s.Req.BehaviorTime, condId, val, s.Req.Event, s.Req.EventData)
}
}
func (s *base) _condTotal(condId int64, total string) {
val, _ := strconv.ParseFloat(total, 64)
s.condTotalData = append(s.condTotalData, &condTotalOne{
condId: condId,
total: val,
})
}
// 获取安装事件的任务
func (s *base) _getInstallTasks(campaignId int64) (tasks []*model.FissionCampaignTask, setHelp bool, err error) {
return s._getTaskAndHelp(campaignId, mtype.FissionCampaignTaskMarkInstall)
}
// 获取注册事件的任务
func (s *base) _getRegisterTasks(campaignId int64) (tasks []*model.FissionCampaignTask, setHelp bool, err error) {
return s._getTaskAndHelp(campaignId, mtype.FissionCampaignTaskMarkRegister)
}
// 获取任务
func (s *base) _getTaskAndHelp(campaignId int64, mark string) (tasks []*model.FissionCampaignTask, setHelp bool, err error) {
m := s.GetDao().FissionCampaignTask
tasks, err = m.WithContext(s.Ctx).Where(
m.CampaignID.Eq(campaignId),
m.TaskMark.Eq(mark),
m.IsDel.Eq(mtype.No),
).Find()
if err != nil {
err = s.BE().MysqlError(err)
return
}
for _, task := range tasks {
if task.PropMark == mtype.FissionCampaignTaskPropMarkChance {
setHelp = true
return
}
}
for _, task := range tasks {
if verify.InArray[string](task.PropMark,
mtype.FissionCampaignTaskPropMarkFixedRoll,
mtype.FissionCampaignTaskPropMarkRevive,
mtype.FissionCampaignTaskPropMarkReward,
) {
setHelp = true
return
}
}
return
}
// 同步数据
func (s *base) dataSync() (err error) {
if len(s.condFinishData) > 0 {
var data = make(map[int64]*condFinishOne)
for _, v := range s.condFinishData {
if _, ok := data[v.condId]; !ok {
data[v.condId] = v
} else {
data[v.condId].num += v.num
}
}
m := s.GetDao().FissionCampaignCond
for _, v := range data {
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(v.condId)).UpdateSimple(m.Num.Add(v.num), m.UserNum.Add(v.userNum))
if err != nil {
err = s.BE().MysqlError(err)
return
}
}
}
if len(s.condTotalData) > 0 {
var data = make(map[int64]float64)
for _, v := range s.condTotalData {
data[v.condId] += v.total
}
m := s.GetDao().FissionCampaignCond
for id, total := range data {
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(id)).UpdateSimple(m.Total.Add(total))
if err != nil {
err = s.BE().MysqlError(err)
return
}
}
}
return
}
// 增加抽奖次数通知
func (s *base) addChanceNotify(userId, chance int64) {
provider.NewFissionClue(s.Ctx, userId).AddChance(chance)
}
// 有邀请类的子活动
func (s *base) hasChildInvite(user *model.FissionUser, invitedId int64, done func() error) (err error) {
child, err := cache.NewCampaignChild(s.Ctx).FindByUniqIdx(user.CampaignID, mtype.FissionCampaignChildTypeInvite)
if err != nil {
err = s.BE().CacheError(err)
return
}
if child == nil {
return
}
cpn, err := cache.NewCampaign(s.Ctx).FindById(child.ChildID)
if err != nil {
err = s.BE().CacheError(err)
return
}
if cpn == nil || cpn.Status == mtype.FissionCampaignStatusCancel || cpn.IsDel == mtype.Yes || !timez.GMTInsideEndless(s.tOffset, cpn.TStart, cpn.TEnd) {
return
}
player, err := cache.NewFissionChildUser(s.Ctx).FindByUniqIdx(user.ID, cpn.ID, cache.WithNilSet(func() ([]byte, error) {
info := withdefault.FissionChildUser(&model.FissionChildUser{
PCpnID: cpn.Pid,
CampaignID: cpn.ID,
UserID: user.ID,
ParentID: user.ParentID,
ChildType: cpn.ChildType,
})
err = s.GetDao().FissionChildUser.WithContext(s.Ctx).Create(info)
if err != nil {
return nil, err
}
return jsonm.Marshal(info)
}))
if err != nil {
err = s.BE().CacheError(err)
return
}
m := s.GetDao().FissionChildUser
var up = []field.AssignExpr{
m.InviteNum.Add(1),
}
if player.JoinTime == 0 {
up = append(up, m.JoinTime.Value(time.Now().Unix()))
}
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(up...)
if err != nil {
err = s.BE().MysqlError(err)
return
}
_ = cache.NewFissionChildUser(s.Ctx).Clear(player)
recordM := s.GetDao().FissionChildUserRecord
err = recordM.WithContext(s.Ctx).Create(withdefault.FissionChildUserRecord(&model.FissionChildUserRecord{
PCpnID: player.PCpnID,
CampaignID: player.CampaignID,
UserID: player.UserID,
PlayerID: player.ID,
ChildType: player.ChildType,
Mark: mtype.FissionChildUserRecordSourceInviteOne,
}))
if err != nil {
err = s.BE().MysqlError(err)
return
}
ranker, err := cachecurd.NewRanker(s.Ctx, ckey.FissionChildRank(cpn.ID), cachecurd.SetChildBuild(cpn.ID, cpn.ChildType))
if err != nil {
err = s.BE().CacheError(err)
return
}
if player.InviteNum == 0 {
err = ranker.InitMember(fmt.Sprint(player.UserID), player.CreatedAt)
if err != nil {
err = s.BE().CacheError(err)
return
}
} else {
err = ranker.Incr(fmt.Sprint(player.UserID))
if err != nil {
err = s.BE().CacheError(err)
return
}
}
s.AddAfterRun(func() (err error) {
fissionaction.NewMarkPoint(s.Ctx).InApp(user).JoinChild(time.Now().Unix(), player.CampaignID, player.ChildType)
fissionaction.NewMarkPoint(s.Ctx).Child(player, user).ChildInvite(time.Now().Unix(), invitedId)
return
})
if done != nil {
return done()
}
return
}