Java SpringBoot集成Freemarker将Html转图片

有个会员营销的功能:用户在下单后将绑定了订单和用户活动信息的二维码生成一张样式随机的宣传海报,用户可以直接下载这张海报进行推广

分析有不同的用户、不同的活动信息、以及不同样式的海报模板。这时候使用了Freemarker填充Html,再将Html转为Png是一个很好的办法。在数据库配置多个海报样式模版,按照用户的维度取到对应的活动信息和模板信息,给模版组装好Map类型数据,自动填充后生成不同信息的海报了。

新建一个SpringBoot项目,在 pom.xml 文件里面引入:Cssbox 和 Freemarker 依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
  ...
   <dependency>
     <groupId>net.sf.cssbox</groupId>
     <artifactId>cssbox</artifactId>
     <version>4.12</version>
   </dependency>
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-freemarker</artifactId>
 </dependency>
  ...
</dependencies>

第一步创建一个配置类来配置 Freemarker 的自定义模版加载器:FreemarkerConfig.java :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.demo.htmltopng.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
 
/**
 * @author AnYuan
 */
 
@Configuration
public class FreemarkerConfig{
 
    /**
     * 自定义的模版加载器
     * @param demoTemplateLoader demoTemplateLoader 自定义从数据库取样式
     * @return FreeMarkerConfigurer
     */
 
    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer(DemoTemplateLoader demoTemplateLoader) {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setPreTemplateLoaders(demoTemplateLoader);
        return configurer;
    }
}

创建一个模版加载器DemoTemplateLoader.java ,实现Freemarker包的TemplateLoader接口。这一步主要是能够通过条件需要从数据库自定义查询模版样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.demo.htmltopng.config;
 
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.demo.htmltopng.model.entity.DemoFreemarkerTemplate;
import com.demo.htmltopng.service.DemoTemplateService;
import freemarker.cache.TemplateLoader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
 
/**
 * @author AnYuan
 */
 
@Slf4j
@Component("DemoTemplateLoader")
public class DemoTemplateLoader implements TemplateLoader {
 
    @Autowired
    private DemoTemplateService demoTemplateService;
 
    /**
     * 从mysql根据 code 查询一个样式模版
     * @param code 查询条件
     * @return Object 模版html
     * @throws IOException IOException
     */
    @Override
    public Object findTemplateSource(String code) throws IOException {
        return  demoTemplateService.getOne(new QueryWrapper<DemoFreemarkerTemplate>().lambda().eq(DemoFreemarkerTemplate::getCode, code));
    }
 
    /**
     * 获取模版的最后更新时间 这里直接使用当前时间
     * @param o 模版
     * @return long 最后时间
     */
    @Override
    public long getLastModified(Object o) {
        return LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
    }
 
    /**
     * 根据查询的模版得到Reader
     * @param Object 模版对象
     * @param String s
     * @return Reader
     * @throws IOException IOException
     */
    @Override
    public Reader getReader(Object o, String s) throws IOException {
        return new StringReader(((DemoFreemarkerTemplate) o).getValue());
    }
 
    /**
     * 关闭模版源
     * @param Object o
     * @throws IOException IOException
     */
    @Override
    public void closeTemplateSource(Object o) throws IOException {
 
    }
}

 因为样式模版是多条配置在数据库,先创建一个表:

1
2
3
4
5
6
7
8
9
CREATE TABLE `demo_freemarker_template` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `code` varchar(4) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '模版名称',
    `value` text COLLATE utf8_unicode_ci COMMENT '模版样式Html',
    PRIMARY KEY (`id`)
DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
<br>
# 模拟数据
INSERT INTO demo_freemarker_template(`code`, `value`) VALUES ('T001', '<html><head><title>Welcome!</title></head><body style=\"margin:0px\"><h1>Welcome ${user}!</h1><img src=\"${info.url}\"></body></html>');<br><br>

创建一个实体类接收数据库的模版数据:DemoFreemarkerTemplate.java :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.demo.htmltopng.model.entity;
 
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
 
import java.io.Serializable;
 
/**
 * @author AnYuan
 */
 
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class DemoFreemarkerTemplate implements Serializable {
 
    private static final long serialVersionUID=1L;
 
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
 
    private String code;
 
    private String value;
}

还有一个 DemoTemplateService.java 接口类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.demo.htmltopng.service;
 
import com.demo.htmltopng.model.entity.DemoFreemarkerTemplate;
import com.baomidou.mybatisplus.extension.service.IService;
 
/**
 * <p>
 *  服务类
 * </p>
 *
 * @author AnYuan
 */
public interface DemoTemplateService extends IService<DemoFreemarkerTemplate> {
 
}

接口接口实现类:DemoTemplateServiceImpl.java :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.demo.htmltopng.service.impl;
 
import com.demo.htmltopng.model.entity.DemoFreemarkerTemplate;
import com.demo.htmltopng.repository.DemoTemplateMapper;
import com.demo.htmltopng.service.DemoTemplateService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
 
