一、Midway 增删改查

阅读本文前,需要提前阅读前置内容:

一、Midway 增删改查
二、Midway 增删改查的封装及工具类
三、Midway 接口安全认证
四、Midway 集成 Swagger 以及支持JWT bearer
五、Midway 中环境变量的使用

midway是阿里巴巴开源的,基于TypeScript语言开发的Nodejs后端框架。
本教程指导大家从0开始搭建一个midway项目。

其遵循遵循两种编程范式

  • 面向对象(OOP + Class + IoC);
  • 函数式(FP + Function + Hooks);

谁较容易上手学习

  • 懂Nodejs技术的前端开发;
  • 会TypeScript的后端开发;

在这里你可以掌握度如下知识

  • 面向对象的开发体验;
  • 增删改查及基类封装;
  • 数据库操作;
  • 缓存操作;
  • 用户安全认证及访问安全控制;
  • JWT访问凭证;
  • 分布式访问状态管理;
  • 密码加解密;
  • 统一返回结果封装;
  • 统一异常管理;
  • Snowflake主键生成;
  • Swagger集成及支持访问认证;
  • 环境变量的使用;
  • Docker镜像构建;
  • Serverless发布;

本项目源码

https://github.com/bestaone/midway-boot

LIVE DEMO

http://midway-boot.hiauth.cn/swagger-ui/index.html

环境准备

  • Nodejs 12+
  • Npm 8+
  • MySql 5+
  • Redis

开发工具

我们这里使用 IntelliJ IDEA

下载地址:https://www.jetbrains.com/zh-cn/idea/download

安装数据库

略...

安装Redis

略...

第一个midway项目

初始化创建

>npm init midway
  • 执行命令后,需要选择模板,标准项目需要选择:koa-v3;
  • 项目名可以自定义(我这里设置为midway-boot);

启动

>cd midway-boot
>npm run dev

启动后浏览器访问:http://127.0.0.1:7001

调整ESLint配置

为了保证代码分隔统一,我们调整下ESLint配置

// .prettierrc.js
module.exports = {
  ...require('mwts/.prettierrc.json'),
  endOfLine: "lf",        // 换行符使用 lf
  printWidth: 120,        // 一行最多 120 字符
  proseWrap: "preserve",  // 使用默认的折行标准
  semi: true,             // 行尾需要有分号
}

在windows中代码的首行、尾行不能有空行,否则ESLint提示格式错误,可能是bug。

项目结构介绍

├─src                     # 源码目录
│  ├─config               # 配置
│  ├─controller           # 控制器
│  ├─entity               # 数据对象模型
│  ├─filter               # 过滤器
│  ├─middleware           # 中间件
│  ├─service              # 服务类
│  ├─configurations.ts    # 服务生命周期管理及配置
│  └─interface.ts         # 接口定义
├─test                    # 测试类目录
├─bootstrap.js            # 启动入口
├─package.json            # 包管理配置
├─tsconfig.json           # TypeScript 编译配置文件

增删改查

ORM组件:TypeORM

TypeORM是Object Relation Mapping工具,提供的数据库操作能力。

安装依赖

>npm i @midwayjs/orm@3 typeorm --save

安装完后package.json文件中会多出如下配置

{
  "dependencies": {
    "@midwayjs/orm": "^3.3.6",
    "typeorm": "^0.3.7"
  }
}

引入组件

src/configuration.ts中引入 orm 组件

// configuration.ts
import { Configuration, App } from '@midwayjs/decorator';
import * as koa from '@midwayjs/koa';
import * as validate from '@midwayjs/validate';
import * as info from '@midwayjs/info';
import { join } from 'path';
import { ReportMiddleware } from './middleware/report.middleware';
import * as orm from '@midwayjs/orm';

@Configuration({
  imports: [
    orm, // 引入orm组件
    koa,
    validate,
    {
      component: info,
      enabledEnvironment: ['local'],
    },
  ],
  importConfigs: [join(__dirname, './config')],
})
export class ContainerLifeCycle {
  @App()
  app: koa.Application;

