java发送url请求地址中有中文和特殊字符,java发送到前端的文件名存在特殊字符,java后端向前端发送blob类型、arraybuffer类型数据流,后端和前端解决方案
如果你的url请求地址没有中文和特殊字符,请移步到另一篇文章:
java后端向前端发送blob类型、arraybuffer类型数据流
下面详细说明项目情况:
1.前端请求图片跨域,考虑前端把这个图片地址发送到后端,后端请求到后通过数据流发送到前端。
2.测试环境一切ok,到了生产环境就崩了,晚上搞到12点没有好,第二天定位问题解决了。原因是生产环境中的url请求地址中有中文和特殊字符。TMD,中文可以理解,特殊字符过分了哦!
先看下这个url长什么样子:
http://10.4.124.195:9001/1/330211000000020570_20220105_210114_02_浙B12UX9_蓝_小车_000_{1875,1207,2734,1717}_{2355,1447,2494,1477}_2-1.jpg
这还是我为了演示简化后的。
遇到的坑:
1.开始这个url地址是通过get请求带参数传过来的,
http://XXX:8080/XXX?url=XXXXXXXXX
这是第一个坑,接到的参数url在我idea运行没问题的,但是springboot项目打了jar包,发布到windows server服务器或者linux服务器就会时不时出问题。什么原因:环境问题,编码方式不对,url通过urlcode编码传到后端就会可能出现编码问题。
解决办法:换成post请求,传过来一个json,解析字符串总不会出问题了。
{"url":"http://10.4.124.195:9001/1/330211000000020570_20220105_210114_02_浙B12UX9_蓝_小车_000_{1875,1207,2734,1717}_{2355,1447,2494,1477}_2-1.jpg"}
2.我想拿着这个地址用java访问。报错:资源不存在 。地址没错,肯定是编码错误了,我不能url中文请求吧:
@PostMapping(value = "/download")
@ApiOperation(value = "图片请求")
public void download(@RequestBody JSONObject object, HttpServletResponse response) throws IOException {
String url = object.getString("url");
URL connection = new URL(url );
//打开链接
HttpURLConnection conn = (HttpURLConnection) connection.openConnection();
//设置请求方式为"GET"
conn.setRequestMethod("GET");
//超时响应时间为5秒
conn.setConnectTimeout(10 * 1000);
}
3.urlcode编码后请求图片,还是资源不存在,找谁说理去。
@PostMapping(value = "/download")
@ApiOperation(value = "图片请求")
public void download(@RequestBody JSONObject object, HttpServletResponse response) throws IOException {
String url = object.getString("url");
String urlencoded =
URLEncoder.encode(url, "utf-8");
URL connection = new URL(urlencoded);
//打开链接
HttpURLConnection conn = (HttpURLConnection) connection.openConnection();
//设置请求方式为"GET"
conn.setRequestMethod("GET");
//超时响应时间为5秒
conn.setConnectTimeout(10 * 1000);
}
那我看看编码后地址变什么样了吧:
http%3A%2F%2F10.4.124.195%3A9001%2F1%2F330211000000020570_20220105_210114_02_%E6%B5%99B12UX9_%E8%93%9D_%E5%B0%8F%E8%BD%A6_000_%7B1875%2C1207%2C2734%2C1717%7D_%7B2355%2C1447%2C2494%2C1477%7D_2-1.jpg
牛啊,牛啊!它妈妈都不认识了。http的://都没了,
再改,替换特殊字符:
@PostMapping(value = "/download")
@ApiOperation(value = "图片请求")
public void download(@RequestBody JSONObject object, HttpServletResponse response) throws IOException {
String url = object.getString("url");
String urlencoded =
URLEncoder.encode(url, "utf-8")
.replaceAll("%3A", ":")
.replaceAll("%2F", "/")
.replaceAll("%2C", ",")
.replaceAll("%7B", "{")
.replaceAll("%3F","?")
.replaceAll("%7D", "}");
URL connection = new URL(urlencoded);
//打开链接
HttpURLConnection conn = (HttpURLConnection) connection.openConnection();
//设置请求方式为"GET"
conn.setRequestMethod("GET");
//超时响应时间为5秒
conn.setConnectTimeout(10 * 1000);
}
好了,请求到了,我开始也以为问题都解决了。其实只是解决了一半。
4.图片流拿到了,发送到前端时,后端报错:远程主机关闭了一个连接。诡异的是postman测试可以拿到文件流的。那就是问题在浏览器请求时的请求头、协议什么的有问题。最后排查到是文件流的文件名里面含有特殊字符。
什么原因:我把这个url地址当做文件名传给前端,可是url地址中有“,”,浏览器解析出错。终止了文件流传输。
真是什么情况都遇到了,找谁说理去!
解决办法:把这个文件名前后加上""括起来。
//文件名有“,”等特殊字符发送到前端会报错,用""括起来解决
response.addHeader("Content-Disposition", "attachment;filename=\"" + urlencoded+"\"");
最终后端代码:
package com.neusoft.viic.server.dataService.controller;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
/**
* @author dume
* @create 2022-01-05 10:42
**/
@Api(tags = "图片请求,前端blob类型或者arraybuffer类型接收")
@RestController
@RequestMapping("/image")
public class ImageController {
private static final Logger log = LoggerFactory.getLogger("adminLogger");
@PostMapping(value = "/download")
@ApiOperation(value = "图片请求")
public void download(@RequestBody JSONObject object, HttpServletResponse response) throws IOException {
ServletOutputStream out =null;
ByteArrayOutputStream baos = null;
String url = object.getString("url");
try {
log.info("图片下载打印utf-8请求地址"+url);
//将中文转成urlencode,将特殊字符转换回来,不然会请求异常,报错找不到资源
String urlencoded =
URLEncoder.encode(url, "utf-8")
.replaceAll("%3A", ":")
.replaceAll("%2F", "/")
.replaceAll("%2C", ",")
.replaceAll("%7B", "{")
.replaceAll("%3F","?")
.replaceAll("%7D", "}");
log.info("图片下载打印urlencoded请求地址"+urlencoded);
//new一个URL对象
URL connection = new URL(urlencoded);
//打开链接
HttpURLConnection conn = (HttpURLConnection) connection.openConnection();
//设置请求方式为"GET"
conn.setRequestMethod("GET");
//超时响应时间为5秒
conn.setConnectTimeout(10 * 1000);
//通过输入流获取图片数据
InputStream inStream = conn.getInputStream();
//获取response输出流
out = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
baos = new ByteArrayOutputStream();
while ((len=inStream.read(buffer))!=-1){
baos.write(buffer,0,len);
}
//设置允许跨域的key
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
//文件名有“,”等特殊字符发送到前端会报错,用""括起来解决
response.addHeader("Content-Disposition", "attachment;filename=\"" + urlencoded+"\"");
//设置文件大小
response.addHeader("Content-Length", "" + baos.size());
//设置文件名,避免问题,这个也用""括起来
response.setHeader("filename", "\"" + urlencoded+"\"");
//设置文件类型
response.setContentType("application/octet-stream");
//向前端返回文件流
out.write(baos.toByteArray());
}catch (Exception e){
e.printStackTrace();
log.error(e.getMessage());
throw e;
}finally {
try{
baos.flush();
out.flush();
response.flushBuffer();
baos.close();
out.close();
}catch (Exception e){
e.printStackTrace();
log.error(e.getMessage());
}
}
}
}
最终前端代码:
图片接收:
//获取图片用于前端打包下载
var getFile = function(imgUrl) {
return new Promise((resolve, reject) => {
axios({
method:'post',
url: HOST_VIIC + 'image/download',
headers: {
'Content-Type': 'application/json'
},
data:{
'url':imgUrl
},
responseType:'arraybuffer', //这个参数必须加上,否则返回的图片打包后无法显示
// responseType:'blob',
}).then(data => {
resolve(data.data)
}).catch(error => {
reject(error.toString())
})
})
}
图片打包:
exportResultImage: function() {
var self = this;
this.imgLoading = this.$loading({
lock: true,
text: '图片下载中,请稍后!',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
const zip = new JSZip();
const cache = {};
const promises = [];
//获取要导出图片的URL数组
var data = [];
const dataList = this.objectList;
for ( var i = 0; i < dataList.length; i++ ) {
var temp_data = {
plateNo: dataList[i].PlateNo,
passTime: dataList[i].PassTime,
imgUrl: dataList[i].ObjectStoragePath
}
data.push(temp_data);
}
data.forEach(item => {
const promise = this.viic.fileMng.getFile(item.imgUrl).then(data => { // 下载文件, 并存成ArrayBuffer对象
const file_name = item.plateNo + "_" + item.passTime + ".jpg";
zip.file(file_name, data, { binary: true }); // 逐个添加文件
cache[file_name] = data;
})
promises.push(promise);
})
Promise.all(promises).then(() => {
zip.generateAsync({type:"blob"}).then(content => { // 生成二进制流
var filename = this.currentConfig.exportTitle + "图片列表.zip";
FileSaver.saveAs(content, filename); // 利用file-saver保存文件
self.imgLoading.close();
})
})
},