#!/usr/bin/env bash set -euo pipefail # ===== 修改这里即可 ===== ACCOUNT="${ACCOUNT:-1234567}" PASSWORD="${PASSWORD:-$ACCOUNT}" GATEWAY_BASE_URL="${GATEWAY_BASE_URL:-http://192.168.110.64:1100}" SYS_ORIGIN="${SYS_ORIGIN:-LIKEI}" SYS_ORIGIN_CHILD="${SYS_ORIGIN_CHILD:-LIKEI}" NICKNAME="${NICKNAME:-local-${ACCOUNT}}" PHONE_PREFIX="${PHONE_PREFIX:-86}" PHONE_NUMBER="${PHONE_NUMBER:-138$(date +%d%H%M%S)}" OPEN_ID="${OPEN_ID:-local-${ACCOUNT}-${PHONE_NUMBER}}" COUNTRY_CODE="${COUNTRY_CODE:-CN}" COUNTRY_NAME="${COUNTRY_NAME:-China}" COUNTRY_ID="${COUNTRY_ID:-}" MYSQL_CONTAINER="${MYSQL_CONTAINER:-chatapp3-local-mysql}" MYSQL_DATABASE="${MYSQL_DATABASE:-likei}" MYSQL_USER="${MYSQL_USER:-root}" MYSQL_PASSWORD="${MYSQL_PASSWORD:-123456}" REDIS_CONTAINER="${REDIS_CONTAINER:-chatapp3-local-redis}" REQ_CLIENT="${REQ_CLIENT:-Android}" REQ_APP_INTEL="${REQ_APP_INTEL:-version=1.0.0;build=1;model=LocalScript;sysVersion=Mac;channel=local;}" REQ_LANG="${REQ_LANG:-zh-CN}" REQ_ZONE="${REQ_ZONE:-Asia/Shanghai}" REQ_IMEI="${REQ_IMEI:-local-create-app-user-${ACCOUNT}}" HTTP_TIMEOUT="${HTTP_TIMEOUT:-15}" # ======================== GATEWAY_BASE_URL="${GATEWAY_BASE_URL%/}" log() { printf '[create-local-app-user] %s\n' "$*" } die() { printf '[create-local-app-user] ERROR: %s\n' "$*" >&2 exit 1 } require_cmd() { command -v "$1" >/dev/null 2>&1 || die "缺少命令: $1" } sql_quote() { printf "%s" "$1" | sed "s/'/''/g" } mysql_exec() { docker exec -i -e MYSQL_PWD="$MYSQL_PASSWORD" "$MYSQL_CONTAINER" \ mysql -u"$MYSQL_USER" "$MYSQL_DATABASE" } mysql_scalar() { docker exec -i -e MYSQL_PWD="$MYSQL_PASSWORD" "$MYSQL_CONTAINER" \ mysql -u"$MYSQL_USER" -N -B "$MYSQL_DATABASE" -e "$1" | head -n 1 } json_login_payload() { ACCOUNT="$ACCOUNT" PASSWORD="$PASSWORD" python3 - <<'PY' import json import os print(json.dumps({ "account": os.environ["ACCOUNT"], "pwd": os.environ["PASSWORD"], }, ensure_ascii=False)) PY } json_register_payload() { ACCOUNT="$ACCOUNT" PASSWORD="$PASSWORD" NICKNAME="$NICKNAME" OPEN_ID="$OPEN_ID" \ PHONE_PREFIX="$PHONE_PREFIX" PHONE_NUMBER="$PHONE_NUMBER" \ COUNTRY_CODE="$COUNTRY_CODE" COUNTRY_NAME="$COUNTRY_NAME" COUNTRY_ID="$COUNTRY_ID" \ python3 - <<'PY' import json import os profile = { "userNickname": os.environ["NICKNAME"], "userAvatar": "", "userSex": 1, "bornYear": 1998, "bornMonth": 1, "bornDay": 1, } if os.environ.get("COUNTRY_CODE"): profile["countryCode"] = os.environ["COUNTRY_CODE"] if os.environ.get("COUNTRY_NAME"): profile["countryName"] = os.environ["COUNTRY_NAME"] if os.environ.get("COUNTRY_ID"): profile["countryId"] = int(os.environ["COUNTRY_ID"]) print(json.dumps({ "type": "MOBILE", "openId": os.environ["OPEN_ID"], "profile": profile, "mobile": { "phonePrefix": int(os.environ["PHONE_PREFIX"]), "phoneNumber": os.environ["PHONE_NUMBER"], "pwd": os.environ["PASSWORD"], "code": "000000", }, }, ensure_ascii=False)) PY } api_post() { local path="$1" local body="$2" local output="$3" local code code="$(curl -sS -m "$HTTP_TIMEOUT" -o "$output" -w "%{http_code}" \ -X POST "${GATEWAY_BASE_URL}${path}" \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -H "Req-Sys-Origin: origin=${SYS_ORIGIN};originChild=${SYS_ORIGIN_CHILD}" \ -H "Req-Client: ${REQ_CLIENT}" \ -H "Req-App-Intel: ${REQ_APP_INTEL}" \ -H "Req-Lang: ${REQ_LANG}" \ -H "Req-Zone: ${REQ_ZONE}" \ -H "req-imei: ${REQ_IMEI}" \ --data "$body" || true)" [[ "$code" == 2* ]] } json_status_ok() { python3 - "$1" <<'PY' import json import sys try: data = json.load(open(sys.argv[1], encoding="utf-8")) except Exception: sys.exit(1) if isinstance(data, dict) and "status" in data: sys.exit(0 if data.get("status") is True else 1) sys.exit(0) PY } print_api_error() { python3 - "$1" <<'PY' import json import sys text = open(sys.argv[1], encoding="utf-8").read() try: data = json.loads(text) except Exception: print(text) sys.exit(0) if isinstance(data, dict): msg = data.get("errorMsg") or data.get("message") or data print(msg) else: print(data) PY } extract_user_id() { python3 - "$1" <<'PY' import json import sys data = json.load(open(sys.argv[1], encoding="utf-8")) body = data.get("body", data) if isinstance(data, dict) else {} profile = body.get("userProfile", {}) if isinstance(body, dict) else {} user_id = profile.get("id") or profile.get("userId") or body.get("userId") print(user_id or "") PY } extract_account() { python3 - "$1" <<'PY' import json import sys data = json.load(open(sys.argv[1], encoding="utf-8")) body = data.get("body", data) if isinstance(data, dict) else {} profile = body.get("userProfile", {}) if isinstance(body, dict) else {} print(profile.get("account") or "") PY } try_login() { local output="$1" local payload payload="$(json_login_payload)" api_post "/auth/account/login" "$payload" "$output" && json_status_ok "$output" } generate_password_hash() { local raw raw="$(htpasswd -bnBC 10 '' "$PASSWORD" | sed 's/^://')" printf '{bcrypt}%s' "$raw" } ensure_account_password() { local user_id="$1" local password_hash="$2" local account_q origin_q origin_child_q hash_q account_q="$(sql_quote "$ACCOUNT")" origin_q="$(sql_quote "$SYS_ORIGIN")" origin_child_q="$(sql_quote "$SYS_ORIGIN_CHILD")" hash_q="$(sql_quote "$password_hash")" mysql_exec </dev/null; done redis-cli DEL 'USER:HASH:${user_id}' 'USER:PROFILE:${user_id}' 'USER:USER_RUN_PROFILE:${user_id}' 'USER:USER_REGION:${user_id}' >/dev/null " >/dev/null || true fi } main() { require_cmd curl require_cmd docker require_cmd python3 require_cmd htpasswd docker ps --format '{{.Names}}' | grep -qx "$MYSQL_CONTAINER" \ || die "MySQL 容器未运行: ${MYSQL_CONTAINER}" log "目标账号: ${ACCOUNT}" log "网关地址: ${GATEWAY_BASE_URL}" local login_response login_response="$(mktemp)" if try_login "$login_response"; then log "账号已可登录,无需创建。userId=$(extract_user_id "$login_response") account=$(extract_account "$login_response")" rm -f "$login_response" return fi local login_error login_error="$(print_api_error "$login_response")" rm -f "$login_response" if [[ -z "$login_error" || "$login_error" == *"Connection refused"* || "$login_error" == *"timed out"* || "$login_error" == *"No route"* ]]; then die "登录接口不可用,请先检查 GATEWAY_BASE_URL/auth/other 服务。原因: ${login_error:-empty response}" fi log "直接登录未通过,开始创建或补齐本地账号密码。原因: ${login_error}" local account_q origin_q existing_user_id user_id account_q="$(sql_quote "$ACCOUNT")" origin_q="$(sql_quote "$SYS_ORIGIN")" existing_user_id="$(mysql_scalar "SELECT id FROM user_base_info WHERE account='${account_q}' AND origin_sys='${origin_q}' AND is_del=0 LIMIT 1;")" if [[ -n "$existing_user_id" ]]; then user_id="$existing_user_id" log "账号已存在,重置账号密码登录信息。userId=${user_id}" else local create_response create_payload generated_account create_response="$(mktemp)" create_payload="$(json_register_payload)" log "账号不存在,先走 App 注册接口创建完整用户。phone=+${PHONE_PREFIX} ${PHONE_NUMBER}" api_post "/auth/account/create" "$create_payload" "$create_response" \ || die "注册接口请求失败: $(print_api_error "$create_response")" json_status_ok "$create_response" \ || die "注册接口返回失败: $(print_api_error "$create_response")" user_id="$(extract_user_id "$create_response")" generated_account="$(extract_account "$create_response")" rm -f "$create_response" [[ -n "$user_id" ]] || die "注册成功但未解析到 userId" log "注册成功,userId=${user_id} generatedAccount=${generated_account},开始绑定指定账号 ${ACCOUNT}" fi ensure_account_password "$user_id" "$(generate_password_hash)" clear_login_cache "$user_id" local verify_response verify_response="$(mktemp)" if try_login "$verify_response"; then log "验证登录成功。userId=$(extract_user_id "$verify_response") account=$(extract_account "$verify_response") password=${PASSWORD}" rm -f "$verify_response" return fi die "账号已写入,但登录验证失败: $(print_api_error "$verify_response")" } main "$@"