Search by

umaos / webman

umaos

Webman plugin umaos/webman

Package info

gitee.com/umaos/webman.git

pkg:composer/umaos/webman

Statistics

Installs: 15

Dependents: 0

Suggesters: 0

dev-master 2026-11-14 16:11 UTC

This package is auto-updated.

Last update: 2026-11-14 16:11:54 UTC


README

面向 Webman 2 的基础能力包,用来和业务工程(如 UMaos 平台)配套使用,提供三端(前台 index / 管理端 admin / 系统中台 system)的登录鉴权、RBAC 权限、统一响应、公共布局、权限节点生成,以及基础插件配置与静态资源安装。

  • 包类型:library + Webman 基础插件(通过 umaos\webman\Install::WEBMAN_PLUGIN 触发安装钩子)
  • 命名空间:umaos\webman\ → src/
  • 依赖:PHP >= 8.1、workerman/webman-framework ^2、webman/think-orm ^2
  • 许可证:MIT

本包只提供“鉴权 + 基础设施”,不包含业务用户管理界面、业务表结构、前端路由。业务页面与接口由使用方工程实现。

目录

  1. 安装与依赖
  2. 目录与模块结构
  3. 配置方式
  4. 请求生命周期与 umaos_init
  5. 控制器侧约定
  6. 登录与鉴权
  7. RBAC 规则匹配
  8. 统一 JSON / 页面响应
  9. 布局渲染
  10. 权限节点生成
  11. 基础插件与静态资源
  12. 数据表与会话键约定
  13. 典型接入步骤
  14. 已知问题与注意事项
  15. 常用 API 速查

1. 安装与依赖

1.1 版本要求

依赖版本说明
PHP>= 8.1使用了 mixed 类型、str_contains 等
workerman/webman-framework^2多应用路由、support\Plugin、Session Bootstrap
webman/think-orm^25 个模型继承 support\think\Model

包内 composer.json:

{
  "name": "umaos/webman",
  "type": "library",
  "require": {
    "php": ">=8.1",
    "workerman/webman-framework": "^2",
    "webman/think-orm": "^2"
  },
  "autoload": {
    "psr-4": { "umaos\\webman\\": "src" }
  }
}

1.2 安装

本包没有发布稳定 tag,配套工程按 dev-master 引入:

composer config minimum-stability dev
composer config prefer-stable true
composer require umaos/webman:dev-master

安装/更新时,support\Plugin 会检测 Install::WEBMAN_PLUGIN 常量,并分别回调 Install::install() / Install::update(),把包内 src/config/plugin 部署到主工程:

config/plugin/umaos/webman/
├── app.php        # 插件主配置
├── route.php      # /plugin/umaos/* 静态资源路由
└── autoload.php   # 全局助手函数加载(helpers.php)

安装后请确认这 3 个文件存在;autoload.php 缺失时所有 success()/error()/login() 都会报未定义函数。

1.3 升级与卸载

  • composer update umaos/webman → 再次执行 install();已存在的配置文件不会被覆盖(src/Install.php:105-128)。
  • 因此:如果新版本新增了配置项或 autoload.php,旧工程不会自动获得,需要手工从 vendor/umaos/webman/src/config/plugin/umaos/webman/ 复制缺失文件。
  • composer remove umaos/webman → 执行 uninstall(),删除主工程 config/plugin/umaos/webman/ 整目录(src/Install.php:82-95)。

提示:本 README 位于 vendor/ 内,执行 composer install/update 时会被包仓库中的版本覆盖。需要长期保留的说明请同步到业务工程的文档目录。

2. 目录与模块结构

