Redian新闻
>
我在前端写Java SpringBoot项目

我在前端写Java SpringBoot项目

公众号新闻

来源 | OSCHINA 社区

作者 | 京东云开发者-京东零售 周明亮

原文链接:https://my.oschina.net/u/4090830/blog/10116557

前言

玩归玩,闹归闹,别拿 C端 开玩笑!这里不推荐大家把 Node 服务作为 C 端服务,毕竟它是单线程多任务 机制。这一特性是 Javascript 语言设计之初,就决定了它的使命 - Java >>>【Script】,这里就不多解释了,大家去看看 JavaScript 的历史就知道啦~这也就决定了,它不能像后端语言那样 多线程多任务,用户访问量小还能承受,一旦承受访问量大高并发,就得凉凉~
那为什么我们还要去写 Node 服务?主要是方便快捷,对于小项目可以迅速完成建设,开发成本小。其次,主要通过写 Nest 完成下面收获:
  • 学习装饰器语法,感受其简洁优美;

  • 自己学习一门新的开发框架,感受不同框架的优缺点,为以后开发选型打基础;

  • 感受服务端排查问题的复杂性,找找前端设计的灵感。

本篇文章主要是使用 NestJs + Sequelize + MySQL 完成基础运行, 带大家了解 Node 服务端的基础搭建,也可以顺便看看 Java SpringBoot 项目的基础结构,它俩真的非常相似,不信你去问服务端开发同学。
养成好习惯,看文章先一键三连~【点赞,关注,转发】,评论可以看完再吐槽~继续完善填坑~

第一步、项目跑起来

在选择服务端的时候,我之前使用过 Egg.js ,所以这次就不选它了。其次,Egg 也是继承了 Koa 的开发基础,加上 Express 也是基于 Koa 上创新的,两者应该差不多,就不选择 Koa 和 Express 。
所以,我想尝试下 Nest.js 看语法跟 Java 是一样的,加上之前也自己开发过 Java + SpringBoot 的项目,当然更古老的 SSH 2.0 也从无到有搭建过,即:Spring2.0 + Struts2+ Hibernate3.2,想想应该会很容易上手,顺便怀旧下写写。
参考文档:
  • https://www.geeksforgeeks.org/best-nodejs-frameworks-for-app-development/

  • https://anywhere.epam.com/business/best-node-js-frameworks

说下我的想法,首先我们刚入门,估计会有一堆不清楚的坑,我们先简单点,后续我们再继续加深。既然要搞服务端,要搞就多搞点,我们都去尝鲜玩玩。我们打算使用 Nest 作为前端框架,Graphql 作为中间处理层。底层数据库我们用传统的 MySQL,比较稳定可靠,而且相对比较熟悉,这个就不玩新的了,毕竟数据库是一切的基石 。
说下我们具体实现步骤:
1. 【必须】没有任何数据库,完成接口请求运行,能够跑起来;
2. 【必须】创建基础数据库 MySQL ,接入 @nestjs/sequelize 库 完成 增删改查 功能即:CRUD
3. 可选】打算采取 Graphql 处理 API 查询,做到精确数据查询,这个已经火了很多了,但是真正使用的很少,我们打算先感受下,后续可以直接用到业务。
4. 可选】接入 Swagger 自动生成 API 文档,快捷进行前端与后端服务联调测试。
 Swagger 是一个开源工具,用于设计、构建、记录和使用 RESTful web 服务。
5. 【可选】接口请求,数据库优化处理
 请求分流,数据库写入加锁,处理并发流程
 增加 middleware 中间件统一处理请求及响应,进行鉴权处理,请求拦截等操作
 数据库分割备份,数据库融灾处理,分为:主、备、灾
 数据库读写分离,数据双写,建立数据库缓存机制,使用 redis 处理
也欢迎大家补充更多的优化点,我们一起探讨~有兴趣可以帮忙补充代码哈~
确定了大概方向,我们就开始整。先不追求一步到位,否则越多越乱,锦上添花的东西,我们可以后续增加,基础功能我们要优先保障完成。Nest.js 官网:https://docs.nestjs.com/ ,话不多说,我们直接开整。
# 进入文件夹目录
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 里面的 bean
 这里不能完全等同哈,两个实现机制上就不同,只是帮助大家理解。
 main.ts - nest 启动入口
 types - typescript 相关声明类型
只是写 demo, 搞快点就没有怎么写注释了,我感觉是一看就懂了,跟 Java SpringBoot 的写法非常一致,部分代码展示:
  • 控制器 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 安装其实很简单,我电脑是 Mac 的,所以下面的截图都是以 mac 为例,先下载对应的数据库。
