女朋友不想开Processon会员,我魔改了一个无限制的在线绘图软件

前言

对于复杂的逻辑或者流程来说,画一画流程图可以帮助我们更好的捋清楚逻辑。平时我女朋友也偶尔会用 processon 来画一下流程图, processon 确实是一个很好的软件。

但是免费版只能创建 9 个文件,所以她平时在用的时候只能删了画、画了删,用起来不是那么方便,但是又不想为了这个东西开会员。

于是我找到了一个很棒的开源的流程图软件------draw.io,它同样也提供了在线的地址------drawio在线地址

但她用了一会之后,感觉这个在线地址也不是那么的方便易用,提出了下面的问题:

  1. 这个在线地址部署在国外,平时使用会受网络影响
  2. 本身不提供文件存储的功能,它对接了多种存储介质,比如你可以下载到本地、或者托管到一些云盘或者 GitHub ,虽然说配置起来不是很麻烦,但也并不是开箱即用
  3. 本身不提供文件管理功能,它可以导入一个个文件,感觉就像是一个编辑器而已,并没有把我创建的、编辑的流程图统一管理起来,没有类似文件/文件夹列表的功能

所以,基于上面的种种问题,我就想着基于 drawio 魔改一个的绘图软件,并且自己后端实现存储,这样就可以让这个东西免费无限制且易用。

其实说是魔改,我们改的东西不多,主要是改变存储、读取的方式,以及有一些功能不需要的可以做一些删减,最后就是自己做一个平台把文件与流程图串起来。

这是项目的体验地址,欢迎大家体验:🌟🌟体验地址🌟🌟

前端实现

首先先把 drawio 的代码拉下来,拉下来之后只需要关注 src/main/webapp 这个目录,所有的前端代码都在里面。

把前端跑起来

入口是 src/main/webapp/index.html 这个文件,我使用了 express 起了一个服务,一来是充当静态资源服务器,二来是充当开发环境的代理,规避接口调用时的跨域问题。

新建一个 server.js 文件,填入如下内容

js 复制代码
// server.js
const express = require("express");
const { createProxyMiddleware } = require("http-proxy-middleware");
const path = require("path");
const compression = require("compression");
const app = express();

app.use(compression());

// 静态资源服务
app.use(express.static(path.join(__dirname, "./src/main/webapp")));

// 接口转发
app.use(
  "/draw-io",
  createProxyMiddleware({
    target: "http://draw.eztool.top",
    changeOrigin: true,
    pathRewrite: {
      "^/draw-io": "/draw-io", // 转发的时候去掉前缀
    },
  })
);

// 启动服务器
const port = 3000;
app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

然后运行一下这个 node 脚本,启动服务。

启动服务之后,这里有两点需要注意的地方:

  1. 如果你的服务器动在 3000 端口,那么你需要访问 http://localhost:3000/?dev=1
  2. 修改 src/main/webapp/index.html 的如下代码,不然静态资源的加载会有问题

随意修改一些东西,然后打开上述的链接,如果看到修改生效,那么就证明我们的开发环境启动成功了

初始化数据

大概看了一下 drawio 的代码,发现流程图的内容是 xml 格式的。对于文件的初始化流程,可以大概找到 App.js 的如下代码,绿色代码是我新加的。

mock 一下数据,把文件的标题与内容通过实例化一个 LocalFile ,并调用 loadFile 加载到画布上

然后我们就可以得到一幅流程图如下图所示

这里真正实现的时候,是根据 id 调用后端接口,去拿标题和内容,然后加载到编辑器中,到这一步,读取数据已经完成。

保存数据

由于我们上面选择的存储介质是 LocalFile ,所以保存内容的逻辑在 LocalFile 这个类中,具体在下面打印的位置

在这个位置,我们可以把数据同步给后端进行更新。

除了内容之外,标题的更新我们也需要考虑。

标题更新的时候会走到下面这个方法,我们可以在这个方法中来发送接口给后端更新文档的标题

至此,基本的数据流向问题已经解决,在流程图层面,我们已经解决了读取数据及更新数据的问题,解决了这两个问题之后,我们就可以把流程图内容信息存在我们自己的服务器中。

其他配置项修改

