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
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/*
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-07-21 13:36:46
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-08-04 22:21:46
|
||||
* @Description: 应用市场统一路由(业务路由 + 对外 API)
|
||||
* @FilePath: \ywxapp_dev\app\appmall\route\app.php
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
*/
|
||||
// ============================================================
|
||||
// 应用市场「统一路由入口」(业务路由 + 对外 API)。
|
||||
//
|
||||
// appmall 已由插件(addon/appmall)迁移为普通应用(app/appmall),对外前缀 /appmall 不变:
|
||||
// think-multi-app 的 MultiApp 中间件按目录名解析 URL 首段 appmall → 命中 app/appmall/,
|
||||
// 并将 pathinfo 剥离首段(→ backend/xxx、developer/xxx、api/addon/*、store 等),
|
||||
// 再由框架自动加载本目录(app/appmall/route/*.php)。
|
||||
//
|
||||
// 因此本文件内的路由一律写成「相对组名」,与剥离首段后的 pathinfo 直接对齐:
|
||||
// backend/... → /appmall/backend/...
|
||||
// developer/... → /appmall/developer/...
|
||||
// api/... → /appmall/api/...
|
||||
// 本文件【不要】自行写 /appmall 前缀(会双重前缀,恒 404)。
|
||||
// ============================================================
|
||||
|
||||
use think\facade\Route;
|
||||
use app\appmall\controller\api\Market;
|
||||
use app\appmall\controller\backend\AddonReview;
|
||||
use app\appmall\controller\backend\Developer;
|
||||
use app\appmall\controller\backend\Revenue as BackendRevenue;
|
||||
use app\appmall\controller\Developer as DeveloperCenter;
|
||||
use app\appmall\controller\backend\Framework;
|
||||
use app\appmall\controller\api\Framework as ApiFramework;
|
||||
use app\appmall\controller\Framework as FrontendFramework;
|
||||
use app\appmall\controller\Store;
|
||||
use app\backend\controller\Rule;
|
||||
|
||||
// ===================== 业务路由 + 对外 API(/appmall/*) =====================
|
||||
// 前缀 /appmall 由 MultiApp 解析并剥离首段,本文件只写相对组名(见文件头说明)。
|
||||
|
||||
// ---------- 后台:插件审核 / 开发者管理 / 收益与结算(统一前缀 /appmall/backend) ----------
|
||||
Route::group('backend', function () {
|
||||
// 插件审核
|
||||
Route::get('addonreview', [AddonReview::class, 'index']);
|
||||
Route::get('addonreview/index', [AddonReview::class, 'index']);
|
||||
Route::post('addonreview/approve', [AddonReview::class, 'approve']);
|
||||
Route::post('addonreview/reject', [AddonReview::class, 'reject']);
|
||||
Route::post('addonreview/updatePrice', [AddonReview::class, 'updatePrice']);
|
||||
Route::get('addonreview/download', [AddonReview::class, 'download']);
|
||||
Route::get('addonreview/detail', [AddonReview::class, 'detail']);
|
||||
Route::get('addonreview/detailview', [AddonReview::class, 'detailview']);
|
||||
Route::get('addonreview/history', [AddonReview::class, 'history']);
|
||||
|
||||
// 用户评价审核(appmall_addon_reviews,区别于 submissions 的上架审核)
|
||||
Route::get('commentreview', [AddonReview::class, 'comments']);
|
||||
Route::get('commentreview/index', [AddonReview::class, 'comments']);
|
||||
Route::post('commentreview/approve', [AddonReview::class, 'approveComment']);
|
||||
Route::post('commentreview/reject', [AddonReview::class, 'rejectComment']);
|
||||
Route::post('commentreview/delete', [AddonReview::class, 'deleteComment']);
|
||||
|
||||
// 开发者管理
|
||||
Route::get('developer', [Developer::class, 'index']);
|
||||
Route::get('developer/index', [Developer::class, 'index']);
|
||||
Route::post('developer/approve', [Developer::class, 'approve']);
|
||||
Route::post('developer/disable', [Developer::class, 'disable']);
|
||||
Route::post('developer/resend', [Developer::class, 'resend']);
|
||||
Route::post('developer/reset', [Developer::class, 'reset']);
|
||||
|
||||
// 收益与结算
|
||||
Route::get('revenue', [BackendRevenue::class, 'index']);
|
||||
Route::get('revenue/index', [BackendRevenue::class, 'index']);
|
||||
Route::get('revenue/withdrawals', [BackendRevenue::class, 'withdrawals']);
|
||||
Route::post('revenue/settle', [BackendRevenue::class, 'settle']);
|
||||
Route::post('revenue/reject', [BackendRevenue::class, 'rejectWithdrawal']);
|
||||
|
||||
// 主框架升级(原 upgrade 插件,已合并进 market)
|
||||
Route::get('framework', [Framework::class, 'index']);
|
||||
Route::get('framework/index', [Framework::class, 'index']);
|
||||
Route::post('framework/upload', [Framework::class, 'upload']);
|
||||
Route::post('framework/publish', [Framework::class, 'publish']);
|
||||
Route::post('framework/delete', [Framework::class, 'delete']);
|
||||
Route::get('framework/download', [Framework::class, 'download']);
|
||||
});
|
||||
|
||||
// ---------- 开发者中心(独立前台应用,公开注册/令牌/收益/提现,非会员中心):统一前缀 /appmall/developer ----------
|
||||
// 仅【中心站】暴露:开发者自助注册/收益/提现依赖 appmall_developers / appmall_addon_revenues / appmall_addon_withdrawals 等
|
||||
// 运营表,客户机(ywxapp.api_url 指向其他服务器,见 is_market_client())作为中心站客户端不持有这些表,故不在路由层注册,
|
||||
// 访问即 404。配合 Developer 控制器 initialize() 的运行时兜底(即便路由缓存陈旧或被直连也能拦截)。
|
||||
// ---------- 开发者中心(独立前台,令牌认证):/appmall/Developer/* ----------
|
||||
|
||||
Route::get('developer/index', [DeveloperCenter::class, 'index']);
|
||||
Route::post('developer/save', [DeveloperCenter::class, 'save']);
|
||||
Route::get('developer/mine', [DeveloperCenter::class, 'mine']);
|
||||
Route::post('developer/lookup', [DeveloperCenter::class, 'lookup']);
|
||||
Route::get('developer/dashboard', [DeveloperCenter::class, 'dashboard']);
|
||||
Route::get('developer/console', [DeveloperCenter::class, 'console']);
|
||||
Route::post('developer/overview', [DeveloperCenter::class, 'overview']);
|
||||
Route::post('developer/apps', [DeveloperCenter::class, 'apps']);
|
||||
Route::post('developer/price', [DeveloperCenter::class, 'price']);
|
||||
Route::post('developer/earnings', [DeveloperCenter::class, 'earnings']);
|
||||
Route::post('developer/withdraw', [DeveloperCenter::class, 'withdraw']);
|
||||
|
||||
// ---------- 对外 API(统一前缀 /appmall/api):远程客户机经 Remote/Addon/FrameworkService 调用 ----------
|
||||
// 由 MultiApp 剥离首段 appmall 后与本文件的 api 组对齐 → /appmall/api/*
|
||||
//(与 /appmall/backend、/appmall/developer 同构,均为「应用名开头」约定)。
|
||||
// 调用方契约(勿单侧改动):
|
||||
// RemoteAppmallService → /appmall/api/addon/*、/appmall/api/index
|
||||
// AddonService → /appmall/api/addon/valid、/appmall/api/index/index
|
||||
// FrameworkService → /appmall/api/framework/version、/appmall/api/framework/download
|
||||
Route::group('api', function () {
|
||||
Route::get('addon/lists', [Market::class, 'lists']);
|
||||
Route::get('addon/info', [Market::class, 'info']);
|
||||
Route::post('addon/valid', [Market::class, 'valid']);
|
||||
Route::post('addon/submit', [Market::class, 'submit']);
|
||||
Route::post('addon/buy', [Market::class, 'buy']);
|
||||
Route::get('addon/orderStatus', [Market::class, 'orderStatus']);
|
||||
Route::post('addon/notify', [Market::class, 'notify']);
|
||||
Route::get('addon/payResult', [Market::class, 'payResult']);
|
||||
Route::post('addon/refund', [Market::class, 'refund']);
|
||||
Route::get('addon/my', [Market::class, 'my']);
|
||||
// 免费试用:商品配置 trial_days 后可用,一个用户对同一插件一次
|
||||
Route::post('addon/trial', [Market::class, 'trial']);
|
||||
// 插件评价:列表(公开已审核)+ 提交(待审)
|
||||
Route::get('addon/reviews', [Market::class, 'reviews']);
|
||||
Route::post('addon/review', [Market::class, 'review']);
|
||||
// 插件 zip 下载(RemoteAppmallService 走 /appmall/api/index,AddonService 走 /appmall/api/index/index)
|
||||
Route::get('index', [Market::class, 'index']);
|
||||
Route::get('index/index', [Market::class, 'index']);
|
||||
// 主框架升级分发(FrameworkService 走 /appmall/api/framework/*)
|
||||
Route::get('framework/version', [ApiFramework::class, 'version']);
|
||||
Route::get('framework/download', [ApiFramework::class, 'download']);
|
||||
});
|
||||
|
||||
// ---------- 前台展示(公开,仅中心站):主框架发布/下载页 + 应用商店 ----------
|
||||
// 前台控制器直接置于 app\appmall\controller 下(如 ForumFrontend 惯例),
|
||||
// 不套 frontend 子目录;URL 前缀为 /appmall/framework、/appmall/store。
|
||||
Route::get('framework', [FrontendFramework::class, 'index']);
|
||||
Route::get('framework/index', [FrontendFramework::class, 'index']);
|
||||
// 应用商店:上线插件列表(/appmall/store)
|
||||
Route::get('store', [Store::class, 'index'])->completeMatch();
|
||||
Route::get('store/index', [Store::class, 'index'])->completeMatch();
|
||||
// 裸访问 /appmall:本应用无 Index 控制器,落地到应用商店列表页,避免 404
|
||||
Route::get('/', [Store::class, 'index']);
|
||||
// 插件详情页(/appmall/store/detail/<name>)
|
||||
Route::get('store/detail/:name', [Store::class, 'detail'])
|
||||
->pattern(['name' => '[\w\-]+']);
|
||||
// 详情页购买 / 试用(买断、订阅续费、跨大版本升级均走 buy,服务端按授权状态判定金额)
|
||||
Route::post('store/buy', [Store::class, 'buy']);
|
||||
Route::post('store/trial', [Store::class, 'trial']);
|
||||
|
||||
Route::rule('store/:action', 'Store/:action', 'GET');
|
||||
Reference in new issue
Block a user