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

测试

相关推荐
救救孩子把39 分钟前
Mac 环境下 JDK 版本切换全指南
java·开发语言·macos
lichuangcsdn42 分钟前
【springcloud学习(dalston.sr1)】Eureka服务端集群的搭建(含源代码)(二)
学习·spring cloud·eureka
路飞雪吖~44 分钟前
【Linux】共享内存
java·linux·服务器
caihuayuan41 小时前
鸿蒙AI开发:10-多模态大模型与原子化服务的集成
java·大数据·sql·spring·课程设计
张哈大1 小时前
【 Redis | 实战篇 秒杀优化 】
java·数据库·redis·笔记·缓存
低维歌者1 小时前
python训练营day27
java·开发语言·python
大帅不是我2 小时前
Python多进程编程执行任务
java·前端·python
purrrew2 小时前
【Java ee初阶】jvm(3)
java·jvm
Hello World......4 小时前
互联网大厂Java面试:从Spring到微服务的全面探讨
java·spring boot·spring cloud·微服务·面试·技术栈·互联网大厂
拾贰_C5 小时前
【SpringBoot】MyBatisPlus(MP | 分页查询操作
java·spring boot·后端·spring·maven·apache·intellij-idea