前言
市面上关于前端低代码的平台或文章很多,后端低代码的文章比较少,这一篇文章我给大家分享一下后端低代码平台比较核心的两个东西,动态建模和动态接口,还有一个比较核心的逻辑编排后面再说。
框架选择
因为我是前端,有两个框架比较适合我,nest.js 和 midway.js,midway.js 这个框架我用的多一点,所以这里我选择使用 midway 框架来讲解。
数据库 orm 库这里我选择的是 typeorm。
使用 midway + typeorm 实现增删改查
创建midway项目
在合适的目录下执行下面命令,创建 midway 项目
npm init midway@latest -y
引入typeorm
安装依赖
在项目里安装 typeorm 依赖
pnpm i @midwayjs/typeorm@3 typeorm --save
引入组件
在 src/configuration.ts
引入 orm 组件
// configuration.ts
import { Configuration } from '@midwayjs/core';
import * as orm from '@midwayjs/typeorm';
import { join } from 'path';
@Configuration({
imports: [
// ...
orm // 加载 typeorm 组件
],
importConfigs: [
join(__dirname, './config')
]
})
export class MainConfiguration {
}
安装mysql数据库驱动
pnpm i mysql2 --save
添加typeorm配置
修改 /src/config/config.default.ts
文件,添加数据库连接信息。
import { MidwayConfig } from '@midwayjs/core';
export default {
// use for cookie sign key, should change to your own and keep security
keys: '1731912689706_6032',
koa: {
port: 7001,
},
typeorm: {
dataSource: {
default: {
/**
* 单数据库实例
*/
type: 'mysql',
host: '127.0.01',
port: 3306,
username: 'root',
password: '12345678',
database: 'lowcode',
synchronize: true, // 如果第一次使用,不存在表,有同步的需求可以写 true,注意会丢数据
logging: true,
entities: [
'entity',
]
}
}
},
} as MidwayConfig;
ps:需要启动数据库,我这里使用 docker 启动的mysql服务。
创建模型
在src文件夹下创建entity文件夹,在entity文件夹下创建user.ts 文件,创建一个 user 表,字段有 id,name,age,id 是主键并且自增。
// /src/entity/user.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('user')
export class UserEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
age: number;
}
@PrimaryGeneratedColumn
表示主键自增列
启动项目
npm run dev
启动项目之后,会根据模型自动在数据库中建表
实现server
在 service 文件夹下创建 user.service.ts 文件
import { Provide } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { UserEntity } from '../entity/user';
import { Repository } from 'typeorm';
@Provide()
export class UserService {
@InjectEntityModel(UserEntity)
userModel: Repository<UserEntity>;
/**
* 查询所有用户
* @returns all user
*/
async list() {
return await this.userModel.find();
}
/**
* 分页查询用户
* @returns page user
*/
async page() {
const [data, total] = await this.userModel.findAndCount();
return {
data,
total
}
}
/**
* 新增用户
* @param user
* @returns
*/
async add(user: UserEntity) {
return await this.userModel.save(user);
}
/**
* 根据id删除用户
* @param id
* @returns
*/
async delete(id: number) {
return await this.userModel.delete(id);
}
/**
* 更新用户
* @param user
* @returns
*/
async update(user: UserEntity) {
return await this.userModel.save(user);
}
/**
* 根据id查询用户
* @param id
* @returns
*/
async findById(id: number) {
return await this.userModel.findOne({ where: { id } });
}
}
实现controller
在 controller 文件夹下创建 user.controller.ts 文件
import { Body, Controller, Del, Get, Inject, Param, Post, Put, Query } from '@midwayjs/core';
import { UserService } from '../service/user.service';
@Controller('/user')
export class ModelController {
@Inject()
userService: UserService;
/**
* 获取所有用户
*/
@Get("/list")
async list() {
return await this.userService.list();
}
/**
* 分页获取用户
*/
@Get("/page")
async page(@Query() query) {
return await this.userService.page(query.page, query.size);
}
/**
* 新增用户
*/
@Post('/')
async create(@Body() body) {
return await this.userService.create(body);
}
/**
* 更新用户
*/
@Put('/')
async update(@Body() body) {
return await this.userService.update(body);
}
/**
* 删除用户
*/
@Del('/:id')
async remove(@Param('id') id) {
return await this.userService.delete(id);
}
}
测试接口
使用postman测试接口
新增
查询所有用户
分页查询
更新用户信息
删除用户
动态建模
上面我们实现了对用户表增删改查,可以发现我们需要 手动创建user模型,还要实现 service 和 contoller,一个简单的功能要写那么多代码,如果这个功能使用低代码去做,应该怎么实现呢,下面给大家分享一下。
动态建表
typeorm 提供了方法可以直接建表,不用自己写 sql 语句建表。
import { Controller, Get } from '@midwayjs/core';
import { InjectDataSource } from '@midwayjs/typeorm';
import { DataSource, Table } from 'typeorm';
@Controller('/')
export class HomeController {
@InjectDataSource()
dataSource: DataSource;
@Get('/')
async home(): Promise<any> {
const table = new Table({
name: 'test',
columns: [
{
name: 'id',
type: 'int',
isPrimary: true,
isGenerated: true,
generationStrategy: 'increment',
},
{
name: 'name',
type: 'varchar',
},
],
});
await this.dataSource.createQueryRunner().createTable(table);
}
}
启动项目,访问 http://localhost:7001, 发现表创建好了
构造schema
上面动态建了表,如果想对表数据进行增删改查,有两个方式
- 动态拼接 sql,然后使用
this.dataSource.createQueryRunner().query('select * from test')
动态执行。 - 使用 typeorm动态构建EntitySchema,不用写 sql。
这里我选择第 2 种方式,因为自己拼接 sql,复杂度太高了。
创建EntitySchema
const schema = new EntitySchema({
name: 'test',
tableName: 'test',
columns: {
id: {
name: 'id',
primary: true,
type: 'int',
},
name: {
name: 'name',
primary: false,
type: 'varchar',
},
},
});
把刚创建的 schema 放到 dataSource 里
await this.dataSource
.setOptions({
entities: [schema],
})
.buildMetadatas();
这里调用 buildMetadatas
方法会报错,因为这个是私有方法,不能访问,ts 语法会报错,这个方法还是我去 typeorm 源码里找到的。
自己封装一个 DataSource 类去继承 typeorm 里的 DataSource, 然后把 buildMetadatas 方法改为 public。
import { DataSource } from 'typeorm';
export class CustomDataSource extends DataSource {
public async buildMetadatas(): Promise<void> {
super.buildMetadatas();
}
}
这里把类型改为CustomDataSource
然后可以这样插入数据和查询数据,不用写一行 sql
完整代码
import { Controller, Get } from '@midwayjs/core';
import { InjectDataSource } from '@midwayjs/typeorm';
import { EntitySchema } from 'typeorm';
import { CustomDataSource } from '../custom.data.source';
@Controller('/')
export class HomeController {
@InjectDataSource()
dataSource: CustomDataSource;
@Get('/')
async home(): Promise<any> {
// const table = new Table({
// name: 'test',
// columns: [
// {
// name: 'id',
// type: 'int',
// isPrimary: true,
// isGenerated: true,
// generationStrategy: 'increment',
// },
// {
// name: 'name',
// type: 'varchar',
// },
// ],
// });
// await this.dataSource.createQueryRunner().createTable(table);
const schema = new EntitySchema({
name: 'test',
tableName: 'test',
columns: {
id: {
name: 'id',
primary: true,
type: 'int',
generated: 'increment',
},
name: {
name: 'name',
primary: false,
type: 'varchar',
},
},
});
await this.dataSource
.setOptions({
entities: [schema],
})
.buildMetadatas();
// 插入数据
await this.dataSource.getRepository(schema).save({ name: 'hello' })
// 查询数据
return await this.dataSource.getRepository(schema).find();
}
}
对外暴露建模接口
上面创建表和列都是写死的数据,这里我们给改造成动态的,可以通过接口动态创建表和列。
创建table entity和column entity
创建table entity和column entity,用来存放我们动态创建的表和列。这里我只加了 name 字段,实际上表和列还可以配置其他很多属性,这里是 demo,我就简单处理了。
table entity
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('table')
export class TableEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
name: string;
}
column entity
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('column')
export class ColumnEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
modelId: number;
}
service实现
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectDataSource, InjectEntityModel } from '@midwayjs/typeorm';
import { TableEntity } from '../entity/table';
import { EntitySchema, Repository, Table, TableColumn } from 'typeorm';
import { ColumnEntity } from '../entity/column';
import { CustomDataSource } from '../custom.data.source';
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class TableService {
@InjectEntityModel(TableEntity)
tableModel: Repository<TableEntity>;
@InjectEntityModel(ColumnEntity)
columnModel: Repository<ColumnEntity>;
@InjectDataSource()
dataSource: CustomDataSource;
/**
* 创建表
* @param data
* @returns
*/
async createModel(data) {
await this.dataSource
.createQueryRunner()
.createTable(
new Table({
name: data.name,
columns: [
{
name: 'id',
type: 'int',
isPrimary: true,
isGenerated: true,
generationStrategy: 'increment',
},
],
})
);
await this.tableModel.save(data);
await this.buildMetadatas();
}
/**
* 创建表字段
* @param data
*/
async createColumn(data) {
const model = await this.tableModel.findOne({
where: {
id: data.modelId
}
});
if (!model) return;
await this.dataSource
.createQueryRunner()
.addColumn(
model.name,
new TableColumn(
{ name: data.name, type: data.type }
)
);
await this.columnModel.save(data);
await this.buildMetadatas();
}
/**
* 获取所有模型和列
*/
async getModelList() {
return await this.tableModel
.createQueryBuilder('t')
.leftJoinAndMapMany('t.children', ColumnEntity, 'c', 't.id = c.modelId')
.getMany();
}
/**
* 动态创建模型
*/
async buildMetadatas() {
const data = await this.tableModel
.createQueryBuilder('t')
.innerJoinAndMapMany('t.columns', ColumnEntity, 'c', 't.id = c.modelId')
.getMany();
const schemas = data.map((cur: any) => {
return new EntitySchema({
name: cur.name,
tableName: cur.name,
columns: {
id: {
name: 'id',
primary: true,
type: 'int',
generated: 'increment',
},
...(cur.columns || []).reduce((p, c) => {
return {
...p,
[c.name]: {
type: 'varchar',
name: c.name
}
}
}, {}),
}
})
});
await this.dataSource
.setOptions({
entities: [
TableEntity,
ColumnEntity,
...schemas
],
})
.buildMetadatas();
}
}
contoller实现
import { Body, Controller, Get, Inject, Post } from '@midwayjs/core';
import { TableService } from '../service/table.service';
@Controller('/table')
export class TableController {
@Inject()
tableService: TableService;
@Post('/')
async createModel(@Body() data) {
return await this.tableService.createModel(data);
}
@Post('/column')
async createColumn(@Body() data) {
return await this.tableService.createColumn(data);
}
@Get('/')
async findModel() {
return await this.tableService.getModelList();
}
}
前端页面实现
使用 react 和 antd 快速实现模型的增删改查
import { Button, Form, Input, Modal, Space, Table } from 'antd'
import axios from 'axios';
import { useEffect, useState } from 'react'
function App() {
const [open, setOpen] = useState(false);
const [data, setData] = useState([]);
const [modelId, setModelId] = useState<number | null>(null);
const [form] = Form.useForm();
useEffect(() => {
getData();
}, []);
useEffect(() => {
form.setFieldsValue({ name: '' })
}, [open])
function getData() {
axios.get('/api/table').then(res => {
setData(res.data);
})
}
async function onFinish(values: any) {
if (!modelId) {
await axios.post('/api/table', {
name: values.name
});
} else {
await axios.post('/api/table/column', {
modelId,
name: values.name,
type: 'varchar',
});
}
setOpen(false);
getData();
}
return (
<Space
direction='vertical'
style={{ width: '100%' }}
>
<Button
onClick={() => {
setOpen(true);
setModelId(null);
}}
type='primary'
>
创建模型
</Button >
<Table
rowKey={'id'}
dataSource={data}
columns={[
{
dataIndex: 'name',
title: '模型名称',
},
{
dataIndex: 'id',
title: '操作',
render: (v) => (
<Button
type='link'
onClick={
() => {
setOpen(true);
setModelId(v);
}
}
>
添加列
</Button>
)
}
]}
size='small'
pagination={false}
/>
<Modal
title="创建"
open={open}
onCancel={() => setOpen(false)}
onOk={() => {
form.submit();
}}
>
<Form
form={form}
onFinish={onFinish}
>
<Form.Item
name="name"
label="名称"
>
<Input />
</Form.Item>
</Form>
</Modal>
</Space>
)
}
export default App
动态接口
前面把模型和表都建好了,下面我们想办法让别人可以通过接口对表数据进行增删改查。
新建一个 ApiController
import { Controller, Param, Body, Post } from '@midwayjs/core';
import { InjectDataSource } from '@midwayjs/typeorm';
import { DataSource } from 'typeorm';
@Controller('/api')
export class APIController {
@InjectDataSource()
dataSource: DataSource;
/**
* 查询对应表数据
*/
@Post('/model/:modelName/get')
async find(@Param('modelName') modelName, @Body() body) {
const data = await this.dataSource
.getRepository(modelName)
.find(body);
return data;
}
/**
* 分页查询对应表数据
*/
@Post('/model/:modelName/page')
async page(@Param('modelName') modelName, @Body() body) {
const [data, total] = await this.dataSource
.getRepository(modelName)
.findAndCount({
skip: (body.page || 0) * (body.size || 10),
take: body.size || 10
});
return {
data,
total,
};
}
/**
* 创建数据
*/
@Post('/model/:modelName/create')
async create(@Param('modelName') modelName, @Body() body) {
const data = await this.dataSource
.getRepository(modelName)
.save(body);
return data;
}
/**
* 更新数据
*/
@Post('/model/:modelName/update')
async update(@Param('modelName') modelName, @Body() body) {
const data = await this.dataSource
.getRepository(modelName)
.save(body);
return data;
}
/**
* 删除数据
*/
@Post('/model/:modelName/delete')
async delete(@Param('modelName') modelName, @Body() body) {
const data = await this.dataSource
.getRepository(modelName)
.delete(body.id);
return data;
}
}
在启动项目的生命周期里, 需要先调用TableService里的buildMetadatas方法。
configuration.ts
效果展示
在前端页面创建一个 book 表,有两个字段,name和author。
使用 postman 调用接口测试
创建数据
更新数据
查询数据
自定义条件查询
分页查询
删除数据
总结
这篇文章给大家分享了使用 midway + typeorm 动态建表以及通过接口操作表里数据一个非常简单的小 demo,后面会给大家分享复杂的东西,比如表与表关联查询,以及逻辑编排等功能。