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

+39
View File
@@ -0,0 +1,39 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
// | 应用市场(appmall)插件【运行期配置的规范默认源】
// |
// | 本文件是 config('appmall.*') 的完整默认集合(代码级默认值,支持 .env 覆盖)。
// | 后台「插件配置」表单(config.php) 仅负责「可编辑字段 + 默认值展示」,
// | 数据库保存值按最高优先级覆盖本文件。读取一律用 config('appmall.xxx')。
// |
// | 约定:布尔项统一用 bool(true/false),不要写 '1'/'0'(字符串),
// | 以免代码 if(!$v) 判定出错('0' 在 PHP 里是 truthy)。
// +----------------------------------------------------------------------
return [
// ===== 市场基础设置(仅中心站相关:运营与开发者自助)=====
'appmall_name' => 'YwxApp 应用市场',
'allow_register' => true, // 开放开发者自助注册
'need_approval' => true, // 注册后需审核(审核通过才下发开发者令牌)
'allow_upload' => true, // 允许后台上传发布核心包
// ===== 官方市场发布(把本机插件推送到 www.ywxapp.cn 官方公共市场)=====
// 与「远程中心站」是两回事:这里是【主动推送】,下面是【被动拉取】。
'developer_token' => env('APPMARKET_DEV_TOKEN', ''),
'developer_name' => env('APPMARKET_DEV_NAME', ''),
// ===== 远程市场 / 中心站客户机模式(仅当 ywxapp.api_url 指向其他服务器时生效)=====
// 「谁是中心站」由 config/ywxapp.php 的 api_url 决定(留空=本机即中心站,无需下面这些)。
'remote_token' => env('APPMARKET_REMOTE_TOKEN', ''), // 客户机↔中心站共享密钥,须与中心站一致
'ssl_verify' => env('APPMARKET_SSL_VERIFY', true), // 连接远程市场时 SSL 证书校验
// ===== 交易 / 安全 =====
'commission_rate' => env('APPMARKET_COMMISSION_RATE', 0.2), // 平台抽成 0~1
'security_scan' => env('APPMARKET_SECURITY_SCAN', true), // 发布前 PHP 语法+高危函数扫描
];
+2 -2
View File
@@ -9,8 +9,8 @@
// +----------------------------------------------------------------------
return [
// 默认缓存驱动
'default' => 'file',
// 默认缓存驱动(集群/多副本部署请在 .env 设 CACHE_DRIVER=redis)
'default' => env('CACHE_DRIVER', 'file'),
'fail_delete' => true,
// 缓存连接方式配置
'stores' => [
+9 -1
View File
@@ -7,7 +7,15 @@
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
return [
// 指令定义
// 指令定义:这里显式登记,避免依赖「某应用 command 目录是否被自动扫描」
//(本仓库为多应用 + 插件式命令目录,自动发现不稳定)。
'commands' => [
// 应用市场到期/试用到期提醒(appmall 模块随插件分发,类不存在时自动跳过)
'appmall:license-notify' => \app\appmall\command\LicenseNotify::class,
// 支付配置自检(SDK / mode / 证书 / 沙箱连通性)
// 已迁到支付中心插件 addon/paycenter(命令名 paycenter:check);这里保留旧别名,
// 插件未安装时类名不存在,Console::addCommands() 会自动跳过,不会致命。
'appmall:pay-check' => \addon\paycenter\command\Check::class,
'paycenter:check' => \addon\paycenter\command\Check::class,
],
];
+6 -6
View File
@@ -13,12 +13,12 @@ return [
'path' => '/',
// cookie 有效域名
'domain' => '',
// cookie 启用安全传输
'secure' => false,
// httponly设置
'httponly' => false,
// cookie 启用安全传输(生产 HTTPS 站点在 .env 设 COOKIE_SECURE=true)
'secure' => env('COOKIE_SECURE', false),
// httponly设置(防 XSS 窃取;JS 侧自行写入的 cookie 不受影响)
'httponly' => env('COOKIE_HTTPONLY', true),
// 是否使用 setcookie
'setcookie' => true,
// samesite 设置,支持 'strict' 'lax'
'samesite' => '',
// samesite 设置,支持 'strict' 'lax'(生产 HTTPS 建议 COOKIE_SAMESITE=strict)
'samesite' => env('COOKIE_SAMESITE', 'lax'),
];
+57 -8
View File
@@ -6,14 +6,63 @@
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
// 分层跨域配置。本文件被 **两套** CORS 机制读取,改动前务必读完注释:
//
// A. ywxapp\middleware\CrossDomain(本项目自研,真正的执行者)
// → 读 app_cross(按应用名分层白名单)+ allowed_methods/headers/max_age/credentials
// B. think\cors\HandleCors(topthink/think-cors,经服务发现自动挂载,见 vendor/services.php)
// → 读 paths / allowed_origins / allowed_methods / allowed_headers / exposed_headers ...
// 注意:它的构造函数会对 allowed_methods / allowed_headers 执行 array_map,
// 因此这两项**必须是数组**;写成 'GET, POST' 这样的字符串会在每个请求
// 构造函数里抛 TypeError(array_map(): Argument #2 must be of type array, string given)。
//
// 现状:跨域已由 CrossDomain 分层接管,think-cors 保持「空转」(paths 为空 → 不匹配任何路径)。
// 若将来想让 think-cors 真正接管,把 paths 配上路径即可,但**必须先移除 CrossDomain**,
// 否则两条链路会重复输出 CORS 头。
//
// app_cross 按应用名取规则,取值:
// '*' 全放开(无凭证模式,公开 API 可用)
// 'a.com,b.com' 精确白名单(支持凭证)
// '*.example' 通配子域
// '' 禁止跨域(默认;同源页面不受影响)
//
// 安全策略:backend / member / frontend 一律留空(零跨域),
// 仅 api 对外开放;生产建议把 api 也收紧为精确白名单。
return [
'paths' => [],
'allowed_origins' => ['*'],
'app_cross' => [
'api' => env('CORS_API_ORIGINS', '*'),
'backend' => env('CORS_BACKEND_ORIGINS', ''),
'member' => env('CORS_MEMBER_ORIGINS', ''),
'frontend' => env('CORS_FRONTEND_ORIGINS', ''),
// 未列出的应用(含 addon 应用、index 兜底)默认禁跨域
'default' => env('CORS_ORIGINS', ''),
],
// 预检/实际请求允许的方法(**数组**,think-cors 强制要求)
'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
// 允许的请求头(**数组**,含本框架 JWT 鉴权实际使用的 Authorization / Authorization-Refresh)
'allowed_headers' => [
'Content-Type',
'Authorization',
'Authorization-Refresh',
'X-Requested-With',
'X-CSRF-TOKEN',
'token',
'X-Token',
],
// 预检结果缓存(秒)
'max_age' => 86400,
// 白名单模式下允许携带 Cookie 凭证(对 '*' 无效,浏览器强制忽略)
'supports_credentials' => true,
// ==================== 以下为 think-cors 专用,保持空转 ====================
// 空数组 → HandleCors::hasMatchingPath() 恒 false → 直接放行,不再输出任何 CORS 头。
// 不要配成 'api/*' 之类,否则与 CrossDomain 重复输出。
'paths' => [],
'allowed_origins' => [],
'allowed_origins_patterns' => [],
'allowed_methods' => ['*'],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
'exposed_headers' => [],
];
+29
View File
@@ -0,0 +1,29 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
// CSRF 防护配置(ywxapp\middleware\VerifyCsrfToken 消费,
// 注册于 app/{backend,member,frontend}/middleware.php、
// addon/docs/middleware.php 与 addon/articles/middleware.php)。
return [
// 启用 CSRF 校验的应用(会话型);api 走 JWT 不注册中间件
// 'docs' / 'articles' / 'blog' 由各自插件的 middleware.php 注册 VerifyCsrfToken 后启用同源校验
'session_apps' => ['backend', 'member', 'frontend', 'index', 'docs', 'articles', 'blog'],
// strict 模式:强校验 __token__ / X-CSRF-TOKEN 与 Session 令牌一致。
// 开启前提:所有表单模板注入 {:token_field()}(或隐藏域 __token__)、
// JS 请求携带 X-CSRF-TOKEN 头,否则现有表单会被拒绝。默认关闭,
// 由第一层「同源校验」先行防护。
'strict' => env('CSRF_STRICT', false),
// 豁免路径(正则,匹配当前应用内 pathinfo):
// 支付异步回调、第三方 webhook 等「必然跨源」的 POST 不做同源校验。
'exempt' => [
'#^(pay|payment|notify|callback|webhook)/#i',
'#/(notify|callback|webhook)$#i',
],
];
+7 -4
View File
@@ -13,7 +13,10 @@ return [
'algorithm' => 'HS256',
// 对称密钥(HS256 使用)
'secret' => env('JWT_SECRET', 'your-default-secret-key-change-in-production'),
// 安全基线:**不允许**默认弱密钥。未配置 JWT_SECRET 时 JwtService 会拒绝签发,
// 而不是退回一个公开的兜底值(否则任何人都能伪造管理员 token)。
// 生成:openssl rand -base64 64
'secret' => env('JWT_SECRET', ''),
// 非对称密钥路径(RS256 使用)
'private_key_path' => root_path() . 'keys/private.key',
@@ -23,7 +26,7 @@ return [
'access_ttl' => 1800, // Access Token 有效期(秒)→ 30分钟
'refresh_ttl' => 604800, // Refresh Token 有效期(秒)→ 7天
// JWT 标准声明
'issuer' => 'https://your-app.com',
'audience' => 'https://api.your-app.com',
// JWT 标准声明(生产部署请按实际域名覆盖 JWT_ISSUER / JWT_AUDIENCE)
'issuer' => env('JWT_ISSUER', 'https://www.ywxapp.cn'),
'audience' => env('JWT_AUDIENCE', 'https://www.ywxapp.cn'),
];
+3 -2
View File
@@ -40,8 +40,9 @@ return [
'close' => false,
// 日志输出格式化
'format' => '[%s][%s] %s',
// 是否实时写入
'realtime_write' => false,
// 是否实时写入:支付/订单/登录失败/授权吊销等关键事件依赖日志,
// 缓冲在内存会在进程崩溃时丢失,生产必须实时落盘
'realtime_write' => true,
],
// 其它日志通道配置
],
+92 -19
View File
@@ -8,29 +8,102 @@
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
return [
// 模拟支付开关:无商户号时直接完成下单并签发授权,便于本地/自托管环境跑通购买闭环。
// 生产环境务必在 .env 设置 PAY_MOCK=false 关闭,否则任何人可「免费」购得付费插件。
'mock_enable' => env('PAY_MOCK', true),
// 应用私钥兼容处理:
// .env 只能放单行,私钥通常以「裸 base64」贴进来(带 PEM 头尾的多行内容塞不进 .env)。
// SDK 的 get_private_cert()(vendor/yansongda/pay/src/Functions.php)规则很挑食:
// ① 是文件路径 → 读文件内容(内容仍要符合 ②③)
// ② 以 '-----BEGIN PRIVATE KEY-----' 开头 → 原样返回(必须是真正的 PKCS8)
// ③ 其余一律再包一层 '-----BEGIN RSA PRIVATE KEY-----'(按 PKCS1 处理)
// 由此得出:PKCS8 必须自己拼好 PKCS8 的 PEM 头;PKCS1 反而必须交【裸 base64】让 SDK 去包。
// 反过来做(给 PKCS1 套 'PRIVATE KEY' 头 / 给已是 PEM 的内容再套一层 RSA 头)就会:
// openssl_sign(): Supplied key param cannot be coerced into a private key
// 所以这里按 DER 里是否含 rsaEncryption 的 OID 判定 PKCS8/PKCS1,再交给 SDK 正确的一侧。
$alipaySecretCert = (string) env('PAY_ALIPAY_APP_SECRET_CERT', '');
if ($alipaySecretCert !== '') {
if (is_file($alipaySecretCert)) {
$alipaySecretCert = (string) file_get_contents($alipaySecretCert);
}
$privateKeyBody = (string) preg_replace('/-----[^-]+-----|\s+/', '', $alipaySecretCert);
$der = base64_decode($privateKeyBody, true) ?: '';
// rsaEncryption OID:1.2.840.113549.1.1.1 —— 只出现在 PKCS8 的结构里
$isPkcs8 = str_contains(substr($der, 0, 64), "\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01");
$wrapped = wordwrap($privateKeyBody, 64, "\n", true);
$alipaySecretCert = $isPkcs8
? "-----BEGIN PRIVATE KEY-----\n{$wrapped}\n-----END PRIVATE KEY-----"
: $privateKeyBody;
}
// 支付宝
// 证书路径统一按项目根目录解析成绝对路径:
// SDK 的 get_public_cert() 是 `is_file($key) ? file_get_contents($key) : $key`
// (见 vendor/yansongda/pay/src/Functions.php),相对路径按【进程 CWD】解析:
// CLI 在项目根目录 → 能读到;Web 入口是 public/index.php、CWD 变成 public/ → 读不到,
// 下单会抛「配置异常: 解析 `app_public_cert_path` 失败」,回调验签同样失败。
// 所以在配置层补全一次,业务代码(MarketService / Market / PayCheck)都无需各自处理。
$resolveCertPath = static function (string $path): string {
if ($path === '') {
return '';
}
if (str_starts_with($path, '/') || str_starts_with($path, '\\') || preg_match('/^[A-Za-z]:[\\\\\/]/', $path)) {
return $path;
}
$root = function_exists('root_path') ? root_path() : dirname(__DIR__) . DIRECTORY_SEPARATOR;
return $root . ltrim($path, '/\\');
};
return [
// 模拟支付开关:仅在 APP_DEBUG=true 且无商户号时生效,便于本地/自托管环境跑通购买闭环。
// 默认 false(防御性默认,避免默认开启后被人"免费"购得付费插件授权)。
// 三重防御确保生产安全:
// 1) 本开关默认关闭(防止默认值泄露);
// 2) 安装向导 public/install/Installer.php::writeEnv 会强制把 PAY_MOCK=false 写入 .env;
// 3) 即使 .env 被手误改错,MarketService / Market 在生产(APP_DEBUG=false)也会拒绝执行 mock。
// 本地调试:.env 中显式设置 PAY_MOCK=true(与 APP_DEBUG=true 配对使用)。
'mock_enable' => env('PAY_MOCK', false),
// 支付宝(Yansongda Pay v3.7 —— 仅支持「证书模式」,普通公钥模式不再被识别)。
// 证书在开放平台 → 应用 → 接口加签方式(沙箱同理)下载:
// app_public_cert_path → appCertPublicKey_*.crt(应用公钥证书)
// alipay_public_cert_path → alipayCertPublicKey_*.crt(支付宝公钥证书,验签用)
// alipay_root_cert_path → alipayRootCert.crt(支付宝根证书)
// 缺任一项都会抛「配置异常: 缺少支付宝配置 -- [xxx]」,见
// vendor/yansongda/pay/src/Plugin/Alipay/V2/StartPlugin.php
// vendor/yansongda/pay/src/Plugin/Alipay/V2/AddPayloadSignaturePlugin.php
'alipay' => [
'mode' => env('PAY_ALIPAY_MODE', 'normal'), // normal | dev | query
'app_id' => env('PAY_ALIPAY_APP_ID', ''),
'ali_public_key' => env('PAY_ALIPAY_PUBLIC_KEY', ''),
'private_key' => env('PAY_ALIPAY_PRIVATE_KEY', ''),
'return_url' => '',
'notify_url' => '',
// mode 必须是【整数】:0=正式 1=沙箱 2=服务商(Yansongda\Pay\Pay::MODE_*)。
// 写成 'normal'/'dev' 字符串会在拼网关 URL 时数组越界。
'mode' => (int) env('PAY_ALIPAY_MODE', 0),
'app_id' => env('PAY_ALIPAY_APP_ID', ''),
// 应用私钥:PEM 全文、单行 base64(已在上方自动补全 PEM 头并识别 PKCS8/PKCS1),或私钥文件路径
'app_secret_cert' => $alipaySecretCert,
'app_public_cert_path' => $resolveCertPath((string) env('PAY_ALIPAY_APP_PUBLIC_CERT_PATH', '')),
'alipay_public_cert_path' => $resolveCertPath((string) env('PAY_ALIPAY_PUBLIC_CERT_PATH', '')),
'alipay_root_cert_path' => $resolveCertPath((string) env('PAY_ALIPAY_ROOT_CERT_PATH', '')),
'return_url' => '',
'notify_url' => '',
],
// 微信支付(如需,在 buy() 中按相同方式调用 Pay::wechat(...) 即可)
// 微信支付(v3 API)。注:微信早已下线官方沙箱,mode 留 0 即可;
// 没有商户号时只能走 mock 或小额真实支付再退款。
// 说明:真实的下单 / 验签 / 回调已迁到支付中心插件(addon/paycenter),
// 这里保留仅为「既有部署的密钥来源」——插件 PayConfig 会用下面的值覆盖自己的同名项。
'wechat' => [
'mch_id' => env('PAY_WECHAT_MCH_ID', ''),
'app_id' => env('PAY_WECHAT_APP_ID', ''),
'secret_key' => env('PAY_WECHAT_SECRET_KEY', ''),
'cert_client' => '',
'cert_key' => '',
'return_url' => '',
'notify_url' => '',
'mode' => (int) env('PAY_WECHAT_MODE', 0),
'mch_id' => env('PAY_WECHAT_MCH_ID', ''),
// SDK 按场景取键:mp → mp_app_id、mini → mini_app_id、app → app_id
'app_id' => env('PAY_WECHAT_APP_ID', ''),
'mp_app_id' => env('PAY_WECHAT_MP_APP_ID', env('PAY_WECHAT_APP_ID', '')),
'mini_app_id' => env('PAY_WECHAT_MINI_APP_ID', env('PAY_WECHAT_APP_ID', '')),
// APIv3 密钥
'mch_secret_key' => env('PAY_WECHAT_SECRET_KEY', ''),
// 商户 API 私钥:文件路径或 PEM 内容(插件会再做一次 PKCS8/PKCS1 判定)
'mch_secret_cert' => env('PAY_WECHAT_SECRET_CERT', ''),
// 商户 API 证书:用于提取证书序列号
'mch_public_cert_path' => env('PAY_WECHAT_PUBLIC_CERT_PATH', ''),
// 微信平台证书(回调验签用):留空时 SDK 凭 APIv3 密钥自动下载;
// 生产环境建议落到本地并配置 [证书序列号 => 文件路径],避免每次请求都去拉取
'wechat_public_cert_path' => [],
'return_url' => '',
'notify_url' => '',
],
];
+34
View File
@@ -0,0 +1,34 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
// API 限流配置(ywxapp\middleware\RateLimit 消费,注册于 app/api/middleware.php)。
//
// rules:正则(匹配 pathinfo,不区分大小写)=> ['limit' => 次数, 'window' => 窗口秒数]
// 按声明顺序匹配,命中即用;limit = 0 表示该规则不限流。
// default:未命中任何规则的兜底限流。
return [
// 兜底:每 IP 每分钟 120 次(普通客户端足够,脚本刷量会被拦)
'default' => [
'limit' => (int) env('RATELIMIT_DEFAULT', 120),
'window' => 60,
],
'rules' => [
// 登录 / 登出:防爆破
'#(login|logout)#i' => ['limit' => 10, 'window' => 60],
// 注册 / 找回密码 / 重置密码:防批量注册与撞库
'#(register|retrieve|forget|reset|findpwd)#i' => ['limit' => 5, 'window' => 300],
// 验证码 / 短信 / 邮件发送:防短信轰炸
'#(captcha|sms|verif|sendmail|send_?code|email)#i' => ['limit' => 5, 'window' => 60],
// 支付 / 下单:防并发刷单
'#(pay|order|checkout)#i' => ['limit' => 20, 'window' => 60],
],
];
+6 -6
View File
@@ -16,8 +16,8 @@
return [
// SessionID 前缀(Cookie 名称)
'prefix' => 'PHPSESSID',
// 驱动方式
'type' => 'file',
// 驱动方式(集群/多副本部署请在 .env 设 SESSION_TYPE=redis,否则会随机掉登录态)
'type' => env('SESSION_TYPE', 'file'),
// 是否自动启动
'auto_start' => true,
// Session 保存路径(留空则使用 runtime_path() . 'session/',backend 应用为 runtime/addon/backend/session/)
@@ -31,10 +31,10 @@ return [
'data_compress' => false,
// Cookie 域名
'domain' => '',
// 仅 HTTPS 传输
'secure' => false,
// 仅 HTTP 可见(防 XSS)
'httponly' => false,
// 仅 HTTPS 传输(生产 HTTPS 站点在 .env 设 SESSION_SECURE=true)
'secure' => env('SESSION_SECURE', false),
// 仅 HTTP 可见(防 XSS 窃取 SessionID)
'httponly' => true,
// 使用 Cookie 传递 SessionID
'use_cookies' => true,
// 客户端缓存限制
+19
View File
@@ -0,0 +1,19 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
// 蜘蛛真伪识别配置(ywxapp\library\SpiderVerify 消费)
return [
// DNS 服务器(用于 PTR 反查与 A/AAAA 正查)
// 留空则自动探测:/etc/resolv.conf 的第一个 nameserver,探测不到用 223.5.5.5
// 内网环境无外网 DNS 时可填本地 DNS,如 '192.168.1.1'
'dns_server' => env('SPIDER_DNS_SERVER', ''),
// 说明:真伪判定走「PTR 反查 → 官方域名后缀 → A/AAAA 正查」三步,
// 结果按 IP 缓存 24 小时(SpiderVerify::TTL),单次 DNS 硬超时 0.5 秒,
// 连续失败 3 次熔断降级 5 分钟,不会拖慢站点。
];
+29
View File
@@ -0,0 +1,29 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp 站点级 layui 主题配置
// +----------------------------------------------------------------------
// | 说明:这是「整站 layui 皮肤」的唯一变量源,对所有页面生效
// | (后台 / 会员中心 / 前台 / 各插件前台),由全局中间件 ThemeInject 注入。
// |
// | 变量名与 public/static/theme/layui-theme.css 中的 --ywx-* 一一对应:
// | primary -> --ywx-primary 主色(按钮/表单聚焦/进度条/时间轴)
// | accent -> --ywx-accent 强调色(导航/分页当前页/上传/logo)
// | radius -> --ywx-radius 统一圆角
// | font -> --ywx-font 正文字体
// |
// | 改这里即可全站换肤,无需改动任何视图文件、也无需清理缓存。
// | 后台可在「模板/主题」界面写入本文件(见 SiteTheme::save())。
// +----------------------------------------------------------------------
return [
// 主题开关:关闭后不注入任何主题样式(用于排查样式冲突)
'enable' => true,
// 站点主题变量(值为空表示使用 layui-theme.css 内的兜底默认值)
'variables' => [
'primary' => '#d99cdd',
'accent' => '#16baaa',
'radius' => '2px',
'font' => '-apple-system, BlinkMacSystemFont, \'Microsoft YaHei\', \'PingFang SC\', sans-serif',
],
];
+33 -6
View File
@@ -35,6 +35,14 @@ return [
'login_captcha' => true,
//登录失败超过10次则1天后重试
'login_failure_retry' => true,
// ---- 登录爆破防护(AdminAuth::login 消费;不依赖 RateLimit 中间件 ----
// 原因:config/ratelimit.php 的 RateLimit 中间件只在 app/api 注册,
// 后台 /admin/login 走不到,必须有独立的计数兜底。
// 同一 IP + 同一账号在窗口内失败达上限 → 封禁 block 秒。设 0 表示关闭限流。
'login_fail_limit' => (int) env('LOGIN_FAIL_LIMIT', 10),
'login_fail_window' => (int) env('LOGIN_FAIL_WINDOW', 600),
'login_block_time' => (int) env('LOGIN_BLOCK_TIME', 900),
//是否同一账号同一时间只能在一个地方登录
'login_unique' => false,
//是否开启IP变动检测
@@ -55,6 +63,9 @@ return [
'version' => '1.1.1',
//API接口地址(本地自测:指向本机;生产部署需改回 https://www.ywxapp.cn,并确保中心站已部署含 framework 路由的新版 market)
'api_url' => env('APP_API_URL', 'https://www.ywxapp.cn'),
// 市场角色(控制 is_market_client()):center=本机即中心站 / client=本机即客户机 / auto=按 api_url+本机主机推断
// 显式声明优于 auto:避免 api_url 误指向别处时角色翻转。本地开发建议直接设 center。
'market_mode' => env('APP_MARKET_MODE', 'auto'),
// 是否开启多语言
'lang_switch_on' => true,
//是否允许未知来源的插件压缩包(本地离线安装需开启,配合 APP_DEBUG 跳过远程授权校验)
@@ -65,7 +76,7 @@ return [
'backup_global_files' => true,
//插件纯净模式,插件启用后是否删除插件目录的application、public和assets文件夹
'addon_pure_mode' => true,
'addon_license_check' => false, // 运行期授权校验开关(true 时声明 license=>true 的付费插件需存在有效授权才加载)
'addon_license_check' => env('ADDON_LICENSE_CHECK', false), // 运行期授权校验开关(true 时声明 license=>true 的付费插件需存在有效授权才加载)
// 下载签名密钥:用于 download 接口 generateSign 校验,防 license_key 泄漏后被冒用下载。
// 生产环境务必改为强随机值(建议写入 .env 的 ADDON_SECRET),并保持与 API 客户端一致。
// 生成强随机密钥示例: openssl rand -base64 64 或 bin2hex(random_bytes(32))
@@ -75,15 +86,31 @@ return [
// 自托管市场(中心站 addon/market 插件,路由 /api/addon/*)已配套实现:客户端 AddonService::download() 在开启时自动签名;
// 启用前请确保 api_url 指向本服务器(服务端/客户端共享同一 addon_secret)。
'addon_download_sign' => false,
// 应用市场(appmall)插件的运行期配置(开发者令牌、远程开关、SSL 校验、抽成比例等)
// 已迁移至插件目录 addon/appmall/config/appmall.php(随插件分发、支持 .env 覆盖),
// 通过 config('appmall.*') 读取。
// 主框架发布包(整站包/补丁/安装包)强制验签开关。
// true:FrameworkService::apply() 解压前必须过 PackageVerifier 的 RSA 验签,
// 未签名/签名不符/版本不一致一律拒绝安装(防升级通道被用作远程代码执行)。
// 仅「自建升级服务器 + 自己签名」的内网调试场景可关闭:FRAMEWORK_PACKAGE_VERIFY=false
'framework_package_verify' => env('FRAMEWORK_PACKAGE_VERIFY', true),
// 发布包公钥路径(留空用 ywxapp/data/package_public.pem,或 PackageVerifier::BUILTIN_PUBLIC_KEY)
'package_public_key_path' => env('PACKAGE_PUBLIC_KEY_PATH', ''),
// 应用市场(appmall)的运行期配置(开发者令牌、远程开关、SSL 校验、抽成比例等):
// appmall 已由插件迁为普通应用(app/appmall),其配置统一放在根级 config/appmall.php
// (全局加载,任意应用均可 config('appmall.*')),并由 .env 覆盖。
// 开发模式开关(抄 Discuz! 插件设计器):开启后后台「插件」出现「设计插件」入口,
// 可可视化创建/编辑本地插件(生成骨架、增删配置项/菜单/事件/路由、生成页面模块),改动实时生效。
// 生产环境务必保持 false(与 APP_DEBUG 解耦,独立开关)。
'developer_token' => env('DEVELOPER_TOKEN', ''),
// 历史上后台「发布到官方市场」误把 developer_token 关联到 DEVELOPER_TOKEN,导致用户照着错误提示配错键。
// 现统一为 APPMARKET_DEV_TOKEN(与 config/appmall.php 同源),错误提示也同步修正。
'developer_token' => env('APPMARKET_DEV_TOKEN', ''),
'addon_developer' => env('ADDON_DEVELOPER', false),
// 钩点可视化标注(Discuz 式开发者模式):开启后页面上的每个 hook() 埋点用蓝色虚线框 + 「⟡ 钩子名」标签标出,
// 未挂载插件的空埋点同样显示,便于对照「模板位置 ↔ 钩子名」。
// 后台「数据运维 → 模板钩子」可临时开关(cookie hook_dev,只在后台应用内生效,防泄漏给前台访客);
// 本开关为全局模式,前台/会员中心也需要标注时才置 true(建议仅开发环境)。
'hook_developer' => env('HOOK_DEVELOPER', false),
'backend_login_url' => '/admin/login/index', // 后台登录页地址(app_map: admin => backend)
'member_login_url' => '/user/login/index', // 会员中心登录页地址(app_map: member => frontend)