业务主表+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赋值表单即可
相关推荐
代码改善世界1 小时前
【C++进阶】二叉搜索树
java·数据结构·c++
雨落在了我的手上1 小时前
初识java(六):方法的使用
java·开发语言
张敬之、1 小时前
sa-token
java
_Evan_Yao2 小时前
从“全量发布”到“小步快跑”:灰度发布的简单实践与学习路径
java·后端·学习
想带你从多云到转晴2 小时前
优选算法---双指针
java·算法
闲适达人2 小时前
nginx传递url的获取方案
java·服务器·前端
折哥的程序人生 · 物流技术专研2 小时前
《Java 100 天进阶之路》第21篇:Java Object类
java·开发语言·后端·面试·哈希算法
吕永强2 小时前
基于SpringBoot+Vue宠物领养系统的设计与实现(源码+论文+部署)
spring boot·毕业设计·毕业论文·宠物领养·宠物领养系统
27669582922 小时前
阿里图像修复验证码自动化分析
java·前端·自动化·阿里滑块·drssionpage·阿里图像修复验证码·阿里图像复原