上次错误

This commit is contained in:
2026-08-08 16:26:31 +08:00
parent 8101177cb5
commit ba660d0c57
50 changed files with 1250 additions and 1326 deletions
-29
View File
@@ -76,35 +76,6 @@ class AdminController extends Controller
return 'assets/uploads/' . $name;
}
/**
* 处理多文件上传(name="gallery[]"),返回相对站点根路径数组。
* 逐张复用 uploadFile 的校验逻辑(真实图像/体积上限/类型白名单/SVG 消毒),
* 任一文件失败不影响其余文件。
*/
protected function uploadFiles(string $key): array
{
if (empty($_FILES[$key]['tmp_name']) || !is_array($_FILES[$key]['tmp_name'])) return [];
$names = $_FILES[$key]['name'] ?? [];
$errors = $_FILES[$key]['error'] ?? [];
$sizes = $_FILES[$key]['size'] ?? [];
$out = [];
foreach ($_FILES[$key]['tmp_name'] as $i => $tmp) {
if (empty($tmp)) continue;
// 桥接到单文件校验逻辑
$_FILES['_multi_tmp'] = [
'name' => $names[$i] ?? 'x.bin',
'type' => '',
'tmp_name' => $tmp,
'error' => $errors[$i] ?? UPLOAD_ERR_OK,
'size' => $sizes[$i] ?? 0,
];
$rel = $this->uploadFile('_multi_tmp');
unset($_FILES['_multi_tmp']);
if ($rel) $out[] = $rel;
}
return $out;
}
/** 消毒 SVG:移除脚本、事件处理器与危险协议,阻断存储型 XSS */
protected function sanitizeSvg(string $svg): string
{
+28 -12
View File
@@ -13,17 +13,17 @@ class AuthController extends Controller
{
if (is_admin()) { $this->redirect(login_landing()); }
$error = '';
$blocked = false;
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 质量红线:登录必须校验 CSRF + 验证码 + IP 级双窗口限速(失败 10 分钟/5 次、成功 30 分钟/5 次),杜绝机器人暴力破解
// 质量红线:登录必须校验 CSRF + 验证码 + IP/会话双重失败限速,杜绝机器人暴力破解
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
if (!csrf_check()) {
$error = '表单已过期,请刷新页面后重试';
} elseif (ip_login_blocked($ip)) {
$error = '尝试次数过多,请 30 分钟后再试';
$blocked = true;
$error = '尝试次数过多,请 15 分钟后再试';
} elseif (!captcha_check($this->post('captcha'))) {
$error = '验证码错误,请重新计算';
} elseif ($this->isBlocked()) {
$error = '尝试次数过多,请 15 分钟后再试';
} else {
$u = trim($this->post('username'));
$p = $this->post('password');
@@ -38,7 +38,8 @@ class AuthController extends Controller
$user = ['id' => 0, 'username' => $u, 'name' => '管理员', 'role' => 'super_admin'];
}
if ($ok) {
ip_login_register_success($ip); // 记录成功登录(纳入 30 分钟 5 次上限),并重置失败计数
$this->clearAttempts();
ip_login_clear($ip);
session_regenerate_id(true); // 防会话固定
$_SESSION['admin_logged'] = true;
$_SESSION['admin_id'] = $user['id'] ?? 0;
@@ -51,15 +52,11 @@ class AuthController extends Controller
$_SESSION['psi_perms'] = $dec($user['psi_perms'] ?? null);
$this->redirect(login_landing());
}
ip_login_register_fail($ip); // 记录一次失败(纳入 10 分钟 5 次上限)
$this->registerAttempt();
ip_login_register($ip);
$error = '用户名或密码错误';
}
}
// 被限速(IP 失败/成功过多)时返回 429 + Retry-After,明确告知客户端稍后再试
if ($blocked && !headers_sent()) {
http_response_code(429);
header('Retry-After: ' . ip_login_remaining($ip));
}
$captcha = captcha_make(); // 每次渲染都发放新的算术验证码
return $this->view('admin/login', ['error' => $error, 'captcha' => $captcha]);
}
@@ -71,6 +68,25 @@ class AuthController extends Controller
&& \Core\App::config('app.driver', 'file') !== 'mysql';
}
/** 暴力破解限速:单会话 15 分钟内失败 5 次即锁定 */
private function isBlocked(): bool
{
$t = $_SESSION['login_attempts'] ?? null;
if (!$t || ($t['time'] + 900) < time()) return false;
return $t['count'] >= 5;
}
private function registerAttempt(): void
{
$t = $_SESSION['login_attempts'] ?? ['count' => 0, 'time' => time()];
if (($t['time'] + 900) < time()) { $t = ['count' => 0, 'time' => time()]; }
$t['count']++;
$_SESSION['login_attempts'] = $t;
}
private function clearAttempts(): void
{
unset($_SESSION['login_attempts']);
}
/** 修改当前登录账号的密码 */
public function password()
{
+12 -54
View File
@@ -19,18 +19,13 @@ class CaseController extends AdminController
public function create()
{
return $this->view('admin/cases_form', [
'c' => null,
'mode' => 'fixed',
]);
return $this->view('admin/cases_form', ['c' => null]);
}
public function store()
{
if (!csrf_check()) { $this->redirect('admin/cases'); }
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
$cover = $this->uploadFile('cover') ?? $this->post('cover_url', '');
$content = ($mode === 'fixed') ? $this->post('content', '') : '';
$model = new CustomerCase();
$id = $model->insert([
'title' => $this->post('title'),
@@ -39,85 +34,48 @@ class CaseController extends AdminController
'industry' => $this->post('industry', ''),
'cover' => $cover,
'summary' => $this->post('summary'),
'content' => $content,
'content' => $this->post('content'),
'published_at' => $this->post('published_at', date('Y-m-d')),
'sort_order' => (int) $this->post('sort_order', 0),
'status' => $this->post('status', 1) ? 1 : 0,
'views' => 0,
'layout' => $this->post('layout', ''),
'mode' => $mode,
]);
// URL 标识留空时按记录序号顺序生成(短、稳定),避免中文标题导致过长
$slug = $this->post('slug') ? slugify($this->post('slug')) : (string)$id;
$model->update($id, ['slug' => $slug]);
// 新建时若选择「可视化编辑」,保存后直接进入可视化编辑器排版
if ($mode === 'builder') {
$this->redirect('admin/cases/edit/' . $id);
}
$this->redirect('admin/cases');
}
public function edit($id)
{
$model = new CustomerCase();
$c = $model->find($id);
if (!$c) { $this->redirect('admin/cases'); }
$mode = empty($c['mode']) ? 'fixed' : $c['mode'];
if ($mode === 'builder') {
$layout = [];
if (!empty($c['layout'])) {
$dec = json_decode($c['layout'], true);
if (is_array($dec)) $layout = $dec;
}
return $this->view('admin/cases_builder', ['c' => $c, 'layout' => $layout, 'mode' => $mode]);
}
return $this->view('admin/cases_form', ['c' => $c, 'mode' => $mode]);
}
/** 切换编辑模式(固定版面 <-> 可视化编辑),仅更新 mode 字段 */
public function switchMode($id)
{
$c = (new CustomerCase())->find($id);
if (!$c) { $this->redirect('admin/cases'); }
$target = ($this->get('mode') === 'builder') ? 'builder' : 'fixed';
(new CustomerCase())->update($id, ['mode' => $target]);
$this->redirect('admin/cases/edit/' . $id);
$case = (new CustomerCase())->find($id);
if (!$case) { $this->redirect('admin/cases'); }
return $this->view('admin/cases_form', ['c' => $case]);
}
public function update($id)
{
if (!csrf_check()) { $this->redirect('admin/cases'); }
$model = new CustomerCase();
$c = $model->find($id);
if (!$c) { $this->redirect('admin/cases'); }
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
$case = $model->find($id);
if (!$case) { $this->redirect('admin/cases'); }
$cover = $this->uploadFile('cover');
if (!$cover && $this->post('cover_url')) $cover = $this->post('cover_url');
if (!$cover) $cover = $c['cover'] ?? '';
$data = [
if (!$cover) $cover = $case['cover'] ?? '';
$model->update($id, [
'title' => $this->post('title'),
'slug' => $this->post('slug') ? slugify($this->post('slug')) : (string)$id,
'customer' => $this->post('customer', ''),
'industry' => $this->post('industry', ''),
'cover' => $cover,
'summary' => $this->post('summary'),
'content' => $this->post('content'),
'published_at' => $this->post('published_at', date('Y-m-d')),
'sort_order' => (int) $this->post('sort_order', 0),
'status' => $this->post('status', 1) ? 1 : 0,
'mode' => $mode,
];
if ($mode === 'fixed') {
$data['content'] = $this->post('content', '');
} else {
$layout = $this->post('layout', '');
if ($layout !== '' && !is_array(json_decode($layout, true))) { $layout = ''; }
$data['layout'] = $layout;
}
$model->update($id, $data);
'layout' => $this->post('layout', ''),
]);
$this->redirect('admin/cases');
}
+7 -51
View File
@@ -15,91 +15,47 @@ class CategoryController extends AdminController
public function create()
{
return $this->view('admin/category_form', [
'c' => null,
'mode' => 'fixed',
]);
return $this->view('admin/category_form', ['c' => null]);
}
public function store()
{
if (!csrf_check()) { $this->redirect('admin/categories'); }
$cat = new Category();
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
$description = ($mode === 'fixed') ? $this->post('description', '') : '';
$id = $cat->insert([
'name' => $this->post('name'),
'slug' => '',
'icon' => $this->post('icon', '❄'),
'description' => $description,
'description' => $this->post('description'),
'sort_order' => (int)$this->post('sort_order', 0),
'status' => $this->post('status', 1) ? 1 : 0,
'layout' => $this->post('layout', ''),
'mode' => $mode,
]);
// URL 标识留空时按记录序号顺序生成(短、稳定)
$slug = $this->post('slug') ? slugify($this->post('slug')) : (string)$id;
$cat->update($id, ['slug' => $slug]);
// 新建时若选择「可视化编辑」,保存后直接进入可视化编辑器排版
if ($mode === 'builder') {
$this->redirect('admin/categories/edit/' . $id);
}
$this->redirect('admin/categories');
}
public function edit($id)
{
$cat = new Category();
$c = $cat->find($id);
if (!$c) { $this->redirect('admin/categories'); }
$mode = empty($c['mode']) ? 'fixed' : $c['mode'];
if ($mode === 'builder') {
$layout = [];
if (!empty($c['layout'])) {
$dec = json_decode($c['layout'], true);
if (is_array($dec)) $layout = $dec;
}
return $this->view('admin/category_builder', ['c' => $c, 'layout' => $layout, 'mode' => $mode]);
}
return $this->view('admin/category_form', ['c' => $c, 'mode' => $mode]);
}
/** 切换编辑模式(固定版面 <-> 可视化编辑),仅更新 mode 字段 */
public function switchMode($id)
{
$c = (new Category())->find($id);
if (!$c) { $this->redirect('admin/categories'); }
$target = ($this->get('mode') === 'builder') ? 'builder' : 'fixed';
(new Category())->update($id, ['mode' => $target]);
$this->redirect('admin/categories/edit/' . $id);
return $this->view('admin/category_form', ['c' => $c]);
}
public function update($id)
{
if (!csrf_check()) { $this->redirect('admin/categories'); }
$cat = new Category();
$c = $cat->find($id);
if (!$c) { $this->redirect('admin/categories'); }
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
$data = [
(new Category())->update($id, [
'name' => $this->post('name'),
'slug' => $this->post('slug') ? slugify($this->post('slug')) : (string)$id,
'icon' => $this->post('icon', '❄'),
'description' => $this->post('description'),
'sort_order' => (int)$this->post('sort_order', 0),
'status' => $this->post('status', 1) ? 1 : 0,
'mode' => $mode,
];
if ($mode === 'fixed') {
$data['description'] = $this->post('description', '');
} else {
$layout = $this->post('layout', '');
if ($layout !== '' && !is_array(json_decode($layout, true))) { $layout = ''; }
$data['layout'] = $layout;
}
$cat->update($id, $data);
'layout' => $this->post('layout', ''),
]);
$this->redirect('admin/categories');
}
+8 -50
View File
@@ -15,67 +15,37 @@ class NewsController extends AdminController
public function create()
{
return $this->view('admin/news_form', [
'n' => null,
'mode' => 'fixed',
]);
return $this->view('admin/news_form', ['n' => null]);
}
public function store()
{
if (!csrf_check()) { $this->redirect('admin/news'); }
$news = new News();
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
$cover = $this->uploadFile('cover') ?? $this->post('cover_url', '');
$content = ($mode === 'fixed') ? $this->post('content', '') : '';
$news = new News();
$id = $news->insert([
'title' => $this->post('title'),
'slug' => '',
'cover' => $cover,
'summary' => $this->post('summary'),
'content' => $content,
'content' => $this->post('content'),
'author' => $this->post('author', '酷冰甲'),
'published_at' => $this->post('published_at', date('Y-m-d')),
'status' => $this->post('status', 1) ? 1 : 0,
'views' => 0,
'layout' => $this->post('layout', ''),
'mode' => $mode,
]);
// URL 标识留空时按记录序号顺序生成(短、稳定)
$slug = $this->post('slug') ? slugify($this->post('slug')) : (string)$id;
$news->update($id, ['slug' => $slug]);
// 新建时若选择「可视化编辑」,保存后直接进入可视化编辑器排版
if ($mode === 'builder') {
$this->redirect('admin/news/edit/' . $id);
}
$this->redirect('admin/news');
}
public function edit($id)
{
$news = new News();
$n = $news->find($id);
if (!$n) { $this->redirect('admin/news'); }
$mode = empty($n['mode']) ? 'fixed' : $n['mode'];
if ($mode === 'builder') {
$layout = [];
if (!empty($n['layout'])) {
$dec = json_decode($n['layout'], true);
if (is_array($dec)) $layout = $dec;
}
return $this->view('admin/news_builder', ['n' => $n, 'layout' => $layout, 'mode' => $mode]);
}
return $this->view('admin/news_form', ['n' => $n, 'mode' => $mode]);
}
/** 切换编辑模式(固定版面 <-> 可视化编辑),仅更新 mode 字段 */
public function switchMode($id)
{
$n = (new News())->find($id);
if (!$n) { $this->redirect('admin/news'); }
$target = ($this->get('mode') === 'builder') ? 'builder' : 'fixed';
(new News())->update($id, ['mode' => $target]);
$this->redirect('admin/news/edit/' . $id);
return $this->view('admin/news_form', ['n' => $n]);
}
public function update($id)
@@ -84,32 +54,20 @@ class NewsController extends AdminController
$news = new News();
$n = $news->find($id);
if (!$n) { $this->redirect('admin/news'); }
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
$cover = $this->uploadFile('cover');
if (!$cover && $this->post('cover_url')) $cover = $this->post('cover_url');
if (!$cover) $cover = $n['cover'] ?? '';
$data = [
$news->update($id, [
'title' => $this->post('title'),
'slug' => $this->post('slug') ? slugify($this->post('slug')) : (string)$id,
'cover' => $cover,
'summary' => $this->post('summary'),
'content' => $this->post('content'),
'author' => $this->post('author', '酷冰甲'),
'published_at' => $this->post('published_at', date('Y-m-d')),
'status' => $this->post('status', 1) ? 1 : 0,
'mode' => $mode,
];
if ($mode === 'fixed') {
$data['content'] = $this->post('content', '');
} else {
$layout = $this->post('layout', '');
if ($layout !== '' && !is_array(json_decode($layout, true))) { $layout = ''; }
$data['layout'] = $layout;
}
$news->update($id, $data);
'layout' => $this->post('layout', ''),
]);
$this->redirect('admin/news');
}
+14 -38
View File
@@ -13,56 +13,32 @@ class PageController extends AdminController
]);
}
/** 编辑:按页面 mode 渲染对应编辑器(固定版面 / 可视化编辑) */
public function edit($id)
{
$p = (new Page())->find($id);
if (!$p) { $this->redirect('admin/pages'); }
$mode = empty($p['mode']) ? 'fixed' : $p['mode'];
if ($mode === 'builder') {
$layout = [];
if (!empty($p['layout'])) {
$dec = json_decode($p['layout'], true);
if (is_array($dec)) $layout = $dec;
}
return $this->view('admin/page_builder', ['p' => $p, 'layout' => $layout, 'mode' => $mode]);
$layout = [];
if (!empty($p['layout'])) {
$dec = json_decode($p['layout'], true);
if (is_array($dec)) $layout = $dec;
}
return $this->view('admin/page_form', ['p' => $p, 'mode' => $mode]);
return $this->view('admin/page_builder', ['p' => $p, 'layout' => $layout]);
}
/** 切换编辑模式(固定版面 <-> 可视化编辑),仅更新 mode 字段 */
public function switchMode($id)
{
$p = (new Page())->find($id);
if (!$p) { $this->redirect('admin/pages'); }
$target = ($this->get('mode') === 'builder') ? 'builder' : 'fixed';
(new Page())->update($id, ['mode' => $target, 'updated_at' => date('Y-m-d')]);
$this->redirect('admin/pages/edit/' . $id);
}
/** 保存:兼容两种编辑器,按实际提交的字段写入(content / layout / mode */
public function update($id)
{
if (!csrf_check()) { $this->redirect('admin/pages'); }
$data = [
$layout = $this->post('layout', '');
// 校验:非空的 layout 必须是合法 JSON 数组
if ($layout !== '') {
$dec = json_decode($layout, true);
if (!is_array($dec)) $layout = '';
}
(new Page())->update($id, [
'title' => $this->post('title'),
'layout' => $layout,
'updated_at' => date('Y-m-d'),
];
if ($this->post('content') !== null) {
$data['content'] = $this->post('content');
}
if ($this->post('layout') !== null) {
$layout = $this->post('layout', '');
if ($layout !== '' && !is_array(json_decode($layout, true))) {
$layout = '';
}
$data['layout'] = $layout;
}
$mode = $this->post('mode');
if ($mode === 'builder' || $mode === 'fixed') {
$data['mode'] = $mode;
}
(new Page())->update($id, $data);
]);
$this->redirect('admin/pages');
}
}
+9 -67
View File
@@ -33,7 +33,6 @@ class ProductController extends AdminController
return $this->view('admin/product_form', [
'p' => null,
'cats' => (new Category())->all(),
'mode' => 'fixed',
]);
}
@@ -42,33 +41,25 @@ class ProductController extends AdminController
if (!csrf_check()) { $this->redirect('admin/products'); }
$product = new Product();
$cover = $this->uploadFile('cover') ?? $this->post('cover_url', '');
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
$gallery = ($mode === 'fixed') ? $this->uploadFiles('gallery') : [];
$description = ($mode === 'fixed') ? $this->post('description', '') : '';
$id = $product->insert([
'category_id' => (int)$this->post('category_id'),
'name' => $this->post('name'),
'slug' => '',
'cover' => $cover,
'summary' => $this->post('summary'),
'description' => $description,
'description' => $this->post('description'),
'price' => (float)$this->post('price', 0),
'specs' => $this->specsToJson($this->post('specs', '')),
'gallery' => json_encode($gallery, JSON_UNESCAPED_UNICODE),
'gallery' => '[]',
'tags' => $this->post('tags', ''),
'sort_order' => (int)$this->post('sort_order', 0),
'status' => $this->post('status', 1) ? 1 : 0,
'created_at' => date('Y-m-d'),
'layout' => $this->post('layout', ''),
'mode' => $mode,
]);
// URL 标识留空时按记录序号顺序生成(短、稳定)
$slug = $this->post('slug') ? slugify($this->post('slug')) : (string)$id;
$product->update($id, ['slug' => $slug]);
// 新建时若选择「可视化编辑」,保存后直接进入可视化编辑器排版,无需再手动点编辑
if ($mode === 'builder') {
$this->redirect('admin/products/edit/' . $id);
}
$this->redirect('admin/products');
}
@@ -77,88 +68,39 @@ class ProductController extends AdminController
$product = new Product();
$p = $product->find($id);
if (!$p) { $this->redirect('admin/products'); }
$mode = empty($p['mode']) ? 'fixed' : $p['mode'];
$specs = $product->specsArray($p);
$specText = '';
foreach ($specs as $s) $specText .= ($s['k'] ?? '') . '|' . ($s['v'] ?? '') . "\n";
$cats = (new Category())->all();
if ($mode === 'builder') {
$layout = [];
if (!empty($p['layout'])) {
$dec = json_decode($p['layout'], true);
if (is_array($dec)) $layout = $dec;
}
return $this->view('admin/product_builder', [
'p' => $p,
'cats' => $cats,
'specText' => $specText,
'layout' => $layout,
'mode' => $mode,
]);
}
return $this->view('admin/product_form', [
'p' => $p,
'cats' => $cats,
'specText' => $specText,
'mode' => $mode,
'p' => $p,
'cats' => (new Category())->all(),
'specText'=> $specText,
]);
}
/** 切换编辑模式(固定版面 <-> 可视化编辑),仅更新 mode 字段,其余数据保留 */
public function switchMode($id)
{
$p = (new Product())->find($id);
if (!$p) { $this->redirect('admin/products'); }
$target = ($this->get('mode') === 'builder') ? 'builder' : 'fixed';
(new Product())->update($id, ['mode' => $target]);
$this->redirect('admin/products/edit/' . $id);
}
public function update($id)
{
if (!csrf_check()) { $this->redirect('admin/products'); }
$product = new Product();
$p = $product->find($id);
if (!$p) { $this->redirect('admin/products'); }
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
$cover = $this->uploadFile('cover');
if (!$cover && $this->post('cover_url')) $cover = $this->post('cover_url');
if (!$cover) $cover = $p['cover'] ?? '';
$data = [
$product->update($id, [
'category_id' => (int)$this->post('category_id'),
'name' => $this->post('name'),
'slug' => $this->post('slug') ? slugify($this->post('slug')) : (string)$id,
'cover' => $cover,
'summary' => $this->post('summary'),
'description' => $this->post('description'),
'price' => (float)$this->post('price', 0),
'specs' => $this->specsToJson($this->post('specs', '')),
'tags' => $this->post('tags', ''),
'sort_order' => (int)$this->post('sort_order', 0),
'status' => $this->post('status', 1) ? 1 : 0,
'mode' => $mode,
];
if ($mode === 'fixed') {
// 固定版面:保存图集与详细描述,保留原有 layout 不被覆盖
$newGal = $this->uploadFiles('gallery');
if (!empty($newGal)) {
$data['gallery'] = json_encode($newGal, JSON_UNESCAPED_UNICODE);
} elseif ($this->post('clear_gallery')) {
$data['gallery'] = '[]';
} else {
$data['gallery'] = $p['gallery'] ?? '[]';
}
$data['description'] = $this->post('description', '');
} else {
// 可视化编辑:更新 layout,保留图集/描述原值
$layout = $this->post('layout', '');
if ($layout !== '' && !is_array(json_decode($layout, true))) { $layout = ''; }
$data['layout'] = $layout;
}
$product->update($id, $data);
'layout' => $this->post('layout', ''),
]);
$this->redirect('admin/products');
}
+1 -13
View File
@@ -14,7 +14,7 @@ class SettingController extends AdminController
];
private $siteFields = [
'site_name', 'site_slogan', 'contact_phone', 'contact_email',
'contact_address', 'site_logo', 'icp', 'gongan', 'seo_title', 'seo_keywords', 'seo_description',
'contact_address', 'icp', 'seo_title', 'seo_keywords', 'seo_description',
];
private $payFields = [
'pay_enabled', 'pay_mode',
@@ -30,18 +30,6 @@ class SettingController extends AdminController
if (csrf_check()) {
$pairs = [];
foreach ($this->siteFields as $k) $pairs[$k] = $this->post($k, '');
// 网站 Logo:优先用上传文件,其次用填写的图片路径/网址;两者皆空则保留现值
$logoUp = $this->uploadFile('logo');
if ($logoUp !== null) {
$pairs['site_logo'] = $logoUp;
} else {
$url = trim((string) $this->post('logo_url', ''));
if ($url !== '') {
$pairs['site_logo'] = $url;
} else {
unset($pairs['site_logo']); // 不覆盖,保留数据库现有值(含默认商标)
}
}
$setting->saveMany($pairs, 'site');
}
$this->redirect('admin/settings');