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:
yangwx committed 2026-10-22 00:53:39 +08:00
1 parent 1d49e6f5ee
commit 18101cf11a
636 files changed
+58517 -9519

No files matched your search

+46 -21
View File
@@ -13,35 +13,40 @@ namespace ywxapp\library;
/**
* 搜索引擎蜘蛛识别
*
* 仅按 User-Agent 特征串识别(不做 DNS 反查,保证零阻塞)。
* 新增蜘蛛只需在 SPIDERS 追加一行:标识 => [UA特征串列表, 显示名]。
* 两层判定:
* 1. detect() —— 仅按 User-Agent 特征串识别(零阻塞,可被伪造)
* 2. rdnsSuffixes() —— 返回该蜘蛛官方反向域名后缀,供 SpiderVerify 做
* 「PTR 反查 + A 正查」双向验证,识别伪造 UA 的假蜘蛛。
*
* 新增蜘蛛:追加一行 标识 => [UA特征串列表, 显示名, 官方rDNS域名后缀列表]。
* rDNS 后缀留空数组表示「官方不提供 rDNS,无法校验」,此类只按 UA 计数。
*
* @author ywxapp <admin@ywxapp.cn>
*/
class SpiderDetect
{
/**
* 蜘蛛特征表:key=入库标识,value=[UA 特征串(不区分大小写,命中任一即算), 中文显示名]
* 蜘蛛特征表:key=入库标识,value=[UA 特征串(不区分大小写,命中任一即算), 中文显示名, 官方 rDNS 域名后缀]
*/
public const SPIDERS = [
'baidu' => [['Baiduspider'], '百度'],
'google' => [['Googlebot', 'Google-InspectionTool', 'AdsBot-Google'], '谷歌'],
'bing' => [['bingbot', 'msnbot', 'BingPreview'], '必应'],
'sogou' => [['Sogou web spider', 'Sogou inst spider', 'Sogou Pic Spider'], '搜狗'],
'so360' => [['360Spider', 'HaoSouSpider', 'qihoobot'], '360'],
'bytedance' => [['Bytespider', 'ToutiaoSpider'], '字节跳动'],
'shenma' => [['YisouSpider'], '神马'],
'huawei' => [['PetalBot'], '华为花瓣'],
'yandex' => [['YandexBot'], 'Yandex'],
'duckduckgo' => [['DuckDuckBot', 'DuckDuckGo-Favicons-Bot'], 'DuckDuckGo'],
'yahoo' => [['Yahoo! Slurp'], '雅虎'],
'apple' => [['Applebot'], '苹果'],
'gptbot' => [['GPTBot', 'ChatGPT-User', 'OAI-SearchBot'], 'OpenAI'],
'claudebot' => [['ClaudeBot', 'anthropic-ai'], 'Anthropic'],
'semrush' => [['SemrushBot'], 'Semrush'],
'ahrefs' => [['AhrefsBot'], 'Ahrefs'],
'mj12' => [['MJ12bot'], 'Majestic'],
'facebook' => [['facebookexternalhit', 'FacebookBot'], 'Facebook'],
'baidu' => [['Baiduspider'], '百度', ['baidu.com', 'baidu.jp']],
'google' => [['Googlebot', 'Google-InspectionTool', 'AdsBot-Google', 'Mediapartners-Google'], '谷歌', ['googlebot.com', 'google.com']],
'bing' => [['bingbot', 'msnbot', 'BingPreview', 'adidxbot'], '必应', ['search.msn.com', 'msn.com']],
'sogou' => [['Sogou web spider', 'Sogou inst spider', 'Sogou Pic Spider', 'Sogou Spider'], '搜狗', ['sogou.com', 'sogou.com.cn']],
'so360' => [['360Spider', 'HaoSouSpider', 'qihoobot'], '360', ['360.cn', '360safe.com', 'qihoo.net']],
'bytedance' => [['Bytespider', 'ToutiaoSpider'], '字节跳动', ['bytedance.com', 'bytespider.com']],
'shenma' => [['YisouSpider'], '神马', ['sm.cn', 'yisou.com']],
'huawei' => [['PetalBot'], '华为花瓣', ['huawei.com']],
'yandex' => [['YandexBot', 'YandexImages', 'YandexMetricaBot'], 'Yandex', ['yandex.ru', 'yandex.net', 'yandex.com']],
'duckduckgo' => [['DuckDuckBot', 'DuckDuckGo-Favicons-Bot'], 'DuckDuckGo', ['duckduckgo.com']],
'yahoo' => [['Yahoo! Slurp'], '雅虎', ['yahoo.com', 'yahoo.net', 'crawl.yahoo.net']],
'apple' => [['Applebot'], '苹果', ['apple.com']],
'gptbot' => [['GPTBot', 'ChatGPT-User', 'OAI-SearchBot'], 'OpenAI', ['openai.com']],
'claudebot' => [['ClaudeBot', 'anthropic-ai'], 'Anthropic', ['anthropic.com']],
'semrush' => [['SemrushBot'], 'Semrush', ['semrush.com']],
'ahrefs' => [['AhrefsBot'], 'Ahrefs', ['ahrefs.com']],
'mj12' => [['MJ12bot'], 'Majestic', ['majestic12.co.uk', 'mj12bot.com']],
'facebook' => [['facebookexternalhit', 'FacebookBot'], 'Facebook', ['facebook.com', 'fbsv.net', 'tfbnw.net']],
];
/**
@@ -81,4 +86,24 @@ class SpiderDetect
}
return $out;
}
/**
* 该蜘蛛的官方 rDNS 域名后缀列表(空数组 = 官方不提供 rDNS,无法做真伪校验)
*
* @param string $key 蜘蛛标识
* @return string[]
*/
public static function rdnsSuffixes(string $key): array
{
$suffixes = self::SPIDERS[$key][2] ?? [];
return is_array($suffixes) ? $suffixes : [];
}
/**
* 该蜘蛛是否支持 rDNS 真伪校验
*/
public static function verifiable(string $key): bool
{
return self::rdnsSuffixes($key) !== [];
}
}