修复项: - 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% 通过 - 综合测试报告 + 专家评估报告
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
-
ISSUE-3 (HIGH): Align MP
createAppointmentwith backend. Either addschedule_idto backendCreateAppointmentReq(to enable schedule-based booking with capacity tracking), or removeschedule_idfrom MP and usenotesinstead ofreason. -
ISSUE-4 (HIGH): Add
departmentto backendAppointmentResp(join from doctor entity), or have MP fetch doctor details separately to get department. -
ISSUE-7 (HIGH): Add
subjectandlast_message(content preview) to backendSessionResp, or remove from MP DTO if not needed. -
ISSUE-1/2 (MEDIUM): Either add
phoneandrelationfields to backendPatientResp(via join or denormalization), or remove from MP interface and handle these at the family-member level. -
ISSUE-5 (MEDIUM): Add missing fields (
appointment_type,cancel_reason,notes) to MPAppointmentDTO to consume available backend data. -
ISSUE-6 (MEDIUM): Standardize field naming: rename backend
schedule_datetodate, or update MP to useschedule_date. Consider addingavailable_countas a computed field in backend response. -
Article tags: Update MP
Article.tagstype tostring[]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.