Anyon e7a8c05556 chore(repo): 统一 v8 仓库品牌名称
将 v8 重构分支中残留的 ThinkAdminDeveloper 文本统一调整为 ThinkAdmin,避免迁移到主仓库后继续暴露旧开发仓库名称。

主要内容:

- 更新 README 标题与项目描述。

- 统一 PHP 文件头注释中的项目标识。

- 同步调整测试、配置、插件与文档中的旧仓库名称文本。

- 保持旧包删除说明与架构边界测试语义不变,只清理品牌名称残留。
2026-05-08 16:15:24 +08:00

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);
}
}