  async onReady() {
    this.app.useMiddleware([ReportMiddleware]);
  }
}

添加数据库配置

修改配置src/config/config.default.ts

// src/config/config.default.ts
import { MidwayConfig } from '@midwayjs/core';

export default {
  keys: '1657707214114_9253',
  koa: {
    port: 7001,
  },
  // 添加orm配置
  orm: {
    type: 'mysql',
    host: '127.0.0.1',      // 改成你的mysql数据库IP
    port: 3306,             // 改成你的mysql数据库端口
    username: 'root',       // 改成你的mysql数据库用户名(需要有创建表结构权限)
    password: '123456',     // 改成你的mysql数据库密码
    database: 'midway_boot',// 改成你的mysql数据库IP
    synchronize: true,      // 如果第一次使用,不存在表,有同步的需求可以写 true
    logging: true,
  },
} as MidwayConfig;

注意:首次启动没有创建表结构的,需要设置自动创建表接口synchronize: true

安装MySql驱动

>npm install mysql2 --save

安装完后package.json文件中会多出如下配置

{
  "dependencies": {
    "mysql2": "^2.3.3"
  }
}

orm的详细文档见:http://www.midwayjs.org/docs/extensions/orm

Entity、Service、Controller

创建Entity实体类

  • 创建目录src/entity;
  • 在该目录下创建实体类user.ts;
// src/entity/user.ts
import { EntityModel } from '@midwayjs/orm';
import {
  Column,
  CreateDateColumn,
  PrimaryColumn,
  UpdateDateColumn,
} from 'typeorm';

@EntityModel('user')
export class User {

  @PrimaryColumn({ type: 'bigint' })
  id: number;

  @Column({ length: 100, nullable: true })
  avatarUrl: string;

  @Column({ length: 20, unique: true })
  username: string;

  @Column({ length: 200 })
  password: string;

  @Column({ length: 20 })
  phoneNum: string;

  @Column()
  regtime: Date;

  @Column({ type: 'bigint' })
  updaterId: number;

  @Column({ type: 'bigint' })
  createrId: number;

  @CreateDateColumn()
  createTime: Date;

  @UpdateDateColumn()
  updateTime: Date;

  @Column({ type: 'int', default: 1 })
  status: number;

}
  • @EntityModel 用来定义一个实体类;
  • @Column 用来描述类的一个熟悉,对应数据库就是一个数据列;
  • @PrimaryColumn 用来定义一个主键,每个实体类必须要要主键;
  • @PrimaryGeneratedColumn 用来定义一个自增主键;
  • @CreateDateColumn 定义创建时,自动设置日期;
  • @UpdateDateColumn 定义更新时,自动设置日期;

对应的数据库结构

