feat: secure analysis PLY sampling

This commit is contained in:
2026-08-13 18:16:57 +08:00
parent 29a69793db
commit 9f4482bea1
5 changed files with 350 additions and 61 deletions

View File

@@ -1890,21 +1890,32 @@ export default function PointCloudViewer({
setPlyOverlayStatus({ status: 'loading', message: '正在加载 PLY 叠加层' });
(async () => {
try {
// 用 /sample/batch 接口按 filePath 采样,最多 30 万点
// 支持多文件(各面板 PLY),合并采样
// 通过受限的分析结果接口加载相对 PLY 路径,支持多文件合并采样
const plyFiles: string[] = Array.isArray(overlay.files) && overlay.files.length > 0
? overlay.files
: [overlay.file];
const resp = await fetch('/api/files/sample/batch', {
const resp = await fetch('/api/files/sample/analysis', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
signal: ac.signal,
body: JSON.stringify({
filePaths: plyFiles.map(f => `/data/tasks/${taskId}/analysis/${resultId}/${f}`),
taskId,
resultId,
files: plyFiles,
totalPoints: 300_000,
}),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
if (!resp.ok) {
const payload = await resp.json().catch(() => null) as { error?: string } | null;
const detail = payload?.error;
if (resp.status === 404) {
throw new Error(detail ?? '分析结果或 PLY 文件不存在');
}
if (resp.status === 400 || resp.status === 403) {
throw new Error(detail ?? 'PLY 文件参数或访问权限无效');
}
throw new Error(detail ?? `服务端采样失败(HTTP ${resp.status})`);
}
const hasColor = resp.headers.get('X-Has-Color') === '1';
const stride = hasColor ? 7 : 4;
const buf = await resp.arrayBuffer();
@@ -1950,7 +1961,10 @@ export default function PointCloudViewer({
if (e instanceof DOMException && e.name === 'AbortError') return;
console.warn('[PlyOverlay] load failed:', e);
setPlyOverlayData(null);
setPlyOverlayStatus({ status: 'error', message: '加载 PLY 叠加层失败' });
setPlyOverlayStatus({
status: 'error',
message: e instanceof Error ? e.message : '服务端采样失败,请稍后重试',
});
}
})();
return () => ac.abort();