Midway.js探索与实践

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

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

前言

我司的技术基建在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 其他服务器账号,阿里云首次部署需要accountId、accountKey、accountSecret。

具体文档在这里:

Midway接口部署方案

总结

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

目录
相关文章
|
前端开发 算法 API
构建高性能图像处理Web应用:Next.js与TailwindCSS实践
本文分享了构建在线图像黑白转换工具的技术实践,涵盖技术栈选择、架构设计与性能优化。项目采用Next.js提供优秀的SSR性能和SEO支持,TailwindCSS加速UI开发,WebAssembly实现高性能图像处理算法。通过渐进式处理、WebWorker隔离及内存管理等策略,解决大图像处理性能瓶颈,并确保跨浏览器兼容性和移动设备优化。实际应用案例展示了其即时处理、高质量输出和客户端隐私保护等特点。未来计划引入WebGPU加速、AI增强等功能,进一步提升用户体验。此技术栈为Web图像处理应用提供了高效可行的解决方案。
|
存储 JavaScript 前端开发
使用JavaScript构建动态交互式网页:从基础到实践
【10月更文挑战第12天】使用JavaScript构建动态交互式网页:从基础到实践
969 1
|
数据采集 Web App开发 JavaScript
利用Selenium和XPath抓取JavaScript动态加载内容的实践案例
利用Selenium和XPath抓取JavaScript动态加载内容的实践案例
|
JavaScript 前端开发 安全
TypeScript的优势与实践:提升JavaScript开发效率
【10月更文挑战第8天】TypeScript的优势与实践:提升JavaScript开发效率
|
JavaScript 前端开发 开发者
理解JavaScript中的原型链:基础与实践
【10月更文挑战第8天】理解JavaScript中的原型链:基础与实践
|
前端开发 JavaScript Java
【Java进阶】JavaScript电灯开关实例:从理论到实践
这个例子展示了JavaScript的基本功能,包括操作HTML元素,监听事件,以及改变元素的样式。通过学习和理解这个例子,你可以了解到JavaScript在网页中的应用,以及如何使用JavaScript来创建交互式的网页。
311 13
|
人工智能 监控 前端开发
基于 Next.js 的书法字体生成工具架构设计与 SSR 优化实践
本项目是一款书法字体生成工具,采用 Next.js 14(App Router)与 Tailwind CSS 构建前端,阿里云 Serverless 部署后端。通过混合渲染策略(SSG/SSR/CSR)、Web Worker 异步计算及 CDN 字体分片加载优化性能。服务端借助阿里云函数计算处理计算密集型任务,将平均耗时从 1200ms 降至 280ms,支持 1000+ QPS。动态路由与 ARMS 监控提升工程化水平,未来计划引入 WebGPU 和 AI 字体风格迁移技术,进一步优化用户体验。
|
缓存 NoSQL JavaScript
Vue.js应用结合Redis数据库:实践与优化
将Vue.js应用与Redis结合,可以实现高效的数据管理和快速响应的用户体验。通过合理的实践步骤和优化策略,可以充分发挥两者的优势,提高应用的性能和可靠性。希望本文能为您在实际开发中提供有价值的参考。
558 11
|
缓存 监控 JavaScript
Vue.js 框架下的性能优化策略与实践
Vue.js 框架下的性能优化策略与实践
|
缓存 自然语言处理 JavaScript
JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南
闭包是JavaScript中不可或缺的部分,它不仅可以增强代码的可维护性,还能在模块化、回调处理等场景中发挥巨大作用。然而,闭包的强大也意味着需要谨慎使用,避免潜在的性能问题和内存泄漏。通过对闭包原理的深入理解以及在实际项目中的灵活应用,你将能够更加高效地编写出简洁且功能强大的代码。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~