通用文件服务组件(HTTP实现版本)

上篇文章介绍了通用文件服务组件(Netty实现版本),本文介绍基于HTTP与Apache FileUpload的方式实现。
本文首发于SegmentFault,转载请注明。

1. 实现原理及功能简介

1.1 服务端实现原理

服务端采用了Spring MVC文件上传的方式,其底层采用了Apache FileUpload组件。

1.1.1 Apache FileUpload

根据Apache FileUpload官网信息可知,它是一个高性能,可为我们的web应用提供文件上传服务的组件。

The Commons FileUpload package makes it easy to add robust, high-performance, file upload capability to your servlets and web applications.

FileUpload parses HTTP requests which conform to RFC 1867, "Form-based File Upload in HTML". That is, if an HTTP request is submitted using the POST method, and with a content type of "multipart/form-data", then FileUpload can parse that request, and make the results available in a manner easily used by the caller.

Starting with version 1.3, FileUpload handles RFC 2047 encoded header values.

Apache Commons FileUpload主要依赖Commons-io组件,详情可参考 Apache FileUpload Dependencies

使用Commons FileUpload需要注意以下几点:

  • Form表单内,需要添加控件<input type="file" name="myfile">
  • Form表单的内容格式需要定义成multipart/form-data
  • 需要类库
    • commons-io
    • commons-fileupload

通过阅读Apache Commons FileUpload官方文档,可以发现以下几个常见的对象,

  • 文件解析对象
DiskFileUpload diskFileUpload = new DiskFileUpload();//磁盘中读取
ServletFileUpload servletFileUpload = new ServletFileUpload();//web servlet中读取
  • 进行文件解析后放在List中,因为这个类库支持多个文件上传,因此把结果会存在List中
List<FileItem> list = diskFileUpload.parseRequest(request);
  • 获取上传文件,进行分析(不是必须)
File remoteFile = new File(new String(fileItem.getName().getBytes(),"UTF-8"));
  • 创建新对象,进行流拷贝
file1 = new File(this.getServletContext().getRealPath("attachment"),remoteFile.getName());
file1.getParentFile().mkdirs();
file1.createNewFile();

InputStream ins = fileItem.getInputStream();
OutputStream ous = new FileOutputStream(file1);

try{
    byte[] buffer = new byte[1024];
    int len = 0;
    while((len = ins.read(buffer)) > -1)
    ous.write(buffer,0,len);
    out.println("以保存文件"+file1.getAbsolutePath()+"<br/>");
}finally{
    ous.close();
    ins.close();
}

关于其他问题可以自行Google或者参考官方文档。

1.1.2 Spring MVC文件上传原理

1.1.2.1 初始化MultipartFile相关对象

我们大家都知道Spring MVC的入口类为 org.springframework.web.servlet.DispatcherServlet,入口方法为doDispatch。通过这个方法可以找到文件处理相关的方法 checkMultipart ,如下所示:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);
        ...
}

该方法定义如下:

/**
     * Convert the request into a multipart request, and make multipart resolver available.
     * <p>If no multipart resolver is set, simply use the existing request.
     * @param request current HTTP request
     * @return the processed request (multipart wrapper if necessary)
     * @see MultipartResolver#resolveMultipart
     */
    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
            ......
            try {
                return this.multipartResolver.resolveMultipart(request);
            }
            catch (MultipartException ex) {
                ......
            }
        }   
        // If not returned before: return original request.
        return request;
    }

上述方法中定义的multipartResolver对象即为 org.springframework.web.multipart.MultipartResolver类型,其中一个实现类为org.springframework.web.multipart.commons.CommonsMultipartResolver,需要在配置类org.fortune.doc.server.conf.web.SpringWebMvcConfiguration 加入Bean定义。如下所示:

@Bean
public CommonsMultipartResolver multipartResolver() {
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    return multipartResolver;
}

我们进入到 CommonsMultipartResolver中可以发现其底层就是利用了Apache FileUpload组件。

Servlet-based {@link MultipartResolver} implementation for Servlet-based {@link MultipartResolver} implementation for 1.2 or above.

