我在前端写Java SpringBoot项目

前端,java,springboot,项目 · 浏览次数 : 0

小编点评

**代码生成内容** * 获取当前时间 * 创建新日志对象 * 将日志对象写入到数据库中 * 获取所有日志对象 * 按要求查找单个日志对象 * 按要求更新日志对象 * 按要求删除日志对象 * 获取所有日志对象 * 按要求查找单个日志对象 * 按要求更新日志对象 * 按要求删除日志对象 * 获取所有日志对象 * 按要求查找单个日志对象 * 按要求更新日志对象 * 按要求删除日志对象 * 获取所有日志对象 * 按要求查找单个日志对象 * 按要求更新日志对象 * 按要求删除日志对象 * 生成内容时需要带简单的排版

正文

前言

玩归玩,闹归闹,别拿 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,比较稳定可靠,而且相对比较熟悉,这个就不玩新的了,毕竟数据库是一切的基石 。

说下我们具体实现步骤:

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

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

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

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

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

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

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

◦ 增加 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')
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.ts
import { 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 Transfer Object
// packages/node-server-demo/src/dto/user.ts
export class CreateUserDto {
  name: string;
  age: number;
  gender: string;
  job: string;
}

// 可以分开写,也可以合并
export class GetUserDto {
  id?: number;
  name: string;
}

// 可以分开写,也可以合并
export class GetUserInfoDto {
  id: number;
}




  • service 数据库交互处理类
// packages/node-server-demo/src/service/user/read.ts
import { 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 模块注册,服务类/控制类
// 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],
})
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 启动注册的所有类
// 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.创建数据库

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

  1. 创建表和属性

选项解答:

PRIMARY KEY 是表中的一个或多个列的组合,它用于唯一标识表中的每一行。

Not NULLUnique 就不解释,就是直译的那个意思。

GENERATED 生成列是表中的一种特殊类型的列,它的值不是从插入语句中获取的,而是根据其他列的值通过一个表达式或函数生成的。

CREATE TABLE 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

◦ 主要用途是存储那些需要按字节进行比较的数据,例如加密哈希值

  • 此外也可顺手传创建一个索引,方便快速查找。
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);



  1. 连接数据库

由于目前 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,
  ],
})
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.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],
})
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 部署。

作者:京东零售 周明亮

来源:京东云开发者社区 转载请注明来源

与我在前端写Java SpringBoot项目相似的内容:

我在前端写Java SpringBoot项目

本篇文章主要是使用 NestJs + Sequelize + MySQL 完成基础运行, 带大家了解 Node 服务端的基础搭建,也可以顺便看看 Java SpringBoot 项目的基础结构,它俩真的非常相似,不信你去问服务端开发同学。

我的第一个项目(三):注册登陆功能(后端)

好家伙,前端出了点bug 我们来搞定后端先: 后端我们用的框架是Spring boot 数据库:MySQl 代码已开源,连接在最后 新建项目: 只点Java Web 项目目录如下: 1.首先,我们在pom.xml文件中导入第三方包: web服务,mysql连接驱动等一系列包 pom.xml文件: <

《HelloTester》第4期

1.前言 终于到了谈面试的部分了! 我在这也说明一下,有同学说之前简历篇的时候一直在说项目的介绍,而面试官真正关心的是技术啊?我在这做个解释,因为我写的这些文章主要针对的是软件测试的同学,所以其他职位的请根据自己的情况来改,比如你是面的前端或者java等,那当然要突出你在编程中的表现了! 首先来说,

彻底理解闭包实现原理

前言 闭包对于一个长期写 Java 的开发者来说估计鲜有耳闻,我在写 Python 和 Go 之前也是没怎么了解,光这名字感觉就有点"神秘莫测",这篇文章的主要目的就是从编译器的角度来分析闭包,彻底搞懂闭包的实现原理。 函数一等公民 一门语言在实现闭包之前首先要具有的特性就是:First class

实战0-1,Java开发者也能看懂的大模型应用开发实践!!!

前言 在前几天的文章《续写AI技术新篇,融汇工程化实践》中,我分享说在RAG领域,很多都是工程上的实践,做AI大模型应用的开发其实Java也能写,那么本文就一个Java开发者的立场,构建实现一个最基础的大模型应用系统。 而大模型应用系统其实在目前阶段,可能应用最广的还是RAG领域,因此,本文也是通过

提取关键词作为标题---Java调用Python实现

[TOC] # 前景提示 * 一个朋友参加面试,在成都面的一家,问我如何给一篇没有标题的文章取个标题,是根据内容分析内容,然后获取标题,写个程序让程序分析内容,提炼出一个最适合的标题. * 提示:先找出高频率的关键词,然后再根据段首段尾段中的不同权重结合同一个关键词出现的频率来综合判断,最后取一个权

阶段总结与展望——我的简历

我是一位刚进大二的大学生,在大数据这个专业已经学习了一年多了。 目前,对c++、python、java等编程语言有所了解,能够编写出一些基本的程序,当前接触了Linux操作系统、爬虫操作、Web前端、数据库,有一定的编写程序能力;在校期间获得了“python程序设计一等奖”。 在这门课程结束后,你将

9.前端初步设计

空了挺长时间没写了。一些琐事耽误,然后另一方面就是在写前端。因为我不是学前端出身,所以前端相对比较弱,一下子我也搞的不是很全面,最主要的是没法讲的很细致,前端这东西吧,都说简单,但是想做的很好看那还是需要一些深入学习的。上一篇的文章是邮箱发送,意在做注册的时候发送验证码。现在页面基本完成了,毕竟登录

C#实现生成Markdown文档目录树

前言 之前我写了一篇关于C#处理Markdown文档的文章:C#解析Markdown文档,实现替换图片链接操作 算是第一次尝试使用C#处理Markdown文档,然后最近又把博客网站的前台改了一下,目前文章渲染使用Editor.md组件在前端渲染,但这个插件生成的目录树很丑,我魔改了一下换成boots

我真的从测试转成了开发......

写在前面 因为走的圈太大了,早上上班差点迟到,幸好有我每日5公里的加持,侥幸踩点进办公室,哈哈,真的好险! 我开发的功能不能用了 上午开始着手某功能的开发,还在写后台逻辑。 结果到了下午,由于前端同学的代码冲突,打包发布后,导致我写的功能直接不能用了,瞬间emo了! 套用我之前同事总说的一句话就是: