Springboot 3 上传图片,并返回路径让前端显示图片
将图片存储在本地数据库中,通过访问后端的静态资源地址来访问图片
整体流程
- 通过接口上传文件(MultipartFile本质上是输入流)
- 对内容进行校验
- 将输入流转变为字节数组,确保可重复使用
- 转换图片格式
- 对图片进行保存和数据库中路径的存储
一、基本代码
代码中的路径尽量使用Paths.get 方法去拼接,而不是直接使用字符串拼接。
Controller层
@PostMapping("/uploadAvatar")public BaseResponse<String> uploadAvatar(@RequestParam MultipartFile file, Long userId) throws IOException {var fileInput = new BufferedInputStream(file.getInputStream());var result = fileService.uploadAvatar(fileInput, userId);return ResultUtils.success(result);}
MultipartFile类不能使用@RequestBody注解
@RequestBody:用于将请求体内容反序列化为Java对象,通常处理JSON或XML格式的数据
MultipartFile:用于处理文件上传,属于
multipart/form-data
格式,而不是JSON/XML
Validator校验类
使用了@Component注解
public File avatarValidator(BufferedInputStream fileInput, Long userId) throws IOException {if (!Objects.nonNull(fileInput) || !Objects.nonNull(userId)) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数错误");}var queryWrapper = new QueryWrapper();queryWrapper.eq(User::getId, userId);var user = userService.getOne(queryWrapper);if (!Objects.nonNull(user)) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在");}
// if (!Objects.nonNull(ImageIO.read(fileInput))) {
// throw new BusinessException(ErrorCode.PARAMS_ERROR, "请上传图片类型文件");
// }byte[] fileBytes = IoUtil.readBytes(fileInput);final int MAX_SIZE = 10 * 1024 * 1024;if (fileBytes.length > MAX_SIZE) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件过大,图片大小不超过10MB");}var image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);try {image = ImgUtil.read(IoUtil.toStream(fileBytes));File tempFile = File.createTempFile("image-", ".jpg");BufferedImage jpgImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);Graphics2D g2d = jpgImage.createGraphics();g2d.setColor(Color.WHITE);g2d.fillRect(0, 0, image.getWidth(), image.getHeight());g2d.drawImage(image, 0, 0, null);g2d.dispose();ImgUtil.write(jpgImage, tempFile);return tempFile;} catch (IOException e) {throw new BusinessException(ErrorCode.OPERATION_ERROR, "图片转换失败");}}
- 在使用一次输入流之后,再访问这个对象会失效,正确的方式需要将其转变为2进制对象。才能做到多次使用。
- 二进制对象再变为图片时如果尺寸大于图片的原尺寸,则会出现白色(或黑色)多余内容,请使用ai自行解决。
Impl实现类
@Overridepublic String uploadAvatar(BufferedInputStream fileInput, Long userId) throws IOException {var image = valid.avatarValidator(fileInput,userId);var extName = FileUtil.extName(image);if (!FileUtil.exist(properties.getUploadBaseDir())){FileUtil.mkdir(properties.getUploadBaseDir());}var currentDate = DateUtil.today();var rand = IdUtil.simpleUUID();var fileName = currentDate + "-" + rand + "." + extName;var IMAGE_PATH = Paths.get("images/avatar", fileName).toString();var queryWrapper = new QueryWrapper();queryWrapper.eq(User::getId,userId);var user = userService.getOne(queryWrapper);if (Objects.nonNull(user.getAvatar())){var oldPath = Paths.get(properties.getUploadBaseDir(),user.getAvatar());var fileQuery = new QueryWrapper();fileQuery.eq(File::getPath,user.getAvatar());var oldFile = this.getOne(fileQuery);this.removeById(oldFile);FileUtil.del(oldPath);}var saveFile = new java.io.File(Paths.get(properties.getUploadBaseDir(), IMAGE_PATH).toString());FileUtil.copy(image.toPath(), saveFile.toPath());//上传新文件user.setAvatar(IMAGE_PATH);userService.updateById(user);image.deleteOnExit();var fileSize = (int)image.length()/1024;var file =new File();file.setType("avatar");file.setPath(IMAGE_PATH);file.setSize( fileSize);file.setCreateId(userId);this.save( file);return IMAGE_PATH;}
二、配置静态资源处理器
public class CorsConfig implements WebMvcConfigurer {private final FileConfig config;public CorsConfig(FileConfig config) {this.config = config;}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {Path avatarDir = Paths.get(config.getUploadBaseDir(), "images/avatar");String normalizedPath = avatarDir.toAbsolutePath().toString().replace("\\", "/");String resourcePath = "file:" + normalizedPath + "/";registry.addResourceHandler("/images/avatar/**").addResourceLocations(resourcePath).setCachePeriod(3600);}
}
三、配置类
@Component
@ConfigurationProperties(prefix = "file.image")
@Data
public class FileConfig {private String uploadBaseDir;
}
在applicaiton.yml或者application.properties中添加自己的路径配置。
#文件上传路径
file:image:upload-base-dir: doc/
四、扩展
图片后续可以存入cos或者使用minIO进行存储,而不是存在本地。
五、前端展示
接口
export async function uploadAvatar(// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)params: API.uploadAvatarParams,body: {},options?: { [key: string]: any }
) {return request<API.BaseResponseString>('/file/uploadAvatar', {method: 'POST',params: {...params,},data: body,...(options || {}),})
}
接口不要添加请求头,不是json的格式
headers: {'Content-Type': 'application/json',},
图片地址
localhost:{后端端口}/{avatarPath} //avatarPath为后端数据库中存储的路径