第一个版本
This commit is contained in:
commit
cc6a19cf40
14
.idea/misc.xml
Normal file
14
.idea/misc.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
89
pom.xml
Normal file
89
pom.xml
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.lion</groupId>
|
||||
<artifactId>storageNode</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.86.Final</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.15.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.28</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.im4java</groupId>
|
||||
<artifactId>im4java</artifactId>
|
||||
<version>1.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
<version>1.2.17</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.16</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.21</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.4.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>lion.Main</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
50
src/main/java/lion/Domain/GalleryTask.java
Normal file
50
src/main/java/lion/Domain/GalleryTask.java
Normal file
@ -0,0 +1,50 @@
|
||||
package lion.Domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GalleryTask {
|
||||
|
||||
public static byte DOWNLOADING = 1;
|
||||
|
||||
public static byte DOWNLOAD_COMPLETE = 2;
|
||||
|
||||
public static byte DOWNLOAD_QUEUED = 3;
|
||||
|
||||
public static byte COMPRESS_COMPLETE = 4;
|
||||
|
||||
|
||||
public static byte DOWNLOAD_ALL = 3;
|
||||
public static byte DOWNLOAD_PREVIEW = 2;
|
||||
public static byte DOWNLOAD_SOURCE = 1;
|
||||
|
||||
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String name;
|
||||
|
||||
private int gid;
|
||||
|
||||
private int pages;
|
||||
|
||||
private byte status;
|
||||
|
||||
private int proceeding;
|
||||
|
||||
private byte type;
|
||||
|
||||
@JsonIgnore
|
||||
private String path;
|
||||
|
||||
@JsonIgnore
|
||||
public boolean is_download_complete(){
|
||||
return status == DOWNLOAD_COMPLETE;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean is_compress_complete(){
|
||||
return status == COMPRESS_COMPLETE;
|
||||
}
|
||||
}
|
||||
9
src/main/java/lion/ErrorCode/ErrorCode.java
Normal file
9
src/main/java/lion/ErrorCode/ErrorCode.java
Normal file
@ -0,0 +1,9 @@
|
||||
package lion.ErrorCode;
|
||||
|
||||
public class ErrorCode {
|
||||
public static final byte IO_ERROR = 1;
|
||||
public static final byte FILE_NOT_FOUND = 2;
|
||||
|
||||
public static final byte COMPRESS_ERROR = 3;
|
||||
|
||||
}
|
||||
28
src/main/java/lion/Main.java
Normal file
28
src/main/java/lion/Main.java
Normal file
@ -0,0 +1,28 @@
|
||||
package lion;
|
||||
|
||||
import lion.ErrorCode.ErrorCode;
|
||||
import lombok.extern.log4j.Log4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
|
||||
@Log4j
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
new Thread(() -> MultiThreadedHTTPServer.main(null)).start();
|
||||
|
||||
new storageNode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
24
src/main/java/lion/Message/AbstractMessage.java
Normal file
24
src/main/java/lion/Message/AbstractMessage.java
Normal file
@ -0,0 +1,24 @@
|
||||
package lion.Message;
|
||||
|
||||
public class AbstractMessage {
|
||||
|
||||
public static final byte DOWNLOAD_POST_MESSAGE = 1;
|
||||
|
||||
public static final byte DOWNLOAD_STATUS_MESSAGE = 2;
|
||||
|
||||
public static final byte DELETE_GALLERY_MESSAGE = 3;
|
||||
|
||||
public static final byte RESPONSE_MESSAGE = 0;
|
||||
|
||||
public static final byte UPDATE_GALLERY_MESSAGE = 4;
|
||||
|
||||
public static final byte GALLERY_PAGE_QUERY_MESSAGE = 5;
|
||||
|
||||
public static final byte IDENTITY_MESSAGE = 6;
|
||||
|
||||
public static final byte GALLERY_REQUEST_MESSAGE = 101;
|
||||
|
||||
public byte messageType;
|
||||
|
||||
public int messageId;
|
||||
}
|
||||
20
src/main/java/lion/Message/DeleteGalleryMessage.java
Normal file
20
src/main/java/lion/Message/DeleteGalleryMessage.java
Normal file
@ -0,0 +1,20 @@
|
||||
package lion.Message;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeleteGalleryMessage extends AbstractMessage{
|
||||
{
|
||||
messageType = DELETE_GALLERY_MESSAGE;
|
||||
}
|
||||
|
||||
public static final byte DELETE_ALL = 3;
|
||||
public static final byte DELETE_PREVIEW = 2;
|
||||
public static final byte DELETE_SOURCE = 1;
|
||||
|
||||
|
||||
|
||||
byte deleteType;
|
||||
|
||||
String galleryName;
|
||||
}
|
||||
13
src/main/java/lion/Message/DownloadPostMessage.java
Normal file
13
src/main/java/lion/Message/DownloadPostMessage.java
Normal file
@ -0,0 +1,13 @@
|
||||
package lion.Message;
|
||||
|
||||
import lion.Domain.GalleryTask;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DownloadPostMessage extends AbstractMessage{
|
||||
{
|
||||
messageType = DOWNLOAD_POST_MESSAGE;
|
||||
}
|
||||
|
||||
GalleryTask galleryTask;
|
||||
}
|
||||
13
src/main/java/lion/Message/DownloadStatusMessage.java
Normal file
13
src/main/java/lion/Message/DownloadStatusMessage.java
Normal file
@ -0,0 +1,13 @@
|
||||
package lion.Message;
|
||||
|
||||
import lion.Domain.GalleryTask;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DownloadStatusMessage extends AbstractMessage{
|
||||
GalleryTask[] galleryTasks;
|
||||
|
||||
{
|
||||
messageType = DOWNLOAD_STATUS_MESSAGE;
|
||||
}
|
||||
}
|
||||
21
src/main/java/lion/Message/GalleryPageQueryMessage.java
Normal file
21
src/main/java/lion/Message/GalleryPageQueryMessage.java
Normal file
@ -0,0 +1,21 @@
|
||||
package lion.Message;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GalleryPageQueryMessage extends AbstractMessage{
|
||||
{
|
||||
messageType = GALLERY_PAGE_QUERY_MESSAGE;
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
String name;
|
||||
|
||||
int page;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
String pageName;
|
||||
|
||||
byte result;
|
||||
}
|
||||
25
src/main/java/lion/Message/GalleryRequestMessage.java
Normal file
25
src/main/java/lion/Message/GalleryRequestMessage.java
Normal file
@ -0,0 +1,25 @@
|
||||
package lion.Message;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
//请求预览/压缩包
|
||||
public class GalleryRequestMessage extends AbstractMessage{
|
||||
|
||||
public static final byte SOURCE = 1;
|
||||
public static final byte PREVIEW = 2;
|
||||
|
||||
public static final byte COMPRESS_SOURCE = 3;
|
||||
|
||||
{
|
||||
messageType = GALLERY_REQUEST_MESSAGE;
|
||||
}
|
||||
|
||||
String galleryName;
|
||||
|
||||
byte type;
|
||||
|
||||
short page;
|
||||
|
||||
short port;
|
||||
}
|
||||
10
src/main/java/lion/Message/IdentityMessage.java
Normal file
10
src/main/java/lion/Message/IdentityMessage.java
Normal file
@ -0,0 +1,10 @@
|
||||
package lion.Message;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class IdentityMessage extends AbstractMessage{
|
||||
{
|
||||
messageType = IDENTITY_MESSAGE;
|
||||
}
|
||||
}
|
||||
58
src/main/java/lion/Message/MessageCodec.java
Normal file
58
src/main/java/lion/Message/MessageCodec.java
Normal file
@ -0,0 +1,58 @@
|
||||
package lion.Message;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageCodec;
|
||||
import lombok.extern.log4j.Log4j;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@Log4j
|
||||
public class MessageCodec extends ByteToMessageCodec<AbstractMessage> {
|
||||
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
public MessageCodec(){
|
||||
objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext channelHandlerContext, AbstractMessage abstractMessage, ByteBuf byteBuf) {
|
||||
byteBuf.writeByte(abstractMessage.messageType);
|
||||
|
||||
byte[] bytes = objectMapper.valueToTree(abstractMessage).toString().getBytes(StandardCharsets.UTF_8);
|
||||
byteBuf.writeInt(bytes.length);
|
||||
byteBuf.writeBytes(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
|
||||
byte messageType = byteBuf.readByte();
|
||||
int length = byteBuf.readInt();
|
||||
System.out.println(length);
|
||||
byte[] bytes = new byte[length];
|
||||
byteBuf.readBytes(bytes);
|
||||
final String metadata = new String(bytes, StandardCharsets.UTF_8);
|
||||
|
||||
AbstractMessage abstractMessage = switch (messageType){
|
||||
case AbstractMessage.DOWNLOAD_POST_MESSAGE -> objectMapper.readValue(metadata, DownloadPostMessage.class);
|
||||
case AbstractMessage.DOWNLOAD_STATUS_MESSAGE -> objectMapper.readValue(metadata, DownloadStatusMessage.class);
|
||||
case AbstractMessage.GALLERY_REQUEST_MESSAGE -> objectMapper.readValue(metadata, GalleryRequestMessage.class);
|
||||
case AbstractMessage.RESPONSE_MESSAGE -> objectMapper.readValue(metadata, ResponseMessage.class);
|
||||
case AbstractMessage.UPDATE_GALLERY_MESSAGE -> objectMapper.readValue(metadata, UpdateGalleryMessage.class);
|
||||
case AbstractMessage.DELETE_GALLERY_MESSAGE -> objectMapper.readValue(metadata, DeleteGalleryMessage.class);
|
||||
case AbstractMessage.GALLERY_PAGE_QUERY_MESSAGE -> objectMapper.readValue(metadata, GalleryPageQueryMessage.class);
|
||||
case AbstractMessage.IDENTITY_MESSAGE -> objectMapper.readValue(metadata, IdentityMessage.class);
|
||||
default -> null;
|
||||
};
|
||||
|
||||
if (abstractMessage == null){
|
||||
log.error("decode error, messageType: " + messageType + "ip:" + channelHandlerContext.channel().remoteAddress().toString());
|
||||
return;
|
||||
}
|
||||
list.add(abstractMessage);
|
||||
}
|
||||
}
|
||||
21
src/main/java/lion/Message/ResponseMessage.java
Normal file
21
src/main/java/lion/Message/ResponseMessage.java
Normal file
@ -0,0 +1,21 @@
|
||||
package lion.Message;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ResponseMessage extends AbstractMessage{
|
||||
{
|
||||
messageType = RESPONSE_MESSAGE;
|
||||
}
|
||||
|
||||
static final byte SUCCESS = 0;
|
||||
|
||||
byte result;
|
||||
|
||||
public ResponseMessage(int messageId, byte result){
|
||||
this.messageId = messageId;
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
13
src/main/java/lion/Message/UpdateGalleryMessage.java
Normal file
13
src/main/java/lion/Message/UpdateGalleryMessage.java
Normal file
@ -0,0 +1,13 @@
|
||||
package lion.Message;
|
||||
|
||||
import lion.Domain.GalleryTask;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UpdateGalleryMessage extends AbstractMessage{
|
||||
{
|
||||
messageType = UPDATE_GALLERY_MESSAGE;
|
||||
}
|
||||
|
||||
GalleryTask galleryTask;
|
||||
}
|
||||
2
src/main/java/lion/Message/lombok.config
Normal file
2
src/main/java/lion/Message/lombok.config
Normal file
@ -0,0 +1,2 @@
|
||||
config.stopBubbling=true
|
||||
lombok.equalsAndHashCode.callSuper=call
|
||||
180
src/main/java/lion/MultiThreadedHTTPServer.java
Normal file
180
src/main/java/lion/MultiThreadedHTTPServer.java
Normal file
@ -0,0 +1,180 @@
|
||||
package lion;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class MultiThreadedHTTPServer {
|
||||
private static final int PORT = 8888;
|
||||
private static final int BUFFER_SIZE = 1024;
|
||||
|
||||
public static void main(String[] args) {
|
||||
ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||
try(ServerSocket serverSocket = new ServerSocket(PORT)) {
|
||||
System.out.println("Server listening on port " + PORT);
|
||||
while (true) {
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
System.out.println("Client connected: " + clientSocket.getInetAddress().getHostAddress());
|
||||
// 线程池处理下载请求
|
||||
threadPool.submit(() -> handleClientRequest(clientSocket));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleClientRequest(Socket clientSocket) {
|
||||
try {
|
||||
BufferedReader requestReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
||||
String requestLine = requestReader.readLine();
|
||||
|
||||
// Parse the request line to get the method and path
|
||||
String[] requestParts = requestLine.split(" ");
|
||||
String method = requestParts[0];
|
||||
Map<String, String> paramMap = parseRequestLine(requestParts[1]);//path
|
||||
System.out.println(Arrays.toString(requestParts));
|
||||
|
||||
// Only handle GET requests
|
||||
if (method.equals("GET")) {
|
||||
// Set the file path for download
|
||||
File file;
|
||||
if(paramMap.get("AuthCode") != null)
|
||||
if(paramMap.get("AuthCode").equals("alone")){
|
||||
String path = URLDecoder.decode(requestParts[1].split("\\?")[0], StandardCharsets.UTF_8);
|
||||
file = new File(path);
|
||||
}
|
||||
else {
|
||||
String filePath = "/root/gallery/gallery";
|
||||
String path = URLDecoder.decode(requestParts[1].split("\\?")[0], StandardCharsets.UTF_8);
|
||||
if(!path.contains(".")){
|
||||
file = new File("/root/abc");
|
||||
}else {
|
||||
String name = path.split("\\.")[0];
|
||||
filePath += (name + "/" + name + ".zip");
|
||||
file = new File(filePath);
|
||||
}
|
||||
}
|
||||
else{
|
||||
sendErrorResponse(clientSocket, "403 Forbidden");
|
||||
return;
|
||||
}
|
||||
System.out.println(file.getAbsolutePath());
|
||||
// Check if the file exists and is readable
|
||||
if (file.exists() && file.isFile() && file.canRead()) {
|
||||
// Get the file length
|
||||
long fileLength = file.length();
|
||||
|
||||
// Get the range information for resuming download
|
||||
long startByte = 0;
|
||||
long endByte = fileLength - 1;
|
||||
String rangeHeader = getRequestHeader(requestReader, "Range");
|
||||
if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
|
||||
String[] rangeValues = rangeHeader.substring(6).split("-");
|
||||
startByte = Long.parseLong(rangeValues[0]);
|
||||
if (rangeValues.length > 1 && !rangeValues[1].isEmpty()) {
|
||||
endByte = Long.parseLong(rangeValues[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Send the HTTP response headers
|
||||
OutputStream responseStream = clientSocket.getOutputStream();
|
||||
PrintWriter responseWriter = new PrintWriter(responseStream, true);
|
||||
responseWriter.println("HTTP/1.1 206 Partial Content");
|
||||
responseWriter.println("Content-Type: application/octet-stream");
|
||||
responseWriter.println("Accept-Ranges: bytes");
|
||||
responseWriter.println("Content-Length: " + (endByte - startByte + 1));
|
||||
responseWriter.println("Content-Range: bytes " + startByte + "-" + endByte + "/" + fileLength);
|
||||
responseWriter.println();
|
||||
|
||||
// Send the file content
|
||||
try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
|
||||
randomAccessFile.seek(startByte);
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead;
|
||||
long bytesRemaining = endByte - startByte + 1;
|
||||
while (bytesRemaining > 0 && (bytesRead = randomAccessFile.read(buffer, 0, (int) Math.min(buffer.length, bytesRemaining))) != -1) {
|
||||
responseStream.write(buffer, 0, bytesRead);
|
||||
bytesRemaining -= bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
// Close the response output stream
|
||||
responseStream.close();
|
||||
} else {
|
||||
// File not found or not readable, send 404 response
|
||||
sendErrorResponse(clientSocket, "404 Not Found");
|
||||
}
|
||||
} else {
|
||||
// Non-GET requests, send 501 response
|
||||
sendErrorResponse(clientSocket, "501 Not Implemented");
|
||||
}
|
||||
|
||||
// Close the request reader and client socket
|
||||
requestReader.close();
|
||||
clientSocket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getRequestHeader(BufferedReader requestReader, String headerName) throws IOException {
|
||||
String line;
|
||||
while ((line = requestReader.readLine()) != null) {
|
||||
if (line.trim().isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.startsWith(headerName + ":")) {
|
||||
return line.substring(headerName.length() + 1).trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Map<String, String> parseRequestLine(String requestLine) {
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
|
||||
if(requestLine == null)
|
||||
return null;
|
||||
|
||||
if (requestLine.contains("?")) {
|
||||
String[] requestParts = requestLine.split("\\?");
|
||||
String path = requestParts[0];
|
||||
queryParams.put("path", path);
|
||||
|
||||
// Extract the query string after the '?' character
|
||||
String queryString = requestParts[1];
|
||||
String[] paramPairs = queryString.split("&");
|
||||
|
||||
// Split the query string into individual parameter key-value pairs
|
||||
for (String paramPair : paramPairs) {
|
||||
String[] keyValue = paramPair.split("=");
|
||||
if (keyValue.length == 2) {
|
||||
String key = URLDecoder.decode(keyValue[0], StandardCharsets.UTF_8);
|
||||
String value = URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8);
|
||||
queryParams.put(key, value);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
queryParams.put("path", requestLine);
|
||||
}
|
||||
return queryParams;
|
||||
}
|
||||
|
||||
private static void sendErrorResponse(Socket clientSocket, String statusCode) throws IOException {
|
||||
OutputStream responseStream = clientSocket.getOutputStream();
|
||||
PrintWriter responseWriter = new PrintWriter(responseStream, true);
|
||||
responseWriter.println("HTTP/1.1 " + statusCode);
|
||||
responseWriter.println("Content-Type: text/html");
|
||||
responseWriter.println();
|
||||
responseWriter.println("<h1>" + statusCode + "</h1>");
|
||||
responseStream.close();
|
||||
}
|
||||
}
|
||||
45
src/main/java/lion/Service/DeleteService.java
Normal file
45
src/main/java/lion/Service/DeleteService.java
Normal file
@ -0,0 +1,45 @@
|
||||
package lion.Service;
|
||||
|
||||
import lion.ErrorCode.ErrorCode;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class DeleteService {
|
||||
public static byte deleteAll(String path){
|
||||
File file = new File(path);
|
||||
if(!file.isDirectory())
|
||||
return ErrorCode.FILE_NOT_FOUND;
|
||||
|
||||
if (FileUtil.del(path)) {
|
||||
return 0;
|
||||
}else{
|
||||
return ErrorCode.IO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte deletePreview(String path){
|
||||
File directory = new File(path);
|
||||
File[] files = directory.listFiles((dir, name) -> !name.endsWith("zip"));
|
||||
|
||||
if(files == null)
|
||||
return ErrorCode.FILE_NOT_FOUND;
|
||||
for (File file : files)
|
||||
if(!FileUtil.del(file))
|
||||
return ErrorCode.IO_ERROR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static byte deleteSource(String path){
|
||||
File directory = new File(path);
|
||||
File[] files = directory.listFiles((dir, name) -> name.endsWith("zip"));
|
||||
|
||||
if(files == null)
|
||||
return ErrorCode.FILE_NOT_FOUND;
|
||||
if(!FileUtil.del(files[0]))
|
||||
return ErrorCode.IO_ERROR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
100
src/main/java/lion/Service/DeliveryService.java
Normal file
100
src/main/java/lion/Service/DeliveryService.java
Normal file
@ -0,0 +1,100 @@
|
||||
package lion.Service;
|
||||
import lion.ErrorCode.ErrorCode;
|
||||
import lion.Message.GalleryPageQueryMessage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class DeliveryService {
|
||||
|
||||
static String storagePath = "/root/gallery/gallery/";
|
||||
//缓存排序后的页数
|
||||
static LinkedHashMap<String, ArrayList<String>> pageCache;
|
||||
|
||||
static ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
|
||||
|
||||
|
||||
static {
|
||||
pageCache = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public static byte deliveryPreview(String name, short page, short port){
|
||||
if(!pageCache.containsKey(name)){
|
||||
byte result;
|
||||
if((result = pageCache(name)) != 0)
|
||||
return result;
|
||||
}
|
||||
|
||||
//0页为缩略图
|
||||
if(page == 0){
|
||||
return delivery(new File(storagePath, name + "/thumbnail.webp"), port);
|
||||
}else{
|
||||
return delivery(new File(storagePath, name + "/" + pageCache.get(name).get(page)), port);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte delivery(File file, short port){
|
||||
if(!file.exists())
|
||||
return ErrorCode.FILE_NOT_FOUND;
|
||||
|
||||
singleThreadPool.submit(() -> {
|
||||
try(SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("194.36.27.28", port));
|
||||
FileChannel fileChannel = FileChannel.open(file.toPath(), StandardOpenOption.READ)) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
while (fileChannel.read(buffer)!=-1){
|
||||
buffer.flip();
|
||||
socketChannel.write(buffer);
|
||||
buffer.clear();
|
||||
}
|
||||
socketChannel.shutdownOutput();
|
||||
return 0;
|
||||
}catch (IOException e){
|
||||
e.printStackTrace();
|
||||
return ErrorCode.IO_ERROR;
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static byte pageQuery(GalleryPageQueryMessage gpqm){
|
||||
if(!pageCache.containsKey(gpqm.getName())){
|
||||
byte result;
|
||||
if((result = pageCache(gpqm.getName())) != 0)
|
||||
return result;
|
||||
}
|
||||
ArrayList<String> pages = pageCache.get(gpqm.getName());
|
||||
if(pages.size() <= gpqm.getPage())
|
||||
return ErrorCode.FILE_NOT_FOUND;
|
||||
gpqm.setPageName(pageCache.get(gpqm.getName()).get(gpqm.getPage()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static byte pageCache(String name){
|
||||
File directory = new File(storagePath, name);
|
||||
if(!directory.isDirectory())
|
||||
return ErrorCode.FILE_NOT_FOUND;
|
||||
ArrayList<String> pageList = new ArrayList<>();
|
||||
File[] files = directory.listFiles(((dir, name1) -> name1.contains(".webp") && !name1.equals("thumbnail.webp")));
|
||||
|
||||
if(files == null)
|
||||
return ErrorCode.FILE_NOT_FOUND;
|
||||
ArrayList<File> fileArrayList = new ArrayList<>(Arrays.asList(files));
|
||||
fileArrayList.sort(Comparator.naturalOrder());
|
||||
pageList.add("thumbnail.webp");
|
||||
fileArrayList.forEach((f) -> pageList.add(f.getName()));
|
||||
pageCache.put(name, pageList);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
132
src/main/java/lion/Service/DownloadCheckService.java
Normal file
132
src/main/java/lion/Service/DownloadCheckService.java
Normal file
@ -0,0 +1,132 @@
|
||||
package lion.Service;
|
||||
|
||||
import lion.Domain.GalleryTask;
|
||||
import lion.ErrorCode.ErrorCode;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import lombok.extern.log4j.Log4j;
|
||||
import org.im4java.core.ConvertCmd;
|
||||
import org.im4java.core.IM4JavaException;
|
||||
import org.im4java.core.IMOperation;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
@Log4j
|
||||
public class DownloadCheckService {
|
||||
ArrayList<GalleryTask> queue;
|
||||
|
||||
String downloadPath = "/root/gallery/hentai/download/";
|
||||
|
||||
String storagePath = "/root/gallery/gallery/";
|
||||
|
||||
public DownloadCheckService(ArrayList<GalleryTask> queue){
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
public boolean downloadCheck(){
|
||||
if(queue.size() == 0)
|
||||
return false;
|
||||
log.info("下载检查:" + Arrays.toString(queue.toArray()));
|
||||
|
||||
File downloadDirectory = new File(downloadPath);
|
||||
File[] fileArray = downloadDirectory.listFiles();
|
||||
|
||||
if(fileArray == null || fileArray.length == 0)
|
||||
return false;
|
||||
|
||||
ArrayList<File> files = new ArrayList<>(Arrays.asList(fileArray));
|
||||
|
||||
//扫描进度
|
||||
for(File file: files)
|
||||
for(GalleryTask galleryTask: queue){
|
||||
if(!file.getName().contains(String.valueOf(galleryTask.getGid())))
|
||||
continue;
|
||||
|
||||
galleryTask.setName(file.getName());
|
||||
|
||||
File[] pages = file.listFiles((dir, name) -> !name.equals("galleryinfo.txt"));
|
||||
if (pages == null || pages.length == 0)
|
||||
continue;
|
||||
galleryTask.setProceeding(pages.length);
|
||||
|
||||
if (new File(file.getPath(), "galleryinfo.txt").exists()) {
|
||||
galleryTask.setStatus(GalleryTask.DOWNLOAD_COMPLETE);
|
||||
galleryTask.setPath(file.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//转格式
|
||||
ConvertCmd convertCmd = new ConvertCmd(true);
|
||||
for(GalleryTask galleryTask: queue){
|
||||
//跳过未完成
|
||||
if (!galleryTask.is_download_complete())
|
||||
continue;
|
||||
|
||||
File[] images = new File(galleryTask.getPath()).listFiles((dir, name) -> name.endsWith(".jpg") || name.endsWith(".png"));
|
||||
if(images == null){
|
||||
galleryTask.setStatus(ErrorCode.COMPRESS_ERROR);
|
||||
continue;
|
||||
}
|
||||
//长度相同比较字典序,否则比较长度
|
||||
images = Arrays.stream(images).sorted((f1, f2) -> {
|
||||
if(f1.getName().length() == f2.getName().length())
|
||||
return f1.compareTo(f2);
|
||||
else
|
||||
return f1.getName().length() - f2.getName().length();
|
||||
}).toArray(File[]::new);
|
||||
|
||||
//创建文件夹
|
||||
File file = new File(storagePath + galleryTask.getName());
|
||||
if(file.isDirectory() || file.mkdirs()){
|
||||
log.info(galleryTask.getName() + "文件夹创建成功");
|
||||
}else{
|
||||
log.error(galleryTask.getName() + "文件夹创建失败");
|
||||
continue;
|
||||
}
|
||||
|
||||
//thumbnail
|
||||
IMOperation operation = new IMOperation();
|
||||
operation.addImage(images[0].getAbsolutePath());
|
||||
operation.resize(500, 500);
|
||||
operation.format("webp");
|
||||
operation.addImage(storagePath + galleryTask.getName() + "/thumbnail.webp");
|
||||
try{
|
||||
log.info("文件" + images[0].getName() + ",转换为thumbnail.webp");
|
||||
convertCmd.run(operation);
|
||||
}catch (IOException | IM4JavaException | InterruptedException e){
|
||||
log.error("创建" + galleryTask.getName() + "缩略图失败");
|
||||
galleryTask.setStatus(ErrorCode.COMPRESS_ERROR);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(galleryTask.getType() == GalleryTask.DOWNLOAD_ALL || galleryTask.getType() == GalleryTask.DOWNLOAD_PREVIEW)
|
||||
for (File image : images) {
|
||||
log.info("文件" + image.getName() + ",转换为webp");
|
||||
operation = new IMOperation();
|
||||
operation.addImage(image.getAbsolutePath());
|
||||
operation.format("webp");
|
||||
operation.addImage(storagePath + galleryTask.getName() + "/" + image.getName().replace(".png", ".webp").replace(".jpg", ".webp"));
|
||||
|
||||
try {
|
||||
convertCmd.run(operation);
|
||||
} catch (IOException | InterruptedException | IM4JavaException e) {
|
||||
log.error("文件" + image.getName() + "转换失败");
|
||||
galleryTask.setStatus(ErrorCode.COMPRESS_ERROR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(galleryTask.getType() == GalleryTask.DOWNLOAD_ALL || galleryTask.getType() == GalleryTask.DOWNLOAD_SOURCE)
|
||||
ZipUtil.zip(galleryTask.getPath(), storagePath + galleryTask.getName() + "/" + galleryTask.getName() + ".zip");
|
||||
FileUtil.del(galleryTask.getPath());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
158
src/main/java/lion/storageNode.java
Normal file
158
src/main/java/lion/storageNode.java
Normal file
@ -0,0 +1,158 @@
|
||||
package lion;
|
||||
|
||||
import lion.Domain.GalleryTask;
|
||||
import lion.Message.*;
|
||||
import lion.Service.DeleteService;
|
||||
import lion.Service.DeliveryService;
|
||||
import lion.Service.DownloadCheckService;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import lombok.extern.log4j.Log4j;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.ListIterator;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@Log4j
|
||||
public class storageNode {
|
||||
|
||||
ChannelFuture channelFuture;
|
||||
|
||||
Channel server;
|
||||
|
||||
DownloadCheckService downloadCheckService;
|
||||
|
||||
ArrayList<GalleryTask> queue;
|
||||
|
||||
ScheduledExecutorService checkThreadPool;
|
||||
|
||||
|
||||
ReentrantLock lock;
|
||||
|
||||
public static String storagePath = "/root/gallery/gallery/";
|
||||
|
||||
public storageNode(){
|
||||
queue = new ArrayList<>(0);
|
||||
lock = new ReentrantLock();
|
||||
channelFuture = new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(new NioEventLoopGroup())
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(100000000, 1, 4));
|
||||
channel.pipeline().addLast(new MessageCodec());
|
||||
channel.pipeline().addLast(new LoggingHandler());
|
||||
channel.pipeline().addLast(new MyChannelInboundHandlerAdapter(queue));
|
||||
|
||||
}
|
||||
}).option(ChannelOption.SO_BACKLOG, 128)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
.bind(26321);
|
||||
log.info("listen port:8080");
|
||||
downloadCheckService = new DownloadCheckService(queue);
|
||||
checkThreadPool = Executors.newScheduledThreadPool(1);
|
||||
checkThreadPool.scheduleAtFixedRate(this::mainThread, 5, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void mainThread(){
|
||||
//检查
|
||||
if(!downloadCheckService.downloadCheck())
|
||||
return;
|
||||
//发送
|
||||
DownloadStatusMessage downloadStatusMessage = new DownloadStatusMessage();
|
||||
downloadStatusMessage.setGalleryTasks(queue.toArray(GalleryTask[]::new));
|
||||
server.writeAndFlush(downloadStatusMessage);
|
||||
|
||||
ListIterator<GalleryTask> listIterator = queue.listIterator();
|
||||
lock.lock();
|
||||
while (listIterator.hasNext()){
|
||||
GalleryTask galleryTask = listIterator.next();
|
||||
if(galleryTask.is_download_complete())
|
||||
listIterator.remove();
|
||||
}
|
||||
lock.unlock();
|
||||
log.info("任务状态发送完成");
|
||||
}
|
||||
|
||||
class MyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter{
|
||||
ArrayList<GalleryTask> queue;
|
||||
|
||||
public MyChannelInboundHandlerAdapter(ArrayList<GalleryTask> queue) {
|
||||
super();
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
System.out.println(msg);
|
||||
|
||||
if(msg instanceof IdentityMessage) {
|
||||
server = ctx.channel();
|
||||
log.info("server 上线");
|
||||
//提交下载
|
||||
}else if(msg instanceof DownloadPostMessage dpm){
|
||||
lock.lock();
|
||||
queue.add(dpm.getGalleryTask());
|
||||
lock.unlock();
|
||||
ctx.writeAndFlush(new ResponseMessage(dpm.messageId, (byte) 0));
|
||||
|
||||
//删除本子 全部/预览/源文件
|
||||
}else if(msg instanceof DeleteGalleryMessage deleteGalleryMessage){
|
||||
byte result = switch (deleteGalleryMessage.getDeleteType()){
|
||||
case DeleteGalleryMessage.DELETE_ALL ->
|
||||
DeleteService.deleteAll(storagePath + deleteGalleryMessage.getGalleryName());
|
||||
case DeleteGalleryMessage.DELETE_PREVIEW ->
|
||||
DeleteService.deletePreview(storagePath + deleteGalleryMessage.getGalleryName());
|
||||
case DeleteGalleryMessage.DELETE_SOURCE ->
|
||||
DeleteService.deleteSource(storagePath + deleteGalleryMessage.getGalleryName());
|
||||
default -> -1;
|
||||
};
|
||||
ResponseMessage responseMessage = new ResponseMessage(deleteGalleryMessage.messageId, result);
|
||||
ctx.writeAndFlush(responseMessage);
|
||||
|
||||
//请求预览
|
||||
}else if(msg instanceof GalleryRequestMessage grm){
|
||||
byte result = DeliveryService.deliveryPreview(grm.getGalleryName(), grm.getPage(), grm.getPort());
|
||||
|
||||
ResponseMessage responseMessage = new ResponseMessage();
|
||||
responseMessage.messageId = grm.messageId;
|
||||
responseMessage.setResult(result);
|
||||
ctx.writeAndFlush(responseMessage);
|
||||
|
||||
//更新本子 删除原有文件,然后加入到队列
|
||||
}else if(msg instanceof UpdateGalleryMessage ugm){
|
||||
FileUtil.del(storagePath + ugm.getGalleryTask().getName());
|
||||
lock.lock();
|
||||
queue.add(ugm.getGalleryTask());
|
||||
lock.unlock();
|
||||
ctx.writeAndFlush(new ResponseMessage(ugm.messageId, (byte) 0));
|
||||
|
||||
}else if(msg instanceof GalleryPageQueryMessage gpqm){
|
||||
byte result = DeliveryService.pageQuery(gpqm);
|
||||
gpqm.setResult(result);
|
||||
ctx.writeAndFlush(gpqm);
|
||||
}
|
||||
|
||||
//修复预览
|
||||
|
||||
//重新生成压缩包
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
||||
if(ctx.channel().equals(server)) {
|
||||
log.info("server 下线");
|
||||
server = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user