fix(阶段0): 端点提取器漏抓 5 个端点,补反向对账
提取器按文件名白名单扫 <app>/urls/{oj,admin}.py,漏掉两类真实挂载:
tutorial/urls/tutorial.py(文件名不在白名单)和 utils/urls.py(没有
urls/ 目录,被 statSync 的 catch 吞掉),共 5 个端点,其中 4 个前端在用。
改为以 OnlineJudge/oj/urls.py 为唯一入口解析 26 条 include,side 与路径
前缀直接取挂载前缀,app 取 Python 模块名首段。127(oj 77 / admin 50),
DEPRECATED 17,与 cat */urls/*.py utils/urls.py | grep -c "path(" 一致。
reconcile.ts 补上反向对账:前端调用了但后端查无此端点的路径会告警并写进
产出的 markdown,让同类系统性盲区不再依赖人眼评审。修完后 orphan 为 0。
另:生成日期改用本地时区(toISOString 是 UTC,本地 UTC+8 晚间会写成前一天);
补记盲点 3(src/utils/download.ts 的独立 axios 实例,提取器抓不到)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,26 +1,42 @@
|
||||
#!/usr/bin/env bun
|
||||
// 从 Django urls/*.py 提取后端端点全集
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs"
|
||||
import { readFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
|
||||
const ROOT = process.argv[2] ?? "../../OnlineJudge"
|
||||
|
||||
type Endpoint = { app: string; side: "oj" | "admin"; pattern: string; view: string; name: string; deprecated: boolean }
|
||||
|
||||
function findUrlFiles(root: string): string[] {
|
||||
const out: string[] = []
|
||||
for (const app of readdirSync(root)) {
|
||||
const dir = join(root, app, "urls")
|
||||
try {
|
||||
if (!statSync(dir).isDirectory()) continue
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
for (const f of readdirSync(dir)) {
|
||||
if (f === "oj.py" || f === "admin.py") out.push(join(dir, f))
|
||||
}
|
||||
// 唯一入口是 oj/urls.py —— 它才是 Django 真实的挂载表。
|
||||
//
|
||||
// 不要按文件名白名单(oj.py / admin.py)去猜 urls 文件:那样会漏掉两类真实存在的形态,
|
||||
// 1. urls/ 目录里文件名不叫 oj/admin 的,如 tutorial/urls/tutorial.py(3 个端点,前端在用)
|
||||
// 2. 根本没有 urls/ 目录、直接是模块文件的,如 utils/urls.py(2 个端点,其一前端在用)
|
||||
// 历史上这两类共 5 个端点被静默漏掉,其中 4 个前端在用。
|
||||
// 现在改为解析 oj/urls.py 的 include 清单,挂载前缀直接取自这里,不再从文件名反推 side。
|
||||
const INCLUDE_RE = /path\(\s*["'](api\/(?:admin\/)?)["']\s*,\s*include\(\s*["']([\w.]+)["']\s*\)/g
|
||||
|
||||
type Mount = { prefix: string; module: string; file: string; app: string; side: "oj" | "admin" }
|
||||
|
||||
function findMounts(root: string): Mount[] {
|
||||
const src = readFileSync(join(root, "oj", "urls.py"), "utf8")
|
||||
const mounts: Mount[] = []
|
||||
INCLUDE_RE.lastIndex = 0
|
||||
let m: RegExpExecArray | null
|
||||
while ((m = INCLUDE_RE.exec(src)) !== null) {
|
||||
const prefix = m[1]
|
||||
const mod = m[2]
|
||||
// Python 模块名 → 文件路径。"tutorial.urls.tutorial" → tutorial/urls/tutorial.py,
|
||||
// "utils.urls" → utils/urls.py。app 一律取模块名首段,不依赖目录层数。
|
||||
mounts.push({
|
||||
prefix,
|
||||
module: mod,
|
||||
file: join(root, ...mod.split(".")) + ".py",
|
||||
app: mod.split(".")[0],
|
||||
side: prefix === "api/admin/" ? "admin" : "oj",
|
||||
})
|
||||
}
|
||||
return out.sort()
|
||||
return mounts
|
||||
}
|
||||
|
||||
// 必须跨行匹配:18 处 path( 的参数换行写,按行扫会漏
|
||||
@@ -33,12 +49,9 @@ const PATH_RE = /path\(\s*r?["']([^"']*)["']\s*,\s*([A-Za-z_][\w.]*)/g
|
||||
const NAME_RE = /name\s*=\s*["']([^"']+)["']/
|
||||
|
||||
const endpoints: Endpoint[] = []
|
||||
for (const file of findUrlFiles(ROOT)) {
|
||||
const parts = file.split("/")
|
||||
const app = parts[parts.length - 3]
|
||||
const side = parts[parts.length - 1] === "oj.py" ? "oj" : "admin"
|
||||
const mounts = findMounts(ROOT)
|
||||
for (const { file, app, side, prefix } of mounts) {
|
||||
const src = readFileSync(file, "utf8")
|
||||
const prefix = side === "oj" ? "/api/" : "/api/admin/"
|
||||
|
||||
// 按括号深度找出每个 path(...) 的完整片段,再连同该行剩余部分(行尾注释可能带
|
||||
// DEPRECATED 标记)一起分析。单纯用正则切片会在 .as_view() 的右括号处截断。
|
||||
@@ -68,7 +81,7 @@ for (const file of findUrlFiles(ROOT)) {
|
||||
endpoints.push({
|
||||
app,
|
||||
side,
|
||||
pattern: prefix + m[1],
|
||||
pattern: "/" + prefix + m[1],
|
||||
view: m[2],
|
||||
name: chunk.match(NAME_RE)?.[1] ?? "",
|
||||
deprecated: /DEPRECATED/.test(chunk),
|
||||
@@ -78,6 +91,7 @@ for (const file of findUrlFiles(ROOT)) {
|
||||
|
||||
const oj = endpoints.filter((e) => e.side === "oj").length
|
||||
const dep = endpoints.filter((e) => e.deprecated).length
|
||||
console.log(`挂载点 ${mounts.length} 个(来自 oj/urls.py 的 include)`)
|
||||
console.log(`后端端点合计 ${endpoints.length} (oj ${oj} / admin ${endpoints.length - oj})`)
|
||||
console.log(`其中已标 DEPRECATED: ${dep}`)
|
||||
await Bun.write("endpoints-backend.json", JSON.stringify(endpoints, null, 2))
|
||||
|
||||
Reference in New Issue
Block a user