chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
# 插件机制对比: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. 关键差异与可借鉴点
|
||||
|
||||
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.php`(`ensureTables()` 运行时自愈建表)、`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_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` 有定义,但中心站若以插件方式独立维护则仍可能缺。)
|
||||
|
||||
2. **无站点 / 域名绑定(DZ 核心能力缺失)**
|
||||
`appmarket_addon_licenses.domain` 列存在但**从未写入**;`valid()` 只按 `uid` 校验 → 同一账号可在无限站点安装。DZ 是"授权绑定到具体站点域名"。
|
||||
|
||||
3. **无退款 / 吊销**
|
||||
无 `/api/addon/refund`;订单 `status` 仅 0/1(无 2 退款/3 关闭);license 无吊销;收益无冲正。DZ 支持退款回收授权。
|
||||
|
||||
4. **`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` 扩展退款/关闭态):
|
||||
|
||||
```sql
|
||||
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()` 改造:
|
||||
1. 免费 → 放行(仍写一条 `site_id` 绑定,便于统计);
|
||||
2. 付费 → 查 `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`;对应 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_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 等价能力)。
|
||||
Reference in New Issue
Block a user