umaos/webman
├── composer.json
├── README.md
├── public/                        # 前端静态资源(由 /plugin/umaos/* 路由暴露)
│   ├── axios.min.js
│   ├── element-plus/
│   ├── icons-vue/
│   ├── pinia.iife.js
│   ├── pinia.iife.prod.js
│   ├── umaos.css
│   ├── vue/
│   └── wallpaper.jpg
└── src/
    ├── helpers.php                # 全局助手函数(由插件 autoload 加载)
    ├── Controller.php             # 可选基类:反射生成权限节点
    ├── Install.php                # 基础插件安装/卸载
    ├── config/plugin/umaos/webman/
    │   ├── app.php                # 插件配置源文件
    │   ├── route.php              # 静态资源路由源文件
    │   └── autoload.php           # 助手函数 autoload 源文件
    ├── library/
    │   ├── Auth.php               # 登录/登出/取用户/权限判定
    │   └── ControllerScanner.php  # 控制器目录扫描
    ├── middleware/
    │   └── Worker.php             # 全局鉴权中间件
    ├── model/
    │   ├── User.php               # fa_index_user
    │   ├── Admin.php              # fa_admin_user
    │   ├── System.php             # fa_system_user
    │   ├── AuthGroup.php          # fa_auth_group
    │   └── AuthRule.php           # fa_auth_rule
    ├── helper/
    │   ├── Show.php               # 统一响应实现
    │   ├── Layout.php             # 业务模板 + 布局合成
    │   ├── Builder.php            # buildTree / buildWhere
    │   ├── Files.php              # addDir
    │   ├── Http.php               # cURL 同步/异步请求
    │   ├── Random.php             # 随机串 / uuid
    │   └── Str.php                # 命名转换
    ├── traits/
    │   ├── Backend.php            # 预留(空)
    │   ├── Frontend.php           # 预留(空)
    │   └── Sysend.php             # 预留(空)
    └── view/                      # 默认公共模板
        ├── layout/{index,admin,system}.html
        ├── login/{index,admin,system}.html
        ├── error/{index,admin,system}.html
        └── tip/{success,warn,error}.html

职责划分:

模块职责
middleware\Worker全局中间件:写语言、跳过非控制器请求、初始化上下文、执行权限校验
library\Auth三端登录/登出/取用户与规则/权限判定
model\*5 张 fa_* 表的 ThinkORM 模型
helper\Show + helper\Layout决定返回 JSON 还是页面,并把业务模板套进公共布局
helpers.php运行时上下文 umaos_init()、show()/success()/error()、树/条件构造、权限节点反射
Install + config/plugin基础插件配置部署、静态资源路由
Controller / ControllerScanner权限节点生成能力(运行时非必需)

3. 配置方式

3.1 config/plugin/umaos/webman/app.php

return [
    // 插件启用开关(约定字段)
    'enable'       => true,
    // 调试标记(包内部暂未消费,供业务读取)
    'debug'        => true,
    // 版本标记(包内部暂未消费)
    'version'      => '1.0.0',
    // 公共模板根目录,项目根相对路径;留空使用包内 src/view
    'template_dir' => '',
];

读取方式:config('plugin.umaos.webman.app.template_dir')(目录末尾斜杠会被去掉,返回值以 / 开头,见 src/helpers.php:419-423)。

自定义公共模板(layout/、login/、error/、tip/):

'template_dir' => '/app/umaos/view',

3.2 config/plugin/umaos/webman/route.php

启动时递归扫描 vendor/umaos/webman/public,为每个文件注册一条 具体路径 的 GET 路由:

vendor/umaos/webman/public/vue/vue.global.prod.js
  → GET /plugin/umaos/vue/vue.global.prod.js
  → response()->file(...)(自动 Content-Type / 304)

注意:

  • 这是“每文件一条路由”,不是通配符,新增静态文件后需要重启/重载才会生效。
  • 路由文件把包路径写死为 base_path() . '/vendor/umaos/webman/public';如果自定义了 composer vendor-dir,需要改这一行。

3.3 config/plugin/umaos/webman/autoload.php

return [
    'files' => [
        base_path() . '/vendor/umaos/webman/src/helpers.php',
    ],
];

Webman 启动时会 include_once,因此 success()、error()、umaos_init() 等函数全局可用,无需写进主工程 composer.json 的 autoload.files。

3.4 主工程必须配合的配置

(1)注册全局中间件 config/middleware.php:

return [
    '' => [
        app\middleware\Lang::class,          // 可选:语言
        umaos\webman\middleware\Worker::class, // 必须
    ],
];

顺序建议:需要 ?lang= 当次请求立即生效时,把 Worker 放在 Lang 之前(Worker 写 session,Lang 读 session;顺序反了会延迟一次请求,详见 14.7)。

(2)启用 Session config/bootstrap.php:

return [
    support\bootstrap\Session::class,
    Webman\ThinkOrm\ThinkOrm::class,
];

(3)数据库 config/think-orm.php(或 .env):

'connections' => [
    'mysql' => [
        'type'     => 'mysql',
        'hostname' => getenv('DB_HOST') ?: '127.0.0.1',
        'database' => getenv('DB_NAME') ?: 'umaos_db',
        'username' => getenv('DB_USER') ?: 'root',
        'password' => getenv('DB_PASSWORD') ?: '',
        'prefix'   => '',
        'charset'  => 'utf8mb4',
    ],
],

模型表名写死为完整表名(fa_index_user 等),因此 prefix 必须留空;想改前缀需要改 5 个模型的 $table,不是配置项。

