浏览代码

初始化

tgz 2 年之前
父节点
当前提交
9306ead42f
共有 63 个文件被更改,包括 5610 次插入2 次删除
  1. 7 0
      .gitignore
  2. 21 0
      LICENSE
  3. 15 2
      README.md
  4. 37 0
      app/command/Test.php
  5. 20 0
      app/controller/Track.php
  6. 63 0
      app/functions.php
  7. 61 0
      app/middleware/ExternalSignCheck.php
  8. 42 0
      app/middleware/StaticFile.php
  9. 48 0
      app/model/DouyinTrack.php
  10. 32 0
      app/model/TableSuffix.php
  11. 29 0
      app/model/Test.php
  12. 23 0
      app/queue/redis/TiktokTrack.php
  13. 11 0
      app/services/BaseService.php
  14. 96 0
      app/services/TrackService.php
  15. 14 0
      app/view/index/view.html
  16. 69 0
      composer.json
  17. 3086 0
      composer.lock
  18. 24 0
      config/app.php
  19. 21 0
      config/autoload.php
  20. 18 0
      config/bootstrap.php
  21. 15 0
      config/container.php
  22. 35 0
      config/database.php
  23. 15 0
      config/dependence.php
  24. 17 0
      config/exception.php
  25. 37 0
      config/log.php
  26. 15 0
      config/middleware.php
  27. 4 0
      config/plugin/kriss/webman-logger/app.php
  28. 88 0
      config/plugin/kriss/webman-logger/log-channel.php
  29. 7 0
      config/plugin/kriss/webman-logger/middleware.php
  30. 44 0
      config/plugin/tinywan/exception-handler/app.php
  31. 69 0
      config/plugin/tinywan/jwt/app.php
  32. 18 0
      config/plugin/webman/console/app.php
  33. 4 0
      config/plugin/webman/log/app.php
  34. 21 0
      config/plugin/webman/log/middleware.php
  35. 4 0
      config/plugin/webman/redis-queue/app.php
  36. 7 0
      config/plugin/webman/redis-queue/command.php
  37. 11 0
      config/plugin/webman/redis-queue/process.php
  38. 13 0
      config/plugin/webman/redis-queue/redis.php
  39. 40 0
      config/process.php
  40. 22 0
      config/redis.php
  41. 20 0
      config/route.php
  42. 31 0
      config/server.php
  43. 61 0
      config/session.php
  44. 23 0
      config/static.php
  45. 25 0
      config/translation.php
  46. 22 0
      config/view.php
  47. 195 0
      process/Monitor.php
  48. 28 0
      process/Task.php
  49. 12 0
      public/404.html
  50. 二进制
      public/favicon.ico
  51. 4 0
      runtime/.gitignore
  52. 2 0
      runtime/logs/.gitignore
  53. 2 0
      runtime/views/.gitignore
  54. 117 0
      start.php
  55. 55 0
      support/Plugin.php
  56. 24 0
      support/Request.php
  57. 24 0
      support/Response.php
  58. 90 0
      support/bootstrap.php
  59. 11 0
      support/facade/Logger.php
  60. 480 0
      support/helpers.php
  61. 44 0
      webman
  62. 3 0
      windows.bat
  63. 114 0
      windows.php

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+/.idea
+/.vscode
+/vendor
+*.log
+.env
+/tests/tmp
+/tests/.phpunit.result.cache

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/webman/contributors)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 15 - 2
README.md

@@ -1,3 +1,16 @@
-# high_per_api
+# webman
 
