Docker下Java文件上传服务三部曲之二:服务端开发

本章是《Docker下Java文件上传服务三部曲》的第二篇,上一章《Docker下Java文件上传服务三部曲之一:准备环境》我们把客户端准备好了,Tomcat容器也部署好了,今天就来开发和部署文件服务的后台应用吧;

原文地址:http://blog.csdn.net/boling_cavalry/article/details/79367520
三部曲所有文章链接

    《Docker下Java文件上传服务三部曲之一:准备环境》
    《Docker下Java文件上传服务三部曲之二:服务端开发》
    《Docker下Java文件上传服务三部曲之三:wireshark抓包分析》

本章实战内容概要

本章要创建三个web应用,都是文件上传的服务端,分别部署在ubuntu电脑上的三个Docker容器中,接收来自客户端的上传文件的请求,结构如下:
这里写图片描述

三个web应用功能相同,都是文件上传的服务端,它们的差别用下表来说明:
应用名     框架     文件服务技术方案     Docker上的部署方式
springmvcfileserver     spring mvc     springframework的CommonsMultipartResolver     构建war,部署到Tomcat容器上
fileserverdemo     spring mvc     apache的commons-fileupload库     构建war,部署到Tomcat容器上
springbootfileserver     springboot     springframework的CommonsMultipartResolver     构建jar,做成Docker镜像
如何将war包在线部署到Tomcat容器上

springmvcfileserver和fileserverdemo在pom.xml中都用到了tomcat7-maven-plugin插件,可以将war包在线部署到Tomcat容器上,记得修改本地的maven配置文件,将登录tomcat的用户名和密码加进去,配置的详情请参照《实战docker,编写Dockerfile定制tomcat镜像,实现web应用在线部署》;
如何将springboot工程构建成docker镜像

springboot工程是可以直接构建成docker镜像的,在docker下以此镜像创建容器,该springboot工程就直接运行起来了,如果您想了解具体构建的细节,请访问以下几篇文章:

    《maven构建docker镜像三部曲之一:准备环境》;
    《maven构建docker镜像三部曲之二:编码和构建镜像》;
    《maven构建docker镜像三部曲之三:推送到远程仓库(内网和阿里云)》;

三个web应用的源码下载

您可以在GitHub下载本章三个web应用的源码,地址和链接信息如下表所示:
名称     链接     备注
项目主页     https://github.com/zq2599/blog_demos     该项目在GitHub上的主页
git仓库地址(https)     https://github.com/zq2599/blog_demos.git     该项目源码的仓库地址,https协议
git仓库地址(ssh)     git@github.com:zq2599/blog_demos.git     该项目源码的仓库地址,ssh协议
这个git项目中有多个目录,本次所需的资源放在springmvcfileserver、fileserverdemo、springbootfileserver这三个目录下,如下图红框所示:

这里写图片描述
SpirngMVC框架如何处理上传的文件?

SpirngMVC对POST请求中的二进制文件的处理,是依赖apache的commons-fileupload库来完成的,如果您想了解更多细节,请参考文章《SpringMVC源码分析:POST请求中的文件处理》;
创建应用springmvcfileserver

    创建一个maven工程springmvcfileserver,pom.xml内容如下:

  1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3   <modelVersion>4.0.0</modelVersion>
  4   <groupId>com.bolingcavalry</groupId>
  5   <artifactId>springmvcfileserver</artifactId>
  6   <packaging>war</packaging>
  7   <version>1.0-SNAPSHOT</version>
  8   <name>springmvcfileserver Maven Webapp</name>
  9   <url>http://maven.apache.org</url>
 10   <properties>
 11     <!-- spring版本号 -->
 12     <spring.version>4.0.2.RELEASE</spring.version>
 13   </properties>
 14   <dependencies>
 15     <!-- spring核心包 -->
 16     <dependency>
 17       <groupId>org.springframework</groupId>
 18       <artifactId>spring-core</artifactId>
 19       <version>${spring.version}</version>
 20     </dependency>
 21     <dependency>
 22       <groupId>org.springframework</groupId>
 23       <artifactId>spring-web</artifactId>
 24       <version>${spring.version}</version>
 25     </dependency>
 26     <dependency>
 27       <groupId>org.springframework</groupId>
 28       <artifactId>spring-webmvc</artifactId>
 29       <version>${spring.version}</version>
 30     </dependency>
 31     <dependency>
 32       <groupId>org.springframework</groupId>
 33       <artifactId>spring-aop</artifactId>
 34       <version>${spring.version}</version>
 35     </dependency>
 36     <dependency>
 37       <groupId>org.springframework</groupId>
 38       <artifactId>spring-context-support</artifactId>
 39       <version>${spring.version}</version>
 40     </dependency>
 41     <!-- 导入java ee jar 包 -->
 42     <dependency>
 43       <groupId>javax</groupId>
 44       <artifactId>javaee-api</artifactId>
 45       <version>7.0</version>
 46     </dependency>
 47 
 48     <!-- 映入JSON -->
 49     <dependency>
 50       <groupId>org.codehaus.jackson</groupId>
 51       <artifactId>jackson-mapper-asl</artifactId>
 52       <version>1.9.13</version>
 53     </dependency>
 54     <!-- 上传组件包 -->
 55     <dependency>
 56       <groupId>commons-fileupload</groupId>
 57       <artifactId>commons-fileupload</artifactId>
 58       <version>1.3.1</version>
 59     </dependency>
 60 
 61     <dependency>
 62       <groupId>org.slf4j</groupId>
 63       <artifactId>slf4j-api</artifactId>
 64       <version>1.7.6</version>
 65     </dependency>
 66 
 67     <dependency>
 68       <groupId>ch.qos.logback</groupId>
 69       <artifactId>logback-core</artifactId>
 70       <version>1.0.9</version>
 71     </dependency>
 72 
 73     <dependency>
 74       <groupId>ch.qos.logback</groupId>
 75       <artifactId>logback-classic</artifactId>
 76       <version>1.0.9</version>
 77     </dependency>
 78 
 79   </dependencies>
 80 
 81   <build>
 82     <finalName>${project.artifactId}</finalName>
 83 
 84     <resources>
 85       <resource>
 86         <directory>src/main/java</directory>
 87         <includes>
 88           <include>**/*.properties</include>
 89           <include>**/*.xml</include>
 90         </includes>
 91         <!-- 是否替换资源中的属性-->
 92         <filtering>false</filtering>
 93       </resource>
 94       <resource>
 95         <directory>src/main/resources</directory>
 96       </resource>
 97     </resources>
 98     <plugins>
 99       <plugin>
100         <groupId>org.apache.tomcat.maven</groupId>
101         <artifactId>tomcat7-maven-plugin</artifactId>
102         <version>2.2</version>
103         <configuration>
104           <url>http://192.168.119.155:8088/manager/text</url>
105           <server>tomcat7</server>
106           <path>/${project.artifactId}</path>
107           <update>true</update>
108         </configuration>
109       </plugin>
110       <plugin>
111         <groupId>org.apache.maven.plugins</groupId>
112         <artifactId>maven-compiler-plugin</artifactId>
113         <configuration>
114           <source>1.6</source>
115           <target>1.6</target>
116         </configuration>
117       </plugin>
118     </plugins>
119   </build>
120 </project>



注意上述配置中的http://192.168.119.155:8088/manager/text这个地址,“192.168.119.155”是我的ubuntu电脑的IP地址,8088是Tomcat容器启动用映射到ubuntu电脑的端口号,请您按照自己电脑的实际情况修改;
2. 在web.xml中添加springmvc相关的配置:

 1 <servlet>
 2     <servlet-name>SpringMVC</servlet-name>
 3     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 4     <init-param>
 5         <param-name>contextConfigLocation</param-name>
 6         <param-value>classpath:spring-mvc.xml</param-value>
 7     </init-param>
 8     <load-on-startup>1</load-on-startup>
 9     <async-supported>true</async-supported>
