如何在NestJS后端搜索引擎中实现图片流式传输?
问题描述
我正在开发一款后端卡牌搜索引擎,用户可搜索卡牌并获取卡牌图片及属性信息。目前已实现卡牌信息与属性的客户端返回,但不知如何将图片与卡牌数据一同返回,希望获取相关实现方案。
客户端交互流程
- 客户端向服务器请求指定名称的卡牌。
- 用户收到包含图片和属性的卡牌数据。
现有代码
Service 文件
import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { YuGiOhCard, YuGiOhCardImage } from '@prisma/client'; @Injectable() export class SearchCardService { constructor(private prisma: PrismaService) {} async findAllCards(query?: string): Promise<YuGiOhCard[]> { return this.prisma.yuGiOhCard.findMany({ where: { name: { contains: query, }, }, include: { YuGiOhCardImage: true, }, }); } async findCardByName(name: YuGiOhCard['name']): Promise<YuGiOhCard> { return this.prisma.yuGiOhCard.findUnique({ where: { name, }, include: { YuGiOhCardImage: true, }, }); } async getCardByNameId( cardNameId: YuGiOhCardImage['cardNameId'], imageUrl: YuGiOhCardImage['imageUrl'], ) { return this.prisma.yuGiOhCardImage.findFirst({ where: { cardNameId: cardNameId, AND: { imageUrl: imageUrl, }, }, }); } async returnCardInfo( cardName: YuGiOhCard['name'], imageUrl: YuGiOhCardImage['imageUrl'], ): Promise<YuGiOhCard> { const name = await this.findCardByName(cardName); if (name) { return this.prisma.yuGiOhCard.findFirst({ include: { YuGiOhCardImage: true, }, where: { name: cardName, YuGiOhCardImage: { some: { imageUrl, }, }, }, }); } return null; } }
Controller 文件
import { Controller, Get, Param } from '@nestjs/common'; import { SearchCardService } from './search-card.service'; import { YuGiOhCard } from '@prisma/client'; import { StreamingFileController } from '../streaming-file/streaming-file.controller'; @Controller('search-card') export class SearchCardController { constructor(private readonly searchCardService: SearchCardService) {} @Get() async findAllCards(query?): Promise<YuGiOhCard[]> { return this.searchCardService.findAllCards(query); } @Get('/:name') async findCardByName( @Param('name') name: YuGiOhCard['name'], ): Promise<YuGiOhCard> { const getCardByName = await this.searchCardService.findCardByName(name); if (getCardByName) { const getFile = new StreamingFileController(); console.log('tetstte'); await getFile.getFile(); console.log(getFile); return getCardByName; } } }
数据库 Schema
// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model YuGiOhCard { id String @id @default(uuid()) name String @unique desc String level Int atk Int def Int extraDeck Boolean @default(false) cardType cardTypeEnum monsterType monsterTypeEnum monsterSubType monsterSubTypeEnum monsterAttribute monsterAttributeEnum createdAt DateTime @default(now()) updatedAt DateTime @updatedAt YuGiOhCardImage YuGiOhCardImage[] } model YuGiOhCardImage { id String @id @default(uuid()) cardName YuGiOhCard @relation(fields: [cardNameId], references: [id]) cardNameId String @default(uuid()) imageUrl String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } enum cardTypeEnum { MONSTER SPELL TRAP } enum monsterTypeEnum { Aqua Beast BeastWarrior Cyberse Dinosaur DivineBeast Dragon Fairy Fiend Fish Insect Machine Plant Psychic Pyro Reptile Rock SeaSerpent Spellcaster Thunder Warrior WingedBeast Wyrm Zombie } enum monsterSubTypeEnum { Normal Effect Flip Gemini Pendulum Spirit Toon Tuner Union Fusion Synchro Xyz Link } enum monsterAttributeEnum { Dark Divine Earth Fire Light Water Wind }
图片存储说明
图片存储在项目根目录的public文件夹中,格式为jpeg。
实现方案
方案一:返回图片访问URL(推荐)
不需要流式传输,直接让客户端通过URL自行加载图片,这是最高效的方式:
- 在
main.ts中配置静态资源服务,让public文件夹可被外部访问:
import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { join } from 'path'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); // 配置静态资源目录,映射到public文件夹 app.useStaticAssets(join(__dirname, '..', 'public')); await app.listen(3000); } bootstrap();
- 确保数据库中
YuGiOhCardImage的imageUrl字段存储的是图片文件名(如blue-eyes-white-dragon.jpeg),返回的卡牌数据已包含YuGiOhCardImage数组,客户端直接用http://你的域名/[imageUrl]即可访问图片。
方案二:将图片转为Base64嵌入返回数据
如果需要一次性返回卡牌属性和图片数据,可把图片转为Base64字符串:
- 修改Service中的方法,读取图片并转换:
import { readFileSync } from 'fs'; import { join } from 'path'; // 在SearchCardService中新增方法 async findCardWithBase64Image(name: YuGiOhCard['name']) { const card = await this.prisma.yuGiOhCard.findUnique({ where: { name }, include: { YuGiOhCardImage: true }, }); if (!card) return null; // 把图片转为Base64 const processedCard = { ...card, YuGiOhCardImage: card.YuGiOhCardImage.map(img => { const imagePath = join(__dirname, '..', '..', 'public', img.imageUrl); const imageBuffer = readFileSync(imagePath); return { ...img, imageBase64: `data:image/jpeg;base64,${imageBuffer.toString('base64')}` }; }) }; return processedCard; }
- 修改Controller的
findCardByName方法:
@Get('/:name') async findCardByName(@Param('name') name: YuGiOhCard['name']) { const cardWithImage = await this.searchCardService.findCardWithBase64Image(name); return cardWithImage || { message: '卡牌不存在' }; }
注意:Base64会让数据体积增大约30%,仅适合小体积图片场景。
方案三:单独提供图片流式接口
保留原属性接口,新增一个返回图片流的接口:
- 在Controller中添加接口:
import { Res } from '@nestjs/common'; import { Response } from 'express'; import { createReadStream } from 'fs'; import { join } from 'path'; // 在SearchCardController中新增 @Get('/image/:filename') async getCardImage(@Param('filename') filename: string, @Res() res: Response) { const imagePath = join(__dirname, '..', '..', 'public', filename); const stream = createReadStream(imagePath); // 流式传输图片到客户端 stream.pipe(res); }
客户端先请求卡牌属性拿到imageUrl,再请求/search-card/image/[imageUrl]获取图片流。
内容的提问来源于stack exchange,提问作者Ashton
相关产品推荐
相关产品推荐

