基于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
相关产品推荐
相关产品推荐