下载地址:https://dev.mysql.com/downloads/mysql/ 至于其他系统的,可以网上找教程,这个应该烂大街了,我就不重复搬运教程了。
  • 注意:安装的数据库,一定要设置密码,连接数据库必须要有密码,否则会导致连接数据库失败。

  • MySQL 我们只安装数据库就行,熟悉指令的童鞋,就直接命令行操作就行。

  • 不熟悉的话,那就下载图形化管理工具。

 Mysql 官方控制台 https://dev.mysql.com/downloads/workbench/
 Windows 也可以使用 https://www.heidisql.com/download.php?download=installer
PS:安装 workbench 时发现要求 MacOS 13以上,我的电脑是 MacOS 12
白白下载,所以只能 https://downloads.mysql.com/archives/workbench/ 从归档里面找低版本 8.0.31。对于数据库服务也有版本要求,大家按照自己电脑版本,选择支持的版本即可。 https://downloads.mysql.com/archives/community/。我这边选择的是默认最新版本:8.0.34,下载好直接安装,一路 Next 到底,记住自己输入的 Root 密码!!!
确认好当前数据库是否已经运行起来了,启动 Workbench 查看状态。
1. 创建数据库
数据库存在字符集选择,不同的字符集和校验规则,会对存储数据产生影响,所以大家可以自行查询,按照自己存储数据原则选择,我这里默认选最广泛的。确认好,就选择右下角的应用按钮。
2. 创建表和属性
选项解答:
 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,直到该字段达到声明的长度,如:00007

  • BINARY 用于存储二进制字符串,如声明一个字段为 BINARY (5),那么存储在这个字段中的字符串都将被处理为长度为 5 的二进制字符串。

 如尝试存储一个长度为 3 的字符串,那么它将在右侧用两个空字节填充。
 如果你尝试存储一个长度为 6 的字符串,那么它将被截断为长度为 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: 进行删除的时候,我们可以进行假删除,两个数据库,一个是备份数据库,一个是主数据库。主数据库可以直接删除或者增加标识表示删除。备份数据库,可以不用删除只写入和更新操作,这样可以进行数据还原操作。
此外,为了防止 SQL 数据库注入,大家需要对数据来源进行统一校验处理或者直接进行 encode 处理,对于重要数据可以直接进行 MD5 加密处理,防止数据库被直接下载泄露。关于 SQL 数据库的安全处理,网上教程有很多,大家找一找就可以啦~
部署就比较简单了,我们就不需要一一赘述了,数据库可以用集团提供的云数据库,而 Nest 就是普通的 node 部署。

往期推荐



M2 Ultra可并行运行128个Llama 2 7B流
23岁博士生修复Firefox中的22年 “幽灵老Bug”
DHH锐评 “打包工具”:前端根本不需要构建 (No Build)




这里有最新开源资讯、软件更新、技术干货等内容

点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
Jenkins + Docker 一键自动化部署 SpringBoot 应用最精简流程SpringBoot 分布式验证码登录方案SpringBoot 22 条最佳实践能挣钱的,开源 SpringBoot 和 Vue 的企业级项目,代码很规范!Java近期新闻:Spring Framework 6.1、Spring Data 2023.1、Payara Platform推荐35款 SpringBoot/SpringCloud 开源项目,附源码SpringBoot:一个注解就能帮你下载任意对象忽视日志吃大亏,手把手教你玩转 SpringBoot 日志SpringBoot+Redis BitMap 实现签到与统计功能SpringBoot 实现动态切换数据源,这样做才更优雅!SpringBoot 采用 JsonSerializer 和 Aop 实现可控制的数据脱敏Redis 和 SpringBoot 的绝佳组合:Lua 脚本的黑科技!Netty+SpringBoot 打造一个 TCP 长连接通讯方案微服务框架之争:Quarkus 是 SpringBoot 的替代品吗?【别的女人】诗朗诵 【Other Women】SpringBoot 高效批量插入万级数据,哪种方式最强?AIGC 在前端 Web 开发中的应用:响应式设计和 Tailwind 配置的完美搭档红色日记 2.18-28最动听的生日歌!SpringBoot + Docker 实现一次构建到处运行~SpringBoot AOP + Redis 延时双删功能实战别再自己瞎写工具类了,SpringBoot内置工具类应有尽有,建议收藏!!浅谈领域驱动在前端的应⽤Spring Boot实战 之 MongoDB分片或复制集操作【2023坛庆】 ※ 小茉莉 ※ 太阳出来了 我会来看望你忽视日志吃大亏,手把手教你玩转 SpringBoot 日志!教你用 SpringBoot+Redis BitMap 实现签到与统计功能《湖天一览楼》1部3章(2)安庆起义SpringBoot 2 种方式快速实现分库分表,轻松拿捏!Next.js支持在前端代码中写SQL,开倒车还是遥遥领先?SpringBoot 动态定时任务,配置写死,太Low了忽视日志可要吃大亏,手把手教你玩转 SpringBoot 日志告别繁琐:SpringBoot 拦截器与统一功能处理SpringBoot 接口签名校验实践牢记这16个SpringBoot 扩展接口,写出更加漂亮的代码
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。