Merge branch 'develop'
# Conflicts: # .gitignore resolved by develop version # Moncter/.gitignore resolved by develop version # Touchkebao/.gitignore resolved by develop version # Touchkebao/.vite/deps/_metadata.json resolved by develop version
This commit is contained in:
13
Moncter/.gitignore
vendored
13
Moncter/.gitignore
vendored
@@ -1,7 +1,8 @@
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
yarn.lock
|
||||
/runtime
|
||||
/.idea
|
||||
/.vscode
|
||||
/vendor
|
||||
*.log
|
||||
.env
|
||||
.DS_Store
|
||||
dist/*
|
||||
/tests/tmp
|
||||
/tests/.phpunit.result.cache
|
||||
|
||||
21
Moncter/LICENSE
Normal file
21
Moncter/LICENSE
Normal file
@@ -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.
|
||||
70
Moncter/README.md
Normal file
70
Moncter/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
<div style="padding:18px;max-width: 1024px;margin:0 auto;background-color:#fff;color:#333">
|
||||
<h1>webman</h1>
|
||||
|
||||
基于<a href="https://www.workerman.net" target="__blank">workerman</a>开发的超高性能PHP框架
|
||||
|
||||
|
||||
<h1>学习</h1>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/webman" target="__blank">主页 / Home page</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://webman.workerman.net" target="__blank">文档 / Document</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/doc/webman/install.html" target="__blank">安装 / Install</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/questions" target="__blank">问答 / Questions</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/apps" target="__blank">市场 / Apps</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/sponsor" target="__blank">赞助 / Sponsors</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/doc/webman/thanks.html" target="__blank">致谢 / Thanks</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div style="float:left;padding-bottom:30px;">
|
||||
|
||||
<h1>赞助商</h1>
|
||||
|
||||
<h4>特别赞助</h4>
|
||||
<a href="https://www.crmeb.com/?form=workerman" target="__blank">
|
||||
<img src="https://www.workerman.net/img/sponsors/6429/20230719111500.svg" width="200">
|
||||
</a>
|
||||
|
||||
<h4>铂金赞助</h4>
|
||||
<a href="https://www.fadetask.com/?from=workerman" target="__blank"><img src="https://www.workerman.net/img/sponsors/1/20230719084316.png" width="200"></a>
|
||||
<a href="https://www.yilianyun.net/?from=workerman" target="__blank" style="margin-left:20px;"><img src="https://www.workerman.net/img/sponsors/6218/20230720114049.png" width="200"></a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div style="float:left;padding-bottom:30px;clear:both">
|
||||
|
||||
<h1>请作者喝咖啡</h1>
|
||||
|
||||
<img src="https://www.workerman.net/img/wx_donate.png" width="200">
|
||||
<img src="https://www.workerman.net/img/ali_donate.png" width="200">
|
||||
<br>
|
||||
<b>如果您觉得webman对您有所帮助,欢迎捐赠。</b>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div style="clear: both">
|
||||
<h1>LICENSE</h1>
|
||||
The webman is open-sourced software licensed under the MIT.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
24
Moncter/app/controller/IndexController.php
Normal file
24
Moncter/app/controller/IndexController.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use support\Request;
|
||||
|
||||
class IndexController
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return "我是数据中心,有何贵干?";
|
||||
}
|
||||
|
||||
public function view(Request $request)
|
||||
{
|
||||
return view('index/view', ['name' => 'webman']);
|
||||
}
|
||||
|
||||
public function json(Request $request)
|
||||
{
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
|
||||
}
|
||||
16
Moncter/app/controller/UserController.php
Normal file
16
Moncter/app/controller/UserController.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace app\controller;
|
||||
|
||||
use support\Request;
|
||||
|
||||
class UserController
|
||||
{
|
||||
public function hello(Request $request)
|
||||
{
|
||||
$default_name = 'webman';
|
||||
// 从get请求里获得name参数,如果没有传递name参数则返回$default_name
|
||||
$name = $request->get('name', $default_name);
|
||||
// 向浏览器返回字符串
|
||||
return response('hello ' . $name);
|
||||
}
|
||||
}
|
||||
4
Moncter/app/functions.php
Normal file
4
Moncter/app/functions.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
/**
|
||||
* Here is your custom functions.
|
||||
*/
|
||||
42
Moncter/app/middleware/StaticFile.php
Normal file
42
Moncter/app/middleware/StaticFile.php
Normal file
@@ -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 $handler): Response
|
||||
{
|
||||
// Access to files beginning with. Is prohibited
|
||||
if (strpos($request->path(), '/.') !== false) {
|
||||
return response('<h1>403 forbidden</h1>', 403);
|
||||
}
|
||||
/** @var Response $response */
|
||||
$response = $handler($request);
|
||||
// Add cross domain HTTP header
|
||||
/*$response->withHeaders([
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Credentials' => 'true',
|
||||
]);*/
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
29
Moncter/app/model/Test.php
Normal file
29
Moncter/app/model/Test.php
Normal file
@@ -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;
|
||||
}
|
||||
37
Moncter/app/model/User.php
Normal file
37
Moncter/app/model/User.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
// app/model/User.php
|
||||
namespace app\model;
|
||||
|
||||
use MongoDB\Laravel\Eloquent\Model;
|
||||
use MongoDB\Laravel\Relations\HasMany; // 若需关联查询(可选)
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
// 对应 MongoDB 集合名(默认复数,可自定义)
|
||||
protected $collection = 'users';
|
||||
|
||||
// 主键(MongoDB 默认 _id,无需修改,自动转为字符串)
|
||||
protected $primaryKey = '_id';
|
||||
|
||||
// 主键类型(官方推荐显式声明)
|
||||
protected $keyType = 'string';
|
||||
|
||||
// 允许批量赋值的字段(白名单)
|
||||
protected $fillable = ['name', 'age', 'email', 'avatar'];
|
||||
|
||||
// 自动转换字段类型(ObjectId 转字符串、日期转 Carbon)
|
||||
protected $casts = [
|
||||
'_id' => 'string',
|
||||
'age' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'tags' => 'array', // 支持数组类型(MongoDB 原生支持数组)
|
||||
];
|
||||
|
||||
// 自动维护时间戳(created_at/updated_at,默认启用)
|
||||
// 若不需要可关闭:public $timestamps = false;
|
||||
|
||||
// 自定义时间戳字段名(可选)
|
||||
// const CREATED_AT = 'create_time';
|
||||
// const UPDATED_AT = 'update_time';
|
||||
}
|
||||
10
Moncter/app/process/Http.php
Normal file
10
Moncter/app/process/Http.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace app\process;
|
||||
|
||||
use Webman\App;
|
||||
|
||||
class Http extends App
|
||||
{
|
||||
|
||||
}
|
||||
305
Moncter/app/process/Monitor.php
Normal file
305
Moncter/app/process/Monitor.php
Normal file
@@ -0,0 +1,305 @@
|
||||
<?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\process;
|
||||
|
||||
use FilesystemIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SplFileInfo;
|
||||
use Workerman\Timer;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class FileMonitor
|
||||
* @package process
|
||||
*/
|
||||
class Monitor
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $paths = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $extensions = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $loadedFiles = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $ppid = 0;
|
||||
|
||||
/**
|
||||
* Pause monitor
|
||||
* @return void
|
||||
*/
|
||||
public static function pause(): void
|
||||
{
|
||||
file_put_contents(static::lockFile(), time());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume monitor
|
||||
* @return void
|
||||
*/
|
||||
public static function resume(): void
|
||||
{
|
||||
clearstatcache();
|
||||
if (is_file(static::lockFile())) {
|
||||
unlink(static::lockFile());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether monitor is paused
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPaused(): bool
|
||||
{
|
||||
clearstatcache();
|
||||
return file_exists(static::lockFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock file
|
||||
* @return string
|
||||
*/
|
||||
protected static function lockFile(): string
|
||||
{
|
||||
return runtime_path('monitor.lock');
|
||||
}
|
||||
|
||||
/**
|
||||
* FileMonitor constructor.
|
||||
* @param $monitorDir
|
||||
* @param $monitorExtensions
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($monitorDir, $monitorExtensions, array $options = [])
|
||||
{
|
||||
$this->ppid = function_exists('posix_getppid') ? posix_getppid() : 0;
|
||||
static::resume();
|
||||
$this->paths = (array)$monitorDir;
|
||||
$this->extensions = $monitorExtensions;
|
||||
foreach (get_included_files() as $index => $file) {
|
||||
$this->loadedFiles[$file] = $index;
|
||||
if (strpos($file, 'webman-framework/src/support/App.php')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!Worker::getAllWorkers()) {
|
||||
return;
|
||||
}
|
||||
$disableFunctions = explode(',', ini_get('disable_functions'));
|
||||
if (in_array('exec', $disableFunctions, 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 ($options['enable_file_monitor'] ?? true) {
|
||||
Timer::add(1, function () {
|
||||
$this->checkAllFilesChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$memoryLimit = $this->getMemoryLimit($options['memory_limit'] ?? null);
|
||||
if ($memoryLimit && ($options['enable_memory_monitor'] ?? true)) {
|
||||
Timer::add(60, [$this, 'checkMemory'], [$memoryLimit]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $monitorDir
|
||||
* @return bool
|
||||
*/
|
||||
public function checkFilesChange($monitorDir): bool
|
||||
{
|
||||
static $lastMtime, $tooManyFilesCheck;
|
||||
if (!$lastMtime) {
|
||||
$lastMtime = time();
|
||||
}
|
||||
clearstatcache();
|
||||
if (!is_dir($monitorDir)) {
|
||||
if (!is_file($monitorDir)) {
|
||||
return false;
|
||||
}
|
||||
$iterator = [new SplFileInfo($monitorDir)];
|
||||
} else {
|
||||
// recursive traversal directory
|
||||
$dirIterator = new RecursiveDirectoryIterator($monitorDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS);
|
||||
$iterator = new RecursiveIteratorIterator($dirIterator);
|
||||
}
|
||||
$count = 0;
|
||||
foreach ($iterator as $file) {
|
||||
$count ++;
|
||||
/** var SplFileInfo $file */
|
||||
if (is_dir($file->getRealPath())) {
|
||||
continue;
|
||||
}
|
||||
// check mtime
|
||||
if (in_array($file->getExtension(), $this->extensions, true) && $lastMtime < $file->getMTime()) {
|
||||
$lastMtime = $file->getMTime();
|
||||
if (DIRECTORY_SEPARATOR === '/' && isset($this->loadedFiles[$file->getRealPath()])) {
|
||||
echo "$file updated but cannot be reloaded because only auto-loaded files support reload.\n";
|
||||
continue;
|
||||
}
|
||||
$var = 0;
|
||||
exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var);
|
||||
if ($var) {
|
||||
continue;
|
||||
}
|
||||
// send SIGUSR1 signal to master process for reload
|
||||
if (DIRECTORY_SEPARATOR === '/') {
|
||||
if ($masterPid = $this->getMasterPid()) {
|
||||
echo $file . " updated and reload\n";
|
||||
posix_kill($masterPid, SIGUSR1);
|
||||
} else {
|
||||
echo "Master process has gone away and can not reload\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
echo $file . " updated and reload\n";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!$tooManyFilesCheck && $count > 1000) {
|
||||
echo "Monitor: There are too many files ($count files) in $monitorDir which makes file monitoring very slow\n";
|
||||
$tooManyFilesCheck = 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMasterPid(): int
|
||||
{
|
||||
if ($this->ppid === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (function_exists('posix_kill') && !posix_kill($this->ppid, 0)) {
|
||||
echo "Master process has gone away\n";
|
||||
return $this->ppid = 0;
|
||||
}
|
||||
if (PHP_OS_FAMILY !== 'Linux') {
|
||||
return $this->ppid;
|
||||
}
|
||||
$cmdline = "/proc/$this->ppid/cmdline";
|
||||
if (!is_readable($cmdline) || !($content = file_get_contents($cmdline)) || (!str_contains($content, 'WorkerMan') && !str_contains($content, 'php'))) {
|
||||
// Process not exist
|
||||
$this->ppid = 0;
|
||||
}
|
||||
return $this->ppid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkAllFilesChange(): bool
|
||||
{
|
||||
if (static::isPaused()) {
|
||||
return false;
|
||||
}
|
||||
foreach ($this->paths as $path) {
|
||||
if ($this->checkFilesChange($path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $memoryLimit
|
||||
* @return void
|
||||
*/
|
||||
public function checkMemory($memoryLimit): void
|
||||
{
|
||||
if (static::isPaused() || $memoryLimit <= 0) {
|
||||
return;
|
||||
}
|
||||
$masterPid = $this->getMasterPid();
|
||||
if ($masterPid <= 0) {
|
||||
echo "Master process has gone away\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$childrenFile = "/proc/$masterPid/task/$masterPid/children";
|
||||
if (!is_file($childrenFile) || !($children = file_get_contents($childrenFile))) {
|
||||
return;
|
||||
}
|
||||
foreach (explode(' ', $children) as $pid) {
|
||||
$pid = (int)$pid;
|
||||
$statusFile = "/proc/$pid/status";
|
||||
if (!is_file($statusFile) || !($status = file_get_contents($statusFile))) {
|
||||
continue;
|
||||
}
|
||||
$mem = 0;
|
||||
if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) {
|
||||
$mem = $match[1];
|
||||
}
|
||||
$mem = (int)($mem / 1024);
|
||||
if ($mem >= $memoryLimit) {
|
||||
posix_kill($pid, SIGINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory limit
|
||||
* @param $memoryLimit
|
||||
* @return int
|
||||
*/
|
||||
protected function getMemoryLimit($memoryLimit): int
|
||||
{
|
||||
if ($memoryLimit === 0) {
|
||||
return 0;
|
||||
}
|
||||
$usePhpIni = false;
|
||||
if (!$memoryLimit) {
|
||||
$memoryLimit = ini_get('memory_limit');
|
||||
$usePhpIni = true;
|
||||
}
|
||||
|
||||
if ($memoryLimit == -1) {
|
||||
return 0;
|
||||
}
|
||||
$unit = strtolower($memoryLimit[strlen($memoryLimit) - 1]);
|
||||
$memoryLimit = (int)$memoryLimit;
|
||||
if ($unit === 'g') {
|
||||
$memoryLimit = 1024 * $memoryLimit;
|
||||
} else if ($unit === 'k') {
|
||||
$memoryLimit = ($memoryLimit / 1024);
|
||||
} else if ($unit === 'm') {
|
||||
$memoryLimit = (int)($memoryLimit);
|
||||
} else if ($unit === 't') {
|
||||
$memoryLimit = (1024 * 1024 * $memoryLimit);
|
||||
} else {
|
||||
$memoryLimit = ($memoryLimit / (1024 * 1024));
|
||||
}
|
||||
if ($memoryLimit < 50) {
|
||||
$memoryLimit = 50;
|
||||
}
|
||||
if ($usePhpIni) {
|
||||
$memoryLimit = (0.8 * $memoryLimit);
|
||||
}
|
||||
return (int)$memoryLimit;
|
||||
}
|
||||
|
||||
}
|
||||
14
Moncter/app/view/index/view.html
Normal file
14
Moncter/app/view/index/view.html
Normal file
@@ -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>
|
||||
56
Moncter/composer.json
Normal file
56
Moncter/composer.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "workerman/webman",
|
||||
"type": "project",
|
||||
"keywords": [
|
||||
"high performance",
|
||||
"http service"
|
||||
],
|
||||
"homepage": "https://www.workerman.net",
|
||||
"license": "MIT",
|
||||
"description": "High performance HTTP Service Framework.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "walkor",
|
||||
"email": "walkor@workerman.net",
|
||||
"homepage": "https://www.workerman.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"email": "walkor@workerman.net",
|
||||
"issues": "https://github.com/walkor/webman/issues",
|
||||
"forum": "https://wenda.workerman.net/",
|
||||
"wiki": "https://workerman.net/doc/webman",
|
||||
"source": "https://github.com/walkor/webman"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"workerman/webman-framework": "^2.1",
|
||||
"monolog/monolog": "^2.0",
|
||||
"mongodb/laravel-mongodb": "^4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "For better performance. "
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"": "./",
|
||||
"app\\": "./app",
|
||||
"App\\": "./app",
|
||||
"app\\View\\Components\\": "./app/view/components"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-package-install": [
|
||||
"support\\Plugin::install"
|
||||
],
|
||||
"post-package-update": [
|
||||
"support\\Plugin::install"
|
||||
],
|
||||
"pre-package-uninstall": [
|
||||
"support\\Plugin::uninstall"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
2455
Moncter/composer.lock
generated
Normal file
2455
Moncter/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Moncter/config/app.php
Normal file
26
Moncter/config/app.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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,
|
||||
'error_reporting' => E_ALL,
|
||||
'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' => 'Controller',
|
||||
'controller_reuse' => false,
|
||||
];
|
||||
21
Moncter/config/autoload.php
Normal file
21
Moncter/config/autoload.php
Normal file
@@ -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',
|
||||
]
|
||||
];
|
||||
17
Moncter/config/bootstrap.php
Normal file
17
Moncter/config/bootstrap.php
Normal file
@@ -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\bootstrap\Session::class,
|
||||
];
|
||||
15
Moncter/config/container.php
Normal file
15
Moncter/config/container.php
Normal file
@@ -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;
|
||||
27
Moncter/config/database.php
Normal file
27
Moncter/config/database.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
// 默认数据库连接(可选改为 mongodb)
|
||||
'default' => 'mysql', // 若需全局用 MongoDB,改为 'mongodb'
|
||||
|
||||
'connections' => [
|
||||
// ... 其他连接(如 mysql)保持不变
|
||||
|
||||
// MongoDB 官方连接配置
|
||||
'mongodb' => [
|
||||
'driver' => 'mongodb',
|
||||
'dsn' => 'mongodb://127.0.0.1:27017', // 集群可写:mongodb://node1:27017,node2:27017
|
||||
'database' => 'Moncter', // 目标数据库名
|
||||
'username' => 'Moncter', // 无认证则省略
|
||||
'password' => '123456', // 无认证则省略
|
||||
'options' => [
|
||||
'replicaSet' => '', // 副本集名称(无则留空)
|
||||
'ssl' => false, // 是否启用 SSL
|
||||
'connectTimeoutMS' => 3000, // 连接超时
|
||||
'socketTimeoutMS' => 5000, // 读写超时
|
||||
// 认证相关(若 MongoDB 启用认证)
|
||||
'authSource' => 'admin', // 认证数据库(默认 admin)
|
||||
'authMechanism' => 'SCRAM-SHA-256', // 认证机制(默认推荐)
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
15
Moncter/config/dependence.php
Normal file
15
Moncter/config/dependence.php
Normal file
@@ -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
Moncter/config/exception.php
Normal file
17
Moncter/config/exception.php
Normal file
@@ -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,
|
||||
];
|
||||
32
Moncter/config/log.php
Normal file
32
Moncter/config/log.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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' => [
|
||||
'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],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
15
Moncter/config/middleware.php
Normal file
15
Moncter/config/middleware.php
Normal file
@@ -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 [];
|
||||
62
Moncter/config/process.php
Normal file
62
Moncter/config/process.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?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\Log;
|
||||
use support\Request;
|
||||
use app\process\Http;
|
||||
|
||||
global $argv;
|
||||
|
||||
return [
|
||||
'webman' => [
|
||||
'handler' => Http::class,
|
||||
'listen' => 'http://0.0.0.0:8787',
|
||||
'count' => cpu_count() * 4,
|
||||
'user' => '',
|
||||
'group' => '',
|
||||
'reusePort' => false,
|
||||
'eventLoop' => '',
|
||||
'context' => [],
|
||||
'constructor' => [
|
||||
'requestClass' => Request::class,
|
||||
'logger' => Log::channel('default'),
|
||||
'appPath' => app_path(),
|
||||
'publicPath' => public_path()
|
||||
]
|
||||
],
|
||||
// File update detection and automatic reload
|
||||
'monitor' => [
|
||||
'handler' => app\process\Monitor::class,
|
||||
'reloadable' => false,
|
||||
'constructor' => [
|
||||
// Monitor these directories
|
||||
'monitorDir' => array_merge([
|
||||
app_path(),
|
||||
config_path(),
|
||||
base_path() . '/process',
|
||||
base_path() . '/support',
|
||||
base_path() . '/resource',
|
||||
base_path() . '/.env',
|
||||
], glob(base_path() . '/plugin/*/app'), glob(base_path() . '/plugin/*/config'), glob(base_path() . '/plugin/*/api')),
|
||||
// Files with these suffixes will be monitored
|
||||
'monitorExtensions' => [
|
||||
'php', 'html', 'htm', 'env'
|
||||
],
|
||||
'options' => [
|
||||
'enable_file_monitor' => !in_array('-d', $argv) && DIRECTORY_SEPARATOR === '/',
|
||||
'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/',
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
21
Moncter/config/route.php
Normal file
21
Moncter/config/route.php
Normal file
@@ -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\Route;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
23
Moncter/config/server.php
Normal file
23
Moncter/config/server.php
Normal file
@@ -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
|
||||
*/
|
||||
|
||||
return [
|
||||
'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
|
||||
];
|
||||
65
Moncter/config/session.php
Normal file
65
Moncter/config/session.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?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\Session\FileSessionHandler;
|
||||
use Webman\Session\RedisSessionHandler;
|
||||
use Webman\Session\RedisClusterSessionHandler;
|
||||
|
||||
return [
|
||||
|
||||
'type' => 'file', // or redis or redis_cluster
|
||||
|
||||
'handler' => 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
Moncter/config/static.php
Normal file
23
Moncter/config/static.php
Normal file
@@ -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
Moncter/config/translation.php
Normal file
25
Moncter/config/translation.php
Normal file
@@ -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
Moncter/config/view.php
Normal file
22
Moncter/config/view.php
Normal file
@@ -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
|
||||
];
|
||||
46
Moncter/go.sh
Executable file
46
Moncter/go.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail # 严格模式:报错立即退出、禁止未定义变量、管道错误触发退出
|
||||
|
||||
# ================= 配置项(可根据实际情况修改)=================
|
||||
# PHP 脚本路径(相对路径/绝对路径均可,推荐绝对路径更稳定)
|
||||
PHP_SCRIPT="start.php"
|
||||
# PHP 解释器路径(默认自动查找,若提示 php 未找到,手动指定如 /usr/bin/php)
|
||||
PHP_BIN=$(which php || echo "/usr/bin/php")
|
||||
# ==============================================================
|
||||
|
||||
# 1. 检查 PHP 解释器是否存在且可执行
|
||||
if [ ! -x "$PHP_BIN" ]; then
|
||||
echo -e "\033[31m错误:未找到可执行的 PHP 解释器!\033[0m"
|
||||
echo " 解决方案:"
|
||||
echo " 1. 安装 PHP:sudo apt install php-cli(Ubuntu/Debian)或 sudo dnf install php-cli(CentOS/RHEL)"
|
||||
echo " 2. 若已安装,手动修改脚本中的 PHP_BIN 为实际路径(通过 which php 查询)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. 检查 PHP 脚本是否存在
|
||||
if [ ! -f "$PHP_SCRIPT" ]; then
|
||||
echo -e "\033[31m错误:未找到脚本文件 $PHP_SCRIPT!\033[0m"
|
||||
echo " 请确保脚本与 $PHP_SCRIPT 在同一目录,或修改脚本中的 PHP_SCRIPT 为绝对路径"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3. 给 PHP 脚本添加执行权限(自动修复权限问题)
|
||||
if [ ! -x "$PHP_SCRIPT" ]; then
|
||||
echo -e "\033[33m警告:$PHP_SCRIPT 缺少执行权限,正在自动添加...\033[0m"
|
||||
chmod u+x "$PHP_SCRIPT" || {
|
||||
echo -e "\033[31m错误:添加执行权限失败,请用 sudo 运行脚本!\033[0m"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# 4. 执行核心命令(带日志输出优化)
|
||||
echo -e "\033[32m=== 开始执行:$PHP_BIN $PHP_SCRIPT start ===\033[0m"
|
||||
$PHP_BIN "$PHP_SCRIPT" start
|
||||
|
||||
# 5. 执行结果判断
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "\033[32m=== 执行成功!===\033[0m"
|
||||
else
|
||||
echo -e "\033[31m=== 执行失败!请查看上方错误信息 ===\033[0m"
|
||||
exit 1
|
||||
fi
|
||||
BIN
Moncter/public/favicon.ico
Normal file
BIN
Moncter/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,147 @@
|
||||
.chatFooter {
|
||||
background: #f7f7f7;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.inputToolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.leftTool {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
transition: all 0.15s;
|
||||
border: none;
|
||||
background: transparent;
|
||||
|
||||
&:hover {
|
||||
background: #e6e6e6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
}
|
||||
|
||||
.inputArea {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.inputWrapper {
|
||||
border: 1px solid #d1d1d1;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #07c160;
|
||||
}
|
||||
}
|
||||
|
||||
.messageInput {
|
||||
width: 100%;
|
||||
border: none;
|
||||
resize: none;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
padding: 8px 10px;
|
||||
background: transparent;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #b3b3b3;
|
||||
}
|
||||
}
|
||||
|
||||
.sendButtonArea {
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sendButton {
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
font-weight: normal;
|
||||
min-width: 60px;
|
||||
font-size: 13px;
|
||||
background: #07c160;
|
||||
border-color: #07c160;
|
||||
|
||||
&:hover {
|
||||
background: #06ad56;
|
||||
border-color: #06ad56;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #059748;
|
||||
border-color: #059748;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #b3b3b3;
|
||||
border-color: #b3b3b3;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.hintButton {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.inputHint {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
text-align: right;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.inputToolbar {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sendButtonArea {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
.stepContent {
|
||||
.stepHeader {
|
||||
margin-bottom: 20px;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step3Content {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
|
||||
.leftColumn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.rightColumn {
|
||||
width: 400px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.messagePreview {
|
||||
border: 2px dashed #52c41a;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: #f6ffed;
|
||||
|
||||
.previewTitle {
|
||||
font-size: 14px;
|
||||
color: #52c41a;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.messageBubble {
|
||||
min-height: 60px;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
|
||||
.currentEditingLabel {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.messageText {
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.savedScriptGroups {
|
||||
.scriptGroupTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.scriptGroupItem {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
background: #fff;
|
||||
|
||||
.scriptGroupHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.scriptGroupLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
|
||||
:global(.ant-radio) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.scriptGroupName {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.messageCount {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.scriptGroupActions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
.actionButton {
|
||||
padding: 4px;
|
||||
color: #666;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scriptGroupContent {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messageInputArea {
|
||||
.messageInput {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.attachmentButtons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.aiRewriteSection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.messageHint {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.settingsPanel {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: #fafafa;
|
||||
|
||||
.settingsTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.settingItem {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.settingLabel {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settingControl {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tagSection {
|
||||
.settingLabel {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.pushPreview {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: #f0f7ff;
|
||||
|
||||
.previewTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.step3Content {
|
||||
.rightColumn {
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.step3Content {
|
||||
flex-direction: column;
|
||||
|
||||
.leftColumn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rightColumn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import ContentSelection from "@/components/ContentSelection";
|
||||
import { ContentItem } from "@/components/ContentSelection/data";
|
||||
import InputMessage from "./InputMessage/InputMessage";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
interface StepSendMessageProps {
|
||||
0
Moncter/src/pages/pc/ckbox/weChat/api.ts
Normal file
0
Moncter/src/pages/pc/ckbox/weChat/api.ts
Normal file
0
Moncter/src/store/module/websocket/websocket.ts
Normal file
0
Moncter/src/store/module/websocket/websocket.ts
Normal file
5
Moncter/start.php
Executable file
5
Moncter/start.php
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
chdir(__DIR__);
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
support\App::run();
|
||||
24
Moncter/support/Request.php
Normal file
24
Moncter/support/Request.php
Normal file
@@ -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
Moncter/support/Response.php
Normal file
24
Moncter/support/Response.php
Normal file
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
139
Moncter/support/bootstrap.php
Normal file
139
Moncter/support/bootstrap.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?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\Log;
|
||||
use Webman\Bootstrap;
|
||||
use Webman\Config;
|
||||
use Webman\Middleware;
|
||||
use Webman\Route;
|
||||
use Webman\Util;
|
||||
use Workerman\Events\Select;
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = $worker ?? null;
|
||||
|
||||
if (empty(Worker::$eventLoopClass)) {
|
||||
Worker::$eventLoopClass = Select::class;
|
||||
}
|
||||
|
||||
set_error_handler(function ($level, $message, $file = '', $line = 0) {
|
||||
if (error_reporting() & $level) {
|
||||
throw new ErrorException($message, 0, $level, $file, $line);
|
||||
}
|
||||
});
|
||||
|
||||
if ($worker) {
|
||||
register_shutdown_function(function ($startTime) {
|
||||
if (time() - $startTime <= 0.1) {
|
||||
sleep(1);
|
||||
}
|
||||
}, time());
|
||||
}
|
||||
|
||||
if (class_exists('Dotenv\Dotenv') && file_exists(base_path(false) . '/.env')) {
|
||||
if (method_exists('Dotenv\Dotenv', 'createUnsafeMutable')) {
|
||||
Dotenv::createUnsafeMutable(base_path(false))->load();
|
||||
} else {
|
||||
Dotenv::createMutable(base_path(false))->load();
|
||||
}
|
||||
}
|
||||
|
||||
Config::clear();
|
||||
support\App::loadAllConfig(['route']);
|
||||
if ($timezone = config('app.default_timezone')) {
|
||||
date_default_timezone_set($timezone);
|
||||
}
|
||||
|
||||
foreach (config('autoload.files', []) as $file) {
|
||||
include_once $file;
|
||||
}
|
||||
foreach (config('plugin', []) as $firm => $projects) {
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['autoload']['files'] ?? [] as $file) {
|
||||
include_once $file;
|
||||
}
|
||||
}
|
||||
foreach ($projects['autoload']['files'] ?? [] as $file) {
|
||||
include_once $file;
|
||||
}
|
||||
}
|
||||
|
||||
Middleware::load(config('middleware', []));
|
||||
foreach (config('plugin', []) as $firm => $projects) {
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!is_array($project) || $name === 'static') {
|
||||
continue;
|
||||
}
|
||||
Middleware::load($project['middleware'] ?? []);
|
||||
}
|
||||
Middleware::load($projects['middleware'] ?? [], $firm);
|
||||
if ($staticMiddlewares = config("plugin.$firm.static.middleware")) {
|
||||
Middleware::load(['__static__' => $staticMiddlewares], $firm);
|
||||
}
|
||||
}
|
||||
Middleware::load(['__static__' => config('static.middleware', [])]);
|
||||
|
||||
foreach (config('bootstrap', []) as $className) {
|
||||
if (!class_exists($className)) {
|
||||
$log = "Warning: Class $className setting in config/bootstrap.php not found\r\n";
|
||||
echo $log;
|
||||
Log::error($log);
|
||||
continue;
|
||||
}
|
||||
/** @var Bootstrap $className */
|
||||
$className::start($worker);
|
||||
}
|
||||
|
||||
foreach (config('plugin', []) as $firm => $projects) {
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['bootstrap'] ?? [] as $className) {
|
||||
if (!class_exists($className)) {
|
||||
$log = "Warning: Class $className setting in config/plugin/$firm/$name/bootstrap.php not found\r\n";
|
||||
echo $log;
|
||||
Log::error($log);
|
||||
continue;
|
||||
}
|
||||
/** @var Bootstrap $className */
|
||||
$className::start($worker);
|
||||
}
|
||||
}
|
||||
foreach ($projects['bootstrap'] ?? [] as $className) {
|
||||
/** @var string $className */
|
||||
if (!class_exists($className)) {
|
||||
$log = "Warning: Class $className setting in plugin/$firm/config/bootstrap.php not found\r\n";
|
||||
echo $log;
|
||||
Log::error($log);
|
||||
continue;
|
||||
}
|
||||
/** @var Bootstrap $className */
|
||||
$className::start($worker);
|
||||
}
|
||||
}
|
||||
|
||||
$directory = base_path() . '/plugin';
|
||||
$paths = [config_path()];
|
||||
foreach (Util::scanDir($directory) as $path) {
|
||||
if (is_dir($path = "$path/config")) {
|
||||
$paths[] = $path;
|
||||
}
|
||||
}
|
||||
Route::load($paths);
|
||||
|
||||
3
Moncter/windows.bat
Normal file
3
Moncter/windows.bat
Normal file
@@ -0,0 +1,3 @@
|
||||
CHCP 65001
|
||||
php windows.php
|
||||
pause
|
||||
136
Moncter/windows.php
Normal file
136
Moncter/windows.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* Start file for windows
|
||||
*/
|
||||
chdir(__DIR__);
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
use support\App;
|
||||
use Workerman\Worker;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
App::loadAllConfig(['route']);
|
||||
|
||||
$errorReporting = config('app.error_reporting');
|
||||
if (isset($errorReporting)) {
|
||||
error_reporting($errorReporting);
|
||||
}
|
||||
|
||||
$runtimeProcessPath = runtime_path() . DIRECTORY_SEPARATOR . '/windows';
|
||||
$paths = [
|
||||
$runtimeProcessPath,
|
||||
runtime_path('logs'),
|
||||
runtime_path('views')
|
||||
];
|
||||
foreach ($paths as $path) {
|
||||
if (!is_dir($path)) {
|
||||
mkdir($path, 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
$processFiles = [];
|
||||
if (config('server.listen')) {
|
||||
$processFiles[] = __DIR__ . DIRECTORY_SEPARATOR . 'start.php';
|
||||
}
|
||||
foreach (config('process', []) as $processName => $config) {
|
||||
$processFiles[] = write_process_file($runtimeProcessPath, $processName, '');
|
||||
}
|
||||
|
||||
foreach (config('plugin', []) as $firm => $projects) {
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['process'] ?? [] as $processName => $config) {
|
||||
$processFiles[] = write_process_file($runtimeProcessPath, $processName, "$firm.$name");
|
||||
}
|
||||
}
|
||||
foreach ($projects['process'] ?? [] as $processName => $config) {
|
||||
$processFiles[] = write_process_file($runtimeProcessPath, $processName, $firm);
|
||||
}
|
||||
}
|
||||
|
||||
function write_process_file($runtimeProcessPath, $processName, $firm): string
|
||||
{
|
||||
$processParam = $firm ? "plugin.$firm.$processName" : $processName;
|
||||
$configParam = $firm ? "config('plugin.$firm.process')['$processName']" : "config('process')['$processName']";
|
||||
$fileContent = <<<EOF
|
||||
<?php
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use Workerman\Worker;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Webman\Config;
|
||||
use support\App;
|
||||
|
||||
ini_set('display_errors', 'on');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (is_callable('opcache_reset')) {
|
||||
opcache_reset();
|
||||
}
|
||||
|
||||
if (!\$appConfigFile = config_path('app.php')) {
|
||||
throw new RuntimeException('Config file not found: app.php');
|
||||
}
|
||||
\$appConfig = require \$appConfigFile;
|
||||
if (\$timezone = \$appConfig['default_timezone'] ?? '') {
|
||||
date_default_timezone_set(\$timezone);
|
||||
}
|
||||
|
||||
App::loadAllConfig(['route']);
|
||||
|
||||
worker_start('$processParam', $configParam);
|
||||
|
||||
if (DIRECTORY_SEPARATOR != "/") {
|
||||
Worker::\$logFile = config('server')['log_file'] ?? Worker::\$logFile;
|
||||
TcpConnection::\$defaultMaxPackageSize = config('server')['max_package_size'] ?? 10*1024*1024;
|
||||
}
|
||||
|
||||
Worker::runAll();
|
||||
|
||||
EOF;
|
||||
$processFile = $runtimeProcessPath . DIRECTORY_SEPARATOR . "start_$processParam.php";
|
||||
file_put_contents($processFile, $fileContent);
|
||||
return $processFile;
|
||||
}
|
||||
|
||||
if ($monitorConfig = config('process.monitor.constructor')) {
|
||||
$monitorHandler = config('process.monitor.handler');
|
||||
$monitor = new $monitorHandler(...array_values($monitorConfig));
|
||||
}
|
||||
|
||||
function popen_processes($processFiles)
|
||||
{
|
||||
$cmd = '"' . PHP_BINARY . '" ' . implode(' ', $processFiles);
|
||||
$descriptorspec = [STDIN, STDOUT, STDOUT];
|
||||
$resource = proc_open($cmd, $descriptorspec, $pipes, null, null, ['bypass_shell' => true]);
|
||||
if (!$resource) {
|
||||
exit("Can not execute $cmd\r\n");
|
||||
}
|
||||
return $resource;
|
||||
}
|
||||
|
||||
$resource = popen_processes($processFiles);
|
||||
echo "\r\n";
|
||||
while (1) {
|
||||
sleep(1);
|
||||
if (!empty($monitor) && $monitor->checkAllFilesChange()) {
|
||||
$status = proc_get_status($resource);
|
||||
$pid = $status['pid'];
|
||||
shell_exec("taskkill /F /T /PID $pid");
|
||||
proc_close($resource);
|
||||
$resource = popen_processes($processFiles);
|
||||
}
|
||||
}
|
||||
348
Moncter/技术方案.md
Normal file
348
Moncter/技术方案.md
Normal file
@@ -0,0 +1,348 @@
|
||||
## 用户标签引擎技术方案(以身份证为主键)
|
||||
|
||||
### 一、目标与范围
|
||||
- 构建可扩展的用户标签引擎,统一以身份证为主键的人(person)进行画像与筛选。
|
||||
- 支持多数据源接入(交易、行为、社群、外呼等),并聚合多个手机号、多个微信号到同一人。
|
||||
- 提供规则驱动的人群筛选、客群快照和线索分发能力,服务销售精细化运营。
|
||||
|
||||
### 0. 需求整合清单(共识)
|
||||
- 数据接入:多数据源/多数据库连接,按“数据源-表/接口”粒度定义 Job;增量水位、批量与重试。
|
||||
- 标识治理:支持弱标识(手机号等)建“临时人”,获取强标识(身份证/unionid/客户号)后合并;全链路幂等与审计。
|
||||
- 标签体系:分通道层与人层;`tag_dict` 定义口径/类型/窗口/聚合/版本;标签写入包含 window/source/version。
|
||||
- 聚合计算:通道→人层遵循 `aggregation`(sum/max/avg/any/best_of 等),支持实时触发与离线批处理。
|
||||
- 规则与人群:DSL 配置、试算/执行、审计;Redis 维护 cohort/位图,支持快照与导出。
|
||||
- 回灌与重算:规则或口径变更可对存量回评估;任务状态、错误与执行明细可观测、可重试。
|
||||
- 安全合规:身份证只存哈希(加盐),最小化数据使用,接口鉴权与导出留痕。
|
||||
|
||||
### 二、总体架构
|
||||
- 核心理念:人层(person)是唯一真相;通道层(channel)承载具体手机号/微信号等标识。
|
||||
- 组件分层:
|
||||
- 数据接入层:标准化事件/明细,写入通道层标签。
|
||||
- 聚合计算层:将通道层指标按口径聚合到人层标签。
|
||||
- 规则/人群层:基于人层标签做筛选、快照、导出与分发。
|
||||
- 存储与缓存:MySQL(字典/事实/审计)+ Redis(cohort 人群集与位图)。
|
||||
|
||||
### 二点五、运行逻辑图(分层架构,数据流视角)
|
||||
```mermaid
|
||||
╔═════════════════════════════════════════════════════════════════════════╗
|
||||
║ 数据源层 ║
|
||||
╚═════════════════════════════════════════════════════════════════════════╝
|
||||
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
|
||||
│交易系统│ │APP行为│ │客服系统│ │CRM │
|
||||
└─────┘ └─────┘ └─────┘ └─────┘
|
||||
│ │ │ │
|
||||
└───────┴─────────┘
|
||||
▼
|
||||
╔═════════════════════════════════════════════════════════════════════════╗
|
||||
║ 接入层 ║
|
||||
╚═════════════════════════════════════════════════════════════════════════╝
|
||||
┌───────────────────────────────────────┐
|
||||
│ Job调度器 增量水位 │
|
||||
└───────────────────────────────────────┘
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ 标准化 校验 │
|
||||
└───────────────────────────────────────┘
|
||||
▼
|
||||
╔═════════════════════════════════════════════════════════════════════════╗
|
||||
║ 身份层 ║
|
||||
╚═════════════════════════════════════════════════════════════════════════╝
|
||||
┌───────────────────────────────────────┐
|
||||
│ IdentifierService │
|
||||
│ 手机号→person_id │
|
||||
└───────────────────────────────────────┘
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ 临时人建表 强标识合并 │
|
||||
└───────────────────────────────────────┘
|
||||
▼
|
||||
╔═════════════════════════════════════════════════════════════════════════╗
|
||||
║ 标签层 ║
|
||||
╚═════════════════════════════════════════════════════════════════════════╝
|
||||
┌───────────────────────────────────────┐
|
||||
│ channel_tags │
|
||||
│ 通道标签存储 │
|
||||
└───────────────────────────────────────┘
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ Aggregator │
|
||||
│ 聚合计算 │
|
||||
└───────────────────────────────────────┘
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ person_tags │
|
||||
│ 人层标签存储 │
|
||||
└───────────────────────────────────────┘
|
||||
▼
|
||||
╔═════════════════════════════════════════════════════════════════════════╗
|
||||
║ 规则层 ║
|
||||
╚═════════════════════════════════════════════════════════════════════════╝
|
||||
┌───────────────────────────────────────┐
|
||||
│ RuleEngine │
|
||||
│ DSL执行 │
|
||||
└───────────────────────────────────────┘
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ Redis Cohort │
|
||||
│ 人群集合 │
|
||||
└───────────────────────────────────────┘
|
||||
▼
|
||||
╔═════════════════════════════════════════════════════════════════════════╗
|
||||
║ 应用层 ║
|
||||
╚═════════════════════════════════════════════════════════════════════════╝
|
||||
┌─────┐ ┌─────┐ ┌─────┐
|
||||
│人群查询│ │快照导出│ │分发推送│
|
||||
└─────┘ └─────┘ └─────┘
|
||||
|
||||
```
|
||||
|
||||
### 二点六、运行逻辑图(时序视角)
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant 业务系统 as 业务系统<br/>(交易/APP/客服)
|
||||
participant 接入服务 as 数据接入服务
|
||||
participant 身份服务 as 身份解析服务
|
||||
participant 通道标签 as channel_tags<br/>(存储)
|
||||
participant 聚合服务 as 聚合计算服务
|
||||
participant 人层标签 as person_tags<br/>(存储)
|
||||
participant 规则引擎 as 规则引擎
|
||||
participant 人群缓存 as Redis Cohort
|
||||
|
||||
业务系统->>接入服务: 1. 推送事件/批量数据
|
||||
接入服务->>接入服务: 2. 标准化、校验、去重
|
||||
接入服务->>身份服务: 3. 解析标识<br/>(手机号/微信→person_id)
|
||||
身份服务-->>接入服务: 返回person_id<br/>(不存在则建临时人)
|
||||
接入服务->>通道标签: 4. 写入通道标签<br/>(幂等、window/source/version)
|
||||
|
||||
通道标签->>聚合服务: 5. 触发聚合事件<br/>(实时/批量)
|
||||
聚合服务->>人层标签: 6. 按口径聚合<br/>(sum/max/avg/any)
|
||||
|
||||
人层标签->>规则引擎: 7. 标签变更触发<br/>(受影响person_id)
|
||||
规则引擎->>规则引擎: 8. 执行DSL规则
|
||||
规则引擎->>人群缓存: 9. 更新cohort<br/>(SADD/SINTER)
|
||||
|
||||
人群缓存-->>业务系统: 10. 人群查询/导出
|
||||
```
|
||||
|
||||
### 二点七、运行逻辑图(简化版,核心路径)
|
||||
```mermaid
|
||||
graph TB
|
||||
Start([数据源<br/>交易/行为/客服/CRM]) --> Ingest[数据接入<br/>Job调度 + 标准化]
|
||||
Ingest --> Identity[身份解析<br/>手机号→person_id]
|
||||
Identity --> Channel[通道标签<br/>channel_tags]
|
||||
Channel --> Aggregate[聚合计算<br/>通道→人层]
|
||||
Aggregate --> Person[人层标签<br/>person_tags]
|
||||
Person --> Rule[规则引擎<br/>DSL筛选]
|
||||
Rule --> Cohort[人群集合<br/>Redis Cohort]
|
||||
Cohort --> Export([应用输出<br/>查询/快照/分发])
|
||||
|
||||
Ingest -.->|状态| Audit1[(ingest_state)]
|
||||
Ingest -.->|错误| Audit2[(ingest_errors)]
|
||||
Rule -.->|执行记录| Audit3[(rule_executions)]
|
||||
|
||||
style Start fill:#e1f5ff
|
||||
style Export fill:#e1f5ff
|
||||
style Ingest fill:#fff4e1
|
||||
style Identity fill:#e8f5e9
|
||||
style Channel fill:#f3e5f5
|
||||
style Aggregate fill:#f3e5f5
|
||||
style Person fill:#f3e5f5
|
||||
style Rule fill:#fff9c4
|
||||
style Cohort fill:#fff9c4
|
||||
```
|
||||
|
||||
### 三、数据模型
|
||||
```sql
|
||||
-- 人:身份证(脱敏哈希)为主键
|
||||
CREATE TABLE person (
|
||||
person_id CHAR(32) PRIMARY KEY, -- md5(uppercase(id_card_no_without_spaces))
|
||||
id_card_hash CHAR(32) UNIQUE,
|
||||
name VARCHAR(64) NULL,
|
||||
gender TINYINT NULL,
|
||||
birthday DATE NULL,
|
||||
created_at DATETIME,
|
||||
updated_at DATETIME
|
||||
);
|
||||
|
||||
-- 标识绑定:一个人可有多个手机号/微信/外部ID
|
||||
CREATE TABLE person_identifier (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
person_id CHAR(32),
|
||||
id_type ENUM('phone','wechat','external','email') NOT NULL,
|
||||
id_value VARCHAR(128) NOT NULL,
|
||||
is_primary TINYINT DEFAULT 0,
|
||||
verified TINYINT DEFAULT 0,
|
||||
created_at DATETIME,
|
||||
updated_at DATETIME,
|
||||
UNIQUE KEY uk_type_value (id_type, id_value),
|
||||
KEY idx_person (person_id)
|
||||
);
|
||||
|
||||
-- 标签字典
|
||||
CREATE TABLE tag_dict (
|
||||
tag_code VARCHAR(128) PRIMARY KEY, -- 例:person.trade.arpu_90d
|
||||
name VARCHAR(128),
|
||||
category VARCHAR(64),
|
||||
level ENUM('person','channel') NOT NULL,
|
||||
type ENUM('int','bool','enum','set','string','float') NOT NULL,
|
||||
enum_values JSON NULL,
|
||||
unit VARCHAR(16) NULL,
|
||||
aggregation ENUM('sum','max','min','avg','any','best_of') NULL, -- 通道→人层口径
|
||||
description TEXT,
|
||||
version INT DEFAULT 1,
|
||||
status ENUM('draft','active','deprecated') DEFAULT 'active',
|
||||
owner VARCHAR(64),
|
||||
created_at DATETIME,
|
||||
updated_at DATETIME
|
||||
);
|
||||
|
||||
-- 人层标签(销售筛选用)
|
||||
CREATE TABLE person_tags (
|
||||
person_id CHAR(32),
|
||||
tag_code VARCHAR(128),
|
||||
tag_value VARCHAR(256),
|
||||
confidence TINYINT DEFAULT 100,
|
||||
source VARCHAR(64),
|
||||
window VARCHAR(32),
|
||||
version INT,
|
||||
updated_at DATETIME,
|
||||
PRIMARY KEY (person_id, tag_code),
|
||||
KEY idx_tag (tag_code, tag_value)
|
||||
);
|
||||
|
||||
-- 通道层标签(手机号/微信号维度)
|
||||
CREATE TABLE channel_tags (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
id_type ENUM('phone','wechat','external') NOT NULL,
|
||||
id_value VARCHAR(128) NOT NULL,
|
||||
tag_code VARCHAR(128),
|
||||
tag_value VARCHAR(256),
|
||||
source VARCHAR(64),
|
||||
window VARCHAR(32),
|
||||
updated_at DATETIME,
|
||||
UNIQUE KEY uk_dim (id_type, id_value, tag_code),
|
||||
KEY idx_tag (tag_code, tag_value)
|
||||
);
|
||||
|
||||
-- 规则配置与执行审计
|
||||
CREATE TABLE tag_rules (
|
||||
rule_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(128),
|
||||
dsl JSON,
|
||||
status ENUM('draft','active','paused') DEFAULT 'active',
|
||||
schedule VARCHAR(64) NULL,
|
||||
output_tag VARCHAR(128) NULL,
|
||||
owner VARCHAR(64),
|
||||
created_at DATETIME,
|
||||
updated_at DATETIME
|
||||
);
|
||||
|
||||
CREATE TABLE rule_executions (
|
||||
exec_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
rule_id BIGINT,
|
||||
started_at DATETIME,
|
||||
finished_at DATETIME,
|
||||
affected_users INT,
|
||||
status ENUM('success','failed','partial'),
|
||||
message TEXT
|
||||
);
|
||||
```
|
||||
|
||||
### 四、标签规范
|
||||
- 分类:
|
||||
- 基础画像、行为活跃、交易能力、风险合规、社交关系、生命周期、设备画像、地域属性。
|
||||
- 命名:`{level}.{category}.{name}_{window}`
|
||||
- 人层:`person.trade.arpu_90d`,`person.behavior.active_days_30d`
|
||||
- 通道层:`channel.wechat.group_join_30d`,`channel.phone.outbound_connect_7d`
|
||||
- 口径:在 `tag_dict` 中声明 `type/enum/range/aggregation/window/update_freq/version`。
|
||||
- 聚合:
|
||||
- 风险类:any(任一通道命中→人层命中)。
|
||||
- 活跃/价值:max/sum/avg 视业务定义。
|
||||
- 可达性:best_of(如选近30日响应率最高的手机号)。
|
||||
|
||||
### 五、规则与人群(DSL)
|
||||
```json
|
||||
{
|
||||
"name": "高潜付费人群",
|
||||
"logic": "AND",
|
||||
"conditions": [
|
||||
{"tag": "person.trade.arpu_90d", "op": "gte", "value": 500},
|
||||
{"tag": "person.behavior.active_days_30d", "op": "gte", "value": 10},
|
||||
{"tag": "person.risk.blacklist", "op": "eq", "value": false}
|
||||
],
|
||||
"window": "rolling_90d",
|
||||
"ttl": "24h"
|
||||
}
|
||||
```
|
||||
|
||||
### 六、计算与刷新策略
|
||||
- 批处理(T+1/T+0小时):生成稳定画像(交易统计、生命周期等)。
|
||||
- 准实时(秒级/分级):事件驱动更新通道层标签,触发对应人层增量聚合。
|
||||
- 回评估:字典/规则变更后对存量人群重算,并写入审计。
|
||||
|
||||
### 七、系统设计(ThinkPHP 5.1)
|
||||
- 目录结构(`application/tag`):
|
||||
- controller:`TagDictController`、`RuleController`、`SegmentController`、`IdentifierController`
|
||||
- model:`TagDict`、`Person`、`PersonIdentifier`、`PersonTags`、`ChannelTags`、`TagRules`、`RuleExecutions`
|
||||
- service:
|
||||
- `IdentifierService`(标识绑定/查找/合并)
|
||||
- `ChannelTagService`(通道标签写入)
|
||||
- `PersonTagService`(人层聚合/重算)
|
||||
- `TagQueryService`(条件解析→Redis/DB组合查询)
|
||||
- `RuleEngineService`(DSL 校验/执行/审计)
|
||||
- `CohortCacheService`(cohort 维护、集合/位图运算)
|
||||
- command:`ExecuteRule`、`RecomputeTags`、`CohortSnapshot`
|
||||
|
||||
- 路由(建议,受 `jwt` 保护):
|
||||
```php
|
||||
Route::group('v1/tag', function () {
|
||||
Route::get('dict', 'app\\tag\\controller\\TagDictController@index');
|
||||
Route::post('dict', 'app\\tag\\controller\\TagDictController@create');
|
||||
Route::post('identifier/bind', 'app\\tag\\controller\\IdentifierController@bind');
|
||||
Route::post('rule/execute/:id', 'app\\tag\\controller\\RuleController@execute');
|
||||
Route::post('segment/query', 'app\\tag\\controller\\SegmentController@query');
|
||||
Route::post('segment/snapshot', 'app\\tag\\controller\\SegmentController@snapshot');
|
||||
})->middleware(['jwt']);
|
||||
```
|
||||
|
||||
### 八、查询与筛选
|
||||
- 人层为主:`person_tags` 组合条件查询,Redis 保存常用 cohort:
|
||||
- Redis 示例:`SINTER cohort:person.trade.arpu_90d:gt500 cohort:person.active_days_30d:gte10 SDIFF cohort:person.risk.blacklist:eq1`
|
||||
- MySQL 组合查询示例:
|
||||
```sql
|
||||
SELECT DISTINCT t1.person_id
|
||||
FROM person_tags t1
|
||||
JOIN person_tags t2 ON t2.person_id=t1.person_id
|
||||
LEFT JOIN person_tags t3 ON t3.person_id=t1.person_id AND t3.tag_code='person.risk.blacklist'
|
||||
WHERE t1.tag_code='person.trade.arpu_90d' AND CAST(t1.tag_value AS DECIMAL)>=500
|
||||
AND t2.tag_code='person.behavior.active_days_30d' AND CAST(t2.tag_value AS SIGNED)>=10
|
||||
AND (t3.tag_value IS NULL OR t3.tag_value='0')
|
||||
LIMIT 50 OFFSET 0;
|
||||
```
|
||||
|
||||
### 九、关键流程
|
||||
1) 事件接入:收到 `id_type + id_value`(如手机号)→ 查 `person_identifier` → 得到 `person_id`。
|
||||
2) 通道标签更新:写 `channel_tags`,并发布“聚合任务”。
|
||||
3) 人层聚合:按 `tag_dict.aggregation` 规则,更新 `person_tags`。
|
||||
4) 规则评估:对受影响的 `person_id` 运行启用中的规则,更新 cohort/输出标签。
|
||||
5) 人群产出:支持分页查询、生成快照、导出或推送 CRM/外呼系统。
|
||||
|
||||
### 十、缓存与索引
|
||||
- Redis:
|
||||
- 集合/位图存 cohort,Key 规范:`cohort:{tag_code}:{op}{value}` 或区间桶。
|
||||
- TTL:默认 24h,可按规则 `ttl` 覆盖。
|
||||
- MySQL:
|
||||
- `person_tags(tag_code, tag_value)`、`channel_tags(tag_code, tag_value)` 倒排索引。
|
||||
- 审计表按时间分区或冷热分离。
|
||||
|
||||
### 十一、合规与安全
|
||||
- 身份证只存哈希(不可逆),不落明文;导出脱敏。
|
||||
- 最小权限访问,接口留痕审计(规则执行、导出、查看)。
|
||||
- 口径透明:标签保留来源、窗口、置信度、版本。
|
||||
|
||||
### 十二、里程碑(落地计划)
|
||||
- M1:建表与服务骨架;接入交易/行为两类数据;产出10个核心标签。
|
||||
- M2:规则试算与快照;Redis cohort;首批销售客群模板(高价值流失预警)。
|
||||
- M3:通道“最佳触达”策略;CRM/外呼对接;质量监控与看板。
|
||||
|
||||
|
||||
25
Moncter/数据库列表.md
Normal file
25
Moncter/数据库列表.md
Normal file
@@ -0,0 +1,25 @@
|
||||
KR
|
||||
KR_KR
|
||||
KR_LinkedIn
|
||||
KR_存客宝
|
||||
KR_存客宝_四表重构KR_KR版
|
||||
KR_国外
|
||||
KR_户口
|
||||
KR_京东
|
||||
KR_酒店
|
||||
KR_卡套私域
|
||||
KR_快递
|
||||
KR_魔兽世界
|
||||
KR_企业
|
||||
KR_企业名录
|
||||
KR_人才库
|
||||
KR_商城
|
||||
KR_手机
|
||||
KR_顺丰
|
||||
KR_淘宝
|
||||
KR_腾讯
|
||||
KR_投资
|
||||
KR_微博
|
||||
KR_香港在大陆投资企业名录
|
||||
KR_销售额3000万元-5000万元企业名录
|
||||
KR_游戏
|
||||
Reference in New Issue
Block a user