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>
複製程式碼
工程結構
postman測試
-
帶name 引數的上傳
-
帶 path引數的上傳