hy-app-monitor/localbox/create_local_app_user.sh
2026-05-09 10:40:17 +08:00

327 lines
9.4 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 <<SQL
START TRANSACTION;
SET @user_id := ${user_id};
SET @account := '${account_q}';
SET @origin := '${origin_q}';
SET @origin_child := '${origin_child_q}';
SET @hash := '${hash_q}';
SET @id_seed := CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(6)) * 1000000 AS UNSIGNED);
UPDATE user_base_info
SET account = @account, origin_sys = @origin, sys_origin_child = @origin_child, update_time = NOW()
WHERE id = @user_id
LIMIT 1;
DELETE FROM user_account_auth WHERE user_id = @user_id;
INSERT INTO user_account_auth (id, user_id, pwd, is_del, create_time, update_time)
VALUES (@id_seed + 1, @user_id, @hash, 0, NOW(), NOW());
DELETE FROM user_auth_type WHERE user_id = @user_id AND type = 'ACCOUNT';
INSERT INTO user_auth_type (id, user_id, type, open_id, is_del, create_time, update_time)
VALUES (@id_seed + 2, @user_id, 'ACCOUNT', CAST(@user_id AS CHAR), 0, NOW(), NOW());
COMMIT;
SQL
}
clear_login_cache() {
local user_id="$1"
if docker ps --format '{{.Names}}' | grep -qx "$REDIS_CONTAINER"; then
docker exec "$REDIS_CONTAINER" sh -lc "
for key in \$(redis-cli --scan --pattern 'ACCOUNT_LOGIN_TIME*_${user_id}'); do redis-cli DEL \"\$key\" >/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 "$@"