10 </servlet>
11     <servlet-mapping>
12         <servlet-name>SpringMVC</servlet-name>
13         <!-- 此处可以可以配置成*.do,对应struts的后缀习惯 -->
14         <url-pattern>/</url-pattern>
15     </servlet-mapping>



    spring-mvc.xml中要配置multipartResolver:

1 <bean id="multipartResolver"
2         class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
3     <property name="defaultEncoding" value="utf-8" />
4     <property name="maxUploadSize" value="10485760000" />
5     <property name="maxInMemorySize" value="40960" />
6 </bean>



    在spring-extends.xml中配置自动扫描的规则:

<context:component-scan base-package="com.bolingcavalry" />

   

    上传服务的controller是UploadController.java,响应请求的upload方法如下:

 1 @RequestMapping(value="/upload",method= RequestMethod.POST)
 2     public void upload(HttpServletRequest request,
 3                        HttpServletResponse response,
 4                          @RequestParam("comment") String comment,
 5                          @RequestParam("file") MultipartFile file) throws Exception {
 6 
 7         logger.info("start upload, comment [{}]", comment);
 8 
 9         if(null==file || file.isEmpty()){
10             logger.error("file item is empty!");
11             responseAndClose(response, "文件数据为空");
12             return;
13         }
14 
15         //上传文件路径
16         String savePath = request.getServletContext().getRealPath("/WEB-INF/upload");
17 
18         //上传文件名
19         String fileName = file.getOriginalFilename();
20 
21         logger.info("base save path [{}], original file name [{}]", savePath, fileName);
22 
23         //得到文件保存的名称
24         fileName = mkFileName(fileName);
25 
26         //得到文件保存的路径
27         String savePathStr = mkFilePath(savePath, fileName);
28 
29         logger.info("real save path [{}], real file name [{}]", savePathStr, fileName);
30 
31         File filepath = new File(savePathStr, fileName);
32 
33         //确保路径存在
34         if(!filepath.getParentFile().exists()){
35             logger.info("real save path is not exists, create now");
36             filepath.getParentFile().mkdirs();
37         }
38 
39         String fullSavePath = savePathStr + File.separator + fileName;
40 
41         //存本地
42         file.transferTo(new File(fullSavePath));
43 
44         logger.info("save file success [{}]", fullSavePath);
45 
46         responseAndClose(response, "Spring MVC环境下,上传文件成功");
47     }

 



前面的分析中,我们已知道入参的MultipartFile对象是apache的commons-fileupload库解析出来的,这里直接用就好了;
7. 在pom.xml文件的目录执行以下命令,即可编译构建war包,并部署到Tomcat容器上去:

mvn clean package -U -Dmaven.test.skip=true tomcat7:deploy

  
    应用部署完毕后,可以通过上一章开发的UploadFileClient类测试服务是否正常;

接下来我们创建第二个应用fileserverdemo;
创建应用fileserverdemo

应用fileserverdemo被设计成不使用spring mvc的文件处理功能,而是自己写代码调用apache的commons-fileupload库来处理上传的文件,关键代码如下:

    在spring-mvc.xml中,不要配置multipartResolver,如果不配置multipartResolver的话,spring mvc是如何处理的呢?一起来看下DispatcherServlet.checkMultipart方法:

 1 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
 2             if (request instanceof MultipartHttpServletRequest) {
 3                 logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
 4                         "this typically results from an additional MultipartFilter in web.xml");
 5             }
 6             else {
 7                 return this.multipartResolver.resolveMultipart(request);
 8             }
 9         }
10         // If not returned before: return original request.
11         return request;



