Vertx 檔案上傳下載

sweetmain發表於2017-12-13

Vert.x 實現檔案上傳下載,並使用 MapDB進行儲存

import com.xiaoniu.vertx.timer.utils.Runner;
import com.xiaoniu.vertx.timer.utils.Utils;
import io.netty.util.internal.StringUtil;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import io.vertx.core.file.FileSystem;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerFileUpload;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.mapdb.HTreeMap;
import org.mapdb.Serializer;

import java.io.File;


/**
 * Windows 版本
 * Created by sweet on 17-8-31.
 */
public class UploadVerticleForWindows extends AbstractVerticle {

  private static final String rootPath = "C:/xiaoniu_doc/fileserver-version-3/vertx-timer/";

  private static final String url = "http://127.0.0.1:7778/file?path=";
  private static final Logger log = LogManager.getLogger(UploadVerticleForWindows.class);
  private DB db;
  private HTreeMap<String, String> map;

  private FileSystem fileSystem;

  public static void main(String[] args) {
    Runner.runExample(UploadVerticleForWindows.class);
  }

  @Override
  public void start(Future<Void> startFuture) throws Exception {

    fileSystem = vertx.fileSystem();

    Future<Boolean> initFuture = Future.future();
    initDb(initFuture);

    initFuture.setHandler(init -> {
      if (init.succeeded() && init.result()) {
        Future uploadFuture = Future.future();
        Future downloadFuture = Future.future();

        upload(uploadFuture);
        download(downloadFuture);

        CompositeFuture.all(uploadFuture, downloadFuture).setHandler(server -> {
          if (server.succeeded()) {
            log.debug("server 全部部署成功");
            startFuture.complete();
          } else {
            server.cause().printStackTrace();
            log.error(server.cause().getMessage());
            startFuture.fail(server.cause());
          }
        });
      } else {
        init.cause().printStackTrace();
        log.error(init.cause().getMessage());
        startFuture.fail(init.cause());
      }
    });
  }

  private void saveFile(HttpServerFileUpload upload, String savePath,
                        String name, String uuid, String nameDb, String time, String urlPath) {
    upload.streamToFileSystem(savePath).endHandler(end -> {
      long size = upload.size();
      System.out.println("size : " + size + " bytes");
      String type = Utils.type(name);

      JsonObject json = new JsonObject()
              .put("id", uuid)
              .put("name", nameDb)
              .put("realName", upload.filename())
              .put("path", savePath)
              .put("type", type)
              .put("createTime", time)
              .put("url", urlPath)
              .put("size", size);

      System.out.println(Json.encodePrettily(json));

      map.put(uuid, json.toString());
    });
  }

  private void initDb(Future<Boolean> future) {
    try {
      File file1 = new File("C:/xiaoniu_doc/fileserver-version-3/vertx-timer/xn-file-windows.db");

      db = DBMaker.fileDB(file1).closeOnJvmShutdown().make();

      map = db.hashMap("xn-file-server", Serializer.STRING, Serializer.STRING)
              .expireStoreSize(1 * 1024 * 1024 * 1024)
              .createOrOpen();

      future.complete(true);
    } catch (Exception e) {
      e.printStackTrace();
      future.fail(e.getCause());
    }
  }

  private void upload(Future uploadFuture) {
    vertx.createHttpServer().requestHandler(req -> {
      req.setExpectMultipart(true);
      req.exceptionHandler(th -> {
        th.printStackTrace();
        log.error(th.getLocalizedMessage());
        respError(req, 500, th.getLocalizedMessage());
      });

      HttpMethod method = req.method();
      String reqPath = req.path();
      System.out.println("method: " + method + " , reqPath: " + reqPath);
      if (method.equals(HttpMethod.POST) && "/api/fileUpload".equals(reqPath)) {

        String name = req.getParam("name"); // 預設 yyyy/MM/dd 資料夾位置 name=xxxx.jpg
        String path = req.getParam("path"); // 帶 path 路徑 path=wang/xue/hello.jpg
        String fileSavePath = rootPath + Utils.dir();
        System.out.println("name: " + name + " path >> " + fileSavePath);

        String time = Utils.time(); // 當前時間戳
        String uuid = Utils.uuid(); //  UUID 做 key

        if (!StringUtil.isNullOrEmpty(name)) { // 處理 name 引數 上傳檔案
          req.uploadHandler(upload -> {
            if (!Utils.checkPath(fileSavePath))
              fileSystem.mkdirsBlocking(fileSavePath);

            System.out.println("fileName : " + upload.filename());
            String nameDb = time + "-" + name;
            String savePath = fileSavePath + nameDb;
            String urlPath = url + uuid;
            System.out.println("path ==> " + savePath);

            saveFile(upload, savePath, name, uuid, nameDb, time, urlPath);
            resp(req, new JsonObject().put("url", urlPath));
          });
        } else if (!StringUtil.isNullOrEmpty(path)) { // 處理 path 路徑 檔案上傳
          String[] pathArray = Utils.path(path);
          // 需要檢查 該路徑是否存在,不存在建立(並檢查是否存在 ..) TODO 測試 6級資料夾深度
          String pathItem = pathArray[0];     // 引數傳入的 路徑
          String reqFileName = pathArray[1]; // 引數傳入的 fileName

          if (pathItem.indexOf(".") > 0) {
            log.error("非法路徑 >> " + path);
            respError(req, 404, "");
          } else {
            String savePath = rootPath + pathItem;
            if (!Utils.checkPath(savePath))
              fileSystem.mkdirsBlocking(savePath);
            req.uploadHandler(upload -> {
              System.out.println("fileName : " + upload.filename());
              String nameDb = time + "-" + reqFileName;
              String urlPath = url + uuid;
              String savePathAndFileName = savePath + nameDb;
              System.out.println("path ==> " + savePathAndFileName);
              saveFile(upload, savePathAndFileName, nameDb, uuid, nameDb, time, urlPath);
              resp(req, new JsonObject().put("url", urlPath));
            });
          }
        } else {
          respError(req, 404, "");
        }
      } else {
        respError(req, 404, "");
      }
    }).listen(7777, res -> {
      if (res.succeeded()) {
        log.debug("Upload Server 7777");
        uploadFuture.complete();
      } else {
        res.cause().printStackTrace();
        log.error(res.cause().getMessage());
        uploadFuture.fail(res.cause());
      }
    });
  }

  private void download(Future downloadFuture) {
    vertx.createHttpServer().requestHandler(req -> {

      req.exceptionHandler(th -> {
        th.printStackTrace();
        log.error(th.getLocalizedMessage());
        respError(req, 500, th.getLocalizedMessage());
      });

      HttpMethod method = req.method();
      String urlPath = req.path();
      System.out.println("method: " + method + " , url: " + urlPath);
      if (method.equals(HttpMethod.GET) && "/file".equals(urlPath)) {

        String path = req.getParam("path"); // 帶 path 路徑 path=wang/xue/hello.jpg
        String json = map.get(path);
        if (StringUtil.isNullOrEmpty(json)) {
          respError(req, 404, "資料不存在");
        } else {
          JsonObject jsonObject = new JsonObject(json);
          System.out.println(jsonObject.encodePrettily());
          String path1 = jsonObject.getString("path");
          System.out.println("path <<< " + path1);
          req.response().sendFile(path1);
        }
      } else {
        respError(req, 404, "路徑不對");
      }
    }).listen(7778, res -> {
      if (res.succeeded()) {
        log.debug("Download Server 7778");
        downloadFuture.complete();
      } else {
        res.cause().printStackTrace();
        log.error(res.cause().getMessage());
        downloadFuture.fail(res.cause());
      }
    });
  }

  private static void resp(HttpServerRequest request, JsonObject ret) {
    request.response()
            .putHeader("content-type", "application/json;charset=utf-8")
            .putHeader("Access-Control-Allow-Origin", "*")
            .putHeader("Access-Control-Allow-Credentials", "true")
            .putHeader("Content-Disposition", "attachment")
            .end(Json.encodePrettily(ret));
    return;
  }

  private static void respError(HttpServerRequest request, int code, String error) {
    request.response()
            .putHeader("content-type", "application/json;charset=utf-8")
            .putHeader("Access-Control-Allow-Origin", "*")
            .putHeader("Access-Control-Allow-Credentials", "true")
            .putHeader("Content-Disposition", "attachment")
            .setStatusCode(code)
            .end(Json.encodePrettily(new JsonObject().put("error", error)));
    return;
  }