(4)视图引擎:包内 Layout/Show 使用 view()、rawBody(),需要配置可用的视图 handler(配套工程用的是 Webman\ThinkTemplate\ThinkPHP):

return [
    'handler' => Webman\ThinkTemplate\ThinkPHP::class,
    'view_path' => '',
];

4. 请求生命周期与 umaos_init

4.1 中间件 Worker

src/middleware/Worker.php:

public function process(Request $request, callable $handler): Response
{
    $session = $request->session();
    $session->set('lang', $request->get('lang', 'cn'));

    // 静态路由、404 等没有 controller 的请求直接放行
    if (!$request->controller) {
        return $handler($request);
    }

    Auth::init(); // → umaos_init()

    return Auth::access() ?: $handler($request);
}
  • 只处理“能解析到控制器”的请求;/plugin/umaos/* 静态路由会进入中间件但被直接放行。
  • 权限校验失败时直接返回 show() 生成的响应,不再进入控制器。

4.2 umaos_init() 做了什么

src/helpers.php:306-370:

步骤说明
判定 JSON 请求isAjax()、?_ajax=任意值 → true;否则 Content-Type 或 Accept 含 application/json → true
解析 module依次匹配 app/{应用}/controller、app/controller/{模块}、plugin/{厂商}/app/{模块}/controller,兜底 index
读取控制器属性反射 getDefaultProperties(),取 noNeedLogin、noNeedRight、moduleName(moduleName 非空则覆盖解析结果)
计算权限路径 auth控制器类名转 /、去掉 Controller、去掉插件前缀、去掉 app/{module}/controller,拼上 /{action},整体转小写
注入上下文Auth::getLogin() 得到 user/rule,写入 $request->auth/user/rule/module/isJson/noNeedLogin/noNeedRight

权限路径计算示例:

控制器动作$request->auth
app\index\controller\IndexControllerloginindex/index/login
app\admin\controller\IndexControllerappsadmin/index/apps
app\system\controller\UserControllerindexsystem/user/index
app\system\controller\AuthGroupControllergetListsystem/authgroup/getlist

注意:类名只做“去掉 Controller + 转小写”,不做下划线转换;动作名也是整体转小写。规则节点的生成规则与此并不完全一致,见 10.3 与 14.6。

4.3 可用的请求上下文

属性类型来源
$request->modulestringumaos_init 解析或 moduleName
$request->authstring当前权限路径
$request->userarray当前登录用户(未登录为 [])
$request->rulearray当前用户规则名集合,超管为 ['*']
$request->isJsonbool是否按 JSON 请求处理
$request->noNeedLoginarray/string控制器属性
$request->noNeedRightarray/string控制器属性
$request->pluginstringWebman 插件名(非插件为空)
$request->layoutstring/bool业务可写:强制布局名或 false 关闭布局

这些都通过 PHP 动态属性写入。PHP 8.2+ 对未声明动态属性会触发 deprecation,见 14.5。

5. 控制器侧约定

控制器不需要继承任何基类,只需声明 3 个属性:

protected $noNeedLogin = ['login']; // 免登录动作;'*' 表示全部
protected $noNeedRight = ['check']; // 免授权动作;'*' 表示全部
protected $moduleName  = 'system';  // index / admin / system
  • noNeedLogin:未登录也可访问(登录、验证码、应用列表等)。
  • noNeedRight:需要登录,但不校验权限(个人中心、登录状态检查等)。
  • moduleName:决定使用哪张用户表、session 键、公共布局与错误页;不写时按命名空间推断。
  • protected 或 public 均可,getDefaultProperties() 都能读到。

5.1 完整示例(系统中台)

<?php

namespace app\system\controller;

use support\Request;
use umaos\webman\library\Auth;

class IndexController
{
    protected $noNeedLogin = ['login'];
    protected $noNeedRight = ['check'];
    protected $moduleName  = 'system';

    // 登录:GET 返回登录页,POST 返回 JSON(前端带 AJAX 头)
    public function login(Request $request)
    {
        if ($request->isPost()) {
            return Auth::setLogin(
                $request->post('username'),
                $request->post('password')
            );
        }
        return success('登录页面', [], true);
    }

    public function logout()
    {
        return Auth::setOut();
    }

    // 中间件已注入 $request->user
    public function info(Request $request)
    {
        return success('用户详情', $request->user);
    }

    public function check(Request $request)
    {
        return $request->user ? success('登录正常', $request->user) : warn('请先登录');
    }
}

5.2 约束

  1. 控制器必须位于 app/{module}/controller/ 下(或插件 plugin/{vendor}/app/{module}/controller/),否则 module 可能推断错误。
  2. 动作名建议全部小写(index、getlist),不要用驼峰:运行时 auth 是整体小写,而节点生成会把驼峰转下划线,二者会不一致(14.6)。
  3. Auth::setLogin() 内部按 $request->module 选表,只内置 index/admin/system 三端;新增第四端需要扩展 Auth。
  4. 如果绕过 Worker(例如自建路由直接调用控制器),需要自己调用 Auth::init() 与 Auth::access()。

6. 登录与鉴权

6.1 登录 Auth::setLogin($username, $password)

src/library/Auth.php:47-79:

  1. 按 $request->module 选择模型(User/Admin/System)并按 username 查询;
  2. 校验 md5(md5($password) . $user['salt']);
  3. 成功后写 token = Random::uuid()、logintime = time() 并 save();
  4. 写 session:键为 {module}_login_user,值为用户 ID;
  5. 返回 Show::success('登陆成功', $user),并剔除 password、salt;
  6. 失败返回 Show::error('用户名错误' | '密码错误')。

6.2 当前用户 Auth::getLogin()

src/library/Auth.php:95-118:

$userId = $request->session()->get($request->module . '_login_user');
$user   = {Model}::getInfo($userId);   // 已剔除 password/salt
$rule   = {Model}::getRules($userId);  // 规则名数组
return ['user' => $user, 'rule' => $rule];

6.3 退出 Auth::setOut()

删除 {module}_login_user,返回 success('退出成功', [], true)。非 AJAX 请求会渲染 tip/success 页面。

6.4 权限判定 Auth::access()

src/library/Auth.php:123-139,返回 false 表示放行,否则返回响应对象:

顺序条件结果
1noNeedLogin == '*' 或包含 * 或包含当前 action放行
2$request->user 为空login('请先登录') → code 3
3noNeedRight == '*' 或包含 * 或包含当前 action放行
4rule 含 '*' 或含 $request->auth放行
5其他error('您没有权限访问该接口') → code 1

6.5 关于 token

token 会写入数据库并随登录响应返回,但本包没有任何接口读取/校验它。登录态完全依赖 PHPSID 会话 Cookie。前端可以把 token 存起来,但不要以为它参与了鉴权。

7. RBAC 规则匹配

7.1 数据关系

用户表(group_ids: "1,11")
   └── fa_auth_group.rules ("*" 或 "1288,1294")
          └── fa_auth_rule.name ("system/user/index")
                 ↕ 与 $request->auth 字符串比较
  • group_ids:用户所属分组 ID,逗号分隔字符串。
  • fa_auth_group.rules:'*' 表示超管;否则是规则 ID 逗号串。
  • fa_auth_group.module:冗余字段,当前匹配逻辑不使用。
  • fa_auth_rule.name:权限路径,唯一;运行时与 $request->auth 精确比较。
  • fa_auth_rule.type:menu(控制器级)/ file(动作级)/ dir(目录级,节点生成时实际不会落库)。
  • fa_auth_rule.login / right:节点生成时写入的提示位,Auth::access 不使用它们。

7.2 规则名格式

规则名 = umaos_init 计算出的 auth:

{module}/{controller}/{action}
system/user/index
system/group/set
admin/index/apps

超管分组:fa_auth_group.rules = '*',getRules() 返回 ['*'],所有接口放行。

7.3 手工新增一条动作级规则

use umaos\webman\model\AuthRule;

AuthRule::create([
    'type'   => 'file',
    'pid'    => 0,
    'name'   => 'system/user/export', // 必须与 $request->auth 完全一致(全小写)
    'title'  => '导出用户',
    'icon'   => '',
    'remark' => '',
    'login'  => 1,
    'right'  => 1,
    'weigh'  => 0,
    'module' => 'system',
]);

然后把该规则 ID 追加到目标分组的 rules 字段。

8. 统一 JSON / 页面响应

8.1 全局函数

success($msg = '成功', $data = [], $tpl = null); // code 0
error($msg = '失败',   $data = [], $tpl = true); // code 1
warn($msg = '警告',    $data = [], $tpl = true); // code 2
login($msg = '警告', $data = [], $tpl = true); // code 3

也提供 Show::success()/error()/warn()/login()/view() 与底层 show()。

8.2 返回 JSON 的条件

满足任意一条即返回 JSON(src/helper/Show.php:79-85):

  • $request->isAjax()(X-Requested-With: XMLHttpRequest)
  • 请求参数 _ajax 存在(值不限,?_ajax=1 即可)
  • $request->isJson(Content-Type 或 Accept 含 application/json)
  • 调用时显式传 $tpl = false

JSON 结构:

{
  "code": 0,
  "msg": "成功",
  "data": {},
  "auth": "system/user/index"
}

8.3 页面返回($tpl 语义)

$tpl行为
false强制 JSON
true按 code 映射公共模板并套布局
null(默认)自动:渲染当前控制器/动作对应的业务模板并套布局

$tpl = true 时的模板映射(Show.php:86-107):

code模板
0{template_dir}/tip/success
1{template_dir}/error/{module}
2{template_dir}/tip/warn
3{template_dir}/login/{module}

非 AJAX 场景下,未登录返回的是 HTTP 200 + 登录页 HTML,而不是 {"code":3}。接口调试必须带 AJAX/JSON 头。

9. 布局渲染

src/helper/Layout.php:41-63,默认配置($defaultConfig):

[
    'enable'  => true,    // 是否套布局
    'layout'  => '',      // 布局名,空则取 module
    'content' => '{@body}', // 业务内容占位符
    'suffix'  => '.html', // 模板后缀
]

渲染顺序:

  1. view($tpl, $vars) 渲染业务模板;
  2. $request->layout === false 或 enable=false → 直接返回业务内容;
  3. 布局名优先级:$layoutOpt['layout'](显式传参)> $request->layout(字符串且不为 true)> $request->module > $request->app > index;
  4. 布局文件:{template_dir}/layout/{layout};
  5. 用业务内容替换布局中的 {@body},返回 response($html)。

布局示例(真实可用):

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>系统中台</title>
    <link rel="stylesheet" href="/plugin/umaos/element-plus/index.css">
    <link rel="stylesheet" href="/plugin/umaos/umaos.css">
    <script src="/plugin/umaos/vue/vue.global.prod.js"></script>
    <script src="/plugin/umaos/element-plus/index.full.min.js"></script>
</head>
<body>
{@body}
</body>
</html>

按请求覆盖布局(控制器内):

$request->layout = 'system'; // 强制使用 layout/system.html

关闭布局:

$request->layout = false;

10. 权限节点生成

10.1 getControllerInfo($controller)

src/helpers.php:137-236(另有同名的 umaos\webman\Controller::getControllerInfo(),见 10.3)。

输入完整类名,返回:

[
    'var'      => [/* 控制器默认属性 */],
    'name'     => 'user',           // 类名转小写下划线
    'class'    => '用户管理',        // 类注释第一行
    'function' => ['index' => '列表', 'add' => '添加'],
    'rule'     => [
        ['app' => 'system', 'name' => 'system/user', 'title' => '用户管理', 'type' => 'menu', 'login' => 0, 'right' => 0, 'module' => 'system'],
        ['app' => 'system', 'name' => 'system/user/index', 'title' => '列表', 'type' => 'file', 'login' => 1, 'right' => 1, 'module' => 'system'],
        // ...
    ],
]

