统计

公司产品迭代,要重新设计统计这一块,分配到我这来做,现在基本已经完成,遂把其中的一部分,统计表的生成的方式拿出来做为总结,由于是一部分,这里我就剔除了业务方面的代码,只描述生成的过程。

  1 package com.sun.stat.basic;
  2 
  3 import java.io.Serializable;
  4 import java.util.ArrayList;
  5 import java.util.List;
  6 
  7 /**
  8  * 表头单元格对象
  9  * 
 10  */
 11 public class HeadCell implements Serializable, Cloneable {
 12 
 13     private static final long serialVersionUID = -1756261752878954234L;
 14 
 15     private String id;
 16     private String name;
 17     private HeadCell parent;// 父单元格对象
 18     private List<HeadCell> children = new ArrayList<HeadCell>();// 子单元格集合
 19     private Integer deep = 0;// 层深度
 20 
 21     public HeadCell(String id, String name, HeadCell parent, Integer deep) {
 22         super();
 23         this.id = id;
 24         this.name = name;
 25         this.parent = parent;
 26         this.deep = deep;
 27     }
 28 
 29     public HeadCell getParent() {
 30         return parent;
 31     }
 32 
 33     public void setParent(HeadCell parent) {
 34         this.parent = parent;
 35     }
 36 
 37     public List<HeadCell> getChildren() {
 38         return children;
 39     }
 40 
 41     public void setChildren(List<HeadCell> children) {
 42         this.children = children;
 43     }
 44 
 45     public String getId() {
 46         return id;
 47     }
 48 
 49     public void setId(String id) {
 50         this.id = id;
 51     }
 52 
 53     public Integer getDeep() {
 54         return deep;
 55     }
 56 
 57     public void setDeep(Integer deep) {
 58         this.deep = deep;
 59     }
 60 
 61     /*
 62      * 克隆headCell实体
 63      * 
 64      * @see java.lang.Object#clone()
 65      */
 66     @Override
 67     public HeadCell clone() {
 68         HeadCell o = null;
 69         try {
 70             o = (HeadCell) super.clone();
 71         } catch (CloneNotSupportedException e) {
 72             e.printStackTrace();
 73         }
 74         return o;
 75     }
 76 
 77     public String getName() {
 78         return name;
 79     }
 80 
 81     public void setName(String name) {
 82         this.name = name;
 83     }
 84 
 85     /**
 86      * 如果选择了多个统计项单元格对象的最底层的子对象的个数
 87      * 
 88      * @param countItem 统计项的个数
 89      * @return
 90      */
 91     public int getColNum(int countItem) {
 92         int rowNum = children.size();
 93         for (HeadCell hc : children) {
 94             if (hc.getColNum(countItem) > 0) {
 95                 rowNum += hc.getColNum(countItem) - 1;
 96             }
 97         }
 98         return rowNum == 0 ? countItem : rowNum;
 99     }
100 
101     /**
102      * 取得单元格对象的最底层的子对象的个数
103      * 
104      * @return 深度
105      */
106     public int getRowNum() {
107         int rowNum = children.size();
108         for (HeadCell hc : children) {
109             if (hc.getRowNum() > 0) {
110                 rowNum += hc.getRowNum() - 1;
111             }
112         }
113         return rowNum;
114     }
115 }

在实际的产品应用中,报表这一块一直是一个产品的亮点,通常一个复杂的报表涉及到了字段的统计,数据范围的统计,多区域的统计,这就需要一条极为复杂的sql了
,这是最考验开发人员严谨性的时候,一个不小心大概就会“弄丢”了某些数据,当然我们这里就不讨论数据的生成了(我所用到的数据是通过实体间的关联关系,“字典”等生产的,说简单点就是我的sql是hibernate给写的,当然了,这是有局限性的,所以我们把sql报表单独抽离出来,但是最终的实现都是殊途同归)。

