Midway.js全栈框架入门

4,979 阅读5分钟

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

前言

我司的技术基建在Midway之上,主要是面向中后台前后端一体化方案,大白话就是全栈应用解决方案,什么是Midway呢?

Midway Serverless 是用于构建 Node.js 云函数的 Serverless 框架。帮助您在云原生时代大幅降低维护成本,更专注于产品研发,而其专注于函数即服务,你只需要编写JavaScript函数就可以像编写Java接口一样的简单,并且提供了开箱即用的部署解决方案。

多编程范式

Midway支持面向对象与函数式两种编程范式,你可以根据实际研发的需要,选择不同的编程范式来开发应用。

从官网中搬移两种案例,相同的hello midway接口编写,是这样的:

面向对象(OOP + Class + IoC)

面向对象写法,采用类+装饰器的形式,可能看起来有点陌生~

// src/controller/home.ts  
import { Controller, Get } from '@midwayjs/core';  
import { Context } from '@midwayjs/koa';  
  
@Controller('/')  
export class HomeController {  
  
    @Inject()  
    ctx: Context  
  
    @Get('/')  
    async home() {  
        return {  
            message: 'Hello Midwayjs!',  
            query: this.ctx.ip  
        }  
    }  
}

函数式(FP + Function + Hooks)

React很相像的一种写法~

import { useContext } from '@midwayjs/hooks'  
import { Context } from '@midwayjs/koa';  
  
export default async function home () {  
    const ctx = useContext<Context>()  
  
    return {  
        message: 'Hello Midwayjs!',  
        query: ctx.ip  
    }  
}

本文将以OOP + Class + IoC的形式来进行实践演示。

初始化构建项目

只需要两行命令,即可启动一个Midway项目,你可以理解为一个启动一个后端服务。

npm init midway
npm run dev

在 controller 目录中,新建一个 src/controller/weather.controller.ts 文件,内容如下。

import { Controller, Get } from '@midwayjs/core';

@Controller('/')
export class WeatherController {
  // 这里是装饰器,定义一个路由
  @Get('/weather')
  async getWeatherInfo(): Promise<string> {
    // 这里是 http 的返回,可以直接返回字符串,数字,JSON,Buffer 等
    return 'Hello Weather!';
  }
}

这样你就可以通过前端请求的形式获取到/weather接口了。

就像这样:

fetch('http://127.0.0.1/weather').then(res => {
    res.json().then(data => {
        console.log(data);    // Hello Weather
    })
})

对于@Controller你可以理解为一个后端项目通过一个控制器来启动一个个接口,在里面包含了许多模块的服务,如user类、list类、upload类等等,而user中可能包含注册、登录、注销;upload中可能包含上传、删除图片等等,所以你的接口看起来会像是这样的:

controller.ts

import { Controller, Post, Inject, Query, Get } from '@midwayjs/core';
import { UserService } from './service/user.service';
import { ListService } from './service/list.service';

@Controller('/')
export class CommonController {
  @Inject()
  ctx;

  @Inject()
  UserService: UserService;

  @Inject()
  ListService: ListService;
  
  @Inject()
  UploadService: UploadService;

  @Post('/register')
  async register(@Query('userId') userId: string, @Query('password') password: string): Promise<boolean> {
    return this.UserService.register({userId, password});
  }
  @Get('/getUserInfo')
  async getUserInfo() {
    return this.UserService.getUserInfo();
  }
  // List和Upload的接口...
}

user.service.ts

import { Provide, Inject, Context } from '@midwayjs/core';

interface UserInfo {
    userName: string;
    age: number;
    sex: string;
}

@Provide()
export class UserService {
  @Inject()
  ctx: Context;
  async register(params): Promise<boolean> {
    // 注册逻辑
    return true;
  }
  async getUserInfo(): Promise<UserInfo> {
    // 获取用户信息逻辑
    return {
        userName: '量子前端',
        age: 20,
        sex: '不详'
    };
  }
}

看起来有没有感受到编写一个接口就像是写一个函数/类一样简单呢?并且Midway还提供了很多强大的功能,如中间件、组件、Http服务等等,接下来我们实践两个全栈场景,分别是图片上传和验证码,来具体感受一下。

案例

图片上传

首先安装依赖包。

npm i @midwayjs/upload@3 --save

configuration.ts中导入:

@Configuration({
  imports: [upload],
  importConfigs: [
    {
      default: defaultConfig,
      prod: prodConfig,
    },
  ],
  conflictCheck: true,
})

接下来在控制器中声明并引用接口:

controller.ts

import { Controller, Post, Inject, Files } from '@midwayjs/core';
import { UploadService } from './service/upload.service';

@Controller('/')
export class CommonController {
  @Inject()
  UploadService: UploadService;
  
  @Post('/upload')
  async upload(@Files() files): Promise<string[]> {
    return this.UploadService.upload(files);
  }
}

upload.service.ts

