基于豆包大模型的前后端分离图文识别项目实战

        在 AI 多模态技术飞速发展的今天,图文识别作为核心应用场景之一,已广泛渗透到智能客服、内容审核、教育答疑等领域。豆包大模型(Doubao LLM)凭借其强大的多模态理解能力,为图文交互提供了高效、准确的技术支撑。本文将详细介绍如何基于豆包大模型构建一套前后端分离的图文识别系统,重点拆解后端实现逻辑与核心代码设计。

        本篇文章先给出后端代码,后续也会继续调用其他模型和应用加进去,至于安卓程序我就在另外的一片文章写,到时候写好发出来后我会在评论区留言。

一、项目背景与技术选型

1. 项目目标

打造一套支持两种图片输入方式(在线图片 URL、本地图片上传)的图文问答系统:用户输入图片(或图片链接)并提出相关问题,系统调用豆包大模型接口,返回精准的图文关联回答。

2. 技术栈选型

  • 后端框架:Spring Boot 3.x(轻量高效,易于集成第三方服务)
  • 核心依赖:火山引擎 Ark Service SDK(对接豆包大模型)、Spring Web(处理 HTTP 请求)、Base64(图片编码)、OkHttp3(HTTP 客户端)
  • 接口文档:Swagger(自动生成接口文档,便于前后端联调)
  • 前端架构:前后端分离(支持 Vue/React 等任意前端框架,通过 RESTful 接口交互)
  • 模型选型:豆包大模型(doubao-seed-1-6-thinking-250715),具备优秀的图文协同理解能力

3. 核心架构设计

  • 前端:负责用户交互(输入问题、上传图片 / 输入图片 URL)、结果展示
  • 后端:提供 RESTful 接口,处理图片编码、参数校验、模型调用、结果封装
  • 豆包大模型:接收后端构造的图文请求,返回自然语言回答

二、后端核心功能设计与代码解析

1. 项目目录结构

2. 统一返回结果设计(AjaxJsonResult)

为保证前后端交互的一致性,定义全局统一返回类AjaxJsonResult,包含状态码、提示信息和响应数据:

package gzj.spring.ai.R;

public class AjaxJsonResult<T> {
    private  T data;
    private  String code;//成功:2000;失败:2001
    private String msg;//code-2000:成功;code-2001:失败

    public AjaxJsonResult(T data, String code, String msg) {
        this.data = data;
        this.code = code;
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

3. 接口层设计(Controller)

控制器提供两个核心接口,分别对应「在线图片 URL 问答」和「本地图片上传问答」,通过 Swagger 注解生成接口文档:

import com.volcengine.ark.runtime.model.responses.response.ResponseObject;
import gzj.spring.ai.R.AjaxJsonResult;
import gzj.spring.ai.req.ChatVisionReq;
import gzj.spring.ai.res.ChatVisionRes;
import gzj.spring.ai.service.ChatVisionService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author DELL
 */
@RestController
@RequestMapping("/doubao")
@Api(tags = "LLM接口")
public class ChatVisionController {

    private final ChatVisionService chatVisionService;

    public ChatVisionController(ChatVisionService chatVisionService) {
        this.chatVisionService = chatVisionService;
    }

    // 图片问答接口(POST请求)
    @PostMapping("/imagellm")
    @ApiModelProperty(value = "问答接口",notes = "问答接口")
    public AjaxJsonResult<ChatVisionRes> imageQa(@RequestBody ChatVisionReq request) {
        // 调用服务层逻辑
        return chatVisionService.getImageAnswer(request);
    }

    @PostMapping("/upload")
    @ApiModelProperty(value = "上传图片接口",notes = "上传图片接口")
    public AjaxJsonResult<ResponseObject> uploadImageAndQa(@RequestParam MultipartFile file, @RequestParam String question) {
        // 调用服务层逻辑
        return chatVisionService.uploadImageAndQa(file, question);
    }

}

4. 服务层实现(Service && ServiceImpl)

服务层是核心逻辑载体,负责参数校验、图片处理、模型调用和结果封装,分为两个核心方法:

import com.volcengine.ark.runtime.model.responses.response.ResponseObject;
import gzj.spring.ai.R.AjaxJsonResult;
import gzj.spring.ai.req.ChatVisionReq;
import gzj.spring.ai.res.ChatVisionRes;
import org.springframework.web.multipart.MultipartFile;


/**
 * @author DELL
 */
public interface ChatVisionService {

    //  图片问答接口( 在线https )
    public AjaxJsonResult<ChatVisionRes> getImageAnswer(ChatVisionReq request);

    //  图片问答接口( 本地图片base64 )
    public AjaxJsonResult<ResponseObject> uploadImageAndQa(MultipartFile file, String question);

}
(1)在线图片 URL 问答实现

接收前端传入的图片 URL 和问题,构造豆包模型请求参数,调用模型接口并返回结果:

@Service
public class ChatVisionServiceImpl implements ChatVisionService {

    private static final Logger log = LoggerFactory.getLogger(ChatVisionServiceImpl.class);

    @Resource
    private ArkServiceUtil arkServiceUtil;

    // 从配置文件读取豆包模型名称(默认值兜底)
    @Value("${doubao.model:doubao-seed-1-6-thinking-250715}")
    private String arkModel;

    @Override
    public AjaxJsonResult<ChatVisionRes> getImageAnswer(ChatVisionReq request) {
        // 获取豆包模型客户端(确保初始化完成)
        ArkService arkService = ArkServiceUtil.getArkService();

        // 构建图文请求参数:图片URL + 问题文本
        List<ChatMessage> messages = new ArrayList<>();
        List<ChatCompletionContentPart> multiParts = new ArrayList<>();

        // 添加图片URL参数
        multiParts.add(ChatCompletionContentPart.builder()
                .type("image_url")
                .imageUrl(new ChatCompletionContentPart.ChatCompletionContentPartImageURL(request.getImageUrl()))
                .build());

        // 添加问题文本参数
        multiParts.add(ChatCompletionContentPart.builder()
                .type("text")
                .text(request.getQuestion())
                .build());

        // 构造用户消息(角色为USER)
        ChatMessage userMessage = ChatMessage.builder()
                .role(ChatMessageRole.USER)
                .multiContent(multiParts)
                .build();
        messages.add(userMessage);

        // 构建模型请求
        ChatCompletionRequest chatRequest = ChatCompletionRequest.builder()
                .model(arkModel)
                .messages(messages)
                .build();

        // 调用豆包模型接口,获取回答
        List<ChatCompletionChoice> choices = arkService.createChatCompletion(chatRequest).getChoices();
        String answer = choices.get(0).getMessage().getContent().toString();

        // 封装响应结果
        ChatVisionRes response = new ChatVisionRes();
        response.setAnswer(answer);
        return new AjaxJsonResult<>(response, "200", "success");
    }
(2)本地图片上传问答实现

处理前端上传的图片文件,转为 Base64 编码(豆包模型要求的图片格式),再调用模型接口:

    @Override
    public AjaxJsonResult<ResponseObject> uploadImageAndQa(MultipartFile file, String question) {
        // 1. 参数校验
        if (file.isEmpty()) {
            return new AjaxJsonResult<>(null, "400", "上传的图片文件不能为空");
        }
        if (question == null || question.trim().isEmpty()) {
            return new AjaxJsonResult<>(null, "400", "问题不能为空");
        }

        ArkService arkService = ArkServiceUtil.getArkService();
        try {
            // 2. 图片转为Base64编码(无文件路径依赖,直接读取字节)
            String base64Str = encodeMultipartFile(file);

            // 3. 动态获取图片类型(适配png/jpg/jpeg)
            String contentType = file.getContentType();
            String imageType = contentType != null && contentType.startsWith("image/")
                    ? contentType.split("/")[1]
                    : "png"; // 兜底默认值

            // 4. 拼接豆包模型要求的Base64前缀(格式:data:image/xxx;base64,xxx)
            String base64Image = String.format("data:image/%s;base64,%s", imageType, base64Str);

            // 5. 构建模型请求:Base64图片 + 问题文本
            CreateResponsesRequest request = CreateResponsesRequest.builder()
                    .model(arkModel)
                    .input(ResponsesInput.builder().addListItem(
                            ItemEasyMessage.builder()
                                    .role(ResponsesConstants.MESSAGE_ROLE_USER)
                                    .content(MessageContent.builder()
                                            .addListItem(InputContentItemImage.builder().imageUrl(base64Image).build())
                                            .addListItem(InputContentItemText.builder().text(question).build())
                                            .build())
                                    .build()
                    ).build())
                    .build();

            // 6. 调用模型接口并返回结果
            ResponseObject response = arkService.createResponse(request);
            return new AjaxJsonResult<>(response, "200", "success");

        } catch (IOException e) {
            log.error("图片Base64编码失败:{}", e.getMessage(), e);
            return new AjaxJsonResult<>(null, "500", "图片处理失败:" + e.getMessage());
        } catch (Exception e) {
            log.error("豆包模型调用失败:{}", e.getMessage(), e);
            return new AjaxJsonResult<>(null, "500", "问答失败:" + e.getMessage());
        }
    }

    // 图片转Base64工具方法
    private String encodeMultipartFile(MultipartFile file) throws IOException {
        byte[] fileBytes = file.getBytes();
        return Base64.getEncoder().encodeToString(fileBytes);
    }
}

5. 豆包模型客户端工具类

ArkServiceUtil负责初始化豆包模型客户端(ArkService),通过@PostConstruct确保 Spring Bean 初始化后再构建客户端,避免空指针异常:

@Component
public class ArkServiceUtil {
    // 从环境变量获取API密钥(避免硬编码,提高安全性)
    private static String apiKey = System.getenv("ARK_API_KEY");

    // 豆包模型接口BaseURL
    private static final String baseUrl = "https://ark.cn-beijing.volces.com/api/v3";

    // HTTP连接池配置(优化性能)
    private static final ConnectionPool connectionPool = new ConnectionPool(5, 1, TimeUnit.SECONDS);
    private static final Dispatcher dispatcher = new Dispatcher();

    // 豆包模型客户端实例
    private static ArkService arkService;

    // Spring Bean初始化完成后执行
    @PostConstruct
    public void initArkService() {
        if (apiKey == null || apiKey.trim().isEmpty()) {
            throw new IllegalArgumentException("请配置ARK_API_KEY环境变量(豆包模型API密钥)");
        }
        // 构建客户端实例
        this.arkService = ArkService.builder()
                .dispatcher(dispatcher)
                .connectionPool(connectionPool)
                .baseUrl(baseUrl)
                .apiKey(apiKey)
                .build();
    }

    // 提供客户端实例获取方法
    public static ArkService getArkService() {
        if (arkService == null) {
            throw new RuntimeException("豆包模型客户端未初始化,请检查配置");
        }
        return arkService;
    }
}

6. 配置文件说明(application.yml)

server:
  port: 8080
spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 100MB
      max-request-size: 100MB
  # Redis配置
  data:
    redis:
      port: 6379 # 端口
      password:  # 无密码则留空
      database: 0 # 数据库索引(默认0)
      timeout: 3000ms # 连接超时
      lettuce:
        pool:
          max-active: 8 # 最大连接数
          max-idle: 8 # 最大空闲连接
          min-idle: 0 # 最小空闲连接
          max-wait: -1ms # 连接等待时间

四、项目亮点与优化方向

1. 核心亮点

  • 多输入方式支持:同时兼容在线图片 URL 和本地图片上传,满足不同场景需求;
  • 健壮性设计:完善的参数校验、异常处理和日志记录,降低系统故障率;
  • 性能优化:通过 HTTP 连接池复用连接,减少模型调用延迟;
  • 安全性保障:API 密钥从环境变量读取,避免硬编码泄露风险;
  • 开发规范:采用分层架构、统一返回结果、Swagger 接口文档,便于维护和联调。

2. 未来优化方向

在后续的模型新增调用以及优化中,我还是会在这个项目的基础上去新增接口或优化,最后我也会把项目资源放出来。

  • 增加图片格式 / 大小校验,避免无效请求;
  • 支持批量图片上传和批量问答;
  • 前端优化:增加图片预览、加载状态提示、历史记录功能;
  • 集成豆包模型的流式响应,实现回答实时展示;
  • 增加缓存机制,缓存高频查询结果,提升响应速度。

五、总结

        本项目基于豆包大模型(doubao-seed-1-6-thinking)构建了一套轻量、高效的前后端分离图文识别系统,通过 Spring Boot 后端封装模型调用逻辑,提供标准化接口,前端快速对接实现用户交互。核心在于准确理解豆包模型的图文请求格式(Base64 编码、参数结构),并通过合理的架构设计保证系统的稳定性和可扩展性。

END

        如果觉得这份修改实用、总结清晰,别忘了动动小手点个赞👍,再关注一下呀~ 后续还会分享更多 AI 接口封装、代码优化的干货技巧,一起解锁更多好用的功能,少踩坑多提效!🥰 你的支持就是我更新的最大动力,咱们下次分享再见呀~🌟

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