您的位置:首页 > 其它

POI之自定义注解生成文档-yellowcong

2017-07-16 00:21 148 查看
到处数据如果是成条的,而且是批量处理的情况下,我们可以通过对数据模型使用注解,来解决这种问题。



环境搭建

<!-- excel -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>

<!-- word -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.17</version>
</dependency>

<!-- xlsx -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<!-- xlsx  依赖这个包 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.17</version>
</dependency>


实现思路

在数据模型对象下,添加一个注解类,说明抽出的字段在excel中所对应的表头,然后通过工具类,通过反射,获取每个字段所对应的标题,同时设定每个字段对应的数据

POI自定义注解类(HFFSAlias.java)

HFFSAlias.java
这个注解类是基于file字段的,用于说明字段在xls中的中文名称

package com.yellowcong.utils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 通过注解来操作别名,判断的是字段上的别名
* 通过这个注解类和我们的PoiUtils 可以很好的解决文档导出的问题
*
* @author yellowcong
* @date 2016年1月7日
*/
@Target({ java.lang.annotation.ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface HFFSAlias{
//设定别名,通过value 这个函数,就不需要写参数了
//@HFFSAlias("xx") 就可以了
public String value() default "";
}


模型类对象(User.java)

这个模型对象,使用的是Hibernate的框架,通过Hibernate做ORM框架,我们只需要调用自定义的
HFFSAlias.java
这个annotation,就可以设定标题了

package com.yellowcong.model;

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import org.hibernate.annotations.Index;
import org.hibernate.validator.constraints.Email;

import com.yellowcong.utils.HFFSAlias;

/**
* yellowcong 的新网站,以后也会一直建立下去
*
* @author yellowcong
*
*
* QQ
* 新浪微博借口
* 微信接口  数据推送
* github 接口
*
*
* 用户登录的 email
* 用户登录的手机号
* 用户的名称
* 必须是唯一的
*
*
* 网名和头像随机生成,如果用户不填写的情况下
*
*
* 手机和email登录
* 三方登录
*
*/
@Entity
@Table(name="ycg_user")
public class User {
@HFFSAlias("编号")
private int id;
//用户名称
@HFFSAlias("用户名称")
private String username;
//别名
//别名不能存在邮箱@ 不然会导致问题,我们需要做js验证
@HFFSAlias("别名")
private String nickname;
//密码
//  @HFFSAlias("密码")
private String password;
//创建日期
@HFFSAlias("创建日期")
private Date createDate;
//用户头像
@HFFSAlias("用户头像")
private String imgUrl;
//用户的邮箱地址
@HFFSAlias("邮箱")
private String email;
//电话
@HFFSAlias("电话号")
private String phone;
//是否邮箱激活
// 0 表示未激活
// 1 表示激活
@HFFSAlias("是否激活")
private int isActive;

public User() {
super();
// TODO Auto-generated constructor stub
}

//用于验证用户是否登录
public User(int id) {
super();
this.id = id;
}

@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Index(name="username")
@Column(name="username",length=16)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}

//我们不需要设定我们的nickname 为唯一的
@Index(name="nickname")
@Column(name="nickname",length=16,unique=false)
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
@Column(length=32)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}

@Column(name="create_date")
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}

@Column(name="img_url")
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}

@Email
@Column(unique=true,length=64)
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}

@Column(name="phone",length=11,unique=true)
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Column(name="is_active",length=1)
public int getIsActive() {
return isActive;
}
public void setIsActive(int isActive) {
this.isActive = isActive;
}

}


工具类

工具类中,主要可供使用的是将获取的
List<?>
数据转化为Excel文档,同时也可以 将转化的Excel文档转化为
List<?>
,这几个方法都有一个特点,他们是有表头,而且还有一点缺点,对于时间类型需要指定,不然有问题,同时逆向工程中,读取的表的数据 少一行

package com.yellowcong.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;

