前言
公司安排我做一个工具,作用是下载日志文件,做了一个初步的东西出来,其实还是有很多可以改进的地方。
需求
日志文件自主下载。
处理办法
因为公司有自己的前段工具,所以我们可以直接使用前段工具,直接上代码
一个展示所有文件的前段页面
idp.event.bind("loadData", function(e) {
debugger;
var params = '';
idp.service.fetch(
"/api/bp/pf/v1.4/business/bill/generalGGXMXX",
params,
false
).done(function(data){
//成功回调方法
debugger
var newDataArray = [];
var grid=idp.control.get("grid_373170");
for(var item in data){
var defaultrow = idp.uiview.modelController.getDefaultData("loggingfiletoget");
$.extend(defaultrow,{NAME:data[item]["name"],FILESIZE:data[item]["filesize"],FILETIME:data[item]["filetime"]});
newDataArray.push(defaultrow);
}
grid.addRows(newDataArray);
}).fail(function(data){
// 失败回调方法
idp.error(idp.lang.get("JZPubFront004")); //("加载单据相关信息异常");
return false;
});
})
loadData
是指的一个前段的加载过程中的一个触发的方法,就是类似Vue的生命周期一样
后端代码我就简单说一下吧
@Override
public List<Loggingfiletoget> getLogging(String s) {
LoggingUtils loggingUtils =new LoggingUtils();
String url1 = loggingUtils.getUrl();
logger.error("loggingUtils");
logger.error(url1);
//上述代码是为了直接找到logging的所在目录的文件夹
File file = new File(url1);
logger.error("file.toString()");
logger.error(file.toString());
File[] files = file.listFiles();
logger.error("Arrays.toString(files)");
logger.error(Arrays.toString(files));
List<Loggingfiletoget> Loggingfiletogetlist =new ArrayList<>();
for (int i = 0; i < files.length; i++) {
File file1 = files[i];
Loggingfiletoget loggingfiletoget = new Loggingfiletoget();
loggingfiletoget.setId(UUID.randomUUID().toString());
loggingfiletoget.setName(file1.getName());
loggingfiletoget.setFilesize(String.valueOf(file1.length()));
loggingfiletoget.setFiletime(longToDate(file1.lastModified()));
logger.error(loggingfiletoget.toString());
Loggingfiletogetlist.add(loggingfiletoget);
}
Collections.sort(Loggingfiletogetlist);
logger.error("Loggingfiletogetlist");
logger.error(Loggingfiletogetlist.toString());
return Loggingfiletogetlist;
}
就是很简单的一个获取路径,获取路径下面的所有文件,拼装实体类然后前段展示。
文件下载
文件下载是大头,将文件名称传到后端,后端去指定文件夹将文件获取,并且传递到前段
function download() {
var row=idp.control.get("grid_373170").getSelectedRows();
for(var i =0 ;i<row.length;i++)
{
var params = {"filename":row[i].NAME}
idp.service.fetch(
"/api/bp/pf/v1.4/business/bill/downloadingss",
params,
false
).done(function(data){
//成功回调方法
}).fail(function(data){
// 失败回调方法
// 使用Blob
var filename = params.filename
if(filename.substring(filename.length-2)=='gz'){
filename = filename.substring(0,filename.length-3)
}
var pp = data.responseText
console.log(pp)
let blob = new Blob([pp], { type: `text/plain;charset=utf-8` });
// 获取heads中的filename文件名
let downloadElement = document.createElement("a");
// 创建下载的链接
let href = window.URL.createObjectURL(blob);
downloadElement.href = href;
// 下载后文件名
downloadElement.download = filename;
document.body.appendChild(downloadElement);
// 点击下载
downloadElement.click();
// 下载完成移除元素
document.body.removeChild(downloadElement);
// 释放掉blob对象
});
}
// var params = {"filename":row.NAME}
}
前段看着麻烦其实也只有传参的时候将文件名传到后台,从后台去获取文件流
对应后端
@POST
@Path("/downloadingss")
// @Consumes(MediaType.APPLICATION_JSON)
public void downloadingss(@RequestParam String s, @Context HttpServletRequest request,@Context HttpServletResponse response) throws IOException;
Controller层中HttpServletRequest 和HttpServletResponse 是通过@Context注解赋值的,不是需要前段进行传参,然后后台将文件流输出
@Override
public void downloadingss(String s, HttpServletRequest request, HttpServletResponse response) throws IOException {
LoggingUtils loggingUtils =new LoggingUtils();
String url1 = loggingUtils.getUrl();
JSONObject jsonObjectFilename = JSONObject.parseObject(s);
String filename = jsonObjectFilename.getString("filename");
File file = new File(url1+File.separator+filename);
String s1 = filename.substring(filename.length() - 2);
byte[] bytes;
if("gz".equals(s1)){
InputStream in = new GZIPInputStream(new FileInputStream(url1+File.separator+filename));
bytes = File3byte(in);
}else{
bytes = File2byte(file);
}
filename = URLEncoder.encode(filename, "UTF-8");
response.reset();
response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
response.addHeader("Content-Length", "" + bytes.length);
response.setContentType("application/octet-stream;charset=UTF-8");
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
response.flushBuffer();
}
这是我从网上找到了一个比较好用的一个版本,至少他传递到前段的文件流是UTF-8的不是乱码的。
获取到文件,这里我们有一个问题需要去处理就是这个文件是两种,第一种是log文件,第二种文件是log.gz
是我们的应用将一定大小的日志自动打包,但是当我们直接用了流进行输出的时候会因为压缩包会有加密所以会出现乱码,所以需要GZIPInputStream进行压缩包的解压。
然后在这里的处理是将文件变成byte数组在OutputStream 传递到前台页面的
工具类
public static byte[] File3byte(InputStream fis){
byte[] buffer = null;
try
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int n;
while ((n = fis.read(b)) != -1)
{
bos.write(b, 0, n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
return buffer;
}
public static byte[] File2byte(File tradeFile){
byte[] buffer = null;
try
{
FileInputStream fis = new FileInputStream(tradeFile);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int n;
while ((n = fis.read(b)) != -1)
{
bos.write(b, 0, n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
return buffer;
}
最重要的
当你的后台处理完毕之后你会发现可以将流传递到前段,但是前段没有任何的展示,我想了好久好久,才知道原来前段也是需要去处理流的才会产生文件
需要确认你的后台返回是正确的,并且返回前台的流中的内容是正常可以读取的,然后才开始处理对应的前段代码
var filename = params.filename
if(filename.substring(filename.length-2)=='gz'){
filename = filename.substring(0,filename.length-3)
}
var pp = data.responseText
console.log(pp)
let blob = new Blob([pp], { type: `text/plain;charset=utf-8` });
// 获取heads中的filename文件名
let downloadElement = document.createElement("a");
// 创建下载的链接
let href = window.URL.createObjectURL(blob);
downloadElement.href = href;
// 下载后文件名
downloadElement.download = filename;
document.body.appendChild(downloadElement);
// 点击下载
downloadElement.click();
// 下载完成移除元素
document.body.removeChild(downloadElement);
// 释放掉blob对象
将流在前段获取之后需要注意一点,我们这边后台处理过gz文件,已经变成了文本文件,如果前段再出现.gz文件则会出现无法解压的问题,所以如果文件是.gz需要在下载的时候将文件名进行修改。
然后这也是一段我从网上下载的处理流变成浏览器文件的前段代码可以直接使用的
上图就是前段脚本的获取的文件流。
不知道我的读者看完会不会有一个问题,为什么我要在处理的时候放在访问接口失败的里面去下载文件,其实是因为我们的自己fetch工具接受接口的返回值只能够是json格式,所以我的流就是返回失败了。
感受
一定要注意,当遇到的问题时候尤其是没有时间的时候尽量多线程一起跑,几种方法一起尝试,我当时从网上down文件下载的流同时发布4个接口,很庆幸有一个可以,这样可以减少重启次数,还有就是当你的接口返回值正常的时候可以看看前段的问题,是不是前段代码出现了问题,当后端返回200并且可以将正确的数返回这个时候需要考虑一下是不是前段出现了问题,特别是当控制台也没有问题的时候,可以看一下是不是你的前段传参到后端的返回值的获取出现了问题,当时我就是返回值直接去了失败,但是控制台没有任何报错包括F12的时候也是,追根溯源吧还是。
改进
其实我真的想是那种直接就是那种流下载,就是不管后台是什么文件,都可以直接下载流文件,但是前段的话好像是不允许,这个我们后续可以在进行测试,然后还有就是公共路径的问题,其实我觉得公共路径反而是变得更好,因为不需要别人进行配置了,更加省力气,其实可以话就是那种直接在前端做一个fstp,我最初的想法,但是可以技术上面的有点难度。