上面的这个对象就是我们定义的单元格对象,一个统计表涉及到了横向表头、纵向表头,每个表头都可以有自己的子查询(子集),这就构建了一套拥有主子关系的对象,通过这些对象进一步生成统计表!

  1 package com.sun.stat.process;
  2 
  3 import java.util.ArrayList;
  4 import java.util.Collections;
  5 import java.util.List;
  6 import java.util.Map;
  7 import java.util.Map.Entry;
  8 import java.util.UUID;
  9 
 10 import org.junit.Test;
 11 
 12 import com.sun.stat.basic.HeadCell;
 13 import com.sun.stat.component.Th;
 14 import com.sun.stat.utils.ListUtils;
 15 
 16 /**
 17  * 通过HeadCell对象集合构建一个复杂的table
 18  */
 19 public class ProductTable {
 20     List<HeadCell> rowHeadCells = new ArrayList<HeadCell>();
 21     List<HeadCell> colHeadCells = new ArrayList<HeadCell>();
 22 
 23     /**
 24      * 准备数据,构造一系列的headCell对象
 25      */
 26     public void prepare() {
 27         // 新建一个横向表头
 28         HeadCell r1 = new HeadCell(UUID.randomUUID().toString(), "r1", null, 0);
 29         HeadCell r2 = new HeadCell(UUID.randomUUID().toString(), "r2", null, 0);
 30         HeadCell r3 = new HeadCell(UUID.randomUUID().toString(), "r3", null, 0);
 31         HeadCell r4 = new HeadCell(UUID.randomUUID().toString(), "r4", null, 0);
 32         HeadCell r5 = new HeadCell(UUID.randomUUID().toString(), "r5", null, 0);
 33         HeadCell r1_1 = new HeadCell(UUID.randomUUID().toString(), "r1_1", r1, 1);
 34         HeadCell r1_2 = new HeadCell(UUID.randomUUID().toString(), "r1_2", r1, 1);
 35         HeadCell r2_1 = new HeadCell(UUID.randomUUID().toString(), "r2_1", r2, 1);
 36         HeadCell r2_2 = new HeadCell(UUID.randomUUID().toString(), "r2_2", r2, 1);
 37         HeadCell r3_1 = new HeadCell(UUID.randomUUID().toString(), "r3_1", r3, 1);
 38         HeadCell r3_2 = new HeadCell(UUID.randomUUID().toString(), "r3_2", r3, 1);
 39         HeadCell r4_1 = new HeadCell(UUID.randomUUID().toString(), "r4_1", r4, 1);
 40         HeadCell r4_2 = new HeadCell(UUID.randomUUID().toString(), "r4_2", r4, 1);
 41         HeadCell r5_1 = new HeadCell(UUID.randomUUID().toString(), "r5_1", r5, 1);
 42         HeadCell r5_2 = new HeadCell(UUID.randomUUID().toString(), "r5_2", r5, 1);
 43         r1.getChildren().add(r1_1);
 44         r1.getChildren().add(r1_2);
 45         r2.getChildren().add(r2_1);
 46         r2.getChildren().add(r2_2);
 47         r3.getChildren().add(r3_1);
 48         r3.getChildren().add(r3_2);
 49         r4.getChildren().add(r4_1);
 50         r4.getChildren().add(r4_2);
 51         r5.getChildren().add(r5_1);
 52         r5.getChildren().add(r5_2);
 53 
 54         rowHeadCells.add(r1);
 55         rowHeadCells.add(r2);
 56         rowHeadCells.add(r3);
 57         rowHeadCells.add(r4);
 58         rowHeadCells.add(r5);
 59 
 60         // 新建一个纵向表头
 61         HeadCell c1 = new HeadCell(UUID.randomUUID().toString(), "c1", null, 0);
 62         HeadCell c2 = new HeadCell(UUID.randomUUID().toString(), "c2", null, 0);
 63         HeadCell c3 = new HeadCell(UUID.randomUUID().toString(), "c3", null, 0);
 64         HeadCell c4 = new HeadCell(UUID.randomUUID().toString(), "c4", null, 0);
 65         HeadCell c5 = new HeadCell(UUID.randomUUID().toString(), "c5", null, 0);
 66         HeadCell c1_1 = new HeadCell(UUID.randomUUID().toString(), "c1_1", c1, 1);
 67         HeadCell c1_2 = new HeadCell(UUID.randomUUID().toString(), "c1_2", c1, 1);
 68         HeadCell c2_1 = new HeadCell(UUID.randomUUID().toString(), "c2_1", c2, 1);
 69         HeadCell c2_2 = new HeadCell(UUID.randomUUID().toString(), "c2_2", c2, 1);
 70         HeadCell c3_1 = new HeadCell(UUID.randomUUID().toString(), "c3_1", c3, 1);
 71         HeadCell c3_2 = new HeadCell(UUID.randomUUID().toString(), "c3_2", c3, 1);
 72         HeadCell c4_1 = new HeadCell(UUID.randomUUID().toString(), "c4_1", c4, 1);
 73         HeadCell c4_2 = new HeadCell(UUID.randomUUID().toString(), "c4_2", c4, 1);
 74         HeadCell c5_1 = new HeadCell(UUID.randomUUID().toString(), "c5_1", c5, 1);
 75         HeadCell c5_2 = new HeadCell(UUID.randomUUID().toString(), "c5_2", c5, 1);
 76         c1.getChildren().add(c1_1);
 77         c1.getChildren().add(c1_2);
 78         c2.getChildren().add(c2_1);
 79         c2.getChildren().add(c2_2);
 80         c3.getChildren().add(c3_1);
 81         c3.getChildren().add(c3_2);
 82         c4.getChildren().add(c4_1);
 83         c4.getChildren().add(c4_2);
 84         c5.getChildren().add(c5_1);
 85         c5.getChildren().add(c5_2);
 86 
 87         colHeadCells.add(c1);
 88         colHeadCells.add(c2);
 89         colHeadCells.add(c3);
 90         colHeadCells.add(c4);
 91         colHeadCells.add(c5);
 92     }
 93 
 94     /**
 95      * 生产一个table
 96      */
 97     @Test
 98     public void getTable() {
 99         prepare();// 准备数据
100         StringBuilder statHtml = new StringBuilder();
101         // 创建thead标题行
102         statHtml.append("<table border = '1' id='expTable' name='statTable'>");
103         statHtml.append("<thead>");
104         List<Th> ths = new ArrayList<Th>();
105         // 这里获得的是纵向表头的最大深度,这是因为我们在生成表格的时候
106         // 1.如果是二维表,那table的表头并不是我们的单元格对象,我们可以通过这个集合生成表头的rowspan
107         // 2.我们获得每个th的rowspan,从级的方面考虑,除了最低一级的th,上层的th他的rowspan只能是1,
108         // 那么最后一级是怎么判断的呢,我们是用最后一级的深度(deep),拿最大的一级减去他的深度加1不就是他所占的rowspan吗
109         int maxDeep = getMaxDeep(colHeadCells);
110         getColThs(colHeadCells, ths, maxDeep);
111         // 这个ths是用来做什么的呢
112         // 在这里我们为th集合分了组,他的行号(rowNum)就是我们的tr,一行可以生产一个tr
113         statHtml.append(productColTh(ths, rowHeadCells, colHeadCells));
114         statHtml.append("</thead>");
115         statHtml.append("<tbody>");
116         String row = buildContext(rowHeadCells, colHeadCells);
117         row = row.replace("</th><tr>", "</th>");
118         statHtml.append(row);
119         statHtml.append("</tbody>");
120         statHtml.append("</table>");
121         System.out.println(statHtml.toString());
122     }
123 
124     /**
125      * 这里我们就要生产tbody的部分,先说思路
126      * 1.纵向也是有表头的,纵向表头也是有主子关系,但是他和横向表头不同,一个对象的一层叶子节点是在一个tr中,这生成tr就显得容易很多
127      * 2.整个生成过程和纵向表头是相反的,但是生成方式不同
128      * 
129      * @param rowHCs
130      * @param colHCs
131      * @return
132      */
133     public String buildContext(List<HeadCell> rowHCs, List<HeadCell> colHCs) {
134         List<HeadCell> colHeadCells = getColTh(colHCs);
135         StringBuffer sb = new StringBuffer();
136         int maxDeep = getMaxDeep(rowHCs);
137         for (HeadCell childCell : rowHCs) {
138             sb.append(bulidCol(childCell, colHeadCells, maxDeep));
139             sb.append("</tr>");
140         }
141         return sb.toString();
142     }
143 
144     /**
145      * 生成横向表头
146      * @param headCell
147      * @param colHCs
148      * @param maxDeep
149      * @return
150      */
151     public String bulidCol(HeadCell headCell, List<HeadCell> colHCs, int maxDeep) {
152         StringBuffer sb = new StringBuffer();
153         sb.append("<tr>");
154         if (headCell.getChildren() != null && headCell.getChildren().size() > 0) {
155             Integer rowNum = headCell.getRowNum() == 0 ? 1 : headCell.getRowNum();
156             sb.append("<th id='" + headCell.getId() + "' rowspan='" + rowNum + "' deep='" + (headCell.getDeep() + 1) + "'>" + headCell.getName() + "</th>");
157             for (HeadCell childCell : headCell.getChildren()) {
158                 sb.append(bulidCol(childCell, colHCs, maxDeep));
159             }
160         } else {
161             int rowNum = headCell.getRowNum() == 0 ? 1 : headCell.getRowNum();
162             int colNum = maxDeep - headCell.getDeep() + 1;
163             int deep = headCell.getDeep() + 1;
164             sb.append("<th name='boottom' id='" + headCell.getId() + "' rowspan='" + rowNum + "' colspan='" + colNum + "' deep='" + deep + "'>" + headCell.getName() + "</th>");
165             for (HeadCell colCell : colHCs) {
166                 sb.append("<td>0.0</td>");
167             }
168         }
169         return sb.toString();
170     }
171 
172     /**
173      * 获得最底层的headCell的集合
174      * @param colHCs
175      * @return
176      */
177     public List<HeadCell> getColTh(List<HeadCell> colHCs) {
178         List<HeadCell> bottomRowHead = new ArrayList<HeadCell>();
179         for (HeadCell rowCell : colHCs) {
180             bottomRowHead.addAll(builds(rowCell));
181         }
182         return bottomRowHead;
183     }
184 
185     public static List<HeadCell> builds(HeadCell headCell) {
186         List<HeadCell> bottom = new ArrayList<HeadCell>();
187         if (headCell.getChildren() != null && headCell.getChildren().size() > 0) {
188             for (HeadCell cheadCell : headCell.getChildren()) {
189                 bottom.addAll(builds(cheadCell));
190             }
191         } else {
192             bottom.add(headCell);
193         }
194         return bottom;
195     }
196 
197     public String productColTh(List<Th> ths, List<HeadCell> rowHCs, List<HeadCell> colHCs) {
198         StringBuffer sb = new StringBuffer();
199         boolean headtitle = true;
200         int rowspan = getMaxDeep(colHCs) + 1;
201         int colspan = getMaxDeep(rowHCs) + 1;
202         try {
203             Map<Integer, List<Th>> maps = ListUtils.groupByProperty(ths, "rowNum");// 按照对象中某个字段分组
204             for (Entry<Integer, List<Th>> entry : maps.entrySet()) {
205                 sb.append("<tr>");
206                 if (headtitle) {
207                     sb.append("<th rowspan='" + rowspan + "' colspan='" + colspan + "'></th>");
208                     headtitle = false;
209                 }
210                 for (Th th : entry.getValue()) {// 生产纵向表头
211                     sb.append(th.getThStr());
212                 }
213                 sb.append("</tr>");
214             }
215         } catch (Exception e) {
216 
217         }
218         return sb.toString();
219     }
220 
221     public void getColThs(List<HeadCell> colHCs, List<Th> ths, int maxDeep) {
222         for (HeadCell hc : colHCs) {
223             if (hc.getChildren() != null && hc.getChildren().size() > 0) {
224                 Th th = new Th();
225                 th.setRowNum(hc.getDeep());
226                 th.setThId(hc.getId());
227                 th.setColspan(hc.getRowNum());
228                 th.setRowspan(1);
229                 if (hc.getParent() != null) {
230                     th.setpId(hc.getParent().getId());
231                 }
232                 th.setValue(hc.getName());
233                 ths.add(th);
234                 getColThs(hc.getChildren(), ths, maxDeep);
235             } else {
236                 Th th = new Th();
237                 th.setRowNum(hc.getDeep());
238                 th.setThId(hc.getId());
239                 th.setColspan(hc.getRowNum());
240                 th.setRowspan(maxDeep - hc.getDeep() + 1);
241                 if (hc.getParent() != null) {
242                     th.setpId(hc.getParent().getId());
243                 }
244                 th.setValue(hc.getName());
245                 ths.add(th);
246             }
247         }
248     }
249 
250     /**
251      * 求表格对象集合的最大深度
252      * 
253      * @param headCells
254      *            单元格集合对象
255      * @return 最大深度
256      */
257     public static Integer getMaxDeep(List<HeadCell> headCells) {
258         if (headCells != null && headCells.size() > 0) {
259             List<Integer> deep = new ArrayList<Integer>();
260             for (HeadCell headCell : headCells) {
261                 deep.add(maxDeep(headCell));
262             }
263             return Collections.max(deep);
264         } else {
265             return 1;
266         }
267     }
268 
269     private static int maxDeep(HeadCell headCell) {
270         int i = 0;
271         if (headCell.getChildren() != null && headCell.getChildren().size() > 0) {
272             for (HeadCell childCell : headCell.getChildren()) {
273                 int j = maxDeep(childCell);
274                 i = j > i ? j : i;
275             }
276         } else {
277             i = headCell.getDeep();
278         }
279         return i;
280     }
281 }

