Files
YwxAppThink/docs/guide/haonav_dev_guide.md
T
yangwx 18101cf11a feat: 上线前安全加固与发布工程化;appmall 由插件迁移为独立应用
安全(P0)
- 发布包签名:新增 scripts/package_keygen.php、scripts/package_verify.php、
  ywxapp\service\PackageVerifier;FrameworkService::apply() 解压前强制验签
  (ywxapp.framework_package_verify,默认开启)
- JWT 收紧:config/jwt.php 移除公开默认密钥,JwtService::assertStrongSecret()
  校验非空 / 长度≥32 / 已知弱值;.env.production.example 键名由 JWT_SECRET_KEY 修正为 JWT_SECRET
- /api/v1/Article:免登录白名单收窄为只读接口,写操作字段白名单 + assertOwner() 越权校验
- 开发者令牌:查找接口不再回显明文、统一失败文案 + 限流
- TemplateInstaller:文件名白名单 + realpath 归一化 + 目录内断言,修复 Zip Slip;补 use ZipArchive
- 后台登录:接入验证码、失败锁定与登录限流(config/ratelimit.php)
- 缓存 / Session 驱动可由 .env 覆盖(CACHE_DRIVER / SESSION_TYPE),集群部署改 redis
- paycenter 支付日志开关改为 PAY_LOG,不再跟随 APP_DEBUG

工程化 / 交付
- 新增 CHANGELOG.md 与 .env.production.example;composer.lock 入库锁定依赖版本
- 插件离线安装 CLI 增加 --uid,后台上传安装透传 uid,付费插件授权校验可通过
- 授权拒绝文案区分「未携带 uid」与「未找到有效授权」,便于排障
- .gitignore 放行 .env.production.example / .env.addon.example / .gitattributes / public/.htaccess

结构
- appmall 由 addon/appmall 迁移为独立应用 app/appmall(市场 / 开发者中心 / 审核 / 分润)
2026-10-22 00:53:39 +08:00

