Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | import { post } from './http'
import { nextIdApi } from './api'
let storageType = null
let ossCredCache = null
const CRED_BUFFER_MS = 5 * 60 * 1000 // 到期前 5 分钟刷新
/** 初始化存储模式,返回 "local" | "aliyun-oss" */
export async function initFileConfig() {
if (storageType) return storageType
const res = await post('/file/config')
storageType = res.data.storageType
return storageType
}
export function getStorageType() {
return storageType
}
/**
* 批量上传文件(自动匹配模式,含 DB 写入)
* @param {{ files: File[], father: string, moduleId: string, cateId?: string }} opts
* @returns {Promise<Object[]>} 文件记录列表
*/
export async function upload({ files, father, moduleId, cateId }) {
if (!storageType) await initFileConfig()
if (storageType === 'aliyun-oss') {
return uploadToOss({ files, father, moduleId, cateId })
}
return uploadToLocal({ files, father, moduleId, cateId })
}
/** Local: 一次请求打包所有文件,后端逐个写磁盘 + INSERT */
async function uploadToLocal({ files, father, moduleId, cateId }) {
const fd = new FormData()
for (const f of files) {
fd.append('files', f)
}
fd.append('father', father)
fd.append('moduleId', moduleId)
if (cateId) fd.append('cateId', cateId)
const res = await post('/file/upload', fd, {
headers: { 'Content-Type': undefined },
})
return res.data // List<Map>
}
/** OSS: 共享凭证 → 逐个 XHR 直传 → 逐个回调 save-record */
async function uploadToOss({ files, father, moduleId, cateId }) {
const cred = await getOssCredential()
const { accessid, policy, signature, dir, host } = cred
const results = []
for (const file of files) {
const idRes = await nextIdApi()
const subid = String(idRes.data)
const ext = extname(file.name)
const objectName = crypto.randomUUID().replace(/-/g, '') + (ext ? '.' + ext : '')
const url = await xhrUpload(host, {
key: dir + objectName,
OSSAccessKeyId: accessid,
policy,
Signature: signature,
file,
filename: objectName,
})
const record = {
subid,
father,
mx_moduleid: moduleId,
mx_cate_id: cateId || '',
mx_filename: file.name,
mx_filesize: String(file.size),
mx_fileext: ext,
mx_mapfilename: url,
}
await post('/file/save-record', record)
results.push(record)
}
return results
}
/* ───── OSS 凭证缓存 ───── */
async function getOssCredential() {
if (ossCredCache && !credExpiring(ossCredCache)) {
return ossCredCache
}
const res = await post('/file/upload-credential')
ossCredCache = { ...res.data, fetchedAt: Date.now() }
return ossCredCache
}
function credExpiring(cache) {
// 如果 g3oss 返回了 UNIX 时间戳过期时间,优先用
if (cache.expire) {
return Date.now() + CRED_BUFFER_MS > Number(cache.expire) * 1000
}
if (cache.Expiration) {
return Date.now() + CRED_BUFFER_MS > new Date(cache.Expiration).getTime()
}
// 兜底:默认有效期 55 分钟
return Date.now() - cache.fetchedAt > 55 * 60 * 1000
}
/* ───── 底层工具 ───── */
function xhrUpload(host, fields) {
const fd = new FormData()
fd.append('key', fields.key)
fd.append('OSSAccessKeyId', fields.OSSAccessKeyId)
fd.append('policy', fields.policy)
fd.append('Signature', fields.Signature)
fd.append('file', fields.file, fields.filename)
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('POST', host)
xhr.addEventListener('load', () => {
if (xhr.status === 200 || xhr.status === 204) {
resolve(host + '/' + fields.key)
} else {
reject(new Error('OSS 上传失败: ' + xhr.status))
}
})
xhr.addEventListener('error', () => reject(new Error('OSS 上传异常')))
xhr.send(fd)
})
}
/** 获取预览/下载地址 */
export function getPreviewUrl(fileRow) {
return fileRow.mx_mapfilename
}
/** 删除文件 */
export async function removeFile(subid) {
return post('/file/delete', { subid })
}
function extname(name) {
if (!name || !name.includes('.')) return ''
return name.substring(name.lastIndexOf('.') + 1).toLowerCase()
}
|