访问头像服务接口可以提供用户头像文件。在终端,项目所在目录下面,先生成一个模块,nest generate module 生成一个模块,名字是 avatar-serve ,放在 avatar/modules/serve 里面,加上 --flat 选项。
在这个模块里添加一个控制器,nest generate controller ,名字是 avatar-serve ,放在 avatar/modules/serve/controllers 里面。然后生成一个 service,执行 nest generate service ,名字是 avatar-serve,放在 avatar/modules/serve/providers 里面。
再生成一个获取用户头像数据用的查询,npm run gq ,名字是 get-user-avatar,放在 avatar/modules/serve/queries 里面。
查询
回到项目,先打开 GetUserAvatarQuery 这个查询,在查询参数里添加一个 userId ,类型是 number ,然后打开它的处理器,在处理器里面先注入一个依赖,需要一个 Repository,实体是 AvatarEntity,名字是 avatarRepository。分别再导入需要用的这些东西。
有 execute 里面,解构一下查询参数,把 userId 解构出来,来自 query.params, 下面解构声明一个 avatar,等于 await 用一下 this.avatarRepository.find,提供一个对象,用 where 设置查询条件,userId 应该等于查询参数里的 userId。
下面判断一下,如果 !avatar,也就是如果没找到用户头像数据,可以 throw 一个异常,新建一个 NotFoundException,没找到用户头像。 最后让查询返回 avatar。
AvatarServeService
下面打开 AvatarServeService,一会儿会用到 fs 模块,在文件顶部导入 fs,来自 fs,还会用到 path ,导入 path,来自 path 。
然后在这个服务里面,注入一个依赖,需要用的是 configService,类型是 ConfigService。下面再定义一个方法,它的作用就是返回头像文件流。
名字是 getUserAvatarStream,支持一个 filename 参数,类型是 string,返回的东西是 fs.ReadStream。
在方法里面,声明一个 avatarPath,用一下 path.join ,先提供一个 this.configService.get,得到 upload.avatar ,再加上一个 filename。
有了文件路径,再检查一下文件是否存在,声明一个 fileExist,等于 fs.existsSync ,把文件路径交给这个方法。下面判断一下,如果 !fileExist,如果文件不存在,可以 throw 一个异常,新建一个 NotFoundException ,异常信息是头像文件不存在。
最后 return 的东西用一下,fs.createReadStream ,提供一个 avatarPath 。
AvatarServeController
然后打开 AvatarServeController,在这里要提供一个头像服务接口,在控制器的构造方法里,注入两个依赖,先注入一个 queryBus,一会执行查询的时候会用到它。还需要用到 avatarServeService,类型是 AvatarServeService。
下面定义一个接口,用 @Get 装饰一下,地址是 users 后面加上一个 :userId 地址参数,然后是 avatar。用 async 标记一下,方法的名字是 serveAvatar。
添加两个参数,用 @Param 装饰一下,需要 userId 这个地址参数,用 ParseIntPipe 处理一下,交给 userId ,类型是 number 。再用一个 @Res 装饰器,它可以获取到响应,交给 response,类型是 Response,注意这个 Response 来自 express 这个包。在文件顶部可以从 express 里面导入它。
这里用了 @Res 装饰器,得到了响应,因为我们要在这个处理器里面手工设置一下平台特有的一些东西,比如要设置一下响应的头部,这个平台在我们的应用里指的就是 Express 框架。Nest 会检测到这个行为,设置完这个响应以后,如果想把其它的事情交还给 Nest 框架处理,需要把 Res 这个装饰器的对象参数里的 passthrough 设置成 true。
在这个处理器方法里面,声明一个 avatar,等于 await ,执行一个查询,新建一个 GetUserAvatarQuery 查询,提供一个对象参数,里面需要一个 userId ,它的值就是这个方法的 userId 参数的值。
下面声明一个 avatarStream,用一下 this.avatarServeService.getUserAvatarStream,提供一个 avatar.filename。
然后再用一下 response.set 设置一下响应头部,需要一个 Content-Type 头部,告诉客户端提供的文件是什么类型的,值可以设置成 avatar.mimetype。 最后可以返回一个 StreamableFile,新建一个 StreamableFile,它来自@nestjs/common,把 avatarStream 交给它。
测试
在 Http 客户端测试一下这个接口,在用户的下面新建一个请求,名字是头像服务,方法选择 GET,配置一下请求地址,一个基本的地址,加上 users 后面是用户的 id,比如 1 ,再加上一个 avatar。 发送一下这个请求,提示无法连接服务。
在运行项目的终端观察一下,你会看到一个无法解决依赖的错误。
回到项目,在这个 GetUserAvatarQuery 的处理器这里注入了一个 Avatar 实体的 Repository,打开这个处理器所属的模块,AvatarServeModule,在这个模块里面添加一个 imports,用一个 forwardRef,返回一个 AvatarModule 模块。
回到 Http 客户端,重新发送一下这个头像服务请求,这回得到了一个异常响应,提示用户头像不存在。
下面可以换一个之前上传过用户头像的用户 id,我这里 id 是 4 的这个用户上传过头像,在 Http 客户端把这个 userId 参数的值改成 4,再发送一下这个请求,这回服务端响应的东西就是这个用户的头像文件,因为作出这个响应的时候,我们设置了响应的 Content-Type 头部, 客户端发现响应的内容类型是图像,所以就会直接显示它。