节点名规则:

  • 控制器节点:{module}/{类名去 Controller 后转小写},type=menu;
  • 动作节点:{控制器节点}/{方法名转下划线},type=file;
  • login/right:动作在 noNeedLogin/noNeedRight 白名单内则为 0,否则 1。

10.2 接入示例

use umaos\webman\model\AuthRule;

$nodes = getControllerInfo('app\\system\\controller\\UserController')['rule'] ?? [];
foreach ($nodes as $node) {
    $pid = 0;
    if ($pos = strrpos($node['name'], '/')) {
        $pid = AuthRule::where('name', substr($node['name'], 0, $pos))->value('id') ?: 0;
    }

    // 只取数据表真实存在的字段,节点里的 app 字段不落库
    $row = [
        'pid'    => $pid,
        'name'   => $node['name'],
        'title'  => $node['title'],
        'type'   => $node['type'],
        'login'  => $node['login'] ?? 0,
        'right'  => $node['right'] ?? 0,
        'module' => $node['module'],
    ];

    $rule = AuthRule::where('name', $node['name'])->find() ?: new AuthRule();
    $rule->save($row);
}

以上逻辑与配套工程的 RuleController::auto() 一致(app/system/controller/RuleController.php:42-86)。

10.3 两套实现(重要)

以插件控制器 plugin/umaos/app/system/controller/UserController 为例:

