shaoguo 7 meses atrás
commit
afa2ed3772
100 arquivos alterados com 14837 adições e 0 exclusões
  1. 5 0
      .gitignore
  2. 8 0
      .idea/.gitignore
  3. 11 0
      .idea/im-instant-chat.iml
  4. 8 0
      .idea/modules.xml
  5. 20 0
      .idea/php.xml
  6. 6 0
      .idea/vcs.xml
  7. 42 0
      .travis.yml
  8. 32 0
      LICENSE.txt
  9. 144 0
      README.en.md
  10. 153 0
      README.md
  11. 1 0
      app/.htaccess
  12. 22 0
      app/AppService.php
  13. 141 0
      app/BaseController.php
  14. 103 0
      app/BaseModel.php
  15. 58 0
      app/ExceptionHandle.php
  16. 8 0
      app/Request.php
  17. 988 0
      app/common.php
  18. 105 0
      app/common/controller/Api.php
  19. 354 0
      app/common/controller/Pub.php
  20. 234 0
      app/common/controller/Upload.php
  21. 13 0
      app/common/listener/UserRegister.php
  22. 35 0
      app/common/middleware/ApiAuth.php
  23. 45 0
      app/common/middleware/CheckAuth.php
  24. 38 0
      app/common/middleware/ManageAuth.php
  25. 67 0
      app/common/task/ClearMessage.php
  26. 52 0
      app/common/task/SetAtRead.php
  27. 92 0
      app/enterprise/controller/Files.php
  28. 171 0
      app/enterprise/controller/Friend.php
  29. 408 0
      app/enterprise/controller/Group.php
  30. 747 0
      app/enterprise/controller/Im.php
  31. 16 0
      app/enterprise/listener/GroupChange.php
  32. 4 0
      app/enterprise/middleware.php
  33. 14 0
      app/enterprise/model/File.php
  34. 28 0
      app/enterprise/model/Friend.php
  35. 60 0
      app/enterprise/model/Group.php
  36. 35 0
      app/enterprise/model/GroupUser.php
  37. 166 0
      app/enterprise/model/Message.php
  38. 393 0
      app/enterprise/model/User.php
  39. 24 0
      app/enterprise/validate/User.php
  40. 19 0
      app/event.php
  41. 173 0
      app/index/controller/Index.php
  42. 554 0
      app/index/controller/Install.php
  43. 17 0
      app/index/route/app.php
  44. 102 0
      app/manage/controller/Config.php
  45. 166 0
      app/manage/controller/Group.php
  46. 189 0
      app/manage/controller/Task.php
  47. 192 0
      app/manage/controller/User.php
  48. 5 0
      app/manage/middleware.php
  49. 45 0
      app/manage/model/Config.php
  50. 19 0
      app/middleware.php
  51. 9 0
      app/provider.php
  52. 9 0
      app/service.php
  53. 105 0
      app/worker/Application.php
  54. 142 0
      app/worker/Events.php
  55. 201 0
      app/worker/command/GatewayWorker.php
  56. 32 0
      app/worker/start_businessworker.php
  57. 42 0
      app/worker/start_gateway.php
  58. 25 0
      app/worker/start_register.php
  59. 67 0
      composer.json
  60. 3548 0
      composer.lock
  61. 49 0
      config/app.php
  62. 38 0
      config/cache.php
  63. 19 0
      config/captcha.php
  64. 14 0
      config/console.php
  65. 18 0
      config/cookie.php
  66. 8 0
      config/cron.php
  67. 62 0
      config/database.php
  68. 42 0
      config/filesystem.php
  69. 33 0
      config/gateway.php
  70. 16 0
      config/hashids.php
  71. 21 0
      config/jwt.php
  72. 25 0
      config/lang.php
  73. 45 0
      config/log.php
  74. 12 0
      config/middleware.php
  75. 39 0
      config/queue.php
  76. 45 0
      config/route.php
  77. 19 0
      config/session.php
  78. 159 0
      config/sms.php
  79. 10 0
      config/trace.php
  80. 45 0
      config/version.php
  81. 28 0
      config/view.php
  82. 97 0
      example.env
  83. 146 0
      extend/Agent.php
  84. 57 0
      extend/Hashids/HashGenerator.php
  85. 374 0
      extend/Hashids/Hashids.php
  86. 109 0
      extend/Ip.php
  87. 94 0
      extend/easyTask/Check.php
  88. 129 0
      extend/easyTask/Command.php
  89. 36 0
      extend/easyTask/Env.php
  90. 113 0
      extend/easyTask/Error.php
  91. 33 0
      extend/easyTask/Exception/ErrorException.php
  92. 503 0
      extend/easyTask/Helper.php
  93. 52 0
      extend/easyTask/Lock.php
  94. 307 0
      extend/easyTask/Process/Linux.php
  95. 252 0
      extend/easyTask/Process/Process.php
  96. 459 0
      extend/easyTask/Process/Win.php
  97. 87 0
      extend/easyTask/Queue.php
  98. 292 0
      extend/easyTask/Table.php
  99. 334 0
      extend/easyTask/Task.php
  100. 104 0
      extend/easyTask/Terminal.php

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+.env
+public/nginx.htaccess
+config/install.lock
+public/.htaccess
+vender/

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 11 - 0
.idea/im-instant-chat.iml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/app" isTestSource="false" packagePrefix="app\" />
+      <sourceFolder url="file://$MODULE_DIR$/extend" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/im-instant-chat.iml" filepath="$PROJECT_DIR$/.idea/im-instant-chat.iml" />
+    </modules>
+  </component>
+</project>

+ 20 - 0
.idea/php.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="MessDetectorOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PHPCSFixerOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PHPCodeSnifferOptionsConfiguration">
+    <option name="highlightLevel" value="WARNING" />
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PhpProjectSharedConfiguration" php_language_level="7.1" />
+  <component name="PhpStanOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PsalmOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 42 - 0
.travis.yml

@@ -0,0 +1,42 @@
+sudo: false
+
+language: php
+
+branches:
+  only:
+    - stable
+
+cache:
+  directories:
+    - $HOME/.composer/cache
+
+before_install:
+  - composer self-update
+
+install:
+  - composer install --no-dev --no-interaction --ignore-platform-reqs
+  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
+  - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
+  - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
+  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
+
+script:
+  - php think unit
+
+deploy:
+  provider: releases
+  api_key:
+    secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
+  file:
+    - ThinkPHP_Core.zip
+    - ThinkPHP_Full.zip
+  skip_cleanup: true
+  on:
+    tags: true

+ 32 - 0
LICENSE.txt

@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
+All rights reserved。
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+Apache Licence是著名的非盈利开源组织Apache采用的协议。
+该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
+允许代码修改,再作为开源或商业软件发布。需要满足
+的条件: 
+1. 需要给代码的用户一份Apache Licence ;
+2. 如果你修改了代码,需要在被修改的文件中说明;
+3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
+带有原来代码中的协议,商标,专利声明和其他原来作者规
+定需要包含的说明;
+4. 如果再发布的产品中包含一个Notice文件,则在Notice文
+件中需要带有本协议内容。你可以在Notice中增加自己的
+许可,但不可以表现为对Apache Licence构成更改。 
+具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.

+ 144 - 0
README.en.md

