☰
Spring Boot图片展示与统一返回格式:从资源映射到泛型封装实战
2026/10/1 6:58:11 网站建设 项目流程

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

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>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询