实现位置插件前缀处理产出示例
全局函数 getControllerInfo()src/helpers.php:137-236substr($auth, strlen('plugin') + strlen($plugin) + 2),连厂商一起截掉system/user/index
类方法 Controller::getControllerInfo()src/Controller.php:31-108substr($auth, strlen('plugin') + 1),只截掉 plugin/umaos/system/user/index

非插件控制器(app/system/controller/...)两者结果一致,都是 system/user/index。

ControllerScanner::getLists() 用的是类方法(src/library/ControllerScanner.php:72-80),而业务里常用的“自动生成权限”走的是全局函数。混用会让规则表里同时出现 system/... 与 umaos/system/... 两套命名,旧分组的规则会匹配不上。详见 14.1。

此外,两个实现都会先构造 dir 层级节点,随后整体覆盖 $list(helpers.php:203-219、Controller.php:81-94),因此实际落库只有 menu + file。

10.4 控制器扫描

  • ControllerScanner::test():glob 扫描 app/controller、app/*/controller、plugin/*/app/{,*/}controller,返回类名字符串数组,适合给“自动生成”下拉框用。
  • ControllerScanner::getAllAppController()/getList():按 extends Webman\Controller 过滤(ControllerScanner.php:180)。本包控制器基类是 umaos\webman\Controller,业务控制器通常不继承任何基类,因此这两个方法在当前工程中返回空,属于历史遗留能力。

