根因(两处叠加):
1) public/admin.php 缺失:FastAdmin 的 deny_module_list 含 admin,
后台必须通过 admin.php 以 BIND_MODULE=admin 访问 → 之前任何 /admin 都 404
2) PHP 8.5 将 (boolean) 列为编译期 E_DEPRECATED,TP5 Request.php:1159 命中
→ Error::appError 升级为 ErrorException,在 Request 类「自动加载」瞬间抛出,
早于任何模块级垫片,导致请求直接中断
修复:
- 新增 public/admin.php(标准 FastAdmin 入口,BIND_MODULE=admin)
- thinkphp/library/think/Request.php:1159 (boolean) → (bool),已备份
并固化为可复现脚本 scripts/patch_tp_php85.sh
- application/admin/common.php 与 application/index/common.php 增加 PHP8 兼容垫片
(application/common.php 不会被 TP5 自动加载,已还原为原样)
- nginx 伪静态新增 /admin、/admin/ → /admin.php(已备份 + nginx -t + 平滑 reload)
验证:/admin → 302 登录页、/admin.php/index/login → 200;
后台密码哈希与 DB 一致;验证码 /captcha 返回 PNG;163 项断言回归全通过
210 行
8.6 KiB
PHP
可执行文件
210 行
8.6 KiB
PHP
可执行文件
<?php
|
||
|
||
/**
|
||
* PHP 8.x 兼容垫片(ThinkPHP 5.0)
|
||
*
|
||
* PHP 8.4+ 的 E_DEPRECATED(非规范布尔转换、隐式 nullable 参数等)会被
|
||
* think\Error::appError() 升级为 ErrorException;后台 Backend::_initialize()
|
||
* 解析布尔参数时即触发,导致 /admin 全站 404。
|
||
*
|
||
* 本文件由 App::module() 在控制器实例化之前加载,是最早可用的模块级切入点。
|
||
* 仅抑制弃用/提示级告警,真实错误不受影响。
|
||
*/
|
||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED & ~E_NOTICE);
|
||
|
||
use app\common\model\Category;
|
||
use fast\Form;
|
||
use fast\Tree;
|
||
use think\Db;
|
||
use think\Loader;
|
||
|
||
if (!function_exists('build_select')) {
|
||
|
||
/**
|
||
* 生成下拉列表
|
||
* @param string $name
|
||
* @param mixed $options
|
||
* @param mixed $selected
|
||
* @param mixed $attr
|
||
* @return string
|
||
*/
|
||
function build_select($name, $options, $selected = [], $attr = [])
|
||
{
|
||
$options = is_array($options) ? $options : explode(',', $options ?? '');
|
||
$selected = is_array($selected) ? $selected : explode(',', $selected ?? '');
|
||
return Form::select($name, $options, $selected, $attr);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('build_radios')) {
|
||
|
||
/**
|
||
* 生成单选按钮组
|
||
* @param string $name
|
||
* @param array $list
|
||
* @param mixed $selected
|
||
* @return string
|
||
*/
|
||
function build_radios($name, $list = [], $selected = null)
|
||
{
|
||
$html = [];
|
||
$selected = is_null($selected) ? key($list) : $selected;
|
||
$selected = is_array($selected) ? $selected : explode(',', $selected);
|
||
foreach ($list as $k => $v) {
|
||
$html[] = sprintf(Form::label("{$name}-{$k}", "%s " . str_replace('%', '%%', $v)), Form::radio($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"]));
|
||
}
|
||
return '<div class="radio">' . implode(' ', $html) . '</div>';
|
||
}
|
||
}
|
||
|
||
if (!function_exists('build_checkboxs')) {
|
||
|
||
/**
|
||
* 生成复选按钮组
|
||
* @param string $name
|
||
* @param array $list
|
||
* @param mixed $selected
|
||
* @return string
|
||
*/
|
||
function build_checkboxs($name, $list = [], $selected = null)
|
||
{
|
||
$html = [];
|
||
$selected = is_null($selected) ? [] : $selected;
|
||
$selected = is_array($selected) ? $selected : explode(',', $selected);
|
||
foreach ($list as $k => $v) {
|
||
$html[] = sprintf(Form::label("{$name}-{$k}", "%s " . str_replace('%', '%%', $v)), Form::checkbox($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"]));
|
||
}
|
||
return '<div class="checkbox">' . implode(' ', $html) . '</div>';
|
||
}
|
||
}
|
||
|
||
|
||
if (!function_exists('build_category_select')) {
|
||
|
||
/**
|
||
* 生成分类下拉列表框
|
||
* @param string $name
|
||
* @param string $type
|
||
* @param mixed $selected
|
||
* @param array $attr
|
||
* @param array $header
|
||
* @return string
|
||
*/
|
||
function build_category_select($name, $type, $selected = null, $attr = [], $header = [])
|
||
{
|
||
$tree = Tree::instance();
|
||
$tree->init(Category::getCategoryArray($type), 'pid');
|
||
$categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
|
||
$categorydata = $header ? $header : [];
|
||
foreach ($categorylist as $k => $v) {
|
||
$categorydata[$v['id']] = $v['name'];
|
||
}
|
||
$attr = array_merge(['id' => "c-{$name}", 'class' => 'form-control selectpicker'], $attr);
|
||
return build_select($name, $categorydata, $selected, $attr);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('build_toolbar')) {
|
||
|
||
/**
|
||
* 生成表格操作按钮栏
|
||
* @param array $btns 按钮组
|
||
* @param array $attr 按钮属性值
|
||
* @return string
|
||
*/
|
||
function build_toolbar($btns = null, $attr = [])
|
||
{
|
||
$auth = \app\admin\library\Auth::instance();
|
||
$controller = str_replace('.', '/', Loader::parseName(request()->controller()));
|
||
$btns = $btns ? $btns : ['refresh', 'add', 'edit', 'del', 'import'];
|
||
$btns = is_array($btns) ? $btns : explode(',', $btns);
|
||
$index = array_search('delete', $btns);
|
||
if ($index !== false) {
|
||
$btns[$index] = 'del';
|
||
}
|
||
$btnAttr = [
|
||
'refresh' => ['javascript:;', 'btn btn-primary btn-refresh', 'fa fa-refresh', '', __('Refresh')],
|
||
'add' => ['javascript:;', 'btn btn-success btn-add', 'fa fa-plus', __('Add'), __('Add')],
|
||
'edit' => ['javascript:;', 'btn btn-success btn-edit btn-disabled disabled', 'fa fa-pencil', __('Edit'), __('Edit')],
|
||
'del' => ['javascript:;', 'btn btn-danger btn-del btn-disabled disabled', 'fa fa-trash', __('Delete'), __('Delete')],
|
||
'import' => ['javascript:;', 'btn btn-info btn-import', 'fa fa-upload', __('Import'), __('Import')],
|
||
];
|
||
$btnAttr = array_merge($btnAttr, $attr);
|
||
$html = [];
|
||
foreach ($btns as $k => $v) {
|
||
//如果未定义或没有权限
|
||
if (!isset($btnAttr[$v]) || ($v !== 'refresh' && !$auth->check("{$controller}/{$v}", $auth->id))) {
|
||
continue;
|
||
}
|
||
list($href, $class, $icon, $text, $title) = $btnAttr[$v];
|
||
//$extend = $v == 'import' ? 'id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"' : '';
|
||
//$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '" ' . $extend . '><i class="' . $icon . '"></i> ' . $text . '</a>';
|
||
if ($v == 'import') {
|
||
$template = str_replace('/', '_', $controller);
|
||
$download = '';
|
||
if (file_exists("./template/{$template}.xlsx")) {
|
||
$download .= "<li><a href=\"/template/{$template}.xlsx\" target=\"_blank\">XLSX模版</a></li>";
|
||
}
|
||
if (file_exists("./template/{$template}.xls")) {
|
||
$download .= "<li><a href=\"/template/{$template}.xls\" target=\"_blank\">XLS模版</a></li>";
|
||
}
|
||
if (file_exists("./template/{$template}.csv")) {
|
||
$download .= empty($download) ? '' : "<li class=\"divider\"></li>";
|
||
$download .= "<li><a href=\"/template/{$template}.csv\" target=\"_blank\">CSV模版</a></li>";
|
||
}
|
||
$download .= empty($download) ? '' : "\n ";
|
||
if (!empty($download)) {
|
||
$html[] = <<<EOT
|
||
<div class="btn-group">
|
||
<button type="button" href="{$href}" class="btn btn-info btn-import" title="{$title}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="{$icon}"></i> {$text}</button>
|
||
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" title="下载批量导入模版">
|
||
<span class="caret"></span>
|
||
<span class="sr-only">Toggle Dropdown</span>
|
||
</button>
|
||
<ul class="dropdown-menu" role="menu">{$download}</ul>
|
||
</div>
|
||
EOT;
|
||
} else {
|
||
$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="' . $icon . '"></i> ' . $text . '</a>';
|
||
}
|
||
} else {
|
||
$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '"><i class="' . $icon . '"></i> ' . $text . '</a>';
|
||
}
|
||
}
|
||
return implode(' ', $html);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('build_heading')) {
|
||
|
||
/**
|
||
* 生成页面Heading
|
||
*
|
||
* @param string $path 指定的path
|
||
* @return string
|
||
*/
|
||
function build_heading($path = null, $container = true)
|
||
{
|
||
$title = $content = '';
|
||
if (is_null($path)) {
|
||
$action = request()->action();
|
||
$controller = str_replace('.', '/', Loader::parseName(request()->controller()));
|
||
$path = strtolower($controller . ($action && $action != 'index' ? '/' . $action : ''));
|
||
}
|
||
// 根据当前的URI自动匹配父节点的标题和备注
|
||
$data = Db::name('auth_rule')->where('name', $path)->field('title,remark')->find();
|
||
if ($data) {
|
||
$title = __($data['title']);
|
||
$content = __($data['remark']);
|
||
}
|
||
if (!$content) {
|
||
return '';
|
||
}
|
||
$result = '<div class="panel-lead"><em>' . $title . '</em>' . $content . '</div>';
|
||
if ($container) {
|
||
$result = '<div class="panel-heading">' . $result . '</div>';
|
||
}
|
||
return $result;
|
||
}
|
||
}
|