使用wkhtmltoimage实现生成长图分享

需求

用户可以选择以长图的形式分享本网页

方法

  • wkhtmltopdf
    • wkhtmltopdf url file
    • wkhtmltoimage url file
  • java
    • Runtime.getRuntime().exec()

下载

直接去官网下载对应的版本:官网

命令行使用WK

复制代码
>  wkhtmltopdf https://www.nowcoder.com /opt/project/java/mycommunity-pdfs/1.pdf            
Loading pages (1/6)
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done

>  wkhtmltoimage  https://www.nowcoder.com /opt/project/java/mycommunity-images/1.png     
Loading page (1/2)
Rendering (2/2)
Done

// 上面那条命令生成的长图太大了,可以使用下面这条:以75%的质量输出
>  wkhtmltoimage --quality 75  https://www.nowcoder.com /opt/project/java/mycommunity-images/1.png     
Loading page (1/2)
Rendering (2/2)
Done

在java中调用

application.properties:

复制代码
mycommunity.path.domain=http://localhost:8080
server.servlet.context-path=/myCommunity
# WK
wk.image.command=/usr/local/bin/wkhtmltoimage
wk.image.storage=/opt/project/java/mycommunity-images/

WK配置类,在每次程序开始运行时自动生成存放图像的文件夹

java 复制代码
@Configuration
public class WkConfig {

    private static final Logger logger = LoggerFactory.getLogger(WkConfig.class);

    @Value("${wk.image.storage}")
    private String wkImageStorage;

    @PostConstruct
    public void init(){
        // create the dic about WKimage
        File file = new File(wkImageStorage);
        if(!file.exists()){
            file.mkdir();
            logger.info("create the dictionary of WKimage: " + wkImageStorage);
        }
    }
}

调用WK的控制层,因为生成长图比较耗时,所以使用异步操作,在用户操作时调用Kafka的生产者生成事件,通知消费者:

java 复制代码
@Controller
public class ShareController implements CommunityConstant {

    private static final Logger logger = LoggerFactory.getLogger(ShareController.class);

    @Autowired
    private EventProducer eventProducer;

    @Value("${mycommunity.path.domain}")
    private String domain;

    @Value("${server.servlet.context-path}")
    private String contextPath;

    @Value("${wk.image.storage}")
    private String wkImageStorage;

    @GetMapping(path = "/share")
    @ResponseBody
    public String share(String htmlUrl){
        // generate the file name
        String fileName = CommunityUtil.generateUUID();

        // Asynchronous generation long pic
        Event event = new Event()
                .setTopic(TOPIC_SHARE)
                .setData("htmlUrl", htmlUrl)
                .setData("fileName", fileName)
                .setData("suffix", ".png");
        eventProducer.fireEvent(event);

        Map<String, Object> map = new HashMap<>();
        map.put("shareUrl", domain + contextPath + "/share/image" + fileName);

        return CommunityUtil.getJSONString(0, null, map);
    }

    //
    @GetMapping(path = "/share/image/{fileName}")
    public void getShareImage(@PathVariable("fileName") String fileName, HttpServletResponse response){
        if(StringUtils.isBlank(fileName)){
            throw new IllegalStateException("the file name cannot be blank");
        }

        response.setContentType("image/png");
        File file = new File(wkImageStorage + "/" + fileName + ".png");
        try {
            OutputStream os = response.getOutputStream();
            FileInputStream fis = new FileInputStream(file);
            byte[] data = new byte[1024];
            int len = 0;
            while ((len = fis.read(data)) != -1) {
                os.write(data, 0, len);
            }
        } catch (IOException e) {
            logger.error("querty the long image failed: ", e.getMessage());
        }
    }
}

Kafka的消费者,定义如何消费生成长图的事件:

java 复制代码
@Component
public class EventConsumer implements CommunityConstant {

    @Autowired
    private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);

    @Value("${wk.image.storage}")
    private String wkImageStorage;

    @Value("${wk.image.command}")
    private String wkImageCommand;

    @KafkaListener(topics = {TOPIC_SHARE})
    public void handleShareMessage(ConsumerRecord record) {
        if (record == null || record.value() == null) {
            logger.error("the content of the message is empty");
            return;
        }

        Event event = JSONObject.parseObject(record.value().toString(), Event.class);
        if (event == null) {
            logger.error("message format error");
            return;
        }

        String htmlUrl = (String) event.getData().get("htmlUrl");
        String fileName = (String) event.getData().get("fileName");
        String suffix = (String) event.getData().get("suffix");

        String cmd = wkImageCommand + " --quality 75 " + htmlUrl + " " + wkImageStorage + "/" + fileName + suffix;
        try {
            Runtime.getRuntime().exec(cmd);
            logger.info("generate long image successfully: " + cmd);
        } catch (IOException e) {
            logger.info("generate long image fail: " + e.getMessage());
        }
    }
}

测试

相关推荐
滴水可藏海13 分钟前
EasyExcel系列:读取空数据行的问题
java
王小二_Leon22 分钟前
JAVA中正则表达式的入门与使用
java·正则表达式
骑牛小道士35 分钟前
java基础 运算符
java
晴天毕设工作室1 小时前
计算机毕业设计指南
java·开发语言·python·计算机网络·课程设计
jhtwn1 小时前
Java NIO之Buffer
java·开发语言
逆水寻舟1 小时前
尚硅谷2019版Java网络编程笔记
java·网络·笔记
谦行1 小时前
前端视角 Java Web 入门手册 5.2:真实世界 Web 开发——Spring Boot 应用启动流程
java·后端
猿java2 小时前
你使用过 Service Mesh吗?
java·面试·service mesh
小蓝波2 小时前
Docker部署jenkins
java·docker·jenkins
风象南2 小时前
SpringBoot中的4种重试机制实现方案
java·spring boot·后端