327 lines
9.4 KiB
Bash
Executable File
327 lines
9.4 KiB
Bash
Executable File
#!/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 "$@"
|