520 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# haonav 网址导航 — 开发文档
> 面向开发者。站长使用说明见 [haonav_user_guide.md](haonav_user_guide.md)。
- 命名空间:`addon\haonav\`
- 版本:1.0.9(`addon/haonav/info.php`)
- 路由前缀:`/haonav`(由 `AppService::loadAddonRoutes()` 在 boot 阶段统一包裹)
---
## 1. 目录结构
```
addon/haonav/
├── Addon.php 插件生命周期钩子
├── info.php 插件元信息(版本/入口/事件)
├── common.php 插件公共函数
├── route/app.php 路由表(相对规则,自动加 /haonav 前缀)
├── command/
│ └── CheckLinks.php 死链检测命令行
├── controller/
│ ├── Index.php 前台骨架控制器(12 个 trait 组合)
│ ├── Account.php 会员登录/注册/资料
│ ├── Favorite.php 云端收藏 + 分享
│ ├── Task.php 计划任务入口(checklinks / autocheck)
│ ├── Widget.php 小组件代理(热搜/天气)
│ ├── trait/ ★ 前台控制器按职责拆分(12 个)
│ │ ├── BaseTrait.php 配置/SEO/用户/广告/点击统计/静态资源输出
│ │ ├── HomeTrait.php index + rank
│ │ ├── SearchTrait.php search + suggest + rate
│ │ ├── SiteDetailTrait.php site 详情(JSON-LD)
│ │ ├── CategoryTrait.php category
│ │ ├── ApplyTrait.php submit + apply
│ │ ├── SitemapTrait.php sitemap 双格式
│ │ ├── ReadMetaTrait.php read
│ │ ├── FaviconTrait.php favicon 代理输出
│ │ ├── SnapshotTrait.php 网站快照
│ │ ├── SwTrait.php PWA Service Worker 输出
│ │ └── AdTrait.php adClick 广告点击跳转
│ └── backend/ 后台控制器
│ ├── HaonavBackend.php 后台基类
│ ├── Dashboard.php 数据概览 + 拼音回填
│ ├── Links.php 链接管理 + 回收站
│ ├── Category.php 分类管理
│ ├── Configs.php 配置管理
│ ├── Ad.php 广告位
│ └── Apply.php 申请审核
├── model/
│ ├── Links.php / Category.php / Configure.php / Ad.php / Apply.php
│ ├── Favorite.php / FavoriteShare.php
│ └── ...
├── library/Pinyin.php 拼音转换
├── validate/ 验证器(Category / Link)
└── view/frontend/ 前台视图
├── index/ 7 个页面
│ ├── index.html / search.html / rank.html / detail.html
│ ├── category.html / apply.html
├── favorite/shared.html 收藏分享页
└── public/ 公共 include
├── styles.html ★ CSS 入口壳(见 §5 陷阱)
└── navbar.html 顶部导航
public/static/haonav/
├── css/
│ ├── nav-base.css 全局重置/变量/卡片/响应式(全页面)
│ ├── nav-theme.css 主题面板 + 背景遮罩层
│ ├── nav-festival.css 节日粒子/徽章
│ └── nav-page-{index|category|detail|apply|share}.css 页面专属
├── js/
│ ├── nav-core.js $ / $all / safeStorage / ready 基础设施
│ ├── nav-theme.js 主题/背景/主色引擎 + 面板
│ ├── nav-festival.js ★ 节日/节气自动换装引擎(36 项)
│ ├── nav-fav.js / nav-search.js / nav-rating.js / nav-widgets.js / auth.js
├── sw.js Service Worker(缓存版本化)
├── manifest.json PWA manifest
└── img/ 默认图标等静态资源
```
---
## 2. 路由表(addon/haonav/route/app.php)
> 规则**不含** `/haonav` 前缀(外层 group 自动包裹)。URL 生成用 `url('haonav/xxx')` 或 `url('[haonav_site]', ['id' => $id])`,**不要写 `haonav/index/xxx` 三段式**(MultiApp 会剥首段导致对不上)。
### 前台
| 规则 | 控制器@方法 | name | 说明 |
|---|---|---|---|
| `index` | Index@index | haonav_index | 首页 |
| `category/:id` | Index@category | haonav_category | 分类页 |
| `site/:id` | Index@site | haonav_site | 详情页 |
| `search` / `suggest` / `rate` | Index@* | — | 搜索/建议/评分 |
| `rank` | Index@rank | — | 排行 |
| `submit` / `apply` | Index@* | — | 投稿 |
| `favicon` / `snapshot` | Index@* | favicons | 图标/快照代理 |
| `sw` | Index@sw | — | SW 输出 |
| `ad/click` | Index@adClick | — | 广告点击 |
| `sitemap(.xml)` | Index@sitemap | — | 站点地图 |
| `account/{login\|register\|logout\|profile}` | Account@* | — | 会员 |
| `widget/{hot\|weather}` | Widget@* | — | 小组件代理 |
| `favorite/{list\|add\|remove\|sort\|merge}` | Favorite@* | — | 云端收藏 API |
| `favorite/share` / `sharesave` | Favorite@* | — | 分享配置(登录) |
| `favorite/shared/:token` | Favorite@shared | — | 公开分享页 |
| `task/checklinks` / `task/autocheck` | Task@* | — | 巡检(需 token) |
### 后台(backend 组)
`dashboard / links / category / configs / ad / apply` + 各自 `:action` 子规则。
### 2.1 URL 生成规范(重点)
YwxApp 提供 **3 种**生成插件 URL 的方式,对应不同场景:
#### ① `url()` ThinkPHP 内置助手(推荐,name 化)
```php
// 方式 A:直接路径(规则须全字符匹配)
url('haonav/index') // → /haonav/index.html
url('haonav/category/5') // → /haonav/category/5.html
// 方式 B:用 route name(路由表里 ->name() 声明的)—— 带参数规则必须用 name
url('[haonav_site]', ['id' => 5]) // → /haonav/site/5.html
url('[haonav_category]', ['id' => 5])// → /haonav/category/5.html
// 附加 GET 参数
url('haonav/search', ['q' => 'haonav']) // → /haonav/search.html?q=haonav
```
**模板语法**:
```html
<a href="{$d.url|default=''}">...</a> <!-- 取控制器 assign 的 url 字段 -->
<a href="{:url('haonav/site', ['id' => $info.id])}">详情</a>
<a href="{:url('[haonav_site]', ['id' => $info.id])}">详情</a> <!-- 推荐 -->
```
#### ② `addon_url()` 框架插件助手(ywxapp/helper.php)
```php
addon_url('haonav', 'site/5'); // → /haonav/site/5
addon_url('haonav', '', ['page' => 2]);// → /haonav?page=2
addon_url('haonav', 'admin/index'); // → /haonav/admin/index
```
适用:跨插件、动态拼接、不依赖路由 name 的场景。
#### ③ 直接拼接(不推荐,仅 static 资源)
```php
// 仅限静态资源、JS 接口路径等无需经过路由解析的链接
'/static/haonav/css/nav-base.css'
```
#### ⚠️ 三大常见错误
| 错误写法 | 后果 | 正确 |
|---|---|---|
| `'haonav/index/rank'`(**三段式**) | MultiApp 剥首段后变成 `index/rank` → 对不上路由 → 404 | `'haonav/rank'`(两段:插件名+规则) |
| `url('haonav/category/5')`(带参数裸写) | 路由表规则 `category/:id` 必须有 name 才能把 5 填进路径段,否则退化为 `?id=5` | `url('[haonav_category]', ['id' => 5])` |
| `'addon_url'('haonav', '/index')`(重复斜杠) | 会生成 `/haonav//index`(无害但脏) | `addon_url('haonav', 'index')` |
#### 绑定域名下的 URL 行为
`url()` 和 `addon_url()` 都依赖 `request()->domain()`,**绑定域名后自动用绑定域名生成**:
```
未绑定:example.com/haonav/index.html
绑定后:hao.example.com/haonav/index.html(自动)
```
无需在模板里手写域名。
### 2.2 域名绑定机制(开发侧)
#### 配置文件 `config/app.php`
```php
'domain_bind' => [
'hao' => 'haonav', // 二级域名匹配
// 'hao.example.com' => 'haonav', // 完整域名匹配
],
```
#### 解析时序(ywxapp/middleware/MultiApp.php)
```
HTTP 请求进入
↓
1. 独立入口判断(如 haonav.php 存在 → 直接解析)
↓ 未命中
2. 域名绑定(domain_bind)
├── 完整域名命中 bind[host()] → 取 appName
├── 子域名命中 bind[subDomain()] → 取 appName
└── 通配 bind['*'] → 取 appName
↓ 未命中
3. URL 路径首段识别 → app_map / 多根查找
↓
resolve(appName) → 在 app_roots 中查找
├── prefix='app' → app/ 目录
└── prefix='addon' → addon/ 目录
```
`app_roots` 默认配置:
```php
'app_roots' => [
'app' => $app->getBasePath(), // app/
'addon' => $app->getRootPath() . 'addon/', // addon/
],
```
#### 多根查找的关键规则
- 裸 appName(如 `haonav`):按 roots 顺序找,**第一个匹配即用**(不是 fallback)
- 显式前缀(如 `addon:haonav`):**只在指定根找**,找不到直接 404
- 因此 `addon/haonav/` 存在 → 自动命中,无需额外配置
#### 调试技巧
```bash
# 1. 打印当前生效的域名绑定
php -r "echo json_encode(config('app.domain_bind'));"
# 2. 模拟请求命中哪个应用
php -r "
\$req = \think\facade\Request::create('http://hao.example.com/foo');
echo \$req->host() . PHP_EOL; // haon.example.com
echo \$req->subDomain() . PHP_EOL; // haon
"
# 3. 路由调试
访问 /haonav/test (Index::test 会 dump 当前路由表)
```
#### 完整域名 vs 子域名匹配的实际差异
```
请求:hao.example.com
完整域名模式 ('hao.example.com' => 'haonav')
host(true) = 'hao.example.com' ← 直接命中
子域名模式 ('hao' => 'haonav')
subDomain() = 'hao' ← 命中
host(true) = 'hao.example.com' ← 不会用作 key
```
两种方式**结果一致**,推荐**子域名模式**(部署同一证书多子域名复用、迁移服务器只改 DNS 即可)。
#### 绑定域名后的冗余首段兼容(框架层,MultiApp::stripBoundAppPrefix)
绑定域名后 URL **首段若与应用名相同**(即插件路径模式的旧链接,如 `docs.example.com/docs/xxx`),MultiApp 会把该段视为冗余前缀剥掉再分发——旧链接继续可用,不会 404。
```
docs.example.com/docs/xxx → 剥掉 docs → docs 应用内路由 xxx ✓(旧链接兼容)
docs.example.com/xxx → 首段 ≠ 应用名 → 不剥,直接路由 xxx ✓(新链接)
```
配套约定(以 docs 插件为参考实现):**插件前台链接生成走"动态前缀"**——
```php
// 插件前台基类(DocsFrontend::docsBase() 同款逻辑)
protected function addonBase(): string
{
// 1) 独立入口 public/<addon>.php → ''
$script = pathinfo((string) ($_SERVER['SCRIPT_FILENAME'] ?? ''), PATHINFO_FILENAME);
if ($script === '<addon名>') { return ''; }
// 2) domain_bind 命中本插件(支持 '<addon>' / 'addon:<addon>' 两种绑定值)→ ''
$bind = app()->config->get('app.domain_bind', []);
// ... 遍历 host(true) / subDomain() / '*' 三级 key 对比
return '/<addon url 前缀>'; // 3) 路径模式 → '/docs'、'/haonav' 等
}
```
- 模板:`href="{$docsBase}/xxx"`(基类统一 assign),**禁止硬编码 `/docs/`**
- 前端 JS:layout 注入 `window.DOCS_BASE = '{$docsBase}'`,JS 用 `BASE + '/xxx'` 拼接;解析 pathname 时先剥掉 `BASE` 段
- 后台模板无需处理(后台始终走主域名路径模式)
#### ⚠️ `$this->fetch()` 返回字符串,不是 Response 对象
框架(ThinkPHP)里控制器的 `fetch()` **返回渲染后的 HTML 字符串**,因此**不能链式调用 Response 方法**:
```php
// ❌ 报错:Call to a member function code() on string
return $this->fetch('read/notfound')->code(404);
// ✅ 用 response() 包装(第 2 参为状态码,第 3 参为 header)
return response($this->fetch('read/notfound'), 404);
// ✅ 需要自定义 Content-Type 时(如 sitemap)
return response($xml, 200, ['Content-Type' => 'application/xml; charset=utf-8']);
```
同理 `$this->fetch()` 也不能直接 `->header()` / `->contentType()`,一律经 `response()` 中转。
(直接 `return $this->fetch(...)` 时框架会自动包装成 200 HTML 响应。)
---
## 3. 控制器架构
### 3.1 前台骨架模式(Index.php)
`Index` 仅是 trait 组合壳 + 路由注释,**业务全在 12 个 trait**。新增前台功能 → 新建 trait → 挂到 Index 类 + 加路由。
```php
class Index extends FrontendBase
{
protected $noNeedLogin = ['*']; // 前台全放行(游客)
protected $noNeedVerify = ['*']; // 跳过用户组校验
use BaseTrait; // 共享 helper(siteConfig/assignSeo/assignAppearance...)
use HomeTrait; use SearchTrait; ... // 按职责
protected function initialize()
{
$this->assignAppearance(); // 外观配置注入所有页面
}
}
```
### 3.2 视图根切换(框架 FrontendBase)
插件上下文(`addon\` 命名空间)自动把 `view_path` 切到 `addon/haonav/view/frontend/`。`fetch()` 钩子叠加皮肤覆盖层(SkinOverlay)、配色变量(SkinVariables,`<style id="ywx-skin-vars">`)与风格切换器注入。
### 3.3 BaseTrait 关键 helper
| 方法 | 用途 |
|---|---|
| `siteConfig()` | 基础配置数组 → assign `$config` |
| `seoConfig()` / `assignSeo()` | SEO TDK → assign `$seo` / `$siteUrl` |
| `assignAppearance()` | ★ 外观(bg_image/bg_mask/bg_random/festival)→ assign `$appearanceJson`(raw JSON) |
| `assignAds()` | 广告位分组 → assign `$ads` |
| `recordClickStat($linkId, $cid)` | (link_id, date, hour) 聚合表 `wxapp_haonav_click_stats`,UPSERT 写入失败静默 |
| `serveFile()` / `serveDefault()` | 静态资源输出(带缓存头) |
---
## 4. 配置系统与自愈机制
配置表:`wxapp_haonav_config`(name/group/title/tip/type/value/content)。
### 4.1 三层自愈(Configure 模型)
| 方法 | 作用 |
|---|---|
| `ensurePk()` | 老库 id 无自增主键 → 运行时修复(解决 1364 错误) |
| `ensureRebateConfig()` | 返利 3 项补种 |
| `ensureDeadlinkConfig()` | task 3 项补种 |
| `ensureAppearanceConfig()` | 外观 4 项补种(bg_image/bg_mask/bg_random/festival_theme) |
调用点:后台 `Configs::index()` 首屏触发,INSERT 按 name 判重幂等。**新增配置项照此模式**:模型加 `ensureXxxConfig()` → Configs::index 调用 → BaseTrait::assignXxx 注入。
### 4.2 前端消费方式
`styles.html` 输出:
```html
<script>window.NavAppearance = {$appearanceJson|default='null'|raw};</script>
```
JS 侧读 `window.NavAppearance.{image,mask,random,festival}`。
---
## 5. 视图层规范
### 5.1 ⚠️ think-template include 传参陷阱(曾致 500)
```html
<!-- ✗ 禁止:跨模板作用域传参不可靠,抛 Undefined variable → HTTP 500 -->
{include file="public/styles" pageCss="index" /}
<!-- ✓ 正确:无参 include + 页面自行 link -->
{include file="public/styles" /}
<link rel="stylesheet" href="/static/haonav/css/nav-page-index.css">
```
Controller 级 `assign()` 是全局作用域,include 的子模板可正常读取(`$seo`、`$config`、`$appearanceJson` 均此机制)。
### 5.2 CSS 拆分架构
| 文件 | 职责 | 加载方 |
|---|---|---|
| `nav-base.css` | `:root` 变量、dark 变量、重置、navbar、搜索、卡片、排行、响应式 | styles.html(全页面) |
| `nav-theme.css` | 主题面板、背景遮罩 `body.nav-bg-img::before` | styles.html |
| `nav-festival.css` | 粒子 `.nav-fw`、徽章 `.nav-festival-badge` | styles.html |
| `nav-page-*.css` | 页面专属(widget/倒计时/tabs...) | 各页面手动 link |
CSS 变量:`--nav-primary` / `--nav-primary-2` / `--nav-bg` / `--nav-card` / `--nav-card-text` / `--nav-muted` / `--nav-border` / `--nav-tag-bg` / `--nav-bg-mask`。
### 5.3 JS 模块依赖链
```
nav-core.js($ / safeStorage / ready)← 必须最先
├─ nav-theme.js(主题引擎,读 window.NavAppearance)
│ └─ nav-festival.js(节日引擎,读 NavAppearance + localStorage)
├─ nav-fav.js / nav-search.js / nav-rating.js / nav-widgets.js
└─ auth.js
```
页面引入顺序(body 尾):`nav-core.js` → `nav-theme.js` → `nav-festival.js` → 业务 JS。
---
## 6. 主题 / 背景 / 节日引擎(核心机制)
### 6.1 存储值协议(localStorage `haonav_bg`)
| 值 | 含义 |
|---|---|
| `''`(无) | 默认(回落到站点配置→默认渐变) |
| `grad:<id>` | 预设渐变(`grad:green` 等,共 10 组) |
| `img:<url>` | 自定义图片(body 加 `nav-bg-img` 类触发遮罩层) |
| `linear-gradient(...)` | 旧版裸值(兼容层自动识别) |
`haonav_primary`(id)、`haonav_theme`(light/dark/auto)、`haonav_festival_off`(访客关闭节日)。
### 6.2 背景优先级链
```
访客 localStorage > 节日主题(nav-festival.js)> 站点 bg_image > 站点 bg_random > :root --nav-bg
```
### 6.3 节日引擎(nav-festival.js)
- `LUNAR`:农历→公历静态表(spring/dragon/midautumn/qixi/chongyang/xiaonian,2026-2036),**数据为公开农历表整理,生产请以紫金山天文台/香港天文台校准**
- `FESTIVALS`:36 项定义(24 节气 `key:'m-d'` 公历固定 + 农历节日 `lunar:+offset` + `span` 持续天数)
- `detect()`:数组顺序遍历,**靠前者优先**(同日多条时决定胜出)
- 命中后:`bg`(渐变或 url)→ body 内联背景;`p/s` → `--nav-primary(-2)`;`icon/count` → 粒子;`suffix` → 徽章文案
- **时间源**:`new Date()` 浏览器本地时间(跨时区偏移 1 天,如需全球一致可改 `Intl.DateTimeFormat('en-CA',{timeZone:'Asia/Shanghai'})`)
### 6.4 扩展:新增节日 / 节气
```js
// 1. LUNAR 加农历映射(农历节日才需要)
LUNAR.newfest = { '2026': '8-15', '2027': '...', ... };
// 2. FESTIVALS 按日历位置插入(数组靠前优先)
{ id: 'newfest', name: '新节', lunar: 'newfest', offset: 0, span: 0, suffix: '快乐',
bg: 'linear-gradient(135deg,#xxx 0%,#yyy 100%)', p: '#xxx', s: '#yyy', icon: '🌸', count: 12 },
// 3. 公历固定日期直接用 key
{ id: 'foo', name: 'Foo节', key: '3-12', span: 0, suffix: '', bg: '...', ... },
// 4. sw.js 缓存版本 +1(否则浏览器拿旧 JS)
var CACHE = 'haonav-v7';
```
---
## 7. Service Worker 缓存策略(sw.js)
- 版本号 `CACHE`:**每次改动 css/js 必须升版本**(v6 当前),activate 时自动清旧缓存
- 静态资源(`/static/haonav/*`)+ favicon/snapshot:**缓存优先,后台更新**
- 页面(`/haonav/*`):**网络优先,离线回退缓存**
- 不缓存:`favorite|widget|task|suggest` 接口
---
## 8. 数据表
| 表 | 说明 |
|---|---|
| `wxapp_haonav_links` | 链接(含 pinyin 搜索串、click_count、快照路径) |
| `wxapp_haonav_category` | 分类(pid 两级、sort、status) |
| `wxapp_haonav_config` | 配置(见 §4) |
| `wxapp_haonav_apply` | 前台投稿 |
| `wxapp_haonav_ad` | 广告位 |
| `wxapp_haonav_favorite` | 云端收藏(uid 维度) |
| `wxapp_haonav_favorite_share` | 收藏夹分享(token) |
| `wxapp_haonav_click_stats` | 点击聚合(link_id, cid, date, hour, clicks UPSERT) |
> 老库结构问题由 `BaseModel::ensureAutoIncrementPk()` 运行时自愈。
---
## 9. 开发流程约定
### 9.1 新增前台页面
1. `controller/trait/` 建 trait(或挂到现有 trait)
2. `route/app.php` 加规则(带参数加 `->name()`)
3. `view/frontend/index/` 建模板:`{include file="public/styles" /}` + 手动 link `nav-page-<name>.css`
4. body 尾按序挂 JS(core → theme → festival → 业务)
5. `sw.js` PRECACHE 增文件 + 版本 +1
### 9.2 新增后台功能
`controller/backend/` 建类继承 `HaonavBackend`,路由加 `backend` 组,视图 `view/backend/`。
### 9.3 新增配置项
模型 `ensureXxxConfig()` → `Configs::index` 调用 → 需要前台消费时在 `BaseTrait` 加 assign 注入 → 前端 `window.NavAppearance` 或模板变量读取。
### 9.4 静态资源改动 checklist
- [ ] `sw.js` `CACHE` 版本 +1
- [ ] 新文件加入 `PRECACHE`
- [ ] 页面 link/script 引入顺序:NavAppearance 注入(head)先于 nav-theme.js(body 尾)
---
## 10. 已知限制与历史坑
| 问题 | 状态 | 说明 |
|---|---|---|
| include 传参 500 | 已修 | page-*.css 手动 link 方案固化,勿回退 |
| 节日时区偏移 | 已知 | 浏览器本地时间判定,海外访客 ±1 天 |
| 农历换算表精度 | 已知 | 静态表 2026-2036,2030+ 建议官方校准 |
| SW 缓存旧资源 | 依赖版本号 | 改动必须升 CACHE |
| 裸路由前缀吞噬 | 已修 | 后台裸规则必须 `->completeMatch(true)` 强制完整匹配 |
---
## 11. 相关文档
- 站长使用文档:[haonav_user_guide.md](haonav_user_guide.md)
- 插件开发指南:[../插件开发指南.md](../插件开发指南.md)
- 框架开发文档:[../开发文档.md](../开发文档.md)
- 插件机制对比(Discuz):[../插件机制对比_Discuz.md](../插件机制对比_Discuz.md)