Spring Boot文件上传之跨服务器上传文件
在实际项目中上传文件保存到到文件服务器是一个常用的操作,而在服务器上保存文件就需要特别小心。因为通常情况下不只是在一个路径里保存文件,所以需要实践一下保存文件到任意位置。当然,前提是你的应用程序有这样的操作权限。
一、分布式服务器上传作用
- 数据库服务器:运行我们的数据库
- 缓存和消息服务器:负责处理大并发访问的缓存和消息
- 文件服务器:负责存储用户上传文件的服务器。
- 应用服务器:负责部署我们的应用
- 在实际开发中,我们会有很多处理不同功能的服务器。(注意:此处说的不是服务器集群)
- 总结:分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。
分布式服务器工作示意图:
二、单独准备一个Tomcat作为文件服务器
除了项目之外,单独在准备一个Tomcat,如果和项目在同一个电脑上则需要对第二个Tomcat的端口作出修改,这里我使用虚拟机中Tomcat作为文件服务器使用,
由于我这里用的是虚拟机中的Tomcat,所以只需要设置非只读即可,如下:
- 在web.xml中添加如下内容 :远程服务器中设置非只读
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>

在Tomcat中webapps下创建upload目录,用于存放上传的图片

启动测试Tomcat是否可以正常访问:

三、创建springboot项目
这里我使用spring官方默认地址网络原因连接不上,就使用了阿里云的地址
输入项目名称和包信息,选择jdk版本,选择Maven项目

勾选spring web导入相关的依赖

四、导入依赖
在pom.xml中导入如下依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--添加druid连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!--mybatis依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!--MySQL数据库连接的依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!--跨服务器上传文件依赖,切记导入,不然无法跨服务器上传--> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.19.4</version> </dependency>
五、设置配置文件
在resources目录创建application.yml配置文件,内容如下
spring: datasource: # 使用阿里的Druid连接池 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver # 填写你数据库的url、登录名、密码和数据库名 url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456 druid: # 连接池的配置信息 # 初始化大小,最小,最大 initial-size: 5 min-idle: 5 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 testWhileIdle: true testOnBorrow: false testOnReturn: false # 打开PSCache,并且指定每个连接上PSCache的大小 poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 filters: stat,wall,slf4j # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 # 配置DruidStatFilter web-stat-filter: enabled: true url-pattern: "/*" exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*" # 配置DruidStatViewServlet stat-view-servlet: url-pattern: "/druid/*" # IP白名单(没有配置或者为空,则允许所有访问) allow: 127.0.0.1,192.168.8.109 # IP黑名单 (存在共同时,deny优先于allow) deny: 192.168.1.188 # 禁用HTML页面上的“Reset All”功能 reset-enable: false # 登录名 login-username: admin # 登录密码 login-password: 123456 servlet: multipart: #设置文件上传单个文件的大小 max-file-size: 10MB #设置多个文件上传总文件的大小 file-size-threshold: 100MB #thymeleaf会自动设置前缀和后缀,这里就不需要手动添加了 # thymeleaf: # suffix: # prefix: mybatis: mapper-locations: classpath:mybatis/*.xml type-aliases-package: com.augus.pojo server: servlet: context-path: /springboot09 port: 8080
六、创建数据库
创建数据库存储新增的信息
CREATE TABLE `player` ( `id` int(25) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `nickname` varchar(255) DEFAULT NULL, `photo` varchar(255) DEFAULT NULL, `filetype` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) )

七、在pojo包下创建player表的实体类
package com.augus.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @AllArgsConstructor @NoArgsConstructor @Data public class Player implements Serializable { private Integer id; private String username; private String password; private String nickname; private String photo; private String filetype; }
八、在controller下创建FileUploadController,内容如下
package com.augus.controller; import com.augus.pojo.Player; import com.augus.service.PlayerService; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.WebResource; 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 org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.UUID; @Controller public class FileUploadController { //文件存储位置,定为私有不可修改的静态属性 private final static String FILESERVER = "http://192.168.93.128:8080/upload/"; @Autowired private PlayerService playerService; //打开信息提交页面 @RequestMapping("/regist") public String testRegister(){ return "userinformation"; } @ResponseBody @RequestMapping("/fileUpload.do") public Map<String,String> fileUpload(MultipartFile headPhoto, HttpServletRequest request) throws IOException { //创建Map集合 Map<String, String> map = new HashMap<String, String>(); //控制文件大小,如果文件大小超过5m,就给前端返回提示 //还有一种直接可以在springmvcxml中即可控制,但是不推荐,没有办法控制给前端的提示信息 if(headPhoto.getSize()>1024*1024*5){ map.put("message","文件大小不能超过5M"); return map; } // 获取文件名,这里获取的是文件名,但是如果有文件名重复,则会出现覆盖,这时候在保存原文件的时候,就不同用原名字 String originalFilename = headPhoto.getOriginalFilename(); //那么就用UUID替换文件名 String s = UUID.randomUUID().toString(); //从获取上传进来的文件后缀名 例如 4.jpg 就取.jpg //lastIndexOf(".")截取文件中最后一个点,防止有多个. String extendsName = originalFilename.substring(originalFilename.lastIndexOf(".")); //控制文件类型要是.jpg格式,如果不是则需要给前端返回提示信息 if(!extendsName.equals(".jpg")){ //指定返回值 map.put("message", "图片类型必须是.jpg"); return map; } // 使用 UUID+后缀名,拼接成文件名 String newFileName = s + extendsName; //创建sun公司提供的jersey包中的client对象 Client client = Client.create(); //将存储地址和文件名组合 WebResource resource = client.resource(FILESERVER+newFileName); //文件保存到另一个服务器, 将删除的图片以新名字提交的另一个图片服务器 resource.put(String.class, headPhoto.getBytes()); System.out.println("这是fileUpload"); //设置上传成功时的响应 map.put("message","上传成功"); map.put("fileName",newFileName); //设置图片回显将图片的名字和格式传递过去 map.put("fileType",headPhoto.getContentType()); //返回文件类型 return map; } //添加信息 @RequestMapping("/addUser") public String testAddUser(Player player){ playerService.addUser(player); return "user"; } }
九、在service层处理如下
在service下创建PlayerService
package com.augus.service; import com.augus.pojo.Player; public interface PlayerService { int addUser(Player player); }
在service下impl包下创建PlayerServiceImpl
package com.augus.service.impl; import com.augus.mapper.PlayerMapper; import com.augus.pojo.Player; import com.augus.service.PlayerService; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class PlayerServiceImpl implements PlayerService { //这个本来@Autowired保存,所以改用@Resource @Resource private PlayerMapper playerMapper; @Override public int addUser(Player player) { return playerMapper.addUser(player); } }
十、在com.augus下次创建mapper包做处理如下
创建接口PlayerMapper
package com.augus.mapper; import com.augus.pojo.Player; import org.apache.ibatis.annotations.Mapper; @Mapper public interface PlayerMapper { int addUser(Player player); }
十一、在resources目录下创建mybatis包,存放mapper映射文件
创建PlayerMapper.xml,操作数据库,内容如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.augus.mapper.PlayerMapper"> <insert id="addUser"> insert into player values(DEFAULT ,#{username},#{password},#{nickname},#{photo},#{filetype}) </insert> </mapper>
十二、在resources下的templates中存放html文件
userinformation.html:实现数据提交和图片上传,内容如下:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>userinformation</title> <style> .progress { width: 200px; height: 10px; border: 1px solid #ccc; border-radius: 10px; margin: 10px 0px; overflow: hidden; } /* 初始状态设置进度条宽度为0px */ .progress > div { width: 0px; height: 100%; background-color: yellowgreen; transition: all .3s ease; } </style> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js" type="text/javascript"></script> <script type="text/javascript"> $(function(){ $("#uploadFile").click(function(){ // 获取要上传的文件 var photoFile =$("#photo")[0].files[0] if(photoFile==undefined){ alert("您还未选中文件") return; } // 将文件装入FormData对象 var formData =new FormData(); //注意这里写的headPhoto,在controller中也要用这个名字 formData.append("headPhoto",photoFile) // ajax向后台发送文件 $.ajax({ type:"post", data:formData, url:"fileUpload.do", processData:false, contentType:false, // result能获取到后台返回的信息 success:function(result){ // 接收后台响应的信息,返回message键所对应的值 alert(result.message) //设置图片回显 result.fileName获取的上传后的图片名称,这里要回显还需图片的地址 $("#headImg").attr("src","http://192.168.93.128:8080/upload/"+result.fileName) //将文件类型和文件名放入form表单 //就是设置input标签value属性的值 $("#photoId").val(result.fileName) $("#filetypeId").val(result.fileType) }, //控制进度条进度 xhr: function() { var xhr = new XMLHttpRequest(); //使用XMLHttpRequest.upload监听上传过程,注册progress事件,打印回调函数中的event事件 xhr.upload.addEventListener('progress', function (e) { console.log(e); //loaded代表上传了多少 //total代表总数为多少 var progressRate = (e.loaded / e.total) * 100 + '%'; //通过设置进度条的宽度达到效果 $('.progress > div').css('width', progressRate); }) return xhr; } }) }) }) </script> </head> <body> <form action="addUser" method="post"> <table style="margin: auto;"> <tr> <td>用户名:</td> <td><input type="text" name="username" required><br></td> </tr> <tr> <td>密码:</td> <td><input type="text" name="password" required><br></td> </tr> <tr> <td>昵称:</td> <td><input type="text" name="nickname" required><br></td> </tr> <tr> <td>头像:</td> <td><input id="photo" type="file"><br></td> <td> <div class="progress"> <div></div> </div> </td> <td> <a id="uploadFile" href="javascript:void(0)">立即上传</a><br> </td> <!--使用隐藏的输入框存储文件名称和文件类型--> <td><input id="photoId" type="hidden" name="photo"></td> <td><input id="filetypeId" type="hidden" name="filetype"></td> </tr> <tr> <td colspan="2"><img id="headImg" style="width: 200px;height: 200px" alt="还未上传图片"><br></td> </tr> <tr> <td colspan="2"> <input type="submit" value="提交" style="margin: auto;"> </td> </tr> </table> </form> </body> </html>
user.html:实现注册成功后的跳转页面,内容如下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册后页面</title> </head> <body> <h1>注册成功</h1> </body> </html>
上述步骤完成后,项目结构如下图所示:

十三、重新部署项目
访问 http://localhost:8080/springboot09/regist 如下:

输入内容,选择上图图片:

在图片服务器Tomcat中也可以看到该图片

点击提交按钮,即可在数据库中看到信息




浙公网安备 33010602011771号