WebClient下载两张图片后处理

文章介绍了在Webflux框架下,利用webClient下载两个图片并进行后续处理的三种不同方案。方案一先下载图片1,成功后再下载图片2;方案二等待两个图片都下载且状态码为2xx再处理;方案三同时下载两个图片,通过onErrorReturn处理错误。所有方案都涉及到检查响应状态码、转换图片为Base64编码以及调用服务进行处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数需要做两件事情,根据请求vo里的url字段,下载2个图片。调用myService的方法,将结果填在返回vo里。使用Webflux的webclient
三种方法的共同代码:

    private void writeRecord(String logId, String f1Url, String f2Url, Float similarity) {
        IdentityEntity e = new IdentityEntity();
        e.setF1Url(f1Url);
        e.setF2Url(f2Url);
        e.setSimilarity(similarity);
        e.setLogId(logId);
        this.repository.save(e);
    }

方法一的共同代码:

private Mono<ClientResponse> getImage(String url) {
        return webClient.get()
                .uri(url)
                .exchange();
    }

方案一、下载图片1,如果成功,再下载图片2。都下载之后进行处理。


    @Override
    public Mono<IdentityResponseVo> compare(Mono<IdentityRequestVo> vo) {
        return vo.flatMap(v -> {
            Mono<ClientResponse> r1 = getImage(v.getF1Url());
            Mono<IdentityResponseVo> map = r1.flatMap(clientResponse -> {
                if (!clientResponse.statusCode().is2xxSuccessful()) {
                    return Mono.just(new IdentityResponseVo(ERR_DOWNLOAD, v.getLogId()));
                } else {
                    Mono<ClientResponse> r2 = getImage(v.getF2Url());
                    Mono<IdentityResponseVo> map1 = r2.flatMap(clientResponse2 -> {
                        if (!clientResponse2.statusCode().is2xxSuccessful()) {

                            return Mono.just(new IdentityResponseVo(ERR_DOWNLOAD, v.getLogId()));
                        } else {
                            // Convert first image to base64
                            Mono<String> f1Base64Mono = r1.flatMap(response -> response.bodyToMono(byte[].class))
                                    .map(Base64::encodeAsString);

                            // Convert second image to base64
                            Mono<String> f2Base64Mono = r2.flatMap(response -> response.bodyToMono(byte[].class))
                                    .map(Base64::encodeAsString);

                            Mono<AiIdentity> aiIdentityMono = f1Base64Mono.flatMap(f1Base64 ->
                                    f2Base64Mono.map(f2Base64 -> myService.handle(f1Base64, f2Base64))
                                            .doOnNext(ret -> this.writeRecord(v.getLogId(), v.getF1Url(), v.getF2Url(), ret.getSimilarity())));
                            return aiIdentityMono.map(ret -> IdentityResponseVo.transform(ret, v.getLogId()));
                        }
                    });
                    return map1;
                }
            });
            return map;
        });
    }

方案二、下载两个图片,如果都是2xx才会进行处理,否则返回错误:

    @Override
    public Mono<IdentityResponseVo> compare2(Mono<IdentityRequestVo> vo) {
        return vo.flatMap(v -> {
            Mono<ClientResponse> r1 = getImage(v.getF1Url());
            Mono<ClientResponse> r2 = getImage(v.getF2Url());
            Mono<Boolean> both2xx = Mono.zip(r1.map(ClientResponse::statusCode), r2.map(ClientResponse::statusCode))
                    .map(statusCode -> {
                        log.info("{} is error: {} and {} is error: {}",
                                v.getF1Url(), statusCode.getT1().is2xxSuccessful(),
                                v.getF2Url(), statusCode.getT2().is2xxSuccessful());
                        return statusCode.getT1().is2xxSuccessful() && statusCode.getT2().is2xxSuccessful();
                    });


            return both2xx.flatMap(received -> {
                if (received) {
                    // Convert first image to base64
                    Mono<String> f1Base64Mono = r1.flatMap(response -> response.bodyToMono(byte[].class))
                            .map(Base64::encodeAsString);

                    // Convert second image to base64
                    Mono<String> f2Base64Mono = r2.flatMap(response -> response.bodyToMono(byte[].class))
                            .map(Base64::encodeAsString);

                    Mono<AiIdentity> aiIdentityMono = f1Base64Mono.flatMap(f1Base64 ->
                            f2Base64Mono.map(f2Base64 -> myService.handle(f1Base64, f2Base64))
                    ).doOnNext(ret -> this.writeRecord(v.getLogId(), v.getF1Url(), v.getF2Url(), ret.getSimilarity()));
                    return aiIdentityMono.map(ret -> IdentityResponseVo.transform(ret, v.getLogId()));
                } else {
                    // Return default JSON if any of the images were not received successfully
                    return Mono.just(new IdentityResponseVo(ERR_DOWNLOAD, v.getLogId()));
                }
            });
        });
    }

