Spring Boot + Vue 实现 XML 动态表单:固定字段 + 自由扩展方案

Spring Boot + Vue 实现 XML 动态表单:固定字段 + 自由扩展方案

一、背景与痛点

在实际的企业级项目开发中,经常会遇到这样的需求:

  • 系统核心表有固定的字段(如任务名称、创建人、状态等),这些字段是确定的,不会频繁变化。
  • 但不同业务场景、不同地区、不同客户,往往需要采集额外的扩展信息 (如办理意见、涉及金额、是否紧急等),这些字段不确定、经常变化

常见的应对方式有:

方案 优点 缺点
每次加字段都 ALTER TABLE 加列 简单直接 改库风险大,频繁发版,无法在线配置
用 JSON 存储扩展字段 灵活 不够结构化,查询不便,与某些老旧系统对接困难
建多张 EAV 扩展表 查询灵活 表关联多、SQL 复杂、性能较差

本文介绍一种基于 XML + BLOB 独立扩展表 + 前端动态渲染的方案,核心思路是:

固定字段存在主表中,扩展字段以 XML 格式存储在独立的 BLOB 字段 中,通过一张字段配置表 管理扩展字段的元信息,前端根据配置动态生成表单


二、整体架构设计

复制代码
┌──────────────────────────────────────────────────────────┐
│                      前端 (Vue3 + Element Plus)          │
│  ┌──────────────┐    ┌──────────────┐                    │
│  │扩展字段配置  │    │  任务管理    │                    │
│  │(增删改查)    │    │ (固定+扩展)  │                    │
│  └──────┬───────┘    └──────┬───────┘                    │
│         │                   │                            │
│         │    ┌──────────────┴──────────────┐             │
│         │    │   DynamicForm 动态表单组件  │             │
│         │    │根据字段配置自动渲染表单控件 │             │
│         │    └─────────────────────────────┘             │
└─────────┼────────────────────────────────────────────────┘
          │  REST API
┌─────────┼────────────────────────────────────────────────┐
│         ▼        后端 (Spring Boot + MyBatis)            │
│  ┌───────────────┐    ┌──────────────┐                   │
│  │FieldConfigCtrl│    │TaskController│                   │
│  └──────┬────────┘    └──────┬───────┘                   │
│         │                   │                            │
│  ┌──────┴───────┐    ┌──────┴───────┐                    │
│  │FieldConfigSvc│    │  TaskService │                    │
│  └──────┬───────┘    └──────┬───────┘                    │
│         │                   │                            │
│         │          ┌────────┴────────┐                   │
│         │          │XmlFormConverter │  ← XML ↔ Map 互转 │
│         │          └────────┬────────┘                   │
│  ┌──────┴───────┐    ┌──────┴───────┐                    │
│  │FieldConfigMap│    │TaskMapper +  │                    │
│  └──────┬───────┘    │TaskExtMapper │                    │
│         │            └──────┬───────┘                    │
└─────────┼───────────────────┼────────────────────────────┘
          ▼                   ▼
   ┌──────────────┐    ┌─────────────┐    ┌─────────────┐
   │t_field_config│    │   t_task    │    │ t_task_ext  │
   │ (字段配置表) │    │ (主数据表)  │    │(BLOB扩展表) │
   └──────────────┘    └─────────────┘    └─────────────┘

核心设计思想:

  1. t_task:存储固定字段(任务名称、创建人、状态等),结构稳定不变
  2. t_task_ext :独立扩展表,用 MEDIUMBLOB 字段存储 XML 格式的扩展数据,主从分离
  3. t_field_config:字段配置表,定义每个扩展字段的中文名、类型、是否必填、下拉选项等
  4. XmlFormConverter:核心引擎,负责 XML ↔ 表单数据的双向转换
  5. DynamicForm:Vue 动态表单组件,根据配置自动渲染不同类型的控件

三、数据库设计

3.1 主数据表(固定字段)

