Files
hms/docs/qa/miniprogram-contract-verification-report.md
iven d623f8b2ff fix: V1 测试版本端到端验证修复 — 6 CRITICAL + 3 HIGH 问题全量修复
修复项:
- fix(db): 迁移 149 — 修复 Admin 角色权限绑定被迁移链破坏 (FE-C1)
- fix(health): 4 个 handler 添加空名称验证 — Doctor/Article/AlertRule/Tag (API-C1~C4)
- fix(health): Stats 仪表盘 new_this_week 查询修复 — SeaORM date_trunc bug (FE-C2)
- fix(server): 添加安全响应头 — X-Frame-Options/CSP/XSS-Protection/Referrer-Policy (SEC-H1)
- fix(mp): 预约创建契约修复 — notes/reason 字段映射 + 移除 schedule_id (MP-H1)
- fix(mp): 咨询会话 subject/last_message 字段改为可选 (MP-H3)
- fix(ai): AiConfig Default derive 替代手写 impl (clippy)

测试报告:
- 8 维度端到端测试全部完成 (后端 87 用例 / 前端 30 页面 / 小程序 80+ API / 安全 20 项 / 性能 20 端点)
- 多角色 7 角色 49 检查 100% 通过
- 综合测试报告 + 专家评估报告
2026-05-18 10:24:40 +08:00

22 KiB

Mini-Program API Contract Verification Report

Date: 2026-05-18 | Tester: API Tester (automated) | Branch: feat/media-library-banner

1. Summary

Comprehensive verification of API contract consistency between the WeChat mini-program service layer (TypeScript) and the backend Rust DTOs and live API responses. The mini-program source is at apps/miniprogram/src/services/ and the backend DTOs are at crates/erp-health/src/dto/.

Overall Status: PASS with 7 issues found (0 CRITICAL, 3 HIGH, 4 MEDIUM)

2. Build Verification

Check Status
pnpm build:weapp PASS (Sass deprecation warnings only, no errors)
Page count (app.config.ts) 60 pages (12 main + 48 subpackage)
All page files exist PASS (60/60 files verified)
Backend server startup PASS (database connected, migrations applied)

3. API Contract Comparison

3.1 Patient Service (services/patient.ts vs dto/patient_dto.rs)

Endpoint MP Path Backend Path Status
List patients GET /health/patients GET /health/patients PASS
Create patient POST /health/patients POST /health/patients PASS
Update patient PUT /health/patients/{id} PUT /health/patients/{id} PASS

Field comparison (MP Patient vs Backend PatientResp):

Field MP Type Backend Type Match
id string Uuid (string) PASS
name string String PASS
gender string? Option PASS
birth_date string? Option PASS
blood_type string? Option PASS
id_number string? Option PASS
allergy_history string? Option PASS
medical_history_summary string? Option PASS
emergency_contact_name string? Option PASS
emergency_contact_phone string? Option PASS
phone string? MISSING ISSUE-1
relation string? MISSING ISSUE-2
status string? String PASS
verification_status string? String PASS
source string? Option PASS
notes string? Option PASS
version number i32 PASS
user_id MISSING Option PASS (internal)
created_at MISSING DateTime PASS (not needed in MP)
updated_at MISSING DateTime PASS (not needed in MP)

ISSUE-1 (MEDIUM): MP Patient.phone does not exist in backend PatientResp. Backend has no phone field on patient entity. This field will always be undefined from the API.

ISSUE-2 (MEDIUM): MP Patient.relation does not exist in backend PatientResp. Relation is a family member concept, not on the patient entity itself. The PatientUpdateInput also includes relation which backend ignores.

3.2 Appointment Service (services/appointment.ts vs dto/appointment_dto.rs)

Endpoint MP Path Backend Path Status
List appointments GET /health/appointments GET /health/appointments PASS
Get appointment GET /health/appointments/{id} GET /health/appointments/{id} PASS
Create appointment POST /health/appointments POST /health/appointments ISSUE-3
Cancel appointment PUT /health/appointments/{id}/status PUT /health/appointments/{id}/status PASS
List doctors GET /health/doctors GET /health/doctors PASS
Get doctor schedules GET /health/doctor-schedules GET /health/doctor-schedules PASS
Calendar view GET /health/doctor-schedules/calendar GET /health/doctor-schedules/calendar PASS

