diff --git a/.gitignore b/.gitignore index db7b752..a52f1af 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ qwen3-tts-frontend/.env.local CLAUDE.md 样本.mp3 aliyun.md +nginx.conf +deploy.md \ No newline at end of file diff --git a/qwen3-tts-backend/api/jobs.py b/qwen3-tts-backend/api/jobs.py index 34319e3..4b61323 100644 --- a/qwen3-tts-backend/api/jobs.py +++ b/qwen3-tts-backend/api/jobs.py @@ -9,7 +9,9 @@ from slowapi.util import get_remote_address from core.database import get_db from core.config import settings +from core.security import decode_access_token from db.models import Job, JobStatus, User +from db.crud import get_user_by_username from api.auth import get_current_user logger = logging.getLogger(__name__) @@ -18,6 +20,42 @@ router = APIRouter(prefix="/jobs", tags=["jobs"]) limiter = Limiter(key_func=get_remote_address) +async def get_user_from_token_or_query( + request: Request, + token: Optional[str] = Query(None), + db: Session = Depends(get_db) +) -> User: + auth_token = None + + auth_header = request.headers.get("Authorization") + if auth_header and auth_header.startswith("Bearer "): + auth_token = auth_header.split(" ")[1] + elif token: + auth_token = token + + if not auth_token: + raise HTTPException( + status_code=401, + detail="Missing authentication token" + ) + + username = decode_access_token(auth_token) + if username is None: + raise HTTPException( + status_code=401, + detail="Invalid or expired token" + ) + + user = get_user_by_username(db, username=username) + if user is None: + raise HTTPException( + status_code=401, + detail="User not found" + ) + + return user + + @router.get("/{job_id}") @limiter.limit("30/minute") async def get_job( @@ -140,7 +178,7 @@ async def delete_job( async def download_job_output( request: Request, job_id: int, - current_user: User = Depends(get_current_user), + current_user: User = Depends(get_user_from_token_or_query), db: Session = Depends(get_db) ): job = db.query(Job).filter(Job.id == job_id).first() diff --git a/qwen3-tts-backend/config.py b/qwen3-tts-backend/config.py index 6110973..8700e75 100644 --- a/qwen3-tts-backend/config.py +++ b/qwen3-tts-backend/config.py @@ -12,7 +12,7 @@ class Settings(BaseSettings): DATABASE_URL: str = Field(default="sqlite:///./qwen_tts.db") CACHE_DIR: str = Field(default="./voice_cache") OUTPUT_DIR: str = Field(default="./outputs") - BASE_URL: str = Field(default="http://localhost:8000") + BASE_URL: str = Field(default="") MODEL_DEVICE: str = Field(default="cuda:0") MODEL_BASE_PATH: str = Field(default="../Qwen") diff --git a/qwen3-tts-backend/main.py b/qwen3-tts-backend/main.py index 7644dac..1d9130a 100644 --- a/qwen3-tts-backend/main.py +++ b/qwen3-tts-backend/main.py @@ -119,13 +119,14 @@ app = FastAPI( app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) +if settings.LOG_LEVEL == "debug": + app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) app.include_router(auth.router) app.include_router(jobs.router) diff --git a/qwen3-tts-frontend/public/vite.svg b/qwen3-tts-frontend/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/qwen3-tts-frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/qwen3-tts-frontend/src/components/AudioPlayer.tsx b/qwen3-tts-frontend/src/components/AudioPlayer.tsx index e78a6c2..ad630da 100644 --- a/qwen3-tts-frontend/src/components/AudioPlayer.tsx +++ b/qwen3-tts-frontend/src/components/AudioPlayer.tsx @@ -11,15 +11,34 @@ interface AudioPlayerProps { jobId: number } +const isMobileDevice = () => { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) +} + const AudioPlayer = memo(({ audioUrl, jobId }: AudioPlayerProps) => { const [blobUrl, setBlobUrl] = useState('') const [isLoading, setIsLoading] = useState(false) const [loadError, setLoadError] = useState(null) + const [useMobileMode, setUseMobileMode] = useState(false) const previousAudioUrlRef = useRef('') + useEffect(() => { + setUseMobileMode(isMobileDevice()) + }, []) + useEffect(() => { if (!audioUrl || audioUrl === previousAudioUrlRef.current) return + if (useMobileMode) { + const token = localStorage.getItem('token') + const separator = audioUrl.includes('?') ? '&' : '?' + const urlWithToken = token ? `${audioUrl}${separator}token=${token}` : audioUrl + setBlobUrl(urlWithToken) + previousAudioUrlRef.current = audioUrl + setIsLoading(false) + return + } + let active = true const prevBlobUrl = blobUrl @@ -55,7 +74,7 @@ const AudioPlayer = memo(({ audioUrl, jobId }: AudioPlayerProps) => { return () => { active = false } - }, [audioUrl]) + }, [audioUrl, useMobileMode]) useEffect(() => { return () => { diff --git a/qwen3-tts-frontend/src/lib/api.ts b/qwen3-tts-frontend/src/lib/api.ts index f500364..887a3a9 100644 --- a/qwen3-tts-frontend/src/lib/api.ts +++ b/qwen3-tts-frontend/src/lib/api.ts @@ -342,6 +342,10 @@ export const jobApi = { getAudioUrl: (id: number, audioPath?: string): string => { if (audioPath) { if (audioPath.startsWith('http')) { + if (audioPath.includes('localhost') || audioPath.includes('127.0.0.1')) { + const url = new URL(audioPath) + return `${import.meta.env.VITE_API_URL}${url.pathname}` + } return audioPath } else { const baseUrl = import.meta.env.VITE_API_URL diff --git a/qwen3-tts-frontend/src/lib/constants.ts b/qwen3-tts-frontend/src/lib/constants.ts index f24d919..c5a5ea3 100644 --- a/qwen3-tts-frontend/src/lib/constants.ts +++ b/qwen3-tts-frontend/src/lib/constants.ts @@ -93,7 +93,7 @@ export const PRESET_INSTRUCTS = [ }, { label: '温柔关怀', - instruct: '温柔体贴的女性声音,语速平缓,音调柔和,充满关怀和安慰', + instruct: '温柔体贴,语速平缓,音调柔和,充满关怀和安慰', text: '别担心,一切都会好起来的。我会一直陪在你身边。', }, { @@ -108,17 +108,17 @@ export const PRESET_INSTRUCTS = [ }, { label: '专业播音员', - instruct: '专业新闻播音员。性别:女性。音高:中等偏高,音域稳定。语速:标准播音语速,吐字清晰。音量:适中,音色饱满。情绪:沉稳专业,不带个人感情色彩。语调:平直中略有起伏,重点词汇加重。性格特征:严谨、客观、权威。', + instruct: '专业新闻播音员。语速:标准播音语速,吐字清晰。情绪:沉稳专业,不带个人感情色彩。语调:平直中略有起伏,重点词汇加重。性格特征:严谨、客观、权威。', text: '据新华社报道,我国航天事业取得重大突破,神舟系列飞船成功完成载人飞行任务。', }, { label: '温暖导师', - instruct: '温暖的中年女性导师。音色:温和醇厚,带有亲和力。语速:不急不缓,娓娓道来。音调:平稳中带有鼓励性上扬。情绪:关怀、耐心、鼓励。性格:善解人意,循循善诱,充满正能量。适合场景:心理咨询、教育引导。', + instruct: '温暖导师。语速:不急不缓,娓娓道来。音调:平稳中带有鼓励性上扬。情绪:关怀、耐心、鼓励。性格:善解人意,循循善诱,充满正能量。', text: '每个人都有自己的节奏,不要着急。慢慢来,你一定能找到属于自己的那条路。', }, { label: '活力少年', - instruct: '充满活力的青少年男性。音高:略高,富有朝气。语速:偏快,吐字利落。音量:响亮明快。情绪:开朗乐观,精力充沛。语调:跳跃感强,抑扬顿挫。性格:外向、自信、热情,充满青春气息。', + instruct: '充满活力。语速:偏快,吐字利落。情绪:开朗乐观,精力充沛。语调:跳跃感强,抑扬顿挫。性格:外向、自信、热情,充满青春气息。', text: '哇,这个游戏太酷了!咱们组队一起玩吧,我保证带你们飞!', }, ] as const diff --git a/qwen3-tts-frontend/vite.config.ts b/qwen3-tts-frontend/vite.config.ts index cbb8a60..3303259 100644 --- a/qwen3-tts-frontend/vite.config.ts +++ b/qwen3-tts-frontend/vite.config.ts @@ -14,6 +14,7 @@ export default defineConfig({ output: { manualChunks: { 'react-vendor': ['react', 'react-dom', 'react-router-dom'], + 'icons': ['lucide-react'], 'ui-vendor': [ '@radix-ui/react-tabs', '@radix-ui/react-label',