sql 复制代码
CREATE TABLE t_task (
    task_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '任务序号',
    task_name VARCHAR(200) NOT NULL COMMENT '任务名称',
    creator VARCHAR(50) COMMENT '创建人',
    create_time DATETIME COMMENT '创建时间',
    task_status TINYINT DEFAULT 0 COMMENT '任务状态 0-待处理 1-处理中 2-已完成',
    district_code VARCHAR(20) COMMENT '所属区域编码',
    update_time DATETIME COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务主数据表(固定字段)';

3.2 扩展数据表(BLOB独立成表)

sql 复制代码
CREATE TABLE t_task_ext (
    ext_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '扩展记录ID',
    task_id BIGINT NOT NULL UNIQUE COMMENT '关联任务ID',
    ext_data MEDIUMBLOB COMMENT 'XML格式的扩展字段数据',
    FOREIGN KEY (task_id) REFERENCES t_task(task_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务扩展数据表(自由扩展字段)';

设计要点:

  • 扩展数据独立成表,主表查询列表时不需要加载 BLOB 大字段,性能友好
  • 使用 MEDIUMBLOB 最大支持 16MB,足够存储大量扩展字段
  • ON DELETE CASCADE 确保主表删除时扩展数据同步清理

3.3 扩展字段配置表

sql 复制代码
CREATE TABLE t_field_config (
    config_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '配置ID',
    field_label VARCHAR(100) NOT NULL COMMENT '字段中文名称(同时作为XML标签和前端Label)',
    field_type VARCHAR(20) DEFAULT 'text' COMMENT '字段类型: text/number/date/select/textarea',
    required TINYINT DEFAULT 0 COMMENT '是否必填 0-否 1-是',
    options TEXT COMMENT '下拉选项JSON数组,如["选项1","选项2"]',
    sort_order INT DEFAULT 0 COMMENT '显示排序',
    status TINYINT DEFAULT 1 COMMENT '状态 0-禁用 1-启用',
    create_time DATETIME COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='扩展字段配置表';

设计要点:

  • field_label 一举三得:既作为字段中文名称显示在页面上,又作为 XML 标签名,同时作为前端表单的 key
  • field_type 支持 text / textarea / number / date / select 五种类型
  • status 字段支持软启用/禁用,禁用后的字段不在表单中显示

3.4 初始化示例数据

sql 复制代码
-- 扩展字段配置
INSERT INTO t_field_config (field_label, field_type, required, options, sort_order, status, create_time) VALUES
('办理意见', 'textarea', 0, NULL, 1, 1, NOW()),
('涉及金额', 'number', 0, NULL, 2, 1, NOW()),
('是否紧急', 'select', 0, '["是","否"]', 3, 1, NOW()),
('预计完成日期', 'date', 0, NULL, 4, 1, NOW()),
('备注信息', 'text', 0, NULL, 5, 1, NOW());

-- 扩展数据(XML存储在BLOB中)
INSERT INTO t_task_ext (task_id, ext_data) VALUES
(1, '<?xml version="1.0" encoding="UTF-8"?>
<extensions>
    <办理意见>同意受理,请尽快安排排查</办理意见>
    <涉及金额>50000</涉及金额>
    <是否紧急>是</是否紧急>
    <预计完成日期>2026-06-30</预计完成日期>
    <备注信息>需协调消防部门联合检查</备注信息>
</extensions>');

四、后端实现

4.1 技术栈

技术 版本 说明
Spring Boot 2.3.12 Web 框架
MyBatis 2.1.4 (starter) ORM 框架
dom4j 1.6.1 XML 解析与生成
Lombok - 简化实体类
MySQL 5.7+ / 8.0 数据库

4.2 项目结构

复制代码
backend/src/main/java/com/xmlform/demo/
├── XmlFormDemoApplication.java        # 启动类
├── common/
│   └── Result.java                     # 统一响应封装
├── controller/
│   ├── TaskController.java             # 任务接口
│   └── FieldConfigController.java      # 字段配置接口
├── engine/
│   └── XmlFormConverter.java           # 核心:XML ↔ 表单转换引擎
├── entity/
│   ├── Task.java                       # 任务实体
│   ├── TaskExt.java                    # 扩展数据实体
│   ├── TaskVO.java                     # 任务视图对象(含扩展字段)
│   └── FieldConfig.java               # 字段配置实体
├── mapper/
│   ├── TaskMapper.java                 # 任务Mapper
│   ├── TaskExtMapper.java              # 扩展数据Mapper
│   └── FieldConfigMapper.java          # 字段配置Mapper
└── service/
    ├── TaskService.java                # 任务业务逻辑
    └── FieldConfigService.java         # 字段配置业务逻辑

4.3 核心引擎:XmlFormConverter

这是整个方案的核心类,负责 XML 与表单数据的双向转换:

java 复制代码
public class XmlFormConverter {

    private static final String ROOT_ELEMENT = "extensions";

    /**
     * XML字符串 → 表单数据Map
     * 将XML中的扩展字段解析为 {字段名: 值} 的Map
     */
    public static Map<String, Object> toFormData(String xml, List<FieldConfig> configs) {
        Map<String, Object> result = new LinkedHashMap<>();
        if (xml == null || xml.trim().isEmpty()) {
            return result;
        }
        try {
            SAXReader reader = new SAXReader();
            Document doc = reader.read(new ByteArrayInputStream(xml.getBytes("UTF-8")));
            Element root = doc.getRootElement();
            for (FieldConfig config : configs) {
                Element el = root.element(config.getFieldLabel());
                if (el != null) {
                    String text = el.getTextTrim();
                    result.put(config.getFieldLabel(), convertType(text, config.getFieldType()));
                } else {
                    result.put(config.getFieldLabel(), null);
                }
            }
        } catch (Exception e) {
            log.error("XML解析失败: {}", e.getMessage());
        }
        return result;
    }

    /**
     * 表单数据Map → XML字符串
     * 将表单提交的数据组装为XML格式
     */
    public static String toXmlString(Map<String, Object> formData, List<FieldConfig> configs) {
        Document doc = DocumentHelper.createDocument();
        Element root = doc.addElement(ROOT_ELEMENT);
        for (FieldConfig config : configs) {
            String label = config.getFieldLabel();
            Object value = formData.get(label);
            Element el = root.addElement(label);
            el.setText(value != null ? String.valueOf(value) : "");
        }
        return doc.asXML();
    }

    /**
     * 类型转换:将XML中的字符串值转为对应的Java类型
     */
    private static Object convertType(String value, String type) {
        if (value == null || value.isEmpty()) return null;
        switch (type) {
            case "number":
                try { return Double.parseDouble(value); } 
                catch (NumberFormatException e) { return value; }
            case "date":
            default:
                return value;
        }
    }
}

转换流程图:

复制代码
保存时:前端表单数据(Map) → toXmlString() → XML字符串 → 存入BLOB
查询时:BLOB → XML字符串 → toFormData() → Map → 返回前端渲染

生成的 XML 示例:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
    <办理意见>同意受理,请尽快安排排查</办理意见>
    <涉及金额>50000</涉及金额>
    <是否紧急>是</是否紧急>
    <预计完成日期>2026-06-30</预计完成日期>
    <备注信息>需协调消防部门联合检查</备注信息>
</extensions>

4.4 任务业务逻辑:TaskService

java 复制代码
@Service
@Slf4j
public class TaskService {

    private final TaskMapper taskMapper;
    private final TaskExtMapper taskExtMapper;
    private final FieldConfigMapper fieldConfigMapper;

    public TaskService(TaskMapper taskMapper, TaskExtMapper taskExtMapper,
                       FieldConfigMapper fieldConfigMapper) {
        this.taskMapper = taskMapper;
        this.taskExtMapper = taskExtMapper;
        this.fieldConfigMapper = fieldConfigMapper;
    }

    /**
     * 查询任务详情(核心:固定字段 + 扩展字段合并)
     */
    @Transactional
    public TaskVO getTaskDetail(Long taskId) {
        Task task = taskMapper.selectByPrimaryKey(taskId);
        if (task == null) return null;

        TaskVO vo = new TaskVO();
        // 拷贝固定字段
        vo.setTaskId(task.getTaskId());
        vo.setTaskName(task.getTaskName());
        vo.setCreator(task.getCreator());
        vo.setCreateTime(task.getCreateTime());
        vo.setTaskStatus(task.getTaskStatus());
        vo.setDistrictCode(task.getDistrictCode());
        vo.setUpdateTime(task.getUpdateTime());

        // 加载扩展字段配置
        List<FieldConfig> configs = fieldConfigMapper.selectActive();
        TaskExt ext = taskExtMapper.selectByTaskId(taskId);

        if (ext != null && ext.getExtData() != null && !configs.isEmpty()) {
            // 将BLOB中的XML解析为表单数据
            Map<String, Object> formData = XmlFormConverter.toFormData(ext.getExtData(), configs);
            List<Map<String, Object>> extFields = new ArrayList<>();
            for (FieldConfig config : configs) {
                Map<String, Object> fieldMap = new LinkedHashMap<>();
                fieldMap.put("fieldLabel", config.getFieldLabel());
                fieldMap.put("fieldType", config.getFieldType());
                fieldMap.put("required", config.getRequired());
                fieldMap.put("options", XmlFormConverter.parseOptions(config.getOptions()));
                fieldMap.put("value", formData.getOrDefault(config.getFieldLabel(), ""));
                extFields.add(fieldMap);
            }
            vo.setExtFields(extFields);
        } else {
            // 无扩展数据时,返回空值模板
            List<Map<String, Object>> extFields = new ArrayList<>();
            for (FieldConfig config : configs) {
                Map<String, Object> fieldMap = new LinkedHashMap<>();
                fieldMap.put("fieldLabel", config.getFieldLabel());
                fieldMap.put("fieldType", config.getFieldType());
                fieldMap.put("required", config.getRequired());
                fieldMap.put("options", XmlFormConverter.parseOptions(config.getOptions()));
                fieldMap.put("value", "");
                extFields.add(fieldMap);
            }
            vo.setExtFields(extFields);
        }

        return vo;
    }

    /**
     * 保存任务(固定字段 + 扩展字段)
     */
    @Transactional
    public void saveTask(Task task, Map<String, Object> extFormData) {
        // 1. 保存主表
        if (task.getTaskId() == null) {
            task.setCreateTime(new Date());
            task.setUpdateTime(new Date());
            taskMapper.insert(task);
        } else {
            task.setUpdateTime(new Date());
            taskMapper.updateByPrimaryKey(task);
        }

        // 2. 将扩展字段转为XML存入BLOB
        List<FieldConfig> configs = fieldConfigMapper.selectActive();
        String xml = XmlFormConverter.toXmlString(extFormData, configs);

        // 3. 保存扩展表
        TaskExt existing = taskExtMapper.selectByTaskId(task.getTaskId());
        if (existing != null) {
            existing.setExtData(xml);
            taskExtMapper.updateByTaskId(existing);
        } else {
            TaskExt ext = new TaskExt();
            ext.setTaskId(task.getTaskId());
            ext.setExtData(xml);
            taskExtMapper.insert(ext);
        }
    }

    @Transactional
    public void deleteTask(Long taskId) {
        taskExtMapper.deleteByTaskId(taskId);
        taskMapper.deleteByPrimaryKey(taskId);
    }
}

关键设计点:

  • 列表查询 listTasks() 只查主表 t_task,不加载 BLOB 大字段,保证列表查询性能
  • 详情查询 getTaskDetail() 才加载扩展数据,将 XML 解析后与字段配置合并返回
  • 保存时在事务内同时操作主表和扩展表,保证数据一致性

4.5 统一响应封装

java 复制代码
@Data
public class Result<T> {
    private int code;
    private String msg;
    private T data;

    public static <T> Result<T> ok(T data) {
        Result<T> r = new Result<>();
        r.setCode(200);
        r.setMsg("success");
        r.setData(data);
        return r;
    }

    public static <T> Result<T> ok() {
        return ok(null);
    }

    public static <T> Result<T> fail(String msg) {
        Result<T> r = new Result<>();
        r.setCode(500);
        r.setMsg(msg);
        return r;
    }
}

4.6 RESTful API 设计

接口 方法 说明
/api/task/list GET 任务列表(不加载扩展数据)
/api/task/detail/{taskId} GET 任务详情(含解析后的扩展字段)
/api/task/save POST 保存任务(含扩展字段)
/api/task/delete/{taskId} DELETE 删除任务
/api/field-config/list GET 所有字段配置
/api/field-config/active GET 启用中的字段配置
/api/field-config/save POST 保存字段配置
/api/field-config/delete/{configId} DELETE 删除字段配置

五、前端实现

5.1 技术栈

技术 版本 说明
Vue 3.x 前端框架
Element Plus - UI 组件库
Axios - HTTP 请求

5.2 前端项目结构

复制代码
frontend/src/
├── App.vue                        # 主布局(Tab切换)
├── api/
│   ├── request.js                 # Axios封装
│   ├── task.js                    # 任务API
│   └── fieldConfig.js             # 字段配置API
├── components/
│   └── DynamicForm.vue            # 核心:动态表单组件
└── views/
    ├── TaskList.vue               # 任务管理页面
    └── FieldConfig.vue            # 扩展字段配置页面

5.3 核心组件:DynamicForm

这是前端的核心组件,根据字段配置动态生成表单控件

vue 复制代码
<template>
  <div class="dynamic-form">
    <el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px">
      <template v-for="field in fields" :key="field.fieldLabel">
        <el-form-item :label="field.fieldLabel" :prop="field.fieldLabel">
          <!-- 文本输入 -->
          <el-input
            v-if="field.fieldType === 'text' || field.fieldType === 'textarea'"
            :type="field.fieldType === 'textarea' ? 'textarea' : 'text'"
            v-model="formData[field.fieldLabel]"
            :placeholder="'请输入' + field.fieldLabel"
          />

          <!-- 数值输入 -->
          <el-input-number
            v-else-if="field.fieldType === 'number'"
            v-model="formData[field.fieldLabel]"
            style="width: 100%"
          />

          <!-- 日期选择 -->
          <el-date-picker
            v-else-if="field.fieldType === 'date'"
            v-model="formData[field.fieldLabel]"
            type="date"
            value-format="YYYY-MM-DD"
            style="width: 100%"
          />

          <!-- 下拉选择 -->
          <el-select
            v-else-if="field.fieldType === 'select'"
            v-model="formData[field.fieldLabel]"
            style="width: 100%"
          >
            <el-option v-for="opt in field.options" :key="opt" :label="opt" :value="opt" />
          </el-select>
        </el-form-item>
      </template>
    </el-form>
  </div>
</template>

<script setup>
import { ref, watch, computed } from 'vue'

const props = defineProps({
  fields: { type: Array, default: () => [] },
  modelValue: { type: Object, default: () => ({}) }
})

const emit = defineEmits(['update:modelValue'])
const formRef = ref(null)
const formData = ref({})

// 根据字段配置动态生成校验规则
const formRules = computed(() => {
  const rules = {}
  props.fields.forEach(field => {
    if (field.required) {
      rules[field.fieldLabel] = [
        { required: true, message: `请输入${field.fieldLabel}`, trigger: 'blur' }
      ]
    }
  })
  return rules
})

// 双向绑定
watch(() => props.modelValue, (val) => {
  if (val) formData.value = { ...val }
}, { immediate: true, deep: true })

watch(() => formData.value, (val) => {
  emit('update:modelValue', val)
}, { deep: true })

// 暴露校验方法给父组件
function validate() {
  return new Promise((resolve) => {
    if (formRef.value) {
      formRef.value.validate((valid) => resolve(valid))
    } else {
      resolve(true)
    }
  })
}

defineExpose({ validate })
</script>

设计要点:

  • 通过 fields prop 接收字段配置数组,动态渲染对应类型的控件
  • 使用 computed 动态生成表单校验规则,必填字段自动添加 required 规则
  • 支持 v-model 双向绑定,父组件可以直接读写表单数据
  • 通过 defineExpose 暴露 validate() 方法,父组件可以触发表单校验

5.4 任务管理页面

任务管理页面将固定字段表单动态扩展字段表单组合在一起:

vue 复制代码
<!-- 新增/编辑对话框 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
  <!-- 固定字段 -->
  <el-divider content-position="left">固定字段</el-divider>
  <el-form :model="taskForm" label-width="100px" :rules="fixedRules" ref="fixedFormRef">
    <el-form-item label="任务名称" prop="taskName">
      <el-input v-model="taskForm.taskName" />
    </el-form-item>
    <!-- ... 其他固定字段 ... -->
  </el-form>

  <!-- 扩展字段(动态渲染) -->
  <el-divider content-position="left">
    扩展字段(XML动态存储)
    <el-tag type="info" size="small">存储于BLOB,列表查询不加载</el-tag>
  </el-divider>
  <DynamicForm
    ref="dynamicFormRef"
    :fields="fieldConfigs"
    v-model="extFormData"
  />

  <template #footer>
    <el-button @click="dialogVisible = false">取消</el-button>
    <el-button type="primary" @click="handleSave">保存</el-button>
  </template>
</el-dialog>

保存逻辑:

javascript 复制代码
async function handleSave() {
  // 分别校验固定字段和扩展字段
  const fixedValid = await fixedFormRef.value.validate().then(() => true).catch(() => false)
  const dynamicValid = await dynamicFormRef.value.validate()
  if (!fixedValid || !dynamicValid) return

  // 将固定字段和扩展字段合并提交
  const payload = {
    ...taskForm.value,
    extFields: { ...extFormData.value }
  }
  await saveTask(payload)
  ElMessage.success('保存成功')
  dialogVisible.value = false
  loadTasks()
}

5.5 字段配置管理页面

字段配置页面允许管理员在线管理扩展字段:

  • 新增字段:输入中文名称、选择类型(text/textarea/number/date/select)、设置是否必填等
  • 编辑字段:修改已有字段的配置
  • 启用/禁用:通过开关控制字段是否在表单中显示
  • 排序:通过排序号控制字段在表单中的显示顺序

六、数据流转全流程

6.1 保存流程

复制代码
用户填写表单
    │
    ▼
前端收集数据:
{
  taskName: "社区安全隐患排查",
  creator: "张三",
  extFields: {
    "办理意见": "同意受理",
    "涉及金额": 50000,
    "是否紧急": "是",
    "预计完成日期": "2026-06-30"
  }
}
    │
    ▼
后端 TaskController.save() 接收
    │
    ├──→ taskMapper.insert/update → t_task 表(固定字段)
    │
    └──→ XmlFormConverter.toXmlString() → 生成 XML
         │
         ▼
    <?xml version="1.0" encoding="UTF-8"?>
    <extensions>
      <办理意见>同意受理</办理意见>
      <涉及金额>50000.0</涉及金额>
      <是否紧急>是</是否紧急>
      <预计完成日期>2026-06-30</预计完成日期>
      <备注信息></备注信息>
    </extensions>
         │
         ▼
    taskExtMapper.insert/update → t_task_ext 表(BLOB)

6.2 查询流程

复制代码
前端请求 GET /api/task/detail/1
    │
    ▼
后端 TaskService.getTaskDetail(1)
    │
    ├──→ taskMapper.selectByPrimaryKey(1) → 主表固定字段
    ├──→ fieldConfigMapper.selectActive() → 字段配置列表
    └──→ taskExtMapper.selectByTaskId(1) → BLOB 中的 XML
         │
         ▼
    XmlFormConverter.toFormData(xml, configs)
         │
         ▼
    返回合并后的 TaskVO:
    {
      taskId: 1,
      taskName: "社区安全隐患排查",
      extFields: [
        { fieldLabel: "办理意见", fieldType: "textarea", value: "同意受理" },
        { fieldLabel: "涉及金额", fieldType: "number", value: 50000 },
        { fieldLabel: "是否紧急", fieldType: "select", value: "是" },
        ...
      ]
    }

七、方案优势总结

7.1 核心优势

优势 说明
零改库扩展 新增业务字段只需在配置表中添加一行记录,无需 ALTER TABLE
列表性能友好 扩展数据独立成表,列表查询不加载 BLOB,避免大字段拖慢查询
在线配置 管理员可在页面上直接增删改扩展字段,无需发版
XML可读性好 扩展数据以 XML 存储,可读性强,便于与外部系统对接
类型安全 支持多种字段类型(文本、数值、日期、下拉),后端自动类型转换
表单校验 前端根据配置动态生成校验规则,必填字段自动校验

7.2 适用场景

  • 政务系统中不同地区/部门有差异化采集需求
  • 工单/任务系统中不同类型任务需要不同的扩展信息
  • 业务字段频繁变化、需要在线配置的场景
  • 需要与外部系统通过 XML 格式交换数据的场景

7.3 局限性

  • 扩展字段不支持 SQL 条件查询(如按扩展字段筛选排序),因为数据在 BLOB 中
  • 字段名(XML标签)使用中文,虽然 dom4j 支持,但在某些场景下可能需要转义处理
  • 扩展数据没有独立的字段级权限控制

八、扩展优化方向

  1. 支持扩展字段条件查询:将高频查询的扩展字段同步到冗余列中
  2. 字段分组:给 t_field_config 增加分组字段,支持多组扩展字段
  3. 字段权限:增加角色-字段的权限控制,不同角色看到不同的扩展字段
  4. 历史版本:记录字段配置的变更历史,保证历史数据的正确解析
  5. 更多字段类型:支持文件上传、富文本、级联选择等复杂控件
  6. XML Schema 校验:使用 XSD 对 XML 数据进行严格的格式校验

九、源码地址

完整项目源码包含:

  • 后端:Spring Boot + MyBatis + dom4j
  • 前端:Vue 3 + Element Plus
  • 数据库建表脚本及示例数据

项目结构清晰,共 14 个 Java 文件、4 个 Vue 文件,代码精简易读,适合作为学习参考或项目脚手架。


如果觉得有帮助,欢迎点赞收藏!有问题可以在评论区交流~

相关推荐
indexsunny2 小时前
互联网大厂Java面试实战:Spring Boot微服务与Kafka消息队列深度解析
java·spring boot·微服务·面试·kafka·消息队列·电商
前端那点事2 小时前
Vue并发控制|几十个请求高效管控(实战方案+可运行代码)
前端·vue.js
前端那点事2 小时前
Vue大批量接口请求优化|告别卡顿、超时!前端落地实战指南
前端·vue.js
2601_949816682 小时前
springcloud springboot nacos版本对应
spring boot·spring·spring cloud
希望永不加班2 小时前
SpringBoot 多级缓存(本地缓存 + Redis)
java·spring boot·redis·后端·缓存
随风,奔跑2 小时前
Spring Boot Alibaba(三)----Sentinel
spring boot·后端·sentinel
空中海2 小时前
第四章:Vue Router
前端·javascript·vue.js
Ruihong2 小时前
你写的是 Vue,跑起来是纯 React?这是什么黑科技
vue.js·react.js·面试
M ? A3 小时前
VuReact 1.6.2 发布,新一代 Vue 3 转 React 编译工具
前端·javascript·vue.js·react.js·面试·开源·vureact