版本:v1(规划稿,未动代码) 日期:2026-10-30 依据:
docs/插件开发规范.mdv1.7(49 钩子全表)+ 原站 6 个前端资源实测 目标目录:plugins/qiming/(目录名小写无下划线 → 无需class_alias) 当前核心版本:V1.3.0(version.php)→min_core取1.3.0
一、素材盘点(实测结论)
1.1 下载结果:6/6 全部拿到
原站 blog.free.je 有 slowAES cookie 挑战(/aes.js + __test cookie),裸 curl 只能拿到挑战页。
已用 PHP 解 AES-128-CBC 算出 cookie 后完整下载,落在 .workbuddy-ai/tmp/zyqm/:
| 文件 | 大小 | 内容 | 可复用性 |
|---|---|---|---|
js/lunar.js | 4,225 B | 农历↔公历(1900–2100 位压缩表 201 项) | ✅ 算法可移植 |
js/bazi.js | 15,434 B | 四柱、节气(Meeus 太阳黄经迭代)、旺衰、喜用神、通关 | ✅ 算法可移植(核心价值) |
js/dict.js | 10,545 B | 168 字字库 + 53 重名 / 76 多音 / 45 忌字黑名单 | ⚠️ 结构可借鉴,数据建议自建 |
js/names.js | 9,364 B | 名字组合 + 打分排序 + 寓意文案拼装 | ✅ 打分逻辑可移植 |
js/app.js | 9,524 B | DOM 绑定与渲染(document.getElementById 硬绑) | ❌ 不可复用,需重写 |
style.css | 18,471 B | 道教风视觉(流云 / 八卦盘 / 水墨) | ❌ 与 FlintHub 主题不兼容,仅取配色灵感 |
挑战机制备忘:
document.cookie="__test="+toHex(slowAES.decrypt(c,2,a,b)),mode 2= CBC、key/iv 各 16 字节。 复现脚本已留在.workbuddy-ai/tmp/zyqm/fetch.php(本机 SSL 需CURLOPT_SSL_VERIFYPEER=false)。
1.2 代码质量评估
四个业务 JS 全是 UMD 自包含、零依赖,末尾统一 module.exports / global.Xxx,
说明原作者就是按「可移植库」写的 —— 这对我们是利好:
lunar.js → LunarConvert = { lunar2solar, leapMonth }
bazi.js → Bazi = { compute, gregorianJD, sunLongitude, termJD, getMonthBranch, getDayPillar, GAN, ZHI, WX, ... }
dict.js → NameDict = { DICT, BANNED, POLYPHONE, TABOO_WORD }
names.js → NameGen = { generateNames, buildPools, buildMeaning }
bazi.js:154 compute() 的入参是纯对象 {year,month,day,hour,minute,isLunar,leap},
出参是完整结构体(四柱 + counts + hidden + favorable + taboo + tongguan + analysisText),
没有任何 DOM 依赖 → 这是可以逐行移植成 PHP 的。
1.3 字库实测数据(决定后续要不要补字)
DICT 条目 168 字 | BANNED 53 | POLYPHONE 76 | TABOO_WORD 45
五行分布(木/火/土/金/水)= 32 / 33 / 31 / 33 / 39 → 均衡,可用
性别分布(通用/男/女) = 110 / 0 / 58 → ⚠️ 男性专用字 0 条
这是原站的硬伤:names.js:38 的过滤是 if (g !== 0 && g !== gender) continue,
选「男」时只能在 110 个通用字里挑,出不来「阳刚」气质(对比女名有 58 个专用字)。
结论:若要做男孩起名,必须补 60–80 个男性专用字,否则男女体验不对称。
1.4 版权与合规提示(必须先定调)
| 素材 | 性质 | 建议 |
|---|---|---|
| 农历表 / 节气算法 / 日柱甲子 | 公开算法(solarlunar、Meeus 天文公式),无版权争议 | 可自行实现 |
| 168 字库 + 每条「寓意 + 出处」文案 | 他人整理,有独创性表达 | ⚠️ 建议自建字库(重写文案、扩到 300–500 字),不要整段照搬 |
| 页面 CSS / 八卦 SVG / 文案 | 他人美术与文案作品 | ❌ 不复用,按 FlintHub 设计语言重做 |
这条不影响技术方案,但影响「能不能公开上架到插件商店」。如果只是自己站内用,风险低; 如果要投稿到 market_hub,字库必须自建。
二、需求边界
2.1 做
- 四柱排盘:公历 / 农历输入,年柱以立春、月柱以十二节为界,日柱甲子历差,时柱五鼠遁(含晚子时 23:00 特殊处理)。
- 五行旺衰与喜用神:子平扶抑 + 从格识别 + 通关神 + 月令禁忌,明确输出「喜用 / 忌神 / 通关 / 缺而不补」四类结论。
- 起名推荐:≥5 组双字名,按喜用神覆盖度 + 阴阳 + 音律 + 出处打分排序,标注「强烈推荐」。
- 换一批:同命局不同候选(salt 打散),不重新排盘。
- 起名记录:登录用户可保存本次结果,可在「我的起名记录」回看 / 删除。
- 后台配置:模块开关、游客权限、每次消耗积分、每日次数上限、默认姓氏性别、结果条数。
2.2 不做(明确砍掉,避免做半成品)
| 原站有的 | 为什么不做 |
|---|---|
| 「姓名测试」服务格 | 原站只是锚点到表单,本身没实现;且五格剖象打分属伪科学,做了反而掉档次 |
| 公司起名 / 成人改名 | 逻辑同构,但字库与规则完全不同(公司名要避重名、要行业字),塞进来会做成四不像 |
| 康熙字典查询 | 需要完整字典数据(几 MB),插件包会被 market_submit_max_kb(5 MB)卡住 |
| AI 解名 / 大师咨询 | 需外呼(net:fetch)+ 计费,属另一个产品形态,留给 ai_assistant 联动 |
服务宫格里保留「宝宝起名 / 八字起名 / 诗词起名」三项即可,都指向同一个表单(原站也是这么做的)。
三、架构决策(这一节需要你拍板)
方案 A:纯前端照搬
把 4 个 JS + 自写 CSS 塞进 assets/,PHP 只输出页面骨架。
- ✅ 改动最小,算法零风险(原样跑)
- ❌ 结果不可记录、不可分享、不可计费 —— 插件退化成「搬了个静态页进来」
- ❌
app.js里全是中文硬编码(alert('请填写姓氏')),i18n 门禁过不去 - ❌ 168 字库 + 算法全量下发浏览器(约 40 KB),首屏多 5 个请求
- ❌ 与规范 §7.3「零内联」冲突:
app.js里style="color:..."拼字符串,需大改
方案 B:PHP 服务端计算(推荐)
lunar.js / bazi.js / names.js 逐行移植为 PHP 类,页面 SSR。
- ✅ 结果天然可记录、可分享(URL 带参数)、可计费
- ✅ 零 AJAX,少一整套错误路径(JSON 解析失败 / 网络中断 / 竞态)
- ✅ SEO 友好(起名页是典型的长尾搜索入口)
- ✅ i18n 可控(文案在语言包,不在 JS 里)
- ⚠️ 风险 = 移植偏差 → 用「node 跑原 JS 出基准 → PHP 逐字段比对」硬门槛兜住(见 §九)
方案 C:PHP 算八字 + 前端 JS 只做表单联动
本质是 B 的子集(B 也需要 60 行 JS 做农历闰月/天数联动),不单列。
推荐 B。代码量估算:算法层约 900–1100 行 PHP,其余(控制器/视图/后台/语言包)约 1200 行。
四、目录结构
plugins/qiming/
├── plugin.json # name/version/min_core/category=entertainment/icon=book
├── Plugin.php # db() / ddl() / activate / deactivate / uninstall + 配置读写 + 记录 CRUD
├── FrontController.php # GET /qiming(表单+结果)· POST /qiming/save · GET /qiming/history · POST /qiming/history/delete
├── AdminController.php # GET /admin/qiming · POST /admin/qiming/save
├── Lunar.php # 农历↔公历(1900–2100 表 + 闰月 + 每月天数)
├── Bazi.php # 儒略日 / 太阳黄经 / 节气迭代 / 四柱 / 旺衰 / 喜用神 / 通关
├── NameDict.php # 字库(自建,结构同原站:[字,拼音,声调,五行,性别,寓意,出处,阴阳])+ 三类黑名单
├── NameGen.php # 组合生成 + 打分排序 + 寓意文案拼装
├── 说明.md # 插件说明(版本行 + 功能 + 配置项 + 免责声明)
├── hook/
│ ├── init_after.php # 建表兜底(戳文件门控,见 §七)
│ ├── route_register.php # 前台路由
│ ├── admin_route_register.php
│ ├── nav_plugin_links.php # 「应用」入口(PC 左栏 + 顶栏下拉,双触发)
│ └── nav_user_menu_items.php # 用户下拉「我的起名记录」(按 placement 分支)
├── views/
│ ├── index.php # 表单 + 四柱 + 五行 + 名字列表(单页)
│ ├── history.php # 我的起名记录
│ └── admin.php # 后台配置
├── assets/
│ ├── style.css # 全部 --mn-* 变量,禁硬编码色
│ └── script.js # 只做:农历闰月/天数联动 + 平滑滚动 + 复制名字
├── lang/
│ ├── zh.php ├── en.php └── zh_tw.php
└── data/ # 运行时自动生成(.htaccess / web.config 由核心补)
为什么算法类平铺在插件根:Autoloader.php:41-48 对 Plugin\ 只做「插件目录 + 剩余路径」拼接,
子目录可用但会引入 Linux 大小写风险(Lib/ vs lib/);现有插件全部平铺,跟随惯例最稳。
五、数据模型
配置走核心 Settings(量小、要后台可视化、要随卸载清理);记录走插件独立库。
5.1 独立库 plugins/qiming/data/qiming.sqlite
-- 起名记录
CREATE TABLE IF NOT EXISTS qm_records (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL DEFAULT 0,
surname TEXT NOT NULL DEFAULT '',
gender INTEGER NOT NULL DEFAULT 0, -- 1 男 / 2 女
calendar TEXT NOT NULL DEFAULT 'solar',
birth_y INTEGER NOT NULL DEFAULT 0,
birth_m INTEGER NOT NULL DEFAULT 0,
birth_d INTEGER NOT NULL DEFAULT 0,
birth_h INTEGER NOT NULL DEFAULT 0, -- 0/1/3/5/.../23 时辰起始小时
is_leap INTEGER NOT NULL DEFAULT 0,
bazi_json TEXT NOT NULL DEFAULT '', -- 四柱 + 喜用神快照
names_json TEXT NOT NULL DEFAULT '', -- 本次推荐的名字数组
created_at INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_qm_rec_uid ON qm_records (user_id, created_at DESC);
-- 每日次数(防刷 / 计费)
CREATE TABLE IF NOT EXISTS qm_usage (
user_id INTEGER NOT NULL,
day TEXT NOT NULL, -- 'YYYY-MM-DD'
cnt INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (user_id, day)
);
⚠️ SQLite 限额坑(记忆里踩过):
WHERE cnt + :a <= :lim这种「表达式 vs 参数」在 SQLite 里 因类型序INTEGER < TEXT恒真,限额静默失效。正解:上限在 PHP 侧算好,用bindValue(PARAM_INT)。
5.2 核心 Settings 键(qm_ 前缀,SETTING_DEFAULTS 白名单)
| 键 | 默认 | 说明 |
|---|---|---|
qm_enabled | 1 | 模块总开关(关 → 路由 404、导航不出入口) |
qm_guest | 1 | 游客是否可起名(关 → requireLogin()) |
qm_cost_points | 0 | 每次起名消耗积分(0 = 免费),走 Points::deduct() |
qm_daily_limit | 0 | 每日次数上限(0 = 不限) |
qm_name_count | 6 | 每批推荐条数(5–10) |
qm_default_surname | 李 | 表单默认姓氏 |
qm_default_gender | 2 | 表单默认性别 |
activate() 里逐键 Settings::update()(内部会 buildCache),
uninstall() 里先 DROP TABLE → 再清 settings 键 → 再 Settings::buildCache()(规范 §11.16)。
六、路由与钩子
6.1 前台路由(hook/route_register.php)
| 方法 | 路径 | 动作 | 说明 |
|---|---|---|---|
| GET | /qiming | index | 无参 = 表单;带参 = 结果页(同一路由,可分享/可刷新/可后退) |
| POST | /qiming/save | save | 保存记录(先 requireLogin() 后 Csrf::verifyOrDie()) |
| GET | /qiming/history | history | 我的起名记录(需登录) |
| POST | /qiming/history/delete | deleteRecord | 删除((int) 强转 + user_id 归属校验防 IDOR) |
结果页走 GET 参数,形如:
/qiming?s=李&g=2&c=solar&y=2026&m=10&d=30&h=4&l=0&salt=0
- 可分享、可收藏、可后退、刷新不弹「重新提交表单」
- 「换一批」=
salt+1的普通链接,零 AJAX - 参数非法(年份越界 / 农历闰月不存在)→ 不报错、不 500,回落到表单页并给一条提示
/qiming与站点根真实目录无冲突(已核实根目录无同名文件/目录,规范 §6.2.2 第 7 条)。
6.2 后台路由(hook/admin_route_register.php)
$router->get('/qiming', ['\Plugin\Qiming\AdminController', 'index']); // 实际 /admin/qiming
$router->post('/qiming/save', ['\Plugin\Qiming\AdminController', 'save']);
⚠️ 不写 /admin 前缀(规范 §11.22,写了会变 /admin/admin/qiming → 404)。
6.3 钩子清单
| 钩子 | 用途 | 坑 |
|---|---|---|
init_after | 建表兜底(activate() 可能被静默跳过) | 用戳文件门控,别每请求跑 DDL |
route_register | 前台路由 | 先 if (!isset($router)) return; |
admin_route_register | 后台路由 | 需声明 route:admin |
nav_plugin_links | 「应用」入口 | 一次渲染触发两次(左栏带 __nav_uri、顶栏不带)→ 高亮必须 !empty($__nav_uri) 守卫;文件内禁声明顶层函数/类;图标 16px |
nav_user_menu_items | 「我的起名记录」 | 双触发(placement='pc' / 'mobile')→ 按 $placement 分别渲染;图标 14px(mn-fs-14) |
不注册 layout_head_end:插件页样式走视图 section('css') 更精准,也避免每页白跑。
不注册 right_sidebar_after:右栏面板要「每页必跑」,会顶破「单插件每请求 hook 查询 ≤ 5」红线。
6.4 plugin.json
{
"name": "周易起名",
"version": "1.0.0",
"min_core": "1.3.0",
"author": "FlintHub",
"description": "周易起名:四柱排盘(立春/十二节定界)、子平旺衰与喜用神、通关神推演,按喜用神与音形义生成宝宝名字,支持记录与分享",
"activated": false,
"category": "entertainment",
"icon": "book",
"sort_order": 0,
"admin_url": "/admin/qiming",
"hooks": { "...": "..." },
"permissions": ["route:admin", "system:settings"]
}
icon=book,已grep "'book'" app/Core/TemplateCompiler.php命中(compass已被yijing_divination占用,避免撞脸;备选pen/magic/seedling也都在表内)- 不需要
net:fetch(纯本地算法,零外呼)
七、前台页面结构
单页三段式(对齐 horoscope / points_mall 的范式):
<div class="mn-section qm-wrap">
① 表单区 .qm-form 姓氏 / 性别 / 历法 / 年月日 / 闰月 / 时辰
② 四柱区 .qm-pillars 年柱 月柱 日柱 时柱(五行色条)+ 日主一句话
③ 五行区 .qm-wuxing 五行分布条 + 喜用/忌/通关标签 + 分析文字 + 免责声明
④ 名字区 .qm-names N 组名字卡(大字 + 拼音 + 五行 + 寓意 + 出处)+「换一批」
</div>
7.1 必须遵守的容器与节奏(规范 §6.4-2 / §6.4-5)
.qm-wrap { max-width: 1080px; margin: 0 auto; padding: 16px 20px 24px; }
.qm-wrap > * + * { margin-top: 18px; } /* 容器统一纵向节奏,不逐块写 margin */
.qm-wrap > :last-child { margin-bottom: 0; }
@media (max-width: 860px) { .qm-wrap { padding: 14px 14px 18px; } } /* 收窄,不是归零 */
这三条是
yijing_divination一页连踩三处「卡片边框叠成一条」后总结出来的,直接照抄。
7.2 主题变量(规范 §11.7)
- 五行配色用插件自定义变量,但底色/文字/边框一律
--mn-*:--qm-wood / --qm-fire / --qm-earth / --qm-metal / --qm-water五个语义色, 浅色 fallback 给足,暗夜星辰下自动跟随--mn-bg-card/--mn-text。 - 禁:
background:#fff、color:#333、硬编码#0066cc。
7.3 零内联
- 全部样式进
assets/style.css,全部交互进assets/script.js - 五行色条宽度属动态 CSS 变量注入(
style="--qm-pct:72%"),属规范 §7.3-4 允许的合规写法 - 农历联动所需的「每年闰哪月 / 每月天数」以
data-*JSON 承载(htmlspecialchars(json_encode(...), ENT_QUOTES)),JS 端JSON.parse
7.4 script.js 只做三件事(约 60–80 行)
- 历法切「农历」时显示闰月复选框
- 年份/月份变化时,按
data-*里的表重算「日」下拉的可选上限,并把不存在的闰月disabled - 「复制名字」按钮(
navigator.clipboard+ 降级execCommand)
无 JS 也能用:服务端会把所有选项渲染出来,提交时服务端再校验一次日期合法性。
八、i18n 策略
三份语言包,键集合必须完全一致(fh_i18n_check.php 门禁):
zh.php:完整en.php/zh_tw.php:UI 文案翻译(标题、按钮、提示、免责声明、配置项)- 命理术语与名字寓意保留中文(「甲木」「喜用神」「通关」翻译成英文反而看不懂,且字库寓意本身是中文数据)→ 在
说明.md与页面免责声明里写明这一点
键前缀统一 plugin.qiming.*;前台交互提示用 *_act 后缀,后台表单校验用 err_*。
九、分阶段实施(每阶段结束停下确认)
P0 · 骨架(不写业务逻辑)
plugin.json + Plugin.php(ddl/activate/uninstall/配置读写)+ 4 个钩子 + 空视图 + 后台配置页。
验收:激活成功 → 独立库建表 → /qiming 返回 200 → /admin/qiming 返回 200(非 404)→ 导航入口出现且两处都有(左栏 + 顶栏下拉)。
P1 · 算法层 + 一致性对照(最关键的一步)
移植 Lunar / Bazi / NameDict / NameGen 四个类。
验收(硬门槛):
- 用 node 跑原 4 个 JS,对样本集输出 JSON 基准
- 样本集 = 1910–2045 每年
01-01 / 06-15 / 12-31× 12 时辰(共约 4900 组) + 立春前后 ±1 天(年柱切换边界)+ 12 个节气交界日(月柱切换)+ 23:00 晚子时(日柱/时干特殊分支)+ 闰月日期 - PHP 跑同样输入,逐字段比对:
年/月/日/时 干支、counts[5]、hidden[5]、favorable、taboo、primaryYong、secondaryYong、tongguan、pattern、strength、score - 零差异才算通过。任一处不一致 → 先定位是浮点精度(节气迭代 8 次收敛够不够)还是整数取模方向,不许「大致一致」放过
P2 · 前台页面
表单 + 四柱 + 五行 + 名字列表 + 换一批,双主题走查。
验收:/qiming 带参渲染 200;五行色条、名字卡、免责声明齐全;浅色/暗夜星辰各走一遍;
grep -c '' 为 0(无问号图标);窄屏 375px 不贴边、卡片间距不为 0。
P3 · 记录与后台
保存 / 历史 / 删除 / 后台 7 项配置。
验收:保存 → 历史可见 → 删除后消失;换一个 uid 访问别人的记录 id → 404 或空(IDOR);
后台保存后前台立即生效(验证 Settings::update 的 buildCache 链);卸载后 qm_* settings 键清零、表已删。
P4 · 增强(可选,按需)
- 积分消耗(
Points::deduct(),不直写users.points) - 每日次数上限(
qm_usage+ PHP 侧算上限) - 「分享到论坛」——⚠️ 风险最高的一项:核心没有公开的「插件代发帖」服务方法,
直接写
topic_index会破坏分片一致性。建议改成「复制分享文案 + 跳转发帖页预填标题」,不做代发。
十、规范红线自查(针对本插件逐条落)
| 规范条款 | 本插件落点 |
|---|---|
§4.1 目录名含下划线 → class_alias | qiming 无下划线,不需要 |
§6.4-4 图标必须在 $map 内 | book 已核实命中;渲染后断言页面无  |
§9.3 nav_plugin_links 双触发 | 文件内禁声明函数/类;!empty($__nav_uri) 守卫;16px |
§9.1 #10 nav_user_menu_items 双触发 | 按 placement 分支;14px |
§11.22 后台路由不写 /admin | /qiming,admin_url 写 /admin/qiming |
§11.24 渲染钩子里 $this 不可用 | 钩子内一律 htmlspecialchars(...) + Csrf::token() |
| §11.28 登录链接 / 写操作顺序 | 用 Auth::loginUrl();先 requireLogin() 后 Csrf::verifyOrDie() |
| §6.2.1 URL 双模式 | 路由用 $this->url() / Url::to();资源用 $bp;JS 用 window.FH_WEB |
| §7.3 零内联 | 视图/钩子零 <style> 零 on*;动态值走 CSS 变量注入 |
| §11.7 主题变量 | 只用 --mn-* + 自定义 --qm-* 语义色 |
| §11.4 性能红线 | 钩子内零 SQL(只读 Settings 缓存);重逻辑全在插件页面内 |
| §11.16 卸载清缓存 | uninstall() 里 Settings::buildCache() |
§12.1-4 全角标点紧跟 $var | 消息串一律 {$var};交付前跑 php_var_cjk_check.php |
§12.1-6 同命名空间漏 use | 交付前跑 php_missing_use_check.php |
| §12.1 零内联审计切片 | 后台页从 admin-content 切到插件自己那行 </script> |
| §11.19 编码卫生 | UTF-8 无 BOM + LF;php -l 全过 |
| §11.34 Linux 大小写 | 跑 plugin_case_lint.py |
十一、风险清单
| # | 风险 | 影响 | 应对 |
|---|---|---|---|
| 1 | 算法移植偏差 | 排盘结果错 → 插件核心价值归零,且用户不会发现(看起来很专业) | §九 P1 的一致性对照,4900+ 样本逐字段比对,零差异才继续 |
| 2 | 节气浮点精度 | 立春当天出生的人年柱可能错一位 | 迭代次数 8 次收敛(原站如此),对照测试必须覆盖立春 ±1 天 |
| 3 | 字库版权 | 上架商店被投诉 / 下架 | 自建字库,重写寓意文案;先做 200 字,后续可扩 |
| 4 | 男性字缺失 | 男孩起名效果明显弱于女孩 | 补 60–80 个男性专用字(原站 0 条) |
| 5 | 命理内容合规 | 平台/监管对「算命」敏感 | 页面固定免责声明「仅供参考,不构成命理定论」;定位为「文化起名工具」而非「算命」;不做付费改运类话术 |
| 6 | 隐私 | 出生时间进 URL 与访问日志 | 结果页参数只含年月日时(无姓名全称、无身份证级信息);提供「保存记录」时明确告知存哪 |
| 7 | 分享到论坛代发帖 | 破坏 SplitDB 分片一致性 | 不做代发,改为「复制文案 + 跳转发帖页」 |
十二、需要你拍板的 4 件事
- 架构:走方案 B(PHP 服务端计算)还是方案 A(纯前端照搬)?我推荐 B。
- 字库:自建(重写 200 字,含男性字)还是先照搬原站 168 字跑通再说?
- 计费:是否要「每次起名消耗积分」?(要的话 P3 一起做,
Points::deduct) - 命名:目录
qiming、路由/qiming、显示名「周易起名」—— 还是要叫「宝宝起名」/「八字起名」?
拍完这 4 项我就按 P0 开始,每阶段结束停下来给你验。