如果没有multipartResolver,那么该方法返回的就是传入的request,该request最终会传递给业务Controller的响应方法中去;
2. 业务controller的响应方法如下:

  1 @RequestMapping("/upload")
  2     public void upload(HttpServletRequest request, HttpServletResponse response) throws Exception{
  3         logger.info("start upload");
  4         //得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
  5         String savePath = request.getServletContext().getRealPath("/WEB-INF/upload");
  6         //上传时生成的临时文件保存目录
  7         String tempPath = request.getServletContext().getRealPath("/WEB-INF/temp");
  8 
  9         logger.info("savePath [{}], tempPath [{}]", savePath, tempPath);
 10 
 11         File file = new File(tempPath);
 12         if(!file.exists()&&!file.isDirectory()){
 13             logger.info("临时文件目录不存在logger.info,现在创建。");
 14             file.mkdir();
 15         }
 16 
 17         //消息提示
 18         String message = "";
 19         try {
 20             //使用Apache文件上传组件处理文件上传步骤:
 21             //1、创建一个DiskFileItemFactory工厂
 22             DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
 23             //设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,就会生成一个临时文件存放到指定的临时目录当中。
 24             diskFileItemFactory.setSizeThreshold(1024*100);
 25             //设置上传时生成的临时文件的保存目录
 26             diskFileItemFactory.setRepository(file);
 27             //2、创建一个文件上传解析器
 28             ServletFileUpload fileUpload = new ServletFileUpload(diskFileItemFactory);
 29             //解决上传文件名的中文乱码
 30             fileUpload.setHeaderEncoding("UTF-8");
 31             //监听文件上传进度
 32             fileUpload.setProgressListener(new ProgressListener(){
 33                 public void update(long pBytesRead, long pContentLength, int arg2) {
 34                     logger.debug("total [{}], now [{}]", pContentLength, pBytesRead);
 35                 }
 36             });
 37 
 38             //3、判断提交上来的数据是否是上传表单的数据
 39             if(!fileUpload.isMultipartContent(request)){
 40                 logger.error("this is not a file post");
 41                 responseAndClose(response, "无效的请求参数,请提交文件");
 42                 //按照传统方式获取数据
 43                 return;
 44             }
 45 
 46             //设置上传单个文件的大小的最大值,目前是设置为1024*1024*1024字节,也就是1G
 47             fileUpload.setFileSizeMax(1024*1024*1024);
 48             //设置上传文件总量的最大值,最大值=同时上传的多个文件的大小的最大值的和,目前设置为10GB
 49             fileUpload.setSizeMax(10*1024*1024*1024);
 50             //4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
 51             List<FileItem> list = fileUpload.parseRequest(request);
 52 
 53             logger.info("after parse request, file item size [{}]", list.size());
 54 
 55             for (FileItem item : list) {
 56                 //如果fileitem中封装的是普通输入项的数据
 57                 if(item.isFormField()){
 58                     String name = item.getFieldName();
 59                     //解决普通输入项的数据的中文乱码问题
 60                     String value = item.getString("UTF-8");
 61                     String value1 = new String(name.getBytes("iso8859-1"),"UTF-8");
 62                     logger.info("form field, name [{}], value [{}], name after convert [{}]", name, value, value1);
 63                 }else{
 64                     //如果fileitem中封装的是上传文件,得到上传的文件名称,
 65                     String fileName = item.getName();
 66                     logger.info("not a form field, file name [{}]", fileName);
 67                     if(fileName==null||fileName.trim().equals("")){
 68                         logger.error("invalid file name");
 69                         continue;
 70                     }
 71                     //注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如:  c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
 72                     //处理获取到的上传文件的文件名的路径部分,只保留文件名部分
 73                     fileName = fileName.substring(fileName.lastIndexOf(File.separator)+1);
 74 
 75                     //得到上传文件的扩展名
 76                     String fileExtName = fileName.substring(fileName.lastIndexOf(".")+1);
 77 
 78                     logger.info("ext name [{}], file name after cut [{}]", fileExtName, fileName);
 79 
 80                     if("zip".equals(fileExtName)||"rar".equals(fileExtName)||"tar".equals(fileExtName)||"jar".equals(fileExtName)){
 81                         logger.error("this type can not upload [{}]", fileExtName);
 82                         responseAndClose(response, "上传文件的类型不符合");
 83                         return;
 84                     }
 85 
 86                     //获取item中的上传文件的输入流
 87                     InputStream is = item.getInputStream();
 88                     //得到文件保存的名称
 89                     fileName = mkFileName(fileName);
 90                     //得到文件保存的路径
 91                     String savePathStr = mkFilePath(savePath, fileName);
 92                     System.out.println("保存路径为:"+savePathStr);
 93                     //创建一个文件输出流
 94                     FileOutputStream fos = new FileOutputStream(savePathStr+File.separator+fileName);
 95                     //创建一个缓冲区
 96                     byte buffer[] = new byte[1024];
 97                     //判断输入流中的数据是否已经读完的标识
 98                     int length = 0;
 99                     //循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
100                     while((length = is.read(buffer))>0){
101                         //使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
102                         fos.write(buffer, 0, length);
103                     }
104                     //关闭输入流
105                     is.close();
106                     //关闭输出流
107                     fos.close();
108                     //删除处理文件上传时生成的临时文件
109                     item.delete();
110                     message = "文件上传成功";
111                 }
112             }
113         } catch (FileUploadBase.FileSizeLimitExceededException e) {
114             logger.error("1. upload fail, ", e);
115             responseAndClose(response, "单个文件超出最大值!!!");
116             return;
117         }catch (FileUploadBase.SizeLimitExceededException e) {
118             logger.error("2. upload fail, ", e);
119             responseAndClose(response, "上传文件的总的大小超出限制的最大值!!!");
120             return;
121         }catch (FileUploadException e) {
122             // TODO Auto-generated catch block
123             logger.error("3. upload fail, ", e);
124             message = "文件上传失败:" + e.toString();
125         }
126 
127         responseAndClose(response, message);
128         logger.info("finish upload");
129     }

 