ISSUE-3 (HIGH): MP createAppointment sends schedule_id and reason fields. Backend CreateAppointmentReq does not have schedule_id or reason. It does have notes (optional). The reason field will be silently dropped. The schedule_id is not used by backend at all.

MP Appointment vs Backend AppointmentResp:

Field MP Type Backend Type Match
patient_name string Option PASS
doctor_name string Option ISSUE-4
department string? MISSING ISSUE-4
appointment_date string NaiveDate PASS
start_time string NaiveTime PASS
end_time string NaiveTime PASS
status string String PASS
version number i32 PASS
appointment_type MISSING String ISSUE-5
patient_id MISSING Uuid PASS (not in MP DTO)
doctor_id MISSING Option PASS (not in MP DTO)
cancel_reason MISSING Option ISSUE-5
notes MISSING Option ISSUE-5
created_at MISSING DateTime PASS (not needed)
updated_at MISSING DateTime PASS (not needed)

ISSUE-4 (HIGH): MP Appointment.department does not exist in backend AppointmentResp. Appointment has no department field -- department belongs to Doctor. MP expects department on each appointment but backend never returns it.

ISSUE-5 (MEDIUM): MP Appointment DTO is missing appointment_type, cancel_reason, and notes fields that backend sends. These fields are lost when consuming the API response.

MP DoctorSchedule vs Backend ScheduleResp:

Field MP Type Backend Type Match
date string MISSING (schedule_date) ISSUE-6
available_count number MISSING (computed) ISSUE-6

ISSUE-6 (MEDIUM): MP DoctorSchedule.date -- backend uses schedule_date. MP also expects available_count which backend does not provide. MP compensates with client-side computation (max_appointments - current_appointments), so this is handled but relies on correct fallback logic.

3.3 Consultation Service (services/consultation.ts vs dto/consultation_dto.rs)

Endpoint MP Path Backend Path Status
List sessions GET /health/consultation-sessions GET /health/consultation-sessions PASS
Get session GET /health/consultation-sessions/{id} GET /health/consultation-sessions/{id} PASS
List messages GET /health/consultation-sessions/{id}/messages GET /health/consultation-sessions/{id}/messages PASS
Send message POST /health/consultation-messages POST /health/consultation-messages PASS
Mark read PUT /health/consultation-sessions/{id}/read PUT /health/consultation-sessions/{id}/read PASS
Poll messages GET /health/consultation-sessions/{id}/messages/poll GET /.../poll PASS

MP ConsultationSession vs Backend SessionResp:

Field MP Type Backend Type Match
subject string? MISSING ISSUE-7
last_message string? MISSING ISSUE-7
updated_at string? DateTime PASS
version number? i32 PASS

ISSUE-7 (HIGH): MP ConsultationSession expects subject and last_message fields that do not exist in backend SessionResp. Backend returns last_message_at (timestamp only, no content). These fields will always be null/undefined.

3.4 Health Data Service (services/health.ts vs dto/health_data_dto.rs)

Endpoint MP Path Backend Path Status
Today summary GET /health/vital-signs/today GET /health/vital-signs/today PASS
Input vital sign POST /health/patients/{id}/vital-signs POST /health/patients/{id}/vital-signs PASS
Get trend GET /health/vital-signs/trend GET /health/vital-signs/trend PASS
Daily monitoring list GET /health/patients/{id}/daily-monitoring GET /.../daily-monitoring PASS
Create daily monitoring POST /health/daily-monitoring POST /health/daily-monitoring PASS
Health thresholds GET /health/critical-value-thresholds/public GET /.../public PASS

Today Summary: MP TodaySummary matches backend MiniTodayResp -- both have blood_pressure, heart_rate, blood_sugar, weight with nested IndicatorSummary objects. PASS.

Input Vital Sign: MP performs indicator_type-to-structured-field mapping before sending (e.g., blood_pressure -> systolic_bp_morning/diastolic_bp_morning). This matches backend CreateVitalSignsReq structure. PASS.

Daily Monitoring: MP DailyMonitoring matches backend DailyMonitoringResp exactly. PASS.