-基于webman的高性能api接口,目前运行监测链接的处理服务
+High performance HTTP Service Framework for PHP based on [Workerman](https://github.com/walkor/workerman).
+
+# Manual
+
+https://www.workerman.net/doc/webman
+
+# Benchmarks
+
+https://www.techempower.com/benchmarks/#section=test&runid=9716e3cd-9e53-433c-b6c5-d2c48c9593c1&hw=ph&test=db&l=zg24n3-1r&a=2
+![image](https://user-images.githubusercontent.com/6073368/96447814-120fc980-1245-11eb-938d-6ea408716c72.png)
+
+## LICENSE
+
+MIT

+ 37 - 0
app/command/Test.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace app\command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Output\OutputInterface;
+
+
+class Test extends Command
+{
+    protected static $defaultName = 'Test:t1';
+    protected static $defaultDescription = 'Test';
+
+    /**
+     * @return void
+     */
+    protected function configure()
+    {
+        $this->addArgument('name', InputArgument::OPTIONAL, 'Name description');
+    }
+
+    /**
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @return int
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $name = $input->getArgument('name');
+        $output->writeln('Hello Test');
+        return self::SUCCESS;
+    }
+
+}

+ 20 - 0
app/controller/Track.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace app\controller;
+
+use support\Request;
+use support\Db;
+use App\services\TrackService;
+use support\facade\Logger;
+
+class Track
+{
+    public function index(Request $request)
+    {
+        $params = $request->all();
+        //Logger::app($params);
+        TrackService::push($params);
+        return 'success' ;
+    }
+
+}

+ 63 - 0
app/functions.php

@@ -0,0 +1,63 @@
+<?php
+use Hashids\Hashids;
+
+/**
+ * Here is your custom functions.
+ */
+    /*
+    快速排序
+*/
+function quickSort($array)
+{
+    if(!isset($array[1]))
+        return $array;
+    $mid = $array[0]; //获取一个用于分割的关键字,一般是首个元素
+    $leftArray = array();
+    $rightArray = array();
+
+    foreach($array as $v)
+    {
+        if($v > $mid)
+            $rightArray[] = $v;  //把比$mid大的数放到一个数组里
+        if($v < $mid)
+            $leftArray[] = $v;   //把比$mid小的数放到另一个数组里
+    }
+
+    $leftArray = quickSort($leftArray); //把比较小的数组再一次进行分割
+    $leftArray[] = $mid;        //把分割的元素加到小的数组后面,不能忘了它哦
+
+    $rightArray = quickSort($rightArray);  //把比较大的数组再一次进行分割
+    return array_merge($leftArray,$rightArray);  //组合两个结果
+}
+
+// 获取Hashids 对象
+function getHashids(){
+    global $hashids;
+    if($hashids instanceof Hashids ){
+        return $hashids;
+    }
+    $hashids = new Hashids('D6M97LIvpp4qWuz3nKzqi6yYN4GAA61b',32);
+    return $hashids;
+    
+}
+
+/**
+ * 获取对象或数组的属性值
+ * @param        $param
+ * @param        $key
+ * @param string $default
+ * @return mixed|string
+ */
+function getProp($param, $key, $default = '')
+{
+    $result = $default;
+    if (is_object($param) && isset($param->$key)) {
+        $result = $param->$key;
+    }
+
+    if (is_array($param) && isset($param[$key])) {
+        $result = $param[$key];
+    }
+
+    return $result;
+}

+ 61 - 0
app/middleware/ExternalSignCheck.php

@@ -0,0 +1,61 @@
+<?php
+namespace App\Middleware;
+
+use Webman\MiddlewareInterface;
+use Webman\Http\Response;
+use Webman\Http\Request;
+
+class ExternalSignCheck implements MiddlewareInterface
+{
+    
+    public function process(Request $request, callable $next): Response
+    {
+        // Access to files beginning with. Is prohibited
+        if (strpos($request->path(), '/.') !== false) {
+            return response('<h1>403 forbidden</h1>', 403);
+        }
+        /** @var Response $response */
+        $response = $next($request);
+        // Add cross domain HTTP header
+        /*$response->withHeaders([
+            'Access-Control-Allow-Origin'      => '*',
+            'Access-Control-Allow-Credentials' => 'true',
+        ]);*/
+        return $response;
+    }
+    
+    /**
+     * 外部通用 判断签名是否正确.
+     */
+    public function handle($request, Closure $next)
+    {
+        $timestamp = !empty($request->get('timestamp'))?$request->get('timestamp'):'';
+        $sign = !empty($request->get('sign'))?$request->get('sign'):'';
+        $response = $next($request);
+
+        return $response;
+    }
+    
+    public function sign($params, $key)
+    {
+        $data = $params;
+        //签名步骤一:按字典序排序参数
+        ksort($data);
+        $buff = "";
+        foreach ($data as $k => $v) {
+            if ($v != null && $k !== "sign" && $v !== "" && !is_array($v)) {
+                $buff .= $k . "=" . $v . "&";
+            }
+        }
+        $buff = trim($buff, "&");
+        //签名步骤二:在string后加入KEY
+        $string = $buff . "&key=" . $key;
+//        \Log::info('Seconds_string:'.$string.'Seconds_key:'.$key);
+        //签名步骤三:MD5加密
+        $string = md5($string);
+        //签名步骤四:所有字符转为大写
+        $result = strtoupper($string);
+        
+        return $result;
+    }
+}

+ 42 - 0
app/middleware/StaticFile.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace app\middleware;
+
+use Webman\MiddlewareInterface;
+use Webman\Http\Response;
+use Webman\Http\Request;
+
+/**
+ * Class StaticFile
+ * @package app\middleware
+ */
+class StaticFile implements MiddlewareInterface
+{
+    public function process(Request $request, callable $next): Response
+    {
+        // Access to files beginning with. Is prohibited
+        if (strpos($request->path(), '/.') !== false) {
+            return response('<h1>403 forbidden</h1>', 403);
+        }
+        /** @var Response $response */
+        $response = $next($request);
+        // Add cross domain HTTP header
+        /*$response->withHeaders([
+            'Access-Control-Allow-Origin'      => '*',
+            'Access-Control-Allow-Credentials' => 'true',
+        ]);*/
+        return $response;
+    }
+}

+ 48 - 0
app/model/DouyinTrack.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace App\model;
+
+//use Illuminate\Database\Eloquent\Model;
+use support\Model;
+
+/**
+ * Class DouyinTrack
+ * @package App\Models
+ * @version April 8, 2020, 2:05 pm CST
+ *
+ * @property string $link_source
+ * @property string $link
+ * @property string $source
+ * @property string $ip_ua
+ * @property string $distribution_channel_id
+ * @property string $click_id
+ * @property string $ua
+ * @property string $ip
+ * @property string $log_time
+ * @property string $adid
+ */
+class DouyinTrack extends Model
+{
+    use TableSuffix;
+
+    public $table = 'douyin_tracks';
+
+    const CREATED_AT = 'created_at';
+    const UPDATED_AT = 'updated_at';
+    
+    protected $connection = 'mysql';
+
+    public $fillable = [
+        'link_source',
+        'link',
+        'source',
+        'ip_ua',
+        'distribution_channel_id',
+        'click_id',
+        'ua',
+        'ip',
+        'log_time',
+        'adid',
+        'callback',
+    ];
+}

+ 32 - 0
app/model/TableSuffix.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\model;
+
+trait TableSuffix
+{
+    private static $suffix;
+
+    public static function suffix($suffix)
+    {
+        static::$suffix = $suffix;
+    }
+
+    public function __construct(array $attributes = [])
+    {
+        $this->table .= static::$suffix;
+        parent::__construct($attributes);
+    }
+
+    public static function model(int $month = 0)
+    {
+        $suffix = '';
+        if ($month == 0) {
+            $suffix = date('Ym');
+        } else {
+            $suffix = date('Ym', strtotime("last day of {$month} month"));
+        }
+        self::suffix($suffix);
+        $model = new self;
+        return $model;
+    }
+}

+ 29 - 0
app/model/Test.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace app\model;
+
+use support\Model;
+
+class Test extends Model
+{
+    /**
+     * The table associated with the model.
+     *
+     * @var string
+     */
+    protected $table = 'test';
+
+    /**
+     * The primary key associated with the table.
+     *
+     * @var string
+     */
+    protected $primaryKey = 'id';
+
+    /**
+     * Indicates if the model should be timestamped.
+     *
+     * @var bool
+     */
+    public $timestamps = false;
+}

+ 23 - 0
app/queue/redis/TiktokTrack.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace app\queue\redis;
+
+use Webman\RedisQueue\Consumer;
+use App\services\TrackService;
+use support\facade\Logger;
+
+class TiktokTrack implements Consumer
+{
+    // 要消费的队列名
+    public $queue = 'tiktok_track';
+
+    // 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
+    public $connection = 'default';
+
+    // 消费
+    public function consume($data)
+    {
+        //Logger::app(time());
+        TrackService::cronDeal();
+    }
+}

+ 11 - 0
app/services/BaseService.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace App\services;
+
+
+class BaseService
+{
+    public static function test (){
+        return 111;
+    }
+}

+ 96 - 0
app/services/TrackService.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace App\services;
+
+use App\model\DouyinTrack;
+use support\Redis;
+use support\Log;
+use support\facade\Logger;
+
+class TrackService 
+{
+    
+    /**
+     *  把请求信息,放到redis队列中
+     */
+    public static function push ($params){
+        $dycallback = getProp($params,'dycallback');
+        switch ($dycallback) {
+            case 1:
+                //投递队列
+                $params['log_time'] = date('Y-m-d H:i:s',time());
+                Redis::lpush('tiktok_track',json_encode($params));
+                // if (!Redis::get('tiktok_track_deal_lock')) { // 锁住
+                //     Redis::setEx('tiktok_track_deal_lock',1,1);
+                //     self::TiktokRedisDeal();
+                // }
+                break;
+            default:
+                // code...
+                break;
+        }
+    }
+    
+    /**
+     *  定时任务批量处理
+     */
+    public static function cronDeal (){
+        // 处理巨量平台的
+        self::TiktokRedisDeal();
+    }
+    
+    /**
+     *  批量处理 巨量 平台发送过来的检测链接 数据信息。-避免过多的install
+     */
+    public static function TiktokRedisDeal ($data= []){
+        if (!$data) {
+            $len = Redis::llen('tiktok_track');
+            //Logger::app('tiktok_track:len'.$len);
+            if($len > 4000){
+                $len= 4000;
+            }
+            for ($i = 1; $i <= $len; $i++) {
+                 $data[] = Redis::rpop('tiktok_track');
+            }
+        }
+        if(empty($data)){
+            return false;
+        }
+        $inster = [] ;
+        $model =  DouyinTrack::model();
+        foreach ($data as $val){
+           $params = json_decode($val,true);
+           if(!getProp($params,'channel_id','')){
+               continue;
+           }
+            //整理数据+
+           $inster[] = [
+                'link' => getProp($params,'link',''),
+                'link_source' => 'tiktok',
+                'ip' => getProp($params,'ip',''),
+                'ua' => getProp($params,'ua',''),
+                'source' => 'zsy',
+                'ip_ua' => md5(getProp($params,'ip','') . getProp($params,'ua','')),
+                'distribution_channel_id' => getProp($params,'channel_id',''),
+                'log_time' => getProp($params,'log_time',''),
+                'created_at' => date('Y-m-d H:i:s',time()),
+                'adid' => getProp($params,'adid',''),
+                'callback' => getProp($params,'clickid',''),
+                //新添加
+                'advertiser_id' => getProp($params,'advertiser_id',''),
+                'campaign_id' => getProp($params,'campaign_id',''),
+                'creativeid' => getProp($params,'creativeid',''),
+                
+            ];
+            if(count($inster) >= 2000){
+                $model->insert($inster);
+                $inster = [];
+            }
+        }
+        if ($inster) {
+            $model->insert($inster);
+        }
+    }
+    
+    
+}

+ 14 - 0
app/view/index/view.html

@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="shortcut icon" href="/favicon.ico"/>
+    <title>webman</title>
+
+</head>
+<body>
+hello <?=htmlspecialchars($name)?>
+</body>
+</html>

+ 69 - 0
composer.json

@@ -0,0 +1,69 @@
+{
+  "name": "workerman/webman",
+  "type": "project",
+  "keywords": [
+    "high performance",
+    "http service"
+  ],
+  "homepage": "http://www.workerman.net",
+  "license": "MIT",
+  "description": "High performance HTTP Service Framework.",
+  "authors": [
+    {
+      "name": "walkor",
+      "email": "walkor@workerman.net",
+      "homepage": "http://www.workerman.net",
+      "role": "Developer"
+    }
+  ],
+  "support": {
+    "email": "walkor@workerman.net",
+    "issues": "https://github.com/walkor/webman/issues",
+    "forum": "http://wenda.workerman.net/",
+    "wiki": "http://workerman.net/doc/webman",
+    "source": "https://github.com/walkor/webman"
+  },
+  "require": {
+    "php": ">=7.2",
+    "workerman/webman-framework": "^1.3.14",
+    "monolog/monolog": "^2.0",
+    "psr/container": "^1.1.1",
+    "illuminate/database": "^9.17",
+    "illuminate/pagination": "^9.17",
+    "illuminate/events": "^9.20",
+    "symfony/var-dumper": "^6.1",
+    "tinywan/exception-handler": "^0.1.0",
+    "webman/console": "^1.0",
+    "hashids/hashids": "^4.1",
+    "vlucas/phpdotenv": "^5.4",
+    "illuminate/redis": "^9.20",
+    "webman/redis-queue": "^1.2",
+    "tinywan/jwt": "^1.3",
+    "workerman/crontab": "^1.0",
+    "webman/log": "^1.0",
+    "kriss/webman-logger": "^1.2"
+  },
+  "suggest": {
+    "ext-event": "For better performance. "
+  },
+  "autoload": {
+    "psr-4": {
+      "": "./",
+      "App\\": "./app"
+    },
+    "files": [
+      "./support/helpers.php"
+    ]
+  },
+  "scripts": {
+    "post-package-install": [
+      "support\\Plugin::install"
+    ],
+    "post-package-update": [
+      "support\\Plugin::install"
+    ],
+    "pre-package-uninstall": [
+      "support\\Plugin::uninstall"
+    ]
+  }
+}

文件差异内容过多而无法显示
+ 3086 - 0
composer.lock


+ 24 - 0
config/app.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use support\Request;
+
+return [
+    'debug' => true,
+    'default_timezone' => 'Asia/Shanghai',
+    'request_class' => Request::class,
+    'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public',
+    'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime',
+    'controller_suffix' => '',
+];

+ 21 - 0
config/autoload.php

@@ -0,0 +1,21 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    'files' => [
+        base_path() . '/app/functions.php',
+        base_path() . '/support/Request.php',
+        base_path() . '/support/Response.php',
+    ]
+];

+ 18 - 0
config/bootstrap.php

@@ -0,0 +1,18 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    support\bootstrap\Session::class,
+    support\bootstrap\LaravelDb::class,
+];

+ 15 - 0
config/container.php

@@ -0,0 +1,15 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return new Webman\Container;

+ 35 - 0
config/database.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+//return [];
+return [
+     // 默认数据库
+     'default' => 'mysql',
+     // 各种数据库配置
+     'connections' => [
+         'mysql' => [
+             'driver'      => 'mysql',
+             'host'        =>   getenv('DB_HOST'),
+             'port'        =>   getenv('DB_PORT'),
+             'database'    =>   getenv('DB_DATABASE'),
+             'username'    =>   getenv('DB_USERNAME'),
+             'password'    =>   getenv('DB_PASSWORD'),
+             'unix_socket' =>  '',
+             'charset'     => 'utf8',
+             'collation'   => 'utf8_unicode_ci',
+             'prefix'      => '',
+             'strict'      => true,
+             'engine'      => null,
+         ]
+    ]
+ ];

+ 15 - 0
config/dependence.php

@@ -0,0 +1,15 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [];

+ 17 - 0
config/exception.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    '' => support\exception\Handler::class,
+];