上述代码有以下两点需要注意:
a. 由于没有multipartResolver 这个bean的配置,因此upload方法入参中没有FileItem对象,request没有做过文件相关的解析和处理;
b. upload方法中创建了ServletFileUpload对象,并调用parseRequest方法解析出所有FileItem对象,然后按照业务需求做各种处理;
3. 其他的配置和springmvcfileserver工程一样,可以参考GitHub上的源码;
4. 在pom.xml文件的目录执行以下命令,即可编译构建war包,并部署到Tomcat容器上去:

mvn clean package -U -Dmaven.test.skip=true tomcat7:deploy

    1

    应用部署完毕后,可以通过上一章开发的UploadFileClient类测试服务是否正常,记得修改POST_URL变量为“http://192.168.119.155:8088/fileserverdemo/upload”,把“192.168.119.155”换成你的ubuntu电脑的IP地址;

至此,两个部署在Tomcat容器上的应用都完成了,接下来我们创建springbootfileserver应用,体验springboot工程提供的文件服务;
创建应用springbootfileserver

springbootfileserver应用对文件的处理,本质上还是spring mvc的处理流程,和springmvcfileserver工程的差异在于:不需要我们配置multipartResolver这个bean,springboot启动的时候已经在应用中默认创建了multipartResolver对象,以下是该工程的几处关键代码:

    pom.xml中新增一个插件,用来将工程构建成docker镜像:

 1 <!--新增的docker maven插件-->
 2             <plugin>
 3                 <groupId>com.spotify</groupId>
 4                 <artifactId>docker-maven-plugin</artifactId>
 5                 <version>0.4.12</version>
 6                 <!--docker镜像相关的配置信息-->
 7                 <configuration>
 8                     <!--镜像名,这里用工程名-->
 9                     <imageName>bolingcavalry/${project.artifactId}</imageName>
