Skip to main content

探索使用Midway3 -渐进式依赖注入Node框架!

探索使用Midway3 -渐进式依赖注入Node框架!

介绍

  • Midway 是阿里巴巴 - 淘宝前端架构团队,基于渐进式理念研发的 Node.js 框架,通过自研的依赖注入容器,搭配各种上层模块,组合出适用于不同场景的解决方案。

  • Midway 基于 TypeScript 开发,结合了面向对象(OOP + Class + IoC)与函数式(FP + Function + Hooks)两种编程范式,并在此之上支持了 Web / 全栈 / 微服务 / RPC / Socket / Serverless 等多种场景,致力于为用户提供简单、易用、可靠的 Node.js 服务端研发体验。

为什么不是egg, nest, koa, express

  • OOP + Class + IoC 写起来太优雅了, 底层可以选择koa, 性能也有保证, 生态依赖齐全
  • 支持阿里的serverless, 以及其他的云服务
  • 支持前后端一体化开发

简单实现一个缓存接口到redis自动注册装饰器的实现

  • 我们的需求是接口同时传参ttl(缓存时间), 在缓存期间接口直接返回缓存数据, 超过缓存时间, 重新请求接口并更新缓存
  • 首先在src目录下创建 decorator 文件夹, 在里面创建一个 cache.decorator.ts 文件
import { createCustomMethodDecorator } from '@midwayjs/decorator';

// 装饰器内部的唯一 id
export const MEMORY_CACHE_KEY = 'decorator:cache_ttl';

/**
 * Description 缓存接口装饰器 使用方法参照 server/src/module/sys/service/login.ts GetCaptchaService
 * @date 06/05/2022 - 3:08:51 PM
 * @author GGbeng
 *
 * @export
 * @param {number} ttl 过期时间
 * @returns {MethodDecorator}
 */
export function Cache(ttl: number): MethodDecorator {
	return createCustomMethodDecorator(MEMORY_CACHE_KEY, {
		ttl,
	});
}
  • 其次, 完成缓存装饰器的注册, 在src/service目录下创建一个 initDecorator.service.ts 文件
import { MEMORY_CACHE_KEY } from '../decorator/cache.decorator';
import { Inject, Scope, ScopeEnum } from '@midwayjs/decorator';
import { Autoload, Init, MidwayDecoratorService } from '@midwayjs/core';
import { CacheManager } from '@midwayjs/cache';
import * as md5 from 'md5';

@Autoload()
@Scope(ScopeEnum.Singleton)
export class InitDecorator {
	@Inject()
	decoratorService: MidwayDecoratorService;
	@Inject()
	cacheManager: CacheManager;
	@Init()
	async cacheInit() {
		this.decoratorService.registerMethodHandler(MEMORY_CACHE_KEY, options => {
			return {
				around: async joinPoint => {
					// 获取过期时间
					const ttl = options.metadata?.ttl || 5;
					const { target, methodName, args } = joinPoint;
					const key = md5(
						String(target.constructor.name) +
							String(methodName) +
							JSON.stringify(args),
					);
					// 获取缓存请求值
					let data: string | undefined = await this.cacheManager.get(key);
					// 如果缓存中有值则直接返回
					if (data) {
						return JSON.parse(data);
					} else {
						// 如果缓存中没有值则执行方法并将结果缓存
						data = await joinPoint.proceed(...joinPoint.args);
						this.cacheManager.set(key, JSON.stringify(data), { ttl });
					}
					return data;
				},
			};
		});
	}
}
  • 在config目录下的 config.default.ts 文件中(根据自己的情况修改自己的环境配置文件) 添加redis缓存配置
import { MidwayConfig } from '@midwayjs/core';
import * as redisStore from 'cache-manager-ioredis';

export default {
	// use for cookie sign key, should change to your own and keep security
	keys: '1667543005095_613',
	koa: {
		port: 7001,
		globalPrefix: '/v1',
	},
	jwt: {
		secret: 'shyt-ggbeng',
		expiresIn: '3d', // https://github.com/vercel/ms
	},
	typeorm: {
		dataSource: {
			default: {
				type: 'mysql',
				host: '127.0.0.1',
				port: 3306,
				username: 'root',
				password: '123456',
				database: 'ggbeng',
				// 自动建表
				synchronize: true,
				// 打印日志
				logging: true,
				// 字符集
				charset: 'utf8mb4',
				entities: '../module/sys/entity',
				timezone: '+08:00',
			},
		},
	},
	cache: {
		store: redisStore, // 支持local存储, 这里我们选择redis会更快一些
		options: {
			host: '127.0.0.1',
			port: 6379,
			password: '123456',
			db: 0,
			ttl: null,
		},
	},
	captcha: {
		default: {
			// 默认配置
			size: 4,
			noise: 1,
			width: 120,
			height: 40,
		},
		image: {
			// 最终会合并 default 配置
			type: 'mixed',
		},
		formula: {}, // 最终会合并 default 配置
		text: {}, // 最终会合并 default 配置
		expirationTime: 60,
		idPrefix: 'midway:vc',
	},
} as MidwayConfig;
  • 同时在configuration.ts文件中注册缓存
import { Configuration, App, Inject } from '@midwayjs/decorator';
import * as koa from '@midwayjs/koa';
import * as validate from '@midwayjs/validate';
import * as info from '@midwayjs/info';
import * as captcha from '@midwayjs/captcha';
import * as orm from '@midwayjs/typeorm';
import * as jwt from '@midwayjs/jwt';
import * as cache from '@midwayjs/cache';

import { MidwayDecoratorService } from '@midwayjs/core';
import { join } from 'path';
import { DefaultErrorFilter } from './filter/default.filter';
import { NotFoundFilter } from './filter/notfound.filter';
import { ReportMiddleware } from './middleware/report.middleware';
import { JwtMiddleware } from './middleware/jwt.middleware';

@Configuration({
	imports: [
		koa,
		validate,
		captcha,
		orm,
		jwt,
		cache, // 注册缓存
		{
			component: info,
			enabledEnvironment: ['local'],
		},
	],
	importConfigs: [join(__dirname, './config')],
})
export class ContainerLifeCycle {
	@App()
	app: koa.Application;

	@Inject()
	decoratorService: MidwayDecoratorService;

	async onReady() {
		// add middleware
		this.app.useMiddleware([ReportMiddleware, JwtMiddleware]);
		// add filter
		this.app.useFilter([NotFoundFilter, DefaultErrorFilter]);
	}
}

使用

  • 在service 的方法中使用
  • 这样这个接口就会被缓存到redis中, 3秒后过期(时间自定)
@Provide()
export class GetCaptchaService {
	@Inject()
	captchaService: CaptchaService;
	// 接口缓存 3 秒 , 防止用户多次点击获取验证码

	/**
	 * Description 获取验证码
	 * @date 06/05/2022 - 3:50:33 PM
	 * @author GGbeng
	 *
	 * @async
	 * @returns {Promise<{ id: string; imageBase64: string }>}
	 */
	@Cache(3)
	async get(): Promise<{ id: string; imageBase64: string }> {
		const { id, imageBase64 } = await this.captchaService.image({
			width: 120,
			height: 40,
		});
		return {
			id, // 验证码 id
			imageBase64, // 验证码 SVG 图片的 base64 数据,可以直接放入前端的 img 标签内
		};
	}
}

结尾

  • 本项目已经重新开源到了GitHub, 一个前后端分离的项目, 前后端依赖均已升级到最新(2022/11)
  • 项目GitHub地址