方案三、“同时”下载图片1和2,通过onErrorReturn,处理任何一个图片响应不是2xx的情况
如果下载图片响应不是404,会得到Mono.error, WebResponseException,这时onErrorReturn就会执行。如果响应404,Controller层直接返回,没有任何的body或者日志。那是因为Mono.empty,终止了整个数据流。

    private Mono<String> getImageByUrl(String url) {
        Mono<byte[]> mono = webClient.get()
                .uri(url)
                .retrieve()
                .bodyToMono(byte[].class)
                .onErrorResume(WebClientResponseException.class,
                        ex -> ex.getRawStatusCode() == 404 ? Mono.empty() : Mono.error(ex));
        return mono.map(Base64::encodeAsString);
    }

    @Override
    public Mono<IdentityResponseVo> compare(Mono<IdentityRequestVo> vo) {
        return vo.flatMap(v -> {
            Mono<String> f1Base64Mono = getImageByUrl(v.getF1Url());
            Mono<String> f2Base64Mono = getImageByUrl(v.getF2Url());
            Mono<AiIdentity> aiIdentityMono = f1Base64Mono.flatMap(f1Base64 ->
                    f2Base64Mono.map(f2Base64 -> myService.handle(f1Base64, f2Base64))
            ).doOnNext(ret -> this.writeRecord(v.getLogId(), v.getF1Url(), v.getF2Url(), ret.getSimilarity()));
            return aiIdentityMono.map(ret -> IdentityResponseVo.transform(ret, v.getLogId()))
                    .onErrorReturn(new IdentityResponseVo(ResponseState.ERR_DOWNLOAD, v.getLogId()));
        });
    }
