mirror of
https://gitee.com/zoujingli/ThinkAdmin.git
synced 2026-06-08 04:48:10 +08:00
将 v8 重构分支中残留的 ThinkAdminDeveloper 文本统一调整为 ThinkAdmin,避免迁移到主仓库后继续暴露旧开发仓库名称。 主要内容: - 更新 README 标题与项目描述。 - 统一 PHP 文件头注释中的项目标识。 - 同步调整测试、配置、插件与文档中的旧仓库名称文本。 - 保持旧包删除说明与架构边界测试语义不变,只清理品牌名称残留。
251 lines
8.2 KiB
PHP
251 lines
8.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
/**
|
|
* +----------------------------------------------------------------------
|
|
* | ThinkAdmin Plugin
|
|
* +----------------------------------------------------------------------
|
|
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
|
* +----------------------------------------------------------------------
|
|
* | Official Website: https://thinkadmin.top
|
|
* +----------------------------------------------------------------------
|
|
* | Licensed: https://mit-license.org
|
|
* | Disclaimer: https://thinkadmin.top/disclaimer
|
|
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
|
* +----------------------------------------------------------------------
|
|
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
|
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
|
* +----------------------------------------------------------------------
|
|
*/
|
|
|
|
namespace think\admin\middleware;
|
|
|
|
use think\admin\extend\FileTools;
|
|
use think\admin\Library;
|
|
use think\admin\route\Route as AdminRoute;
|
|
use think\admin\runtime\RequestContext;
|
|
use think\admin\service\AppService;
|
|
use think\admin\service\NodeService;
|
|
use think\App;
|
|
use think\Request;
|
|
use think\Response;
|
|
use think\View;
|
|
|
|
/**
|
|
* 多应用调度中间件.
|
|
* @class MultAccess
|
|
*/
|
|
class MultAccess
|
|
{
|
|
/**
|
|
* 应用实例.
|
|
*/
|
|
private App $app;
|
|
|
|
/**
|
|
* 当前应用路径.
|
|
*/
|
|
private string $appPath = '';
|
|
|
|
/**
|
|
* 当前应用命名空间.
|
|
*/
|
|
private string $appSpace = '';
|
|
|
|
public function __construct(App $app)
|
|
{
|
|
$this->app = $app;
|
|
}
|
|
|
|
/**
|
|
* 多应用解析.
|
|
*/
|
|
public function handle(Request $request, \Closure $next): Response
|
|
{
|
|
[$this->appPath, $this->appSpace] = ['', ''];
|
|
if (!$this->parseMultiApp()) {
|
|
return $next($request);
|
|
}
|
|
|
|
return $this->app->middleware->pipeline('app')->send($request)->then(fn ($request) => $next($request));
|
|
}
|
|
|
|
/**
|
|
* 调度优先级:
|
|
* 1. 显式插件前缀
|
|
* 2. 显式本地 app 前缀
|
|
* 3. 根路由声明的目标应用
|
|
* 4. 动态插件切换
|
|
* 5. 默认本地 app.
|
|
*/
|
|
private function parseMultiApp(): bool
|
|
{
|
|
$request = $this->app->request;
|
|
$pathinfo = $request->pathinfo();
|
|
|
|
if ($plugin = AppService::matchPluginPath($pathinfo)) {
|
|
return $this->applyPlugin($plugin, true);
|
|
}
|
|
|
|
if ($local = AppService::matchPath($pathinfo)) {
|
|
return $this->applyLocal($local, true);
|
|
}
|
|
|
|
if ($target = $this->resolveGlobalRouteTarget($request)) {
|
|
return ($target['type'] ?? '') === 'plugin'
|
|
? $this->applyPlugin($target, false)
|
|
: $this->applyLocal($target, false);
|
|
}
|
|
|
|
$switch = AppService::detectPluginSwitch($request);
|
|
if ($switch && ($plugin = AppService::resolvePlugin($switch))) {
|
|
$plugin['entry'] = RequestContext::ENTRY_WEB;
|
|
$plugin['matched_prefix'] = '';
|
|
$plugin['pathinfo'] = $pathinfo;
|
|
return $this->applyPlugin($plugin, false);
|
|
}
|
|
|
|
RequestContext::instance()->setEntryType(RequestContext::ENTRY_WEB);
|
|
return $this->setMultiApp(AppService::defaultAppCode(), true);
|
|
}
|
|
|
|
/**
|
|
* 应用插件调度结果.
|
|
*
|
|
* @param array<string, mixed> $plugin
|
|
*/
|
|
private function applyPlugin(array $plugin, bool $stripPrefix): bool
|
|
{
|
|
[$this->appPath, $this->appSpace] = [strval($plugin['path'] ?? ''), strval($plugin['space'] ?? '')];
|
|
RequestContext::instance()->setEntryType(strval($plugin['entry'] ?? RequestContext::ENTRY_WEB));
|
|
|
|
$prefix = trim(strval($plugin['matched_prefix'] ?? ''), '\/');
|
|
if ($stripPrefix && $prefix !== '') {
|
|
$root = strval($plugin['entry'] ?? '') === RequestContext::ENTRY_API
|
|
? '/' . trim(AppService::pluginApiPrefix() . '/' . $prefix, '/')
|
|
: '/' . $prefix;
|
|
$this->app->request->setRoot($root);
|
|
$this->app->request->setPathinfo(strval($plugin['pathinfo'] ?? ''));
|
|
}
|
|
|
|
return $this->setMultiApp(strval($plugin['code'] ?? ''), true, $prefix);
|
|
}
|
|
|
|
/**
|
|
* 设置应用参数.
|
|
*/
|
|
private function setMultiApp(string $appName, bool $appBind, string $prefix = ''): bool
|
|
{
|
|
if ($appName === '') {
|
|
return false;
|
|
}
|
|
|
|
$app = AppService::get($appName);
|
|
($app['type'] ?? '') === 'plugin' ? AppService::activatePlugin($appName, $prefix) : AppService::activatePlugin();
|
|
|
|
if (empty($this->appPath) && $app) {
|
|
[$this->appPath, $this->appSpace] = [strval($app['path'] ?? ''), strval($app['space'] ?? '')];
|
|
}
|
|
|
|
if (!is_dir($this->appPath)) {
|
|
return false;
|
|
}
|
|
|
|
$this->app->setNamespace($this->appSpace ?: NodeService::space($appName))->setAppPath($this->appPath);
|
|
$this->app->http->setBind($appBind)->name($appName)->path($this->appPath)->setRoutePath($this->appPath . 'route' . DIRECTORY_SEPARATOR);
|
|
|
|
$replace = $this->app->config->get('view.tpl_replace_string', []);
|
|
$replace = is_array($replace) ? $replace : [];
|
|
$replace = array_filter($replace, static function ($value, $key) {
|
|
return is_string($key) && (is_scalar($value) || $value === null);
|
|
}, ARRAY_FILTER_USE_BOTH);
|
|
$replace = array_map(static fn ($value) => strval($value ?? ''), $replace);
|
|
$uris = array_merge($replace, AppService::uris());
|
|
$viewConfig = [
|
|
'view_path' => $this->appPath . 'view' . DIRECTORY_SEPARATOR,
|
|
'tpl_replace_string' => $uris,
|
|
];
|
|
$this->app->config->set($viewConfig, 'view');
|
|
$this->app->make(View::class)->engine()->config($viewConfig);
|
|
|
|
return $this->loadMultiApp($this->appPath);
|
|
}
|
|
|
|
/**
|
|
* 加载应用文件.
|
|
* @codeCoverageIgnore
|
|
*/
|
|
private function loadMultiApp(string $appPath): bool
|
|
{
|
|
[$ext, $fmaps] = [$this->app->getConfigExt(), []];
|
|
|
|
if (is_file($file = "{$appPath}common{$ext}")) {
|
|
Library::load($file);
|
|
}
|
|
|
|
FileTools::find($appPath . 'config', 1, function (\SplFileInfo $info) use ($ext, &$fmaps) {
|
|
if ($info->isFile() && strtolower(".{$info->getExtension()}") === $ext) {
|
|
$name = $info->getBasename($ext);
|
|
$fmaps[] = $name;
|
|
$this->app->config->load($info->getPathname(), $name);
|
|
}
|
|
});
|
|
|
|
if ((in_array('route', $fmaps, true) || is_dir($appPath . 'route')) && method_exists($this->app->route, 'reload')) {
|
|
$this->app->route->reload();
|
|
}
|
|
|
|
if (is_file($file = "{$appPath}provider{$ext}")) {
|
|
$this->app->bind(include $file);
|
|
}
|
|
|
|
if (is_file($file = "{$appPath}event{$ext}")) {
|
|
$this->app->loadEvent(include $file);
|
|
}
|
|
|
|
if (is_file($file = "{$appPath}middleware{$ext}")) {
|
|
$this->app->middleware->import(include $file, 'app');
|
|
}
|
|
|
|
if (method_exists($this->app->lang, 'switchLangSet')) {
|
|
$this->app->lang->switchLangSet($this->app->lang->getLangSet());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 应用本地 app 调度结果.
|
|
*
|
|
* @param array<string, mixed> $local
|
|
*/
|
|
private function applyLocal(array $local, bool $stripPrefix): bool
|
|
{
|
|
[$this->appPath, $this->appSpace] = [strval($local['path'] ?? ''), strval($local['space'] ?? '')];
|
|
RequestContext::instance()->setEntryType(strval($local['entry'] ?? RequestContext::ENTRY_WEB));
|
|
|
|
$prefix = trim(strval($local['matched_prefix'] ?? ''), '\/');
|
|
if ($stripPrefix && $prefix !== '') {
|
|
$this->app->request->setRoot('/' . $prefix);
|
|
$this->app->request->setPathinfo(strval($local['pathinfo'] ?? ''));
|
|
}
|
|
|
|
return $this->setMultiApp(strval($local['code'] ?? ''), true);
|
|
}
|
|
|
|
/**
|
|
* 从根路由预解析目标应用。
|
|
*
|
|
* @return null|array<string, mixed>
|
|
*/
|
|
private function resolveGlobalRouteTarget(Request $request): ?array
|
|
{
|
|
$route = $this->app->route;
|
|
if (!$route instanceof AdminRoute) {
|
|
return null;
|
|
}
|
|
|
|
return $route->resolveTarget($request, $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR);
|
|
}
|
|
}
|