Browse Source

user:游客默认 1000 天会员和 1000 算力,存量不足的只补一次到底线。

Co-authored-by: Cursor <cursoragent@cursor.com>
along-test
Rodger-Wang 2 days ago
parent
commit
943a522233
  1. 16
      apps/services/modules/user/api_sgin.go
  2. 49
      apps/services/modules/user/model_user.go
  3. 43
      apps/services/modules/user/newuser_gift.go
  4. 26
      apps/services/modules/user/newuser_gift_test.go

16
apps/services/modules/user/api_sgin.go

@ -243,9 +243,15 @@ func (this *apiComp) Sgin(session comm.IUserSession, req *pb.UserSginReq) (resp
// 新用户开户礼:VIP 天数 + 算力,两个数额由后台「会员与算力」页配置。
// 在 add 之前赋值,随用户一起落库,不需要额外一次写。见 newuser_gift.go。
// 参数取一次传给发放与流水两处,保证两者用同一份快照。
// 游客不走这档配置,固定 1000 天 VIP + 1000 算力。
giftNow := time.Now().Unix()
giftParams := comm.LoadOpsParams()
applyNewUserGift(user, giftNow, giftParams)
var giftParams comm.OpsParams
if req.Stype == pb.SginTyoe_Tourists {
applyTouristEntitlement(user, giftNow)
} else {
giftParams = comm.LoadOpsParams()
applyNewUserGift(user, giftNow, giftParams)
}
if err = this.module.model.add(user); err != nil {
errdata = &pb.ErrorData{
Code: pb.ErrorCode_DBError,
@ -253,7 +259,11 @@ func (this *apiComp) Sgin(session comm.IUserSession, req *pb.UserSginReq) (resp
}
return
}
logNewUserGift(user.Uid, giftNow, giftParams)
if req.Stype == pb.SginTyoe_Tourists {
logTouristEntitlement(user.Uid, giftNow)
} else {
logNewUserGift(user.Uid, giftNow, giftParams)
}
// 新增用户埋点
if this.module.analyze != nil {
this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventRegister, Uid: user.Uid})

49
apps/services/modules/user/model_user.go

@ -41,10 +41,59 @@ func (this *modelUserComp) Init(service core.IService, module core.IModule, comp
this.module.Errorln(err)
}
this.migrateLegacyBucketsToCompute()
this.grantTouristEntitlement()
return
}
// touristEntitlementKey 游客权益补发标记。存在即不再跑,避免把花掉的算力再补回 1000。
const touristEntitlementKey = "TOURIST_ENTITLEMENT_1000"
// 与 findtouristformac 同一口径:所有登录身份都空,才是游客。NULL 与空串都算空。
const touristIdentitySQL = "COALESCE(phone,'')='' AND COALESCE(mail,'')='' AND COALESCE(account,'')='' " +
"AND COALESCE(wxopenid,'')='' AND COALESCE(googleopenid,'')='' " +
"AND COALESCE(facebookopenid,'')='' AND COALESCE(appleopenid,'')=''"
// grantTouristEntitlement 给存量游客补到 1000 天 VIP、1000 算力。只跑一次。
//
// 只抬底:会员已经更长、算力已经更多的不动。禁用账号不补。
// 比较用的是「此刻起 1000 天」,所以会员剩 10 天的会被续到 1000 天,剩 2000 天的保持不动。
func (this *modelUserComp) grantTouristEntitlement() {
marker := &pb.DBAppConfigItem{}
if err := mysql.FindOne(comm.TableAppConfig, marker, "`key`=?", touristEntitlementKey); err == nil {
return
} else if err != mysql.ErrNoDocuments {
this.module.Warn("游客权益:读标记失败,本次跳过", log.Field{Key: "err", Value: err.Error()})
return
}
sec := touristVipDays * 24 * 60 * 60
res := mysql.Exec(
"UPDATE "+comm.TableUser+" SET "+
"computebalance = GREATEST(computebalance, ?), "+
"vipexptime = GREATEST(vipexptime, UNIX_TIMESTAMP() + ?) "+
"WHERE "+touristIdentitySQL+" AND state = ? "+
"AND (computebalance < ? OR vipexptime < UNIX_TIMESTAMP() + ?)",
touristCompute, sec, int32(pb.UserState_Normal), touristCompute, sec)
if res.Error != nil {
this.module.Error("游客权益:补发失败", log.Field{Key: "err", Value: res.Error.Error()})
return
}
if err := mysql.Insert(comm.TableAppConfig, &pb.DBAppConfigItem{
Group: "compute", Key: touristEntitlementKey, Value: "1",
Description: "存量游客已补到 1000 天 VIP + 1000 算力(自动迁移标记,勿删)",
}); err != nil {
// 同一容器里 home/api 会各跑一次。另一边已经写过标记就不必再警告。
again := &pb.DBAppConfigItem{}
if mysql.FindOne(comm.TableAppConfig, again, "`key`=?", touristEntitlementKey) == nil {
this.module.Infof("游客权益:已为 %d 个游客补到底线", res.RowsAffected)
return
}
this.module.Warn("游客权益:写标记失败,下次启动会再补一次", log.Field{Key: "err", Value: err.Error()})
return
}
this.module.Infof("游客权益:已为 %d 个游客补到底线", res.RowsAffected)
}
// legacyComputeMigratedKey config 表里的一次性迁移标记;存在即不再跑。
const legacyComputeMigratedKey = "COMPUTE_LEGACY_MIGRATED"

