You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于JSON Schema自动生成CRUD UI与MongoDB关联REST API的方案问询

基于React+Node.js的JSON Schema驱动MongoDB CRUD方案

后端:Node.js + Mongoose 自动生成REST API

1. 从JSON Schema生成Mongoose模型

先安装核心依赖:

npm install mongoose @json-schema-tools/mongoose-schema express cors

定义AudioInputDevices的JSON Schema(建议放在前后端共享目录,避免重复维护):

// shared/schemas/AudioInputDevices.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "title": "AudioInputDevice",
  "required": ["name", "deviceId", "sampleRate"],
  "properties": {
    "name": { "type": "string", "description": "设备名称" },
    "deviceId": { "type": "string", "description": "系统设备ID" },
    "sampleRate": { "type": "integer", "minimum": 8000, "maximum": 192000 },
    "isActive": { "type": "boolean", "default": true },
    "description": { "type": "string" }
  }
}

写工具函数将JSON Schema转成Mongoose模型:

// backend/utils/schemaToModel.js
const mongoose = require('mongoose');
const { convert } = require('@json-schema-tools/mongoose-schema');

function createModel(schemaJson, modelName) {
  const mongooseSchema = convert(schemaJson);
  // 自动添加创建/更新时间戳
  mongooseSchema.set('timestamps', true);
  return mongoose.model(modelName, mongooseSchema);
}

module.exports = createModel;

2. 通用CRUD路由生成

封装通用控制器,处理所有模型的增删改查逻辑:

// backend/controllers/crudController.js
function createCrudController(model) {
  return {
    // 获取列表(支持分页、基础过滤)
    getAll: async (req, res) => {
      try {
        const { page = 1, limit = 10, ...filters } = req.query;
        const query = model.find(filters);
        const data = await query.skip((page - 1) * limit).limit(parseInt(limit));
        const total = await model.countDocuments(filters);
        res.json({ data, total, page: parseInt(page), limit: parseInt(limit) });
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    },
    // 获取单个文档
    getOne: async (req, res) => {
      try {
        const doc = await model.findById(req.params.id);
        if (!doc) return res.status(404).json({ error: '文档不存在' });
        res.json(doc);
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    },
    // 创建文档
    create: async (req, res) => {
      try {
        const doc = await model.create(req.body);
        res.status(201).json(doc);
      } catch (err) {
        res.status(400).json({ error: err.message });
      }
    },
    // 更新文档
    update: async (req, res) => {
      try {
        const doc = await model.findByIdAndUpdate(req.params.id, req.body, {
          new: true,
          runValidators: true // 启用Schema验证
        });
        if (!doc) return res.status(404).json({ error: '文档不存在' });
        res.json(doc);
      } catch (err) {
        res.status(400).json({ error: err.message });
      }
    },
    // 删除文档
    delete: async (req, res) => {
      try {
        const doc = await model.findByIdAndDelete(req.params.id);
        if (!doc) return res.status(404).json({ error: '文档不存在' });
        res.json({ message: '删除成功' });
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    }
  };
}

module.exports = createCrudController;

在Express中注册路由:

// backend/server.js
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const createModel = require('./utils/schemaToModel');
const createCrudController = require('./controllers/crudController');
const audioDeviceSchema = require('../shared/schemas/AudioInputDevices.json');

const app = express();
const PORT = process.env.PORT || 3001;

// 连接MongoDB
mongoose.connect('mongodb://localhost:27017/audio-devices-db')
  .then(() => console.log('MongoDB连接成功'))
  .catch(err => console.error('MongoDB连接失败', err));

app.use(cors());
app.use(express.json());

// 生成AudioInputDevices的模型与路由
const AudioDevice = createModel(audioDeviceSchema, 'AudioInputDevice');
const audioDeviceController = createCrudController(AudioDevice);

app.get('/api/audio-input-devices', audioDeviceController.getAll);
app.get('/api/audio-input-devices/:id', audioDeviceController.getOne);
app.post('/api/audio-input-devices', audioDeviceController.create);
app.put('/api/audio-input-devices/:id', audioDeviceController.update);
app.delete('/api/audio-input-devices/:id', audioDeviceController.delete);

app.listen(PORT, () => console.log(`服务运行在端口 ${PORT}`));

前端:React 自动生成CRUD UI

1. 依赖安装

npm install @rjsf/core @rjsf/utils react-table axios

2. 自动生成表单(新增/编辑)

基于JSON Schema生成表单,复用后端的Schema:

// src/components/AudioDeviceForm.js
import React, { useState, useEffect } from 'react';
import { Form } from '@rjsf/core';
import axios from 'axios';
import audioDeviceSchema from '../shared/schemas/AudioInputDevices.json';

const AudioDeviceForm = ({ deviceId, onSubmitSuccess }) => {
  const [formData, setFormData] = useState({});
  const [loading, setLoading] = useState(false);

  // 编辑模式下拉取数据
  useEffect(() => {
    if (deviceId) {
      setLoading(true);
      axios.get(`/api/audio-input-devices/${deviceId}`)
        .then(res => setFormData(res.data))
        .finally(() => setLoading(false));
    }
  }, [deviceId]);

  const handleSubmit = async ({ formData }) => {
    setLoading(true);
    try {
      deviceId 
        ? await axios.put(`/api/audio-input-devices/${deviceId}`, formData)
        : await axios.post('/api/audio-input-devices', formData);
      onSubmitSuccess();
    } catch (err) {
      alert(err.response?.data?.error || '提交失败');
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <div>加载中...</div>;

  return (
    <div className="form-container">
      <h2>{deviceId ? '编辑音频输入设备' : '添加音频输入设备'}</h2>
      <Form
        schema={audioDeviceSchema}
        formData={formData}
        onSubmit={handleSubmit}
        disabled={loading}
      />
    </div>
  );
};

export default AudioDeviceForm;

3. 自动生成表格列表

基于Schema生成表格列,配合react-table实现列表展示:

// src/components/AudioDeviceTable.js
import React, { useState, useEffect } from 'react';
import { useTable } from 'react-table';
import axios from 'axios';
import AudioDeviceForm from './AudioDeviceForm';
import audioDeviceSchema from '../shared/schemas/AudioInputDevices.json';

const AudioDeviceTable = () => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [editingId, setEditingId] = useState(null);
  const [showForm, setShowForm] = useState(false);

  // 拉取列表数据
  const fetchData = async () => {
    setLoading(true);
    try {
      const res = await axios.get('/api/audio-input-devices');
      setData(res.data.data);
    } catch (err) {
      alert(err.response?.data?.error || '获取数据失败');
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, []);

  // 从Schema自动生成表格列
  const columns = Object.entries(audioDeviceSchema.properties).map(([key, prop]) => ({
    Header: prop.description || key,
    accessor: key,
  })).concat([
    {
      Header: '操作',
      accessor: 'actions',
      Cell: ({ row }) => (
        <div>
          <button onClick={() => { setEditingId(row._id); setShowForm(true); }}>编辑</button>
          <button onClick={() => handleDelete(row._id)}>删除</button>
        </div>
      )
    }
  ]);

  const handleDelete = async (id) => {
    if (window.confirm('确定要删除该设备吗?')) {
      setLoading(true);
      try {
        await axios.delete(`/api/audio-input-devices/${id}`);
        fetchData();
      } catch (err) {
        alert(err.response?.data?.error || '删除失败');
      } finally {
        setLoading(false);
      }
    }
  };

  const handleFormSuccess = () => {
    setShowForm(false);
    setEditingId(null);
    fetchData();
  };

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
  } = useTable({ columns, data });

  if (loading) return <div>加载中...</div>;

  return (
    <div className="table-container">
      <button onClick={() => { setShowForm(true); setEditingId(null); }}>添加设备</button>
      {showForm && (
        <AudioDeviceForm
          deviceId={editingId}
          onSubmitSuccess={handleFormSuccess}
        />
      )}
      <table {...getTableProps()}>
        <thead>
          {headerGroups.map(headerGroup => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map(column => (
                <th {...column.getHeaderProps()}>{column.render('Header')}</th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {rows.map(row => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map(cell => (
                  <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
                ))}
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
};

export default AudioDeviceTable;

4. 主页面整合

// src/App.js
import React from 'react';
import AudioDeviceTable from './components/AudioDeviceTable';

function App() {
  return (
    <div className="App">
      <h1>音频输入设备管理</h1>
      <AudioDeviceTable />
    </div>
  );
}

export default App;

关键注意事项

  • Schema兼容性:Mongoose与JSON Schema存在细微差异(如ObjectId类型),可在转换时手动补充配置,或通过@json-schema-tools/mongoose-schema的参数调整。
  • 前后端验证同步:后端依赖Mongoose的Schema验证,前端依赖rjsf的内置验证,确保两端规则一致,避免无效数据提交。
  • Schema共享:可将Schema放在独立的npm包中,前后端共同引用,彻底避免重复维护。
  • 扩展功能:可基于现有代码快速添加排序、高级过滤、批量操作等功能,只需调整后端查询逻辑和前端表格配置。

内容的提问来源于stack exchange,提问作者Forivin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 02:40:39