From 8228776b8c10b92688afb4c9aa3bc0f7a384291f Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 17 Sep 2026 21:01:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(files):=20=E4=BF=AE=E5=A4=8D=20304=20?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=9C=AA=E9=94=80=E6=AF=81=E6=B5=81=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E7=9A=84=20S3=20=E8=BF=9E=E6=8E=A5=E6=B1=A0=E6=B3=84?= =?UTF-8?q?=E6=BC=8F=E4=B8=8E=E5=9B=BE=E7=89=87=E6=8C=82=E8=B5=B7=E5=8D=A1?= =?UTF-8?q?=E6=AD=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/src/routes/files.ts | 21 ++++++++++++++++ .../services/file-storage/object-storage.ts | 24 ++++++++++++++----- .../work-order-material-mappers.ts | 1 - apps/frontend/src/styles/worker.css | 1 - apps/frontend/src/types/consignment.ts | 1 - 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/apps/backend/src/routes/files.ts b/apps/backend/src/routes/files.ts index 80dbbcab..02242826 100644 --- a/apps/backend/src/routes/files.ts +++ b/apps/backend/src/routes/files.ts @@ -1,3 +1,4 @@ +import type { Readable } from 'node:stream' import { Router } from 'express' import { @@ -9,14 +10,18 @@ import { buildNotFoundPayload, sendRouteError } from '../utils/http.js' const router = Router() router.get('/object', async (req, res) => { + let readerToDestroy: Readable | null = null try { const rawKey = String(req.query.key || '').trim() const isPublic = isPublicObjectKey(rawKey) const object = await getStoredFileObject(req.query.key, req.query.variant) + readerToDestroy = object.reader if (object.etag) { res.setHeader('ETag', object.etag) if (req.headers['if-none-match'] === object.etag) { + object.reader.destroy() + readerToDestroy = null res.status(304).end() return } @@ -26,12 +31,17 @@ router.get('/object', async (req, res) => { if (req.headers['if-modified-since']) { const ifModifiedSince = new Date(req.headers['if-modified-since']) if (!isNaN(ifModifiedSince.getTime()) && object.lastModified <= ifModifiedSince) { + object.reader.destroy() + readerToDestroy = null res.status(304).end() return } } } + if (object.size > 0) { + res.setHeader('Content-Length', object.size) + } res.setHeader('Content-Type', object.contentType || 'application/octet-stream') res.setHeader('X-Content-Type-Options', 'nosniff') @@ -41,7 +51,14 @@ router.get('/object', async (req, res) => { res.setHeader('Cache-Control', 'private, max-age=300') } + res.on('close', () => { + if (!res.writableEnded) { + object.reader.destroy() + } + }) + object.reader.on('error', (error) => { + object.reader.destroy() if (!res.headersSent) { sendRouteError(res, error, '读取文件失败', '[files/object]') } else { @@ -49,7 +66,11 @@ router.get('/object', async (req, res) => { } }) object.reader.pipe(res) + readerToDestroy = null } catch (error) { + if (readerToDestroy) { + readerToDestroy.destroy() + } sendRouteError(res, error, '读取文件失败', '[files/object]') } }) diff --git a/apps/backend/src/services/file-storage/object-storage.ts b/apps/backend/src/services/file-storage/object-storage.ts index 85b17d55..6644ab4e 100644 --- a/apps/backend/src/services/file-storage/object-storage.ts +++ b/apps/backend/src/services/file-storage/object-storage.ts @@ -1,3 +1,5 @@ +import http from 'node:http' +import https from 'node:https' import { Readable } from 'node:stream' import { @@ -10,6 +12,7 @@ import { PutObjectCommand, S3Client, } from '@aws-sdk/client-s3' +import { NodeHttpHandler } from '@smithy/node-http-handler' import { runtimeConfig } from '../../config/runtime.js' import { createHttpError } from '../../utils/http.js' @@ -65,6 +68,12 @@ export class ObjectStorage { accessKeyId: String(config.accessKeyId || '').trim(), secretAccessKey: String(config.secretAccessKey || '').trim(), }, + requestHandler: new NodeHttpHandler({ + connectionTimeout: 5000, + requestTimeout: 15000, + httpAgent: new http.Agent({ maxSockets: 200, keepAlive: true }), + httpsAgent: new https.Agent({ maxSockets: 200, keepAlive: true }), + }), // 默认的 CRC32 请求校验头不被 OSS 的 S3 兼容层支持,关闭以保持兼容。 requestChecksumCalculation: 'WHEN_REQUIRED', responseChecksumValidation: 'WHEN_REQUIRED', @@ -175,6 +184,12 @@ export class ObjectStorage { return } + // 阿里云 OSS 桶由控制台预先创建,跳过 HeadBucketCommand 避免 RAM 权限受限或挂起阻塞 + if (!this.pathStyle) { + this.bucketReady = true + return + } + if (!this.bucketReadyPromise) { this.bucketReadyPromise = (async () => { try { @@ -183,11 +198,6 @@ export class ObjectStorage { if (!isNotFoundError(error)) { throw error } - if (!this.pathStyle) { - throw new Error(`OSS bucket ${this.bucket} 不存在,请先在阿里云控制台创建`, { - cause: error, - }) - } await this.client.send(new CreateBucketCommand({ Bucket: this.bucket })) } this.bucketReady = true @@ -303,7 +313,9 @@ function toObjectStorageConfig(config: { export function normalizeStorageEndpoint(rawEndpoint: unknown) { const endpoint = String(rawEndpoint || '').trim() - const parsed = new URL(/^https?:\/\//i.test(endpoint) ? endpoint : `http://${endpoint}`) + const isOss = endpoint.toLowerCase().includes('.aliyuncs.com') + const defaultScheme = isOss ? 'https://' : 'http://' + const parsed = new URL(/^https?:\/\//i.test(endpoint) ? endpoint : `${defaultScheme}${endpoint}`) const port = parsed.port ? Number(parsed.port) : parsed.protocol === 'https:' ? 443 : 80 return { diff --git a/apps/backend/src/services/worker-platform/work-order-material-mappers.ts b/apps/backend/src/services/worker-platform/work-order-material-mappers.ts index 2ced6785..aaa2088c 100644 --- a/apps/backend/src/services/worker-platform/work-order-material-mappers.ts +++ b/apps/backend/src/services/worker-platform/work-order-material-mappers.ts @@ -110,4 +110,3 @@ export function resolveCollectSubmittedFieldValues(workOrder: WorkOrderRow) { : {} return normalizeSubmittedFields(collect.fields) } - diff --git a/apps/frontend/src/styles/worker.css b/apps/frontend/src/styles/worker.css index 2f39b933..3d0ec129 100644 --- a/apps/frontend/src/styles/worker.css +++ b/apps/frontend/src/styles/worker.css @@ -4511,4 +4511,3 @@ body { padding-top: 6px; border-top: 1px dashed #cbd5e1; } - diff --git a/apps/frontend/src/types/consignment.ts b/apps/frontend/src/types/consignment.ts index ebfc7726..b7f0403a 100644 --- a/apps/frontend/src/types/consignment.ts +++ b/apps/frontend/src/types/consignment.ts @@ -85,7 +85,6 @@ export type ConsignmentBuyerInfo = { screenshots: string[] } - /** * 工单 `material.consignment` 里的寄售履约信息(履约/指派时后端写入)。 * 历史老单没有 sourceSubmissionNo,展示时必须回退到 inventoryNo。