功能:自动存点增加金币奖励 + VIP 加成

- heartbeat 增加金币奖励逻辑,读取 jjb_per_heartbeat 配置
- 支持固定值('5')和范围('1-10')两种奖励配置格式
- VIP 会员自动应用经验和金币加成倍率
- 前端手动存点显示金币余额和本次获得的奖励增量
- 新增迁移文件插入 jjb_per_heartbeat 配置项(默认 1-3)
- 更新 exp_per_heartbeat 描述说明支持范围格式
This commit is contained in:
2026-02-27 00:44:45 +08:00
parent c7b668b1ba
commit 4cc2982d9f
3 changed files with 112 additions and 13 deletions

View File

@@ -149,17 +149,29 @@ class ChatController extends Controller
return response()->json(['status' => 'error'], 401);
}
// 1. 心跳经验:通过 Redis 限制最小间隔默认30秒防止频繁点击刷经验
$expCooldownKey = "heartbeat_exp:{$user->id}";
$canGainExp = ! Redis::exists($expCooldownKey);
// 1. 心跳奖励:通过 Redis 限制最小间隔默认30秒防止频繁点击
$cooldownKey = "heartbeat_exp:{$user->id}";
$canGainReward = ! Redis::exists($cooldownKey);
$actualExpGain = 0;
$actualJjbGain = 0;
if ($canGainExp) {
$expGain = (int) Sysparam::getValue('exp_per_heartbeat', '1');
if ($canGainReward) {
// 经验奖励(支持固定值 "1" 或范围 "1-10"
$expGain = $this->parseRewardValue(Sysparam::getValue('exp_per_heartbeat', '1'));
$expMultiplier = $this->vipService->getExpMultiplier($user);
$user->exp_num += (int) round($expGain * $expMultiplier);
$actualExpGain = (int) round($expGain * $expMultiplier);
$user->exp_num += $actualExpGain;
// 设置冷却30秒内不再给经验
Redis::setex($expCooldownKey, 30, 1);
// 金币奖励(支持固定值 "1" 或范围 "1-5"
$jjbGain = $this->parseRewardValue(Sysparam::getValue('jjb_per_heartbeat', '0'));
if ($jjbGain > 0) {
$jjbMultiplier = $this->vipService->getJjbMultiplier($user);
$actualJjbGain = (int) round($jjbGain * $jjbMultiplier);
$user->jjb = ($user->jjb ?? 0) + $actualJjbGain;
}
// 设置冷却30秒内不再给奖励
Redis::setex($cooldownKey, 30, 1);
}
// 2. 使用 sysparam 表中可配置的等级-经验阈值计算等级
@@ -263,6 +275,9 @@ class ChatController extends Controller
'status' => 'success',
'data' => [
'exp_num' => $user->exp_num,
'jjb' => $user->jjb ?? 0,
'exp_gain' => $actualExpGain,
'jjb_gain' => $actualJjbGain,
'user_level' => $user->user_level,
'leveled_up' => $leveledUp,
'is_max_level' => $user->user_level >= $superLevel,
@@ -408,4 +423,31 @@ class ChatController extends Controller
'announcement' => $room->announcement,
]);
}
/**
* 解析奖励数值配置(支持固定值或范围格式)
*
* 支持格式:
* "5" 固定返回 5
* "1-10" 随机返回 1~10 之间的整数
* "0" 返回 0(关闭该奖励)
*
* @param string $value 配置值
* @return int 解析后的奖励数值
*/
private function parseRewardValue(string $value): int
{
$value = trim($value);
// 支持范围格式 "min-max"
if (str_contains($value, '-')) {
$parts = explode('-', $value, 2);
$min = max(0, (int) $parts[0]);
$max = max($min, (int) $parts[1]);
return rand($min, $max);
}
return max(0, (int) $value);
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* 文件功能:向 sysparam 表插入金币奖励和经验奖励相关配置项
*
* 新增 jjb_per_heartbeat每次心跳金币奖励配置
* 并更新 exp_per_heartbeat 的描述以说明支持范围格式。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* 插入金币奖励配置记录,更新经验描述
*/
public function up(): void
{
// 金币奖励配置默认0=关闭,可设为 "1" 或 "1-5" 范围格式)
if (! DB::table('sysparam')->where('alias', 'jjb_per_heartbeat')->exists()) {
DB::table('sysparam')->insert([
'alias' => 'jjb_per_heartbeat',
'body' => '1-3',
'guidetxt' => '💰 每次心跳金币奖励(支持固定值如"1",或范围如"1-5"设为0关闭',
]);
}
// 更新经验配置描述(说明支持范围格式)
DB::table('sysparam')
->where('alias', 'exp_per_heartbeat')
->update(['guidetxt' => '⚡ 每次心跳经验奖励(支持固定值如"1",或范围如"1-10"设为0关闭']);
}
/**
* 回滚:删除金币配置记录
*/
public function down(): void
{
DB::table('sysparam')->where('alias', 'jjb_per_heartbeat')->delete();
DB::table('sysparam')
->where('alias', 'exp_per_heartbeat')
->update(['guidetxt' => '每次心跳经验值']);
}
};

View File

@@ -830,11 +830,18 @@
let levelInfo = '';
if (d.is_max_level) {
levelInfo = `级别(${d.user_level});累积经验(${d.exp_num});已满级。`;
levelInfo = `级别(${d.user_level});经验(${d.exp_num});金币(${d.jjb}枚);已满级。`;
} else {
const requiredExp = d.user_level * d.user_level * 10;
const remaining = Math.max(0, requiredExp - d.exp_num);
levelInfo = `级别(${d.user_level});累积经验(${d.exp_num});还有(${remaining})升级。`;
levelInfo = `级别(${d.user_level});经验(${d.exp_num});金币(${d.jjb}枚)。`;
}
// 本次获得的奖励提示
let gainInfo = '';
if (d.exp_gain > 0 || d.jjb_gain > 0) {
const parts = [];
if (d.exp_gain > 0) parts.push(`经验+${d.exp_gain}`);
if (d.jjb_gain > 0) parts.push(`金币+${d.jjb_gain}`);
gainInfo = `(本次: ${parts.join(', ')})`;
}
if (data.data.leveled_up) {
@@ -850,7 +857,7 @@
const detailDiv = document.createElement('div');
detailDiv.className = 'msg-line';
detailDiv.innerHTML =
`<span style="color: green;">【${levelTitle}存点】您的最新情况:${levelInfo}</span><span class="msg-time">(${timeStr})</span>`;
`<span style="color: green;">【${levelTitle}存点】您的最新情况:${levelInfo} ${gainInfo}</span><span class="msg-time">(${timeStr})</span>`;
container2.appendChild(detailDiv);
if (autoScroll) container2.scrollTop = container2.scrollHeight;
} else {