@@ -0,0 +1,144 @@
+# Instant Messaging
+
+### Introduce
+Raingad-IM is an open source instant communication demo, which needs to be used together with the front and back ends. It is mainly used for learning and communication, and provides you with the development ideas of instant communication. Many functions need to be developed by itself, and the original intention of development is to quickly establish the internal communication system, Intranet communication and community communication.
+
+|  type | url |
+| --------- | ---- |
+| Front-end source code    | https://gitee.com/raingad/im-chat-front |
+| Back-end source code | https://gitee.com/raingad/im-instant-chat |
+| Web Demo | http://im.raingad.com/index.html |
+| H5 Demo | http://im.raingad.com/h5 |
+| Android Demo | https://emoji.raingad.com/file/raingad.apk |
+
+
+Dmeo account:13800000002  password:123456
+
+The ending number can be modified to 2、3、4......18、19、20 
+
+Dmeo account:13800000020  password:123456 
+
+### Supported features
+
+-Supports single chat and group chat, and supports sending emoticons, images, voice, video, and file messages
+-Single chat supports displaying the status of messages that have been read but not read, and displaying online status
+-Group chat creation, deletion, group member management, group announcements, group bans, etc
+-Support for top contacts and message privacy;
+-Support for setting new message sound reminders and browser notifications
+-Support administrator to recall group member messages
+-Support group members cannot add friends to each other
+-Supports one-on-one audio and video calls (connected to both web and mobile devices, not supported by mini programs)
+-Supports online preview of files, images, and most media files
+-Support for mobile devices (H5, APP, and mini programs, some functions are not compatible)
+-New support for enterprise mode and community mode, with community mode supporting registration and adding friends functions
+-The app supports online and offline push of single chat messages (requires self application for unipush service)
+-Support simple backend management, including user management, group management, system settings, etc
+
+
+### Software architecture
+
+Back-end technology stack:`thinkphp6+workerman+redis`
+
+Front-end technology stack:`vue2+Lemon-IMUI+element-UI`
+
+
+### Installation
+> The installation program needs to have some experience in PHP and server operation and maintenance, if not, please join the communication group to contact the author, the author provides paid deployment services!
+
+#### Preparatory work
+You need to install the running environment first. The BAOTA server is recommended. The LNMP architecture is recommended. The following software needs to be installed:
+
+|  environment | version | remark | Recommended |
+| --------- | ---- | ---- | ---|
+| linux    | >= 7.0 |  The following versions were not tested   | 7.9 |
+| nginx    | >= 1.17 |     | latest |
+| php | >= 7.1 |  incompatible php8    | 7.3 |
+| mysql    | >= 5.7 | Must be 5.7 and above     | 5.7 |
+| redis    | >= 5.0 |     | 7.0 |
+
+**重要操作**
+
+1. PHP needs to install an extension : `redis` `fileinfo`
+
+2. PHP needs to undisable the function : `shell_exec` `proc_open` `pcntl_exec` `pcntl_alarm` `pcntl_fork` `pcntl_waitpid` `pcntl_wait` `pcntl_signal` `pcntl_signal_dispatch` `putenv`
+
+#### Source code download
+- Clone to local : 
+``` 
+git clone https://gitee.com/raingad/im-instant-chat.git
+```
+- 进入项目目录,执行: 
+```
+composer install
+```
+or
+
+- 【Recommended】Download the full source code and put it on your own server. Take a look at the  [(releases)](https://gitee.com/raingad/im-instant-chat/releases) at the top of the gitee project home page and download the latest release in the distribution.
+
+#### Start installation
+1. Create a website by pointing the site's running directory to the 'public' directory in the project root.
+
+
+
+2. Enable pseudo-static and set the reverse proxy, the following only shows the pseudo-static and reverse proxy configuration of nginx, apache please Baidu or use chatGPT conversion.
+
+
+``` 
+location ~* (runtime|application)/{
+	return 403;
+}
+location / {
+	if (!-e $request_filename){
+		rewrite  ^(.*)$  /index.php?s=$1  last;   break;
+	}
+}
+
+#Reverse proxy port 8282, no modification required
+
+location /wss
+    {
+      proxy_pass http://127.0.0.1:8282;
+      proxy_http_version 1.1;
+      proxy_set_header Upgrade $http_upgrade;
+      proxy_set_header Connection "Upgrade";
+      proxy_set_header X-Real-IP $remote_addr;
+    }
+```
+
+3. If you have a domain name and want to use services such as audio and video calls and voice messaging, you also need to configure a certificate to enable HTTPS. You can use a free 'Let's Encrypt' certificate. If you don't need these services, you can directly use the HTTP protocol, but the functionality will be limited.
+   
+4. Access your IP or domain name to enter the custom installation wizard.
+   
+5. Access your IP or domain name to enter the custom installation wizard.
+
+#### If installation fails
+1.  Enter  `public\sql\database.sql`  to import the database into your own database.
+
+2.  Enter the project root directory, modify  `example.env` to `.env` , and modify the corresponding database parameters. **Please carefully read the configuration instructions in env**.
+
+> if you want to save chat files to oss, you need to configure them in the background. Do not modify the environment configuration files after configuration.
+
+### Start the message push service
+Because the chat software needs to use websockt, so we need to start workerman, the system has built-in corresponding services, you can manage the home page in the background to run services, but the first use needs to be debugged.
+
+1. Enter the project root directory to run `php think worker:gateway start -d`, or run `php start.php start -d` to run the message service. Do not use `- d` during testing. Under windows, run the `start_for_ win.bat` file in the root directory directly. Since there are many restrictions on the use of Workerman under Windows, it is recommended to use Linux system in formal environment, while windows system is only recommended for development environment.
+
+2. The message service needs to release port 8282. If you need to modify it, please modify the corresponding parameters in the `WORKER` section of the environment configuration file. For windows users, please modify port 8282 in [`app\worker\start_gateway.php`]. The port number needs to be changed according to the situation.
+   
+3. The system uses the domain name as the address of the websocket service directly, so it needs to configure the proxy in the nginx of the website and listen to port 8282. The parameters of the proxy configuration have been written in the pseudo-static.
+
+4. For more information about the use of workerman, please visit [workerman official website](https://www.workerman.net/) official website.
+
+5. After the deployment, the password for the administrator account is: `administrator``123456`, and the management entry is located in the lower left corner of the chat interface.
+
+### Install deployment Services
+
+The author provides the installation services of the system, including the back-end and front-end deployment to the online, to ensure the perfect operation of the project, 200 yuan per time, the installation service can provide detailed installation tutorials and interface documents, if necessary, you can contact the author!
+
+### QQ Communication group
+If you have any questions, please leave a message or join our QQ group!
+
+It's not easy to create. Click a star.
+
+[QQ Communication group:336921267](https://jq.qq.com/?_wv=1027&k=jMQAt9lh)
+

+ 153 - 0
README.md

@@ -0,0 +1,153 @@
+# IM即时聊天
+
+### 介绍
+Raingad-IM是一个开源的即时通信demo,需要前后端配合使用,主要用于学习交流,为大家提供即时通讯的开发思路,许多功能需要自行开发,开发的初衷旨在快速建立企业内部通讯系统、内网交流、社区交流。
+
+|  类型 | 链接 |
+| --------- | ---- |
+| 前端源码    | https://gitee.com/raingad/im-chat-front |
+| 后端源码 | https://gitee.com/raingad/im-instant-chat |
+| web端演示 | http://im.raingad.com/index.html |
+| 移动端H5演示 | http://im.raingad.com/h5 |
+| 安卓APP演示 | https://emoji.raingad.com/file/raingad.apk |
+
+
+体验账号:13800000002  密码:123456
+
+尾号2、3、4......18、19、20 都是
+
+体验账号:13800000020  密码:123456 
+
+### 支持功能
+
+- 支持单聊和群聊,支持发送表情、图片、语音、视频和文件消息
+- 单聊支持消息已读未读的状态显示,在线状态显示
+- 群聊创建、删除和群成员管理、群公告、群禁言、@群成员等
+- 支持置顶联系人,消息免打扰;
+- 支持设置新消息声音提醒,浏览器通知
+- 支持管理员撤回群成员消息,支持群成员不能互相添加好友
+- 支持一对一音视频通话(已打通web端和移动端,小程序不支持)
+- 支持文件、图片和绝大部分媒体文件在线预览
+- 支持移动端(由uniapp开发,可打包H5、APP和小程序)
+- 全新支持企业模式和社区模式,社区模式支持注册、添加好友功能
+- APP支持单聊消息在线、离线推送(需要自行申请unipush服务)
+- 支持简易后台管理,包括用户管理、群组管理、系统设置等
+
+### 最新更新
+请查看右侧发行版更新日志
+
+** v4.0.0 **
+1. 全新增加@群成员功能,支持@所有人
+2. 修改最近聊天界面,增加未读消息和@消息的筛选
+3. 重写web端文件的消息类型,并支持文件拖拽发送
+4. 修复移动端网络断开后,又连上网络时,websocket自动重连
+5. 修复扫码加群时无加载动画导致多次入群
+6. 修复若干BUG
+
+### 软件架构
+
+后端技术栈:`thinkphp6+workerman+redis`
+
+前端技术栈:`vue2+Lemon-IMUI+element-UI`
+
+桌面端:`vue2+Lemon-IMUI+element-UI + electron`
+
+移动端:`uniapp for vue3 + pinia`
+
+### 安装教程
+> 安装程序需要有一定的PHP经验和服务器运维经验,如果没有请加入交流群联系作者,作者提供付费部署服务!
+
+#### 准备工作
+
+需要先安装好运行环境,推荐使用宝塔服务器,安装LNMP的架构,建议使用nginx作为服务器,不建议使用apache。需要安装以下软件:
+|  所需环境 | 版本 | 备注 | 推荐版本 |
+| --------- | ---- | ---- | ---|
+| linux    | >= 7.0 |  以下的版本未做测试   | 7.9 |
+| nginx    | >= 1.17 |     | 最新的 |
+| php | >= 7.1 |  不兼容8    | 7.3 |
+| mysql    | >= 5.7 | 必须要5.7及以上     | 5.7 |
+| redis    | >= 5.0 |     | 7.0 |
+
+**重要操作**
+
+1、PHP需要安装扩展:`redis` `fileinfo`
+
+2、PHP需要取消禁用函数:`shell_exec` `proc_open` `pcntl_exec` `pcntl_alarm` `pcntl_fork` `pcntl_waitpid` `pcntl_wait` `pcntl_signal` `pcntl_signal_dispatch` `putenv`
+
+#### 源码下载
+- 克隆代码到本地: 
+``` 
+git clone https://gitee.com/raingad/im-instant-chat.git
+```
+- 进入项目目录,执行: 
+```
+composer install
+```
+或者
+- 【推荐使用】下载完整源码放到自己的服务器上。请注意看gitee项目主页顶部右侧 [(发行版)](https://gitee.com/raingad/im-instant-chat/releases) ,请在发行版中下载最新发布的版本。
+
+#### 开始安装
+1. 创建网站,把网站的运行目录指向项目根目录下的 `public` 目录。
+
+2. 开启伪静态和设置反向代理,下面只展示nginx的伪静态和反向代理配置,apache的请自行百度或者使用chatGPT转换。
+
+
+``` 
+location ~* (runtime|application)/{
+	return 403;
+}
+location / {
+	if (!-e $request_filename){
+		rewrite  ^(.*)$  /index.php?s=$1  last;   break;
+	}
+}
+
+#反向代理8282端口,无需修改内容
+
+location /wss
+    {
+      proxy_pass http://127.0.0.1:8282;
+      proxy_http_version 1.1;
+      proxy_set_header Upgrade $http_upgrade;
+      proxy_set_header Connection "Upgrade";
+      proxy_set_header X-Real-IP $remote_addr;
+    }
+```
+
+3. 如果有域名并且要使用音视频通话、语音消息等服务还需要配置证书来开启HTTPS,可以使用免费的 `Let's Encrypt` 证书,如果不需要这些服务,可以直接使用HTTP协议,但是功能会受限。
+   
+4. 访问你的ip或者域名即可进入自定义安装向导,如果访问出现错误请参考下一节安装失败的第二项,将环境配置文件的 `APP_DEBUG` 打开,看报错情况。
+   
+5. 先参考下一章“启动消息推送服务”,再来安装程序最佳。
+
+#### 如果安装失败
+1.  进入 `public\sql\database.sql` 将数据库导入自己的数据库。
+
+2.  进入项目根目录,修改 `example.env` 为 `.env` ,并修改数据库相应的参数,**请仔细阅读env中的配置说明**。
+
+PS:如需开启聊天文件存入oss,需要在后台中进行配置,配置后不要再对环境配置文件进行修改。
+
+### 启动消息推送服务
+因为是聊天软件需要用到websockt,所以我们需要启动workerman,系统已经内置了相应的服务,可以在后台管理首页进行运行服务,但是首次使用需要先进行调试。
+
+1. 进入项目根目录 运行 `php think worker:gateway start -d`,或者运行 `php start.php start -d` 即可运行消息服务,测试时不要`-d`。windows下请直接运行根目录下的`start_for_win.bat`文件,由于Workerman在Windows下有诸多使用限制,所以正式环境建议用Linux系统,windows系统仅建议用于开发环境。
+
+2. 消息服务需要放行 8282 端口,如需修改,请修改环境噢配置文件中`WORKER` 板块的相应参数。windows用户请修改 [ `app\worker\start_gateway.php`] 中的 8282 端口。端口号根据情况需改。
+   
+3. 系统采用直接用域名作为websocket服务的地址,所以需要在网站的nginx中配置代理并监听8282端口,已在伪静态中写了代理配置的参数。
+
+4. 更多关于workerman的使用,请进入[workerman官网](https://www.workerman.net/)官网进行查阅。
+
+5. 部署完成之后管理员账号密码为:`administrator`  `123456`,管理入口在聊天界面的左下角。
+
+### 安装部署服务
+
+作者提供本系统的安装服务,包括后端和前端部署到线上,可手把手教学,保证项目的完美运行,200元/次,安装服务可赠送详细的安装教程以及接口文档,如有需要可以进群联系作者!
+
+### 交流群
+如果有什么问题,请留言,或者加入我们的QQ群!
+
+创作不易,点个star吧
+
+[QQ 交流群:336921267](https://jq.qq.com/?_wv=1027&k=jMQAt9lh)
+

+ 1 - 0
app/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 22 - 0
app/AppService.php

@@ -0,0 +1,22 @@
+<?php
+declare (strict_types = 1);
+
+namespace app;
+
+use think\Service;
+
+/**
+ * 应用服务类
+ */
+class AppService extends Service
+{
+    public function register()
+    {
+        // 服务注册
+    }
+
+    public function boot()
+    {
+        // 服务启动
+    }
+}

+ 141 - 0
app/BaseController.php

@@ -0,0 +1,141 @@
+<?php
+declare (strict_types = 1);
+
+namespace app;
+
+use think\App;
+use think\exception\ValidateException;
+use think\Validate;
+use app\manage\model\{Config};
+use think\facade\Cache;
+use thans\jwt\facade\JWTAuth;
+/**
+ * 控制器基础类
+ */
+abstract class BaseController
+{
+    /**
+     * Request实例
+     * @var \think\Request
+     */
+    protected $request;
+
+    /**
+     * 应用实例
+     * @var \think\App
+     */
+    protected $app;
+
+    /**
+     * 是否批量验证
+     * @var bool
+     */
+    protected $batchValidate = false;
+
+    /**
+     * 控制器中间件
+     * @var array
+     */
+    protected $middleware = [];
+
+    /**
+     * 是否批量验证
+     * @var bool
+     */
+    protected $userInfo = [];
+
+        /**
+     * 接收的post数据
+     * @var bool
+     */
+    protected $postData = [];
+
+    protected $uid = 0;
+
+    protected $globalConfig = [];
+
+    protected $chatSetting = [];
+
+    /**
+     * 构造方法
+     * @access public
+     * @param  App  $app  应用对象
+     */
+    public function __construct(App $app)
+    {
+        $this->app     = $app;
+        $this->request = $this->app->request;
+        // 控制器初始化
+        $this->initialize();
+    }
+
+    // 初始化
+    protected function initialize()
+    {
+        $this->userInfo=$this->request->userInfo;
+        $this->uid=$this->userInfo['user_id'] ?? 0;
+        $config=Config::getSystemInfo();
+        if($config){
+            $this->globalConfig = $config;
+            $this->chatSetting = $config['chatInfo'] ?? [];
+        }
+        // 验证版本,如果不一致,就需要退出重新登陆
+        $version =config('app.app_version');
+        $oldVersion=Cache::get('app_version');
+        if($version!=$oldVersion){
+            Cache::set('app_version',$version);
+            JWTAuth::refresh();
+            Cache::delete('systemInfo');
+        }
+    }
+
+    /**
+     * 验证数据
+     * @access protected
+     * @param  array        $data     数据
+     * @param  string|array $validate 验证器名或者验证规则数组
+     * @param  array        $message  提示信息
+     * @param  bool         $batch    是否批量验证
+     * @return array|string|true
+     * @throws ValidateException
+     */
+    protected function validate(array $data, $validate, array $message = [], bool $batch = false)
+    {
+        if (is_array($validate)) {
+            $v = new Validate();
+            $v->rule($validate);
+        } else {
+            if (strpos($validate, '.')) {
+                // 支持场景
+                [$validate, $scene] = explode('.', $validate);
+            }
+            $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
+            $v     = new $class();
+            if (!empty($scene)) {
+                $v->scene($scene);
+            }
+        }
+
+        $v->message($message);
+
+        // 是否批量验证
+        if ($batch || $this->batchValidate) {
+            $v->batch(true);
+        }
+
+        return $v->failException(true)->check($data);
+    }
+
+    
+    /**
+     * 自动获取前端传递的分页数量
+     * @param \think\Model|\think\model\relation\HasMany $model
+     * @return \think\Paginator
+     */
+    protected function paginate($model)
+    {
+        $limit = $this->request->param('limit', 20);
+        return $model->paginate($limit);
+    }
+
+}

+ 103 - 0
app/BaseModel.php

@@ -0,0 +1,103 @@
+<?php
+/**
+ * Created by PhpStorm
+ * User xiekunyu@kaishanlaw.com
+ * Date 2021/7/9 16:15
+ */
+
+namespace app;
+
+use think\facade\Db;
+use think\Model;
+
+class BaseModel extends Model
+{
+    protected        $defaultSoftDelete = 0;
+    protected        $error             = '';
+    protected static $db_prefix         = 'yu_';
+    protected static $userInfo          = null;
+    protected static $uid          = null;
+
+
+    protected static function init()
+    {
+        self::$db_prefix = config('database.connections.mysql.prefix') ?: "yu_";
+        self::initModel();
+    }
+
+    // 加载模型自动处理
+    public static function initModel()
+    {
+        self::$userInfo=request()->userInfo ?? null;
+        self::$uid=request()->userInfo['user_id'] ?? null;
+    }
+
+    /**
+     * 获取树状信息
+     * @param array $config
+     */
+    public static function getCheckNode($arr, $pid, $field = "parent_id", $table = '')
+    {
+        if (!$table) {
+            $res = self::find($pid);
+        } else {
+            $res = Db::name($table)->find($pid);
+        }
+        if ($res) {
+            if ($res[$field] > 0) {
+                array_unshift($arr, $res[$field]);
+                return self::getCheckNode($arr, $res[$field], $field, $table);
+            }
+        }
+        return $arr;
+    }
+
+    // 获取错误信息
+    public function getError()
+    {
+        return $this->error;
+    }
+
+    /**
+     * 获取模型的json字段数组
+     * @return array
+     */
+    public function getJsonFieldName(): array
+    {
+        return $this->json;
+    }
+
+     // 匹配列表信息
+     public static function filterIdr($data, $many, $field)
+     {
+         if ($many) {
+             $idr = \utils\Arr::arrayToString($data, $field, false);
+         } else {
+             $idr = [];
+             if (is_array($field)) {
+                 foreach ($field as $v) {
+                     $idr[] = $data[$v];
+                 }
+             } else {
+                 $idr = [$data[$field]];
+             }
+         }
+         $key = array_search(0, $idr);
+         if ($key) {
+             array_splice($idr, $key, 1);
+         }
+         $idr  = array_unique($idr);
+ 
+         return $idr ? : [];
+     }
+
+    //  获取某一项数据的统计
+     public static function getTotal($map,$where=[],$field,$group){
+        return self::field($field)
+            ->where($map)
+            ->where($where)
+            ->group($group)
+            ->select()->toArray();
+    }
+    
+}

+ 58 - 0
app/ExceptionHandle.php

@@ -0,0 +1,58 @@
+<?php
+namespace app;
+
+use think\db\exception\DataNotFoundException;
+use think\db\exception\ModelNotFoundException;
+use think\exception\Handle;
+use think\exception\HttpException;
+use think\exception\HttpResponseException;
+use think\exception\ValidateException;
+use think\Response;
+use Throwable;
+
+/**
+ * 应用异常处理类
+ */
+class ExceptionHandle extends Handle
+{
+    /**
+     * 不需要记录信息(日志)的异常类列表
+     * @var array
+     */
+    protected $ignoreReport = [
+        HttpException::class,
+        HttpResponseException::class,
+        ModelNotFoundException::class,
+        DataNotFoundException::class,
+        ValidateException::class,
+    ];
+
+    /**
+     * 记录异常信息(包括日志或者其它方式记录)
+     *
+     * @access public
+     * @param  Throwable $exception
+     * @return void
+     */
+    public function report(Throwable $exception): void
+    {
+        // 使用内置的方式记录异常日志
+        parent::report($exception);
+    }
+
+    /**
+     * Render an exception into an HTTP response.
+     *
+     * @access public
+     * @param \think\Request   $request
+     * @param Throwable $e
+     * @return Response
+     */
+    public function render($request, Throwable $e): Response
+    {
+        // 添加自定义异常处理机制
+
+        // 其他错误交给系统处理
+        return parent::render($request, $e);
+    }
+}

+ 8 - 0
app/Request.php

@@ -0,0 +1,8 @@
+<?php
+namespace app;
+
+// 应用请求对象类
+class Request extends \think\Request
+{
+
+}

+ 988 - 0
app/common.php

@@ -0,0 +1,988 @@
+<?php
+// 应用公共文件
+use SingKa\Sms\SkSms;
+use GatewayClient\Gateway;
+use \utils\Str;
+/**
+ * 框架内部默认ajax返回
+ * @param string $msg      提示信息
+ * @param string $redirect 重定向类型 current|parent|''
+ * @param string $alert    父层弹框信息
+ * @param bool $close      是否关闭当前层
+ * @param string $url      重定向地址
+ * @param string $data     附加数据
+ * @param int $code        错误码
+ * @param array $extend    扩展数据
+ * @param int $count    总数
+ */
+function success($msg = '操作成功', $data = '', $count = 0, $page = 1, $code = 0)
+{
+    return ret($code, $msg, $data, $count, $page);
+}
+
+/**
+ * 返回警告json信息
+ */
+function warning($msg = '操作失败', $data = '', $count = 0, $page = 1 , $code = 400)
+{
+    return success($msg, $data, $count, $page, $code);
+}
+
+/**
+ * 返回错误json信息
+ */
+function error($msg = '操作失败', $code = 502)
+{
+    return ret($code, '系统错误:'.$msg);
+}
+
+/**
+ * 提前终止信息
+ */
+function shutdown($msg = '禁止访问', $code = 401)
+{
+    exit(json_encode(['code' => $code, 'msg' => $msg, 'data' => []]));
+}
+
+
+/**
+ * ajax数据返回,规范格式
+ * @param array $data   返回的数据,默认空数组
+ * @param string $msg   信息
+ * @param int $code     错误码,0-未出现错误|其他出现错误
+ * @param array $extend 扩展数据
+ */
+function ret($code, $msg = "",$data = [],$count=0, $page=0)
+{
+    $ret = ["code" =>$code, "msg" => $msg,'count'=>$count, "data" => $data,'page'=>$page];
+    return json($ret);
+}
+
+
+/* @param string $string 原文或者密文
+* @param string $operation 操作(ENCODE | DECODE), 默认为 DECODE
+* @param string $key 密钥
+* @param int $expiry 密文有效期, 加密时候有效, 单位 秒,0 为永久有效
+* @return string 处理后的 原文或者 经过 base64_encode 处理后的密文
+*
+* @example
+*
+*  $a = authcode('abc', 'ENCODE', 'key');
+*  $b = authcode($a, 'DECODE', 'key');  // $b(abc)
+*
+*  $a = authcode('abc', 'ENCODE', 'key', 3600);
+*  $b = authcode('abc', 'DECODE', 'key'); // 在一个小时内,$b(abc),否则 $b 为空
+*/
+function authcode($string, $operation = 'DECODE', $key = '', $expiry = 3600) {
+
+    $ckey_length = 4;   
+    // 随机密钥长度 取值 0-32;
+    // 加入随机密钥,可以令密文无任何规律,即便是原文和密钥完全相同,加密结果也会每次不同,增大破解难度。
+    // 取值越大,密文变动规律越大,密文变化 = 16 的 $ckey_length 次方
+    // 当此值为 0 时,则不产生随机密钥
+
+    $key = md5($key ? $key : 'default_key'); //这里可以填写默认key值
+    $keya = md5(substr($key, 0, 16));
+    $keyb = md5(substr($key, 16, 16));
+    $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
+
+    $cryptkey = $keya.md5($keya.$keyc);
+    $key_length = strlen($cryptkey);
+
+    $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
+    $string_length = strlen($string);
+
+    $result = '';
+    $box = range(0, 255);
+
+    $rndkey = array();
+    for($i = 0; $i <= 255; $i++) {
+        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
+    }
+
+    for($j = $i = 0; $i < 256; $i++) {
+        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
+        $tmp = $box[$i];
+        $box[$i] = $box[$j];
+        $box[$j] = $tmp;
+    }
+
+    for($a = $j = $i = 0; $i < $string_length; $i++) {
+        $a = ($a + 1) % 256;
+        $j = ($j + $box[$a]) % 256;
+        $tmp = $box[$a];
+        $box[$a] = $box[$j];
+        $box[$j] = $tmp;
+        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
+    }
+     
+    if($operation == 'DECODE') {
+            if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
+                return substr($result, 26);
+            } else {
+                return '';
+            }
+        } else {
+            return $keyc.str_replace('=', '', base64_encode($result));
+    }
+}
+
+function ssoTokenEncode($str,$key='lvzhesso',$expire=0){
+    $ids=encryptIds($str);
+   return authcode($ids,"ENCODE",$key,$expire);
+}
+
+function ssoTokenDecode($str,$key='lvzhesso'){
+    $ids=authcode($str,"DECODE",$key);
+    try{
+        return decryptIds($ids);
+    }catch(\Exception $e){
+        return '';
+    }
+}
+
+
+//id加密
+function encryptIds($str)
+{
+    $hash = config('hashids');
+    return \Hashids\Hashids::instance($hash['length'], $hash['salt'])->encode($str);
+}
+
+//id解密
+function decryptIds($str)
+{
+    $hash = config('hashids');
+    return \Hashids\Hashids::instance($hash['length'], $hash['salt'])->decode($str);
+}
+
+    /**
+    * 短信发送示例
+    *
+    * @mobile  短信发送对象手机号码
+    * @action  短信发送场景,会自动传入短信模板
+    * @parme   短信内容数组
+    */
+    function sendSms($mobile, $action, $parme)
+    {
+        $config = config('sms');
+        //$this->SmsDefaultDriver是从数据库中读取的短信默认驱动
+        $driver = $config['driver'] ?: 'aliyun'; 
+        $conf=$config[$driver];
+        $sms = new SkSms($driver, $conf);//传入短信驱动和配置信息
+        //判断短信发送驱动,非阿里云和七牛云,需将内容数组主键序号化
+        if ($driver == 'aliyun') {
+            $result = $sms->$action($mobile, $parme);
+        } elseif ($driver == 'qiniu') {
+            $result = $sms->$action([$mobile], $parme);
+        } elseif ($driver == 'upyun') {
+            $result = $sms->$action($mobile, implode('|', restoreArray($parme)));
+        } else {
+            $result = $sms->$action($mobile, restoreArray($parme));
+        }
+        if ($result['code'] == 200) {
+            $data['code'] = 200;
+            $data['msg'] = '短信发送成功';
+        } else {
+            $data['code'] = $result['code'];
+            $data['msg'] = $result['msg'];
+        }
+        return $data;
+    }
+  	
+    /**
+    * 数组主键序号化
+    *
+    * @arr  需要转换的数组
+    */
+    function restoreArray($arr)
+    {
+        if (!is_array($arr)){
+            return $arr;
+        }
+        $c = 0;
+        $new = [];
+        foreach ($arr as $key => $value) {
+            $new[$c] = $value;
+            $c++;
+        }
+        return $new;
+    }
+
+//密码生成规则
+function password_hash_tp($password,$salt)
+{
+    return md5($salt.$password.$salt);
+}
+
+// 获取url中的主机名
+function getHost($url){ 
+    if(!preg_match('/http[s]:\/\/[\w.]+[\w\/]*[\w.]*\??[\w=&\+\%]*/is',$url)){
+        return '';
+    }
+    $search = '~^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?~i';
+    $url = trim($url);
+    preg_match_all($search, $url ,$rr);
+    return $rr[4][0];
+}
+
+//根据姓名画头像
+function circleAvatar($str,$s,$uid=0,$is_save=0,$save_path=''){
+    //定义输出为图像类型
+    header("content-type:image/png");
+    $str =$str?:"律者";
+    $uid =$uid?:rand(0,10);
+    $text=\utils\Str::getLastName($str,2);
+    $width = $height = $s?:80;
+    if($width<40 or $width>120){
+        $width = $height =80;
+    }
+    $colors=['#F56C6C','#E6A23C','#fbbd08','#67C23A','#39b54a','#1cbbb4','#409EFF','#6739b6','#e239ff','#e03997'];
+    $color=hex2rgb($colors[(int)$uid%10]);
+    $size=$width/4;
+    $textLeft=($height/2)-$size-$width/10;
+    if($width<=80){
+        $text=\utils\Str::getLastName($str,1);
+        $size=$width/2;
+        $textLeft=$size/3;
+    }
+//新建图象
+    $pic=imagecreate($width,$height);
+//定义黑白颜色
+    $background=imagecolorallocate($pic,$color['r'],$color['g'],$color['b']);
+    $textColor=imagecolorallocate($pic,255,255,255);
+    imagefill($pic,0,0,$background);//填充背景色
+//定义字体
+    $font=root_path()."/public/static/fonts/PingFangHeavy.ttf";
+    //写 TTF 文字到图中
+    imagettftext($pic,$size,0,$textLeft,($height/2)+$size/2,$textColor,$font,$text);
+    if($is_save){
+        $path=$save_path."/".$uid.".png";
+        $dir = pathinfo($path,PATHINFO_DIRNAME);
+        if(!is_dir($dir)){
+            $file_create_res = mkdir($dir,0777,true);
+            if(!$file_create_res){
+                return false;//没有创建成功
+            }
+        }
+        imagepng($pic,$path);
+        imagedestroy($pic);
+        return $path;
+    }else{
+        //输出图象
+        imagepng($pic);
+        //结束图形,释放内存空间
+        imagedestroy($pic);
+        return $pic;
+    }
+}
+
+//头像拼接
+function avatarUrl($path, $str = "雨",$uid=0,$s=80)
+{
+    $str = Str::strFilter($str);
+    if ($path) {
+        // 判断头像路径中是否有http
+        if (strpos($path, 'http') !== false) {
+            $url = $path;
+        } else {
+            $url = getDiskUrl() .'/'. ltrim($path,'/') ;
+        }
+    }else {
+        if($str){
+            $url=request()->domain()."/avatar/".$str.'/'.$s.'/'.$uid;
+        }else{
+            $url='';
+        }
+    }
+    return $url;
+}
+
+// 获取文件的地址
+function getFileUrl($path){
+    return getDiskUrl() .'/'. ltrim($path,'/') ;
+}
+
+/**
+ * 十六进制 转 RGB
+ */
+function hex2rgb($hexColor)
+{
+    $color = str_replace('#', '', $hexColor);
+    if (strlen($color) > 3) {
+        $rgb = array(
+            'r' => hexdec(substr($color, 0, 2)),
+            'g' => hexdec(substr($color, 2, 2)),
+            'b' => hexdec(substr($color, 4, 2))
+        );
+    } else {
+        $color = $hexColor;
+        $r = substr($color, 0, 1) . substr($color, 0, 1);
+        $g = substr($color, 1, 1) . substr($color, 1, 1);
+        $b = substr($color, 2, 1) . substr($color, 2, 1);
+        $rgb = array(
+            'r' => hexdec($r),
+            'g' => hexdec($g),
+            'b' => hexdec($b)
+        );
+    }
+    return $rgb;
+}
+
+/**
+ * 将数组按字母A-Z排序
+ * @return [type] [description]
+ */
+function chartSort($array, $field,$isGroup=true,$chart='chart')
+{
+    $newArray = [];
+    foreach ($array as $k => &$v) {
+        $v[$chart] = getFirstChart($v[$field]);
+        $newArray[] = $v;
+    }
+    $data = [];
+    if($isGroup){
+        foreach ($newArray as $k => $v) {
+            if (array_key_exists($v[$chart], $data)) {
+                $data[$v[$chart]][] = $v;
+            } else {
+                $data[$v[$chart]] = [];
+                $data[$v[$chart]][] = $v;
+            }
+        }
+        ksort($data);
+    }else{
+       return $newArray;
+    }
+    return $data;
+}
+
+/**
+ * 返回取汉字的第一个字的首字母
+ * @param  [type] $str [string]
+ * @return [type]      [strind]
+ */
+function getFirstChart($str)
+{
+    $str = str_replace(' ', '', $str);
+    // 过滤特殊符号
+    $str = preg_replace('/[^\x{4e00}-\x{9fa5}A-Za-z0-9]/u', '', $str);
+    if (empty($str)) {
+        return '#';
+    }
+    $char = ord($str[0]);
+    if ($char >= ord('A') && $char <= ord('z')) {
+        return strtoupper($str[0]);
+    }
+    $s1 = iconv('UTF-8', 'gb2312//IGNORE', $str);
+    $s2 = iconv('gb2312', 'UTF-8//IGNORE', $s1);
+    $s = $s2 == $str ? $s1 : $str;
+    $asc = ord($s[0]) * 256 + ord($s[1]) - 65536;
+    if ($asc >= -20319 && $asc <= -20284) return 'A';
+    if ($asc >= -20283 && $asc <= -19776) return 'B';
+    if ($asc >= -19775 && $asc <= -19219) return 'C';
+    if ($asc >= -19218 && $asc <= -18711) return 'D';
+    if ($asc >= -18710 && $asc <= -18527) return 'E';
+    if ($asc >= -18526 && $asc <= -18240) return 'F';
+    if ($asc >= -18239 && $asc <= -17923) return 'G';
+    if ($asc >= -17922 && $asc <= -17418) return 'H';
+    if ($asc >= -17417 && $asc <= -16475) return 'J';
+    if ($asc >= -16474 && $asc <= -16213) return 'K';
+    if ($asc >= -16212 && $asc <= -15641) return 'L';
+    if ($asc >= -15640 && $asc <= -15166) return 'M';
+    if ($asc >= -15165 && $asc <= -14923) return 'N';
+    if ($asc >= -14922 && $asc <= -14915) return 'O';
+    if ($asc >= -14914 && $asc <= -14631) return 'P';
+    if ($asc >= -14630 && $asc <= -14150) return 'Q';
+    if ($asc >= -14149 && $asc <= -14091) return 'R';
+    if ($asc >= -14090 && $asc <= -13319) return 'S';
+    if ($asc >= -13318 && $asc <= -12839) return 'T';
+    if ($asc >= -12838 && $asc <= -12557) return 'W';
+    if ($asc >= -12556 && $asc <= -11848) return 'X';
+    if ($asc >= -11847 && $asc <= -11056) return 'Y';
+    if ($asc >= -11055 && $asc <= -10247) return 'Z';
+    return "#";
+}
+
+// 拼接聊天对象
+function chat_identify($from_user,$to_user){
+    $identify=[$from_user,$to_user];
+    sort($identify);
+    return implode('-',$identify);
+}
+
+//数组中获取ID字符串
+function arrayToString($array,$field,$isStr=true){
+    $idArr = [];
+    foreach ($array as $k => $v) {
+        if(is_array($field)){
+            foreach($field as $val){
+                $idArr[]=$v[$val];
+            }
+        }else{
+            $idArr[] = $v[$field];
+        }
+    }
+    if ($isStr) {
+        $idStr = implode(',', $idArr);
+        return $idStr;
+    } else {
+        return $idArr;
+    }
+
+}
+
+// 根据文件后缀进行分类
+function getFileType($ext,$rst=false){
+    $ext=strtolower($ext);
+    $image=['jpg','jpeg','png','bmp','gif'];
+    $radio=['mp3','wav','wmv','amr'];
+    $video=['mp4','3gp','avi','m2v','mkv','mov'];
+    $doc=['ppt','pptx','doc','docx','xls','xlsx','pdf','txt','md'];
+    $msgType='file';
+    if(in_array($ext,$doc)){
+        $fileType=1;
+    }elseif(in_array($ext,$image)){
+        $fileType=2;
+        $msgType='image';
+    }elseif(in_array($ext,$radio)){
+        $fileType=3;
+        $msgType='voice';
+    }elseif(in_array($ext,$video)){
+        $fileType=4;
+        $msgType='video';
+    }else{
+        $fileType=9;
+    }
+    if($rst){
+        return $msgType;
+    }else{
+        return $fileType;
+    }
+}
+
+/**
+ * 二位数组排序
+ * $array 需要排序的数组
+ * $sort_key 需要排序的字段
+ * $sort_order 正序还是倒序
+ * $sort_type  排序的类型:数字,字母
+ */
+function sortArray($arrays, $sort_key, $sort_order = SORT_ASC, $sort_type = SORT_NUMERIC)
+{
+    if (is_array($arrays)) {
+        foreach ($arrays as $array) {
+            if (is_array($array)) {
+                $key_arrays[] = $array[$sort_key];
+            } else {
+                return false;
+            }
+        }
+    } else {
+        return false;
+    }
+    array_multisort($key_arrays, $sort_order, $sort_type, $arrays);
+    return $arrays;
+}
+
+//gateway向web页面推送消息
+function wsSendMsg($user, $type,  $data, $isGroup=0)
+{
+    $message = json_encode([
+        'type' => $type,
+        'time' => time(),
+        'data' => $data
+    ]);
+    try{
+        Gateway::$registerAddress = config('gateway.registerAddress');
+        if (!$user) {
+            Gateway::sendToAll($message);
+        } else {
+            if (!$isGroup) {
+                $send = 'sendToUid';
+                // 如果是单聊和语音通话需要使用unipush推送
+                $event=$data['extends']['event'] ?? '';
+                if(in_array($type,['simple']) || ($event=='calling' && $type=='webrtc')){
+                    unipush($user,$data);
+                }
+            } else {
+                $send = "sendToGroup";
+            }
+            Gateway::$send($user, $message);
+        }
+     }catch(\Exception $e){
+        //忽略错误
+     }
+    
+}
+
+// 绑定unipush的cid
+function bindCid($uid,$cid){
+    $url=env('unipush.url','');
+    if(!$url){
+        return false;
+    }
+    $data=[
+        'type'=>'bindCid',
+        'alias'=>[[
+            'cid'=>$cid,
+            'alias'=>$uid
+        ]]
+        
+    ];
+    try{
+        $data=json_encode($data);
+        utils\Curl::curl_post($url,$data,true,["Content-Type: application/json"]);
+    }catch(\Exception $e){
+        //忽略错误
+    }
+    
+}
+
+// unipush推送
+function unipush($toUser,$data){
+    $url=env('unipush.url','');
+    if(!$url){
+        return false;
+    }
+    $content='';
+    if($data['type']=='text'){
+        $content=Str::subStr($data['content'],0,50);
+    }else{
+        $content=getMsgType($data['type'],$data['extends']['type'] ?? 0);
+    }
+    // 这个推送不需要发给发送人
+    $fromUser=$data['fromUser']['id'] ?? '';
+    if(is_array($toUser)){
+        $toUser=array_diff($toUser,[$fromUser]);
+    }
+    $is_force=env('unipush.is_force',false);
+    $data=[
+        'type'=>'push',
+        'toUser'=>$toUser,
+        'title'=>$data['fromUser']['displayName'],
+        'content'=>$content,
+        'force_notification'=>$is_force,
+        'payload'=>$data
+    ];
+    try{
+        $data=json_encode($data);
+        utils\Curl::curl_post($url,$data,true,["Content-Type: application/json"]);
+    }catch(\Exception $e){
+        //忽略错误
+    }
+}
+
+// 预览文件
+function previewUrl($url){
+    $previewUrl=env('preview.own','');
+    // $preview='';
+    // $suffix=explode('.',$url);
+    // $ext=$suffix[count($suffix)-1];
+    // $media=['jpg','jpeg','png','bmp','gif','pdf','mp3','wav','wmv','amr','mp4','3gp','avi','m2v','mkv','mov','webp'];
+    // $doc=['ppt','pptx','doc','docx','xls','xlsx','pdf'];
+    // if(in_array($ext,$media) && $previewConf['own']){
+    //     $preview=$previewConf['own']."view.html?src=".$url;
+    // }elseif(in_array($ext,$doc) && $previewConf['yzdcs']){
+    //     $preview=$previewConf['yzdcs'].'?k='.$previewConf['keycode'].'&url='.$url;
+    // }else{
+        
+    // }
+    if($previewUrl){
+        $preview=$previewUrl.$url;
+    }else{
+        $preview=rtrim(request()->domain(),'/')."/view.html?src=".$url;
+    }
+    return $preview;
+}
+
+/**
+ * 解析sql语句
+ * @param  string $content sql内容
+ * @param  int $limit  如果为1,则只返回一条sql语句,默认返回所有
+ * @param  array $prefix 替换表前缀
+ * @return array|string 除去注释之后的sql语句数组或一条语句
+ */
+function parse_sql($sql = '', $limit = 0, $prefix = []) {
+    // 被替换的前缀
+    $from = '';
+    // 要替换的前缀
+    $to = '';
+    // 替换表前缀
+    if (!empty($prefix)) {
+        $to   = current($prefix);
+        $from = current(array_flip($prefix));
+    }
+    if ($sql != '') {
+        // 纯sql内容
+        $pure_sql = [];
+        // 多行注释标记
+        $comment = false;
+        // 按行分割,兼容多个平台
+        $sql = str_replace(["\r\n", "\r"], "\n", $sql);
+        $sql = explode("\n", trim($sql));
+        // 循环处理每一行
+        foreach ($sql as $key => $line) {
+            // 跳过空行
+            if ($line == '') {
+                continue;
+            }
+            // 跳过以#或者--开头的单行注释
+            if (preg_match("/^(#|--)/", $line)) {
+                continue;
+            }
+            // 跳过以/**/包裹起来的单行注释
+            if (preg_match("/^\/\*(.*?)\*\//", $line)) {
+                continue;
+            }
+            // 多行注释开始
+            if (substr($line, 0, 2) == '/*') {
+                $comment = true;
+                continue;
+            }
+            // 多行注释结束
+            if (substr($line, -2) == '*/') {
+                $comment = false;
+                continue;
+            }
+            // 多行注释没有结束,继续跳过
+            if ($comment) {
+                continue;
+            }
+            // 替换表前缀
+            if ($from != '') {
+                $line = str_replace('`'.$from, '`'.$to, $line);
+            }
+            if ($line == 'BEGIN;' || $line =='COMMIT;') {
+                continue;
+            }
+            // sql语句
+            array_push($pure_sql, $line);
+        }
+        // 只返回一条语句
+        if ($limit == 1) {
+            return implode("",$pure_sql);
+        }
+        // 以数组形式返回sql语句
+        $pure_sql = implode("\n",$pure_sql);
+        $pure_sql = explode(";\n", $pure_sql);
+        return $pure_sql;
+    } else {
+        return $limit == 1 ? '' : [];
+    }
+}
+
+/**
+ * 更新或添加环境变量
+ *
+ * @param string $key 环境变量的键
+ * @param string $value 环境变量的值
+ * @return bool 成功返回 true,失败返回 false
+ */
+function updateEnv($key, $value)
+{
+    $envFile = app()->getRootPath() . '.env';
+    if (!file_exists($envFile) || !is_writable($envFile)){
+        return false;
+    }
+
+    // 读取 .env 文件内容
+    $envContent = file_get_contents($envFile);
+    $keyPattern = preg_quote($key, '/');
+    $pattern = "/^{$keyPattern}=(.*)\$/m";
+
+    if (preg_match($pattern, $envContent)) {
+        // 如果找到了键值对,替换其值
+        $replacement = "{$key}={$value}";
+        $newEnvContent = preg_replace($pattern, $replacement, $envContent);
+    } else {
+        // 如果没有找到键值对,添加新的键值对
+        $newEnvContent = $envContent . PHP_EOL . "{$key}={$value}";
+    }
+    // 保存更新后的 .env 文件内容
+    return file_put_contents($envFile, $newEnvContent) !== false;
+}
+
+// 获取文件的域名
+function getDiskUrl(){
+    $disk=env('filesystem.driver','local');
+    $url=request()->domain();
+    if($disk=='aliyun'){
+        $url=env('filesystem.aliyun_url','');
+    }elseif($disk=='qiniu'){
+        $url=env('filesystem.qiniu_url','');
+    }elseif($disk=='qcloud'){
+        $url=env('filesystem.qcloud_cdn','');
+    }
+    $url=rtrim($url,'/');
+    return $url;
+}
+
+/**
+ * 合成图片
+ * @param  array   $pic_list  [图片列表数组]
+ * @param  boolean $is_save   [是否保存,true保存,false输出到浏览器]
+ * @param  string  $save_path [保存路径]
+ * @return boolean|string
+ */
+function getGroupAvatar($pic_list=array(),$is_save=false,$save_path=''){
+    //验证参数
+    if(empty($pic_list) || empty($save_path)){
+        return false;
+    }
+    if($is_save){
+        //如果需要保存,需要传保存地址
+        if(empty($save_path)){
+            return false;
+        }
+    }
+    // 只操作前9个图片
+    $pic_list = array_slice($pic_list, 0, 9);
+    //设置背景图片宽高
+    $bg_w = 150; // 背景图片宽度
+    $bg_h = 150; // 背景图片高度
+    //新建一个真彩色图像作为背景
+    $background = imagecreatetruecolor($bg_w,$bg_h);
+    //为真彩色画布创建白灰色背景,再设置为透明
+    $color = imagecolorallocate($background, 202, 201, 201);
+    imagefill($background, 0, 0, $color);
+    imageColorTransparent($background, $color);
+    //根据图片个数设置图片位置
+    $pic_count = count($pic_list);
+    $lineArr = array();//需要换行的位置
+    $space_x = 3;
+    $space_y = 3;
+    $line_x = 0;
+    switch($pic_count) {
+        case 1: // 正中间
+            $start_x = intval($bg_w/4); // 开始位置X
+            $start_y = intval($bg_h/4); // 开始位置Y
+            $pic_w = intval($bg_w/2); // 宽度
+            $pic_h = intval($bg_h/2); // 高度
+            break;
+        case 2: // 中间位置并排
+            $start_x = 2;
+            $start_y = intval($bg_h/4) + 3;
+            $pic_w = intval($bg_w/2) - 5;
+            $pic_h = intval($bg_h/2) - 5;
+            $space_x = 5;
+            break;
+        case 3:
+            $start_x = 40; // 开始位置X
+            $start_y = 5; // 开始位置Y
+            $pic_w = intval($bg_w/2) - 5; // 宽度
+            $pic_h = intval($bg_h/2) - 5; // 高度
+            $lineArr = array(2);
+            $line_x = 4;
+            break;
+        case 4:
+            $start_x = 4; // 开始位置X
+            $start_y = 5; // 开始位置Y
+            $pic_w = intval($bg_w/2) - 5; // 宽度
+            $pic_h = intval($bg_h/2) - 5; // 高度
+            $lineArr = array(3);
+            $line_x = 4;
+            break;
+        case 5:
+            $start_x = 30; // 开始位置X
+            $start_y = 30; // 开始位置Y
+            $pic_w = intval($bg_w/3) - 5; // 宽度
+            $pic_h = intval($bg_h/3) - 5; // 高度
+            $lineArr = array(3);
+            $line_x = 5;
+            break;
+        case 6:
+            $start_x = 5; // 开始位置X
+            $start_y = 30; // 开始位置Y
+            $pic_w = intval($bg_w/3) - 5; // 宽度
+            $pic_h = intval($bg_h/3) - 5; // 高度
+            $lineArr = array(4);
+            $line_x = 5;
+            break;
+        case 7:
+            $start_x = 53; // 开始位置X
+            $start_y = 5; // 开始位置Y
+            $pic_w = intval($bg_w/3) - 5; // 宽度
+            $pic_h = intval($bg_h/3) - 5; // 高度
+            $lineArr = array(2,5);
+            $line_x = 5;
+            break;
+        case 8:
+            $start_x = 30; // 开始位置X
+            $start_y = 5; // 开始位置Y
+            $pic_w = intval($bg_w/3) - 5; // 宽度
+            $pic_h = intval($bg_h/3) - 5; // 高度
+            $lineArr = array(3,6);
+            $line_x = 5;
+            break;
+        case 9:
+            $start_x = 5; // 开始位置X
+            $start_y = 5; // 开始位置Y
+            $pic_w = intval($bg_w/3) - 5; // 宽度
+            $pic_h = intval($bg_h/3) - 5; // 高度
+            $lineArr = array(4,7);
+            $line_x = 5;
+            break;
+    }
+    foreach( $pic_list as $k=>$pic_path ) {
+        $kk = $k + 1;
+        if ( in_array($kk, $lineArr) ) {
+            $start_x = $line_x;
+            $start_y = $start_y + $pic_h + $space_y;
+        }
+        //获取图片文件扩展类型和mime类型,判断是否是正常图片文件
+        //非正常图片文件,相应位置空着,跳过处理
+        $image_mime_info = @getimagesize($pic_path);
+        if($image_mime_info && !empty($image_mime_info['mime'])){
+            $mime_arr = explode('/',$image_mime_info['mime']);
+            if(is_array($mime_arr) && $mime_arr[0] == 'image' && !empty($mime_arr[1])){
+                switch($mime_arr[1]) {
+                    case 'jpg':
+                    case 'jpeg':
+                        $imagecreatefromjpeg = 'imagecreatefromjpeg';
+                        break;
+                    case 'png':
+                        $imagecreatefromjpeg = 'imagecreatefrompng';
+                        break;
+                    case 'gif':
+                    default:
+                        $imagecreatefromjpeg = 'imagecreatefromstring';
+                        $pic_path = file_get_contents($pic_path);
+                        break;
+                }
+                //创建一个新图像
+                $resource = $imagecreatefromjpeg($pic_path);
+                //将图像中的一块矩形区域拷贝到另一个背景图像中
+                // $start_x,$start_y 放置在背景中的起始位置
+                // 0,0 裁剪的源头像的起点位置
+                // $pic_w,$pic_h copy后的高度和宽度
+                imagecopyresized($background,$resource,$start_x,$start_y,0,0,$pic_w,$pic_h,imagesx($resource),imagesy($resource));
+            }
+        }
+        // 最后两个参数为原始图片宽度和高度,倒数两个参数为copy时的图片宽度和高度
+        $start_x = $start_x + $pic_w + $space_x;
+    }
+    if($is_save){
+        $dir = pathinfo($save_path,PATHINFO_DIRNAME);
+        if(!is_dir($dir)){
+            $file_create_res = mkdir($dir,0777,true);
+            if(!$file_create_res){
+                return false;//没有创建成功
+            }
+        }
+        $res = imagejpeg($background,$save_path);
+        imagedestroy($background);
+        if($res){
+            return true;
+        }else{
+            return false;
+        }
+    }else{
+        //直接输出
+        header("Content-type: image/jpg");
+        imagejpeg($background);
+        imagedestroy($background);
+    }
+}
+
+/**
+ * 获取一个唯一token
+ * @return string
+ */
+function getOnlyToken()
+{
+    return md5(uniqid(md5(microtime(true)), true));
+}
+
+// 设置排序规则
+function orderBy($field, $type, $prefix = '', $default = 'update_time')
+{
+    $type=is_numeric($type)?($type==1?'asc':'desc'):$type;
+    if ($field) {
+        $order = $prefix . $field . ' ' . $type;
+    } else {
+        $order = $prefix . $default . ' desc';
+    }
+    return $order;
+}
+
+// 获取文件后缀图片
+function getExtUrl($path){
+    $ext=explode('.',$path);
+    $ext=end($ext);
+    // 如果是图片文件,就直接返回图片地址
+    $image=['jpg','jpeg','png','bmp','gif','webp'];
+    if(in_array($ext,$image)){
+        return getFileUrl($path);
+    }
+    $extUrl='/static/img/ext/'.strtoupper($ext).'.png';
+    // 判断文件是否存在
+    if(!file_exists(public_path().$extUrl)){
+        $extUrl='/static/img/ext/folder.png';
+    }
+    return rtrim(request()->domain(),'/').$extUrl;
+}
+
+// 字符串内容加解密函数
+function str_encipher($str,$encode=true,$key=''){
+    if($key==''){
+         $key=config('app.aes_chat_key');
+    }
+    if($key==''){
+        return $str;
+    }
+    if($encode){
+        $s=\utils\Aes::encrypt($str,$key);
+    }else{
+        $s=\utils\Aes::decrypt($str,$key) ?:'';
+    }
+    return $s;
+}
+
+// 推送时获取消息的类型
+function getMsgType($type,$callVideo=false){
+    $msgName='[暂不支持的消息类型]';
+    switch($type){
+        case 'image':
+            $msgName='[图片]';
+            break;
+        case 'voice':
+            $msgName='[语音]';
+            break;
+        case 'video':
+            $msgName='[视频]';
+            break;
+        case 'file':
+            $msgName='[文件]';
+            break;
+        case 'webrtc':
+            if($callVideo){
+                $msgName='[正在请求与您视频通话]';
+            }else{
+                $msgName='[正在请求与您语音通话]';
+            }
+            break;
+    }
+    return $msgName;
+}
+
+// 获取app的下载链接
+function getAppDowmUrl($platform='andriod'){
+    $config=config('version.'.$platform);
+    $name=config('version.app_name');
+    if($platform=='windows'){
+        $packageName=$name."_Setup_".$config['version'].".exe";
+        $path="/downloadApp/windows";
+    }elseif($platform=='mac'){
+        $packageName=$name."_Setup_".$config['version'].".dmg";
+        $path="/downloadApp/mac";
+    }else{
+        $packageName=$name."_Setup_".$config['version'].".apk";
+        $path="/downloadApp/andriod";
+    }
+    if(is_file(PACKAGE_PATH . $packageName)){
+        return rtrim(request()->domain(),'/').$path;
+    }else{
+        return '';
+    }
+}

+ 105 - 0
app/common/controller/Api.php

@@ -0,0 +1,105 @@
+<?php
+
+namespace app\common\controller;
+
+use think\App;
+use app\enterprise\model\{User,Group};
+use app\index\controller\Extension;
+use think\facade\Session;
+use think\facade\Cache;
+use think\facade\Db;
+use GatewayClient\Gateway;
+use app\manage\model\Config;
+use thans\jwt\facade\JWTAuth;
+
+
+/**
+ * API接口类
+ */
+class Api
+{
+    /**
+     * Request实例
+     * @var \think\Request
+     */
+    protected $request;
+
+    /**
+     * 应用实例
+     * @var \think\App
+     */
+    protected $app;
+
+    protected $middleware=['apiAuth'];
+
+    /**
+     * 构造方法
+     * @access public
+     * @param  App  $app  应用对象
+     */
+    public function __construct(App $app)
+    {
+        $this->app     = $app;
+        $this->request = $this->app->request;
+    }
+
+    // 创建用户
+    public function createUser()
+    {
+        $data = $this->request->param();
+        if(!isset($data['account']) || !isset($data['realname'])){
+            return warning('缺少参数');
+        }
+        $user=new User();
+        $verify=$user->checkAccount($data);
+        if(!$verify){
+            return success('账号已存在');
+        }
+        $salt=\utils\Str::random(4);
+        $data['password'] = password_hash_tp(rand(100000,999999),$salt);
+        $data['salt'] =$salt;
+        $data['register_ip'] =$this->request->ip();
+        $data['name_py'] = pinyin_sentence($data['realname']);
+        $user->save($data);
+        $data['user_id']=$user->user_id;
+        $data['open_id']=encryptIds($user->user_id);
+        // 监听用户注册后的操作
+        event('UserRegister',$data);
+        return success('注册成功', $data);
+    }
+
+    // 用户登录
+    public function login()
+    {
+        $param=$this->request->param();
+        $isMobile=$param['is_mobile'] ?? false;
+        if(!isset($param['account']) || !isset($param['open_id'])){
+            return warning('缺少参数');
+        }
+        $userInfo=User::where(['account'=> $param['account']])->withoutField('register_ip,login_count,update_time,create_time')->find();
+        if(!$userInfo){
+            return warning('当前用户不存在!');
+        }
+        try{
+            $hash_id=decryptIds($param['open_id']);
+            if($hash_id!=$userInfo['user_id']){
+                return warning('当前用户不存在!');
+            }
+        }catch (\Exception $e){
+            return error($e->getMessage());
+        }
+        $md5=md5(json_encode($userInfo));
+        // 将用户信息缓存5分钟
+        Cache::set($md5,$userInfo,300);
+        // 生成Url
+        if($isMobile){
+            $url=rtrim(request()->domain(),'/').'/h5/#/pages/login/index?token='.$md5;
+        }else{
+            $url=rtrim(request()->domain(),'/').'/#/login?token='.$md5;
+        }
+        return success('登录成功',$url);
+        
+    }
+   
+    
+}

+ 354 - 0
app/common/controller/Pub.php

@@ -0,0 +1,354 @@
+<?php
+
+namespace app\common\controller;
+
+use think\App;
+use app\enterprise\model\{User,Group};
+use app\index\controller\Extension;
+use think\facade\Session;
+use think\facade\Cache;
+use think\facade\Db;
+use GatewayClient\Gateway;
+use app\manage\model\Config;
+use thans\jwt\facade\JWTAuth;
+
+
+/**
+ * 控制器基础类
+ */
+class Pub
+{
+    /**
+     * Request实例
+     * @var \think\Request
+     */
+    protected $request;
+
+    /**
+     * 应用实例
+     * @var \think\App
+     */
+    protected $app;
+
+    /**
+     * 构造方法
+     * @access public
+     * @param  App  $app  应用对象
+     */
+    public function __construct(App $app)
+    {
+        Gateway::$registerAddress = config('gateway.registerAddress');
+        $this->app     = $app;
+        $this->request = $this->app->request;
+
+        // 控制器初始化
+        // $this->initialize();
+    }
+
+    public function login(){
+        $param=request()->param();
+        $token=$param['token'] ?? '';
+        // token一键登录
+        if($token){
+            $apiStatus=config('app.api_status');
+            if(!$apiStatus){
+                return warning('接口已关闭');
+            }
+            $userInfo=Cache::get($token);
+            if(!$userInfo){
+                return warning('TOKEN已失效!');
+            }
+        }else{
+            $userInfo=User::where(['account'=> $param['account']])->withoutField('register_ip,login_count,update_time,create_time')->find();
+            if($userInfo==null){
+                return warning('当前用户不存在!');
+            }
+            if($userInfo['status']==0){
+                return warning('您的账号已被禁用');
+            }
+            $password=password_hash_tp($param['password'],$userInfo['salt']);
+            $code=$param['code'] ?? '';
+            if($code){
+                if($code!=Cache::get($param['account'])){
+                    return warning('验证码错误!');
+                }
+                Cache::delete($param['account']);
+            }else{
+                if($password!=$userInfo['password']){
+                    return warning('密码错误!');
+                }
+            }
+        }
+        $userInfo['avatar']=avatarUrl($userInfo['avatar'],$userInfo['realname'],$userInfo['user_id']);
+        //    如果用户已经有设置
+        $setting=$userInfo['setting'] ?: '';
+        if($setting){
+            $setting['hideMessageName']= $setting['hideMessageName']=='true' ? true : false;
+            $setting['hideMessageTime']= $setting['hideMessageTime']=='true' ? true : false;
+            $setting['avatarCricle']= $setting['avatarCricle']=='true' ? true : false;
+            $setting['isVoice']= $setting['isVoice']=='true' ? true : false;
+            $setting['sendKey']=(int)$setting['sendKey'];
+            $userInfo['setting']=$setting;
+        }
+        //如果登录信息中含有client——id则自动进行绑定
+        $client_id=$this->request->param('client_id');
+        if($client_id){
+            $cid=$this->request->header('cid','');
+            $this->doBindUid($userInfo['user_id'],$client_id,$cid);
+        }
+        $update=[
+            'last_login_time'=>time(),
+            'last_login_ip'=>$this->request->ip(),
+            'login_count'=>Db::raw('login_count+1')
+        ];
+        User::where('user_id',$userInfo['user_id'])->update($update);
+        $userInfo['qrUrl']=request()->domain().'/scan/u/'.encryptIds($userInfo['user_id']);
+        unset($userInfo['password'],$userInfo['salt']);
+        $userInfo['displayName']=$userInfo['realname'];
+        $userInfo['id']=$userInfo['user_id'];
+        $authToken=User::refreshToken($userInfo,$param['terminal'] ?? 'web');
+        $data=[
+            'sessionId'=>Session::getId(),
+            'authToken'=>$authToken,
+            'userInfo'=>$userInfo
+        ];
+        return success('登录成功!',$data);
+   }
+
+    //退出登录
+    public function logout(){
+        try {
+            $jwtData = JWTAuth::auth();
+        } catch (\Exception $e) {
+            return success('退出成功!');
+        }
+
+        $userInfo = $jwtData['info']->getValue();
+        //解密token中的用户信息
+        $userInfo = str_encipher($userInfo,false, config('app.aes_token_key'));
+
+        if (!$userInfo) {
+            return success('退出成功!');
+        }
+        //解析json
+        $userInfo = (array)json_decode($userInfo, true);
+        if($userInfo){
+            $client_id=$this->request->param('client_id','');
+            if($client_id){
+                Gateway::unbindUid($client_id,$userInfo['user_id']);
+            }
+            wsSendMsg(0,'isOnline',['id'=>$userInfo['user_id'],'is_online'=>0]);
+        }
+        JWTAuth::invalidate(JWTAuth::token()->get());
+        return success('退出成功!');
+    }
+
+    // 注册用户
+    public function register(){
+        try{
+            $data = $this->request->param();
+            $systemInfo=Config::getSystemInfo();
+            // 判断系统是否开启注册
+            if($systemInfo['sysInfo']['regtype']==2){
+                $inviteCode=$data['inviteCode'] ?? '';
+                if(!$inviteCode){
+                    return warning('当前系统已关闭注册功能!');
+                }
+                if(!Cache::get($inviteCode)){
+                    return warning('邀请码已失效!');
+                }
+            }
+            $code=$data['code'] ?? '';
+            if($code){
+                if($code!=Cache::get($data['account'])){
+                    return warning('验证码错误!');
+                }
+                Cache::delete($data['account']);
+            }
+            $user=new User();
+            $verify=$user->checkAccount($data);
+            if(!$verify){
+                return warning($user->getError());
+            }
+            $salt=\utils\Str::random(4);
+            $data['password'] = password_hash_tp($data['password'],$salt);
+            $data['salt'] =$salt;
+            $data['register_ip'] =$this->request->ip();
+            $data['name_py'] = pinyin_sentence($data['realname']);
+            $user->save($data);
+            $data['user_id']=$user->user_id;
+            // 监听用户注册后的操作
+            event('UserRegister',$data);
+            return success('注册成功', $data);
+        }catch (\Exception $e){
+            return error($e->getMessage());
+        }
+
+    }
+
+    //头像生成
+    public function avatar(){
+        circleAvatar(input('str'),input('s')?:80,input('uid'));die;
+    }
+
+    /**
+     * 将用户UId绑定到消息推送服务中
+     * @return \think\response\Json
+     */
+    public function bindUid(){
+        $client_id=$this->request->param('client_id');
+        $user_id=$this->request->param('user_id');
+        $cid=$this->request->param('cid','');
+        try{
+            $this->doBindUid($user_id,$client_id,$cid);
+        }catch(\Exception $e){
+            // 未找到用户
+        }
+        return success('');
+    }
+
+    // 执行绑定
+    public function doBindUid($user_id,$client_id,$cid=''){
+        // 如果当前ID在线,将其他地方登陆挤兑下线
+        if(Gateway::isUidOnline($user_id)){
+            wsSendMsg($user_id,'offline',['id'=>$user_id,'client_id'=>$client_id,'isMobile'=>$this->request->isMobile()]);
+        }
+        Gateway::bindUid($client_id, $user_id);
+        // 查询团队,如果有团队则加入团队
+        $group=Group::getMyGroup(['gu.user_id'=>$user_id,'gu.status'=>1]);
+        if($group){
+            $group=$group->toArray();
+            $group_ids=arrayToString($group,'group_id',false);
+            foreach($group_ids as $v){
+                Gateway::joinGroup($client_id, $v); 
+            }
+        }
+        if($cid){
+            bindCid($user_id,$cid);
+        }
+        wsSendMsg(0,'isOnline',['id'=>$user_id,'is_online'=>1]);
+    }
+
+    // 下线通知
+    public function offline(){
+        $user_id=input('user_id');
+        try{
+            $client_ids=Gateway::getClientIdByUid($user_id);
+            // 一个终端登录时才发送下线通知
+            if(count($client_ids)<2){
+                wsSendMsg(0,'isOnline',['id'=>$user_id,'is_online'=>0]);
+            }
+        }catch(\Exception $e){
+            // 未找到用户
+        }
+        return success('');
+    }
+  
+ /**
+     * 将用户团队绑定到消息推送服务中
+     * @return \think\response\Json
+     */
+    public function bindGroup(){
+        $client_id=input('client_id');
+        $group_id=input('group_id');
+        $group_id = explode('-', $group_id)[1];
+        Gateway::joinGroup($client_id, $group_id); 
+        return success('');
+    }
+
+    // 获取系统配置信息
+    public function getSystemInfo(){
+        $systemInfo=Config::getSystemInfo();
+        $systemInfo['demon_mode']=env('app.demon_mode',false);
+        return success('',$systemInfo);
+    }
+
+    // 发送验证码
+    public function sendCode(){
+        $account=$this->request->param('account');
+        $type=$this->request->param('type',1);
+        if(in_array($type,[3,4]) && !$account){
+            $userInfo=request()->userInfo;
+            $acType=\utils\Regular::check_account($userInfo['account']);
+            if($acType){
+                $account=$userInfo['account'];
+            }else{
+                $account=$userInfo['email'];
+            }
+        };
+        $acType=\utils\Regular::check_account($account);
+        if(!$acType){
+            return warning('账户必须为手机号或者邮箱');
+        }
+        if(Cache::get($account.'_time')) return warning('请一分钟后再试!');
+        if($type==1){
+            $text='登录账户';
+            $actions="login";
+        }elseif($type==2){
+            $text='注册账户';
+            $actions="register";
+        }elseif($type==3){
+            $text='修改密码';
+            $actions="changePassword";
+        }else{
+            $text="修改账户";
+            $actions="changeUserinfo";
+        }
+        $code=rand(100000,999999);
+        Cache::set($account,$code,300);
+        Cache::set($account.'_time',$code,60);
+        if($acType==2){
+            $conf=Config::where(['name'=>'smtp'])->value('value');
+            $conf['temp']='code';
+            $mail=new \mail\Mail($conf);
+            $mail->sendEmail([$account],$text,$code);
+            return success('发送成功');
+        }else{
+            $parmes=[
+                'code'=>$code
+            ];
+            $res=sendSms($account,$actions,$parmes);
+            return success($res['msg']);
+        }
+    }
+
+    // 检查app版本升级
+    public function checkVersion(){
+        $oldRelease=$this->request->param('release',0);
+        $setupPage=$this->request->param('setupPage',false);
+        $platform=$this->request->param('platform',1101);
+        $config=Config::where('name','sysInfo')->value('value');
+        if($platform==1101){
+            $teminal='andriod';
+        }else{
+            $teminal='ios';
+        }
+        $versionInfo=config('version.'.$teminal);
+        $data=[
+            'versionName'=>$versionInfo['version'],
+            'versionCode'=>$versionInfo['release'],
+            'updateType'=>$versionInfo['update_type'],
+            'versionInfo'=>$versionInfo['update_info'],
+            'downloadUrl'=>'',
+        ];
+        // 是否手动检测更新,是的话就不能强制更新或者静默更新
+        if($setupPage){
+            $data['updateType']='solicit';
+        }
+        // 如果旧版本大于等于当前版本则不更新
+        if($oldRelease>=$versionInfo['release']){
+            return success('',$data);
+        }
+        $downUrl='';
+        $andriod=getAppDowmUrl('andriod');
+        // 如果是ios则返回ios地址
+        if($platform==1101){
+            $downUrl=env('app.andriod_webclip','') ? : $andriod;
+        }else{
+            $downUrl=env('app.ios_webclip','');
+        }
+        $data['downloadUrl']=$downUrl;
+        return success('',$data);
+    }
+    
+}

+ 234 - 0
app/common/controller/Upload.php

@@ -0,0 +1,234 @@
+<?php
+/**
+ * lvzheAdmin [a web admin based ThinkPHP5]
+ * @author    xiekunyu<raingad@foxmail.com>
+ */
+namespace app\common\controller;
+
+use app\BaseController;
+use app\enterprise\model\{File as FileModel,Message,User}; 
+use app\manage\model\{Config}; 
+use think\facade\Filesystem;
+use think\facade\Request;
+use think\File;
+class Upload extends BaseController
+{
+    protected $middleware = ['checkAuth'];
+    protected $disk='';
+    protected $url='';
+
+    public function __construct()
+    {
+        parent::__construct(app());
+        $this->disk=env('filesystem.driver','local');
+        $this->url=getDiskUrl().'/';
+        
+    }
+
+    /**
+     * 文件上传
+     */
+    public function upload($data,$path,$prefix = "",$fileObj = true)
+    {
+        $message=$data['message'] ?? '';
+        if($message){
+            $message=json_decode($message,true);
+        }
+        $uid=request()->userInfo['user_id'];
+        if($fileObj){
+            $filePath = $path;
+        }else{
+            $filePath = new File($path);
+        }
+        $info=$this->getFileInfo($filePath,$path,$fileObj);
+        if($info['ext']=='' && $message){
+            $pathInfo        = pathinfo($message['fileName'] ?? '');
+            $info['ext']     = $pathInfo['extension'];
+            $info['name']    =$message['fileName'] ?? '';
+        }
+        $conf=Config::where(['name'=>'fileUpload'])->value('value');
+        if($conf['size']*1024*1024 < $info['size']){
+            return shutdown('文件大小超过限制');
+        }
+        // 兼容uniapp文件上传
+        if($info['ext']=='' && isset($data['ext'])){
+            $info['ext']=$data['ext'];
+        }
+        $info['ext']=strtolower($info['ext']);
+        if(!in_array($info['ext'],$conf['fileExt'])){
+            return shutdown('文件格式不支持');
+        }
+        $fileType=getFileType($info['ext']);
+        if($fileType==2){
+            $filecate="image";
+        }elseif($fileType==3){
+            $msgType=$message['type'] ?? '';
+            // 如果是语音消息,类型才为语音,否者为文件,主要是兼容发送音频文件
+            if($msgType=='voice'){
+                $filecate="voice";
+            }else{
+                $filecate="file";
+            }
+        }elseif($fileType==4){
+            $filecate="video";
+        }else{
+            $filecate="file";
+        }
+        if(!$prefix){
+            $prefix=$filecate.'/'.$uid.'/'.date('Y-m-d')."/";
+        }
+        $name=str_replace('.'.$info['ext'],'',$info['name']);
+        $file=FileModel::where(['md5'=>$info['md5']])->find();
+        // 判断文件是否存在,如果有则不再上传
+        if(!$file){
+            $newName   = uniqid() . '.' . $info['ext'];
+            $object = $prefix . $newName;
+            if($this->disk=='local'){
+                $object='storage/'.$object;
+            }
+            Filesystem::disk($this->disk)->putFileAs($prefix, $filePath, $newName);
+        }else{
+            $object = $file['src'];
+        }
+        // 把左边的/去掉再加上,避免有些有/有些没有
+        $object='/'.ltrim($object,'/');
+        $ret = [
+            "src"      => $object,
+            "name"     => $name,
+            "cate" => $fileType,
+            "size"     => $info['size'],
+            "md5"     => $info['md5'],
+            "file_type"     => $info['mime'],
+            "ext"     => $info['ext'],
+            "type"     =>2,
+            'user_id'=>$uid,
+        ];
+        
+        if($message){
+            // 自动获取视频第一帧,视频并且是使用的阿里云
+            if($message['type']=='video'){
+                if($this->disk=='aliyun'){
+                    $message['extends']['poster']=$this->url.$ret['src'].'?x-oss-process=video/snapshot,t_1000,m_fast,w_800,f_png';
+                }else{
+                    $message['extends']['poster']='https://im.file.raingad.com/static/image/video.png';
+                }
+            }
+            // 如果发送的文件是图片、视频、音频则将消息类型改为对应的类型
+            if(in_array($fileType,[2,3,4])){
+                $message['type']=$filecate;
+            }
+            if($message['type']=='image'){
+                $extends=$this->getImageSizeInfo($info['path']);
+                $message['extends']=$extends;
+            }
+            $newFile=new FileModel;
+            // 录音就不保存了
+            if($message['type']!='voice'){
+                $newFile->save($ret);
+            }
+            $message['content']=$ret['src'];
+            $message['file_id']=$newFile->file_id ?? 0;
+            $message['file_cate']=$fileType;
+            $message['file_size']=$info['size'];
+            $message['file_name']= $name.'.'.$info['ext'];
+            $message['user_id']= $uid;
+            $data=Message::sendMessage($message);
+            return $data;
+        }else{
+            return $ret;
+        }
+        
+    }
+
+    // 上传一般文件
+    public function uploadFile(){
+        $param=$this->request->param();
+        try{
+            $file=request()->file('file');
+            $info=$this->upload($param,$file);
+            return success("上传成功",$info);
+        } catch(\Exception $e) {
+            return error($e->getMessage());
+        }
+    }
+
+
+    // 获取上传文件的信息
+    protected function getFileInfo($file,$path,$isObj=false){
+        $info= [
+            'path'=>$file->getRealPath(),
+            'size'=>$file->getSize(),
+            'mime'=>$file->getMime(),
+            'ext'=>$file->extension(),
+            'md5'=>$file->md5(),
+        ];
+        if($isObj){
+            $info['name']=$file->getOriginalName();
+        }else{
+            // 根据路径获取文件名
+            $pathInfo        = pathinfo($path);
+            $info['name']    = $pathInfo['basename'];
+        }
+        return $info;
+        
+    }
+
+    // 上传图片
+    public function uploadImage(){
+        $param=request::param();
+        try{
+            $file=request()->file('file');
+            $info=$this->upload($param,$file,'image/'.date('Y-m-d').'/');
+            $url=$this->url.$info['src'];
+            return success("上传成功",$url);
+        } catch(\Exception $e) {
+            return error($e->getMessage());
+        }
+    }
+
+    // 普通上传头像
+    public function uploadAvatar(){
+        $param=request::param();
+        try{
+            $file=request()->file('file');
+            $uid=request()->userInfo['user_id'];
+            $info=$this->upload($param,$file,'avatar/'.$uid.'/');
+            User::where(['user_id'=>$uid])->update(['avatar'=>$info['src']]);
+            $url=$this->url.$info['src'];
+            return success("上传成功",$url);
+        } catch(\Exception $e) {
+            return error($e->getMessage());
+        }
+    }
+
+    // 服务器上传头像
+    public function uploadLocalAvatar($file,$param,$uid){
+        try{
+            $info=$this->upload($param,$file,'avatar/'.$uid.'/',false);
+            return $info['src'];
+        } catch(\Exception $e) {
+            return $e->getMessage().$e->getLine();
+        }
+    }
+
+    // 获取图片的尺寸
+    protected function getImageSizeInfo($file){
+        $extends=[];
+        // 如果图片获取图片的尺寸
+        $imageSize = getimagesize($file);
+        $extends['width']=$imageSize[0];
+        $extends['height']=$imageSize[1];
+        // 如果宽大于高则为横图,宽度填充模式,否则为竖图,高度填充模式
+        if($imageSize[0]>=$imageSize[1]){
+            $extends['fixMode']=1;  // 宽度填充
+        }else{
+            $extends['fixMode']=2;  // 高度填充
+        }
+        if($imageSize[0]<200 && $imageSize[1]<240){
+            $extends['fixMode']=3;    // 小图
+        }
+        return $extends;
+    }
+
+
+}

+ 13 - 0
app/common/listener/UserRegister.php

@@ -0,0 +1,13 @@
+<?php
+namespace app\common\listener;
+
+use app\enterprise\model\{User};
+
+// 监听用户注册后的操作
+class UserRegister
+{
+
+    public function handle(User $user,$data){
+
+    }
+}

+ 35 - 0
app/common/middleware/ApiAuth.php

@@ -0,0 +1,35 @@
+<?php
+namespace app\common\middleware;
+
+//验证权限
+class ApiAuth
+{
+    public function handle($request, \Closure $next)
+    {
+        $apiStatus=config('app.api_status');
+        if(!$apiStatus){
+            return shutdown('接口已关闭');
+        }
+        $appId=config('app.app_id');
+        $appSecret=config('app.app_secret');
+        $header = $request->header();
+        $app_id=$header['x-im-appid'] ?? '';
+        $timeStamp=$header['x-im-timestamp'] ?? 0;
+        $sign=$header['x-im-sign'] ?? '';
+        if(!$app_id || !$timeStamp || !$sign){
+            return shutdown('缺少参数');
+        }
+        // 时间戳不能大约60秒
+        if(time()-$timeStamp>60){
+            return shutdown('请求超时');
+        }
+        if($appId!=$app_id){
+            return shutdown('appId错误');
+        }
+        $signStr=md5($appId.$timeStamp.$appSecret);
+        if($sign!=$signStr){
+            return shutdown('签名错误');
+        }
+        return $next($request);
+    }
+}

+ 45 - 0
app/common/middleware/CheckAuth.php

@@ -0,0 +1,45 @@
+<?php
+namespace app\common\middleware;
+
+use Exception;
+use thans\jwt\exception\TokenInvalidException;
+use thans\jwt\facade\JWTAuth;
+//验证权限
+class CheckAuth
+{
+    public function handle($request, \Closure $next)
+    {
+        try {
+            $jwtData = JWTAuth::auth();
+        } catch (Exception $exception) {
+
+            //token有误
+            if (get_class($exception) == TokenInvalidException::class) {
+                return shutdown('登陆信息有误 请重新登录', -1);
+            }
+
+            $errorMsgArr = [
+                'Must have token' => '请先登陆系统',
+                'The token is in blacklist.' => '登陆已失效 请重新登陆',
+                'The token is expired.' => '登陆已过期 请重新登陆',
+                'The token is in blacklist grace period list.' => '登陆已过期 请重新登陆'
+            ];
+            return shutdown($errorMsgArr[$exception->getMessage()] ?? $exception->getMessage(), -1);
+        }
+
+        $userInfo = $jwtData['info']->getValue();
+        //解密token中的用户信息
+        $userInfo = str_encipher($userInfo,false, config('app.aes_token_key'));
+
+        if (!$userInfo) {
+            return shutdown('用户信息有误,请重新登陆', -1);
+        }
+        //解析json
+        $userInfo = (array)json_decode($userInfo, true);
+        //已经登陆,将用户信息存入请求头
+        $request->userInfo  = $userInfo;
+        $request->uid       = $userInfo['id'];
+        $request->userToken = JWTAuth::token()->get();
+        return $next($request);
+    }
+}

+ 38 - 0
app/common/middleware/ManageAuth.php

@@ -0,0 +1,38 @@
+<?php
+namespace app\common\middleware;
+//验证权限
+class ManageAuth
+{
+    public function handle($request, \Closure $next)
+    {
+        
+        // 设置演示模式,演示模式下无法修改配置
+        $request->demonMode=env('app.demon_mode',false);
+        if(!$request->demonMode){
+            if($request->userInfo['user_id']!=1 && $request->userInfo['role']!=2){
+                shutdown('您没有权限访问该接口',-1);
+            }
+        }else{
+            $rules=[
+                'user/add',
+                'user/edit',
+                'user/del',
+                'user/setrole',
+                'user/setstatus',
+                'user/editpassword',
+                'group/del',
+                'group/changeowner',
+                'group/delgroupuser',
+                'task/starttask',
+                'task/stoptask',
+                'config/setconfig'
+            ];
+            // 获取pathinfo信息
+            $pathinfo = strtolower($request->pathinfo());
+            if(in_array($pathinfo,$rules)){
+                 return shutdown('演示模式下无法操作!',400);
+            }
+        }
+        return $next($request);
+    }
+}

+ 67 - 0
app/common/task/ClearMessage.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace app\common\task;
+
+use yunwuxin\cron\Task;
+use think\Exception;
+use app\manage\model\{Config};
+use app\enterprise\model\Message;
+
+// 自动清理消息定时任务
+class ClearMessage extends Task
+{
+    
+    //    定时任务日志内容
+    protected $content='';
+    protected $path='';
+    protected $daytime=86400;
+
+    /**
+     * 自动写入定时任务日志
+     * @return \think\response\Json
+     */
+    protected function writeLog($text)
+    {
+        $this->path = root_path() . 'crontab.txt';
+
+        $content = '重置中!';
+        if (!file_exists($this->path)) {
+            fopen($this->path, 'w');
+        }
+        if (date('d') != 10) {
+            $content = file_get_contents($this->path);
+        }
+        file_put_contents($this->path, $content . date('Y-m-d H:i:s') . ':' . $text . PHP_EOL);
+    }
+
+    public function configure()
+    {
+        //设置每天2点执行
+        $this->dailyAt('02:00'); 
+    }
+
+    /**
+     * 执行任务
+     * @return mixed
+     */
+    protected function execute()
+    {
+        if(date('H:i')!='02:00'){
+            return false;
+        }
+        try {
+           $config=Config::getSystemInfo();
+           $status=$config['chatInfo']['msgClear'] ?? false;
+           $days=$config['chatInfo']['msgClearDay'] ?? 0;
+           if($status && $days){
+                $time=time() - ($days * $this->daytime);
+                $where[]=['create_time','<',$time];
+                $where[]=['is_last','=',0];
+                Message::where($where)->delete();
+           }
+           print "****************消息清理成功******************\n";
+        } catch (Exception $e) {
+            print '消息清理失败:'.$e->getMessage()."\n";
+        }
+    }
+}

+ 52 - 0
app/common/task/SetAtRead.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace app\common\task;
+
+use yunwuxin\cron\Task;
+use think\Exception;
+use think\facade\Cache;
+use app\manage\model\{Config};
+use app\enterprise\model\Message;
+
+// 自动清理消息定时任务
+class SetAtRead extends Task
+{
+    
+    //    定时任务日志内容
+    protected $content='';
+    protected $path='';
+    protected $daytime=86400;
+
+    public function configure()
+    {
+        //设置每天8点执行
+        $this->everyMinute(); 
+    }
+
+    /**
+     * 执行任务
+     * @return mixed
+     */
+    protected function execute()
+    {
+        try {
+           $atListQueue=Cache::get('atListQueue');
+            if($atListQueue){
+                foreach ($atListQueue as $key=>$val){
+                    $message=Message::where('msg_id',$key)->value('at');
+                    $atList=($message ?? null) ? explode(',',$message): [];
+                    // 两个数组取差集
+                    $uniqueArr=array_unique($val);
+                    $newAtList = array_filter($atList, function ($value) use ($uniqueArr) {
+                        return !in_array($value, $uniqueArr);
+                    });
+                    Message::where('msg_id',$key)->update(['at'=>implode(',',$newAtList)]);
+                }
+                Cache::delete('atListQueue');
+            }
+            print "****************设置已读成功******************\n";
+        } catch (Exception $e) {
+            print '设置已读失败:'.$e->getMessage()."\n";
+        }
+    }
+}

+ 92 - 0
app/enterprise/controller/Files.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace app\enterprise\controller;
+
+use app\BaseController;
+
+use app\enterprise\model\{File,User,Message};
+use think\facade\View;
+class Files extends BaseController
+{
+    // 文件列表
+    public function index()
+    {
+        $param = $this->request->param();
+        $is_all = $param['is_all'] ?? 0;
+        $map = [];
+        $data=[];
+        // 如果是查询全部,就查询file表,否则查询message表
+        if ($is_all) {
+            if ($param['cate'] ?? 0) {
+                $map[] = ['cate', '=', $param['cate']];
+            }
+            $model = new File();
+            if ($param['keywords'] ?? '') {
+                $model = $model->where('name', 'like', '%' . $param['keywords'] . '%');
+            }
+            $list = $this->paginate($model->where($map)->order('file_id desc'));
+            
+            if ($list) {
+                $data = $list->toArray()['data'];
+                $userList = User::matchUser($data, true, 'user_id', 120);
+                foreach ($data as $k => $v) {
+                    $url=getFileUrl($v['src']);
+                    $data[$k]['src'] =$url;
+                    $data[$k]['preview'] = previewUrl($url);
+                    $data[$k]['extUrl'] = getExtUrl($v['src']);
+                    $data[$k]['name'] = $v['name'].'.'.$v['ext'];
+                    $data[$k]['msg_type'] = getFileType($v['ext'],true);
+                    $data[$k]['user_id_info'] = $userList[$v['user_id']] ?? [];
+                    $data[$k]['download'] = request()->domain().'/filedown/'.encryptIds($v['file_id']);
+                }
+                
+            }
+        } else {
+            $map = [
+               ['file_id', '>', 0],
+               ['type', '<>', 'voice'],
+               ['is_group', '=', 0],
+               ['is_undo', '=', 0],
+            ];
+            if ($param['cate'] ?? 0) {
+                $map[] = ['file_cate', '=', $param['cate']];
+            }
+            $user_id = $this->uid;
+            $model = new Message();
+            if ($param['keywords'] ?? '') {
+               $map[] = ['file_name', 'like', '%' . $param['keywords'] . '%'];
+            }
+            $role = $param['role'] ?? 0;
+            $where=[];
+            if($role==1){
+               $map[] = ['from_user', '=', $user_id];
+            }elseif($role==2){
+               $map[] = ['to_user', '=', $user_id];
+            }else{
+                $where='(from_user='.$user_id.' or to_user='.$user_id.')';
+            }
+
+            $list = $this->paginate($model->where($map)->where($where)->order('create_time desc'));
+            if ($list) {
+                $data = $list->toArray()['data'];
+                $userList = User::matchUser($data, true, 'from_user', 120);
+                foreach ($data as $k => $v) {
+                    $content=str_encipher($v['content'],false);
+                    $url=getFileUrl($content);
+                    $data[$k]['src'] = $url;
+                    $data[$k]['preview'] = previewUrl($url);
+                    $data[$k]['extUrl'] = getExtUrl($content);
+                    $data[$k]['cate'] = $v['file_cate'];
+                    $data[$k]['name'] = $v['file_name'];
+                    $data[$k]['size'] = $v['file_size'];
+                    $data[$k]['msg_type'] = $v['type'];
+                    $ext=explode('.',$content);
+                    $data[$k]['ext'] = end($ext);
+                    $data[$k]['user_id_info'] = $userList[$v['from_user']] ?? [];
+                    $data[$k]['download'] = request()->domain().'/filedown/'.encryptIds($v['file_id']);
+                }
+            }
+        }
+        return success('', $data, $list->total(), $list->currentPage());
+    }
+}

+ 171 - 0
app/enterprise/controller/Friend.php

@@ -0,0 +1,171 @@
+<?php
+
+namespace app\enterprise\controller;
+
+use app\BaseController;
+
+use app\enterprise\model\{Friend as FriendModel,User};
+
+class Friend extends BaseController
+{
+    // 好友申请列表
+    public function index()
+    {
+        $param = $this->request->param();
+        $map = [];
+        $map[]=['is_invite','=',1];
+        $isMine=$param['is_mine'] ?? 0;
+        if($isMine){
+            // 我发起的
+            $map[]=['create_user','=',$this->uid];
+        }else{
+            // 我收到的
+            $map[]=['friend_user_id','=',$this->uid];
+        }
+        $data=[];
+        $model = new FriendModel();
+        $list = $this->paginate($model->where($map)->order('friend_id desc'));
+        if ($list) {
+            $data = $list->toArray()['data'];
+            $userList = User::matchUser($data, true, ['create_user','friend_user_id'], 120);
+            foreach ($data as $k => $v) {
+                $data[$k]['create_user_info'] = $userList[$v['create_user']] ?? [];
+                $data[$k]['user_id_info'] = $userList[$v['friend_user_id']] ?? [];
+                $data[$k]['is_group'] = 0;
+            }
+        }
+        return success('', $data,$list->total(),$list->currentPage());
+    }
+
+    // 添加好友
+    public function add()
+    {
+        $param = $this->request->param();
+        $user_id=$param['user_id'] ?? 0;
+        if($user_id==$this->uid){
+            return warning('不能添加自己为好友');
+        }
+        $friend=FriendModel::where(['friend_user_id'=>$user_id,'create_user'=>$this->uid])->find();
+        if($friend){
+            if($friend->status==1){
+                return warning('你们已经是好友了');
+            }elseif($friend->status==2){
+                return warning('你已经申请过了,请等待对方同意');
+            }
+        }
+        $status=2;
+        $otherFriend=FriendModel::where(['friend_user_id'=>$this->uid,'create_user'=>$user_id])->find();
+        if($otherFriend){
+            if($otherFriend->status>0){
+                $status=1;
+            }
+        }
+        $model = new FriendModel();
+        $data=[
+            'friend_user_id'=>$user_id,
+            'status'=>$status,
+            'create_user'=>$this->uid,
+            'remark'=>$param['remark'],
+            'is_invite'=>1 // 是否为发起方
+        ];
+        $model->save($data);
+        $msg=[
+            'fromUser'=>[
+                'id'=>'system',
+                'displayName'=>'新朋友',
+                'avatar'=>'',
+            ],
+            'toContactId'=>'system',
+            'id'=>uniqid(),
+            'is_group'=>2,
+            'content'=>"添加您为好友",
+            'status'=>'succeed',
+            'sendTime'=>time()*1000,
+            'type'=>'event',
+            'fileSize'=>0,
+            'fileName'=>'',
+        ];
+        // 发送好友申请
+        wsSendMsg($user_id,'simple',$msg);
+        return success('添加成功');
+    }
+
+    // 接受或者拒绝好友申请
+    public function update()
+    {
+        $param = $this->request->param();
+        $friend=FriendModel::find($param['friend_id']);
+        if(!$friend){
+            return warning('申请不存在');
+        }
+        $map=[
+            'friend_id'=>$param['friend_id']
+        ];
+        FriendModel::where($map)->update(['status'=>$param['status']]);
+        // 如果是接收,就添加到好友列表
+        if($param['status']){
+            $data=[
+                'friend_user_id'=>$friend->create_user,
+                'create_user'=>$this->uid,
+            ];
+            $newFriend=FriendModel::where($data)->find();
+            if($newFriend){
+                FriendModel::where($data)->update(['status'=>1]);
+                return success('你们已经是好友了');
+            }else{
+                $data['status']=1;
+                FriendModel::create($data);
+            }
+            // 将对方的信息发送给我,把我的信息发送对方
+            $user=User::setContact($friend->create_user);
+            if($user){
+                wsSendMsg($this->uid,'appendContact',$user);
+            }
+            $myInfo=User::setContact($this->uid);
+            if($myInfo){
+                wsSendMsg($friend->create_user,'appendContact',$myInfo);
+            }
+            
+        }
+        return success('操作成功');
+    }
+
+
+    // 删除好友
+    public function del()
+    {
+        $param = $this->request->param();
+        $map=['friend_user_id'=>$param['id'],'create_user'=>$this->uid];
+        $friend=FriendModel::where($map)->find();
+        if(!$friend){
+            return warning('好友不存在');
+        }
+        // 需要删除双方的好友关系
+        FriendModel::where($map)->delete();
+        FriendModel::where(['friend_user_id'=>$this->uid,'create_user'=>$param['id']])->delete();
+        // 性质和删除群聊一样
+        wsSendMsg($param['id'],'removeGroup',['group_id'=>$this->uid]);
+        return success('删除成功');
+    }
+
+    // 设置好友备注
+    public function setNickname()
+    {
+        $param = $this->request->param();
+        if(!$param['nickname']){
+            return warning('备注不能为空');
+        }
+        FriendModel::update(['nickname'=>$param['nickname']],['friend_id'=>$param['friend_id']]);
+        return success('设置成功');
+    }
+
+    // 获取最新的一条和申请的总数
+    public function getApplyMsg(){
+        $model = new FriendModel();
+        $map[]=['friend_user_id','=',$this->uid];
+        $map[]=['status','=',2];
+        $count=$model->where($map)->count();
+        return success('', $count);
+    }
+
+}

+ 408 - 0
app/enterprise/controller/Group.php

@@ -0,0 +1,408 @@
+<?php
+
+namespace app\enterprise\controller;
+
+use app\BaseController;
+use app\enterprise\model\{User,Group as GroupModel,GroupUser,Message};
+use think\Exception;
+use think\facade\Db;
+use app\common\controller\Upload;
+
+class Group extends BaseController
+{
+
+   protected $setting=['manage' => 0, 'invite' => 1, 'nospeak' => 0];
+      // 获取联系人列表
+   public function getAllUser(){
+      $param=$this->request->param();
+      $user_ids=isset($param['user_ids'])?$param['user_ids']:[];
+      $groupId=$param['group_id'] ?? '';
+      $group_id='';
+      if($groupId){
+         $group_id=explode('-',$groupId)[1];
+      }
+      $data=User::getAllUser([['status','=',1],['user_id','<>',$this->userInfo['user_id']]],$user_ids,$this->uid,$group_id);
+      return success('',$data);
+   }
+
+   // 获取群成员
+   public function groupUserList()
+   {
+      $param = $this->request->param();
+      try {
+         $group_id = explode('-', $param['group_id'])[1];
+         $data = GroupUser::getGroupUser(['group_id' => $group_id]);
+         return success('', $data);
+      } catch (Exception $e) {
+         return error($e->getMessage());
+      }
+   }
+
+   // 获取群基本信息
+   public function groupInfo()
+   {
+      $param = $this->request->param();
+      try {
+         $jm='qr';
+         $groupId=$param['group_id'] ?? '';
+         $groupInfo = explode('-', $groupId);
+         $group_id=$groupInfo[1];
+         $group=GroupModel::find($group_id)->toArray();
+         $userList=User::matchUser($group,false,'owner_id');
+         $userCount=GroupUser::where(['group_id'=>$group_id])->count();
+         $userInfo=$userList[$group['owner_id']];
+         $expire=time()+7*86400;
+         $token=urlencode(authcode($this->uid.'-'.$group_id,'ENCODE', $jm,7*86400));
+         $qrUrl=request()->domain().'/scan/g/'.$token;
+         $group['id']=$groupId;
+         $group['qrUrl']=$qrUrl;
+         $group['qrExpire']=date('m月d日',$expire);
+         $group['userInfo']=$userInfo;
+         $group['ownerName']=$userInfo['realname'];
+         $group['groupUserCount']=$userCount;
+         $group['displayName']=$group['name'];
+         $group['avatar']=avatarUrl($group['avatar'],$group['name'],$group['group_id'],120);
+         $group['setting']=$group['setting']?json_decode($group['setting'],true):['manage' => 0, 'invite' => 1, 'nospeak' => 0];
+         $group['isJoin']=GroupUser::where(['group_id'=>$group_id,'user_id'=>$this->uid])->value('role') ?: 0;
+         return success('', $group);
+      } catch (Exception $e) {
+         return error($e->getMessage());
+      }
+   }
+
+   // 修改团队名称
+   public function editGroupName()
+   {
+      $param = $this->request->param();
+      $group_id = explode('-', $param['id'])[1];
+      $role=GroupUser::where(['group_id'=>$group_id,'user_id'=>$this->userInfo['user_id']])->value('role');
+      if($role>2){
+         return warning('你没有操作权限,只有群主和群管理员才可以修改!');
+      }
+      GroupModel::where(['group_id' => $group_id])->update(['name' => $param['displayName'],'name_py'=>pinyin_sentence($param['displayName'])]);
+      $param['editUserName'] = $this->userInfo['realname'];
+      $action='editGroupName';
+      event('GroupChange', ['action' => $action, 'group_id' => $group_id, 'param' => $param]);
+      wsSendMsg($group_id, $action, $param, 1);
+      return success('修改成功');
+   }
+
+   // 添加群成员
+   public function addGroupUser(){
+      $param = $this->request->param();
+      $uid=$this->userInfo['user_id'];
+      $group_id = explode('-', $param['id'])[1];
+      $user_ids=$param['user_ids'];
+      $groupUserCount=GroupUser::where(['group_id'=>$group_id,'status'=>1])->count();
+      if((count($user_ids) + $groupUserCount) > $this->chatSetting['groupUserMax'] && $this->chatSetting['groupUserMax']!=0){
+         return warning("人数不能超过".$this->chatSetting['groupUserMax']."人!");
+      }
+      $data=[];
+      try{
+         foreach($user_ids as $k=>$v){
+            $data[]=[
+               'group_id'=>$group_id,
+               'user_id'=>$v,
+               'role'=>3,
+               'invite_id'=>$uid
+            ];
+         }
+         $groupUser=new GroupUser;
+         $groupUser->saveAll($data);
+         $url=GroupModel::setGroupAvatar($group_id);
+         wsSendMsg($group_id,"addGroupUser",['group_id'=>$param['id'],'avatar'=>$url],1);
+         return success('添加成功');
+      }catch(Exception $e){
+         return error($e->getMessage());
+      }
+   }
+
+      // 设置管理员
+      public function setManager(){
+         $param = $this->request->param();
+         $uid=$this->userInfo['user_id'];
+         $group_id = explode('-', $param['id'])[1];
+         $user_id=$param['user_id'];
+         $role=$param['role'];
+         if(!GroupUser::checkAuth(['group_id'=>$group_id,'user_id'=>$uid])){
+            return warning('您没有操作权限!');
+         }
+         $groupUser=GroupUser::where(['group_id'=>$group_id,'user_id'=>$user_id])->find();
+         if($groupUser){
+            $groupUser->role=$role;
+            $groupUser->save();
+            wsSendMsg($group_id,"setManager",['group_id'=>$param['id']],1);
+            return success('设置成功');
+         }else{
+            return warning('设置失败!');
+         }
+         
+      }
+
+      // 添加群聊
+      public function add(){
+         $param = $this->request->param();
+         $uid=$this->userInfo['user_id'];
+         $user_ids=$param['user_ids'];
+         if($this->chatSetting['groupChat']==0){
+            return warning("您没有创建群聊的权限!");
+         }
+         if(count($user_ids)>$this->chatSetting['groupUserMax'] && $this->chatSetting['groupUserMax']!=0){
+            return warning("人数不能超过".$this->chatSetting['groupUserMax']."人!");
+         }
+         if(count($user_ids)<=1){
+            return warning("请至少选择两人!");
+         }
+         // 将自己也加入群聊
+         $user_ids[]=$this->userInfo['user_id'];
+         Db::startTrans();
+         $setting=$this->setting;
+         try{
+            $create=[
+               'create_user'=>$uid,
+               'owner_id'=>$uid,
+               'name'=>"群聊",
+               'name_py'=>"qunliao",
+               'setting'=>json_encode($setting),
+            ];
+            $name=$param['name'] ?? '';
+            if($name){
+               $create['name']=$name;
+               $create['name_py']=pinyin_sentence($name);
+            }
+            $group=new GroupModel();
+            $group->save($create);
+            $group_id=$group->group_id;
+            $data=[];
+            sort($user_ids);
+            foreach($user_ids as $k=>$v){
+               $info=[
+                  'user_id'=>$v,
+                  'invite_id'=>$uid,
+                  'status'=>1,
+                  'role'=>3,
+                  'group_id'=>$group_id
+               ];
+               if($v==$uid){
+                  $info['invite_id']=0;
+                  $info['role']=1;
+               }
+               $data[]=$info;
+            }
+            $groupUser=new GroupUser();
+            $groupUser->saveAll($data);
+            $url=GroupModel::setGroupAvatar($group_id);
+            $groupInfo=[
+               'displayName'=>$create['name'],
+               'owner_id'=>$create['owner_id'],
+               'role'=>3,
+               'name_py'=>$create['name_py'],
+               'id'=>'group-'.$group_id,
+               'avatar'=>avatarUrl($url,$create['name'],$group_id,120),
+               'is_group'=>1,
+               'lastContent'=>$this->userInfo['realname'].' 创建了群聊',
+               'lastSendTime'=>time()*1000,
+               'index'=>"[2]群聊",
+               'is_notice'=>1,
+               'is_top'=>0,
+               'setting'=>$setting,
+         
+            ];
+            Message::create([
+               'from_user'=>$uid,
+               'to_user'=>$group_id,
+               'content'=>str_encipher('创建了群聊'),
+               'type'=>'event',
+               'is_group'=>1,
+               'is_read'=>1,
+               'is_last'=>1,
+               'chat_identify'=>'group-'.$group_id
+            ]);
+            wsSendMsg($user_ids, 'addGroup', $groupInfo);
+            Db::commit();
+            $groupInfo['role']=1;
+            return success('',$groupInfo);
+         }catch(Exception $e){
+            Db::rollback();
+            return error($e->getMessage());
+         }
+      }
+
+      // 移除成员
+      public function removeUser(){
+         $param = $this->request->param();
+         $uid=$this->userInfo['user_id'];
+         $group_id = explode('-', $param['id'])[1];
+         $user_id=$param['user_id'];
+         $role=GroupUser::where(['group_id'=>$group_id,'user_id'=>$uid])->value('role');
+         if($role>2 && $user_id!=$uid){
+            return warning('您没有操作权限!');
+         }
+         $groupUser=GroupUser::where(['group_id'=>$group_id,'user_id'=>$user_id])->find();
+         if(($groupUser && $groupUser['role']>$role) || $user_id==$uid){
+            GroupUser::destroy($groupUser->id);
+         }else{
+            return warning('您的权限不够!');
+         }
+         $url=GroupModel::setGroupAvatar($group_id);
+         wsSendMsg($group_id,"removeUser",['group_id'=>$param['id'],'avatar'=>$url,'user_id'=>$user_id],1);
+         return success('删除成功');
+      }
+
+      // 解散团队
+      public function removeGroup(){
+         $param = $this->request->param();
+         $uid=$this->userInfo['user_id'];
+         $group_id = explode('-', $param['id'])[1];
+         $role=GroupUser::where(['group_id'=>$group_id,'user_id'=>$uid])->value('role');
+         if($role>1){
+            return warning('您没有操作权限!');
+         }
+         Db::startTrans();
+         try{
+            // 删除团队成员
+            GroupUser::where(['group_id'=>$group_id])->delete();
+            // 删除团队
+            GroupModel::destroy($group_id);
+            wsSendMsg($group_id,"removeGroup",['group_id'=>$param['id']],1);
+            Db::commit();
+            return success('');
+         }catch(Exception $e){
+            Db::rollback();
+            return error($e->getMessage());
+         }
+      }
+
+      // 设置公告
+      public function setNotice(){
+         $param = $this->request->param();
+         $uid=$this->userInfo['user_id'];
+         $group_id = explode('-', $param['id'])[1];
+         if($param['notice']==''){
+            return warning('请输入内容!');
+         }
+         $role=GroupUser::where(['group_id'=>$group_id,'user_id'=>$uid])->value('role');
+         // if($role>2){
+         //    return warning('您没有操作权限!');
+         // }
+         GroupModel::update(['notice'=>$param['notice']],['group_id'=>$group_id]);
+         wsSendMsg($group_id,"setNotice",['group_id'=>$param['id'],'notice'=>$param['notice']],1);
+         return success('');
+      }
+
+      // 群聊设置
+      public function groupSetting(){
+         $param = $this->request->param();
+         $uid=$this->userInfo['user_id'];
+         $group_id = explode('-', $param['id'])[1];
+         $role=GroupUser::where(['group_id'=>$group_id,'user_id'=>$uid])->value('role');
+         if($role!=1){
+            return warning('您没有操作权限!');
+         }
+         $setting=json_encode($param['setting']);
+         GroupModel::update(['setting'=>$setting],['group_id'=>$group_id]);
+         wsSendMsg($group_id,"groupSetting",['group_id'=>$param['id'],'setting'=>$param['setting']],1);
+         return success('');
+      }
+
+      //生成群聊头像
+      protected function setGroupAvatar($group_id){
+         $userList=GroupUser::where('group_id',$group_id)->limit(9)->column('user_id');
+         $userList=User::where('user_id','in',$userList)->select()->toArray();
+         $imgList=[];
+         $dirPath=app()->getRootPath().'public/temp';
+         foreach($userList as $k=>$v){
+            if($v['avatar']){
+               $imgList[]=avatarUrl($v['avatar'],$v['realname'],$v['user_id']);
+            }else{
+               $imgList[]=circleAvatar($v['realname'],80,$v['user_id'],1,$dirPath);
+            }
+         }
+         $groupId='group_'.$group_id;
+         $path=$dirPath.'/'.$groupId.'.jpg';
+         $a = getGroupAvatar($imgList,1,$path);
+         $url='';
+         if($a){
+            $upload=new Upload();
+            $newPath=$upload->uploadLocalAvatar($path,[],$groupId);
+            if($newPath){
+               GroupModel::where('group_id',$group_id)->update(['avatar'=>$newPath]);
+               $url=avatarUrl($newPath);
+            }
+         }
+         // 删除目录下的所有文件
+         $files = glob($dirPath . '/*'); // 获取目录下所有文件路径
+         foreach ($files as $file) {
+            if (is_file($file)) { // 如果是文件则删除
+               unlink($file);
+            }
+         }
+         return $url;
+      }
+
+      // 加入群
+      public function joinGroup(){
+         $param = $this->request->param();
+         $uid=$this->userInfo['user_id'];
+         $group_id = explode('-', $param['group_id'])[1];
+         $inviteUid=$param['inviteUid'] ?? '';
+         $groupUserCount=GroupUser::where(['group_id'=>$group_id,'status'=>1])->count();
+         $groupUser=GroupUser::where(['group_id'=>$group_id,'user_id'=>$uid])->find();
+         if($groupUser){
+            return warning('您已经加入该群!');
+         }
+         if(($groupUserCount+1) > $this->chatSetting['groupUserMax'] && $this->chatSetting['groupUserMax']!=0){
+            return warning("人数不能超过".$this->chatSetting['groupUserMax']."人!");
+         }
+         try{
+            $data=[
+               'group_id'=>$group_id,
+               'user_id'=>$uid,
+               'role'=>3,
+               'invite_id'=>$inviteUid
+            ];
+            GroupUser::create($data);
+            $url=GroupModel::setGroupAvatar($group_id);
+            $action='joinGroup';
+            event('GroupChange', ['action' => $action, 'group_id' => $group_id, 'param' => $param]);
+            wsSendMsg($group_id,"addGroupUser",['group_id'=>$param['group_id'],'avatar'=>$url],1);
+            return success('加入成功');
+         }catch(Exception $e){
+            return error($e->getMessage());
+         }
+      }
+
+   // 更换群主
+    public function changeOwner()
+    {
+        $user_id = $this->request->param('user_id');
+        $id = $this->request->param('id');
+        $group_id = explode('-', $id)[1];
+        $uid=$this->userInfo['user_id'];
+        $group=GroupModel::where('group_id',$group_id)->find();
+        if(!$group){
+            return warning('群组不存在');
+        }
+        $user=User::where('user_id',$user_id)->find();
+        if(!$user){
+            return warning('用户不存在');
+        }
+        $role=GroupUser::where(['group_id'=>$group_id,'user_id'=>$uid])->value('role');
+        if($role>1){
+           return warning('您没有操作权限!');
+        }
+        Db::startTrans();
+        try{
+            GroupUser::where('group_id',$group_id)->where('user_id',$user_id)->update(['role'=>1]);
+            GroupUser::where('group_id',$group_id)->where('user_id',$group->owner_id)->update(['role'=>3]);
+            $group->owner_id=$user_id;
+            $group->save();
+            wsSendMsg($group_id,"changeOwner",['group_id'=>'group-'.$group_id,'user_id'=>$user_id],1);
+            Db::commit();
+            return success('转让成功');
+        }catch (\Exception $e){
+            Db::rollback();
+            return warning('更换失败');
+        }
+    }
+}

+ 747 - 0
app/enterprise/controller/Im.php

@@ -0,0 +1,747 @@
+<?php
+
+namespace app\enterprise\controller;
+
+use app\BaseController;
+use think\facade\Request;
+use think\facade\Db;
+use app\enterprise\model\{User, Message, GroupUser, Friend};
+use GatewayClient\Gateway;
+use Exception;
+use League\Flysystem\Util;
+use think\facade\Cache;
+
+class Im extends BaseController
+{
+    protected $fileType = ['file', 'image','video','voice'];
+    // 获取联系人列表
+    public function getContacts()
+    {
+        $data = User::getUserList([['status', '=', 1], ['user_id', '<>', $this->userInfo['user_id']]], $this->userInfo['user_id']);
+        $count=Friend::where(['status'=>2,'friend_user_id'=>$this->uid])->count();
+        $time=Friend::where(['friend_user_id'=>$this->uid,'is_invite'=>1])->order('create_time desc')->value('create_time');
+        return success('', $data,$count,$time*1000);
+    }
+
+
+    //发送消息
+    public function sendMessage()
+    {
+        $param = $this->request->param();
+        $param['user_id'] = $this->userInfo['user_id'];
+        $is_group=$param['is_group']??0;
+        $chatSetting=$this->chatSetting;
+        if($is_group==0 && $chatSetting['simpleChat']==0){
+            return warning('目前禁止用户私聊!');
+        }
+        // 如果是单聊,并且是社区模式,需要判断是否是好友
+        if($is_group==0 && $this->globalConfig['sysInfo']['runMode']==2){
+            $friend=Friend::where(['friend_user_id'=>$this->uid,'create_user'=>$param['toContactId']])->find();
+            if(!$friend){
+                return warning('您不在TA的好友列表,不能发消息!');
+            }
+            $otherFriend=Friend::where(['friend_user_id'=>$param['toContactId'],'create_user'=>$this->uid])->find();
+            if(!$otherFriend){
+                return warning('TA还不是您的好友,不能发消息!');
+            }
+        }
+        $data = Message::sendMessage($param);
+        if ($data) {
+            return success('', $data);
+        } else {
+            return error('发送失败');
+        }
+    }
+
+    //转发消息
+    public function forwardMessage()
+    {
+        $param = $this->request->param();
+        $userIds=$param['user_ids'] ?? [];
+        if(!$userIds || count($userIds)>5){
+            return warning('请选择转发的用户或者数量不操作5个!');
+        }
+        $msg_id=$param['msg_id'] ?? 0;
+        $message=Message::find($msg_id);
+        if(!$message){
+            return warning('消息不存在');
+        }
+        $message=$message->toArray();
+        $userInfo=$this->userInfo;
+        try{
+            $is_group=0;
+            $error=0;
+            $chatSetting=$this->chatSetting;
+            foreach($userIds as $k=>$v){
+                $msgInfo=$message;
+                if(strpos($v,'group')!==false){
+                    $is_group=1;
+                }else{
+                    $is_group=0;
+                }
+                if($is_group==0 && $chatSetting['simpleChat']==0){
+                    $error++;
+                    continue;
+                }
+                $msgInfo['id']=\utils\Str::getUuid();
+                $msgInfo['status']='successd';
+                $msgInfo['user_id']=$userInfo['user_id'];
+                $msgInfo['sendTime']=time()*1000;
+                $msgInfo['toContactId']=$v;
+                $msgInfo['content']=str_encipher($msgInfo['content'],false);
+                $msgInfo['fromUser']=[
+                    'id'=>$userInfo['user_id'],
+                    'avatar'=>avatarUrl($userInfo['avatar'],$userInfo['realname'],$userInfo['user_id'],120),
+                    'displayName'=>$userInfo['realname']
+                ];
+                $msgInfo['is_group']=$is_group;
+                // 如果是单聊,并且是社区模式,需要判断是否是好友
+                if($is_group==0 && $this->globalConfig['sysInfo']['runMode']==2){
+                    $friend=Friend::where(['friend_user_id'=>$this->uid,'create_user'=>$v])->find();
+                    if(!$friend){
+                        $error++;
+                        continue;
+                    }
+                    $otherFriend=Friend::where(['friend_user_id'=>$v,'create_user'=>$this->uid])->find();
+                    if(!$otherFriend){
+                        $error++;
+                        continue;
+                    }
+                }
+                Message::sendMessage($msgInfo);
+            }
+        }catch(\Exception $e){
+            return error($e->getMessage());
+        }
+        if ($error) {
+            $text='由于规则限制,转发失败'.$error.'条';
+        } else {
+            $text='转发成功';
+        }
+        return success($text);
+    }
+
+    // 获取用户信息
+    public function getUserInfo()
+    {
+        $user_id = $this->request->param('user_id');
+        $user=User::find($user_id);
+        if(!$user){
+            return error('用户不存在');
+        }
+        $user->avatar=avatarUrl($user->avatar,$user->realname,$user->user_id,120);
+        // 查询好友关系
+        $friend=Friend::where(['friend_user_id'=>$user_id,'create_user'=>$this->userInfo['user_id']])->find();
+        $user->friend=$friend ? : '';
+        $location='';
+        if($user->last_login_ip){
+            $location=implode(" ", \Ip::find($user->last_login_ip));
+        }
+        $user->location=$location;
+        $user->password='';
+        return success('', $user);
+    }
+
+    // 搜索用户
+    public function searchUser(){
+        $keywords=$this->request->param('keywords','');
+        if(!$keywords){
+            return success('',[]);
+        }
+        $map=['status'=>1,'account'=>$keywords];
+        $list=User::where($map)->field(User::$defaultField)->where([['account','<>',$this->userInfo['account']]])->select()->toArray();
+        if($list){
+            $ids=array_column($list,'user_id');
+            $friendList=Friend::getFriend([['create_user','=',$this->uid],['friend_user_id','in',$ids]]);
+            foreach($list as $k=>$v){
+                $list[$k]['avatar']=avatarUrl($v['avatar'],$v['realname'],$v['user_id'],120);
+                $list[$k]['friend']=$friendList[$v['user_id']] ?? '';
+            }
+        }
+        return success('', $list);
+    }
+
+    // 获取聊天记录
+    public function getMessageList()
+    {
+        $param = $this->request->param();
+        $is_group = isset($param['is_group']) ? $param['is_group'] : 0;
+        // 设置当前聊天消息为已读
+        $chat_identify = $this->setIsRead($is_group, $param['toContactId']);
+        $type = isset($param['type']) ? $param['type'] : '';
+        $is_at = isset($param['is_at']) ? $param['is_at'] : '';
+        $map = ['chat_identify' => $chat_identify, 'status' => 1, 'is_group' => $is_group];
+        $where = [];
+        if ($type && $type != "all") {
+            $map['type'] = $type;
+        } else {
+            if (isset($param['type'])) {
+                $where[] = ['type', '<>', 'event'];
+            }
+        }
+        $keywords = isset($param['keywords']) ? $param['keywords'] : '';
+        if ($keywords && in_array($type, ['text', 'all'])) {
+            $where[] = ['content', 'like', '%' . $keywords . '%'];
+        }
+        // 如果是查询@数据
+        if($is_at){
+            $atList=Db::name('message')->where($map)->where($where)->whereFindInSet('at',$this->userInfo['user_id'])->order('msg_id desc')->select()->toArray();
+            if($atList){
+                $data = $this->recombileMsg($atList,false);
+                Message::setAtread($data,$this->userInfo['user_id']);
+                return success('', $data, count($data));
+            }else{
+                return success('', [], 0);
+            }
+        }
+        $listRows = $param['limit'] ?: 20;
+        $pageSize = $param['page'] ?: 1;
+        $last_id = $param['last_id'] ?? 0;
+        if($last_id){
+            $where[]=['msg_id','<',$last_id];
+        }
+        $list = Message::getList($map, $where, 'msg_id desc', $listRows, $pageSize);
+        $data = $this->recombileMsg($list);
+        // 如果是群聊并且是第一页消息,需要推送@数据给用户
+        if($param['is_group']==1 && $param['page']==1){
+            $isPush=Cache::get('atMsgPush'.$chat_identify) ?? '';
+            $atList=Db::name('message')->where(['chat_identify'=>$chat_identify,'is_group'=>1])->whereFindInSet('at',$this->userInfo['user_id'])->order('msg_id desc')->select()->toArray();
+            $msgIda=array_column($atList,'msg_id');
+            // 如果两次推送at数据的列表不一样,则推送
+            if($isPush!=json_encode($msgIda)){
+                $atData=$this->recombileMsg($atList,false);
+                wsSendMsg($this->userInfo['user_id'],'atMsgList',[
+                    'list'=>$atData,
+                    'count'=>count($atData),
+                    'toContactId'=>$param['toContactId']
+                ]);
+                Cache::set('atMsgPush'.$chat_identify,json_encode($msgIda),60);
+            }
+        }
+        // 如果是消息管理器则不用倒序
+        if (!isset($param['type'])) {
+            $data = array_reverse($data);
+        }
+        return success('', $data, $list->total());
+    }
+
+    protected function recombileMsg($list,$isPagination=true)
+    {
+        $data = [];
+        $userInfo = $this->userInfo;
+        if ($list) {
+            $listData = $isPagination ? $list->toArray()['data'] : $list;
+            $userList = User::matchUser($listData, true, 'from_user', 120);
+            foreach ($listData as $k => $v) {
+                // 屏蔽已删除的消息
+                if ($v['del_user']) {
+                    $delUser = explode(',', $v['del_user']);
+                    if (in_array($userInfo['user_id'], $delUser)) {
+                        unset($list[$k]);
+                        continue;
+                        // $v['type']="event";
+                        // $v['content']="删除了一条消息";
+                    }
+                }
+                $content = str_encipher($v['content'],false);
+                $preview = '';
+                $ext='';
+                if (in_array($v['type'], $this->fileType)) {
+                    $content = getFileUrl($content);
+                    $preview = previewUrl($content);
+                    $ext=getExtUrl($content);
+                }
+                
+                $fromUser = $userList[$v['from_user']];
+                // 处理撤回的消息
+                if ($v['type'] == "event") {
+                    if ($v['from_user'] == $userInfo['user_id']) {
+                        $content = "你" . $content;
+                    } elseif ($v['is_group'] == 1) {
+                        $content = $fromUser['realname'] . $content;
+                    } else {
+                        $content = "对方" . $content;
+                    }
+                }
+                $toContactId=$v['is_group'] ==1 ?  'group-'.$v['to_user'] : $v['to_user'];
+                $atList=($v['at'] ?? null) ? explode(',',$v['at']): [];
+                $data[] = [
+                    'msg_id' => $v['msg_id'],
+                    'id' => $v['id'],
+                    'status' => "succeed",
+                    'type' => $v['type'],
+                    'sendTime' => $v['create_time'] * 1000,
+                    'content' => $content,
+                    'preview' => $preview,
+                    'download' => $v['file_id'] ? request()->domain().'/filedown/'.encryptIds($v['file_id']) : '',
+                    'is_read' => $v['is_read'],
+                    'is_group' => $v['is_group'],
+                    'at' => $atList,
+                    'toContactId' => $toContactId,
+                    'from_user' => $v['from_user'],
+                    'file_id' => $v['file_id'],
+                    'file_cate' => $v['file_cate'],
+                    'fileName' => $v['file_name'],
+                    'fileSize' => $v['file_size'],
+                    'fromUser' => $fromUser,
+                    'extUrl'=>$ext,
+                    'extends'=>is_string($v['extends'])?json_decode($v['extends'],true) : $v['extends']
+                ];
+            }
+        }
+        return $data;
+    }
+
+    // 设置当前窗口的消息默认为已读
+    public function setMsgIsRead()
+    {
+        $param = $this->request->param();
+        
+        // 判断是否是一个二维数组
+        if (is_array($param['messages'][0] ?? '')) {
+           $messages=$param['messages'];
+        } else {
+            $messages=[$param['messages']];
+        }
+        $this->setIsRead($param['is_group'], $param['toContactId'],$messages);
+        if (!$param['is_group']) {
+            wsSendMsg($param['fromUser'], 'isRead', $messages, 0);
+        }
+        return success('');
+    }
+
+    // 设置消息已读
+    protected function setIsRead($is_group, $to_user,$messages=[])
+    {
+        if ($is_group) {
+            $chat_identify = $to_user;
+            $toContactId = explode('-', $to_user)[1];
+            // 将@消息放到定时任务中逐步清理
+            if($messages){
+                Message::setAtRead($messages,$this->userInfo['user_id']);
+            }
+            // 更新群里面我的所有未读消息为0
+            GroupUser::editGroupUser(['user_id' => $this->userInfo['user_id'], 'group_id' => $toContactId], ['unread' => 0]);
+        } else {
+            $chat_identify = chat_identify($this->userInfo['user_id'], $to_user);
+            // 更新我的未读消息为0
+            Message::update(['is_read' => 1], [['chat_identify', '=', $chat_identify], ['to_user', '=', $this->userInfo['user_id']]]);
+            // 告诉对方我阅读了消息
+            wsSendMsg($to_user, 'readAll', ['toContactId' => $this->userInfo['user_id']]);
+        }
+        return $chat_identify;
+    }
+
+    // 聊天设置
+    public function setting()
+    {
+        $param = $this->request->param();
+        if ($param) {
+            User::where(['user_id' => $this->userInfo['user_id']])->update(['setting' => $param]);
+            return success('');
+        }
+        return warning('设置失败');
+    }
+
+    // 撤回消息
+    public function undoMessage()
+    {
+        $param = $this->request->param();
+        $id = $param['id'];
+        $message = Message::where(['id' => $id])->find();
+        if ($message) {
+            // 如果时间超过了2分钟也不能撤回
+            $createTime=is_string($message['create_time']) ? strtotime($message['create_time']) : $message['create_time'];
+            if(time()-$createTime>120 && $message['is_group']==0){
+                return warning('超过2分钟不能撤回!');
+            }
+            $text = "撤回了一条消息";
+            $fromUserName = "对方";
+            $toContactId = $message['to_user'];
+            if ($message['is_group'] == 1) {
+                $fromUserName = $this->userInfo['realname'];
+                $toContactId = explode('-', $message['chat_identify'])[1];
+                // 如果是群聊消息撤回,需要判断是否是群主或者管理员,如果是则可以撤回
+                if($message['from_user']!=$this->userInfo['user_id']){
+                    $groupUser=GroupUser::where(['user_id'=>$this->userInfo['user_id'],'group_id'=>$toContactId])->find();
+                    if(!$groupUser || !in_array($groupUser['role'],[1,2])){
+                        return warning('您没有权限撤回该消息!');
+                    }
+                    $text='被(管理员)撤回了一条消息';
+                }
+            }
+            $message->content = str_encipher($text);
+            $message->type = 'event';
+            $message->is_undo = 1;
+            $message->save();
+            $info = $message->toArray();
+            // $data = $info;
+            $data['content'] = $fromUserName . $text;
+            $data['sendTime'] = $createTime * 1000;
+            $data['id'] = $info['id'];
+            $data['from_user'] = $info['from_user'];
+            $data['msg_id'] = $info['msg_id'];
+            $data['status'] = $info['status'];
+            $data['type'] = 'event';
+            $data['isMobile'] = $this->request->isMobile() ? 1 : 0;
+            wsSendMsg($toContactId, 'undoMessage', $data, $info['is_group']); 
+            if($info['is_group']==0){
+               // 给自己也发一份推送,多端同步
+                $data['content'] = "你". $text;
+                wsSendMsg($this->userInfo['user_id'], 'undoMessage', $data, $info['is_group']); 
+            }
+            return success('');
+        } else {
+            return warning();
+        }
+    }
+
+    // 删除消息
+    public function removeMessage()
+    {
+        $param = $this->request->param();
+        $id = $param['id'];
+        $map = ['id' => $id];
+        $message = Message::where($map)->find();
+        if ($message) {
+            $message->del_user = $this->userInfo['user_id'];
+            if ($message['is_group'] == 1) {
+                if ($message['del_user']) {
+                    $message->del_user .= ',' . $this->userInfo['user_id'];
+                }
+            } else {
+                if ($message['del_user'] > 0) {
+                    $message->where($map)->delete();
+                    return success('删除成功!');
+                }
+            }
+            $message->save();
+            return success('');
+        } else {
+            return warning('');
+        }
+    }
+
+    // 消息免打扰
+    public function isNotice()
+    {
+        $param = $this->request->param();
+        $user_id = $this->userInfo['user_id'];
+        $id = $param['id'];
+        if ($param['is_group'] == 1) {
+            $group_id = explode('-', $param['id'])[1];
+            GroupUser::update(['is_notice' => $param['is_notice']], ['user_id' => $user_id, 'group_id' => $group_id]);
+        } else {
+            $map = ['create_user' => $user_id, 'friend_user_id' => $id];
+            $friend = Friend::where($map)->find();
+            try {
+                if ($friend) {
+                    $friend->is_notice = $param['is_notice'];
+                    $friend->save();
+                } else {
+                    $info = [
+                        'create_user' => $user_id,
+                        'friend_user_id' => $id,
+                        'is_notice' => $param['is_notice']
+                    ];
+                    Friend::create($info);
+                }
+                return success('');
+            } catch (Exception $e) {
+                return error($e->getMessage());
+            }
+        }
+        wsSendMsg($user_id,"setIsNotice",['id'=>$id,'is_notice'=>$param['is_notice'],'is_group'=>$param['is_group']]);
+        return success('');
+    }
+
+    // 设置聊天置顶
+    public function setChatTop()
+    {
+        $param = $this->request->param();
+        $user_id = $this->userInfo['user_id'];
+        $is_group = $param['is_group'] ?: 0;
+        $id = $param['id'];
+        
+        try {
+            if ($is_group == 1) {
+                $group_id = explode('-', $param['id'])[1];
+                GroupUser::update(['is_top' => $param['is_top']], ['user_id' => $user_id, 'group_id' => $group_id]);
+            } else {
+                $map = ['create_user' => $user_id, 'friend_user_id' => $id];
+                $friend = Friend::where($map)->find();
+                if ($friend) {
+                    $friend->is_top = $param['is_top'];
+                    $friend->save();
+                } else {
+                    $info = [
+                        'create_user' => $user_id,
+                        'friend_user_id' => $id,
+                        'is_top' => $param['is_top']
+                    ];
+                    Friend::create($info);
+                }
+            }
+            wsSendMsg($user_id,"setChatTop",['id'=>$id,'is_top'=>$param['is_top'],'is_group'=>$is_group]);
+            return success('');
+        } catch (Exception $e) {
+            return error($e->getMessage());
+        }
+    }
+    
+    // 删除聊天
+    public function delChat()
+    {
+        $param = $this->request->param();
+        $user_id = $this->userInfo['user_id'];
+        $is_group = $param['is_group'] ?: 0;
+        $id = $param['id'];
+        if(!$is_group){
+            $chat_identify=chat_identify($user_id,$id);
+        }else{
+            return success('');
+        }
+        Message::where(['chat_identify' => $chat_identify])->update(['is_last' => 0]);
+        return success('');
+    }
+
+    // 向用户发送消息
+    public function sendToMsg(){
+        $param=$this->request->param();
+        $toContactId=$param['toContactId'];
+        
+        $type=$param['type'];
+        $status=$param['status'];
+        $event=$param['event'] ?? 'calling';
+        if($event=='calling'){
+            $status=3;
+        }
+        $sdp=$param['sdp'] ?? '';
+        $iceCandidate=$param['iceCandidate'] ?? '';
+        $callTime=$param['callTime'] ?? '';
+        $msg_id=$param['msg_id'] ?? '';
+        $id=$param['id'] ?? '';
+        $code=($param['code'] ?? '') ?: 901;
+        // 如果该用户不在线,则发送忙线
+        Gateway::$registerAddress = config('gateway.registerAddress');
+        if(!Gateway::isUidOnline($toContactId)){
+            $toContactId=$this->userInfo['user_id'];
+            $code=907;
+            $event='busy';
+            sleep(1);
+        }
+        switch($code){
+            case 902:
+                $content='已取消通话';
+                break;
+            case 903:
+                $content='已拒绝';
+                break;
+            case 905:
+                $content='未接通';
+                break;
+            case 906:
+                $content='通话时长 '.date("i:s",$callTime);
+                break;
+            case 907:
+                $content='忙线中';
+                break;
+            case 908:
+                $content='其他端已操作';
+                break;
+            default:
+                $content=$type==1 ?'视频通话' : '语音通话';
+                break;
+        }
+        switch($event){
+            case 'calling':
+                $content=$type==1 ?'视频通话' : '语音通话';
+                break;
+            case 'acceptRtc':
+                $content='接听通话请求';
+                break;
+            case 'iceCandidate':
+                $content='数据交换中';
+                break;
+        }
+        $userInfo=$this->userInfo;
+        $userInfo['id']=$userInfo['user_id'];
+        $data=[
+            'id'=>$id,
+            'msg_id'=>$msg_id,
+            'sendTime'=>time()*1000,
+            'toContactId'=>$toContactId,
+            'content'=>$content,
+            'type'=>'webrtc',
+            'status'=>'succeed',
+            'is_group'=>0,
+            'is_read'=>0,
+            'fromUser'=>$userInfo,
+            'at'=>[],
+            'extends'=>[
+                'type'=>$type,    //通话类型,1视频,0语音。
+                'status'=>$status, //,1拨打方,2接听方
+                'event'=>$event,
+                'callTime'=>$callTime,
+                'sdp'=>$sdp,
+                'code'=>$code,  //通话状态:呼叫901,取消902,拒绝903,接听904,未接通905,接通后挂断906,忙线907,其他端操作908
+                'iceCandidate'=>$iceCandidate,
+                'isMobile'=>$this->request->isMobile() ? 1 : 0,
+            ]
+        ];
+        if($event=='calling'){
+            $chat_identify=chat_identify($userInfo['id'],$toContactId);
+            $msg=[
+                'from_user'=>$userInfo['id'],
+                'to_user'=>$toContactId,
+                'id'=>$id,
+                'content'=>str_encipher($content),
+                'chat_identify'=>$chat_identify,
+                'create_time'=>time(),
+                'type'=>$data['type'],
+                'is_group'=>0,
+                'is_read'=>0,
+                'extends'=>$data['extends'],
+            ];
+            $message=new Message();
+            $message->update(['is_last'=>0],['chat_identify'=>$chat_identify]);
+            $message->save($msg);
+            $msg_id=$message->msg_id;
+            $data['msg_id']=$msg_id;
+            // 将接收人设置为发送人才能定位到该消息
+            $data['toContactId']=$userInfo['id'];
+            $data['toUser']=$toContactId;
+        }elseif($event=='hangup'){
+            $message=Message::where(['id'=>$id])->find();
+            if(!$message){
+                return error('通话失败!');
+            }
+            if($message){
+                $message->content=str_encipher($content);
+                $extends=$message->extends;
+                $extends['code']=$code;
+                $extends['callTime']=$callTime;
+                $message->extends=$extends;
+                $message->save();
+            }
+        }
+        wsSendMsg($toContactId,'webrtc',$data);
+        $wsData=$data;
+        if(in_array($event,['calling','acceptRtc','hangup'])){
+            if(in_array($event,['acceptRtc','hangup'])){
+                $data['extends']['event']='otherOpt'; //其他端操作
+            }
+            $data['toContactId']=$toContactId;
+            wsSendMsg($userInfo['id'],'webrtc',$data);
+        }
+        return success('',$wsData);
+    }
+
+    // 修改密码
+    public function editPassword()
+    {
+        if(env('app.demon_mode',false)){
+            return warning('演示模式不支持修改');
+        }
+        
+        $user_id = $this->userInfo['user_id'];
+        $user=User::find($user_id);
+        if(!$user){
+            return warning('用户不存在');
+        }
+        $account=$user->account;
+        $code=$this->request->param('code','');
+        $originalPassword = $this->request->param('originalPassword', '');
+        if($code){
+            if(Cache::get($account)!=$code){
+                return warning('验证码不正确!');
+            }
+        }elseif($originalPassword){
+            if(password_hash_tp($originalPassword,$user->salt)!= $user->password){
+                return warning('原密码不正确!');
+            }
+        }else{
+            return warning('参数错误!');
+        }
+        try{
+            $password = $this->request->param('password','');
+            if($password){
+                $salt=$user->salt;
+                $user->password= password_hash_tp($password,$salt);
+            }
+            $user->save();
+            return success('修改成功');
+        }catch (\Exception $e){
+            return error('修改失败');
+        }
+    }
+
+    // 修改用户信息
+    public function updateUserInfo(){
+        try{
+            $data = $this->request->param();
+            $user=User::find($this->uid);
+            if(!$user){
+                return warning('用户不存在');
+            }
+            $user->realname =$data['realname'];
+            $user->email =$data['email'];
+            $user->motto=$data['motto'];
+            $user->sex =$data['sex'];
+            $user->name_py= pinyin_sentence($data['realname']);
+            $user->save();
+            return success('修改成功', $data);
+        }catch (\Exception $e){
+            return error($e->getMessage());
+        }
+    }
+
+    // 修改账户
+    public function editAccount(){
+        if(env('app.demon_mode',false)){
+            return warning('演示模式不支持修改');
+        }
+        $code=$this->request->param('code','');
+        $newCode=$this->request->param('newCode','');
+        $account=$this->request->param('account','');
+        $isUser=User::where('account',$account)->find();
+        if($isUser){
+            return warning('账户已存在');
+        }
+        $user=User::find($this->uid);
+        if(!$user){
+            return warning('用户不存在');
+        }
+        // 如果已经认证过了,则需要验证验证码
+        if($user->is_auth){
+            if(Cache::get($user->account)!=$code){
+                return warning('验证码不正确!');
+            }
+        }
+        if(Cache::get($account)!=$newCode){
+            return warning('新账户验证码不正确!');
+        }
+        try{
+            $user->account=$account;
+            $user->is_auth=1;
+            $user->save();
+            return success('修改成功');
+        }catch (\Exception $e){
+            return error('修改失败');
+        }
+    }
+
+    // 阅读@消息
+    public function readAtMsg(){
+        $param = $this->request->param();
+        $atList=Db::name('message')->where(['chat_identify'=>$param['toContactId'],'is_group'=>1])->whereFindInSet('at',$this->userInfo['user_id'])->order('msg_id desc')->select();
+        $atData=$this->recombileMsg($atList,false);
+        Message::setAtRead($atData,$this->userInfo['user_id']);
+        // $message=Message::where('msg_id',$param['msg_id'])->select();
+        // $atList=($message ?? null) ? explode(',',$message): [];
+        // // 两个数组取差集
+        // $newAtList = array_diff($atList, [$this->userInfo['user_id']]);
+        // Message::where('msg_id',$param['msg_id'])->update(['at'=>implode(',',$newAtList)]);
+        return success('');
+    }
+}

+ 16 - 0
app/enterprise/listener/GroupChange.php

@@ -0,0 +1,16 @@
+<?php
+namespace app\enterprise\listener;
+
+use app\enterprise\model\{Group,User};
+use GatewayClient\Gateway;
+
+// 监听群聊变更事件
+class GroupChange
+{
+    public function handle(Group $group,User $user,$data){
+        Gateway::$registerAddress = config('gateway.registerAddress');
+        if($data['action'] == 'joinGroup'){
+            Gateway::joinGroup(request()->header('clientId'),$data['group_id']);
+        }
+    }
+}

+ 4 - 0
app/enterprise/middleware.php

@@ -0,0 +1,4 @@
+<?php
+return [
+"checkAuth"
+];

+ 14 - 0
app/enterprise/model/File.php

@@ -0,0 +1,14 @@
+<?php
+/**
+ * raingad IM [ThinkPHP6]
+ * @author xiekunyu <raingad@foxmail.com>
+ */
+namespace app\enterprise\model;
+
+use app\BaseModel;
+class File extends BaseModel
+{
+    protected $pk="file_id";
+
+
+}

+ 28 - 0
app/enterprise/model/Friend.php

@@ -0,0 +1,28 @@
+<?php
+/**
+ * raingad IM [ThinkPHP6]
+ * @author xiekunyu <raingad@foxmail.com>
+ */
+namespace app\enterprise\model;
+
+use app\BaseModel;
+use think\facade\Db;
+
+class Friend extends BaseModel
+{
+    protected $pk="friend_id";
+    
+
+    public static function getFriend($map){
+       $list=self::where($map)->select();
+       $data=[];
+       if($list){
+          $list=$list->toArray();
+          foreach($list as $k=>$v){
+             $data[$v['friend_user_id']]=$v;
+          }
+       }
+       return $data;
+    }
+   
+}

+ 60 - 0
app/enterprise/model/Group.php

@@ -0,0 +1,60 @@
+<?php
+/**
+ * raingad IM [ThinkPHP6]
+ * @author xiekunyu <raingad@foxmail.com>
+ */
+namespace app\enterprise\model;
+
+use app\BaseModel;
+use think\facade\Db;
+use app\common\controller\Upload;
+class Group extends BaseModel
+{
+    protected $pk="group_id";
+
+   // 获取我的团队
+   public static function getMyGroup($map){
+      return Db::name('group_user')
+      ->alias('gu')
+      ->field('gr.group_id,gr.avatar,gr.name as displayName,gu.unread,gr.name_py,gr.owner_id,gr.notice,gu.role,gu.is_notice,gu.is_top,gr.setting')
+      ->join('group gr','gu.group_id=gr.group_id','left')
+      ->where($map)
+      ->select();
+   }
+
+   //生成群聊头像
+   public static function setGroupAvatar($group_id){
+      $userList=GroupUser::where('group_id',$group_id)->limit(9)->column('user_id');
+      $userList=User::where('user_id','in',$userList)->select()->toArray();
+      $imgList=[];
+      $dirPath=app()->getRootPath().'public/temp';
+      foreach($userList as $k=>$v){
+         if($v['avatar']){
+            $imgList[]=avatarUrl($v['avatar'],$v['realname'],$v['user_id']);
+         }else{
+            $imgList[]=circleAvatar($v['realname'],80,$v['user_id'],1,$dirPath);
+         }
+      }
+      $groupId='group_'.$group_id;
+      $path=$dirPath.'/'.$groupId.'.jpg';
+      $a = getGroupAvatar($imgList,1,$path);
+      $url='';
+      if($a){
+         $upload=new Upload();
+         $newPath=$upload->uploadLocalAvatar($path,[],$groupId);
+         if($newPath){
+            Group::where('group_id',$group_id)->update(['avatar'=>$newPath]);
+            $url=avatarUrl($newPath);
+         }
+      }
+      // 删除目录下的所有文件
+      $files = glob($dirPath . '/*'); // 获取目录下所有文件路径
+      foreach ($files as $file) {
+         if (is_file($file)) { // 如果是文件则删除
+            unlink($file);
+         }
+      }
+      return $url;
+   }
+
+}

+ 35 - 0
app/enterprise/model/GroupUser.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * raingad IM [ThinkPHP6]
+ * @author xiekunyu <raingad@foxmail.com>
+ */
+namespace app\enterprise\model;
+
+use app\BaseModel;
+use think\facade\Db;
+
+class GroupUser extends BaseModel
+{
+    protected $pk="id";
+
+   // 编辑团队信息
+   public static function editGroupUser($map,$data){
+      return self::where($map)->update($data);
+   }
+
+   // 获取团队成员列表
+   public static function getGroupUser($map){
+      $data=self::where($map)->order('role asc')->select();
+      return User::matchAllUser($data,true,'user_id');
+   }
+
+   // 验证权限
+   public static function checkAuth($map,$role=1){
+      $info=self::where($map)->find()->toArray();
+      if($info['role']<=$role){
+         return true;
+      }else{
+         return false;
+      }
+   }
+}

+ 166 - 0
app/enterprise/model/Message.php

@@ -0,0 +1,166 @@
+<?php
+/**
+ * raingad IM [ThinkPHP6]
+ * @author xiekunyu <raingad@foxmail.com>
+ */
+namespace app\enterprise\model;
+
+use app\BaseModel;
+use think\facade\Db;
+use think\facade\Cache;
+class Message extends BaseModel
+{
+    protected $pk="msg_id";
+    protected $json      = ["extends"];
+    protected $jsonAssoc = true;
+    protected static $fileType=['file','image','video','voice'];
+
+    // 添加聊天记录
+    public static function addData($data){
+       return Db::name('message')->insert($data);
+    }
+
+    // 更新消息状态
+    public static function editData($update,$map){
+        return Db::name('message')->where($map)->update($update);
+    }
+
+    // 查询聊天记录
+    public static function getList($map,$where,$sort,$listRows,$pageSize){
+        $list= Db::name('message')
+        ->where($map)
+        ->where($where)
+        ->order($sort)
+        ->paginate(['list_rows'=>$listRows,'page'=>$pageSize]);
+        return $list;
+     }
+
+         //    发送消息
+    public static function sendMessage($param){
+        $toContactId=$param['toContactId'];
+        $is_group=$param['is_group']?:0;
+        if(!$is_group){
+            $chat_identify=chat_identify($param['user_id'],$toContactId);
+            $is_read=0;
+        }else{
+            $chat_identify=$toContactId;
+            $toContactIdArr=explode('-',$toContactId);
+            $toContactId=$toContactIdArr[1];
+            $is_read=1;
+            if(!self::nospeak($toContactId,$param['user_id'])){
+                return shutdown("群聊已禁言!");
+            }
+        }
+        $fileSzie=isset($param['file_size'])?$param['file_size']:'';
+        $fileName=isset($param['file_name'])?$param['file_name']:'';
+        $ossUrl=getDiskUrl();
+        // 如果是转发图片文件的消息,必须把域名去除掉
+        $content=$param['content'];
+        if(in_array($param['type'],self::$fileType)){
+            if(strpos($param['content'],$ossUrl)!==false){
+                $content=str_replace($ossUrl,'',$param['content']);
+            }
+        }
+        $atList=($param['at'] ?? null) ? array_map('intval', $param['at']): [];
+        // 如果at里面有0,代表@所有人
+        if($atList && in_array(0,$atList)){
+            $atList=GroupUser::where([['group_id','=',$toContactId],['status','=',1],['user_id','<>',$param['user_id']]])->column('user_id');
+        }
+        $at=$atList ? implode(',',$atList) : null;
+        $data=[
+            'from_user'=>$param['user_id'],
+            'to_user'=>$toContactId,
+            'id'=>$param['id'],
+            'content'=>str_encipher($content,true),
+            'chat_identify'=>$chat_identify,
+            'create_time'=>time(),
+            'type'=>$param['type'],
+            'is_group'=>$is_group,
+            'is_read'=>$is_read,
+            'file_id'=>$param['file_id'] ?? 0,
+            "file_cate"=>$param['file_cate'] ?? 0,
+            'file_size'=>$fileSzie,
+            'file_name'=>$fileName,
+            'at'=>$at,
+            'extends'=>($param['extends'] ?? null) ? $param['extends'] : null,
+        ];
+        $message=new self();
+        $message->update(['is_last'=>0],['chat_identify'=>$chat_identify]);
+        $message->save($data);
+        // 拼接消息推送
+        $type=$is_group?'group':'simple';
+        $sendData=$param;
+        $sendData['status']='succeed';
+        $sendData['at']=$atList;
+        $sendData['msg_id']=$message->msg_id;
+        $sendData['is_read']=0;
+        $sendData['to_user']=$toContactId;
+        $sendData['sendTime']=(int)$sendData['sendTime'];
+        //这里单聊中发送对方的消息,对方是接受状态,自己是对方的联系人,要把发送对象设置为发送者的ID。
+        if($is_group){
+            $sendData['toContactId']=$param['toContactId'];
+            // 将团队所有成员的未读状态+1
+            GroupUser::editGroupUser([['group_id','=',$toContactId],['user_id','<>',$param['user_id']]],['unread'=>Db::raw('unread+1')]);
+        }else{
+            $sendData['toContactId']=$param['user_id'];
+        }
+        $sendData['fromUser']['id']=(int)$sendData['fromUser']['id'];
+        $sendData['fileSize']=$fileSzie;
+        $sendData['fileName']=$fileName;
+        
+        if(in_array($sendData['type'],self::$fileType)){
+            $sendData['content']=getFileUrl($sendData['content']);
+            if($sendData['type']=='image'){
+                $pre=1;
+            }else{
+                $pre=2;
+            }
+            $sendData['preview']=previewUrl($sendData['content'],$pre);
+            $sendData['extUrl']=getExtUrl($sendData['content']);
+            $sendData['download']= $sendData['file_id'] ? request()->domain().'/filedown/'.encryptIds($sendData['file_id']) : '';
+        }
+        if($is_group==0){
+            $toContactId=[$toContactId,$param['user_id']];
+        }
+        $sendData['toUser']=$param['toContactId'];
+        // 向发送方发送消息
+        wsSendMsg($toContactId,$type,$sendData,$is_group);
+        
+        $sendData['toContactId']=$param['toContactId'];
+        return $sendData;
+    }
+
+    // 群禁言
+    public static function nospeak($group_id,$user_id){
+        $group=Group::find($group_id);
+        if($group->owner_id==$user_id){
+            return true;
+        }
+        if($group->setting){
+            $setting=json_decode($group->setting,true);
+            $nospeak=isset($setting['nospeak'])?$setting['nospeak']:0;
+            $role=GroupUser::where(['group_id'=>$group_id,'user_id'=>$user_id])->value('role');
+            if($nospeak==1 && $role>2){
+                return false;
+            }elseif($nospeak==2 && $role!=1){
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // 将消息中的@用户加入到atListQueue中
+    public static function setAtread($messages,$user_id){
+        foreach($messages as $k=>$v){
+            if(!isset($v['at'])){
+                continue;
+            }
+            if($v['at'] && in_array($user_id,$v['at'])){
+               $atListQueue=Cache::get("atListQueue");
+               $atListQueue[$v['msg_id']][]=$user_id;
+               Cache::set("atListQueue",$atListQueue);
+            }
+        }
+    }
+
+}

+ 393 - 0
app/enterprise/model/User.php

@@ -0,0 +1,393 @@
+<?php
+
+/**
+ * raingad IM [ThinkPHP6]
+ * @author xiekunyu <raingad@foxmail.com>
+ */
+
+namespace app\enterprise\model;
+
+use GatewayClient\Gateway;
+use app\BaseModel;
+use think\facade\Db;
+use think\facade\Request;
+use think\model\concern\SoftDelete;
+use app\manage\model\Config;
+use thans\jwt\facade\JWTAuth;
+
+class User extends BaseModel
+{
+   use SoftDelete;
+
+   protected $pk = "user_id";
+   
+   public static $defaultField = 'user_id,realname,realname as displayName,account,avatar,name_py,email,last_login_ip';
+
+   protected $json = ['setting'];
+   protected $jsonAssoc = true;
+
+   public function getUid()
+   {
+      return self::$uid;
+   }
+
+   //查询用户信息
+   public static function getUserInfo($map=[])
+   {
+      if(!$map){
+         return self::$userInfo;
+      }
+      $data = self::where($map)->find();
+      if ($data) {
+         $data = $data->toArray();
+      }
+      return $data;
+   }
+   
+   /**
+     * 刷新用户token 之前token将被拉黑
+     * 修改用户数据后 调用该方法 并返回前台更新token
+     * @param array $info 用户信息
+     * @param string $terminal 客户端标识
+     * @return string
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public static function refreshToken($info,$terminal)
+    {
+        $info      = str_encipher(json_encode($info),true, config('app.aes_token_key'));
+        $authToken = 'bearer '.JWTAuth::builder(['info' => $info, 'terminal' => $terminal]);
+        return $authToken;
+    }
+
+   //   获取所有用户列表
+   public static function getAllUser($map, $user_ids = [],$user_id,$group_id = 0)
+   {
+      $field = self::$defaultField;
+      $list=[];
+      if($group_id){
+         $groupUser=GroupUser::where([['group_id','=',$group_id],['role','<>',1],['status','=',1]])->column('user_id');
+         if($groupUser){
+            $list=User::where([['user_id','in',$groupUser]])->field($field)->select()->toArray();
+         }
+      }else{
+         $config=Config::getSystemInfo();
+         // 如果是社区模式,就只查询自己的好友,如果是企业模式,就查询所有用户
+         if($config['sysInfo']['runMode']==1){
+            $list = self::where($map)->field($field)->select()->toArray();
+         }else{
+            $friendList = Friend::getFriend(['create_user' => $user_id,'status'=>1]);
+            $userList = array_keys($friendList);
+            $list = self::where($map)->where('user_id', 'in', $userList)->field($field)->select()->toArray();
+         }
+      }
+      foreach ($list as $k => $v) {
+         $list[$k]['disabled'] = false;
+         $list[$k]['avatar'] = avatarUrl($v['avatar'], $v['realname'], $v['user_id']);
+         if ($user_ids) {
+            if (in_array($v['user_id'], $user_ids)) {
+               $list[$k]['disabled'] = true;
+            }
+         }
+      }
+      return $list;
+   }
+
+   //查询用户列表
+   public static function getUserList($map, $user_id, $field = "")
+   {
+      if (!$field) {
+         $field = self::$defaultField;
+      }
+      $friendList = Friend::getFriend(['create_user' => $user_id]);
+      $config=Config::getSystemInfo();
+      // 如果是社区模式,就只查询自己的好友,如果是企业模式,就查询所有用户
+      if($config['sysInfo']['runMode']==1){
+         $list = self::where($map)->field($field)->select();
+      }else{
+         $userList = array_keys($friendList);
+         $list = self::where($map)->where('user_id', 'in', $userList)->field($field)->select();
+      }
+      $list_chart = chartSort($list, 'realname', false, 'index');
+      // 查询未读消息
+      $unread = Db::name('message')
+         ->field('from_user,count(msg_id) as unread')
+         ->where([['to_user', '=', $user_id], ['is_read', '=', 0], ['is_group', '=', 0]])
+         ->group('from_user')
+         ->select();
+      // 查询最近的联系人
+      $map1 = [['to_user', '=', $user_id], ['is_last', '=', 1], ['is_group', '=', 0]];
+      $map2 = [['from_user', '=', $user_id], ['is_last', '=', 1], ['is_group', '=', 0]];
+      $msgField = 'from_user,to_user,content as lastContent,create_time as lastSendTime,chat_identify,type,del_user';
+      $lasMsgList = Db::name('message')
+         ->field($msgField)
+         ->whereOr([$map1, $map2])
+         ->order('create_time desc')
+         ->select();
+      // 查询群聊
+      $group = Group::getMyGroup(['gu.user_id' => $user_id, 'gu.status' => 1]);
+      if ($group) {
+         $group = $group->toArray();
+         $group_ids = arrayToString($group, 'group_id');
+         $getGroupLastMsg = Db::name('message')->field($msgField)->where([['to_user', 'in', $group_ids], ['is_group', '=', 1], ['is_last', '=', 1]])->select();
+         $getAtMsg=Db::name('message')->field($msgField)->where([['to_user', 'in', $group_ids], ['is_group', '=', 1]])->whereFindInSet('at',$user_id)->select();
+
+         // halt($getAtMsg);
+         foreach ($group as $k => $v) {
+            $setting = $v['setting'] ? json_decode($v['setting'], true) : ['manage' => 0, 'invite' => 1, 'nospeak' => 0];
+            $group_id = 'group-' . $v['group_id'];
+            $group[$k]['id'] = $group_id;
+            $group[$k]['account'] = $group_id;
+            $group[$k]['avatar'] = avatarUrl($v['avatar'], $v['displayName'], $v['group_id'], 120);
+            $group[$k]['name_py'] = $v['name_py'];
+            $group[$k]['owner_id'] = $v['owner_id'];
+            $group[$k]['role'] = $v['role'];
+            $group[$k]['is_group'] = 1;
+            $group[$k]['setting'] = $setting;
+            $group[$k]['index'] = "[2]群聊";
+            $group[$k]['realname'] = $v['displayName'] . " [群聊]";
+            $group[$k]['is_notice'] = $v['is_notice'];
+            $group[$k]['is_top'] = $v['is_top'];
+            $group[$k]['is_online'] = 1;
+            $group[$k]['is_at'] = 0;
+            if ($getGroupLastMsg) {
+               foreach ($getGroupLastMsg as $key=>$val) {
+                  if ($val['to_user'] == $v['group_id']) {
+                     $group[$k]['type'] =$val['type'];
+                     $group[$k]['lastContent'] = str_encipher($val['lastContent'],false);
+                     $group[$k]['lastSendTime'] = $val['lastSendTime'] * 1000;
+                     // 已经赋值了删除掉提升下次循环的性能
+                     unset($getGroupLastMsg[$key]);
+                     break;
+                  }
+               }
+            }
+            if($getAtMsg){
+               foreach ($getAtMsg as $key=> $val) {
+                  if ($val['to_user'] == $v['group_id']) {
+                     ++$group[$k]['is_at'];
+                     // 已经赋值了删除掉提升下次循环的性能
+                     unset($getAtMsg[$key]);
+                  }
+               }
+            }
+         }
+      }
+      try{
+         Gateway::$registerAddress = config('gateway.registerAddress');
+         $onlineList=Gateway::getAllUidList();
+      }catch(\Exception $e){
+         $onlineList=[];
+      }
+      foreach ($list_chart as $k => $v) {
+         // 是否有消息通知或者置顶聊天
+         $friend = isset($friendList[$v['user_id']]) ? $friendList[$v['user_id']] : [];
+         $list_chart[$k]['id'] = $v['user_id'];
+         $list_chart[$k]['displayName'] = ($friend['nickname'] ?? '') ? : $v['realname'];
+         $list_chart[$k]['name_py'] = $v['name_py'];
+         $list_chart[$k]['avatar'] = avatarUrl($v['avatar'], $v['realname'], $v['user_id'], 120);
+         $list_chart[$k]['lastContent'] = '';
+         $list_chart[$k]['unread'] = 0;
+         $list_chart[$k]['lastSendTime'] = time() * 1000;
+         $list_chart[$k]['is_group'] = 0;
+         $list_chart[$k]['setting'] = [];
+         $list_chart[$k]['is_at'] = 0;
+         $list_chart[$k]['last_login_ip'] = $v['last_login_ip'];
+         $list_chart[$k]['location'] =$v['last_login_ip'] ? implode(" ", \Ip::find($v['last_login_ip'])) : "未知";
+         $is_online=0;
+         if(isset($onlineList[$v['user_id']])){
+            $is_online=1;
+         }
+         $list_chart[$k]['is_online'] = $is_online;
+         
+         $is_top = 0;
+         $is_notice = 1;
+         if ($friend) {
+            $is_top = $friend['is_top'];
+            $is_notice = $friend['is_notice'];
+         }
+         $list_chart[$k]['is_top'] = $is_top;
+         $list_chart[$k]['is_notice'] = $is_notice;
+         if ($unread) {
+            foreach ($unread as $val) {
+               if ($val['from_user'] == $v['user_id']) {
+                  $list_chart[$k]['unread'] = $val['unread'];
+                  break;
+               }
+            }
+         }
+         if ($lasMsgList) {
+            foreach ($lasMsgList as $val) {
+               if ($val['from_user'] == $v['user_id'] || $val['to_user'] == $v['user_id']) {
+                  $content = str_encipher($val['lastContent'],false);
+                  // 屏蔽已删除的消息
+                  if ($val['del_user']) {
+                     $delUser = explode(',', $val['del_user']);
+                     if (in_array($user_id, $delUser)) {
+                        $content = "";
+                     }
+                  }
+                  $list_chart[$k]['type'] = $val['type'];
+                  $list_chart[$k]['lastContent'] = $content;
+                  $list_chart[$k]['lastSendTime'] = $val['lastSendTime'] * 1000;
+
+                  break;
+               }
+            }
+         }
+      }
+      // 合并群聊和联系人
+      $data = array_merge($list_chart, $group);
+      return $data;
+   }
+
+   public static function getList($map)
+   {
+      return self::field(self::$defaultField)->where($map)->select();
+   }
+
+   // 匹配用户列表信息(返回用户信息)
+
+   public static function matchUser($data, $many = false, $field = 'user_id', $cs = 80)
+   {
+      if ($many) {
+         $idr = arrayToString($data, $field, false);
+      } else {
+         $idr = [];
+         if (is_array($field)) {
+            foreach ($field as $v) {
+               $idr[] = $data[$v];
+            }
+         } else {
+            $idr = [$data[$field]];
+         }
+      }
+      $key = array_search(0, $idr);
+      if ($key) {
+         array_splice($idr, $key, 1);
+      }
+      $userList = self::where([['user_id', 'in', $idr]])->field(self::$defaultField)->select()->toArray();
+      $list = [];
+      foreach ($userList as $v) {
+         $v['avatar'] = avatarUrl($v['avatar'], $v['realname'], $v['user_id'], $cs);
+         $v['id'] = $v['user_id'];
+         $list[$v['user_id']] = $v;
+      }
+      return $list;
+   }
+
+   // 匹配用户列表信息(返回data)  
+   public static function matchAllUser($data, $many = false, $field = 'user_id', $key = "userInfo", $cs = 80)
+   {
+      if ($many) {
+         $idr = arrayToString($data, $field);
+         $userList = self::getList([['user_id', 'in', $idr]]);
+         foreach ($data as $k => $v) {
+            foreach ($userList as $vv) {
+               if ($v[$field] == $vv['user_id']) {
+                  $data[$k][$key] = [
+                     'id' => $vv['user_id'],
+                     'displayName' => $vv['realname'],
+                     'account' => $vv['account'],
+                     'name_py' => $vv['name_py'],
+                     'avatar' => avatarUrl($vv['avatar'], $vv['realname'], $vv['user_id'], $cs),
+                  ];
+               }
+            }
+         }
+      } else {
+         $user = self::getUserInfo(['user_id' => $data[$field]]);
+         $data[$key] = [
+            'id' => $user['user_id'],
+            'displayName' => $user['realname'],
+            'account' => $user['account'],
+            'name_py' => $user['name_py'],
+            'avatar' => avatarUrl($user['avatar'], $user['realname'], $user['user_id']),
+         ];
+      }
+      return $data;
+   }
+
+   // 将用户信息转换成联系人信息
+   public static function setContact($user_id){
+      $user=self::where('user_id',$user_id)->field(self::$defaultField)->find();
+      if($user){
+         $user['avatar']=avatarUrl($user['avatar'],$user['realname'],$user['user_id']);
+         $user['id']=$user['user_id'];
+         $user['displayName']=$user['realname'];
+         $user['lastContent']="你们已经成功添加为好友,现在开始聊天吧!";
+         $user['unread']=0;
+         $user['lastSendTime']=time() * 1000;
+         $user['is_group']=0;
+         $user['is_top']=0;
+         $user['is_notice']=1;
+         $user['is_online']=1;
+         $user['type']='event';
+         $user['index']=getFirstChart($user['realname']);
+         return $user;
+      }else{
+         return false;
+      }
+   }
+
+   // 验证账号的合法性
+   public function checkAccount(&$data){
+      $user_id=$data['user_id'] ?? 0;
+      if($user_id){
+         $user=self::find($data['user_id']);
+         if(!$user){
+            $this->error='账户不存在';
+            return false;
+         }
+         if($user->user_id==1 && self::$uid!=1){
+            $this->error='超管账户只有自己才能修改';
+            return false;
+         }
+         $other=self::where([['account','=',$data['account']],['user_id','<>',$data['user_id']]])->find();
+         if($other){
+            $this->error='账户已存在';
+            return false;
+         }
+      }else{
+         $user=self::where('account',$data['account'])->find();
+         if($user){
+               $this->error='账户已存在';
+               return false;
+         }
+      }
+      $config=Config::getSystemInfo();
+      $regauth=$config['sysInfo']['regauth'] ?? 0;
+      $acType=\utils\Regular::check_account($data['account']);
+      switch($regauth){
+            case 1:
+               if($acType!=1){
+                  $this->error='当前系统只允许账号为手机号!';
+                  return false;
+               }
+               break;
+            case 2:
+               if($acType!=2){
+                  $this->error='当前系统只允许账号为邮箱!';
+                  return false;
+               }
+               break;
+            case 3:
+               // 验证账号是否为手机号或者邮箱
+               if(!$acType){
+                  $this->error='账户必须为手机号或者邮箱';
+                  return false;
+               }
+               break;
+            default:
+               break;
+      }
+
+      $data['is_auth'] =$regauth ? 1 : 0;
+      $email=$data['email'] ?? '';
+      if($data['is_auth'] && $acType==2 && !$email){
+            $data['email'] =$data['account'];
+      }
+      return true;
+   }
+}

+ 24 - 0
app/enterprise/validate/User.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * lvzhe [a web admin based ThinkPHP5]
+ */
+
+namespace app\enterprise\validate;
+
+use think\Validate;
+
+class User extends Validate
+{
+    protected $rule = [
+        'account|帐号'      => 'require',
+        'password|密码'     => 'require',
+        'captcha|验证码'     => 'require|captcha',
+        'oldpassword|旧密码' => 'require',
+        'repassword|重复密码' => 'require',
+    ];
+
+    protected $scene = [
+        'password' => ['password', 'oldpassword', 'repassword'],
+        'login'    => ['account', 'password'],
+    ];
+}

+ 19 - 0
app/event.php

@@ -0,0 +1,19 @@
+<?php
+// 事件定义文件
+return [
+    'bind'      => [
+    ],
+
+    'listen'    => [
+        'AppInit'  => [],
+        'HttpRun'  => [],
+        'HttpEnd'  => [],
+        'LogLevel' => [],
+        'LogWrite' => [],
+        'UserRegister'=>['app\common\listener\UserRegister'],
+        'GroupChange'=>['app\enterprise\listener\GroupChange'],
+    ],
+
+    'subscribe' => [
+    ],
+];

+ 173 - 0
app/index/controller/Index.php

@@ -0,0 +1,173 @@
+<?php
+namespace app\index\controller;
+
+use app\enterprise\model\{File,Group,User};
+use think\facade\View;
+use app\manage\model\Config;
+
+class Index
+{
+
+    public function index()
+    {
+        if (!file_exists(PACKAGE_PATH . "install.lock")) {
+            return redirect(url('index/install/index'));
+        }
+        return redirect("/index.html");
+    }
+
+    public function view()
+    {
+        $url=request()->param('src');
+        $suffix=explode('.',$url);
+        $ext=$suffix[count($suffix)-1];
+        return View::fetch('',[
+            'url'  => $url,
+            'ext'=>$ext,
+            'name'=>"预览文件"
+        ]);
+    }
+
+    //    头像生成
+    public function avatar()
+    {
+        circleAvatar(input('str'), input('s') ?: 80, input('uid'));die;
+    }
+
+    // 文件下载
+    public function download()
+    {
+        
+        if (strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false) {
+            throw new \think\Exception('请使用浏览器下载!',400);
+        }
+        $param = request()->param();
+        $file_id = $param['file_id'] ?? 0;
+        if (!$file_id) {
+            throw new \think\Exception('参数错误', 502);
+        }
+        try {
+            $file_id = decryptIds($file_id);
+        } catch (\Exception $e) {
+            throw new \think\Exception($e->getMessage(), 400);
+        }
+        $file = File::find($file_id);
+        if (!$file) {
+            throw new \think\Exception('该文件不存在!',404);
+        }
+        $file = $file->toArray();
+        // 兼容本地文件下载
+        $fileUrl=getDiskUrl();
+        if($fileUrl==request()->domain()){
+            $url=rtrim(public_path(),'/').$file['src'];
+        }else{
+            $url= getFileUrl($file['src']);
+        }
+        return \utils\File::download($url, $file['name'] . '.' . $file['ext'], $file['size'], $file['ext']);
+    }
+
+    // 扫码获取信息
+    public function scanQr(){
+        $param=request()->param();
+        $action=$param['action'] ?? '';
+        $token=$param['token'] ?? '';
+        $realToken=$param['realToken'] ?? '';
+        if(request()->isPost() && $action && $token && $realToken){
+            $actions=[
+                'g'=>'group',
+                'u'=>'user',
+            ];
+            $a=$actions[$action] ?? '';
+            if(!$a){
+                return warning('二维码已失效');
+            }
+            return $this->$a($param);
+        }else{
+            return $this->index();
+        }
+    }
+
+    protected function group($param)
+    {
+        $token=authcode(urldecode($param['realToken']),"DECODE", 'qr');
+        if(!$token){
+            return warning('二维码已失效');
+        }
+        $groupInfo=explode('-',$token);
+        $uid=$groupInfo[0];
+        $group_id=$groupInfo[1];
+        $group=Group::find($group_id);
+        if($group){
+            $group=$group->toArray();
+            $group['avatar']=avatarUrl($group['avatar'],$group['name'],$group_id,120);
+            $group['invite_id']=$uid;
+            $group['id']='group-'.$group_id;
+            $group['action']='groupInfo';
+            return success('',$group);
+        }else{
+            return warning('二维码已失效');
+        }
+    }
+
+    protected function user($param)
+    {
+        $id=decryptIds($param['token']);
+        if(!$id){
+            return warning('二维码已失效');
+        }
+        $user=User::where(['user_id'=>$id])->field(User::$defaultField)->find();
+        if($user){
+            $user=$user->toArray();
+            $user['avatar']=avatarUrl($user['avatar'],$user['realname'],$user['user_id'],120);
+            $user['id']=$user['user_id'];
+            $user['action']='userInfo';
+            return success('',$user);
+        }else{
+            return warning('二维码已失效');
+        }
+    }
+
+    // app下载页
+    public function downApp(){
+        $config=Config::where('name','sysInfo')->value('value');
+        $andriod=getAppDowmUrl('andriod');
+        $winUrl=getAppDowmUrl('windows');
+        $macUrl=getAppDowmUrl('mac');
+        $client=[
+            'andriod_appid'=>env('app.andriod_appid',''),
+            'andriod_webclip'=>env('app.andriod_webclip','') ? : $andriod,
+            'ios_appid'=>env('app.ios_appid',''),
+            'ios_webclip'=>env('app.ios_webclip',''),
+            'win_webclip'=>env('app.win_webclip','') ? : $winUrl,
+            'mac_webclip'=>env('app.mac_webclip','') ? : $macUrl
+        ];
+        $noUrl=false;
+        if(!$client['andriod_appid'] && !$client['andriod_webclip']  && !$client['ios_appid'] && !$client['ios_webclip']){
+           $noUrl=true;
+        }
+        View::assign('noUrl',$noUrl);
+        View::assign('client',$client);
+        View::assign('config',$config);
+        return View::fetch();
+    }
+
+    // 下载APP
+    public function downloadApp(){
+        $platform=request()->param('platform','windows');
+        $config=config('version.'.$platform);
+        $name=config('version.app_name');
+        if($platform=='andriod'){
+            $packageName=$name."_Setup_".$config['version'].".apk";
+        }elseif($platform=='mac'){
+            $packageName=$name."_Setup_".$config['version'].".dmg";
+        }else{
+            $packageName=$name."_Setup_".$config['version'].".exe";
+        }
+        $file=PACKAGE_PATH . $packageName;
+        if(is_file($file)){
+            return \utils\File::download($file, $packageName);
+        }else{
+            return warning('文件不存在');
+        }
+    }
+}

+ 554 - 0
app/index/controller/Install.php

@@ -0,0 +1,554 @@
+<?php
+// +----------------------------------------------------------------------
+// | Description: 安装
+// +----------------------------------------------------------------------
+// | Author:  xiekunyu | raingad@foxmail.com 
+// +----------------------------------------------------------------------
+
+namespace app\index\controller;
+use think\facade\Request;
+use think\facade\Db;
+use think\facade\View;
+use think\facade\Config;
+use Env;
+
+class Install
+{
+    // private $count = 100;
+    // private $now = 0; 
+    protected $status=1;
+
+    public function _initialize()
+    {
+        /*防止跨域*/      
+        header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);
+        header('Access-Control-Allow-Credentials: true');
+        header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
+        header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, authKey, sessionId");
+    }
+
+    /**
+     * [index 安装步骤]
+     * @author Michael_xu 
+     * @param  
+     */    
+    public function index()
+    {
+        $protocol = strpos(strtolower($_SERVER['SERVER_PROTOCOL']), 'https') === false ? 'http' : 'https';
+
+        if (file_exists(PACKAGE_PATH . "install.lock")) {
+            echo "<meta http-equiv='content-type' content='text/html; charset=UTF-8'> <script>alert('请勿重复安装!');location.href='".$protocol."://".$_SERVER["HTTP_HOST"]."';</script>";
+            die();     
+        }
+
+        if (!file_exists(PUBLIC_PATH . "sql/database.sql")) {
+            echo "<meta http-equiv='content-type' content='text/html; charset=UTF-8'> <script>alert('缺少必要的数据库文件!');location.href='".$protocol."://".$_SERVER["HTTP_HOST"]."';</script>";
+            die();     
+        }
+
+        return View::fetch('index');
+    }
+
+    // 检测环境配置和文件夹读写权限
+    public function getEnv()
+    {
+        $data           = [];
+        $data['env']    = self::checkEnv();
+        $data['dir']    = self::checkDir();
+        $data['version'] = $this->version();
+        $data['status'] = $this->status;
+        return success('',$data);
+    }
+
+    //版本
+    public function version()
+    {
+        $res = include(CONF_PATH.'app.php'); 
+        $data=[
+            'VERSION'=>$res['app_version'],
+            'RELEASE'=>$res['app_release'],
+        ];
+        return $data ? : array('VERSION' => '0.5.18','RELEASE' => '20210518'); 
+    }    
+
+    // 检查数据库
+    public function checkDatabase(){
+         
+        if (file_exists(PACKAGE_PATH . "install.lock")) {
+            return warning('请勿重复安装!');       
+        } 
+        if (!file_exists(PUBLIC_PATH . "sql/database.sql")) {
+            return warning('缺少必要的数据库文件!');     
+        } 
+        $temp = request()->param();
+        $db_config = $temp['form'];
+        $db_config['type'] = 'mysql';
+        if (empty($db_config['hostname'])) {
+            return warning('请填写数据库主机!');
+        }           
+        if (empty($db_config['hostport'])) {
+            return warning('请填写数据库端口!');
+        }
+        if (preg_match('/[^0-9]/', $db_config['hostport'])) {
+            return warning('数据库端口只能是数字!');
+        }
+        if (empty($db_config['database'])) {
+            return warning('请填写数据库名!');
+        }
+        if (empty($db_config['username'])) {
+            return warning('请填写数据库用户名!');
+        }
+        if (empty($db_config['password'])) {
+            return warning('请填写数据库密码!');
+        }        
+        if (empty($db_config['prefix'])) {
+            return warning('请填写表前缀!');
+        }
+        if (empty($db_config['redishost'])) {
+            return warning('请填写redis主机地址!');
+        }
+        if (empty($db_config['redisport'])) {
+            return warning('请填写redis端口!');
+        }
+        if (preg_match('/[^a-z0-9_]/i', $db_config['prefix'])) {
+            return warning('表前缀只能包含数字、字母和下划线!');
+        }
+        
+        // 创建数据库配置文件
+        self::mkDatabase($db_config);
+        // 检测数据库连接
+        try{
+            $conn=mysqli_connect($db_config['hostname'], $db_config['username'], $db_config['password']);
+            // 检测连接
+            if ($conn->connect_error) {
+                return warning("连接失败: " . $conn->connect_error);
+            }
+            // 创建数据库
+            $sql = "CREATE DATABASE IF NOT EXISTS `".$db_config['database']."` default collate utf8_general_ci ";
+            if ($conn->query($sql) === TRUE) {
+                return success('数据库连接成功',['status'=>1]);
+            } else{
+                return warning('没有找到您填写的数据库名且无法创建!请检查连接账号是否有创建数据库的权限!');
+            }
+        }catch(\Exception $e){
+            return warning('数据库连接失败,请检查数据库配置!');
+        }
+        
+    }
+
+    // 执行安装
+    public function install(){
+        $db_config=Config::get('database.connections.mysql');
+        $sql = file_get_contents( PUBLIC_PATH . "sql/database.sql");
+        $sqlList = parse_sql($sql, 0, ['yu_' => $db_config['prefix']]);
+        $install_count=0;
+        if ($sqlList) {
+            $sqlList = array_filter($sqlList);
+            $install_count = count($sqlList);
+            foreach ($sqlList as $k=>$v) {
+                try {
+                    $temp_sql = $v.';';
+                    Db::query($temp_sql);
+                } catch(\Exception $e) {
+                    touch(PACKAGE_PATH . "install.lock");
+                    return error('数据库sql安装出错,请操作数据库手动导入sql文件'.$e->getMessage());
+                }
+            }
+        } 
+        touch(PACKAGE_PATH . "install.lock");
+        return success('安装成功',['status'=>$this->status],$install_count);
+    }
+
+	//ajax 进度条
+    public function progress()
+    {
+        $data['length'] = session('install_count');
+        $data['now'] = session('install_now');
+        return success('',$data);
+    }
+
+        //添加database.php文件
+        private function mkDatabase(array $data)
+        {
+            $code = <<<INFO
+APP_DEBUG = true
+
+[APP]
+DEFAULT_TIMEZONE = Asia/Shanghai
+ID = a1b2c3d4e5f
+SECRET = GHJKUG123456sdfghjkl
+API_STATUS = true
+
+# 安卓包名,如果上架了市场,根据市场ID跳转市场
+ANDRIOD_APPID = 
+#安卓下载地址,如果未设置会检测根目录是否有app.apk
+ANDRIOD_WEBCLIP =https://emoji.raingad.com/file/raingad.apk
+#APPSTORE市场ID
+IOS_APPID =
+#IOS下载地址,如果没有市场的ID则使用下载地址
+IOS_WEBCLIP =
+#windows下载地址
+WIN_WEBCLIP = 
+#mac下载地址
+MAC_WEBCLIP = 
+
+[DATABASE]
+TYPE = {$data['type']}
+HOSTNAME = {$data['hostname']}
+DATABASE = {$data['database']}
+USERNAME = {$data['username']}
+PASSWORD = {$data['password']}
+HOSTPORT = {$data['hostport']}
+CHARSET = utf8
+DEBUG = true
+prefix = {$data['prefix']}
+[LANG]
+default_lang = zh-cn
+
+[REDIS]
+HOST = {$data['redishost']}
+PORT = {$data['redisport']}
+PASSWORD ={$data['redispass']}
+
+
+[AES]
+TOKEN_KEY = tHTi8USApxsdfnhTM
+LOGIN_KEY = t2fe6HMnmssswDVi2
+#聊天内容加密,如果不加密则留空,一旦加密就不能修改,如果修改了需要清空所有聊天记录
+CHAT_KEY  =
+
+[JWT]
+SECRET = 17b190c0d612321f94f57325ae5a8b4c
+TTL = 2592000
+
+[WORKER]
+NAME = businessWorker
+PORT = 8282
+# 根据自己的核心数而配置
+COUNT = 1
+START_PORT = 2300
+REGISTER_ADDRESS =127.0.0.1:1236
+lAN_IP = 127.0.0.1
+# 分部署部署只需要启动一个gateway,其他的gateway只需要配置register_address即可
+REGISTER_DEPLOY = true
+
+#配置预览功能,本系统主要使用第三方的预览工具,比如永中云转换,自带预览系统
+[PREVIEW]
+# 自带预览系统URL,主要用于预览媒体文件,已内置,必须要有最后的/斜杠
+own=
+# 永中云文件预览,主要用于文档预览,必须要有最后的/斜杠
+yzdcs= 
+# 永中云api code
+keycode=17444844212312
+
+[UNIPUSH]
+# unipush的云函数转url地址,主要用于推送
+URL=
+# unipush直接推送通知栏还是app接收后再创建通知栏
+IS_FORCE=false
+
+# 配置对象储存,主要用于聊天文件储存,可以通过后台进行配置
+
+[FILESYSTEM]
+driver=local
+aliyun_accessId=false
+aliyun_accessSecret=false
+aliyun_bucket=false
+aliyun_endpoint=false
+aliyun_url=false
+qiniu_accessKey=false
+qiniu_secretKey=false
+qiniu_bucket=false
+qiniu_url=false
+qcloud_region=false
+qcloud_appId=false
+qcloud_secretId=false
+qcloud_secretKey=false
+qcloud_bucket=false
+qcloud_cdn=false
+INFO;
+    
+            @file_put_contents( root_path().'.env', $code);
+            $database=env('database.database');
+            // 判断写入是否成功
+            if (empty($database) || $database != $data['database']) {
+                return warning('[.env]数据库配置写入失败!');
+            }
+            return true;
+        }
+
+    //添加database.php文件
+    private function mkDatabase1(array $data)
+    {
+        $code = <<<INFO
+<?php
+return [
+    // 自定义时间查询规则
+    'time_query_rule' => [],
+
+    // 自动写入时间戳字段
+    // true为自动识别类型 false关闭
+    // 字符串则明确指定时间字段类型 支持 int timestamp datetime date
+    'auto_timestamp'  => true,
+
+    // 时间字段取出后的默认时间格式
+    'datetime_format' => 'Y-m-d H:i:s',
+    'default'    =>    '{$data['type']}',
+    'connections'    =>    [
+        'mysql'    =>    [
+            // 数据库类型
+            'type'            =>env('database.type', '{$data['type']}'),
+            // 服务器地址
+            'hostname'        => env('database.hostname','{$data['hostname']}'),
+            // 数据库名
+            'database'        => env('database.database','{$data['database']}'),
+            // 用户名
+            'username'        => env('database.username','{$data['username']}'),
+            // 密码
+            'password'        => env('database.password','{$data['password']}'),
+            // 端口
+            'hostport'        => env('database.hostport','{$data['hostport']}'),
+            // 数据库连接参数
+            'params'            => [],
+            // 数据库编码默认采用utf8
+            'charset'           => env('database.charset', 'utf8'),
+            // 数据库表前缀
+            'prefix'            => env('database.prefix', '{$data['prefix']}'),
+
+            // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+            'deploy'            => 0,
+            // 数据库读写是否分离 主从式有效
+            'rw_separate'       => false,
+            // 读写分离后 主服务器数量
+            'master_num'        => 1,
+            // 指定从服务器序号
+            'slave_no'          => '',
+            // 是否严格检查字段是否存在
+            'fields_strict'     => true,
+            // 是否需要断线重连
+            'break_reconnect'   => false,
+            // 监听SQL
+            'trigger_sql'       => env('app_debug', true),
+            // 开启字段缓存
+            'fields_cache'      => false,
+            // 字段缓存路径
+            'schema_cache_path' => app()->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR,
+            ]
+        ]
+
+];
+
+INFO;
+        file_put_contents( CONF_PATH.'database.php', $code);
+        // 判断写入是否成功
+        $config = include CONF_PATH.'database.php';
+        if (empty($config['database']) || $config['database'] != $data['database']) {
+            return warning('[config/database.php]数据库配置写入失败!');
+        }
+        return true;
+    }
+
+    //检查目录权限
+    public function check_dir_iswritable($dir_path){ 
+        $dir_path=str_replace( '\\','/',$dir_path); 
+        $is_writale=1; 
+        if (!is_dir($dir_path)) { 
+            $is_writale=0; 
+            return $is_writale; 
+        } else { 
+            $file_hd=@fopen($dir_path.'/test.txt','w'); 
+            if (!$file_hd) { 
+                @fclose($file_hd); 
+                @unlink($dir_path.'/test.txt'); 
+                $is_writale=0; 
+                return $is_writale; 
+            } 
+            $dir_hd = opendir($dir_path); 
+            while (false !== ($file=readdir($dir_hd))) { 
+                if ($file != "." && $file != "..") { 
+                    if (is_file($dir_path.'/'.$file)) { 
+                        //文件不可写,直接返回 
+                        if (!is_writable($dir_path.'/'.$file)) { 
+                            return 0; 
+                        }  
+                    } else { 
+                        $file_hd2=@fopen($dir_path.'/'.$file.'/test.txt','w'); 
+                        if (!$file_hd2) { 
+                            @fclose($file_hd2); 
+                            @unlink($dir_path.'/'.$file.'/test.txt'); 
+                            $is_writale=0; 
+                            return $is_writale; 
+                        } 
+                        //递归 
+                        $is_writale=$this->check_dir_iswritable($dir_path.'/'.$file); 
+                    } 
+                } 
+            } 
+        } 
+        return $is_writale; 
+    }    
+
+    /**
+     * 环境检测
+     * @return array
+     */
+    private function checkEnv()
+    {
+        // $items = [
+        //     'os'      => ['操作系统', PHP_OS, '类Unix', 'ok'],
+        //     'php'     => ['PHP版本', PHP_VERSION, '7.3 ( <em style="color: #888; font-size: 12px;">>= 7.0</em> )', 'ok','性能更佳'],
+        //     'gd'      => ['gd', '开启', '开启', 'ok'],
+        //     'openssl' => ['openssl', '开启', '开启', 'ok'],
+        //     'pdo' => ['pdo', '开启', '开启', 'ok'],
+        // ];
+        $items = [
+            ['name'=>'操作系统','alias'=>'os','value'=>PHP_OS,'status'=> 'ok','description'=>"操作系统需要类Unix"],
+            ['name'=>'PHP版本','alias'=>'version','value'=> PHP_VERSION,  'status'=>'ok','description'=>"PHP版本必须大于7.0"],
+            ['name'=>'gd库','alias'=>'gd', 'value'=>'开启', 'status'=>'ok','description'=>"开启GD库"],
+            ['name'=>'pdo','alias'=>'pdo', 'value'=>'开启', 'status'=>'ok','description'=>"PDO扩展"],
+            ['name'=>'openssl','alias'=>'openssl', 'value'=>'开启',  'status'=>'ok','description'=>"OPENSSL扩展"],
+            ['name'=>'pcntl','alias'=>'pcntl', 'value'=>'开启',  'status'=>'ok','description'=>"pcntl扩展,消息推送必须开启"],
+            ['name'=>'posix','alias'=>'posix', 'value'=>'开启',  'status'=>'ok','description'=>"posix扩展,消息推送必须开启"],
+            ['name'=>'event','alias'=>'event', 'value'=>'开启',  'status'=>'ok','description'=>"event扩展(可选安装),处理消息推送高并发"],
+        ];
+        foreach($items as $k=>$v){
+            $status='ok';
+            switch($v['alias']){
+                case 'php':
+                    if (substr($v['value'],0,3) < '7.0') {
+                        $status='no';
+                        $this->status=0;
+                    }
+                    break;
+                case 'gd':
+                    if (!extension_loaded('gd')) {
+                        $items[$k]['value'] = '未开启';
+                        $status='no';
+                        $this->status=0;
+                    }
+                    break;
+                case 'openssl':
+                    if (!extension_loaded('openssl')) {
+                        $items[$k]['value'] = '未开启';
+                        $status='no';
+                        $this->status=0;
+                    }
+                    break;
+                case 'pdo':
+                    if (!extension_loaded('pdo')) {
+                        $this->status=0;
+                        $items[$k]['value'] = '未开启';
+                        $status='no';
+                    }
+                    break;
+                case 'pcntl':
+                    if (PHP_OS === 'Linux') {
+                        if (!extension_loaded('pcntl')) {
+                            $items[$k]['value'] = '未开启';
+                            $status='no';
+                        }
+                    } else {
+                        $items[$k]['value'] = 'win无需开启';
+                    }
+                    break;
+                case 'posix':
+                    if (PHP_OS === 'Linux') {
+                        if (!extension_loaded('posix')) {
+                            $this->status=0;
+                            $items[$k]['value'] = '未开启';
+                            $status='no';
+                        }
+                    } else {
+                        $items[$k]['value'] = 'win无需开启';
+                    }
+                    
+                    break;
+                case 'event':
+                    if (PHP_OS === 'Linux') {
+                        if (!extension_loaded('event')) {
+                            $items[$k]['value'] = '未开启';
+                            $status='no';
+                        }
+                    } else {
+                        $items[$k]['value'] = 'win无需开启';
+                    }
+                    break;
+            }
+            
+            $items[$k]['status'] = $status;
+        }
+        return $items;
+    }
+    
+    /**
+     * 目录权限检查
+     * @return array
+     */
+    private function checkDir()
+    {
+        $items = [
+            ['dir', root_path().'app', 'app', '读写', '读写', 'ok'],
+            ['dir', root_path().'extend', 'extend', '读写', '读写', 'ok'],
+            ['dir', root_path().'runtime', './temp', '读写', '读写', 'ok'],
+            ['dir', root_path().'public', './upload', '读写', '读写', 'ok'],
+            ['file', root_path().'config', 'config', '读写', '读写', 'ok'],
+        ];
+        $items = [
+            ['path'=>root_path().'app', 'dir'=>'app', 'value'=>'读写', 'type'=>'dir','status'=>'ok'],
+            ['path'=>root_path().'extend', 'dir'=>'extend', 'value'=>'读写', 'type'=>'dir','status'=>'ok'],
+            ['path'=> root_path().'runtime', 'dir'=>'runtime', 'value'=>'读写', 'type'=>'dir','status'=>'ok'],
+            ['path'=>root_path().'public', 'dir'=>'public', 'value'=>'读写', 'type'=>'dir','status'=>'ok'],
+            ['path'=>root_path().'config', 'dir'=>'config', 'value'=>'读写', 'type'=>'file','status'=>'ok'],
+        ];
+        $status=1;
+        foreach ($items as $k=>$v) {
+            if ($v['type'] == 'dir') {// 文件夹
+                if (!is_writable($v['path'])) {
+                    if (is_dir($v['path'])) {
+                        $items[$k]['value'] = '不可写';
+                        $items[$k]['status'] = 'no';
+                    } else {
+                        $items[$k]['value'] = '不存在';
+                        $items[$k]['status'] = 'no';
+                    }
+                    $this->status=0;
+                }
+            } else {// 文件
+                if (!is_writable($v['path'])) {
+                    $items[$k]['value'] = '不可写';
+                    $items[$k]['status'] = 'no';
+                    $this->status=0;
+                }
+            }
+        }
+        return $items;
+    }
+
+    /**
+     * 验证序列号
+     * @param 
+     * @return
+     */        
+    public function checkCodeOld($username) {
+        $encryption = md5($username);
+        $substr = substr($username, strlen($username)-6);
+        $subArr = str_split($substr, 1);
+        $code = '';
+        for ($i = 0; $i <= 5; $i++) {
+            $code .= $encryption[$subArr[$i]];
+        }
+        return $code;
+    }
+
+    //写入license文件
+    private function mkLicense($wkcode)
+    {
+        file_put_contents( CONF_PATH.'license.dat', $wkcode);
+        // 判断写入是否成功
+        // $config = include CONF_PATH.'license.dat';
+        // if (empty($config)) {
+        //     return resultArray(['error' => 'license配置写入失败!']);
+        // }
+        return true;
+    }    
+}

+ 17 - 0
app/index/route/app.php

@@ -0,0 +1,17 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+use think\facade\Route;
+Route::rule('avatar/:str/:s/:uid','index/avatar');
+Route::rule('view','index/index/view');
+Route::rule('filedown/:file_id','index/download');
+Route::rule('scan/:action/:token','index/scanQr');
+Route::rule('downapp','index/index/downapp');
+Route::rule('downloadApp/:platform','index/index/downloadApp');

+ 102 - 0
app/manage/controller/Config.php

@@ -0,0 +1,102 @@
+<?php
+/**
+ * Created by PhpStorm
+ * User Julyssn
+ * Date 2022/12/14 17:24
+ */
+
+
+namespace app\manage\controller;
+
+
+use app\BaseController;
+use app\manage\model\{Config as Conf};
+use think\facade\Cache;
+class Config extends BaseController
+{
+    /**
+     * 获取单个配置
+     * @return \think\response\Json
+     */
+    public function getInfo()
+    {
+        $name=$this->request->param('name');
+        $data = Conf::where(['name'=>$name])->value('value');
+        return success('', $data);
+    }
+
+    /**
+     * 获取配置
+     * @return \think\response\Json
+     */
+    public function getAllConfig()
+    {
+        $name=['sysInfo','chatInfo','smtp','fileUpload'];
+        $list = Conf::where(['name'=>$name])->select();
+        return success('', $list);
+    }
+
+    /**
+     * 修改配置
+     * @return \think\response\Json
+     */
+    public function setConfig()
+    {
+        $name = $this->request->param('name');
+        $value = $this->request->param('value');
+        if(Conf::where(['name'=>$name])->find()){
+            Conf::where(['name'=>$name])->update(['value'=>$value]);
+        }else{
+            Conf::create(['name'=>$name,'value'=>$value]);
+        }
+        if($name=='fileUpload'){
+            updateEnv('driver',$value['disk']);
+            updateEnv('own',$value['preview']);
+            foreach ($value['aliyun'] as $k=>$v){
+                if($v){
+                    updateEnv('aliyun_'.$k,$v);
+                }
+            }
+            foreach ($value['qiniu'] as $k=>$v){
+                if($v){
+                    updateEnv('qiniu_'.$k,$v);
+                }
+            }
+            foreach ($value['qcloud'] as $k=>$v){
+                if($v){
+                    updateEnv('qcloud_'.$k,$v);
+                }
+            }
+        }else{
+            // 更新系统缓存
+            Conf::getSystemInfo(true);
+        }
+        return success('保存成功');
+    }
+
+    /**
+     * 获取邀请链接
+     * @return \think\response\Json
+     */
+    public function getInviteLink(){
+        $uid=$this->userInfo['user_id'];
+        // 邀请码仅两天有效
+        $code=\utils\Str::random(8);
+        Cache::set($code,$uid,172800);
+        $url=request()->domain().'/index.html/#/register?inviteCode='.$code;
+        return success('',$url);
+    }
+
+    // 发送测试邮件
+    public function sendTestEmail(){
+        $email=$this->request->param('email');
+        if(!$email || !(\utils\Regular::is_email($email))){
+            return warning('请输入正确的邮箱');
+        }
+        $conf=Conf::where(['name'=>'smtp'])->value('value');
+        $mail=new \mail\Mail($conf);
+        $mail->sendEmail([$email],'测试邮件','这是一封测试邮件,当您收到之后表明您的所有配置都是正确的!');
+        return success('发送成功');
+
+    } 
+}

+ 166 - 0
app/manage/controller/Group.php

@@ -0,0 +1,166 @@
+<?php
+/**
+ * Created by PhpStorm
+ * User raingad@foxmail.com
+ * Date 2022/12/14 17:24
+ */
+namespace app\manage\controller;
+use app\BaseController;
+use app\enterprise\model\{User as UserModel,GroupUser,Group as GroupModel};
+use think\facade\Db;
+
+class Group extends BaseController
+{
+    // 获取群聊列表
+    public function index()
+    {
+        $map = [];
+        $model=new GroupModel();
+        $param = $this->request->param();
+        //搜索关键词
+        if ($keyword = $this->request->param('keywords')) {
+            $model = $model->whereLike('name|name_py', '%' . $keyword . '%');
+        }
+        // 排序
+        $order='group_id DESC';
+        if ($param['order_field'] ?? '') {
+            $order = orderBy($param['order_field'],$param['order_type'] ?? 1);
+        }
+        $list = $this->paginate($model->where($map)->order($order));
+        if ($list) {
+            $data = $list->toArray()['data'];
+            $userList=UserModel::matchUser($data,true,'owner_id',120);
+            foreach($data as $k=>$v){
+                $data[$k]['avatar']=avatarUrl($v['avatar'],$v['name'],$v['group_id'],120);
+                $data[$k]['owner_id_info']=$userList[$v['owner_id']] ?? [];
+            }
+        }
+        return success('', $data, $list->total(), $list->currentPage());
+    }
+
+    // 更换群主
+    public function changeOwner()
+    {
+        $group_id = $this->request->param('group_id');
+        $user_id = $this->request->param('user_id');
+        $group=GroupModel::where('group_id',$group_id)->find();
+        if(!$group){
+            return warning('群组不存在');
+        }
+        $user=UserModel::where('user_id',$user_id)->find();
+        if(!$user){
+            return warning('用户不存在');
+        }
+        Db::startTrans();
+        try{
+            GroupUser::where('group_id',$group_id)->where('user_id',$user_id)->update(['role'=>1]);
+            GroupUser::where('group_id',$group_id)->where('user_id',$group->owner_id)->update(['role'=>3]);
+            $group->owner_id=$user_id;
+            $group->save();
+            wsSendMsg($group_id,"changeOwner",['group_id'=>'group-'.$group_id,'user_id'=>$user_id],1);
+            Db::commit();
+            return success('保存成功');
+        }catch (\Exception $e){
+            Db::rollback();
+            return warning('更换失败');
+        }
+    }
+
+    // 解散群聊
+    public function del()
+    {
+        $group_id = $this->request->param('group_id');
+        $group=GroupModel::where('group_id',$group_id)->find();
+        if(!$group){
+            return warning('群组不存在');
+        }
+        Db::startTrans();
+        try{
+            // 删除团队成员
+            GroupUser::where('group_id',$group_id)->delete();
+            // 删除团队
+            GroupModel::destroy($group_id);
+            wsSendMsg($group_id,"removeGroup",['group_id'=>'group-'.$group_id],1);
+            Db::commit();
+            return success('解散成功');
+        }catch (\Exception $e){
+            Db::rollback();
+            return warning('解散失败');
+        }
+    }
+
+    // 添加群成员
+    public function addGroupUser(){
+        $param = $this->request->param();
+        $uid=$this->userInfo['user_id'];
+        $group_id = $param['group_id'];
+        $group=GroupModel::where('group_id',$group_id)->find();
+        if(!$group){
+            return warning('群组不存在');
+        }
+        $user_ids=$param['user_ids'];
+        $data=[];
+        try{
+            foreach($user_ids as $k=>$v){
+                $data[]=[
+                    'group_id'=>$group_id,
+                    'user_id'=>$v,
+                    'role'=>3,
+                    'invite_id'=>$uid
+                ];
+            }
+            $groupUser=new GroupUser;
+            $groupUser->saveAll($data);
+            $url=GroupModel::setGroupAvatar($group_id);
+            wsSendMsg($group_id,"addGroupUser",['group_id'=>"group-".$group_id,'avatar'=>$url],1);
+            return success('添加成功');
+        }catch(\Exception $e){
+                return error($e->getMessage());
+        }
+        
+    }
+
+    // 删除群成员
+    public function delGroupUser(){
+        $param = $this->request->param();
+        $group_id = $param['group_id'];
+        $group=GroupModel::where('group_id',$group_id)->find();
+        if(!$group){
+            return warning('群组不存在');
+        }
+        $user_id=$param['user_id'];
+        $groupUser=GroupUser::where(['group_id'=>$group_id,'user_id'=>$user_id])->find();
+        if($groupUser){
+            $groupUser->delete();
+            wsSendMsg($group_id,"removeUser",['group_id'=>'group-'.$group_id],1);
+            return success('删除成功');
+        }else{
+            return warning('删除失败!');
+        }
+        
+    }
+
+    // 设置管理员
+    public function setManager(){
+       $param = $this->request->param();
+       $group_id = $param['group_id'];
+        $group=GroupModel::where('group_id',$group_id)->find();
+        if(!$group){
+            return warning('群组不存在');
+        }
+       $user_id=$param['user_id'];
+       $role=$param['role'];
+       $groupUser=GroupUser::where(['group_id'=>$group_id,'user_id'=>$user_id])->find();
+       if($groupUser){
+          $groupUser->role=$role;
+          $groupUser->save();
+          wsSendMsg($group_id,"setManager",['group_id'=>'group-'.$group_id],1);
+          return success('设置成功');
+       }else{
+          return warning('设置失败!');
+       }
+       
+    }
+
+
+}

+ 189 - 0
app/manage/controller/Task.php

@@ -0,0 +1,189 @@
+<?php
+/**
+ * Created by PhpStorm
+ * User Julyssn
+ * Date 2022/12/14 17:24
+ */
+
+
+namespace app\manage\controller;
+
+
+use app\BaseController;
+use easyTask\Terminal;
+use think\App;
+use think\facade\Console;
+use think\Response;
+
+class Task extends BaseController
+{
+    /**
+     * 项目根目录
+     * @var string
+     */
+    protected $rootPath;
+
+    protected $taskNames = [
+        'schedule' => '计划任务',
+        'queue' => '消息队列',
+        'worker' => '消息推送',
+        'clearStd' => '清理日志',
+    ];
+
+    public function __construct(App $app)
+    {
+        parent::__construct($app);
+
+        $this->rootPath = root_path();
+        chdir($this->rootPath);
+    }
+
+    /**
+     * 任务列表
+     * @return Response
+     */
+    public function getTaskList()
+    {
+        $data = $this->taskMsg();
+
+        if (!count($data)) {
+            return warning('');
+        }
+
+        foreach ($data as &$datum) {
+            $expName = explode('_', $datum['name']);
+
+            $datum['remark'] = $this->taskNames[$expName[count($expName) - 1]] ?? "未知任务";
+        }
+        unset($datum);
+        return success('', $data);
+    }
+
+    /**
+     * 启动全部进程
+     * @return Response
+     */
+    public function startTask()
+    {
+        if(strpos(strtolower(PHP_OS), 'win') === 0)
+        {
+            return warning("windows启动请运行根目录下的:start_for_win.bat");
+        }
+
+        if (count($this->taskMsg())) {
+            return warning('进程已启动');
+        }
+
+        // 启动
+        $out = Terminal::instance(2)->exec('php think task start');
+        if (!count($this->analysisMsg($out))) {
+            return warning('启动失败');
+        }
+
+        return success('启动成功');
+    }
+
+    /**
+     * 强制停止全部进程
+     * @return Response
+     */
+    public function stopTask()
+    {
+        if (!count($this->taskMsg())) {
+            return warning('进程未启动');
+        }
+
+        // 强制停止
+        Terminal::instance(2)->exec('php think task stop force');
+
+        return success('停止成功');
+    }
+
+    /**
+     * 获取单个任务日志
+     * @return Response
+     */
+    public function getTaskLog()
+    {
+        $name = $this->request->param('name');
+
+        $path = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'easy_task' . DIRECTORY_SEPARATOR . 'Std' . DIRECTORY_SEPARATOR;
+
+        if (!file_exists($path . 'exec_' . $name . '.std')) {
+            $expName = explode('_', $name);
+            $name    = $expName[count($expName) - 1];
+            if (!file_exists($path . 'exec_' . $name . '.std')) {
+                return warning('日志不存在');
+            }
+        }
+
+        return success('', file_get_contents($path . 'exec_' . $name . '.std'));
+    }
+
+    /**
+     * 清理单个任务日志
+     * @return Response
+     */
+    public function clearTaskLog()
+    {
+        $name = $this->request->param('name');
+
+        $path = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'easy_task' . DIRECTORY_SEPARATOR . 'Std' . DIRECTORY_SEPARATOR;
+
+        if (!file_exists($path . 'exec_' . $name . '.std')) {
+            $expName = explode('_', $name);
+            $name    = $expName[count($expName) - 1];
+            if (!file_exists($path . 'exec_' . $name . '.std')) {
+                return warning('日志不存在');
+            }
+        }
+
+        file_put_contents($path . 'exec_' . $name . '.std', '');
+        return success('清理成功');
+    }
+
+
+    /**
+     * 获取运行状态
+     * @return array
+     */
+    private function taskMsg()
+    {
+        $out = Terminal::instance(2)->exec('php think task status');
+        return $this->analysisMsg($out);
+    }
+
+    /**
+     * 解析数据
+     * @param string $out 带解析数据
+     * @return array
+     */
+    private function analysisMsg(string $out)
+    {
+        $re = '/│ *([\w+]+) *│ *([\w+]+)[ ]*│ *([\w+]+|[0-9- :]+) *│ *([\w+]+) *│ *([\w+]+) *│ *([\w+]+) *│/m';
+
+        preg_match_all($re, $out, $matches, PREG_SET_ORDER, 0);
+
+        if (!count($matches)) {
+            return [];
+        }
+
+        $data  = [];
+        $names = $matches[0];
+        unset($names[0]);
+        $names = array_values($names);
+        unset($matches[0]);
+
+        foreach ($matches as $match) {
+            $temp = [];
+            foreach ($match as $key => $item) {
+                if ($key !== 0) {
+                    $temp[$names[$key - 1]] = $item;
+                }
+            }
+            $data[] = $temp;
+        }
+
+        return $data;
+    }
+}

+ 192 - 0
app/manage/controller/User.php

@@ -0,0 +1,192 @@
+<?php
+/**
+ * Created by PhpStorm
+ * User raingad@foxmail.com
+ * Date 2022/12/14 17:24
+ */
+namespace app\manage\controller;
+use app\BaseController;
+use app\enterprise\model\{User as UserModel,GroupUser,Friend};
+use app\manage\model\Config;
+use think\facade\Db;
+
+class User extends BaseController
+{
+    // 获取用户列表
+    public function index()
+    {
+        $map = [];
+        $model=new UserModel();
+        $param = $this->request->param();
+        //搜索关键词
+        if ($keyword = $this->request->param('keywords')) {
+            $model = $model->whereLike('realname|account|name_py|email', '%' . $keyword . '%');
+        }
+        // 排序
+        $order='user_id DESC';
+        if ($param['order_field'] ?? '') {
+            $order = orderBy($param['order_field'],$param['order_type'] ?? 1);
+        }
+        $list = $this->paginate($model->where($map)->order($order));
+        if ($list) {
+            $data = $list->toArray()['data'];
+            foreach($data as $k=>$v){
+                $data[$k]['avatar']=avatarUrl($v['avatar'],$v['realname'],$v['user_id'],120);
+                $data[$k]['location']=$v['last_login_ip'] ? implode(" ", \Ip::find($v['last_login_ip'])) : '--';
+                $data[$k]['reg_location']=$v['register_ip'] ? implode(" ", \Ip::find($v['register_ip'])) : '--';
+                $data[$k]['last_login_time']=$v['last_login_time'] ? date('Y-m-d H:i:s',$v['last_login_time']) : '--';
+                unset($data[$k]['password']);
+            }
+        }
+        return success('', $data, $list->total(), $list->currentPage());
+    }
+
+    // 添加用户
+    public function add()
+    {
+        try{
+            $data = $this->request->param();
+            $user=new UserModel();
+            $verify=$user->checkAccount($data);
+            if(!$verify){
+                return warning($user->getError());
+            }
+            $salt=\utils\Str::random(4);
+            $data['password'] = password_hash_tp($data['password'],$salt);
+            $data['salt'] =$salt;
+            $data['register_ip'] =$this->request->ip();
+            $data['name_py'] = pinyin_sentence($data['realname']);
+            $user->save($data);
+            $data['user_id']=$user->user_id;
+            return success('添加成功', $data);
+        }catch (\Exception $e){
+            return error('添加失败');
+        }
+    }
+
+    // 修改用户    
+    public function edit()
+    {
+        try{
+            $data = $this->request->param();
+            $user=new UserModel();
+            $verify=$user->checkAccount($data);
+            if(!$verify){
+                return warning($user->getError());
+            }
+            $user=UserModel::find($data['user_id']);
+            $user->account =$data['account'];
+            $user->realname =$data['realname'];
+            $user->email =$data['email'];
+            $user->remark=$data['remark'];
+            $user->sex =$data['sex'];
+            // 只有超管才能设置管理员
+            if($this->userInfo['user_id']==1){
+                $user->role =$data['role'];
+            }
+            $user->status =$data['status'];
+            $user->name_py= pinyin_sentence($data['realname']);
+            $user->save();
+            return success('修改成功', $data);
+        }catch (\Exception $e){
+            return error('修改失败');
+        }
+    }
+
+    // 删除用户
+    public function del()
+    {
+        $user_id = $this->request->param('user_id');
+        $user=UserModel::find($user_id);
+        if(!$user || $user->user_id==1){
+            return warning('用户不存在');
+        }
+        Db::startTrans();
+        try{
+            // 删除其好友关系
+            Friend::where('create_user', $user_id)->whereOr(['friend_user_id'=>$user_id])->delete();
+            // 删除其群组关系
+            GroupUser::where('user_id', $user_id)->delete();
+            UserModel::destroy($user_id);
+            Db::commit();
+            return success('删除成功');
+        }catch (\Exception $e){
+            Db::rollback();
+            return error($e->getMessage());
+        }
+    }
+
+    // 修改用户状态
+    public function setStatus()
+    {
+        $user_id = $this->request->param('user_id');
+        $user=UserModel::find($user_id);
+        if(!$user){
+            return warning('用户不存在');
+        }
+        try{
+            $status = $this->request->param('status',0);
+            UserModel::where('user_id', $user_id)->update(['status'=>$status]);
+            return success('修改成功');
+        }catch (\Exception $e){
+            return error('修改失败');
+        }
+    }
+
+    // 获取用户信息
+    public function detail()
+    {
+        $user_id = $this->request->param('user_id');
+        $user=UserModel::find($user_id);
+        if(!$user){
+            return error('用户不存在');
+        }
+        $user->avatar=avatarUrl($user->avatar,$user->realname,$user->user_id,120);
+        $location='';
+        if($user->last_login_ip){
+            $location=implode(" ", \Ip::find($user->last_login_ip));
+        }
+        $user->location=$location;
+        $user->password='';
+        return success('', $user);
+    }
+
+    // 设置用户角色
+    public function setRole()
+    {
+        $user_id = $this->request->param('user_id');
+        $user=UserModel::find($user_id);
+        if(!$user){
+            return warning('用户不存在');
+        }
+        try{
+            $role = $this->request->param('role');
+            UserModel::where('user_id', $user_id)->update(['role'=>$role]);
+            return success('修改成功');
+        }catch (\Exception $e){
+            return error('修改失败');
+        }
+    }
+
+    // 修改密码
+    public function editPassword()
+    {
+        $user_id = $this->request->param('user_id');
+        $user=UserModel::find($user_id);
+        if(!$user){
+            return warning('用户不存在');
+        }
+        try{
+            $password = $this->request->param('password','');
+            if($password){
+                $salt=$user->salt;
+                $user->password= password_hash_tp($password,$salt);
+            }
+            $user->save();
+            return success('修改成功');
+        }catch (\Exception $e){
+            return error('修改失败');
+        }
+    }
+
+}

+ 5 - 0
app/manage/middleware.php

@@ -0,0 +1,5 @@
+<?php
+return [
+"checkAuth",
+"manageAuth"
+];

+ 45 - 0
app/manage/model/Config.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * raingad IM [ThinkPHP6]
+ * @author xiekunyu <raingad@foxmail.com>
+ */
+namespace app\manage\model;
+
+use app\BaseModel;
+use think\facade\Cache;
+class Config extends BaseModel
+{
+    protected $json = ['value'];
+    protected $jsonAssoc = true;
+
+    // 获取系统配置信息
+    public static function getSystemInfo($update=false){
+        $name='systemInfo';
+        // $auth=request()->header('Authorization');
+        $nameFields=['sysInfo','fileUpload','chatInfo'];
+        // 如果是登录状态才会返回chatINfo
+        // if($auth){
+        //     $name='all'.$name;
+        //     $nameFields[]="chatInfo";
+        // }
+        if(Cache::has($name) && !$update){
+            $systemInfo=Cache::get($name);
+        }else{
+            $systemInfo=[];
+            $conf=Config::where([['name','in',$nameFields]])->select()->toArray();
+            foreach($conf as $v){
+                $value=[];
+                if($v['name']=='fileUpload'){
+                    $value['size'] = $v['value']['size'];
+                    $value['preview'] = $v['value']['preview'];
+                    $value['fileExt'] = $v['value']['fileExt'];
+                }else{
+                    $value=$v['value'];
+                }
+                $systemInfo[$v['name']]=$value;
+            }
+            Cache::set($name,$systemInfo,7*86400);
+        }
+        return $systemInfo;
+    }
+}

+ 19 - 0
app/middleware.php

@@ -0,0 +1,19 @@
+<?php
+// 全局中间件定义文件
+return [
+      // 全局请求缓存
+
+    // 'think\middleware\CheckRequestCache',
+
+    // 多语言加载
+
+    // 'think\middleware\LoadLangPack',
+
+    // Session初始化
+
+    'think\middleware\SessionInit',
+
+    // 页面Trace调试
+
+    // 'think\middleware\TraceDebug',
+];

+ 9 - 0
app/provider.php

@@ -0,0 +1,9 @@
+<?php
+use app\ExceptionHandle;
+use app\Request;
+
+// 容器Provider定义文件
+return [
+    'think\Request'          => Request::class,
+    'think\exception\Handle' => ExceptionHandle::class,
+];

+ 9 - 0
app/service.php

@@ -0,0 +1,9 @@
+<?php
+
+use app\AppService;
+
+// 系统服务定义文件
+// 服务在完成全局初始化之后执行
+return [
+    AppService::class,
+];

+ 105 - 0
app/worker/Application.php

@@ -0,0 +1,105 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace app\worker;
+
+use think\App;
+use think\exception\Handle;
+use think\exception\HttpException;
+use Workerman\Connection\TcpConnection;
+use Workerman\Protocols\Http\Response;
+/**
+ * Worker应用对象
+ */
+class Application extends App
+{
+    /**
+     * 处理Worker请求
+     * @access public
+     * @param  \Workerman\Connection\TcpConnection   $connection
+     * @param  void
+     */
+    public function worker(TcpConnection $connection)
+    {
+        try {
+            $this->beginTime = microtime(true);
+            $this->beginMem  = memory_get_usage();
+            $this->db->clearQueryTimes();
+
+            $pathinfo = ltrim(strpos($_SERVER['REQUEST_URI'], '?') ? strstr($_SERVER['REQUEST_URI'], '?', true) : $_SERVER['REQUEST_URI'], '/');
+
+            $this->request
+                ->setPathinfo($pathinfo)
+                ->withInput($GLOBALS['HTTP_RAW_POST_DATA']);
+
+            while (ob_get_level() > 1) {
+                ob_end_clean();
+            }
+
+            ob_start();
+            $response = $this->http->run();
+            $content  = ob_get_clean();
+
+            ob_start();
+
+            $response->send();
+            $this->http->end($response);
+
+            $content .= ob_get_clean() ?: '';
+
+            $this->httpResponseCode($response->getCode());
+            $header=[];
+            foreach ($response->getHeader() as $name => $val) {
+                // 发送头部信息
+                $header[$name] =!is_null($val) ? $val : '';
+            }
+            if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
+                $connection->send(new Response(200, $header, $content));
+            } else {
+                $connection->close(new Response(200, $header, $content));
+            }
+        } catch (HttpException | \Exception | \Throwable $e) {
+            $this->exception($connection, $e);
+        }
+    }
+
+    /**
+     * 是否运行在命令行下
+     * @return bool
+     */
+    public function runningInConsole(): bool
+    {
+        return false;
+    }
+
+    protected function httpResponseCode($code = 200)
+    {
+            new Response($code);
+    }
+
+    protected function exception($connection, $e)
+    {
+        if ($e instanceof \Exception) {
+            $handler = $this->make(Handle::class);
+            $handler->report($e);
+
+            $resp    = $handler->render($this->request, $e);
+            $content = $resp->getContent();
+            $code    = $resp->getCode();
+
+            $this->httpResponseCode(new Response($code, [], $content));
+            $connection->send($content);
+        } else {
+            $connection->send(new Response(500, [], $e->getMessage()));
+        }
+    }
+
+}

+ 142 - 0
app/worker/Events.php

@@ -0,0 +1,142 @@
+<?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 app\worker;
+/**
+ * 推送主逻辑
+ * 主要是处理 onMessage onClose 
+ */
+use GatewayWorker\Lib\Gateway;
+use app\worker\Application;
+use think\facade\Config;
+use Lcobucci\JWT\Builder;
+use Lcobucci\JWT\Parser;
+use thans\jwt\provider\JWT\Lcobucci;
+use utils\Aes;
+
+class Events
+{
+    // 使用TP框架
+    public static function onWorkerStart()
+    {
+        $app = new Application;
+        $app->initialize();
+    }
+
+    // 当有客户端连接时,将client_id返回,让mvc框架判断当前uid并执行绑定
+    public static function onConnect($client_id)
+    {
+        Gateway::sendToClient($client_id, json_encode(array(
+            'type'      => 'init',
+            'client_id' => $client_id
+        )));
+    }
+    /**
+    * 有消息时
+    * @param int $client_id
+    * @param mixed $message
+    */
+   public static function onMessage($client_id, $message)
+   {
+        // 客户端传递的是json数据
+        $message_data = json_decode($message, true);
+        if(!$message_data)
+        {
+            return ;
+        }
+        
+        // 根据类型执行不同的业务
+        switch($message_data['type'])
+        {
+            // 客户端回应服务端的心跳
+            case 'pong':
+                break;
+            case 'ping':
+                self::sendStatus($client_id);
+                break;
+            case 'bindUid':
+                self::auth($client_id,$message_data);
+                break;
+        }
+        return;
+   }
+
+   protected static function sendStatus($client_id){
+        $uid=$_SESSION['user_id'] ?? 0;
+        $multiport=false;
+        if($uid){
+            $arr=Gateway::getClientIdByUid($uid);
+            if(count($arr)>1){
+                $multiport=true;
+            }
+        }
+        Gateway::sendToClient($client_id, json_encode(array(
+            'type' => 'pong',
+            'multiport' => $multiport,
+        )));
+   }
+
+    //验证用户的真实性并绑定
+    protected static function auth($client_id, $msg){
+        $token=$msg['token'] ?? '';
+        $config   = Config::get('jwt');
+        $keys     = $config['secret'] ?: [
+            'public' => $config['public_key'],
+            'private' => $config['private_key'],
+            'password' => $config['password'],
+        ];
+        $provider = new Lcobucci(new Builder(), new Parser(), $config['algo'], $keys);
+        try {
+            $token=str_replace('bearer ','',$token);
+            $jwtData = $provider->decode((string)$token);
+        } catch (\Exception $exception) {
+            self::closeClient($client_id);
+        }
+
+        $userInfo = $jwtData['info']->getValue();
+        //解密token中的用户信息
+        $userInfo = Aes::decrypt($userInfo, config('app.aes_token_key'));
+        //解析json
+        $userInfo = (array)json_decode($userInfo, true);
+        if(!$userInfo){
+            self::closeClient($client_id);
+        }
+        $_SESSION['user_id']=$userInfo['user_id'];
+        self::sendStatus($client_id);
+    }
+
+    //断开连接
+    protected static function closeClient($client_id){
+        $_SESSION['user_id']=null;
+        Gateway::closeClient($client_id);
+    }
+
+    /**
+    * 当断开连接时
+    * @param int $client_id
+    */
+    public static function onClose($client_id)
+    {
+        $user_id=$_SESSION['user_id'];
+        if($user_id){
+            Gateway::sendToAll(json_encode(array(
+            'type'      => 'isOnline',
+            'time' => time(),
+            'data' => ['id'=>$user_id,'is_online'=>0]
+        )));
+        }
+        
+    }
+  
+}

+ 201 - 0
app/worker/command/GatewayWorker.php

@@ -0,0 +1,201 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace app\worker\command;
+
+use GatewayWorker\BusinessWorker;
+use GatewayWorker\Gateway;
+use GatewayWorker\Register;
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+use think\facade\Config;
+use Workerman\Worker;
+
+/**
+ * Worker 命令行类
+ */
+class GatewayWorker extends Command
+{
+    public function configure()
+    {
+        $this->setName('worker:gateway')
+            ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status|connections", 'start')
+            ->addOption('host', 'H', Option::VALUE_OPTIONAL, 'the host of workerman server.', null)
+            ->addOption('port', 'p', Option::VALUE_OPTIONAL, 'the port of workerman server.', null)
+            ->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the workerman server in daemon mode.')
+            ->setDescription('GatewayWorker Server for ThinkPHP');
+    }
+
+    public function execute(Input $input, Output $output)
+    {
+        $action = $input->getArgument('action');
+
+        if (DIRECTORY_SEPARATOR !== '\\') {
+            if (!in_array($action, ['start', 'stop', 'reload', 'restart', 'status', 'connections'])) {
+                $output->writeln("Invalid argument action:{$action}, Expected start|stop|restart|reload|status|connections .");
+                exit(1);
+            }
+
+            global $argv;
+            array_shift($argv);
+            array_shift($argv);
+            array_unshift($argv, 'think', $action);
+        } else {
+            $output->writeln("GatewayWorker Not Support On Windows.");
+            exit(1);
+        }
+
+        if ('start' == $action) {
+            $output->writeln('Starting GatewayWorker server...');
+        }
+
+        $option = Config::get('gateway');
+
+        if ($input->hasOption('host')) {
+            $host = $input->getOption('host');
+        } else {
+            $host = !empty($option['host']) ? $option['host'] : '0.0.0.0';
+        }
+
+        if ($input->hasOption('port')) {
+            $port = $input->getOption('port');
+        } else {
+            $port = !empty($option['port']) ? $option['port'] : '2347';
+        }
+
+        $this->start($host, (int) $port, $option);
+    }
+
+    /**
+     * 启动
+     * @access public
+     * @param  string   $host 监听地址
+     * @param  integer  $port 监听端口
+     * @param  array    $option 参数
+     * @return void
+     */
+    public function start(string $host, int $port, array $option = [])
+    {
+        $registerAddress = !empty($option['registerAddress']) ? $option['registerAddress'] : '127.0.0.1:1236';
+
+        if (!empty($option['register_deploy'])) {
+            // 分布式部署的时候其它服务器可以关闭register服务
+            // 注意需要设置不同的lanIp
+            $this->register($registerAddress);
+        }
+
+        // 启动businessWorker
+        if (!empty($option['businessWorker_deploy'])) {
+            $this->businessWorker($registerAddress, $option['businessWorker'] ?? []);
+        }
+
+        // 启动gateway
+        if (!empty($option['gateway_deploy'])) {
+            $this->gateway($registerAddress, $host, $port, $option);
+        }
+
+        Worker::runAll();
+    }
+
+    /**
+     * 启动register
+     * @access public
+     * @param  string   $registerAddress
+     * @return void
+     */
+    public function register(string $registerAddress)
+    {
+        // 初始化register
+        new Register('text://' . $registerAddress);
+    }
+
+    /**
+     * 启动businessWorker
+     * @access public
+     * @param  string   $registerAddress registerAddress
+     * @param  array    $option 参数
+     * @return void
+     */
+    public function businessWorker(string $registerAddress, array $option = [])
+    {
+        // 初始化 bussinessWorker 进程
+        $worker = new BusinessWorker();
+
+        $this->option($worker, $option);
+
+        $worker->registerAddress = $registerAddress;
+    }
+
+    /**
+     * 启动gateway
+     * @access public
+     * @param  string  $registerAddress registerAddress
+     * @param  string  $host 服务地址
+     * @param  integer $port 监听端口
+     * @param  array   $option 参数
+     * @return void
+     */
+    public function gateway(string $registerAddress, string $host, int $port, array $option = [])
+    {
+        // 初始化 gateway 进程
+        if (!empty($option['socket'])) {
+            $socket = $option['socket'];
+            unset($option['socket']);
+        } else {
+            $protocol = !empty($option['protocol']) ? $option['protocol'] : 'websocket';
+            $socket   = $protocol . '://' . $host . ':' . $port;
+            unset($option['host'], $option['port'], $option['protocol']);
+        }
+
+        $gateway = new Gateway($socket, $option['context'] ?? []);
+
+        // 以下设置参数都可以在配置文件中重新定义覆盖
+        $gateway->name                 = 'Gateway';
+        $gateway->count                = 4;
+        $gateway->lanIp                = '127.0.0.1';
+        $gateway->startPort            = 2000;
+        $gateway->pingInterval         = 30;
+        $gateway->pingNotResponseLimit = 0;
+        $gateway->pingData             = '{"type":"ping"}';
+        $gateway->registerAddress      = $registerAddress;
+
+        // 全局静态属性设置
+        foreach ($option as $name => $val) {
+            if (in_array($name, ['stdoutFile', 'daemonize', 'pidFile', 'logFile'])) {
+                Worker::${$name} = $val;
+                unset($option[$name]);
+            }
+        }
+
+        $this->option($gateway, $option);
+    }
+
+    /**
+     * 设置参数
+     * @access protected
+     * @param  Worker $worker Worker对象
+     * @param  array  $option 参数
+     * @return void
+     */
+    protected function option(Worker $worker, array $option = [])
+    {
+        // 设置参数
+        if (!empty($option)) {
+            foreach ($option as $key => $val) {
+                $worker->$key = $val;
+            }
+        }
+    }
+
+}

+ 32 - 0
app/worker/start_businessworker.php

@@ -0,0 +1,32 @@
+<?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
+ */
+use \Workerman\Worker;
+use \GatewayWorker\BusinessWorker;
+
+require_once __DIR__ . '/../../vendor/autoload.php';
+// bussinessWorker 进程
+$worker = new BusinessWorker();
+// worker名称
+$worker->name = 'PushBusinessWorker';
+// bussinessWorker进程数量
+$worker->count = 1;
+// 服务注册地址
+$worker->registerAddress = '127.0.0.1:1236';
+$worker->eventHandler = 'app\worker\Events';
+// 如果不是在根目录启动,则运行runAll方法
+if(!defined('GLOBAL_START'))
+{
+    Worker::runAll();
+}
+

+ 42 - 0
app/worker/start_gateway.php

@@ -0,0 +1,42 @@
+<?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
+ */
+use \Workerman\Worker;
+use \GatewayWorker\Gateway;
+use \Workerman\Autoloader;
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+// gateway 进程
+$gateway = new Gateway("Websocket://0.0.0.0:8282");
+// 设置名称,方便status时查看
+$gateway->name = 'pushMessage';
+// 设置进程数,gateway进程数建议与cpu核数相同
+$gateway->count = 1;
+// 分布式部署时请设置成内网ip(非127.0.0.1)
+$gateway->lanIp = '127.0.0.1';
+// 内部通讯起始端口。假如$gateway->count=4,起始端口为2300
+// 则一般会使用2300 2301 2302 2303 4个端口作为内部通讯端口 
+$gateway->startPort = 2300;
+// 心跳间隔
+$gateway->pingInterval = 20;
+// 心跳数据
+$gateway->pingData = '{"type":"ping"}';
+// 服务注册地址
+$gateway->registerAddress = '127.0.0.1:1236';
+
+// 如果不是在根目录启动,则运行runAll方法
+if(!defined('GLOBAL_START'))
+{
+    Worker::runAll();
+}
+

+ 25 - 0
app/worker/start_register.php

@@ -0,0 +1,25 @@
+<?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
+ */
+use \Workerman\Worker;
+use \GatewayWorker\Register;
+require_once __DIR__ . '/../../vendor/autoload.php';
+// register 服务必须是text协议
+$register = new Register('text://0.0.0.0:1236');
+
+// 如果不是在根目录启动,则运行runAll方法
+if(!defined('GLOBAL_START'))
+{
+    Worker::runAll();
+}
+

+ 67 - 0
composer.json

@@ -0,0 +1,67 @@
+{
+    "name": "topthink/think",
+    "description": "the new thinkphp framework",
+    "type": "project",
+    "keywords": [
+        "framework",
+        "thinkphp",
+        "ORM"
+    ],
+    "homepage": "http://thinkphp.cn/",
+    "license": "Apache-2.0",
+    "authors": [{
+            "name": "liu21st",
+            "email": "liu21st@gmail.com"
+        },
+        {
+            "name": "yunwuxin",
+            "email": "448901948@qq.com"
+        }
+    ],
+    "require": {
+        "php": ">=7.1.0",
+        "topthink/framework": "^6.0.0",
+        "topthink/think-orm": "^2.0",
+        "jasny/sso": "^0.3.0",
+        "xiaodi/think-pullword": "^1.0",
+        "topthink/think-view": "^1.0",
+        "aliyuncs/oss-sdk-php": "^2.3",
+        "tcwei/imglazyload": "^1.3",
+        "tcwei/imgsrc": "^2.0",
+        "topthink/think-captcha": "^3.0",
+        "alibabacloud/client": "^1.5",
+        "xiaodi/think-pinyin": "^1.0",
+        "workerman/workerman": "^4.0",
+        "workerman/gateway-worker": "^3.0",
+        "workerman/gatewayclient": "^3.0",
+        "topthink/think-multi-app": "^1.0",
+        "thans/thinkphp-filesystem-cloud": "^1.0",
+        "topthink/think-queue": "^3.0",
+        "yunwuxin/think-cron": "^3.0",
+        "swiftmailer/swiftmailer": "^6.0",
+        "thans/tp-jwt-auth": "^1.3",
+        "singka/singka-sms": "^1.6"
+    },
+    "require-dev": {
+        "symfony/var-dumper": "^4.2",
+        "topthink/think-trace": "^1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "app\\": "app"
+        },
+        "psr-0": {
+            "": "extend/"
+        }
+    },
+    "config": {
+        "preferred-install": "dist",
+        "secure-http": false 
+    },
+    "scripts": {
+        "post-autoload-dump": [
+            "@php think service:discover",
+            "@php think vendor:publish"
+        ]
+    }
+}

+ 3548 - 0
composer.lock

@@ -0,0 +1,3548 @@
+{
+    "_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": "674bc39f3462a2a4238e15002063fa0d",
+    "packages": [
+        {
+            "name": "adbario/php-dot-notation",
+            "version": "2.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/adbario/php-dot-notation.git",
+                "reference": "081e2cca50c84bfeeea2e3ef9b2c8d206d80ccae"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/adbario/php-dot-notation/zipball/081e2cca50c84bfeeea2e3ef9b2c8d206d80ccae",
+                "reference": "081e2cca50c84bfeeea2e3ef9b2c8d206d80ccae",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "php": "^5.5 || ^7.0 || ^8.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8|^5.7|^6.6|^7.5|^8.5|^9.5",
+                "squizlabs/php_codesniffer": "^3.6"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/helpers.php"
+                ],
+                "psr-4": {
+                    "Adbar\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Riku Särkinen",
+                    "email": "riku@adbar.io"
+                }
+            ],
+            "description": "PHP dot notation access to arrays",
+            "homepage": "https://github.com/adbario/php-dot-notation",
+            "keywords": [
+                "ArrayAccess",
+                "dotnotation"
+            ],
+            "time": "2022-10-14T20:31:46+00:00"
+        },
+        {
+            "name": "alibabacloud/client",
+            "version": "1.5.32",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/aliyun/openapi-sdk-php-client.git",
+                "reference": "5bc6f6d660797dcee2c3aef29700ab41ee764f4d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/aliyun/openapi-sdk-php-client/zipball/5bc6f6d660797dcee2c3aef29700ab41ee764f4d",
+                "reference": "5bc6f6d660797dcee2c3aef29700ab41ee764f4d",
+                "shasum": ""
+            },
+            "require": {
+                "adbario/php-dot-notation": "^2.4.1",
+                "clagiordano/weblibs-configmanager": "^1.0",
+                "ext-curl": "*",
+                "ext-json": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-openssl": "*",
+                "ext-simplexml": "*",
+                "ext-xmlwriter": "*",
+                "guzzlehttp/guzzle": "^6.3|^7.0",
+                "mtdowling/jmespath.php": "^2.5",
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "composer/composer": "^1.8",
+                "drupal/coder": "^8.3",
+                "ext-dom": "*",
+                "ext-pcre": "*",
+                "ext-sockets": "*",
+                "ext-spl": "*",
+                "league/climate": "^3.2.4",
+                "mikey179/vfsstream": "^1.6",
+                "monolog/monolog": "^1.24",
+                "phpunit/phpunit": "^5.7|^6.6|^7.5|^8.5|^9.5",
+                "psr/cache": "^1.0",
+                "symfony/dotenv": "^3.4",
+                "symfony/var-dumper": "^3.4"
+            },
+            "suggest": {
+                "ext-sockets": "To use client-side monitoring"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/Functions.php"
+                ],
+                "psr-4": {
+                    "AlibabaCloud\\Client\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Alibaba Cloud SDK",
+                    "email": "sdk-team@alibabacloud.com",
+                    "homepage": "http://www.alibabacloud.com"
+                }
+            ],
+            "description": "Alibaba Cloud Client for PHP - Use Alibaba Cloud in your PHP project",
+            "homepage": "https://www.alibabacloud.com/",
+            "keywords": [
+                "alibaba",
+                "alibabacloud",
+                "aliyun",
+                "client",
+                "cloud",
+                "library",
+                "sdk",
+                "tool"
+            ],
+            "time": "2022-12-09T04:05:55+00:00"
+        },
+        {
+            "name": "aliyuncs/oss-sdk-php",
+            "version": "v2.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/aliyun/aliyun-oss-php-sdk.git",
+                "reference": "572d0f8e099e8630ae7139ed3fdedb926c7a760f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/aliyun/aliyun-oss-php-sdk/zipball/572d0f8e099e8630ae7139ed3fdedb926c7a760f",
+                "reference": "572d0f8e099e8630ae7139ed3fdedb926c7a760f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "*",
+                "satooshi/php-coveralls": "*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "OSS\\": "src/OSS"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Aliyuncs",
+                    "homepage": "http://www.aliyun.com"
+                }
+            ],
+            "description": "Aliyun OSS SDK for PHP",
+            "homepage": "http://www.aliyun.com/product/oss/",
+            "time": "2022-08-03T08:06:01+00:00"
+        },
+        {
+            "name": "clagiordano/weblibs-configmanager",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/clagiordano/weblibs-configmanager.git",
+                "reference": "5c8ebcc62782313b1278afe802b120d18c07a059"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/clagiordano/weblibs-configmanager/zipball/5c8ebcc62782313b1278afe802b120d18c07a059",
+                "reference": "5c8ebcc62782313b1278afe802b120d18c07a059",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4"
+            },
+            "require-dev": {
+                "clagiordano/phpunit-result-printer": "^1",
+                "phpunit/phpunit": "^4.8"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "clagiordano\\weblibs\\configmanager\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-3.0-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Claudio Giordano",
+                    "email": "claudio.giordano@autistici.org",
+                    "role": "Developer"
+                }
+            ],
+            "description": "weblibs-configmanager is a tool library for easily read and access to php config array file and direct read/write configuration file / object",
+            "keywords": [
+                "clagiordano",
+                "configuration",
+                "manager",
+                "tool",
+                "weblibs"
+            ],
+            "time": "2021-05-18T17:55:57+00:00"
+        },
+        {
+            "name": "desarrolla2/cache",
+            "version": "v2.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/desarrolla2/Cache.git",
+                "reference": "cbc42cae703e6f8cc9e17231f083304f0038318e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/desarrolla2/Cache/zipball/cbc42cae703e6f8cc9e17231f083304f0038318e",
+                "reference": "cbc42cae703e6f8cc9e17231f083304f0038318e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "predis/predis": "~1.0.0"
+            },
+            "suggest": {
+                "predis/predis": "Predis support"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Desarrolla2\\Cache\\": "src/",
+                    "Desarrolla2\\Test\\Cache\\": "test/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Daniel González",
+                    "homepage": "http://desarrolla2.com/"
+                }
+            ],
+            "description": "Provides an cache interface for several adapters Apc, Apcu, File, Mongo, Memcache, Memcached, Mysql, Mongo, Redis is supported. New adapters is comming!",
+            "homepage": "https://github.com/desarrolla2/Cache/",
+            "keywords": [
+                "apc",
+                "apcu",
+                "cache",
+                "file",
+                "memcache",
+                "memcached",
+                "mongo",
+                "mysql",
+                "redis"
+            ],
+            "time": "2023-05-04T14:59:36+00:00"
+        },
+        {
+            "name": "doctrine/lexer",
+            "version": "1.2.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/lexer.git",
+                "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229",
+                "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^9.0",
+                "phpstan/phpstan": "^1.3",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+                "vimeo/psalm": "^4.11"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
+                {
+                    "name": "Roman Borschel",
+                    "email": "roman@code-factory.org"
+                },
+                {
+                    "name": "Johannes Schmitt",
+                    "email": "schmittjoh@gmail.com"
+                }
+            ],
+            "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+            "homepage": "https://www.doctrine-project.org/projects/lexer.html",
+            "keywords": [
+                "annotations",
+                "docblock",
+                "lexer",
+                "parser",
+                "php"
+            ],
+            "time": "2022-02-28T11:07:21+00:00"
+        },
+        {
+            "name": "dragonmantank/cron-expression",
+            "version": "v3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dragonmantank/cron-expression.git",
+                "reference": "48212cdc0a79051d50d7fc2f0645c5a321caf926"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/48212cdc0a79051d50d7fc2f0645c5a321caf926",
+                "reference": "48212cdc0a79051d50d7fc2f0645c5a321caf926",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1|^8.0"
+            },
+            "replace": {
+                "mtdowling/cron-expression": "^1.0"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^0.11|^0.12",
+                "phpunit/phpunit": "^7.0|^8.0|^9.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Cron\\": "src/Cron/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Chris Tankersley",
+                    "email": "chris@ctankersley.com",
+                    "homepage": "https://github.com/dragonmantank"
+                }
+            ],
+            "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
+            "keywords": [
+                "cron",
+                "schedule"
+            ],
+            "time": "2020-10-13T01:26:01+00:00"
+        },
+        {
+            "name": "egulias/email-validator",
+            "version": "2.1.25",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/egulias/EmailValidator.git",
+                "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0dbf5d78455d4d6a41d186da50adc1122ec066f4",
+                "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/lexer": "^1.0.1",
+                "php": ">=5.5",
+                "symfony/polyfill-intl-idn": "^1.10"
+            },
+            "require-dev": {
+                "dominicsayers/isemail": "^3.0.7",
+                "phpunit/phpunit": "^4.8.36|^7.5.15",
+                "satooshi/php-coveralls": "^1.0.1"
+            },
+            "suggest": {
+                "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Egulias\\EmailValidator\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Eduardo Gulias Davis"
+                }
+            ],
+            "description": "A library for validating emails against several RFCs",
+            "homepage": "https://github.com/egulias/EmailValidator",
+            "keywords": [
+                "email",
+                "emailvalidation",
+                "emailvalidator",
+                "validation",
+                "validator"
+            ],
+            "time": "2020-12-29T14:50:06+00:00"
+        },
+        {
+            "name": "guzzlehttp/command",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/command.git",
+                "reference": "2aaa2521a8f8269d6f5dfc13fe2af12c76921034"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/command/zipball/2aaa2521a8f8269d6f5dfc13fe2af12c76921034",
+                "reference": "2aaa2521a8f8269d6f5dfc13fe2af12c76921034",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/guzzle": "^6.2",
+                "guzzlehttp/promises": "~1.3",
+                "guzzlehttp/psr7": "~1.0",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0|~5.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "0.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Command\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia"
+                }
+            ],
+            "description": "Provides the foundation for building command-based web service clients",
+            "time": "2016-11-24T13:34:15+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "6.5.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a52f0440530b54fa079ce76e8c5d196a42cad981",
+                "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.0",
+                "guzzlehttp/psr7": "^1.9",
+                "php": ">=5.5",
+                "symfony/polyfill-intl-idn": "^1.17"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+                "psr/log": "^1.1"
+            },
+            "suggest": {
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "6.5-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "homepage": "http://guzzlephp.org/",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "rest",
+                "web service"
+            ],
+            "time": "2022-06-20T22:16:07+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle-services",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle-services.git",
+                "reference": "9e3abf20161cbf662d616cbb995f2811771759f7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle-services/zipball/9e3abf20161cbf662d616cbb995f2811771759f7",
+                "reference": "9e3abf20161cbf662d616cbb995f2811771759f7",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/command": "~1.0",
+                "guzzlehttp/guzzle": "^6.2",
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "gimler/guzzle-description-loader": "^0.0.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Command\\Guzzle\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia"
+                },
+                {
+                    "name": "Stefano Kowalke",
+                    "email": "blueduck@mail.org",
+                    "homepage": "https://github.com/konafets"
+                }
+            ],
+            "description": "Provides an implementation of the Guzzle Command library that uses Guzzle service descriptions to describe web services, serialize requests, and parse responses into easy to use model structures.",
+            "time": "2017-10-06T14:32:02+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "1.5.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e",
+                "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^4.4 || ^5.1"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "time": "2023-05-21T12:31:43+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "1.9.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b",
+                "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0",
+                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "ext-zlib": "*",
+                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "time": "2023-04-17T16:00:37+00:00"
+        },
+        {
+            "name": "jasny/sso",
+            "version": "v0.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jasny/sso.git",
+                "reference": "0cb18c072e7b14db3d2d2549c051f41ca837e5e9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jasny/sso/zipball/0cb18c072e7b14db3d2d2549c051f41ca837e5e9",
+                "reference": "0cb18c072e7b14db3d2d2549c051f41ca837e5e9",
+                "shasum": ""
+            },
+            "require": {
+                "desarrolla2/cache": "^2.0.0",
+                "jasny/validation-result": "^1.0.0",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "codeception/codeception": "^2.1.0",
+                "jasny/php-code-quality": "^1.1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Jasny\\SSO\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Arnold Daniels",
+                    "email": "arnold@jasny.net",
+                    "homepage": "http://www.jasny.net"
+                }
+            ],
+            "description": "Simple Single Sign-On",
+            "homepage": "http://www.jasny.net/articles/simple-single-sign-on-for-php/",
+            "keywords": [
+                "SSO",
+                "auth"
+            ],
+            "time": "2017-02-24T22:44:27+00:00"
+        },
+        {
+            "name": "jasny/validation-result",
+            "version": "v1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jasny/validation-result.git",
+                "reference": "fbce54837c8414cf5af22981ac33b26eb13b2a51"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jasny/validation-result/zipball/fbce54837c8414cf5af22981ac33b26eb13b2a51",
+                "reference": "fbce54837c8414cf5af22981ac33b26eb13b2a51",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6.0"
+            },
+            "require-dev": {
+                "jasny/php-code-quality": "^1.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Jasny\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Arnold Daniels",
+                    "email": "arnold@jasny.net",
+                    "homepage": "http://www.jasny.net"
+                }
+            ],
+            "description": "A result object for validation",
+            "keywords": [
+                "validation"
+            ],
+            "time": "2019-02-12T12:51:58+00:00"
+        },
+        {
+            "name": "league/flysystem",
+            "version": "1.0.70",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/flysystem.git",
+                "reference": "585824702f534f8d3cf7fab7225e8466cc4b7493"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/585824702f534f8d3cf7fab7225e8466cc4b7493",
+                "reference": "585824702f534f8d3cf7fab7225e8466cc4b7493",
+                "shasum": ""
+            },
+            "require": {
+                "ext-fileinfo": "*",
+                "php": ">=5.5.9"
+            },
+            "conflict": {
+                "league/flysystem-sftp": "<1.0.6"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "^3.4 || ^4.0 || ^5.0 || ^6.0",
+                "phpunit/phpunit": "^5.7.26"
+            },
+            "suggest": {
+                "ext-fileinfo": "Required for MimeType",
+                "ext-ftp": "Allows you to use FTP server storage",
+                "ext-openssl": "Allows you to use FTPS server storage",
+                "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+                "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+                "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+                "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+                "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+                "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+                "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+                "league/flysystem-webdav": "Allows you to use WebDAV storage",
+                "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
+                "spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
+                "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Flysystem\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frenky.net"
+                }
+            ],
+            "description": "Filesystem abstraction: Many filesystems, one API.",
+            "keywords": [
+                "Cloud Files",
+                "WebDAV",
+                "abstraction",
+                "aws",
+                "cloud",
+                "copy.com",
+                "dropbox",
+                "file systems",
+                "files",
+                "filesystem",
+                "filesystems",
+                "ftp",
+                "rackspace",
+                "remote",
+                "s3",
+                "sftp",
+                "storage"
+            ],
+            "time": "2020-07-26T07:20:36+00:00"
+        },
+        {
+            "name": "league/flysystem-cached-adapter",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/flysystem-cached-adapter.git",
+                "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/flysystem-cached-adapter/zipball/d1925efb2207ac4be3ad0c40b8277175f99ffaff",
+                "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff",
+                "shasum": ""
+            },
+            "require": {
+                "league/flysystem": "~1.0",
+                "psr/cache": "^1.0.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "~0.9",
+                "phpspec/phpspec": "^3.4",
+                "phpunit/phpunit": "^5.7",
+                "predis/predis": "~1.0",
+                "tedivm/stash": "~0.12"
+            },
+            "suggest": {
+                "ext-phpredis": "Pure C implemented extension for PHP"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "League\\Flysystem\\Cached\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "frankdejonge",
+                    "email": "info@frenky.net"
+                }
+            ],
+            "description": "An adapter decorator to enable meta-data caching.",
+            "time": "2020-07-25T15:56:04+00:00"
+        },
+        {
+            "name": "liz/flysystem-qiniu",
+            "version": "v1.23",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/l396635210/flysystem-qiniu.git",
+                "reference": "24af0f95644fcf66e82cc04ce840f31f6450e026"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/l396635210/flysystem-qiniu/zipball/24af0f95644fcf66e82cc04ce840f31f6450e026",
+                "reference": "24af0f95644fcf66e82cc04ce840f31f6450e026",
+                "shasum": ""
+            },
+            "require": {
+                "league/flysystem": "^1.0",
+                "php": "^7.0 || ^8.0",
+                "qiniu/php-sdk": "^7.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Liz\\Flysystem\\QiNiu\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "liz in company",
+                    "email": "396635210@qq.com"
+                }
+            ],
+            "description": "QiNiu oss adapter for flysystem",
+            "time": "2021-01-12T12:50:38+00:00"
+        },
+        {
+            "name": "mtdowling/jmespath.php",
+            "version": "2.6.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jmespath/jmespath.php.git",
+                "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb",
+                "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.4 || ^7.0 || ^8.0",
+                "symfony/polyfill-mbstring": "^1.17"
+            },
+            "require-dev": {
+                "composer/xdebug-handler": "^1.4 || ^2.0",
+                "phpunit/phpunit": "^4.8.36 || ^7.5.15"
+            },
+            "bin": [
+                "bin/jp.php"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.6-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/JmesPath.php"
+                ],
+                "psr-4": {
+                    "JmesPath\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Declaratively specify how to extract elements from a JSON document",
+            "keywords": [
+                "json",
+                "jsonpath"
+            ],
+            "time": "2021-06-14T00:11:39+00:00"
+        },
+        {
+            "name": "myclabs/php-enum",
+            "version": "1.7.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/php-enum.git",
+                "reference": "d178027d1e679832db9f38248fcc7200647dc2b7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/php-enum/zipball/d178027d1e679832db9f38248fcc7200647dc2b7",
+                "reference": "d178027d1e679832db9f38248fcc7200647dc2b7",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "php": ">=7.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7",
+                "squizlabs/php_codesniffer": "1.*",
+                "vimeo/psalm": "^3.8"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "MyCLabs\\Enum\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP Enum contributors",
+                    "homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
+                }
+            ],
+            "description": "PHP Enum implementation",
+            "homepage": "http://github.com/myclabs/php-enum",
+            "keywords": [
+                "enum"
+            ],
+            "time": "2020-11-14T18:14:52+00:00"
+        },
+        {
+            "name": "nesbot/carbon",
+            "version": "2.68.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/briannesbitt/Carbon.git",
+                "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4f991ed2a403c85efbc4f23eb4030063fdbe01da",
+                "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "php": "^7.1.8 || ^8.0",
+                "symfony/polyfill-mbstring": "^1.0",
+                "symfony/polyfill-php80": "^1.16",
+                "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0"
+            },
+            "require-dev": {
+                "doctrine/dbal": "^2.0 || ^3.1.4",
+                "doctrine/orm": "^2.7",
+                "friendsofphp/php-cs-fixer": "^3.0",
+                "kylekatarnls/multi-tester": "^2.0",
+                "ondrejmirtes/better-reflection": "*",
+                "phpmd/phpmd": "^2.9",
+                "phpstan/extension-installer": "^1.0",
+                "phpstan/phpstan": "^0.12.99 || ^1.7.14",
+                "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6",
+                "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20",
+                "squizlabs/php_codesniffer": "^3.4"
+            },
+            "bin": [
+                "bin/carbon"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-3.x": "3.x-dev",
+                    "dev-master": "2.x-dev"
+                },
+                "laravel": {
+                    "providers": [
+                        "Carbon\\Laravel\\ServiceProvider"
+                    ]
+                },
+                "phpstan": {
+                    "includes": [
+                        "extension.neon"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Carbon\\": "src/Carbon/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Brian Nesbitt",
+                    "email": "brian@nesbot.com",
+                    "homepage": "https://markido.com"
+                },
+                {
+                    "name": "kylekatarnls",
+                    "homepage": "https://github.com/kylekatarnls"
+                }
+            ],
+            "description": "An API extension for DateTime that supports 281 different languages.",
+            "homepage": "https://carbon.nesbot.com",
+            "keywords": [
+                "date",
+                "datetime",
+                "time"
+            ],
+            "time": "2023-06-20T18:29:04+00:00"
+        },
+        {
+            "name": "overtrue/flysystem-cos",
+            "version": "2.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/flysystem-cos.git",
+                "reference": "f6985f38f3c76a9be962f404f127b7222a9b5fff"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/flysystem-cos/zipball/f6985f38f3c76a9be962f404f127b7222a9b5fff",
+                "reference": "f6985f38f3c76a9be962f404f127b7222a9b5fff",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/guzzle": "^6.3|^7.0",
+                "league/flysystem": "^1.0",
+                "php": ">=7.0",
+                "qcloud/cos-sdk-v5": "^2.0.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "~1.0",
+                "php": ">=7.1",
+                "phpunit/phpunit": "^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Overtrue\\Flysystem\\Cos\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "overtrue",
+                    "email": "i@overtrue.me"
+                }
+            ],
+            "description": "Flysystem adapter for the QCloud COS storage.",
+            "time": "2020-10-22T10:28:58+00:00"
+        },
+        {
+            "name": "overtrue/pinyin",
+            "version": "4.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/pinyin.git",
+                "reference": "4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/pinyin/zipball/4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7",
+                "reference": "4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "require-dev": {
+                "brainmaestro/composer-git-hooks": "^2.7",
+                "friendsofphp/php-cs-fixer": "^2.16",
+                "phpunit/phpunit": "~8.0"
+            },
+            "type": "library",
+            "extra": {
+                "hooks": {
+                    "pre-commit": [
+                        "composer test",
+                        "composer fix-style"
+                    ],
+                    "pre-push": [
+                        "composer test",
+                        "composer check-style"
+                    ]
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/const.php"
+                ],
+                "psr-4": {
+                    "Overtrue\\Pinyin\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "overtrue",
+                    "email": "anzhengchao@gmail.com",
+                    "homepage": "http://github.com/overtrue"
+                }
+            ],
+            "description": "Chinese to pinyin translator.",
+            "homepage": "https://github.com/overtrue/pinyin",
+            "keywords": [
+                "Chinese",
+                "Pinyin",
+                "cn2pinyin"
+            ],
+            "time": "2023-04-27T10:17:12+00:00"
+        },
+        {
+            "name": "psr/cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/cache.git",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Cache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for caching libraries",
+            "keywords": [
+                "cache",
+                "psr",
+                "psr-6"
+            ],
+            "time": "2016-08-06T20:24:11+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "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/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "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/http-message",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2016-08-06T14:39:51+00:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
+                "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/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "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"
+        },
+        {
+            "name": "psr/simple-cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "time": "2017-10-23T01:57:42+00:00"
+        },
+        {
+            "name": "qcloud/cos-sdk-v5",
+            "version": "v2.6.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/tencentyun/cos-php-sdk-v5.git",
+                "reference": "dd1b7a096cbdcafc9de265cb2bb1b4222ac60136"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/tencentyun/cos-php-sdk-v5/zipball/dd1b7a096cbdcafc9de265cb2bb1b4222ac60136",
+                "reference": "dd1b7a096cbdcafc9de265cb2bb1b4222ac60136",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "ext-simplexml": "*",
+                "guzzlehttp/guzzle": "^6.2.1 || ^7.0",
+                "guzzlehttp/guzzle-services": "^1.1",
+                "guzzlehttp/psr7": "^1.3.1 || ^2.0",
+                "php": ">=5.6"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.4-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/Common.php"
+                ],
+                "psr-4": {
+                    "Qcloud\\Cos\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "yaozongyou",
+                    "email": "yaozongyou@vip.qq.com"
+                },
+                {
+                    "name": "lewzylu",
+                    "email": "327874225@qq.com"
+                },
+                {
+                    "name": "tuunalai",
+                    "email": "550566181@qq.com"
+                }
+            ],
+            "description": "PHP SDK for QCloud COS",
+            "keywords": [
+                "cos",
+                "php",
+                "qcloud"
+            ],
+            "time": "2023-06-14T03:18:14+00:00"
+        },
+        {
+            "name": "qcloudsms/qcloudsms_php",
+            "version": "v0.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/qcloudsms/qcloudsms_php.git",
+                "reference": "48822045772d343b93c3d505d8a187cd51153c5a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://mirrors.huaweicloud.com/repository/php/qcloudsms/qcloudsms_php/v0.1.4/qcloudsms-qcloudsms_php-v0.1.4.zip",
+                "reference": "48822045772d343b93c3d505d8a187cd51153c5a",
+                "shasum": ""
+            },
+            "require-dev": {
+                "sami/sami": "dev-master"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Qcloud\\Sms\\": "src/"
+                }
+            },
+            "license": [
+                "MIT"
+            ],
+            "description": "qcloud sms php sdk",
+            "keywords": [
+                "php",
+                "qcloud",
+                "sdk",
+                "sms"
+            ],
+            "time": "2018-09-19T07:19:17+00:00"
+        },
+        {
+            "name": "qeq66/jwt",
+            "version": "3.3.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/qeq66/jwt.git",
+                "reference": "bd2fa6c51704dc18c61026c852c789224d7190a0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://mirrors.huaweicloud.com/repository/php/qeq66/jwt/3.3.5/qeq66-jwt-3.3.5.zip",
+                "reference": "bd2fa6c51704dc18c61026c852c789224d7190a0",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "ext-openssl": "*",
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "mikey179/vfsstream": "~1.5",
+                "phpmd/phpmd": "~2.2",
+                "phpunit/php-invoker": "~1.1",
+                "phpunit/phpunit": "^5.7 || ^7.3",
+                "squizlabs/php_codesniffer": "~2.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Lcobucci\\JWT\\": "src"
+                }
+            },
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Luís Otávio Cobucci Oblonczyk",
+                    "email": "lcobucci@gmail.com",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to work with JSON Web Token and JSON Web Signature",
+            "keywords": [
+                "JWS",
+                "jwt"
+            ],
+            "time": "2022-07-11T08:31:22+00:00"
+        },
+        {
+            "name": "qiniu/php-sdk",
+            "version": "v7.9.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/qiniu/php-sdk.git",
+                "reference": "3c0ebeee6a7439a0d2874f24b56dfe43545a1d2e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/qiniu/php-sdk/zipball/3c0ebeee6a7439a0d2874f24b56dfe43545a1d2e",
+                "reference": "3c0ebeee6a7439a0d2874f24b56dfe43545a1d2e",
+                "shasum": ""
+            },
+            "require": {
+                "myclabs/php-enum": "~1.5.2 || ~1.6.6 || ~1.7.7 || ~1.8.4",
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "paragonie/random_compat": ">=2",
+                "phpunit/phpunit": "^4.8 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4",
+                "squizlabs/php_codesniffer": "^2.3 || ~3.6"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/Qiniu/functions.php"
+                ],
+                "psr-4": {
+                    "Qiniu\\": "src/Qiniu"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Qiniu",
+                    "email": "sdk@qiniu.com",
+                    "homepage": "http://www.qiniu.com"
+                }
+            ],
+            "description": "Qiniu Resource (Cloud) Storage SDK for PHP",
+            "homepage": "http://developer.qiniu.com/",
+            "keywords": [
+                "cloud",
+                "qiniu",
+                "sdk",
+                "storage"
+            ],
+            "time": "2023-05-06T04:36:16+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "singka/singka-sms",
+            "version": "v1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/SingKa-TECH/singka-sms.git",
+                "reference": "8db3543dfb7f28851b94de99dfe9b762f0b9b6a4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://mirrors.huaweicloud.com/repository/php/singka/singka-sms/v1.6/singka-singka-sms-v1.6.zip",
+                "reference": "8db3543dfb7f28851b94de99dfe9b762f0b9b6a4",
+                "shasum": ""
+            },
+            "require": {
+                "alibabacloud/client": "^1.5",
+                "guzzlehttp/guzzle": "~6.0@dev",
+                "php": ">=7.0",
+                "qcloudsms/qcloudsms_php": "0.1.*",
+                "qiniu/php-sdk": "^7.2",
+                "singka/ucloud-sms": "^1.8"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "config": {
+                        "sms": "config/config.php"
+                    }
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "SingKa\\Sms\\": "src/"
+                }
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "宁波晟嘉网络科技有限公司 夏慧新",
+                    "email": "shycomet@singka.email"
+                }
+            ],
+            "description": "适用于ThinkPHP6.0的各种短信接口集成服务,本项目集成了各大云服务厂商的短信业务平台,支持ThinkPHP5.0、ThinkPHP5.1和ThinkPHP6.0,由宁波晟嘉网络科技有限公司维护,目前支持阿里云、腾讯云、七牛云、又拍云、Ucloud和华为云。",
+            "time": "2020-07-02T05:07:00+00:00"
+        },
+        {
+            "name": "singka/ucloud-sms",
+            "version": "v1.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/SingKa-TECH/ucloud-sms.git",
+                "reference": "7c4ae42e9c7b26b4db7e6340ff03f68b15e3b0d8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://mirrors.huaweicloud.com/repository/php/singka/ucloud-sms/v1.8/singka-ucloud-sms-v1.8.zip",
+                "reference": "7c4ae42e9c7b26b4db7e6340ff03f68b15e3b0d8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "config": {
+                        "usms": "config/config.php"
+                    }
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Singka\\UcloudSms\\": "src/"
+                }
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "宁波晟嘉网络科技有限公司 夏慧新",
+                    "email": "shycomet@singka.email"
+                }
+            ],
+            "description": "ThinkPHP系列Ucloud的短信接口,支持ThinkPHP6、ThinkPHP5.1和ThinkPHP5.0",
+            "time": "2020-03-24T16:24:09+00:00"
+        },
+        {
+            "name": "swiftmailer/swiftmailer",
+            "version": "v6.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/swiftmailer/swiftmailer.git",
+                "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8a5d5072dca8f48460fce2f4131fcc495eec654c",
+                "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c",
+                "shasum": ""
+            },
+            "require": {
+                "egulias/email-validator": "^2.0|^3.1",
+                "php": ">=7.0.0",
+                "symfony/polyfill-iconv": "^1.0",
+                "symfony/polyfill-intl-idn": "^1.10",
+                "symfony/polyfill-mbstring": "^1.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.0",
+                "symfony/phpunit-bridge": "^4.4|^5.4"
+            },
+            "suggest": {
+                "ext-intl": "Needed to support internationalized email addresses"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "6.2-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "lib/swift_required.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Chris Corbyn"
+                },
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Swiftmailer, free feature-rich PHP mailer",
+            "homepage": "https://swiftmailer.symfony.com",
+            "keywords": [
+                "email",
+                "mail",
+                "mailer"
+            ],
+            "abandoned": "symfony/mailer",
+            "time": "2021-10-18T15:26:12+00:00"
+        },
+        {
+            "name": "symfony/polyfill-iconv",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-iconv.git",
+                "reference": "927013f3aac555983a5059aada98e1907d842695"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/927013f3aac555983a5059aada98e1907d842695",
+                "reference": "927013f3aac555983a5059aada98e1907d842695",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-iconv": "*"
+            },
+            "suggest": {
+                "ext-iconv": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Iconv\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Iconv extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "iconv",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-idn",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-idn.git",
+                "reference": "639084e360537a19f9ee352433b84ce831f3d2da"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da",
+                "reference": "639084e360537a19f9ee352433b84ce831f3d2da",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1",
+                "symfony/polyfill-intl-normalizer": "^1.10",
+                "symfony/polyfill-php72": "^1.10"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Idn\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Laurent Bassin",
+                    "email": "laurent@bassin.info"
+                },
+                {
+                    "name": "Trevor Rowbotham",
+                    "email": "trevor.rowbotham@pm.me"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "idn",
+                "intl",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-normalizer",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+                "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
+                "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's Normalizer class and related functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "intl",
+                "normalizer",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+                "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-mbstring": "*"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php72",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php72.git",
+                "reference": "869329b1e9894268a8a61dabb69153029b7a8c97"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97",
+                "reference": "869329b1e9894268a8a61dabb69153029b7a8c97",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php72\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
+                "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/process",
+            "version": "v4.4.44",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/process.git",
+                "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/process/zipball/5cee9cdc4f7805e2699d9fd66991a0e6df8252a2",
+                "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.3",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Process\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Executes commands in sub-processes",
+            "homepage": "https://symfony.com",
+            "time": "2022-06-27T13:16:42+00:00"
+        },
+        {
+            "name": "symfony/translation",
+            "version": "v4.4.47",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/translation.git",
+                "reference": "45036b1d53accc48fe9bab71ccd86d57eba0dd94"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/45036b1d53accc48fe9bab71ccd86d57eba0dd94",
+                "reference": "45036b1d53accc48fe9bab71ccd86d57eba0dd94",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php80": "^1.16",
+                "symfony/translation-contracts": "^1.1.6|^2"
+            },
+            "conflict": {
+                "symfony/config": "<3.4",
+                "symfony/dependency-injection": "<3.4",
+                "symfony/http-kernel": "<4.4",
+                "symfony/yaml": "<3.4"
+            },
+            "provide": {
+                "symfony/translation-implementation": "1.0|2.0"
+            },
+            "require-dev": {
+                "psr/log": "^1|^2|^3",
+                "symfony/config": "^3.4|^4.0|^5.0",
+                "symfony/console": "^3.4|^4.0|^5.0",
+                "symfony/dependency-injection": "^3.4|^4.0|^5.0",
+                "symfony/finder": "~2.8|~3.0|~4.0|^5.0",
+                "symfony/http-kernel": "^4.4",
+                "symfony/intl": "^3.4|^4.0|^5.0",
+                "symfony/service-contracts": "^1.1.2|^2",
+                "symfony/yaml": "^3.4|^4.0|^5.0"
+            },
+            "suggest": {
+                "psr/log-implementation": "To use logging capability in translator",
+                "symfony/config": "",
+                "symfony/yaml": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Translation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides tools to internationalize your application",
+            "homepage": "https://symfony.com",
+            "time": "2022-10-03T15:15:11+00:00"
+        },
+        {
+            "name": "symfony/translation-contracts",
+            "version": "v1.1.13",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/translation-contracts.git",
+                "reference": "7462e5c4cb8b9cd152f992e8f10963b5641921f6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/7462e5c4cb8b9cd152f992e8f10963b5641921f6",
+                "reference": "7462e5c4cb8b9cd152f992e8f10963b5641921f6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.3"
+            },
+            "suggest": {
+                "symfony/translation-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.1-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Translation\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to translation",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2022-06-27T13:16:42+00:00"
+        },
+        {
+            "name": "tcwei/imglazyload",
+            "version": "v1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ITzhiwei/ArticleImgLazyload.git",
+                "reference": "f7634e446de972a26aac6973141c39ca2ea62b89"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ITzhiwei/ArticleImgLazyload/zipball/f7634e446de972a26aac6973141c39ca2ea62b89",
+                "reference": "f7634e446de972a26aac6973141c39ca2ea62b89",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "tcwei\\smallTools\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "zhiwei",
+                    "email": "2394599321@qq.com"
+                }
+            ],
+            "description": "文章图片懒加载,当文章中存在大量图片时,可使用该库进行图片懒加载,看不到的图片不进行加载,节省带宽",
+            "time": "2020-06-25T14:08:30+00:00"
+        },
+        {
+            "name": "tcwei/imgsrc",
+            "version": "v2.02",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ITzhiwei/getImgSrc.git",
+                "reference": "832342b664fc7d84e0c6253ab90407f2b565887e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ITzhiwei/getImgSrc/zipball/832342b664fc7d84e0c6253ab90407f2b565887e",
+                "reference": "832342b664fc7d84e0c6253ab90407f2b565887e",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "tcwei\\smallTools\\": "imgSrc/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "zhiwei",
+                    "email": "2394599321@qq.com"
+                }
+            ],
+            "description": "高效率字符串模式(可比正则获取快3倍)从HTML或者文章内容等字符串中提取图片的src,可指定提取第几张图片、顺数第几或逆数第几、可指定黑名单等。页面下面有使用例子:",
+            "time": "2021-06-16T15:07:12+00:00"
+        },
+        {
+            "name": "thans/thinkphp-filesystem-cloud",
+            "version": "v1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/QThans/thinkphp-filesystem-cloud.git",
+                "reference": "b8d6d61a8b28df12ae9b1b19249b90e8a645ffa2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/QThans/thinkphp-filesystem-cloud/zipball/b8d6d61a8b28df12ae9b1b19249b90e8a645ffa2",
+                "reference": "b8d6d61a8b28df12ae9b1b19249b90e8a645ffa2",
+                "shasum": ""
+            },
+            "require": {
+                "liz/flysystem-qiniu": "^1.10",
+                "overtrue/flysystem-cos": "^2.0.0",
+                "php": ">=7.1.0",
+                "topthink/framework": "^6.0.0",
+                "xxtime/flysystem-aliyun-oss": "^1.4"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "thans\\filesystem\\Service"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "thans\\filesystem\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Thans",
+                    "email": "360641274@qq.com"
+                }
+            ],
+            "description": "thinkphp6.0 filesystem,include Aliyun and Qiniu",
+            "time": "2019-11-29T00:57:33+00:00"
+        },
+        {
+            "name": "thans/tp-jwt-auth",
+            "version": "v1.3.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/QThans/jwt-auth.git",
+                "reference": "ab5efcc0fd920df81fea2c404c34bb967ef13aba"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://mirrors.huaweicloud.com/repository/php/thans/tp-jwt-auth/v1.3.1/thans-tp-jwt-auth-v1.3.1.zip",
+                "reference": "ab5efcc0fd920df81fea2c404c34bb967ef13aba",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0 || ^8.0",
+                "qeq66/jwt": "3.3.*",
+                "topthink/framework": "^5.1.10 || ^6.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "thans\\jwt\\Service"
+                    ],
+                    "config": {
+                        "jwt": "config/config.php"
+                    }
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/helper.php"
+                ],
+                "psr-4": {
+                    "thans\\jwt\\": "src"
+                }
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Thans",
+                    "email": "360641274@qq.com"
+                }
+            ],
+            "description": "thinkphp  jwt auth composer",
+            "time": "2022-11-01T02:44:23+00:00"
+        },
+        {
+            "name": "topthink/framework",
+            "version": "v6.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/framework.git",
+                "reference": "4789343672aef06d571d556da369c0e156609bce"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/framework/zipball/4789343672aef06d571d556da369c0e156609bce",
+                "reference": "4789343672aef06d571d556da369c0e156609bce",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "league/flysystem": "^1.0",
+                "league/flysystem-cached-adapter": "^1.0",
+                "php": ">=7.1.0",
+                "psr/container": "~1.0",
+                "psr/log": "~1.0",
+                "psr/simple-cache": "^1.0",
+                "topthink/think-helper": "^3.1.1",
+                "topthink/think-orm": "^2.0"
+            },
+            "require-dev": {
+                "mikey179/vfsstream": "^1.6",
+                "mockery/mockery": "^1.2",
+                "phpunit/phpunit": "^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [],
+                "psr-4": {
+                    "think\\": "src/think/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                },
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP Framework.",
+            "homepage": "http://thinkphp.cn/",
+            "keywords": [
+                "framework",
+                "orm",
+                "thinkphp"
+            ],
+            "time": "2021-04-27T00:41:08+00:00"
+        },
+        {
+            "name": "topthink/think-captcha",
+            "version": "v3.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-captcha.git",
+                "reference": "b1ef360670578214edeebcf824aaf6ab7ee0528b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-captcha/zipball/b1ef360670578214edeebcf824aaf6ab7ee0528b",
+                "reference": "b1ef360670578214edeebcf824aaf6ab7ee0528b",
+                "shasum": ""
+            },
+            "require": {
+                "topthink/framework": "^6.0|^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "think\\captcha\\CaptchaService"
+                    ],
+                    "config": {
+                        "captcha": "src/config.php"
+                    }
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/helper.php"
+                ],
+                "psr-4": {
+                    "think\\captcha\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "captcha package for thinkphp",
+            "time": "2023-04-27T07:18:40+00:00"
+        },
+        {
+            "name": "topthink/think-helper",
+            "version": "v3.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-helper.git",
+                "reference": "769acbe50a4274327162f9c68ec2e89a38eb2aff"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-helper/zipball/769acbe50a4274327162f9c68ec2e89a38eb2aff",
+                "reference": "769acbe50a4274327162f9c68ec2e89a38eb2aff",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/helper.php"
+                ],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP6 Helper Package",
+            "time": "2021-12-15T04:27:55+00:00"
+        },
+        {
+            "name": "topthink/think-multi-app",
+            "version": "v1.0.17",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-multi-app.git",
+                "reference": "4055a6187296ac16c0bc7bbab4ed5d92f82f791c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-multi-app/zipball/4055a6187296ac16c0bc7bbab4ed5d92f82f791c",
+                "reference": "4055a6187296ac16c0bc7bbab4ed5d92f82f791c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0",
+                "topthink/framework": "^6.0|^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "think\\app\\Service"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\app\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "thinkphp multi app support",
+            "time": "2023-03-29T02:04:29+00:00"
+        },
+        {
+            "name": "topthink/think-orm",
+            "version": "v2.0.61",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-orm.git",
+                "reference": "10528ebf4a5106b19c3bac9c6deae7a67ff49de6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-orm/zipball/10528ebf4a5106b19c3bac9c6deae7a67ff49de6",
+                "reference": "10528ebf4a5106b19c3bac9c6deae7a67ff49de6",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-pdo": "*",
+                "php": ">=7.1.0",
+                "psr/log": "^1.0|^2.0",
+                "psr/simple-cache": "^1.0|^2.0",
+                "topthink/think-helper": "^3.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7|^8|^9.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "stubs/load_stubs.php"
+                ],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "think orm",
+            "keywords": [
+                "database",
+                "orm"
+            ],
+            "time": "2023-04-20T14:27:51+00:00"
+        },
+        {
+            "name": "topthink/think-queue",
+            "version": "v3.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-queue.git",
+                "reference": "654812b47dd7c708c4443deed27f212f8382e8da"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-queue/zipball/654812b47dd7c708c4443deed27f212f8382e8da",
+                "reference": "654812b47dd7c708c4443deed27f212f8382e8da",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "nesbot/carbon": "^2.16",
+                "symfony/process": ">=4.2",
+                "topthink/framework": "^6.0 || ^8.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.2",
+                "phpunit/phpunit": "^6.2",
+                "topthink/think-migration": "^3.0"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "think\\queue\\Service"
+                    ],
+                    "config": {
+                        "queue": "src/config.php"
+                    }
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/common.php"
+                ],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP6 Queue Package",
+            "time": "2023-07-03T05:42:01+00:00"
+        },
+        {
+            "name": "topthink/think-template",
+            "version": "v2.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-template.git",
+                "reference": "6d25642ae0e306166742fd7073dc7a159e18073c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-template/zipball/6d25642ae0e306166742fd7073dc7a159e18073c",
+                "reference": "6d25642ae0e306166742fd7073dc7a159e18073c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0",
+                "psr/simple-cache": "^1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "the php template engine",
+            "time": "2023-02-14T10:50:39+00:00"
+        },
+        {
+            "name": "topthink/think-view",
+            "version": "v1.0.14",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-view.git",
+                "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-view/zipball/edce0ae2c9551ab65f9e94a222604b0dead3576d",
+                "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0",
+                "topthink/think-template": "^2.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\view\\driver\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "thinkphp template driver",
+            "time": "2019-11-06T11:40:13+00:00"
+        },
+        {
+            "name": "workerman/gateway-worker",
+            "version": "v3.0.28",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/walkor/GatewayWorker.git",
+                "reference": "a7dffc53403133131a51b9fd3c6c6d70869cb6d3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/a7dffc53403133131a51b9fd3c6c6d70869cb6d3",
+                "reference": "a7dffc53403133131a51b9fd3c6c6d70869cb6d3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0",
+                "workerman/workerman": "^4.0.30"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "GatewayWorker\\": "./src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "homepage": "http://www.workerman.net",
+            "keywords": [
+                "communication",
+                "distributed"
+            ],
+            "time": "2023-03-24T03:56:27+00:00"
+        },
+        {
+            "name": "workerman/gatewayclient",
+            "version": "v3.0.14",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/walkor/GatewayClient.git",
+                "reference": "4362468d68251015b2b385c310252afb4d6648ed"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/walkor/GatewayClient/zipball/4362468d68251015b2b385c310252afb4d6648ed",
+                "reference": "4362468d68251015b2b385c310252afb4d6648ed",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "GatewayClient\\": "./"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "homepage": "http://www.workerman.net",
+            "time": "2021-11-29T07:03:50+00:00"
+        },
+        {
+            "name": "workerman/workerman",
+            "version": "v4.1.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/walkor/workerman.git",
+                "reference": "e967b79f95b9251a72acb971be05623ec1a51e83"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/walkor/workerman/zipball/e967b79f95b9251a72acb971be05623ec1a51e83",
+                "reference": "e967b79f95b9251a72acb971be05623ec1a51e83",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0"
+            },
+            "suggest": {
+                "ext-event": "For better performance. "
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Workerman\\": "./"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "walkor",
+                    "email": "walkor@workerman.net",
+                    "homepage": "http://www.workerman.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
+            "homepage": "http://www.workerman.net",
+            "keywords": [
+                "asynchronous",
+                "event-loop"
+            ],
+            "time": "2023-05-01T02:12:20+00:00"
+        },
+        {
+            "name": "xiaodi/think-pinyin",
+            "version": "v1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/edenleung/think-pinyin.git",
+                "reference": "4675515d3be42bebff712383c306ff99ef279f97"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/edenleung/think-pinyin/zipball/4675515d3be42bebff712383c306ff99ef279f97",
+                "reference": "4675515d3be42bebff712383c306ff99ef279f97",
+                "shasum": ""
+            },
+            "require": {
+                "overtrue/pinyin": "~4.0",
+                "topthink/framework": "6.0.*|5.1.*"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "xiaodi\\ThinkPinyin\\PinyinService"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "xiaodi\\ThinkPinyin\\": "src/"
+                },
+                "files": [
+                    "src/helpers.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "xiaodi",
+                    "email": "liangjinbiao@live.com"
+                }
+            ],
+            "description": "ThinkPHP 中文转拼音扩展包",
+            "keywords": [
+                "Chinese",
+                "Pinyin",
+                "thinkphp"
+            ],
+            "time": "2019-10-25T09:19:32+00:00"
+        },
+        {
+            "name": "xiaodi/think-pullword",
+            "version": "v1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/friendsofthinkphp/think-pullword.git",
+                "reference": "0e6f1ee141090a012dc8876209eaa3cf166c84c4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/friendsofthinkphp/think-pullword/zipball/0e6f1ee141090a012dc8876209eaa3cf166c84c4",
+                "reference": "0e6f1ee141090a012dc8876209eaa3cf166c84c4",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/guzzle": "^5.0|^6.0|^7.0",
+                "topthink/framework": "6.0.*|5.1.*"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "PullWord\\PullWordService"
+                    ]
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/helper.php"
+                ],
+                "psr-4": {
+                    "PullWord\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "xiaodi",
+                    "email": "liangjinbiao@live.com"
+                }
+            ],
+            "description": "ThinkPHP 分词/抽词 扩展包",
+            "keywords": [
+                "php",
+                "think-extend",
+                "thinkphp"
+            ],
+            "time": "2021-01-05T02:48:17+00:00"
+        },
+        {
+            "name": "xxtime/flysystem-aliyun-oss",
+            "version": "1.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/xxtime/flysystem-aliyun-oss.git",
+                "reference": "ae873b5919076157b9cfeaf39d2f56d2dbb39ee9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/xxtime/flysystem-aliyun-oss/zipball/ae873b5919076157b9cfeaf39d2f56d2dbb39ee9",
+                "reference": "ae873b5919076157b9cfeaf39d2f56d2dbb39ee9",
+                "shasum": ""
+            },
+            "require": {
+                "aliyuncs/oss-sdk-php": "~2.3",
+                "league/flysystem": "^1.0.49",
+                "php": ">=5.5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Xxtime\\Flysystem\\Aliyun\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Joe",
+                    "email": "joe@xxtime.com",
+                    "homepage": "https://github.com/xxtime",
+                    "role": "Developer"
+                }
+            ],
+            "description": "AliYun OSS adapter for flysystem. aliyuncs/oss-sdk-php ~2.3",
+            "homepage": "https://github.com/xxtime/flysystem-aliyun-oss",
+            "keywords": [
+                "Flysystem",
+                "aliyun-oss",
+                "flysystem-aliyun-oss"
+            ],
+            "time": "2019-11-12T07:57:34+00:00"
+        },
+        {
+            "name": "yunwuxin/think-cron",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/yunwuxin/think-cron.git",
+                "reference": "4013c39cea4600e05ffd10de5b63177bfb9bf480"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/yunwuxin/think-cron/zipball/4013c39cea4600e05ffd10de5b63177bfb9bf480",
+                "reference": "4013c39cea4600e05ffd10de5b63177bfb9bf480",
+                "shasum": ""
+            },
+            "require": {
+                "dragonmantank/cron-expression": "^3.0",
+                "nesbot/carbon": "^2.28",
+                "symfony/process": "^4.4 || ^5.0",
+                "topthink/framework": "^6.0 || ^8.0"
+            },
+            "require-dev": {
+                "topthink/think-swoole": "^4.0"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "config": {
+                        "cron": "src/config.php"
+                    },
+                    "services": [
+                        "yunwuxin\\cron\\Service"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "yunwuxin\\cron\\": "src/cron"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "计划任务",
+            "time": "2023-07-01T11:10:51+00:00"
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "symfony/var-dumper",
+            "version": "v4.4.47",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-dumper.git",
+                "reference": "1069c7a3fca74578022fab6f81643248d02f8e63"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/1069c7a3fca74578022fab6f81643248d02f8e63",
+                "reference": "1069c7a3fca74578022fab6f81643248d02f8e63",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php72": "~1.5",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "conflict": {
+                "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+                "symfony/console": "<3.4"
+            },
+            "require-dev": {
+                "ext-iconv": "*",
+                "symfony/console": "^3.4|^4.0|^5.0",
+                "symfony/process": "^4.4|^5.0",
+                "twig/twig": "^1.43|^2.13|^3.0.4"
+            },
+            "suggest": {
+                "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+                "ext-intl": "To show region name in time zone dump",
+                "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
+            },
+            "bin": [
+                "Resources/bin/var-dump-server"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "Resources/functions/dump.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\VarDumper\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides mechanisms for walking through any arbitrary PHP variable",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "debug",
+                "dump"
+            ],
+            "time": "2022-10-03T15:15:11+00:00"
+        },
+        {
+            "name": "topthink/think-trace",
+            "version": "v1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-trace.git",
+                "reference": "136cd5d97e8bdb780e4b5c1637c588ed7ca3e142"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-trace/zipball/136cd5d97e8bdb780e4b5c1637c588ed7ca3e142",
+                "reference": "136cd5d97e8bdb780e4b5c1637c588ed7ca3e142",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0",
+                "topthink/framework": "^6.0|^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "think\\trace\\Service"
+                    ],
+                    "config": {
+                        "trace": "src/config.php"
+                    }
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\trace\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "thinkphp debug trace",
+            "time": "2023-02-07T08:36:32+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=7.1.0"
+    },
+    "platform-dev": []
+}

