13 changed files with 667 additions and 3 deletions
-
93plugin/piadmin/app/controller/v1/ProxyMenuController.php
-
30plugin/piadmin/app/controller/v1/ProxyUserLoginController.php
-
17plugin/piadmin/app/dao/ProxyMenuDao.php
-
17plugin/piadmin/app/dao/ProxyUserDao.php
-
82plugin/piadmin/app/middleware/ProxyUserAuthorizationMiddleware.php
-
16plugin/piadmin/app/model/ProxyMenu.php
-
21plugin/piadmin/app/model/ProxyUser.php
-
21plugin/piadmin/app/route/v1/proxy.php
-
13plugin/piadmin/app/route/v1/route.php
-
222plugin/piadmin/app/service/ProxyMenuService.php
-
130plugin/piadmin/app/service/ProxyUserService.php
-
6plugin/piadmin/config/piadmin.php
-
2plugin/piadmin/config/route.php
@ -0,0 +1,93 @@ |
|||
<?php |
|||
|
|||
namespace plugin\piadmin\app\controller\v1; |
|||
|
|||
use plugin\piadmin\app\base\BaseController; |
|||
use plugin\piadmin\app\service\ProxyMenuService; |
|||
use plugin\piadmin\app\validate\MenuValidate; |
|||
use support\Request; |
|||
|
|||
class ProxyMenuController extends BaseController |
|||
{ |
|||
|
|||
public function __construct() |
|||
{ |
|||
parent::__construct(); |
|||
$this->service = app()->make(ProxyMenuService::class); |
|||
} |
|||
|
|||
public function index(Request $request) |
|||
{ |
|||
|
|||
$all = $request->all(); |
|||
|
|||
$list = $this->service->getMenuList($all); |
|||
|
|||
return success($list); |
|||
} |
|||
|
|||
public function save(Request $request) |
|||
{ |
|||
$params = requestOnly([ |
|||
'pid' => 0, |
|||
'name' => '', |
|||
'code' => '', |
|||
'icon' => '', |
|||
'route' => '', |
|||
'component' => '', |
|||
'redirect' => '', |
|||
'is_hidden' => 2, |
|||
'is_layout' => 2, |
|||
'type' => '', |
|||
'status' => 1, |
|||
'sort' => 1000, |
|||
'remark' => '' |
|||
]); |
|||
validate(MenuValidate::class)->scene('save')->check($params); |
|||
$res = $this->service->saveData($params); |
|||
return success($res); |
|||
} |
|||
|
|||
public function update(Request $request) |
|||
{ |
|||
$params = requestOnly([ |
|||
'id' => '', |
|||
'pid' => 0, |
|||
'name' => '', |
|||
'code' => '', |
|||
'icon' => '', |
|||
'route' => '', |
|||
'component' => '', |
|||
'redirect' => '', |
|||
'is_hidden' => 2, |
|||
'is_layout' => 2, |
|||
'type' => '', |
|||
'status' => 1, |
|||
'sort' => 1000, |
|||
'remark' => '' |
|||
]); |
|||
validate(MenuValidate::class)->scene('update')->check($params); |
|||
$res = $this->service->updateData($params); |
|||
return success($res); |
|||
} |
|||
|
|||
public function delete(Request $request) |
|||
{ |
|||
$ids = $request->input('ids'); |
|||
$res = $this->service->deleteData($ids); |
|||
return success($res); |
|||
} |
|||
|
|||
public function read(Request $request) |
|||
{ |
|||
$id = $request->input('id'); |
|||
$res = $this->service->readData($id); |
|||
return success($res); |
|||
} |
|||
|
|||
public function getProxyMenuPermissions() |
|||
{ |
|||
return success($this->service->getUserMenu()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
<?php |
|||
|
|||
namespace plugin\piadmin\app\controller\v1; |
|||
|
|||
use plugin\piadmin\app\base\BaseController; |
|||
use plugin\piadmin\app\service\ProxyUserService; |
|||
use plugin\piadmin\app\validate\LoginValidate; |
|||
use support\Request; |
|||
|
|||
class ProxyUserLoginController extends BaseController |
|||
{ |
|||
|
|||
public function __construct() |
|||
{ |
|||
parent::__construct(); |
|||
$this->service = app()->make(ProxyUserService::class); |
|||
} |
|||
|
|||
public function login(Request $request){ |
|||
$params = $request->only([ |
|||
'username', |
|||
'password' |
|||
]); |
|||
validate(LoginValidate::class)->scene('loginByPassword')->check($params); |
|||
// 用户登录
|
|||
$res = $this->service->login($params); |
|||
return success($res); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
<?php |
|||
|
|||
namespace plugin\piadmin\app\dao; |
|||
|
|||
use plugin\piadmin\app\base\BaseDao; |
|||
use plugin\piadmin\app\model\ProxyMenu; |
|||
|
|||
class ProxyMenuDao extends BaseDao |
|||
{ |
|||
|
|||
protected function setModel(): string |
|||
{ |
|||
return ProxyMenu::class; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
<?php |
|||
|
|||
namespace plugin\piadmin\app\dao; |
|||
|
|||
use plugin\piadmin\app\base\BaseDao; |
|||
use plugin\piadmin\app\model\ProxyUser; |
|||
|
|||
class ProxyUserDao extends BaseDao |
|||
{ |
|||
|
|||
protected function setModel(): string |
|||
{ |
|||
return ProxyUser::class; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
<?php |
|||
namespace plugin\piadmin\app\middleware; |
|||
|
|||
use Firebase\JWT\JWT; |
|||
use Firebase\JWT\Key; |
|||
use plugin\piadmin\app\dao\ProxyUserDao; |
|||
use plugin\piadmin\app\exception\ApiException; |
|||
use plugin\piadmin\app\utils\CacheUtils; |
|||
use support\Context; |
|||
use Webman\Http\Request; |
|||
use Webman\Http\Response; |
|||
use Webman\MiddlewareInterface; |
|||
|
|||
class ProxyUserAuthorizationMiddleware implements MiddlewareInterface |
|||
{ |
|||
public function process(Request $request, callable $handler) : Response |
|||
{ |
|||
$authHeader = config('plugin.piadmin.piadmin.jwt.proxy.auth_header'); |
|||
$token = $request->header($authHeader, ''); |
|||
if (empty($token)) { |
|||
throw new ApiException(403); |
|||
} |
|||
$token = trim(str_replace('Bearer ', '', $token)); |
|||
// 解token payload
|
|||
$key = config("plugin.piadmin.piadmin.jwt.proxy.key"); |
|||
$keyId = config("plugin.piadmin.piadmin.jwt.proxy.key_id"); |
|||
$payload = JWT::decode($token, new Key($key, 'HS256')); |
|||
if (empty($token)) { |
|||
throw new ApiException(403); |
|||
} |
|||
// 缓存
|
|||
$tokenCacheData = CacheUtils::get('proxy.token.'. md5($token)); |
|||
|
|||
if (empty($tokenCacheData)) { |
|||
throw new ApiException(403); |
|||
} |
|||
$tokenCacheData = json_decode($tokenCacheData, true); |
|||
if (empty($tokenCacheData) || !isset($tokenCacheData['uid'])) { |
|||
throw new ApiException(403); |
|||
} |
|||
// 验证账号状态
|
|||
$uid = $tokenCacheData['uid']; |
|||
$userDao = app()->make(ProxyUserDao::class); |
|||
$user = $userDao->getOne(['id' => $uid]); |
|||
if (empty($user)) { |
|||
throw new ApiException(403); |
|||
} |
|||
if ($user['status'] != 1 ) { |
|||
throw new ApiException(400006); |
|||
} |
|||
$proxyUserInfo = $user->toArray(); |
|||
$proxyUserInfo['is_login'] = true; |
|||
$proxyUserInfo['uid'] = $proxyUserInfo['id']; |
|||
$proxyUserInfo['token'] = $token; |
|||
//数据权限,目前只查自己
|
|||
$proxyUserInfo['dataPermission'] = [$proxyUserInfo['id']]; |
|||
// 检查版本
|
|||
$this->checkVersionKey($uid, $token); |
|||
$request->proxy_user = $proxyUserInfo; |
|||
// todo 补全session
|
|||
Context::set('proxy_user', $proxyUserInfo); |
|||
// session('user', $userInfo);
|
|||
return $handler($request); |
|||
} |
|||
|
|||
/** |
|||
* 版本检查 |
|||
* @param string|int $uid |
|||
* @param string $token |
|||
* @return void |
|||
* @throws ApiException |
|||
*/ |
|||
private function checkVersionKey(string|int $uid, string $token): void |
|||
{ |
|||
$userVersionKey = "user.version.{$uid}"; |
|||
$version = CacheUtils::get($userVersionKey); |
|||
if ($version != getPiAdminVersion()) { |
|||
CacheUtils::delete(md5($token)); |
|||
throw new ApiException(403); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<?php |
|||
|
|||
namespace plugin\piadmin\app\model; |
|||
|
|||
use plugin\piadmin\app\base\BaseModel; |
|||
use think\model\concern\SoftDelete; |
|||
|
|||
class ProxyMenu extends BaseModel |
|||
{ |
|||
protected $table = 'pi_proxy_menu'; |
|||
|
|||
use SoftDelete; |
|||
protected string $deleteTime = 'delete_time'; |
|||
|
|||
protected $defaultSoftDelete = 0; |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
namespace plugin\piadmin\app\model; |
|||
|
|||
use plugin\piadmin\app\base\BaseModel; |
|||
use think\model\concern\SoftDelete; |
|||
|
|||
class ProxyUser extends BaseModel |
|||
{ |
|||
protected $table = 'pi_proxy_user'; |
|||
|
|||
protected $hidden = ['password']; |
|||
|
|||
use SoftDelete; |
|||
protected string $deleteTime = 'delete_time'; |
|||
|
|||
protected $defaultSoftDelete = 0; |
|||
|
|||
protected $append = []; |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
use plugin\piadmin\app\controller\v1\ProxyMenuController; |
|||
use plugin\piadmin\app\controller\v1\ProxyUserLoginController; |
|||
use plugin\piadmin\app\middleware\ProxyUserAuthorizationMiddleware; |
|||
use Webman\Route; |
|||
|
|||
// 不需要登录的接口
|
|||
Route::group('/piadmin/v1/proxy', function () { |
|||
//账号密码登录
|
|||
Route::post('/login', [ProxyUserLoginController::class, 'login'])->setParams(['perm' => ['userLogin']]); |
|||
}); |
|||
|
|||
// 需要登录的接口
|
|||
Route::group('/piadmin/v1/proxy', function () { |
|||
// 获取当前用户菜单权限
|
|||
Route::get('/getMenuPermissions', [ProxyMenuController::class, 'getProxyMenuPermissions'])->setParams(['perm' => ['getProxyMenuPermissions']]); |
|||
|
|||
})->middleware([ |
|||
ProxyUserAuthorizationMiddleware::class, |
|||
]); |
|||
@ -0,0 +1,222 @@ |
|||
<?php |
|||
|
|||
namespace plugin\piadmin\app\service; |
|||
|
|||
use plugin\piadmin\app\base\BaseService; |
|||
use plugin\piadmin\app\dao\ProxyMenuDao; |
|||
use plugin\piadmin\app\exception\ApiException; |
|||
use plugin\piadmin\app\utils\IdPathUtils; |
|||
use plugin\piadmin\app\utils\IdUtils; |
|||
use plugin\piadmin\app\utils\TreeUtils; |
|||
use support\Log; |
|||
use think\facade\Db; |
|||
|
|||
class ProxyMenuService extends BaseService |
|||
{ |
|||
|
|||
public function __construct(ProxyMenuDao $dao) |
|||
{ |
|||
$this->dao = $dao; |
|||
} |
|||
|
|||
|
|||
public function getMenuList($params = []) |
|||
{ |
|||
$where = []; |
|||
$list = $this->dao->getList($where, '*', 0, 0, 'sort asc'); |
|||
return TreeUtils::toTree($list); |
|||
} |
|||
|
|||
/** |
|||
* 保存数据 |
|||
* @param $params |
|||
* @return \plugin\piadmin\app\base\BaseModel |
|||
* @throws \think\db\exception\DataNotFoundException |
|||
* @throws \think\db\exception\DbException |
|||
* @throws \think\db\exception\ModelNotFoundException |
|||
*/ |
|||
public function saveData($params) |
|||
{ |
|||
// 检查code
|
|||
$codeExist = $this->dao->be([ |
|||
'code'=>$params['code'] |
|||
]); |
|||
if ($codeExist) { |
|||
throw new ApiException(trans(401003)); |
|||
} |
|||
$parentMenu = $this->getMenu($params['pid']); |
|||
if (empty($parentMenu)) { |
|||
throw new ApiException(trans(401001)); |
|||
} |
|||
$newUuid = IdUtils::uuid(); |
|||
$newIdPath = IdPathUtils::addItem($parentMenu['id_path'], $newUuid); |
|||
$params['id_path'] = $newIdPath; |
|||
$params['uuid'] = $newUuid; |
|||
return $this->dao->save($params); |
|||
} |
|||
|
|||
/** |
|||
* 更新数据 |
|||
* @param $params |
|||
* @return \plugin\piadmin\app\base\BaseModel |
|||
* @throws \think\db\exception\DataNotFoundException |
|||
* @throws \think\db\exception\DbException |
|||
* @throws \think\db\exception\ModelNotFoundException |
|||
*/ |
|||
public function updateData($params) |
|||
{ |
|||
$menu = $this->dao->get($params['id']); |
|||
if (empty($menu)) { |
|||
throw new ApiException(trans(401005)); |
|||
} |
|||
if($params['pid'] == $menu['id']){ |
|||
throw new ApiException(trans(401008)); |
|||
} |
|||
// code有没有变化
|
|||
if ($params['code'] != $menu['code']) { |
|||
$codeExist = $this->dao->be([ |
|||
'code'=>$params['code'] |
|||
]); |
|||
if ($codeExist) { |
|||
throw new ApiException(trans(401003)); |
|||
} |
|||
} |
|||
$newIdPath = ''; |
|||
// 上级菜单有没有变化
|
|||
if ($params['pid'] != $menu['pid']) { |
|||
$parentMenu = $this->getMenu($params['pid']); |
|||
if (empty($parentMenu)) { |
|||
throw new ApiException(trans(401001)); |
|||
} |
|||
$newIdPath = IdPathUtils::addItem($parentMenu['id_path'], $menu['id']); |
|||
$params['id_path'] = $newIdPath; |
|||
} |
|||
unset($params['id']); |
|||
|
|||
// 落库
|
|||
Db::startTrans(); |
|||
try { |
|||
$this->dao->update(['id' => $menu['id']], $params); |
|||
if (!empty($newIdPath)) { |
|||
$this->updateAllChildrenIdPath($menu['id_path'], $newIdPath); |
|||
} |
|||
Db::commit(); |
|||
} catch (\Exception $exception) { |
|||
Db::rollback(); |
|||
Log::error($exception->getTraceAsString()); |
|||
throw new ApiException($exception->getMessage()); |
|||
} |
|||
return $params; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 删除数据 |
|||
* @param mixed $id |
|||
* @return array |
|||
*/ |
|||
public function deleteData($ids) |
|||
{ |
|||
//根据ids获取所有子菜单
|
|||
$ids = $this->getChildrenMenuIdsBatch($ids); |
|||
// 落库
|
|||
Db::startTrans(); |
|||
try { |
|||
foreach ($ids as $id) { |
|||
$this->dao->softDel($id); |
|||
} |
|||
Db::commit(); |
|||
} catch (\Exception $exception) { |
|||
Db::rollback(); |
|||
Log::error($exception->getTraceAsString()); |
|||
throw new ApiException($exception->getMessage()); |
|||
} |
|||
return []; |
|||
} |
|||
|
|||
/** |
|||
* 获取数据 |
|||
* @param mixed $id |
|||
* @return array |
|||
* @throws \think\db\exception\DataNotFoundException |
|||
* @throws \think\db\exception\DbException |
|||
* @throws \think\db\exception\ModelNotFoundException |
|||
*/ |
|||
public function readData(mixed $id) |
|||
{ |
|||
$menu = $this->dao->get(['id' => $id]); |
|||
if (empty($menu)) { |
|||
return []; |
|||
} |
|||
return $menu->toArray(); |
|||
} |
|||
|
|||
public function getUserMenu() |
|||
{ |
|||
$list = $this->dao->getList([], '*', 0, 0, 'sort asc'); |
|||
return TreeUtils::toTree($list); |
|||
} |
|||
|
|||
// ============================================================ 私有方法 ===============================================
|
|||
|
|||
|
|||
/** |
|||
* 获取菜单 |
|||
* @param $id |
|||
* @return array |
|||
* @throws \think\db\exception\DataNotFoundException |
|||
* @throws \think\db\exception\DbException |
|||
* @throws \think\db\exception\ModelNotFoundException |
|||
*/ |
|||
private function getMenu($id): array |
|||
{ |
|||
if ($id == 0) { |
|||
return [ |
|||
'id' => 0, |
|||
'uuid' => 0, |
|||
'name' => '顶级菜单', |
|||
'code' => 'top', |
|||
'type' => 'M', |
|||
'status' => 1, |
|||
'sort' => 1000, |
|||
'pid' => 0, |
|||
'id_path' => '0', |
|||
]; |
|||
} |
|||
$menu = $this->dao->get(['id' => $id]); |
|||
if (empty($menu)) { |
|||
return []; |
|||
} |
|||
return $menu->toArray(); |
|||
} |
|||
|
|||
private function updateAllChildrenIdPath($oldIdPath, $newIdPath) |
|||
{ |
|||
// 获取所有子级
|
|||
$childrenIds = $this->dao->getColumn([ |
|||
['id_path', 'like', $oldIdPath . ',%'] |
|||
], 'id'); |
|||
$children = $this->dao->getList([ |
|||
['id', 'in', $childrenIds] |
|||
], 'id, id_path'); |
|||
foreach ($children as $child) { |
|||
$newIdPath = str_replace($oldIdPath, $newIdPath, $child['id_path']); |
|||
$this->dao->update(['id' => $child['id']], [ |
|||
'id_path' => $newIdPath |
|||
]); |
|||
} |
|||
} |
|||
|
|||
public function getChildrenMenuIdsBatch(array $ids): array |
|||
{ |
|||
$allMenus = $this->dao->getList([], 'id, pid'); |
|||
$allChildrenIds = []; |
|||
|
|||
// 批量处理所有ID的子节点查找
|
|||
foreach ($ids as $id) { |
|||
$childIds = TreeUtils::getChildrenIds($allMenus, $id); |
|||
$allChildrenIds = array_merge($allChildrenIds, $childIds); |
|||
} |
|||
return array_values(array_unique($allChildrenIds)); // 重新索引并去重
|
|||
} |
|||
} |
|||
@ -0,0 +1,130 @@ |
|||
<?php |
|||
|
|||
namespace plugin\piadmin\app\service; |
|||
|
|||
use plugin\piadmin\app\base\BaseService; |
|||
use plugin\piadmin\app\dao\ProxyUserDao; |
|||
use plugin\piadmin\app\exception\ApiException; |
|||
use plugin\piadmin\app\utils\CacheUtils; |
|||
use plugin\piadmin\app\utils\JwtUtils; |
|||
|
|||
class ProxyUserService extends BaseService |
|||
{ |
|||
|
|||
|
|||
|
|||
public function __construct(ProxyUserDao $dao) |
|||
{ |
|||
$this->dao = $dao; |
|||
} |
|||
|
|||
// 用户登录
|
|||
public function login($params) |
|||
{ |
|||
$proxyUser = $this->dao->getModel()->where(function ($query) use ($params){ |
|||
$query->where('email', '=', $params['username']) |
|||
->whereOr('phone', '=', $params['username']); |
|||
})->find(); |
|||
$this->validPassword($proxyUser, $params); |
|||
$tokenInfo = $this->commonLogin($proxyUser, $params); |
|||
$tokenInfo['user'] = $proxyUser->toArray(); |
|||
$tokenInfo['expire_time'] = $tokenInfo['expire'] + time(); |
|||
return $tokenInfo; |
|||
} |
|||
|
|||
// ============================================================ 私有方法 ===============================================
|
|||
|
|||
|
|||
/** |
|||
* 验证密码 |
|||
* @param mixed $adminUser |
|||
* @param $params |
|||
* @return void |
|||
*/ |
|||
private function validPassword(mixed $proxyUser, $params): void |
|||
{ |
|||
// 验证登录信息
|
|||
if (empty($proxyUser)) { |
|||
throw new ApiException(trans(400005)); |
|||
} |
|||
// 密码验证
|
|||
if (!password_verify($params['password'], $proxyUser['password'])) { |
|||
throw new ApiException(trans(400005)); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 通用登录流程 |
|||
* @param mixed $adminUser |
|||
* @param array $params |
|||
* @return array |
|||
* @throws ApiException |
|||
*/ |
|||
private function commonLogin(mixed $proxyUser, array $params) |
|||
{ |
|||
if ($proxyUser['status'] == 2) { |
|||
throw new ApiException(trans(400006)); |
|||
} |
|||
$tokenInfo = JwtUtils::createToken($proxyUser['id'], 'proxy'); |
|||
$tokenCacheInfo = [ |
|||
'uid' => $proxyUser['id'], |
|||
'type' => 'user_token', |
|||
'token' => $tokenInfo['token'], |
|||
'expire_time' => time() + $tokenInfo['expire'], |
|||
'user' => $proxyUser->toArray(), |
|||
'login_time' => date('Y-m-d H:i:s'), |
|||
'os_type' => $this->getClientOS(), |
|||
'browser_name' => $this->getClientBrowserName(), |
|||
'login_ip' => request()->getRemoteIp() |
|||
]; |
|||
// 保存token缓存
|
|||
CacheUtils::set('proxy.token.' . md5($tokenInfo['token']), json_encode($tokenCacheInfo), (int)$tokenInfo['expire']); |
|||
// 保存用户对应版本缓存
|
|||
$currentVersion = getPiAdminVersion(); |
|||
$userVersionKey = "proxy.version.{$proxyUser['id']}"; |
|||
CacheUtils::set($userVersionKey, $currentVersion, (int)$tokenInfo['expire']); |
|||
return $tokenInfo; |
|||
} |
|||
|
|||
/** |
|||
* 获取客户端操作系统 |
|||
* @return int |
|||
*/ |
|||
private function getClientOS(): int |
|||
{ |
|||
$userAgent = request()->header('user-agent'); |
|||
if (stripos($userAgent, 'Windows') !== false) { |
|||
$os = 1; |
|||
} elseif (stripos($userAgent, 'Mac') !== false) { |
|||
$os = 2; |
|||
} elseif (stripos($userAgent, 'Linux') !== false) { |
|||
$os = 3; |
|||
} elseif (stripos($userAgent, 'iPhone|iPad|iPod') !== false) { |
|||
$os = 4; |
|||
} elseif (stripos($userAgent, 'Android') !== false) { |
|||
$os = 5; |
|||
} else { |
|||
$os = 6; |
|||
} |
|||
return $os; |
|||
} |
|||
|
|||
/** |
|||
* 获取客户端浏览器名称 |
|||
* @return string |
|||
*/ |
|||
private function getClientBrowserName(): string |
|||
{ |
|||
$userAgent = request()->header('user-agent'); |
|||
$pattern = '/(MSIE|Trident|Edge|Firefox|Chrome|Safari|Opera|OPR)\/?\s*(\d+\.\d+)?/i'; |
|||
preg_match($pattern, $userAgent, $matches); |
|||
if (isset($matches[1])) { |
|||
// 处理Edge和Opera的特殊标识
|
|||
if ($matches[1] === 'Trident') return 'Internet Explorer'; |
|||
if ($matches[1] === 'OPR') return 'Opera'; |
|||
return $matches[1]; |
|||
} |
|||
return 'Unknown Browser'; |
|||
} |
|||
|
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue