更新.gitignore文件,移除不必要的soul-api目录,确保版本控制的清晰性与一致性。
This commit is contained in:
Binary file not shown.
18
soul-api/scripts/add-admin-users.sql
Normal file
18
soul-api/scripts/add-admin-users.sql
Normal 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)
|
||||
23
soul-api/scripts/add-author-config-table.sql
Normal file
23
soul-api/scripts/add-author-config-table.sql
Normal 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);
|
||||
9
soul-api/scripts/add-chapter-edition-fields.sql
Normal file
9
soul-api/scripts/add-chapter-edition-fields.sql
Normal 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=是';
|
||||
8
soul-api/scripts/add-chapters-is-new.sql
Normal file
8
soul-api/scripts/add-chapters-is-new.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- ============================================================
|
||||
-- stitch_soul P0:chapters 表新增 is_new 字段
|
||||
-- 用途:目录/首页「最新新增」标识,管理端可勾选
|
||||
-- 执行前请先备份数据库!
|
||||
-- ============================================================
|
||||
|
||||
-- 新增 is_new 字段(若列已存在会报 Duplicate column name,可忽略)
|
||||
ALTER TABLE chapters ADD COLUMN is_new TINYINT(1) NULL DEFAULT 0 COMMENT '是否标记为最新新增';
|
||||
40
soul-api/scripts/add-mentors.sql
Normal file
40
soul-api/scripts/add-mentors.sql
Normal 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;
|
||||
5
soul-api/scripts/add-order-refund-reason.sql
Normal file
5
soul-api/scripts/add-order-refund-reason.sql
Normal 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;
|
||||
15
soul-api/scripts/add-orders-indexes.sql
Normal file
15
soul-api/scripts/add-orders-indexes.sql
Normal 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;
|
||||
5
soul-api/scripts/add-sort-order-to-chapters.sql
Normal file
5
soul-api/scripts/add-sort-order-to-chapters.sql
Normal 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;
|
||||
15
soul-api/scripts/add-user-profile-fields.sql
Normal file
15
soul-api/scripts/add-user-profile-fields.sql
Normal 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;
|
||||
13
soul-api/scripts/add-vip-activated-at.sql
Normal file
13
soul-api/scripts/add-vip-activated-at.sql
Normal 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;
|
||||
20
soul-api/scripts/add-vip-profile-fields.sql
Normal file
20
soul-api/scripts/add-vip-profile-fields.sql
Normal 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 '一句话简介';
|
||||
25
soul-api/scripts/add-vip-roles-and-fields.sql
Normal file
25
soul-api/scripts/add-vip-roles-and-fields.sql
Normal 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 选或手动填写';
|
||||
83
soul-api/scripts/fix-vip-orders.sql
Normal file
83
soul-api/scripts/fix-vip-orders.sql
Normal 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);
|
||||
12
soul-api/scripts/sync-orders.sh
Normal file
12
soul-api/scripts/sync-orders.sh
Normal 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 ""
|
||||
61
soul-api/scripts/test-p0-endpoints.ps1
Normal file
61
soul-api/scripts/test-p0-endpoints.ps1
Normal 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" })
|
||||
93
soul-api/scripts/test_transfer_notify.py
Normal file
93
soul-api/scripts/test_transfer_notify.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
模拟微信「商家转账到零钱」结果通知回调,请求本地/远程回调接口,
|
||||
用于验证:1)接口是否可达 2)wechat_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()
|
||||
64
soul-api/scripts/test_withdraw.py
Normal file
64
soul-api/scripts/test_withdraw.py
Normal 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()
|
||||
Reference in New Issue
Block a user