java后台导出word,详细过程及趟过的坑

一、我使用的java导出word,由XML+FreeMarker来实现的,因此需要以下工具:
1、office。这里不能使用wps,因为wps由word文档转为xml文档时,解析会发生错误,导致最终模板和设想的样子有一定的区别。
2、xml编辑工具,这里我推荐使用Firstobject free XML editor。
3、引用FreeMarker的jar包。

	<dependency>
		<groupId>org.freemarker</groupId>
		<artifactId>freemarker</artifactId>
		<version>2.3.23</version>
	</dependency>

二、模板制作
1、模板制作一定要谨慎,最好是样式全部调试好了,位置都OK了,需求基本不会变了再制作,因为每次修改哪怕一点点东西,都有可能导致全部从头再来一次,非常麻烦。
2、尽量使用office制作,不要用wps制作,然后再用office开发,因为这两者编码和解析方式都有很大的不同,可能导致你导出的word样式变形。
3、如果导出的样式涉及到了图片(多张图片)时,一定要使用小图片制作,最好只有几十K的,不要使用大图片然后缩小,没用的,base64还是会占那么多位置,替换图片时长到你怀疑人生。二是,如果最终导出是有多张图片的,一定要使用多张图片,如果是复制粘贴的,最终xml里只有一段base64,给替换造成非常大的麻烦。
4、如果调试好自己要的所有样式后,另存为Word 2003XML,wps没有这个模式,只有xml格式,使用这个模式最终效果并不理想。转好xml格式后,打开Firstobject free XML editor,直接将xml扔进去就行了,F8调整格式,左边类似目录层级(只用看body就行了),右边为具体源码。
5、替换源码里相应位置的信息换成freemarker的标识。其实就是Map<String, Object>中key。如:姓名替换为${name}
6、 如果还有地方需要循环使用时,可以使用:<#list maps as map></#list> maps是Map<String, Object>中key,值为数组,map为自定义;如果有的内容需要一定条件下使用时,可以使用:<#if name??></#if>,判断循环是否为空:<#if orgList?? && (orgList?size > 0) > </#if>。
7、图片处理(我认为最麻烦的地方)。
(1)在加入了图片占位的地方,会看到一片base64编码后的代码,把base64替换成"{image},也就是Map<String, Object>中key,值必须要处理成base64。(意味着传入的值必须是base64)
(2)这时如果不对模板图片大小做处理,导出来的图片大小就是你模板的大小,基本就是变形+严重缩小,因此还需要对图片大小进行替换。如:<v:shape id="_x0000_i1030" type="#_x0000_t75" style=“width:60pt;height:60pt”>中的60pt整体替换为你输入的值,替换后为:<v:shape id="_x0000_i1030" type="#_x0000_t75" style=“width:{problemsImagesList.w4};height:{problemsImagesList.h4}”>,这里的w4和h4为自己图片大小。
(3)如果你的图片需要循环输出,你还需要改动图片的引用,如果不改动,第二组循环图片还是第一组的。如:
<w:binData w:name="{“wordml://0200000”+0000086+".jpg"}" 和<v:imagedata src="{“wordml://0200000”+0000086+".jpg"}" o:title=“d”/>中的00000086替换掉,替换换后为:<w:binData w:name="{“wordml://0200000”+problemsImagesList_index+4+".jpg"}" 和<v:imagedata src="{“wordml://0200000”+problemsImagesList_index+4+".jpg"}" o:title=“d”/>,这里叫什么无所谓,只要上下两处引用相同,和这一次循环的其他东方图片不同即可。
注意这里有美元符号,不懂为什么放到文章中,会隐藏很多,因此全部美元符号都去掉了
在这里插入图片描述
8、至此模板准备基本就OK啦,只需要在Firstobject free XML editor里另存为flt格式就行了,注意flt不要再用word打开了,如果你打开了,那么恭喜你,模板从头再来一次吧。

