66 lines
2.6 KiB
TypeScript
66 lines
2.6 KiB
TypeScript
/** 与后端及 asset-sync 的公开资源配置保持一致;此处不包含上传凭据。 */
|
|
export const assetConfig = {
|
|
publicDomain: 'https://cdn.joeyone.cn',
|
|
keyPrefix: 'doodle-assets/',
|
|
};
|
|
|
|
/** 固定字体和页面预览使用相对路径;接口返回的资源 URL 直接使用。 */
|
|
export function publicAssetUrl(relativePath: string): string {
|
|
const parts = relativePath.split('/');
|
|
if (
|
|
relativePath.includes('\\') ||
|
|
[...relativePath].some(
|
|
(char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127,
|
|
) ||
|
|
parts.some((part) => !part || part === '.' || part === '..')
|
|
)
|
|
throw new Error('资源相对路径不正确');
|
|
const domain = assetConfig.publicDomain.replace(/\/+$/, '');
|
|
const prefix = assetConfig.keyPrefix.replace(/\/+$/, '');
|
|
return `${domain}/${[...(prefix ? prefix.split('/') : []), ...parts].map(encodeURIComponent).join('/')}`;
|
|
}
|
|
|
|
const LEGACY_PREVIEW_PREFIX =
|
|
'cloud://cloud1-9gifs7a2756e2c87.636c-cloud1-9gifs7a2756e2c87-1351593184/';
|
|
const LEGACY_PREVIEW_HTTPS =
|
|
/^https?:\/\/636c-cloud1-9gifs7a2756e2c87-1351593184\.tcb\.qcloud\.la\/([^?#]+)(?:[?#].*)?$/;
|
|
|
|
/** 旧预拉取、缓存和云函数中的预览图指向已迁移的公开目录;其他资源不作替换。 */
|
|
export function resolvePreviewUrl(value: unknown): string {
|
|
if (typeof value !== 'string') return '';
|
|
const raw = value.trim();
|
|
let path: string;
|
|
if (raw.startsWith(LEGACY_PREVIEW_PREFIX)) {
|
|
path = raw.slice(LEGACY_PREVIEW_PREFIX.length);
|
|
if (/[?#]/.test(path)) return '';
|
|
} else {
|
|
const match = raw.match(LEGACY_PREVIEW_HTTPS);
|
|
if (!match) return raw;
|
|
path = match[1];
|
|
}
|
|
try {
|
|
path = path.split('/').map(decodeURIComponent).join('/');
|
|
if (!path.startsWith('assets/previews/')) return raw;
|
|
return publicAssetUrl(path);
|
|
} catch {
|
|
// 畸形路径不继续请求旧云存储,也不生成越界的 CDN 地址。
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/** 统一处理预拉取/缓存中的嵌套 previewImg;不修改原数据、头像和其他云文件引用。 */
|
|
export function normalizePreviewImages<T>(value: T): T {
|
|
if (Array.isArray(value)) {
|
|
return value.map((item) => normalizePreviewImages(item)) as T;
|
|
}
|
|
if (!value || typeof value !== 'object') return value;
|
|
const result = { ...value } as Record<string, unknown>;
|
|
for (const key of Object.keys(result)) {
|
|
result[key] =
|
|
key === 'previewImg'
|
|
? resolvePreviewUrl(result[key])
|
|
: normalizePreviewImages(result[key]);
|
|
}
|
|
return result as T;
|
|
}
|