3.5 Points Service (services/points.ts vs dto/points_dto.rs)

Endpoint MP Path Backend Path Status
Get account GET /health/points/account GET /health/points/account PASS
Daily checkin POST /health/points/checkin POST /health/points/checkin PASS
Checkin status GET /health/points/checkin/status GET /health/points/checkin/status PASS
List products GET /health/points/products GET /health/points/products PASS
Get product GET /health/points/products/{id} GET /health/points/products/{id} PASS
Exchange POST /health/points/exchange POST /health/points/exchange PASS
List orders GET /health/points/orders GET /health/points/orders PASS
List transactions GET /health/points/transactions GET /health/points/transactions PASS
List offline events GET /health/offline-events GET /health/offline-events PASS
Register event POST /health/offline-events/{id}/register POST /.../register PASS

Field verification (live API responses):

DTO Match
PointsAccount vs PointsAccountResp PASS (all fields present: id, patient_id, balance, total_earned, total_spent, total_expired)
CheckinStatus vs CheckinStatusResp PASS (checked_in_today, consecutive_days, next_streak_milestone)
PointsProduct vs PointsProductResp PASS
PointsOrder vs PointsOrderResp PASS (qr_code is UUID string, verified_by/verified_at nullable)
PointsTransaction vs PointsTransactionResp PASS (transaction_type matches backend field name)
OfflineEvent vs OfflineEventResp PASS (all fields including start_time/end_time which are optional)

3.6 Alert Service (services/alert.ts vs dto/alert_dto.rs)

Endpoint MP Path Backend Path Status
List patient alerts GET /health/alerts GET /health/alerts PASS

MP Alert vs Backend AlertResponse:

Field Match
id, patient_id, rule_id, severity, title, status, created_at PASS
detail (Record<string,unknown>) PASS (backend returns JSON)
message MISSING in backend
acknowledged_at, resolved_at, version PASS

Note: MP has message field which does not exist in AlertResponse. Backend has acknowledged_by, acknowledged_by_name which MP does not consume. Minor gap, not causing errors.

3.7 Article Service (services/article.ts vs dto/article_dto.rs)

Endpoint MP Path Backend Path Status
List articles GET /health/articles GET /health/articles PASS
Get article detail GET /health/articles/{id} GET /health/articles/{id} PASS
Public article detail GET /public/articles/{id} GET /public/articles/{id} PASS
List categories GET /health/article-categories GET /health/article-categories PASS

MP Article vs Backend ArticleResp / ArticleListItem:

Backend returns tags as Vec<String> (tag names), but MP expects tags?: { id: string; name: string }[] (objects with id and name). The live API confirms tags are returned as string[]. MP's typed interface will not match -- tag objects would be undefined. This is handled gracefully by TypeScript (optional field), but category_name would be null while MP expects it.

3.8 Follow-Up Service (services/followup.ts vs dto/follow_up_dto.rs)

Endpoint MP Path Backend Path Status
List tasks GET /health/follow-up-tasks GET /health/follow-up-tasks PASS
Get task detail GET /health/follow-up-tasks/{id} GET /health/follow-up-tasks/{id} PASS
Submit record POST /health/follow-up-tasks/{id}/records POST /.../records PASS
List records GET /health/follow-up-records GET /health/follow-up-records PASS

MP FollowUpTask vs Backend FollowUpTaskResp: PASS -- all fields match.

3.9 Other Services Verified

Service Endpoints Status
Dialysis listDialysisRecords, getDialysisRecord, listDialysisPrescriptions, getDialysisPrescription PASS
Health Record listHealthRecords, listDiagnoses PASS
Consent listConsents, grantConsent, revokeConsent PASS
Medication Reminder listReminders, createReminder, updateReminder, deleteReminder PASS
Device Sync uploadReadings, queryDeviceReadings, queryHourlyReadings PASS
Action Inbox listActionItems, getActionThread PASS
Notification list, markRead, markAllRead, getUnreadCount PASS
AI Chat sendAiMessage PASS
AI Analysis listAiAnalysis, getAiAnalysisDetail PASS
Doctor Dashboard getDashboard PASS
Doctor Patient listPatients, getPatient, getHealthSummary, listPatientTags, getPatientStats PASS
Doctor Consultation listSessions, getSession, listMessages, sendMessage, markSessionRead, closeSession, pollMessages, getConsultationStats PASS
Doctor Follow-up listFollowUpTasks, getFollowUpTask, updateFollowUpTask, createFollowUpRecord, listFollowUpRecords, getFollowUpStats PASS
Doctor Lab Report listLabReports, getLabReport, reviewLabReport PASS
Doctor Alerts listAlerts, getAlert, acknowledgeAlert, dismissAlert, resolveAlert PASS
Doctor Appointment listAppointments PASS
Doctor Dialysis Full CRUD + stats PASS
Analytics trackEvent, flushEvents PASS (fire-and-forget)
Auth credentialLogin, wechatLogin, wechatBindPhone, getPatients PASS

