mirror of
https://github.com/remvze/moodist.git
synced 2025-12-19 09:54:17 +00:00
feat: 升级到 v3.0.0 - 独立音乐播放系统与 Docker 数据持久化
🎉 版本升级: 2.5.0 → 3.0.0 🎵 音乐播放系统重构: - 独立的音乐播放系统,不影响当前选中声音 - 修复 React Hooks 调用错误 - 使用直接 Howl API 实现音频控制 - 添加播放/停止状态视觉反馈 - 组件显示逻辑完全分离 🐳 Docker 部署优化: - 所有 compose 文件添加 SQLite 数据库挂载 - 支持 WAL 模式和并发写入 - 数据持久化,容器重启不丢失数据 - 创建详细的 Docker 数据库挂载文档 🎨 UI/UX 改进: - 修复当前选中声音与音乐列表显示互斥问题 - 播放按钮状态动态显示 - 组件模块完全独立展示 🗄️ 数据库性能优化: - 启用 WAL 模式提高并发性能 - 优化 SQLite 配置参数 - 添加详细日志和错误处理 📦 新增文件: - docker-database-mount.md: Docker 数据库挂载说明文档
This commit is contained in:
parent
26f6619904
commit
a3c95ec19b
12 changed files with 350 additions and 88 deletions
BIN
data/users.db
BIN
data/users.db
Binary file not shown.
BIN
data/users.db-shm
Normal file
BIN
data/users.db-shm
Normal file
Binary file not shown.
BIN
data/users.db-wal
Normal file
BIN
data/users.db-wal
Normal file
Binary file not shown.
|
|
@ -31,6 +31,8 @@ services:
|
||||||
- .:/app
|
- .:/app
|
||||||
- /app/node_modules # 防止node_modules被覆盖
|
- /app/node_modules # 防止node_modules被覆盖
|
||||||
- moodist-dist:/app/dist
|
- moodist-dist:/app/dist
|
||||||
|
# 挂载 SQLite 数据库文件目录
|
||||||
|
- ./data:/app/data:rw
|
||||||
|
|
||||||
# 工作目录
|
# 工作目录
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,14 @@ services:
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
|
|
||||||
# 只读根文件系统
|
# 数据卷挂载
|
||||||
|
volumes:
|
||||||
|
# 挂载 SQLite 数据库文件目录(需要读写权限)
|
||||||
|
- ./data:/app/data:rw
|
||||||
|
# 挂载临时目录用于 SQLite WAL 文件
|
||||||
|
- moodist-temp:/tmp:rw
|
||||||
|
|
||||||
|
# 只读根文件系统(除了数据目录)
|
||||||
read_only: true
|
read_only: true
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /var/cache/nginx
|
- /var/cache/nginx
|
||||||
|
|
@ -76,6 +83,10 @@ services:
|
||||||
profiles:
|
profiles:
|
||||||
- proxy
|
- proxy
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
moodist-temp:
|
||||||
|
driver: local
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
moodist-network:
|
moodist-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,8 @@ services:
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- '8080:8080'
|
- '8080:8080'
|
||||||
|
volumes:
|
||||||
|
# 挂载 SQLite 数据库文件和 WAL 文件
|
||||||
|
- ./data:/app/data:rw
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
|
|
||||||
113
docker-database-mount.md
Normal file
113
docker-database-mount.md
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# Docker 数据库挂载说明
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本项目已配置 SQLite 数据库文件挂载,确保数据在容器重启后不会丢失。
|
||||||
|
|
||||||
|
## 数据库文件位置
|
||||||
|
|
||||||
|
SQLite 数据库文件位于项目的 `./data` 目录中:
|
||||||
|
- `./data/users.db` - 主数据库文件
|
||||||
|
- `./data/users.db-wal` - Write-Ahead Log 文件
|
||||||
|
- `./data/users.db-shm` - 共享内存文件
|
||||||
|
|
||||||
|
## Docker Compose 配置
|
||||||
|
|
||||||
|
### 1. 基础配置 (`docker-compose.yml`)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
moodist:
|
||||||
|
volumes:
|
||||||
|
# 挂载 SQLite 数据库文件和 WAL 文件
|
||||||
|
- ./data:/app/data:rw
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 优化配置 (`docker-compose.optimized.yml`)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
moodist:
|
||||||
|
volumes:
|
||||||
|
# 挂载 SQLite 数据库文件目录(需要读写权限)
|
||||||
|
- ./data:/app/data:rw
|
||||||
|
# 挂载临时目录用于 SQLite WAL 文件
|
||||||
|
- moodist-temp:/tmp:rw
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
moodist-temp:
|
||||||
|
driver: local
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 开发配置 (`docker-compose.dev.yml`)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
moodist-dev:
|
||||||
|
volumes:
|
||||||
|
# 挂载 SQLite 数据库文件目录
|
||||||
|
- ./data:/app/data:rw
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 生产环境
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 优化环境
|
||||||
|
docker-compose -f docker-compose.optimized.yml up -d
|
||||||
|
|
||||||
|
# 开发环境
|
||||||
|
docker-compose -f docker-compose.dev.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据持久化
|
||||||
|
|
||||||
|
- 数据库文件会自动创建在 `./data` 目录中
|
||||||
|
- 容器重启或重新创建后数据不会丢失
|
||||||
|
- 支持数据库备份和迁移
|
||||||
|
|
||||||
|
### 备份数据库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 备份数据库
|
||||||
|
cp ./data/users.db ./data/users.db.backup.$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
# 查看数据库文件
|
||||||
|
ls -la ./data/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **权限问题**: 确保 `./data` 目录有正确的读写权限
|
||||||
|
2. **WAL 模式**: SQLite 使用 WAL (Write-Ahead Logging) 模式,会产生额外的 WAL 和 SHM 文件
|
||||||
|
3. **并发访问**: Docker 挂载确保文件系统的一致性
|
||||||
|
4. **备份策略**: 建议定期备份数据库文件
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 数据库锁定问题
|
||||||
|
如果遇到数据库锁定错误:
|
||||||
|
1. 停止容器:`docker-compose down`
|
||||||
|
2. 删除 WAL 文件:`rm ./data/users.db-wal ./data/users.db-shm`
|
||||||
|
3. 重新启动容器:`docker-compose up -d`
|
||||||
|
|
||||||
|
### 权限问题
|
||||||
|
如果遇到权限错误:
|
||||||
|
```bash
|
||||||
|
# 设置正确的目录权限
|
||||||
|
sudo chown -R 1000:1000 ./data
|
||||||
|
chmod 755 ./data
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发环境注意事项
|
||||||
|
|
||||||
|
开发环境中,数据库文件会被实时同步到本地文件系统,便于:
|
||||||
|
- 调试和测试
|
||||||
|
- 数据分析
|
||||||
|
- 快速重置测试数据
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "moodist",
|
"name": "moodist",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.5.0",
|
"version": "3.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useMemo, useState, useEffect } from 'react';
|
import { useMemo, useState, useEffect, useRef } from 'react';
|
||||||
import { AnimatePresence, motion } from 'motion/react';
|
import { AnimatePresence, motion } from 'motion/react';
|
||||||
import { FaSave, FaPlay, FaTrash, FaEdit, FaCog, FaSignOutAlt, FaMusic, FaChevronDown, FaChevronRight } from 'react-icons/fa/index';
|
import { FaSave, FaPlay, FaStop, FaTrash, FaEdit, FaCog, FaSignOutAlt, FaMusic, FaChevronDown, FaChevronRight } from 'react-icons/fa/index';
|
||||||
import { SaveMusicButton } from '@/components/buttons/save-music/save-music';
|
import { SaveMusicButton } from '@/components/buttons/save-music/save-music';
|
||||||
import { DeleteMusicButton } from '@/components/buttons/delete-music/delete-music';
|
import { DeleteMusicButton } from '@/components/buttons/delete-music/delete-music';
|
||||||
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { useLocalizedSounds } from '@/hooks/useLocalizedSounds';
|
||||||
import { useTranslation } from '@/hooks/useTranslation';
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { ApiClient } from '@/lib/api-client';
|
import { ApiClient } from '@/lib/api-client';
|
||||||
|
import { Howl } from 'howler';
|
||||||
|
|
||||||
import { Sound } from '@/components/sounds/sound';
|
import { Sound } from '@/components/sounds/sound';
|
||||||
import styles from '../sounds/sounds.module.css';
|
import styles from '../sounds/sounds.module.css';
|
||||||
|
|
@ -42,6 +43,11 @@ export function SelectedSoundsDisplay() {
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [musicName, setMusicName] = useState('');
|
const [musicName, setMusicName] = useState('');
|
||||||
|
|
||||||
|
// 独立的音乐播放状态
|
||||||
|
const [currentlyPlayingMusic, setCurrentlyPlayingMusic] = useState<SavedMusic | null>(null);
|
||||||
|
const musicHowlInstances = useRef<Record<string, Howl>>({});
|
||||||
|
const [isPlayingMusic, setIsPlayingMusic] = useState(false);
|
||||||
|
|
||||||
// 获取声音store
|
// 获取声音store
|
||||||
const sounds = useSoundStore(state => state.sounds);
|
const sounds = useSoundStore(state => state.sounds);
|
||||||
|
|
||||||
|
|
@ -65,34 +71,112 @@ export function SelectedSoundsDisplay() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取声音store的操作函数
|
// 获取声音store的操作函数(仅用于控制主要播放状态)
|
||||||
const unselectAll = useSoundStore(state => state.unselectAll);
|
const play = useSoundStore(state => state.play);
|
||||||
const select = useSoundStore(state => state.select);
|
const pause = useSoundStore(state => state.pause);
|
||||||
|
|
||||||
// 播放音乐记录 - 清空当前选择并加载音乐的声音配置
|
// 停止音乐播放
|
||||||
|
const stopMusic = () => {
|
||||||
|
console.log('🛑 停止音乐播放');
|
||||||
|
|
||||||
|
// 停止所有音乐相关的 Howl 实例
|
||||||
|
Object.values(musicHowlInstances.current).forEach(howlInstance => {
|
||||||
|
if (howlInstance) {
|
||||||
|
howlInstance.stop();
|
||||||
|
howlInstance.unload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
musicHowlInstances.current = {};
|
||||||
|
setCurrentlyPlayingMusic(null);
|
||||||
|
setIsPlayingMusic(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 播放音乐记录 - 使用独立的音乐播放系统,不影响当前选中声音
|
||||||
const playMusicRecord = async (music: SavedMusic) => {
|
const playMusicRecord = async (music: SavedMusic) => {
|
||||||
try {
|
try {
|
||||||
// 清空当前所有选择
|
console.log('🎵 开始播放音乐:', music.name);
|
||||||
unselectAll();
|
console.log('🎵 音乐数据:', {
|
||||||
|
sounds: music.sounds,
|
||||||
// 根据音乐记录重新选择声音并设置参数
|
volume: music.volume,
|
||||||
for (const [soundId, volume] of Object.entries(music.volume)) {
|
speed: music.speed,
|
||||||
const speed = music.speed[soundId] || 1;
|
rate: music.rate,
|
||||||
const rate = music.rate[soundId] || 1;
|
random_effects: music.random_effects
|
||||||
const randomEffect = music.random_effects[soundId] || false;
|
|
||||||
|
|
||||||
// 选择声音并设置参数
|
|
||||||
select(soundId, {
|
|
||||||
volume,
|
|
||||||
speed,
|
|
||||||
rate,
|
|
||||||
randomEffect
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 先停止当前播放的音乐
|
||||||
|
stopMusic();
|
||||||
|
|
||||||
|
// 停止主要的选中声音播放(但不改变选中状态)
|
||||||
|
pause();
|
||||||
|
|
||||||
|
// 获取所有声音数据
|
||||||
|
const allSounds = localizedCategories
|
||||||
|
.map(category => category.sounds)
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
// 创建所有声音的 Howl 实例
|
||||||
|
const howlPromises: Promise<Howl>[] = [];
|
||||||
|
|
||||||
|
for (const soundId of music.sounds) {
|
||||||
|
const soundData = allSounds.find(s => s.id === soundId);
|
||||||
|
if (!soundData || !soundData.src) continue;
|
||||||
|
|
||||||
|
const volume = music.volume[soundId] || 0.5;
|
||||||
|
const rate = music.rate[soundId] || 1;
|
||||||
|
const speed = music.speed[soundId] || 1;
|
||||||
|
|
||||||
|
console.log(`🔊 创建音乐声音: ${soundId}`, { volume, rate, speed });
|
||||||
|
|
||||||
|
// 创建 Howl 实例的 Promise
|
||||||
|
const howlPromise = new Promise<Howl>((resolve, reject) => {
|
||||||
|
const howl = new Howl({
|
||||||
|
src: [soundData.src],
|
||||||
|
loop: true,
|
||||||
|
volume: volume,
|
||||||
|
rate: rate,
|
||||||
|
preload: true,
|
||||||
|
onload: () => {
|
||||||
|
console.log(`✅ 声音加载完成: ${soundId}`);
|
||||||
|
resolve(howl);
|
||||||
|
},
|
||||||
|
onloaderror: (id, error) => {
|
||||||
|
console.error(`❌ 声音加载失败: ${soundId}`, error);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存实例引用
|
||||||
|
musicHowlInstances.current[soundId] = howl;
|
||||||
|
});
|
||||||
|
|
||||||
|
howlPromises.push(howlPromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`🎵 播放音乐记录: ${music.name}`);
|
// 等待所有声音加载完成
|
||||||
|
console.log('⏳ 等待所有声音加载...');
|
||||||
|
await Promise.all(howlPromises);
|
||||||
|
console.log('✅ 所有声音加载完成,开始播放');
|
||||||
|
|
||||||
|
// 播放所有声音
|
||||||
|
Object.values(musicHowlInstances.current).forEach(howlInstance => {
|
||||||
|
if (howlInstance && howlInstance.state() === 'loaded') {
|
||||||
|
howlInstance.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置播放状态
|
||||||
|
setCurrentlyPlayingMusic(music);
|
||||||
|
setIsPlayingMusic(true);
|
||||||
|
|
||||||
|
// 展开对应的音乐记录
|
||||||
|
setExpandedMusic(new Set([music.id]));
|
||||||
|
setExpandedCurrent(false); // 收起当前选中声音模块
|
||||||
|
|
||||||
|
console.log(`✅ 播放音乐记录完成: ${music.name}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 播放音乐记录失败:', error);
|
console.error('❌ 播放音乐记录失败:', error);
|
||||||
|
stopMusic();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -253,6 +337,13 @@ export function SelectedSoundsDisplay() {
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, user]);
|
}, [isAuthenticated, user]);
|
||||||
|
|
||||||
|
// 组件卸载时清理音乐播放
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
stopMusic();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 监听音乐列表数量,超过5个时默认收起
|
// 监听音乐列表数量,超过5个时默认收起
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (savedMusicList.length > 5) {
|
if (savedMusicList.length > 5) {
|
||||||
|
|
@ -262,14 +353,15 @@ export function SelectedSoundsDisplay() {
|
||||||
}
|
}
|
||||||
}, [savedMusicList.length]);
|
}, [savedMusicList.length]);
|
||||||
|
|
||||||
// 如果没有选中的声音,不渲染组件
|
// 如果既没有选中声音,也没有音乐列表,则不渲染组件
|
||||||
if (selectedSounds.length === 0) {
|
if (selectedSounds.length === 0 && (!isAuthenticated || savedMusicList.length === 0)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
{/* 当前选中声音模块 */}
|
{/* 当前选中声音模块 - 只有选中声音时才显示 */}
|
||||||
|
{selectedSounds.length > 0 && (
|
||||||
<div className={styles.currentSoundsModule}>
|
<div className={styles.currentSoundsModule}>
|
||||||
<div className={styles.currentSoundsHeader}>
|
<div className={styles.currentSoundsHeader}>
|
||||||
<h4 className={styles.currentSoundsTitle}>
|
<h4 className={styles.currentSoundsTitle}>
|
||||||
|
|
@ -322,6 +414,7 @@ export function SelectedSoundsDisplay() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 音乐列表模块 - 只有登录用户且有音乐时才显示 */}
|
{/* 音乐列表模块 - 只有登录用户且有音乐时才显示 */}
|
||||||
{isAuthenticated && savedMusicList.length > 0 && (
|
{isAuthenticated && savedMusicList.length > 0 && (
|
||||||
|
|
@ -400,6 +493,21 @@ export function SelectedSoundsDisplay() {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.musicContent}>
|
<div className={styles.musicContent}>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (currentlyPlayingMusic?.id === music.id) {
|
||||||
|
stopMusic();
|
||||||
|
} else {
|
||||||
|
playMusicRecord(music);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`${styles.playButton} ${
|
||||||
|
currentlyPlayingMusic?.id === music.id ? styles.playing : ''
|
||||||
|
}`}
|
||||||
|
title={currentlyPlayingMusic?.id === music.id ? "停止播放" : "播放这首音乐"}
|
||||||
|
>
|
||||||
|
{currentlyPlayingMusic?.id === music.id ? <FaStop /> : <FaPlay />}
|
||||||
|
</button>
|
||||||
<div className={styles.musicInfo}>
|
<div className={styles.musicInfo}>
|
||||||
<div className={styles.musicName}>{music.name}</div>
|
<div className={styles.musicName}>{music.name}</div>
|
||||||
<div className={styles.soundNames}>
|
<div className={styles.soundNames}>
|
||||||
|
|
@ -443,18 +551,6 @@ export function SelectedSoundsDisplay() {
|
||||||
{/* 展开时显示的声音内容 */}
|
{/* 展开时显示的声音内容 */}
|
||||||
{expandedMusic.has(music.id) && (
|
{expandedMusic.has(music.id) && (
|
||||||
<div className={styles.expandedMusicContent}>
|
<div className={styles.expandedMusicContent}>
|
||||||
{/* 播放按钮 */}
|
|
||||||
<div className={styles.expandedMusicActions}>
|
|
||||||
<button
|
|
||||||
onClick={() => playMusicRecord(music)}
|
|
||||||
className={styles.playMusicButton}
|
|
||||||
title="播放这首音乐"
|
|
||||||
>
|
|
||||||
<FaPlay />
|
|
||||||
播放
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 声音组件展示 */}
|
{/* 声音组件展示 */}
|
||||||
<div className={styles.sounds}>
|
<div className={styles.sounds}>
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,15 @@
|
||||||
background: var(--color-foreground-subtle);
|
background: var(--color-foreground-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.playButton.playing {
|
||||||
|
background: #e74c3c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playButton.playing:hover {
|
||||||
|
background: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
/* 音乐名称 */
|
/* 音乐名称 */
|
||||||
.musicName {
|
.musicName {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,17 @@ export function getDatabase(): Database.Database {
|
||||||
fs.mkdirSync(dbDir, { recursive: true });
|
fs.mkdirSync(dbDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
db = new Database(dbPath);
|
// 以读写模式打开数据库,启用WAL模式提高并发性能
|
||||||
|
db = new Database(dbPath, {
|
||||||
|
readonly: false,
|
||||||
|
fileMustExist: false,
|
||||||
|
verbose: console.log // 添加SQL执行日志
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启用WAL模式,提高并发写入性能
|
||||||
|
db.pragma('journal_mode = WAL');
|
||||||
|
db.pragma('synchronous = NORMAL');
|
||||||
|
db.pragma('cache_size = 1000');
|
||||||
|
|
||||||
// 创建用户表
|
// 创建用户表
|
||||||
db.exec(`
|
db.exec(`
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
const { data } = bodyResult;
|
const { data } = bodyResult;
|
||||||
const { name, sounds, volume, speed, rate, random_effects } = data;
|
const { name, sounds, volume, speed, rate, random_effects } = data;
|
||||||
|
|
||||||
|
console.log('🎵 保存音乐请求:', { userId: user?.id, name, soundsCount: sounds?.length });
|
||||||
|
|
||||||
// 验证输入
|
// 验证输入
|
||||||
if (!name || !sounds || !Array.isArray(sounds)) {
|
if (!name || !sounds || !Array.isArray(sounds)) {
|
||||||
return new Response(JSON.stringify({
|
return new Response(JSON.stringify({
|
||||||
|
|
@ -47,6 +49,8 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
random_effects: random_effects || {},
|
random_effects: random_effects || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('✅ 音乐保存成功:', { id: music.id, name: music.name });
|
||||||
|
|
||||||
return new Response(JSON.stringify({
|
return new Response(JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
message: '音乐保存成功',
|
message: '音乐保存成功',
|
||||||
|
|
@ -61,6 +65,18 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('❌ 保存音乐API错误:', error);
|
||||||
|
|
||||||
|
// 处理特定的数据库错误
|
||||||
|
if (error instanceof Error && error.message.includes('readonly')) {
|
||||||
|
return new Response(JSON.stringify({
|
||||||
|
error: '数据库权限错误,请检查文件权限'
|
||||||
|
}), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return handleApiError(error, '保存音乐');
|
return handleApiError(error, '保存音乐');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Loading…
Add table
Reference in a new issue