🔮 前言:低代码的本质是“元数据”
市面上的低代码平台(如宜搭、简道云)看似眼花缭乱,核心逻辑其实只有一条:
UI 即数据,数据即代码。
- 设计态(Design Time):前端拖拽组件,生成一份 JSON 配置(Schema)。
- 运行态(Runtime):前端根据 JSON 渲染页面,后端根据 JSON 生成 SQL。
我们不需要为每个表单写 Java 类,我们需要的是一个**“通用的解释引擎”**。
🏗️ 一、 系统架构:模型驱动设计
我们的目标是实现:用户在左边拖一个“输入框”,右边数据库就自动建一个VARCHAR字段,并且POST /api/data/{modelId}接口立马生效。
架构数据流 (Mermaid):
🎨 二、 前端实战:Vue3 + SortableJS 实现可视化设计器
前端的核心在于**“拖拽”和“Schema 生成”**。
我们使用vuedraggable(基于 SortableJS) 来实现拖拽,维护一个响应式的widgetList数组。
1. 定义数据结构 (Schema)
这是低代码的灵魂。一个简单的表单配置可能长这样:
{"modelId":"order_table","fields":[{"type":"input","label":"商品名称","key":"product_name","rules":[{"required":true,"message":"必填"}]},{"type":"number","label":"价格","key":"price"}]}2. 设计器核心代码
利用 Vue 3 的<component :is="...">动态组件来渲染左侧的物料区和中间的画布区。
<template><divclass="designer-container"><draggable:list="sourceWidgets":group="{ name: 'widgets', pull: 'clone', put: false }":sort="false"><template#item="{ element }"><divclass="widget-item">{{ element.label }}</div></template></draggable><draggablev-model="formConf.fields"group="widgets"class="drawing-board"><template#item="{ element }"><render-widget:conf="element"/></template></draggable></div></template>⚙️ 三、 后端实战:万能的通用接口
后端最难的是:没有 Entity,没有 Mapper,怎么操作数据库?
传统的 ORM (Hibernate/MyBatis) 失效了。我们需要回归 JDBC 或者使用 MyBatis 的动态 SQL 能力。
1. 动态 DDL (建表)
当用户点击“发布”时,后端解析 JSON,生成CREATE TABLE语句。
@ServicepublicclassModelService{@AutowiredprivateJdbcTemplatejdbcTemplate;publicvoidcreateTable(ModelSchemaschema){StringBuildersql=newStringBuilder("CREATE TABLE ");sql.append(schema.getModelId()).append(" (");sql.append("id BIGINT PRIMARY KEY AUTO_INCREMENT, ");for(Fieldfield:schema.getFields()){sql.append(field.getKey()).append(" ");// 类型映射:JSON type -> SQL typeswitch(field.getType()){case"input":sql.append("VARCHAR(255)");break;case"number":sql.append("DECIMAL(10,2)");break;case"date":sql.append("DATETIME");break;// ...}sql.append(", ");}// SaaS 核心:加上租户IDsql.append("tenant_id VARCHAR(32)");sql.append(")");jdbcTemplate.execute(sql.toString());}}2. 动态 CRUD (万能 Controller)
我们不需要为每个表写 Controller,只需要一个接收modelId的接口。
@RestController@RequestMapping("/api/dynamic")publicclassDynamicDataController{@AutowiredprivateDynamicServicedynamicService;// 插入数据:POST /api/dynamic/{modelId}@PostMapping("/{modelId}")publicResultsave(@PathVariableStringmodelId,@RequestBodyMap<String,Object>data){// 1. 获取当前租户上下文StringtenantId=UserContext.getTenantId();data.put("tenant_id",tenantId);// 2. 动态插入dynamicService.insert(modelId,data);returnResult.ok();}// 查询列表:GET /api/dynamic/{modelId}@GetMapping("/{modelId}")publicResult<List<Map<String,Object>>>list(@PathVariableStringmodelId){returnResult.ok(dynamicService.queryList(modelId));}}DynamicService 的实现技巧:
使用 MyBatis 的<foreach>标签或者 JDBC 的SimpleJdbcInsert来处理不确定的Map数据。
🏢 四、 SaaS 化的关键:多租户隔离
既然是 SaaS 平台,数据隔离是红线。
我们采用共享数据库,独立 Schema或共享 Schema,字段隔离的策略。
考虑到低代码平台的表结构是动态生成的,字段隔离 (tenant_id)是最灵活的。
实现方案:
利用MyBatis-Plus 的多租户插件或者手写拦截器,在执行所有 SQL 时自动拼接WHERE tenant_id = ?。
// MyBatis-Plus 租户拦截器示例@ComponentpublicclassTenantLineHandlerImplimplementsTenantLineHandler{@OverridepublicExpressiongetTenantId(){// 从 ThreadLocal 获取当前登录用户的租户IDreturnnewStringValue(UserContext.getTenantId());}@OverridepublicStringgetTenantIdColumn(){return"tenant_id";}@OverridepublicbooleanignoreTable(StringtableName){// 系统表不需要隔离return"sys_user".equals(tableName);}}🎯 总结与展望
通过以上三步,我们构建了一个低代码平台的最小可行性产品 (MVP):
- Vue3负责生产“图纸” (JSON Schema)。
- Spring Boot负责根据“图纸”施工 (DDL) 和 运营 (CRUD)。
- SaaS 隔离保证了不同租户互不干扰。
进阶方向:
- 逻辑编排 (Logic Flow):单纯的表单不够用,需要通过连线图编排业务逻辑(如:提交表单 -> 发送邮件 -> 更新库存)。
- 代码生成器:对于复杂的二开需求,支持将 JSON 逆向生成
.java和.vue源码供开发者下载。
Next Step:
尝试写一个简单的DynamicController,用 Postman 往一个你根本没创建过 Entity 的表里插入数据,那感觉就像变魔术一样!