Noline 的仓库里除了主题还有一个 plugins/NolineApi/。它名义上是「给 App 用的 REST 接口」,但用下来它其实承担了三件事:把后台换成前厅那套皮、让主题和插件成对自己更新、给健康页和记账页当地盘。这篇交代这三件。
后台一共 15 个设置项
站点头像地址、博主名字、座右铭、首页头图、强调色、动态分类、导航图标、首页公告、右栏分类卡、页面转场样式、GitHub 显示名、GitHub 链接、Bilibili 显示名、Bilibili 链接、站点底部版权。
这 15 项就是接口能读写的全部字段,一一对应,没有隐藏项。插件的 Theme.php 里有一张字段表,把名称、控件类型、长度上限、默认值、提示文案全写在一起;_dev/phpcheck.py 会拿这张表和 functions.php 里的 themeConfig() 对一遍——名字漂了、控件类型漂了、少了一项,构建直接失败。这个检查是必要的:我删过一个设置项,库里那行残留的旧键让插件设置页整整 500 过一次(Typecho 保存是 array_merge,不清旧键),后来只能在 config() 里主动 prune 那份 Options 单例。
整个后台会换成主题那套视觉
由 admin/header.php:header 这一条钩子注入一份 assets/admin.css,接在核心三份样式表后面。不用 !important,不动核心文件。改的不只是配色:
- 顶栏改成悬浮的玻璃胶囊条,窄屏收成抽屉;页面底色带一层前台那种暖光晕;
- 标题、分组名、卡片抬头统一加金点,和前台
.card-title一个配方; - 列表页的表格按前台信息流重排成卡片:
admin.js给每个<td>写上data-label(列名取自表头),CSS 把<tr>变成 flex 卡、<td>变成带小标签的字段; - 写文章页:标题输入框抬成 hero,侧栏选项卡成药丸,图床面板接在核心附件面板顶上;
- 登录页和通知条跟着同一套令牌,但保留
error / notice / success的语义色,不被卡片白底洗掉; - 深色开关在顶栏,选择记在
localStorage,页面绘制前就同步写好,不会先白一下再黑。
1.2 和 1.3 两套后台标记都吃得住。 1.3 动了骨架:<div class="main"> 换成 <main>、.typecho-table-wrap 整层删掉、页头的「新增」从 <h2> 里挪到外面、批量操作条从 <div> 变成 <form>、空态从 <h6 class="typecho-list-table-title"> 变成 <td class="none">、勾选高亮改用 li:has(input:checked)。这些位置皮肤都是两条选择器并写,不做浏览器嗅探。这个决定救过一次:Typecho 1.3 刚改菜单结构的时候,顶栏那整段皮肤直接失效过。
所有「先藏起来」的规则都挂在 html.nl-js 后面——关掉 JS 只是没动画,不会少内容。不喜欢这套皮可以在插件设置里关掉「后台皮肤」,核心功能一样不缺,图床照旧能用。
自动更新是默认开着的,而且不用点
后台路过任意一个页面时查一次(结论缓存 6 小时,两轮之间至少隔 1 小时),只有管理员登录的后台请求会触发。发现 Release 里的版本号比本机高,就当场下载、验收,把旧主题和旧插件各整份挪进 usr/uploads/noline_backup/<版本>-<时间>/{theme,plugin},再让新版本顶上。不需要点任何东西。
交换那几十微秒里撞上的前台请求会吃到一次错误;中途被打断(超时、断电)会自己把备份搬回原位——成对交换要么两边都成,要么两边都退回旧版本,退不干净就把待办留着下次路过再试。usr/plugins 不可写时这一轮只换主题,页面上会写明插件停在旧版本。
这一台机器上的实况:10-25 05:24 自动跑过一轮,主题 1.33.0 → 1.34.0、插件 1.9.2 → 1.9.3,包从 GitHub 直连下的,233 个文件,另外把本机独有的 2 个 views/ 文件补进了新版本。备份目录里现在躺着 8 份,随时能成对回滚。
它不判断 Release 本身是好是坏,只认版本号比本机高。发布前自己验一遍。不放心就在插件设置里改成「只提示,我自己点」。
连不上 GitHub 的站点
插件设置里有个「主题更新镜像」多行框,一行一条,最多 8 条。顺序永远是 GitHub 直连优先,只有直连连不上 / 超时 / 被限流 / 回 5xx 才一条条退到镜像;试过不通的镜像进 30 分钟冷却,不会拿每个后台页面去撞它。
三条安全上的硬规矩:
-
token 只发给
api.github.com,绝不发给镜像; - 附件地址一律按 repo + tag 自己拼,不用镜像回的 JSON 里那个
browser_download_url; - 摘要可信度是分开的:只有直连拿到的 sha256 才算可信。只从镜像检测到新版本时,自动覆盖会停住(镜像要改包,连摘要也能一起编),页面上写明走的哪条出口,等你自己点确认。
钩子怎么跟着一起换
在线更新换的是磁盘上的文件,换不掉数据库里的钩子登记。所以插件每次路过 index.php:begin 和 admin/menu.php:navBar,都会拿数据库里的登记和当前代码比一遍,对不上就当场按核心自己的语义重登记(deactivate() → 重新 factory() → activate() → 写回 plugins 那一行),不碰设置,也不要求你去插件页点任何东西。
唯一的前提:这两个入口钩子不能在同一次发布里同时消失或改名,否则自愈没有落脚点。到底登记成没登记不用猜——「主题管理」页有一节「后台钩子自检」,逐条列出钩子名和已登记/未登记。
图床:只走账号密码,不走 token
Cloudreve 的实例地址 + 那个网盘账号的邮箱和密码。启用之后写文章/写页面的附件面板顶部会多一个「图床」框,选文件、粘贴截图、拖图片进去都能传,传完自动把直链按当前编辑器形态(Markdown 或富文本)插到光标处。Typecho 自带的附件上传一点不动,两者并存。
密码不落在浏览器里:邮箱、密码和令牌都只待在服务端,浏览器请求先到插件,由插件加上 Authorization 再转发给 Cloudreve。插件只在需要时登一次,会话缓存在 usr/uploads/noline_api/cloudreve.php;v4 的 access token 默认只活 1 小时,到期前用同一串 refresh token 续。你在 Cloudreve 那边改了密码、退出过登录,refresh 会作废——插件自己会重新拿邮箱密码登一次,不需要回来补任何东西。
两道坎插件绕不过去,命中会直接把话说清楚:实例开了登录验证码(请求在进业务逻辑之前就被挡下)、账号开了两步验证(登录只回一个会话 id,动态码只在人手上)。要么关掉它们,要么给图床单独建一个不开两步验证的账号。
还有一条也拒绝得很干脆:如果实例的存储策略配成浏览器直传第三方对象存储(file/upload 会返回非空 upload_urls),插件明确拒绝并提示去后台把传输方式改成「服务端中转」,而不是自作主张去接一套签名协议。我写这篇的时候正好撞上它——所以这几篇文章的配图走的是 Typecho 自带的附件上传,不是图床。
接口和数据落在哪
GET /api/ping 连通性自检,不需要密钥
GET /api/theme 站点门面 + 15 项配置 + 由强调色推出的配色
PUT /api/theme 写配置(另一个独立开关控制)
GET /api/health 体重概览:最新值 / BMI / 统计 / 趋势 / 目标
GET /api/health/records 明细,limit ≤ 366
POST /api/health/records 写入单条或批量
DELETE /api/health/records?date= 删除某天记录
GET /api/bills 记账概览:周 / 月 / 年 / 累计 / 近 7 个月 / 分类占比
GET /api/bills/records 流水明细,可带 from / to / kind
POST /api/bills/records 记一笔或批量,返回 ids[]
PUT /api/bills/records 按 id 改一条
DELETE /api/bills/records?id= 按 id 删一条
数据落在 usr/uploads/noline_api/:体重在 store.php(上限 3660 条),买菜流水在 bills.php(上限 5000 条,超了丢最旧的)。两个文件各配一把锁,互不阻塞,都以 .php 结尾且带 __TYPECHO_ROOT_DIR__ 守卫,直接访问只会是空白页。
两个容易搞混的语义:记账没有后台录入表单,写入只有接口一条路;而且 POST 是永远新增(同一天记两笔就是两笔),和健康数据的「按天覆盖」相反。插件设置里的「数据写入」默认是只读,记不进去先去看这一项。


