0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Java SpringBoot项目:Node服务端搭建

OSC开源社区 来源:OSCHINA 社区 2023-11-02 14:56 次阅读

来源| OSCHINA 社区

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

前言

玩归玩,闹归闹,别拿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,想想应该会很容易上手,顺便怀旧下写写。

说下我的想法,首先我们刚入门,估计会有一堆不清楚的坑,我们先简单点,后续我们再继续加深。既然要搞服务端,要搞就多搞点,我们都去尝鲜玩玩。我们打算使用Nest作为前端框架,Graphql作为中间处理层。底层数据库我们用传统的MySQL,比较稳定可靠,而且相对比较熟悉,这个就不玩新的了,毕竟数据库是一切的基石 。

说下我们具体实现步骤:

【必须】没有任何数据库,完成接口请求运行,能够跑起来;

【必须】创建基础数据库MySQL,接入@nestjs/sequelize库 完成增删改查功能即:CRUD

【可选】打算采取Graphql处理 API 查询,做到精确数据查询,这个已经火了很多了,但是真正使用的很少,我们打算先感受下,后续可以直接用到业务。

【可选】接入Swagger自动生成 API 文档,快捷进行前端与后端服务联调测试。

◦ Swagger 是一个开源工具,用于设计、构建、记录和使用 RESTful web 服务。

【可选】接口请求,数据库优化处理

◦ 请求分流,数据库写入加锁,处理并发流程

◦ 增加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 相关声明类型

085b79c0-6c1f-11ee-939d-92fbcf53809c.jpg


只是写 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{
    // 可以处理判空,从数据库读取/写入数据,可能会被多个 controller 进行调用
    console.info('ReadUserService findByName > ', name);
    returnPromise.resolve({ id:1, name, job:'程序员', gender:1, age:18});
  }

  asyncfindById(id:number):Promise{
    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(AppModule);
  // 监听端口 3000 
  await app.listen(3000);
}

bootstrap();


这样一个单机的服务器就启动起来了,我们可以使用Postwoman 进行请求,瞅瞅看返回效果。

085f59fa-6c1f-11ee-939d-92fbcf53809c.jpg086f61ce-6c1f-11ee-939d-92fbcf53809c.jpg
控制台也收到日志了,后面可以把这些日志请求保留成.log文件,这样请求日志也有了,完美!下一步,我们开始连接数据库,这样就不用单机玩泥巴了~

第二步、配置 MySQL

MySQL 安装其实很简单,我电脑是 Mac 的,所以下面的截图都是以 mac 为例,先下载对应的数据库。


至于其他系统的,可以网上找教程,这个应该烂大街了,我就不重复搬运教程了。

注意:安装的数据库,一定要设置密码,连接数据库必须要有密码,否则会导致连接数据库失败。


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


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

◦◦ 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 查看状态。

087ad82e-6c1f-11ee-939d-92fbcf53809c.jpg 


1.创建数据库 088315ac-6c1f-11ee-939d-92fbcf53809c.jpg 


数据库存在字符集选择,不同的字符集和校验规则,会对存储数据产生影响,所以大家可以自行查询,按照自己存储数据原则选择,我这里默认选最广泛的。确认好,就选择右下角的应用按钮。


2.创建表和属性 088d0e5e-6c1f-11ee-939d-92fbcf53809c.jpg 选项解答:

•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{
  @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>{
    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! 0890f44c-6c1f-11ee-939d-92fbcf53809c.jpg089bd592-6c1f-11ee-939d-92fbcf53809c.jpg
连接及创建数据成功!此时已经完成基础功能啦~

第三步、实现CRUD基础功能

剩下的内容,其实大家可以自行脑补了,就是调用数据库的操作逻辑。先说说什么是CRUD

Ccreate 创建

Rread 读取

Uupdate 更新

Ddelete 删除

下面给个简单示例,大家看看,剩下就去找文档,实现业务逻辑即可:

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 部署。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2904

    浏览量

    103001
  • 数据库
    +关注

    关注

    7

    文章

    3591

    浏览量

    63377
  • MySQL
    +关注

    关注

    1

    文章

    775

    浏览量

    26006
  • 服务端
    +关注

    关注

    0

    文章

    62

    浏览量

    6871
  • SpringBoot
    +关注

    关注

    0

    文章

    172

    浏览量

    106

原文标题:我在前端写Java SpringBoot项目

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    TCP服务端测试工具

    本帖最后由 小子个 于 2024-3-20 22:58 编辑 该TCP服务端工具可以理解为 “TCP服务端” 或者 “服务器” ,目的是帮助大家在没有服务器的情况下,完成客户
    发表于 06-29 09:22

    俩台电脑进行TCP连接时,服务端崩溃

    在同一台电脑上可以正常使用。俩台电脑之间就不行了。运行服务端和客户,几秒种后服务端就自行崩溃了。(不是停止运行,是整个labview崩溃)。XP和win7,WIN7和win7的都试过不行。想问问是怎么回事。
    发表于 10-28 22:52

    用于开发ModbusTCP服务端的DLL库文件

    本帖最后由 一只耳朵怪 于 2018-6-11 11:34 编辑 ModbusTCP服务端的开发包,根据该开发包可在Windows系统下快速实现服务端,建立数据仿真系统,为组态软件、触摸屏、PC高级语言等通讯等提供数据接口,也可实现系统本地数据交换;兼容x86/x6
    发表于 06-11 11:17

    服务端视角看高并发难题

    `所谓服务器大流量高并发指的是:在同时或极短时间内,有大量的请求到达服务端,每个请求都需要服务端耗费资源进行处理,并做出相应的反馈。 从服务端视角看高并发
    发表于 11-02 15:11

    BTC设备服务器的系统搭建

    服务端的实时通信保持通话,它不像 ajax 请求,每次对话完成后都会把连接断开。Socket 通信在 Node.js 中实现其实很简单,没有想象中复杂,基本上只要懂得监听(.on)和推送(.emit
    发表于 09-24 09:05

    TCP服务端的实现

    Swoole TCP服务端与客户 持续更新
    发表于 09-26 16:04

    那些年,我们见过的 Java 服务端“问题”

    一间房。是的,知识理论哪有什么精粗之分,只是人的认识程度不同而已。笔者在初创公司摸爬滚打数年,接触了各式各样的Java服务端架构,见得多了自然也就认识深了,就能分辨出各种方案的优劣了。这里,笔者总结
    发表于 10-12 15:12

    TCP通信时服务端如何接收客户的数据?

    毕设采用的是TCP协议,组员做的是下位机,C编程,WiFi模块工作处于客户。我负责上位机,Labview使用tcp协议时服务端怎么接收客户的数据呢? 我找到的例程都是服务端发、客户
    发表于 04-14 14:49

    寻找!嵌入式linux利用web服务器升级

    利用http搭建一个服务器,通过web操作,+后台服务端+终端升级嵌入式linux通过服务端后台对设备进行远程升级升级内容:内核、驱动、文
    发表于 06-24 11:11

    监控系统客户服务端设计

    项目开发报告1 项目简介1.1 概述1.2 开发环境1.3 其他支持1.4 应用界面1.4.1 服务器端1.4.2 客户1.5 程序使用2 项目
    发表于 12-21 07:02

    ARM服务器准备如何解决服务端渲染的问题

    针对云手机、视频流云游戏行业,ARM服务器准备如何解决服务端渲染的问题?目前的状况了解,PCIE显卡对安卓游戏的支持还不够成熟
    发表于 09-13 14:58

    使用6300EVK板子搭建LWIP TCP服务端,在响应客户回传数据时无规律性丢包的原因?

    使用官方6300EVK板子 搭建 LWIP TCP服务端在响应客户回传数据时无规律性丢包电脑PING连接也会丢包
    发表于 05-26 08:10

    java开发者现在可以使用Google的PaaS云服务来构建网页应用

    Node.js。之前,Google的云服务支持Java,Python,PHP和Go。从这周开始,将会以测试形式提供对Java服务端的支持。开
    发表于 10-11 17:28 0次下载

    在远程服务器上搭建一个OpenVPN服务端

    应公司要求需要在一台远程服务器上搭建一个OpenVPN服务端,在公司本地服务器上搭建一个OpenVPN客户端,实现公司内网可以访问远程
    的头像 发表于 05-26 15:03 4621次阅读
    在远程<b class='flag-5'>服务</b>器上<b class='flag-5'>搭建</b>一个OpenVPN<b class='flag-5'>服务端</b>

    Node搭建本地MQTT服务

    Tips:点击上方“在线学习”按钮即可在线观看精品IT课程大家好,我是牛牛。今天给分享一下使用Node搭建MQTT服务端的流程,主要包含以下几部分内容:MQTT介绍MQTT基本原理及相关概念N
    的头像 发表于 05-26 14:11 964次阅读
    <b class='flag-5'>Node</b><b class='flag-5'>搭建</b>本地MQTT<b class='flag-5'>服务</b>器