+ 49 - 0
config/app.php

@@ -0,0 +1,49 @@
+<?php
+// +----------------------------------------------------------------------
+// | 应用设置
+// +----------------------------------------------------------------------
+
+return [
+    'app_name' =>"Raingad-IM",
+    'app_logo' =>"https://im.file.raingad.com/logo/logo.png",
+    'app_version' => '4.0.4',
+    'app_release' =>"20240322",
+    // 应用地址
+    'app_host'         => env('app.host', ''),
+    // 应用的命名空间
+    'app_namespace'    => '',
+    // 是否启用路由
+    'with_route'       => true,
+    'app_express'    =>    true,
+    // 默认应用
+    'default_app'      => 'index',
+    // 默认时区
+    'default_timezone' => 'Asia/Shanghai',
+
+    // 应用映射(自动多应用模式有效)
+    'app_map'          => [],
+    // 域名绑定(自动多应用模式有效)
+    'domain_bind'      => [],
+    // 禁止URL访问的应用列表(自动多应用模式有效)
+    'deny_app_list'    => [],
+
+    // 异常页面的模板文件
+    'exception_tmpl'   => app()->getThinkPath() . 'tpl/think_exception.tpl',
+
+    // 错误显示信息,非调试模式有效
+    'error_message'    => '页面错误!请稍后再试~',
+    // 显示错误信息
+    'show_error_msg'   => false,
+    'auto_multi_app' =>true,
+     //用户token加密用的秘钥
+     'aes_token_key' => env('AES_TOKEN_KEY', ''),
+     //用户LOGIN加密用的秘钥
+     'aes_login_key' => env('AES_LOGIN_KEY', ''),
+     //用户chat加密用的秘钥
+     'aes_chat_key' => env('AES_CHAT_KEY', ''),
+    //  接口加密用的秘钥
+     'app_id' => env('APP_ID', ''),
+     'app_secret' => env('APP_SECRET', ''),
+     'api_status' => env('APP_API_STATUS', true),
+];
+

