Files
webman-buildadmin/app/common/service/GameHotDataRedis.php
2026-04-20 10:31:14 +08:00

280 lines
7.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\common\service;
use app\common\model\User;
use support\Redis;
use support\think\Db;
use Throwable;
/**
* 热点数据 Redis 缓存game_config、game_record、user 全行 JSON。
* Redis 不可用时自动回退为直连数据库。
*/
final class GameHotDataRedis
{
private const KEY_GC = 'dfw:v1:gc:';
private const KEY_GR_ID = 'dfw:v1:gr:id:';
private const KEY_GR_ACTIVE = 'dfw:v1:gr:active';
private const KEY_GR_LATEST = 'dfw:v1:gr:latest';
private const KEY_USER = 'dfw:v1:user:';
public static function enabled(): bool
{
return config('game_hot_cache.enabled', true) === true;
}
/**
* @return array<string, mixed>|null
*/
public static function gameConfigRow(string $configKey): ?array
{
if ($configKey === '') {
return null;
}
if (self::enabled()) {
$cached = self::redisGet(self::KEY_GC . $configKey);
if ($cached !== null && $cached !== '') {
$decoded = json_decode($cached, true);
if (is_array($decoded)) {
return $decoded;
}
}
}
$row = Db::name('game_config')->where('config_key', $configKey)->find();
if (!$row) {
return null;
}
if (self::enabled()) {
$ttl = self::intConfig('ttl_game_config', 86400);
self::redisSetEx(self::KEY_GC . $configKey, $ttl, json_encode($row, JSON_UNESCAPED_UNICODE));
}
return $row;
}
public static function gameConfigForget(string $configKey): void
{
if ($configKey === '') {
return;
}
self::redisDel(self::KEY_GC . $configKey);
}
/**
* @return array<string, mixed>|null
*/
public static function gameRecordById(int $id): ?array
{
if ($id <= 0) {
return null;
}
$key = self::KEY_GR_ID . $id;
if (self::enabled()) {
$cached = self::redisGet($key);
if ($cached !== null && $cached !== '') {
$decoded = json_decode($cached, true);
if (is_array($decoded)) {
return $decoded;
}
}
}
$row = Db::name('game_record')->where('id', $id)->find();
if (!$row) {
return null;
}
if (self::enabled()) {
$ttl = self::intConfig('ttl_game_record', 60);
self::redisSetEx($key, $ttl, json_encode($row, JSON_UNESCAPED_UNICODE));
}
return $row;
}
/**
* 当前未完全结束的对局status ∈ 0,1,2,3 中 id 最大的一条)
*
* @return array<string, mixed>|null
*/
public static function gameRecordActive(): ?array
{
if (self::enabled()) {
$cached = self::redisGet(self::KEY_GR_ACTIVE);
if ($cached !== null && $cached !== '') {
$decoded = json_decode($cached, true);
if (is_array($decoded)) {
return $decoded;
}
}
}
$row = Db::name('game_record')->whereIn('status', [0, 1, 2, 3])->order('id', 'desc')->find();
if (!$row) {
return null;
}
if (self::enabled()) {
$ttl = self::intConfig('ttl_game_record', 60);
self::redisSetEx(self::KEY_GR_ACTIVE, $ttl, json_encode($row, JSON_UNESCAPED_UNICODE));
}
return $row;
}
/**
* 最新一条对局(含已结束),与 GameRecord::order('id','desc')->find() 一致
*
* @return array<string, mixed>|null
*/
public static function gameRecordLatest(): ?array
{
if (self::enabled()) {
$cached = self::redisGet(self::KEY_GR_LATEST);
if ($cached !== null && $cached !== '') {
$decoded = json_decode($cached, true);
if (is_array($decoded)) {
return $decoded;
}
}
}
$row = Db::name('game_record')->order('id', 'desc')->find();
if (!$row) {
return null;
}
if (self::enabled()) {
$ttl = self::intConfig('ttl_game_record', 60);
self::redisSetEx(self::KEY_GR_LATEST, $ttl, json_encode($row, JSON_UNESCAPED_UNICODE));
}
return $row;
}
/**
* 任意 game_record 变更后调用:删除活跃局、最新局及指定 id 缓存
*/
public static function gameRecordForget(?int $id = null): void
{
$keys = [self::KEY_GR_ACTIVE, self::KEY_GR_LATEST];
if ($id !== null && $id > 0) {
$keys[] = self::KEY_GR_ID . $id;
}
self::redisDel(...$keys);
}
/**
* @return array<string, mixed>|null
*/
public static function userRow(int $userId): ?array
{
if ($userId <= 0) {
return null;
}
$key = self::KEY_USER . $userId;
if (self::enabled()) {
$cached = self::redisGet($key);
if ($cached !== null && $cached !== '') {
$decoded = json_decode($cached, true);
if (is_array($decoded)) {
return $decoded;
}
}
}
$row = Db::name('user')->where('id', $userId)->find();
if (!$row) {
return null;
}
if (self::enabled()) {
$ttl = self::intConfig('ttl_user', 90);
self::redisSetEx($key, $ttl, json_encode($row, JSON_UNESCAPED_UNICODE));
}
return $row;
}
public static function userRemember(User $user): void
{
if (!self::enabled()) {
return;
}
$id = $user->getAttr('id');
$uid = filter_var($id, FILTER_VALIDATE_INT);
if ($uid === false || $uid <= 0) {
return;
}
$ttl = self::intConfig('ttl_user', 90);
self::redisSetEx(self::KEY_USER . $uid, $ttl, json_encode($user->toArray(), JSON_UNESCAPED_UNICODE));
}
public static function userForget(int $userId): void
{
if ($userId <= 0) {
return;
}
self::redisDel(self::KEY_USER . $userId);
}
/**
* 用缓存行构造已存在库的 User供 Auth 等高频读)
*/
public static function userModelFromCacheOrDb(int $userId): ?User
{
$row = self::userRow($userId);
if ($row === null) {
return null;
}
return (new User())->newInstance($row);
}
private static function intConfig(string $name, int $default): int
{
$v = config('game_hot_cache.' . $name, $default);
if (is_int($v)) {
return $v > 0 ? $v : $default;
}
if (is_numeric($v)) {
$n = filter_var($v, FILTER_VALIDATE_INT);
if ($n === false) {
return $default;
}
return $n > 0 ? $n : $default;
}
return $default;
}
private static function redisGet(string $key): ?string
{
try {
$v = Redis::get($key);
if ($v === false || $v === null) {
return null;
}
if (!is_string($v)) {
return null;
}
return $v;
} catch (Throwable) {
return null;
}
}
private static function redisSetEx(string $key, int $ttl, string $value): void
{
try {
Redis::setEx($key, $ttl, $value);
} catch (Throwable) {
}
}
private static function redisDel(string ...$keys): void
{
if ($keys === []) {
return;
}
try {
Redis::del(...$keys);
} catch (Throwable) {
}
}
}