代码初始化
This commit is contained in:
87
application/common/behavior/Common.php
Normal file
87
application/common/behavior/Common.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\behavior;
|
||||
|
||||
use think\Config;
|
||||
use think\Lang;
|
||||
use think\Loader;
|
||||
|
||||
class Common
|
||||
{
|
||||
|
||||
public function appInit()
|
||||
{
|
||||
$allowLangList = Config::get('allow_lang_list') ?? ['zh-cn', 'en'];
|
||||
Lang::setAllowLangList($allowLangList);
|
||||
}
|
||||
|
||||
public function appDispatch(&$dispatch)
|
||||
{
|
||||
$pathinfoArr = explode('/', request()->pathinfo());
|
||||
if (!Config::get('url_domain_deploy') && $pathinfoArr && in_array($pathinfoArr[0], ['index', 'api'])) {
|
||||
//如果是以index或api开始的URL则关闭路由检测
|
||||
\think\App::route(false);
|
||||
}
|
||||
}
|
||||
|
||||
public function moduleInit(&$request)
|
||||
{
|
||||
// 设置mbstring字符编码
|
||||
mb_internal_encoding("UTF-8");
|
||||
|
||||
// 如果修改了index.php入口地址,则需要手动修改cdnurl的值
|
||||
$url = preg_replace("/\/(\w+)\.php$/i", '', $request->root());
|
||||
// 如果未设置__CDN__则自动匹配得出
|
||||
if (!Config::get('view_replace_str.__CDN__')) {
|
||||
Config::set('view_replace_str.__CDN__', $url);
|
||||
}
|
||||
// 如果未设置__PUBLIC__则自动匹配得出
|
||||
if (!Config::get('view_replace_str.__PUBLIC__')) {
|
||||
Config::set('view_replace_str.__PUBLIC__', $url . '/');
|
||||
}
|
||||
// 如果未设置__ROOT__则自动匹配得出
|
||||
if (!Config::get('view_replace_str.__ROOT__')) {
|
||||
Config::set('view_replace_str.__ROOT__', preg_replace("/\/public\/$/", '', $url . '/'));
|
||||
}
|
||||
// 如果未设置cdnurl则自动匹配得出
|
||||
if (!Config::get('site.cdnurl')) {
|
||||
Config::set('site.cdnurl', $url);
|
||||
}
|
||||
// 如果未设置cdnurl则自动匹配得出
|
||||
if (!Config::get('upload.cdnurl')) {
|
||||
Config::set('upload.cdnurl', $url);
|
||||
}
|
||||
if (Config::get('app_debug')) {
|
||||
// 如果是调试模式将version置为当前的时间戳可避免缓存
|
||||
Config::set('site.version', time());
|
||||
// 如果是开发模式那么将异常模板修改成官方的
|
||||
Config::set('exception_tmpl', THINK_PATH . 'tpl' . DS . 'think_exception.tpl');
|
||||
}
|
||||
// 如果是trace模式且Ajax的情况下关闭trace
|
||||
if (Config::get('app_trace') && $request->isAjax()) {
|
||||
Config::set('app_trace', false);
|
||||
}
|
||||
// 切换多语言
|
||||
if (Config::get('lang_switch_on')) {
|
||||
$lang = $request->get('lang', '');
|
||||
if (preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang)) {
|
||||
\think\Cookie::set('think_var', $lang);
|
||||
}
|
||||
}
|
||||
// Form别名
|
||||
if (!class_exists('Form')) {
|
||||
class_alias('fast\\Form', 'Form');
|
||||
}
|
||||
}
|
||||
|
||||
public function addonBegin(&$request)
|
||||
{
|
||||
// 加载插件语言包
|
||||
$lang = request()->langset();
|
||||
$lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn';
|
||||
Lang::load([
|
||||
APP_PATH . 'common' . DS . 'lang' . DS . $lang . DS . 'addon' . EXT,
|
||||
]);
|
||||
$this->moduleInit($request);
|
||||
}
|
||||
}
|
||||
330
application/common/controller/Api.php
Normal file
330
application/common/controller/Api.php
Normal file
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\library\Auth;
|
||||
use think\Config;
|
||||
use think\exception\HttpResponseException;
|
||||
use think\exception\ValidateException;
|
||||
use think\Hook;
|
||||
use think\Lang;
|
||||
use think\Loader;
|
||||
use think\Request;
|
||||
use think\Response;
|
||||
use think\Route;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* API控制器基类
|
||||
*/
|
||||
class Api
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Request Request 实例
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var bool 验证失败是否抛出异常
|
||||
*/
|
||||
protected $failException = false;
|
||||
|
||||
/**
|
||||
* @var bool 是否批量验证
|
||||
*/
|
||||
protected $batchValidate = false;
|
||||
|
||||
/**
|
||||
* @var array 前置操作方法列表
|
||||
*/
|
||||
protected $beforeActionList = [];
|
||||
|
||||
/**
|
||||
* 无需登录的方法,同时也就不需要鉴权了
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = [];
|
||||
|
||||
/**
|
||||
* 无需鉴权的方法,但需要登录
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedRight = [];
|
||||
|
||||
/**
|
||||
* 权限Auth
|
||||
* @var Auth
|
||||
*/
|
||||
protected $auth = null;
|
||||
|
||||
/**
|
||||
* 默认响应输出类型,支持json/xml
|
||||
* @var string
|
||||
*/
|
||||
protected $responseType = 'json';
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
* @access public
|
||||
* @param Request $request Request 对象
|
||||
*/
|
||||
public function __construct(Request $request = null)
|
||||
{
|
||||
$this->request = is_null($request) ? Request::instance() : $request;
|
||||
|
||||
// 控制器初始化
|
||||
$this->_initialize();
|
||||
|
||||
// 前置操作方法
|
||||
if ($this->beforeActionList) {
|
||||
foreach ($this->beforeActionList as $method => $options) {
|
||||
is_numeric($method) ?
|
||||
$this->beforeAction($options) :
|
||||
$this->beforeAction($method, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化操作
|
||||
* @access protected
|
||||
*/
|
||||
protected function _initialize()
|
||||
{
|
||||
//跨域请求检测
|
||||
check_cors_request();
|
||||
|
||||
// 检测IP是否允许
|
||||
check_ip_allowed();
|
||||
|
||||
//移除HTML标签
|
||||
$this->request->filter('trim,strip_tags,htmlspecialchars');
|
||||
|
||||
$this->auth = Auth::instance();
|
||||
|
||||
$modulename = $this->request->module();
|
||||
$controllername = Loader::parseName($this->request->controller());
|
||||
$actionname = strtolower($this->request->action());
|
||||
|
||||
// token
|
||||
$token = $this->request->server('HTTP_TOKEN', $this->request->request('token', \think\Cookie::get('token')));
|
||||
|
||||
$path = str_replace('.', '/', $controllername) . '/' . $actionname;
|
||||
// 设置当前请求的URI
|
||||
$this->auth->setRequestUri($path);
|
||||
// 检测是否需要验证登录
|
||||
if (!$this->auth->match($this->noNeedLogin)) {
|
||||
//初始化
|
||||
$this->auth->init($token);
|
||||
//检测是否登录
|
||||
if (!$this->auth->isLogin()) {
|
||||
$this->error(__('Please login first'), null, 401);
|
||||
}
|
||||
// 判断是否需要验证权限
|
||||
if (!$this->auth->match($this->noNeedRight)) {
|
||||
// 判断控制器和方法判断是否有对应权限
|
||||
if (!$this->auth->check($path)) {
|
||||
$this->error(__('You have no permission'), null, 403);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果有传递token才验证是否登录状态
|
||||
if ($token) {
|
||||
$this->auth->init($token);
|
||||
}
|
||||
}
|
||||
|
||||
$upload = \app\common\model\Config::upload();
|
||||
|
||||
// 上传信息配置后
|
||||
Hook::listen("upload_config_init", $upload);
|
||||
|
||||
Config::set('upload', array_merge(Config::get('upload'), $upload));
|
||||
|
||||
// 加载当前控制器语言包
|
||||
$this->loadlang($controllername);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载语言文件
|
||||
* @param string $name
|
||||
*/
|
||||
protected function loadlang($name)
|
||||
{
|
||||
$name = Loader::parseName($name);
|
||||
$name = preg_match("/^([a-zA-Z0-9_\.\/]+)\$/i", $name) ? $name : 'index';
|
||||
$lang = $this->request->langset();
|
||||
$lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn';
|
||||
Lang::load(APP_PATH . $this->request->module() . '/lang/' . $lang . '/' . str_replace('.', '/', $name) . '.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功返回的数据
|
||||
* @param string $msg 提示信息
|
||||
* @param mixed $data 要返回的数据
|
||||
* @param int $code 错误码,默认为1
|
||||
* @param string $type 输出类型
|
||||
* @param array $header 发送的 Header 信息
|
||||
*/
|
||||
protected function success($msg = '', $data = null, $code = 1, $type = null, array $header = [])
|
||||
{
|
||||
$this->result($msg, $data, $code, $type, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败返回的数据
|
||||
* @param string $msg 提示信息
|
||||
* @param mixed $data 要返回的数据
|
||||
* @param int $code 错误码,默认为0
|
||||
* @param string $type 输出类型
|
||||
* @param array $header 发送的 Header 信息
|
||||
*/
|
||||
protected function error($msg = '', $data = null, $code = 0, $type = null, array $header = [])
|
||||
{
|
||||
$this->result($msg, $data, $code, $type, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回封装后的 API 数据到客户端
|
||||
* @access protected
|
||||
* @param mixed $msg 提示信息
|
||||
* @param mixed $data 要返回的数据
|
||||
* @param int $code 错误码,默认为0
|
||||
* @param string $type 输出类型,支持json/xml/jsonp
|
||||
* @param array $header 发送的 Header 信息
|
||||
* @return void
|
||||
* @throws HttpResponseException
|
||||
*/
|
||||
protected function result($msg, $data = null, $code = 0, $type = null, array $header = [])
|
||||
{
|
||||
$result = [
|
||||
'code' => $code,
|
||||
'msg' => $msg,
|
||||
'time' => Request::instance()->server('REQUEST_TIME'),
|
||||
'data' => $data,
|
||||
];
|
||||
// 如果未设置类型则使用默认类型判断
|
||||
$type = $type ? : $this->responseType;
|
||||
|
||||
if (isset($header['statuscode'])) {
|
||||
$code = $header['statuscode'];
|
||||
unset($header['statuscode']);
|
||||
} else {
|
||||
//未设置状态码,根据code值判断
|
||||
$code = $code >= 1000 || $code < 200 ? 200 : $code;
|
||||
}
|
||||
$response = Response::create($result, $type, $code)->header($header);
|
||||
throw new HttpResponseException($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 前置操作
|
||||
* @access protected
|
||||
* @param string $method 前置操作方法名
|
||||
* @param array $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]]
|
||||
* @return void
|
||||
*/
|
||||
protected function beforeAction($method, $options = [])
|
||||
{
|
||||
if (isset($options['only'])) {
|
||||
if (is_string($options['only'])) {
|
||||
$options['only'] = explode(',', $options['only']);
|
||||
}
|
||||
|
||||
if (!in_array($this->request->action(), $options['only'])) {
|
||||
return;
|
||||
}
|
||||
} elseif (isset($options['except'])) {
|
||||
if (is_string($options['except'])) {
|
||||
$options['except'] = explode(',', $options['except']);
|
||||
}
|
||||
|
||||
if (in_array($this->request->action(), $options['except'])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
call_user_func([$this, $method]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证失败后是否抛出异常
|
||||
* @access protected
|
||||
* @param bool $fail 是否抛出异常
|
||||
* @return $this
|
||||
*/
|
||||
protected function validateFailException($fail = true)
|
||||
{
|
||||
$this->failException = $fail;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
* @access protected
|
||||
* @param array $data 数据
|
||||
* @param string|array $validate 验证器名或者验证规则数组
|
||||
* @param array $message 提示信息
|
||||
* @param bool $batch 是否批量验证
|
||||
* @param mixed $callback 回调方法(闭包)
|
||||
* @return array|string|true
|
||||
* @throws ValidateException
|
||||
*/
|
||||
protected function validate($data, $validate, $message = [], $batch = false, $callback = null)
|
||||
{
|
||||
if (is_array($validate)) {
|
||||
$v = Loader::validate();
|
||||
$v->rule($validate);
|
||||
} else {
|
||||
// 支持场景
|
||||
if (strpos($validate, '.')) {
|
||||
list($validate, $scene) = explode('.', $validate);
|
||||
}
|
||||
|
||||
$v = Loader::validate($validate);
|
||||
|
||||
!empty($scene) && $v->scene($scene);
|
||||
}
|
||||
|
||||
// 批量验证
|
||||
if ($batch || $this->batchValidate) {
|
||||
$v->batch(true);
|
||||
}
|
||||
// 设置错误信息
|
||||
if (is_array($message)) {
|
||||
$v->message($message);
|
||||
}
|
||||
// 使用回调验证
|
||||
if ($callback && is_callable($callback)) {
|
||||
call_user_func_array($callback, [$v, &$data]);
|
||||
}
|
||||
|
||||
if (!$v->check($data)) {
|
||||
if ($this->failException) {
|
||||
throw new ValidateException($v->getError());
|
||||
}
|
||||
|
||||
return $v->getError();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
protected function token()
|
||||
{
|
||||
$token = $this->request->param('__token__');
|
||||
|
||||
//验证Token
|
||||
if (!Validate::make()->check(['__token__' => $token], ['__token__' => 'require|token'])) {
|
||||
$this->error(__('Token verification error'), ['__token__' => $this->request->token()]);
|
||||
}
|
||||
|
||||
//刷新Token
|
||||
$this->request->token();
|
||||
}
|
||||
}
|
||||
614
application/common/controller/Backend.php
Normal file
614
application/common/controller/Backend.php
Normal file
@@ -0,0 +1,614 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\admin\library\Auth;
|
||||
use think\Config;
|
||||
use think\Controller;
|
||||
use think\Hook;
|
||||
use think\Lang;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
use think\Session;
|
||||
use fast\Tree;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 后台控制器基类
|
||||
*/
|
||||
class Backend extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* 无需登录的方法,同时也就不需要鉴权了
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = [];
|
||||
|
||||
/**
|
||||
* 无需鉴权的方法,但需要登录
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedRight = [];
|
||||
|
||||
/**
|
||||
* 布局模板
|
||||
* @var string
|
||||
*/
|
||||
protected $layout = 'default';
|
||||
|
||||
/**
|
||||
* 权限控制类
|
||||
* @var Auth
|
||||
*/
|
||||
protected $auth = null;
|
||||
|
||||
/**
|
||||
* 模型对象
|
||||
* @var \think\Model
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* 快速搜索时执行查找的字段
|
||||
*/
|
||||
protected $searchFields = 'id';
|
||||
|
||||
/**
|
||||
* 是否是关联查询
|
||||
*/
|
||||
protected $relationSearch = false;
|
||||
|
||||
/**
|
||||
* 是否开启数据限制
|
||||
* 支持auth/personal
|
||||
* 表示按权限判断/仅限个人
|
||||
* 默认为禁用,若启用请务必保证表中存在admin_id字段
|
||||
*/
|
||||
protected $dataLimit = false;
|
||||
|
||||
/**
|
||||
* 数据限制字段
|
||||
*/
|
||||
protected $dataLimitField = 'admin_id';
|
||||
|
||||
/**
|
||||
* 数据限制开启时自动填充限制字段值
|
||||
*/
|
||||
protected $dataLimitFieldAutoFill = true;
|
||||
|
||||
/**
|
||||
* 是否开启Validate验证
|
||||
*/
|
||||
protected $modelValidate = false;
|
||||
|
||||
/**
|
||||
* 是否开启模型场景验证
|
||||
*/
|
||||
protected $modelSceneValidate = false;
|
||||
|
||||
/**
|
||||
* Multi方法可批量修改的字段
|
||||
*/
|
||||
protected $multiFields = 'status';
|
||||
|
||||
/**
|
||||
* Selectpage可显示的字段
|
||||
*/
|
||||
protected $selectpageFields = '*';
|
||||
|
||||
/**
|
||||
* 前台提交过来,需要排除的字段数据
|
||||
*/
|
||||
protected $excludeFields = "";
|
||||
|
||||
/**
|
||||
* 导入文件首行类型
|
||||
* 支持comment/name
|
||||
* 表示注释或字段名
|
||||
*/
|
||||
protected $importHeadType = 'comment';
|
||||
|
||||
/**
|
||||
* 引入后台控制器的traits
|
||||
*/
|
||||
// use \app\admin\library\traits\Backend;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
$modulename = $this->request->module();
|
||||
$controllername = Loader::parseName($this->request->controller());
|
||||
$actionname = strtolower($this->request->action());
|
||||
|
||||
$path = str_replace('.', '/', $controllername) . '/' . $actionname;
|
||||
|
||||
// 定义是否Addtabs请求
|
||||
!defined('IS_ADDTABS') && define('IS_ADDTABS', (bool)input("addtabs"));
|
||||
|
||||
// 定义是否Dialog请求
|
||||
!defined('IS_DIALOG') && define('IS_DIALOG', (bool)input("dialog"));
|
||||
|
||||
// 定义是否AJAX请求
|
||||
!defined('IS_AJAX') && define('IS_AJAX', $this->request->isAjax());
|
||||
|
||||
// 检测IP是否允许
|
||||
check_ip_allowed();
|
||||
|
||||
$this->auth = Auth::instance();
|
||||
|
||||
// 设置当前请求的URI
|
||||
$this->auth->setRequestUri($path);
|
||||
// 检测是否需要验证登录
|
||||
if (!$this->auth->match($this->noNeedLogin)) {
|
||||
//检测是否登录
|
||||
if (!$this->auth->isLogin()) {
|
||||
Hook::listen('admin_nologin', $this);
|
||||
$url = Session::get('referer');
|
||||
$url = $url ? $url : $this->request->url();
|
||||
if (in_array($this->request->pathinfo(), ['/', 'index/index'])) {
|
||||
$this->redirect('index/login', [], 302, ['referer' => $url]);
|
||||
exit;
|
||||
}
|
||||
$this->error(__('Please login first'), url('index/login', ['url' => $url]));
|
||||
}
|
||||
// 判断是否需要验证权限
|
||||
if (!$this->auth->match($this->noNeedRight)) {
|
||||
// 判断控制器和方法是否有对应权限
|
||||
if (!$this->auth->check($path)) {
|
||||
Hook::listen('admin_nopermission', $this);
|
||||
$this->error(__('You have no permission'), '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 非选项卡时重定向
|
||||
if (!$this->request->isPost() && !IS_AJAX && !IS_ADDTABS && !IS_DIALOG && input("ref") == 'addtabs') {
|
||||
$url = preg_replace_callback("/([\?|&]+)ref=addtabs(&?)/i", function ($matches) {
|
||||
return $matches[2] == '&' ? $matches[1] : '';
|
||||
}, $this->request->url());
|
||||
if (Config::get('url_domain_deploy')) {
|
||||
if (stripos($url, $this->request->server('SCRIPT_NAME')) === 0) {
|
||||
$url = substr($url, strlen($this->request->server('SCRIPT_NAME')));
|
||||
}
|
||||
$url = url($url, '', false);
|
||||
}
|
||||
$this->redirect('index/index', [], 302, ['referer' => $url]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 设置面包屑导航数据
|
||||
$breadcrumb = [];
|
||||
if (!IS_DIALOG && !config('fastadmin.multiplenav') && config('fastadmin.breadcrumb')) {
|
||||
$breadcrumb = $this->auth->getBreadCrumb($path);
|
||||
array_pop($breadcrumb);
|
||||
}
|
||||
$this->view->breadcrumb = $breadcrumb;
|
||||
|
||||
// 如果有使用模板布局
|
||||
if ($this->layout) {
|
||||
$this->view->engine->layout('layout/' . $this->layout);
|
||||
}
|
||||
|
||||
// 语言检测
|
||||
$lang = $this->request->langset();
|
||||
$lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn';
|
||||
|
||||
$site = Config::get("site");
|
||||
|
||||
$upload = \app\common\model\Config::upload();
|
||||
|
||||
// 上传信息配置后
|
||||
Hook::listen("upload_config_init", $upload);
|
||||
|
||||
// 配置信息
|
||||
$config = [
|
||||
'site' => array_intersect_key($site, array_flip(['name', 'indexurl', 'cdnurl', 'version', 'timezone', 'languages'])),
|
||||
'upload' => $upload,
|
||||
'modulename' => $modulename,
|
||||
'controllername' => $controllername,
|
||||
'actionname' => $actionname,
|
||||
'jsname' => 'backend/' . str_replace('.', '/', $controllername),
|
||||
'moduleurl' => rtrim(url("/{$modulename}", '', false), '/'),
|
||||
'language' => $lang,
|
||||
'referer' => Session::get("referer")
|
||||
];
|
||||
$config = array_merge($config, Config::get("view_replace_str"));
|
||||
|
||||
Config::set('upload', array_merge(Config::get('upload'), $upload));
|
||||
|
||||
// 配置信息后
|
||||
Hook::listen("config_init", $config);
|
||||
//加载当前控制器语言包
|
||||
$this->loadlang($controllername);
|
||||
//渲染站点配置
|
||||
$this->assign('site', $site);
|
||||
//渲染配置信息
|
||||
$this->assign('config', $config);
|
||||
//渲染权限对象
|
||||
$this->assign('auth', $this->auth);
|
||||
//渲染管理员对象
|
||||
$this->assign('admin', Session::get('admin'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载语言文件
|
||||
* @param string $name
|
||||
*/
|
||||
protected function loadlang($name)
|
||||
{
|
||||
$name = Loader::parseName($name);
|
||||
$name = preg_match("/^([a-zA-Z0-9_\.\/]+)\$/i", $name) ? $name : 'index';
|
||||
$lang = $this->request->langset();
|
||||
$lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn';
|
||||
Lang::load(APP_PATH . $this->request->module() . '/lang/' . $lang . '/' . str_replace('.', '/', $name) . '.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染配置信息
|
||||
* @param mixed $name 键名或数组
|
||||
* @param mixed $value 值
|
||||
*/
|
||||
protected function assignconfig($name, $value = '')
|
||||
{
|
||||
$this->view->config = array_merge($this->view->config ? $this->view->config : [], is_array($name) ? $name : [$name => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成查询所需要的条件,排序方式
|
||||
* @param mixed $searchfields 快速查询的字段
|
||||
* @param boolean $relationSearch 是否关联查询
|
||||
* @return array
|
||||
*/
|
||||
protected function buildparams($searchfields = null, $relationSearch = null)
|
||||
{
|
||||
$searchfields = is_null($searchfields) ? $this->searchFields : $searchfields;
|
||||
$relationSearch = is_null($relationSearch) ? $this->relationSearch : $relationSearch;
|
||||
$search = $this->request->get("search", '');
|
||||
$filter = $this->request->get("filter", '');
|
||||
$op = $this->request->get("op", '', 'trim');
|
||||
$sort = $this->request->get("sort", !empty($this->model) && $this->model->getPk() ? $this->model->getPk() : 'id');
|
||||
$order = $this->request->get("order", "DESC");
|
||||
$offset = max(0, $this->request->get("offset/d", 0));
|
||||
$limit = max(0, $this->request->get("limit/d", 0));
|
||||
$limit = $limit ?: 999999;
|
||||
//新增自动计算页码
|
||||
$page = $limit ? intval($offset / $limit) + 1 : 1;
|
||||
if ($this->request->has("page")) {
|
||||
$page = max(0, $this->request->get("page/d", 1));
|
||||
}
|
||||
$this->request->get([config('paginate.var_page') => $page]);
|
||||
$filter = (array)json_decode($filter, true);
|
||||
$op = (array)json_decode($op, true);
|
||||
$filter = $filter ? $filter : [];
|
||||
$where = [];
|
||||
$alias = [];
|
||||
$bind = [];
|
||||
$name = '';
|
||||
$aliasName = '';
|
||||
if (!empty($this->model) && $relationSearch) {
|
||||
$name = $this->model->getTable();
|
||||
$alias[$name] = Loader::parseName(basename(str_replace('\\', '/', get_class($this->model))));
|
||||
$aliasName = $alias[$name] . '.';
|
||||
}
|
||||
$sortArr = explode(',', $sort);
|
||||
foreach ($sortArr as $index => & $item) {
|
||||
$item = stripos($item, ".") === false ? $aliasName . trim($item) : $item;
|
||||
}
|
||||
unset($item);
|
||||
$sort = implode(',', $sortArr);
|
||||
$adminIds = $this->getDataLimitAdminIds();
|
||||
if (is_array($adminIds)) {
|
||||
$where[] = [$aliasName . $this->dataLimitField, 'in', $adminIds];
|
||||
}
|
||||
if ($search) {
|
||||
$searcharr = is_array($searchfields) ? $searchfields : explode(',', $searchfields);
|
||||
foreach ($searcharr as $k => &$v) {
|
||||
$v = stripos($v, ".") === false ? $aliasName . $v : $v;
|
||||
}
|
||||
unset($v);
|
||||
$where[] = [implode("|", $searcharr), "LIKE", "%{$search}%"];
|
||||
}
|
||||
$index = 0;
|
||||
foreach ($filter as $k => $v) {
|
||||
if (!preg_match('/^[a-zA-Z0-9_\-\.]+$/', $k)) {
|
||||
continue;
|
||||
}
|
||||
$sym = $op[$k] ?? '=';
|
||||
if (stripos($k, ".") === false) {
|
||||
$k = $aliasName . $k;
|
||||
}
|
||||
$v = !is_array($v) ? trim($v) : $v;
|
||||
$sym = strtoupper($op[$k] ?? $sym);
|
||||
//null和空字符串特殊处理
|
||||
if (!is_array($v)) {
|
||||
if (in_array(strtoupper($v), ['NULL', 'NOT NULL'])) {
|
||||
$sym = strtoupper($v);
|
||||
}
|
||||
if (in_array($v, ['""', "''"])) {
|
||||
$v = '';
|
||||
$sym = '=';
|
||||
}
|
||||
}
|
||||
|
||||
switch ($sym) {
|
||||
case '=':
|
||||
case '<>':
|
||||
$where[] = [$k, $sym, (string)$v];
|
||||
break;
|
||||
case 'LIKE':
|
||||
case 'NOT LIKE':
|
||||
case 'LIKE %...%':
|
||||
case 'NOT LIKE %...%':
|
||||
$where[] = [$k, trim(str_replace('%...%', '', $sym)), "%{$v}%"];
|
||||
break;
|
||||
case '>':
|
||||
case '>=':
|
||||
case '<':
|
||||
case '<=':
|
||||
$where[] = [$k, $sym, intval($v)];
|
||||
break;
|
||||
case 'FINDIN':
|
||||
case 'FINDINSET':
|
||||
case 'FIND_IN_SET':
|
||||
$v = is_array($v) ? $v : explode(',', str_replace(' ', ',', $v));
|
||||
$findArr = array_values($v);
|
||||
foreach ($findArr as $idx => $item) {
|
||||
$bindName = "item_" . $index . "_" . $idx;
|
||||
$bind[$bindName] = $item;
|
||||
$where[] = "FIND_IN_SET(:{$bindName}, `" . str_replace('.', '`.`', $k) . "`)";
|
||||
}
|
||||
break;
|
||||
case 'IN':
|
||||
case 'IN(...)':
|
||||
case 'NOT IN':
|
||||
case 'NOT IN(...)':
|
||||
$where[] = [$k, str_replace('(...)', '', $sym), is_array($v) ? $v : explode(',', $v)];
|
||||
break;
|
||||
case 'BETWEEN':
|
||||
case 'NOT BETWEEN':
|
||||
$arr = array_slice(explode(',', $v), 0, 2);
|
||||
if (stripos($v, ',') === false || !array_filter($arr, function ($v) {
|
||||
return $v != '' && $v !== false && $v !== null;
|
||||
})) {
|
||||
continue 2;
|
||||
}
|
||||
//当出现一边为空时改变操作符
|
||||
if ($arr[0] === '') {
|
||||
$sym = $sym == 'BETWEEN' ? '<=' : '>';
|
||||
$arr = $arr[1];
|
||||
} elseif ($arr[1] === '') {
|
||||
$sym = $sym == 'BETWEEN' ? '>=' : '<';
|
||||
$arr = $arr[0];
|
||||
}
|
||||
$where[] = [$k, $sym, $arr];
|
||||
break;
|
||||
case 'RANGE':
|
||||
case 'NOT RANGE':
|
||||
$v = str_replace(' - ', ',', $v);
|
||||
$arr = array_slice(explode(',', $v), 0, 2);
|
||||
if (stripos($v, ',') === false || !array_filter($arr)) {
|
||||
continue 2;
|
||||
}
|
||||
//当出现一边为空时改变操作符
|
||||
if ($arr[0] === '') {
|
||||
$sym = $sym == 'RANGE' ? '<=' : '>';
|
||||
$arr = $arr[1];
|
||||
} elseif ($arr[1] === '') {
|
||||
$sym = $sym == 'RANGE' ? '>=' : '<';
|
||||
$arr = $arr[0];
|
||||
}
|
||||
$tableArr = explode('.', $k);
|
||||
if (count($tableArr) > 1 && $tableArr[0] != $name && !in_array($tableArr[0], $alias)
|
||||
&& !empty($this->model) && $this->relationSearch) {
|
||||
//修复关联模型下时间无法搜索的BUG
|
||||
$relation = Loader::parseName($tableArr[0], 1, false);
|
||||
$alias[$this->model->$relation()->getTable()] = $tableArr[0];
|
||||
}
|
||||
$where[] = [$k, str_replace('RANGE', 'BETWEEN', $sym) . ' TIME', $arr];
|
||||
break;
|
||||
case 'NULL':
|
||||
case 'IS NULL':
|
||||
case 'NOT NULL':
|
||||
case 'IS NOT NULL':
|
||||
$where[] = [$k, strtolower(str_replace('IS ', '', $sym))];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
if (!empty($this->model)) {
|
||||
$this->model->alias($alias);
|
||||
}
|
||||
$model = $this->model;
|
||||
$where = function ($query) use ($where, $alias, $bind, &$model) {
|
||||
if (!empty($model)) {
|
||||
$model->alias($alias);
|
||||
$model->bind($bind);
|
||||
}
|
||||
foreach ($where as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
call_user_func_array([$query, 'where'], $v);
|
||||
} else {
|
||||
$query->where($v);
|
||||
}
|
||||
}
|
||||
};
|
||||
return [$where, $sort, $order, $offset, $limit, $page, $alias, $bind];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据限制的管理员ID
|
||||
* 禁用数据限制时返回的是null
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getDataLimitAdminIds()
|
||||
{
|
||||
if (!$this->dataLimit) {
|
||||
return null;
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return null;
|
||||
}
|
||||
$adminIds = [];
|
||||
if (in_array($this->dataLimit, ['auth', 'personal'])) {
|
||||
$adminIds = $this->dataLimit == 'auth' ? $this->auth->getChildrenAdminIds(true) : [$this->auth->id];
|
||||
}
|
||||
return $adminIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selectpage的实现方法
|
||||
*
|
||||
* 当前方法只是一个比较通用的搜索匹配,请按需重载此方法来编写自己的搜索逻辑,$where按自己的需求写即可
|
||||
* 这里示例了所有的参数,所以比较复杂,实现上自己实现只需简单的几行即可
|
||||
*
|
||||
*/
|
||||
protected function selectpage()
|
||||
{
|
||||
//设置过滤方法
|
||||
$this->request->filter(['trim', 'strip_tags', 'htmlspecialchars']);
|
||||
|
||||
//搜索关键词,客户端输入以空格分开,这里接收为数组
|
||||
$word = (array)$this->request->request("q_word/a");
|
||||
//当前页
|
||||
$page = $this->request->request("pageNumber");
|
||||
//分页大小
|
||||
$pagesize = $this->request->request("pageSize");
|
||||
//搜索条件
|
||||
$andor = $this->request->request("andOr", "and", "strtoupper");
|
||||
//排序方式
|
||||
$orderby = (array)$this->request->request("orderBy/a");
|
||||
//显示的字段
|
||||
$field = $this->request->request("showField");
|
||||
//主键
|
||||
$primarykey = $this->request->request("keyField");
|
||||
//主键值
|
||||
$primaryvalue = $this->request->request("keyValue");
|
||||
//搜索字段
|
||||
$searchfield = (array)$this->request->request("searchField/a");
|
||||
//自定义搜索条件
|
||||
$custom = (array)$this->request->request("custom/a");
|
||||
//是否返回树形结构
|
||||
$istree = $this->request->request("isTree", 0);
|
||||
$ishtml = $this->request->request("isHtml", 0);
|
||||
if ($istree) {
|
||||
$word = [];
|
||||
$pagesize = 999999;
|
||||
}
|
||||
$order = [];
|
||||
foreach ($orderby as $k => $v) {
|
||||
$order[$v[0]] = $v[1];
|
||||
}
|
||||
$field = $field ? $field : 'name';
|
||||
|
||||
//如果有primaryvalue,说明当前是初始化传值
|
||||
if ($primaryvalue !== null) {
|
||||
$where = [$primarykey => ['in', $primaryvalue]];
|
||||
$pagesize = 999999;
|
||||
} else {
|
||||
$where = function ($query) use ($word, $andor, $field, $searchfield, $custom) {
|
||||
$logic = $andor == 'AND' ? '&' : '|';
|
||||
$searchfield = is_array($searchfield) ? implode($logic, $searchfield) : $searchfield;
|
||||
$searchfield = str_replace(',', $logic, $searchfield);
|
||||
$word = array_filter(array_unique($word));
|
||||
if (count($word) == 1) {
|
||||
$query->where($searchfield, "like", "%" . reset($word) . "%");
|
||||
} else {
|
||||
$query->where(function ($query) use ($word, $searchfield) {
|
||||
foreach ($word as $index => $item) {
|
||||
$query->whereOr(function ($query) use ($item, $searchfield) {
|
||||
$query->where($searchfield, "like", "%{$item}%");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($custom && is_array($custom)) {
|
||||
foreach ($custom as $k => $v) {
|
||||
if (is_array($v) && 2 == count($v)) {
|
||||
$query->where($k, trim($v[0]), $v[1]);
|
||||
} else {
|
||||
$query->where($k, '=', $v);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
$adminIds = $this->getDataLimitAdminIds();
|
||||
if (is_array($adminIds)) {
|
||||
$this->model->where($this->dataLimitField, 'in', $adminIds);
|
||||
}
|
||||
$list = [];
|
||||
$total = $this->model->where($where)->count();
|
||||
if ($total > 0) {
|
||||
if (is_array($adminIds)) {
|
||||
$this->model->where($this->dataLimitField, 'in', $adminIds);
|
||||
}
|
||||
|
||||
$fields = is_array($this->selectpageFields) ? $this->selectpageFields : ($this->selectpageFields && $this->selectpageFields != '*' ? explode(',', $this->selectpageFields) : []);
|
||||
|
||||
//如果有primaryvalue,说明当前是初始化传值,按照选择顺序排序
|
||||
if ($primaryvalue !== null && preg_match("/^[a-z0-9_\-]+$/i", $primarykey)) {
|
||||
$primaryvalue = array_unique(is_array($primaryvalue) ? $primaryvalue : explode(',', $primaryvalue));
|
||||
//修复自定义data-primary-key为字符串内容时,给排序字段添加上引号
|
||||
$primaryvalue = array_map(function ($value) {
|
||||
return '\'' . $value . '\'';
|
||||
}, $primaryvalue);
|
||||
|
||||
$primaryvalue = implode(',', $primaryvalue);
|
||||
|
||||
$this->model->orderRaw("FIELD(`{$primarykey}`, {$primaryvalue})");
|
||||
} else {
|
||||
$this->model->order($order);
|
||||
}
|
||||
|
||||
$datalist = $this->model->where($where)
|
||||
->page($page, $pagesize)
|
||||
->select();
|
||||
|
||||
foreach ($datalist as $index => $item) {
|
||||
unset($item['password'], $item['salt']);
|
||||
if ($this->selectpageFields == '*') {
|
||||
$result = [
|
||||
$primarykey => $item[$primarykey] ?? '',
|
||||
$field => $item[$field] ?? '',
|
||||
];
|
||||
} else {
|
||||
$result = array_intersect_key(($item instanceof Model ? $item->toArray() : (array)$item), array_flip($fields));
|
||||
}
|
||||
$result['pid'] = isset($item['pid']) ? $item['pid'] : (isset($item['parent_id']) ? $item['parent_id'] : 0);
|
||||
$result = array_map("htmlentities", $result);
|
||||
$list[] = $result;
|
||||
}
|
||||
if ($istree && !$primaryvalue) {
|
||||
$tree = Tree::instance();
|
||||
$tree->init(collection($list)->toArray(), 'pid');
|
||||
$list = $tree->getTreeList($tree->getTreeArray(0), $field);
|
||||
if (!$ishtml) {
|
||||
foreach ($list as &$item) {
|
||||
$item = str_replace(' ', ' ', $item);
|
||||
}
|
||||
unset($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
//这里一定要返回有list这个字段,total是可选的,如果total<=list的数量,则会隐藏分页按钮
|
||||
return json(['list' => $list, 'total' => $total]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
protected function token()
|
||||
{
|
||||
$token = $this->request->param('__token__');
|
||||
|
||||
//验证Token
|
||||
if (!Validate::make()->check(['__token__' => $token], ['__token__' => 'require|token'])) {
|
||||
$this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]);
|
||||
}
|
||||
|
||||
//刷新Token
|
||||
$this->request->token();
|
||||
}
|
||||
}
|
||||
46
application/common/controller/BaseCom.php
Normal file
46
application/common/controller/BaseCom.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use think\Controller;
|
||||
|
||||
|
||||
class BaseCom extends Controller
|
||||
{
|
||||
public $uid = 0;
|
||||
public $redis = '';
|
||||
//初始化
|
||||
protected function _initialize()
|
||||
{
|
||||
//允许跨域
|
||||
header("Access-Control-Allow-Origin: *"); // 允许所有域访问
|
||||
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Authorization");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
|
||||
//检测系统是否维护中
|
||||
// $config = get_system_config();
|
||||
$is_maintenance = get_system_config_value('is_maintenance');
|
||||
if($is_maintenance == 2){
|
||||
return V(203, '系统维护中');
|
||||
}
|
||||
$token = input('token', '');
|
||||
if (empty($token)) {
|
||||
$token = request()->header('token');
|
||||
if(empty($token)){
|
||||
return V(301, '登录失效');
|
||||
}
|
||||
}
|
||||
|
||||
$reslut = model('UserToken')->check_login_token($token);
|
||||
if($reslut['code'] != 1) {
|
||||
model('UserToken')->where('token', $token)->update(['token' => 1]);
|
||||
return V($reslut['code'], $reslut['msg'],$reslut['data']);
|
||||
} else {
|
||||
$this->uid = $reslut['data'];
|
||||
//定义一个常量
|
||||
define('UID', $this->uid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
162
application/common/controller/Frontend.php
Normal file
162
application/common/controller/Frontend.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\library\Auth;
|
||||
use think\Config;
|
||||
use think\Controller;
|
||||
use think\Hook;
|
||||
use think\Lang;
|
||||
use think\Loader;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 前台控制器基类
|
||||
*/
|
||||
class Frontend extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* 布局模板
|
||||
* @var string
|
||||
*/
|
||||
protected $layout = '';
|
||||
|
||||
/**
|
||||
* 无需登录的方法,同时也就不需要鉴权了
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = [];
|
||||
|
||||
/**
|
||||
* 无需鉴权的方法,但需要登录
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedRight = [];
|
||||
|
||||
/**
|
||||
* 权限Auth
|
||||
* @var Auth
|
||||
*/
|
||||
protected $auth = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
//移除HTML标签
|
||||
$this->request->filter('trim,strip_tags,htmlspecialchars');
|
||||
$modulename = $this->request->module();
|
||||
$controllername = Loader::parseName($this->request->controller());
|
||||
$actionname = strtolower($this->request->action());
|
||||
|
||||
// 检测IP是否允许
|
||||
check_ip_allowed();
|
||||
|
||||
// 如果有使用模板布局
|
||||
if ($this->layout) {
|
||||
$this->view->engine->layout('layout/' . $this->layout);
|
||||
}
|
||||
$this->auth = Auth::instance();
|
||||
|
||||
// token
|
||||
$token = $this->request->server('HTTP_TOKEN', $this->request->request('token', \think\Cookie::get('token')));
|
||||
|
||||
$path = str_replace('.', '/', $controllername) . '/' . $actionname;
|
||||
// 设置当前请求的URI
|
||||
$this->auth->setRequestUri($path);
|
||||
// 检测是否需要验证登录
|
||||
if (!$this->auth->match($this->noNeedLogin)) {
|
||||
//初始化
|
||||
$this->auth->init($token);
|
||||
//检测是否登录
|
||||
if (!$this->auth->isLogin()) {
|
||||
$this->error(__('Please login first'), 'index/user/login');
|
||||
}
|
||||
// 判断是否需要验证权限
|
||||
if (!$this->auth->match($this->noNeedRight)) {
|
||||
// 判断控制器和方法判断是否有对应权限
|
||||
if (!$this->auth->check($path)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果有传递token才验证是否登录状态
|
||||
if ($token) {
|
||||
$this->auth->init($token);
|
||||
}
|
||||
}
|
||||
|
||||
$this->view->assign('user', $this->auth->getUser());
|
||||
|
||||
// 语言检测
|
||||
$lang = $this->request->langset();
|
||||
$lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn';
|
||||
|
||||
$site = Config::get("site");
|
||||
|
||||
$upload = \app\common\model\Config::upload();
|
||||
|
||||
// 上传信息配置后
|
||||
Hook::listen("upload_config_init", $upload);
|
||||
|
||||
// 配置信息
|
||||
$config = [
|
||||
'site' => array_intersect_key($site, array_flip(['name', 'cdnurl', 'version', 'timezone', 'languages'])),
|
||||
'upload' => $upload,
|
||||
'modulename' => $modulename,
|
||||
'controllername' => $controllername,
|
||||
'actionname' => $actionname,
|
||||
'jsname' => 'frontend/' . str_replace('.', '/', $controllername),
|
||||
'moduleurl' => rtrim(url("/{$modulename}", '', false), '/'),
|
||||
'language' => $lang
|
||||
];
|
||||
$config = array_merge($config, Config::get("view_replace_str"));
|
||||
|
||||
Config::set('upload', array_merge(Config::get('upload'), $upload));
|
||||
|
||||
// 配置信息后
|
||||
Hook::listen("config_init", $config);
|
||||
// 加载当前控制器语言包
|
||||
$this->loadlang($controllername);
|
||||
$this->assign('site', $site);
|
||||
$this->assign('config', $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载语言文件
|
||||
* @param string $name
|
||||
*/
|
||||
protected function loadlang($name)
|
||||
{
|
||||
$name = Loader::parseName($name);
|
||||
$name = preg_match("/^([a-zA-Z0-9_\.\/]+)\$/i", $name) ? $name : 'index';
|
||||
$lang = $this->request->langset();
|
||||
$lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn';
|
||||
Lang::load(APP_PATH . $this->request->module() . '/lang/' . $lang . '/' . str_replace('.', '/', $name) . '.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染配置信息
|
||||
* @param mixed $name 键名或数组
|
||||
* @param mixed $value 值
|
||||
*/
|
||||
protected function assignconfig($name, $value = '')
|
||||
{
|
||||
$this->view->config = array_merge($this->view->config ? $this->view->config : [], is_array($name) ? $name : [$name => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
protected function token()
|
||||
{
|
||||
$token = $this->request->param('__token__');
|
||||
|
||||
//验证Token
|
||||
if (!Validate::make()->check(['__token__' => $token], ['__token__' => 'require|token'])) {
|
||||
$this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]);
|
||||
}
|
||||
|
||||
//刷新Token
|
||||
$this->request->token();
|
||||
}
|
||||
}
|
||||
64
application/common/controller/NumberAuth.php
Normal file
64
application/common/controller/NumberAuth.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use AlibabaCloud\SDK\Dypnsapi\V20170525\Dypnsapi;
|
||||
use Darabonba\OpenApi\Models\Config;
|
||||
use AlibabaCloud\SDK\Dypnsapi\V20170525\Models\CreateVerifySchemeRequest;
|
||||
use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
|
||||
use AlibabaCloud\Credentials\Credential;
|
||||
|
||||
use \Exception;
|
||||
use AlibabaCloud\Tea\Exception\TeaError;
|
||||
use AlibabaCloud\Tea\Utils\Utils;
|
||||
|
||||
|
||||
class NumberAuth
|
||||
{
|
||||
public static function getClient() {
|
||||
$config = get_system_config();
|
||||
$credential = new Credential();
|
||||
$config = new Config([
|
||||
"credential" => $credential,
|
||||
'accessKeyId' => $config['aliyun_access_key_id'],
|
||||
'accessKeySecret' => $config['aliyun_access_key_secret'],
|
||||
]);
|
||||
// Endpoint 请参考 https://api.aliyun.com/product/Dypnsapi
|
||||
$config->endpoint = "dypnsapi.aliyuncs.com";
|
||||
return new Dypnsapi($config);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Token获取手机号
|
||||
* @param string $token 客户端SDK返回的Token
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getMobileByToken($token) {
|
||||
|
||||
$client = self::getClient();
|
||||
$createVerifySchemeRequest = new CreateVerifySchemeRequest([
|
||||
'accessToken' => $token
|
||||
]);
|
||||
|
||||
try {
|
||||
$response = $client->getMobileWithOptions($createVerifySchemeRequest, new RuntimeOptions([]));
|
||||
// var_dump($response->body);die;
|
||||
if ($response->body->code == 'OK') {
|
||||
return $response->body->getMobileResultDTO->mobile;
|
||||
}
|
||||
}
|
||||
catch (Exception $error) {
|
||||
if (!($error instanceof TeaError)) {
|
||||
$error = new TeaError([], $error->getMessage(), $error->getCode(), $error);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
$path = __DIR__ . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . 'vendor' . \DIRECTORY_SEPARATOR . 'autoload.php';
|
||||
if (file_exists($path)) {
|
||||
require_once $path;
|
||||
}
|
||||
//NumberAuth::getMobileByToken(array_slice($token, 1));
|
||||
699
application/common/controller/Push.php
Normal file
699
application/common/controller/Push.php
Normal file
@@ -0,0 +1,699 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use think\Loader;
|
||||
|
||||
class Push
|
||||
{
|
||||
const PUSH_POPULARITY = 5001;//推送房间-人气变化
|
||||
const PUSH_RIDE = 5003;//推送房间-坐骑进场特效
|
||||
const PUSH_NOBILITY = 5004;//推送房间-爵位用户进场特效
|
||||
const PUSH_APPLY_COUNT = 5005;//推送房间-上麦申请人数变化
|
||||
const PUSH_BANNED_USER = 5007;//推送房间-用户禁言 1禁言2解禁
|
||||
const PUSH_CLOSE_PIT = 5011;//推送房间-是否封麦 1封麦2解封
|
||||
const PUSH_CLEAR_CARDIAC = 5013;//推送房间-清空单个麦位心动值
|
||||
const PUSH_CLEAR_CARDIAC_ALL = 5014;//推送房间-清空所有麦位心动值
|
||||
const PUSH_SET_MANAGER = 5015;//推送房间-设置房间管理员
|
||||
const PUSH_DELETE_MANAGER = 5016;//推送房间-删除房间管理员
|
||||
const PUSH_SWITCH_VOICE = 5017;//推送房间-开关麦 1开0关
|
||||
const PUSH_GIFT_BANNER = 5019;//推送所有人-横幅礼物通知
|
||||
const PUSH_GIFT_CHAT_ROOM = 5020;//推送房间-聊天室礼物通知
|
||||
const PUSH_FISH = 5021;//推送所有人-许愿池钓到大礼物时通知
|
||||
const PUSH_ROOM_PASSWORD = 5022;//推送房间-房间密码变化通知 0取消密码1设置或修改密码
|
||||
const PUSH_CARDIAC_SWITCH = 5023;//推送房间-房间心动值开关变化通知 1开2关
|
||||
const PUSH_ROOM_WHEAT = 5024;//推送房间-上麦模式变化通知 1自由2排麦
|
||||
const PUSH_UPDATE_ROOM_NAME = 5025;//推送房间-修改房间名称
|
||||
const PUSH_WEEK_STAR = 5027;//推送房间-周星用户进场特效
|
||||
const PUSH_UPDATE_ROOM_BACKGROUND = 5028;//推送房间-修改房间背景
|
||||
const PUSH_UPDATE_ROOM_PLAYING = 5029;//推送房间-修改房间 玩法|公告
|
||||
const PUSH_BOSS_ATTACK = 5030;//推送房间-BOSS大作战 超过99999金币 发送所有人消息
|
||||
const PUSH_BOSS_BLOOD = 5031;//推送所有正在攻击boss的用户-boss大作战血量变化推送
|
||||
const PUSH_PIT_ON = 5032;//推送房间-上麦
|
||||
const PUSH_PIT_DOWN = 5033;//推送房间-下麦
|
||||
const PUSH_KICK_OUT = 5034;//推送单独用户-被踢出房间
|
||||
const PUSH_APPLY_USER = 5035;//推送单独用户-定向推向给上麦的用户
|
||||
const PUSH_SHUT_UP = 5036;//推送房间-用户禁麦 1禁麦2解禁
|
||||
const PUSH_JOIN_ROOM = 5037;//推送房间-用户进入房间
|
||||
const PUSH_COUNT_DOWN = 5038;//推送房间-麦位倒计时
|
||||
const PUSH_ROLL = 5039;//推送房间-扔骰子
|
||||
const PUSH_FM_GOLD = 5040;//推送所有房间-电台房开通黄金守护
|
||||
const PUSH_FACE = 5041;//推送房间-在麦上发送表情
|
||||
const PUSH_ZEGO_LOG = 5042;//推送单独用户-上传即构日志
|
||||
const PUSH_ROOM_CHAT_STATUS = 5043;//推送房间-公屏状态
|
||||
const PUSH_BALL_START = 5044;//推送房间-球球大作战-开球
|
||||
const PUSH_BALL_THROW = 5045;//推送房间-球球大作战-弃球
|
||||
const PUSH_BALL_SHOW = 5046;//推送房间-球球大作战-亮球
|
||||
const PUSH_SOUND_EFFECT_CHANGE = 5047;//推送房间-房间音效改变
|
||||
const PUSH_ORDER_RECEIVE = 5048;//推送大神用户-有用户下单
|
||||
const PUSH_ORDER_REFUND = 5049;//推送给大神-有用户要退款
|
||||
const PUSH_ROOM_DEMAND_BOSS = 5050;//推送单独用户-推送给8号麦的老板
|
||||
const PUSH_ROOM_DEMAND_UPDATE = 5051;//推送房间-更新派单需求
|
||||
const PUSH_ROOM_DEMAND_TO_ANCHOR = 5052;//推送所有用户-将派单需求推送给所有符合条件的大神
|
||||
const PUSH_ORDER_MESSAGE = 5053;//推送所有的订单消息(浮窗形式)
|
||||
const PUSH_ROOM_OWNER_MODEL = 5054;//推送房间-更新房主模式
|
||||
const PUSH_ROOM_QUIT = 5055;//推送房间-用户退出房间
|
||||
const PUSH_ROOM_OWNER_JOIN = 5056;//推送房间-房主进入房间
|
||||
const PUSH_LUCKYRANK = 5057;//推送房间-许愿池手气榜推送
|
||||
const PUSH_GAME_TIPS = 5060;//房间游戏飘屏
|
||||
const PUSH_CHANGE_HEARTBEAT = 5061;//推送房间-心动值变化
|
||||
const PUSH_PIT_CHANGE = 5062;//推送房间-换麦位
|
||||
const PUSH_PIT_JOIN_SMALL_ROOM = 5063;//推送单独用户-进小房间
|
||||
const PUSH_PIT_OUT_SMALL_ROOM = 5064;//推送单独用户-退出小黑屋
|
||||
const PUSH_FRIEND_STATUS = 5065;//推送房间-交友环节状态
|
||||
const PUSH_ONLINE_DATING_NUM = 5066;//推送交友房在线对数发生变化
|
||||
const PUSH_FRIEND_ADD_TIME = 5067;//推送房间-交友-心动连线环节 延时推送
|
||||
const PUSH_SMALL_ROOM_ADD_TIME = 5068;//推送房间-送礼物增加时间
|
||||
const PUSH_CHANGE_PIT = 5069;//自由上麦换麦
|
||||
const PUSH_CHANGE_ROOM_LABEL = 5070;//推送房间类型发生变化
|
||||
const PUSH_WISH_PROGRESS = 7001;//游戏进度
|
||||
//推送系统消息
|
||||
const PUSH_SYSTEM_MESSAGE = 7000;//推送系统消息
|
||||
|
||||
public $user_id, $room_id, $topic_room, $topic_client;
|
||||
|
||||
public function __construct($user_id = 0, $room_id = 0)
|
||||
{
|
||||
$this->user_id = $user_id;
|
||||
$this->room_id = $room_id;
|
||||
|
||||
$this->topic_room = 'room_' . $this->room_id;
|
||||
$this->topic_client = 'user_' . $this->user_id;
|
||||
}
|
||||
|
||||
public function setUser($user_id)
|
||||
{
|
||||
$this->user_id = $user_id;
|
||||
$this->topic_client = 'user_' . $user_id;
|
||||
}
|
||||
|
||||
public function setRoom($room_id)
|
||||
{
|
||||
$this->room_id = $room_id;
|
||||
$this->topic_room = 'room_' . $room_id;
|
||||
}
|
||||
|
||||
private function push($push_type, $topic, $data = [])
|
||||
{
|
||||
Loader::import('Mqtt.Mqtt', EXTEND_PATH, '.php');
|
||||
$mqtt = new \Mqtt();
|
||||
$content = json_encode(
|
||||
[
|
||||
'type' => $push_type,
|
||||
'time' => getMillisecond(),
|
||||
'msg' => $data,
|
||||
]
|
||||
);
|
||||
$mqtt->publishs($topic, $content);
|
||||
}
|
||||
/**
|
||||
* 推送许愿池手气榜记录
|
||||
*/
|
||||
public function luckyRank($data)
|
||||
{
|
||||
$topic = 'room';
|
||||
$this->push(self::PUSH_LUCKYRANK, $topic, $data);
|
||||
return true;
|
||||
}
|
||||
|
||||
//推送人气
|
||||
public function updatePopularity($popularity)
|
||||
{
|
||||
$topic = '$delayed/1/' . $this->topic_room;
|
||||
$data = ['popularity' => $popularity, 'room_id' => $this->room_id];
|
||||
$this->push(self::PUSH_POPULARITY, $topic, $data);
|
||||
}
|
||||
|
||||
//推送上麦
|
||||
public function pitOn($pit_number, $data)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data['room_id'] = $this->room_id;
|
||||
$data['pit_number'] = intval($pit_number);
|
||||
|
||||
$ball = S('room:user:ball:' . $this->room_id . ':' . $this->user_id);
|
||||
$data['ball_state'] = $ball === false ? 0 : 1;
|
||||
$this->push(self::PUSH_PIT_ON, $topic, $data);
|
||||
}
|
||||
|
||||
//推送下麦
|
||||
public function pitDown($down_info)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id,
|
||||
'pit_number' => intval($down_info['pit_number']),
|
||||
'user_id' => intval($this->user_id),
|
||||
'emchat_username'=>$down_info['emchat_username']];
|
||||
$this->push(self::PUSH_PIT_DOWN, $topic, $data);
|
||||
}
|
||||
|
||||
//带坐骑用户进场特效通知
|
||||
public function ride($ride_info)
|
||||
{
|
||||
$topic = '$delayed/1/' . $this->topic_room;
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'ride_url' => $ride_info['special'],
|
||||
'show_type' => $ride_info['show_type']
|
||||
];
|
||||
$this->push(self::PUSH_RIDE, $topic, $data);
|
||||
}
|
||||
|
||||
//带爵位用户进场特效通知
|
||||
public function nobility($data)
|
||||
{
|
||||
$topic = '$delayed/1/' . $this->topic_room;
|
||||
if ($data['nobilityId'] == 6) {
|
||||
$special = 'https://yutangyuyin.oss-cn-hangzhou.aliyuncs.com/nobility/' . $data['nobilityId'] . '.svga';
|
||||
} else {
|
||||
$special = '';
|
||||
}
|
||||
|
||||
//爵位特效暂时不推
|
||||
$special = '';
|
||||
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'user_id' => $this->user_id,
|
||||
'nobility_name' => $data['nobilityName'],
|
||||
'nobility_id' => $data['nobilityId'],
|
||||
'nobility_icon' => $data['nobility_icon'],
|
||||
'avatar' => $data['headPicture'],
|
||||
'nickname' => $data['userName'],
|
||||
'special' => $special,
|
||||
'sex' => $data['sex'],
|
||||
];
|
||||
$this->push(self::PUSH_NOBILITY, $topic, $data);
|
||||
}
|
||||
|
||||
//上麦申请人数变化通知
|
||||
public function applyCount($count, $user_ids = '')
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
|
||||
$count_8 = M('RoomPitApply')->where(['room_id' => $this->room_id, 'pit_number' => 8])->count();
|
||||
$count_8 = $count_8 > 0 ? $count_8 : 0;
|
||||
|
||||
$total_count = $count - $count_8;
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'count' => $total_count > 0 ? $total_count : 0,
|
||||
'count_8' => $count_8,
|
||||
'user_ids' => $user_ids
|
||||
];
|
||||
$this->push(self::PUSH_APPLY_COUNT, $topic, $data);
|
||||
}
|
||||
|
||||
//用户被禁言 action 1禁言2解禁
|
||||
public function bannedUser($action)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'user_id' => $this->user_id, 'action' => $action];
|
||||
$this->push(self::PUSH_BANNED_USER, $topic, $data);
|
||||
}
|
||||
|
||||
//是否封麦 1.封麦 2.解除封麦
|
||||
public function closePit($pit_number, $action)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'pit_number' => $pit_number, 'action' => $action];
|
||||
$this->push(self::PUSH_CLOSE_PIT, $topic, $data);
|
||||
}
|
||||
|
||||
//清空单个麦位心动值
|
||||
public function clearCardiac($pit_number)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'pit_number' => $pit_number];
|
||||
$this->push(self::PUSH_CLEAR_CARDIAC, $topic, $data);
|
||||
}
|
||||
|
||||
//清空所有麦位心动值
|
||||
public function clearCardiacAll()
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id];
|
||||
$this->push(self::PUSH_CLEAR_CARDIAC_ALL, $topic, $data);
|
||||
}
|
||||
|
||||
//设置房间管理员
|
||||
public function setManager()
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'user_id' => $this->user_id];
|
||||
$this->push(self::PUSH_SET_MANAGER, $topic, $data);
|
||||
}
|
||||
|
||||
//删除房间管理员
|
||||
public function deleteManager()
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'user_id' => $this->user_id];
|
||||
$this->push(self::PUSH_DELETE_MANAGER, $topic, $data);
|
||||
}
|
||||
|
||||
//开关麦 action 1开0关
|
||||
public function switchVoice($action, $pit_number = 0)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'user_id' => $this->user_id,
|
||||
'pit_number' => $pit_number,
|
||||
'action' => $action
|
||||
];
|
||||
$this->push(self::PUSH_SWITCH_VOICE, $topic, $data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// =======================================================================================================
|
||||
// ========================================羽声使用开始=====================================================================
|
||||
|
||||
//横幅礼物通知
|
||||
public function giftBanner($gift_list)
|
||||
{
|
||||
$topic = 'qx_room_topic';
|
||||
$data = ['room_id' => $this->room_id, 'list' => $gift_list];
|
||||
$this->push(self::PUSH_GIFT_BANNER, $topic, $data);
|
||||
}
|
||||
|
||||
//系统消息
|
||||
public function systemMessage($text_list)
|
||||
{
|
||||
$topic = 'system_message';
|
||||
$data = ['list' => $text_list];
|
||||
$this->push(self::PUSH_SYSTEM_MESSAGE, $topic, $data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// =========================================羽声使用结束=====================================================
|
||||
// =============================================================================================================
|
||||
|
||||
|
||||
|
||||
|
||||
//聊天室礼物通知
|
||||
public function giftChatRoom($gift_list, $cardiac, $contribution, $show_cat = 0)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'gift_list' => $gift_list,
|
||||
'cardiac_list' => $cardiac,
|
||||
'contribution' => $contribution,
|
||||
'show_cat' => $show_cat,
|
||||
'user_id' => $this->user_id
|
||||
];
|
||||
$this->push(self::PUSH_GIFT_CHAT_ROOM, $topic, $data);
|
||||
}
|
||||
|
||||
//自由上麦换麦通知
|
||||
public function sendPitNumber($data)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = $data;
|
||||
$this->push(self::PUSH_CHANGE_PIT, $topic, $data);
|
||||
}
|
||||
|
||||
//聊天室心动值变化通知
|
||||
public function heartChatRoom($heart)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data['list'] = $heart;
|
||||
$data['room_id'] = $this->room_id;
|
||||
$this->push(self::PUSH_CHANGE_HEARTBEAT, $topic, $data);
|
||||
}
|
||||
|
||||
//推送房间-交友-心动连线环节 延时推送
|
||||
public function friendDelayed($endtime)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = $endtime;
|
||||
$this->push(self::PUSH_FRIEND_ADD_TIME, $topic, $data);
|
||||
}
|
||||
|
||||
//私密小屋刷礼物后推送
|
||||
public function heartSmallRoom($heart)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = $heart;
|
||||
$this->push(self::PUSH_SMALL_ROOM_ADD_TIME, $topic, $data);
|
||||
}
|
||||
|
||||
//聊天室排行榜通知
|
||||
public function rankChatRoom($data_list)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data['list'] = $data_list;
|
||||
$data['room_id'] = $this->room_id;
|
||||
$this->push(self::PUSH_PIT_CHANGE, $topic, $data);
|
||||
}
|
||||
|
||||
//进入小黑屋
|
||||
//退出小黑屋
|
||||
public function blackRoom($cp_room_id,$users,$action,$room_on_line_cp=0,$heart_id=0,$heart_value=0)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
if($action == 1){ //进入小黑屋
|
||||
$data = ['room_id' => $this->room_id,'cp_room_id'=>$cp_room_id,'users' => $users,'room_on_line_cp' => $room_on_line_cp,'heart_id'=>$heart_id,'heart_value'=>$heart_value,'end_time'=>time()+600];
|
||||
$this->push(self::PUSH_PIT_JOIN_SMALL_ROOM, $topic, $data);
|
||||
}else{//退出小黑屋
|
||||
$data = ['room_id' => $this->room_id,'pid_room_id'=>$cp_room_id,'users' => $users,'room_on_line_cp' => $room_on_line_cp,'heart_id'=>$heart_id,'heart_value'=>$heart_value];
|
||||
$this->push(self::PUSH_PIT_OUT_SMALL_ROOM, $topic, $data);
|
||||
}
|
||||
}
|
||||
|
||||
//交由环节状态
|
||||
public function stage($roomid,$stage,$end_time="")
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
if($stage == 2) {
|
||||
$data = ['room_id' => $roomid, 'status' => $stage,'end_time'=>$end_time];
|
||||
}else{
|
||||
$data = ['room_id' => $roomid, 'status' => $stage];
|
||||
}
|
||||
$this->push(self::PUSH_FRIEND_STATUS, $topic, $data);
|
||||
}
|
||||
|
||||
//聊天室在线心动人数 PUSH_ONLINE_DATING_NUM
|
||||
public function onlineDatingNum($room_id,$online_num)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $room_id, 'online_num' => $online_num];
|
||||
$this->push(self::PUSH_ONLINE_DATING_NUM, $topic, $data);
|
||||
}
|
||||
|
||||
//许愿池钓到大礼物时通知
|
||||
public function fish($data, $user_info,$name)
|
||||
{
|
||||
$gift_list = $data['gift_list'];
|
||||
$txt = "<font color='#FFFFFF'>哇塞</font><font color='#B9FF5E'>".$user_info['nickname']."</font><font color='#FFA7E7'>在{$name}中获得</font>";
|
||||
$txt_extra = [];
|
||||
foreach ($gift_list as $key => $value) {
|
||||
if ($value['price'] >= C('WISH_PUSH_MONEY')) {
|
||||
$txt_extra [] = [
|
||||
'text' => $txt."<font color='#5AFDFF'>".$value['prize_title']."</font><font color='#FFFFFF'>X".$value['number']."</font>",
|
||||
'picture' => $value['picture'],
|
||||
];
|
||||
}
|
||||
}
|
||||
if (!empty($txt_extra)) {
|
||||
$topic = 'room';
|
||||
$this->push(self::PUSH_FISH, $topic, $txt_extra);
|
||||
}
|
||||
}
|
||||
|
||||
//房间密码 0取消密码1设置或修改密码
|
||||
public function roomPassword($action)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'action' => $action];
|
||||
$this->push(self::PUSH_ROOM_PASSWORD, $topic, $data);
|
||||
}
|
||||
|
||||
//房间心动值开关 1开2关
|
||||
public function cardiacSwitch($action)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'action' => $action];
|
||||
$this->push(self::PUSH_CARDIAC_SWITCH, $topic, $data);
|
||||
}
|
||||
|
||||
//上麦模式变化 1自由2排麦
|
||||
public function roomWheat($action)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'action' => $action];
|
||||
$this->push(self::PUSH_ROOM_WHEAT, $topic, $data);
|
||||
}
|
||||
|
||||
//修改房间名称
|
||||
public function updateRoomName($room_name)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'room_name' => $room_name];
|
||||
$this->push(self::PUSH_UPDATE_ROOM_NAME, $topic, $data);
|
||||
}
|
||||
|
||||
//周星用户进场
|
||||
public function weekStar($nickname, $rank)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'nickname' => $nickname, 'rank' => $rank];
|
||||
$this->push(self::PUSH_WEEK_STAR, $topic, $data);
|
||||
}
|
||||
|
||||
//修改房间背景
|
||||
public function updateRoomBackground($background)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'background' => $background];
|
||||
$this->push(self::PUSH_UPDATE_ROOM_BACKGROUND, $topic, $data);
|
||||
}
|
||||
|
||||
//修改房间玩法|公告
|
||||
public function updateRoomPlaying($playing, $greeting)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'playing' => $playing, 'greeting' => $greeting];
|
||||
$this->push(self::PUSH_UPDATE_ROOM_PLAYING, $topic, $data);
|
||||
}
|
||||
|
||||
//boss大作战 超过99999金币 发送所有人消息
|
||||
|
||||
public function AttackBoss($gift_list, $user_info)
|
||||
{
|
||||
$txt = "<font color='#FFFFFF'>哇塞</font><font color='#FD8469'>" . $user_info['nickname'] . "</font><font color='#FFFFFF'>在BOSS大作战中获得</font>";
|
||||
$txt_extra = [];
|
||||
foreach ($gift_list as $key => $value) {
|
||||
if ($value['price'] >= 5200) {
|
||||
$txt_extra [] = [
|
||||
'text' => $txt."<font color='#FABA5C'>".$value['prize_title']."</font><font color='#FFFFFF'>X".$value['number']."</font>",
|
||||
'picture' => $value['picture'],
|
||||
];
|
||||
}
|
||||
}
|
||||
if (!empty($txt_extra)) {
|
||||
$topic = 'room';
|
||||
$this->push(self::PUSH_BOSS_ATTACK, $topic, $txt_extra);
|
||||
}
|
||||
}
|
||||
|
||||
//boss大作战血量变化推送
|
||||
public function bossBlood($left_blood = 0, $total_blood = 0, $gift_list = [], $user_info = [])
|
||||
{
|
||||
if ($gift_list) {
|
||||
foreach ($gift_list as $key => $value) {
|
||||
if ($value['price'] >= 520) {
|
||||
$gift_list[$key]['nickname'] = $user_info['nickname'];
|
||||
} else {
|
||||
unset($gift_list[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$topic = 'boss';
|
||||
$data = ['left_blood' => $left_blood, 'total_blood' => $total_blood, 'gift_list' => array_values($gift_list)];
|
||||
$this->push(self::PUSH_BOSS_BLOOD, $topic, $data);
|
||||
}
|
||||
|
||||
//定向推送给上麦的用户
|
||||
public function agreeApplyUser($pit_number)
|
||||
{
|
||||
$topic = $this->topic_client;
|
||||
$data = ['room_id' => $this->room_id, 'pit_number' => $pit_number];
|
||||
$this->push(self::PUSH_APPLY_USER, $topic, $data);
|
||||
}
|
||||
|
||||
//定向推送给被踢出房间的用户
|
||||
public function kickOut()
|
||||
{
|
||||
$topic = $this->topic_client;
|
||||
$data = ['room_id' => $this->room_id, 'user_id' => $this->user_id];
|
||||
$this->push(self::PUSH_KICK_OUT, $topic, $data);
|
||||
}
|
||||
|
||||
//用户禁麦 action 1禁麦2解禁
|
||||
public function shutUp($action, $pit_number)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'action' => $action,
|
||||
'pit_number' => $pit_number,
|
||||
'user_id' => $this->user_id
|
||||
];
|
||||
$this->push(self::PUSH_SHUT_UP, $topic, $data);
|
||||
}
|
||||
|
||||
//用户进入房间
|
||||
public function joinRoom($nickname, $rank_icon, $role, $user_is_new)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'user_id' => $this->user_id,
|
||||
'nickname' => $nickname,
|
||||
'rank_icon' => $rank_icon,
|
||||
'role' => $role,
|
||||
'user_is_new' => $user_is_new
|
||||
];
|
||||
$this->push(self::PUSH_JOIN_ROOM, $topic, $data);
|
||||
}
|
||||
|
||||
//麦位倒计时
|
||||
public function countDown($pit_number, $seconds)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'pit_number' => $pit_number, 'seconds' => $seconds];
|
||||
$this->push(self::PUSH_COUNT_DOWN, $topic, $data);
|
||||
}
|
||||
|
||||
//扔骰子
|
||||
public function roll($number, $pit_number)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'user_id' => $this->user_id,
|
||||
'number' => $number,
|
||||
'pit_number' => $pit_number
|
||||
];
|
||||
$this->push(self::PUSH_ROLL, $topic, $data);
|
||||
}
|
||||
|
||||
//电台房开通黄金守护
|
||||
public function fmGold($nickname_from, $nickname_to)
|
||||
{
|
||||
$topic = 'room';
|
||||
$data = ['room_id' => $this->room_id, 'nickname_from' => $nickname_from, 'nickname_to' => $nickname_to];
|
||||
$this->push(self::PUSH_FM_GOLD, $topic, $data);
|
||||
}
|
||||
|
||||
//发送表情
|
||||
public function face($pit_number, $picture, $special)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'pit_number' => $pit_number,
|
||||
'picture' => $picture,
|
||||
'special' => $special
|
||||
];
|
||||
$this->push(self::PUSH_FACE, $topic, $data);
|
||||
}
|
||||
|
||||
//上传即构日志
|
||||
public function zegoLog()
|
||||
{
|
||||
$topic = $this->topic_client;
|
||||
$data = ['user_id' => $this->user_id];
|
||||
$this->push(self::PUSH_ZEGO_LOG, $topic, $data);
|
||||
}
|
||||
|
||||
//公屏状态
|
||||
public function roomChatStatus($status)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'status' => $status];
|
||||
$this->push(self::PUSH_ROOM_CHAT_STATUS, $topic, $data);
|
||||
}
|
||||
|
||||
//球球大作战-开球
|
||||
public function ballStart($pit_number)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'pit_number' => $pit_number];
|
||||
$this->push(self::PUSH_BALL_START, $topic, $data);
|
||||
}
|
||||
|
||||
//球球大作战-弃球
|
||||
public function ballThrow($pit_number)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = ['room_id' => $this->room_id, 'pit_number' => $pit_number];
|
||||
$this->push(self::PUSH_BALL_THROW, $topic, $data);
|
||||
}
|
||||
|
||||
//球球大作战-亮球
|
||||
public function ballShow($pit_number, $first, $second, $third)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'pit_number' => $pit_number,
|
||||
'first' => $first,
|
||||
'second' => $second,
|
||||
'third' => $third,
|
||||
'user_id' => $this->user_id
|
||||
];
|
||||
$this->push(self::PUSH_BALL_SHOW, $topic, $data);
|
||||
}
|
||||
|
||||
//房间音效改变
|
||||
public function soundEffectChange($sound_effect_detail)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$data = [
|
||||
'room_id' => $this->room_id,
|
||||
'id' => $sound_effect_detail['id'],
|
||||
'config' => $sound_effect_detail['config']
|
||||
];
|
||||
$this->push(self::PUSH_SOUND_EFFECT_CHANGE, $topic, $data);
|
||||
}
|
||||
|
||||
//更新房主模式
|
||||
public function ownerModel($data)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$this->push(self::PUSH_ROOM_OWNER_MODEL, $topic, $data);
|
||||
}
|
||||
|
||||
//退出房间
|
||||
public function quitRoom($data)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$this->push(self::PUSH_ROOM_QUIT, $topic, $data);
|
||||
}
|
||||
|
||||
public function gamePush($data){
|
||||
$topic = 'room';
|
||||
$this->push(self::PUSH_GAME_TIPS, $topic, $data);
|
||||
}
|
||||
|
||||
public function gameProgressPush($data){
|
||||
$topic = 'room';
|
||||
$this->push(self::PUSH_WISH_PROGRESS, $topic, $data);
|
||||
}
|
||||
|
||||
//游戏达到大礼物时通知
|
||||
public function game($giftInfo, $nickname,$title)
|
||||
{
|
||||
$txt = "<font color='#FFFFFF'>哇塞</font><font color='#FD8469'>".$nickname."</font><font color='#FFFFFF'>在{$title}中获得</font>";
|
||||
$txt_extra = [];
|
||||
foreach ($giftInfo as $key => $value) {
|
||||
if ($value['price'] >= 5200) {
|
||||
$txt_extra [] = [
|
||||
'text' => $txt."<font color='#FABA5C'>".$value['prize_title']."</font><font color='#FFFFFF'>X".$value['number']."</font>",
|
||||
'picture' => $value['picture']
|
||||
];
|
||||
}
|
||||
}
|
||||
if (!empty($txt_extra)) {
|
||||
$topic = 'room';
|
||||
$this->push(self::PUSH_FISH, $topic, $txt_extra);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//推送房间类型发生变化
|
||||
public function roomTypeChange($data)
|
||||
{
|
||||
$topic = $this->topic_room;
|
||||
$this->push(self::PUSH_CHANGE_ROOM_LABEL, $topic, $data);
|
||||
}
|
||||
|
||||
}
|
||||
48
application/common/controller/Upload.php
Normal file
48
application/common/controller/Upload.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use OSS\Credentials\EnvironmentVariableCredentialsProvider;
|
||||
use OSS\OssClient;
|
||||
use OSS\Core\OssException;
|
||||
|
||||
class Upload
|
||||
{
|
||||
// 显式声明属性
|
||||
private $config;
|
||||
private $ossClient; // 修复点:添加该属性声明
|
||||
private $bucket;
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = get_system_config();
|
||||
$endpoint = $this->config['oss_region_url'];
|
||||
$this->bucket= $this->config['oss_bucket_name'];
|
||||
//获取配置
|
||||
$this->ossClient = new OssClient(
|
||||
$this->config['oss_access_key_id'],
|
||||
$this->config['oss_access_key_secret'],
|
||||
$endpoint
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* 上传文件
|
||||
* 参数:
|
||||
* $bucket: 存储空间名称
|
||||
* $object: 文件名
|
||||
* $filePath: 文件路径
|
||||
* 返回:
|
||||
* true: 上传成功
|
||||
* false: 上传失败
|
||||
*/
|
||||
public function uploadFile($object, $filePath) {
|
||||
try {
|
||||
$result = $this->ossClient->uploadFile($this->bucket, $object, $filePath);
|
||||
return true; // 上传成功返回true
|
||||
} catch (OssException $e) {
|
||||
return false; // 上传失败返回false并记录错误信息,例如:echo $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
622
application/common/controller/adminApi.php
Normal file
622
application/common/controller/adminApi.php
Normal file
@@ -0,0 +1,622 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\admin\library\Auth;
|
||||
use think\Config;
|
||||
use think\Controller;
|
||||
use think\Hook;
|
||||
use think\Lang;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
use think\Session;
|
||||
use fast\Tree;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 后台控制器基类
|
||||
*/
|
||||
class adminApi extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* 无需登录的方法,同时也就不需要鉴权了
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = [];
|
||||
|
||||
/**
|
||||
* 无需鉴权的方法,但需要登录
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedRight = [];
|
||||
|
||||
/**
|
||||
* 布局模板
|
||||
* @var string
|
||||
*/
|
||||
protected $layout = 'default';
|
||||
|
||||
/**
|
||||
* 权限控制类
|
||||
* @var Auth
|
||||
*/
|
||||
protected $auth = null;
|
||||
|
||||
/**
|
||||
* 模型对象
|
||||
* @var \think\Model
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* 快速搜索时执行查找的字段
|
||||
*/
|
||||
protected $searchFields = 'id';
|
||||
|
||||
/**
|
||||
* 是否是关联查询
|
||||
*/
|
||||
protected $relationSearch = false;
|
||||
|
||||
/**
|
||||
* 是否开启数据限制
|
||||
* 支持auth/personal
|
||||
* 表示按权限判断/仅限个人
|
||||
* 默认为禁用,若启用请务必保证表中存在admin_id字段
|
||||
*/
|
||||
protected $dataLimit = false;
|
||||
|
||||
/**
|
||||
* 数据限制字段
|
||||
*/
|
||||
protected $dataLimitField = 'admin_id';
|
||||
|
||||
/**
|
||||
* 数据限制开启时自动填充限制字段值
|
||||
*/
|
||||
protected $dataLimitFieldAutoFill = true;
|
||||
|
||||
/**
|
||||
* 是否开启Validate验证
|
||||
*/
|
||||
protected $modelValidate = false;
|
||||
|
||||
/**
|
||||
* 是否开启模型场景验证
|
||||
*/
|
||||
protected $modelSceneValidate = false;
|
||||
|
||||
/**
|
||||
* Multi方法可批量修改的字段
|
||||
*/
|
||||
protected $multiFields = 'status';
|
||||
|
||||
/**
|
||||
* Selectpage可显示的字段
|
||||
*/
|
||||
protected $selectpageFields = '*';
|
||||
|
||||
/**
|
||||
* 前台提交过来,需要排除的字段数据
|
||||
*/
|
||||
protected $excludeFields = "";
|
||||
|
||||
/**
|
||||
* 导入文件首行类型
|
||||
* 支持comment/name
|
||||
* 表示注释或字段名
|
||||
*/
|
||||
protected $importHeadType = 'comment';
|
||||
|
||||
/**
|
||||
* 引入后台控制器的traits
|
||||
*/
|
||||
use \app\admin\library\traits\Backend;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
//只记录POST请求的日志
|
||||
if (request()->isPost() && config('myadmin.auto_record_log')) {
|
||||
$this_auth_rule_name = db('auth_rule')->where('name', substr(xss_clean(strip_tags(request()->url())), 0, 1500))->value('title');
|
||||
if(empty($this_auth_rule_name)){
|
||||
$this_auth_rule_name = '未知操作';
|
||||
}
|
||||
\app\admin\model\AdminLog::record($this_auth_rule_name);
|
||||
}
|
||||
$modulename = $this->request->module();
|
||||
$controllername = Loader::parseName($this->request->controller());
|
||||
$actionname = strtolower($this->request->action());
|
||||
$path = '/' .$modulename.'/' .str_replace('.', '/', $controllername) . '/' . $actionname;
|
||||
// 定义是否Addtabs请求
|
||||
!defined('IS_ADDTABS') && define('IS_ADDTABS', (bool)input("addtabs"));
|
||||
|
||||
// 定义是否Dialog请求
|
||||
!defined('IS_DIALOG') && define('IS_DIALOG', (bool)input("dialog"));
|
||||
|
||||
// 定义是否AJAX请求
|
||||
!defined('IS_AJAX') && define('IS_AJAX', $this->request->isAjax());
|
||||
|
||||
// 检测IP是否允许
|
||||
check_ip_allowed();
|
||||
|
||||
$this->auth = Auth::instance();
|
||||
|
||||
// 设置当前请求的URI
|
||||
$this->auth->setRequestUri($path);
|
||||
// 检测是否需要验证登录
|
||||
if (!$this->auth->match($this->noNeedLogin)) {
|
||||
//获取 token
|
||||
//通过头部信息获取authorization0
|
||||
$token = $this->request->server('HTTP_AUTHORIZATION', $this->request->request('token', \think\Cookie::get('token')));
|
||||
//检测是否登录
|
||||
if (!$this->auth->isLogin($token)) {
|
||||
Hook::listen('admin_nologin', $this);
|
||||
$url = Session::get('referer');
|
||||
$url = $url ? $url : $this->request->url();
|
||||
if (in_array($this->request->pathinfo(), ['/', 'index/index'])) {
|
||||
return V(301,"请登录后操作", url('index/login', ['url' => $url]));
|
||||
exit;
|
||||
}
|
||||
return V(301,"请登录后操作", url('index/login', ['url' => $url]));
|
||||
}
|
||||
// 判断是否需要验证权限
|
||||
if (!$this->auth->match($this->noNeedRight)) {
|
||||
// 判断控制器和方法是否有对应权限
|
||||
if (!$this->auth->check($path)) {
|
||||
Hook::listen('admin_nopermission', $this);
|
||||
return V(302,"你没有权限访问", url('index/login', []));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 非选项卡时重定向
|
||||
if (!$this->request->isPost() && !IS_AJAX && !IS_ADDTABS && !IS_DIALOG && input("ref") == 'addtabs') {
|
||||
$url = preg_replace_callback("/([\?|&]+)ref=addtabs(&?)/i", function ($matches) {
|
||||
return $matches[2] == '&' ? $matches[1] : '';
|
||||
}, $this->request->url());
|
||||
if (Config::get('url_domain_deploy')) {
|
||||
if (stripos($url, $this->request->server('SCRIPT_NAME')) === 0) {
|
||||
$url = substr($url, strlen($this->request->server('SCRIPT_NAME')));
|
||||
}
|
||||
$url = url($url, '', false);
|
||||
}
|
||||
$this->redirect('index/index', [], 302, ['referer' => $url]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 设置面包屑导航数据
|
||||
$breadcrumb = [];
|
||||
if (!IS_DIALOG && !config('fastadmin.multiplenav') && config('fastadmin.breadcrumb')) {
|
||||
$breadcrumb = $this->auth->getBreadCrumb($path);
|
||||
array_pop($breadcrumb);
|
||||
}
|
||||
$this->view->breadcrumb = $breadcrumb;
|
||||
|
||||
// 如果有使用模板布局
|
||||
if ($this->layout) {
|
||||
$this->view->engine->layout('layout/' . $this->layout);
|
||||
}
|
||||
|
||||
// 语言检测
|
||||
$lang = $this->request->langset();
|
||||
$lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn';
|
||||
|
||||
$site = Config::get("site");
|
||||
$upload = \app\common\model\Config::upload();
|
||||
|
||||
// 上传信息配置后
|
||||
Hook::listen("upload_config_init", $upload);
|
||||
|
||||
// 配置信息
|
||||
$config = [
|
||||
'site' => array_intersect_key($site, array_flip(['name', 'indexurl', 'cdnurl', 'version', 'timezone', 'languages'])),
|
||||
'upload' => $upload,
|
||||
'modulename' => $modulename,
|
||||
'controllername' => $controllername,
|
||||
'actionname' => $actionname,
|
||||
'jsname' => 'backend/' . str_replace('.', '/', $controllername),
|
||||
'moduleurl' => rtrim(url("/{$modulename}", '', false), '/'),
|
||||
'language' => $lang,
|
||||
'referer' => Session::get("referer")
|
||||
];
|
||||
$config = array_merge($config, Config::get("view_replace_str"));
|
||||
|
||||
Config::set('upload', array_merge(Config::get('upload'), $upload));
|
||||
|
||||
// 配置信息后
|
||||
Hook::listen("config_init", $config);
|
||||
//加载当前控制器语言包
|
||||
$this->loadlang($controllername);
|
||||
//渲染站点配置
|
||||
$this->assign('site', $site);
|
||||
//渲染配置信息
|
||||
$this->assign('config', $config);
|
||||
//渲染权限对象
|
||||
$this->assign('auth', $this->auth);
|
||||
//渲染管理员对象
|
||||
$this->assign('admin', Session::get('admin'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载语言文件
|
||||
* @param string $name
|
||||
*/
|
||||
protected function loadlang($name)
|
||||
{
|
||||
$name = Loader::parseName($name);
|
||||
$name = preg_match("/^([a-zA-Z0-9_\.\/]+)\$/i", $name) ? $name : 'index';
|
||||
$lang = $this->request->langset();
|
||||
$lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn';
|
||||
Lang::load(APP_PATH . $this->request->module() . '/lang/' . $lang . '/' . str_replace('.', '/', $name) . '.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染配置信息
|
||||
* @param mixed $name 键名或数组
|
||||
* @param mixed $value 值
|
||||
*/
|
||||
protected function assignconfig($name, $value = '')
|
||||
{
|
||||
$this->view->config = array_merge($this->view->config ? $this->view->config : [], is_array($name) ? $name : [$name => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成查询所需要的条件,排序方式
|
||||
* @param mixed $searchfields 快速查询的字段
|
||||
* @param boolean $relationSearch 是否关联查询
|
||||
* @return array
|
||||
*/
|
||||
protected function buildparams($searchfields = null, $relationSearch = null)
|
||||
{
|
||||
$searchfields = is_null($searchfields) ? $this->searchFields : $searchfields;
|
||||
$relationSearch = is_null($relationSearch) ? $this->relationSearch : $relationSearch;
|
||||
$search = $this->request->get("search", '');
|
||||
$filter = $this->request->get("filter", '');
|
||||
$op = $this->request->get("op", '', 'trim');
|
||||
$sort = $this->request->get("sort", !empty($this->model) && $this->model->getPk() ? $this->model->getPk() : 'id');
|
||||
$order = $this->request->get("order", "DESC");
|
||||
$offset = max(0, $this->request->get("offset/d", 0));
|
||||
$limit = max(0, $this->request->get("limit/d", 0));
|
||||
$limit = $limit ?: 999999;
|
||||
//新增自动计算页码
|
||||
$page = $limit ? intval($offset / $limit) + 1 : 1;
|
||||
if ($this->request->has("page")) {
|
||||
$page = max(0, $this->request->get("page/d", 1));
|
||||
}
|
||||
$this->request->get([config('paginate.var_page') => $page]);
|
||||
$filter = (array)json_decode($filter, true);
|
||||
$op = (array)json_decode($op, true);
|
||||
$filter = $filter ? $filter : [];
|
||||
$where = [];
|
||||
$alias = [];
|
||||
$bind = [];
|
||||
$name = '';
|
||||
$aliasName = '';
|
||||
if (!empty($this->model) && $relationSearch) {
|
||||
$name = $this->model->getTable();
|
||||
$alias[$name] = Loader::parseName(basename(str_replace('\\', '/', get_class($this->model))));
|
||||
$aliasName = $alias[$name] . '.';
|
||||
}
|
||||
$sortArr = explode(',', $sort);
|
||||
foreach ($sortArr as $index => & $item) {
|
||||
$item = stripos($item, ".") === false ? $aliasName . trim($item) : $item;
|
||||
}
|
||||
unset($item);
|
||||
$sort = implode(',', $sortArr);
|
||||
$adminIds = $this->getDataLimitAdminIds();
|
||||
if (is_array($adminIds)) {
|
||||
$where[] = [$aliasName . $this->dataLimitField, 'in', $adminIds];
|
||||
}
|
||||
if ($search) {
|
||||
$searcharr = is_array($searchfields) ? $searchfields : explode(',', $searchfields);
|
||||
foreach ($searcharr as $k => &$v) {
|
||||
$v = stripos($v, ".") === false ? $aliasName . $v : $v;
|
||||
}
|
||||
unset($v);
|
||||
$where[] = [implode("|", $searcharr), "LIKE", "%{$search}%"];
|
||||
}
|
||||
$index = 0;
|
||||
foreach ($filter as $k => $v) {
|
||||
if (!preg_match('/^[a-zA-Z0-9_\-\.]+$/', $k)) {
|
||||
continue;
|
||||
}
|
||||
$sym = $op[$k] ?? '=';
|
||||
if (stripos($k, ".") === false) {
|
||||
$k = $aliasName . $k;
|
||||
}
|
||||
$v = !is_array($v) ? trim($v) : $v;
|
||||
$sym = strtoupper($op[$k] ?? $sym);
|
||||
//null和空字符串特殊处理
|
||||
if (!is_array($v)) {
|
||||
if (in_array(strtoupper($v), ['NULL', 'NOT NULL'])) {
|
||||
$sym = strtoupper($v);
|
||||
}
|
||||
if (in_array($v, ['""', "''"])) {
|
||||
$v = '';
|
||||
$sym = '=';
|
||||
}
|
||||
}
|
||||
|
||||
switch ($sym) {
|
||||
case '=':
|
||||
case '<>':
|
||||
$where[] = [$k, $sym, (string)$v];
|
||||
break;
|
||||
case 'LIKE':
|
||||
case 'NOT LIKE':
|
||||
case 'LIKE %...%':
|
||||
case 'NOT LIKE %...%':
|
||||
$where[] = [$k, trim(str_replace('%...%', '', $sym)), "%{$v}%"];
|
||||
break;
|
||||
case '>':
|
||||
case '>=':
|
||||
case '<':
|
||||
case '<=':
|
||||
$where[] = [$k, $sym, intval($v)];
|
||||
break;
|
||||
case 'FINDIN':
|
||||
case 'FINDINSET':
|
||||
case 'FIND_IN_SET':
|
||||
$v = is_array($v) ? $v : explode(',', str_replace(' ', ',', $v));
|
||||
$findArr = array_values($v);
|
||||
foreach ($findArr as $idx => $item) {
|
||||
$bindName = "item_" . $index . "_" . $idx;
|
||||
$bind[$bindName] = $item;
|
||||
$where[] = "FIND_IN_SET(:{$bindName}, `" . str_replace('.', '`.`', $k) . "`)";
|
||||
}
|
||||
break;
|
||||
case 'IN':
|
||||
case 'IN(...)':
|
||||
case 'NOT IN':
|
||||
case 'NOT IN(...)':
|
||||
$where[] = [$k, str_replace('(...)', '', $sym), is_array($v) ? $v : explode(',', $v)];
|
||||
break;
|
||||
case 'BETWEEN':
|
||||
case 'NOT BETWEEN':
|
||||
$arr = array_slice(explode(',', $v), 0, 2);
|
||||
if (stripos($v, ',') === false || !array_filter($arr, function ($v) {
|
||||
return $v != '' && $v !== false && $v !== null;
|
||||
})) {
|
||||
continue 2;
|
||||
}
|
||||
//当出现一边为空时改变操作符
|
||||
if ($arr[0] === '') {
|
||||
$sym = $sym == 'BETWEEN' ? '<=' : '>';
|
||||
$arr = $arr[1];
|
||||
} elseif ($arr[1] === '') {
|
||||
$sym = $sym == 'BETWEEN' ? '>=' : '<';
|
||||
$arr = $arr[0];
|
||||
}
|
||||
$where[] = [$k, $sym, $arr];
|
||||
break;
|
||||
case 'RANGE':
|
||||
case 'NOT RANGE':
|
||||
$v = str_replace(' - ', ',', $v);
|
||||
$arr = array_slice(explode(',', $v), 0, 2);
|
||||
if (stripos($v, ',') === false || !array_filter($arr)) {
|
||||
continue 2;
|
||||
}
|
||||
//当出现一边为空时改变操作符
|
||||
if ($arr[0] === '') {
|
||||
$sym = $sym == 'RANGE' ? '<=' : '>';
|
||||
$arr = $arr[1];
|
||||
} elseif ($arr[1] === '') {
|
||||
$sym = $sym == 'RANGE' ? '>=' : '<';
|
||||
$arr = $arr[0];
|
||||
}
|
||||
$tableArr = explode('.', $k);
|
||||
if (count($tableArr) > 1 && $tableArr[0] != $name && !in_array($tableArr[0], $alias)
|
||||
&& !empty($this->model) && $this->relationSearch) {
|
||||
//修复关联模型下时间无法搜索的BUG
|
||||
$relation = Loader::parseName($tableArr[0], 1, false);
|
||||
$alias[$this->model->$relation()->getTable()] = $tableArr[0];
|
||||
}
|
||||
$where[] = [$k, str_replace('RANGE', 'BETWEEN', $sym) . ' TIME', $arr];
|
||||
break;
|
||||
case 'NULL':
|
||||
case 'IS NULL':
|
||||
case 'NOT NULL':
|
||||
case 'IS NOT NULL':
|
||||
$where[] = [$k, strtolower(str_replace('IS ', '', $sym))];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
if (!empty($this->model)) {
|
||||
$this->model->alias($alias);
|
||||
}
|
||||
$model = $this->model;
|
||||
$where = function ($query) use ($where, $alias, $bind, &$model) {
|
||||
if (!empty($model)) {
|
||||
$model->alias($alias);
|
||||
$model->bind($bind);
|
||||
}
|
||||
foreach ($where as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
call_user_func_array([$query, 'where'], $v);
|
||||
} else {
|
||||
$query->where($v);
|
||||
}
|
||||
}
|
||||
};
|
||||
return [$where, $sort, $order, $offset, $limit, $page, $alias, $bind];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据限制的管理员ID
|
||||
* 禁用数据限制时返回的是null
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getDataLimitAdminIds()
|
||||
{
|
||||
if (!$this->dataLimit) {
|
||||
return null;
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return null;
|
||||
}
|
||||
$adminIds = [];
|
||||
if (in_array($this->dataLimit, ['auth', 'personal'])) {
|
||||
$adminIds = $this->dataLimit == 'auth' ? $this->auth->getChildrenAdminIds(true) : [$this->auth->id];
|
||||
}
|
||||
return $adminIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selectpage的实现方法
|
||||
*
|
||||
* 当前方法只是一个比较通用的搜索匹配,请按需重载此方法来编写自己的搜索逻辑,$where按自己的需求写即可
|
||||
* 这里示例了所有的参数,所以比较复杂,实现上自己实现只需简单的几行即可
|
||||
*
|
||||
*/
|
||||
protected function selectpage()
|
||||
{
|
||||
//设置过滤方法
|
||||
$this->request->filter(['trim', 'strip_tags', 'htmlspecialchars']);
|
||||
|
||||
//搜索关键词,客户端输入以空格分开,这里接收为数组
|
||||
$word = (array)$this->request->request("q_word/a");
|
||||
//当前页
|
||||
$page = $this->request->request("pageNumber");
|
||||
//分页大小
|
||||
$pagesize = $this->request->request("pageSize");
|
||||
//搜索条件
|
||||
$andor = $this->request->request("andOr", "and", "strtoupper");
|
||||
//排序方式
|
||||
$orderby = (array)$this->request->request("orderBy/a");
|
||||
//显示的字段
|
||||
$field = $this->request->request("showField");
|
||||
//主键
|
||||
$primarykey = $this->request->request("keyField");
|
||||
//主键值
|
||||
$primaryvalue = $this->request->request("keyValue");
|
||||
//搜索字段
|
||||
$searchfield = (array)$this->request->request("searchField/a");
|
||||
//自定义搜索条件
|
||||
$custom = (array)$this->request->request("custom/a");
|
||||
//是否返回树形结构
|
||||
$istree = $this->request->request("isTree", 0);
|
||||
$ishtml = $this->request->request("isHtml", 0);
|
||||
if ($istree) {
|
||||
$word = [];
|
||||
$pagesize = 999999;
|
||||
}
|
||||
$order = [];
|
||||
foreach ($orderby as $k => $v) {
|
||||
$order[$v[0]] = $v[1];
|
||||
}
|
||||
$field = $field ? $field : 'name';
|
||||
|
||||
//如果有primaryvalue,说明当前是初始化传值
|
||||
if ($primaryvalue !== null) {
|
||||
$where = [$primarykey => ['in', $primaryvalue]];
|
||||
$pagesize = 999999;
|
||||
} else {
|
||||
$where = function ($query) use ($word, $andor, $field, $searchfield, $custom) {
|
||||
$logic = $andor == 'AND' ? '&' : '|';
|
||||
$searchfield = is_array($searchfield) ? implode($logic, $searchfield) : $searchfield;
|
||||
$searchfield = str_replace(',', $logic, $searchfield);
|
||||
$word = array_filter(array_unique($word));
|
||||
if (count($word) == 1) {
|
||||
$query->where($searchfield, "like", "%" . reset($word) . "%");
|
||||
} else {
|
||||
$query->where(function ($query) use ($word, $searchfield) {
|
||||
foreach ($word as $index => $item) {
|
||||
$query->whereOr(function ($query) use ($item, $searchfield) {
|
||||
$query->where($searchfield, "like", "%{$item}%");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($custom && is_array($custom)) {
|
||||
foreach ($custom as $k => $v) {
|
||||
if (is_array($v) && 2 == count($v)) {
|
||||
$query->where($k, trim($v[0]), $v[1]);
|
||||
} else {
|
||||
$query->where($k, '=', $v);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
$adminIds = $this->getDataLimitAdminIds();
|
||||
if (is_array($adminIds)) {
|
||||
$this->model->where($this->dataLimitField, 'in', $adminIds);
|
||||
}
|
||||
$list = [];
|
||||
$total = $this->model->where($where)->count();
|
||||
if ($total > 0) {
|
||||
if (is_array($adminIds)) {
|
||||
$this->model->where($this->dataLimitField, 'in', $adminIds);
|
||||
}
|
||||
|
||||
$fields = is_array($this->selectpageFields) ? $this->selectpageFields : ($this->selectpageFields && $this->selectpageFields != '*' ? explode(',', $this->selectpageFields) : []);
|
||||
|
||||
//如果有primaryvalue,说明当前是初始化传值,按照选择顺序排序
|
||||
if ($primaryvalue !== null && preg_match("/^[a-z0-9_\-]+$/i", $primarykey)) {
|
||||
$primaryvalue = array_unique(is_array($primaryvalue) ? $primaryvalue : explode(',', $primaryvalue));
|
||||
//修复自定义data-primary-key为字符串内容时,给排序字段添加上引号
|
||||
$primaryvalue = array_map(function ($value) {
|
||||
return '\'' . $value . '\'';
|
||||
}, $primaryvalue);
|
||||
|
||||
$primaryvalue = implode(',', $primaryvalue);
|
||||
|
||||
$this->model->orderRaw("FIELD(`{$primarykey}`, {$primaryvalue})");
|
||||
} else {
|
||||
$this->model->order($order);
|
||||
}
|
||||
|
||||
$datalist = $this->model->where($where)
|
||||
->page($page, $pagesize)
|
||||
->select();
|
||||
|
||||
foreach ($datalist as $index => $item) {
|
||||
unset($item['password'], $item['salt']);
|
||||
if ($this->selectpageFields == '*') {
|
||||
$result = [
|
||||
$primarykey => $item[$primarykey] ?? '',
|
||||
$field => $item[$field] ?? '',
|
||||
];
|
||||
} else {
|
||||
$result = array_intersect_key(($item instanceof Model ? $item->toArray() : (array)$item), array_flip($fields));
|
||||
}
|
||||
$result['pid'] = isset($item['pid']) ? $item['pid'] : (isset($item['parent_id']) ? $item['parent_id'] : 0);
|
||||
$result = array_map("htmlentities", $result);
|
||||
$list[] = $result;
|
||||
}
|
||||
if ($istree && !$primaryvalue) {
|
||||
$tree = Tree::instance();
|
||||
$tree->init(collection($list)->toArray(), 'pid');
|
||||
$list = $tree->getTreeList($tree->getTreeArray(0), $field);
|
||||
if (!$ishtml) {
|
||||
foreach ($list as &$item) {
|
||||
$item = str_replace(' ', ' ', $item);
|
||||
}
|
||||
unset($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
//这里一定要返回有list这个字段,total是可选的,如果total<=list的数量,则会隐藏分页按钮
|
||||
return json(['list' => $list, 'total' => $total]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
protected function token()
|
||||
{
|
||||
$token = $this->request->param('__token__');
|
||||
|
||||
//验证Token
|
||||
if (!Validate::make()->check(['__token__' => $token], ['__token__' => 'require|token'])) {
|
||||
$this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]);
|
||||
}
|
||||
|
||||
//刷新Token
|
||||
$this->request->token();
|
||||
}
|
||||
}
|
||||
17
application/common/exception/UploadException.php
Normal file
17
application/common/exception/UploadException.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\exception;
|
||||
|
||||
use think\Exception;
|
||||
use Throwable;
|
||||
|
||||
class UploadException extends Exception
|
||||
{
|
||||
public function __construct($message = "", $code = 0, $data = [])
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->code = $code;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
}
|
||||
97
application/common/lang/zh-cn/addon.php
Normal file
97
application/common/lang/zh-cn/addon.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'addon %s not found' => '插件未找到',
|
||||
'addon %s is disabled' => '插件已禁用',
|
||||
'addon controller %s not found' => '插件控制器未找到',
|
||||
'addon action %s not found' => '插件控制器方法未找到',
|
||||
'addon can not be empty' => '插件不能为空',
|
||||
'Keep login' => '保持会话',
|
||||
'Forgot password' => '忘记密码?',
|
||||
'Username' => '用户名',
|
||||
'User id' => '会员ID',
|
||||
'Nickname' => '昵称',
|
||||
'Password' => '密码',
|
||||
'Sign up' => '注 册',
|
||||
'Sign in' => '登 录',
|
||||
'Sign out' => '退 出',
|
||||
'Guest' => '游客',
|
||||
'Welcome' => '%s,你好!',
|
||||
'Add' => '添加',
|
||||
'Edit' => '编辑',
|
||||
'Delete' => '删除',
|
||||
'Move' => '移动',
|
||||
'Name' => '名称',
|
||||
'Status' => '状态',
|
||||
'Weigh' => '权重',
|
||||
'Operate' => '操作',
|
||||
'Warning' => '温馨提示',
|
||||
'Default' => '默认',
|
||||
'Article' => '文章',
|
||||
'Page' => '单页',
|
||||
'OK' => '确定',
|
||||
'Cancel' => '取消',
|
||||
'Loading' => '加载中',
|
||||
'More' => '更多',
|
||||
'Normal' => '正常',
|
||||
'Hidden' => '隐藏',
|
||||
'Submit' => '提交',
|
||||
'Reset' => '重置',
|
||||
'Execute' => '执行',
|
||||
'Close' => '关闭',
|
||||
'Search' => '搜索',
|
||||
'Refresh' => '刷新',
|
||||
'First' => '首页',
|
||||
'Previous' => '上一页',
|
||||
'Next' => '下一页',
|
||||
'Last' => '末页',
|
||||
'None' => '无',
|
||||
'Online' => '在线',
|
||||
'Logout' => '退出',
|
||||
'Profile' => '个人资料',
|
||||
'Index' => '首页',
|
||||
'Hot' => '热门',
|
||||
'Recommend' => '推荐',
|
||||
'Dashboard' => '控制台',
|
||||
'Code' => '编号',
|
||||
'Message' => '内容',
|
||||
'Line' => '行号',
|
||||
'File' => '文件',
|
||||
'Menu' => '菜单',
|
||||
'Type' => '类型',
|
||||
'Title' => '标题',
|
||||
'Content' => '内容',
|
||||
'Append' => '追加',
|
||||
'Memo' => '备注',
|
||||
'Parent' => '父级',
|
||||
'Params' => '参数',
|
||||
'Permission' => '权限',
|
||||
'Begin time' => '开始时间',
|
||||
'End time' => '结束时间',
|
||||
'Create time' => '创建时间',
|
||||
'Flag' => '标志',
|
||||
'Home' => '首页',
|
||||
'Store' => '插件市场',
|
||||
'Services' => '服务',
|
||||
'Download' => '下载',
|
||||
'Demo' => '演示',
|
||||
'Donation' => '捐赠',
|
||||
'Forum' => '社区',
|
||||
'Docs' => '文档',
|
||||
'Go back' => '返回首页',
|
||||
'Jump now' => '立即跳转',
|
||||
'Please login first' => '请登录后再操作',
|
||||
'Send verification code' => '发送验证码',
|
||||
'Redirect now' => '立即跳转',
|
||||
'Operation completed' => '操作成功!',
|
||||
'Operation failed' => '操作失败!',
|
||||
'Unknown data format' => '未知的数据格式!',
|
||||
'Network error' => '网络错误!',
|
||||
'Advanced search' => '高级搜索',
|
||||
'Invalid parameters' => '未知参数',
|
||||
'No results were found' => '记录未找到',
|
||||
'Parameter %s can not be empty' => '参数%s不能为空',
|
||||
'You have no permission' => '你没有权限访问',
|
||||
'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中',
|
||||
'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转',
|
||||
];
|
||||
583
application/common/library/Auth.php
Normal file
583
application/common/library/Auth.php
Normal file
@@ -0,0 +1,583 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserRule;
|
||||
use fast\Random;
|
||||
use think\Config;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
use think\Hook;
|
||||
use think\Request;
|
||||
use think\Validate;
|
||||
|
||||
class Auth
|
||||
{
|
||||
protected static $instance = null;
|
||||
protected $_error = '';
|
||||
protected $_logined = false;
|
||||
protected $_user = null;
|
||||
protected $_token = '';
|
||||
//Token默认有效时长
|
||||
protected $keeptime = 2592000;
|
||||
protected $requestUri = '';
|
||||
protected $rules = [];
|
||||
//默认配置
|
||||
protected $config = [];
|
||||
protected $options = [];
|
||||
protected $allowFields = ['id', 'username', 'nickname', 'mobile', 'avatar', 'score'];
|
||||
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if ($config = Config::get('user')) {
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
$this->options = array_merge($this->config, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $options 参数
|
||||
* @return Auth
|
||||
*/
|
||||
public static function instance($options = [])
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new static($options);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取User模型
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容调用user模型的属性
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->_user ? $this->_user->$name : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容调用user模型的属性
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($this->_user) ? isset($this->_user->$name) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Token初始化
|
||||
*
|
||||
* @param string $token Token
|
||||
* @return boolean
|
||||
*/
|
||||
public function init($token)
|
||||
{
|
||||
if ($this->_logined) {
|
||||
return true;
|
||||
}
|
||||
if ($this->_error) {
|
||||
return false;
|
||||
}
|
||||
$data = Token::get($token);
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
$user_id = intval($data['user_id']);
|
||||
if ($user_id > 0) {
|
||||
$user = User::get($user_id);
|
||||
if (!$user) {
|
||||
$this->setError('Account not exist');
|
||||
return false;
|
||||
}
|
||||
if ($user['status'] != 'normal') {
|
||||
$this->setError('Account is locked');
|
||||
return false;
|
||||
}
|
||||
$this->_user = $user;
|
||||
$this->_logined = true;
|
||||
$this->_token = $token;
|
||||
|
||||
//初始化成功的事件
|
||||
Hook::listen("user_init_successed", $this->_user);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
$this->setError('You are not logged in');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册用户
|
||||
*
|
||||
* @param string $username 用户名
|
||||
* @param string $password 密码
|
||||
* @param string $email 邮箱
|
||||
* @param string $mobile 手机号
|
||||
* @param array $extend 扩展参数
|
||||
* @return boolean
|
||||
*/
|
||||
public function register($username, $password, $email = '', $mobile = '', $extend = [])
|
||||
{
|
||||
// 检测用户名、昵称、邮箱、手机号是否存在
|
||||
if (User::getByUsername($username)) {
|
||||
$this->setError('Username already exist');
|
||||
return false;
|
||||
}
|
||||
if (User::getByNickname($username)) {
|
||||
$this->setError('Nickname already exist');
|
||||
return false;
|
||||
}
|
||||
if ($email && User::getByEmail($email)) {
|
||||
$this->setError('Email already exist');
|
||||
return false;
|
||||
}
|
||||
if ($mobile && User::getByMobile($mobile)) {
|
||||
$this->setError('Mobile already exist');
|
||||
return false;
|
||||
}
|
||||
|
||||
$ip = request()->ip();
|
||||
$time = time();
|
||||
|
||||
$data = [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'email' => $email,
|
||||
'mobile' => $mobile,
|
||||
'level' => 1,
|
||||
'score' => 0,
|
||||
'avatar' => '',
|
||||
];
|
||||
$params = array_merge($data, [
|
||||
'nickname' => preg_match("/^1[3-9]{1}\d{9}$/", $username) ? substr_replace($username, '****', 3, 4) : $username,
|
||||
'salt' => Random::alnum(),
|
||||
'jointime' => $time,
|
||||
'joinip' => $ip,
|
||||
'logintime' => $time,
|
||||
'loginip' => $ip,
|
||||
'prevtime' => $time,
|
||||
'status' => 'normal'
|
||||
]);
|
||||
$params['password'] = $this->getEncryptPassword($password, $params['salt']);
|
||||
$params = array_merge($params, $extend);
|
||||
|
||||
//账号注册时需要开启事务,避免出现垃圾数据
|
||||
Db::startTrans();
|
||||
try {
|
||||
$user = User::create($params, true);
|
||||
|
||||
$this->_user = User::get($user->id);
|
||||
|
||||
//设置Token
|
||||
$this->_token = Random::uuid();
|
||||
Token::set($this->_token, $user->id, $this->keeptime);
|
||||
|
||||
//设置登录状态
|
||||
$this->_logined = true;
|
||||
|
||||
//注册成功的事件
|
||||
Hook::listen("user_register_successed", $this->_user, $data);
|
||||
Db::commit();
|
||||
} catch (Exception $e) {
|
||||
$this->setError($e->getMessage());
|
||||
Db::rollback();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @param string $account 账号,用户名、邮箱、手机号
|
||||
* @param string $password 密码
|
||||
* @return boolean
|
||||
*/
|
||||
public function login($account, $password)
|
||||
{
|
||||
$field = Validate::is($account, 'email') ? 'email' : (Validate::regex($account, '/^1\d{10}$/') ? 'mobile' : 'username');
|
||||
$user = User::get([$field => $account]);
|
||||
if (!$user) {
|
||||
$this->setError('Account is incorrect');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->status != 'normal') {
|
||||
$this->setError('Account is locked');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->loginfailure >= 10 && time() - $user->loginfailuretime < 86400) {
|
||||
$this->setError('Please try again after 1 day');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->password != $this->getEncryptPassword($password, $user->salt)) {
|
||||
$user->save(['loginfailure' => $user->loginfailure + 1, 'loginfailuretime' => time()]);
|
||||
$this->setError('Password is incorrect');
|
||||
return false;
|
||||
}
|
||||
|
||||
//直接登录会员
|
||||
return $this->direct($user->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
if (!$this->_logined) {
|
||||
$this->setError('You are not logged in');
|
||||
return false;
|
||||
}
|
||||
//设置登录标识
|
||||
$this->_logined = false;
|
||||
//删除Token
|
||||
Token::delete($this->_token);
|
||||
//退出成功的事件
|
||||
Hook::listen("user_logout_successed", $this->_user);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param string $newpassword 新密码
|
||||
* @param string $oldpassword 旧密码
|
||||
* @param bool $ignoreoldpassword 忽略旧密码
|
||||
* @return boolean
|
||||
*/
|
||||
public function changepwd($newpassword, $oldpassword = '', $ignoreoldpassword = false)
|
||||
{
|
||||
if (!$this->_logined) {
|
||||
$this->setError('You are not logged in');
|
||||
return false;
|
||||
}
|
||||
//判断旧密码是否正确
|
||||
if ($this->_user->password == $this->getEncryptPassword($oldpassword, $this->_user->salt) || $ignoreoldpassword) {
|
||||
Db::startTrans();
|
||||
try {
|
||||
$salt = Random::alnum();
|
||||
$newpassword = $this->getEncryptPassword($newpassword, $salt);
|
||||
$this->_user->save(['loginfailure' => 0, 'password' => $newpassword, 'salt' => $salt]);
|
||||
|
||||
Token::delete($this->_token);
|
||||
//修改密码成功的事件
|
||||
Hook::listen("user_changepwd_successed", $this->_user);
|
||||
Db::commit();
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
$this->setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
$this->setError('Password is incorrect');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接登录账号
|
||||
* @param int $user_id
|
||||
* @return boolean
|
||||
*/
|
||||
public function direct($user_id)
|
||||
{
|
||||
$user = User::get($user_id);
|
||||
if ($user) {
|
||||
Db::startTrans();
|
||||
try {
|
||||
$ip = request()->ip();
|
||||
$time = time();
|
||||
|
||||
//判断连续登录和最大连续登录
|
||||
if ($user->logintime < \fast\Date::unixtime('day')) {
|
||||
$user->successions = $user->logintime < \fast\Date::unixtime('day', -1) ? 1 : $user->successions + 1;
|
||||
$user->maxsuccessions = max($user->successions, $user->maxsuccessions);
|
||||
}
|
||||
|
||||
$user->prevtime = $user->logintime;
|
||||
//记录本次登录的IP和时间
|
||||
$user->loginip = $ip;
|
||||
$user->logintime = $time;
|
||||
//重置登录失败次数
|
||||
$user->loginfailure = 0;
|
||||
|
||||
$user->save();
|
||||
|
||||
$this->_user = $user;
|
||||
|
||||
$this->_token = Random::uuid();
|
||||
Token::set($this->_token, $user->id, $this->keeptime);
|
||||
|
||||
$this->_logined = true;
|
||||
|
||||
//登录成功的事件
|
||||
Hook::listen("user_login_successed", $this->_user);
|
||||
Db::commit();
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
$this->setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否是否有对应权限
|
||||
* @param string $path 控制器/方法
|
||||
* @param string $module 模块 默认为当前模块
|
||||
* @return boolean
|
||||
*/
|
||||
public function check($path = null, $module = null)
|
||||
{
|
||||
if (!$this->_logined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ruleList = $this->getRuleList();
|
||||
$rules = [];
|
||||
foreach ($ruleList as $k => $v) {
|
||||
$rules[] = $v['name'];
|
||||
}
|
||||
$url = ($module ? $module : request()->module()) . '/' . (is_null($path) ? $this->getRequestUri() : $path);
|
||||
$url = strtolower(str_replace('.', '/', $url));
|
||||
return in_array($url, $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否登录
|
||||
* @return boolean
|
||||
*/
|
||||
public function isLogin()
|
||||
{
|
||||
if ($this->_logined) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前Token
|
||||
* @return string
|
||||
*/
|
||||
public function getToken()
|
||||
{
|
||||
return $this->_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员基本信息
|
||||
*/
|
||||
public function getUserinfo()
|
||||
{
|
||||
$data = $this->_user->toArray();
|
||||
$allowFields = $this->getAllowFields();
|
||||
$userinfo = array_intersect_key($data, array_flip($allowFields));
|
||||
$userinfo = array_merge($userinfo, Token::get($this->_token));
|
||||
return $userinfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员组别规则列表
|
||||
* @return array|bool|\PDOStatement|string|\think\Collection
|
||||
*/
|
||||
public function getRuleList()
|
||||
{
|
||||
if ($this->rules) {
|
||||
return $this->rules;
|
||||
}
|
||||
$group = $this->_user->group;
|
||||
if (!$group) {
|
||||
return [];
|
||||
}
|
||||
$rules = explode(',', $group->rules);
|
||||
$this->rules = UserRule::where('status', 'normal')->where('id', 'in', $rules)->field('id,pid,name,title,ismenu')->select();
|
||||
return $this->rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求的URI
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestUri()
|
||||
{
|
||||
return $this->requestUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前请求的URI
|
||||
* @param string $uri
|
||||
*/
|
||||
public function setRequestUri($uri)
|
||||
{
|
||||
$this->requestUri = $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取允许输出的字段
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowFields()
|
||||
{
|
||||
return $this->allowFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置允许输出的字段
|
||||
* @param array $fields
|
||||
*/
|
||||
public function setAllowFields($fields)
|
||||
{
|
||||
$this->allowFields = $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个指定会员
|
||||
* @param int $user_id 会员ID
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete($user_id)
|
||||
{
|
||||
$user = User::get($user_id);
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 删除会员
|
||||
User::destroy($user_id);
|
||||
// 删除会员指定的所有Token
|
||||
Token::clear($user_id);
|
||||
|
||||
Hook::listen("user_delete_successed", $user);
|
||||
Db::commit();
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
$this->setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取密码加密后的字符串
|
||||
* @param string $password 密码
|
||||
* @param string $salt 密码盐
|
||||
* @return string
|
||||
*/
|
||||
public function getEncryptPassword($password, $salt = '')
|
||||
{
|
||||
return md5(md5($password) . $salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测当前控制器和方法是否匹配传递的数组
|
||||
*
|
||||
* @param array $arr 需要验证权限的数组
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($arr = [])
|
||||
{
|
||||
$request = Request::instance();
|
||||
$arr = is_array($arr) ? $arr : explode(',', $arr);
|
||||
if (!$arr) {
|
||||
return false;
|
||||
}
|
||||
$arr = array_map('strtolower', $arr);
|
||||
// 是否存在
|
||||
if (in_array(strtolower($request->action()), $arr) || in_array('*', $arr)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 没找到匹配
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话有效时间
|
||||
* @param int $keeptime 默认为永久
|
||||
*/
|
||||
public function keeptime($keeptime = 0)
|
||||
{
|
||||
$this->keeptime = $keeptime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染用户数据
|
||||
* @param array $datalist 二维数组
|
||||
* @param mixed $fields 加载的字段列表
|
||||
* @param string $fieldkey 渲染的字段
|
||||
* @param string $renderkey 结果字段
|
||||
* @return array
|
||||
*/
|
||||
public function render(&$datalist, $fields = [], $fieldkey = 'user_id', $renderkey = 'userinfo')
|
||||
{
|
||||
$fields = !$fields ? ['id', 'nickname', 'level', 'avatar'] : (is_array($fields) ? $fields : explode(',', $fields));
|
||||
$ids = [];
|
||||
foreach ($datalist as $k => $v) {
|
||||
if (!isset($v[$fieldkey])) {
|
||||
continue;
|
||||
}
|
||||
$ids[] = $v[$fieldkey];
|
||||
}
|
||||
$list = [];
|
||||
if ($ids) {
|
||||
if (!in_array('id', $fields)) {
|
||||
$fields[] = 'id';
|
||||
}
|
||||
$ids = array_unique($ids);
|
||||
$selectlist = User::where('id', 'in', $ids)->column($fields);
|
||||
foreach ($selectlist as $k => $v) {
|
||||
$list[$v['id']] = $v;
|
||||
}
|
||||
}
|
||||
foreach ($datalist as $k => &$v) {
|
||||
$v[$renderkey] = $list[$v[$fieldkey]] ?? null;
|
||||
}
|
||||
unset($v);
|
||||
return $datalist;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误信息
|
||||
*
|
||||
* @param string $error 错误信息
|
||||
* @return Auth
|
||||
*/
|
||||
public function setError($error)
|
||||
{
|
||||
$this->_error = $error;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @return string
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->_error ? __($this->_error) : '';
|
||||
}
|
||||
}
|
||||
236
application/common/library/Email.php
Normal file
236
application/common/library/Email.php
Normal file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use think\Config;
|
||||
use Tx\Mailer;
|
||||
use Tx\Mailer\Exceptions\CodeException;
|
||||
use Tx\Mailer\Exceptions\SendException;
|
||||
|
||||
class Email
|
||||
{
|
||||
|
||||
/**
|
||||
* 单例对象
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* phpmailer对象
|
||||
*/
|
||||
protected $mail = null;
|
||||
|
||||
/**
|
||||
* 错误内容
|
||||
*/
|
||||
protected $error = '';
|
||||
|
||||
/**
|
||||
* 默认配置
|
||||
*/
|
||||
public $options = [
|
||||
'charset' => 'utf-8', //编码格式
|
||||
'debug' => false, //调式模式
|
||||
'mail_type' => 0, //状态
|
||||
];
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @access public
|
||||
* @param array $options 参数
|
||||
* @return Email
|
||||
*/
|
||||
public static function instance($options = [])
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new static($options);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if ($config = Config::get('site')) {
|
||||
$this->options = array_merge($this->options, $config);
|
||||
}
|
||||
$this->options = array_merge($this->options, $options);
|
||||
$secureArr = [0 => '', 1 => 'tls', 2 => 'ssl'];
|
||||
$secure = $secureArr[$this->options['mail_verify_type']] ?? '';
|
||||
|
||||
$logger = isset($this->options['debug']) && $this->options['debug'] ? new Log : null;
|
||||
$this->mail = new Mailer($logger);
|
||||
$this->mail->setServer($this->options['mail_smtp_host'], $this->options['mail_smtp_port'], $secure);
|
||||
$this->mail->setAuth($this->options['mail_from'], $this->options['mail_smtp_pass']);
|
||||
|
||||
//设置发件人
|
||||
$this->from($this->options['mail_from'], $this->options['mail_smtp_user']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置邮件主题
|
||||
* @param string $subject 邮件主题
|
||||
* @return $this
|
||||
*/
|
||||
public function subject($subject)
|
||||
{
|
||||
$this->mail->setSubject($subject);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置发件人
|
||||
* @param string $email 发件人邮箱
|
||||
* @param string $name 发件人名称
|
||||
* @return $this
|
||||
*/
|
||||
public function from($email, $name = '')
|
||||
{
|
||||
$this->mail->setFrom($name, $email);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置收件人
|
||||
* @param mixed $email 收件人,多个收件人以,进行分隔
|
||||
* @return $this
|
||||
*/
|
||||
public function to($email)
|
||||
{
|
||||
$emailArr = $this->buildAddress($email);
|
||||
foreach ($emailArr as $address => $name) {
|
||||
$this->mail->addTo($name, $address);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置抄送
|
||||
* @param mixed $email 收件人,多个收件人以,进行分隔
|
||||
* @param string $name 收件人名称
|
||||
* @return Email
|
||||
*/
|
||||
public function cc($email, $name = '')
|
||||
{
|
||||
$emailArr = $this->buildAddress($email);
|
||||
if (count($emailArr) == 1 && $name) {
|
||||
$emailArr[key($emailArr)] = $name;
|
||||
}
|
||||
foreach ($emailArr as $address => $name) {
|
||||
$this->mail->addCC($name, $address);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置密送
|
||||
* @param mixed $email 收件人,多个收件人以,进行分隔
|
||||
* @param string $name 收件人名称
|
||||
* @return Email
|
||||
*/
|
||||
public function bcc($email, $name = '')
|
||||
{
|
||||
$emailArr = $this->buildAddress($email);
|
||||
if (count($emailArr) == 1 && $name) {
|
||||
$emailArr[key($emailArr)] = $name;
|
||||
}
|
||||
foreach ($emailArr as $address => $name) {
|
||||
$this->mail->addBCC($name, $address);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置邮件正文
|
||||
* @param string $body 邮件下方
|
||||
* @param boolean $ishtml 是否HTML格式
|
||||
* @return $this
|
||||
*/
|
||||
public function message($body, $ishtml = true)
|
||||
{
|
||||
$this->mail->setBody($body);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加附件
|
||||
* @param string $path 附件路径
|
||||
* @param string $name 附件名称
|
||||
* @return Email
|
||||
*/
|
||||
public function attachment($path, $name = '')
|
||||
{
|
||||
$this->mail->addAttachment($name, $path);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建Email地址
|
||||
* @param mixed $emails Email数据
|
||||
* @return array
|
||||
*/
|
||||
protected function buildAddress($emails)
|
||||
{
|
||||
if (!is_array($emails)) {
|
||||
$emails = array_flip(explode(',', str_replace(";", ",", $emails)));
|
||||
foreach ($emails as $key => $value) {
|
||||
$emails[$key] = strstr($key, '@', true);
|
||||
}
|
||||
}
|
||||
return $emails;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后产生的错误
|
||||
* @return string
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误
|
||||
* @param string $error 信息信息
|
||||
*/
|
||||
protected function setError($error)
|
||||
{
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件
|
||||
* @return boolean
|
||||
*/
|
||||
public function send()
|
||||
{
|
||||
$result = false;
|
||||
if (in_array($this->options['mail_type'], [1, 2])) {
|
||||
try {
|
||||
$result = $this->mail->send();
|
||||
} catch (SendException $e) {
|
||||
$this->setError($e->getCode() . $e->getMessage());
|
||||
} catch (CodeException $e) {
|
||||
preg_match_all("/Expected: (\d+)\, Got: (\d+)( \| (.*))?\$/i", $e->getMessage(), $matches);
|
||||
$code = $matches[2][0] ?? 0;
|
||||
$message = isset($matches[2][0]) && isset($matches[4][0]) ? $matches[4][0] : $e->getMessage();
|
||||
$message = mb_convert_encoding($message, 'UTF-8', 'GBK,GB2312,BIG5');
|
||||
$this->setError($message);
|
||||
} catch (\Exception $e) {
|
||||
$this->setError($e->getMessage());
|
||||
}
|
||||
|
||||
$this->setError($result ? '' : $this->getError());
|
||||
} else {
|
||||
//邮件功能已关闭
|
||||
$this->setError(__('Mail already closed'));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
157
application/common/library/Ems.php
Normal file
157
application/common/library/Ems.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use fast\Random;
|
||||
use think\Hook;
|
||||
|
||||
/**
|
||||
* 邮箱验证码类
|
||||
*/
|
||||
class Ems
|
||||
{
|
||||
|
||||
/**
|
||||
* 验证码有效时长
|
||||
* @var int
|
||||
*/
|
||||
protected static $expire = 120;
|
||||
|
||||
/**
|
||||
* 最大允许检测的次数
|
||||
* @var int
|
||||
*/
|
||||
protected static $maxCheckNums = 10;
|
||||
|
||||
/**
|
||||
* 获取最后一次邮箱发送的数据
|
||||
*
|
||||
* @param int $email 邮箱
|
||||
* @param string $event 事件
|
||||
* @return Ems|null
|
||||
*/
|
||||
public static function get($email, $event = 'default')
|
||||
{
|
||||
$ems = \app\common\model\Ems::where(['email' => $email, 'event' => $event])
|
||||
->order('id', 'DESC')
|
||||
->find();
|
||||
Hook::listen('ems_get', $ems, null, true);
|
||||
return $ems ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
*
|
||||
* @param int $email 邮箱
|
||||
* @param int $code 验证码,为空时将自动生成4位数字
|
||||
* @param string $event 事件
|
||||
* @return boolean
|
||||
*/
|
||||
public static function send($email, $code = null, $event = 'default')
|
||||
{
|
||||
$code = is_null($code) ? Random::numeric(config('captcha.length')) : $code;
|
||||
$time = time();
|
||||
$ip = request()->ip();
|
||||
$ems = \app\common\model\Ems::create(['event' => $event, 'email' => $email, 'code' => $code, 'ip' => $ip, 'createtime' => $time]);
|
||||
if (!Hook::get('ems_send')) {
|
||||
//采用框架默认的邮件推送
|
||||
Hook::add('ems_send', function ($params) {
|
||||
$obj = new Email();
|
||||
$result = $obj
|
||||
->to($params->email)
|
||||
->subject('请查收你的验证码!')
|
||||
->message("你的验证码是:" . $params->code . "," . ceil(self::$expire / 60) . "分钟内有效。")
|
||||
->send();
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
$result = Hook::listen('ems_send', $ems, null, true);
|
||||
if (!$result) {
|
||||
$ems->delete();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送通知
|
||||
*
|
||||
* @param mixed $email 邮箱,多个以,分隔
|
||||
* @param string $msg 消息内容
|
||||
* @param string $template 消息模板
|
||||
* @return boolean
|
||||
*/
|
||||
public static function notice($email, $msg = '', $template = null)
|
||||
{
|
||||
$params = [
|
||||
'email' => $email,
|
||||
'msg' => $msg,
|
||||
'template' => $template
|
||||
];
|
||||
if (!Hook::get('ems_notice')) {
|
||||
//采用框架默认的邮件推送
|
||||
Hook::add('ems_notice', function ($params) {
|
||||
$subject = '你收到一封新的邮件!';
|
||||
$content = $params['msg'];
|
||||
$email = new Email();
|
||||
$result = $email->to($params['email'])
|
||||
->subject($subject)
|
||||
->message($content)
|
||||
->send();
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
$result = Hook::listen('ems_notice', $params, null, true);
|
||||
return (bool)$result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
* @param int $email 邮箱
|
||||
* @param int $code 验证码
|
||||
* @param string $event 事件
|
||||
* @return boolean
|
||||
*/
|
||||
public static function check($email, $code, $event = 'default')
|
||||
{
|
||||
$time = time() - self::$expire;
|
||||
$ems = \app\common\model\Ems::where(['email' => $email, 'event' => $event])
|
||||
->order('id', 'DESC')
|
||||
->find();
|
||||
if ($ems) {
|
||||
if ($ems['createtime'] > $time && $ems['times'] <= self::$maxCheckNums) {
|
||||
$correct = $code == $ems['code'];
|
||||
if (!$correct) {
|
||||
$ems->times = $ems->times + 1;
|
||||
$ems->save();
|
||||
return false;
|
||||
} else {
|
||||
$result = Hook::listen('ems_check', $ems, null, true);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// 过期则清空该邮箱验证码
|
||||
self::flush($email, $event);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空指定邮箱验证码
|
||||
*
|
||||
* @param int $email 邮箱
|
||||
* @param string $event 事件
|
||||
* @return boolean
|
||||
*/
|
||||
public static function flush($email, $event = 'default')
|
||||
{
|
||||
\app\common\model\Ems::where(['email' => $email, 'event' => $event])
|
||||
->delete();
|
||||
Hook::listen('ems_flush');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
27
application/common/library/Log.php
Normal file
27
application/common/library/Log.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use Psr\Log\AbstractLogger;
|
||||
use think\Hook;
|
||||
|
||||
/**
|
||||
* 日志记录类
|
||||
*/
|
||||
class Log extends AbstractLogger
|
||||
{
|
||||
|
||||
/**
|
||||
* Logs with an arbitrary level.
|
||||
*
|
||||
* @param mixed $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function log($level, $message, array $context = [])
|
||||
{
|
||||
\think\Log::write($message);
|
||||
}
|
||||
}
|
||||
225
application/common/library/Menu.php
Normal file
225
application/common/library/Menu.php
Normal file
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use app\admin\model\AuthRule;
|
||||
use fast\Tree;
|
||||
use think\addons\Service;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
use think\exception\PDOException;
|
||||
|
||||
class Menu
|
||||
{
|
||||
|
||||
/**
|
||||
* 创建菜单
|
||||
* @param array $menu
|
||||
* @param mixed $parent 父类的name或pid
|
||||
*/
|
||||
public static function create($menu = [], $parent = 0)
|
||||
{
|
||||
$old = [];
|
||||
self::menuUpdate($menu, $old, $parent);
|
||||
|
||||
//菜单刷新处理
|
||||
$info = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
|
||||
preg_match('/addons\\\\([a-z0-9]+)\\\\/i', $info['class'], $matches);
|
||||
if ($matches && isset($matches[1])) {
|
||||
Menu::refresh($matches[1], $menu);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除菜单
|
||||
* @param string $name 规则name
|
||||
* @return boolean
|
||||
*/
|
||||
public static function delete($name)
|
||||
{
|
||||
$ids = self::getAuthRuleIdsByName($name);
|
||||
if (!$ids) {
|
||||
return false;
|
||||
}
|
||||
AuthRule::destroy($ids);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用菜单
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
public static function enable($name)
|
||||
{
|
||||
$ids = self::getAuthRuleIdsByName($name);
|
||||
if (!$ids) {
|
||||
return false;
|
||||
}
|
||||
AuthRule::where('id', 'in', $ids)->update(['status' => 'normal']);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用菜单
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
public static function disable($name)
|
||||
{
|
||||
$ids = self::getAuthRuleIdsByName($name);
|
||||
if (!$ids) {
|
||||
return false;
|
||||
}
|
||||
AuthRule::where('id', 'in', $ids)->update(['status' => 'hidden']);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 升级菜单
|
||||
* @param string $name 插件名称
|
||||
* @param array $menu 新菜单
|
||||
* @return bool
|
||||
*/
|
||||
public static function upgrade($name, $menu)
|
||||
{
|
||||
$ids = self::getAuthRuleIdsByName($name);
|
||||
$old = AuthRule::where('id', 'in', $ids)->select();
|
||||
$old = collection($old)->toArray();
|
||||
$old = array_column($old, null, 'name');
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
self::menuUpdate($menu, $old);
|
||||
$ids = [];
|
||||
foreach ($old as $index => $item) {
|
||||
if (!isset($item['keep'])) {
|
||||
$ids[] = $item['id'];
|
||||
}
|
||||
}
|
||||
if ($ids) {
|
||||
//旧版本的菜单需要做删除处理
|
||||
$config = Service::config($name);
|
||||
$menus = $config['menus'] ?? [];
|
||||
$where = ['id' => ['in', $ids]];
|
||||
if ($menus) {
|
||||
//必须是旧版本中的菜单,可排除用户自主创建的菜单
|
||||
$where['name'] = ['in', $menus];
|
||||
}
|
||||
AuthRule::where($where)->delete();
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
} catch (PDOException $e) {
|
||||
Db::rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
Menu::refresh($name, $menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新插件菜单配置缓存
|
||||
* @param string $name
|
||||
* @param array $menu
|
||||
*/
|
||||
public static function refresh($name, $menu = [])
|
||||
{
|
||||
if (!$menu) {
|
||||
// $menu为空时表示首次安装,首次安装需刷新插件菜单标识缓存
|
||||
$menuIds = Menu::getAuthRuleIdsByName($name);
|
||||
$menus = Db::name("auth_rule")->where('id', 'in', $menuIds)->column('name');
|
||||
} else {
|
||||
// 刷新新的菜单缓存
|
||||
$getMenus = function ($menu) use (&$getMenus) {
|
||||
$result = [];
|
||||
foreach ($menu as $index => $item) {
|
||||
$result[] = $item['name'];
|
||||
$result = array_merge($result, isset($item['sublist']) && is_array($item['sublist']) ? $getMenus($item['sublist']) : []);
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
$menus = $getMenus($menu);
|
||||
}
|
||||
|
||||
//刷新新的插件核心菜单缓存
|
||||
Service::config($name, ['menus' => $menus]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出指定名称的菜单规则
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public static function export($name)
|
||||
{
|
||||
$ids = self::getAuthRuleIdsByName($name);
|
||||
if (!$ids) {
|
||||
return [];
|
||||
}
|
||||
$menuList = [];
|
||||
$menu = AuthRule::getByName($name);
|
||||
if ($menu) {
|
||||
$ruleList = collection(AuthRule::where('id', 'in', $ids)->select())->toArray();
|
||||
$menuList = Tree::instance()->init($ruleList)->getTreeArray($menu['id']);
|
||||
}
|
||||
return $menuList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单升级
|
||||
* @param array $newMenu
|
||||
* @param array $oldMenu
|
||||
* @param int $parent
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function menuUpdate($newMenu, &$oldMenu, $parent = 0)
|
||||
{
|
||||
if (!is_numeric($parent)) {
|
||||
$parentRule = AuthRule::getByName($parent);
|
||||
$pid = $parentRule ? $parentRule['id'] : 0;
|
||||
} else {
|
||||
$pid = $parent;
|
||||
}
|
||||
$allow = array_flip(['file', 'name', 'title', 'url', 'icon', 'condition', 'remark', 'ismenu', 'menutype', 'extend', 'weigh', 'status']);
|
||||
foreach ($newMenu as $k => $v) {
|
||||
$hasChild = isset($v['sublist']) && $v['sublist'];
|
||||
$data = array_intersect_key($v, $allow);
|
||||
$data['ismenu'] = $data['ismenu'] ?? ($hasChild ? 1 : 0);
|
||||
$data['icon'] = $data['icon'] ?? ($hasChild ? 'fa fa-list' : 'fa fa-circle-o');
|
||||
$data['pid'] = $pid;
|
||||
$data['status'] = $data['status'] ?? 'normal';
|
||||
if (!isset($oldMenu[$data['name']])) {
|
||||
$menu = AuthRule::create($data);
|
||||
} else {
|
||||
$menu = $oldMenu[$data['name']];
|
||||
//更新旧菜单
|
||||
AuthRule::update($data, ['id' => $menu['id']]);
|
||||
$oldMenu[$data['name']]['keep'] = true;
|
||||
}
|
||||
if ($hasChild) {
|
||||
self::menuUpdate($v['sublist'], $oldMenu, $menu['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称获取规则IDS
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public static function getAuthRuleIdsByName($name)
|
||||
{
|
||||
$ids = [];
|
||||
$menu = AuthRule::getByName($name);
|
||||
if ($menu) {
|
||||
// 必须将结果集转换为数组
|
||||
$ruleList = collection(AuthRule::order('weigh', 'desc')->field('id,pid,name')->select())->toArray();
|
||||
// 构造菜单数据
|
||||
$ids = Tree::instance()->init($ruleList)->getChildrenIds($menu['id'], true);
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
872
application/common/library/Security.php
Normal file
872
application/common/library/Security.php
Normal file
@@ -0,0 +1,872 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* 安全过滤类
|
||||
*
|
||||
* @category Security
|
||||
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
|
||||
* @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @author EllisLab Dev Team
|
||||
*/
|
||||
class Security
|
||||
{
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* List of sanitize filename strings
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $filename_bad_chars = array(
|
||||
'../',
|
||||
'<!--',
|
||||
'-->',
|
||||
'<',
|
||||
'>',
|
||||
"'",
|
||||
'"',
|
||||
'&',
|
||||
'$',
|
||||
'#',
|
||||
'{',
|
||||
'}',
|
||||
'[',
|
||||
']',
|
||||
'=',
|
||||
';',
|
||||
'?',
|
||||
'%20',
|
||||
'%22',
|
||||
'%3c', // <
|
||||
'%253c', // <
|
||||
'%3e', // >
|
||||
'%0e', // >
|
||||
'%28', // (
|
||||
'%29', // )
|
||||
'%2528', // (
|
||||
'%26', // &
|
||||
'%24', // $
|
||||
'%3f', // ?
|
||||
'%3b', // ;
|
||||
'%3d' // =
|
||||
);
|
||||
|
||||
/**
|
||||
* Character set
|
||||
*
|
||||
* Will be overridden by the constructor.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* XSS Hash
|
||||
*
|
||||
* Random Hash for protecting URLs.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_xss_hash;
|
||||
|
||||
/**
|
||||
* List of never allowed strings
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_never_allowed_str = array(
|
||||
'document.cookie' => '[removed]',
|
||||
'(document).cookie' => '[removed]',
|
||||
'document.write' => '[removed]',
|
||||
'(document).write' => '[removed]',
|
||||
'.parentNode' => '[removed]',
|
||||
'.innerHTML' => '[removed]',
|
||||
'-moz-binding' => '[removed]',
|
||||
'<!--' => '<!--',
|
||||
'-->' => '-->',
|
||||
'<![CDATA[' => '<![CDATA[',
|
||||
'<comment>' => '<comment>',
|
||||
'<%' => '<%'
|
||||
);
|
||||
|
||||
/**
|
||||
* List of never allowed regex replacements
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_never_allowed_regex = array(
|
||||
'javascript\s*:',
|
||||
'(\(?document\)?|\(?window\)?(\.document)?)\.(location|on\w*)',
|
||||
'expression\s*(\(|&\#40;)', // CSS and IE
|
||||
'vbscript\s*:', // IE, surprise!
|
||||
'wscript\s*:', // IE
|
||||
'jscript\s*:', // IE
|
||||
'vbs\s*:', // IE
|
||||
'Redirect\s+30\d',
|
||||
"([\"'])?data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?"
|
||||
);
|
||||
|
||||
protected $options = [
|
||||
'placeholder' => '[removed]'
|
||||
];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
$this->options = array_merge($this->options, $options);
|
||||
foreach ($this->_never_allowed_str as $index => &$item) {
|
||||
$item = str_replace('[removed]', $this->options['placeholder'], $item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $options 参数
|
||||
* @return Security
|
||||
*/
|
||||
public static function instance($options = [])
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new static($options);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* XSS Clean
|
||||
*
|
||||
* Sanitizes data so that Cross Site Scripting Hacks can be
|
||||
* prevented. This method does a fair amount of work but
|
||||
* it is extremely thorough, designed to prevent even the
|
||||
* most obscure XSS attempts. Nothing is ever 100% foolproof,
|
||||
* of course, but I haven't been able to get anything passed
|
||||
* the filter.
|
||||
*
|
||||
* Note: Should only be used to deal with data upon submission.
|
||||
* It's not something that should be used for general
|
||||
* runtime processing.
|
||||
*
|
||||
* @link http://channel.bitflux.ch/wiki/XSS_Prevention
|
||||
* Based in part on some code and ideas from Bitflux.
|
||||
*
|
||||
* @link http://ha.ckers.org/xss.html
|
||||
* To help develop this script I used this great list of
|
||||
* vulnerabilities along with a few other hacks I've
|
||||
* harvested from examining vulnerabilities in other programs.
|
||||
*
|
||||
* @param string|string[] $str Input data
|
||||
* @param bool $is_image Whether the input is an image
|
||||
* @return string
|
||||
*/
|
||||
public function xss_clean($str, $is_image = false)
|
||||
{
|
||||
// Is the string an array?
|
||||
if (is_array($str)) {
|
||||
foreach ($str as $key => &$value) {
|
||||
$str[$key] = $this->xss_clean($value);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
// Remove Invisible Characters
|
||||
$str = $this->remove_invisible_characters($str);
|
||||
|
||||
/*
|
||||
* URL Decode
|
||||
*
|
||||
* Just in case stuff like this is submitted:
|
||||
*
|
||||
* <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
|
||||
*
|
||||
* Note: Use rawurldecode() so it does not remove plus signs
|
||||
*/
|
||||
if (stripos($str, '%') !== false) {
|
||||
do {
|
||||
$oldstr = $str;
|
||||
$str = rawurldecode($str);
|
||||
$str = preg_replace_callback('#%(?:\s*[0-9a-f]){2,}#i', array($this, '_urldecodespaces'), $str);
|
||||
} while ($oldstr !== $str);
|
||||
unset($oldstr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert character entities to ASCII
|
||||
*
|
||||
* This permits our tests below to work reliably.
|
||||
* We only convert entities that are within tags since
|
||||
* these are the ones that will pose security problems.
|
||||
*/
|
||||
$str = preg_replace_callback("/[^a-z0-9>]+[a-z0-9]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
|
||||
$str = preg_replace_callback('/<\w+.*/si', array($this, '_decode_entity'), $str);
|
||||
|
||||
// Remove Invisible Characters Again!
|
||||
$str = $this->remove_invisible_characters($str);
|
||||
|
||||
/*
|
||||
* Convert all tabs to spaces
|
||||
*
|
||||
* This prevents strings like this: ja vascript
|
||||
* NOTE: we deal with spaces between characters later.
|
||||
* NOTE: preg_replace was found to be amazingly slow here on
|
||||
* large blocks of data, so we use str_replace.
|
||||
*/
|
||||
$str = str_replace("\t", ' ', $str);
|
||||
|
||||
// Capture converted string for later comparison
|
||||
$converted_string = $str;
|
||||
|
||||
// Remove Strings that are never allowed
|
||||
$str = $this->_do_never_allowed($str);
|
||||
|
||||
/*
|
||||
* Makes PHP tags safe
|
||||
*
|
||||
* Note: XML tags are inadvertently replaced too:
|
||||
*
|
||||
* <?xml
|
||||
*
|
||||
* But it doesn't seem to pose a problem.
|
||||
*/
|
||||
if ($is_image === true) {
|
||||
// Images have a tendency to have the PHP short opening and
|
||||
// closing tags every so often so we skip those and only
|
||||
// do the long opening tags.
|
||||
$str = preg_replace('/<\?(php)/i', '<?\\1', $str);
|
||||
} else {
|
||||
$str = str_replace(array('<?', '?' . '>'), array('<?', '?>'), $str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compact any exploded words
|
||||
*
|
||||
* This corrects words like: j a v a s c r i p t
|
||||
* These words are compacted back to their correct state.
|
||||
*/
|
||||
$words = array(
|
||||
'javascript',
|
||||
'expression',
|
||||
'vbscript',
|
||||
'jscript',
|
||||
'wscript',
|
||||
'vbs',
|
||||
'script',
|
||||
'base64',
|
||||
'applet',
|
||||
'alert',
|
||||
'document',
|
||||
'write',
|
||||
'cookie',
|
||||
'window',
|
||||
'confirm',
|
||||
'prompt',
|
||||
'eval'
|
||||
);
|
||||
|
||||
foreach ($words as $word) {
|
||||
$word = implode('\s*', str_split($word)) . '\s*';
|
||||
|
||||
// We only want to do this when it is followed by a non-word character
|
||||
// That way valid stuff like "dealer to" does not become "dealerto"
|
||||
$str = preg_replace_callback('#(' . substr($word, 0, -3) . ')(\W)#is', array($this, '_compact_exploded_words'), $str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove disallowed Javascript in links or img tags
|
||||
* We used to do some version comparisons and use of stripos(),
|
||||
* but it is dog slow compared to these simplified non-capturing
|
||||
* preg_match(), especially if the pattern exists in the string
|
||||
*
|
||||
* Note: It was reported that not only space characters, but all in
|
||||
* the following pattern can be parsed as separators between a tag name
|
||||
* and its attributes: [\d\s"\'`;,\/\=\(\x00\x0B\x09\x0C]
|
||||
* ... however, $this->remove_invisible_characters() above already strips the
|
||||
* hex-encoded ones, so we'll skip them below.
|
||||
*/
|
||||
do {
|
||||
$original = $str;
|
||||
|
||||
if (preg_match('/<a/i', $str)) {
|
||||
$str = preg_replace_callback('#<a(?:rea)?[^a-z0-9>]+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str);
|
||||
}
|
||||
|
||||
if (preg_match('/<img/i', $str)) {
|
||||
$str = preg_replace_callback('#<img[^a-z0-9]+([^>]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str);
|
||||
}
|
||||
|
||||
if (preg_match('/script|xss/i', $str)) {
|
||||
$str = preg_replace('#</*(?:script|xss).*?>#si', $this->options['placeholder'], $str);
|
||||
}
|
||||
} while ($original !== $str);
|
||||
unset($original);
|
||||
|
||||
/*
|
||||
* Sanitize naughty HTML elements
|
||||
*
|
||||
* If a tag containing any of the words in the list
|
||||
* below is found, the tag gets converted to entities.
|
||||
*
|
||||
* So this: <blink>
|
||||
* Becomes: <blink>
|
||||
*/
|
||||
$pattern = '#'
|
||||
. '<((?<slash>/*\s*)((?<tagName>[a-z0-9]+)(?=[^a-z0-9]|$)|.+)' // tag start and name, followed by a non-tag character
|
||||
. '[^\s\042\047a-z0-9>/=]*' // a valid attribute character immediately after the tag would count as a separator
|
||||
// optional attributes
|
||||
. '(?<attributes>(?:[\s\042\047/=]*' // non-attribute characters, excluding > (tag close) for obvious reasons
|
||||
. '[^\s\042\047>/=]+' // attribute characters
|
||||
// optional attribute-value
|
||||
. '(?:\s*=' // attribute-value separator
|
||||
. '(?:[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*))' // single, double or non-quoted value
|
||||
. ')?' // end optional attribute-value group
|
||||
. ')*)' // end optional attributes group
|
||||
. '[^>]*)(?<closeTag>\>)?#isS';
|
||||
|
||||
// Note: It would be nice to optimize this for speed, BUT
|
||||
// only matching the naughty elements here results in
|
||||
// false positives and in turn - vulnerabilities!
|
||||
do {
|
||||
$old_str = $str;
|
||||
$str = preg_replace_callback($pattern, array($this, '_sanitize_naughty_html'), $str);
|
||||
} while ($old_str !== $str);
|
||||
unset($old_str);
|
||||
|
||||
/*
|
||||
* Sanitize naughty scripting elements
|
||||
*
|
||||
* Similar to above, only instead of looking for
|
||||
* tags it looks for PHP and JavaScript commands
|
||||
* that are disallowed. Rather than removing the
|
||||
* code, it simply converts the parenthesis to entities
|
||||
* rendering the code un-executable.
|
||||
*
|
||||
* For example: eval('some code')
|
||||
* Becomes: eval('some code')
|
||||
*/
|
||||
$str = preg_replace(
|
||||
'#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si',
|
||||
'\\1\\2(\\3)',
|
||||
$str
|
||||
);
|
||||
|
||||
// Same thing, but for "tag functions" (e.g. eval`some code`)
|
||||
$str = preg_replace(
|
||||
'#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)`(.*?)`#si',
|
||||
'\\1\\2`\\3`',
|
||||
$str
|
||||
);
|
||||
|
||||
// Final clean up
|
||||
// This adds a bit of extra precaution in case
|
||||
// something got through the above filters
|
||||
$str = $this->_do_never_allowed($str);
|
||||
|
||||
/*
|
||||
* Images are Handled in a Special Way
|
||||
* - Essentially, we want to know that after all of the character
|
||||
* conversion is done whether any unwanted, likely XSS, code was found.
|
||||
* If not, we return TRUE, as the image is clean.
|
||||
* However, if the string post-conversion does not matched the
|
||||
* string post-removal of XSS, then it fails, as there was unwanted XSS
|
||||
* code found and removed/changed during processing.
|
||||
*/
|
||||
if ($is_image === true) {
|
||||
return ($str === $converted_string);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* XSS Hash
|
||||
*
|
||||
* Generates the XSS hash if needed and returns it.
|
||||
*
|
||||
* @return string XSS hash
|
||||
*/
|
||||
public function xss_hash()
|
||||
{
|
||||
if ($this->_xss_hash === null) {
|
||||
$rand = $this->get_random_bytes(16);
|
||||
$this->_xss_hash = ($rand === false)
|
||||
? md5(uniqid(mt_rand(), true))
|
||||
: bin2hex($rand);
|
||||
}
|
||||
|
||||
return $this->_xss_hash;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get random bytes
|
||||
*
|
||||
* @param int $length Output length
|
||||
* @return string
|
||||
*/
|
||||
public function get_random_bytes($length)
|
||||
{
|
||||
if (empty($length) or !ctype_digit((string)$length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (function_exists('random_bytes')) {
|
||||
try {
|
||||
// The cast is required to avoid TypeError
|
||||
return random_bytes((int)$length);
|
||||
} catch (Exception $e) {
|
||||
// If random_bytes() can't do the job, we can't either ...
|
||||
// There's no point in using fallbacks.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Unfortunately, none of the following PRNGs is guaranteed to exist ...
|
||||
if (defined('MCRYPT_DEV_URANDOM') && ($output = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)) !== false) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
if (is_readable('/dev/urandom') && ($fp = fopen('/dev/urandom', 'rb')) !== false) {
|
||||
// Try not to waste entropy ...
|
||||
stream_set_chunk_size($fp, $length);
|
||||
$output = fread($fp, $length);
|
||||
fclose($fp);
|
||||
if ($output !== false) {
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
return openssl_random_pseudo_bytes($length);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* HTML Entities Decode
|
||||
*
|
||||
* A replacement for html_entity_decode()
|
||||
*
|
||||
* The reason we are not using html_entity_decode() by itself is because
|
||||
* while it is not technically correct to leave out the semicolon
|
||||
* at the end of an entity most browsers will still interpret the entity
|
||||
* correctly. html_entity_decode() does not convert entities without
|
||||
* semicolons, so we are left with our own little solution here. Bummer.
|
||||
*
|
||||
* @link https://secure.php.net/html-entity-decode
|
||||
*
|
||||
* @param string $str Input
|
||||
* @param string $charset Character set
|
||||
* @return string
|
||||
*/
|
||||
public function entity_decode($str, $charset = null)
|
||||
{
|
||||
if (strpos($str, '&') === false) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
static $_entities;
|
||||
|
||||
isset($charset) or $charset = $this->charset;
|
||||
isset($_entities) or $_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, ENT_COMPAT | ENT_HTML5, $charset));
|
||||
|
||||
do {
|
||||
$str_compare = $str;
|
||||
|
||||
// Decode standard entities, avoiding false positives
|
||||
if (preg_match_all('/&[a-z]{2,}(?![a-z;])/i', $str, $matches)) {
|
||||
$replace = array();
|
||||
$matches = array_unique(array_map('strtolower', $matches[0]));
|
||||
foreach ($matches as &$match) {
|
||||
if (($char = array_search($match . ';', $_entities, true)) !== false) {
|
||||
$replace[$match] = $char;
|
||||
}
|
||||
}
|
||||
|
||||
$str = str_replace(array_keys($replace), array_values($replace), $str);
|
||||
}
|
||||
|
||||
// Decode numeric & UTF16 two byte entities
|
||||
$str = html_entity_decode(
|
||||
preg_replace('/(&#(?:x0*[0-9a-f]{2,5}(?![0-9a-f;])|(?:0*\d{2,4}(?![0-9;]))))/iS', '$1;', $str),
|
||||
ENT_COMPAT | ENT_HTML5,
|
||||
$charset
|
||||
);
|
||||
} while ($str_compare !== $str);
|
||||
return $str;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sanitize Filename
|
||||
*
|
||||
* @param string $str Input file name
|
||||
* @param bool $relative_path Whether to preserve paths
|
||||
* @return string
|
||||
*/
|
||||
public function sanitize_filename($str, $relative_path = false)
|
||||
{
|
||||
$bad = $this->filename_bad_chars;
|
||||
|
||||
if (!$relative_path) {
|
||||
$bad[] = './';
|
||||
$bad[] = '/';
|
||||
}
|
||||
|
||||
$str = $this->remove_invisible_characters($str, false);
|
||||
|
||||
do {
|
||||
$old = $str;
|
||||
$str = str_replace($bad, '', $str);
|
||||
} while ($old !== $str);
|
||||
|
||||
return stripslashes($str);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Strip Image Tags
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public function strip_image_tags($str)
|
||||
{
|
||||
return preg_replace(
|
||||
array(
|
||||
'#<img[\s/]+.*?src\s*=\s*(["\'])([^\\1]+?)\\1.*?\>#i',
|
||||
'#<img[\s/]+.*?src\s*=\s*?(([^\s"\'=<>`]+)).*?\>#i'
|
||||
),
|
||||
'\\2',
|
||||
$str
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* URL-decode taking spaces into account
|
||||
*
|
||||
* @param array $matches
|
||||
* @return string
|
||||
*/
|
||||
protected function _urldecodespaces($matches)
|
||||
{
|
||||
$input = $matches[0];
|
||||
$nospaces = preg_replace('#\s+#', '', $input);
|
||||
return ($nospaces === $input)
|
||||
? $input
|
||||
: rawurldecode($nospaces);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Compact Exploded Words
|
||||
*
|
||||
* Callback method for xss_clean() to remove whitespace from
|
||||
* things like 'j a v a s c r i p t'.
|
||||
*
|
||||
* @param array $matches
|
||||
* @return string
|
||||
*/
|
||||
protected function _compact_exploded_words($matches)
|
||||
{
|
||||
return preg_replace('/\s+/s', '', $matches[1]) . $matches[2];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sanitize Naughty HTML
|
||||
*
|
||||
* Callback method for xss_clean() to remove naughty HTML elements.
|
||||
*
|
||||
* @param array $matches
|
||||
* @return string
|
||||
*/
|
||||
protected function _sanitize_naughty_html($matches)
|
||||
{
|
||||
static $naughty_tags = array(
|
||||
'alert',
|
||||
'area',
|
||||
'prompt',
|
||||
'confirm',
|
||||
'applet',
|
||||
'audio',
|
||||
'basefont',
|
||||
'base',
|
||||
'behavior',
|
||||
'bgsound',
|
||||
'blink',
|
||||
'body',
|
||||
'embed',
|
||||
'expression',
|
||||
'form',
|
||||
'frameset',
|
||||
'frame',
|
||||
'head',
|
||||
'html',
|
||||
'ilayer',
|
||||
'iframe',
|
||||
'input',
|
||||
'button',
|
||||
'select',
|
||||
'isindex',
|
||||
'layer',
|
||||
'link',
|
||||
'meta',
|
||||
'keygen',
|
||||
'object',
|
||||
'plaintext',
|
||||
'style',
|
||||
'script',
|
||||
'textarea',
|
||||
'title',
|
||||
'math',
|
||||
'video',
|
||||
'svg',
|
||||
'xml',
|
||||
'xss'
|
||||
);
|
||||
|
||||
static $evil_attributes = array(
|
||||
'on\w+',
|
||||
'style',
|
||||
'xmlns',
|
||||
'formaction',
|
||||
'form',
|
||||
'xlink:href',
|
||||
'FSCommand',
|
||||
'seekSegmentTime'
|
||||
);
|
||||
|
||||
// First, escape unclosed tags
|
||||
if (empty($matches['closeTag'])) {
|
||||
return '<' . $matches[1];
|
||||
} // Is the element that we caught naughty? If so, escape it
|
||||
elseif (in_array(strtolower($matches['tagName']), $naughty_tags, true)) {
|
||||
return '<' . $matches[1] . '>';
|
||||
} // For other tags, see if their attributes are "evil" and strip those
|
||||
elseif (isset($matches['attributes'])) {
|
||||
// We'll store the already filtered attributes here
|
||||
$attributes = array();
|
||||
|
||||
// Attribute-catching pattern
|
||||
$attributes_pattern = '#'
|
||||
. '(?<name>[^\s\042\047>/=]+)' // attribute characters
|
||||
// optional attribute-value
|
||||
. '(?:\s*=(?<value>[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*)))' // attribute-value separator
|
||||
. '#i';
|
||||
|
||||
// Blacklist pattern for evil attribute names
|
||||
$is_evil_pattern = '#^(' . implode('|', $evil_attributes) . ')$#i';
|
||||
|
||||
// Each iteration filters a single attribute
|
||||
do {
|
||||
// Strip any non-alpha characters that may precede an attribute.
|
||||
// Browsers often parse these incorrectly and that has been a
|
||||
// of numerous XSS issues we've had.
|
||||
$matches['attributes'] = preg_replace('#^[^a-z]+#i', '', $matches['attributes']);
|
||||
|
||||
if (!preg_match($attributes_pattern, $matches['attributes'], $attribute, PREG_OFFSET_CAPTURE)) {
|
||||
// No (valid) attribute found? Discard everything else inside the tag
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
// Is it indeed an "evil" attribute?
|
||||
preg_match($is_evil_pattern, $attribute['name'][0])
|
||||
// Or does it have an equals sign, but no value and not quoted? Strip that too!
|
||||
or (trim($attribute['value'][0]) === '')
|
||||
) {
|
||||
$attributes[] = 'xss=removed';
|
||||
} else {
|
||||
$attributes[] = $attribute[0][0];
|
||||
}
|
||||
|
||||
$matches['attributes'] = substr($matches['attributes'], $attribute[0][1] + strlen($attribute[0][0]));
|
||||
} while ($matches['attributes'] !== '');
|
||||
|
||||
$attributes = empty($attributes)
|
||||
? ''
|
||||
: ' ' . implode(' ', $attributes);
|
||||
return '<' . $matches['slash'] . $matches['tagName'] . $attributes . '>';
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* JS Link Removal
|
||||
*
|
||||
* Callback method for xss_clean() to sanitize links.
|
||||
*
|
||||
* This limits the PCRE backtracks, making it more performance friendly
|
||||
* and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
|
||||
* PHP 5.2+ on link-heavy strings.
|
||||
*
|
||||
* @param array $match
|
||||
* @return string
|
||||
*/
|
||||
protected function _js_link_removal($match)
|
||||
{
|
||||
return str_replace(
|
||||
$match[1],
|
||||
preg_replace(
|
||||
'#href=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;|`|&\#96;)|javascript:|livescript:|mocha:|charset=|window\.|\(?document\)?\.|\.cookie|<script|<xss|d\s*a\s*t\s*a\s*:)#si',
|
||||
'',
|
||||
$this->_filter_attributes($match[1])
|
||||
),
|
||||
$match[0]
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* JS Image Removal
|
||||
*
|
||||
* Callback method for xss_clean() to sanitize image tags.
|
||||
*
|
||||
* This limits the PCRE backtracks, making it more performance friendly
|
||||
* and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
|
||||
* PHP 5.2+ on image tag heavy strings.
|
||||
*
|
||||
* @param array $match
|
||||
* @return string
|
||||
*/
|
||||
protected function _js_img_removal($match)
|
||||
{
|
||||
return str_replace(
|
||||
$match[1],
|
||||
preg_replace(
|
||||
'#src=.*?(?:(?:alert|prompt|confirm|eval)(?:\(|&\#40;|`|&\#96;)|javascript:|livescript:|mocha:|charset=|window\.|\(?document\)?\.|\.cookie|<script|<xss|base64\s*,)#si',
|
||||
'',
|
||||
$this->_filter_attributes($match[1])
|
||||
),
|
||||
$match[0]
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Attribute Conversion
|
||||
*
|
||||
* @param array $match
|
||||
* @return string
|
||||
*/
|
||||
protected function _convert_attribute($match)
|
||||
{
|
||||
return str_replace(array('>', '<', '\\'), array('>', '<', '\\\\'), $match[0]);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Filter Attributes
|
||||
*
|
||||
* Filters tag attributes for consistency and safety.
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
protected function _filter_attributes($str)
|
||||
{
|
||||
$out = '';
|
||||
if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches)) {
|
||||
foreach ($matches[0] as $match) {
|
||||
$out .= preg_replace('#/\*.*?\*/#s', '', $match);
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* HTML Entity Decode Callback
|
||||
*
|
||||
* @param array $match
|
||||
* @return string
|
||||
*/
|
||||
protected function _decode_entity($match)
|
||||
{
|
||||
// Protect GET variables in URLs
|
||||
// 901119URL5918AMP18930PROTECT8198
|
||||
$match = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-/]+)|i', $this->xss_hash() . '\\1=\\2', $match[0]);
|
||||
|
||||
// Decode, then un-protect URL GET vars
|
||||
return str_replace(
|
||||
$this->xss_hash(),
|
||||
'&',
|
||||
$this->entity_decode($match, $this->charset)
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Do Never Allowed
|
||||
*
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
protected function _do_never_allowed($str)
|
||||
{
|
||||
$str = str_replace(array_keys($this->_never_allowed_str), $this->_never_allowed_str, $str);
|
||||
|
||||
foreach ($this->_never_allowed_regex as $regex) {
|
||||
$str = preg_replace('#' . $regex . '#is', $this->options['placeholder'], $str);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Invisible Characters
|
||||
*/
|
||||
public function remove_invisible_characters($str, $url_encoded = true)
|
||||
{
|
||||
$non_displayables = array();
|
||||
|
||||
// every control character except newline (dec 10),
|
||||
// carriage return (dec 13) and horizontal tab (dec 09)
|
||||
if ($url_encoded) {
|
||||
$non_displayables[] = '/%0[0-8bcef]/i'; // url encoded 00-08, 11, 12, 14, 15
|
||||
$non_displayables[] = '/%1[0-9a-f]/i'; // url encoded 16-31
|
||||
$non_displayables[] = '/%7f/i'; // url encoded 127
|
||||
}
|
||||
|
||||
$non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127
|
||||
|
||||
do {
|
||||
$str = preg_replace($non_displayables, '', $str, -1, $count);
|
||||
} while ($count);
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
132
application/common/library/Sms.php
Normal file
132
application/common/library/Sms.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use fast\Random;
|
||||
use think\Hook;
|
||||
|
||||
/**
|
||||
* 短信验证码类
|
||||
*/
|
||||
class Sms
|
||||
{
|
||||
|
||||
/**
|
||||
* 验证码有效时长
|
||||
* @var int
|
||||
*/
|
||||
protected static $expire = 120;
|
||||
|
||||
/**
|
||||
* 最大允许检测的次数
|
||||
* @var int
|
||||
*/
|
||||
protected static $maxCheckNums = 10;
|
||||
|
||||
/**
|
||||
* 获取最后一次手机发送的数据
|
||||
*
|
||||
* @param int $mobile 手机号
|
||||
* @param string $event 事件
|
||||
* @return Sms
|
||||
*/
|
||||
public static function get($mobile, $event = 'default')
|
||||
{
|
||||
$sms = \app\common\model\Sms::where(['mobile' => $mobile, 'event' => $event])
|
||||
->order('id', 'DESC')
|
||||
->find();
|
||||
Hook::listen('sms_get', $sms, null, true);
|
||||
return $sms ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
*
|
||||
* @param int $mobile 手机号
|
||||
* @param int $code 验证码,为空时将自动生成4位数字
|
||||
* @param string $event 事件
|
||||
* @return boolean
|
||||
*/
|
||||
public static function send($mobile, $code = null, $event = 'default')
|
||||
{
|
||||
$code = is_null($code) ? Random::numeric(config('captcha.length')) : $code;
|
||||
$time = time();
|
||||
$ip = request()->ip();
|
||||
$sms = \app\common\model\Sms::create(['event' => $event, 'mobile' => $mobile, 'code' => $code, 'ip' => $ip, 'createtime' => $time]);
|
||||
$result = Hook::listen('sms_send', $sms, null, true);
|
||||
if (!$result) {
|
||||
$sms->delete();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送通知
|
||||
*
|
||||
* @param mixed $mobile 手机号,多个以,分隔
|
||||
* @param string $msg 消息内容
|
||||
* @param string $template 消息模板
|
||||
* @return boolean
|
||||
*/
|
||||
public static function notice($mobile, $msg = '', $template = null)
|
||||
{
|
||||
$params = [
|
||||
'mobile' => $mobile,
|
||||
'msg' => $msg,
|
||||
'template' => $template
|
||||
];
|
||||
$result = Hook::listen('sms_notice', $params, null, true);
|
||||
return (bool)$result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
* @param int $mobile 手机号
|
||||
* @param int $code 验证码
|
||||
* @param string $event 事件
|
||||
* @return boolean
|
||||
*/
|
||||
public static function check($mobile, $code, $event = 'default')
|
||||
{
|
||||
$time = time() - self::$expire;
|
||||
$sms = \app\common\model\Sms::where(['mobile' => $mobile, 'event' => $event])
|
||||
->order('id', 'DESC')
|
||||
->find();
|
||||
if ($sms) {
|
||||
if ($sms['createtime'] > $time && $sms['times'] <= self::$maxCheckNums) {
|
||||
$correct = $code == $sms['code'];
|
||||
if (!$correct) {
|
||||
$sms->times = $sms->times + 1;
|
||||
$sms->save();
|
||||
return false;
|
||||
} else {
|
||||
$result = Hook::listen('sms_check', $sms, null, true);
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
// 过期则清空该手机验证码
|
||||
self::flush($mobile, $event);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空指定手机号验证码
|
||||
*
|
||||
* @param int $mobile 手机号
|
||||
* @param string $event 事件
|
||||
* @return boolean
|
||||
*/
|
||||
public static function flush($mobile, $event = 'default')
|
||||
{
|
||||
\app\common\model\Sms::where(['mobile' => $mobile, 'event' => $event])
|
||||
->delete();
|
||||
Hook::listen('sms_flush');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
161
application/common/library/Token.php
Normal file
161
application/common/library/Token.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use app\common\library\token\Driver;
|
||||
use think\App;
|
||||
use think\Config;
|
||||
use think\Log;
|
||||
|
||||
/**
|
||||
* Token操作类
|
||||
*/
|
||||
class Token
|
||||
{
|
||||
/**
|
||||
* @var array Token的实例
|
||||
*/
|
||||
public static $instance = [];
|
||||
|
||||
/**
|
||||
* @var object 操作句柄
|
||||
*/
|
||||
public static $handler;
|
||||
|
||||
/**
|
||||
* 连接Token驱动
|
||||
* @access public
|
||||
* @param array $options 配置数组
|
||||
* @param bool|string $name Token连接标识 true 强制重新连接
|
||||
* @return Driver
|
||||
*/
|
||||
public static function connect(array $options = [], $name = false)
|
||||
{
|
||||
$type = !empty($options['type']) ? $options['type'] : 'File';
|
||||
|
||||
if (false === $name) {
|
||||
$name = md5(serialize($options));
|
||||
}
|
||||
|
||||
if (true === $name || !isset(self::$instance[$name])) {
|
||||
$class = false === strpos($type, '\\') ?
|
||||
'\\app\\common\\library\\token\\driver\\' . ucwords($type) :
|
||||
$type;
|
||||
|
||||
// 记录初始化信息
|
||||
App::$debug && Log::record('[ TOKEN ] INIT ' . $type, 'info');
|
||||
|
||||
if (true === $name) {
|
||||
return new $class($options);
|
||||
}
|
||||
|
||||
self::$instance[$name] = new $class($options);
|
||||
}
|
||||
|
||||
return self::$instance[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动初始化Token
|
||||
* @access public
|
||||
* @param array $options 配置数组
|
||||
* @return Driver
|
||||
*/
|
||||
public static function init(array $options = [])
|
||||
{
|
||||
if (is_null(self::$handler)) {
|
||||
if (empty($options) && 'complex' == Config::get('token.type')) {
|
||||
$default = Config::get('token.default');
|
||||
// 获取默认Token配置,并连接
|
||||
$options = Config::get('token.' . $default['type']) ?: $default;
|
||||
} elseif (empty($options)) {
|
||||
$options = Config::get('token');
|
||||
}
|
||||
|
||||
self::$handler = self::connect($options);
|
||||
}
|
||||
|
||||
return self::$handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Token是否可用(check别名)
|
||||
* @access public
|
||||
* @param string $token Token标识
|
||||
* @param int $user_id 会员ID
|
||||
* @return bool
|
||||
*/
|
||||
public static function has($token, $user_id)
|
||||
{
|
||||
return self::check($token, $user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Token是否可用
|
||||
* @param string $token Token标识
|
||||
* @param int $user_id 会员ID
|
||||
* @return bool
|
||||
*/
|
||||
public static function check($token, $user_id)
|
||||
{
|
||||
return self::init()->check($token, $user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取Token
|
||||
* @access public
|
||||
* @param string $token Token标识
|
||||
* @param mixed $default 默认值
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get($token, $default = false)
|
||||
{
|
||||
return self::init()->get($token) ?: $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入Token
|
||||
* @access public
|
||||
* @param string $token Token标识
|
||||
* @param mixed $user_id 会员ID
|
||||
* @param int|null $expire 有效时间 0为永久
|
||||
* @return boolean
|
||||
*/
|
||||
public static function set($token, $user_id, $expire = null)
|
||||
{
|
||||
return self::init()->set($token, $user_id, $expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Token(delete别名)
|
||||
* @access public
|
||||
* @param string $token Token标识
|
||||
* @return boolean
|
||||
*/
|
||||
public static function rm($token)
|
||||
{
|
||||
return self::delete($token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Token
|
||||
* @param string $token 标签名
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete($token)
|
||||
{
|
||||
return self::init()->delete($token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除Token
|
||||
* @access public
|
||||
* @param int $user_id 会员ID
|
||||
* @return boolean
|
||||
*/
|
||||
public static function clear($user_id = null)
|
||||
{
|
||||
return self::init()->clear($user_id);
|
||||
}
|
||||
|
||||
}
|
||||
447
application/common/library/Upload.php
Normal file
447
application/common/library/Upload.php
Normal file
@@ -0,0 +1,447 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use app\common\exception\UploadException;
|
||||
use app\common\model\Attachment;
|
||||
use fast\Random;
|
||||
use FilesystemIterator;
|
||||
use think\Config;
|
||||
use think\File;
|
||||
use think\Hook;
|
||||
|
||||
/**
|
||||
* 文件上传类
|
||||
*/
|
||||
class Upload
|
||||
{
|
||||
protected $merging = false;
|
||||
|
||||
protected $chunkDir = null;
|
||||
|
||||
protected $config = [];
|
||||
|
||||
protected $error = '';
|
||||
|
||||
/**
|
||||
* @var File
|
||||
*/
|
||||
protected $file = null;
|
||||
protected $fileInfo = null;
|
||||
|
||||
public function __construct($file = null)
|
||||
{
|
||||
$this->config = Config::get('upload');
|
||||
$this->chunkDir = RUNTIME_PATH . 'chunks';
|
||||
if ($file) {
|
||||
$this->setFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分片目录
|
||||
* @param $dir
|
||||
*/
|
||||
public function setChunkDir($dir)
|
||||
{
|
||||
$this->chunkDir = $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件
|
||||
* @return File
|
||||
*/
|
||||
public function getFile()
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文件
|
||||
* @param $file
|
||||
* @throws UploadException
|
||||
*/
|
||||
public function setFile($file)
|
||||
{
|
||||
if (empty($file)) {
|
||||
throw new UploadException(__('No file upload or server upload limit exceeded'));
|
||||
}
|
||||
|
||||
$fileInfo = $file->getInfo();
|
||||
$suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
|
||||
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
|
||||
$fileInfo['suffix'] = $suffix;
|
||||
$fileInfo['imagewidth'] = 0;
|
||||
$fileInfo['imageheight'] = 0;
|
||||
|
||||
$this->file = $file;
|
||||
$this->fileInfo = $fileInfo;
|
||||
$this->checkExecutable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为可执行脚本
|
||||
* @return bool
|
||||
* @throws UploadException
|
||||
*/
|
||||
protected function checkExecutable()
|
||||
{
|
||||
//禁止上传以.开头的文件
|
||||
if (substr($this->fileInfo['name'], 0, 1) === '.') {
|
||||
throw new UploadException(__('Uploaded file format is limited'));
|
||||
}
|
||||
|
||||
//禁止上传PHP和HTML文件
|
||||
if (in_array($this->fileInfo['type'], ['text/x-php', 'text/html']) || in_array($this->fileInfo['suffix'], ['php', 'html', 'htm', 'phar', 'phtml']) || preg_match("/^php(.*)/i", $this->fileInfo['suffix'])) {
|
||||
throw new UploadException(__('Uploaded file format is limited'));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测文件类型
|
||||
* @return bool
|
||||
* @throws UploadException
|
||||
*/
|
||||
protected function checkMimetype()
|
||||
{
|
||||
$mimetypeArr = explode(',', strtolower($this->config['mimetype']));
|
||||
$typeArr = explode('/', $this->fileInfo['type']);
|
||||
//Mimetype值不正确
|
||||
if (stripos($this->fileInfo['type'], '/') === false) {
|
||||
throw new UploadException(__('Uploaded file format is limited'));
|
||||
}
|
||||
//验证文件后缀
|
||||
if (in_array($this->fileInfo['suffix'], $mimetypeArr) || in_array('.' . $this->fileInfo['suffix'], $mimetypeArr)
|
||||
|| in_array($typeArr[0] . "/*", $mimetypeArr) || (in_array($this->fileInfo['type'], $mimetypeArr) && stripos($this->fileInfo['type'], '/') !== false)) {
|
||||
return true;
|
||||
}
|
||||
throw new UploadException(__('Uploaded file format is limited'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否图片
|
||||
* @param bool $force
|
||||
* @return bool
|
||||
* @throws UploadException
|
||||
*/
|
||||
protected function checkImage($force = false)
|
||||
{
|
||||
//验证是否为图片文件
|
||||
if (in_array($this->fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($this->fileInfo['suffix'], ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) {
|
||||
$imgInfo = getimagesize($this->fileInfo['tmp_name']);
|
||||
if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) {
|
||||
throw new UploadException(__('Uploaded file is not a valid image'));
|
||||
}
|
||||
$this->fileInfo['imagewidth'] = $imgInfo[0] ?? 0;
|
||||
$this->fileInfo['imageheight'] = $imgInfo[1] ?? 0;
|
||||
return true;
|
||||
} else {
|
||||
return !$force;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测文件大小
|
||||
* @throws UploadException
|
||||
*/
|
||||
protected function checkSize()
|
||||
{
|
||||
preg_match('/([0-9\.]+)(\w+)/', $this->config['maxsize'], $matches);
|
||||
$size = $matches ? $matches[1] : $this->config['maxsize'];
|
||||
$type = $matches ? strtolower($matches[2]) : 'b';
|
||||
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
|
||||
$size = (int)($size * pow(1024, $typeDict[$type] ?? 0));
|
||||
if ($this->fileInfo['size'] > $size) {
|
||||
throw new UploadException(__(
|
||||
'File is too big (%sMiB), Max filesize: %sMiB.',
|
||||
round($this->fileInfo['size'] / pow(1024, 2), 2),
|
||||
round($size / pow(1024, 2), 2)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取后缀
|
||||
* @return string
|
||||
*/
|
||||
public function getSuffix()
|
||||
{
|
||||
return $this->fileInfo['suffix'] ?: 'file';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储的文件名
|
||||
* @param string $savekey 保存路径
|
||||
* @param string $filename 文件名
|
||||
* @param string $md5 文件MD5
|
||||
* @param string $category 分类
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getSavekey($savekey = null, $filename = null, $md5 = null, $category = null)
|
||||
{
|
||||
if ($filename) {
|
||||
$suffix = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
} else {
|
||||
$suffix = $this->fileInfo['suffix'] ?? '';
|
||||
}
|
||||
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
|
||||
$filename = $filename ? $filename : ($this->fileInfo['name'] ?? 'unknown');
|
||||
$filename = xss_clean(strip_tags(htmlspecialchars($filename)));
|
||||
$fileprefix = substr($filename, 0, strripos($filename, '.'));
|
||||
$md5 = $md5 ? $md5 : (isset($this->fileInfo['tmp_name']) ? md5_file($this->fileInfo['tmp_name']) : '');
|
||||
$category = $category ? $category : request()->post('category');
|
||||
$category = $category ? xss_clean($category) : 'all';
|
||||
$replaceArr = [
|
||||
'{year}' => date("Y"),
|
||||
'{mon}' => date("m"),
|
||||
'{day}' => date("d"),
|
||||
'{hour}' => date("H"),
|
||||
'{min}' => date("i"),
|
||||
'{sec}' => date("s"),
|
||||
'{random}' => Random::alnum(16),
|
||||
'{random32}' => Random::alnum(32),
|
||||
'{category}' => $category ? $category : '',
|
||||
'{filename}' => substr($filename, 0, 100),
|
||||
'{fileprefix}' => substr($fileprefix, 0, 100),
|
||||
'{suffix}' => $suffix,
|
||||
'{.suffix}' => $suffix ? '.' . $suffix : '',
|
||||
'{filemd5}' => $md5,
|
||||
];
|
||||
$savekey = $savekey ? $savekey : $this->config['savekey'];
|
||||
$savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey);
|
||||
|
||||
return $savekey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理分片文件
|
||||
* @param $chunkid
|
||||
*/
|
||||
public function clean($chunkid)
|
||||
{
|
||||
if (!preg_match('/^[a-z0-9\-]{36}$/', $chunkid)) {
|
||||
throw new UploadException(__('Invalid parameters'));
|
||||
}
|
||||
$iterator = new \GlobIterator($this->chunkDir . DS . $chunkid . '-*', FilesystemIterator::KEY_AS_FILENAME);
|
||||
$array = iterator_to_array($iterator);
|
||||
foreach ($array as $index => &$item) {
|
||||
$sourceFile = $item->getRealPath() ?: $item->getPathname();
|
||||
$item = null;
|
||||
@unlink($sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并分片文件
|
||||
* @param string $chunkid
|
||||
* @param int $chunkcount
|
||||
* @param string $filename
|
||||
* @return attachment|\think\Model
|
||||
* @throws UploadException
|
||||
*/
|
||||
public function merge($chunkid, $chunkcount, $filename)
|
||||
{
|
||||
if (!preg_match('/^[a-z0-9\-]{36}$/', $chunkid)) {
|
||||
throw new UploadException(__('Invalid parameters'));
|
||||
}
|
||||
|
||||
$filePath = $this->chunkDir . DS . $chunkid;
|
||||
|
||||
$completed = true;
|
||||
//检查所有分片是否都存在
|
||||
for ($i = 0; $i < $chunkcount; $i++) {
|
||||
if (!file_exists("{$filePath}-{$i}.part")) {
|
||||
$completed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$completed) {
|
||||
$this->clean($chunkid);
|
||||
throw new UploadException(__('Chunk file info error'));
|
||||
}
|
||||
|
||||
//如果所有文件分片都上传完毕,开始合并
|
||||
$uploadPath = $filePath;
|
||||
|
||||
if (!$destFile = @fopen($uploadPath, "wb")) {
|
||||
$this->clean($chunkid);
|
||||
throw new UploadException(__('Chunk file merge error'));
|
||||
}
|
||||
if (flock($destFile, LOCK_EX)) { // 进行排他型锁定
|
||||
for ($i = 0; $i < $chunkcount; $i++) {
|
||||
$partFile = "{$filePath}-{$i}.part";
|
||||
if (!$handle = @fopen($partFile, "rb")) {
|
||||
break;
|
||||
}
|
||||
while ($buff = fread($handle, filesize($partFile))) {
|
||||
fwrite($destFile, $buff);
|
||||
}
|
||||
@fclose($handle);
|
||||
@unlink($partFile); //删除分片
|
||||
}
|
||||
|
||||
flock($destFile, LOCK_UN);
|
||||
}
|
||||
@fclose($destFile);
|
||||
|
||||
$attachment = null;
|
||||
try {
|
||||
$file = new File($uploadPath);
|
||||
$info = [
|
||||
'name' => $filename,
|
||||
'type' => $file->getMime(),
|
||||
'tmp_name' => $uploadPath,
|
||||
'error' => 0,
|
||||
'size' => $file->getSize()
|
||||
];
|
||||
$file->setSaveName($filename)->setUploadInfo($info);
|
||||
$file->isTest(true);
|
||||
|
||||
//重新设置文件
|
||||
$this->setFile($file);
|
||||
|
||||
unset($file);
|
||||
$this->merging = true;
|
||||
|
||||
//允许大文件
|
||||
$this->config['maxsize'] = "1024G";
|
||||
|
||||
$attachment = $this->upload();
|
||||
} catch (\Exception $e) {
|
||||
@unlink($uploadPath);
|
||||
throw new UploadException($e->getMessage());
|
||||
}
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分片上传
|
||||
* @throws UploadException
|
||||
*/
|
||||
public function chunk($chunkid, $chunkindex, $chunkcount, $chunkfilesize = null, $chunkfilename = null, $direct = false)
|
||||
{
|
||||
if ($this->fileInfo['type'] != 'application/octet-stream') {
|
||||
throw new UploadException(__('Uploaded file format is limited'));
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-z0-9\-]{36}$/', $chunkid)) {
|
||||
throw new UploadException(__('Invalid parameters'));
|
||||
}
|
||||
|
||||
$destDir = RUNTIME_PATH . 'chunks';
|
||||
$fileName = $chunkid . "-" . $chunkindex . '.part';
|
||||
$destFile = $destDir . DS . $fileName;
|
||||
if (!is_dir($destDir)) {
|
||||
@mkdir($destDir, 0755, true);
|
||||
}
|
||||
if (!move_uploaded_file($this->file->getPathname(), $destFile)) {
|
||||
throw new UploadException(__('Chunk file write error'));
|
||||
}
|
||||
$file = new File($destFile);
|
||||
$info = [
|
||||
'name' => $fileName,
|
||||
'type' => $file->getMime(),
|
||||
'tmp_name' => $destFile,
|
||||
'error' => 0,
|
||||
'size' => $file->getSize()
|
||||
];
|
||||
$file->setSaveName($fileName)->setUploadInfo($info);
|
||||
$this->setFile($file);
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通上传
|
||||
* @return \app\common\model\attachment|\think\Model
|
||||
* @throws UploadException
|
||||
*/
|
||||
public function upload($savekey = null)
|
||||
{
|
||||
if (empty($this->file)) {
|
||||
throw new UploadException(__('No file upload or server upload limit exceeded'));
|
||||
}
|
||||
|
||||
$this->checkSize();
|
||||
$this->checkExecutable();
|
||||
$this->checkMimetype();
|
||||
$this->checkImage();
|
||||
|
||||
$savekey = $savekey ? $savekey : $this->getSavekey();
|
||||
$savekey = '/' . ltrim($savekey, '/');
|
||||
$uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1);
|
||||
$fileName = substr($savekey, strripos($savekey, '/') + 1);
|
||||
|
||||
$destDir = ROOT_PATH . 'public' . str_replace('/', DS, $uploadDir);
|
||||
|
||||
$sha1 = $this->file->hash();
|
||||
|
||||
//如果是合并文件
|
||||
if ($this->merging) {
|
||||
if (!$this->file->check()) {
|
||||
throw new UploadException($this->file->getError());
|
||||
}
|
||||
$destFile = $destDir . $fileName;
|
||||
$sourceFile = $this->file->getRealPath() ?: $this->file->getPathname();
|
||||
$info = $this->file->getInfo();
|
||||
$this->file = null;
|
||||
if (!is_dir($destDir)) {
|
||||
@mkdir($destDir, 0755, true);
|
||||
}
|
||||
rename($sourceFile, $destFile);
|
||||
$file = new File($destFile);
|
||||
$file->setSaveName($fileName)->setUploadInfo($info);
|
||||
} else {
|
||||
$file = $this->file->move($destDir, $fileName);
|
||||
if (!$file) {
|
||||
// 上传失败获取错误信息
|
||||
throw new UploadException($this->file->getError());
|
||||
}
|
||||
}
|
||||
$this->file = $file;
|
||||
$category = request()->post('category');
|
||||
$category = array_key_exists($category, config('site.attachmentcategory') ?? []) ? $category : '';
|
||||
$auth = Auth::instance();
|
||||
$params = array(
|
||||
'admin_id' => (int)session('admin.id'),
|
||||
'user_id' => (int)$auth->id,
|
||||
'filename' => mb_substr(htmlspecialchars(strip_tags($this->fileInfo['name'])), 0, 100),
|
||||
'category' => $category,
|
||||
'filesize' => $this->fileInfo['size'],
|
||||
'imagewidth' => $this->fileInfo['imagewidth'],
|
||||
'imageheight' => $this->fileInfo['imageheight'],
|
||||
'imagetype' => $this->fileInfo['suffix'],
|
||||
'imageframes' => 0,
|
||||
'mimetype' => $this->fileInfo['type'],
|
||||
'url' => $uploadDir . $file->getSaveName(),
|
||||
'uploadtime' => time(),
|
||||
'storage' => 'local',
|
||||
'sha1' => $sha1,
|
||||
'extparam' => '',
|
||||
);
|
||||
$attachment = new Attachment();
|
||||
$attachment->data(array_filter($params));
|
||||
$attachment->save();
|
||||
|
||||
\think\Hook::listen("upload_after", $attachment);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误信息
|
||||
* @param $msg
|
||||
*/
|
||||
public function setError($msg)
|
||||
{
|
||||
$this->error = $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @return string
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
}
|
||||
92
application/common/library/token/Driver.php
Normal file
92
application/common/library/token/Driver.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?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>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\common\library\token;
|
||||
|
||||
/**
|
||||
* Token基础类
|
||||
*/
|
||||
abstract class Driver
|
||||
{
|
||||
protected $handler = null;
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* 存储Token
|
||||
* @param string $token Token
|
||||
* @param int $user_id 会员ID
|
||||
* @param int $expire 过期时长,0表示无限,单位秒
|
||||
* @return bool
|
||||
*/
|
||||
abstract function set($token, $user_id, $expire = 0);
|
||||
|
||||
/**
|
||||
* 获取Token内的信息
|
||||
* @param string $token
|
||||
* @return array
|
||||
*/
|
||||
abstract function get($token);
|
||||
|
||||
/**
|
||||
* 判断Token是否可用
|
||||
* @param string $token Token
|
||||
* @param int $user_id 会员ID
|
||||
* @return boolean
|
||||
*/
|
||||
abstract function check($token, $user_id);
|
||||
|
||||
/**
|
||||
* 删除Token
|
||||
* @param string $token
|
||||
* @return boolean
|
||||
*/
|
||||
abstract function delete($token);
|
||||
|
||||
/**
|
||||
* 删除指定用户的所有Token
|
||||
* @param int $user_id
|
||||
* @return boolean
|
||||
*/
|
||||
abstract function clear($user_id);
|
||||
|
||||
/**
|
||||
* 返回句柄对象,可执行其它高级方法
|
||||
*
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function handler()
|
||||
{
|
||||
return $this->handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密后的Token
|
||||
* @param string $token Token标识
|
||||
* @return string
|
||||
*/
|
||||
protected function getEncryptedToken($token)
|
||||
{
|
||||
$config = \think\Config::get('token');
|
||||
$token = $token ?? ''; // 为兼容 php8
|
||||
return hash_hmac($config['hashalgo'], $token, $config['key']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期剩余时长
|
||||
* @param $expiretime
|
||||
* @return float|int|mixed
|
||||
*/
|
||||
protected function getExpiredIn($expiretime)
|
||||
{
|
||||
return $expiretime ? max(0, $expiretime - time()) : 365 * 86400;
|
||||
}
|
||||
}
|
||||
118
application/common/library/token/driver/Mysql.php
Normal file
118
application/common/library/token/driver/Mysql.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library\token\driver;
|
||||
|
||||
use app\common\library\token\Driver;
|
||||
|
||||
/**
|
||||
* Token操作类
|
||||
*/
|
||||
class Mysql extends Driver
|
||||
{
|
||||
|
||||
/**
|
||||
* 默认配置
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [
|
||||
'table' => 'user_token',
|
||||
'expire' => 2592000,
|
||||
'connection' => [],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $options 参数
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
if ($this->options['connection']) {
|
||||
$this->handler = \think\Db::connect($this->options['connection'])->name($this->options['table']);
|
||||
} else {
|
||||
$this->handler = \think\Db::name($this->options['table']);
|
||||
}
|
||||
$time = time();
|
||||
$tokentime = cache('tokentime');
|
||||
if (!$tokentime || $tokentime < $time - 86400) {
|
||||
cache('tokentime', $time);
|
||||
$this->handler->where('expiretime', '<', $time)->where('expiretime', '>', 0)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储Token
|
||||
* @param string $token Token
|
||||
* @param int $user_id 会员ID
|
||||
* @param int $expire 过期时长,0表示无限,单位秒
|
||||
* @return bool
|
||||
*/
|
||||
public function set($token, $user_id, $expire = null)
|
||||
{
|
||||
$expiretime = !is_null($expire) && $expire !== 0 ? time() + $expire : 0;
|
||||
$token = $this->getEncryptedToken($token);
|
||||
$this->handler->insert(['token' => $token, 'user_id' => $user_id, 'createtime' => time(), 'expiretime' => $expiretime]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Token内的信息
|
||||
* @param string $token
|
||||
* @return array
|
||||
*/
|
||||
public function get($token)
|
||||
{
|
||||
$data = $this->handler->where('token', $this->getEncryptedToken($token))->find();
|
||||
if ($data) {
|
||||
if (!$data['expiretime'] || $data['expiretime'] > time()) {
|
||||
//返回未加密的token给客户端使用
|
||||
$data['token'] = $token;
|
||||
//返回剩余有效时间
|
||||
$data['expires_in'] = $this->getExpiredIn($data['expiretime']);
|
||||
return $data;
|
||||
} else {
|
||||
self::delete($token);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Token是否可用
|
||||
* @param string $token Token
|
||||
* @param int $user_id 会员ID
|
||||
* @return boolean
|
||||
*/
|
||||
public function check($token, $user_id)
|
||||
{
|
||||
$data = $this->get($token);
|
||||
return $data && $data['user_id'] == $user_id ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Token
|
||||
* @param string $token
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete($token)
|
||||
{
|
||||
$this->handler->where('token', $this->getEncryptedToken($token))->delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定用户的所有Token
|
||||
* @param int $user_id
|
||||
* @return boolean
|
||||
*/
|
||||
public function clear($user_id)
|
||||
{
|
||||
$this->handler->where('user_id', $user_id)->delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
168
application/common/library/token/driver/Redis.php
Normal file
168
application/common/library/token/driver/Redis.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library\token\driver;
|
||||
|
||||
use app\common\library\token\Driver;
|
||||
|
||||
/**
|
||||
* Token操作类
|
||||
*/
|
||||
class Redis extends Driver
|
||||
{
|
||||
|
||||
protected $options = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6379,
|
||||
'password' => '',
|
||||
'select' => 0,
|
||||
'timeout' => 0,
|
||||
'expire' => 0,
|
||||
'persistent' => false,
|
||||
'userprefix' => 'up:',
|
||||
'tokenprefix' => 'tp:',
|
||||
];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $options 缓存参数
|
||||
* @throws \BadFunctionCallException
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!extension_loaded('redis')) {
|
||||
throw new \BadFunctionCallException('not support: redis');
|
||||
}
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
$this->handler = new \Redis;
|
||||
if ($this->options['persistent']) {
|
||||
$this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']);
|
||||
} else {
|
||||
$this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']);
|
||||
}
|
||||
|
||||
if ('' != $this->options['password']) {
|
||||
$this->handler->auth($this->options['password']);
|
||||
}
|
||||
|
||||
if (0 != $this->options['select']) {
|
||||
$this->handler->select($this->options['select']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密后的Token
|
||||
* @param string $token Token标识
|
||||
* @return string
|
||||
*/
|
||||
protected function getEncryptedToken($token)
|
||||
{
|
||||
$config = \think\Config::get('token');
|
||||
$token = $token ?? ''; // 为兼容 php8
|
||||
return $this->options['tokenprefix'] . hash_hmac($config['hashalgo'], $token, $config['key']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员的key
|
||||
* @param $user_id
|
||||
* @return string
|
||||
*/
|
||||
protected function getUserKey($user_id)
|
||||
{
|
||||
return $this->options['userprefix'] . $user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储Token
|
||||
* @param string $token Token
|
||||
* @param int $user_id 会员ID
|
||||
* @param int $expire 过期时长,0表示无限,单位秒
|
||||
* @return bool
|
||||
*/
|
||||
public function set($token, $user_id, $expire = 0)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
if ($expire instanceof \DateTime) {
|
||||
$expire = $expire->getTimestamp() - time();
|
||||
}
|
||||
$key = $this->getEncryptedToken($token);
|
||||
if ($expire) {
|
||||
$result = $this->handler->setex($key, $expire, $user_id);
|
||||
} else {
|
||||
$result = $this->handler->set($key, $user_id);
|
||||
}
|
||||
//写入会员关联的token
|
||||
$this->handler->sAdd($this->getUserKey($user_id), $key);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Token内的信息
|
||||
* @param string $token
|
||||
* @return array
|
||||
*/
|
||||
public function get($token)
|
||||
{
|
||||
$key = $this->getEncryptedToken($token);
|
||||
$value = $this->handler->get($key);
|
||||
if (is_null($value) || false === $value) {
|
||||
return [];
|
||||
}
|
||||
//获取有效期
|
||||
$expire = $this->handler->ttl($key);
|
||||
$expire = $expire < 0 ? 365 * 86400 : $expire;
|
||||
$expiretime = time() + $expire;
|
||||
//解决使用redis方式储存token时api接口Token刷新与检测因expires_in拼写错误报错的BUG
|
||||
$result = ['token' => $token, 'user_id' => $value, 'expiretime' => $expiretime, 'expires_in' => $expire];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Token是否可用
|
||||
* @param string $token Token
|
||||
* @param int $user_id 会员ID
|
||||
* @return boolean
|
||||
*/
|
||||
public function check($token, $user_id)
|
||||
{
|
||||
$data = self::get($token);
|
||||
return $data && $data['user_id'] == $user_id ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Token
|
||||
* @param string $token
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete($token)
|
||||
{
|
||||
$data = $this->get($token);
|
||||
if ($data) {
|
||||
$key = $this->getEncryptedToken($token);
|
||||
$user_id = $data['user_id'];
|
||||
$this->handler->del($key);
|
||||
$this->handler->sRem($this->getUserKey($user_id), $key);
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定用户的所有Token
|
||||
* @param int $user_id
|
||||
* @return boolean
|
||||
*/
|
||||
public function clear($user_id)
|
||||
{
|
||||
$keys = $this->handler->sMembers($this->getUserKey($user_id));
|
||||
$this->handler->del($this->getUserKey($user_id));
|
||||
$this->handler->del($keys);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
88
application/common/model/Area.php
Normal file
88
application/common/model/Area.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Cache;
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 地区数据模型
|
||||
*/
|
||||
class Area extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* 根据经纬度获取当前地区信息
|
||||
*
|
||||
* @param string $lng 经度
|
||||
* @param string $lat 纬度
|
||||
* @return Area 城市信息
|
||||
*/
|
||||
public static function getAreaFromLngLat($lng, $lat, $level = 3)
|
||||
{
|
||||
$namearr = [1 => 'geo:province', 2 => 'geo:city', 3 => 'geo:district'];
|
||||
$rangearr = [1 => 15000, 2 => 1000, 3 => 200];
|
||||
$geoname = $namearr[$level] ?? $namearr[3];
|
||||
$georange = $rangearr[$level] ?? $rangearr[3];
|
||||
// 读取范围内的ID
|
||||
$redis = Cache::store('redis')->handler();
|
||||
$georadiuslist = [];
|
||||
if (method_exists($redis, 'georadius')) {
|
||||
$georadiuslist = $redis->georadius($geoname, $lng, $lat, $georange, 'km', ['WITHDIST', 'COUNT' => 5, 'ASC']);
|
||||
}
|
||||
|
||||
if ($georadiuslist) {
|
||||
list($id, $distance) = $georadiuslist[0];
|
||||
}
|
||||
$id = isset($id) && $id ? $id : 3;
|
||||
return self::get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据经纬度获取省份
|
||||
*
|
||||
* @param string $lng 经度
|
||||
* @param string $lat 纬度
|
||||
* @return Area
|
||||
*/
|
||||
public static function getProvinceFromLngLat($lng, $lat)
|
||||
{
|
||||
$provincedata = null;
|
||||
$citydata = self::getCityFromLngLat($lng, $lat);
|
||||
if ($citydata) {
|
||||
$provincedata = self::get($citydata['pid']);
|
||||
}
|
||||
return $provincedata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据经纬度获取城市
|
||||
*
|
||||
* @param string $lng 经度
|
||||
* @param string $lat 纬度
|
||||
* @return Area
|
||||
*/
|
||||
public static function getCityFromLngLat($lng, $lat)
|
||||
{
|
||||
$citydata = null;
|
||||
$districtdata = self::getDistrictFromLngLat($lng, $lat);
|
||||
if ($districtdata) {
|
||||
$citydata = self::get($districtdata['pid']);
|
||||
}
|
||||
return $citydata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据经纬度获取地区
|
||||
*
|
||||
* @param string $lng 经度
|
||||
* @param string $lat 纬度
|
||||
* @return Area
|
||||
*/
|
||||
public static function getDistrictFromLngLat($lng, $lat)
|
||||
{
|
||||
$districtdata = self::getAreaFromLngLat($lng, $lat, 3);
|
||||
return $districtdata;
|
||||
}
|
||||
|
||||
}
|
||||
98
application/common/model/Attachment.php
Normal file
98
application/common/model/Attachment.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Attachment extends Model
|
||||
{
|
||||
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
];
|
||||
protected $append = [
|
||||
'thumb_style'
|
||||
];
|
||||
|
||||
protected static function init()
|
||||
{
|
||||
// 如果已经上传该资源,则不再记录
|
||||
self::beforeInsert(function ($model) {
|
||||
if (self::where('url', '=', $model['url'])->where('storage', $model['storage'])->find()) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
self::beforeWrite(function ($row) {
|
||||
if (isset($row['category']) && $row['category'] == 'unclassed') {
|
||||
$row['category'] = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function setUploadtimeAttr($value)
|
||||
{
|
||||
return is_numeric($value) ? $value : strtotime($value);
|
||||
}
|
||||
|
||||
public function getCategoryAttr($value)
|
||||
{
|
||||
return $value == '' ? 'unclassed' : $value;
|
||||
}
|
||||
|
||||
public function setCategoryAttr($value)
|
||||
{
|
||||
return $value == 'unclassed' ? '' : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取云储存的缩略图样式字符
|
||||
*/
|
||||
public function getThumbStyleAttr($value, $data)
|
||||
{
|
||||
if (!isset($data['storage']) || $data['storage'] == 'local') {
|
||||
return '';
|
||||
} else {
|
||||
$config = get_addon_config($data['storage']);
|
||||
if ($config && isset($config['thumbstyle'])) {
|
||||
return $config['thumbstyle'];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Mimetype列表
|
||||
* @return array
|
||||
*/
|
||||
public static function getMimetypeList()
|
||||
{
|
||||
$data = [
|
||||
"image/*" => __("Image"),
|
||||
"audio/*" => __("Audio"),
|
||||
"video/*" => __("Video"),
|
||||
"text/*" => __("Text"),
|
||||
"application/*" => __("Application"),
|
||||
"zip,rar,7z,tar" => __("Zip"),
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取定义的附件类别列表
|
||||
* @return array
|
||||
*/
|
||||
public static function getCategoryList()
|
||||
{
|
||||
$data = config('site.attachmentcategory') ?? [];
|
||||
foreach ($data as $index => &$datum) {
|
||||
$datum = __($datum);
|
||||
}
|
||||
$data['unclassed'] = __('Unclassed');
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
89
application/common/model/Category.php
Normal file
89
application/common/model/Category.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 分类模型
|
||||
*/
|
||||
class Category extends Model
|
||||
{
|
||||
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
'type_text',
|
||||
'flag_text',
|
||||
];
|
||||
|
||||
protected static function init()
|
||||
{
|
||||
self::afterInsert(function ($row) {
|
||||
if (!$row['weigh']) {
|
||||
$row->save(['weigh' => $row['id']]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function setFlagAttr($value, $data)
|
||||
{
|
||||
return is_array($value) ? implode(',', $value) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取分类类型
|
||||
* @return array
|
||||
*/
|
||||
public static function getTypeList()
|
||||
{
|
||||
$typeList = config('site.categorytype');
|
||||
foreach ($typeList as $k => &$v) {
|
||||
$v = __($v);
|
||||
}
|
||||
return $typeList;
|
||||
}
|
||||
|
||||
public function getTypeTextAttr($value, $data)
|
||||
{
|
||||
$value = $value ? $value : $data['type'];
|
||||
$list = $this->getTypeList();
|
||||
return $list[$value] ?? '';
|
||||
}
|
||||
|
||||
public function getFlagList()
|
||||
{
|
||||
return ['hot' => __('Hot'), 'index' => __('Index'), 'recommend' => __('Recommend')];
|
||||
}
|
||||
|
||||
public function getFlagTextAttr($value, $data)
|
||||
{
|
||||
$value = $value ? $value : $data['flag'];
|
||||
$valueArr = explode(',', $value);
|
||||
$list = $this->getFlagList();
|
||||
return implode(',', array_intersect_key($list, array_flip($valueArr)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取分类列表
|
||||
* @param string $type 指定类型
|
||||
* @param string $status 指定状态
|
||||
* @return array
|
||||
*/
|
||||
public static function getCategoryArray($type = null, $status = null)
|
||||
{
|
||||
$list = collection(self::where(function ($query) use ($type, $status) {
|
||||
if (!is_null($type)) {
|
||||
$query->where('type', '=', $type);
|
||||
}
|
||||
if (!is_null($status)) {
|
||||
$query->where('status', '=', $status);
|
||||
}
|
||||
})->order('weigh', 'desc')->select())->toArray();
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
227
application/common/model/Config.php
Normal file
227
application/common/model/Config.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 配置模型
|
||||
*/
|
||||
class Config extends Model
|
||||
{
|
||||
|
||||
// 表名,不含前缀
|
||||
protected $name = 'config';
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = false;
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = false;
|
||||
protected $updateTime = false;
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
'extend_html'
|
||||
];
|
||||
protected $type = [
|
||||
'setting' => 'json',
|
||||
];
|
||||
|
||||
/**
|
||||
* 读取配置类型
|
||||
* @return array
|
||||
*/
|
||||
public static function getTypeList()
|
||||
{
|
||||
$typeList = [
|
||||
'string' => __('String'),
|
||||
'password' => __('Password'),
|
||||
'text' => __('Text'),
|
||||
'editor' => __('Editor'),
|
||||
'number' => __('Number'),
|
||||
'date' => __('Date'),
|
||||
'time' => __('Time'),
|
||||
'datetime' => __('Datetime'),
|
||||
'datetimerange' => __('Datetimerange'),
|
||||
'select' => __('Select'),
|
||||
'selects' => __('Selects'),
|
||||
'image' => __('Image'),
|
||||
'images' => __('Images'),
|
||||
'file' => __('File'),
|
||||
'files' => __('Files'),
|
||||
'switch' => __('Switch'),
|
||||
'checkbox' => __('Checkbox'),
|
||||
'radio' => __('Radio'),
|
||||
'city' => __('City'),
|
||||
'selectpage' => __('Selectpage'),
|
||||
'selectpages' => __('Selectpages'),
|
||||
'array' => __('Array'),
|
||||
'custom' => __('Custom'),
|
||||
];
|
||||
return $typeList;
|
||||
}
|
||||
|
||||
public static function getRegexList()
|
||||
{
|
||||
$regexList = [
|
||||
'required' => '必选',
|
||||
'digits' => '数字',
|
||||
'letters' => '字母',
|
||||
'date' => '日期',
|
||||
'time' => '时间',
|
||||
'email' => '邮箱',
|
||||
'url' => '网址',
|
||||
'qq' => 'QQ号',
|
||||
'IDcard' => '身份证',
|
||||
'tel' => '座机电话',
|
||||
'mobile' => '手机号',
|
||||
'zipcode' => '邮编',
|
||||
'chinese' => '中文',
|
||||
'username' => '用户名',
|
||||
'password' => '密码'
|
||||
];
|
||||
return $regexList;
|
||||
}
|
||||
|
||||
public function getExtendHtmlAttr($value, $data)
|
||||
{
|
||||
$result = preg_replace_callback("/\{([a-zA-Z]+)\}/", function ($matches) use ($data) {
|
||||
if (isset($data[$matches[1]])) {
|
||||
return $data[$matches[1]];
|
||||
}
|
||||
}, $data['extend']);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取分类分组列表
|
||||
* @return array
|
||||
*/
|
||||
public static function getGroupList()
|
||||
{
|
||||
$groupList = config('site.configgroup');
|
||||
foreach ($groupList as $k => &$v) {
|
||||
$v = __($v);
|
||||
}
|
||||
return $groupList;
|
||||
}
|
||||
|
||||
public static function getArrayData($data)
|
||||
{
|
||||
if (!isset($data['value'])) {
|
||||
$result = [];
|
||||
foreach ($data as $index => $datum) {
|
||||
$result['field'][$index] = $datum['key'];
|
||||
$result['value'][$index] = $datum['value'];
|
||||
}
|
||||
$data = $result;
|
||||
}
|
||||
$fieldarr = $valuearr = [];
|
||||
$field = $data['field'] ?? ($data['key'] ?? []);
|
||||
$value = $data['value'] ?? [];
|
||||
foreach ($field as $m => $n) {
|
||||
if ($n != '') {
|
||||
$fieldarr[] = $field[$m];
|
||||
$valuearr[] = $value[$m];
|
||||
}
|
||||
}
|
||||
return $fieldarr ? array_combine($fieldarr, $valuearr) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串解析成键值数组
|
||||
* @param string $text
|
||||
* @return array
|
||||
*/
|
||||
public static function decode($text, $split = "\r\n")
|
||||
{
|
||||
$content = explode($split, $text);
|
||||
$arr = [];
|
||||
foreach ($content as $k => $v) {
|
||||
if (stripos($v, "|") !== false) {
|
||||
$item = explode('|', $v);
|
||||
$arr[$item[0]] = $item[1];
|
||||
}
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将键值数组转换为字符串
|
||||
* @param array $array
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($array, $split = "\r\n")
|
||||
{
|
||||
$content = '';
|
||||
if ($array && is_array($array)) {
|
||||
$arr = [];
|
||||
foreach ($array as $k => $v) {
|
||||
$arr[] = "{$k}|{$v}";
|
||||
}
|
||||
$content = implode($split, $arr);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地上传配置信息
|
||||
* @return array
|
||||
*/
|
||||
public static function upload()
|
||||
{
|
||||
$uploadcfg = config('upload');
|
||||
|
||||
$uploadurl = request()->module() ? $uploadcfg['uploadurl'] : ($uploadcfg['uploadurl'] === 'ajax/upload' ? 'index/' . $uploadcfg['uploadurl'] : $uploadcfg['uploadurl']);
|
||||
|
||||
if (!preg_match("/^((?:[a-z]+:)?\/\/)(.*)/i", $uploadurl) && substr($uploadurl, 0, 1) !== '/') {
|
||||
$uploadurl = url($uploadurl, '', false);
|
||||
}
|
||||
$uploadcfg['fullmode'] = isset($uploadcfg['fullmode']) && $uploadcfg['fullmode'];
|
||||
$uploadcfg['thumbstyle'] = $uploadcfg['thumbstyle'] ?? '';
|
||||
|
||||
$upload = [
|
||||
'cdnurl' => $uploadcfg['cdnurl'],
|
||||
'uploadurl' => $uploadurl,
|
||||
'bucket' => 'local',
|
||||
'maxsize' => $uploadcfg['maxsize'],
|
||||
'mimetype' => $uploadcfg['mimetype'],
|
||||
'chunking' => $uploadcfg['chunking'],
|
||||
'chunksize' => $uploadcfg['chunksize'],
|
||||
'savekey' => $uploadcfg['savekey'],
|
||||
'multipart' => [],
|
||||
'multiple' => $uploadcfg['multiple'],
|
||||
'fullmode' => $uploadcfg['fullmode'],
|
||||
'thumbstyle' => $uploadcfg['thumbstyle'],
|
||||
'storage' => 'local'
|
||||
];
|
||||
return $upload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新配置文件
|
||||
*/
|
||||
public static function refreshFile()
|
||||
{
|
||||
//如果没有配置权限无法进行修改
|
||||
if (!\app\admin\library\Auth::instance()->check('general/config/edit')) {
|
||||
return false;
|
||||
}
|
||||
$config = [];
|
||||
$configList = self::all();
|
||||
foreach ($configList as $k => $v) {
|
||||
$value = $v->toArray();
|
||||
if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files'])) {
|
||||
$value['value'] = explode(',', $value['value']);
|
||||
}
|
||||
if ($value['type'] == 'array') {
|
||||
$value['value'] = (array)json_decode($value['value'], true);
|
||||
}
|
||||
$config[$value['name']] = $value['value'];
|
||||
}
|
||||
file_put_contents(
|
||||
CONF_PATH . 'extra' . DS . 'site.php',
|
||||
'<?php' . "\n\nreturn " . var_export($config, true) . ";\n"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
21
application/common/model/Ems.php
Normal file
21
application/common/model/Ems.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 邮箱验证码
|
||||
*/
|
||||
class Ems extends Model
|
||||
{
|
||||
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = false;
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
];
|
||||
}
|
||||
23
application/common/model/MoneyLog.php
Normal file
23
application/common/model/MoneyLog.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 会员余额日志模型
|
||||
*/
|
||||
class MoneyLog extends Model
|
||||
{
|
||||
|
||||
// 表名
|
||||
protected $name = 'user_money_log';
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = '';
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
];
|
||||
}
|
||||
23
application/common/model/ScoreLog.php
Normal file
23
application/common/model/ScoreLog.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 会员积分日志模型
|
||||
*/
|
||||
class ScoreLog extends Model
|
||||
{
|
||||
|
||||
// 表名
|
||||
protected $name = 'user_score_log';
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = '';
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
];
|
||||
}
|
||||
21
application/common/model/Sms.php
Normal file
21
application/common/model/Sms.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 短信验证码
|
||||
*/
|
||||
class Sms extends Model
|
||||
{
|
||||
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = false;
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
];
|
||||
}
|
||||
155
application/common/model/User.php
Normal file
155
application/common/model/User.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Db;
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 会员模型
|
||||
* @method static mixed getByUsername($str) 通过用户名查询用户
|
||||
* @method static mixed getByNickname($str) 通过昵称查询用户
|
||||
* @method static mixed getByMobile($str) 通过手机查询用户
|
||||
* @method static mixed getByEmail($str) 通过邮箱查询用户
|
||||
*/
|
||||
class User extends Model
|
||||
{
|
||||
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
'url',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取个人URL
|
||||
* @param string $value
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function getUrlAttr($value, $data)
|
||||
{
|
||||
return "/u/" . $data['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取头像
|
||||
* @param string $value
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function getAvatarAttr($value, $data)
|
||||
{
|
||||
if (!$value) {
|
||||
//如果不需要启用首字母头像,请使用
|
||||
//$value = '/assets/img/avatar.png';
|
||||
$value = letter_avatar($data['nickname']);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员的组别
|
||||
*/
|
||||
public function getGroupAttr($value, $data)
|
||||
{
|
||||
return UserGroup::get($data['group_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证字段数组值
|
||||
* @param string $value
|
||||
* @param array $data
|
||||
* @return object
|
||||
*/
|
||||
public function getVerificationAttr($value, $data)
|
||||
{
|
||||
$value = array_filter((array)json_decode($value, true));
|
||||
$value = array_merge(['email' => 0, 'mobile' => 0], $value);
|
||||
return (object)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证字段
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
public function setVerificationAttr($value)
|
||||
{
|
||||
$value = is_object($value) || is_array($value) ? json_encode($value) : $value;
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 变更会员余额
|
||||
* @param int $money 余额
|
||||
* @param int $user_id 会员ID
|
||||
* @param string $memo 备注
|
||||
*/
|
||||
public static function money($money, $user_id, $memo)
|
||||
{
|
||||
Db::startTrans();
|
||||
try {
|
||||
$user = self::lock(true)->find($user_id);
|
||||
if ($user && $money != 0) {
|
||||
$before = $user->money;
|
||||
//$after = $user->money + $money;
|
||||
$after = function_exists('bcadd') ? bcadd($user->money, $money, 2) : $user->money + $money;
|
||||
//更新会员信息
|
||||
$user->save(['money' => $after]);
|
||||
//写入日志
|
||||
MoneyLog::create(['user_id' => $user_id, 'money' => $money, 'before' => $before, 'after' => $after, 'memo' => $memo]);
|
||||
}
|
||||
Db::commit();
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 变更会员积分
|
||||
* @param int $score 积分
|
||||
* @param int $user_id 会员ID
|
||||
* @param string $memo 备注
|
||||
*/
|
||||
public static function score($score, $user_id, $memo)
|
||||
{
|
||||
Db::startTrans();
|
||||
try {
|
||||
$user = self::lock(true)->find($user_id);
|
||||
if ($user && $score != 0) {
|
||||
$before = $user->score;
|
||||
$after = $user->score + $score;
|
||||
$level = self::nextlevel($after);
|
||||
//更新会员信息
|
||||
$user->save(['score' => $after, 'level' => $level]);
|
||||
//写入日志
|
||||
ScoreLog::create(['user_id' => $user_id, 'score' => $score, 'before' => $before, 'after' => $after, 'memo' => $memo]);
|
||||
}
|
||||
Db::commit();
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据积分获取等级
|
||||
* @param int $score 积分
|
||||
* @return int
|
||||
*/
|
||||
public static function nextlevel($score = 0)
|
||||
{
|
||||
$lv = array(1 => 0, 2 => 30, 3 => 100, 4 => 500, 5 => 1000, 6 => 2000, 7 => 3000, 8 => 5000, 9 => 8000, 10 => 10000);
|
||||
$level = 1;
|
||||
foreach ($lv as $key => $value) {
|
||||
if ($score >= $value) {
|
||||
$level = $key;
|
||||
}
|
||||
}
|
||||
return $level;
|
||||
}
|
||||
}
|
||||
21
application/common/model/UserGroup.php
Normal file
21
application/common/model/UserGroup.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class UserGroup extends Model
|
||||
{
|
||||
|
||||
// 表名
|
||||
protected $name = 'user_group';
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
];
|
||||
|
||||
}
|
||||
21
application/common/model/UserRule.php
Normal file
21
application/common/model/UserRule.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class UserRule extends Model
|
||||
{
|
||||
|
||||
// 表名
|
||||
protected $name = 'user_rule';
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
];
|
||||
|
||||
}
|
||||
283
application/common/model/UserWallet.php
Normal file
283
application/common/model/UserWallet.php
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
use think\Db;
|
||||
use think\Model;
|
||||
|
||||
|
||||
/**
|
||||
* 会员钱包模型
|
||||
*/
|
||||
class UserWallet extends Model
|
||||
{
|
||||
protected $table = 'user_wallet';
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
|
||||
//常量
|
||||
//钱包类型
|
||||
const MONEYTYPECOIN = 1; //金币
|
||||
const MONEYTYPEARNINGS = 2; //钻石
|
||||
|
||||
//资金操作
|
||||
//系统调节
|
||||
const OPERATION_SYSTEM = 1;
|
||||
//会员充值
|
||||
const OPERATION_RECHARGE = 2;
|
||||
//会员提现
|
||||
const OPERATION_WITHDRAW = 3;
|
||||
//金币转增(送出)
|
||||
const OPERATION_CONSUME = 4;
|
||||
//每日任务奖励
|
||||
const DAILY_TASKS_REWARD = 5;
|
||||
//推广用户充值返利
|
||||
const OPERATION_INVITE_REBATE = 6;
|
||||
//购买装扮
|
||||
const OPERATION_DECORATION = 7;
|
||||
//礼盒奖励
|
||||
const GIFT_BOX_REWARD = 8;
|
||||
//房间补贴
|
||||
const ROOM_SUBSIDY = 9;
|
||||
//购买礼物
|
||||
const OPERATION_GIFT = 10;
|
||||
//收礼增加收益
|
||||
const GIVE_GIFT_EARNING = 11;
|
||||
//工会补贴
|
||||
const GUILD_SUBSIDY = 12;
|
||||
//会员转赠(接收)
|
||||
const USER_RECEIVE = 13;
|
||||
//收益兑换
|
||||
const MONEY_CONVERSION = 14;
|
||||
//首充
|
||||
const FIRST_CHARGE = 15;
|
||||
//天降好礼充值
|
||||
const DROP_GIFT_REWARD = 16;
|
||||
//退出工会扣款
|
||||
const GUILD_EXIT = 17;
|
||||
//房主收益
|
||||
const ROOM_OWNER_EARNINGS = 18;
|
||||
//主持人收益
|
||||
const HOST_EARNINGS = 19;
|
||||
//抢头条
|
||||
const HEADLINE_REWARD = 20;
|
||||
//公会长收益
|
||||
const GUILD_EARNINGS = 21;
|
||||
//提现驳回或提现失败返还
|
||||
const WITHDRAW_FAILURE = 22;
|
||||
//财富等级奖励金币领取
|
||||
const FINANCE_LEVEL_REWARD = 23;
|
||||
//删除关系扣金币
|
||||
const DELETE_RELATION_COIN = 24;
|
||||
//赠送好友金币
|
||||
const TRANSFER_COIN = 25;
|
||||
//好友转赠所得金币
|
||||
const RECEIVE_COIN = 26;
|
||||
|
||||
|
||||
//金币支出类型数组
|
||||
public $coin_consumption_type_array = [
|
||||
self::OPERATION_CONSUME,
|
||||
self::OPERATION_DECORATION,
|
||||
self::OPERATION_GIFT,
|
||||
self::GUILD_EXIT,
|
||||
self::HEADLINE_REWARD,
|
||||
self::TRANSFER_COIN
|
||||
];
|
||||
//钻石支出类型数组
|
||||
public $diamond_consumption_type_array = [
|
||||
self::OPERATION_WITHDRAW,
|
||||
self::MONEY_CONVERSION
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->hasOne('User', 'id', 'user_id');
|
||||
}
|
||||
//钱包类型
|
||||
public static function getMoneyType($value)
|
||||
{
|
||||
$status = [
|
||||
self::MONEYTYPECOIN => '金币',
|
||||
self::MONEYTYPEARNINGS => '钻石'
|
||||
];
|
||||
return isset($status[$value]) ? $status[$value] : '';
|
||||
|
||||
}
|
||||
public static function ChangeTypeLable($type)
|
||||
{
|
||||
$status = [
|
||||
self::OPERATION_SYSTEM => '系统调节',
|
||||
self::OPERATION_RECHARGE => '会员充值',
|
||||
self::OPERATION_WITHDRAW => '会员提现',
|
||||
self::OPERATION_CONSUME => '金币转增(送出)',
|
||||
self::DAILY_TASKS_REWARD => '每日任务奖励',
|
||||
self::OPERATION_INVITE_REBATE => '邀请用户充值返利',
|
||||
self::OPERATION_DECORATION => '购买装扮',
|
||||
self::GIFT_BOX_REWARD => '礼盒奖励',
|
||||
self::ROOM_SUBSIDY => '房间补贴',
|
||||
self::OPERATION_GIFT => '购买礼物',
|
||||
self::GIVE_GIFT_EARNING => '送礼增加收益',
|
||||
self::GUILD_SUBSIDY => '工会补贴',
|
||||
self::USER_RECEIVE => '会员转赠(接收)',
|
||||
self::MONEY_CONVERSION => '钻石兑换金币',
|
||||
self::FIRST_CHARGE => '首充',
|
||||
self::DROP_GIFT_REWARD => '天降好礼充值',
|
||||
self::GUILD_EXIT => '退出工会扣款',
|
||||
self::ROOM_OWNER_EARNINGS => '房主收益',
|
||||
self::HOST_EARNINGS => '主持人收益',
|
||||
self::HEADLINE_REWARD => '抢头条',
|
||||
self::GUILD_EARNINGS => '公会长收益',
|
||||
self::WITHDRAW_FAILURE => '提现驳回或提现失败返还',
|
||||
self::FINANCE_LEVEL_REWARD => '财富等级奖励金币领取',
|
||||
self::DELETE_RELATION_COIN => '删除关系扣金币',
|
||||
self::TRANSFER_COIN => '赠送好友金币',
|
||||
self::RECEIVE_COIN => '好友转赠所得金币'
|
||||
];
|
||||
return $status[$type] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户资金
|
||||
* @param $user_id 用户ID
|
||||
* @param $change_value
|
||||
* @param $money_type
|
||||
* @param $change_type
|
||||
* @param $remarks
|
||||
* @param $from_uid
|
||||
* @param $from_id
|
||||
* @param $rid
|
||||
* @param $is_uid_search
|
||||
* @return array|void
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function change_user_money($user_id, $change_value, $money_type,$change_type, $remarks = "", $room_id=0,$from_uid = 0, $from_id = 0)
|
||||
{
|
||||
if (in_array($change_type, $this->coin_consumption_type_array) && $money_type==self::MONEYTYPECOIN) {//金币支出
|
||||
$change_value = $change_value * -1;
|
||||
}
|
||||
if (in_array($change_type, $this->diamond_consumption_type_array) && $money_type==self::MONEYTYPEARNINGS){//钻石支出
|
||||
$change_value = $change_value * -1;
|
||||
}
|
||||
$user_info = db::name('user')->find($user_id);
|
||||
if (empty($user_info['id'])) {
|
||||
return ['code' => 0, 'msg' => "用户信息错误", 'data' => null];
|
||||
}
|
||||
$user_wallet = db::name('user_wallet')->where(['user_id' => $user_id])->find();
|
||||
if (empty($user_wallet['id'])) {
|
||||
return ['code' => 0, 'msg' => "用户信息错误", 'data' => null];
|
||||
}
|
||||
$money_type_str = $this->getMoneyType($money_type);
|
||||
if (empty($money_type_str)) {
|
||||
return ['code' => 0, 'msg' => "非法资金类型", 'data' => null];
|
||||
}
|
||||
$after_coin = $user_wallet['coin'];
|
||||
$after_earnings = $user_wallet['earnings'];
|
||||
if ($money_type == 1) {
|
||||
$change_field = "coin";
|
||||
$after_coin += $change_value;
|
||||
if($after_coin > 99999999){
|
||||
return ['code' => 0, 'msg' => "当前用户金币已达上限", 'data' => null];
|
||||
}
|
||||
} elseif ($money_type == 2) {
|
||||
$change_field = "earnings";
|
||||
$after_earnings += $change_value;
|
||||
if($after_earnings > 99999999){
|
||||
return ['code' => 0, 'msg' => "当前用户钻石已达上限", 'data' => null];
|
||||
}
|
||||
} else {
|
||||
return ['code' => 0, 'msg' => "非法资金类型", 'data' => null];
|
||||
}
|
||||
$change_name = $this->ChangeTypeLable($change_type);
|
||||
if(empty($change_name)){
|
||||
return ['code' => 0, 'msg' => "非法资金变动类型", 'data' => null];
|
||||
}
|
||||
if (!is_numeric($change_value)) {
|
||||
return ['code' => 0, 'msg' => "变动的数值必须为数字", 'data' => null];
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$data['user_id'] = $user_id;
|
||||
$data['room_id'] = $room_id;
|
||||
$data['change_type'] = $change_type;
|
||||
$data['money_type'] = $money_type;
|
||||
$data['change_value'] = abs($change_value);
|
||||
$data['after_coin'] = $after_coin;
|
||||
$data['after_earnings'] = $after_earnings;
|
||||
$data['from_id'] = $from_id;
|
||||
$data['from_uid'] = $from_uid;
|
||||
$data['remarks'] = $remarks;
|
||||
$data['createtime'] = time();
|
||||
$data['updatetime'] = time();
|
||||
Db::startTrans();
|
||||
try {
|
||||
if($change_value < 0){
|
||||
$change_value_abs = abs($change_value);
|
||||
$change_value_up = $user_wallet[$change_field] - $change_value_abs;
|
||||
if($change_value_up<0){
|
||||
Db::rollback();
|
||||
return ['code' => 0, 'msg' => $money_type_str . "不足", 'data' => null];
|
||||
}
|
||||
}
|
||||
Db::name('user_wallet')->where('user_id', $user_id)->inc($change_field, $change_value)->update(['updatetime' => time()]);
|
||||
$reslut = Db::name('vs_user_money_log')->insert($data);
|
||||
if (!$reslut) {
|
||||
Db::rollback();
|
||||
return ['code' => 0, 'msg' => "请重试", 'data' => null];
|
||||
}
|
||||
// 提交事务
|
||||
Db::commit();
|
||||
return ['code' => 1, 'msg' => "操作成功", 'data' => null];
|
||||
} catch (\Exception $e) {
|
||||
// 回滚事务
|
||||
Db::rollback();
|
||||
return ['code' => 0, 'msg' => "请重试", 'data' => null];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 用户资金变动日志
|
||||
*/
|
||||
public function money_change_log($user_id, $money_type=0, $page=0,$page_limit=30){
|
||||
if($money_type){
|
||||
$where['money_type'] =$money_type;
|
||||
}
|
||||
$log['count'] = Db::name('vs_user_money_log')->where($where)->where('user_id',$user_id)->count();
|
||||
$log_select = Db::name('vs_user_money_log')
|
||||
->where($where)
|
||||
->where('user_id',$user_id)
|
||||
->order('log_id desc');
|
||||
if($page){
|
||||
$log_select->page($page,$page_limit);
|
||||
}
|
||||
$log['list'] = $log_select->select();
|
||||
foreach ($log['list'] as $key => &$value) {
|
||||
$value['money_type'] = $this->getMoneyType($value['money_type']);
|
||||
$change_type = $value['change_type'];
|
||||
$value['change_type'] = $this->ChangeTypeLable($value['change_type']);
|
||||
$value['createtime'] = date('Y-m-d H:i:s',$value['createtime']);
|
||||
if($money_type==1 ){
|
||||
if(in_array($change_type,$this->coin_consumption_type_array)){
|
||||
$value['change_in_out'] = "支出";
|
||||
$value['change_value'] = $value['change_value']*-1;
|
||||
}else{
|
||||
$value['change_in_out'] = "收入";
|
||||
}
|
||||
}else{
|
||||
if(in_array($change_type,$this->diamond_consumption_type_array)){
|
||||
$value['change_in_out'] = "支出";
|
||||
$value['change_value'] = $value['change_value']*-1;
|
||||
}else{
|
||||
$value['change_in_out'] = "收入";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return $log;
|
||||
}
|
||||
}
|
||||
50
application/common/model/Version.php
Normal file
50
application/common/model/Version.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Version extends Model
|
||||
{
|
||||
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
];
|
||||
|
||||
/**
|
||||
* 检测版本号
|
||||
*
|
||||
* @param string $version 客户端版本号
|
||||
* @return array
|
||||
*/
|
||||
public static function check($version)
|
||||
{
|
||||
$versionlist = self::where('status', 'normal')->cache('__version__')->order('weigh desc,id desc')->select();
|
||||
foreach ($versionlist as $k => $v) {
|
||||
// 版本正常且新版本号不等于验证的版本号且找到匹配的旧版本
|
||||
if ($v['status'] == 'normal' && $v['newversion'] !== $version && \fast\Version::check($version, $v['oldversion'])) {
|
||||
$updateversion = $v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isset($updateversion)) {
|
||||
$search = ['{version}', '{newversion}', '{downloadurl}', '{url}', '{packagesize}'];
|
||||
$replace = [$version, $updateversion['newversion'], $updateversion['downloadurl'], $updateversion['downloadurl'], $updateversion['packagesize']];
|
||||
$upgradetext = str_replace($search, $replace, $updateversion['content']);
|
||||
return [
|
||||
"enforce" => $updateversion['enforce'],
|
||||
"version" => $version,
|
||||
"newversion" => $updateversion['newversion'],
|
||||
"downloadurl" => $updateversion['downloadurl'],
|
||||
"packagesize" => $updateversion['packagesize'],
|
||||
"upgradetext" => $upgradetext
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
1
application/common/view/tpl/404.tpl
Normal file
1
application/common/view/tpl/404.tpl
Normal file
@@ -0,0 +1 @@
|
||||
页面未找到
|
||||
64
application/common/view/tpl/dispatch_jump.tpl
Normal file
64
application/common/view/tpl/dispatch_jump.tpl
Normal file
@@ -0,0 +1,64 @@
|
||||
{__NOLAYOUT__}<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>{:__('Warning')}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="__CDN__/assets/img/favicon.ico" />
|
||||
<style type="text/css">
|
||||
*{box-sizing:border-box;margin:0;padding:0;font-family:Lantinghei SC,Open Sans,Arial,Hiragino Sans GB,Microsoft YaHei,"微软雅黑",STHeiti,WenQuanYi Micro Hei,SimSun,sans-serif;-webkit-font-smoothing:antialiased}
|
||||
body{padding:70px 50px;background:#f4f6f8;font-weight:400;font-size:1pc;-webkit-text-size-adjust:none;color:#333}
|
||||
a{outline:0;color:#3498db;text-decoration:none;cursor:pointer}
|
||||
.system-message{margin:20px auto;padding:50px 0px;background:#fff;box-shadow:0 0 30px hsla(0,0%,39%,.06);text-align:center;width:100%;border-radius:2px;}
|
||||
.system-message h1{margin:0;margin-bottom:9pt;color:#444;font-weight:400;font-size:30px}
|
||||
.system-message .jump,.system-message .image{margin:20px 0;padding:0;padding:10px 0;font-weight:400}
|
||||
.system-message .jump{font-size:14px}
|
||||
.system-message .jump a{color:#333}
|
||||
.system-message p{font-size:9pt;line-height:20px}
|
||||
.system-message .btn{display:inline-block;margin-right:10px;width:138px;height:2pc;border:1px solid #44a0e8;border-radius:30px;color:#44a0e8;text-align:center;font-size:1pc;line-height:2pc;margin-bottom:5px;}
|
||||
.success .btn{border-color:#69bf4e;color:#69bf4e}
|
||||
.error .btn{border-color:#ff8992;color:#ff8992}
|
||||
.info .btn{border-color:#3498db;color:#3498db}
|
||||
.copyright p{width:100%;color:#919191;text-align:center;font-size:10px}
|
||||
.system-message .btn-grey{border-color:#bbb;color:#bbb}
|
||||
.clearfix:after{clear:both;display:block;visibility:hidden;height:0;content:"."}
|
||||
@media (max-width:768px){body {padding:20px;}}
|
||||
@media (max-width:480px){.system-message h1{font-size:30px;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{php}$codeText=$code == 1 ? 'success' : ($code == 0 ? 'error' : 'info');{/php}
|
||||
<div class="system-message {$codeText}">
|
||||
<div class="image">
|
||||
<img src="__CDN__/assets/img/{$codeText}.svg" alt="" width="120" />
|
||||
</div>
|
||||
<h1>{$msg}</h1>
|
||||
{if $url}
|
||||
<p class="jump">
|
||||
{:__('This page will be re-directed in %s seconds', '<span id="wait">' . $wait . '</span>')}
|
||||
</p>
|
||||
{/if}
|
||||
<p class="clearfix">
|
||||
<a href="__PUBLIC__" class="btn btn-grey">{:__('Go back')}</a>
|
||||
{if $url}
|
||||
<a id="href" href="{$url|htmlentities}" class="btn btn-primary">{:__('Jump now')}</a>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{if $url}
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
var wait = document.getElementById('wait'),
|
||||
href = document.getElementById('href').href;
|
||||
var interval = setInterval(function () {
|
||||
var time = --wait.innerHTML;
|
||||
if (time <= 0) {
|
||||
location.href = href;
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 1000);
|
||||
})();
|
||||
</script>
|
||||
{/if}
|
||||
</body>
|
||||
</html>
|
||||
101
application/common/view/tpl/think_exception.tpl
Normal file
101
application/common/view/tpl/think_exception.tpl
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
$cdnurl = function_exists('config') ? config('view_replace_str.__CDN__') : '';
|
||||
$publicurl = function_exists('config') ? (config('view_replace_str.__PUBLIC__')?:'/') : '/';
|
||||
$debug = function_exists('config') ? config('app_debug') : false;
|
||||
|
||||
$lang = [
|
||||
'An error occurred' => '发生错误',
|
||||
'Home' => '返回主页',
|
||||
'Previous Page' => '返回上一页',
|
||||
'The page you are looking for is temporarily unavailable' => '你所浏览的页面暂时无法访问',
|
||||
'You can return to the previous page and try again' => '你可以返回上一页重试'
|
||||
];
|
||||
|
||||
$langSet = '';
|
||||
|
||||
if (isset($_GET['lang'])) {
|
||||
$langSet = strtolower($_GET['lang']);
|
||||
} elseif (isset($_COOKIE['think_var'])) {
|
||||
$langSet = strtolower($_COOKIE['think_var']);
|
||||
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
|
||||
$langSet = strtolower($matches[1] ?? '');
|
||||
}
|
||||
$langSet = $langSet && in_array($langSet, ['zh-cn', 'en']) ? $langSet : 'zh-cn';
|
||||
$langSet == 'en' && $lang = array_combine(array_keys($lang), array_keys($lang));
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||
<title><?=$lang['An error occurred']?></title>
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<link rel="shortcut icon" href="<?php echo $cdnurl;?>/assets/img/favicon.ico" />
|
||||
<style>
|
||||
* {-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;}
|
||||
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,caption,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video {margin:0;padding:0;border:0;outline:0;vertical-align:baseline;background:transparent;}
|
||||
article,aside,details,figcaption,figure,footer,header,hgroup,nav,section {display:block;}
|
||||
html {font-size:16px;line-height:24px;width:100%;height:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;overflow-y:scroll;overflow-x:hidden;}
|
||||
img {vertical-align:middle;max-width:100%;height:auto;border:0;-ms-interpolation-mode:bicubic;}
|
||||
body {min-height:100%;background:#f4f6f8;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",微软雅黑,Arial,sans-serif;}
|
||||
.clearfix {clear:both;zoom:1;}
|
||||
.clearfix:before,.clearfix:after {content:"\0020";display:block;height:0;visibility:hidden;}
|
||||
.clearfix:after {clear:both;}
|
||||
body.error-page-wrapper,.error-page-wrapper.preview {background-position:center center;background-repeat:no-repeat;background-size:cover;position:relative;}
|
||||
.error-page-wrapper .content-container {border-radius:2px;text-align:center;box-shadow:0 0 30px rgba(99,99,99,0.06);padding:50px;background-color:#fff;width:100%;max-width:560px;position:absolute;left:50%;top:50%;margin-top:-220px;margin-left:-280px;}
|
||||
.error-page-wrapper .content-container.in {left:0px;opacity:1;}
|
||||
.error-page-wrapper .head-line {transition:color .2s linear;font-size:40px;line-height:60px;letter-spacing:-1px;margin-bottom:20px;color:#777;}
|
||||
.error-page-wrapper .subheader {transition:color .2s linear;font-size:32px;line-height:46px;color:#494949;}
|
||||
.error-page-wrapper .hr {height:1px;background-color:#eee;width:80%;max-width:350px;margin:25px auto;}
|
||||
.error-page-wrapper .context {transition:color .2s linear;font-size:16px;line-height:27px;color:#aaa;}
|
||||
.error-page-wrapper .context p {margin:0;}
|
||||
.error-page-wrapper .context p:nth-child(n+2) {margin-top:16px;}
|
||||
.error-page-wrapper .buttons-container {margin-top:35px;overflow:hidden;}
|
||||
.error-page-wrapper .buttons-container a {transition:text-indent .2s ease-out,color .2s linear,background-color .2s linear;text-indent:0px;font-size:14px;text-transform:uppercase;text-decoration:none;color:#fff;background-color:#2ecc71;border-radius:99px;padding:8px 0 8px;text-align:center;display:inline-block;overflow:hidden;position:relative;width:45%;}
|
||||
.error-page-wrapper .buttons-container a:hover {text-indent:15px;}
|
||||
.error-page-wrapper .buttons-container a:nth-child(1) {float:left;}
|
||||
.error-page-wrapper .buttons-container a:nth-child(2) {float:right;}
|
||||
@media screen and (max-width:580px) {
|
||||
.error-page-wrapper {padding:30px 5%;}
|
||||
.error-page-wrapper .content-container {padding:37px;position:static;left:0;margin-top:0;margin-left:0;}
|
||||
.error-page-wrapper .head-line {font-size:36px;}
|
||||
.error-page-wrapper .subheader {font-size:27px;line-height:37px;}
|
||||
.error-page-wrapper .hr {margin:30px auto;width:215px;}
|
||||
}
|
||||
@media screen and (max-width:450px) {
|
||||
.error-page-wrapper {padding:30px;}
|
||||
.error-page-wrapper .head-line {font-size:32px;}
|
||||
.error-page-wrapper .hr {margin:25px auto;width:180px;}
|
||||
.error-page-wrapper .context {font-size:15px;line-height:22px;}
|
||||
.error-page-wrapper .context p:nth-child(n+2) {margin-top:10px;}
|
||||
.error-page-wrapper .buttons-container {margin-top:29px;}
|
||||
.error-page-wrapper .buttons-container a {float:none !important;width:65%;margin:0 auto;font-size:13px;padding:9px 0;}
|
||||
.error-page-wrapper .buttons-container a:nth-child(2) {margin-top:12px;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="error-page-wrapper">
|
||||
<div class="content-container">
|
||||
<div class="head-line">
|
||||
<img src="<?=$cdnurl?>/assets/img/error.svg" alt="" width="120"/>
|
||||
</div>
|
||||
<div class="subheader">
|
||||
<?=$debug?$message:$lang['The page you are looking for is temporarily unavailable']?>
|
||||
</div>
|
||||
<div class="hr"></div>
|
||||
<div class="context">
|
||||
|
||||
<p>
|
||||
<?=$lang['You can return to the previous page and try again']?>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="buttons-container">
|
||||
<a href="<?=$publicurl?>"><?=$lang['Home']?></a>
|
||||
<a href="javascript:" onclick="history.go(-1)"><?=$lang['Previous Page']?></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user