10                     <!--TAG,这里用工程版本号-->
11                     <imageTags>
12                         <imageTag>${project.version}</imageTag>
13                     </imageTags>
14                     <!--镜像的FROM,使用java官方镜像-->
15                     <baseImage>java:8u111-jdk</baseImage>
16                     <!--该镜像的容器启动后,直接运行spring boot工程-->
17                     <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint>
18                     <!--构建镜像的配置信息-->
19                     <resources>
20                         <resource>
21                             <targetPath>/</targetPath>
22                             <directory>${project.build.directory}</directory>
23                             <include>${project.build.finalName}.jar</include>
24                         </resource>
25                     </resources>
26                 </configuration>
27             </plugin>

 




    业务controller的响应方法如下:

 1 @RequestMapping(value="/upload",method= RequestMethod.POST)
 2     public void upload(HttpServletRequest request,
 3                        HttpServletResponse response,
 4                        @RequestParam("comment") String comment,
 5                        @RequestParam("file") MultipartFile file) throws Exception {
 6 
 7         logger.info("start upload, comment [{}]", comment);
 8 
 9         if(null==file || file.isEmpty()){
10             logger.error("file item is empty!");
11             responseAndClose(response, "文件数据为空");
12             return;
13         }
14 
15         //上传文件路径
16         String savePath = request.getServletContext().getRealPath("/WEB-INF/upload");
17 
18         //上传文件名
19         String fileName = file.getOriginalFilename();
20 
21         logger.info("base save path [{}], original file name [{}]", savePath, fileName);
22 
23         //得到文件保存的名称
24         fileName = mkFileName(fileName);
25 
26         //得到文件保存的路径
27         String savePathStr = mkFilePath(savePath, fileName);
28 
29         logger.info("real save path [{}], real file name [{}]", savePathStr, fileName);
30 
31         File filepath = new File(savePathStr, fileName);
32 
33         //确保路径存在
34         if(!filepath.getParentFile().exists()){
35             logger.info("real save path is not exists, create now");
36             filepath.getParentFile().mkdirs();
37         }
38 
39         String fullSavePath = savePathStr + File.separator + fileName;
40 
41         //存本地
42         file.transferTo(new File(fullSavePath));
43 
44         logger.info("save file success [{}]", fullSavePath);
45 
46         responseAndClose(response, "SpringBoot环境下,上传文件成功");
47     }

 



可见和springmvcfileserver工程的业务controller基本一致;
3. 在pom.xml所在目录执行以下命令,即可将当前工程编译构建,并制作成docker镜像:

mvn clean package -DskipTests docker:build

   

注意:由于要制作docker镜像,所以要求当前电脑同时安装了maven和docker,推荐在ubuntu电脑上执行,因为做出来的镜像稍后就会用到了;
4. 在ubuntu电脑上运行以下命令,就能将springbootfileserver工程在容器中运行起来:

docker run --name fileserver001 -p 8080:8080 -v /usr/local/work/fileupload/upload:/usr/Downloads -idt  bolingcavalry/springbootfileserver:0.0.1-SNAPSHOT

   

注意:此命令在上一章执行过,不同的是用的镜像是刚刚构建的;
5. 用UploadFileClient类测试服务是否正常;

至此三个web应用就开发完成了,这三个应用在处理文件服务的过程中,有的简单易用,有的能把控更多细节用来满足相对复杂的业务需求,希望能对您在实现业务需求时提供一些参考;

基本的开发和部署工作已经全部完成,下一章我们通过wireshark抓包,来看看HTTP的POST请求和响应的细节,看下一章请点击《Docker下Java文件上传服务三部曲之三:wireshar抓包分析》;
欢迎关注我的公众号:程序员欣宸

posted on 2020-07-04 17:22  专注30  阅读(577)  评论(0)    收藏  举报

导航