更新上传文件

This commit is contained in:
mkm 2023-12-03 09:58:50 +08:00
parent 7eef968cd3
commit 53c6128808
100 changed files with 19427 additions and 4505 deletions

View File

@ -46,7 +46,7 @@
"symfony/cache": "^5.4",
"max/var-dumper": "^0.1.1",
"textalk/websocket": "^1.5",
"workerman/mqtt": "^1.5"
"mongdch/webman-uploadslice": "^1.0"
},
"suggest": {
"ext-event": "For better performance. "

146
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a17d1e2eafc455d4b265f28452681146",
"content-hash": "010dcca3d3b8046a639dc83a69a08fdc",
"packages": [
{
"name": "aliyuncs/oss-sdk-php",
@ -1331,6 +1331,95 @@
"homepage": "https://github.com/marxphp/var-dumper",
"time": "2023-07-07T12:54:36+00:00"
},
{
"name": "mongdch/mon-util",
"version": "1.3.12",
"source": {
"type": "git",
"url": "https://github.com/MonGDCH/mon-util.git",
"reference": "dee088b3bee124b1784d39714addb54d141447b0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MonGDCH/mon-util/zipball/dee088b3bee124b1784d39714addb54d141447b0",
"reference": "dee088b3bee124b1784d39714addb54d141447b0",
"shasum": ""
},
"require": {
"php": ">=5.6.0"
},
"type": "library",
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"mon\\util\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "MonGDCH",
"email": "985558837@qq.com"
}
],
"description": "常用的PHP工具类库包含了各种各式各样的工具容器、时间、验证器、多语言、图片、二维码、IP地址、文件上传、文件操作、加解密、数据字典、各种算法等等等等。。。",
"homepage": "http://www.gdmon.com",
"keywords": [
"mon",
"tool",
"util"
],
"support": {
"issues": "https://github.com/MonGDCH/mon-util/issues",
"source": "https://github.com/MonGDCH/mon-util/tree/1.3.12"
},
"time": "2022-07-15T02:26:44+00:00"
},
{
"name": "mongdch/webman-uploadslice",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/MonGDCH/webman-uploadslice.git",
"reference": "bf38ef7c9e92c5a0fc67d24828d922f4a84ec563"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MonGDCH/webman-uploadslice/zipball/bf38ef7c9e92c5a0fc67d24828d922f4a84ec563",
"reference": "bf38ef7c9e92c5a0fc67d24828d922f4a84ec563",
"shasum": ""
},
"require": {
"mongdch/mon-util": "^1.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Mongdch\\WebmanUploadslice\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "MonGDCH",
"email": "985558837@qq.com"
}
],
"description": "Webman plugin mongdch/webman-uploadslice",
"support": {
"issues": "https://github.com/MonGDCH/webman-uploadslice/issues",
"source": "https://github.com/MonGDCH/webman-uploadslice/tree/1.0.0"
},
"time": "2022-08-15T06:45:25+00:00"
},
{
"name": "monolog/monolog",
"version": "2.9.1",
@ -3702,61 +3791,6 @@
],
"time": "2022-06-03T18:03:27+00:00"
},
{
"name": "workerman/mqtt",
"version": "v1.5",
"source": {
"type": "git",
"url": "https://github.com/walkor/mqtt.git",
"reference": "7075c2dbc31449352ac83ad9e1ee2d480962779f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/mqtt/zipball/7075c2dbc31449352ac83ad9e1ee2d480962779f",
"reference": "7075c2dbc31449352ac83ad9e1ee2d480962779f",
"shasum": ""
},
"require": {
"workerman/workerman": ">=3.3.0"
},
"suggest": {
"ext-event": "For better performance. "
},
"type": "library",
"autoload": {
"psr-4": {
"Workerman\\Mqtt\\": "./src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"homepage": "https://www.workerman.net",
"keywords": [
"event-loop",
"mqtt",
"mqtt3",
"mqtt5.0",
"mqtt_client",
"php",
"workerman"
],
"support": {
"email": "walkor@workerman.net",
"issues": "https://github.com/walkor/mqtt/issues",
"source": "https://github.com/walkor/mqtt/tree/v1.5"
},
"time": "2023-04-21T11:23:53+00:00"
},
{
"name": "workerman/webman-framework",
"version": "v1.5.8",

View File

@ -0,0 +1,14 @@
<?php
return [
// 启用插件
'enable' => true,
// 允许上传的文件后缀
'exts' => [],
// 分片文件大小限制
'sliceSize' => 0,
// 保存根路径
'rootPath' => public_path() . DIRECTORY_SEPARATOR . 'upload',
// 临时文件存储路径基于rootPath
'tmpPath' => 'tmp'
];

View File

@ -21,6 +21,7 @@ return array(
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
'72579e7bd17821bb1321b87411366eae' => $vendorDir . '/illuminate/support/helpers.php',
'95cbb2114eda2a039354f2117212c38d' => $vendorDir . '/mongdch/mon-util/src/functions.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'35fab96057f1bf5e7aba31a8a6d5fdde' => $vendorDir . '/topthink/think-orm/stubs/load_stubs.php',

View File

@ -10,10 +10,10 @@ return array(
'think\\' => array($vendorDir . '/topthink/think-container/src', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src'),
'taoser\\' => array($vendorDir . '/taoser/webman-validate/src'),
'support\\' => array($vendorDir . '/workerman/webman-framework/src/support'),
'mon\\util\\' => array($vendorDir . '/mongdch/mon-util/src'),
'app\\View\\Components\\' => array($baseDir . '/app/view/components'),
'app\\' => array($baseDir . '/app'),
'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'),
'Workerman\\Mqtt\\' => array($vendorDir . '/workerman/mqtt/src'),
'Workerman\\' => array($vendorDir . '/workerman/workerman'),
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
'Webman\\ThinkOrm\\' => array($vendorDir . '/webman/think-orm/src'),
@ -55,6 +55,7 @@ return array(
'PhpDocReader\\' => array($vendorDir . '/php-di/phpdoc-reader/src/PhpDocReader'),
'OSS\\' => array($vendorDir . '/aliyuncs/oss-sdk-php/src/OSS'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'Mongdch\\WebmanUploadslice\\' => array($vendorDir . '/mongdch/webman-uploadslice/src'),
'Max\\VarDumper\\' => array($vendorDir . '/max/var-dumper/src'),
'Matrix\\' => array($vendorDir . '/markbaker/matrix/classes/src'),
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),

View File

@ -22,6 +22,7 @@ class ComposerStaticInita5e937600be5568fba59bedece01053c
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
'72579e7bd17821bb1321b87411366eae' => __DIR__ . '/..' . '/illuminate/support/helpers.php',
'95cbb2114eda2a039354f2117212c38d' => __DIR__ . '/..' . '/mongdch/mon-util/src/functions.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php',
@ -47,6 +48,10 @@ class ComposerStaticInita5e937600be5568fba59bedece01053c
array (
'support\\' => 8,
),
'm' =>
array (
'mon\\util\\' => 9,
),
'a' =>
array (
'app\\View\\Components\\' => 20,
@ -58,7 +63,6 @@ class ComposerStaticInita5e937600be5568fba59bedece01053c
),
'W' =>
array (
'Workerman\\Mqtt\\' => 15,
'Workerman\\' => 10,
'Webmozart\\Assert\\' => 17,
'Webman\\ThinkOrm\\' => 16,
@ -118,6 +122,7 @@ class ComposerStaticInita5e937600be5568fba59bedece01053c
'M' =>
array (
'Monolog\\' => 8,
'Mongdch\\WebmanUploadslice\\' => 26,
'Max\\VarDumper\\' => 14,
'Matrix\\' => 7,
),
@ -186,6 +191,10 @@ class ComposerStaticInita5e937600be5568fba59bedece01053c
array (
0 => __DIR__ . '/..' . '/workerman/webman-framework/src/support',
),
'mon\\util\\' =>
array (
0 => __DIR__ . '/..' . '/mongdch/mon-util/src',
),
'app\\View\\Components\\' =>
array (
0 => __DIR__ . '/../..' . '/app/view/components',
@ -198,10 +207,6 @@ class ComposerStaticInita5e937600be5568fba59bedece01053c
array (
0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src',
),
'Workerman\\Mqtt\\' =>
array (
0 => __DIR__ . '/..' . '/workerman/mqtt/src',
),
'Workerman\\' =>
array (
0 => __DIR__ . '/..' . '/workerman/workerman',
@ -367,6 +372,10 @@ class ComposerStaticInita5e937600be5568fba59bedece01053c
array (
0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
),
'Mongdch\\WebmanUploadslice\\' =>
array (
0 => __DIR__ . '/..' . '/mongdch/webman-uploadslice/src',
),
'Max\\VarDumper\\' =>
array (
0 => __DIR__ . '/..' . '/max/var-dumper/src',

View File

@ -1400,6 +1400,101 @@
"homepage": "https://github.com/marxphp/var-dumper",
"install-path": "../max/var-dumper"
},
{
"name": "mongdch/mon-util",
"version": "1.3.12",
"version_normalized": "1.3.12.0",
"source": {
"type": "git",
"url": "https://github.com/MonGDCH/mon-util.git",
"reference": "dee088b3bee124b1784d39714addb54d141447b0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MonGDCH/mon-util/zipball/dee088b3bee124b1784d39714addb54d141447b0",
"reference": "dee088b3bee124b1784d39714addb54d141447b0",
"shasum": ""
},
"require": {
"php": ">=5.6.0"
},
"time": "2022-07-15T02:26:44+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"mon\\util\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "MonGDCH",
"email": "985558837@qq.com"
}
],
"description": "常用的PHP工具类库包含了各种各式各样的工具容器、时间、验证器、多语言、图片、二维码、IP地址、文件上传、文件操作、加解密、数据字典、各种算法等等等等。。。",
"homepage": "http://www.gdmon.com",
"keywords": [
"mon",
"tool",
"util"
],
"support": {
"issues": "https://github.com/MonGDCH/mon-util/issues",
"source": "https://github.com/MonGDCH/mon-util/tree/1.3.12"
},
"install-path": "../mongdch/mon-util"
},
{
"name": "mongdch/webman-uploadslice",
"version": "1.0.0",
"version_normalized": "1.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/MonGDCH/webman-uploadslice.git",
"reference": "bf38ef7c9e92c5a0fc67d24828d922f4a84ec563"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MonGDCH/webman-uploadslice/zipball/bf38ef7c9e92c5a0fc67d24828d922f4a84ec563",
"reference": "bf38ef7c9e92c5a0fc67d24828d922f4a84ec563",
"shasum": ""
},
"require": {
"mongdch/mon-util": "^1.3"
},
"time": "2022-08-15T06:45:25+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Mongdch\\WebmanUploadslice\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "MonGDCH",
"email": "985558837@qq.com"
}
],
"description": "Webman plugin mongdch/webman-uploadslice",
"support": {
"issues": "https://github.com/MonGDCH/webman-uploadslice/issues",
"source": "https://github.com/MonGDCH/webman-uploadslice/tree/1.0.0"
},
"install-path": "../mongdch/webman-uploadslice"
},
{
"name": "monolog/monolog",
"version": "2.9.1",
@ -3912,64 +4007,6 @@
],
"install-path": "../webmozart/assert"
},
{
"name": "workerman/mqtt",
"version": "v1.5",
"version_normalized": "1.5.0.0",
"source": {
"type": "git",
"url": "https://github.com/walkor/mqtt.git",
"reference": "7075c2dbc31449352ac83ad9e1ee2d480962779f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/mqtt/zipball/7075c2dbc31449352ac83ad9e1ee2d480962779f",
"reference": "7075c2dbc31449352ac83ad9e1ee2d480962779f",
"shasum": ""
},
"require": {
"workerman/workerman": ">=3.3.0"
},
"suggest": {
"ext-event": "For better performance. "
},
"time": "2023-04-21T11:23:53+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Workerman\\Mqtt\\": "./src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"homepage": "https://www.workerman.net",
"keywords": [
"event-loop",
"mqtt",
"mqtt3",
"mqtt5.0",
"mqtt_client",
"php",
"workerman"
],
"support": {
"email": "walkor@workerman.net",
"issues": "https://github.com/walkor/mqtt/issues",
"source": "https://github.com/walkor/mqtt/tree/v1.5"
},
"install-path": "../workerman/mqtt"
},
{
"name": "workerman/webman-framework",
"version": "v1.5.8",

View File

@ -3,7 +3,7 @@
'name' => 'workerman/webman',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'e68d605171c1be1e01a8a7ae2021617a73184ec8',
'reference' => '3a57936b3180850a6555ea18517b28261c9d2c4d',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -235,6 +235,24 @@
'aliases' => array(),
'dev_requirement' => false,
),
'mongdch/mon-util' => array(
'pretty_version' => '1.3.12',
'version' => '1.3.12.0',
'reference' => 'dee088b3bee124b1784d39714addb54d141447b0',
'type' => 'library',
'install_path' => __DIR__ . '/../mongdch/mon-util',
'aliases' => array(),
'dev_requirement' => false,
),
'mongdch/webman-uploadslice' => array(
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'reference' => 'bf38ef7c9e92c5a0fc67d24828d922f4a84ec563',
'type' => 'library',
'install_path' => __DIR__ . '/../mongdch/webman-uploadslice',
'aliases' => array(),
'dev_requirement' => false,
),
'monolog/monolog' => array(
'pretty_version' => '2.9.1',
'version' => '2.9.1.0',
@ -725,19 +743,10 @@
'aliases' => array(),
'dev_requirement' => false,
),
'workerman/mqtt' => array(
'pretty_version' => 'v1.5',
'version' => '1.5.0.0',
'reference' => '7075c2dbc31449352ac83ad9e1ee2d480962779f',
'type' => 'library',
'install_path' => __DIR__ . '/../workerman/mqtt',
'aliases' => array(),
'dev_requirement' => false,
),
'workerman/webman' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'e68d605171c1be1e01a8a7ae2021617a73184ec8',
'reference' => '3a57936b3180850a6555ea18517b28261c9d2c4d',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),

6
vendor/mongdch/mon-util/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
composer.phar
/vendor/
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock

187
vendor/mongdch/mon-util/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,187 @@
### 更新日志
> 所有值得注意的版本信息都将记录在该文件中
#### [1.3.12](#) (2022-07-15)
- 优化文档
- 增加`Log`日志处理类
#### [1.3.11](https://github.com/MonGDCH/mon-util/commit/de7fe3ffd752e7051a9624f2fe500bba932b6ead) (2022-07-13)
* 优化`Sql`
* 优化`Dictionary`
* 增加`Migrate`Mysql数据库备份迁移类
#### [1.3.10](https://github.com/MonGDCH/mon-util/commit/95b3be663e8807b17bf6c9af9c6f27707df20b68) (2022-05-18)
* 优化代码
* `Tool`类增加base64图片转换方法
#### [1.3.9](https://github.com/MonGDCH/mon-util/commit/1a689833b9cf22edc7bc68dab0725b89664667bb) (2022-04-21)
* 优化代码
* 增加UploadSilce类用户处理大文件分片上传
* 增加Pinyin类中文转拼音
#### [1.3.8](https://github.com/MonGDCH/mon-util/commit/a9b5dd32ae15f77caa569e03fe9e9606a11e1976) (2022-01-24)
* 优化增加文件导出压缩包 Tool::exportZip 方法
* 增加 Tool::exportZipForDir 方法,压缩导出整个目录
* 优化代码注解
* 补全版本
#### [1.3.7](https://github.com/MonGDCH/mon-util/commit/a5beb3ed528fd8e00e52f84648bafc5f84e08361) (2021-11-05)
* 优化增强Image类
* 补全版本
#### [1.3.6](https://github.com/MonGDCH/mon-util/commit/aba7e439f7f17bf8d0e0d3d1fd2757b4e1641549) (2021-10-14)
* 优化代码
#### [1.3.5](https://github.com/MonGDCH/mon-util/commit/7b4d9401ade441d79350cf2acc7d85a2175e1031) (2021-08-25)
* 优化代码,`Client`类更改为`Network`类,优化错误处理,增加模拟表单文件上传功能
* 优化`Date`类,增加获取年月日周等开始时间及结束时间
#### [1.3.4](https://github.com/MonGDCH/mon-util/commit/b52464f523c05464c7a48bc6ebf3b1d7f1222fce) (2021-08-11)
* 优化代码,移除`Tool`类中的TCP、UDP方法
* 增加`Client`支持HTTP、TCP、UDP请求
#### [1.3.3](https://github.com/MonGDCH/mon-util/commit/499ce3d6c5b3c91d200b4543e8e5786d0f305017) (2021-07-01)
* 优化代码,增强验证器
* File类增加 `copyDir` 文件夹复制方法
#### [1.3.2](https://github.com/MonGDCH/mon-util/commit/8495104332a7d4d73b2fc21bb1ad51bb3314e3cb) (2021-04-26)
* 优化代码,优化错误处理机制
* Validate类check方法统一返回boolean值通过getError方法获取错误信息
* 移除部分内置函数
#### [1.3.1](https://github.com/MonGDCH/mon-util/commit/1de8c9a6c935b1b37823eda6c40585487c54aafe) (2021-03-18)
* 优化代码,优化错误处理机制
* 移除Validate类内置单例模式增加confirm、eq方法
* 移除Date类内置单例模式优化业务逻辑
* 优化IdCard类增加根据身份证号获取所属省市区地址
* 增加Lottery类概率抽奖工具类
#### [1.2.10](https://github.com/MonGDCH/mon-util/commit/ce683230948c6ade093a1ad8f1e038ec1cab5962) (2021-03-11)
* 增加Sql类用于解析SQL文件获取sql操作语句
* 增加IdCard类用于处理身份证相关的业务
#### [1.2.9](https://github.com/MonGDCH/mon-util/commit/ece2233632916f89e0fd51719c0da683f98598cb) (2021-03-08)
* 增加DocParse类用于PHP文档解析
#### [1.2.8](https://github.com/MonGDCH/mon-util/commit/b0024ac96d4311554a767bd9cad0e93c893d9058) (2021-03-01)
* 优化代码
* 从[mongdch/mon-container]迁移container类库优化类库
* 增加File文件操作类库
#### [1.2.7](https://github.com/MonGDCH/mon-util/commit/d7d5f4bd76acfb1844b3ba69397dd2a86010da47) (2021-02-03)
* 优化代码
* 增加IPLocatoin类用于处理基于纯真IP库的地址定位
#### [1.2.6](https://github.com/MonGDCH/mon-util/commit/d816217e31ab2d715a2c8c73d250b6dc583ae19f) (2021-01-28)
* 优化id加密生成code类
#### [1.2.5](https://github.com/MonGDCH/mon-util/commit/6b14c1e22ba031c8ff3bb5b735741365a9e202e4) (2020-12-09)
* 优化代码
* 增加更多的工具方法
#### [1.2.4](https://github.com/MonGDCH/mon-util/commit/75139eb2f4e6e190fd23556913e053f9aec1fc66) (2020-11-30)
* 优化decryption字符串解密方法
* 增加Tool类download方法用于下载保存文件
* 增加IdCode类用于将int类型的ID值转为code值
#### [1.2.3](https://github.com/MonGDCH/mon-util/commit/9bb8f1c810bfac4e801a3b0ec7d6003e3bacb212) (2020-11-25)
* 优化验证器,验证器规则支持数组定义
* 增加Qrcode二维码工具类、增加Tool类getDistance方法用于获取两个坐标距离
* 增加Tool类exportZip、unZip、qrcode等方法同步支持直接方法名调用
#### [1.2.2](https://github.com/MonGDCH/mon-util/commit/031e6c308918f56e190863cc028bdf1984fdbcc8) (2020-06-29)
* 优化代码使用PHP自带的filter_var方法验证邮件及IP类型
* 增加Tool类safe_ip方法用于验证IP白名单或黑名单
#### [1.2.1](https://github.com/MonGDCH/mon-util/commit/6473e5e18b8215a7f33978b53340f61c0b398d91) (2020-06-16)
* 优化代码
#### [1.2.0](https://github.com/MonGDCH/mon-util/commit/b6036ea7db9c8e2161cd6a5d91e4281e7dff5e86) (2020-05-30)
* 优化代码,增加注解
* 增加工具类函数调用方式
#### [1.1.0](https://github.com/MonGDCH/mon-util/commit/cf1dcf3f389abdaab2530de568fc28a5e30b6352) (2019-12-20)
* 优化代码
* 增加Dictionary类用于做数据字典
#### [1.0.4](https://github.com/MonGDCH/mon-util/commit/4e0925df23fcca06f2e903fb27654175b8c5ecc7) (2019-09-12)
* 优化代码
* 移除Form类
* 增加Lang多语言操作类
#### [1.0.3](https://github.com/MonGDCH/mon-util/commit/9c8bcf2724df50685804eb735bb9532d2e5a97f0) (2019-06-15)
* 修正函数缺失的BUG
* 优化代码
#### [1.0.2](https://github.com/MonGDCH/mon-util/commit/d51b57dd00043aaa687ab9e0a6bfe22a55876a8d) (2019-06-14)
* 验证器使用场景验证的情况下字段未设置【required】验证规则仍然必须存在字段BUG
* 增加Image类、UploadFile类、GIF类
* 优化代码结构
#### [1.0.1](https://github.com/MonGDCH/mon-util/commit/6e4456b222ec84db045d6b1bc9d41596b69f13d6) (2019-04-18)
* 增强Common类、Tool类功能函数
* 增加From类、Instance类
* 优化代码结构
#### [1.0.0](https://github.com/MonGDCH/mon-util/commit/30c2688aca8875bdf6b49c6c79b561ebd704f93e) (2019-04-11)
- 发布第一个版本

201
vendor/mongdch/mon-util/LICENSE vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

48
vendor/mongdch/mon-util/README.md vendored Normal file
View File

@ -0,0 +1,48 @@
# mon-util
> PHP常用工具、辅助类集合包含各种常用的类库
* Container类对象容器
* Date类时间操作
* Instance类 trait单例实现
* Tree类树结构操作
* UpdateImg类base64图片上传
* UpdateFile类文件上传
* File类文件操作
* Validate类验证器
* Tool类常用工具类库函数
* Common类公共工具函数库
* Image类图片处理工具
* UploadFile类文件上传
* GIF类辅助Image类进行GIF图片处理
* Lang类多语言操作
* Dictionary类数据字典
* IdCode类用于整形ID加密转短字符串可结合应用用于生成短链接
* IPLocation类基于纯真IP库的ip地址定位解析GBK数据输出UTF8编码地址
* Qrcode类用于生成图片二维码
* DocParse类解析PHP类对象注解生成文档
* Sql类解析SQL文件获取sql操作语句
* IdCard类处理身份证相关的业务工具类
* Lottery类概率抽奖工具类
* Nework类网络客户端工具类
* UploadSilce类处理大文件分片上传
* Pinyin类中文转拼音
* Migrate类Mysql数据库备份迁移
* Log类日志处理
## 安装
```bash
composer require mongdch/mon-util
```
## 致谢
感谢您的支持和阅读,如果有什么不足的地方或者建议还请@我如果你觉得对你有帮助的话还请给个star。
## 关于
作者邮箱: 985558837@qq.com

28
vendor/mongdch/mon-util/composer.json vendored Normal file
View File

@ -0,0 +1,28 @@
{
"name": "mongdch/mon-util",
"description": "常用的PHP工具类库包含了各种各式各样的工具容器、时间、验证器、多语言、图片、二维码、IP地址、文件上传、文件操作、加解密、数据字典、各种算法等等等等。。。",
"license": "Apache-2.0",
"keywords": [
"mon",
"util",
"tool"
],
"homepage": "http://www.gdmon.com",
"authors": [
{
"name": "MonGDCH",
"email": "985558837@qq.com"
}
],
"require": {
"php": ">=5.6.0"
},
"autoload": {
"psr-4": {
"mon\\util\\": "src/"
},
"files": [
"src/functions.php"
]
}
}

20
vendor/mongdch/mon-util/composer.lock generated vendored Normal file
View File

@ -0,0 +1,20 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "39b00a050ae84046e9557c615fbfe782",
"packages": [],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.6.0"
},
"platform-dev": [],
"plugin-api-version": "2.2.0"
}

View File

@ -0,0 +1,29 @@
<?php
use mon\util\Container;
require __DIR__ . '/../vendor/autoload.php';
class T
{
public function test($args)
{
debug($args);
}
}
// 绑定匿名函数
Container::instance()->bind('dump', function ($ages) {
debug($ages);
});
// 调用
Container::instance()->get('dump', ['asdfgg']);
// 绑定对象
Container::set('t', T::class);
// 调用
Container::instance()->t->test('aaasd');

View File

@ -0,0 +1,53 @@
<?php
use mon\util\DocParse;
require __DIR__ . '/../vendor/autoload.php';
$data = DocParse::instance()->parseClass(DemoTest::class);
debug($data);
class DemoTest
{
/**
* 测试方法
*
* @param string $a 字符串参数
* @param integer $b 整型参数
* @param float $c 浮点型
* @param Test $d 对象类型
* @param array $e 数组类型
* @author Mon <985558837@qq.com>
* @copyright MonLam
* @deprecated deprecated test
* @example location description
* @final lalala
* @global 全局的xxx
* @ignore skldgkasgj for ignore
* @license MIT
* @link http://url.com
* @package ppap
* @abstract ccccc
* @static staticcccc
* @var mixed
* @version 1.0.0
* @todo Something
* @throws Exception 的事实
* @return void
*/
public function test($a, $b, $c, $d, $e)
{
return false;
}
/**
* demo方法
*
* @return array
*/
public function demo()
{
return [];
}
}

View File

@ -0,0 +1,14 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
use mon\util\Network;
$path = __DIR__ . '/img.php';
$url = 'http://localhost/index.php';
$data = Network::instance()->sendFile($url, $path, ['a' => 1, 'b' => 2]);
var_dump($data);

View File

@ -0,0 +1,50 @@
<?php
use mon\util\Instance;
use mon\util\Tool;
use mon\util\Validate;
require __DIR__ . '/../vendor/autoload.php';
// $base64 = Tool::instance()->img_base64('111.jpg');
// $img = Tool::instance()->base64_img($base64, '222.jpg');
// debug($base64);
// debug($img);
// $create = Tool::instance()->createTicket('123456', '123456', 3600, 'aaa', 'aaa_time');
// $check = Tool::instance()->checkTicket('123456');
// debug($check);
$check = check('num', '1');
debug($check);
class V extends Validate
{
use Instance;
public $rule = [
'a' => 'required|num',
'b' => 'required|str',
'c' => 'confirm:b'
];
public $message = [
'a' => 'a错误',
'b' => 'b错误',
'c' => 'c错误'
];
}
$data = [
'a' => '1',
'b' => '1123',
'c' => '1123'
];
$check = V::instance()->data($data)->check();
debug($check);
if (!$check) {
debug(V::instance()->getError());
}

View File

@ -0,0 +1,17 @@
<?php
use mon\util\IPLocation;
require __DIR__ . '/../vendor/autoload.php';
$qqwry = __DIR__ . '/qqwry.dat';
$class = new IPLocation($qqwry);
$ip = '255.255.255.1';
// $location = $class->getLocation($ip);
$location = IPLocation::instance()->init($qqwry)->getLocation($ip);
debug($location);

View File

@ -0,0 +1,20 @@
<?php
use mon\util\Lottery;
require __DIR__ . '/../vendor/autoload.php';
// 抽奖奖品
$awards = array(
'0' => array('id' => 1, 'title' => '平板电脑', 'probability' => 0.5),
'1' => array('id' => 2, 'title' => '数码相机', 'probability' => 0.15),
'2' => array('id' => 3, 'title' => '音箱设备', 'probability' => 0.25),
'3' => array('id' => 4, 'title' => '4G优盘', 'probability' => 24.5),
'4' => array('id' => 5, 'title' => '10Q币', 'probability' => 3.5),
);
$lottery = new Lottery();
// 初始化抽奖配置,抽奖
$gift = $lottery->init($awards)->getDraw();
debug($gift);

View File

@ -0,0 +1,63 @@
<?php
use mon\util\Dictionary;
use mon\util\Migrate;
require __DIR__ . '/../vendor/autoload.php';
$config = [
// 数据库备份路径
'path' => './data/',
// 数据库备份卷大小
'part' => 20971520,
// 数据库备份文件是否启用压缩 0不压缩 1 压缩
'compress' => 1,
// 压缩级别
'level' => 9,
// 数据库配置
'db' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'host' => '127.0.0.1',
// 数据库名
'database' => 'test',
// 用户名
'username' => 'root',
// 密码
'password' => 'root',
// 端口
'port' => '3306',
// 数据库编码默认采用utf8
'charset' => 'utf8mb4',
// 数据库连接参数
'params' => []
]
];
// 获取所有表格信息
$tableList = Migrate::instance($config)->tableList();
// 获取指定表格结构
$tableStruct = Migrate::instance()->tableStruct('record');
// 获取生成的备份文件列表
$fileList = Migrate::instance()->fileList();
// 获取指定备份文件信息
$fileInfo = Migrate::instance()->fileInfo();
// 删除备份文件
// $del = Migrate::instance()->del();
// 备份指定表
// $backup = Migrate::instance()->backup('record');
// 备份所有数据表
// $migrate = Migrate::instance()->migrate();
// 下载导出,参数为$fileList返回结果集中对应的time字段值
// $export = Migrate::instance()->export(1657692293);
// 导入,参数为$fileList返回结果集中对应的time字段值
// $import = Migrate::instance()->import(1657696160);
// debug($tableList);
$d = new Dictionary($config['db']);
echo $d->getHTML();

View File

@ -0,0 +1,9 @@
<?php
use mon\util\Pinyin;
require __DIR__ . '/../vendor/autoload.php';
echo Pinyin::instance()->format('我是拼 音,一起 拼音!');

View File

@ -0,0 +1,11 @@
<?php
use mon\util\QRcode;
use mon\util\Tool;
require __DIR__ . '/../vendor/autoload.php';
// qrcode('https://gdmon.com/');
Tool::instance()->qrcode('https://gdmon.com/');
// QRcode::png('https://gdmon.com/', false, QR_ECLEVEL_L, 8, 1);
// QRcode::png('https://gdmon.com/', 'a.png', QR_ECLEVEL_L, 12, 1, true);

View File

@ -0,0 +1,10 @@
<?php
use mon\util\Network;
require __DIR__ . '/../vendor/autoload.php';
$result = Network::instance()->sendTCP('127.0.0.1', '8818', 'test');
debug($result);
// debug($result);

View File

@ -0,0 +1,35 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>upfile</title>
</head>
<body>
<?php
require __DIR__ . '/../vendor/autoload.php';
if ($_POST) {
$file = new \mon\util\UploadFile([
'rootPath' => __DIR__ . '/upload/',
'exts' => ['jpg']
]);
try {
// 获取上传文件信息
$info = $file->upload()->getFile();
var_dump($file);
$save = $file->save()->getFile();
var_dump($save);
} catch (\mon\util\exception\UploadException $e) {
var_dump($e->getMessage(), $e->getCode());
}
} else {
?>
<form action="" method='post' enctype="multipart/form-data">
<input type="file" name="file" id="file" />
<input type="hidden" name="sf" value="sf" />
<input type="submit" value="上传" name="sub" />
</form>
<?php
}
?>
</body>
</html>

View File

@ -0,0 +1,405 @@
<!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.0">
<title>多线程异步大文件分片上传</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.main {
width: 860px;
margin: 40px auto;
}
#percent-bg {
margin-top: 20px;
position: relative;
width: 100%;
height: 30px;
border: 1px solid #ccc;
}
#percent {
position: absolute;
display: block;
width: 0;
height: 100%;
left: 0;
background: #67C23A;
z-index: 100;
}
#percent_num {
position: absolute;
display: block;
width: 30px;
height: 100%;
left: 50%;
margin-left: -15px;
z-index: 200;
font-size: 14px;
line-height: 30px;
text-align: center;
}
#message {
margin-top: 12px;
width: 100%;
height: 400px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 4px;
outline: none;
line-height: 20px;
font-size: 14px;
}
#loading-modal {
position: fixed;
z-index: 9999999999999999;
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, .5);
}
.loading-main {
position: relative;
width: 56px;
height: 56px;
}
#loading-num {
width: 120px;
font-size: 14px;
margin-top: 8px;
transform: translateX(-14px);
color: #fff
}
.loading-time {
width: 56px;
height: 56px;
border-bottom: 8px solid #f3f3f3;
border-top: 8px solid #f3f3f3;
border-color: #3498db #f3f3f3;
border-style: solid;
border-width: 8px;
border-radius: 50%;
animation: loading-spin 2s linear infinite;
-webkit-animation: loading-spin 2s linear infinite;
box-sizing: border-box;
/* background: #fff; */
}
@-webkit-keyframes loading-spin {
0% {
-webkit-transform: rotate(0deg)
}
to {
-webkit-transform: rotate(1turn)
}
}
@keyframes loading-spin {
0% {
transform: rotate(0deg)
}
to {
transform: rotate(1turn)
}
}
</style>
</head>
<body>
<div class="main">
<input type="file" name="file" id="file">
<button onclick="send()">上传文件</button>
<textarea id="message" readonly></textarea>
<div id="percent-bg">
<span id="percent"></span>
<span id="percent_num">0%</span>
</div>
</div>
<div id="loading-modal" style="display: none;">
<div class="loading-main">
<div class="loading-time"></div>
<div id="loading-num">正在解析文件</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/browser-md5-file@1.1.1/dist/index.umd.min.js"></script>
<script>
const config = {
// 文件大小限制20M
"maxSize": 1024 * 1024 * 500,
// 单个分片大小
"bufferSize": 1024 * 1024,
// 上传线程数量
"threadNum": 2,
}
// 分片数据集合
let blocks = []
// 上传的本地文件真实文件名
let filename = ''
// 文件uuid
let uuid = ''
// 分片索引
let __index = 0;
// 当前活跃线程数量
let __activeThreadCount = 0;
// 已上传的block数量
let __sendedBlockCount = 0;
// 强制结束进程
let stopRun = false;
// 文件上传
async function send() {
if (document.getElementById("file").files.length < 1) {
return false;
}
const file = document.getElementById("file").files[0]
// 验证文件大小
if (file.size > config.maxSize) {
console.log(111)
showMessage("文件大小限制:" + config.maxSize + ",实际文件大小:" + file.size);
return;
}
// 重置上传信息
reset()
// 打开Loading
showLoading(true)
// 使用文件md5作为uuid
uuid = await getUid(file)
// 文件分片
let endByte = 0;
let startByte = 0;
while (true) {
startByte = endByte;
if (endByte + config.bufferSize >= file.size) {
endByte = file.size;
} else {
endByte = endByte + config.bufferSize;
}
let block = sliceFile(file, startByte, endByte);
if (!block) {
showMessage("分片失败");
return;
}
blocks.push(block);
if (endByte >= file.size) {
break;
}
}
// 关闭Loading
showLoading(false)
showMessage("文件名:" + file.name);
showMessage("文件大小:" + file.size);
showMessage("总分片数量:" + blocks.length);
filename = file.name
// 开启上传进程
const threadNum = Math.min(config.threadNum, blocks.length);
for (var i = 0; i < threadNum; i++) {
if (stopRun) {
return;
}
(function (i) {
setTimeout(function () {
__activeThreadCount++;
run(i);
}, 500);
})(i);
}
}
// 获取文件uuid
function getUid(file) {
const bmf = new browserMD5File()
return new Promise((resolve, reject) => {
bmf.md5(file, function (err, md5) {
if (err) {
console.error('get uid err:', err);
showMessage('获取文件md5失败!');
return reject(err);
}
// console.log('md5 string:', md5);
// uuid = md5
resolve(md5)
})
})
}
// 重置
function reset() {
blocks = []
filename = ''
uuid = ''
__index = 0;
__activeThreadCount = 0;
__sendedBlockCount = 0;
stopRun = false
}
// 运行上传线程
function run(i) {
if (stopRun) {
return;
}
if (__index >= blocks.length) {
showMessage("线程" + i + ' 结束');
__activeThreadCount--;
if (__activeThreadCount == 0) {
showMessage("------------------------");
showMessage('多线程分片上传完毕,正在处理分片数据...');
merge();
}
return;
}
uploadSlice(i, __index);
__index++;
}
// 上传分片
function uploadSlice(name, chunkIndex) {
showMessage('线程' + name + ' 分片' + chunkIndex + ' start')
// 发送数据
const formData = new FormData()
formData.append('file', blocks[chunkIndex])
formData.append('filename', filename)
formData.append('chunk', chunkIndex)
formData.append('chunkLength', blocks.length)
formData.append('uuid', uuid)
formData.append('action', 'slice')
// 发送请求
sendXhr('upload_slice.php', formData, function (result) {
if (result.code != '1') {
stopRun = true;
showMessage('上传失败! Message' + result.msg);
return;
}
showMessage("线程" + name + " 分片" + chunkIndex + " end");
__sendedBlockCount++;
showPercent();
run(name);
})
}
// 发起合并请求
function merge() {
// 发送数据
const formData = new FormData()
formData.append('filename', filename)
formData.append('chunkLength', blocks.length)
formData.append('uuid', uuid)
formData.append('action', 'merge')
// 发送请求
sendXhr('upload_slice.php', formData, function (result) {
if (result.code != '1') {
stopRun = true;
showMessage('上传失败! Message' + result.msg);
return;
}
showMessage('分片数据处理完成,任务结束');
showMessage("")
showMessage("")
reset();
})
}
// 分割file
function sliceFile(file, startByte, endByte) {
if (file.slice) {
return file.slice(startByte, endByte);
}
if (file.webkitSlice) {
return file.webkitSlice(startByte, endByte);
}
if (file.mozSlice) {
return file.mozSlice(startByte, endByte);
}
return null;
}
//显示进度
function showPercent() {
var percent = parseInt(__sendedBlockCount / blocks.length * 100);
if (percent > 100) { percent = 100; }
document.querySelector('#percent').style.width = percent + "%"
document.querySelector('#percent_num').innerHTML = percent + "%"
}
// 渲染消息
function showMessage(msg) {
const txt = document.querySelector('#message').value
const message = txt + msg + "\r\n"
document.querySelector('#message').value = message
toMessageBottom()
}
// 消息至底部
function toMessageBottom() {
var div = document.querySelector('#message');
div.scrollTop = div.scrollHeight;
}
// 已送异步请求
function sendXhr(url, data, success, error, headers = {}) {
// 1.创建对象
let xhr = new XMLHttpRequest();
// 2.设置请求行(get请求数据写在url后面)
xhr.open('post', url);
// 3.设置请求头(get请求可以省略,post不发送数据也可以省略)
for (let key in headers) {
xhr.setRequestHeader(key, headers[key]);
}
// 4.注册回调函数
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
success(JSON.parse(xhr.responseText));
} else {
error(xhr.responseText);
}
}
};
// XHR2.0新增 上传进度监控
xhr.upload.onprogress = function (event) {
// console.log(event);
}
// 处理发送数据
// let data = new FormData()
// let params = this.getAttribute('data-params');
// params = JSON.parse(params)
// for (let key in params) {
// let value = params[key]
// data.append(key, value)
// }
// let name = this.getAttribute('name');
// data.append(name, file)
// 6.请求主体发送
xhr.send(data);
}
// loading
function showLoading(show) {
if (show) {
document.querySelector('#loading-modal').style.display = 'flex'
} else {
document.querySelector('#loading-modal').style.display = 'none'
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,140 @@
<?php
use mon\util\exception\UploadException;
use mon\util\UploadSlice;
use mon\util\Validate;
require __DIR__ . '/../vendor/autoload.php';
class App
{
/**
* 配置信息
*
* @var array
*/
protected $config = [
// 允许上传的文件后缀
'exts' => [],
// 分片文件大小限制
'sliceSize' => 1024 * 1024 * 2,
// 保存根路径
'rootPath' => __DIR__ . DIRECTORY_SEPARATOR . 'upload',
// 临时文件存储路径基于rootPath
'tmpPath' => 'tmp'
];
/**
* 文件上传
*
* @return void
*/
public function upload()
{
if (empty($_POST)) {
return $this->result(0, 'query faild');
}
// 验证数据
$validate = new Validate();
$check = $validate->data($_POST)->rule([
'action' => ['in:slice,merge'],
'filename' => ['required', 'str'],
'chunk' => ['int', 'min:0'],
'chunkLength' => ['required', 'int', 'min:0'],
'uuid' => ['required', 'str']
])->message([
'action' => 'action faild',
'filename' => 'filename faild',
'chunk' => 'chunk faild',
'chunkLength' => 'chunkLength faild',
'uuid' => 'uuid faild'
])->check();
if (!$check) {
return $this->result(0, $validate->getError());
}
if ($_POST['action'] == 'slice' && !isset($_POST['chunk'])) {
return $this->result(0, 'chunk required');
}
if ($_POST['action'] == 'slice' && empty($_FILES)) {
return $this->result(0, 'upload faild');
}
// 处理上传业务
$action = $this->post('action');
$filename = $this->post('filename');
$chunk = $this->post('chunk');
$chunkLength = $this->post('chunkLength');
$uuid = $this->post('uuid');
$upload = new UploadSlice($this->config);
try {
if ($action == 'slice') {
// 保存分片
$save = $upload->upload($uuid, $chunk);
return $this->result(1, 'ok', $save);
}
// 合并
$merge = $upload->merge($uuid, $chunkLength, $filename);
return $this->result(1, 'ok', $merge);
} catch (UploadException $e) {
return $this->result(0, $e->getMessage());
}
}
/**
* GET参数
*
* @param string $field
* @param string $default
* @return mixed
*/
protected function get($field, $default = '')
{
return isset($_GET[$field]) ? $_GET[$field] : $default;
}
/**
* POST参数
*
* @param string $field
* @param string $default
* @return mixed
*/
protected function post($field, $default = '')
{
return isset($_POST[$field]) ? $_POST[$field] : $default;
}
/**
* 返回json
*
* @param integer $code
* @param string $msg
* @param array $data
* @return void
*/
protected function result($code, $msg, $data = [])
{
$result = json_encode([
'code' => $code,
'msg' => $msg,
// 'data' => $this->m_mb_convert_encoding($data)
'data' => $data
], JSON_UNESCAPED_UNICODE);
echo $result;
}
protected function m_mb_convert_encoding($string)
{
if (is_array($string)) {
foreach ($string as $key => $value) {
$string[$key] = $this->m_mb_convert_encoding($value);
}
return $string;
}
return mb_convert_encoding($string, 'UTF-8', 'UTF-8');
}
}
(new App())->upload();

751
vendor/mongdch/mon-util/src/Common.php vendored Normal file
View File

@ -0,0 +1,751 @@
<?php
namespace mon\util;
use mon\util\Instance;
/**
* 公共工具类库(数据处理)
*
* @author Mon <985558837@qq.com>
* @version 1.1.1 优化代码 2022-07-8
*/
class Common
{
use Instance;
/**
* 字符串编码过滤(中文、英文、数字不过滤,只过滤特殊字符)
*
* @param string $src 安全转码的字符串
* @return string
*/
public function encodeEX($src)
{
$result = '';
$len = mb_strlen($src);
$encode_buf = '';
for ($i = 0; $i < $len; $i++) {
$sChar = mb_substr($src, $i, 1);
switch ($sChar) {
case "~":
case "`":
case "!":
case "@":
case "#":
case "$":
case "%":
case "^":
case "&":
case "*":
case "(":
case ")":
case "-":
case "_":
case "+":
case "=":
case "{":
case "}":
case "[":
case "]":
case "|":
case "\\":
case ";":
case ":":
case "\"":
case ",":
case "<":
case ">":
case ".":
case "?":
case "/":
case " ":
case "'":
case "\"":
case "\n":
case "\r":
case "\t":
$encode_buf = sprintf("%%%s", bin2hex($sChar));
$result .= $encode_buf;
break;
default:
$result .= $sChar;
break;
}
}
return $result;
}
/**
* 字符串解码对应encodeEX
*
* @param string $src 安全解码的字符串
* @return string
*/
public function decodeEX($src)
{
$result = '';
$len = mb_strlen($src);
for ($i = 0; $i < $len; $i++) {
$sChar = mb_substr($src, $i, 1);
if ($sChar == '%' && $i < ($len - 2) && $this->IsXDigit(mb_substr($src, $i + 1, 1)) && $this->IsXDigit(mb_substr($src, $i + 2, 1))) {
$chDecode = mb_substr($src, $i + 1, 2);
$result .= pack("H*", $chDecode);
$i += 2;
} else {
$result .= $sChar;
}
}
return $result;
}
/**
* 字符串加密方法
*
* @param string $str 加密的字符串
* @param string $salt 加密盐
* @return string
*/
public function encryption($str, $salt)
{
$str = base64_encode($this->randString(4, 5) . "." . $str . "." . $this->randString(4, 5));
$key = base64_encode($salt);
$str = base64_encode($str);
$mix = mb_strlen($key) >= mb_strlen($str) ? ceil(mb_strlen($key) / mb_strlen($str)) : ceil(mb_strlen($str) / mb_strlen($key));
$temp = str_split($str);
$ftmp = str_split($key);
foreach ($ftmp as $k => $v) {
isset($temp[$k * $mix]) && $temp[$k * $mix] .= $v;
}
$str = str_replace(array("=", "+", "/"), array("i00i", "k00k", "z00z"), implode($temp));
return base64_encode($str);
}
/**
* 字符串解密方法
*
* @param string $str 解密的字符串
* @param string $salt 解密的盐
* @return string
*/
public function decryption($str, $salt)
{
$str = base64_decode($str);
if (empty($str)) {
return '';
};
$key = base64_encode($salt);
$str = str_replace(array("i00i", "k00k", "z00z"), array("=", "+", "/"), $str);
$mix = mb_strlen($key) >= mb_strlen($str) ? ceil(mb_strlen($key) / mb_strlen($str)) : ceil(mb_strlen($str) / mb_strlen($key));
$temp = str_split($str);
for ($k = 0; $k < mb_strlen($key); $k++) {
if (!isset($temp[$k * $mix + 1])) {
break;
}
unset($temp[$k * $mix + 1]);
}
$str = base64_decode(base64_decode(implode($temp)));
$_arr = explode(".", $str);
return isset($_arr[1]) ? $_arr[1] : null;
}
/**
* 判断是否为16进制由于PHP没有相关的API所以折中处理
*
* @param string $src 验证的字符串
* @return boolean
*/
public function isXDigit($src)
{
if (mb_strlen($src) < 1) {
return false;
}
if (($src >= '0' && $src <= '9') || ($src >= 'A' && $src <= 'F') || ($src >= 'a' && $src <= 'f')) {
return true;
}
return false;
}
/**
* 检查字符串是否是UTF8编码
*
* @param string $string 验证的字符串
* @return boolean
*/
public function isUtf8($str)
{
$c = 0;
$b = 0;
$bits = 0;
$len = mb_strlen($str);
for ($i = 0; $i < $len; $i++) {
$c = ord($str[$i]);
if ($c > 128) {
if (($c >= 254)) {
return false;
} elseif ($c >= 252) {
$bits = 6;
} elseif ($c >= 248) {
$bits = 5;
} elseif ($c >= 240) {
$bits = 4;
} elseif ($c >= 224) {
$bits = 3;
} elseif ($c >= 192) {
$bits = 2;
} else {
return false;
}
if (($i + $bits) > $len) {
return false;
}
while ($bits > 1) {
$i++;
$b = ord($str[$i]);
if ($b < 128 || $b > 191) {
return false;
}
$bits--;
}
}
}
return true;
}
/**
* 获取余数
*
* @param integer $bn 被除数
* @param integer $sn 除数
* @return float
*/
public function mod($bn, $sn)
{
$mod = intval(fmod(floatval($bn), $sn));
return abs($mod);
}
/**
* 返回正数的ip2long值
*
* @param string $ip ip
* @return integer
*/
public function ip2long_positive($ip)
{
return sprintf("%u", $this->mIp2long($ip));
}
/**
* IP地址转为数字地址
* php ip2long 这个函数有问题
* 133.205.0.0 ==>> 2244804608
*
* @param string $ip 要转换的 ip 地址
* @return integer 转换完成的数字
*/
public function mIp2long($ip)
{
$ip_arr = explode('.', $ip);
$iplong = (16777216 * intval($ip_arr[0])) + (65536 * intval($ip_arr[1])) + (256 * intval($ip_arr[2])) + intval($ip_arr[3]);
return $iplong;
}
/**
* 递归转换数组数据为XML只作为exportXML的辅助方法使用
*
* @param array $data 输出的数据
* @return string
*/
public function arrToXML(array $data)
{
$xml = '';
foreach ($data as $key => $val) {
$xml .= "<{$key}>";
$xml .= (is_array($val) || is_object($val)) ? $this->arrToXML($val) : $val;
$xml .= "</{$key}>";
}
return $xml;
}
/**
* XML转数组
*
* @param string $xml
* @return array
*/
public function xmlToArr($xml)
{
$obj = simplexml_load_string($xml);
$json = json_encode($obj);
return json_decode($json, true);
}
/**
* URI字符串转数组
*
* @param string $str 入参,待转换的字符串
* @return array 字符数组
*/
public function strToMap($str)
{
$str = trim($str);
$infoMap = array();
$strArr = explode("&", $str);
for ($i = 0; $i < count($strArr); $i++) {
$infoArr = explode("=", $strArr[$i]);
if (count($infoArr) != 2) {
continue;
}
$infoMap[$infoArr[0]] = $infoArr[1];
}
return $infoMap;
}
/**
* 数组转字符串
*
* @param array $map 入参,待转换的数组
* @return string
*/
public function mapToStr(array $map)
{
$str = "";
if (!empty($map)) {
foreach ($map as $k => $v) {
$str .= "&" . $k . "=" . $v;
}
}
return $str;
}
/**
* 二维数组去重(&值不能完全相同)
*
* @param array $arr 需要去重的数组
* @return array
*/
public function array_2D_unique(array $arr)
{
foreach ($arr as $v) {
// 降维,将一维数组转换为用","连接的字符串.
$v = implode(",", $v);
$result[] = $v;
}
// 去掉重复的字符串,也就是重复的一维数组
$result = array_unique($result);
// 重组数组
foreach ($result as $k => $v) {
// 再将拆开的数组重新组装
$result[$k] = explode(",", $v);
}
sort($result);
return $result;
}
/**
* 二维数组去重(值不能相同)
*
* @param array $arr 需要去重的数组
* @return array
*/
public function array_2D_value_unique(array $arr)
{
$tmp = array();
foreach ($arr as $k => $v) {
// 搜索$v[$key]是否在$tmp数组中存在若存在返回true
if (in_array($v, $tmp)) {
unset($arr[$k]);
} else {
$tmp[] = $v;
}
}
sort($arr);
return $arr;
}
/**
* 是否为关联数组
*
* @param array $array 验证码的数组
* @return boolean
*/
public function isAssoc(array $array)
{
$keys = array_keys($array);
return array_keys($keys) !== $keys;
}
/**
* 二维数组排序
*
* @param array $array 排序的数组
* @param string $keys 排序的键名
* @param integer $sort 排序方式默认值SORT_DESC
* @return array
*/
public function array2DSort($array, $keys, $sort = SORT_DESC)
{
$keysValue = [];
foreach ($array as $k => $v) {
$keysValue[$k] = $v[$keys];
}
array_multisort($keysValue, $sort, $array);
return $array;
}
/**
* php获取中文字符拼音首字母
*
* @param string $str 中文字符串
* @return string
*/
public function getFirstChar($str)
{
if (empty($str)) {
return '';
}
$fchar = ord($str[0]);
if ($fchar >= ord('A') && $fchar <= ord('z')) {
return strtoupper($str[0]);
}
$s1 = iconv('UTF-8', 'gb2312', $str);
$s2 = iconv('gb2312', 'UTF-8', $s1);
$s = $s2 == $str ? $s1 : $str;
if (empty($s[1])) {
return '';
}
$asc = ord($s[0]) * 256 + ord($s[1]) - 65536;
if ($asc >= -20319 && $asc <= -20284) return 'A';
if ($asc >= -20283 && $asc <= -19776) return 'B';
if ($asc >= -19775 && $asc <= -19219) return 'C';
if ($asc >= -19218 && $asc <= -18711) return 'D';
if ($asc >= -18710 && $asc <= -18527) return 'E';
if ($asc >= -18526 && $asc <= -18240) return 'F';
if ($asc >= -18239 && $asc <= -17923) return 'G';
if ($asc >= -17922 && $asc <= -17418) return 'H';
if ($asc >= -17417 && $asc <= -16475) return 'J';
if ($asc >= -16474 && $asc <= -16213) return 'K';
if ($asc >= -16212 && $asc <= -15641) return 'L';
if ($asc >= -15640 && $asc <= -15166) return 'M';
if ($asc >= -15165 && $asc <= -14923) return 'N';
if ($asc >= -14922 && $asc <= -14915) return 'O';
if ($asc >= -14914 && $asc <= -14631) return 'P';
if ($asc >= -14630 && $asc <= -14150) return 'Q';
if ($asc >= -14149 && $asc <= -14091) return 'R';
if ($asc >= -14090 && $asc <= -13319) return 'S';
if ($asc >= -13318 && $asc <= -12839) return 'T';
if ($asc >= -12838 && $asc <= -12557) return 'W';
if ($asc >= -12556 && $asc <= -11848) return 'X';
if ($asc >= -11847 && $asc <= -11056) return 'Y';
if ($asc >= -11055 && $asc <= -10247) return 'Z';
return null;
}
/**
* 生成UUID 单机使用
*
* @return string
*/
public function uuid()
{
$charid = md5(uniqid(mt_rand(), true));
$hyphen = chr(45); // "-"
$uuid = mb_substr($charid, 0, 8) . $hyphen
. mb_substr($charid, 8, 4) . $hyphen
. mb_substr($charid, 12, 4) . $hyphen
. mb_substr($charid, 16, 4) . $hyphen
. mb_substr($charid, 20, 12);
return $uuid;
}
/**
* 生成Guid主键
*
* @return string
*/
public function keyGen()
{
return str_replace('-', '', mb_substr($this->uuid(), 1, -1));
}
/**
* 字符串截取,支持中文和其他编码
*
* @param string $str 需要转换的字符串
* @param string $start 开始位置
* @param string $length 截取长度
* @param string $charset 编码格式
* @param string $suffix 截断显示字符
* @return string
*/
public function mSubstr($str, $length, $start = 0, $charset = "utf-8", $suffix = true)
{
if (function_exists("mb_substr")) {
$slice = mb_substr($str, $start, $length, $charset);
} elseif (function_exists('iconv_substr')) {
$slice = iconv_substr($str, $start, $length, $charset);
} else {
$re['utf-8'] = '/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/';
$re['gb2312'] = '/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/';
$re['gbk'] = '/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/';
$re['big5'] = '/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/';
preg_match_all($re[$charset], $str, $match);
$slice = join('', array_slice($match[0], $start, $length));
}
return $suffix ? $slice . '...' : $slice;
}
/**
* 产生随机字串,可用来自动生成密码
* 默认长度6位 字母和数字混合 支持中文
*
* @param string $len 长度
* @param string $type 字串类型0:字母;1:数字;2:大写字母;3:小写字母;4:中文;5:字母数字混合;othor:过滤掉混淆字符的字母数字组合
* @param string $addChars 额外字符
* @return string
*/
public function randString($len = 6, $type = '', $addChars = '')
{
$str = '';
switch ($type) {
case '0':
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars;
break;
case '1':
$chars = str_repeat('0123456789' . $addChars, 3);
break;
case '2':
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars;
break;
case '3':
$chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars;
break;
case '4':
$chars = '们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借' . $addChars;
break;
case '5':
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890' . $addChars;
break;
default:
// 默认去掉了容易混淆的字符oOLl和数字01要添加请使用addChars参数
$chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars;
break;
}
if ($len > 10) {
//位数过长重复字符串一定次数
$chars = ($type == 1) ? str_repeat($chars, $len) : str_repeat($chars, 5);
}
if ($type != 4) {
$chars = str_shuffle($chars);
$str = mb_substr($chars, 0, $len);
} else {
// 中文随机字
for ($i = 0; $i < $len; $i++) {
$str .= $this->msubstr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1, 'utf-8', false);
}
}
return $str;
}
/**
* 递归转换字符集
*
* @param mixed $data 要转换的数据
* @param string $out_charset 输出编码
* @param string $in_charset 输入编码
* @return mixed
*/
public function iconv_recursion($data, $out_charset, $in_charset)
{
switch (gettype($data)) {
case 'integer':
case 'boolean':
case 'float':
case 'double':
case 'NULL':
return $data;
case 'string':
if (empty($data) || is_numeric($data)) {
return $data;
} elseif (function_exists('mb_convert_encoding')) {
$data = mb_convert_encoding($data, $out_charset, $in_charset);
} elseif (function_exists('iconv')) {
$data = iconv($in_charset, $out_charset, $data);
}
return $data;
case 'object':
$vars = array_keys(get_object_vars($data));
foreach ($vars as $key) {
$data->$key = $this->iconv_recursion($data->$key, $out_charset, $in_charset);
}
return $data;
case 'array':
foreach ($data as $k => $v) {
$data[$this->iconv_recursion($k, $out_charset, $in_charset)] = $this->iconv_recursion($v, $out_charset, $in_charset);
}
return $data;
default:
return $data;
}
}
/**
* 笛卡尔积生成规格
*
* @param array $arr1 要进行笛卡尔积的二维数组
* @param array $arr2 最终实现的笛卡尔积组合,可不传
* @return array
*/
public function specCartesian($arr1, $arr2 = [])
{
$result = [];
if (!empty($arr1)) {
// 去除第一个元素
$first = array_splice($arr1, 0, 1);
// 判断是否是第一次进行拼接
if (count($arr2) > 0) {
foreach ($arr2 as $v) {
foreach ($first[0]['value'] as $vs) {
$result[] = $v . ',' . $vs;
}
}
} else {
foreach ($first[0]['value'] as $vs) {
$result[] = $vs;
}
}
// 递归进行拼接
if (count($arr1) > 0) {
$result = $this->specCartesian($arr1, $result);
}
}
return $result;
}
/**
* 字符串转Ascii码
*
* @param string $str 字符串
* @return string
*/
public function str2ascii($str)
{
$change_after = '';
if (!empty($str)) {
// 编码处理
$encode = mb_detect_encoding($str);
if ($encode != 'UTF-8') {
$str = mb_convert_encoding($str, 'UTF-8', $encode);
}
// 开始转换
for ($i = 0, $l = mb_strlen($str); $i < $l; $i++) {
$temp_str = dechex(ord($str[$i]));
if (isset($temp_str[1])) {
$change_after .= $temp_str[1];
}
if (isset($temp_str[0])) {
$change_after .= $temp_str[0];
}
}
}
return strtoupper($change_after);
}
/**
* Ascii码转字符串
*
* @param string $ascii Ascii码
* @return string
*/
public function ascii2str($ascii)
{
$str = '';
if (!empty($ascii)) {
// 开始转换
$asc_arr = str_split(strtolower($ascii), 2);
for ($i = 0; $i < count($asc_arr); $i++) {
$str .= chr(hexdec($asc_arr[$i][1] . $asc_arr[$i][0]));
}
// 编码处理
$encode = mb_detect_encoding($str);
if ($encode != 'UTF-8') {
$str = mb_convert_encoding($str, 'UTF-8', $encode);
}
}
return $str;
}
/**
* 删除字符串中的空格
*
* @param $str 要删除空格的字符串
* @return string 返回删除空格后的字符串
*/
public function trimAll($str)
{
$str = str_replace(" ", '', $str);
$str = str_ireplace(array("\r", "\n", '\r', '\n'), '', $str);
return $str;
}
/**
* 将一个字符串部分字符用$re替代隐藏
*
* @param string $string 待处理的字符串
* @param integer $start 规定在字符串的何处开始,
* 正数 - 在字符串的指定位置开始
* 负数 - 在从字符串结尾的指定位置开始
* 0 - 在字符串中的第一个字符处开始
* @param integer $length 可选。规定要隐藏的字符串长度。默认是直到字符串的结尾。
* 正数 - start 参数所在的位置隐藏
* 负数 - 从字符串末端隐藏
* @param string $re 替代符
* @return string 处理后的字符串
*/
public function hidestr($string, $start = 0, $length = 0, $re = '*')
{
if (empty($string)) {
return false;
}
$strarr = [];
$mb_strlen = mb_strlen($string);
while ($mb_strlen) {
$strarr[] = mb_substr($string, 0, 1, 'utf8');
$string = mb_substr($string, 1, $mb_strlen, 'utf8');
$mb_strlen = mb_strlen($string);
}
$strlen = count($strarr);
$begin = $start >= 0 ? $start : ($strlen - abs($start));
$end = $last = $strlen - 1;
if ($length > 0) {
$end = $begin + $length - 1;
} elseif ($length < 0) {
$end -= abs($length);
}
for ($i = $begin; $i <= $end; $i++) {
$strarr[$i] = $re;
}
if ($begin >= $end || $begin >= $last || $end > $last) {
return false;
}
return implode('', $strarr);
}
}

View File

@ -0,0 +1,308 @@
<?php
namespace mon\util;
use Closure;
use ReflectionClass;
use ReflectionMethod;
use ReflectionFunction;
use InvalidArgumentException;
/**
* 服务容器类
*
* @see 注意:该类由[mongdch/mon-container]包迁移,后续将不再维护[mongdch/mon-container]包。
* @author Mon 985558837@qq.com
* @version 1.2 2018-07-05
* @version 1.3.0 优化代码,增强注解 2021-03-01
*/
class Container
{
use Instance;
/**
* 容器中对象的标识符
*
* @var array
*/
protected $bind = [];
/**
* 实例容器
*
* @var array
*/
protected $service = [];
/**
* 获取容器中的对象实例
*
* @param string $abstract 对象名称或标识
* @param array $vars 入参
* @param boolean $newInstance 是否获取新的实例
* @return mixed
*/
public static function get($abstract, $vars = [], $newInstance = false)
{
return static::instance()->make($abstract, $vars, $newInstance);
}
/**
* 绑定一个类、闭包、实例、接口实现到容器
*
* @param string $abstract 类名称或标识符
* @param mixed $server 要绑定的实例
* @return Container
*/
public static function set($abstract, $server = null)
{
return static::instance()->bind($abstract, $server);
}
/**
* 私有化构造方法
*/
protected function __construct()
{
}
/**
* 魔术方法获取实例
*
* @param string $abstract 对象名称或标识
* @return mixed
*/
public function __get($abstract)
{
return static::get($abstract);
}
/**
* 绑定类、闭包、实例、接口实现到容器
*
* @param mixed $abstract 类名称或标识符或者数组
* @param mixed $server 要绑定的实例
* @return Container
*/
public function bind($abstract, $server = null)
{
// 传入数组,批量注册
if (is_array($abstract)) {
foreach ($abstract as $prefix => $service) {
// 数组,定义前缀并绑定服务
if (is_array($service)) {
foreach ($service as $k => $v) {
$name = $prefix . '_' . $k;
$this->register($name, $v);
}
} else {
$this->register($prefix, $service);
}
}
} else {
$this->register($abstract, $server);
}
return $this;
}
/**
* 判断容器中是否存在某个类或标识
*
* @param string $name 类名称或标识符
* @return boolean [description]
*/
public function has($name)
{
return isset($this->bind[$name]) || isset($this->service[$name]);
}
/**
* 创建获取对象的实例
*
* @param string $name 类名称或标识符
* @param array $vars 绑定的参数
* @param boolean $new 是否保存实例
* @return mixed
*/
public function make($name, $vars = [], $new = false)
{
if (isset($this->service[$name]) && !$new) {
$object = $this->service[$name];
} else {
if (isset($this->bind[$name])) {
// 存在标识
$service = $this->bind[$name];
if ($service instanceof Closure) {
// 匿名函数,绑定参数
$object = $this->invokeFunction($service, $vars);
} elseif (is_object($service)) {
// 已实例化的对象
$object = $service;
} else {
// 类对象,回调获取实例
$object = $this->make($service, $vars, $new);
}
} else {
// 不存在,判断为直接写入的类对象, 获取实例
$object = $this->invokeClass($name, $vars);
}
// 保存实例
if (!$new) {
$this->service[$name] = $object;
}
}
return $object;
}
/**
* 绑定参数,执行函数或者闭包
*
* @param mixed $function 函数或者闭包
* @param array $vars 绑定参数
* @return mixed
*/
public function invokeFunction($function, $vars = [])
{
// 创建反射对象
$reflact = new ReflectionFunction($function);
// 获取参数
$args = $this->bindParams($reflact, $vars);
return $reflact->invokeArgs($args);
}
/**
* 执行类方法, 绑定参数
*
* @param string|array $method 类方法, @分割, : Test@say
* @param array $vars 绑定参数
* @return mixed
*/
public function invokeMethd($method, $vars = [])
{
// 字符串转数组
if (is_string($method)) {
$method = explode('@', $method);
}
// 反射绑定类方法
$class = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
$reflact = new ReflectionMethod($class, $method[1]);
// 绑定参数
$args = $this->bindParams($reflact, $vars);
return $reflact->invokeArgs($class, $args);
}
/**
* 反射执行对象实例化,支持构造方法依赖注入
*
* @param string $class 对象名称
* @param array $vars 绑定构造方法参数
* @return mixed
*/
public function invokeClass($class, $vars = [])
{
$reflect = new ReflectionClass($class);
// 获取构造方法
$constructor = $reflect->getConstructor();
if ($constructor) {
// 存在构造方法
$args = $this->bindParams($constructor, $vars);
} else {
$args = [];
}
return $reflect->newInstanceArgs($args);
}
/**
* 反射执行回调方法
*
* @param mixed $callback 回调方法
* @param array $vars 参数
* @return mixed
*/
public function invoke($callback, $vars = [])
{
if ($callback instanceof Closure) {
$result = $this->invokeFunction($callback, $vars);
} else {
$result = $this->invokeMethd($callback, $vars);
}
return $result;
}
/**
* 注册服务容器
*
* @param string $name 名称
* @param mixed $server 要绑定的实例
* @return Container
*/
protected function register($name, $server)
{
// 闭包,绑定闭包
if ($server instanceof Closure) {
$this->bind[$name] = $server;
}
// 实例化后的对象, 保存到实例容器中
elseif (is_object($server)) {
$this->service[$name] = $server;
}
// 对象类名称,先保存,不实例化
else {
$this->bind[$name] = $server;
}
return $this;
}
/**
* 为反射对象绑定参数
*
* @param mixed $reflact 反射对象
* @param array $vars 参数
* @throws InvalidArgumentException
* @return array
*/
protected function bindParams($reflact, $vars = [])
{
$args = [];
if ($reflact->getNumberOfParameters() > 0) {
// 判断数组类型 数字数组时按顺序绑定参数
reset($vars);
$type = key($vars) === 0 ? 1 : 0;
// 获取类方法需要的参数
$params = $reflact->getParameters();
// 获取参数类型, 绑定参数
foreach ($params as $param) {
$name = $param->getName();
$class = $param->getClass();
if ($class) {
$className = $class->getName();
$args[] = $this->make($className);
} elseif (1 == $type && !empty($vars)) {
$args[] = array_shift($vars);
} elseif (0 == $type && isset($vars[$name])) {
$args[] = $vars[$name];
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
throw new InvalidArgumentException('bind parameters were not found![' . $name . ']', 500);
}
}
}
return $args;
}
}

646
vendor/mongdch/mon-util/src/Date.php vendored Normal file
View File

@ -0,0 +1,646 @@
<?php
namespace mon\util;
/**
* 时间日期相关操作
*
* @author Mon 985558837@qq.com
* @version 1.0.3 优化代码 2022-07-08
*/
class Date
{
/**
* 日期的时间戳
*
* @var integer
*/
protected $date;
/**
* 时区
*
* @var integer
*/
protected $timezone;
/**
*
*
* @var integer
*/
protected $year;
/**
*
*
* @var integer
*/
protected $month;
/**
*
*
* @var integer
*/
protected $day;
/**
*
*
* @var integer
*/
protected $hour;
/**
*
*
* @var integer
*/
protected $minute;
/**
*
*
* @var integer
*/
protected $second;
/**
* 星期的数字表示
*
* @var integer
*/
protected $weekday;
/**
* 星期的完整表示
*
* @var string
*/
protected $cWeekday;
/**
* 一年中的天数 0365
*
* @var integer
*/
protected $yDay;
/**
* 月份的完整表示
*
* @var string
*/
protected $cMonth;
/**
* 日期CDATE表示
*
* @var string
*/
protected $CDATE;
/**
* 日期的YMD表示
*
* @var string
*/
protected $YMD;
/**
* 时间的输出表示
*
* @var string
*/
protected $CTIME;
/**
* 星期的输出
*
* @var array
*/
protected $Week = array("", "", "", "", "", "", "");
/**
* 构造方法
*
* @param string $date 日期
* @return void
*/
public function __construct($date = '')
{
$this->date = $this->parse($date);
$this->setDate($this->date);
}
/**
* 日期分析,获取时间戳
*
* @param mixed $date 日期
* @return string 时间戳
*/
public function parse($date)
{
// 字符串解析
if (is_numeric($date)) {
//数字格式直接转换为时间戳
$tmpdate = $date;
} elseif (is_null($date)) {
//为空默认取得当前时间戳
$tmpdate = time();
} elseif (is_string($date)) {
if (($date == '') || strtotime($date) == -1) {
//为空默认取得当前时间戳
$tmpdate = time();
} else {
//把字符串转换成UNIX时间戳
$tmpdate = strtotime($date);
}
} else {
//默认取当前时间戳
$tmpdate = time();
}
return $tmpdate;
}
/**
* 日期相关参数设置
*
* @param integer $date 日期时间戳
* @return Date
*/
public function setDate($date)
{
$dateArray = getdate($date);
$this->date = $dateArray[0]; //时间戳
$this->second = $dateArray["seconds"]; //秒
$this->minute = $dateArray["minutes"]; //分
$this->hour = $dateArray["hours"]; //时
$this->day = $dateArray["mday"]; //日
$this->month = $dateArray["mon"]; //月
$this->year = $dateArray["year"]; //年
$this->weekday = $dateArray["wday"]; //星期 06
$this->cWeekday = '星期' . $this->Week[$this->weekday]; //$dateArray["weekday"]; //星期完整表示
$this->yDay = $dateArray["yday"]; //一年中的天数 0365
$this->cMonth = $dateArray["month"]; //月份的完整表示
$this->CDATE = $this->format("Y-m-d"); //日期表示
$this->YMD = $this->format("Ymd"); //简单日期
$this->CTIME = $this->format("H:i:s"); //时间表示
return $this;
}
/**
* 日期格式化
* 默认返回 1970-01-01 11:30:45 格式
*
* @param string $format 格式化参数
* @return string
*/
public function format($format = "Y-m-d H:i:s")
{
return date($format, $this->date);
}
/**
* 获取日期开始和结束的时间戳
*
* @param integer $timeStamp 时间戳,默认当天
* @return array
*/
public function getDayTime($timeStamp = '')
{
$date = $timeStamp ? $timeStamp : $this->date;
list($y, $m, $d) = explode('-', date('Y-m-d', $date));
return [
mktime(0, 0, 0, $m, $d, $y),
mktime(23, 59, 59, $m, $d, $y)
];
}
/**
* 获取周开始和结束的时间戳
*
* @param integer $timeStamp 默认当周
* @return array
*/
public function getWeekTime($timeStamp = '')
{
$date = $timeStamp ? $timeStamp : $this->date;
list($y, $m, $d, $w) = explode('-', date('Y-m-d-w', $date));
// 修正周日的问题
if ($w == 0) {
$w = 7;
}
return [
mktime(0, 0, 0, $m, $d - $w + 1, $y),
mktime(23, 59, 59, $m, $d - $w + 7, $y)
];
}
/**
* 获取月开始和结束的时间戳
*
* @param integer $timeStamp 默认当月
* @return array
*/
public function getMonthTime($timeStamp = '')
{
$date = $timeStamp ? $timeStamp : $this->date;
list($y, $m, $t) = explode('-', date('Y-m-t', $date));
return [
mktime(0, 0, 0, $m, 1, $y),
mktime(23, 59, 59, $m, $t, $y)
];
}
/**
* 获取年开始和结束的时间戳
*
* @param integer $timeStamp 默认当年
* @return array
*/
public function getYearTime($timeStamp = '')
{
$date = $timeStamp ? $timeStamp : $this->date;
$y = date('Y', $date);
return [
mktime(0, 0, 0, 1, 1, $y),
mktime(23, 59, 59, 12, 31, $y)
];
}
/**
* 根据指定日期和1~7来获取周一至周日对应的日期
*
* @param string $date 指定日期,为空则默认为当前天
* @param integer $weekday 指定返回周几的日期1~7),默认为返回周一对应的日期
* @param string $format 指定返回日期的格式
* @return string
*/
public function getWeekDay($weekday = 1, $date = '', $format = 'Y-m-d')
{
$time = strtotime($date);
$time = ($time == '') ? $this->date : $time;
return date($format, $time - 86400 * (date('N', $time) - $weekday));
}
/**
* 是否为闰年
*
* @param string $year 年份
* @return boolean
*/
public function isLeapYear($year = '')
{
if (empty($year)) {
$year = $this->year;
}
return ((($year % 4) == 0) && (($year % 100) != 0) || (($year % 400) == 0));
}
/**
* 计算日期差
*
* w -
* d -
* h -
* m -
* s -
* i -
* y -
*
* @param mixed $date 要比较的日期
* @param string $elaps 比较跨度
* @return integer
*/
public function dateDiff($date, $elaps = "d")
{
$__DAYS_PER_WEEK__ = (7);
$__DAYS_PER_MONTH__ = (30);
$__DAYS_PER_YEAR__ = (365);
$__HOURS_IN_A_DAY__ = (24);
$__MINUTES_IN_A_DAY__ = (1440);
$__SECONDS_IN_A_DAY__ = (86400);
//计算天数差
$__DAYSELAPS = ($this->parse($date) - $this->date) / $__SECONDS_IN_A_DAY__;
switch ($elaps) {
case "y": //转换成年
$__DAYSELAPS = $__DAYSELAPS / $__DAYS_PER_YEAR__;
break;
case "m": //转换成月
$__DAYSELAPS = $__DAYSELAPS / $__DAYS_PER_MONTH__;
break;
case "w": //转换成星期
$__DAYSELAPS = $__DAYSELAPS / $__DAYS_PER_WEEK__;
break;
case "h": //转换成小时
$__DAYSELAPS = $__DAYSELAPS * $__HOURS_IN_A_DAY__;
break;
case "i": //转换成分钟
$__DAYSELAPS = $__DAYSELAPS * $__MINUTES_IN_A_DAY__;
break;
case "s": //转换成秒
$__DAYSELAPS = $__DAYSELAPS * $__SECONDS_IN_A_DAY__;
break;
}
return $__DAYSELAPS;
}
/**
* 人性化的计算日期差
*
* @param mixed $time 要比较的时间
* @param mixed $precision 返回的精度
* @return string
*/
public function timeDiff($time, $precision = false)
{
if (!is_numeric($precision) && !is_bool($precision)) {
static $_diff = ['y' => '年', 'm' => '个月', 'd' => '天', 'h' => '小时', 'i' => '分钟', 's' => '秒', 'w' => '周'];
return ceil($this->dateDiff($time, $precision)) . $_diff[$precision] . '前';
}
$diff = abs($this->parse($time) - $this->date);
static $chunks = [[31536000, '年'], [2592000, '个月'], [604800, '周'], [86400, '天'], [3600, '小时'], [60, '分钟'], [1, '秒']];
$count = 0;
$since = '';
for ($i = 0; $i < count($chunks); $i++) {
if ($diff >= $chunks[$i][0]) {
$num = floor($diff / $chunks[$i][0]);
$since .= sprintf('%d' . $chunks[$i][1], $num);
$diff = (int) ($diff - $chunks[$i][0] * $num);
$count++;
if (!$precision || $count >= $precision) {
break;
}
}
}
return $since . '前';
}
/**
* 返回周的某一天 返回Date对象
*
* @param integer $n 星期几
* @return Date
*/
public function getDayOfWeek($n)
{
$week = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
return (new self($week[$n]));
}
/**
* 计算周的第一天 返回Date对象
*
* @return Date
*/
public function firstDayOfWeek()
{
return $this->getDayOfWeek(1);
}
/**
* 计算月份的第一天 返回Date对象
*
* @return Date
*/
public function firstDayOfMonth()
{
return (new self(mktime(0, 0, 0, $this->month, 1, $this->year)));
}
/**
* 计算年份的第一天 返回Date对象
*
* @return Date
*/
public function firstDayOfYear()
{
return (new self(mktime(0, 0, 0, 1, 1, $this->year)));
}
/**
* 计算周的最后一天 返回Date对象
*
* @return Date
*/
public function lastDayOfWeek()
{
return $this->getDayOfWeek(0);
}
/**
* 计算月份的最后一天 返回Date对象
*
* @return Date
*/
public function lastDayOfMonth()
{
return (new self(mktime(0, 0, 0, $this->month + 1, 0, $this->year)));
}
/**
* 计算年份的最后一天 返回Date对象
*
* @return Date
*/
public function lastDayOfYear()
{
return (new self(mktime(0, 0, 0, 1, 0, $this->year + 1)));
}
/**
* 计算月份的最大天数
*
* @return integer
*/
public function maxDayOfMonth()
{
return $this->dateDiff(strtotime($this->dateAdd(1, 'm')), 'd');
}
/**
* 取得指定间隔日期
*
* yyyy -
* q - 季度
* m -
* y - day of year
* d -
* w -
* ww - week of year
* h - 小时
* n - 分钟
* s -
*
* @param integer $number 间隔数目
* @param string $interval 比较类型
* @return Date
*/
public function dateAdd($number = 0, $interval = "d")
{
$hours = $this->hour;
$minutes = $this->minute;
$seconds = $this->second;
$month = $this->month;
$day = $this->day;
$year = $this->year;
switch ($interval) {
case "yyyy":
//---Add $number to year
$year += $number;
break;
case "q":
//---Add $number to quarter
$month += ($number * 3);
break;
case "m":
//---Add $number to month
$month += $number;
break;
case "y":
case "d":
case "w":
//---Add $number to day of year, day, day of week
$day += $number;
break;
case "ww":
//---Add $number to week
$day += ($number * 7);
break;
case "h":
//---Add $number to hours
$hours += $number;
break;
case "n":
//---Add $number to minutes
$minutes += $number;
break;
case "s":
//---Add $number to seconds
$seconds += $number;
break;
}
return (new self(mktime($hours, $minutes, $seconds, $month, $day, $year)));
}
/**
* 日期数字转中文,用于日和月、周
*
* @param integer $number 日期数字
* @return string
*/
public function numberToCh($number)
{
$number = intval($number);
$array = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
$str = '';
if ($number == 0) {
$str .= "";
}
if ($number < 10) {
$str .= $array[$number - 1];
} elseif ($number < 20) {
$str .= "" . $array[$number - 11];
} elseif ($number < 30) {
$str .= "二十" . $array[$number - 21];
} else {
$str .= "三十" . $array[$number - 31];
}
return $str;
}
/**
* 年份数字转中文
*
* @param integer $yearStr 年份数字
* @param boolean $flag 是否显示公元
* @return string
*/
public function yearToCh($yearStr, $flag = false)
{
$array = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
$str = $flag ? '公元' : '';
for ($i = 0; $i < 4; $i++) {
$str .= $array[mb_substr($yearStr, $i, 1)];
}
return $str;
}
/**
* 判断日期 所属 干支 生肖 星座
* type 参数XZ 星座 GZ 干支 SX 生肖
*
* @param string $type 获取信息类型
* @return string
*/
public function magicInfo($type)
{
$result = '';
$m = $this->month;
$y = $this->year;
$d = $this->day;
switch ($type) {
case 'XZ': //星座
$XZDict = ['摩羯', '宝瓶', '双鱼', '白羊', '金牛', '双子', '巨蟹', '狮子', '处女', '天秤', '天蝎', '射手'];
$Zone = [1222, 122, 222, 321, 421, 522, 622, 722, 822, 922, 1022, 1122, 1222];
if ((100 * $m + $d) >= $Zone[0] || (100 * $m + $d) < $Zone[1]) {
$i = 0;
} else {
for ($i = 1; $i < 12; $i++) {
if ((100 * $m + $d) >= $Zone[$i] && (100 * $m + $d) < $Zone[$i + 1]) {
break;
}
}
}
$result = $XZDict[$i] . '座';
break;
case 'GZ': //干支
$GZDict = array(
['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'],
['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']
);
$i = $y - 1900 + 36;
$result = $GZDict[0][$i % 10] . $GZDict[1][$i % 12];
break;
case 'SX': //生肖
$SXDict = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
$result = $SXDict[($y - 4) % 12];
break;
}
return $result;
}
/**
* 魔术方法,支持字符串输出对象
*
* @return string
*/
public function __toString()
{
return $this->format();
}
}

View File

@ -0,0 +1,339 @@
<?php
namespace mon\util;
use PDO;
use PDOException;
/**
* mysql数据字典
*
* @author Mon <985558837@qq.com>
* @version 1.0.0 2019-12-19
*/
class Dictionary
{
/**
* Mysql链接实例
*
* @var PDO
*/
protected $db;
/**
* 数据库配置
*
* @var array
*/
protected $config = [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'host' => '127.0.0.1',
// 数据库名
'database' => '',
// 用户名
'username' => '',
// 密码
'password' => '',
// 端口
'port' => '3306',
// 数据库编码默认采用utf8
'charset' => 'utf8mb4',
// 数据库连接参数
'params' => []
];
/**
* 视图表前缀标志
*
* @var string
*/
protected $viewMark;
/**
* 构造方法
*
* @param array $config
*/
public function __construct(array $config = [])
{
if (!empty($config)) {
$this->config = array_merge($this->config, $config);
}
}
/**
* 设置DB配置
*
* @param array $config DB配置信息
* @return Dictionary
*/
public function setConfig(array $config)
{
$this->config = array_merge($this->config, $config);
return $this;
}
/**
* 设置视图前缀标志
*
* @param string $mark 视图前缀标志
* @return Dictionary
*/
public function setViewMark($mark)
{
$this->viewMark = $mark;
return $this;
}
/**
* 获取所有表
*
* @return array
*/
public function getTable()
{
$table = [];
$result = $this->query('SHOW TABLES');
// 取得所有的表名
foreach ($result as $index => $tableName) {
$table[]['TABLE_NAME'] = $tableName[0];
}
return $table;
}
/**
* 获取所有表信息
*
* @return array
*/
public function getTableInfo()
{
$tables = $this->getTable();
foreach ($tables as $k => $v) {
$sql = "SHOW INDEX FROM " . $v['TABLE_NAME'];
$result = $this->query($sql);
foreach ($result as $item) {
$tables[$k]['INDEX'][] = $item;
}
$sql = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '{$v['TABLE_NAME']}' AND table_schema = '{$this->config['database']}'";
$table_result = $this->query($sql);
foreach ($table_result as $item2) {
$tables[$k]['TABLE_COMMENT'][] = $item2['TABLE_COMMENT'];
}
$sql = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = '{$v['TABLE_NAME']}' AND table_schema = '{$this->config['database']}'";
$fields = [];
$filed_result = $this->query($sql);
foreach ($filed_result as $t) {
$fields[] = $t;
}
$tables[$k]['COLUMN'] = $fields;
}
return $tables;
}
/**
* 获取内容
*
* @param boolean $menu 是否需要菜单
* @return string
*/
public function getContent($menu = true)
{
// 获取所有表信息
$tables = $this->getTableInfo();
// 构造HTMl
$html = '';
if ($menu) {
// 循环生成右边导航栏
$html .= '<div class="left-side">' . "\n";
$html .= '<h2><a href="#TABLE">数据表</a></h2>' . "\n";
foreach ($tables as $key => $val) {
static $first = false;
if (!$first && !empty($this->viewMark)) {
if (strstr($val['TABLE_NAME'], $this->viewMark)) {
$first = true;
$html .= '<h2><a href="#TABLE">视图</a></h2>' . "\n";
}
}
$html .= '<a href="#' . $val['TABLE_NAME'] . '" class="tab-btn">' . $val['TABLE_NAME'] . '</a>' . "\n";
}
$html .= '</div>' . "\n";
}
// 循环生成左边数据表内容
$html .= '<div class="right-side">' . "\n";
foreach ($tables as $k => $v) {
// 数据表头
$html .= '<table class="dictionary" id="' . $v['TABLE_NAME'] . '">' . "\n";
$html .= '<colgroup class="data-table"><col/><col/><col/><col/><col/><col/></colgroup>';
$html .= '<thead>' . "\n";
$html .= '<tr><th colspan="6">数据表名称:' . $v['TABLE_NAME'] . '</th></tr>' . "\n";
$html .= '<tr><th colspan="6" align="left">表备注:' . (isset($v['TABLE_COMMENT'][0]) ? $v['TABLE_COMMENT'][0] : '') . '</th></tr>' . "\n";
$html .= '<tr>' . "\n";
$html .= '<th>字段名</th>' . "\n";
$html .= '<th>字段类型</th>' . "\n";
$html .= '<th>默认值</th>' . "\n";
$html .= '<th>允许为空</th>' . "\n";
$html .= '<th>自动递增</th>' . "\n";
$html .= '<th>备注</th>' . "\n";
$html .= '</tr>' . "\n";
$html .= '</thead>' . "\n";
// 数据表内容
$html .= '<tbody>' . "\n";
foreach ($v['COLUMN'] as $f) {
$html .= ' <tr>' . "\n";
$html .= ' <td>' . $f['COLUMN_NAME'] . '</td>' . "\n";
$html .= ' <td>' . $f['COLUMN_TYPE'] . '</td>' . "\n";
$html .= ' <td>' . $f['COLUMN_DEFAULT'] . '</td>' . "\n";
$html .= ' <td>' . $f['IS_NULLABLE'] . '</td>' . "\n";
$html .= ' <td>' . ($f['EXTRA'] == 'auto_increment' ? '是' : '&nbsp;') . '</td>' . "\n";
$html .= ' <td>' . $f['COLUMN_COMMENT'] . '</td>' . "\n";
$html .= ' </tr>';
}
$html .= '</tbody>' . "\n";
$html .= '</table>';
// 数据表索引表头
if (isset($v['INDEX']) && is_array($v['INDEX'])) {
$html .= '<table class="dictionary">' . "\n";
$html .= '<colgroup class="index-table"><col/><col/><col/></colgroup>';
$html .= '<thead>' . "\n";
$html .= '<tr><th colspan="3" align="left">索引信息:</th></tr>' . "\n";
$html .= '<tr>' . "\n";
$html .= '<th>字段名</th>' . "\n";
$html .= '<th>是否唯一</th>' . "\n";
$html .= '<th>索引名称</th>' . "\n";
$html .= '</tr>' . "\n";
$html .= '</thead>' . "\n";
foreach ($v['INDEX'] as $idx) {
$html .= '<tr>' . "\n";
$html .= '<td>' . $idx['Column_name'] . '</td>' . "\n";
$html .= '<td>' . ($idx['Non_unique'] ? '否' : '是') . '</td>' . "\n";
$html .= '<td>' . $idx['Key_name'] . '</td>' . "\n";
$html .= '</tr>';
}
}
$html .= '</table>' . "\n" . '<br>' . "\n" . '<br>' . "\n";
}
$html .= '</div>' . "\n";
return $html;
}
/**
* 获取HTML内容
*
* @return string
*/
public function getHTML()
{
$content = $this->getContent();
$html = "<!doctype html>
<html>
<head>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">
<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">
<title>{$this->config['database']}库数据字典</title>
<style>
html{color:#000;background:#ECEADF}
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,form,input,textarea,p,blockquote,th,td{margin:0;padding:0;font-size:14px;font-family:Arial,sans-serif}
table{border-collapse:collapse;border-spacing:0;table-layout: fixed;}
a{color:#00e;}
h2{padding-left:10px;}
h2 a{text-decoration: none;}
.tab-btn{display:block; padding-left:30px; height:26px; line-height: 26px; text-decoration: none;font-weight: bold;border-radius: 5px;}
.left-side{width:240px; position: absolute; left: 0; top:0; bottom: 0; overflow-y: scroll; overflow-x: auto; padding: 20px 0 120px 0;}
.right-side{width:auto; position: absolute; left: 240px; right:0; top:0; bottom: 0; overflow-y: scroll; overflow-x: auto; padding:20px 0 10px 20px;}
.dictionary{border: none; border-left:1px #aaa solid; border-top:1px #aaa solid; width:98%; margin-top: 20px;}
.dictionary thead{background:#e0e0d0; }
.dictionary tr{height:25px;}
.dictionary tr:nth-child(2n){background: #e0e0d0;}
.dictionary tr:hover td{background: #654b24;color:#fff;}
.dictionary tr.selected td{background: #b83400;color: #fff;}
.dictionary tr:hover a,.dictionary tr:hover span,.dictionary tr.selected a,.dictionary tr.selected span{color:#fff;}
.dictionary tr th,.dictionary tr td{border-width:0 1px 1px 0; border-style: solid; border-color: #aaa; padding: 0 4px;overflow: hidden;}
.dictionary .data-table col:nth-child(1), .dictionary .data-table col:nth-child(2), .dictionary .data-table col:nth-child(3) {width: 140px;}
.dictionary .data-table col:nth-child(4), .dictionary .data-table col:nth-child(5) {width: 80px;}
.dictionary .data-table col:nth-child(6) {width: auto;}
.dictionary .index-table col:nth-child(1), .dictionary .index-table col:nth-child(2) {width: 140px;}
</style>
</head>
<body>
{$content}
</body>
</html>";
return $html;
}
/**
* 到处HTML数据字典
*
* @return void
*/
public function exportHTML()
{
header("Content-Type: application/html");
header("Content-Disposition: attachment; filename={$this->config['database']}库数据字典.html");
echo $this->getHTML();
}
/**
* 获取DB链接
*
* @throws PDOException
* @return PDO
*/
protected function getDB()
{
if (!$this->db) {
// 生成mysql连接dsn
$is_port = (isset($this->config['port']) && is_int($this->config['port'] * 1));
$dsn = 'mysql:host=' . $this->config['host'] . ($is_port ? ';port=' . $this->config['port'] : '') . ';dbname=' . $this->config['database'];
if (!empty($this->config['charset'])) {
$dsn .= ';charset=' . $this->config['charset'];
}
// 数据库连接参数
$params = [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false,
];
if (isset($config['params']) && is_array($config['params'])) {
$params = $config['params'] + $params;
}
// 链接
$this->db = new PDO(
$dsn,
$this->config['username'],
$this->config['password'],
$params
);
}
return $this->db;
}
/**
* 执行查询语句
*
* @param string $sql SQL语句
* @return array
*/
protected function query($sql)
{
$query = $this->getDB()->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_BOTH);
}
}

180
vendor/mongdch/mon-util/src/DocParse.php vendored Normal file
View File

@ -0,0 +1,180 @@
<?php
namespace mon\util;
use ReflectionClass;
use ReflectionMethod;
/**
* PHP文档解析
*
* @author Mon <985558837@qq.com>
* @version 1.0.0
*/
class DocParse
{
use Instance;
/**
* 解析类型对象, 获取类型注解文档
*
* @param string $class 对象名称
* @param integer $type ReflectionMethod对应的方法访问类型默认Public
* @return mixed
*/
public function parseClass($class, $type = ReflectionMethod::IS_PUBLIC)
{
if (class_exists($class)) {
$result = [];
$reflection = new ReflectionClass($class);
$method = $reflection->getMethods($type);
// 解析文档中所有的方法
foreach ($method as $action) {
$doc = $action->getDocComment();
$data = $this->parse($doc);
$result[$action->name] = $data;
}
return $result;
}
return false;
}
/**
* 解析文档,获取文档内容
*
* @param string $doc 注解文档内容
* @return array
*/
public function parse($doc)
{
// 解析注解文本块,获取文档内容
if (preg_match('#^/\*\*(.*)\*/#s', $doc, $comment) === false) {
return [];
}
$comment = trim($comment[1]);
// 获取所有行并去除第一个 * 字符
if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false) {
return [];
}
// 解析每行注解,获取对应的内容信息
$result = $this->parseLines($lines[1]);
return $result;
}
/**
* 遍历解析注解信息,整理获取注解内容
*
* @param array $lines 注解内容
* @return array
*/
protected function parseLines($lines)
{
$result = [];
$description = [];
foreach ($lines as $line) {
$lineData = $this->parseLine($line);
if (is_string($lineData)) {
$description[] = $lineData;
} else if (is_array($lineData)) {
$result[$lineData['type']][] = $lineData['data'];
}
}
$result['description'] = implode(PHP_EOL, $description);
return $result;
}
/**
* 逐行解析注解信息,获取注解内容
*
* @param string $line 行信息
* @return array|string
*/
protected function parseLine($line)
{
$content = trim($line);
if (mb_strpos($content, '@') === 0) {
if (mb_strpos($content, ' ') > 0) {
// 获取参数名称
$param = mb_substr($content, 1, mb_strpos($content, ' ') - 1);
// 获取值
$value = mb_substr($content, mb_strlen($param) + 2);
} else {
$param = mb_substr($content, 1);
$value = '';
}
// 解析行参数
switch ($param) {
case 'param':
$value = $this->formatParam($value);
break;
case 'return':
case 'throws':
$value = $this->formatResult($value);
break;
}
return [
'type' => $param,
'data' => $value
];
}
return $content;
}
/**
* 解析return或throws类型的参数
*
* @param string $string 注解字符串
* @return string|array
*/
protected function formatResult($string)
{
$string = trim($string);
if (mb_strpos($string, ' ') !== false) {
$data = explode(' ', $string, 3);
$type = $data[0];
$desc = isset($data[1]) ? $data[1] : '';
return [
'type' => $type,
'name' => '',
'desc' => trim($desc)
];
}
return $string;
}
/**
* 解析param类型的参数
*
* @param string $string 注解字符串
* @return string|array
*/
protected function formatParam($string)
{
$string = trim($string);
if (mb_strpos($string, ' ') !== false) {
$data = explode(' ', $string, 3);
$type = $data[0];
if (count($data) > 1) {
$name = $data[1];
$desc = $data[2];
} else {
$name = $data[1];
}
return [
'type' => $type,
'name' => $name,
'desc' => isset($desc) ? trim($desc) : ''
];
}
return $string;
}
}

554
vendor/mongdch/mon-util/src/File.php vendored Normal file
View File

@ -0,0 +1,554 @@
<?php
namespace mon\util;
use RuntimeException;
use DirectoryIterator;
use InvalidArgumentException;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
/**
* 文章操作类
*
* @author Mon <985558837@qq.com>
* @version 1.1.1 优化注解 2022-07-08
*/
class File
{
use Instance;
/**
* 字节格式化 把字节数格式为 B K M G T P E Z Y 描述的大小
*
* @param integer $size 大小
* @param integer $dec 精准度,小数位数
* @param boolean $toString 输出字符串
* @return array|string
*/
public function formatByte($size, $dec = 0, $toString = true)
{
$type = array("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB");
$pos = 0;
while ($size >= 1024) {
$size /= 1024;
$pos++;
}
$result = [
'size' => round($size, $dec),
'type' => $type[$pos]
];
return $toString ? ($result['size'] . ' ' . $result['type']) : $result;
}
/**
* 改变文件和目录的相关属性
*
* @param string $file 文件路径
* @param string $type 操作类型, 支持group、mode、ower
* @param mixed $ch_info 操作信息
* @throws InvalidArgumentException
* @return boolean
*/
public function changeAuth($file, $type, $ch_info)
{
switch ($type) {
case 'group':
// 改变文件组。
return chgrp($file, $ch_info);
case 'mode':
// 改变文件模式。
return chmod($file, $ch_info);
case 'ower':
// 改变文件所有者。
return chown($file, $ch_info);
default:
throw new InvalidArgumentException("type prams invalid.[group|mode|ower]");
}
}
/**
* 获取上传文件信息
*
* @param string $field $_FILES 字段索引
* @param array $files 文件信息源,默认$_FILES
* @return array
*/
public function uploadFileInfo($field, $files = [])
{
$files = empty($files) ? $_FILES : $files;
// 取得上传文件基本信息
$fileInfo = $files[$field];
$info = [];
// 取得文件类型
$info['type'] = strtolower(trim(stripslashes(preg_replace("/^(.+?);.*$/", "\\1", $fileInfo['type'])), '"'));
// 取得上传文件在服务器中临时保存目录
$info['temp'] = $fileInfo['tmp_name'];
// 取得上传文件大小
$info['size'] = $fileInfo['size'];
// 取得文件上传错误
$info['error'] = $fileInfo['error'];
// 取得上传文件名
$info['name'] = $fileInfo['name'];
// 取得上传文件后缀
$info['ext'] = $this->getExt($fileInfo['name']);
return $info;
}
/**
* 创建目录
*
* @param string $dirPath 目录路径
* @return boolean
*/
public function createDir($dirPath)
{
if (is_dir($dirPath)) {
return true;
}
return mkdir($dirPath, 0755, true);
}
/**
* 复制文件夹
*
* @param string $source 源文件夹
* @param string $dest 目标文件夹
* @return void
*/
public function copydir($source, $dest)
{
$this->createDir($dest);
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $item) {
if ($item->isDir()) {
$sontDir = $dest . '/' . $iterator->getSubPathName();
$this->createDir($sontDir);
} else {
copy($item, $dest . '/' . $iterator->getSubPathName());
}
}
}
/**
* 删除非空目录
* 说明:只能删除非系统和特定权限的文件,否则会出现错误
*
* @param string $dirPath 目录路径
* @param boolean $all 是否删除所有
* @return boolean
*/
public function removeDir($dirPath, $all = false)
{
$dirName = $this->pathReplace($dirPath);
$handle = @opendir($dirName);
while (($file = @readdir($handle)) !== FALSE) {
if ($file != '.' && $file != '..') {
$dir = $dirName . '/' . $file;
if ($all) {
is_dir($dir) ? $this->removeDir($dir) : $this->removeFile($dir);
} else {
if (is_file($dir)) {
$this->removeFile($dir);
}
}
}
}
closedir($handle);
return @rmdir($dirName);
}
/**
* 获取指定目录的信息
*
* @param string $dir 目录路径
* @return array
*/
public function getDirInfo($dir)
{
$handle = @opendir($dir); //打开指定目录
$directory_count = 0;
$total_size = 0;
$file_cout = 0;
while (false !== ($path = readdir($handle))) {
if ($path != "." && $path != "..") {
$next_path = $dir . '/' . $path;
if (is_dir($next_path)) {
$directory_count++;
$result_value = $this->getDirInfo($next_path);
$total_size += $result_value['size'];
$file_cout += $result_value['filecount'];
$directory_count += $result_value['dircount'];
} elseif (is_file($next_path)) {
$total_size += filesize($next_path);
$file_cout++;
}
}
}
closedir($handle); //关闭指定目录
$result_value['size'] = $total_size;
$result_value['filecount'] = $file_cout;
$result_value['dircount'] = $directory_count;
return $result_value;
}
/**
* 获取目录内容
*
* @param string $dir 目录路径
* @throws InvalidArgumentException
* @return array
*/
public function getDirContent($dir)
{
if (!is_dir($dir)) {
throw new InvalidArgumentException("dir path is not dir!");
}
//遍历目录取得文件信息
$data = [];
if ($handle = opendir($dir)) {
$i = 0;
while (false !== ($filename = readdir($handle))) {
if (mb_strpos($filename, '.') === 0) {
continue;
}
$file = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename;
if (is_dir($file)) {
// 是否文件夹
$data[$i]['is_dir'] = true;
// 文件夹是否包含文件
$data[$i]['has_file'] = (count(scandir($file)) > 2);
// 文件大小
$data[$i]['filesize'] = 0;
// 文件类别,用扩展名判断
$data[$i]['filetype'] = '';
} else {
$data[$i]['is_dir'] = false;
$data[$i]['has_file'] = false;
$data[$i]['filesize'] = filesize($file);
$data[$i]['filetype'] = $this->getExt($file);
}
// 文件名,包含扩展名
$data[$i]['filename'] = $filename;
// 文件最后修改时间
$data[$i]['datetime'] = date('Y-m-d H:i:s', filemtime($file));
$i++;
}
closedir($handle);
}
return $data;
}
/**
* 创建文件
*
* @param string $content 写入内容
* @param string $path 文件路径
* @param boolean $append 存在文件是否继续写入
* @return boolean|integer
*/
public function createFile($content, $path, $append = true)
{
$dirPath = dirname($path);
is_dir($dirPath) || $this->createDir($dirPath);
if ($append) {
// 添加写入
return file_put_contents($path, $content, FILE_APPEND);
} else {
// 重新写入
return file_put_contents($path, $content);
}
}
/**
* 删除文件
*
* @param string $path 文件路径
* @return boolean
*/
public function removeFile($path)
{
$path = $this->pathReplace($path);
if (file_exists($path)) {
return unlink($path);
}
return true;
}
/**
* 获取完整文件名称
*
* @param string $path 目录路径
* @return string
*/
public function getBaseName($path)
{
return basename(str_replace('\\', '/', $this->pathReplace($path)));
}
/**
* 获取文件后缀名
*
* @param string $path 文件路径
* @return string
*/
public function getExt($path)
{
return pathinfo($this->pathReplace($path), PATHINFO_EXTENSION);
}
/**
* 重命名文件
*
* @param string $oldFileName 旧名称
* @param string $newFileNmae 新名称
* @return boolean
*/
public function rename($oldFileName, $newFileNmae)
{
if (($oldFileName != $newFileNmae) && is_writable($oldFileName)) {
return rename($oldFileName, $newFileNmae);
}
return false;
}
/**
* 读取文件内容
*
* @param string $file 文件路径
* @throws InvalidArgumentException
* @return string
*/
public function read($file)
{
if (!file_exists($file)) {
throw new InvalidArgumentException('file not found[' . $file . ']');
}
return file_get_contents($file);
}
/**
* 获取文件信息
*
* @param string $file 文件路径
* @return array
*/
public function getFileInfo($file)
{
$info = [];
// 返回路径中的文件名部分
$info['filename'] = basename($file);
// 返回绝对路径名
$info['pathname'] = realpath($file);
// 文件的 user ID (所有者)
$info['owner'] = fileowner($file);
// 返回文件的 inode 编号
$info['perms'] = fileperms($file);
// 返回文件的 inode 编号
$info['inode'] = fileinode($file);
// 返回文件的组 ID
$info['group'] = filegroup($file);
// 返回路径中的目录名称部分
$info['path'] = dirname($file);
// 返回文件的上次访问时间
$info['atime'] = fileatime($file);
// 返回文件的上次改变时间
$info['ctime'] = filectime($file);
// 返回文件的权限
$info['perms'] = fileperms($file);
// 返回文件大小
$info['size'] = filesize($file);
// 返回文件类型
$info['type'] = filetype($file);
// 返回文件后缀名
$info['ext'] = is_file($file) ? pathinfo($file, PATHINFO_EXTENSION) : '';
// 返回文件的上次修改时间
$info['mtime'] = filemtime($file);
// 判断指定的文件名是否是一个目录
$info['isDir'] = is_dir($file);
// 判断指定文件是否为常规的文件
$info['isFile'] = is_file($file);
// 判断指定的文件是否是连接
$info['isLink'] = is_link($file);
// 判断文件是否可读
$info['isReadable'] = is_readable($file);
// 判断文件是否可写
$info['isWritable'] = is_writable($file);
// 判断文件是否是通过 HTTP POST 上传的
$info['isUpload'] = is_uploaded_file($file);
return $info;
}
/**
* 分卷记录文件
*
* @param string $content 记录的内容
* @param string $path 保存的路径, 不含后缀
* @param integer $maxSize 文件最大尺寸
* @param string $rollNum 分卷数
* @param string $postfix 文件后缀
* @throws RuntimeException
* @return boolean|integer
*/
public function subsectionFile($content, $path, $maxSize = 20480000, $rollNum = 3, $postfix = '.log')
{
$destination = $path . $postfix;
$contentLength = mb_strlen($content);
// 判断写入内容的大小
if ($contentLength > $maxSize) {
throw new RuntimeException("Save content size cannot exceed {$maxSize}, content size: {$contentLength}");
}
// 判断记录文件是否已存在,存在时文件大小不足写入
elseif (file_exists($destination) && floor($maxSize) < (filesize($destination) + $contentLength)) {
// 超出剩余写入大小,分卷写入
$this->shiftFile($path, (int) $rollNum, $postfix);
return $this->createFile($content, $destination, false);
}
// 不存在文件或文件大小足够继续写入
else {
return $this->createFile($content, $destination);
}
}
/**
* 获取路径下所有的内容及后代内容
*
* @param string $path 路径
* @param boolean $tree 输出树结构还是数组
* @return array
*/
public function getFoldersContent($path, $tree = false)
{
if ((!file_exists($path) || !is_dir($path))) {
return [];
}
$dir = new DirectoryIterator($path);
return $tree ? $this->directoryIteratorToTree($dir) : $this->directoryIteratorToArray($dir);
}
/**
* 获取路径下所有的内容及后代内容转数组辅助方法
*
* @param DirectoryIterator $dir
* @return array
*/
protected function directoryIteratorToArray(DirectoryIterator $dir)
{
$result = [];
foreach ($dir as $key => $child) {
if ($child->isDot()) {
continue;
}
$name = $child->getBasename();
if ($child->isDir()) {
$subit = new DirectoryIterator($child->getPathname());
$result[$name] = $this->DirectoryIteratorToArray($subit);
} else {
$result[] = $name;
}
}
return $result;
}
/**
* 获取路径下所有的内容及后代内容转树结构辅助方法
*
* @param DirectoryIterator $dir
* @return array
*/
protected function directoryIteratorToTree(DirectoryIterator $dir)
{
$result = [];
foreach ($dir as $key => $child) {
if ($child->isDot()) {
continue;
}
$name = $child->getBasename();
if ($child->isDir()) {
$path = $child->getPathname();
$subit = new DirectoryIterator($path);
$result[$key] = [
'children' => $this->directoryIteratorToTree($subit),
'title' => $name,
'path' => $path,
];
} else {
$result[$key] = [
'title' => $name,
'path' => $child->getPathname(),
];
}
}
return $result;
}
/**
* 分卷重命名文件
*
* @param string $path 文件路径
* @param int $rollNum 分卷数
* @param string $postfix 后缀名
* @throws RuntimeException
* @return void
*/
protected function shiftFile($path, $rollNum, $postfix = '.log')
{
// 判断是否存在最老的一份文件,存在则删除
$oldest = $this->buildShiftName($path, ($rollNum - 1));
$oldestFile = $oldest . $postfix;
if (!$this->removeFile($oldestFile)) {
throw new RuntimeException("Failed to delete old file, oldFileName: {$oldestFile}");
}
// 循环重命名文件
for ($i = ($rollNum - 2); $i >= 0; $i--) {
// 最新的一卷不需要加上分卷号
if ($i == 0) {
$oldFile = $path;
} else {
// 获取分卷号文件名称
$oldFile = $this->buildShiftName($path, $i);
}
// 重命名文件
$oldFileName = $oldFile . $postfix;
if (file_exists($oldFileName)) {
$newFileNmae = $this->buildShiftName($path, ($i + 1)) . $postfix;
// 重命名
if (!$this->rename($oldFile, $newFileNmae)) {
throw new RuntimeException("Failed to rename volume file name, oldFileName: {$oldFileName}, newFileNmae: {$newFileNmae}");
}
}
}
}
/**
* 构造分卷文件名称
*
* @param string $fileName 文件名称,不含后缀
* @param integer $num 分卷数
* @return string
*/
protected function buildShiftName($fileName, $num)
{
return $fileName . '_' . $num;
}
/**
* 路径替换相应的字符
*
* @param string $path 路径
* @return string
*/
protected function pathReplace($path)
{
return str_replace('//', '/', str_replace('\\', '/', $path));
}
}

494
vendor/mongdch/mon-util/src/Gif.php vendored Normal file
View File

@ -0,0 +1,494 @@
<?php
namespace mon\util;
use Exception;
use mon\util\exception\ImgException;
/**
* GIF图像处理
*/
class GIF
{
/**
* GIF帧列表
*
* @var array
*/
private $frames = [];
/**
* 每帧等待时间列表
*
* @var array
*/
private $delays = [];
/**
* 构造方法用于解码GIF图片
*
* @param string $src GIF图片数据
* @param string $mod 图片数据类型
* @throws ImgException
*/
public function __construct($src = null, $mod = 'url')
{
if (!is_null($src)) {
if ('url' == $mod && is_file($src)) {
$src = file_get_contents($src);
}
// 解码GIF图片
try {
$de = new GIFDecoder($src);
$this->frames = $de->GIFGetFrames();
$this->delays = $de->GIFGetDelays();
} catch (Exception $e) {
throw new ImgException("解码GIF图片出错!" . $e->getMessage(), ImgException::ERROR_GIT_PARSE);
}
}
}
/**
* 设置或获取当前帧的数据
*
* @param string $stream 二进制数据流
* @return mixed 获取到的数据
*/
public function image($stream = null)
{
if (is_null($stream)) {
$current = current($this->frames);
return false === $current ? reset($this->frames) : $current;
} else {
$this->frames[key($this->frames)] = $stream;
}
}
/**
* 将当前帧移动到下一帧
*
* @return mixed 当前帧数据
*/
public function nextImage()
{
return next($this->frames);
}
/**
* 保存当前GIF图片
*
* @param string $gifname 图片名称
* @return boolean|integer
*/
public function save($gifname)
{
$gif = new GIFEncoder($this->frames, $this->delays, 0, 2, 0, 0, 0, 'bin');
return file_put_contents($gifname, $gif->GetAnimation());
}
}
/**
* 解析GIF
*/
class GIFEncoder
{
private $GIF = "GIF89a";
private $VER = "GIFEncoder V2.05";
private $BUF = [];
private $LOP = 0;
private $DIS = 2;
private $COL = -1;
private $IMG = -1;
private $ERR = array(
'ERR00' => "Does not supported function for only one image!",
'ERR01' => "Source is not a GIF image!",
'ERR02' => "Unintelligible flag ",
'ERR03' => "Does not make animation from animated GIF source",
);
/**
* 构造方法
*
* @param [type] $GIF_src
* @param [type] $GIF_dly
* @param [type] $GIF_lop
* @param [type] $GIF_dis
* @param [type] $GIF_red
* @param [type] $GIF_grn
* @param [type] $GIF_blu
* @param [type] $GIF_mod
*/
public function __construct($GIF_src, $GIF_dly, $GIF_lop, $GIF_dis, $GIF_red, $GIF_grn, $GIF_blu, $GIF_mod)
{
if (!is_array($GIF_src)) {
printf("%s: %s", $this->VER, $this->ERR['ERR00']);
exit(0);
}
$this->LOP = ($GIF_lop > -1) ? $GIF_lop : 0;
$this->DIS = ($GIF_dis > -1) ? (($GIF_dis < 3) ? $GIF_dis : 3) : 2;
$this->COL = ($GIF_red > -1 && $GIF_grn > -1 && $GIF_blu > -1) ? ($GIF_red | ($GIF_grn << 8) | ($GIF_blu << 16)) : -1;
for ($i = 0; $i < count($GIF_src); $i++) {
if (strToLower($GIF_mod) == "url") {
$this->BUF[] = fread(fopen($GIF_src[$i], "rb"), filesize($GIF_src[$i]));
} else if (strToLower($GIF_mod) == "bin") {
$this->BUF[] = $GIF_src[$i];
} else {
printf("%s: %s ( %s )!", $this->VER, $this->ERR['ERR02'], $GIF_mod);
exit(0);
}
if (substr($this->BUF[$i], 0, 6) != "GIF87a" && substr($this->BUF[$i], 0, 6) != "GIF89a") {
printf("%s: %d %s", $this->VER, $i, $this->ERR['ERR01']);
exit(0);
}
for ($j = (13 + 3 * (2 << (ord($this->BUF[$i][10]) & 0x07))), $k = TRUE; $k; $j++) {
switch ($this->BUF[$i][$j]) {
case "!":
if ((substr($this->BUF[$i], ($j + 3), 8)) == "NETSCAPE") {
printf("%s: %s ( %s source )!", $this->VER, $this->ERR['ERR03'], ($i + 1));
exit(0);
}
break;
case ";":
$k = FALSE;
break;
}
}
}
$this->GIFAddHeader();
for ($i = 0; $i < count($this->BUF); $i++) {
$this->GIFAddFrames($i, $GIF_dly[$i]);
}
$this->GIFAddFooter();
}
/**
* GIFAddHeader
*/
private function GIFAddHeader()
{
$cmap = 0;
if (ord($this->BUF[0][10]) & 0x80) {
$cmap = 3 * (2 << (ord($this->BUF[0][10]) & 0x07));
$this->GIF .= substr($this->BUF[0], 6, 7);
$this->GIF .= substr($this->BUF[0], 13, $cmap);
$this->GIF .= "!\377\13NETSCAPE2.0\3\1" . $this->GIFWord($this->LOP) . "\0";
}
}
/**
* GIFAddFrames
*
* @param [type] $i
* @param [type] $d
* @return void
*/
private function GIFAddFrames($i, $d)
{
$Locals_str = 13 + 3 * (2 << (ord($this->BUF[$i][10]) & 0x07));
$Locals_end = strlen($this->BUF[$i]) - $Locals_str - 1;
$Locals_tmp = substr($this->BUF[$i], $Locals_str, $Locals_end);
$Global_len = 2 << (ord($this->BUF[0][10]) & 0x07);
$Locals_len = 2 << (ord($this->BUF[$i][10]) & 0x07);
$Global_rgb = substr($this->BUF[0], 13, 3 * (2 << (ord($this->BUF[0][10]) & 0x07)));
$Locals_rgb = substr($this->BUF[$i], 13, 3 * (2 << (ord($this->BUF[$i][10]) & 0x07)));
$Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 0) .
chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . "\x0\x0";
if ($this->COL > -1 && ord($this->BUF[$i][10]) & 0x80) {
for ($j = 0; $j < (2 << (ord($this->BUF[$i][10]) & 0x07)); $j++) {
if (
ord($Locals_rgb[3 * $j + 0]) == (($this->COL >> 16) & 0xFF) &&
ord($Locals_rgb[3 * $j + 1]) == (($this->COL >> 8) & 0xFF) &&
ord($Locals_rgb[3 * $j + 2]) == (($this->COL >> 0) & 0xFF)
) {
$Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 1) . chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . chr($j) . "\x0";
break;
}
}
}
switch ($Locals_tmp[0]) {
case "!":
$Locals_img = substr($Locals_tmp, 8, 10);
$Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18);
break;
case ",":
$Locals_img = substr($Locals_tmp, 0, 10);
$Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10);
break;
}
if (ord($this->BUF[$i][10]) & 0x80 && $this->IMG > -1) {
if ($Global_len == $Locals_len) {
if ($this->GIFBlockCompare($Global_rgb, $Locals_rgb, $Global_len)) {
$this->GIF .= ($Locals_ext . $Locals_img . $Locals_tmp);
} else {
$byte = ord($Locals_img[9]);
$byte |= 0x80;
$byte &= 0xF8;
$byte |= (ord($this->BUF[0][10]) & 0x07);
$Locals_img[9] = chr($byte);
$this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp);
}
} else {
$byte = ord($Locals_img[9]);
$byte |= 0x80;
$byte &= 0xF8;
$byte |= (ord($this->BUF[$i][10]) & 0x07);
$Locals_img[9] = chr($byte);
$this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp);
}
} else {
$this->GIF .= ($Locals_ext . $Locals_img . $Locals_tmp);
}
$this->IMG = 1;
}
/**
* GIFAddFooter
*
* @return void
*/
private function GIFAddFooter()
{
$this->GIF .= ";";
}
/**
* GIFBlockCompare
*
* @param [type] $GlobalBlock
* @param [type] $LocalBlock
* @param [type] $Len
* @return void
*/
private function GIFBlockCompare($GlobalBlock, $LocalBlock, $Len)
{
for ($i = 0; $i < $Len; $i++) {
if (
$GlobalBlock[3 * $i + 0] != $LocalBlock[3 * $i + 0] ||
$GlobalBlock[3 * $i + 0] != $LocalBlock[3 * $i + 0] ||
$GlobalBlock[3 * $i + 2] != $LocalBlock[3 * $i + 2]
) {
return (0);
}
}
return (1);
}
/**
* GIFWord
*
* @param [type] $int
* @return void
*/
private function GIFWord($int)
{
return (chr($int & 0xFF) . chr(($int >> 8) & 0xFF));
}
/**
* GetAnimation
*
* @return void
*/
public function GetAnimation()
{
return ($this->GIF);
}
}
/**
* 解析GIF
*/
class GIFDecoder
{
private $GIF_buffer = [];
private $GIF_arrays = [];
private $GIF_delays = [];
private $GIF_stream = "";
private $GIF_string = "";
private $GIF_bfseek = 0;
private $GIF_screen = [];
private $GIF_global = [];
private $GIF_sorted;
private $GIF_colorS;
private $GIF_colorC;
private $GIF_colorF;
/**
* 构造方法
*
* @param [type] $GIF_pointer
*/
public function __construct($GIF_pointer)
{
$this->GIF_stream = $GIF_pointer;
$this->GIFGetByte(6); // GIF89a
$this->GIFGetByte(7); // Logical Screen Descriptor
$this->GIF_screen = $this->GIF_buffer;
$this->GIF_colorF = $this->GIF_buffer[4] & 0x80 ? 1 : 0;
$this->GIF_sorted = $this->GIF_buffer[4] & 0x08 ? 1 : 0;
$this->GIF_colorC = $this->GIF_buffer[4] & 0x07;
$this->GIF_colorS = 2 << $this->GIF_colorC;
if ($this->GIF_colorF == 1) {
$this->GIFGetByte(3 * $this->GIF_colorS);
$this->GIF_global = $this->GIF_buffer;
}
for ($cycle = 1; $cycle;) {
if ($this->GIFGetByte(1)) {
switch ($this->GIF_buffer[0]) {
case 0x21:
$this->GIFReadExtensions();
break;
case 0x2C:
$this->GIFReadDescriptor();
break;
case 0x3B:
$cycle = 0;
break;
}
} else {
$cycle = 0;
}
}
}
/**
* GIFReadExtension
*
* @return void
*/
private function GIFReadExtensions()
{
$this->GIFGetByte(1);
for (;;) {
$this->GIFGetByte(1);
if (($u = $this->GIF_buffer[0]) == 0x00) {
break;
}
$this->GIFGetByte($u);
if ($u == 4) {
$this->GIF_delays[] = ($this->GIF_buffer[1] | $this->GIF_buffer[2] << 8);
}
}
}
/**
* GIFReadExtension
*
* @return void
*/
private function GIFReadDescriptor()
{
$GIF_screen = [];
$this->GIFGetByte(9);
$GIF_screen = $this->GIF_buffer;
$GIF_colorF = $this->GIF_buffer[8] & 0x80 ? 1 : 0;
if ($GIF_colorF) {
$GIF_code = $this->GIF_buffer[8] & 0x07;
$GIF_sort = $this->GIF_buffer[8] & 0x20 ? 1 : 0;
} else {
$GIF_code = $this->GIF_colorC;
$GIF_sort = $this->GIF_sorted;
}
$GIF_size = 2 << $GIF_code;
$this->GIF_screen[4] &= 0x70;
$this->GIF_screen[4] |= 0x80;
$this->GIF_screen[4] |= $GIF_code;
if ($GIF_sort) {
$this->GIF_screen[4] |= 0x08;
}
$this->GIF_string = "GIF87a";
$this->GIFPutByte($this->GIF_screen);
if ($GIF_colorF == 1) {
$this->GIFGetByte(3 * $GIF_size);
$this->GIFPutByte($this->GIF_buffer);
} else {
$this->GIFPutByte($this->GIF_global);
}
$this->GIF_string .= chr(0x2C);
$GIF_screen[8] &= 0x40;
$this->GIFPutByte($GIF_screen);
$this->GIFGetByte(1);
$this->GIFPutByte($this->GIF_buffer);
for (;;) {
$this->GIFGetByte(1);
$this->GIFPutByte($this->GIF_buffer);
if (($u = $this->GIF_buffer[0]) == 0x00) {
break;
}
$this->GIFGetByte($u);
$this->GIFPutByte($this->GIF_buffer);
}
$this->GIF_string .= chr(0x3B);
$this->GIF_arrays[] = $this->GIF_string;
}
/**
* GIFGetByte
*
* @param [type] $len
* @return void
*/
private function GIFGetByte($len)
{
$this->GIF_buffer = [];
for ($i = 0; $i < $len; $i++) {
if ($this->GIF_bfseek > strlen($this->GIF_stream)) {
return 0;
}
$this->GIF_buffer[] = ord($this->GIF_stream[$this->GIF_bfseek++]);
}
return 1;
}
/**
* GIFPutByte
*
* @param [type] $bytes
* @return void
*/
private function GIFPutByte($bytes)
{
for ($i = 0; $i < count($bytes); $i++) {
$this->GIF_string .= chr($bytes[$i]);
}
}
/**
* GIFGetFrames
*
* @return void
*/
public function GIFGetFrames()
{
return ($this->GIF_arrays);
}
/**
* GIFGetDelays
*
* @return void
*/
public function GIFGetDelays()
{
return ($this->GIF_delays);
}
}

View File

@ -0,0 +1,495 @@
<?php
namespace mon\util;
use mon\util\exception\IPLocationException;
/**
* 基于纯真IP库的ip地址定位解析GBK数据输出UTF8编码地址
*
* @see https://cz88.net/ 可通过纯真IP库官网下载最新版IP库文件
* @author Mon <985558837@qq.com>
* @version 1.0.1 优化注解 2022-07-08
*/
class IPLocation
{
use Instance;
/**
* qqwry.dat 文件指针
*
* @var resource
*/
private $fp;
/**
* 第一条IP记录的偏移地址
*
* @var integer
*/
private $firstip;
/**
* 最后一条IP记录的偏移地址
*
* @var integer
*/
private $lastip;
/**
* IP记录的总条数不包含版本信息记录
*
* @var integer
*/
private $totalip;
/**
* 运营商
*
* @var array
*/
private $dict_isp = ['联通', '移动', '铁通', '电信', '长城', '鹏博士'];
/**
* 直辖市
*
* @var array
*/
private $dict_city_directly = ['北京', '天津', '重庆', '上海'];
/**
* 省份
*
* @var array
*/
private $dict_province = [
'北京', '天津', '重庆', '上海', '河北', '山西', '辽宁', '吉林', '黑龙江', '江苏', '浙江', '安徽', '福建', '江西', '山东', '河南', '湖北', '湖南',
'广东', '海南', '四川', '贵州', '云南', '陕西', '甘肃', '青海', '台湾', '内蒙古', '广西', '宁夏', '新疆', '西藏', '香港', '澳门'
];
/**
* 初始化标志
*
* @var boolean
*/
private $init = false;
/**
* 构造方法
*
* @param string $db IP库文件路径
*/
public function __construct($db = '')
{
if (!empty($db)) {
$this->init($db);
}
}
/**
* 析构函数关闭打开的IP库文件
*/
public function __destruct()
{
if ($this->fp) {
fclose($this->fp);
}
$this->fp = 0;
}
/**
* 初始化
*
* @param string $db IP库文件路径
* @return IPLocation
*/
public function init($db)
{
if (!file_exists($db)) {
throw new IPLocationException('IP数据文件未找到', IPLocationException::ERROR_DATA_NOT_FOUND);
}
$this->fp = 0;
if (($this->fp = fopen($db, 'rb')) !== false) {
$this->firstip = $this->getlong();
$this->lastip = $this->getlong();
$this->totalip = ($this->lastip - $this->firstip) / 7;
}
$this->init = true;
return $this;
}
/**
* 获取IP所在位置信息
* province city county isp 对中国以外的ip无法识别
*
* <code>
* $result 是返回的数组
* $result['ip'] 输入的ip
* $result['country'] 国家 中国
* $result['province'] 省份信息 河北省
* $result['city'] 市区 邢台市
* $result['county'] 郡县 威县
* $result['isp'] 运营商 联通
* $result['area'] 最完整的信息 中国河北省邢台市威县新科网吧(北外街)
* </code>
*
* @param string $ip IP地址
* @throws IPLocationException
* @return array
*/
public function getLocation($ip)
{
if (!$this->init) {
throw new IPLocationException('未初始化实例', IPLocationException::ERROR_NOT_INIT);
}
$result = [];
$location = [];
$is_china = false;
$seperator_sheng = '省';
$seperator_shi = '市';
$seperator_xian = '县';
$seperator_qu = '区';
if (!$this->isValidIpV4($ip)) {
// 验证IP格式
throw new IPLocationException('无效的IPv4地址', IPLocationException::ERROR_IPV4_FAILD);
} else {
// 获取所在地区信息
$location = $this->getLocationFromIP($ip);
if (!$location) {
throw new IPLocationException('读取IP数据文件失败', IPLocationException::ERROR_DATA_READ_FAILD);
}
// 原国家数据(例:北京市朝阳区)
$location['org_country'] = $location['country'];
// 原地区数据(例:// 金桥国际小区)
$location['org_area'] = $location['area'];
// 定义省市区参数
$location['province'] = '';
$location['city'] = '';
$location['county'] = '';
// 获取省市区信息
$_tmp_province = explode($seperator_sheng, $location['country']);
// 存在 省 标志 xxx省yyyy 中的yyyy
if (isset($_tmp_province[1])) {
$is_china = true;
$location['province'] = $_tmp_province[0];
// 存在市
if (mb_strpos($_tmp_province[1], $seperator_shi) !== false) {
$_tmp_city = explode($seperator_shi, $_tmp_province[1]);
$location['city'] = $_tmp_city[0] . $seperator_shi;
// 存在县
if (isset($_tmp_city[1])) {
if (mb_strpos($_tmp_city[1], $seperator_xian) !== false) {
$_tmp_county = explode($seperator_xian, $_tmp_city[1]);
$location['county'] = $_tmp_county[0] . $seperator_xian;
}
// 存在区
if (!$location['county'] && mb_strpos($_tmp_city[1], $seperator_qu) !== false) {
$_tmp_qu = explode($seperator_qu, $_tmp_city[1]);
$location['county'] = $_tmp_qu[0] . $seperator_qu;
}
}
}
} else {
// 处理如内蒙古等不带省份类型的和直辖市
foreach ($this->dict_province as $key => $value) {
if (false !== mb_strpos($location['country'], $value)) {
$is_china = true;
// 存在直辖市
if (in_array($value, $this->dict_city_directly)) {
$_tmp_province = explode($seperator_shi, $location['country']);
// 上海市浦江区xxx
if ($_tmp_province[0] == $value) {
$location['province'] = $_tmp_province[0];
// 市辖区
if (isset($_tmp_province[1])) {
if (mb_strpos($_tmp_province[1], $seperator_qu) !== false) {
$_tmp_qu = explode($seperator_qu, $_tmp_province[1]);
$location['city'] = $_tmp_qu[0] . $seperator_qu;
}
}
} else {
// 上海交通大学
$location['province'] = $value;
$location['org_area'] = $location['org_country'] . $location['org_area'];
}
} else {
// 省
$location['province'] = $value;
//没有省份标志 只能替换
$_tmp_city = str_replace($location['province'], '', $location['country']);
//防止直辖市捣乱 上海市xxx区 =》 市xx区
$_tmp_shi_pos = mb_stripos($_tmp_city, $seperator_shi);
if ($_tmp_shi_pos === 0) {
$_tmp_city = mb_substr($_tmp_city, 1);
}
// 内蒙古类型的获取市县信息
if (mb_strpos($_tmp_city, $seperator_shi) !== false) {
// 市
$_tmp_city = explode($seperator_shi, $_tmp_city);
$location['city'] = $_tmp_city[0] . $seperator_shi;
// 县
if (isset($_tmp_city[1])) {
if (mb_strpos($_tmp_city[1], $seperator_xian) !== false) {
$_tmp_county = explode($seperator_xian, $_tmp_city[1]);
$location['county'] = $_tmp_county[0] . $seperator_xian;
}
// 区
if (!$location['county'] && mb_strpos($_tmp_city[1], $seperator_qu) !== false) {
$_tmp_qu = explode($seperator_qu, $_tmp_city[1]);
$location['county'] = $_tmp_qu[0] . $seperator_qu;
}
}
}
}
break;
}
}
}
// 标志国家为中国
if ($is_china) {
$location['country'] = '中国';
}
// 获取运营商信息
$location['isp'] = $this->getIsp($location['area']);
// $result['beginip'] = $location['beginip'];
// $result['endip'] = $location['endip'];
// $result['org_country'] = $location['org_country'];
// $result['org_area'] = $location['org_area'];
// 返回数据
$result['ip'] = $location['ip'];
$result['country'] = $location['country'];
$result['province'] = $location['province'];
$result['city'] = $location['city'];
$result['county'] = $location['county'];
$result['isp'] = $location['isp'];
$result['area'] = $location['country'] . $location['province'] . $location['city'] . $location['county'] . $location['org_area'];
}
return $result;
}
/**
* 根据所给IP地址或域名获取所在地区信息
*
* @param string $ip IP地址
* @return array
*/
private function getLocationFromIP($ip)
{
// 如果数据文件没有被正确打开,则直接返回空
if (!$this->fp) {
return null;
}
$location['ip'] = $ip;
// 将输入的IP地址转化为可比较的IP地址不合法的IP地址会被转化为255.255.255.255
$ip = $this->packip($location['ip']);
// 搜索的下边界
$l = 0;
// 搜索的上边界
$u = $this->totalip;
// 如果没有找到就返回最后一条IP记录qqwry.dat的版本信息
$findip = $this->lastip;
// 当上边界小于下边界时,查找失败
while ($l <= $u) {
// 计算近似中间记录
$i = floor(($l + $u) / 2);
fseek($this->fp, $this->firstip + $i * 7);
// 获取中间记录的开始IP地址
$beginip = strrev(fread($this->fp, 4));
// strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式以便用于比较后面相同。
if ($ip < $beginip) {
// 用户的IP小于中间记录的开始IP地址时将搜索的上边界修改为中间记录减一
$u = $i - 1;
} else {
fseek($this->fp, $this->getlong3());
// 获取中间记录的结束IP地址
$endip = strrev(fread($this->fp, 4));
if ($ip > $endip) {
// 用户的IP大于中间记录的结束IP地址时将搜索的下边界修改为中间记录加一
$l = $i + 1;
} else {
// 用户的IP在中间记录的IP范围内时则表示找到结果退出循环
$findip = $this->firstip + $i * 7;
break;
}
}
}
// 获取查找到的IP地理位置信息
fseek($this->fp, $findip);
// 用户IP所在范围的开始地址
$location['beginip'] = long2ip($this->getlong());
$offset = $this->getlong3();
fseek($this->fp, $offset);
// 用户IP所在范围的结束地址
$location['endip'] = long2ip($this->getlong());
$byte = fread($this->fp, 1);
switch (ord($byte)) {
case 1:
// 标志字节为1表示国家和区域信息都被同时重定向
$countryOffset = $this->getlong3();
fseek($this->fp, $countryOffset);
$byte = fread($this->fp, 1);
switch (ord($byte)) {
case 2:
// 标志字节为2表示国家信息被重定向
fseek($this->fp, $this->getlong3());
$location['country'] = $this->getstring();
fseek($this->fp, $countryOffset + 4);
$location['area'] = $this->getarea();
break;
default:
// 否则,表示国家信息没有被重定向
$location['country'] = $this->getstring($byte);
$location['area'] = $this->getarea();
break;
}
break;
case 2:
// 标志字节为2表示国家信息被重定向
fseek($this->fp, $this->getlong3());
$location['country'] = $this->getstring();
fseek($this->fp, $offset + 8);
$location['area'] = $this->getarea();
break;
default:
// 否则,表示国家信息没有被重定向
$location['country'] = $this->getstring($byte);
$location['area'] = $this->getarea();
break;
}
// GBK转UTF8
$location['country'] = iconv("GBK", "UTF-8", $location['country']);
$location['area'] = iconv("GBK", "UTF-8", $location['area']);
// CZ88.NET表示没有有效信息
if ((mb_strpos($location['country'], 'CZ88.NET') !== false)) {
$location['country'] = "未知";
}
if (mb_strpos($location['area'], 'CZ88.NET') !== false) {
$location['area'] = '';
}
return $location;
}
/**
* 返回读取的长整型数
*
* @return integer
*/
private function getlong()
{
// 将读取的little-endian编码的4个字节转化为长整型数
$result = unpack('Vlong', fread($this->fp, 4));
return $result['long'];
}
/**
* 返回读取的3个字节的长整型数
*
* @return integer
*/
private function getlong3()
{
// 将读取的little-endian编码的3个字节转化为长整型数
$result = unpack('Vlong', fread($this->fp, 3) . chr(0));
return $result['long'];
}
/**
* 验证IP地址是否为IPV4
*
* @param string $ip IP地址
* @return boolean
*/
private function isValidIpV4($ip)
{
return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
}
/**
* 返回压缩后可进行比较的IP地址
*
* @param string $ip IP地址
* @return string
*/
private function packip($ip)
{
// 将IP地址转化为长整型数如果在PHP5中IP地址错误则返回False
// 这时intval将Flase转化为整数-1之后压缩成big-endian编码的字符串
return pack('N', intval(Common::instance()->mIp2long($ip)));
}
/**
* 返回读取的字符串
*
* @param string $data
* @return string
*/
private function getstring($data = "")
{
$char = fread($this->fp, 1);
while (ord($char) > 0) {
// 字符串按照C格式保存以\0结束
$data .= $char;
// 将读取的字符连接到给定字符串之后
$char = fread($this->fp, 1);
}
return $data;
}
/**
* 获取地区信息
*
* @return string
*/
private function getarea()
{
// 标志字节
$byte = fread($this->fp, 1);
switch (ord($byte)) {
case 0:
// 没有区域信息
$area = "";
break;
case 1:
case 2:
// 标志字节为1或2表示区域信息被重定向
fseek($this->fp, $this->getlong3());
$area = $this->getstring();
break;
default:
// 否则,表示区域信息没有被重定向
$area = $this->getstring($byte);
break;
}
return $area;
}
/**
* 获取运营商信息
*
* @param $str
* @return string
*/
private function getIsp($str)
{
$ret = '';
foreach ($this->dict_isp as $k => $v) {
if (false !== mb_strpos($str, $v)) {
$ret = $v;
break;
}
}
return $ret;
}
}

226
vendor/mongdch/mon-util/src/IdCard.php vendored Normal file
View File

@ -0,0 +1,226 @@
<?php
namespace mon\util;
use RuntimeException;
use InvalidArgumentException;
/**
* 身份证号码工具类(支持15位和18位)
*
* @author Mon <985558837@qq.com>
* @version 1.0.1 增加获取省份城市信息
*/
class IdCard
{
use Instance;
/**
* 省份城市地区扩展数据配置文件路径
*
* @var string
*/
public $dataFile = __DIR__ . '/data/idcard.php';
/**
* 正则验证规则
*
* @var string
*/
protected $regx = '/^([\d]{17}[xX\d]|[\d]{15})$/';
/**
* 省份信息
*
* @var array
*/
protected $provinces = [
11 => "北京", 12 => "天津", 13 => "河北", 14 => "山西", 15 => "内蒙古",
21 => "辽宁", 22 => "吉林", 23 => "黑龙江", 31 => "上海", 32 => "江苏",
33 => "浙江", 34 => "安徽", 35 => "福建", 36 => "江西", 37 => "山东", 41 => "河南",
42 => "湖北", 43 => "湖南", 44 => "广东", 45 => "广西", 46 => "海南", 50 => "重庆",
51 => "四川", 52 => "贵州", 53 => "云南", 54 => "西藏", 61 => "陕西", 62 => "甘肃",
63 => "青海", 64 => "宁夏", 65 => "新疆", 71 => "台湾", 81 => "香港", 82 => "澳门", 91 => "国外"
];
/**
* 详细的省份城市地区信息
*
* @var array
*/
protected $location = null;
/**
* 校验身份证号码
*
* @param string $idcard 身份证号码
* @return boolean
*/
public function check($idcard)
{
// 校验位数
if (!preg_match($this->regx, $idcard)) {
return false;
}
// 校验省份码
if (!isset($this->provinces[mb_substr($idcard, 0, 2)])) {
return false;
}
// 校验生日
$birthday = $this->getBirthday($idcard);
if (date('Y-m-d', strtotime($birthday)) != $birthday) {
return false;
}
// 验证校验码
if (mb_strlen($idcard) == 18) {
$vSum = 0;
for ($i = 17; $i >= 0; $i--) {
$vSubStr = mb_substr($idcard, 17 - $i, 1);
$vSum += (pow(2, $i) % 11) * (($vSubStr == 'a') ? 10 : intval($vSubStr, 11));
}
if ($vSum % 11 != 1) {
return false;
}
}
return true;
}
/**
* 获取所属省份
*
* @param string $idcard 身份证号码
* @return string
*/
public function getProvinces($idcard)
{
$code = mb_substr($idcard, 0, 2);
return isset($this->provinces[$code]) ? $this->provinces[$code] : '';
}
/**
* 获取所属省份城市
*
* @param string $idcard 身份证号码
* @throws RuntimeException
* @return string
*/
public function getCity($idcard)
{
if (is_null($this->location)) {
$this->location = include($this->dataFile);
if (empty($this->location) || !is_array($this->location)) {
throw new RuntimeException('Failed to get extended location information data!');
}
}
$code = mb_substr($idcard, 0, 4) . '00';
return isset($this->location[$code]) ? $this->location[$code] : '';
}
/**
* 获取所属省份城市地区信息
*
* @param string $idcard 身份证号码
* @throws RuntimeException
* @return string
*/
public function getLocation($idcard)
{
if (is_null($this->location)) {
$this->location = include($this->dataFile);
if (empty($this->location) || !is_array($this->location)) {
throw new RuntimeException('Failed to get extended location information data!');
}
}
$code = mb_substr($idcard, 0, 6);
return isset($this->location[$code]) ? $this->location[$code] : '';
}
/**
* 获取生日
*
* @param string $idcard 身份证号
* @return string
*/
public function getBirthday($idcard)
{
$idcard = preg_replace('/[xX]$/i', 'a', $idcard);
$vLength = mb_strlen($idcard);
if ($vLength == 18) {
$birthday = mb_substr($idcard, 6, 4) . '-' . mb_substr($idcard, 10, 2) . '-' . mb_substr($idcard, 12, 2);
} else {
$birthday = '19' . mb_substr($idcard, 6, 2) . '-' . mb_substr($idcard, 8, 2) . '-' . mb_substr($idcard, 10, 2);
}
return $birthday;
}
/**
* 获取性别
*
* @param string $idcard 身份证号码
* @return integer 1 2
*/
public function getSex($idcard)
{
// 转18位身份证号码
$idcard = $this->fifteen2Eighteen($idcard);
return (int)mb_substr($idcard, 16, 1) % 2 === 0 ? 2 : 1;
}
/**
* 获取年龄
*
* @param string $idcard 身份证号码
* @return integer
*/
public function getAge($idcard)
{
$birthday = $this->getBirthday($idcard) . '000000';
$birthday_ts = strtotime($birthday);
$curr_ts = time();
//得到两个日期相差的大体年数
$diff = floor(($curr_ts - $birthday_ts) / 86400 / 365);
$age = strtotime(mb_substr($birthday, 0, 8) . ' +' . $diff . 'years') > $curr_ts ? ($diff + 1) : $diff;
return intval($age);
}
/**
* 15位转18位身份证号
*
* @param string $idcard 身份证号
* @return string
*/
public function fifteen2Eighteen($idcard)
{
if (mb_strlen($idcard) != 15) {
return $idcard;
}
$code = '19';
$idCardBase = mb_substr($idcard, 0, 6) . $code . mb_substr($idcard, 6, 9);
return $idCardBase . $this->getCode($idCardBase);
}
/**
* 获取校验码
*
* @param string $idCardBase 17位以上的身份号码
* @throws InvalidArgumentException
* @return string
*/
protected function getCode($idCardBase)
{
$length = 17;
if (mb_strlen($idCardBase) < $length) {
throw new InvalidArgumentException('idCardBase params faild');
}
$factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
$verifyNumbers = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
$sum = 0;
for ($i = 0; $i < $length; $i++) {
$sum += mb_substr($idCardBase, $i, 1) * $factor[$i];
}
$index = $sum % 11;
return $verifyNumbers[$index];
}
}

157
vendor/mongdch/mon-util/src/IdCode.php vendored Normal file
View File

@ -0,0 +1,157 @@
<?php
namespace mon\util;
/**
* id转换为code字符串
*
* @author Mon <985558837@qq.com>
* @version 1.0.1 优化代码 2022-07-08
*/
class IdCode
{
use Instance;
/**
* 起始数
*
* @var integer
*/
private $initNum = 4015732869;
/**
* 进制的基本字符串
*
* @var string
*/
private $baseChar = '8745692031ZYXWVTSRQPNMKJHGFEDCBA';
/**
* 使用32进制运算
*
* @var integer
*/
private $type = 32;
/**
* 补足第3位
*
* @var string
*/
private $three = 'K';
/**
* 补足第4位
*
* @var string
*/
private $four = 'X';
/**
* 初始化
*
* @param integer $initNum 起始数
* @param string $baseChar 进制的基本字符串
* @return IdCode
*/
public function init($initNum = 4015732869, $baseChar = '8745692031ZYXWVTSRQPNMKJHGFEDCBA', $three = 'K', $four = 'X')
{
$this->initNum = $initNum;
$this->baseChar = $baseChar;
$this->three = $three;
$this->four = $four;
return $this;
}
/**
* ID转code(10位内id 返回7位字母数字)
*
* @see 10进制数转换成32进制数
* @param integer $id id值
* @return string
*/
public function id2code($id)
{
//数组 增加备用数值
$id += $this->initNum;
//左补0 补齐10位
$str = str_pad($id, 10, '0', STR_PAD_LEFT);
//按位 拆分 4 6位32进制 4 6位划分
$num1 = intval($str[0] . $str[2] . $str[6] . $str[9]);
$num2 = intval($str[1] . $str[3] . $str[4] . $str[5] . $str[7] . $str[8]);
$str1 = $str2 = '';
$str1 = $this->id2String($num1);
$str1 = strrev($str1);
$str2 = $this->id2String($num2);
$str2 = strrev($str2);
// 补足第3、4位
return str_pad($str1, 3, $this->three, STR_PAD_RIGHT) . str_pad($str2, 4, $this->four, STR_PAD_RIGHT);
}
/**
* code转ID
*
* @see 三十二进制数转换成十机制数
* @param integer $id id值
* @return integer
*/
public function code2id($code)
{
// 清除3、4位补足位
$str1 = trim(mb_substr($code, 0, 3), $this->three);
$str2 = trim(mb_substr($code, 3, 4), $this->four);
// 转换数值
$num1 = $this->string2Id($str1);
$num2 = $this->string2Id($str2);
// 补位拼接
$str1 = str_pad($num1, 4, '0', STR_PAD_LEFT);
$str2 = str_pad($num2, 6, '0', STR_PAD_LEFT);
$id = ltrim($str1[0] . $str2[0] . $str1[1] . $str2[1] . $str2[2] . $str2[3] . $str1[2] . $str2[4] . $str2[5] . $str1[3], '0');
if ($id === '') {
return $id;
}
// 减去初始数值
$id -= $this->initNum;
return $id;
}
/**
* 数字进行进制转换
*
* @param integer $num 数值
* @return string
*/
protected function id2String($num)
{
$str = '';
while ($num != 0) {
$tmp = $num % $this->type;
$str .= $this->baseChar[$tmp];
$num = intval($num / $this->type);
}
return $str;
}
/**
* 字符串转数字
*
* @param string $str
* @return float|int|string
*/
protected function string2Id($str)
{
//转换为数组
$charArr = array_flip((array)str_split($this->baseChar));
$num = 0;
for ($i = 0, $l = mb_strlen($str); $i <= $l - 1; $i++) {
$linshi = mb_substr($str, $i, 1);
if (!isset($charArr[$linshi])) {
return '';
}
$num += $charArr[$linshi] * pow($this->type, mb_strlen($str) - $i - 1);
}
return $num;
}
}

646
vendor/mongdch/mon-util/src/Image.php vendored Normal file
View File

@ -0,0 +1,646 @@
<?php
namespace mon\util;
use mon\util\GIF;
use mon\util\exception\ImgException;
/**
* 图像操作类
*
* @author Name <985558837@qq.com>
* @version 1.1.1 优化代码 2022-07-8
*/
class Image
{
/**
* 图像资源对象
*
* @var \GdImage
*/
private $img;
/**
* 图像信息包括width,height,type,mime,size
*
* @var array
*/
private $info;
/**
* gif图片特殊处理实例
*
* @var GIF
*/
protected $gif;
/**
* 构造方法
*
* @throws ImgException
* @param string $img 图片地址
*/
public function __construct($img = null)
{
if (!extension_loaded('gd')) {
throw new ImgException('未启用GD扩展');
}
if (is_string($img)) {
$this->open($img);
}
}
/**
* 打开一张图片
*
* @param string $imgname 图片路径
* @throws ImgException
* @return Image
*/
public function open($imgname)
{
// 检测图像文件
if (!is_file($imgname)) {
throw new ImgException('不存在的图像文件', ImgException::ERROR_IMG_NOT_FOUND);
}
// 获取图像信息
$info = getimagesize($imgname);
// 检测图像合法性
if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) {
throw new ImgException('非法图像文件', ImgException::ERROR_IMG_FAILD);
}
// 设置图像信息
$this->info = [
'width' => $info[0],
'height' => $info[1],
'type' => image_type_to_extension($info[2], false),
'mime' => $info['mime'],
];
// 销毁已存在的图像
empty($this->img) || imagedestroy($this->img);
//打开图像
if ('gif' == $this->info['type']) {
$this->gif = new GIF($imgname);
$this->img = imagecreatefromstring($this->gif->image());
} else {
$fun = "imagecreatefrom{$this->info['type']}";
if (!function_exists($fun)) {
throw new ImgException('不支持的图片类型', ImgException::ERROR_IMG_TYPE_NOT_SUPPORT);
}
$this->img = call_user_func($fun, $imgname);
}
return $this;
}
/**
* 保存图像
*
* @param string $imgname 图像保存名称
* @param string $type 图像类型
* @param boolean $interlace 是否对JPEG类型图像设置隔行扫描
* @throws ImgException
* @return boolean|integer
*/
public function save($imgname, $type = null, $interlace = true)
{
if (empty($this->img)) {
throw new ImgException('没有可以被保存的图像资源', ImgException::ERROR_IMG_SAVE);
}
// 自动获取图像类型
if (is_null($type)) {
$type = $this->info['type'];
} else {
$type = strtolower($type);
}
// JPEG图像设置隔行扫描
if ('jpeg' == $type || 'jpg' == $type) {
$type = 'jpeg';
imageinterlace($this->img, $interlace);
}
// 保存图像
if ('gif' == $type && !empty($this->gif)) {
$save = $this->gif->save($imgname);
} else {
$fun = "image{$type}";
if (!function_exists($fun)) {
throw new ImgException('不支持的图片类型', ImgException::ERROR_IMG_TYPE_NOT_SUPPORT);
}
$save = call_user_func($fun, $this->img, $imgname);
}
return $save;
}
/**
* 输出图片
*
* @param boolean $echo 是否直接输出
* @throws ImgException
* @return mixed
*/
public function output($type = null, $interlace = true, $echo = true)
{
if (empty($this->img)) {
throw new ImgException('没有可以被保存的图像资源', ImgException::ERROR_IMG_SAVE);
}
// 自动获取图像类型
if (is_null($type)) {
$type = $this->info['type'];
} else {
$type = strtolower($type);
}
// JPEG图像设置隔行扫描
if ('jpeg' == $type || 'jpg' == $type) {
$type = 'jpeg';
imageinterlace($this->img, $interlace);
}
$fun = "image{$type}";
if (!function_exists($fun)) {
throw new ImgException('不支持的图片类型', ImgException::ERROR_IMG_TYPE_NOT_SUPPORT);
}
// 获取输出图像
ob_start();
call_user_func($fun, $this->img);
$content = ob_get_clean();
// 输出图像
if ($echo) {
header("Content-type: image/" . $type);
echo $content;
}
return $content;
}
/**
* 返回图像宽度
*
* @throws ImgException
* @return integer 图像宽度
*/
public function width()
{
if (empty($this->img)) {
throw new ImgException('没有指定图像资源', ImgException::ERROR_IMG_NOT_SPECIFIED);
}
return $this->info['width'];
}
/**
* 返回图像高度
*
* @throws ImgException
* @return integer 图像高度
*/
public function height()
{
if (empty($this->img)) {
throw new ImgException('没有指定图像资源', ImgException::ERROR_IMG_NOT_SPECIFIED);
}
return $this->info['height'];
}
/**
* 返回图像类型
*
* @throws ImgException
* @return string 图像类型
*/
public function type()
{
if (empty($this->img)) {
throw new ImgException('没有指定图像资源', ImgException::ERROR_IMG_NOT_SPECIFIED);
}
return $this->info['type'];
}
/**
* 返回图像MIME类型
*
* @throws ImgException
* @return string 图像MIME类型
*/
public function mime()
{
if (empty($this->img)) {
throw new ImgException('没有指定图像资源', ImgException::ERROR_IMG_NOT_SPECIFIED);
}
return $this->info['mime'];
}
/**
* 返回图像尺寸数组 0 - 图像宽度1 - 图像高度
*
* @throws ImgException
* @return array 图像尺寸
*/
public function size()
{
if (empty($this->img)) {
throw new ImgException('没有指定图像资源', ImgException::ERROR_IMG_NOT_SPECIFIED);
}
return [
'width' => $this->info['width'],
'height' => $this->info['height']
];
}
/**
* 裁剪图像
*
* @param integer $w 裁剪区域宽度
* @param integer $h 裁剪区域高度
* @param integer $x 裁剪区域x坐标
* @param integer $y 裁剪区域y坐标
* @param integer $width 图像保存宽度
* @param integer $height 图像保存高度
* @throws ImgException
* @return Image
*/
public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null)
{
if (empty($this->img)) {
throw new ImgException('没有指定图像资源', ImgException::ERROR_IMG_NOT_SPECIFIED);
}
//设置保存尺寸
empty($width) && $width = $w;
empty($height) && $height = $h;
do {
// 创建新图像
$img = imagecreatetruecolor($width, $height);
// 调整默认颜色
$color = imagecolorallocate($img, 255, 255, 255);
imagefill($img, 0, 0, $color);
// 裁剪
imagecopyresampled($img, $this->img, 0, 0, $x, $y, $width, $height, $w, $h);
// 销毁原图
imagedestroy($this->img);
// 设置新图像
$this->img = $img;
} while (!empty($this->gif) && $this->gifNext());
$this->info['width'] = $width;
$this->info['height'] = $height;
return $this;
}
/**
* 生成缩略图
*
* @param integer $width 缩略图最大宽度
* @param integer $height 缩略图最大高度
* @param integer $type 缩略图裁剪类型, 1:等比例缩放;2:居中裁剪;3:左上角裁剪;4:右下角裁剪;5:填充;6:固定
* @throws ImgException
* @return Image
*/
public function thumb($width, $height, $type = 1)
{
if (empty($this->img)) {
throw new ImgException('没有指定图像资源', ImgException::ERROR_IMG_NOT_SPECIFIED);
}
// 原图宽度和高度
$w = $this->info['width'];
$h = $this->info['height'];
// 计算缩略图生成的必要参数
switch ($type) {
case 1:
// 等比例缩放 原图尺寸小于缩略图尺寸则不进行缩略
if ($w < $width && $h < $height) {
return;
};
// 计算缩放比例
$scale = min($width / $w, $height / $h);
// 设置缩略图的坐标及宽度和高度
$x = $y = 0;
$width = $w * $scale;
$height = $h * $scale;
break;
case 2:
// 居中裁剪
$scale = max($width / $w, $height / $h);
// 设置缩略图的坐标及宽度和高度
$w = $width / $scale;
$h = $height / $scale;
$x = ($this->info['width'] - $w) / 2;
$y = ($this->info['height'] - $h) / 2;
break;
case 3:
// 左上角裁剪
$scale = max($width / $w, $height / $h);
// 设置缩略图的坐标及宽度和高度
$x = $y = 0;
$w = $width / $scale;
$h = $height / $scale;
break;
case 4:
// 右下角裁剪
$scale = max($width / $w, $height / $h);
// 设置缩略图的坐标及宽度和高度
$w = $width / $scale;
$h = $height / $scale;
$x = $this->info['width'] - $w;
$y = $this->info['height'] - $h;
break;
case 5:
// 填充
if ($w < $width && $h < $height) {
$scale = 1;
} else {
$scale = min($width / $w, $height / $h);
}
// 设置缩略图的坐标及宽度和高度
$neww = $w * $scale;
$newh = $h * $scale;
$posx = ($width - $w * $scale) / 2;
$posy = ($height - $h * $scale) / 2;
$x = $y = 0;
do {
// 创建新图像
$img = imagecreatetruecolor($width, $height);
// 调整默认颜色
$color = imagecolorallocate($img, 255, 255, 255);
imagefill($img, 0, 0, $color);
// 裁剪
imagecopyresampled($img, $this->img, $posx, $posy, $x, $y, $neww, $newh, $w, $h);
imagedestroy($this->img); //销毁原图
$this->img = $img;
} while (!empty($this->gif) && $this->gifNext());
$this->info['width'] = $width;
$this->info['height'] = $height;
return $this;
case 6:
// 固定
$x = $y = 0;
break;
default:
throw new ImgException('不支持的缩略图裁剪类型', ImgException::ERROR_IMG_NOT_SUPPORT);
}
// 裁剪图像
return $this->crop($w, $h, $x, $y, $width, $height);
}
/**
* 添加水印
*
* @param string $source 水印图片路径
* @param integer $locate 水印位置, 1-9对应数字键盘位置
* @throws ImgException
* @return Image
*/
public function water($source, $locate = 3)
{
if (empty($this->img)) {
throw new ImgException('没有指定图像资源', ImgException::ERROR_IMG_NOT_SPECIFIED);
}
if (!is_file($source)) {
throw new ImgException('水印图像不存在', ImgException::ERROR_IMG_NOT_FOUND_WATER);
}
// 获取水印图像信息
$info = getimagesize($source);
if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) {
throw new ImgException('非法水印文件', ImgException::ERROR_IMG_FAILD_WATER);
}
// 创建水印图像资源
$fun = 'imagecreatefrom' . image_type_to_extension($info[2], false);
$water = $fun($source);
// 设定水印图像的混色模式
imagealphablending($water, true);
// 设定水印位置
switch ($locate) {
case 3:
// 右下角水印
$x = $this->info['width'] - $info[0];
$y = $this->info['height'] - $info[1];
break;
case 1:
// 左下角水印
$x = 0;
$y = $this->info['height'] - $info[1];
break;
case 7:
// 左上角水印
$x = $y = 0;
break;
case 9:
// 右上角水印
$x = $this->info['width'] - $info[0];
$y = 0;
break;
case 5:
// 居中水印
$x = ($this->info['width'] - $info[0]) / 2;
$y = ($this->info['height'] - $info[1]) / 2;
break;
case 2:
// 下居中水印
$x = ($this->info['width'] - $info[0]) / 2;
$y = $this->info['height'] - $info[1];
break;
case 6:
// 右居中水印
$x = $this->info['width'] - $info[0];
$y = ($this->info['height'] - $info[1]) / 2;
break;
case 8:
// 上居中水印
$x = ($this->info['width'] - $info[0]) / 2;
$y = 0;
break;
case 4:
// 左居中水印
$x = 0;
$y = ($this->info['height'] - $info[1]) / 2;
break;
default:
// 自定义水印坐标
if (is_array($locate)) {
list($x, $y) = $locate;
} else {
throw new ImgException('不支持的水印位置类型', ImgException::ERROR_IMG_NOT_SUPPORT_WATER);
}
break;
}
do {
// 添加水印
$src = imagecreatetruecolor($info[0], $info[1]);
// 调整默认颜色
$color = imagecolorallocate($src, 255, 255, 255);
imagefill($src, 0, 0, $color);
imagecopy($src, $this->img, 0, 0, $x, $y, $info[0], $info[1]);
imagecopy($src, $water, 0, 0, 0, 0, $info[0], $info[1]);
imagecopymerge($this->img, $src, $x, $y, 0, 0, $info[0], $info[1], 100);
//销毁零时图片资源
imagedestroy($src);
} while (!empty($this->gif) && $this->gifNext());
// 销毁水印资源
imagedestroy($water);
return $this;
}
/**
* 图像添加文字
*
* @param string $text 添加的文字
* @param string $font 字体路径
* @param integer $size 字号
* @param string $color 文字颜色
* @param integer $locate 文字写入位置, 1-9对应小键盘位置
* @param integer|array $offset 文字相对当前位置的偏移量
* @param integer $angle 文字倾斜角度
* @throws ImgException
* @return Image
*/
public function text($text, $font, $size, $color = '#00000000', $locate = 3, $offset = 0, $angle = 0)
{
// 资源检测
if (empty($this->img)) {
throw new ImgException('没有指定图像资源', ImgException::ERROR_IMG_NOT_SPECIFIED);
}
if (!is_file($font)) {
throw new ImgException("不存在的字体文件:{$font}", ImgException::ERROR_IMG_NOT_FOUND_FONT);
}
// 获取文字信息
$info = imagettfbbox($size, $angle, realpath($font), $text);
$minx = min($info[0], $info[2], $info[4], $info[6]);
$maxx = max($info[0], $info[2], $info[4], $info[6]);
$miny = min($info[1], $info[3], $info[5], $info[7]);
$maxy = max($info[1], $info[3], $info[5], $info[7]);
// 计算文字初始坐标和尺寸
$x = $minx;
$y = abs($miny);
$w = $maxx - $minx;
$h = $maxy - $miny;
// 设定文字位置
switch ($locate) {
case 3:
// 右下角文字
$x += $this->info['width'] - $w;
$y += $this->info['height'] - $h;
break;
case 1:
// 左下角文字
$y += $this->info['height'] - $h;
break;
case 7:
// 左上角文字,起始坐标即为左上角坐标,无需调整
break;
case 9:
// 右上角文字
$x += $this->info['width'] - $w;
break;
case 5:
// 居中文字
$x += ($this->info['width'] - $w) / 2;
$y += ($this->info['height'] - $h) / 2;
break;
case 2:
// 下居中文字
$x += ($this->info['width'] - $w) / 2;
$y += $this->info['height'] - $h;
break;
case 6:
// 右居中文字
$x += $this->info['width'] - $w;
$y += ($this->info['height'] - $h) / 2;
break;
case 8:
// 上居中文字
$x += ($this->info['width'] - $w) / 2;
break;
case 4:
// 左居中文字
$y += ($this->info['height'] - $h) / 2;
break;
default:
// 自定义文字坐标
if (is_array($locate)) {
list($posx, $posy) = $locate;
$x += $posx;
$y += $posy;
} else {
throw new ImgException('不支持的文字位置类型', ImgException::ERROR_IMG_NOT_SUPPORT_FONT);
}
break;
}
// 设置偏移量
if (is_array($offset)) {
$offset = array_map('intval', $offset);
list($ox, $oy) = $offset;
} else {
$offset = intval($offset);
$ox = $oy = $offset;
}
// 设置颜色
if (is_string($color) && 0 === mb_strpos($color, '#')) {
$color = str_split(mb_substr($color, 1), 2);
$color = array_map('hexdec', (array) $color);
if (empty($color[3]) || $color[3] > 127) {
$color[3] = 0;
}
} elseif (!is_array($color)) {
throw new ImgException('错误的颜色值', ImgException::ERROR_IMG_FAILD_COLOR);
}
do {
// 写入文字
$col = imagecolorallocatealpha($this->img, $color[0], $color[1], $color[2], $color[3]);
imagettftext($this->img, $size, $angle, $x + $ox, $y + $oy, $col, realpath($font), $text);
} while (!empty($this->gif) && $this->gifNext());
return $this;
}
/**
* 切换到GIF的下一帧并保存当前帧
*
* @return mixed
*/
protected function gifNext()
{
ob_start();
ob_implicit_flush(0);
imagegif($this->img);
$img = ob_get_clean();
$this->gif->image($img);
$next = $this->gif->nextImage();
if ($next) {
imagedestroy($this->img);
$this->img = imagecreatefromstring($next);
return $next;
} else {
imagedestroy($this->img);
$this->img = imagecreatefromstring($this->gif->image());
return false;
}
}
/**
* 析构方法,用于销毁图像资源
*
* @return void
*/
public function __destruct()
{
empty($this->img) || imagedestroy($this->img);
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace mon\util;
use InvalidArgumentException;
/**
* 单例trait
*
* @author Mon 985558837@qq.com
* @version 1.0.1 修正获取当前实例的方式为static而非self
*/
trait Instance
{
/**
* 单例实体
*
* @var null
*/
protected static $instance = null;
/**
* 获取单例
*
* @param array $options 初始化参数
* @return static
*/
public static function instance($options = [])
{
if (is_null(static::$instance)) {
static::$instance = new static($options);
}
return static::$instance;
}
/**
* 静态调用支持,以"_"开头加方法名调用非静态方法
*
* @see 例子: className::_methodName()
* @param string $method 方法名
* @param array $params 参数
* @return mixed
*/
public static function __callStatic($method, $params)
{
if (is_null(static::$instance)) {
static::$instance = new static();
}
$call = mb_substr($method, 1);
if (0 === mb_strpos($method, '_') && is_callable([static::$instance, $call])) {
return call_user_func_array([static::$instance, $call], (array) $params);
} else {
throw new InvalidArgumentException("method not found => " . $method);
}
}
}

183
vendor/mongdch/mon-util/src/Lang.php vendored Normal file
View File

@ -0,0 +1,183 @@
<?php
namespace mon\util;
use mon\util\Instance;
/**
* 多语言控制
*
* @author Mon <985558837@qq.com>
* @version 1.0.0
*/
class Lang
{
use Instance;
/**
* 语言信息
*
* @var array
*/
protected $lang = [];
/**
* 当前选择的默认语言
*
* @var string
*/
protected $range = 'zh-cn';
/**
* 多语言cookie标志
*
* @var string
*/
protected $lang_var = 'mon_lang_var';
/**
* 设定当前的语言
*
* @param string $range 语言类型
* @return string
*/
public function range($range = '')
{
if ($range) {
$this->range = $range;
}
return $this->range;
}
/**
* 动态语言定义
*
* @param string|array $name 键名
* @param string $value
* @param string $range 语言作用域
*/
public function set($name, $value = '', $range = '')
{
$range = $range ?: $this->range;
if (!isset($this->lang[$range])) {
$this->lang[$range] = [];
}
if (is_array($name)) {
return $this->lang[$range] = array_change_key_case((array) $name) + $this->lang[$range];
}
return $this->lang[$range][strtolower($name)] = $value;
}
/**
* 加载语言包
*
* @param array|string $file 语言文件
* @param string $range 语言作用域
* @return mixed
*/
public function load($file, $range = '')
{
$range = $range ?: $this->range;
if (!isset($this->lang[$range])) {
$this->lang[$range] = [];
}
$lang = [];
if (is_file($file)) {
$_lang = include $file;
if (is_array($_lang)) {
$lang = array_change_key_case($_lang) + $lang;
}
}
if (!empty($lang)) {
$this->lang[$range] = $lang + $this->lang[$range];
}
return $this->lang[$range];
}
/**
* 判断语言是否已定义
*
* @param string $name 键名
* @param string $range 语言作用域
* @return boolean
*/
public function has($name, $range = '')
{
$range = $range ?: $this->range;
return isset($this->lang[$range][strtolower($name)]);
}
/**
* 获取语言定义
*
* @param string $name 键名
* @param array $vars 替换变量
* @param string $range 语言类型
* @return mixed
*/
public function get($name, $vars = [], $range = '')
{
$range = $range ?: $this->range;
// 空参数返回所有定义
if (empty($name)) {
return $this->lang[$range];
}
$key = strtolower($name);
$value = isset($this->lang[$range][$key]) ? $this->lang[$range][$key] : $name;
// 变量解析
if (!empty($vars) && is_array($vars)) {
/**
* Notes:
* 为了检测的方便数字索引的判断仅仅是参数数组的第一个元素的key为数字0
* 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数
*/
if (key($vars) === 0) {
// 数字索引解析
array_unshift($vars, $value);
$value = call_user_func_array('sprintf', $vars);
} else {
// 关联索引解析
$replace = array_keys($vars);
foreach ($replace as &$v) {
$v = "{$v}";
}
$value = str_replace($replace, $vars, $value);
}
}
return $value;
}
/**
* 检测设置获取当前语言
*
* @return mixed
*/
public function detect()
{
if (isset($_COOKIE[$this->lang_var])) {
$this->range = strtolower($_COOKIE[$this->lang_var]);
} else if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// 自动侦测浏览器语言
preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
$lang = strtolower($matches[1]);
if (isset($this->lang[$lang])) {
$this->range = $this->lang[$lang];
}
}
return $this->range;
}
}

242
vendor/mongdch/mon-util/src/Log.php vendored Normal file
View File

@ -0,0 +1,242 @@
<?php
namespace mon\util;
/**
* 日志处理
*
* @author Mon <985558837@qq.com>
* @version 1.0.0
*/
class Log
{
use Instance;
/**
* 日志级别
*/
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
/**
* 日志配置
*
* @var array
*/
protected $config = [
'maxSize' => 20480000, // 日志文件大小
'logPath' => '', // 日志目录
'rollNum' => 3, // 日志滚动卷数
'logName' => '', // 日志名称,空则使用当前日期作为名称
'splitLine' => '====================================================================================',
];
/**
* 日志信息
*
* @var array
*/
protected $log = [];
/**
* 初始化日志配置
*
* @param array $config 配置信息
*/
protected function __construct(array $config = [])
{
$this->setConfig($config);
}
/**
* 定义日志配置信息
*
* @param array $config 配置信息
* @return Log
*/
public function setConfig(array $config)
{
$this->config = array_merge($this->config, $config);
return $this;
}
/**
* 获取配置信息
*
* @return array
*/
public function getConfig()
{
return $this->config;
}
/**
* 获取日志信息
*
* @param string $type 信息类型
* @return array
*/
public function getLog($type = null)
{
return $type ? $this->log[$type] : $this->log;
}
/**
* 清空日志信息
*
* @return Log
*/
public function clear()
{
$this->log = [];
return $this;
}
/**
* 记录日志信息
*
* @param string $message 日志信息
* @param string $type 日志类型
* @param boolean $trace 是否开启日志追踪
* @param integer $level 日志追踪层级,一般不需要自定义
* @return Log
*/
public function record($message, $type = Log::INFO, $trace = false, $level = 1)
{
if ($trace) {
$traceInfo = debug_backtrace(false, $level);
$infoLevel = $level - 1;
$file = $traceInfo[$infoLevel]['file'];
$line = $traceInfo[$infoLevel]['line'];
$message = "[{$file} => {$line}] " . $message;
}
$this->log[strtolower($type)][] = $message;
return $this;
}
/**
* 记录调试信息
*
* @param string $message 日志信息
* @param boolean $trace 是否开启日志追踪
* @return Log
*/
public function debug($message, $trace = false)
{
$level = $trace ? 2 : 1;
return $this->record($message, Log::DEBUG, $trace, $level);
}
/**
* 记录一般信息
*
* @param string $message 日志信息
* @param boolean $trace 是否开启日志追踪
* @return Log
*/
public function info($message, $trace = false)
{
$level = $trace ? 2 : 1;
return $this->record($message, Log::INFO, $trace, $level);
}
/**
* 记录通知信息
*
* @param string $message 日志信息
* @param boolean $trace 是否开启日志追踪
* @return Log
*/
public function notice($message, $trace = false)
{
$level = $trace ? 2 : 1;
return $this->record($message, Log::NOTICE, $trace, $level);
}
/**
* 记录警告信息
*
* @param string $message 日志信息
* @param boolean $trace 是否开启日志追踪
* @return Log
*/
public function warning($message, $trace = false)
{
$level = $trace ? 2 : 1;
return $this->record($message, Log::WARNING, $trace, $level);
}
/**
* 记录错误信息
*
* @param string $message 日志信息
* @param boolean $trace 是否开启日志追踪
* @return Log
*/
public function error($message, $trace = false)
{
$level = $trace ? 2 : 1;
return $this->record($message, Log::ERROR, $trace, $level);
}
/**
* 批量写入日志
*
* @return boolean
*/
public function save()
{
if (!empty($this->log)) {
// 解析获取日志内容
$log = $this->parseLog($this->log);
$time = time();
$logName = empty($this->config['logName']) ? date('Ym', $time) . DIRECTORY_SEPARATOR . date('Ymd', $time) : $this->config['logName'];
$path = $this->config['logPath'] . DIRECTORY_SEPARATOR . $logName;
// 分卷记录日志
$save = File::instance()->subsectionFile($log, $path, $this->config['maxSize'], $this->config['rollNum']);
// 保存成功,清空日志
if ($save) {
$this->clear();
}
return $save;
}
return true;
}
/**
* 解析日志
*
* @param array $logs 日志列表
* @return string 解析生成的日志字符串
*/
protected function parseLog($logs)
{
$log = '';
$now = date('Y-m-d H:i:s', time());
foreach ($logs as $type => $value) {
$offset = "[{$now}] [{$type}] ";
if (is_array($value)) {
$info = '';
foreach ($value as $msg) {
$info = $info . $offset . $msg . PHP_EOL;
}
} else {
$info = $offset . $value . PHP_EOL;
}
$log .= $info;
}
// 添加分割线
if (!empty($this->config['splitLine'])) {
$log .= $this->config['splitLine'] . PHP_EOL;
}
return $log;
}
}

126
vendor/mongdch/mon-util/src/Lottery.php vendored Normal file
View File

@ -0,0 +1,126 @@
<?php
namespace mon\util;
use mon\util\exception\LotteryException;
/**
* 概率抽奖工具类
*
* @author Mon <985558837@qq.com>
* @version 1.0.0 2021-03-18
*/
class Lottery
{
/**
* 初始化标志
*
* @var boolean
*/
protected $init = false;
/**
* 奖品列表
*
* @var array
*/
protected $awards = [];
/**
* 概率总和
*
* @var integer
*/
protected $probabilityCount = 100;
/**
* 奖品概率索引
*
* @var string
*/
protected $probabilityKey = 'probability';
/**
* 支持概率的小数位数
*
* @var integer
*/
protected $scale = 4;
/**
* 初始化
*
* @param array $awards 奖品列表
* @param string $probabilityKey 奖品概率索引
* @param string $notWin 未中奖信息
* @param integer $scale 支持概率的小数位数
* @param integer $probabilityCount 概率总和
* @throws LotteryException
* @return Lottery
*/
public function init(array $awards, $probabilityKey = 'probability', $notWin = '抱歉,您未中奖', $scale = 4, $probabilityCount = 100)
{
$this->awards = $awards;
$this->probabilityKey = $probabilityKey;
$this->scale = $scale;
$this->probabilityCount = $probabilityCount;
// 计算奖品总概率
$probabilityCount = 0;
foreach ($this->awards as &$item) {
$probabilityCount = bcadd($probabilityCount, $item[$this->probabilityKey], $this->scale);
// 标记中奖
$item['isWin'] = true;
}
if (bccomp($probabilityCount, $this->probabilityCount, $this->scale) > 0) {
throw new LotteryException('概率总和必须小于等于概率总和(' . $this->probabilityCount . '),当前总和为:' . $probabilityCount, LotteryException::ERROR_PROBABILITY_MIN);
}
// 未中奖奖项
$notAward = [
'title' => $notWin,
'isWin' => false,
$this->probabilityKey => bcsub($this->probabilityCount, $probabilityCount, $this->scale)
];
$this->awards[] = $notAward;
// 打乱顺序
shuffle($this->awards);
$this->init = true;
return $this;
}
/**
* 采用经典概率算法进行抽奖
*
* @throws LotteryException
* @return array 返回数据中isWin为true则表示中奖
*/
public function getDraw()
{
if (!$this->init) {
throw new LotteryException('未初始化抽奖配置', LotteryException::ERROR_NOT_INIT);
}
$probabilityCount = $this->probabilityCount;
$pow = pow(10, $this->scale);
foreach ($this->awards as $item) {
$randProbability = $probabilityCount * $pow;
$randInum = mt_rand(1, $randProbability);
$randNum = bcdiv($randInum, $pow, $this->scale);
$bccomp = bccomp($randNum, $item[$this->probabilityKey], $this->scale);
if ($bccomp == 0 || $bccomp == -1) {
return $item;
}
$probabilityCount = bcsub($probabilityCount, $item[$this->probabilityKey], $this->scale);
}
throw new LotteryException('抽奖失败,未抽到奖品', LotteryException::ERROR_NOT_AWARD);
}
/**
* 获取奖品列表
*
* @return array
*/
public function getAwards()
{
return $this->awards;
}
}

625
vendor/mongdch/mon-util/src/Migrate.php vendored Normal file
View File

@ -0,0 +1,625 @@
<?php
namespace mon\util;
use PDO;
use FilesystemIterator;
use InvalidArgumentException;
use mon\util\exception\SqlException;
use mon\util\exception\MigrateException;
/**
* Mysql数据库迁移备份
*
* @author Mon <985558837@qq.com>
* @version 1.0.0 2022-07-13
*/
class Migrate
{
use Instance;
/**
* 数据库连接
*
* @var PDO
*/
protected $db = null;
/**
* 文件指针
*
* @var resource
*/
protected $fp = null;
/**
* 备份文件信息 part - 卷号name - 文件名
*
* @var array
*/
protected $file = [];
/**
* 当前打开文件大小
*
* @var integer
*/
protected $size = 0;
/**
* 备份配置
*
* @var array
*/
protected $config = [
// 数据库备份路径
'path' => './data/',
// 数据库备份卷大小
'part' => 20971520,
// 数据库备份文件是否启用压缩 0不压缩 1 压缩
'compress' => 0,
// 压缩级别
'level' => 9,
// 数据库配置
'db' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'host' => '127.0.0.1',
// 数据库名
'database' => '',
// 用户名
'username' => '',
// 密码
'password' => '',
// 端口
'port' => '3306',
// 数据库编码默认采用utf8
'charset' => 'utf8mb4',
// 数据库连接参数
'params' => []
]
];
/**
* 数据库备份构造方法
*
* @param array $config 配置信息
*/
public function __construct(array $config = [])
{
$this->config = array_merge($this->config, $config);
// 初始化文件名
$this->setFile();
}
/**
* 析构方法,用于关闭文件资源
*/
public function __destruct()
{
$this->close();
}
/**
* 设置配置信息
*
* @param array $config 配置信息
* @return Migrate
*/
public function setConfig(array $config)
{
$this->config = array_merge($this->config, $config);
return $this;
}
/**
* 获取配置信息
*
* @param string $name
* @return mixed
*/
public function getConfig($name = null)
{
return is_null($name) ? $this->config : $this->config[$name];
}
/**
* 设置备份文件名
*
* @param array $file 文件信息
* @demo setFile(['name' => '20220713', 'part' => 1])
* @return Migrate
*/
public function setFile($file = null)
{
if (is_null($file)) {
$this->file = ['name' => date('Ymd-His'), 'part' => 1];
} else {
$this->file = $file;
}
return $this;
}
/**
* 获取文件信息
*
* @return void
*/
public function getFile()
{
return $this->file;
}
/**
* 获取数据库表数据
*
* @return array
*/
public function tableList()
{
$list = $this->query("SHOW TABLE STATUS");
return array_map('array_change_key_case', (array) $list);
}
/**
* 获取表结构
*
* @param string $table
* @return array
*/
public function tableStruct($table)
{
$sql = "SELECT * FROM `information_schema`.`columns` WHERE `TABLE_NAME` = '{$table}' AND `TABLE_SCHEMA` = '{$this->config['db']['database']}'";
$data = $this->query($sql);
return $data;
}
/**
* 数据库备份文件列表
*
* @throws MigrateException
* @return array
*/
public function fileList()
{
// 检查文件是否可写
if (!File::instance()->createDir($this->config['path'])) {
throw new MigrateException("The current directory is not writable");
}
$path = realpath($this->config['path']);
$glob = new FilesystemIterator($path, FilesystemIterator::KEY_AS_FILENAME);
$list = [];
foreach ($glob as $name => $file) {
if (preg_match('/^\\d{8,8}-\\d{6,6}-\\d+\\.sql(?:\\.gz)?$/', $name)) {
$info['filename'] = $name;
$name = sscanf($name, '%4s%2s%2s-%2s%2s%2s-%d');
$date = "{$name[0]}-{$name[1]}-{$name[2]}";
$time = "{$name[3]}:{$name[4]}:{$name[5]}";
$part = $name[6];
if (isset($list["{$date} {$time}"])) {
$info = $list["{$date} {$time}"];
$info['part'] = max($info['part'], $part);
$info['size'] = $info['size'] + $file->getSize();
} else {
$info['part'] = $part;
$info['size'] = $file->getSize();
}
$extension = strtoupper(pathinfo($file->getFilename(), PATHINFO_EXTENSION));
$info['compress'] = $extension === 'SQL' ? '-' : $extension;
$info['time'] = strtotime("{$date} {$time}");
$list["{$date} {$time}"] = $info;
}
}
return $list;
}
/**
* 获取备份的数据库文件信息
*
* @param string $type 操作类型
* @param integer $time 时间戳
* @throws MigrateException
* @return array|false|string
*/
public function fileInfo($type = '', $time = 0)
{
if (!is_numeric($time)) {
throw new MigrateException("Time [{$time}] illegal data type");
}
switch ($type) {
case 'time':
$name = date('Ymd-His', $time) . '-*.sql*';
$path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $name;
return glob($path);
case 'timeverif':
$name = date('Ymd-His', $time) . '-*.sql*';
$path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $name;
$files = glob($path);
$list = array();
foreach ($files as $name) {
$basename = basename($name);
$match = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d');
$gz = preg_match('/^\\d{8,8}-\\d{6,6}-\\d+\\.sql.gz$/', $basename);
$list[$match[6]] = array($match[6], $name, $gz);
}
$last = end($list);
if (count($list) === $last[0]) {
return $list;
} else {
throw new MigrateException("File [{$files['0']}] may be damaged, please check again");
}
case 'pathname':
return "{$this->config['path']}{$this->file['name']}-{$this->file['part']}.sql";
case 'filename':
return "{$this->file['name']}-{$this->file['part']}.sql";
case 'filepath':
return $this->config['path'];
default:
$arr = [
'pathname' => "{$this->config['path']}{$this->file['name']}-{$this->file['part']}.sql",
'filename' => "{$this->file['name']}-{$this->file['part']}.sql",
'filepath' => $this->config['path'],
'file' => $this->file
];
return $arr;
}
}
/**
* 删除备份文件
*
* @param integer $time 时间戳
* @throws MigrateException
* @return mixed
*/
public function del($time)
{
if (!$time) {
throw new MigrateException("{$time} Time parameter is incorrect");
}
$filePathArr = $this->fileInfo('time', $time);
array_map("unlink", $filePathArr);
if (count($this->fileInfo('time', $time))) {
throw new MigrateException("File {$time} deleted failed");
} else {
return $time;
}
}
/**
* 导出下载备份
*
* @param integer $time 文件名时间戳
* @param integer $part
* @throws MigrateException
* @throws InvalidArgumentException
* @return void
*/
public function export($time, $part = 0)
{
$file = $this->fileInfo('time', $time);
if (!isset($file[$part])) {
throw new MigrateException("{$time} Part is abnormal");
}
$fileName = $file[$part];
if (!file_exists($fileName)) {
throw new MigrateException("{$time} File is abnormal");
}
return Tool::instance()->exportFile($fileName, basename($fileName));
}
/**
* 导入数据库文件
*
* @param integer $time 文件名时间戳
* @param integer $part
* @throws MigrateException
* @throws SqlException
* @return true
*/
public function import($time, $part = 0)
{
$file = $this->fileInfo('time', $time);
if (!isset($file[$part])) {
throw new MigrateException("{$time} Part is abnormal");
}
$fileName = $file[$part];
if (!file_exists($fileName)) {
throw new MigrateException("{$time} File is abnormal");
}
// 读取sql文件
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
if (strtolower($ext) == 'gz') {
$content = $this->read($fileName);
$sqls = Sql::instance()->parseSql($content);
} else {
$sqls = Sql::instance()->parseFile($fileName);
}
// 执行sql
foreach ($sqls as $sql) {
$this->execute($sql);
}
return true;
}
/**
* 备份表
*
* @param array $tables 表名列表,空则备份所有表
* @param boolean $bakData 是否备份数据
* @return array 备份失败的表名,空则表示成功
*/
public function migrate(array $tables = [], $bakData = true)
{
$error = [];
if (empty($tables)) {
$tableList = $this->tableList();
$tables = array_column($tableList, 'name');
}
for ($i = 0, $l = count($tables); $i < $l; $i++) {
$table = $tables[$i];
$backup = Migrate::instance()->backup($table, 0, $bakData, ($i + 1 == $l));
if ($backup === false) {
$error[] = $table;
}
}
return $error;
}
/**
* 备份表
*
* @param string $table 表名
* @param integer $start 起始位
* @param boolean $bakData 是否备份数据
* @param boolean $closeFp 是否关闭文件句柄
* @return boolean
*/
public function backup($table, $start = 0, $bakData = true, $closeFp = true)
{
// 查询表结构
$result = $this->query("SHOW CREATE TABLE `{$table}`");
if (!$result) {
if ($closeFp) {
$this->close();
}
throw new MigrateException("{$table} not found!");
}
$tableInfo = $result[0];
// 备份表结构
if (0 == $start) {
$sql = "\n";
$sql .= "-- -----------------------------\n";
// 获取建表语言或者视图语句
if (isset($tableInfo['Create Table'])) {
$sql .= "-- Table structure for `{$table}`\n";
$sql .= "-- -----------------------------\n";
$sql .= "DROP TABLE IF EXISTS `{$table}`;\n";
$sql .= trim($tableInfo['Create Table']) . ";\n\n";
} else if (isset($tableInfo['Create View'])) {
$sql .= "-- View structure for `{$table}`\n";
$sql .= "-- -----------------------------\n";
$sql .= "DROP VIEW IF EXISTS `{$table}`;\n";
$sql .= trim($tableInfo['Create View']) . ";\n\n";
}
if (false === $this->write($sql)) {
if ($closeFp) {
$this->close();
}
return false;
}
}
// 备份表数据
if ($bakData && isset($tableInfo['Table'])) {
// 数据总数
$countData = $this->query("SELECT count(*) AS 'count' FROM `{$table}`");
$count = isset($countData[0]) && isset($countData[0]['count']) ? $countData[0]['count'] : false;
// 备份表数据
if ($count) {
// 写入数据注释
if (0 == $start) {
$sql = "-- -----------------------------\n";
$sql .= "-- Records of `{$table}`\n";
$sql .= "-- -----------------------------\n";
$this->write($sql);
}
// 备份数据记录
$start = intval($start);
$result = $this->query("SELECT * FROM `{$table}` LIMIT {$start}, 1000");
foreach ($result as $row) {
$row = array_map('addslashes', $row);
$sql = "INSERT INTO `{$table}` VALUES ('" . str_replace(["\r", "\n"], ['\\r', '\\n'], implode("', '", $row)) . "');\n";
if (false === $this->write($sql)) {
if ($closeFp) {
$this->close();
}
return false;
}
}
// 还有更多数据
if ($count > $start + 1000) {
return $this->backup($table, $start + 1000, $bakData, $closeFp);
}
}
}
// 备份完成
if ($closeFp) {
$this->close();
}
return true;
}
/**
* 执行查询语句
*
* @param string $sql SQL语句
* @return array
*/
public function query($sql)
{
$query = $this->getDB()->prepare($sql);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
/**
* 执行更新语句
*
* @param string $sql
* @return integer
*/
public function execute($sql)
{
$query = $this->getDB()->prepare($sql);
$query->execute();
return $query->rowCount();
}
/**
* 获取DB链接
*
* @return PDO
*/
public function getDB()
{
if (!$this->db) {
// 生成mysql连接dsn
$config = $this->config['db'];
$is_port = (isset($config['port']) && is_int($config['port'] * 1));
$dsn = 'mysql:host=' . $config['host'] . ($is_port ? ';port=' . $config['port'] : '') . ';dbname=' . $config['database'];
if (!empty($config['charset'])) {
$dsn .= ';charset=' . $config['charset'];
}
// 数据库连接参数
$params = [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false,
];
if (isset($config['params']) && is_array($config['params'])) {
$params = $config['params'] + $params;
}
// 链接
$this->db = new PDO(
$dsn,
$config['username'],
$config['password'],
$params
);
}
return $this->db;
}
/**
* 读取gzip文件内容
*
* @param string $file 文件路径
* @return string
*/
protected function read($file)
{
// 一次读取4kb的内容
$buffer_size = 4096;
$file = gzopen($file, 'rb');
$str = '';
while (!gzeof($file)) {
$str .= gzread($file, $buffer_size);
}
gzclose($file);
return $str;
}
/**
* 写入SQL语句
*
* @param string $sql 要写入的SQL语句
* @return boolean true - 写入成功false - 写入失败!
*/
protected function write($sql)
{
$size = strlen($sql);
// 由于压缩原因无法计算出压缩后的长度这里假设压缩率为50%
// 一般情况压缩率都会高于50%
$size = $this->config['compress'] ? $size / 2 : $size;
$this->open($size);
return $this->config['compress'] ? gzwrite($this->fp, $sql) : fwrite($this->fp, $sql);
}
/**
* 打开一个文件,用于写入数据
*
* @param integer $size 写入数据的大小
* @return void
*/
protected function open($size)
{
if ($this->fp) {
$this->size += $size;
if ($this->size > $this->config['part']) {
$this->config['compress'] ? gzclose($this->fp) : fclose($this->fp);
$this->fp = null;
$this->file['part']++;
$this->backupInit();
}
} else {
$backuppath = $this->config['path'];
$filename = "{$backuppath}{$this->file['name']}-{$this->file['part']}.sql";
if ($this->config['compress']) {
$filename = "{$filename}.gz";
$this->fp = gzopen($filename, "a{$this->config['level']}");
} else {
$this->fp = fopen($filename, 'a');
}
$this->size = filesize($filename) + $size;
}
}
/**
* 关闭文件句柄
*
* @return void
*/
protected function close()
{
if (!is_null($this->fp)) {
$this->config['compress'] ? gzclose($this->fp) : fclose($this->fp);
$this->fp = null;
}
}
/**
* 写入初始数据
*
* @return boolean true - 写入成功false - 写入失败
*/
protected function backupInit()
{
$sql = "-- -----------------------------\n";
$sql .= "-- Mon MySQL Data Migrate\n";
$sql .= "-- \n";
$sql .= "-- Host : " . $this->config['db']['host'] . "\n";
$sql .= "-- Port : " . $this->config['db']['port'] . "\n";
$sql .= "-- Database : " . $this->config['db']['database'] . "\n";
$sql .= "-- UserName : " . $this->config['db']['username'] . "\n";
$sql .= "-- \n";
$sql .= "-- Version : 1.0.0";
$sql .= "-- Part : #{$this->file['part']}\n";
$sql .= "-- Date : " . date("Y-m-d H:i:s") . "\n";
$sql .= "-- -----------------------------\n\n";
$sql .= "SET FOREIGN_KEY_CHECKS = 0;\n\n";
return $this->write($sql);
}
}

380
vendor/mongdch/mon-util/src/Network.php vendored Normal file
View File

@ -0,0 +1,380 @@
<?php
namespace mon\util;
use mon\util\exception\NetWorkException;
/**
* 网络客户端工具
* 支持HTTP、批量发送HTTP、文件上传、TCP、UDP等请求
*
* @author Mon <985558837@qq.com>
* @version 1.1.0 优化错误处理,增加模拟表单上传
*/
class Network
{
use Instance;
/**
* 默认的user-agent
*
* @var string
*/
protected $userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36';
/**
* 数组转换成uri
*
* @param array $data 一维数组
* @return string uri
*/
public function arrToUri(array $data)
{
$ds = "&";
$result = "";
foreach ($data as $key => $value) {
$result = $result . $ds . trim($key) . "=" . trim($value);
}
return $result;
}
/**
* HTTP以URL的形式发送请求
*
* @param string $url 请求地址
* @param array $data 传递数据
* @param string $type 请求类型
* @param boolean $toJson 解析json返回数组
* @param integer $timeOut 请求超时时间
* @param array $header 请求头
* @param string $agent 请求user-agent
* @return mixed 结果集
*/
public function sendHTTP($url, $data = [], $type = 'GET', $toJson = false, $timeOut = 2, array $header = [], $agent = '')
{
$method = strtoupper($type);
$queryData = $data;
// get请求
if ($method == 'GET' && is_array($data) && count($data) > 0) {
$uri = $this->arrToUri($data);
$url = $url . (strpos($url, '?') === false ? '?' : '') . $uri;
$queryData = [];
}
$ch = $this->getRequest($url, $queryData, $method, $timeOut, $header, $agent);
// 发起请求
$html = curl_exec($ch);
if ($html === false) {
throw new NetWorkException('发起HTTP请求失败!', 0, null, $ch);
}
// 关闭请求句柄
curl_close($ch);
$result = ($toJson) ? json_decode($html, true) : $html;
return $result;
}
/**
* 批量发起HTTP请求
*
* @param array $queryList 请求列表
* @param integer $rolling 默认最大滚动窗口数
* @param integer $timeOut 默认超时时间
* @param array $header 默认请求头
* @param string $agent 默认user-agent
* @return array 成功结果集与失败结果集
*/
public function sendMultiHTTP($queryList, $rolling = 5, $timeOut = 2, $header = [], $agent = '')
{
$result = [];
$errors = [];
$curls = [];
$master = curl_multi_init();
// 确保滚动窗口不大于网址数量
$rolling = (count($queryList) < $rolling) ? count($queryList) : $rolling;
for ($i = 0; $i < $rolling; $i++) {
$item = $queryList[$i];
// 获取curl
$ch = $this->getCh($item, $timeOut, $header, $agent);
// 写入批量请求
curl_multi_add_handle($master, $ch);
// 记录队列
$key = (string)$ch;
$curls[$key] = [
'index' => $i,
'data' => $item,
];
}
// 发起请求
do {
while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM);
if ($execrun != CURLM_OK) {
break;
}
while ($done = curl_multi_info_read($master)) {
// 获取请求信息
$info = curl_getinfo($done['handle']);
// 请求成功
if ($info['http_code'] == 200) {
// 获取返回内容
$output = curl_multi_getcontent($done['handle']);
// 请求成功,存在回调函数,执行回调函数
$key = (string) $done['handle'];
if (isset($curls[$key]) && isset($curls[$key]['data']['callback']) && !empty($curls[$key]['data']['callback'])) {
$output = Container::instance()->invoke($curls[$key]['data']['callback'], [$output, $curls[$key], $done['handle']]);
}
$result[] = $output;
} else {
// 请求失败,执行错误处理
$key = (string) $done['handle'];
$errors[] = [
'ch' => $done['handle'],
'item' => $curls[$key]
];
}
// 发起新请求(在删除旧请求之前,请务必先执行此操作), 当$i等于$urls数组大小时不用再增加了
if ($i < count($queryList)) {
$ch = $this->getCh($queryList[$i++]);
curl_multi_add_handle($master, $ch);
// 记录队列
$key = (string)$ch;
$curls[$key] = [
'index' => $i,
'data' => $item,
];
}
// 执行下一个句柄
curl_multi_remove_handle($master, $done['handle']);
}
} while ($running);
return [
'success' => $result,
'error' => $errors
];
}
/**
* 文件上传
*
* @param string $url 上传路径
* @param string $path 文件路径
* @param array $data 额外的post参数
* @param string $filename 模拟post表单input的name值
* @param string $name 文件名
* @param array $header 额外的请求头
* @param string $agent 请求user-agent
* @param integer $timeout 上传超时时间
* @return mixed
*/
public function sendFile($url, $path, $data = [], $filename = '', $name = 'file', $toJson = false, $header = [], $agent = '', $timeout = 300)
{
// 处理文件上传数据集
$filename = empty($filename) ? basename($path) : $filename;
$sendData = array_merge($data, [$name => new \CURLFile(realpath($path), mime_content_type($path), $filename)]);
$header = array_merge(['Content-Type: multipart/form-data'], $header);
$ch = $this->getRequest($url, $sendData, 'post', $timeout, $header, $agent);
// 发起请求
$html = curl_exec($ch);
if ($html === false) {
throw new NetWorkException('发起文件上传请求失败!', 0, null, $ch);
}
// 关闭请求句柄
curl_close($ch);
$result = ($toJson) ? json_decode($html, true) : $html;
return $result;
}
/**
* 发送TCP请求
*
* @param string $ip IP
* @param integer $port 端口
* @param string $cmd 请求命令套接字
* @param integer $timeOut 超时时间
* @param boolean $toJson 是否转换JSON数组为数组
* @param boolean $close 是否关闭链接
* @return mixed 结果集
*/
public function sendTCP($ip, $port, $cmd, $timeOut = 2, $toJson = false, $close = true)
{
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
throw new NetWorkException('创建TCP-Socket失败');
}
$timeouter = ['sec' => $timeOut, 'usec' => 0];
socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeouter);
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeouter);
if (socket_connect($socket, $ip, $port) == false) {
throw new NetWorkException('链接TCP-Socket失败');
}
$send_len = strlen($cmd);
$sent = socket_write($socket, $cmd, $send_len);
if ($sent != $send_len) {
throw new NetWorkException('发送TCP套接字数据失败');
}
// 读取返回数据
$data = socket_read($socket, 1024);
// 是否转换Json格式
$result = $toJson ? json_decode($data, true) : $data;
// 是否关闭链接
if ($close) {
socket_close($socket);
}
// 返回结果集
return $result;
}
/**
* 发送UDP请求
*
* @param string $ip IP
* @param integer $port 端口
* @param string $cmd 请求命令套接字
* @param integer $timeOut 超时时间
* @param boolean $toJson 是否转换JSON数组为数组
* @param boolean $close 是否关闭链接
* @return mixed 结果集
*/
public function sendUDP($ip, $port, $cmd, $timeOut = 2, $toJson = false, $close = true)
{
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
if (!$socket) {
throw new NetWorkException('创建UDP-Socket失败');
}
$timeouter = ['sec' => $timeOut, 'usec' => 0];
socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeouter);
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeouter);
if (socket_connect($socket, $ip, $port) == false) {
// 执行链接Socket失败钩子
throw new NetWorkException('链接UDP-Socket失败');
}
$send_len = strlen($cmd);
$sent = socket_write($socket, $cmd, $send_len);
if ($sent != $send_len) {
throw new NetWorkException('发送UDP套接字数据失败');
}
// 读取返回数据
$data = socket_read($socket, 1024);
// 是否转换Json格式
$result = $toJson ? json_decode($data, true) : $data;
// 是否关闭链接
if ($close) {
socket_close($socket);
}
// 执行返回结果集
return $result;
}
/**
* 生成CURL请求
*
* @param string $url 请求的URL
* @param array $data 请求的数据
* @param string $type 请求方式
* @param integer $timeOut 超时时间
* @param array $header 请求头
* @param string $agent 请求user-agent
* @return mixed cURL句柄
*/
public function getRequest($url, $data = [], $type = 'GET', $timeOut = 2, array $header = [], $agent = '')
{
// 判断是否为https请求
$ssl = strtolower(substr($url, 0, 8)) == "https://" ? true : false;
$ch = curl_init();
// 设置请求URL
curl_setopt($ch, CURLOPT_URL, $url);
// 判断请求类型
switch (strtoupper($type)) {
case 'GET':
curl_setopt($ch, CURLOPT_HTTPGET, true);
break;
case 'POST':
curl_setopt($ch, CURLOPT_POST, true);
break;
case 'PUT':
case 'DELETE':
case 'PATCH':
$data = empty($data) ? $data : json_encode($data, JSON_UNESCAPED_UNICODE);
$header[] = 'Content-Type:application/json';
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $type);
break;
default:
throw new NetWorkException("不支持的HTTP请求类型({$type})");
}
// 判断是否需要传递数据
if (!empty($data)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
// 设置超时时间
if ($timeOut > 0) {
curl_setopt($ch, CURLOPT_TIMEOUT, $timeOut);
}
// 设置内容以文本形式返回,而不直接返回
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_VERBOSE, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 防止无限重定向
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 6);
// 设置user-agent
$userAgent = $agent ? $agent : $this->userAgent;
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
// 设置请求头
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
if (!empty($header)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
if ($ssl) {
// 跳过证书检查
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// 从证书中检查SSL加密算法是否存在
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
return $ch;
}
/**
* 解析请求列表项获取curl
*
* @param array $item 请求配置信息
* @param integer $timeOut 超时时间
* @param array $header 请求头
* @param string $agent 请求user-agent
* @return mixed cURL句柄
*/
protected function getCh($item, $timeOut = 2, $header = [], $agent = '')
{
// 请求URL
if (!isset($item['url']) || empty($item['url'])) {
throw new NetWorkException('HTTP请求列表必须存在url参数');
}
$url = $item['url'];
// 请求方式默认使用get请求
$method = (isset($item['method']) && !empty($item['method'])) ? strtoupper($item['method']) : 'GET';
// 请求数据
$data = [];
if (isset($item['data']) && !empty($item['data'])) {
$data = $item['data'];
if ($method == 'GET') {
$uri = $this->arrToUri($data);
$url = $url . (strpos($url, '?') === false ? '?' : '') . $uri;
$data = [];
}
}
// 超时时间
$timeOut = (isset($item['timeout']) && is_numeric($item['timeout'])) ? $item['timeout'] : $timeOut;
// 请求头
$header = (isset($item['header']) && !empty($item['header'])) ? $item['header'] : $header;
// 请求user-agent
$agent = (isset($item['agent']) && !empty($item['agent'])) ? $item['agent'] : $agent;
// 获取curl请求
$ch = $this->getRequest($url, $data, $method, $timeOut, $header, $agent);
return $ch;
}
}

479
vendor/mongdch/mon-util/src/Pinyin.php vendored Normal file

File diff suppressed because one or more lines are too long

3076
vendor/mongdch/mon-util/src/Qrcode.php vendored Normal file

File diff suppressed because it is too large Load Diff

82
vendor/mongdch/mon-util/src/Sql.php vendored Normal file
View File

@ -0,0 +1,82 @@
<?php
namespace mon\util;
/**
* 解析SQL文件获取SQL语句
*
* @author Mon <985558837@qq.com>
* @version 1.0.1 采用File对象读取sql文件
*/
class Sql
{
use Instance;
/**
* 解析SQL文件
*
* @param string $file sql文件路径
* @return array
*/
public function parseFile($file)
{
$content = File::instance()->read($file);
return $this->parseSql($content);
}
/**
* 解析sql内容分析生成sql语句
*
* @param string $content sql内容
* @return array
*/
public function parseSql($content)
{
if (empty($content)) {
return [];
}
// 纯sql内容
$sql = [];
// 多行注释标记
$comment = false;
// 按行分割,兼容多个平台
$content = str_replace(["\r\n", "\r"], "\n", $content);
$content = explode("\n", trim($content));
// 遍历处理每一行
foreach ($content as $key => $line) {
// 跳过空行
if ($line == '') {
continue;
}
// 跳过以#或者--开头的单行注释
if (preg_match("/^(#|--)/", $line)) {
continue;
}
// 跳过以/**/包裹起来的单行注释
if (preg_match("/^\/\*(.*?)\*\//", $line)) {
continue;
}
// 多行注释开始
if (mb_substr($line, 0, 2) == '/*') {
$comment = true;
continue;
}
// 多行注释结束
if (mb_substr($line, -2) == '*/') {
$comment = false;
continue;
}
// 多行注释没有结束,继续跳过
if ($comment) {
continue;
}
// 记录sql语句
array_push($sql, $line);
}
// 以数组形式返回sql语句
$sql = implode("\n", $sql);
$sql = explode(";\n", $sql);
return $sql;
}
}

699
vendor/mongdch/mon-util/src/Tool.php vendored Normal file
View File

@ -0,0 +1,699 @@
<?php
namespace mon\util;
use RuntimeException;
use mon\util\Instance;
use InvalidArgumentException;
/**
* 常用工具类(数据渲染)
*
* @author Mon <98558837@qq.com>
* @version v1.0.0
*/
class Tool
{
use Instance;
/**
* 调试方法(浏览器友好处理)
*
* @param mixed $var 变量
* @param boolean $echo 是否输出 默认为True 如果为false 则返回输出字符串
* @param string $label 标签 默认为空
* @param boolean $strict 是否严谨 默认为true
* @return void|string
*/
public function debug($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE)
{
$label = (null === $label) ? '' : rtrim($label) . ':';
ob_start();
var_dump($var);
$output = ob_get_clean();
$output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
if (PHP_SAPI == 'cli' || PHP_SAPI == 'cli-server') {
// CLI模式
$output = PHP_EOL . $label . $output . PHP_EOL;
} else {
if (!extension_loaded('xdebug')) {
$output = htmlspecialchars($output, $flags);
}
$output = '<pre>' . $label . $output . '</pre>';
}
if ($echo) {
echo ($output);
return;
} else {
return $output;
}
}
/**
* 判断是否为微信浏览器发起的请求
*
* @return boolean
*/
public function is_wx()
{
if (mb_strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false) {
return true;
}
return false;
}
/**
* 判断是否为安卓发起的请求
*
* @return boolean
*/
public function is_android()
{
if (mb_strpos($_SERVER['HTTP_USER_AGENT'], 'Android') !== false) {
return true;
}
return false;
}
/**
* 判断是否为苹果发起的请求
*
* @return boolean
*/
public function is_ios()
{
if (mb_strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== false || mb_strpos($_SERVER['HTTP_USER_AGENT'], 'iPad') !== false) {
return true;
}
return false;
}
/**
* 创建基于cookies的Token
*
* @param string $ticket 验证秘钥
* @param string $salt 加密盐
* @param integer $expire Cookie生存时间
* @param string $tokenName Cookie创建token的名称
* @param string $tokenTimeName cookie创建token创建时间的名称
* @return array
*/
public function createTicket($ticket, $salt = "MonUtil", $expire = 3600, $tokenName = '_token_', $tokenTimeName = '_tokenTime_')
{
$now = time();
$token = md5($salt . $now . $ticket);
$_COOKIE[$tokenName] = $token;
$_COOKIE[$tokenTimeName] = $now;
setcookie($tokenName, $token, $now + $expire, "/");
setcookie($tokenTimeName, $now, $now + $expire, "/");
return ['token' => $token, 'tokenTime' => $now];
}
/**
* 校验基于cookies的Token
*
* @param string $ticket 验证秘钥
* @param string $token Token值
* @param string $tokenTime Token创建时间
* @param string $salt 加密盐
* @param boolean $destroy 是否清除Cookie
* @param integer $expire Cookie生存时间
* @param string $tokenName Cookie创建token的名称
* @param string $tokenTimeName Cookie创建token创建时间的名称
* @return boolean
*/
public function checkTicket($ticket, $token = null, $tokenTime = null, $salt = "MonUtil", $destroy = true, $expire = 3600, $tokenName = '_token_', $tokenTimeName = '_tokenTime_')
{
$token = empty($token) ? (isset($_COOKIE[$tokenName]) ? $_COOKIE[$tokenName] : '') : $token;
$tokenTime = empty($tokenTime) ? (isset($_COOKIE[$tokenTimeName]) ? $_COOKIE[$tokenTimeName] : 0) : $tokenTime;
$now = time();
$result = false;
if (empty($token) || empty($tokenTime)) {
return $result;
}
// 校验
$check = md5($salt . $tokenTime . $ticket);
$timeGap = $now - $tokenTime;
if ($check == $token && $timeGap <= $expire) {
$result = true;
}
// 判断是否需要清空Cookie
if ($destroy) {
setcookie($tokenName, "", $now - $expire, "/");
setcookie($tokenTimeName, "", $now - $expire, "/");
}
return $result;
}
/**
* 导出CSV格式文件
*
* @param string $filename 导出文件名
* @param array $title 表格标题列表(生成:"序号,姓名,性别,年龄\n")
* @param array $titleKey 表格标题列表对应键名(注意对应title排序)
* @param array $data 导出数据
* @return void
*/
public function exportCsv($filename, $title, $titleKey = [], $data = [])
{
// 清空之前的输出
ob_get_contents() && ob_end_clean();
// 获取标题
$title = implode(",", $title) . "\n";
$str = @iconv('utf-8', 'gbk', $title); // 中文转码GBK
$len = count($titleKey);
// 遍历二维数组获取需要生成的数据
foreach ($data as $key => $value) {
// 遍历键列表获取对应数据中的键值
for ($i = 0; $i < $len; $i++) {
$val = @iconv('utf-8', 'gbk', $value[$titleKey[$i]]);
// 判断是否为最后一列数据
if ($i == ($len - 1)) {
$str .= $val . "\n";
} else {
$str .= $val . ",";
}
}
}
// 输出头信息
header("Content-type:text/csv");
header("Content-Disposition:attachment;filename=" . $filename . ".csv");
header('Cache-Control:must-revalidate,post-check=0,pre-check=0');
header('Expires:0');
header('Pragma:public');
header("Content-Length: " . mb_strlen($str));
header("Content-Transfer-Encoding: binary");
// 输出文件
echo $str;
}
/**
* 导出XML
*
* @param array $data 输出的数据
* @param string $root 根节点
* @param string $encoding 编码
* @return void
*/
public function exportXML(array $data, $root = "Mon", $encoding = 'UTF-8')
{
// 清空之前的输出
ob_get_contents() && ob_end_clean();
$xml = "<?xml version=\"1.0\" encoding=\"{$encoding}\"?>";
$xml .= "<{$root}>";
$xml .= Common::instance()->arrToXML($data);
$xml .= "</{$root}>";
header("Content-type:text/xml");
echo $xml;
}
/**
* 隐藏银行卡号
*
* @param string $id 银行卡号
* @return string
*/
public function hideBankcard($id)
{
if (empty($id)) {
return '';
}
//截取银行卡号前4位
$prefix = mb_substr($id, 0, 4);
//截取银行卡号后4位
$suffix = mb_substr($id, -4, 4);
return $prefix . " **** **** **** " . $suffix;
}
/**
* 隐藏手机号
*
* @param string $id 手机号
* @return string
*/
public function hideMoble($id)
{
if (empty($id)) {
return '';
}
return substr_replace($id, '****', 3, 4);
}
/**
* 获取客户端的IP地址
*
* @return string
*/
public function ip()
{
foreach (['X_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'] as $key) {
if (isset($_SERVER[$key])) {
return $_SERVER[$key];
}
}
return '';
}
/**
* 安全IP检测支持IP段检测
*
* @param string $ip 要检测的IP','分割例如白名单IP192.168.1.13,123.23.23.44,193.134.*.*
* @param string|array $ips 白名单IP或者黑名单IP
* @return boolean true 在白名单或者黑名单中,否则不在
*/
public function safe_ip($ip, $ips)
{
if (is_string($ips)) {
$ips = explode(",", $ips);
}
if (in_array($ip, $ips)) {
return true;
}
// IP段验证
$ipregexp = implode('|', str_replace(['*', '.'], ['\d+', '\.'], $ips));
$rs = preg_match("/^(" . $ipregexp . ")$/", $ip);
if ($rs) {
return true;
}
return false;
}
/**
* 获取两坐标距离
*
* @param float $lng1 经度1
* @param float $lat1 纬度1
* @param float $lng2 经度2
* @param float $lat2 纬度2
* @return float
*/
public function getDistance($lng1, $lat1, $lng2, $lat2)
{
$radLat1 = deg2rad($lat1);
$radLat2 = deg2rad($lat2);
$radLng1 = deg2rad($lng1);
$radLng2 = deg2rad($lng2);
$a = $radLat1 - $radLat2;
$b = $radLng1 - $radLng2;
return 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2))) * 6378.137 * 1000;
}
/**
* 计算某个经纬度的周围某段距离的正方形的四个点
* $lng = '116.655540';
* $lat = '39.910980';
* $squares = GetSquarePoint($lng, $lat);
*
* print_r($squares);
* $info_sql = "select id,locateinfo,lat,lng from `lbs_info` where lat<>0 and lat>{$squares['right-bottom']['lat']} and lat<{$squares['left-top']['lat']} and lng>{$squares['left-top']['lng']} and lng<{$squares['right-bottom']['lng']} ";
*
* @param float $lng 经度
* @param float $lat 纬度
* @param float $distance 该点所在圆的半径该圆与此正方形内切默认值为0.5千米
* @return array 正方形的四个点的经纬度坐标
*/
public function getSquarePoint($lng, $lat, $distance = 0.5)
{
if (empty($lng) || empty($lat)) {
return '';
};
// 地球半径平均半径为6371km
$radius = 6371;
$d_lng = 2 * asin(sin($distance / (2 * $radius)) / cos(deg2rad($lat)));
$d_lng = rad2deg($d_lng);
$d_lat = $distance / $radius;
$d_lat = rad2deg($d_lat);
return [
'left-top' => ['lat' => $lat + $d_lat, 'lng' => $lng - $d_lng],
'right-top' => ['lat' => $lat + $d_lat, 'lng' => $lng + $d_lng],
'left-bottom' => ['lat' => $lat - $d_lat, 'lng' => $lng - $d_lng],
'right-bottom' => ['lat' => $lat - $d_lat, 'lng' => $lng + $d_lng]
];
}
/**
* 文件打包下载
*
* @param string $downloadZip 打包后保存的文件名
* @param array $list 打包文件列表
* @param string $fileName 下载文件名,默认为打包后的文件名
* @throws RuntimeException
* @return void
*/
public function exportZip($downloadZip, array $list, $fileName = null)
{
// 初始化Zip并打开
$zip = new \ZipArchive();
// 初始化
$bool = $zip->open($downloadZip, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
// 打开文件
if ($bool !== true) {
throw new RuntimeException('PHP-ZipArchive扩展打开文件失败, Code' . $bool);
}
foreach ($list as $key => $val) {
// 把文件追加到Zip包
$zip->addFile($val, basename($val));
}
// 关闭Zip对象
$zip->close();
// 下载Zip包
$fileName = $fileName ? $fileName : basename($downloadZip);
header('Cache-Control: max-age=0');
header('Content-Description: File Transfer');
header('Content-disposition: attachment; filename=' . $fileName);
header('Content-Type: application/zip'); // zip格式的
header('Content-Transfer-Encoding: binary'); // 二进制文件
header('Content-Length: ' . filesize($downloadZip)); // 文件大小
// 清空文件的头部信息,解决文件下载无法打开问题
ob_clean();
flush();
readfile($downloadZip);
}
/**
* 目录打包下载
*
* @param string $downloadZip 打包后保存的文件名
* @param string $dirPath 打包的目录
* @param string $fileName 下载文件名,默认为打包后的文件名
* @throws InvalidArgumentException
* @return void
*/
public function exportZipForDir($downloadZip, $dirPath, $fileName = null)
{
if (!is_dir($dirPath)) {
throw new InvalidArgumentException('打包目录不存在!');
}
// 初始化Zip并打开
$zip = new \ZipArchive();
// 初始化
$bool = $zip->open($downloadZip, \ZIPARCHIVE::CREATE | \ZipArchive::OVERWRITE);
if ($bool !== true) {
throw new RuntimeException('PHP-ZipArchive扩展打开文件失败, Code' . $bool);
}
// 打开目录,压缩文件
$this->compressZip($zip, opendir($dirPath), $dirPath);
// 关闭Zip对象
$zip->close();
// 下载Zip包
$fileName = $fileName ? $fileName : basename($downloadZip);
header('Cache-Control: max-age=0');
header('Content-Description: File Transfer');
header('Content-disposition: attachment; filename=' . $fileName);
header('Content-Type: application/zip');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($downloadZip));
// 清空文件的头部信息,解决文件下载无法打开问题
ob_clean();
flush();
readfile($downloadZip);
}
/**
* 压缩添加目录文件到zip压缩包中
*
* @param \ZipArchive $zip zip句柄
* @param mixed $fileResource 文件列表句柄
* @param string $sourcePath 资源路径
* @param string $compressPath 添加zip句柄中的文件路径
* @return void
*/
protected function compressZip($zip, $fileResource, $sourcePath, $compressPath = '')
{
while (($file = readdir($fileResource)) != false) {
if ($file == "." || $file == "..") {
continue;
}
$sourceTemp = $sourcePath . '/' . $file;
$newTemp = $compressPath == '' ? $file : $compressPath . '/' . $file;
if (is_dir($sourceTemp)) {
$zip->addEmptyDir($newTemp);
$this->compressZip($zip, opendir($sourceTemp), $sourceTemp, $newTemp);
}
if (is_file($sourceTemp)) {
$zip->addFile($sourceTemp, $newTemp);
}
}
}
/**
* 解压压缩包
*
* @param string $zipName 要解压的压缩包
* @param string $dest 解压到指定目录
* @return boolean
*/
public function unZip($zipName, $dest)
{
// 检测要解压压缩包是否存在
if (!is_file($zipName)) {
return false;
}
// 检测目标路径是否存在
if (!is_dir($dest)) {
mkdir($dest, 0777, true);
}
// 初始化Zip并打开
$zip = new \ZipArchive();
// 打开并解压
if ($zip->open($zipName)) {
$zip->extractTo($dest);
$zip->close();
return true;
}
return false;
}
/**
* 输出下载文件
* 可以指定下载显示的文件名并自动发送相应的Header信息
* 如果指定了content参数则下载该参数的内容
*
* @param string $filename 下载文件名
* @param string $showname 下载显示的文件名
* @param integer $expire 下载内容浏览器缓存时间
* @throws InvalidArgumentException
* @return void
*/
public function exportFile($filename, $showname = '', $expire = 180)
{
if (!file_exists($filename)) {
throw new InvalidArgumentException($filename . '下载文件不存在!');
}
$length = filesize($filename);
if (empty($showname)) {
$showname = $filename;
}
$showname = $this->getBaseName($showname);;
if (!empty($filename)) {
$finfo = new \finfo(FILEINFO_MIME);
$type = $finfo->file($filename);
} else {
$type = "application/octet-stream";
}
// 发送Http Header信息 开始下载
header("Pragma: public");
header("Cache-Control: max-age=" . $expire);
// header('Cache-Control: no-store, no-cache, must-revalidate');
header("Expires: " . gmdate("D, d M Y H:i:s", time() + $expire) . "GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s", time()) . "GMT");
header("Content-Disposition: attachment; filename=" . $showname);
header("Content-Length: " . $length);
header("Content-type: " . $type);
header('Content-Encoding: none');
header("Content-Transfer-Encoding: binary");
// 清空文件的头部信息,解决文件下载无法打开问题
ob_clean();
flush();
readfile($filename);
}
/**
* 获取文件的名称,兼容中文名
*
* @return string
*/
public function getBaseName($filename)
{
return preg_replace('/^.+[\\\\\\/]/', '', $filename);
}
/**
* 二维码图片
*
* @param string $text 生成二维码的内容
* @param boolean|string $outfile 保存文件, false则不保存字符串路径则表示保存路径
* @param integer $level 压缩错误级别
* @param integer $size 图片尺寸 0-3
* @param integer $margin 图片边距
* @param boolean $saveandprint 是否输出图片及保存文件
* @return void
*/
public function qrcode($text, $outfile = false, $level = 0, $size = 8, $margin = 1, $saveandprint = false)
{
return QRcode::png($text, $outfile, $level, $size, $margin, $saveandprint);
}
/**
* 下载保存文件
*
* @param string $url 下载的文件路径
* @param string $savePath 保存的文件路径
* @param string $filename 保存的文件名称
* @param boolean $createDir 是否自动创建二级目录进行保存
* @throws RuntimeException|InvalidArgumentException
* @return string
*/
public function download($url, $savePath, $filename = '', $createDir = true)
{
$path = $createDir ? ($savePath . '/' . date('Ym') . '/') : ($savePath . '/');
if (!is_dir($path)) {
$create = mkdir($path, 0777, true);
if (!$create) {
throw new RuntimeException('创建下载文件保存目录失败!');
}
} else if (!is_writable($path)) {
throw new InvalidArgumentException('下载文件保存路径不可写入!');
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 300);
// 判断是否为https请求
$ssl = strtolower(mb_substr($url, 0, 8)) == "https://" ? true : false;
if ($ssl) {
curl_setopt($ch, CURLOPT_SSLVERSION, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
$file = curl_exec($ch);
curl_close($ch);
$filename = empty($filename) ? pathinfo($url, PATHINFO_BASENAME) : $filename;
$resource = fopen($path . $filename, 'a');
fwrite($resource, $file);
fclose($resource);
return $path . $filename;
}
/**
* RGB颜色值转十六进制
*
* @param string|array $reg reg颜色值
* @return string
*/
public function rgb2hex($rgb)
{
if (is_array($rgb)) {
$match = $rgb;
} else if (mb_strpos($rgb, 'rgb(') === 0) {
// 判断是否为rgb开头
$regexp = "/^rgb\(([0-9]{0,3})\,\s*([0-9]{0,3})\,\s*([0-9]{0,3})\)/";
preg_match($regexp, $rgb, $match);
$re = array_shift($match);
} else {
// 直接传入rgb的值
$match = explode(',', $rgb);
}
$hex_color = "#";
$hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
for ($i = 0; $i < 3; $i++) {
$r = null;
$c = $match[$i];
$hex_array = [];
while ($c > 16) {
$r = $c % 16;
$c = ($c / 16) >> 0;
array_push($hex_array, $hex[$r]);
}
array_push($hex_array, $hex[$c]);
$ret = array_reverse($hex_array);
$item = implode('', $ret);
$item = str_pad($item, 2, '0', STR_PAD_LEFT);
$hex_color .= $item;
}
return $hex_color;
}
/**
* 十六进制转RGB颜色
*
* @param string $hex_color 十六进制颜色值
* @return string
*/
public function hex2rgb($hex_color)
{
$color = str_replace('#', '', $hex_color);
if (mb_strlen($color) > 3) {
$rgb = [
'r' => hexdec(mb_substr($color, 0, 2)),
'g' => hexdec(mb_substr($color, 2, 2)),
'b' => hexdec(mb_substr($color, 4, 2))
];
} else {
$color = $hex_color;
$r = mb_substr($color, 0, 1) . mb_substr($color, 0, 1);
$g = mb_substr($color, 1, 1) . mb_substr($color, 1, 1);
$b = mb_substr($color, 2, 1) . mb_substr($color, 2, 1);
$rgb = [
'r' => hexdec($r),
'g' => hexdec($g),
'b' => hexdec($b)
];
}
return $rgb;
}
/**
* base64转图片
*
* @param string $base64 图片base64
* @param string $path 保存路径
* @return boolean|integer
*/
public function base64_img($base64, $path)
{
$base64Info = explode(',', $base64);
$content = base64_decode($base64Info[1]);
return File::instance()->createFile($content, $path, false);
}
/**
* 图片转base64
*
* @param string $path 图片路径
* @return string
*/
public function img_base64($path)
{
$img = getimagesize($path);
$content = chunk_split(base64_encode(file_get_contents($path)));
$base64 = 'data:' . $img['mime'] . ';base64,' . $content;
return $base64;
}
}

456
vendor/mongdch/mon-util/src/Tree.php vendored Normal file
View File

@ -0,0 +1,456 @@
<?php
namespace mon\util;
use mon\util\Instance;
/**
* 树结构数据操作类
*
* @author Mon <985558837@qq.com>
* @version 1.1 优化代码
*/
class Tree
{
use Instance;
/**
* 生成树型结构所需要的2维数组
*
* @var array
*/
public $data = [];
/**
* 配置信息
*
* @var array
*/
protected $config = [
// 空格替换
'nbsp' => " ",
// 主键
'id' => 'id',
// 子ID键
'pid' => 'pid',
// 顶级子ID
'root' => 0,
// 分级前缀
'icon' => ['│', '├', '└'],
// html生成模板
'tpl' => [
'ul' => '<li value="@id" @selected @disabled>@name @childlist</li>',
'option' => '<option value="@id" @selected @disabled>@spacer@name</option>',
],
];
/**
* 构造方法
*
* @param array $config 配置信息
*/
public function __construct($config = [])
{
$this->config = array_merge($this->config, $config);
}
/**
* 树初始化配置,支持链式操作
*
* @param array $config 配置信息
* @return Tree
*/
public function init($config = [])
{
if ($config) {
$this->config = array_merge($this->config, $config);
}
return $this;
}
/**
* 设置操作的data数组, 支持链式操作
*
* @param array $arr 操作的数据
* @return Tree
*/
public function data($arr)
{
$this->data = (array) $arr;
return $this;
}
/**
* 获取对应子级数据
*
* @param integer $pid 子级对应父级的PID
* @return array
*/
public function getChild($pid)
{
$result = [];
foreach ($this->data as $value) {
if (!isset($value[$this->config['pid']])) {
// 不存在对应子键,跳过
continue;
}
if ($value[$this->config['pid']] == $pid) {
$result[] = $value;
}
}
return $result;
}
/**
* 递归获取对应所有子级后代的数据
*
* @param integer $pid 子级对应父级的PID
* @param boolean $self 是否包含自身
* @return array
*/
public function getChildren($pid, $self = false)
{
$result = [];
foreach ($this->data as $value) {
if (!isset($value[$this->config['pid']])) {
// 不存在对应子键,跳过
continue;
}
if ($value[$this->config['pid']] == $pid) {
$result[] = $value;
// 递归获取
$result = array_merge($result, (array) $this->getChildren($value['id']));
} elseif ($self && $value[$this->config['id']] == $pid) {
$result[] = $value;
}
}
return $result;
}
/**
* 获取对应所有后代ID
*
* @param integer $pid 子级对应父级的PID
* @param boolean $self 是否包含自身
* @return array
*/
public function getChildrenIds($pid, $self = false)
{
$result = [];
$list = $this->getChildren($pid, $self);
foreach ($list as $item) {
$result[] = $item[$this->config['id']];
}
return $result;
}
/**
* 获取当前节点对应父级数据
*
* @param integer $id 节点ID
* @return array
*/
public function getParent($id)
{
$result = [];
$pid = 0;
foreach ($this->data as $value) {
if (!isset($value[$this->config['id']]) || !isset($value[$this->config['pid']])) {
// 不存在对应节点,跳过
continue;
}
if ($value[$this->config['id']] == $id) {
// 获取当前节点父节点ID
$pid = $value[$this->config['pid']];
break;
}
}
// 存在父级节点
if ($pid) {
foreach ($this->data as $item) {
if (!isset($item[$this->config['id']])) {
// 不存在对应节点,跳过
continue;
}
if ($item[$this->config['id']] == $pid) {
// 获取当前节点父节点ID
$result = $item;
break;
}
}
}
return $result;
}
/**
* 递归获取当前节点所有父级数据
*
* @param integer $id 节点ID
* @param boolean $self 是否包含自身
* @return array
*/
public function getParents($id, $self = false)
{
$result = [];
$pid = 0;
foreach ($this->data as $value) {
if (!isset($value[$this->config['id']]) || !isset($value[$this->config['pid']])) {
// 不存在对应节点,跳过
continue;
}
if ($value[$this->config['id']] == $id) {
if ($self) {
// 包含自身
$result[] = $value;
}
// 获取父级ID
$pid = $value[$this->config['pid']];
break;
}
}
// 存在父级节点
if ($pid) {
$result = array_merge((array) $this->getParents($pid, true), $result);
}
return $result;
}
/**
* 递归获取当前节点所有父级ID
*
* @param integer $id 节点ID
* @param boolean $self 是否包含自身
* @return array
*/
public function getParentsIds($id, $self)
{
$result = [];
$list = $this->getParents($id, $self);
foreach ($list as $item) {
$result[] = $item[$this->config['id']];
}
return $result;
}
/**
* 组成树结构
*
* @param string $mark 子级标志位
* @return array
*/
public function getTree($mark = 'child')
{
// 创建Tree
$tree = [];
// 创建基于主键的数组引用
$refer = [];
foreach ($this->data as $key => $data) {
$refer[$data[$this->config['id']]] = &$this->data[$key];
}
foreach ($this->data as $key => $data) {
// 判断是否存在parent
$parentId = $data[$this->config['pid']];
if ($this->config['root'] == $parentId) {
$this->data[$key]['haschild'] = 1;
$tree[] = &$this->data[$key];
} elseif (isset($refer[$parentId])) {
$parent = &$refer[$parentId];
$this->data[$key]['_mark_'] = $this->config['icon'][2];
$parent[$mark][] = &$this->data[$key];
}
}
return $tree;
}
/**
* 回滚由getTree方法生成的树结果为二维数组
*
* @param array $data 树结构数据
* @param string $mark 子级标志位
* @return array
*/
public function rollbackTree($data, $mark = 'child')
{
$result = [];
foreach ($data as $k => $v) {
// 判断是否存在子集
$child = isset($v[$mark]) ? $v[$mark] : [];
unset($v[$mark]);
$result[] = $v;
if ($child) {
// 递归合并
$result = array_merge($result, (array) $this->rollbackTree($child, $mark));
}
}
return $result;
}
/**
* 生成Option树型结构
*
* @param integer $pid 表示获得这个ID下的所有子级
* @param string $itemtpl 条目模板 如:"<option value=@id @selected @disabled>@spacer@name</option>"
* @param mixed $selectedids 被选中的ID比如在做树型下拉框的时候需要用到
* @param mixed $disabledids 被禁用的ID比如在做树型下拉框的时候需要用到
* @param string $itemprefix 每一项前缀
* @param string $toptpl 顶级栏目的模板
* @return string
*/
public function getTreeOption($pid, $itemtpl = null, $selectedids = '', $disabledids = '', $itemprefix = '', $toptpl = '')
{
$itemtpl = is_null($itemtpl) ? $this->config['tpl']['option'] : $itemtpl;
$ret = '';
$number = 1;
$childs = $this->getChild($pid);
if ($childs) {
$total = count($childs);
foreach ($childs as $value) {
$id = $value[$this->config['id']];
$j = $k = '';
if ($number == $total) {
$j .= $this->config['icon'][2];
$k = $itemprefix ? $this->config['nbsp'] : '';
} else {
$j .= $this->config['icon'][1];
$k = $itemprefix ? $this->config['icon'][0] : '';
}
$spacer = $itemprefix ? $itemprefix . $j : '';
// 判断是否需要选中
$selected = '';
if ($selectedids) {
$in = (is_array($selectedids)) ? $selectedids : explode(",", $selectedids);
$selected = in_array($id, $in) ? "selected" : '';
}
// 判断是否需要禁用
$disabled = '';
if ($disabledids) {
$in = (is_array($disabledids)) ? $disabledids : explode(",", $disabledids);
$disabled = in_array($id, $in) ? "disabled" : '';
}
$value = array_merge($value, array('selected' => $selected, 'disabled' => $disabled, 'spacer' => $spacer));
$value = array_combine(array_map(function ($k) {
return '@' . $k;
}, array_keys($value)), $value);
$nstr = strtr((($value["@{$this->config['pid']}"] == 0 || $this->getChild($id)) && $toptpl ? $toptpl : $itemtpl), $value);
$ret .= $nstr;
$ret .= $this->getTreeOption($id, $itemtpl, $selectedids, $disabledids, $itemprefix . $k . $this->config['nbsp'], $toptpl);
$number++;
}
}
return $ret;
}
/**
* 树型结构UL
*
* @param integer $pid 表示获得这个ID下的所有子级
* @param string $itemtpl 条目模板 如:"<li value=@id @selected @disabled>@name @childlist</li>"
* @param string $selectedids 选中的ID
* @param string $disabledids 禁用的ID
* @param string $wraptag 子列表包裹标签
* @return string
*/
public function getTreeUl($pid, $itemtpl = null, $selectedids = '', $disabledids = '', $wraptag = 'ul', $wrapattr = '')
{
$itemtpl = is_null($itemtpl) ? $this->config['tpl']['ul'] : $itemtpl;
$str = '';
$childs = $this->getChild($pid);
if ($childs) {
foreach ($childs as $value) {
$id = $value[$this->config['id']];
unset($value['child']);
// 判断是否需要选中
$selected = '';
if ($selectedids) {
$in = (is_array($selectedids)) ? $selectedids : explode(",", $selectedids);
$selected = in_array($id, $in) ? "selected" : '';
}
// 判断是否需要禁用
$disabled = '';
if ($disabledids) {
$in = (is_array($disabledids)) ? $disabledids : explode(",", $disabledids);
$disabled = in_array($id, $in) ? "disabled" : '';
}
$value = array_merge($value, array('selected' => $selected, 'disabled' => $disabled));
$value = array_combine(array_map(function ($k) {
return '@' . $k;
}, array_keys($value)), $value);
$nstr = strtr($itemtpl, $value);
$childdata = $this->getTreeUl($id, $itemtpl, $selectedids, $disabledids, $wraptag, $wrapattr);
$childlist = $childdata ? "<{$wraptag} {$wrapattr}>" . $childdata . "</{$wraptag}>" : "";
$str .= strtr($nstr, array('@childlist' => $childlist));
}
}
return $str;
}
/**
* 获取树状数组
*
* @param string $myid 要查询的ID
* @param string $nametpl 名称条目模板
* @param string $itemprefix 前缀
* @return string
*/
public function getTreeArray($myid, $itemprefix = '')
{
$childs = $this->getChild($myid);
$n = 0;
$data = [];
$number = 1;
if ($childs) {
$total = count($childs);
foreach ($childs as $id => $value) {
$j = $k = '';
if ($number == $total) {
$j .= $this->config['icon'][2];
$k = $itemprefix ? $this->config['nbsp'] : '';
} else {
$j .= $this->config['icon'][1];
$k = $itemprefix ? $this->config['icon'][0] : '';
}
$spacer = $itemprefix ? $itemprefix . $j : '';
$value['spacer'] = $spacer;
$data[$n] = $value;
$data[$n]['childlist'] = $this->getTreeArray($value['id'], $itemprefix . $k . $this->config['nbsp']);
$n++;
$number++;
}
}
return $data;
}
/**
* 将getTreeArray的结果返回为二维数组
*
* @param array $data 树结构数据
* @param string $field 字段名称
* @return array
*/
public function getTreeList($data = [], $field = 'name')
{
$arr = [];
foreach ($data as $k => $v) {
$childlist = isset($v['childlist']) ? $v['childlist'] : [];
unset($v['childlist']);
$v[$field] = $v['spacer'] . ' ' . $v[$field];
$v['haschild'] = ($childlist || $v['pid'] == 0) ? 1 : 0;
if ($v['id']) {
$arr[] = $v;
}
if ($childlist) {
$arr = array_merge($arr, $this->getTreeList($childlist, $field));
}
}
return $arr;
}
}

View File

@ -0,0 +1,294 @@
<?php
namespace mon\util;
use mon\util\exception\UploadException;
/**
* 文件上传类
*
* @author Mon <985558837@qq.com>
* @version 1.0.0
*/
class UploadFile
{
/**
* 配置信息
*
* @var array
*/
protected $config = [
// 允许上传的文件MiMe类型
'mimes' => [],
// 上传的文件大小限制0不做限制
'maxSize' => 0,
// 允许上传的文件后缀
'exts' => [],
// 保存根路径
'rootPath' => '',
];
/**
* 上传文件的信息
*
* @var array
*/
protected $file = [];
/**
* 构造方法
*
* @param array $config 自定义配置信息
*/
public function __construct(array $config = [])
{
$this->config = array_merge($this->config, $config);
}
/**
* 获取配置信息
*
* @param string $name 配置名称
* @return mixed 配置值
*/
public function __get($name)
{
return $this->config[$name];
}
/**
* 设置配置信息
*
* @param string $name 配置名称
* @param mixed $value 配置值
* @return void
*/
public function __set($name, $value)
{
if (isset($this->config[$name])) {
$this->config[$name] = $value;
}
}
/**
* 获取所有配置信息
*
* @return array
*/
public function getConfig()
{
return $this->config;
}
/**
* 获取上传文件信息
*
* @return mixed
*/
public function getFile()
{
return $this->file;
}
/**
* 文件上传
*
* @param string $name 文件流索引
* @param string $files 文件流,默认 $_FILES
* @throws UploadException
* @return UploadFile
*/
public function upload($name = 'file', $files = null)
{
if (is_null($files)) {
$files = $_FILES;
}
if (empty($files) || !isset($files[$name])) {
throw new UploadException('未上传文件', UploadException::ERROR_UPLOAD_FAILD);
}
// 检测上传保存路径
if (!$this->checkRootPath()) {
return false;
}
$file = $files[$name];
// 安全过滤文件名
$file['name'] = strip_tags($file['name']);
// 获取上传文件后缀,允许上传无后缀文件
$file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION);
// 检测文件
if (!$this->checkFile($file) || !$this->checkImg($file)) {
return false;
}
// 文件md5
$file['md5'] = md5_file($file['tmp_name']);
// 文件sha1
$file['sha1'] = sha1_file($file['tmp_name']);
$this->file = $file;
return $this;
}
/**
* 保存上传的文件
*
* @param string $fileName 保存文件名
* @param string $saveDir 基于 rootPath 路径下的多级目录存储路径
* @param boolean $replace 是否替换旧文件
* @throws UploadException
* @return UploadFile
*/
public function save($fileName = '', $saveDir = '', $replace = true)
{
if (empty($this->file)) {
throw new UploadException('未获取上传的文件', UploadException::ERROR_UPLOAD_NOT_FOUND);
}
// 多级目录存储
$savePath = $this->config['rootPath'] . DIRECTORY_SEPARATOR . $saveDir;
if (!empty($saveDir) && !is_dir($savePath)) {
if (!File::instance()->createDir($savePath)) {
throw new UploadException('创建文件存储目录失败', UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
}
$fileName = empty($fileName) ? uniqid(mt_rand()) . '.' . $this->file['ext'] : $fileName;
$saveName = $savePath . $fileName;
if (!$replace && is_file($saveName)) {
throw new UploadException('文件已存在', UploadException::ERROR_UPLOAD_EXISTS);
}
if (!move_uploaded_file($this->file['tmp_name'], $saveName)) {
throw new UploadException('文件上传保存错误', UploadException::ERROR_UPLOAD_SAVE_FAILD);
}
$this->file['savePath'] = $saveName;
$this->file['saveName'] = $fileName;
return $this;
}
/**
* 检测上传根目录
*
* @throws UploadException
* @return boolean
*/
protected function checkRootPath()
{
if (!(is_dir($this->config['rootPath']) && is_writable($this->config['rootPath']))) {
throw new UploadException('上传目录不存在或不可写入!请尝试手动创建:' . $this->config['rootPath'], UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
return true;
}
/**
* 检测文件
*
* @param string $file 文件路径
* @throws UploadException
* @return boolean
*/
protected function checkFile($file)
{
if ($file['error']) {
throw new UploadException($this->uploadErrorMsg($file['error']), UploadException::ERROR_UPLOAD_CHECK_FAILD);
}
// 无效上传
if (empty($file['name'])) {
throw new UploadException('未知上传错误', UploadException::ERROR_UPLOAD_NOT_MESSAGE);
}
// 检查是否合法上传
if (!is_uploaded_file($file['tmp_name'])) {
throw new UploadException('非法上传文件', UploadException::ERROR_UPLOAD_ILLEGAL);
}
// 检查文件大小
if (!$this->checkSize($file['size'])) {
throw new UploadException('上传文件大小不符', UploadException::ERROR_UPLOAD_SIZE_FAILD);
}
// 检查文件Mime类型
if (!$this->checkMime($file['type'])) {
throw new UploadException('上传文件MIME类型不允许', UploadException::ERROR_UPLOAD_MINI_FAILD);
}
// 检查文件后缀
if (!$this->checkExt($file['ext'])) {
throw new UploadException('上传文件后缀不允许', UploadException::ERROR_UPLOAD_EXT_FAILD);
}
return true;
}
/**
* 检测图片
*
* @param string $file 文件路径
* @throws UploadException
* @return boolean
*/
protected function checkImg($file)
{
$ext = strtolower($file['ext']);
if (in_array($ext, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'])) {
$imginfo = getimagesize($file['tmp_name']);
if (empty($imginfo) || ('gif' == $ext && empty($imginfo['bits']))) {
throw new UploadException('非法图像文件', UploadException::ERROR_UPLOAD_NOT_IMG);
}
}
return true;
}
/**
* 检查文件大小是否合法
*
* @param integer $size 文件大小
* @return boolean
*/
protected function checkSize($size)
{
return !($size > $this->config['maxSize']) || (0 == $this->config['maxSize']);
}
/**
* 检查上传的文件MIME类型是否合法
*
* @param string $mime 文件类型
* @return boolean
*/
protected function checkMime($mime)
{
return empty($this->config['mimes']) ? true : in_array(strtolower($mime), $this->config['mimes']);
}
/**
* 检查上传的文件后缀是否合法
*
* @param string $ext 文件后缀
* @return boolean
*/
private function checkExt($ext)
{
return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->config['exts']);
}
/**
* 获取错误代码信息
*
* @param integer $errorNo 错误号
* @return string
*/
protected function uploadErrorMsg($errorNo)
{
switch ($errorNo) {
case 1:
return '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值!';
case 2:
return '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值!';
case 3:
return '文件只有部分被上传!';
case 4:
return '没有文件被上传!';
case 6:
return '找不到临时文件夹!';
case 7:
return '文件写入失败!';
default:
return '未知上传错误!';
}
}
}

View File

@ -0,0 +1,153 @@
<?php
namespace mon\util;
use mon\util\exception\UploadException;
/**
* base64图片上传类
*
* @author Mon <985558837@qq.com>
* @version v1.1 增加图片大小限制
*/
class UploadImg
{
/**
* 文件保存路径
*
* @var string
*/
protected $path = '';
/**
* 文件保存名称
*
* @var string
*/
protected $name = '';
/**
* 设置默认文件保存路径
*
* @param string $path 保存的文件路径
* @return UploadImg
*/
public function setPath($path)
{
$this->path = $path;
return $this;
}
/**
* 获取默认文件保存路径
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* 设置默认文件保存名称
*
* @param string $name 保存的文件名称
* @return UploadImg
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* 获取默认文件保存名称
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* 保存上传图片
*
* @param string $data 图片base64
* @param string $path 保存路径
* @param string $name 保存名称
* @param string $maxSize 图片最大尺寸, 0或空则不验证
* @throws UploadException
* @return string
*/
public function upload($data, $path = '', $name = '', $maxSize = '2048000')
{
$img_base64 = explode('base64,', $data);
if (!$img_base64) {
throw new UploadException('未获取到图片数据', UploadException::ERROR_UPLOAD_FAILD);
}
$img = base64_decode(trim($img_base64[1]));
$img_type = explode('/', trim($img_base64[0], ';'));
switch (strtolower($img_type[1])) {
case 'jpeg':
case 'jpg':
$img_suffix = "jpg";
break;
case 'png':
$img_suffix = "png";
break;
case 'gif':
$img_suffix = "gif";
break;
default:
throw new UploadException('图片类型错误', UploadException::ERROR_UPLOAD_NOT_IMG);
}
if (!empty($maxSize) && mb_strlen($data) > $maxSize) {
throw new UploadException('文件大小超出', UploadException::ERROR_UPLOAD_SIZE_FAILD);
}
return $this->saveImg($img, $img_suffix, $path, $name);
}
/**
* 保存图片
*
* @param string $img 内容
* @param string $suffix 文件名称后缀
* @param string $path 保存路径
* @param string $name 保存文件名
* @throws UploadException
* @return string
*/
protected function saveImg($img, $suffix, $path, $name)
{
$path = empty($path) ? $this->path : $path;
$name = empty($name) ? $this->name : $name;
// 检测目录是否存在, 不存在则创建
if (!is_dir($path) && !mkdir($path, 0755, true)) {
throw new UploadException('保存图片失败,文件目录不存在', UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
$file_name = $this->buildName($name) . '.' . $suffix;
$path = $path . DIRECTORY_SEPARATOR . $file_name;
$save = File::instance()->createFile($img, $path, false);
if ($save === false) {
throw new UploadException('保存图片失败', UploadException::ERROR_UPLOAD_SAVE_FAILD);
}
return $file_name;
}
/**
* 获取文章保存名称
*
* @param string $name 文件名
* @return string
*/
protected function buildName($name)
{
return empty($name) ? uniqid(mt_rand()) : $name;
}
}

View File

@ -0,0 +1,263 @@
<?php
namespace mon\util;
use mon\util\exception\UploadException;
/**
* 大文件分片上传
*
* @author Mon <985558837@qq.com>
* @version 1.0.1 优化代码 2022-07-08
*/
class UploadSlice
{
/**
* 配置信息
*
* @var array
*/
protected $config = [
// 允许上传的文件后缀
'exts' => [],
// 分片文件大小限制
'sliceSize' => 0,
// 保存根路径
'rootPath' => '',
// 临时文件存储路径基于rootPath
'tmpPath' => 'tmp'
];
/**
* 错误的分片序号
*
* @var array
*/
protected $error_chunk = [];
/**
* 构造方法
*
* @param array $config 自定义配置信息
*/
public function __construct(array $config = [])
{
$this->config = array_merge($this->config, $config);
}
/**
* 获取配置信息
*
* @return array
*/
public function getConfig()
{
return $this->config;
}
/**
* 设置配置信息
*
* @param array|string $config 配置信息或配置节点
* @param mixed $value
* @return UploadSlice
*/
public function setConifg($config, $value = null)
{
if (is_array($config)) {
$this->config = array_merge($this->config, $config);
} else {
$this->config[$config] = $value;
}
return $this;
}
/**
* 获取错误的分片序号
*
* @return array
*/
public function getErrorChunk()
{
return $this->error_chunk;
}
/**
* 保存上传的文件分片到临时文件目录
*
* @param string $fileID 文件唯一ID
* @param integer $chunk 文件分片序号从0递增到N
* @param array $files 文件流,默认 $_FILES
* @param string $name 文件流索引,默认 file
* @throws UploadException
* @return false|array 文件保存路径
*/
public function upload($fileID, $chunk = 0, $name = 'file', $files = null)
{
if (is_null($files)) {
$files = $_FILES;
}
if (empty($files) || !isset($files[$name])) {
throw new UploadException('未上传文件', UploadException::ERROR_UPLOAD_FAILD);
}
// 检测上传保存路径
if (!$this->checkPath()) {
return false;
}
// 文件信息
$file = $files[$name];
// 校验文件
if (!$this->checkFile($file)) {
return false;
}
// 保存临时文件
$fileName = md5($fileID) . '_' . $chunk;
$tmpPath = $this->config['rootPath'] . DIRECTORY_SEPARATOR . $this->config['tmpPath'] . DIRECTORY_SEPARATOR . $fileID;
if (!File::instance()->createDir($tmpPath)) {
throw new UploadException('创建临时文件存储目录失败', UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
$savePath = $tmpPath . DIRECTORY_SEPARATOR . $fileName;
if (!move_uploaded_file($file['tmp_name'], $savePath)) {
throw new UploadException('临时文件保存失败', UploadException::ERROR_UPLOAD_SAVE_FAILD);
}
return ['savePath' => $savePath, 'saveDir' => $tmpPath, 'fileName' => $fileName];
}
/**
* 合并分片临时文件,生成上传文件
*
* @param string $fileID 文件唯一ID
* @param integer $chunkLength 文件分片长度
* @param string $fileName 保存文件名
* @param string $saveDir 基于 rootPath 路径下的多级目录存储路径
* @throws UploadException
* @return array 文件保存路径
*/
public function merge($fileID, $chunkLength, $fileName, $saveDir = '')
{
// 分片临时文件存储目录
$tmpPath = $this->config['rootPath'] . DIRECTORY_SEPARATOR . $this->config['tmpPath'] . DIRECTORY_SEPARATOR . $fileID;
if (!is_dir($tmpPath)) {
throw new UploadException('临时文件不存在', UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
// 验证文件名
$ext = File::instance()->getExt($fileName);
if (!empty($this->config['exts']) && !in_array($ext, $this->config['exts'])) {
throw new UploadException('不支持文件保存类型', UploadException::ERROR_UPLOAD_EXT_FAILD);
}
// 多级目录存储
$savePath = $this->config['rootPath'] . DIRECTORY_SEPARATOR . $saveDir;
if (!empty($saveDir) && !is_dir($savePath)) {
if (!File::instance()->createDir($savePath)) {
throw new UploadException('创建文件存储目录失败', UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
}
// 验证分片文件完整性
$this->error_chunk = [];
$chunkName = md5($fileID);
for ($i = 0; $i < $chunkLength; $i++) {
$checkName = $chunkName . '_' . $i;
$chunkPath = $tmpPath . DIRECTORY_SEPARATOR . $checkName;
if (!file_exists($chunkPath)) {
$this->error_chunk[] = $i;
throw new UploadException('分片文件不完整', UploadException::ERROR_CHUNK_FAILD);
}
}
// 合并文件
$saveFile = $savePath . DIRECTORY_SEPARATOR . $fileName;
// 打开保存文件句柄
$writerFp = fopen($saveFile, "ab");
for ($k = 0; $k < $chunkLength; $k++) {
$checkName = $chunkName . '_' . $k;
$chunkPath = $tmpPath . DIRECTORY_SEPARATOR . $checkName;
// 读取临时文件
$readerFp = fopen($chunkPath, "rb");
// 写入
fwrite($writerFp, fread($readerFp, filesize($chunkPath)));
// 关闭句柄
fclose($readerFp);
unset($readerFp);
// 删除临时文件
File::instance()->removeFile($chunkPath);
}
// 关闭保存文件句柄
fclose($writerFp);
// 删除临时目录
File::instance()->removeDir($tmpPath);
return ['savePath' => $saveFile, 'saveDir' => $savePath, 'fileName' => $fileName];
}
/**
* 校验文件
*
* @param array $file 文件信息
* @return boolean
*/
protected function checkFile($file)
{
if ($file['error']) {
throw new UploadException($this->uploadErrorMsg($file['error']), UploadException::ERROR_UPLOAD_CHECK_FAILD);
}
// 无效上传
if (empty($file['name'])) {
throw new UploadException('未知上传错误', UploadException::ERROR_UPLOAD_NOT_MESSAGE);
}
// 检查是否合法上传
if (!is_uploaded_file($file['tmp_name'])) {
throw new UploadException('非法上传文件', UploadException::ERROR_UPLOAD_ILLEGAL);
}
if ($file['size'] > $this->config['sliceSize'] && $this->config['sliceSize'] > 0) {
throw new UploadException('分片文件大小不符', UploadException::ERROR_UPLOAD_SIZE_FAILD);
}
return true;
}
/**
* 检测上传根目录
*
* @throws UploadException
* @return boolean
*/
protected function checkPath()
{
$rootPath = $this->config['rootPath'];
if ((!is_dir($rootPath) && !File::instance()->createDir($rootPath)) || (is_dir($rootPath) && !is_writable($rootPath))) {
throw new UploadException('上传文件保存目录不可写入:' . $rootPath, UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
$tmpPath = $rootPath . DIRECTORY_SEPARATOR . $this->config['tmpPath'];
if ((!is_dir($tmpPath) && !File::instance()->createDir($tmpPath)) || (is_dir($tmpPath) && !is_writable($tmpPath))) {
throw new UploadException('上传文件临时保存目录不可写入:' . $tmpPath, UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
return true;
}
/**
* 获取错误代码信息
*
* @param integer $errorNo 错误号
* @return string
*/
protected function uploadErrorMsg($errorNo)
{
switch ($errorNo) {
case 1:
return '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值!';
case 2:
return '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值!';
case 3:
return '文件只有部分被上传!';
case 4:
return '没有文件被上传!';
case 6:
return '找不到临时文件夹!';
case 7:
return '文件写入失败!';
default:
return '未知上传错误!';
}
}
}

769
vendor/mongdch/mon-util/src/Validate.php vendored Normal file
View File

@ -0,0 +1,769 @@
<?php
namespace mon\util;
use mon\util\exception\ValidateException;
/**
* 验证器
*
* @method ip 验证IP地址
* @method moble 验证手机号码
* @method tel 验证固定电话号码
* @method email 验证邮箱
* @method china 验证中文
* @method language 验证字母数字
* @method alpha 验证字母
* @method lower 验证小写字母
* @method upper 验证大写字母
* @method account 验证账号,只允许字母、数字和下划线、破折号
* @method id 验证ID, 大于0的正整数
* @method num 验证数字
* @method max 验证最大值
* @method min 验证最小值
* @method length 验证长度
* @method maxLength 验证最大长度
* @method minLength 验证最小长度
* @method required 验证不能为空
* @method date 验证是否为一个有效的日期
* @method timestamp 验证是否为一个有效的时间戳
* @method after 验证最后日期
* @method before 验证最早日期
* @method url 验证URL
* @method float 验证浮点数
* @method integer 验证整数
* @method regexp 自定义正则验证
* @method in 相当于in_array
* @method notIn 相当于!in_array
* @method str 验证字符串
* @method arr 验证数组
* @method json 验证JSON
* @method xml 验证XML
* @method idCard 验证身份证号
* @method confirm 比较字段
* @method eq 比较值
*
* @author Mon <985558837@qq.com>
* @version 1.3.3 2021-04-26 优化代码增加getError获取错误信息check方法返回固定boolean值
*/
class Validate
{
/**
* 对应待验证数据使用的验证规则
* [
* 'name' => 'required|length:3'
* ]
*
* @var array
*/
public $rule = [];
/**
* 当前验证规则
*
* @var array
*/
public $checkRule = [];
/**
* 待验证的数据
* [
* 'name' => 'abc',
* 'age' => '18'
* ]
*
* @var array
*/
public $data = [];
/**
* 错误提示
* [
* 'name' => [
* 'required' => '名称未设置',
* 'length' => '长度错误'
* ]
* ]
*
* @var array
*/
public $message = [];
/**
* 验证的场景
*
* @var array
*/
public $scope = [];
/**
* 当前校验的场景
*
* @var mixed
*/
public $checkScope = null;
/**
* 错误信息
*
* @var mixed
*/
public $error = null;
/**
* 正则匹配规则
*
* @var array
*/
protected $regex = [
'moble' => '/^[1][3456789][0-9]{9}$/',
'tel' => '/^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/',
'china' => '/^[\x{4e00}-\x{9fa5}]+$/u', // 中文
'language' => '/^\w*$/', // 英文数字
'alpha' => '/^[A-Za-z]+$/', // 只允许英文
'account' => '/^[A-Za-z0-9\-\_]+$/', // 只允许字母、数字和下划线 破折号
'lower' => '/^[a-z]+$/', // 小写字母
'upper' => '/^[A-Z]+$/', // 大写字母
];
/**
* 执行数据验证
*
* @param array $data 验证的数据
* @return boolean
*/
public function check(array $data = [])
{
if (!empty($data) && is_array($data)) {
$this->data = array_merge($this->data, $data);
}
// 解析验证规则
$errorItme = null;
$checkRule = empty($this->checkRule) ? $this->rule : $this->checkRule;
// 判断是否存在验证场景,获取验证字段
if (!empty($this->checkScope) && is_array($this->checkScope)) {
foreach ($this->checkScope as $v) {
if (isset($checkRule[$v])) {
$scopeRule[$v] = $checkRule[$v];
}
}
$this->checkScope = null;
} else {
$scopeRule = $checkRule;
}
foreach ($scopeRule as $dataItem => $rules) {
// 分割获取验证规则
$rule = is_array($rules) ? $rules : explode("|", $rules);
// 存在节点,验证节点
if (isset($this->data[$dataItem])) {
$value = $this->data[$dataItem];
// 解析规则
$status = $this->analysis($value, $rule, $dataItem);
if ($status !== true) {
// 验证错误,返回[错误节点,错误规则]
$errorItme = [$dataItem, $status];
break;
}
} elseif (in_array('required', $rule)) {
// 不存在节点,返回节点不存在
$errorItme = [$dataItem, 'required'];
break;
}
}
// 不存在错误节点,验证通过
if (empty($errorItme)) {
return true;
}
// 存在错误提示信息, 返回错误提示信息
if (isset($this->message[$errorItme[0]])) {
if (is_string($this->message[$errorItme[0]])) {
// 字符串,直接返回提示
$this->error = $this->message[$errorItme[0]];
return false;
} elseif (isset($this->message[$errorItme[0]][$errorItme[1]])) {
// 数组,返回对应节点提示
$this->error = $this->message[$errorItme[0]][$errorItme[1]];
return false;
} else {
// 返回默认提示
$this->error = $errorItme[0] . ' check error';
return false;
}
}
// 返回默认提示
$this->error = $errorItme[0] . ' check faild';
return false;
}
/**
* check方法的异常处理封装当验证不通过是抛出异常
*
* @param array $data
* @throws ValidateException
* @return true
*/
public function checked(array $data = [])
{
$check = $this->check($data);
if ($check !== true) {
throw new ValidateException($this->getError(), 500);
}
return true;
}
/**
* 设置本次数据的验证规则
*
* @param array $rule 验证规则
* @return Validate
*/
public function rule(array $rule = [])
{
$this->checkRule = $rule;
return $this;
}
/**
* 设置需要验证的数据
*
* @param array $data 验证的数据
* @return Validate
*/
public function data(array $data = [])
{
$this->data = $data;
return $this;
}
/**
* 设置错误提示信息
*
* @param array $message 错误信息
* @return Validate
*/
public function message($message = [])
{
$this->message = $message;
return $this;
}
/**
* 设置校验场景
*
* @param string|array $item 查询场景名称
* @return Validate
*/
public function scope($item)
{
if (is_array($item)) {
$this->checkScope = $item;
}
if (is_string($item) && isset($this->scope[$item])) {
$this->checkScope = $this->scope[$item];
}
return $this;
}
/**
* 获取错误信息
*
* @return mixed
*/
public function getError()
{
return $this->error;
}
############################### 辅助方法 ###################################
/**
* 解析规则
*
* @param mixed $value 验证的值
* @param mixed $rule 对应的验证规则
* @param string $dataItem 验证的字段名
* @return true|string 成功返回true,失败返回验证失败的规则名称
*/
protected function analysis($value, $rule, $dataItem)
{
$resule = true;
foreach ($rule as $key => $type) {
// 分割获取规则参数支持二维。例子max:9
$item = explode(":", $type, 2);
if (count($item) > 1) {
$status = $this->checkItem($dataItem, $value, $item[0], $item[1]);
} else {
$status = $this->checkItem($dataItem, $value, $item[0]);
}
// 判断验证是否通过,失败返回当前校验失败的规则名称
if (!$status) {
$resule = $item[0];
break;
}
}
return $resule;
}
/**
* 验证数据
*
* @param string $field 验证字段名称
* @param mixed $value 验证值
* @param string $rule 验证规则
* @param mixed $rule_data 规则参数
* @return boolean
*/
protected function checkItem($field, $value, $rule, $rule_data = null)
{
if (!is_null($rule_data)) {
$resule = call_user_func_array([$this, $rule], [$value, $rule_data, $field]);
} else {
$resule = call_user_func_array([$this, $rule], [$value, $field]);
}
return $resule;
}
/**
* 获取数组或字符串长度
*
* @param mixed $value 操作的数据
* @return integer
*/
protected function getLength($value)
{
if (is_array($value)) {
$length = count($value);
} else {
$length = mb_strlen((string) $value, 'UTF-8');
}
return $length;
}
############################### 验证规则 ###################################
/**
* 不能为空
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function required($value)
{
return !empty($value) || '0' == $value;
}
/**
* 最大值
*
* @param mixed $value 操作的数据
* @param mixed $max 验证的数据
* @return boolean
*/
public function max($value, $max)
{
return $this->num($value) && $value <= $max;
}
/**
* 最小值
*
* @param mixed $value 操作的数据
* @param mixed $min 验证的数据
* @return boolean
*/
public function min($value, $min)
{
return $this->num($value) && $value >= $min;
}
/**
* 指定长度(数组或字符串)
*
* @param mixed $value 操作的数据
* @param mixed $length 验证的数据
* @return boolean
*/
public function length($value, $length)
{
return $this->getLength($value) == $length;
}
/**
* 最大长度(数组或字符串)
*
* @param mixed $value 操作的数据
* @param mixed $maxLength 验证的数据
* @return boolean
*/
public function maxLength($value, $maxLength)
{
return $this->getLength($value) <= $maxLength;
}
/**
* 最小长度(数组或字符串)
*
* @param mixed $value 操作的数据
* @param mixed $minLength 验证的数据
* @return boolean
*/
public function minLength($value, $minLength)
{
return $this->getLength($value) >= $minLength;
}
/**
* 验证是否为一个有效的日期
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function date($value)
{
return strtotime($value) !== false;
}
/**
* 验证是否为一个有效的时间戳
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function timestamp($value)
{
return $this->int($value) && (strtotime(date('Y-m-d H:i:s', $value)) === $value);
}
/**
* 最后日期
*
* @param mixed $value 操作的数据
* @param mixed $date 验证的数据
* @return boolean
*/
public function afterDate($value, $date)
{
return ($this->date($value) && strtotime($value) >= strtotime($date));
}
/**
* 最早日期
*
* @param mixed $value 操作的数据
* @param mixed $date 验证的数据
* @return boolean
*/
public function beforeDate($value, $date)
{
return ($this->date($value) && strtotime($value) <= strtotime($date));
}
/**
* 正则验证
*
* @param mixed $value 操作的数据
* @param mixed $regexp 验证的数据
* @return boolean
*/
public function regexp($value, $regexp)
{
// 判断是否存在'/',不存在则补上
if (mb_strpos($regexp, '/') !== 0) {
// 不是正则表达式则两端补上/
$regexp = '/^' . $regexp . '$/';
}
return preg_match($regexp, $value) === 1;
}
/**
* IP地址
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function ip($value)
{
return filter_var($value, FILTER_VALIDATE_IP) !== false;
}
/**
* 手机号码
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function moble($value)
{
return preg_match($this->regex['moble'], $value) === 1;
}
/**
* 固定电话
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function tel($value)
{
return preg_match($this->regex['tel'], $value) === 1;
}
/**
* 邮箱地址
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function email($value)
{
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* 中文只支持UTF-8格式编码
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function china($value)
{
return preg_match($this->regex['china'], $value) === 1;
}
/**
* 字母和数字
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function language($value)
{
return preg_match($this->regex['language'], $value) === 1;
}
/**
* 字母
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function alpha($value)
{
return preg_match($this->regex['alpha'], $value) === 1;
}
/**
* 小写字母
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function lower($value)
{
return preg_match($this->regex['lower'], $value) === 1;
}
/**
* 大写字母
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function upper($value)
{
return preg_match($this->regex['upper'], $value) === 1;
}
/**
* 只允许字母、数字和下划线 破折号
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function account($value)
{
return preg_match($this->regex['account'], $value) === 1;
}
/**
* 大于0的正整数
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function id($value)
{
return $this->int($value) && ($value > 0);
}
/**
* 有效URL
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function url($value)
{
return filter_var($value, FILTER_VALIDATE_URL) !== false;
}
/**
* 浮点数
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function float($value)
{
return filter_var($value, FILTER_VALIDATE_FLOAT) !== false;
}
/**
* 整数
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function int($value)
{
return (is_numeric($value) && is_int($value + 0));
}
/**
* 数字
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function num($value)
{
return is_numeric($value);
}
/**
* 字符串
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function str($value)
{
return is_string($value);
}
/**
* 数组
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function arr($value)
{
return is_array($value);
}
/**
* JSON
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function json($value)
{
return $this->str($value) && !is_null(json_decode($value));
}
/**
* XML
*
* @param mixed $value 操作的数据
* @return boolean
*/
public function xml($value)
{
$xmlParser = xml_parser_create();
if (!$this->str($value) || !xml_parse($xmlParser, $value, true)) {
xml_parser_free($xmlParser);
return false;
}
return true;
}
/**
* 只允许某些值
*
* @param mixed $value 操作的数据
* @param string|array $in 验证的数据
* @return boolean
*/
public function in($value, $in)
{
$in = is_string($in) ? explode(',', $in) : $in;
return in_array($value, $in);
}
/**
* 不允许某些值
*
* @param mixed $value 操作的数据
* @param string|array $notin 验证的数据
* @return boolean
*/
public function notIn($value, $notin)
{
$notin = is_string($notin) ? explode(',', $notin) : $notin;
return !in_array($value, $notin);
}
/**
* 身份证号码(支持15位和18位)
*
* @param string $idcard 身份证号
* @return boolean
*/
public function idCard($idcard)
{
return IdCard::instance()->check($idcard);
}
/**
* 比较字段值是否一致
*
* @param mixed $value 比较的值
* @param string $rule 比较的字段名
* @param string $field 当前的字段名
* @param array $data 用于比较的数据集,默认为$this->data
* @return boolean
*/
public function confirm($value, $rule, $field = null, $data = [])
{
$data = empty($data) ? $this->data : $data;
return !(!isset($data[$rule]) || $value != $data[$rule]);
}
/**
* 比较两值是否相等
*
* @param mixed $value 比较的值1
* @param mixed $rule 比较的值2
* @return boolean
*/
public function eq($value, $rule)
{
return $value == $rule;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
<?php
namespace mon\util\exception;
use Exception;
/**
* 图片操作异常
*/
class IPLocationException extends Exception
{
/**
* IP数据文件未找到
*/
const ERROR_DATA_NOT_FOUND = 0;
/**
* 请先初始化
*/
const ERROR_NOT_INIT = 1;
/**
* IPV4格式错误
*/
const ERROR_IPV4_FAILD = 2;
/**
* 读取IP数据文件失败
*/
const ERROR_DATA_READ_FAILD = 3;
}

View File

@ -0,0 +1,76 @@
<?php
namespace mon\util\exception;
use Exception;
/**
* 图片操作异常
*/
class ImgException extends Exception
{
/**
* 解码GIF图片出错
*/
const ERROR_GIT_PARSE = -1;
/**
* 不存在的图像文件
*/
const ERROR_IMG_NOT_FOUND = 1;
/**
* 非法图像文件
*/
const ERROR_IMG_FAILD = 2;
/**
* 没有可以被保存的图像资源
*/
const ERROR_IMG_SAVE = 3;
/**
* 没有指定图像资源
*/
const ERROR_IMG_NOT_SPECIFIED = 4;
/**
* 不支持的缩略图裁剪类型
*/
const ERROR_IMG_NOT_SUPPORT = 5;
/**
* 水印图像不存在
*/
const ERROR_IMG_NOT_FOUND_WATER = 6;
/**
* 非法水印文件
*/
const ERROR_IMG_FAILD_WATER = 7;
/**
* 不支持的水印位置类型
*/
const ERROR_IMG_NOT_SUPPORT_WATER = 8;
/**
* 不存在的字体文件
*/
const ERROR_IMG_NOT_FOUND_FONT = 9;
/**
* 不支持的文字位置类型
*/
const ERROR_IMG_NOT_SUPPORT_FONT = 10;
/**
* 错误的颜色值
*/
const ERROR_IMG_FAILD_COLOR = 11;
/**
* 图片类型不支持
*/
const ERROR_IMG_TYPE_NOT_SUPPORT = 12;
}

View File

@ -0,0 +1,26 @@
<?php
namespace mon\util\exception;
use Exception;
/**
* 抽奖异常
*/
class LotteryException extends Exception
{
/**
* 未初始化抽奖配置
*/
const ERROR_NOT_INIT = 0;
/**
* 抽奖失败,未抽到奖品
*/
const ERROR_NOT_AWARD = -1;
/**
* 概率总和必须小于等于概率总和
*/
const ERROR_PROBABILITY_MIN = -2;
}

View File

@ -0,0 +1,12 @@
<?php
namespace mon\util\exception;
use Exception;
/**
* 数据库迁移备份异常
*/
class MigrateException extends Exception
{
}

View File

@ -0,0 +1,42 @@
<?php
namespace mon\util\exception;
use Exception;
/**
* 客服端操作异常
*/
class NetWorkException extends Exception
{
/**
* curl句柄
*
* @var mixed
*/
private $ch = null;
/**
* 重载构造方法
*
* @param string $message
* @param integer $code
* @param \Throwable $previous
* @param mixed $ch
*/
public function __construct($message, $code = 0, $previous = null, $ch = null)
{
parent::__construct($message, $code, $previous);
$this->ch = $ch;
}
/**
* 获取异常的curl句柄
*
* @return mixed
*/
public function getCh()
{
return $this->ch;
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace mon\util\exception;
use Exception;
/**
* 文件上传异常
*/
class UploadException extends Exception
{
/**
* 上传资源未找到
*/
const ERROR_UPLOAD_FAILD = 1;
/**
* 未上传
*/
const ERROR_UPLOAD_NOT_FOUND = 2;
/**
* 上传文件已重复
*/
const ERROR_UPLOAD_EXISTS = 3;
/**
* 保存失败
*/
const ERROR_UPLOAD_SAVE_FAILD = 4;
/**
* 上传目录不存在或不可写入
*/
const ERROR_UPLOAD_DIR_NOT_FOUND = 5;
/**
* 校验未通过
*/
const ERROR_UPLOAD_CHECK_FAILD = 6;
/**
* 未知上传错误
*/
const ERROR_UPLOAD_NOT_MESSAGE = 7;
/**
* 非法上传文件
*/
const ERROR_UPLOAD_ILLEGAL = 8;
/**
* 上传文件大小不符
*/
const ERROR_UPLOAD_SIZE_FAILD = 9;
/**
* 上传文件MIME类型不允许
*/
const ERROR_UPLOAD_MINI_FAILD = 10;
/**
* 上传文件后缀不允许
*/
const ERROR_UPLOAD_EXT_FAILD = 11;
/**
* 非法图像文件
*/
const ERROR_UPLOAD_NOT_IMG = 12;
/**
* 分片文件不完整
*/
const ERROR_CHUNK_FAILD = 13;
}

View File

@ -0,0 +1,24 @@
<?php
namespace mon\util\exception;
use Exception;
/**
* 验证器验证错误
*/
class ValidateException extends Exception
{
/**
* 获取错误信息
*
* @return array
*/
public function getData()
{
return [
'code' => $this->getCode(),
'msg' => $this->getMessage()
];
}
}

View File

@ -0,0 +1,432 @@
<?php
/*
|--------------------------------------------------------------------------
| 工具类函数支持
|--------------------------------------------------------------------------
| 工具类函数定义文件
|
*/
use mon\util\Tool;
use mon\util\Common;
use mon\util\IdCode;
use mon\util\Validate;
if (!function_exists('check')) {
/**
* 验证格式
*
* @param string $type 格式类型支持validate类的默认的所有方式
* @param array $args 可变参数
* @throws \Exception
* @return boolean
*/
function check($type, ...$args)
{
static $validate = null;
if (is_null($validate)) {
$validate = new Validate();
}
if (method_exists($validate, $type)) {
return call_user_func_array([$validate, $type], (array) $args);
}
throw new \Exception('不支持的验证类型[' . $type . ']');
}
}
if (!function_exists('debug')) {
/**
* 调试方法(浏览器友好处理)
*
* @param mixed $var 变量
* @param boolean $echo 是否输出 默认为True 如果为false 则返回输出字符串
* @param string $label 标签 默认为空
* @param boolean $strict 是否严谨 默认为true
* @return void|string
*/
function debug($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE)
{
return Tool::instance()->debug($var, $echo, $label, $flags);
}
}
if (!function_exists('is_wx')) {
/**
* 判断是否为微信浏览器发起的请求
*
* @return boolean
*/
function is_wx()
{
return Tool::instance()->is_wx();
}
}
if (!function_exists('is_android')) {
/**
* 判断是否为安卓发起的请求
*
* @return boolean
*/
function is_android()
{
return Tool::instance()->is_android();
}
}
if (!function_exists('is_ios')) {
/**
* 判断是否为苹果发起的请求
*
* @return boolean
*/
function is_ios()
{
return Tool::instance()->is_ios();
}
}
if (!function_exists('hideBankcard')) {
/**
* 隐藏银行卡号
*
* @param string $id 银行卡号
* @return string
*/
function hideBankcard($id)
{
return Tool::instance()->hideBankcard($id);
}
}
if (!function_exists('hideMoble')) {
/**
* 隐藏手机号
*
* @param string $id 手机号
* @return string
*/
function hideMoble($id)
{
return Tool::instance()->hideMoble($id);
}
}
if (!function_exists('trimAll')) {
/**
* 删除字符串中的空格
*
* @param $str 要删除空格的字符串
* @return $str 返回删除空格后的字符串
*/
function trimAll($str)
{
return Common::instance()->trimAll($str);
}
}
if (!function_exists('hidestr')) {
/**
* 将一个字符串部分字符用$re替代隐藏
*
* @param string $string 待处理的字符串
* @param integer $start 规定在字符串的何处开始,
* 正数 - 在字符串的指定位置开始
* 负数 - 在从字符串结尾的指定位置开始
* 0 - 在字符串中的第一个字符处开始
* @param integer $length 可选。规定要隐藏的字符串长度。默认是直到字符串的结尾。
* 正数 - start 参数所在的位置隐藏
* 负数 - 从字符串末端隐藏
* @param string $re 替代符
* @return string 处理后的字符串
*/
function hidestr($string, $start = 0, $length = 0, $re = '*')
{
return Common::instance()->hidestr($string, $start, $length, $re);
}
}
if (!function_exists('ip')) {
/**
* 获取客户端的IP地址
*
* @return string
*/
function ip()
{
return Tool::instance()->ip();
}
}
if (!function_exists('safe_ip')) {
/**
* 安全IP检测支持IP段检测
*
* @param string $ip 要检测的IP','分割
* @param string|array $ips 白名单IP或者黑名单IP
* @return boolean true 在白名单或者黑名单中,否则不在
*/
function safe_ip($ip, $ips)
{
return Tool::instance()->safe_ip($ip, $ips);
}
}
if (!function_exists('encodeEX')) {
/**
* 字符串编码过滤(中文、英文、数字不过滤,只过滤特殊字符)
*
* @param string $src 安全转码的字符串
* @return string
*/
function encodeEX($src)
{
return Common::instance()->encodeEX($src);
}
}
if (!function_exists('decodeEX')) {
/**
* 字符串编码过滤(中文、英文、数字不过滤,只过滤特殊字符)
*
* @param string $src 安全转码的字符串
* @return string
*/
function decodeEX($src)
{
return Common::instance()->decodeEX($src);
}
}
if (!function_exists('encryption')) {
/**
* 字符串加密方法
*
* @param string $str 加密的字符串
* @param string $salt 加密盐
* @return string
*/
function encryption($str, $salt)
{
return Common::instance()->encryption($str, $salt);
}
}
if (!function_exists('decryption')) {
/**
* 字符串解密方法
*
* @param string $str 解密的字符串
* @param string $salt 解密的盐
* @return string
*/
function decryption($str, $salt)
{
return Common::instance()->decryption($str, $salt);
}
}
if (!function_exists('mod')) {
/**
* 获取余数
*
* @param integer $bn 被除数
* @param integer $sn 除数
* @return integer
*/
function mod($bn, $sn)
{
return Common::instance()->mod($bn, $sn);
}
}
if (!function_exists('ip2long_positive')) {
/**
* 返回正数的ip2long值
*
* @param string $ip ip
* @return integer
*/
function ip2long_positive($ip)
{
return Common::instance()->ip2long_positive($ip);
}
}
if (!function_exists('strToMap')) {
/**
* URI字符串转数组
*
* @param string $str 入参,待转换的字符串
* @return array 字符数组
*/
function strToMap($str)
{
return Common::instance()->strToMap($str);
}
}
if (!function_exists('mapToStr')) {
/**
* 数组转字符串
*
* @param array $map 入参,待转换的数组
* @return string
*/
function mapToStr($map)
{
return Common::instance()->mapToStr($map);
}
}
if (!function_exists('array_2D_unique')) {
/**
* 二维数组去重(&值不能完全相同)
*
* @param array $arr 需要去重的数组
* @return array
*/
function array_2D_unique($arr)
{
return Common::instance()->array_2D_unique($arr);
}
}
if (!function_exists('array_2D_value_unique')) {
/**
* 二维数组去重(值不能相同)
*
* @param array $arr 需要去重的数组
* @return array
*/
function array_2D_value_unique($arr)
{
return Common::instance()->array_2D_value_unique($arr);
}
}
if (!function_exists('isAssoc')) {
/**
* 是否为关联数组
*
* @param array $array 验证码的数组
* @return boolean
*/
function isAssoc($arr)
{
return Common::instance()->isAssoc($arr);
}
}
if (!function_exists('array2DSort')) {
/**
* 二维数组排序
*
* @param array $array 排序的数组
* @param string $keys 排序的键名
* @param integer $sort 排序方式默认值SORT_DESC
* @return array
*/
function array2DSort($array, $keys, $sort = SORT_DESC)
{
return Common::instance()->array2DSort($array, $keys, $sort);
}
}
if (!function_exists('uuid')) {
/**
* 生成UUID 单机使用
*
* @return string
*/
function uuid()
{
return Common::instance()->uuid();
}
}
if (!function_exists('keyGen')) {
/**
* 生成Guid主键
*
* @return string
*/
function keyGen()
{
return Common::instance()->keyGen();
}
}
if (!function_exists('mSubstr')) {
/**
* 字符串截取,支持中文和其他编码
*
* @param string $str 需要转换的字符串
* @param string $start 开始位置
* @param string $length 截取长度
* @param string $charset 编码格式
* @param string $suffix 截断显示字符
* @return string
*/
function mSubstr($str, $length, $start = 0, $charset = "utf-8", $suffix = true)
{
return Common::instance()->mSubstr($str, $start, $length, $charset, $suffix);
}
}
if (!function_exists('randString')) {
/**
* 产生随机字串,可用来自动生成密码
* 默认长度6位 字母和数字混合 支持中文
*
* @param string $len 长度
* @param string $type 字串类型0:字母;1:数字;2:大写字母;3:小写字母;4:中文;5:字母数字混合;othor:过滤掉混淆字符的字母数字组合
* @param string $addChars 额外字符
* @return string
*/
function randString($len = 6, $type = '', $addChars = '')
{
return Common::instance()->randString($len, $type, $addChars);
}
}
if (function_exists('getBaseName')) {
/**
* 获取文件的名称,兼容中文名
*
* @return string
*/
function getBaseName($filename)
{
return Tool::instance()->getBaseName($filename);
}
}
if (!function_exists('id2code')) {
/**
* id转code字符串
*
* @param integer $id 要加密的id值
* @return string
*/
function id2code($id)
{
return IdCode::instance()->id2code($id);
}
}
if (!function_exists('code2id')) {
/**
* code转ID
*
* @param string $code 加密生成的code
* @return integer
*/
function code2id($code)
{
return IdCode::instance()->code2id($code);
}
}

View File

@ -0,0 +1,6 @@
composer.phar
/vendor/
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2018 Mon
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.

View File

@ -0,0 +1,3 @@
# webman-uploadslice
webman大文件分片上传插件

View File

@ -0,0 +1,20 @@
{
"name": "mongdch/webman-uploadslice",
"type": "library",
"license": "MIT",
"description": "Webman plugin mongdch/webman-uploadslice",
"authors": [
{
"name": "MonGDCH",
"email": "985558837@qq.com"
}
],
"require": {
"mongdch/mon-util": "^1.3"
},
"autoload": {
"psr-4": {
"Mongdch\\WebmanUploadslice\\": "src"
}
}
}

144
vendor/mongdch/webman-uploadslice/composer.lock generated vendored Normal file
View File

@ -0,0 +1,144 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "25ca72cf2240efdef6486158858be113",
"packages": [
{
"name": "mongdch/mon-util",
"version": "1.3.13",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/mongdch/mon-util/1.3.13/mongdch-mon-util-1.3.13.zip",
"reference": "9f3d445a7c0f3bace9a3f623164d7d3914cc84c8",
"shasum": ""
},
"require": {
"php": ">=5.6.0",
"psr/container": "1.0.0",
"psr/log": "^1.1"
},
"type": "library",
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"mon\\util\\": "src/"
}
},
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "MonGDCH",
"email": "985558837@qq.com"
}
],
"description": "常用的PHP工具类库包含了各种各式各样的工具容器、时间、验证器、多语言、图片、二维码、IP地址、文件上传、文件操作、加解密、数据字典、各种算法等等等等。。。",
"homepage": "http://www.gdmon.com",
"keywords": [
"mon",
"tool",
"util"
],
"time": "2022-07-26T07:59:23+00:00"
},
{
"name": "psr/container",
"version": "1.0.0",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/psr/container/1.0.0/psr-container-1.0.0.zip",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"time": "2017-02-14T16:28:37+00:00"
},
{
"name": "psr/log",
"version": "1.1.4",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/psr/log/1.1.4/psr-log-1.1.4.zip",
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2021-05-03T11:20:27+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.2.0"
}

View File

@ -0,0 +1,75 @@
<?php
/**
* 演示使用的上传接口DEMO
*
* @author Mon <985558837@qq.com>
* @version 1.0.0
*/
use Webman\Route;
use support\Request;
use mon\util\Validate;
use mon\util\exception\UploadException;
use Mongdch\WebmanUploadslice\UploadSlice;
// 上传页面
Route::any('/', function (Request $request) {
return view('upload');
});
// 上传接口
Route::post('/upload', function (Request $request) {
$data = $request->post();
// 验证数据
$validate = new Validate();
$check = $validate->data($data)->rule([
'action' => ['in:slice,merge'],
'filename' => ['required', 'str'],
'chunk' => ['int', 'min:0'],
'chunkLength' => ['required', 'int', 'min:0'],
'uuid' => ['required', 'str']
])->message([
'action' => 'action faild',
'filename' => 'filename faild',
'chunk' => 'chunk faild',
'chunkLength' => 'chunkLength faild',
'uuid' => 'uuid faild'
])->check();
if (!$check) {
return json(['code' => 0, 'msg' => $validate->getError()]);
}
// 验证上传分片必须的参数
if ($request->post('action') == 'slice' && is_null($request->post('chunk'))) {
return json(['code' => 0, 'msg' => 'chunk required']);
}
if ($request->post('action') == 'slice' && empty($request->file())) {
return json(['code' => 0, 'msg' => 'upload faild']);
}
// 上传
$sdk = new UploadSlice();
$file = $request->file('file');
try {
if ($data['action'] == 'slice') {
// 保存分片
$saveInfo = $sdk->upload($data['uuid'], $file, $data['chunk']);
return json(['code' => 1, 'msg' => 'ok', 'data' => $saveInfo]);
}
// 合并
$mergeInfo = $sdk->merge($data['uuid'], $data['chunkLength'], $data['filename']);
// $mergeInfo = $sdk->merge($data['uuid'], $data['chunkLength'], $data['filename'], 'dirname');
return json(['code' => 1, 'msg' => 'ok', 'data' => $mergeInfo]);
} catch (UploadException $e) {
return json(['code' => 0, 'msg' => $e->getMessage()]);
}
return json($sdk->getConfig());
});
Route::fallback(function () {
return json(['code' => 404, 'msg' => '404 not found']);
});
Route::disableDefaultRoute();

View File

@ -0,0 +1,407 @@
<!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.0">
<title>多线程异步大文件分片上传</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.main {
width: 860px;
margin: 40px auto;
}
#percent-bg {
margin-top: 20px;
position: relative;
width: 100%;
height: 30px;
border: 1px solid #ccc;
}
#percent {
position: absolute;
display: block;
width: 0;
height: 100%;
left: 0;
background: #67C23A;
z-index: 100;
}
#percent_num {
position: absolute;
display: block;
width: 30px;
height: 100%;
left: 50%;
margin-left: -15px;
z-index: 200;
font-size: 14px;
line-height: 30px;
text-align: center;
}
#message {
margin-top: 12px;
width: 100%;
height: 400px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 4px;
outline: none;
line-height: 20px;
font-size: 14px;
}
#loading-modal {
position: fixed;
z-index: 9999999999999999;
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, .5);
}
.loading-main {
position: relative;
width: 56px;
height: 56px;
}
#loading-num {
width: 120px;
font-size: 14px;
margin-top: 8px;
transform: translateX(-14px);
color: #fff
}
.loading-time {
width: 56px;
height: 56px;
border-bottom: 8px solid #f3f3f3;
border-top: 8px solid #f3f3f3;
border-color: #3498db #f3f3f3;
border-style: solid;
border-width: 8px;
border-radius: 50%;
animation: loading-spin 2s linear infinite;
-webkit-animation: loading-spin 2s linear infinite;
box-sizing: border-box;
/* background: #fff; */
}
@-webkit-keyframes loading-spin {
0% {
-webkit-transform: rotate(0deg)
}
to {
-webkit-transform: rotate(1turn)
}
}
@keyframes loading-spin {
0% {
transform: rotate(0deg)
}
to {
transform: rotate(1turn)
}
}
</style>
</head>
<body>
<div class="main">
<input type="file" name="file" id="file">
<button onclick="send()">上传文件</button>
<textarea id="message" readonly></textarea>
<div id="percent-bg">
<span id="percent"></span>
<span id="percent_num">0%</span>
</div>
</div>
<div id="loading-modal" style="display: none;">
<div class="loading-main">
<div class="loading-time"></div>
<div id="loading-num">正在解析文件</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/browser-md5-file@1.1.1/dist/index.umd.min.js"></script>
<script>
const config = {
// 文件大小限制20M
"maxSize": 1024 * 1024 * 500,
// 单个分片大小
"bufferSize": 1024 * 1024,
// 上传线程数量
"threadNum": 2,
}
// 分片数据集合
let blocks = []
// 上传的本地文件真实文件名
let filename = ''
// 文件uuid
let uuid = ''
// 分片索引
let __index = 0;
// 当前活跃线程数量
let __activeThreadCount = 0;
// 已上传的block数量
let __sendedBlockCount = 0;
// 强制结束进程
let stopRun = false;
// 上传URL
const sednURL = '/upload'
// 文件上传
async function send() {
if (document.getElementById("file").files.length < 1) {
return false;
}
const file = document.getElementById("file").files[0]
// 验证文件大小
if (file.size > config.maxSize) {
console.log(111)
showMessage("文件大小限制:" + config.maxSize + ",实际文件大小:" + file.size);
return;
}
// 重置上传信息
reset()
// 打开Loading
showLoading(true)
// 使用文件md5作为uuid
uuid = await getUid(file)
// 文件分片
let endByte = 0;
let startByte = 0;
while (true) {
startByte = endByte;
if (endByte + config.bufferSize >= file.size) {
endByte = file.size;
} else {
endByte = endByte + config.bufferSize;
}
let block = sliceFile(file, startByte, endByte);
if (!block) {
showMessage("分片失败");
return;
}
blocks.push(block);
if (endByte >= file.size) {
break;
}
}
// 关闭Loading
showLoading(false)
showMessage("文件名:" + file.name);
showMessage("文件大小:" + file.size);
showMessage("总分片数量:" + blocks.length);
filename = file.name
// 开启上传进程
const threadNum = Math.min(config.threadNum, blocks.length);
for (var i = 0; i < threadNum; i++) {
if (stopRun) {
return;
}
(function (i) {
setTimeout(function () {
__activeThreadCount++;
run(i);
}, 500);
})(i);
}
}
// 获取文件uuid
function getUid(file) {
const bmf = new browserMD5File()
return new Promise((resolve, reject) => {
bmf.md5(file, function (err, md5) {
if (err) {
console.error('get uid err:', err);
showMessage('获取文件md5失败!');
return reject(err);
}
// console.log('md5 string:', md5);
// uuid = md5
resolve(md5)
})
})
}
// 重置
function reset() {
blocks = []
filename = ''
uuid = ''
__index = 0;
__activeThreadCount = 0;
__sendedBlockCount = 0;
stopRun = false
}
// 运行上传线程
function run(i) {
if (stopRun) {
return;
}
if (__index >= blocks.length) {
showMessage("线程" + i + ' 结束');
__activeThreadCount--;
if (__activeThreadCount == 0) {
showMessage("------------------------");
showMessage('多线程分片上传完毕,正在处理分片数据...');
merge();
}
return;
}
uploadSlice(i, __index);
__index++;
}
// 上传分片
function uploadSlice(name, chunkIndex) {
showMessage('线程' + name + ' 分片' + chunkIndex + ' start')
// 发送数据
const formData = new FormData()
formData.append('file', blocks[chunkIndex])
formData.append('filename', filename)
formData.append('chunk', chunkIndex)
formData.append('chunkLength', blocks.length)
formData.append('uuid', uuid)
formData.append('action', 'slice')
// 发送请求
sendXhr(sednURL, formData, function (result) {
if (result.code != '1') {
stopRun = true;
showMessage('上传失败! Message' + result.msg);
return;
}
showMessage("线程" + name + " 分片" + chunkIndex + " end");
__sendedBlockCount++;
showPercent();
run(name);
})
}
// 发起合并请求
function merge() {
// 发送数据
const formData = new FormData()
formData.append('filename', filename)
formData.append('chunkLength', blocks.length)
formData.append('uuid', uuid)
formData.append('action', 'merge')
// 发送请求
sendXhr(sednURL, formData, function (result) {
if (result.code != '1') {
stopRun = true;
showMessage('上传失败! Message' + result.msg);
return;
}
showMessage('分片数据处理完成,任务结束');
showMessage("")
showMessage("")
reset();
})
}
// 分割file
function sliceFile(file, startByte, endByte) {
if (file.slice) {
return file.slice(startByte, endByte);
}
if (file.webkitSlice) {
return file.webkitSlice(startByte, endByte);
}
if (file.mozSlice) {
return file.mozSlice(startByte, endByte);
}
return null;
}
//显示进度
function showPercent() {
var percent = parseInt(__sendedBlockCount / blocks.length * 100);
if (percent > 100) { percent = 100; }
document.querySelector('#percent').style.width = percent + "%"
document.querySelector('#percent_num').innerHTML = percent + "%"
}
// 渲染消息
function showMessage(msg) {
const txt = document.querySelector('#message').value
const message = txt + msg + "\r\n"
document.querySelector('#message').value = message
toMessageBottom()
}
// 消息至底部
function toMessageBottom() {
var div = document.querySelector('#message');
div.scrollTop = div.scrollHeight;
}
// 已送异步请求
function sendXhr(url, data, success, error, headers = {}) {
// 1.创建对象
let xhr = new XMLHttpRequest();
// 2.设置请求行(get请求数据写在url后面)
xhr.open('post', url);
// 3.设置请求头(get请求可以省略,post不发送数据也可以省略)
for (let key in headers) {
xhr.setRequestHeader(key, headers[key]);
}
// 4.注册回调函数
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
success(JSON.parse(xhr.responseText));
} else {
error(xhr.responseText);
}
}
};
// XHR2.0新增 上传进度监控
xhr.upload.onprogress = function (event) {
// console.log(event);
}
// 处理发送数据
// let data = new FormData()
// let params = this.getAttribute('data-params');
// params = JSON.parse(params)
// for (let key in params) {
// let value = params[key]
// data.append(key, value)
// }
// let name = this.getAttribute('name');
// data.append(name, file)
// 6.请求主体发送
xhr.send(data);
}
// loading
function showLoading(show) {
if (show) {
document.querySelector('#loading-modal').style.display = 'flex'
} else {
document.querySelector('#loading-modal').style.display = 'none'
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,74 @@
<?php
namespace Mongdch\WebmanUploadslice;
class Install
{
const WEBMAN_PLUGIN = true;
/**
* @var array
*/
protected static $pathRelation = array (
'config/plugin/mongdch/webman-uploadslice' => 'config/plugin/mongdch/webman-uploadslice',
);
/**
* Install
* @return void
*/
public static function install()
{
static::installByRelation();
}
/**
* Uninstall
* @return void
*/
public static function uninstall()
{
self::uninstallByRelation();
}
/**
* installByRelation
* @return void
*/
public static function installByRelation()
{
foreach (static::$pathRelation as $source => $dest) {
if ($pos = strrpos($dest, '/')) {
$parent_dir = base_path().'/'.substr($dest, 0, $pos);
if (!is_dir($parent_dir)) {
mkdir($parent_dir, 0777, true);
}
}
//symlink(__DIR__ . "/$source", base_path()."/$dest");
copy_dir(__DIR__ . "/$source", base_path()."/$dest");
echo "Create $dest
";
}
}
/**
* uninstallByRelation
* @return void
*/
public static function uninstallByRelation()
{
foreach (static::$pathRelation as $source => $dest) {
$path = base_path()."/$dest";
if (!is_dir($path) && !is_file($path)) {
continue;
}
echo "Remove $dest
";
if (is_file($path) || is_link($path)) {
unlink($path);
continue;
}
remove_dir($path);
}
}
}

View File

@ -0,0 +1,206 @@
<?php
namespace Mongdch\WebmanUploadslice;
use mon\util\File;
use Webman\File as WebmanFile;
use mon\util\exception\UploadException;
/**
* 大文件分片上传
*
* @author Mon <985558837@qq.com>
* @version 1.0.0
*/
class UploadSlice
{
/**
* 配置信息
*
* @var array
*/
protected $config = [
// 允许上传的文件后缀
'exts' => [],
// 分片文件大小限制
'sliceSize' => 0,
// 保存根路径
'rootPath' => '',
// 临时文件存储路径基于rootPath
'tmpPath' => 'tmp'
];
/**
* 错误的分片序号
*
* @var array
*/
protected $error_chunk = [];
/**
* 构造方法
*
* @param array $config 自定义配置信息
*/
public function __construct(array $config = [])
{
if (empty($config)) {
$config = config('plugin.mongdch.webman-uploadslice.app');
}
$this->config = array_merge($this->config, $config);
}
/**
* 获取配置信息
*
* @return array
*/
public function getConfig(): array
{
return $this->config;
}
/**
* 设置配置信息
*
* @param array|string $config 配置信息或配置节点
* @param mixed $value
* @return UploadSlice
*/
public function setConifg($config, $value = null): UploadSlice
{
if (is_array($config)) {
$this->config = array_merge($this->config, $config);
} else {
$this->config[$config] = $value;
}
return $this;
}
/**
* 获取错误的分片序号
*
* @return array
*/
public function getErrorChunk(): array
{
return $this->error_chunk;
}
/**
* 保存上传的文件分片到临时文件目录
*
* @param string $fileID 文件唯一ID
* @param WebmanFile $file 文件流对象
* @param integer $chunk 文件分片序号从0递增到N
* @throws UploadException
* @return false|array 文件保存路径
*/
public function upload(string $fileID, WebmanFile $file, int $chunk = 0)
{
// 检测上传保存路径
if (!$this->checkPath()) {
return false;
}
// 校验分片文件大小
if ($this->config['sliceSize'] > 0 && $file->getSize() > $this->config['sliceSize']) {
throw new UploadException('分片文件大小不符', UploadException::ERROR_UPLOAD_SIZE_FAILD);
}
// 保存临时文件
$fileName = md5($fileID) . '_' . $chunk;
$tmpPath = $this->config['rootPath'] . DIRECTORY_SEPARATOR . $this->config['tmpPath'] . DIRECTORY_SEPARATOR . $fileID;
if (!File::instance()->createDir($tmpPath)) {
throw new UploadException('创建临时文件存储目录失败', UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
$savePath = $tmpPath . DIRECTORY_SEPARATOR . $fileName;
$file->move($savePath);
return ['savePath' => $savePath, 'saveDir' => $tmpPath, 'fileName' => $fileName];
}
/**
* 合并分片临时文件,生成上传文件
*
* @param string $fileID 文件唯一ID
* @param integer $chunkLength 文件分片长度
* @param string $fileName 保存文件名
* @param string $saveDir 基于 rootPath 路径下的多级目录存储路径
* @throws UploadException
* @return array 文件保存路径
*/
public function merge(string $fileID, int $chunkLength, string $fileName, string $saveDir = ''): array
{
// 分片临时文件存储目录
$tmpPath = $this->config['rootPath'] . DIRECTORY_SEPARATOR . $this->config['tmpPath'] . DIRECTORY_SEPARATOR . $fileID;
if (!is_dir($tmpPath)) {
throw new UploadException('临时文件不存在', UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
// 验证文件名
$ext = File::instance()->getExt($fileName);
if (!empty($this->config['exts']) && !in_array($ext, $this->config['exts'])) {
throw new UploadException('不支持文件保存类型', UploadException::ERROR_UPLOAD_EXT_FAILD);
}
// 多级目录存储
$savePath = $this->config['rootPath'] . DIRECTORY_SEPARATOR . $saveDir;
if (!empty($saveDir) && !is_dir($savePath)) {
if (!File::instance()->createDir($savePath)) {
throw new UploadException('创建文件存储目录失败', UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
}
// 验证分片文件完整性
$this->error_chunk = [];
$chunkName = md5($fileID);
for ($i = 0; $i < $chunkLength; $i++) {
$checkName = $chunkName . '_' . $i;
$chunkPath = $tmpPath . DIRECTORY_SEPARATOR . $checkName;
if (!file_exists($chunkPath)) {
$this->error_chunk[] = $i;
throw new UploadException('分片文件不完整', UploadException::ERROR_CHUNK_FAILD);
}
}
// 合并文件
$saveFile = $savePath . DIRECTORY_SEPARATOR . $fileName;
// 打开保存文件句柄
$writerFp = fopen($saveFile, "ab");
for ($k = 0; $k < $chunkLength; $k++) {
$checkName = $chunkName . '_' . $k;
$chunkPath = $tmpPath . DIRECTORY_SEPARATOR . $checkName;
// 读取临时文件
$readerFp = fopen($chunkPath, "rb");
// 写入
fwrite($writerFp, fread($readerFp, filesize($chunkPath)));
// 关闭句柄
fclose($readerFp);
unset($readerFp);
// 删除临时文件
File::instance()->removeFile($chunkPath);
}
// 关闭保存文件句柄
fclose($writerFp);
// 删除临时目录
File::instance()->removeDir($tmpPath);
return ['savePath' => $saveFile, 'saveDir' => $savePath, 'fileName' => $fileName];
}
/**
* 检测上传根目录
*
* @throws UploadException
* @return boolean
*/
protected function checkPath()
{
$rootPath = $this->config['rootPath'];
if ((!is_dir($rootPath) && !File::instance()->createDir($rootPath)) || (is_dir($rootPath) && !is_writable($rootPath))) {
throw new UploadException('上传文件保存目录不可写入:' . $rootPath, UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
$tmpPath = $rootPath . DIRECTORY_SEPARATOR . $this->config['tmpPath'];
if ((!is_dir($tmpPath) && !File::instance()->createDir($tmpPath)) || (is_dir($tmpPath) && !is_writable($tmpPath))) {
throw new UploadException('上传文件临时保存目录不可写入:' . $tmpPath, UploadException::ERROR_UPLOAD_DIR_NOT_FOUND);
}
return true;
}
}

View File

@ -0,0 +1,14 @@
<?php
return [
// 启用插件
'enable' => true,
// 允许上传的文件后缀
'exts' => [],
// 分片文件大小限制
'sliceSize' => 0,
// 保存根路径
'rootPath' => public_path() . DIRECTORY_SEPARATOR . 'upload',
// 临时文件存储路径基于rootPath
'tmpPath' => 'tmp'
];

View File

@ -1,8 +0,0 @@
logs
.buildpath
.project
.settings
.idea
.DS_Store
composer.lock
vendor

View File

@ -1,200 +0,0 @@
# MQTT
Asynchronous MQTT client for PHP based on workerman.
# Installation
composer require workerman/mqtt
# 文档
[中文文档](https://www.workerman.net/doc/workerman/components/workerman-mqtt.html)
# Example
**subscribe.php**
```php
<?php
require __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker();
$worker->onWorkerStart = function(){
$mqtt = new Workerman\Mqtt\Client('mqtt://test.mosquitto.org:1883');
$mqtt->onConnect = function($mqtt) {
$mqtt->subscribe('test');
};
$mqtt->onMessage = function($topic, $content){
var_dump($topic, $content);
};
$mqtt->connect();
};
Worker::runAll();
```
Run with command ```php subscribe.php start```
**publish.php**
```php
<?php
require __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker();
$worker->onWorkerStart = function(){
$mqtt = new Workerman\Mqtt\Client('mqtt://test.mosquitto.org:1883');
$mqtt->onConnect = function($mqtt) {
$mqtt->publish('test', 'hello workerman mqtt');
};
$mqtt->connect();
};
Worker::runAll();
```
Run with command ```php publish.php start```
## API
* <a href="#construct"><code>Client::<b>__construct()</b></code></a>
* <a href="#connect"><code>Client::<b>connect()</b></code></a>
* <a href="#publish"><code>Client::<b>publish()</b></code></a>
* <a href="#subscribe"><code>Client::<b>subscribe()</b></code></a>
* <a href="#unsubscribe"><code>Client::<b>unsubscribe()</b></code></a>
* <a href="#disconnect"><code>Client::<b>disconnect()</b></code></a>
* <a href="#close"><code>Client::<b>close()</b></code></a>
* <a href="#onConnect"><code>callback <b>onConnect</b></code></a>
* <a href="#onMessage"><code>callback <b>onMessage</b></code></a>
* <a href="#onError"><code>callback <b>onError</b></code></a>
* <a href="#onClose"><code>callback <b>onClose</b></code></a>
-------------------------------------------------------
<a name="construct"></a>
### __construct (string $address, [array $options])
Create an instance by $address and $options.
* `$address` can be on the following protocols: 'mqtt', 'mqtts', 'mqtt://test.mosquitto.org:1883'.
* `$options` is the client connection options. Defaults:
* `keepalive`: `50` seconds, set to `0` to disable
* `client_id`: client id, default `workerman-mqtt-client-{$mt_rand}`
* `protocol_name`: `'MQTT'` or '`MQIsdp`'
* `protocol_level`: `'MQTT'` is `4` and '`MQIsdp`' is `3`
* `clean_session`: `true`, set to false to receive QoS 1 and 2 messages while
offline
* `reconnect_period`: `1` second, interval between two reconnections
* `connect_timeout`: `30` senconds, time to wait before a CONNACK is received
* `username`: the username required by your broker, if any
* `password`: the password required by your broker, if any
* `will`: a message that will sent by the broker automatically when
the client disconnect badly. The format is:
* `topic`: the topic to publish
* `content`: the message to publish
* `qos`: the QoS
* `retain`: the retain flag
* `resubscribe` : if connection is broken and reconnects,
subscribed topics are automatically subscribed again (default `true`)
* `bindto` default '', used to specify the IP address that PHP will use to access the network
* `ssl` default `false`, it can be set `true` or `ssl context` see http://php.net/manual/en/context.ssl.php
* `debug` default `false`, set `true` to show debug info
-------------------------------------------------------
<a name="connect"></a>
### connect()
Connect to broker specified by the given $address and $options in `__construct($address, $options)`.
-------------------------------------------------------
<a name="publish"></a>
### publish(String $topic, String $content, [array $options], [callable $callback])
Publish a message to a topic
* `$topic` is the topic to publish to, `String`
* `$message` is the message to publish, `String`
* `$options` is the options to publish with, including:
* `qos` QoS level, `Number`, default `0`
* `retain` retain flag, `Boolean`, default `false`
* `dup` mark as duplicate flag, `Boolean`, default `false`
* `$callback` - `function (\Exception $exception)`, fired when the QoS handling completes,
or at the next tick if QoS 0. No error occurs then `$exception` will be null.
-------------------------------------------------------
<a name="subscribe"></a>
### subscribe(mixed $topic, [array $options], [callable $callback])
Subscribe to a topic or topics
* `$topic` is a `String` topic or an `Array` which has as keys the topic name and as value
the QoS like `array('test1'=> 0, 'test2'=> 1)` to subscribe.
* `$options` is the options to subscribe with, including:
* `qos` qos subscription level, default 0
* `$callback` - `function (\Exception $exception, array $granted)`
callback fired on suback where:
* `exception` a subscription error or an error that occurs when client is disconnecting
* `granted` is an array of `array('topic' => 'qos', 'topic' => 'qos')` where:
* `topic` is a subscribed to topic
* `qos` is the granted qos level on it
-------------------------------------------------------
<a name="unsubscribe"></a>
### unsubscribe(mixed $topic, [callable $callback])
Unsubscribe from a topic or topics
* `$topic` is a `String` topic or an array of topics to unsubscribe from
* `$callback` - `function (\Exception $e)`, fired on unsuback. No error occurs then `$exception` will be null..
-------------------------------------------------------
<a name="disconnect"></a>
### disconnect()
Send DISCONNECT package to broker and close the client.
-------------------------------------------------------
<a name="close"></a>
### close()
Close the client without DISCONNECT package.
-------------------------------------------------------
<a name="onConnect"></a>
### callback onConnect(Client $mqtt)
Emitted on successful connection (`CONNACK` package received).
-------------------------------------------------------
<a name="onMessage"></a>
### callback onMessage(String $topic, String $content, Client $mqtt)
`function (topic, message, packet) {}`
Emitted when the client receives a publish packet
* `$topic` topic of the received packet
* `$content` payload of the received packet
* `$mqtt` Client instance.
-------------------------------------------------------
<a name="onError"></a>
### callback onError(\Exception $exception)
Emitted when something wrong for example the client cannot connect broker.
-------------------------------------------------------
<a name="onClose"></a>
### callback onClose()
Emitted when connection closed.
-------------------------------------------------------
# License
MIT

View File

@ -1,35 +0,0 @@
{
"name" : "workerman/mqtt",
"type" : "library",
"keywords": [
"event-loop",
"php",
"mqtt",
"workerman",
"mqtt_client",
"mqtt3",
"mqtt5.0"
],
"homepage": "https://www.workerman.net",
"license" : "MIT",
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"support": {
"email": "walkor@workerman.net"
},
"require": {
"workerman/workerman" : ">=3.3.0"
},
"suggest": {
"ext-event": "For better performance. "
},
"autoload": {
"psr-4": {"Workerman\\Mqtt\\": "./src"}
}
}

View File

@ -1 +0,0 @@
1. Exception instead of RuntimeException

View File

@ -1,32 +0,0 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
$worker = new Worker();
$worker->onWorkerStart = function(){
// $mqtt = new Workerman\Mqtt\Client('mqtt://127.0.0.1:1883');
// $mqtt = new Workerman\Mqtt\Client('mqtt://test.mosquitto.org:1884', ["username"=>"rw", "password"=>"readwrite"]);
$mqtt = new Workerman\Mqtt\Client('mqtt://broker.emqx.io:1883', ["username"=>"rw", "password"=>"readwrite"]);
$mqtt->onConnect = function($mqtt) {
$room = 'workerman';
echo "connect mqtt success!\r\n";
$mqtt->subscribe($room, null, function(){
echo "join room success! type something to talk!\r\n";
});
$mqtt->onMessage = function($room, $message){
echo "room[$room]:", $message, "\r\n";
};
// Read message from console and publish it to all.
$console = new TcpConnection(STDIN);
$console->onMessage = function($console, $message) use ($mqtt){
$mqtt->publish('workerman', trim($message));
};
};
$mqtt->connect();
};
Worker::runAll();

View File

@ -1,21 +0,0 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
use Workerman\Worker;
use Workerman\Timer;
$worker = new Worker();
$worker->onWorkerStart = function(){
$mqtt = new Workerman\Mqtt\Client('mqtt://broker.emqx.io:1883', array(
'debug' => true,
"username"=>"rw",
"password"=>"readwrite"
));
$mqtt->onConnect = function($mqtt) {
// publish message every 2 seconds.
Timer::add(2, function() use ($mqtt) {
$mqtt->publish('workerman', 'hello workerman mqtt');
});
};
$mqtt->connect();
};
Worker::runAll();

View File

@ -1,24 +0,0 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
use Workerman\Worker;
use Workerman\Timer;
$worker = new Worker();
$worker->onWorkerStart = function(){
$mqtt = new Workerman\Mqtt\Client('mqtts://test.mosquitto.org:8883', array(
'ssl' => array(
'local_cert' => __DIR__ . '/ssl/mosquitto.org.crt',
'local_pk' => __DIR__ . '/ssl/mosquitto.org.key',
'verify_peer' => false,
),
'debug' => true
));
$mqtt->onConnect = function($mqtt) {
// publish message every 2 seconds.
Timer::add(2, function() use ($mqtt) {
$mqtt->publish('workerman', 'hello workerman mqtt');
});
};
$mqtt->connect();
};
Worker::runAll();

View File

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDPDCCAqWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMCR0Ix
FzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTESMBAGA1UE
CgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVpdHRvLm9y
ZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzAeFw0xODA3MDYwMzMx
NDhaFw0xODEwMDQwMzMxNDhaMIGWMQswCQYDVQQGEwJ6aDEQMA4GA1UECAwHc2lj
aHVhbjEQMA4GA1UEBwwHY2hlbmdkdTESMBAGA1UECgwJd29ya2VybWFuMRIwEAYD
VQQLDAl3b3JrZXJtYW4xFjAUBgNVBAMMDXdvcmtlcm1hbi5uZXQxIzAhBgkqhkiG
9w0BCQEWFHdhbGtvckB3b3JrZXJtYW4ubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAsq86D1x0iQ7eI0zQFsBxX0hXy9BDnzc8V+e6X3Ox7yEDV/YM
XoHYtjoAEMFA0BcMyE59LlyJq+CFi2h9Js4hrPuAy7rAaMqURD/qJB622O/QVAVU
tFcTHG5mOqwogWAG+8zkCS0agnuhQ0goCSzlreATSv359FJrwFQyDXifmjkfTwK7
0CMaAg3iwBGMGfpXEcdFkyRb5/vWPVThUpAuPCTbceZkguXoAnboGIXEC7nSPCC+
ItthWicI1oSiHSiybcps2kzNpTTBvDeEZbnZGAHhGPXILGj7Q/wjTFRBrjhdxDy9
b/oekoJNYRjG08Jx6WED4hcHjDxqRkyCzZ2VjQIDAQABoxowGDAJBgNVHRMEAjAA
MAsGA1UdDwQEAwIF4DANBgkqhkiG9w0BAQsFAAOBgQCQkJLTRDYxR7IatKeA/n8p
YB6CwTJWWmQFvV5GSipDiELCdbwC2f0ues1Jcp6Nms4W1/fMea93wTALi0f4L6y9
/B+PwBKR5XFSvARlKKzNy3rHV4+FqdScK4VHiLQ9i04xkmDa0qWfJ32QIiue/i9G
M6QPAC1AYUOzyQ4OBlFb9w==
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAsq86D1x0iQ7eI0zQFsBxX0hXy9BDnzc8V+e6X3Ox7yEDV/YM
XoHYtjoAEMFA0BcMyE59LlyJq+CFi2h9Js4hrPuAy7rAaMqURD/qJB622O/QVAVU
tFcTHG5mOqwogWAG+8zkCS0agnuhQ0goCSzlreATSv359FJrwFQyDXifmjkfTwK7
0CMaAg3iwBGMGfpXEcdFkyRb5/vWPVThUpAuPCTbceZkguXoAnboGIXEC7nSPCC+
ItthWicI1oSiHSiybcps2kzNpTTBvDeEZbnZGAHhGPXILGj7Q/wjTFRBrjhdxDy9
b/oekoJNYRjG08Jx6WED4hcHjDxqRkyCzZ2VjQIDAQABAoIBADtq+3KaOwJMmF/C
rjuymMCnjNJwmdv7ASz2GMqe7V9oafU5E5nlscu+88Ceks/fqld9ijD9gqZODfpy
r5NU3mvoYkbc/hiarL0ZR+Hk+Mag0HTwJN+nSOqeZ45BK2MCLLBUzMukXPlg5Ro7
h1ytul6cca3xUMoHEl+tX8uM/f3JTLpd/PL9XXWgLYHQkrPRPykZD3dRv52Mf0l8
jVEuBZ6F2jjpYLGLlFgIBxOngtUVrTqJ0dk+8Wd5WGI0iQsNTwU1zXJDFaNhSFsf
tSfA5EYgeK/T9QhjDYeRrIhMgbyl8qpV3BK7Y3mZSiR7JQqW1LfkmAK+Zg//x8wF
RHa+Tt0CgYEA1utBOj4zSecCuu7gTj74+F1Q8hQ9D8x5sYV3JvjHsf1xaTPN5sm2
ejl+/CwpGHGL1jX7GgzHXsddKWdOk9F59G0XUAuQz8Ivexwui8+OdriPEEwbFXT9
8LqJnvWKV0Vdgjzepx+9HGNe7ip/vNK02Ka5BGnLR9boFno9fx/bVe8CgYEA1Nbj
98F8B8Ayh5DvmAslC6Z+GJGgIqkys315Zmkb3fwCIUMjyNV69nDx0tvx1p/G49uu
c9RJSUvpKAwXgtFhroamy0iLGjP+LA+GJ4WSEEMJJ48p7rowe+AXCu9qZrmhV1Bu
0lnIehi3U7o96YzyxuN3ryEC/asii+EMsc2DaEMCgYB10uminaEOnfgwNW+BViK2
Pqp23MORGr+IpCvrkK4iFVkMnNr/8Iv6u/SzGR66iN1p5ZQw9tqCSnf3j+xTO59J
clk6h+yvCb1wF8Yo3fQzgADmpWKfw9DIHev+2owqRhv6n5ZNNyg9HPlZRrFa+oUs
1VJPahSI4PfCAugd/oAHvwKBgQC1b1Y4LqUHimzIqeHEz8NNsikN0p4azfddkNm7
VmCeFCTQrAZGPs2qZU+P+1SmK+AE/5EwRkgPBnoEXIJyTwEhIMHwyq9hr+69KqKm
8DM6T5rvMRiC3A9WmgmqlbszRIzn/LXr9QN0Kbul0T7T6AZRSzkMfmypR4iUi00i
CNo7NwKBgHvEOfErCA0tEkUHIwxkmYEDxSvxh1mqYOewrs27QgvnVtueftq48RrK
C/Sk+SEAvYFrMztCCK0kkbNHBo0Qq6KaOca0ryslXzJZan6hFQXdbjDhcbTTBooR
+2H/xap/A4xciDpDG4NuHOow6CQ+4z6Ckj8tM1ipXggH774THi/t
-----END RSA PRIVATE KEY-----

View File

@ -1,19 +0,0 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker();
$worker->onWorkerStart = function(){
$mqtt = new Workerman\Mqtt\Client('mqtt://broker.emqx.io:1883', array(
'debug' => true,
"username"=>"rw", "password"=>"readwrite"
));
$mqtt->onConnect = function($mqtt) {
$mqtt->subscribe('workerman');
};
$mqtt->onMessage = function($topic, $content){
echo "topic:$topic content:$content\n";
};
$mqtt->connect();
};
Worker::runAll();

View File

@ -1,24 +0,0 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker();
$worker->onWorkerStart = function(){
$mqtt = new Workerman\Mqtt\Client('mqtts://test.mosquitto.org:8883', array(
'ssl' => array(
'local_cert' => __DIR__ . '/ssl/mosquitto.org.crt',
'local_pk' => __DIR__ . '/ssl/mosquitto.org.key',
'verify_peer' => false,
),
'debug' => true,
// "username"=>"rw", "password"=>"readwrite"
));
$mqtt->onConnect = function($mqtt) {
$mqtt->subscribe('workerman');
};
$mqtt->onMessage = function($topic, $content){
echo "topic:$topic content:$content\n";
};
$mqtt->connect();
};
Worker::runAll();

View File

@ -1,41 +0,0 @@
<?php
require __DIR__ . '/../../vendor/autoload.php';
use Workerman\Worker;
use Workerman\Timer;
$worker = new Worker();
$worker->onWorkerStart = function(){
$mqtt = new Workerman\Mqtt\Client('mqtt://broker.emqx.io:1883', array(
'debug' => true,
"username"=>"rw", "password"=>"readwrite",
'properties' =>[
'session_expiry_interval' => 60,
'receive_maximum' => 65535,
'topic_alias_maximum' => 65535,
],
'protocol_level' => 5,
));
$mqtt->onConnect = function(Workerman\Mqtt\Client $mqtt) {
// publish message every 2 seconds.
Timer::add(2, function() use ($mqtt) {
$mqtt->publish('workerman',
'{"time":' . 1223 . '}',
[
'qos' =>0,
'retain' =>0,
'dup' => 0
],
null,
[
'topic_alias' => 1,
'message_expiry_interval' => 12,
'correlation_data' => 'your_correlation_data',
'response_topic' => 'your_response_topic',
]
);
});
};
$mqtt->connect();
};
Worker::runAll();

View File

@ -1,62 +0,0 @@
<?php
require __DIR__ . '/../../vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker();
$worker->onWorkerStart = function(){
$will = [
'topic' => 'workerman-mqtt/user/delete',
'qos' => 1,
'retain' => 0,
'content' => 'byebye',
'properties' => [
'will_delay_interval' => 60,
'message_expiry_interval' => 60,
'content_type' => 'test',
'payload_format_indicator' => true, // false 0 1
],
];
$mqtt = new Workerman\Mqtt\Client('mqtt://broker.emqx.io:1883', array(
'debug' => true,
"username"=>"rw", "password"=>"readwrite",
'properties' =>[
'session_expiry_interval' => 60,
'receive_maximum' => 65535,
'topic_alias_maximum' => 65535,
],
'protocol_level' => 5,
'will' => $will
));
$mqtt->onConnect = function(\Workerman\Mqtt\Client $mqtt) {
// MQTT 5.0
$topics = [
// 主题 => 选项
'workerman-mqtt/user/get' => [
'qos' => 1,
'no_local' => true,
'retain_as_published' => true,
'retain_handling' => 2,
],
'workerman-mqtt/user/update' => [
'qos' => 0,
'no_local' => false,
'retain_as_published' => true,
'retain_handling' => 2,
],
];
$mqtt->subscribe($topics);
$mqtt->subscribe('workerman', [
'qos' => 0,
'no_local' => false,
'retain_as_published' => true,
'retain_handling' => 2,
]);
};
$mqtt->onMessage = function($topic, $content){
echo "topic:$topic content:$content\n";
};
$mqtt->connect();
};
Worker::runAll();

File diff suppressed because it is too large Load Diff

View File

@ -1,116 +0,0 @@
<?php
namespace Workerman\Mqtt\Consts;
/**
* MQTTConst
*
* @package Workerman\Mqtt\Config
*/
class MQTTConst
{
const MQTT_PROTOCOL_LEVEL_3_1 = 3;
const MQTT_PROTOCOL_LEVEL_3_1_1 = 4;
const MQTT_PROTOCOL_LEVEL_5_0 = 5;
const MQISDP_PROTOCOL_NAME = 'MQIsdp';
const MQTT_PROTOCOL_NAME = 'MQTT';
const MQTT_QOS_0 = 0;
const MQTT_QOS_1 = 1;
const MQTT_QOS_2 = 2;
const MQTT_RETAIN_0 = 0;
const MQTT_RETAIN_1 = 1;
const MQTT_RETAIN_2 = 2;
const MQTT_DUP_0 = 0;
const MQTT_DUP_1 = 1;
const MQTT_SESSION_PRESENT_0 = 0;
const MQTT_SESSION_PRESENT_1 = 1;
/**
* CONNECT Packet.
*/
const CMD_CONNECT = 1;
/**
* CONNACK
*/
const CMD_CONNACK = 2;
/**
* PUBLISH
*/
const CMD_PUBLISH = 3;
/**
* PUBACK
*/
const CMD_PUBACK = 4;
/**
* PUBREC
*/
const CMD_PUBREC = 5;
/**
* PUBREL
*/
const CMD_PUBREL = 6;
/**
* PUBCOMP
*/
const CMD_PUBCOMP = 7;
/**
* SUBSCRIBE
*/
const CMD_SUBSCRIBE = 8;
/**
* SUBACK
*/
const CMD_SUBACK = 9;
/**
* UNSUBSCRIBE
*/
const CMD_UNSUBSCRIBE = 10;
/**
* UNSUBACK
*/
const CMD_UNSUBACK = 11;
/**
* PINGREQ
*/
const CMD_PINGREQ = 12;
/**
* PINGRESP
*/
const CMD_PINGRESP = 13;
/**
* DISCONNECT
*/
const CMD_DISCONNECT = 14;
/**
* Authentication exchange
*/
const CMD_AUTH = 15;
}

View File

@ -1,63 +0,0 @@
<?php
namespace Workerman\Mqtt\Consts;
/**
* @see https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901029
*/
class PropertyConst
{
const PAYLOAD_FORMAT_INDICATOR = 0x01;
const MESSAGE_EXPIRY_INTERVAL = 0x02;
const CONTENT_TYPE = 0x03;
const RESPONSE_TOPIC = 0x08;
const CORRELATION_DATA = 0x09;
const SUBSCRIPTION_IDENTIFIER = 0x0B;
const SESSION_EXPIRY_INTERVAL = 0x11;
const ASSIGNED_CLIENT_IDENTIFIER = 0x12;
const SERVER_KEEP_ALIVE = 0x13;
const AUTHENTICATION_METHOD = 0x15;
const AUTHENTICATION_DATA = 0x16;
const REQUEST_PROBLEM_INFORMATION = 0x17;
const WILL_DELAY_INTERVAL = 0x18;
const REQUEST_RESPONSE_INFORMATION = 0x19;
const RESPONSE_INFORMATION = 0x1A;
const SERVER_REFERENCE = 0x1C;
const REASON_STRING = 0x1F;
const RECEIVE_MAXIMUM = 0x21;
const TOPIC_ALIAS_MAXIMUM = 0x22;
const TOPIC_ALIAS = 0x23;
const MAXIMUM_QOS = 0x24;
const RETAIN_AVAILABLE = 0x25;
const USER_PROPERTY = 0x26;
const MAXIMUM_PACKET_SIZE = 0x27;
const WILDCARD_SUBSCRIPTION_AVAILABLE = 0x28;
const SUBSCRIPTION_IDENTIFIER_AVAILABLE = 0x29;
const SHARED_SUBSCRIPTION_AVAILABLE = 0x2A;
}

View File

@ -1,177 +0,0 @@
<?php
namespace Workerman\Mqtt\Consts;
/**
* @see https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901031
*/
class ReasonCodeConst
{
public const SUCCESS = 0x00;
public const NORMAL_DISCONNECTION = 0x00;
public const GRANTED_QOS_0 = 0x00;
public const GRANTED_QOS_1 = 0x01;
public const GRANTED_QOS_2 = 0x02;
public const DISCONNECT_WITH_WILL_MESSAGE = 0x04;
public const NO_MATCHING_SUBSCRIBERS = 0x10;
public const NO_SUBSCRIPTION_EXISTED = 0x11;
public const CONTINUE_AUTHENTICATION = 0x18;
public const RE_AUTHENTICATE = 0x19;
public const UNSPECIFIED_ERROR = 0x80;
public const MALFORMED_PACKET = 0x81;
public const PROTOCOL_ERROR = 0x82;
public const IMPLEMENTATION_SPECIFIC_ERROR = 0x83;
public const UNSUPPORTED_PROTOCOL_VERSION = 0x84;
public const CLIENT_IDENTIFIER_NOT_VALID = 0x85;
public const BAD_USER_NAME_OR_PASSWORD = 0x86;
public const NOT_AUTHORIZED = 0x87;
public const SERVER_UNAVAILABLE = 0x88;
public const SERVER_BUSY = 0x89;
public const BANNED = 0x8A;
public const SERVER_SHUTTING_DOWN = 0x8B;
public const BAD_AUTHENTICATION_METHOD = 0x8C;
public const KEEP_ALIVE_TIMEOUT = 0x8D;
public const SESSION_TAKEN_OVER = 0x8E;
public const TOPIC_FILTER_INVALID = 0x8F;
public const TOPIC_NAME_INVALID = 0x90;
public const PACKET_IDENTIFIER_IN_USE = 0x91;
public const PACKET_IDENTIFIER_NOT_FOUND = 0x92;
public const RECEIVE_MAXIMUM_EXCEEDED = 0x93;
public const TOPIC_ALIAS_INVALID = 0x94;
public const PACKET_TOO_LARGE = 0x95;
public const MESSAGE_RATE_TOO_HIGH = 0x96;
public const QUOTA_EXCEEDED = 0x97;
public const ADMINISTRATIVE_ACTION = 0x98;
public const PAYLOAD_FORMAT_INVALID = 0x99;
public const RETAIN_NOT_SUPPORTED = 0x9A;
public const QOS_NOT_SUPPORTED = 0x9B;
public const USE_ANOTHER_SERVER = 0x9C;
public const SERVER_MOVED = 0x9D;
public const SHARED_SUBSCRIPTIONS_NOT_SUPPORTED = 0x9E;
public const CONNECTION_RATE_EXCEEDED = 0x9F;
public const MAXIMUM_CONNECT_TIME = 0xA0;
public const SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED = 0xA1;
public const WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED = 0xA2;
/**
* @var array
* @see https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901079
*/
protected static $reasonPhrases = [
self::SUCCESS => 'Success',
self::UNSPECIFIED_ERROR => 'Unspecified error',
self::MALFORMED_PACKET => 'Malformed Packet',
self::PROTOCOL_ERROR => 'Protocol Error',
self::IMPLEMENTATION_SPECIFIC_ERROR => 'Implementation specific error',
self::UNSUPPORTED_PROTOCOL_VERSION => 'Unsupported Protocol Version',
self::CLIENT_IDENTIFIER_NOT_VALID => 'Client Identifier not valid',
self::BAD_USER_NAME_OR_PASSWORD => 'Bad User Name or Password',
self::NOT_AUTHORIZED => 'Not authorized',
self::SERVER_UNAVAILABLE => 'Server unavailable',
self::SERVER_BUSY => 'Server busy',
self::BANNED => 'Banned',
self::BAD_AUTHENTICATION_METHOD => 'Bad authentication method',
self::TOPIC_NAME_INVALID => 'Topic Name invalid',
self::PACKET_TOO_LARGE => 'Packet too large',
self::QUOTA_EXCEEDED => 'Quota exceeded',
self::PAYLOAD_FORMAT_INVALID => 'Payload format invalid',
self::RETAIN_NOT_SUPPORTED => 'Retain not supported',
self::QOS_NOT_SUPPORTED => 'QoS not supported',
self::USE_ANOTHER_SERVER => 'Use another server',
self::SERVER_MOVED => 'Server moved',
self::CONNECTION_RATE_EXCEEDED => 'Connection rate exceeded',
self::DISCONNECT_WITH_WILL_MESSAGE => 'Disconnect with Will Message',
self::SERVER_SHUTTING_DOWN => 'Server shutting down',
self::KEEP_ALIVE_TIMEOUT => 'Keep Alive timeout',
self::SESSION_TAKEN_OVER => 'Session taken over',
self::TOPIC_FILTER_INVALID => 'Topic Filter invalid',
self::RECEIVE_MAXIMUM_EXCEEDED => 'Receive Maximum exceeded',
self::TOPIC_ALIAS_INVALID => 'Topic Alias invalid',
self::MESSAGE_RATE_TOO_HIGH => 'Message rate too high',
self::ADMINISTRATIVE_ACTION => 'Administrative action',
self::SHARED_SUBSCRIPTIONS_NOT_SUPPORTED => 'Shared Subscriptions not supported',
self::MAXIMUM_CONNECT_TIME => 'Maximum connect time',
self::SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED => 'Subscription Identifiers not supported',
self::WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED => 'Wildcard Subscriptions not supported',
self::NO_MATCHING_SUBSCRIBERS => 'No matching subscribers',
self::NO_SUBSCRIPTION_EXISTED => 'No subscription existed',
self::CONTINUE_AUTHENTICATION => 'Continue authentication',
self::RE_AUTHENTICATE => 'Re-authenticate',
self::PACKET_IDENTIFIER_IN_USE => 'Packet Identifier in use',
self::PACKET_IDENTIFIER_NOT_FOUND => 'Packet Identifier not found',
];
/** @var array */
protected static $qosReasonPhrases = [
self::GRANTED_QOS_0 => 'Granted QoS 0',
self::GRANTED_QOS_1 => 'Granted QoS 1',
self::GRANTED_QOS_2 => 'Granted QoS 2',
];
public static function getReasonPhrases(bool $isQos = false): array
{
if ($isQos) {
return static::$qosReasonPhrases;
}
return static::$reasonPhrases;
}
public static function getReasonPhrase(int $value, bool $isQos = false): string
{
if ($isQos) {
return static::$qosReasonPhrases[$value] ?? 'QoS not supported';
}
return static::$reasonPhrases[$value] ?? 'Unknown';
}
public static function getReason(int $value): string
{
return static::$reasonPhrases[$value] ?? (static::$qosReasonPhrases[$value] ?? 'QoS not supported');
}
}

View File

@ -1,304 +0,0 @@
<?php
namespace Workerman\Mqtt\Handle;
use Workerman\Mqtt\Consts\MQTTConst;
use Workerman\Mqtt\Consts\ReasonCodeConst;
use Workerman\Mqtt\Handle\Property\UnPackProperty;
/**
* DecodeV5
*
* @package Workerman\Mqtt\Handle
*/
class DecodeV5
{
public static function connect(string $body): array
{
$protocol_name = Decoder::readString($body);
$protocol_level = ord($body[0]);
$clean_session = ord($body[1]) >> 1 & 0x1;
$will_flag = ord($body[1]) >> 2 & 0x1;
$will_qos = ord($body[1]) >> 3 & 0x3;
$will_retain = ord($body[1]) >> 5 & 0x1;
$password_flag = ord($body[1]) >> 6 & 0x1;
$userName_flag = ord($body[1]) >> 7 & 0x1;
$body = substr($body, 2);
$keepAlive = Decoder::shortInt($body);
$properties_total_length = Decoder::byte($body);
if ($properties_total_length) {
$properties = UnPackProperty::connect($properties_total_length, $body);
}
$clientId = Decoder::readString($body);
$will_properties = [];
if ($will_flag) {
$will_properties_total_length = Decoder::byte($body);
if ($will_properties_total_length) {
$will_properties = UnPackProperty::willProperties($will_properties_total_length, $body);
}
$will_topic = Decoder::readString($body);
$will_content = Decoder::readString($body);
}
$userName = $password = '';
if ($userName_flag) {
$userName = Decoder::readString($body);
}
if ($password_flag) {
$password = Decoder::readString($body);
}
$package = [
'cmd' => MQTTConst::CMD_CONNECT,
'protocol_name' => $protocol_name,
'protocol_level' => $protocol_level,
'clean_session' => $clean_session,
'properties' => [],
'will' => [],
'username' => $userName,
'password' => $password,
'keepalive' => $keepAlive,
'client_id' => $clientId,
];
if ($properties_total_length) {
$package['properties'] = $properties;
} else {
unset($package['properties']);
}
// $package['client_id'] = $clientId;
if ($will_flag) {
if ($will_properties_total_length) {
$package['will']['properties'] = $will_properties;
}
$package['will'] += [
'qos' => $will_qos,
'retain' => $will_retain,
'topic' => $will_topic,
'message' => $will_content,
];
} else {
unset($package['will']);
}
return $package;
}
public static function connAck(string $body): array
{
$session_present = ord($body[0]) & 0x01;
$code = ord($body[1]);
$body = substr($body, 2);
$package = [
'cmd' => MQTTConst::CMD_CONNACK,
'session_present' => $session_present,
'code' => $code,
];
$properties_total_length = Decoder::byte($body);
if ($properties_total_length) {
$package['properties'] = UnPackProperty::connAck($properties_total_length, $body);
}
return $package;
}
public static function publish(int $dup, int $qos, int $retain, string $body): array
{
$topic = Decoder::readString($body);
$package = [
'cmd' => MQTTConst::CMD_PUBLISH,
'dup' => $dup,
'qos' => $qos,
'retain' => $retain,
'topic' => $topic,
];
if ($qos) {
$package['message_id'] = Decoder::shortInt($body);
}
$properties_total_length = Decoder::byte($body);
if ($properties_total_length) {
$package['properties'] = UnPackProperty::publish($properties_total_length, $body);
}
$package['content'] = $body;
return $package;
}
public static function subscribe(string $body): array
{
$message_id = Decoder::shortInt($body);
$package = [
'cmd' => MQTTConst::CMD_SUBSCRIBE,
'message_id' => $message_id,
];
$properties_total_length = Decoder::byte($body);
if ($properties_total_length) {
$package['properties'] = UnPackProperty::subscribe($properties_total_length, $body);
}
$topics = [];
while ($body) {
$topic = Decoder::readString($body);
$topics[$topic] = [
'qos' => ord($body[0]) & 0x3,
'no_local' => (bool) (ord($body[0]) >> 2 & 0x1),
'retain_as_published' => (bool) (ord($body[0]) >> 3 & 0x1),
'retain_handling' => ord($body[0]) >> 4,
];
$body = substr($body, 1);
}
$package['topics'] = $topics;
return $package;
}
public static function subAck(string $body): array
{
$message_id = Decoder::shortInt($body);
$package = [
'cmd' => MQTTConst::CMD_SUBACK,
'message_id' => $message_id,
];
$properties_total_length = Decoder::byte($body);
if ($properties_total_length) {
$package['properties'] = UnPackProperty::pubAndSub($properties_total_length, $body);
}
$codes = unpack('C*', $body);
$package['codes'] = array_values($codes);
return $package;
}
public static function unSubscribe(string $body): array
{
$message_id = Decoder::shortInt($body);
$package = [
'cmd' => MQTTConst::CMD_UNSUBSCRIBE,
'message_id' => $message_id,
];
$properties_total_length = Decoder::byte($body);
if ($properties_total_length) {
$package['properties'] = UnPackProperty::unSubscribe($properties_total_length, $body);
}
$topics = [];
while ($body) {
$topic = Decoder::readString($body);
$topics[] = $topic;
}
$package['topics'] = $topics;
return $package;
}
public static function unSubAck(string $body): array
{
$message_id = Decoder::shortInt($body);
$package = [
'cmd' => MQTTConst::CMD_UNSUBACK,
'message_id' => $message_id,
];
$properties_total_length = Decoder::byte($body);
if ($properties_total_length) {
$package['properties'] = UnPackProperty::pubAndSub($properties_total_length, $body);
}
$codes = unpack('C*', $body);
$package['codes'] = array_values($codes);
return $package;
}
public static function disconnect(string $body): array
{
if (isset($body[0])) {
$code = Decoder::byte($body);
} else {
$code = ReasonCodeConst::NORMAL_DISCONNECTION;
}
$package = [
'cmd' => MQTTConst::CMD_DISCONNECT,
'code' => $code,
];
$properties_total_length = 0;
if (isset($body[0])) {
$properties_total_length = Decoder::byte($body);
}
if ($properties_total_length) {
$package['properties'] = UnPackProperty::disconnect($properties_total_length, $body);
}
return $package;
}
public static function getReasonCode(int $cmd, string $body): array
{
$message_id = Decoder::shortInt($body);
if (isset($body[0])) {
$code = Decoder::byte($body);
} else {
$code = ReasonCodeConst::SUCCESS;
}
$package = [
'cmd' => $cmd,
'message_id' => $message_id,
'code' => $code,
];
$properties_total_length = 0;
if (isset($body[0])) {
$properties_total_length = Decoder::byte($body);
}
if ($properties_total_length) {
$package['properties'] = UnPackProperty::pubAndSub($properties_total_length, $body);
}
return $package;
}
public static function auth(string $data): array
{
if (isset($data[0])) {
$code = Decoder::byte($data);
} else {
$code = ReasonCodeConst::SUCCESS;
}
$package = [
'cmd' => MQTTConst::CMD_AUTH,
'code' => $code,
];
$properties_total_length = 0;
if (isset($data[0])) {
$properties_total_length = Decoder::byte($data);
}
if ($properties_total_length) {
$package['properties'] = UnPackProperty::auth($properties_total_length, $data);
}
return $package;
}
}

View File

@ -1,141 +0,0 @@
<?php
namespace Workerman\Mqtt\Handle;
use Workerman\Mqtt\Consts\MQTTConst;
class Decoder
{
/**
* Get cmd.
*
* @param string $buffer
* @return int
*/
public static function getCmd(string $buffer)
{
return ord($buffer[0]) >> 4;
}
/**
* Read string from buffer.
* @param string $buffer
* @return string
*/
public static function readString(string &$buffer)
{
$length = unpack('n', $buffer)[1];
if ($length + 2 > strlen($buffer)) {
echo "buffer:" . bin2hex($buffer) . " length:$length not enough for unpackString\n";
}
$string = substr($buffer, 2, $length);
$buffer = substr($buffer, $length + 2);
return $string;
}
/**
* Read unsigned short int from buffer.
* @param string $buffer
* @return mixed
*/
public static function shortInt(string &$buffer)
{
$tmp = unpack('n', $buffer);
$buffer = substr($buffer, 2);
return $tmp[1];
}
public static function longInt(string &$buffer)
{
$tmp = unpack('N', $buffer);
$buffer = substr($buffer, 4);
return $tmp[1];
}
public static function byte(string &$buffer)
{
$tmp = ord($buffer[0]);
$buffer = substr($buffer, 1);
return $tmp;
}
public static function varInt(string&$remaining, &$len)
{
$body_length = static::getBodyLength($remaining, $head_bytes);
$len = $head_bytes;
$result = $shift = 0;
for ($i = 0; $i < $len; $i++) {
$byte = ord($remaining[$i]);
$result |= ($byte & 0x7F) << $shift++ * 7;
}
$remaining = substr($remaining, $head_bytes, $body_length);
return $result;
}
/**
* Get body length.
*
* @param string $buffer
* @param null|int $head_bytes
* @return int
*/
public static function getBodyLength(string $buffer, &$head_bytes)
{
$head_bytes = $multiplier = 1;
$value = 0;
do {
if (!isset($buffer[$head_bytes])) {
throw new \Exception('Malformed Remaining Length');
// $head_bytes = 0;
// return 0;
}
$digit = ord($buffer[$head_bytes]);
$value += ($digit & 127) * $multiplier;
$multiplier *= 128;
++$head_bytes;
} while (($digit & 128) != 0);
return $value;
}
/**
* Get body.
*
* @param string $buffer
* @return string
*/
public static function getBody(string $buffer)
{
$body_length = static::getBodyLength($buffer, $head_bytes);
return substr($buffer, $head_bytes, $body_length);
}
/**
* Get the MQTT protocol level.
*
* @param string $data
* @return int
*/
public static function getLevel(string $data)
{
$cmd = static::getCmd($data);
if ($cmd !== MQTTConst::CMD_CONNECT) {
throw new \InvalidArgumentException(
sprintf('packet must be of type connect, cmd value equal %s given', $cmd)
);
}
$remaining = static::getBody($data);
$length = unpack('n', $remaining)[1];
return ord($remaining[$length + 2]);
}
}

View File

@ -1,217 +0,0 @@
<?php
namespace Workerman\Mqtt\Handle;
use Workerman\Mqtt\Consts\MQTTConst;
use Workerman\Mqtt\Consts\ReasonCodeConst;
use Workerman\Mqtt\Handle\Property\PackProperty;
/**
* EncodeV5
*
* @package Workerman\Mqtt\Handle
*/
class EncodeV5
{
public static function connect(array $data): string
{
$body = Encoder::packString($data['protocol_name']) . chr($data['protocol_level']);
$connect_flags = 0;
if (!empty($data['clean_session'])) {
$connect_flags |= 1 << 1;
}
if (!empty($data['will'])) {
$connect_flags |= 1 << 2;
// qos > 2 是否 support
$connect_flags |= $data['will']['qos'] << 3;
if (!empty($data['will']['retain'])) {
$connect_flags |= 1 << 5;
}
}
if (!empty($data['password'])) {
$connect_flags |= 1 << 6;
}
if (!empty($data['username'])) {
$connect_flags |= 1 << 7;
}
$body .= chr($connect_flags);
$keepalive = !empty($data['keepalive']) && (int)$data['keepalive'] >= 0 ? (int)$data['keepalive'] : 0;
$body .= Encoder::shortInt($keepalive);
// CONNECT Properties for MQTT5
$body .= PackProperty::connect($data['properties'] ?? []);
$body .= Encoder::packString($data['client_id']);
if (!empty($data['will'])) {
// Will Properties for MQTT5
$body .= PackProperty::willProperties($data['will']['properties'] ?? []);
$body .= Encoder::packString($data['will']['topic']);
$body .= Encoder::packString($data['will']['content']);
}
if (!empty($data['username']) || $data['username'] === '0') {
$body .= Encoder::packString($data['username']);
}
if (!empty($data['password']) || $data['password'] === '0') {
$body .= Encoder::packString($data['password']);
}
$head = Encoder::packHead(MQTTConst::CMD_CONNECT, strlen($body));
return $head . $body;
}
public static function connAck(array $data): string
{
$body = !empty($data['session_present']) ? chr(1) : chr(0);
$code = !empty($data['code']) ? $data['code'] : 0;
$body .= chr($code);
// CONNACK Properties for MQTT5
$body .= PackProperty::connAck($data['properties'] ?? []);
$head = Encoder::packHead(MQTTConst::CMD_CONNACK, strlen($body));
return $head . $body;
}
public static function publish(array $data): string
{
$body = Encoder::packString($data['topic']);
$qos = $data['qos'] ?? 0;
if ($qos) {
$body .= Encoder::shortInt($data['message_id']);
}
$dup = $data['dup'] ?? 0;
$retain = $data['retain'] ?? 0;
// PUBLISH Properties for MQTT5
$body .= PackProperty::publish($data['properties'] ?? []);
$body .= $data['content'];
$head = Encoder::packHead(MQTTConst::CMD_PUBLISH, strlen($body), $dup, $qos, $retain);
return $head . $body;
}
public static function genReasonPhrase(array $data): string
{
$body = Encoder::shortInt($data['message_id']);
$code = !empty($data['code']) ? $data['code'] : ReasonCodeConst::SUCCESS;
$body .= chr($code);
// pubAck, pubRec, pubRel, pubComp Properties
$body .= PackProperty::pubAndSub($data['properties'] ?? []);
if ($data['cmd'] === MQTTConst::CMD_PUBREL) {
$head = Encoder::packHead($data['cmd'], strlen($body), 0, 1);
} else {
$head = Encoder::packHead($data['cmd'], strlen($body));
}
return $head . $body;
}
public static function subscribe(array $data): string
{
$body = Encoder::shortInt($data['message_id']);
// SUBSCRIBE Properties
$body .= PackProperty::subscribe($data['properties'] ?? []);
foreach ($data['topics'] as $topic => $options) {
$body .= Encoder::packString($topic);
$subscribeOptions = 0;
if (isset($options['qos'])) {
$subscribeOptions |= (int)$options['qos'];
}
if (isset($options['no_local'])) {
$subscribeOptions |= (int)$options['no_local'] << 2;
}
if (isset($options['retain_as_published'])) {
$subscribeOptions |= (int)$options['retain_as_published'] << 3;
}
if (isset($options['retain_handling'])) {
$subscribeOptions |= (int)$options['retain_handling'] << 4;
}
$body .= chr($subscribeOptions);
}
$head = Encoder::packHead(MQTTConst::CMD_SUBSCRIBE, strlen($body), 0, 1);
return $head . $body;
}
public static function subAck(array $data): string
{
$codes = $data['codes'];
$body = Encoder::shortInt($data['message_id']);
// SUBACK Properties
$body .= PackProperty::pubAndSub($data['properties'] ?? []);
$body .= call_user_func_array(
'pack',
array_merge(['C*'], $codes)
);
$head = Encoder::packHead(MQTTConst::CMD_SUBACK, strlen($body));
return $head . $body;
}
public static function unSubscribe(array $data): string
{
$body = Encoder::shortInt($data['message_id']);
// UNSUBSCRIBE Properties
$body .= PackProperty::unSubscribe($data['properties'] ?? []);
foreach ($data['topics'] as $topic) {
$body .= Encoder::packString($topic);
}
$head = Encoder::packHead(MQTTConst::CMD_UNSUBSCRIBE, strlen($body), 0, 1);
return $head . $body;
}
public static function unSubAck(array $data): string
{
$body = Encoder::shortInt($data['message_id']);
// UNSUBACK Properties
$body .= PackProperty::pubAndSub($data['properties'] ?? []);
$body .= call_user_func_array(
'pack',
array_merge(['C*'], $data['codes'])
);
$head = Encoder::packHead(MQTTConst::CMD_UNSUBACK, strlen($body));
return $head . $body;
}
public static function disconnect(array $data): string
{
$code = !empty($data['code']) ? $data['code'] : ReasonCodeConst::NORMAL_DISCONNECTION;
$body = chr($code);
// DISCONNECT Properties
$body .= PackProperty::disConnect($data['properties'] ?? []);
$head = Encoder::packHead(MQTTConst::CMD_DISCONNECT, strlen($body));
return $head . $body;
}
public static function auth(array $data): string
{
$code = !empty($data['code']) ? $data['code'] : ReasonCodeConst::SUCCESS;
$body = chr($code);
// AUTH Properties
$body .= PackProperty::auth($data['properties'] ?? []);
$head = Encoder::packHead(MQTTConst::CMD_AUTH, strlen($body));
return $head . $body;
}
}

View File

@ -1,87 +0,0 @@
<?php
namespace Workerman\Mqtt\Handle;
class Encoder
{
/**
* Pack string.
*
* @param string $str
* @return string
*/
public static function packString(string $str): string
{
$len = strlen($str);
return pack('n', $len) . $str;
}
public static function stringPair(string $key, string $value): string
{
return static::packString($key) . static::packString($value);
}
public static function longInt(int $int): string
{
return pack('N', $int);
}
public static function shortInt(int $int): string
{
return pack('n', $int);
}
public static function varInt(int $int): string
{
return static::writeBodyLength($int);
}
/**
* packHead.
*
* @param int $cmd
* @param int $body_length
* @param int $dup
* @param int $qos
* @param int $retain
* @return string
*/
public static function packHead(int $cmd,
int $body_length,
int $dup = 0, int $qos = 0, int $retain = 0): string
{
$cmd = $cmd << 4;
if ($dup) {
$cmd |= 1 << 3;
}
if ($qos) {
$cmd |= $qos << 1;
}
if ($retain) {
$cmd |= 1;
}
return chr($cmd) . static::writeBodyLength($body_length);
}
/**
* Write body length.
*
* @param int $len
* @return string
*/
protected static function writeBodyLength(int$len): string
{
$string = '';
do {
$digit = $len % 128;
$len = $len >> 7;
// if there are more digits to encode, set the top bit of this digit
if ($len > 0) {
$digit = ($digit | 0x80);
}
$string .= chr($digit);
} while ($len > 0);
return $string;
}
}

View File

@ -1,337 +0,0 @@
<?php
namespace Workerman\Mqtt\Handle\Property;
use Workerman\Mqtt\Consts\PropertyConst;
use Workerman\Mqtt\Handle\Encoder;
class PackProperty
{
public static function connect(array $data): string
{
$length = 0;
$tmpBody = '';
$connect = array_flip(PacketMap::$connect);
foreach ($data as $key => $item) {
if (isset($connect[$key])) {
$property = $connect[$key];
$tmpBody .= chr($property);
switch ($property) {
case PropertyConst::SESSION_EXPIRY_INTERVAL:
$length += 5;
$tmpBody .= Encoder::longInt($item);
break;
case PropertyConst::AUTHENTICATION_METHOD:
case PropertyConst::AUTHENTICATION_DATA:
$length += 3;
$length += strlen($item);
$tmpBody .= Encoder::packString($item);
break;
case PropertyConst::REQUEST_PROBLEM_INFORMATION:
case PropertyConst::REQUEST_RESPONSE_INFORMATION:
$length += 2;
$tmpBody .= chr((int) $item);
break;
case PropertyConst::RECEIVE_MAXIMUM:
case PropertyConst::TOPIC_ALIAS_MAXIMUM:
case PropertyConst::MAXIMUM_PACKET_SIZE:
$length += 3;
$tmpBody .= Encoder::shortInt($item);
break;
}
} else {
// Property::USER_PROPERTY
$length += 5;
$length += strlen((string) $key);
$length += strlen((string) $item);
$tmpBody .= chr($connect['user_property']);
$tmpBody .= Encoder::stringPair((string) $key, (string) $item);
}
}
return chr($length) . $tmpBody;
}
public static function willProperties(array $data): string
{
$length = 0;
$tmpBody = '';
$willProperties = array_flip(PacketMap::$willProperties);
foreach ($data as $key => $item) {
if (isset($willProperties[$key])) {
$property = $willProperties[$key];
$tmpBody .= chr($property);
switch ($property) {
case PropertyConst::MESSAGE_EXPIRY_INTERVAL:
case PropertyConst::WILL_DELAY_INTERVAL:
$length += 5;
$tmpBody .= Encoder::longInt($item);
break;
case PropertyConst::CONTENT_TYPE:
case PropertyConst::RESPONSE_TOPIC:
case PropertyConst::CORRELATION_DATA:
$length += 3;
$length += strlen($item);
$tmpBody .= Encoder::packString($item);
break;
case PropertyConst::PAYLOAD_FORMAT_INDICATOR:
$length += 2;
$tmpBody .= chr((int) $item);
break;
}
} else {
// Property::USER_PROPERTY
$length += 5;
$length += strlen((string) $key);
$length += strlen((string) $item);
$tmpBody .= chr($willProperties['user_property']);
$tmpBody .= Encoder::stringPair((string) $key, (string) $item);
}
}
return chr($length) . $tmpBody;
}
public static function connAck(array $data): string
{
$length = 0;
$tmpBody = '';
$connAck = array_flip(PacketMap::$connAck);
foreach ($data as $key => $item) {
if (isset($connAck[$key])) {
$property = $connAck[$key];
$tmpBody .= chr($property);
switch ($property) {
case PropertyConst::SESSION_EXPIRY_INTERVAL:
case PropertyConst::MAXIMUM_PACKET_SIZE:
$length += 5;
$tmpBody .= Encoder::longInt($item);
break;
case PropertyConst::SERVER_KEEP_ALIVE:
case PropertyConst::RECEIVE_MAXIMUM:
case PropertyConst::TOPIC_ALIAS_MAXIMUM:
$length += 3;
$tmpBody .= Encoder::shortInt($item);
break;
case PropertyConst::ASSIGNED_CLIENT_IDENTIFIER:
case PropertyConst::AUTHENTICATION_METHOD:
case PropertyConst::AUTHENTICATION_DATA:
case PropertyConst::RESPONSE_INFORMATION:
case PropertyConst::SERVER_REFERENCE:
case PropertyConst::REASON_STRING:
$length += 3;
$length += strlen($item);
$tmpBody .= Encoder::packString($item);
break;
case PropertyConst::MAXIMUM_QOS:
case PropertyConst::RETAIN_AVAILABLE:
case PropertyConst::WILDCARD_SUBSCRIPTION_AVAILABLE:
case PropertyConst::SUBSCRIPTION_IDENTIFIER_AVAILABLE:
case PropertyConst::SHARED_SUBSCRIPTION_AVAILABLE:
$length += 2;
$tmpBody .= chr((int) $item);
break;
}
} else {
// Property::USER_PROPERTY
$length += 5;
$length += strlen((string) $key);
$length += strlen((string) $item);
$tmpBody .= chr($connAck['user_property']);
$tmpBody .= Encoder::stringPair((string) $key, (string) $item);
}
}
return chr($length) . $tmpBody;
}
public static function publish(array $data): string
{
$length = 0;
$tmpBody = '';
$publish = array_flip(PacketMap::$publish);
foreach ($data as $key => $item) {
if (isset($publish[$key])) {
$property = $publish[$key];
$tmpBody .= chr($property);
switch ($property) {
case PropertyConst::MESSAGE_EXPIRY_INTERVAL:
$length += 5;
$tmpBody .= Encoder::longInt($item);
break;
case PropertyConst::TOPIC_ALIAS:
$length += 3;
$tmpBody .= Encoder::shortInt($item);
break;
case PropertyConst::CONTENT_TYPE:
case PropertyConst::RESPONSE_TOPIC:
case PropertyConst::CORRELATION_DATA:
$length += 3;
$length += strlen($item);
$tmpBody .= Encoder::packString($item);
break;
case PropertyConst::PAYLOAD_FORMAT_INDICATOR:
$length += 2;
$tmpBody .= chr((int) $item);
break;
case PropertyConst::SUBSCRIPTION_IDENTIFIER:
$length += 1;
$value = Encoder::varInt((int) $item);
$length += strlen($value);
$tmpBody .= $value;
break;
}
} else {
// Property::USER_PROPERTY
$length += 5;
$length += strlen((string) $key);
$length += strlen((string) $item);
$tmpBody .= chr($publish['user_property']);
$tmpBody .= Encoder::stringPair((string) $key, (string) $item);
}
}
return chr($length) . $tmpBody;
}
public static function pubAndSub(array $data): string
{
$length = 0;
$tmpBody = '';
$pubAndSub = array_flip(PacketMap::$pubAndSub);
foreach ($data as $key => $item) {
if (isset($pubAndSub[$key])) {
$property = $pubAndSub[$key];
$tmpBody .= chr($property);
switch ($property) {
case PropertyConst::REASON_STRING:
$length += 3;
$length += strlen($item);
$tmpBody .= Encoder::packString($item);
break;
}
} else {
// Property::USER_PROPERTY
$length += 5;
$length += strlen((string) $key);
$length += strlen((string) $item);
$tmpBody .= chr($pubAndSub['user_property']);
$tmpBody .= Encoder::stringPair((string) $key, (string) $item);
}
}
return chr($length) . $tmpBody;
}
public static function subscribe(array $data): string
{
$length = 0;
$tmpBody = '';
$subscribe = array_flip(PacketMap::$subscribe);
foreach ($data as $key => $item) {
if (isset($subscribe[$key])) {
$property = $subscribe[$key];
$tmpBody .= chr($property);
switch ($property) {
case PropertyConst::SUBSCRIPTION_IDENTIFIER:
$length += 1;
$value = Encoder::varInt((int) $item);
$length += strlen($value);
$tmpBody .= $value;
break;
}
} else {
// Property::USER_PROPERTY
$length += 5;
$length += strlen((string) $key);
$length += strlen((string) $item);
$tmpBody .= chr($subscribe['user_property']);
$tmpBody .= Encoder::stringPair((string) $key, (string) $item);
}
}
return chr($length) . $tmpBody;
}
public static function unSubscribe(array $data): string
{
$length = 0;
$tmpBody = '';
$unSubscribe = array_flip(PacketMap::$unSubscribe);
foreach ($data as $key => $item) {
// Property::USER_PROPERTY
$length += 5;
$length += strlen((string) $key);
$length += strlen((string) $item);
$tmpBody .= chr($unSubscribe['user_property']);
$tmpBody .= Encoder::stringPair((string) $key, (string) $item);
}
return chr($length) . $tmpBody;
}
public static function disConnect(array $data): string
{
$length = 0;
$tmpBody = '';
$disConnect = array_flip(PacketMap::$disConnect);
foreach ($data as $key => $item) {
if (isset($disConnect[$key])) {
$property = $disConnect[$key];
$tmpBody .= chr($property);
switch ($property) {
case PropertyConst::SESSION_EXPIRY_INTERVAL:
$length += 5;
$tmpBody .= Encoder::longInt($item);
break;
case PropertyConst::SERVER_REFERENCE:
case PropertyConst::REASON_STRING:
$length += 3;
$length += strlen($item);
$tmpBody .= Encoder::packString($item);
break;
}
} else {
// Property::USER_PROPERTY
$length += 5;
$length += strlen((string) $key);
$length += strlen((string) $item);
$tmpBody .= chr($disConnect['user_property']);
$tmpBody .= Encoder::stringPair((string) $key, (string) $item);
}
}
return chr($length) . $tmpBody;
}
public static function auth(array $data): string
{
$length = 0;
$tmpBody = '';
$auth = array_flip(PacketMap::$auth);
foreach ($data as $key => $item) {
if (isset($auth[$key])) {
$property = $auth[$key];
$tmpBody .= chr($property);
switch ($property) {
case PropertyConst::AUTHENTICATION_METHOD:
case PropertyConst::AUTHENTICATION_DATA:
case PropertyConst::REASON_STRING:
$length += 3;
$length += strlen($item);
$tmpBody .= Encoder::packString($item);
break;
}
} else {
// Property::USER_PROPERTY
$length += 5;
$length += strlen((string) $key);
$length += strlen((string) $item);
$tmpBody .= chr($auth['user_property']);
$tmpBody .= Encoder::stringPair((string) $key, (string) $item);
}
}
return chr($length) . $tmpBody;
}
}

View File

@ -1,124 +0,0 @@
<?php
namespace Workerman\Mqtt\Handle\Property;
use Workerman\Mqtt\Consts\PropertyConst;
/**
* @see https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901029
* @see https://cloud.tencent.com/developer/article/1967534
* MQTT 5.0 协议中携带有效载荷的报文有
* CONNECTPUBLISH、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK
*/
class PacketMap
{
/**
* CONNECT 报文携带 Payload在可变头部新增的属性有
* @var string[]
*/
public static $connect = [
PropertyConst::SESSION_EXPIRY_INTERVAL => 'session_expiry_interval',
PropertyConst::AUTHENTICATION_METHOD => 'authentication_method',
PropertyConst::AUTHENTICATION_DATA => 'authentication_data',
PropertyConst::REQUEST_PROBLEM_INFORMATION => 'request_problem_information',
PropertyConst::REQUEST_RESPONSE_INFORMATION => 'request_response_information',
PropertyConst::RECEIVE_MAXIMUM => 'receive_maximum',
PropertyConst::TOPIC_ALIAS_MAXIMUM => 'topic_alias_maximum',
PropertyConst::USER_PROPERTY => 'user_property',
PropertyConst::MAXIMUM_PACKET_SIZE => 'maximum_packet_size',
];
/**
* CONNACK 在可变头部中包含的属性有
* @var string[]
*/
public static $connAck = [
PropertyConst::SESSION_EXPIRY_INTERVAL => 'session_expiry_interval',
PropertyConst::ASSIGNED_CLIENT_IDENTIFIER => 'assigned_client_identifier',
PropertyConst::SERVER_KEEP_ALIVE => 'server_keep_alive',
PropertyConst::AUTHENTICATION_METHOD => 'authentication_method',
PropertyConst::AUTHENTICATION_DATA => 'authentication_data',
PropertyConst::RESPONSE_INFORMATION => 'response_information',
PropertyConst::SERVER_REFERENCE => 'server_reference',
PropertyConst::REASON_STRING => 'reason_string',
PropertyConst::RECEIVE_MAXIMUM => 'receive_maximum',
PropertyConst::TOPIC_ALIAS_MAXIMUM => 'topic_alias_maximum',
PropertyConst::MAXIMUM_QOS => 'maximum_qos',
PropertyConst::RETAIN_AVAILABLE => 'retain_available',
PropertyConst::USER_PROPERTY => 'user_property',
PropertyConst::MAXIMUM_PACKET_SIZE => 'maximum_packet_size',
PropertyConst::WILDCARD_SUBSCRIPTION_AVAILABLE => 'wildcard_subscription_available',
PropertyConst::SUBSCRIPTION_IDENTIFIER_AVAILABLE => 'subscription_identifier_available',
PropertyConst::SHARED_SUBSCRIPTION_AVAILABLE => 'shared_subscription_available',
];
/**
* PUBLISH 报文携带 Payload在可变头部的属性有
* @var string[]
*/
public static $publish = [
PropertyConst::PAYLOAD_FORMAT_INDICATOR => 'payload_format_indicator',
PropertyConst::MESSAGE_EXPIRY_INTERVAL => 'message_expiry_interval',
PropertyConst::CONTENT_TYPE => 'content_type',
PropertyConst::RESPONSE_TOPIC => 'response_topic',
PropertyConst::CORRELATION_DATA => 'correlation_data',
PropertyConst::SUBSCRIPTION_IDENTIFIER => 'subscription_identifier',
PropertyConst::TOPIC_ALIAS => 'topic_alias',
PropertyConst::USER_PROPERTY => 'user_property',
];
/**
* pubAck, pubRec, pubRel, pubComp, subAck, unSubAck 都具备以下属性
*/
public static $pubAndSub = [
PropertyConst::REASON_STRING => 'reason_string',
PropertyConst::USER_PROPERTY => 'user_property',
];
/**
* SUBSCRIBE 报文携带 Payload属性同样存在可变头部中
* @var string[]
*/
public static $subscribe = [
PropertyConst::SUBSCRIPTION_IDENTIFIER => 'subscription_identifier',
PropertyConst::USER_PROPERTY => 'user_property',
];
/**
* UNSUBSCRIBE 报文携带 Payload仅有两个属性属性长度和用户属性
* @var string[]
*/
public static $unSubscribe = [
PropertyConst::USER_PROPERTY => 'user_property',
];
/**
* DISCONNECT 报文是 MQTT 5.0 新增的报文,它的引入意味着 mqtt broker 拥有了主动断开连接的能力。
* DISCONNECT 报文所具备的属性有
* @var string[]
*/
public static $disConnect = [
PropertyConst::SESSION_EXPIRY_INTERVAL => 'session_expiry_interval',
PropertyConst::SERVER_REFERENCE => 'server_reference',
PropertyConst::REASON_STRING => 'reason_string',
PropertyConst::USER_PROPERTY => 'user_property',
];
public static $auth = [
PropertyConst::AUTHENTICATION_METHOD => 'authentication_method',
PropertyConst::AUTHENTICATION_DATA => 'authentication_data',
PropertyConst::REASON_STRING => 'reason_string',
PropertyConst::USER_PROPERTY => 'user_property',
];
public static $willProperties = [
PropertyConst::PAYLOAD_FORMAT_INDICATOR => 'payload_format_indicator',
PropertyConst::MESSAGE_EXPIRY_INTERVAL => 'message_expiry_interval',
PropertyConst::CONTENT_TYPE => 'content_type',
PropertyConst::RESPONSE_TOPIC => 'response_topic',
PropertyConst::CORRELATION_DATA => 'correlation_data',
PropertyConst::WILL_DELAY_INTERVAL => 'will_delay_interval',
PropertyConst::USER_PROPERTY => 'user_property',
];
}

View File

@ -1,368 +0,0 @@
<?php
namespace Workerman\Mqtt\Handle\Property;
use Workerman\Mqtt\Consts\PropertyConst as Property;
use Workerman\Mqtt\Handle\Decoder;
class UnPackProperty
{
public static function connect(int $length, string &$body): array
{
$properties = [];
do {
$property = ord($body[0]);
if (isset(PacketMap::$connect[$property])) {
$key = PacketMap::$connect[$property];
$body = substr($body, 1);
switch ($property) {
case Property::SESSION_EXPIRY_INTERVAL:
$properties[$key] = Decoder::longInt($body);
$length -= 5;
break;
case Property::AUTHENTICATION_METHOD:
case Property::AUTHENTICATION_DATA:
$properties[$key] = Decoder::readString($body);
$length -= 3;
$length -= strlen($properties[$key]);
break;
case Property::REQUEST_PROBLEM_INFORMATION:
case Property::REQUEST_RESPONSE_INFORMATION:
$properties[$key] = Decoder::byte($body);
$length -= 2;
break;
case Property::RECEIVE_MAXIMUM:
case Property::TOPIC_ALIAS_MAXIMUM:
case Property::MAXIMUM_PACKET_SIZE:
$properties[$key] = Decoder::shortInt($body);
$length -= 3;
break;
case Property::USER_PROPERTY:
$userKey = Decoder::readString($body);
$userValue = Decoder::readString($body);
$properties[$userKey] = $userValue;
$length -= 5;
$length -= strlen($userKey);
$length -= strlen($userValue);
break;
}
} else {
$errType = dechex($property);
throw new \InvalidArgumentException("Property [0x{$errType}] not exist");
}
} while ($length > 0);
return $properties;
}
public static function willProperties(int $length, string &$body): array
{
$properties = [];
do {
$property = ord($body[0]);
if (isset(PacketMap::$willProperties[$property])) {
$key = PacketMap::$willProperties[$property];
$body = substr($body, 1);
switch ($property) {
case Property::MESSAGE_EXPIRY_INTERVAL:
case Property::WILL_DELAY_INTERVAL:
$properties[$key] = Decoder::longInt($body);
$length -= 5;
break;
case Property::CONTENT_TYPE:
case Property::RESPONSE_TOPIC:
case Property::CORRELATION_DATA:
$properties[$key] = Decoder::readString($body);
$length -= 3;
$length -= strlen($properties[$key]);
break;
case Property::PAYLOAD_FORMAT_INDICATOR:
$properties[$key] = Decoder::byte($body);
$length -= 2;
break;
case Property::USER_PROPERTY:
$userKey = Decoder::readString($body);
$userValue = Decoder::readString($body);
$properties[$userKey] = $userValue;
$length -= 5;
$length -= strlen($userKey);
$length -= strlen($userValue);
break;
}
} else {
$errType = dechex($property);
throw new \InvalidArgumentException("Property [0x{$errType}] not exist");
}
} while ($length > 0);
return $properties;
}
public static function connAck(int $length, string &$body): array
{
$properties = [];
do {
$property = ord($body[0]);
if (isset(PacketMap::$connAck[$property])) {
$key = PacketMap::$connAck[$property];
$body = substr($body, 1);
switch ($property) {
case Property::SESSION_EXPIRY_INTERVAL:
case Property::MAXIMUM_PACKET_SIZE:
$properties[$key] = Decoder::longInt($body);
$length -= 5;
break;
case Property::SERVER_KEEP_ALIVE:
case Property::RECEIVE_MAXIMUM:
case Property::TOPIC_ALIAS_MAXIMUM:
$properties[$key] = Decoder::shortInt($body);
$length -= 3;
break;
case Property::ASSIGNED_CLIENT_IDENTIFIER:
case Property::AUTHENTICATION_METHOD:
case Property::AUTHENTICATION_DATA:
case Property::RESPONSE_INFORMATION:
case Property::SERVER_REFERENCE:
case Property::REASON_STRING:
$properties[$key] = Decoder::readString($body);
$length -= 3;
$length -= strlen($properties[$key]);
break;
case Property::MAXIMUM_QOS:
case Property::RETAIN_AVAILABLE:
case Property::WILDCARD_SUBSCRIPTION_AVAILABLE:
case Property::SUBSCRIPTION_IDENTIFIER_AVAILABLE:
case Property::SHARED_SUBSCRIPTION_AVAILABLE:
$properties[$key] = Decoder::byte($body);
$length -= 2;
break;
case Property::USER_PROPERTY:
$userKey = Decoder::readString($body);
$userValue = Decoder::readString($body);
$properties[$userKey] = $userValue;
$length -= 5;
$length -= strlen($userKey);
$length -= strlen($userValue);
break;
}
} else {
$errType = dechex($property);
throw new \InvalidArgumentException("Property [0x{$errType}] not exist");
}
} while ($length > 0);
return $properties;
}
public static function publish(int $length, string &$body): array
{
$properties = [];
do {
$property = ord($body[0]);
if (isset(PacketMap::$publish[$property])) {
$key = PacketMap::$publish[$property];
$body = substr($body, 1);
switch ($property) {
case Property::MESSAGE_EXPIRY_INTERVAL:
$properties[$key] = Decoder::longInt($body);
$length -= 5;
break;
case Property::TOPIC_ALIAS:
$properties[$key] = Decoder::shortInt($body);
$length -= 3;
break;
case Property::CONTENT_TYPE:
case Property::RESPONSE_TOPIC:
case Property::CORRELATION_DATA:
$properties[$key] = Decoder::readString($body);
$length -= 3;
$length -= strlen($properties[$key]);
break;
case Property::PAYLOAD_FORMAT_INDICATOR:
$properties[$key] = Decoder::byte($body);
$length -= 2;
break;
case Property::USER_PROPERTY:
$userKey = Decoder::readString($body);
$userValue = Decoder::readString($body);
$properties[$userKey] = $userValue;
$length -= 5;
$length -= strlen($userKey);
$length -= strlen($userValue);
break;
case Property::SUBSCRIPTION_IDENTIFIER:
$length -= 1;
$properties[$key] = Decoder::varInt($body, $len);
$length -= $len;
break;
}
} else {
$errType = dechex($property);
throw new \InvalidArgumentException("Property [0x{$errType}] not exist");
}
} while ($length > 0);
return $properties;
}
public static function pubAndSub(int $length, string &$body): array
{
$properties = [];
do {
$property = ord($body[0]);
if (isset(PacketMap::$pubAndSub[$property])) {
$key = PacketMap::$pubAndSub[$property];
$body = substr($body, 1);
switch ($property) {
case Property::REASON_STRING:
$properties[$key] = Decoder::readString($body);
$length -= 3;
$length -= strlen($properties[$key]);
break;
case Property::USER_PROPERTY:
$userKey = Decoder::readString($body);
$userValue = Decoder::readString($body);
$properties[$userKey] = $userValue;
$length -= 5;
$length -= strlen($userKey);
$length -= strlen($userValue);
break;
}
} else {
$errType = dechex($property);
throw new \InvalidArgumentException("Property [0x{$errType}] not exist");
}
} while ($length > 0);
return $properties;
}
public static function subscribe(int $length, string &$body): array
{
$properties = [];
do {
$property = ord($body[0]);
if (isset(PacketMap::$subscribe[$property])) {
$key = PacketMap::$subscribe[$property];
$body = substr($body, 1);
switch ($property) {
case Property::USER_PROPERTY:
$userKey = Decoder::readString($body);
$userValue = Decoder::readString($body);
$properties[$userKey] = $userValue;
$length -= 5;
$length -= strlen($userKey);
$length -= strlen($userValue);
break;
case Property::SUBSCRIPTION_IDENTIFIER:
$length -= 1;
$properties[$key] = Decoder::varInt($body, $len);
$length -= $len;
break;
}
} else {
$errType = dechex($property);
throw new \InvalidArgumentException("Property [0x{$errType}] not exist");
}
} while ($length > 0);
return $properties;
}
public static function unSubscribe(int $length, string &$body): array
{
$properties = [];
do {
$property = ord($body[0]);
if (isset(PacketMap::$unSubscribe[$property])) {
$body = substr($body, 1);
switch ($property) {
case Property::USER_PROPERTY:
$userKey = Decoder::readString($body);
$userValue = Decoder::readString($body);
$properties[$userKey] = $userValue;
$length -= 5;
$length -= strlen($userKey);
$length -= strlen($userValue);
break;
}
} else {
$errType = dechex($property);
throw new \InvalidArgumentException("Property [0x{$errType}] not exist");
}
} while ($length > 0);
return $properties;
}
public static function disConnect(int $length, string &$body): array
{
$properties = [];
do {
$property = ord($body[0]);
if (isset(PacketMap::$disConnect[$property])) {
$key = PacketMap::$disConnect[$property];
$body = substr($body, 1);
switch ($property) {
case Property::SESSION_EXPIRY_INTERVAL:
$properties[$key] = Decoder::longInt($body);
$length -= 5;
break;
case Property::SERVER_REFERENCE:
case Property::REASON_STRING:
$properties[$key] = Decoder::readString($body);
$length -= 3;
$length -= strlen($properties[$key]);
break;
case Property::USER_PROPERTY:
$userKey = Decoder::readString($body);
$userValue = Decoder::readString($body);
$properties[$userKey] = $userValue;
$length -= 5;
$length -= strlen($userKey);
$length -= strlen($userValue);
break;
}
} else {
$errType = dechex($property);
throw new \InvalidArgumentException("Property [0x{$errType}] not exist");
}
} while ($length > 0);
return $properties;
}
public static function auth(int $length, string &$body): array
{
$properties = [];
do {
$property = ord($body[0]);
if (isset(PacketMap::$auth[$property])) {
$key = PacketMap::$auth[$property];
$body = substr($body, 1);
switch ($property) {
case Property::AUTHENTICATION_METHOD:
case Property::AUTHENTICATION_DATA:
case Property::REASON_STRING:
$properties[$key] = Decoder::readString($body);
$length -= 3;
$length -= strlen($properties[$key]);
break;
case Property::USER_PROPERTY:
$userKey = Decoder::readString($body);
$userValue = Decoder::readString($body);
$properties[$userKey] = $userValue;
$length -= 5;
$length -= strlen($userKey);
$length -= strlen($userValue);
break;
}
} else {
$errType = dechex($property);
throw new \InvalidArgumentException("Property [0x{$errType}] not exist");
}
} while ($length > 0);
return $properties;
}
}

View File

@ -1,479 +0,0 @@
<?php
/**
* This file is part of workerman.
*
* 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 Workerman\Mqtt\Protocols;
/**
* Mqtt Protocol.
*
* @author walkor<walkor@workerman.net>
*/
class Mqtt
{
/**
* CONNECT Packet.
*/
const CMD_CONNECT = 1;
/**
* CONNACK
*/
const CMD_CONNACK = 2;
/**
* PUBLISH
*/
const CMD_PUBLISH = 3;
/**
* PUBACK
*/
const CMD_PUBACK = 4;
/**
* PUBREC
*/
const CMD_PUBREC = 5;
/**
* PUBREL
*/
const CMD_PUBREL = 6;
/**
* PUBCOMP
*/
const CMD_PUBCOMP = 7;
/**
* SUBSCRIBE
*/
const CMD_SUBSCRIBE = 8;
/**
* SUBACK
*/
const CMD_SUBACK = 9;
/**
* UNSUBSCRIBE
*/
const CMD_UNSUBSCRIBE = 10;
/**
* UNSUBACK
*/
const CMD_UNSUBACK = 11;
/**
* PINGREQ
*/
const CMD_PINGREQ = 12;
/**
* PINGRESP
*/
const CMD_PINGRESP = 13;
/**
* DISCONNECT
*/
const CMD_DISCONNECT = 14;
/**
* Check the integrity of the package.
*
* @param string $buffer
* @return int
*/
public static function input($buffer)
{
$length = strlen($buffer);
$body_length = static::getBodyLength($buffer, $head_bytes);
$total_length = $body_length + $head_bytes;
if ($length < $total_length) {
return 0;
}
return $total_length;
}
/**
* Encode.
*
* @param string $buffer
* @return string
*/
public static function encode($data)
{
$cmd = $data['cmd'];
switch ($data['cmd']) {
// ['cmd'=>1, 'clean_session'=>x, 'will'=>['qos'=>x, 'retain'=>x, 'topic'=>x, 'content'=>x],'username'=>x, 'password'=>x, 'keepalive'=>x, 'protocol_name'=>x, 'protocol_level'=>x, 'client_id' => x]
case static::CMD_CONNECT;
$body = self::packString($data['protocol_name']).chr($data['protocol_level']);
$connect_flags = 0;
if (!empty($data['clean_session'])) {
$connect_flags |= 1 << 1;
}
if (!empty($data['will'])) {
$connect_flags |= 1 << 2;
$connect_flags |= $data['will']['qos'] << 3;
if ($data['will']['retain']) {
$connect_flags |= 1 << 5;
}
}
if (!empty($data['password'])) {
$connect_flags |= 1 << 6;
}
if (!empty($data['username'])) {
$connect_flags |= 1 << 7;
}
$body .= chr($connect_flags);
$keepalive = !empty($data['keepalive']) && (int)$data['keepalive'] >= 0 ? (int)$data['keepalive'] : 0;
$body .= pack('n', $keepalive);
$body .= static::packString($data['client_id']);
if (!empty($data['will'])) {
$body .= static::packString($data['will']['topic']);
$body .= static::packString($data['will']['content']);
}
if(!empty($data['username']) || $data['username'] === '0') {
$body .= static::packString($data['username']);
}
if(!empty($data['password']) || $data['password'] === '0') {
$body .= static::packString($data['password']);
}
$head = self::packHead($cmd, strlen($body));
return $head.$body;
//['cmd'=>2, 'session_present'=>0/1, 'code'=>x]
case static::CMD_CONNACK:
$body = !empty($data['session_present']) ? chr(1) : chr(0);
$code = !empty($data['code']) ? $data['code'] : 0;
$body .= chr($code);
$head = static::packHead($cmd, strlen($body));
return $head.$body;
// ['cmd'=>3, 'message_id'=>x, 'topic'=>x, 'content'=>x, 'qos'=>0/1/2, 'dup'=>0/1, 'retain'=>0/1]
case static::CMD_PUBLISH:
$body = static::packString($data['topic']);
$qos = isset($data['qos']) ? $data['qos'] : 0;
if ($qos) {
$body .= pack('n', $data['message_id']);
}
$body .= $data['content'];
$dup = isset($data['dup']) ? $data['dup'] : 0;
$retain = isset($data['retain']) ? $data['retain'] : 0;
$head = static::packHead($cmd, strlen($body), $dup, $qos, $retain);
return $head.$body;
// ['cmd'=>x, 'message_id'=>x]
case static::CMD_PUBACK:
case static::CMD_PUBREC:
case static::CMD_PUBREL:
case static::CMD_PUBCOMP:
$body = pack('n', $data['message_id']);
if ($cmd === static::CMD_PUBREL) {
$head = static::packHead($cmd, strlen($body), 0, 1);
} else {
$head = static::packHead($cmd, strlen($body));
}
return $head.$body;
// ['cmd'=>8, 'message_id'=>x, 'topics'=>[topic=>qos, ..]]]
case static::CMD_SUBSCRIBE:
$id = $data['message_id'];
$body = pack('n', $id);
foreach($data['topics'] as $topic => $qos){
$body .= self::packString($topic);
$body .= chr($qos);
}
$head = static::packHead($cmd, strlen($body), 0, 1);
return $head.$body;
// ['cmd'=>9, 'message_id'=>x, 'codes'=>[x,x,..]]
case static::CMD_SUBACK:
$codes = $data['codes'];
$body = pack('n', $data['message_id']).call_user_func_array('pack', array_merge(array('C*'), $codes));
$head = static::packHead($cmd, strlen($body));
return $head.$body;
// ['cmd' => 10, 'message_id' => $message_id, 'topics' => $topics];
case static::CMD_UNSUBSCRIBE:
$body = pack('n', $data['message_id']);
foreach ($data['topics'] as $topic) {
$body .= static::packString($topic);
}
$head = static::packHead($cmd, strlen($body), 0, 1);
return $head . $body;
// ['cmd'=>11, 'message_id'=>x]
case static::CMD_UNSUBACK:
$body = pack('n', $data['message_id']);
$head = static::packHead($cmd, strlen($body));
return $head.$body;
// ['cmd'=>x]
case static::CMD_PINGREQ;
case static::CMD_PINGRESP:
case static::CMD_DISCONNECT:
return static::packHead($cmd, 0);
}
}
/**
* Decode.
*
* @param string $buffer
* @return string
*/
public static function decode($buffer)
{
$cmd = static::getCmd($buffer);
$body = static::getBody($buffer);
switch ($cmd) {
case static::CMD_CONNECT:
$protocol_name = static::readString($body);
$protocol_level = ord($body[0]);
$clean_session = ord($body[1]) >> 1 & 0x1;
$will_flag = ord($body[1]) >> 2 & 0x1;
$will_qos = ord($body[1]) >> 3 & 0x3;
$will_retain = ord($body[1]) >> 5 & 0x1;
$password_flag = ord($body[1]) >> 6 & 0x1;
$username_flag = ord($body[1]) >> 7 & 0x1;
$body = substr($body, 2);
$tmp = unpack('n', $body, $body);
$keepalive = $tmp[1];
$body = substr($body, 2);
$client_id = static::readString($body);
if ($will_flag) {
$will_topic = static::readString($body);
$will_content = static::readString($body);
}
$username = $password = '';
if ($username_flag) {
$username = static::readString($body);
}
if ($password_flag) {
$password = static::readString($body);
}
// ['cmd'=>1, 'clean_session'=>x, 'will'=>['qos'=>x, 'retain'=>x, 'topic'=>x, 'content'=>x],'username'=>x, 'password'=>x, 'keepalive'=>x, 'protocol_name'=>x, 'protocol_level'=>x, 'client_id' => x]
$package = array(
'cmd' => $cmd,
'protocol_name' => $protocol_name,
'protocol_level' => $protocol_level,
'clean_session' => $clean_session,
'will' => array(),
'username' => $username,
'password' => $password,
'keepalive' => $keepalive,
'client_id' => $client_id,
);
if ($will_flag) {
$package['will'] = array(
'qos' => $will_qos,
'retain' => $will_retain,
'topic' => $will_topic,
'content' => $will_content
);
} else {
unset($package['will']);
}
return $package;
case static::CMD_CONNACK:
$session_present = ord($body[0]) & 0x01;
$code = ord($body[1]);
return array('cmd' => $cmd, 'session_present' => $session_present, 'code' => $code);
case static::CMD_PUBLISH:
$dup = ord($buffer[0]) >> 3 & 0x1;
$qos = ord($buffer[0]) >> 1 & 0x3;
$retain = ord($buffer[0]) & 0x1;
$topic = static::readString($body);
if ($qos) {
$message_id = static::readShortInt($body);
}
$package = array('cmd' => $cmd, 'topic' => $topic, 'content' => $body, 'dup' => $dup, 'qos' => $qos, 'retain' => $retain);
if ($qos) {
$package['message_id'] = $message_id;
}
return $package;
case static::CMD_PUBACK:
case static::CMD_PUBREC:
case static::CMD_PUBREL:
case static::CMD_PUBCOMP:
$message_id = static::readShortInt($body);
return array('cmd' => $cmd, 'message_id' => $message_id);
case static::CMD_SUBSCRIBE:
$message_id = static::readShortInt($body);
$topics = array();
while ($body) {
$topic = static::readString($body);
$qos = ord($body[0]);
$topics[$topic] = $qos;
$body = substr($body, 1);
}
return array('cmd' => $cmd, 'message_id' => $message_id, 'topics' => $topics);
case static::CMD_SUBACK:
$message_id = static::readShortInt($body);
$tmp = unpack('C*', $body);
$codes = array_values($tmp);
return array('cmd' => $cmd, 'message_id'=> $message_id, 'codes' => $codes);
case static::CMD_UNSUBSCRIBE:
$message_id = static::readShortInt($body);
$topics = array();
while ($body) {
$topic = static::readString($body);
$topics[] = $topic;
}
return array('cmd' => $cmd, 'message_id' => $message_id, 'topics' => $topics);
case static::CMD_UNSUBACK:
$message_id = static::readShortInt($body);
return array('cmd' => $cmd, 'message_id' => $message_id);
case static::CMD_PINGREQ:
case static::CMD_PINGRESP:
case static::CMD_DISCONNECT:
return array('cmd' => $cmd);
}
return $buffer;
}
/**
* Pack string.
*
* @param $str
* @return string
*/
public static function packString($str){
$len = strlen($str);
return pack('n', $len).$str;
}
/**
* Write body length.
*
* @param $len
* @return string
*/
protected static function writeBodyLength($len)
{
$string = "";
do{
$digit = $len % 128;
$len = $len >> 7;
// if there are more digits to encode, set the top bit of this digit
if ( $len > 0 )
$digit = ($digit | 0x80);
$string .= chr($digit);
}while ( $len > 0 );
return $string;
}
/**
* Get cmd.
*
* @param $buffer
* @return int
*/
public static function getCmd($buffer)
{
return ord($buffer[0]) >> 4;
}
/**
* Get body length.
*
* @param $buffer
* @param $head_bytes
* @return int
*/
public static function getBodyLength($buffer, &$head_bytes){
$head_bytes = $multiplier = 1;
$value = 0;
do{
if (!isset($buffer[$head_bytes])) {
$head_bytes = 0;
return 0;
}
$digit = ord($buffer[$head_bytes]);
$value += ($digit & 127) * $multiplier;
$multiplier *= 128;
$head_bytes++;
}while (($digit & 128) != 0);
return $value;
}
/**
* Get body.
*
* @param $buffer
* @return string
*/
public static function getBody($buffer)
{
$body_length = static::getBodyLength($buffer, $head_bytes);
$buffer = substr($buffer, $head_bytes, $body_length);
return $buffer;
}
/**
* Read string from buffer.
* @param $buffer
* @return string
*/
public static function readString(&$buffer) {
$tmp = unpack('n', $buffer);
$length = $tmp[1];
if ($length + 2 > strlen($buffer)) {
echo "buffer:".bin2hex($buffer)." lenth:$length not enough for unpackString\n";
}
$string = substr($buffer, 2, $length);
$buffer = substr($buffer, $length + 2);
return $string;
}
/**
* Read unsigned short int from buffer.
* @param $buffer
* @return mixed
*/
public static function readShortInt(&$buffer) {
$tmp = unpack('n', $buffer);
$buffer = substr($buffer, 2);
return $tmp[1];
}
/**
* packHead.
* @param $cmd
* @param $body_length
* @param int $dup
* @param int $qos
* @param int $retain
* @return string
*/
public static function packHead($cmd, $body_length, $dup = 0, $qos = 0, $retain = 0)
{
$cmd = $cmd << 4;
if ($dup) {
$cmd |= 1 << 3;
}
if ($qos) {
$cmd |= $qos << 1;
}
if ($retain) {
$cmd |= 1;
}
return chr($cmd).static::writeBodyLength($body_length);
}
}

View File

@ -1,179 +0,0 @@
<?php
/**
* This file is part of workerman.
*
* 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 Workerman\Mqtt\Protocols;
use Workerman\Mqtt\Consts\MQTTConst;
use Workerman\Mqtt\Handle\Decoder;
use Workerman\Mqtt\Handle\DecodeV5;
use Workerman\Mqtt\Handle\Encoder;
use Workerman\Mqtt\Handle\EncodeV5;
/**
* Mqtt5 Protocol.
*
* @author maoxps
*/
class Mqtt5
{
/**
* Check the integrity of the package.
*
* 如果可以在$buffer中得到请求包的长度则返回整个包的长度
* 否则返回0表示需要更多的数据才能得到当前请求包的长度
* 如果返回false或者负数则代表错误的请求则连接会断开
* @param string $buffer
* @return int
*/
public static function input(string $buffer)
{
$length = strlen($buffer);
$body_length = Decoder::getBodyLength($buffer, $head_bytes);
$total_length = $body_length + $head_bytes;
if ($length < $total_length) {
return 0;
}
return $total_length;
}
/**
* 用于请求打包
*
* 当需要向客户端发送数据即调用$connection->send($data);
* 会自动把$data用encode打包一次变成符合协议的数据格式然后再发送给客户端
* 也就是说发送给客户端的数据会自动encode打包无需业务代码中手动调用
* @param array $data
* @return string
*/
public static function encode(array $data)
{
$cmd = $data['cmd'];
switch ($cmd) {
// ['cmd'=>1, 'clean_session'=>x, 'will'=>['qos'=>x, 'retain'=>x, 'topic'=>x, 'content'=>x],'username'=>x, 'password'=>x, 'keepalive'=>x, 'protocol_name'=>x, 'protocol_level'=>x, 'client_id' => x]
case MQTTConst::CMD_CONNECT:
$package = EncodeV5::connect($data);
break;
//['cmd'=>2, 'session_present'=>0/1, 'code'=>x]
case MQTTConst::CMD_CONNACK:
$package = EncodeV5::connAck($data);
break;
// ['cmd'=>3, 'message_id'=>x, 'topic'=>x, 'content'=>x, 'qos'=>0/1/2, 'dup'=>0/1, 'retain'=>0/1]
case MQTTConst::CMD_PUBLISH:
$package = EncodeV5::publish($data);
// var_dump((new Debug($package))->hexDumpAscii());
break;
// ['cmd'=>x, 'message_id'=>x]
case MQTTConst::CMD_PUBACK:
case MQTTConst::CMD_PUBREC:
case MQTTConst::CMD_PUBREL:
case MQTTConst::CMD_PUBCOMP:
$package = EncodeV5::genReasonPhrase($data);
break;
// ['cmd'=>8, 'message_id'=>x, 'topics'=>[topic=>qos, ..]]]
case MQTTConst::CMD_SUBSCRIBE:
$package = EncodeV5::subscribe($data);
break;
// ['cmd'=>9, 'message_id'=>x, 'codes'=>[x,x,..]]
case MQTTConst::CMD_SUBACK:
$package = EncodeV5::subAck($data);
break;
// ['cmd' => 10, 'message_id' => $message_id, 'topics' => $topics, 'properties'=>[]];
case MQTTConst::CMD_UNSUBSCRIBE:
$package = EncodeV5::unSubscribe($data);
break;
// ['cmd'=>11, 'message_id'=>x, 'properties'= []]
case MQTTConst::CMD_UNSUBACK:
$package = EncodeV5::unSubAck($data);
break;
// ['cmd'=>x]
case MQTTConst::CMD_PINGREQ;
case MQTTConst::CMD_PINGRESP:
$package = Encoder::packHead($cmd, 0);
break;
case MQTTConst::CMD_DISCONNECT:
$package = EncodeV5::disconnect($data);
break;
case MQTTConst::CMD_AUTH:
$package = EncodeV5::auth($data);
break;
default:
throw new \InvalidArgumentException('MQTT Type not exist');
}
return $package;
}
/**
* 用于请求解包
*
* input返回值大于0并且WorkerMan收到了足够的数据则自动调用decode
* 然后触发onMessage回调并将decode解码后的数据传递给onMessage回调的第二个参数
* 也就是说当收到完整的客户端请求时会自动调用decode解码无需业务代码中手动调用
* @param string $buffer
* @return array
*/
public static function decode(string $buffer)
{
// var_dump((new Debug($buffer))->hexDumpAscii());
$cmd = Decoder::getCmd($buffer);
$body = Decoder::getBody($buffer);
switch ($cmd) {
// ['cmd'=>1, 'clean_session'=>x, 'will'=>['qos'=>x, 'retain'=>x, 'topic'=>x, 'content'=>x],'username'=>x, 'password'=>x, 'keepalive'=>x, 'protocol_name'=>x, 'protocol_level'=>x, 'client_id' => x]
case MQTTConst::CMD_CONNECT:
$package = DecodeV5::connect($body);
break;
case MQTTConst::CMD_CONNACK:
$package = DecodeV5::connAck($body);
break;
case MQTTConst::CMD_PUBLISH:
$dup = ord($buffer[0]) >> 3 & 0x1;
$qos = ord($buffer[0]) >> 1 & 0x3;
$retain = ord($buffer[0]) & 0x1;
$package = DecodeV5::publish($dup, $qos, $retain, $body);
break;
case MQTTConst::CMD_PUBACK:
case MQTTConst::CMD_PUBREC:
case MQTTConst::CMD_PUBREL:
case MQTTConst::CMD_PUBCOMP:
$package = DecodeV5::getReasonCode($cmd, $body);
break;
case MQTTConst::CMD_PINGREQ:
case MQTTConst::CMD_PINGRESP:
$package = ['cmd' => $cmd];
break;
case MQTTConst::CMD_DISCONNECT:
$package = DecodeV5::disconnect($body);
break;
case MQTTConst::CMD_SUBSCRIBE:
$package = DecodeV5::subscribe($body);
break;
case MQTTConst::CMD_SUBACK:
$package = DecodeV5::subAck($body);
break;
case MQTTConst::CMD_UNSUBSCRIBE:
$package = DecodeV5::unSubscribe($body);
break;
case MQTTConst::CMD_UNSUBACK:
$package = DecodeV5::unSubAck($body);
break;
case MQTTConst::CMD_AUTH:
$package = DecodeV5::auth($body);
break;
default:
$package = [];
}
return $package;
}
}

View File

@ -1,33 +0,0 @@
<?php
namespace Workerman\Mqtt\Tools;
/**
* @method static string hexDump(string $encode) // 以16进制显示
* @method static string hexDumpAscii(string $encode) // 以16进制和相应的ASCII字符显示
* @method static string printableText(string $encode) // 可打印字符
* @method static string hexStream(string $encode) // 16进制流
* @method static string ascii(string $encode) // 以ASCII字符显示
*/
abstract class Common
{
public static function printf(string $data)
{
echo "\033[36m";
for ($i = 0, $iMax = strlen($data); $i < $iMax; $i++) {
$ascii = ord($data[$i]);
if ($ascii > 31) {
$chr = $data[$i];
} else {
$chr = ' ';
}
printf("%4d: %08b : 0x%02x : %d : %s\n", $i, $ascii, $ascii, $ascii, $chr);
}
echo "\033[0m";
}
public static function __callStatic($method, $arguments)
{
return (new Debug())->setEncode(...$arguments)->{$method}();
}
}

View File

@ -1,123 +0,0 @@
<?php
namespace Workerman\Mqtt\Tools;
class Debug
{
private $encode;
public function __construct(string $encode = '')
{
$this->encode = $encode;
}
public function setEncode(string $encode): self
{
$this->encode = $encode;
return $this;
}
public function getEncode(): string
{
return $this->encode;
}
public function hexDump(): string
{
return $this->toHexDump($this->getEncode());
}
public function hexDumpAscii(): string
{
return $this->toHexDump($this->getEncode(), true);
}
public function ascii(): string
{
return $this->toAscii($this->getEncode());
}
public function printableText(): string
{
return $this->getEncode();
}
public function hexStream(): string
{
return bin2hex($this->getEncode());
}
private function toHexDump(string $contents, bool $hasAscii = false): string
{
$address = $column = 0;
$result = $hexDump = $asciiDump = '';
$sprintf = '%08x %-48s';
if ($hasAscii) {
$sprintf = '%08x %-48s %s';
}
foreach (str_split($contents) as $c) {
$hexDump = $hexDump . sprintf('%02x ', ord($c));
if ($hasAscii) {
if (ord($c) > 31 && ord($c) < 128) {
$asciiDump .= $c;
} else {
$asciiDump .= '.';
}
}
$column++;
if (($column % 16) == 0) {
$line = sprintf($sprintf, $address, $hexDump, $asciiDump);
$result .= $line . PHP_EOL;
$asciiDump = '';
$hexDump = '';
$column = 0;
$address += 16;
}
}
if ($column > 0) {
$line = sprintf($sprintf, $address, $hexDump, $asciiDump);
$result .= $line;
}
return $result;
}
private function toAscii(string $contents): string
{
$address = $column = 0;
$result = $asciiDump = '';
$sprintf = '%08x %s';
foreach (str_split($contents) as $c) {
if (ord($c) > 31 && ord($c) < 128) {
$asciiDump .= $c;
} else {
$asciiDump .= '.';
}
$column++;
if (($column % 16) == 0) {
$line = sprintf($sprintf, $address, $asciiDump);
$result .= $line . PHP_EOL;
$asciiDump = '';
$column = 0;
$address += 16;
}
}
if ($column > 0) {
$line = sprintf($sprintf, $address, $asciiDump);
$result .= $line;
}
return $result;
}
}