CREATE TABLE `user` (
  `id` bigint NOT NULL,
  `avatarUrl` varchar(100) DEFAULT NULL,
  `username` varchar(20) NOT NULL,
  `password` varchar(200) NOT NULL,
  `phoneNum` varchar(20) NOT NULL,
  `regtime` datetime NOT NULL,
  `updaterId` bigint NOT NULL,
  `createrId` bigint NOT NULL,
  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
  `status` int NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`),
  UNIQUE KEY `IDX_78a916df40e02a9deb1c4b75ed` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建UserService

创建或者修改src/service/user.service.ts文件。

// src/service/user.service.ts
import { Provide } from '@midwayjs/decorator';
import { User } from '../eneity/user';
import { InjectEntityModel } from '@midwayjs/orm';
import { Repository } from 'typeorm';
import { DeleteResult } from 'typeorm/query-builder/result/DeleteResult';

@Provide()
export class UserService {

  @InjectEntityModel(User)
  userModel: Repository<User>;

  async create(user: User): Promise<User> {
    return this.userModel.save(user);
  }

  async findById(id: number): Promise<User> {
    return this.userModel.findOneBy({ id });
  }

  async delete(id: number): Promise<DeleteResult> {
    return this.userModel.delete(id);
  }

}
  • @Provide 表示这个类将会由系统自动实例化,在使用的时候,只需要使用@Inject注入就可以了;
  • @InjectEntityModel 注入实体模型数据库操作工具;

注意:由于调整了UserService,src/controller/api.controller.tstest/controller/api.test.ts会报错,直接删掉即可

创建UserController

创建或者修改src/controller/user.controller.ts文件。

// src/controller/user.controller.ts
import { Inject, Controller, Query, Post, Body } from '@midwayjs/decorator';
import { User } from '../eneity/user';
import { UserService } from '../service/user.service';
import { DeleteResult } from 'typeorm/query-builder/result/DeleteResult';

@Controller('/api/user')
export class UserController {
  @Inject()
  userService: UserService;

  @Post('/create', { description: '创建' })
  async create(@Body() user: User): Promise<User> {
    Object.assign(user, {
      id: new Date().getTime(),
      regtime: new Date(),
      updaterId: 1,
      createrId: 1,
    });
    return this.userService.save(user);
  }

  @Post('/findById', { description: '通过主键查找' })
  async findById(@Query('id') id: number): Promise<User> {
    return this.userService.findById(id);
  }

  @Post('/delete', { description: '删除' })
  async delete(@Query('id') id: number): Promise<DeleteResult> {
    return this.userService.delete(id);
  }
}
  • @Inject()装饰类指定该对象会被自动注入;

单元测试

添加单元测试类

添加文件test/controller/user.test.ts

// test/controller/user.test.ts
import {close, createApp, createHttpRequest} from '@midwayjs/mock';
import {Application, Framework} from '@midwayjs/koa';
import {User} from '../../src/eneity/user'

describe('test/controller/user.test.ts', () => {

  let app: Application;
  let o: User;

  beforeAll(async () => {
    try {
      app = await createApp<Framework>();
    } catch(err) {
      console.error('test beforeAll error', err);
      throw err;
    }
  });

  afterAll(async () => {
    await close(app);
  });

  // create
  it('should POST /api/user/create', async () => {
    o = new User();
    Object.assign(o, {
      username: new Date().getTime().toString(),
      password: new Date().getTime().toString(),
      phoneNum: new Date().getTime().toString(),
    });
    const result = await createHttpRequest(app).post('/api/user/create')
      .send(o);
    expect(result.status).toBe(200);
    // 将创建好的数据存起来,以供后面测试使用(返回的数据会有id)
    o = result.body;
  });

  // findById
  it('should POST /api/user/findById', async () => {
    const result = await createHttpRequest(app).post('/api/user/findById?id=' + o.id);
    expect(result.status).toBe(200);
  });

  // delete
  it('should POST /api/user/delete', async () => {
    const result = await createHttpRequest(app).post('/api/user/delete?id=' + o.id);
    expect(result.status).toBe(200);
  });
});
  • beforeAllafterAll 分别会在测试开始前、后执行;
  • createApp<Framework>() BeforeAll阶段的error会忽略,需要手动处理异常;

单元测试的详细文档,见:http://www.midwayjs.org/docs/testing

执行单元测试

>npm run test

如果测试时间过长,会导致测试失败,那么我们需要修改超时时间

修改测试类的超时时间

  • 在根目录中添加文件jest.setup.js;
// jest.setup.js
// 只需要一行代码
// 设置单元测试超时时间
jest.setTimeout(60000);
  • 修改jest配置文件jest.config.js;
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
  coveragePathIgnorePatterns: ['<rootDir>/test/'],
  // 添加如下一行代码,引入jest初始化文件
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js']
};

开发调试

IntelliJ IDEA中Debug

  • 运行/调试配置


    运行/调试配置
  • 启动Debug


    启动Debug

使用Postman测试

  • 新增


    新增
  • 查找


    查找
  • 删除


    删除

版权所有,转载请注明出处 [码道功成]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容