Spring Boot图片展示与统一返回格式:从资源映射到泛型封装实战

第一部分:图片展示问题(图片展示前半段请看上一期文章)

1.1 上传与展示的区别

⭐ 老师强调 :上传 和展示是两个独立步骤------上传只是把文件存进去,展示是后续从存储中读取并呈现,不能以为传完就自动显示。

步骤 说明
上传 把文件存到磁盘(如E:/upload/)
展示 从存储中读取并渲染到页面

问题现象:

  • 用户上传图片后,前端页面没有变化
  • 后端接口仅返回了success状态,没有返回图片具体信息
  • 前端接收到数据后,缺少处理逻辑,无法展示图片

1.2 图片的特殊性

⭐ 老师强调 :图片较特殊,必须显式引入页面才能展示;而word、pdf等一般文件不需要引。

复制代码
<!-- 图片必须用img标签引入 -->
<img src="cebbcb019f2f469ca6330d7fb5dbb431.jpg">

1.3 路径问题

⭐ 老师强调 :不能直接写文件路径 。因为网页默认用http协议 加载,直接写本地路径对应的是file协议,两协议不同,浏览器不允许这样加载资源。

协议 说明
http协议 网页默认使用
file协议 本地文件路径使用

⭐ 老师强调 :传统Tomcat部署目录有文件夹,可直接通过路径访问静态资源;但Spring Boot内嵌Tomcat无法操作,文件在磁盘某处也访问不了。

第二部分:资源映射配置(WebConfig)

2.1 解决思路

⭐ 老师强调 :写一个普通Java工具类,实现Spring MVC提供的扩展配置接口。该接口能在不修改Spring Boot配置的前提下改变Spring MVC行为。

WebMvcConfigurer接口:

  • 可拦截静态资源
  • 可处理跨域
  • 接口内含多个方法,按需求实现即可

2.2 WebConfig代码

复制代码
package com.qcby.util;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

// 资源处理器映射
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 访问地址:OIP-C.jpg
        registry.addResourceHandler("/showImg/**")
                .addResourceLocations("file:E:/upload/");
    }
}

2.3 映射规则

⭐ 老师强调 :访问前缀与本地目录的绑定关系,且配置需成对修改保持一致。

部分 作用 示例
/showImg/** 访问前缀(可随意改名) xxx.jpg
file:E:/upload/ 本地磁盘目录 E:xxx.jpg

映射效果:

复制代码
请求 OIP-C.jpg
    │
    ▼
映射到 file:E:OIP-C.jpg
    │
    ▼
返回图片给浏览器

⭐ 老师强调:

  • 这类似给网址起"外号"指向真实文件夹
  • 前缀像门牌,后段像房号,拼起来才找到文件
  • 代码两处要同步改,只改一处会导致映射失效

第三部分:统一返回格式

3.1 问题背景

⭐ 老师强调 :后端接口查单个返回的是对象 ,查所有返回的是数组,两者格式不统一。前端拿到对象只能"点属性"直接用,拿到数组却要加循环依次套用,得先判断查的是"一个"还是"所有"。

接口 返回类型 前端处理
查全部 List 需要循环
按id查 Student对象 直接点属性

⭐ 老师强调 :这种对象与数组混用的方式不合理,应该像之前Servlet那样统一返回格式,避免前端额外区分。

3.2 统一返回模板

⭐ 老师强调 :不管查询方式是查全部还是按id查,也不管结果有几条,返回结构必须统一------业务数据都放在data字段里。

返回模板结构:

复制代码
{
    "code": 200,
    "msg": "提示信息",
    "data": "业务数据"
}

3.3 ResponseResult代码

复制代码
package com.qcby.model;(放这)

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {
    private Integer code;
    private String msg;
    private T data;
    
    public ResponseResult(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    
    public ResponseResult(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

    public Integer getCode() { return code; }
    public void setCode(Integer code) { this.code = code; }

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

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

3.4 泛型 vs Object

⭐ 老师强调 :Object可作为最大父类型来兜底承载数据,但更推荐用泛型,由调用方传入具体类型,灵活且类型安全。

对比 Object 泛型<T>
类型安全 需强制转换 编译期检查
灵活性 低 高
推荐度 ❌ ✅

3.5 构造方法

⭐ 老师强调:至少可定义三个构造方法,也可按需简化。

复制代码
// 完整构造(含msg)
public ResponseResult(Integer code, String msg, T data) { ... }

// 简化构造(不含msg)
public ResponseResult(Integer code, T data) { ... }

⭐ 老师强调:根据传参决定调用哪个构造方法,比如传200和list时,只返回code和data,不带message。

3.6 @JsonInclude注解

⭐ 老师强调 :未赋值的引用类型字段(如message设为null)默认也会输出。若想空值不展示,可使用@JsonInclude注解。

复制代码
放在ResponseResult上

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {
    // message为空时不输出
}

效果对比:

设置 返回结果
无注解 {"code":200,"msg":null,"data":[...]}
有注解 {"code":200,"data":[...]}

⭐ 老师强调 :区分"不赋值"和"不输出"------不赋值是数据状态,不输出靠注解控制序列化行为,二者不可混淆。

第四部分:Controller改造

4.1 改造前

复制代码
@RequestMapping("search")
@ResponseBody
public List<Student> findAll() {
    List<Student> list = studentService.findAll();
    return list;  // 直接返回List
}

@RequestMapping("searchById")
@ResponseBody
public Student searchById(Integer id) {
    Student student = studentService.findById(id);
    return student;  // 直接返回对象
}

4.2 改造后(统一格式)

复制代码
@RequestMapping("search")
@ResponseBody
public ResponseResult findAll() {
    System.out.println("controller层查找");
    List<Student> list = studentService.findAll();
    return new ResponseResult(200, list);  // 包进Result
}

@RequestMapping("searchById")
@ResponseBody
public ResponseResult searchById(Integer id) {
    Student student = studentService.findById(id);
    return new ResponseResult(200, student);  // 包进Result
}

⭐ 老师强调:

  • 别直接返裸数据,要"绕一下"包进Result
  • 所有数据都放进data字段,保持结构统一
  • 前端拿到的永远是value.data,不用区分对象还是数组

附录一:完整代码汇总

WebConfig.java(资源映射)

复制代码
package com.qcby.util;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/showImg/**")
                .addResourceLocations("file:E:/upload/");
    }
}

ResponseResult.java(统一返回模板)

复制代码
package com.qcby.model;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {
    private Integer code;
    private String msg;
    private T data;

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

    public ResponseResult(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

    public Integer getCode() { return code; }
    public void setCode(Integer code) { this.code = code; }

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

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

StudentController.java

复制代码
package com.qcby.controller;

import com.qcby.entity.Student;
import com.qcby.model.ResponseResult;
import com.qcby.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@Controller
public class StudentController {

    @Autowired
    StudentService studentService;

    @RequestMapping("search")
    @ResponseBody
    public ResponseResult findAll() {
        System.out.println("controller层查找");
        List<Student> list = studentService.findAll();
        return new ResponseResult(200, list);
    }

    @RequestMapping("searchById")
    @ResponseBody
    public ResponseResult searchById(Integer id) {
        Student student = studentService.findById(id);
        return new ResponseResult(200, student);
    }

    @RequestMapping("delete")
    @ResponseBody
    public int delete(Integer id) {
        int code = studentService.delete(id);
        return code;
    }
}

index.html(图片展示)

复制代码
<!-- 上传表单 -->
<form class="filebox">
    选择文件:<input type="file" class="file" name="file">
    <input type="button" class="btn" value="上传">
</form>

<!-- 图片展示:路径指向映射前缀 -->
<img src="cebbcb019f2f469ca6330d7fb5dbb431.jpg">

<!-- 文件下载 -->
<h3>文件下载</h3>
<div class="block">
    <div>服务器保存文件名(saveFileName):</div>
    <input type="text" class="downFileName" placeholder="上传成功后复制saveFileName填入">
    <button class="内部工具Btn">下载文件</button>
</div>
相关推荐
钝挫力PROGRAMER13 小时前
开发踩坑记:MyBatis selectKey、空 SQL
数据库·sql·mybatis
自强的小白14 小时前
核心功能(Service接口)
java·mybatis
自强的小白19 小时前
mybatis-plus 的扩展功能
mybatis·mybatis_plus
自强的小白20 小时前
MYbatis-plus(自定义sql)
java·sql·mybatis
\光辉岁月/1 天前
6.mybatisplus学习-BaseMapper、Service、常用注解
java·mybatis·mybatisplus
\光辉岁月/2 天前
1.mybatis学习-基础
学习·mybatis
代码什么用2 天前
在WEB中的应用
mybatis
牧瀬クリスだ2 天前
Mybatis-Plus:让CRUD快如闪电
java·spring boot·spring·tomcat·mybatis·yml
九皇叔叔3 天前
【09】SpringBoot4 MyBatisPlus 增删改查(CRUD)
java·mybatis·mybatisplus