update
This commit is contained in:
commit
22d166de26
|
@ -0,0 +1,8 @@
|
|||
.idea
|
||||
.code
|
||||
public/nginx.htaccess
|
||||
runtime/*
|
||||
vendor/workerman/*.log
|
||||
vendor/workerman/*.pid
|
||||
.env
|
||||
start_for_win.bat
|
|
@ -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.
|
|
@ -0,0 +1,36 @@
|
|||
# IM即时聊天
|
||||
|
||||
#### Description
|
||||
im后端代码,需要配合前端使用
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
|
@ -0,0 +1,192 @@
|
|||
# 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
|
||||
|
||||
#### 支持功能
|
||||
|
||||
1. 支持单聊和群聊,支持发送表情、图片、语音、视频和文件消息
|
||||
|
||||
2. 单聊支持消息已读未读的状态显示,在线状态显示
|
||||
|
||||
3. 群聊创建、删除和群成员管理、群公告、群禁言等
|
||||
|
||||
4. 支持置顶联系人,消息免打扰;支持设置新消息声音提醒,浏览器通知
|
||||
|
||||
5. 支持一对一音视频通话(已打通web端和移动端)
|
||||
|
||||
6. 支持文件、图片和绝大部分媒体文件在线预览
|
||||
|
||||
7. 支持移动端(H5、APP和小程序,部分功能不兼容),支持简易后台管理
|
||||
|
||||
8. 全新支持企业模式和社区模式,社区模式支持注册、添加好友功能
|
||||
|
||||
|
||||
#### 最新更新
|
||||
**v-3.0.0** (2023年9月4日)
|
||||
<font color="red">不兼容之前的版本!!!</font>
|
||||
1. 全新升级,不兼容之前版本.
|
||||
2. 修改登录方式,新增JWT token登录,不再服务器缓存登录信息,节约服务器资源。
|
||||
3. 新增聊天内容加密,聊天数据更安全,请仔细阅读 `.example.env` 里面的配置.
|
||||
4. 消息推送服务可以自定义端口,已集成到环境配置文件中 `.example.env` ,仅支持linux,window请按照原来的方式操作。
|
||||
5. 新增websocket连接认证,未认证会被断开连接。
|
||||
6. 新增群二维码,移动端可以扫码加群。暂未实现聊天中长按图片识别二维码加群。
|
||||
7. 修复聊天记录重复加载的情况。
|
||||
8. 修复历史消息不会实时置为已读的情况。
|
||||
9. 优化若干样式和bug
|
||||
|
||||
|
||||
**v-2.8.25** (2023年8月25日)
|
||||
1. 优化移动端的websocket连接
|
||||
2. 移动端新增记住密码
|
||||
3. 优化消息接收的处理以及上下线状态
|
||||
4. 移动端完善用户资料修改,密码修改等
|
||||
5. 打通移动端和web端的音视频互通,有bug,但是可以忽略,已开源web端音视频源码
|
||||
6. 新增音视频通话消息类型
|
||||
|
||||
**v-2.8.1** (2023年8月1日)
|
||||
1. 移动端新增消息撤回、复制,消息的发送状态,1对1消息已读、未读状态
|
||||
2. 移动端修复聊天页面中,消息不能滚动到底部的情况
|
||||
3. 移动端新增音视频通话引导用户获取音视频权限
|
||||
4. web端新增截屏功能,功能用的第三方插件,虽然有瑕疵,但是很强大
|
||||
5. 优化websocket掉线问题,增加客户端主动心跳
|
||||
6. 优化web端浏览器通知
|
||||
7. 移动端支持社区模式,加好友等功能
|
||||
8. 新增邮件加密方式选择
|
||||
9. 兼容php7.4
|
||||
10. 修复element-ui图标有时候加载乱码
|
||||
|
||||
|
||||
**v-2.7.14** (2023年7月14日)
|
||||
1. 支持简易后台管理
|
||||
2. 全新支持企业模式和社区模式,可自由切换
|
||||
3. 新增阿里云、七牛云、腾讯云等对象储存
|
||||
4. 新增群头像自动生成
|
||||
5. 新增人员资料查看
|
||||
6. 新增文件管理,可以快速发送到聊天。
|
||||
7. 新增移动端1对1音视频通话,不和web端互通
|
||||
|
||||
|
||||
**v-2.5.30** (2023年5月20日)
|
||||
1. 新增windows系统的支持,建议windows仅用于开发环境,正式环境请使用linux。
|
||||
2. 新增企业模式下全局发送消息的演示页面。
|
||||
|
||||
|
||||
**v-1.10.30** (2022年10月30日)
|
||||
1. 升级vue-cli2到vue-cli3
|
||||
2. 优化发送按键和换行键
|
||||
3. 新增语音消息、视频消息
|
||||
4. 新增支持音视频通话(peerjs)
|
||||
5. 使用sass依赖替代node-sass(这东西太坑了)
|
||||
|
||||
|
||||
**v-0.1.0** (2021年04月24日)
|
||||
1. 项目建立,只是有项目需要,才开发了这个应用
|
||||
|
||||
#### 软件架构
|
||||
|
||||
后端技术栈:`thinkphp6+workerman+redis`
|
||||
|
||||
前端技术栈:`vue2+Lemon-IMUI+element-UI`
|
||||
|
||||
|
||||
#### 安装教程
|
||||
##### 源码下载
|
||||
- 克隆代码到本地:
|
||||
```
|
||||
git clone https://gitee.com/raingad/im-instant-chat.git
|
||||
```
|
||||
- 进入项目目录,执行:
|
||||
```
|
||||
composer install
|
||||
```
|
||||
或者
|
||||
- 下载完整源码放到自己的服务器上。请注意看gitee项目主页顶部 `右侧的发行版`,请在发行版中下载最新发布的版本。
|
||||
|
||||
##### 开始安装
|
||||
|
||||
1. 开启伪静态,下面只展示nginx的,Apache的已内置
|
||||
```
|
||||
location ~* (runtime|application)/{
|
||||
return 403;
|
||||
}
|
||||
location / {
|
||||
if (!-e $request_filename){
|
||||
rewrite ^(.*)$ /index.php?s=$1 last; break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 访问你的ip或者域名即可进入自定义安装向导。
|
||||
|
||||
4. 先参考下一章“启动消息推送服务”,再来安装程序最佳。
|
||||
|
||||
ps:必须要取消禁用的函数 `shell_exec` `proc_open` `pcntl_exec`。其它的根据调试情况来取消禁用
|
||||
|
||||
|
||||
##### 如果安装失败
|
||||
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 端口,如需修改,请修改环境噢配置文件中`WORKEER` 板块的相应参数。windows用户请修改 [ `app\worker\start_gateway.php`] 中的 8282 端口。端口号根据情况需改,如果修改了端口号,需要将前端的程序修改并重新打包上传到项目的public目录下。
|
||||
|
||||
3. 系统采用直接用域名作为websocket服务的地址,所以监听端口需要在网站的nginx中配置代理并监听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;
|
||||
}
|
||||
```
|
||||
|
||||
4. 更多关于workerman的使用,请进入[workerman官网](https://www.workerman.net/)官网进行查阅。
|
||||
|
||||
5. 部署完成之后管理员账号密码为:`administrator` `123456`,管理入口在聊天界面的左下角。
|
||||
|
||||
#### 安装部署服务
|
||||
|
||||
服务器要求:
|
||||
| 所需环境 | 版本 | 备注 |
|
||||
| --------- | ---- | ---- |
|
||||
| linux | >= 7.0 | 以下的版本未做测试 |
|
||||
| php | >= 7.1 | 不兼容8 |
|
||||
| mysql | >= 5.7 | 必须要5.7及以上 |
|
||||
| redis | >= 5.0 | |
|
||||
| workerman | >= 4.0 | 用于消息服务部署 |
|
||||
|
||||
作者提供本系统的安装服务,包括后端和前端部署到线上,保证项目的完美运行,200元/次,安装服务可赠送web端音视频通话源码,如有需要可以进群联系作者!
|
||||
|
||||
#### 交流群
|
||||
如果有什么问题,请留言,或者加入我们的QQ群!
|
||||
|
||||
创作不易,点个star吧
|
||||
|
||||
[QQ 交流群:336921267](https://jq.qq.com/?_wv=1027&k=jMQAt9lh)
|
||||
|
|
@ -0,0 +1 @@
|
|||
deny from all
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace app;
|
||||
|
||||
use think\Service;
|
||||
|
||||
/**
|
||||
* 应用服务类
|
||||
*/
|
||||
class AppService extends Service
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
// 服务注册
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
// 服务启动
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace app;
|
||||
|
||||
// 应用请求对象类
|
||||
class Request extends \think\Request
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,872 @@
|
|||
<?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);
|
||||
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';
|
||||
} else {
|
||||
$send = "sendToGroup";
|
||||
}
|
||||
Gateway::$send($user, $message);
|
||||
}
|
||||
}catch(\Exception $e){
|
||||
//忽略错误
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 预览文件
|
||||
function previewUrl($url){
|
||||
$previewConf=config('preview');
|
||||
$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{
|
||||
$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 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;
|
||||
}
|
|
@ -0,0 +1,297 @@
|
|||
<?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();
|
||||
$userInfo=User::where(['account'=> $param['account']])->withoutField('register_ip,login_count,update_time,create_time')->find();
|
||||
if($userInfo==null){
|
||||
return warning('当前用户不存在!');
|
||||
}elseif($userInfo['status']==0){
|
||||
return warning('您的账号已被禁用');
|
||||
}else{
|
||||
$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){
|
||||
$this->doBindUid($userInfo['user_id'],$client_id);
|
||||
}
|
||||
$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');
|
||||
try{
|
||||
$this->doBindUid($user_id,$client_id);
|
||||
}catch(\Exception $e){
|
||||
// 未找到用户
|
||||
}
|
||||
return success('');
|
||||
}
|
||||
|
||||
// 执行绑定
|
||||
public function doBindUid($user_id,$client_id){
|
||||
// 如果当前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);
|
||||
}
|
||||
}
|
||||
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']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
<?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'];
|
||||
}
|
||||
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' && $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;
|
||||
}
|
||||
$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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
namespace app\common\listener;
|
||||
|
||||
use app\enterprise\model\{User};
|
||||
|
||||
// 监听用户注册后的操作
|
||||
class UserRegister
|
||||
{
|
||||
|
||||
public function handle(User $user,$data){
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?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()
|
||||
{
|
||||
//设置每天8点执行
|
||||
$this->dailyAt('02:00');
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行任务
|
||||
* @return mixed
|
||||
*/
|
||||
protected function execute()
|
||||
{
|
||||
$this->writeLog('任务开始执行');
|
||||
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();
|
||||
}
|
||||
$this->writeLog('消息清理成功');
|
||||
} catch (Exception $e) {
|
||||
$this->writeLog('消息清理失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
<?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;
|
||||
$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',
|
||||
'nickname'=>'新朋友',
|
||||
'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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,406 @@
|
|||
<?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],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];
|
||||
// event('GroupChange', ['action' => 'joinGroup', 'group_id' => $group_id, 'param' => $param]);
|
||||
// exit();
|
||||
$inviteUid=$param['inviteUid'] ?? '';
|
||||
$groupUserCount=GroupUser::where(['group_id'=>$group_id,'status'=>1])->count();
|
||||
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('更换失败');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,667 @@
|
|||
<?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;
|
||||
use Char0n\FFMpegPHP\Movie;
|
||||
|
||||
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'] : '';
|
||||
$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 . '%'];
|
||||
}
|
||||
$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 (!isset($param['type'])) {
|
||||
$data = array_reverse($data);
|
||||
}
|
||||
return success('', $data, $list->total());
|
||||
}
|
||||
|
||||
protected function recombileMsg($list)
|
||||
{
|
||||
$data = [];
|
||||
$userInfo = $this->userInfo;
|
||||
if ($list) {
|
||||
$listData = $list->toArray()['data'];
|
||||
$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 = '';
|
||||
if (in_array($v['type'], $this->fileType)) {
|
||||
$content = getFileUrl($content);
|
||||
$preview = previewUrl($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;
|
||||
}
|
||||
}
|
||||
$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'],
|
||||
'toContactId' => $v['to_user'],
|
||||
'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,
|
||||
'extends'=>is_string($v['extends'])?json_decode($v['extends'],true) : $v['extends']
|
||||
];
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
// 设置当前窗口的消息默认为已读
|
||||
public function setMsgIsRead()
|
||||
{
|
||||
$param = $this->request->param();
|
||||
$this->setIsRead($param['is_group'], $param['toContactId']);
|
||||
// 判断是否是一个二维数组
|
||||
if (is_array($param['messages'][0] ?? '')) {
|
||||
$messages=$param['messages'];
|
||||
} else {
|
||||
$messages=[$param['messages']];
|
||||
}
|
||||
if (!$param['is_group']) {
|
||||
wsSendMsg($param['fromUser'], 'isRead', $messages, 0);
|
||||
}
|
||||
return success('');
|
||||
}
|
||||
|
||||
// 设置消息已读
|
||||
protected function setIsRead($is_group, $to_user)
|
||||
{
|
||||
if ($is_group) {
|
||||
$chat_identify = $to_user;
|
||||
$toContactId = explode('-', $to_user)[1];
|
||||
// 更新群里面我的所有未读消息为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) {
|
||||
$text = "撤回了一条消息";
|
||||
$fromUserName = "对方";
|
||||
$toContactId = $message['to_user'];
|
||||
if ($message['is_group'] == 1) {
|
||||
$fromUserName = $this->userInfo['realname'];
|
||||
$toContactId = explode('-', $message['chat_identify'])[1];
|
||||
}
|
||||
$message->content = str_encipher($text);
|
||||
$message->type = 'event';
|
||||
$message->is_undo = 1;
|
||||
$message->create_time = time();
|
||||
$message->save();
|
||||
$data = $message->toArray();
|
||||
$data['content'] = $fromUserName . $text;
|
||||
wsSendMsg($toContactId, 'undoMessage', $data, $message['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,
|
||||
'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->save();
|
||||
return success('修改成功');
|
||||
}catch (\Exception $e){
|
||||
return error('修改失败');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
return [
|
||||
"checkAuth"
|
||||
];
|
|
@ -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";
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
/**
|
||||
* raingad IM [ThinkPHP6]
|
||||
* @author xiekunyu <raingad@foxmail.com>
|
||||
*/
|
||||
namespace app\enterprise\model;
|
||||
|
||||
use app\BaseModel;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
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']);
|
||||
}
|
||||
}
|
||||
$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,
|
||||
'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['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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public function matchData(){
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,377 @@
|
|||
<?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,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();
|
||||
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;
|
||||
if ($getGroupLastMsg) {
|
||||
foreach ($getGroupLastMsg as $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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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]['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;
|
||||
}
|
||||
}
|
|
@ -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'],
|
||||
];
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
// 事件定义文件
|
||||
return [
|
||||
'bind' => [
|
||||
],
|
||||
|
||||
'listen' => [
|
||||
'AppInit' => [],
|
||||
'HttpRun' => [],
|
||||
'HttpEnd' => [],
|
||||
'LogLevel' => [],
|
||||
'LogWrite' => [],
|
||||
'UserRegister'=>['app\common\listener\UserRegister'],
|
||||
'GroupChange'=>['app\enterprise\listener\GroupChange'],
|
||||
],
|
||||
|
||||
'subscribe' => [
|
||||
],
|
||||
];
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
namespace app\index\controller;
|
||||
|
||||
use app\enterprise\model\{File,Group,User};
|
||||
use think\facade\View;
|
||||
|
||||
class Index
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
if (!file_exists(PUBLIC_PATH . "install.lock")) {
|
||||
return redirect(url('index/install/index'));
|
||||
}
|
||||
return redirect("/index.html");
|
||||
}
|
||||
|
||||
public function view()
|
||||
{
|
||||
return view::fetch();
|
||||
}
|
||||
|
||||
// 头像生成
|
||||
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('二维码已失效');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,528 @@
|
|||
<?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(PUBLIC_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.'version.php');
|
||||
return $res ? : array('VERSION' => '0.5.18','RELEASE' => '20210518');
|
||||
}
|
||||
|
||||
// 检查数据库
|
||||
public function checkDatabase(){
|
||||
|
||||
if (file_exists(PUBLIC_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(PUBLIC_PATH . "install.lock");
|
||||
return error('数据库sql安装出错,请操作数据库手动导入sql文件'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
touch(PUBLIC_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
|
||||
|
||||
[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=http://view.riangad.com/
|
||||
# 永中云文件预览,主要用于文档预览,必须要有最后的/斜杠
|
||||
yzdcs=
|
||||
# 永中云api code
|
||||
keycode=17444844212312
|
||||
|
||||
# 配置对象储存,主要用于聊天文件储存,可以通过后台进行配置
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?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');
|
|
@ -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('发送成功');
|
||||
|
||||
}
|
||||
}
|
|
@ -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('设置失败!');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
<?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' => '消息推送'
|
||||
];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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('修改失败');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
return [
|
||||
"checkAuth",
|
||||
"manageAuth"
|
||||
];
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
// 全局中间件定义文件
|
||||
return [
|
||||
// 全局请求缓存
|
||||
|
||||
// 'think\middleware\CheckRequestCache',
|
||||
|
||||
// 多语言加载
|
||||
|
||||
// 'think\middleware\LoadLangPack',
|
||||
|
||||
// Session初始化
|
||||
|
||||
'think\middleware\SessionInit',
|
||||
|
||||
// 页面Trace调试
|
||||
|
||||
// 'think\middleware\TraceDebug',
|
||||
];
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
use app\ExceptionHandle;
|
||||
use app\Request;
|
||||
|
||||
// 容器Provider定义文件
|
||||
return [
|
||||
'think\Request' => Request::class,
|
||||
'think\exception\Handle' => ExceptionHandle::class,
|
||||
];
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
use app\AppService;
|
||||
|
||||
// 系统服务定义文件
|
||||
// 服务在完成全局初始化之后执行
|
||||
return [
|
||||
AppService::class,
|
||||
];
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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]
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"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",
|
||||
"char0n/ffmpeg-php": "^3.0.0",
|
||||
"ext-gd": "*"
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 应用设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
'app_name' =>"Raingad-IM",
|
||||
'app_logo' =>"https://im.file.raingad.com/logo/logo.png",
|
||||
'app_version' =>"3.0.4",
|
||||
'app_release' =>"20230914",
|
||||
// 应用地址
|
||||
'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', ''),
|
||||
];
|
||||
|
|
@ -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', ''),
|
||||
]
|
||||
// 更多的缓存连接
|
||||
],
|
||||
];
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
|
||||
return [
|
||||
// 验证码字符集合
|
||||
'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY',
|
||||
// 验证码字体大小(px)
|
||||
'fontSize' => 20,
|
||||
// 是否画混淆曲线
|
||||
'useCurve' => false,
|
||||
// 验证码图片高度
|
||||
'imageH' => 40,
|
||||
// 验证码图片宽度
|
||||
'imageW' => 150,
|
||||
// 验证码位数
|
||||
'length' => 4,
|
||||
// 验证成功后是否重置
|
||||
'reset' => true
|
||||
];
|
|
@ -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
|
||||
],
|
||||
];
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Cookie设置
|
||||
// +----------------------------------------------------------------------
|
||||
return [
|
||||
// cookie 保存时间
|
||||
'expire' => 0,
|
||||
// cookie 保存路径
|
||||
'path' => '/',
|
||||
// cookie 有效域名
|
||||
'domain' => '',
|
||||
// cookie 启用安全传输
|
||||
'secure' => false,
|
||||
// httponly设置
|
||||
'httponly' => false,
|
||||
// 是否使用 setcookie
|
||||
'setcookie' => true,
|
||||
];
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'tasks' => [
|
||||
\app\common\task\ClearMessage::class, //任务的完整类名
|
||||
]
|
||||
];
|
|
@ -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,
|
||||
],
|
||||
|
||||
// 更多的数据库配置信息
|
||||
],
|
||||
];
|
|
@ -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,
|
||||
]
|
||||
],
|
||||
];
|
|
@ -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',
|
||||
],
|
||||
|
||||
];
|
|
@ -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' => '', // 字符仓库,不填写默认为扩展里的字符仓库
|
||||
];
|
|
@ -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,
|
||||
];
|
|
@ -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,
|
||||
];
|
|
@ -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,
|
||||
],
|
||||
// 其它日志通道配置
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// 中间件配置
|
||||
return [
|
||||
// 别名或分组
|
||||
'alias' => [
|
||||
'checkAuth'=>app\common\middleware\CheckAuth::class,
|
||||
'manageAuth'=>app\common\middleware\ManageAuth::class,
|
||||
],
|
||||
// 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
|
||||
'priority' => [],
|
||||
];
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
return [
|
||||
'own'=>env('preview.own', ''),
|
||||
'yzdcs'=>env('preview.yzdcs', ''),
|
||||
'keycode'=>env('preview.keycode', ''),
|
||||
];
|
|
@ -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',
|
||||
],
|
||||
];
|
|
@ -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',
|
||||
];
|
|
@ -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' => '',
|
||||
];
|
|
@ -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',
|
||||
],
|
||||
],
|
||||
]
|
||||
];
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Trace设置 开启调试模式后有效
|
||||
// +----------------------------------------------------------------------
|
||||
return [
|
||||
// 内置Html和Console两种方式 支持扩展
|
||||
'type' => 'Html',
|
||||
// 读取的日志通道名
|
||||
'channel' => '',
|
||||
];
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 胜家云 [ SingKa Cloud ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2019 https://www.singka.net All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | 宁波晟嘉网络科技有限公司
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ShyComet <shycomet@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
return [
|
||||
//API秘钥之公钥 可在后台查找
|
||||
'PUBLIC_KEY' => '',
|
||||
//API秘钥之私钥 可在后台查找
|
||||
'PRIVATE_KEY' => '',
|
||||
//项目ID 登录Ucloud后台可以查找
|
||||
'PROJECT_ID' => '',
|
||||
//API通信地址
|
||||
'BASE_URL' => 'https://api.ucloud.cn',
|
||||
];
|
|
@ -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',
|
||||
]
|
||||
];
|
|
@ -0,0 +1,73 @@
|
|||
APP_DEBUG = true
|
||||
|
||||
[APP]
|
||||
DEFAULT_TIMEZONE = Asia/Shanghai
|
||||
|
||||
[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
|
||||
|
||||
# 配置对象储存,主要用于聊天文件储存,可以通过后台进行配置
|
||||
|
||||
[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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?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;
|
||||
|
||||
|
||||
/**
|
||||
* @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'];
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
<?php
|
||||
namespace easyTask;
|
||||
|
||||
use \Com as Com;
|
||||
use \Exception as Exception;
|
||||
|
||||
/**
|
||||
* Class Wpc
|
||||
* @package easyTask
|
||||
*/
|
||||
class Wpc
|
||||
{
|
||||
/**
|
||||
* Wpc实例
|
||||
* @var null
|
||||
*/
|
||||
private $instance = null;
|
||||
|
||||
/**
|
||||
* Wpc constructor.
|
||||
* @return $this
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->instance = new Com('Wpc.Core');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Com_Variant
|
||||
* @return Com
|
||||
*/
|
||||
public function getInstance()
|
||||
{
|
||||
return $this->instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进程文件
|
||||
* @param string $filename
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setFile($filename)
|
||||
{
|
||||
$filename = realpath($filename);
|
||||
if (!file_exists($filename))
|
||||
{
|
||||
throw new Exception("the file:{$filename} is not exist");
|
||||
}
|
||||
$this->instance->SetFile($filename);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进程域
|
||||
* @param string $domain
|
||||
* @return $this
|
||||
*/
|
||||
public function setDomain($domain)
|
||||
{
|
||||
$domain = (string)$domain;
|
||||
$this->instance->SetDomain($domain);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进程参数
|
||||
* @param string $argument
|
||||
* @return $this
|
||||
*/
|
||||
public function setArgument($argument)
|
||||
{
|
||||
$argument = (string)$argument;
|
||||
$this->instance->SetArgument($argument);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进程是否带窗口
|
||||
* @param bool $set
|
||||
* @return $this
|
||||
*/
|
||||
public function setNoWindow($set)
|
||||
{
|
||||
$set = (bool)$set;
|
||||
$this->instance->SetNoWindow($set);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置启动进程的用户
|
||||
* @param string $username
|
||||
* @return $this
|
||||
*/
|
||||
public function setUsername($username)
|
||||
{
|
||||
$username = (string)$username;
|
||||
$this->instance->SetUsername($username);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置启动进程的密码
|
||||
* @param string $password
|
||||
* @return $this
|
||||
*/
|
||||
public function setPassword($password)
|
||||
{
|
||||
$password = (string)$password;
|
||||
$this->instance->SetPassword($password);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进程风格
|
||||
* @param int $style (0.正常 1.隐藏 2.最小化 3.最大化)
|
||||
* @return $this
|
||||
*/
|
||||
public function setStyle($style)
|
||||
{
|
||||
$style = (int)$style;
|
||||
$this->instance->SetStyle($style);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进程工作目录
|
||||
* @param string $path
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setWorkDir($path)
|
||||
{
|
||||
$path = realpath($path);
|
||||
if (!is_dir($path))
|
||||
{
|
||||
throw new Exception("the path:{$path} is not exist");
|
||||
}
|
||||
$this->instance->SetWorkDir($path);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置等待关联进程退出
|
||||
* @param int $timeOut 超时时间
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setWaitForExit($timeOut = 1024)
|
||||
{
|
||||
$timeOut = (int)$timeOut;
|
||||
$this->instance->SetWaitForExit($timeOut);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程ID
|
||||
* @return int
|
||||
*/
|
||||
public function getPid()
|
||||
{
|
||||
return $this->instance->GetPid();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程sessionId
|
||||
* @return int
|
||||
*/
|
||||
public function getSessionId()
|
||||
{
|
||||
return $this->instance->GetSessionId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取程是否已经退出
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExited()
|
||||
{
|
||||
return $this->instance->HasExited();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程名称
|
||||
* @return string
|
||||
*/
|
||||
public function getProcessName()
|
||||
{
|
||||
return $this->instance->GetProcessName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程打开的资源句柄数
|
||||
* @return int
|
||||
*/
|
||||
public function getHandleCount()
|
||||
{
|
||||
return $this->instance->GetHandleCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程主窗口标题
|
||||
* @return string
|
||||
*/
|
||||
public function getMainWindowTitle()
|
||||
{
|
||||
return $this->instance->GetMainWindowTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程启动时间
|
||||
* @return string
|
||||
*/
|
||||
public function getStartTime()
|
||||
{
|
||||
return $this->instance->GetStartTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程停止时间
|
||||
* @return string
|
||||
*/
|
||||
public function getStopTime()
|
||||
{
|
||||
return $this->instance->GetStopTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动进程
|
||||
* @return int 进程id
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
return $this->instance->Start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止进程
|
||||
* @param int $force (1.正常停止 2.强制停止)
|
||||
*/
|
||||
public function stop($force = 1)
|
||||
{
|
||||
$this->instance->Stop($force);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
namespace easyTask;
|
||||
|
||||
use \Closure as Closure;
|
||||
|
||||
/**
|
||||
* Class Wts
|
||||
* @package easyTask
|
||||
*/
|
||||
class Wts
|
||||
{
|
||||
/**
|
||||
* 进程锁
|
||||
* @var Lock
|
||||
*/
|
||||
private $lock;
|
||||
|
||||
/**
|
||||
* 进程名称列表
|
||||
* @var array
|
||||
*/
|
||||
private $processNames = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//创建进程锁
|
||||
$this->lock = new Lock();
|
||||
|
||||
//创建进程信息文件
|
||||
$processFile = $this->getProcessInfoFile();
|
||||
if (!file_exists($processFile))
|
||||
{
|
||||
file_put_contents($processFile, '');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册进程名称
|
||||
* @param string $name
|
||||
*/
|
||||
public function joinProcess($name)
|
||||
{
|
||||
$this->processNames[] = $name;
|
||||
$file = $this->getProcessFile($name);
|
||||
if (!file_exists($file))
|
||||
{
|
||||
file_put_contents($file, $name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程文件名
|
||||
* @param string $name 进程名称
|
||||
* @return string
|
||||
*/
|
||||
public function getProcessFile($name)
|
||||
{
|
||||
$runPath = Helper::getWinPath();
|
||||
return $runPath . md5($name) . '.win';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程保存信息的文件名
|
||||
* @return string
|
||||
*/
|
||||
public function getProcessInfoFile()
|
||||
{
|
||||
$runPath = Helper::getWinPath();
|
||||
$infoFile = md5(__FILE__) . '.win';
|
||||
return $runPath . $infoFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程状态
|
||||
* @param string $name 进程名称
|
||||
* @return bool
|
||||
*/
|
||||
public function getProcessStatus($name)
|
||||
{
|
||||
$file = $this->getProcessFile($name);
|
||||
if (!file_exists($file))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$fp = fopen($file, "r");
|
||||
if (flock($fp, LOCK_EX | LOCK_NB))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程信息(非阻塞)
|
||||
* @return array
|
||||
*/
|
||||
public function getProcessInfo()
|
||||
{
|
||||
$file = $this->getProcessInfoFile();
|
||||
$info = file_get_contents($file);
|
||||
$info = json_decode($info, true);
|
||||
return is_array($info) ? $info : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理进程信息
|
||||
*/
|
||||
public function cleanProcessInfo()
|
||||
{
|
||||
//加锁执行
|
||||
$this->lock->execute(function () {
|
||||
@file_put_contents($this->getProcessInfoFile(), '');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存进程信息
|
||||
* @param array $info
|
||||
*/
|
||||
public function saveProcessInfo($info)
|
||||
{
|
||||
//加锁执行
|
||||
$this->lock->execute(function () use ($info) {
|
||||
|
||||
//进程信息文件
|
||||
$name = $info['name'];
|
||||
$file = $this->getProcessInfoFile();
|
||||
|
||||
//读取原数据
|
||||
$content = @file_get_contents($file);
|
||||
$oldInfo = $content ? json_decode($content, true) : [$name => $info];
|
||||
|
||||
//追加数据
|
||||
$oldInfo ? $oldInfo[$name] = $info : $oldInfo = $info;
|
||||
file_put_contents($file, json_encode($oldInfo));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配进程
|
||||
* @param Closure $func
|
||||
* @return bool
|
||||
*/
|
||||
public function allocateProcess($func)
|
||||
{
|
||||
$processNames = $this->processNames;
|
||||
foreach ($processNames as $name)
|
||||
{
|
||||
$file = $this->getProcessFile($name);
|
||||
$fp = fopen($file, 'w');
|
||||
if (flock($fp, LOCK_EX | LOCK_NB))
|
||||
{
|
||||
$func($name);
|
||||
flock($fp, LOCK_UN);
|
||||
return true;
|
||||
}
|
||||
fclose($fp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,292 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
|
||||
全球 IPv4 地址归属地数据库(IPIP.NET 版)
|
||||
|
||||
--- 2016 年 6 月引言 ---
|
||||
|
||||
这是 IP 库的第十五个公开版本。
|
||||
|
||||
半年不见,目前的数据条目数超过 320000 条了。
|
||||
|
||||
基于目前的客户购买情况,我们会在 7 月 1 日开始执行新价格方案。6 月份购买的不受影响,请潜在客户尽快考虑。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.ipip.net/
|
||||
|
||||
--- 2015 年 12 月引言 ---
|
||||
|
||||
这是 IP 库的第十四个公开版本。
|
||||
|
||||
目前的数据条目数超过 220000 条了。
|
||||
|
||||
我们一直在发布 DISCUZ 和 ECSHOP 的专用版本,但是不知道是用户变少还是用户不关心还是如何,这两年几乎无人咨询相关版本的事情,所以我们从 2016 年起,不再发布针对 DISCUZ 和 ECSHOP 的免费版。
|
||||
|
||||
这次的免费版也有一些小变化,不过对于不关心的用户来说,没有变化。;-)
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.ipip.net/
|
||||
|
||||
--- 2015 年 10 月引言 ---
|
||||
|
||||
这是 IP 库的第十三个公开版本。
|
||||
|
||||
目前的数据条目数超过 200000 条了。
|
||||
|
||||
这个月底,我们这个事情,就已经两岁啦。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.ipip.net/
|
||||
|
||||
--- 2015 年 8 月引言 ---
|
||||
|
||||
这是 IP 库的第十二个公开版本。
|
||||
|
||||
目前的数据条目数接近 180000 条了,我们基本上能做到两个月新增一万条左右,注意是新增,不包括修改。
|
||||
|
||||
认可我们的数据质量的用户和客户越来越多,在一个 2015 TOP100 互联网公司名单里,我们在里面找到了有将近 20 家客户了。
|
||||
|
||||
我们和合作伙伴一起合作的的区县级 IP 数据库也赶在七月底上线了,有兴趣的请联系我们。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.ipip.net/
|
||||
|
||||
--- 2015 年 6 月引言 ---
|
||||
|
||||
这是 IP 库的第十一个公开版本。
|
||||
|
||||
目前的数据条目数超过了 170000 条了。希望下一个万条可以更快的达成。
|
||||
|
||||
正在做些互联网基础设施相关的新事情,等有一定结果的时候,再来汇报吧。
|
||||
|
||||
寻求 Golang 语言开发人员,具体请看链接:http://www.lagou.com/jobs/649340.html
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.ipip.net/
|
||||
|
||||
--- 2015 年 4 月引言 ---
|
||||
|
||||
这是 IP 库的第十个公开版本。
|
||||
|
||||
三月份发布了 CDN 版本,针对节点调度需求做了一定的优化,也有了基本的英文版了。
|
||||
|
||||
目前的数据条目数超过了 160000 条了,算是个小里程碑,也有了新的专职编辑正在学习中,希望更好的维护这个数据库。
|
||||
|
||||
如果有人对基于全球 IP 分布以及连通性方面的数据挖掘以及相关的企业级服务有兴趣,请联系我,我们正在找专职的人员。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.ipip.net/
|
||||
|
||||
--- 2015 年 2 月引言 ---
|
||||
|
||||
这是 IP 库的第九个公开版本。
|
||||
|
||||
我们已经更换了新的官网:http://www.ipip.net/
|
||||
|
||||
这个月也会尽力发布 CDN 的专属版本以及英文版本。
|
||||
|
||||
基站数据库也会看时间发布正式版。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.ipip.net/
|
||||
|
||||
--- 2014 年 12 月引言 ---
|
||||
|
||||
这是 IP 库的第八个公开版本。
|
||||
|
||||
11 月份算是 IP 库进展最大的一个月了,我们对数据进行了全面的扫描,并且在微博上发了一篇长文章后,带来了不少付费客户,远超乎我的预期,也让我对这个事情有了新的想法。
|
||||
|
||||
文章地址是: http://www.evernote.com/l/AAHsqnNK9T9LkqZb9LW-fLjzkx0B4Dj90lY/ ,希望您也看看。
|
||||
|
||||
我们也针对高级付费客户开发了 DATX 格式版本,同时集成经纬度、时区信息,在读取速度上,同等环境下是 8000QPS 对 48000QPS 的区别。
|
||||
|
||||
我们同时也发布了官方支持的 JAVA 版读取代码。
|
||||
|
||||
这个月会把网站重新改版,更换域名,发布英文版网站和数据。
|
||||
|
||||
下个月就是 2015 年,明年我们在努力提高数据准确度的情况下,会提高付费客户的价格,为什么提价,我会写文章来解释的,如果您需要,请在本月购买,我们承诺老客户老价格。
|
||||
|
||||
我们也会针对 CDN、DNS、VPN 厂商的需求,专门发布对应的版本。
|
||||
|
||||
另外免费版的发布情况也可能会做调整,不作任何保证。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.17mon.cn/
|
||||
|
||||
--- 2014 年 10 月引言 ---
|
||||
|
||||
这是 IP 库的第七个公开版本。
|
||||
|
||||
最近几个月在以新的方式在对 IP 数据进行标注,可以更加保证数据的正确性,目前完成度 80%,数据量接近十四万行,预计在 10 月底可以初步完成。
|
||||
|
||||
使用官方 PHP 代码的朋友,请更新一下代码,之前犯了一个变量赋值的错误,导致缓存一直不会被命中。感谢 QQ 群里那位名字写得很乱的朋友发现的问题。做事认真严谨很重要。
|
||||
|
||||
另外预告一下,明年初,我们会对付费版的价格进行调整,价格只高不低,当然之前购买的用户不会受影响。
|
||||
|
||||
解释一下,我们毕竟在这个事情上投入了大量的精力,而且按照价格和准确度的情况来看,不说最好的,也是极好的了,再加上国内用户购买意愿低,我们只能考虑先提价的方式,毕竟对于任何一家对 IP 库有更高需求的业务,一个月付 200、300 元能够得到数据库并能保证及时更新的话,远比自己雇一个人去更新维护的成本低很多很多,何况 IP 库这件事水也很深,雇来的人的能力就能比我们做强吗?
|
||||
|
||||
如果未来可以有足够付费用户的时候,我们也会考虑调低价格的。也请大家多理解多支持吧。
|
||||
|
||||
我们也在做国家、城市经纬度和相关时区的数据,有需要的可以试试。
|
||||
|
||||
年底之前也争取把域名换掉,也省得很多人问我 17MON 代表啥意思。:-)
|
||||
|
||||
再求专职维护人员,有兴趣可以找我聊。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.17mon.cn/
|
||||
|
||||
--- 2014 年 8 月引言 ---
|
||||
|
||||
这是 IP 库的第六个公开版本。
|
||||
|
||||
这个月参加了 ThinkInLAMP 2014 PHP 技术峰会,做了一次有关 IP 数据库的演讲,也算是对将近一年多时间的投入的一个总结。大家有兴趣的,可以看看我的 ppt。
|
||||
|
||||
实在是精力有限,为承诺计,从这个月开始,将每两个月发布免费版了。如果您需要更好的服务,请购买付费版。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.17mon.cn/
|
||||
|
||||
--- 2014 年 7 月引言 ---
|
||||
|
||||
这是 IP 库的第五个公开版本。
|
||||
|
||||
这个月公司和家里事情比较多,更新的慢了一点,还请大家谅解。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.17mon.cn/
|
||||
|
||||
--- 2014 年 6 月引言 ---
|
||||
|
||||
这是 IP 库的第四个公开版本。
|
||||
|
||||
祝大家六一、端午快乐,这次我们增加了一个字段,原来是把城市和单位放在了一起,这样比如在有学校名称的时候,就没法知道所在具体城市了。
|
||||
|
||||
这种情况被客户们投诉了。这次趁着三天假的机会,下决心将其分开,这回大家都满意了吧?:-)
|
||||
|
||||
因为增加了字段,所以对于字段有明确要求的,请仔细核查数据和相关代码,如果有问题,请到 QQ 群: 346280296 中反馈。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.17mon.cn/
|
||||
|
||||
--- 2014 年 5 月引言 ---
|
||||
|
||||
这是 IP 库的第三个公开版本。
|
||||
|
||||
祝大家五一快乐,假期之后的一周后,付费计划将上线。另外 WINDOWS 版的客户端也会同期上线。
|
||||
|
||||
已经有公司与我联系了购买了付费服务,我很欣慰,呵呵。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.17mon.cn/
|
||||
|
||||
--- 2014 年 4 月引言 ---
|
||||
|
||||
这是 IP 库的第二个公开版本。
|
||||
|
||||
这个版本放开了国内市一级的数据,和网站版的数据相比,只有运营商的数据和国外到市一级的数据没有放开,我想对于一般的应用来说足够用了。
|
||||
|
||||
这个月,更大的收获是,校准 IP 库的方法,进一步的成型,可以更少的工作、更高的正确率,对于做付费计划,我更有信心了。
|
||||
|
||||
付费计划应该在本月底之前上线。希望对于数据的数据和更新频度以及支持有要求的,请一定给予支持。不然这个事情没有办法长期执行下去。
|
||||
|
||||
如果您的软件或者应用里需要内置 IP 库,可以找我来谈更紧密的合作和更新方式。
|
||||
|
||||
如果您有任何问题,请到 QQ 群来说。
|
||||
|
||||
不多说,睡觉去了。。。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.17mon.cn/
|
||||
|
||||
--- 2014 年 3 月引言 ---
|
||||
|
||||
经过半年多的不断努力,终于可以发布一个公开的版本了。
|
||||
|
||||
IP 地址是互联网的基础部分,那么 IP 归属地数据库同样也很重要。
|
||||
|
||||
之前在 ECSHOP 时期就研究过,只是最终没有更深入。
|
||||
|
||||
终于半公半私都有关系的原因,下定决心做了,我一个人为主,也有同事很大的帮助。
|
||||
|
||||
这半年多时间以来,知识见涨,尤其是全球地理方面的知识。:-)
|
||||
|
||||
这次为了发布,也专门改进了库的生成方式,更准确(之前的生成代码有个小问题,导致 ECSHOP、DISCUZ 的也有同样问题,只是轻易碰不上,这次一并都改进了),库的体积也更小。
|
||||
|
||||
言归正传,有空再闲扯。
|
||||
|
||||
想使用很简单,把 17monipdb.dat 和 IP.class.php 放在同一个目录,使用 IP::find('x.x.x.x') 调用即可。
|
||||
工程师们请注意:目前为了向后兼容增加更多项目,并且方便进行二次处理,所以两个文本使用制表符分割;目前使用 UTF-8 字符集,其它字符集请自行转义;欢迎大家补充其它语言的版本。
|
||||
|
||||
几点说明:
|
||||
|
||||
1、维护这个数据库是个长期工作,甚至是一个无期工作,所以有打算做付费维护的考虑,请各位工程师们理解其辛苦程度。不是为赚钱,而是为了更好的维护数据库,让数据更准确,更有价值。
|
||||
|
||||
2、目前公开版仅发布国内具体到省,国外具体到国家的格式数据。一般来说够用了。基于准确度优先的问题,我们暂时没有做除了直辖市或者可以明确到市的数据,即使有,目前也比较少。这个还请见谅,您可以保持关注。数据库的更新周期暂定一个月左右。17mon.cn 所对外的数据是完全版也是最新版,有需要可以使用 http://ip.17mon.cn/ 进行查询。
|
||||
|
||||
3、有些 IP 段不会标注国家,是因为要么是路由器 IP 段,要么是做了 ANYCAST 技术,无法具体定位,一般来说,针对普通用户进行定位,不会碰到此类 IP,只有面对 CDN、DNS、服务器、路由器等等所在的 IP,才有可能碰到。
|
||||
|
||||
4、在整理过程中,感谢 DNSPOD 的建议和数据支持,部分数据参考了纯真 IP 库、淘宝 IP 库、腾讯 IP 库、新浪 IP 库、中国互联网广告行业 IP 库的数据,还包括 BGP.HE.NET 以及全球各大地区的 IP 管理机构的 WHOIS 信息数据,感谢给我帮助和支持的大家还有我的同事,还有很多基础性的文章和资料,包括中国地图出版社的美国地图和欧洲地图。
|
||||
|
||||
5、为了便于查询您使用的 IP 库版本,将 255.255.255.0 - 255.255.255.255 作为版本数据输出,您需要了解版本的话,请使用 IP::find('255.255.255.255') 查询即可。
|
||||
|
||||
6、我们为了自己,也为了他人方便,集成了一个大全版,有兴趣者可访问 http://ip.17mon.cn/ 。
|
||||
|
||||
7、数据量超大,更不要提未来的 IPv6 了,尤其我们为了准确,尽量使用实证方式维护数据,错误难免,请加 QQ 群: 346280296 进行讨论。
|
||||
|
||||
8、如果您所在公司有更准确的 IP 数据库需求,可以与我联系,希望可以发挥各自长处合作共建,而不是各自单打独斗。
|
||||
|
||||
说完了。
|
||||
|
||||
--
|
||||
高春辉
|
||||
Paul Gao
|
||||
gaochunhui@gmail.com
|
||||
http://www.17mon.cn/
|
||||
|
||||
*/
|
||||
|
||||
?>
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
/**
|
||||
* Created by PhpStorm
|
||||
* User Julyssn
|
||||
* Date 2021/8/3 13:50
|
||||
*/
|
||||
|
||||
|
||||
namespace mail;
|
||||
|
||||
|
||||
use Swift_Mailer;
|
||||
use Swift_Message;
|
||||
use Swift_SmtpTransport;
|
||||
use think\facade\View;
|
||||
|
||||
class Mail
|
||||
{
|
||||
public $Config = [
|
||||
'driver' => 'smtp', // 邮件驱动, 支持 smtp|sendmail|mail 三种驱动
|
||||
'host' => 'smtp.qq.com', // SMTP服务器地址
|
||||
'port' => 465, // SMTP服务器端口号,一般为25
|
||||
'addr' => '', // 发件邮箱地址
|
||||
'pass' => '', // 发件邮箱密码
|
||||
'sign' => '', // 发件邮箱名称
|
||||
'content_type' => 'text/html', // 默认文本内容 text/html|text/plain
|
||||
'charset' => 'utf-8', // 默认字符集
|
||||
'security' => 'ssl', // 加密方式 null|ssl|tls, QQ邮箱必须使用ssl
|
||||
'temp' => '', //邮件模板
|
||||
'logo' => '', //邮件logo
|
||||
];
|
||||
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->Config = array_merge($this->Config, $config);
|
||||
//默认模板
|
||||
$this->Config['temp'] = $this->Config['temp'] ?: 'temp';
|
||||
$this->Config['logo'] = $this->Config['logo'] ?: 'https://im.file.raingad.com/logo/logo.png';
|
||||
}
|
||||
|
||||
public function sendEmail(array $toEmails, $title, $content)
|
||||
{
|
||||
|
||||
// 创建Transport对象,设置邮件服务器和端口号,并设置用户名和密码以供验证
|
||||
$transport = (new Swift_SmtpTransport($this->Config['host'], $this->Config['port'], $this->Config['security']))
|
||||
->setUsername($this->Config['addr'])
|
||||
->setPassword($this->Config['pass']);
|
||||
|
||||
//创建mailer对象
|
||||
$mailer = new Swift_Mailer($transport);
|
||||
|
||||
//创建message对象
|
||||
$message = (new Swift_Message($title));//设置邮件主题
|
||||
|
||||
//用关联数组设置发件人地址,可以设置多个发件人
|
||||
$message->setFrom([$this->Config['addr'] => $this->Config['sign']]);
|
||||
|
||||
//用关联数组设置收件人地址,可以设置多个收件人
|
||||
$message->setTo($toEmails);
|
||||
|
||||
//设置邮件内容
|
||||
$data = [
|
||||
'logo' => $this->Config['logo'],
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'time' => date('Y-m-d H:i:s'),
|
||||
'name' => $this->Config['sign']
|
||||
];
|
||||
$html = View::fetch(dirname(__FILE__) . '/' . $this->Config['temp'] . '.html', ['data' => $data]);
|
||||
$message->setBody($html, 'text/html');
|
||||
|
||||
// //创建attachment对象,content-type这个参数可以省略
|
||||
// $attachment = Swift_Attachment::fromPath('image.jpg', 'image/jpeg')->setFilename('cool.jpg');
|
||||
// //添加附件
|
||||
// $message->attach($attachment);
|
||||
|
||||
|
||||
// //添加抄送人
|
||||
// $message->setCc(array(
|
||||
// 'Cc@qq.com' => 'Cc'
|
||||
// ));
|
||||
|
||||
// //添加密送人
|
||||
// $message->setBcc(array(
|
||||
// 'Bcc@qq.com' => 'Bcc'
|
||||
// ));
|
||||
|
||||
// //设置邮件回执
|
||||
// $message->setReadReceiptTo('receipt@163.com');
|
||||
|
||||
//发送邮件
|
||||
return $mailer->send($message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN">
|
||||
<head>
|
||||
<base target="_blank" />
|
||||
<style type="text/css">::-webkit-scrollbar{ display: none; }</style>
|
||||
<style id="cloudAttachStyle" type="text/css">#divNeteaseBigAttach, #divNeteaseBigAttach_bak{display:none;}</style>
|
||||
<style id="blockquoteStyle" type="text/css">blockquote{display:none;}</style>
|
||||
<style type="text/css">
|
||||
body{font-size:14px;font-family:arial,verdana,sans-serif;line-height:1.666;padding:0;margin:0;overflow:auto;white-space:normal;word-wrap:break-word;min-height:100px}
|
||||
td, input, button, select, body{font-family:Helvetica, \'Microsoft Yahei\', verdana}
|
||||
pre {white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;width:95%}
|
||||
th,td{font-family:arial,verdana,sans-serif;line-height:1.666}
|
||||
img{ border:0}
|
||||
header,footer,section,aside,article,nav,hgroup,figure,figcaption{display:block}
|
||||
blockquote{margin-right:0px}
|
||||
</style>
|
||||
</head>
|
||||
<body tabindex="0" role="listitem">
|
||||
<table width="700" border="0" align="center" cellspacing="0" style="width:700px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="width:700px;margin:0 auto;border-bottom:1px solid #ccc;margin-bottom:30px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="700" height="39" style="font:12px Tahoma, Arial, 宋体;">
|
||||
<tbody><tr><td width="210"></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="width:680px;padding:0 10px;margin:0 auto;">
|
||||
<div style="line-height:1.5;font-size:14px;margin-bottom:25px;color:#4d4d4d;">
|
||||
<strong style="display:block;margin-bottom:15px;">尊敬的用户:<span style="color:#f60;font-size: 16px;"></span>您好!</strong>
|
||||
<strong style="display:block;margin-bottom:15px;">
|
||||
您正在进行<span style="color: red">{$data.title}</span>操作,请在验证码输入框中输入:<span style="color:#f60;font-size: 24px">{$data.content}</span>,以完成操作。
|
||||
</strong>
|
||||
</div>
|
||||
<div style="margin-bottom:30px;">
|
||||
<small style="display:block;margin-bottom:20px;font-size:12px;">
|
||||
<p style="color:#747474;">
|
||||
注意:此操作可能会修改您的密码、登录邮箱或绑定手机。如非本人操作,请及时登录并修改密码以保证帐户安全
|
||||
<br>(工作人员不会向你索取此验证码,请勿泄漏!)
|
||||
</p>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width:700px;margin:0 auto;">
|
||||
<div style="padding:10px 10px 0;border-top:1px solid #ccc;color:#747474;margin-bottom:20px;line-height:1.3em;font-size:12px;">
|
||||
<p>此为系统邮件,请勿回复<br>
|
||||
请保管好您的邮箱,避免账号被他人盗用
|
||||
</p>
|
||||
<p>{$data.sign ?? ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN">
|
||||
<head>
|
||||
<base target="_blank" />
|
||||
<style type="text/css">::-webkit-scrollbar{ display: none; }</style>
|
||||
<style id="cloudAttachStyle" type="text/css">#divNeteaseBigAttach, #divNeteaseBigAttach_bak{display:none;}</style>
|
||||
<style id="blockquoteStyle" type="text/css">blockquote{display:none;}</style>
|
||||
<style type="text/css">
|
||||
body{font-size:14px;font-family:arial,verdana,sans-serif;line-height:1.666;padding:0;margin:0;overflow:auto;white-space:normal;word-wrap:break-word;min-height:100px}
|
||||
td, input, button, select, body{font-family:Helvetica, \'Microsoft Yahei\', verdana}
|
||||
pre {white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;width:95%}
|
||||
th,td{font-family:arial,verdana,sans-serif;line-height:1.666}
|
||||
img{ border:0}
|
||||
header,footer,section,aside,article,nav,hgroup,figure,figcaption{display:block}
|
||||
blockquote{margin-right:0px}
|
||||
</style>
|
||||
</head>
|
||||
<body tabindex="0" role="listitem">
|
||||
<table width="700" border="0" align="center" cellspacing="0" style="width:700px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="width:700px;margin:0 auto;border-bottom:1px solid #ccc;margin-bottom:30px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="700" height="39" style="font:12px Tahoma, Arial, 宋体;">
|
||||
<tbody><tr><td width="210"></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="width:680px;padding:0 10px;margin:0 auto;">
|
||||
<div style="line-height:1.5;font-size:14px;margin-bottom:25px;color:#4d4d4d;">
|
||||
<strong style="display:block;margin-bottom:15px;">尊敬的用户:您好!</strong>
|
||||
<strong style="display:block;margin-bottom:15px;">
|
||||
{$data.content}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width:700px;margin:0 auto;">
|
||||
<div style="padding:10px 10px 0;border-top:1px solid #ccc;color:#747474;margin-bottom:20px;line-height:1.3em;font-size:12px;">
|
||||
<p>此为系统邮件,请勿回复<br>
|
||||
请保管好您的邮箱,避免账号被他人盗用
|
||||
</p>
|
||||
<p>Raingad-IM</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
/**
|
||||
* Created by PhpStorm
|
||||
* User Julyssn
|
||||
* Date 2022/12/14 16:12
|
||||
*/
|
||||
|
||||
|
||||
namespace task\command;
|
||||
|
||||
|
||||
use easyTask\Helper;
|
||||
use easyTask\Task as EasyTask;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Argument;
|
||||
use think\console\Output;
|
||||
use think\helper\Str;
|
||||
|
||||
class Task extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
// 指令配置
|
||||
$this->setName('task')
|
||||
->addArgument('action', Argument::OPTIONAL, "action", '')
|
||||
->addArgument('force', Argument::OPTIONAL, "force", '')
|
||||
->setDescription('the task command');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
//获取输入参数
|
||||
$action = trim($input->getArgument('action'));
|
||||
$force = trim($input->getArgument('force'));
|
||||
|
||||
$rootPath = root_path();
|
||||
|
||||
// 配置任务,每隔20秒访问2次网站
|
||||
$task = new EasyTask();
|
||||
|
||||
// 设置常驻内存
|
||||
$task->setDaemon(!Helper::isWin());
|
||||
|
||||
// 设置项目名称 获取运行目录文件夹名称
|
||||
$task->setPrefix('easy_task');
|
||||
|
||||
// 设置子进程挂掉自动重启
|
||||
$task->setAutoRecover(true);
|
||||
|
||||
// 设置运行时目录(日志或缓存目录)
|
||||
$task->setRunTimePath($rootPath . 'runtime');
|
||||
// 消息推送
|
||||
$task->addCommand('php think worker:gateway start', 'worker', 0);
|
||||
// 定时任务
|
||||
$task->addCommand('php think cron:schedule', 'schedule', 0);
|
||||
// 律者队列
|
||||
$task->addCommand('php think queue:listen --sleep 0.3 --queue lvzhe', 'queue', 0);
|
||||
|
||||
|
||||
// 根据命令执行
|
||||
if ($action == 'start') {
|
||||
$task->start();
|
||||
} elseif ($action == 'status') {
|
||||
$task->status();
|
||||
} elseif ($action == 'stop') {
|
||||
$force = ($force == 'force'); //是否强制停止
|
||||
$task->stop($force);
|
||||
} else {
|
||||
exit('Command is not exist');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue