需求背景介绍
文件转换功能, 如上传jpg转为png, 服务器可能会返回正常的png文件, 也可能会返回
{code: 5001, msg: "读取文件失败, 请检查文件完整性"}
或者Excel导入功能, 服务器可能返回{code: 2000, msg: “文件导入成功”}, 也可能返回一个带有错误信息的文件
此时后台(示例为springboot)文件处理机制大致流程:
@PostMapping("jpg2png")
public void jpg2png(MultipartFile file, HttpServletResponse resp) throws IOException {
// 假设此时为正常返回, 但是抛出异常被全局异常捕获, 返回了json
IOUtils.copy(convert(file), resp.getOutputStream());
}
public InputStream convert(MultipartFile file) {
// 模拟可能发生的异常
throw new RuntimeException("文件转换失败");
}
那么在axios实例中调用
const jpg2png = (file: File)=>{
const fd = new FormData()
fd.append('file', file)
return axios.post('http://xxx:xx/jpg2png', fd, {responseType: 'blob'})
}
此时jpg2png返回格式只有blog, 并非我们想要的json或blob
解决方案
方案其实很简单, 在响应拦截器中判断, 如果返回内容为blob, 但是响应头中的content-type值却是’application/json’(如果成功的话会是对应的文件类型, 但后台有特殊情况的请自行调整)
放一下我在用的拦截器
import axios, {type AxiosInstance} from 'axios'
import {FileUtil} from '@/util/file-util'
const instance = axios.create({
timeout: 5 * 1000,
baseURL: import.meta.env.VITE_API_BASE_URL
})
instance.interceptors.request.use(config => {
config.headers.token = localStorage.getItem('token')
return config
}, error => {
console.error('req error : ', error)
if (axios.isCancel(error)) {
return error
}
return Promise.reject(error)
})
instance.interceptors.response.use(async response => {
// 请求时设置返回blob, 但是实际上可能返回的是json的情况
if (response.data instanceof Blob) {
if (!response.headers['content-type']?.includes('application/json')) {
FileUtil.saveBlob(response.data, decodeURI(response.headers['filename'] ?? ''))
return null
}
response.data = JSON.parse(await (response.data as Blob).text())
}
const {
code,
message,
data
} = response.data
if (code !== 2000) {
alert(message)
return Promise.reject(response)
}
return data
}, error => {
alert('请求异常')
console.error('请求异常: ', error)
return Promise.reject(error)
})
export const rest: AxiosInstance = instance
再来一个js版本的:
import axios from 'axios'
import {FileUtil} from '@/util/file-util'
const instance = axios.create({
timeout: 5 * 1000,
baseURL: import.meta.env.VITE_API_BASE_URL
})
instance.interceptors.request.use(config => {
config.headers.token = localStorage.getItem('token')
return config
}, error => {
console.error('req error : ', error)
if (axios.isCancel(error)) {
return error
}
return Promise.reject(error)
})
instance.interceptors.response.use(async response => {
// 请求时设置返回blob, 但是实际上可能返回的是json的情况
if (response.data instanceof Blob) {
if (!response.headers['content-type']?.includes('application/json')) {
FileUtil.saveBlob(response.data, decodeURI(response.headers['filename'] ?? ''))
return null
}
response.data = JSON.parse(await response.data.text())
}
const {
code,
message,
data
} = response.data
if (code !== 2000) {
alert(message)
return Promise.reject(response)
}
return data
}, error => {
alert('请求异常')
console.error('请求异常: ', error)
return Promise.reject(error)
})
export const rest = instance
核心逻辑就是if (response.data instanceof Blob) {
这段代码了
附:
file-util.ts
export class FileUtil {
static #tagA = document.createElement('a')
/**
* 保存文件
*/
static saveBlob(file: Blob, filename: string) {
this.#tagA.download = filename
const url = URL.createObjectURL(file)
this.#tagA.href = url
this.#tagA.click()
URL.revokeObjectURL(url)
}
}
总结
没什么优雅的方案, 我的需求仅仅是拦截器拦截到文件则自动下载即可, 业务代码无需后续处理, 如果是请求文件, 返回null, 表示处理完成.