/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author AnYuan
 */
@Service
public class DemoTemplateServiceImpl extends ServiceImpl<DemoTemplateMapper, DemoFreemarkerTemplate> implements DemoTemplateService {
 
}

写填充html生成png的逻辑,一个服务接口:HtmlToPngService.java : 

1
2
3
4
5
6
7
8
9
10
package com.demo.htmltopng.service;
 
/**
 * @author AnYuan
 * 生成png服务接口
 */
public interface HtmlToPngService {
 
    void htmlToPng() throws Exception;
}

一个接口实现类:HtmlToPngServiceImpl.java :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package com.demo.htmltopng.service.impl;
 
import com.demo.htmltopng.service.HtmlToPngService;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.fit.cssbox.css.CSSNorm;
import org.fit.cssbox.css.DOMAnalyzer;
import org.fit.cssbox.io.DOMSource;
import org.fit.cssbox.io.DefaultDOMSource;
import org.fit.cssbox.io.DocumentSource;
import org.fit.cssbox.io.StreamDocumentSource;
import org.fit.cssbox.layout.BrowserCanvas;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
 
import javax.imageio.ImageIO;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
 
/**
 * @author AnYuan
 * 生成png服务实现
 */
 
@Slf4j
@Service
public class HtmlToPngServiceImpl implements HtmlToPngService {
 
    /**
     * Mock 的数据
     * @return Map<String, String>
     */
    private Map<String, String> getUser() {
 
        HashMap info = new HashMap(2);
        info.put("age", "18");
        info.put("url", "https://pic.cnblogs.com/avatar/2319511/20210304170859.png");
 
        Map user = new HashMap(2);
        user.put("user", "安逺");
        user.put("info", info);
 
        return user;
    }
 
    @Autowired
    private FreeMarkerConfigurer configuration;
 
    @Override
    public void htmlToPng() throws Exception {
 
        // 查询模版条件
        String templateCode = "T001";
 
        // png图片宽度
        int width = 220;
        // png图片高度
        int height = 150;
 
        // 从数据库查询一个模版
        Template template = configuration.getConfiguration().getTemplate(templateCode);
        // 将数据替换模版里面的参数
        String readyParsedTemplate = FreeMarkerTemplateUtils.processTemplateIntoString(template, getUser());
 
        // 创建一个字节流
        InputStream is = new ByteArrayInputStream(readyParsedTemplate.getBytes(StandardCharsets.UTF_8));
        // 创建一个文档资源
        DocumentSource docSource = new StreamDocumentSource(is, null, "text/html; charset=utf-8");
        // 创建一个文件流
        String fileName = UUID.randomUUID().toString();
        FileOutputStream out = new FileOutputStream("./" + new File(fileName + ".png"));
 
        try {
            // 解析输入文档
            DOMSource parser = new DefaultDOMSource(docSource);
            // 创建CSS解析器
            DOMAnalyzer da = new DOMAnalyzer(parser.parse(), docSource.getURL());
 
            // 设置样式属性
            da.attributesToStyles();
            da.addStyleSheet(null, CSSNorm.stdStyleSheet(), DOMAnalyzer.Origin.AGENT);
            da.addStyleSheet(null, CSSNorm.userStyleSheet(), DOMAnalyzer.Origin.AGENT);
            da.addStyleSheet(null, CSSNorm.formsStyleSheet(), DOMAnalyzer.Origin.AGENT);
            da.getStyleSheets();
            BrowserCanvas contentCanvas = new BrowserCanvas(da.getRoot(), da, docSource.getURL());
            contentCanvas.createLayout(new Dimension(width, height));
 
            // 生成png文件
            ImageIO.write(contentCanvas.getImage(), "png", out);
 
        } catch (Exception e) {
           log.info("HtmlToPng Exception", e);
 
        } finally {
            out.close();
            is.close();
            docSource.close();
        }
    }
}

以上就已经能够根据Html填充数据后生成一张Png的图了,最后写一个单元测试:HtmlToPngServiceImplTest.java : 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.demo.htmltopng.service.impl;
 
import com.demo.htmltopng.service.HtmlToPngService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
 
@Slf4j
@SpringBootTest
class HtmlToPngServiceImplTest {
 
    @Autowired
    private HtmlToPngService htmlToPngService;
 
 
    @Test
    public void htmlToPngTest(){
        try {
            htmlToPngService.htmlToPng();
        }catch (Exception e) {
            log.info("PngError:{}", e.getMessage());
        }
    }
}

运行后会在设置好的路径生成一张Png的图片了,这里生成的Png在项目根目录:

只要先把Html的样式先写好配置在数据库,再传入与Html模版里面填充的字段名相同的Map数据,就能直接生成Html页面对应的Png图片了

 

Freemarker语法参考:http://freemarker.foofun.cn/

本篇代码Github地址:https://github.com/Journeyerr/cnblogs/tree/master/htmltopng

 

posted @   安逺  阅读(2887)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示