+ 37 - 0
config/log.php

@@ -0,0 +1,37 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use support\facade\Logger;
+
+return array_merge(
+    [
+        'default' => [
+            'handlers' => [
+                [
+                    'class' => Monolog\Handler\RotatingFileHandler::class,
+                    'constructor' => [
+                        runtime_path() . '/logs/webman.log',
+                        7, //$maxFiles
+                        Monolog\Logger::DEBUG,
+                    ],
+                    'formatter' => [
+                        'class' => Monolog\Formatter\LineFormatter::class,
+                        'constructor' => [null, 'Y-m-d H:i:s', true],
+                    ],
+                ]
+            ],
+        ],
+    ],
+    Logger::getLogChannelConfigs(), // merge 这个
+);

+ 15 - 0
config/middleware.php

@@ -0,0 +1,15 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [];

+ 4 - 0
config/plugin/kriss/webman-logger/app.php

@@ -0,0 +1,4 @@
+<?php
+return [
+    'enable' => true,
+];

+ 88 - 0
config/plugin/kriss/webman-logger/log-channel.php

@@ -0,0 +1,88 @@
+<?php
+
+use Kriss\WebmanLogger\Formatter\ChannelFormatter;
+use Kriss\WebmanLogger\Formatter\ChannelMixedFormatter;
+use Kriss\WebmanLogger\Processors\RequestRouteProcessor;
+use Kriss\WebmanLogger\Processors\RequestUidProcessor;
+use Kriss\WebmanLogger\Processors\CurrentUserProcessor;
+use Monolog\Processor\PsrLogMessageProcessor;
+
+return [
+    // channels
+    'channels' => [
+        'app',
+        'sql',
+    ],
+    // 记录等级,仅大于设定等级的日志才会真实写入日志文件
+    'levels' => [
+        // 默认等级
+        'default' => config('app.debug') ? 'debug' : 'info',
+        // 特殊的等级
+        'special' => [
+            //'channelName' => 'info',
+        ],
+    ],
+    // processors
+    'processors' => function () {
+        return [
+            new PsrLogMessageProcessor('Y-m-d H:i:s'),
+            new RequestRouteProcessor(),
+            new CurrentUserProcessor(function () {
+                // 返回当前用户id
+                return 0;
+            }),
+            new RequestUidProcessor(),
+        ];
+    },
+    // 模式
+    'modes' => [
+        // 按照channel分目录记录
+        'split' => [
+            'class' => Kriss\WebmanLogger\Mode\SplitMode::class,
+            'enable' => true,
+            'except_channels' => [],
+            'only_channels' => [],
+            'formatter' => [
+                'class' => ChannelFormatter::class,
+            ],
+            'max_files' => 30, // 最大文件数
+        ],
+        // 将所有channel合并到一起记录
+        'mix' => [
+            'class' => Kriss\WebmanLogger\Mode\MixMode::class,
+            'enable' => false,
+            'except_channels' => [],
+            'only_channels' => [],
+            'formatter' => [
+                'class' => ChannelMixedFormatter::class,
+            ],
+            'max_files' => 30, // 最大文件数
+            'name' => 'channelMixed', // 合并时的日志文件名
+        ],
+        // 控制台输出
+        'stdout' => [
+            'class' => Kriss\WebmanLogger\Mode\StdoutMode::class,
+            'enable' => false,
+            'except_channels' => [],
+            'only_channels' => [],
+            'formatter' => [
+                'class' => ChannelMixedFormatter::class,
+            ],
+        ],
+        // 输出到 redis
+        'redis' => [
+            'class' => Kriss\WebmanLogger\Mode\RedisMode::class,
+            'enable' => false,
+            'except_channels' => [],
+            'only_channels' => [],
+            'formatter' => [
+                'class' => ChannelFormatter::class,
+            ],
+            'redis' => function () {
+                return support\Redis::connection('default')->client();
+            },
+            'redis_key_prefix' => 'webmanLog:',
+            'redis_size' => 0,
+        ],
+    ],
+];

+ 7 - 0
config/plugin/kriss/webman-logger/middleware.php

@@ -0,0 +1,7 @@
+<?php
+
+return [
+    '' => [
+        Kriss\WebmanLogger\Middleware\RequestUid::class,
+    ],
+];

+ 44 - 0
config/plugin/tinywan/exception-handler/app.php