通过 this.multipartResolver.resolveMultipart(request)方法我们可以知道,这个方法就是对 MultipartHttpServletRequest类的一系列初始化操作。MultipartHttpServletRequest接口具有两个实现类(DefaultMultipartHttpServletRequestStandardMultipartHttpServletRequest),此处使用 DefaultMultipartHttpServletRequest,两种加载方法, 一种是即时加载(初始化),一种是懒加载(初始化),通过变量resolveLazily实现自由切换,默认为即时加载(初始化),实现方法如下:

@Override
    public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
        Assert.notNull(request, "Request must not be null");
        if (this.resolveLazily) {
            return new DefaultMultipartHttpServletRequest(request) {
                @Override
                protected void initializeMultipart() {
                    MultipartParsingResult parsingResult = parseRequest(request);
                    setMultipartFiles(parsingResult.getMultipartFiles());
                    setMultipartParameters(parsingResult.getMultipartParameters());
                    setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
                }
            };
        }
        else {
            MultipartParsingResult parsingResult = parseRequest(request);
            return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
                    parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
        }
    }

上述方法我们可以发现方法 MultipartParsingResult parsingResult = parseRequest(request);,这个方法其实就是读取文件上传相关的对象,比如文件列表List<FileItem>,MultipartFileFileUpload 对象等。

 * Parse the given servlet request, resolving its multipart elements.
     * @param request the request to parse
     * @return the parsing result
     * @throws MultipartException if multipart resolution failed.
     */
    protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
        String encoding = determineEncoding(request);
        FileUpload fileUpload = prepareFileUpload(encoding);
        try {
            List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
            return parseFileItems(fileItems, encoding);
        }
        ......
    }

上述代码很清晰的说明了我们在前一小节讲到的Apache FileUpload中的用法。

1.1.2.2 获取MultipartFile相关对象

通过上述介绍,我们已经得到了上传文件需要的一系列依赖对象,这时候我们就可以利用这些对象进行文件上传相关的操作了,比如本组件中文件上传时候需要得到request中的文件对象,就可以通过以下方法很方便的得到文件对象。

if (request instanceof MultipartHttpServletRequest) {
    MultipartHttpServletRequest mreqeust = (MultipartHttpServletRequest)request;
    String fileName = mreqeust.getParameter(Constants.FILE_NAME_KEY);
    MultipartFile file = mreqeust.getFile(Constants.FILE_DATA_KEY);
    .....
}

然后利用方法 org.springframework.web.multipart.MultipartFile#transferTo(java.io.File) 就可以获取到客户端传过来的真正的文件内容了。

1.2 客户端实现原理

为了能够使客户端发送Http请求变得容易,而且也方便开发人员测试接口(基于Http协议的),即提高了开发的效率,也方便提高代码的健壮性,这时候我们自然就想到了Apache的一个基于Http的客户端组件,它在3.x之前称为Apache HttpClient,在4.x之后成为Apache HttpComponents

关于Apache HttpClient组件的官方说明如下:

The Commons HttpClient project is now end of life, and is no longer being developed. It has been replaced by the Apache HttpComponents project in its HttpClient and HttpCore modules, which offer better performance and more flexibility.

关于Apache HttpComponents组件的官方说明如下:

The Apache HttpComponents™ project is responsible for creating and maintaining a toolset of low level Java components focused on HTTP and associated protocols.

1.2.1 HttpComponents 介绍

通过其官网可知,HttpComponents组件具有以下几个主要的modules,

  • HttpComponents Core:核心包
  • HttpComponents Client:客户端处理包
  • HttpComponents AsyncClient:具有异步功能的客户端处理包

1.2.2 HttpCore

HttpCore组件就是整个Http客户端组件的核心包,它包括两种I/O模型,阻塞I/O模型(Java IO)和非阻塞,事件驱动的I/O模型(Java NIO)。

HttpCore is a set of low level HTTP transport components that can be used to build custom client and server side HTTP services with a minimal footprint. HttpCore supports two I/O models: blocking I/O model based on the classic Java I/O and non-blocking, event driven I/O model based on Java NIO.

1.2.3 HttpClient

HttpClient组件是基于Http协议的一个组件,它依赖于HttpCore组件,它提供了可重用的在客户端权限验证,HTTP状态管理,HTTP连接管理等功能。

