OfficeSystem/utils/qiniu.js
2025-11-12 15:34:06 +08:00

404 lines
12 KiB
JavaScript
Raw Permalink 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.

/**
* 七牛云上传工具封装
* 提供图片和文件上传到七牛云的功能
*/
import { getQiniuUploadToken } from '@/api'
/**
* 上传文件到七牛云
* @param {string} filePath - 文件路径(临时路径)
* @param {string} token - 七牛云上传token
* @param {string} key - 文件key可选不传则自动生成
* @returns {Promise<object>} 返回上传结果包含key等信息
*/
export function uploadToQiniu(filePath, token, key = '') {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: 'https://up-z2.qiniup.com',
filePath: filePath,
name: 'file',
formData: {
token: token,
key: key,
},
success: (res) => {
try {
// 七牛云上传成功返回格式: { hash: "...", key: "uploads/xxx.jpg" }
const data = JSON.parse(res.data)
// 验证返回数据格式
if (!data || typeof data !== 'object') {
reject(new Error('上传响应数据格式错误'))
return
}
// 检查是否有错误信息(七牛云可能返回错误)
if (data.error) {
reject(new Error(data.error || '上传失败'))
return
}
resolve(data)
} catch (error) {
console.error('解析上传响应失败:', res.data, error)
reject(new Error('响应数据解析失败: ' + error.message))
}
},
fail: (error) => {
reject(error)
},
})
})
}
/**
* 七牛云图片上传服务
* 将临时文件路径转换为七牛云URL
* @param {string} tempFilePath - 临时文件路径
* @param {object} [options] - 配置选项
* @param {number} [options.maxSize=10] - 最大文件大小MB
* @param {string} [options.prefix='uploads/'] - 文件前缀路径
* @param {boolean} [options.showToast=true] - 是否显示提示
* @param {string} [options.domain='https://api.ccttiot.com'] - 七牛云域名
* @returns {Promise<string>} 七牛云文件URL
*/
export const tempUrlToRealUrl = async (tempFilePath, options = {}) => {
const {
maxSize = 10,
prefix = 'uploads/',
showToast = true,
domain = 'https://api.ccttiot.com',
} = options
try {
// 1. 检查文件大小
const fileInfo = await getFileInfo(tempFilePath)
const sizeInMB = fileInfo.size / (1024 * 1024)
if (sizeInMB > maxSize) {
throw new Error(`文件大小不能超过 ${maxSize}MB`)
}
// 2. 获取上传凭证
const token = await getQiniuToken()
if (!token) {
throw new Error('获取上传凭证失败')
}
// 3. 生成唯一文件名
const fileExt = getFileExtension(tempFilePath)
const key = generateUniqueKey(prefix, fileExt)
// 4. 上传到七牛云
const uploadResult = await uploadToQiniu(tempFilePath, token, key)
// 验证上传结果(七牛云返回格式: { hash: "...", key: "uploads/xxx.jpg" }
if (!uploadResult || typeof uploadResult !== 'object') {
throw new Error('上传失败:返回数据格式错误')
}
if (!uploadResult.key) {
console.error('上传响应缺少key字段:', uploadResult)
throw new Error('上传失败未返回文件key')
}
// 5. 构建完整URL
const qiniuUrl = `${domain}/${uploadResult.key}`
console.log('上传成功:', { key: uploadResult.key, hash: uploadResult.hash, url: qiniuUrl })
if (showToast) {
// 上传成功不显示提示,避免打扰用户
// uni.showToast({ title: '上传成功', icon: 'success' })
}
return qiniuUrl
} catch (error) {
console.error('七牛云上传失败:', error)
if (showToast) {
uni.showToast({
title: error.message || '上传失败',
icon: 'none',
})
}
throw error
}
}
/**
* 批量上传多张图片
* @param {string[]} tempFilePaths - 临时文件路径数组
* @param {object} [options] - 配置选项
* @returns {Promise<string[]>} 七牛云URL数组
*/
export const batchUploadToQiniu = async (tempFilePaths, options = {}) => {
return Promise.all(
tempFilePaths.map(filePath => tempUrlToRealUrl(filePath, { ...options, showToast: false }))
)
}
/**
* 选择图片并自动上传到七牛云
* @param {object} [options] - 配置选项
* @param {number} [options.count=9] - 最多选择图片数量
* @param {string[]} [options.sizeType=['original', 'compressed']] - 图片尺寸类型
* @param {string[]} [options.sourceType=['album', 'camera']] - 图片来源
* @param {object} [options.uploadOptions] - 上传配置选项传递给tempUrlToRealUrl
* @returns {Promise<string[]>} 七牛云URL数组
*/
export const chooseAndUploadImages = (options = {}) => {
const {
count = 9,
sizeType = ['original', 'compressed'],
sourceType = ['album', 'camera'],
uploadOptions = {}
} = options
return new Promise((resolve, reject) => {
uni.chooseImage({
count: count,
sizeType: sizeType,
sourceType: sourceType,
success: async (res) => {
try {
// 显示上传中提示
uni.showLoading({
title: '上传中...',
mask: true
})
// 批量上传图片
const urls = await batchUploadToQiniu(res.tempFilePaths, uploadOptions)
uni.hideLoading()
resolve(urls)
} catch (error) {
uni.hideLoading()
reject(error)
}
},
fail: (err) => {
reject(err)
}
})
})
}
/**
* 上传单个文件到七牛云(支持图片和文档)
* @param {string} filePath - 文件临时路径或URI
* @param {string} fileName - 文件名(可选,用于文档文件)
* @param {object} [options] - 配置选项
* @param {number} [options.maxSize=50] - 最大文件大小MB文档文件默认50MB
* @param {string} [options.prefix='uploads/'] - 文件前缀路径
* @param {boolean} [options.showToast=true] - 是否显示提示
* @param {string} [options.domain='https://api.ccttiot.com'] - 七牛云域名
* @returns {Promise<object>} 返回 { url: string, name: string, size: number }
*/
export const uploadFileToQiniu = async (filePath, fileName = '', options = {}) => {
const {
maxSize = 50,
prefix = 'uploads/',
showToast = true,
domain = 'https://api.ccttiot.com',
} = options
try {
// 1. 如果是URI格式content://),需要先转换为临时路径
let tempFilePath = filePath
if (filePath.startsWith('content://')) {
// 在uni-app中content:// URI需要特殊处理
// 对于文档文件uni.uploadFile可以直接使用URI
tempFilePath = filePath
}
// 2. 获取文件信息如果不是URI格式
let fileInfo = { size: 0 }
if (!filePath.startsWith('content://')) {
try {
fileInfo = await getFileInfo(tempFilePath)
} catch (error) {
console.warn('获取文件信息失败:', error)
}
}
const sizeInMB = fileInfo.size / (1024 * 1024)
if (sizeInMB > maxSize) {
throw new Error(`文件大小不能超过 ${maxSize}MB`)
}
// 3. 获取上传凭证
const token = await getQiniuToken()
if (!token) {
throw new Error('获取上传凭证失败')
}
// 4. 生成唯一文件名
let fileExt = 'file'
if (fileName) {
const parts = fileName.split('.')
if (parts.length > 1) {
fileExt = parts.pop().toLowerCase()
}
} else {
fileExt = getFileExtension(tempFilePath)
}
const key = generateUniqueKey(prefix, fileExt)
// 5. 上传到七牛云
const uploadResult = await uploadToQiniu(tempFilePath, token, key)
// 验证上传结果
if (!uploadResult || typeof uploadResult !== 'object') {
throw new Error('上传失败:返回数据格式错误')
}
if (!uploadResult.key) {
console.error('上传响应缺少key字段:', uploadResult)
throw new Error('上传失败未返回文件key')
}
// 6. 构建完整URL
const qiniuUrl = `${domain}/${uploadResult.key}`
console.log('文件上传成功:', { key: uploadResult.key, url: qiniuUrl })
// 7. 生成文件名(如果没有提供)
const finalFileName = fileName || `文件.${fileExt}`
if (showToast) {
// 上传成功不显示提示,避免打扰用户
}
return {
url: qiniuUrl,
name: finalFileName,
size: fileInfo.size || 0
}
} catch (error) {
console.error('文件上传失败:', error)
if (showToast) {
uni.showToast({
title: error.message || '上传失败',
icon: 'none',
})
}
throw error
}
}
/**
* 批量上传文件到七牛云(支持图片和文档)
* @param {Array<{path: string, name?: string}>} files - 文件数组每个文件包含path和可选的name
* @param {object} [options] - 配置选项
* @returns {Promise<Array<{url: string, name: string, size: number}>>} 上传结果数组
*/
export const batchUploadFilesToQiniu = async (files, options = {}) => {
return Promise.all(
files.map(file => uploadFileToQiniu(file.path, file.name || '', { ...options, showToast: false }))
)
}
/**
* 获取文件信息
* @private
*/
const getFileInfo = (filePath) => {
return new Promise((resolve, reject) => {
uni.getFileInfo({
filePath,
success: resolve,
fail: reject,
})
})
}
/**
* 获取七牛云上传token
* @private
*/
const getQiniuToken = async () => {
try {
const res = await getQiniuUploadToken()
// 根据实际API返回格式处理
// 如果API返回的是 { code: 200, data: { token: 'xxx' } } 格式
if (res && typeof res === 'object') {
// 如果响应拦截器已经处理过直接返回token
if (res.token) {
return res.token
}
// 如果返回的是 { data: { token: 'xxx' } } 格式
if (res.data && res.data.token) {
return res.data.token
}
// 如果返回的是 { token: 'xxx' } 格式
if (res.token) {
return res.token
}
}
// 如果返回的是字符串token
if (typeof res === 'string') {
return res
}
throw new Error('获取token失败返回格式不正确')
} catch (error) {
console.error('获取七牛云token失败:', error)
throw error
}
}
/**
* 获取文件扩展名
* @private
*/
const getFileExtension = (filePath) => {
const parts = filePath.split('.')
return parts.length > 1 ? parts.pop().toLowerCase() : 'jpg'
}
/**
* 生成唯一文件名
* @private
*/
const generateUniqueKey = (prefix, extension) => {
const timestamp = Date.now()
const randomStr = Math.random().toString(36).slice(2, 10)
return `${prefix}${timestamp}_${randomStr}.${extension}`
}
/**
* 工具函数:检查是否为临时路径
* @param {string} url - 要检查的URL
* @returns {boolean} 是否为临时路径
*/
export const isTempFilePath = (url) => {
return (
url &&
(url.startsWith('http://temp/') ||
url.startsWith('wxfile://') ||
url.includes('tmp/') ||
!url.startsWith('http'))
)
}
/**
* 工具函数从七牛云URL提取文件key
* @param {string} qiniuUrl - 七牛云完整URL
* @returns {string|null} 文件key
*/
export const extractQiniuKey = (qiniuUrl) => {
if (!qiniuUrl) return null
try {
const urlObj = new URL(qiniuUrl)
return urlObj.pathname.substring(1) // 去除开头的/
} catch (error) {
// 如果不是标准URL尝试直接提取路径
const match = qiniuUrl.match(/https?:\/\/[^\/]+\/(.+)/)
return match ? match[1] : null
}
}