I write Java SpringBoot project on the front end



Preface
Let's have fun, let's make trouble, don't make fun of the C-side! It is not recommended that you use the Node service as a C-side service. After all, it is a single-threaded multi-tasking mechanism. This feature determines the mission of the Javascript language from the beginning of its design - Java >>>[Script]. I won’t explain it much here. Just look at the history of JavaScript and you will know. This also determines its mission. It cannot be multi-threaded and multi-tasked like a back-end language. It can tolerate a small amount of user visits. But once it withstands a large number of visits and high concurrency, it will be cool~
So why do we still need to write Node services? The main reason is that it is convenient and fast. For small projects, construction can be completed quickly and the development cost is small. Secondly, the following gains are achieved mainly by writing Nest:
  • Learn the decorator syntax and feel its simplicity and beauty;
  • Learn a new development framework by yourself, feel the advantages and disadvantages of different frameworks, and lay the foundation for future development and selection;
  • Feel the complexity of server-side troubleshooting and find inspiration for front-end design.
This article mainly uses NestJs + Sequelize + MySQL to complete the basic operation. It will show you the basic construction of the Node server. You can also take a look at the basic structure of the Java SpringBoot project. They are really very similar. If you don’t believe me, ask about server development. classmate.


Step one: start the project

When choosing the server, I have used Egg.js before, so I won’t choose it this time. Secondly, Egg also inherits the development foundation of Koa, and Express is also innovative based on Koa. The two should be similar, so I don't choose Koa and Express.
Therefore, I want to try Nest.js to see that the syntax is the same as Java. In addition, I have developed a Java + SpringBoot project before. Of course, the older SSH 2.0 has also been built from scratch, that is: Spring2.0 + Struts2+Hibernate3.2, I think it should be easy to get started, and I will write about it in retrospect.
Reference documentation:
  •  https://www.geeksforgeeks.org/best-nodejs-frameworks-for-app-development/ 
  •  https://anywhere.epam.com/business/best-node-js-frameworks 
Let me tell you my thoughts. First of all, we have just started, and there will probably be a lot of unclear pitfalls. Let’s keep it simple first, and we will continue to deepen it later. Since we want to develop the server side, if we want to do it, do more. Let’s all try it out and have fun. We plan to use Nest as the front-end framework and Graphql as the middle processing layer. We use the traditional MySQL as the underlying database, which is relatively stable, reliable, and relatively familiar. I won’t use anything new here. After all, the database is the cornerstone of everything.
Let’s talk about our specific implementation steps:
  1. [Required] There is no database, the interface request is completed, and it can run;
  2. [Required] Create the basic database MySQL and access the @nestjs/sequelize library to complete the add, delete, modify and query functions: CRUD
  3. [Optional] We plan to use Graphql to process API queries to achieve accurate data query. This has been popular for a long time, and we plan to use it directly in business in the future.
  4. [Optional] Connect to Swagger to automatically generate API documents and quickly conduct joint debugging and testing of front-end and back-end services. Swagger is an open source tool for designing, building, documenting and consuming RESTful web services.
  5. [Optional] Interface request, database optimization processing
    1. Request offloading, database write locking, and concurrent process processing
    2. Add middleware to uniformly process requests and responses, perform authentication processing, request interception and other operations
    3. Database split backup, database disaster recovery processing, divided into: primary, backup, disaster
    4. Database reading and writing are separated, data is double-written, a database caching mechanism is established, and redis is used for processing.
You are also welcome to add more optimization points, and we will discuss them together~ If you are interested, you can help add code~
After determining the general direction, we started to organize. Let’s not pursue one step, otherwise the more things we add, the more chaos we will get. We can add icing on the cake later, and we must prioritize the completion of basic functions. Nest.js official website: https://docs.nestjs.com/ Without further ado, let’s get straight to the details.
  
  
  
  
  
# 进入文件夹目录cd full-stack-demo/packages# 安装脚手架npm i -g @nestjs/cli# 创建基础项目nest new node-server-demo # 进入项目 cd new node-server-demo # 运行项目测试npm run start:dev
Let's remove some unnecessary things, start simple and then complex, don't get yourself confused. Next, I will write a simple example to get a feel for this framework, and then I will publish the complete code later. Without further ado, let’s get started! Directory structure after adjustment:
  • common - public method class
  • config - configuration class file
  • controller - controller, used to handle various requests initiated by the front end
  • service - service class, used to handle interaction logic with the database
  • dto - DTO (Data Transfer Object) can be used to validate input data and limit the fields or formats transferred.
  • entities - entity class, used to describe object-related attribute information
  • module - module, used to register all service classes and controller classes, similar to beans in Spring

