Files

254 lines
8.4 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace ywxapp\controller;
use think\App;
use think\exception\ValidateException;
use think\Validate;
use ywxapp\library\Result;
use think\facade\Lang;
use think\facade\View;
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* 应用实例
* @var \think\App
*/
protected $app;
/**
* Request实例
* @var \think\Request
*/
protected $request;
/**
* 应用模块名称
* @var string
*/
protected $module = '';
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [];
/**
* 控制器模型
* @var \think\Model
*/
protected $model = null;
/**
* 响应实例
* @var \ywxapp\library\Result
*/
protected $result;
/**
* 用户实例
* @var \ywxapp\library\Auth
*/
protected $auth;
/**
* Summary of needLogin
* @var array
*/
protected $noNeedLogin = [];
/**
* Summary of needRight
* @var array
*/
protected $noNeedVerify = [];
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->app = $app;
// 安全的CORS配置(必须在 $this->app 赋值后再调用,使用容器 Env 而非全局 env() 助手)
$this->setCorsHeaders();
$this->module = $app->http->getName();
$this->result = $app->result;
$this->request = $app->request;
$this->auth = $app->auth;
// 尝试用 token 还原登录态:有 token 就解析并初始化用户,没有则跳过(不报错、不影响后续)
$this->auth->tryInitByToken();
// 登录验证(需要登录的 action 未登录返回 401;已登录则校验权限)
// 加载当前控制器语言包
$this->loadlang($this->request->controller());
$this->_initialize();
$this->assignSite();
}
/**
* 设置安全的CORS头部
*/
protected function setCorsHeaders(): void
{
// 从配置或环境变量获取允许的来源(使用容器 Env,避免依赖全局 env() 助手在插件/CLI 上下文未注册)
$allowedOrigins = $this->app->env->get('CORS_ALLOWED_ORIGINS', 'http://localhost:3000,http://localhost:8080');
$allowedOriginsArray = array_filter(array_map('trim', explode(',', $allowedOrigins)));
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
// 检查来源是否在白名单中
if (in_array($origin, $allowedOriginsArray) || empty($origin)) {
if (!empty($origin)) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Credentials: true');
}
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
header('Access-Control-Max-Age: 86400'); // 24小时预检缓存
}
// 处理OPTIONS预检请求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
}
/**
* 向模板注入当前应用的基础路径信息。
*
* 关键:对外 URL 的模块段必须是 app_map 的「虚拟名」(admin/user/home),
* 而非 http->getName() 返回的「真实目录名」(backend/member/frontend)
* 否则拼接出的 URL 会与路由不匹配(重复 / 错位模块名)。
* 虚拟名直接取自 request->root() 中解析出的那段。
*/
protected function assignSite(): void
{
$root = $this->app->request->root(); // 如 /admin、/user、默认应用为空
$appRoot = ltrim($root, '/'); // 虚拟名:admin/user/home,默认应用为 ''
// 部署根(去掉模块段):/sub/admin -> /sub/admin -> ''(网站根目录)
$deployRoot = preg_replace('#^/[^/]+#', '', $root);
View::assign('site', [
'root' => $deployRoot === '' ? '' : rtrim($deployRoot, '/'),
'module' => $appRoot, // 对外模块段(虚拟名,URL 用):admin/user/home
'app' => $this->module, // 真实应用目录名(静态资源目录用):backend/member/frontend/home
'controller' => $this->request->controller(),
'action' => $this->request->action(),
'devToken' => config('ywxapp.developer_token'),
'devAddon' => config('ywxapp.addon_developer')
]);
// 后端生成一个「带占位符的基准 URL」,供 JS(route.js) 做片段替换,
// 从而与后端路由规则(app_map/别名/后缀)完全对齐。
// 例如当前应用在 backend 时生成: /admin/{__CTRL__}/{__ACT__}
// 注意:不可用 Route::buildUrl('{__CTRL__}/{__ACT__}') —— 它把占位符当真实
// 路由解析,在某些路由配置下会误生成 /menu/{__CTRL__} 之类的异常路径。
// 改为按 app_map 反查当前应用(真实目录名 $this->module)的虚拟前缀手动拼。
$appMap = (array) config('app.app_map');
$appPrefix = array_search($this->module, $appMap, true);
if (! $appPrefix) {
$appPrefix = $this->module; // 无映射时直接用真实目录名
}
$base = $deployRoot === '' ? '' : rtrim($deployRoot, '/');
$routeBase = $base . '/' . $appPrefix . '/{__CTRL__}/{__ACT__}';
View::assign('route_base', $routeBase);
}
/**
* 控制器初始化 _initialize
* @return void
*/
protected function _initialize()
{}
/**
* 加载语言文件.
*
* @param string $name
*/
protected function loadlang($name = '')
{
$name = $name ?: $this->request->controller();
if (strpos($name, '.')) {
$_arr = explode('.', $name);
if (count($_arr) == 2) {
$path = $_arr[0] . '/' . strtolower($_arr[1]);
} else {
$path = strtolower($name);
}
} else {
$path = strtolower($name);
}
Lang::load($this->app->getAppPath() . '/lang/' . Lang::getLangset() . '/' . $path . '.php');
}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, string | array $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
[$validate, $scene] = explode('.', $validate);
}
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
$v = new $class();
if (! empty($scene)) {
$v->scene($scene);
}
}
$v->message($message);
if ($batch || $this->batchValidate) {
$v->batch(true);
}
return $v->failException(true)->check($data);
}
/**
* 成功响应(转发到 Result).
*/
public function success($data = null, $message = 'success', int $code = 0)
{
return $this->result->success($data, $message, $code);
}
/**
* 失败响应(转发到 Result).
*/
public function error($message = 'Error', int $code = 1, $data = null)
{
return $this->result->error($message, $code, $data);
}
}