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

使用JAX-RS向React MUI返回图片无法显示的问题排查

问题描述

我用Java 17 Jersey的JAX-RS接口给React MUI前端返回PNG图片,前端能收到和后端一致的Base64字符串,但网页只显示图片的alt文本,无法正常展示图片。

后端代码(Jersey)

@GET // get data
@Path("downloadPanoramas") // file/downloadPanoramas is URL
@Produces(MediaType.APPLICATION_OCTET_STREAM) // what format we be use
public Response getPanoramas4()
{
    Response result;

    try
    {
        final String fileName = "pano_vis_a_panorama.png";
        java.nio.file.Path panoramaPath = get(YamlReader.getImagePath(), fileName);
        BufferedImage image = ImageIO.read(panoramaPath.toFile());
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "png", outputStream);
        byte[] encodedImage = Base64.encode(outputStream.toByteArray());

        Response.ResponseBuilder builder = Response.ok(encodedImage).header("Content-Disposition", "attachment; filename=" + fileName);
        // All Done.
        result = builder.build();
    }
    catch (IOException | RuntimeException ex)
    {
        JsonObject resultMsg = Json.createObjectBuilder().add("status",
                        Json.createObjectBuilder().add("code", 1)
                                .add("message", ex.getMessage())
                                .build())
                .build();
        return Response.status(Response.Status.BAD_REQUEST).entity(resultMsg.toString()).build();
    }

    return result;
}

前端代码(React + MUI)

import Typography from "@mui/material/Typography";
import React, {useState} from "react";
import Box from "@mui/material/Box";
import axios from "axios";
import Button from "@mui/material/Button";
import {Buffer} from 'buffer';

const API_ENDPOINT = process.env.REACT_APP_API_ENDPOINT;
const SERVLET_NAME = process.env.REACT_APP_SERVLET_NAME;

function PanoramaTool(props) {
    const [image64, setImage64] = useState("");

    const url_DP  = API_ENDPOINT + "/" + SERVLET_NAME + "/rdt/file/downloadPanoramas"; // Download panorama file request

    const getImage = () => {
        axios({
            method: 'GET',
            url: url_DP,
            responseType: 'arraybuffer',
        }).then((response) => {
                setImage64(Buffer.from(response.data, "binary").toString("base64"))
        }).catch(function(error) {
            console.log(error);
        })
    };

    return (
        <Box sx={{ margin: 2 }}>
            <Typography variant="h2" align="center" sx={{mb: 1}} gutterBottom>
                Panorama Configuration
            </Typography>
            <Box>
                <Box>
                    <img src={`data:image/png;base64,${image64}`} alt="pano_vis_a_panorama.png"/>
                    <Button onClick={getImage} sx={{ mt: 1, mr: 1 }}>
                        panorama
                    </Button>
                </Box>
            </Box>
        </Box>
    );
}

export default PanoramaTool;
问题原因及解决办法

核心问题是后端和前端的编码逻辑重复且不匹配:后端把图片转成Base64编码的字节数组后以二进制流返回,前端又把收到的二进制流再转一次Base64,相当于对图片做了两次Base64处理,最终生成的dataURL无效,导致图片无法显示。

两种修复方案

方案一:直接返回图片二进制(推荐,性能更优)

后端不做Base64编码,直接返回图片原始字节流,前端将二进制转成Base64使用:

后端修改

@GET
@Path("downloadPanoramas")
@Produces(MediaType.IMAGE_PNG) // 明确返回PNG图片类型
public Response getPanoramas4() {
    try {
        final String fileName = "pano_vis_a_panorama.png";
        java.nio.file.Path panoramaPath = get(YamlReader.getImagePath(), fileName);
        // 直接读取文件字节,跳过BufferedImage转写,避免格式兼容问题
        byte[] imageBytes = Files.readAllBytes(panoramaPath);
        
        Response.ResponseBuilder builder = Response.ok(imageBytes)
                .header("Content-Disposition", "inline; filename=" + fileName); // 用inline让浏览器直接显示图片而非触发下载
        return builder.build();
    } catch (IOException | RuntimeException ex) {
        JsonObject resultMsg = Json.createObjectBuilder()
                .add("status", Json.createObjectBuilder()
                        .add("code", 1)
                        .add("message", ex.getMessage())
                        .build())
                .build();
        return Response.status(Response.Status.BAD_REQUEST)
                .entity(resultMsg.toString())
                .build();
    }
}

前端修改

保持responseType为arraybuffer,转Base64逻辑不变:

const getImage = () => {
    axios({
        method: 'GET',
        url: url_DP,
        responseType: 'arraybuffer',
    }).then((response) => {
            setImage64(Buffer.from(response.data).toString("base64"))
    }).catch(function(error) {
        console.log(error);
    })
};

方案二:后端返回Base64文本,前端直接使用

后端把图片转成Base64字符串以文本形式返回,前端直接使用该字符串:

后端修改

@GET
@Path("downloadPanoramas")
@Produces(MediaType.TEXT_PLAIN) // 返回文本类型
public Response getPanoramas4() {
    try {
        final String fileName = "pano_vis_a_panorama.png";
        java.nio.file.Path panoramaPath = get(YamlReader.getImagePath(), fileName);
        BufferedImage image = ImageIO.read(panoramaPath.toFile());
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "png", outputStream);
        // 转成Base64字符串而非字节数组
        String encodedImage = Base64.getEncoder().encodeToString(outputStream.toByteArray());
        
        Response.ResponseBuilder builder = Response.ok(encodedImage);
        return builder.build();
    } catch (IOException | RuntimeException ex) {
        JsonObject resultMsg = Json.createObjectBuilder()
                .add("status", Json.createObjectBuilder()
                        .add("code", 1)
                        .add("message", ex.getMessage())
                        .build())
                .build();
        return Response.status(Response.Status.BAD_REQUEST)
                .entity(resultMsg.toString())
                .build();
    }
}

前端修改

将responseType改为text,直接使用返回的字符串:

const getImage = () => {
    axios({
        method: 'GET',
        url: url_DP,
        responseType: 'text', // 改为文本类型
    }).then((response) => {
            setImage64(response.data) // 直接赋值,无需二次转换
    }).catch(function(error) {
        console.log(error);
    })
};

额外注意点

  • 方案一中用Files.readAllBytes直接读取文件,比通过BufferedImage转写更高效,也避免了ImageIO可能带来的格式兼容问题(比如某些PNG透明通道处理异常)。
  • Content-Disposition头的inline和attachment分别对应浏览器直接显示图片和触发下载,可根据需求选择。

内容的提问来源于stack exchange,提问作者John kerich

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 15:14:52