情景:我有多条数据,每条数据中都存了很多附件。现在,我想把这些附件全都下载下来,而我又不想一条数据一条数据的点开,然后一个附件一个附件的下载。请问我该怎么办?
数据库结构:
id | title | enclosure |
1 | 美女照片合辑 | "[{\"type\":\"image\",\"url\":\"https://abc.xxx-xxx-xxxx.aliyuncs.com/ccc/upload/宇宙第一大美女.jpg\",\"name\":\"3c9293ec247a8632195f6d8b0a6e639b.jpg\",\"uid\":1519637358677, \"status\":\"success\",\"percentage\":100},
{\"type\":\"image\",\"url\":\"https://abc.xxx-xxx-xxxx.aliyuncs.com/ccc/upload/宇宙第二大美女.jpg\",\"name\":\"68-1612051H502.jpg\",\"uid\":1519637358678,\"status\":\"success\",\"percentage\":100}]" |
2 | 帅哥照片合辑 | "[{\"type\":\"image\",\"url\":\"https://abc.xxx-xxx-xxxx.aliyuncs.com/ccc/upload/10b04d56-4c95-48cf-ad14-51a8990ef36b.jpg\",\"name\":\"宇宙第一大帅哥.jpg\",\"uid\":1514340723425,\"status\":\"success\",\"percentage\":100}]" |
如上,我的数据库表结构是这样的,id是就是id,没什么好说的,title是我这条数据的名称,enclosure是我这条数据里带的附件,enclosure里存的是我把附件上传到文件服务器后附件在服务器的地址。第一条数据,它的标题叫“美女照片合辑”,它里面有两张美女的照片,在它对应的enclosure字段里我们也可以很清晰的看到有两条结构一样但是内容不一样的字符串(为了方便查看,已用空格隔开),如下:
而“帅哥照片合辑“里现在只有一张宇宙第一大帅哥的照片地址。
背景已交代完毕。现在确定功能实现思路:
(1) 通过id拿到对应数据enclosure字段的值,把附件地址全部罗列出来,放到一个集合A里;
(2)遍历集合A中中的附件地址,通过该地址拿到对应附件,把它转成的文件流,保存到一个压缩文件里;
(3)循环往复,直至所有附件都已下载完毕保存至压缩文件中,然后把压缩文件传到服务器中;
(4)拿到压缩包的地址,丢给前端,下载。
具体实现如下:
(按调用顺序controller→service书写)
controller层
/**
* 导出附件
* @return
*/
@ApiOperation("导出附件-照片合辑")
@PostMapping("export")
Output photoExport(Input input) {
//获取当前操作人的用户名,这是个人封装的方法,如果你没有的话可以不写,问题不大
String userName = this.getLoginUser().getUserName();
Output output = photoService.photoIdList(input,userName);
return output;
}
参数说明:
(1)首先参数Input只是一个自己封装的输入类而已,比如我有一个列表,我现在想下载标题名中含有“美女”两个字的所有数据的附件,那么我必定要先在一堆数据中把含有“美女”两个字的所有数据找出来,那“美女”这两个字必定是有前端传给后端的,而我这个input就只是一个用来接收前端传入的数据的一个容器而已。各人有个人的写法,各家有各家的框架,这个不必相同,掌握思想即可。
(2)this.getLoginUser().getUserName(); 这个是为了获取当前操作人的用户名,这是公司自己封装的方法,你们有类似的方法就用。没有就不用,问题不大。
service层
(1)photoIdList(Input input,String username)方法
第一步:根据前端传入的条件,找出所有符合条件的数据,把它们的id取出来放在一个数组中
/**
* 导出照片合辑附件的准备工作
* @param input
* @param username
* @return
*/
public Output photoIdList(Input input,String username) {
Output output=new Output();
StringBuilder sql=new StringBuilder("select * from photo ");
List<Photo> photoList = jdbcRepository.list(sql.toString(),input,Photo.class);
if(null!=photoList &&photoList .size()>0){
int[] ids=new int[photoList .size()];
for(int i=0;i<photoList .size();i++){
ids[i]=photoList.get(i).getId();
}
output.setResult(this.photoExport(ids,username));
}
return output;
}
(2)photoExport(int[] ids, String userName)方法
/**
* 导出照片附件
* @return
*/
public Output photoExport(int[] ids, String userName) throws YaolingException {
logger.debug("开始导出附件");
//new一个新集合,用于存放所有id的所有附件地址集合
List<FileUrlBean> allList = new ArrayList<>();
//遍历数组,把每个id对应的数据查出来
for (int id:ids) {
//查出id对应的那条数据
Photo photo=photoRepository.findOne(id);
//new一个新集合,用于存放当前id的所有附件地址
List<FileUrlBean> fileUrlBeans = new ArrayList<>();
//如果这条数据的附件字段不为空
if(!ObjectUtils.isEmpty(photo.getEnclosure())){
//把附件字段的值取出来赋值给字符串url,此时的url包括所有附件的文件地址
String url = photo.getEnclosure();
//把地址中的“\\”替换成“”,其实就是把\换掉,记住,是\而不是\\,第一个\是转义
url = url.replace("\\","");
//截取url第1位到第url.length()-1位
url = url.substring(1,url.length()-1);
//截取的字符串转成json数组
JSONArray jsonArray = JSONArray.fromObject(url);
//数组转成fileUrlBean实体
fileUrlBeans = JSONArray.toList(jsonArray,FileUrlBean.class);
for (FileUrlBean fileUrlBean:fileUrlBeans){
//把该条id对应的title赋值给fileUrlBean的title字段
//后期用来标记各个图片都属于哪一条数据
fileUrlBean.setTitle(photo.getTitle);
}
allList.addAll(fileUrlBeans);
}
}
//调用下一个打包压缩的方法,并返回压缩包的云地址
String returnUrl = mutileFileToGzip(allList);
return new Output(returnUrl);
}
部分讲解如下:
更正:这里面FileUrlBean并不是我想要的字段的汇集,而是为url中图片地址多定义的一个地址。内容如下:
(3)mutileFileToGzip(List<FileUrlBean> urlList)方法
/**
* 将多个压缩文件为一个zip文件
* @param urlList 要压缩的文件url集合
*/
public String mutileFileToGzip(List<FileUrlBean> urlList) {
try {
byte[] buffer = new byte[1024];
// 创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//创建一个新的缓冲输出流,以将数据写入指定的底层输出流
BufferedOutputStream fos = new BufferedOutputStream(baos);
ZipOutputStream zos = new ZipOutputStream(fos);
UploadUtil uploadUtil = new UploadUtil();
for (int i = 0; i < urlList.size(); i++) {
if(urlList.get(i).getPercentage().equals("100")&&urlList.get(i).getStatus().equals("success")){
String spStr=urlList.get(i).getUrl();
InputStream fis= uploadUtil.downLoad2(spStr);//获取到线上文件的数据流
zos.putNextEntry(new ZipEntry(urlList.get(i).getTitle()+"_"+i+"_"+urlList.get(i).getName()));//压缩文件内的文件名称
int length;
while ((length = fis.read(buffer)) > 0) {
zos.write(buffer, 0, length);//将文件读入压缩文件内
}
zos.closeEntry();
fis.close();
}
}
zos.close();
fos.flush();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String aa =uploadUtil.save("photo"+sdf.format(new Date()),new ByteArrayInputStream(baos.toByteArray()));
return aa;
} catch (Exception ioe) {
ioe.printStackTrace();
}
return null;
}
讲解如下:
(4)downLoad2(String url)方法
/**
* 下载文件2
* @param url 文件路径
* @return
* @throws Exception
*/
public InputStream downLoad2(String url)throws Exception{
String prefix2 = "https://abc.xxx-xxx-xxxx.aliyuncs.com/";
String prefix1 = "https://def.xxx-xxx-xxxx.aliyuncs.com/";
String[] sp=null;
if(-1!=url.indexOf(prefix2)) {
sp = url.split(prefix2);
}else if(-1!=url.indexOf(prefix1)){
sp = url.split(prefix1);
}
if(null==sp){
throw new Exception("附件地址错误!");
}
String spStr=sp[1].toString();
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
OSSObject ossObject =null;
if(-1!=url.indexOf(prefix2)) {
ossObject=ossClient.getObject("abc",spStr);
}else if(-1!=url.indexOf(prefix1)){
ossObject=ossClient.getObject("def",spStr);
}
if(null==ossObject){
throw new Exception("附件获取失败!");
}
InputStream inputStream = ossObject.getObjectContent();
return inputStream;
}
(5)上传压缩包
public String save(String fileName,ByteArrayInputStream byteArrayInputStream) throws Exception {
// 1.创建OSSClient实例
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
// 2. 上传后文件名
StringBuilder name= new StringBuilder(fileName);
String key =name+".zip";
// 3.上传文件
ossClient.putObject(bucketName, key, byteArrayInputStream);
// 4 关闭client
ossClient.shutdown();
return prefix.concat(key);
}