11. 基础插件与静态资源

11.1 安装钩子

Install 实现了 Webman 基础插件协议:

  • const WEBMAN_PLUGIN = 'umaos/webman'
  • install()/update() → deployConfig():复制 src/config/plugin 到主工程 config/plugin,只补缺失文件,不覆盖已存在文件。
  • uninstall():删除主工程 config/plugin/umaos/webman/。

主工程 composer.json 需要有 webman 默认脚本(否则钩子不触发):

"scripts": {
  "post-package-install": ["support\\Plugin::install"],
  "post-package-update": ["support\\Plugin::install"],
  "pre-package-uninstall": ["support\\Plugin::uninstall"]
}

11.2 静态资源

  • URL 前缀:/plugin/umaos/
  • 物理目录:vendor/umaos/webman/public/
  • 路由:启动时逐文件注册,response()->file() 返回,支持 304。
  • 内置资源包括:vue/(vue.global.prod.js、vue-router.global.js、vue-demi.iife.js)、element-plus/、icons-vue/、axios.min.js、pinia.iife.js、pinia.iife.prod.js、umaos.css、wallpaper.jpg。

在模板中直接引用:

<link rel="stylesheet" href="/plugin/umaos/umaos.css">
<script src="/plugin/umaos/vue/vue.global.prod.js"></script>

注意:新增/删除包内静态文件后必须重启或 reload,否则路由不会更新。

12. 数据表与会话键约定

12.1 表结构(实际字段)

用户表 fa_index_user / fa_admin_user / fa_system_user(三张同构):

字段类型用途
idint unsigned PK用户 ID
usernamevarchar(20) unique登录名
nicknamevarchar(50)昵称
passwordvarchar(32)md5(md5(明文).salt)
saltvarchar(30)盐
avatarvarchar(255)头像
group_idsvarchar(255)分组 ID,逗号分隔
tokenvarchar(59)登录时写入的 UUID(不校验)
logintimebigint最近登录时间戳
createtime / updatetimebigint创建/更新时间戳
statusvarchar(30)业务状态(包内未使用)

分组表 fa_auth_group:

字段类型用途
idint unsigned PK分组 ID
pidint unsigned父分组
namevarchar(100)分组名
rulestext'*' 或规则 ID 逗号串
modulevarchar(255)所属端(当前匹配逻辑不读取)
statusvarchar(30)业务状态
createtime / updatetimebigint时间戳

规则表 fa_auth_rule:

字段类型用途
idint unsigned PK规则 ID
typeenum('menu','file','dir')节点类型
pidint unsigned父节点
namevarchar(100) unique权限路径
titlevarchar(50)显示名
iconvarchar(50)图标
remarkvarchar(255)备注
logintinyint节点生成时的登录提示位(不参与匹配)
righttinyint节点生成时的授权提示位(不参与匹配)
weighint排序
modulevarchar(255)所属端

12.2 会话键

键值说明
{module}_login_user用户 IDindex_login_user / admin_login_user / system_login_user
lang语言Worker 每次请求写入 ?lang,默认 cn

会话使用主工程 config/session.php 的配置;配套工程为 file handler,Cookie 名 PHPSID。

12.3 生成一个可登录账号

$salt = 'abc123';
$password = md5(md5('admin888') . $salt); // 4473... 存入 password 字段
INSERT INTO fa_system_user (username, nickname, password, salt, group_ids, status, createtime, updatetime)
VALUES ('admin', '管理员', MD5(CONCAT(MD5('admin888'), 'abc123')), 'abc123', '1', 'normal', UNIX_TIMESTAMP(), UNIX_TIMESTAMP());

超管分组:UPDATE fa_auth_group SET rules='*' WHERE id=1;

