功能:存点时自动同步在职用户勤务日志
- heartbeat 手动存点:调用 tickDutyLog() - AutoSaveExp 自动存点:调用 tickDutyLog() - 逻辑:今日已有开放日志则刷新 duration_seconds,无则新建(login_at 取 in_time 进房时间) - 修复:TIMESTAMPDIFF 结果用 GREATEST(0, ...) 防 unsigned 溢出 - 修复:database.php MySQL 连接加 timezone=+08:00,与 PHP Asia/Shanghai 时区对齐
This commit is contained in:
@@ -9,22 +9,24 @@
|
|||||||
* 3. 在聊天室内推送"系统为你自动存点"提示
|
* 3. 在聊天室内推送"系统为你自动存点"提示
|
||||||
* 4. 若用户等级提升,向全频道广播恭喜消息
|
* 4. 若用户等级提升,向全频道广播恭喜消息
|
||||||
*
|
*
|
||||||
* @package App\Console\Commands
|
|
||||||
* @author ChatRoom Laravel
|
* @author ChatRoom Laravel
|
||||||
|
*
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Events\MessageSent;
|
|
||||||
use App\Enums\CurrencySource;
|
use App\Enums\CurrencySource;
|
||||||
|
use App\Events\MessageSent;
|
||||||
use App\Jobs\SaveMessageJob;
|
use App\Jobs\SaveMessageJob;
|
||||||
|
use App\Models\PositionDutyLog;
|
||||||
use App\Models\Sysparam;
|
use App\Models\Sysparam;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\ChatStateService;
|
use App\Services\ChatStateService;
|
||||||
use App\Services\UserCurrencyService;
|
use App\Services\UserCurrencyService;
|
||||||
use App\Services\VipService;
|
use App\Services\VipService;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
class AutoSaveExp extends Command
|
class AutoSaveExp extends Command
|
||||||
@@ -71,6 +73,7 @@ class AutoSaveExp extends Command
|
|||||||
|
|
||||||
if (empty($roomMap)) {
|
if (empty($roomMap)) {
|
||||||
$this->info('当前没有在线用户,跳过存点。');
|
$this->info('当前没有在线用户,跳过存点。');
|
||||||
|
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +206,7 @@ class AutoSaveExp extends Command
|
|||||||
$gainParts[] = "金币+{$actualJjbGain}";
|
$gainParts[] = "金币+{$actualJjbGain}";
|
||||||
}
|
}
|
||||||
$jjbDisplay = $user->jjb ?? 0;
|
$jjbDisplay = $user->jjb ?? 0;
|
||||||
$gainStr = ! empty($gainParts) ? ' 本次获得:' . implode(',', $gainParts) : '';
|
$gainStr = ! empty($gainParts) ? ' 本次获得:'.implode(',', $gainParts) : '';
|
||||||
|
|
||||||
// 格式:⏰ 自动存点 · LV.100 · 经验 10,468 · 金币 8,345 · 已满级 · 本次获得:经验+1,金币+3
|
// 格式:⏰ 自动存点 · LV.100 · 经验 10,468 · 金币 8,345 · 已满级 · 本次获得:经验+1,金币+3
|
||||||
$statusTag = $user->user_level >= $superLevel ? ' · 已满级 ✓' : '';
|
$statusTag = $user->user_level >= $superLevel ? ' · 已满级 ✓' : '';
|
||||||
@@ -223,6 +226,9 @@ class AutoSaveExp extends Command
|
|||||||
|
|
||||||
$this->chatState->pushMessage($roomId, $noticeMsg);
|
$this->chatState->pushMessage($roomId, $noticeMsg);
|
||||||
broadcast(new MessageSent($roomId, $noticeMsg));
|
broadcast(new MessageSent($roomId, $noticeMsg));
|
||||||
|
|
||||||
|
// 6. 同步更新在职用户的勤务时长
|
||||||
|
$this->tickDutyLog($user, $roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -233,16 +239,63 @@ class AutoSaveExp extends Command
|
|||||||
* - 随机范围:如 "3-10" → 返回 [3, 10] 之间的随机整数
|
* - 随机范围:如 "3-10" → 返回 [3, 10] 之间的随机整数
|
||||||
*
|
*
|
||||||
* @param string $raw 原始配置字符串
|
* @param string $raw 原始配置字符串
|
||||||
* @return int
|
|
||||||
*/
|
*/
|
||||||
private function parseRewardValue(string $raw): int
|
private function parseRewardValue(string $raw): int
|
||||||
{
|
{
|
||||||
$raw = trim($raw);
|
$raw = trim($raw);
|
||||||
if (str_contains($raw, '-')) {
|
if (str_contains($raw, '-')) {
|
||||||
[$min, $max] = explode('-', $raw, 2);
|
[$min, $max] = explode('-', $raw, 2);
|
||||||
|
|
||||||
return rand((int) $min, (int) $max);
|
return rand((int) $min, (int) $max);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int) $raw;
|
return (int) $raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动存点时同步更新或创建在职用户的勤务日志。
|
||||||
|
*
|
||||||
|
* 逻辑同 ChatController::tickDutyLog:
|
||||||
|
* 1. 无在职职务 → 跳过
|
||||||
|
* 2. 今日已有开放日志 → 刷新 duration_seconds
|
||||||
|
* 3. 今日无日志 → 新建,login_at 取 user->in_time(进房时间)
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $user 已 refresh 的用户实例
|
||||||
|
* @param int $roomId 所在房间 ID
|
||||||
|
*/
|
||||||
|
private function tickDutyLog(User $user, int $roomId): void
|
||||||
|
{
|
||||||
|
$activeUP = $user->activePosition;
|
||||||
|
if (! $activeUP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$openLog = PositionDutyLog::query()
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->whereNull('logout_at')
|
||||||
|
->whereDate('login_at', today())
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($openLog) {
|
||||||
|
// 绕过模型 cast(integer),使用 DB::table 直接执行 SQL 表达式
|
||||||
|
DB::table('position_duty_logs')
|
||||||
|
->where('id', $openLog->id)
|
||||||
|
->update(['duration_seconds' => DB::raw('GREATEST(0, TIMESTAMPDIFF(SECOND, login_at, NOW()))')]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 今日无日志,新建:取进房时间为 login_at(防止时长丢失)
|
||||||
|
$loginAt = $user->in_time && $user->in_time->isToday()
|
||||||
|
? $user->in_time
|
||||||
|
: now();
|
||||||
|
|
||||||
|
PositionDutyLog::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'user_position_id' => $activeUP->id,
|
||||||
|
'login_at' => $loginAt,
|
||||||
|
'ip_address' => '0.0.0.0', // 定时任务无 HTTP 请求,占位符
|
||||||
|
'room_id' => $roomId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,6 +340,9 @@ class ChatController extends Controller
|
|||||||
|
|
||||||
$user->save(); // 存点入库
|
$user->save(); // 存点入库
|
||||||
|
|
||||||
|
// 手动心跳存点:同步更新在职用户的勤务时长
|
||||||
|
$this->tickDutyLog($user, $id);
|
||||||
|
|
||||||
// 3. 将新的等级反馈给当前用户的在线名单上
|
// 3. 将新的等级反馈给当前用户的在线名单上
|
||||||
// 确保刚刚升级后别人查看到的也是最准确等级
|
// 确保刚刚升级后别人查看到的也是最准确等级
|
||||||
$activePosition = $user->activePosition;
|
$activePosition = $user->activePosition;
|
||||||
@@ -820,7 +823,57 @@ class ChatController extends Controller
|
|||||||
->whereNull('logout_at')
|
->whereNull('logout_at')
|
||||||
->update([
|
->update([
|
||||||
'logout_at' => now(),
|
'logout_at' => now(),
|
||||||
'duration_seconds' => DB::raw('TIMESTAMPDIFF(SECOND, login_at, NOW())'),
|
'duration_seconds' => DB::raw('GREATEST(0, TIMESTAMPDIFF(SECOND, login_at, NOW()))'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存点时同步更新或创建在职用户的勤务日志。
|
||||||
|
*
|
||||||
|
* 逻辑:
|
||||||
|
* 1. 用户无在职职务 → 跳过
|
||||||
|
* 2. 今日已有开放日志(无 logout_at)→ 刷新 duration_seconds(实时时长)
|
||||||
|
* 3. 今日无任何日志 → 新建,login_at 取 user->in_time(进房时间),保证时长不丢失
|
||||||
|
*
|
||||||
|
* @param \App\Models\User $user 当前用户(必须已 fresh/refresh)
|
||||||
|
* @param int $roomId 所在房间 ID
|
||||||
|
*/
|
||||||
|
private function tickDutyLog(User $user, int $roomId): void
|
||||||
|
{
|
||||||
|
// 无在职职务,无需记录
|
||||||
|
$activeUP = $user->activePosition;
|
||||||
|
if (! $activeUP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 今日是否已有开放日志
|
||||||
|
$openLog = PositionDutyLog::query()
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->whereNull('logout_at')
|
||||||
|
->whereDate('login_at', today())
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($openLog) {
|
||||||
|
// 刷新实时在线时长
|
||||||
|
// 绕过模型 cast(integer),使用 DB::table 直接执行 SQL 表达式
|
||||||
|
DB::table('position_duty_logs')
|
||||||
|
->where('id', $openLog->id)
|
||||||
|
->update(['duration_seconds' => DB::raw('GREATEST(0, TIMESTAMPDIFF(SECOND, login_at, NOW()))')]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 今日无日志 → 新建,login_at 优先用进房时间
|
||||||
|
$loginAt = $user->in_time && $user->in_time->isToday()
|
||||||
|
? $user->in_time
|
||||||
|
: now();
|
||||||
|
|
||||||
|
PositionDutyLog::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'user_position_id' => $activeUP->id,
|
||||||
|
'login_at' => $loginAt,
|
||||||
|
'ip_address' => request()->ip(),
|
||||||
|
'room_id' => $roomId,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ return [
|
|||||||
'prefix_indexes' => true,
|
'prefix_indexes' => true,
|
||||||
'strict' => true,
|
'strict' => true,
|
||||||
'engine' => null,
|
'engine' => null,
|
||||||
|
'timezone' => '+08:00', // 与 PHP Asia/Shanghai 时区对齐,NOW() 返回北京时间
|
||||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
||||||
]) : [],
|
]) : [],
|
||||||
|
|||||||
Reference in New Issue
Block a user