Files
leaudit-platform-frontend/app/routes/api.collabora.wopi.files.$.tsx

112 lines
3.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* WOPI 协议 API 路由(Splat路由,支持多级路径)
*
* 功能:
* - CheckFileInfo: GET /api/collabora/wopi/files/{...fileId}
* - GetFile: GET /api/collabora/wopi/files/{...fileId}/contents
* - PutFile: POST /api/collabora/wopi/files/{...fileId}/contents
*
* 注意:使用splat路由($)匹配多级文件路径,如 documents/mz/合同文档/2025/test.docx
*
* @encoding UTF-8
*/
import { type LoaderFunctionArgs, type ActionFunctionArgs } from '@remix-run/node';
import { WopiService } from '../services/collabora.wopi.server';
const wopiService = new WopiService();
/**
* GET 请求处理
* - 无 /contents 后缀 → CheckFileInfo
* - 有 /contents 后缀 → GetFile
*/
export async function loader({ request, params }: LoaderFunctionArgs) {
try {
const url = new URL(request.url);
const accessToken = url.searchParams.get('access_token');
if (!accessToken) {
return new Response('访问令牌缺失', { status: 401 });
}
// 获取文件 ID(使用 splat 参数 '*'
let fileId = params['*'] || '';
// 判断是否是 GetFile 请求(路径以 /contents 结尾)
const isContentsRequest = url.pathname.endsWith('/contents');
// 如果是 GetFile 请求,需要移除路径末尾的 /contents
if (isContentsRequest && fileId.endsWith('/contents')) {
fileId = fileId.slice(0, -9); // 移除 '/contents'
}
if (isContentsRequest) {
// GetFile: 返回文件内容
const { buffer, metadata } = await wopiService.getFile(fileId, accessToken);
return new Response(buffer, {
headers: {
'Content-Type': metadata.contentType,
'Content-Length': metadata.size.toString(),
'Content-Disposition': 'inline',
},
});
}
// CheckFileInfo: 返回文件元数据
const checkFileInfo = await wopiService.checkFileInfo(fileId, accessToken);
// 注意:CheckFileInfo 必须返回纯 JSON,不能使用 Result.success() 包装
return Response.json(checkFileInfo);
} catch (error) {
console.error('WOPI GET 失败:', error);
return new Response(
error instanceof Error ? error.message : 'Internal server error',
{ status: 500 }
);
}
}
/**
* POST 请求处理
* - PutFile: 保存文件内容
*/
export async function action({ request, params }: ActionFunctionArgs) {
try {
const url = new URL(request.url);
const accessToken = url.searchParams.get('access_token');
if (!accessToken) {
return new Response('访问令牌缺失', { status: 401 });
}
// 获取文件 ID(使用 splat 参数 '*'
let fileId = params['*'] || '';
// 判断是否是 PutFile 请求(路径以 /contents 结尾)
const isContentsRequest = url.pathname.endsWith('/contents');
if (!isContentsRequest) {
return new Response('PutFile 必须使用 /contents 路径', { status: 400 });
}
// 移除路径末尾的 /contents
if (fileId.endsWith('/contents')) {
fileId = fileId.slice(0, -9);
}
// PutFile: 保存文件
const fileBuffer = await request.arrayBuffer();
await wopiService.putFile(fileId, accessToken, fileBuffer);
return new Response(null, { status: 200 });
} catch (error) {
console.error('WOPI POST 失败:', error);
return new Response(
error instanceof Error ? error.message : 'Internal server error',
{ status: 500 }
);
}
}