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(市场 / 开发者中心 / 审核 / 分润)
This commit is contained in:
1 parent
1d49e6f5ee
commit
18101cf11a
636 files changed
+58517
-9519
No files matched your search
@@ -13,6 +13,7 @@ namespace ywxapp\middleware;
|
||||
use think\facade\Db;
|
||||
use ywxapp\model\BaseModel;
|
||||
use ywxapp\library\SpiderDetect;
|
||||
use ywxapp\library\SpiderVerify;
|
||||
|
||||
/**
|
||||
* 全站搜索蜘蛛统计中间件(全局注册于 app/middleware.php)
|
||||
@@ -24,6 +25,12 @@ use ywxapp\library\SpiderDetect;
|
||||
* - 任何数据库异常静默吞掉(统计绝不能影响业务),缺表时经 BaseModel 自愈引擎 自愈一次;
|
||||
* - install.sql 为唯一事实源,此处 DDL 仅为老库运行时兜底。
|
||||
*
|
||||
* 真伪识别(v2 新增):
|
||||
* - UA 命中后调用 SpiderVerify 做 rDNS 双向验证(PTR + 官方后缀 + A/AAAA 正查);
|
||||
* - 结果写入 verified(0待验证 / 1真蜘蛛 / 2假蜘蛛)与 rdns(反查主机名);
|
||||
* - 聚合表额外维护 fake_count,后台可直观看到「多少是伪造 UA 刷的」;
|
||||
* - 校验结果按 IP 缓存 24h,DNS 查询有硬超时与熔断,不会拖慢站点。
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class SpiderStat
|
||||
@@ -46,7 +53,7 @@ class SpiderStat
|
||||
}
|
||||
|
||||
/**
|
||||
* 落库:明细 + 按日聚合
|
||||
* 落库:明细 + 按日聚合(含真伪判定)
|
||||
*/
|
||||
protected function record($request, $response, string $spider, string $ua): void
|
||||
{
|
||||
@@ -54,20 +61,27 @@ class SpiderStat
|
||||
$logTable = $prefix . 'common_spider_log';
|
||||
$statTable = $prefix . 'common_spider_stat';
|
||||
|
||||
$ip = mb_substr((string) $request->ip(), 0, 45);
|
||||
|
||||
// 真伪校验:命中缓存则零 DNS 开销;仅对配置了官方 rDNS 的蜘蛛生效
|
||||
$verdict = SpiderVerify::verify($ip, $spider);
|
||||
|
||||
$data = [
|
||||
'spider' => $spider,
|
||||
'url' => mb_substr((string) $request->url(), 0, 500),
|
||||
'ip' => mb_substr((string) $request->ip(), 0, 45),
|
||||
'ip' => $ip,
|
||||
'app' => mb_substr((string) (app('http')->getName() ?: ''), 0, 20),
|
||||
'user_agent' => mb_substr($ua, 0, 500),
|
||||
'http_code' => method_exists($response, 'getCode') ? (int) $response->getCode() : 200,
|
||||
'verified' => (int) $verdict['status'],
|
||||
'rdns' => mb_substr((string) $verdict['rdns'], 0, 255),
|
||||
'create_at' => time(),
|
||||
];
|
||||
|
||||
try {
|
||||
$this->insert($logTable, $statTable, $data, $spider);
|
||||
} catch (\Throwable $e) {
|
||||
// 表可能不存在(老库未升级):自愈一次后重试
|
||||
// 表可能不存在或老库缺列:自愈一次后重试
|
||||
$this->ensureTables($logTable, $statTable);
|
||||
$this->insert($logTable, $statTable, $data, $spider);
|
||||
}
|
||||
@@ -79,16 +93,17 @@ class SpiderStat
|
||||
protected function insert(string $logTable, string $statTable, array $data, string $spider): void
|
||||
{
|
||||
Db::table($logTable)->insert($data);
|
||||
// 按日聚合:主键(stat_date, spider),存在则计数+1
|
||||
|
||||
// 按日聚合:主键(stat_date, spider),存在则累加;fake_count 只累加假蜘蛛次数
|
||||
Db::execute(
|
||||
"INSERT INTO `{$statTable}` (`stat_date`, `spider`, `count`) VALUES (?, ?, 1) "
|
||||
. "ON DUPLICATE KEY UPDATE `count` = `count` + 1",
|
||||
[date('Y-m-d'), $spider]
|
||||
"INSERT INTO `{$statTable}` (`stat_date`, `spider`, `count`, `fake_count`) VALUES (?, ?, 1, ?) "
|
||||
. 'ON DUPLICATE KEY UPDATE `count` = `count` + 1, `fake_count` = `fake_count` + VALUES(`fake_count`)',
|
||||
[date('Y-m-d'), $spider, (int) ($data['verified'] ?? 0) === 2 ? 1 : 0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缺表自愈(DDL 与 public/install/install.sql 保持一致)
|
||||
* 缺表/缺列自愈(DDL 与 public/install/install.sql 保持一致)
|
||||
*/
|
||||
protected function ensureTables(string $logTable, string $statTable): void
|
||||
{
|
||||
@@ -100,17 +115,26 @@ class SpiderStat
|
||||
`app` varchar(20) NOT NULL DEFAULT '' COMMENT '应用名',
|
||||
`user_agent` varchar(500) NOT NULL DEFAULT '' COMMENT 'User-Agent',
|
||||
`http_code` smallint unsigned NOT NULL DEFAULT '200' COMMENT '响应状态码',
|
||||
`verified` tinyint(1) NOT NULL DEFAULT '0' COMMENT '真伪:0待验证 1真蜘蛛 2假蜘蛛',
|
||||
`rdns` varchar(255) NOT NULL DEFAULT '' COMMENT 'rDNS反查主机名',
|
||||
`create_at` int NOT NULL DEFAULT '0' COMMENT '抓取时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_spider_time` (`spider`,`create_at`),
|
||||
KEY `idx_create_at` (`create_at`)
|
||||
KEY `idx_create_at` (`create_at`),
|
||||
KEY `idx_verified` (`verified`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='搜索蜘蛛抓取日志'");
|
||||
|
||||
BaseModel::ensureTable($statTable, "CREATE TABLE IF NOT EXISTS `{$statTable}` (
|
||||
`stat_date` date NOT NULL COMMENT '统计日期',
|
||||
`spider` varchar(20) NOT NULL DEFAULT '' COMMENT '蜘蛛标识',
|
||||
`count` int unsigned NOT NULL DEFAULT '0' COMMENT '抓取次数',
|
||||
`fake_count` int unsigned NOT NULL DEFAULT '0' COMMENT '其中假蜘蛛次数',
|
||||
PRIMARY KEY (`stat_date`,`spider`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='搜索蜘蛛按日统计'");
|
||||
|
||||
// 老库补列(表已存在但缺新字段)
|
||||
BaseModel::ensureColumn($logTable, 'verified', "tinyint(1) NOT NULL DEFAULT '0' COMMENT '真伪:0待验证 1真蜘蛛 2假蜘蛛'");
|
||||
BaseModel::ensureColumn($logTable, 'rdns', "varchar(255) NOT NULL DEFAULT '' COMMENT 'rDNS反查主机名'");
|
||||
BaseModel::ensureColumn($statTable, 'fake_count', "int unsigned NOT NULL DEFAULT '0' COMMENT '其中假蜘蛛次数'");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user