圣堂之魂
开发指南

v1.11.0 源码导航索引

按请求链路、安全边界和存储职责导航 Short_NURL v1.11.0 源码


本页对应 URL_PHP_v1.11.0。它不维护容易随重构失真的“函数总数、调用总数、锚点合计”,而以文件职责、入口、关键函数和数据流作为稳定导航。源码中的 @外接口_#@关键_$ 等注释仍可用于全文搜索,但本文不承诺手工编号连续或数量恒定。

一、从公开路由进入

路由PHP 入口认证主要去向
GET /api/pingapi/ping.php直接返回 {ok:true},不加载业务 bootstrap
POST /api/createapi/routes/create.php标准链路验证、去重、双 Store 事务、热同步
POST /api/deleteapi/routes/delete.php标准链路冷删除后热删除
GET /api/listapi/routes/list.php标准链路读取冷存储并过滤过期临时条目
GET /api/statapi/routes/stat.php标准链路冷计数权威,热计数诊断
POST /headless/api/createheadless/create.php无头链路同类创建逻辑,增加服务 Key/len
POST /headless/api/deleteheadless/delete.php无头链路冷删除后热删除,机器可读错误码
GET /headless/api/listheadless/list.php无头链路冷存储列表
GET /headless/api/statheadless/stat.php无头链路冷计数权威,热计数诊断
GET /headless/api/get/{code}headless/get.php无头链路永久优先的单条冷存储查询

路由映射和长度约束从 backend/nginx/nginx.md 开始看。该文件还包含公开目录屏蔽、API/跳转限流、内部 18500 端口、PHP-FPM 映射、1-5 位跳转规则和 worker 初始化入口。


二、认证链路

标准链路

