背景
公司低代码平台目前支持的功能有配置管理后台的列表页面、表单、图表等,而且支持在配置出的页面上增加各种钩子函数,完成自定义开发需求,基本满足常规管理后台的需求。
目前做法是设计器保存大JSON字符串到后端一个字段存起来,后端也只使用到一个单表,基本就是单表CRUD提供给前端而已(还有一个大部分是和流程那边相关的,这块先不动)。
我考虑的是把这块CRUD的接口从后端java程序改为前端NestJS,数据库用sqllite,这样后端就完全不用管整个低代码设计了,后续增加需求前端自己搞就行了。
思路
大概要做这么几件事:
- 梳理要迁移的接口并NestJS实现
- 统一鉴权处理并NestJS实现,因为接口token需要鉴权
- sqllite设计根据字段设计表结构
- Dockerfile改造支持pm2跑nodejs服务
- 旧数据从mysql迁移到sqlite
- Jenkins和流水线新增配置
项目里面搜索了下,总共6个接口,整理出来是需要去重写的,再需要考虑鉴权问题
bash
post /online/desform/page
post /online/desform/add
post /online/desform/edit
delete /online/desform/delete
get /online/desform/detail
get /online/desform/detailKey
NestJS 项目配置
初始化项目
css
npm i -g @nestjs/cli
nest new [项目名]
npm run start:dev // 运行开发模式,看到 http://localhost:3000 页面正常
启用CORS跨域
一般我在nginx里面统一做跨域,这里我就在代码main.js里支持
javascript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as bodyParser from 'body-parser';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 防止 报错 request entity too large
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
// 启用CORS
app.enableCors();
await app.listen(3000);
}
bootstrap();
解决 Delete ␍
eslint
VSCODE很多eslint的报错提示,原因是windows的文件结尾是CRLF,linux的文件结尾是LF。
eslint就提示报错了,解决方法很多。我们直接把修改 .eslintrc.js 不提示报错就行,在 rules 添加如下规则
arduino
'prettier/prettier': ['error',
{
endOfLine: 'auto', //不让prettier检测文件每行结束的格式
},
],
sqllite3安装
bash
npm install @nestjs/typeorm typeorm sqlite3
正式创建接口
创建模块资源文件
arduino
nest g resource desform // 快速创建CRUD相关文件,选RestApi即可,desform是模块名称
自动创建了如下dto\entiryes\controller\module\service文件
修改 create-desform.dto.ts
这个类是http请求时,请求参数映射的实体,修改完 create-desform.dto.ts,同理 update-desform.dto.ts也可同样修改,如果感觉麻烦,后面都用同一个也行
typescript
export class CreateDesformDto {
alias: string;
formContent: string;
formName: string;
id: number;
formType: number;
}
修改 desform.entity.ts
这个是 对应数据库的字段映射,这里保存文件后,会自动生成sqllite的表结构,而且 entity.ts 文件属性更改,也会自动更新数据库的表结构
这里我考虑formContent字段是很大字符串字段,我查了说sqllite varchar字段类型是无限大小的,大概可以存2G的内容,那够用了
less
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Desform {
@PrimaryGeneratedColumn()
id: number; // id自增长
@Column({ nullable: true })
alias: string; // 可为null
@Column()
formContent: string; // 必填
@Column()
formName: string; // 必填
@Column({ default: 1 })
formType: number; // 默认值1
}
修改 desform.module.ts
主要是增加imports内容,导入对象映射
python
import { Module } from '@nestjs/common';
import { DesformService } from './desform.service';
import { DesformController } from './desform.controller';
import { Desform } from './entities/desform.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forFeature([Desform])], // 增加 imports 内容
controllers: [DesformController],
providers: [DesformService],
})
export class DesformModule {}
修改 desform.service.ts
主要是添加constructor内容,而且在create中调用save方法,就保存数据成功
typescript
import { Injectable } from '@nestjs/common';
import { CreateDesformDto } from './dto/create-desform.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Desform } from './entities/desform.entity';
import { Repository } from 'typeorm';
@Injectable()
export class DesformService {
constructor(
@InjectRepository(Desform) private desformRepository: Repository<Desform>,
) {}
create(createDesformDto: CreateDesformDto) {
return this.desformRepository.save(createDesformDto);
}
}
修改 app.module.ts
主要是增加 sqllite 配置,导入引用模型
这里特别注意 synchronize: true
,这里会自动同步enity.ts中数据结构到sqllite表结构里面
typescript
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DesformModule } from './desform/desform.module';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: 'jeecgdb.db',
autoLoadEntities: true, // 自动加载实体,也就是数据库表和 TypeScript 的映射类。
synchronize: true, // 设置为 true 后,项目启动的时候会根据实体的配置,自动创建 sqlite3 的数据库表。
}),
DesformModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
postman测试
我们代码里面有个create方法,是默认post方法,所以不用带路径,调用后数据库会插入一条数据,成功!
下载个SQLiteStudio可以打开jeecgdb.db这个文件看到数据库数据。
工程化设置
.npmrc 和 .dockerignore
添加文件 .npmrc
ini
registry=https://registry.npmmirror.com
添加文件 .dockerignore
.git
node_modules
pm2 支持
pm2学习可以看 juejin.cn/post/722959...
全局安装pm2
npm install -g pm2
新建pm2配置文件
根目录 ecosystem.config.js,其中apps是数组,可以放多个启动脚本;scripts是指向最终nestjs执行build之后的文件
css
module.exports = {
apps: [
{
name: 'jeecg-nest-server',
script: './dist/main.js',
},
],
};
pm2 命令
arduino
pm2 logs // 查看日志
pm2 delete [pid] // 根据启动后得到的id,删除进程
// 普通命令行启动,黑框框弹出后消失运行在后台,容器中不能用这个
pm2 start ecosystem.config.js
// Docker 容器中使用这个启动,因为用 pm2 start 容器将在运行该进程后立即死亡,
// 所以为了容器专门用 pm2-runtime。
pm2-runtime ecosystem.config.js
pm2 和 pm2-runtime 之间的主要区别是
-
pm2-runtime 专为 Docker 容器设计,它将应用程序保持在前台,使容器保持运行,
-
pm2 专为在后台发送或运行应用程序的正常使用而设计。
build打包项目
Dockerfile 文件
三点:要用node18版本的,要python环境,时区东八区
bash
FROM keymetrics/pm2:18-alpine
# 默认是UTC时间,改为我们的日期东八区
ENV TZ=Asia/Shanghai
RUN apk update \
&& apk add tzdata \
&& echo "${TZ}" > /etc/timezone \
&& ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
&& rm /var/cache/apk/*
# NestJS需要安装python3
ENV PYTHONUNBUFFERED=1
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python && python3 -m ensurepip && pip3 install --no-cache --upgrade pip setuptools
# 打包
RUN mkdir -p /app
WORKDIR /app
COPY . ./
RUN npm install -g pnpm && pnpm install && pnpm run build
# 暴露端口和pm2运行
EXPOSE 3000
CMD [ "pm2-runtime", "ecosystem.config.js" ]
【废弃尝试】pm2:18-buster
pm2:18-buster 是全面版本容器,但是太大了有900M,改用pm2:18-alpine只有149M,但是打包中发现报错,需要python环境,于是在alpine中配置python3环境,最后我看Harbor里面压缩后只有155M还是能接受
【废弃尝试】引入webpack打包
为什么要引入webpack呢,很扯蛋,如果谁看到有其他办法请告诉我
正常在 package.json 中有build命令,我正常执行 npm run build
之后发布到linux上,看起来docker容器已经运行起来了,但是其实报错了 Error: Cannot find module '@nestjs/core'
,除非在容器中安装node_modules,但是我又不想在镜像里面安装node_modules,会导致镜像文件太大了
原因是 build 之后dist里面没有引入这些包,从上面Dockerfile可以看到是Jenkins里面构建之后,只是把dist目录拷贝进去了,如果是本地开发会自动找到node_modules能正常运行,但是服务器上没有node_modules那肯定运行不了,也不能把这个拷贝进去吧
找到一篇文章说方法 juejin.cn/post/706572... , 方法就是在nest-build章节找到了这个配置项的相关内容,发现他可以在打包命令后面添加--webpack
参数来生成单文件main.js
安装包
注意:上述webpack配置文件要求package.json
中webpack的版本号为^5.11.0"
,还需要安装fork-ts-checker-webpack-plugin
依赖包到devDependencies中。
perl
npm install webpack fork-ts-checker-webpack-plugin -D
修改 package.json
json
{
"scripts": {
"build": "nest build --webpack --webpackPath=./webpack.config.js",
}
}
新建 webpack.config.js
javascript
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const webpack = require('webpack');
// fork-ts-checker-webpack-plugin需要单独安装
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
entry: './src/main',
target: 'node',
// 置为空即可忽略webpack-node-externals插件
externals: {},
// ts文件的处理
module: {
rules: [
{
test: /\.ts?$/,
use: {
loader: 'ts-loader',
options: { transpileOnly: true },
},
exclude: /node_modules/,
},
],
},
// 打包后的文件名称以及位置
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
resolve: {
extensions: ['.js', '.ts', '.json'],
},
plugins: [
// 需要进行忽略的插件
new webpack.IgnorePlugin({
checkResource(resource) {
const lazyImports = [
'@nestjs/microservices',
'@nestjs/microservices/microservices-module',
'@nestjs/websockets/socket-module',
'cache-manager',
'class-validator',
'class-transformer',
];
if (!lazyImports.includes(resource)) {
return false;
}
try {
require.resolve(resource, {
paths: [process.cwd()],
});
} catch (err) {
return true;
}
return false;
},
}),
new ForkTsCheckerWebpackPlugin(),
],
};
结果还是报错了
算了,还是在服务器上安装包吧,认命吧
前端nginx增加转发规则
如图把前端nest前端的接口,都转发到新的nestjs开发的接口上去
Jenkins 新增项目流水线配置
nestjs流水线注意两点,把sqlite挂载到宿主机上,监听暴露的3000端口