最近專案中需要實現點選按鈕下載檔案的需求,前端用的vue,因為檔案是各種型別的,比如圖片、pdf、word之類的。這裡後端是可以返回檔案的地址給前端的,但我看了下網上各種五花八門的答案,感覺都不是我想要的。

因為不確定檔案是哪種型別的,所以我們在儲存檔案到資料庫的時候,應該把檔案的Content-Type一起存入,這樣從資料庫取出返回前端的時候,帶上Content-Type標識是哪種型別的檔案,前端解析即可。

1、後端程式碼

這裡我先寫後端的介面,考慮一下後端需要什麼東西。因為檔案資訊已經提前存入資料庫,所以我們只需要傳入主鍵id就可以拿到檔案的資訊。確定引數後,就需要確定一下返回值型別。這裡可以使用ResponseEntity返回。ResponseEntity可以一次返回多個資訊,包括狀態碼,響應頭資訊,響應內容等。

話不多說,看程式碼。

/**
* 下載附件
* @param attachmentId
* @return
*/
public ResponseEntity<byte[]> download(Long attachmentId) {
// 查詢附件是否存在
SysAttachment sysAttachment = sysAttachmentMapper.selectSysAttachmentById(attachmentId);
if (StringUtils.isNull(sysAttachment)) {
return null;
} ByteArrayOutputStream bos = null;
InputStream ins = null;
try {
String fileName = sysAttachment.getOrgFileName();
String ossFileName = sysAttachment.getUrl();
bos = new ByteArrayOutputStream();
ins = OssUtils.getInstance().getObject(ossFileName).getObjectContent();
// 取流中的資料
int len = 0;
byte[] buf = new byte[256];
while ((len = ins.read(buf, 0, 256)) > -1) {
bos.write(buf, 0, len);
} // 防止中文亂碼
fileName = URLEncoder.encode(fileName, "utf-8");
// 設定響應頭
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=" + fileName);
headers.add("Content-Type", sysAttachment.getContentType());
// 設定響應嗎
HttpStatus statusCode = HttpStatus.OK;
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(bos.toByteArray(), headers, statusCode);
return response;
} catch (Exception e) {
throw new CustomException("下載失敗");
} finally {
try {
if (ins != null) {
ins.close();
}
if (bos != null) {
bos.close();
}
} catch (Exception e) {
throw new CustomException("下載失敗");
}
}
}

這裡我們從資料庫拿出檔案的url後,再通過阿里雲oss拿到檔案的輸入流,接著把檔案輸出為二進位制,封裝到ResponseEntity中,並把檔案的型別設定到Content-Type中,同時為了防止檔名帶有中文名亂碼,設定utf-8編碼,至此後端介面完成。

通過上面的資訊,我們在資料庫儲存檔案資訊時,至少應該儲存下面幾個欄位:檔案的url(一般在上傳到oss後會給你一個)、檔案的型別、原始檔名、檔案大小等。

2、前端程式碼

有了後端介面,接下來就是前端了。這裡可以把檔案下載的方法封裝成一個通用方法全域性掛載,之後需要使用的地方直接使用即可。

我們需要標識不同的檔案,所以我們需要一個鍵值對錶示不同的檔案。

const mimeMap = {
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
xls: 'application/vnd.ms-excel',
zip: 'application/zip',
jpg: 'image/jpg',
jpeg: 'image/jpeg',
png: 'image/png',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
ppt: 'application/vnd.ms-powerpoint',
txt: 'text/plain',
pdf: 'application/pdf'
}

有需要的可以繼續補充。接下來自然就是發請求了,這裡的返回型別可以設定為blob,使用axios直接傳送

/**
* 下載附件
* @param path 介面地址
* @param param 請求引數
*/
export function downloadAttachment(path, param) {
var url = baseUrl + path + param
axios({
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': getToken() }
}).then(res => {
resolveBlob(res, res.data.type)
})
}

介面地址和請求引數從外部傳入。同時需要攜帶token,不然會跨域訪問。拿到後端返回的資料後,需要解析二進位制檔案,這裡定義resolveBlob方法,該方法有兩個引數,返回物件和檔案的型別,檔案的型別,我們在後端已經放入Content-Type中了,這裡直接取。

/**
* 解析blob響應內容並下載
* @param {*} res blob響應內容
* @param {String} mimeType MIME型別
*/
export function resolveBlob(res, mimeType) {
const aLink = document.createElement('a')
var blob = new Blob([res.data], { type: mimeType })
// 從response的headers中獲取filename, 後端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 設定的檔名;
var patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*')
var contentDisposition = decodeURI(res.headers['content-disposition'])
var result = patt.exec(contentDisposition)
var fileName = result[1]
fileName = fileName.replace(/\"/g, '')
aLink.href = URL.createObjectURL(blob)
aLink.setAttribute('download', fileName) // 設定下載檔名稱
document.body.appendChild(aLink)
aLink.click()
document.body.removeChild(aLink);
}

這程式碼不用多解釋了吧,前端大佬們自然看得懂。OK了啊,到這裡前後端程式碼都完成了。

3、使用

使用那就更簡單啦。先掛載到全域性

import { downloadAttachment } from "@/utils/download"
Vue.prototype.downloadAttac = downloadAttachment

在使用的地方直接呼叫即可

<el-button
type="text"
icon="el-icon-download"
size="mini"
@click="downloadAttachRow(scope.row.attachmentId)"
></el-button> /** 下載附件 */
downloadAttachRow(attachId) {
this.$confirm('是否確認下載該檔案?', "警告", {
confirmButtonText: "確定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.downloadAttac('/system/attachment/download/', attachId)
}).then(() => {
this.msgSuccess("下載成功")
}).catch(() => {})
}

到此結束。如果過程中遇到什麼問題,可以在下方留言,我會回覆的。

覺得好的可以幫忙點個贊啊,也可以關注我的公眾號【禿頭哥程式設計】