約掛號(hào)系統(tǒng)開發(fā):Django/Flask后端與醫(yī)患交互實(shí)戰(zhàn))
這段時(shí)間我一直在折騰一個(gè)醫(yī)院門診掛號(hào)系統(tǒng)的完整實(shí)現(xiàn)。整個(gè)項(xiàng)目選型就是用微信小程序作為患者端后端走Python生態(tài)Django和Flask兩個(gè)框架我都實(shí)際跑過一輪最后沉淀出了一套在線醫(yī)患交互預(yù)約方案。這套系統(tǒng)既要支撐傳統(tǒng)掛號(hào)的“選科室—選醫(yī)生—選時(shí)間段—支付—取號(hào)”還要把在線圖文咨詢、復(fù)診隨訪、預(yù)約提醒這些交互做進(jìn)去。不管你是想給中小醫(yī)院做信息化改造還是拿它當(dāng)畢業(yè)設(shè)計(jì)、開源參賽項(xiàng)目這篇從需求拆解到部署避坑的完整記錄都能給你一條靠譜的路線。1. 項(xiàng)目整體設(shè)計(jì)與功能拆解1.1 為什么是微信小程序而不是App或H5做醫(yī)療類產(chǎn)品第一步要確認(rèn)的不是技術(shù)棧而是“患者憑什么愿意裝你這個(gè)東西”。很多人一上來就想做原生App結(jié)果卡在下載安裝這一關(guān)用戶流失率高得嚇人。微信小程序最大的優(yōu)勢(shì)就是“即用即走”患者掃個(gè)碼、搜一搜就能打開不需要經(jīng)歷“下載—安裝—注冊(cè)—授權(quán)”這一連串勸退流程。對(duì)醫(yī)院來說小程序掛在公眾號(hào)菜單里、貼在診室門口推廣成本幾乎為零。另外微信生態(tài)里天然帶了身份識(shí)別能力wx.login可以直接換取 openid配合手機(jī)號(hào)授權(quán)基本能做到“一次授權(quán)永久識(shí)別”。這在預(yù)約掛號(hào)場(chǎng)景里特別重要因?yàn)閽焯?hào)必須實(shí)名制身份信息一旦錯(cuò)后續(xù)就診、取號(hào)、退費(fèi)全是問題。H5 雖然也能做但微信內(nèi)置瀏覽器的接口能力有限推送提醒、支付回調(diào)、地理位置這些能力都隔了一層體驗(yàn)和穩(wěn)定性都不如原生小程序外殼。選型結(jié)論很直接患者端用微信小程序醫(yī)生端和管理后臺(tái)用 Web 頁面后端統(tǒng)一提供 RESTful API。這樣患者側(cè)輕量快捷醫(yī)生側(cè)功能復(fù)雜也不怕瀏覽器兼容問題前后端徹底分離后續(xù)加管理功能、報(bào)表功能都很方便。1.2 核心角色與功能清單整個(gè)系統(tǒng)按角色拆成三塊每一塊的痛點(diǎn)不一樣功能設(shè)計(jì)自然也不一樣?;颊叨宋⑿判〕绦蜃?cè)登錄、瀏覽醫(yī)院和科室、查看醫(yī)生排班、選擇時(shí)間段預(yù)約掛號(hào)、在線支付或到院支付、取消預(yù)約、在線圖文問診、查看就診記錄和待辦提醒。這里最核心的是“排班可約性”的實(shí)時(shí)展示患者選醫(yī)生時(shí)看到的是剩余號(hào)源不能是死數(shù)據(jù)。醫(yī)生端Web/小程序查看自己的出診排班、開啟或停診、處理患者的在線咨詢、標(biāo)記復(fù)診、補(bǔ)錄診后隨訪信息。很多醫(yī)院系統(tǒng)把醫(yī)生端做得極其復(fù)雜我實(shí)際落地的經(jīng)驗(yàn)是先做“出診管理 消息回復(fù)”兩個(gè)主功能其他權(quán)限控制后面再慢慢加。管理后臺(tái)Web維護(hù)科室、醫(yī)生信息、排班規(guī)則、號(hào)源總量、停診通知、黑名單管理、訂單對(duì)賬。后臺(tái)是給掛號(hào)處和系統(tǒng)管理員用的操作必須清晰盡量用表格化界面少搞花哨交互。在線醫(yī)患交互預(yù)約的核心不是簡(jiǎn)單把線下掛號(hào)流程搬到線上而是要把“預(yù)約前咨詢”和“預(yù)約后隨訪”這兩個(gè)非結(jié)構(gòu)化場(chǎng)景做出來。預(yù)約前患者不確定自己的癥狀掛哪個(gè)科可以在線留言問醫(yī)生醫(yī)生有空了回復(fù)預(yù)約后醫(yī)生可以發(fā)復(fù)診提醒、開檢查注意事項(xiàng)患者也可以直接反饋恢復(fù)情況。這些功能做出來后整個(gè)系統(tǒng)就不是一個(gè)冷冰冰的掛號(hào)機(jī)而是一個(gè)有溫度的隨訪工具。2. Django還是Flask后端框架選型與API設(shè)計(jì)2.1 兩個(gè)框架都試過之后的選擇先講實(shí)話我一開始是用 Flask 快速搭的原型因?yàn)?Flask 輕路由、視圖、請(qǐng)求上下文都特別直觀適合驗(yàn)證業(yè)務(wù)邏輯。但項(xiàng)目推進(jìn)到用戶體系、訂單狀態(tài)機(jī)、排班并發(fā)控制這些模塊時(shí)Flask 需要自己拼的零件越來越多數(shù)據(jù)庫遷移、表單校驗(yàn)、Admin 后臺(tái)、認(rèn)證鑒權(quán)每一樣都要找第三方庫再組合組合久了反而是負(fù)擔(dān)。后來我切換到 Django核心原因有三點(diǎn)。第一Django 自帶 ORM 和 migration排班表、訂單表這種強(qiáng)關(guān)系數(shù)據(jù)結(jié)構(gòu)用 Django ORM 管理起來非常順手改字段后一句makemigrations就能生成遷移腳本不至于上線前手改數(shù)據(jù)庫表結(jié)構(gòu)。第二Django 自帶 Admin 后臺(tái)管理科室和醫(yī)生這種低頻但必須有的功能直接配置一下 ModelAdmin 就能用省掉一整套后臺(tái)開發(fā)時(shí)間。第三Django REST FrameworkDRF把序列化、視圖集、權(quán)限、限流都封裝好了API 開發(fā)的效率比 Flask 裸寫高出一大截。但這不代表 Flask 不行。如果你的項(xiàng)目只做幾個(gè)接口、不需要復(fù)雜后臺(tái)或者團(tuán)隊(duì)對(duì) Flask 更熟用 Flask Flask-SQLAlchemy Flask-Migrate 也完全能把在線預(yù)約做出來。Django 和 Flask 的選擇本質(zhì)是“全家桶”和“樂高積木”的區(qū)別。做醫(yī)療這種領(lǐng)域模型多、狀態(tài)流轉(zhuǎn)復(fù)雜的項(xiàng)目我建議直接上 Django短期學(xué)習(xí)成本換來的是長(zhǎng)期維護(hù)成本的大幅下降。維度DjangoFlaskORM與遷移自帶開箱即用需要外接 SQLAlchemy后臺(tái)管理自帶 Admin配置即可需要第三方擴(kuò)展REST APIDRF生態(tài)成熟Flask-RESTful 或手動(dòng)實(shí)現(xiàn)技術(shù)棧統(tǒng)一性高約定優(yōu)于配置自由度高但需要自己定規(guī)范適合場(chǎng)景完整業(yè)務(wù)系統(tǒng)、多人協(xié)作原型驗(yàn)證、輕量API服務(wù)2.2 RESTful接口設(shè)計(jì)與DRF示例前端小程序只認(rèn)接口后端無論用哪個(gè)框架接口設(shè)計(jì)都必須清晰。我按資源維度拆不按頁面維度拆這樣每個(gè)接口可以復(fù)用到小程序端、醫(yī)生Web端、管理后臺(tái)甚至未來可能的App端。核心接口清單大致是/api/hospitals/醫(yī)院列表/api/departments/?hospital1科室列表支持按醫(yī)院過濾/api/doctors/?department2醫(yī)生列表帶出職稱、擅長(zhǎng)、排班概覽/api/schedules/?doctor3某醫(yī)生的排班信息返回日期、時(shí)段、剩余號(hào)源/api/appointments/創(chuàng)建預(yù)約、查詢我的預(yù)約、取消預(yù)約/api/orders/訂單信息支付回調(diào)后更新狀態(tài)/api/messages/在線問診留言列表和發(fā)送用 DRF 寫排班接口非???。舉個(gè)例子排班模型Schedule序列化器里直接返回醫(yī)生姓名和剩余號(hào)源視圖集配合 DjangoFilterBackend 做篩選幾行代碼就能給小程序一個(gè)干凈的數(shù)據(jù)結(jié)構(gòu)class ScheduleSerializer(serializers.ModelSerializer): doctor_name serializers.CharField(sourcedoctor.name, read_onlyTrue) department_name serializers.CharField(sourcedoctor.department.name, read_onlyTrue) class Meta: model Schedule fields [id, doctor, doctor_name, department_name, date, start_time, end_time, slot_count, remaining_count] class ScheduleViewSet(viewsets.ReadOnlyModelViewSet): queryset Schedule.objects.select_related(doctor__department).filter(remaining_count__gt0) serializer_class ScheduleSerializer filterset_fields [doctor, date]接口設(shè)計(jì)時(shí)的關(guān)鍵心法是“一次給夠別讓前端調(diào)二次”。小程序的網(wǎng)絡(luò)請(qǐng)求在弱網(wǎng)環(huán)境下很慢如果排班接口只給 schedule id前端還要再調(diào)一次醫(yī)生詳情才能顯示姓名體驗(yàn)就廢了。所以我習(xí)慣把醫(yī)生姓名、科室名、醫(yī)院名全部嵌套進(jìn)排班數(shù)據(jù)里前端拿一個(gè)列表直接渲染省時(shí)省力。3. 微信小程序端核心模塊落地3.1 登錄授權(quán)與數(shù)據(jù)請(qǐng)求封裝小程序端第一個(gè)坑就是登錄?,F(xiàn)在很多教程還停留在老的wx.getUserInfo彈窗授權(quán)這套方案已經(jīng)被微信廢棄了隱私接口規(guī)范調(diào)整之后頭像昵稱和手機(jī)號(hào)都必須走新的“頭像昵稱填寫能力”和“手機(jī)號(hào)快速驗(yàn)證組件”。正確做法是用wx.login拿 code把 code 傳到后端后端再調(diào)微信的code2Session接口換 openid然后把 openid 作為業(yè)務(wù)唯一標(biāo)識(shí)自己生成 token 返回給小程序。后續(xù)所有請(qǐng)求都在 header 里帶 token后端校驗(yàn) token 后就知道是哪個(gè)患者。手機(jī)號(hào)這塊不要用普通的輸入框收集直接在小程序里用button open-typegetPhoneNumber拿到動(dòng)態(tài)令牌后傳給后端后端再向微信接口換取真實(shí)手機(jī)號(hào)。這一步對(duì)掛號(hào)系統(tǒng)尤其重要因?yàn)槿√?hào)通知、停診短信都靠手機(jī)號(hào)觸達(dá)。因?yàn)榻涌跈?quán)限和審核規(guī)則會(huì)變上線前一定去微信公眾平臺(tái)確認(rèn)最新要求別拿舊代碼硬套。請(qǐng)求封裝用wx.request做一個(gè)統(tǒng)一模塊好處是攔截器邏輯只寫一遍。我在request.js里做了三件事統(tǒng)一拼接 baseURL、加 token 到 header、統(tǒng)一處理 HTTP 狀態(tài)碼和業(yè)務(wù) code。遇到 401 自動(dòng)跳轉(zhuǎn)登錄頁遇到 500 彈 toast 而不是讓頁面白屏。代碼大概長(zhǎng)這樣const request (url, method, data) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${url}, method, data, header: { Authorization: Bearer ${wx.getStorageSync(token)}, Content-Type: application/json }, success: (res) { if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/login }); reject(res); } else if (res.statusCode 200 res.statusCode 300) { resolve(res.data); } else { wx.showToast({ title: res.data.message || 請(qǐng)求失敗, icon: none }); reject(res); } }, fail: reject }); }); };3.2 預(yù)約掛號(hào)全流程實(shí)現(xiàn)預(yù)約掛號(hào)流程是系統(tǒng)的心臟頁面再多也逃不過“選日期→選時(shí)段→確認(rèn)患者信息→支付→完成”這幾步。我實(shí)現(xiàn)的時(shí)候把整個(gè)流程拆成了三個(gè)關(guān)鍵頁面排班列表頁、預(yù)約確認(rèn)頁、支付結(jié)果頁另外加了一個(gè)“預(yù)約成功”作為狀態(tài)展示不單獨(dú)設(shè)頁面。排班列表頁的數(shù)據(jù)依賴接口返回的date和remaining_count。我默認(rèn)做了一個(gè)“近7天可約”的橫向日歷日期切換時(shí)重新請(qǐng)求/api/schedules/?doctorxxxdateyyyy-mm-dd。這里有個(gè)細(xì)節(jié)slot_count是初始號(hào)源數(shù)remaining_count是當(dāng)前剩余數(shù)前端顯示的是“剩余 x 號(hào)”當(dāng)remaining_count為 0 時(shí)這個(gè)時(shí)段的按鈕要直接置灰不能等用戶點(diǎn)擊后才提示。我在界面層用disabled屬性控制實(shí)測(cè)比按鈕可點(diǎn)擊再彈窗提示的體驗(yàn)好很多。用戶點(diǎn)擊“預(yù)約”之后跳到確認(rèn)頁。確認(rèn)頁要做的不只是展示信息還要做一個(gè)重要的校驗(yàn)再次向后端確認(rèn)該時(shí)段仍有剩余號(hào)源。因?yàn)榛颊咴谇耙粋€(gè)頁面停留太久時(shí)號(hào)源可能已經(jīng)被別人搶走。這個(gè)二次確認(rèn)接口的返回結(jié)果直接決定是進(jìn)入支付流程還是彈窗提示“該時(shí)段已被約滿請(qǐng)選擇其他時(shí)間”。支付環(huán)節(jié)我優(yōu)先接微信支付。小程序里用wx.requestPayment后端預(yù)下單接口返回支付參數(shù)前端拉起支付。支付回調(diào)走后端異步通知更新訂單和號(hào)源狀態(tài)。測(cè)試階段沒有商戶號(hào)的話可以用模擬支付開關(guān)正式上線前必須把 mock 支付關(guān)掉否則審核和財(cái)務(wù)對(duì)賬都會(huì)出問題。3.3 在線醫(yī)患交互從留言到WebSocket推送在線醫(yī)患交互是這個(gè)系統(tǒng)區(qū)別于普通掛號(hào)系統(tǒng)的地方。患者掛號(hào)后如果還有問題不需要再跑一趟醫(yī)院直接在訂單詳情頁發(fā)起在線問診把癥狀、圖片發(fā)過去醫(yī)生在 Web 端或小程序端打開消息列表看到后回復(fù)。我最初用最簡(jiǎn)單的“留言板”方式實(shí)現(xiàn)就是一張message表患者發(fā)一條醫(yī)生回一條頁面靠下拉刷新拉新消息。這個(gè)方案在測(cè)試階段沒問題但真機(jī)體驗(yàn)不佳患者發(fā)完消息后要反復(fù)下拉非常煎熬。后來我改用 WebSocket 做消息實(shí)時(shí)推送。后端如果是 Django用channels寫一個(gè) WebSocket consumer如果還是 Flask則用flask-socketio。前端小程序里用wx.connectSocket建立長(zhǎng)連接服務(wù)端有消息時(shí)主動(dòng)推給小程序。核心流程是患者進(jìn)入對(duì)話頁時(shí)先拉歷史消息同時(shí)建立 WebSocket 連接醫(yī)生回復(fù)后WebSocket 把新消息推給患者前端追加到聊天記錄里患者發(fā)送消息時(shí)先通過普通 API 寫入數(shù)據(jù)庫再通過 WebSocket 通知對(duì)方。這里要提醒的是WebSocket 不能作為唯一的數(shù)據(jù)通道。微信小程序在切后臺(tái)、網(wǎng)絡(luò)切換時(shí)WebSocket 很容易掉線所以必須在onShow生命周期里做一次“重新拉取未讀消息”的邏輯。也就是說實(shí)時(shí)推送是增強(qiáng)體驗(yàn)的手段數(shù)據(jù)落庫和重拉兜底才是保證不丟消息的關(guān)鍵。團(tuán)隊(duì)如果不想引入 WebSocket 維護(hù)成本用“輪詢 未讀數(shù)角標(biāo)”也能滿足絕大多數(shù)復(fù)診溝通場(chǎng)景。4. 數(shù)據(jù)庫設(shè)計(jì)與號(hào)源并發(fā)控制4.1 核心表結(jié)構(gòu)速覽預(yù)約系統(tǒng)的數(shù)據(jù)庫設(shè)計(jì)比很多人想象的要重。因?yàn)獒t(yī)療場(chǎng)景涉及實(shí)名、排班、訂單、支付、消息多條鏈路表之間關(guān)系復(fù)雜。我落地的核心表大概有這些User患者或醫(yī)生賬號(hào)包含微信 openid、手機(jī)號(hào)、姓名、身份證號(hào)脫敏存儲(chǔ)、角色類型。Department科室表包含醫(yī)院、科室名稱、門診位置。Doctor醫(yī)生表包含姓名、職稱、擅長(zhǎng)領(lǐng)域、所屬科室、頭像、簡(jiǎn)介。Schedule排班表包含醫(yī)生、日期、開始時(shí)間、結(jié)束時(shí)間、總號(hào)源數(shù)、剩余號(hào)源數(shù)、狀態(tài)。Appointment預(yù)約記錄表包含患者、排班、預(yù)約時(shí)間、狀態(tài)、就診序號(hào)。Order訂單表包含預(yù)約、支付金額、支付狀態(tài)、第三方支付流水號(hào)。Message問診消息表包含會(huì)話ID、發(fā)送者、內(nèi)容、圖片路徑、已讀狀態(tài)。這中間最容易忽略的是“就診序號(hào)”。部分醫(yī)院要求患者到院后按序號(hào)排隊(duì)呼叫所以我在Schedule里存了start_number每成功預(yù)約一個(gè)號(hào)就在排班下遞增生成序號(hào)返回給前端。如果不做這個(gè)字段后面接院內(nèi)叫號(hào)系統(tǒng)時(shí)會(huì)非常痛苦。Django 里創(chuàng)建 App 時(shí)要順手把User模型改成自定義的因?yàn)楹罄m(xù)要掛身份證、微信 openid、角色這些字段默認(rèn)的 auth.User 不夠用。方法是在settings.py里設(shè)置AUTH_USER_MODEL accounts.User繼承AbstractUser擴(kuò)展字段。這個(gè)配置最好在項(xiàng)目初始化時(shí)做完項(xiàng)目跑到一半再換用戶模型Django 的 migration 會(huì)很折騰。4.2 號(hào)源不超賣的關(guān)鍵操作在線掛號(hào)和秒殺系統(tǒng)本質(zhì)上是一類問題多個(gè)患者同時(shí)搶同一時(shí)段的號(hào)怎么保證不超賣最穩(wěn)妥的辦法不是先查再減而是用一條條件更新 SQL 把“判斷剩余號(hào)源”和“扣減號(hào)源”合并成一個(gè)原子操作。在 Django ORM 里可以這么寫from django.db.models import F from django.db import transaction with transaction.atomic(): updated Schedule.objects.filter( idschedule_id, remaining_count__gt0, statusopen ).update( remaining_countF(remaining_count) - 1 ) if updated 0: # 說明這一時(shí)刻號(hào)源已經(jīng)被搶完 raise AppointmentConflict(當(dāng)前時(shí)段已約滿) appointment Appointment.objects.create(...)核心就是update()只在remaining_count 0時(shí)才會(huì)生效并且返回受影響行數(shù)。如果返回 0就說明條件不滿足不創(chuàng)建預(yù)約記錄。這么做既避免了“先 select 再 update”之間的時(shí)間差也不需要犧牲太多性能。支付環(huán)節(jié)我建議把號(hào)源鎖定和訂單創(chuàng)建放在同一個(gè)事務(wù)里防止出現(xiàn)“訂單沒支付成功號(hào)卻已經(jīng)扣了”的臟數(shù)據(jù)。針對(duì)超時(shí)未支付的問題我做了“15分鐘未支付自動(dòng)取消”的定時(shí)任務(wù)每小時(shí)掃描一次Order表找到超過15分鐘仍處于待支付狀態(tài)的訂單取消訂單并把號(hào)源回補(bǔ)?;匮a(bǔ)時(shí)同樣用條件更新把remaining_count加回去但要注意不能超過slot_count上限SQL 里加remaining_count__ltF(slot_count)作為守衛(wèi)。4.3 預(yù)約狀態(tài)機(jī)與取消規(guī)則預(yù)約記錄的狀態(tài)不能只用一個(gè)字符串存否則業(yè)務(wù)邏輯里到處是if status waiting的臟判斷。我定義了四個(gè)主要狀態(tài)外加兩個(gè)終態(tài)狀態(tài)含義可操作場(chǎng)景pending已創(chuàng)建訂單等待支付患者可以取消超時(shí)自動(dòng)關(guān)閉confirmed支付成功號(hào)源已鎖定就診日未開始時(shí)可以申請(qǐng)退號(hào)completed就診完成醫(yī)生標(biāo)記或系統(tǒng)自動(dòng)更新支持復(fù)診隨訪cancelled已取消或已退號(hào)終態(tài)不可進(jìn)行任何操作noshow爽約對(duì)黑名單和信用分有影響取消規(guī)則的難點(diǎn)在于“退號(hào)退費(fèi)”和“號(hào)源回補(bǔ)”不能直接變成一行。我處理的邏輯是就診日期前一天23:59前取消訂單全額退款號(hào)源回補(bǔ)當(dāng)日取消需要線上提出申請(qǐng)由后臺(tái)人工審核號(hào)源暫時(shí)凍結(jié)而不是立刻釋放。這是為了避免有人反復(fù)搶占和退訂把排班表刷成一片混亂。5. 部署上線與微信側(cè)審核避坑5.1 小程序合法域名與后端部署小程序上線前必須在微信公眾平臺(tái)的“開發(fā)設(shè)置”里配置服務(wù)器域名。request合法域名必須是 HTTPS且域名不能帶端口號(hào)。這意味著如果你的后端跑在http://192.168.1.100:8000本地調(diào)試可以勾選“不校驗(yàn)合法域名”但正式版一律攔截。所以上線的第一個(gè)前提是準(zhǔn)備好一個(gè)已備案的域名給它配上 HTTPS 證書。后端部署方案我推薦用 Nginx Gunicorn Django 這種組合。Nginx 負(fù)責(zé)靜態(tài)文件、HTTPS 證書、反向代理Gunicorn 負(fù)責(zé)跑 Python 應(yīng)用。配置文件時(shí)注意把靜態(tài)文件和 media 文件的 location 單獨(dú)定義不然你會(huì)發(fā)現(xiàn) VSCode 里寫的img標(biāo)簽在 Django 的 static 文件中顯示不了。這個(gè)問題多半是STATIC_URL配置不對(duì)或者DEBUGFalse之后 Django 不再自動(dòng)服務(wù)靜態(tài)文件需要用collectstatic收集后才能由 Nginx 提供訪問。Flask 部署也是同理只是應(yīng)用啟動(dòng)入口不同。有一個(gè)常見問題是“Flask 如何綁定到網(wǎng)頁元素”這其實(shí)是沒搞清楚 Flask 只管后端邏輯網(wǎng)頁里的按鈕、輸入框要靠 HTML/JS 去渲染。如果你需要在 Flask 里返回一個(gè)帶交互的頁面正確做法是用render_template渲染 Jinja2 模板在模板里引 CSS 和 JS再通過 fetch 或 AJAX 調(diào)后端接口。這個(gè)思路和小程序完全一致只是把小程序端換成了瀏覽器端。5.2 高頻問題排查實(shí)錄這個(gè)項(xiàng)目從開發(fā)到測(cè)試我踩過的坑還真不少。挑幾個(gè)典型的記錄一下給后來人省點(diǎn)時(shí)間?,F(xiàn)象可能原因解決辦法小程序 request 報(bào)url not in domain list合法域名未配置或沒等生效在公眾平臺(tái)添加域名確認(rèn)證書鏈完整本地開發(fā)時(shí)臨時(shí)勾選不校驗(yàn)號(hào)源明明還有剩余仍然提示約滿數(shù)據(jù)庫行鎖競(jìng)爭(zhēng)或事務(wù)隔離級(jí)別過高檢查是否用了select_for_update()后又長(zhǎng)事務(wù)盡量鎖范圍小、提交快Django 靜態(tài)圖片加載不出來STATIC_URL錯(cuò)誤或DEBUGFalse未收集靜態(tài)文件配置 Nginx alias 指向staticfiles執(zhí)行collectstaticFlask 頁面點(diǎn)擊按鈕沒有任何效果后端只返回了模板前端 JS 路徑或接口地址寫錯(cuò)打開瀏覽器控制臺(tái)觀察 Network 請(qǐng)求是否 404/500微信支付回調(diào)后訂單狀態(tài)沒更新回調(diào)驗(yàn)簽失敗或冪等處理缺失先校驗(yàn)簽名再判斷訂單狀態(tài)重復(fù)通知時(shí)直接返回成功還有一個(gè)容易被忽略的坑微信小程序頂部導(dǎo)航欄高度。安卓和 iOS 的狀態(tài)欄高度不一樣如果用了自定義導(dǎo)航欄不要寫死高度應(yīng)該用wx.getSystemInfoSync()獲取statusBarHeight再動(dòng)態(tài)計(jì)算。我早期就是寫死 64px結(jié)果 iPhone 上按鈕被劉海擋住被測(cè)試同事拿截圖懟了好幾次。6. 幾點(diǎn)實(shí)操心德項(xiàng)目做到最后我發(fā)現(xiàn)最難的不是寫代碼而是把“線下流程”翻譯成“線上系統(tǒng)邏輯”。比如線下掛號(hào)時(shí)患者到窗口問一句“還有號(hào)嗎”工作人員看一眼登記本就能回答但線上系統(tǒng)必須在同一時(shí)刻面對(duì)幾百個(gè)查詢還要保證每個(gè)人看到的號(hào)源是準(zhǔn)的。這個(gè)問題的解法前面講的remaining_count條件更新只是第一步更關(guān)鍵的是整個(gè)團(tuán)隊(duì)都要建立“并發(fā)敏感”的思維每個(gè)時(shí)序操作都要問一句如果兩個(gè)請(qǐng)求同時(shí)進(jìn)來系統(tǒng)會(huì)不會(huì)亂還有一點(diǎn)醫(yī)患交互功能上線前一定要做隱私合規(guī)自查。昵稱、頭像、手機(jī)號(hào)、聊天圖片這些都屬于用戶敏感信息小程序端不能隨便存到本地接口傳輸要用 HTTPS后臺(tái)展示時(shí)要打碼。我在患者列表里默認(rèn)只顯示“張*”身份證號(hào)也只保留前四位和后四位完整信息只在管理員授權(quán)后才能看到。這個(gè)設(shè)計(jì)聽起來簡(jiǎn)單但真到了審核環(huán)節(jié)它能幫你省掉一大半被駁回的風(fēng)險(xiǎn)。如果后面你想繼續(xù)擴(kuò)展我建議優(yōu)先做“候診隊(duì)列提醒”和“電子病歷檔案”。排隊(duì)叫號(hào)信息推送到小程序患者拿號(hào)后不用站在診室門口等去看檢查、買藥也能收到提醒電子病歷則把每次問診記錄和檢查結(jié)果沉淀下來復(fù)診時(shí)醫(yī)生一眼看到歷史比讓患者翻紙質(zhì)病歷本強(qiáng)太多。在線醫(yī)患交互預(yù)約這套底座搭好之后這些功能都只是往上加模塊的事。