业务主表+JSON自定义字段

SpringBoot 单据自定义字段实现

方案:业务主表+JSON自定义字段,元数据统一配置,无冗余大表,高性能,适配多厂家动态字段

一、数据库SQL脚本

1. 自定义字段元数据表(全局配置小表)

sql 复制代码
CREATE TABLE sys_biz_custom_field (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    biz_table VARCHAR(64) NOT NULL COMMENT '业务表名 如mes_production_order',
    factory_code VARCHAR(64) DEFAULT '' COMMENT '厂家编码,空为全局通用',
    field_key VARCHAR(64) NOT NULL COMMENT '自定义字段唯一标识',
    field_name VARCHAR(100) NOT NULL COMMENT '字段展示名称',
    field_type VARCHAR(32) NOT NULL COMMENT '字段类型:text/number/date/select/textarea',
    dict_type VARCHAR(64) DEFAULT '' COMMENT '若依字典类型',
    field_default VARCHAR(500) DEFAULT '' COMMENT '默认值',
    is_required CHAR(1) DEFAULT '0' COMMENT '是否必填 0否1是',
    sort_num INT DEFAULT 0 COMMENT '排序号',
    status CHAR(1) DEFAULT '0' COMMENT '状态0正常1停用',
    remark VARCHAR(500) DEFAULT '' COMMENT '备注',
    create_by VARCHAR(64) DEFAULT '',
    create_time DATETIME DEFAULT NULL,
    update_by VARCHAR(64) DEFAULT '',
    update_time DATETIME DEFAULT NULL,
    del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志',
    PRIMARY KEY (id),
    UNIQUE INDEX idx_table_factory_key (biz_table,factory_code,field_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='业务自定义字段元数据表';

2. MES生产工单主表(新增JSON自定义字段)

sql 复制代码
CREATE TABLE mes_production_order (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
    order_no VARCHAR(50) NOT NULL COMMENT '工单编号',
    product_id BIGINT NOT NULL COMMENT '产品ID',
    product_name VARCHAR(100) DEFAULT '' COMMENT '产品名称',
    plan_num DECIMAL(10,2) DEFAULT 0 COMMENT '计划生产数量',
    finish_num DECIMAL(10,2) DEFAULT 0 COMMENT '已完成数量',
    factory_code VARCHAR(64) DEFAULT '' COMMENT '所属厂家编码',
    order_status TINYINT DEFAULT 0 COMMENT '工单状态',
    start_time DATETIME DEFAULT NULL COMMENT '计划开工时间',
    end_time DATETIME DEFAULT NULL COMMENT '计划完工时间',
    -- 核心JSON自定义字段
    custom_field_json JSON NULL COMMENT '动态自定义字段集合',
    create_by VARCHAR(64) DEFAULT '',
    create_time DATETIME DEFAULT NULL,
    update_by VARCHAR(64) DEFAULT '',
    update_time DATETIME DEFAULT NULL,
    del_flag CHAR(1) DEFAULT '0',
    PRIMARY KEY (id),
    INDEX idx_factory_code (factory_code),
    INDEX idx_order_no (order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES生产工单主表';

二、Maven依赖(若依已内置,确认即可)

xml 复制代码
<!-- JSON类型处理器 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- fastjson2 序列化 -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
</dependency>

三、后端实体类

1. 自定义字段元数据实体 SysBizCustomField.java

java 复制代码
package com.ruoyi.mes.domain;

import com.ruoyi.common.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("sys_biz_custom_field")
public class SysBizCustomField extends BaseEntity {

    private Long id;

    /** 业务表名 */
    private String bizTable;

    /** 厂家编码 */
    private String factoryCode;

    /** 字段标识 */
    private String fieldKey;

    /** 字段名称 */
    private String fieldName;

    /** 字段类型 */
    private String fieldType;

    /** 字典类型 */
    private String dictType;

    /** 默认值 */
    private String fieldDefault;

    /** 是否必填 */
    private String isRequired;

    /** 排序 */
    private Integer sortNum;

    /** 状态 */
    private String status;

    /** 备注 */
    private String remark;
}

2. 生产工单实体 MesProductionOrder.java

java 复制代码
package com.ruoyi.mes.domain;

import com.alibaba.fastjson2.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;

@Data
@TableName("mes_production_order")
public class MesProductionOrder extends BaseEntity {

    private Long id;

    /** 工单编号 */
    private String orderNo;

    /** 产品ID */
    private Long productId;

    /** 产品名称 */
    private String productName;

    /** 计划数量 */
    private BigDecimal planNum;

    /** 完成数量 */
    private BigDecimal finishNum;

    /** 厂家编码 */
    private String factoryCode;

    /** 工单状态 */
    private Integer orderStatus;

    /** 开工时间 */
    private Date startTime;

    /** 完工时间 */
    private Date endTime;

    /** JSON自定义字段 */
    @TableField(typeHandler = com.ruoyi.common.handler.MapTypeHandler.class)
    private Map<String, Object> customFieldJson;

    /** 非数据库字段:前端接收自定义字段专用 */
    @TableField(exist = false)
    @JSONField(serialize = false)
    private Map<String, Object> customFieldMap;
}

3. 全局JSON Map类型处理器 MapTypeHandler.java

java 复制代码
package com.ruoyi.common.handler;

import com.alibaba.fastjson2.JSON;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

@MappedTypes(Map.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class MapTypeHandler extends BaseTypeHandler<Map<String,Object>> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Map<String, Object> parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, JSON.toJSONString(parameter));
    }

    @Override
    public Map<String, Object> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return strToMap(rs.getString(columnName));
    }

    @Override
    public Map<String, Object> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return strToMap(rs.getString(columnIndex));
    }

    @Override
    public Map<String, Object> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return strToMap(cs.getString(columnIndex));
    }

    private Map<String,Object> strToMap(String str){
        if(str == null || str.isEmpty()){
            return new HashMap<>();
        }
        return JSON.parseObject(str, Map.class);
    }
}

四、Mapper层

1. 自定义字段元数据 Mapper

java 复制代码
package com.ruoyi.mes.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.mes.domain.SysBizCustomField;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface SysBizCustomFieldMapper extends BaseMapper<SysBizCustomField> {

    /**
     * 根据表名+厂家查询可用自定义字段
     */
    List<SysBizCustomField> selectFieldList(@Param("bizTable") String bizTable, @Param("factoryCode") String factoryCode);
}

2. 生产工单 Mapper

java 复制代码
package com.ruoyi.mes.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.mes.domain.MesProductionOrder;

public interface MesProductionOrderMapper extends BaseMapper<MesProductionOrder> {
}

五、Service层

1. 自定义字段Service

java 复制代码
package com.ruoyi.mes.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.mes.domain.SysBizCustomField;
import java.util.List;

public interface ISysBizCustomFieldService extends IService<SysBizCustomField> {

    List<SysBizCustomField> getFieldByTableAndFactory(String bizTable, String factoryCode);
}
java 复制代码
package com.ruoyi.mes.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.mes.domain.SysBizCustomField;
import com.ruoyi.mes.mapper.SysBizCustomFieldMapper;
import com.ruoyi.mes.service.ISysBizCustomFieldService;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class SysBizCustomFieldServiceImpl extends ServiceImpl<SysBizCustomFieldMapper, SysBizCustomField>
        implements ISysBizCustomFieldService {

    @Override
    public List<SysBizCustomField> getFieldByTableAndFactory(String bizTable, String factoryCode) {
        return baseMapper.selectFieldList(bizTable,factoryCode);
    }
}

2. 生产工单Service(核心:自动赋值JSON自定义字段)

java 复制代码
package com.ruoyi.mes.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.mes.domain.MesProductionOrder;

public interface IMesProductionOrderService extends IService<MesProductionOrder> {

    /**
     * 保存工单+自动封装自定义字段到JSON
     */
    boolean saveOrderWithCustomField(MesProductionOrder order);

    /**
     * 修改工单+更新自定义字段
     */
    boolean updateOrderWithCustomField(MesProductionOrder order);
}
java 复制代码
package com.ruoyi.mes.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.mes.domain.MesProductionOrder;
import com.ruoyi.mes.mapper.MesProductionOrderMapper;
import com.ruoyi.mes.service.IMesProductionOrderService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;

@Service
public class MesProductionOrderServiceImpl extends ServiceImpl<MesProductionOrderMapper, MesProductionOrder>
        implements IMesProductionOrderService {

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrderWithCustomField(MesProductionOrder order) {
        // 将前端传入的自定义字段Map 赋值给JSON字段
        Map<String,Object> customMap = order.getCustomFieldMap();
        if(customMap == null){
            customMap = new HashMap<>();
        }
        order.setCustomFieldJson(customMap);
        // 清空临时字段,避免冗余
        order.setCustomFieldMap(null);
        return this.save(order);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateOrderWithCustomField(MesProductionOrder order) {
        Map<String,Object> customMap = order.getCustomFieldMap();
        if(customMap == null){
            customMap = new HashMap<>();
        }
        order.setCustomFieldJson(customMap);
        order.setCustomFieldMap(null);
        return this.updateById(order);
    }
}

六、Controller层

1. 自定义字段配置控制器

java 复制代码
package com.ruoyi.mes.controller;

import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.mes.domain.SysBizCustomField;
import com.ruoyi.mes.service.ISysBizCustomFieldService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/mes/custom/field")
public class SysBizCustomFieldController extends BaseController {

    @Autowired
    private ISysBizCustomFieldService sysBizCustomFieldService;

    /**
     * 获取单据对应厂家的所有自定义字段配置
     */
    @GetMapping("/list")
    public AjaxResult getCustomFieldList(@RequestParam String bizTable,@RequestParam String factoryCode){
        List<SysBizCustomField> list = sysBizCustomFieldService.getFieldByTableAndFactory(bizTable,factoryCode);
        return AjaxResult.success(list);
    }

    /**
     * 新增自定义字段配置
     */
    @PostMapping
    public AjaxResult add(@RequestBody SysBizCustomField field){
        return toAjax(sysBizCustomFieldService.save(field));
    }

    /**
     * 修改自定义字段配置
     */
    @PutMapping
    public AjaxResult edit(@RequestBody SysBizCustomField field){
        return toAjax(sysBizCustomFieldService.updateById(field));
    }

    /**
     * 删除字段配置
     */
    @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable Long[] ids){
        return toAjax(sysBizCustomFieldService.removeByIds(List.of(ids)));
    }
}

2. 生产工单控制器

java 复制代码
package com.ruoyi.mes.controller;

import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.mes.domain.MesProductionOrder;
import com.ruoyi.mes.service.IMesProductionOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/mes/prod/order")
public class MesProductionOrderController extends BaseController {

    @Autowired
    private IMesProductionOrderService mesProductionOrderService;

    /**
     * 工单列表查询
     */
    @GetMapping("/list")
    public TableDataInfo list(MesProductionOrder order){
        startPage();
        List<MesProductionOrder> list = mesProductionOrderService.list();
        return getDataTable(list);
    }

    /**
     * 获取工单详情(自带JSON自定义字段)
     */
    @GetMapping("/{id}")
    public AjaxResult getInfo(@PathVariable Long id){
        return AjaxResult.success(mesProductionOrderService.getById(id));
    }

    /**
     * 新增工单(携带自定义字段)
     */
    @PostMapping
    public AjaxResult add(@RequestBody MesProductionOrder order){
        return toAjax(mesProductionOrderService.saveOrderWithCustomField(order));
    }

    /**
     * 修改工单
     */
    @PutMapping
    public AjaxResult edit(@RequestBody MesProductionOrder order){
        return toAjax(mesProductionOrderService.updateOrderWithCustomField(order));
    }

    /**
     * 删除工单
     */
    @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable Long[] ids){
        return toAjax(mesProductionOrderService.removeByIds(List.of(ids)));
    }
}

七、XML查询语句(SysBizCustomFieldMapper.xml)

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="com.ruoyi.mes.mapper.SysBizCustomFieldMapper">

    <select id="selectFieldList" resultType="com.ruoyi.mes.domain.SysBizCustomField">
        SELECT * FROM sys_biz_custom_field
        WHERE del_flag = '0' AND status = '0'
        AND biz_table = #{bizTable}
        AND (factory_code = #{factoryCode} OR factory_code = '')
        ORDER BY sort_num ASC
    </select>

</mapper>

八、核心特性说明

  1. 无大表压力:所有自定义字段内嵌主表JSON字段,不分通用大表
  2. 多厂家隔离 :通过factory_code区分不同厂商单据字段配置
  3. 零改表结构:前端配置字段,无需重启服务、无需改实体
  4. 若依原生兼容:字典、分页、权限、导出全部原生支持
  5. 查询高效:单表查询,无联表,千万级数据无性能瓶颈
  6. 前端适配简单 :通过/mes/custom/field/list接口拉取配置动态渲染表单

九、前端对接要点

  1. 新增/编辑页先调用字段配置接口,根据当前厂家+表名拿到所有自定义字段
  2. 动态渲染输入框、日期、下拉字典组件
  3. 表单自定义数据统一存入customFieldMap
  4. 提交后端自动转JSON存入custom_field_json字段
  5. 回显直接读取实体中customFieldJson赋值表单即可
相关推荐
人道领域15 分钟前
【LeetCode刷题日记】131.分割回文串,动态规划优化
java·开发语言·leetcode
z落落27 分钟前
C# 接口 interface (多接口实现、类+接口、成员重名)
java·开发语言
发际线向北32 分钟前
0x05 深入了解JVM虚拟机(JVM方法调用 -Ⅰ)
java
宋哥转AI36 分钟前
学了Spring AI Graph再看LangGraph,发现API几乎一模一样
java·人工智能·agent
AskHarries1 小时前
Workspace:文件系统、项目上下文和执行边界
java·服务器·前端
摇滚侠1 小时前
JavaWeb 全套教程 Servlet 66-74
java·servlet·tomcat·intellij-idea·jar
Solis程序员1 小时前
滑动窗口热键探测与三级缓存设计
java·spring·缓存
好家伙VCC1 小时前
区块链双向支付通道实战:从签名到结算
java·后端·区块链·asp.net
ss2732 小时前
【入门OJ题解】分苹果问题(Python/Java/C 实现)
java·c语言·python
weikecms2 小时前
美团霸王餐报名API接口
java·开发语言