文件上传之跨服务器上传文件
在实际项目中上传文件保存到到文件服务器是一个常用的操作,而在服务器上保存文件就需要特别小心。因为通常情况下不只是在一个路径里保存文件,所以需要实践一下保存文件到任意位置。当然,前提是你的应用程序有这样的操作权限。
一、分布式服务器上传作用
- 数据库服务器:运行我们的数据库
- 缓存和消息服务器:负责处理大并发访问的缓存和消息
- 文件服务器:负责存储用户上传文件的服务器。
- 应用服务器:负责部署我们的应用
- 在实际开发中,我们会有很多处理不同功能的服务器。(注意:此处说的不是服务器集群)
- 总结:分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。
分布式服务器工作示意图:
二、单独解压一个Tomcat作为文件服务器
除了项目之外,单独在准备一个Tomcat,如果和项目在同一个电脑上则需要对第二个Tomcat做如下设置
- server.xml中需要修改三个端口,不然会出现端口冲突
- 在web.xml中添加如下内容 :远程服务器中设置非只读
<init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param>
注意:我这里用的是虚拟机中的Tomcat,所以只需要设置非只读即可
webapps下创建一个upload目录,用于存放上传的图片
启动测试
三、导入跨服务器上传文件依赖
这里还是在上一节文件上传环境上改动而来,在pom.xml中添加依赖
<!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-client --> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.19.4</version> </dependency>
四、创建数据库
创建数据库存储上传的信息
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; }
六、在WEB-INF/templates下创建userinformation.html,user.html
- userinformation.html:实现数据提交和图片上传
<!DOCTYPE html> <html lang="en"> <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) //设置图片回显 $("#headImg").attr("src","http://192.168.141.134: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>
七、在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.141.134: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 "redirect:/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); } }
九、在mapper层处理如下
- 创建接口PlayerMapper
package com.augus.mapper; import com.augus.pojo.Player; public interface PlayerMapper { int addUser(Player player); }
- 创建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>
十、重新部署Tomcat,访问http://localhost:8080/upfile_war_exploded/regist