+ 38 - 0
config/cache.php

@@ -0,0 +1,38 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | 缓存设置
+// +----------------------------------------------------------------------
+
+return [
+    // 默认缓存驱动
+    'default' => env('cache.driver', 'redis'),
+
+    // 缓存连接方式配置
+    'stores'  => [
+        'file' => [
+            // 驱动方式
+            'type'       => 'File',
+            // 缓存保存目录
+            'path'       => '',
+            // 缓存前缀
+            'prefix'     => '',
+            // 缓存有效期 0表示永久缓存
+            'expire'     => 0,
+            // 缓存标签前缀
+            'tag_prefix' => 'tag:',
+            // 序列化机制 例如 ['serialize', 'unserialize']
+            'serialize'  => [],
+        ],
+        'redis'                  => [
+            // 驱动方式
+            'type'   => 'redis',
+            'host'   =>env('redis.host', '127.0.0.1'),
+            'port'   => env('redis.port', '6379'),
+            'password' => env('redis.password', ''),
+            // 缓存前缀
+            'prefix' => env('redis.prefix', ''),
+        ]
+        // 更多的缓存连接
+    ],
+];

+ 19 - 0
config/captcha.php

@@ -0,0 +1,19 @@
+<?php
+
+
+return [
+    // 验证码字符集合
+    'codeSet'  => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY',
+    // 验证码字体大小(px)
+    'fontSize' => 20,
+    // 是否画混淆曲线
+    'useCurve' => false,
+    // 验证码图片高度
+    'imageH'   => 40,
+    // 验证码图片宽度
+    'imageW'   => 150,
+    // 验证码位数
+    'length'   => 4,
+    // 验证成功后是否重置
+    'reset'    => true
+];

