java调用shell脚本实现百万数据的csv报表导出

  利用shell脚本访问远程数据库,实现百万级数据导出csv文件

背景:工作中报表导出大多用的poi方式进行导出excel文件,数据量在几千,几万的不用考虑性能问题,但是部分报表中数据量需要导出百万数据,导出操作为了有好体验,避免页面假死且可进行后续操作,导出操作均是跳转新的页面,而项目中服务的页面ngnix响应时间是30秒,而即使使用poi多线程导出,100万数据测试了下大概也需要90秒左右,完全超过了页面响应时间导致页面错误,不可能为了这一个页面去调整ngnix参数,因此想到了用脚本导出。

思路:1.java程序创建线程,通过RUmtime执行随项目打包到服务器上的脚本

          2.服务器脚本访问远程Mysql数据库,实现数据查询并导出追加到指定路径下的文件

           3.Java程序访问指定文件夹,用输出流下载该文件并上传到浏览器,实现下。

           4.避免文件堆积在下载完成之后,进行删除服务器文件操作。

技术知识点:

  1. java程序调用脚本,并对脚本执行结果的获取打印(执行脚本的命令需要使用数据编写,中间不能带有空格,查询sql动态生成,直接作为一个整体的参数进行传输);
  2. 访问远程mysql数据库实现数据导出的Shell脚本的编写(服务器需要安装mysql客户端,才能支持Mysql命令,切注意数据库文件的输出编码格式,输出文件名不能带有中文)
  3. 下载功能实现,输出流以及http对象response的运用

具体实现过程:

  1. java部分
  • 1)生成sql以及调用shell脚本
  • 2)实现下载
/**
	 * @param bo
	 * @param response
	 * @throws Exception
	 * 脚本导出csv文件,拼接sql
	 * 2018年12月20日
	 * @auther:liming
	 */
	public void importExcel(ReportBo bo,HttpServletResponse response) throws Exception {
		//1.拼接导出查询数据sql
		StringBuilder sb = new StringBuilder();
		
		 sb.append("select  v.prov_desc as provName, ifnull(v.area_name,'') as cityName,");
		 sb.append("ifnull(v.yxcp,'')  as marketProduct");
		 sb.append(" from  clyx_app_baobiao.tc_user_operater_info   v");
		 sb.append(" where 1 = 1 ");
		if(bo.getProvId()==null){
			String [] provIds = bo.getProvIds();
			StringBuilder ids = new StringBuilder();
			for(int i=0;i<provIds.length;i++){
				ids.append(provIds[i]+",");
			}
			ids.toString().substring(0, ids.length()-1);
			sb.append("and v.prov_id in ("+ids+")");
		}
	--------部分sql生成过程已省略---	
		if(bo.getCityName()!=null&&!bo.getCityName().equals("")) {
			sb.append("and v.area_name like   CONCAT('%','"+bo.getCityName()+"','%')  ");
		}	
		if(bo.getActivityName()!=null&&!bo.getActivityName().equals("")){
			sb.append("and v.activity_name like CONCAT('%','"+bo.getActivityName()+"','%')");
		}
		if(bo.getOptionState().equals("1")){
			sb.append("and v.is_operate  = '操作'");
			if(bo.getStartDate()!=null&&!bo.getStartDate().equals("")){
				sb.append("and v.contact_date >= str_to_date('"+bo.getStartDate()+"','%Y-%m-%d %H:%i:%s') ");
			}
			if(bo.getEndDate()!=null&&!bo.getEndDate().equals("")){
				sb.append(" and v.contact_date  <= str_to_date(date_add(DATE_FORMAT('"+bo.getEndDate()+"','%Y-%m-%d %H:%i:%s'),INTERVAL '23:59:59' hour_second),'%Y-%m-%d %H:%i:%s')");
			}
		}
		if(bo.getOptionState().equals("2")){
			sb.append("and v.is_operate  != '操作'");
			if(bo.getStartDate()!=null&&!bo.getStartDate().equals("")){
				sb.append("and v.date_id >= '"+ bo.getStartDate()+"'");
			}
			if(bo.getEndDate()!=null&&!bo.getEndDate().equals("")){
			 sb.append(" and v.date_id  <= '"+bo.getEndDate()+"'");
			}
		}
		if(bo.getOptionState().equals("3")){
			if(bo.getStartDate()!=null&&!bo.getStartDate().equals("")){
				sb.append("and v.date_id >= '"+ bo.getStartDate()+"'");
			}
			if(bo.getEndDate()!=null&&!bo.getEndDate().equals("")){
			 sb.append(" and v.date_id  <= '"+bo.getEndDate()+"'");
			}
		}
		sb.append(" order by  v.pop_time desc,v.date_id desc");
		final String sql = sb.toString();
		System.out.println("拼接sql操作完成,sql语句为:"+sql);
		
		//生成导出文件名
		String now = new SimpleDateFormat("YYYYMMDDHHmmss").format(new Date());
		final String fileName = "tcOption" + now+".csv" ; //文件名前缀(导出文件名+当前时间)tcOption2018122114410
		
		String filePath = ClassUtils.getDefaultClassLoader().getResource("").getPath()+"files/"; //项目的根路径,文件路径
		//2.调用脚本导出文件
		shellExportExcel(sql, filePath+fileName);
		
		//3.下载文件需要下载的路径+文件名
		String fullFilePath = filePath+fileName;
		System.out.println("要查找的文件是" + fullFilePath);
		 File file = new File(fullFilePath);
		  
	     long commandTime = new Date().getTime();  
	     long nowTime = new Date().getTime();
	     long timeout = 600*1000;
	        while(true){
	        	if(file.exists()){
	            	System.out.println("找到文件了,开始下载文件");
	            	
	            	break;
	            }else{
	            	if (nowTime - commandTime > timeout) { 
	            		System.out.println("导出时间超出10分钟,导出失败");
	            		throw new Exception("导出时间超出10分钟,导出失败");
	                } else {
	                	nowTime = new Date().getTime();
	                	System.out.println("没有找到文件,30秒后继续查找文件");
	                	Thread.sleep(30*1000);
	                }
	            	
	            }
	        }
	        download(fileName, filePath,response);
	}