@@ -0,0 +1,44 @@
+<?php
+
+return [
+    'enable' => true,
+    // 错误异常配置
+    'exception_handler' => [
+        // 不需要记录错误日志
+        'dont_report' => [
+            Tinywan\ExceptionHandler\Exception\BadRequestHttpException::class,
+            Tinywan\ExceptionHandler\Exception\UnauthorizedHttpException::class,
+            Tinywan\ExceptionHandler\Exception\ForbiddenHttpException::class,
+            Tinywan\ExceptionHandler\Exception\NotFoundHttpException::class,
+            Tinywan\ExceptionHandler\Exception\RouteNotFoundException::class,
+            Tinywan\ExceptionHandler\Exception\TooManyRequestsHttpException::class,
+            Tinywan\ExceptionHandler\Exception\ServerErrorHttpException::class,
+            Tinywan\Validate\Exception\ValidateException::class,
+            Tinywan\Jwt\Exception\JwtTokenException::class
+        ],
+        // 自定义HTTP状态码
+        'status' => [
+            'validate' => 400, // 验证器异常
+            'jwt_token' => 401, // JWT 认证失败
+            'jwt_token_expired' => 402, // JWT 令牌过期
+            'server_error' => 500, // 服务器内部错误
+        ],
+        // 自定义响应消息
+        'body' => [
+            'code' => 0,
+            'msg' => '服务器内部异常',
+            'data' => null
+        ],
+        // 事件
+        'event' => [
+            'enable' => false,
+            // 钉钉机器人
+            'ding_talk' => [
+                'accessToken' => 'xxxxxxxxxxxxxxxx',
+                'secret' => 'xxxxxxxxxxxxxxxx',
+                'title' => '钉钉机器人异常通知',
+            ]
+        ],
+    ],
+
+];

+ 69 - 0
config/plugin/tinywan/jwt/app.php

@@ -0,0 +1,69 @@
+<?php
+
+return [
+    'enable' => true,
+    'jwt' => [
+        // 算法类型 HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、Ed25519
+        'algorithms' => 'HS256',
+        // access令牌秘钥
+        'access_secret_key' => '2022d3d3LmJq',
+        // access令牌过期时间,单位:秒。默认 2 小时
+        'access_exp' => 7200,
+        // refresh令牌秘钥
+        'refresh_secret_key' => '2022KTxigxc9o50c',
+        // refresh令牌过期时间,单位:秒。默认 7 天
+        'refresh_exp' => 604800,
+        // refresh 令牌是否禁用,默认不禁用 false
+        'refresh_disable' => false,
+        // 令牌签发者
+        'iss' => 'webman.tinywan.cn',
+        // 时钟偏差冗余时间,单位秒。建议这个余地应该不大于几分钟。
+        'leeway' => 60,
+        // 单设备登录
+        'is_single_device' => false,
+        // 缓存令牌时间,单位:秒。默认 7 天
+        'cache_token_ttl' => 604800,
+        // 缓存令牌前缀
+        'cache_token_pre' => 'JWT:TOKEN:',
+        // 用户信息模型
+        'user_model' => function($uid){
+            return [];
+        },
+
+        /**
+         * access令牌私钥
+         */
+        'access_private_key' => <<<EOD
+-----BEGIN RSA PRIVATE KEY-----
+...
+-----END RSA PRIVATE KEY-----
+EOD,
+
+        /**
+         * access令牌公钥
+         */
+        'access_public_key' => <<<EOD
+-----BEGIN PUBLIC KEY-----
+...
+-----END PUBLIC KEY-----
+EOD,
+
+        /**
+         * refresh令牌私钥
+         */
+        'refresh_private_key' => <<<EOD
+-----BEGIN RSA PRIVATE KEY-----
+...
+-----END RSA PRIVATE KEY-----
+EOD,
+
+        /**
+         * refresh令牌公钥
+         */
+        'refresh_public_key' => <<<EOD
+-----BEGIN PUBLIC KEY-----
+...
+-----END PUBLIC KEY-----
+EOD,
+    ],
+];

+ 18 - 0
config/plugin/webman/console/app.php

@@ -0,0 +1,18 @@
+<?php
+return [
+    'enable'            => true,
+
+    'phar_file_output_dir'    => BASE_PATH . DIRECTORY_SEPARATOR . 'build',
+
+    'phar_filename'     => 'webman.phar',
+
+    'signature_algorithm'=> Phar::SHA256, //set the signature algorithm for a phar and apply it. The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL.
+
+    'private_key_file'  => '', // The file path for certificate or OpenSSL private key file.
+
+    //'exclude_pattern'   => '#^(?!.*(config/plugin/webman/console/app.php|webman/console/src/Commands/(PharPackCommand.php|ReloadCommand.php)|LICENSE|composer.json|.github|.idea|doc|docs|.git|.setting|runtime|test|test_old|tests|Tests|vendor-bin|.md))(.*)$#',
+
+    'exclude_files'     => [
+        '.env', 'LICENSE', 'composer.json', 'composer.lock','start.php'
+    ]
+];

+ 4 - 0
config/plugin/webman/log/app.php

@@ -0,0 +1,4 @@
+<?php
+return [
+    'enable' => false,
+];

+ 21 - 0
config/plugin/webman/log/middleware.php

@@ -0,0 +1,21 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use Webman\Log\Middleware;
+
+return [
+    '' => [
+        Middleware::class
+    ]
+];

+ 4 - 0
config/plugin/webman/redis-queue/app.php

@@ -0,0 +1,4 @@
+<?php
+return [
+    'enable' => true,
+];

+ 7 - 0
config/plugin/webman/redis-queue/command.php

@@ -0,0 +1,7 @@
+<?php
+
+use Webman\RedisQueue\Command\MakeConsumerCommand;
+
+return [
+    MakeConsumerCommand::class
+];

+ 11 - 0
config/plugin/webman/redis-queue/process.php

@@ -0,0 +1,11 @@
+<?php
+return [
+    'consumer'  => [
+        'handler'     => Webman\RedisQueue\Process\Consumer::class,
+        'count'       => 16, // 可以设置多进程同时消费
+        'constructor' => [
+            // 消费者类目录
+            'consumer_dir' => app_path() . '/queue/redis'
+        ]
+    ]
+];

+ 13 - 0
config/plugin/webman/redis-queue/redis.php

@@ -0,0 +1,13 @@
+<?php
+return [
+    'default' => [
+        'host' => 'redis://127.0.0.1:6379',
+        'options' => [
+            'auth' => null,       // 密码,字符串类型,可选参数
+            'db' => 0,            // 数据库
+            'prefix' => '',       // key 前缀
+            'max_attempts'  => 3, // 消费失败后,重试次数
+            'retry_seconds' => 3, // 重试间隔,单位秒
+        ]
+    ],
+];

+ 40 - 0
config/process.php

@@ -0,0 +1,40 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+
+return [
+    // File update detection and automatic reload
+    'monitor' => [
+        'handler' => process\Monitor::class,
+        'reloadable' => false,
+        'constructor' => [
+            // Monitor these directories
+            'monitor_dir' => [
+                app_path(),
+                config_path(),
+                base_path() . '/process',
+                base_path() . '/support',
+                base_path() . '/resource',
+                base_path() . '/.env',
+            ],
+            // Files with these suffixes will be monitored
+            'monitor_extensions' => [
+                'php', 'html', 'htm', 'env'
+            ]
+        ]
+    ],
+    'cron' => [
+        'handler'  => process\Task::class
+    ]
+];

+ 22 - 0
config/redis.php

@@ -0,0 +1,22 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    'default' => [
+        'host' => getenv('REDIS_HOST'),
+        'password' => null,
+        'port' => getenv('REDIS_PORT'),
+        'database' => 0,
+    ],
+];

+ 20 - 0
config/route.php

@@ -0,0 +1,20 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use Webman\Route;
+
+Route::any('/track', [app\controller\Track::class, 'index']);
+
+
+

+ 31 - 0
config/server.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    'listen' => 'http://0.0.0.0:'.getenv('LISTEN_PORT'),
+    'transport' => 'tcp',
+    'context' => [],
+    'name' => 'webman',
+    'count' => cpu_count() * 5,
+    'user' => '',
+    'group' => '',
+    'reusePort' => false,
+    'event_loop' => '',
+    'stop_timeout' => 2,
+    'pid_file' => runtime_path() . '/webman.pid',
+    'status_file' => runtime_path() . '/webman.status',
+    'stdout_file' => runtime_path() . '/logs/stdout.log',
+    'log_file' => runtime_path() . '/logs/workerman.log',
+    'max_package_size' => 10 * 1024 * 1024
+];

+ 61 - 0
config/session.php

