modified 完善中间件

This commit is contained in:
zhaoxiang 2020-10-10 20:13:06 +08:00
parent 0f1fbab604
commit f62885ed67
12 changed files with 727 additions and 5 deletions

View File

@ -0,0 +1,162 @@
<?php
declare (strict_types=1);
/**
* 登录登出
* @since 2017-11-02
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
namespace app\controller\admin;
use app\model\AdminAuthGroupAccess;
use app\model\AdminAuthRule;
use app\model\AdminMenu;
use app\model\AdminUser;
use app\model\AdminUserData;
use app\util\ReturnCode;
use app\util\RouterTool;
use app\util\Tools;
use think\Response;
class Login extends Base {
/**
* 用户登录【账号密码登录】
* @return \think\Response
* @throws \think\Exception
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function index(): Response {
$username = $this->request->post('username');
$password = $this->request->post('password');
if (!$username) {
return $this->buildFailed(ReturnCode::LOGIN_ERROR, '缺少用户名!');
}
if (!$password) {
return $this->buildFailed(ReturnCode::LOGIN_ERROR, '缺少密码!');
} else {
$password = Tools::userMd5($password);
}
$userInfo = AdminUser::find(['username' => $username, 'password' => $password]);
if (!empty($userInfo)) {
if ($userInfo['status']) {
//更新用户数据
$userData = $userInfo->userData;
$data = [];
if ($userData) {
$userData->login_times++;
$userData->last_login_ip = $this->request->ip(1);
$userData->last_login_time = time();
$userData->save();
} else {
$data['login_times'] = 1;
$data['uid'] = $userInfo['id'];
$data['last_login_ip'] = $this->request->ip(1);
$data['last_login_time'] = time();
$data['head_img'] = '';
AdminUserData::create($data);
$userInfo['userData'] = $data;
}
} else {
return $this->buildFailed(ReturnCode::LOGIN_ERROR, '用户已被封禁,请联系管理员');
}
} else {
return $this->buildFailed(ReturnCode::LOGIN_ERROR, '用户名密码不正确');
}
$userInfo['access'] = $this->getAccess($userInfo['id']);
$userInfo['menu'] = $this->getAccessMenu($userInfo['id']);
$apiAuth = md5(uniqid() . time());
cache('Login:' . $apiAuth, json_encode($userInfo), config('apiadmin.ONLINE_TIME'));
cache('Login:' . $userInfo['id'], $apiAuth, config('apiadmin.ONLINE_TIME'));
$userInfo['apiAuth'] = $apiAuth;
return $this->buildSuccess($userInfo, '登录成功');
}
/**
* 获取用户信息
* @return mixed
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function getUserInfo() {
return $this->buildSuccess($this->userInfo);
}
/**
* 用户登出
* @return array
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function logout() {
$ApiAuth = $this->request->header('ApiAuth');
cache('Login:' . $ApiAuth, null);
cache('Login:' . $this->userInfo['id'], null);
return $this->buildSuccess([], '登出成功');
}
/**
* 获取当前用户的允许菜单
* @param int $uid
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function getAccessMenu($uid = 0) {
if ($uid == 0) {
$uid = $this->userInfo['id'];
}
$returnData = [];
$isSupper = Tools::isAdministrator($uid);
if ($isSupper) {
$access = (new AdminMenu())->where('router', '<>', '')->select();
$returnData = Tools::listToTree(Tools::buildArrFromObj($access));
} else {
$groups = AdminAuthGroupAccess::get(['uid' => $uid]);
if (isset($groups) && $groups->group_id) {
$access = (new AdminAuthRule())->whereIn('group_id', $groups->group_id)->select();
$access = array_unique(array_column(Tools::buildArrFromObj($access), 'url'));
array_push($access, "");
$menus = (new AdminMenu())->whereIn('url', $access)->where('show', 1)->select();
$returnData = Tools::listToTree(Tools::buildArrFromObj($menus));
RouterTool::buildVueRouter($returnData);
}
}
if ($uid == 0) {
return $this->buildSuccess($returnData);
} else {
return $returnData;
}
}
/**
* 获取用户权限数据
* @param $uid
* @return array
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function getAccess($uid) {
$isSupper = Tools::isAdministrator($uid);
if ($isSupper) {
$access = AdminMenu::all();
$access = Tools::buildArrFromObj($access);
return array_values(array_filter(array_column($access, 'url')));
} else {
$groups = AdminAuthGroupAccess::get(['uid' => $uid]);
if (isset($groups) && $groups->group_id) {
$access = (new AdminAuthRule())->whereIn('group_id', $groups->group_id)->select();
$access = Tools::buildArrFromObj($access);
return array_values(array_unique(array_column($access, 'url')));
} else {
return [];
}
}
}
}

View File

@ -0,0 +1,43 @@
<?php
declare (strict_types=1);
namespace app\middleware;
use app\util\ReturnCode;
use think\Response;
class AdminAuth {
/**
* ApiAuth鉴权
* @param \think\facade\Request $request
* @param \Closure $next
* @return mixed|\think\response\Json
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function handle($request, \Closure $next): Response {
$header = config('apiadmin.CROSS_DOMAIN');
$ApiAuth = $request->header('apiAuth', '');
if ($ApiAuth) {
$userInfo = cache('Login:' . $ApiAuth);
$userInfo = json_decode($userInfo, true);
if (!$userInfo || !isset($userInfo['id'])) {
return json([
'code' => ReturnCode::AUTH_ERROR,
'msg' => 'ApiAuth不匹配',
'data' => []
])->header($header);
} else {
$request->API_ADMIN_USER_INFO = $userInfo;
}
return $next($request);
} else {
return json([
'code' => ReturnCode::AUTH_ERROR,
'msg' => '缺少ApiAuth',
'data' => []
])->header($header);
}
}
}

View File

@ -0,0 +1,45 @@
<?php
declare (strict_types=1);
namespace app\middleware;
use app\model\AdminMenu;
use app\model\AdminUserAction;
use app\util\ReturnCode;
use think\Response;
class AdminLog {
/**
* @param \think\facade\Request $request
* @param \Closure $next
* @return \think\response\Json
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function handle($request, \Closure $next): Response {
$userInfo = $request->API_ADMIN_USER_INFO;
$menuInfo = AdminMenu::get(['url' => $request->path()]);
if ($menuInfo) {
$menuInfo = $menuInfo->toArray();
} else {
return json([
'code' => ReturnCode::INVALID,
'msg' => '当前路由非法:' . $request->path(),
'data' => []
])->header(config('apiadmin.CROSS_DOMAIN'));
}
AdminUserAction::create([
'action_name' => $menuInfo['title'],
'uid' => $userInfo['id'],
'nickname' => $userInfo['nickname'],
'add_time' => time(),
'url' => $request->path(),
'data' => json_encode($request->param())
]);
return $next($request);
}
}

View File

@ -0,0 +1,96 @@
<?php
declare (strict_types=1);
namespace app\middleware;
use app\model\AdminAuthGroup;
use app\model\AdminAuthGroupAccess;
use app\model\AdminAuthRule;
use app\util\ReturnCode;
use app\util\Tools;
use think\Response;
class AdminPermission {
/**
* 用户权限检测
* @param \think\facade\Request $request
* @param \Closure $next
* @return mixed|\think\response\Json
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function handle($request, \Closure $next): Response {
$userInfo = $request->API_ADMIN_USER_INFO;
if (!$this->checkAuth($userInfo['id'], $request->path())) {
return json([
'code' => ReturnCode::INVALID,
'msg' => '非常抱歉,您没有权限这么做!',
'data' => []
])->header(config('apiadmin.CROSS_DOMAIN'));
}
return $next($request);
}
/**
* 检测用户权限
* @param $uid
* @param $route
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
private function checkAuth($uid, $route) {
$isSupper = Tools::isAdministrator($uid);
if (!$isSupper) {
$rules = $this->getAuth($uid);
return in_array($route, $rules);
} else {
return true;
}
}
/**
* 根据用户ID获取全部权限节点
* @param $uid
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
private function getAuth($uid) {
$groups = AdminAuthGroupAccess::get(['uid' => $uid]);
if (isset($groups) && $groups->group_id) {
$openGroup = (new AdminAuthGroup())->whereIn('id', $groups->group_id)->where(['status' => 1])->select();
if (isset($openGroup)) {
$openGroupArr = [];
foreach ($openGroup as $group) {
$openGroupArr[] = $group->id;
}
$allRules = (new AdminAuthRule())->whereIn('group_id', $openGroupArr)->select();
if (isset($allRules)) {
$rules = [];
foreach ($allRules as $rule) {
$rules[] = $rule->url;
}
$rules = array_unique($rules);
return $rules;
} else {
return [];
}
} else {
return [];
}
} else {
return [];
}
}
}

View File

@ -0,0 +1,14 @@
<?php
declare (strict_types=1);
namespace app\middleware;
use think\facade\Config;
use think\Response;
class AdminResponse {
public function handle($request, \Closure $next): Response {
return $next($request)->header(Config::get('apiadmin.CROSS_DOMAIN'));
}
}

118
app/middleware/ApiAuth.php Normal file
View File

@ -0,0 +1,118 @@
<?php
namespace app\http\middleware;
use app\model\AdminApp;
use app\model\AdminList;
use app\util\ReturnCode;
use think\facade\Cache;
class ApiAuth {
/**
* 获取接口基本配置参数校验接口Hash是否合法校验APP_ID是否合法等
* @param \think\facade\Request $request
* @param \Closure $next
* @return mixed|\think\response\Json
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function handle($request, \Closure $next) {
$header = config('apiadmin.CROSS_DOMAIN');
$apiHash = substr($request->path(), 4);
if ($apiHash) {
$cached = Cache::has('ApiInfo:' . $apiHash);
if ($cached) {
$apiInfo = Cache::get('ApiInfo:' . $apiHash);
} else {
$apiInfo = AdminList::get(['hash' => $apiHash, 'hash_type' => 2]);
if ($apiInfo) {
$apiInfo = $apiInfo->toArray();
Cache::rm('ApiInfo:' . $apiInfo['api_class']);
Cache::set('ApiInfo:' . $apiHash, $apiInfo);
} else {
$apiInfo = AdminList::get(['api_class' => $apiHash, 'hash_type' => 1]);
if ($apiInfo) {
$apiInfo = $apiInfo->toArray();
Cache::rm('ApiInfo:' . $apiInfo['hash']);
Cache::set('ApiInfo:' . $apiHash, $apiInfo);
} else {
return json([
'code' => ReturnCode::DB_READ_ERROR,
'msg' => '获取接口配置数据失败',
'data' => []
])->header($header);
}
}
}
$accessToken = $request->header('access-token', '');
if (!$accessToken) {
return json([
'code' => ReturnCode::AUTH_ERROR,
'msg' => '缺少必要参数access-token',
'data' => []
])->header($header);
}
if ($apiInfo['access_token']) {
$appInfo = $this->doCheck($accessToken);
} else {
$appInfo = $this->doEasyCheck($accessToken);
}
if ($appInfo === false) {
return json([
'code' => ReturnCode::ACCESS_TOKEN_TIMEOUT,
'msg' => 'access-token已过期',
'data' => []
])->header($header);
}
$request->APP_CONF_DETAIL = $appInfo;
$request->API_CONF_DETAIL = $apiInfo;
return $next($request);
} else {
return json([
'code' => ReturnCode::AUTH_ERROR,
'msg' => '缺少接口Hash',
'data' => []
])->header($header);
}
}
/**
* 简易鉴权更具APP_SECRET获取应用信息
* @param $accessToken
* @return bool|mixed
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
private function doEasyCheck($accessToken) {
$appInfo = cache('AccessToken:Easy:' . $accessToken);
if (!$appInfo) {
$appInfo = AdminApp::get(['app_secret' => $accessToken]);
if (!$appInfo) {
return false;
} else {
$appInfo = $appInfo->toArray();
cache('AccessToken:Easy:' . $accessToken, $appInfo);
}
}
return $appInfo;
}
/**
* 复杂鉴权需要先通过接口获取AccessToken
* @param $accessToken
* @return bool|mixed
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
private function doCheck($accessToken) {
$appInfo = cache('AccessToken:' . $accessToken);
if (!$appInfo) {
return false;
} else {
return $appInfo;
}
}
}

30
app/middleware/ApiLog.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace app\http\middleware;
use app\util\ApiLogTool;
class ApiLog {
/**
* @param \think\facade\Request $request
* @param \Closure $next
* @return mixed|\think\response\Json
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function handle($request, \Closure $next) {
$response = $next($request);
$requestInfo = $request->param();
unset($requestInfo['API_CONF_DETAIL']);
unset($requestInfo['APP_CONF_DETAIL']);
ApiLogTool::setApiInfo($request->API_CONF_DETAIL);
ApiLogTool::setAppInfo($request->APP_CONF_DETAIL);
ApiLogTool::setRequest($requestInfo);
ApiLogTool::setResponse($response->getData(), isset($response->getData()['code']) ? $response->getData()['code'] : 'null');
ApiLogTool::setHeader($request->header());
ApiLogTool::save();
return $response;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace app\http\middleware;
use app\util\ReturnCode;
class ApiPermission {
/**
* 校验当前App是否有请求当前接口的权限
* @param \think\facade\Request $request
* @param \Closure $next
* @return mixed|\think\response\Json
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function handle($request, \Closure $next) {
$header = config('apiadmin.CROSS_DOMAIN');
$appInfo = $request->APP_CONF_DETAIL;
$apiInfo = $request->API_CONF_DETAIL;
$allRules = explode(',', $appInfo['app_api']);
if (!in_array($apiInfo['hash'], $allRules)) {
return json([
'code' => ReturnCode::INVALID,
'msg' => '非常抱歉,您没有权限这么做!',
'data' => []
])->header($header);
}
return $next($request);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace app\http\middleware;
use think\facade\Config;
class ApiResponse {
public function handle($request, \Closure $next) {
return $next($request)->header(Config::get('apiadmin.CROSS_DOMAIN'));
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace app\http\middleware;
use app\model\AdminFields;
use app\util\DataType;
use app\util\ReturnCode;
use think\facade\Cache;
use think\facade\Validate;
class RequestFilter {
/**
* 接口请求字段过滤【只验证数据的合法性,不再过滤数据】
* @param \think\facade\Request $request
* @param \Closure $next
* @return mixed|\think\response\Json
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function handle($request, \Closure $next) {
$apiInfo = $request->API_CONF_DETAIL;
$data = $request->param();
$has = Cache::has('RequestFields:NewRule:' . $apiInfo['hash']);
if ($has) {
$newRule = cache('RequestFields:NewRule:' . $apiInfo['hash']);
} else {
$rule = AdminFields::all(['hash' => $apiInfo['hash'], 'type' => 0]);
$newRule = $this->buildValidateRule($rule);
cache('RequestFields:NewRule:' . $apiInfo['hash'], $newRule);
}
if ($newRule) {
$validate = Validate::make($newRule);
if (!$validate->check($data)) {
return json(['code' => ReturnCode::PARAM_INVALID, 'msg' => $validate->getError(), 'data' => []]);
}
}
return $next($request);
}
/**
* 将数据库中的规则转换成TP_Validate使用的规则数组
* @param array $rule
* @return array
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function buildValidateRule($rule = array()) {
$newRule = [];
if ($rule) {
foreach ($rule as $value) {
if ($value['is_must']) {
$newRule[$value['field_name'] . '|' . $value['info']][] = 'require';
}
switch ($value['data_type']) {
case DataType::TYPE_INTEGER:
$newRule[$value['field_name'] . '|' . $value['info']][] = 'number';
if ($value['range']) {
$range = htmlspecialchars_decode($value['range']);
$range = json_decode($range, true);
if (isset($range['min'])) {
$newRule[$value['field_name'] . '|' . $value['info']]['egt'] = $range['min'];
}
if (isset($range['max'])) {
$newRule[$value['field_name'] . '|' . $value['info']]['elt'] = $range['max'];
}
}
break;
case DataType::TYPE_STRING:
if ($value['range']) {
$range = htmlspecialchars_decode($value['range']);
$range = json_decode($range, true);
if (isset($range['min'])) {
$newRule[$value['field_name'] . '|' . $value['info']]['min'] = $range['min'];
}
if (isset($range['max'])) {
$newRule[$value['field_name'] . '|' . $value['info']]['max'] = $range['max'];
}
}
break;
case DataType::TYPE_ENUM:
if ($value['range']) {
$range = htmlspecialchars_decode($value['range']);
$range = json_decode($range, true);
$newRule[$value['field_name'] . '|' . $value['info']]['in'] = implode(',', $range);
}
break;
case DataType::TYPE_FLOAT:
$newRule[$value['field_name'] . '|' . $value['info']][] = 'float';
if ($value['range']) {
$range = htmlspecialchars_decode($value['range']);
$range = json_decode($range, true);
if (isset($range['min'])) {
$newRule[$value['field_name'] . '|' . $value['info']]['egt'] = $range['min'];
}
if (isset($range['max'])) {
$newRule[$value['field_name'] . '|' . $value['info']]['elt'] = $range['max'];
}
}
break;
case DataType::TYPE_ARRAY:
$newRule[$value['field_name']][] = 'array';
if ($value['range']) {
$range = htmlspecialchars_decode($value['range']);
$range = json_decode($range, true);
if (isset($range['min'])) {
$newRule[$value['field_name'] . '|' . $value['info']]['min'] = $range['min'];
}
if (isset($range['max'])) {
$newRule[$value['field_name'] . '|' . $value['info']]['max'] = $range['max'];
}
}
break;
case DataType::TYPE_MOBILE:
$newRule[$value['field_name'] . '|' . $value['info']]['regex'] = '/^1[3456789]\d{9}$/';
break;
}
}
}
return $newRule;
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace app\http\middleware;
use app\util\ReturnCode;
class WikiAuth {
/**
* ApiAuth鉴权
* @param \think\facade\Request $request
* @param \Closure $next
* @return mixed|\think\response\Json
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function handle($request, \Closure $next) {
$header = config('apiadmin.CROSS_DOMAIN');
$ApiAuth = $request->header('apiAuth', '');
if ($ApiAuth) {
$userInfo = cache('Login:' . $ApiAuth);
if (!$userInfo) {
$userInfo = cache('WikiLogin:' . $ApiAuth);
} else {
$userInfo = json_decode($userInfo, true);
$userInfo['app_id'] = -1;
}
if (!$userInfo || !isset($userInfo['id'])) {
return json([
'code' => ReturnCode::AUTH_ERROR,
'msg' => 'ApiAuth不匹配',
'data' => []
])->header($header);
} else {
$request->API_WIKI_USER_INFO = $userInfo;
}
return $next($request);
} else {
return json([
'code' => ReturnCode::AUTH_ERROR,
'msg' => '缺少ApiAuth',
'data' => []
])->header($header);
}
}
}

View File

@ -91,17 +91,17 @@ class RouterTool {
* @author zhaoxiang <zhaoxiang051405@gmail.com> * @author zhaoxiang <zhaoxiang051405@gmail.com>
*/ */
private static function getAdminMiddleware(array $menu): string { private static function getAdminMiddleware(array $menu): string {
$middle = ['AdminResponse']; $middle = ['app\middleware\AdminResponse::class'];
if ($menu['log']) { if ($menu['log']) {
array_unshift($middle, 'AdminLog'); array_unshift($middle, 'app\middleware\AdminLog::class');
} }
if ($menu['permission']) { if ($menu['permission']) {
array_unshift($middle, 'AdminPermission'); array_unshift($middle, 'app\middleware\AdminPermission::class');
} }
if ($menu['auth']) { if ($menu['auth']) {
array_unshift($middle, 'AdminAuth'); array_unshift($middle, 'app\middleware\AdminAuth::class');
} }
return '->middleware(["' . implode('", "', $middle) . '"]);'; return '->middleware([' . implode(', ', $middle) . ']);';
} }
} }