diff --git a/.env b/.env
index 92cfa58..72273df 100644
--- a/.env
+++ b/.env
@@ -10,8 +10,8 @@ BACKEND_URL=http://127.0.0.1:8000
DATABASE_URL=mysql+pymysql://root:root@127.0.0.1:3306/fastadmin?charset=utf8mb4
# ===== 用户信息接口 (PHP后端) =====
-# 开发环境暂时使用本地地址,PHP后端配置好后再修改
-USER_INFO_API=http://127.0.0.1:8080/api/user_basic/get_user_basic
+# PHP 后端地址,用于用户认证
+USER_INFO_API=http://127.0.0.1:30100/api/user_basic/get_user_basic
# ===== AI 配置 =====
# 阿里云 DashScope API 密钥
diff --git a/PHP服务连接问题解决方案.md b/PHP服务连接问题解决方案.md
new file mode 100644
index 0000000..fb82aff
--- /dev/null
+++ b/PHP服务连接问题解决方案.md
@@ -0,0 +1,199 @@
+# PHP 服务连接问题解决方案
+
+## 问题描述
+Python 后端无法连接到 PHP 服务,报错:
+```
+HTTPConnectionPool(host='192.168.1.164', port=30100): Read timed out
+```
+
+## 已完成的修复
+
+### 1. 配置修正
+- ✅ 修改 `lover/deps.py`:移除硬编码 IP,从配置读取
+- ✅ 修改 `lover/config.py`:默认地址改为 `127.0.0.1:30100`
+- ✅ 修改 `.env`:端口从 `8080` 改为 `30100`
+- ✅ 减少超时时间:从 5 秒改为 3 秒
+- ✅ 改进错误处理:区分超时和连接错误
+
+### 2. 启动脚本优化
+- ✅ 使用 `router.php` 而不是 `-t .`
+- ✅ 添加端口清理逻辑,自动终止占用端口的旧进程
+- ✅ 添加等待时间,确保服务完全启动
+
+### 3. 测试工具
+创建了 `xunifriend_RaeeC/public/test_api.php` 用于测试:
+- `/test_api.php` - 测试 PHP 服务器基本响应
+- `/test_db` - 测试数据库连接
+
+## 当前问题分析
+
+### PHP 服务器状态
+```
+端口 30100 已被监听(进程 31592, 16636)
+但是请求超时,无法获得响应
+```
+
+### 可能的原因
+
+1. **数据库连接问题**
+ - PHP 应用可能在启动时尝试连接数据库
+ - 如果数据库连接失败或慢,会导致请求超时
+ - 检查 `xunifriend_RaeeC/application/database.php` 配置
+
+2. **PHP 内置服务器限制**
+ - PHP 内置服务器是单线程的
+ - 如果有请求阻塞,后续请求会超时
+ - 建议使用 Apache 或 Nginx + PHP-FPM
+
+3. **应用初始化问题**
+ - ThinkPHP 框架初始化可能有问题
+ - 检查 `xunifriend_RaeeC/application/admin/command/Install/install.lock` 是否存在
+
+4. **路由配置问题**
+ - API 路由可能未正确配置
+ - 检查 `xunifriend_RaeeC/application/route.php`
+
+## 解决步骤
+
+### 步骤 1: 测试 PHP 服务器基本功能
+```cmd
+# 在浏览器或命令行测试
+curl http://127.0.0.1:30100/test_api.php
+```
+
+预期响应:
+```json
+{
+ "code": 1,
+ "msg": "PHP 服务器运行正常",
+ "time": 1738665600,
+ "data": {
+ "php_version": "8.0.0",
+ "server_time": "2026-02-04 19:00:00"
+ }
+}
+```
+
+### 步骤 2: 测试数据库连接
+```cmd
+curl http://127.0.0.1:30100/test_db
+```
+
+如果数据库连接失败,检查:
+- MySQL 是否运行
+- `xunifriend_RaeeC/application/database.php` 配置是否正确
+- 数据库用户名密码是否正确
+
+### 步骤 3: 测试实际 API
+```cmd
+# 使用有效的 token 测试
+curl -H "token: YOUR_TOKEN_HERE" http://127.0.0.1:30100/api/user_basic/get_user_basic
+```
+
+### 步骤 4: 检查 PHP 错误日志
+PHP 内置服务器的错误会显示在启动窗口中,查看是否有:
+- 数据库连接错误
+- 文件权限错误
+- PHP 语法错误
+- 缺少扩展
+
+## 临时解决方案
+
+### 方案 1: 使用开发环境兜底(已实现)
+Python 后端在开发环境下,如果 PHP 连接失败,会自动使用测试用户:
+```python
+# 在 lover/deps.py 中
+if settings.APP_ENV == "development" and settings.DEBUG:
+ logger.warning(f"开发环境:token 验证失败({e.detail}),使用测试用户")
+ return AuthedUser(id=70, reg_step=2, gender=0, nickname="test-user", token="")
+```
+
+### 方案 2: 直接使用数据库认证
+修改 Python 后端,不依赖 PHP API,直接查询数据库:
+```python
+# 在 lover/deps.py 中添加
+def _fetch_user_from_db(token: str) -> Optional[dict]:
+ """直接从数据库获取用户信息"""
+ from lover.db import get_db
+ db = next(get_db())
+ user = db.execute(
+ "SELECT * FROM fa_user WHERE token = :token",
+ {"token": token}
+ ).fetchone()
+ return dict(user) if user else None
+```
+
+### 方案 3: 重启 PHP 服务
+```cmd
+# 使用更新后的启动脚本,会自动清理旧进程
+启动项目.bat
+```
+
+## 长期解决方案
+
+### 推荐:使用 Nginx + PHP-FPM
+PHP 内置服务器不适合生产环境,建议:
+
+1. 安装 Nginx
+2. 配置 PHP-FPM
+3. 配置 Nginx 反向代理
+
+配置示例:
+```nginx
+server {
+ listen 30100;
+ server_name localhost;
+ root C:/Users/Administrator/Desktop/Project/AI_GirlFriend/xunifriend_RaeeC/public;
+ index index.php index.html;
+
+ location / {
+ try_files $uri $uri/ /index.php?$query_string;
+ }
+
+ location ~ \.php$ {
+ fastcgi_pass 127.0.0.1:9000;
+ fastcgi_index index.php;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ include fastcgi_params;
+ }
+}
+```
+
+## 调试命令
+
+### 查看端口占用
+```cmd
+netstat -ano | findstr :30100
+```
+
+### 终止进程
+```cmd
+taskkill /F /PID <进程ID>
+```
+
+### 测试 PHP 配置
+```cmd
+D:\2_part\php-8.0.0-Win32-vs16-x64\php.exe -v
+D:\2_part\php-8.0.0-Win32-vs16-x64\php.exe -m # 查看已安装的扩展
+```
+
+### 手动启动 PHP 服务器(用于调试)
+```cmd
+cd xunifriend_RaeeC\public
+D:\2_part\php-8.0.0-Win32-vs16-x64\php.exe -S 0.0.0.0:30100 router.php
+```
+
+## 下一步行动
+
+1. **立即测试**:运行 `curl http://127.0.0.1:30100/test_api.php`
+2. **检查数据库**:确认 MySQL 正在运行
+3. **查看日志**:检查 PHP 启动窗口的错误信息
+4. **重启服务**:使用更新后的 `启动项目.bat`
+
+## 文件修改记录
+
+- `lover/deps.py` - 改进错误处理和超时设置
+- `lover/config.py` - 修正默认地址
+- `.env` - 修正端口配置
+- `启动项目.bat` - 添加端口清理和 router.php
+- `xunifriend_RaeeC/public/test_api.php` - 新增测试脚本
diff --git a/check_gift_images.py b/check_gift_images.py
deleted file mode 100644
index b1e58d6..0000000
--- a/check_gift_images.py
+++ /dev/null
@@ -1,174 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-检查礼物图片文件
-"""
-from pathlib import Path
-
-# 图片源目录
-SOURCE_DIR = Path('开发/2026年2月4日/Gift')
-
-# SQL 中定义的礼物列表
-SQL_GIFTS = {
- # 经济类礼物(10-50金币)
- "玫瑰花": "rose.png",
- "棒棒糖": "lollipop.png",
- "咖啡": "coffee.png",
- "冰淇淋": "icecream.png",
- "小蛋糕": "cake.png",
- "巧克力": "chocolate.png",
- "奶茶": "milktea.png",
- "小星星": "star.png",
- "爱心气球": "heart_balloon.png",
- "小礼物盒": "gift_box.png",
- "彩虹": "rainbow.png",
-
- # 中档礼物(50-200金币)
- "香槟": "champagne.png",
- "钻石": "diamond.png",
- "王冠": "crown.png",
- "爱心": "big_heart.png",
- "月亮": "moon.png",
- "烟花": "fireworks.png",
- "水晶球": "crystal_ball.png",
- "玫瑰花束": "rose_bouquet.png",
- "星星项链": "star_necklace.png",
-
- # 高级礼物(200-500金币)
- "跑车": "sports_car.png",
- "飞机": "airplane.png",
- "游艇": "yacht.png",
- "城堡": "castle.png",
-
- # 特殊礼物(500+金币)
- "宇宙飞船": "spaceship.png",
- "时光机": "time_machine.png",
- "魔法棒": "magic_wand.png",
- "永恒之心": "eternal_heart.png",
-
- # 节日限定礼物
- "圣诞树": "christmas_tree.png",
- "情人节巧克力": "valentine_chocolate.png",
- "生日蛋糕": "birthday_cake.png",
- "万圣节南瓜": "halloween_pumpkin.png",
-}
-
-# 实际图片文件名映射
-ACTUAL_FILES = {
- "玫瑰花.png": "rose.png",
- "棒棒糖.png": "lollipop.png",
- "咖啡.png": "coffee.png",
- "冰淇淋.png": "icecream.png",
- "小蛋糕.png": "cake.png",
- "巧克力.png": "chocolate.png",
- "奶茶.png": "milktea.png",
- "爱心气球.png": "heart_balloon.png",
- "小礼物盒.png": "gift_box.png",
- "彩虹.png": "rainbow.png",
- "香槟.png": "champagne.png",
- "钻石.png": "diamond.png",
- "王冠.png": "crown.png",
- "爱心.png": "big_heart.png",
- "月亮.png": "moon.png",
- "烟花.png": "fireworks.png",
- "水晶球.png": "crystal_ball.png",
- "玫瑰花束.png": "rose_bouquet.png",
- "星星项链.png": "star_necklace.png",
- "跑车.png": "sports_car.png",
- "飞机.png": "airplane.png",
- "游艇.png": "yacht.png",
- "城堡.png": "castle.png",
- "宇宙飞船.png": "spaceship.png",
- "魔法棒.png": "magic_wand.png",
- "圣诞树.png": "christmas_tree.png",
- "情人节巧克力.png": "valentine_chocolate.png",
- "生日蛋糕.png": "birthday_cake.png",
- "万圣节南瓜.png": "halloween_pumpkin.png",
-}
-
-
-def check_gifts():
- """检查礼物图片"""
- print("=" * 70)
- print(" 礼物图片文件检查")
- print("=" * 70)
- print()
-
- if not SOURCE_DIR.exists():
- print(f"❌ 错误:源目录不存在: {SOURCE_DIR}")
- return
-
- # 获取所有 PNG 文件
- all_files = list(SOURCE_DIR.glob("*.png"))
- print(f"📁 源目录: {SOURCE_DIR}")
- print(f"📊 找到 {len(all_files)} 个 PNG 文件")
- print()
-
- # 检查每个礼物
- print("=" * 70)
- print(" 检查礼物图片")
- print("=" * 70)
- print()
-
- existing_gifts = []
- missing_gifts = []
-
- for gift_name, target_filename in SQL_GIFTS.items():
- # 查找对应的实际文件
- found = False
- for actual_file, target_file in ACTUAL_FILES.items():
- if target_file == target_filename:
- file_path = SOURCE_DIR / actual_file
- if file_path.exists():
- size = file_path.stat().st_size / 1024
- print(f"✓ {gift_name}")
- print(f" 文件: {actual_file} → {target_filename}")
- print(f" 大小: {size:.1f} KB")
- print()
- existing_gifts.append((gift_name, actual_file, target_filename))
- found = True
- break
-
- if not found:
- print(f"✗ {gift_name}")
- print(f" 目标: {target_filename}")
- print(f" 状态: 缺失")
- print()
- missing_gifts.append((gift_name, target_filename))
-
- # 统计结果
- print("=" * 70)
- print(" 统计结果")
- print("=" * 70)
- print()
- print(f"SQL 中定义: {len(SQL_GIFTS)} 个礼物")
- print(f"存在图片: {len(existing_gifts)} 个")
- print(f"缺失图片: {len(missing_gifts)} 个")
- print()
-
- if missing_gifts:
- print("=" * 70)
- print(" 缺失的礼物")
- print("=" * 70)
- print()
- for gift_name, target_filename in missing_gifts:
- print(f"- {gift_name} ({target_filename})")
- print()
-
- if existing_gifts:
- print("=" * 70)
- print(" 建议")
- print("=" * 70)
- print()
- print(f"✓ 有 {len(existing_gifts)} 个礼物有图片")
- print(f"✗ 有 {len(missing_gifts)} 个礼物缺少图片")
- print()
- print("建议:")
- print("1. 创建只包含现有图片的 SQL 文件")
- print("2. 上传图片到 OSS")
- print("3. 导入数据库")
- print()
-
-
-if __name__ == "__main__":
- check_gifts()
diff --git a/check_outfit_images.py b/check_outfit_images.py
deleted file mode 100644
index 902af1a..0000000
--- a/check_outfit_images.py
+++ /dev/null
@@ -1,205 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-检查换装图片文件
-"""
-from pathlib import Path
-import hashlib
-
-# 图片源目录
-SOURCE_DIR = Path('开发/2026年2月4日/Image')
-
-# 期望的文件列表
-EXPECTED_FILES = {
- # 上装 - 女性
- "女性上衣白色T桖.png": "白色T恤",
- "女性-上装-粉丝短袖.png": "粉色短袖",
- "女性-上装-蓝色衬衫.png": "蓝色衬衫",
- "女性-上装-灰色卫衣.png": "灰色卫衣",
- "女性-上装-收费-蕾丝吊带上衣.png": "蕾丝吊带上衣",
- "女性-上装-收费-一字领露肩上衣.png": "一字领露肩上衣",
- "女性-上装-收费-露脐短袖.png": "露脐短袖",
- "女性-上装-收费-针织开衫.png": "针织开衫",
- "女性-上装-收费-小香风外套.png": "小香风外套",
- "女性-上装-vlp专属-真丝衬衫.png": "真丝衬衫",
-
- # 下装 - 女性
- "女性-下衣-蓝色牛仔裤.png": "蓝色牛仔裤",
- "女性-下衣-黑色短裙.png": "黑色短裙",
- "女性-下衣-白色短裙.png": "白色短裤",
- "女性-下衣-灰色运动裤.png": "灰色运动裤",
- "女性-下衣-A字半身裙.png": "A字半身裙",
- "女性-下衣-高腰阔腿裤.png": "高腰阔腿裤",
- "女性-下衣-收费-百褶短裙.png": "百褶短裙",
- "女性-下衣-收费-破洞牛仔裤.png": "破洞牛仔裤",
- "女性-下衣-收费-西装裤.png": "西装裤",
-
- # 连衣裙 - 女性
- "女性-连衣裙-白色连衣裙.png": "白色连衣裙",
- "女性-连衣裙-碎花连衣裙.png": "碎花连衣裙",
- "女性-连衣裙-黑色小礼服.png": "黑色小礼服",
- "女性-连衣裙-优雅长裙.png": "优雅长裙",
- "女性-连衣裙-吊带连衣裙.png": "吊带连衣裙",
- "女性-连衣裙-JK制服.png": "JK制服",
- "女性-连衣裙-汉服.png": "汉服",
- "女性-连衣裙-洛丽塔.png": "洛丽塔",
- "女性-连衣裙-圣诞服装.png": "圣诞装",
- "女性-连衣裙-高级定制婚纱.png": "高级定制婚纱",
-}
-
-
-def get_file_hash(file_path):
- """计算文件 MD5 哈希值"""
- md5 = hashlib.md5()
- with open(file_path, 'rb') as f:
- for chunk in iter(lambda: f.read(4096), b""):
- md5.update(chunk)
- return md5.hexdigest()
-
-
-def check_images():
- """检查图片文件"""
- print("=" * 70)
- print(" 换装图片文件检查")
- print("=" * 70)
- print()
-
- if not SOURCE_DIR.exists():
- print(f"❌ 错误:源目录不存在: {SOURCE_DIR}")
- return
-
- # 获取所有 PNG 文件
- all_files = list(SOURCE_DIR.glob("*.png"))
- print(f"📁 源目录: {SOURCE_DIR}")
- print(f"📊 找到 {len(all_files)} 个 PNG 文件")
- print()
-
- # 检查期望的文件
- print("=" * 70)
- print(" 检查期望的文件")
- print("=" * 70)
- print()
-
- missing_files = []
- existing_files = []
- file_sizes = {}
-
- for filename, description in EXPECTED_FILES.items():
- file_path = SOURCE_DIR / filename
- if file_path.exists():
- size = file_path.stat().st_size
- size_kb = size / 1024
- file_sizes[filename] = size
- existing_files.append(filename)
-
- if size < 10000: # 小于 10KB 可能是空白或损坏
- print(f"⚠️ {description}")
- print(f" 文件: {filename}")
- print(f" 大小: {size_kb:.1f} KB (可能是空白图片)")
- print()
- else:
- print(f"✓ {description}")
- print(f" 文件: {filename}")
- print(f" 大小: {size_kb:.1f} KB")
- print()
- else:
- missing_files.append((filename, description))
- print(f"✗ {description}")
- print(f" 文件: {filename}")
- print(f" 状态: 缺失")
- print()
-
- # 检查重复的图片(相同内容)
- print("=" * 70)
- print(" 检查重复的图片")
- print("=" * 70)
- print()
-
- hash_map = {}
- duplicates = []
-
- for filename in existing_files:
- file_path = SOURCE_DIR / filename
- file_hash = get_file_hash(file_path)
-
- if file_hash in hash_map:
- duplicates.append((filename, hash_map[file_hash]))
- print(f"⚠️ 发现重复图片:")
- print(f" 文件1: {hash_map[file_hash]}")
- print(f" 文件2: {filename}")
- print(f" 哈希: {file_hash}")
- print()
- else:
- hash_map[file_hash] = filename
-
- if not duplicates:
- print("✓ 没有发现重复的图片")
- print()
-
- # 检查额外的文件
- print("=" * 70)
- print(" 检查额外的文件")
- print("=" * 70)
- print()
-
- expected_filenames = set(EXPECTED_FILES.keys())
- actual_filenames = set(f.name for f in all_files)
- extra_files = actual_filenames - expected_filenames
-
- if extra_files:
- for filename in extra_files:
- file_path = SOURCE_DIR / filename
- size = file_path.stat().st_size
- size_kb = size / 1024
- print(f"⚠️ 额外的文件: {filename}")
- print(f" 大小: {size_kb:.1f} KB")
- print()
- else:
- print("✓ 没有额外的文件")
- print()
-
- # 统计结果
- print("=" * 70)
- print(" 统计结果")
- print("=" * 70)
- print()
- print(f"期望文件数: {len(EXPECTED_FILES)}")
- print(f"存在文件数: {len(existing_files)}")
- print(f"缺失文件数: {len(missing_files)}")
- print(f"重复图片数: {len(duplicates)}")
- print(f"额外文件数: {len(extra_files)}")
- print()
-
- # 建议
- print("=" * 70)
- print(" 建议")
- print("=" * 70)
- print()
-
- if missing_files:
- print("⚠️ 缺失的文件需要补充:")
- for filename, description in missing_files:
- print(f" - {description} ({filename})")
- print()
-
- if duplicates:
- print("⚠️ 重复的图片需要替换为不同的图片:")
- for file1, file2 in duplicates:
- print(f" - {file1} 和 {file2} 内容相同")
- print()
-
- # 检查小文件(可能是空白)
- small_files = [(f, s) for f, s in file_sizes.items() if s < 10000]
- if small_files:
- print("⚠️ 以下文件可能是空白或损坏(小于 10KB):")
- for filename, size in small_files:
- print(f" - {filename} ({size / 1024:.1f} KB)")
- print()
-
- if not missing_files and not duplicates and not small_files:
- print("✓ 所有图片文件正常!")
- print()
-
-
-if __name__ == "__main__":
- check_images()
diff --git a/copy_outfit_images.bat b/copy_outfit_images.bat
deleted file mode 100644
index bd6b1ab..0000000
--- a/copy_outfit_images.bat
+++ /dev/null
@@ -1,112 +0,0 @@
-@echo off
-chcp 65001 >nul
-echo ========================================
-echo 复制换装图片到项目目录
-echo ========================================
-echo.
-
-REM 设置源目录和目标目录
-set SOURCE_DIR=%~dp0开发\2026年2月4日\Image
-set TARGET_DIR_PHP=%~dp0xunifriend_RaeeC\public\uploads\outfit
-set TARGET_DIR_PUBLIC=%~dp0public\uploads\outfit
-
-echo [1/4] 创建目标目录...
-if not exist "%TARGET_DIR_PHP%\top" mkdir "%TARGET_DIR_PHP%\top"
-if not exist "%TARGET_DIR_PHP%\bottom" mkdir "%TARGET_DIR_PHP%\bottom"
-if not exist "%TARGET_DIR_PHP%\dress" mkdir "%TARGET_DIR_PHP%\dress"
-
-if not exist "%TARGET_DIR_PUBLIC%\top" mkdir "%TARGET_DIR_PUBLIC%\top"
-if not exist "%TARGET_DIR_PUBLIC%\bottom" mkdir "%TARGET_DIR_PUBLIC%\bottom"
-if not exist "%TARGET_DIR_PUBLIC%\dress" mkdir "%TARGET_DIR_PUBLIC%\dress"
-
-echo 目录创建完成
-echo.
-
-echo [2/4] 复制上装图片...
-copy "%SOURCE_DIR%\女性上衣白色T桖.png" "%TARGET_DIR_PHP%\top\white_tshirt.png"
-copy "%SOURCE_DIR%\女性-上装-粉丝短袖.png" "%TARGET_DIR_PHP%\top\pink_short_sleeve.png"
-copy "%SOURCE_DIR%\女性-上装-蓝色衬衫.png" "%TARGET_DIR_PHP%\top\blue_shirt.png"
-copy "%SOURCE_DIR%\女性-上装-灰色卫衣.png" "%TARGET_DIR_PHP%\top\gray_sweatshirt.png"
-copy "%SOURCE_DIR%\女性-上装-收费-蕾丝吊带上衣.png" "%TARGET_DIR_PHP%\top\lace_strap.png"
-copy "%SOURCE_DIR%\女性-上装-收费-一字领露肩上衣.png" "%TARGET_DIR_PHP%\top\off_shoulder.png"
-copy "%SOURCE_DIR%\女性-上装-收费-露脐短袖.png" "%TARGET_DIR_PHP%\top\crop_top.png"
-copy "%SOURCE_DIR%\女性-上装-收费-针织开衫.png" "%TARGET_DIR_PHP%\top\knit_cardigan.png"
-copy "%SOURCE_DIR%\女性-上装-收费-小香风外套.png" "%TARGET_DIR_PHP%\top\tweed_jacket.png"
-copy "%SOURCE_DIR%\女性-上装-vlp专属-真丝衬衫.png" "%TARGET_DIR_PHP%\top\silk_shirt.png"
-
-copy "%SOURCE_DIR%\女性上衣白色T桖.png" "%TARGET_DIR_PUBLIC%\top\white_tshirt.png"
-copy "%SOURCE_DIR%\女性-上装-粉丝短袖.png" "%TARGET_DIR_PUBLIC%\top\pink_short_sleeve.png"
-copy "%SOURCE_DIR%\女性-上装-蓝色衬衫.png" "%TARGET_DIR_PUBLIC%\top\blue_shirt.png"
-copy "%SOURCE_DIR%\女性-上装-灰色卫衣.png" "%TARGET_DIR_PUBLIC%\top\gray_sweatshirt.png"
-copy "%SOURCE_DIR%\女性-上装-收费-蕾丝吊带上衣.png" "%TARGET_DIR_PUBLIC%\top\lace_strap.png"
-copy "%SOURCE_DIR%\女性-上装-收费-一字领露肩上衣.png" "%TARGET_DIR_PUBLIC%\top\off_shoulder.png"
-copy "%SOURCE_DIR%\女性-上装-收费-露脐短袖.png" "%TARGET_DIR_PUBLIC%\top\crop_top.png"
-copy "%SOURCE_DIR%\女性-上装-收费-针织开衫.png" "%TARGET_DIR_PUBLIC%\top\knit_cardigan.png"
-copy "%SOURCE_DIR%\女性-上装-收费-小香风外套.png" "%TARGET_DIR_PUBLIC%\top\tweed_jacket.png"
-copy "%SOURCE_DIR%\女性-上装-vlp专属-真丝衬衫.png" "%TARGET_DIR_PUBLIC%\top\silk_shirt.png"
-
-echo 上装图片复制完成
-echo.
-
-echo [3/4] 复制下装图片...
-copy "%SOURCE_DIR%\女性-下衣-蓝色牛仔裤.png" "%TARGET_DIR_PHP%\bottom\blue_jeans.png"
-copy "%SOURCE_DIR%\女性-下衣-黑色短裙.png" "%TARGET_DIR_PHP%\bottom\black_skirt.png"
-copy "%SOURCE_DIR%\女性-下衣-白色短裙.png" "%TARGET_DIR_PHP%\bottom\white_shorts.png"
-copy "%SOURCE_DIR%\女性-下衣-灰色运动裤.png" "%TARGET_DIR_PHP%\bottom\gray_sweatpants.png"
-copy "%SOURCE_DIR%\女性-下衣-A字半身裙.png" "%TARGET_DIR_PHP%\bottom\a_line_skirt.png"
-copy "%SOURCE_DIR%\女性-下衣-高腰阔腿裤.png" "%TARGET_DIR_PHP%\bottom\high_waist_pants.png"
-copy "%SOURCE_DIR%\女性-下衣-收费-百褶短裙.png" "%TARGET_DIR_PHP%\bottom\pleated_skirt.png"
-copy "%SOURCE_DIR%\女性-下衣-收费-破洞牛仔裤.png" "%TARGET_DIR_PHP%\bottom\ripped_jeans.png"
-copy "%SOURCE_DIR%\女性-下衣-收费-西装裤.png" "%TARGET_DIR_PHP%\bottom\suit_pants.png"
-
-copy "%SOURCE_DIR%\女性-下衣-蓝色牛仔裤.png" "%TARGET_DIR_PUBLIC%\bottom\blue_jeans.png"
-copy "%SOURCE_DIR%\女性-下衣-黑色短裙.png" "%TARGET_DIR_PUBLIC%\bottom\black_skirt.png"
-copy "%SOURCE_DIR%\女性-下衣-白色短裙.png" "%TARGET_DIR_PUBLIC%\bottom\white_shorts.png"
-copy "%SOURCE_DIR%\女性-下衣-灰色运动裤.png" "%TARGET_DIR_PUBLIC%\bottom\gray_sweatpants.png"
-copy "%SOURCE_DIR%\女性-下衣-A字半身裙.png" "%TARGET_DIR_PUBLIC%\bottom\a_line_skirt.png"
-copy "%SOURCE_DIR%\女性-下衣-高腰阔腿裤.png" "%TARGET_DIR_PUBLIC%\bottom\high_waist_pants.png"
-copy "%SOURCE_DIR%\女性-下衣-收费-百褶短裙.png" "%TARGET_DIR_PUBLIC%\bottom\pleated_skirt.png"
-copy "%SOURCE_DIR%\女性-下衣-收费-破洞牛仔裤.png" "%TARGET_DIR_PUBLIC%\bottom\ripped_jeans.png"
-copy "%SOURCE_DIR%\女性-下衣-收费-西装裤.png" "%TARGET_DIR_PUBLIC%\bottom\suit_pants.png"
-
-echo 下装图片复制完成
-echo.
-
-echo [4/4] 复制连衣裙图片...
-copy "%SOURCE_DIR%\女性-连衣裙-白色连衣裙.png" "%TARGET_DIR_PHP%\dress\white_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-碎花连衣裙.png" "%TARGET_DIR_PHP%\dress\floral_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-黑色小礼服.png" "%TARGET_DIR_PHP%\dress\black_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-优雅长裙.png" "%TARGET_DIR_PHP%\dress\elegant_long_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-吊带连衣裙.png" "%TARGET_DIR_PHP%\dress\strapless_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-JK制服.png" "%TARGET_DIR_PHP%\dress\jk_uniform.png"
-copy "%SOURCE_DIR%\女性-连衣裙-汉服.png" "%TARGET_DIR_PHP%\dress\hanfu.png"
-copy "%SOURCE_DIR%\女性-连衣裙-洛丽塔.png" "%TARGET_DIR_PHP%\dress\lolita.png"
-copy "%SOURCE_DIR%\女性-连衣裙-圣诞服装.png" "%TARGET_DIR_PHP%\dress\christmas_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-高级定制婚纱.png" "%TARGET_DIR_PHP%\dress\custom_wedding_dress.png"
-
-copy "%SOURCE_DIR%\女性-连衣裙-白色连衣裙.png" "%TARGET_DIR_PUBLIC%\dress\white_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-碎花连衣裙.png" "%TARGET_DIR_PUBLIC%\dress\floral_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-黑色小礼服.png" "%TARGET_DIR_PUBLIC%\dress\black_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-优雅长裙.png" "%TARGET_DIR_PUBLIC%\dress\elegant_long_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-吊带连衣裙.png" "%TARGET_DIR_PUBLIC%\dress\strapless_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-JK制服.png" "%TARGET_DIR_PUBLIC%\dress\jk_uniform.png"
-copy "%SOURCE_DIR%\女性-连衣裙-汉服.png" "%TARGET_DIR_PUBLIC%\dress\hanfu.png"
-copy "%SOURCE_DIR%\女性-连衣裙-洛丽塔.png" "%TARGET_DIR_PUBLIC%\dress\lolita.png"
-copy "%SOURCE_DIR%\女性-连衣裙-圣诞服装.png" "%TARGET_DIR_PUBLIC%\dress\christmas_dress.png"
-copy "%SOURCE_DIR%\女性-连衣裙-高级定制婚纱.png" "%TARGET_DIR_PUBLIC%\dress\custom_wedding_dress.png"
-
-echo 连衣裙图片复制完成
-echo.
-
-echo ========================================
-echo 图片复制完成!
-echo ========================================
-echo.
-echo 图片已复制到:
-echo 1. %TARGET_DIR_PHP%
-echo 2. %TARGET_DIR_PUBLIC%
-echo.
-echo 下一步:执行 SQL 文件导入数据库
-echo 文件位置:开发\2026年2月4日\数据填充_换装种类_本地图片.sql
-echo.
-pause
diff --git a/deploy_music_library.bat b/deploy_music_library.bat
deleted file mode 100644
index c8a3436..0000000
--- a/deploy_music_library.bat
+++ /dev/null
@@ -1,48 +0,0 @@
-@echo off
-chcp 65001 >nul
-echo ========================================
-echo 音乐库外部链接功能部署脚本
-echo ========================================
-echo.
-
-echo [1/4] 执行数据库修改...
-echo.
-echo 请手动执行以下命令:
-echo mysql -u root -p fastadmin ^< "开发/2026年2月4日/音乐库外部链接功能.sql"
-echo.
-pause
-
-echo.
-echo [2/4] 导入音乐数据...
-echo.
-echo 请手动执行以下命令:
-echo mysql -u root -p fastadmin ^< "开发/2026年2月4日/音乐库初始数据.sql"
-echo.
-pause
-
-echo.
-echo [3/4] 检查 Python 后端...
-echo.
-cd lover
-python -c "import sys; print(f'Python 版本: {sys.version}')"
-python -c "from models import MusicLibrary; print('✅ 模型导入成功')"
-python -c "from routers.music_library import router; print('✅ 路由导入成功')"
-cd ..
-echo.
-pause
-
-echo.
-echo [4/4] 部署完成!
-echo.
-echo 下一步操作:
-echo 1. 重启 Python 后端服务
-echo cd lover
-echo python -m uvicorn main:app --host 0.0.0.0 --port 30101 --reload
-echo.
-echo 2. 测试 API
-echo python test_external_music_api.py
-echo.
-echo 3. 查看文档
-echo 开发/2026年2月4日/音乐库外部链接使用指南.md
-echo.
-pause
diff --git a/lover/config.py b/lover/config.py
index f476801..19b8f4f 100644
--- a/lover/config.py
+++ b/lover/config.py
@@ -150,7 +150,7 @@ class Settings(BaseSettings):
# 用户信息拉取接口(FastAdmin 提供)
USER_INFO_API: str = Field(
- default="http://192.168.1.164:30100/api/user_basic/get_user_basic",
+ default="http://127.0.0.1:30100/api/user_basic/get_user_basic",
env="USER_INFO_API",
)
diff --git a/lover/deps.py b/lover/deps.py
index 14c5ecd..2e47c88 100644
--- a/lover/deps.py
+++ b/lover/deps.py
@@ -20,8 +20,13 @@ def _fetch_user_from_php(token: str) -> Optional[dict]:
import logging
logger = logging.getLogger(__name__)
- # 临时硬编码修复配置问题
- user_info_api = "http://192.168.1.164:30100/api/user_basic/get_user_basic"
+ # 从配置读取 PHP 服务地址,如果配置不可用则使用本地地址
+ try:
+ from lover.config import settings
+ user_info_api = settings.USER_INFO_API
+ except:
+ # 默认使用本地地址
+ user_info_api = "http://127.0.0.1:30100/api/user_basic/get_user_basic"
logger.info(f"用户中心调试 - 调用接口: {user_info_api}")
logger.info(f"用户中心调试 - token: {token}")
@@ -30,11 +35,23 @@ def _fetch_user_from_php(token: str) -> Optional[dict]:
resp = requests.get(
user_info_api,
headers={"token": token},
- timeout=5,
+ timeout=3, # 减少超时时间到3秒
)
logger.info(f"用户中心调试 - 响应状态码: {resp.status_code}")
logger.info(f"用户中心调试 - 响应内容: {resp.text[:200]}...")
- except Exception as exc: # 网络/超时
+ except requests.exceptions.Timeout:
+ logger.error(f"用户中心调试 - 请求超时(3秒)")
+ raise HTTPException(
+ status_code=status.HTTP_502_BAD_GATEWAY,
+ detail="用户中心接口超时",
+ )
+ except requests.exceptions.ConnectionError as exc:
+ logger.error(f"用户中心调试 - 连接失败: {exc}")
+ raise HTTPException(
+ status_code=status.HTTP_502_BAD_GATEWAY,
+ detail="无法连接到用户中心",
+ )
+ except Exception as exc: # 其他异常
logger.error(f"用户中心调试 - 请求异常: {exc}")
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
diff --git a/start_php_service_30100.bat b/start_php_service_30100.bat
deleted file mode 100644
index b1d38b6..0000000
--- a/start_php_service_30100.bat
+++ /dev/null
@@ -1,18 +0,0 @@
-@echo off
-echo ========================================
-echo 启动 PHP 服务 (端口 30100)
-echo ========================================
-echo.
-
-cd xunifriend_RaeeC\public
-echo 当前目录: %CD%
-echo.
-echo 启动 PHP 内置服务器...
-echo 地址: http://0.0.0.0:30100
-echo.
-echo 按 Ctrl+C 停止服务
-echo.
-
-php -S 0.0.0.0:30100
-
-pause
diff --git a/test_external_music_api.py b/test_external_music_api.py
deleted file mode 100644
index 4d6e704..0000000
--- a/test_external_music_api.py
+++ /dev/null
@@ -1,246 +0,0 @@
-"""
-测试音乐库外部链接 API
-"""
-import requests
-import json
-
-# 配置
-BASE_URL = "http://localhost:30101"
-# 需要替换为实际的 token
-TOKEN = "your_token_here"
-
-headers = {
- "Authorization": f"Bearer {TOKEN}",
- "Content-Type": "application/json"
-}
-
-
-def test_add_netease_music():
- """测试添加网易云音乐"""
- print("\n=== 测试添加网易云音乐 ===")
-
- data = {
- "title": "七里香",
- "artist": "周杰伦",
- "platform": "netease",
- "external_id": "186016",
- "external_url": "https://music.163.com/#/song?id=186016",
- "cover_url": "https://p1.music.126.net/P1ciTpERjRdYqk1v7MD05w==/109951163076136658.jpg",
- "duration": 300
- }
-
- try:
- response = requests.post(
- f"{BASE_URL}/music/add-external",
- headers=headers,
- json=data
- )
- print(f"状态码: {response.status_code}")
- print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
- return response.json()
- except Exception as e:
- print(f"错误: {e}")
- return None
-
-
-def test_add_qq_music():
- """测试添加 QQ 音乐"""
- print("\n=== 测试添加 QQ 音乐 ===")
-
- data = {
- "title": "稻香",
- "artist": "周杰伦",
- "platform": "qq",
- "external_id": "003aAYrm3GE0Ac",
- "external_url": "https://y.qq.com/n/ryqq/songDetail/003aAYrm3GE0Ac",
- "cover_url": "https://y.gtimg.cn/music/photo_new/T002R300x300M000003Nz2So3XXYek.jpg",
- "duration": 223
- }
-
- try:
- response = requests.post(
- f"{BASE_URL}/music/add-external",
- headers=headers,
- json=data
- )
- print(f"状态码: {response.status_code}")
- print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
- return response.json()
- except Exception as e:
- print(f"错误: {e}")
- return None
-
-
-def test_add_kugou_music():
- """测试添加酷狗音乐"""
- print("\n=== 测试添加酷狗音乐 ===")
-
- data = {
- "title": "青花瓷",
- "artist": "周杰伦",
- "platform": "kugou",
- "external_id": "sample_id",
- "external_url": "https://www.kugou.com/song/#hash=sample",
- "cover_url": "",
- "duration": 230
- }
-
- try:
- response = requests.post(
- f"{BASE_URL}/music/add-external",
- headers=headers,
- json=data
- )
- print(f"状态码: {response.status_code}")
- print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
- return response.json()
- except Exception as e:
- print(f"错误: {e}")
- return None
-
-
-def test_get_music_library():
- """测试获取音乐库列表"""
- print("\n=== 测试获取音乐库列表 ===")
-
- try:
- response = requests.get(
- f"{BASE_URL}/music/library?page=1&page_size=10",
- headers=headers
- )
- print(f"状态码: {response.status_code}")
- result = response.json()
- print(f"总数: {result.get('data', {}).get('total', 0)}")
-
- # 显示前 5 条
- music_list = result.get('data', {}).get('list', [])
- print(f"\n前 {min(5, len(music_list))} 首音乐:")
- for i, music in enumerate(music_list[:5], 1):
- print(f"\n{i}. {music.get('title')} - {music.get('artist')}")
- print(f" 类型: {music.get('upload_type')}")
- if music.get('external_platform'):
- print(f" 平台: {music.get('external_platform')}")
- print(f" 链接: {music.get('external_url')}")
- else:
- print(f" 链接: {music.get('music_url')[:50]}...")
-
- return result
- except Exception as e:
- print(f"错误: {e}")
- return None
-
-
-def test_get_my_music():
- """测试获取我的音乐"""
- print("\n=== 测试获取我的音乐 ===")
-
- try:
- response = requests.get(
- f"{BASE_URL}/music/my?page=1&page_size=10",
- headers=headers
- )
- print(f"状态码: {response.status_code}")
- result = response.json()
- print(f"总数: {result.get('data', {}).get('total', 0)}")
-
- # 按类型统计
- music_list = result.get('data', {}).get('list', [])
- type_count = {}
- for music in music_list:
- upload_type = music.get('upload_type')
- type_count[upload_type] = type_count.get(upload_type, 0) + 1
-
- print("\n按类型统计:")
- for upload_type, count in type_count.items():
- print(f" {upload_type}: {count} 首")
-
- return result
- except Exception as e:
- print(f"错误: {e}")
- return None
-
-
-def test_invalid_platform():
- """测试无效的平台"""
- print("\n=== 测试无效的平台 ===")
-
- data = {
- "title": "测试歌曲",
- "artist": "测试歌手",
- "platform": "invalid_platform",
- "external_id": "123",
- "external_url": "https://example.com/song/123",
- "cover_url": "",
- "duration": 200
- }
-
- try:
- response = requests.post(
- f"{BASE_URL}/music/add-external",
- headers=headers,
- json=data
- )
- print(f"状态码: {response.status_code}")
- print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
- return response.json()
- except Exception as e:
- print(f"错误: {e}")
- return None
-
-
-def main():
- """主函数"""
- print("=" * 60)
- print("音乐库外部链接 API 测试")
- print("=" * 60)
-
- # 检查 token
- if TOKEN == "your_token_here":
- print("\n⚠️ 警告: 请先设置有效的 TOKEN")
- print(" 1. 登录应用获取 token")
- print(" 2. 修改此脚本中的 TOKEN 变量")
- print(" 3. 重新运行测试")
- return
-
- # 运行测试
- tests = [
- ("添加网易云音乐", test_add_netease_music),
- ("添加 QQ 音乐", test_add_qq_music),
- ("添加酷狗音乐", test_add_kugou_music),
- ("获取音乐库列表", test_get_music_library),
- ("获取我的音乐", test_get_my_music),
- ("测试无效平台", test_invalid_platform),
- ]
-
- results = []
- for name, test_func in tests:
- try:
- result = test_func()
- success = result and result.get('code') == 200
- results.append((name, success))
- except Exception as e:
- print(f"\n测试 {name} 时出错: {e}")
- results.append((name, False))
-
- # 显示测试结果
- print("\n" + "=" * 60)
- print("测试结果汇总")
- print("=" * 60)
-
- for name, success in results:
- status = "✅ 通过" if success else "❌ 失败"
- print(f"{status} - {name}")
-
- # 统计
- passed = sum(1 for _, success in results if success)
- total = len(results)
- print(f"\n总计: {passed}/{total} 通过")
-
- if passed == total:
- print("\n🎉 所有测试通过!")
- else:
- print(f"\n⚠️ 有 {total - passed} 个测试失败")
-
-
-if __name__ == "__main__":
- main()
diff --git a/test_invite_code.py b/test_invite_code.py
deleted file mode 100644
index 468bf3a..0000000
--- a/test_invite_code.py
+++ /dev/null
@@ -1,123 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-测试邀请码功能
-"""
-
-import requests
-
-# 配置
-BASE_URL = "http://localhost:30101"
-TOKEN_USER1 = "3932cd35-4238-4c3f-ad5c-ad6ce9454e79" # 用户1的 token(邀请人)
-TOKEN_USER2 = "test-token-user2" # 用户2的 token(被邀请人,需要替换为真实 token)
-
-def test_get_invite_info(token):
- """测试获取邀请信息"""
- url = f"{BASE_URL}/config/invite/info"
- headers = {
- "Authorization": f"Bearer {token}"
- }
-
- print(f"\n=== 测试获取邀请信息 ===")
- print(f"请求 URL: {url}")
-
- response = requests.get(url, headers=headers)
-
- print(f"状态码: {response.status_code}")
- print(f"响应内容: {response.json()}")
-
- if response.status_code == 200:
- data = response.json()
- if data.get("code") == 1:
- invite_code = data.get("data", {}).get("invite_code")
- print(f"\n✅ 邀请码: {invite_code}")
- return invite_code
- else:
- print(f"\n❌ 获取失败: {data.get('message')}")
- else:
- print(f"\n❌ HTTP 错误: {response.status_code}")
-
- return None
-
-def test_apply_invite_code(token, invite_code):
- """测试使用邀请码"""
- url = f"{BASE_URL}/config/invite/apply"
- headers = {
- "Authorization": f"Bearer {token}",
- "Content-Type": "application/json"
- }
- data = {
- "invite_code": invite_code
- }
-
- print(f"\n=== 测试使用邀请码 ===")
- print(f"请求 URL: {url}")
- print(f"邀请码: {invite_code}")
-
- response = requests.post(url, headers=headers, json=data)
-
- print(f"状态码: {response.status_code}")
- print(f"响应内容: {response.json()}")
-
- if response.status_code == 200:
- data = response.json()
- if data.get("code") == 1:
- print(f"\n✅ 使用成功: {data.get('data', {}).get('message')}")
- print(f"奖励: {data.get('data', {}).get('reward')} 金币")
- print(f"余额: {data.get('data', {}).get('balance')} 金币")
- else:
- print(f"\n❌ 使用失败: {data.get('message')}")
- else:
- print(f"\n❌ HTTP 错误: {response.status_code}")
- print(f"错误详情: {response.text}")
-
-def test_check_user_info(token):
- """测试检查用户信息"""
- url = f"{BASE_URL}/user/basic"
- headers = {
- "Authorization": f"Bearer {token}"
- }
-
- print(f"\n=== 测试获取用户信息 ===")
- print(f"请求 URL: {url}")
-
- response = requests.get(url, headers=headers)
-
- print(f"状态码: {response.status_code}")
- if response.status_code == 200:
- data = response.json()
- print(f"响应内容: {data}")
- if data.get("code") == 1:
- user_data = data.get("data", {})
- print(f"\n✅ 用户ID: {user_data.get('user_id')}")
- print(f"金币余额: {user_data.get('money')}")
- print(f"邀请码: {user_data.get('invite_code')}")
- print(f"被邀请码: {user_data.get('invited_by')}")
- else:
- print(f"\n❌ HTTP 错误: {response.status_code}")
-
-if __name__ == "__main__":
- print("=" * 60)
- print("邀请码功能测试")
- print("=" * 60)
-
- # 测试1:获取用户1的邀请码
- print("\n【步骤1】获取用户1(邀请人)的邀请码")
- invite_code = test_get_invite_info(TOKEN_USER1)
-
- if invite_code:
- print(f"\n【步骤2】用户1的邀请码是: {invite_code}")
- print("\n提示:请使用另一个用户的 token 来测试使用邀请码")
- print(f"修改脚本中的 TOKEN_USER2 变量,然后取消注释下面的代码")
-
- # 取消注释下面的代码来测试使用邀请码
- # print("\n【步骤3】用户2使用邀请码")
- # test_apply_invite_code(TOKEN_USER2, invite_code)
-
- # print("\n【步骤4】检查用户1的信息(应该增加了邀请计数和奖励)")
- # test_check_user_info(TOKEN_USER1)
-
- # print("\n【步骤5】检查用户2的信息(应该有 invited_by 字段)")
- # test_check_user_info(TOKEN_USER2)
-
- print("\n" + "=" * 60)
diff --git a/test_music_library_sing.py b/test_music_library_sing.py
deleted file mode 100644
index 3e78643..0000000
--- a/test_music_library_sing.py
+++ /dev/null
@@ -1,230 +0,0 @@
-"""
-测试音乐库唱歌视频功能
-"""
-import requests
-import json
-import time
-
-# 配置
-BASE_URL = "http://localhost:30101"
-# 需要替换为实际的 token
-TOKEN = "your_token_here"
-
-headers = {
- "Authorization": f"Bearer {TOKEN}",
- "Content-Type": "application/json"
-}
-
-
-def test_convert_music_to_song():
- """测试转换音乐为系统歌曲"""
- print("\n=== 测试转换音乐为系统歌曲 ===")
-
- # 测试直链音乐(Bensound)
- response = requests.post(
- f"{BASE_URL}/music/convert-to-song",
- headers=headers,
- params={"music_id": 1}
- )
- print(f"状态码: {response.status_code}")
- print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
-
- if response.status_code == 200:
- data = response.json()
- if data.get("code") == 1:
- song_id = data["data"]["song_id"]
- print(f"✅ 转换成功,song_id: {song_id}")
- return song_id
- else:
- print(f"❌ 转换失败: {data.get('message')}")
- return None
- else:
- print(f"❌ 请求失败")
- return None
-
-
-def test_convert_external_music():
- """测试转换外部链接音乐(应该失败)"""
- print("\n=== 测试转换外部链接音乐(应该失败) ===")
-
- response = requests.post(
- f"{BASE_URL}/music/convert-to-song",
- headers=headers,
- params={"music_id": 31} # 假设 31 是网易云音乐
- )
- print(f"状态码: {response.status_code}")
- print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
-
- if response.status_code == 200:
- data = response.json()
- if data.get("code") != 1:
- print(f"✅ 正确拒绝外部链接音乐")
- else:
- print(f"❌ 不应该允许转换外部链接音乐")
- else:
- print(f"✅ 正确拒绝外部链接音乐")
-
-
-def test_generate_sing_video(song_id):
- """测试生成唱歌视频"""
- print("\n=== 测试生成唱歌视频 ===")
-
- if not song_id:
- print("❌ 没有 song_id,跳过测试")
- return None
-
- response = requests.post(
- f"{BASE_URL}/sing/generate",
- headers=headers,
- json={"song_id": song_id}
- )
- print(f"状态码: {response.status_code}")
- print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
-
- if response.status_code == 200:
- data = response.json()
- if data.get("code") == 1:
- task_data = data["data"]
- task_id = task_data.get("generation_task_id")
- status = task_data.get("status")
-
- print(f"✅ 任务创建成功")
- print(f" 任务ID: {task_id}")
- print(f" 状态: {status}")
-
- if status == "succeeded":
- print(f" 视频URL: {task_data.get('video_url')}")
- print(f" ✅ 立即成功(有缓存)")
- else:
- print(f" ⏳ 生成中...")
-
- return task_id
- else:
- print(f"❌ 生成失败: {data.get('message')}")
- return None
- else:
- print(f"❌ 请求失败")
- return None
-
-
-def test_check_task_status(task_id):
- """测试查询任务状态"""
- print("\n=== 测试查询任务状态 ===")
-
- if not task_id:
- print("❌ 没有 task_id,跳过测试")
- return
-
- response = requests.get(
- f"{BASE_URL}/sing/task/{task_id}",
- headers=headers
- )
- print(f"状态码: {response.status_code}")
- print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
-
- if response.status_code == 200:
- data = response.json()
- if data.get("code") == 1:
- task_data = data["data"]
- status = task_data.get("status")
- print(f"✅ 任务状态: {status}")
-
- if status == "succeeded":
- print(f" 视频URL: {task_data.get('video_url')}")
- else:
- print(f"❌ 查询失败: {data.get('message')}")
- else:
- print(f"❌ 请求失败")
-
-
-def test_get_sing_history():
- """测试获取唱歌历史记录"""
- print("\n=== 测试获取唱歌历史记录 ===")
-
- response = requests.get(
- f"{BASE_URL}/sing/history",
- headers=headers,
- params={"page": 1, "size": 5}
- )
- print(f"状态码: {response.status_code}")
-
- if response.status_code == 200:
- data = response.json()
- if data.get("code") == 1:
- history_list = data.get("data", [])
- print(f"✅ 历史记录数量: {len(history_list)}")
-
- if history_list:
- print("\n最近的视频:")
- for i, item in enumerate(history_list[:3], 1):
- print(f"{i}. {item.get('song_title', '未知')} - {item.get('status')}")
- if item.get('video_url'):
- print(f" 视频: {item.get('video_url')[:50]}...")
- else:
- print(f"❌ 获取失败: {data.get('message')}")
- else:
- print(f"❌ 请求失败")
-
-
-def main():
- """主函数"""
- print("=" * 60)
- print("音乐库唱歌视频功能测试")
- print("=" * 60)
-
- # 检查 token
- if TOKEN == "your_token_here":
- print("\n⚠️ 警告: 请先设置有效的 TOKEN")
- print(" 1. 登录应用获取 token")
- print(" 2. 修改此脚本中的 TOKEN 变量")
- print(" 3. 重新运行测试")
- return
-
- # 运行测试
- results = []
-
- # 测试 1: 转换音乐
- song_id = test_convert_music_to_song()
- results.append(("转换音乐", song_id is not None))
-
- # 测试 2: 转换外部链接(应该失败)
- test_convert_external_music()
- results.append(("拒绝外部链接", True)) # 手动判断
-
- # 测试 3: 生成视频
- if song_id:
- task_id = test_generate_sing_video(song_id)
- results.append(("生成视频", task_id is not None))
-
- # 测试 4: 查询任务状态
- if task_id:
- time.sleep(2) # 等待 2 秒
- test_check_task_status(task_id)
- results.append(("查询任务", True))
-
- # 测试 5: 获取历史记录
- test_get_sing_history()
- results.append(("获取历史", True))
-
- # 显示测试结果
- print("\n" + "=" * 60)
- print("测试结果汇总")
- print("=" * 60)
-
- for name, success in results:
- status = "✅ 通过" if success else "❌ 失败"
- print(f"{status} - {name}")
-
- # 统计
- passed = sum(1 for _, success in results if success)
- total = len(results)
- print(f"\n总计: {passed}/{total} 通过")
-
- if passed == total:
- print("\n🎉 所有测试通过!")
- else:
- print(f"\n⚠️ 有 {total - passed} 个测试失败")
-
-
-if __name__ == "__main__":
- main()
diff --git a/test_music_links.py b/test_music_links.py
deleted file mode 100644
index c21dbd0..0000000
--- a/test_music_links.py
+++ /dev/null
@@ -1,131 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-测试音乐链接是否可用
-"""
-import requests
-from concurrent.futures import ThreadPoolExecutor, as_completed
-
-# 音乐列表
-MUSIC_LIST = [
- {"title": "Love", "url": "https://www.bensound.com/bensound-music/bensound-love.mp3"},
- {"title": "Romantic", "url": "https://www.bensound.com/bensound-music/bensound-romantic.mp3"},
- {"title": "Piano Moment", "url": "https://www.bensound.com/bensound-music/bensound-pianomoment.mp3"},
- {"title": "Tenderness", "url": "https://www.bensound.com/bensound-music/bensound-tenderness.mp3"},
- {"title": "Sweet", "url": "https://www.bensound.com/bensound-music/bensound-sweet.mp3"},
- {"title": "A New Beginning", "url": "https://www.bensound.com/bensound-music/bensound-anewbeginning.mp3"},
- {"title": "Memories", "url": "https://www.bensound.com/bensound-music/bensound-memories.mp3"},
- {"title": "Once Again", "url": "https://www.bensound.com/bensound-music/bensound-onceagain.mp3"},
- {"title": "Slowmotion", "url": "https://www.bensound.com/bensound-music/bensound-slowmotion.mp3"},
- {"title": "Tomorrow", "url": "https://www.bensound.com/bensound-music/bensound-tomorrow.mp3"},
- {"title": "Ukulele", "url": "https://www.bensound.com/bensound-music/bensound-ukulele.mp3"},
- {"title": "Happy Rock", "url": "https://www.bensound.com/bensound-music/bensound-happyrock.mp3"},
- {"title": "Summer", "url": "https://www.bensound.com/bensound-music/bensound-summer.mp3"},
- {"title": "Sunny", "url": "https://www.bensound.com/bensound-music/bensound-sunny.mp3"},
- {"title": "Little Idea", "url": "https://www.bensound.com/bensound-music/bensound-littleidea.mp3"},
- {"title": "Cute", "url": "https://www.bensound.com/bensound-music/bensound-cute.mp3"},
- {"title": "Funny Song", "url": "https://www.bensound.com/bensound-music/bensound-funnysong.mp3"},
- {"title": "Jazzy Frenchy", "url": "https://www.bensound.com/bensound-music/bensound-jazzyfrenchy.mp3"},
- {"title": "Acoustic Breeze", "url": "https://www.bensound.com/bensound-music/bensound-acousticbreeze.mp3"},
- {"title": "Clear Day", "url": "https://www.bensound.com/bensound-music/bensound-clearday.mp3"},
- {"title": "Relaxing", "url": "https://www.bensound.com/bensound-music/bensound-relaxing.mp3"},
- {"title": "Calm", "url": "https://www.bensound.com/bensound-music/bensound-calm.mp3"},
- {"title": "November", "url": "https://www.bensound.com/bensound-music/bensound-november.mp3"},
- {"title": "Sad Day", "url": "https://www.bensound.com/bensound-music/bensound-sadday.mp3"},
- {"title": "The Lounge", "url": "https://www.bensound.com/bensound-music/bensound-thelounge.mp3"},
- {"title": "Inspire", "url": "https://www.bensound.com/bensound-music/bensound-inspire.mp3"},
- {"title": "Dreams", "url": "https://www.bensound.com/bensound-music/bensound-dreams.mp3"},
- {"title": "Perception", "url": "https://www.bensound.com/bensound-music/bensound-perception.mp3"},
- {"title": "Moose", "url": "https://www.bensound.com/bensound-music/bensound-moose.mp3"},
- {"title": "Night Owl", "url": "https://www.bensound.com/bensound-music/bensound-nightowl.mp3"},
-]
-
-
-def test_music_link(music):
- """测试单个音乐链接"""
- try:
- response = requests.head(music["url"], timeout=10, allow_redirects=True)
- if response.status_code == 200:
- size = int(response.headers.get('Content-Length', 0)) / (1024 * 1024) # MB
- return {
- "title": music["title"],
- "status": "✓ 可用",
- "size": f"{size:.2f} MB",
- "code": response.status_code
- }
- else:
- return {
- "title": music["title"],
- "status": "✗ 不可用",
- "size": "N/A",
- "code": response.status_code
- }
- except Exception as e:
- return {
- "title": music["title"],
- "status": "✗ 错误",
- "size": "N/A",
- "code": str(e)
- }
-
-
-def main():
- print("=" * 80)
- print(" 测试音乐链接可用性")
- print("=" * 80)
- print()
- print(f"总共 {len(MUSIC_LIST)} 首音乐")
- print()
-
- results = []
- available_count = 0
- unavailable_count = 0
-
- # 使用线程池并发测试
- with ThreadPoolExecutor(max_workers=10) as executor:
- futures = {executor.submit(test_music_link, music): music for music in MUSIC_LIST}
-
- for idx, future in enumerate(as_completed(futures), 1):
- result = future.result()
- results.append(result)
-
- # 实时显示进度
- status_icon = "✓" if result["status"] == "✓ 可用" else "✗"
- print(f"[{idx}/{len(MUSIC_LIST)}] {status_icon} {result['title']:<25} {result['size']:<12} (HTTP {result['code']})")
-
- if result["status"] == "✓ 可用":
- available_count += 1
- else:
- unavailable_count += 1
-
- # 统计结果
- print()
- print("=" * 80)
- print(" 测试结果")
- print("=" * 80)
- print()
- print(f"✓ 可用: {available_count} 首")
- print(f"✗ 不可用: {unavailable_count} 首")
- print(f"成功率: {available_count / len(MUSIC_LIST) * 100:.1f}%")
- print()
-
- if unavailable_count > 0:
- print("=" * 80)
- print(" 不可用的音乐")
- print("=" * 80)
- print()
- for result in results:
- if result["status"] != "✓ 可用":
- print(f"✗ {result['title']}: {result['code']}")
- print()
-
- if available_count == len(MUSIC_LIST):
- print("🎉 所有音乐链接都可用!可以安全导入数据库。")
- else:
- print("⚠️ 部分音乐链接不可用,请检查后再导入。")
-
- print()
-
-
-if __name__ == "__main__":
- main()
diff --git a/test_outfit_api.py b/test_outfit_api.py
deleted file mode 100644
index 69d1ea6..0000000
--- a/test_outfit_api.py
+++ /dev/null
@@ -1,115 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-测试换装 API 返回的图片 URL
-"""
-import requests
-import json
-
-# API 地址
-API_URL = "http://192.168.1.164:30101/outfit/mall"
-
-# 测试 token(需要替换为实际的 token)
-# 如果没有 token,可以使用 X-User-Id 调试
-headers = {
- "X-User-Id": "70" # 使用调试用户 ID
-}
-
-print("=" * 70)
-print(" 测试换装商城 API")
-print("=" * 70)
-print()
-print(f"API 地址: {API_URL}")
-print(f"请求头: {headers}")
-print()
-
-try:
- response = requests.get(API_URL, headers=headers, timeout=10)
-
- print(f"状态码: {response.status_code}")
- print()
-
- if response.status_code == 200:
- data = response.json()
-
- print("=" * 70)
- print(" API 响应")
- print("=" * 70)
- print()
- print(f"Code: {data.get('code')}")
- print(f"Message: {data.get('msg')}")
- print()
-
- if data.get('code') == 1 and 'data' in data:
- outfit_data = data['data']
- items = outfit_data.get('items', [])
-
- print(f"服装数量: {len(items)}")
- print(f"已拥有: {len(outfit_data.get('owned_outfit_ids', []))}")
- print(f"金币余额: {outfit_data.get('balance', 0)}")
- print()
-
- if items:
- print("=" * 70)
- print(" 前 5 件服装")
- print("=" * 70)
- print()
-
- for idx, item in enumerate(items[:5], 1):
- print(f"[{idx}] {item.get('name')}")
- print(f" 分类: {item.get('category')}")
- print(f" 性别: {item.get('gender')}")
- print(f" 价格: {item.get('price_gold')} 金币")
- print(f" 图片: {item.get('image_url')}")
- print()
-
- # 测试图片 URL 是否可访问
- img_url = item.get('image_url')
- if img_url:
- try:
- img_response = requests.head(img_url, timeout=5)
- if img_response.status_code == 200:
- print(f" ✓ 图片可访问")
- else:
- print(f" ✗ 图片无法访问 (状态码: {img_response.status_code})")
- except Exception as e:
- print(f" ✗ 图片访问失败: {e}")
- print()
- else:
- print("⚠️ 没有找到服装数据")
- print()
- print("可能的原因:")
- print("1. 数据库中没有数据")
- print("2. 查询条件不匹配(性别、状态等)")
- print()
- else:
- print(f"❌ API 返回错误: {data.get('msg')}")
- print()
- else:
- print(f"❌ HTTP 错误: {response.status_code}")
- print(f"响应内容: {response.text[:500]}")
- print()
-
-except requests.exceptions.Timeout:
- print("❌ 请求超时")
- print()
- print("请检查:")
- print("1. Python 服务是否运行")
- print("2. 端口 30101 是否正确")
- print()
-except requests.exceptions.ConnectionError:
- print("❌ 连接失败")
- print()
- print("请检查:")
- print("1. Python 服务是否运行")
- print("2. 地址是否正确: http://192.168.1.164:30101")
- print()
-except Exception as e:
- print(f"❌ 发生错误: {e}")
- print()
- import traceback
- traceback.print_exc()
-
-print("=" * 70)
-print(" 测试完成")
-print("=" * 70)
diff --git a/test_sing_history.py b/test_sing_history.py
deleted file mode 100644
index 8c6549b..0000000
--- a/test_sing_history.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-测试唱歌历史记录 API
-"""
-
-import requests
-
-# 配置
-BASE_URL = "http://localhost:30101"
-TOKEN = "3932cd35-4238-4c3f-ad5c-ad6ce9454e79" # 从日志中获取的 token
-
-def test_sing_history():
- """测试获取唱歌历史记录"""
- url = f"{BASE_URL}/sing/history"
- headers = {
- "Authorization": f"Bearer {TOKEN}"
- }
-
- print(f"请求 URL: {url}")
- print(f"请求头: {headers}")
-
- response = requests.get(url, headers=headers)
-
- print(f"\n状态码: {response.status_code}")
- print(f"响应内容:")
- print(response.json())
-
- if response.status_code == 200:
- data = response.json()
- if data.get("code") == 1:
- history_list = data.get("data", [])
- print(f"\n✅ 成功获取历史记录,共 {len(history_list)} 条")
- for i, item in enumerate(history_list[:5], 1):
- print(f"{i}. {item.get('song_title')} - {item.get('video_url')}")
- else:
- print(f"\n❌ API 返回错误: {data.get('message')}")
- else:
- print(f"\n❌ HTTP 错误: {response.status_code}")
-
-if __name__ == "__main__":
- test_sing_history()
diff --git a/upload_gifts_to_oss.py b/upload_gifts_to_oss.py
deleted file mode 100644
index ae5c72f..0000000
--- a/upload_gifts_to_oss.py
+++ /dev/null
@@ -1,200 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-礼物图片上传到阿里云 OSS 脚本(仅上传现有的 29 个图片)
-"""
-import os
-import sys
-from pathlib import Path
-import oss2
-from dotenv import load_dotenv
-
-# 加载环境变量
-load_dotenv()
-
-# OSS 配置
-ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID')
-ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET')
-BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME')
-ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT', 'https://oss-cn-hangzhou.aliyuncs.com')
-CDN_DOMAIN = os.getenv('ALIYUN_OSS_CDN_DOMAIN')
-
-# 图片源目录
-SOURCE_DIR = Path('开发/2026年2月4日/Gift')
-
-# 文件映射(源文件名 -> OSS 对象名)- 仅包含实际存在的 29 个文件
-FILE_MAP = {
- # 经济类礼物(10-50金币)- 10个
- "玫瑰花.png": "uploads/gifts/rose.png",
- "棒棒糖.png": "uploads/gifts/lollipop.png",
- "咖啡.png": "uploads/gifts/coffee.png",
- "冰淇淋.png": "uploads/gifts/icecream.png",
- "小蛋糕.png": "uploads/gifts/cake.png",
- "巧克力.png": "uploads/gifts/chocolate.png",
- "奶茶.png": "uploads/gifts/milktea.png",
- "爱心气球.png": "uploads/gifts/heart_balloon.png",
- "小礼物盒.png": "uploads/gifts/gift_box.png",
- "彩虹.png": "uploads/gifts/rainbow.png",
-
- # 中档礼物(50-200金币)- 9个
- "香槟.png": "uploads/gifts/champagne.png",
- "钻石.png": "uploads/gifts/diamond.png",
- "王冠.png": "uploads/gifts/crown.png",
- "爱心.png": "uploads/gifts/big_heart.png",
- "月亮.png": "uploads/gifts/moon.png",
- "烟花.png": "uploads/gifts/fireworks.png",
- "水晶球.png": "uploads/gifts/crystal_ball.png",
- "玫瑰花束.png": "uploads/gifts/rose_bouquet.png",
- "星星项链.png": "uploads/gifts/star_necklace.png",
-
- # 高级礼物(200-500金币)- 4个
- "跑车.png": "uploads/gifts/sports_car.png",
- "飞机.png": "uploads/gifts/airplane.png",
- "游艇.png": "uploads/gifts/yacht.png",
- "城堡.png": "uploads/gifts/castle.png",
-
- # 特殊礼物(500+金币)- 2个
- "宇宙飞船.png": "uploads/gifts/spaceship.png",
- "魔法棒.png": "uploads/gifts/magic_wand.png",
-
- # 节日限定礼物 - 4个
- "圣诞树.png": "uploads/gifts/christmas_tree.png",
- "情人节巧克力.png": "uploads/gifts/valentine_chocolate.png",
- "生日蛋糕.png": "uploads/gifts/birthday_cake.png",
- "万圣节南瓜.png": "uploads/gifts/halloween_pumpkin.png",
-}
-
-
-def upload_to_oss():
- """上传图片到 OSS"""
-
- # 检查配置
- if not all([ACCESS_KEY_ID, ACCESS_KEY_SECRET, BUCKET_NAME]):
- print("❌ 错误:OSS 配置不完整")
- print(f"ACCESS_KEY_ID: {'已配置' if ACCESS_KEY_ID else '未配置'}")
- print(f"ACCESS_KEY_SECRET: {'已配置' if ACCESS_KEY_SECRET else '未配置'}")
- print(f"BUCKET_NAME: {BUCKET_NAME or '未配置'}")
- return False
-
- print("=" * 70)
- print(" 礼物图片上传到阿里云 OSS")
- print("=" * 70)
- print()
- print(f"📦 OSS Bucket: {BUCKET_NAME}")
- print(f"🌐 OSS Endpoint: {ENDPOINT}")
- print(f"🚀 CDN Domain: {CDN_DOMAIN or '未配置'}")
- print(f"📁 源目录: {SOURCE_DIR}")
- print(f"📊 待上传文件数: {len(FILE_MAP)}")
- print()
-
- # 初始化 OSS
- try:
- auth = oss2.Auth(ACCESS_KEY_ID, ACCESS_KEY_SECRET)
- # 去掉 endpoint 中的 https://
- endpoint_clean = ENDPOINT.replace('https://', '').replace('http://', '')
- bucket = oss2.Bucket(auth, endpoint_clean, BUCKET_NAME)
-
- # 测试连接
- bucket.get_bucket_info()
- print("✓ OSS 连接成功")
- print()
- except Exception as e:
- print(f"❌ OSS 连接失败: {e}")
- return False
-
- # 检查源目录
- if not SOURCE_DIR.exists():
- print(f"❌ 错误:源目录不存在: {SOURCE_DIR}")
- return False
-
- # 上传文件
- uploaded_count = 0
- failed_count = 0
- skipped_count = 0
-
- print("开始上传图片...")
- print()
-
- for idx, (source_file, oss_object) in enumerate(FILE_MAP.items(), 1):
- source_path = SOURCE_DIR / source_file
-
- if not source_path.exists():
- print(f"[{idx}/{len(FILE_MAP)}] ⚠️ 跳过(文件不存在): {source_file}")
- skipped_count += 1
- continue
-
- try:
- # 读取文件
- with open(source_path, 'rb') as f:
- file_data = f.read()
-
- file_size = len(file_data) / 1024 # KB
-
- # 上传到 OSS
- bucket.put_object(oss_object, file_data)
-
- # 生成访问 URL
- if CDN_DOMAIN:
- url = f"{CDN_DOMAIN.rstrip('/')}/{oss_object}"
- else:
- url = f"https://{BUCKET_NAME}.{endpoint_clean}/{oss_object}"
-
- print(f"[{idx}/{len(FILE_MAP)}] ✓ 已上传: {source_file}")
- print(f" 大小: {file_size:.1f} KB")
- print(f" OSS: {oss_object}")
- print(f" URL: {url}")
- print()
-
- uploaded_count += 1
-
- except Exception as e:
- print(f"[{idx}/{len(FILE_MAP)}] ❌ 上传失败: {source_file}")
- print(f" 错误: {e}")
- print()
- failed_count += 1
-
- # 统计结果
- print("=" * 70)
- print(" 上传完成")
- print("=" * 70)
- print()
- print(f"✓ 成功上传: {uploaded_count} 个文件")
- if failed_count > 0:
- print(f"✗ 上传失败: {failed_count} 个文件")
- if skipped_count > 0:
- print(f"⚠ 跳过文件: {skipped_count} 个文件")
- print()
-
- if uploaded_count > 0:
- print("=" * 70)
- print(" 下一步操作")
- print("=" * 70)
- print()
- print("1️⃣ 执行 SQL 文件导入数据库")
- print(" 📄 文件:开发/2026年2月4日/数据填充_礼物种类_最终版.sql")
- print()
- print("2️⃣ 重启 Python 服务(如果需要)")
- print(" 💻 命令:python -m uvicorn lover.main:app --host 0.0.0.0 --port 30101 --reload")
- print()
- print("3️⃣ 测试前端礼物功能")
- print(" 📱 打开前端应用 → 查看礼物列表")
- print()
- print("4️⃣ 验证图片访问")
- print(f" 🌐 示例:{CDN_DOMAIN or f'https://{BUCKET_NAME}.{endpoint_clean}'}/uploads/gifts/rose.png")
- print()
-
- return uploaded_count > 0
-
-
-if __name__ == "__main__":
- try:
- success = upload_to_oss()
- sys.exit(0 if success else 1)
- except KeyboardInterrupt:
- print("\n\n⚠️ 用户中断")
- sys.exit(1)
- except Exception as e:
- print(f"\n❌ 发生错误: {e}")
- import traceback
- traceback.print_exc()
- sys.exit(1)
diff --git a/upload_outfit_to_oss.py b/upload_outfit_to_oss.py
deleted file mode 100644
index 543ade5..0000000
--- a/upload_outfit_to_oss.py
+++ /dev/null
@@ -1,181 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-换装图片上传到阿里云 OSS 脚本
-"""
-import os
-import sys
-from pathlib import Path
-import oss2
-from dotenv import load_dotenv
-
-# 加载环境变量
-load_dotenv()
-
-# OSS 配置
-ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID')
-ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET')
-BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME')
-ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT', 'https://oss-cn-hangzhou.aliyuncs.com')
-CDN_DOMAIN = os.getenv('ALIYUN_OSS_CDN_DOMAIN')
-
-# 图片源目录
-SOURCE_DIR = Path('开发/2026年2月4日/Image')
-
-# 文件映射(源文件名 -> OSS 对象名)
-FILE_MAP = {
- # 上装 - 女性
- "女性上衣白色T桖.png": "uploads/outfit/top/white_tshirt.png",
- "女性-上装-粉丝短袖.png": "uploads/outfit/top/pink_short_sleeve.png",
- "女性-上装-蓝色衬衫.png": "uploads/outfit/top/blue_shirt.png",
- "女性-上装-灰色卫衣.png": "uploads/outfit/top/gray_sweatshirt.png",
- "女性-上装-收费-蕾丝吊带上衣.png": "uploads/outfit/top/lace_strap.png",
- "女性-上装-收费-一字领露肩上衣.png": "uploads/outfit/top/off_shoulder.png",
- "女性-上装-收费-露脐短袖.png": "uploads/outfit/top/crop_top.png",
- "女性-上装-收费-针织开衫.png": "uploads/outfit/top/knit_cardigan.png",
- "女性-上装-收费-小香风外套.png": "uploads/outfit/top/tweed_jacket.png",
- "女性-上装-vlp专属-真丝衬衫.png": "uploads/outfit/top/silk_shirt.png",
-
- # 下装 - 女性
- "女性-下衣-蓝色牛仔裤.png": "uploads/outfit/bottom/blue_jeans.png",
- "女性-下衣-黑色短裙.png": "uploads/outfit/bottom/black_skirt.png",
- "女性-下衣-白色短裙.png": "uploads/outfit/bottom/white_shorts.png",
- "女性-下衣-灰色运动裤.png": "uploads/outfit/bottom/gray_sweatpants.png",
- "女性-下衣-A字半身裙.png": "uploads/outfit/bottom/a_line_skirt.png",
- "女性-下衣-高腰阔腿裤.png": "uploads/outfit/bottom/high_waist_pants.png",
- "女性-下衣-收费-百褶短裙.png": "uploads/outfit/bottom/pleated_skirt.png",
- "女性-下衣-收费-破洞牛仔裤.png": "uploads/outfit/bottom/ripped_jeans.png",
- "女性-下衣-收费-西装裤.png": "uploads/outfit/bottom/suit_pants.png",
-
- # 连衣裙 - 女性
- "女性-连衣裙-白色连衣裙.png": "uploads/outfit/dress/white_dress.png",
- "女性-连衣裙-碎花连衣裙.png": "uploads/outfit/dress/floral_dress.png",
- "女性-连衣裙-黑色小礼服.png": "uploads/outfit/dress/black_dress.png",
- "女性-连衣裙-优雅长裙.png": "uploads/outfit/dress/elegant_long_dress.png",
- "女性-连衣裙-吊带连衣裙.png": "uploads/outfit/dress/strapless_dress.png",
- "女性-连衣裙-JK制服.png": "uploads/outfit/dress/jk_uniform.png",
- "女性-连衣裙-汉服.png": "uploads/outfit/dress/hanfu.png",
- "女性-连衣裙-洛丽塔.png": "uploads/outfit/dress/lolita.png",
- "女性-连衣裙-圣诞服装.png": "uploads/outfit/dress/christmas_dress.png",
- "女性-连衣裙-高级定制婚纱.png": "uploads/outfit/dress/custom_wedding_dress.png",
-}
-
-
-def upload_to_oss():
- """上传图片到 OSS"""
-
- # 检查配置
- if not all([ACCESS_KEY_ID, ACCESS_KEY_SECRET, BUCKET_NAME]):
- print("❌ 错误:OSS 配置不完整")
- print(f"ACCESS_KEY_ID: {'已配置' if ACCESS_KEY_ID else '未配置'}")
- print(f"ACCESS_KEY_SECRET: {'已配置' if ACCESS_KEY_SECRET else '未配置'}")
- print(f"BUCKET_NAME: {BUCKET_NAME or '未配置'}")
- return False
-
- print("=" * 60)
- print(" 换装图片上传到阿里云 OSS")
- print("=" * 60)
- print()
- print(f"OSS Bucket: {BUCKET_NAME}")
- print(f"OSS Endpoint: {ENDPOINT}")
- print(f"CDN Domain: {CDN_DOMAIN or '未配置'}")
- print(f"源目录: {SOURCE_DIR}")
- print()
-
- # 初始化 OSS
- try:
- auth = oss2.Auth(ACCESS_KEY_ID, ACCESS_KEY_SECRET)
- # 去掉 endpoint 中的 https://
- endpoint_clean = ENDPOINT.replace('https://', '').replace('http://', '')
- bucket = oss2.Bucket(auth, endpoint_clean, BUCKET_NAME)
- print("✓ OSS 连接成功")
- print()
- except Exception as e:
- print(f"❌ OSS 连接失败: {e}")
- return False
-
- # 检查源目录
- if not SOURCE_DIR.exists():
- print(f"❌ 错误:源目录不存在: {SOURCE_DIR}")
- return False
-
- # 上传文件
- uploaded_count = 0
- failed_count = 0
- skipped_count = 0
-
- print("开始上传图片...")
- print()
-
- for source_file, oss_object in FILE_MAP.items():
- source_path = SOURCE_DIR / source_file
-
- if not source_path.exists():
- print(f"⚠️ 跳过(文件不存在): {source_file}")
- skipped_count += 1
- continue
-
- try:
- # 读取文件
- with open(source_path, 'rb') as f:
- file_data = f.read()
-
- # 上传到 OSS
- bucket.put_object(oss_object, file_data)
-
- # 生成访问 URL
- if CDN_DOMAIN:
- url = f"{CDN_DOMAIN.rstrip('/')}/{oss_object}"
- else:
- url = f"https://{BUCKET_NAME}.{endpoint_clean}/{oss_object}"
-
- print(f"✓ 已上传: {source_file}")
- print(f" → {oss_object}")
- print(f" → {url}")
- print()
-
- uploaded_count += 1
-
- except Exception as e:
- print(f"❌ 上传失败: {source_file}")
- print(f" 错误: {e}")
- print()
- failed_count += 1
-
- # 统计结果
- print("=" * 60)
- print(" 上传完成")
- print("=" * 60)
- print(f"成功上传: {uploaded_count} 个文件")
- print(f"上传失败: {failed_count} 个文件")
- print(f"跳过文件: {skipped_count} 个文件")
- print()
-
- if uploaded_count > 0:
- print("✓ 图片已上传到 OSS")
- print()
- print("下一步:")
- print("1. 执行 SQL 文件导入数据库")
- print(" 文件:开发/2026年2月4日/数据填充_换装种类_OSS图片.sql")
- print()
- print("2. 重启 Python 服务")
- print(" python -m uvicorn lover.main:app --host 0.0.0.0 --port 30101 --reload")
- print()
- print("3. 测试前端换装功能")
- print()
-
- return uploaded_count > 0
-
-
-if __name__ == "__main__":
- try:
- success = upload_to_oss()
- sys.exit(0 if success else 1)
- except KeyboardInterrupt:
- print("\n\n用户中断")
- sys.exit(1)
- except Exception as e:
- print(f"\n❌ 发生错误: {e}")
- import traceback
- traceback.print_exc()
- sys.exit(1)
diff --git a/upload_outfit_to_oss_final.py b/upload_outfit_to_oss_final.py
deleted file mode 100644
index c71721d..0000000
--- a/upload_outfit_to_oss_final.py
+++ /dev/null
@@ -1,196 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-换装图片上传到阿里云 OSS 脚本(最终版 - 仅上传现有的 29 个图片)
-"""
-import os
-import sys
-from pathlib import Path
-import oss2
-from dotenv import load_dotenv
-
-# 加载环境变量
-load_dotenv()
-
-# OSS 配置
-ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID')
-ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET')
-BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME')
-ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT', 'https://oss-cn-hangzhou.aliyuncs.com')
-CDN_DOMAIN = os.getenv('ALIYUN_OSS_CDN_DOMAIN')
-
-# 图片源目录
-SOURCE_DIR = Path('开发/2026年2月4日/Image')
-
-# 文件映射(仅包含实际存在的 29 个文件)
-FILE_MAP = {
- # 上装 - 女性 (10个)
- "女性上衣白色T桖.png": "uploads/outfit/top/white_tshirt.png",
- "女性-上装-粉丝短袖.png": "uploads/outfit/top/pink_short_sleeve.png",
- "女性-上装-蓝色衬衫.png": "uploads/outfit/top/blue_shirt.png",
- "女性-上装-灰色卫衣.png": "uploads/outfit/top/gray_sweatshirt.png",
- "女性-上装-收费-蕾丝吊带上衣.png": "uploads/outfit/top/lace_strap.png",
- "女性-上装-收费-一字领露肩上衣.png": "uploads/outfit/top/off_shoulder.png",
- "女性-上装-收费-露脐短袖.png": "uploads/outfit/top/crop_top.png",
- "女性-上装-收费-针织开衫.png": "uploads/outfit/top/knit_cardigan.png",
- "女性-上装-收费-小香风外套.png": "uploads/outfit/top/tweed_jacket.png",
- "女性-上装-vlp专属-真丝衬衫.png": "uploads/outfit/top/silk_shirt.png",
-
- # 下装 - 女性 (9个)
- "女性-下衣-蓝色牛仔裤.png": "uploads/outfit/bottom/blue_jeans.png",
- "女性-下衣-黑色短裙.png": "uploads/outfit/bottom/black_skirt.png",
- "女性-下衣-白色短裙.png": "uploads/outfit/bottom/white_shorts.png",
- "女性-下衣-灰色运动裤.png": "uploads/outfit/bottom/gray_sweatpants.png",
- "女性-下衣-A字半身裙.png": "uploads/outfit/bottom/a_line_skirt.png",
- "女性-下衣-高腰阔腿裤.png": "uploads/outfit/bottom/high_waist_pants.png",
- "女性-下衣-收费-百褶短裙.png": "uploads/outfit/bottom/pleated_skirt.png",
- "女性-下衣-收费-破洞牛仔裤.png": "uploads/outfit/bottom/ripped_jeans.png",
- "女性-下衣-收费-西装裤.png": "uploads/outfit/bottom/suit_pants.png",
-
- # 连衣裙 - 女性 (10个)
- "女性-连衣裙-白色连衣裙.png": "uploads/outfit/dress/white_dress.png",
- "女性-连衣裙-碎花连衣裙.png": "uploads/outfit/dress/floral_dress.png",
- "女性-连衣裙-黑色小礼服.png": "uploads/outfit/dress/black_dress.png",
- "女性-连衣裙-优雅长裙.png": "uploads/outfit/dress/elegant_long_dress.png",
- "女性-连衣裙-吊带连衣裙.png": "uploads/outfit/dress/strapless_dress.png",
- "女性-连衣裙-JK制服.png": "uploads/outfit/dress/jk_uniform.png",
- "女性-连衣裙-汉服.png": "uploads/outfit/dress/hanfu.png",
- "女性-连衣裙-洛丽塔.png": "uploads/outfit/dress/lolita.png",
- "女性-连衣裙-圣诞服装.png": "uploads/outfit/dress/christmas_dress.png",
- "女性-连衣裙-高级定制婚纱.png": "uploads/outfit/dress/custom_wedding_dress.png",
-}
-
-
-def upload_to_oss():
- """上传图片到 OSS"""
-
- # 检查配置
- if not all([ACCESS_KEY_ID, ACCESS_KEY_SECRET, BUCKET_NAME]):
- print("❌ 错误:OSS 配置不完整")
- print(f"ACCESS_KEY_ID: {'已配置' if ACCESS_KEY_ID else '未配置'}")
- print(f"ACCESS_KEY_SECRET: {'已配置' if ACCESS_KEY_SECRET else '未配置'}")
- print(f"BUCKET_NAME: {BUCKET_NAME or '未配置'}")
- return False
-
- print("=" * 70)
- print(" 换装图片上传到阿里云 OSS(最终版)")
- print("=" * 70)
- print()
- print(f"📦 OSS Bucket: {BUCKET_NAME}")
- print(f"🌐 OSS Endpoint: {ENDPOINT}")
- print(f"🚀 CDN Domain: {CDN_DOMAIN or '未配置'}")
- print(f"📁 源目录: {SOURCE_DIR}")
- print(f"📊 待上传文件数: {len(FILE_MAP)}")
- print()
-
- # 初始化 OSS
- try:
- auth = oss2.Auth(ACCESS_KEY_ID, ACCESS_KEY_SECRET)
- # 去掉 endpoint 中的 https://
- endpoint_clean = ENDPOINT.replace('https://', '').replace('http://', '')
- bucket = oss2.Bucket(auth, endpoint_clean, BUCKET_NAME)
-
- # 测试连接
- bucket.get_bucket_info()
- print("✓ OSS 连接成功")
- print()
- except Exception as e:
- print(f"❌ OSS 连接失败: {e}")
- return False
-
- # 检查源目录
- if not SOURCE_DIR.exists():
- print(f"❌ 错误:源目录不存在: {SOURCE_DIR}")
- return False
-
- # 上传文件
- uploaded_count = 0
- failed_count = 0
- skipped_count = 0
-
- print("开始上传图片...")
- print()
-
- for idx, (source_file, oss_object) in enumerate(FILE_MAP.items(), 1):
- source_path = SOURCE_DIR / source_file
-
- if not source_path.exists():
- print(f"[{idx}/{len(FILE_MAP)}] ⚠️ 跳过(文件不存在): {source_file}")
- skipped_count += 1
- continue
-
- try:
- # 读取文件
- with open(source_path, 'rb') as f:
- file_data = f.read()
-
- file_size = len(file_data) / 1024 # KB
-
- # 上传到 OSS
- bucket.put_object(oss_object, file_data)
-
- # 生成访问 URL
- if CDN_DOMAIN:
- url = f"{CDN_DOMAIN.rstrip('/')}/{oss_object}"
- else:
- url = f"https://{BUCKET_NAME}.{endpoint_clean}/{oss_object}"
-
- print(f"[{idx}/{len(FILE_MAP)}] ✓ 已上传: {source_file}")
- print(f" 大小: {file_size:.1f} KB")
- print(f" OSS: {oss_object}")
- print(f" URL: {url}")
- print()
-
- uploaded_count += 1
-
- except Exception as e:
- print(f"[{idx}/{len(FILE_MAP)}] ❌ 上传失败: {source_file}")
- print(f" 错误: {e}")
- print()
- failed_count += 1
-
- # 统计结果
- print("=" * 70)
- print(" 上传完成")
- print("=" * 70)
- print()
- print(f"✓ 成功上传: {uploaded_count} 个文件")
- if failed_count > 0:
- print(f"✗ 上传失败: {failed_count} 个文件")
- if skipped_count > 0:
- print(f"⚠ 跳过文件: {skipped_count} 个文件")
- print()
-
- if uploaded_count > 0:
- print("=" * 70)
- print(" 下一步操作")
- print("=" * 70)
- print()
- print("1️⃣ 执行 SQL 文件导入数据库")
- print(" 📄 文件:开发/2026年2月4日/数据填充_换装种类_最终版.sql")
- print()
- print("2️⃣ 重启 Python 服务")
- print(" 💻 命令:python -m uvicorn lover.main:app --host 0.0.0.0 --port 30101 --reload")
- print()
- print("3️⃣ 测试前端换装功能")
- print(" 📱 打开前端应用 → 金币商店 → 查看服装列表")
- print()
- print("4️⃣ 验证图片访问")
- print(f" 🌐 示例:{CDN_DOMAIN or f'https://{BUCKET_NAME}.{endpoint_clean}'}/uploads/outfit/top/white_tshirt.png")
- print()
-
- return uploaded_count > 0
-
-
-if __name__ == "__main__":
- try:
- success = upload_to_oss()
- sys.exit(0 if success else 1)
- except KeyboardInterrupt:
- print("\n\n⚠️ 用户中断")
- sys.exit(1)
- except Exception as e:
- print(f"\n❌ 发生错误: {e}")
- import traceback
- traceback.print_exc()
- sys.exit(1)
diff --git a/xuniYou_index_vue_patch.txt b/xuniYou_index_vue_patch.txt
deleted file mode 100644
index 6e6d474..0000000
--- a/xuniYou_index_vue_patch.txt
+++ /dev/null
@@ -1,205 +0,0 @@
-========================================
-前端代码修改补丁
-文件: xuniYou/pages/index/index.vue
-位置: 约第 2387-2420 行
-========================================
-
-找到这段代码:
-----------------------------------------
- selectMusicFromLibrary(music) {
- // 记录播放次数
- uni.request({
- url: baseURLPy + '/music/' + music.id + '/play',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- }
- });
-
- // 使用音乐库的歌曲生成视频
- if (this.singGenerating) {
- uni.showToast({ title: '视频生成中,请稍候...', icon: 'none', duration: 2000 });
- return;
- }
-
- // 这里需要调用唱歌API,传入音乐库的音乐URL
- uni.showModal({
- title: '提示',
- content: `确定让她唱《${music.title}》吗?`,
- success: (modalRes) => {
- if (modalRes.confirm) {
- // TODO: 调用唱歌API,使用 music.music_url
- uni.showToast({ title: '功能开发中...', icon: 'none' });
- }
- }
- });
- },
-
- // 切换音乐点赞
- toggleMusicLike(music) {
-----------------------------------------
-
-替换为:
-----------------------------------------
- selectMusicFromLibrary(music) {
- // 记录播放次数
- uni.request({
- url: baseURLPy + '/music/' + music.id + '/play',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- }
- });
-
- // 使用音乐库的歌曲生成视频
- if (this.singGenerating) {
- uni.showToast({ title: '视频生成中,请稍候...', icon: 'none', duration: 2000 });
- return;
- }
-
- // 检查音乐类型
- if (music.upload_type === 'external') {
- uni.showModal({
- title: '提示',
- content: '外部平台音乐无法生成视频,请使用直链或上传的音乐',
- showCancel: false
- });
- return;
- }
-
- // 确认生成
- uni.showModal({
- title: '生成唱歌视频',
- content: `确定让她唱《${music.title}》吗?`,
- success: (modalRes) => {
- if (modalRes.confirm) {
- this.generateSingVideoFromLibrary(music);
- }
- }
- });
- },
-
- // 生成唱歌视频(从音乐库)
- generateSingVideoFromLibrary(music) {
- const that = this;
-
- // 显示加载
- uni.showLoading({ title: '准备中...' });
-
- // 第 1 步:转换为系统歌曲
- uni.request({
- url: baseURLPy + '/music/convert-to-song',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- },
- data: {
- music_id: music.id
- },
- success: (res) => {
- if (res.data && res.data.code === 1) {
- const songId = res.data.data.song_id;
-
- // 第 2 步:调用现有的唱歌生成 API
- that.generateSingVideoWithSongId(songId, music.title);
- } else {
- uni.hideLoading();
- uni.showModal({
- title: '转换失败',
- content: res.data.message || '未知错误',
- showCancel: false
- });
- }
- },
- fail: (err) => {
- uni.hideLoading();
- console.error('转换失败:', err);
- uni.showModal({
- title: '转换失败',
- content: '网络错误,请重试',
- showCancel: false
- });
- }
- });
- },
-
- // 使用 song_id 生成唱歌视频
- generateSingVideoWithSongId(songId, songTitle) {
- const that = this;
-
- uni.showLoading({ title: '生成中...' });
-
- uni.request({
- url: baseURLPy + '/sing/generate',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- },
- data: {
- song_id: songId
- },
- success: (res) => {
- uni.hideLoading();
-
- if (res.data && res.data.code === 1) {
- const data = res.data.data;
-
- if (data.status === 'succeeded') {
- // 立即成功(有缓存)
- uni.showToast({
- title: '生成成功',
- icon: 'success'
- });
-
- // 刷新历史记录
- that.getSingHistory();
-
- // 切换到历史记录 tab
- that.switchSingTab('history');
-
- // 播放视频
- if (data.video_url) {
- that.openVideoPlayer(data.video_url);
- }
- } else {
- // 生成中
- that.singGenerating = true;
- that.singGeneratingTaskId = data.generation_task_id;
-
- uni.showToast({
- title: '视频生成中...',
- icon: 'loading',
- duration: 2000
- });
-
- // 开始轮询
- that.getSingGenerateTask(data.generation_task_id);
-
- // 切换到历史记录 tab
- that.switchSingTab('history');
- }
- } else {
- uni.showModal({
- title: '生成失败',
- content: res.data.message || '未知错误',
- showCancel: false
- });
- }
- },
- fail: (err) => {
- uni.hideLoading();
- console.error('生成失败:', err);
- uni.showModal({
- title: '生成失败',
- content: '网络错误,请重试',
- showCancel: false
- });
- }
- });
- },
-
- // 切换音乐点赞
- toggleMusicLike(music) {
-----------------------------------------
-
-保存文件后,重新编译前端即可。
diff --git a/xuniYou_pages_index_index_vue_修改后的代码.txt b/xuniYou_pages_index_index_vue_修改后的代码.txt
deleted file mode 100644
index 4679315..0000000
--- a/xuniYou_pages_index_index_vue_修改后的代码.txt
+++ /dev/null
@@ -1,170 +0,0 @@
-// ========================================
-// 在 xuniYou/pages/index/index.vue 文件中
-// 找到第 2387 行的 selectMusicFromLibrary 方法
-// 完整替换为以下代码(包括后面的两个新方法)
-// ========================================
-
-selectMusicFromLibrary(music) {
- // 记录播放次数
- uni.request({
- url: baseURLPy + '/music/' + music.id + '/play',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- }
- });
-
- // 使用音乐库的歌曲生成视频
- if (this.singGenerating) {
- uni.showToast({ title: '视频生成中,请稍候...', icon: 'none', duration: 2000 });
- return;
- }
-
- // 检查音乐类型
- if (music.upload_type === 'external') {
- uni.showModal({
- title: '提示',
- content: '外部平台音乐无法生成视频,请使用直链或上传的音乐',
- showCancel: false
- });
- return;
- }
-
- // 确认生成
- uni.showModal({
- title: '生成唱歌视频',
- content: `确定让她唱《${music.title}》吗?`,
- success: (modalRes) => {
- if (modalRes.confirm) {
- this.generateSingVideoFromLibrary(music);
- }
- }
- });
-},
-
-// 生成唱歌视频(从音乐库)
-generateSingVideoFromLibrary(music) {
- const that = this;
-
- // 显示加载
- uni.showLoading({ title: '准备中...' });
-
- // 第 1 步:转换为系统歌曲
- uni.request({
- url: baseURLPy + '/music/convert-to-song',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- },
- data: {
- music_id: music.id
- },
- success: (res) => {
- if (res.data && res.data.code === 1) {
- const songId = res.data.data.song_id;
-
- // 第 2 步:调用现有的唱歌生成 API
- that.generateSingVideoWithSongId(songId, music.title);
- } else {
- uni.hideLoading();
- uni.showModal({
- title: '转换失败',
- content: res.data.message || '未知错误',
- showCancel: false
- });
- }
- },
- fail: (err) => {
- uni.hideLoading();
- console.error('转换失败:', err);
- uni.showModal({
- title: '转换失败',
- content: '网络错误,请重试',
- showCancel: false
- });
- }
- });
-},
-
-// 使用 song_id 生成唱歌视频
-generateSingVideoWithSongId(songId, songTitle) {
- const that = this;
-
- uni.showLoading({ title: '生成中...' });
-
- uni.request({
- url: baseURLPy + '/sing/generate',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- },
- data: {
- song_id: songId
- },
- success: (res) => {
- uni.hideLoading();
-
- if (res.data && res.data.code === 1) {
- const data = res.data.data;
-
- if (data.status === 'succeeded') {
- // 立即成功(有缓存)
- uni.showToast({
- title: '生成成功',
- icon: 'success'
- });
-
- // 刷新历史记录
- that.getSingHistory();
-
- // 切换到历史记录 tab
- that.switchSingTab('history');
-
- // 播放视频
- if (data.video_url) {
- that.openVideoPlayer(data.video_url);
- }
- } else {
- // 生成中
- that.singGenerating = true;
- that.singGeneratingTaskId = data.generation_task_id;
-
- uni.showToast({
- title: '视频生成中...',
- icon: 'loading',
- duration: 2000
- });
-
- // 开始轮询
- that.getSingGenerateTask(data.generation_task_id);
-
- // 切换到历史记录 tab
- that.switchSingTab('history');
- }
- } else {
- uni.showModal({
- title: '生成失败',
- content: res.data.message || '未知错误',
- showCancel: false
- });
- }
- },
- fail: (err) => {
- uni.hideLoading();
- console.error('生成失败:', err);
- uni.showModal({
- title: '生成失败',
- content: '网络错误,请重试',
- showCancel: false
- });
- }
- });
-},
-
-// ========================================
-// 修改说明:
-// 1. 删除了 "uni.showToast({ title: '功能开发中...', icon: 'none' });" 这行
-// 2. 添加了外部链接检查
-// 3. 添加了两个新方法:generateSingVideoFromLibrary 和 generateSingVideoWithSongId
-// 4. 保存文件后,重新编译前端即可
-// ========================================
diff --git a/修改前端代码说明.md b/修改前端代码说明.md
deleted file mode 100644
index dbe9921..0000000
--- a/修改前端代码说明.md
+++ /dev/null
@@ -1,239 +0,0 @@
-# 前端代码修改说明
-
-## 🎯 问题
-
-点击音乐库音乐时显示"功能开发中...",因为前端代码还没有修改。
-
-## 🔧 解决方案
-
-需要修改 `xuniYou/pages/index/index.vue` 文件。
-
-## 📝 修改步骤
-
-### 步骤 1: 打开文件
-
-使用编辑器打开:`xuniYou/pages/index/index.vue`
-
-### 步骤 2: 找到要修改的代码
-
-搜索 `selectMusicFromLibrary`,找到约第 2387 行的这段代码:
-
-```javascript
-selectMusicFromLibrary(music) {
- // 记录播放次数
- uni.request({
- url: baseURLPy + '/music/' + music.id + '/play',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- }
- });
-
- // 使用音乐库的歌曲生成视频
- if (this.singGenerating) {
- uni.showToast({ title: '视频生成中,请稍候...', icon: 'none', duration: 2000 });
- return;
- }
-
- // 这里需要调用唱歌API,传入音乐库的音乐URL
- uni.showModal({
- title: '提示',
- content: `确定让她唱《${music.title}》吗?`,
- success: (modalRes) => {
- if (modalRes.confirm) {
- // TODO: 调用唱歌API,使用 music.music_url
- uni.showToast({ title: '功能开发中...', icon: 'none' }); // ← 这里是问题
- }
- }
- });
-},
-```
-
-### 步骤 3: 删除旧代码
-
-删除从 `// 这里需要调用唱歌API` 到 `});` 的整段代码(包括 showModal)。
-
-### 步骤 4: 添加新代码
-
-在原位置添加以下代码:
-
-```javascript
-// 检查音乐类型
-if (music.upload_type === 'external') {
- uni.showModal({
- title: '提示',
- content: '外部平台音乐无法生成视频,请使用直链或上传的音乐',
- showCancel: false
- });
- return;
-}
-
-// 确认生成
-uni.showModal({
- title: '生成唱歌视频',
- content: `确定让她唱《${music.title}》吗?`,
- success: (modalRes) => {
- if (modalRes.confirm) {
- this.generateSingVideoFromLibrary(music);
- }
- }
-});
-```
-
-### 步骤 5: 添加两个新方法
-
-在 `selectMusicFromLibrary` 方法后面(在 `toggleMusicLike` 方法前面),添加两个新方法:
-
-```javascript
-// 生成唱歌视频(从音乐库)
-generateSingVideoFromLibrary(music) {
- const that = this;
-
- // 显示加载
- uni.showLoading({ title: '准备中...' });
-
- // 第 1 步:转换为系统歌曲
- uni.request({
- url: baseURLPy + '/music/convert-to-song',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- },
- data: {
- music_id: music.id
- },
- success: (res) => {
- if (res.data && res.data.code === 1) {
- const songId = res.data.data.song_id;
-
- // 第 2 步:调用现有的唱歌生成 API
- that.generateSingVideoWithSongId(songId, music.title);
- } else {
- uni.hideLoading();
- uni.showModal({
- title: '转换失败',
- content: res.data.message || '未知错误',
- showCancel: false
- });
- }
- },
- fail: (err) => {
- uni.hideLoading();
- console.error('转换失败:', err);
- uni.showModal({
- title: '转换失败',
- content: '网络错误,请重试',
- showCancel: false
- });
- }
- });
-},
-
-// 使用 song_id 生成唱歌视频
-generateSingVideoWithSongId(songId, songTitle) {
- const that = this;
-
- uni.showLoading({ title: '生成中...' });
-
- uni.request({
- url: baseURLPy + '/sing/generate',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- },
- data: {
- song_id: songId
- },
- success: (res) => {
- uni.hideLoading();
-
- if (res.data && res.data.code === 1) {
- const data = res.data.data;
-
- if (data.status === 'succeeded') {
- // 立即成功(有缓存)
- uni.showToast({
- title: '生成成功',
- icon: 'success'
- });
-
- // 刷新历史记录
- that.getSingHistory();
-
- // 切换到历史记录 tab
- that.switchSingTab('history');
-
- // 播放视频
- if (data.video_url) {
- that.openVideoPlayer(data.video_url);
- }
- } else {
- // 生成中
- that.singGenerating = true;
- that.singGeneratingTaskId = data.generation_task_id;
-
- uni.showToast({
- title: '视频生成中...',
- icon: 'loading',
- duration: 2000
- });
-
- // 开始轮询
- that.getSingGenerateTask(data.generation_task_id);
-
- // 切换到历史记录 tab
- that.switchSingTab('history');
- }
- } else {
- uni.showModal({
- title: '生成失败',
- content: res.data.message || '未知错误',
- showCancel: false
- });
- }
- },
- fail: (err) => {
- uni.hideLoading();
- console.error('生成失败:', err);
- uni.showModal({
- title: '生成失败',
- content: '网络错误,请重试',
- showCancel: false
- });
- }
- });
-},
-```
-
-### 步骤 6: 保存文件
-
-保存 `xuniYou/pages/index/index.vue` 文件。
-
-### 步骤 7: 重新编译
-
-重新编译前端项目。
-
-## ✅ 完成后的效果
-
-1. 点击音乐库音乐 → 弹出"生成唱歌视频"确认框
-2. 确认后 → 显示"准备中..." → "生成中..."
-3. 生成完成 → 自动切换到"历史记录" tab
-4. 显示并播放生成的视频
-
-## ⚠️ 注意事项
-
-1. 确保后端服务已启动(运行 `启动后端服务.bat`)
-2. 外部链接音乐会提示无法生成
-3. 生成过程中会自动切换到历史记录 tab
-
-## 🔍 如果还是不行
-
-1. 检查后端服务是否运行:访问 http://localhost:30101/docs
-2. 检查浏览器控制台是否有错误
-3. 检查 API 地址是否正确(baseURLPy)
-4. 检查 TOKEN 是否有效
-
----
-
-**修改说明版本**: 1.0
-**创建时间**: 2026-02-04
diff --git a/前端代码_完整替换版.js b/前端代码_完整替换版.js
deleted file mode 100644
index e05ebaa..0000000
--- a/前端代码_完整替换版.js
+++ /dev/null
@@ -1,170 +0,0 @@
-// ========================================
-// 完整的替换代码
-// 文件: xuniYou/pages/index/index.vue
-// 位置: selectMusicFromLibrary 方法及后续
-// ========================================
-
-// 找到 selectMusicFromLibrary 方法,完整替换为以下代码:
-
-selectMusicFromLibrary(music) {
- // 记录播放次数
- uni.request({
- url: baseURLPy + '/music/' + music.id + '/play',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- }
- });
-
- // 使用音乐库的歌曲生成视频
- if (this.singGenerating) {
- uni.showToast({ title: '视频生成中,请稍候...', icon: 'none', duration: 2000 });
- return;
- }
-
- // 检查音乐类型
- if (music.upload_type === 'external') {
- uni.showModal({
- title: '提示',
- content: '外部平台音乐无法生成视频,请使用直链或上传的音乐',
- showCancel: false
- });
- return;
- }
-
- // 确认生成
- uni.showModal({
- title: '生成唱歌视频',
- content: `确定让她唱《${music.title}》吗?`,
- success: (modalRes) => {
- if (modalRes.confirm) {
- this.generateSingVideoFromLibrary(music);
- }
- }
- });
-},
-
-// 在 selectMusicFromLibrary 后面添加这两个新方法:
-
-// 生成唱歌视频(从音乐库)
-generateSingVideoFromLibrary(music) {
- const that = this;
-
- // 显示加载
- uni.showLoading({ title: '准备中...' });
-
- // 第 1 步:转换为系统歌曲
- uni.request({
- url: baseURLPy + '/music/convert-to-song',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- },
- data: {
- music_id: music.id
- },
- success: (res) => {
- if (res.data && res.data.code === 1) {
- const songId = res.data.data.song_id;
-
- // 第 2 步:调用现有的唱歌生成 API
- that.generateSingVideoWithSongId(songId, music.title);
- } else {
- uni.hideLoading();
- uni.showModal({
- title: '转换失败',
- content: res.data.message || '未知错误',
- showCancel: false
- });
- }
- },
- fail: (err) => {
- uni.hideLoading();
- console.error('转换失败:', err);
- uni.showModal({
- title: '转换失败',
- content: '网络错误,请重试',
- showCancel: false
- });
- }
- });
-},
-
-// 使用 song_id 生成唱歌视频
-generateSingVideoWithSongId(songId, songTitle) {
- const that = this;
-
- uni.showLoading({ title: '生成中...' });
-
- uni.request({
- url: baseURLPy + '/sing/generate',
- method: 'POST',
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync("token")
- },
- data: {
- song_id: songId
- },
- success: (res) => {
- uni.hideLoading();
-
- if (res.data && res.data.code === 1) {
- const data = res.data.data;
-
- if (data.status === 'succeeded') {
- // 立即成功(有缓存)
- uni.showToast({
- title: '生成成功',
- icon: 'success'
- });
-
- // 刷新历史记录
- that.getSingHistory();
-
- // 切换到历史记录 tab
- that.switchSingTab('history');
-
- // 播放视频
- if (data.video_url) {
- that.openVideoPlayer(data.video_url);
- }
- } else {
- // 生成中
- that.singGenerating = true;
- that.singGeneratingTaskId = data.generation_task_id;
-
- uni.showToast({
- title: '视频生成中...',
- icon: 'loading',
- duration: 2000
- });
-
- // 开始轮询
- that.getSingGenerateTask(data.generation_task_id);
-
- // 切换到历史记录 tab
- that.switchSingTab('history');
- }
- } else {
- uni.showModal({
- title: '生成失败',
- content: res.data.message || '未知错误',
- showCancel: false
- });
- }
- },
- fail: (err) => {
- uni.hideLoading();
- console.error('生成失败:', err);
- uni.showModal({
- title: '生成失败',
- content: '网络错误,请重试',
- showCancel: false
- });
- }
- });
-},
-
-// ========================================
-// 修改完成后,保存文件并重新编译前端
-// ========================================
diff --git a/历史记录修复说明.md b/历史记录修复说明.md
deleted file mode 100644
index 2a9a556..0000000
--- a/历史记录修复说明.md
+++ /dev/null
@@ -1,125 +0,0 @@
-# 历史记录修复说明
-
-## 🐛 问题
-
-用户反馈:"历史记录原本的生成记录都没有啦"
-
-## 🔍 问题分析
-
-### 1. 后端检查
-
-测试后端 API `/sing/history`:
-```bash
-python test_sing_history.py
-```
-
-**结果**:✅ 后端正常,返回了 3 条历史记录
-
-### 2. 数据库检查
-
-查询数据库:
-```sql
-SELECT COUNT(*) FROM nf_sing_song_video WHERE status='succeeded';
-```
-
-**结果**:✅ 数据库有 12 条成功记录
-
-### 3. 前端检查
-
-检查前端代码 `xuniYou/pages/index/index.vue`:
-
-**发现问题**:
-- `onShow` 方法中调用了 `getSingSongs()`
-- 但是**没有调用 `getSingHistory()`**
-- 导致页面加载时不会获取历史记录
-
-## ✅ 解决方案
-
-### 修改文件:`xuniYou/pages/index/index.vue`
-
-在 `onShow` 方法中添加 `getSingHistory()` 调用:
-
-**修改前**:
-```javascript
-// 获取歌曲列表
-this.getSingSongs();
-this.getDanceHistory();
-```
-
-**修改后**:
-```javascript
-// 获取歌曲列表
-this.getSingSongs();
-this.getSingHistory(); // ← 添加这一行
-this.getDanceHistory();
-```
-
-## 🎯 修改位置
-
-文件:`xuniYou/pages/index/index.vue`
-方法:`onShow()`
-行号:约 976 行
-
-## 📊 修改效果
-
-### 修改前
-- 页面加载时不会获取历史记录
-- 历史记录 tab 显示为空
-- 只有手动切换到历史记录 tab 或生成新视频后才会刷新
-
-### 修改后
-- 页面加载时自动获取历史记录
-- 历史记录 tab 正常显示所有记录
-- 用户体验更好
-
-## 🚀 部署步骤
-
-1. **保存文件**(已自动保存)
-2. **重新编译前端**
-3. **测试**:
- - 打开应用
- - 切换到"唱歌"tab
- - 点击"历史记录"子 tab
- - 应该能看到所有历史记录
-
-## 🔍 测试结果
-
-### 后端测试
-```bash
-python test_sing_history.py
-```
-
-**输出**:
-```
-✅ 成功获取历史记录,共 3 条
-1. 如愿 - https://hello12312312.oss-cn-hangzhou.aliyuncs.com/lover/47/sing/1770101009_5.mp4
-2. 一半一半 - https://hello12312312.oss-cn-hangzhou.aliyuncs.com/lover/47/sing/1770100721_9.mp4
-3. 离开我的依赖 - https://nvlovers.oss-cn-qingdao.aliyuncs.com/lover/47/sing/1769435474_4.mp4
-```
-
-### 数据库测试
-```sql
-SELECT COUNT(*) FROM nf_sing_song_video WHERE status='succeeded';
-```
-
-**结果**:12 条记录
-
-## 💡 根本原因
-
-前端代码在页面加载时没有调用 `getSingHistory()` 方法,导致历史记录数据没有被加载到前端。
-
-这不是数据丢失,而是**数据没有被显示**。
-
-## 📝 注意事项
-
-1. 数据库中的历史记录完好无损
-2. 后端 API 工作正常
-3. 只需要修改前端代码即可
-4. 修改后需要重新编译前端
-
----
-
-**修复说明版本**: 1.0
-**创建时间**: 2026-02-04 18:45
-**状态**: ✅ 已修复
-
diff --git a/启动后端服务.bat b/启动后端服务.bat
deleted file mode 100644
index a0f78e7..0000000
--- a/启动后端服务.bat
+++ /dev/null
@@ -1,12 +0,0 @@
-@echo off
-chcp 65001 >nul
-echo ========================================
-echo 启动 Python 后端服务
-echo ========================================
-echo.
-
-cd lover
-echo 正在启动服务...
-python -m uvicorn main:app --host 0.0.0.0 --port 30101 --reload
-
-pause
diff --git a/启动项目.bat b/启动项目.bat
new file mode 100644
index 0000000..d1785b2
--- /dev/null
+++ b/启动项目.bat
@@ -0,0 +1,216 @@
+@echo off
+chcp 65001 >nul
+title AI 女友项目 - 启动服务
+
+REM ==========================================
+REM 配置区域
+REM ==========================================
+set PHP_PATH=D:\2_part\php-8.0.0-Win32-vs16-x64\php.exe
+set PHP_PORT=30100
+set PYTHON_PORT=30101
+
+REM ==========================================
+REM 检查 PHP
+REM ==========================================
+cls
+echo.
+echo ╔════════════════════════════════════╗
+echo ║ AI 女友项目 - 启动服务 ║
+echo ╚════════════════════════════════════╝
+echo.
+echo [检查] 正在检查 PHP...
+
+if not exist "%PHP_PATH%" (
+ echo [错误] PHP 未找到: %PHP_PATH%
+ echo.
+ echo 请修改脚本中的 PHP_PATH 变量
+ echo.
+ pause
+ exit /b 1
+)
+
+"%PHP_PATH%" -v >nul 2>&1
+if errorlevel 1 (
+ echo [错误] PHP 无法运行
+ pause
+ exit /b 1
+)
+
+echo [成功] PHP 已就绪
+echo.
+
+REM ==========================================
+REM 检查 Python
+REM ==========================================
+echo [检查] 正在检查 Python...
+
+python --version >nul 2>&1
+if errorlevel 1 (
+ echo [错误] Python 未找到或未添加到 PATH
+ echo.
+ pause
+ exit /b 1
+)
+
+echo [成功] Python 已就绪
+echo.
+
+REM ==========================================
+REM 检查项目目录
+REM ==========================================
+echo [检查] 正在检查项目目录...
+
+if not exist "%~dp0xunifriend_RaeeC\public" (
+ echo [错误] PHP 项目目录未找到
+ pause
+ exit /b 1
+)
+
+if not exist "%~dp0lover" (
+ echo [错误] Python 项目目录未找到
+ pause
+ exit /b 1
+)
+
+echo [成功] 项目目录已就绪
+echo.
+
+REM ==========================================
+REM 获取本机 IP
+REM ==========================================
+for /f "tokens=2 delims=:" %%a in ('ipconfig ^| findstr /C:"IPv4"') do (
+ set LOCAL_IP=%%a
+ goto :IP_FOUND
+)
+:IP_FOUND
+set LOCAL_IP=%LOCAL_IP: =%
+
+REM ==========================================
+REM 显示启动信息
+REM ==========================================
+cls
+echo.
+echo ╔════════════════════════════════════╗
+echo ║ AI 女友项目 - 启动服务 ║
+echo ╚════════════════════════════════════╝
+echo.
+echo 准备启动以下服务:
+echo.
+echo [PHP 服务器]
+echo → 端口: %PHP_PORT%
+echo → 本地: http://127.0.0.1:%PHP_PORT%
+echo → 局域网: http://%LOCAL_IP%:%PHP_PORT%
+echo.
+echo [Python 后端]
+echo → 端口: %PYTHON_PORT%
+echo → 本地: http://127.0.0.1:%PYTHON_PORT%
+echo → 局域网: http://%LOCAL_IP%:%PYTHON_PORT%
+echo.
+echo ────────────────────────────────────
+echo.
+echo 按任意键开始启动...
+pause >nul
+
+REM ==========================================
+REM 清理旧的服务进程
+REM ==========================================
+echo [清理] 正在检查并清理旧的服务进程...
+echo.
+
+REM 查找占用 30100 端口的进程并终止
+echo [清理] 检查端口 30100...
+for /f "tokens=5" %%a in ('netstat -ano ^| findstr :30100') do (
+ echo [清理] 终止进程 PID: %%a
+ taskkill /F /PID %%a >nul 2>&1
+)
+
+REM 查找占用 30101 端口的进程并终止
+echo [清理] 检查端口 30101...
+for /f "tokens=5" %%a in ('netstat -ano ^| findstr :30101') do (
+ echo [清理] 终止进程 PID: %%a
+ taskkill /F /PID %%a >nul 2>&1
+)
+
+echo.
+echo [成功] 端口清理完成
+echo.
+
+REM 等待端口完全释放
+echo [等待] 等待端口释放...
+timeout /t 3 >nul
+echo.
+
+REM ==========================================
+REM 启动 PHP 服务器
+REM ==========================================
+cls
+echo.
+echo [1/2] 启动 PHP 服务器...
+echo.
+
+start "PHP 服务器 - 端口 %PHP_PORT%" cmd /k "title PHP 服务器 - 端口 %PHP_PORT% && cd /d "%~dp0xunifriend_RaeeC\public" && echo [PHP 服务器] 正在启动... && echo [PHP 服务器] 端口: %PHP_PORT% && echo [PHP 服务器] 访问: http://127.0.0.1:%PHP_PORT% && echo [PHP 服务器] 测试: http://127.0.0.1:%PHP_PORT%/test_api.php && echo. && "%PHP_PATH%" -S 0.0.0.0:%PHP_PORT% router.php"
+
+echo [成功] PHP 服务器已启动
+echo.
+
+REM 等待 3 秒让 PHP 完全启动
+echo 等待 PHP 服务器启动...
+timeout /t 3 >nul
+
+REM ==========================================
+REM 启动 Python 后端
+REM ==========================================
+echo [2/2] 启动 Python 后端...
+echo.
+
+start "Python 后端 - 端口 %PYTHON_PORT%" cmd /k "title Python 后端 - 端口 %PYTHON_PORT% && cd /d "%~dp0" && echo [Python 后端] 正在启动... && echo [Python 后端] 端口: %PYTHON_PORT% && echo [Python 后端] 访问: http://127.0.0.1:%PYTHON_PORT% && echo [Python 后端] API 文档: http://127.0.0.1:%PYTHON_PORT%/docs && echo. && python -m uvicorn lover.main:app --host 0.0.0.0 --port %PYTHON_PORT% --reload"
+
+echo [成功] Python 后端已启动
+echo.
+
+REM 等待 3 秒让 Python 完全启动
+echo 等待 Python 后端启动...
+timeout /t 3 >nul
+
+REM ==========================================
+REM 显示完成信息
+REM ==========================================
+cls
+echo.
+echo ╔════════════════════════════════════╗
+echo ║ 启动成功! ║
+echo ╚════════════════════════════════════╝
+echo.
+echo [PHP 服务器] ✓ 已启动
+echo → http://127.0.0.1:%PHP_PORT%
+echo → http://127.0.0.1:%PHP_PORT%/admin
+echo → http://%LOCAL_IP%:%PHP_PORT%
+echo.
+echo [Python 后端] ✓ 已启动
+echo → http://127.0.0.1:%PYTHON_PORT%
+echo → http://127.0.0.1:%PYTHON_PORT%/docs
+echo → http://%LOCAL_IP%:%PYTHON_PORT%
+echo.
+echo ────────────────────────────────────
+echo.
+echo 提示:
+echo • 两个服务已在独立窗口中启动
+echo • 可以在窗口中查看运行日志
+echo • 关闭对应窗口可停止服务
+echo.
+echo 按任意键打开浏览器...
+pause >nul
+
+REM ==========================================
+REM 打开浏览器
+REM ==========================================
+start http://127.0.0.1:%PHP_PORT%
+timeout /t 1 >nul
+start http://127.0.0.1:%PYTHON_PORT%/docs
+
+echo.
+echo 浏览器已打开!
+echo.
+echo 按任意键退出(不影响服务运行)...
+pause >nul
+exit
diff --git a/启动项目_改进版.bat b/启动项目_改进版.bat
new file mode 100644
index 0000000..96e7a9c
--- /dev/null
+++ b/启动项目_改进版.bat
@@ -0,0 +1,263 @@
+@echo off
+chcp 65001 >nul
+title AI 女友项目 - 启动服务 (改进版)
+
+REM ==========================================
+REM 配置区域
+REM ==========================================
+set PHP_PATH=D:\2_part\php-8.0.0-Win32-vs16-x64\php.exe
+set PHP_PORT=30100
+set PYTHON_PORT=30101
+
+REM ==========================================
+REM 检查 MySQL 是否运行
+REM ==========================================
+cls
+echo.
+echo ╔════════════════════════════════════╗
+echo ║ AI 女友项目 - 启动服务 ║
+echo ╚════════════════════════════════════╝
+echo.
+echo [检查] 正在检查 MySQL 服务...
+
+netstat -ano | findstr :3306 >nul 2>&1
+if errorlevel 1 (
+ echo [警告] MySQL 服务未运行(端口 3306 未监听)
+ echo.
+ echo 请先启动 MySQL 服务,否则 PHP 应用可能无法正常工作
+ echo.
+ set /p CONTINUE=是否继续启动?(Y/N):
+ if /i not "!CONTINUE!"=="Y" (
+ echo.
+ echo 已取消启动
+ pause
+ exit /b 1
+ )
+) else (
+ echo [成功] MySQL 服务正在运行
+)
+echo.
+
+REM ==========================================
+REM 清理旧的服务进程
+REM ==========================================
+echo [清理] 正在检查并清理旧的服务进程...
+echo.
+
+REM 查找占用 30100 端口的进程并终止
+echo [清理] 检查端口 30100...
+for /f "tokens=5" %%a in ('netstat -ano ^| findstr :30100 2^>nul') do (
+ echo [清理] 终止进程 PID: %%a
+ taskkill /F /PID %%a >nul 2>&1
+)
+
+REM 查找占用 30101 端口的进程并终止
+echo [清理] 检查端口 30101...
+for /f "tokens=5" %%a in ('netstat -ano ^| findstr :30101 2^>nul') do (
+ echo [清理] 终止进程 PID: %%a
+ taskkill /F /PID %%a >nul 2>&1
+)
+
+echo.
+echo [成功] 端口清理完成
+echo.
+
+REM 等待端口完全释放
+echo [等待] 等待端口释放(3秒)...
+timeout /t 3 >nul
+echo.
+
+REM ==========================================
+REM 检查 PHP
+REM ==========================================
+echo [检查] 正在检查 PHP...
+
+if not exist "%PHP_PATH%" (
+ echo [错误] PHP 未找到: %PHP_PATH%
+ echo.
+ echo 请修改脚本中的 PHP_PATH 变量
+ echo.
+ pause
+ exit /b 1
+)
+
+"%PHP_PATH%" -v >nul 2>&1
+if errorlevel 1 (
+ echo [错误] PHP 无法运行
+ pause
+ exit /b 1
+)
+
+echo [成功] PHP 已就绪
+echo.
+
+REM ==========================================
+REM 检查 Python
+REM ==========================================
+echo [检查] 正在检查 Python...
+
+python --version >nul 2>&1
+if errorlevel 1 (
+ echo [错误] Python 未找到或未添加到 PATH
+ echo.
+ pause
+ exit /b 1
+)
+
+echo [成功] Python 已就绪
+echo.
+
+REM ==========================================
+REM 检查项目目录
+REM ==========================================
+echo [检查] 正在检查项目目录...
+
+if not exist "%~dp0xunifriend_RaeeC\public" (
+ echo [错误] PHP 项目目录未找到
+ pause
+ exit /b 1
+)
+
+if not exist "%~dp0lover" (
+ echo [错误] Python 项目目录未找到
+ pause
+ exit /b 1
+)
+
+echo [成功] 项目目录已就绪
+echo.
+
+REM ==========================================
+REM 获取本机 IP
+REM ==========================================
+for /f "tokens=2 delims=:" %%a in ('ipconfig ^| findstr /C:"IPv4"') do (
+ set LOCAL_IP=%%a
+ goto :IP_FOUND
+)
+:IP_FOUND
+set LOCAL_IP=%LOCAL_IP: =%
+
+REM ==========================================
+REM 显示启动信息
+REM ==========================================
+cls
+echo.
+echo ╔════════════════════════════════════╗
+echo ║ AI 女友项目 - 启动服务 ║
+echo ╚════════════════════════════════════╝
+echo.
+echo 准备启动以下服务:
+echo.
+echo [PHP 服务器]
+echo → 端口: %PHP_PORT%
+echo → 本地: http://127.0.0.1:%PHP_PORT%
+echo → 测试: http://127.0.0.1:%PHP_PORT%/test_api.php
+echo → 局域网: http://%LOCAL_IP%:%PHP_PORT%
+echo.
+echo [Python 后端]
+echo → 端口: %PYTHON_PORT%
+echo → 本地: http://127.0.0.1:%PYTHON_PORT%
+echo → 局域网: http://%LOCAL_IP%:%PYTHON_PORT%
+echo.
+echo ────────────────────────────────────
+echo.
+echo 按任意键开始启动...
+pause >nul
+
+REM ==========================================
+REM 启动 PHP 服务器(使用 router.php)
+REM ==========================================
+cls
+echo.
+echo [1/2] 启动 PHP 服务器...
+echo.
+
+start "PHP 服务器 - 端口 %PHP_PORT%" cmd /k "title PHP 服务器 - 端口 %PHP_PORT% && cd /d "%~dp0xunifriend_RaeeC\public" && echo [PHP 服务器] 正在启动... && echo [PHP 服务器] 端口: %PHP_PORT% && echo [PHP 服务器] 访问: http://127.0.0.1:%PHP_PORT% && echo [PHP 服务器] 测试: http://127.0.0.1:%PHP_PORT%/test_api.php && echo. && "%PHP_PATH%" -S 0.0.0.0:%PHP_PORT% router.php"
+
+echo [成功] PHP 服务器已启动
+echo.
+
+REM 等待 5 秒让 PHP 完全启动
+echo 等待 PHP 服务器启动(5秒)...
+timeout /t 5 >nul
+
+REM ==========================================
+REM 测试 PHP 服务器
+REM ==========================================
+echo.
+echo [测试] 正在测试 PHP 服务器...
+echo.
+
+powershell -Command "try { $response = Invoke-WebRequest -Uri 'http://127.0.0.1:%PHP_PORT%/test_api.php' -TimeoutSec 5; if ($response.StatusCode -eq 200) { Write-Host '[成功] PHP 服务器响应正常' -ForegroundColor Green } else { Write-Host '[警告] PHP 服务器响应异常' -ForegroundColor Yellow } } catch { Write-Host '[错误] PHP 服务器无响应' -ForegroundColor Red; Write-Host $_.Exception.Message -ForegroundColor Red }"
+
+echo.
+set /p CONTINUE_PYTHON=PHP 服务器已启动,是否继续启动 Python 后端?(Y/N):
+if /i not "%CONTINUE_PYTHON%"=="Y" (
+ echo.
+ echo 已取消 Python 后端启动
+ echo.
+ echo 按任意键退出...
+ pause >nul
+ exit /b 0
+)
+
+REM ==========================================
+REM 启动 Python 后端
+REM ==========================================
+echo.
+echo [2/2] 启动 Python 后端...
+echo.
+
+start "Python 后端 - 端口 %PYTHON_PORT%" cmd /k "title Python 后端 - 端口 %PYTHON_PORT% && cd /d "%~dp0" && echo [Python 后端] 正在启动... && echo [Python 后端] 端口: %PYTHON_PORT% && echo [Python 后端] 访问: http://127.0.0.1:%PYTHON_PORT% && echo [Python 后端] API 文档: http://127.0.0.1:%PYTHON_PORT%/docs && echo. && python -m uvicorn lover.main:app --host 0.0.0.0 --port %PYTHON_PORT% --reload"
+
+echo [成功] Python 后端已启动
+echo.
+
+REM 等待 3 秒让 Python 完全启动
+echo 等待 Python 后端启动(3秒)...
+timeout /t 3 >nul
+
+REM ==========================================
+REM 显示完成信息
+REM ==========================================
+cls
+echo.
+echo ╔════════════════════════════════════╗
+echo ║ 启动成功! ║
+echo ╚════════════════════════════════════╝
+echo.
+echo [PHP 服务器] ✓ 已启动
+echo → http://127.0.0.1:%PHP_PORT%
+echo → http://127.0.0.1:%PHP_PORT%/test_api.php (测试)
+echo → http://127.0.0.1:%PHP_PORT%/admin
+echo → http://%LOCAL_IP%:%PHP_PORT%
+echo.
+echo [Python 后端] ✓ 已启动
+echo → http://127.0.0.1:%PYTHON_PORT%
+echo → http://127.0.0.1:%PYTHON_PORT%/docs
+echo → http://%LOCAL_IP%:%PYTHON_PORT%
+echo.
+echo ────────────────────────────────────
+echo.
+echo 提示:
+echo • 两个服务已在独立窗口中启动
+echo • 可以在窗口中查看运行日志
+echo • 关闭对应窗口可停止服务
+echo • 如果 PHP API 超时,请检查 MySQL 是否运行
+echo.
+echo 按任意键打开浏览器测试...
+pause >nul
+
+REM ==========================================
+REM 打开浏览器测试
+REM ==========================================
+start http://127.0.0.1:%PHP_PORT%/test_api.php
+timeout /t 1 >nul
+start http://127.0.0.1:%PYTHON_PORT%/docs
+
+echo.
+echo 浏览器已打开!
+echo.
+echo 按任意键退出(不影响服务运行)...
+pause >nul
+exit
diff --git a/并发控制说明.md b/并发控制说明.md
deleted file mode 100644
index ac168e5..0000000
--- a/并发控制说明.md
+++ /dev/null
@@ -1,183 +0,0 @@
-# 并发控制说明 - 音乐库唱歌视频功能
-
-## 🎯 问题
-
-用户在视频生成过程中重复点击音乐,导致多个生成任务同时运行。
-
-## ✅ 解决方案
-
-实现了**前后端双重并发控制**。
-
----
-
-## 🔒 前端控制
-
-### 1. 状态检查
-
-在 `selectMusicFromLibrary` 方法中添加状态检查:
-
-```javascript
-if (this.singGenerating) {
- uni.showToast({
- title: '视频生成中,请稍候...',
- icon: 'none',
- duration: 2000
- });
- return;
-}
-```
-
-**效果**:如果有视频正在生成,直接提示用户,不发送请求。
-
-### 2. 错误处理
-
-在 `generateSingVideoWithSongId` 方法的 `fail` 回调中添加 409 错误处理:
-
-```javascript
-fail: (err) => {
- uni.hideLoading();
- console.error('生成失败:', err);
-
- // 检查是否是 409 错误(已有任务进行中)
- if (err.statusCode === 409 || (err.data && err.data.detail && err.data.detail.includes('进行中'))) {
- uni.showModal({
- title: '提示',
- content: '已有视频正在生成中,请稍后再试',
- showCancel: false
- });
- } else {
- uni.showModal({
- title: '生成失败',
- content: '网络错误,请重试',
- showCancel: false
- });
- }
-}
-```
-
-**效果**:如果后端返回 409 错误,显示友好的提示信息。
-
----
-
-## 🔒 后端控制
-
-### 1. 任务检查
-
-后端在 `lover/routers/sing.py` 的 `generate_sing_video` 方法中检查是否有进行中的任务:
-
-```python
-# 检查是否有进行中的任务
-existing_task = db.query(SingSongVideo).filter(
- SingSongVideo.user_id == user.id,
- SingSongVideo.status.in_(['pending', 'processing'])
-).first()
-
-if existing_task:
- raise HTTPException(status_code=409, detail="已有视频生成任务进行中,请稍后再试")
-```
-
-**效果**:如果有进行中的任务,返回 409 错误。
-
----
-
-## 📊 控制流程
-
-### 场景 1:正常生成
-
-1. 用户点击音乐
-2. 前端检查 `singGenerating` → `false`
-3. 发送请求到后端
-4. 后端检查数据库 → 无进行中任务
-5. 创建新任务,返回成功
-6. 前端设置 `singGenerating = true`
-7. 开始轮询任务状态
-
-### 场景 2:生成中再次点击(前端拦截)
-
-1. 用户点击音乐
-2. 前端检查 `singGenerating` → `true`
-3. 显示提示:"视频生成中,请稍候..."
-4. **不发送请求**
-
-### 场景 3:生成中再次点击(后端拦截)
-
-1. 用户点击音乐
-2. 前端检查 `singGenerating` → `false`(可能状态未同步)
-3. 发送请求到后端
-4. 后端检查数据库 → **有进行中任务**
-5. 返回 409 错误:"已有视频生成任务进行中,请稍后再试"
-6. 前端捕获 409 错误
-7. 显示提示:"已有视频正在生成中,请稍后再试"
-
----
-
-## 🎯 用户体验
-
-### 提示信息
-
-1. **前端拦截**:
- - 提示:`视频生成中,请稍候...`
- - 类型:Toast(2秒后自动消失)
- - 场景:用户在生成中点击
-
-2. **后端拦截**:
- - 提示:`已有视频正在生成中,请稍后再试`
- - 类型:Modal(需要点击确定)
- - 场景:前端状态未同步时
-
-### 状态管理
-
-- `singGenerating`:标记是否有视频正在生成
-- `singGeneratingTaskId`:当前生成任务的 ID
-- 生成完成后自动重置状态
-
----
-
-## 🔍 测试方法
-
-### 测试 1:前端拦截
-
-1. 点击音乐 A,开始生成
-2. 立即再次点击音乐 A
-3. 应该看到 Toast:"视频生成中,请稍候..."
-4. 不应该发送新的请求
-
-### 测试 2:后端拦截
-
-1. 点击音乐 A,开始生成
-2. 刷新页面(清除前端状态)
-3. 再次点击音乐 A
-4. 应该看到 Modal:"已有视频正在生成中,请稍后再试"
-5. 后端日志应该显示 409 错误
-
-### 测试 3:生成完成后
-
-1. 点击音乐 A,等待生成完成
-2. 再次点击音乐 A
-3. 应该立即成功(使用缓存)
-4. 不应该看到任何拦截提示
-
----
-
-## 💡 技术亮点
-
-1. **双重保护**:前端 + 后端,确保不会有重复任务
-2. **友好提示**:区分前端拦截和后端拦截,提供不同的提示
-3. **状态同步**:使用 `singGenerating` 标记,自动管理状态
-4. **错误处理**:完善的 409 错误处理逻辑
-
----
-
-## 📝 注意事项
-
-1. 前端状态可能因为刷新页面而丢失,所以需要后端检查
-2. 后端检查是最终保障,确保数据库不会有重复任务
-3. 409 错误是标准的 HTTP 状态码,表示"冲突"
-4. 生成完成后会自动重置 `singGenerating` 状态
-
----
-
-**并发控制说明版本**: 1.0
-**创建时间**: 2026-02-04 18:30
-**状态**: ✅ 已实现并测试
-
diff --git a/开发/2026年2月3日/PHP连接泄漏问题修复.md b/开发/2026年2月3日/PHP连接泄漏问题修复.md
deleted file mode 100644
index 06ee33a..0000000
--- a/开发/2026年2月3日/PHP连接泄漏问题修复.md
+++ /dev/null
@@ -1,385 +0,0 @@
-# PHP 连接泄漏问题修复
-
-## 问题描述
-
-Python 后端调用 PHP 后端接口时出现超时错误:
-
-```
-HTTPConnectionPool(host='192.168.1.164', port=30100): Read timed out. (read timeout=5)
-```
-
-## 问题根源
-
-通过 `netstat -ano | findstr :30100` 检查发现:
-- PHP 服务(PID 23736 和 1416)有 30+ 个 `CLOSE_WAIT` 连接
-- `CLOSE_WAIT` 状态表示:客户端已关闭连接,但服务器端未关闭
-- 这是典型的**连接泄漏**问题
-
-### 为什么会出现 CLOSE_WAIT?
-
-1. **PHP 内置开发服务器的限制**
- - `php -S` 是单线程服务器,设计用于开发测试
- - 在处理大量并发请求时容易出现连接泄漏
- - 长时间运行会导致资源耗尽
-
-2. **连接未正确关闭**
- - 客户端(Python)发送请求后关闭连接
- - 服务器端(PHP)没有正确关闭 socket
- - 连接进入 CLOSE_WAIT 状态并一直保持
-
-3. **资源耗尽**
- - 大量 CLOSE_WAIT 连接占用系统资源
- - 导致新请求无法处理或响应缓慢
- - 最终导致超时错误
-
----
-
-## 临时解决方案:重启 PHP 服务
-
-### 方法 1:使用快速重启脚本(推荐)
-
-双击运行 `restart_php_service.bat`:
-
-```batch
-restart_php_service.bat
-```
-
-这个脚本会:
-1. 检查当前 PHP 服务状态
-2. 停止所有 PHP 服务进程
-3. 等待端口释放
-4. 启动新的 PHP 服务
-
-### 方法 2:手动重启
-
-```bash
-# 1. 查看当前 PHP 进程
-netstat -ano | findstr :30100
-
-# 2. 停止所有 PHP 进程(替换 PID)
-taskkill /F /PID 23736
-taskkill /F /PID 1416
-
-# 3. 等待 2 秒
-
-# 4. 启动新的 PHP 服务
-cd C:\Users\Administrator\Desktop\Project\AI_GirlFriend\xunifriend_RaeeC\public
-php -S 192.168.1.164:30100
-```
-
-### 验证服务已重启
-
-```bash
-# 检查服务状态
-netstat -ano | findstr :30100
-
-# 应该只看到 LISTENING 状态,没有 CLOSE_WAIT
-```
-
----
-
-## 监控连接状态
-
-### 使用监控脚本
-
-双击运行 `monitor_php_connections.bat`:
-
-```batch
-monitor_php_connections.bat
-```
-
-这个脚本会每 5 秒刷新一次,显示:
-- 所有连接状态
-- ESTABLISHED 连接数(正常活跃连接)
-- CLOSE_WAIT 连接数(连接泄漏)
-- TIME_WAIT 连接数(正常关闭中)
-
-### 判断标准
-
-- **正常**:CLOSE_WAIT < 10 个
-- **注意**:CLOSE_WAIT 10-20 个(需要关注)
-- **警告**:CLOSE_WAIT > 20 个(建议立即重启)
-
----
-
-## 长期解决方案
-
-### 方案 1:使用 Nginx + PHP-FPM(推荐)
-
-PHP 内置服务器不适合生产环境,建议使用 Nginx + PHP-FPM。
-
-#### 安装步骤
-
-1. **下载 Nginx for Windows**
- - 访问:https://nginx.org/en/download.html
- - 下载稳定版(Stable version)
-
-2. **下载 PHP(非线程安全版本)**
- - 访问:https://windows.php.net/download/
- - 下载 NTS (Non Thread Safe) 版本
-
-3. **配置 PHP-FPM**
-
-创建 `php-cgi.bat`:
-
-```batch
-@echo off
-cd C:\php
-php-cgi.exe -b 127.0.0.1:9000
-```
-
-4. **配置 Nginx**
-
-编辑 `nginx.conf`:
-
-```nginx
-server {
- listen 30100;
- server_name 192.168.1.164;
- root C:/Users/Administrator/Desktop/Project/AI_GirlFriend/xunifriend_RaeeC/public;
- index index.php index.html;
-
- location / {
- try_files $uri $uri/ /index.php?$query_string;
- }
-
- location ~ \.php$ {
- fastcgi_pass 127.0.0.1:9000;
- fastcgi_index index.php;
- fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
- include fastcgi_params;
- }
-}
-```
-
-5. **启动服务**
-
-```batch
-# 启动 PHP-FPM
-start php-cgi.bat
-
-# 启动 Nginx
-cd C:\nginx
-start nginx.exe
-```
-
-#### 优点
-
-- 支持多进程,性能更好
-- 连接管理更稳定
-- 适合生产环境
-- 不会出现连接泄漏
-
-### 方案 2:定期自动重启 PHP 服务
-
-如果暂时无法切换到 Nginx,可以设置定时任务自动重启 PHP 服务。
-
-#### 创建定时任务
-
-1. 打开"任务计划程序"(Task Scheduler)
-2. 创建基本任务
-3. 设置触发器:每 4 小时
-4. 操作:启动程序 `restart_php_service.bat`
-
-#### 或使用 Windows 计划任务命令
-
-```batch
-schtasks /create /tn "重启PHP服务" /tr "C:\path\to\restart_php_service.bat" /sc hourly /mo 4
-```
-
-### 方案 3:优化 Python 请求代码
-
-在 `lover/deps.py` 中优化 HTTP 请求:
-
-```python
-import requests
-from requests.adapters import HTTPAdapter
-from urllib3.util.retry import Retry
-
-# 创建带重试和连接池的 session
-def get_http_session():
- session = requests.Session()
-
- # 配置重试策略
- retry = Retry(
- total=3,
- backoff_factor=0.3,
- status_forcelist=[500, 502, 503, 504]
- )
-
- # 配置连接池
- adapter = HTTPAdapter(
- max_retries=retry,
- pool_connections=10,
- pool_maxsize=20,
- pool_block=False
- )
-
- session.mount('http://', adapter)
- session.mount('https://', adapter)
-
- return session
-
-# 使用 session
-def _fetch_user_from_php(token: str) -> Optional[dict]:
- """通过 PHP/FastAdmin 接口获取用户信息。"""
- import logging
- logger = logging.getLogger(__name__)
-
- user_info_api = "http://192.168.1.164:30100/api/user_basic/get_user_basic"
-
- logger.info(f"用户中心调试 - 调用接口: {user_info_api}")
-
- try:
- session = get_http_session()
- resp = session.get(
- user_info_api,
- headers={
- "token": token,
- "Connection": "close" # 明确关闭连接
- },
- timeout=10, # 增加超时时间
- )
- logger.info(f"用户中心调试 - 响应状态码: {resp.status_code}")
-
- # 确保连接关闭
- resp.close()
-
- except requests.exceptions.Timeout:
- logger.error(f"用户中心调试 - 请求超时")
- raise HTTPException(
- status_code=status.HTTP_504_GATEWAY_TIMEOUT,
- detail="用户中心接口超时",
- )
- except Exception as exc:
- logger.error(f"用户中心调试 - 请求异常: {exc}")
- raise HTTPException(
- status_code=status.HTTP_502_BAD_GATEWAY,
- detail="用户中心接口不可用",
- ) from exc
-
- # ... 其余代码
-```
-
----
-
-## 预防措施
-
-### 1. 监控连接状态
-
-定期运行 `monitor_php_connections.bat` 检查连接状态。
-
-### 2. 设置告警
-
-当 CLOSE_WAIT 连接数超过阈值时,发送告警通知。
-
-### 3. 日志记录
-
-在 Python 代码中记录每次 PHP 调用的耗时:
-
-```python
-import time
-
-start_time = time.time()
-resp = requests.get(...)
-elapsed_time = time.time() - start_time
-
-logger.info(f"PHP 接口调用耗时: {elapsed_time:.2f}秒")
-
-if elapsed_time > 3:
- logger.warning(f"PHP 接口响应缓慢: {elapsed_time:.2f}秒")
-```
-
-### 4. 健康检查
-
-添加健康检查端点,定期检查 PHP 服务状态:
-
-```python
-@app.get("/health/php")
-async def check_php_health():
- try:
- resp = requests.get(
- "http://192.168.1.164:30100/api/health",
- timeout=2
- )
- return {
- "status": "healthy" if resp.status_code == 200 else "unhealthy",
- "response_time": resp.elapsed.total_seconds()
- }
- except:
- return {"status": "down"}
-```
-
----
-
-## 常见问题
-
-### Q1: 为什么会有两个 PHP 进程(PID 23736 和 1416)?
-
-**A**: 可能是之前启动了多次 PHP 服务,导致有多个进程在监听同一端口。建议:
-1. 停止所有 PHP 进程
-2. 只启动一个 PHP 服务
-
-### Q2: 重启后还是有 CLOSE_WAIT 怎么办?
-
-**A**:
-1. 确认已停止所有旧的 PHP 进程
-2. 检查是否有其他程序占用端口
-3. 考虑更换端口或使用 Nginx
-
-### Q3: 如何判断是 PHP 问题还是 Python 问题?
-
-**A**:
-1. 使用 curl 直接测试 PHP 接口:
- ```bash
- curl -X GET "http://192.168.1.164:30100/api/user_basic/get_user_basic" -H "token: YOUR_TOKEN"
- ```
-2. 如果 curl 正常,说明是 Python 客户端问题
-3. 如果 curl 也慢,说明是 PHP 服务器问题
-
-### Q4: 生产环境应该用什么?
-
-**A**:
-- **不推荐**:`php -S`(仅用于开发)
-- **推荐**:Nginx + PHP-FPM
-- **备选**:Apache + mod_php
-
----
-
-## 快速参考
-
-### 检查连接状态
-```bash
-netstat -ano | findstr :30100
-```
-
-### 重启 PHP 服务
-```bash
-restart_php_service.bat
-```
-
-### 监控连接
-```bash
-monitor_php_connections.bat
-```
-
-### 停止所有服务
-```bash
-stop_all_services.bat
-```
-
-### 启动所有服务
-```bash
-start_all_services.bat
-```
-
----
-
-## 总结
-
-1. **问题根源**:PHP 内置服务器连接泄漏
-2. **临时方案**:定期重启 PHP 服务
-3. **长期方案**:使用 Nginx + PHP-FPM
-4. **监控措施**:使用监控脚本定期检查
-
-建议尽快切换到 Nginx + PHP-FPM,彻底解决连接泄漏问题!
diff --git a/开发/2026年2月4日/db_update.sql b/开发/2026年2月4日/db_update.sql
deleted file mode 100644
index da18906..0000000
--- a/开发/2026年2月4日/db_update.sql
+++ /dev/null
@@ -1,16 +0,0 @@
-USE fastadmin;
-
-ALTER TABLE nf_sing_song_video
-ADD COLUMN music_library_id BIGINT NULL AFTER song_id,
-ADD COLUMN music_source VARCHAR(20) DEFAULT 'system' AFTER music_library_id;
-
-ALTER TABLE nf_song_library
-ADD COLUMN audio_hash VARCHAR(64) NULL AFTER audio_url;
-
-UPDATE nf_sing_song_video SET music_source = 'system' WHERE music_source IS NULL;
-
-CREATE INDEX idx_music_library_id ON nf_sing_song_video(music_library_id);
-CREATE INDEX idx_music_source ON nf_sing_song_video(music_source);
-CREATE INDEX idx_audio_hash ON nf_song_library(audio_hash);
-
-SELECT 'Database update completed!' AS message;
diff --git a/开发/2026年2月4日/今日工作总结_最终版.md b/开发/2026年2月4日/今日工作总结_最终版.md
deleted file mode 100644
index bd15b48..0000000
--- a/开发/2026年2月4日/今日工作总结_最终版.md
+++ /dev/null
@@ -1,192 +0,0 @@
-# 今日工作总结 - 2026年2月4日
-
-## ✅ 已完成的功能
-
-### 1. 音乐库唱歌视频功能(完整实现)
-
-**功能描述**:用户点击音乐库中的音乐,AI 恋人会唱这首歌并生成视频,保存到历史记录。
-
-**实现方案**:
-- 采用简化方案:将音乐库音乐转换为系统歌曲,复用现有唱歌功能
-- 前后端完整对接,已修复所有 bug
-- 添加了并发控制和错误处理
-
-**技术细节**:
-
-#### 后端实现(Python FastAPI)
-1. **新增 API**:`POST /music/convert-to-song?music_id={id}`
- - 将音乐库音乐转换为系统歌曲
- - 返回 `song_id` 供唱歌 API 使用
- - 支持缓存机制(避免重复转换)
-
-2. **数据库修改**:
- - `fa_sing_song_video` 表新增字段:
- - `music_library_id` (int) - 关联音乐库 ID
- - `music_source` (varchar) - 音乐来源('library' 或 'system')
- - `fa_song_library` 表新增字段:
- - `audio_hash` (varchar) - 音频 URL 的 MD5 哈希(用于去重)
-
-3. **文件修改**:
- - `lover/models.py` - 更新数据模型
- - `lover/routers/music_library.py` - 新增转换 API
-
-#### 前端实现(uni-app Vue)
-1. **修改文件**:`xuniYou/pages/index/index.vue`
-
-2. **修改内容**:
- - 修改 `selectMusicFromLibrary` 方法:
- - 添加外部链接检查
- - 添加生成中状态检查(防止重复点击)
- - 调用新的生成方法
-
- - 新增 `generateSingVideoFromLibrary` 方法:
- - 调用转换 API(使用 query 参数)
- - 显示"准备中..."加载提示
-
- - 新增 `generateSingVideoWithSongId` 方法:
- - 调用唱歌生成 API
- - 处理生成状态(立即成功/生成中)
- - 处理 409 错误(已有任务进行中)
- - 自动切换到历史记录 tab
- - 自动播放生成的视频
-
-3. **关键修复**:
- - 修复了 API 参数传递问题:从 `data: { music_id }` 改为 `url?music_id=`
- - 添加了 409 错误处理:显示"已有视频正在生成中,请稍后再试"
- - 删除了重复的方法定义
- - 清理了多余的注释
-
-**用户体验流程**:
-1. 用户点击音乐库音乐
-2. 检查是否有视频正在生成 → 如果有,提示"视频生成中,请稍候..."
-3. 检查是否是外部链接 → 如果是,提示"外部平台音乐无法生成视频"
-4. 弹出确认框:"确定让她唱《xxx》吗?"
-5. 确认后 → 显示"准备中..." → "生成中..."
-6. 生成完成 → 自动切换到"历史记录" tab
-7. 显示并自动播放生成的视频
-
-**并发控制**:
-- ✅ 前端检查:`if (this.singGenerating)` 防止重复点击
-- ✅ 后端检查:返回 409 错误,提示"已有视频生成任务进行中"
-- ✅ 前端处理:捕获 409 错误,显示友好提示
-
-**限制说明**:
-- ✅ 直链音乐(Bensound 等)可以生成视频
-- ✅ 用户上传的音乐可以生成视频
-- ❌ 外部平台音乐(网易云、QQ音乐)无法生成视频(会提示用户)
-- ❌ 生成中不能再次点击(会提示用户)
-
----
-
-## 📁 相关文件
-
-### 数据库
-- `开发/2026年2月4日/音乐库唱歌视频数据库修改.sql`
-
-### 后端
-- `lover/models.py`
-- `lover/routers/music_library.py`
-
-### 前端
-- `xuniYou/pages/index/index.vue`
-
-### 测试
-- `test_music_library_sing.py`
-
-### 文档
-- `开发/2026年2月4日/音乐库唱歌视频功能实现总结.md`
-- `开发/2026年2月4日/音乐库唱歌视频部署清单.md`
-- `快速修复指南.md`
-- `测试音乐库唱歌功能.md`
-
----
-
-## 🐛 已修复的 Bug
-
-### Bug 1: 前端代码重复
-**问题**:`selectMusicFromLibrary` 方法重复定义,导致代码混乱
-**解决**:删除旧方法,保留新方法
-
-### Bug 2: API 参数传递错误
-**问题**:前端使用 `data: { music_id }` 发送,后端期望 query 参数
-**错误信息**:`422 Unprocessable Content - Field required: music_id`
-**解决**:修改前端为 `url?music_id={id}`
-
-### Bug 3: 并发控制不完善
-**问题**:用户可以重复点击,导致多个生成任务
-**错误信息**:`409 Conflict - 已有视频生成任务进行中,请稍后再试`
-**解决**:
-- 前端添加 `singGenerating` 状态检查
-- 前端添加 409 错误处理,显示友好提示
-
----
-
-## 🚀 部署步骤
-
-### 1. 数据库更新
-```bash
-mysql -u root -p fastadmin < "开发/2026年2月4日/音乐库唱歌视频数据库修改.sql"
-```
-
-### 2. 启动后端服务
-```bash
-cd lover
-python -m uvicorn main:app --host 0.0.0.0 --port 30101 --reload
-```
-
-或双击:`启动后端服务.bat`
-
-### 3. 重新编译前端
-保存 `xuniYou/pages/index/index.vue` 后,重新编译前端项目。
-
-### 4. 测试
-- 访问 http://localhost:30101/docs 确认 API 可用
-- 点击音乐库音乐,测试生成功能
-- 测试并发控制:生成中再次点击,应该提示"请稍后再试"
-
----
-
-## 📊 测试结果
-
-### API 测试
-- ✅ `POST /music/convert-to-song` - 转换成功
-- ✅ `POST /sing/generate` - 生成成功
-- ✅ 缓存机制 - 相同音乐复用视频
-- ✅ 并发控制 - 返回 409 错误
-
-### 前端测试
-- ✅ 点击音乐 - 弹出确认框
-- ✅ 确认生成 - 显示加载提示
-- ✅ 生成完成 - 自动切换 tab
-- ✅ 外部链接 - 正确提示无法生成
-- ✅ 生成中点击 - 提示"请稍后再试"
-- ✅ 409 错误 - 显示友好提示
-
----
-
-## 💡 技术亮点
-
-1. **缓存机制**:使用 `audio_hash` 避免重复转换和生成
-2. **错误处理**:完善的错误提示和异常处理
-3. **并发控制**:前后端双重检查,防止重复生成
-4. **用户体验**:自动切换 tab、自动播放视频、友好的错误提示
-5. **代码复用**:复用现有唱歌功能,减少开发量
-
----
-
-## 📝 注意事项
-
-1. 确保后端服务运行在 30101 端口
-2. 确保数据库已执行更新脚本
-3. 前端修改后需要重新编译
-4. 外部链接音乐无法生成视频(这是设计限制)
-5. 生成中不能重复点击(会显示提示)
-
----
-
-**工作总结版本**: 3.0(最终版 - 已添加并发控制)
-**创建时间**: 2026-02-04 18:25
-**状态**: ✅ 功能完整,已修复所有 bug,已添加并发控制
-
-
-
diff --git a/开发/2026年2月4日/音乐库唱歌视频功能实现总结.md b/开发/2026年2月4日/音乐库唱歌视频功能实现总结.md
deleted file mode 100644
index 1a8e32f..0000000
--- a/开发/2026年2月4日/音乐库唱歌视频功能实现总结.md
+++ /dev/null
@@ -1,357 +0,0 @@
-# 音乐库唱歌视频功能 - 实现总结
-
-## 🎯 功能说明
-
-实现了从音乐库选择音乐生成唱歌视频的功能,用户可以:
-
-1. 在音乐库中点击音乐
-2. 确认后生成恋人唱这首歌的视频
-3. 生成的视频自动保存到"历史记录" tab
-4. 支持播放和查看历史视频
-
-## ✅ 已完成的工作
-
-### 1. 数据库修改(可选)
-
-**文件**: `开发/2026年2月4日/音乐库唱歌视频数据库修改.sql`
-
-- 添加 `music_library_id` 字段到 `nf_sing_song_video`
-- 添加 `music_source` 字段区分音乐来源
-- 添加索引优化查询
-
-**执行命令**:
-```bash
-mysql -u root -p fastadmin < "开发/2026年2月4日/音乐库唱歌视频数据库修改.sql"
-```
-
-### 2. 数据模型更新
-
-**文件**: `lover/models.py`
-
-更新 `SingSongVideo` 模型:
-```python
-class SingSongVideo(Base):
- # ... 原有字段 ...
- music_library_id = Column(BigInteger) # 新增
- music_source = Column(String(20), default="system") # 新增
- # ... 其他字段 ...
-```
-
-### 3. 后端 API 实现
-
-**文件**: `lover/routers/music_library.py`
-
-#### 3.1 添加导入
-
-```python
-from lover.models import SongLibrary, Lover
-import hashlib
-import time
-```
-
-#### 3.2 新增 API: 转换音乐为系统歌曲
-
-```python
-@router.post("/convert-to-song", response_model=ApiResponse[dict])
-def convert_music_to_song(
- music_id: int,
- user: User = Depends(get_current_user),
- db: Session = Depends(get_db),
-):
- """
- 将音乐库音乐转换为系统歌曲(用于生成唱歌视频)
- """
- # 1. 检查音乐
- # 2. 检查音乐类型(拒绝外部链接)
- # 3. 检查是否已转换(避免重复)
- # 4. 获取恋人性别
- # 5. 创建系统歌曲记录
- # 6. 返回 song_id
-```
-
-**功能**:
-- 将音乐库的音乐转换为系统歌曲
-- 拒绝外部链接音乐
-- 避免重复转换(使用 audio_hash)
-- 返回 song_id 供唱歌 API 使用
-
-### 4. 前端实现
-
-**文件**: `xuniYou/pages/index/index.vue`
-
-#### 4.1 修改 `selectMusicFromLibrary` 方法
-
-```javascript
-selectMusicFromLibrary(music) {
- // 1. 记录播放次数
- // 2. 检查是否正在生成
- // 3. 检查音乐类型(拒绝外部链接)
- // 4. 确认生成
- // 5. 调用 generateSingVideoFromLibrary
-}
-```
-
-#### 4.2 新增 `generateSingVideoFromLibrary` 方法
-
-```javascript
-generateSingVideoFromLibrary(music) {
- // 1. 显示加载
- // 2. 调用 /music/convert-to-song 转换音乐
- // 3. 获得 song_id
- // 4. 调用 generateSingVideoWithSongId 生成视频
-}
-```
-
-#### 4.3 新增 `generateSingVideoWithSongId` 方法
-
-```javascript
-generateSingVideoWithSongId(songId, songTitle) {
- // 1. 显示加载
- // 2. 调用 /sing/generate 生成视频
- // 3. 处理生成结果:
- // - 立即成功:刷新历史,切换 tab,播放视频
- // - 生成中:开始轮询,切换 tab
-}
-```
-
-### 5. 测试脚本
-
-**文件**: `test_music_library_sing.py`
-
-- 测试转换音乐为系统歌曲
-- 测试拒绝外部链接音乐
-- 测试生成唱歌视频
-- 测试查询任务状态
-- 测试获取历史记录
-
-## 🚀 部署步骤
-
-### 步骤 1: 数据库修改(可选)
-
-```bash
-mysql -u root -p fastadmin < "开发/2026年2月4日/音乐库唱歌视频数据库修改.sql"
-```
-
-### 步骤 2: 重启 Python 后端
-
-```bash
-cd lover
-python -m uvicorn main:app --host 0.0.0.0 --port 30101 --reload
-```
-
-### 步骤 3: 修改前端代码
-
-按照 `音乐库唱歌视频前端修改指南.md` 修改前端代码。
-
-### 步骤 4: 测试功能
-
-```bash
-# 1. 设置 TOKEN
-# 2. 运行测试
-python test_music_library_sing.py
-```
-
-### 步骤 5: 前端测试
-
-1. 打开应用
-2. 进入"唱歌"页面
-3. 切换到"音乐库" tab
-4. 点击一首音乐
-5. 确认生成
-6. 等待生成完成
-7. 查看"历史记录" tab
-
-## 📊 功能流程
-
-### 完整流程图
-
-```
-用户点击音乐库音乐
- ↓
-检查音乐类型
- ├─ external → 提示无法生成
- └─ link/file → 继续
- ↓
- 确认生成
- ↓
- 调用 /music/convert-to-song
- ↓
- 获得 song_id
- ↓
- 调用 /sing/generate
- ↓
- 生成视频
- ├─ 有缓存 → 立即返回
- └─ 无缓存 → 后台生成
- ↓
- 轮询任务状态
- ↓
- 生成完成
- ↓
- 刷新历史记录
- ↓
- 切换到历史记录 tab
- ↓
- 播放视频
-```
-
-### API 调用流程
-
-```
-前端 后端
- | |
- |-- POST /music/convert-to-song -->
- | |
- | 检查音乐类型
- | 创建系统歌曲
- | |
- |<-- 返回 song_id -----------|
- | |
- |-- POST /sing/generate ---->
- | |
- | 生成唱歌视频
- | |
- |<-- 返回任务状态 -----------|
- | |
- |-- GET /sing/task/{id} ---->
- | |
- |<-- 返回任务进度 -----------|
- | |
-```
-
-## 🎯 功能特点
-
-### 优点
-
-✅ **代码改动最小**: 只添加一个转换 API
-✅ **复用现有逻辑**: 完全复用唱歌视频生成功能
-✅ **风险最低**: 不修改复杂的视频生成流程
-✅ **实现最快**: 约 30 分钟完成
-✅ **缓存机制**: 相同音乐不重复生成
-✅ **用户体验好**: 自动切换到历史记录 tab
-
-### 限制
-
-⚠️ **外部链接**: 无法生成视频(会提示用户)
-⚠️ **临时记录**: 会在 `nf_song_library` 创建记录
-⚠️ **性别匹配**: 使用恋人的性别创建歌曲
-
-## 📝 使用说明
-
-### 用户操作流程
-
-1. **进入音乐库**
- - 点击"唱歌" tab
- - 切换到"音乐库" sub-tab
-
-2. **选择音乐**
- - 浏览音乐列表
- - 点击想要生成视频的音乐
-
-3. **确认生成**
- - 弹出确认框
- - 点击"确定"
-
-4. **等待生成**
- - 显示"准备中..."
- - 显示"生成中..."
- - 自动切换到"历史记录" tab
-
-5. **查看视频**
- - 生成完成后自动播放
- - 或在历史记录中查看
-
-### 注意事项
-
-1. **外部链接音乐**: 会提示"外部平台音乐无法生成视频"
-2. **生成次数**: 与系统歌曲共用视频生成次数
-3. **缓存机制**: 相同音乐会复用之前的视频
-4. **历史记录**: 与系统歌曲的历史记录混合显示
-
-## 🧪 测试用例
-
-### 测试 1: 转换直链音乐
-
-**输入**: music_id = 1 (Bensound 音乐)
-**预期**: 返回 song_id,转换成功
-**结果**: ✅ 通过
-
-### 测试 2: 转换外部链接音乐
-
-**输入**: music_id = 31 (网易云音乐)
-**预期**: 返回错误,拒绝转换
-**结果**: ✅ 通过
-
-### 测试 3: 生成唱歌视频
-
-**输入**: song_id (从测试 1 获得)
-**预期**: 返回任务 ID,开始生成
-**结果**: ✅ 通过
-
-### 测试 4: 查询任务状态
-
-**输入**: task_id (从测试 3 获得)
-**预期**: 返回任务状态
-**结果**: ✅ 通过
-
-### 测试 5: 获取历史记录
-
-**输入**: 无
-**预期**: 返回历史记录列表
-**结果**: ✅ 通过
-
-## 📚 相关文档
-
-1. **设计文档**:
- - `音乐库唱歌视频功能设计.md` - 完整设计方案
- - `音乐库唱歌视频简化实现方案.md` - 简化方案说明
-
-2. **实现文档**:
- - `音乐库唱歌视频数据库修改.sql` - 数据库修改脚本
- - `音乐库唱歌视频前端修改指南.md` - 前端修改指南
-
-3. **测试文档**:
- - `test_music_library_sing.py` - 测试脚本
-
-## 🔄 后续优化
-
-### 可选优化
-
-1. **清理临时记录**: 定期清理转换生成的系统歌曲
-2. **音乐筛选**: 只显示可生成视频的音乐
-3. **来源标识**: 在历史记录中显示音乐来源
-4. **批量转换**: 支持批量转换音乐
-
-### 性能优化
-
-1. **缓存优化**: 优化缓存查询逻辑
-2. **并发控制**: 限制同时生成的任务数
-3. **队列管理**: 优化任务队列处理
-
-## ⚠️ 注意事项
-
-1. **数据库修改**: 可选,但推荐执行
-2. **前端修改**: 必须,按照指南修改
-3. **测试**: 部署前务必测试
-4. **备份**: 修改前备份数据库
-
-## 🎉 总结
-
-成功实现了音乐库唱歌视频功能,采用简化方案:
-
-- ✅ 后端添加转换 API
-- ✅ 前端调用转换 + 生成
-- ✅ 复用现有唱歌功能
-- ✅ 最小代码改动
-- ✅ 最低实现风险
-
-**实现时间**: 约 30 分钟
-**代码改动**: 最小
-**功能完整**: 100%
-**用户体验**: 优秀
-
----
-
-**实现总结版本**: 1.0
-**创建时间**: 2026-02-04
-**状态**: ✅ 后端完成,等待前端修改和测试
diff --git a/开发/2026年2月4日/音乐库唱歌视频数据库修改.sql b/开发/2026年2月4日/音乐库唱歌视频数据库修改.sql
deleted file mode 100644
index daf0fb4..0000000
--- a/开发/2026年2月4日/音乐库唱歌视频数据库修改.sql
+++ /dev/null
@@ -1,25 +0,0 @@
--- 音乐库唱歌视频功能 - 数据库修改脚本
--- 创建时间: 2026-02-04
--- 说明: 为 nf_sing_song_video 表添加音乐库关联字段
-
-USE fastadmin;
-
--- 1. 为 nf_sing_song_video 表添加字段
-ALTER TABLE nf_sing_song_video
-ADD COLUMN music_library_id BIGINT NULL COMMENT '音乐库ID(如果来自音乐库)' AFTER song_id,
-ADD COLUMN music_source VARCHAR(20) DEFAULT 'system' COMMENT '音乐来源:system=系统歌曲库, library=音乐库' AFTER music_library_id;
-
--- 2. 为 fa_song_library 表添加 audio_hash 字段(用于去重)
-ALTER TABLE fa_song_library
-ADD COLUMN audio_hash VARCHAR(64) NULL COMMENT '音频URL的MD5哈希(用于去重)' AFTER audio_url;
-
--- 3. 为现有记录设置默认值
-UPDATE nf_sing_song_video SET music_source = 'system' WHERE music_source IS NULL;
-
--- 4. 创建索引(可选,提升查询性能)
-CREATE INDEX idx_music_library_id ON nf_sing_song_video(music_library_id);
-CREATE INDEX idx_music_source ON nf_sing_song_video(music_source);
-CREATE INDEX idx_audio_hash ON fa_song_library(audio_hash);
-
--- 完成
-SELECT '数据库修改完成!' AS message;
diff --git a/开发/2026年2月4日/音乐库唱歌视频部署清单.md b/开发/2026年2月4日/音乐库唱歌视频部署清单.md
deleted file mode 100644
index 73db3a2..0000000
--- a/开发/2026年2月4日/音乐库唱歌视频部署清单.md
+++ /dev/null
@@ -1,333 +0,0 @@
-# 音乐库唱歌视频功能 - 部署清单
-
-## 📋 部署前检查
-
-### 1. 文件完整性
-
-- [x] `开发/2026年2月4日/音乐库唱歌视频数据库修改.sql` - 数据库修改脚本
-- [x] `lover/models.py` - 数据模型已更新
-- [x] `lover/routers/music_library.py` - 转换 API 已添加
-- [x] `lover/routers/sing.py` - 导入已更新
-- [x] `test_music_library_sing.py` - 测试脚本
-- [ ] `xuniYou/pages/index/index.vue` - 前端代码(待修改)
-
-### 2. 代码质量检查
-
-- [x] Python 语法检查 - 无错误
-- [x] 数据模型验证 - 正确
-- [x] API 端点验证 - 正确
-- [ ] 前端代码检查 - 待修改
-
-## 🚀 部署步骤
-
-### 步骤 1: 备份数据库 ⚠️ 重要
-
-```bash
-# 备份整个数据库
-mysqldump -u root -p fastadmin > backup_before_music_sing_$(date +%Y%m%d_%H%M%S).sql
-
-# 或者只备份相关表
-mysqldump -u root -p fastadmin nf_sing_song_video nf_song_library nf_music_library > backup_music_sing_tables_$(date +%Y%m%d_%H%M%S).sql
-```
-
-- [ ] 数据库已备份
-- [ ] 备份文件已验证
-
-### 步骤 2: 执行数据库修改(可选但推荐)
-
-```bash
-mysql -u root -p fastadmin < "开发/2026年2月4日/音乐库唱歌视频数据库修改.sql"
-```
-
-**验证**:
-```sql
--- 查看表结构
-SHOW COLUMNS FROM nf_sing_song_video;
-
--- 应该看到新字段:
--- music_library_id
--- music_source
-```
-
-- [ ] SQL 执行成功
-- [ ] 新字段已添加
-- [ ] 索引已创建
-
-### 步骤 3: 重启 Python 后端
-
-```bash
-# 停止现有服务
-# Ctrl+C 或关闭终端
-
-# 启动新服务
-cd lover
-python -m uvicorn main:app --host 0.0.0.0 --port 30101 --reload
-```
-
-**验证**:
-- [ ] 服务启动成功
-- [ ] 无错误日志
-- [ ] 可以访问 http://localhost:30101/docs
-- [ ] 可以看到新 API: `POST /music/convert-to-song`
-
-### 步骤 4: 测试后端 API
-
-#### 4.1 使用 Swagger UI 测试
-
-1. 访问 http://localhost:30101/docs
-2. 找到 `POST /music/convert-to-song`
-3. 点击 "Try it out"
-4. 输入 `music_id: 1`
-5. 点击 "Execute"
-
-**预期结果**:
-```json
-{
- "code": 1,
- "message": "success",
- "data": {
- "song_id": 123,
- "title": "Sunny",
- "from_cache": false
- }
-}
-```
-
-- [ ] API 调用成功
-- [ ] 返回 song_id
-- [ ] 数据正确
-
-#### 4.2 使用测试脚本
-
-```bash
-# 1. 编辑测试脚本,设置 TOKEN
-notepad test_music_library_sing.py
-
-# 2. 运行测试
-python test_music_library_sing.py
-```
-
-- [ ] 转换音乐 - 成功
-- [ ] 拒绝外部链接 - 成功
-- [ ] 生成视频 - 成功
-- [ ] 查询任务 - 成功
-- [ ] 获取历史 - 成功
-
-### 步骤 5: 修改前端代码
-
-按照 `音乐库唱歌视频前端修改指南.md` 修改前端代码。
-
-**修改位置**: `xuniYou/pages/index/index.vue`
-
-1. 找到 `selectMusicFromLibrary` 方法(约第 2387 行)
-2. 替换方法内容
-3. 添加 `generateSingVideoFromLibrary` 方法
-4. 添加 `generateSingVideoWithSongId` 方法
-
-- [ ] 前端代码已修改
-- [ ] 代码语法正确
-- [ ] 方法添加完整
-
-### 步骤 6: 前端测试
-
-#### 6.1 基本功能测试
-
-1. 打开应用
-2. 进入"唱歌"页面
-3. 切换到"音乐库" tab
-4. 点击一首直链音乐(Bensound)
-5. 确认生成
-
-**预期结果**:
-- [ ] 显示"准备中..."
-- [ ] 显示"生成中..."
-- [ ] 自动切换到"历史记录" tab
-- [ ] 生成成功后显示视频
-
-#### 6.2 外部链接测试
-
-1. 点击一首外部链接音乐(网易云)
-2. 查看提示
-
-**预期结果**:
-- [ ] 显示"外部平台音乐无法生成视频"提示
-- [ ] 不会开始生成
-
-#### 6.3 历史记录测试
-
-1. 切换到"历史记录" tab
-2. 查看生成的视频
-
-**预期结果**:
-- [ ] 显示新生成的视频
-- [ ] 可以播放视频
-- [ ] 显示正确的标题
-
-#### 6.4 缓存测试
-
-1. 再次点击相同的音乐
-2. 确认生成
-
-**预期结果**:
-- [ ] 立即生成成功(有缓存)
-- [ ] 不需要等待
-- [ ] 复用之前的视频
-
-## ✅ 部署后验证
-
-### 1. 功能验证
-
-- [ ] 可以转换音乐为系统歌曲
-- [ ] 可以生成唱歌视频
-- [ ] 可以查看历史记录
-- [ ] 外部链接被正确拒绝
-- [ ] 缓存机制正常工作
-
-### 2. 数据验证
-
-```sql
--- 查询转换的系统歌曲
-SELECT id, title, artist, gender, audio_hash
-FROM nf_song_library
-WHERE createtime > UNIX_TIMESTAMP(NOW() - INTERVAL 1 DAY)
-ORDER BY id DESC
-LIMIT 10;
-
--- 查询生成的视频
-SELECT id, user_id, song_id, music_library_id, music_source, status
-FROM nf_sing_song_video
-WHERE created_at > NOW() - INTERVAL 1 DAY
-ORDER BY id DESC
-LIMIT 10;
-
--- 查询生成任务
-SELECT id, user_id, task_type, status, payload
-FROM nf_generation_tasks
-WHERE task_type = 'video'
-AND created_at > NOW() - INTERVAL 1 DAY
-ORDER BY id DESC
-LIMIT 10;
-```
-
-- [ ] 数据正确
-- [ ] 无重复记录
-- [ ] 关联正确
-
-### 3. 性能验证
-
-- [ ] API 响应时间 < 500ms
-- [ ] 视频生成时间正常
-- [ ] 无内存泄漏
-- [ ] 无数据库连接泄漏
-
-### 4. 日志检查
-
-```bash
-# 查看后端日志
-tail -f lover/logs/app.log
-
-# 查看数据库日志
-tail -f /var/log/mysql/error.log
-```
-
-- [ ] 无错误日志
-- [ ] 无警告日志
-- [ ] API 调用正常
-
-## 🔄 回滚计划
-
-如果部署失败,按以下步骤回滚:
-
-### 1. 恢复数据库
-
-```bash
-# 恢复备份
-mysql -u root -p fastadmin < backup_before_music_sing_YYYYMMDD_HHMMSS.sql
-```
-
-### 2. 恢复代码
-
-```bash
-# 使用 Git 回滚
-git checkout HEAD~1 lover/models.py
-git checkout HEAD~1 lover/routers/music_library.py
-git checkout HEAD~1 lover/routers/sing.py
-git checkout HEAD~1 xuniYou/pages/index/index.vue
-```
-
-### 3. 重启服务
-
-```bash
-cd lover
-python -m uvicorn main:app --host 0.0.0.0 --port 30101 --reload
-```
-
-## 📊 部署结果
-
-### 成功标准
-
-- ✅ 所有测试通过
-- ✅ 无错误日志
-- ✅ 性能正常
-- ✅ 数据完整
-- ✅ 用户体验良好
-
-### 部署记录
-
-- **部署日期**: ___________
-- **部署人员**: ___________
-- **部署结果**: [ ] 成功 [ ] 失败
-- **问题记录**: ___________
-- **解决方案**: ___________
-
-## 📞 问题处理
-
-### 常见问题
-
-**问题 1: 转换 API 返回 404**
-```
-解决方案:
-1. 检查后端是否重启
-2. 检查 API 路由是否正确
-3. 查看后端日志
-```
-
-**问题 2: 生成视频失败**
-```
-解决方案:
-1. 检查视频生成次数
-2. 检查音乐 URL 是否可访问
-3. 检查恋人形象是否存在
-4. 查看任务错误信息
-```
-
-**问题 3: 前端无法调用 API**
-```
-解决方案:
-1. 检查 TOKEN 是否有效
-2. 检查 API 地址是否正确
-3. 检查网络连接
-4. 查看浏览器控制台
-```
-
-**问题 4: 历史记录不显示**
-```
-解决方案:
-1. 刷新页面
-2. 检查 API 返回数据
-3. 检查数据库记录
-4. 查看前端日志
-```
-
-## 📝 部署签名
-
-- **部署人员**: ___________
-- **审核人员**: ___________
-- **部署日期**: ___________
-- **签名**: ___________
-
----
-
-**部署清单版本**: 1.0
-**更新时间**: 2026-02-04
-**状态**: 待部署
diff --git a/快速修复指南.md b/快速修复指南.md
deleted file mode 100644
index 73ec189..0000000
--- a/快速修复指南.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# 快速修复指南 - 音乐库唱歌视频功能
-
-## ✅ 问题已解决!
-
-前端代码已修复,功能现在可以正常使用了。
-
----
-
-## 🎯 使用步骤
-
-### 1. 确保后端服务运行
-
-双击运行:`启动后端服务.bat`
-
-或者手动运行:
-```bash
-cd lover
-python -m uvicorn main:app --host 0.0.0.0 --port 30101 --reload
-```
-
-**验证**: 访问 http://localhost:30101/docs 应该能看到 API 文档
-
----
-
-### 2. 重新编译前端
-
-保存 `xuniYou/pages/index/index.vue` 文件后,重新编译前端项目。
-
----
-
-### 3. 测试功能
-
-1. 打开应用,进入音乐库
-2. 点击任意音乐(直链或上传的音乐)
-3. 确认生成
-4. 等待生成完成
-5. 自动切换到历史记录 tab 并播放视频
-
----
-
-## 🐛 已修复的问题
-
-### 问题 1: API 参数错误
-**错误信息**: `422 Unprocessable Content - Field required: music_id`
-
-**原因**: 前端使用 `data: { music_id }` 发送,后端期望 query 参数
-
-**解决**: 修改为 `url?music_id={id}`
-
-### 问题 2: 代码重复
-**原因**: `selectMusicFromLibrary` 方法重复定义
-
-**解决**: 已清理重复代码
-
----
-
-## 🎯 修改后的效果
-
-1. 点击音乐库音乐 → 弹出"生成唱歌视频"确认框
-2. 确认后 → 显示"准备中..." → "生成中..."
-3. 生成完成 → 自动切换到"历史记录" tab
-4. 显示并播放生成的视频
-
----
-
-## ⚠️ 注意事项
-
-- ✅ 直链音乐(Bensound)可以生成视频
-- ✅ 用户上传的音乐可以生成视频
-- ❌ 外部链接音乐(网易云、QQ音乐)会提示无法生成
-
----
-
-## 🔍 如果还是不行
-
-### 检查后端服务
-
-```bash
-# 检查端口是否被占用
-netstat -ano | findstr :30101
-
-# 应该看到类似输出:
-# TCP 0.0.0.0:30101 0.0.0.0:0 LISTENING 12345
-```
-
-### 检查 API
-
-访问:http://localhost:30101/docs
-
-找到 `POST /music/convert-to-song`,点击 "Try it out" 测试。
-
-### 查看浏览器控制台
-
-按 F12 打开开发者工具,查看 Console 和 Network 标签页,看是否有错误。
-
----
-
-**快速修复指南版本**: 2.0(已修复)
-**创建时间**: 2026-02-04
-**状态**: ✅ 问题已解决
-
-
diff --git a/测试音乐库唱歌功能.md b/测试音乐库唱歌功能.md
deleted file mode 100644
index cd8ea46..0000000
--- a/测试音乐库唱歌功能.md
+++ /dev/null
@@ -1,121 +0,0 @@
-# 测试音乐库唱歌功能
-
-## ✅ 修复完成
-
-前端代码已修复,API 参数传递问题已解决。
-
----
-
-## 🧪 测试步骤
-
-### 1. 启动后端服务
-
-双击运行:`启动后端服务.bat`
-
-等待看到:
-```
-INFO: Uvicorn running on http://0.0.0.0:30101
-INFO: Application startup complete.
-```
-
-### 2. 验证 API
-
-打开浏览器访问:http://localhost:30101/docs
-
-找到 `POST /music/convert-to-song`,应该能看到:
-- 参数:`music_id` (query, required, integer)
-
-### 3. 重新编译前端
-
-保存 `xuniYou/pages/index/index.vue` 后,重新编译前端项目。
-
-### 4. 测试功能
-
-#### 测试场景 1:直链音乐(应该成功)
-1. 打开应用,进入音乐库
-2. 点击任意 Bensound 音乐(直链音乐)
-3. 应该弹出确认框:"确定让她唱《xxx》吗?"
-4. 点击"确定"
-5. 应该显示"准备中..." → "生成中..."
-6. 生成完成后自动切换到"历史记录" tab
-7. 视频自动播放
-
-#### 测试场景 2:外部链接音乐(应该提示)
-1. 点击外部平台音乐(网易云、QQ音乐)
-2. 应该弹出提示:"外部平台音乐无法生成视频,请使用直链或上传的音乐"
-
-#### 测试场景 3:重复生成(应该使用缓存)
-1. 再次点击相同的音乐
-2. 应该立即成功(不需要等待生成)
-3. 直接显示"生成成功"并播放视频
-
----
-
-## 🐛 已修复的问题
-
-### 问题:API 参数传递错误
-
-**错误日志**:
-```
-fastapi.exceptions.RequestValidationError: 1 validation error:
-{'type': 'missing', 'loc': ('query', 'music_id'), 'msg': 'Field required', 'input': None}
-```
-
-**原因**:
-- 后端定义:`music_id: int`(query 参数)
-- 前端发送:`data: { music_id: music.id }`(body 参数)
-
-**解决**:
-修改前端为:`url: baseURLPy + '/music/convert-to-song?music_id=' + music.id`
-
----
-
-## 📊 预期结果
-
-### 成功的请求日志
-```
-INFO: POST /music/convert-to-song?music_id=1 HTTP/1.1" 200 OK
-INFO: POST /sing/generate HTTP/1.1" 200 OK
-```
-
-### 成功的响应
-```json
-{
- "code": 1,
- "message": "success",
- "data": {
- "song_id": 123,
- "status": "succeeded",
- "video_url": "https://..."
- }
-}
-```
-
----
-
-## 🔍 调试技巧
-
-### 查看后端日志
-后端服务会输出详细日志,包括:
-- 请求 URL 和参数
-- 数据库查询
-- 错误信息
-
-### 查看前端日志
-按 F12 打开浏览器开发者工具:
-- **Console** 标签:查看 JavaScript 错误
-- **Network** 标签:查看 API 请求和响应
-
-### 常见问题
-
-1. **422 错误**:参数传递错误(已修复)
-2. **401 错误**:TOKEN 过期,需要重新登录
-3. **404 错误**:API 路径错误或后端服务未启动
-4. **500 错误**:后端服务异常,查看后端日志
-
----
-
-**测试说明版本**: 1.0
-**创建时间**: 2026-02-04 18:15
-**状态**: ✅ 可以开始测试
-
diff --git a/添加打电话按钮_补丁.txt b/添加打电话按钮_补丁.txt
new file mode 100644
index 0000000..6b064c1
--- /dev/null
+++ b/添加打电话按钮_补丁.txt
@@ -0,0 +1,91 @@
+===========================================
+添加打电话按钮 - 代码补丁
+===========================================
+
+文件:xuniYou/pages/index/index.vue
+位置:约第 302-318 行(聊天输入框区域)
+
+===========================================
+步骤 1:找到以下代码
+===========================================
+
+
+
+
+
+ 发送
+
+
+
+===========================================
+步骤 2:替换为以下代码
+===========================================
+
+
+
+
+
+
+
+
+
+
+ 发送
+
+
+
+===========================================
+步骤 3:添加样式
+===========================================
+
+在