三、接下来就是在java里组装你需要导入到模板里的数据啦,其实我个人认为,模板只要为问题,这一步就很轻松了,使用的几个工具类我就直接扔出来就行了。

1、生成word的doc格式的类

在这里插入代码片package com.gykj.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import freemarker.template.Configuration;
import freemarker.template.Template;

public class WordUtils {
	//配置信息,代码本身写的还是很可读的,就不过多注解了  
	private static Configuration configuration = null;  
	//这里注意的是利用WordUtils的类加载器动态获得模板文件的位置  
	private static final String templateFolder =  WordUtils.class.getClassLoader().getResource("").getPath();  //"D:/patrol";//
	//private static final String templateFolder = "H:/我的项目/lm/lm/web/src/main/webapp/WEB-INF/templates";  
	static {  
		configuration = new Configuration();  
		configuration.setDefaultEncoding("utf-8");  
		try {  
			configuration.setDirectoryForTemplateLoading(new File(templateFolder));  
		} catch (IOException e) {  
			e.printStackTrace();  
		}  
	}  

	private WordUtils() {  
		throw new AssertionError();  
	}  

	public static void exportMillCertificateWord(HttpServletRequest request, HttpServletResponse response, Map map,String title,String ftlFile,String flieUrl) throws IOException {  
		Template freemarkerTemplate = configuration.getTemplate(ftlFile);  
		File file = null;  
		InputStream fin = null;  
		//ServletOutputStream out = null;  
		FileOutputStream out = null;
		try {  
			// 调用工具类的createDoc方法生成Word文档  
			file = createDoc(map,freemarkerTemplate);  
			fin = new FileInputStream(file);  
			String fileName = title + ".doc";
			/*response.setCharacterEncoding("utf-8");  
			response.setContentType("application/msword");  
			// 设置浏览器以下载的方式处理该文件名  
			String fileName = title + ".doc";  
			response.setHeader("Content-Disposition", "attachment;filename="  
					.concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));  

			out = response.getOutputStream(); */ 
			File file2 = new File(flieUrl+fileName);
			if (!file2.exists()) {
				file2.createNewFile();
			}
			out = new FileOutputStream(file2);
			byte[] buffer = new byte[512];  // 缓冲区  
			int bytesToRead = -1;  
			// 通过循环将读入的Word文件的内容输出到浏览器中  
			while((bytesToRead = fin.read(buffer)) != -1) {  
				out.write(buffer, 0, bytesToRead);  
			}  
		} finally {  
			if(fin != null) fin.close();  
			if(out != null) out.close();  
			if(file != null) file.delete(); // 删除临时文件  
		}  
	}  

	private static File createDoc(Map<?, ?> dataMap, Template template) {  
		String name =  "sellPlan.doc";  
		File f = new File(name);  
		Template t = template;  
		try {  
			// 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开  
			Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");  
			t.process(dataMap, w);  
			w.close();  
		} catch (Exception ex) {  
			ex.printStackTrace();  
			throw new RuntimeException(ex);  
		}  
		return f;  
	}  
}

2、将图片转为base64的类,这里我因为需求问题做了等比例缩放功能,缩放到多大由double widthMax = 350; double heightMax = 350;这两个值确定,这个大小基本就是你存图片表格的大小。

package com.gykj.utils;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;

import sun.misc.BASE64Encoder;

public class getImageBase {