@@ -0,0 +1,61 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+
+    'type' => 'file', // or redis or redis_cluster
+
+    'handler' => Webman\FileSessionHandler::class,
+
+    'config' => [
+        'file' => [
+            'save_path' => runtime_path() . '/sessions',
+        ],
+        'redis' => [
+            'host' => '127.0.0.1',
+            'port' => 6379,
+            'auth' => '',
+            'timeout' => 2,
+            'database' => '',
+            'prefix' => 'redis_session_',
+        ],
+        'redis_cluster' => [
+            'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'],
+            'timeout' => 2,
+            'auth' => '',
+            'prefix' => 'redis_session_',
+        ]
+    ],
+
+    'session_name' => 'PHPSID',
+    
+    'auto_update_timestamp' => false,
+
+    'lifetime' => 7*24*60*60,
+
+    'cookie_lifetime' => 365*24*60*60,
+
+    'cookie_path' => '/',
+
+    'domain' => '',
+    
+    'http_only' => true,
+
+    'secure' => false,
+    
+    'same_site' => '',
+
+    'gc_probability' => [1, 1000],
+
+];

+ 23 - 0
config/static.php

@@ -0,0 +1,23 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+/**
+ * Static file settings
+ */
+return [
+    'enable' => true,
+    'middleware' => [     // Static file Middleware
+        //app\middleware\StaticFile::class,
+    ],
+];

+ 25 - 0
config/translation.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+/**
+ * Multilingual configuration
+ */
+return [
+    // Default language
+    'locale' => 'zh_CN',
+    // Fallback language
+    'fallback_locale' => ['zh_CN', 'en'],
+    // Folder where language files are stored
+    'path' => base_path() . '/resource/translations',
+];

+ 22 - 0
config/view.php

@@ -0,0 +1,22 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use support\view\Raw;
+use support\view\Twig;
+use support\view\Blade;
+use support\view\ThinkPHP;
+
+return [
+    'handler' => Raw::class
+];

+ 195 - 0
process/Monitor.php

