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(市场 / 开发者中心 / 审核 / 分润)
This commit is contained in:
1 parent
1d49e6f5ee
commit
18101cf11a
636 files changed
+58517
-9519
No files matched your search
@@ -0,0 +1,519 @@
|
||||
# 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)
|
||||
Reference in new issue
Block a user