前言
前面两章节讲的是图像的缩放、旋转、位移的处理,本章将介绍进行后续的图像生成服务处理。案例及背景都已做脱敏处理。
背景
将照片生成为不同证件尺寸模版下排版并返回给客户端资源链接。ps: 此服务不仅仅是用来处理证件照片的,还可以处理各类海报业务及头像处理服务。 示例如下:
Nest.js创建一个微服务模块
首先全局安装Nest
的cli
工具:
npm i -g @nestjs/cli
然后用Nest
的cli
快速的创建一个微服务模块,这里是假设已有一个主服务的情况下。
nest new micro-node-canvas
npm i --save @nestjs/microservices
修改main.ts
为,因为微服务通过 模式 识别消息。模式是一个普通值,例如对象、字符串。模式将自动序列化,并与消息的数据部分一起通过网络发送。这里选用TCP模式,具体可以查看文档微服务 (nestjs.cn)
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
import { NestFactory } from '@nestjs/core';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: {
host: '0.0.0.0',
port: 8888,
},
},
);
app.listen();
}
bootstrap();
app.module.ts
模块导入全局配置模块,这些模块导出当前模块所需提供者。
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { getConfiguration } from './config/configuration';
import { qiniuProvider } from './qiniu.provider';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [getConfiguration],
envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
}),
],
controllers: [AppController],
providers: [AppService, qiniuProvider()],
})
export class AppModule {}
qiniu.provider.ts
七牛云模块提供者,这里不再贴出具体配置项代码。
import { ConfigService } from '@nestjs/config';
import { ConfigurationKeyPaths } from './config/configuration';
import { FactoryProvider } from '@nestjs/common';
import { IQiniuConfig } from './config/config.interface';
import { QINIU_CONFIG } from './constants/config.constants';
/**
* 提供使用 @Inject(QINIU_CONFIG) 直接获取七牛配置
*/
export function qiniuProvider(): FactoryProvider {
return {
provide: QINIU_CONFIG,
useFactory: (
configService: ConfigService<ConfigurationKeyPaths>,
): IQiniuConfig => ({
accessKey: configService.get('qiniu.accessKey'),
secretKey: configService.get('qiniu.secretKey'),
domain: configService.get('qiniu.domain'),
bucket: configService.get('qiniu.bucket'),
zone: configService.get('qiniu.zone'),
access: configService.get('qiniu.access'),
}),
inject: [ConfigService],
};
}
贴一下./config/configuration
类型体操
import * as qiniu from 'qiniu';
const parseZone = (zone: string) => {
switch (zone) {
case 'Zone_as0':
return qiniu.zone.Zone_as0;
case 'Zone_na0':
return qiniu.zone.Zone_na0;
case 'Zone_z0':
return qiniu.zone.Zone_z0;
case 'Zone_z1':
return qiniu.zone.Zone_z1;
case 'Zone_z2':
return qiniu.zone.Zone_z2;
}
};
/**
* NestedKeyOf
* Get all the possible paths of an object
* @example
* type Keys = NestedKeyOf<{ a: { b: { c: string } }>
* // 'a' | 'a.b' | 'a.b.c'
*/
type NestedKeyOf<ObjectType extends object> = {
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
? `${Key}` | `${Key}.${NestedKeyOf<ObjectType[Key]>}`
: `${Key}`;
}[keyof ObjectType & (string | number)];
export const getConfiguration = () =>
({
// qiniu config
qiniu: {
accessKey: process.env.QINIU_ACCESSKEY,
secretKey: process.env.QINIU_SECRETKEY,
domain: process.env.QINIU_DOMAIN,
bucket: process.env.QINIU_BUCKET,
zone: parseZone(process.env.QINIU_ZONE || 'Zone_z2'),
access: (process.env.QINIU_ACCESS_TYPE as any) || 'public',
},
} as const);
export type ConfigurationType = ReturnType<typeof getConfiguration>;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export type ConfigurationKeyPaths = Record<NestedKeyOf<ConfigurationType>, any>;
app.service.ts
的主要工作就是处理图像服务,将处理好的图像上传至七牛云,返回给客户端预览。此处仅贴图展示。
在主服务中需要注入的
Controller
的Modules
中注册这个微服务,如下:
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'CANVAS_SERVICE',
transport: Transport.TCP,
options: {
port: 8888,
},
},
])
],
//此处省略下面代码
})
ClientsModule
和ClientProxy
类需要从@nestjs/microservices
包导入。
使用 ClientsModule
暴露的静态register()
方法。此方法将数组作为参数,其中每个元素都具有 name
属性,以及一个可选的transport
属性(默认是Transport.TCP
),以及特定于微服务的options
属性。
name
属性充当一个 injection token
,可以在需要时将其用于注入 ClientProxy
实例。name
属性的值作为注入标记。在外部模块导入后,可以使用@Inject()
装饰器将'CANVAS_SERVICE'
注入ClientProxy
的一个实例。
在主服务中引入完整服务后,我们可以来测试生成的效果。
测试完一组数据后效果还算可以,但是测试用例有很多,模拟用户数据一个一个测试费时间还容易将数据弄混,所幸成熟的软件产品都会集成自动化测试这部分。
回到
micro-node-canvas
微服务模块中,创建app.controller.spec.ts
文件:
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { getConfiguration } from './config/configuration';
import { jestMockData } from './mock/data';
import { qiniuProvider } from './qiniu.provider';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [getConfiguration],
envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
}),
],
controllers: [AppController],
providers: [AppService, qiniuProvider()],
}).compile();
appController = app.get<AppController>(AppController);
});
describe.each(jestMockData)('生成照片集合', (dto) => {
const { modeInfo } = dto;
test(`returns ${modeInfo.media_name}`, async () => {
const res = await appController.genImage(dto);
expect(res).toBe(
`/public/images/test/${modeInfo.media_name}${modeInfo.media_size}${modeInfo.media_set_type}.jpg`,
);
});
});
});
jestMockData
中就是所有客户端所传的集合,然后执行npm jest
命令跑起测试用例。
嗯,一个完整的流程完毕,最后测试下七牛云上传是否正常。
测试完毕,整个流程结束。
结束语
本次服务中用到的图像库为Automattic/node-canvas: Node canvas is a Cairo backed Canvas implementation for NodeJS. (github.com),API使用上与Canvas Web Canvas API
一致,用着完全无心智负担,放心食用。