#!/usr/bin/env bash set -euo pipefail REMOTE_SSH="${AIHR_AUG1_REMOTE_SSH:-YCWY}" REMOTE_SERVICE="${AIHR_AUG1_REMOTE_SERVICE:-wygj-aihr.service}" REMOTE_DB="${AIHR_AUG1_REMOTE_DB:-ry-vue}" REMOTE_BASE_URL="${AIHR_AUG1_REMOTE_BASE_URL:-https://peilian.njzhmj.top/dev-api}" if [[ "${1:-}" != "--execute" ]]; then cat <<'USAGE' Usage: ./scripts/verify-aug1-authenticated-production.sh --execute Runs an authenticated August 1 production smoke test. The remote service must have both fixed-code settings enabled. The script selects an existing active APP user with a unique organization identity, never prints the phone, code, or token, performs no registration and submits no business records. USAGE exit 2 fi [[ "$REMOTE_DB" =~ ^[A-Za-z0-9_-]+$ ]] || { echo "FAIL: invalid remote database name" >&2 exit 3 } ssh -o BatchMode=yes -o ConnectTimeout=10 "$REMOTE_SSH" bash -s -- \ "$REMOTE_SERVICE" "$REMOTE_DB" "$REMOTE_BASE_URL" <<'REMOTE' set -euo pipefail service="$1" db="$2" base="$3" pid="$(systemctl show -p MainPID --value "$service")" [[ -n "$pid" && "$pid" != "0" ]] || { echo "FAIL: production service is not running" >&2 exit 10 } fixed_code="" fixed_enabled="false" while IFS='=' read -r key value; do case "$key" in AIHR_SMS_DEV_FIXED_CODE) fixed_code="$value" ;; AIHR_SMS_PROD_FIXED_CODE_ENABLED) fixed_enabled="$value" ;; esac done < <(tr '\0' '\n' < "/proc/$pid/environ") [[ -n "$fixed_code" && "$fixed_enabled" == "true" ]] || { echo "FAIL: production fixed-code mode is not explicitly enabled" >&2 exit 11 } phone="$(mysql --batch --skip-column-names --connect-timeout=5 "$db" -e " SELECT o.person_phone FROM aihr_org_snapshot o JOIN sys_user u ON u.tenant_id = o.tenant_id AND BINARY u.phonenumber = BINARY o.person_phone AND u.user_type = 'app_user' AND u.status = '0' AND u.del_flag = '0' WHERE o.tenant_id = '000000' AND o.employment_status = 'active' AND o.person_phone REGEXP '^1[3-9][0-9]{9}$' AND NOT EXISTS ( SELECT 1 FROM sys_user su WHERE su.tenant_id = o.tenant_id AND BINARY su.phonenumber = BINARY o.person_phone AND (su.user_type IS NULL OR su.user_type <> 'app_user') ) AND NOT EXISTS ( SELECT 1 FROM aihr_org_snapshot x WHERE x.tenant_id = o.tenant_id AND x.employment_status = 'active' AND x.ext_party_id = o.ext_party_id AND NULLIF(x.person_phone, '') IS NOT NULL AND BINARY x.person_phone <> BINARY o.person_phone ) GROUP BY o.person_phone HAVING COUNT(DISTINCT o.ext_party_id) = 1 ORDER BY MAX(CASE WHEN o.position_name = '生活顾问' THEN 1 ELSE 0 END) DESC, MAX(o.snapshot_date) DESC LIMIT 1 ")" [[ "$phone" =~ ^1[3-9][0-9]{9}$ ]] || { echo "FAIL: no existing active APP user has a unique organization identity" >&2 exit 12 } count_fixed_log() { local total=0 root matches for root in /opt/wygj/logs /opt/wygj/app/logs /var/log/wygj; do [[ -d "$root" ]] || continue matches="$(grep -R -F -h --include='*.log' --include='*.out' \ '短信验证码走固定码,未真实发送短信' "$root" 2>/dev/null | wc -l)" total=$((total + matches)) done printf '%s\n' "$total" } fixed_log_before="$(count_fixed_log)" AIHR_TEST_BASE="$base" \ AIHR_TEST_PHONE="$phone" \ AIHR_TEST_CODE="$fixed_code" \ python3 - <<'PY' import json import os import time import urllib.error import urllib.parse import urllib.request base = os.environ["AIHR_TEST_BASE"].rstrip("/") phone = os.environ["AIHR_TEST_PHONE"] code = os.environ["AIHR_TEST_CODE"] def call(method, path, body=None, token=None, client=None): data = None if body is None else json.dumps(body, ensure_ascii=False).encode("utf-8") headers = {"Accept": "application/json"} if data is not None: headers["Content-Type"] = "application/json" if token: headers["Authorization"] = f"Bearer {token}" if client: headers["clientid"] = client request = urllib.request.Request(base + path, data=data, headers=headers, method=method) try: with urllib.request.urlopen(request, timeout=45) as response: raw = response.read().decode("utf-8", "replace") return response.status, json.loads(raw or "{}") except urllib.error.HTTPError as error: raw = error.read().decode("utf-8", "replace") try: payload = json.loads(raw or "{}") except Exception: payload = {} return error.code, payload def require_ok(label, result): status, payload = result business_code = payload.get("code") if isinstance(payload, dict) else None passed = status == 200 and business_code == 200 print(f"{label}={'PASS' if passed else 'FAIL'} http={status} biz={business_code}") if not passed: raise SystemExit(20) return payload.get("data") sms_path = "/resource/sms/code?phonenumber=" + urllib.parse.quote(phone) require_ok("FIXED_SMS_REQUEST", call("GET", sms_path)) login = require_ok( "MOBILE_LOGIN", call("POST", "/auth/mobile/sms-login", {"phonenumber": phone, "smsCode": code}), ) if not isinstance(login, dict) or not login.get("access_token") or not login.get("client_id"): print("MOBILE_LOGIN_TOKEN=FAIL") raise SystemExit(21) token = login["access_token"] client = login["client_id"] identity = require_ok( "MOBILE_ME", call("GET", "/api/aihr/mobile/me", token=token, client=client), ) role = identity.get("role") if isinstance(identity, dict) else None projects = identity.get("projects") if isinstance(identity, dict) else [] print("MOBILE_ROLE_VALID=" + ("PASS" if role in ("user", "supervisor") else "FAIL")) print("MOBILE_PROJECT_SCOPE=" + ("NONEMPTY" if isinstance(projects, list) and projects else "EMPTY")) if role not in ("user", "supervisor") or not isinstance(projects, list) or not projects: raise SystemExit(22) route_role = "supervisor" if role == "supervisor" else "user" read_checks = [ ("MOBILE_HOME_AUTHENTICATED", f"/api/aihr/mobile/home/{route_role}"), ("PRACTICE_HISTORY_READ", "/api/aihr/mobile/practice/history"), ("PRACTICE_PROFILE_READ", "/api/aihr/mobile/profile"), ("DIRECT_MINE_READ", "/api/aihr/direct/mine?pageNum=1&pageSize=10"), ("BROADCAST_READ", "/api/aihr/broadcast/messages?pageNum=1&pageSize=10&scope=all"), ("EXAM_READ", "/api/aihr/mobile/exams"), ("CASE_CAPABILITIES_READ", "/api/knowledge/case/capabilities"), ("WEB_AI_CAPABILITIES_READ", "/api/aihr/web-ai/capabilities"), ] for label, path in read_checks: require_ok(label, call("GET", path, token=token, client=client)) channels = require_ok( "DIRECT_CHANNELS_READ", call("GET", "/api/aihr/direct/channels", token=token, client=client), ) channel_count = len(channels) if isinstance(channels, list) else -1 print(f"DIRECT_CHANNEL_COUNT={channel_count}") if channel_count != 5: raise SystemExit(23) require_ok( "MOBILE_LOGOUT", call("POST", "/auth/logout", {}, token=token, client=client), ) status, payload = call("GET", "/api/aihr/mobile/me", token=token, client=client) business_code = payload.get("code") if isinstance(payload, dict) else None expired = status == 401 or business_code == 401 print(f"LOGOUT_TOKEN_REJECTED={'PASS' if expired else 'FAIL'} http={status} biz={business_code}") if not expired: raise SystemExit(24) time.sleep(60) require_ok("RELOGIN_FIXED_SMS_REQUEST", call("GET", sms_path)) relogin = require_ok( "MOBILE_RELOGIN", call("POST", "/auth/mobile/sms-login", {"phonenumber": phone, "smsCode": code}), ) if not isinstance(relogin, dict) or not relogin.get("access_token") or not relogin.get("client_id"): print("MOBILE_RELOGIN_TOKEN=FAIL") raise SystemExit(25) relogin_token = relogin["access_token"] relogin_client = relogin["client_id"] require_ok( "MOBILE_RELOGIN_ME", call("GET", "/api/aihr/mobile/me", token=relogin_token, client=relogin_client), ) require_ok( "MOBILE_RELOGIN_LOGOUT", call("POST", "/auth/logout", {}, token=relogin_token, client=relogin_client), ) PY fixed_log_after="$fixed_log_before" fixed_log_expected=$((fixed_log_before + 2)) for _ in 1 2 3 4 5; do fixed_log_after="$(count_fixed_log)" (( fixed_log_after >= fixed_log_expected )) && break sleep 1 done if (( fixed_log_after < fixed_log_expected )); then echo "FIXED_SMS_NO_REAL_SEND_LOG=FAIL" exit 13 fi echo "FIXED_SMS_NO_REAL_SEND_LOG=PASS" REMOTE