0.2 在线版本打通

This commit is contained in:
liaibo
2026-01-15 20:54:21 +08:00
parent 725a60d3aa
commit 94957dc361
1596 changed files with 153254 additions and 309 deletions
+85 -5
View File
@@ -1,9 +1,65 @@
import { NextResponse } from "next/server";
import { createSupabaseRouteClient } from "@/lib/supabase/server";
import { rewriteToPublicOrigin } from "@/lib/url/rewritePublicOrigin";
import { getMnoteRuntimeConfig } from "@/lib/runtime-config";
import supabaseAdmin from "@/lib/supabase/admin";
export const dynamic = "force-dynamic";
const isUuid = (value: string) =>
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
const resolveAssetObjectLocation = async (params: {
bucket?: string | null;
storagePath?: string | null;
workspaceId?: string | null;
fileName?: string | null;
}) => {
const { bucket, storagePath, workspaceId, fileName } = params;
const ws = (workspaceId ?? "").trim();
const fn = (fileName ?? "").trim();
if (!ws || !isUuid(ws) || !fn) return null;
try {
const storage = supabaseAdmin.schema("storage");
// 1) 若已给出 bucket/path,先校验是否存在
const b = (bucket ?? "").trim();
const p = (storagePath ?? "").trim();
if (b && p) {
const { data: exact, error: exactError } = await storage
.from("objects")
.select("bucket_id,name")
.eq("bucket_id", b)
.eq("name", p)
.limit(1);
if (!exactError && exact && exact.length > 0) {
return { bucket: b, path: p };
}
}
// 2) 兼容历史数据:按 workspace_id/%/file_name 查找
const pattern = `${ws}/%/${fn}`;
const { data: candidates, error: candError } = await storage
.from("objects")
.select("bucket_id,name,created_at")
.like("name", pattern)
.order("created_at", { ascending: false })
.limit(20);
if (candError || !candidates || candidates.length === 0) return null;
const exactCandidate =
candidates.find((o) => String(o?.name || "").endsWith(`/${fn}`)) ??
candidates[0];
if (!exactCandidate?.bucket_id || !exactCandidate?.name) return null;
return { bucket: String(exactCandidate.bucket_id), path: String(exactCandidate.name) };
} catch {
return null;
}
};
export async function GET(request: Request) {
const supabase = await createSupabaseRouteClient();
const {
@@ -33,6 +89,30 @@ export async function GET(request: Request) {
return NextResponse.json({ error: "资源不存在" }, { status: 404 });
}
// 兼容:早期/异常写入的 media_assets 可能缺少 bucket/storage_path
// 导致生成的签名链接指向不存在的对象(ONLYOFFICE 会报“下载失败”)。
let bucket = (asset.bucket as string | null) ?? null;
let storagePath = (asset.storage_path as string | null) ?? null;
if (!bucket || !storagePath) {
const resolved = await resolveAssetObjectLocation({
bucket,
storagePath,
workspaceId: asset.workspace_id ?? null,
fileName: asset.file_name ?? null,
});
if (!resolved) {
return NextResponse.json({ error: "未找到存储对象,无法生成签名链接" }, { status: 404 });
}
bucket = resolved.bucket;
storagePath = resolved.path;
// 尽量回填,避免后续重复修复
await supabase
.from("media_assets")
.update({ bucket, storage_path: storagePath })
.eq("id", asset.id);
}
// 对于图片,添加高质量参数以获得更好的显示效果
// Supabase Storage 支持通过 URL 参数控制图片质量和尺寸
const isImage = asset.mime_type?.startsWith("image/");
@@ -42,8 +122,8 @@ export async function GET(request: Request) {
// 使用 createSignedUrl 并添加高质量参数
// 注意:transform 参数需要在签名时指定
const { data: signedUrlData, error: signError } = await supabase.storage
.from(asset.bucket)
.createSignedUrl(asset.storage_path, 60 * 60, {
.from(bucket)
.createSignedUrl(storagePath, 60 * 60, {
// 添加转换参数以获得高质量图片
transform: {
quality: 95, // 高质量
@@ -58,8 +138,8 @@ export async function GET(request: Request) {
} else {
// 非图片文件,直接生成签名 URL
const { data: signedUrlData, error: signError } = await supabase.storage
.from(asset.bucket)
.createSignedUrl(asset.storage_path, 60 * 60);
.from(bucket)
.createSignedUrl(storagePath, 60 * 60);
if (signError || !signedUrlData) {
return NextResponse.json({ error: "生成签名 URL 失败" }, { status: 500 });
@@ -67,7 +147,7 @@ export async function GET(request: Request) {
signedUrl = signedUrlData.signedUrl;
}
signedUrl = rewriteToPublicOrigin(signedUrl, process.env.NEXT_PUBLIC_SUPABASE_URL);
signedUrl = rewriteToPublicOrigin(signedUrl, getMnoteRuntimeConfig().supabaseUrl);
return NextResponse.json({
signedUrl,