- Spring Boot 后端服务 (hss-home-service) - delivery-miniapp 配送小程序 - website 官网 (Nuxt) - docs 架构设计文档 - Docker 容器化部署配置 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
212 lines
14 KiB
Bash
Executable File
212 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
||
# 居家上门服务系统 — 端对端全链路测试
|
||
set -e
|
||
|
||
BASE="http://localhost:18080/api/hss"
|
||
TS=$(date +%s)
|
||
PID="9${TS: -8}" # Unique patient ID
|
||
HDRS="-H Content-Type:application/json -H X-Tenant-Id:1 -H X-Org-Id:1"
|
||
PASS=0; FAIL=0; G='\033[0;32m'; R='\033[0;31m'; N='\033[0m'
|
||
|
||
ok() { PASS=$((PASS+1)); echo -e "${G}PASS${N} $1"; }
|
||
fail() { FAIL=$((FAIL+1)); echo -e "${R}FAIL${N} $1 — $2"; }
|
||
assert() {
|
||
local resp="$1" field="$2" expect="$3" msg="$4"
|
||
local got=$(echo "$resp" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d$field)" 2>/dev/null)
|
||
[ "$got" = "$expect" ] && ok "$msg" || fail "$msg" "expected=$expect got=$got"
|
||
}
|
||
has_data() {
|
||
local resp="$1" msg="$2"
|
||
local code=$(echo "$resp" | python3 -c "import sys,json;print(json.load(sys.stdin).get('code',0))" 2>/dev/null)
|
||
[ "$code" = "200" ] && ok "$msg" || fail "$msg" "$resp"
|
||
}
|
||
|
||
echo "╔════════════════════════════════════════════╗"
|
||
echo "║ 居家上门服务系统 E2E 全链路测试 ║"
|
||
echo "╚════════════════════════════════════════════╝"
|
||
|
||
# ── 准备:插入测试用户 ──
|
||
docker exec hss-postgres psql -U hss -d hss_home_service -qc \
|
||
"INSERT INTO hss_patient_profiles (tenant_id, patient_id, name, phone, address, region_code) VALUES (1, $PID, 'E2E测试', '13800000000', '梅江区金山街道', '441402001') ON CONFLICT DO NOTHING" 2>/dev/null
|
||
|
||
# ── Step 1: 创建申请 ──
|
||
echo ""; echo "── 阶段一:需求受理 ──"
|
||
RESP=$(curl -s -X POST "$BASE/applications" $HDRS -H "X-User-Role:PATIENT" \
|
||
-H "Idempotency-Key:e2e-s1-$TS" \
|
||
-d "{\"patientId\":\"$PID\",\"serviceType\":\"HOME_CARE\",\"channel\":\"WECHAT\",\"contactName\":\"张三\",\"contactPhone\":\"13812345678\",\"address\":\"梅江区金山街道XX小区3栋201\",\"regionCode\":\"441402001\"}")
|
||
assert "$RESP" "['data']['status']" "DRAFT" "创建申请 → 状态=DRAFT"
|
||
APP_ID=$(echo "$RESP" | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['id'])" 2>/dev/null)
|
||
|
||
# ── Step 2: 提交申请 ──
|
||
RESP=$(curl -s -X POST "$BASE/applications/$APP_ID/submit" $HDRS -H "X-User-Role:PATIENT" \
|
||
-H "Idempotency-Key:e2e-s2-$TS")
|
||
assert "$RESP" "['data']['status']" "PENDING_ACCEPTANCE" "提交申请 → 状态=PENDING_ACCEPTANCE"
|
||
|
||
# ── Step 3: 受理通过 ──
|
||
RESP=$(curl -s -X POST "$BASE/applications/$APP_ID/accept" $HDRS -H "X-User-Role:RECEPTIONIST" \
|
||
-H "Idempotency-Key:e2e-s3-$TS")
|
||
assert "$RESP" "['data']['status']" "PENDING_ASSESSMENT" "受理通过 → 状态=PENDING_ASSESSMENT"
|
||
|
||
# ── 阶段二:评估定级 ──
|
||
echo ""; echo "── 阶段二:评估定级 ──"
|
||
RESP=$(curl -s -X POST "$BASE/assessments/$APP_ID/assign" $HDRS -H "X-User-Role:RECEPTIONIST" \
|
||
-H "Idempotency-Key:e2e-s4-$TS" -d '{"assessorId":1}')
|
||
has_data "$RESP" "派发评估任务"
|
||
ASM_ID=$(echo "$RESP" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('data',{}).get('id',1))" 2>/dev/null || echo "1")
|
||
|
||
RESP=$(curl -s -X POST "$BASE/assessments/$ASM_ID/submit" $HDRS -H "X-User-Role:ASSESSOR" \
|
||
-H "Idempotency-Key:e2e-s5-$TS" \
|
||
-d '{"careLevel":"LEVEL_3","riskLevel":"MEDIUM","reportContent":"{\"mobility\":\"limited\"}"}')
|
||
has_data "$RESP" "提交评估报告"
|
||
|
||
# ── 阶段三:方案制定 → 签署 → 生成计划 → 生成工单 ──
|
||
echo ""; echo "── 阶段三:方案制定+签署+工单 ──"
|
||
RESP=$(curl -s -X POST "$BASE/service-plans" $HDRS -H "X-User-Role:PLANNER" \
|
||
-H "Idempotency-Key:e2e-s6-$TS" \
|
||
-d "{\"applicationId\":$APP_ID,\"assessmentTaskId\":$ASM_ID,\"items\":[{\"serviceItemId\":1,\"itemName\":\"助洁服务\",\"unitPrice\":50.00,\"frequency\":3,\"standardDuration\":60,\"evidenceRequired\":false}]}")
|
||
has_data "$RESP" "创建方案"
|
||
PLAN_ID=$(echo "$RESP" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('data',{}).get('id',1))" 2>/dev/null || echo "1")
|
||
|
||
RESP=$(curl -s -X POST "$BASE/service-plans/$PLAN_ID/submit-sign" $HDRS -H "X-User-Role:PLANNER" \
|
||
-H "Idempotency-Key:e2e-s7-$TS")
|
||
assert "$RESP" "['data']['status']" "PLAN_PENDING_SIGN" "提交签署 → PLAN_PENDING_SIGN"
|
||
|
||
RESP=$(curl -s -X POST "$BASE/service-plans/$PLAN_ID/sign" $HDRS -H "X-User-Role:PATIENT" \
|
||
-H "Idempotency-Key:e2e-s8-$TS")
|
||
assert "$RESP" "['data']['status']" "PLAN_EFFECTIVE" "签署方案 → PLAN_EFFECTIVE"
|
||
|
||
# 验证服务计划
|
||
SCH_RESP=$(curl -s "$BASE/service-schedules/plans/$PLAN_ID" $HDRS -H "X-User-Role:PLANNER")
|
||
SCH_COUNT=$(echo "$SCH_RESP" | python3 -c "import sys,json;print(len(json.load(sys.stdin).get('data',[])))" 2>/dev/null || echo "0")
|
||
[ "$SCH_COUNT" -gt 0 ] && ok "服务计划已生成(${SCH_COUNT}条)" || fail "服务计划" "count=$SCH_COUNT"
|
||
|
||
# 从服务计划生成工单并复制方案项目为工单项目
|
||
docker exec hss-postgres psql -U hss -d hss_home_service -qc "
|
||
INSERT INTO hss_work_orders (tenant_id, org_id, schedule_id, plan_id, application_id, patient_id, status, service_date, time_window_start, time_window_end, risk_level, is_high_risk, created_at, updated_at)
|
||
SELECT ss.tenant_id, sp.org_id, ss.id, sp.id, sp.application_id, sa.patient_id, 'ORDER_CREATED', ss.scheduled_date,
|
||
COALESCE(ss.time_window_start, '09:00'::time), COALESCE(ss.time_window_end, '10:00'::time),
|
||
'LOW', false, NOW(), NOW()
|
||
FROM hss_service_schedules ss
|
||
JOIN hss_service_plans sp ON ss.plan_id = sp.id
|
||
JOIN hss_service_applications sa ON sp.application_id = sa.id
|
||
WHERE ss.plan_id = $PLAN_ID AND ss.status = 'SCHEDULED'
|
||
AND NOT EXISTS (SELECT 1 FROM hss_work_orders wo WHERE wo.schedule_id = ss.id AND wo.deleted = 0)
|
||
LIMIT 3" 2>/dev/null
|
||
|
||
# 从方案项目复制到工单项目
|
||
docker exec hss-postgres psql -U hss -d hss_home_service -qc "
|
||
INSERT INTO hss_work_order_items (work_order_id, plan_item_id, item_name, unit_price, required, evidence_required, sort_order, created_at)
|
||
SELECT wo.id, spi.id, spi.item_name, spi.unit_price, true, COALESCE(spi.evidence_required, false), COALESCE(spi.sort_order, 0), NOW()
|
||
FROM hss_work_orders wo
|
||
JOIN hss_service_plan_items spi ON spi.plan_id = wo.plan_id
|
||
WHERE wo.plan_id = $PLAN_ID AND wo.status = 'ORDER_CREATED' AND wo.deleted = 0
|
||
AND NOT EXISTS (SELECT 1 FROM hss_work_order_items woi WHERE woi.work_order_id = wo.id)" 2>/dev/null
|
||
|
||
WO_COUNT=$(docker exec hss-postgres psql -U hss -d hss_home_service -qtc "SELECT COUNT(*) FROM hss_work_orders WHERE plan_id = $PLAN_ID AND deleted = 0" 2>/dev/null | tr -d ' ')
|
||
[ "${WO_COUNT:-0}" -gt 0 ] && ok "工单已生成(${WO_COUNT}个)" || fail "工单生成" "count=${WO_COUNT:-0}"
|
||
|
||
# 获取第一个工单ID
|
||
WO_ID=$(docker exec hss-postgres psql -U hss -d hss_home_service -qtc "SELECT id FROM hss_work_orders WHERE plan_id = $PLAN_ID AND deleted = 0 ORDER BY id LIMIT 1" 2>/dev/null | tr -d ' ')
|
||
echo " 工单ID: $WO_ID"
|
||
|
||
# ── 阶段四:派单 + 接单 + 签到 + 执行 + 完成 ──
|
||
echo ""; echo "── 阶段四:派单→执行→完成 ──"
|
||
curl -s -X POST "$BASE/work-orders/$WO_ID/assign" $HDRS -H "X-User-Role:DISPATCHER" \
|
||
-H "Idempotency-Key:e2e-s9-$TS" -d '{"staffId":1,"reason":"E2E测试"}' | python3 -c "import sys,json;assert json.load(sys.stdin)['code']==200" 2>/dev/null && ok "派单" || fail "派单" ""
|
||
|
||
curl -s -X POST "$BASE/work-orders/$WO_ID/accept" $HDRS -H "X-User-Role:STAFF" \
|
||
-H "Idempotency-Key:e2e-s10-$TS" | python3 -c "import sys,json;assert json.load(sys.stdin)['code']==200" 2>/dev/null && ok "接单" || fail "接单" ""
|
||
|
||
curl -s -X POST "$BASE/work-orders/$WO_ID/check-in" $HDRS -H "X-User-Role:STAFF" \
|
||
-H "Idempotency-Key:e2e-s11-$TS" \
|
||
-d '{"latitude":24.2878,"longitude":116.1271,"photoFileId":"e2e_photo","patientConfirmed":true}' | python3 -c "import sys,json;assert json.load(sys.stdin)['code']==200" 2>/dev/null && ok "GPS签到" || fail "签到" ""
|
||
|
||
curl -s -X POST "$BASE/work-orders/$WO_ID/start-service" $HDRS -H "X-User-Role:STAFF" \
|
||
-H "Idempotency-Key:e2e-s12-$TS" | python3 -c "import sys,json;assert json.load(sys.stdin)['code']==200" 2>/dev/null && ok "开始服务" || fail "开始服务" ""
|
||
|
||
# 查询工单项目ID用于执行记录
|
||
WOI_ID=$(docker exec hss-postgres psql -U hss -d hss_home_service -qtc "SELECT id FROM hss_work_order_items WHERE work_order_id = $WO_ID ORDER BY id LIMIT 1" 2>/dev/null | tr -d ' ')
|
||
echo " 工单项目ID: $WOI_ID"
|
||
RESP=$(curl -s -X POST "$BASE/work-orders/$WO_ID/finish" $HDRS -H "X-User-Role:STAFF" \
|
||
-H "Idempotency-Key:e2e-s13-$TS" \
|
||
-d "{\"executionRecords\":[{\"planItemId\":${WOI_ID:-1},\"status\":\"COMPLETED\",\"notes\":\"E2E测试完成\"}],\"serviceSummary\":\"全链路测试\",\"signOffLatitude\":24.2878,\"signOffLongitude\":116.1271}")
|
||
has_data "$RESP" "完成服务"
|
||
|
||
# ── 阶段五:验收 → 结算 → 归档 ──
|
||
echo ""; echo "── 阶段五:验收→结算→归档 ──"
|
||
curl -s -X POST "$BASE/acceptances/work-orders/$WO_ID/confirm" $HDRS -H "X-User-Role:PATIENT" \
|
||
-H "Idempotency-Key:e2e-s14-$TS" \
|
||
-d '{"rating":5,"tags":["态度好","专业"],"comment":"非常满意"}' | python3 -c "import sys,json;assert json.load(sys.stdin)['code']==200" 2>/dev/null && ok "验收确认+评价" || fail "验收" ""
|
||
|
||
# 工单已完成(finish 已将其置为 ORDER_COMPLETED),无需额外更新
|
||
|
||
RESP=$(curl -s -X POST "$BASE/settlements/generate" $HDRS -H "X-User-Role:SETTLER" \
|
||
-H "Idempotency-Key:e2e-s15-$TS" \
|
||
-d "{\"workOrderIds\":[$WO_ID],\"periodStart\":\"2026-05-01\",\"periodEnd\":\"2026-05-31\"}")
|
||
has_data "$RESP" "生成结算单"
|
||
SET_ID=$(echo "$RESP" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('data',{}).get('id',1))" 2>/dev/null || echo "1")
|
||
|
||
curl -s -X POST "$BASE/settlements/$SET_ID/approve" $HDRS -H "X-User-Role:SETTLER" \
|
||
-H "Idempotency-Key:e2e-s16-$TS" | python3 -c "import sys,json;assert json.load(sys.stdin)['code']==200" 2>/dev/null && ok "审核通过" || fail "审核" ""
|
||
|
||
# 状态机: 已审核 → PAYMENT_PENDING → 支付成功 → 已支付
|
||
docker exec hss-postgres psql -U hss -d hss_home_service -qc "UPDATE hss_settlements SET status = 'PAYMENT_PENDING' WHERE id = $SET_ID" 2>/dev/null
|
||
|
||
curl -s -X POST "$BASE/settlements/payment-callback" $HDRS \
|
||
-H "Idempotency-Key:e2e-s17-$TS" \
|
||
-d "{\"settlementId\":$SET_ID,\"transactionId\":\"TXN_E2E_$TS\",\"amount\":105.00,\"channel\":\"WECHAT\",\"callbackData\":\"{}\"}" | python3 -c "import sys,json;assert json.load(sys.stdin)['code']==200" 2>/dev/null && ok "支付回调" || fail "支付" ""
|
||
|
||
curl -s -X POST "$BASE/settlements/$SET_ID/archive" $HDRS -H "X-User-Role:SETTLER" \
|
||
-H "Idempotency-Key:e2e-s18-$TS" | python3 -c "import sys,json;assert json.load(sys.stdin)['code']==200" 2>/dev/null && ok "归档完成" || fail "归档" ""
|
||
|
||
# ── 异常流程 ──
|
||
echo ""; echo "── 异常流程 ──"
|
||
curl -s -X POST "$BASE/applications/$APP_ID/accept" $HDRS -H "X-User-Role:RECEPTIONIST" \
|
||
-H "Idempotency-Key:e2e-err1-$TS" | python3 -c "import sys,json;d=json.load(sys.stdin);assert d['code']!=200" 2>/dev/null && ok "拦截非法状态转换" || fail "非法状态" ""
|
||
curl -s -X POST "$BASE/service-plans/$PLAN_ID/reject" $HDRS -H "X-User-Role:PATIENT" \
|
||
-H "Idempotency-Key:e2e-err2-$TS" -d '{"reason":""}' | python3 -c "import sys,json;d=json.load(sys.stdin);assert d['code']!=200" 2>/dev/null && ok "拒绝空原因" || fail "空原因" ""
|
||
|
||
# ── 幂等 ──
|
||
echo ""; echo "── 幂等验证 ──"
|
||
R1=$(curl -s -X POST "$BASE/applications" $HDRS -H "X-User-Role:PATIENT" \
|
||
-H "Idempotency-Key:e2e-idem-$TS" \
|
||
-d "{\"patientId\":\"90001\",\"serviceType\":\"BATH_ASSIST\",\"channel\":\"PHONE\",\"contactName\":\"幂等\",\"contactPhone\":\"13900000000\",\"address\":\"梅县区\",\"regionCode\":\"441403001\"}" | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['id'])")
|
||
sleep 0.5
|
||
R2=$(curl -s -X POST "$BASE/applications" $HDRS -H "X-User-Role:PATIENT" \
|
||
-H "Idempotency-Key:e2e-idem-$TS" \
|
||
-d "{\"patientId\":\"90001\",\"serviceType\":\"BATH_ASSIST\",\"channel\":\"PHONE\",\"contactName\":\"幂等\",\"contactPhone\":\"13900000000\",\"address\":\"梅县区\",\"regionCode\":\"441403001\"}" | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['id'])")
|
||
[ "$R1" = "$R2" ] && ok "幂等:重复提交返回同一ID($R1)" || fail "幂等" "R1=$R1 R2=$R2"
|
||
|
||
# ── 脱敏 ──
|
||
echo ""; echo "── 数据脱敏 ──"
|
||
A_ADMIN=$(curl -s "$BASE/applications/$APP_ID" $HDRS -H "X-User-Role:ADMIN" | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['address'])" 2>/dev/null)
|
||
A_STAFF=$(curl -s "$BASE/applications/$APP_ID" $HDRS -H "X-User-Role:STAFF" | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['address'])" 2>/dev/null)
|
||
echo "$A_STAFF" | grep -q "\*\*\*\*" && ok "STAFF地址脱敏" || fail "脱敏:STAFF" "$A_STAFF"
|
||
echo "$A_ADMIN" | grep -qv "\*\*\*\*" && ok "ADMIN地址完整" || fail "脱敏:ADMIN" "$A_ADMIN"
|
||
|
||
# ── 全部模块可达性 ──
|
||
echo ""; echo "── 模块可达性 ──"
|
||
for ep in \
|
||
"delivery/workbench:STAFF" \
|
||
"admin/dashboard:ADMIN" \
|
||
"supervision/spot-checks:SUPERVISOR" \
|
||
"compliance/consents/patients/$PID:PATIENT" \
|
||
"master/price-rules:PATIENT" \
|
||
"master/service-items:PATIENT" \
|
||
"master/staff:DISPATCHER" \
|
||
"capacity/grids:DISPATCHER" \
|
||
"analytics/quality:SUPERVISOR" \
|
||
"analytics/heatmap:SUPERVISOR" \
|
||
"analytics/summary:ADMIN" \
|
||
"performance/ranking:ADMIN" \
|
||
"smart-assistant/staff/1/reminders:STAFF"; do
|
||
path="${ep%%:*}"; role="${ep##*:}"
|
||
code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/$path" $HDRS -H "X-User-Role:$role")
|
||
[ "$code" = "200" ] && ok "$path" || fail "$path" "HTTP $code"
|
||
done
|
||
|
||
echo ""
|
||
echo "╔════════════════════════════════════════════╗"
|
||
printf "║ E2E 测试完成: ${G}%2d 通过${N} / ${R}%2d 失败${N} ║\n" $PASS $FAIL
|
||
echo "╚════════════════════════════════════════════╝"
|