Another way to generate reports (excel tables) in java

As we all know, jfree, a tool for generating reports in java, can generate various charts and graphics, but today we are talking about generating a report in tabular form.
In fact, this is also simple. Excel documents are the first choice for tables. As long as the template is determined, data filling is not a problem, and excel operation is not a problem. You can do whatever you want!
But the product said that if you want to be able to visually view and directly forward the form report, after the report is generated, you need to notify me with a WeChat template message. Do you want me to download an excel document in WeChat and open it before forwarding it? There is no user experience! Are there any anti-human operations!
. . . . . .
Then I will make the report into a picture, and generate this batch of pictures on an html page. When you click on the template message, you will see this page. The report is lined up. ,how is everything?
Let me have a look at it first. The report looks like this:
insert image description hereSpecial Note: Subordinate 1 and Subordinate 2 have more than 2 subordinates. Some projects have 5 or more subordinates, and some projects have no subordinates. ! There is also a line of small characters below the total, which gives a basic explanation of the headers in the report. The headers of the report may increase or decrease every few months, and the general template is basically fixed like this. . .
This demand, I started to think about it: a colleague had done a report similar to this before, but he left the job after finishing this, and they are not very familiar with each other. Looking at the original code, wow, more than 1200 lines There are more than 600 lines of code, including the method of generating report pictures. After reading the code, it is clear and clear, no wonder I left the job!
The original method is to calculate the distance item by item, determine the xy coordinates of the text, and draw them sequentially with Graphics2D. The coordinate points between a position and a position have only logical associations. For example, now add a table before the table header 6 Header, let the original table with only 11 columns become 12 columns, then all the text and frame coordinates of the original code starting from header 6 must be recalculated with a calculator, which is obviously very scary!
According to this idea, I started to use the object-oriented method to consider. A report is composed of several tables. A table has four sides: background color, font color, font size, and font position coordinates in the middle. So there are the following entity attributes (don't worry about access modifiers):

    public String text="",text1=text,text2="";//表格内容较宽时要换成两行显示
    public Font font = new Font("宋体", Font.BOLD, 58);
    public Color fontColor=Color.BLACK;
    public Color backgroundColor=Color.WHITE;
    public Color borderColor=Color.black;
    public int fontX,fontY,fontX2,fontY2;//表格内容换行时两行文字的各自XY轴坐标
    public int borderX=0,borderY=0,borderWidth,borderHeight;
    public int gridX,gridY,gridWidth,gridHeight;
    public int orginWidth,orginHeight;//当前表格原点距离坐标原点的宽度和高度

It looks great, so I started to make table titles:

/**
     * 得到表格标题grid对象
     * @param tableName
     * @param gridWith
     * @param gridHeight
     * @return
     */
    public static XXXXTableGridBean getInstanceForTableName(String tableName,int gridWith,int gridHeight){
        XXXXTableGridBean bean = new XXXXTableGridBean();
        bean.text = tableName;
        bean.font = new Font("宋体", Font.BOLD, 70);
        bean.fontColor = Color.WHITE;
        FontMetrics fm = new JLabel().getFontMetrics(bean.font);
        bean.fontX = (gridWith - fm.stringWidth(tableName))/2;//文字居中
        bean.fontY = 70;
        bean.backgroundColor = new Color(238, 154, 0);
        bean.gridX=bean.gridY=0;
        bean.gridWidth=gridWith;
        bean.gridHeight=gridHeight;
        bean.borderWidth=bean.gridWidth;
        bean.borderHeight=bean.gridHeight;
        bean.orginWidth = bean.gridWidth;
        bean.orginHeight = bean.gridHeight;
        return bean;
    }

After finishing the title, make the header again:

/**
     * 得到表头grid对象
     * @param title
     * @param tableNameGridBean
     * @param preTitleGridBean
     * @param gridX
     * @param gridY
     * @param gridWidth
     * @param gridHeight
     * @return
     */
    public static XXXXTableGridBean getInstanceForTableTitle(String title,
            XXXXTableGridBean tableNameGridBean,XXXXTableGridBean preTitleGridBean,
            int gridX, int gridY, int gridWidth, int gridHeight) {
        XXXXTableGridBean bean = new XXXXTableGridBean();
        bean.text = title;
        bean.backgroundColor = new Color(159, 121, 238);
        bean.gridX=gridX;
        bean.gridY=gridY;
        bean.gridWidth=gridWidth;
        bean.gridHeight=gridHeight;
        bean.borderX=gridX;
        bean.borderY=gridY;
        bean.borderWidth=bean.gridWidth;
        bean.borderHeight=bean.gridHeight;
        bean.orginWidth = preTitleGridBean.orginWidth+preTitleGridBean.gridWidth;
        bean.orginHeight = tableNameGridBean.gridHeight;
        bean.fontColor = Color.WHITE;
        FontMetrics fm = new JLabel().getFontMetrics(bean.font);
        int fontWidth = fm.stringWidth(title);
        if(fontWidth>gridWidth){//文字宽度超过表格宽度,则换行文字
            bean.text1 = bean.text.substring(0,4);
            bean.text2 = bean.text.replace(bean.text1,"").trim();
            fontWidth = fm.stringWidth(bean.text1);
            bean.fontX = bean.orginWidth+((bean.gridWidth - fontWidth)/2);//文字居中
            bean.fontY = tableNameGridBean.gridHeight+80;
            bean.fontX2 = bean.fontX;
            bean.fontY2 =  bean.fontY + 60;
        }else{
            bean.fontX = bean.orginWidth+((bean.gridWidth - fontWidth)/2);//文字居中
            bean.fontY = tableNameGridBean.gridHeight+80;
        }
        return bean;
    }

It’s okay, and the table header is also generated smoothly, so the content of the table is made. Note that the first cross-row table appears here, and the specific number of rows is determined dynamically by the number of data items in the parameter:

public static XXXXTableGridBean getInstanceForTableContentGrid_FirstRowSpan(String val,List<XXXXDTO> allCityInfos,XXXXDTO nowCityInfo){
        XXXXTableGridBean bean = new XXXXTableGridBean();
        bean.text = val;
        int index = allCityInfos.indexOf(nowCityInfo);
        int projectSize=0;
        for(int i=0;i<index;i++){
            projectSize += allCityInfos.get(i).getProjectInfoDTOList().size();
        }
        int topHeight = nameGridHeight+titleGridHeight+projectSize*contentGridHeight;
        bean.borderX=0;
        bean.borderY=topHeight;
        bean.borderWidth=GridBeanConfigs[0].getWidth();
        bean.borderHeight=nowCityInfo.getProjectInfoDTOList().size()*contentGridHeight;
        bean.gridX=bean.borderX;
        bean.gridY=bean.borderY;
        bean.gridWidth=bean.borderWidth;
        bean.gridHeight=bean.borderHeight;
        bean.orginWidth=bean.gridWidth;
        bean.orginHeight=topHeight;
        FontMetrics fm = new JLabel().getFontMetrics(bean.font);
        int fontWidth = fm.stringWidth(bean.text);
        bean.fontX = (bean.orginWidth-fontWidth)/2;
        bean.fontY = bean.orginHeight+(bean.borderHeight-bean.font.getSize())/2+bean.font.getSize();
        return bean;
    }

Then the second cross-row grid appears, and the number of cross-rows here is also dynamically determined by the number of data items in the parameter:

public static XXXXTableGridBean getInstanceForTableContentGrid_GridContent(String val,XXXXTableGridBean preGridBean,
            int gridWidth,int gridHeight,int currentLineNumber,int allSubLineNumber,int nowSubLineNumber){
        XXXXTableGridBean bean = new XXXXTableGridBean();
        bean.text = val;
        bean.font =  new Font("宋体", Font.BOLD, 58/allSubLineNumber*nowSubLineNumber);
        bean.gridWidth=gridWidth;
        bean.gridHeight=gridHeight/allSubLineNumber*nowSubLineNumber;
        bean.borderWidth=bean.gridWidth;
        bean.borderHeight=bean.gridHeight/allSubLineNumber*nowSubLineNumber;
        if(preGridBean==null){
            bean.orginWidth=GridBeanConfigs[0].getWidth();
            bean.orginHeight=nameGridHeight+titleGridHeight+((currentLineNumber-1)*contentGridHeight)/allSubLineNumber*nowSubLineNumber;
        }else{
            bean.orginWidth = preGridBean.orginWidth+preGridBean.gridWidth;
            bean.orginHeight = preGridBean.orginHeight;
        }
        bean.gridX=bean.orginWidth;
        bean.gridY=bean.orginHeight;
        bean.borderX=bean.gridX;
        bean.borderY=bean.gridY;
        FontMetrics fm = new JLabel().getFontMetrics(bean.font);
        bean.fontX = bean.orginWidth+((bean.gridWidth - fm.stringWidth(val))/2);//文字居中
        bean.fontY = bean.orginHeight+bean.font.getSize()+30/allSubLineNumber*nowSubLineNumber;
        return bean;
    }

After doing this, I was short of breath and dizzy, so I quickly listened to my heartbeat with a stethoscope!
insert image description here
I feel that this is exhausting and unreliable. In the later stage of this drawing, the brains are completely insufficient, and maintenance will also be a big problem in the future. The scalability may be a little higher than before, but the advantages are not obvious. It's a big head to go up, so can't there be other solutions?
So I meditated on this question in my sleep, mainly thinking: Since the report should be easy to expand and easy to change, the easiest way is to make it into Excel. Can Excel dynamically set cross-rows? Yes, can excel automatically take a screenshot after the server is opened? I don’t know, but excel can be saved as a picture. You need to use poi or jxl to operate excel, and you need to use the aspose-cells package to save pictures. This article introduces: https://www.cnblogs.com/abc8023/p/ 9336513.html
still finds it troublesome. The table is the most familiar thing than the html table tag. Is it possible to save the completed html as a picture? Hey, there is really:
the implementation based on Java's built-in browser DJNativeSwing ( https://blog.csdn.net/ltllml44/article/details/72910295)
shi based on qt's qtjambi package ( https://blog.csdn.net/ redlevin/article/details/80145963)
based on the implementation of the Html2Image package ( https://www.cnblogs.com/zxf330301/p/5666592.html)
seems to suddenly open the door to a new world!
After a comprehensive comparison, I decided to try it with Html2Image:
First construct the data, and generate an html template page based on the data (jsp is used here):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <style type="text/css">
     html,body{margin:0px;padding:0px;}
     table{width:3680px;font-size:58px;border-top:1px solid black;border-left:1px solid black;text-align: center;}
     table td, table th{height:120px;border-right:1px solid black; border-bottom:1px solid black;line-height:58px;text-align: center;}
     table td{font-size: 58px;text-align: center;}
    </style>
</head>
<body>
<table cellpadding="0" cellspacing="0">
    <tr>
        <th colspan="14" style="height:100px;line-height: 100px;background-color:#EF9A01;color:white;font-size:70px;">XXX报表(${dto.provinceName})${dto.dayText}</th>
    </tr>
    <tr style="height: 170px;background-color: #9F7AEE;color:white;">
        <th width="220">表头</th>
        <th width="520">表头</th>
        <th width="240">表头</th>
        <th width="240">表头</th>
        <th width="240">表头</th>
        <th width="240">表头</th>
        <th width="240">表头</th>
        <th width="300">表头</th>
        <th width="240">表头</th>
        <th width="240">表头</th>
        <th width="240">表头</th>
        <th width="240">表头</th>
        <th width="240">表头</th>
        <th width="240">表头</th>
    </tr>
    <c:forEach items="${dto.dxxxDTOs}" var="d" varStatus="dStatus">
        <c:set var="preCityGridIndex" value="-1" />
        <c:set var="preProjectGridIndex" value="-1" />
        <c:forEach items="${d.projectInfoDTOList}" var="project" varStatus="pStatus">
            <c:forEach items="${project.childXXXDTOS}" var="c">
                <tr>
                    <c:if test="${dStatus.index!=preCityGridIndex}">
                        <td rowspan="${d.cityRowSpanCount}">${d.downtownName}</td>
                    </c:if>
                    <c:if test="${pStatus.index!=preProjectGridIndex}">
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item0}</td>
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item1}</td>
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item2}</td>
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item3}</td>
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item4}</td>
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item5}</td>
                    </c:if>
                    <td >${c.childName}</td>
                    <td >${c.childxx}</td>
                    <td >${c.childxx}</td>
                    <td >${c.childxx}</td>
                    <td >${c.childxx}</td>
                    <td >${c.childxx}</td>
                    <td >${c.childxx}</td>
                </tr>
                <c:set var="preCityGridIndex" value="${dStatus.index}" />
                <c:set var="preProjectGridIndex" value="${pStatus.index}" />
            </c:forEach>
        </c:forEach>
    </c:forEach>
    <tr style="height: 120px;background-color:#7D26CD;color:white;line-height: 58px;">
        <td colspan="2">合计</td>
        <c:forEach items="${dto.totalCountList}" var="n" varStatus="nStatus">
            <c:if test="${nStatus.index == 5}">
                <td>&nbsp;</td>
            </c:if>
            <c:if test="${nStatus.index != 5}">
                <td>${n}</td>
            </c:if>
        </c:forEach>
    </tr>