//调用脚本
public void shellExportExcel(String sql,String filePathAndName) {
        Process process = null;
       String shellPath = "/usr/local/tomcat/webapps/clyxstatcount/WEB-INF/classes/TcReport.sh";
       //脚本目录以及名称   两个参数:sql  导出文件名   
        System.out.println("------导出文件名"+filePathAndName);
        System.out.println("------脚本所在目录为"+shellPath);
      // String[] command = {"/bin/bash",shellPath,filePathAndName,"\""+sql+"\""}; //加双引号避免空格问题
       String[] command = {"/bin/bash",ClassUtils.getDefaultClassLoader().getResource("").getPath()+"test.sh"}; 
        long timeout = 600 * 1000;
        try {  
        	System.out.println("开始执行shell命令");
            process = Runtime.getRuntime().exec(command);  
            CommandWaitForThread commandThread = new CommandWaitForThread(process);  
            commandThread.run();  
  
            long commandTime = new Date().getTime();  
            long nowTime = new Date().getTime();  
            boolean timeoutFlag = false;  
            while (!commandThread.isFinish()) {  //判断脚本是否执行完成
                if (nowTime - commandTime > timeout) {  
                    timeoutFlag = true;  
                    break;  
                } else {  
                    Thread.sleep(1000);  
                    nowTime = new Date().getTime();  
                }  
            }  
            if (timeoutFlag) {  
                // 命令超时   
                System.out.println("正式执行命令:" + command + "超时");  
            } else if (commandThread.getExitValue() == 0) {
            	System.out.println("正式执行命令:" + command + "完毕");  
            }
        } catch (IOException | InterruptedException e) {  
            e.printStackTrace();  
        } finally {  
            if (process != null) {  
                process.destroy();  
                System.out.println("process destroy");
            }
        }
	}
//下载
public void download(String downloadFileName, String filePath,HttpServletResponse response) {
        // 定义输出流
        OutputStream outputStream = null;
        InputStream inputStream = null;
        
        try {
            // 重置输出对象
            response.reset();
            response.setContentType("text/plain");
            // 设置输出头信息
            response.setHeader("Content-disposition", "attachment;filename=" + downloadFileName);
            response.setHeader("Pragma", "No-cache");

            // 设置输出流
            outputStream = response.getOutputStream();

            HttpServletRequest request = ((ServletRequestAttributes)      RequestContextHolder.getRequestAttributes()).getRequest();
            // 读取导出文件
            String realPath = request.getServletContext().getRealPath("/");
            //inputStream = new FileInputStream(new File(new StringBuilder(realPath).append("/files/export_files/").append(downloadFileName).toString()));
            inputStream = new FileInputStream(new File(new StringBuilder(filePath).append(downloadFileName).toString()));
            
            byte[] buff = new byte[1024];
            int readed = -1;
            while ((readed = inputStream.read(buff)) > 0) {
                outputStream.write(buff, 0, readed);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != outputStream) {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    //e.printStackTrace();
                }
            }
         //不管报错没报错,都执行删除文件操作
            File file = new File(filePath+downloadFileName);
            if(file.exists()){
            	log.info(file+"文件存在开始删除");
            	if(file.delete()){
            		log.info(file+"文件删除成功");
            	}else{
            		log.info(file+"文件删除失败");
            	}
            }else{
            	log.info(file+"文件不存在,不执行删除操作");
            }
        }
	}

脚本的内容如下:

#!/bin/bash

echo  'export start'

mysql --default-character-set=utf8 -h数据库地址 -P3306 -u用户名 -p'密码' 数据库名  -e"${@:2}" >  $1

echo 'export end'

exit

脚本注意点:

  1. java程序调用脚本的时候,sql作为参数当中会存在空格,脚本中${@:2},是将其他所有的参数都当作为第二个参数
  2. $1为参数1,文件名称
  3. 编写脚本时,在linux环境下编写,如果在windows环境下编写会存在转义问题,例如换行符问题,查看脚本属于哪个环境
  4. 脚本存放路径为项目的根目录

  •  vi  脚本名称

:set ff  (查看脚本环境)  dos 问windows环境,unix为Linux环境

  • 补充:

dos2unix  --->  是把windows格式的文本转换成linux下格式

unix2dos  --->是把linux下文本格式转换成windows下的文本格式

5.脚本中涉及路径问题,直接写绝对路径,避免因为路径问题文件没找到

6.服务部署的时候没有安装mysql在执行mysql脚本会报错,因此需要安装mysql客户端,由于我的服务是在容器中部署,在构建镜像文件dockerfiles需要添加mysql

经过测试脚本导出操作100万数据大概10秒左右生成文件,下载依据网速不同时间不等,但10秒这个时间完全在页面响应之内。

版权声明:本文为博主原创文章,转载请注明本页地址:https://blog.csdn.net/l1994m/article/details/86186851

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员lm

感谢您的支持,后续分享更多知识

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值