HttpClient is a HTTP/1.1 compliant HTTP agent implementation based on HttpCore. It also provides reusable components for client-side authentication, HTTP state management, and HTTP connection management. HttpComponents Client is a successor of and replacement for Commons HttpClient 3.x. Users of Commons HttpClient are strongly encouraged to upgrade.

使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可:

  1. 创建HttpClient对象,HttpClients.createDefault()。

  2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象,HttpPost httpPost = new HttpPost(url)

  3. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。

    List<NameValuePair> valuePairs = new LinkedList<NameValuePair>();
    valuePairs.add(new BasicNameValuePair(entry.getKey(),entry.getValue()));httpPost.setEntity(formEntity)
    
  4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。

  5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。

  6. 释放连接。无论执行方法是否成功,都必须释放连接。

1.3 组件功能介绍

该组件基于Apache HTTPComponents组件实现,具有如下功能:文件上传,文件替换,文件删除,如果是图片的话,还可以生成缩略图等功能。使用简单,只需要引入commons-doc-client-http,即可以实现文件的以上操作。

本组件分为三个module,分别为:

  • commons-doc-server-http:Http实现文件服务组件的服务端
  • commons-doc-common:Http文件服务组件公共组件
  • commons-doc-client-http:Http文件服务组件的客户端

2. 服务端

2.1 功能简介

服务端组件实现功能与Netty实现版本一致,具有以下功能:文件上传,文件替换,文件删除,如果是图片的话,还可以生成缩略图等功能。

2.2 实现步骤

同样的,不管是普通文件,还是图片处理,他们都有一个共同的基类 org.fortune.doc.server.handler.DocServerHandler,然后文件处理基类 org.fortune.doc.server.handler.attachment.AttachmentServerHandler继承自DocServerHandler,同理,图片处理基类 org.fortune.doc.server.handler.image.ImageServerHandler也继承它。以文件处理为例,主要有以下几个实现类:

  • UploadAttachmentServerHandler:文档上传处理类
  • ReplaceAttachmentServerHandler:文档替换处理类
  • DeleteAttachmentServerHandler:文档删除处理类

实现以图片上传为例。

实现方法如下:

public ImageDocResult doUpload(HttpServletRequest request) {
        ImageDocResult result = new ImageDocResult();
        DocAccountBean accountBean = super.getAccount(request);
        if (accountBean == null) {
            result.buildFailed();
            result.buildCustomMsg("账号信息不对,请重新确认");
            return result;
        } else {
            if (request instanceof MultipartHttpServletRequest) {
                MultipartHttpServletRequest mreqeust = (MultipartHttpServletRequest)request;
                String fileName = mreqeust.getParameter(Constants.FILE_NAME_KEY);
                MultipartFile file = mreqeust.getFile(Constants.FILE_DATA_KEY);
                if (!file.isEmpty()) {
                    try {
                        String rootPath = super.getImageRootPath();
                        LOGGER.info("图片上传的根目录:{}",rootPath);
                        this.checkRootPath(rootPath);
                        String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
                        String savePath = super.getImageBasePath() + File.separator + FileUtil.generateFileSavePath(accountBean);
                        LOGGER.info("图片上传需要保存到数据库的目录:{}",savePath);
                        String dirPath = super.getRootPath() + File.separator + savePath;
                        LOGGER.info("图片上传绝对路径:{}",dirPath);
                        String newFileName = FileUtil.generateFileNameOfTime(fileName);
                        LOGGER.info("图片上传动态生成的图片文件名称:{}",newFileName);
                        String newFileName0 = newFileName.substring(0, newFileName.lastIndexOf("."));
                        this.checkStorePath(dirPath);
                        File uploadedFile3 = new File(dirPath, newFileName);
                        file.transferTo(uploadedFile3);
                        String thumbMark = request.getParameter(Constants.THUMB_MARK_KEY);
                        if (Constants.THUMB_MARK_VAL.equals(thumbMark)) {
                            List<ImageDocThumbBean> thumbBeans = accountBean.getThumbConfig();
                            if (thumbBeans != null) {
                                Iterator thumbBean = thumbBeans.iterator();
                                while(thumbBean.hasNext()) {
                                    ImageDocThumbBean thumb = (ImageDocThumbBean)thumbBean.next();
                                    File uploadedFile1 = new File(dirPath, newFileName0 + thumb.getSuffix() + "." + fileExt);
                                    LOGGER.info("图片上传动态生成的缩略图图片文件:{}",uploadedFile1);
                                    ImageUtils.ratioZoom2(uploadedFile3, uploadedFile1, thumb.getRatio());
                                }
                            }
                        }
                        result.setFilePath(savePath + File.separator + newFileName);
                        result.buildSuccess();
                        LOGGER.info("图片上传成功,路径为{}",result.getFilePath());
                    } catch (Exception ex) {
                        result.buildFailed();
                        LOGGER.error("图片上传失败", ex);
                    }
                } else {
                    result.buildFailed();
                    LOGGER.error("图片上传失败,上传的文件未提供");
                }
            }
            return result;
        }
    }