  @Override
  public void stop(Future<Void> stopFuture) throws Exception {
    map.close();
    db.close();
    stopFuture.complete();
  }
複製程式碼

使用 IDE執行該程式碼時,如果是在Windows下停止應用,MapDB儲存檔案會被損壞。Linux上沒有問題,即時去掉 stop裡的程式碼也沒有問題。上述程式碼裡的檔案存放位置需要自己手動修改。

工具類 Utils.java

import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

/**
 * Created by sweet on 17-9-7.
 */
public class Utils {

  private Utils(){}

  public static void main(String[] args) {
    String[] path = path("/xxxx/xxxx/111.jpg");
    for (String s: path) {
      System.out.println(s);
    }

    boolean exists = Files.exists(Paths.get("C:\\xiaoniu_doc\\fileserver-version-3\\vertx-timer\\xxxx\\"));
    System.out.println(exists);

    System.out.println(path[0].indexOf("."));
    System.out.println("xxxxx/xxxx/xx.jpg".indexOf("."));
  }

  /**
   * 生成 2017/10/01 路徑
   * @return
   */
  public static String dir() {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd/");
    return simpleDateFormat.format(new Date());
  }

  /**
   * 當前時間戳
   * @return
   */
  public static String time() {
    return System.currentTimeMillis()+"";
  }

  /**
   * 大寫 uuid
   * @return
   */
  public static String uuid() {
    return UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
  }

  /**
   * 獲取檔案的型別
   * @param name
   * @return
   */
  public static String type(String name) {
    return name.substring(name.lastIndexOf(".")+1, name.length());
  }

  /**
   * 處理檔案 path /xxxx/xxxx/111.jpg
   * @param path
   * @return [0] 檔案路徑
   *          [1] 檔名
   *
   */
  public static String[] path(String path) {
    String[] s = new String[3];

    int i = path.lastIndexOf("/");
    String fileName = path.substring(i + 1, path.length());
    s[0] = path.substring(0, i+1);
    s[1] = fileName;

    return s;
  }

  /**
   * 路徑存在 返回 true,如果不存在 返回 false
   * @param path
   * @return
   */
  public static boolean checkPath(String path) {
    return Files.exists(Paths.get(path));
  }
}
複製程式碼

log4j2.xml 裡面有些地方需要根據自己的實際工程位置進行修改

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Properties>
        <Property name="log-pattern">%d{yyyy-MM-dd HH:mm:ss} |- %highlight{%5p}{TRACE=blue, DEBUG=green, INFO=green, WARN=yellow, ERROR=red, FATAL=red} in %style{%C{1}:%L}{cyan} [%style{%t}{magenta}] - %m%n</Property>
    </Properties>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${log-pattern}" />
        </Console>
        <RollingRandomAccessFile name="FILE" fileName="logs/app.log" append="true" filePattern="logs/lsp_app.log.%d{yyyyMMdd}">
            <PatternLayout>
                <Pattern>%d %-5p [%c] %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy  interval="24"  modulate="true"/>
            </Policies>
        </RollingRandomAccessFile>
    </Appenders>

    <!-- Logger levels: trace, debug, info, warn, error, fatal -->
    <Loggers>        <!--name 是你需要打log的包名-->
        <AsyncLogger name="com.xiaoniu.vertx.timer" level="DEBUG" additivity="false" includeLocation="true">
            <AppenderRef ref="Console" />
            <!--<appender-ref ref="FILE" />-->
        </AsyncLogger>

        <Root level="WARN">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>
複製程式碼

最後是 pom.xml

<dependencies>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-core</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mapdb</groupId>
            <artifactId>mapdb</artifactId>
            <version>3.0.2</version>
        </dependency>

        <!-- log4j2 日誌配置-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-jcl</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-jul</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.3.6</version>
        </dependency>
        <!--日誌配置結束-->
    </dependencies>
複製程式碼

工程結構

image.png

postman測試

  • 帶name 引數的上傳

    image.png

  • 帶 path引數的上傳

    image.png

相關文章