优化docker
This commit is contained in:
@@ -13,6 +13,7 @@ ENV NEXT_TELEMETRY_DISABLED=1
|
|||||||
|
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
|
RUN mkdir -p public/audio
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM ${NODE_IMAGE} AS runner
|
FROM ${NODE_IMAGE} AS runner
|
||||||
@@ -23,6 +24,7 @@ ENV NODE_ENV=production
|
|||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ENV HOSTNAME=0.0.0.0
|
ENV HOSTNAME=0.0.0.0
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
|
ENV AUDIO_DIRECTORY=/app/public/audio
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm config set registry ${NPM_REGISTRY} && npm ci --omit=dev && npm cache clean --force
|
RUN npm config set registry ${NPM_REGISTRY} && npm ci --omit=dev && npm cache clean --force
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { existsSync, mkdirSync } from "node:fs";
|
import { existsSync, mkdirSync } from "node:fs";
|
||||||
import { writeFile } from "node:fs/promises";
|
import { writeFile } from "node:fs/promises";
|
||||||
import path from "node:path";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { EdgeTTS } from "edge-tts-universal";
|
import { EdgeTTS } from "edge-tts-universal";
|
||||||
|
import { getAudioFilePath, getAudioPublicPath } from "@/lib/audio";
|
||||||
|
|
||||||
export const runtime = "nodejs";
|
export const runtime = "nodejs";
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
@@ -14,14 +14,14 @@ export async function POST(request: NextRequest) {
|
|||||||
const rate = String(body.rate ?? "-10%");
|
const rate = String(body.rate ?? "-10%");
|
||||||
const text = String(body.text ?? "Hello, welcome to English PK.");
|
const text = String(body.text ?? "Hello, welcome to English PK.");
|
||||||
|
|
||||||
const audioDirectory = path.join(process.cwd(), "public", "audio", "preview");
|
const audioDirectory = getAudioFilePath("preview");
|
||||||
if (!existsSync(audioDirectory)) {
|
if (!existsSync(audioDirectory)) {
|
||||||
mkdirSync(audioDirectory, { recursive: true });
|
mkdirSync(audioDirectory, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileName = `tts-preview-${Date.now()}.mp3`;
|
const fileName = `tts-preview-${Date.now()}.mp3`;
|
||||||
const filePath = path.join(audioDirectory, fileName);
|
const filePath = getAudioFilePath("preview", fileName);
|
||||||
const publicPath = `/audio/preview/${fileName}`;
|
const publicPath = getAudioPublicPath(`preview/${fileName}`);
|
||||||
|
|
||||||
const tts = new EdgeTTS(text, voice, { rate });
|
const tts = new EdgeTTS(text, voice, { rate });
|
||||||
const result = await tts.synthesize();
|
const result = await tts.synthesize();
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { existsSync, mkdirSync } from "node:fs";
|
import { existsSync, mkdirSync } from "node:fs";
|
||||||
import { writeFile } from "node:fs/promises";
|
import { writeFile } from "node:fs/promises";
|
||||||
import path from "node:path";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { EdgeTTS } from "edge-tts-universal";
|
import { EdgeTTS } from "edge-tts-universal";
|
||||||
|
import { getAudioDirectory, getAudioFilePath, getAudioPublicPath } from "@/lib/audio";
|
||||||
import { findWord, getSettings, updateWordAudio } from "@/lib/db";
|
import { findWord, getSettings, updateWordAudio } from "@/lib/db";
|
||||||
|
|
||||||
export const runtime = "nodejs";
|
export const runtime = "nodejs";
|
||||||
@@ -22,15 +22,15 @@ export async function POST(_request: NextRequest, context: RouteContext) {
|
|||||||
return NextResponse.json({ message: "词库不存在" }, { status: 404 });
|
return NextResponse.json({ message: "词库不存在" }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const audioDirectory = path.join(process.cwd(), "public", "audio");
|
const audioDirectory = getAudioDirectory();
|
||||||
if (!existsSync(audioDirectory)) {
|
if (!existsSync(audioDirectory)) {
|
||||||
mkdirSync(audioDirectory, { recursive: true });
|
mkdirSync(audioDirectory, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const safeEnglish = word.english.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
const safeEnglish = word.english.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
||||||
const fileName = `${word.id}-${safeEnglish || "word"}.mp3`;
|
const fileName = `${word.id}-${safeEnglish || "word"}.mp3`;
|
||||||
const filePath = path.join(audioDirectory, fileName);
|
const filePath = getAudioFilePath(fileName);
|
||||||
const publicPath = `/audio/${fileName}`;
|
const publicPath = getAudioPublicPath(fileName);
|
||||||
|
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
const tts = new EdgeTTS(word.english, settings.ttsVoice, {
|
const tts = new EdgeTTS(word.english, settings.ttsVoice, {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { stat, readFile } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { resolveAudioFilePath } from "@/lib/audio";
|
||||||
|
|
||||||
|
export const runtime = "nodejs";
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
type RouteContext = {
|
||||||
|
params: Promise<{ path: string[] }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function GET(_request: NextRequest, context: RouteContext) {
|
||||||
|
const { path: audioPath } = await context.params;
|
||||||
|
const filePath = resolveAudioFilePath(audioPath);
|
||||||
|
|
||||||
|
if (!filePath || path.extname(filePath).toLowerCase() !== ".mp3") {
|
||||||
|
return new NextResponse("Not found", { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileStat = await stat(filePath);
|
||||||
|
if (!fileStat.isFile()) {
|
||||||
|
return new NextResponse("Not found", { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = await readFile(filePath);
|
||||||
|
return new NextResponse(new Uint8Array(file), {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "audio/mpeg",
|
||||||
|
"Content-Length": String(fileStat.size),
|
||||||
|
"Cache-Control": "public, max-age=31536000, immutable",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return new NextResponse("Not found", { status: 404 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
NEXT_TELEMETRY_DISABLED: "1"
|
NEXT_TELEMETRY_DISABLED: "1"
|
||||||
|
AUDIO_DIRECTORY: /app/public/audio
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
- ./public/audio:/app/public/audio
|
- ./public/audio:/app/public/audio
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const audioPathPrefix = "/audio";
|
||||||
|
|
||||||
|
export function getAudioDirectory() {
|
||||||
|
return process.env.AUDIO_DIRECTORY || path.join(/*turbopackIgnore: true*/ process.cwd(), "public", "audio");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAudioPublicPath(fileName: string) {
|
||||||
|
return `${audioPathPrefix}/${fileName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAudioFilePath(...segments: string[]) {
|
||||||
|
return path.join(getAudioDirectory(), ...segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveAudioFilePath(segments: string[]) {
|
||||||
|
const audioDirectory = path.resolve(/*turbopackIgnore: true*/ getAudioDirectory());
|
||||||
|
const filePath = path.resolve(audioDirectory, ...segments);
|
||||||
|
|
||||||
|
if (filePath !== audioDirectory && !filePath.startsWith(`${audioDirectory}${path.sep}`)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
Generated
-7821
File diff suppressed because it is too large
Load Diff
+1
-3
@@ -7,8 +7,7 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint"
|
||||||
"tauri": "tauri"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-dialog": "^1.1.17",
|
"@radix-ui/react-dialog": "^1.1.17",
|
||||||
@@ -25,7 +24,6 @@
|
|||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.0.0",
|
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/react": "^19.0.1",
|
"@types/react": "^19.0.1",
|
||||||
"@types/react-dom": "^19.0.2",
|
"@types/react-dom": "^19.0.2",
|
||||||
|
|||||||
Generated
+4939
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
|||||||
|
allowBuilds:
|
||||||
|
sharp: set this to true or false
|
||||||
|
onlyBuiltDependencies:
|
||||||
|
- sharp
|
||||||
|
- unrs-resolver
|
||||||
Reference in New Issue
Block a user