外观
月卡/终身卡 · 技术实现
「玩家设定 →」
1. 代码位置
| 层 | 类 | 职责 |
|---|---|---|
| Service | service/InventoryService | 使用道具激活:useItem 命中 MONTHLY_CARD_ID/LIFETIME_CARD_ID 时调 gameRepository.activateMonthlyCard 续期,并给 VIP 经验(VIP_ITEM_USE) |
| Service | service/GrowthTowerSplitService | 修炼自动结算开关:setTrainingAutoSettleEnabled(仅月卡有效期内可开启,业务码 7416),返回 McAutoSettleToggleDto |
| Service | service/GrowthTowerAutoAdvanceScheduler | 统一调度入口 autoSettleDispatch(30 秒一跳):同一跳内串行执行三档——月卡快速档(每跳 30 秒)/ 在线玩家档(30 分钟)/ 离线玩家档(2 小时),单池串行消除时间重叠;快速档扫描中发现月卡到期会自愈关闭开关并记 fight log |
| Controller | controller/GrowthTowerController | POST /growth-tower/training/auto-settle-toggle 开关接口 |
| Service | service/ShopService | buildOverview 组装 ShopOverviewDto.mcAutoSettleEnabled(月卡过期即视为关闭,DB 位由快速档自愈清零) |
| repository | repository/GameRepository | activateMonthlyCard(playerId, expireAt) 写特权;setMcAutoSettleEnabled/isMcAutoSettleEnabled/listTrainingTowerStatesForMcAutoSettle 维护与扫描开关 |
| domain | domain/ShopBalanceRules | 卡常量与成长倍率 |
| domain | domain/PlayerPrivilege | isMonthlyCardActive/isLifetimeCardActive(到期判定) |
2. 数据表
| 表 | 关键列 | 说明 |
|---|---|---|
player_privilege | monthly_card_expire_at、lifetime_card_expire_at | 月卡到期(V10 迁移加列)、终身卡(V57) |
player_training_tower_state | mc_auto_settle_enabled | F8 月卡 30 秒自动结算开关(V128,默认 0;索引 (mc_auto_settle_enabled, player_id)) |
vip_experience_change | player_id、delta、source=VIP_ITEM_USE | 激活卡片的 VIP 经验(V58) |
3. 配置与常量
- 卡片常量:
名称 数值 说明 welfareTicketIdWELFARE_TICKET_ID 4001L — rainbowStoneIdRAINBOW_STONE_ID 4002L — monthlyCardIdMONTHLY_CARD_ID 4003L — namingCardIdNAMING_CARD_ID 4004L — lifetimeCardIdLIFETIME_CARD_ID 4005L — welfareTicketNameWELFARE_TICKET_NAME "福利点券" — dailySignInRewardTicketsDAILY_SIGN_IN_REWARD_TICKETS 1_000 — rainbowStoneNameRAINBOW_STONE_NAME "五彩神石" — monthlyCardNameMONTHLY_CARD_NAME "月卡" — namingCardNameNAMING_CARD_NAME "冠名卡" — lifetimeCardNameLIFETIME_CARD_NAME "终身卡" — namingNameNAMING_NAME "冠名" — dailyWelfareChallengeLimitDAILY_WELFARE_CHALLENGE_LIMIT 100 — rainbowStonePriceRAINBOW_STONE_PRICE 100 — baseDailyStoneLimitBASE_DAILY_STONE_LIMIT 50 — monthlyCardExtraStoneLimitMONTHLY_CARD_EXTRA_STONE_LIMIT 50 — namingExtraStoneLimitNAMING_EXTRA_STONE_LIMIT 400 — lifetimeCardSweepQuotaBonusLIFETIME_CARD_SWEEP_QUOTA_BONUS 10 — serverDailyStoneLimitSERVER_DAILY_STONE_LIMIT 600 — worldHornLimitIdWORLD_HORN_LIMIT_ID 9501L 世界喇叭福利点券兑换(对齐 docs/33 §4.2) worldHornLimitNameWORLD_HORN_LIMIT_NAME "世界喇叭日限购" — worldHornPriceWORLD_HORN_PRICE 100 — dailyWorldHornLimitDAILY_WORLD_HORN_LIMIT 10 — rainbowStoneResonanceRatePerStoneRAINBOW_STONE_RESONANCE_RATE_PER_STONE 0.0004D VIP7 最终 ×1.5、VIP8 基础 +1 / 最终 ×1.5;VIP9/10 数值权益并入 VIP8。 vip5GrowthThresholdVIP5_GROWTH_THRESHOLD 5 — vip6GrowthThresholdVIP6_GROWTH_THRESHOLD 6 — vip7GrowthThresholdVIP7_GROWTH_THRESHOLD 7 — vip8GrowthThresholdVIP8_GROWTH_THRESHOLD 8 — monthlyCardGrowthBaseContributionMONTHLY_CARD_GROWTH_BASE_CONTRIBUTION 1 — vip5GrowthBaseContributionVIP5_GROWTH_BASE_CONTRIBUTION 1 — vip6GrowthBaseContributionVIP6_GROWTH_BASE_CONTRIBUTION 1 — vip8GrowthBaseContributionVIP8_GROWTH_BASE_CONTRIBUTION 1 — namingGrowthBaseContributionNAMING_GROWTH_BASE_CONTRIBUTION 1 — lifetimeCardGrowthBaseContributionLIFETIME_CARD_GROWTH_BASE_CONTRIBUTION 1 — monthlyCardFinalMultiplierMONTHLY_CARD_FINAL_MULTIPLIER 1.2D — lifetimeCardFinalMultiplierLIFETIME_CARD_FINAL_MULTIPLIER 1.2D — vip5FinalMultiplierVIP5_FINAL_MULTIPLIER 1.2D — vip7FinalMultiplierVIP7_FINAL_MULTIPLIER 1.5D — vip8FinalMultiplierVIP8_FINAL_MULTIPLIER 1.5D — namingFinalMultiplierNAMING_FINAL_MULTIPLIER 2.0D — baseGrowthRateBASE_GROWTH_RATE 5.0D — monthlyCardAttackSpeedBonusMONTHLY_CARD_ATTACK_SPEED_BONUS 2.0D — lifetimeCardAttackSpeedBonusLIFETIME_CARD_ATTACK_SPEED_BONUS 2.0D — namingAttackSpeedBonusNAMING_ATTACK_SPEED_BONUS 3.0D — namingExtraTicketsNAMING_EXTRA_TICKETS 500 — mountDropRateMOUNT_DROP_RATE 0.001D 坐骑掉率,对齐进阶石 mountMaterialQtyEasyMOUNT_MATERIAL_QTY_EASY 50 坐骑/翅膀副本升级材料按怪物分级掉落(百次模型,NORMAL 100 颗/次) mountMaterialQtyNormalMOUNT_MATERIAL_QTY_NORMAL 100 — mountMaterialQtyHardMOUNT_MATERIAL_QTY_HARD 150 — mountMaterialQtyEliteMOUNT_MATERIAL_QTY_ELITE 200 — mountMaterialQtyBossMOUNT_MATERIAL_QTY_BOSS 250 — entryDungeonMaterialMultiplierENTRY_DUNGEON_MATERIAL_MULTIPLIER 0.40D — wingDropRateWING_DROP_RATE 0.001D 翅膀副本掉落与升级消耗(对齐 docs/翅膀玩法设计文档.md §5),数值镜像坐骑。 petDropRatePET_DROP_RATE 0.001D — materialDungeonVipBonusPerRunMATERIAL_DUNGEON_VIP_BONUS_PER_RUN 0.5D VIP5+ 每场 +50% materialDungeonMonthlyBonusPerRunMATERIAL_DUNGEON_MONTHLY_BONUS_PER_RUN 0.5D 月卡 每场 +50% materialDungeonNamingBonusPerRunMATERIAL_DUNGEON_NAMING_BONUS_PER_RUN 1.0D 冠名 每场 +100% materialDungeonMaxRewardMultiplierMATERIAL_DUNGEON_MAX_REWARD_MULTIPLIER 3.0D — materialDungeonTierEasyFactorMATERIAL_DUNGEON_TIER_EASY_FACTOR 0.25D 更高难度(BOSS)有真实溢价,更低难度(EASY/NORMAL/HARD)产出下降,纠正"所有难度一样"的体验。 materialDungeonTierNormalFactorMATERIAL_DUNGEON_TIER_NORMAL_FACTOR 0.5D — materialDungeonTierHardFactorMATERIAL_DUNGEON_TIER_HARD_FACTOR 0.75D — materialDungeonTierEliteFactorMATERIAL_DUNGEON_TIER_ELITE_FACTOR 1.0D — materialDungeonTierBossFactorMATERIAL_DUNGEON_TIER_BOSS_FACTOR 1.25D — welfareBaseTicketsWELFARE_BASE_TICKETS 10 福利怪掉落规则(加法模型,可叠加) monthlyCardExtraTicketsMONTHLY_CARD_EXTRA_TICKETS 100 — maxVipLevelMAX_VIP_LEVEL 10 — maxVipBenefitLevelMAX_VIP_BENEFIT_LEVEL 8 承载数值权益的最高 VIP 等级(docs/64 §1.3):VIP1-8 承载全部成长、战斗、材料、 额度、次数与便利加成,VIP9/10 是荣誉等级、不再提供任何数值权益。 所有依赖 VIP 等级的数值权益一律用 {@link #effectiveVipLevel(int)} 取等级。 vipThresholdsVIP_THRESHOLDS { 0, 600, 3_000, 6_800, 12_800, 25_800, 64_800, 150_000, 300_000, 500_000, 1_000_000 } VIP0 vipWelfareTicketBonusVIP_WELFARE_TICKET_BONUS {0, 10, 10, 35, 35, 75, 110, 150, 200} VIP1-8 固定档位加成(docs/64 §1.3):索引即 VIP 等级,VIP2/VIP4 不重复加成, 累计值 = 本级数组值(VIP8 封顶 +200)。 vipStoneLimitBonusVIP_STONE_LIMIT_BONUS {0, 10, 10, 35, 35, 75, 110, 150, 200} — vipAttackSpeedBonusVIP_ATTACK_SPEED_BONUS {0.0D, 0.10D, 0.10D, 0.35D, 0.35D, 0.75D, 1.10D, 1.50D, 2.00D} — 来源:emberfall-server/src/main/java/com/emberfall/domain/ShopBalanceRules.java· commit22d062aff99fabdc373f3b1c82b2a6eff4a034d3MONTHLY_CARD_ID=4003、LIFETIME_CARD_ID=4005、MONTHLY_CARD_FINAL_MULTIPLIER=1.2、LIFETIME_CARD_FINAL_MULTIPLIER=1.2(成长最终倍率 ×1.2)MONTHLY_CARD_ATTACK_SPEED_BONUS=2.0、LIFETIME_CARD_ATTACK_SPEED_BONUS=2.0(攻速 +2.0)- 每日神石额度 +50(
MONTHLY_CARD_EXTRA_STONE_LIMIT)、福利点券每档 +100(MONTHLY_CARD_EXTRA_TICKETS)
- yml 键:无(注意
V26__mount_wing_monthly_card_tier.sql月卡档位影响坐骑/翅膀等级上限) - 修炼自动结算(F8)yml 键(
emberfall.growth-tower.auto-settle.*):dispatch-delay-ms=30000(统一调度入口,30 秒一跳)、dispatch-initial-delay-ms=20000mc-batch-size=50、mc-threshold-seconds=30(距上次结算超过 30 秒才处理)- 在线玩家档:
active-delay-ms=1800000(30 分钟)、active-threshold-seconds=1800、active-login-window-seconds=600 - 离线玩家档:
offline-delay-ms=7200000(2 小时)、offline-threshold-seconds=7200
4. 数据流简述
玩家购买/领取卡片道具(4003/4005)后,POST /api/inventory/use(InventoryService.useItem)识别卡片类型,调用 activateMonthlyCard 将 monthly_card_expire_at 顺延 30 天(终身卡置永久),同时 VipExperienceService 记 VIP_ITEM_USE 经验。此后 PlayerPrivilege.isMonthlyCardActive() 在成长倍率(×1.2)、攻速、每日神石额度、试炼入场/留存加成(trial.v2.entry.monthly-bonus)等处生效,到期自动失效。
4.1 修炼自动结算(F8 月卡快速档)
- 开启:月卡页开关 →
POST /growth-tower/training/auto-settle-toggle→setTrainingAutoSettleEnabled校验isMonthlyCardActive()(否则 7416「开通月卡后可用」),写player_training_tower_state.mc_auto_settle_enabled=1并记 fight log。默认关闭,重新开通月卡后需手动再开。 - 结算:
GrowthTowerAutoAdvanceScheduler.autoSettleDispatch每 30 秒一跳,同一跳内串行执行到期的档位(月卡快速档每跳 → 在线档 30 分钟 → 离线档 2 小时),复用settleTrainingForAuto(结算公式与收益总量不变,只改变到账颗粒度);结算后调playerService.invalidatePlayerInfoCache失效属性缓存,前端打开首页详情(fetchMidFrequencyData(true),15 秒节流)即拉到最新属性。 - 单档排空:每档单轮循环扫到短批或游标回绕为止(
player_id > cursor主查询 +player_id <= cursor回绕查询恰好互补覆盖全量玩家一圈),保证「在线玩家全服 30 分钟一轮 / 离线 2 小时一轮 / 快速档每跳一轮」,大服下不会被批大小拖慢成 N 轮才扫完;单档异常在 dispatch 内隔离,失败档下跳自动重试,不影响其他档。回绕即止(活锁护栏):满批且整批结算失败时(last_settle_time不更新)回绕后同批玩家会反复命中、循环永不终止——检测到新游标 ≤ 旧游标即一圈扫完立即跳出,失败玩家留待下跳重试,每跳每玩家至多一次结算尝试。 - 到期自愈:扫描中发现月卡已到期的玩家,直接置
mc_auto_settle_enabled=0并记 fight log「月卡已到期,修炼自动结算(30 秒快速档)已自动关闭」;满 12 小时上限的到期玩家不受性能护栏过滤,保证自愈必定执行。 - 性能护栏(每日挂机上限 12 小时,避免无效高频结算):
- 扫描 SQL
listTrainingTowerStatesForMcAutoSettleLEFT JOINplayer_growth_state过滤raw_combat_seconds >= 43200且月卡仍有效的玩家——当日已满 12 小时的玩家在 DB 层直接跳过快速档,不进入结算流程; - 满上限玩家的经验不丢失:四维收益为 0 但经验照常累计,其普通/离线档 SQL 条件
mc_auto_settle_enabled=0 OR raw_combat_seconds >= 43200使其回落在线/离线档以普通粒度继续结算,开关开/关的累计收益保持一致(数值不变性); last_settle_time各档阈值(MC 30 秒 / 在线 1800 秒 / 离线 7200 秒)跳过刚结算过的玩家;- 统一调度入口 + 单线程池串行执行(
AtomicBoolean防重入),三档时间上永不重叠;各档独立分布式锁 + 游标翻页(含回绕,保证每轮全量覆盖)。
- 扫描 SQL
- 在线判定:
online=player.last_login_at在active-login-window-seconds(600 秒)窗口内;该列由鉴权请求节流心跳维护(JwtAuthenticationFilter→GameRepository.touchPlayerActivity,进程内ConcurrentHashMap+ DB 侧 WHERE 双层节流,默认 5 分钟一次,emberfall.player.activity-touch-interval-seconds),语义为「最近活跃」——修复了仅登录时写入导致在线档扫不到人的问题。语义影响面:last_login_at从「最后登录时间」变为「最近活跃时间」,管理端玩家列表的「最后登录」列实际展示最后活跃;追赶统计findPlayerIdsActiveSince的「活跃玩家」口径随之变宽(有任意请求即算活跃),运营读数时需注意。
5. 已知差异
无
6. 相关代码
- VIP(
VipExperienceService:卡片激活给经验) - 冠名卡(
ShopBalanceRules.NAMING_CARD_ID=4004) - 余烬试炼场(
trial.v2.entry.monthly-bonus/lifetime-bonus) - 坐骑/翅膀档位(V26:月卡档位等级上限)
- 成长塔修炼挂机(
GrowthTowerSplitService/GrowthTowerAutoAdvanceScheduler:三档自动结算与 12 小时上限)