增强用户隐私保护,新增昵称授权功能。更新头像选择逻辑,用户可直接通过按钮选择微信头像或相册图片。优化个人资料页面,强化手机号必填提示,提升用户体验。调整多个页面以支持新隐私授权机制,确保符合最新隐私规范。

This commit is contained in:
Alex-larget
2026-03-19 18:26:45 +08:00
parent 35aecdcd8c
commit 588dad2518
124 changed files with 4093 additions and 38827 deletions

View File

@@ -1,62 +0,0 @@
# 文章 base64 图片迁移脚本
`chapters` 表中 `content` 字段内嵌的 base64 图片提取为独立文件,并替换为 `/uploads/book-images/xxx` 的 URL减小文章体积。
## 适用场景
- 历史文章中有大量粘贴的 base64 图片
- 保存时因 content 过大导致超时或失败
- 需要将 base64 转为文件存储
## 执行方式
### 1. 测试环境(建议先执行)
```bash
cd soul-api
# 加载测试环境配置(.env.development
$env:APP_ENV="development"
# 先 dry-run 预览,不写入
go run ./cmd/migrate-base64-images --dry-run
# 确认无误后正式执行
go run ./cmd/migrate-base64-images
```
### 2. 生产环境
```bash
cd soul-api
$env:APP_ENV="production"
go run ./cmd/migrate-base64-images --dry-run # 先预览
go run ./cmd/migrate-base64-images # 正式执行
```
### 3. 指定 DSN覆盖 .env
```bash
$env:DB_DSN="user:pass@tcp(host:port)/db?charset=utf8mb4&parseTime=True"
go run ./cmd/migrate-base64-images --dry-run
```
## 参数
| 参数 | 说明 |
|------|------|
| `--dry-run` | 仅统计和预览,不写入文件与数据库 |
## 行为说明
1. 查询 `content LIKE '%data:image%'` 的章节
2. 用正则提取 `src="data:image/xxx;base64,..."``src='...'`
3. 解码 base64保存到 `uploads/book-images/{timestamp}_{random}.{ext}`
4. 将 content 中的 base64 src 替换为 `/uploads/book-images/xxx`
5. 更新数据库
## 注意事项
- **务必先在测试环境验证**,确认无误后再跑生产
- 脚本依赖 `UPLOAD_DIR` 或默认 `uploads` 目录
- 图片格式支持png、jpeg、jpg、gif、webp

View File