	//获得图片的base64码
		//@SuppressWarnings("deprecation")
		public static Map<String, Object> getBase64(String imgsrc) {
//			if(src==null||src==""){
//				return "";
//			}
//			File file = new File(src);
//			if(!file.exists()) {
//				return "";
//			}
//			InputStream in = null;
//			byte[] data = null;  
//			try {
//				in = new FileInputStream(file);
//			} catch (FileNotFoundException e1) {
//				e1.printStackTrace();
//			}
//			try {  
//				data = new byte[in.available()];  
//				in.read(data);  
//				in.close();  
//			} catch (IOException e) {  
//				e.printStackTrace();  
//			} 
//			BASE64Encoder encoder = new BASE64Encoder();
//			return encoder.encode(data);
			
			Map<String, Object> map = new HashMap<String, Object>();
			 try {
				 	double rate = 0;
				 	int widthdist = 0;
				 	int heightdist = 0;
				 	double widthMax = 350;
				 	double heightMax = 350;
				 	
		            File srcfile = new File(imgsrc);
		            // 检查图片文件是否存在
		            if (!srcfile.exists()) {
		                System.out.println("文件不存在");
		            }
		            // 如果比例不为空则说明是按比例压缩
		                //获得源图片的宽高存入数组中
		            int[] results = getImgWidthHeight(srcfile);
		            
		            //计算具体等比比例
		            int width = results[0];
		            int height = results[1];
		            double widthRate = widthMax/width;
		            double heightRate = heightMax/height;
		            if (heightRate > widthRate) {
						rate = widthRate;
					}else {
						rate = heightRate;
					}
		            
		            if (results == null || results[0] == 0 || results[1] == 0) {
		            	return null;
		            } else {
		            	//按比例缩放或扩大图片大小,将浮点型转为整型
		            	widthdist = (int) (results[0] * rate);
		            	heightdist = (int) (results[1] * rate);
		            }
		            // 开始读取文件并进行压缩
		            Image src = ImageIO.read(srcfile); 

		            // 构造一个类型为预定义图像类型之一的 BufferedImage
		            BufferedImage tag = new BufferedImage((int) widthdist, (int) heightdist, BufferedImage.TYPE_INT_RGB);

		            //绘制图像  getScaledInstance表示创建此图像的缩放版本,返回一个新的缩放版本Image,按指定的width,height呈现图像
		            //Image.SCALE_SMOOTH,选择图像平滑度比缩放速度具有更高优先级的图像缩放算法。
		            tag.getGraphics().drawImage(src.getScaledInstance(widthdist, heightdist, Image.SCALE_SMOOTH), 0, 0, null);

//		            //创建文件输出流
//		            FileOutputStream out = new FileOutputStream(imgdist);
//		            //将图片按JPEG压缩,保存到out中
//		            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
//		            encoder.encode(tag);
//		            //关闭文件输出流
//		            out.close();
		            
		            ByteArrayOutputStream baos = new ByteArrayOutputStream();
		            ImageIO.write(tag, "jpg", baos);
		            byte[] bytes = baos.toByteArray();
		            BASE64Encoder encoder = new BASE64Encoder();
		            map.put("img", encoder.encodeBuffer(bytes).trim());
		            map.put("w", widthdist);
		            map.put("h", heightdist);
		            map.put("name", imgsrc);
		            baos.close();
		            return map;
		        } catch (Exception ef) {
		            ef.printStackTrace();
		        }
			return null;
			
			
			
		}

		/**
		 * 获取图片宽度和高度
		 * 
		 * @param 图片路径
		 * @return 返回图片的宽度
		 */
		public static int[] getImgWidthHeight(File file) {
			InputStream is = null;
			BufferedImage src = null;
			int result[] = { 0, 0 };
			try {
				// 获得文件输入流
				is = new FileInputStream(file);
				// 从流里将图片写入缓冲图片区
				src = ImageIO.read(is);
				result[0] =src.getWidth(null); // 得到源图片宽
				result[1] =src.getHeight(null);// 得到源图片高
				is.close();  //关闭输入流
			} catch (Exception ef) {
				ef.printStackTrace();
			}
		
			return result;
		}
	
}

3、最后的值测试样例类以及测试模板,因此篇幅原因不上来了,如需要,留言邮箱即可
4、需要demo的人可能有点多,我没法及时回复,就放百度云盘了:
https://pan.baidu.com/s/1fgjJBIbtzXJNnCwLlI7F0Q
提取码:u6qq

  • 8
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值