feat: 更新 .env.example 文件,新增彩票业务配置与 Redis、邮件、队列等环境变量,优化开发环境设置
This commit is contained in:
166
app/Services/AuditLogger.php
Normal file
166
app/Services/AuditLogger.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\Player;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* 审计日志写入入口:落到表 audit_logs,仅 created_at。
|
||||
*
|
||||
* operator_type:admin / player / system(常量见此类)。
|
||||
*/
|
||||
final class AuditLogger
|
||||
{
|
||||
/** 后台账号,operator_id = admin_users.id */
|
||||
public const OPERATOR_ADMIN = 'admin';
|
||||
|
||||
/** 终端玩家,operator_id = players.id */
|
||||
public const OPERATOR_PLAYER = 'player';
|
||||
|
||||
/** 系统任务(定时任务、异步 Job 无自然人操作者时使用) */
|
||||
public const OPERATOR_SYSTEM = 'system';
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $beforeJson
|
||||
* @param array<string, mixed>|null $afterJson
|
||||
*/
|
||||
public static function record(
|
||||
string $operatorType,
|
||||
int $operatorId,
|
||||
?string $moduleCode = null,
|
||||
?string $actionCode = null,
|
||||
?string $targetType = null,
|
||||
?string $targetId = null,
|
||||
?array $beforeJson = null,
|
||||
?array $afterJson = null,
|
||||
?string $ip = null,
|
||||
?string $userAgent = null,
|
||||
): AuditLog {
|
||||
return AuditLog::query()->create([
|
||||
'operator_type' => $operatorType,
|
||||
'operator_id' => $operatorId,
|
||||
'module_code' => $moduleCode,
|
||||
'action_code' => $actionCode,
|
||||
'target_type' => $targetType,
|
||||
'target_id' => $targetId,
|
||||
'before_json' => $beforeJson,
|
||||
'after_json' => $afterJson,
|
||||
'ip' => $ip,
|
||||
'user_agent' => self::truncateUserAgent($userAgent),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前 HTTP 请求补全 IP、User-Agent(后台 / 玩家 API 调用方便)。
|
||||
*
|
||||
* @param array<string, mixed>|null $beforeJson
|
||||
* @param array<string, mixed>|null $afterJson
|
||||
*/
|
||||
public static function recordFromRequest(
|
||||
Request $request,
|
||||
string $operatorType,
|
||||
int $operatorId,
|
||||
?string $moduleCode = null,
|
||||
?string $actionCode = null,
|
||||
?string $targetType = null,
|
||||
?string $targetId = null,
|
||||
?array $beforeJson = null,
|
||||
?array $afterJson = null,
|
||||
): AuditLog {
|
||||
return self::record(
|
||||
$operatorType,
|
||||
$operatorId,
|
||||
$moduleCode,
|
||||
$actionCode,
|
||||
$targetType,
|
||||
$targetId,
|
||||
$beforeJson,
|
||||
$afterJson,
|
||||
$request->ip(),
|
||||
$request->userAgent(),
|
||||
);
|
||||
}
|
||||
|
||||
public static function recordForAdmin(AdminUser $admin, ?Request $request = null, ?string $moduleCode = null, ?string $actionCode = null, ?string $targetType = null, ?string $targetId = null, ?array $beforeJson = null, ?array $afterJson = null): AuditLog
|
||||
{
|
||||
if ($request !== null) {
|
||||
return self::recordFromRequest(
|
||||
$request,
|
||||
self::OPERATOR_ADMIN,
|
||||
(int) $admin->getKey(),
|
||||
$moduleCode,
|
||||
$actionCode,
|
||||
$targetType,
|
||||
$targetId,
|
||||
$beforeJson,
|
||||
$afterJson,
|
||||
);
|
||||
}
|
||||
|
||||
return self::record(
|
||||
self::OPERATOR_ADMIN,
|
||||
(int) $admin->getKey(),
|
||||
$moduleCode,
|
||||
$actionCode,
|
||||
$targetType,
|
||||
$targetId,
|
||||
$beforeJson,
|
||||
$afterJson,
|
||||
);
|
||||
}
|
||||
|
||||
public static function recordForPlayer(Player $player, ?Request $request = null, ?string $moduleCode = null, ?string $actionCode = null, ?string $targetType = null, ?string $targetId = null, ?array $beforeJson = null, ?array $afterJson = null): AuditLog
|
||||
{
|
||||
if ($request !== null) {
|
||||
return self::recordFromRequest(
|
||||
$request,
|
||||
self::OPERATOR_PLAYER,
|
||||
(int) $player->getKey(),
|
||||
$moduleCode,
|
||||
$actionCode,
|
||||
$targetType,
|
||||
$targetId,
|
||||
$beforeJson,
|
||||
$afterJson,
|
||||
);
|
||||
}
|
||||
|
||||
return self::record(
|
||||
self::OPERATOR_PLAYER,
|
||||
(int) $player->getKey(),
|
||||
$moduleCode,
|
||||
$actionCode,
|
||||
$targetType,
|
||||
$targetId,
|
||||
$beforeJson,
|
||||
$afterJson,
|
||||
);
|
||||
}
|
||||
|
||||
/** 定时任务或队列内无 Request 时使用。 */
|
||||
public static function recordForSystem(?string $moduleCode = null, ?string $actionCode = null, ?string $targetType = null, ?string $targetId = null, ?array $beforeJson = null, ?array $afterJson = null): AuditLog
|
||||
{
|
||||
return self::record(
|
||||
self::OPERATOR_SYSTEM,
|
||||
0,
|
||||
$moduleCode,
|
||||
$actionCode,
|
||||
$targetType,
|
||||
$targetId,
|
||||
$beforeJson,
|
||||
$afterJson,
|
||||
);
|
||||
}
|
||||
|
||||
private static function truncateUserAgent(?string $userAgent): ?string
|
||||
{
|
||||
if ($userAgent === null || $userAgent === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mb_substr($userAgent, 0, 255);
|
||||
}
|
||||
}
|
||||
94
app/Services/LotterySettings.php
Normal file
94
app/Services/LotterySettings.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\LotterySetting;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* 【配置中心】统一读入口:按 key 取运行期配置,默认带缓存。
|
||||
*
|
||||
* - 写入:控制台 / Seeder / 后续管理端可调 {@see put()};
|
||||
* - 【重要】库里不存在的 key **不写入缓存**,避免长期命中默认值导致后台新增配置不生效。
|
||||
*/
|
||||
final class LotterySettings
|
||||
{
|
||||
public static function cacheTtlSeconds(): int
|
||||
{
|
||||
return max(5, (int) config('lottery.settings.cache_ttl_seconds', 60));
|
||||
}
|
||||
|
||||
/** 取单个配置;若无行则返回 $default(不写缓存)。 */
|
||||
public static function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
$cacheKey = self::cacheKey($key);
|
||||
if (Cache::has($cacheKey)) {
|
||||
return Cache::get($cacheKey);
|
||||
}
|
||||
|
||||
/** @var LotterySetting|null $row */
|
||||
$row = LotterySetting::query()->where('setting_key', $key)->first();
|
||||
if ($row === null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$value = self::normalizeValue($row->value_json);
|
||||
Cache::put($cacheKey, $value, self::cacheTtlSeconds());
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量读取(逐项走 get;已存在的键会受益于缓存)。
|
||||
*
|
||||
* @param array<string, mixed> $keysToDefault key => default
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function many(array $keysToDefault): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($keysToDefault as $key => $def) {
|
||||
$out[$key] = self::get($key, $def);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** 删单 key 缓存;写入 lottery_settings 后务必调用(或运维 `php artisan cache:clear`) */
|
||||
public static function forgetKey(string $key): void
|
||||
{
|
||||
Cache::forget(self::cacheKey($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入或更新一行配置并刷新该 key 的缓存。(Seeder / 后续管理端共用)
|
||||
*/
|
||||
public static function put(
|
||||
string $key,
|
||||
mixed $value,
|
||||
string $groupName = 'general',
|
||||
?string $descriptionZh = null,
|
||||
): void {
|
||||
LotterySetting::query()->updateOrCreate(
|
||||
['setting_key' => $key],
|
||||
[
|
||||
'value_json' => $value,
|
||||
'group_name' => $groupName,
|
||||
'description_zh' => $descriptionZh,
|
||||
],
|
||||
);
|
||||
self::forgetKey($key);
|
||||
// 写入后立即预热缓存,下一次 get 可走 Cache::has
|
||||
Cache::put(self::cacheKey($key), self::normalizeValue($value), self::cacheTtlSeconds());
|
||||
}
|
||||
|
||||
public static function cacheKey(string $key): string
|
||||
{
|
||||
return 'lottery_settings:'.$key;
|
||||
}
|
||||
|
||||
private static function normalizeValue(mixed $value): mixed
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user