feat: 小程序超级个体/个人资料/CKB获客;VIP列表展示过滤;管理端与API联调
- 超级个体:去掉首位特例;列表仅展示有头像且非微信默认昵称(vip.go) - 个人资料:居中头像、低调联系方式、点头像优先走存客宝 lead(ckbLeadToken) - 阅读页分享朋友圈复制与 toast 去重 - soul-api: miniprogram users 带 ckbLeadToken;其它 handler 与路由调整 - 脚本:content_upload、miniprogram 上传辅助等 Made-with: Cursor
This commit is contained in:
@@ -25,6 +25,8 @@ import subprocess
|
||||
import shutil
|
||||
import tarfile
|
||||
import time
|
||||
import threading
|
||||
import shlex
|
||||
|
||||
try:
|
||||
import paramiko
|
||||
@@ -311,8 +313,20 @@ def upload_and_extract(cfg, tarball_path, no_restart=False, restart_method="auto
|
||||
"mkdir -p %s && cd %s && tar -xzf %s && "
|
||||
"chmod +x soul-api && rm -f %s && echo OK"
|
||||
) % (project_path, project_path, remote_tar, remote_tar)
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=60)
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=120)
|
||||
ex_err = []
|
||||
|
||||
def _drain_tar_stderr():
|
||||
try:
|
||||
ex_err.append(stderr.read().decode("utf-8", errors="replace"))
|
||||
except Exception:
|
||||
ex_err.append("")
|
||||
|
||||
t_tar = threading.Thread(target=_drain_tar_stderr)
|
||||
t_tar.daemon = True
|
||||
t_tar.start()
|
||||
out = stdout.read().decode("utf-8", errors="replace").strip()
|
||||
t_tar.join(timeout=10)
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
if exit_status != 0 or "OK" not in out:
|
||||
print(" [失败] 解压失败,退出码:", exit_status)
|
||||
@@ -325,17 +339,32 @@ def upload_and_extract(cfg, tarball_path, no_restart=False, restart_method="auto
|
||||
if restart_method in ("auto", "btapi") and (cfg.get("bt_panel_url") and cfg.get("bt_api_key")):
|
||||
ok = restart_via_bt_api(cfg)
|
||||
if not ok and restart_method in ("auto", "ssh"):
|
||||
# SSH:只杀「工作目录为本项目」的 soul-api,避免误杀其他 Go 项目
|
||||
restart_cmd = (
|
||||
"cd %s && T=$(readlink -f .) && for p in $(pgrep -f soul-api 2>/dev/null); do "
|
||||
"[ \"$(readlink -f /proc/$p/cwd 2>/dev/null)\" = \"$T\" ] && kill $p 2>/dev/null; done; "
|
||||
"sleep 2; setsid nohup ./soul-api >> soul-api.log 2>&1 </dev/null & "
|
||||
"sleep 3; T=$(readlink -f .) && for p in $(pgrep -f soul-api 2>/dev/null); do "
|
||||
"[ \"$(readlink -f /proc/$p/cwd 2>/dev/null)\" = \"$T\" ] && echo RESTART_OK && exit 0; done; echo RESTART_FAIL"
|
||||
) % project_path
|
||||
stdin, stdout, stderr = client.exec_command(restart_cmd, timeout=20)
|
||||
# SSH:正式环境固定监听 DEPLOY_PORT(默认 8080)。用 fuser 释放端口,避免宝塔守护
|
||||
# 启动的进程 cwd 与项目目录不一致导致 pgrep+cwd 校验永远失败。
|
||||
# 用 timeout + bash -c 单引号包裹,避免远端偶发挂死导致 Paramiko stdout.read 永久阻塞。
|
||||
restart_inner = (
|
||||
"cd %s && (fuser -k %d/tcp 2>/dev/null || true) && sleep 2 && "
|
||||
"setsid nohup ./soul-api >> soul-api.log 2>&1 </dev/null & "
|
||||
"sleep 12 && curl -sf --connect-timeout 5 --max-time 15 "
|
||||
"http://127.0.0.1:%d/health 2>/dev/null | grep -q '\"status\"' "
|
||||
"&& echo RESTART_OK || echo RESTART_FAIL"
|
||||
) % (project_path, DEPLOY_PORT, DEPLOY_PORT)
|
||||
restart_cmd = "timeout 95 bash -c " + shlex.quote(restart_inner)
|
||||
stdin, stdout, stderr = client.exec_command(restart_cmd, timeout=110)
|
||||
err_holder = []
|
||||
|
||||
def _drain_stderr():
|
||||
try:
|
||||
err_holder.append(stderr.read().decode("utf-8", errors="replace"))
|
||||
except Exception:
|
||||
err_holder.append("")
|
||||
|
||||
t = threading.Thread(target=_drain_stderr)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
out = stdout.read().decode("utf-8", errors="replace").strip()
|
||||
err = (stderr.read().decode("utf-8", errors="replace") or "").strip()
|
||||
t.join(timeout=5)
|
||||
err = (err_holder[0] if err_holder else "").strip()
|
||||
if err:
|
||||
print(" [stderr] %s" % err[:200])
|
||||
ok = "RESTART_OK" in out
|
||||
|
||||
Reference in New Issue
Block a user