Files
2026-05-30 17:38:49 +08:00

304 lines
8.7 KiB
PHP

<?php
namespace Modules\SystemTool\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Modules\SystemTool\Enum\SiteConfigType;
use Modules\SystemTool\Models\SysConfigGroupModel;
use Modules\SystemTool\Models\SysConfigItemsModel;
/**
* 系统设置服务
*/
class SysConfigService
{
/**
* 缓存过期时间(秒),默认30天
*/
private const CACHE_TTL = 60 * 60 * 24 * 30;
/**
* 刷新系统设置缓存
* @return bool
*/
public static function refreshConfig(): bool
{
try {
$groups = SysConfigGroupModel::with('settings')->get();
$settings = [];
foreach ($groups as $group) {
$settings[$group->key] = [];
foreach ($group->settings as $setting) {
// 根据类型自动转换值
$settings[$group->key][$setting->key] = self::castValue($setting->values, $setting->type);
}
}
// 使用带过期时间的缓存,而非永久缓存
Cache::put(self::getCacheKey(), $settings, self::CACHE_TTL);
Log::info('系统配置缓存已刷新', ['settings_count' => count($settings)]);
return true;
} catch (\Throwable $e) {
Log::error('刷新系统配置缓存失败', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return false;
}
}
/**
* 获取设置
* @param string|null $name 设置名称,格式:'group.key' 或 'group',为null时返回所有配置
* @param mixed $default 默认值
* @return mixed
*/
public static function getConfig(?string $name = null, mixed $default = null): mixed
{
$settings = Cache::get(self::getCacheKey());
// 缓存不存在时重新加载
if (empty($settings)) {
self::refreshConfig();
$settings = Cache::get(self::getCacheKey(), []);
}
// 返回所有配置
if (is_null($name)) {
return $settings;
}
// 解析配置路径
$keys = explode('.', $name);
// 支持多级获取:group.key 或 group
if (count($keys) === 2) {
return $settings[$keys[0]][$keys[1]] ?? $default;
} elseif (count($keys) === 1) {
return $settings[$keys[0]] ?? $default;
}
return $default;
}
/**
* 设置配置项(更新配置并刷新缓存)
* @param int $id 设置ID
* @param mixed $value 设置值
* @return bool
*/
public static function setConfig(int $id, mixed $value): bool
{
try {
// 查找设置组和设置项
$setting = SysConfigItemsModel::find($id);
if (!$setting) {
throw new \RuntimeException("设置项不存在: {$id}");
}
// 更新值
$setting->values = is_array($value) || is_object($value) ? json_encode($value) : (string)$value;
$setting->save();
return true;
} catch (\Throwable $e) {
Log::error('设置配置项失败', [
'id' => $id,
'value' => $value,
'error' => $e->getMessage()
]);
return false;
}
}
/**
* 批量保存设置项
*
* 先验证所有 ID 是否存在,再在事务内统一更新。
* 任一保存失败则全部回滚。
*
* @param array $settings [['id' => int, 'values' => mixed], ...]
* @return array{success: bool, errors: array}
*/
public static function batchSetConfig(array $settings): array
{
$errors = [];
$ids = array_column($settings, 'id');
$items = SysConfigItemsModel::whereIn('id', $ids)->get()->keyBy('id');
// 先验证所有 ID 是否存在,收集缺失项信息
foreach ($settings as $item) {
$id = (int)$item['id'];
if (!$items->has($id)) {
$errors[] = [
'key' => "unknown_{$id}",
'title' => "未知设置(ID: {$id})",
];
}
}
if (!empty($errors)) {
return ['success' => false, 'errors' => $errors];
}
// 事务内执行批量更新
try {
DB::transaction(function () use ($settings, $items) {
foreach ($settings as $item) {
$id = (int)$item['id'];
$model = $items->get($id);
$value = $item['value'];
$model->values = is_array($value) || is_object($value) ? json_encode($value) : (string)$value;
$model->save();
}
});
return ['success' => true, 'errors' => []];
} catch (\Throwable $e) {
Log::error('批量保存设置项事务失败', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
// 事务已回滚,收集所有项的报告信息
foreach ($settings as $item) {
$id = (int)$item['id'];
$model = $items->get($id);
$errors[] = [
'key' => $model ? $model->key : "unknown_{$id}",
'title' => $model ? $model->title : "未知设置(ID: {$id})",
];
}
return ['success' => false, 'errors' => $errors];
}
}
/**
* 检查配置项是否存在
* @param string $name
* @return bool
*/
public static function has(string $name): bool
{
$keys = explode('.', $name);
$settings = Cache::get(self::getCacheKey(), []);
if (count($keys) === 2) {
return isset($settings[$keys[0]][$keys[1]]);
} elseif (count($keys) === 1) {
return isset($settings[$keys[0]]);
}
return false;
}
/**
* 清除设置缓存
* @return bool
*/
public static function clearCache(): bool
{
return Cache::forget(self::getCacheKey());
}
/**
* 根据类型转换值
* @param mixed $value 原始值
* @param string $type 类型(对应前端表单组件类型)
* @return mixed
*/
private static function castValue(mixed $value, string $type): mixed
{
// 空值直接返回
if (is_null($value) || $value === '') {
return $value;
}
// 尝试转换为枚举类型
$settingType = SiteConfigType::fromString($type);
// 如果无法识别类型,返回字符串
if (is_null($settingType)) {
return (string)$value;
}
// 使用枚举判断进行类型转换
if ($settingType->isNumeric()) {
return is_numeric($value)
? (str_contains((string)$value, '.') ? (float)$value : (int)$value)
: $value;
}
if ($settingType->isBoolean()) {
return self::toBool($value);
}
if ($settingType->isArray()) {
return self::toArrayValue($value);
}
return (string)$value;
}
/**
* 转换为布尔值
* @param mixed $value
* @return bool
*/
private static function toBool(mixed $value): bool
{
if (is_bool($value)) {
return $value;
}
if (is_numeric($value)) {
return (bool)(int)$value;
}
if (is_string($value)) {
$value = strtolower(trim($value));
return in_array($value, ['true', '1', 'yes', 'on'], true);
}
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
/**
* 转换为数组值(兼容 Checkbox、JSON、Array 类型)
* @param mixed $value
* @return array|bool
*/
private static function toArrayValue(mixed $value): array|bool
{
// 如果是 JSON 字符串,尝试解析为数组
if (is_string($value)) {
$decoded = json_decode($value, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
return $decoded;
}
// Checkbox 单个值转为布尔
return self::toBool($value);
}
// 已经是数组直接返回
if (is_array($value)) {
return $value;
}
// 其他情况转为布尔(Checkbox 单选)
return self::toBool($value);
}
/**
* 获取缓存KEY
* @return string
*/
private static function getCacheKey(): string
{
return config('site_config.cache_key', 'site_config');
}
}