package players import ( "context" "joybuywork/internal/app" "joybuywork/internal/consts" "joybuywork/internal/consts/ckey" "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/cmm" "joybuywork/internal/service/provider" "joybuywork/internal/service/provider/award" "joybuywork/internal/service/provider/cachecurd" "joybuywork/internal/service/provider/fissionaction" "joybuywork/internal/service/provider/stocks" "joybuywork/internal/utils" "joybuywork/internal/utils/timez" "errors" "fmt" "strings" "time" "joybuywork/proto/go_proto/workerr" "github.com/golang-module/carbon/v2" "github.com/rs/xid" "gorm.io/gen/field" "gorm.io/gorm" ) const ( SignStatusOk = iota //签到成功 SignStatusFail //签到失败 SignStatusMissCard //补签卡不足 SignStatusDone //已签到 SignStatusCard //补签签到 SignStatusWait //活动未开启 ) const ( SignWait = iota // 未签到 SignDone // 已签到 SignMiss // 漏签 ) type Status struct { DirectNum int64 // 直接邀请人数 MaxContinue int32 // 最大连续签到 TodaySign int32 // 今天是否有签到 CycleDay int32 // 周期签到天数 } // Sign 签到交互逻辑 type Sign struct { Player player *model.FissionSign conf *model.FissionCampaignSign upPlayer []field.AssignExpr signDataKey string cacheData *cachecurd.HMap } func NewSign(ctx context.Context, user *model.FissionUser, opts ...Option) (player *Sign, err error) { player = &Sign{} err = player.Init(ctx, user, consts.IdSignIn, opts...) if err != nil { return } err = player.initPlayer() if err != nil { return } return } type SignReward struct { RewardTitle string //奖品名称 RewardNum int32 //获得的奖品数量 ImgUrl string //奖品图片 RewardId int64 //奖品id ReceiveType int32 //发放类型(0-无操作,1-系统发放,2-联系客服,3-客户端推送) KvData []*mtype.KV //键值数据 Contact string //联系方式 } // SignIn 签到 func (s *Sign) SignIn(signTime int64) (status int32, reward *SignReward, err error) { defer func() { s.AfterDo(err) }() tz := s.GetTz() if !tz.IsRunning() { status = SignStatusWait return } // 今天的开始 today, _ := timez.GMTTimeStart(tz.GetTz()) // 提交上来的签到时间有这个可能是补签卡 submitDay, _ := timez.GMTTimeStart(tz.GetTz(), signTime) s.Tran() defer func() { s.TranCommit(err) }() conf := s.GetConf() if conf.SignType == mtype.FissionCampaignSignTypeCycle { if signTime == 0 || today.Equal(submitDay) { return s._cycleSign() } return s._cycleCardSign(submitDay) } // 下面的逻辑太老旧了,不想嵌套到里面 !!! if today.Before(submitDay) { // 如果当前时间小于提交的时间,那就是未来的时间,未来时间不可以签到 status = SignStatusFail return } if signTime == 0 || today.Equal(submitDay) { return s._todaySign(today) } return s._cardSign(submitDay) } // 周期补签卡签到 func (s *Sign) _cycleCardSign(signTime time.Time) (status int32, reward *SignReward, err error) { s._cycleStart() playerM := s.GetDao().FissionSign player := s.GetPlayer() if player.CardNum <= 0 { status = SignStatusMissCard return } if player.CycleStart > signTime.Unix() || signTime.Unix() >= time.Now().Unix() { status = SignStatusFail return } status, signDay, err := s._sign(signTime, true) if err != nil || status != SignStatusOk { return } cd, err := s._cycleData(false) if err != nil { return } // 签到的天更新 signStr, err := cd.Get(signCycleDay) if err != nil { return } if signStr == "" { signStr = signDay.SignDate } else { signStr += consts.JoinComma + signDay.SignDate } s.AddOkDone(func() { _ = cd.Set(signCycleDay, signStr) }) continueNum, maxContinue, _, err := s._continueNum() if err != nil { return } s.AddOkDone(func() { _ = cd.Set(signMaxContinue, fmt.Sprint(maxContinue)) _ = cd.Set(signContinueNum, fmt.Sprint(continueNum)) fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).UseProp(time.Now().Unix(), consts.PropNameSignCard) }) s.upPlayer = append( s.upPlayer, playerM.SignContinue.Value(continueNum), playerM.MaxContinue.Value(maxContinue), playerM.CardNum.Sub(1), ) signDays := strings.Split(signStr, consts.JoinComma) signNum := int32(len(signDays)) reward, recordIds, err := s._cycleReward(signNum, signDay) if err != nil { return } err = s._cyclePlayerSync(recordIds) if err != nil { return } return } // 周期签到 func (s *Sign) _cycleSign() (status int32, reward *SignReward, err error) { s._cycleStart() playerM := s.GetDao().FissionSign player, tz := s.GetPlayer(), s.GetTz() // 今天的开始 today, _ := timez.GMTTimeStart(tz.GetTz()) status, signDay, err := s._sign(today, false) if err != nil || status != SignStatusOk { return } if player.SignStart == 0 { s.player.SignStart = today.Unix() s.upPlayer = append(s.upPlayer, playerM.SignStart.Value(today.Unix())) } cd, err := s._cycleData(false) if err != nil { return } s.AddOkDone(func() { // 下一次签到时间 err = cd.Set(signNextDay, fmt.Sprint(today.AddDate(0, 0, 1).Unix())) if err != nil { return } // 最后签到时间 err = cd.Set(signLastDay, fmt.Sprint(today.Unix())) if err != nil { return } }) // 签到的天更新 signStr, err := cd.Get(signCycleDay) if err != nil { return } if signStr == "" { signStr = signDay.SignDate } else { signStr += consts.JoinComma + signDay.SignDate } s.AddOkDone(func() { _ = cd.Set(signCycleDay, signStr) }) // 连续签到数据 continueNum, _ := cd.GetInt64(signContinueNum) s.AddOkDone(func() { _, _ = cd.Incr(signContinueNum, 1) }) continueNum += 1 maxContinue, _ := cd.GetInt64(signMaxContinue) if continueNum > maxContinue { maxContinue = continueNum s.AddOkDone(func() { _ = cd.Set(signMaxContinue, fmt.Sprint(maxContinue)) }) } s.upPlayer = append( s.upPlayer, playerM.SignContinue.Value(int32(continueNum)), playerM.MaxContinue.Value(int32(maxContinue)), ) signDays := strings.Split(signStr, consts.JoinComma) signNum := int32(len(signDays)) reward, recordIds, err := s._cycleReward(signNum, signDay) if err != nil { return } err = s._cyclePlayerSync(recordIds) if err != nil { return } return } // 周期奖品处理 func (s *Sign) _cycleReward(signNum int32, signDay *model.FissionSignDay) (reward *SignReward, recordIds []int64, err error) { //当前签到是否有奖品 rewards, err := cmm.NewUserAppReward(s.Ctx).Get(s.GetDao(), s.user) if err != nil { return } var signReward *model.FissionCampaignReward for _, v := range rewards { if v.SignDay > signNum { break } if v.SignDay == signNum { signReward = v } } if signReward == nil { return } reward, recordIds, err = s._signReward(signDay, signReward) if err != nil { return } // 绑定数据 sdm := s.GetDao().FissionSignDay _, err = sdm.WithContext(s.Ctx).Where(sdm.ID.Eq(signDay.ID)).UpdateSimple(sdm.RecordIds.Value(utils.Join(recordIds, consts.JoinComma))) if err != nil { err = s.BE().MysqlError(err) return } return } // 周期用户更新数据同步 func (s *Sign) _cyclePlayerSync(recordIds []int64) (err error) { playerM := s.GetDao().FissionSign player := s.GetPlayer() // 更新玩家数据 _, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).UpdateSimple(s.upPlayer...) if err != nil { err = s.BE().MysqlError(err) return } // 更新统计数据 statM := s.GetDao().FissionUserStat _, err = statM.WithContext(s.Ctx).Where(statM.UserID.Eq(player.UserID)).UpdateSimple(statM.SignNum.Add(1), statM.SignRewardNum.Add(int64(len(recordIds)))) if err != nil { err = s.BE().MysqlError(err) return } return } type SignBoard struct { SignDate []*SignBoardOne //签到板信息 NowMouth string //当前月 CycleDate []*SignCycleDateOne } type SignCycleDateOne struct { SignTime int64 //签到时间 SignIn int32 //签到类型(0-未签,1-已签,2-漏签) NowDay bool //是否今天 RewardId int64 //奖品id,0即没有礼物 RewardTitle string //奖品图片 RewardImg string //奖品标题 IsSuper int32 //是否超级大奖 (0-否,1-是) Y string //年 M string //月 D string //日 } type SignBoardOne struct { Y string //年 M string //月 D string //日 SignIn int32 //签到类型(0-未签,1-已签,2-漏签) Ban bool //是否非签到日期 RewardId int64 //奖品id,0即没有礼物 Open bool //是否已经领奖 NowWeek bool //是否本周 NowDay bool //是否今天 LastMiss bool //是否最后一个漏签 NowMouth bool //是否本月 } // SignBoard 签到版 func (s *Sign) SignBoard(pageType int32, nowMouth string) (signBoard *SignBoard, err error) { defer func() { s.AfterDo(err) }() conf := s.GetConf() if conf.SignType == mtype.FissionCampaignSignTypeCycle { return s._cycleSignBoard() } return s._normalSignBoard(pageType, nowMouth) } // 周期签到板 func (s *Sign) _cycleSignBoard() (signBoard *SignBoard, err error) { s._cycleStart() rewards, err := cmm.NewUserAppReward(s.Ctx).Get(s.GetDao(), s.user, s.GetDao().FissionCampaignReward.SignDay) if err != nil { return } var rewardIds []int64 var dayReward = make(map[int32]*model.FissionCampaignReward) for _, reward := range rewards { dayReward[reward.SignDay] = reward rewardIds = append(rewardIds, reward.RewardID) } prewards, err := provider.NewRewards(s.Ctx, s.cpn.ProjectID, rewardIds...) if err != nil { return } player, conf, tz := s.GetPlayer(), s.GetConf(), s.GetTz() today, _ := timez.GMTTimeStart(tz.GetTz()) cycleStart, _ := timez.GMTTimeStart(tz.GetTz(), player.CycleStart) cpnEnd := s.GetTz().EndTime() cd, err := s._cycleData(false) if err != nil { return } signDayStr, _ := cd.Get(signCycleDay) var signDay []string if signDayStr != "" { signDay = strings.Split(signDayStr, consts.JoinComma) } var signMap = make(map[string]struct{}) for _, v := range signDay { signMap[v] = struct{}{} } var signNum int32 var signDate = make([]*SignCycleDateOne, 0, int(conf.SignCycle)) for i := 0; i < int(conf.SignCycle); i++ { one := &SignCycleDateOne{ SignTime: cycleStart.Unix(), SignIn: 0, NowDay: false, RewardId: 0, RewardTitle: "", RewardImg: "", IsSuper: 0, Y: fmt.Sprint(cycleStart.Year()), M: fmt.Sprint(int(cycleStart.Month())), D: fmt.Sprint(cycleStart.Day()), } shortDate := cycleStart.Format(timez.ShortDateOnly) if cycleStart.Before(today) { if _, ok := signMap[shortDate]; ok { one.SignIn = SignDone signNum++ } else { one.SignIn = SignMiss } } else { signNum++ } if cycleStart.Equal(today) { one.NowDay = true if _, ok := signMap[shortDate]; ok { one.SignIn = SignDone } } if reward, ok := dayReward[signNum]; ok { info := prewards.AutoLang(reward.RewardID, s.user.LangMark) one.RewardId = reward.ID one.IsSuper = int32(reward.IsSuper) one.RewardTitle = info.Title one.RewardImg = info.ImgUrl delete(dayReward, signNum) } signDate = append(signDate, one) cycleStart = cycleStart.AddDate(0, 0, 1) if cycleStart.After(cpnEnd) { break } } signBoard = &SignBoard{CycleDate: signDate} return } // 看看要不要重制周期时间 func (s *Sign) _cycleStart() { cd, err := s._cycleData(false) if err != nil { return } player, conf := s.GetPlayer(), s.GetConf() dayStart := s._dayStart() yesterdayStart := dayStart.AddDate(0, 0, -1) var refresh bool playerM := s.GetDao().FissionSign if player.CycleStart == 0 { s.player.CycleStart = dayStart.Unix() s.upPlayer = append(s.upPlayer, playerM.CycleStart.Value(dayStart.Unix())) s.AddOkDone(func() { _ = cd.Set(signNextDay, fmt.Sprint(dayStart.Unix())) }) } else if conf.RecycleType == mtype.FissionCampaignRecycleTypeContinue { lastDay, _ := cd.GetInt64(signLastDay) if lastDay < yesterdayStart.Unix() { s.player.CycleStart = dayStart.Unix() s.upPlayer = append(s.upPlayer, playerM.CycleStart.Value(dayStart.Unix())) _ = cd.Clear() refresh = true } } else if conf.RecycleType == mtype.FissionCampaignRecycleTypeCycle { if (dayStart.Unix()-player.CycleStart)/consts.DaySec >= int64(conf.SignCycle) { s.player.CycleStart = dayStart.Unix() s.upPlayer = append(s.upPlayer, playerM.CycleStart.Value(dayStart.Unix())) _ = cd.Clear() refresh = true } } _, err = s._cycleData(refresh) if err != nil { return } } func (s *Sign) _dayStart() time.Time { tz := s.GetTz() dayStart, _ := timez.GMTTimeStart(tz.GetTz()) cpnStart := tz.StarTime() if dayStart.Before(cpnStart) { dayStart = cpnStart } return dayStart } // GetSignCard 获得了签到卡 func (s *Sign) GetSignCard(num int32) (err error) { player := s.GetPlayer() m := s.GetDao().FissionSign _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(m.CardNum.Add(num)) if err != nil { err = s.BE().MysqlError(err) return } val, _ := jsonm.ToObjString(mtype.SignLogVal{CardNum: num}) err = s._addLog(withdefault.FissionSignLog(&model.FissionSignLog{ UserID: player.UserID, CampaignID: player.CampaignID, SignID: player.ID, Type: mtype.FissionSignLogTypeSignCard, Source: mtype.FissionSignLogSourceGetCard, Val: val, })) if err != nil { return } s.appTrack().GetProp(time.Now().Unix(), consts.PropNameSignCard, num) return } // GetStatus 用户额外信息 func (s *Sign) GetStatus() (status Status, err error) { player, conf, tz := s.GetPlayer(), s.GetConf(), s.GetTz() statM := s.GetDao().FissionUserStat stat, err := statM.WithContext(s.Ctx).Where(statM.UserID.Eq(player.UserID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if stat != nil { status.DirectNum = stat.DirectNum } var maxContinue = player.MaxContinue if player.NextStep > 0 { maxContinue = 0 m := s.GetDao().FissionCampaignReward if player.NextStep > 1 { var reward *model.FissionCampaignReward reward, err = m.WithContext(s.Ctx).Where(m.CampaignID.Eq(player.CampaignID), m.Step.Eq(player.NextStep-1)).Take() if err != nil { err = s.BE().MysqlError(err) return } if reward != nil { maxContinue = reward.SignDay } } else if player.LoopNum > 0 { var rewards []*model.FissionCampaignReward rewards, err = cmm.NewCMM(s.Ctx, s.GetDao()).UserAppReward(s.user, m.Step) if err != nil { return } l := len(rewards) maxContinue = rewards[l-1].SignDay } } status.MaxContinue = maxContinue today, _ := timez.GMTTimeStart(tz.GetTz()) exist, err := s._hasSign(today) if err != nil { return } status.TodaySign = utils.Ternary[int32](exist, 1, 0) // 周期签到天数 if conf.SignType == mtype.FissionCampaignSignTypeCycle { s._cycleStart() cd, _ := s._cycleData(false) cycleDay, _ := cd.Get(signCycleDay) if cycleDay != "" { status.CycleDay = int32(len(strings.Split(cycleDay, consts.JoinComma))) } } return } // GetConf 获取玩法配置 func (s *Sign) GetConf() *model.FissionCampaignSign { if s.conf == nil { s.conf, _ = cache.NewCampaignSign(s.Ctx).FindByCampaignId(s.user.CampaignID) } return s.conf } // GetPlayer 获取玩家基础数据 func (s *Sign) GetPlayer() *model.FissionSign { return s.player } // 玩家 func (s *Sign) initPlayer() (err error) { player, err := s._dbFirst() if err != nil { return } defer func() { if err != nil { return } s.player = player s.signDataKey = ckey.FissionSignData(player.ID) }() if player != nil { return } player, err = s.firstOrCreate() if err != nil { return } return } // 创建玩家基础数据 func (s *Sign) firstOrCreate() (player *model.FissionSign, err error) { lc := app.NewFollower(s.Ctx, ckey.FissionUserCreatePlayerOnce(s.user.ID)).Lock() defer lc.Release() if lc.Error() != nil { err = lc.Error() return } player, err = s._dbFirst() if err != nil { return } if player != nil { return } return s.createPlayer() } // 数据库查询 func (s *Sign) _dbFirst() (*model.FissionSign, error) { playerM := dao.FissionSign player, err := playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(s.user.ID)).Take() if err = dbcheck.DbError(err); err != nil { return nil, s.BE().MysqlError(err) } return player, nil } // 创建一个玩家 func (s *Sign) createPlayer() (player *model.FissionSign, err error) { playerM := dao.FissionSign player = withdefault.FissionSign(&model.FissionSign{ CampaignID: s.user.CampaignID, UserID: s.user.ID, }) err = playerM.WithContext(s.Ctx).Create(player) if err != nil { err = s.BE().MysqlError(err) return } return } func (s *Sign) _addLog(logs ...*model.FissionSignLog) (err error) { err = s.GetDao().FissionSignLog.WithContext(s.Ctx).Create(logs...) if err != nil { err = s.BE().MysqlError(err) return } return } // 连续签到数据 func (s *Sign) _continueNum() (continueNum, maxContinue int32, cycleDate []string, err error) { player, tz := s.GetPlayer(), s.GetTz() m := s.GetDao().FissionSignDay list, err := m.WithContext(s.Ctx).Where( m.UserID.Eq(player.UserID), ).Find() if err != nil { err = s.BE().MysqlError(err) return } signMap := make(map[string]struct{}) for _, day := range list { signMap[day.SignDate] = struct{}{} } var start = player.SignStart if start == 0 { start = time.Now().Unix() } sdn := timez.GMTCarbon(tz.GetTz()).Now() sd := utils.BetweenDay(timez.GMTCarbon(tz.GetTz()).CreateFromTimestamp(start).StartOfDay(), sdn) cycleStart, _ := timez.GMTTimeStart(tz.GetTz(), player.CycleStart) cycle := cycleStart.Format(timez.ShortDateOnly) for _, day := range sd { _, ok := signMap[day] if !ok { continueNum = 0 continue } if day >= cycle { cycleDate = append(cycleDate, day) } continueNum++ if continueNum > maxContinue { maxContinue = continueNum } } return } // 获得奖品通用处理 func (s *Sign) _signReward(sign *model.FissionSignDay, rewards ...*model.FissionCampaignReward) (signReward *SignReward, recordId []int64, err error) { if len(rewards) == 0 { return } var rewardIds []int64 var add []*award.RewardOne for _, reward := range rewards { rewardIds = append(rewardIds, reward.ID) add = append(add, &award.RewardOne{ Reward: reward, }) } records, async, err := award.NewAwardReward(s.Ctx, s.GetDao(), s.cpn.ProjectID, s.user).Batch(add, false) if err != nil { return } stock, err := stocks.NewCampaignReward(s.Ctx, rewardIds...) if err != nil { return } player := s.GetPlayer() var addLog []*model.FissionSignLog for _, v := range records { recordId = append(recordId, v.Record.ID) val, _ := jsonm.MarshalToString(mtype.SignLogVal{ TOffset: s.GetTz().GetTz(), SignDate: sign.SignDate, RecordId: v.Record.ID, RewardId: v.Record.RewardID, }) addLog = append(addLog, withdefault.FissionSignLog(&model.FissionSignLog{ UserID: player.UserID, CampaignID: player.CampaignID, SignID: player.ID, Type: mtype.FissionSignLogTypeGetReward, Source: utils.Ternary[string](sign.SignType == mtype.FissionSignTypeRemedy, mtype.FissionSignLogSourceCardReward, mtype.FissionSignLogSourceSignReward), Val: val, })) _ = stock.AddUseStock(1, v.Record.RewardID) } err = s._addLog(addLog...) if err != nil { return } record, info := records[0].Record, records[0].Info signReward = &SignReward{ RewardTitle: info.Title, RewardNum: record.RewardNum, ImgUrl: info.ImgUrl, RewardId: record.RewardID, ReceiveType: info.ReceiveType, KvData: make([]*mtype.KV, 0, len(info.KvData)), Contact: info.Contact, } for _, v := range info.KvData { signReward.KvData = append(signReward.KvData, &mtype.KV{ Key: v.Key, Value: v.Value, }) } s.AddOkDone(async...) return } const ( signNextDay = "next_day" // 下次签到日期 signLastDay = "last_day" // 最后签到日期 signContinueNum = "continue_num" // 连续签到天数 signMaxContinue = "max_continue" // 最大连续签到天数 signCycleDay = "cycle_day" // 周期内签到天 ) func (s *Sign) _cycleData(refresh bool) (data *cachecurd.HMap, err error) { if s.cacheData != nil && !refresh { return s.cacheData, nil } player, tz := s.GetPlayer(), s.GetTz() dayStart, _ := timez.GMTTimeStart(tz.GetTz()) yesterdayStart := dayStart.AddDate(0, 0, -1) data, err = cachecurd.NewHMap(s.Ctx, s.signDataKey, cachecurd.SetHMapExp(2*consts.TimeDay)) if err != nil { return } data, err = data.CustomBuild(func() ([]string, error) { var values []string today := dayStart.Format(timez.ShortDateOnly) yesterday := yesterdayStart.Format(timez.ShortDateOnly) m := s.GetDao().FissionSignDay sign, err1 := m.WithContext(s.Ctx).Where(m.UserID.Eq(player.UserID), m.SignDate.In(today, yesterday)).Find() if err1 != nil { return nil, err1 } var todaySign, yesterdaySign *model.FissionSignDay for _, v := range sign { if v.SignDate == today { todaySign = v } else if v.SignDate == yesterday { yesterdaySign = v } } if todaySign == nil { values = append(values, signNextDay, fmt.Sprint(dayStart.Unix())) } else { values = append(values, signNextDay, fmt.Sprint(dayStart.AddDate(0, 0, 1).Unix())) } if yesterdaySign == nil { values = append(values, signLastDay, "0") } else { values = append(values, signLastDay, fmt.Sprint(yesterdayStart.Unix())) } // 连续签到天数 continueNum, maxContinue, cycleDate, err1 := s._continueNum() if err1 != nil { return nil, err1 } values = append(values, signContinueNum, fmt.Sprint(continueNum), signMaxContinue, fmt.Sprint(maxContinue), signCycleDay, strings.Join(cycleDate, consts.JoinComma)) return values, nil }) if err != nil { return } s.cacheData = data return } // 是否已经签到了 func (s *Sign) _hasSign(today time.Time) (exist bool, err error) { shortDay := today.Format(timez.ShortDateOnly) player := s.GetPlayer() key := ckey.FissionCampaignSignState(player.CampaignID, shortDay) if app.Redis.Cache.HExists(s.Ctx, key, fmt.Sprint(player.UserID)).Val() { exist = true return } m := s.GetDao().FissionSignDay sign, err := m.WithContext(s.Ctx).Where(m.UserID.Eq(player.UserID), m.SignDate.Eq(shortDay)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } return sign != nil, nil } // 签到某一天 func (s *Sign) _sign(signTime time.Time, useCard bool) (status int32, signDay *model.FissionSignDay, err error) { shortDay := signTime.Format(timez.ShortDateOnly) player, tz, conf := s.GetPlayer(), s.GetTz(), s.GetConf() // 是否已经签到了 exist, err := s._hasSign(signTime) if err != nil { return } if exist { status = SignStatusDone return } var value = xid.New().String() var mapKey = fmt.Sprint(player.UserID) var key = ckey.FissionCampaignSignState(player.CampaignID, shortDay) // 预防一些错误,要回滚缓存 s.AddErrDone(func() { v := app.Redis.Cache.HGet(s.Ctx, key, mapKey).Val() if v == value { app.Redis.Cache.HDel(s.Ctx, key, mapKey) } }) if !app.Redis.Cache.HSetNX(s.Ctx, key, mapKey, value).Val() { status = SignStatusDone return } // 延长缓存时间 app.Redis.Cache.ExpireAt(s.Ctx, key, signTime.AddDate(0, 0, 1)) // 签到记录 signDay = withdefault.FissionSignDay(&model.FissionSignDay{ UserID: player.UserID, CampaignID: player.CampaignID, SignDate: shortDay, SignType: utils.Ternary[int8](useCard, mtype.FissionSignTypeOk, mtype.FissionSignTypeRemedy), RecordIds: "", }) if err = s.GetDao().FissionSignDay.WithContext(s.Ctx).Create(signDay); err != nil { if errors.Is(err, gorm.ErrDuplicatedKey) { status = SignStatusFail return } err = s.BE().MysqlError(err) return } // 加日志 if useCard { val, _ := jsonm.ToObjString(mtype.SignLogVal{ TOffset: tz.GetTz(), SignDate: shortDay, CardNum: 1, }) err = s._addLog(withdefault.FissionSignLog(&model.FissionSignLog{ UserID: player.UserID, CampaignID: player.CampaignID, SignID: player.ID, Type: mtype.FissionSignLogTypeSignCard, Source: mtype.FissionSignLogSourceUseCard, Val: val, })) if err != nil { return } } else { val, _ := jsonm.ToObjString(mtype.SignLogVal{ TOffset: tz.GetTz(), SignDate: shortDay, }) err = s._addLog(withdefault.FissionSignLog(&model.FissionSignLog{ UserID: player.UserID, CampaignID: player.CampaignID, SignID: player.ID, Type: mtype.FissionSignLogTypeSign, Source: utils.Ternary[string](conf.SignType == mtype.FissionCampaignSignTypeCycle, mtype.FissionSignLogSourceManualSign, mtype.FissionSignLogSourceAutoSign), Val: val, })) if err != nil { return } } s.AddOkDone(func() { // 新版报表数据 if s.cpn.ReportType == mtype.FissionCampaignReportTypeNew { fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).Sign(time.Now().Unix(), tz.GetTz(), signTime.Format(timez.ShortDateOnly)) } }) s.upPlayer = append(s.upPlayer, s.GetDao().FissionSign.SignDay.Add(1)) return } // 当天签到(旧) func (s *Sign) _todaySign(today time.Time) (status int32, reward *SignReward, err error) { status, signDay, err := s._sign(today, false) if err != nil || status != SignStatusOk { return } reward, err = s._signOk(signDay) if err != nil { return } return } // 补签卡签到(旧) func (s *Sign) _cardSign(signDate time.Time) (status int32, reward *SignReward, err error) { player, tz := s.GetPlayer(), s.GetTz() if player.CardNum == 0 { status = SignStatusMissCard return } shortDay := signDate.Format(timez.ShortDateOnly) m := s.GetDao().FissionSignDay sign, err := m.WithContext(s.Ctx).Where(m.UserID.Eq(player.UserID), m.SignDate.Eq(shortDay)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if sign != nil { status = SignStatusDone return } signDay := withdefault.FissionSignDay(&model.FissionSignDay{ UserID: player.UserID, CampaignID: player.CampaignID, SignDate: shortDay, SignType: mtype.FissionSignTypeOk, RecordIds: "", }) if err = m.WithContext(s.Ctx).Create(signDay); err != nil { if errors.Is(err, gorm.ErrDuplicatedKey) { status = SignStatusFail return } err = s.BE().MysqlError(err) return } status = SignStatusCard playerM := s.GetDao().FissionSign s.upPlayer = append(s.upPlayer, playerM.CardNum.Sub(1)) // 加日志 val, _ := jsonm.ToObjString(mtype.SignLogVal{ TOffset: tz.GetTz(), SignDate: shortDay, }) err = s._addLog(withdefault.FissionSignLog(&model.FissionSignLog{ UserID: player.UserID, CampaignID: player.CampaignID, SignID: player.ID, Type: mtype.FissionSignLogTypeSignCard, Source: mtype.FissionSignLogSourceUseCard, Val: val, })) if err != nil { return } reward, err = s._signOk(signDay) if err != nil { return } if s.cpn.ReportType == mtype.FissionCampaignReportTypeNew { fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).UseProp(time.Now().Unix(), consts.PropNameSignCard) } return } // 普通签到版(旧) func (s *Sign) _normalSignBoard(pageType int32, nowMouth string) (signBoard *SignBoard, err error) { cpn, conf, player, tz := s.cpn, s.conf, s.GetPlayer(), s.GetTz() // 签到开始和结束时间 var now = timez.GMTCarbon(tz.GetTz()).Now() var start carbon.Carbon var end = timez.GMTCarbon(tz.GetTz()).CreateFromTimestamp(cpn.TEnd).EndOfDay() if cpn.Status == mtype.FissionCampaignStatusWait { start = timez.GMTCarbon(tz.GetTz()).CreateFromTimestamp(cpn.TStart) } else if player.SignStart != 0 { start = timez.GMTCarbon(tz.GetTz()).CreateFromTimestamp(player.SignStart) } else { start = now } start = start.StartOfDay() // 奖品日期 dayReward, lastReward, err := s._dayReward(start, end, player.LoopNum) if err != nil { return } // 签到数据 signMap, rewardIdMap, openMap, lastMiss, err := s._dataMap(start, now, dayReward, lastReward) if err != nil { return } // 本周开始和周结束 wStart, wEnd := now.StartOfWeek(), now.EndOfWeek() // 某个呀的签到板 var mStart carbon.Carbon switch pageType { case 1: mStart = timez.GMTCarbon(tz.GetTz()).ParseByFormat(nowMouth, "Y-n").AddMonth() case 2: mStart = timez.GMTCarbon(tz.GetTz()).ParseByFormat(nowMouth, "Y-n").SubMonth() } if mStart.IsInvalid() { mStart = now } // 完整月 mStart = mStart.StartOfMonth() mEnd := mStart.EndOfMonth() signBoard = &SignBoard{ SignDate: nil, NowMouth: mStart.ToFormatString("Y-n"), } // 之前的时间 nw := mStart.Week() for i := nw; i > 0; i-- { s._appendSignDate(mStart.SubDays(i), start, end, wStart, wEnd, mStart, mEnd, rewardIdMap, signMap, openMap, conf.HasCard, lastMiss, signBoard) } begin := mStart for { if begin.Gte(mEnd) { break } s._appendSignDate(begin, start, end, wStart, wEnd, mStart, mEnd, rewardIdMap, signMap, openMap, conf.HasCard, lastMiss, signBoard) begin = begin.AddDay() } ew := begin.Week() for i := 0; i < 7-ew; i++ { s._appendSignDate(begin.AddDays(i), start, end, wStart, wEnd, mStart, mEnd, rewardIdMap, signMap, openMap, conf.HasCard, lastMiss, signBoard) } return } // 普通签到获得奖品逻辑(旧) func (s *Sign) _normalReward(sign *model.FissionSignDay, totalNum int32) (signReward *SignReward, recordId []int64, err error) { cm := cmm.NewCMM(s.Ctx, s.GetDao()) rewards, err := cm.UserAppReward(s.user) if err != nil { return } if len(rewards) == 0 { return } reward := rewards[0] num := totalNum / reward.SignDay if num == 0 { return } player := s.GetPlayer() m := s.GetDao().FissionRewardRecord.SubTable(player.ID) count, err := m.WithContext(s.Ctx).Where( m.UserID.Eq(player.ID), m.RewardID.Eq(reward.ID), m.RewardType.Eq(mtype.FissionCampaignRewardTypeApp), ).Count() if err != nil { err = s.BE().MysqlError(err) return } can := int64(num) - count if can == 0 { return } var addRewards []*model.FissionCampaignReward for i := can; i > 0; i-- { addRewards = append(addRewards, reward) } return s._signReward(sign, addRewards...) } // 阶梯签到获得奖品逻辑(旧) func (s *Sign) _stepReward(sign *model.FissionSignDay, totalNum int32) (signReward *SignReward, recordId []int64, err error) { cm := cmm.NewCMM(s.Ctx, s.GetDao()) rewards, err := cm.UserAppReward(s.user, s.GetDao().FissionCampaignReward.Step) if err != nil { return } if len(rewards) == 0 { return } player, conf := s.GetPlayer(), s.GetConf() var stepMap = make(map[int32]*model.FissionCampaignReward) for _, v := range rewards { stepMap[v.Step] = v } m := s.GetDao().FissionRewardRecord.SubTable(player.CampaignID) list, err := m.WithContext(s.Ctx).Where( m.UserID.Eq(player.UserID), m.RewardType.Eq(mtype.FissionCampaignRewardTypeApp), ).Find() if err != nil { err = s.BE().MysqlError(err) return } if conf.IsLoop == mtype.No { for _, v := range list { delete(stepMap, v.Step) } var addRewards []*model.FissionCampaignReward for _, reward := range stepMap { if totalNum < reward.SignDay { continue } addRewards = append(addRewards, reward) } signReward, recordId, err = s._signReward(sign, addRewards...) if err != nil { return } return } l := len(rewards) lastReward := rewards[l-1] signDay := 1 + player.SignDay - player.LoopNum*lastReward.SignDay nextStep := player.NextStep if nextStep == 0 { nextStep = 1 } if signDay < totalNum { totalNum = signDay } var addLoopNum int32 var addRewards []*model.FissionCampaignReward for { stepReward := stepMap[nextStep] if totalNum < stepReward.SignDay { break } if stepReward.ID == lastReward.ID { addLoopNum++ nextStep = 1 totalNum -= lastReward.SignDay } else { nextStep++ } addRewards = append(addRewards, stepReward) } signReward, recordId, err = s._signReward(sign, addRewards...) if err != nil { return } playerM := s.GetDao().FissionSign s.upPlayer = append(s.upPlayer, playerM.LoopNum.Add(addLoopNum), playerM.NextStep.Value(nextStep)) return } type signState struct { RewardId int64 SignDay int32 addDay int } // 签到板奖品分布(旧) func (s *Sign) _dayReward(start, end carbon.Carbon, loopNum int32) (dayReward []*signState, lastReward *model.FissionCampaignReward, err error) { conf := s.GetConf() rewards, err := cmm.NewCMM(s.Ctx, s.GetDao()).UserAppReward(s.user) if err != nil { return } if len(rewards) == 0 { err = workerr.NotFound("not found reward") return } l := len(rewards) lastReward = rewards[l-1] // 非阶梯一个奖品,多次获得 if conf.SignType == mtype.FissionCampaignSignTypeNormal { reward := rewards[0] if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if reward == nil { err = workerr.NotFound("not found reward") return } diff := start.DiffAbsInDays(end) + 1 times := int(diff / int64(reward.SignDay)) dayReward = make([]*signState, times) for i := 0; i < times; i++ { day := int32(i+1)*reward.SignDay - 1 dayReward[i] = &signState{ RewardId: reward.ID, addDay: int(day), SignDay: reward.SignDay, } } return } // 活动奖品,阶梯多奖品 // 不支持阶梯循环 if conf.IsLoop == mtype.No { dayReward = make([]*signState, 0, len(rewards)) for _, v := range rewards { dayReward = append(dayReward, &signState{ RewardId: v.ID, addDay: int(v.SignDay - 1), SignDay: v.SignDay, }) } return } doLoop := int(loopNum + 1) for i := 0; i < doLoop; i++ { for _, v := range rewards { dayReward = append(dayReward, &signState{ RewardId: v.ID, addDay: int(v.SignDay-1) + int(lastReward.SignDay)*i, SignDay: v.SignDay, }) } } return } // 签到版基础数据(旧) func (s *Sign) _dataMap(start, now carbon.Carbon, dayReward []*signState, lastReward *model.FissionCampaignReward, ) (signMap map[string]int8, rewardIdMap map[string]int64, openMap map[int64]int, lastMiss string, err error) { player, conf, tz := s.GetPlayer(), s.GetConf(), s.GetTz() // 签到的所有数据 sdm := s.GetDao().FissionSignDay sd, err := sdm.WithContext(s.Ctx). Select(sdm.SignDate, sdm.RecordIds). Where( sdm.UserID.Eq(player.UserID), sdm.SignDate.Gte(start.ToShortDateString()), sdm.SignDate.Lte(now.ToShortDateString()), ).Order(sdm.SignDate).Find() if err != nil { err = s.BE().MysqlError(err) return } sdMap := make(map[string][]int64) for _, day := range sd { sdMap[day.SignDate] = day.GetRecordIds() } rewardIdMap = make(map[string]int64) var recordIds []int64 signMap = make(map[string]int8) se := utils.BetweenDay(start, now) for _, day := range se { idv, ok := sdMap[day] if ok { if len(idv) > 0 { recordIds = append(recordIds, idv...) } signMap[day] = mtype.FissionSignTypeOk continue } signMap[day] = mtype.FissionSignTypeMiss lastMiss = day missDay := timez.GMTCarbon(tz.GetTz()).ParseByFormat(day, "Ymd") if conf.IsContinue == mtype.Yes { var idx int32 for k, reward := range dayReward { rewardDay := start.AddDays(reward.addDay) if missDay.Gt(rewardDay) { continue } switch conf.SignType { case mtype.FissionCampaignSignTypeStep: dayReward[k].addDay = int(missDay.AddDays(int(reward.SignDay)).DiffAbsInDays(start)) + int(idx*lastReward.SignDay) if reward.RewardId == lastReward.ID { idx++ } case mtype.FissionCampaignSignTypeNormal: idx++ dayReward[k].addDay = int(missDay.AddDays(int(idx * reward.SignDay)).DiffAbsInDays(start)) } } } else { for k, reward := range dayReward { if missDay.Gt(start.AddDays(reward.addDay)) { continue } dayReward[k].addDay++ } } } for _, state := range dayReward { rewardIdMap[start.AddDays(state.addDay).ToShortDateString()] = state.RewardId } if len(recordIds) == 0 { return } // 领奖数据 openMap = make(map[int64]int) recordM := s.GetDao().FissionRewardRecord.SubTable(player.CampaignID) records, err := recordM.WithContext(s.Ctx).Where(recordM.ID.In(recordIds...)).Find() if err != nil { err = s.BE().MysqlError(err) return } for _, record := range records { openMap[record.RewardID]++ } return } // 签到板追加数据(旧) func (s *Sign) _appendSignDate(dt, start, end, wStart, wEnd, mStart, mEnd carbon.Carbon, rewardIdMap map[string]int64, signMap map[string]int8, openMap map[int64]int, hashCard int8, lastMiss string, res *SignBoard) { dateStr := dt.ToShortDateString() one := &SignBoardOne{ Y: fmt.Sprint(dt.Year()), M: fmt.Sprint(dt.Month()), D: fmt.Sprint(dt.Day()), SignIn: 0, Ban: dt.Lt(start) || dt.Gt(end), RewardId: rewardIdMap[dateStr], Open: false, NowWeek: dt.Gte(wStart) && dt.Lte(wEnd), NowDay: dt.IsToday(), LastMiss: lastMiss != "" && lastMiss == dateStr, NowMouth: dt.Gte(mStart) && dt.Lte(mEnd), } if st, ok := signMap[dateStr]; ok { one.SignIn = utils.Ternary[int32](st == mtype.FissionSignTypeMiss, 2, 1) one.Ban = !one.Ban && st == mtype.FissionSignTypeMiss && hashCard == mtype.No // 不能补签禁用 } if open, ok := openMap[one.RewardId]; ok { one.Open = open > 0 openMap[one.RewardId]-- } res.SignDate = append(res.SignDate, one) } // 签到成功 (旧) func (s *Sign) _signOk(signDay *model.FissionSignDay) (reward *SignReward, err error) { continueNum, maxContinue, _, err := s._continueNum() if err != nil { return } player, conf := s.GetPlayer(), s.GetConf() playerM := s.GetDao().FissionSign s.upPlayer = append(s.upPlayer, playerM.SignContinue.Value(continueNum), playerM.MaxContinue.Value(maxContinue), ) if player.SignStart == 0 { s.upPlayer = append(s.upPlayer, playerM.SignStart.Value(time.Now().Unix())) } var totalNum = player.SignDay + 1 if conf.IsLoop == mtype.Yes { totalNum = continueNum } else if conf.IsContinue == mtype.Yes { totalNum = maxContinue } var recordIds []int64 switch conf.SignType { case mtype.FissionCampaignSignTypeNormal: reward, recordIds, err = s._normalReward(signDay, totalNum) if err != nil { return } case mtype.FissionCampaignSignTypeStep: reward, recordIds, err = s._stepReward(signDay, totalNum) if err != nil { return } } // 绑定数据 if len(recordIds) > 0 { sdm := s.GetDao().FissionSignDay _, err = sdm.WithContext(s.Ctx).Where(sdm.ID.Eq(signDay.ID)).UpdateSimple(sdm.RecordIds.Value(utils.Join(recordIds, consts.JoinComma))) if err != nil { err = s.BE().MysqlError(err) return } } // 更新玩家数据 _, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).UpdateSimple(s.upPlayer...) if err != nil { err = s.BE().MysqlError(err) return } // 更新统计数据 statM := s.GetDao().FissionUserStat _, err = statM.WithContext(s.Ctx).Where(statM.UserID.Eq(player.UserID)).UpdateSimple(statM.SignNum.Add(1), statM.SignRewardNum.Add(int64(len(recordIds)))) if err != nil { err = s.BE().MysqlError(err) return } return }