<?php
declare(strict_types=1);
* +----------------------------------------------------------------------
* | ThinkAdmin Plugin for ThinkAdmin
* +----------------------------------------------------------------------
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
* +----------------------------------------------------------------------
* | 官方网站: https://thinkadmin.top
* +----------------------------------------------------------------------
* | 开源协议 ( https://mit-license.org )
* | 免责声明 ( https://thinkadmin.top/disclaimer )
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
* +----------------------------------------------------------------------
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
* +----------------------------------------------------------------------
*/
namespace think\admin\service;
use think\admin\Exception;
use think\admin\extend\ToolsExtend;
use think\admin\Library;
use think\admin\Plugin;
use think\admin\Service;
* 应用节点服务管理.
* @class NodeService
* @method static array getModules() 获取应用列表
* @method static array scanDirectory() 扫描目录列表
*/
class NodeService extends Service
{
* 重构兼容处理.
* @return array
* @throws Exception
*/
public function __call(string $name, array $arguments)
{
return static::__callStatic($name, $arguments);
}
* 重构兼容处理.
* @return array
* @throws Exception
*/
public static function __callStatic(string $name, array $arguments)
{
if ($name === 'scanDirectory') {
return ToolsExtend::scan(...$arguments);
}
if ($name === 'getModules') {
return ModuleService::getModules(...$arguments);
}
throw new Exception("method not exists: NodeService::{$name}()");
}
* 获取默认应用空间名.
* @param string $suffix 后缀路径
*/
public static function space(string $suffix = ''): string
{
$default = Library::$sapp->config->get('app.app_namespace') ?: 'app';
return empty($suffix) ? $default : trim($default . '\\' . trim($suffix, '\/'), '\\');
}
* 驼峰转下划线规则.
*/
public static function nameTolower(string $name): string
{
$dots = [];
foreach (explode('.', strtr($name, '/', '.')) as $dot) {
$dots[] = trim(preg_replace('/[A-Z]/', '_\0', $dot), '_');
}
return strtolower(join('.', $dots));
}
* 获取当前节点内容.
* @param string $type app|module|controller|action
*/
public static function getCurrent(string $type = ''): string
{
$appname = strtolower(Library::$sapp->http->getName());
if (in_array($type, ['app', 'module'])) {
return $appname;
}
$controller = static::nameTolower(Library::$sapp->request->controller());
if ($type === 'controller') {
return "{$appname}/{$controller}";
}
$method = strtolower(Library::$sapp->request->action());
return "{$appname}/{$controller}/{$method}";
}
* 检查并完整节点内容.
*/
public static function fullNode(?string $node = ''): string
{
if (empty($node)) {
return static::getCurrent();
}
switch (count($attrs = explode('/', $node))) {
case 1:
return static::getCurrent('controller') . '/' . strtolower($node);
case 2:
$suffix = static::nameTolower($attrs[0]) . '/' . $attrs[1];
return static::getCurrent('module') . '/' . strtolower($suffix);
default:
$attrs[1] = static::nameTolower($attrs[1]);
return strtolower(join('/', $attrs));
}
}
* 获取所有控制器入口.
* @param bool $force 强制更新
*/
public static function getMethods(bool $force = false): array
{
$skey = 'think.admin.methods';
if (empty($force)) {
$data = sysvar($skey) ?: Library::$sapp->cache->get('SystemAuthNode', []);
if (count($data) > 0) {
return sysvar($skey, $data);
}
} else {
$data = [];
}
$ignoreMethods = get_class_methods('\think\admin\Controller');
$ignoreAppNames = Library::$sapp->config->get('app.rbac_ignore', []);
foreach (ToolsExtend::scan(Library::$sapp->getBasePath(), null, 'php') as $name) {
if (preg_match('|^(\w+)/controller/(.+)\.php$|i', strtr($name, '\\', '/'), $matches)) {
[, $appName, $className] = $matches;
if (in_array($appName, $ignoreAppNames)) {
continue;
}
static::_parseClass($appName, self::space($appName), $className, $ignoreMethods, $data);
}
}
foreach (Plugin::get() as $appName => $plugin) {
if (in_array($appName, $ignoreAppNames)) {
continue;
}
[$appPath, $appSpace] = [$plugin['path'], $plugin['space']];
foreach (ToolsExtend::scan($appPath, null, 'php') as $name) {
if (preg_match('|^.*?controller/(.+)\.php$|i', strtr($name, '\\', '/'), $matches)) {
static::_parseClass($appName, $appSpace, $matches[1], $ignoreMethods, $data);
}
}
}
if (function_exists('admin_node_filter')) {
$data = call_user_func('admin_node_filter', $data);
}
Library::$sapp->cache->set('SystemAuthNode', $data);
return sysvar($skey, $data);
}
* 解析节点数据.
* @param string $appName 应用名称
* @param string $appSpace 应用空间
* @param string $className 应用类型
* @param array $ignoreNode 忽略节点
* @param array $data 绑定节点的数据
*/
private static function _parseClass(string $appName, string $appSpace, string $className, array $ignoreNode, array &$data)
{
$classfull = strtr("{$appSpace}/controller/{$className}", '/', '\\');
if (class_exists($classfull) && ($class = new \ReflectionClass($classfull))) {
$prefix = strtolower(strtr("{$appName}/" . static::nameTolower($className), '\\', '/'));
$data[$prefix] = static::_parseComment($class->getDocComment() ?: '', $className);
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
if (in_array($metname = $method->getName(), $ignoreNode)) {
continue;
}
$data[strtolower("{$prefix}/{$metname}")] = static::_parseComment($method->getDocComment() ?: '', $metname);
}
}
}
* 解析硬节点属性.
* @param string $comment 备注内容
* @param string $default 默认标题
*/
private static function _parseComment(string $comment, string $default = ''): array
{
$text = strtr($comment, "\n", ' ');
$title = preg_replace('/^\/\*\s*\*\s*\*\s*(.*?)\s*\*.*?$/', '$1', $text);
if (in_array(substr($title, 0, 5), ['@auth', '@menu', '@logi'])) {
$title = $default;
}
return [
'title' => $title ?: $default,
'isauth' => intval(preg_match('/@auth\s*true/i', $text)),
'ismenu' => intval(preg_match('/@menu\s*true/i', $text)),
'islogin' => intval(preg_match('/@login\s*true/i', $text)),
];
}
}