前言
前两天安排了作为Java小码农的我实现一个功能需求。花了两天时间,理解了具体功能业务,并实现了接口。故记录一下此次开发的思路。
1. 功能实现
1.1 效果说明
项目是基于jeecg-boot的前后端分离项目,前端已有一个组件,叫动态表单设计器,演示如下:
可通过拖拽控件个性化设计表单。
实际上表单上的控件内容是通过解析json数据进行渲染的。
点击这个按钮可以获取当前设计表单的json结构数据。
需要实现功能如下:
- 建立一张表,用来存储表单设计器中定义好的结构json数据。
- 通过表单的结构json数据动态(自动)的在数据库中新建该表,字段名为控件名称,字段内容为控件中填入的值。
- 当前端通过某张模板表新建流程后,将该表的结构json返还给前端渲染。
- 当前端在流程中使用该表并编辑数据保存后,能够将表中的字段数据存入数据库对应生成的动态表中。
- 当前端在流程中想要查看该表的实时内容时,调用接口将动态生成的表的字段以及值数据以json格式返还给前端做渲染。
1.2 功能流程图
- 表单设计器生成表单
- 选择表单模板创建流程
3.进入流程查看表单历史数据
1.3 具体后端实现
-
在数据库中新增了关于表单设计器的数据库表
在后端java代码中生成sys_custom_form_json表的entity,mapper,service,controller通用CRUD全家桶。
-
业务改造,研究了动态生成表单的实现(动态生成数据库表极为关键)
具体思路:在controller中解析表单结构json字符串,获取到需要动态创建的表名,字段名,调用mapper中自定义的create table语句,将tableName(表名),tableField(字段名)用#{}或者${}填入mapper.xml文件中,mybatis-plus执行sql语句,生成数据库表。
实现如下:
// Controller层
/**
* 添加
*
* @param sysCustomFormJsonObject
* @return
*/
@PostMapping(value = "/add")
public Result<?> add(@RequestBody JSONObject sysCustomFormJsonObject) {
//获取json数据中的tableName
/**
* json结构:
* {
* "config": {
* "tableName": "XXXX", <--------需要获取
* "layout": "horizontal",
* },
* "list": [{},{}]
* }
*/
Map config = (Map)sysCustomFormJsonObject.get("config");
//给tableName添加前缀,标识为自定义
String tableName = "dynamic_" + config.get("tableName");
SysCustomFormJson sysCustomFormJson = new SysCustomFormJson();
sysCustomFormJson.setFormName(tableName);
//将table的数据存入数据库
sysCustomFormJson.setFormJson(sysCustomFormJsonObject.toString());
sysCustomFormJsonService.save(sysCustomFormJson);
//解析json中的控件信息,并存入tableFilelds,这里使用IdentityHashMap可以存储重复type的控件名
Map<String, String> tableFields = new IdentityHashMap<>();
List joArray = (List)sysCustomFormJsonObject.get("list");
for (int i = 0; i < joArray.size(); i++) {
Map<String, String> map = (Map)joArray.get(i);
tableFields.put(map.get("type"), map.get("key"));
}
sysCustomFormJsonService.createAutoTask(tableName,tableFields);
return Result.ok("添加成功!");
}
/** DynamicTableMapper.xml */
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.system.mapper.DynamicTableMapper">
<update id="createAutoTask" parameterType="map">
create table `${tableName}` (
`id` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键ID',
<foreach collection="tableFields" index="key" item="_value">
<choose>
<when test=" key == 'number' or key == 'rate'">
`${_value}` int NULL DEFAULT NULL COMMENT '数字输入框',
</when>
<when test=" key == 'radio' or key == 'switch' ">
`${_value}` int NULL DEFAULT NULL COMMENT '单选框',
</when>
<when test=" key == 'date' or key == 'time'">
`${_value}` DATETIME NULL DEFAULT NULL COMMENT '日期选择器',
</when>
<otherwise>
`${_value}` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '控件',
</otherwise>
</choose>
</foreach>
`processId` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '绑定的流程ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
</update>
</mapper>
- 功能:当前端开启流程并选择某个设计好的表单时,存储表单内数据到动态生成的数据库表。
实现:
Controller层:
/**
* 添加动态table的字段数据
*
* @param jsonObject
* @return
*/
@PostMapping(value = "/addTableData")
public Result<?> addTableData(@RequestBody JSONObject jsonObject) {
// 将tableName存入tableData中
String tableName = "" + jsonObject.get("tableName");
Map<String, Object> tableData = new HashMap<>();
tableData.put("tableName",tableName);
// 自动生成32位UUID存入表的字段映射中,确保insert操作
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
Map<String, String> tableFieldMap = new HashMap();
tableFieldMap.put("id", uuid);
// processId, 将processId存入表的字段映射中
String processId = (String)jsonObject.get("processId");
tableFieldMap.put("processId",processId);
// 组装字段数据到字段映射中
Map<String, String> map = (Map)jsonObject.get("tableFiled");
tableFieldMap.putAll(map);
// 再将table的字段映射map存入tableData中
tableData.put("tableFieldMap",tableFieldMap);
sysCustomFormJsonService.addTableData(tableData);
return Result.ok("添加成功!");
}
Service层:
package org.jeecg.modules.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.system.entity.SysCustomFormJson;
import java.util.Map;
/**
* @Description: 自定义form表
* @Author: kmlc
* @Date: 2020-04-14
* @Version: V1.0
*/
public interface ISysCustomFormJsonService extends IService<SysCustomFormJson> {
/**
* 根据tableName,控件field自动创建数据库表
* @param tableName
* @param tableFields
*/
void createAutoTask(String tableName, Map<String, String> tableFields);
/**
* 根据tableName,processId查询数据
* @param processId
* @param tableName
* @return
*/
Map<String, Object> queryTableByProcessId(String processId, String tableName);
/**
* 动态添加自定义表单填入的数据。
* @param tableData Map<String, Object>
*/
void addTableData(Map<String, Object> tableData);
/**
* 根据tableName删除动态生成的表
* @param tableName
*/
void removeByTableName(String tableName);
}
Mapper.xml:
<insert id="addTableData" parameterType="map">
insert into ${tableName}
(
<foreach collection="tableFieldMap" index="key" item="_value" separator=",">
`${key}`
</foreach>)
values (
<foreach collection="tableFieldMap" index="key" item="_value" separator=",">
#{_value}
</foreach>)
</insert>
- 功能,当点击某个流程时,返回表单结构,表单数据。
实现:前端点击流程时
(1) 通过table的ID调用接口获取到表单结构json字符串。
(2) 通过流程ID和tableName调用接口获取到表单数据。
Controller:
/**
* 通过id查询
*
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<?> queryById(@RequestParam(name="id",required=true) String id) {
SysCustomFormJson sysCustomFormJson = sysCustomFormJsonService.getById(id);
return Result.ok(sysCustomFormJson);
}
/**
* 通过processId和tableName查询动态生成的表单数据
*
* @param processId
* @param tableName
* @return
*/
@GetMapping(value = "/queryTableDataByIdAndTableName")
public Result<?> queryById(@RequestParam(name="processId",required=true) String processId,
@RequestParam(name="tableName",required=true) String tableName) {
Map<String, Object> map = sysCustomFormJsonService.queryTableByProcessId(processId, tableName);
//如果只取出一条数据才返回获取成功,否则返回500
if(map.size() == 1){
for (String key : map.keySet()) {
Object value = map.get(key);
return Result.ok(value);
}
}
return Result.error(500,"获取失败,因为存在多条相同记录或者不存在该记录。");
}
Mapper.xml
<select id="queryTableByProcessId" parameterType="map" resultType="java.util.Map">
select * from ${tableName} where ${tableName}.processId = #{processId}
</select>
1.4 实现效果
在数据库中动态生成了表。
dynamic_test1_table | dynamic_test3_table |
---|---|
可以看到,能够进行个性化自动生成数据库表。
调用查询接口可以动态查询到数据:
- GET请求参数查询表单的json数据
- GET请求参数查询dynamic_text1_table:
- GET请求参数查询dynamic_text3_table:
2. 尾声
获取到结果以后,之后的渲染等操作就交给前端开发人员解决,深藏功与名。