api/routes/*.php
  -> api/common/bootstrap.php
  -> auth_extract(RAW_INPUT, "api")
  -> auth_verify(raw, "api")
  -> KeyStore::verify()

无头链路

headless/*.php
  -> headless/bootstrap.php
  -> auth_extract(RAW_INPUT, "headless")
  -> auth_verify(raw, "headless")
  -> KeyStore::verify()
文件/函数阅读重点
api/key/auth_extract.php / auth_extract标准链路只接受 X-Tokenbody.key,Header 优先;无头只接受 X-Headless-Token
api/key/auth_verify.php / auth_verify生成统一 valid/type/reason 上下文;服务 Key 在标准链路触发 wrong_channel
api/key/auth_hash.php / auth_hash集中定义认证哈希算法
api/key/keys.php / KeyStore::verify常驻有效期、服务 Key、一次性消费、独立 .lock 锁和 hash_equals
api/key/keys.php / generateResident, fillPool, generateService, revoke, statusCLI Key 生命周期管理
api/key/base58.php / base58_encode, base58_decode32 字节随机 Key 的 Base58 编解码

一次性 Key 安全流

收到凭证
  -> 对原文做 auth_hash
  -> 锁定 keys.json.lock
  -> 常驻 / 服务 / 一次性槽恒定时间比较
  -> 一次性命中后把 key_hash 设为 null
  -> safe write keys.json
  -> 返回 type=onetime
  -> 之后才进入路由业务校验

这解释了为何已通过 Header 完成认证后,坏 JSON 或后续失败也会消费 Key。标准链路若仅把 Key 放在坏 JSON 的 body 中则无法提取凭证,也就不会消费。delete/list/stat 在 bootstrap 后显式禁止一次性 Key;create 允许;headless/get.php 当前未设置该禁令。


三、公共引导与配置

文件职责
api/config.php域名、panel_originpanel_enabled、时区、文件路径、普通/服务配额、服务短码长度、TTL 上限、保留字、内部 API 地址和令牌路径
api/common/bootstrap.php标准 JSON/CORS 头、面板开关、OPTIONS、数据访问检查、domain 检查、RAW_INPUT 缓存和认证
headless/bootstrap.php无头 JSON 头、OPTIONS、domain/数据检查、hl_error 和无头认证;不设置 CORS,不读取面板开关
api/common/helpers.php时间格式、过期判断、数据访问、KeyStore 单例、SSRF 和热同步重试

关键公共函数:

函数用途
formatIso8601支持固定偏移和 IANA 时区名称的 ISO 8601 输出
isExpired用 Unix 时间戳判断临时条目是否过期
cleanExpiredEntries写入流程中清理临时冷存储过期项
checkFileAccess / checkDataAccess文件存在时查可读写,不存在时查父目录可写
getKeyStore延迟创建共享 KeyStore 实例
isPrivateUrlURL host、IP 字面量、DNS A/AAAA 的 SSRF 防护
internalSetWithRetry热写最多 3 次指数退避重试
hl_error无头 {error,message} 输出并终止请求

四、创建链路

阅读 api/routes/create.phpheadless/create.php 时,按以下阶段定位:

  1. bootstrap 已完成数据访问检查与 Key 消费。
  2. 解码缓存的 RAW_INPUT
  3. 校验 URL 字符串、HTTP(S)、2048 长度和 SSRF。
  4. 解析严格整数 TTL,缺省为 0。
  5. 根据 $AUTH['type'] 选择普通或服务短码/配额。
  6. 永久、无自定义短码时从冷存储做 URL 严格去重。
  7. 校验自定义短码与保留字。
  8. JsonStore::withBothLocks 同时锁定永久/临时 Store,原子检查冲突、配额并写入一个 Store。
  9. internalSetWithRetry 同步 OpenResty 热存储。
  10. 返回固定 short_url/exp/dedup/synced/warning/key_consumed 字段。

无头创建的额外分支:服务 Key 可用 len 在 4/5 位自动短码间覆盖配置;自定义服务短码最长 5 位;创建使用 svc_perm_limit/svc_temp_limit

安全相关代码锚点:

风险入口
URL 类型/长度两个 create.php 的“验证目标链接”段
SSRFhelpers.php::isPrivateUrl,由两个创建入口调用
TTL 类型两个 create.phparray_key_exists('ttl', ...) 分支
跨 Store TOCTOUjson_store.php::withBothLocks
随机短码两个 create.phprandom_int 循环
总容量保护两个 create.php 的四类 limit 总和检查
热同步窗口helpers.php::internalSetWithRetryapi/lua/internal.php

五、删除、列表与统计

删除

api/routes/delete.phpheadless/delete.php

冷存储查类型
  -> 对目标 Store 加独立 .lock 排他锁
  -> 再读、再确认、删除、安全写入
  -> internalDelete(code, "perm"|"temp")
  -> {ok:true, synced:boolean}

冷存储提交后不因热删除失败而回滚。Lua 使用 PHP 传入的冷存储类型作为计数器递减的权威提示。

列表与单条查询

文件行为
api/routes/list.php冷存储列表,过滤过期临时项
headless/list.php同类行为,使用无头错误格式
headless/get.php永久优先,再查临时;过期返回 expired;条目结构损坏返回 data_corrupted

统计

api/routes/stat.phpheadless/stat.php 均先用 JsonStore::countActive() 计算冷存储权威值,再调用 internalStat() 获取热诊断值:

响应字段来源
perm_count, temp_countJSON 冷存储,有效条目遍历,权威
perm_limit, temp_limit普通配额配置
hot_available内部接口是否成功
hot_perm_count, hot_temp_countsu_meta 热计数,仅诊断且条件出现

标准 stat 还会记录热冷漂移日志;无头 stat 当前只在热不可用时记录日志。


六、冷存储

核心文件:api/storage/json_store.php

函数导航说明
read依赖原子 rename 的无锁完整读取;损坏时抛异常
write备份、.php.tmp、rename、读回验证、失败回滚
find, count, countActive查找与统计辅助
lockBegin, readLocked, writeLocked, lockEnd单 Store 原子读-检查-写
getPath为多 Store 固定锁序提供路径
withBothLocks同时锁永久和临时 Store,按路径排序避免死锁并消除跨 Store TOCTOU

冷存储 envelope:

{
  "v": 1,
  "at": "ISO 8601",
  "d": {
    "code": { "id": "code", "url": "https://...", "lurl": "https://.../code", "t": "临时项才有" }
  }
}

Key 存储的安全写入在 api/key/keys.php::writeLockedFile,使用相同的备份、临时文件、rename、验证和回滚思想,但锁定 keys.json.lock


七、PHP 到 OpenResty 的内部桥

核心文件:api/lua/internal.php

函数内部端点说明
readInternalToken-每次请求实时读取内部令牌,支持 CLI 轮换后立即生效
buildInternalHeaders-构建 LPA-Key Header
internalSetPOST /internal/setsu_url/su_exp
internalDeletePOST /internal/delete删除热条目并带类型提示
internalStatGET /internal/stat读取热计数
internalPost内部 POST 通用cURL、Content-Type、HTTP 和 JSON 校验
getLastInternalError-给 create/delete 记录同步诊断

内部接口同时依赖 Nginx IP allow/deny 和 LPA-Key。令牌文件路径由 api/config.php::internal_token_path 与 Nginx $su_internal_token_path 对齐。


八、OpenResty 热路径

文件关键入口职责
backend/lua/shorturl/internal.luadispatch验证内部令牌并分发 set/delete/stat
internal_set.luahandle二次验证 code/url/ttl/exp,写共享字典并维护计数
internal_delete.luahandle删除共享字典,优先使用 PHP 类型提示递减计数
internal_stat.luahandlesu_meta 返回热计数
redirect.luago纯内存查 su_url 并按配置 301/302 跳转
init.luainit_worker, initworker 定时器注册、首次请求冷启动、共享字典重建
expire_sweep.luarun 及恢复辅助清理过期项、从冷存储恢复被驱逐项、重算计数
config.lua配置表init_worker 上下文无法读取 ngx.var 时的定时配置
util/time.luaparse_iso8601, now_iso8601与服务器本地时区无关的 ISO 8601 解析/输出

热存储结构:

shared dict内容
su_urlcode -> target URL,临时项使用原生 TTL
su_expcode -> "0" 或 ISO 8601,用于类型和过期恢复
su_meta初始化锁、路径、计数、驱逐标记、定时间隔
su_blocklist内部接口认证失败的临时阻断状态

九、部署与 CLI

文件用途
nurl本机管理 Key、服务 Key、内部令牌、过期清理和状态
nurl-key通过无头 API 远程 list/stat/get/add/rm
backend/nginx/nginx.md推荐主配置与站点路由
backend/nginx/nginx - 副本.md仓库中的配置副本;修改部署模板时应与主文件核对差异
SECURITY_FIXES.mdv1.10.6 到 v1.11.0 的安全修复背景与威胁说明

SECURITY_FIXES.md 建议与源码一起阅读,重点覆盖:Lua 随机播种移除、共享字典驱逐恢复、常驻 Key 验证只读化、双 Store TOCTOU 修复、损坏 Key 文件拒绝覆盖、跨服务器时区的 ISO 8601 修复。


十、按问题检索

想排查的问题先看这些位置
为什么浏览器 CORS 失败api/config.phppanel_origin/panel_enabledapi/common/bootstrap.php、Nginx /api/limit_except
为什么返回 430两个 bootstrap 的 checkFileAccess 循环、服务器 error log
为什么一次性 Key 失败后也没了KeyStore::verify,再看 bootstrap 与路由的执行顺序
为什么服务 Key 在标准 API 失败auth_verify.phpwrong_channel
为什么服务短码能到 5 位headless/create.phpsvcCodeLength/maxCodeLen/len、Nginx 1-5 位路由
为什么创建成功但暂时跳不通internalSetWithRetryinternal.php、成功响应的 synced/warning
为什么删除成功但热缓存仍在delete 的冷先热后顺序、synced=falseinternal_delete.lua
为什么 stat 热冷不同PHP stat 的冷权威策略、internal_stat.luaexpire_sweep.lua
为什么 URL 被判为内网helpers.php::isPrivateUrl 的 IP/DNS 双栈检查
为什么并发创建没有跨 Store 重码JsonStore::withBothLocks
为什么 Nginx reload 后仍需恢复热数据init.lua::init_worker/initexpire_sweep.lua

本页目录