优化杀分逻辑

This commit is contained in:
2026-03-17 15:36:14 +08:00
parent 1892c7bcb7
commit 150d31eac5
9 changed files with 38 additions and 37 deletions

View File

@@ -18,7 +18,6 @@
"t5Weight": "T5 Pool Weight (%)", "t5Weight": "T5 Pool Weight (%)",
"weightsSumHint": "Total pool weights: ", "weightsSumHint": "Total pool weights: ",
"weightsSumUnit": "% / 100% (must equal 100%)", "weightsSumUnit": "% / 100% (must equal 100%)",
"weightsSumUnitCurrent": "% / 100%",
"currentPoolTitle": "Current Lottery Pool", "currentPoolTitle": "Current Lottery Pool",
"loading": "Loading...", "loading": "Loading...",
"poolName": "Pool Name", "poolName": "Pool Name",

View File

@@ -18,7 +18,6 @@
"t5Weight": "T5池权重(%)", "t5Weight": "T5池权重(%)",
"weightsSumHint": "五个池权重总和:", "weightsSumHint": "五个池权重总和:",
"weightsSumUnit": "% / 100%必须为100%", "weightsSumUnit": "% / 100%必须为100%",
"weightsSumUnitCurrent": "% / 100%须为100%",
"currentPoolTitle": "当前彩金池", "currentPoolTitle": "当前彩金池",
"loading": "加载中...", "loading": "加载中...",
"poolName": "池子名称", "poolName": "池子名称",

View File

