mirror of
https://gitee.com/zoujingli/ThinkAdmin.git
synced 2026-06-07 20:48:09 +08:00
432 lines
16 KiB
PHP
432 lines
16 KiB
PHP
<?php
|
||
|
||
// +----------------------------------------------------------------------
|
||
// | Library for ThinkAdmin
|
||
// +----------------------------------------------------------------------
|
||
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
|
||
// +----------------------------------------------------------------------
|
||
// | 官方网站: https://thinkadmin.top
|
||
// +----------------------------------------------------------------------
|
||
// | 开源协议 ( https://mit-license.org )
|
||
// | 免费声明 ( https://thinkadmin.top/disclaimer )
|
||
// +----------------------------------------------------------------------
|
||
// | gitee 仓库地址 :https://gitee.com/zoujingli/ThinkLibrary
|
||
// | github 仓库地址 :https://github.com/zoujingli/ThinkLibrary
|
||
// +----------------------------------------------------------------------
|
||
|
||
declare (strict_types=1);
|
||
|
||
namespace think\admin\service;
|
||
|
||
use think\admin\Exception;
|
||
use think\admin\extend\FaviconExtend;
|
||
use think\admin\Helper;
|
||
use think\admin\Library;
|
||
use think\admin\model\SystemConfig;
|
||
use think\admin\model\SystemData;
|
||
use think\admin\model\SystemOplog;
|
||
use think\admin\Service;
|
||
use think\admin\Storage;
|
||
use think\admin\storage\LocalStorage;
|
||
use think\App;
|
||
use think\db\Query;
|
||
use think\Model;
|
||
|
||
/**
|
||
* 系统参数管理服务
|
||
* @class SystemService
|
||
* @package think\admin\service
|
||
*
|
||
* @method static bool isDebug() 调式模式运行
|
||
* @method static bool isOnline() 产品模式运行
|
||
*
|
||
* 运行环境配置
|
||
* @method static array getRuntime(?string $name = null, array $default = []) 获取动态配置
|
||
* @method static bool setRuntime(?string $mode = null, ?array $appmap = [], ?array $domain = []) 设置动态配置
|
||
* @method static bool bindRuntime(array $data = []) 绑定动态配置
|
||
*
|
||
* 运行缓存管理
|
||
* @method static bool pushRuntime() 压缩发布项目
|
||
* @method static bool clearRuntime() 清理运行缓存
|
||
* @method static bool checkRunMode(string $type = 'dev') 判断运行环境
|
||
*
|
||
* 初始化启动系统
|
||
* @method static mixed doInit(?App $app = null) 初始化主程序
|
||
* @method static mixed doConsoleInit(?App $app = null) 初始化命令行
|
||
*/
|
||
class SystemService extends Service
|
||
{
|
||
/**
|
||
* 生成静态路径链接
|
||
* @param string $path 后缀路径
|
||
* @param ?string $type 路径类型
|
||
* @param mixed $default 默认数据
|
||
* @return string|array
|
||
*/
|
||
public static function uri(string $path = '', ?string $type = '__ROOT__', $default = '')
|
||
{
|
||
$plugin = Library::$sapp->http->getName();
|
||
if (strlen($path)) $path = '/' . ltrim($path, '/');
|
||
$prefix = rtrim(dirname(Library::$sapp->request->basefile()), '\\/');
|
||
$data = [
|
||
'__APP__' => rtrim(url('@')->build(), '\\/') . $path,
|
||
'__ROOT__' => $prefix . $path,
|
||
'__PLUG__' => "{$prefix}/static/extra/{$plugin}{$path}",
|
||
'__FULL__' => Library::$sapp->request->domain() . $prefix . $path
|
||
];
|
||
return is_null($type) ? $data : ($data[$type] ?? $default);
|
||
}
|
||
|
||
/**
|
||
* 生成全部静态路径
|
||
* @param string $path
|
||
* @return string[]
|
||
*/
|
||
public static function uris(string $path = ''): array
|
||
{
|
||
return static::uri($path, null);
|
||
}
|
||
|
||
/**
|
||
* 设置配置数据
|
||
* @param string $name 配置名称
|
||
* @param mixed $value 配置内容
|
||
* @return integer|string
|
||
* @throws \think\admin\Exception
|
||
*/
|
||
public static function set(string $name, $value = '')
|
||
{
|
||
[$type, $field] = static::_parse($name);
|
||
if (is_array($value)) {
|
||
$count = 0;
|
||
foreach ($value as $kk => $vv) {
|
||
$count += static::set("{$field}.{$kk}", $vv);
|
||
}
|
||
return $count;
|
||
} else try {
|
||
$map = ['type' => $type, 'name' => $field];
|
||
SystemConfig::mk()->master()->where($map)->findOrEmpty()->save(array_merge($map, ['value' => $value]));
|
||
sysvar('think.admin.config', []);
|
||
Library::$sapp->cache->delete('SystemConfig');
|
||
return 1;
|
||
} catch (\Exception $exception) {
|
||
throw new Exception($exception->getMessage(), $exception->getCode());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 读取配置数据
|
||
* @param string $name
|
||
* @param string $default
|
||
* @return array|mixed|string
|
||
* @throws \think\admin\Exception
|
||
*/
|
||
public static function get(string $name = '', string $default = '')
|
||
{
|
||
try {
|
||
if (empty($config = sysvar($keys = 'think.admin.config') ?: [])) {
|
||
SystemConfig::mk()->cache('SystemConfig')->select()->map(function ($item) use (&$config) {
|
||
$config[$item['type']][$item['name']] = $item['value'];
|
||
});
|
||
sysvar($keys, $config);
|
||
}
|
||
[$type, $field, $outer] = static::_parse($name);
|
||
if (empty($name)) {
|
||
return $config;
|
||
} elseif (isset($config[$type])) {
|
||
$group = $config[$type];
|
||
if ($outer !== 'raw') foreach ($group as $kk => $vo) {
|
||
$group[$kk] = htmlspecialchars(strval($vo));
|
||
}
|
||
return $field ? ($group[$field] ?? $default) : $group;
|
||
} else {
|
||
return $default;
|
||
}
|
||
} catch (\Exception $exception) {
|
||
throw new Exception($exception->getMessage(), $exception->getCode());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 数据增量保存
|
||
* @param Model|Query|string $query 数据查询对象
|
||
* @param array $data 需要保存的数据,成功返回对应模型
|
||
* @param string $key 更新条件查询主键
|
||
* @param mixed $map 额外更新查询条件
|
||
* @return boolean|integer 失败返回 false, 成功返回主键值或 true
|
||
* @throws \think\admin\Exception
|
||
*/
|
||
public static function save($query, array &$data, string $key = 'id', $map = [])
|
||
{
|
||
try {
|
||
$query = Helper::buildQuery($query)->master()->strict(false);
|
||
if (empty($map[$key])) $query->where([$key => $data[$key] ?? null]);
|
||
$model = $query->where($map)->findOrEmpty();
|
||
// 当前操作方法描述
|
||
$action = $model->isExists() ? 'onAdminUpdate' : 'onAdminInsert';
|
||
// 写入或更新模型数据
|
||
if ($model->save($data) === false) return false;
|
||
// 模型自定义事件回调
|
||
if ($model instanceof \think\admin\Model) {
|
||
$model->$action(strval($model->getAttr($key)));
|
||
}
|
||
$data = $model->toArray();
|
||
return $model[$key] ?? true;
|
||
} catch (\Exception $exception) {
|
||
throw new Exception($exception->getMessage(), $exception->getCode());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 批量更新保存数据
|
||
* @param Model|Query|string $query 数据查询对象
|
||
* @param array $data 需要保存的数据,成功返回对应模型
|
||
* @param string $key 更新条件查询主键
|
||
* @param mixed $map 额外更新查询条件
|
||
* @return boolean|integer 失败返回 false, 成功返回主键值或 true
|
||
* @throws \think\admin\Exception
|
||
*/
|
||
public static function batchUpdate($query, array $data, string $key = 'id', $map = [])
|
||
{
|
||
try {
|
||
$query = Helper::buildQuery($query)->master()->strict(false)->where($map);
|
||
if (empty($map[$key])) $query->where([$key => $data[$key] ?? null]);
|
||
return (clone $query)->count() > 0 ? (clone $query)->update($data) : (clone $query)->save($data);
|
||
} catch (\Exception $exception) {
|
||
throw new Exception($exception->getMessage(), $exception->getCode());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解析缓存名称
|
||
* @param string $rule 配置名称
|
||
* @return array
|
||
*/
|
||
private static function _parse(string $rule): array
|
||
{
|
||
$type = 'base';
|
||
if (stripos($rule, '.') !== false) {
|
||
[$type, $rule] = explode('.', $rule, 2);
|
||
}
|
||
[$field, $outer] = explode('|', "{$rule}|");
|
||
return [$type, $field, strtolower($outer)];
|
||
}
|
||
|
||
/**
|
||
* 获取数据库所有数据表
|
||
* @return array [table, total, count]
|
||
*/
|
||
public static function getTables(): array
|
||
{
|
||
$tables = Library::$sapp->db->getTables();
|
||
return [$tables, count($tables), 0];
|
||
}
|
||
|
||
/**
|
||
* 复制并创建表结构
|
||
* @param string $from 来源表名
|
||
* @param string $create 创建表名
|
||
* @param array $tables 现有表集合
|
||
* @param boolean $copy 是否复制
|
||
* @param mixed $where 复制条件
|
||
* @throws \think\admin\Exception
|
||
*/
|
||
public static function copyTableStruct(string $from, string $create, array $tables = [], bool $copy = false, $where = [])
|
||
{
|
||
try {
|
||
if (empty($tables)) [$tables] = static::getTables();
|
||
if (!in_array($from, $tables)) {
|
||
throw new Exception("待复制的数据表 {$from} 不存在!");
|
||
}
|
||
if (!in_array($create, $tables)) {
|
||
Library::$sapp->db->connect()->query("CREATE TABLE IF NOT EXISTS {$create} (LIKE {$from})");
|
||
if ($copy) {
|
||
$sql1 = Library::$sapp->db->name($from)->where($where)->buildSql(false);
|
||
Library::$sapp->db->connect()->query("INSERT INTO {$create} {$sql1}");
|
||
}
|
||
}
|
||
} catch (\Exception $exception) {
|
||
throw new Exception($exception->getMessage(), $exception->getCode());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存数据内容
|
||
* @param string $name 数据名称
|
||
* @param mixed $value 数据内容
|
||
* @return boolean
|
||
* @throws \think\admin\Exception
|
||
*/
|
||
public static function setData(string $name, $value): bool
|
||
{
|
||
try {
|
||
$data = ['name' => $name, 'value' => json_encode([$value], 64 | 256)];
|
||
return SystemData::mk()->where(['name' => $name])->findOrEmpty()->save($data);
|
||
} catch (\Exception $exception) {
|
||
throw new Exception($exception->getMessage(), $exception->getCode());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 读取数据内容
|
||
* @param string $name 数据名称
|
||
* @param mixed $default 默认内容
|
||
* @return mixed
|
||
*/
|
||
public static function getData(string $name, $default = [])
|
||
{
|
||
try {
|
||
// 读取原始序列化或JSON数据
|
||
$value = SystemData::mk()->where(['name' => $name])->value('value');
|
||
if (is_null($value)) return $default;
|
||
if (is_string($value) && strpos($value, '[') === 0) {
|
||
return json_decode($value, true)[0];
|
||
}
|
||
} catch (\Exception $exception) {
|
||
trace_file($exception);
|
||
return $default;
|
||
}
|
||
try {
|
||
// 尝试正常反序列解析
|
||
return unserialize($value);
|
||
} catch (\Exception $exception) {
|
||
trace_file($exception);
|
||
}
|
||
try {
|
||
// 尝试修复反序列解析
|
||
$unit = 'i:\d+;|b:[01];|s:\d+:".*?";|O:\d+:".*?":\d+:\{';
|
||
$preg = '/(?=^|' . $unit . ')s:(\d+):"(.*?)";(?=' . $unit . '|}+$)/';
|
||
return unserialize(preg_replace_callback($preg, static function ($attr) {
|
||
return sprintf('s:%d:"%s";', strlen($attr[2]), $attr[2]);
|
||
}, $value));
|
||
} catch (\Exception $exception) {
|
||
trace_file($exception);
|
||
return $default;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 写入系统日志内容
|
||
* @param string $action
|
||
* @param string $content
|
||
* @return boolean
|
||
*/
|
||
public static function setOplog(string $action, string $content): bool
|
||
{
|
||
return SystemOplog::mk()->save(static::getOplog($action, $content)) !== false;
|
||
}
|
||
|
||
/**
|
||
* 获取系统日志内容
|
||
* @param string $action
|
||
* @param string $content
|
||
* @return array
|
||
*/
|
||
public static function getOplog(string $action, string $content): array
|
||
{
|
||
return [
|
||
'node' => NodeService::getCurrent(),
|
||
'action' => lang($action), 'content' => lang($content),
|
||
'geoip' => Library::$sapp->request->ip() ?: '127.0.0.1',
|
||
'username' => AdminService::getUserName() ?: '-',
|
||
'create_at' => date('Y-m-d H:i:s'),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 打印输出数据到文件
|
||
* @param mixed $data 输出的数据
|
||
* @param boolean $new 强制替换文件
|
||
* @param string|null $file 文件名称
|
||
* @return false|int
|
||
*/
|
||
public static function putDebug($data, bool $new = false, ?string $file = null)
|
||
{
|
||
ob_start();
|
||
var_dump($data);
|
||
$output = preg_replace('/]=>\n(\s+)/m', '] => ', ob_get_clean());
|
||
if (is_null($file)) $file = syspath('runtime/' . date('Ymd') . '.log');
|
||
else if (!preg_match('#[/\\\\]+#', $file)) $file = syspath("runtime/{$file}.log");
|
||
is_dir($dir = dirname($file)) or mkdir($dir, 0777, true);
|
||
return $new ? file_put_contents($file, $output) : file_put_contents($file, $output, FILE_APPEND);
|
||
}
|
||
|
||
/**
|
||
* 设置网页标签图标
|
||
* @param ?string $icon 网页标签图标
|
||
* @return boolean
|
||
* @throws \think\admin\Exception
|
||
*/
|
||
public static function setFavicon(?string $icon = null): bool
|
||
{
|
||
try {
|
||
$icon = $icon ?: sysconf('base.site_icon|raw');
|
||
if (!preg_match('#^https?://#i', $icon)) {
|
||
throw new Exception(lang('无效的原文件地址!'));
|
||
}
|
||
if (preg_match('#/upload/(\w{2}/\w{30}.\w+)$#i', $icon, $vars)) {
|
||
$info = LocalStorage::instance()->info($vars[1]);
|
||
}
|
||
if (empty($info) || empty($info['file'])) {
|
||
$name = Storage::name($icon, 'tmp', 'icon');
|
||
$info = LocalStorage::instance()->set($name, Storage::curlGet($icon), true);
|
||
}
|
||
if (empty($info) || empty($info['file'])) return false;
|
||
$favicon = new FaviconExtend($info['file'], [48, 48]);
|
||
return $favicon->saveIco(syspath('public/favicon.ico'));
|
||
} catch (Exception $exception) {
|
||
throw $exception;
|
||
} catch (\Exception $exception) {
|
||
trace_file($exception);
|
||
throw new Exception($exception->getMessage(), $exception->getCode());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 魔术方法调用(临时)
|
||
* @param string $method 方法名称
|
||
* @param array $arguments 调用参数
|
||
* @return mixed
|
||
* @throws \think\admin\Exception
|
||
*/
|
||
public function __call(string $method, array $arguments)
|
||
{
|
||
return static::__callStatic($method, $arguments);
|
||
}
|
||
|
||
/**
|
||
* 静态方法兼容(临时)
|
||
* @param string $method 方法名称
|
||
* @param array $arguments 调用参数
|
||
* @return mixed
|
||
* @throws \think\admin\Exception
|
||
*/
|
||
public static function __callStatic(string $method, array $arguments)
|
||
{
|
||
$map = [
|
||
'setRuntime' => 'set',
|
||
'getRuntime' => 'get',
|
||
'bindRuntime' => 'apply',
|
||
'isDebug' => 'isDebug',
|
||
'isOnline' => 'isOnline',
|
||
'doInit' => 'doWebsiteInit',
|
||
'doConsoleInit' => 'doConsoleInit',
|
||
'pushRuntime' => 'push',
|
||
'clearRuntime' => 'clear',
|
||
'checkRunMode' => 'check',
|
||
];
|
||
switch (strtolower($method)) {
|
||
case 'setconfig':
|
||
return self::setData(...$arguments);
|
||
case 'getconfig':
|
||
return self::getData(...$arguments);
|
||
}
|
||
if (isset($map[$method])) {
|
||
return RuntimeService::{$map[$method]}(...$arguments);
|
||
} else {
|
||
throw new Exception("method not exists: RuntimeService::{$method}()");
|
||
}
|
||
}
|
||
}
|
||
|