4. Page Route Verification

All 60 page routes in app.config.ts have corresponding .tsx files:

  • Main pages: 12/12 exist
  • pkg-health subpackage: 5/5 exist
  • pkg-doctor-core subpackage: 8/8 exist
  • pkg-doctor-clinical subpackage: 10/10 exist
  • pkg-mall subpackage: 4/4 exist
  • pkg-profile subpackage: 18/18 exist
  • ai-report subpackage: 2/2 exist
  • article subpackage: 2/2 exist
  • pkg-consultation subpackage: 1/1 exist

Status: PASS

5. Cross-Platform Data Flow Verification

5.1 Patient Created on Web -> Retrieved via API

Tested: GET /health/patients returns 83 patients including ones created through web admin. Response structure matches PatientResp DTO. All standard fields present.

Status: PASS

5.2 Appointment Created via API -> Has All Fields

Backend AppointmentResp includes patient_name, doctor_name, appointment_type, cancel_reason, notes, and all timing fields. MP receives the full response.

Status: PASS (MP just does not consume all fields in its DTO)

5.3 Health Data Input

MP's inputVitalSign() correctly maps indicator types to backend's structured format (systolic_bp_morning, etc.). The CreateVitalSignsReq DTO matches what MP sends.

Status: PASS

5.4 Consultation Messages

Both patient and doctor services use the same message endpoints. ConsultationMessage DTOs match backend MessageResp (id, session_id, sender_id, sender_role, content_type, content, is_read, created_at).

Status: PASS

5.5 Points/Balance Consistency

Points account, transactions, and orders all use the same backend data. Balance changes via checkin or exchange are reflected in the account immediately. No MP-specific caching issues.

Status: PASS

6. Issues Summary

HIGH Priority

ID Service Issue Impact
ISSUE-3 Appointment MP sends schedule_id and reason fields in create request; backend does not accept these. CreateAppointmentReq has notes instead of reason, and no schedule_id field. Data silently dropped. Appointment may not link to schedule.
ISSUE-4 Appointment MP Appointment.department does not exist on backend. Backend returns department only on Doctor entity, not on Appointment. UI always shows empty/undefined for department.
ISSUE-7 Consultation MP ConsultationSession.subject and last_message fields do not exist on backend SessionResp. These fields always null/undefined in MP UI.

MEDIUM Priority

ID Service Issue Impact
ISSUE-1 Patient MP Patient.phone does not exist in backend PatientResp. No phone field on patient entity. Field always undefined.
ISSUE-2 Patient MP Patient.relation and PatientUpdateInput.relation do not exist on backend. Field always undefined; update sends data backend ignores.
ISSUE-5 Appointment MP Appointment DTO missing appointment_type, cancel_reason, notes from backend response. Data available from API but not consumed by MP.
ISSUE-6 DoctorSchedule MP expects date field but backend uses schedule_date. MP expects available_count but it is not returned. Client-side workaround exists (field rename + computation), but fragile.

LOW Priority

ID Service Issue Impact
- Article MP expects tags as {id, name}[] but backend returns string[]. Tags display may be empty or incorrect.
- Article MP expects category_name but backend returns category (string) and category_id (UUID). Minor naming mismatch, no data loss.
- Alert MP has message field not in backend. Backend has acknowledged_by_name not consumed by MP. Extra/null fields, no functional impact.