+ 14 - 0
config/console.php

@@ -0,0 +1,14 @@
+<?php
+// +----------------------------------------------------------------------
+// | 控制台配置
+// +----------------------------------------------------------------------
+return [
+    // 指令定义
+    'commands' => [
+        'queue:work' => think\queue\command\Work::class,
+        'queue:listen' => think\queue\command\Listen::class,
+        'queue:Restart' => think\queue\command\Restart::class,
+        'task' => task\command\Task::class,
+        'worker:gateway' => app\worker\command\GatewayWorker::class
+    ],
+];

+ 18 - 0
config/cookie.php

@@ -0,0 +1,18 @@
+<?php
+// +----------------------------------------------------------------------
+// | Cookie设置
+// +----------------------------------------------------------------------
+return [
+    // cookie 保存时间
+    'expire'    => 0,
+    // cookie 保存路径
+    'path'      => '/',
+    // cookie 有效域名
+    'domain'    => '',
+    //  cookie 启用安全传输
+    'secure'    => false,
+    // httponly设置
+    'httponly'  => false,
+    // 是否使用 setcookie
+    'setcookie' => true,
+];

+ 8 - 0
config/cron.php

@@ -0,0 +1,8 @@
+<?php
+
+return [
+    'tasks' => [
+        \app\common\task\ClearMessage::class, //定时清理消息
+        \app\common\task\SetAtRead::class, //定时清理@消息
+        ]
+];