官方有文件上传和流上传两种模式,这里以文件上传的方式将图片保存在Midway项目的public目录中。

import { Provide, Inject, Context } from '@midwayjs/core';
import * as path from 'path';
import * as moment from 'moment';
import * as uuid from 'uuid';
import * as fs from 'fs';

@Provide()
export class UploadService {
  @Inject()
  ctx: Context;
  
  async upload(files): Promise<string[]> {
    const fileDir = path.join(this.ctx.app.getBaseDir(), '..', 'public');
    const timeDir = `${moment().format('YYYY')}/${moment().format('MM-DD')}`;
    const url = path.join(fileDir, timeDir);
    const fileList = [];
    if (!fs.existsSync(url)) fs.mkdirSync(url, { recursive: true });
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      const extname: string = path.extname(file.filename).toLowerCase();
      const data = fs.readFileSync(file.data);
      const fileName = uuid.v1();
      const target = path.join(url, `${fileName}${extname}`);
      fs.writeFileSync(target, data);
      fileList.push(`${url}/${fileName}${extname}`);
    }
    return fileList;
  }
}

接下来我们简单写一个前端请求来测试。

const fileUpload = (e) => {
    const formData = new FormData();
    formData.append('file', e.target.files[0]);
    console.log(e.target.files);
    console.log(360, formData);
    fetch('http://127.0.0.1:7002/upload', {
      method: 'POST',
      body: formData,
    }).then((res) => {
      res.json().then((data) => {
        // 获取到图片上传的fileList,回显在DOM中
      });
    });
}

return (
    <input type="file" onChange={fileUpload} />
)

就这样一个简单基础版本的图片上传接口就写完啦~

验证码校验

首先安装依赖包。

npm i @midwayjs/captcha@3 --save

configuration.ts中导入:

@Configuration({
  imports: [captcha],
  importConfigs: [
    {
      default: defaultConfig,
      prod: prodConfig,
    },
  ],
  conflictCheck: true,
})

然后我们声明两个接口,分别是获取验证码接口和验证码校验接口,这里以图形验证码为例:

controller.ts

import { Controller, Post, Inject, Get } from '@midwayjs/core';
import { CaptchaService } from '@midwayjs/captcha';

@Controller('/')
export class CommonController {
    @Inject()
    CaptchaService: CaptchaService;
    
    @Get('/get-image-captcha')
      async getImageCaptcha() {
        const { id, imageBase64 } = await this.CaptchaService.image({
          width: 120,
          height: 40,
          size: 6,
          type: 'number',
        });
        return {
          id, // 验证码 id
          imageBase64, // 验证码 SVG 图片的 base64 数据,可以直接放入前端的 img 标签内
        };
      }
      // 验证验证码是否正确
      @Post('/check-captcha')
      async getCaptcha() {
        const { id, answer } = this.ctx.request.body;
        const passed: boolean = await this.CaptchaService.check(id, answer);
        return passed;
      }
}

这里直接用官方的服务接口。

  • 获取验证码接口直接返回给前端一个验证码图片id和图片base64地址;
  • 校验验证码接口前端将验证的结果和获取验证码的id给后端来校验是否一致;

这里简单写一段react伪代码调试一下:

const openCheckCaptchaModal = () => {
    // 获取所有tab的商户数量,默认选中的tab不取,走列表
    fetch('http://127.0.0.1:7002/get-image-captcha').then((res) => {
      res.json().then(({ id, imageBase64 }) => {
        Modal.alert({
          content: (
            <>
              <img src={imageBase64} />
              <Form form={form}>
                <Form.Item name="captcha">
                  <Input placeholder="请输入验证码" />
                </Form.Item>
              </Form>
              <span
                onClick={() => {
                  Modal.clear();
                  openCheckCaptchaModal();
                }}
              >
                换一张
              </span>
            </>
          ),
          onConfirm: () => {
            const captcha = form.getFieldValue('captcha');
            if (captcha) {
              fetch('http://127.0.0.1:7002/check-captcha', {
                method: 'POST',
                body: JSON.stringify({
                  id,
                  answer: captcha,
                }),
                headers: {
                  'Content-Type': 'application/json',
                },
              }).then((res) => {
                res.json().then((data) => {
                  if(data) {
                      return Message.success('验证成功');
                  }
                });
              });
            }
          },
        });
      });
    });
  };

return (
    <span onClick={openCheckCaptchaModal}>验证</span>
)

前端效果:

image.png

获取验证码:

image.png

校验:

image.png

部署接口

部署接口很方便,在Midway项目中执行npm run deploy即可进入部署流程,需前置准备阿里云 or 其他服务器账号,阿里云首次部署需要accountIdaccountKeyaccountSecret

具体文档在这里:

Midway接口部署方案

总结

如果你没有Serverless相关概念,通过本文了解Midway是一个快速入门认知到概念的方式,Midway的能力有很多,可以继续在官方文档中探索。