SystemService.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkAdmin
  4. // +----------------------------------------------------------------------
  5. // | 版权所有 2014~2021 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
  6. // +----------------------------------------------------------------------
  7. // | 官方网站: https://gitee.com/zoujingli/ThinkLibrary
  8. // +----------------------------------------------------------------------
  9. // | 开源协议 ( https://mit-license.org )
  10. // +----------------------------------------------------------------------
  11. // | gitee 代码仓库:https://gitee.com/zoujingli/ThinkLibrary
  12. // | github 代码仓库:https://github.com/zoujingli/ThinkLibrary
  13. // +----------------------------------------------------------------------
  14. declare (strict_types=1);
  15. namespace think\admin\service;
  16. use Exception;
  17. use think\admin\Service;
  18. use think\App;
  19. use think\db\exception\DataNotFoundException;
  20. use think\db\exception\DbException;
  21. use think\db\exception\ModelNotFoundException;
  22. use think\db\Query;
  23. use think\helper\Str;
  24. use think\Model;
  25. /**
  26. * 系统参数管理服务
  27. * Class SystemService
  28. * @package think\admin\service
  29. */
  30. class SystemService extends Service
  31. {
  32. /**
  33. * 配置数据缓存
  34. * @var array
  35. */
  36. protected $data = [];
  37. /**
  38. * 绑定配置数据表
  39. * @var string
  40. */
  41. protected $table = 'SystemConfig';
  42. /**
  43. * 设置配置数据
  44. * @param string $name 配置名称
  45. * @param mixed $value 配置内容
  46. * @return integer|string
  47. * @throws DataNotFoundException
  48. * @throws DbException
  49. * @throws ModelNotFoundException
  50. */
  51. public function set(string $name, $value = '')
  52. {
  53. $this->data = [];
  54. [$type, $field] = $this->_parse($name);
  55. if (is_array($value)) {
  56. $count = 0;
  57. foreach ($value as $kk => $vv) {
  58. $count += $this->set("{$field}.{$kk}", $vv);
  59. }
  60. return $count;
  61. } else {
  62. $this->app->cache->delete($this->table);
  63. $map = ['type' => $type, 'name' => $field];
  64. $data = array_merge($map, ['value' => $value]);
  65. $query = $this->app->db->name($this->table)->master(true)->where($map);
  66. return (clone $query)->count() > 0 ? $query->update($data) : $query->insert($data);
  67. }
  68. }
  69. /**
  70. * 读取配置数据
  71. * @param string $name
  72. * @param string $default
  73. * @return array|mixed|string
  74. * @throws DataNotFoundException
  75. * @throws DbException
  76. * @throws ModelNotFoundException
  77. */
  78. public function get(string $name = '', string $default = '')
  79. {
  80. if (empty($this->data)) {
  81. $this->app->db->name($this->table)->cache($this->table)->select()->map(function ($item) {
  82. $this->data[$item['type']][$item['name']] = $item['value'];
  83. });
  84. }
  85. [$type, $field, $outer] = $this->_parse($name);
  86. if (empty($name)) {
  87. return $this->data;
  88. } elseif (isset($this->data[$type])) {
  89. $group = $this->data[$type];
  90. if ($outer !== 'raw') foreach ($group as $kk => $vo) {
  91. $group[$kk] = htmlspecialchars($vo);
  92. }
  93. return $field ? ($group[$field] ?? $default) : $group;
  94. } else {
  95. return $default;
  96. }
  97. }
  98. /**
  99. * 数据增量保存
  100. * @param Model|Query|string $query 数据查询对象
  101. * @param array $data 需要保存的数据
  102. * @param string $key 更新条件查询主键
  103. * @param array $map 额外更新查询条件
  104. * @return boolean|integer 失败返回 false, 成功返回主键值或 true
  105. * @throws DataNotFoundException
  106. * @throws DbException
  107. * @throws ModelNotFoundException
  108. */
  109. public function save($query, array $data, string $key = 'id', array $map = [])
  110. {
  111. $query = is_string($query) ? $this->app->db->name($query) : ($query instanceof Model ? $query->db() : $query);
  112. if (!$query instanceof Query) throw new ModelNotFoundException('数据库操作对象异常!');
  113. [$value] = [$data[$key] ?? null, $query->master()->strict(false)->where($map)];
  114. if (empty($map[$key])) if (is_string($value) && strpos($value, ',') !== false) {
  115. $query->whereIn($key, str2arr($value));
  116. } else {
  117. $query->where([$key => $value]);
  118. }
  119. if (($info = (clone $query)->find()) && !empty($info)) {
  120. if ($info instanceof Model) $info = $info->toArray();
  121. $query->update($data);
  122. return $info[$key] ?? true;
  123. } else {
  124. return $query->insertGetId($data);
  125. }
  126. }
  127. /**
  128. * 解析缓存名称
  129. * @param string $rule 配置名称
  130. * @return array
  131. */
  132. private function _parse(string $rule): array
  133. {
  134. $type = 'base';
  135. if (stripos($rule, '.') !== false) {
  136. [$type, $rule] = explode('.', $rule, 2);
  137. }
  138. [$field, $outer] = explode('|', "{$rule}|");
  139. return [$type, $field, strtolower($outer)];
  140. }
  141. /**
  142. * 生成最短URL地址
  143. * @param string $url 路由地址
  144. * @param array $vars PATH 变量
  145. * @param boolean|string $suffix 后缀
  146. * @param boolean|string $domain 域名
  147. * @return string
  148. */
  149. public function sysuri(string $url = '', array $vars = [], $suffix = true, $domain = false): string
  150. {
  151. $ext = $this->app->config->get('route.url_html_suffix', 'html');
  152. $pre = $this->app->route->buildUrl('@')->suffix(false)->domain($domain)->build();
  153. $uri = $this->app->route->buildUrl($url, $vars)->suffix($suffix)->domain($domain)->build();
  154. // 默认节点配置数据
  155. $app = $this->app->config->get('app.default_app');
  156. $act = Str::lower($this->app->config->get('route.default_action'));
  157. $ctr = Str::snake($this->app->config->get('route.default_controller'));
  158. // 替换省略链接路径
  159. return preg_replace([
  160. "#^({$pre}){$app}/{$ctr}/{$act}(\.{$ext}|^\w|\?|$)?#i",
  161. "#^({$pre}[\w\.]+)/{$ctr}/{$act}(\.{$ext}|^\w|\?|$)#i",
  162. "#^({$pre}[\w\.]+)(/[\w\.]+)/{$act}(\.{$ext}|^\w|\?|$)#i",
  163. ], ['$1$2', '$1$2', '$1$2$3'], $uri);
  164. }
  165. /**
  166. * 保存数据内容
  167. * @param string $name
  168. * @param mixed $value
  169. * @return boolean
  170. * @throws DataNotFoundException
  171. * @throws DbException
  172. * @throws ModelNotFoundException
  173. */
  174. public function setData(string $name, $value)
  175. {
  176. return $this->save('SystemData', ['name' => $name, 'value' => serialize($value)], 'name');
  177. }
  178. /**
  179. * 获取数据库所有数据表
  180. * @return array [table, total, count]
  181. */
  182. public function getTables(): array
  183. {
  184. $tables = [];
  185. foreach ($this->app->db->query("show tables") as $item) {
  186. $tables = array_merge($tables, array_values($item));
  187. }
  188. return [$tables, count($tables), 0];
  189. }
  190. /**
  191. * 读取数据内容
  192. * @param string $name
  193. * @param mixed $default
  194. * @return mixed
  195. */
  196. public function getData(string $name, $default = [])
  197. {
  198. try {
  199. $value = $this->app->db->name('SystemData')->where(['name' => $name])->value('value');
  200. return is_null($value) ? $default : unserialize($value);
  201. } catch (Exception $exception) {
  202. return $default;
  203. }
  204. }
  205. /**
  206. * 写入系统日志内容
  207. * @param string $action
  208. * @param string $content
  209. * @return boolean
  210. */
  211. public function setOplog(string $action, string $content): bool
  212. {
  213. $oplog = $this->getOplog($action, $content);
  214. return $this->app->db->name('SystemOplog')->insert($oplog) !== false;
  215. }
  216. /**
  217. * 获取系统日志内容
  218. * @param string $action
  219. * @param string $content
  220. * @return array
  221. */
  222. public function getOplog(string $action, string $content): array
  223. {
  224. return [
  225. 'node' => NodeService::instance()->getCurrent(),
  226. 'action' => $action, 'content' => $content,
  227. 'geoip' => $this->app->request->ip() ?: '127.0.0.1',
  228. 'username' => AdminService::instance()->getUserName() ?: '-',
  229. ];
  230. }
  231. /**
  232. * 打印输出数据到文件
  233. * @param mixed $data 输出的数据
  234. * @param boolean $new 强制替换文件
  235. * @param string|null $file 文件名称
  236. * @return false|int
  237. */
  238. public function putDebug($data, bool $new = false, ?string $file = null)
  239. {
  240. if (is_null($file)) $file = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . date('Ymd') . '.log';
  241. $str = (is_string($data) ? $data : ((is_array($data) || is_object($data)) ? print_r($data, true) : var_export($data, true))) . PHP_EOL;
  242. return $new ? file_put_contents($file, $str) : file_put_contents($file, $str, FILE_APPEND);
  243. }
  244. /**
  245. * 判断运行环境
  246. * @param string $type 运行模式(dev|demo|local)
  247. * @return boolean
  248. */
  249. public function checkRunMode(string $type = 'dev'): bool
  250. {
  251. $domain = $this->app->request->host(true);
  252. $isDemo = is_numeric(stripos($domain, 'thinkadmin.top'));
  253. $isLocal = in_array($domain, ['127.0.0.1', 'localhost']);
  254. if ($type === 'dev') return $isLocal || $isDemo;
  255. if ($type === 'demo') return $isDemo;
  256. if ($type === 'local') return $isLocal;
  257. return true;
  258. }
  259. /**
  260. * 压缩发布项目
  261. */
  262. public function pushRuntime(): void
  263. {
  264. $connection = $this->app->db->getConfig('default');
  265. $this->app->console->call("optimize:schema", ["--connection={$connection}"]);
  266. foreach (NodeService::instance()->getModules() as $module) {
  267. $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . $module;
  268. file_exists($path) && is_dir($path) || mkdir($path, 0755, true);
  269. $this->app->console->call("optimize:route", [$module]);
  270. }
  271. }
  272. /**
  273. * 清理运行缓存
  274. */
  275. public function clearRuntime(): void
  276. {
  277. $data = $this->getRuntime();
  278. $this->app->cache->clear();
  279. $this->app->console->call('clear', ['--dir']);
  280. $this->setRuntime($data['mode'], $data['appmap'], $data['domain']);
  281. }
  282. /**
  283. * 判断实时运行模式
  284. * @return boolean
  285. */
  286. public function isDebug(): bool
  287. {
  288. return $this->getRuntime('mode') !== 'product';
  289. }
  290. /**
  291. * 设置实时运行配置
  292. * @param null|mixed $mode 支持模式
  293. * @param null|array $appmap 应用映射
  294. * @param null|array $domain 域名映射
  295. * @return boolean 是否调试模式
  296. */
  297. public function setRuntime(?string $mode = null, ?array $appmap = [], ?array $domain = []): bool
  298. {
  299. $data = $this->getRuntime();
  300. $data['mode'] = $mode ?: $data['mode'];
  301. $data['appmap'] = $this->uniqueArray($data['appmap'], $appmap);
  302. $data['domain'] = $this->uniqueArray($data['domain'], $domain);
  303. // 组装配置文件格式
  304. $rows[] = "mode = {$data['mode']}";
  305. foreach ($data['appmap'] as $key => $item) $rows[] = "appmap[{$key}] = {$item}";
  306. foreach ($data['domain'] as $key => $item) $rows[] = "domain[{$key}] = {$item}";
  307. // 数据配置保存文件
  308. $env = $this->app->getRootPath() . 'runtime/.env';
  309. file_put_contents($env, "[RUNTIME]\n" . join("\n", $rows));
  310. return $this->bindRuntime($data);
  311. }
  312. /**
  313. * 获取实时运行配置
  314. * @param null|string $name 配置名称
  315. * @param array $default 配置内容
  316. * @return array|string
  317. */
  318. public function getRuntime(?string $name = null, array $default = [])
  319. {
  320. $env = $this->app->getRootPath() . 'runtime/.env';
  321. if (file_exists($env)) $this->app->env->load($env);
  322. $data = [
  323. 'mode' => $this->app->env->get('RUNTIME_MODE') ?: 'debug',
  324. 'appmap' => $this->app->env->get('RUNTIME_APPMAP') ?: [],
  325. 'domain' => $this->app->env->get('RUNTIME_DOMAIN') ?: [],
  326. ];
  327. return is_null($name) ? $data : ($data[$name] ?? $default);
  328. }
  329. /**
  330. * 绑定应用实时配置
  331. * @param array $data 配置数据
  332. * @return boolean 是否调试模式
  333. */
  334. public function bindRuntime(array $data = []): bool
  335. {
  336. if (empty($data)) $data = $this->getRuntime();
  337. $bind['app_map'] = $this->uniqueArray($this->app->config->get('app.app_map', []), $data['appmap']);
  338. $bind['domain_bind'] = $this->uniqueArray($this->app->config->get('app.domain_bind', []), $data['domain']);
  339. $this->app->config->set($bind, 'app');
  340. return $this->app->debug($data['mode'] !== 'product')->isDebug();
  341. }
  342. /**
  343. * 初始化并运行主程序
  344. * @param null|App $app
  345. */
  346. public function doInit(?App $app = null)
  347. {
  348. $this->app = $app ?: $this->app;
  349. $this->app->debug($this->isDebug());
  350. ($response = $this->app->http->run())->send();
  351. $this->app->http->end($response);
  352. }
  353. /**
  354. * 初始化命令行主程序
  355. * @param App|null $app
  356. * @throws Exception
  357. */
  358. public function doConsoleInit(?App $app = null)
  359. {
  360. $this->app = $app ?: $this->app;
  361. $this->app->debug($this->isDebug());
  362. $this->app->console->run();
  363. }
  364. /**
  365. * 获取唯一数组参数
  366. * @param array ...$args
  367. * @return array
  368. */
  369. private function uniqueArray(...$args): array
  370. {
  371. return array_unique(array_reverse(array_merge(...$args)));
  372. }
  373. }