27 KiB
插件机制对比:Discuz! vs YwxApp
配套文档:《插件机制对比与差距分析》(对标 FastAdmin)、《插件开发指南》、《FastAdmin 插件机制参考》。 本文聚焦 Discuz! X(含应用中心/云平台) 与本项目 YwxApp 的插件全生命周期对比,并给出"购买/授权闭环"落地方案。 编写日期:2026-07-24。
0. 一句话结论
| Discuz! X | YwxApp(本项目) | |
|---|---|---|
| 插件本质 | /source/plugin/<identifier>/ 脚本集合,挂主程序运行 |
addon/<name>/ 是完整独立的 ThinkPHP 多应用(命名空间 addon\<name>,自带 route/config/event/middleware) |
| 扩展范式 | 模板/源码预埋 <hook> 点 + 同名函数类(AOP 静态织入) |
ThinkPHP Event 事件监听 + info.php['events'] 声明(运行时触发) |
| 市场/分发 | 官方强中心化云平台(应用中心),一键云端推送 | 自研 market/upgrade 服务端(提交/审核/下载/校验)+ 离线包 |
| 商业化 | 完整电商:购买→云端授权绑定站点→安装 | license 授权校验模型为主,下单/支付/退款闭环不明显 |
总体:你的路由/隔离/配置存储/升级自动化优于 Discuz!;但在钩子开箱即用性与商业化交易闭环上仍有差距。注意:购买/授权闭环其实已约 80% 实现(见 §5),剩下的是"中心站建表缺失 + 无站点绑定 + 无退款",属补齐而非从零。
1. Discuz! 插件全流程
1.1 开发:目录 + XML 声明式
/source/plugin/<identifier>/ ← 目录名=唯一标识
├── plugin.php / 各类脚本
├── <module>.class.php ← 页面嵌入脚本(普通版)
├── <module>.inc.php ← 程序链接 / 扩展项目
├── install.php / uninstall.php / upgrade.php / enable.php / disable.php
├── template/ table/ cron/ adv/ ...
- 元信息/结构全写在
plugin.xml(导出 XML):含version / title / intro / author / modules / pluginvars(变量) / installfile / uninstallfile / upgradefile / license等节点。 - 模块三类:程序链接(前台菜单挂接)、扩展项目(嵌入系统原有界面)、程序脚本(独立逻辑 / 页面嵌入)。
- 钩子(核心机制)= 页面嵌入点(AOP 静态嵌入):
- 系统源码/模板预埋
<hook>点;插件定义类plugin_<identifier>/plugin_<identifier>_<CURSCRIPT>,写同名方法即自动在该点输出返回值。 - 全局钩子(
common()、discuzcode()、deletethread()…)、脚本钩子(xxx_output())、自定义钩子(CURMODULE_USERDEFINE[_output]())。 - 这是 DZ 插件"不改动核心源码即扩展"的根本——模板/源码预埋点 + 同名函数,成熟稳定。
- 系统源码/模板预埋
- 所有脚本首行必须
if(!defined('IN_DISCUZ')) exit('Access Denied');
1.2 上架:官方云平台(应用中心)
- 开发者在应用中心开发者平台上传作品,设「免费/收费」并填价格。
- 经平台审核后进入市场列表(推荐/排行/搜索)。
- 站长的站点需绑定云平台账号(站点唯一标识),这是后续授权校验的基础。
1.3 购买(收费插件)
官方文档原话:"如果是收费插件,需要您购买后才可以进行安装。"
- 站长先在应用中心付费购买(钱给开发者)。
- 购买后云端把授权与站点绑定,安装时平台校验该站点是否拥有此插件的授权。
- 授权形态是平台侧云端记录 + 站点绑定(早期有授权码
md5(md5(domain).license)模式)。
1.4 安装 / 卸载 / 升级(一键云端)
- 安装:后台点「安装」→ 云端推送文件 + 自动部署(无 FTP/压缩包手工解压)→ 执行
install.php里的runquery()建表(表名写cdb_前缀占位)→ 输出$finish=TRUE。 - 启用:安装后仍需站长手动启用才生效。
- 卸载:
uninstall.php(用runquery()删自定义表);系统自动移除 plugin.xml 中声明的后台菜单模块。X3.1 起卸载不再支持页面跳转。 - 升级:
upgrade.php可用$fromversion/$toversion判断分支。 - 开关:X3.1+ 还有
enable.php/disable.php脚本钩子(runquery()可用)。
2. YwxApp 插件全流程(现状)
2.1 开发:MultiApp 独立应用
addon/<name>/
├── Addon.php # 基类钩子 install()/uninstall() 抽象 + enable/disable/upgrade 可重写
├── info.php # 元信息(PHP 数组)
├── menu.json # 三端菜单(后台/会员/前台)
├── install.sql # 建表,安装时自动导入
├── route/app.php # 站内路由(框架外包 <plugin> 前缀)
├── route/api.php # 对外 API(/api/*,经 app/api/route/route.php 加载)
├── config.php / controller/ / model/ / view/ / static/
- 元信息
info.php:name/title/version/state/license/events/middleware/services。state=1才被AppService::loadAddonRelevant()加载。 Addon.php基类(ywxapp/addon):install()/uninstall()抽象;enable()/disable()/upgrade()空实现可重写,由AddonService回调。- 钩子机制 = ThinkPHP Event 事件监听:在
info.php['events']声明 listener 类,boot 期loadEvent注册;业务点用event()触发(已埋约 30 个钩子点)。 - 菜单:
createMenu()读menu.json→ 写admin_power(后台)/user_rule(前台/会员),幂等。
2.2 上架 / 市场(自研服务端)
market插件部署在中心站(插件服务器),提供:市场列表 / 下载 / 授权校验 / 开发者提交与审核(POST /api/addon/submit+DEVELOPER_TOKEN)。upgrade插件部署在中心站,提供主框架在线升级(版本列表/核心包下载)。- 客户端通过
config ywxapp.api_url连接中心站。 - 表:
wxapp_appmarket_addon_list/wxapp_appmarket_addon_submissions/wxapp_appmarket_developers/wxapp_appmarket_addon_revenues/wxapp_appmarket_addon_withdrawals。
2.3 购买 / 授权
- 服务端
addon/appappmarket/controller/api/Market.php已实现:buy()下单 +notify()支付回调 +completeOrder()签发 license +valid()安装校验。 - 接
Yansongda\Pay(支付宝/微信),config/pay.php含mock_enable直购模式(自托管友好)。 - 客户端
app/api/controller/v1/Addon.php+ywxapp/service/RemoteService.php远程代理;AddonOrder/AddonLicense/AddonDownloadLog模型。
2.4 安装 / 卸载 / 升级
- 安装:
package/local(离线)+ 在线下载;install.sql自动导入;createMenu()建菜单;install()钩子可做额外初始化。 - 卸载:
AddonService::uninstall()→ 框架统一deleteMenu()清菜单(修复点:必须destroy($ids,true)物理删除,否则AdminPower/UserRule软删除残留)+ 插件uninstall()钩子清理自建表。 - 升级:
AddonService::onlineUpgrade()已实现(下载→备份→覆盖→upgrade()钩子→增量 SQL)。
3. 核心对比表
| 维度 | Discuz! | YwxApp(你) | 谁更强 |
|---|---|---|---|
| 插件本质 | /source/plugin/<id>/ 脚本集合,挂主程序 |
addon/<name>/ 完整独立 TP 多应用(自带 route/config/event) |
✅ 你:隔离更彻底 |
| 元信息 | plugin.xml(XML 声明式) |
info.php(PHP 数组,含 events/middleware/services) |
✅ 你:更灵活、可编程 |
| 扩展/钩子机制 | 模板/源码预埋 <hook> + 同名函数类(AOP 静态嵌入,极成熟) |
TP Event 事件监听 + info.php 声明(运行时触发,需主动埋点) |
🔶 各有所长:DZ 嵌入点开箱即用,你更现代但依赖埋点 |
| 前后台菜单 | plugin.xml 声明模块,系统自动建/拆 | menu.json + 框架 createMenu/deleteMenu |
✅ 对等(你区分三端更细) |
| 安装脚本 | install.php + runquery() 执行 SQL |
install.sql 自动导入 + install() 钩子 |
🔶 对等,你更"约定优于配置" |
| 卸载清理 | uninstall.php 开发者自选删表 + 系统拆菜单 |
框架统一 deleteMenu + uninstall() 钩子 |
✅ 你更一致(软删除坑已修:须物理删除) |
| 升级 | upgrade.php 按 $from/$to 分支 |
onlineUpgrade()(下载/备份/覆盖/钩子/增量 SQL) |
✅ 你:全程自动化更完整 |
| 上架/市场 | 官方强中心化云平台(审核+分发+绑定) | 自研 market/upgrade 服务端(提交/审核/下载/校验) |
🔶 你功能对等但生态弱(无官方流量/评级) |
| 购买/收费 | 应用中心完整电商:购买→云端授权绑定站点→安装 | buy/notify/completeOrder/valid/refund 已实现;中心站建表 + 站点绑定 + 退款 已于 2026-07-24 落地 |
✅ 已对齐(可在 §6 见落地记录) |
| 安装方式 | 一键云端推送(无 FTP/压缩包) | 离线包 + 在线下载覆盖 | 🔶 你支持离线(私有化友好),DZ 纯云端 |
| 授权绑定 | 站点↔云平台账号绑定(云端记录) | valid() 已支持站点/域名绑定(首次安装绑定、之后仅同域名),appmarket_addon_licenses.domain/site_id 落库 |
✅ 已对齐 DZ(见 §6) |
4. 关键差异与可借鉴点
-
钩子范式不同,但你的更现代 DZ 的"
<hook>预埋点 + 同名函数"是编译期/模板期静态织入,插件作者几乎零配置即可挂到任意页面位置;你的"Event 事件监听"是运行时触发,更解耦、更可控,但需要框架方在业务点主动埋event()。建议:把常用钩子点清单固化进文档(已做约 30 个),并保持"埋点即文档"。 -
卸载清理:你的框架统一处理更省心,但刚踩了雷 DZ 卸载靠开发者
uninstall.php自觉删表 + 系统拆菜单;你由框架deleteMenu()统一清菜单 + 插件uninstall()钩子清表,一致性更好。但AdminPower/UserRule启用软删除,普通delete()只软删→菜单残留(已修:destroy($ids,true)物理删除)。DZ 用原生 SQL 物理删,你用模型易踩软删坑——已修复。 -
购买/授权闭环:主体已建,补齐三项即对齐 DZ(详见 §5) 订单表 + 支付回调 + license 签发 + 收益分成都已实现;真正缺的是:① 中心站
appmall/install.sql漏建appmarket_addon_orders/appmarket_addon_download_logs/appmarket_addon_licenses(客户端靠ensureTables()自愈,中心站不触发);② 授权只绑uid未绑站点域名(DZ 核心能力);③ 无退款/吊销。 -
云端一键安装 vs 离线包 DZ 纯云端推送;你支持离线包(利于私有化/内网)。若要追平"一键装",可强化
onlineUpgrade的在线安装路径,并把market的下载接口与客户端安装流程打通。
5. 购买 / 授权闭环:现状盘点与落地方案
5.0 已实现(实测代码,非从零)
服务端入口 addon/appappmarket/controller/api/Market.php,接口契约:/api/addon/*(由 app/api/route/route.php 在 api 应用阶段 include 插件 route/api.php 加载):
| 方法 | 作用 | 状态 |
|---|---|---|
lists() |
市场列表(含 price) |
✅ |
info() |
详情 + purchased 标记 |
✅ |
submit() |
开发者提交(待审核) | ✅ |
buy() |
下单:写 appmarket_addon_orders(status=0);有商户号走 Yansongda\Pay 支付宝/微信(H5/web),无则 pay.mock_enable 直购 |
✅ |
notify() |
异步回调:验签→金额校验→completeOrder() |
✅ |
orderStatus() |
轮询订单状态 | ✅ |
payResult() |
同步落地页(return_url) | ✅ |
completeOrder() |
订单置已付 + 向 appmarket_addon_licenses 签发 license_key + 写 appmarket_addon_revenues(按 appmarket_commission_rate 抽成)+ 开发者 balance 累加 |
✅ |
valid() |
安装前授权校验:免费直过;付费查 appmarket_addon_licenses(uid+aid) 且未过期 |
✅(仅 uid 维度) |
index() |
下载 zip(支持签名校验) | ✅ |
客户端配套:app/api/controller/v1/Addon.php(ensureTables() 运行时自愈建表)、ywxapp/service/RemoteService.php(远程代理)、AddonOrder/AddonLicense/AddonDownloadLog 模型、config/pay.php(含 mock)。
5.1 真实缺口(对齐 DZ 前必须补)
-
中心站
appmall/install.sql缺建表(最高优先级,急性 bug)Market.php跑在中心站,但其install.sql只建appmarket_addon_list / appmarket_addon_submissions / appmarket_developers / appmarket_addon_revenues / appmarket_addon_withdrawals,缺appmarket_addon_orders/appmarket_addon_download_logs/appmarket_addon_licenses。 客户端靠app/api/controller/v1/Addon.php::ensureTables()在"首次访问客户端 Addon 控制器"时自愈建表,但中心站走Market.php,该自愈不触发 → 全新部署的中心站调用buy()/notify()/下载会报「表不存在」。 (appmarket_addon_licenses在public/install/install.sql与docs/appappmarket_schema.sql有定义,但中心站若以插件方式独立维护则仍可能缺。) -
无站点 / 域名绑定(DZ 核心能力缺失)
appmarket_addon_licenses.domain列存在但从未写入;valid()只按uid校验 → 同一账号可在无限站点安装。DZ 是"授权绑定到具体站点域名"。 -
无退款 / 吊销 无
/api/addon/refund;订单status仅 0/1(无 2 退款/3 关闭);license 无吊销;收益无冲正。DZ 支持退款回收授权。 -
docs/appappmarket_schema.sql与appmall/install.sql不一致 前者缺appmarket_addon_orders / appmarket_addon_download_logs / appmarket_addon_revenues / appmarket_addon_withdrawals / appmarket_addon_submissions / appmarket_developers;需统一为唯一权威 schema(供docs/split_install_sql.py校验)。
5.2 落地 A:补齐中心站建表(最优先,止血)
在 addon/appappmarket/install.sql 追加(字段与客户端 ensureTables() 对齐,status 扩展退款/关闭态):
CREATE TABLE IF NOT EXISTS `wxapp_appmarket_addon_orders` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`uid` int unsigned NOT NULL DEFAULT 0,
`aid` int unsigned NOT NULL DEFAULT 0 COMMENT 'appmarket_addon_list.id',
`amount` decimal(10,2) NOT NULL DEFAULT 0,
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0=待支付 1=已支付 2=已退款 3=已关闭',
`trade_no` varchar(64) NOT NULL DEFAULT '' COMMENT '商户订单号',
`pay_time` int unsigned DEFAULT 0,
`create_at` int unsigned DEFAULT 0,
`update_at` int unsigned DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_uid` (`uid`),
KEY `idx_trade` (`trade_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件购买订单表';
CREATE TABLE IF NOT EXISTS `wxapp_appmarket_addon_licenses` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`uid` int unsigned NOT NULL COMMENT '用户ID',
`aid` int unsigned NOT NULL COMMENT '对应 appmarket_addon_list.id',
`site_id` int unsigned NOT NULL DEFAULT 0 COMMENT '绑定站点(站点授权)',
`license_key` varchar(64) NOT NULL COMMENT '授权码',
`domain` varchar(255) DEFAULT NULL COMMENT '绑定域名(站点授权)',
`expire_time` int unsigned DEFAULT 0 COMMENT '0 为永久',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '1=有效 0=吊销',
`download_count` int DEFAULT 0 COMMENT '下载次数限制',
`create_at` int DEFAULT 0,
`update_at` int DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `license_key` (`license_key`),
UNIQUE KEY `uk_uid_aid_site` (`uid`,`aid`,`site_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件授权表';
CREATE TABLE IF NOT EXISTS `wxapp_appmarket_addon_download_logs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`license_id` int unsigned NOT NULL DEFAULT 0,
`uid` int unsigned NOT NULL DEFAULT 0,
`aid` int unsigned NOT NULL DEFAULT 0,
`ip` varchar(45) DEFAULT '',
`create_at` int unsigned DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_license` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件下载日志表';
并在 Market.php 加与客户端一致的 ensureTables() 防御(首调用自愈),避免旧中心站漏表。
同时将 docs/appappmarket_schema.sql 补全为包含全部 8 张表的唯一权威 schema。
5.3 落地 B:站点 / 域名授权绑定(对齐 DZ)
- 新增
wxapp_appmarket_sites(中心站注册的客户端站点):site_token/domain/owner_uid/status。 - 客户端安装
valid()时携带domain与site_token;Market::valid()改造:- 免费 → 放行(仍写一条
site_id绑定,便于统计); - 付费 → 查
appmarket_addon_licenses且site_id/domain匹配且status=1且未过期。
- 免费 → 放行(仍写一条
completeOrder()签发 license 时写入site_id/domain(建议下单即采集domain)。- 达到"一个授权码只在一个站点生效",与 DZ「站点↔授权绑定」一致。
5.4 落地 C:退款 / 吊销
- 新增
POST /api/addon/refund {order_sn, rtoken}:- 订单
status=2;对应 licensestatus=0(吊销); - 收益冲正:
appmarket_addon_revenues标记退款,开发者balance扣回income(或转frozen_balance待审); - 幂等:已退款直接返回成功。
- 订单
valid()对status=0的 license 返回"授权已吊销"。
5.5 与 DZ 对齐对照(补齐后)
| DZ 能力 | 你现状 | 补齐后 |
|---|---|---|
| 下单 | buy() ✅ |
✅ |
| 支付(支付宝/微信) | notify() + Yansongda ✅ |
✅ |
| 授权绑定站点域名 | ❌(仅 uid) | ✅(site_id/domain) |
| 安装前校验 | valid() ✅ |
✅(含域名) |
| 退款 | ❌ | ✅ |
| 开发者分成 | revenues+balance ✅ |
✅ |
| 一键云端安装 | 离线包 + 在线下载 | 在线下载 ✅ |
5.6 实施顺序
- 补
appmall/install.sql三表 +Market.php::ensureTables()(止血,最高优先)。 - 统一
docs/appappmarket_schema.sql为权威 8 表。 - 站点绑定(
wxapp_appmarket_sites+valid/completeOrder改造)。 - 退款 / 吊销端点。
- 前端:下单页 + 安装前域名校验接入
AddonService::install()。
完成后,你的商业化能力将与 DZ 应用中心对齐(购买→授权绑定站点→安装校验→退款吊销),同时保留离线/私有化优势。
6. 落地记录(2026-07-24,已实施)
上述 §5.2–§5.4 方案已综合落地,改动文件清单:
| 文件 | 改动 |
|---|---|
addon/appappmarket/install.sql |
A:补齐 wxapp_appmarket_addon_licenses(含 site_id/status)、wxapp_appmarket_addon_orders(含 status 0/1/2 退款态、site_id)、wxapp_appmarket_addon_download_logs、wxapp_appmarket_sites 四表 |
docs/appappmarket_schema.sql |
A:重写为权威 9 表 schema(与 install.sql、客户端 ensureTables 对齐),收益账本加 status=2 退款冲正 |
addon/appappmarket/controller/api/Market.php |
A/B/C:新增 ensureTables() 自愈并接入 buy/notify/valid/refund;valid() 加站点/域名绑定(首次安装绑定、之后仅同域名、已吊销拒绝);新增 refund()(订单置 2 + license 吊销 + 收益冲正,幂等) |
addon/appappmarket/route/api.php |
C:注册 POST /api/addon/refund |
app/api/controller/v1/Addon.php |
B/C:ensureTables() 防御补列 appmarket_addon_licenses.status/site_id;download() 加授权吊销校验;新增 refund() 本地退款/吊销 + remoteRefund() 代理 |
ywxapp/service/RemoteService.php |
C:新增 refund() 代理中心站 |
ywxapp/service/AddonService.php |
B:prepareInstallParams() 注入当前站点 domain,供中心站 valid() 做绑定判定 |
6.1 落地后行为
- 购买闭环:新装中心站不再报「表不存在」(install.sql + ensureTables 双保险)。
- 站点绑定(对齐 DZ):用户购买后首次安装某付费插件时,中心站把
appmarket_addon_licenses.domain绑定到当前站点域名;之后换站点安装同一授权被拒。免费插件不受影响。 - 退款:
POST /api/v1/addon/refund(本地或远程)将订单置status=2、授权吊销(status=0且expire_time=now,download/valid双重拦截)、未结算收益扣回开发者余额,幂等。 - 兼容旧库:客户端
ensureTables()用ensureColumn给已存在的appmarket_addon_licenses补status/site_id列;服务端ensureTables()在表缺失时自愈,均不破坏旧结构。
6.2 远程下载严格校验(2026-07-24 落地)
- 中心站
Market::index()下载端点已加纵深防御:请求带uid时校验该用户已购买、授权未吊销、未过期、且绑定域名匹配(免费插件与不带uid的 sign 模式维持原白名单,向后兼容)。 - 客户端
Addon.php::remoteDownload经RemoteService::downloadBinary($name,$version,$uid,$domain)透传当前用户与站点域名(注入$request->domain())。 - 注:客户端
remoteDownload原本已用中心站info()的purchased做购买校验,此次为中心站侧再加一道授权校验。
6.3 后台商店页退款按钮 + 安装前域名提示(2026-07-24 落地)
- 新增后台
addon::refund(name):按插件名退本地最新「已支付」订单,吊销授权 + 订单置status=2+ 收益冲正(与会员端/api/v1/addon/refund逻辑一致)。仅本地模式(appmarket_remote_enable=false,默认);远程模式订单在中心站、属会员 uid,后台无会员信息,返回提示「请于会员中心申请退款」。 public/static/backend/modules/appmall.js:已安装且付费插件卡片加「退款」按钮;付费插件安装前layer.confirm弹出域名授权提示(显示当前location.host)。- 会员端退款接口
/api/v1/addon/refund(上一轮落地)保留,供会员中心/H5 前端使用。
6.4 插件前台「我的插件」页 + 远程退款闭环(2026-07-24 落地)
- 新增中心站
Market::my()(GET /api/addon/my,authRemote保护):列出指定uid的已购付费插件(appmarket_addon_licenses 联 appmarket_addon_list + 最近订单),返回license_status/expire_time/domain/order_id/trade_no/refundable。路由addon/appappmarket/route/api.php注册addon/my。 ywxapp/service/RemoteService.php新增my($uid)代理/api/addon/my。- 客户端
app/api/controller/v1/Addon.php新增mine()(本地读本地库 / 远程代理remoteMine()),与refund()同一控制器,复用登录态。 - 应用商店 / 我的插件 = 核心后台「插件管理」(非 market 插件):用户明确——客户机不安装 market 插件,其所需的「应用商店(浏览中心站市场并一键安装)+ 我的插件(本机已装管理)」直接写在核心
app\backend\controller\addon.php:market()(服务端经RemoteService::lists()渲染,/backend/addon/market)+my()(列本机已装,/backend/addon/my);视图app/backend/view/addon/{market,my}.html;app/backend/view/addon/index.html已挂「应用市场 / 我的插件」按钮。market 插件因此只保留中心站运营职能(menu.json的store组已删除,仅剩market(应用市场管理)/revenue(收益与结算) 两个centerOnly组)。早期 store 曾误放controller/走公开前台/appmall/store、后又放 market 插件controller/backend/Store.php,现已统一迁入核心后台。 - 远程模式退款闭环:付费插件购买/退款仍由会员中心
/api/v1/addon/*(含mine/refund,本地/远程自动切换)承载;后台「我的插件」页(核心 addon::my)展示本机已装插件,完整升级/启停/卸载见「插件管理」列表。 - market 插件菜单挂载到「插件管理」:
menu.json顶层"superior": "addon:index"(核心「插件管理」菜单 id=8,见public/install/install.sql:158),使 market 插件后台菜单整体挂到「插件管理」下;仅market/revenue两个centerOnly组(应用市场管理 / 收益与结算)。改完menu.json须重导菜单:php think addon:manage appmall -a refresh-menu。 - 客户机隐藏中心运营菜单:
menu.json给应用市场管理(market) 与收益与结算(revenue) 两组加"centerOnly": true。AddonService::saveMenus()递归里新增过滤——当config('ywxapp.appmarket_remote_enable')=true(本机作为远程客户端连接中心站,即客户机)时,跳过centerOnly项及其子树;中心站(appmarket_remote_enable=false,默认) 正常显示全部。该过滤在createMenu()内,install/enable/refresh-menu 三入口共用,故重导菜单php think addon:manage appmall -a refresh-menu即可生效(客户机须先设appmarket_remote_enable=true)。另getOrCreateAddonRootMenuId()在客户机下把根菜单标题里的「(服务端)」字样剥离(插件应用市场(服务端)→插件应用市场),避免误导;角色切换后重导菜单即按新环境重建。(注:客户机不安装 market 插件,上述菜单/路由均不加载;拦截仅作「万一安装」兜底。) - 前台开发者中心同样仅中心站:独立前台应用
controller/developer/Developer.php(路由/appmall/developer/*,注册/令牌/收益/提现)依赖appmarket_developers/appmarket_addon_revenues/appmarket_addon_withdrawals运营表,客户机不持有,故双层拦截:① 路由层route/app.php的Route::group('developer')内if (Config::get('ywxapp.appmarket_remote_enable', false)) return;使客户机整组不注册(访问 404);② 控制器initialize()运行时兜底if (config('ywxapp.appmarket_remote_enable', false)) abort(404);(防路由缓存陈旧/直连)。客户机若开过路由缓存须php think route:clear。全项目无指向/appmall/developer的入口链接,不会出现死链。 - 服务中心对外 API 仅中心站(关键):
addon/appappmarket/route/api.php提供/api/addon/*(lists/valid/submit/info/buy/orderStatus/notify/payResult/refund/my)与/api/index(下载 zip),均操作中心站运营表,且/api/addon/notify接收支付平台回调写订单。但app/api/route/route.php对所有state=1插件的route/api.php无条件 include → 客户机也会注册这套 API,可能写出脏数据/越权下载。已在route/api.php顶部if ((bool) Config::get('ywxapp.appmarket_remote_enable', false)) return;整文件跳过注册(客户机访问 404)。路由缓存陈旧时须php think route:clear。注:Market控制器无统一基类、仅经路由可达,故路由层拦截即足够(无需控制器级兜底)。
6.5 待办(非阻断,可选)
- 可选的
site_token上报与wxapp_appmarket_sites落库逻辑(当前仅用domain字符串绑定,已满足 DZ 等价能力)。