</table>
</body>
</html>

Then use Html2Image to generate pictures:
add jar package:

<dependency>
	<groupId>com.github.xuwei-k</groupId>
	<artifactId>html2image</artifactId>
	<version>0.1.0</version>
</dependency>

Generate picture:

private void html2Image(String configId,String imageID, String province, String templateWebURL) {
        HtmlImageGenerator generator = new HtmlImageGenerator();
        generator.loadUrl(templateWebURL);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        generator.getBufferedImage();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        String entryKey = chineseCharToPinYin(province);
        if (StringUtil.isEmpty(entryKey)) {
            entryKey = String.valueOf(Math.random());
        }
        entryKey += imageID;
        imageFilePath = Constant.XXXFilePath
                .replace("{day}", DateUtil.formatNoLineDate(new Date()))
                .replace("{daily_configId}", configId)
                + "img/";
        File imgFile = new File(imageFilePath);
        if (!imgFile.exists()) {
            imgFile.mkdirs();
        }
        generator.saveAsImage(imageFilePath + entryKey + ".png"); //经试验,在win7本地生成jpg图片整体背景色会发红,生成png则正常
    }

Note here: The overall background color of the jpg image generated locally in win7 will be red, but it is normal when the png is generated. I don’t know why.
Ok, the picture file is easy to get here, plus the code for generating the html file and sending the WeChat template message notification code, there are about 500 lines of code in total. The key is that this method is very convenient for future expansion, and it is easy to modify the html template page, picture The generation is completely decoupled from the image template, so it is easy to maintain!

This note, to check!

Guess you like

Origin blog.csdn.net/AJian759447583/article/details/86500638