在我们使用app的时候有很多情况需要分享,比如背单词每日打卡,而分享实际上就是将当前页面截图然后保存下来,这里我们就需要模拟APP来实现Web的生成长图并查看。
在这里我们需要用到wkhtmltopdf,这是一个将html页面转换成image或者pdf的工具,具体命令是类似wkhtmltoimage --quality 75 https://www.baidu.com d:/work/data,其中wkhtmltoimage表示将html转换成image,–quality 75表示将图片压缩到75%,再后面跟的是html的url和图片保存的路径。

配置

在配置上我们需要将wkhtmltopdf的指令和路径配置到application.properties中

# wk
wk.image.command=d:/work/wkhtmltopdf/bin/wkhtmltoimage
wk.image.storage=d:/work/data/wk-images

然后,wkhtmltopdf并不会自己创建目录,因此我们要确保目录是存在的。我们可以写一个config类,然后写一个初始化方法,使用@PostConstruct注解确保只会在服务启动时初始化一次,在该方法中检查路径是否存在,不存在就创建。

@Configuration
public class WkConfig {

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

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

    @PostConstruct
    public void init(){
        // 创建WK图片目录
        File file = new File(wkImageStorage);
        if (!file.exists()){
            file.mkdir();
            logger.info("创建WK图片目录:" + wkImageStorage);
        }
    }
}

Controller

我们采用消息队列来实现生成长图功能,当controller收到一个生成长图的请求时我们将任务放入消息队列中,然后然后一个可以访问图片的路径即可。
这里为了简便,我们就要求用户将路径以参数的方式传过来,我们获取url,然后使用UUID生成随机的文件名,将这些参数放入Event实例中,然后返回一个带长图路径的map即可,对于路径我们设置为domain + contextPath + “/share/image/” + fileName。
然后我们在写一个Controller用于输出png文件即可。这里和之前显示头像是一致的。具体就是用File指向文件,然后使用FileInputStream装饰File实例,将内容读取到byte数组buffer中去,然后再将buffer数组中的写到response的OutputStream输出流实例中,循环中读取完全部数据。

@Controller
public class ShareController implements CommunityConstant {

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

    @Autowired
    private EventProducer eventProducer;

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

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

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

    @RequestMapping(path = "/share",method = RequestMethod.GET)
    @ResponseBody
    public String share(String htmlUrl){
        // 文件名
        String fileName = CommunityUtil.generateUUID();

        // 异步生成长图
        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(200,null, map);
    }

    // 获取长图
    @RequestMapping(path = "/share/image/{fileName}",method = RequestMethod.GET)
    public void getShareImage(@PathVariable("fileName") String fileName, HttpServletResponse response){
        if (StringUtils.isBlank(fileName)){
            throw new IllegalArgumentException("文件名不能为空");
        }

        response.setContentType("image/png");
        File file = new File(wkImageStorage+"/"+fileName+".png");
        try {
            OutputStream os = response.getOutputStream();
            FileInputStream fis = new FileInputStream(file);
            byte[] buffer = new byte[1024];
            int b = 0;
            while ((b = fis.read(buffer)) != -1){
                os.write(buffer,0,b);
            }
        } catch (IOException e) {
            logger.error("获取长图失败:" + e.getMessage());
        }
    }
}

生成长图

我们需要消费Topic为share的任务来生成长图。
使用ConsumerRecord实例来获取event,检查空值和消息格式和之前一致,然后我们得到htmlUrl,fileName,suffix,拼接出wkhtmltoimage命令,然后调用Runtime.getRuntime().exec(cmd)即可,注意该语句是请求操作系统去执行cmd指令,因此是异步的。

// 消费分享时间
@KafkaListener(topics = TOPIC_SHARE)
public void handleShareMessage(ConsumerRecord record){
    if (record == null || record.value() == null ){
        logger.error("消息的内容为空!");
        return;
    }

    Event event = JSONObject.parseObject(record.value().toString(), Event.class);
    if (event == null){
        logger.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("生成长图成功:" + cmd);
    } catch (IOException e) {
        logger.error("生成长图失败:" + e.getMessage());
        e.printStackTrace();
    }
}