/**
* 报表工具包,用来操作报表中的数据
* @author yellowcong
* @date 2016年1月6日
* 依赖 poi-2.5.1.jar
* beanutil
*
* 重要的方法
* 创建xls文件
*
* 基于annotation
* 类对象需要使用@HHFSAlias 注解 给字段设定别名
* createHSSFByAnnotation(List<?>)
*
* 自己配置好映射关系,然后传 List<?>对象
* createHSSF(LinkedHashMap<String, String>, List<?>)
*
* 逆向生成 java对象
* 类对象需要使用@HHFSAlias 注解 给字段设定别名
* reverseHSSFByAnnotation(File, Class<?>)
*
* 对于文档直接使用,使用String[]来配置我们的 映射关系,boolena 设定是否有表头
* reverseHSSF(File, String[], Class<?>, boolean)
*
* 对于有表头的可以使用这个,也可以使用上一个,上一个的效率高一点
* reverseHSSF(File, Map<String, String>, Class<?>)
*/
public class PoiUtils {
private PoiUtils(){}

/**
* 将HSSFWorkbook 对象转化为 文件
* @param wb  HSSFWorkbook 对象
* @param file 文件
*/
public static void copyHSSFToFile(HSSFWorkbook wb,File file){
FileOutputStream  out = null;
try {
out = new FileOutputStream(file);
} catch (Exception e) {
// TODO: handle exception
}finally{

try {
if(wb != null){
wb.write(out);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
if(out != null){
out.close();
}
} catch (Exception e2) {
// TODO: handle exception
}
}
}
}

/**
* 获取一个不带有输入流的HSSFWorkbook 对象
* @return
*/
public static HSSFWorkbook getHSSFWorkbook(){
return getHSSFWorkbook(null);
}
/**
* 获取我们的一个HSSFWorkbook
* @return
*/
public static HSSFWorkbook getHSSFWorkbook(File file){
HSSFWorkbook workbook= null;
try {
if(file != null){
workbook  = new HSSFWorkbook(new FileInputStream(file));
}else{
workbook = new HSSFWorkbook();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return workbook;
}
//判断数据
/**
* 通过 结合注解,来生成我们的poi文档操作
* 通过注解调用的时候,我们的类中必须要有注解写出,如果没有就创建不了
* 需要结合@HFFSAlias 这个类
* @param data 我们的数据集合
* @return
*/
public static HSSFWorkbook createHSSFByAnnotation(List<?> data) {
LinkedHashMap<String, String> title = null;
//获取到我们的title数据
if(data != null && data.size() >0){
title = new LinkedHashMap<String, String>();
/*Field [] fields = data.get(0).getClass().getDeclaredFields();
for(Field field:fields){
HFFSAlias alias = field.getAnnotation(HFFSAlias.class);
if(alias != null){
title.put(field.getName(), alias.value());
}
}*/
title = PoiUtils.getAnnotationAlias(data.get(0).getClass());
//然后调用已经存在的方法来生成表
return PoiUtils.createHSSF(title, data);
}
return null;
}
/**
* 逆向生成,我们需要的数据,将xls中的数据转化为字符串数据
* @param clazz 映射的类对象
* @param file xls文件对象
* @return
*/
public static List<?> reverseHSSFByAnnotation(File file,Class<?> clazz ){
//获取field中的信息
return PoiUtils.reverseHSSF(file, PoiUtils.getAnnotationAlias(clazz), clazz);
}

/**
* 通过clazz 来获取到里面的title数据
* @param clazz
* @return
*/
public static LinkedHashMap<String,String> getAnnotationAlias(Class<?> clazz){
Field []  fields = clazz.getDeclaredFields();
LinkedHashMap<String,String> title = new LinkedHashMap<String, String>();
for(Field field:fields){
HFFSAlias alias = field.getAnnotation(HFFSAlias.class);
if(alias != null){
title.put(field.getName(), alias.value());
}
}
return title;
}
/**
* 没有行标题的数据,需要通过这种方法来确定数据
* 这个方法使用起来 比通过Map 更加的简便
* @param file  逆向工程的xls文件爱你
* @param title  这个 需要设定 映射关系,而且顺序不能错
* @param clazz   映射的类对象
* @param hasHead  是否包含表头,有表头的数据就 需要从1 开始
* @return
*/
public static List<?> reverseHSSF(File file,String[] title,Class<?> clazz ,boolean hasHead){
try {
//创建work
HSSFWorkbook work = PoiUtils.getHSSFWorkbook(file);
//获取第一页
HSSFSheet sheet = work.getSheetAt(0);
//row是从0开始的,获取列和行的数目
HSSFRow row =  sheet.getRow(0);
List list =null;
List<String> dateFields = null;
int rownum = sheet.getLastRowNum();
int cellnum = row.getLastCellNum();
//          System.out.println(rownum);
String [] ids = title;

if(rownum >0 && cellnum >0){
list = new ArrayList();
dateFields = PoiUtils.getDataTimeFiled(clazz);
//遍历别的数据,、第一行数据 是需要的
int start = 0;
if(hasHead){
start = 1;
}
for(int i=start;i<rownum;i++){
row = sheet.getRow(i);
//实例化对象
Object obj = clazz.newInstance();
//设定属性
for(int j=0;j<cellnum;j++){
HSSFCell cell = row.getCell((short)j);
String result ="";
//当时数字类型的数据,不可以转化
if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){
result = cell.getNumericCellValue()+"";
}else{
result =cell.getStringCellValue();
}

//日期类型的数据不好注入,需要判断,和设定数据的类型
if(dateFields.contains(ids[j])){
BeanUtils.setProperty(obj, ids[j], new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(result));
}else{
BeanUtils.setProperty(obj, ids[j], result);
}
}
list.add(obj);
}
}
return list;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 通过配置的方法来生成逆向工程
* 给定文件,将对象转化为类
* 设定中,sheeet 就一页
* @param file  xls文件名称
* @param title 标题,表头关系
* @param clazz 类对象
* @return
*/
public static List<?> reverseHSSF(File file,Map<String,String> title,Class<?> clazz ){
try {
//创建work
HSSFWorkbook work = PoiUtils.getHSSFWorkbook(file);
//获取第一页
HSSFSheet sheet = work.getSheetAt(0);
//row是从0开始的,获取列和行的数目
HSSFRow row =  sheet.getRow(0);
List list =null;
List<String> dateFields = null;
int rownum = sheet.getLastRowNum();
int cellnum = row.getLastCellNum();
//          System.out.println(rownum);
String [] ids = new String[cellnum];

if(rownum >0 && cellnum >0){
title = PoiUtils.reverseMap(title);
list = new ArrayList();
dateFields = PoiUtils.getDataTimeFiled(clazz);
//这个可以 提取为 获取第一行数据的信息
//获取地一行,标题行  获取标题对应信息
for(int j=0;j<cellnum;j++){
HSSFCell cell = row.getCell((short)j);
String result = "";
//当时数字类型的数据,不可以转化
if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){
//                  ids[j]= title.containsValue(value);
result = cell.getNumericCellValue()+"";
}else{
result = cell.getStringCellValue();
}
//获取的数据进行注入操作

//需要反转map集合
if(title != null && result != null){
if(!title.containsKey(result)){
throw new RuntimeException("内容不匹配");
}
ids[j] = title.get(result);
}
}

//遍历别的数据
for(int i=1;i<rownum;i++){
row = sheet.getRow(i);
//实例化对象
Object obj = clazz.newInstance();
//设定属性
for(int j=0;j<cellnum;j++){
HSSFCell cell = row.getCell((short)j);
String result ="";
//当时数字类型的数据,不可以转化
if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){
result = cell.getNumericCellValue()+"";
}else{
result =cell.getStringCellValue();
}

//日期类型的数据不好注入,需要判断,和设定数据的类型
if(dateFields.contains(ids[j])){
BeanUtils.setProperty(obj, ids[j], new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(result));
}else{
BeanUtils.setProperty(obj, ids[j], result);
}
}
list.add(obj);
}
}
return list;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}

/**
* 通过给定类,来获取到日期类型的字段
* 解决日期类型 注入不进去的问题
* @param clazz
* @return
*/
public static List<String> getDataTimeFiled(Class<?> clazz){
Field [] fields = clazz.getDeclaredFields();
List<String> fieldNames = new ArrayList<String>();
for(Field  field :fields){
if(field.getType().getSimpleName().equals("Date")){
fieldNames.add(field.getName());
}
}
return  fieldNames;
}
/**
* 创建我们的HSSFwork 通过配置别名属性
* @param tiles 文档的title         Map<属性名称, 别名>
* @param date List<?> 是一个集合的数据
* @return
*/
public static HSSFWorkbook createHSSF(LinkedHashMap<String, String> title,List<?> data) {
try {
//一个汉字的宽度是 500
//英文260
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();

//获取字符的长度
//      sheet.setColumnGroupCollapsed(columnNumber, collapsed);
//          sheet.setColumnWidth(column, width);
//写title中的数据
if(title!= null && title.size() >0){
//创建样式
HSSFCellStyle style = PoiUtils.getHeadStyle(workbook);
int index = 0;
HSSFRow row = sheet.createRow(0);
//获取宽度
Map<String, Short> columns = PoiUtils.getColumnWidth(title, data);
//获取数据
for(Map.Entry<String, String> entry:title.entrySet()){
String val = entry.getValue();
HSSFCell cell = row.createCell((short)index);
//设置单元格的类型
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
//设置编码格式
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
//设定样式
cell.setCellStyle(style);
cell.setCellValue(val);
sheet.setColumnWidth((short)index, columns.get(entry.getKey()));
index++;
}

//创建样式
style = PoiUtils.getBodyStyle(workbook);
//设定数据
if(data != null && data.size() >0){
//获取我们的 data中的数据
//内容从第二行开始
int rownum =1;
for(Object obj:data){
row = sheet.createRow(rownum);
//列从第一行开始
int colnum = 0;
for(Map.Entry<String, String> entry:title.entrySet()){
String key = entry.getKey();
//获取装的对象数据
String val = BeanUtils.getProperty(obj, key);
if(val == null || "".equals(val.trim())){
val = "-";
}
HSSFCell cell  = row.createCell((short)colnum);
//设置单元格的类型
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
//设置编码格式
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
//设定样式
cell.setCellStyle(style);
cell.setCellValue(val);
//增加列
colnum ++;
}
rownum ++;
}
}
}

return workbook;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}

/**
* 计算每个数据列的最适合的宽度
* 通过数据来获取到我们的数据的宽度
* @param title 数据标题
* @param data 数据几何
* @return
*/
public static Map<String, Short> getColumnWidth(Map<String, String> title,List<?> data){
try {
//存储我们的每个属性的宽度
//Map<属性明,宽度>
Map<String,Short> map = new HashMap<String, Short>();

if(title!= null && title.size() >0){
//计算我们的第一行的宽度
for(Map.Entry<String, String> entry:title.entrySet()){
int max = PoiUtils.countColumnLength(entry.getValue());

//计算下面的所有 同一字段的长度
for(Object obj:data){
String val = BeanUtils.getProperty(obj, entry.getKey());
int temp = PoiUtils.countColumnLength(val);
max = max >temp?max:temp;
}
//设定字段的值
map.put(entry.getKey(), (short)max);
}
}
return map;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}

/**
*
* 根据字符的数量,来计算列的宽度
* 不同的数据他的数据长度是不一样的,所以我们需要计算总体的长度
* @param str 字符串
* @return int 计算出列宽
*
*/
private static int countColumnLength(String str){
int len =0;
if(str != null && !"".equals(str.trim())){
//一个汉字的宽度是 500
//英文260
char [] chars = str.toCharArray();
//z
int chines = 0;
int other =0;
for(char val:chars){
if(PoiUtils.isChineseChar(val)){
chines ++;
}else{
other++;
}
}
//获取到了数据然后计算长度
len = chines*500+other*260+1000;
}
return len;
}
/**
* 创建样式
* @return
*/
public static HSSFCellStyle getHeadStyle(HSSFWorkbook workbook){
HSSFCellStyle  style = workbook.createCellStyle();
//居中显示
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setFont(PoiUtils.getSongRed10(workbook));
return style;
}

/**
* 创建样式
* @return
*/
public static HSSFCellStyle getBodyStyle(HSSFWorkbook workbook){
HSSFCellStyle  style = workbook.createCellStyle();
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setFont(PoiUtils.getSong10(workbook));
return style;
}

/**
* 创建字体
* @param fontFamly 字体
* @param size 大小 字号 14  13
* @param color  HSSFColor.BLACK.index 这个HSSFColor中 的数据
* @param isBold 是否加粗 操作
* @return
*/
//字号 是14号 -->对应的short 280宽度 是 14好字体
public static HSSFFont creatFont(HSSFWorkbook workbook,String fontFamly,Integer size,Short color,boolean isBold){
HSSFFont font = workbook.createFont();
//字号 是14号 -->对应的short 宽度 是 14好字体
font.setFontHeight((short)((size== null  || size ==0 )?280:size*20));
font.setBoldweight(isBold?HSSFFont.BOLDWEIGHT_BOLD:HSSFFont.BOLDWEIGHT_NORMAL);
//将字体颜色变红色
//      short colors = HSSFColor.RED.index;
font.setColor(color== null?HSSFColor.BLACK.index:color);
return font;
}

/**
* 判断我们的数据 中,是否有中文字符
* @param str
* @return
*/
public static boolean isChineseChar(char str){
boolean flag = false;
Pattern pattern = Pattern.compile("[\u4e00-\u9fa5]");
if(pattern.matcher(str+"").find()){
flag = true;
}
return flag;
}

/**
* 得到宋体10 号 黑色
* 默认字体的大小就是10 号
* @return
*/
public static HSSFFont getSong10(HSSFWorkbook workbook){
return creatFont(workbook,"宋体", 10, null,false);
}

/**
* 得到宋体10 号  红色加粗
* @return
*/
public static HSSFFont getSongRed10(HSSFWorkbook workbook){
return creatFont(workbook,"宋体", 10, HSSFColor.RED.index,true);
}

/**
* 将Map
* @param map
* @return
*/
public static Map<String,String> reverseMap(Map<String,String> map){
Map<String,String> newMap = null;
//将便利的结果重新装填一下
if(map != null  && map.size() >0){
newMap = new LinkedHashMap<String, String>();
for(Map.Entry<String, String> entry:map.entrySet()){
newMap.put(entry.getValue(), entry.getKey());
}
}
return newMap;
}
}


测试类

主要测试了,自己手动编写表头和自动配置生成表头两种方法,同时还测试了Excel转List数据

package com.yellowcong.test;

import java.io.File;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.yellowcong.model.Pager;
import com.yellowcong.model.SystemContext;
import com.yellowcong.model.User;
import com.yellowcong.service.UserService;
import com.yellowcong.utils.HFFSAlias;
import com.yellowcong.utils.PoiUtils;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:ApplicationContext.xml")
public class UserServiceTest {

private UserService userService;

@Resource(name="userService")
public void setUserService(UserService userService) {
this.userService = userService;
}

@Test
public void testList(){
List<User> users= userService.listByPager().getData();
//可以做成annotation的
Map<String,String> map = new HashMap<String, String>();
map.put("id", "编号");
map.put("username", "用户名");
map.put("nickname", "别名");
map.put("password", "密码");
map.put("createDate", "创建日期");
map.put("imgUrl", "头像路径");
map.put("email", "邮箱");
map.put("phone", "电话");
map.put("isActive", "是否激活");

HSSFWorkbook work = PoiUtils.createHSSF(map, users);
PoiUtils.copyHSSFToFile(work, new File("D:/users.xls"));
}

@Test
public void testList2Anotation(){
List<User> users= userService.listByPager().getData();
HSSFWorkbook work = PoiUtils.createHSSFByAnnotation(users);
PoiUtils.copyHSSFToFile(work, new File("D:/users.xls"));
}

}


测试结果

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: