我在前端写Java SpringBoot项目
来源 | OSCHINA 社区
作者 | 京东云开发者-京东零售 周明亮
原文链接:https://my.oschina.net/u/4090830/blog/10116557
前言
C端
开玩笑!这里不推荐大家把 Node 服务作为 C 端服务,毕竟它是单线程多任务
机制。这一特性是 Javascript
语言设计之初,就决定了它的使命 - Java >>>【Script】,这里就不多解释了,大家去看看 JavaScript 的历史就知道啦~这也就决定了,它不能像后端语言那样 多线程多任务
,用户访问量小还能承受,一旦承受访问量大高并发,就得凉凉~Nest
完成下面收获:学习装饰器语法,感受其简洁优美;
自己学习一门新的开发框架,感受不同框架的优缺点,为以后开发选型打基础;
感受服务端排查问题的复杂性,找找前端设计的灵感。
第一步、项目跑起来
https://www.geeksforgeeks.org/best-nodejs-frameworks-for-app-development/
https://anywhere.epam.com/business/best-node-js-frameworks
Nest
作为前端框架,Graphql
作为中间处理层。底层数据库我们用传统的 MySQL
,比较稳定可靠,而且相对比较熟悉,这个就不玩新的了,毕竟数据库是一切的基石 。MySQL
,接入 @nestjs/sequelize
库 完成 增删改查
功能即:CRUD
Graphql
处理 API 查询,做到精确数据查询,这个已经火了很多了,但是真正使用的很少,我们打算先感受下,后续可以直接用到业务。Swagger
自动生成 API 文档,快捷进行前端与后端服务联调测试。middleware
中间件统一处理请求及响应,进行鉴权处理,请求拦截等操作redis
处理# 进入文件夹目录
cd full-stack-demo/packages
# 安装脚手架
npm i -g @nestjs/cli
# 创建基础项目
nest new node-server-demo
# 进入项目
cd new node-server-demo
# 运行项目测试
npm run start:dev
common
- 公用方法类config
- 配置类文件controller
- 控制器,用于处理前端发起的各类请求service
- 服务类,用于处理与数据库交互逻辑dto
- DTO(Data Transfer Object)可以用于验证输入数据、限制传输的字段或格式。entities
- 实体类,用于描述对象相关的属性信息module
- 模块,用于注册所有的服务类、控制器类,类似 Spring 里面的 beanmain.ts
- nest 启动入口types
- typescript 相关声明类型控制器 controller
// packages/node-server-demo/src/controller/user/index.ts
import{ Controller, Get, Query }from'@nestjs/common';
import UserServices from'@/service/user';
import{ GetUserDto, GetUserInfoDto }from'@/dto/user';
@Controller('user')
exportclassUserController{
constructor(privatereadonly userService: UserServices){}
// Get 请求 user/name?name=bricechou
@Get('name')
asyncfindByName(@Query() getUserDto: GetUserDto){
returnthis.userService.read.findByName(getUserDto.name);
}
// Get 请求 user/info?id=123
@Get('info')
asyncfindById(@Query() getUserInfoDto: GetUserInfoDto){
const user =awaitthis.userService.read.findById(getUserInfoDto.id);
return{ gender: user.gender, job: user.job };
}
}
// packages/node-server-demo/src/controller/log/add.ts
import{ Controller, Post, Body }from'@nestjs/common';
import{ AddLogDto }from'@/dto/log';
import LogServices from'@/service/log';
@Controller('log')
exportclassCreateLogController{
constructor(privatereadonly logServices: LogServices){}
// post('/log/add')
@Post('add')
create(@Body() createLogDto: AddLogDto){
returnthis.logServices.create.create(createLogDto);
}
}
数据转换 Data Transfer Object
// packages/node-server-demo/src/dto/user.ts
exportclassCreateUserDto{
name:string;
age:number;
gender:string;
job:string;
}
// 可以分开写,也可以合并
exportclassGetUserDto{
id?:number;
name:string;
}
// 可以分开写,也可以合并
exportclassGetUserInfoDto{
id:number;
}
service 数据库交互处理类
// packages/node-server-demo/src/service/user/read.ts
import{ Injectable }from'@nestjs/common';
import{ User }from'@/entities/User';
@Injectable()
exportclassReadUserService{
constructor(){}
asyncfindByName(name:string):Promise<User>{
// 可以处理判空,从数据库读取/写入数据,可能会被多个 controller 进行调用
console.info('ReadUserService findByName > ', name);
returnPromise.resolve({ id:1, name, job:'程序员', gender:1, age:18});
}
asyncfindById(id:number):Promise<User>{
console.info('ReadUserService findById > ', id);
returnPromise.resolve({
id:1,
name:'BriceChou',
job:'程序员',
gender:1,
age:18,
});
}
}
module 模块注册,服务类 / 控制类
// packages/node-server-demo/src/module/user.ts
import{ Module }from'@nestjs/common';
import UserService,{ ReadUserService }from'@/service/user';
import{ UserController }from'@/controller/user';
@Module({
providers:[UserService, ReadUserService],
controllers:[UserController],
})
exportclassUserModule{}
// packages/node-server-demo/src/module/index.ts 根模块注入
import{ Module }from'@nestjs/common';
import{ UserModule }from'./user';
import{ LogModule }from'./log';
@Module({
imports:[
UserModule,
LogModule,
],
})
exportclassAppModule{}
main.js 启动注册的所有类
// packages/node-server-demo/src/main.ts
import { AppModule } from '@/module';
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 监听端口 3000
await app.listen(3000);
}
bootstrap();
Postwoman
[https://hoppscotch.io/] 进行请求,瞅瞅看返回效果。.log
文件,这样请求日志也有了,完美!下一步,我们开始连接数据库,这样就不用单机玩泥巴了~第二步、配置 MySQL
注意:安装的数据库,一定要设置密码,连接数据库必须要有密码,否则会导致连接数据库失败。
MySQL 我们只安装数据库就行,熟悉指令的童鞋,就直接命令行操作就行。
不熟悉的话,那就下载图形化管理工具。
MacOS 13
以上,我的电脑是 MacOS 12
。8.0.31
。对于数据库服务也有版本要求,大家按照自己电脑版本,选择支持的版本即可。 https://downloads.mysql.com/archives/community/
。我这边选择的是默认最新版本:8.0.34
,下载好直接安装,一路 Next
到底,记住自己输入的 Root 密码!!!PRIMARY KEY
是表中的一个或多个列的组合,它用于唯一标识表中的每一行。Not NULL
和 Unique
就不解释,就是直译的那个意思。GENERATED
生成列是表中的一种特殊类型的列,它的值不是从插入语句中获取的,而是根据其他列的值通过一个表达式或函数生成的。CREATETABLE people (
first_name VARCHAR(100),
last_name VARCHAR(100),
full_name VARCHAR(200)AS(CONCAT(first_name,' ', last_name))
);
UNSIGNED
这个数值类型就只能存储正数(包括零),不会存储负数。ZEROFILL
将数值类型的字段的前面填充零,他会自动使字段变为UNSIGNED
,直到该字段达到声明的长度,如:00007BINARY
用于存储二进制字符串,如声明一个字段为 BINARY (5),那么存储在这个字段中的字符串都将被处理为长度为 5 的二进制字符串。
此外也可顺手传创建一个索引,方便快速查找。
CREATETABLE`rrweb`.`test_sys_req_log`(
`id`INTUNSIGNEDNOTNULLAUTO_INCREMENT,
`content`TEXTNOTNULL,
`l_level`INTUNSIGNEDNOTNULL,
`l_category`VARCHAR(255)NOTNULL,
`l_created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMP,
`l_updated_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMP,
PRIMARYKEY(`id`),
UNIQUEINDEX`id_UNIQUE`(`id`ASC) VISIBLE,
INDEX`table_index`(`l_level`ASC,`l_category`ASC,`l_time`ASC) VISIBLE);
3. 连接数据库
由于目前 node-oracledb
官方尚未提供针对 Apple Silicon 架构的预编译二进制文件。导致我们无法在 Mac M1 芯片上使用 TypeORM
链接数据库操作,它目前只支持 Mac x86 芯片。哎~折腾老半天,查阅各种文档,居然有这个坑,没关系我们换个方式打开。
我们不得不放弃,从而选用 https://docs.nestjs.com/techniques/database#sequelize-integration 哐哐哐~一顿操作猛如虎,盘它!
安装
Sequelize
# 安装连接库
npm install --save @nestjs/sequelize sequelize sequelize-typescript mysql2
# 安装 type
npm install --save-dev @types/sequelize
配置数据库基础信息
// packages/node-server-demo/src/module/index.ts
import{ Module }from'@nestjs/common';
import{ UserModule }from'./user';
import{ LogModule }from'./log';
import{ Log }from'@/entities/Log';
import{ SequelizeModule }from'@nestjs/sequelize';
@Module({
imports:[
SequelizeModule.forRoot({
dialect:'mysql',
// 按数据库实际配置
host:'127.0.0.1',
// 按数据库实际配置
port:3306,
// 按数据库实际配置
username:'root',
// 按数据库实际配置
password:'hello',
// 按数据库实际配置
database:'world',
synchronize:true,
models:[Log],
autoLoadModels:true,
}),
LogModule,
UserModule,
],
})
exportclassAppModule{}
实体与数据库一一映射处理
import{ getNow }from'@/common/date';
import{
Model,
Table,
Column,
PrimaryKey,
DataType,
}from'sequelize-typescript';
@Table({ tableName:'test_sys_req_log'})
exportclassLogextendsModel<Log>{
@PrimaryKey
@Column({
type: DataType.INTEGER,
autoIncrement:true,
field:'id',
})
id:number;
@Column({ field:'content', type: DataType.TEXT})
content:string;
@Column({ field:'l_level', type: DataType.INTEGER})
level:number;// 3严重,2危险,1轻微
@Column({ field:'l_category'})
category:string;// 模块分类/来源分类
@Column({
field:'l_created_at',
type: DataType.NOW,
defaultValue:getNow(),
})
createdAt:number;
@Column({
field:'l_updated_at',
type: DataType.NOW,
defaultValue:getNow(),
})
updatedAt:number;
}
module 注册实体
// packages/node-server-demo/src/module/log.ts
import{ Module }from'@nestjs/common';
import{ SequelizeModule }from'@nestjs/sequelize';
import{ Log }from'@/entities/Log';
import LogServices,{
CreateLogService,
UpdateLogService,
DeleteLogService,
ReadLogService,
}from'@/service/log';
import{
CreateLogController,
RemoveLogController,
UpdateLogController,
}from'@/controller/log';
@Module({
imports:[SequelizeModule.forFeature([Log])],
providers:[
LogServices,
CreateLogService,
UpdateLogService,
DeleteLogService,
ReadLogService,
],
controllers:[CreateLogController, RemoveLogController, UpdateLogController],
})
exportclassLogModule{}
service 操作数据库处理数据
import{ Log }from'@/entities/Log';
import{ Injectable }from'@nestjs/common';
import{ AddLogDto }from'@/dto/log';
import{ InjectModel }from'@nestjs/sequelize';
import{ ResponseStatus }from'@/types/BaseResponse';
import{ getErrRes, getSucVoidRes }from'@/common/response';
@Injectable()
exportclassCreateLogService{
constructor(
@InjectModel(Log)
private logModel:typeof Log,
){}
asynccreate(createLogDto: AddLogDto):Promise<ResponseStatus<null>>{
console.info('CreateLogService create > ', createLogDto);
const{ level =1, content ='', category ='INFO'}= createLogDto ||{};
const str = content.trim();
if(!str){
returngetErrRes(500,'日志内容为空');
}
const item ={
level,
category,
// Tips: 为防止外部数据进行数据注入,我们可以对内容进行 encode 处理。
// content: encodeURIComponent(str),
content: str,
};
awaitthis.logModel.create(item);
returngetSucVoidRes();
}
}
hello world!
第三步、实现 CRUD
基础功能
CRUD
C
create 创建R
read 读取U
update 更新D
delete 删除
import{ Injectable }from'@nestjs/common';
import{ InjectModel }from'@nestjs/sequelize';
import{ User }from'./user.model';
@Injectable()
exportclassUserService{
constructor(
@InjectModel(User)
private userModel:typeof User,
){}
// 创建新数据
asynccreate(user: User){
const newUser =awaitthis.userModel.create(user);
return newUser;
}
// 查找所有数据
asyncfindAll(){
returnthis.userModel.findAll();
}
// 按要求查找单个
asyncfindOne(id:string){
returnthis.userModel.findOne({ where:{ id }});
}
// 按要求更新
asyncupdate(id:string, user: User){
awaitthis.userModel.update(user,{ where:{ id }});
returnthis.userModel.findOne({ where:{ id }});
}
// 按要求删除
asyncdelete(id:string){
const user =awaitthis.userModel.findOne({ where:{ id }});
await user.destroy();
}
}
Tips:
进行删除的时候,我们可以进行假删除,两个数据库,一个是备份数据库,一个是主数据库。主数据库可以直接删除或者增加标识表示删除。备份数据库,可以不用删除只写入和更新操作,这样可以进行数据还原操作。往期推荐
点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦
微信扫码关注该文公众号作者