feat: 初始化居家上门服务系统完整项目代码

- Spring Boot 后端服务 (hss-home-service)
- delivery-miniapp 配送小程序
- website 官网 (Nuxt)
- docs 架构设计文档
- Docker 容器化部署配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 09:04:49 +08:00
parent 46c7887a18
commit c02029a5f3
471 changed files with 42313 additions and 2 deletions

182
hss-home-service/test-api.sh Executable file
View File

@@ -0,0 +1,182 @@
#!/bin/bash
# 居家上门服务系统 — 接口集成测试脚本
# 用法: chmod +x test-api.sh && ./test-api.sh
set -e
BASE="http://localhost:18080/api/hss"
TS=$(date +%s)
PASS=0; FAIL=0
GREEN='\033[0;32m'; RED='\033[0;31m'; NC='\033[0m'
hdrs() { echo "-H 'Content-Type: application/json' -H 'X-Tenant-Id: 1' -H 'Idempotency-Key: test-$(date +%s%N)'"; }
ok() { PASS=$((PASS+1)); echo -e "${GREEN}PASS${NC} $1"; }
fail() { FAIL=$((FAIL+1)); echo -e "${RED}FAIL${NC} $1$2"; }
check() { local code=$1; shift; if [ "$code" -eq 200 ] || [ "$code" -eq 302 ]; then ok "$*"; else fail "$*" "HTTP $code"; fi; }
check_json() { echo "$1" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['code']==$2,f'Expected code $2 got {d[\"code\"]}: {d.get(\"message\",\"\")}'" 2>&1; }
echo "============================================"
echo " 居家上门服务系统 API 集成测试"
echo "============================================"
echo ""
# === 1. Infrastructure ===
echo "--- 1. 基础设施 ---"
R=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/applications?page=1&size=1")
check "$R" "OpenAPI可达"
R=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:18080/swagger-ui/index.html")
check "$R" "Swagger UI"
R=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:18080/api-docs")
check "$R" "OpenAPI JSON"
echo ""
# === 2. 主链路:申请 → 评估 → 方案 → 计划 → 工单 → 执行 → 验收 → 结算 → 归档 ===
echo "--- 2. 主链路全流程 ---"
# Step 1: 创建申请
RESP=$(curl -s -X POST "$BASE/applications" \
-H "Content-Type: application/json" \
-H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:PATIENT" -H "Idempotency-Key: e2e-run3-${TS}-app-1" \
-d '{"patientId":"2001","serviceType":"HOME_CARE","channel":"WECHAT","contactName":"张三","contactPhone":"13812345678","address":"梅江区金山街道XX小区3栋201","regionCode":"441402001"}')
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['code']==200;assert d['data']['status']=='DRAFT'" 2>&1 && ok "创建申请(DRAFT)" || fail "创建申请(DRAFT)" "$RESP"
APP_ID=$(echo "$RESP" | python3 -c "import sys,json;print(json.loads(sys.stdin.read())['data']['id'])")
# Step 2: 提交申请
RESP=$(curl -s -X POST "$BASE/applications/$APP_ID/submit" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:PATIENT" -H "Idempotency-Key: e2e-run3-${TS}-app-2")
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['data']['status']=='PENDING_ACCEPTANCE'" 2>&1 && ok "提交申请(PENDING_ACCEPTANCE)" || fail "提交申请" "$RESP"
# Step 3: 受理通过
RESP=$(curl -s -X POST "$BASE/applications/$APP_ID/accept" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:RECEPTIONIST" -H "Idempotency-Key: e2e-run3-${TS}-app-3")
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['data']['status']=='PENDING_ASSESSMENT'" 2>&1 && ok "受理通过(PENDING_ASSESSMENT)" || fail "受理通过" "$RESP"
# Step 4: 派发评估
RESP=$(curl -s -X POST "$BASE/assessments/$APP_ID/assign" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:RECEPTIONIST" -H "Idempotency-Key: e2e-run3-${TS}-asm-1" \
-d '{"assessorId": 100}')
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['code']==200" 2>&1 && ok "派发评估" || fail "派发评估" "$RESP"
ASM_ID=$(echo "$RESP" | python3 -c "import sys,json;print(json.loads(sys.stdin.read())['data']['id'])" 2>/dev/null || echo "1")
# Step 5: 提交评估报告
RESP=$(curl -s -X POST "$BASE/assessments/$ASM_ID/submit" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:ASSESSOR" -H "Idempotency-Key: e2e-run3-${TS}-asm-2" \
-d '{"careLevel":"LEVEL_3","riskLevel":"MEDIUM","reportContent":"{\"mobility\":\"limited\",\"cognition\":\"normal\"}"}')
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['code']==200" 2>&1 && ok "提交评估报告" || fail "提交评估报告" "$RESP"
# Step 6: 创建方案
RESP=$(curl -s -X POST "$BASE/service-plans" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:PLANNER" -H "Idempotency-Key: e2e-run3-${TS}-plan-1" \
-d '{"applicationId":'$APP_ID',"assessmentTaskId":'$ASM_ID',"items":[{"serviceItemId":1,"itemName":"助洁服务","unitPrice":50.00,"frequency":3,"standardDuration":60,"evidenceRequired":false}]}')
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['code']==200" 2>&1 && ok "创建方案(PLAN_DRAFT)" || fail "创建方案" "$RESP"
PLAN_ID=$(echo "$RESP" | python3 -c "import sys,json;print(json.loads(sys.stdin.read())['data']['id'])")
# Step 7: 提交签署
RESP=$(curl -s -X POST "$BASE/service-plans/$PLAN_ID/submit-sign" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:PLANNER" -H "Idempotency-Key: e2e-run3-${TS}-plan-2")
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['data']['status']=='PLAN_PENDING_SIGN'" 2>&1 && ok "提交签署(PENDING_SIGN)" || fail "提交签署" "$RESP"
# Step 8: 签署方案(自动生成服务计划)
RESP=$(curl -s -X POST "$BASE/service-plans/$PLAN_ID/sign" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:PATIENT" -H "Idempotency-Key: e2e-run3-${TS}-plan-3")
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['data']['status']=='PLAN_EFFECTIVE'" 2>&1 && ok "签署方案(PLAN_EFFECTIVE)" || fail "签署方案" "$RESP"
# Step 9: 验证服务计划已生成
SCH_RESP=$(curl -s "$BASE/service-schedules/plans/$PLAN_ID" -H "X-Tenant-Id: 1")
echo "$SCH_RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert len(d['data'])>0" 2>&1 && ok "服务计划已自动生成" || fail "服务计划生成" "$SCH_RESP"
echo ""
echo "--- 3. 异常流程 ---"
# Step 10: 非法状态转换(已通过不能再次接受)
RESP=$(curl -s -X POST "$BASE/applications/$APP_ID/accept" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:RECEPTIONIST" -H "Idempotency-Key: e2e-run3-${TS}-fail-1")
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['code']!=200" 2>&1 && ok "拦截非法状态转换" || fail "非法状态转换" "$RESP"
# Step 11: 角色越权PATIENT 不能受理)
RESP=$(curl -s -X POST "$BASE/applications/$APP_ID/accept" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:PATIENT" -H "Idempotency-Key: e2e-run3-${TS}-fail-2")
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['code']!=200" 2>&1 && ok "拦截角色越权" || fail "角色越权" "$RESP"
# Step 12: 退回缺少原因
RESP=$(curl -s -X POST "$BASE/applications/99999/return" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:RECEPTIONIST" -H "Idempotency-Key: e2e-run3-${TS}-fail-3" \
-d '{"reason":""}')
echo "$RESP" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());assert d['code']!=200" 2>&1 && ok "拒绝空退回原因" || fail "空退回原因" "$RESP"
echo ""
echo "--- 4. 幂等验证 ---"
# 重复提交同一 Idempotency-Key
R1=$(curl -s -X POST "$BASE/applications" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:PATIENT" -H "Idempotency-Key: e2e-run3-${TS}-idem-dup" \
-d '{"patientId":"3001","serviceType":"BATH_ASSIST","channel":"PHONE","contactName":"重复测试","contactPhone":"13900000000","address":"梅县区","regionCode":"441403001"}' | python3 -c "import sys,json;print(json.loads(sys.stdin.read())['data']['id'])")
sleep 1
R2=$(curl -s -X POST "$BASE/applications" \
-H "Content-Type: application/json" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:PATIENT" -H "Idempotency-Key: e2e-run3-${TS}-idem-dup" \
-d '{"patientId":"3001","serviceType":"BATH_ASSIST","channel":"PHONE","contactName":"重复测试","contactPhone":"13900000000","address":"梅县区","regionCode":"441403001"}' | python3 -c "import sys,json;print(json.loads(sys.stdin.read())['data']['id'])")
if [ "$R1" = "$R2" ]; then ok "幂等:重复提交返回同一结果($R1)"; else fail "幂等重复提交" "R1=$R1 R2=$R2"; fi
echo ""
echo "--- 5. 数据脱敏 ---"
R_ADMIN=$(curl -s "$BASE/applications/$APP_ID" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:ADMIN")
R_STAFF=$(curl -s "$BASE/applications/$APP_ID" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:STAFF")
ADDR_ADMIN=$(echo "$R_ADMIN" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());print(d['data'].get('address',''))")
ADDR_STAFF=$(echo "$R_STAFF" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());print(d['data'].get('address',''))")
if [ "$ADDR_ADMIN" != "$ADDR_STAFF" ] && echo "$ADDR_STAFF" | grep -q "\*\*\*\*"; then
ok "角色脱敏ADMIN完整地址, STAFF已脱敏"
else
fail "角色脱敏" "ADMIN=$ADDR_ADMIN STAFF=$ADDR_STAFF"
fi
echo ""
echo "--- 6. Delivery 接口 ---"
R=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/delivery/workbench" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:STAFF")
check "$R" "Delivery工作台"
R=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/delivery/work-orders/today" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:STAFF")
check "$R" "Delivery今日工单"
R=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/delivery/messages" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:STAFF")
check "$R" "Delivery消息"
echo ""
echo "--- 7. 管理端 ---"
R=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/admin/dashboard" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:ADMIN")
check "$R" "管理端仪表盘"
R=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/admin/work-orders" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:DISPATCHER")
check "$R" "管理端工单列表"
echo ""
echo "--- 8. 监管/合规/主数据/运力/看板/绩效/智能助手 ---"
for endpoint in \
"supervision/spot-checks:ADMIN" \
"compliance/consents/patients/1:PATIENT" \
"master/service-items:PATIENT" \
"master/price-rules:PATIENT" \
"master/regions:PATIENT" \
"master/staff:DISPATCHER" \
"capacity/grids:DISPATCHER" \
"capacity/dashboard:DISPATCHER" \
"analytics/quality:SUPERVISOR" \
"analytics/heatmap:SUPERVISOR" \
"analytics/continuity:PATIENT" \
"analytics/staff-load:DISPATCHER" \
"analytics/summary:ADMIN" \
"performance/ranking:DISPATCHER"; do
path="${endpoint%%:*}"
role="${endpoint##*:}"
R=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/$path" -H "X-Tenant-Id: 1" -H "X-Org-Id: 1" -H "X-User-Role:$role")
check "$R" "$path"
done
echo ""
echo "============================================"
echo -e " 结果: ${GREEN}$PASS 通过${NC} / ${RED}$FAIL 失败${NC}"
echo "============================================"