在springboot项目中调用PlantUML生成图片

1,577 阅读3分钟

公司的一个产品中,有一个组织架构变更的场景,需要将变动的组织架构形成脑图,并且将变更提交到oa系统审批的时候,审批人需要能查看这个脑图。

image.png

预览地址方案

需求很简单,但是在实际执行的时候,发现问题很多

我们原来的想法是让前端单独建一个页面,去渲染这个脑图,将页面的地址也放oa审批单里,审批人就能直接看了。

但是问题来了

  1. 组织架构预览地址的系统和oa系统不能做统一登录,oa审批过程中,审批人想看脑图时,还需要跳转登录,体验不好。
  2. oa系统有手机端,但是组织架构的预览页面没有适配手机端,手机查看不友好
  3. 组织架构的系统是公司内网系统,如果没在公司用手机无法登录,需要连vpn,操作要求太高了。

所以直接放一个预览页面的url,体验太差,根本实施不了。怎么办?
页面截图!这是大家觉得最简单的办法,截图直接随流程提交oa,在流程审批中,点击图片查看,电脑和手机都兼容,而且图片存储在oa,没有网络限制
貌似所有问题迎刃而解,但是问题又来了,怎么截图?

截图方案-capture-website

要将整个页面完整的截图,后端是做不了的,因为我们是前后端分离的,页面前端自己渲染。

同事出了个方案,公司的前端部门部署过一套开源的截图系统,是nodejs写的,可以对指定的url地址先通过V8引擎进行渲染,与浏览器访问看到的页面一模一样,然后在截图。效果符合我们需求。

于是进行了对接,中间也遇到了一些问题,比如怎么对登录页面截图,截图太早,页面还未渲染完毕等。

好在这些问题截图的系统都是支持的,只要对传参进行一些适配就行
有个同事提出了公司里有个前端部门部署了一个开源的浏览器页面截图功能,可以满足需要。
这个服务是基于capture-website,github上一个开源项目:
github.com/sindresorhu…

秉承拿来主义原则,用就完了,上代码

public File generateOrgImg2(Long orgFlowId) {
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    String serverPath = CommonUtils.getServerPath(request);
    String cookieValue = CookieUtil.getCookie(request, "SESSION");
    String cookie = "SESSION=" + cookieValue + "; Path=/; HttpOnly;";
    String cutPicUrl = "http://192.168.0.80:8088/departmentTree/departmentTreePage?target=123" + orgFlowId;

    Map<String, Object> dataMap = new HashMap<>();
    dataMap.put("url", cutPicUrl);
    dataMap.put("fullpage", true);
    dataMap.put("delay", 2);
    dataMap.put("cookies", Arrays.asList(cookie));

    File imgFile = null;
    try {
        String data = JSONObject.toJSONString(dataMap);
        ResponseBody result = OKHttpUtil.postJsonBody(captureUrl, data);

        log.info("截图返回结果:{}", JSONObject.toJSONString(result));
        imgFile = DFSFileUtil.createTempFile(".png");
        FileUtils.writeByteArrayToFile(imgFile, result.bytes());
    } catch (Exception e) {
        log.error("截图生成失败,generateOrgImg: {}", e);
    }
    return imgFile;
}

这里的cutPicUrl是我们想截图的页面,captureUrl就是截图服务的调用地址,通过http调用就能返回你想截的图片,使用非常简单

问题:

用了几个月后,就出现截图返回不了的问题,后来排查后没结果,重启服务后,🈶可以了,过了几个月又出现了问题

我们认为这个项目会有不稳定的情况出现,于是,我们急需找到一种可以替代的方案

本以为可以高枕无忧了,运行了几个月后,截图服务就挂了,图片没有返回,让前端团队排查问题,他们也懵逼,因为这个是开源系统,他们不敢瞎改。

于是重启服务,竟然就好了。后面也是出现了几次,一般都是几个月出现一次,频率不是特别高,但是出问题了,就得挨领导骂。

后来组织架构系统这边将前端的代码从分支里独立出去了,前端页面地址需要在后端渲染出的首页里嵌入,地址是一个客户端能访问的地址了。

然后问题再一次升级,截图服务彻底不能用了。因为截图是在服务端执行截图的,服务器中不能访问外部的客户端地址,必须内部地址,截图时前端页面无法访问

有一次陷入困境.....

截图方案-PlantUML

有个同事提出了一个解决方案,因为我们的页面简单,只是一个脑图,所以有个插件PlantUML就能在后端直接可以将文本渲染出脑图,再生成图片

研究了使用手册后,得出结论是可行!

插件官网:plantuml.com/zh/

PlantUML使用

plantUML很强大,能在线作图,实时显示,但是我们不需要用这个功能,我们只要在后端根据树形结构数据生成脑图就行。

springboot项目使用非常简单,在pom.xml中引入最新依赖

<dependency>
    <groupId>net.sourceforge.plantuml</groupId>
    <artifactId>plantuml</artifactId>
    <version>1.2023.9</version>
</dependency>

然后建个controller类

@GetMapping("/flows")
public void getFlows() throws IOException {
    OutputStream png = new FileOutputStream("/Users/td/uml.png");
    String source = "@startmindmap\n" +
            "* 一级部门\n" +
            "** 数字金融部-张三【主管】\n" +
            "*** Linux Mint\n" +
            "*** Kubuntu\n" +
            "*** Lubuntu\n" +
            "*** KDE Neon\n" +
            "** LMDE\n" +
            "** SolydXK\n" +
            "** SteamOS\n" +
            "** Raspbian with a very long name\n" +
            "*** <s>Raspmbc</s> => OSMC\n" +
            "*** <s>Raspyfi</s> => Volumio\n" +
            "@endmindmap";

    SourceStringReader reader = new SourceStringReader(source);
    String desc = reader.outputImage(png).getDescription();
}

生成的图片如下:

image.png

默认生成的图片清晰度不够,而且还可以进行颜色等样式的美化
清晰度可以用skinparam来设置,还能通过title来设置标题,[#Orange]来设置颜色,Orange是颜色,具体的就去翻文档吧

设置后的代码如下:

@GetMapping("/flows")
public void getFlows() throws IOException {
    OutputStream png = new FileOutputStream("/Users/td/uml.png");
    String source = "@startmindmap\n" +
            "skinparam dpi 300\n"+
            "title 组织架构变更\n" +
            "*[#Orange] 一级部门\n" +
            "** 数字金融部-张三【主管】\n" +
            "*** Linux Mint\n" +
            "*** Kubuntu\n" +
            "*** Lubuntu\n" +
            "*** KDE Neon\n" +
            "** LMDE\n" +
            "** SolydXK\n" +
            "** SteamOS\n" +
            "** Raspbian with a very long name\n" +
            "*** <s>Raspmbc</s> => OSMC\n" +
            "*** <s>Raspyfi</s> => Volumio\n" +
            "@endmindmap";

    SourceStringReader reader = new SourceStringReader(source);
    String desc = reader.outputImage(png).getDescription();
}

生成的图片如下:

image.png

效果非常满意,也解决了我的问题。当然这个工具还有很多的用途和使用场景,等待我们去发现!