<think>好的,我现在需要在Spring Boot中实现类似微信的九宫格群头像生成功能。首先,我需要理解用户的需求。用户想要生成一个群头像,当有多个用户时,将这些头像按照九宫格的方式排列组合成一张图片。常见的排列方式有1x1、2x2、3x3等,最多支持九个头像。超过九个的话,可能只显示前九个或者进行压缩处理。 接下来,我需要考虑技术实现方案。Spring Boot作为后端框架,主要负责处理图片生成逻辑,而前端可能需要上传图片或者提供用户头像的URL。生成群头像的关键点在于图像处理和合成,所以需要用到Java的图像处理库。常见的库有Java自带的BufferedImage和Graphics2D,或者第三方库如Thumbnailator来简化操作。 首先,确定步骤: 1. 获取用户头像列表,可能需要从数据库或第三方服务获取URL。 2. 下载头像图片,可能需要处理网络请求和图片缓存。 3. 将头像处理成统一尺寸,比如正方形,并裁剪或缩放以适应每个宫格的位置。 4. 创建一个画布,按照九宫格的布局排列这些头像。 5. 将合成后的图片保存或返回给前端。 接下来,具体实现: - 使用Java的BufferedImage创建目标画布,尺寸可能需要根据需求设定,比如300x300像素。 - 对于每个头像,使用Thumbnailator进行缩放和裁剪,确保每个小图是正方形的,避免变形。 - 根据头像数量确定布局,例如1个头像全屏,2个头像分成左右两半,4个头像2x2排列,最多9个3x3排列。 - 计算每个小图的位置和尺寸,使用Graphics2D绘制到画布上。 - 处理可能出现的异常,如图片下载失败、尺寸不匹配等。 代码方面,可能需要编写一个服务类,如GroupAvatarService,包含生成头像的方法。使用RestTemplate或WebClient下载图片,处理HTTP请求。图片处理部分,用Thumbnailator的缩放和裁剪功能。布局逻辑需要根据头像数量动态调整,可能需要一个工具类来处理布局计算。 另外,需要考虑性能优化,比如缓存处理过的头像,避免重复下载和缩放。同时,生成的图片可能需要存储到本地或云存储,并返回URL给前端。 测试方面,需要验证不同数量的头像(1-9个)是否正确生成,图片是否清晰,布局是否合理。还要测试异常情况,比如无效的图片URL,网络超时等如何处理。 最后,将功能集成到Spring Boot的Controller中,提供一个API接口,接收用户ID列表,返回生成的群头像URL。 可能遇到的问题包括图片处理性能、内存溢出(处理大图时)、以及跨平台兼容性。需要确保使用的图像库在不同环境下都能正常工作,比如在Linux服务器上是否有足够的图形处理支持。 总结,实现步骤大致分为获取头像、处理图片、布局合成、返回结果。代码示例需要展示这些关键步骤,使用合适的库和方法,确保功能可靠高效。</think>### 实现步骤与示例代码 #### 一、技术实现思路 1. **图像处理**:使用Java的`BufferedImage`和`Graphics2D`进行图像合成,结合`Thumbnailator`库简化缩放裁剪[^2][^3]。 2. **布局计算**:根据头像数量动态生成九宫格布局(如1x1到3x3)。 3. **网络请求**:通过`RestTemplate`或`OkHttp`下载远程头像。 4. **异常处理**:处理无效URL、网络超时等问题。 #### 二、核心代码示例 ```java @Service public class GroupAvatarService { // 定义画布尺寸和小图尺寸 private static final int CANVAS_SIZE = 300; private static final int MAX_GRID = 3; public BufferedImage generateGroupAvatar(List<String> imageUrls) throws IOException { int imageCount = Math.min(imageUrls.size(), 9); int gridSize = calculateGridSize(imageCount); int cellSize = CANVAS_SIZE / gridSize; // 创建画布 BufferedImage canvas = new BufferedImage(CANVAS_SIZE, CANVAS_SIZE, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = canvas.createGraphics(); graphics.setBackground(Color.WHITE); graphics.clearRect(0, 0, CANVAS_SIZE, CANVAS_SIZE); // 遍历图片并绘制 for (int i = 0; i < imageCount; i++) { BufferedImage img = downloadAndProcessImage(imageUrls.get(i), cellSize); int[] position = calculatePosition(i, gridSize, cellSize); graphics.drawImage(img, position[0], position[1], null); } graphics.dispose(); return canvas; } private BufferedImage downloadAndProcessImage(String url, int targetSize) throws IOException { BufferedImage srcImage = ImageIO.read(new URL(url)); return Thumbnails.of(srcImage) .size(targetSize, targetSize) .asBufferedImage(); } private int calculateGridSize(int count) { if (count <= 1) return 1; return (int) Math.ceil(Math.sqrt(count)); } private int[] calculatePosition(int index, int gridSize, int cellSize) { int row = index / gridSize; int col = index % gridSize; return new int[]{col * cellSize, row * cellSize}; } } ``` #### 三、Controller层调用 ```java @RestController @RequestMapping("/api/avatar") public class AvatarController { @Autowired private GroupAvatarService avatarService; @PostMapping("/generate") public ResponseEntity<byte[]> generateAvatar(@RequestBody List<String> imageUrls) throws IOException { BufferedImage result = avatarService.generateGroupAvatar(imageUrls); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(result, "PNG", baos); return ResponseEntity.ok() .contentType(MediaType.IMAGE_PNG) .body(baos.toByteArray()); } } ``` #### 四、优化方向 1. **缓存机制**:对下载的远程头像进行本地缓存,减少重复请求[^3]。 2. **异步处理**:使用`@Async`注解提升高并发下的响应速度[^2]。 3. **错误处理**:添加默认头像替换失效URL。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值