first commit

This commit is contained in:
liu
2026-07-13 15:23:29 +08:00
commit 50885a98c8
473 changed files with 33772 additions and 0 deletions
@@ -0,0 +1,346 @@
<?php
namespace Modules\SystemTool\Base;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
use Modules\SystemTool\Attributes\Setting;
use Modules\SystemTool\Enum\ESettingType;
use ReflectionClass;
/**
* 设置定义基类
*/
abstract class SettingsDefinition
{
/**
* 聚合缓存键名 — 全局中间件 LoadAppSettingsMiddleware 使用
* 所有设置一次性缓存在此键中,set() 写入时自动清除。
*/
const AGGREGATE_CACHE_KEY = 'app-settings-all';
/**
* 解析后的定义缓存(类名 → 定义数组)
*
* @var array<string, array<string, array{config: string, type: string, description: string|null}>>
*/
protected static array $definitions = [];
/**
* 从 #[Setting] Attribute 解析设置定义
*
* @return array<string, array{config: string, type: string, description: string|null}>
*/
public static function getDefinition(): array
{
$class = static::class;
if (! isset(self::$definitions[$class])) {
$reflection = new ReflectionClass($class);
$attributes = $reflection->getAttributes(Setting::class);
$definition = [];
foreach ($attributes as $attribute) {
/** @var Setting $instance */
$instance = $attribute->newInstance();
$definition[$instance->config] = [
'config' => $instance->config,
'type' => $instance->type->value,
'description' => $instance->description,
];
}
self::$definitions[$class] = $definition;
}
return self::$definitions[$class];
}
/**
* 判断某个 key 是否在本类的定义中
*/
public static function hasDefinitionKey(string $key): bool
{
return array_key_exists($key, static::getDefinition());
}
/**
* 获取数据库表名
*/
public static function getTableName(): string
{
return config('app_settings.table', 'sys_app_settings');
}
/**
* 将本类的所有配置项定义同步到 应用配置 表
*
* - 新 key 插入,值来自当前 config() 的默认值
* - 已有 key 只更新 description,不覆盖值
* - 部署时在 migration 中调用
*/
public static function init(): void
{
$definition = static::getDefinition();
$table = static::getTableName();
foreach ($definition as $key => $setting) {
$exists = DB::table($table)->where('key', $key)->exists();
if (! $exists) {
$value = static::getConfigValue($key);
static::set($key, $value);
} else {
DB::table($table)
->where('key', $key)
->update([
'description' => $setting['description'] ?? null,
'updated_at' => now(),
]);
}
}
}
/**
* 读取配置值
*
* @throws InvalidArgumentException 当 key 未定义时
*/
public static function get(string $key, mixed $default = null): mixed
{
if (! static::hasDefinitionKey($key)) {
throw new InvalidArgumentException("Setting key '{$key}' is not defined in " . static::class);
}
return static::getCacheValue($key, function () use ($key, $default) {
return static::getDBValue($key, function () use ($key, $default) {
return static::setCacheValue(
key: $key,
value: static::getConfigValue($key, $default),
);
});
});
}
/**
* 写入配置值 → DB + Cache
*
* @throws InvalidArgumentException 当 key 未定义或类型不匹配时
*/
public static function set(string $key, mixed $value): void
{
if (! static::hasDefinitionKey($key)) {
throw new InvalidArgumentException("Setting key '{$key}' is not defined in " . static::class);
}
static::setDBValue($key, $value);
static::setCacheValue($key, $value);
// 当前请求立即生效(中间件在下一个请求才会重新加载)
config([$key => $value]);
// 清除聚合缓存,使下次请求通过中间件重新从 DB 加载
Cache::forget(self::AGGREGATE_CACHE_KEY);
}
/**
* 从 Laravel config 读取(最终 fallback
*/
public static function getConfigValue(string $key, mixed $default = null): mixed
{
return config($key, $default);
}
/**
* 从缓存读取
*
* @param mixed $default 默认值或闭包 (fn() => mixed)
*/
public static function getCacheValue(string $key, mixed $default = null): mixed
{
if ($default instanceof \Closure) {
return Cache::rememberForever('app-setting-' . $key, $default);
}
return Cache::get('app-setting-' . $key, $default);
}
/**
* 写入缓存
*/
public static function setCacheValue(string $key, mixed $value, ?int $ttl = null): mixed
{
if ($ttl !== null) {
Cache::put('app-setting-' . $key, $value, $ttl);
} else {
Cache::forever('app-setting-' . $key, $value);
}
return $value;
}
/**
* 将本类所有配置项从 DB 加载到 Laravel config() 运行时
*
* 调用后 config('filesystems.default') 等可直接返回 DB 值。
* 在 getConfig() 批量读取场景下避免逐个 get() 的开销。
*/
public static function reloadIntoConfig(): void
{
$table = static::getTableName();
$rows = DB::table($table)->whereIn('key', array_keys(static::getDefinition()))->get();
foreach ($rows as $row) {
$value = match ((int) $row->type) {
ESettingType::String->value => $row->s,
ESettingType::Bool->value => is_null($row->n) ? null : (bool) $row->n,
ESettingType::Number->value => is_null($row->n) ? null : (int) $row->n,
ESettingType::Array->value => is_null($row->e) ? null : json_decode($row->e, true),
ESettingType::Object->value => is_null($row->e) ? null : unserialize(base64_decode($row->e)),
ESettingType::EncryptedString->value => is_null($row->e) ? null : \Illuminate\Support\Facades\Crypt::decrypt(base64_decode($row->e)),
default => $row->s ?? null,
};
config([$row->key => $value]);
}
}
/**
* 清除某个 key 的缓存
*/
public static function forgetCache(string $key): void
{
Cache::forget('app-setting-' . $key);
// 同时清除聚合缓存,保持一致性
Cache::forget(self::AGGREGATE_CACHE_KEY);
}
/**
* 从数据库读取(按类型自动转换)
*
* @param mixed $default 默认值或闭包
*/
public static function getDBValue(string $key, mixed $default = null): mixed
{
$rec = DB::table(static::getTableName())->where('key', $key)->first();
if (! $rec) {
return $default instanceof \Closure ? $default() : value($default);
}
return match ((int) $rec->type) {
ESettingType::String->value => $rec->s,
ESettingType::Bool->value => is_null($rec->n) ? null : (bool) $rec->n,
ESettingType::Number->value => is_null($rec->n) ? null : (int) $rec->n,
ESettingType::Array->value => is_null($rec->e) ? null : json_decode($rec->e, true),
ESettingType::Object->value => is_null($rec->e) ? null : unserialize(base64_decode($rec->e)),
ESettingType::EncryptedString->value => is_null($rec->e) ? null : Crypt::decrypt(base64_decode($rec->e)),
default => $rec->s ?? $rec->value ?? null,
};
}
/**
* 写入数据库(按类型选择列)
*
* @throws InvalidArgumentException
*/
public static function setDBValue(string $key, mixed $value): void
{
$definition = static::getDefinition();
if (! isset($definition[$key])) {
throw new InvalidArgumentException("Setting key '{$key}' is not defined in " . static::class);
}
$type = $definition[$key]['type'];
$description = $definition[$key]['description'] ?? null;
$table = static::getTableName();
$data = [
'key' => $key,
'type' => $type,
'description' => $description,
'updated_at' => now(),
];
switch ((int) $type) {
case ESettingType::String->value:
if (! is_string($value) && ! is_null($value)) {
throw new InvalidArgumentException("Value for '{$key}' must be a string.");
}
$data['s'] = $value;
$data['n'] = null;
$data['e'] = null;
break;
case ESettingType::Bool->value:
if (! is_bool($value) && ! is_null($value)) {
throw new InvalidArgumentException("Value for '{$key}' must be a boolean.");
}
$data['n'] = is_null($value) ? null : (int) $value;
$data['s'] = null;
$data['e'] = null;
break;
case ESettingType::Number->value:
if (! is_numeric($value) && ! is_null($value)) {
throw new InvalidArgumentException("Value for '{$key}' must be a number.");
}
$data['n'] = $value;
$data['s'] = null;
$data['e'] = null;
break;
case ESettingType::Array->value:
if (! is_array($value) && ! is_null($value)) {
throw new InvalidArgumentException("Value for '{$key}' must be an array.");
}
$data['e'] = is_null($value) ? null : json_encode($value, JSON_UNESCAPED_UNICODE);
$data['s'] = null;
$data['n'] = null;
break;
case ESettingType::Object->value:
if (! is_object($value) && ! is_null($value)) {
throw new InvalidArgumentException("Value for '{$key}' must be an object.");
}
$data['e'] = is_null($value) ? null : base64_encode(serialize($value));
$data['s'] = null;
$data['n'] = null;
break;
case ESettingType::EncryptedString->value:
if (! is_string($value) && ! is_null($value)) {
throw new InvalidArgumentException("Value for '{$key}' must be a string.");
}
$data['e'] = is_null($value) ? null : base64_encode(Crypt::encrypt($value));
$data['s'] = null;
$data['n'] = null;
break;
default:
throw new InvalidArgumentException("Unknown setting type '{$type}' for key '{$key}'.");
}
$exists = DB::table($table)->where('key', $key)->exists();
if ($exists) {
DB::table($table)->where('key', $key)->update($data);
} else {
$data['created_at'] = now();
DB::table($table)->insert($data);
}
}
/**
* 从数据库删除某个配置项
*/
public static function deleteDBValue(string $key): void
{
DB::table(static::getTableName())->where('key', $key)->delete();
}
}