You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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自行加载图片,这是最高效的方式:

  1. 在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();
  1. 确保数据库中YuGiOhCardImage的imageUrl字段存储的是图片文件名(如blue-eyes-white-dragon.jpeg),返回的卡牌数据已包含YuGiOhCardImage数组,客户端直接用http://你的域名/[imageUrl]即可访问图片。

方案二:将图片转为Base64嵌入返回数据

如果需要一次性返回卡牌属性和图片数据,可把图片转为Base64字符串:

  1. 修改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;
}
  1. 修改Controller的findCardByName方法:
@Get('/:name')
async findCardByName(@Param('name') name: YuGiOhCard['name']) {
  const cardWithImage = await this.searchCardService.findCardWithBase64Image(name);
  return cardWithImage || { message: '卡牌不存在' };
}

注意:Base64会让数据体积增大约30%,仅适合小体积图片场景。

方案三:单独提供图片流式接口

保留原属性接口,新增一个返回图片流的接口:

  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 06:45:17