7. Recommendations

  1. ISSUE-3 (HIGH): Align MP createAppointment with backend. Either add schedule_id to backend CreateAppointmentReq (to enable schedule-based booking with capacity tracking), or remove schedule_id from MP and use notes instead of reason.

  2. ISSUE-4 (HIGH): Add department to backend AppointmentResp (join from doctor entity), or have MP fetch doctor details separately to get department.

  3. ISSUE-7 (HIGH): Add subject and last_message (content preview) to backend SessionResp, or remove from MP DTO if not needed.

  4. ISSUE-1/2 (MEDIUM): Either add phone and relation fields to backend PatientResp (via join or denormalization), or remove from MP interface and handle these at the family-member level.

  5. ISSUE-5 (MEDIUM): Add missing fields (appointment_type, cancel_reason, notes) to MP Appointment DTO to consume available backend data.

  6. ISSUE-6 (MEDIUM): Standardize field naming: rename backend schedule_date to date, or update MP to use schedule_date. Consider adding available_count as a computed field in backend response.

  7. Article tags: Update MP Article.tags type to string[] to match backend, or update backend to return full tag objects.

8. Service File Inventory

File Endpoints Functions Interfaces
request.ts N/A (base) api.get/post/put/delete, requestUnlimited ApiResponse, CacheEntry
patient.ts 3 listPatients, createPatient, updatePatient Patient, PatientUpdateInput
appointment.ts 7 listAppointments, getAppointment, createAppointment, cancelAppointment, getDoctorSchedules, listDoctors, calendarView Appointment, Doctor, DoctorSchedule
consultation.ts 6 listConsultations, getSession, listMessages, sendMessage, markSessionRead, pollMessages ConsultationSession, ConsultationMessage
health.ts 6 getTodaySummary, inputVitalSign, getTrend, createDailyMonitoring, listDailyMonitoring, getHealthThresholds TodaySummary, DailyMonitoring, HealthThreshold
points.ts 11 getAccount, dailyCheckin, getCheckinStatus, listProducts, getProduct, exchangeProduct, listMyOrders, listMyTransactions, listOfflineEvents, registerEvent PointsAccount, PointsProduct, etc.
alert.ts 1 listPatientAlerts Alert
article.ts 4 listArticles, getArticleDetail, getPublicArticleDetail, listCategories Article, ArticleCategory
followup.ts 4 listTasks, getTaskDetail, submitRecord, listRecords FollowUpTask, FollowUpRecord
dialysis.ts 4 listDialysisRecords, getDialysisRecord, listDialysisPrescriptions, getDialysisPrescription DialysisRecord, DialysisPrescription
health-record.ts 2 listHealthRecords, listDiagnoses HealthRecord, Diagnosis
consent.ts 3 listConsents, grantConsent, revokeConsent Consent
medication-reminder.ts 4 listReminders, createReminder, updateReminder, deleteReminder MedicationReminder
device-sync.ts 3 uploadReadings, queryDeviceReadings, queryHourlyReadings BatchReadingRequest, BatchResult
action-inbox.ts 2 listActionItems, getActionThread ActionItem, ThreadResponse
notification.ts 4 list, markRead, markAllRead, getUnreadCount (anonymous)
auth.ts 4 credentialLogin, wechatLogin, wechatBindPhone, getPatients UserInfo, LoginResp, PatientInfo
ai-chat.ts 2 sendAiMessage, (getLocalHistory, saveLocalHistory) AiChatMessage, AiChatResponse
ai-analysis.ts 3 listAiAnalysis, getAiAnalysisDetail, listPendingSuggestions AiAnalysisItem, AiSuggestionItem
analytics.ts 3 trackEvent, trackPageView, flushEvents (internal)
doctor.ts re-export 8 sub-modules (delegated)

Total: 43 service files, ~80 API functions, ~45 TypeScript interfaces

9. Conclusion

The mini-program API service layer is well-aligned with the backend, with most contracts matching correctly. The 7 identified issues are concentrated in three areas: Appointment (3 issues), Patient (2 issues), and Consultation (1 issue), plus one field naming issue in DoctorSchedule. No CRITICAL issues were found -- all API paths are valid, response envelopes are consistent ({success, data, message}), and field types align correctly where they exist. The 3 HIGH issues represent missing fields that cause silent data loss or always-null UI elements, and should be addressed in the next iteration.