/** 不依赖小程序中可能不存在的 URL 构造器,不接受任意下载主机。 */ export function validateContractDocument(query, storageBase) { const url = String(query?.url || '') const type = String(query?.type || '').toLowerCase() const base = String(storageBase || '').replace(/\/$/, '') if (!['pdf', 'docx'].includes(type)) throw new Error('不支持的文档格式') if (!/^https?:\/\/[^/@?#]+(?:\/[^?#]*)?$/.test(base) || !url.startsWith(base + '/')) { throw new Error('无效的文档地址') } const path = url.split(/[?#]/)[0] let decoded try { decoded = decodeURIComponent(path) } catch { throw new Error('无效的文档地址') } if (/[\\\s\x00-\x1f]/.test(url) || decoded.split('/').some((part) => part === '..' || part === '.')) { throw new Error('无效的文档地址') } return { url, type, name: String(query?.name || `合同.${type}`) } } /** 保留微信下载错误用于排查,但不展示含临时签名的文件地址。 */ export function contractDownloadErrorMessage(error, url) { const raw = String(error?.errMsg || '') const origin = String(url || '').match(/^https?:\/\/[^/?#]+/)?.[0] || '文件存储域名' if (/url not in domain list|domain.*not.*allowed|域名.*不.*合法/i.test(raw)) { return `下载被微信拦截:请管理员将 ${origin} 加入小程序的 downloadFile 合法域名,更新配置后重新进入小程序。` } const detail = raw.replace(/https?:\/\/[^\s]+/gi, '[下载地址]') .replace(/[\r\n\t]/g, ' ').replace(/^downloadFile:fail\s*/i, '').trim().slice(0, 180) const code = error?.errno ?? error?.errCode const codeText = Number.isFinite(Number(code)) && code != null ? `(错误码 ${Number(code)})` : '' return detail ? `下载失败:${detail}${codeText}` : `下载失败${codeText},请检查网络或稍后重试` }