MidwayJS 全栈开发(二)HelloWorld实战
MidwayJS 全栈开发(二)HelloWorld实战
前言
上一篇内容回顾:# MidwayJS 全栈开发(一)技术选型。
本篇:通过编写一个 HelloWorld 应用,串一下后端开发流程。
初始化项目
这里我们可以通过官方指引完成项目初始化和启动,我们按 官网脚手架 初始化生成项目,执行 npm run dev
启动应用即可(自带代码更新重启)。
略略略。
项目结构
我们只关注 ./src
目录下的内容,里面包括文件如下:
./src
├── config // 配置文件目录
│ ├── config.default.ts
│ └── config.unittest.ts
├── configuration.ts // 应用入口文件
├── controller // 控制器目录,管理路由
│ ├── api.controller.ts
│ └── home.controller.ts
├── filter // 过滤器目录,先不看
│ ├── default.filter.ts
│ └── notfound.filter.ts
├── interface.ts // 全局TS类型定义文件
├── middleware // 中间件目录,先不看
│ └── report.middleware.ts
└── service // 服务目录,编写CRUD逻辑
└── user.service.ts
HelloWord 实战
这里按照个人的开发习惯重组一下代码,借此大家可以更深入了解下 Midway 项目大致组织结构,也方便后续教程进行。
这里推荐大家使用 VsCode 编辑器进行开发。
修改配置
koa 里的配置对象是基于约定的,最终会传给底层依赖的 Koa 框架。我们可以尝试修改,看看效果。
1. 端口号
找到 ./src/config/config.default.ts
文件,修改端口号 7001
为 8080
或者其他。
2. 全局路由前缀
同样,可以添加 globalPrefix
字段设置全局路由的前缀。
// ./src/config/config.default.ts
import { MidwayConfig } from '@midwayjs/core';
export default {
// use for cookie sign key, should change to your own and keep security
keys: '1675590443187_6035',
koa: {
port: 8080,
globalPrefix: "/api",
},
} as MidwayConfig;
之后只要请求服务,所有的路由都要加上 api
前缀。
globalPrefix-image
这里推荐大家使用 ApiFox 进行API请求和调试。
新增模块
关于目录结构,个人习惯于以目录的形式聚合相关联 controller 和 service,方便聚焦管理和查找。
- Controller 主要负责路由的管理以及入参出参的校验
- Service 主要负责业务逻辑和 CRUD。两者需要划分边界,以此开后续代码的可维护性
这里我们以创建 get_user
(根据 id
获取用户信息) API 为例。
1. 新增 Service
新增 ./src/modules/user
目录,在 user 目录下创建 user.service.ts
文件。
import { Provide } from '@midwayjs/decorator';
@Provide()
export class UserService {
async get(uid: string) {
if (!uid) return null;
// mock 数据库查询返回数据
return {
uid: uid,
username: '不败花',
phone: '12345678901',
email: 'xxxxxx@xxx.com',
};
}
}
2. 新增 Controller
再在 user 目录下创建 user.controller.ts
文件。请求API路径为 /api/user/get_user
。
import { Controller, Get, Inject, Query } from '@midwayjs/decorator';
import { UserService } from './user.service';
// 路由
@Controller('/user')
export class UserController {
@Inject()
userService: UserService;
@Get('/get_user')
async getUser(@Query('uid') uid) {
const user = await this.userService.get(uid);
if (!user) return { code: 6000, msg: 'User not found', data: null };
return { code: 0, msg: 'OK', data: user };
}
}
@Get
表示以 GET
方式接收请求,通过 @Query('uid')
来获取请求查询参数 uid
。
最后,我们用 Apifox 测试验证一下请求,传入任意请求参数 uid
。
image.png
可以看到,API 请求是可以成功的。这里我们并没有使用将 UserController
在任何地方配置引入,路由就可以生效。这是因为我们通过 @Controller('/user')
实现了路由注册,MidwayJS 默认在 src
目录下 @Controller
声明的都会被注册为 API 路由,最终生效。这点可能和 NestJS 不大相同(需要最终在根模块上声明)。
小结
至此,对于获取用户的 API,我们的整个开发流程已经完成的七七八八了。
可以看到,对于搬砖工作,主要还是集中在 Service 和 Controller 里面,所以我更倾向于聚合在一个文件夹下面,不至于太乱。
响应结构一致性
对于 Rest API 的响应体设计,我们可以看到,大多数是和前端约定好的统一的数据结构,这样前端对接可以做相应的逻辑处理;同时,约定统一的接口响应规范也可以减少重复编码,提高团队整体效率。
值得一提的是,团队基于此可以做很多事情,比如埋点统一读取字段上报统计分析、接口文档统一导出、请求库统一封装等等。
{ code: number, msg: string, data: any }
1. 新增辅助类
这里我们后端一般会抽象一个工具类来实现响应结构统一,以后都可以使用这个辅助类进行接口出参处理。
// ./src/utils/index.ts
export class BaseResponse<T> {
code: number;
msg: string;
data: T;
constructor(data: T, code: number, msg: string) {
this.code = code;
this.msg = msg;
this.data = data;
}
static create<T>(data: T, code: number, msg: string) {
return new BaseResponse(data, code, msg);
}
static ok<T>(data: T) {
return BaseResponse.create(data, 0, 'OK');
}
static error(msg: string, code: number) {
return BaseResponse.create(null, code, msg);
}
}
2. BaseResponse 改造
然后,我们回到 user.service.ts
进行一波改造,这样就可以愉快的玩耍啦。
import { Controller, Get, Inject, Query } from '@midwayjs/decorator';
+ import { BaseResponse } from '../../utils';
import { UserService } from './user.service';
// 路由
@Controller('/user')
export class AppController {
@Inject()
userService: UserService;
@Get('/get_user')
async getUser(@Query('uid') uid) {
const user = await this.userService.get(uid);
- if (!user) return { code: 6000, msg: 'User not found', data: null };
+ if (!user) return BaseResponse.error('User not found', 6000);
- return { code: 0, msg: 'OK', data: user };
+ return BaseResponse.ok(user);
}
}
小结
主要讲了 BaseResponse 的优化,帮助我们统一前后端接口基础规范。
全文总结
本篇主要以 Midway 作为入手,完成了项目从零到 HelloWorld 的过程实践。可以看到,对于 RestAPI 项目,主要聚焦在 Service(负责逻辑处理)和 Controller(负责路由管理)两块的实现,使用上还是比较简单的。
当然对于装饰器的原理以及依赖注入,这里不做深入剖析,感兴趣的老友可以了解看看。