@@ -0,0 +1,195 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace process;
+
+use Workerman\Timer;
+use Workerman\Worker;
+
+/**
+ * Class FileMonitor
+ * @package process
+ */
+class Monitor
+{
+    /**
+     * @var array
+     */
+    protected $_paths = [];
+
+    /**
+     * @var array
+     */
+    protected $_extensions = [];
+
+    /**
+     * FileMonitor constructor.
+     * @param $monitor_dir
+     * @param $monitor_extensions
+     * @param $memory_limit
+     */
+    public function __construct($monitor_dir, $monitor_extensions, $memory_limit = null)
+    {
+        $this->_paths = (array)$monitor_dir;
+        $this->_extensions = $monitor_extensions;
+        if (!Worker::getAllWorkers()) {
+            return;
+        }
+        $disable_functions = explode(',', ini_get('disable_functions'));
+        if (in_array('exec', $disable_functions, true)) {
+            echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n";
+        } else {
+            if (!Worker::$daemonize) {
+                Timer::add(1, function () {
+                    $this->checkAllFilesChange();
+                });
+            }
+        }
+
+        $memory_limit = $this->getMemoryLimit($memory_limit);
+        if ($memory_limit && DIRECTORY_SEPARATOR === '/') {
+            Timer::add(60, [$this, 'checkMemory'], [$memory_limit]);
+        }
+    }
+
+    /**
+     * @param $monitor_dir
+     */
+    public function checkFilesChange($monitor_dir)
+    {
+        static $last_mtime, $too_many_files_check;
+        if (!$last_mtime) {
+            $last_mtime = time();
+        }
+        clearstatcache();
+        if (!is_dir($monitor_dir)) {
+            if (!is_file($monitor_dir)) {
+                return;
+            }
+            $iterator = [new \SplFileInfo($monitor_dir)];
+        } else {
+            // recursive traversal directory
+            $dir_iterator = new \RecursiveDirectoryIterator($monitor_dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS);
+            $iterator = new \RecursiveIteratorIterator($dir_iterator);
+        }
+        $count = 0;
+        foreach ($iterator as $file) {
+            $count ++;
+            /** var SplFileInfo $file */
+            if (is_dir($file)) {
+                continue;
+            }
+            // check mtime
+            if ($last_mtime < $file->getMTime() && in_array($file->getExtension(), $this->_extensions, true)) {
+                $var = 0;
+                exec(PHP_BINARY . " -l " . $file, $out, $var);
+                if ($var) {
+                    $last_mtime = $file->getMTime();
+                    continue;
+                }
+                $last_mtime = $file->getMTime();
+                echo $file . " update and reload\n";
+                // send SIGUSR1 signal to master process for reload
+                if (DIRECTORY_SEPARATOR === '/') {
+                    posix_kill(posix_getppid(), SIGUSR1);
+                } else {
+                    return true;
+                }
+                break;
+            }
+        }
+        if (!$too_many_files_check && $count > 1000) {
+            echo "Monitor: There are too many files ($count files) in $monitor_dir which makes file monitoring very slow\n";
+            $too_many_files_check = 1;
+        }
+    }
+
+    /**
+     * @return bool
+     */
+    public function checkAllFilesChange()
+    {
+        foreach ($this->_paths as $path) {
+            if ($this->checkFilesChange($path)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param $memory_limit
+     * @return void
+     */
+    public function checkMemory($memory_limit)
+    {
+        $ppid = posix_getppid();
+        $children_file = "/proc/$ppid/task/$ppid/children";
+        if (!is_file($children_file) || !($children = file_get_contents($children_file))) {
+            return;
+        }
+        foreach (explode(' ', $children) as $pid) {
+            $pid = (int)$pid;
+            $status_file = "/proc/$pid/status";
+            if (!is_file($status_file) || !($status = file_get_contents($status_file))) {
+                continue;
+            }
+            $mem = 0;
+            if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) {
+                $mem = $match[1];
+            }
+            $mem = (int)($mem / 1024);
+            if ($mem >= $memory_limit) {
+                posix_kill($pid, SIGINT);
+            }
+        }
+    }
+
+    /**
+     * Get memory limit
+     * @return float
+     */
+    protected function getMemoryLimit($memory_limit)
+    {
+        if ($memory_limit === 0) {
+            return 0;
+        }
+        $use_php_ini = false;
+        if (!$memory_limit) {
+            $memory_limit = ini_get('memory_limit');
+            $use_php_ini = true;
+        }
+
+        if ($memory_limit == -1) {
+            return 0;
+        }
+        $unit = $memory_limit[strlen($memory_limit) - 1];
+        if ($unit == 'G') {
+            $memory_limit = 1024 * (int)$memory_limit;
+        } else if ($unit == 'M') {
+            $memory_limit = (int)$memory_limit;
+        } else if ($unit == 'K') {
+            $memory_limit = (int)($memory_limit / 1024);
+        } else {
+            $memory_limit = (int)($memory_limit / (1024 * 1024));
+        }
+        if ($memory_limit < 30) {
+            $memory_limit = 30;
+        }
+        if ($use_php_ini) {
+            $memory_limit = (int)(0.8 * $memory_limit);
+        }
+        return $memory_limit;
+    }
+}

+ 28 - 0
process/Task.php

@@ -0,0 +1,28 @@
+<?php
+namespace process;
+
+use Workerman\Crontab\Crontab;
+use support\Log;
+use App\services\TrackService;
+use Webman\RedisQueue\Redis;
+
+class Task
+{
+    public function onWorkerStart()
+    {
+        // 每一秒执行
+        new Crontab('*/1 * * * * *', function(){
+            //TrackService::cronDeal();
+            //echo date('Y-m-d H:i:s')."\n";
+             // 队列名
+            $queue = 'tiktok_track';
+            // 投递消息
+            Redis::send($queue, []);
+            // usleep(300000);
+            // Redis::send($queue, []);
+            // usleep(300000);
+            // Redis::send($queue, []);
+        });
+        
+    }
+}

+ 12 - 0
public/404.html

@@ -0,0 +1,12 @@
+<html>
+<head>
+    <title>404 Not Found - webman</title>
+</head>
+<body>
+<center>
+    <h1>404 Not Found</h1>
+</center>
+<hr>
+<center><a href="https://www.workerman.net">webman</a></center>
+</body>
+</html>

二进制
public/favicon.ico


+ 4 - 0
runtime/.gitignore

@@ -0,0 +1,4 @@
+*
+!logs
+!views
+!.gitignore

+ 2 - 0
runtime/logs/.gitignore

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

+ 2 - 0
runtime/views/.gitignore

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

+ 117 - 0
start.php

@@ -0,0 +1,117 @@
+#!/usr/bin/env php
+<?php
+require_once __DIR__ . '/vendor/autoload.php';
+
+use Workerman\Worker;
+use Workerman\Protocols\Http;
+use Workerman\Connection\TcpConnection;
+use Webman\App;
+use Webman\Config;
+use Webman\Route;
+use Webman\Middleware;
+use Dotenv\Dotenv;
+use support\Request;
+use support\Log;
+use support\Container;
+
+ini_set('display_errors', 'on');
+error_reporting(E_ALL);
+
+if (class_exists('Dotenv\Dotenv') && file_exists(base_path() . '/.env')) {
+    if (method_exists('Dotenv\Dotenv', 'createUnsafeImmutable')) {
+        Dotenv::createUnsafeImmutable(base_path())->load();
+    } else {
+        Dotenv::createMutable(base_path())->load();
+    }
+}
+
+Config::load(config_path(), ['route', 'container']);
+
+$error_reporting = config('app.error_reporting');
+if (isset($error_reporting)) {
+    error_reporting($error_reporting);
+}
+
+if ($timezone = config('app.default_timezone')) {
+    date_default_timezone_set($timezone);
+}
+
+$runtime_logs_path = runtime_path() . DIRECTORY_SEPARATOR . 'logs';
+if ( !file_exists($runtime_logs_path) || !is_dir($runtime_logs_path) ) {
+    if (!mkdir($runtime_logs_path,0777,true)) {
+        throw new \RuntimeException("Failed to create runtime logs directory. Please check the permission.");
+    }
+}
+
+$runtime_views_path = runtime_path() . DIRECTORY_SEPARATOR . 'views';
+if ( !file_exists($runtime_views_path) || !is_dir($runtime_views_path) ) {
+    if (!mkdir($runtime_views_path,0777,true)) {
+        throw new \RuntimeException("Failed to create runtime views directory. Please check the permission.");
+    }
+}
+
+Worker::$onMasterReload = function () {
+    if (function_exists('opcache_get_status')) {
+        if ($status = opcache_get_status()) {
+            if (isset($status['scripts']) && $scripts = $status['scripts']) {
+                foreach (array_keys($scripts) as $file) {
+                    opcache_invalidate($file, true);
+                }
+            }
+        }
+    }
+};
+
+$config = config('server');
+Worker::$pidFile = $config['pid_file'];
+Worker::$stdoutFile = $config['stdout_file'];
+Worker::$logFile = $config['log_file'];
+Worker::$eventLoopClass = $config['event_loop'] ?? '';
+TcpConnection::$defaultMaxPackageSize = $config['max_package_size'] ?? 10 * 1024 * 1024;
+if (property_exists(Worker::class, 'statusFile')) {
+    Worker::$statusFile = $config['status_file'] ?? '';
+}
+if (property_exists(Worker::class, 'stopTimeout')) {
+    Worker::$stopTimeout = $config['stop_timeout'] ?? 2;
+}
+
+if ($config['listen']) {
+    $worker = new Worker($config['listen'], $config['context']);
+    $property_map = [
+        'name',
+        'count',
+        'user',
+        'group',
+        'reusePort',
+        'transport',
+        'protocol'
+    ];
+    foreach ($property_map as $property) {
+        if (isset($config[$property])) {
+            $worker->$property = $config[$property];
+        }
+    }
+
+    $worker->onWorkerStart = function ($worker) {
+        require_once base_path() . '/support/bootstrap.php';
+        $app = new App($worker, Container::instance(), Log::channel('default'), app_path(), public_path());
+        Http::requestClass(config('app.request_class', config('server.request_class', Request::class)));
+        $worker->onMessage = [$app, 'onMessage'];
+    };
+}
+
+// Windows does not support custom processes.
+if (\DIRECTORY_SEPARATOR === '/') {
+    foreach (config('process', []) as $process_name => $config) {
+        worker_start($process_name, $config);
+    }
+    foreach (config('plugin', []) as $firm => $projects) {
+        foreach ($projects as $name => $project) {
+            foreach ($project['process'] ?? [] as $process_name => $config) {
+                worker_start("plugin.$firm.$name.$process_name", $config);
+            }
+        }
+    }
+}
+
+Worker::runAll();

+ 55 - 0
support/Plugin.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace support;
+
+class Plugin
+{
+    public static function install($event)
+    {
+        static::findHepler();
+        $operation = $event->getOperation();
+        $autoload = method_exists($operation, 'getPackage') ? $operation->getPackage()->getAutoload() : $operation->getTargetPackage()->getAutoload();
+        if (!isset($autoload['psr-4'])) {
+            return;
+        }
+        $namespace = key($autoload['psr-4']);
+        $install_function = "\\{$namespace}Install::install";
+        $plugin_const = "\\{$namespace}Install::WEBMAN_PLUGIN";
+        if (defined($plugin_const) && is_callable($install_function)) {
+            $install_function();
+        }
+    }
+
+    public static function update($event)
+    {
+        static::install($event);
+    }
+
+    public static function uninstall($event)
+    {
+        static::findHepler();
+        $autoload = $event->getOperation()->getPackage()->getAutoload();
+        if (!isset($autoload['psr-4'])) {
+            return;
+        }
+        $namespace = key($autoload['psr-4']);
+        $uninstall_function = "\\{$namespace}Install::uninstall";
+        $plugin_const = "\\{$namespace}Install::WEBMAN_PLUGIN";
+        if (defined($plugin_const) && is_callable($uninstall_function)) {
+            $uninstall_function();
+        }
+    }
+
+    protected static function findHepler()
+    {
+        // Plugin.php in vendor
+        $file = __DIR__ . '/../../../../../support/helpers.php';
+        if (is_file($file)) {
+            require_once $file;
+            return;
+        }
+        // Plugin.php in webman
+        require_once __DIR__ . '/helpers.php';
+    }
+
+}

+ 24 - 0
support/Request.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace support;
+
+/**
+ * Class Request
+ * @package support
+ */
+class Request extends \Webman\Http\Request
+{
+
+}

+ 24 - 0
support/Response.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace support;
+
+/**
+ * Class Response
+ * @package support
+ */
+class Response extends \Webman\Http\Response
+{
+
+}

+ 90 - 0
support/bootstrap.php

@@ -0,0 +1,90 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use Dotenv\Dotenv;
+use support\Container;
+use Webman\Config;
+use Webman\Route;
+use Webman\Middleware;
+
+$worker = $worker ?? null;
+
+if ($timezone = config('app.default_timezone')) {
+    date_default_timezone_set($timezone);
+}
+
+set_error_handler(function ($level, $message, $file = '', $line = 0, $context = []) {
+    if (error_reporting() & $level) {
+        throw new ErrorException($message, 0, $level, $file, $line);
+    }
+});
+
+if ($worker) {
+    register_shutdown_function(function ($start_time) {
+        if (time() - $start_time <= 1) {
+            sleep(1);
+        }
+    }, time());
+}
+
+if (class_exists('Dotenv\Dotenv') && file_exists(base_path() . '/.env')) {
+    if (method_exists('Dotenv\Dotenv', 'createUnsafeImmutable')) {
+        Dotenv::createUnsafeImmutable(base_path())->load();
+    } else {
+        Dotenv::createMutable(base_path())->load();
+    }
+}
+
+Config::reload(config_path(), ['route', 'container']);
+
+foreach (config('plugin', []) as $firm => $projects) {
+    foreach ($projects as $name => $project) {
+        foreach ($project['autoload']['files'] ?? [] as $file) {
+            include_once $file;
+        }
+    }
+}
+
+foreach (config('autoload.files', []) as $file) {
+    include_once $file;
+}
+
+$container = Container::instance();
+Route::container($container);
+Middleware::container($container);
+
+Middleware::load(config('middleware', []));
+foreach (config('plugin', []) as $firm => $projects) {
+    foreach ($projects as $name => $project) {
+        Middleware::load($project['middleware'] ?? []);
+    }
+}
+Middleware::load(['__static__' => config('static.middleware', [])]);
+
+foreach (config('bootstrap', []) as $class_name) {
+    /** @var \Webman\Bootstrap $class_name */
+    $class_name::start($worker);
+}
+
+foreach (config('plugin', []) as $firm => $projects) {
+    foreach ($projects as $name => $project) {
+        foreach ($project['bootstrap'] ?? [] as $class_name) {
+            /** @var \Webman\Bootstrap $class_name */
+            $class_name::start($worker);
+        }
+    }
+}
+
+Route::load(config_path());
+

+ 11 - 0
support/facade/Logger.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace support\facade;
+
+/**
+ * @method static void app($msg, string $type = 'info', array $context = [])
+ * @method static void sql($msg, string $type = 'info', array $context = [])
+ */
+class Logger extends \Kriss\WebmanLogger\Logger
+{
+}

+ 480 - 0
support/helpers.php

@@ -0,0 +1,480 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use support\Request;
+use support\Response;
+use support\Translation;
+use support\Container;
+use support\view\Raw;
+use support\view\Blade;
+use support\view\ThinkPHP;
+use support\view\Twig;
+use Workerman\Worker;
+use Webman\App;
+use Webman\Config;
+use Webman\Route;
+
+// Phar support.
+if (is_phar()) {
+    define('BASE_PATH', dirname(__DIR__));
+} else {
+    define('BASE_PATH', realpath(__DIR__ . '/../'));
+}
+define('WEBMAN_VERSION', '1.3.0');
+
+/**
+ * @param $return_phar
+ * @return false|string
+ */
+function base_path($return_phar = true)
+{
+    static $real_path = '';
+    if (!$real_path) {
+        $real_path = is_phar() ? dirname(Phar::running(false)) : BASE_PATH;
+    }
+    return $return_phar ? BASE_PATH : $real_path;
+}
+
+/**
+ * @return string
+ */
+function app_path()
+{
+    return BASE_PATH . DIRECTORY_SEPARATOR . 'app';
+}
+
+/**
+ * @return string
+ */
+function public_path()
+{
+    static $path = '';
+    if (!$path) {
+        $path = config('app.public_path', BASE_PATH . DIRECTORY_SEPARATOR . 'public');
+    }
+    return $path;
+}
+
+/**
+ * @return string
+ */
+function config_path()
+{
+    return BASE_PATH . DIRECTORY_SEPARATOR . 'config';
+}
+
+/**
+ * Phar support.
+ * Compatible with the 'realpath' function in the phar file.
+ *
+ * @return string
+ */
+function runtime_path()
+{
+    static $path = '';
+    if (!$path) {
+        $path = config('app.runtime_path', BASE_PATH . DIRECTORY_SEPARATOR . 'runtime');
+    }
+    return $path;
+}
+
+/**
+ * @param int $status
+ * @param array $headers
+ * @param string $body
+ * @return Response
+ */
+function response($body = '', $status = 200, $headers = array())
+{
+    return new Response($status, $headers, $body);
+}
+
+/**
+ * @param $data
+ * @param int $options
+ * @return Response
+ */
+function json($data, $options = JSON_UNESCAPED_UNICODE)
+{
+    return new Response(200, ['Content-Type' => 'application/json'], json_encode($data, $options));
+}
+
+/**
+ * @param $xml
+ * @return Response
+ */
+function xml($xml)
+{
+    if ($xml instanceof SimpleXMLElement) {
+        $xml = $xml->asXML();
+    }
+    return new Response(200, ['Content-Type' => 'text/xml'], $xml);
+}
+
+/**
+ * @param $data
+ * @param string $callback_name
+ * @return Response
+ */
+function jsonp($data, $callback_name = 'callback')
+{
+    if (!is_scalar($data) && null !== $data) {
+        $data = json_encode($data);
+    }
+    return new Response(200, [], "$callback_name($data)");
+}
+
+/**
+ * @param $location
+ * @param int $status
+ * @param array $headers
+ * @return Response
+ */
+function redirect($location, $status = 302, $headers = [])
+{
+    $response = new Response($status, ['Location' => $location]);
+    if (!empty($headers)) {
+        $response->withHeaders($headers);
+    }
+    return $response;
+}
+
+/**
+ * @param $template
+ * @param array $vars
+ * @param null $app
+ * @return Response
+ */
+function view($template, $vars = [], $app = null)
+{
+    static $handler;
+    if (null === $handler) {
+        $handler = config('view.handler');
+    }
+    return new Response(200, [], $handler::render($template, $vars, $app));
+}
+
+/**
+ * @param $template
+ * @param array $vars
+ * @param null $app
+ * @return Response
+ */
+function raw_view($template, $vars = [], $app = null)
+{
+    return new Response(200, [], Raw::render($template, $vars, $app));
+}
+
+/**
+ * @param $template
+ * @param array $vars
+ * @param null $app
+ * @return Response
+ */
+function blade_view($template, $vars = [], $app = null)
+{
+    return new Response(200, [], Blade::render($template, $vars, $app));
+}
+
+/**
+ * @param $template
+ * @param array $vars
+ * @param null $app
+ * @return Response
+ */
+function think_view($template, $vars = [], $app = null)
+{
+    return new Response(200, [], ThinkPHP::render($template, $vars, $app));
+}
+
+/**
+ * @param $template
+ * @param array $vars
+ * @param null $app
+ * @return Response
+ */
+function twig_view($template, $vars = [], $app = null)
+{
+    return new Response(200, [], Twig::render($template, $vars, $app));
+}
+
+/**
+ * @return Request
+ */
+function request()
+{
+    return App::request();
+}
+
+/**
+ * @param $key
+ * @param null $default
+ * @return mixed
+ */
+function config($key = null, $default = null)
+{
+    return Config::get($key, $default);
+}
+
+/**
+ * @param $name
+ * @param ...$parameters
+ * @return string
+ */
+function route($name, ...$parameters)
+{
+    $route = Route::getByName($name);
+    if (!$route) {
+        return '';
+    }
+
+    if (!$parameters) {
+        return $route->url();
+    }
+
+    if (is_array(current($parameters))) {
+        $parameters = current($parameters);
+    }
+
+    return $route->url($parameters);
+}
+
+/**
+ * @param mixed $key
+ * @param mixed $default
+ * @return mixed
+ */
+function session($key = null, $default = null)
+{
+    $session = request()->session();
+    if (null === $key) {
+        return $session;
+    }
+    if (\is_array($key)) {
+        $session->put($key);
+        return null;
+    }
+    if (\strpos($key, '.')) {
+        $key_array = \explode('.', $key);
+        $value = $session->all();
+        foreach ($key_array as $index) {
+            if (!isset($value[$index])) {
+                return $default;
+            }
+            $value = $value[$index];
+        }
+        return $value;
+    }
+    return $session->get($key, $default);
+}
+
+/**
+ * @param null|string $id
+ * @param array $parameters
+ * @param string|null $domain
+ * @param string|null $locale
+ * @return string
+ */
+function trans(string $id, array $parameters = [], string $domain = null, string $locale = null)
+{
+    $res = Translation::trans($id, $parameters, $domain, $locale);
+    return $res === '' ? $id : $res;
+}
+
+/**
+ * @param null|string $locale
+ * @return string
+ */
+function locale(string $locale = null)
+{
+    if (!$locale) {
+        return Translation::getLocale();
+    }
+    Translation::setLocale($locale);
+}
+
+/**
+ * 404 not found
+ *
+ * @return Response
+ */
+function not_found()
+{
+    return new Response(404, [], file_get_contents(public_path() . '/404.html'));
+}
+
+/**
+ * Copy dir.
+ * @param $source
+ * @param $dest
+ * @param bool $overwrite
+ * @return void
+ */
+function copy_dir($source, $dest, $overwrite = false)
+{
+    if (is_dir($source)) {
+        if (!is_dir($dest)) {
+            mkdir($dest);
+        }
+        $files = scandir($source);
+        foreach ($files as $file) {
+            if ($file !== "." && $file !== "..") {
+                copy_dir("$source/$file", "$dest/$file");
+            }
+        }
+    } else if (file_exists($source) && ($overwrite || !file_exists($dest))) {
+        copy($source, $dest);
+    }
+}
+
+/**
+ * Remove dir.
+ * @param $dir
+ * @return bool
+ */
+function remove_dir($dir)
+{
+    if (is_link($dir) || is_file($dir)) {
+        return unlink($dir);
+    }
+    $files = array_diff(scandir($dir), array('.', '..'));
+    foreach ($files as $file) {
+        (is_dir("$dir/$file") && !is_link($dir)) ? remove_dir("$dir/$file") : unlink("$dir/$file");
+    }
+    return rmdir($dir);
+}
+
+/**
+ * @param $worker
+ * @param $class
+ */
+function worker_bind($worker, $class)
+{
+    $callback_map = [
+        'onConnect',
+        'onMessage',
+        'onClose',
+        'onError',
+        'onBufferFull',
+        'onBufferDrain',
+        'onWorkerStop',
+        'onWebSocketConnect'
+    ];
+    foreach ($callback_map as $name) {
+        if (method_exists($class, $name)) {
+            $worker->$name = [$class, $name];
+        }
+    }
+    if (method_exists($class, 'onWorkerStart')) {
+        call_user_func([$class, 'onWorkerStart'], $worker);
+    }
+}
+
+/**
+ * @param $process_name
+ * @param $config
+ * @return void
+ */
+function worker_start($process_name, $config)
+{
+    $worker = new Worker($config['listen'] ?? null, $config['context'] ?? []);
+    $property_map = [
+        'count',
+        'user',
+        'group',
+        'reloadable',
+        'reusePort',
+        'transport',
+        'protocol',
+    ];
+    $worker->name = $process_name;
+    foreach ($property_map as $property) {
+        if (isset($config[$property])) {
+            $worker->$property = $config[$property];
+        }
+    }
+
+    $worker->onWorkerStart = function ($worker) use ($config) {
+        require_once base_path() . '/support/bootstrap.php';
+
+        foreach ($config['services'] ?? [] as $server) {
+            if (!class_exists($server['handler'])) {
+                echo "process error: class {$server['handler']} not exists\r\n";
+                continue;
+            }
+            $listen = new Worker($server['listen'] ?? null, $server['context'] ?? []);
+            if (isset($server['listen'])) {
+                echo "listen: {$server['listen']}\n";
+            }
+            $instance = Container::make($server['handler'], $server['constructor'] ?? []);
+            worker_bind($listen, $instance);
+            $listen->listen();
+        }
+
+        if (isset($config['handler'])) {
+            if (!class_exists($config['handler'])) {
+                echo "process error: class {$config['handler']} not exists\r\n";
+                return;
+            }
+
+            $instance = Container::make($config['handler'], $config['constructor'] ?? []);
+            worker_bind($worker, $instance);
+        }
+
+    };
+}
+
+/**
+ * Phar support.
+ * Compatible with the 'realpath' function in the phar file.
+ *
+ * @param string $file_path
+ * @return string
+ */
+function get_realpath(string $file_path): string
+{
+    if (strpos($file_path, 'phar://') === 0) {
+        return $file_path;
+    } else {
+        return realpath($file_path);
+    }
+}
+
+/**
+ * @return bool
+ */
+function is_phar()
+{
+    return class_exists(\Phar::class, false) && Phar::running();
+}
+
+/**
+ * @return int
+ */
+function cpu_count()
+{
+    // Windows does not support the number of processes setting.
+    if (\DIRECTORY_SEPARATOR === '\\') {
+        return 1;
+    }
+    $count = 4;
+    if (is_callable('shell_exec')) {
+        if (strtolower(PHP_OS) === 'darwin') {
+            $count = (int)shell_exec('sysctl -n machdep.cpu.core_count');
+        } else {
+            $count = (int)shell_exec('nproc');
+        }
+    }
+    return $count > 0 ? $count : 4;
+}

+ 44 - 0
webman

@@ -0,0 +1,44 @@
+#!/usr/bin/env php
+<?php
+
+use Webman\Config;
+use Webman\Console\Command;
+use Webman\Console\Util;
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) {
+    require_once __DIR__ . '/support/bootstrap.php';
+} else {
+    if (class_exists('Support\App')) {
+        Support\App::loadAllConfig(['route']);
+    } else {
+        Config::reload(config_path(), ['route', 'container']);
+    }
+}
+
+$cli = new Command();
+$cli->setName('webman cli');
+$cli->installInternalCommands();
+if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) {
+    $cli->installCommands($command_path);
+}
+
+foreach (config('plugin', []) as $firm => $projects) {
+    if (isset($projects['app'])) {
+        if ($command_str = Util::guessPath(base_path() . "/plugin/$firm", 'command')) {
+            $command_path = base_path() . "/plugin/$firm/$command_str";
+            $cli->installCommands($command_path, "plugin\\$firm\\$command_str");
+        }
+    }
+    foreach ($projects as $name => $project) {
+        if (!is_array($project)) {
+            continue;
+        }
+        foreach ($project['command'] ?? [] as $command) {
+            $cli->add(new $command);
+        }
+    }
+}
+
+$cli->run();

