HCY Blog

技术知识点

本文介绍了在线C语言编译器的核心实现技术,重点解析了Monaco Editor的集成、智能缩进算法与浏览器持久化策略。读者可学习如何通过CDN直接引入编辑器、实现自适应布局与主题切换,掌握基于状态机的代码格式化方法,以及利用localStorage进行数据存储的技巧。

技术知识点

在线 C 语言编译器

  • Monaco Editor 集成
    • 项目未使用打包工具,直接通过 CDN 提供的 AMD loader 引入 Monaco:
require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.33.0/min/vs' } });
require(['vs/editor/editor.main'], () => {
  window.editor = monaco.editor.create(document.getElementById('editor'), {
    value: DEFAULT_CODE,
    language: 'c',
    theme: localStorage.getItem('editorTheme') === 'light' ? 'vs-light' : 'vs-dark',
    automaticLayout: true,
    minimap: { enabled: false },
    fontSize: 13
  });
});
  • 重点:使用 automaticLayout 保障窗口缩放时不需要手动 layout;使用 window.editor.trigger 绑定撤销/重做、注释切换等命令;通过 CSS 控制整体暗/明主题。

  • 面试补充:相比 textarea,Monaco 自带语法折叠、多光标、命令面板,提升在线编译器体验;项目中测试过 Chrome/Safari/Edge 的中文输入法,未出现光标漂移。

  • 智能缩进 / 格式化

    • 采用自定义的 smartCIndent 函数,对每行代码建立状态机,核心规则:
      1. 维护块栈统计 {} 层级;
      2. case/defaultdo...while 设置专属分支避免误缩;
      3. 遍历时跳过字符串、注释,保证内部花括号不干扰。
function smartCIndent(code, tabSize = 4) {
  const lines = code.split(/\r?\n/);
  let indent = 0;
  let inBlockComment = false, inString = false, stringChar = '';
  return lines.map((line) => {
    const trimmed = line.trim();
    if (trimmed.startsWith('}')) indent = Math.max(0, indent - 1);
    const formatted = ' '.repeat(tabSize * indent) + trimmed;
    // ...扫描字符更新 indent / 状态...
    if (trimmed.endsWith('{')) indent += 1;
    return formatted;
  }).join('\n');
}
  • 面试补充:若老师问为何不用 AST,可以说明 AST 解析器体积大且需要完整 C 标准库;纯栈 + 状态机已经覆盖主流语法结构,成本更低。

  • 浏览器持久化策略

    • 使用多个 localStorage Key 分别记录代码、主题、测试用例:
localStorage.setItem('deepseek_saved_c', window.editor.getValue());
localStorage.setItem('editorTheme', isDark ? 'dark' : 'light');
localStorage.setItem('test_cases_v1', JSON.stringify(cases));
  • 页面加载时先读取存档,fallback 到默认模板;在 beforeunload 事件中追加自动保存,减少意外丢失。

  • 面试补充:Safari 隐私模式会抛出 QuotaExceededError,因此所有写操作都包裹 try...catch 并在 UI 提醒用户。

  • Wandbox API 调用

    • 运行按钮触发 tryWandboxCompile,内部实现:
const wandboxUrl = 'https://wandbox.org/api/compile.json';
const resp = await fetch(wandboxUrl, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ code, compiler: 'gcc-head', stdin })
});
const json = await resp.json();
const out = (json.program_output || '') + (json.program_error ? `\n[stderr]\n${json.program_error}` : '');
stdoutEl.textContent = `${out}\n[exit code] ${json.status ?? 0}`;
  • 若请求失败(超时 / 网络 / HTTP 错误),直接在输出面板显示真实错误信息并写入控制台,无本地 WASM 回退,用真实情况教育用户。

  • 面试补充:通过 Promise.race + 手动超时避免长时间等待;在测试用例批量运行里沿用同一 API,统一错误处理经验。

  • URL hash 分享

    • 分享弹窗会把数据打包成 JSON 再 Base64:
function buildShareUrl(opts) {
  const payload = {
    code: window.editor.getValue(),
    stdin: opts.stdin ? stdinEl.value : undefined,
    cases: opts.cases ? cases : undefined,
    name: currentFilename
  };
  const b64 = btoa(unescape(encodeURIComponent(JSON.stringify(payload))));
  return `${location.origin}${location.pathname}#share=${b64}`;
}
  • 页面加载检查 location.hash,若识别到 #share= 前缀就解码恢复编辑器、stdin 和测试用例。

  • 面试补充:Base64 长度比原文短一截,但仍受浏览器 URL 长度限制;所以 UI 中提示“过长建议下载文件”。

  • Service Worker / PWA

    • 手写 sw.js 维护固定版本号,核心策略是 precache + cache-first:
const CACHE_NAME = `c-compiler-${VERSION}`;
self.addEventListener('fetch', (event) => {
  if (event.request.method !== 'GET') return;
  event.respondWith(
    caches.match(event.request).then((cached) => cached || fetch(event.request).then((resp) => {
      if (resp.status === 200) caches.open(CACHE_NAME).then((cache) => cache.put(event.request, resp.clone()));
      return resp;
    }).catch(() => caches.match(event.request)))
  );
});
  • 安装阶段缓存首页、Manifest、图片;激活时清理旧版本缓存,并在离线时返回友好文本提示。

  • 面试补充:没有借助 Workbox,便于完全掌控缓存逻辑;通过 scripts/version.js 的版本号手动触发缓存刷新。

  • GitHub Pages 发布

    • 项目无构建链路,直接把静态资源放在主分支根目录,GitHub Pages 自动部署。若要更新,只需 git push 主分支。
    • 面试补充:若老师问自动化,可说明未来计划增加轻量工作流生成压缩版资源;目前重点是快速迭代前端功能。

AI 医学辅助诊断系统

  • 多 Agent 协同流程
    • SymptomAnalyzer 将一次分析拆成搜索计划、证据检索、证据排序、临床规划、追问、中文翻译六个阶段,每个阶段都用 DeepSeek Agent + JSON Schema 约束输出结构。
result, raw = await self._run_agent(
    agent_name="ClinicalPlannerAgent",
    system_prompt="You design safe, evidence-aware triage plans for telemedicine workflows.",
    instructions=instructions,
    schema=schema,
    context=context,
    temperature=0.2,
)
  • 每个 Agent 的原始输出与结构化结果都会记录在 agent_traces,方便面试时解释“如何保证可追溯性”。当 LLM 返回空值或证据不足时,代码回退到预置安全方案,确保 API 不会直接返回空响应。

  • PubMed/NICE 抓取与过滤

    • knowledge.py 直接调用 NCBI E-utilities(esearch/esummary/efetch),并对返回文献做人口、物种和研究类型过滤:
      • 年龄段:根据患者年龄组构建包含/排除词,例如成人病例会自动排除 neonatalpediatric
      • 物种:串联 HUMAN_ONLY_FILTERVETERINARY_FILTER 和关键词黑名单(caninevet journal 等)。
      • 研究类型:优先保留指南、系统综述等高质量文献,检索不到时再回退到病例报告。
if item.category and "guideline" in item.category.lower():
    category_bonus = 0.25
    guideline_priority = True
  • 同时抓取 NICE 官方指南 HTML,提取标题、摘要和 PDF 链接,与 PubMed 结果合并后统一排序。

  • 证据打分与可视化

    • _rank_evidence 使用词覆盖度、证据新鲜度、研究类型、实验室指标匹配等特征打分,并给指南附加优先级;最终分数写回 EvidenceItem.score
    • _build_visualizations 将文献按年份聚合生成 ChartDatum 列表,前端据此渲染“年份柱状图 + 来源占比圆图”。这部分是手写 CSS 图表,面试时可强调“无第三方图表库,也能快速实现轻量可视化”。
  • 前端状态编排

    • 主页面 page.tsx 使用 React hooks 管理病例列表、追问草稿、分析结果、折叠状态,并通过 localStorage 在刷新后恢复:
const payload = { cases, caseCounter, activeCaseIndex, analyses: caseAnalyses, followupHistory, collapsedCases };
localStorage.setItem(CASE_STORAGE_KEY, JSON.stringify(payload));
  • 一旦收到 analysis.requires_followup=true,界面会锁定结果区,展示追问列表并记录历史追问轮次;补充回答后再调用 /api/v1/analyze 生成最终方案。

  • 进度追踪与下载接口

    • 每次请求在后台注册进度令牌,前端通过 setInterval 调用 /api/v1/progress/:token 显示实时进度条。
    • download_pubmed_asset 根据 PMID 调用 PubMed API 获取摘要或全文 PDF URL,封装成二进制响应返回前端,支持一键下载文献。
  • 部署与调试

    • 项目提供 deploy.sh,根据 git diff 判断本次需要同步的模块,并通过 systemd 重启 medagt-api(FastAPI)和 medagt-web(Next.js)。
    • 日志分散在前端浏览器控制台与后端 logging 输出,调试 Agent 行为时可以直接查看 agent_traces,定位是检索、排序还是规划阶段出现偏差。 说明:这种结构化格式方便后端做校验,例如检查 severity 是否在 mild/moderate/severe 范围内。老师如果问到多语言输入(中文症状如何转换成英文),可以说会结合自建词典或调用医学术语标准(如 SNOMED CT)将自然语言对应到统一编码。
  • 大模型安全策略

    • 提示模板中要求“必须引用检索证据编号”,并在返回后校验。如果模型输出未引用证据或涉及药物剂量等高风险内容,规则引擎会重写为“建议线下就医”。