@@ -1,96 +0,0 @@
# 数据库与 Go Model 字段对照检查报告
> 后端工程师对照 `soul-api/internal/model` 与 `soul_miniprogram.sql` 建表结构,列出**数据库表里可能缺失、但代码里在用**的字段。
> 若当前库是由旧版 SQL 导入或从未执行过迁移脚本,按本报告执行 `sync-users-vip-and-schema.sql` 或依赖 AutoMigrate 即可补全。
---
## 一、结论摘要
| 表名 | 是否缺字段 | 缺失字段Model 有、SQL 无) | 影响 |
|------|------------|-----------------------------|------|
| **users** | 是(旧库可能缺) | is_vip, vip_expire_date, vip_activated_at, vip_sort, vip_role | 订单列表、用户列表、VIP 设置、提现、匹配记录等接口报错(不含 vip_name/vip_avatar 等,小程序已改为直接读用户资料) |
| **chapters** | 是SQL 导出无此列) | hot_score | 文章排名、热门章节等依赖热度分的接口报错 |
| 其他业务表 | 否 | - | 与当前 SQL 一致 |
---
## 二、users 表
- **Model 文件**`internal/model/user.go`
- **SQL 表**`soul_miniprogram.sql``CREATE TABLE users` 已包含 VIP 相关列;若你的库是**更早的备份**或**未导入最新 SQL**,可能缺少以下列。
| 列名(蛇形) | 类型 | 说明 |
|-------------|------|------|
| is_vip | TINYINT(1) NULL DEFAULT 0 | 是否 VIP |
| vip_expire_date | DATETIME(3) NULL | VIP 到期时间 |
| vip_activated_at | DATETIME(3) NULL | 成为 VIP 时间,排序用 |
| vip_sort | INT NULL | 手动排序,越小越前 |
| vip_role | VARCHAR(50) NULL | 角色:从 vip_roles 选或手动填写 |
vip_name、vip_avatar、vip_project、vip_contact、vip_bio 不再在迁移中新增,小程序已改为直接读用户资料 nickname/avatar/projectIntro/phone 等;已有库可保留该五列作兼容。)
**修复**:执行 `scripts/sync-users-vip-and-schema.sql` 中 users 部分,或重启 soul-api未设 `SKIP_AUTO_MIGRATE` 时 AutoMigrate 会补列)。
---
## 三、chapters 表
- **Model 文件**`internal/model/chapter.go`
- **SQL 表**:当前 `soul_miniprogram.sql``chapters` 仅有 `hot_score_override`decimal**没有** `hot_score`int
Model 使用的是 `hot_score`(热度分,用于排名算法),因此仅按该 SQL 建表时,数据库**缺少** `hot_score`
| 列名(蛇形) | 类型 | 说明 |
|-------------|------|------|
| hot_score | INT NOT NULL DEFAULT 0 | 热度分,用于排名算法 |
**修复**:执行 `scripts/sync-users-vip-and-schema.sql` 中 chapters 部分,或执行 `scripts/add-hot-score.sql`,或依赖 soul-api 启动时对 Chapter 的 AutoMigrate。
---
## 四、已核对无缺列的表
以下表在 `soul_miniprogram.sql` 中的列与对应 Model 一致,**无需补列**(仅列名与类型一致即可,顺序可不同):
- **orders**:与 `model.Order` 一致
- **withdrawals**:与 `model.Withdrawal` 一致(库中多出的 transaction_id、error_message 不影响)
- **admin_users**:与 `model.AdminUser` 一致
- **system_config**:与 `model.SystemConfig` 一致
- **referral_bindings**Model 字段在表中均存在
- **referral_visits**:与 `model.ReferralVisit` 一致
- **user_rules**:与 `model.UserRule` 一致
- **user_tracks**:与 `model.UserTrack` 一致
- **reading_progress**:与 `model.ReadingProgress` 一致(表为 section_idModel 为 section_id
- **match_records**:与 `model.MatchRecord` 一致
- **mentor_consultations**:与 `model.MentorConsultation` 一致
- **mentors**:与 `model.Mentor` 一致
- **link_tags**:与 `model.LinkTag` 一致
- **persons**:与 `model.Person` 一致
- **author_config**:与 `model.AuthorConfig` 一致
- **ckb_lead_records**:与 `model.CkbLeadRecord` 一致
- **ckb_submit_records**:与 `model.CkbSubmitRecord` 一致
- **user_addresses**:与 `model.UserAddress` 一致
- **vip_roles**:与 `model.VipRole` 一致
- **wechat_callback_logs**:与 `model.WechatCallbackLog` 一致
---
## 五、推荐操作
1. **一次性补全(推荐)**
在备份后执行:
```bash
mysql -u 用户 -p 数据库名 < soul-api/scripts/sync-users-vip-and-schema.sql
```
若某条报 `Duplicate column name`,表示该列已存在,可跳过。
2. **依赖 AutoMigrate**
确保 soul-api 的 `database.Init` 中已对 `User`、`SystemConfig`、`Chapter` 执行 `AutoMigrate`,且未设置 `SKIP_AUTO_MIGRATE`,重启服务后会自动补全缺失列。
3. **新建库**
若从零建库,建议用**最新**的 `soul_miniprogram.sql` 导入后,再执行一次 `sync-users-vip-and-schema.sql`,确保 users 与 chapters 与 Model 完全一致。
---
**检查日期**:按代码与 SQL 导出时点生成
**检查范围**soul-api 全部 `internal/model` 与 soul_miniprogram.sql 中对应表结构

View File

@@ -1,18 +0,0 @@
-- 后台管理员用户表(与 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

@@ -1,23 +0,0 @@
-- 作者详情独立表,每个字段单独列,便于编辑与保存
-- 执行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

@@ -1,28 +0,0 @@
-- 余额相关表(新版迁移)
-- 执行mysql -u user -p database < soul-api/scripts/add-balance-tables.sql
-- user_balances
CREATE TABLE IF NOT EXISTS user_balances (
user_id VARCHAR(50) PRIMARY KEY,
balance DECIMAL(10,2) DEFAULT 0,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- balance_transactions
CREATE TABLE IF NOT EXISTS balance_transactions (
id VARCHAR(50) PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
type VARCHAR(20) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
order_id VARCHAR(50) DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_created (user_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- orders 增加 payment_method
SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE table_schema = DATABASE() AND table_name = 'orders' AND column_name = 'payment_method');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE orders ADD COLUMN payment_method VARCHAR(20) DEFAULT NULL AFTER referrer_id', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@@ -1,9 +0,0 @@
-- ============================================================
-- 章节归属字段 - 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

@@ -1,5 +0,0 @@
-- 为 all-chapters 接口加速sort_order + id 排序索引
-- 执行node .cursor/scripts/db-exec/run.js -f soul-api/scripts/add-chapters-index-for-all-chapters.sql
-- 若索引已存在会报错,可忽略
CREATE INDEX idx_chapters_sort_id ON chapters(sort_order, id);

View File

@@ -1,8 +0,0 @@
-- ============================================================
-- 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

@@ -1,2 +0,0 @@
-- 仅添加 ckb_plan_id若 add-persons-ckb-fields.sql 已部分执行或需单独补列)
ALTER TABLE `persons` ADD COLUMN `ckb_plan_id` BIGINT NOT NULL DEFAULT 0 COMMENT '存客宝获客计划ID';

View File

@@ -1,6 +0,0 @@
-- 代付逻辑改造gift_pay_requests 增加 quantity、redeemed_count
-- 执行mysql -u user -p db < soul-api/scripts/add-gift-pay-quantity.sql
-- 若列已存在会报 Duplicate column可忽略
ALTER TABLE gift_pay_requests ADD COLUMN quantity INT NOT NULL DEFAULT 1;
ALTER TABLE gift_pay_requests ADD COLUMN redeemed_count INT NOT NULL DEFAULT 0;

View File

@@ -1,25 +0,0 @@
-- 代付请求表 + 订单表代付字段
-- 执行mysql -u user -p db < soul-api/scripts/add-gift-pay-requests.sql
-- 注orders 表新增字段由 GORM AutoMigrate 自动添加;若需手动执行:
-- ALTER TABLE orders ADD COLUMN gift_pay_request_id VARCHAR(50) DEFAULT NULL;
-- ALTER TABLE orders ADD COLUMN payer_user_id VARCHAR(50) DEFAULT NULL;
CREATE TABLE IF NOT EXISTS gift_pay_requests (
id VARCHAR(50) PRIMARY KEY,
request_sn VARCHAR(32) NOT NULL UNIQUE,
initiator_user_id VARCHAR(50) NOT NULL,
product_type VARCHAR(30) NOT NULL,
product_id VARCHAR(50) NOT NULL DEFAULT '',
amount DECIMAL(10,2) NOT NULL,
description VARCHAR(200) NOT NULL DEFAULT '',
status VARCHAR(20) NOT NULL DEFAULT 'pending',
payer_user_id VARCHAR(50) DEFAULT NULL,
order_id VARCHAR(50) DEFAULT NULL,
expire_at DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_initiator (initiator_user_id),
INDEX idx_payer (payer_user_id),
INDEX idx_status (status),
INDEX idx_request_sn (request_sn)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@@ -1,2 +0,0 @@
-- 为 chapters 表新增 hot_score 字段(热度分,用于排名算法)
ALTER TABLE chapters ADD COLUMN hot_score INT NOT NULL DEFAULT 0;

View File

@@ -1,40 +0,0 @@
-- 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

@@ -1,5 +0,0 @@
-- 订单表新增退款原因列
-- 执行: 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

@@ -1,15 +0,0 @@
-- 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

@@ -1,6 +0,0 @@
-- persons 表新增 ckb_api_key 字段
-- 作用:存储该 @人物 在存客宝的接入密钥,点击加好友时用该 Key 推线索;留空则 fallback 全局 CKB_LEAD_API_KEY
-- 执行mysql -u user -p db < soul-api/scripts/add-persons-ckb-api-key.sql
ALTER TABLE persons
ADD COLUMN ckb_api_key VARCHAR(100) NOT NULL DEFAULT '' AFTER label;

View File

@@ -1,12 +0,0 @@
-- 为 persons 表增加存客宝 API 获客相关字段,便于管理端回显与二次编辑
ALTER TABLE `persons`
ADD COLUMN `greeting` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '存客宝打招呼语' AFTER `ckb_api_key`,
ADD COLUMN `tips` TEXT NULL COMMENT '获客成功提示' AFTER `greeting`,
ADD COLUMN `remark_type` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '备注类型phone/nickname/source' AFTER `tips`,
ADD COLUMN `remark_format` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '备注格式' AFTER `remark_type`,
ADD COLUMN `add_friend_interval` INT NOT NULL DEFAULT 1 COMMENT '添加好友间隔(分钟)' AFTER `remark_format`,
ADD COLUMN `start_time` VARCHAR(10) NOT NULL DEFAULT '09:00' COMMENT '允许加人开始时间 HH:MM' AFTER `add_friend_interval`,
ADD COLUMN `end_time` VARCHAR(10) NOT NULL DEFAULT '18:00' COMMENT '允许加人结束时间 HH:MM' AFTER `start_time`,
ADD COLUMN `device_groups` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '参与计划的设备ID列表逗号分隔' AFTER `end_time`,
ADD COLUMN `ckb_plan_id` BIGINT NOT NULL DEFAULT 0 COMMENT '存客宝获客计划ID' AFTER `device_groups`;

View File

@@ -1,23 +0,0 @@
-- persons、link_tags 表,供 ContentPage @提及人物与链接标签配置
-- 执行mysql -u user -p db < soul-api/scripts/add-persons-link-tags.sql
CREATE TABLE IF NOT EXISTS persons (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
person_id VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL DEFAULT '',
label VARCHAR(200) NOT NULL DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS link_tags (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tag_id VARCHAR(50) NOT NULL UNIQUE,
label VARCHAR(200) NOT NULL DEFAULT '',
url VARCHAR(500) NOT NULL DEFAULT '',
type VARCHAR(20) NOT NULL DEFAULT 'url',
app_id VARCHAR(100) NOT NULL DEFAULT '',
page_path VARCHAR(500) NOT NULL DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@@ -1,6 +0,0 @@
-- persons 表新增 token 字段32 位唯一,@ 时存此值,小程序用此兑换 ckb_api_key
-- 执行cd 项目根目录 && node .cursor/scripts/db-exec/run.js -f soul-api/scripts/add-persons-token.sql
-- 或mysql -u user -p db < soul-api/scripts/add-persons-token.sql
ALTER TABLE persons ADD COLUMN token VARCHAR(36) NOT NULL DEFAULT '' AFTER person_id;
ALTER TABLE persons ADD UNIQUE INDEX idx_persons_token (token);

View File

@@ -1,10 +0,0 @@
-- persons 表新增 user_id用于“超级个体开通后自动创建@人”等幂等绑定
-- 说明:
-- - 允许为空(历史数据/手工创建不绑定 user
-- - 允许多条 NULLMySQL UNIQUE 对 NULL 不冲突)
-- - 绑定后建议一人仅一条 Person满足“昵称同名@人”需求)
ALTER TABLE persons
ADD COLUMN user_id VARCHAR(50) DEFAULT NULL COMMENT '绑定用户ID幂等创建@人)',
ADD UNIQUE KEY uk_persons_user_id (user_id);

View File

@@ -1,5 +0,0 @@
-- 为 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

@@ -1,15 +0,0 @@
-- 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

@@ -1,8 +0,0 @@
-- 规则引擎默认数据:插入「注册」规则,供登录后完善头像引导
-- 执行mysql -u user -p db < soul-api/scripts/add-user-rules-default.sql
-- 幂等:若已存在 trigger='注册' 则跳过
INSERT INTO user_rules (title, description, `trigger`, sort, enabled, created_at, updated_at)
SELECT '完善个人信息', '设置头像和昵称,让其他创业者更容易认识你', '注册', 1, 1, NOW(), NOW()
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM user_rules WHERE `trigger` = '注册' LIMIT 1);

View File

@@ -1,5 +0,0 @@
-- 用户软删除:管理端假删除,用户再次登录会新建账号
-- 执行后DELETE 操作改为 SET deleted_at不再物理删除避免外键约束
ALTER TABLE users ADD COLUMN deleted_at DATETIME(3) NULL DEFAULT NULL COMMENT '软删除时间' AFTER updated_at;
CREATE INDEX idx_users_deleted_at ON users (deleted_at);

View File

@@ -1,13 +0,0 @@
-- 新增 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

@@ -1,20 +0,0 @@
-- ============================================================
-- 会员资料字段 - 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

@@ -1,25 +0,0 @@
-- 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

@@ -1,9 +0,0 @@
# 补全 persons.ckb_api_key
若存在 ckb_plan_id 但 ckb_api_key 为空的 Person可手动调用 plan/detail 补全。
**执行前**:确保 soul-api 可连接存客宝CKB_OPEN_API_KEY、CKB_OPEN_ACCOUNT 已配置)。
**方式一**:管理端逐个编辑保存(会触发存客宝同步,若 Person 无 ckb_api_key 需在编辑弹窗填写或重新创建)。
**方式二**:写一次性脚本,遍历 `ckb_plan_id > 0 AND (ckb_api_key IS NULL OR ckb_api_key = '')` 的 Person调 ckbOpenGetPlanDetail 获取 apiKey 并 UPDATE。

View File

@@ -1,5 +0,0 @@
-- 修复篇章标题:将 slug 形式的 part_title 更新为展示标题数据来源DB
-- 执行node .cursor/scripts/db-exec/run.js -f soul-api/scripts/fix-part-titles.sql
-- part-2026-daily 的标题应为「2026每日派对干货」
UPDATE chapters SET part_title = '2026每日派对干货' WHERE part_id = 'part-2026-daily' AND (part_title = 'part-2026-daily' OR part_title = '' OR part_title IS NULL);

View File

@@ -1,83 +0,0 @@
-- ============================================================
-- 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

@@ -1,14 +0,0 @@
-- 预置用户旅程引导规则(高频行为锚点)
-- 执行时幂等:如果规则已存在则忽略
INSERT IGNORE INTO user_rules (title, description, trigger, sort, enabled, created_at, updated_at) VALUES
('注册完成 → 填写头像', '用户完成注册后,引导填写头像和昵称,提升个人信息完整度', '注册', 10, 1, NOW(), NOW()),
('完成匹配 → 补充个人资料', '用户完成 Soul 派对房匹配后,引导填写 MBTI、行业、职位等详细信息', '完成匹配', 20, 1, NOW(), NOW()),
('首次浏览章节 → 绑定手机号', '用户点击阅读收费章节时,引导绑定手机号以完成身份验证', '点击收费章节', 30, 1, NOW(), NOW()),
('付款 ¥1980 → 填写完整信息', '购买全书1980元需填写真实姓名、联系方式、所在行业、MBTI以便进入 VIP 群', '完成付款', 40, 1, NOW(), NOW()),
('加入派对房 → 填写项目介绍', '进入 Soul 派对房前,引导填写个人项目介绍和核心需求,便于精准匹配', '加入派对房', 50, 1, NOW(), NOW()),
('浏览 5 个章节 → 分享推广', '用户累计阅读 5 个章节后,触发分享引导,邀请好友可获得收益', '累计浏览5章节', 60, 1, NOW(), NOW()),
('绑定微信 → 开启分销', '绑定微信后,提示用户开启分销功能,生成专属推广码', '绑定微信', 70, 1, NOW(), NOW()),
('收益达到 ¥50 → 申请提现', '累计分销收益超过 50 元时,引导用户申请提现', '收益满50元', 80, 1, NOW(), NOW()),
('完善存客宝信息 → 进入流量池', '引导用户授权存客宝信息同步,进入对应微信流量池,获得精准服务', '手动触发', 90, 1, NOW(), NOW()),
('浏览导师主页 → 预约咨询', '用户浏览导师详情页超过 30 秒,引导预约一对一咨询', '浏览导师页', 100, 1, NOW(), NOW());

View File

@@ -1,12 +0,0 @@
#!/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

@@ -1,16 +0,0 @@
-- ============================================================
-- 同步 users 表与 Go Model仅 VIP 身份/状态字段,不含单独 VIP 资料列)
-- 小程序已改为直接读用户资料nickname/avatar/projectIntro/phone不再单独存 vip_name/vip_avatar 等。
-- 若某条 ALTER 报 Duplicate column name说明该列已存在跳过即可。
-- 也可直接重启 soul-api未设 SKIP_AUTO_MIGRATE 时会自动补列)。
-- ============================================================
-- users 表VIP 身份与状态(与 internal/model/user.go 一致)
ALTER TABLE users ADD COLUMN is_vip TINYINT(1) NULL DEFAULT 0 COMMENT '是否 VIP';
ALTER TABLE users ADD COLUMN vip_expire_date DATETIME(3) NULL DEFAULT NULL COMMENT 'VIP 到期时间';
ALTER TABLE users ADD COLUMN vip_activated_at DATETIME(3) NULL DEFAULT NULL COMMENT '成为 VIP 时间,排序用';
ALTER TABLE users ADD COLUMN vip_sort INT NULL DEFAULT NULL COMMENT '手动排序,越小越前';
ALTER TABLE users ADD COLUMN vip_role VARCHAR(50) NULL DEFAULT NULL COMMENT '角色:从 vip_roles 选或手动填写';
-- chapters 表Model 使用 hot_score热度分SQL 导出里只有 hot_score_override缺则排名等接口报错
ALTER TABLE chapters ADD COLUMN hot_score INT NOT NULL DEFAULT 0 COMMENT '热度分,用于排名算法';

View File

@@ -1,61 +0,0 @@
# 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

@@ -1,93 +0,0 @@
#!/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

@@ -1,64 +0,0 @@
#!/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()