chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# 目录结构
|
||||
|
||||
YwxApp 采用 ThinkPHP 多应用模式,目录职责划分清晰。
|
||||
|
||||
## 顶层目录
|
||||
|
||||
```
|
||||
ywxapp_dev/
|
||||
├── app/ 应用目录(多应用模式)
|
||||
├── addon/ 插件目录,每个子目录是一个独立插件
|
||||
├── config/ 全局配置
|
||||
├── docs/ 开发文档(本文档站的数据源)
|
||||
├── extend/ 扩展类库(PSR-0)
|
||||
├── public/ Web 根目录,入口文件与静态资源
|
||||
├── route/ 全局路由定义
|
||||
├── runtime/ 运行时缓存、日志、临时文件
|
||||
├── templates/ 模板皮肤包
|
||||
├── vendor/ Composer 依赖
|
||||
└── ywxapp/ 框架核心库(PSR-4 命名空间 ywxapp\)
|
||||
```
|
||||
|
||||
## app 应用目录
|
||||
|
||||
```
|
||||
app/
|
||||
├── api/ 对外 API 接口应用
|
||||
├── backend/ 后台管理应用
|
||||
├── member/ 会员中心应用
|
||||
├── frontend/ 前台应用
|
||||
├── site/ 展示官网 / 文档站应用
|
||||
├── common.php 全局公共函数
|
||||
├── event.php 全局事件定义与监听器注册
|
||||
├── middleware.php 全局中间件
|
||||
├── provider.php 容器绑定
|
||||
└── service.php 服务注册(含 AppService)
|
||||
```
|
||||
|
||||
> **注意**:多应用模式下 `base_path()` 返回的是 `app/` 目录而非项目根目录。
|
||||
> 若需要扫描 `addon/`、`app/` 等顶层目录,必须使用 `root_path()`。
|
||||
|
||||
## addon 插件目录
|
||||
|
||||
单个插件的标准结构:
|
||||
|
||||
```
|
||||
addon/<插件名>/
|
||||
├── info.php 插件清单:name/title/intro/version/state
|
||||
├── Addon.php 插件主类,继承 ywxapp\addon
|
||||
├── config.php 插件配置项定义
|
||||
├── menu.json 后台菜单定义
|
||||
├── install.sql 安装 SQL
|
||||
├── controller/
|
||||
│ ├── backend/ 后台控制器(继承 AddonBackend)
|
||||
│ ├── api/ 接口控制器
|
||||
│ └── frontend/ 前台控制器
|
||||
├── model/ 数据模型
|
||||
├── service/ 业务服务
|
||||
├── subscribe/ 事件订阅器(钩子实现)
|
||||
├── route/app.php 插件路由(写相对组名,框架自动补前缀)
|
||||
├── view/ 视图模板
|
||||
└── docs/ 插件文档
|
||||
```
|
||||
|
||||
## ywxapp 核心库
|
||||
|
||||
```
|
||||
ywxapp/
|
||||
├── command/ 自定义命令行工具
|
||||
├── controller/ 控制器基类(Frontend / Backend / AddonBackend / Api)
|
||||
├── library/ 工具类库(SchemaGuard / SkinOverlay / Markdown 等)
|
||||
├── middleware/ 框架中间件(MultiApp / AddonPerformance 等)
|
||||
├── service/ 核心服务(AppService / AddonService)
|
||||
├── addon.php 插件基类
|
||||
└── helper.php 全局辅助函数
|
||||
```
|
||||
|
||||
## 路由约定
|
||||
|
||||
由于 `MultiApp` 中间件会将 URL 首段解析为应用名,因此:
|
||||
|
||||
- 应用级路由文件 `app/<app>/route/route.php` **不会自动加载**
|
||||
- 全局路由统一写在 `route/app.php`,并携带应用名前缀
|
||||
|
||||
```php
|
||||
// route/app.php
|
||||
Route::get('site/doc', 'site/doc/index');
|
||||
```
|
||||
|
||||
插件路由则写在 `addon/<插件名>/route/app.php`,使用相对组名,由
|
||||
`AppService::loadAddonRoutes()` 自动包裹 `Route::group('<插件名>')`。
|
||||
@@ -0,0 +1,44 @@
|
||||
# 框架介绍
|
||||
|
||||
YwxApp 是一套基于 **ThinkPHP 8** 构建的多应用业务框架,核心目标是让「业务代码」与「扩展能力」彻底解耦。
|
||||
|
||||
## 设计理念
|
||||
|
||||
传统 PHP 项目做二次开发时,往往需要直接改动核心代码,导致后续升级困难。YwxApp 通过**事件驱动的插件机制**解决这个问题:
|
||||
|
||||
- 核心框架只负责触发事件(钩子)
|
||||
- 插件通过订阅器监听事件并注入自己的逻辑
|
||||
- 卸载插件后核心代码完全不受影响
|
||||
|
||||
## 核心特性
|
||||
|
||||
| 特性 | 说明 |
|
||||
| --- | --- |
|
||||
| 插件机制 | 基于事件订阅器的热插拔插件,钩子自动注册 |
|
||||
| 多应用架构 | api / backend / member / frontend / site 应用隔离 |
|
||||
| 插件市场 | 中心站分发、客户机在线安装与升级 |
|
||||
| 会员与支付 | 免签支付 + 会员开通事件链,扫码到账自动开通 |
|
||||
| 模板皮肤 | 运行时视图覆盖,插件视图与皮肤合并 |
|
||||
| 命令行工具 | addon:make / addon:manage / addon:health 等脚手架 |
|
||||
|
||||
## 技术栈
|
||||
|
||||
- PHP >= 8.0
|
||||
- ThinkPHP 8.x + think-orm 4.x
|
||||
- think-multi-app 多应用扩展
|
||||
- MySQL 5.7+ / MariaDB
|
||||
- 前端:Layui + 原生模板引擎
|
||||
|
||||
## 与同类框架对比
|
||||
|
||||
相比 FastAdmin 的钩子机制,YwxApp 的差异在于:
|
||||
|
||||
1. **基于原生事件系统**:不需要额外的 hook 表,订阅器由 `AppService` 在启动阶段自动扫描注册
|
||||
2. **插件自带路由**:插件可在 `route/app.php` 中注册自己的路由,由框架自动加入分组
|
||||
3. **数据库自愈**:通过 `SchemaGuard` 统一管理表结构,插件升级时自动补齐缺失的列
|
||||
|
||||
## 下一步
|
||||
|
||||
- 阅读 [目录结构](guide/directory) 了解项目组织方式
|
||||
- 阅读 [快速开始](guide/quickstart) 搭建本地开发环境
|
||||
- 阅读 [插件开发指南](addon/development) 编写第一个插件
|
||||
@@ -0,0 +1,93 @@
|
||||
# 模型自愈机制(Schema Self-Heal)
|
||||
|
||||
YwxApp 的数据库结构自愈引擎收敛在 `ywxapp/model/BaseModel.php` 的静态方法中。
|
||||
它的定位是 **「缺表 / 缺列兜底」**,不是迁移工具——旧表改名、跨前缀重整等问题仍需手动执行迁移 SQL(见 `docs/migrations/`)。
|
||||
|
||||
## 一、自愈引擎能力(BaseModel 静态方法)
|
||||
|
||||
| 方法 | 作用 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `ensureTable($table, $sql)` | 表不存在则 CREATE | `$table` 为已带前缀的完整表名;内部先 `SHOW TABLES LIKE` 探测 |
|
||||
| `ensureTableFromInstall($prefix, $table)` | 从 `public/install/install.sql` 正则提取建表 DDL 执行 | **install.sql 是唯一事实源**;`$table` 为不含前缀的表名 |
|
||||
| `ensureColumn($table, $col, $def)` | 列不存在则 ALTER ADD | MySQL 不支持 `ADD COLUMN IF NOT EXISTS`,故先探测 |
|
||||
| `ensureAutoIncrementPk($table, $col='id')` | 修复 id 非自增主键 | 老库 `id` 定义成 NOT NULL 但无 AUTO_INCREMENT 时,create() 会报 1364 |
|
||||
| `tableExists($table)` | 探测表是否存在 | `SHOW TABLES LIKE`,异常时返回 false |
|
||||
| `currentPrefix()` | 取运行时表前缀 | 优先 `config/database.php`,回退 `Db::getConfig('prefix')` |
|
||||
|
||||
所有方法内部都包了 `try/catch`:自愈失败(如数据库账号无 `CREATE/ALTER` 权限、`NO_BACKSLASH_ESCAPES` 导致 SQL 出错)会**被静默吞掉并记录日志**,不会阻断业务——但意味着「自愈没起作用」时不会立刻报错,需要查日志才能发现。
|
||||
|
||||
## 二、模型壳约定
|
||||
|
||||
每个需要自愈的模型都自带一个静态方法:
|
||||
|
||||
```php
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
try {
|
||||
$prefix = \think\facade\Db::getConfig('prefix') ?: '';
|
||||
$table = $prefix . 'notice';
|
||||
// 缺表建表(优先 ensureTableFromInstall,或从 install.sql 同款 DDL 兜底)
|
||||
self::ensureTable($table, "CREATE TABLE IF NOT EXISTS `{$prefix}notice` (...)");
|
||||
// 老库扩展列兜底(幂等)
|
||||
self::ensureColumn($table, 'type', "tinyint(1) NOT NULL DEFAULT 1");
|
||||
} catch (\Throwable $e) {
|
||||
// 忽略自愈失败,由业务暴露
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**幂等性**:所有自愈都先探测(SHOW TABLES / SHOW COLUMNS)再操作,已存在则跳过,重复调用安全。
|
||||
|
||||
## 三、触发时机(谁在什么时候调用 ensureSchema)
|
||||
|
||||
自愈**不会定时 / 不会自动**,全部是「业务请求时按需触发」。
|
||||
|
||||
### 1. 核心库(每次相关请求都跑一次,开销极小)
|
||||
|
||||
| 入口 | 调用 | 触发场景 |
|
||||
| --- | --- | --- |
|
||||
| `ywxapp/library/AdminAuth.php` (`login`/`register`) | `BackendAdmin::ensureSchema()` | 后台登录 / 注册 |
|
||||
| `ywxapp/controller/MemberBase.php` (`_initialize`) | `MemberUser::ensureSchema()` | 访问任意会员中心控制器 |
|
||||
|
||||
### 2. 业务方法内触发
|
||||
|
||||
| 模型 / 类 | 触发方法 | 触发场景 |
|
||||
| --- | --- | --- |
|
||||
| `ywxapp/model/Notice.php` | `getActiveTopNotices()` / `getList()` | 前台公告条渲染、公告列表页 |
|
||||
| `ywxapp/model/Ad.php` | `getByPosition()` / `getSlots()` | 广告位渲染 |
|
||||
|
||||
### 3. 中间件 / 蜘蛛统计
|
||||
|
||||
| 入口 | 调用 | 触发场景 |
|
||||
| --- | --- | --- |
|
||||
| `SpiderStat` 中间件 | `ensureTables()` | 每次请求(蜘蛛日志 / 统计表) |
|
||||
|
||||
### 4. 插件安装 / 升级
|
||||
|
||||
| 插件 | 触发入口 | 触发场景 |
|
||||
| --- | --- | --- |
|
||||
| blog | `BlogSchema::ensure*` | 插件业务方法 |
|
||||
| forum | `ForumSchema::ensure*` | 插件业务方法 |
|
||||
| appmall(中心站) | `AppmallSchema::ensure*` | 开发者中心业务方法 |
|
||||
| haonav / seo | `Addon.php::upgrade()` / `ensureTablesFromInstallSql()` | 插件升级 / 安装 |
|
||||
| wxchat / mqttbroker / mqpay 等 | 各自 Schema 类 | 插件初始化 |
|
||||
|
||||
## 四、线上老库迁移不在自愈范围
|
||||
|
||||
自愈只负责「缺表 / 缺列兜底」,**不会做 RENAME、不会跨前缀重整**。旧库改名必须手动执行迁移 SQL:
|
||||
|
||||
- `docs/migrations/migrate_admin_user_to_backend_member_wxapp.sql` — admin/user → backend/member
|
||||
- `docs/migrations/rename_table_prefix.sql` — 批量改前缀
|
||||
- `docs/migrations/fix_missing_wxapp_notice.sql` — 自愈因权限被吞时手动补表
|
||||
|
||||
## 五、排错:遇到 1146 Table doesn't exist
|
||||
|
||||
1. 确认表是否在 `install.sql` 中有建表语句(事实源)。
|
||||
2. 若是核心 / 插件表,确认对应 `ensureSchema()` 是否被调用(见第三节入口)。
|
||||
3. ⚠️ **高频根因:运行时没取到表前缀**。`ensureSchema()` 若用 `Db::getConfig('prefix')` 取前缀,在部分线上环境(多应用路由 / CLI / Db 配置未就绪)会返回空字符串,导致自愈建出**无前缀的表**(如 `notice`),而模型 `name` 拼前缀后查的是 `wxapp_notice` → 1146。
|
||||
- **修复**:所有模型壳 / 控制器一律改用 `BaseModel::currentPrefix()` 取前缀(优先 `config/database.php`,回退 `Db::getConfig`),并已统一修复 `Notice` / `Ad` 及 `appmall` / `appmall` 控制器。
|
||||
- 自查:`grep "Db::getConfig('prefix')"` 应只剩 0 处(全部换成 `currentPrefix`)。
|
||||
4. 若前缀正确、`ensureSchema()` 已调用仍 1146 → **多半是线上 DB 账号无 CREATE/ALTER 权限**,自愈被静默吞掉。
|
||||
- 根治:给 DB 用户授予建表 / 改表权限,自愈会自动补齐;
|
||||
- 兜底:手动执行 `docs/migrations/` 下对应修复 SQL(如 `fix_missing_wxapp_notice.sql`)。
|
||||
5. 若是改名类(admin_* / user_*)→ 必须手动跑迁移 SQL,自愈无法处理。
|
||||
@@ -0,0 +1,114 @@
|
||||
# 快速开始
|
||||
|
||||
本文介绍如何在本地搭建 YwxApp 开发环境并运行第一个页面。
|
||||
|
||||
## 环境要求
|
||||
|
||||
| 项目 | 要求 |
|
||||
| --- | --- |
|
||||
| PHP | >= 8.0(推荐 8.3) |
|
||||
| 数据库 | MySQL 5.7+ / MariaDB 10.3+ |
|
||||
| Composer | 2.x |
|
||||
| 扩展 | pdo_mysql、mbstring、openssl、fileinfo、zip |
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 1. 获取代码并安装依赖
|
||||
|
||||
```bash
|
||||
git clone <仓库地址> ywxapp
|
||||
cd ywxapp
|
||||
composer install
|
||||
```
|
||||
|
||||
### 2. 配置环境变量
|
||||
|
||||
复制示例配置并按实际情况修改:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
关键配置项:
|
||||
|
||||
```ini
|
||||
APP_DEBUG = true
|
||||
|
||||
[DATABASE]
|
||||
TYPE = mysql
|
||||
HOSTNAME = 127.0.0.1
|
||||
DATABASE = ywxapp
|
||||
USERNAME = root
|
||||
PASSWORD =
|
||||
HOSTPORT = 3306
|
||||
PREFIX = wxapp_
|
||||
```
|
||||
|
||||
### 3. 导入数据库
|
||||
|
||||
将 `install.sql` 导入到你创建的数据库中,或访问安装向导按提示完成。
|
||||
|
||||
### 4. 配置 Web 服务器
|
||||
|
||||
将站点根目录指向 `public/`,并配置伪静态。
|
||||
|
||||
**Nginx**:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
if (!-e $request_filename) {
|
||||
rewrite ^(.*)$ /index.php?s=$1 last;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Apache**:`public/.htaccess` 已内置规则,确保开启 `mod_rewrite`。
|
||||
|
||||
### 5. 本地快速启动
|
||||
|
||||
若暂时不配置 Web 服务器,可用 PHP 内置服务器:
|
||||
|
||||
```bash
|
||||
php think run -p 8000
|
||||
```
|
||||
|
||||
访问 `http://127.0.0.1:8000` 即可看到展示官网首页。
|
||||
|
||||
## 目录权限
|
||||
|
||||
以下目录需要可写权限:
|
||||
|
||||
```
|
||||
runtime/
|
||||
public/uploads/
|
||||
addon/
|
||||
config/
|
||||
```
|
||||
|
||||
Linux 下执行:
|
||||
|
||||
```bash
|
||||
chmod -R 755 runtime public/uploads addon config
|
||||
```
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 创建插件脚手架
|
||||
php think addon:make demo -a build
|
||||
|
||||
# 安装 / 开发模式安装插件
|
||||
php think addon:manage demo -a develop
|
||||
|
||||
# 刷新插件后台菜单
|
||||
php think addon:manage demo -a refresh-menu
|
||||
|
||||
# 插件健康体检
|
||||
php think addon:health --detail
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [插件开发指南](addon/development):编写你的第一个插件
|
||||
- [支付钩子机制](pay/hooks):了解事件驱动的扩展方式
|
||||
- [自定义命令清单](cli/commands):查看全部 CLI 工具
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 友情链接管理完善:软删除字段 + 后台权限种子
|
||||
-- 适用:已安装(数据来自 abc.sql / 线上库)需要手动执行以下语句
|
||||
|
||||
-- 1) 给 wxapp_links 增加软删除时间字段(启用回收站功能)
|
||||
ALTER TABLE `wxapp_links`
|
||||
ADD COLUMN `delete_at` int DEFAULT NULL COMMENT '软删除时间' AFTER `update_at`;
|
||||
|
||||
-- 2) 补充 links 子权限(父级为 links:index,id=61)
|
||||
-- 注意:若你们的 user_rule 已存在同名 name,请先确认 id 不冲突再执行
|
||||
INSERT INTO `wxapp_user_rule`
|
||||
(`id`, `pid`, `title`, `name`, `type`, `module`, `route`, `icon`, `sort`, `status`, `create_at`, `update_at`, `delete_at`)
|
||||
VALUES
|
||||
(621, 61, '添加友链', 'links:add', 3, 'links/save', 'layui-icon ', 0, NULL, 1, 0, 0, 0),
|
||||
(622, 61, '编辑友链', 'links:edit', 3, 'links/edit', 'layui-icon ', 0, NULL, 1, 0, 0, 0),
|
||||
(623, 61, '删除友链', 'links:delete', 3, 'links/delete', 'layui-icon ', 0, NULL, 1, 0, 0, 0),
|
||||
(624, 61, '友链回收站', 'links:recyclebin', 3, 'links/recyclebin', 'layui-icon ', 0, NULL, 1, 0, 0, 0),
|
||||
(625, 61, '恢复友链', 'links:restore', 3, 'links/restore', 'layui-icon ', 0, NULL, 1, 0, 0, 0);
|
||||
@@ -0,0 +1,75 @@
|
||||
-- ============================================================
|
||||
-- 远程库时间字段规范化迁移 SQL
|
||||
-- 作用:把曾由错误代码(createTime='create_time')写入的
|
||||
-- 旧列 create_time / update_time / delete_time
|
||||
-- 重命名为规范列 create_at / update_at / delete_at,
|
||||
-- 并修正列类型/默认值/注释,与 install.sql 保持一致。
|
||||
-- 覆盖表(前缀示例 wxapp_,执行前全部替换为线上真实前缀):
|
||||
-- card, help, medal, member_medal, prop, score_log,
|
||||
-- shop, sms, task, member_prop, member_task, notice
|
||||
-- 说明:
|
||||
-- 1) 旧列若不存在(线上已是新列名),自动跳过,不报错。
|
||||
-- 2) 新列若已存在(说明之前已改过),CHANGE 会失败——故先 DROP 旧列再 ADD 新列,
|
||||
-- 但为安全采用「先探测旧列是否存在」的方式。
|
||||
-- 3) 本脚本幂等:重复执行安全(旧列不存在时整段跳过)。
|
||||
-- ============================================================
|
||||
|
||||
DELIMITER //
|
||||
|
||||
-- 通用迁移存储过程:仅当旧列存在且新列不存在时才改
|
||||
DROP PROCEDURE IF EXISTS `__migrate_time_col`//
|
||||
CREATE PROCEDURE `__migrate_time_col`(
|
||||
IN p_table VARCHAR(64),
|
||||
IN p_old VARCHAR(64),
|
||||
IN p_new VARCHAR(64),
|
||||
IN p_def VARCHAR(255)
|
||||
)
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = p_table
|
||||
AND COLUMN_NAME = p_old
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = p_table
|
||||
AND COLUMN_NAME = p_new
|
||||
) THEN
|
||||
SET @sql = CONCAT('ALTER TABLE `', p_table, '` CHANGE `', p_old, '` `', p_new, '` ', p_def);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END IF;
|
||||
END//
|
||||
|
||||
DROP PROCEDURE IF EXISTS `__migrate_table`//
|
||||
CREATE PROCEDURE `__migrate_table`(IN p_table VARCHAR(64))
|
||||
BEGIN
|
||||
-- 三列全改(按各自 install.sql 定义)
|
||||
CALL __migrate_time_col(p_table, 'create_time', 'create_at', "int(11) NOT NULL DEFAULT 0 COMMENT '创建时间'");
|
||||
CALL __migrate_time_col(p_table, 'update_time', 'update_at', "int(11) NOT NULL DEFAULT 0 COMMENT '更新时间'");
|
||||
CALL __migrate_time_col(p_table, 'delete_time', 'delete_at', "int(11) NOT NULL DEFAULT 0 COMMENT '删除时间'");
|
||||
END//
|
||||
|
||||
DELIMITER ;
|
||||
|
||||
-- 逐表迁移(member_medal / member_prop / member_task 只有 create_at,无 update/delete)
|
||||
CALL __migrate_table('wxapp_card');
|
||||
CALL __migrate_table('wxapp_help');
|
||||
CALL __migrate_table('wxapp_medal');
|
||||
CALL __migrate_table('wxapp_prop');
|
||||
CALL __migrate_table('wxapp_shop');
|
||||
CALL __migrate_table('wxapp_sms');
|
||||
CALL __migrate_table('wxapp_task');
|
||||
CALL __migrate_table('wxapp_notice');
|
||||
CALL __migrate_table('wxapp_score_log');
|
||||
|
||||
-- 仅含 create_at 的表
|
||||
CALL __migrate_time_col('wxapp_member_medal', 'create_time', 'create_at', "int(11) NOT NULL DEFAULT 0 COMMENT '获得时间'");
|
||||
CALL __migrate_time_col('wxapp_member_prop', 'create_time', 'create_at', "int(11) NOT NULL DEFAULT '0' COMMENT '获得时间'");
|
||||
CALL __migrate_time_col('wxapp_member_task', 'create_time', 'create_at', "int(11) NOT NULL DEFAULT '0' COMMENT '领取时间'");
|
||||
|
||||
-- 清理临时存储过程
|
||||
DROP PROCEDURE IF EXISTS `__migrate_time_col`;
|
||||
DROP PROCEDURE IF EXISTS `__migrate_table`;
|
||||
@@ -0,0 +1,161 @@
|
||||
-- ============================================================
|
||||
-- 营运模块缺失表建表 SQL(远程库手动执行)
|
||||
-- 前缀示例 wxapp_,执行前全部替换为线上真实前缀。
|
||||
-- 安全:使用 CREATE TABLE IF NOT EXISTS,重复执行不报错。
|
||||
-- 来源:public/install/install.sql 的规范定义。
|
||||
-- 说明:sms 表 COLLATE 已统一为 utf8mb4 以兼容 MySQL 5.7+。
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_card` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uid` int(11) NOT NULL DEFAULT 0 COMMENT '兑换会员ID(0未兑换)',
|
||||
`batch_no` varchar(30) NOT NULL DEFAULT '' COMMENT '批量生成批次号',
|
||||
`cardno` varchar(100) NOT NULL DEFAULT '' COMMENT '卡号',
|
||||
`password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '面值',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态 0未售1已售2已用',
|
||||
`use_time` int(11) NOT NULL DEFAULT 0 COMMENT '使用时间',
|
||||
`sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||
`delete_at` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_cardno` (`cardno`),
|
||||
KEY `idx_batch_no` (`batch_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='充值卡密';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_prop` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '道具名称',
|
||||
`icon` varchar(255) NOT NULL DEFAULT '' COMMENT '图标',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '价格',
|
||||
`description` text COMMENT '描述',
|
||||
`sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态',
|
||||
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||
`delete_at` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='道具中心';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_shop` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '商品名称',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '价格',
|
||||
`stock` int(11) NOT NULL DEFAULT 0 COMMENT '库存',
|
||||
`description` text COMMENT '描述',
|
||||
`sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态',
|
||||
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||
`delete_at` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='电子商务';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_sms` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`mobile` varchar(20) NOT NULL COMMENT '手机号',
|
||||
`code` varchar(10) NOT NULL COMMENT '验证码',
|
||||
`event` varchar(20) DEFAULT 'login' COMMENT '类型 login:登录 register:注册',
|
||||
`times` int DEFAULT '0' COMMENT '验证次数',
|
||||
`ip` varchar(50) DEFAULT NULL COMMENT '发送IP',
|
||||
`create_at` int DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_mobile` (`mobile`),
|
||||
KEY `idx_times` (`times`),
|
||||
KEY `idx_create_at` (`create_at`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短信日志表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_task` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '任务标题',
|
||||
`description` text COMMENT '任务描述',
|
||||
`reward_type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '奖励类型',
|
||||
`reward_num` int(11) NOT NULL DEFAULT 0 COMMENT '奖励数量/道具ID',
|
||||
`prop_num` int(11) NOT NULL DEFAULT 1 COMMENT '道具数量(奖励类型为道具时生效)',
|
||||
`sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态',
|
||||
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||
`delete_at` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站点任务';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_member_prop` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uid` int unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
|
||||
`prop_id` int unsigned NOT NULL DEFAULT '0' COMMENT '道具ID',
|
||||
`num` int unsigned NOT NULL DEFAULT '1' COMMENT '数量',
|
||||
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '获得时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_uid` (`uid`),
|
||||
KEY `idx_uid_prop` (`uid`,`prop_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户拥有道具';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_member_task` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uid` int unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
|
||||
`task_id` int unsigned NOT NULL DEFAULT '0' COMMENT '任务ID',
|
||||
`reward_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '实际发放奖励类型',
|
||||
`reward_num` int(11) NOT NULL DEFAULT '0' COMMENT '实际发放数量',
|
||||
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '领取时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_uid_task` (`uid`,`task_id`),
|
||||
KEY `idx_uid` (`uid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户任务领取记录';
|
||||
|
||||
-- 以下两张已在原自愈覆盖(medal / score_log / score_rule),
|
||||
-- 一并补上保证完整,IF NOT EXISTS 不会冲突。
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_medal` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '勋章名称',
|
||||
`image` varchar(255) NOT NULL DEFAULT '' COMMENT '图片',
|
||||
`description` text COMMENT '描述',
|
||||
`sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态',
|
||||
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||
`delete_at` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='勋章中心';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_member_medal` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uid` int unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
|
||||
`medal_id` int unsigned NOT NULL DEFAULT '0' COMMENT '勋章ID',
|
||||
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '获得时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_uid_medal` (`uid`,`medal_id`),
|
||||
KEY `idx_uid` (`uid`),
|
||||
KEY `idx_medal` (`medal_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户拥有勋章';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_score_log` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '用户ID',
|
||||
`rule_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '关联规则ID',
|
||||
`type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1收入2支出',
|
||||
`value` int(11) NOT NULL DEFAULT 0 COMMENT '变动值',
|
||||
`balance` int(11) NOT NULL DEFAULT 0 COMMENT '变动后余额',
|
||||
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
|
||||
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_uid` (`uid`),
|
||||
KEY `idx_uid_create` (`uid`,`create_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分流水';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_score_rule` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '规则名称',
|
||||
`action` varchar(50) NOT NULL DEFAULT '' COMMENT '触发动作标识(task_complete/signin/register等)',
|
||||
`type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1获取2消费',
|
||||
`value` int(11) NOT NULL DEFAULT 0 COMMENT '变动值',
|
||||
`status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态 0禁用1启用',
|
||||
`sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||
`delete_at` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_action` (`action`),
|
||||
KEY `idx_status_sort` (`status`,`sort`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分规则';
|
||||
@@ -0,0 +1,32 @@
|
||||
-- ============================================================
|
||||
-- 修复:wxapp_notice 表缺失(线上报 1146)
|
||||
-- ============================================================
|
||||
-- 背景:
|
||||
-- ywxapp/model/Notice.php 的 ensureSchema() 会尝试自建 wxapp_notice,
|
||||
-- 但若线上数据库账号无 CREATE TABLE 权限,建表语句会被 try/catch 静默吞掉,
|
||||
-- 后续 SELECT 即报 1146 Table 'ywxapp_www.wxapp_notice' doesn't exist。
|
||||
-- 用法:
|
||||
-- ① 先确认前缀:SELECT @@global.version 不重要,看报错里的表名前缀即可(此处为 wxapp_)。
|
||||
-- ② 把下面的 wxapp_ 替换成你线上实际前缀(若不同),然后在线上库执行本文件。
|
||||
-- ③ 优先方案是给线上 DB 账号授予 CREATE/ALTER 权限,自愈会自动补齐,无需手动。
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_notice` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '公告标题',
|
||||
`content` text COMMENT '公告内容',
|
||||
`author` varchar(50) NOT NULL DEFAULT '' COMMENT '发布人',
|
||||
`type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '类型 1系统维护 2活动公告 3版本更新 4其他',
|
||||
`is_top` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否置顶 0否 1是',
|
||||
`start_time` int(11) NOT NULL DEFAULT 0 COMMENT '展示开始时间 0=长期',
|
||||
`end_time` int(11) NOT NULL DEFAULT 0 COMMENT '展示结束时间 0=长期',
|
||||
`view_count` int(11) NOT NULL DEFAULT 0 COMMENT '浏览量',
|
||||
`sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态 0禁用 1启用',
|
||||
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||
`delete_at` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_type` (`type`),
|
||||
KEY `idx_is_top` (`is_top`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站点公告';
|
||||
@@ -0,0 +1,65 @@
|
||||
-- ============================================================
|
||||
-- 远程服务器修复整合 SQL
|
||||
-- 适用:ywxapp 线上库(前缀示例 wxapp_,请按实际替换)
|
||||
-- 背景:
|
||||
-- 1) 自愈引擎因线上 Db::getConfig('prefix') 取空,把 wxapp_notice 建成了无前缀 notice 表,
|
||||
-- 导致 SELECT wxapp_notice 报 1146。已修复代码改用 BaseModel::currentPrefix(),
|
||||
-- 但线上已误建的表需要手动清理并重建。
|
||||
-- 2) install.sql 种子未含后台「营运」菜单,需补充插入。
|
||||
-- 用法:
|
||||
-- ① 把本文件所有 `wxapp_` 替换成你线上实际表前缀(如保持不变则直接执行)。
|
||||
-- ② 一次性执行全部语句(含清理旧表 + 建正确表 + 插菜单)。
|
||||
-- ============================================================
|
||||
|
||||
-- ---------- 1. 修复 wxapp_notice 表 ----------
|
||||
-- 1.1 清理自愈误建的无前缀表(若存在)
|
||||
DROP TABLE IF EXISTS `notice`;
|
||||
|
||||
-- 1.2 重建正确前缀的表
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_notice` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '公告标题',
|
||||
`content` text COMMENT '公告内容',
|
||||
`author` varchar(50) NOT NULL DEFAULT '' COMMENT '发布人',
|
||||
`type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '类型 1系统维护 2活动公告 3版本更新 4其他',
|
||||
`is_top` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否置顶 0否 1是',
|
||||
`start_time` int(11) NOT NULL DEFAULT 0 COMMENT '展示开始时间 0=长期',
|
||||
`end_time` int(11) NOT NULL DEFAULT 0 COMMENT '展示结束时间 0=长期',
|
||||
`view_count` int(11) NOT NULL DEFAULT 0 COMMENT '浏览量',
|
||||
`sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态 0禁用 1启用',
|
||||
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||
`update_at` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||
`delete_at` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_type` (`type`),
|
||||
KEY `idx_is_top` (`is_top`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站点公告';
|
||||
|
||||
-- ---------- 2. 补充后台「营运」菜单 ----------
|
||||
-- 2.1 若线上已存在同名菜单,先清理(避免 Duplicate entry)
|
||||
DELETE FROM `wxapp_backend_power` WHERE `name` IN ('operate','links:index','notice:index','ad:index','task:index','prop:index','medal:index','sms:index','help:index','shop:index','card:index','score:index');
|
||||
|
||||
-- 2.2 营运(顶级目录)
|
||||
INSERT INTO `wxapp_backend_power`
|
||||
(`pid`, `title`, `name`, `type`, `route`, `icon`, `sort`, `addon`, `status`, `create_at`, `update_at`, `delete_at`)
|
||||
VALUES
|
||||
(0, '营运', 'operate', 1, '/operate', 'layui-icon layui-icon-engine', 4, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0);
|
||||
|
||||
SET @operate_id = LAST_INSERT_ID();
|
||||
|
||||
-- 2.3 营运下子菜单
|
||||
INSERT INTO `wxapp_backend_power`
|
||||
(`pid`, `title`, `name`, `type`, `route`, `icon`, `sort`, `addon`, `status`, `create_at`, `update_at`, `delete_at`)
|
||||
VALUES
|
||||
(@operate_id, '友情链接', 'links:index', 2, 'links/index', '', 0, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '站点公告', 'notice:index', 2, 'notice/index', '', 1, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '站点广告', 'ad:index', 2, 'ad/index', '', 2, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '站点任务', 'task:index', 2, 'task/index', '', 3, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '道具中心', 'prop:index', 2, 'prop/index', '', 4, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '勋章中心', 'medal:index', 2, 'medal/index', '', 5, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '短信服务', 'sms:index', 2, 'sms/index', '', 6, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '站点帮助', 'help:index', 2, 'help/index', '', 7, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '电子商务', 'shop:index', 2, 'shop/index', '', 8, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '充值卡密', 'card:index', 2, 'card/index', '', 10, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '积分规则', 'score:index', 2, 'score/index', '', 11, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0);
|
||||
@@ -0,0 +1,34 @@
|
||||
-- ============================================================
|
||||
-- 后台「营运」菜单及其子菜单 插入 SQL
|
||||
-- 来源:public/install/install.sql 的 backend_power 数据(name=operate 营运 + 其下子菜单)
|
||||
-- 用法:
|
||||
-- 1. 把 wxapp_ 替换为线上实际表前缀后执行;
|
||||
-- 2. 本脚本用 @operate_id 变量避免硬编码 id 冲突(父菜单插入后取 LAST_INSERT_ID,子菜单 pid 引用它);
|
||||
-- 3. name 唯一约束:若线上已存在 operate 等菜单,会报 Duplicate entry,请先删除同名菜单或忽略;
|
||||
-- DELETE FROM `wxapp_backend_power` WHERE `name` IN ('operate','links:index','notice:index','ad:index','task:index','prop:index','medal:index','sms:index','help:index','shop:index','card:index','score:index');
|
||||
-- 4. 仅含菜单节点(type 1/2),未含按钮权限(type 3/4);如需完整权限按钮请用 Menu::export('operate') 生成。
|
||||
-- ============================================================
|
||||
|
||||
-- 营运(顶级目录)
|
||||
INSERT INTO `wxapp_backend_power`
|
||||
(`pid`, `title`, `name`, `type`, `route`, `icon`, `sort`, `addon`, `status`, `create_at`, `update_at`, `delete_at`)
|
||||
VALUES
|
||||
(0, '营运', 'operate', 1, '/operate', 'layui-icon layui-icon-engine', 4, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0);
|
||||
|
||||
SET @operate_id = LAST_INSERT_ID();
|
||||
|
||||
-- 营运下子菜单
|
||||
INSERT INTO `wxapp_backend_power`
|
||||
(`pid`, `title`, `name`, `type`, `route`, `icon`, `sort`, `addon`, `status`, `create_at`, `update_at`, `delete_at`)
|
||||
VALUES
|
||||
(@operate_id, '友情链接', 'links:index', 2, 'links/index', '', 0, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '站点公告', 'notice:index', 2, 'notice/index', '', 1, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '站点广告', 'ad:index', 2, 'ad/index', '', 2, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '站点任务', 'task:index', 2, 'task/index', '', 3, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '道具中心', 'prop:index', 2, 'prop/index', '', 4, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '勋章中心', 'medal:index', 2, 'medal/index', '', 5, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '短信服务', 'sms:index', 2, 'sms/index', '', 6, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '站点帮助', 'help:index', 2, 'help/index', '', 7, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '电子商务', 'shop:index', 2, 'shop/index', '', 8, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '充值卡密', 'card:index', 2, 'card/index', '', 10, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(@operate_id, '积分规则', 'score:index', 2, 'score/index', '', 11, NULL, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0);
|
||||
@@ -0,0 +1,51 @@
|
||||
-- ============================================================
|
||||
-- 前台主导航菜单 补库 SQL
|
||||
-- 适用:已部署环境增量补齐(新建表 + 默认菜单项 + 后台菜单入口)
|
||||
-- 说明:表名已统一为 common_nav_menu(前缀由部署时替换 __PREFIX__)
|
||||
-- ============================================================
|
||||
|
||||
-- 1) 创建导航菜单表(若不存在)
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__common_nav_menu` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`parent_id` int unsigned NOT NULL DEFAULT '0' COMMENT '父级ID(0=一级)',
|
||||
`title` varchar(50) NOT NULL DEFAULT '' COMMENT '菜单名称',
|
||||
`url` varchar(255) NOT NULL DEFAULT '' COMMENT '链接(controller/action 或 外链 http)',
|
||||
`icon` varchar(50) NOT NULL DEFAULT '' COMMENT '图标类',
|
||||
`sort` int NOT NULL DEFAULT '0' COMMENT '排序(越小越靠前)',
|
||||
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(1=显示 0=隐藏)',
|
||||
`create_at` int unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
|
||||
`update_at` int unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
|
||||
`delete_at` int unsigned NOT NULL DEFAULT '0' COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `parent_id` (`parent_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='前台主导航菜单';
|
||||
|
||||
-- 2) 插入默认菜单项(示例:首页/产品/案例/新闻/关于我们,含二级下拉)
|
||||
-- 先清理可能存在的旧默认数据,避免重复(按 title 去重,仅针对本批默认项)
|
||||
DELETE FROM `__PREFIX__common_nav_menu` WHERE `title` IN ('首页','产品中心','软件产品','硬件产品','客户案例','新闻动态','关于我们') AND `delete_at` = 0;
|
||||
|
||||
INSERT INTO `__PREFIX__common_nav_menu` (`parent_id`, `title`, `url`, `icon`, `sort`, `status`, `create_at`, `update_at`, `delete_at`) VALUES
|
||||
(0, '首页', 'index/index', 'layui-icon-home', 1, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(0, '产品中心', '', 'layui-icon-app', 2, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(0, '客户案例', 'case/index', 'layui-icon-template',3, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(0, '新闻动态', 'news/index', 'layui-icon-note', 4, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0),
|
||||
(0, '关于我们', 'about/index', 'layui-icon-about', 5, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0);
|
||||
|
||||
-- 二级菜单(父级=产品中心,取其刚插入的 id)
|
||||
INSERT INTO `__PREFIX__common_nav_menu` (`parent_id`, `title`, `url`, `icon`, `sort`, `status`, `create_at`, `update_at`, `delete_at`)
|
||||
SELECT id, '软件产品', 'product/index?type=soft', 'layui-icon-component', 1, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0
|
||||
FROM `__PREFIX__common_nav_menu` WHERE `title` = '产品中心' AND `delete_at` = 0 LIMIT 1;
|
||||
|
||||
INSERT INTO `__PREFIX__common_nav_menu` (`parent_id`, `title`, `url`, `icon`, `sort`, `status`, `create_at`, `update_at`, `delete_at`)
|
||||
SELECT id, '硬件产品', 'product/index?type=hard', 'layui-icon-rate', 2, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0
|
||||
FROM `__PREFIX__common_nav_menu` WHERE `title` = '产品中心' AND `delete_at` = 0 LIMIT 1;
|
||||
|
||||
-- 3) 后台菜单入口(backend_power):挂到「营运」(pid=629) 下
|
||||
-- 用 INSERT IGNORE 避免与 install.sql 全量初始化冲突
|
||||
INSERT IGNORE INTO `__PREFIX__backend_power`
|
||||
(`id`, `pid`, `name`, `url`, `type`, `route`, `icon`, `o`, `v`, `s`, `sort`, `status`, `create_at`, `update_at`, `delete_at`)
|
||||
VALUES
|
||||
(63, 3, '主导航菜单', 'nav:index', 2, 'nav/index', 'layui-icon layui-icon-list', 12, NULL, 1, 0, 0, 0),
|
||||
(64, 63, '添加菜单', 'nav:add', 3, 'nav/save', 'layui-icon ', 0, NULL, 1, 0, 0, 0),
|
||||
(65, 63, '编辑菜单', 'nav:edit', 3, 'nav/edit', 'layui-icon ', 0, NULL, 1, 0, 0, 0),
|
||||
(66, 63, '删除菜单', 'nav:delete',3, 'nav/delete', 'layui-icon ', 0, NULL, 1, 0, 0, 0);
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
# YwxApp 功能模块分类
|
||||
|
||||
> 编写日期:2026-08-07
|
||||
> 参考来源:[Discuz! 版本功能对比页](https://www.discuz.vip/version)
|
||||
|
||||
本文参考 Discuz! 官方版本页的功能模块组织方式,结合 YwxApp 当前代码库实际落地的模块,做一套功能模块分类,用于产品梳理、版本规划与对外介绍。
|
||||
|
||||
---
|
||||
|
||||
## 一、参考来源说明(Discuz! 版本页的分类逻辑)
|
||||
|
||||
Discuz! 版本对比页将功能划分为若干大类,其组织思路是:
|
||||
|
||||
- **核心基础**:站点底层能力(架构、账号、权限、用户组)。
|
||||
- **内容生产**:论坛、门户、博客、文档等内容创作与消费模块。
|
||||
- **运营工具**:积分、勋章、道具、任务、支付、营销、活动等促活/变现手段。
|
||||
- **安全与管控**:认证、审核、防灌水、日志、访问控制。
|
||||
- **扩展与生态**:插件、模板、多端、开放接口、第三方对接。
|
||||
|
||||
其核心经验:**以用户生命周期(注册 → 活跃 → 付费 → 沉淀)为主线组织模块,而非按技术栈划分**。
|
||||
|
||||
---
|
||||
|
||||
## 二、YwxApp 功能模块分类(按落地情况)
|
||||
|
||||
### 1. 核心基础(账号 / 认证 / 架构 / 市场内核)
|
||||
| 子模块 | 实现位置 | 说明 |
|
||||
|---|---|---|
|
||||
| 账号与认证体系 | `ywxapp/library/Auth.php`(`verifyAuth()`) | 登录、JWT、用户组权限校验 |
|
||||
| 会员中心框架 | `app/member/` | 控制台、iframe 工作台、主题换肤 |
|
||||
| 多应用架构 | `app/{api,backend,frontend,member,wxapp}` | 后台 / 前台 / 会员 / 接口 / 小程序多端 |
|
||||
| 应用市场内核 | `addon/appmall/` | 插件提交 → 审核 → 上架 → 购买 → 授权闭环 |
|
||||
|
||||
### 2. 内容生产(社区 / 博客 / 导航 / 文档)
|
||||
| 子模块 | 实现位置 | 说明 |
|
||||
|---|---|---|
|
||||
| 轻社区论坛 | `addon/forum/` | 发帖、我的帖子、会员中心集成 |
|
||||
| 博客系统 | `addon/blog/` | 文章发布与管理 |
|
||||
| 站点导航 | `addon/haonav/` | 自定义导航菜单 |
|
||||
| 站内文档 | `addon/docs/` | 文档 / 帮助沉淀 |
|
||||
|
||||
### 3. 运营工具(积分 / 勋章 / 道具 / 任务 / 支付 / 短信)
|
||||
| 子模块 | 实现位置 | 说明 |
|
||||
|---|---|---|
|
||||
| 任务中心 | `app/member/controller/Task.php` + `ywxapp/model/Task.php` | 任务领取、进度跟踪 |
|
||||
| 我的道具 | `wxapp_user_prop` 表 + `SchemaGuard::ensureTaskSchema()` | 道具持有与兑换 |
|
||||
| 我的勋章 | `Medal` 模型 + `wxapp_user_medal` | 成就 / 勋章授予 |
|
||||
| 充值中心 | `app/member` payment / card | 套餐充值、卡密兑换、兑换记录 |
|
||||
| 支付网关 | `addon/mqpay/`(免签)、Yansongda | 支付与回调 |
|
||||
| 短信服务 | `addon/smsbao/`、`addon/smssend/`、`ywxapp/model/Sms.php` | 验证码 / 通知 |
|
||||
|
||||
### 4. 安全与管控(权限 / 授权 / 锁定 / SEO / 通信)
|
||||
| 子模块 | 实现位置 | 说明 |
|
||||
|---|---|---|
|
||||
| 三端分离登录 | `BackendBase` / `MemberBase` / `FrontendBase` | 后台 / 会员 / 前台独立鉴权 |
|
||||
| 插件授权巡检 | `command/AddonLicenseCheck.php`、`AddonService::verifyRuntimeLicense()` | 授权闸门与过期巡检 |
|
||||
| 登录锁定 | `Auth::login` + `User::isLocked()` | 失败次数 / 时段锁定 |
|
||||
| SEO 优化 | `addon/seo/` | 静态化 / 收录优化 |
|
||||
| 即时通信 | `addon/mqttbroker/`、`addon/wxchat/` | MQTT 消息、微信会话 |
|
||||
|
||||
### 5. 扩展与生态(多端 / 开放能力 / CLI / 部署)
|
||||
| 子模块 | 实现位置 | 说明 |
|
||||
|---|---|---|
|
||||
| 微信生态 | `addon/wxchat/` | 小程序 / 微信对接(含 uni-app `.uvue/.uts`) |
|
||||
| 应用市场生态 | `addon/appmall/` | 开发者中心、分润、下载安装 |
|
||||
| CLI 命令体系 | `ywxapp/service/AppService.php` 注册 | `addon:health` / `make` / `manage` / `license-check` |
|
||||
| 打包与分发 | `package_appmarket_addon.php` / `package_framework.php` | 插件包 / 框架包生成 |
|
||||
|
||||
---
|
||||
|
||||
## 三、与 Discuz! 的对照与差距
|
||||
|
||||
### 3.1 已具备对应能力(但部分缺后台管理端)
|
||||
- 权限控制 / 用户组 / 积分 / 勋章 / 道具 / 任务:YwxApp 已有对应能力,但 **勋章、道具、任务目前主要在会员中心侧边菜单**,尚未补齐 `app/backend` 后台管理入口(Discuz! 后台可配置积分规则、勋章发放、道具管理等)。
|
||||
|
||||
### 3.2 差异化优势(Discuz! 开源版没有)
|
||||
- **应用市场商业化闭环**:提交 → 审核 → 购买 → 支付回调 → 域名授权 → 运行期过期巡检 → 分润提现。这是 YwxApp 的独有模块,也是商业变现的核心。
|
||||
|
||||
### 3.3 暂缺能力(Discuz! 有,YwxApp 尚未实现)
|
||||
- 内容审核 / 云审机制
|
||||
- GEO 地理位置 / LBS
|
||||
- AI 推荐 / 个性化
|
||||
- ElasticSearch 全文检索
|
||||
- 可视化工作流 / 审批流
|
||||
|
||||
---
|
||||
|
||||
## 四、模块状态总览(已上线 / 待完善 / 缺失)
|
||||
|
||||
图例:✅ 已上线 🟡 待完善(有基础能力但缺管理端/不完整) ⬜ 缺失(未实现)
|
||||
|
||||
### 核心基础
|
||||
| 子模块 | 状态 | 说明 |
|
||||
|---|---|---|
|
||||
| 账号与认证体系 | ✅ | Auth + verifyAuth 完整 |
|
||||
| 会员中心框架 | ✅ | 控制台/工作台/换肤完整 |
|
||||
| 多应用架构 | ✅ | 五端齐备 |
|
||||
| 应用市场内核 | ✅ | 提交→审核→购买→授权闭环完整 |
|
||||
|
||||
### 内容生产
|
||||
| 子模块 | 状态 | 说明 |
|
||||
|---|---|---|
|
||||
| 轻社区论坛 | ✅ | forum 插件完整(含内容审核:need_audit 配置 + 机审/人工复核) |
|
||||
| 博客系统 | ✅ | blog 插件含 `controller/backend/` 下 9 个控制器,后台管理完整(含内容审核:audit_status) |
|
||||
| 站点导航 | ✅ | haonav 完整 |
|
||||
| 站内文档 | ✅ | docs 完整 |
|
||||
|
||||
### 运营工具
|
||||
| 子模块 | 状态 | 说明 |
|
||||
|---|---|---|
|
||||
| 任务中心 | ✅ | 前台+后台 CRUD 完整 |
|
||||
| 我的道具 | ✅ | 前台+后台(Prop)完整 |
|
||||
| 我的勋章 | ✅ | 前台+后台(Medal)完整 |
|
||||
| 充值中心 | ✅ | 套餐/卡密/记录+后台(Card/Shop)完整 |
|
||||
| 支付网关 | ✅ | mqpay 免签(收款码+安卓监听自动确认+后台人工确认)+ Yansongda 真实商户支付均已落地 |
|
||||
| 短信服务 | ✅ | smsbao/smssend+Sms 模型+后台完整 |
|
||||
| **积分规则管理** | ✅ | 本次新增 `score_rule`/`score_log` 表 + `Score` 控制器/模型/菜单 |
|
||||
|
||||
### 安全与管控
|
||||
| 子模块 | 状态 | 说明 |
|
||||
|---|---|---|
|
||||
| 三端分离登录 | ✅ | Backend/Member/Frontend 完整 |
|
||||
| 插件授权巡检 | ✅ | license-check + 运行时闸门完整 |
|
||||
| 登录锁定 | ✅ | Auth::login + isLocked 完整 |
|
||||
| SEO 优化 | ✅ | seo 插件完整 |
|
||||
| 内容审核 | ✅ | forum/blog 已接入机审+人工复核队列 |
|
||||
| 即时通信 | ✅ | mqttbroker 连接数/限流保护 + 重连封装 + 压测脚本完整 |
|
||||
| 全文检索 | ✅ | ES 可选增强(默认 LIKE,ES 不可用自动降级),forum/blog 已接入 |
|
||||
|
||||
### 扩展与生态
|
||||
| 子模块 | 状态 | 说明 |
|
||||
|---|---|---|
|
||||
| 微信生态 | ✅ | wxchat + uni-app 对接完整 |
|
||||
| 应用市场生态 | ✅ | 开发者中心/分润/下载完整 |
|
||||
| CLI 命令体系 | ✅ | AppService 注册 4 条命令完整 |
|
||||
| 打包与分发 | ✅ | 两个 package 脚本完整 |
|
||||
|
||||
---
|
||||
|
||||
## 五、补齐路线图(待完善 / 缺失能力)
|
||||
|
||||
### A. 运营工具补全(优先级高)— 已全部完成 ✅(2026-08-07)
|
||||
1. **积分规则管理后台(⬜→✅,2026-08-07 新增)**:新增 `score_rule`/`score_log` 表 + `app/backend/controller/Score.php`(规则 CRUD + 流水查看)+ 模型 + validate + 视图/JS + `admin_power` 菜单种子;任务发积分改为走 `Score::change()`(事务内改 `user.score` 并写 `score_log` 流水)。
|
||||
2. **支付网关 mqpay 免签真接入(🟡→✅,核实已具备)**:经排查 `addon/mqpay` 插件已实现免签全流程——个人收款码扫码 + 金额零头防撞单 + 安卓监听 App 自动确认(`notify-app`,md5 验签)+ 后台人工确认(`Confirm`),并可选启用 Yansongda 真实商户支付。原标注"未接入"系调研不完整所致,实际无需改动。
|
||||
3. **博客后台管理完善(🟡→✅,核实已具备)**:`addon/blog/controller/backend/` 下已有 Article/Category/Comment/Dashboard/Index/Setting/Stat/Tag/User 共 9 个后台控制器,后台管理完整,无需补齐。
|
||||
|
||||
### B. 安全与管控增强(优先级中)— 已全部完成 ✅(2026-08-07)
|
||||
1. **内容审核 / 云审机制(⬜→✅,2026-08-07 新增)**:
|
||||
- forum:`forum_topic` 补 `audit_reason` 列(install.sql 事实源 + `SchemaGuard::ensureForumSchema()` 兜底);`need_audit` 开启时发帖走 `AiAuditService` 机审(命中违规直接驳回,通过转人工待审);后台 `Topic::audit()/reject()` + 路由 + 「待审帖子」菜单子项 + 列表审核按钮;前台详情页仅 `status=1` 可见。
|
||||
- blog:`blog_article` 补 `audit_status` 列(0草稿/1待审/2通过/3驳回,install.sql + `SchemaGuard::ensureBlogSchema()` 兜底);`BlogArticle` 模型 `$allowField` 加 `audit_status`;发布走机审;后台 `Article::audit()/reject()` + 审核状态列/筛选 + 路由 + 菜单子项。
|
||||
- 复用 `addon/wxchat/service/AiAuditService.php`(已存在的云审接入点)。
|
||||
2. **即时通信稳定性(🟡→✅,2026-08-07 新增)**:
|
||||
- Broker 过载保护:新增 `max_connections`(连接数硬上限拒绝新连接防雪崩)+ `publish_rate_limit`(单连接令牌桶限流,超量静默丢弃);config 暴露后台可调。
|
||||
- 客户端重连封装:`client/MqttClient.js`(指数退避 1s→30s + 会话恢复 + 遗嘱 LWT + 心跳兜底)。
|
||||
- 压测脚本:`benchmark.php`(纯 PHP 零依赖,模拟 N 客户端并发连接 + 高频发布,验证稳定性)。
|
||||
|
||||
### C. 差异化能力扩展(优先级低,对标 Discuz! 缺失项)
|
||||
1. **GEO 地理位置 / LBS(⬜→✅,2026-08-08 新增)**:
|
||||
- `forum_topic`/`blog_article` 加可选 `lat`/`lng` 字段(install.sql 事实源 + `SchemaGuard::ensureForumSchema/ensureBlogSchema` 兜底)。
|
||||
- `ForumSearchService::nearby()` / `BlogSearchService::nearby()`:Haversine 球面距离过滤(零依赖),发帖时可带坐标(前端定位可选填)。
|
||||
- 前台「附近帖子」(`/forum/nearby`)、「附近文章」(`/blog/nearby`) 页:浏览器定位 → 半径列表,未定位可手动传 lat/lng。
|
||||
2. **AI 推荐 / 个性化(⬜→✅,2026-08-08 新增)**:
|
||||
- 基于内容相似度的相关推荐:`EsSearch::moreLikeThis()`(ES more_like_this,内容相关度,启用 ES 时生效)+ `ForumSearchService::related()` / `BlogSearchService::related()`(ES 不可用时降级为同版块/同分类/标签重叠相似度)。
|
||||
- 接入点:forum 帖子详情页右侧「相关帖子」、blog 文章详情页「相关推荐」。
|
||||
3. **ElasticSearch 全文检索(⬜→✅,2026-08-08 新增)**:
|
||||
- 通用封装 `ywxapp/library/Search/EsSearch.php`(GuzzleHttp 调 ES 9200 REST,ensureIndex/indexDoc/deleteDoc/search/rebuild,IK 分词降级 standard,不可用时抛异常由上层降级)。
|
||||
- `ForumSearchService` / `BlogSearchService`:配置开关 `search_engine=like|es`,默认 LIKE,**ES 不可用时自动降级**到原生模糊检索(零风险)。
|
||||
- 薄封装:ES 仅返回 id 列表 → `whereIn('id', $ids)` 回表取完整行 + 关联,复用现有视图。
|
||||
- 接入点:forum 前台搜索、blog 前台搜索替换为 ES 查询;发帖/编辑/删除时同步索引(`sync/remove`)。
|
||||
- 配置项:`addon/forum/config.php` + `addon/blog/config.php` 加 `search_engine`/`es_host`/`es_index`。
|
||||
4. **可视化工作流 / 审批流(✅)**——用于插件上架审核流可视化。
|
||||
- `appmarket_addon_review_log` 审核历史日志表(install.sql + SchemaGuard 双源)。
|
||||
- `AddonReview` 后台加 `approve`/`reject` 写入审核日志;`detail`(JSON)/`detailview`(HTML)/`history`(JSON) 三接口 + 路由。
|
||||
- 列表页"查看详情"按钮 → iframe 弹层打开 `detailview`:提交信息卡 + 审核状态步骤条 + 历史时间线。
|
||||
|
||||
### D. 对外材料
|
||||
- 对外介绍页可直接复用本分类五大类结构,突出「应用市场商业化闭环」差异化卖点。
|
||||
- 版本规划表以第四节状态总览为准,按 A→B→C 优先级排期。
|
||||
@@ -0,0 +1,84 @@
|
||||
# 应用市场商业化流程(Discuz! 式插件/模板售卖闭环)
|
||||
|
||||
> 业务模型:**框架免费,靠卖插件/模板变现**。
|
||||
> 中心站(market)承载商品/交易/授权,客户机(client)拉取并受授权闸门约束。
|
||||
|
||||
## 一、总体流程图
|
||||
|
||||
```
|
||||
开发者提交 ──▶ 审核 ──▶ 上架 ──▶ 浏览详情 ──▶ 前台购买 ──▶ 支付 ──▶ 回调出授权
|
||||
│ │ │ │ │ │ │
|
||||
Market::submit 后台审核 catalog() Store::detail Store::buy Yansongda completeOrder()
|
||||
│ │ │ │ │ │ │ → 写 license_key
|
||||
│ │ │ │ │ │ │ → 写分润/开发者余额
|
||||
└────────────┴─────────┴───────────┴────────────┴──────────┴───────────┴──▶ 下载安装(绑定域名)
|
||||
│
|
||||
Market::valid / index
|
||||
│
|
||||
启用闸门(运行期校验)
|
||||
│
|
||||
AddonService::verifyRuntimeLicense
|
||||
│
|
||||
AddonService::checkLicenses (crontab)
|
||||
│
|
||||
过期/吊销 → 自动禁用
|
||||
│
|
||||
分润提现 Withdrawal 模型
|
||||
```
|
||||
|
||||
## 二、各阶段落地清单
|
||||
|
||||
| 阶段 | 状态 | 关键代码 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| 1. 开发者提交 | ✅ | `Market::submit` | 支持 addon / template 两种类型,含安全扫描 |
|
||||
| 2. 审核 / 上架 | ✅ | 后台审核 + `MarketService::catalog()` | 通过后才进 `catalog()` 列表 |
|
||||
| 3. 浏览详情 | ✅ | `Store::detail` + `view/frontend/store/detail.html` | 价格/截图/版本/授权说明 |
|
||||
| 4. 前台购买下单 | ✅ | `Store::buy`(POST `/appmall/store/buy`) | 取会员 uid;中心站直连 `MarketService::buy`,客户机经 `RemoteService::buy` |
|
||||
| 5. 支付 | ✅ | `MarketService::buy` 内 `Yansongda\Pay`;`Market::notify` / `payResult` | 返回 `pay_params`(web 表单)前端 `window.open` 拉起收银台 |
|
||||
| 6. 回调出授权 | ✅ | `MarketService::completeOrder` | 支付成功生成 `license_key`(HMAC 签名)+ 写分润/开发者余额 |
|
||||
| 7. 订单轮询 | ✅ | `Store::orderStatus`(GET `/appmall/store/orderStatus`) | 前端拉起收银台后轮询,拿到 `license_key` |
|
||||
| 8. 下载安装 + 域名绑定 | ✅ | `Market::index`(下载)/ `Market::valid`(安装时校验绑定域名) | 校验:免费→uid→存在→吊销→过期→域名绑定 |
|
||||
| 9. 启用闸门 | ✅ | `AddonService::enable` → `verifyRuntimeLicense` | POST 中心站 `validDomain`;失败抛 `AddonException`(除非 `offline_tolerant`) |
|
||||
| 10. 运行期过期巡检 | ✅ | `AddonService::checkLicenses` + `addon:license-check` 命令 | crontab 每日巡检,过期落库禁用 |
|
||||
| 11. 分润 / 提现 | ✅ | `Withdrawal` 模型 + 收益后台 | 开发者余额可提现 |
|
||||
|
||||
## 三、配置开关
|
||||
|
||||
`config/ywxapp.php`:
|
||||
- `market_mode` / `api_url`:决定中心站 or 客户机。客户机(`is_market_client()===true`)才启用授权校验。
|
||||
- `addon_license_check`:`false`(关闭校验)/ `true`(中心站不可达也禁用)/ `'offline_tolerant'`(不可达放行,仅真实过期拒绝禁用)。
|
||||
|
||||
## 四、前端购买链路(detail.html)
|
||||
|
||||
```
|
||||
点击「立即购买」
|
||||
→ POST /appmall/store/buy {name}
|
||||
← code=1, license_key → 提示已购,引导后台安装绑定域名
|
||||
← code=1, trade_no+pay_params → window.open 收银台 + 轮询 orderStatus
|
||||
← code=1, trade_no(无pay_params, mock) → 演示确认 → 轮询
|
||||
← code=0, need_login → 跳 /member/login
|
||||
```
|
||||
|
||||
## 五、部署清单
|
||||
|
||||
**中心站需同步**:`addon/appmall/`(Store 控制器 + route/app.php + view/frontend/store/detail.html)、`config/pay.php`(Yansongda 配置)。
|
||||
|
||||
**客户机需同步**:
|
||||
- `ywxapp/service/AddonService.php`(`verifyRuntimeLicense` + `checkLicenses`)
|
||||
- `ywxapp/service/AppService.php`(`addon:license-check` 注册)
|
||||
- `ywxapp/command/AddonLicenseCheck.php`
|
||||
- `config/ywxapp.php`(`addon_license_check`)
|
||||
|
||||
**定时巡检(客户机)**:
|
||||
```
|
||||
10 3 * * * cd /path/to/site && php think addon:license-check >> runtime/license_check.log 2>&1
|
||||
```
|
||||
|
||||
## 六、待办(真实支付与增长)
|
||||
|
||||
1. 真实支付:当前 `pay_params` 来自 `Yansongda\Pay`(中心站 `config/pay.php`),mqpay 免签插件未参与此链路;如需接 mqpay 收银台需另接。
|
||||
2. 域名绑定可视化:安装时填写域名并校验的 UI 提示。
|
||||
3. 运营增长:优惠券/分销、开发者实名结算、SEO 内容填充。
|
||||
|
||||
---
|
||||
*参考:`docs/自定义命令清单.md`(`addon:license-check`)、`docs/系统架构与解耦设计.md`。*
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
# 打包与发布指南
|
||||
|
||||
本仓库提供三套打包脚本,分别用于**主框架整站升级包**、**单个插件市场包**、**批量插件市场包**。
|
||||
三者均已支持**自动递增版本号**,并配套「框架在线升级的数据库变更」机制。
|
||||
|
||||
> 部署(中心站 / 客户端如何对接)不在本文范围,参见 `docs/DEPLOY_INTEGRATION.md`。
|
||||
|
||||
## 0.0 产物与用途总览
|
||||
|
||||
`package_framework.php` **一次运行同时产出三个文件**;插件包由独立脚本产出:
|
||||
|
||||
| 产物 | 来源命令 | 输出位置 | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| 框架整包 `ywxapp-<ver>.zip` | `package_framework.php` | `build/framework/` | 框架升级(整站覆盖) |
|
||||
| 框架补丁 `ywxapp-<ver>-patch.zip` | `package_framework.php` | `build/framework/` | 框架增量升级(`from_version` 起 git diff) |
|
||||
| 完整安装包 `ywxapp-<ver>-install.zip` | `package_framework.php` | `build/framework/` | **新站首次部署**(白名单:app/config/extend/public/ywxapp/route + 根文件;不含插件、不含真实 `.env`/`database.php`/`ywxapp.php`,内置 `.env` 占位模板) |
|
||||
| 插件市场 zip + 测试 SQL | `package_all_addon.php` / `package_appmarket_addon.php` | `build/addon/`、`build/sql/` | 上传到应用市场后台,供客户机**后台购买后下载安装** |
|
||||
|
||||
> ⚠️ **重要约定**
|
||||
> - 框架整包与完整安装包**都刻意不含 `addon/` 插件目录**(整包经 `includeDirs` 排除;安装包用白名单 + 排除 `public/static/<插件>/`、`public/addon/`)。全新部署框架后,插件需经后台应用市场**独立下载安装**,不要把插件打进框架包。
|
||||
> - `package_install.php` 已废弃并合并进 `package_framework.php`(安装包产物由后者统一产出),请勿再单独调用。
|
||||
> - 插件走「后台购买 → 下载 → 安装」的在线分发链路,与框架整包互相独立,二者不要混包。
|
||||
|
||||
---
|
||||
|
||||
## 0. 版本号来源(重要)
|
||||
|
||||
| 产物 | 版本号来源 | 是否进包 | 写回位置 |
|
||||
|---|---|---|---|
|
||||
| 主框架整站包 | `config/ywxapp.php` 第 37 行 `version` | 不进包(被排除) | `config/ywxapp.php` |
|
||||
| market 插件包 | `addon/appmall/info.php` 的 `version` | 不进包(随插件本身) | `addon/appmall/info.php` |
|
||||
| 其他插件包 | `addon/<name>/info.php` 的 `version` | 不进包(随插件本身) | `addon/<name>/info.php` |
|
||||
|
||||
- **框架版本与各插件版本互相独立**,各自维护、各自递增。
|
||||
- 打包脚本每次都会把新版本**写回对应文件**,保证下次打包在此基础上再 `+1`。
|
||||
- 写回会改动 git 跟踪文件,属预期行为;但该文件被排除在升级包外,不会覆盖客户机。
|
||||
|
||||
---
|
||||
|
||||
## 1. 主框架整站升级包 — `scripts/package_framework.php`
|
||||
|
||||
生成「整站升级包」(含 `app/config/extend/ywxapp/public/route` + 根文件 `think/composer.json/README.md/LICENSE.txt`),
|
||||
客户端 `ywxapp/service/FrameworkService::apply()` 解压即完成整站升级。
|
||||
|
||||
**产物**(`build/framework/`,非 Web 可访问):
|
||||
- `ywxapp-<version>.zip` —— 整包(整站,**不含插件**)
|
||||
- `ywxapp-<version>-patch.zip` —— 增量补丁包(`from_version` = 基础版本)
|
||||
|
||||
**安全排除**(绝不进包,避免覆盖客户机本地配置与数据):
|
||||
`runtime/`、`vendor/`、`data/`、`.git`、`public/uploads`、`config/database.php`、`config/ywxapp.php`、`.env`、`install.lock`、`addon/`(插件独立分发)。
|
||||
|
||||
### 用法(支持自动递增版本号)
|
||||
|
||||
```bash
|
||||
# 不带版本号:自动按 patch +1(读 config/ywxapp.php 当前版本,写回新值)
|
||||
php scripts/package_framework.php
|
||||
|
||||
# 按段递增
|
||||
php scripts/package_framework.php minor # 如 1.0.2 -> 1.1.0
|
||||
php scripts/package_framework.php major # 如 1.0.2 -> 2.0.0
|
||||
|
||||
# 显式指定新版本
|
||||
php scripts/package_framework.php 1.2.3
|
||||
```
|
||||
|
||||
- `baseVersion` 为第二可选参数(升级起点/补丁基础版本),缺省取写回前的旧值。
|
||||
- 打包前会跑 `regressionCheckConfigVersion()` 校验,拦截「写坏配置」的历史 bug 写法(若触发直接中断打包)。
|
||||
|
||||
### 数据库升级 SQL 约定
|
||||
|
||||
若本次升级涉及数据库结构/数据变更,把**增量 SQL** 放到仓库 `ywxapp/upgrade.sql`:
|
||||
- 整站包包含 `ywxapp/` 目录,`upgrade.sql` 会自动打进包,解压后位于 `项目根/ywxapp/upgrade.sql`。
|
||||
- `apply()` 解压完成后自动检测并执行(`coreDir/upgrade.sql` 或 `root/upgrade.sql`)。
|
||||
- 按语句拆分(忽略 `--` 行注释与 `/* */` 块注释),用 `Db::connect()->execute()` 执行。
|
||||
- **幂等**:已执行版本记入 `runtime/framework/sql_applied.json`(`版本:文件名` 为键),重复升级不重复执行;执行成功后会删除该 `upgrade.sql`。
|
||||
- 执行失败抛 `数据库升级失败:...` 异常,由升级流程提示(文件已提前备份)。
|
||||
- ⚠️ **无事务回滚**,SQL 须写成幂等(如 `CREATE TABLE IF NOT EXISTS`、`ADD COLUMN IF NOT EXISTS`),并在预发环境先验证。
|
||||
|
||||
---
|
||||
|
||||
## 2. 单个插件市场包 — `scripts/package_appmarket_addon.php`
|
||||
|
||||
把本地插件打包成市场 zip(结构对齐 `AddonService::package()`):`addon/<name>/` 为 zip 根,`public/static/addon/<name>/` 为 `static/` 前缀。
|
||||
并生成 `wxapp_appmarket_addon_list` 测试记录 SQL(用于往中心站插一条数据,便于联调)。
|
||||
|
||||
**产物**:
|
||||
- `build/addon/<addon>-<version>.zip`
|
||||
- `build/sql/<addon>-<version>-appmarket_addon_list.sql`(测试记录,`file_path` 为本地绝对路径,中心站部署时需改成实际路径或走后台上传)
|
||||
|
||||
### 用法(支持自动递增版本号)
|
||||
|
||||
```bash
|
||||
# 不带版本号:自动按 patch +1(读 addon/<addon>/info.php 当前版本,写回新值)
|
||||
php scripts/package_appmarket_addon.php appmall
|
||||
|
||||
# 按段递增
|
||||
php scripts/package_appmarket_addon.php appmall minor # 1.0.1 -> 1.1.0
|
||||
|
||||
# 显式指定
|
||||
php scripts/package_appmarket_addon.php appmall 1.2.0
|
||||
|
||||
# 兼容旧式(带标题等可选参数,version 缺省则按 patch 自动递增)
|
||||
php scripts/package_appmarket_addon.php mqttbroker "MQTT代理" "" "基于 Workerman 自研 MQTT Broker" 0
|
||||
```
|
||||
|
||||
- 版本参数位于 `$argv[2]`:若匹配 `x.y.z` 或 `major|minor|patch`,则视为版本参数,其后 `title/author/description/price` 整体左移一位,保持旧调用兼容。
|
||||
|
||||
---
|
||||
|
||||
## 3. 批量插件市场包 — `scripts/package_all_addon.php`
|
||||
|
||||
一次性打包 `addon/` 下所有插件。
|
||||
|
||||
**产物**:`build/addon/<name>-<version>.zip` + `build/sql/<name>-<version>-appmarket_addon_list.sql`(每个插件一份)。
|
||||
|
||||
### 用法
|
||||
|
||||
```bash
|
||||
# 不带 bump:按各插件 info.php 当前版本打包,不改版本号(安全、可重复执行)
|
||||
php scripts/package_all_addon.php
|
||||
|
||||
# bump:每个插件版本按 patch +1 并写回各自 info.php(发版用)
|
||||
php scripts/package_all_addon.php bump
|
||||
|
||||
# 跳过指定插件(与 bump 可组合)
|
||||
php scripts/package_all_addon.php skip:market
|
||||
php scripts/package_all_addon.php bump skip:market
|
||||
```
|
||||
|
||||
> ⚠️ `bump` 会修改所有被打包插件的 `info.php` 版本号。日常只想重新生成 zip 时**不要带 `bump`**。
|
||||
|
||||
---
|
||||
|
||||
## 4. 典型发版流程
|
||||
|
||||
```bash
|
||||
# 1) 框架整站升级(自动 +1,含可选 upgrade.sql)
|
||||
php scripts/package_framework.php
|
||||
|
||||
# 2) 中心站插件(market)发版(自动 +1)
|
||||
php scripts/package_appmarket_addon.php appmall
|
||||
|
||||
# 3) 其余插件批量发版(各 +1)
|
||||
php scripts/package_all_addon.php bump
|
||||
```
|
||||
|
||||
1. 把 `build/framework/ywxapp-<ver>.zip`(整包)在中心站后台「主框架版本管理」上传并**发布**(客户端方可检测;该整包即**新站安装包**,不含插件)。
|
||||
2. 把 `build/addon/*.zip` 在中心站「应用市场管理 / 插件审核」上传,或走后台「上传升级包」(客户机后台购买后下载安装)。
|
||||
3. 涉及数据库变更时,确保打包前已把增量 SQL 写入 `ywxapp/upgrade.sql`(随包发布、升级时自动执行)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 常见问题
|
||||
|
||||
- **Q:怎么让每次打包版本号自动 +1?**
|
||||
A:框架包直接空参运行;插件包空参或带 `minor/major`;批量包带 `bump`。三者都会写回版本文件。
|
||||
- **Q:框架升级改了数据库表结构,客户机怎么同步?**
|
||||
A:把增量 SQL 放 `ywxapp/upgrade.sql`,随包发布,`apply()` 自动幂等执行(见第 1 节)。
|
||||
- **Q:补丁包比整包大 / 变更很多?**
|
||||
A:补丁基于 git 工作树相对基础版本的 diff。若工作树相对某基线改动很多,补丁会偏大;先把代码提交到稳定基线后再打,补丁即为当次真实改动。
|
||||
- **Q:打包提示 LF/CRLF 警告?**
|
||||
A:git `autocrlf` 噪声,非错误,可忽略。
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
# Ywxapp 插件使用说明
|
||||
|
||||
> 适用框架:Ywxapp(基于 ThinkPHP 8 的多应用框架)
|
||||
> 本文逐一说明 `addon/` 目录下**全部 6 个插件**的功能、安装、配置与使用方法。
|
||||
> 插件机制与开发规范详见同级《插件开发指南.md》。
|
||||
|
||||
---
|
||||
|
||||
## 插件清单
|
||||
|
||||
| 插件 | 标题 | 版本 | 状态 | 形态 | 一句话简介 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `blog` | 博客应用 | 1.0.0 | 启用 | 完整业务 | 多用户博客(前台展示 / 会员中心 / 后台管理) |
|
||||
| `mqttbroker` | MQTT代理 | 2.1.0 | 启用 | 常驻进程 | 基于 Workerman 自研的 MQTT 3.1.1/5.0 Broker |
|
||||
| `haonav` | 网址导航 | 1.0.0 | 启用 | 完整业务 | 网址分类与站点收录、展示、后台管理 |
|
||||
| `wxchat` | 聊天应用 | 1.0.0 | 启用 | 常驻进程 | 仿微信的移动端 IM 社交(GatewayWorker) |
|
||||
| `smsbao` | 短信宝 | 1.0.0 | 启用 | 事件订阅 | 对接短信宝网关发送验证码(半成品) |
|
||||
| `demo` | 演示插件 | 1.0.0 | 启用 | 脚手架 | 插件开发规范示例骨架(仅返回一句话) |
|
||||
|
||||
---
|
||||
|
||||
## 通用:安装 / 启用 / 卸载
|
||||
|
||||
所有插件遵循同一套生命周期,详见《插件开发指南.md》第 4 节。常用操作:
|
||||
|
||||
```bash
|
||||
# 开发者模式(本地已放好源码时,免打包、可反复执行,幂等)
|
||||
php think addon:manage <name> -a develop
|
||||
|
||||
# 后台操作
|
||||
# 插件管理 → 离线安装(上传 zip)→ 启用 / 禁用 / 卸载
|
||||
```
|
||||
|
||||
- **安装**:写入 `info.php`(`state`)、执行 `install.sql` 建表、注入 `menu.json` 菜单、调用 `Addon::install()` 钩子。
|
||||
- **启用 / 禁用**:仅切换 `info.php` 的 `state`,并联动菜单显隐;`enable/disable` 钩子存在默认空实现。
|
||||
- **卸载**:调用 `Addon::uninstall()`(清理数据表与菜单),再删除插件目录。生产环境操作前务必备份。
|
||||
|
||||
> 安装 / 启动命令需在**项目根目录**执行;常驻进程(Broker、IM 服务)建议在 Linux 上以 `nohup php think xxx &` 或 `supervisor` 守护。
|
||||
|
||||
---
|
||||
|
||||
## 1. blog(博客应用)
|
||||
|
||||
### 1.1 功能简介
|
||||
一个多用户博客平台,提供三套界面:
|
||||
- **前台展示(公开)**:首页(最新/热门/推荐文章、分类、标签云、最新评论)、文章详情、分类/标签筛选、搜索、作者主页、关于/联系页、RSS 订阅。
|
||||
- **会员中心(需登录)**:发布/编辑/删除自己的文章、自建分类、个人资料。
|
||||
- **后台管理(需管理员登录)**:仪表盘统计、文章审核、全局分类/标签、评论审核、用户管理、统计分析、站点/SEO/邮件设置。
|
||||
|
||||
### 1.2 数据表(安装时自动创建,`wxapp_blog_*`)
|
||||
| 表 | 用途 |
|
||||
| --- | --- |
|
||||
| `wxapp_blog_article` | 文章(标题/正文/封面/作者 uid/分类/状态/置顶/计数) |
|
||||
| `wxapp_blog_category` | 分类(全局共享 uid=0,或会员自建) |
|
||||
| `wxapp_blog_tag` | 标签 |
|
||||
| `wxapp_blog_article_tag` | 文章-标签关联 |
|
||||
| `wxapp_blog_comment` | 评论(支持盖楼、审核) |
|
||||
| `wxapp_blog_like` | 点赞 |
|
||||
| `wxapp_blog_favorite` | 收藏 |
|
||||
| `wxapp_blog_user` | 博客用户(映射表名 `user`) |
|
||||
| `wxapp_blog_visit` | 访客记录 |
|
||||
|
||||
### 1.3 入口与菜单
|
||||
- **前台入口**:`/blog`(首页)、`/blog/article/:id`、`/blog/category/:id`、`/blog/tag/:id`、`/blog/rss` 等。
|
||||
- **会员中心**:`/blog/member`(强制登录)。
|
||||
- **后台**:已注入菜单「博客管理」,含 仪表盘 / 文章管理 / 分类管理 / 标签管理 / 评论管理 / 用户管理 / 统计分析 / 系统设置。
|
||||
|
||||
### 1.4 配置说明
|
||||
- 插件 `config.php` 与 `info.php['config']` 均为空,**无独立配置项**。
|
||||
- 站点 / SEO / 邮件配置通过后台「系统设置」直接写入应用级 `config/site.php`、`config/seo.php`、`config/email.php`。
|
||||
|
||||
### 1.5 使用要点
|
||||
- 文章由**会员在会员中心发布**,后台仅做管理与审核(不发布新文章)。
|
||||
- 后台「系统设置」可配置站点信息、SEO 与邮件(发信)参数。
|
||||
- 前台文章评论带敏感词过滤;互动(点赞/收藏)需登录。
|
||||
|
||||
### 1.6 注意事项
|
||||
- 后台路由挂载 `AdminAuth` 中间件(未登录跳转 `/admin/login`);会员路由挂载 `MemberAuth`(未登录跳 `/member/login`)。
|
||||
- 自带 `READMD.md` 描述的是独立 ThinkPHP 应用形态(表名、登录账号与实现不符),**以本插件实际代码为准**。
|
||||
|
||||
---
|
||||
|
||||
## 2. mqttbroker(MQTT 代理)
|
||||
|
||||
### 2.1 功能简介
|
||||
基于 Workerman 自研的 MQTT 3.1.1 / 5.0 Broker(类 EMQX 轻量版):
|
||||
- 协议:CONNECT/PUBLISH(QoS0/1/2)/SUBSCRIBE/PING/DISCONNECT/AUTH。
|
||||
- 传输:TCP(默认 1883)、WebSocket(8083,浏览器/小程序)、可选 TLS。
|
||||
- 认证 / ACL、保留消息、遗嘱、离线消息持久化、共享订阅(`$share/{group}/{filter}`)。
|
||||
- `$SYS/broker/#` 系统指标、`/static/addon/mqttbroker/` 实时监控仪表盘。
|
||||
- 基于 Redis Stream 的多进程/WS 桥接、规则引擎转发到外部 Broker(如 EMQX)。
|
||||
- 手动发布走 DB 出站队列(零外部依赖)。
|
||||
|
||||
### 2.2 启动命令(常驻进程)
|
||||
```bash
|
||||
php think mqttbroker:start # 启动 Broker(Linux 建议守护)
|
||||
php think mqttbroker:acltest # ACL 规则测试工具
|
||||
```
|
||||
> Windows 下单进程调试模式,WebSocket 多监听在 Windows 不支持;生产请使用 Linux。
|
||||
|
||||
### 2.3 数据表(`wxapp_mqttbroker_*`)
|
||||
连接、消息日志、订阅关系、认证账号、ACL 规则、保留消息、离线消息、实时指标、转发规则、出站队列(共 11 张,`stats` 表有代码自愈建表)。
|
||||
|
||||
### 2.4 后台入口与菜单
|
||||
已注入菜单「MQTT代理」:概览 / 客户端连接 / 消息日志 / 发布消息 / 认证账号 / ACL规则 / 实时监控 / 转发规则 / 服务设置(均在 `/mqttbroker/backend/...`)。
|
||||
|
||||
### 2.5 配置说明(后台「服务设置」)
|
||||
配置项存于数据库 `wxapp_addon_config`(每个配置独立成行),由后台「插件管理 → 配置」或插件内「服务设置」页写入,读取走缓存(键 `addon_config_mqttbroker`)。表单字段由 `config.php` 定义,运行期默认值在 `Broker::loadConfig()` 中(`array_merge($defaults, $saved)`)。**两处默认值应保持一致**。关键项:
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `port` | 1883 | TCP 监听端口 |
|
||||
| `host` | 0.0.0.0 | 绑定地址 |
|
||||
| `ws_port` | 8083 | WebSocket 端口,0=关闭 |
|
||||
| `ssl_enabled` | 0 | 对 TCP 启用 TLS |
|
||||
| `ssl_cert` / `ssl_key` | 空 | PEM 证书/私钥绝对路径 |
|
||||
| `allow_anonymous` | 1 | 允许匿名连接;设为 0 时需在「认证账号」维护账号 |
|
||||
| `acl_enabled` | 0 | 开启后按「ACL规则」校验发布/订阅 |
|
||||
| `acl_default` | allow | 规则未命中时的兜底策略 |
|
||||
| `max_keepalive` | 60 | 最大保活间隔(秒) |
|
||||
| `sys_enabled` / `sys_interval` | 1 / 10 | `$SYS` 指标开关与发布周期 |
|
||||
| `persist_retain` / `persist_offline` | 1 / 1 | 保留消息 / 离线消息持久化 |
|
||||
| `offline_limit` | 1000 | 单客户端离线消息上限 |
|
||||
| `cluster_enabled` | 0 | 集群桥接(需 Redis) |
|
||||
| `rule_enabled` | 1 | 转发规则引擎开关 |
|
||||
| `redis_*` | 127.0.0.1:6379 | 集群桥接用 Redis 连接 |
|
||||
| `log_level` | 1 | 日志级别(0 关 / 1 仅错误 / 2 详细) |
|
||||
|
||||
### 2.6 使用要点
|
||||
1. 启动 Broker 进程(命令见 2.2)。
|
||||
2. 后台「服务设置」按需配置端口、匿名/认证、ACL、TLS、持久化。
|
||||
3. 客户端连接:`tcp://<host>:1883` 或 `ws://<host>:8083`;认证在「认证账号」维护,权限在「ACL规则」维护。
|
||||
4. 后台「发布消息」可手动向主题推送;「实时监控」查看连接与吞吐;「转发规则」可将消息桥接到外部 Broker。
|
||||
|
||||
### 2.7 事件与扩展点
|
||||
- 业务侧在 `service/Broker.php` 预埋了 `mqtt_message_published` 事件(参数:`topic/payload/qos/retain/clientId`;`$SYS/` 主题不触发,qos2 在 PUBREL 后触发,确保每消息一次)。插件可监听此事件做业务联动。
|
||||
|
||||
### 2.8 注意事项
|
||||
- 单进程内存态(`worker->count=1`),会话/订阅在内存,保留/离线消息落库。
|
||||
- 卸载会 DROP 全部 11 张表,操作前备份。
|
||||
|
||||
---
|
||||
|
||||
## 3. haonav(网址导航)
|
||||
|
||||
### 3.1 功能简介
|
||||
网站 / 网址导航系统(类 hao123):
|
||||
- 网址分类管理(预置 11 个分类:社交、视频、购物、办公 AI、金融等)。
|
||||
- 网址收录与展示(预置约 138 条国内外站点),前台展示首页分类列表 + 热门站点 + 分类/站点详情。
|
||||
- 后台分类、网址、配置可视化 CRUD(含回收站 / 还原)。
|
||||
- 网址模型可**自动抓取目标页 meta 信息**(标题/关键词/描述,基于 Guzzle + DOMDocument)。
|
||||
|
||||
### 3.2 数据表(`wxapp_haonav_*`)
|
||||
| 表 | 用途 |
|
||||
| --- | --- |
|
||||
| `wxapp_haonav_category` | 网址分类(树形 pid) |
|
||||
| `wxapp_haonav_links` | 网址信息(含全文索引、点击/热门/推荐/状态) |
|
||||
| `wxapp_haonav_config` | 系统配置 KV(初始化为空,需后台添加) |
|
||||
|
||||
> 该插件**无 `Addon.php`**,安装时由框架直接执行 `install.sql`(建表 + 预置分类/站点种子数据)。
|
||||
|
||||
### 3.3 入口
|
||||
- **前台**:`/haonav`(首页,按分类展示)、`/haonav/category/:id`(分类详情)、`/haonav/site/:id`(站点详情)。均免登录。
|
||||
- **后台**:控制器位于 `controller/backend/`(Category / Links / Configs)。
|
||||
|
||||
> ⚠️ **菜单未注入**:`menu.json` 三套均为空数组,后台左侧**没有菜单入口**。需直接通过 URL 访问后台控制器(如 `haonav/backend.category`),或自行在 `menu.json` 补充菜单后再 `addon:manage <name> -a develop` 重注入。
|
||||
|
||||
### 3.4 配置说明
|
||||
- 无 `config.php`;配置通过数据库 `wxapp_haonav_config` 表 + 后台「Configs」控制器以 KV 形式维护。
|
||||
|
||||
### 3.5 使用要点
|
||||
- 前台直接访问即可浏览分类与站点。
|
||||
- 后台「网址」可新增/编辑站点,保存时若填写了 URL 会自动抓取目标页 meta(需安装 `guzzlehttp/guzzle`)。
|
||||
|
||||
### 3.6 注意事项
|
||||
- **安全**:所有前台/后台控制器当前 `noNeedLogin = ['*']`、`noNeedVerify = ['*']`(未强制登录与鉴权),**上线前务必补充权限校验**。
|
||||
- **依赖**:`Links::fetchMeta` 依赖 `guzzlehttp/guzzle`,缺失会抛异常,需 `composer require guzzlehttp/guzzle`。
|
||||
- 存在调试遗留:`Index::read()` 含 `dump();die;`,`Category::updateRolePowers()` 引用了不存在的关联表,建议清理。
|
||||
|
||||
---
|
||||
|
||||
## 4. wxchat(聊天应用)
|
||||
|
||||
### 4.1 功能简介
|
||||
仿微信风格的移动端 IM 社交后端(GatewayWorker):
|
||||
- 实时聊天:单聊/群聊,文本/图片/语音/视频/文件,心跳、认证、撤回、已读回执、敏感词过滤。
|
||||
- 用户体系:短信验证码登录、一键登录、完善资料、邀请码、`TokenService` 鉴权。
|
||||
- 社交关系:好友、关注/粉丝/互关、亲密度等级。
|
||||
- 动态(朋友圈):发布、评论、点赞、收藏、话题、浏览历史。
|
||||
- APP 版本管理(`AppVersionCheck` 中间件:强制/可选更新)。
|
||||
- 启动配置下发:`appConf` / `launchData` 返回聊天/广告/服务器地址等。
|
||||
|
||||
### 4.2 启动命令(常驻进程)
|
||||
```bash
|
||||
php think wxchat:server # 启动 GatewayWorker 聊天服务
|
||||
```
|
||||
> `common.php` 注册了指令 `wxchat:server`;WebSocket 实际地址由 `appConf` 的 `socketUrl`(默认 `wss://dev.xixingwl.cn:2346`)下发。
|
||||
|
||||
### 4.3 数据表(`wxapp_wxchat_*`)
|
||||
共 20 张,含:app_versions、follow、friend、group_members、groups、intimacy、messages、message_recalls、moments(动态)、moment_comments/likes/collects/shares/medias、moment_hashtags(话题)、unread_counts、view_history 等。
|
||||
|
||||
### 4.4 入口与菜单
|
||||
- **前台/API**:控制器继承 `Frontend`,对外 URL 带层前缀 `/wxchat`:对外 API 为 `/wxchat/api/*`(如 `/wxchat/api/login/smsLogin`、`/wxchat/api/index/appConf`),会员中心为 `/wxchat/member/*`,后台为 `/wxchat/backend/*`。插件接口统一返回 JSON。
|
||||
- **后台菜单**:`Addon::install()` 注入**空菜单数组**,且**无 `menu.json`**,因此后台不显示入口。
|
||||
|
||||
### 4.5 配置说明
|
||||
- 已新增 `config.php`,约 34 项配置(功能开关、业务限制、广告、服务器地址、第三方 AppId、版本兼容)现由后台「插件管理 → 配置」(`/backend/addon/setting?addon=wxchat`)可视化维护,实际值存于数据库 `wxapp_addon_config`(独立项 + 缓存)。
|
||||
- `appConf()` 接口以硬编码数组作为默认值,运行时通过 `AddonService::config('wxchat')` 读取数据库配置并按类型(bool/int/string)还原后覆盖默认值下发给端。
|
||||
- 数组类配置(`supportedPlatforms`)保持代码默认值,未纳入后台表单。
|
||||
- **默认值一致性**:`config.php` 中声明的 `value` 应与 `appConf()` 的 `$defaults` 保持一致(开关为 `1/0`、数字为字符串形式)。
|
||||
|
||||
### 4.6 事件与扩展点
|
||||
- `info.php` 声明了事件订阅 `addon\wxchat\subscribe\User`,用于对接会员体系(注册/登录等系统事件),在会员注册/登录后同步 IM 账户或下发通知。
|
||||
- 声明了中间件 `AppVersionCheck`(版本检查)与服务 `service\Service`。
|
||||
|
||||
### 4.7 注意事项
|
||||
- **登录校验**:大量控制器标注 `noNeedLogin = ['*']`,当前几乎全部接口放行,**属开发/调试状态**,上线前需开启登录与签名校验。
|
||||
- `Login::index()` 含直接调用短信宝接口发短信的测试代码(含明文账号密码),上线前删除。
|
||||
- 根目录 `README.md` 实为「消息协议结构设计草稿」,并非使用说明。
|
||||
|
||||
---
|
||||
|
||||
## 5. smsbao(短信宝)
|
||||
|
||||
### 5.1 功能简介
|
||||
对接「短信宝」第三方短信网关(`https://api.smsbao.com/sms`)的短信发送插件。属于**事件订阅 + 短信发送**型插件:
|
||||
- 通过 `subscribe/Smsbao.php` 监听 `SmsSend` 事件,向短信宝接口发送验证码短信。
|
||||
- 短信模板固定:`【妙趣横生】您的短信验证码为:{code}, 五分钟有效,请勿告诉他人`。
|
||||
|
||||
### 5.2 数据表 / 菜单 / 路由
|
||||
- **无 `install.sql`**:不创建任何数据表。
|
||||
- **无 `menu.json`**:`Addon::install()` 注入空菜单,后台无入口。
|
||||
- **无 `route/` 目录**:不提供 HTTP 接口。
|
||||
|
||||
### 5.3 配置说明
|
||||
- 已新增 `config.php` 定义账号(`u`)、密码(`p`)、签名(`sign`)、模板(`template`)四个配置项;实际值存于数据库 `wxapp_addon_config`,可在后台「插件管理 → 配置」中修改,不再硬编码。默认值为迁移占位,请替换为自有账号与签名。
|
||||
|
||||
### 5.4 使用要点(如何触发发短信)
|
||||
在业务侧主动触发自定义事件即可调用本插件发送验证码:
|
||||
```php
|
||||
// 业务代码(如注册/找回密码流程)
|
||||
event('SmsSend', [
|
||||
'mobile' => '13800138000',
|
||||
'code' => '123456',
|
||||
]);
|
||||
```
|
||||
订阅器 `Smsbao` 的事件前缀为 `Sms`(方法 `onSend` → 事件 `SmsSend`)。`SmsGet` / `SmsNotice` / `SmsCheck` 方法当前为占位(直接 `return true`)。
|
||||
|
||||
### 5.5 注意事项(重要)
|
||||
- 短信宝账号/密码、短信签名均为**测试硬编码值**,使用前必须替换为自己的账号/签名,并建议改为从配置项读取(新增 `config.php` 与 `.addonrc` 读写)。
|
||||
- 本插件**不主动监听** `user_register_after` 等系统事件,必须由业务侧显式 `event('SmsSend', $sms)` 触发。
|
||||
- 当前为**半成品**状态:无表、无菜单、无配置、控制器为空壳。
|
||||
|
||||
---
|
||||
|
||||
## 6. demo(演示插件)
|
||||
|
||||
### 6.1 功能简介
|
||||
插件开发规范示例骨架。唯一实际能力:`controller/Index.php::index()` 返回字符串 `这是一个addon\demo 插件应用`,访问 `/demo` 可见。其余控制器/路由/视图均为空占位。
|
||||
|
||||
### 6.2 安装与结构
|
||||
- 无 `Addon.php`(用系统默认 install/uninstall 钩子),无 `install.sql`(不建表)。
|
||||
- 含示例 `menu.json`(frontend/member/backend 均为「一级/二级」占位菜单)、`route/app.php`(仅引入 `Route` 门面占位)。
|
||||
|
||||
### 6.3 用途
|
||||
- 作为新插件脚手架参考;可基于 `php think addon:make <name> -a build` 生成更完整的骨架。
|
||||
|
||||
---
|
||||
|
||||
## 附:命令速查
|
||||
|
||||
| 命令 | 归属 | 作用 |
|
||||
| --- | --- | --- |
|
||||
| `php think mqttbroker:start` | mqttbroker | 启动 MQTT Broker(常驻) |
|
||||
| `php think mqttbroker:acltest` | mqttbroker | ACL 规则测试 |
|
||||
| `php think wxchat:server` | wxchat | 启动 GatewayWorker 聊天服务(常驻) |
|
||||
| `php think workerman:gateway` | 框架 | GatewayWorker 通用网关 |
|
||||
| `php think addon:manage <name> -a develop` | 框架 | 开发者模式安装(建表+菜单+启用,可重跑) |
|
||||
|
||||
## 附:状态与风险提示
|
||||
|
||||
| 插件 | 成熟度 | 上线前需处理 |
|
||||
| --- | --- | --- |
|
||||
| blog | 完整可用 | 无需特殊处理 |
|
||||
| mqttbroker | 完整可用 | 生产用 Linux 守护;按需配置认证/ACL/TLS |
|
||||
| haonav | 基本可用 | 后台菜单未注入;补登录鉴权;装 guzzle |
|
||||
| wxchat | 开发态 | 开启登录/签名校验;删除测试发短信代码;补后台菜单 |
|
||||
| smsbao | 半成品 | 替换硬编码账号/签名;改为配置驱动;补触发逻辑 |
|
||||
| demo | 脚手架 | 仅作示例,不用于生产 |
|
||||
+505
@@ -0,0 +1,505 @@
|
||||
# Ywxapp 插件应用开发指南
|
||||
|
||||
> 适用框架:Ywxapp(基于 ThinkPHP 8 的多应用框架)
|
||||
> 本文以 `mqttbroker` 插件为实例,说明从脚手架到打包上线的完整流程。
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
Ywxapp 的「插件」本质上就是一个 **ThinkPHP 多应用(Multi-App)**:
|
||||
|
||||
- 插件源码目录:`addon/<name>/`
|
||||
- 插件命名空间:`addon\<name>\...`
|
||||
- 访问 URL 前缀:`/<name>/...`(例如 `/mqttbroker/backend/index`)
|
||||
|
||||
核心支撑类:
|
||||
|
||||
| 类 / 文件 | 职责 |
|
||||
| --- | --- |
|
||||
| `ywxapp\addon` | 插件基类,定义 `install()` / `uninstall()` 抽象钩子 |
|
||||
| `ywxapp\service\AddonService` | 安装 / 卸载 / 打包 / 开发者模式 |
|
||||
| `ywxapp\service\AppService` | 框架 boot 时扫描 `addon/`,加载已启用插件的事件、中间件、服务 |
|
||||
| `config/addon.php` | 插件机制全局配置(路径、优先级、白名单等) |
|
||||
|
||||
一个插件 = 一个自带路由、控制器、模型、视图、配置、命令、静态资源的独立应用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 插件目录结构
|
||||
|
||||
以 `mqttbroker` 为例:
|
||||
|
||||
```
|
||||
addon/mqttbroker/
|
||||
├── Addon.php # 安装/卸载钩子(必须)
|
||||
├── info.php # 插件元信息(必须)
|
||||
├── menu.json # 后台/会员/前台菜单定义
|
||||
├── install.sql # 建表/种子 SQL(安装时自动导入)
|
||||
├── common.php # 应用公共文件(随应用自动加载,可选)
|
||||
├── config.php # 设置表单字段定义(可选,仅当插wophp 文件,
|
||||
# 统一在 info.php 的 events / middleware / services 数组里声明(详见 3.1)。
|
||||
# - config.php 非必须:无可配置项的插件直接不建即可。
|
||||
├── controller/ # 控制器:admin/、api/ ...
|
||||
├── model/ # 模型
|
||||
├── service/ # 业务服务类
|
||||
├── protocol/ # 自定义协议(如 MQTT)
|
||||
├── view/ # 视图模板(backend/admin/*.html)
|
||||
├── route/
|
||||
│ └── app.php # 插件路由
|
||||
└── static/ # 静态资源(打包时自动迁出到 public/static/addon/mqttbroker/)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心文件详解
|
||||
|
||||
### 3.1 `info.php`(元信息,必须)
|
||||
|
||||
```php
|
||||
return [
|
||||
'name' => 'mqttbroker', // 小写字母,唯一标识,须与目录名一致
|
||||
'title' => 'MQTT代理',
|
||||
'intro' => '...',
|
||||
'author' => '',
|
||||
'website' => '',
|
||||
'version' => '2.1.0', // 语义化版本 x.y.z
|
||||
'state' => 1, // 0=禁用 1=启用(由框架读取控制加载)
|
||||
'url' => '/mqttbroker/admin',
|
||||
'license' => '', // 付费插件可声明 license=>true,配合 addon_license_check
|
||||
'config' => [], // boot 时已合并进容器(见 3.5)
|
||||
'events' => [], // 随应用加载:app()->loadEvent(...)
|
||||
'middleware' => [], // 随应用加载:app()->middleware->import(...)
|
||||
'services' => [], // 随应用加载:app()->bind(...)
|
||||
];
|
||||
```
|
||||
|
||||
- 框架 `AppService::loadAddonRelevant()` 在 boot 期扫描 `addon/`,仅对 `state=1` 的插件加载其 `events/middleware/services`。
|
||||
- 安装流程会写入 `install_time`,并管理 `state`。
|
||||
|
||||
> **事件 / 中间件 / 服务在哪里声明?** 统一在 `info.php` 对应数组里填写(见下方示例)。虽然 ThinkPHP 的 MultiApp 在分发到该插件应用时也会自动加载 `event.php` / `middleware.php` / `provider.php`,但插件体系的**推荐入口是 `info.php`**:它在 boot 期由 `AppService::loadAddonRelevant()` 读取,对所有启用插件生效,且不依赖当前请求是否命中该插件(时机早、行为一致)。因此插件**无需创建** `event.php` / `middleware.php` 文件。
|
||||
> - `events`:事件监听映射,如 `['MqttClientConnect' => \addon\mqttbroker\listener\Connect::class]`(等价于 `Event::listen`)。
|
||||
> - `middleware`:要注册进应用中间件栈的类,如 `[\addon\mqttbroker\middleware\MqttAuth::class]`。
|
||||
> - `services`:服务容器绑定(TP 服务类),如 `['mqttBroker' => \addon\mqttbroker\service\Broker::class]`。
|
||||
>
|
||||
> 没有要注册的就保留空数组 `[]`(你的 `mqttbroker` 当前即如此)。这些数组仅在 `state=1`(启用)时才会被加载。
|
||||
|
||||
### 3.2 `Addon.php`(钩子,必须实现 install/uninstall)
|
||||
|
||||
```php
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
namespace addon\mqttbroker;
|
||||
|
||||
use think\facade\Db;
|
||||
use ywxapp\AddonBase;
|
||||
|
||||
class Addon extends addon
|
||||
{
|
||||
// 安装钩子:表由 install.sql 自动创建,通常无需额外处理
|
||||
public function install()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 卸载钩子:清理本插件数据表与菜单残留(DROP/ALTER 必须放这里!)
|
||||
public function uninstall()
|
||||
{
|
||||
Db::execute("DROP TABLE IF EXISTS `wxapp_mqttbroker_connection`");
|
||||
// ... 其余表
|
||||
Db::execute("DELETE FROM wxapp_admin_power WHERE addon='mqttbroker'");
|
||||
Db::execute("DELETE FROM wxapp_user_rule WHERE name LIKE 'mqttbroker:%'");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ **重要事实**:`enable/disable` 在 `ywxapp\addon` 中**不是**抽象方法;框架 `AddonService::enable()/disable()` 仅切换 `info.php` 的 `state` 值,**不会**回调插件的 `enable()/disable()` 钩子。因此不要把"启用/禁用时需要执行的逻辑"写在钩子里,菜单显隐完全由 `state` 决定。
|
||||
|
||||
### 3.3 `install.sql`(建表,自动导入)
|
||||
|
||||
安装时由 `AddonService::importsql()` 自动执行,规则如下:
|
||||
|
||||
- **白名单限制**:仅允许 `CREATE TABLE` 与 `INSERT` 语句;`SET NAMES` / `SET FOREIGN_KEY_CHECKS` 等会被跳过(仅记日志)。
|
||||
- **表命名**:`wxapp_<插件名>_*`,与核心表前缀保持一致。
|
||||
- **前缀替换**:SQL 中的 `__PREFIX__` 会被替换为数据库实际前缀(若直接写死 `wxapp_` 也可,但需与数据库配置一致)。
|
||||
- **建议全部用 `CREATE TABLE IF NOT EXISTS`**,便于重跑。
|
||||
- **破坏性语句(DROP / ALTER / DELETE)必须放进 `Addon::uninstall()`,绝不能写进 install.sql**(白名单会拒绝执行)。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS `wxapp_mqttbroker_connection` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`client_id` varchar(191) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_client_id` (`client_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MQTT客户端连接表';
|
||||
```
|
||||
|
||||
### 3.4 `menu.json`(菜单,自动注入)
|
||||
|
||||
```json
|
||||
{
|
||||
"backend": [
|
||||
{
|
||||
"name": "mqttbroker",
|
||||
"title": "MQTT代理",
|
||||
"icon": "fa fa-sitemap",
|
||||
"type": 1,
|
||||
"sort": 60,
|
||||
"status": 1,
|
||||
"child": [
|
||||
{ "name": "mqttbroker/index", "title": "概览", "icon": "fa fa-dashboard", "type": 2, "sort": 1, "route": "/mqttbroker/backend/index" },
|
||||
{ "name": "mqttbroker/stats", "title": "实时监控", "type": 2, "sort": 8, "route": "/mqttbroker/backend/stats" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"member": [],
|
||||
"frontend": []
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
- 顶层三类:`backend`(后台)、`member`(会员中心)、`frontend`(前台)。
|
||||
- 每项:`name`(权限标识,首个菜单须与插件标识同名)、`title`、`icon`、`type`(1=菜单 2=按钮)、`sort`、`route`(实际访问 URL)、`child`(子项)。
|
||||
- 框架会把 `name` 处理为 `<addon>:<type>:<name>`,写入 `wxapp_admin_power`(后台)或 `wxapp_user_rule`(会员/前台)。
|
||||
- 安装时 `createMenu()` 注入;卸载/重装时先清理旧菜单再重建(保证幂等)。
|
||||
|
||||
### 3.5 `config.php`(后台设置表单,**可选**)
|
||||
|
||||
> **重要:`config.php` 不是必须文件,框架也不会自动读取/渲染它。** 它只是一份「设置表单字段定义」,是否生效完全取决于插件**自己有没有实现一个后台设置页**去 `include` 它。
|
||||
> - 若插件**没有可配置项**(如 `wxchat`),就**不要创建** `config.php`(早期脚手架生成的 `['status'=>true]` 属于无效占位,已清理)。
|
||||
> - 若插件**需要后台可配置**(如 `mqttbroker`),则用「字段描述符数组」格式编写,并在插件内自建设置页读取它(见下方"如何真正用起来")。
|
||||
|
||||
```php
|
||||
return [
|
||||
[
|
||||
'name' => 'port',
|
||||
'title' => '监听端口',
|
||||
'type' => 'number',
|
||||
'value' => '1883',
|
||||
'tip' => 'Broker 监听的 TCP 端口',
|
||||
],
|
||||
[
|
||||
'name' => 'ssl_enabled',
|
||||
'title' => '启用TLS',
|
||||
'type' => 'select',
|
||||
'options' => ['0' => '关闭', '1' => '开启'],
|
||||
'value' => '0',
|
||||
],
|
||||
// ...
|
||||
];
|
||||
```
|
||||
|
||||
**字段格式**:每项为一个描述符 `['name'=>标识, 'title'=>显示名, 'type'=>控件类型, 'value'=>默认值, 'tip'=>提示, 'options'=>下拉项]`。`type` 常用 `string / number / select / textarea`。
|
||||
|
||||
**如何真正用起来(以 `mqttbroker` 为例)**:`config.php` 是「设置表单字段定义」,框架统一把配置以**独立项**写入数据库表 `wxapp_addon_config`(`addon`+`name` 唯一,每行一个配置项),并配合缓存(键 `addon_config_<name>`,默认 1 小时)。读取/保存统一走 `AddonService::config()`:
|
||||
|
||||
1. **后台通用配置页 + 列表快捷入口**:后台「插件管理」列表页(`app/backend/view/backend/addon/index.html`)每行带「配置」按钮(仅对含 `config.php` 的插件显示),直达通用页(`app/backend/controller/addon.php::setting`)。通用页自动按 `config.php` 渲染表单、AJAX 保存写库并清缓存。**无需每个插件自建页面**,没有 `config.php` 的插件提示「无可配置项」。
|
||||
2. 插件也可自建设置页(如 mqttbroker 的 `controller/backend/MqttBroker.php::setting/saveSetting`),只需调用:
|
||||
```php
|
||||
$def = include ADDON_PATH . 'mqttbroker' . DIRECTORY_SEPARATOR . 'config.php';
|
||||
$saved = \ywxapp\service\AddonService::config('mqttbroker'); // 读数据库配置(带缓存)
|
||||
// 保存:\ywxapp\service\AddonService::config('mqttbroker', input('post.'));
|
||||
```
|
||||
3. 运行期读取:业务代码用 `AddonService::config($name)`、基类 `getConfig()` 或助手 `get_addon_config($name)` 取值。建议业务默认值在代码里用 `array_merge($defaults, $saved)` 叠加,保证未配置时使用默认值(`config.php` 的 `value` 仅作表单初始显示)。
|
||||
|
||||
**三条容易混淆的配置入口,务必区分**:
|
||||
|
||||
| 入口 | 存储位置 | 加载时机 | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| `config.php` + 数据库 | `wxapp_addon_config`(独立项,每行一个配置) | 后台通用配置页或插件自建设置页调用 `AddonService::config()` | 管理员可视化配置(端口、开关等),带缓存 |
|
||||
| `info.php` 的 `config`/`events`/`middleware`/`services` | `info.php` | boot 期由 `AppService` 加载 | 事件/中间件/服务注册 |
|
||||
| `config/` 目录下的 `*.php` | 各配置文件 | MultiApp 分发到该插件时加载 | 标准 TP 应用级配置 |
|
||||
|
||||
> 另:框架基类 `ywxapp\addon::getConfig()` 也能读 `config.php`(同样合并 `.addonrc`),但仅当你的类继承自它时可用;`mqttbroker` 的做法是直接 `include` + `AddonService::config()`,二者等效,按需选一即可。
|
||||
|
||||
### 3.6 `route/app.php`(路由)
|
||||
|
||||
```php
|
||||
<?php
|
||||
use think\facade\Route;
|
||||
|
||||
// 后台
|
||||
Route::group('admin', function () {
|
||||
Route::get('index', 'backend/MqttBroker/index');
|
||||
Route::post('publish', 'backend/MqttBroker/doPublish');
|
||||
Route::get('stats', 'backend/MqttBroker/stats');
|
||||
});
|
||||
// 对外 API
|
||||
Route::group('api', function () {
|
||||
Route::get('status', 'api/Mqtt/status');
|
||||
Route::post('publish', 'api/Mqtt/publish');
|
||||
});
|
||||
```
|
||||
|
||||
- 框架以**多应用**方式加载插件:`addon/<name>` 即应用 `<name>`,URL 前缀为 `/<name>/`。
|
||||
- 控制器引用格式:`layer/Controller/action` → 对应文件 `controller/<layer>/<Controller>.php`(如 `backend/MqttBroker/index` → `controller/backend/MqttBroker.php::index()`)。
|
||||
- 同目录下的 `common.php` 会随应用自动加载。
|
||||
- ⚠️ 插件的事件监听、中间件、服务容器绑定**推荐通过 `info.php` 的 `events` / `middleware` / `services` 数组声明**(boot 期由 `AppService::loadAddonRelevant()` 读取,见 3.1),而**不要**依赖 `event.php` / `middleware.php` 文件——后者仅由 MultiApp 在请求命中该插件应用时才分发加载,时机较晚且与插件统一加载机制不一致。所以插件**无需创建**这两个文件;要注册就写进 `info.php`,不需要则留空数组 `[]`。
|
||||
|
||||
### 3.7 控制器 / 模型 / 视图 / 服务
|
||||
|
||||
- 命名空间:
|
||||
- 后台控制器:`addon\<name>\controller\admin`,继承 `ywxapp\controller\AddonBackend`
|
||||
- API 控制器:`addon\<name>\controller\api`,继承 `ywxapp\controller\ApiController`
|
||||
- 模型:`addon\<name>\model\Xxx`
|
||||
- 服务:`addon\<name>\service\Xxx`
|
||||
- 鉴权开关(开发期可放开):
|
||||
```php
|
||||
protected $noNeedLogin = ['*'];
|
||||
protected $noNeedVerify = ['*'];
|
||||
```
|
||||
- 视图:后台模板放 `view/backend/admin/*.html`,用 `$this->fetch('admin/index')` 渲染(路径前缀由框架视图配置决定)。
|
||||
|
||||
### 3.8 `command/`(命令行 / 常驻进程)
|
||||
|
||||
```php
|
||||
// config/console.php 注册
|
||||
"mqttbroker:start" => \addon\mqttbroker\command\MqttBroker::class,
|
||||
```
|
||||
|
||||
- 命令行放在 `command/`,在 `config/console.php` 注册。
|
||||
- 长驻进程(如 Broker)基于 Workerman:命令内 `Worker::runAll()`。
|
||||
- Windows 与 Linux 行为差异:单 PHP 进程无法多 `Worker` 实例,需用 `DIRECTORY_SEPARATOR === '\\'` 做平台降级(如 WS 监听仅 Linux 实网生效)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 插件生命周期
|
||||
|
||||
```
|
||||
框架启动 (AppService::boot)
|
||||
└─ loadAddonRelevant(): 扫描 addon/,对 state=1 的插件加载 events/middleware/services
|
||||
└─ 加载 ywxapp/helper.php(提供 addon_url / hook / get_addon_* 等全局函数)
|
||||
|
||||
安装 (addon:manage -a develop 或 后台离线/在线安装)
|
||||
└─ executeInstall(): 写 info(state=0) → install 钩子 → 注入菜单 → importsql
|
||||
(离线/在线安装默认 state=0,需手动启用;addon:manage -a develop 直接置 state=1)
|
||||
|
||||
启用 / 禁用 (后台 toggle)
|
||||
└─ enable()/disable() 钩子(可重写,返回 false 阻断)→ 再修改 info.php 的 state
|
||||
默认实现会联动 Menu::enable/disable 同步菜单可见性(见 Addon.php 生成模板)
|
||||
|
||||
升级 (后台升级)
|
||||
└─ upgrade($currentVersion) 钩子(可重写,用于数据/配置迁移)→ 清理配置缓存
|
||||
|
||||
卸载 (后台卸载)
|
||||
└─ uninstall() 钩子(DROP 表 / 清菜单)→ 删除插件目录
|
||||
```
|
||||
|
||||
> 生命周期钩子统一由 `AddonService::callAddonHook($method)` 调用插件主类
|
||||
> `addon\<name>\Addon` 的同名方法(`install`/`uninstall`/`enable`/`disable`/`upgrade`)。
|
||||
> 基类 `ywxapp\addon` 已为 `enable`/`disable`/`upgrade` 提供默认空实现,**只有 `install`/`uninstall` 仍是抽象方法,必须实现**。
|
||||
|
||||
---
|
||||
|
||||
## 4.1 钩子体系(可插拔扩展点)
|
||||
|
||||
直接复用 ThinkPHP 自带的事件机制,**无需框架额外封装**:
|
||||
|
||||
- 业务侧在关键节点用 ThinkPHP 内置全局助手 `event('事件名', $params)` 触发(即 `Event::trigger` 的全局封装,无需额外封装)。
|
||||
- 插件侧在 `info.php['events']['listen']` 中声明监听器即可挂接,框架 boot 期 `loadEvent` 会自动注册,无需改框架源码。
|
||||
|
||||
```php
|
||||
// addon/<name>/info.php
|
||||
'events' => [
|
||||
'listen' => [
|
||||
'user_login_after' => [\addon\xxx\listener\UserLogin::class],
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
```php
|
||||
// addon/<name>/listener/UserLogin.php
|
||||
namespace addon\xxx\listener;
|
||||
class UserLogin
|
||||
{
|
||||
public function handle($user)
|
||||
{
|
||||
// $user 为登录成功的会员模型实例
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
业务侧在关键节点预埋触发点(完整清单):
|
||||
|
||||
### 会员 / 认证(`ywxapp/library/User.php`)
|
||||
|
||||
| 事件名 | 触发时机 | 参数 | 代码位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `user_register_after` | 会员注册成功 | 会员模型实例 `$user` | `User::register()` 事务提交前 |
|
||||
| `user_login_after` | 会员/API 登录成功 | 会员模型实例 `$user` | `User::login()` 末尾 |
|
||||
| `user_logout_after` | 会员登出成功 | 会员模型实例 `$user` | `User::logout()` 末尾 |
|
||||
| `user_changepwd_after` | 会员修改密码成功 | 会员模型实例 `$user` | `User::changepwd()` 事务提交前 |
|
||||
|
||||
> 监听器 `handle($user)` 收到的 `$user` 均为 `ywxapp\model\User` 实例。
|
||||
|
||||
### 内容 / 文章(`app/api/controller/v1/Article.php`)
|
||||
|
||||
| 事件名 | 触发时机 | 参数 | 代码位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `article_create_after` | 文章创建成功 | 文章模型实例 `$article` | `Article::create()` 成功后 |
|
||||
| `article_update_after` | 文章更新成功 | 文章模型实例 `$article` | `Article::update()` 保存后 |
|
||||
| `article_delete_after` | 文章删除成功 | 文章模型实例 `$article` | `Article::delete()` 删除后 |
|
||||
|
||||
### MQTT 消息(`addon/mqttbroker/service/Broker.php`)
|
||||
|
||||
| 事件名 | 触发时机 | 参数(数组) | 代码位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `mqtt_message_published` | 客户端发布消息被 Broker 接受并分发 | `['topic'=>string, 'payload'=>string, 'qos'=>int, 'retain'=>bool, 'clientId'=>string]` | `Broker::handlePublish()`(qos0/1)与 `Broker::handlePubRel()`(qos2)|
|
||||
|
||||
> 说明:`$SYS/` 开头的内部指标主题**不会**触发该事件(避免每秒指标造成噪声);
|
||||
> qos2 消息在 PUBREL 确认后才真正分发,故事件在 `handlePubRel` 中触发,保证每一条消息仅触发一次。
|
||||
|
||||
### 全局异常(`ywxapp/handler/ExceptionHandle.php`)
|
||||
|
||||
| 事件名 | 触发时机 | 参数 | 代码位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `app_exception` | 应用抛出异常 | `['exception'=>\Throwable, 'ignoreReport'=>bool]` | `ExceptionHandle::report()` |
|
||||
| `app_exception_report` | 异常进入上报流程 | 同上 | `ExceptionHandle::report()` |
|
||||
|
||||
### 插件市场 / 订单(`app/api/controller/v1/Addon.php`)
|
||||
|
||||
| 事件名 | 触发时机 | 参数(数组) | 代码位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `order_paid` | 插件购买订单支付完成(已签发授权 + 写入开发者分红账本) | `['order'=>订单模型, 'aid'=>int 插件id, 'uid'=>int 用户id]` | `Addon::completeOrder()` 末尾 |
|
||||
|
||||
> 插件可监听此钩子实现"发货 / 开通权益 / 发送通知"等购买后联动。
|
||||
|
||||
### 插件生命周期(`ywxapp/service/AddonService.php`)
|
||||
|
||||
| 事件名 | 触发时机 | 参数(数组) | 代码位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `addon_install_after` | 插件安装成功(已写 info 状态、注入菜单、导入 SQL) | `['name'=>string 插件标识, 'info'=>array 插件信息]` | `AddonService::executeInstall()` 提交后 |
|
||||
| `addon_uninstall_after` | 插件卸载完成(目录与数据表已清理) | `['name'=>string 插件标识]` | `AddonService::uninstall()` 末尾 |
|
||||
| `addon_enable_after` | 插件被启用 | `['name'=>string 插件标识]` | `AddonService::enable()` 末尾 |
|
||||
| `addon_disable_after` | 插件被禁用 | `['name'=>string 插件标识]` | `AddonService::disable()` 末尾 |
|
||||
|
||||
> 注意:与插件主类自身的 `install()/uninstall()/enable()/disable()` 生命周期方法(由 `callAddonHook` 调用)不同,
|
||||
> 上述 `*_after` 是**全局事件**——任意插件均可监听以对"其它插件"的状态变化做出联动。
|
||||
|
||||
> 新增业务钩子点只需在对应位置调用 `event('your_event', $data)` 即可,
|
||||
> 插件侧用同名事件名在 `info.php['events']['listen']` 中监听,无需任何注册代码。
|
||||
|
||||
---
|
||||
|
||||
## 4.2 辅助函数层(`ywxapp/helper.php`)
|
||||
|
||||
由 `AppService::register()` 在框架启动时自动加载,全局可用:
|
||||
|
||||
| 函数 | 作用 |
|
||||
| --- | --- |
|
||||
| `addon_url($name, $url='', $vars=[])` | 生成插件访问地址 `/<name>/<url>`(修复了原先未定义的致命 bug) |
|
||||
| `get_addon_instance($name)` | 获取插件主类实例 |
|
||||
| `get_addon_info($name, $force=false)` | 读取插件元信息(优先缓存) |
|
||||
| `get_addon_config($name, $force=false)` | 读取插件配置(优先缓存) |
|
||||
| `get_appmarket_addon_list($onlyEnabled=false)` | 扫描插件目录,返回元信息列表 |
|
||||
|
||||
> 事件触发统一使用 ThinkPHP 自带的 `event('事件名', $params)` 助手,无需框架额外封装。
|
||||
|
||||
---
|
||||
|
||||
## 5. 开发者模式(本地边改边测)⭐
|
||||
|
||||
### 痛点
|
||||
|
||||
当你的插件源码**直接放在 `addon/<name>/`** 本地改代码时,框架的「压缩包安装」流程要求目标目录**不存在**才允许安装(`validateAddonInfo` 会抛 "Addon already exists")。结果是:目录在、代码能跑,但 `install.sql`(建表)和 `menu.json`(菜单)**从未执行过**,卡在中间态,无法测试。
|
||||
|
||||
### 解决方案:`addon:manage -a develop` 命令
|
||||
|
||||
```bash
|
||||
php think addon:manage mqttbroker -a develop
|
||||
```
|
||||
|
||||
它在**原地**执行标准安装步骤,**不需要先打包 zip**:
|
||||
|
||||
1. 置 `info.php` 的 `state = 1`(直接启用)
|
||||
2. 调用 `install` 钩子
|
||||
3. 清理旧菜单 → 重新注入 `menu.json`
|
||||
4. 执行 `importsql()`(建表)
|
||||
|
||||
**幂等、可反复执行**:改了 `install.sql` 或 `menu.json` 后,再跑一次即可生效(`CREATE TABLE IF NOT EXISTS` + 菜单先清后建,不会报错)。
|
||||
|
||||
> 开发环境若需跳过远程授权校验:设置 `app_debug=1` 且 `ywxapp.unknownsources=1`(`AddonService::valid()` 会直接放行)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 打包与发布上线
|
||||
|
||||
### 本地打包
|
||||
|
||||
通过**后台「插件管理 → 打包」按钮**(调用 `AddonService::package()`),生成:
|
||||
|
||||
```
|
||||
addon/temp/mqttbroker-2.1.0.zip
|
||||
```
|
||||
|
||||
打包内容 = 整个插件目录 + `public/static/addon/<name>/`(若存在)。注意:打包时 `static/` 会被迁出到 `public/static/addon/<name>/` 并删除插件目录内的 `static` 副本。
|
||||
|
||||
### 上传到服务器
|
||||
|
||||
1. 服务器后台「插件管理 → 离线安装」,上传该 zip。
|
||||
2. 服务器侧 `local()` 流程:校验 → 解压到 `addon/<name>/` → `install` 钩子 → 注入菜单 → `importsql()`。
|
||||
3. 安装后默认 `state=0`(禁用),在后台**启用**插件即可。
|
||||
|
||||
### 注意事项
|
||||
|
||||
- 离线安装要求服务器上 `addon/<name>` **不存在**;重装需先卸载。
|
||||
- `install.sql` 白名单限制同上(仅 `CREATE TABLE` / `INSERT`)。
|
||||
- 静态资源访问路径:`/static/addon/<name>/...`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 完整开发流程(实例)
|
||||
|
||||
```bash
|
||||
# 1. 脚手架(或手写目录结构)
|
||||
php think addon:make mqttbroker -a build
|
||||
|
||||
# 2. 编写 info.php / Addon.php / install.sql / menu.json / config.php / route/app.php / 控制器 / 模型
|
||||
|
||||
# 3. 开发者模式:建表 + 注入菜单 + 启用(只需一次,之后改 SQL/菜单再跑)
|
||||
php think addon:manage mqttbroker -a develop
|
||||
|
||||
# 4. 启动服务 / 调试
|
||||
php think mqttbroker:start
|
||||
|
||||
# 5. 迭代:改业务代码即时生效;
|
||||
# 改了 install.sql 或 menu.json → 再跑一次 addon:manage <name> -a develop
|
||||
|
||||
# 6. 打包
|
||||
# 后台「插件管理 → 打包」→ 下载 mqttbroker-2.1.0.zip
|
||||
|
||||
# 7. 部署:服务器后台离线安装 → 启用
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 常用命令速查
|
||||
|
||||
| 命令 | 作用 |
|
||||
| --- | --- |
|
||||
| `php think addon:manage <name> -a develop` | 开发者模式安装(建表+菜单+启用,免打包,可重跑) |
|
||||
| `php think addon:make <name> -a build` | 生成插件脚手架(统一命令 `addon:make`,`-a` 指定动作) |
|
||||
| `php think addon:make <name> -a clear` | 清理插件缓存/临时文件 |
|
||||
| `php think addon:make <name> -a controller --name=<类>` | 生成控制器(另有 `--api`/`--plain`) |
|
||||
| `php think addon:make <name> -a model --name=<类>` | 生成模型 |
|
||||
| `php think addon:make <name> -a <action> --name=<类>` | 其余生成器:`middleware`/`validate`/`event`/`listener`/`service`/`subscribe` |
|
||||
| (后台按钮)打包 | 生成 `<name>-<version>.zip` |
|
||||
| `php think mqttbroker:start` | 示例插件:启动 Broker(具体命令名随插件自定义) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 常见坑
|
||||
|
||||
1. **install.sql 只允许 CREATE TABLE / INSERT**;DROP / ALTER / DELETE 必须放 `Addon::uninstall()`。
|
||||
2. **enable/disable 不触发插件钩子**,仅切换 `info.php` 的 `state`。
|
||||
3. **表前缀**:install.sql 用 `__PREFIX__` 或固定 `wxapp_`,需与数据库配置一致。
|
||||
4. **路由控制器引用格式**:`layer/Controller/action` 对应 `controller/<layer>/<Controller>.php`。
|
||||
5. **卸载会 DROP 表且删除目录**,生产环境操作前务必备份。
|
||||
6. **本地改了 install.sql 没生效**:忘了重跑 `addon:manage <name> -a develop`(它幂等,放心跑)。
|
||||
7. **Windows 下多 Worker / WebSocket 仅 Linux 实网可验**:用 `DIRECTORY_SEPARATOR === '\\'` 做平台降级。
|
||||
8. **事件/中间件/服务不要写进 `event.php` / `middleware.php` 文件**:插件体系通过 `info.php` 的 `events` / `middleware` / `services` 数组声明(boot 期由 `AppService` 加载),插件目录里**无需创建**这两个文件;命令行则放进 `command/` 并在 `config/console.php` 注册(不能省略)。
|
||||
9. **`config.php` 是可选的,框架不会自动读它**:只有插件自建后台设置页去 `include` 它才生效(见 3.5)。没有可配置项就别建(`['status'=>true]` 这类占位无意义,会误导)。默认值别忘了与运行期代码里的默认值保持一致。
|
||||
@@ -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 等价能力)。
|
||||
@@ -0,0 +1,129 @@
|
||||
# 插件机制对比与差距分析(本项目 vs FastAdmin)
|
||||
|
||||
> 配套文档:《FastAdmin 插件机制参考》(`docs/FastAdmin插件机制参考.md`)、《插件开发指南》(`docs/插件开发指南.md`)。
|
||||
> 本文基于通读双方源码得出,目的是:**看清架构差异 → 定位真实短板 → 给出可落地的完善建议**。
|
||||
|
||||
---
|
||||
|
||||
## 0. 一句话结论
|
||||
|
||||
本项目与 FastAdmin 是**两种不同的插件架构**:
|
||||
|
||||
| | 架构路线 | 插件本质 |
|
||||
| --- | --- | --- |
|
||||
| **FastAdmin** | Route 分发 + copydirs 半集成 | 挂在主框架上的"模块",通过统一路由 `addon/{addon}/{...}` 访问,可把文件复制注入主框架 |
|
||||
| **本项目(ywxapp)** | **MultiApp 多应用** | 每个插件是一个**完整独立的 ThinkPHP8 应用**(命名空间 `addon\<name>`),通过 `/<addon>/<controller>/<action>` 访问 |
|
||||
|
||||
所以"是否达到 FastAdmin 水平"不能简单对标——你的**路由/隔离模型比 FastAdmin 更强更干净**(无需 copydirs 往主框架塞文件),但在**钩子体系、辅助函数层、生命周期完整度**上确有缺口。
|
||||
|
||||
---
|
||||
|
||||
## 1. 逐项对比表
|
||||
|
||||
| 能力 | FastAdmin | 本项目 ywxapp | 评价 |
|
||||
| --- | --- | --- | --- |
|
||||
| **元信息** | `info.ini`(INI) | `info.php`(PHP 数组,含 `events/middleware/services/license`) | ✅ 各有取舍,PHP 数组更灵活 |
|
||||
| **加载入口** | 框架启动读 `get_addon_autoload_config` 生成的 hooks/route 配置 | `AppService::boot()→loadAddonRelevant()`,boot 期扫描 `addon/*/info.php`,仅 `state=1` 加载 | ✅ 时机早、统一 |
|
||||
| **路由分发** | `\think\addon\Route::execute` 解析 `addon/{addon}/{controller}/{action}` + `_empty` 兜底 | `MultiApp` 把 `/<addon>/...` 识别为独立应用,走标准 TP 路由(支持插件内 `route/app.php`) | ✅ 本项目更强:每插件可有完整 `config/`、`route/`、`event.php`、`middleware.php`、`provider.php` |
|
||||
| **自动加载** | 动态:helper `get_addon_class` + 生成 hooks/route 配置 | 静态 PSR-4 `addon\: addon`(composer.json) | ✅ 本项目更简单稳健 |
|
||||
| **扩展注册(事件/中间件/服务)** | 钩子体系(见下)+ 行为事件 | `info.php` 的 `events/middleware/services` 三数组,boot 期 `loadEvent/middleware->import/bind` | ⚠️ 能用,但**无"钩子"抽象** |
|
||||
| **钩子 / 行为事件** | ⭐ 核心:**方法即钩子**(反射差集自动注册)+ `hook()`/`Hook::listen` + 前端 `bootstrap.js` 合并 | ❌ **缺失**。只有原生 TP Event(需在 info.php 显式声明 listener 类);`BaseAddon::registerHooks()` 是空壳且**从未被调用** | ❌ **最大短板** |
|
||||
| **生命周期** | install/uninstall(抽象)+ enable/disable/upgrade(可选)+ importsql + copydirs + refresh + 冲突检测 + 授权 | `AddonService`:install/local/uninstall/enable/disable/package/backupData/restoreBackup/importsql/createMenu;基类 `addon` 仅抽象 install/uninstall | 🔶 大体齐全,enable/disable 钩子未打通到主类 |
|
||||
| **后台菜单注入** | `install()` 内写 `admin_rule` 等表 | `createMenu()` 读 `menu.json` → 写 `admin_power`(后台)/ 用户规则(member/frontend),先清后建幂等 | ✅ 对等甚至更规整(区分三端) |
|
||||
| **业务配置 config.php** | 字段描述符;`set_addon_config` **写回 config.php 文件** | 字段描述符;已改为**数据库表 + 缓存**(`service/Config.php`),`config.php` 仅作默认值来源 | ✅ 本项目更合理(不改插件文件) |
|
||||
| **前台控制器基类** | `\think\addon\Controller`(内置 Auth/多语言/`__ADDON__`/noNeedLogin) | `AddonBackend → BaseController`(`auth->verifyAuth`),走 MultiApp 的标准应用控制器 | 🔶 有鉴权,但缺"资源前缀/多语言"等便利封装 |
|
||||
| **辅助函数层** | 完整:`get_addon_info/config/list/instance/class`、`addon_url`、`hook`、`get_addon_tables` 等 | ❌ **几乎缺失**。`addon.php:105` 调用了 `addon_url()`,但**全项目无此函数定义**(疑似潜在致命错误,见 §3.2) | ❌ 短板 + 隐藏 bug |
|
||||
| **冲突检测/全局文件** | `noconflict/getGlobalFiles`(因 copydirs 需要) | 无(MultiApp 隔离,**本就不需要**) | ✅ N/A,非缺陷 |
|
||||
| **在线插件市场/授权** | 远程下载/升级 + 域名授权 `md5(md5(domain).license)` | `package/local` 打包与离线安装;`info.php['license']` + `hasValidLicense()` 授权校验 | 🔶 有授权与离线安装,缺在线市场/一键升级 |
|
||||
| **前端钩子 bootstrap.js** | 合并到 `public/assets/js/addon.js` | ❌ 无 | 🔶 视需要 |
|
||||
| **基类一致性** | 单一基类 `\think\addon` | ⚠️ **两套并存**:实际用 `ywxapp\addon`,另有 `ywxapp\BaseAddon`(含 registerHooks/registerRoutes/getThinkVersion)**完全未被使用**(死代码) | ❌ 需收敛 |
|
||||
|
||||
---
|
||||
|
||||
> **实施状态(截至 2026-07-21)**:本文列出的短板已全部补齐——
|
||||
> - **§3.2 `addon_url()` 未定义** → 已新增 `ywxapp/helper.php` 辅助层(`addon_url` / `get_addon_*`)。
|
||||
> - **§3.3 基类收敛** → 死代码 `BaseAddon` 已移除,统一使用 `ywxapp\addon`。
|
||||
> - **§3.4 生命周期打通** → `AddonService::enable/disable/upgrade` 已回调插件主类对应方法。
|
||||
> - **§3.1 钩子体系** → 已在会员/文章/支付/MQTT/异常/插件市场/插件生命周期等约 30 个节点预埋钩子点,统一使用 ThinkPHP 内置 `event()` 触发;完整钩子点清单见 `docs/插件开发指南.md` §4.1。
|
||||
> - **§3.6 在线升级** → `AddonService::onlineUpgrade()` 已实现(下载 → 备份 → 覆盖 → upgrade 钩子 → 增量 SQL)。
|
||||
>
|
||||
> 当前架构在"可插拔能力、生命周期完整度、开发者体验"上已**达到并在路由 / 隔离 / 配置存储方面超过** FastAdmin 水平。下文 §3 / §5 保留原始差距分析与路线图,供复盘参考。
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构差异详解(为什么不能照搬 FastAdmin)
|
||||
|
||||
### 2.1 路由:MultiApp(你) vs Route::execute(FA)
|
||||
- **FastAdmin**:所有插件请求走同一条 `addon/:addon/:controller/:action` 路由,由 `Route::execute` 反射定位 `\addon\<name>\controller\<X>`,插件控制器必须继承 `\think\addon\Controller`。插件想要"独立配置/独立路由"很别扭。
|
||||
- **本项目**:`MultiApp::setAddon()` 直接把插件当成一个**独立 TP 应用**:设置 `namespace=addon\<name>`、独立 `runtime`、`route_path`,并 `loadApp()` 加载该插件的 `common.php / config/*.php / event.php / middleware.php / provider.php / 语言包`。这意味着**每个插件几乎拥有主应用的全部能力**,隔离更彻底。
|
||||
- 👉 结论:**你的路由模型优于 FastAdmin**,无需引入 `addon/{addon}/...` 路由,也无需 copydirs。
|
||||
|
||||
### 2.2 文件注入:copydirs(FA)你不需要
|
||||
FastAdmin 靠 `copydirs` 把插件的 `application/`、`public/` 复制进主框架才能生效,由此衍生出"冲突检测、全局文件清单、启用即复制/禁用即删除"一整套复杂机制。**MultiApp 天然隔离,完全不需要这套**——这是你的优势,不是缺失。
|
||||
|
||||
### 2.3 扩展注册:Event 数组(你) vs 方法即钩子(FA)
|
||||
- 你现在:插件要监听全局事件,需在 `info.php['events']` 里写 `['GlobalEvent' => [监听类::class]]`,boot 期 `loadEvent` 注册。**能用,但不是"钩子"**——没有"插件主类里写个同名方法就自动挂载"的便利,也没有统一的"钩子点"清单。
|
||||
- FastAdmin:定义好一批钩子名(如 `user_login_after`),任何插件主类写同名方法即自动参与,业务侧 `hook('user_login_after',$data)` 一行触发。**扩展性和"可插拔感"明显更强**。
|
||||
|
||||
---
|
||||
|
||||
## 3. 真实短板与修复建议(按优先级)
|
||||
|
||||
### 3.1 ⭐ P0:缺"钩子/行为事件"体系(最该补)
|
||||
**现状**:只有原生 Event + info.php 声明,无统一钩子点、无方法即钩子、`BaseAddon::registerHooks` 是空壳。
|
||||
|
||||
**建议方案(贴合 TP8,不照搬 FA 反射)**:
|
||||
1. 在框架侧定义**钩子触发助手** `hook($name, &$params)`,内部走 `think\facade\Event::trigger("addon.$name", $params)`,并约定命名空间前缀 `addon.`。
|
||||
2. 约定插件在 `info.php['events']` 里声明 `'addon.user_login_after' => [\addon\x\listener\Xxx::class]`——**复用你现有的 boot 期 loadEvent**,零新增加载逻辑。
|
||||
3. 在关键业务点(登录、下单、消息投递等)预埋 `hook('user_login_after', $user)`。
|
||||
4. 文档里维护一份**全局钩子点清单**(钩子名 + 触发时机 + 参数),这是"可插拔"的关键。
|
||||
> 如果想要"方法即钩子"的便利,可在 `AppService::loadAddonRelevant()` 里对启用插件主类做一次 `get_class_methods` 差集,把匹配钩子名的方法用 `Event::listen` 注册——但**非必需**,声明式已够用且更可控。
|
||||
|
||||
### 3.2 ⭐ P0:`addon_url()` 未定义(潜在致命 bug)
|
||||
`ywxapp/addon.php:105` 执行 `$info['url'] = addon_url($name);`,但**全项目搜不到 `function addon_url` 的定义**。当 `getInfo()` 走"未命中缓存"分支时会触发 `Call to undefined function`。
|
||||
**建议**:新建插件助手文件(如 `ywxapp/helper.php`,并在 composer `autoload.files` 引入),实现:
|
||||
```php
|
||||
function addon_url(string $name, string $url = '', array $vars = []): string {
|
||||
// MultiApp 模型下:/<addon>/<controller>/<action>
|
||||
$path = '/' . $name . ($url ? '/' . ltrim($url, '/') : '');
|
||||
return $vars ? $path . '?' . http_build_query($vars) : $path;
|
||||
}
|
||||
```
|
||||
同时补齐 `get_addon_info/get_addon_config/get_appmarket_addon_list/get_addon_instance` 等常用包装(内部转调 `AddonService` / `Config`),形成统一辅助层。
|
||||
|
||||
### 3.3 P1:基类收敛(消除死代码 `BaseAddon`)
|
||||
`ywxapp\BaseAddon` 与 `ywxapp\addon` 两套并存,实际插件全部 `extends addon`,`BaseAddon` 从未被继承/调用。
|
||||
**建议**:二选一。要么把 `BaseAddon` 的有用能力(`enable/disable/upgrade` 默认实现、`getThinkVersion` 版本约束、`getDependencies` 依赖声明)**合并进 `addon`**,删掉 `BaseAddon`;要么明确废弃并从仓库移除,避免误导。
|
||||
|
||||
### 3.4 P1:打通 enable/disable/upgrade 到插件主类
|
||||
现在 `AddonService::enable()/disable()` 主要做 state 切换与菜单/表处理,但**未回调插件主类的 `enable()/disable()/upgrade()`**(基类 `addon` 也没这几个方法)。
|
||||
**建议**:在 `addon` 基类补 `enable()/disable()/upgrade()` 空实现(可重写),并在 `AddonService` 对应流程中"若方法存在则调用",与 install/uninstall 对称——对齐 FastAdmin 生命周期。
|
||||
|
||||
### 3.5 P2:前台控制器基类便利封装
|
||||
可选:为插件前台控制器提供一个基类,内置 `config` 注入视图、`__ADDON__` 静态资源前缀、按 `noNeedLogin` 的会员鉴权、多语言目录约定,减少每个插件的样板代码。
|
||||
|
||||
### 3.6 P2:升级与在线市场(按业务需要)
|
||||
已有 `package/local` 离线打包安装与 `license` 授权;若要对齐 FastAdmin,可补"版本比对 + 覆盖升级(保留数据)+ 远程下载"。非核心可后置。
|
||||
|
||||
---
|
||||
|
||||
## 4. 无需对齐的项(避免过度设计)
|
||||
|
||||
以下 FastAdmin 特性由本项目架构决定**不需要**引入:
|
||||
- `copydirs` 全局文件映射 —— MultiApp 已隔离。
|
||||
- `noconflict/getGlobalFiles` 冲突检测 —— 无文件注入即无冲突。
|
||||
- `addon/{addon}/{controller}/{action}` 统一路由 —— 多应用路由更强。
|
||||
- `info.ini` —— `info.php` 更适合本项目。
|
||||
- `set_addon_config` 写回 `config.php` 文件 —— 已用"数据库+缓存"替代,更优。
|
||||
|
||||
---
|
||||
|
||||
## 5. 完善路线图(建议顺序)
|
||||
|
||||
1. **修 `addon_url` 未定义**(§3.2)——止血,最快。
|
||||
2. **建统一辅助层** `ywxapp/helper.php`(§3.2)——`get_addon_*` 系列。
|
||||
3. **引入钩子体系**(§3.1)——`hook()` 助手 + 钩子点清单文档 + 复用 info.php events。
|
||||
4. **基类收敛 + 生命周期打通**(§3.3、§3.4)——删/并 `BaseAddon`,补 enable/disable/upgrade 回调。
|
||||
5. (可选)前台控制器基类、在线升级(§3.5、§3.6)。
|
||||
|
||||
> 完成 1~4 后,本项目插件机制在"可插拔能力、生命周期完整度、开发者体验"上即可**达到并在路由/隔离/配置存储方面超过** FastAdmin 的水平。
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
# YwxApp 自定义命令清单
|
||||
|
||||
> 生成日期:2026-07-30
|
||||
> 运行方式:在项目根目录执行 `php think <命令> [参数] [选项]`
|
||||
> 命令注册入口:`ywxapp/service/AppService.php` 的 `boot()` 方法(`$this->commands([...])`)
|
||||
> 命令类扫描范围:`app/command`、`ywxapp/command`、`addon/*/command`
|
||||
|
||||
---
|
||||
|
||||
## 一、已注册命令(实际可用,共 14 个)
|
||||
|
||||
按功能分组列出。
|
||||
|
||||
### 1. 插件管理
|
||||
|
||||
#### `addon:manage <插件名> -a <action>`
|
||||
- **文件**:`ywxapp/command/addon/Manage.php`(health 动作委托 `app/command/AddonHealth.php`)
|
||||
- **用途**:插件统一管理入口,合并原 `addon:develop` / `addon:install` / `addon:refresh-menu` / `addon:reload` / `addon:health` 五条命令为单入口。
|
||||
- **参数**:
|
||||
- `<插件名>`(可选):部分动作需要;留空语义见各动作
|
||||
- `-a, --action`(必填):`develop` | `install` | `refresh-menu` | `reload` | `health`
|
||||
- **动作与选项**:
|
||||
- `develop`:开发者模式安装(源码已在 `addon/<name>`,原地建表 / 注入菜单 / 启用,免打包)。例:`php think addon:manage mqttbroker -a develop`
|
||||
- `install`:安装插件。`--local=<zip>` 离线安装;`--force` 在线强制覆盖。例:`php think addon:manage demo -a install` / `php think addon:manage demo -a install --local=/path/addon.zip`
|
||||
- `refresh-menu`:重导菜单(`menu.json → admin_power / user_rule`),不改安装 / 启用、不跑 SQL;插件名留空 = 刷新全部。例:`php think addon:manage` / `php think addon:manage blog -a refresh-menu`
|
||||
- `reload`:热重载(需 `app_debug`)。`--force` 强制;`--status` 查看状态;插件名留空 = 全部。例:`php think addon:manage myplugin -a reload --force` / `php think addon:manage -a reload --status`
|
||||
- `health`:健康检查(委托 `AddonHealth`)。`[插件名]` 可选;`--detail` 详情;`--fix` 修复;`--output=text|json|html`;`--send-alert`;`--webhook=<url>`;`--email=<addr>`;`--save-report`。例:`php think addon:manage -a health` / `php think addon:manage myplugin -a health --fix` / `php think addon:manage -a health --output=json --send-alert`
|
||||
|
||||
#### `ywxapp:upgrade`
|
||||
- **文件**:`ywxapp/command/Upgrade.php`
|
||||
- **用途**:本地离线升级一键命令。绕过在线下载,直接对本地打包产物执行升级,复用系统既有升级逻辑:
|
||||
- **框架**:`FrameworkService::apply()` —— 备份、解压覆盖、跑 `upgrade.sql`、写回 `config/ywxapp.php` 版本号(自动备份到 `runtime/framework/backup/`)。
|
||||
- **插件**:`AddonService::localUpgrade()` —— 备份旧目录、解压覆盖(含 Zip Slip 防护与 static 资源处理)、增量 SQL、更新 `info.php` 版本(自动备份到 `runtime/addon/backup/`)。
|
||||
- 区别于 `addon:manage -a install --local=`(首次安装,插件已存在会拒绝):本命令支持**覆盖已安装插件**的离线升级。
|
||||
- **选项**:
|
||||
- `-f, --file=<zip>`(必填):升级包本地路径(绝对或相对项目根)
|
||||
- `-t, --type=<type>`(必填):`framework` | `addon`
|
||||
- `-r, --ver=<ver>`(可选):目标版本号。**framework 必填**(写入 `config/ywxapp.php`);addon 自动从 zip 内 `info.php` 读取
|
||||
- `-p, --patch`:框架补丁包标记(核心包,解压时剥离 `ywxapp/` 前缀解到 `ywxapp/`;整站包**勿加**)
|
||||
- **示例**:
|
||||
- 框架整站包:`php think ywxapp:upgrade -f runtime/framework/ywxapp-1.0.9.zip -t framework -r 1.0.9`
|
||||
- 框架核心补丁包:`php think ywxapp:upgrade -f runtime/framework/ywxapp-1.0.9.zip -t framework -r 1.0.9 -p`
|
||||
- 插件升级:`php think ywxapp:upgrade -f runtime/market/forum-1.0.2.zip -t addon`
|
||||
- **注意**:执行后会提示清理 `runtime/cache`、`runtime/temp`;插件升级前会自动备份到 `runtime/addon/backup/<name>-<ver>-<时间戳>/`,失败可回滚。
|
||||
|
||||
### 2. 数据库
|
||||
|
||||
#### `install:copy`
|
||||
- **文件**:`app/command/InstallCopy.php`
|
||||
- **用途**:复制 / 初始化本项目数据库安装(核心表 + 插件表)。脚本只执行 `CREATE TABLE / INSERT`(白名单),对已存在的表 / 数据做幂等跳过。
|
||||
- **选项**:
|
||||
- `-c, --core`:仅安装核心表
|
||||
- `-a, --addon=<name>`:仅安装指定插件(逗号分隔),默认全部插件
|
||||
- **示例**:
|
||||
- `php think install:copy`
|
||||
- `php think install:copy --core`
|
||||
- `php think install:copy --addon=blog,wxchat`
|
||||
|
||||
### 3. 服务进程(常驻 / 网关)
|
||||
|
||||
#### `workerman:gateway`
|
||||
- **文件**:`ywxapp/command/GatewayWorker.php`
|
||||
- **用途**:启动 Workerman GatewayWorker 服务(Register / Gateway / BusinessWorker)。
|
||||
- **选项**:
|
||||
- `-a, --acthon=<val>`:操作(start / stop / restart 等,透传给 Workerman)
|
||||
- **示例**:`php think workerman:gateway -a start`
|
||||
|
||||
#### `wxchat:server`
|
||||
- **文件**:`addon/wxchat/command/ChatServer.php`
|
||||
- **用途**:启动 wxchat 的 GatewayWorker 即时通讯服务。
|
||||
- **选项**:
|
||||
- `-a, --action=<val>`:操作(如 start)
|
||||
- **示例**:`php think wxchat:server -a start`
|
||||
|
||||
#### `mqttbroker:start`
|
||||
- **文件**:`addon/mqttbroker/command/MqttBroker.php`
|
||||
- **用途**:启动 MQTT Broker 常驻进程(兼容 MQTT 3.1.1 / 5.0,类 EMQX 轻量 broker,零外部依赖;转发规则桥接 EMQX 需 `workerman/mqtt`,未安装时自动跳过)。
|
||||
- **选项**:
|
||||
- `-p, --port=<val>`:监听端口(默认 `1883`)
|
||||
- `-H, --host=<val>`:监听地址(默认 `0.0.0.0`)
|
||||
- **示例**:`php think mqttbroker:start` / `php think mqttbroker:start -p 1883`
|
||||
|
||||
### 4. 插件运维 / 监控
|
||||
|
||||
#### `addon:health` / `addon:reload` 已并入 `addon:manage`
|
||||
- 原 `addon:health`、`addon:reload` 现统一为 `addon:manage -a health` / `addon:manage -a reload`,选项与行为不变。详见上文 §1 `addon:manage`。
|
||||
|
||||
#### `addon:license-check`
|
||||
- **文件**:`ywxapp/command/AddonLicenseCheck.php`
|
||||
- **用途**:运行期授权巡检(Discuz 式:已启用付费插件定期复查授权,过期自动禁用)。遍历 `addon/` 下已启用且 `info['license']=true` 的付费插件,逐个向中心站 `validDomain` 校验,校验拒绝或(非 `offline_tolerant` 时)中心站不可达 → 落库 `status=0` 禁用并写 `Log::warning`。放行条件:调试+未知来源 / `config('ywxapp.addon_license_check')===false` / 非客户机(`!is_market_client()`)时直接跳过。
|
||||
- **依赖**:仅客户机(`is_market_client()===true`)且 `addon_license_check` 开启时生效。
|
||||
- **示例**:
|
||||
- `php think addon:license-check`
|
||||
- crontab(建议每日 03:10 巡检一次):
|
||||
```
|
||||
10 3 * * * cd /path/to/site && php think addon:license-check >> runtime/license_check.log 2>&1
|
||||
```
|
||||
- 仅观察不禁用:临时设 `addon_license_check=offline_tolerant` 后运行,中心站不可达不禁用(仅真实过期拒绝仍禁用)。
|
||||
|
||||
### 5. 代码生成
|
||||
|
||||
#### `make:modely <name>`
|
||||
- **文件**:`ywxapp/command/ModelY.php`
|
||||
- **用途**:创建一个新模型类(继承自 `think\console\command\Make`)。
|
||||
- **参数**:
|
||||
- `name`(必填):模型类名,例如 `Yasd`
|
||||
- **示例**:`php think make:modely Yasd`
|
||||
|
||||
### 6. 第三方 / 业务集成
|
||||
|
||||
#### `mqttbroker:acltest`
|
||||
- **文件**:`addon/mqttbroker/command/TestAcl.php`
|
||||
- **用途**:ACL 实网决策验证,用真实 DB 规则校验 `Auth::checkAcl` 的放行 / 拒绝逻辑(deny 拒绝、默认放行、用户定向拒绝、超管绕过、`$SYS` 只读)。不依赖运行中的 Broker。
|
||||
- **示例**:`php think mqttbroker:acltest`
|
||||
|
||||
#### `haonav:checklinks`
|
||||
- **文件**:`addon/haonav/command/CheckLinks.php`
|
||||
- **用途**:haonav 网址导航死链巡检,更新 `status_code / last_check_at`。适合放 crontab。
|
||||
- **选项**:
|
||||
- `--limit=<n>`:本次最多检测条数(`0`=全部,按最久未检测优先,默认 `0`)
|
||||
- `--timeout=<s>`:单条检测超时秒数(默认 `8`)
|
||||
- **示例**:
|
||||
- `php think haonav:checklinks`
|
||||
- `php think haonav:checklinks --limit=50`
|
||||
- crontab:`0 3 * * * cd /path/to/site && php think haonav:checklinks >> runtime/haonav_check.log 2>&1`
|
||||
|
||||
---
|
||||
|
||||
## 二、已定义但未注册命令(共 4 个)
|
||||
|
||||
以下命令类已实现 `setName`,但**未写入 `config/console.php`**,因此 `php think list` 不会列出、直接执行会报「命令不存在」。如需启用,把对应键值加入 `config/console.php` 的 `commands` 数组即可(注意 `clear` 重名冲突问题)。
|
||||
|
||||
| 命令名 | 类 | 文件 | 说明 / 参数 |
|
||||
|--------|-----|------|-------------|
|
||||
| `addon:make` | `ywxapp\command\addon\Addon` | `ywxapp/command/addon/Addon.php` | **统一脚手架/生成器**(已通过 `AppService` 注册,替代原 `addon:build`/`addon:clear`/`addon:controller`/`addon:model`/`addon:middleware`/`addon:validate`/`addon:event`/`addon:listener`/`addon:service`/`addon:subscribe`)。用法 `php think addon:make <插件名> -a <action>`;`-a` 取 `build`/`clear`/`controller`/`model`/`middleware`/`validate`/`event`/`listener`/`service`/`subscribe`;生成器用 `--name=<类>`;`controller` 另支持 `--api`/`--plain`;`clear` 支持 `-c`/`-l`/`-r`。 |
|
||||
| `app` | `ywxapp\command\AppCommand` | `ywxapp/command/AppCommand.php` | **应用命令统一入口**(已通过 `AppService` 注册)。聚合 `ywxapp/command/app/*` 全部命令:`build`/`clear`/`controller`/`model`/`event`/`listener`/`middleware`/`service`/`subscribe`/`validate`。用法 `php think app <子命令> [参数]`;如 `php think app controller app\index --api`、`php think app clear --cache`。子命令专有选项(`--api`/`--plain`/`--cache` 等)透传至原命令类,未内联、不另立独立命令名。 |
|
||||
|
||||
> 说明:`ywxapp/command/Create.php` 中的 `YMake` 是**抽象基类**(`abstract class YMake extends Command`),本身不是可执行命令,供 `ModelY` 等生成类继承,不在清单内。
|
||||
|
||||
---
|
||||
|
||||
## 三、附:`config/console.php` 当前注册表(去重后)
|
||||
|
||||
```php
|
||||
'commands' => [
|
||||
"workerman:gateway" => \ywxapp\command\GatewayWorker::class,
|
||||
"make:modely" => \ywxapp\command\ModelY::class,
|
||||
"wxchat:server" => \addon\wxchat\command\ChatServer::class,
|
||||
"install:copy" => \app\command\InstallCopy::class,
|
||||
"mqttbroker:start" => \addon\mqttbroker\command\MqttBroker::class,
|
||||
"mqttbroker:acltest" => \addon\mqttbroker\command\TestAcl::class,
|
||||
"addon:manage" => \ywxapp\command\addon\Manage::class,
|
||||
"addon:license-check" => \ywxapp\command\AddonLicenseCheck::class,
|
||||
"haonav:checklinks" => \addon\haonav\command\CheckLinks::class,
|
||||
]
|
||||
```
|
||||
|
||||
> 注:原文件中 `install:copy` 出现了两次(重复键),实际生效一份,建议清理重复项。
|
||||
Reference in New Issue
Block a user