Util.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. <?php
  2. namespace plugin\admin\app\common;
  3. use process\Monitor;
  4. use Throwable;
  5. use Illuminate\Database\Connection;
  6. use Illuminate\Database\Schema\Builder;
  7. use plugin\admin\app\model\Option;
  8. use support\exception\BusinessException;
  9. use support\Db;
  10. use Workerman\Timer;
  11. use Workerman\Worker;
  12. class Util
  13. {
  14. /**
  15. * 密码哈希
  16. * @param $password
  17. * @param string $algo
  18. * @return false|string|null
  19. */
  20. public static function passwordHash($password, string $algo = PASSWORD_DEFAULT)
  21. {
  22. return password_hash($password, $algo);
  23. }
  24. /**
  25. * 验证密码哈希
  26. * @param $password
  27. * @param $hash
  28. * @return bool
  29. */
  30. public static function passwordVerify(string $password, string $hash): bool
  31. {
  32. return password_verify($password, $hash);
  33. }
  34. /**
  35. * 获取webman-admin数据库连接
  36. * @return Connection
  37. */
  38. public static function db(): Connection
  39. {
  40. return Db::connection('plugin.admin.mysql');
  41. }
  42. /**
  43. * 获取SchemaBuilder
  44. * @return Builder
  45. */
  46. public static function schema(): Builder
  47. {
  48. return Db::schema('plugin.admin.mysql');
  49. }
  50. /**
  51. * 获取语义化时间
  52. * @param $time
  53. * @return false|string
  54. */
  55. public static function humanDate($time)
  56. {
  57. $timestamp = is_numeric($time) ? $time : strtotime($time);
  58. $dur = time() - $timestamp;
  59. if ($dur < 0) {
  60. return date('Y-m-d', $timestamp);
  61. } else {
  62. if ($dur < 60) {
  63. return $dur . '秒前';
  64. } else {
  65. if ($dur < 3600) {
  66. return floor($dur / 60) . '分钟前';
  67. } else {
  68. if ($dur < 86400) {
  69. return floor($dur / 3600) . '小时前';
  70. } else {
  71. if ($dur < 2592000) { // 30天内
  72. return floor($dur / 86400) . '天前';
  73. } else {
  74. return date('Y-m-d', $timestamp);;
  75. }
  76. }
  77. }
  78. }
  79. }
  80. return date('Y-m-d', $timestamp);
  81. }
  82. /**
  83. * 格式化文件大小
  84. * @param $file_size
  85. * @return string
  86. */
  87. public static function formatBytes($file_size): string
  88. {
  89. $size = sprintf("%u", $file_size);
  90. if($size == 0) {
  91. return("0 Bytes");
  92. }
  93. $size_name = array(" Bytes", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB");
  94. return round($size/pow(1024, ($i = floor(log($size, 1024)))), 2) . $size_name[$i];
  95. }
  96. /**
  97. * 数据库字符串转义
  98. * @param $var
  99. * @return false|string
  100. */
  101. public static function pdoQuote($var)
  102. {
  103. return Util::db()->getPdo()->quote($var, \PDO::PARAM_STR);
  104. }
  105. /**
  106. * 检查表名是否合法
  107. * @param string $table
  108. * @return string
  109. * @throws BusinessException
  110. */
  111. public static function checkTableName(string $table): string
  112. {
  113. if (!preg_match('/^[a-zA-Z_0-9]+$/', $table)) {
  114. throw new BusinessException('表名不合法');
  115. }
  116. return $table;
  117. }
  118. /**
  119. * 变量或数组中的元素只能是字母数字下划线组合
  120. * @param $var
  121. * @return mixed
  122. * @throws BusinessException
  123. */
  124. public static function filterAlphaNum($var)
  125. {
  126. $vars = (array)$var;
  127. array_walk_recursive($vars, function ($item) {
  128. if (is_string($item) && !preg_match('/^[a-zA-Z_0-9]+$/', $item)) {
  129. throw new BusinessException('参数不合法');
  130. }
  131. });
  132. return $var;
  133. }
  134. /**
  135. * 变量或数组中的元素只能是字母数字
  136. * @param $var
  137. * @return mixed
  138. * @throws BusinessException
  139. */
  140. public static function filterNum($var)
  141. {
  142. $vars = (array)$var;
  143. array_walk_recursive($vars, function ($item) {
  144. if (is_string($item) && !preg_match('/^[0-9]+$/', $item)) {
  145. throw new BusinessException('参数不合法');
  146. }
  147. });
  148. return $var;
  149. }
  150. /**
  151. * 检测是否是合法URL Path
  152. * @param $var
  153. * @return string
  154. * @throws BusinessException
  155. */
  156. public static function filterUrlPath($var): string
  157. {
  158. if (!is_string($var) || !preg_match('/^[a-zA-Z0-9_\-\/&?.]+$/', $var)) {
  159. throw new BusinessException('参数不合法');
  160. }
  161. return $var;
  162. }
  163. /**
  164. * 检测是否是合法Path
  165. * @param $var
  166. * @return string
  167. * @throws BusinessException
  168. */
  169. public static function filterPath($var): string
  170. {
  171. if (!is_string($var) || !preg_match('/^[a-zA-Z0-9_\-\/]+$/', $var)) {
  172. throw new BusinessException('参数不合法');
  173. }
  174. return $var;
  175. }
  176. /**
  177. * 类转换为url path
  178. * @param $controller_class
  179. * @return false|string
  180. */
  181. static function controllerToUrlPath($controller_class)
  182. {
  183. $key = strtolower($controller_class);
  184. $action = '';
  185. if (strpos($key, '@')) {
  186. [$key, $action] = explode( '@', $key, 2);
  187. }
  188. $prefix = 'plugin';
  189. $paths = explode('\\', $key);
  190. if (count($paths) < 2) {
  191. return false;
  192. }
  193. $base = '';
  194. if (strpos($key, "$prefix\\") === 0) {
  195. if (count($paths) < 4) {
  196. return false;
  197. }
  198. array_shift($paths);
  199. $plugin = array_shift($paths);
  200. $base = "/app/$plugin/";
  201. }
  202. array_shift($paths);
  203. foreach ($paths as $index => $path) {
  204. if ($path === 'controller') {
  205. unset($paths[$index]);
  206. }
  207. }
  208. $suffix = 'controller';
  209. $code = $base . implode('/', $paths);
  210. if (substr($code, -strlen($suffix)) === $suffix) {
  211. $code = substr($code, 0, -strlen($suffix));
  212. }
  213. return $action ? "$code/$action" : $code;
  214. }
  215. /**
  216. * 转换为驼峰
  217. * @param string $value
  218. * @return string
  219. */
  220. public static function camel(string $value): string
  221. {
  222. static $cache = [];
  223. $key = $value;
  224. if (isset($cache[$key])) {
  225. return $cache[$key];
  226. }
  227. $value = ucwords(str_replace(['-', '_'], ' ', $value));
  228. return $cache[$key] = str_replace(' ', '', $value);
  229. }
  230. /**
  231. * 转换为小驼峰
  232. * @param $value
  233. * @return string
  234. */
  235. public static function smCamel($value): string
  236. {
  237. return lcfirst(static::camel($value));
  238. }
  239. /**
  240. * 获取注释中第一行
  241. * @param $comment
  242. * @return false|mixed|string
  243. */
  244. public static function getCommentFirstLine($comment)
  245. {
  246. if ($comment === false) {
  247. return false;
  248. }
  249. foreach (explode("\n", $comment) as $str) {
  250. if ($s = trim($str, "*/\ \t\n\r\0\x0B")) {
  251. return $s;
  252. }
  253. }
  254. return $comment;
  255. }
  256. /**
  257. * 表单类型到插件的映射
  258. * @return \string[][]
  259. */
  260. public static function methodControlMap(): array
  261. {
  262. return [
  263. //method=>[控件]
  264. 'integer' => ['InputNumber'],
  265. 'string' => ['Input'],
  266. 'text' => ['TextArea'],
  267. 'date' => ['DatePicker'],
  268. 'enum' => ['Select'],
  269. 'float' => ['Input'],
  270. 'tinyInteger' => ['InputNumber'],
  271. 'smallInteger' => ['InputNumber'],
  272. 'mediumInteger' => ['InputNumber'],
  273. 'bigInteger' => ['InputNumber'],
  274. 'unsignedInteger' => ['InputNumber'],
  275. 'unsignedTinyInteger' => ['InputNumber'],
  276. 'unsignedSmallInteger' => ['InputNumber'],
  277. 'unsignedMediumInteger' => ['InputNumber'],
  278. 'unsignedBigInteger' => ['InputNumber'],
  279. 'decimal' => ['Input'],
  280. 'double' => ['Input'],
  281. 'mediumText' => ['TextArea'],
  282. 'longText' => ['TextArea'],
  283. 'dateTime' => ['DateTimePicker'],
  284. 'time' => ['DateTimePicker'],
  285. 'timestamp' => ['DateTimePicker'],
  286. 'char' => ['Input'],
  287. 'binary' => ['Input'],
  288. 'json' => ['input']
  289. ];
  290. }
  291. /**
  292. * 数据库类型到插件的转换
  293. * @param $type
  294. * @return string
  295. */
  296. public static function typeToControl($type): string
  297. {
  298. if (stripos($type, 'int') !== false) {
  299. return 'inputNumber';
  300. }
  301. if (stripos($type, 'time') !== false || stripos($type, 'date') !== false) {
  302. return 'dateTimePicker';
  303. }
  304. if (stripos($type, 'text') !== false) {
  305. return 'textArea';
  306. }
  307. if ($type === 'enum') {
  308. return 'select';
  309. }
  310. return 'input';
  311. }
  312. /**
  313. * 数据库类型到表单类型的转换
  314. * @param $type
  315. * @param $unsigned
  316. * @return string
  317. */
  318. public static function typeToMethod($type, $unsigned = false)
  319. {
  320. if (stripos($type, 'int') !== false) {
  321. $type = str_replace('int', 'Integer', $type);
  322. return $unsigned ? "unsigned" . ucfirst($type) : lcfirst($type);
  323. }
  324. $map = [
  325. 'int' => 'integer',
  326. 'varchar' => 'string',
  327. 'mediumtext' => 'mediumText',
  328. 'longtext' => 'longText',
  329. 'datetime' => 'dateTime',
  330. ];
  331. return $map[$type] ?? $type;
  332. }
  333. /**
  334. * 按表获取摘要
  335. * @param $table
  336. * @param null $section
  337. * @return array|mixed
  338. * @throws BusinessException
  339. */
  340. public static function getSchema($table, $section = null)
  341. {
  342. Util::checkTableName($table);
  343. $database = config('database.connections')['plugin.admin.mysql']['database'];
  344. $schema_raw = $section !== 'table' ? Util::db()->select("select * from information_schema.COLUMNS where TABLE_SCHEMA = '$database' and table_name = '$table' order by ORDINAL_POSITION") : [];
  345. $forms = [];
  346. $columns = [];
  347. foreach ($schema_raw as $item) {
  348. $field = $item->COLUMN_NAME;
  349. $columns[$field] = [
  350. 'field' => $field,
  351. 'type' => Util::typeToMethod($item->DATA_TYPE, (bool)strpos($item->COLUMN_TYPE, 'unsigned')),
  352. 'comment' => $item->COLUMN_COMMENT,
  353. 'default' => $item->COLUMN_DEFAULT,
  354. 'length' => static::getLengthValue($item),
  355. 'nullable' => $item->IS_NULLABLE !== 'NO',
  356. 'primary_key' => $item->COLUMN_KEY === 'PRI',
  357. 'auto_increment' => strpos($item->EXTRA, 'auto_increment') !== false
  358. ];
  359. $forms[$field] = [
  360. 'field' => $field,
  361. 'comment' => $item->COLUMN_COMMENT,
  362. 'control' => static::typeToControl($item->DATA_TYPE),
  363. 'form_show' => $item->COLUMN_KEY !== 'PRI',
  364. 'list_show' => true,
  365. 'enable_sort' => false,
  366. 'searchable' => false,
  367. 'search_type' => 'normal',
  368. 'control_args' => '',
  369. ];
  370. }
  371. $table_schema = $section == 'table' || !$section ? Util::db()->select("SELECT TABLE_COMMENT FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' and TABLE_NAME='$table'") : [];
  372. $indexes = !$section || in_array($section, ['keys', 'table']) ? Util::db()->select("SHOW INDEX FROM `$table`") : [];
  373. $keys = [];
  374. $primary_key = [];
  375. foreach ($indexes as $index) {
  376. $key_name = $index->Key_name;
  377. if ($key_name == 'PRIMARY') {
  378. $primary_key[] = $index->Column_name;
  379. continue;
  380. }
  381. if (!isset($keys[$key_name])) {
  382. $keys[$key_name] = [
  383. 'name' => $key_name,
  384. 'columns' => [],
  385. 'type' => $index->Non_unique == 0 ? 'unique' : 'normal'
  386. ];
  387. }
  388. $keys[$key_name]['columns'][] = $index->Column_name;
  389. }
  390. $data = [
  391. 'table' => ['name' => $table, 'comment' => $table_schema[0]->TABLE_COMMENT ?? '', 'primary_key' => $primary_key],
  392. 'columns' => $columns,
  393. 'forms' => $forms,
  394. 'keys' => array_reverse($keys, true)
  395. ];
  396. $schema = Option::where('name', "table_form_schema_$table")->value('value');
  397. $form_schema_map = $schema ? json_decode($schema, true) : [];
  398. foreach ($data['forms'] as $field => $item) {
  399. if (isset($form_schema_map[$field])) {
  400. $data['forms'][$field] = $form_schema_map[$field];
  401. }
  402. }
  403. return $section ? $data[$section] : $data;
  404. }
  405. /**
  406. * 获取字段长度或默认值
  407. * @param $schema
  408. * @return mixed|string
  409. */
  410. public static function getLengthValue($schema)
  411. {
  412. $type = $schema->DATA_TYPE;
  413. if (in_array($type, ['float', 'decimal', 'double'])) {
  414. return "{$schema->NUMERIC_PRECISION},{$schema->NUMERIC_SCALE}";
  415. }
  416. if ($type === 'enum') {
  417. return implode(',', array_map(function($item){
  418. return trim($item, "'");
  419. }, explode(',', substr($schema->COLUMN_TYPE, 5, -1))));
  420. }
  421. if (in_array($type, ['varchar', 'text', 'char'])) {
  422. return $schema->CHARACTER_MAXIMUM_LENGTH;
  423. }
  424. if (in_array($type, ['time', 'datetime', 'timestamp'])) {
  425. return $schema->CHARACTER_MAXIMUM_LENGTH;
  426. }
  427. return '';
  428. }
  429. /**
  430. * 获取控件参数
  431. * @param $control
  432. * @param $control_args
  433. * @return array
  434. */
  435. public static function getControlProps($control, $control_args): array
  436. {
  437. if (!$control_args) {
  438. return [];
  439. }
  440. $control = strtolower($control);
  441. $props = [];
  442. $split = explode(';', $control_args);
  443. foreach ($split as $item) {
  444. $pos = strpos($item, ':');
  445. if ($pos === false) {
  446. continue;
  447. }
  448. $name = trim(substr($item, 0, $pos));
  449. $values = trim(substr($item, $pos + 1));
  450. // values = a:v,c:d
  451. $pos = strpos($values, ':');
  452. if ($pos !== false && strpos($values, "#") !== 0) {
  453. $options = explode(',', $values);
  454. $values = [];
  455. foreach ($options as $option) {
  456. [$v, $n] = explode(':', $option);
  457. if (in_array($control, ['select', 'selectmulti', 'treeselect', 'treemultiselect']) && $name == 'data') {
  458. $values[] = ['value' => $v, 'name' => $n];
  459. } else {
  460. $values[$v] = $n;
  461. }
  462. }
  463. }
  464. $props[$name] = $values;
  465. }
  466. return $props;
  467. }
  468. /**
  469. * 获取某个composer包的版本
  470. * @param string $package
  471. * @return mixed|string
  472. */
  473. public static function getPackageVersion(string $package)
  474. {
  475. $installed_php = base_path('vendor/composer/installed.php');
  476. if (is_file($installed_php)) {
  477. $packages = include $installed_php;
  478. }
  479. return substr($packages['versions'][$package]['version'] ?? 'unknown ', 0, -2);
  480. }
  481. /**
  482. * Reload webman
  483. * @return bool
  484. */
  485. public static function reloadWebman()
  486. {
  487. if (function_exists('posix_kill')) {
  488. try {
  489. posix_kill(posix_getppid(), SIGUSR1);
  490. return true;
  491. } catch (Throwable $e) {}
  492. } else {
  493. Timer::add(1, function () {
  494. Worker::stopAll();
  495. });
  496. }
  497. return false;
  498. }
  499. /**
  500. * Pause file monitor
  501. * @return void
  502. */
  503. public static function pauseFileMonitor()
  504. {
  505. if (method_exists(Monitor::class, 'pause')) {
  506. Monitor::pause();
  507. }
  508. }
  509. /**
  510. * Resume file monitor
  511. * @return void
  512. */
  513. public static function resumeFileMonitor()
  514. {
  515. if (method_exists(Monitor::class, 'resume')) {
  516. Monitor::resume();
  517. }
  518. }
  519. }