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 工具
|
||||
Reference in New Issue
Block a user