13. 典型接入步骤

  1. 安装包并确认插件配置

    composer require umaos/webman:dev-master
    ls config/plugin/umaos/webman   # 应看到 app.php route.php autoload.php
    
  2. 配置数据库与会话:config/think-orm.php、config/bootstrap.php(Session + ThinkOrm)。

  3. 注册全局中间件:

    '' => [umaos\webman\middleware\Worker::class],
    
  4. 建表:3 张用户表 + fa_auth_group + fa_auth_rule(字段见第 12 节)。

  5. 写登录接口(控制器属性 + Auth::setLogin()):

    protected $noNeedLogin = ['login'];
    protected $moduleName  = 'system';
    
  6. 写业务接口,用 $request->user 取当前用户,用 success/error/warn 返回。

  7. 生成权限节点:调用 getControllerInfo() 落库,或手工插入 fa_auth_rule。

  8. 分配权限:把规则 ID 写入 fa_auth_group.rules,再把分组 ID 写入用户 group_ids;超管直接 rules='*'。

  9. 自定义界面(可选):复制 vendor/umaos/webman/src/view 到工程,配置 template_dir,使用 /plugin/umaos/* 静态资源。

  10. 验证:

    curl -s -c /tmp/ck -H 'Content-Type: application/json' -H 'X-Requested-With: XMLHttpRequest' \
         -d '{"username":"admin","password":"admin888"}' \
         http://127.0.0.1:1787/system/index/login
    

14. 已知问题与注意事项

14.1 规则名有两套实现,旧分组可能永远匹配不上(高)

  • 全局 getControllerInfo()(helpers.php:196)与类 Controller::getControllerInfo()(Controller.php:76-78)对插件前缀的截断长度不同:对 plugin/umaos/app/system/controller/UserController,前者产出 system/user/index,后者产出 umaos/system/user/index。
  • 历史上控制器位于 plugin/umaos/... 时生成的节点就是 umaos/system/... 形态,迁到 app/system/... 后运行时 auth 变成 system/...,两者不再相等。
  • 现象:超管(rules='*')一切正常,普通分组配置了权限却提示“您没有权限访问该接口”。
  • 处理:统一用全局 getControllerInfo() 重新生成节点;或把旧规则 name 批量去掉 umaos/ 前缀(并去重)。在修复包内实现前,不要混用 ControllerScanner::getLists() 与 RuleController::auto。

14.2 User::getRules() 硬编码了 admin

src/model/User.php:64-65:

$userGroup = User::getGroup($userId, 'admin');
$groupRule = AuthGroup::getRules('admin');

由于 getGroup()/AuthGroup::getRules() 当前都忽略第二个参数,暂时“碰巧能跑”,但语义错误。若后续给这两个方法加上 module 过滤,index 端会直接失效。

14.3 AuthGroup::getRules() 忽略 module,分组规则跨端混用

src/model/AuthGroup.php:33-47 查询全部分组与全部规则,不按 module 过滤。任何一端的分组只要 rules='*',该分组用户在所有端都是超管;index 的分组被误配到 admin 用户时,会拿到 index 的规则名。

14.4 composer.lock 可能落后于 vendor 中的包代码(部署风险)

在配套工程中曾出现:

  • composer.lock 记录 umaos/webman 为 dev-master @ eb49329;
  • vendor/umaos/webman 实际 HEAD 为 732a23f,领先 5 个提交。

此时 composer install 会把代码回退到 lock 版本。检查和修复:

composer show umaos/webman
git -C vendor/umaos/webman log --oneline -3
composer update umaos/webman

另外,composer.8.4.json 这类变体清单如果没有 umaos/webman,按它安装会直接缺包。

14.5 动态属性在 PHP 8.2+ 的兼容性

umaos_init() 会写 $request->isJson/module/auth/user/rule/noNeedLogin/noNeedRight,Layout 会读 $request->layout,Webman\Http\Request 并未声明这些属性,也未加 #[AllowDynamicProperties]。PHP 8.2+ 会产生 deprecation(部分错误处理下不可见)。升级 PHP 前建议在 Request 子类中显式声明,或等待包内改为显式属性。

14.6 驼峰动作名会导致规则不匹配

  • 运行时:auth = strtolower(类名 + '/' + action) → system/user/getlist
  • 节点生成:方法名 camelToSnake() → system/user/get_list

两者不相等。动作名请使用全小写单词(index、add、getlist)。

14.7 ?lang= 只影响下一次请求,且会被重置

Worker 每次控制器请求都会执行:

$request->session()->set('lang', $request->get('lang', 'cn'));
  • 若 Lang 在 Worker 之前(配套工程当前顺序):Lang 先读 session,本次 ?lang=en 不生效;下一次请求读到 en,但同一次请求的 Worker 又把它写回 cn。
  • 若 Worker 在 Lang 之前:本次即可生效,但不带 ?lang 的请求仍会把语言重置为 cn。

需要“设置一次、长期保持”的语言切换,应改为仅在 lang 参数存在时写入 session(需调整 Worker),而不是依赖默认值。

14.8 token 不参与鉴权,登录安全偏弱

  • 后端只写 token,从不读取;鉴权依赖 Session Cookie。
  • 登录成功后没有 session_regenerate_id,存在会话固定风险。
  • 密码是双层 MD5,且包内没有登录频率限制/验证码。

生产环境建议在业务层补:会话 ID 重置、失败次数限制、强哈希迁移。

14.9 非 AJAX 请求的“未登录”是 HTML,不是 code 3

这是 show() 的设计行为,不是 bug。SPA/AJAX 客户端务必带 X-Requested-With、Accept: application/json 或 _ajax 参数;否则会拿到 200 + 登录页。

14.10 debug 模式会回传堆栈

配套工程 config/app.php 默认 debug=true 时,未捕获异常会以 {"code":500,...,"traces":"..."} 返回(HTTP 状态仍为 200),其中包含绝对路径。上线前关闭 debug。

14.11 升级不会覆盖已有配置

deployConfig() 只复制缺失文件。新版新增 autoload.php/配置项时,旧工程需要手工补齐;否则会出现“函数未定义”或配置读取为空。

14.12 静态资源路由在启动时生成

新增包内 public/ 文件后要 php webman reload 或 restart;同时路由写死了 vendor/umaos/webman/public,自定义 vendor-dir 时需改 route.php 与 autoload.php。

14.13 历史遗留/未使用代码

  • umaos_access()(helpers.php:386-406)无调用方,实际执行的是 Auth::access()。
  • viewPath()(helpers.php:528-581)无调用方,实际渲染走 Layout::render()。
  • helper\Builder 与 helpers.php 中的 buildTree/buildWhere 重复。
  • traits/{Backend,Frontend,Sysend}.php 为空。
  • ControllerScanner::getAllAppController()/getList() 的继承过滤条件(Webman\Controller)与实际控制器不符(见 10.4)。
  • Install 与 route.php 的部分注释仍写 /app/umaos/,实际 URL 是 /plugin/umaos/。

14.14 只支持三端

Auth::setLogin/getLogin 的 switch 只包含 index/admin/system。moduleName 写其他值会走到 default,导致登录返回“用户名错误”、规则为空。

15. 常用 API 速查

15.1 全局函数

函数说明
success($msg, $data, $tpl) / error() / warn() / login()统一响应,code 0/1/2/3
show($code, $msg, $data, $tpl)底层响应分发
layout_view($tpl, $vars, $layoutOpt)业务模板 + 布局
umaos_init()初始化请求上下文(中间件自动调用)
umaos_access()权限判定(历史函数,建议直接用 Auth::access())
umaos_template_dir()公共模板根目录
getControllerInfo($class)反射生成权限节点
buildTree($data, $pid = 0)列表转树(children)
buildWhere($custom, $field)返回查询闭包,可直接传给 ThinkORM 的 ->where()
camelToSnake() / snakeToCamel() / snakeToPascal()命名转换

15.2 类

类常用方法
library\Authinit()、setLogin()、setOut()、getLogin()、access()
helper\Showsuccess/error/warn/login/view/layoutView
helper\Layoutrender()
helper\BuilderbuildTree()、buildWhere()
helper\Randomalnum()、alpha()、numeric()、nozero()、uuid()
helper\StrcamelToSnake()、snakeToCamel()、snakeToPascal()
helper\FilesaddDir()
helper\Httpget()、post()、sendRequest()、sendAsyncRequest()、sendToBrowser()
library\ControllerScannertest()、getLists()
model\{User,Admin,System}getInfo()、getGroup()、getRules()

15.3 调试小贴士

# 启动(前台,改代码自动 reload)
php webman start

# JSON 接口调试(必须带 AJAX 头 + Cookie)
curl -s -c /tmp/ck -H 'Content-Type: application/json' \
     -H 'X-Requested-With: XMLHttpRequest' \
     -d '{"username":"admin","password":"admin888"}' \
     http://127.0.0.1:1787/system/index/login

curl -s -b /tmp/ck -H 'X-Requested-With: XMLHttpRequest' \
     http://127.0.0.1:1787/system/index/info

# 改了 vendor/umaos/webman 内的代码或静态资源后
php webman reload