From 155c9f824c68b6809c5251e6b32f17fb75b49fd6 Mon Sep 17 00:00:00 2001 From: xiao12feng8 <16507319+xiao12feng8@user.noreply.gitee.com> Date: Wed, 4 Feb 2026 19:26:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=9A=E6=B8=85=E7=90=86?= =?UTF-8?q?=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 4 +- PHP服务连接问题解决方案.md | 199 +++++++++ check_gift_images.py | 174 -------- check_outfit_images.py | 205 ---------- copy_outfit_images.bat | 112 ----- deploy_music_library.bat | 48 --- lover/config.py | 2 +- lover/deps.py | 25 +- start_php_service_30100.bat | 18 - test_external_music_api.py | 246 ----------- test_invite_code.py | 123 ------ test_music_library_sing.py | 230 ----------- test_music_links.py | 131 ------ test_outfit_api.py | 115 ------ test_sing_history.py | 42 -- upload_gifts_to_oss.py | 200 --------- upload_outfit_to_oss.py | 181 -------- upload_outfit_to_oss_final.py | 196 --------- xuniYou_index_vue_patch.txt | 205 ---------- ...You_pages_index_index_vue_修改后的代码.txt | 170 -------- 修改前端代码说明.md | 239 ----------- 前端代码_完整替换版.js | 170 -------- 历史记录修复说明.md | 125 ------ 启动后端服务.bat | 12 - 启动项目.bat | 216 ++++++++++ 启动项目_改进版.bat | 263 ++++++++++++ 并发控制说明.md | 183 --------- 开发/2026年2月3日/PHP连接泄漏问题修复.md | 385 ------------------ 开发/2026年2月4日/db_update.sql | 16 - 开发/2026年2月4日/今日工作总结_最终版.md | 192 --------- .../2026年2月4日/音乐库唱歌视频功能实现总结.md | 357 ---------------- .../2026年2月4日/音乐库唱歌视频数据库修改.sql | 25 -- 开发/2026年2月4日/音乐库唱歌视频部署清单.md | 333 --------------- 快速修复指南.md | 102 ----- 测试音乐库唱歌功能.md | 121 ------ 添加打电话按钮_补丁.txt | 91 +++++ 清理端口.bat | 87 ++++ 诊断问题.bat | 82 ++++ 邀请码功能修复说明.md | 275 ------------- 39 files changed, 962 insertions(+), 4938 deletions(-) create mode 100644 PHP服务连接问题解决方案.md delete mode 100644 check_gift_images.py delete mode 100644 check_outfit_images.py delete mode 100644 copy_outfit_images.bat delete mode 100644 deploy_music_library.bat delete mode 100644 start_php_service_30100.bat delete mode 100644 test_external_music_api.py delete mode 100644 test_invite_code.py delete mode 100644 test_music_library_sing.py delete mode 100644 test_music_links.py delete mode 100644 test_outfit_api.py delete mode 100644 test_sing_history.py delete mode 100644 upload_gifts_to_oss.py delete mode 100644 upload_outfit_to_oss.py delete mode 100644 upload_outfit_to_oss_final.py delete mode 100644 xuniYou_index_vue_patch.txt delete mode 100644 xuniYou_pages_index_index_vue_修改后的代码.txt delete mode 100644 修改前端代码说明.md delete mode 100644 前端代码_完整替换版.js delete mode 100644 历史记录修复说明.md delete mode 100644 启动后端服务.bat create mode 100644 启动项目.bat create mode 100644 启动项目_改进版.bat delete mode 100644 并发控制说明.md delete mode 100644 开发/2026年2月3日/PHP连接泄漏问题修复.md delete mode 100644 开发/2026年2月4日/db_update.sql delete mode 100644 开发/2026年2月4日/今日工作总结_最终版.md delete mode 100644 开发/2026年2月4日/音乐库唱歌视频功能实现总结.md delete mode 100644 开发/2026年2月4日/音乐库唱歌视频数据库修改.sql delete mode 100644 开发/2026年2月4日/音乐库唱歌视频部署清单.md delete mode 100644 快速修复指南.md delete mode 100644 测试音乐库唱歌功能.md create mode 100644 添加打电话按钮_补丁.txt create mode 100644 清理端口.bat create mode 100644 诊断问题.bat delete mode 100644 邀请码功能修复说明.md 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:添加样式 +=========================================== + +在