还有一些其他配置项的修改,这个就根据我们自身的情况来,看看哪些东西是我们不想要的,哪些东西是要改的。

这里就得耐心去读一下它的源码了,没什么技巧,找到你自己想要改的地方,改它。这里我举两个例子。

第一个,图标的修改

上面框出来的图标,在下面的文件中,修改成你想要的图标就行。

第二个,菜单的修改,我这里对【文件】这个菜单删了很多,只保留了我觉得必要的东西

菜单在下面这个文件中,基本上就是找到你不想要的东西把它注释掉或者删掉。

打包部署

这个项目打包的工具用的 ant ,它是一个基于 java 的打包工具。所以要打包我们先要装好 javajdk 以及 ant

安装好后进入到 /etc/build 这个目录下,执行 ant 命令,就可以发现打包成功了。

部署的时候使用 nginx 开了一个目录,然后我比较偷懒。我把整个 webapp 目录都丢了上去,但是呢 webapp 目录又很大,我也不想每次通过 ftp 工具去传。

于是我就建了一个 git 仓库,把在我本地的 webapp 目录推了上去,然后在服务器拉取这个仓库。这样做了以后,我每次通过 ant 打包完之后推送代码,然后在服务器 pull 一下,代码就更新了。

还有一点要注意的是,这个项目的前端并没有使用一些现代化的打包工具,打包出来的文件名不会有 hash

为了避免缓存导致代码不生效的问题,我在 nginx 配置的时候使用了协商缓存,配置如下

css 复制代码
  location ~* \.(js|css|html)$ {
        add_header cache-control no-cache;
    }

首屏加载优化

在打包部署完成之后,发现了加载还是挺慢的,一个是我服务器的带宽比较小,另一个是确实加载了几个比较大的 js 文件。

首先 app.min.js 这个是主包,是不能省略的。然后看到 stenceils.min.jsextensions.min.js ,看看他们可不可以不阻塞主流程。

大概看了一下 stenceils.min.js 的内容,它里面都是模版,好家伙,怪不得这么大;其次关于 extensions.min.js ,看了一下 /etc/build/build.xml 打包文件,它大概是做一些拓展逻辑的,比如说一些导入导出之类的。

所以这两个包的加载完毕与否,是不影响正常的主绘制流程使用的。

这里便是上面提到的两个包的加载入口,让这个加载函数加载完第一个包之后就执行回调函数即可。

这样之后,我们不需要再等待这个包加载完成就能开始用主要的绘图逻辑,这个包加载了 13S ,也就是说,我们不需要再等这 13S

首屏加载速度提升了 10多秒 啊兄弟们,恐怖如斯~

现在没有缓存的情况下,首屏加载 3S 左右,还是挺丝滑的

平台实现

我另外用 React 实现了一个用户登录、管理文件的平台,目前做的功能有:

  • 登录/注册/修改密码
  • 文件列表,新增,修改,删除,重命名

目前来说做的还比较简单,只提供了最基本的文件管理功能,这个平台跟上面的绘图页面可以理解为是两个项目。

在新建或者打开的时候,会从这个平台跳转到绘图项目:

js 复制代码
export const openDraw = (id) => {
  const dev = location.href.includes("localhost");
  let url;
  if (dev) {
    url = `http://localhost:3000?dev=1&id=${id}`;
  } else {
    url = `http://draw.eztool.top/draw?id=${id}`;
  }
  window.open(url);
};

后端

后端使用的 java ,使用的是 SpringBoot 搭建的项目。

相关技术栈:

  • JWT:鉴权
  • MongoDB:使用 Mongo-Plus 作为数据存储
  • Redis:缓存
  • Spring mail:邮件发送验证码
  • transmittable-thread-local:上下文信息传输

后端实现主要分为三块:

鉴权

在之前做 工具网站 的时候用到了 sa-token 框架,这个框架整体来说功能挺强大的,但对于小网站来说可能很多东西都不太需要。所以我这次基于 hutool 的工具类自己包了一层,实现了一个较为简洁的 JWT 鉴权流程。

java 复制代码
@Slf4j
@UtilityClass
public class JwtUtil {

    String jwtStr  = "xxxxxxx";

    public String getToken(User user) {
        return JWTUtil.createToken(BeanUtil.toBean(user,Map.class), jwtStr.getBytes());
    }

