稀土掘金技术社区

女朋友说没找到好用的画ER图工具,于是我们自己手搓了一个!

前言

学校数据库作业是让我们画一堆ER图,我对象把这个重任交给了我。起初我一听画er图嘛,那不简简单单,我没有很在意。直到我尝试花了一个之后,就已经过了一个上午,看着剩下的一堆和仅剩2天的时间,我天塌了.........女朋友觉得我不在意她,于是她和我说写不完了就分手吧!

于是我躺在床上幻想我们曾经的点点滴滴.............灵机一动那我们两个人写一个不就好了嘛,不仅可以增加我们团队开发项目的经验,还可以提高我们学习效率。沟通好之后直接开干!经历几天时间终于把工具做好啦,大家可以试试在线生成ER图工具

项目介绍

在数据库课程或毕业设计阶段,手绘ER图是否总是让你头疼不已?无论是老师布置的数据库设计任务,还是毕业项目中的系统建模,ER图似乎成了必经的难关。但你是否意识到,ER图其实与数据库中的表结构一一对应,难道我们真的需要一个个手动绘制表、字段和关系吗?答案是否定的!

为了解决这一难题,我们特别开发了一款 「在线SQL转ER图工具」。只需将你的SQL语句输入工具,它便能自动解析数据库表结构,精准生成专业的ER图。无论是创建表、设置外键约束,还是处理其他复杂的数据库结构,这款工具都能一键完成转化,让你轻松拥有清晰、规范的ER图。

我们深知,学校的数据库课程通常要求根据特定需求设计数据库并绘制ER图,许多同学因此苦恼不已。手动画图不仅耗时费力,还容易出错。我们开发这个工具,正是为了解决这一痛点。它不仅帮助你快速、准确地生成ER图,还让你能够专注于数据库设计的核心内容,而无需花费大量时间在绘图上。

无论你是数据库初学者,还是已经具备一定基础的学生,这款工具都能大幅提升你的工作效率,助你在数据库作业和毕业设计中游刃有余,轻松应对各种挑战。

立即试用我们的 「画ER图工具」,告别繁琐手绘,节省宝贵时间,让你的数据库设计更加高效与专业!

工具地址:在线SQL转ER图工具

有了这款工具,你将享受以下超赞优势:

  1. 「极速生成ER图」:只需复制粘贴你的SQL语句到工具里,立马自动解析生成ER图,省下大量手动绘图的时间。
  2. 「智能识别表结构和关系」:工具能精准识别SQL里的表定义、字段类型、约束条件等信息,自动生成关系图,细节一点不错过。
  3. 「易于理解和修改」:ER图清晰展示了各数据表之间的关系,帮你更好地掌握数据库结构。图形直观明了,修改起来也超级方便。
  4. 「轻松搞定作业难题」:不再为学校要求手动绘制ER图而头疼,有了这个工具,画图变得简单轻松,再也不用为此烦心啦!

在线SQL转ER图

SQL直接转ER图功能:

例如我现在有一段SQL语句,我想要把它转化为作业中需要用到的ER图效果:

-- 学生信息表CREATE TABLE `students`(    `id`              INT AUTO_INCREMENT COMMENT '学生ID',    `name`            VARCHAR(50)       NOT NULL COMMENT '姓名',    `gender`          ENUM ('男', '女') NOT NULL COMMENT '性别',    `birth_date`      DATE COMMENT '出生日期',    `email`           VARCHAR(100) COMMENT '邮箱',    `phone`           VARCHAR(20) COMMENT '电话',    `address`         VARCHAR(255) COMMENT '家庭地址',    `enrollment_date` DATE COMMENT '入学日期',    `status`          ENUM ('在校', '毕业', '退学') DEFAULT '在校' COMMENT '状态',    PRIMARY KEY (`id`)) COMMENT ='学生';
-- 课程信息表CREATE TABLE `courses`( `id` INT AUTO_INCREMENT COMMENT '课程ID', `name` VARCHAR(100) NOT NULL COMMENT '课程名称', `description` TEXT COMMENT '课程描述', `credit` INT NOT NULL COMMENT '学分', `teacher` VARCHAR(50) COMMENT '授课教师', PRIMARY KEY (`id`)) COMMENT ='课程';
-- 学生选课记录表CREATE TABLE `student_courses`( `id` INT AUTO_INCREMENT COMMENT '选课记录ID', `student_id` INT NOT NULL COMMENT '学生ID', `course_id` INT NOT NULL COMMENT '课程ID', `enroll_date` DATE COMMENT '选课日期', `grade` DECIMAL(5, 2) COMMENT '成绩', PRIMARY KEY (`id`), FOREIGN KEY (`student_id`) REFERENCES `students` (`id`) ON DELETE CASCADE, FOREIGN KEY (`course_id`) REFERENCES `courses` (`id`) ON DELETE CASCADE) COMMENT ='选课记录';

使用步骤如下:

  1. 将你的SQL复制到输入框中
  2. 选择使用SQL转为ER图的模式
  3. 点击生成按钮即可生成对应的ER图

Image

如果对图中的节点不满意,可以对对应的节点名称进行修改,操作步骤如下:

  1. 点击对应的节点
  2. 点击右上角的节点详情
  3. 修改对应的实体名称
  4. 可以查看实体对应的属性,如果需要修改对应的节点属性,请点击对应的节点再去修改实体名称
  5. 可以在右下角给实体新增属性,属性一般使用椭圆来表示

Image

AI智能生成ER图

系统提供根据自然语言来直接生成对应的系统ER图功能,可用于快速完成作业,或者在没有SQL的情况下使用

  1. 在输入框输入对应的系统名称(只需要你想要生成的系统,例如学生管理系统)
  2. 在生成模式中选择使用AI来生成
  3. 点击生成按钮即可生成对应的系统ER图

Image

技术相关(部分后端技术)

1. 解析sql语句

解析步骤

  1. 「预处理SQL语句」:去除SQL语句中的注释和空白字符。
  2. 「关键字检查」:对SQL语句中的关键字进行过滤。
  3. 「SQL语句解析」:使用SQL解析工具解析SQL语句,提取语法树或抽象语法树(AST)。
  4. 「遍历解析结果」:提取表名、字段信息以及字段注释。
  5. 「输出或存储解析结果」:最终将提取的表结构信息输出或存储。

1. 预处理SQL语句

在解析SQL语句之前,过滤SQL中的注释信息,避免它们干扰解析。常见的注释类型包括:

  • 单行注释(-- 或 #)
  • 多行注释(/*...*/)

使用正则表达式将注释部分去除:

String cleanSql = sql        .replace("\r\n\t", " ")        // 去除单行注释以 "--" 开头        .replaceAll("--.*?(\r\n|\n)", "")        // 去除单行注释以 "#" 开头        .replaceAll("#.*?(\r\n|\n)", "")        // 去除多行注释以 "/*...*/" 包含        .replaceAll("/\*.*?\*/", "");

2. 关键字检查(不重要)

在进行SQL解析之前,过滤SQL中的潜在风险,

SqlUtil.filterKeyword(cleanSql);

该方法会遍历SQL语句,检查是否包含在SQL_REGEX中定义的关键字。特殊情况如CREATE TABLE语句会被跳过检查,防止误报。其实这里不用处理,因为没有涉及对本地数据库的操作

3. SQL语句解析

使用SQL解析工具(如SQLUtils.parseStatements)将清理后的SQL语句解析为抽象语法树(AST)。在这里,我们使用了MySqlCreateTableStatement来解析CREATE TABLE语句:

List<SQLStatement> sqlStatements = SQLUtils.parseStatements(cleanSql, DbType.mysql);

解析后的SQL语句被转化为一系列的SQLStatement对象,其中包括各种类型的SQL语句(如CREATE TABLE、INSERT等)。

4. 遍历解析结果

得到SQL语句的抽象语法树后,接下来就是遍历树中的节点,提取所需的信息。我们首先检查当前的SQLStatement是否是CREATE TABLE语句,然后提取表名和字段信息。

for (SQLStatement sqlStatement : sqlStatements) {    if (sqlStatement instanceof MySqlCreateTableStatement) {        MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement;
// 提取表名并去除反引号 String tableName = createTableStatement.getTableName().replaceAll("`", ""); System.out.println("Table Name: " + tableName);
// 提取表注释 String tableComment = tableName; // 默认使用表名 if (createTableStatement.getComment() != null) { SQLExpr commentExpr = createTableStatement.getComment(); if (commentExpr instanceof SQLCharExpr) { tableComment = ((SQLCharExpr) commentExpr).getText().replaceAll("`", ""); } } System.out.println("Table Comment: " + tableComment);

这段代码提取了表名和表注释。若没有指定注释,默认使用表名作为注释。

5. 提取字段信息

在CREATE TABLE语句中,字段信息通常以SQLColumnDefinition的形式出现,我们通过遍历表中的元素列表来提取每个字段的名称和注释。

for (SQLTableElement element : createTableStatement.getTableElementList()) {    if (element instanceof SQLColumnDefinition) {        SQLColumnDefinition columnDefinition = (SQLColumnDefinition) element;
// 提取字段名并去除反引号 String columnName = columnDefinition.getColumnName().replaceAll("`", "");
// 提取字段注释 String columnComment = columnDefinition.getColumnName(); // 默认使用字段名称作为注释 SQLExpr commentExpr = columnDefinition.getComment(); if (commentExpr instanceof SQLCharExpr) { columnComment = ((SQLCharExpr) commentExpr).getText().replaceAll("`", ""); }
// 输出字段信息 System.out.println(" Column Name: " + columnName); System.out.println(" Column Comment: " + columnComment); }}

「完整代码:」

「工具类」

 /**     * sql操作工具类     *     *      */    public class SqlUtil    {        /**         * 定义常用的 sql关键字         */        public static String SQL_REGEX = "and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
/** * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) */ public static String SQL_PATTERN = "[a-zA-Z0-9_\ \,\.]+";
/** * 限制orderBy最大长度 */ private static final int ORDER_BY_MAX_LENGTH = 500;

/** * SQL关键字检查 */ public static void filterKeyword(String value) { if (StringUtils.isEmpty(value)) { return; } String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\|"); for (String sqlKeyword : sqlKeywords) { // 更新:跳过 CREATE TABLE 语句的检查 if (StringUtils.startsWithIgnoreCase(value.trim(), "CREATE TABLE")) { continue; // 如果是 CREATE TABLE 语句,跳过过滤检查 } if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) { throw new UtilException("参数存在SQL注入风险"); } } }

/** * 检查字符,防止注入绕过 */ public static String escapeOrderBySql(String value) { if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { throw new UtilException("参数不符合规范,不能进行查询"); } if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH) { throw new UtilException("参数已超过最大限制,不能进行查询"); } return value; }
/** * 验证 order by 语法是否符合规范 */ public static boolean isValidOrderBySql(String value) { return value.matches(SQL_PATTERN); }

}
  public static void parseMultipleSql(String sql) {            // 去除SQL中的注释            String cleanSql = sql                    .replace("\r\n\t", " ")                    // 去除单行注释以 "--" 开头                    .replaceAll("--.*?(\r\n|\n)", "")                    // 去除单行注释以 "#" 开头                    .replaceAll("#.*?(\r\n|\n)", "")                    // 去除多行注释以 "/*...*/" 包含                    .replaceAll("/\*.*?\*/", "");
// 过滤SQL关键字并解析SQL语句 SqlUtil.filterKeyword(cleanSql); List<SQLStatement> sqlStatements = SQLUtils.parseStatements(cleanSql, DbType.mysql);
// 遍历所有SQL语句 for (SQLStatement sqlStatement : sqlStatements) { // 检查是否为创建表语句 if (sqlStatement instanceof MySqlCreateTableStatement) { MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement;
// 提取表名并去除反引号 String tableName = createTableStatement.getTableName().replaceAll("`", "");
// 提取表注释,若没有注释则使用表名作为默认值 String tableComment = tableName; // 默认使用表名 if (createTableStatement.getComment() != null) { SQLExpr commentExpr = createTableStatement.getComment(); if (commentExpr instanceof SQLCharExpr) { tableComment = ((SQLCharExpr) commentExpr).getText().replaceAll("`", ""); } }
// 输出表信息 System.out.println("Table Name: " + tableName); System.out.println("Table Comment: " + tableComment);
// 遍历表的字段信息 for (SQLTableElement element : createTableStatement.getTableElementList()) { // 检查是否为字段定义 if (element instanceof SQLColumnDefinition) { SQLColumnDefinition columnDefinition = (SQLColumnDefinition) element;
// 提取字段名并去除反引号 String columnName = columnDefinition.getColumnName().replaceAll("`", "");
// 提取字段注释,若没有注释则使用字段名称作为默认值 String columnComment = columnDefinition.getColumnName(); // 默认使用字段名称作为注释 SQLExpr commentExpr = columnDefinition.getComment(); if (commentExpr instanceof SQLCharExpr) { // 如果字段有注释,则提取注释内容 columnComment = ((SQLCharExpr) commentExpr).getText().replaceAll("`", ""); }
// 输出字段信息 System.out.println(" Column Name: " + columnName); System.out.println(" Column Comment: " + columnComment); } } } } }

「测试:」

public static void main(String[] args) throws Exception {            String sql =                    "CREATE TABLE Employee\n" +                            "(\n" +                            "    id            INT PRIMARY KEY comment '主键ID',\n" +                            "    name          VARCHAR(100) comment '姓名'\n" +                            ") comment '员工';\n" +                            "CREATE TABLE Department\n" +                            "(\n" +                            "    id          INT PRIMARY KEY comment '主键ID',\n" +                            "    name        VARCHAR(100) comment '部门名称',\n" +                            "    location    VARCHAR(100) comment '位置'\n" +                            ") comment '部门';\n" +
"CREATE TABLE Project\n" + "(\n" + " id INT PRIMARY KEY comment '主键ID',\n" + " name VARCHAR(100) comment '项目名称',\n" + " budget DECIMAL(10, 2) comment '预算'\n" + ") comment '项目';\n" + "CREATE TABLE Employee_Project\n" + "(\n" + " employee_id INT comment '员工ID',\n" + " project_id INT comment '项目ID',\n" + " hours INT comment '工作小时数',\n" + " PRIMARY KEY (employee_id, project_id)\n" + ") comment '员工-项目关系表';\n";
parseMultipleSql(sql); }

「运行结果:」

Image


采用设计模式ER图转换工具的设计

策略模式(Strategy Pattern)

「概述」

策略模式定义了一系列算法,将每个算法封装起来,并使它们可以互换。这种模式使得算法可以独立于使用它的客户端变化。

  • 接口定义

    ErConversionStrategy

    接口定义了生成ER图的基本方法。


  • public interface ErConversionStrategy {    GraphData generateErDiagram(String userInput) throws Exception;    int key();}
  • 具体策略实现


  • @Componentpublic class SqlErConversionStrategy implements ErConversionStrategy { /* ... */ }
    @Componentpublic class AiErConversionStrategy implements ErConversionStrategy { /* ... */ }
    • SqlErConversionStrategy:基于SQL解析生成ER图。
    • AiErConversionStrategy:利用AI模型生成ER图。
「优点」
  • 「灵活性高」:可以根据不同的业务需求(如不同的转换方式)轻松切换策略。
  • 「易于扩展」:添加新的转换策略,只需实现 ErConversionStrategy 接口,无需修改现有代码。
  • 「单一职责」:每个策略类只负责一种转换方式,职责清晰。
「实现流程」
  1. 「定义接口」:ErConversionStrategy 定义了生成ER图的方法。
  2. 「实现具体策略」:不同的转换方式(如SQL解析、AI生成)通过实现该接口来提供具体的实现。
  3. 「客户端使用」:ErController 通过工厂获取具体的策略并调用其方法生成ER图。

工厂模式(Factory Pattern)

「概述」

工厂模式定义了一个用于创建对象的接口,让子类决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类。

  • 工厂类

    ErConversionStrategyFactory

    负责管理和提供不同的

    ErConversionStrategy

    实例。


  • @Componentpublic class ErConversionStrategyFactory implements InitializingBean {    @Resource    private ApplicationContext context;
    private static final Map<Integer, ErConversionStrategy> strategies = new HashMap<>();
    public static ErConversionStrategy getStrategy(int businessType) { ErConversionStrategy sqlStrategy = strategies.get(businessType); ThrowUtils.throwIf(sqlStrategy == null, new BusinessException("业务类型无效")); return sqlStrategy; }
    @Override public void afterPropertiesSet() throws Exception { Map<String, ErConversionStrategy> beansOfType = context.getBeansOfType(ErConversionStrategy.class); beansOfType.values().forEach(strategy -> strategies.put(strategy.key(), strategy)); }}
  • 「获取策略」:通过 getStrategy 方法,根据业务类型(businessType)获取相应的策略实例。

「优点」
  • 「集中管理」:所有策略实例的创建和管理集中在工厂类中,便于维护和扩展。
  • 「解耦」:客户端(如 ErController)无需知道具体的策略实现,只需通过工厂获取所需策略,降低了耦合度。
  • 「灵活性」:可以根据需要动态添加或移除策略,而不影响客户端代码。
「实现流程」
  1. 「初始化」:ErConversionStrategyFactory 在 Spring 容器初始化时,通过 afterPropertiesSet 方法获取所有 ErConversionStrategy 的实现并存入 strategies 映射。
  2. 「获取策略」:客户端通过调用 getStrategy 方法,传入业务类型,工厂返回相应的策略实例。
  3. 「使用策略」:客户端使用获取到的策略实例执行具体的转换逻辑。

依赖注入(Dependency Injection)

「概述」

依赖注入是一种设计模式,通过将对象的依赖关系从代码中移除,转而通过外部注入,使得系统更加模块化、易于测试和维护。

  • Spring 框架:使用 @Component,@Resource等注解实现依赖注入。


  • @Componentpublic class ErConversionStrategyFactory implements InitializingBean {    @Resource    private ApplicationContext context;    // ...}
    @Componentpublic class AiErConversionStrategy implements ErConversionStrategy { @Resource private ChatLanguageModel chatLanguageModel; @Resource private CurrencyService currencyService; // ...}
    @RestController@RequestMapping("/er")public class ErController { @Resource private ChatLanguageModel chatLanguageModel; // ...}
「优点」
  • 「松耦合」:对象之间的依赖关系由框架管理,减少了类之间的直接依赖。
  • 「易于测试」:可以方便地替换依赖对象为mock对象,进行单元测试。
  • 「模块化」:各个模块之间通过接口和依赖注入进行通信,增强了系统的模块化程度。

单例模式(Singleton Pattern)

「概述」

单例模式确保一个类只有一个实例,并提供一个全局访问点。

  • Spring Bean

    ErConversionStrategyFactory

    作为 Spring 容器中的单例 Bean,确保在整个应用中只有一个工厂实例。


  • @Componentpublic class ErConversionStrategyFactory implements InitializingBean {    // ...}
「优点」
  • 「节省资源」:避免了多次创建工厂实例,节省内存和资源。
  • 「全局访问」:通过工厂的静态方法 getStrategy,可以在任何地方方便地获取策略实例。

通过结合策略模式和工厂模式,ER图转换工具实现了高度的灵活性和可扩展性。依赖注入和单例模式进一步提升了系统的模块化和资源管理效率。这种设计不仅符合面向对象设计原则(如开闭原则和单一职责原则),如果女朋友嫌弃功能不齐全不够好可以直接加呢!这样不会破坏其他的部分,还为可以避免未来吵架呢,哈哈哈。

「整体架构流程」

  1. 「用户请求」:ErController 接收到生成ER图的请求,包含SQL语句和业务类型。
  2. 「获取策略」:ErController 通过 ErConversionStrategyFactory 根据业务类型获取相应的策略实例。
  3. 「执行转换」:策略实例(如 SqlErConversionStrategy 或 AiErConversionStrategy)执行 generateErDiagram 方法,生成ER图数据。
  4. 「返回结果」:ErController 将生成的ER图数据封装到 BaseResponse 中返回给客户端。

最后

由于时间关系这里先介绍这么多,产品还在继续开发中。如有问题希望大家积极反馈哦!!!