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

+175 -39
View File
@@ -1,63 +1,199 @@
# =============================================================
# YwxApp 环境变量模板 (.env.example)
# 复制本文件为 .env 后填写真实值。.env 已被 .gitignore 忽略,不会入库。
# 注意:键名前后不要留空格(如 `KEY=value`,不能是 `KEY =value`)。
# -------------------------------------------------------------
# 用法:cp `config.example` `.env` 后填写真实值。
# .env 已被 .gitignore 忽略,不会入库。
# 注意:等号两侧不要留空格(`KEY=value`,不能是 `KEY= value` 或 `KEY =value`)。
# 注释行以 `#` 起始;空值表示「使用代码内默认值」(详见各键说明)。
# 章节:① 部署相关 ② 应用基础 ③ 数据库 ④ Redis 缓存 ⑤ 文件存储
# ⑥ 多语言 ⑦ JWT/鉴权 ⑧ 邮件 ⑨ 数据同步 ⑩ 支付
# ⑪ 应用市场 / 插件密钥 ⑫ 第三方插件(短信等可选)
# =============================================================
# -------------------- 部署 FTP 密码 --------------------
# scripts/deploy_wxapp_ftp.ps1 部署脚本读取此密码(优先环境变量 $env:WXAPP_FTP_PASS,其次本文件)
WXAPP_FTP_PASS=在此填写FTP密码
# ==================== ① 部署相关 ====================
# 用于将打包产物(scripts/package_*.php 生成)通过 FTP 推送到生产服务器。
# 该变量是「部署者专用钩子」,不依赖项目内任何脚本——
# 部署脚本(如 PowerShell / Bash 等)由部署方自行维护,scripts/ 内不存在此脚本。
# 部署脚本读取建议:优先环境变量 $env:WXAPP_FTP_PASS / $WXAPP_FTP_PASS,其次本文件。
# 仅用于部署发布,与运行期无关;不需要可留空。
WXAPP_FTP_PASS=
# -------------------- 数据库(ThinkPHP 标准键) --------------------
# config/database.php 通过 env('DB_*') 读取
DB_TYPE=mysql
# ==================== ② 应用基础 ====================
# 读取:config/app.php — app_debug / app_host
APP_DEBUG=true
# APP_DEBUG=true → 显示错误、SQL 日志、关闭模板缓存(仅本地/调试用)
# APP_DEBUG=false → 生产环境务必设为 false
APP_HOST=
# 官方 API 地址:用于框架在线升级、远程市场同步
# 读取:config/ywxapp.php — api_url(env('APP_API_URL', 'https://www.ywxapp.cn'))
# 中心站模式留空或指向本机;客户机模式指向中心站域名。
APP_API_URL=https://www.ywxapp.cn
# 市场角色(控制 is_market_client()):center / client / auto
# 读取:config/ywxapp.php — market_mode(env('APP_MARKET_MODE', 'auto'))
# center → 显式锁定本机为中心站(本地开发推荐,不再依赖 api_url 推断)
# client → 显式锁定本机为客户机(应用市场公开入口会主动 404)
# auto → api_url 为空/本机时为中心站,否则为客户机(默认,受 api_url 影响)
# 显式声明优于 auto,避免后续误改 APP_API_URL 时角色被翻转。
APP_MARKET_MODE=auto
# ==================== ③ 数据库(ThinkPHP 标准键) ====================
# 读取:config/database.php
# 安装向导(public/install/Installer.php)会重新写入这些键。
DB_DRIVER=mysql # default 连接驱动(一般无需修改)
DB_TYPE=mysql # connections.mysql.type
DB_HOST=127.0.0.1
DB_NAME=ywxapp_dev
DB_NAME=ywxapp
DB_USER=root
DB_PASS=
DB_PORT=3306
DB_CHARSET=utf8
DB_PREFIX=wx_
DB_PERSISTENT=true
DB_CHARSET=utf8mb4 # 建议 utf8mb4(emoji/中文兼容)
DB_PREFIX=wxapp_ # 表前缀,安装时由 Installer 统一写入
DB_PERSISTENT=true # 是否启用长连接(高并发场景视 MySQL 配置决定)
# -------------------- 应用 --------------------
# config/app.php 通过 env('APP_DEBUG' / 'APP_HOST') 读取
APP_DEBUG=true
APP_HOST=localhost
# -------------------- Redis 缓存(ThinkPHP 标准键) --------------------
# config/cache.php 通过 env('REDIS_*') 读取
# ==================== ④ Redis 缓存(ThinkPHP 标准键) ====================
# 读取:config/cache.php
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# -------------------- 应用市场 / 插件密钥 --------------------
# 后台 JWT 鉴权密钥(ywxapp/service/JwtService.php 读 env('JWT_SECRET'))
# 生产环境务必替换为强随机值
# HS256 要求密钥 ≥ 256 bits(32 字节),下面为 48 字节 base64 占位,生产请替换为自己的强随机值
JWT_SECRET=replace_with_a_48_byte_base64_strong_random_secret_key_2026xxxx
# ==================== ⑤ 文件存储 ====================
# 读取:config/storage.php — default + drivers.{alioss,qcos}
# 取值:local(默认)/ alioss / qcos(其它驱动需在 drivers 中添加)
FILE_STORAGE_DRIVER=local
# 插件签名/下载校验密钥(ywxapp/service/AddonService.php、FrameworkService.php 读 config('ywxapp.addon_secret'))
# 中心站与客户机需一致;生产务必替换
# ---- 阿里云 OSS(FILE_STORAGE_DRIVER=alioss 时必填)----
ALIOSS_ACCESS_KEY=
ALIOSS_SECRET_KEY=
ALIOSS_ENDPOINT= # 如 oss-cn-hangzhou.aliyuncs.com
ALIOSS_BUCKET=
ALIOSS_CDN_DOMAIN= # 可选:启用 CDN 加速域名
# ---- 腾讯云 COS(FILE_STORAGE_DRIVER=qcos 时必填)----
QCOS_REGION= # 如 ap-guangzhou
QCOS_SECRET_ID=
QCOS_SECRET_KEY=
QCOS_BUCKET=
# ==================== ⑥ 多语言 ====================
# 读取:config/lang.php — default_lang
# 取值:zh-cn / en-us / 自定义语种(需配套语言包)
DEFAULT_LANG=zh-cn
# ==================== ⑦ JWT / 鉴权 ====================
# 后台 / 会员 JWT 鉴权密钥
# 读取:config/jwt.php — secret(默认值仅为占位,生产务必替换)
# HS256 要求 ≥ 256 bits(32 字节)。建议用 `openssl rand -base64 48` 生成。
JWT_SECRET=replace_with_48_byte_base64_strong_random_secret_key_xxxxx
# ==================== ⑧ 邮件 ====================
# 读取:config/mail.php
# 留空时发信功能不会启用;建议使用 SMTP 协议 + SSL。
MAIL_HOST=smtp.example.com
MAIL_PORT=465
MAIL_SECURE=ssl # ssl / tls / 空
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM=
MAIL_FROM_NAME=YwxApp 应用市场
# ==================== ⑨ 数据同步 ====================
# 读取:config/sync.php — isEnable / baseUrl / key
# 用于「主框架 ↔ 业务后台」数据同步通道(开关、目标地址、共享密钥)。
SYNC_IS_ENABLE=false
SYNC_BASE_URL=https://sync.example.com
SYNC_KEY=your-256-bit-secret-key-here
# ==================== ⑩ 支付 ====================
# 读取:config/pay.php
# 三重防御(已就位):
# 1) 本键默认关闭(防御性默认,避免默认值泄露);
# 2) Installer.writeEnv() 会强制把 PAY_MOCK=false 写入 .env;
# 3) MarketService / Market 在生产(APP_DEBUG=false)拒绝执行 mock。
# 本地调试:显式设 PAY_MOCK=true(须与 APP_DEBUG=true 配对)。
PAY_MOCK=false
# 说明:真实的下单 / 验签 / 回调已收拢到支付中心插件 addon/paycenter(见其 README)。
# 下面的 PAY_ALIPAY_* / PAY_WECHAT_* 仍是密钥的唯一来源(不进数据库),插件与核心共用。
# 自检:php think paycenter:check --ping(--channel=alipay|wechat|all)
# ---- 支付宝(证书模式,Yansongda Pay v3.7 的硬性要求)----
# 普通公钥模式在该版本已不被识别,必须在开放平台「接口加签方式」切换为证书并下载三份证书。
# 私钥可填 PEM 全文、单行 base64,或文件路径;三份证书放 runtime/cert/ 下即可(路径可以是相对路径,
# 配 config/pay.php 会按项目根目录解析成绝对路径,避免 Web 入口 CWD 在 public/ 时读不到)。
# 文件名随意,只要与下面三个 *_PATH 对得上:官方下载名分别是
# appCertPublicKey_*.crt(应用公钥证书)/ alipayCertPublicKey_*.crt(支付宝公钥证书)/ alipayRootCert.crt(根证书)
PAY_ALIPAY_MODE=0 # 整数:0=正式 1=沙箱 2=服务商(见 Yansongda\Pay\Pay::MODE_*)
PAY_ALIPAY_APP_ID=
PAY_ALIPAY_APP_SECRET_CERT= # 应用私钥(或填私钥文件路径)
PAY_ALIPAY_APP_PUBLIC_CERT_PATH=runtime/cert/appCertPublicKey.crt
PAY_ALIPAY_PUBLIC_CERT_PATH=runtime/cert/alipayCertPublicKey.crt
PAY_ALIPAY_ROOT_CERT_PATH=runtime/cert/alipayRootCert.crt
# 仅 Windows/未配 curl.cainfo 的 PHP 需要:连网关报 cURL error 60(SSL certificate problem)时,
# 下载 https://curl.se/ca/cacert.pem 放本地并在这里指向它;正常服务器留空即可
PAY_HTTP_CA=
# ---- 微信支付(v3;微信已无官方沙箱,mode 恒为 0,只能真实小额支付后退款)----
# 商户平台 → API 安全:设置 APIv3 密钥、下载 API 证书(apiclient_cert.pem)与私钥(apiclient_key.pem)
PAY_WECHAT_MCH_ID=
PAY_WECHAT_APP_ID= # 未单独配置时 mp/mini 也用它
PAY_WECHAT_MP_APP_ID= # 公众号 JSAPI(可选,默认取 APP_ID)
PAY_WECHAT_MINI_APP_ID= # 小程序(可选,默认取 APP_ID)
PAY_WECHAT_SECRET_KEY= # APIv3 密钥
PAY_WECHAT_SECRET_CERT= # 商户 API 私钥(PEM / 单行 base64 / 文件路径)
PAY_WECHAT_PUBLIC_CERT_PATH=runtime/cert/apiclient_cert.pem
# 平台证书(回调验签用)留空即可:SDK 会凭 APIv3 密钥自动下载;
# 生产环境建议 php think paycenter:check 触发一次后落到 runtime/cert/wechat/ 并配置 wechat_public_cert_path。
# ==================== ⑪ 应用市场 / 插件密钥 ====================
# ---- 开发者令牌:后台「发布到官方市场」与 appmall 插件开发者中心共用 ----
# 读取:
# config/appmall.php → config('appmall.developer_token')
# config/ywxapp.php → config('ywxapp.developer_token')(已与 appmall 同源)
# app/backend/controller/Addons.php::submitOfficial() 实际取的是上面这个值
# 留空则后台操作时会提示「未配置开发者令牌:请在 .env 设置 APPMARKET_DEV_TOKEN」。
APPMARKET_DEV_TOKEN=
# 开发者账号名(可选,仅用于显示)
APPMARKET_DEV_NAME=
# 远程市场共享密钥:仅当本机作「客户机」连接中心站时需要
# 读取:config/appmall.php → config('appmall.remote_token')
# 单站点中心站模式留空即可;客户机必须与中心站 APPMARKET_REMOTE_TOKEN 一致。
APPMARKET_REMOTE_TOKEN=
# 连接远程市场时是否校验 SSL 证书(生产建议 true)
APPMARKET_SSL_VERIFY=true
# 平台抽成比例(0~1):插件市场成交时平台分账比例,默认 0.2(20%)
APPMARKET_COMMISSION_RATE=0.2
# 发布前 PHP 语法 + 高危函数扫描(生产建议 true)
APPMARKET_SECURITY_SCAN=true
# ---- 插件签名 / 下载校验密钥(中心站与客户机共享)----
# 读取:config/ywxapp.php — addon_secret
# 生产务必替换为强随机值;中心站与所有客户机必须一致。
ADDON_SECRET=change_me_to_a_strong_addon_secret
# 是否允许未知来源插件(离线包)安装:true 时跳过远程授权校验
# 读取:Env::get('app_debug') && config('ywxapp.unknownsources')
# 读取:config/ywxapp.php — unknownsources(仅当 APP_DEBUG=true 时生效)
ADDON_UNKNOWN_SOURCES=false
# 开发者模式开关:开启后允许本地开发/调试插件发布
# 读取:Env::get('addon_developer') 或 config('ywxapp.addon_developer')
# 开发者模式开关:开启后后台「插件」出现「设计插件」入口
# 读取:config/ywxapp.php — addon_developer
# 生产务必保持 false(与 APP_DEBUG 解耦的独立开关)。
ADDON_DEVELOPER=false
# 发布到官方公共市场(www.ywxapp.cn)所需令牌,由官方运营分配
# 留空则后台「发布到官方市场」会提示先配置;读取:env('APPMARKET_DEV_TOKEN')
APPMARKET_DEV_TOKEN=
# ==================== ⑫ 第三方插件(按需启用) ====================
# 短信插件 addon/smssend 需配置网关(由插件自身 config 维护,不一定走 .env);
# 其他插件的配置项请参考各插件 addon/<name>/README.md。
# -------------------- 应用市场授权校验开关(config/ywxapp.php) --------------------
# addon_license_check:控制客户机启用插件时的授权闸门与运行期巡检
# false → 关闭校验(开发/自用)
# true → 开启;中心站不可达也判定失败并禁用插件(防离线绕过)
# offline_tolerant → 开启;仅中心站明确拒绝(吊销/过期/域名不符)才禁用,不可达放行
# 说明:此键写在 config/ywxapp.php 中,不在此 .env;此处仅作说明,便于查阅。
# 对应代码:ywxapp/service/AddonService.php verifyRuntimeLicense() / checkLicenses()
# =============================================================
# 提示:以下为「代码硬编码、未走 env()」的项目,仅作备忘。
# config/ywxapp.php: addon_license_check=false (运行期授权巡检开关,写死 false;
# 如需开启,请直接编辑该文件或后续 PR 改造为 env() 控制)
# =============================================================