package checkinser import ( "strconv" "time" "91porn-server/app/appg" "91porn-server/common/db" "91porn-server/common/log" "91porn-server/common/stderr" "91porn-server/common/timeutil" "91porn-server/models/v/checkinconfigmod" "91porn-server/models/v/checkinprizemod" "91porn-server/models/v/prizemod" "91porn-server/models/v/txnmod" "91porn-server/models/v/usercheckinmod" "91porn-server/models/v/usermod" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo/options" ) const ( checkinLimitKeyPrefix = "checkin::limit::" checkinLimitExpire = 3 * time.Second ) // CheckinInfo 签到信息 type CheckinInfo struct { TodayChecked bool `json:"todayChecked"` ContinuouslyDays int64 `json:"continuouslyDays"` CumulativeDays int64 `json:"cumulativeDays"` DoubleReward bool `json:"doubleReward"` } // CheckinConfigResp 签到配置返回 type CheckinConfigResp struct { Enable bool `json:"enable"` Description string `json:"description"` BackgroundImage string `json:"backgroundImage"` IntegerExchangeList []checkinconfigmod.GiftItem `json:"integerExchangeList"` } // PrizeBrief 奖品摘要 type PrizeBrief struct { PrizeImage string `json:"prizeImage"` PrizeType prizemod.PrizeType `json:"prizeType"` PrizeCount int64 `json:"prizeCount"` PrizeTitle string `json:"prizeTitle"` } type PrizeBriefList []*PrizeBrief // UserCheckinResp 用户签到返回 type UserCheckinResp struct { Message string `json:"message"` Checkin CheckinInfo `json:"checkin"` Prizes PrizeBriefList `json:"prizes"` PrizeVideo string `json:"prizeVideo"` } // CheckinDto 签到奖品DTO type CheckinDto struct { checkinprizemod.CheckinPrize `bson:",inline"` Score int64 `json:"score"` PrizeType prizemod.PrizeType `json:"prizeType"` IsReceive bool `json:"isReceive"` IsCheckedIn bool `json:"isCheckedIn"` CanClaim bool `json:"canClaim"` IsExpired bool `json:"isExpired"` } // GetCheckinPrizeResp 获取签到奖品返回 type GetCheckinPrizeResp struct { Prizes []CheckinDto `json:"prizes"` BigPrizes []CheckinDto `json:"bigPrizes"` Checkin CheckinInfo `json:"checkin"` Config CheckinConfigResp `json:"config"` } // getCheckinConfig 获取签到配置 func getCheckinConfig() *checkinconfigmod.CheckinConfig { cfg, _ := checkinconfigmod.FindOne(bson.M{}) return cfg } // giveCheckinPrizeById 发放签到奖品 func giveCheckinPrizeById(uid uint64, checkInPrize *checkinprizemod.CheckinPrize, isVip bool) (*PrizeBrief, error) { if checkInPrize == nil || checkInPrize.PrizeId.IsZero() { return nil, nil } prizes, err := prizemod.GetPrizeListByIDs([]primitive.ObjectID{checkInPrize.PrizeId}) if err != nil || len(prizes) == 0 { return nil, err } prize := *prizes[0] // 非VIP专属奖励 && 是VIP用户 && 积分类型 → 积分+2 if isVip && !checkInPrize.BigPrize && prize.Type == prizemod.Integral { prize.Price += 2 } handler, err := prizemod.Run(uid, prize, txnmod.SignBoon) if err != nil { return nil, err } if handler == nil { return nil, nil } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { if err := handler.Run(t); err != nil { return err } return txnmod.InsertManyTransactionLog(t, handler.GetTransactionLog()) }); err != nil { return nil, err } prizeBrief := &PrizeBrief{ PrizeImage: prize.Image, PrizeType: prize.Type, PrizeCount: prize.Price, PrizeTitle: checkInPrize.PrizeName, } return prizeBrief, nil } // AddCheckin 添加一次签到记录 func AddCheckin(uid uint64) (*UserCheckinResp, stderr.Code) { // 3秒限速 limitKey := checkinLimitKeyPrefix + strconv.FormatUint(uid, 10) if ok, _ := appg.Redis.Setnx_NewOK(limitKey, 1, checkinLimitExpire); !ok { return nil, stderr.ErrLoginTooFrequently } now := time.Now().Local() cfg := getCheckinConfig() if cfg == nil || !cfg.Enable { return nil, stderr.FunctionNotEnabled } today := timeutil.BeginOfTime(now) // 获取今天签到记录 checkinToday, err := usercheckinmod.FindOne(bson.M{ "userId": uid, "date": today, }) if err != nil { log.Error("AddCheckin:FindOne:today", log.E(err)) return nil, stderr.ErrDbQueryError } if checkinToday != nil { resp := &UserCheckinResp{ Message: "今日已经签到,请勿重复签到", Checkin: CheckinInfo{ TodayChecked: true, ContinuouslyDays: checkinToday.ContinuouslyDays, CumulativeDays: checkinToday.CumulativeDays, }, } return resp, stderr.Success } // 插入签到记录 checkin := usercheckinmod.UserCheckin{ ID: primitive.NewObjectID(), Date: today, UserId: uid, Prizes: nil, Gave: false, CreatedAt: now, IsReset: false, } if err := usercheckinmod.InsertOne(&checkin); err != nil { log.Error("AddCheckin:InsertOne failed", log.E(err)) return nil, stderr.ErrDbInsertError } // 查询本月签到记录 limit := int64(now.Day()) checkinOpt := options.Find() checkinOpt.SetSort(bson.D{{Key: "date", Value: -1}}).SetLimit(limit) checkinList, err := usercheckinmod.FindMany(bson.M{"userId": uid}, checkinOpt) if err != nil { log.Error("AddCheckin:FindMany", log.E(err)) return nil, stderr.ErrDbQueryError } ContinuouslyDays := int64(1) CumulativeDays := int64(1) doubleBonusReceived := false beginOfCurrentMonth := timeutil.BeginningOfMonth(now) yesterday := today.Add(-24 * time.Hour) for i, item := range checkinList { if i == 0 { continue // 跳过今天刚插入的记录 } if item.Date.Before(beginOfCurrentMonth) { continue } CumulativeDays += 1 if i == 1 { if item.Date.Equal(yesterday) { ContinuouslyDays = item.ContinuouslyDays + 1 } } else if item.ContinuouslyDays == 7 { doubleBonusReceived = true } } // 查找签到奖励 prizeBriefList := make(PrizeBriefList, 0) bgMediaUrl := "" continuouslyPrizes, err := checkinprizemod.FindMany(bson.M{ "status": true, "checkinType": checkinprizemod.CheckinTypeContinuously, "checkinDays": ContinuouslyDays, }) if err != nil { log.Error("AddCheckin:FindMany prizes", log.E(err)) return nil, stderr.ErrDbQueryError } u, err := usermod.FindUserByUID(uid) if err != nil { return nil, stderr.ErrDbQueryError } prizeIds := make([]primitive.ObjectID, 0) giveVipPrize := false isVip := u != nil && u.IsPaidVIP() continuousGiveTimes := int64(1) if len(continuouslyPrizes) > 0 { if ContinuouslyDays == 7 && !doubleBonusReceived { continuousGiveTimes = 2 } for _, item := range continuouslyPrizes { if item.BigPrize && !isVip { continue } for i := int64(0); i < continuousGiveTimes; i++ { prizeBrief, err := giveCheckinPrizeById(uid, item, isVip) if err != nil { log.Error("giveCheckinPrizeById", log.E(err)) continue } if prizeBrief == nil { continue } prizeBriefList = append(prizeBriefList, prizeBrief) prizeIds = append(prizeIds, item.PrizeId) giveVipPrize = giveVipPrize || item.BigPrize // 7天翻倍,根据非VIP的奖励类型来返回奖励背景视频 if !item.BigPrize && continuousGiveTimes == 2 && cfg.RewardBgVideos != nil { for _, vItem := range cfg.RewardBgVideos { if vItem.PrizeType == prizeBrief.PrizeType { bgMediaUrl = vItem.BgMediaUrl break } } } } } } // 更新签到记录 update := bson.M{ "continuouslyDays": ContinuouslyDays, "cumulativeDays": CumulativeDays, "prizes": prizeIds, "gave": true, "vipPrizeGave": giveVipPrize, } if err = usercheckinmod.UpdateOne(bson.M{"_id": checkin.ID}, bson.M{"$set": update}); err != nil { log.Error("AddCheckin:UpdateOne", log.E(err)) } resp := &UserCheckinResp{ Message: "签到成功", Checkin: CheckinInfo{ TodayChecked: true, ContinuouslyDays: ContinuouslyDays, CumulativeDays: CumulativeDays, DoubleReward: continuousGiveTimes == 2, }, Prizes: prizeBriefList, PrizeVideo: bgMediaUrl, } return resp, stderr.Success } // GetCheckinPrizes 获取签到奖品 func GetCheckinPrizes(uid uint64) (*GetCheckinPrizeResp, stderr.Code) { now := time.Now().Local() cfg := getCheckinConfig() cfgResp := CheckinConfigResp{} if cfg != nil { cfgResp.Enable = cfg.Enable cfgResp.Description = cfg.Description cfgResp.BackgroundImage = cfg.BackgroundImage cfgResp.IntegerExchangeList = cfg.IntegerExchangeList } // 获取所有启用的连续签到奖品 opts := options.Find() opts.SetSort(bson.D{{Key: "checkinDays", Value: 1}}) var info []CheckinDto err := checkinprizemod.FindManyWithBind(&info, bson.M{ "status": true, "checkinType": checkinprizemod.CheckinTypeContinuously, }, opts) if err != nil { log.Error("GetCheckinPrizes:FindMany", log.E(err)) return nil, stderr.ErrDbQueryError } // 查询奖品信息,填充积分数据 prizeIdSet := make(map[primitive.ObjectID]struct{}) for _, item := range info { prizeIdSet[item.PrizeId] = struct{}{} } prizeIds := make([]primitive.ObjectID, 0, len(prizeIdSet)) for id := range prizeIdSet { prizeIds = append(prizeIds, id) } if len(prizeIds) > 0 { prizeList, _ := prizemod.GetPrizeListByIDs(prizeIds) prizeMap := make(map[primitive.ObjectID]*prizemod.Prize) for _, p := range prizeList { prizeMap[p.ID] = p } for i, v := range info { if prize, ok := prizeMap[v.PrizeId]; ok { info[i].Score = int64(prize.Count) info[i].PrizeType = prize.Type if prize.Type == prizemod.Gold { info[i].Score = int64(prize.Count) / 10 } } } } // 分组 normalPrizes := make([]CheckinDto, 0) bigPrizes := make([]CheckinDto, 0) for _, item := range info { if item.BigPrize { bigPrizes = append(bigPrizes, item) } else { normalPrizes = append(normalPrizes, item) } } // 签到记录 limit := int64(now.Day()) checkinOpt := options.Find() checkinOpt.SetSort(bson.D{{Key: "date", Value: -1}}).SetLimit(limit) checkinList, err := usercheckinmod.FindMany(bson.M{"userId": uid}, checkinOpt) if err != nil { log.Error("GetCheckinPrizes:FindMany checkins", log.E(err)) return nil, stderr.ErrDbQueryError } ContinuouslyDays := int64(0) CumulativeDays := int64(0) lastCheckinDay := timeutil.BeginOfTime(now) doubleRewardsReceived := false isTodayCheckin := false // 查询用户VIP状态 isVip := false if u, uErr := usermod.FindUserByUID(uid); uErr == nil && u != nil { isVip = u.IsPaidVIP() } if len(checkinList) > 0 { if checkinList[0].Date.Equal(lastCheckinDay) { isTodayCheckin = true } else if checkinList[0].Date.Equal(lastCheckinDay.Add(-24 * time.Hour)) { lastCheckinDay = lastCheckinDay.Add(-24 * time.Hour) } beginOfCurrentMonth := timeutil.BeginningOfMonth(now) streakBroken := false for _, item := range checkinList { CumulativeDays += 1 if lastCheckinDay.Before(beginOfCurrentMonth) { lastCheckinDay = beginOfCurrentMonth } if !streakBroken && item.Date.Equal(lastCheckinDay) { ContinuouslyDays += 1 lastCheckinDay = item.Date.Add(-24 * time.Hour) } else { streakBroken = true } if ContinuouslyDays >= 7 { doubleRewardsReceived = true } } // 只对当前连续签到的记录映射IsReceive和IsCheckedIn for i := int64(0); i < ContinuouslyDays; i++ { record := checkinList[ContinuouslyDays-1-i] idx := int(i) if idx < len(normalPrizes) { normalPrizes[idx].IsReceive = record.Gave normalPrizes[idx].IsCheckedIn = true } if idx < len(bigPrizes) { bigPrizes[idx].IsReceive = record.VipPrizeGave bigPrizes[idx].IsCheckedIn = true } } } // 标记可补领的VIP奖励 if isVip && isTodayCheckin && len(checkinList) > 0 { for _, item := range checkinList { if !item.Date.Equal(timeutil.BeginOfTime(now)) { continue } if item.VipPrizeGave { continue } for i := range bigPrizes { if bigPrizes[i].CheckinDays == item.ContinuouslyDays { bigPrizes[i].CanClaim = true break } } } } // 标记已过期的大会员奖励:已签到、未领取、不能补领、且不是今天签到对应的那天 todayPrizeIdx := int(ContinuouslyDays - 1) // 今天签到对应的bigPrizes索引 for i := range bigPrizes { if i == todayPrizeIdx && isTodayCheckin { continue } if bigPrizes[i].IsCheckedIn && !bigPrizes[i].IsReceive && !bigPrizes[i].CanClaim { bigPrizes[i].IsExpired = true } } doubleReward := !doubleRewardsReceived && ContinuouslyDays == 6 && !isTodayCheckin resp := &GetCheckinPrizeResp{ Prizes: normalPrizes, BigPrizes: bigPrizes, Checkin: CheckinInfo{ TodayChecked: isTodayCheckin, DoubleReward: doubleReward, ContinuouslyDays: ContinuouslyDays, CumulativeDays: CumulativeDays, }, Config: cfgResp, } return resp, stderr.Success } // ClaimVipCheckinPrizes 补领VIP签到奖励 func ClaimVipCheckinPrizes(uid uint64) (*UserCheckinResp, stderr.Code) { // 3秒限速 limitKey := checkinLimitKeyPrefix + strconv.FormatUint(uid, 10) if ok, _ := appg.Redis.Setnx_NewOK(limitKey, 1, checkinLimitExpire); !ok { return nil, stderr.ErrLoginTooFrequently } now := time.Now().Local() cfg := getCheckinConfig() if cfg == nil || !cfg.Enable { return nil, stderr.FunctionNotEnabled } // 校验用户是否是VIP u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return nil, stderr.UserIsNotExists } if !u.IsPaidVIP() { return nil, stderr.NoVipPrivilege } // 查询当天未领取VIP奖励的签到记录 today := timeutil.BeginOfTime(now) checkinOpt := options.Find() checkinOpt.SetSort(bson.D{{Key: "date", Value: 1}}) unclaimed, err := usercheckinmod.FindMany(bson.M{ "userId": uid, "vipPrizeGave": false, "date": today, }, checkinOpt) if err != nil { return nil, stderr.ErrDbQueryError } if len(unclaimed) == 0 { return nil, stderr.Failure } // 获取所有启用的连续签到VIP奖励配置 vipPrizes, err := checkinprizemod.FindMany(bson.M{ "status": true, "checkinType": checkinprizemod.CheckinTypeContinuously, "bigPrize": true, }) if err != nil { return nil, stderr.ErrDbQueryError } vipPrizeMap := make(map[int64]*checkinprizemod.CheckinPrize) for _, p := range vipPrizes { vipPrizeMap[p.CheckinDays] = p } // 逐条补领 prizeBriefList := make(PrizeBriefList, 0) for _, record := range unclaimed { prize, ok := vipPrizeMap[record.ContinuouslyDays] if !ok { continue } // 原子更新防并发 if err = usercheckinmod.UpdateOne(bson.M{ "_id": record.ID, "vipPrizeGave": false, }, bson.M{ "$set": bson.M{"vipPrizeGave": true}, }); err != nil { continue } prizeBrief, gErr := giveCheckinPrizeById(uid, prize, true) if gErr != nil { log.Error("ClaimVipCheckinPrizes:giveCheckinPrizeById", log.E(gErr), log.Any("recordId", record.ID)) continue } if prizeBrief != nil { prizeBriefList = append(prizeBriefList, prizeBrief) } } if len(prizeBriefList) == 0 { return nil, stderr.Failure } resp := &UserCheckinResp{ Message: "补领成功", Prizes: prizeBriefList, } return resp, stderr.Success } // GetCheckinConfig 获取签到配置(给后台用) func GetCheckinConfig() (*checkinconfigmod.CheckinConfig, error) { cfg, err := checkinconfigmod.FindOne(bson.M{}) if err != nil { return nil, err } if cfg == nil { return &checkinconfigmod.CheckinConfig{}, nil } return cfg, nil } // UpdateCheckinConfig 更新签到配置 func UpdateCheckinConfig(update bson.M) error { count, err := checkinconfigmod.Count(bson.M{}) if err != nil { return err } if count == 0 { newConfig := &checkinconfigmod.CheckinConfig{} return checkinconfigmod.InsertOne(newConfig) } // 查找并更新第一条 cfg, err := checkinconfigmod.FindOne(bson.M{}) if err != nil { return err } return checkinconfigmod.UpdateOne(bson.M{"_id": cfg.ID}, update) } // GetCheckinPrizeList 获取签到奖品列表(后台) func GetCheckinPrizeList(pageNumber, pageSize int64) (checkinprizemod.CheckinPrizeList, int64, error) { filter := bson.M{} total, err := checkinprizemod.Count(filter) if err != nil { return nil, 0, err } if total == 0 { return make(checkinprizemod.CheckinPrizeList, 0), 0, nil } opts := options.Find() opts.SetLimit(pageSize) opts.SetSkip((pageNumber - 1) * pageSize) opts.SetSort(bson.D{{Key: "checkinDays", Value: 1}}) data, err := checkinprizemod.FindMany(filter, opts) if err != nil { return nil, 0, err } return data, total, nil } // AddCheckinPrize 添加签到奖品 func AddCheckinPrize(prize *checkinprizemod.CheckinPrize) error { now := time.Now() prize.CreatedAt = now prize.UpdatedAt = now return checkinprizemod.InsertOne(prize) } // UpdateCheckinPrize 更新签到奖品 func UpdateCheckinPrize(id primitive.ObjectID, update bson.M) error { return checkinprizemod.UpdateOne(bson.M{"_id": id}, bson.M{"$set": update}) } // DeleteCheckinPrize 删除签到奖品 func DeleteCheckinPrize(id primitive.ObjectID) error { _, err := checkinprizemod.DeleteOne(bson.M{"_id": id}) return err } // GetUserCheckinList 获取用户签到历史(后台) func GetUserCheckinList(filter bson.M, pageNumber, pageSize int64) (usercheckinmod.CheckinList, int64, error) { total, err := usercheckinmod.Count(filter) if err != nil { return nil, 0, err } if total == 0 { return make(usercheckinmod.CheckinList, 0), 0, nil } opts := options.Find() opts.SetSort(bson.D{{Key: "createdAt", Value: -1}}) opts.SetSkip((pageNumber - 1) * pageSize) opts.SetLimit(pageSize) data, err := usercheckinmod.FindMany(filter, opts) if err != nil { return nil, 0, err } return data, total, nil } // DeleteUserCheckin 删除用户签到记录 func DeleteUserCheckin(id primitive.ObjectID) error { _, err := usercheckinmod.DeleteOne(bson.M{"_id": id}) return err }