構(gòu)化電子病歷生成實戰(zhàn))
簡介這是一套基于 Vue 與 SpringBoot 構(gòu)建的智慧醫(yī)院就診系統(tǒng)完整畢業(yè)設(shè)計資源包面向醫(yī)療信息化方向的高校學(xué)生、Java 全棧開發(fā)者及醫(yī)院信息系統(tǒng)技術(shù)人員。系統(tǒng)覆蓋預(yù)約掛號、智能問診、醫(yī)生工作臺、科室排班、患者服務(wù)、系統(tǒng)日志與權(quán)限管理等核心模塊并將 DeepSeek 大語言模型融入診療流程創(chuàng)新實現(xiàn)癥狀自查、結(jié)構(gòu)化電子病歷生成與臨床建議輔助有助于減少醫(yī)生非臨床工作負擔(dān)。資源共 710 個文件以 Java 源碼、Vue 組件、JavaScript 腳本、CSS 樣式、SQL 數(shù)據(jù)庫腳本及說明文檔為主另含圖片和動態(tài)演示素材壓縮包約 10.08MB目錄結(jié)構(gòu)清晰便于按模塊閱讀和二次開發(fā)。當(dāng)前已有 243 人學(xué)習(xí)適合用于理解醫(yī)院就診業(yè)務(wù)閉環(huán)也可作為畢業(yè)設(shè)計答辯演示、功能擴展或代碼重構(gòu)的參考基礎(chǔ)。1. 把 VueSpringBoot 就診系統(tǒng)講透DeepSeek 癥狀自查與電子病歷生成的落地方案做智慧醫(yī)院就診系統(tǒng)最難的不是掛號、排隊叫號那些常規(guī)模塊而是“怎么讓系統(tǒng)看起來真的懂醫(yī)療”。大部分畢業(yè)設(shè)計開源項目止步于 CRUD 增刪改查界面再華麗業(yè)務(wù)邏輯還是“查表 狀態(tài)機”。這套基于 Vue 3 SpringBoot 3 的就診系統(tǒng)源碼真正不一樣的地方在于接入了 DeepSeek 大語言模型把癥狀自查、結(jié)構(gòu)化電子病歷生成、臨床建議輔助這三件事做成了可運行的功能而不是 PPT 上的概念。我拆完整個前后端代碼和數(shù)據(jù)庫腳本后確認(rèn)它適合兩類人一類是正在做畢設(shè)但不想只交“管理系統(tǒng)”的學(xué)生另一類是準(zhǔn)備把 LLM 塞進傳統(tǒng)業(yè)務(wù)系統(tǒng)、想少走彎路的 Java 工程師。你能直接看到模型接口怎么封裝、業(yè)務(wù)層怎么兜底、前端流式輸出怎么接這比從頭讀文檔高效太多。整套系統(tǒng)覆蓋了患者端、醫(yī)生端和管理員端三個角色核心鏈路是“患者描述癥狀 → DeepSeek 返回候選疾病和問診建議 → 醫(yī)生選擇生成結(jié)構(gòu)化電子病歷 → 系統(tǒng)給出臨床建議”。源碼里帶有完整數(shù)據(jù)庫建表腳本MySQL包含用戶表、預(yù)約表、病歷表、藥品表、科室表等視圖和存儲過程也有幾個能直接跑通預(yù)約掛號、門診就診、病歷歸檔的閉環(huán)。我前后花了一個多星期把每個模塊從“能跑”調(diào)試到“能講”下面這篇筆記按資源是什么、結(jié)構(gòu)怎么拆、DeepSeek 怎么接、病歷和癥狀自查怎么落地、坑在哪、怎么把系統(tǒng)做亮這幾個順序把這份資源徹底講清楚。2. 系統(tǒng)架構(gòu)與代碼包結(jié)構(gòu)先看清前后端邊界和 LLM 的介入位置2.1 三層架構(gòu)里的模型調(diào)用設(shè)計為什么是 Vue3 SpringBoot 而不是單體模板引擎?zhèn)鹘y(tǒng)就診系統(tǒng)常做在 JSP Servlet 或 Thymeleaf 模板里頁面跳來跳去前后端耦合嚴(yán)重。這套源碼采用前后端分離SpringBoot 提供 RESTful APIVue 3 通過 axios 發(fā)起請求。這樣設(shè)計不是圖“時髦”而是因為接入 DeepSeek 后前端需要處理流式輸出和異步狀態(tài)模板引擎在這種交互下會非常別扭。后端模塊我拆開看主要分這幾塊authentication基于 JWT 的登錄鑒權(quán)醫(yī)生和患者走不同角色攔截器business掛號預(yù)約、分診、收費、藥房等傳統(tǒng)模塊aiDeepSeek 調(diào)用封裝包括 prompt 構(gòu)建、響應(yīng)解析、異常兜底medical病歷管理、診斷建議、檢驗報告的結(jié)構(gòu)化存儲單看模塊劃分不稀奇關(guān)鍵在于 ai 模塊的“隔離性”。項目把大模型調(diào)用單獨放在一個 service 里前端所有 AI 功能都經(jīng)由/api/ai/*統(tǒng)一入口進入不進業(yè)務(wù)表直接寫數(shù)據(jù)庫。這樣做有實際意義模型輸出是不可控的萬一某次返回了非 JSON 內(nèi)容你不會把臟數(shù)據(jù)污染到病歷表里前端也可以根據(jù)接口狀態(tài)單獨展示“AI 生成中”的 Loading而不會拖慢掛號接口。前端項目結(jié)構(gòu)里重點看src/api和src/views/ai兩個目錄。前者是接口統(tǒng)一封裝層后者是癥狀自查和病歷生成的前端交互頁面。Vue 3 用組合式 API 寫邏輯狀態(tài)管理只用了一個極簡的 pinia store 來存用戶信息和 JWT沒有過度設(shè)計。提示資源里自帶的 README 有早期版本說明里面“引入 DeepSeek”這幾個字實際實現(xiàn)比說明文檔多了不少細節(jié)建議以源碼實際行為為準(zhǔn)。2.2 數(shù)據(jù)庫表設(shè)計病歷表、用戶表、癥狀表如何支撐“結(jié)構(gòu)化”需求結(jié)構(gòu)化電子病歷的“結(jié)構(gòu)化”不是指前端把文本塞進textarea就叫結(jié)構(gòu)化而是數(shù)據(jù)庫里要有明確的字段承接不同粒度的醫(yī)療數(shù)據(jù)。打開sql目錄里的建表腳本核心表有這么幾張user患者和醫(yī)生共用一張表用role字段區(qū)分存姓名、手機號、加密后的登錄密碼department科室表關(guān)聯(lián)醫(yī)生排班appointment預(yù)約掛號表包含時間槽和就診狀態(tài)medical_record病歷主表涵蓋主訴、現(xiàn)病史、既往史、初步診斷、處理建議和開藥信息symptom_check癥狀自查記錄表存用戶輸入的自然語言癥狀文本和模型返回的結(jié)構(gòu)化結(jié)果JSON 字段ai_logAI 調(diào)用日志表記每次請求的輸入 token、輸出 token 和耗時這里的symptom_check和ai_log兩張表是這套資源區(qū)別于普通管理系統(tǒng)的地方。symptom_check表里有一個result_json字段類型用了TEXT專門承接 DeepSeek 返回的嵌套 JSON 數(shù)據(jù)——包括候選疾病列表、匹配度評分、建議就診科室。這不是偷懶的做法因為模型輸出結(jié)構(gòu)不固定用TEXT存 JSON 比強行拆幾十個字段更現(xiàn)實讀取后再用 Jackson 解析成 Java 對象。ai_log表值得一提查問題的時候你能回看每一次模型調(diào)用哪些 prompt 導(dǎo)致了 token 浪費、哪些輸入讓模型回復(fù)了非預(yù)期內(nèi)容一目了然。這是從業(yè)者的習(xí)慣很多畢設(shè)根本沒有這個表。2.3 核心參數(shù)配置application.yml 里的模型與數(shù)據(jù)庫連接解讀后端src/main/resources/application.yml里除了常規(guī)的 MySQL 數(shù)據(jù)源、Redis 配置、JWT 密鑰還有一個獨立的deepseek配置段deepseek: api-key: ${DEEPSEEK_API_KEY:your-key-here} base-url: https://api.deepseek.com model: deepseek-chat max-tokens: 2048 temperature: 0.3 timeout: 60這段配置的意思是API Key 通過環(huán)境變量DEEPSEEK_API_KEY注入避免把密鑰寫死在代碼里base-url是 DeepSeek 官方接口地址model指定使用deepseek-chat這是 DeepSeek 對外提供的通用對話模型支持顯式上下文max-tokens控制單次返回的最大 token 數(shù)temperature0.3是為了讓輸出更穩(wěn)定、更少隨機。醫(yī)療場景不像寫詩不需要高溫度值溫度調(diào)低一點模型輸出的可預(yù)測性高很多timeout設(shè)成 60 秒是考慮到長文本病歷生成時模型推理時間可能超過普通 HTTP 請求的默認(rèn)超時。數(shù)據(jù)庫連接部分你根據(jù)自己的 MySQL 版本改url、username、password就行。資源自帶的是 MySQL 5.7 適用的建表語句如果要跑在 MySQL 8.0注意默認(rèn)密碼插件不同驅(qū)動版本選對即可。3. DeepSeek 接入實戰(zhàn)從 HTTP 封裝到流式輸出的完整鏈路3.1 AI 接口封裝用 WebClient 調(diào) DeepSeek Chat Completion 接口后端 AI 模塊核心代碼是封裝了 DeepSeek 的 Chat Completion 接口。這里不用 RestTemplate因為它是同步阻塞模型高并發(fā)場景下容易吃滿 Tomcat 線程。SpringBoot 3 自帶的 WebClient 走響應(yīng)式鏈路單次調(diào)用不阻塞業(yè)務(wù)線程。下面這段是資源里DeepSeekClient.java的核心邏輯精簡版Service public class DeepSeekClient { private final WebClient webClient; private final DeepSeekProperties properties; public DeepSeekClient(DeepSeekProperties properties) { this.properties properties; this.webClient WebClient.builder() .baseUrl(properties.getBaseUrl()) .defaultHeader(Authorization, Bearer properties.getApiKey()) .defaultHeader(Content-Type, application/json) .codecs(configurer - configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024)) .build(); } public String chatCompletion(ListChatMessage messages) { MapString, Object requestBody new HashMap(); requestBody.put(model, properties.getModel()); requestBody.put(messages, messages); requestBody.put(temperature, properties.getTemperature()); requestBody.put(max_tokens, properties.getMaxTokens()); requestBody.put(stream, false); return webClient.post() .uri(/chat/completions) .bodyValue(requestBody) .retrieve() .bodyToMono(String.class) .timeout(Duration.ofSeconds(properties.getTimeout())) .block(); } }邏輯說明WebClient在構(gòu)造時讀配置里的地址和 Key每次調(diào)用走POST /chat/completions。關(guān)鍵參數(shù)messages是數(shù)組每個數(shù)組元素帶rolesystem / user和content這是 DeepSeek 接口識別對話角色的標(biāo)準(zhǔn)方式。system角色用于設(shè)定 AI 行為user角色用于傳用戶輸入。streamfalse表示非流式返回整體把模型回答一次性拿回來適合后期解析 JSON如果做前端打字機效果需要改用流式streamtrue返回 SSE并在前端用fetch讀ReadableStream。參數(shù)說明maxInMemorySize別小看不配的話默認(rèn) 256KB病歷生成動輒幾千 token 很容易超限報錯這也是一個典型坑。block()方法在 WebClient 里是同步等待好在timeout兜底了最長等待時間。3.2 Prompt 構(gòu)建與業(yè)務(wù)解析讓模型按 JSON Schema 返回調(diào)通接口只是第一步真正難的是讓模型輸出“可解析的”結(jié)構(gòu)化內(nèi)容。源碼里的PromptBuilder.java把整個 prompt 模板和 JSON Schema 固定下來我看完之后強烈建議你直接抄這份模板而不是自己現(xiàn)造。public String buildSymptomCheckPrompt(String userInput) { return 你是一名具有豐富臨床經(jīng)驗的導(dǎo)診醫(yī)生。 用戶的癥狀描述如下 userInput 請嚴(yán)格按以下 JSON 格式回復(fù)不要輸出任何多余文字 {\candidate_diseases\: [{\disease_name\: \疾病名稱\, \probability\: 0.85, \department\: \建議就診科室\}], \questions\: [\為確認(rèn)診斷你需要追問患者的問題1\, \追問問題2\], \urgent\: false}; }邏輯說明system提示詞固定了“醫(yī)生”角色user內(nèi)容是對用戶輸入的封裝。JSON Schema在 prompt 里的作用是告訴模型輸出結(jié)構(gòu)——候選疾病數(shù)組帶疾病名、概率、科室追問問題數(shù)組以及一個urgent布爾值表示是否需要立即急診。這個結(jié)構(gòu)要和前端 Vue 頁面里的v-for列表相對應(yīng)。參數(shù)說明probability用 0-1 小數(shù)方便前端渲染百分比urgent字段直接驅(qū)動分診邏輯——為 true 時界面跳出紅色警示條。candidate_diseases數(shù)組長度最好限制 3 到 5 個太多會讓患者焦慮太少參考價值低。解析端用 Jackson 把模型返回字符串反序列化成SymptomCheckResult對象如果解析失敗則走兜底邏輯。兜底邏輯很重要模型偶爾會輸出純文本或 Markdown直接轉(zhuǎn)對象會拋異常項目里專門寫了一個fallbackParse()把文本按回車拆成臨時疾病描述保證用戶永遠看得到反饋而不是報錯白屏。3.3 流式輸出的可選實現(xiàn)SSE 讓病歷生成像“打字機”一樣展示資源里的非流式實現(xiàn)已經(jīng)夠完成畢設(shè)但如果你想拔高亮點可以把病歷生成改成走 SSEServer-Sent Events。很多現(xiàn)場演示翻車就是因為點擊“生成病歷”后界面白等 10 秒沒反饋評委以為系統(tǒng)死掉了。改成流式以后用戶能實時看到模型吐字體驗完全不同。后端在DeepSeekClient里加一個流式方法走WebClient的retrieve().bodyToFlux(String.class)響應(yīng)體是text/event-stream格式前端用原生EventSource或fetch讀流。核心改動就兩點請求體里streamtrue以及前端不再等完整 JSON而是每收到一個data:塊就 append 到頁面。但這有個代價——流式輸出沒法保證一次返回完整 JSON所以自動生成結(jié)構(gòu)化病歷不能直接用流式結(jié)果入庫比較合理的做法是“流式展示 完整返回入庫”雙通道前端展示用流式接口落庫用非流式接口二次調(diào)用。資源里非流式版本已經(jīng)能跑通全流程我建議先跑通再去改流式。4. 癥狀自查與結(jié)構(gòu)化電子病歷生成把模型輸出變成業(yè)務(wù)資產(chǎn)4.1 癥狀自查模塊前端輸入與后端結(jié)果剪裁前端頁面SymptomCheck.vue的思路很簡單一個多行文本輸入框用戶用自然語言描述癥狀比如“最近三天頭暈、頭痛、惡心伴隨腹瀉”提交后后端把這段文本連同病歷上下文拼進 prompt發(fā)給 DeepSeek返回候選疾病概率列表和追問問題。頁面核心代碼如下template div classsymptom-check el-input typetextarea v-modelsymptomText placeholder請描述你的癥狀比如頭痛、發(fā)燒、咳嗽持續(xù)兩天 :rows4 / el-button typeprimary :loadingloading clicksubmitSymptom {{ loading ? AI 分析中… : 開始自查 }} /el-button div v-ifcheckResult classresult-panel div v-fordisease in checkResult.candidate_diseases :keydisease.disease_name span{{ disease.disease_name }}/span span{{ Math.round(disease.probability * 100) }}%/span el-tag{{ disease.department }}/el-tag /div /div /div /template script setup import { ref } from vue import { checkSymptom } from /api/ai const symptomText ref() const loading ref(false) const checkResult ref(null) async function submitSymptom() { loading.value true try { const res await checkSymptom({ text: symptomText.value }) checkResult.value res.data } finally { loading.value false } } /script邏輯說明checkSymptom調(diào)用后端/api/ai/symptom-check后端返回的對象里有candidate_diseases、questions、urgent三個字段。模板里用v-for循環(huán)候選疾病并展示概率和科室如果urgent為 true再加一條el-alert提示緊急情況。這個頁面在演示時非常出效果尤其是概率數(shù)字滾動出現(xiàn)的時候。參數(shù)說明:loading在請求期間鎖住按鈕防止重復(fù)提交接口層要加錯誤處理因為 DeepSeek 偶爾超時超時后給用戶提示“網(wǎng)絡(luò)不穩(wěn)定請重試”。千萬不要把模型超時錯誤直接透傳到界面看起來很不專業(yè)。4.2 結(jié)構(gòu)化電子病歷從聽診記錄到字段落庫醫(yī)生端“生成病歷”功能的核心價值是在醫(yī)生填完主訴和現(xiàn)病史后系統(tǒng)能幫他把這些自然語言文本自動擴展成一份可查詢的病歷草稿。源碼里AutoGenerateMedicalRecord()這個服務(wù)的邏輯是拼一個要求模型“生成結(jié)構(gòu)化 SOAP 格式病歷”的 prompt然后把模型輸出按{subjective: 主訴, objective: 體格檢查, assessment: 診斷依據(jù), plan: 治療建議}的 JSON 結(jié)構(gòu)拆開落到medical_record表對應(yīng)的字段里。需要注意模型生成的內(nèi)容再完整也不能直接蓋過醫(yī)生手寫結(jié)論。資源設(shè)計里有個很聰明的點AI 生成的內(nèi)容只作為“草稿”預(yù)填到表單醫(yī)生最終要點擊“確認(rèn)保存”后才會落庫。這避免了“AI 寫的診斷被當(dāng)成醫(yī)生確診”這種在醫(yī)療場景里不可接受的錯誤。如果你在答辯時被問到“AI 出錯怎么辦”這個設(shè)計就是你最好的回答模型只是輔助錄入權(quán)威性在醫(yī)生手里。落庫后病歷數(shù)據(jù)會被整理成record_view視圖關(guān)聯(lián)患者檔案、處方、檢查單前端“歷史病歷”頁面直接查這些關(guān)聯(lián)表還能做關(guān)鍵詞搜索。這套數(shù)據(jù)模型雖然不是國標(biāo)級別的結(jié)構(gòu)化但足以支撐完整的業(yè)務(wù)演示。4.3 AI 日志與 token 成本控制別讓演示崩在余額上面一次癥狀自查平均消耗 300-500 token一次完整病歷生成可能消耗 1000-2000 token。如果你把 demo 演示沓三五次賬戶余額就肉眼可見地往下掉。項目里的ai_log表除了記內(nèi)容還記錄每次調(diào)用消耗的 token 數(shù)和耗時。我建議在調(diào)通后把max-tokens從 2048 降到 1024并給 prompt 里加一句“控制在 300 字以內(nèi)”對畢設(shè)演示完全夠用成本直接砍半。另外要警惕循環(huán)調(diào)用。后端接口沒有做 Redis 限流如果被頻繁刷請求AI 費用會瘋漲。準(zhǔn)備在公網(wǎng)演示時一定要在網(wǎng)關(guān)或過濾器層做一個基于用戶 ID 的簡單限流同一個用戶一分鐘最多調(diào)用 10 次 AI 接口。代碼量不大但能防止你演示現(xiàn)場被無意識 F5 刷爆。5. 三個必踩的坑和我的排查記錄從接口 401 到 JSON 解析失敗5.1 現(xiàn)象一DeepSeek 接口一直返回 401 認(rèn)證失敗第一次啟動項目ai_log表里出現(xiàn)一堆 401 錯誤。排查后發(fā)現(xiàn)是application.yml里${DEEPSEEK_API_KEY:your-key-here}這個配置的問題——本地環(huán)境變量沒設(shè)配置直接讀成了字面量your-key-here密鑰當(dāng)然無效。解決方法是確保啟動腳本中export DEEPSEEK_API_KEYsk-xxxx或者用 IDE 環(huán)境變量面板注入。血淚經(jīng)驗寫配置時別把假 Key 放在默認(rèn)值位置寧可留空并在啟動時校驗。5.2 現(xiàn)象二病歷生成偶爾出現(xiàn)“半截 JSON”導(dǎo)致前端白屏有一次演示點擊生成病歷后前端一直轉(zhuǎn)圈控制臺報JSON parse error。原因是請求超時設(shè)置太短模型生成到一半被timeout掐斷返回了不完整的 JSON。解決方法是把timeout從 30 秒提高到 60 秒并在后端加一個“解析失敗→返回友好提示”的兜底。從那以后我每次改 prompt 模板都強制跑一遍“長文本 短超時”兩個邊界用例。5.3 現(xiàn)象三前端 axios 拿不到 AI 接口的響應(yīng)頭頁面可以通過api/ai/*正常拿數(shù)據(jù)但用CrossOrigin配的 CORS 攔不住自定義 header。排查發(fā)現(xiàn)是網(wǎng)關(guān)層沒有把Authorization頭透傳給前端跨域預(yù)檢請求。解決方式是使用 Spring 官方CorsFilter配置顯式允許Authorization和Content-Type兩個請求頭。如果你用的是網(wǎng)關(guān)記得在路由配置里把這兩個頭加入 allowedHeaders。5.4 現(xiàn)象四JWT 在 Redis 里過期后用戶下次操作仍顯示“登錄過期”因為資源里 JWT 沒有接 Redis 統(tǒng)一校驗。用戶已登錄期間如果 Redis 重啟token 驗證照樣通過因為 JWT 是無狀態(tài)簽名服務(wù)端不存儲會話。解決方法是把 Redis 緩存放進 JWT 攔截器里做二次校驗——查user_session表token 對應(yīng)記錄為空直接返回 401。這是一個在答辯演示時非常容易出丑的坑務(wù)必提前處理。5.5 現(xiàn)象五MySQL 5.7 建 SQL 跑在 MySQL 8.0 時報時區(qū)錯誤這個問題是環(huán)境差異不是代碼問題。MySQL 8.0 默認(rèn)時區(qū)與 5.7 不同啟動報The server time zone value xxx is unrecognized。解決方法是連接串加上serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue。也就是每次拆這種帶數(shù)據(jù)庫的源碼第一件事先看 MySQL 版本和驅(qū)動版本是否匹配。6. 把這個系統(tǒng)做亮的三個進階技巧答辯演示的臨門一腳6.1 用“追問閉環(huán)”讓癥狀自查更有醫(yī)療邏輯原始資源的癥狀自查是單輪問答輸入癥狀 → 返回結(jié)果。我后來自己加了一個追問閉環(huán)每當(dāng)返回候選疾病時同時把questions數(shù)組展示在界面底部讓用戶再回答一輪“頭痛持續(xù)多久了有沒有嘔吐”把第二次問答的結(jié)果拼進初始文本再次調(diào)用模型。這樣一來交互深度明顯提升答辯時你能現(xiàn)場演示“AI 主動向用戶提問”這比一次輸?shù)降椎姆桨父荏w現(xiàn)業(yè)務(wù)思考。實現(xiàn)起來不難前端維護一個conversationHistory用數(shù)組存{role: user | assistant, content}提交時把所有聊天記錄一起傳給后端后端全部塞進messages。模型會基于上下文修正診斷比每次只傳一句話準(zhǔn)確得多。6.2 通過ai_log做“費用看板”展示系統(tǒng)可觀測性把ai_log表里的token_count和cost_estimate聚合統(tǒng)計在管理員頁面做一個簡單的折線圖——按天展示 AI 調(diào)用次數(shù)和 token 消耗。這一步代碼量不大但答辯時你告訴評委“系統(tǒng)能定量觀測 AI 資源消耗”瞬間拉開和普通管理系統(tǒng)的差距。具體做法很直接后端寫一個/api/ai/dashboard接口SQL 里GROUP BY DATE(create_time)并按 token 求和前端用 ECharts 畫折線。既然資源里已經(jīng)存了日志這個功能只是三小時工作量。6.3 用 SSE 把病歷生成的“打字機”效果做出來前面提過流式輸出的價值。具體落地時前端不要用EventSource因為 DeepSeek 接口需要自定義 Header。用原生fetch發(fā) POST 請求讀取response.body.getReader()每次讀到一段字符就 append 到目標(biāo)div。核心代碼段如下const response await fetch(/api/ai/medical-record/stream, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ caseId }) }) const reader response.body.getReader() const decoder new TextDecoder() while (true) { const { done, value } await reader.read() if (done) break contentText.value decoder.decode(value, { stream: true }) }邏輯說明reader.read()每次返回一個 chunk用TextDecoder轉(zhuǎn)成字符串后直接追加。這樣頁面會看到逐字輸出雖然不是逐 token 平滑打字但也有明顯的流式效果。后端在流式接口里要注意關(guān)閉響應(yīng)緩沖否則前置的壓縮或緩沖組件會讓流式失效這是一個反復(fù)出現(xiàn)的坑。每當(dāng)我拆到類似的 AI 業(yè)務(wù)系統(tǒng)我最先確認(rèn)的就是三件事模型輸出有沒有留痕日志表、失敗時用戶看到什么兜底文案、以及數(shù)據(jù)是否被不可控內(nèi)容污染。從那以后我跑這套系統(tǒng)第一件事是拿“長文本癥狀 短超時”和“空輸入”兩個用例把 AI 接口打一遍確認(rèn)不會白屏然后才敢正式演示。這份資源最大的價值不只是能跑通的代碼而是把大模型怎么融入傳統(tǒng)業(yè)務(wù)系統(tǒng)的工程思路完整演示了一遍。希望這套源碼和你照著做的過程能幫你順利通關(guān)畢業(yè)答辯也能讓你以后做類似功能時少踩我踩過的那些坑——用它當(dāng)骨架把業(yè)務(wù)細節(jié)補成你自己的作品祝你好運。本文還有配套的精品資源點擊獲取