Files
YwxAppThink/ywxapp/middleware/ThemeInject.php
T
yangwx 18101cf11a feat: 上线前安全加固与发布工程化;appmall 由插件迁移为独立应用
安全(P0)
- 发布包签名:新增 scripts/package_keygen.php、scripts/package_verify.php、
  ywxapp\service\PackageVerifier;FrameworkService::apply() 解压前强制验签
  (ywxapp.framework_package_verify,默认开启)
- JWT 收紧:config/jwt.php 移除公开默认密钥,JwtService::assertStrongSecret()
  校验非空 / 长度≥32 / 已知弱值;.env.production.example 键名由 JWT_SECRET_KEY 修正为 JWT_SECRET
- /api/v1/Article:免登录白名单收窄为只读接口,写操作字段白名单 + assertOwner() 越权校验
- 开发者令牌:查找接口不再回显明文、统一失败文案 + 限流
- TemplateInstaller:文件名白名单 + realpath 归一化 + 目录内断言,修复 Zip Slip;补 use ZipArchive
- 后台登录:接入验证码、失败锁定与登录限流(config/ratelimit.php)
- 缓存 / Session 驱动可由 .env 覆盖(CACHE_DRIVER / SESSION_TYPE),集群部署改 redis
- paycenter 支付日志开关改为 PAY_LOG,不再跟随 APP_DEBUG

工程化 / 交付
- 新增 CHANGELOG.md 与 .env.production.example;composer.lock 入库锁定依赖版本
- 插件离线安装 CLI 增加 --uid,后台上传安装透传 uid,付费插件授权校验可通过
- 授权拒绝文案区分「未携带 uid」与「未找到有效授权」,便于排障
- .gitignore 放行 .env.production.example / .env.addon.example / .gitattributes / public/.htaccess

结构
- appmall 由 addon/appmall 迁移为独立应用 app/appmall(市场 / 开发者中心 / 审核 / 分润)
2026-10-22 00:53:39 +08:00

88 lines
3.4 KiB
PHP
Raw 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\middleware;
use ywxapp\library\SiteTheme;
/**
* 整站 layui 主题注入中间件(全局注册于 app/middleware.php)。
*
* 为什么用中间件而不是改各 Base 控制器:
* - 现有 SkinVariables 只在「插件上下文」注入(FrontendBase/MemberBase/BackendBase
* 各自 fetch 钩子),非插件页面拿不到任何皮肤变量,不是真正的整站。
* - 中间件在响应发出前统一处理,天然覆盖全部应用(backend/member/frontend/api)
* 与全部插件页面,且不侵入任何控制器代码。
*
* 注入内容(插入到 </head> 之前,即排在页面自有 CSS 之后):
* 1. /static/theme/layui-theme.css —— layui 主色已变量化的覆写层
* 2. <style id="ywx-site-theme">:root{--ywx-*}</style> —— 站点变量(config/theme.php)
*
* 层叠关系(后加载者胜):
* layui.css -> 页面/插件自有 CSS -> 本站主题层 -> 站点变量
* 因此:站点变量控制 layui 组件底色;插件模板又能通过自身 CSS 覆盖细节。
*
* 严格限制只处理 HTML 页面响应,避免污染 JSON / 文件下载 / 重定向等。
*
* @author ywxapp <admin@ywxapp.cn>
*/
class ThemeInject
{
public function handle($request, \Closure $next)
{
$response = $next($request);
try {
if (! SiteTheme::isEnabled()) {
return $response;
}
// 只看 HTML 文档响应
if (! $this->isHtmlPage($response)) {
return $response;
}
$html = (string) $response->getContent();
if ($html === '' || stripos($html, '</head>') === false) {
return $response;
}
// 幂等:已注入过则跳过(例如视图里显式引入了主题层)
if (stripos($html, 'ywx-site-theme') !== false || stripos($html, 'layui-theme.css') !== false) {
return $response;
}
$inject = SiteTheme::linkTag() . SiteTheme::styleTag() . "\n";
$html = preg_replace('/<\/head>/i', $inject . '</head>', $html, 1);
$response->content($html);
} catch (\Throwable $e) {
// 主题注入绝不能影响页面输出:失败时原样返回
}
return $response;
}
/**
* 判定是否为可注入的 HTML 页面响应。
* 先看 Content-Type,缺失时用内容兜底(避免 API JSON / 下载被污染)。
*/
protected function isHtmlPage($response): bool
{
$contentType = '';
try {
$header = $response->getHeader('Content-Type');
$contentType = is_array($header) ? (string) ($header[0] ?? '') : (string) $header;
} catch (\Throwable $e) {
$contentType = '';
}
if ($contentType !== '') {
return stripos($contentType, 'text/html') !== false;
}
return true; // 无 Content-Type 时交给后续 </head> 检测兜底
}
}