diff --git a/miniprogram/app.js b/miniprogram/app.js index c3103395..83d2ad6a 100644 --- a/miniprogram/app.js +++ b/miniprogram/app.js @@ -6,7 +6,7 @@ App({ globalData: { // API基础地址 - 连接真实后端 - baseUrl: 'https://soulApi.quwanzhi.com', + baseUrl: 'https://soulapi.quwanzhi.com', // baseUrl: 'http://localhost:3006', // baseUrl: 'http://localhost:8080', diff --git a/miniprogram/project.private.config.json b/miniprogram/project.private.config.json index 7ffda1aa..3c780dcf 100644 --- a/miniprogram/project.private.config.json +++ b/miniprogram/project.private.config.json @@ -3,7 +3,7 @@ "projectname": "miniprogram", "setting": { "compileHotReLoad": true, - "urlCheck": false, + "urlCheck": true, "coverView": true, "lazyloadPlaceholderEnable": false, "skylineRenderEnable": false, diff --git a/soul-api/.env b/soul-api/.env index 230286a5..1e98a20b 100644 --- a/soul-api/.env +++ b/soul-api/.env @@ -1,4 +1,4 @@ -# 服务 +# 服务(监听端口,修改后重启 soul-api 生效) PORT=8080 GIN_MODE=debug # 版本号(打包 zip 前改这里,上传后访问 /health 可看到) diff --git a/soul-api/.env.example b/soul-api/.env.example index cff1f158..7871613a 100644 --- a/soul-api/.env.example +++ b/soul-api/.env.example @@ -1,4 +1,4 @@ -# 服务 +# 服务(启动端口在 .env 中配置,修改 PORT 后重启生效) PORT=8080 GIN_MODE=debug diff --git a/soul-api/dev_dev.py b/soul-api/dev_dev.py new file mode 100644 index 00000000..de91ea69 --- /dev/null +++ b/soul-api/dev_dev.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +soul-api Go 项目一键部署到宝塔,重启的是宝塔里的 soulDev 项目 +- 本地交叉编译 Linux 二进制 +- 上传到 /www/wwwroot/自营/soul-dev +- 重启 soulDev:优先宝塔 API(需配置),否则 SSH 下 setsid nohup 启动 + +宝塔 API 重启(可选):在环境变量或 .env 中设置 + BT_PANEL_URL = https://你的面板地址:9988 + BT_API_KEY = 面板 设置 -> API 接口 中的密钥 + BT_GO_PROJECT_NAME = soulDev (与宝塔 Go 项目列表里名称一致) +并安装 requests: pip install requests +""" + +from __future__ import print_function + +import hashlib +import os +import sys +import tempfile +import argparse +import subprocess +import shutil +import tarfile +import time + +try: + import paramiko +except ImportError: + print("错误: 请先安装 paramiko") + print(" pip install paramiko") + sys.exit(1) + +try: + import requests + try: + import urllib3 + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + except Exception: + pass +except ImportError: + requests = None + +# ==================== 配置 ==================== + +DEPLOY_PROJECT_PATH = "/www/wwwroot/自营/soul-dev" +DEFAULT_SSH_PORT = int(os.environ.get("DEPLOY_SSH_PORT", "22022")) + + +# 宝塔 API 密钥(写死,用于部署后重启 Go 项目) +BT_API_KEY_DEFAULT = "qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT" + + +def get_cfg(): + host = os.environ.get("DEPLOY_HOST", "43.139.27.93") + bt_url = (os.environ.get("BT_PANEL_URL") or "").strip().rstrip("/") + if not bt_url: + bt_url = "https://%s:9988" % host + return { + "host": host, + "user": os.environ.get("DEPLOY_USER", "root"), + "password": os.environ.get("DEPLOY_PASSWORD", "Zhiqun1984"), + "ssh_key": os.environ.get("DEPLOY_SSH_KEY", ""), + "project_path": os.environ.get("DEPLOY_PROJECT_PATH", DEPLOY_PROJECT_PATH), + "bt_panel_url": bt_url, + "bt_api_key": os.environ.get("BT_API_KEY", BT_API_KEY_DEFAULT), + "bt_go_project_name": os.environ.get("BT_GO_PROJECT_NAME", "soulDev"), + } + + +# ==================== 本地构建 ==================== + + +def run_build(root): + """交叉编译 Go 二进制(Linux amd64)""" + print("[1/4] 本地交叉编译 Go 二进制 ...") + env = os.environ.copy() + env["GOOS"] = "linux" + env["GOARCH"] = "amd64" + env["CGO_ENABLED"] = "0" + # 必须 shell=False,否则 Windows 下 -ldflags 等参数会被当成包路径导致 "malformed import path" + cmd = ["go", "build", "-o", "soul-api", "./cmd/server"] + try: + r = subprocess.run( + cmd, + cwd=root, + env=env, + shell=False, + timeout=120, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + ) + if r.returncode != 0: + print(" [失败] go build 失败,退出码:", r.returncode) + if r.stderr: + for line in (r.stderr or "").strip().split("\n")[-10:]: + print(" " + line) + return None + out_path = os.path.join(root, "soul-api") + if not os.path.isfile(out_path): + print(" [失败] 未找到编译产物 soul-api") + return None + print(" [成功] 编译完成: %s (%.2f MB)" % (out_path, os.path.getsize(out_path) / 1024 / 1024)) + return out_path + except subprocess.TimeoutExpired: + print(" [失败] 编译超时") + return None + except FileNotFoundError: + print(" [失败] 未找到 go 命令,请安装 Go") + return None + except Exception as e: + print(" [失败] 编译异常:", str(e)) + return None + + +# ==================== 打包 ==================== + + +def pack_deploy(root, binary_path, include_env=True): + """打包二进制和 .env 为 tar.gz""" + print("[2/4] 打包部署文件 ...") + staging = tempfile.mkdtemp(prefix="soul_api_deploy_") + try: + shutil.copy2(binary_path, os.path.join(staging, "soul-api")) + env_src = os.path.join(root, ".env") + if include_env and os.path.isfile(env_src): + shutil.copy2(env_src, os.path.join(staging, ".env")) + print(" [已包含] .env") + else: + env_example = os.path.join(root, ".env.example") + if os.path.isfile(env_example): + shutil.copy2(env_example, os.path.join(staging, ".env")) + print(" [已包含] .env.example -> .env (请服务器上检查配置)") + tarball = os.path.join(tempfile.gettempdir(), "soul_api_deploy.tar.gz") + with tarfile.open(tarball, "w:gz") as tf: + for name in os.listdir(staging): + tf.add(os.path.join(staging, name), arcname=name) + print(" [成功] 打包完成: %s (%.2f MB)" % (tarball, os.path.getsize(tarball) / 1024 / 1024)) + return tarball + except Exception as e: + print(" [失败] 打包异常:", str(e)) + return None + finally: + shutil.rmtree(staging, ignore_errors=True) + + +# ==================== 宝塔 API 重启 ==================== + + +def restart_via_bt_api(cfg): + """通过宝塔 API 重启 Go 项目(需配置 BT_PANEL_URL、BT_API_KEY、BT_GO_PROJECT_NAME)""" + url = cfg.get("bt_panel_url") or "" + key = cfg.get("bt_api_key") or "" + name = cfg.get("bt_go_project_name", "soulDev") + if not url or not key: + return False + if not requests: + print(" [提示] 未安装 requests,无法使用宝塔 API,将用 SSH 重启。pip install requests") + return False + try: + req_time = int(time.time()) + sk_md5 = hashlib.md5(key.encode()).hexdigest() + req_token = hashlib.md5(("%s%s" % (req_time, sk_md5)).encode()).hexdigest() + # 宝塔 Go 项目插件:先停止再启动,接口以实际面板版本为准 + base = url.rstrip("/") + params = {"request_time": req_time, "request_token": req_token} + # 常见形式:/plugin?name=go_project,POST 带 action、project_name + for action in ("stop_go_project", "start_go_project"): + data = dict(params) + data["action"] = action + data["project_name"] = name + r = requests.post( + base + "/plugin?name=go_project", + data=data, + timeout=15, + verify=False, + ) + if r.status_code != 200: + continue + j = r.json() if r.headers.get("content-type", "").startswith("application/json") else {} + if action == "stop_go_project": + time.sleep(2) + if j.get("status") is False and j.get("msg"): + print(" [宝塔API] %s: %s" % (action, j.get("msg", ""))) + # 再调一次 start 确保启动 + data = dict(params) + data["action"] = "start_go_project" + data["project_name"] = name + r = requests.post(base + "/plugin?name=go_project", data=data, timeout=15, verify=False) + if r.status_code == 200: + j = r.json() if r.headers.get("content-type", "").startswith("application/json") else {} + if j.get("status") is True: + print(" [成功] 已通过宝塔 API 重启 Go 项目: %s" % name) + return True + return False + except Exception as e: + print(" [宝塔API 失败] %s" % str(e)) + return False + + +# ==================== SSH 上传 ==================== + + +def upload_and_extract(cfg, tarball_path, no_restart=False, restart_method="auto"): + """上传 tar.gz 到服务器并解压、重启""" + print("[3/4] SSH 上传并解压 ...") + if not cfg.get("password") and not cfg.get("ssh_key"): + print(" [失败] 请设置 DEPLOY_PASSWORD 或 DEPLOY_SSH_KEY") + return False + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + if cfg.get("ssh_key") and os.path.isfile(cfg["ssh_key"]): + client.connect( + cfg["host"], port=DEFAULT_SSH_PORT, + username=cfg["user"], key_filename=cfg["ssh_key"], + timeout=15, + ) + else: + client.connect( + cfg["host"], port=DEFAULT_SSH_PORT, + username=cfg["user"], password=cfg["password"], + timeout=15, + ) + sftp = client.open_sftp() + remote_tar = "/tmp/soul_api_deploy.tar.gz" + project_path = cfg["project_path"] + sftp.put(tarball_path, remote_tar) + sftp.close() + + cmd = ( + "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) + out = stdout.read().decode("utf-8", errors="replace").strip() + exit_status = stdout.channel.recv_exit_status() + if exit_status != 0 or "OK" not in out: + print(" [失败] 解压失败,退出码:", exit_status) + return False + print(" [成功] 已解压到: %s" % project_path) + + if not no_restart: + print("[4/4] 重启 soulDev 服务 ...") + ok = False + 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:用 setsid nohup 避免断开杀进程,多等几秒再检测 + restart_cmd = ( + "cd %s && pkill -f './soul-api' 2>/dev/null; sleep 2; " + "setsid nohup ./soul-api >> soul-api.log 2>&1 /dev/null && echo RESTART_OK || echo RESTART_FAIL" + ) % project_path + stdin, stdout, stderr = client.exec_command(restart_cmd, timeout=20) + out = stdout.read().decode("utf-8", errors="replace").strip() + err = (stderr.read().decode("utf-8", errors="replace") or "").strip() + if err: + print(" [stderr] %s" % err[:200]) + ok = "RESTART_OK" in out + if ok: + print(" [成功] soulDev 已通过 SSH 重启") + else: + print(" [警告] SSH 重启状态未知,请到宝塔 Go 项目里手动点击启动,或执行: cd %s && ./soul-api" % project_path) + else: + print("[4/4] 跳过重启 (--no-restart)") + + return True + except Exception as e: + print(" [失败] SSH 错误:", str(e)) + return False + finally: + client.close() + + +# ==================== 主函数 ==================== + + +def main(): + parser = argparse.ArgumentParser( + description="soul-api 一键部署到宝塔,重启 soulDev 项目", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("--no-build", action="store_true", help="跳过本地编译(使用已有 soul-api 二进制)") + parser.add_argument("--no-env", action="store_true", help="不打包 .env(保留服务器现有 .env)") + parser.add_argument("--no-restart", action="store_true", help="上传后不重启服务") + parser.add_argument( + "--restart-method", + choices=("auto", "btapi", "ssh"), + default="auto", + help="重启方式: auto=先试宝塔API再SSH, btapi=仅宝塔API, ssh=仅SSH (默认 auto)", + ) + args = parser.parse_args() + + script_dir = os.path.dirname(os.path.abspath(__file__)) + root = script_dir + + cfg = get_cfg() + print("=" * 60) + print(" soul-api 部署到宝塔,重启 soulDev") + print("=" * 60) + print(" 服务器: %s@%s:%s" % (cfg["user"], cfg["host"], DEFAULT_SSH_PORT)) + print(" 目标目录: %s" % cfg["project_path"]) + print("=" * 60) + + binary_path = os.path.join(root, "soul-api") + if not args.no_build: + p = run_build(root) + if not p: + return 1 + else: + if not os.path.isfile(binary_path): + print("[错误] 未找到 soul-api 二进制,请先编译或去掉 --no-build") + return 1 + print("[1/4] 跳过编译,使用现有 soul-api") + + tarball = pack_deploy(root, binary_path, include_env=not args.no_env) + if not tarball: + return 1 + + if not upload_and_extract(cfg, tarball, no_restart=args.no_restart, restart_method=args.restart_method): + return 1 + + try: + os.remove(tarball) + except Exception: + pass + + print("") + print(" 部署完成!目录: %s" % cfg["project_path"]) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/soul-api/internal/config/config.go b/soul-api/internal/config/config.go index 1caf5373..5fb7ff6a 100644 --- a/soul-api/internal/config/config.go +++ b/soul-api/internal/config/config.go @@ -2,6 +2,7 @@ package config import ( "os" + "path/filepath" "strings" "github.com/joho/godotenv" @@ -73,9 +74,12 @@ func parseCORSOrigins() []string { return origins } -// Load 加载配置,开发环境可读 .env +// Load 加载配置,端口等从 .env 读取。优先从可执行文件同目录加载 .env,再试当前目录 func Load() (*Config, error) { - _ = godotenv.Load() + if execPath, err := os.Executable(); err == nil { + _ = godotenv.Load(filepath.Join(filepath.Dir(execPath), ".env")) + } + _ = godotenv.Load(".env") port := os.Getenv("PORT") if port == "" { diff --git a/soul-api/internal/handler/withdraw.go b/soul-api/internal/handler/withdraw.go index c0236f6b..939a3a85 100644 --- a/soul-api/internal/handler/withdraw.go +++ b/soul-api/internal/handler/withdraw.go @@ -270,8 +270,8 @@ func WithdrawConfirmInfo(c *gin.Context) { }) } -// WithdrawPendingConfirm GET /api/withdraw/pending-confirm?userId= 待确认/处理中收款列表 -// 返回 pending、processing、pending_confirm 的提现,供小程序展示;并返回 mchId、appId 供确认收款用 +// WithdrawPendingConfirm GET /api/withdraw/pending-confirm?userId= 待确认收款列表(仅审核通过后) +// 只返回 processing、pending_confirm,供「我的」页「待确认收款」展示;pending 为待审核,不在此列表 func WithdrawPendingConfirm(c *gin.Context) { userId := c.Query("userId") if userId == "" { @@ -280,8 +280,8 @@ func WithdrawPendingConfirm(c *gin.Context) { } db := database.DB() var list []model.Withdrawal - // 进行中的提现:待处理、处理中、待确认收款(与 next 的 pending_confirm 兼容) - if err := db.Where("user_id = ? AND status IN ?", userId, []string{"pending", "processing", "pending_confirm"}). + // 仅审核已通过、等待用户确认收款的:processing(微信处理中)、pending_confirm(待用户点确认收款) + if err := db.Where("user_id = ? AND status IN ?", userId, []string{"processing", "pending_confirm"}). Order("created_at DESC"). Find(&list).Error; err != nil { list = nil diff --git a/soul-api/soul-api b/soul-api/soul-api index 5c5936eb..91df9314 100644 Binary files a/soul-api/soul-api and b/soul-api/soul-api differ diff --git a/soul-api/tmp/main.exe b/soul-api/tmp/main.exe deleted file mode 100644 index 4eb67294..00000000 Binary files a/soul-api/tmp/main.exe and /dev/null differ