+ 3 - 0
windows.bat

@@ -0,0 +1,3 @@
+CHCP 65001
+php windows.php
+pause

+ 114 - 0
windows.php

@@ -0,0 +1,114 @@
+<?php
+/**
+ * Start file for windows
+ */
+require_once __DIR__ . '/vendor/autoload.php';
+
+use process\Monitor;
+use Workerman\Worker;
+use Webman\Config;
+
+ini_set('display_errors', 'on');
+error_reporting(E_ALL);
+
+Config::load(config_path(), ['route', 'container']);
+
+$runtime_process_path = runtime_path() . DIRECTORY_SEPARATOR . '/windows';
+if (!is_dir($runtime_process_path)) {
+    mkdir($runtime_process_path);
+}
+$process_files = [
+    __DIR__ . DIRECTORY_SEPARATOR . 'start.php'
+];
+foreach (config('process', []) as $process_name => $config) {
+    $file_content = <<<EOF
+<?php
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+use Workerman\Worker;
+use Webman\Config;
+
+ini_set('display_errors', 'on');
+error_reporting(E_ALL);
+
+if (is_callable('opcache_reset')) {
+    opcache_reset();
+}
+    
+Config::load(config_path(), ['route', 'container']);
+
+worker_start('$process_name', config('process')['$process_name']);
+
+if (DIRECTORY_SEPARATOR != "/") {
+    Worker::\$logFile = config('server')['log_file'] ?? Worker::\$logFile;
+}
+
+Worker::runAll();
+
+EOF;
+
+    $process_file = $runtime_process_path . DIRECTORY_SEPARATOR . "start_$process_name.php";
+    $process_files[] = $process_file;
+    file_put_contents($process_file, $file_content);
+}
+
+foreach (config('plugin', []) as $firm => $projects) {
+    foreach ($projects as $name => $project) {
+        foreach ($project['process'] ?? [] as $process_name => $config) {
+            $file_content = <<<EOF
+<?php
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+use Workerman\Worker;
+use Webman\Config;
+
+ini_set('display_errors', 'on');
+error_reporting(E_ALL);
+
+if (is_callable('opcache_reset')) {
+    opcache_reset();
+}
+
+Config::load(config_path(), ['route', 'container']);
+
+worker_start("plugin.$firm.$name.$process_name", config("plugin.$firm.$name.process")['$process_name']);
+
+if (DIRECTORY_SEPARATOR != "/") {
+    Worker::\$logFile = config('server')['log_file'] ?? Worker::\$logFile;
+}
+
+Worker::runAll();
+
+EOF;
+            $process_file = $runtime_process_path . DIRECTORY_SEPARATOR . "start_$process_name.php";
+            $process_files[] = $process_file;
+            file_put_contents($process_file, $file_content);
+        }
+    }
+}
+
+$monitor = new Monitor(...array_values(config('process.monitor.constructor')));
+
+function popen_processes($process_files)
+{
+    $cmd = "php " . implode(' ', $process_files);
+    $descriptorspec = [STDIN, STDOUT, STDOUT];
+    $resource = proc_open($cmd, $descriptorspec, $pipes);
+    if (!$resource) {
+        exit("Can not execute $cmd\r\n");
+    }
+    return $resource;
+}
+
+$resource = popen_processes($process_files);
+echo "\r\n";
+while (1) {
+    sleep(1);
+    if ($monitor->checkAllFilesChange()) {
+        $status = proc_get_status($resource);
+        $pid = $status['pid'];
+        shell_exec("taskkill /F /T /PID $pid");
+        proc_close($resource);
+        $resource = popen_processes($process_files);
+    }
+}