43
apps/services/modules/user/newuser_gift.go

@ -35,6 +35,49 @@ func applyNewUserGift(user *pb.DBUser, now int64, p comm.OpsParams) {
user.Computebalance = p.NewUserGiftCompute
}
// 游客权益:固定 1000 天 VIP + 1000 算力,不走后台「开户礼」配置。
//
// 游客没有手机号/支付账号,后台那档开户礼(默认 30 天 + 100 算力)不够用。
// 只抬底、不削顶:已经比这个多的(更长的会员、更多的算力)保持原值。
// 新建账号从零值抬上来就是正好 1000;存量补发用同一套比较,避免把花掉的算力再补回去——
// 存量只在启动时补一次,见 modelUserComp.grantTouristEntitlement。
const (
touristVipDays int64 = 1000
touristCompute int64 = 1000
)
// applyTouristEntitlement 把游客权益写进尚未落库的新游客。
func applyTouristEntitlement(user *pb.DBUser, now int64) {
if user == nil {
return
}
user.Vipexptime, user.Computebalance = raiseTouristEntitlement(user.Vipexptime, user.Computebalance, now)
}
// raiseTouristEntitlement 会员到期与算力向游客底线靠拢。返回值可直接写回用户。
func raiseTouristEntitlement(vipExp, compute, now int64) (int64, int64) {
floorExp := now + touristVipDays*24*60*60
if vipExp < floorExp {
vipExp = floorExp
}
if compute < touristCompute {
compute = touristCompute
}
return vipExp, compute
}
// logTouristEntitlement 记一条游客开户流水。礼包已随用户落库,流水写失败不回滚。
func logTouristEntitlement(uid string, now int64) {
_ = mysql.Insert(comm.TableUserUseLog, &pb.DBUserUseLog{
Uid: uid,
Ts: now,
Logtype: pb.UserLogType_ActivityReward,
Addvipday: touristVipDays,
Compute: touristCompute,
Extra: "tourist entitlement",
})
}
// logNewUserGift 记一条活动奖励流水,便于后台对账「送出去多少」。
// 礼包已经随用户一起落库了,这条流水写失败不回滚、只忽略。
func logNewUserGift(uid string, now int64, p comm.OpsParams) {

26
apps/services/modules/user/newuser_gift_test.go

@ -52,6 +52,32 @@ func TestApplyNewUserGiftComputeOnly(t *testing.T) {
// 老用户不该被碰:applyNewUserGift 只在「新建用户」分支调用,
// 这里顺带守住它是直接赋值而非累加(新对象都是零值,累加与赋值等价;
// 万一将来有人挪去老用户路径,这个测试会提醒他先想清楚语义)。
func TestApplyTouristEntitlementFloor(t *testing.T) {
const now int64 = 1_700_000_000
floorExp := now + touristVipDays*24*60*60
fresh := &pb.DBUser{}
applyTouristEntitlement(fresh, now)
if fresh.Vipexptime != floorExp || fresh.Computebalance != touristCompute {
t.Fatalf("新游客应正好 1000 天 + 1000 算力,得到 vip=%d compute=%d", fresh.Vipexptime, fresh.Computebalance)
}
if fresh.Tradeintegral != 0 || fresh.Meetintegral != 0 || fresh.Aichatintegral != 0 {
t.Fatalf("游客权益不应再写旧额度桶")
}
rich := &pb.DBUser{Vipexptime: floorExp + 86400, Computebalance: touristCompute + 50}
applyTouristEntitlement(rich, now)
if rich.Vipexptime != floorExp+86400 || rich.Computebalance != touristCompute+50 {
t.Fatalf("已经更高的不应被削低,得到 vip=%d compute=%d", rich.Vipexptime, rich.Computebalance)
}
short := &pb.DBUser{Vipexptime: now + 10*86400, Computebalance: 20}
applyTouristEntitlement(short, now)
if short.Vipexptime != floorExp || short.Computebalance != touristCompute {
t.Fatalf("不足的应抬到游客底线,得到 vip=%d compute=%d", short.Vipexptime, short.Computebalance)
}
}
func TestApplyNewUserGiftIsAssignNotAccumulate(t *testing.T) {
const now int64 = 1_700_000_000
p := comm.DefaultOpsParams()

Loading…
Cancel
Save