使用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());
        }
    }
}

测试

相关推荐
嘟嘟可在哪里。4 小时前
IntelliJ IDEA git凭据帮助程序
java·git·intellij-idea
岁忧4 小时前
(LeetCode 每日一题) 3541. 找到频率最高的元音和辅音 (哈希表)
java·c++·算法·leetcode·go·散列表
_extraordinary_4 小时前
Java 多线程进阶(四)-- 锁策略,CAS,synchronized的原理,JUC当中常见的类
java·开发语言
纪元A梦5 小时前
贪心算法应用:信用评分分箱问题详解
java·算法·贪心算法
007php0075 小时前
Redis高级面试题解析:深入理解Redis的工作原理与优化策略
java·开发语言·redis·nginx·缓存·面试·职场和发展
Yeats_Liao5 小时前
Spring缓存(二):解决缓存雪崩、击穿、穿透问题
java·spring·缓存
猿究院-赵晨鹤6 小时前
String、StringBuffer 和 StringBuilder 的区别
java·开发语言
葵野寺6 小时前
【RelayMQ】基于 Java 实现轻量级消息队列(九)
java·开发语言·rabbitmq·java-rabbitmq
代码不停6 小时前
MySQL联合查询
java·数据库·mysql
nightunderblackcat6 小时前
新手向:C语言、Java、Python 的选择与未来指南
java·c语言·python