Improve WeChat MP replies and ship MindSpace/H5 production updates.
Add WeChat service account routing with sync acks, connectivity tests, and context isolation; document deploy runbooks; and bundle related MindSpace, voice, Plaza, and server gateway changes for production rollout. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+45
-1
@@ -30,6 +30,7 @@ const ALLOWED_EXTENSIONS = new Map([
|
||||
['.html', 'text/html'],
|
||||
['.htm', 'text/html'],
|
||||
]);
|
||||
const MAX_IMAGE_UPLOAD_BYTES = 1536 * 1024;
|
||||
|
||||
function asNumber(value) {
|
||||
return Number(value ?? 0);
|
||||
@@ -141,6 +142,9 @@ export function validateUploadRequest({ filename, sizeBytes, maxFileBytes = DEFA
|
||||
if (!mimeType) {
|
||||
throw Object.assign(new Error('暂不支持该文件类型'), { code: 'unsupported_file_type' });
|
||||
}
|
||||
if (mimeType.startsWith('image/') && normalizedSize > MAX_IMAGE_UPLOAD_BYTES) {
|
||||
throw Object.assign(new Error('图片文件超过单文件大小限制'), { code: 'file_too_large' });
|
||||
}
|
||||
return { filename: normalizedFilename, sizeBytes: normalizedSize, expectedMimeType: mimeType };
|
||||
}
|
||||
|
||||
@@ -176,6 +180,37 @@ export function createAssetService(pool, options = {}) {
|
||||
return resolved;
|
||||
};
|
||||
|
||||
const resolveStoragePathCandidates = (storageKey) => {
|
||||
const normalized = String(storageKey ?? '').replace(/\\/g, '/');
|
||||
const withoutMd = normalized.endsWith('.md') ? normalized.slice(0, -3) : normalized;
|
||||
const match = withoutMd.match(/^users\/([^/]+)\/(assets|pages)\/([^/]+)\/(v\d+)$/);
|
||||
if (!match) return [normalized];
|
||||
const [, userId, scope, entityId, versionTag] = match;
|
||||
return [
|
||||
normalized,
|
||||
`users/${userId}/${scope}/${entityId}/versions/${versionTag}.md`,
|
||||
`users/${userId}/${scope}/${entityId}/versions/${versionTag}`,
|
||||
].filter((candidate, index, list) => list.indexOf(candidate) === index);
|
||||
};
|
||||
|
||||
const resolveReadableStoragePath = async (storageKey) => {
|
||||
let lastError = null;
|
||||
for (const candidate of resolveStoragePathCandidates(storageKey)) {
|
||||
const absolutePath = absoluteStoragePath(candidate);
|
||||
try {
|
||||
await fs.stat(absolutePath);
|
||||
return absolutePath;
|
||||
} catch (error) {
|
||||
if (error?.code === 'ENOENT') {
|
||||
lastError = error;
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
throw lastError ?? Object.assign(new Error('存储文件不存在'), { code: 'storage_not_found' });
|
||||
};
|
||||
|
||||
const createUpload = async (userId, input) => {
|
||||
const validated = validateUploadRequest({ ...input, maxFileBytes });
|
||||
const conn = await pool.getConnection();
|
||||
@@ -293,6 +328,9 @@ export function createAssetService(pool, options = {}) {
|
||||
if (!detectedMimeType) {
|
||||
throw Object.assign(new Error('无法确认文件类型'), { code: 'unsupported_file_type' });
|
||||
}
|
||||
if (detectedMimeType.startsWith('image/') && buffer.length > MAX_IMAGE_UPLOAD_BYTES) {
|
||||
throw Object.assign(new Error('图片文件超过单文件大小限制'), { code: 'file_too_large' });
|
||||
}
|
||||
|
||||
const target = absoluteStoragePath(upload.temporary_storage_key);
|
||||
await fs.mkdir(path.dirname(target), { recursive: true });
|
||||
@@ -644,7 +682,10 @@ export function createAssetService(pool, options = {}) {
|
||||
const asset = rows[0];
|
||||
if (!asset) throw Object.assign(new Error('资产不存在'), { code: 'asset_not_found' });
|
||||
assertAssetDownloadable(asset);
|
||||
return { asset: assetResponse(asset), path: absoluteStoragePath(asset.storage_key) };
|
||||
return {
|
||||
asset: assetResponse(asset),
|
||||
path: await resolveReadableStoragePath(asset.storage_key),
|
||||
};
|
||||
};
|
||||
|
||||
const createChatAsset = async (
|
||||
@@ -662,6 +703,9 @@ export function createAssetService(pool, options = {}) {
|
||||
if (!detectedMimeType) {
|
||||
throw Object.assign(new Error('无法确认文件类型'), { code: 'unsupported_file_type' });
|
||||
}
|
||||
if (detectedMimeType.startsWith('image/') && buffer.length > MAX_IMAGE_UPLOAD_BYTES) {
|
||||
throw Object.assign(new Error('图片文件超过单文件大小限制'), { code: 'file_too_large' });
|
||||
}
|
||||
const conn = await pool.getConnection();
|
||||
let finalPath;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user