返回 AiToEarn
oss.ts
1 // 获取完整的OSS URL
2 export function getOssUrl(path?: string) {
3 if (!path)
4 return ''
5 if (
6 path.startsWith('http')
7 || path.startsWith('https')
8 || path.startsWith('ossProxy')
9 || path.startsWith('/ossProxy/')
10 || path.startsWith('blob:http')
11 || path.startsWith('blob:https')
12 ) {
13 return path
14 }
15 return `${process.env.NEXT_PUBLIC_OSS_URL}${path}`
16 }
17
18 export interface OssThumbnailOptions {
19 width?: number
20 height?: number
21 quality?: number
22 }
23
24 const DEFAULT_THUMBNAIL_QUALITY = 75
25 const UNSUPPORTED_THUMBNAIL_EXTENSIONS = new Set(['.svg', '.gif', '.mp4', '.mov', '.webm', '.m4v'])
26 const PROJECT_OSS_HOSTNAMES = new Set(['assets.aitoearn.cn', 'assets.aitoearn.ai'])
27
28 type OssStorageProvider = 'aliyun' | 'cloudflare'
29
30 function getNormalizedDimension(value?: number) {
31 if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
32 return undefined
33
34 return Math.ceil(value)
35 }
36
37 function getNormalizedQuality(value?: number) {
38 if (typeof value !== 'number' || !Number.isFinite(value))
39 return DEFAULT_THUMBNAIL_QUALITY
40
41 return Math.min(100, Math.max(1, Math.round(value)))
42 }
43
44 function getUrlExtension(url: URL) {
45 const lastDotIndex = url.pathname.lastIndexOf('.')
46 if (lastDotIndex < 0)
47 return ''
48
49 return url.pathname.slice(lastDotIndex).toLowerCase()
50 }
51
52 function isProjectOssUrl(url: URL) {
53 if (PROJECT_OSS_HOSTNAMES.has(url.hostname))
54 return true
55
56 const ossUrl = process.env.NEXT_PUBLIC_OSS_URL
57 if (!ossUrl)
58 return false
59
60 try {
61 const ossBaseUrl = new URL(ossUrl)
62 return url.origin === ossBaseUrl.origin
63 }
64 catch {
65 return false
66 }
67 }
68
69 function getOssStorageProvider(url: URL): OssStorageProvider {
70 if (url.hostname === 'assets.aitoearn.cn')
71 return 'aliyun'
72
73 if (url.hostname === 'assets.aitoearn.ai')
74 return 'cloudflare'
75
76 return 'cloudflare'
77 }
78
79 function canUseOssThumbnail(url: URL) {
80 if (!isProjectOssUrl(url))
81 return false
82
83 if (url.searchParams.has('x-oss-process') || url.pathname.includes('/cdn-cgi/image/'))
84 return false
85
86 return !UNSUPPORTED_THUMBNAIL_EXTENSIONS.has(getUrlExtension(url))
87 }
88
89 function buildAliyunOssThumbnailUrl(url: URL, width?: number, height?: number, quality?: number) {
90 const resizeOptions = [
91 width ? `w_${width}` : '',
92 height ? `h_${height}` : '',
93 ].filter(Boolean).join(',')
94
95 const processValue = [
96 `image/resize,${resizeOptions}`,
97 `quality,q_${quality}`,
98 'format,webp',
99 ].join('/')
100
101 const thumbnailUrl = new URL(url.toString())
102 thumbnailUrl.searchParams.set('x-oss-process', processValue)
103 return thumbnailUrl.toString()
104 }
105
106 export function getOssThumbnailUrl(path?: string, options: OssThumbnailOptions = {}) {
107 const url = getOssUrl(path)
108 if (!url)
109 return ''
110
111 const width = getNormalizedDimension(options.width)
112 const height = getNormalizedDimension(options.height)
113 if (!width && !height)
114 return url
115
116 let parsedUrl: URL
117 try {
118 parsedUrl = new URL(url)
119 }
120 catch {
121 return url
122 }
123
124 if (!canUseOssThumbnail(parsedUrl))
125 return url
126
127 if (getOssStorageProvider(parsedUrl) !== 'aliyun')
128 return url
129
130 const quality = getNormalizedQuality(options.quality)
131 return buildAliyunOssThumbnailUrl(parsedUrl, width, height, quality)
132 }
133
134 // 将完整的oss url转为代理的 oss url
135 export function getOssProxyPath(ossUrl?: string) {
136 if (!ossUrl)
137 return ''
138
139 return ossUrl?.replace(
140 process.env.NEXT_PUBLIC_OSS_URL ?? '',
141 process.env.NEXT_PUBLIC_OSS_URL_PROXY ?? '',
142 )
143 }
144
144 lines TYPESCRIPT