+ 62 - 0
config/database.php

@@ -0,0 +1,62 @@
+<?php
+
+return [
+    // 默认使用的数据库连接配置
+    'default'         => env('database.driver', 'mysql'),
+
+    // 自定义时间查询规则
+    'time_query_rule' => [],
+
+    // 自动写入时间戳字段
+    // true为自动识别类型 false关闭
+    // 字符串则明确指定时间字段类型 支持 int timestamp datetime date
+    'auto_timestamp'  => true,
+
+    // 时间字段取出后的默认时间格式
+    'datetime_format' => 'Y-m-d H:i:s',
+
+    // 数据库连接配置信息
+    'connections'     => [
+        'mysql' => [
+            // 数据库类型
+            'type'              => env('database.type', 'mysql'),
+            // 服务器地址
+            'hostname'          => env('database.hostname', '127.0.0.1'),
+            // 数据库名
+            'database'          => env('database.database', ''),
+            // 用户名
+            'username'          => env('database.username', 'root'),
+            // 密码
+            'password'          => env('database.password', ''),
+            // 端口
+            'hostport'          => env('database.hostport', '3306'),
+            // 数据库连接参数
+            'params'            => [],
+            // 数据库编码默认采用utf8
+            'charset'           => env('database.charset', 'utf8'),
+            // 数据库表前缀
+            'prefix'            => env('database.prefix', ''),
+
+            // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+            'deploy'            => 0,
+            // 数据库读写是否分离 主从式有效
+            'rw_separate'       => false,
+            // 读写分离后 主服务器数量
+            'master_num'        => 1,
+            // 指定从服务器序号
+            'slave_no'          => '',
+            // 是否严格检查字段是否存在
+            'fields_strict'     => true,
+            // 是否需要断线重连
+            'break_reconnect'   => false,
+            // 监听SQL
+            'trigger_sql'       => env('app_debug', true),
+            // 开启字段缓存
+            'fields_cache'      => false,
+            // 字段缓存路径
+            'schema_cache_path' => app()->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR,
+        ],
+
+        // 更多的数据库配置信息
+    ],
+];

