1. 前言
最近在维护一个比较老的 Web 项目,其中用到了 DWR 2.0(一种可以在 js 里调用 Java 方法的远程通信框架)。现在要利用这个框架实现上传文件到服务端和从服务端下载文件,然而这个项目用的 DWR 2.0,默认只支持调用以基本数据类型,以及 String、List、Map 等常用类型作为参数和返回值的 Java 方法,无法使用 FileTransfer、InputStream、MultipartFile 这些对象。
既然可以传递字符串,那就采用文件与字符串互转的方式进行前后交互。流程如下:上传文件时,文件 -> ArrayBuffer -> 16 进制字符串 -> byte[] -> 文件 下载文件时,文件 -> byte[] -> 16 进制字符串 -> Uint8Array -> blob -> 文件
2. 上传文件
HTML 代码:
<input type=”file” multiple=”multiple” onchange=”readFilesAndUpload(event)” />
JS 代码:
// 将 ArrayBuffer 转为 16 进制字符串
function bufToHex(buffer) {
return Array.prototype.map.call(new Uint8Array(buffer), function (x) {
return (’00’ + x.toString(16)).slice(-2)
}).join(”)
}
function readFilesAndUpload(event) {
var processed = 0
var files = event.target.files
var len = files.length
var filenameArr = new Array(len) // 文件名
var fileContextArr = new Array(len) // 文件内容
for (var i = 0; i < len; ++i) {
var reader = new FileReader()
reader.index = i
reader.filename = files[i].name
reader.readAsArrayBuffer(files[i]) // 将文件读到 ArrayBuffer
reader.onload = function (e) {
filenameArr[this.index] = this.filename
fileContextArr[this.index] = bufToHex(this.result)
// FileReader 以异步的方式读取文件,需要借助外部变量判断是否读完全部文件
if (++processed === len) {
// 将 filenameArr 和 fileContext 上传到服务端
}
}
}
}
Java 代码:
private static final String UPLOAD_DIR = “D://Files/”;
public void uploadFiles(List<String> filenameArr, List<String> fileContextArr) throws IOException {
byte[] bytes;
FileOutputStream fos;
for (int i = 0; i < filenameArr.size(); ++i) {
String file = fileContextArr.get(i);
// 将 16 进制字符串转换成 byte[]
bytes = new byte[file.length() / 2];
for (int j = 0; j < file.length() / 2; ++j) {
String subStr = file.substring(j * 2, j * 2 + 2);
bytes[j] = (byte) Integer.parseInt(subStr, 16);
}
// 保存到本地磁盘
fos = new FileOutputStream(UPLOAD_DIR + filenameArr.get(i), true);
fos.write(bytes);
fos.close();
}
}
3. 下载文件
Java 代码:
public String downloadFile(String filename) throws IOException {
File file = new File(UPLOAD_DIR + filename);
if (!file.exists()) {
return null;
}
// 将文件读到 byte[]
byte[] buffer = new byte[(int) file.length()];
InputStream is = new FileInputStream(file);
is.read(buffer);
is.close();
// 将 byte[] 转换成 16 进制字符串
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < buffer.length; i++) {
int v = buffer[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
JS 代码:
// 16 进制字符串转换成整型数组
function hexToBytes(hexStr) {
var bytes = []
for (var c = 0; c < hexStr.length; c += 2)
bytes.push(parseInt(hexStr.substr(c, 2), 16))
return bytes
}
function downloadFile() {
// 调用服务端方法,取得 16 进制字符串 res
var uint8Array = new Uint8Array(hexToBytes(res))
var blob = new Blob([uint8Array], {type: “application/octet-stream”})
// 兼容 IE、火狐和谷歌的下载方式
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, filename)
} else {
var downloadElement = document.createElement(“a”)
var href = window.URL.createObjectURL(blob)
downloadElement.href = href
downloadElement.download = filename
document.body.appendChild(downloadElement)
downloadElement.click()
downloadElement.remove()
window.URL.revokeObjectURL(href)
}
}