You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
156 lines
8.7 KiB
156 lines
8.7 KiB
import json, re
|
|
|
|
with open("scripts/openapi.json", encoding="utf-8") as f:
|
|
spec = json.load(f)
|
|
real_paths = set()
|
|
real_path_methods = {}
|
|
for p, methods in spec.get("paths", {}).items():
|
|
real_paths.add(p)
|
|
for m in methods:
|
|
real_path_methods.setdefault(p, []).append(m.upper())
|
|
|
|
with open("src/api/api.ts", encoding="utf-8") as f:
|
|
lines = f.readlines()
|
|
|
|
func_re = re.compile(r"^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(")
|
|
static_req = re.compile(r"request(?:<[^>]*>)?\(\s*['\"]([^'\"]+)['\"]")
|
|
tmpl_req = re.compile(r"request(?:<[^>]*>)?\(\s*`([^`]*)`")
|
|
dai_re = re.compile(r"【待确认】")
|
|
|
|
cur = None
|
|
funcs = {}
|
|
for i, ln in enumerate(lines):
|
|
m = func_re.match(ln)
|
|
if m:
|
|
cur = m.group(1)
|
|
funcs[cur] = {"static": [], "tmpl": [], "dai": False, "line": i+1}
|
|
if cur:
|
|
for sm in static_req.finditer(ln):
|
|
funcs[cur]["static"].append(sm.group(1))
|
|
for tm in tmpl_req.finditer(ln):
|
|
funcs[cur]["tmpl"].append(tm.group(1))
|
|
if dai_re.search(ln):
|
|
funcs[cur]["dai"] = True
|
|
|
|
# Manual real-equivalent judgment for template/partial/path-wrong functions
|
|
# value: (verdict, real_path_or_None, note)
|
|
manual = {
|
|
# template literals - verify
|
|
"createJobReview": ("ALIGNED", "POST /jobs/{jobId}/reviews", "路径命中真实契约"),
|
|
"fetchEmployeeReviews": ("ALIGNED", "GET /jobs/{jobId}/reviews", "路径命中真实契约"),
|
|
"fetchJobDetail": ("ALIGNED", "GET /jobs/{jobId}", "路径命中真实契约"),
|
|
"fetchCandidateDetail": ("WRONG->REAL", "/company/candidates/{applicationId}", "前端猜 /enterprise/candidates,真路径在 /company 域"),
|
|
"fetchInterviewScore": ("WRONG->REAL", "GET /interviews/{interviewId}/score", "前端猜 /enterprise/interviews,真路径在 /interviews 域"),
|
|
"fetchInterviewScoreDraft": ("WRONG->REAL", "PUT /interviews/{interviewId}/score", "真路径无 /draft 子路径,保存草稿用 PUT /interviews/{id}/score"),
|
|
"fetchConversation": ("WRONG->REAL", "GET /conversations/{id}", "前端猜 /chat/conversations,真路径 /conversations/{id}"),
|
|
"deleteSalaryRecord": ("PHANTOM", None, "无 /salary 域,后端未提供"),
|
|
"fetchLegalDocument": ("PHANTOM", None, "无 /legal 域,后端未提供"),
|
|
"fetchPrivacyDetailSetting": ("PHANTOM", None, "无 /settings 域,后端未提供"),
|
|
"fetchSalaryData": ("PHANTOM", None, "无 /jobs/{id}/salary,后端未提供薪资明细端点"),
|
|
"fetchJobOverallScore": ("PHANTOM", None, "无 /jobs/{id}/score,综合分见 /jobs/{id}/competition-ranking 或 /profile/me/candidate-scoring"),
|
|
# partial static
|
|
"fetchJobPreference": ("WRONG->REAL", "GET/PUT /profile/me/preferences", "前端猜 /profile/me/job-preference,真路径 /profile/me/preferences"),
|
|
"saveJobPreference": ("WRONG->REAL", "PUT /profile/me/preferences", "同上"),
|
|
"fetchPendingOfferCount": ("PHANTOM", None, "无 /offers/pending/count,可用 /offers/statistics 替代(需确认)"),
|
|
"fetchRankData": ("WRONG->REAL", "GET /jobs/{jobId}/competition-ranking", "前端猜 /jobs/rank,真为按岗位竞争力排名,语义不同(需 jobId)"),
|
|
"fetchWeightSettings": ("PHANTOM", None, "无 /jobs/weight-settings,权重见 /profile/me/review-weights、/review-weight-templates"),
|
|
# already-correct static
|
|
"fetchJobList": ("ALIGNED", "GET /jobs", "命中真实契约"),
|
|
"fetchOfferCompareData": ("ALIGNED", "GET /offers/compare", "命中真实契约"),
|
|
"fetchUserProfile": ("ALIGNED", "GET /profile/me", "命中真实契约"),
|
|
# path-wrong (phantom base) that have real equivalents elsewhere
|
|
"fetchConversations": ("WRONG->REAL", "GET /conversations", "前端猜 /messages/conversations,真 /conversations"),
|
|
"fetchNotifications": ("WRONG->REAL", "GET /notifications", "前端猜 /messages/notifications,真 /notifications"),
|
|
"fetchInterviews": ("WRONG->REAL", "GET /interviews/me", "前端猜 /messages/interviews,真 /interviews/me(我的面试列表)"),
|
|
"fetchMatchResult": ("WRONG->REAL", "GET /applications/{id}/match 或 /jobs/{jobId}/match", "前端猜 /messages/match-result,真为投递/岗位匹配诊断"),
|
|
"fetchResume": ("WRONG->REAL", "GET /profile/me/resume", "前端猜 /resume,真 /profile/me/resume(下载简历)"),
|
|
"fetchFavorites": ("WRONG->REAL", "GET /jobs/favorites", "前端猜 /profile/me/favorites,真 /jobs/favorites"),
|
|
"fetchJobProgress": ("WRONG->REAL", "GET /profile/me/career-progress", "前端猜 /tools/job-progress,真 /profile/me/career-progress(统一求职进度聚合)"),
|
|
"fetchResumeSavedAt": ("PHANTOM", None, "无 /resume/saved-at,简历保存时间可由 /profile/me/resume-versions 派生"),
|
|
"fetchResumeExportSettings": ("PHANTOM", None, "无 /resume/export-settings,导出设置本地维护"),
|
|
"saveResumeBasicInfo": ("WRONG->REAL", "POST /profile/me/resume (multipart)", "前端猜 /resume/basic-info,真为上传简历文件(multipart),结构化字段在 PUT /profile/me"),
|
|
"saveResumeEducation": ("PHANTOM", None, "无 /resume/education,简历整体由 /profile/me/resume + resume-versions 管理"),
|
|
"saveResumeExperience": ("PHANTOM", None, "同上"),
|
|
"saveResumeProjects": ("PHANTOM", None, "同上"),
|
|
"saveResumeSelfEvaluation": ("PHANTOM", None, "同上"),
|
|
"saveResumeSkills": ("PHANTOM", None, "同上"),
|
|
"saveResumeSnapshot": ("PHANTOM", None, "同上"),
|
|
"saveSalaryRecord": ("PHANTOM", None, "无 /salary/records"),
|
|
"saveWeightSettings": ("PHANTOM", None, "无 /weight-settings"),
|
|
}
|
|
|
|
def base_has_real(p):
|
|
segs = [s for s in p.split("/") if s and "{" not in s and "$" not in s]
|
|
if not segs:
|
|
return False
|
|
base = "/" + segs[0]
|
|
return any(rp == base or rp.startswith(base + "/") for rp in real_paths)
|
|
|
|
verdicts = {}
|
|
for name, info in funcs.items():
|
|
if not info["static"] and not info["tmpl"]:
|
|
continue
|
|
if name in manual:
|
|
v, rp, note = manual[name]
|
|
verdicts[name] = (v, rp, note, info["dai"], info["line"])
|
|
continue
|
|
# auto
|
|
paths = info["static"] + info["tmpl"]
|
|
# for static-only auto
|
|
allphantom = True
|
|
anyok = False
|
|
for p in info["static"]:
|
|
if p in real_paths:
|
|
anyok = True; allphantom = False
|
|
elif base_has_real(p):
|
|
allphantom = False
|
|
if anyok:
|
|
verdicts[name] = ("ALIGNED", ", ".join(info["static"]), "命中真实契约", info["dai"], info["line"])
|
|
elif allphantom is False and info["static"]:
|
|
verdicts[name] = ("PHANTOM", ", ".join(info["static"]), "父域不存在于 OpenAPI", info["dai"], info["line"])
|
|
else:
|
|
verdicts[name] = ("PHANTOM", ", ".join(paths), "未命中 OpenAPI", info["dai"], info["line"])
|
|
|
|
# also catch functions flagged dai but no request captured (e.g. buildPrivacySummaryText) - skip
|
|
|
|
order = {"ALIGNED":0, "WRONG->REAL":1, "PHANTOM":2}
|
|
rows = sorted(verdicts.items(), key=lambda kv: (order.get(kv[1][0],9), kv[0]))
|
|
|
|
from collections import Counter
|
|
c = Counter(v[0] for v in verdicts.values())
|
|
print("SUMMARY:", dict(c), "TOTAL FUNCS MAPPED:", len(verdicts))
|
|
|
|
md = ["# 联调路径校正映射表(基于 job.youthome.com.cn OpenAPI 实测)", ""]
|
|
md.append(f"> 生成日期:2026-09-28 | 数据源:`scripts/openapi.json`(182 条真实路径 / 220 path-methods)")
|
|
md.append(f"> 审计范围:api.ts 内全部 `request()` 调用({len(verdicts)} 个函数)")
|
|
md.append(f"> 结论:ALIGNED(已对齐)= {c.get('ALIGNED',0)},WRONG→REAL(路径错但有真实等价)= {c.get('WRONG->REAL',0)},PHANTOM(幽灵域,后端无契约)= {c.get('PHANTOM',0)}")
|
|
md.append("")
|
|
md.append("## 一、已对齐(无需改路径)")
|
|
md.append("")
|
|
md.append("| 函数 | 真实路径 | 原标记 |")
|
|
md.append("|---|---|---|")
|
|
for n,(v,rp,note,dai,line) in rows:
|
|
if v=="ALIGNED":
|
|
md.append(f"| `{n}` | `{rp}` | {'【待确认】' if dai else '已校正'} |")
|
|
md.append("")
|
|
md.append("## 二、路径错但有真实等价端点(需改路径)")
|
|
md.append("")
|
|
md.append("| 函数 | 当前(错) | 应改为(真实) | 说明 |")
|
|
md.append("|---|---|---|---|")
|
|
for n,(v,rp,note,dai,line) in rows:
|
|
if v=="WRONG->REAL":
|
|
curpath = ", ".join(funcs[n]["static"]+funcs[n]["tmpl"])
|
|
md.append(f"| `{n}` | `{curpath}` | `{rp}` | {note} |")
|
|
md.append("")
|
|
md.append("## 三、幽灵域(后端 OpenAPI 无契约,必须退回 mock-only)")
|
|
md.append("")
|
|
md.append("| 函数 | 当前(幽灵)路径 | 说明 |")
|
|
md.append("|---|---|---|")
|
|
for n,(v,rp,note,dai,line) in rows:
|
|
if v=="PHANTOM":
|
|
curpath = ", ".join(funcs[n]["static"]+funcs[n]["tmpl"])
|
|
md.append(f"| `{n}` | `{curpath}` | {note} |")
|
|
md.append("")
|
|
with open("docs/联调路径校正映射.md", "w", encoding="utf-8") as f:
|
|
f.write("\n".join(md))
|
|
print("WROTE docs/联调路径校正映射.md")
|
|
|