+ 42 - 0
config/filesystem.php

@@ -0,0 +1,42 @@
+<?php
+
+return [
+    // 默认磁盘
+    'default' => env('filesystem.driver', 'local'),
+    // 磁盘列表
+    'disks'   => [
+        'local'  => [
+            'type' => 'local',
+            'root'       => app()->getRootPath() . 'public/storage',
+        ],
+        // 更多的磁盘配置信息
+        'aliyun' => [
+            'type'         => 'aliyun',
+            'accessId'     => env('filesystem.aliyun_accessId',''),
+            'accessSecret' => env('filesystem.aliyun_accessSecret',''),
+            'bucket'       => env('filesystem.aliyun_bucket',''),
+            'endpoint'     => env('filesystem.aliyun_endpoint',''),
+            'url'          => env('filesystem.aliyun_url',''),//不要斜杠结尾,此处为URL地址域名。
+        ],
+        'qiniu'  => [
+            'type'      => 'qiniu',
+            'accessKey' => env('filesystem.qiniu_accessKey',''),
+            'secretKey' => env('filesystem.qiniu_secretKey',''),
+            'bucket'    => env('filesystem.qiniu_bucket',''),
+            'url'       => env('filesystem.qiniu_url',''),//不要斜杠结尾,此处为URL地址域名。
+        ],
+        'qcloud' => [
+            'type'       => 'qcloud',
+            'region'      => env('filesystem.qcloud_region',''),//bucket 所属区域 英文
+            'appId'      => env('filesystem.qcloud_appId',''), // 域名中数字部分
+            'secretId'   => env('filesystem.qcloud_secretId',''),
+            'secretKey'  => env('filesystem.qcloud_secretKey',''),
+            'bucket'          => env('filesystem.qcloud_bucket',''),
+            'timeout'         => 60,
+            'connect_timeout' => 60,
+            'cdn'             => env('filesystem.qcloud_cdn',''),
+            'scheme'          => 'https',
+            'read_from_cdn'   => false,
+        ]
+    ],
+];