@@ -104,6 +104,7 @@ export default {
id: number id: number
name: string name: string
safety_line: number safety_line: number
kill_enabled: number
t1_weight: number t1_weight: number
t2_weight: number t2_weight: number
t3_weight: number t3_weight: number
@@ -118,14 +119,7 @@ export default {
/** /**
* 更新当前彩金池:仅 safety_line、t1_weightt5_weight不可改 profit_amount * 更新当前彩金池:仅 safety_line、t1_weightt5_weight不可改 profit_amount
*/ */
updateCurrentPool(params: { updateCurrentPool(params: { safety_line?: number; kill_enabled?: number }) {
safety_line?: number
t1_weight?: number
t2_weight?: number
t3_weight?: number
t4_weight?: number
t5_weight?: number
}) {
return request.post<any>({ return request.post<any>({
url: '/core/dice/lottery_pool_config/DiceLotteryPoolConfig/updateCurrentPool', url: '/core/dice/lottery_pool_config/DiceLotteryPoolConfig/updateCurrentPool',
data: params data: params

View File

@@ -42,6 +42,9 @@
style="width: 100%" style="width: 100%"
/> />
</el-form-item> </el-form-item>
<el-form-item label="开启杀分">
<el-switch v-model="formData.kill_enabled" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item :label="$t('page.form.killScoreWeights')"> <el-form-item :label="$t('page.form.killScoreWeights')">
<div class="text-gray-500 text-sm"> <div class="text-gray-500 text-sm">
T1: {{ pool.t1_weight }}% / T2: {{ pool.t2_weight }}% / T3: {{ pool.t3_weight }}% / T4: {{ pool.t4_weight }}% / T5: {{ pool.t5_weight }}% T1: {{ pool.t1_weight }}% / T2: {{ pool.t2_weight }}% / T3: {{ pool.t3_weight }}% / T4: {{ pool.t4_weight }}% / T5: {{ pool.t5_weight }}%
@@ -49,10 +52,7 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<div class="text-gray-500 text-sm"> <div class="text-gray-500 text-sm">
{{ $t('page.form.weightsSumHint') }}<span :class="weightsSum !== 100 ? 'text-red-500' : ''">{{ {{ $t('page.form.killWeightNote') }}
weightsSum
}}</span
>{{ $t('page.form.weightsSumUnitCurrent') }} {{ $t('page.form.killWeightNote') }}
</div> </div>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -81,6 +81,7 @@
id: number id: number
name: string name: string
safety_line: number safety_line: number
kill_enabled: number
t1_weight: number t1_weight: number
t2_weight: number t2_weight: number
t3_weight: number t3_weight: number
@@ -104,7 +105,8 @@
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
const formData = reactive({ const formData = reactive({
safety_line: 0 safety_line: 0,
kill_enabled: 1
}) })
const rules = computed<FormRules>(() => ({ const rules = computed<FormRules>(() => ({
@@ -143,6 +145,7 @@
if (data && typeof data === 'object') { if (data && typeof data === 'object') {
pool.value = data pool.value = data
formData.safety_line = data.safety_line ?? 0 formData.safety_line = data.safety_line ?? 0
formData.kill_enabled = (data.kill_enabled ?? 1) === 1 ? 1 : 0
} }
} catch (e: any) { } catch (e: any) {
ElMessage.error(e?.message ?? t('page.form.msgGetPoolFailed')) ElMessage.error(e?.message ?? t('page.form.msgGetPoolFailed'))
@@ -180,7 +183,8 @@
await formRef.value?.validate?.() await formRef.value?.validate?.()
saving.value = true saving.value = true
await api.updateCurrentPool({ await api.updateCurrentPool({
safety_line: formData.safety_line safety_line: formData.safety_line,
kill_enabled: formData.kill_enabled
}) })
ElMessage.success(t('page.form.msgSaveSuccess')) ElMessage.success(t('page.form.msgSaveSuccess'))
await loadPool() await loadPool()

View File

@@ -75,10 +75,6 @@ class PlayStartLogic
if (!$configType0) { if (!$configType0) {
throw new ApiException('奖池配置不存在(需 type=0'); throw new ApiException('奖池配置不存在(需 type=0');
} }
// 杀分时使用 type=1 配置的权重;未杀分时付费用 type=0、免费用 type=1无 type=1 时回退 type=0
$configForWeights = $ticketType === self::LOTTERY_TYPE_PAID
? $configType0
: ($configType1 ?? $configType0);
// 玩家累计盈利:仅统计 lottery_config_id=type=0 的成功对局(中奖金额-100*局数) // 玩家累计盈利:仅统计 lottery_config_id=type=0 的成功对局(中奖金额-100*局数)
$playerQuery = DicePlayRecord::where('player_id', $playerId) $playerQuery = DicePlayRecord::where('player_id', $playerId)
@@ -88,13 +84,11 @@ class PlayStartLogic
$playerPlayCount = (int) $playerQuery->count(); $playerPlayCount = (int) $playerQuery->count();
$playerProfitTotal = $playerWinSum - 100.0 * $playerPlayCount; $playerProfitTotal = $playerWinSum - 100.0 * $playerPlayCount;
$safetyLine = (int) ($configType0->safety_line ?? 0); $safetyLine = (int) ($configType0->safety_line ?? 0);
// 玩家累计盈利>=安全线时杀分:用 type=1 的 T*_weight并记录 lottery_config_id=type=1 的 id否则用玩家权重记录对应配置 id $killEnabled = ((int) ($configType0->kill_enabled ?? 1)) === 1;
$usePoolWeights = $playerProfitTotal >= $safetyLine && $configType1 !== null; // 玩家累计盈利>=安全线时杀分:无论付费/免费,都用 type=1 的 T*_weight未达到时一律按玩家权重
if ($usePoolWeights) { // 记录 lottery_config_id杀分记 type=1未杀分统一记当前池 type=0
$config = $configType1; $usePoolWeights = $killEnabled && $playerProfitTotal >= $safetyLine && $configType1 !== null;
} else { $config = $usePoolWeights ? $configType1 : $configType0;
$config = $configForWeights;
}
// 按档位 T1-T5 抽取后,从 DiceReward 表按当前方向取该档位数据,再按 weight 抽取一条得到 grid_number // 按档位 T1-T5 抽取后,从 DiceReward 表按当前方向取该档位数据,再按 weight 抽取一条得到 grid_number
$rewardInstance = DiceReward::getCachedInstance(); $rewardInstance = DiceReward::getCachedInstance();

View File

@@ -34,7 +34,7 @@ class DiceLotteryPoolConfigLogic extends BaseLogic
* 获取当前彩金池type=0+ 杀分权重为 type=1 的只读展示 * 获取当前彩金池type=0+ 杀分权重为 type=1 的只读展示
* profit_amount 每次从 DB 实时读取t1_weightt5_weight 来自 type=1杀分权重不可在弹窗内修改 * profit_amount 每次从 DB 实时读取t1_weightt5_weight 来自 type=1杀分权重不可在弹窗内修改
* *
* @return array{id:int,name:string,safety_line:int,t1_weight:int,...,t5_weight:int,profit_amount:float} * @return array{id:int,name:string,safety_line:int,kill_enabled:int,t1_weight:int,...,t5_weight:int,profit_amount:float}
*/ */
public function getCurrentPool(): array public function getCurrentPool(): array
{ {
@@ -49,6 +49,7 @@ class DiceLotteryPoolConfigLogic extends BaseLogic
'id' => (int) $row0['id'], 'id' => (int) $row0['id'],
'name' => (string) ($row0['name'] ?? ''), 'name' => (string) ($row0['name'] ?? ''),
'safety_line' => (int) ($row0['safety_line'] ?? 0), 'safety_line' => (int) ($row0['safety_line'] ?? 0),
'kill_enabled' => (int) ($row0['kill_enabled'] ?? 1),
'profit_amount' => $profitAmount, 'profit_amount' => $profitAmount,
]; ];
$row1 = $configType1 ? $configType1->toArray() : []; $row1 = $configType1 ? $configType1->toArray() : [];
@@ -61,19 +62,28 @@ class DiceLotteryPoolConfigLogic extends BaseLogic
} }
/** /**
* 更新当前彩金池:仅允许修改 type=0 的 safety_line杀分权重来自 type=1不可在此接口修改 * 更新当前彩金池:仅允许修改 type=0 的 safety_line、kill_enabled(杀分权重来自 type=1不可在此接口修改
* *
* @param array{safety_line?:int} $data * @param array{safety_line?:int,kill_enabled?:int} $data
*/ */
public function updateCurrentPool(array $data): void public function updateCurrentPool(array $data): void
{ {
$pool = $this->getCurrentPool(); $pool = $this->getCurrentPool();
$id = (int) $pool['id']; $id = (int) $pool['id'];
if (!array_key_exists('safety_line', $data)) { if (!array_key_exists('safety_line', $data) && !array_key_exists('kill_enabled', $data)) {
return; return;
} }
$safetyLine = (int) $data['safety_line']; $update = [];
DiceLotteryPoolConfig::where('id', $id)->update(['safety_line' => $safetyLine]); if (array_key_exists('safety_line', $data)) {
$update['safety_line'] = (int) $data['safety_line'];
}
if (array_key_exists('kill_enabled', $data)) {
$update['kill_enabled'] = ((int) $data['kill_enabled']) === 1 ? 1 : 0;
}
if ($update === []) {
return;
}
DiceLotteryPoolConfig::where('id', $id)->update($update);
} }
/** /**

View File

@@ -11,13 +11,14 @@ use plugin\saiadmin\basic\think\BaseModel;
/** /**
* 色子奖池配置模型 * 色子奖池配置模型
* *
* dice_lottery_config 色子奖池配置 * dice_lottery_pool_config 色子奖池配置
* *
* @property $id ID * @property $id ID
* @property $name 名称 * @property $name 名称
* @property $remark 备注 * @property $remark 备注
* @property $type 奖池类型 * @property $type 奖池类型
* @property $safety_line 安全线 * @property $safety_line 安全线
* @property $kill_enabled 是否启用杀分0=关闭 1=开启
* @property $create_time 创建时间 * @property $create_time 创建时间
* @property $update_time 修改时间 * @property $update_time 修改时间
* @property $t1_weight T1池权重 * @property $t1_weight T1池权重
@@ -39,7 +40,7 @@ class DiceLotteryPoolConfig extends BaseModel
* 数据库表名称 * 数据库表名称
* @var string * @var string
*/ */
protected $table = 'dice_lottery_config'; protected $table = 'dice_lottery_pool_config';
/** /**
* 名称 搜索 * 名称 搜索

View File

@@ -115,7 +115,7 @@ Goal: hot paths should hit Redis most of the time, and DB should primarily be fo
- In `playStart`, ensure player is loaded once and reused (do not call `DicePlayer::find` multiple times per request). - In `playStart`, ensure player is loaded once and reused (do not call `DicePlayer::find` multiple times per request).
3. EV update strategy: 3. EV update strategy:
- Repeated `UPDATE dice_lottery_config SET ev = ev - ?` on a hot row causes lock contention. - Repeated `UPDATE dice_lottery_pool_config SET ev = ev - ?` on a hot row causes lock contention.
- Better: - Better:
- Accumulate EV deltas in Redis (per pool or per shard). - Accumulate EV deltas in Redis (per pool or per shard).
- Periodic cron job to aggregate Redis deltas back into MySQL in batches. - Periodic cron job to aggregate Redis deltas back into MySQL in batches.

View File

@@ -113,7 +113,7 @@
-`playStart` 中,玩家信息应只查询一次:`$player = DicePlayer::find($playerId)`,后续逻辑统一使用 `$player`,避免重复 `find` -`playStart` 中,玩家信息应只查询一次:`$player = DicePlayer::find($playerId)`,后续逻辑统一使用 `$player`,避免重复 `find`
3. **EV 更新策略** 3. **EV 更新策略**
- 频繁在在线请求中执行 `UPDATE dice_lottery_config SET ev = ev - ?`,会造成该行热点锁竞争; - 频繁在在线请求中执行 `UPDATE dice_lottery_pool_config SET ev = ev - ?`,会造成该行热点锁竞争;
- 建议: - 建议:
- 在线请求仅将 EV 变动累加到 Redis 计数器; - 在线请求仅将 EV 变动累加到 Redis 计数器;
- 通过定时任务批量同步 Redis 中的统计数据回 MySQL。 - 通过定时任务批量同步 Redis 中的统计数据回 MySQL。