These are not completely equivalent. The two implementation mechanisms are different. It is just to help everyone understand.

  • main.ts - nest startup entry

  • types - typescript related declaration types

 I’m just writing a demo, so I don’t have to write comments if I hurry up. I feel that I can understand it at a glance. It is very consistent with the writing method of Java SpringBoot. Part of the code is shown:
  • controllercontroller
  
  
  
  
  
// packages/node-server-demo/src/controller/user/index.tsimport { Controller, Get, Query } from '@nestjs/common';import UserServices from '@/service/user';import { GetUserDto, GetUserInfoDto } from '@/dto/user';
@Controller('user')export class UserController { constructor(private readonly userService: UserServices) {}
// Get 请求 user/name?name=bricechou @Get('name') async findByName(@Query() getUserDto: GetUserDto) { return this.userService.read.findByName(getUserDto.name); } // Get 请求 user/info?id=123 @Get('info') async findById(@Query() getUserInfoDto: GetUserInfoDto) { const user = await this.userService.read.findById(getUserInfoDto.id); return { gender: user.gender, job: user.job }; }}
// packages/node-server-demo/src/controller/log/add.tsimport { Controller, Post, Body } from '@nestjs/common';import { AddLogDto } from '@/dto/log';import LogServices from '@/service/log';
@Controller('log')export class CreateLogController { constructor(private readonly logServices: LogServices) {}
// post('/log/add') @Post('add') create(@Body() createLogDto: AddLogDto) { return this.logServices.create.create(createLogDto); }}

  • Data TransferData Transfer Object
  
  
  
  
  
// packages/node-server-demo/src/dto/user.tsexport class CreateUserDto {  name: string;  age: number;  gender: string;  job: string;}
// 可以分开写,也可以合并export class GetUserDto { id?: number; name: string;}
// 可以分开写,也可以合并export class GetUserInfoDto { id: number;}

  • service database interaction processing class
  
  
  
  
  
// packages/node-server-demo/src/service/user/read.tsimport { Injectable } from '@nestjs/common';import { User } from '@/entities/User';
@Injectable()export class ReadUserService { constructor() {}
async findByName(name: string): Promise<User> { // 可以处理判空,从数据库读取/写入数据,可能会被多个 controller 进行调用 console.info('ReadUserService findByName > ', name); return Promise.resolve({ id: 1, name, job: '程序员', gender: 1, age: 18 }); }
async findById(id: number): Promise<User> { console.info('ReadUserService findById > ', id); return Promise.resolve({ id: 1, name: 'BriceChou', job: '程序员', gender: 1, age: 18, }); }}

  • module module registration, service class/control class
  
  
  
  
  
// packages/node-server-demo/src/module/user.tsimport { Module } from '@nestjs/common';import UserService, { ReadUserService } from '@/service/user';import { UserController } from '@/controller/user';
@Module({ providers: [UserService, ReadUserService], controllers: [UserController],})export class UserModule {}

// 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, ],})export class AppModule {}
  • main.js starts all registered classes
  
  
  
  
  
// packages/node-server-demo/src/main.tsimport { 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();
In this way, a stand-alone server is started. We can use Postwoman [ https://hoppscotch.io/ ] to make a request and see the return effect.
 The console has also received the logs. These log requests can be saved as .log files later, so that the request logs are also available, perfect! Next, we start to connect to the database, so that we don’t have to play in the mud on a single machine~


Step 2: Configure MySQL

MySQL installation is actually very simple. My computer is a Mac, so the screenshots below are all based on a Mac. First download the corresponding database.
Download address: https://dev.mysql.com/downloads/mysql/ As for other systems, you can find tutorials online. This one is probably pretty common, so I won’t repeat the tutorial.
  • Note: You must set a password for the installed database, and you must have a password to connect to the database, otherwise the database connection will fail.
  • For MySQL, we only need to install the database. If you are familiar with the instructions, you can just operate it directly from the command line.
  • If you are not familiar with it, then download the graphical management tool.
Mysql official console https://dev.mysql.com/downloads/workbench/ 
Windows can also use https://www.heidisql.com/download.php?download=installer 
PS: When installing workbench, I found that MacOS 13 or above is required, and my computer is 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 生成列是表中的一种特殊类型的列,它的值不是从插入语句中获取的,而是根据其他列的值通过一个表达式或函数生成的。
  
  
  
  
  
CREATE TABLE people (    first_name VARCHAR(100),    last_name VARCHAR(100),    full_name VARCHAR(200) AS (CONCAT(first_name, ' ', last_name)));
  • UNS IGNED 这个数值类型就只能存储正数(包括零),不会存储负数。
  • ZEROFILL 将数值类型的字段的前面填充零,他会自动使字段变为 UNSIGNED,直到该字段达到声明的长度,如: 00007
  • BINARY 用于存储二进制字符串,如声明一个字段为 BINARY(5),那么存储在这个字段中的字符串都将被处理为长度为 5 的二进制字符串。
如尝试存储一个长度为 3 的字符串,那么它将在右侧用两个空字节填充。
如果你尝试存储一个长度为 6 的字符串,那么它将被截断为长度为 5
主要用途是存储那些需要按字节进行比较的数据,例如加密哈希值
  • 此外也可顺手创建一个索引,方便快速查找。
  
  
  
  
  
CREATE TABLE `rrweb`.`test_sys_req_log` (  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,  `content` TEXT NOT NULL,  `l_level` INT UNSIGNED NOT NULL,  `l_category` VARCHAR(255) NOT NULL,  `l_created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,  `l_updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,  PRIMARY KEY (`id`),  UNIQUE INDEX `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# 安装 typenpm install --save-dev @types/sequelize
  • 配置数据库基础信息
  
  
  
  
  
// packages/node-server-demo/src/module/index.tsimport { 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, ],})export class AppModule {}

  • 实体与数据库一一映射处理
  
  
  
  
  
import { getNow } from '@/common/date';import {  Model,  Table,  Column,  PrimaryKey,  DataType,} from 'sequelize-typescript';
@Table({ tableName: 'test_sys_req_log' })export class Log extends Model<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.tsimport { 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],})export class LogModule {}

  • 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()export class CreateLogService { constructor( @InjectModel(Log) private logModel: typeof Log,) {}
async create(createLogDto: AddLogDto): Promise<ResponseStatus<null>> { console.info('CreateLogService create > ', createLogDto); const { level = 1, content = '', category = 'INFO' } = createLogDto || {}; const str = content.trim(); if (!str) { return getErrRes(500, '日志内容为空'); } const item = { level, category, // Tips: 为防止外部数据进行数据注入,我们可以对内容进行 encode 处理。 // content: encodeURIComponent(str), content: str, }; await this.logModel.create(item); return getSucVoidRes(); }}
一路操作猛如虎,回头一看嘿嘿嘿~终于,我们收到了来自外界的第一条数据!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()export class UserService { constructor( @InjectModel(User) private userModel: typeof User,) {} // 创建新数据 async create(user: User) { const newUser = await this.userModel.create(user); return newUser; } // 查找所有数据 async findAll() { return this.userModel.findAll(); } // 按要求查找单个 async findOne(id: string) { return this.userModel.findOne({ where: { id } }); } // 按要求更新 async update(id: string, user: User) { await this.userModel.update(user, { where: { id } }); return this.userModel.findOne({ where: { id } }); } // 按要求删除 async delete(id: string) { const user = await this.userModel.findOne({ where: { id } }); await user.destroy(); }}
Tips: 进行删除的时候,我们可以进行假删除,两个数据库,一个是备份数据库,一个是主数据库。主数据库可以直接删除或者增加标识表示删除。备份数据库,可以不用删除只写入和更新操作,这样可以进行数据还原操作。
此外,为了防止 SQL 数据库注入,大家需要对数据来源进行统一校验处理或者直接进行 encode 处理,对于重要数据可以直接进行 MD5 加密处理,防止数据库被直接下载泄露。关于 SQL 数据库的安全处理,网上教程有很多,大家找一找就可以啦~
部署就比较简单了,我们就不需要一一赘述了,数据库可以用集团提供的云数据库,而 Nest 就是普通的 node 部署。
-end-

本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

雷军:小米全新操作系统澎湃 OS 正式版已完成封包 国美 App 抽奖页面弹窗辱骂其创始人 美国政府限制向中国出口 NVIDIA H800 GPU 小米澎湃OS界面曝光 大神用 Scratch 手搓 RISC-V 模拟器,成功运行 Linux 内核 RustDesk 远程桌面 1.2.3 发布,增强 Wayland 支持 拔出罗技 USB 接收器后,Linux 内核竟然崩溃了 DHH 锐评“打包工具”:前端根本不需要构建 (No Build) JetBrains 推出 Writerside,创建技术文档的工具 Node.js 21 正式发布
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10120392