+ 33 - 0
config/gateway.php

@@ -0,0 +1,33 @@
+<?php
+return [
+    // 扩展自身需要的配置
+    'protocol'              => 'websocket', // 协议 支持 tcp udp unix http websocket text
+    'host'                  => '0.0.0.0', // 监听地址
+    'port'                  => env('worker_port',8282), // 监听端口
+    'socket'                => '', // 完整监听地址
+    'context'               => [], // socket 上下文选项
+    'register_deploy'       => env('worker_register_deploy',true), // 是否需要部署register
+    'businessWorker_deploy' => true, // 是否需要部署businessWorker
+    'gateway_deploy'        => true, // 是否需要部署gateway
+
+    // Register配置
+    'registerAddress'       => env('worker_register_address','127.0.0.1:1236'),
+
+    // Gateway配置
+    'name'                  => env('worker_name','pushGateWay'),
+    'count'                 => env('worker_count',1),
+    'lanIp'                 => env('worker_lan_ip','127.0.0.1'),
+    'startPort'             => env('worker_start_port',2300),
+    'daemonize'             => false,
+    'pingInterval'          => 20,
+    'pingNotResponseLimit'  => 0,
+    'pingData'              => '{"type":"ping"}',
+
+    // BusinsessWorker配置
+    'businessWorker'        => [
+        'name'         => 'BusinessWorker',
+        'count'        => 1,
+        'eventHandler' => 'app\worker\Events',
+    ],
+
+];

+ 16 - 0
config/hashids.php

@@ -0,0 +1,16 @@
+<?php
+/**
+ * tpAdmin [a web admin based ThinkPHP5]
+ *
+ * @author yuan1994 <tianpian0805@gmail.com>
+ * @link http://tpadmin.yuan1994.com/
+ * @copyright 2016 yuan1994 all rights reserved.
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+return [
+    // Hashids 的配置项
+    'length'   => 12, // 加密字符串长度
+    'salt'     => 'raingads', // 加密盐值
+    'alphabet' => '', // 字符仓库,不填写默认为扩展里的字符仓库
+];

+ 21 - 0
config/jwt.php

@@ -0,0 +1,21 @@
+<?php
+
+
+return [
+    'secret'      => env('JWT_SECRET'),
+    //Asymmetric key
+    'public_key'  => env('JWT_PUBLIC_KEY'),
+    'private_key' => env('JWT_PRIVATE_KEY'),
+    'password'    => env('JWT_PASSWORD'),
+    //JWT time to live
+    'ttl'         => env('JWT_TTL', 60),
+    //Refresh time to live
+    'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),
+    //JWT hashing algorithm
+    'algo'        => env('JWT_ALGO', 'HS256'),
+    //token获取方式,数组靠前值优先
+    'token_mode'    => ['header', 'cookie', 'param'],
+    //黑名单后有效期
+    'blacklist_grace_period' => env('BLACKLIST_GRACE_PERIOD', 10),
+    'blacklist_storage' => thans\jwt\provider\storage\Tp5::class,
+];

+ 25 - 0
config/lang.php

@@ -0,0 +1,25 @@
+<?php
+// +----------------------------------------------------------------------
+// | 多语言设置
+// +----------------------------------------------------------------------
+
+return [
+    // 默认语言
+    'default_lang'    => env('lang.default_lang', 'zh-cn'),
+    // 允许的语言列表
+    'allow_lang_list' => [],
+    // 多语言自动侦测变量名
+    'detect_var'      => 'lang',
+    // 是否使用Cookie记录
+    'use_cookie'      => true,
+    // 多语言cookie变量
+    'cookie_var'      => 'think_lang',
+    // 扩展语言包
+    'extend_list'     => [],
+    // Accept-Language转义为对应语言包名称
+    'accept_language' => [
+        'zh-hans-cn' => 'zh-cn',
+    ],
+    // 是否支持语言分组
+    'allow_group'     => false,
+];

+ 45 - 0
config/log.php

@@ -0,0 +1,45 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | 日志设置
+// +----------------------------------------------------------------------
+return [
+    // 默认日志记录通道
+    'default'      => env('log.channel', 'file'),
+    // 日志记录级别
+    'level'        => [],
+    // 日志类型记录的通道 ['error'=>'email',...]
+    'type_channel' => [],
+    // 关闭全局日志写入
+    'close'        => false,
+    // 全局日志处理 支持闭包
+    'processor'    => null,
+
+    // 日志通道列表
+    'channels'     => [
+        'file' => [
+            // 日志记录方式
+            'type'           => 'File',
+            // 日志保存目录
+            'path'           => '',
+            // 单文件日志写入
+            'single'         => false,
+            // 独立日志级别
+            'apart_level'    => [],
+            // 最大日志文件数量
+            'max_files'      => 0,
+            // 使用JSON格式记录
+            'json'           => false,
+            // 日志处理
+            'processor'      => null,
+            // 关闭通道日志写入
+            'close'          => false,
+            // 日志输出格式化
+            'format'         => '[%s][%s] %s',
+            // 是否实时写入
+            'realtime_write' => false,
+        ],
+        // 其它日志通道配置
+    ],
+
+];

+ 12 - 0
config/middleware.php

@@ -0,0 +1,12 @@
+<?php
+// 中间件配置
+return [
+    // 别名或分组
+    'alias'    => [
+        'checkAuth'=>app\common\middleware\CheckAuth::class,
+        'manageAuth'=>app\common\middleware\ManageAuth::class,
+        'apiAuth'=>app\common\middleware\ApiAuth::class,
+    ],
+    // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
+    'priority' => [],
+];

+ 39 - 0
config/queue.php

@@ -0,0 +1,39 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+return [
+    'default'     => 'sync',
+    'connections' => [
+        'sync'     => [
+            'type' => 'sync',
+        ],
+        'database' => [
+            'type'       => 'database',
+            'queue'      => 'default',
+            'table'      => 'jobs',
+            'connection' => null,
+        ],
+        'redis'    => [
+            'type'       => 'redis',
+            'queue'      => 'default',
+            'host'       => '127.0.0.1',
+            'port'       => 6379,
+            'password'   => '',
+            'select'     => 0,
+            'timeout'    => 0,
+            'persistent' => false,
+        ],
+    ],
+    'failed'      => [
+        'type'  => 'none',
+        'table' => 'failed_jobs',
+    ],
+];

+ 45 - 0
config/route.php

@@ -0,0 +1,45 @@
+<?php
+// +----------------------------------------------------------------------
+// | 路由设置
+// +----------------------------------------------------------------------
+
+return [
+    // pathinfo分隔符
+    'pathinfo_depr'         => '/',
+    // URL伪静态后缀
+    'url_html_suffix'       => 'html',
+    // URL普通方式参数 用于自动生成
+    'url_common_param'      => true,
+    // 是否开启路由延迟解析
+    'url_lazy_route'        => false,
+    // 是否强制使用路由
+    'url_route_must'        => false,
+    // 合并路由规则
+    'route_rule_merge'      => false,
+    // 路由是否完全匹配
+    'route_complete_match'  => false,
+    // 访问控制器层名称
+    'controller_layer'      => 'controller',
+    // 空控制器名
+    'empty_controller'      => 'Error',
+    // 是否使用控制器后缀
+    'controller_suffix'     => false,
+    // 默认的路由变量规则
+    'default_route_pattern' => '[\w\.]+',
+    // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
+    'request_cache_key'     => false,
+    // 请求缓存有效期
+    'request_cache_expire'  => null,
+    // 全局请求缓存排除规则
+    'request_cache_except'  => [],
+    // 默认控制器名
+    'default_controller'    => 'Index',
+    // 默认操作名
+    'default_action'        => 'index',
+    // 操作方法后缀
+    'action_suffix'         => '',
+    // 默认JSONP格式返回的处理方法
+    'default_jsonp_handler' => 'jsonpReturn',
+    // 默认JSONP处理方法
+    'var_jsonp_handler'     => 'callback',
+];

+ 19 - 0
config/session.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | 会话设置
+// +----------------------------------------------------------------------
+
+return [
+    // session name
+    'name'           => 'PHPSESSID',
+    // SESSION_ID的提交变量,解决flash上传跨域
+    'var_session_id' => '',
+    // 驱动方式 支持file cache
+    'type'           => 'file',
+    // 存储连接标识 当type使用cache的时候有效
+    'store'          => null,
+    // 过期时间
+    'expire'         => 9*3600,
+    // 前缀
+    'prefix'         => '',
+];

+ 159 - 0
config/sms.php

@@ -0,0 +1,159 @@
+<?php
+// +----------------------------------------------------------------------
+// | 胜家云 [ SingKa Cloud ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016~2020 https://www.singka.net All rights reserved.
+// +----------------------------------------------------------------------
+// | 宁波晟嘉网络科技有限公司
+// +----------------------------------------------------------------------
+// | Author: ShyComet <shycomet@qq.com>
+// +----------------------------------------------------------------------
+return [
+    'driver'       => 'aliyun', // 驱动器
+    'aliyun'       => [
+        'version'       => '2017-05-25',
+        'host'          => 'dysmsapi.aliyuncs.com',
+        'scheme'        => 'http',
+        'region_id'     => 'cn-hangzhou',
+        'access_key'    => '',
+        'access_secret' => '',
+        'sign_name'     => '',
+        'actions'       => [
+            'register'        => [
+                'actions_name'      => '注册验证',
+                'template_id'  => 'SMS_53115055',
+            ],
+            'login'           => [
+                'actions_name'      => '登录验证',
+                'template_id'  => 'SMS_53115057',
+            ],
+            'changePassword' => [
+                'actions_name'      => '修改密码',
+                'template_id'  => 'SMS_53115053',
+            ],
+            'changeUserinfo' => [
+                'actions_name'      => '变更信息',
+                'template_id'  => 'SMS_53115052',
+            ],
+        ],
+    ],
+    'ucloud'       => [
+        'public_key'   =>  '',
+        'private_key'  =>  '',
+        'project_id'   =>  '',
+        'base_url'     =>  'https://api.ucloud.cn',
+        'sign_name'       => '',
+        'actions'       => [
+            'register'        => [
+                'actions_name'      => '注册验证',
+                'template_id'  => 'UTA1910164E29F4',
+            ],
+            'login'           => [
+                'actions_name'      => '登录验证',
+                'template_id'  => 'UTA1910164E29F4',
+            ],
+            'changePassword' => [
+                'actions_name'      => '修改密码',
+                'template_id'  => 'UTA1910164E29F4',
+            ],
+            'changeUserinfo' => [
+                'actions_name'      => '变更信息',
+                'template_id'  => 'UTA1910164E29F4',
+            ],
+        ],
+    ],
+    'qcloud'       => [
+        'appid'   =>  '',
+        'appkey'  =>  '',
+        'sign_name'       => '',
+        'actions'       => [
+            'register'        => [
+                'actions_name'      => '注册验证',
+                'template_id'  => '566198',
+            ],
+            'login'           => [
+                'actions_name'      => '登录验证',
+                'template_id'  => '566197',
+            ],
+            'changePassword' => [
+                'actions_name'      => '修改密码',
+                'template_id'  => '566199',
+            ],
+            'changeUserinfo' => [
+                'actions_name'      => '变更信息',
+                'template_id'  => '566200',
+            ],
+        ],
+    ],
+    'qiniu'       => [
+        'AccessKey'   =>  '',
+        'SecretKey'  =>  '',
+        'actions'       => [
+            'register'        => [
+                'actions_name'      => '注册验证',
+                'template_id'  => '1246849772845797376',
+            ],
+            'login'           => [
+                'actions_name'      => '登录验证',
+                'template_id'  => '1246849654881001472',
+            ],
+            'changePassword' => [
+                'actions_name'      => '修改密码',
+                'template_id'  => '1246849964902977536',
+            ],
+            'changeUserinfo' => [
+                'actions_name'      => '变更信息',
+                'template_id'  => '1246849860733243392',
+            ],
+        ],
+    ],
+    'upyun'       => [
+        'id'   =>  '',
+        'token'  =>  '',
+        'apiurl'  =>  '',
+        'actions'       => [
+            'register'        => [
+                'actions_name'      => '注册验证',
+                'template_id'  => '2591',
+            ],
+            'login'           => [
+                'actions_name'      => '登录验证',
+                'template_id'  => '2592',
+            ],
+            'changePassword' => [
+                'actions_name'      => '修改密码',
+                'template_id'  => '2590',
+            ],
+            'changeUserinfo' => [
+                'actions_name'      => '变更信息',
+                'template_id'  => '2589',
+            ],
+        ],
+    ],
+    'huawei'       => [
+        'url'  =>  '',
+        'appKey'   =>  '',
+        'appSecret'  =>  '',
+        'sender'  =>  '',
+        'signature'  =>  '',
+        'statusCallback'  =>  '',
+        'actions'       => [
+            'register'        => [
+                'actions_name'      => '注册验证',
+                'template_id'  => '2591',
+            ],
+            'login'           => [
+                'actions_name'      => '登录验证',
+                'template_id'  => '2592',
+            ],
+            'changePassword' => [
+                'actions_name'      => '修改密码',
+                'template_id'  => '2590',
+            ],
+            'changeUserinfo' => [
+                'actions_name'      => '变更信息',
+                'template_id'  => '2589',
+            ],
+        ],
+    ]
+];

+ 10 - 0
config/trace.php

@@ -0,0 +1,10 @@
+<?php
+// +----------------------------------------------------------------------
+// | Trace设置 开启调试模式后有效
+// +----------------------------------------------------------------------
+return [
+    // 内置Html和Console两种方式 支持扩展
+    'type'    => 'Html',
+    // 读取的日志通道名
+    'channel' => '',
+];

+ 45 - 0
config/version.php

@@ -0,0 +1,45 @@
+<?php
+# app_name 应用名称,所有的安装包都是用该名称命名,不能用中文!!!!!!!!!!!!
+
+# VERSION:移动端app版本信息
+# RELEASE:根据该参数确定版本,版本比移动罐的版本大就会提示更新,苹果和ios通过这个检测
+# UPDATE_TYPE :forcibly 强制更新, solicit弹窗确认更新, silent 静默更新 
+# UPDATE_INFO :更新说明,换行用\n 
+return [
+    'app_name'=>'Raingad-IM',
+    'andriod' => [
+        'version' => '4.1.0',
+        'release' => '20240323',
+        'url' =>env('app.andriod_webclip',''),
+        'update_info' => '1.修复了一些bug\n2.优化了一些功能',
+        'update_type' => 'solicit',
+    ],
+    'ios' => [
+        'version' => '4.1.0',
+        'release' => '20240323',
+        'url' => env('app.ios_webclip',''),
+        'update_info' => '暂无',
+        'update_type' => 'solicit',
+    ],
+    'windows' => [
+        'version' => '4.1.0',
+        'release' => '20240328',
+        'url' => env('app.win_webclip',''),
+        'update_info' => '1.增加自动更新机制',
+        'update_type' => 'solicit',
+    ],
+    'mac' => [
+        'version' => '4.0.0',
+        'release' => '20240323',
+        'url' => env('app.mac_webclip',''),
+        'update_info' => '1.修复了一些bug\n2.优化了一些功能',
+        'update_type' => 'solicit',
+    ],
+    'serve' => [
+        'version' => '4.0.0',
+        'release' => '20240323',
+        'url' => '',
+        'update_info' => '1.修复了一些bug\n2.优化了一些功能',
+        'update_type' => 'solicit',
+    ],
+];

+ 28 - 0
config/view.php

@@ -0,0 +1,28 @@
+<?php
+// +----------------------------------------------------------------------
+// | 模板设置
+// +----------------------------------------------------------------------
+
+return [
+    // 模板引擎类型使用Think
+    'type'          => 'Think',
+    // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
+    'auto_rule'     => 1,
+    // 模板目录名
+    'view_dir_name' => 'view',
+    // 模板后缀
+    'view_suffix'   => 'html',
+    // 模板文件名分隔符
+    'view_depr'     => DIRECTORY_SEPARATOR,
+    // 模板引擎普通标签开始标记
+    'tpl_begin'     => '{',
+    // 模板引擎普通标签结束标记
+    'tpl_end'       => '}',
+    // 标签库标签开始标记
+    'taglib_begin'  => '{',
+    // 标签库标签结束标记
+    'taglib_end'    => '}',
+    'tpl_replace_string'  =>  [
+        '__STATIC__'=>'/static',
+    ]
+];

+ 97 - 0
example.env

@@ -0,0 +1,97 @@
+APP_DEBUG = true
+
+[APP]
+DEFAULT_TIMEZONE = Asia/Shanghai
+#开放api的开关
+API_STATUS = true
+#开放api接口的配置信息
+ID = a1b2c3d4e5f
+SECRET = 123456sdfghjkl
+
+# 安卓包名,如果上架了市场,根据市场ID跳转市场
+ANDRIOD_APPID = 
+#安卓下载地址,如果未设置会检测根目录是否有app.apk
+ANDRIOD_WEBCLIP =https://emoji.raingad.com/file/raingad.apk
+#APPSTORE市场ID
+IOS_APPID =
+#IOS下载地址,如果没有市场的ID则使用下载地址
+IOS_WEBCLIP =
+#windows下载地址
+WIN_WEBCLIP = 
+#mac下载地址
+MAC_WEBCLIP = 
+
+[DATABASE]
+TYPE = mysql
+HOSTNAME = 127.0.0.1
+DATABASE = im
+USERNAME = root
+PASSWORD = My01020304
+HOSTPORT = 3306
+CHARSET = utf8mb4
+DEBUG = true
+prefix = yu_
+[LANG]
+default_lang = zh-cn
+
+[REDIS]
+HOST = 127.0.0.1
+PORT = 6379
+PASSWORD =
+PREFIX = 
+
+[AES]
+TOKEN_KEY = tHTi8USApxsdfnhTM
+LOGIN_KEY = t2fe6HMnmssswDVi2
+#聊天内容加密,如果不加密则留空,一旦加密就不能修改,如果修改了需要清空所有聊天记录
+CHAT_KEY  =
+
+[JWT]
+SECRET = 17b190c0d612321f94f57325ae5a8b4c
+TTL = 2592000
+
+[WORKER]
+NAME = businessWorker
+PORT = 8282
+# 根据自己的核心数而配置
+COUNT = 1
+START_PORT = 2300
+REGISTER_ADDRESS =127.0.0.1:1236
+lAN_IP = 127.0.0.1
+# 分部署部署只需要启动一个gateway,其他的gateway只需要配置register_address即可
+REGISTER_DEPLOY = true
+
+#配置预览功能,本系统主要使用第三方的预览工具,比如永中云转换,自带预览系统
+[PREVIEW]
+# 自带预览系统URL,主要用于预览媒体文件,已内置,必须要有最后的/斜杠
+own=
+# 永中云文件预览,主要用于文档预览,必须要有最后的/斜杠
+yzdcs=http://domain/
+# 永中云api code
+keycode=17444844212312
+
+[UNIPUSH]
+# unipush的云函数转url地址,主要用于推送
+URL=
+# unipush直接推送通知栏还是app接收后再创建通知栏
+IS_FORCE=false
+
+# 配置对象储存,主要用于聊天文件储存,可以通过后台进行配置
+
+[FILESYSTEM]
+driver=local
+aliyun_accessId=false
+aliyun_accessSecret=false
+aliyun_bucket=false
+aliyun_endpoint=false
+aliyun_url=false
+qiniu_accessKey=false
+qiniu_secretKey=false
+qiniu_bucket=false
+qiniu_url=false
+qcloud_region=false
+qcloud_appId=false
+qcloud_secretId=false
+qcloud_secretKey=false
+qcloud_bucket=false
+qcloud_cdn=false

+ 146 - 0
extend/Agent.php

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

+ 57 - 0
extend/Hashids/HashGenerator.php

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

+ 374 - 0
extend/Hashids/Hashids.php

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

+ 109 - 0
extend/Ip.php

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

+ 94 - 0
extend/easyTask/Check.php

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

+ 129 - 0
extend/easyTask/Command.php

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

+ 36 - 0
extend/easyTask/Env.php

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

+ 113 - 0
extend/easyTask/Error.php

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

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

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

+ 503 - 0
extend/easyTask/Helper.php

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

+ 52 - 0
extend/easyTask/Lock.php

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

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

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

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

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

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

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

+ 87 - 0
extend/easyTask/Queue.php

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

+ 292 - 0
extend/easyTask/Table.php

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

+ 334 - 0
extend/easyTask/Task.php

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

+ 104 - 0
extend/easyTask/Terminal.php

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

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff