umaos / webman
Webman plugin umaos/webman
Requires
- php: >=8.1
- webman/think-orm: ^2
- workerman/webman-framework: ^2
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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
本包只提供“鉴权 + 基础设施”,不包含业务用户管理界面、业务表结构、前端路由。业务页面与接口由使用方工程实现。
目录
- 安装与依赖
- 目录与模块结构
- 配置方式
- 请求生命周期与 umaos_init
- 控制器侧约定
- 登录与鉴权
- RBAC 规则匹配
- 统一 JSON / 页面响应
- 布局渲染
- 权限节点生成
- 基础插件与静态资源
- 数据表与会话键约定
- 典型接入步骤
- 已知问题与注意事项
- 常用 API 速查
1. 安装与依赖
1.1 版本要求
| 依赖 | 版本 | 说明 |
|---|---|---|
| PHP | >= 8.1 | 使用了 mixed 类型、str_contains 等 |
workerman/webman-framework | ^2 | 多应用路由、support\Plugin、Session Bootstrap |
webman/think-orm | ^2 | 5 个模型继承 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';如果自定义了 composervendor-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\IndexController | login | index/index/login |
app\admin\controller\IndexController | apps | admin/index/apps |
app\system\controller\UserController | index | system/user/index |
app\system\controller\AuthGroupController | getList | system/authgroup/getlist |
注意:类名只做“去掉 Controller + 转小写”,不做下划线转换;动作名也是整体转小写。规则节点的生成规则与此并不完全一致,见 10.3 与 14.6。
4.3 可用的请求上下文
| 属性 | 类型 | 来源 |
|---|---|---|
$request->module | string | umaos_init 解析或 moduleName |
$request->auth | string | 当前权限路径 |
$request->user | array | 当前登录用户(未登录为 []) |
$request->rule | array | 当前用户规则名集合,超管为 ['*'] |
$request->isJson | bool | 是否按 JSON 请求处理 |
$request->noNeedLogin | array/string | 控制器属性 |
$request->noNeedRight | array/string | 控制器属性 |
$request->plugin | string | Webman 插件名(非插件为空) |
$request->layout | string/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 约束
- 控制器必须位于
app/{module}/controller/下(或插件plugin/{vendor}/app/{module}/controller/),否则 module 可能推断错误。 - 动作名建议全部小写(
index、getlist),不要用驼峰:运行时auth是整体小写,而节点生成会把驼峰转下划线,二者会不一致(14.6)。 Auth::setLogin()内部按$request->module选表,只内置index/admin/system三端;新增第四端需要扩展Auth。- 如果绕过
Worker(例如自建路由直接调用控制器),需要自己调用Auth::init()与Auth::access()。
6. 登录与鉴权
6.1 登录 Auth::setLogin($username, $password)
src/library/Auth.php:47-79:
- 按
$request->module选择模型(User/Admin/System)并按username查询; - 校验
md5(md5($password) . $user['salt']); - 成功后写
token = Random::uuid()、logintime = time()并save(); - 写 session:键为
{module}_login_user,值为用户 ID; - 返回
Show::success('登陆成功', $user),并剔除password、salt; - 失败返回
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 表示放行,否则返回响应对象:
| 顺序 | 条件 | 结果 |
|---|---|---|
| 1 | noNeedLogin == '*' 或包含 * 或包含当前 action | 放行 |
| 2 | $request->user 为空 | login('请先登录') → code 3 |
| 3 | noNeedRight == '*' 或包含 * 或包含当前 action | 放行 |
| 4 | rule 含 '*' 或含 $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', // 模板后缀
]
渲染顺序:
view($tpl, $vars)渲染业务模板;$request->layout === false或enable=false→ 直接返回业务内容;- 布局名优先级:
$layoutOpt['layout'](显式传参)>$request->layout(字符串且不为true)>$request->module>$request->app>index; - 布局文件:
{template_dir}/layout/{layout}; - 用业务内容替换布局中的
{@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-236 | substr($auth, strlen('plugin') + strlen($plugin) + 2),连厂商一起截掉 | system/user/index |
类方法 Controller::getControllerInfo() | src/Controller.php:31-108 | substr($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(三张同构):
| 字段 | 类型 | 用途 |
|---|---|---|
id | int unsigned PK | 用户 ID |
username | varchar(20) unique | 登录名 |
nickname | varchar(50) | 昵称 |
password | varchar(32) | md5(md5(明文).salt) |
salt | varchar(30) | 盐 |
avatar | varchar(255) | 头像 |
group_ids | varchar(255) | 分组 ID,逗号分隔 |
token | varchar(59) | 登录时写入的 UUID(不校验) |
logintime | bigint | 最近登录时间戳 |
createtime / updatetime | bigint | 创建/更新时间戳 |
status | varchar(30) | 业务状态(包内未使用) |
分组表 fa_auth_group:
| 字段 | 类型 | 用途 |
|---|---|---|
id | int unsigned PK | 分组 ID |
pid | int unsigned | 父分组 |
name | varchar(100) | 分组名 |
rules | text | '*' 或规则 ID 逗号串 |
module | varchar(255) | 所属端(当前匹配逻辑不读取) |
status | varchar(30) | 业务状态 |
createtime / updatetime | bigint | 时间戳 |
规则表 fa_auth_rule:
| 字段 | 类型 | 用途 |
|---|---|---|
id | int unsigned PK | 规则 ID |
type | enum('menu','file','dir') | 节点类型 |
pid | int unsigned | 父节点 |
name | varchar(100) unique | 权限路径 |
title | varchar(50) | 显示名 |
icon | varchar(50) | 图标 |
remark | varchar(255) | 备注 |
login | tinyint | 节点生成时的登录提示位(不参与匹配) |
right | tinyint | 节点生成时的授权提示位(不参与匹配) |
weigh | int | 排序 |
module | varchar(255) | 所属端 |
12.2 会话键
| 键 | 值 | 说明 |
|---|---|---|
{module}_login_user | 用户 ID | index_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. 典型接入步骤
安装包并确认插件配置
composer require umaos/webman:dev-master ls config/plugin/umaos/webman # 应看到 app.php route.php autoload.php配置数据库与会话:
config/think-orm.php、config/bootstrap.php(Session + ThinkOrm)。注册全局中间件:
'' => [umaos\webman\middleware\Worker::class],建表:3 张用户表 +
fa_auth_group+fa_auth_rule(字段见第 12 节)。写登录接口(控制器属性 +
Auth::setLogin()):protected $noNeedLogin = ['login']; protected $moduleName = 'system';写业务接口,用
$request->user取当前用户,用success/error/warn返回。生成权限节点:调用
getControllerInfo()落库,或手工插入fa_auth_rule。分配权限:把规则 ID 写入
fa_auth_group.rules,再把分组 ID 写入用户group_ids;超管直接rules='*'。自定义界面(可选):复制
vendor/umaos/webman/src/view到工程,配置template_dir,使用/plugin/umaos/*静态资源。验证:
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\Auth | init()、setLogin()、setOut()、getLogin()、access() |
helper\Show | success/error/warn/login/view/layoutView |
helper\Layout | render() |
helper\Builder | buildTree()、buildWhere() |
helper\Random | alnum()、alpha()、numeric()、nozero()、uuid() |
helper\Str | camelToSnake()、snakeToCamel()、snakeToPascal() |
helper\Files | addDir() |
helper\Http | get()、post()、sendRequest()、sendAsyncRequest()、sendToBrowser() |
library\ControllerScanner | test()、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