更新.gitignore文件,移除不必要的soul-api目录,确保版本控制的清晰性与一致性。

This commit is contained in:
Alex-larget
2026-03-06 17:52:52 +08:00
parent 2af49611e9
commit 9aaffd8024
117 changed files with 13609 additions and 1 deletions

View File

@@ -0,0 +1,18 @@
-- 后台管理员用户表(与 soul-api 管理端鉴权配合)
CREATE TABLE IF NOT EXISTS admin_users (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(64) NOT NULL,
password_hash VARCHAR(128) NOT NULL,
role VARCHAR(32) NOT NULL DEFAULT 'admin',
name VARCHAR(64) DEFAULT '',
status VARCHAR(16) NOT NULL DEFAULT 'active',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at DATETIME DEFAULT NULL,
UNIQUE KEY uk_admin_users_username (username),
KEY idx_admin_users_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 初始超级管理员需通过 soul-api 启动时从 ADMIN_USERNAME/ADMIN_PASSWORD 自动迁移
-- 或执行以下命令生成 bcrypt 哈希后插入(示例密码 admin123
-- 在 Go 中: hash, _ := bcrypt.GenerateFromPassword([]byte("admin123"), bcrypt.DefaultCost)

View File

@@ -0,0 +1,23 @@
-- 作者详情独立表,每个字段单独列,便于编辑与保存
-- 执行mysql -u user -p db < soul-api/scripts/add-author-config-table.sql
CREATE TABLE IF NOT EXISTS author_config (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(80) NOT NULL DEFAULT '卡若',
avatar VARCHAR(4) NOT NULL DEFAULT 'K',
avatar_img VARCHAR(500) NOT NULL DEFAULT '',
title VARCHAR(200) NOT NULL DEFAULT '',
bio TEXT,
stats TEXT COMMENT 'JSON: [{"label":"","value":""}]',
highlights TEXT COMMENT 'JSON: ["a","b"]',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入默认一行(仅当表为空时)
INSERT INTO author_config (name, avatar, avatar_img, title, bio, stats, highlights)
SELECT '卡若', 'K', '', 'Soul派对房主理人 · 私域运营专家',
'每天早上6点到9点在Soul派对房分享真实的创业故事。专注私域运营与项目变现用云阿米巴模式帮助创业者构建可持续的商业体系。',
'[{"label":"商业案例","value":"62"},{"label":"连续直播","value":"365天"},{"label":"派对分享","value":"1000+"}]',
'["5年私域运营经验","帮助100+品牌从0到1增长","连续创业者,擅长商业模式设计"]'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM author_config LIMIT 1);

View File

@@ -0,0 +1,9 @@
-- ============================================================
-- 章节归属字段 - chapters 表(普通版/增值版)
-- 用途:添加文章时区分该章节属于普通版还是增值版
-- 执行mysql -u user -p database < soul-api/scripts/add-chapter-edition-fields.sql
-- ============================================================
-- 新增字段(若列已存在会报错,可忽略)
ALTER TABLE chapters ADD COLUMN edition_standard TINYINT(1) NULL DEFAULT 1 COMMENT '是否属于普通版1=是';
ALTER TABLE chapters ADD COLUMN edition_premium TINYINT(1) NULL DEFAULT 0 COMMENT '是否属于增值版1=是';

View File

@@ -0,0 +1,8 @@
-- ============================================================
-- stitch_soul P0chapters 表新增 is_new 字段
-- 用途:目录/首页「最新新增」标识,管理端可勾选
-- 执行前请先备份数据库!
-- ============================================================
-- 新增 is_new 字段(若列已存在会报 Duplicate column name可忽略
ALTER TABLE chapters ADD COLUMN is_new TINYINT(1) NULL DEFAULT 0 COMMENT '是否标记为最新新增';

View File

@@ -0,0 +1,40 @@
-- stitch_soul 导师与预约表
-- 执行mysql -u user -p db < soul-api/scripts/add-mentors.sql
CREATE TABLE IF NOT EXISTS `mentors` (
`id` int NOT NULL AUTO_INCREMENT,
`avatar` varchar(500) DEFAULT '',
`name` varchar(80) NOT NULL,
`intro` varchar(500) DEFAULT '',
`tags` varchar(500) DEFAULT '',
`price_single` decimal(10,2) DEFAULT NULL,
`price_half_year` decimal(10,2) DEFAULT NULL,
`price_year` decimal(10,2) DEFAULT NULL,
`quote` varchar(500) DEFAULT '',
`why_find` text,
`offering` text,
`judgment_style` varchar(500) DEFAULT '',
`sort` int DEFAULT 0,
`enabled` tinyint(1) DEFAULT 1,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_enabled_sort` (`enabled`,`sort`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `mentor_consultations` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`mentor_id` int NOT NULL,
`consultation_type` varchar(20) NOT NULL,
`amount` decimal(10,2) NOT NULL,
`status` varchar(20) DEFAULT 'created',
`order_id` int DEFAULT NULL,
`scheduled_at` datetime DEFAULT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_mentor_id` (`mentor_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,5 @@
-- 订单表新增退款原因列
-- 执行: mysql -u user -p db < soul-api/scripts/add-order-refund-reason.sql
-- 若列已存在(如由 GORM AutoMigrate 创建)会报错,可忽略
ALTER TABLE orders ADD COLUMN refund_reason VARCHAR(500) DEFAULT NULL COMMENT '退款原因' AFTER referrer_id;

View File

@@ -0,0 +1,15 @@
-- orders 表索引优化:提升 /api/orders 列表、营收统计查询性能
-- 执行mysql -u user -p database < soul-api/scripts/add-orders-indexes.sql
-- 幂等:索引已存在时跳过,可重复执行
-- 1. idx_status_created 复合索引
SELECT COUNT(*) INTO @cnt FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'orders' AND index_name = 'idx_status_created';
SET @sql = IF(@cnt = 0, 'ALTER TABLE orders ADD INDEX idx_status_created (status, created_at)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 2. idx_user_id 索引
SELECT COUNT(*) INTO @cnt FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'orders' AND index_name = 'idx_user_id';
SET @sql = IF(@cnt = 0, 'ALTER TABLE orders ADD INDEX idx_user_id (user_id)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

View File

@@ -0,0 +1,5 @@
-- 为 chapters 表添加 sort_order 列(支持拖拽排序)
-- 执行cd 项目根目录 && node .cursor/scripts/db-exec/run.js -f soul-api/scripts/add-sort-order-to-chapters.sql
-- 若 sort_order 已存在会报错,可忽略
ALTER TABLE chapters ADD COLUMN sort_order INT DEFAULT 0;

View File

@@ -0,0 +1,15 @@
-- P3 资料扩展users 表新增个人资料字段stitch_soul
-- 用于 comprehensive_profile_editor、enhanced_professional_profile
ALTER TABLE users ADD COLUMN mbti VARCHAR(16) NULL COMMENT 'MBTI类型' AFTER wechat_id;
ALTER TABLE users ADD COLUMN region VARCHAR(100) NULL COMMENT '地区' AFTER mbti;
ALTER TABLE users ADD COLUMN industry VARCHAR(100) NULL COMMENT '行业' AFTER region;
ALTER TABLE users ADD COLUMN position VARCHAR(100) NULL COMMENT '职位' AFTER industry;
ALTER TABLE users ADD COLUMN business_scale VARCHAR(100) NULL COMMENT '业务体量' AFTER position;
ALTER TABLE users ADD COLUMN skills VARCHAR(500) NULL COMMENT '我擅长' AFTER business_scale;
ALTER TABLE users ADD COLUMN story_best_month TEXT NULL COMMENT '最赚钱的一个月' AFTER skills;
ALTER TABLE users ADD COLUMN story_achievement TEXT NULL COMMENT '最有成就感的事' AFTER story_best_month;
ALTER TABLE users ADD COLUMN story_turning TEXT NULL COMMENT '人生转折点' AFTER story_achievement;
ALTER TABLE users ADD COLUMN help_offer VARCHAR(500) NULL COMMENT '我能帮助大家什么' AFTER story_turning;
ALTER TABLE users ADD COLUMN help_need VARCHAR(500) NULL COMMENT '我需要什么帮助' AFTER help_offer;
ALTER TABLE users ADD COLUMN project_intro TEXT NULL COMMENT '项目介绍' AFTER help_need;

View File

@@ -0,0 +1,13 @@
-- 新增 users.vip_activated_at成为 VIP 时间,用于排序(后付款/后设置在前)
-- 执行mysql -u user -p database < add-vip-activated-at.sql
-- 若列已存在会报错,可忽略
ALTER TABLE users ADD COLUMN vip_activated_at DATETIME NULL COMMENT '成为VIP时间付款=pay_time手动=now排序用';
-- 可选:为已有 VIP 用户回填 vip_activated_at取该用户最近一次 vip 订单的 pay_time
-- UPDATE users u
-- SET u.vip_activated_at = (
-- SELECT MAX(o.pay_time) FROM orders o
-- WHERE o.user_id = u.id AND o.product_type = 'vip' AND o.status = 'paid'
-- )
-- WHERE u.is_vip = 1 AND u.vip_activated_at IS NULL;

View File

@@ -0,0 +1,20 @@
-- ============================================================
-- 会员资料字段 - users 表
-- 用途VIP 页保存资料、创业老板排行展示(与用户信息 phone/wechat_id 分离)
-- 执行前请先备份数据库!
-- ============================================================
-- 1. 检查:查看 users 表是否已有这些列(可选执行)
-- SHOW COLUMNS FROM users LIKE 'vip_name';
-- SHOW COLUMNS FROM users LIKE 'vip_avatar';
-- SHOW COLUMNS FROM users LIKE 'vip_project';
-- SHOW COLUMNS FROM users LIKE 'vip_contact';
-- SHOW COLUMNS FROM users LIKE 'vip_bio';
-- 2. 新增会员资料字段(若列已存在会报 Duplicate column name可忽略该条
-- --------------------------------------------------------
ALTER TABLE users ADD COLUMN vip_name VARCHAR(100) NULL COMMENT '会员姓名(创业老板排行)';
ALTER TABLE users ADD COLUMN vip_avatar VARCHAR(500) NULL COMMENT '会员头像';
ALTER TABLE users ADD COLUMN vip_project VARCHAR(200) NULL COMMENT '项目名称';
ALTER TABLE users ADD COLUMN vip_contact VARCHAR(100) NULL COMMENT '会员联系方式(展示用,与 phone/wechat_id 分离)';
ALTER TABLE users ADD COLUMN vip_bio TEXT NULL COMMENT '一句话简介';

View File

@@ -0,0 +1,25 @@
-- 1. 新建 vip_roles 表
CREATE TABLE IF NOT EXISTS vip_roles (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE COMMENT '角色名称',
sort INT DEFAULT 0 COMMENT '下拉展示顺序,越小越前',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) COMMENT '超级个体固定角色';
-- 2. 插入默认角色UNIQUE name 防重复)
INSERT IGNORE INTO vip_roles (name, sort) VALUES
('创始人', 1),
('投资人', 2),
('产品经理', 3),
('技术负责人', 4),
('运营总监', 5),
('销售总监', 6),
('市场总监', 7),
('合伙人', 8),
('顾问', 9),
('品牌主理人', 10);
-- 3. users 表新增 vip_sort、vip_role
ALTER TABLE users ADD COLUMN vip_sort INT NULL COMMENT '手动排序,越小越前';
ALTER TABLE users ADD COLUMN vip_role VARCHAR(50) NULL COMMENT '角色:从 vip_roles 选或手动填写';

View File

@@ -0,0 +1,83 @@
-- ============================================================
-- VIP 订单修复脚本
-- 场景:甲方开发的 VIP 支付可能未正确设置 product_type
-- 会员价1980 元
-- 执行前请先备份数据库!
-- ============================================================
-- 1. 诊断:查看当前疑似 VIP 订单的状态(执行后人工确认)
-- --------------------------------------------------------
SELECT id, order_sn, user_id, product_type, amount, status, pay_time, created_at
FROM orders
WHERE amount = 1980
AND (status = 'paid' OR status = 'completed')
ORDER BY pay_time DESC;
-- 2. 统计:有多少条需要修复
-- --------------------------------------------------------
SELECT COUNT(*) AS need_fix_count
FROM orders
WHERE amount = 1980
AND (status = 'paid' OR status = 'completed')
AND (product_type IS NULL OR product_type = '' OR product_type NOT IN ('vip', 'fullbook'));
-- 3. 修复:将 1980 元已支付订单的 product_type 设为 'vip'
-- --------------------------------------------------------
-- 条件:金额=1980 且 已支付 且 product_type 不是 vip/fullbook
UPDATE orders
SET product_type = 'vip'
WHERE amount = 1980
AND (status = 'paid' OR status = 'completed')
AND (product_type IS NULL OR product_type = '' OR product_type NOT IN ('vip', 'fullbook'));
-- 4. 兼容大小写:若 product_type 为 'VIP'、'Vip' 等,统一为小写
-- --------------------------------------------------------
UPDATE orders
SET product_type = 'vip'
WHERE amount = 1980
AND (status = 'paid' OR status = 'completed')
AND LOWER(TRIM(product_type)) = 'vip'
AND product_type != 'vip';
-- 5. 验证:修复后应无遗漏
-- --------------------------------------------------------
SELECT id, order_sn, user_id, product_type, amount, status
FROM orders
WHERE amount = 1980
AND (status = 'paid' OR status = 'completed')
AND product_type NOT IN ('vip', 'fullbook');
-- 期望结果0 行
-- ============================================================
-- 可选:若线上 next-project 用 users 表存 is_vip需确保字段存在
-- 执行前请确认 users 表是否已有这些列!
-- ============================================================
-- 6. 检查 users 表是否有 VIP 相关列MySQL
-- SHOW COLUMNS FROM users LIKE 'is_vip';
-- SHOW COLUMNS FROM users LIKE 'vip_expire_date';
-- 7. 若 users 表无 VIP 列,可执行以下 ALTER按需取消注释
-- --------------------------------------------------------
-- ALTER TABLE users ADD COLUMN is_vip TINYINT(1) DEFAULT 0;
-- ALTER TABLE users ADD COLUMN vip_expire_date DATETIME NULL;
-- ALTER TABLE users ADD COLUMN vip_name VARCHAR(100) NULL;
-- ALTER TABLE users ADD COLUMN vip_avatar VARCHAR(500) NULL;
-- ALTER TABLE users ADD COLUMN vip_project VARCHAR(200) NULL;
-- ALTER TABLE users ADD COLUMN vip_contact VARCHAR(100) NULL;
-- ALTER TABLE users ADD COLUMN vip_bio TEXT NULL;
-- 8. 从 orders 同步到 users仅当用 users 表存 VIP 时)
-- 将 1980 元已支付订单对应的用户标记为 VIP过期日 = pay_time + 365 天
-- --------------------------------------------------------
-- UPDATE users u
-- INNER JOIN (
-- SELECT user_id, MAX(pay_time) AS last_pay
-- FROM orders
-- WHERE amount = 1980
-- AND (status = 'paid' OR status = 'completed')
-- AND product_type IN ('vip', 'fullbook')
-- GROUP BY user_id
-- ) o ON u.id = o.user_id
-- SET u.is_vip = 1,
-- u.vip_expire_date = DATE_ADD(o.last_pay, INTERVAL 365 DAY);

View File

@@ -0,0 +1,12 @@
#!/bin/bash
# 订单对账防漏单 - 宝塔定时任务用
# 建议每 10 分钟执行一次
URL="${SYNC_ORDERS_URL:-https://soul.quwanzhi.com/api/cron/sync-orders}"
curl -s -X GET "$URL" \
-H "User-Agent: Baota-Cron/1.0" \
--connect-timeout 10 \
--max-time 30
echo ""

View File

@@ -0,0 +1,61 @@
# stitch_soul P0 接口验证脚本
# 用法:先启动 soul-api然后执行 .\scripts\test-p0-endpoints.ps1
# 可指定 baseUrl$env:API_BASE = "http://localhost:8080"; .\scripts\test-p0-endpoints.ps1
$base = if ($env:API_BASE) { $env:API_BASE } else { "http://localhost:8080" }
Write-Host "=== stitch_soul P0 接口测试 ===" -ForegroundColor Cyan
Write-Host "Base: $base`n" -ForegroundColor Gray
$passed = 0
$failed = 0
function Test-Endpoint {
param($name, $path)
try {
$r = Invoke-RestMethod -Uri "$base$path" -Method GET -TimeoutSec 5
if ($r.success -eq $true) {
Write-Host "[PASS] $name" -ForegroundColor Green
$script:passed++
return $r
} else {
Write-Host "[FAIL] $name - success != true" -ForegroundColor Red
$script:failed++
}
} catch {
Write-Host "[FAIL] $name - $($_.Exception.Message)" -ForegroundColor Red
$script:failed++
}
return $null
}
# 1. book/all-chapters含 isNew
$r1 = Test-Endpoint "GET /api/miniprogram/book/all-chapters" "/api/miniprogram/book/all-chapters"
if ($r1 -and $r1.data) {
$first = $r1.data[0]
if ($null -ne $first.PSObject.Properties['isNew']) {
Write-Host " -> isNew 字段存在" -ForegroundColor Green
} else {
Write-Host " -> 警告: isNew 字段可能缺失" -ForegroundColor Yellow
}
}
# 2. book/recommended精选推荐前3章+tag
$r2 = Test-Endpoint "GET /api/miniprogram/book/recommended" "/api/miniprogram/book/recommended"
if ($r2 -and $r2.data) {
Write-Host " -> 返回 $($r2.data.Count)tag: $($r2.data[0].tag)" -ForegroundColor Gray
}
# 3. book/latest-chapters最新更新
$r3 = Test-Endpoint "GET /api/miniprogram/book/latest-chapters" "/api/miniprogram/book/latest-chapters"
if ($r3 -and $r3.data) {
Write-Host " -> 返回 $($r3.data.Count)" -ForegroundColor Gray
}
# 4. book/hot热门章节
$r4 = Test-Endpoint "GET /api/miniprogram/book/hot" "/api/miniprogram/book/hot"
if ($r4 -and $r4.data) {
Write-Host " -> 返回 $($r4.data.Count)" -ForegroundColor Gray
}
Write-Host "`n=== 结果: $passed 通过, $failed 失败 ===" -ForegroundColor $(if ($failed -eq 0) { "Green" } else { "Yellow" })

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
模拟微信「商家转账到零钱」结果通知回调,请求本地/远程回调接口,
用于验证1接口是否可达 2wechat_callback_logs 表是否会写入一条记录。
说明:未使用真实签名与加密,服务端会验签失败并返回 500
但仍会写入 wechat_callback_logs 一条 handler_result=fail 的记录。
运行前请确保 soul-api 已启动;运行后请查表 wechat_callback_logs 是否有新行。
"""
import json
import ssl
import sys
from datetime import datetime
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
# 默认请求地址(可改环境或命令行)
DEFAULT_URL = "http://localhost:8080/api/payment/wechat/transfer/notify"
def main():
args = [a for a in sys.argv[1:] if a and not a.startswith("-")]
insecure = "--insecure" in sys.argv or "-k" in sys.argv
url = args[0] if args else DEFAULT_URL
if insecure and url.startswith("https://"):
print("已启用 --insecure跳过 SSL 证书校验(仅用于本地/测试)")
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
else:
ctx = None
# 模拟微信回调的请求体结构(真实场景中 resource.ciphertext 为 AEAD_AES_256_GCM 加密,这里用占位)
body = {
"id": "test-notify-id-" + datetime.now().strftime("%Y%m%d%H%M%S"),
"create_time": datetime.now().strftime("%Y-%m-%dT%H:%M:%S+08:00"),
"resource_type": "encrypt-resource",
"event_type": "MCHTRANSFER.BILL.FINISHED",
"summary": "模拟转账结果通知",
"resource": {
"original_type": "mch_payment",
"algorithm": "AEAD_AES_256_GCM",
"ciphertext": "fake-base64-ciphertext-for-test",
"nonce": "fake-nonce",
"associated_data": "mch_payment",
},
}
body_bytes = json.dumps(body, ensure_ascii=False).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Wechatpay-Timestamp": str(int(datetime.now().timestamp())),
"Wechatpay-Nonce": "test-nonce-" + datetime.now().strftime("%H%M%S"),
"Wechatpay-Signature": "fake-signature-for-test",
"Wechatpay-Serial": "fake-serial-for-test",
}
req = Request(url, data=body_bytes, headers=headers, method="POST")
print(f"POST {url}")
print(f"Body (摘要): event_type={body['event_type']}, resource_type={body['resource_type']}")
print("-" * 50)
try:
with urlopen(req, timeout=10, context=ctx) as resp:
print(f"HTTP 状态: {resp.status}")
raw = resp.read().decode("utf-8", errors="replace")
try:
parsed = json.loads(raw)
print("响应 JSON:", json.dumps(parsed, ensure_ascii=False, indent=2))
except Exception:
print("响应 body:", raw[:500])
except HTTPError as e:
print(f"HTTP 状态: {e.code}")
raw = e.read().decode("utf-8", errors="replace")
try:
parsed = json.loads(raw)
print("响应 JSON:", json.dumps(parsed, ensure_ascii=False, indent=2))
except Exception:
print("响应 body:", raw[:500])
except URLError as e:
print(f"请求失败: {e.reason}")
sys.exit(1)
print("-" * 50)
print("请检查数据库表 wechat_callback_logs 是否有新记录(本次为模拟请求,预期会有一条 handler_result=fail 的记录)。")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
请求提现测试接口:固定用户提现 1 元(默认),无需 admin_session。
用法:
python test_withdraw.py
python test_withdraw.py https://soul.quwanzhi.com
python test_withdraw.py http://localhost:8080 2
"""
import json
import sys
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
from urllib.parse import urlencode
DEFAULT_BASE = "http://localhost:8080"
DEFAULT_USER_ID = "ogpTW5fmXRGNpoUbXB3UEqnVe5Tg"
DEFAULT_AMOUNT = "1"
def main():
base = DEFAULT_BASE
amount = DEFAULT_AMOUNT
args = [a for a in sys.argv[1:] if a]
if args:
if args[0].startswith("http://") or args[0].startswith("https://"):
base = args[0].rstrip("/")
args = args[1:]
if args:
amount = args[0]
path = "/api/withdraw-test"
if not base.endswith(path):
base = base.rstrip("/") + path
url = f"{base}?{urlencode({'userId': DEFAULT_USER_ID, 'amount': amount})}"
req = Request(url, method="GET")
req.add_header("Accept", "application/json")
print(f"GET {url}")
print("-" * 50)
try:
with urlopen(req, timeout=15) as resp:
raw = resp.read().decode("utf-8", errors="replace")
try:
print(json.dumps(json.loads(raw), ensure_ascii=False, indent=2))
except Exception:
print(raw)
except HTTPError as e:
raw = e.read().decode("utf-8", errors="replace")
try:
print(json.dumps(json.loads(raw), ensure_ascii=False, indent=2))
except Exception:
print(raw)
print(f"HTTP {e.code}", file=sys.stderr)
except URLError as e:
print(f"请求失败: {e.reason}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()