#!/bin/bash # CampusNotice 接口 curl 测试脚本 v2 # 用法: ./api-curl-test.sh [BASE] [PLATFORM] # BASE 默认 http://localhost:8101 # PLATFORM 默认 manage(CAS 仅拦 /api/* 与 /mobiapi/*,manage 为匿名通道;生产环境应封死,见安全提醒) # 注意:中文 JSON 体必须走 UTF-8 文件(--data-binary @file), # 直接 -d '中文' 会被 Git Bash→curl.exe 的 argv 转换变成 GBK,服务端报 # Invalid UTF-8 start byte(HttpMessageNotReadable → code=6),属测试工具问题。 BASE="${1:-http://localhost:8101}" PF="${2:-manage}" U="$BASE/$PF" TMPB="$(dirname "$0")/.curl-body.json" PASS=0; BIZ=0; FAIL=0 CREATED_IDS=() req() { # req 名称 方法 路径 [json体] local name="$1" method="$2" path="$3" body="$4" local args=(-s --max-time 15 -w "\n%{http_code}" -H "Accept: application/json") if [ -n "$body" ]; then printf '%s' "$body" > "$TMPB" # UTF-8 原样字节落盘 args+=(-X POST -H "Content-Type: application/json; charset=utf-8" --data-binary "@$(cygpath -w "$TMPB" 2>/dev/null || echo "$TMPB")") elif [ "$method" = "POST" ]; then args+=(-X POST) fi local out; out=$(curl "${args[@]}" "$U$path" 2>/dev/null) local http="${out##*$'\n'}"; local json="${out%$'\n'*}" local parsed; parsed=$(node -e " try{const j=JSON.parse(process.argv[1]); const d=j.data; const ds=d===null||d===undefined?'null':Array.isArray(d)?'array['+d.length+']':typeof d==='object'?'{'+Object.keys(d).slice(0,4).join(',')+'}':String(d).slice(0,30); console.log([j.code,(j.msg||'').slice(0,46),ds].join('\u0001')); }catch(e){console.log(['-','-','-'].join('\u0001'))}" "$json" 2>/dev/null) local code msg data code=$(echo "$parsed" | cut -d$'\001' -f1) msg=$(echo "$parsed" | cut -d$'\001' -f2) data=$(echo "$parsed" | cut -d$'\001' -f3) local st if [ "$code" = "0" ]; then PASS=$((PASS+1)); st="✅PASS"; elif [ "$code" = "-" ] || [ "$http" != "200" ]; then FAIL=$((FAIL+1)); st="❌FAIL"; else BIZ=$((BIZ+1)); st="🟡BIZ"; fi printf "%-26s | HTTP %s | code=%-3s | msg=%-46s | data=%s\n" "$name" "$http" "$code" "$msg" "$data" } extract_id() { # 从最近一次输出无法跨函数取值,改为调用方直接用 node 从 curl 结果提取 node -e "try{console.log(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).data ?? '')}catch(e){console.log('')}" "$1" 2>/dev/null } echo "================ CampusNotice curl 矩阵 (BASE=$BASE PF=$PF) ================" echo "---- A. 静态(直连 BASE,不经 platform 前缀) ----" for p in "/" "/api-test.html"; do http=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$BASE$p") if [ "$http" = "200" ]; then PASS=$((PASS+1)); printf "%-26s | HTTP %s\n" "静态 $p" "$http"; else FAIL=$((FAIL+1)); printf "%-26s | HTTP %s ❌\n" "静态 $p" "$http"; fi done http=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$BASE/api/notice/Statis") if [ "$http" = "302" ]; then PASS=$((PASS+1)); printf "%-26s | HTTP 302 → CAS 登录墙(符合预期)\n" "/api 登录墙对照"; else FAIL=$((FAIL+1)); printf "%-26s | HTTP $http ❌(预期302)\n" "/api 登录墙对照"; fi echo "---- B. 平台/基础(匿名语义) ----" req "GetUserInfo" GET "/base/GetUserInfo" req "GetUserMod(匿名预期拒)" GET "/base/GetUserMod" req "GetSchoolAll" GET "/base/GetSchoolAll" req "GetGroupUser(未接入桩)" GET "/base/GetGroupUser?gid=1&role=2" echo "---- C. 通知查询 ----" req "notice/List" GET "/notice/List?pageIndex=1&pageSize=3" req "notice/SendList" GET "/notice/SendList?pageIndex=1&pageSize=3&status=1&scope=user" req "notice/Statis" GET "/notice/Statis" req "notice/GetNextPrev" GET "/notice/GetNextPrev?serid=1&type=0" req "notice/DraftList" GET "/notice/DraftList?pageIndex=1&pageSize=3" req "notice/ApproverSuggest" GET "/notice/ApproverSuggest" req "ApprovalList(pending)" GET "/notice/ApprovalList?auditState=pending&pageIndex=1&pageSize=3" req "ApprovalList(reviewed)" GET "/notice/ApprovalList?auditState=reviewed&pageIndex=1&pageSize=3" echo "---- D. 推送范围(匿名预期拒) ----" req "notice/ScopeTree" GET "/notice/ScopeTree" req "notice/ScopePreview" GET "/notice/ScopePreview?scopes=%5B%7B%22scopeType%22%3A1%2C%22scopeId%22%3A0%2C%22scopeName%22%3A%22%E5%85%A8%E6%A0%A1%22%7D%5D" echo "---- E. 写链路(UTF-8 文件体,测后自清理) ----" printf '%s' '{"title":"【测试】curl直发","content":"
curl
","type":1,"deptId":"1","deptName":"校长室","topmost":0,"receiveList":"ALL","fileIds":"","timingSendTime":null,"smsFlag":0,"receiptFlag":1,"publishType":"direct"}' > "$TMPB" resp=$(curl -s --max-time 15 -X POST -H "Content-Type: application/json; charset=utf-8" --data-binary "@$(cygpath -w "$TMPB" 2>/dev/null || echo "$TMPB")" "$U/notice/Create") echo "$resp" > "$TMPB.out" ID_DIRECT=$(extract_id "$TMPB.out") if [ -n "$ID_DIRECT" ]; then PASS=$((PASS+1)); printf "%-26s | code=0 | id=%s ✅\n" "Create(direct)" "$ID_DIRECT"; else FAIL=$((FAIL+1)); printf "%-26s | ❌ 未返回id\n" "Create(direct)"; fi CREATED_IDS+=("$ID_DIRECT") printf '%s' '{"title":"【测试】curl草稿","content":"curl
","type":1,"deptId":"1","deptName":"校长室","topmost":0,"receiveList":"ALL","fileIds":"","timingSendTime":null,"smsFlag":0,"receiptFlag":1}' > "$TMPB" resp=$(curl -s --max-time 15 -X POST -H "Content-Type: application/json; charset=utf-8" --data-binary "@$(cygpath -w "$TMPB" 2>/dev/null || echo "$TMPB")" "$U/notice/SaveDraft") echo "$resp" > "$TMPB.out" ID_DRAFT=$(extract_id "$TMPB.out") if [ -n "$ID_DRAFT" ]; then PASS=$((PASS+1)); printf "%-26s | code=0 | id=%s ✅\n" "SaveDraft" "$ID_DRAFT"; else FAIL=$((FAIL+1)); printf "%-26s | ❌ 未返回id\n" "SaveDraft"; fi CREATED_IDS+=("$ID_DRAFT") if [ -n "$ID_DRAFT" ]; then printf '%s' "{\"serid\":$ID_DRAFT,\"approverAccounts\":[\"lijing\"],\"opinion\":\"请审批\"}" > "$TMPB" resp=$(curl -s --max-time 15 -X POST -H "Content-Type: application/json; charset=utf-8" --data-binary "@$(cygpath -w "$TMPB" 2>/dev/null || echo "$TMPB")" "$U/notice/Submit") code=$(node -e "try{console.log(JSON.parse(process.argv[1]).code)}catch(e){console.log('-')}" "$resp") if [ "$code" = "0" ]; then PASS=$((PASS+1)); printf "%-26s | code=0 | 草稿→待审批 ✅\n" "Submit"; else BIZ=$((BIZ+1)); printf "%-26s | code=%s 🟡\n" "Submit" "$code"; fi fi req "Approve(匿名预期拒)" POST "/notice/Approve?serid=${ID_DRAFT:-0}&opinion=x" req "Reject(匿名预期拒)" POST "/notice/Reject?serid=${ID_DRAFT:-0}&opinion=x" echo "---- F. 其他模块 ----" req "dict/ManageList" GET "/dict/ManageList" req "template/Typelist" GET "/template/Typelist" req "contacts/GetGroup" GET "/contacts/GetGroup" req "sms/GetSchool" GET "/sms/GetSchool" echo "---- G. 附件 ----" f="$(dirname "$0")/.curl-upload-sample.txt"; echo "curl upload sample $(date +%s)" > "$f" req "base/FileUpload" POST "/base/FileUpload" "" "file=@$(cygpath -w "$f" 2>/dev/null || echo "$f")" rm -f "$f" echo "---- H. 清理测试数据 ----" if [ "${#CREATED_IDS[@]}" -gt 0 ]; then ids=$(IFS=,; echo "${CREATED_IDS[*]}") printf '%s' "[$ids]" > "$TMPB" resp=$(curl -s --max-time 15 -X POST -H "Content-Type: application/json; charset=utf-8" --data-binary "@$(cygpath -w "$TMPB" 2>/dev/null || echo "$TMPB")" "$U/notice/Delete") code=$(node -e "try{console.log(JSON.parse(process.argv[1]).code)}catch(e){console.log('-')}" "$resp") if [ "$code" = "0" ]; then PASS=$((PASS+1)); printf "%-26s | code=0 | ids=[$ids] 已清理 ✅\n" "Delete清理"; else FAIL=$((FAIL+1)); printf "%-26s | code=%s ❌\n" "Delete清理" "$code"; fi fi rm -f "$TMPB" "$TMPB.out" echo "================ 统计: PASS=$PASS BIZ(业务拒绝/环境预期)=$BIZ FAIL=$FAIL ================" echo "提醒: /manage 匿名可访问全部业务接口——CAS 仅拦 /api/* 与 /mobiapi/*,生产环境务必封死(安全项)。"