gongxiangp2/lib/utils.ts
chenpei 29115dddb8 反编译恢复源码:阶段1 - 核心API和工具库
- 反编译 app/api/materials/route.ts (素材列表和删除API)
- 反编译 app/api/materials/stats/route.ts (统计API)
- 新增 lib/material-log.ts (素材操作日志工具)
- 新增 lib/utils.ts (URL规范化工具)

这是从生产系统2.2.0版本编译代码反编译恢复的第一批文件
2025-12-20 18:10:47 +08:00

159 lines
4.2 KiB
TypeScript
Raw 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.

/**
* URL 规范化工具
* 将内网地址转换为公网地址
*/
const LOCALHOST_ADDRESSES = new Set(['localhost', '127.0.0.1', '::1']);
/**
* 获取公网访问配置
*/
function getPublicAccessConfig() {
const s3PublicDomain = (process.env.S3_PUBLIC_DOMAIN || '').trim();
const serverIp = (process.env.SERVER_IP || '').trim();
const minioApiPort = (process.env.MINIO_API_PORT || '9000').trim();
const appPublicUrl = (process.env.APP_PUBLIC_URL || process.env.NEXTAUTH_URL || '').trim();
let publicHost: string | null = null;
let publicPort = minioApiPort || '9000';
let protocol = 'http:';
// 优先使用 S3_PUBLIC_DOMAIN
if (s3PublicDomain) {
try {
const domainUrl = s3PublicDomain.match(/^https?:\/\//i)
? s3PublicDomain
: `https://${s3PublicDomain}`;
const url = new URL(domainUrl);
publicHost = url.hostname;
protocol = url.protocol;
publicPort = url.port ? url.port : (protocol === 'https:' ? '443' : '80');
// 如果是有效的公网域名,直接返回
if (publicHost && !LOCALHOST_ADDRESSES.has(publicHost)) {
// 如果是 http 协议,强制改为 https
if (protocol !== 'https:') {
protocol = 'https:';
if (!url.port) {
publicPort = '443';
}
}
return { protocol, host: publicHost, port: publicPort };
}
} catch (e) {
// 解析失败,继续尝试其他配置
}
}
// 使用 SERVER_IP
if (serverIp && !LOCALHOST_ADDRESSES.has(serverIp)) {
publicHost = serverIp;
protocol = 'http:';
publicPort = minioApiPort || '9000';
}
// 最后尝试从 APP_PUBLIC_URL 提取
if (!publicHost && appPublicUrl) {
try {
const url = new URL(appPublicUrl);
if (!LOCALHOST_ADDRESSES.has(url.hostname)) {
publicHost = url.hostname;
protocol = url.protocol;
// 如果 hostname 和 SERVER_IP 相同,使用 MinIO 端口
if (publicHost === serverIp) {
protocol = 'http:';
publicPort = minioApiPort || '9000';
} else {
publicPort = url.port || (protocol === 'https:' ? '443' : '80');
}
}
} catch (e) {
// 解析失败
}
}
if (!publicHost) {
return null;
}
return { protocol, host: publicHost, port: publicPort };
}
/**
* 规范化 URL
* 将内网地址(localhost, 127.0.0.1, 192.168.x.x, 10.x.x.x)转换为公网地址
*/
export function normalizeUrl(url: string | null | undefined): string | null {
if (!url || typeof url !== 'string') {
return url ?? null;
}
const trimmedUrl = url.trim();
if (!trimmedUrl) {
return null;
}
// 如果不是 HTTP/HTTPS URL,直接返回
if (!/^https?:\/\//i.test(trimmedUrl)) {
return trimmedUrl;
}
const publicConfig = getPublicAccessConfig();
if (!publicConfig) {
return trimmedUrl;
}
try {
const urlObj = new URL(trimmedUrl);
const isLocalhost = LOCALHOST_ADDRESSES.has(urlObj.hostname);
const isPrivateIp = /^192\.168\.|^10\.|^172\.(1[6-9]|2[0-9]|3[01])\./.test(urlObj.hostname);
// 如果是内网地址,替换为公网地址
if (isLocalhost || isPrivateIp) {
urlObj.protocol = publicConfig.protocol;
urlObj.hostname = publicConfig.host;
urlObj.port = publicConfig.port;
return urlObj.toString();
}
return trimmedUrl;
} catch (e) {
return trimmedUrl;
}
}
/**
* 规范化素材对象中的所有 URL
*/
export function normalizeMaterial(material: any): any {
if (!material || typeof material !== 'object') {
return material;
}
const normalized = { ...material };
// 规范化所有 URL 字段
const urlFields = [
'previewUrl',
'videoCoverUrl',
'videoPreview540Url',
'videoPreviewClipUrl',
'videoSourceUrl',
];
urlFields.forEach(field => {
if (field in normalized) {
normalized[field] = normalizeUrl(normalized[field]);
}
});
// 规范化 displayImages 数组
if (Array.isArray(normalized.displayImages)) {
normalized.displayImages = normalized.displayImages.map(img =>
typeof img === 'string' ? normalizeUrl(img) : img
);
}
return normalized;
}