Files
YwxAppThink/docs/插件机制对比_Discuz.md
T

27 KiB
Raw Blame History

插件机制对比: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.phpname/title/version/state/license/events/middleware/servicesstate=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.phpmock_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.xmlXML 声明式) info.phpPHP 数组,含 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. 关键差异与可借鉴点

  1. 钩子范式不同,但你的更现代 DZ 的"<hook> 预埋点 + 同名函数"是编译期/模板期静态织入,插件作者几乎零配置即可挂到任意页面位置;你的"Event 事件监听"是运行时触发,更解耦、更可控,但需要框架方在业务点主动埋 event()。建议:把常用钩子点清单固化进文档(已做约 30 个),并保持"埋点即文档"。

  2. 卸载清理:你的框架统一处理更省心,但刚踩了雷 DZ 卸载靠开发者 uninstall.php 自觉删表 + 系统拆菜单;你由框架 deleteMenu() 统一清菜单 + 插件 uninstall() 钩子清表,一致性更好。但 AdminPower/UserRule 启用软删除,普通 delete() 只软删→菜单残留(已修:destroy($ids,true) 物理删除)。DZ 用原生 SQL 物理删,你用模型易踩软删坑——已修复。

  3. 购买/授权闭环:主体已建,补齐三项即对齐 DZ(详见 §5) 订单表 + 支付回调 + license 签发 + 收益分成都已实现;真正缺的是:① 中心站 appmall/install.sql 漏建 appmarket_addon_orders/appmarket_addon_download_logs/appmarket_addon_licenses(客户端靠 ensureTables() 自愈,中心站不触发);② 授权只绑 uid 未绑站点域名(DZ 核心能力);③ 无退款/吊销。

  4. 云端一键安装 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.phpensureTables() 运行时自愈建表)、ywxapp/service/RemoteService.php(远程代理)、AddonOrder/AddonLicense/AddonDownloadLog 模型、config/pay.php(含 mock)。

5.1 真实缺口(对齐 DZ 前必须补)

  1. 中心站 appmall/install.sql 缺建表(最高优先级,急性 bug Market.php 跑在中心站,但其 install.sql 只建 appmarket_addon_list / appmarket_addon_submissions / appmarket_developers / appmarket_addon_revenues / appmarket_addon_withdrawalsappmarket_addon_orders / appmarket_addon_download_logs / appmarket_addon_licenses。 客户端靠 app/api/controller/v1/Addon.php::ensureTables() 在"首次访问客户端 Addon 控制器"时自愈建表,但中心站走 Market.php,该自愈不触发 → 全新部署的中心站调用 buy()/notify()/下载 会报「表不存在」。 appmarket_addon_licensespublic/install/install.sqldocs/appappmarket_schema.sql 有定义,但中心站若以插件方式独立维护则仍可能缺。)

  2. 无站点 / 域名绑定(DZ 核心能力缺失) appmarket_addon_licenses.domain 列存在但从未写入valid() 只按 uid 校验 → 同一账号可在无限站点安装。DZ 是"授权绑定到具体站点域名"。

  3. 无退款 / 吊销/api/addon/refund;订单 status 仅 0/1(无 2 退款/3 关闭);license 无吊销;收益无冲正。DZ 支持退款回收授权。

  4. docs/appappmarket_schema.sqlappmall/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()携带 domainsite_tokenMarket::valid() 改造:
    1. 免费 → 放行(仍写一条 site_id 绑定,便于统计);
    2. 付费 → 查 appmarket_addon_licensessite_id/domain 匹配且 status=1 且未过期。
  • completeOrder() 签发 license 时写入 site_id/domain(建议下单即采集 domain)。
  • 达到"一个授权码只在一个站点生效",与 DZ「站点↔授权绑定」一致。

5.4 落地 C:退款 / 吊销

  • 新增 POST /api/addon/refund {order_sn, rtoken}
    • 订单 status=2;对应 license status=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 实施顺序

  1. appmall/install.sql 三表 + Market.php::ensureTables()(止血,最高优先)。
  2. 统一 docs/appappmarket_schema.sql 为权威 8 表。
  3. 站点绑定(wxapp_appmarket_sites + valid/completeOrder 改造)。
  4. 退款 / 吊销端点。
  5. 前端:下单页 + 安装前域名校验接入 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_logswxapp_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/refundvalid()站点/域名绑定(首次安装绑定、之后仅同域名、已吊销拒绝);新增 refund()(订单置 2 + license 吊销 + 收益冲正,幂等)
addon/appappmarket/route/api.php C:注册 POST /api/addon/refund
app/api/controller/v1/Addon.php B/CensureTables() 防御补列 appmarket_addon_licenses.status/site_iddownload() 加授权吊销校验;新增 refund() 本地退款/吊销 + remoteRefund() 代理
ywxapp/service/RemoteService.php C:新增 refund() 代理中心站
ywxapp/service/AddonService.php BprepareInstallParams() 注入当前站点 domain,供中心站 valid() 做绑定判定

6.1 落地后行为

  • 购买闭环:新装中心站不再报「表不存在」(install.sql + ensureTables 双保险)。
  • 站点绑定(对齐 DZ:用户购买后首次安装某付费插件时,中心站把 appmarket_addon_licenses.domain 绑定到当前站点域名;之后换站点安装同一授权被拒。免费插件不受影响。
  • 退款POST /api/v1/addon/refund(本地或远程)将订单置 status=2、授权吊销(status=0expire_time=nowdownload/valid 双重拦截)、未结算收益扣回开发者余额,幂等。
  • 兼容旧库:客户端 ensureTables()ensureColumn 给已存在的 appmarket_addon_licensesstatus/site_id 列;服务端 ensureTables() 在表缺失时自愈,均不破坏旧结构。

6.2 远程下载严格校验(2026-07-24 落地)

  • 中心站 Market::index() 下载端点已加纵深防御:请求带 uid 时校验该用户已购买、授权未吊销、未过期、且绑定域名匹配(免费插件与不带 uid 的 sign 模式维持原白名单,向后兼容)。
  • 客户端 Addon.php::remoteDownloadRemoteService::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/myauthRemote 保护):列出指定 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.phpmarket()(服务端经 RemoteService::lists() 渲染,/backend/addon/market+ my()(列本机已装,/backend/addon/my);视图 app/backend/view/addon/{market,my}.htmlapp/backend/view/addon/index.html 已挂「应用市场 / 我的插件」按钮。market 插件因此只保留中心站运营职能menu.jsonstore 组已删除,仅剩 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": trueAddonService::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.phpRoute::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 等价能力)。