由上述代码可知,其核心逻辑就是判断当前的request是否是 MultipartHttpServletRequest类型,如果是,则说明当前是文件处理逻辑,根据前面章节的说明,我们可以根据此对象获取到相应的 MultipartFile对象进行相应的文件操作,如 file.transferTo(uploadedFile3);这句代码即为文件上传核心代码。

3. 客户端

3.1 功能简介

客户端组件也是跟Netty实现版本一致,主要提供对外访问服务端组件的接口,提供以下接口:文件上传,文件替换,文件删除,如果是图片的话,还可以生成缩略图等功能。

3.2 实现步骤

不管是普通的文件处理,还是图片处理,他们都有一个抽象类 org.fortune.doc.client.handler.AbstractDocClientHandler该类主要是一些参数配置等公共方法,然后其有三个子抽象类,分别如下:

  • AbstractUploadDocClientHandler: 上传功能抽象句柄
  • AbstractReplaceDocClientHandler:替换功能抽象句柄
  • AbstractDeleteDocClientHandler:删除功能抽象句柄

然后文件处理和图片处理分别有对应的实现类,大家可以参考Github实现,在此说明一下,不管是哪个实现类,最后都会调用一个包装类 org.fortune.doc.client.support.DocClientWrapper,该类封装了Apache HttpClient的基本配置和基本操作。核心方法doPost实现如下:

public DocResponseContent doPost(String uri, String urlEncoding) throws HttpException, IOException {
        HttpEntity entity = null;
        HttpRequestBase request = null;
        CloseableHttpResponse response = null;

        try {
            String url = this.buildUrl(uri);
            HttpPost httpPost = new HttpPost(url);
            MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
            entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
            Iterator<NameValuePair> nameValuePairIterator = this.getNVBodies().iterator();

            while(nameValuePairIterator.hasNext()) {
                NameValuePair nameValuePair = nameValuePairIterator.next();
                entityBuilder.addPart(nameValuePair.getName(), new StringBody(nameValuePair.getValue(), ContentType.create("text/plain", urlEncoding)));
            }

            Iterator it = this.getContentBodies().entrySet().iterator();

            while(it.hasNext()) {
                Map.Entry<String, ContentBody> item = (Map.Entry)it.next();
                entityBuilder.addPart(item.getKey(), item.getValue());
            }

            entityBuilder.setCharset(CharsetUtils.get(urlEncoding));
            httpPost.setEntity(entityBuilder.build());
            request = httpPost;
            response = this.client.execute(httpPost);
            StatusLine statusLine = response.getStatusLine();
            entity = response.getEntity();
            DocResponseContent ret = new DocResponseContent();
            ret.setStatusCode(statusLine.getStatusCode());
            this.getResponseContent(entity, ret);
            DocResponseContent var12 = ret;
            return var12;
        } finally {
            this.close(entity, request, response);
        }
    }

以上方法其实就是我们前面讲解的客户端实现原理章节中的内容了。

4. 操作指引

该文件服务组件的使用需要分为两个部分,一个是服务端配置与启动,一个是客户端的配置与启动。

配置基本上与Netty实现版本是一致的,只是细微的差别。

4.1 服务端配置与启动

4.1.1 配置

服务端的配置采用yml文件的配置,更加的简洁明了,主要的注意点是文件存放位置的配置,在开发过程中,可以有两种方式配置:

  • Idea自启动方式:如果采用此种方式则需要把rootPath配置到工程路径下(target目录),如下所示:

    # 在idea中执行的话,需要配置target目录下的打包文件
        rootPath: C:\03_code\idea_workspace\fortune-commons\commons-doc-server-http\target\commons-doc-server-http\ #上传文件的根目录,实际工作环境按照实际情况更改即可
    
  • 打包后在tomcat独立启动方式

    # 也可以单独把打包后的war包拷贝到tomcat webapp目录下直接运行也可以
        rootPath: C:\05_webserver\apache-tomcat-8.5.42\webapps\doc-server #上传文件的根目录,实际工作环境按照实际情况更改即可
    

4.1.2 启动

本文采用的是idea自启动方式,则需要配置一下tomcat路径,以及引入相应的module即可,如下图所示:

[图片上传失败...(image-e9e83d-1565017352267)]

需要注意的是,在Deployment页签,需要配置该项目访问的Application Context,否则有可能启动后出现404的情况。如下图所示:

[图片上传失败...(image-d7a096-1565017352267)]

配置完成即可启动文件服务组件,如下图即为启动信息日志:

2019-08-04  22:07:17.484 [RMI TCP Connection(5)-127.0.0.1] INFO  org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started
2019-08-04  22:07:19.211 [RMI TCP Connection(5)-127.0.0.1] INFO  org.fortune.commons.core.cglib.BeanCopierContainer - 创建模型的BeanCopier,start...
2019-08-04  22:07:19.214 [RMI TCP Connection(5)-127.0.0.1] INFO  org.fortune.commons.core.cglib.BeanCopierContainer - 创建模型的BeanCopier,end;size=0
2019-08-04  22:07:20.158 [RMI TCP Connection(5)-127.0.0.1] INFO  org.fortune.commons.core.help.BeanInitializeCompletedListener - spring conf 容器初始化完毕..处理启动之后事件--start
2019-08-04  22:07:20.164 [RMI TCP Connection(5)-127.0.0.1] INFO  org.fortune.doc.server.DocServerContainer - 加入账户:fortune
2019-08-04  22:07:20.164 [RMI TCP Connection(5)-127.0.0.1] INFO  org.fortune.doc.server.DocServerContainer - 加入账户:fortune0
2019-08-04  22:07:20.166 [RMI TCP Connection(5)-127.0.0.1] INFO  org.fortune.doc.server.DocServerContainer - 加入默认账户:Account{userName='default_account', password='lyx', rootPath='C:\05_webserver\apache-tomcat-8.5.42\bin\', level=1, thumbHeight=20, thumbWidth=20}
2019-08-04  22:07:20.166 [RMI TCP Connection(5)-127.0.0.1] INFO  org.fortune.commons.core.help.BeanInitializeCompletedListener - spring conf 容器初始化完毕..处理启动之后事件--end
2019-08-04  22:07:20.182 [RMI TCP Connection(5)-127.0.0.1] INFO  org.springframework.web.context.ContextLoader - Root WebApplicationContext initialized in 2690 ms
2019-08-04  22:07:20.198 [RMI TCP Connection(5)-127.0.0.1] INFO  org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'doc-server'
2019-08-04  22:07:20.337 [RMI TCP Connection(5)-127.0.0.1] INFO  org.springframework.web.servlet.DispatcherServlet - Completed initialization in 138 ms
[2019-08-04 10:07:20,366] Artifact commons-doc-server-http:war exploded: Artifact is deployed successfully
[2019-08-04 10:07:20,366] Artifact commons-doc-server-http:war exploded: Deploy took 6,004 milliseconds

然后就可以访问如下页面,显示如下:

[图片上传失败...(image-1cb0e8-1565017352267)]

如果是初次启动上述服务,并且当前浏览器未安装Flash控件,则可能会出现上述截图所示的情况,点击安装即可。安装完成不出意外的话,则是如下画面,

[图片上传失败...(image-b6c6ae-1565017352267)]

选择图片上传即可,上传完成显示如下结果,

[图片上传失败...(image-3f2505-1565017352267)]

并且在控制台打印出的日志如下:

2019-08-04  22:10:28.207 [http-nio-8080-exec-10] INFO  org.fortune.doc.server.handler.image.UploadImageServerHandler - 图片上传的根目录:C:\03_code\idea_workspace\fortune-commons\commons-doc-server-http\target\commons-doc-server-http\/\images
2019-08-04  22:10:28.210 [http-nio-8080-exec-10] INFO  org.fortune.doc.server.handler.image.UploadImageServerHandler - 图片上传需要保存到数据库的目录:images\fortune\0\
2019-08-04  22:10:28.210 [http-nio-8080-exec-10] INFO  org.fortune.doc.server.handler.image.UploadImageServerHandler - 图片上传绝对路径:C:\03_code\idea_workspace\fortune-commons\commons-doc-server-http\target\commons-doc-server-http\/\images\fortune\0\
2019-08-04  22:10:28.210 [http-nio-8080-exec-10] INFO  org.fortune.doc.server.handler.image.UploadImageServerHandler - 图片上传动态生成的图片文件名称:190804221028_9605.JPG
2019-08-04  22:10:28.212 [http-nio-8080-exec-10] INFO  org.fortune.doc.server.handler.image.UploadImageServerHandler - 图片上传成功,路径为images\fortune\0\\190804221028_9605.JPG

可以看出,我们的图片保存到相对路径为 images\fortune\0\\190804221028_9605.JPG的地方,在idea的target目录下也可以看到该图片,如下所示:

[图片上传失败...(image-5420e7-1565017352267)]

我们可以通过如下地址进行访问,

http://localhost:8080/doc-server/images/fortune/0/190804221028_9605.JPG,关于如何得到图片地址,则在客户端工具类中有相应的方法获取。

[图片上传失败...(image-40d494-1565017352267)]

4.2 客户端配置与启动

4.2.1 配置

客户端的配置比较简单,也是采用yml文件方式配置如下:

#上传成功后,访问文件的正确方式为:
# ${host}:${port}/${domainName}/${type}/${path}
# appName为远程文件服务app名称,比如doc-server
# type 为images/attaches
# path为文件上传后服务端返回的文件相对路径
#http://localhost:8080/doc-server/images/fortune/20190629/1561769031017.jpg
upload:
  server:
    port: 8080 #需要配置的是远程文件服务器的tomcat的端口号,非远程文件服务netty端口号
    host: 127.0.0.1
    domainName: doc-server
  userName: fortune
  password: fortune

需要注意的是,文件访问的端口号跟远程文件服务器netty对应的端口是不一样的,这点需要特别注意。在调用文件服务返回的路径的时候,需要用到服务端访问文件的地址,进而访问相应的文件内容。可通过方法 org.fortune.doc.client.DocClientContainer#getDocServerUrl得到相应的服务端地址,再拼接上返回的相对路径,即可得到文件的完整地址了。

4.2.2 启动/调用

客户端单元测试用例如下:

package org.fortune.doc.client;

import org.fortune.commons.core.conf.ApplicationContextHelperConfiguration;
import org.fortune.commons.core.conf.SettingsConfiguration;
import org.fortune.doc.client.conf.DocClientConfiguration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.io.File;

/**
 * @author: landy
 * @date: 2019/5/30 23:37
 * @description:
 */
@RunWith(SpringJUnit4ClassRunner.class) //调用Spring单元测试类
@ContextConfiguration(classes = {
        SettingsConfiguration.class,  // common settings configuration
        DocClientConfiguration.class,
        ApplicationContextHelperConfiguration.class
}) //加载Spring配置文件
public class DocClientTest {

    @Test
    public void test() {
        //DocClient.doImageUpload("psb.jpg", new File("C:\\06_temp\\psb.jpg"));
        //DocClient.doImageDelete("images\\fortune\\b\\190707114835_9573.jpg");
        DocClient.doImageReplace("images\\fortune\\f\\\\190707114616_3742.JPG", new File("C:\\06_temp\\psb.jpg"));
    }

    @Test
    public void docServerUrl() {
        System.out.println(DocClientContainer.getInstance().getDocServerUrl());
    }

}

同样的服务端也会显示出上述相似的日志信息。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容