150 lines
4.6 KiB
JavaScript
150 lines
4.6 KiB
JavaScript
/**
|
||
* OA PDF 预览页 query 解析(短 fileUrl / 兼容旧 url)
|
||
* 微信 onLoad 可能已 decode 一次;须安全二次解码,并兼容大小写 key。
|
||
*/
|
||
|
||
const LOG_TAG = '[oa-pdf-preview]'
|
||
|
||
export function pdfPreviewLog(...args) {
|
||
try {
|
||
console.log(LOG_TAG, ...args)
|
||
} catch (_) {
|
||
/* ignore */
|
||
}
|
||
}
|
||
|
||
export function safeDecodeURIComponent(value) {
|
||
const raw = String(value == null ? '' : value).trim()
|
||
if (!raw) return ''
|
||
try {
|
||
return decodeURIComponent(raw)
|
||
} catch (_) {
|
||
return raw
|
||
}
|
||
}
|
||
|
||
/** 按候选 key 取值;忽略大小写;兼容微信偶发改写 */
|
||
export function pickQueryValue(query, keys) {
|
||
const q = query && typeof query === 'object' ? query : {}
|
||
const list = Array.isArray(keys) ? keys : [keys]
|
||
for (let i = 0; i < list.length; i++) {
|
||
const k = list[i]
|
||
if (k == null) continue
|
||
if (Object.prototype.hasOwnProperty.call(q, k) && q[k] != null) {
|
||
const v = String(q[k]).trim()
|
||
if (v) return v
|
||
}
|
||
}
|
||
const lowerMap = {}
|
||
Object.keys(q).forEach((k) => {
|
||
lowerMap[String(k).toLowerCase()] = q[k]
|
||
})
|
||
for (let i = 0; i < list.length; i++) {
|
||
const k = String(list[i] || '').toLowerCase()
|
||
if (!k) continue
|
||
if (lowerMap[k] != null) {
|
||
const v = String(lowerMap[k]).trim()
|
||
if (v) return v
|
||
}
|
||
}
|
||
return ''
|
||
}
|
||
|
||
/**
|
||
* @returns {{ fileUrl: string, legacyUrl: string, name: string, rawKeys: string[] }}
|
||
*/
|
||
export function parsePdfPreviewQuery(query) {
|
||
const rawKeys = Object.keys(query && typeof query === 'object' ? query : {})
|
||
const fileUrl = safeDecodeURIComponent(
|
||
pickQueryValue(query, ['fileUrl', 'fileurl', 'file_url', 'f'])
|
||
)
|
||
const legacyUrl = safeDecodeURIComponent(
|
||
pickQueryValue(query, ['url', 'downloadUrl', 'download_url'])
|
||
)
|
||
const name =
|
||
safeDecodeURIComponent(pickQueryValue(query, ['name', 'fileName', 'file_name', 'n'])) ||
|
||
'approval.pdf'
|
||
return { fileUrl, legacyUrl, name, rawKeys }
|
||
}
|
||
|
||
/**
|
||
* 后端 MINIO_BASE_URL 常配成同域相对路径(如 /minio/zkxw),预签名改写后也是
|
||
* `/minio/zkxw/...?X-Amz-...`。微信 downloadFile 必须绝对 http(s)。
|
||
*
|
||
* @param {string} url
|
||
* @param {{ apiBaseUrl?: string, minioBaseUrl?: string }} [bases]
|
||
*/
|
||
export function toAbsoluteDownloadUrl(url, bases = {}) {
|
||
const s = String(url || '').trim()
|
||
if (!s) return ''
|
||
if (/^https?:\/\//i.test(s)) return s
|
||
if (s.startsWith('//')) return 'https:' + s
|
||
|
||
const apiBase = String(bases.apiBaseUrl || '').trim()
|
||
const minioBase = String(bases.minioBaseUrl || '').trim()
|
||
const originFrom = (base) => {
|
||
const m = String(base || '').match(/^(https?:\/\/[^/]+)/i)
|
||
return m ? m[1] : ''
|
||
}
|
||
const origin = originFrom(apiBase) || originFrom(minioBase)
|
||
|
||
if (s.startsWith('/')) {
|
||
return origin ? origin + s : ''
|
||
}
|
||
|
||
// 相对 object key
|
||
const minio = minioBase.replace(/\/$/, '')
|
||
if (/^https?:\/\//i.test(minio)) {
|
||
return minio + '/' + s.replace(/^\//, '')
|
||
}
|
||
if (minio.startsWith('/') && origin) {
|
||
return origin + minio + '/' + s.replace(/^\//, '')
|
||
}
|
||
return ''
|
||
}
|
||
|
||
/**
|
||
* 拼 MinIO 公网直链(无预签名)。
|
||
* 预签名经相对路径改写后微信 downloadFile 常 403;公网直链对本环境可 200。
|
||
*/
|
||
export function buildDirectMinioUrl(fileUrl, bases = {}) {
|
||
const raw = String(fileUrl || '').trim()
|
||
if (!raw) return ''
|
||
|
||
const minioBase = String(bases.minioBaseUrl || '').replace(/\/$/, '')
|
||
const apiBase = String(bases.apiBaseUrl || '').trim()
|
||
const originMatch = (apiBase || minioBase).match(/^(https?:\/\/[^/]+)/i)
|
||
const origin = originMatch ? originMatch[1] : ''
|
||
|
||
// 已是绝对 http(s):去掉 query(签名)后若可识别为同域 MinIO 则规范化,否则原样去 query
|
||
if (/^https?:\/\//i.test(raw)) {
|
||
const noQuery = raw.split('?')[0]
|
||
try {
|
||
const u = new URL(noQuery)
|
||
const minioPath = minioBase.replace(/^https?:\/\/[^/]+/i, '') || ''
|
||
if (minioPath && u.pathname.indexOf(minioPath) === 0) {
|
||
return origin && minioPath.startsWith('/')
|
||
? origin + u.pathname
|
||
: noQuery
|
||
}
|
||
} catch (_) {
|
||
/* ignore */
|
||
}
|
||
return noQuery
|
||
}
|
||
|
||
// 根相对:/minio/zkxw/key.pdf?... → 拼 origin
|
||
if (raw.startsWith('/')) {
|
||
const pathOnly = raw.split('?')[0]
|
||
return origin ? origin + pathOnly : toAbsoluteDownloadUrl(pathOnly, bases)
|
||
}
|
||
|
||
// object key:去掉可能误带的 minio 路径前缀
|
||
let key = raw.replace(/^\//, '').split('?')[0]
|
||
const minioPath = minioBase.replace(/^https?:\/\/[^/]+/i, '').replace(/^\//, '')
|
||
if (minioPath && key.indexOf(minioPath + '/') === 0) {
|
||
key = key.slice(minioPath.length + 1)
|
||
}
|
||
return toAbsoluteDownloadUrl(key, bases)
|
||
}
|