这是生成纵向表头的类

 1 package com.sun.stat.component;
 2 
 3 import org.apache.commons.lang3.StringUtils;
 4 
 5 /**
 6  * 用于生成横向表头的html
 7  * 这里这样设计的原因是我们的表格是按照横向生成也就是一个tr一个tr的生成,然而
 8  * 横向表头只有上下级有主子关系,所以我这里用rowNum来区分它们,最终达到我生成tr的效果
 9  */
10 public class Th {
11     private int colspan = 1;// 合并列
12     private int rowspan = 1;// 合并行
13     private String thStr;// html代码
14     private String value;// th标签文本值
15     private int rowNum;// 行号
16     private String thId;//这里的id和pid主要用于一个js的功能,即当一行或者一列的td全为空时隐藏掉当前行
17     private String pId;
18 
19     public int getRowNum() {
20         return rowNum;
21     }
22 
23     public void setRowNum(int rowNum) {
24         this.rowNum = rowNum;
25     }
26 
27     public int getColspan() {
28         return colspan;
29     }
30 
31     public void setColspan(int colspan) {
32         this.colspan = colspan;
33     }
34 
35     public int getRowspan() {
36         return rowspan;
37     }
38 
39     public void setRowspan(int rowspan) {
40         this.rowspan = rowspan;
41     }
42 
43     public String getThStr() {
44         StringBuilder thHtml = new StringBuilder();
45         if (colspan == 0) {
46             colspan = 1;
47         }
48         if (rowspan == 0) {
49             rowspan = 1;
50         }
51         if (StringUtils.isBlank(pId)) {
52             pId = "";
53         }
54         thHtml.append("<th id='").append(thId).append("'").append(" pId='").append(pId).append("' deep='").append(rowNum + 1).append("' colspan='").append(colspan).append("' rowspan='").append(rowspan);
55         thHtml.append("'>").append(value).append("</th>");
56         return thHtml.toString();
57     }
58 
59     public void setThStr(String thStr) {
60         this.thStr = thStr;
61     }
62 
63     public String getValue() {
64         return value;
65     }
66 
67     public void setValue(String value) {
68         this.value = value;
69     }
70 
71     public String getThId() {
72         return thId;
73     }
74 
75     public void setThId(String thId) {
76         this.thId = thId;
77     }
78 
79     public String getpId() {
80         return pId;
81     }
82 
83     public void setpId(String pId) {
84         this.pId = pId;
85     }
86 }

生成的html代码就是我们需要的统计表table了,我们的表格生成也就完成了。

当然这里涉及了合计,多项拆分,合并拆分的问题,这些都可以通过业务逻辑判断,并不影响表格的生成.

这里涉及了我博客中的一个工具类方法,还有我为什么要生成id和pid呢,也是我后面需要的一个功能,即隐藏0值的功能,可以在我的博客中找到,这里不再赘述。

原文地址:https://www.cnblogs.com/sun-space/p/5592592.html