chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+253
View File
@@ -0,0 +1,253 @@
<?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);
}
}