当前位置: 首页 > news >正文

Windows VMWare Centos Docker部署Springboot 应用实现文件上传返回文件http链接

前置文章

Windows VMWare Centos环境下安装Docker并配置MySqlhttps://blog.csdn.net/u013224722/article/details/148928081 Windows VMWare Centos Docker部署Springboot应用https://blog.csdn.net/u013224722/article/details/148958480
Windows VMWare Centos Docker部署Nginx并配置对Springboot应用的访问代理https://blog.csdn.net/u013224722/article/details/149007158
Windows VMWare Centos Docker部署Springboot + mybatis + MySql应用https://blog.csdn.net/u013224722/article/details/149041367

一、Springboot实现文件上传接口 

修改FileRecordMapper相关文件,新增文件记录查询功能代码。(数据库表结构可参考前置文章)

# FileRecordMapper.java 新增List<FileRecord> selectAll();# FileRecordMapper.xml 新增<select id="selectAll" resultMap="BaseResultMap">select<include refid="Base_Column_List" />from files</select>

 新建FileController.java,创建文件上传、管理相关接口

package com.duelapi.controller;import com.duelapi.service.IFileService;
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.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@Controller
@RequestMapping("/file")
public class FileController {private IFileService fileService;@Autowiredpublic FileController(IFileService fileService) {this.fileService = fileService;}@RequestMapping(value = "/uploadFile", method = RequestMethod.POST)@ResponseBodypublic Map<String, Object> uploadFile(@RequestParam(value = "file") MultipartFile fileInfo,@RequestParam(value = "memberId", required = false) Integer memberId) {try {return this.fileService.uploadFile(fileInfo, memberId);} catch (IOException ex) {Map<String, Object> resultMap = new HashMap<>();resultMap.put("status", "-1");resultMap.put("msg", "error");return resultMap;}}@RequestMapping(value = "/getFileList", method = RequestMethod.GET)@ResponseBodypublic Map<String, Object> getFileList() {return this.fileService.getFileList();}@RequestMapping(value = "/deleteFile", method = RequestMethod.GET)@ResponseBodypublic Map<String, Object> deleteFile(@RequestParam(value = "id") Integer id) {return this.fileService.deleteFile(id);}
}

新建Interface   IFileService.java文件

package com.duelapi.service;import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.Map;public interface IFileService {Map<String, Object> uploadFile(MultipartFile fileInfo, Integer memberId) throws IOException;Map<String, Object> getFileList();Map<String, Object> deleteFile(Integer id);
}

新建FileService.java文件,处理文件存储。

package com.duelapi.serviceimpl;import com.duelapi.mapper.FileRecordMapper;
import com.duelapi.model.FileRecord;
import com.duelapi.service.IFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@Service
public class FileService implements IFileService {private FileRecordMapper fileMapper;@Autowiredpublic  FileService(FileRecordMapper fileMapper){this.fileMapper = fileMapper;}@Overridepublic Map<String, Object> uploadFile(MultipartFile fileInfo, Integer memberId) throws IOException {Map<String, Object> resultMap = new HashMap<>();try{String fileName = fileInfo.getOriginalFilename().trim();String fileType = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();String relativePath = "";if (memberId != null)relativePath = memberId + "/";elserelativePath = "ungroup/";String targetFileName = System.currentTimeMillis() + fileType;String sCacheFile = "/uploads/Cache/" + targetFileName;String sTargetFile = "/uploads/" + relativePath + targetFileName;File cacheFile = new File(sCacheFile);if (cacheFile.exists())cacheFile.delete();if (!cacheFile.getParentFile().exists())cacheFile.getParentFile().mkdirs();fileInfo.transferTo(cacheFile);File targetFile = new File(sTargetFile);if(targetFile.exists())targetFile.delete();if (!targetFile.getParentFile().exists())targetFile.getParentFile().mkdirs();cacheFile.renameTo(targetFile);String sUrl = "http://192.168.23.134:38080"+ "/uploads/" + relativePath + targetFileName;FileRecord fileRec = new FileRecord();fileRec.setFileName(fileName);fileRec.setFileType(fileType);fileRec.setStatusId(1);fileRec.setServerSavePath(sTargetFile);fileRec.setUrl(sUrl);fileRec.setUserId(memberId);int nFlag = fileMapper.insertSelective(fileRec);if(nFlag == 1){resultMap.put("status", 1);resultMap.put("msg", "success");resultMap.put("url", sUrl);resultMap.put("savePath", sTargetFile);}else {resultMap.put("status", 0);resultMap.put("msg", "Failed to save data to the database!");}return resultMap;}catch (Exception ex){resultMap.put("status", -1);resultMap.put("msg", ex.getMessage());return resultMap;}}@Overridepublic Map<String, Object> getFileList() {List<FileRecord> arrRecords = this.fileMapper.selectAll();List<Map<String, Object>> ltMaps = new ArrayList<>();int nAmount = 0;if (arrRecords != null && !arrRecords.isEmpty()) {ltMaps = arrRecords.stream().map(vo -> vo.toMap()).collect(Collectors.toList());nAmount = arrRecords.size();}Map<String, Object> resultMap = new HashMap<>();resultMap.put("rows", ltMaps);resultMap.put("total", nAmount);return resultMap;}@Overridepublic Map<String, Object> deleteFile(Integer id) {Map<String, Object> resultMap = new HashMap<>();FileRecord fileRec = this.fileMapper.selectByPrimaryKey(id);if(fileRec != null){String sFile = fileRec.getServerSavePath();File file = new File(sFile);if (file.exists())file.delete();int nFlag = this.fileMapper.deleteByPrimaryKey(id);if(nFlag == 1){resultMap.put("status", 1);resultMap.put("msg", "success");}else{resultMap.put("status", 0);resultMap.put("msg", "failed");}}else{resultMap.put("status", 0);resultMap.put("msg", "file record missing!");}return resultMap;}
}

二、H5实现测试板块

 新建文件管理的测试html文件  - files.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>测试</title><link href="./plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet"><link href="./plugins/bootstrap/css/bootstrap-table.min.css" rel="stylesheet">
</head>
<body><div style="max-width: 1360px; margin: 30px auto"><div id="tabToolbar"><a class="btn btn-info" href="index.html">Go To index Html</a></div><table id="tabMain" data-toolbar="#tabToolbar"></table><div style="margin-top: 40px; background: #e7e7e7; padding: 30px"><h4>添加文件</h4><input type="file" id="btnSelectFile" accept="*/*" style="display: none"><a class="btn btn-primary" onclick="$('input[id=btnSelectFile]').click();">选择上传</a></div>
</div><script type="text/javascript" src="js/const.js"></script>
<script type="text/javascript" src="js/jquery.min.js?v2.1.4"></script>
<script src="./plugins/bootstrap/js/bootstrap.min.js"></script>
<script src="./plugins/bootstrap/js/bootstrap-table.min.js"></script><script>$(document).ready(function () {doUpdateTab();bindSelectFileChange();});function doUpdateTab() {$('#tabMain').bootstrapTable('destroy');$('#tabMain').bootstrapTable({method: 'get',toolbar: '#tabToolbar',    //工具按钮用哪个容器striped: true,      //是否显示行间隔色cache: false,      //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)pagination: true,     //是否显示分页(*)sortable: false,      //是否启用排序sortOrder: "desc",     //排序方式pageNumber: 1,      //初始化加载第一页,默认第一页pageSize: 50,      //每页的记录行数(*)pageList: [10, 25, 50, 100],  //可供选择的每页的行数(*)url: constUtils.Server + "file/getFileList",//这个接口需要处理bootstrap table传递的固定参数queryParamsType: 'undefined', //默认值为 'limit' ,在默认情况下 传给服务端的参数为:offset,limit,sortqueryParams: function queryParams(queryParams) {   //设置查询参数return {};},//前端调用服务时,会默认传递上边提到的参数,如果需要添加自定义参数,可以自定义一个函数返回请求参数sidePagination: "server",   //分页方式:client客户端分页,server服务端分页(*)search: true,      //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,所以,个人感觉意义不大strictSearch: false,showColumns: true,     //是否显示所有的列showRefresh: true,     //是否显示刷新按钮minimumCountColumns: 2,    //最少允许的列数clickToSelect: true,    //是否启用点击选中行searchOnEnterKey: true,columns: [{title: '序号',align: 'center',formatter: function (value, row, index) {return index + 1;}},{field: 'fileName',title: '文件名',searchable: true,align: 'center'},{field: 'serverSavePath',title: '服务端存储路径',searchable: true,align: 'center'},{field: 'url',title: '链接',searchable: true,align: 'center',formatter: function (value, row, index) {let fielType = row.fileType.toString().toLocaleLowerCase();if(fielType == ".jpg" || fielType == ".png" || fielType == ".jpeg")return '<img src="' + row.url + '" style="max-height: 80px">';else  return row.url;}},{field: 'userId',title: 'memberId',searchable: true,align: 'center'},{title: '操作',align: 'center',searchable: false,formatter: function (value, row, index) {return '<a class="btn" style="margin-left: 10px;" ' +' onclick="deleteRecord(\'' + row.id + '\')">删除</a>';}}],onLoadSuccess: function (data) {  //加载成功时执行console.log(data)},onLoadError: function (err) {console.log(err);},showToggle: false,    //是否显示详细视图和列表视图的切换按钮cardView: false,     //是否显示详细视图detailView: false,     //是否显示父子表});}function bindSelectFileChange() {$('input[id=btnSelectFile]').change(function () {let file = $('#btnSelectFile')[0].files[0];if (file) {let formData = new FormData();formData.append("file", file);formData.append("memberId", "1");$.ajax({type: 'post',url: constUtils.Server + "file/uploadFile",data: formData,cache: false,dataType: "json",processData: false,contentType: false,success: function (res) {console.log(res);$('#tabMain').bootstrapTable('refresh');},error: function (err) {console.log(err);}});}});}function deleteRecord(id) {$.ajax({method: "GET",url: constUtils.Server + "file/deleteFile",data: {id: id},cache: false,dataType: "json",contentType: "application/json",async: false, //同步success: function (res) {console.log(res);$('#tabMain').bootstrapTable('refresh');},error: function (err) {console.log(err);}});}</script></body>
</html>

三、Docker中的部署实现

1、打包Springboot应用

IDEA 修改Springboot pom.xml文件,将Springboot应用打包为 dapi-1.0.3.jar 文件。我的打包方式可参照前置文章。

2、修改Dockerfile文件

将jar包拷贝至 VMWare Centos 中,并修改Dockerfile文件:

FROM openjdk:24
# 后端工作目录
VOLUME /app
# 后端jar包名称
COPY dapi-1.0.3.jar /app/dapi.jar
# 后端项目的端口号
EXPOSE 8093
#启动时指令
ENTRYPOINT ["java", "-jar", "/app/dapi.jar"]

 3、卸载之前的容器

VMWare Centos Terminal终端中卸载之前的Springboot应用,(前置文章中安装的容器)

sudo docker stop dapi
sudo docker rm dapi
sudo docker rmi dapi:0.0.2

4、新建存储路径

VMWare Centos 中创建文件夹用于存储上传的文件。

新建文件夹【uploads】,创建后完整路径为【/home/duel/workspace/nginx/html/dweb/uploads】

其中【uploads】文件夹所在路径,已经被我安装的Nginx通过指令挂载:

# Nginx容器安装时包含的映射指令-v /home/duel/workspace/nginx/html:/usr/share/nginx/html

即系统路径【/home/duel/workspace/nginx/html】  挂载到了Nginx的 【/usr/share/nginx/html】

通过IP加端口号访问Docker Nginx时:

【http://Centos_IP:38080/】指向目录 【/usr/share/nginx/html/dweb】 ,即指向【/home/duel/workspace/nginx/html/dweb】

这样,新上传的文件存储在【uploads】文件夹里,就可以通过 【http://Centos_IP:38080/uploads/* 】的方式进行访问。

我的Nginx安装指令及配置如下,详细可参考前置文章中的详细搭建流程。

# 我安装Nginx的指令sudo docker run --name nginx -p 80:80 -p 443:443 -p 38080:38080 -p 38081:38081
-v /home/duel/workspace/nginx/html:/usr/share/nginx/html
-v /home/duel/workspace/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
-v /home/duel/workspace/nginx/conf.d:/etc/nginx/conf.d
-v /home/duel/workspace/nginx/logs:/var/log/nginx
-v /home/duel/workspace/nginx/ssl:/etc/nginx/ssl
-d --restart=always nginx:latest
# 访问静态文件
server {listen       38080;server_name  localhost; location / {root   /usr/share/nginx/html/dweb;index  index.html index.htm;}    location ~* \.(html|css|js|png|jpg|gif|ico|mp4|mkv|rmvb|flv|eot|svg|ttf|woff|woff2|pptx|rar|zip)$ {root /usr/share/nginx/html/dweb;autoindex on;}    error_page   500 502 503 504  /50x.html;location = /50x.html {root   /usr/share/nginx/html;}
}

 5、安装新版Springboot应用、挂载存储路径

# 通过修改过的Dockerfile 加载新的Jar包$ sudo docker build -t dapi:1.0.3 .#启动容器、映射端口、 映射文件存储路径$ sudo docker run --name dapi -p 8093:8093 -v /home/duel/workspace/nginx/html/dweb/uploads:/uploads -d --restart=always dapi:1.0.3

Docker中安装Springboot容器时,将系统路径【/home/duel/workspace/nginx/html/dweb/uploads】挂载到了容器里的 【/uploads】目录。

Springboot中 文件存储到【/uploads】路径,即保存到了 【/home/duel/workspace/nginx/html/dweb/uploads】。

  如下图所示,文件上传接口实现时,存储路径直接用挂载映射后的【/uploads】,返回相应Http链接即可。

四、测试

1、静态文件发布

将我的前后端分离的Html部分,拷贝到Centos中的【/home/duel/workspace/nginx/html/dweb】路径下,该路径为Nginx指定的系统路径,可通过 ip:38080访问。 Nginx容器安装以及文件夹的挂载可参考我的前置文章,里面有我的Nginx容器安装配置实践的完整描述。

2、宿主机测试

回到Windows系统,打开浏览器,访问发布到VMWare Centos中的静态html,测试相应接口。

通过ip和端口号,访问测试成功。

选择一张照片上传后,回显成功。 其中的 【/uploads】 路径,对应着容器挂载的系统路径【/home/duel/workspace/nginx/html/dweb/uploads】。

返回的图片地址链接也可正常访问到。

回到VMWare Centos,挂载的系统路径下,也可以看到上传的文件。

 测试一下删除

实现删除接口时, 按 “挂载后的文件路径” 删除文件即可。即删除【/uploads/1/1751456548949.jpg】

  FileRecord fileRec = this.fileMapper.selectByPrimaryKey(id);String sFile = fileRec.getServerSavePath();// sfile的值为 /uploads/1/1751456548949.jpgFile file = new File(sFile);if (file.exists())file.delete();

 删除后,系统文件夹里面的照片已删除,测试成功。

五、小结 

Docker +Springboot应用实现文件上传功能时,可创建【存储文件夹】挂载至Springboot应用容器中,通过【挂载后的路径】对文件进行添加、删除。 同时可创建Nginx容器,通过Nginx实现对【存储文件夹】的http访问服务。

六、源码参考

我的实践源码:https://gitee.com/duelcode/duel-api

http://www.dtcms.com/a/264528.html

相关文章:

  • php上传或者压缩图片后图片出现倒转或者反转的问题
  • Hyper-YOLO: When Visual Object Detection Meets Hypergraph Computation
  • 在Ubuntu上多网卡配置HTTP-HTTPS代理服务器
  • c语言中的函数II
  • 今日学习:音视频领域入门文章参考(待完善)
  • 数据结构:数组(Array)
  • 文心快码答用户问|Comate AI IDE专场
  • 文心4.5开源模型部署实践
  • 使用Vue3实现输入emoji 表情包
  • 阿里云AppFlow AI助手打造智能搜索摘要新体验
  • 基于开源链动2+1模式AI智能名片S2B2C商城小程序的场景零售创新研究
  • 【Unity】MiniGame编辑器小游戏(八)三国华容道【HuarongRoad】
  • Active-Prompt:让AI更智能地学习推理的革命性技术
  • BlenderBot对话机器人大模型Facebook开发
  • Spring Framework 中 Java 配置
  • 51单片机外部引脚案例分析
  • 环境土壤物理Hydrus1D2D模型实践技术应用及典型案例分析
  • Docker Desktop导致存储空间不足时的解决方案
  • 【QT】ROS2 Humble联合使用QT教程
  • 【Unity】MiniGame编辑器小游戏(九)打砖块【Breakout】
  • 纹理贴图算法研究论文综述
  • 二、jenkins之idea提交项目到gitlab、jenkins获取项目
  • 将大仓库拆分为多个小仓库
  • 前端请求浏览器提示net::ERR_UNSAFE_PORT的解决方案
  • WPF路由事件:冒泡、隧道与直接全解析
  • 【Harmony】鸿蒙企业应用详解
  • 小型水电站综合自动化系统的介绍
  • 计算机组成笔记:缓存替换算法
  • QT6 源(147)模型视图架构里的表格窗体 QTableWidget 的范例代码举例,以及其条目 QTableWidgetItem 类型的源代码。
  • Re:从零开始的 磁盘调度进程调度算法(考研向)