   public Boolean verify(String token) {
       return JWTUtil.verify(token, jwtStr.getBytes());
   }


   public void isLogin(String token){
        if (StrUtil.isBlank(token)) {
            throw new BusinessException("请先登录");
        }
        if (!verify(token)) {
            throw new BusinessException("请先登录");
        }
   }

   public User getUser(String token){
       isLogin(token);
       JWTPayload payload = JWTUtil.parseToken(token).getPayload();
       if (Objects.isNull(payload)){
           throw new BusinessException("用户信息为空");
       }
       return User.tpJWTPayload(payload);
   }

}

这里封装了一个设置 token 以及解析 token 的工具类,登录成功后 token 就被设置到 cookie 中,请求过来时解析 cookie 中的 token 以获取用户信息。

用户信息

这里包含了用户的注册、登录、修改密码等功能。

用户信息表的结构如下:

json 复制代码
{
    "userId": "",
    "ip": "",
    "name": "",
    "account": "",
    "password": "",
    "id": "",
    "createTime": "",
    "updateTime": ""
}

注册这里用到了邮箱验证码作为校验,验证码发送出去后会存在 redis 中,并设有有效期。

然后注册一个新邮箱作为发送验证码的邮箱,以 163邮箱 为例。

在这里开通 SMTP

然后引入邮件依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

配置yml

yml 复制代码
spring:
  mail:
    # 设置邮箱主机
    host: smtp.163.com
    # SMTP 服务器的端口
    port: 587
    # 设置用户名,这里使用你邮箱账号就行
    username: 123456789@163.com
    # 设置密码,该处的密码是邮箱开启SMTP的授权码而非邮箱密码
    password: SODJSHAUHGQWRQWE
    default-encoding: UTF-8
    protocol: smtps
    properties:
      mail:
        smtp:
          ssl:
            enable: true

具体的实现如下:

java 复制代码
@Slf4j
@Service
@RequiredArgsConstructor
public class MailServiceImpl  implements MailService {

    private final JavaMailSender javaMailSender;
    
    //发送邮件
    @Override
    public void sendEmail(String email,String subject, String text) {
        try {
            SimpleMailMessage mailMessage = new SimpleMailMessage();
            //你的邮箱账号
            mailMessage.setFrom("123456789@163.com");
            //接收方的邮箱账号
            mailMessage.setTo(email);
            //标题
            mailMessage.setSubject(subject);
            //内容
            mailMessage.setText(text);
            //发送邮件
            javaMailSender.send(mailMessage);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("发送邮箱失败:{}",e.getMessage());
        }
    }

}

文件表

文件表里包括文件夹跟文件,主要通过 type 去区分。更详尽的表结构字段如下:

json 复制代码
{
    "fileId": "", //文件ID
    "parentId": "", //上级文件id
    "name": "", //名称
    "type": "", //文件类型 0=文件 1=文件夹
    "content": "", //文件内容
    "isSub": false, //是否有下级(针对文件夹点击下拉判断)
    "createId": "", //创建用户id
    "updateId": "", //修改用户id
    "delFlag": "", //逻辑删除 0=正常 1=删除
    "id": "",
    "createTime": "",
    "updateTime": ""
}

剩下的就是关于文件的一些增删改查逻辑,这里就不再放具体的代码。通过维护 parentIdfileId 的对应关系,就可以实现文件树的逻辑。

最后

这就是我基于 drawio 魔改的一个在线绘图软件,对于我们自身的要求来说是够用了。后续的拓展的话,我尽量还是以平台拓展为主,绘图功能拓展为辅,因为这个绘图功能已经很强大了,甚至对我来说,这个绘图我常用的还不到它功能的 10% ,所以我也不太想花太多精力去改它。

后续可能会拓展的点:

  • 模版创建
  • 模版市场
  • 回收站
  • 分享

如果你也有像我们一样的痛点,欢迎你体验我们的站点。如果你觉得有哪里用得觉得不舒服的地方,也欢迎随时与我们反馈。希望这个对你会有帮助~

以上就是本文的全部内容,如果你觉得有意思的话,点点关注点点赞吧~

相关推荐
喵叔哟33 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕2 小时前
Django 搭建数据管理web——商品管理
前端·python·django