需求
用户可以选择以长图的形式分享本网页
方法
- 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配置类,在每次程序开始运行时自动生成存放图像的文件夹
@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的生产者生成事件,通知消费者:
@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的消费者,定义如何消费生成长图的事件:
@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());
}
}
}