不发版也想知道的:它没有 PHP 也能守门槛
_dev/ 里那十几个检查脚本不依赖 PHP 或 Node——我本机就没有 PHP 运行时。phpcheck.py 靠自己的词法扫,拦的是括号配对、文件头守卫、self:: 指向的方法存不存在、私有方法漏调用、跨类读 private 成员(PHP 里这是 fatal 不是警告)、字符串里套同种引号(引号数量照样是偶数、括号照样配平,只有走词法才看得见)、_t() 占位符缺参、主题字段表漂移。
build.py 是最后一道门:核对四处版本号(主题 NOLINE_VERSION + index.php 的 @version,插件 const VERSION + 文件头 @version)两两相等,按内容重压一遍 CSS 和仓库里的 .min.css 比对(不看时间戳,git clone 出来的时间戳是随机的),顶层出现白名单之外的目录直接构建失败。
发布就是推一个 tag:.github/workflows/release.yml 在 Actions 里跑 build.py + publish_release.py,建好 Release 并挂上 noline.zip。本地不需要装 PHP,也不需要把任何令牌交给别人。没有 noline.zip 的 Release 等于没发布——后台读的是 releases/latest 那个附件,页面会直接报「这个 Release 没有 noline.zip 附件」。

评论 0
还没有评论,来抢沙发