if not has_reference(advice):
  advice = "当前证据不足,请建议患者尽快线下就医。"

说明:这个检查函数会解析模型输出中是否包含像 [1](PMID:xxxxxx) 之类的引文标注。老师若问到如何防止模型推荐违规药品,可以说会在规则引擎里增加黑名单词汇,发现敏感词就自动改写成“需要医生评估”。

  • 人工校准闭环
    • 审核界面展示模型建议、引用文献、置信度。医生点击“通过/修改/驳回”后,将最终意见写入审核日志表,为后续模型调优提供数据。
INSERT INTO review_logs(case_id, reviewer, final_decision, confidence)
VALUES (:case_id, :reviewer, :decision, :confidence);

说明:人工校准记录可以追踪每条建议的责任人,符合医疗合规要求。老师如果问“人手不够怎么办”,可以说会根据置信度分类,只有低置信度结果才必须人工审核,高置信度结果标注“未经人工复核”让医生快速浏览。

  • API 设计范例
    • 前端提交病例数据到 /api/cases, 后端返回 triage 信息和建议。接口遵循 REST 风格,返回的 JSON 包含决策解释和证据链。
POST /api/cases
{
  "case_id": "CASE-001",
  "symptoms": [ ... ]
}

HTTP/1.1 200 OK
{
  "triage": "Level 3",
  "recommendation": "建议 24 小时内就诊",
  "references": [ { "id": "PMID:123456" } ]
}

说明:接口返回的 Level 3 对应院内急诊分级标准,老师可能会问“这个等级从哪里来”,可以回答参考了国际常用的 ESI (Emergency Severity Index),并请医学顾问调整到本土标准。如果需要图像输入,也可以在 symptoms 中增加附件 URL,后端再连接影像识别服务。

  • 部署与安全要点
    • 后端部署在阿里云 ECS,预计接入 HTTPS 证书。为演示模式提供“本地存储”选项,把病例数据仅保留在浏览器,降低隐私风险。
sudo certbot --nginx -d medagt.example.com

说明:医疗项目对安全要求高,老师若问还有哪些安全措施,可以补充:部署时会打开阿里云 WAF,并将敏感日志透过 KMS 加密后储存。对于演示账号,会限制只能使用匿名病例,并在登入页加上告警提示。

  • 向量数据库维护
    • Milvus 定期导入最新文献摘要,使用定时任务重建索引。对低置信度检索结果进行人工标注并回填,提高后续检索准确率。
python scripts/update_embeddings.py --source pubmed.csv --collection med_papers

说明:这个脚本会重新计算向量并写入 Milvus。老师如果问到“如何确保同步过程不影响线上服务”,可以回答会使用灰度方案:先写入新集合,待检查完毕后再切换索引别名。若源数据格式变动,会先在测试环境演练。

  • DeepSeek LLM 集成
    • 通过 HTTP API 发送用户请求,设置温度较低(如 0.2)保证回答稳定可控。预留本地部署接口,以便未来在院内环境运行。
response = requests.post(
  'https://api.deepseek.com/v1/chat/completions',
  headers={'Authorization': f'Bearer {DEEPSEEK_KEY}'},
  json={
    'model': 'deepseek-medical',
    'messages': [
      { 'role': 'system', 'content': '你是医学问诊助手,必须引用证据。' },
      { 'role': 'user', 'content': prompt }
    ],
    'temperature': 0.2
  }
)

说明:温度设为 0.2 可以让回答更稳定。若老师问到“如果 API 失效怎么办”,可以说会设定重试机制与后备模型(例如本地部署版),并在监控面板透过 Prometheus + Grafana 跟踪请求成功率。一旦失败率升高,会自动切换到备援模式。