优化docker

This commit is contained in:
2026-06-30 16:31:03 +08:00
parent 68bc72ff2a
commit 908bad097c
10 changed files with 5020 additions and 7832 deletions
+2
View File
@@ -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
+4 -4
View File
@@ -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();
+4 -4
View File
@@ -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, {
+38
View File
@@ -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 });
}
}
+1
View File
@@ -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
+26
View File
@@ -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;
}
-7821
View File
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -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",
+4939
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
allowBuilds:
sharp: set this to true or false
onlyBuiltDependencies:
- sharp
- unrs-resolver