From 9155cb27a669ec13ab04cd18326d0ec19daa2072 Mon Sep 17 00:00:00 2001 From: chuzhongzai Date: Fri, 8 Dec 2023 13:25:39 +0800 Subject: [PATCH] first commit --- .gitignore | 33 + pom.xml | 106 ++ ...ScalableNetworkStorageSiteApplication.java | 28 + .../WebComponentConfiguration.java | 30 + .../lion/snss/controller/FileController.java | 32 + .../lion/snss/controller/IndexController.java | 19 + .../snss/dao/CustomConfigurationMapper.java | 31 + .../com/lion/snss/dao/ShareFileMapper.java | 59 + .../lion/snss/interceptor/Interceptor.java | 44 + .../lion/snss/message/AbstractMessage.java | 32 + .../lion/snss/message/AdjustShareMessage.java | 14 + .../lion/snss/message/CancelShareMessage.java | 12 + .../snss/message/CompressFileMessage.java | 15 + .../com/lion/snss/message/ConfigMessage.java | 31 + .../com/lion/snss/message/ConnectMessage.java | 10 + .../snss/message/DynamicConfigMessage.java | 33 + .../lion/snss/message/ExtractFileMessage.java | 15 + .../lion/snss/message/FileOperateMessage.java | 22 + .../lion/snss/message/FileQueryMessage.java | 13 + .../snss/message/FileResponseMessage.java | 15 + .../com/lion/snss/message/MessageCodec.java | 64 + .../snss/message/MoveFileRequestMessage.java | 29 + .../snss/message/MoveFileResponseMessage.java | 13 + .../com/lion/snss/message/PairMessage.java | 21 + .../lion/snss/message/PairResultMessage.java | 17 + .../lion/snss/message/ResponseMessage.java | 69 + .../lion/snss/message/ShareFileMessage.java | 15 + .../snss/message/ShareFileQueryMessage.java | 18 + .../com/lion/snss/message/StatusMessage.java | 25 + .../lion/snss/message/TaskCancelMessage.java | 11 + .../lion/snss/message/TaskStatusMessage.java | 14 + .../com/lion/snss/message/UnPairMessage.java | 10 + .../java/com/lion/snss/message/lombok.config | 2 + .../java/com/lion/snss/pojo/FileNode.java | 25 + .../java/com/lion/snss/pojo/ShareFile.java | 23 + .../snss/pojo/ShareFileDownloadRecord.java | 11 + src/main/java/com/lion/snss/pojo/Task.java | 119 ++ src/main/java/com/lion/snss/pojo/User.java | 14 + .../service/CommunicateToMainService.java | 1247 +++++++++++++++++ .../service/CommunicateToSiteService.java | 136 ++ .../com/lion/snss/service/FileService.java | 201 +++ .../com/lion/snss/service/TaskService.java | 113 ++ .../java/com/lion/snss/util/CustomUtil.java | 72 + src/main/java/com/lion/snss/util/IoUtil.java | 270 ++++ .../java/com/lion/snss/util/Response.java | 87 ++ src/main/resources/application.yaml | 26 + src/main/resources/snss-empty.db | Bin 0 -> 36864 bytes src/main/resources/snss.db | Bin 0 -> 36864 bytes 48 files changed, 3246 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/lion/snss/ScalableNetworkStorageSiteApplication.java create mode 100644 src/main/java/com/lion/snss/configuration/WebComponentConfiguration.java create mode 100644 src/main/java/com/lion/snss/controller/FileController.java create mode 100644 src/main/java/com/lion/snss/controller/IndexController.java create mode 100644 src/main/java/com/lion/snss/dao/CustomConfigurationMapper.java create mode 100644 src/main/java/com/lion/snss/dao/ShareFileMapper.java create mode 100644 src/main/java/com/lion/snss/interceptor/Interceptor.java create mode 100644 src/main/java/com/lion/snss/message/AbstractMessage.java create mode 100644 src/main/java/com/lion/snss/message/AdjustShareMessage.java create mode 100644 src/main/java/com/lion/snss/message/CancelShareMessage.java create mode 100644 src/main/java/com/lion/snss/message/CompressFileMessage.java create mode 100644 src/main/java/com/lion/snss/message/ConfigMessage.java create mode 100644 src/main/java/com/lion/snss/message/ConnectMessage.java create mode 100644 src/main/java/com/lion/snss/message/DynamicConfigMessage.java create mode 100644 src/main/java/com/lion/snss/message/ExtractFileMessage.java create mode 100644 src/main/java/com/lion/snss/message/FileOperateMessage.java create mode 100644 src/main/java/com/lion/snss/message/FileQueryMessage.java create mode 100644 src/main/java/com/lion/snss/message/FileResponseMessage.java create mode 100644 src/main/java/com/lion/snss/message/MessageCodec.java create mode 100644 src/main/java/com/lion/snss/message/MoveFileRequestMessage.java create mode 100644 src/main/java/com/lion/snss/message/MoveFileResponseMessage.java create mode 100644 src/main/java/com/lion/snss/message/PairMessage.java create mode 100644 src/main/java/com/lion/snss/message/PairResultMessage.java create mode 100644 src/main/java/com/lion/snss/message/ResponseMessage.java create mode 100644 src/main/java/com/lion/snss/message/ShareFileMessage.java create mode 100644 src/main/java/com/lion/snss/message/ShareFileQueryMessage.java create mode 100644 src/main/java/com/lion/snss/message/StatusMessage.java create mode 100644 src/main/java/com/lion/snss/message/TaskCancelMessage.java create mode 100644 src/main/java/com/lion/snss/message/TaskStatusMessage.java create mode 100644 src/main/java/com/lion/snss/message/UnPairMessage.java create mode 100644 src/main/java/com/lion/snss/message/lombok.config create mode 100644 src/main/java/com/lion/snss/pojo/FileNode.java create mode 100644 src/main/java/com/lion/snss/pojo/ShareFile.java create mode 100644 src/main/java/com/lion/snss/pojo/ShareFileDownloadRecord.java create mode 100644 src/main/java/com/lion/snss/pojo/Task.java create mode 100644 src/main/java/com/lion/snss/pojo/User.java create mode 100644 src/main/java/com/lion/snss/service/CommunicateToMainService.java create mode 100644 src/main/java/com/lion/snss/service/CommunicateToSiteService.java create mode 100644 src/main/java/com/lion/snss/service/FileService.java create mode 100644 src/main/java/com/lion/snss/service/TaskService.java create mode 100644 src/main/java/com/lion/snss/util/CustomUtil.java create mode 100644 src/main/java/com/lion/snss/util/IoUtil.java create mode 100644 src/main/java/com/lion/snss/util/Response.java create mode 100644 src/main/resources/application.yaml create mode 100644 src/main/resources/snss-empty.db create mode 100644 src/main/resources/snss.db diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e50161b --- /dev/null +++ b/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + com.lion + snss + 0.0.1-SNAPSHOT + scalable-network-storage-site + scalable-network-storage-site + + 17 + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-websocket + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.2 + + + + org.xerial + sqlite-jdbc + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + 1.18.30 + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.mybatis.spring.boot + mybatis-spring-boot-starter-test + 3.0.2 + test + + + + cn.hutool + hutool-all + 5.8.20 + + + + com.github.oshi + oshi-core + 6.4.5 + + + + io.netty + netty-all + + + + org.apache.commons + commons-compress + 1.21 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/com/lion/snss/ScalableNetworkStorageSiteApplication.java b/src/main/java/com/lion/snss/ScalableNetworkStorageSiteApplication.java new file mode 100644 index 0000000..021024f --- /dev/null +++ b/src/main/java/com/lion/snss/ScalableNetworkStorageSiteApplication.java @@ -0,0 +1,28 @@ +package com.lion.snss; + +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +@SpringBootApplication +public class ScalableNetworkStorageSiteApplication { + + private static ConfigurableApplicationContext context; + + public static void main(String[] args) { + context = SpringApplication.run(ScalableNetworkStorageSiteApplication.class, args); + } + + public static void restart() { + ApplicationArguments args = context.getBean(ApplicationArguments.class); + + Thread thread = new Thread(() -> { + context.close(); + context = SpringApplication.run(ScalableNetworkStorageSiteApplication.class, args.getSourceArgs()); + }); + + thread.setDaemon(false); + thread.start(); + } +} diff --git a/src/main/java/com/lion/snss/configuration/WebComponentConfiguration.java b/src/main/java/com/lion/snss/configuration/WebComponentConfiguration.java new file mode 100644 index 0000000..d8e1697 --- /dev/null +++ b/src/main/java/com/lion/snss/configuration/WebComponentConfiguration.java @@ -0,0 +1,30 @@ +package com.lion.snss.configuration; + +import com.lion.snss.interceptor.Interceptor; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebComponentConfiguration implements WebMvcConfigurer { + + @Resource + Interceptor interceptor; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowCredentials(true) + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .exposedHeaders("*"); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(interceptor).excludePathPatterns("/getFileByShareCode/**"); + } +} diff --git a/src/main/java/com/lion/snss/controller/FileController.java b/src/main/java/com/lion/snss/controller/FileController.java new file mode 100644 index 0000000..18ab9cf --- /dev/null +++ b/src/main/java/com/lion/snss/controller/FileController.java @@ -0,0 +1,32 @@ +package com.lion.snss.controller; + +import com.lion.snss.service.FileService; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/file") +public class FileController { + + @Resource + FileService fileService; + + @GetMapping("/getFile/**") + public void getFile(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String path, String sessionId){ + fileService.getFile(httpRequest, httpResponse, path, sessionId); + } + + @PostMapping(path="/upload/**") + public String uploadFile(HttpServletRequest request, MultipartFile file, String sessionId){ + String url = request.getRequestURL().toString(); + String[] temp = url.split("/upload/"); + System.out.println("上传文件:" + file.getOriginalFilename()); + if(temp.length == 1) + return fileService.uploadFile("", file, sessionId); + else + return fileService.uploadFile(temp[1], file, sessionId); + } +} diff --git a/src/main/java/com/lion/snss/controller/IndexController.java b/src/main/java/com/lion/snss/controller/IndexController.java new file mode 100644 index 0000000..53fa942 --- /dev/null +++ b/src/main/java/com/lion/snss/controller/IndexController.java @@ -0,0 +1,19 @@ +package com.lion.snss.controller; + +import com.lion.snss.service.FileService; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class IndexController { + @Resource + FileService fileService; + + @GetMapping("/getFileByShareCode/**") + public void getFileByShareCode(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String shareCode){ + fileService.getFileByShareCode(httpRequest, httpResponse, shareCode); + } +} diff --git a/src/main/java/com/lion/snss/dao/CustomConfigurationMapper.java b/src/main/java/com/lion/snss/dao/CustomConfigurationMapper.java new file mode 100644 index 0000000..d8b90fc --- /dev/null +++ b/src/main/java/com/lion/snss/dao/CustomConfigurationMapper.java @@ -0,0 +1,31 @@ +package com.lion.snss.dao; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +@Mapper +public interface CustomConfigurationMapper { + String PATH = "path"; + String IP = "ip"; + + String DOMAIN = "domain"; + + String IS_AUTH = "is_auth"; + + String ID = "id"; + + String REVERSE_PROXY_PREFIX = "reverse_proxy_prefix"; + + String TOTAL_SPACE = "total_space"; + + String AVAILABLE_SPACE = "available_space"; + + + @Select("select value from custom_configuration where key=#{key}") + String selectValue(String key); + + @Update("update custom_configuration set value=#{value} where key=#{key}") + void updateValue(@Param("key") String key, @Param("value") String value); +} diff --git a/src/main/java/com/lion/snss/dao/ShareFileMapper.java b/src/main/java/com/lion/snss/dao/ShareFileMapper.java new file mode 100644 index 0000000..30cd4c2 --- /dev/null +++ b/src/main/java/com/lion/snss/dao/ShareFileMapper.java @@ -0,0 +1,59 @@ +package com.lion.snss.dao; + +import com.lion.snss.pojo.ShareFileDownloadRecord; +import com.lion.snss.pojo.ShareFile; +import org.apache.ibatis.annotations.*; + +import java.util.ArrayList; + +@Mapper +public interface ShareFileMapper { + + @Insert("insert into share_file (share_code, file_path, expire_time, available_count, total_count, sharer) " + + "values (#{shareCode}, #{filePath}, #{expireTime}, #{availableCount}, #{totalCount}, #{sharer})") + void insertShareFilePojo(ShareFile shareFile); + + @Select("select * from share_file where share_code=#{shareCode}") + ShareFile selectShareFileByShareCode(String shareCode); + + @Select("select * from share_file where file_path=#{filePath}") + ShareFile selectShareFileByFilePath(String filePath); + + @Select("select * from share_file where file_path like '%' || #{filePath} || '%'") + ArrayList selectShareFilesByFilePath(String filePath); + + @Select("select share_code from share_file where file_path=#{filePath}") + String selectShareCodeByFilePath(String filePath); + + + @Delete("delete from share_file where share_code=#{shareCode}") + void deleteShareFile(String shareCode); + + @Delete("delete from share_file") + void deleteAllShareFile(); + + @Select("select * from share_file") + ShareFile[] selectAllShareFile(); + + @Select("select * from share_file where sharer=#{userid}") + ShareFile[] selectAllShareFileByUserid(int userid); + + @Update("update share_file set expire_time=#{expireTime}, available_count=#{availableCount}, total_count=#{totalCount} " + + "where share_code = #{shareCode}") + void updateShareFile(ShareFile shareFile); + + @Insert("insert into share_file_download_record values (#{shareCode}, #{ip}, #{time}, #{ua})") + void insertShareFileRecord(@Param("shareCode") String shareCode, @Param("ip") String ip, @Param("time") long time, @Param("ua") String ua); + + @Select("select count(*) from share_file_download_record where share_code=#{shareCode} and ip=#{ip}") + int selectShareFileRecordAmount(@Param("shareCode") String shareCode, @Param("ip") String ip); + + @Select("select * from share_file_download_record where share_code=#{shareCode}") + ShareFileDownloadRecord[] selectDownloadRecord(@Param("shareCode") String shareCode); + + @Delete("delete from share_file_download_record where share_code=#{shareCode}") + void deleteShareFileRecord(String shareCode); + + @Delete("delete from share_file_download_record") + void deleteAllShareFileRecord(); +} diff --git a/src/main/java/com/lion/snss/interceptor/Interceptor.java b/src/main/java/com/lion/snss/interceptor/Interceptor.java new file mode 100644 index 0000000..81c031e --- /dev/null +++ b/src/main/java/com/lion/snss/interceptor/Interceptor.java @@ -0,0 +1,44 @@ +package com.lion.snss.interceptor; + +import com.lion.snss.pojo.User; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class Interceptor implements HandlerInterceptor { + + public HashMap sessionId2user; + public Interceptor(){ + sessionId2user = new HashMap<>(); + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){ + String sessionId = request.getParameter("sessionId"); + return sessionId != null && sessionId2user.containsKey(sessionId); + } + + public void updateUsers(HashMap users){ + this.sessionId2user.clear(); + this.sessionId2user.putAll(users); + } + + public void addUser(Map.Entry entry){ + sessionId2user.put(entry.getKey(), entry.getValue()); + } + + public User removeUser(String sessionId){ + return sessionId2user.remove(sessionId); + } + + public void insertAdminUser(String sessionId){ + User user = new User(); + user.setId(1); + sessionId2user.put(sessionId, user); + } +} diff --git a/src/main/java/com/lion/snss/message/AbstractMessage.java b/src/main/java/com/lion/snss/message/AbstractMessage.java new file mode 100644 index 0000000..7af2774 --- /dev/null +++ b/src/main/java/com/lion/snss/message/AbstractMessage.java @@ -0,0 +1,32 @@ +package com.lion.snss.message; + +import lombok.Data; + + +@Data +public class AbstractMessage { + public static final int FILE_QUERY_MESSAGE = 1; + public static final int FILE_RESPONSE_MESSAGE = 2; + public static final int STATUS_MESSAGE = 10; + public static final int MOVE_FILE_REQUEST_MESSAGE = 15; + public static final int MOVE_FILE_RESPONSE_MESSAGE = 16; + public static final int PAIR_MESSAGE = 20; + public static final int PAIR_RESULT_MESSAGE = 21; + public static final int UN_PAIR_MESSAGE = 22; + public static final int DYNAMIC_CONFIG_MESSAGE = 30; + public static final int RESPONSE_MESSAGE = 40; + public static final int TASK_STATUS_MESSAGE = 50; + public static final int TASK_CANCEL_MESSAGE = 51; + public static final int COMPRESS_FILE_MESSAGE = 60; + public static final int FILE_OPERATE_MESSAGE = 61; + public static final int EXTRACT_FILE_MESSAGE = 62; + public static final int SHARE_FILE_MESSAGE = 70; + public static final int ADJUST_SHARE_MESSAGE = 71; + public static final int CANCEL_SHARE_MESSAGE = 72; + public static final int SHARE_FILE_QUERY_MESSAGE = 73; + public static final int CONFIG_MESSAGE = 80; + public static final int CONNECT_MESSAGE = 90; + + public int messageType; + public int messageId; +} diff --git a/src/main/java/com/lion/snss/message/AdjustShareMessage.java b/src/main/java/com/lion/snss/message/AdjustShareMessage.java new file mode 100644 index 0000000..db699c7 --- /dev/null +++ b/src/main/java/com/lion/snss/message/AdjustShareMessage.java @@ -0,0 +1,14 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class AdjustShareMessage extends AbstractMessage{ + { + messageType = ADJUST_SHARE_MESSAGE; + } + String shareCode; + Integer time; + Integer count; + int userid; +} diff --git a/src/main/java/com/lion/snss/message/CancelShareMessage.java b/src/main/java/com/lion/snss/message/CancelShareMessage.java new file mode 100644 index 0000000..a69edfb --- /dev/null +++ b/src/main/java/com/lion/snss/message/CancelShareMessage.java @@ -0,0 +1,12 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class CancelShareMessage extends AbstractMessage{ + { + messageType = CANCEL_SHARE_MESSAGE; + } + String shareCode; + int userid; +} diff --git a/src/main/java/com/lion/snss/message/CompressFileMessage.java b/src/main/java/com/lion/snss/message/CompressFileMessage.java new file mode 100644 index 0000000..2c651bc --- /dev/null +++ b/src/main/java/com/lion/snss/message/CompressFileMessage.java @@ -0,0 +1,15 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class CompressFileMessage extends AbstractMessage{ + { + messageType = COMPRESS_FILE_MESSAGE; + } + String targetPath; + String relativePath; + String[] paths; + int taskId; + int userid; +} diff --git a/src/main/java/com/lion/snss/message/ConfigMessage.java b/src/main/java/com/lion/snss/message/ConfigMessage.java new file mode 100644 index 0000000..d2eef79 --- /dev/null +++ b/src/main/java/com/lion/snss/message/ConfigMessage.java @@ -0,0 +1,31 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class ConfigMessage extends AbstractMessage{ + { + messageType = CONFIG_MESSAGE; + } + + public final static byte MOVE_SITE_STORAGE_PATH = 1; + public final static byte MOVE_USER_STORAGE_PATH = 2; + public final static byte VERIFY_USER_SPACE = 3; + + int operate; + String oldPath; + String newPath; + String path; + + public void moveSitePath(String oldPath, String newPath){ + this.oldPath = oldPath; + this.newPath = newPath; + operate = MOVE_SITE_STORAGE_PATH; + } + + public void moveUserPath(String oldPath, String newPath){ + this.oldPath = oldPath; + this.newPath = newPath; + operate = MOVE_USER_STORAGE_PATH; + } +} diff --git a/src/main/java/com/lion/snss/message/ConnectMessage.java b/src/main/java/com/lion/snss/message/ConnectMessage.java new file mode 100644 index 0000000..139dbf8 --- /dev/null +++ b/src/main/java/com/lion/snss/message/ConnectMessage.java @@ -0,0 +1,10 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class ConnectMessage extends AbstractMessage{ + { + messageType = CONNECT_MESSAGE; + } +} diff --git a/src/main/java/com/lion/snss/message/DynamicConfigMessage.java b/src/main/java/com/lion/snss/message/DynamicConfigMessage.java new file mode 100644 index 0000000..c2c9d72 --- /dev/null +++ b/src/main/java/com/lion/snss/message/DynamicConfigMessage.java @@ -0,0 +1,33 @@ +package com.lion.snss.message; + +import com.lion.snss.pojo.User; +import lombok.Data; + +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; + +@Data +public class DynamicConfigMessage extends AbstractMessage{ + { + messageType = DYNAMIC_CONFIG_MESSAGE; + } + public static final byte ALL = 0; + public static final byte ADD_IP = 1; + public static final byte REMOVE_IP = 2; + public static final byte ADD_USER = 3; + public static final byte REMOVE_USER = 4; + public static final byte UPDATE_USER_STORAGE = 5; + + byte operate; + Map.Entry ip; + Map.Entry user; + + public void updateUserStorage(User user){ + this.user = new AbstractMap.SimpleEntry<>(null, user); + operate = UPDATE_USER_STORAGE; + } + + HashMap ips; + HashMap users; +} diff --git a/src/main/java/com/lion/snss/message/ExtractFileMessage.java b/src/main/java/com/lion/snss/message/ExtractFileMessage.java new file mode 100644 index 0000000..f4f9b4a --- /dev/null +++ b/src/main/java/com/lion/snss/message/ExtractFileMessage.java @@ -0,0 +1,15 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class ExtractFileMessage extends AbstractMessage{ + { + messageType = EXTRACT_FILE_MESSAGE; + } + String relativePath; + String sourcePath; + String targetPath; + int taskId; + int userid; +} diff --git a/src/main/java/com/lion/snss/message/FileOperateMessage.java b/src/main/java/com/lion/snss/message/FileOperateMessage.java new file mode 100644 index 0000000..57ec311 --- /dev/null +++ b/src/main/java/com/lion/snss/message/FileOperateMessage.java @@ -0,0 +1,22 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class FileOperateMessage extends AbstractMessage{ + { + messageType = FILE_OPERATE_MESSAGE; + } + + //新建 + //删除 + //重命名 + public final static int CREATE = 1; + public final static int DELETE = 2; + public final static int RENAME = 3; + int operate; + int userid; + String[] paths; + String name; + String path; +} diff --git a/src/main/java/com/lion/snss/message/FileQueryMessage.java b/src/main/java/com/lion/snss/message/FileQueryMessage.java new file mode 100644 index 0000000..1bca79b --- /dev/null +++ b/src/main/java/com/lion/snss/message/FileQueryMessage.java @@ -0,0 +1,13 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class FileQueryMessage extends AbstractMessage{ + { + messageType = FILE_QUERY_MESSAGE; + } + String path; + String relativePath; //相对路径,用于去除多余的路径 + int userid; +} diff --git a/src/main/java/com/lion/snss/message/FileResponseMessage.java b/src/main/java/com/lion/snss/message/FileResponseMessage.java new file mode 100644 index 0000000..c876447 --- /dev/null +++ b/src/main/java/com/lion/snss/message/FileResponseMessage.java @@ -0,0 +1,15 @@ +package com.lion.snss.message; + +import com.lion.snss.pojo.FileNode; +import lombok.Data; + +import java.util.List; + +@Data +public class FileResponseMessage extends AbstractMessage { + { + messageType = FILE_RESPONSE_MESSAGE; + } + List files; + String data; +} diff --git a/src/main/java/com/lion/snss/message/MessageCodec.java b/src/main/java/com/lion/snss/message/MessageCodec.java new file mode 100644 index 0000000..121e340 --- /dev/null +++ b/src/main/java/com/lion/snss/message/MessageCodec.java @@ -0,0 +1,64 @@ +package com.lion.snss.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.slf4j.Slf4j; + + +import java.util.List; + +@Slf4j +public class MessageCodec extends ByteToMessageCodec { + ObjectMapper objectMapper; + + + public MessageCodec(){ + objectMapper = new ObjectMapper(); + } + + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, AbstractMessage abstractMessage, ByteBuf byteBuf) throws Exception { + byte[] bytes = objectMapper.writeValueAsBytes(abstractMessage); + + byteBuf.writeInt(abstractMessage.messageType); + byteBuf.writeInt(bytes.length); + byteBuf.writeBytes(bytes); + } + + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { + int type = byteBuf.readInt(); + int length = byteBuf.readInt(); + AbstractMessage abstractMessage; + byte[] bytes = new byte[length]; + byteBuf.readBytes(bytes); + abstractMessage = switch (type){ + case AbstractMessage.FILE_QUERY_MESSAGE -> objectMapper.readValue(bytes, FileQueryMessage.class); + case AbstractMessage.FILE_RESPONSE_MESSAGE -> objectMapper.readValue(bytes, FileResponseMessage.class); + case AbstractMessage.STATUS_MESSAGE -> objectMapper.readValue(bytes, StatusMessage.class); + case AbstractMessage.MOVE_FILE_REQUEST_MESSAGE -> objectMapper.readValue(bytes, MoveFileRequestMessage.class); + case AbstractMessage.MOVE_FILE_RESPONSE_MESSAGE -> objectMapper.readValue(bytes, MoveFileResponseMessage.class); + case AbstractMessage.PAIR_MESSAGE -> objectMapper.readValue(bytes, PairMessage.class); + case AbstractMessage.PAIR_RESULT_MESSAGE -> objectMapper.readValue(bytes, PairResultMessage.class); + case AbstractMessage.UN_PAIR_MESSAGE -> objectMapper.readValue(bytes, UnPairMessage.class); + case AbstractMessage.DYNAMIC_CONFIG_MESSAGE -> objectMapper.readValue(bytes, DynamicConfigMessage.class); + case AbstractMessage.RESPONSE_MESSAGE -> objectMapper.readValue(bytes, ResponseMessage.class); + case AbstractMessage.TASK_STATUS_MESSAGE -> objectMapper.readValue(bytes, TaskStatusMessage.class); + case AbstractMessage.TASK_CANCEL_MESSAGE -> objectMapper.readValue(bytes, TaskCancelMessage.class); + case AbstractMessage.COMPRESS_FILE_MESSAGE -> objectMapper.readValue(bytes, CompressFileMessage.class); + case AbstractMessage.FILE_OPERATE_MESSAGE -> objectMapper.readValue(bytes, FileOperateMessage.class); + case AbstractMessage.EXTRACT_FILE_MESSAGE -> objectMapper.readValue(bytes, ExtractFileMessage.class); + case AbstractMessage.SHARE_FILE_MESSAGE -> objectMapper.readValue(bytes, ShareFileMessage.class); + case AbstractMessage.ADJUST_SHARE_MESSAGE -> objectMapper.readValue(bytes, AdjustShareMessage.class); + case AbstractMessage.CANCEL_SHARE_MESSAGE -> objectMapper.readValue(bytes, CancelShareMessage.class); + case AbstractMessage.SHARE_FILE_QUERY_MESSAGE -> objectMapper.readValue(bytes, ShareFileQueryMessage.class); + case AbstractMessage.CONFIG_MESSAGE -> objectMapper.readValue(bytes, ConfigMessage.class); + case AbstractMessage.CONNECT_MESSAGE -> objectMapper.readValue(bytes, ConnectMessage.class); + default -> throw new IllegalStateException("Unexpected value: " + type); + }; + + list.add(abstractMessage); + } +} diff --git a/src/main/java/com/lion/snss/message/MoveFileRequestMessage.java b/src/main/java/com/lion/snss/message/MoveFileRequestMessage.java new file mode 100644 index 0000000..3d4ccb9 --- /dev/null +++ b/src/main/java/com/lion/snss/message/MoveFileRequestMessage.java @@ -0,0 +1,29 @@ +package com.lion.snss.message; + +import lombok.Data; + +/** + * 四种情况 + * 1.主服务器发送到子服务器 + * 2.子服务器发送到主服务器 + * 3.子服务器发送到子服务器 + * 4.子服务器发送到其他子服务器 + */ +@Data +public class MoveFileRequestMessage extends AbstractMessage{ + { + messageType = MOVE_FILE_REQUEST_MESSAGE; + } + int userid; + int sender; + int receiver; + int port; + //起始id, 如果是多个文件,则每个文件的任务id为起始id+index + int taskId; + boolean isDelete; + long fileSize; + String targetPath; + String[] fileNames; + String sourcePath; + String fileName; +} diff --git a/src/main/java/com/lion/snss/message/MoveFileResponseMessage.java b/src/main/java/com/lion/snss/message/MoveFileResponseMessage.java new file mode 100644 index 0000000..124b427 --- /dev/null +++ b/src/main/java/com/lion/snss/message/MoveFileResponseMessage.java @@ -0,0 +1,13 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class MoveFileResponseMessage extends AbstractMessage{ + { + messageType = MOVE_FILE_RESPONSE_MESSAGE; + } + boolean result; + String cause; + int taskId; +} diff --git a/src/main/java/com/lion/snss/message/PairMessage.java b/src/main/java/com/lion/snss/message/PairMessage.java new file mode 100644 index 0000000..fe6a438 --- /dev/null +++ b/src/main/java/com/lion/snss/message/PairMessage.java @@ -0,0 +1,21 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class PairMessage extends AbstractMessage{ + { + messageType = PAIR_MESSAGE; + } + String ip; + String domain; + String reverseProxyPrefix; + String hostname; + String system; + String cpuArch; + String cpuName; + int cpuCore; + int cpuThread; + String storagePath; + long availableSpace; +} diff --git a/src/main/java/com/lion/snss/message/PairResultMessage.java b/src/main/java/com/lion/snss/message/PairResultMessage.java new file mode 100644 index 0000000..07b4350 --- /dev/null +++ b/src/main/java/com/lion/snss/message/PairResultMessage.java @@ -0,0 +1,17 @@ +package com.lion.snss.message; + +import lombok.Data; + +import java.util.HashMap; + + +@Data +public class PairResultMessage extends AbstractMessage{ + { + messageType = PAIR_RESULT_MESSAGE; + } + int id; //分配的id -1为拒绝 + HashMap ips; //当前连接的所有服务器ip以及id + String sessionId; //管理员的sessionId + long totalSpace; //服务器总共可分配的容量 +} diff --git a/src/main/java/com/lion/snss/message/ResponseMessage.java b/src/main/java/com/lion/snss/message/ResponseMessage.java new file mode 100644 index 0000000..6c68c82 --- /dev/null +++ b/src/main/java/com/lion/snss/message/ResponseMessage.java @@ -0,0 +1,69 @@ +package com.lion.snss.message; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.HashMap; + +@Data +public class ResponseMessage extends AbstractMessage{ + { + messageType = RESPONSE_MESSAGE; + } + boolean result; + + @JsonInclude(JsonInclude.Include.NON_NULL) + HashMap data; + + public ResponseMessage(){ + data = new HashMap<>(); + } + + public ResponseMessage(int messageId){ + this.messageId = messageId; + data = new HashMap<>(); + } + + public void setDataAndResult(String data, boolean result){ + this.result = result; + if(result){ + this.data.put("data", data); + }else{ + this.data.put("cause", data); + } + } + + public void put(String key, String value){ + data.put(key, value); + } + + public void put(String key, Object o){ + data.put(key, String.valueOf(o)); + } + + public String get(String key){ + return data.get(key); + } + + public ResponseMessage success(String data) { + this.data.put("data", data); + result = true; + return this; + } + + public ResponseMessage success(Object o){ + success(String.valueOf(o)); + return this; + } + + public ResponseMessage success(){ + result = true; + return this; + } + + public ResponseMessage failure(String cause){ + this.data.put("cause", cause); + result = false; + return this; + } +} diff --git a/src/main/java/com/lion/snss/message/ShareFileMessage.java b/src/main/java/com/lion/snss/message/ShareFileMessage.java new file mode 100644 index 0000000..1c00708 --- /dev/null +++ b/src/main/java/com/lion/snss/message/ShareFileMessage.java @@ -0,0 +1,15 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class ShareFileMessage extends AbstractMessage{ + { + messageType = SHARE_FILE_MESSAGE; + } + String path; + Integer count; + Integer time; + String[] fileNames; + int userid; +} diff --git a/src/main/java/com/lion/snss/message/ShareFileQueryMessage.java b/src/main/java/com/lion/snss/message/ShareFileQueryMessage.java new file mode 100644 index 0000000..3546c9a --- /dev/null +++ b/src/main/java/com/lion/snss/message/ShareFileQueryMessage.java @@ -0,0 +1,18 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class ShareFileQueryMessage extends AbstractMessage{ + { + messageType = SHARE_FILE_QUERY_MESSAGE; + } + public final static byte QUERY_SHARE_FILE = 1; + public final static byte QUERY_SHARE_FILES = 2; + + byte operate; + int userid; + String username; + String relativePath; + String shareCode; +} diff --git a/src/main/java/com/lion/snss/message/StatusMessage.java b/src/main/java/com/lion/snss/message/StatusMessage.java new file mode 100644 index 0000000..add21df --- /dev/null +++ b/src/main/java/com/lion/snss/message/StatusMessage.java @@ -0,0 +1,25 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class StatusMessage extends AbstractMessage{ + { + messageType = STATUS_MESSAGE; + } + int id; + long usedMemory; + long totalMemory; + double usedMemoryPercentage; + long usedSpace; + long totalSpace; + double usedSpacePercentage; + double[] systemLoad; + double usedCpuPercentage; + long ioRead; + long ioWrite; + long networkReceive; + long networkSend; + long systemUpTime; + long systemBootTime; +} diff --git a/src/main/java/com/lion/snss/message/TaskCancelMessage.java b/src/main/java/com/lion/snss/message/TaskCancelMessage.java new file mode 100644 index 0000000..ce8ebe0 --- /dev/null +++ b/src/main/java/com/lion/snss/message/TaskCancelMessage.java @@ -0,0 +1,11 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class TaskCancelMessage extends AbstractMessage{ + { + messageType = TASK_CANCEL_MESSAGE; + } + int[] taskIds; +} diff --git a/src/main/java/com/lion/snss/message/TaskStatusMessage.java b/src/main/java/com/lion/snss/message/TaskStatusMessage.java new file mode 100644 index 0000000..1de9c4d --- /dev/null +++ b/src/main/java/com/lion/snss/message/TaskStatusMessage.java @@ -0,0 +1,14 @@ +package com.lion.snss.message; + +import com.lion.snss.pojo.Task; +import lombok.Data; + +import java.util.Set; + +@Data +public class TaskStatusMessage extends AbstractMessage{ + { + messageType = TASK_STATUS_MESSAGE; + } + Set tasks; +} diff --git a/src/main/java/com/lion/snss/message/UnPairMessage.java b/src/main/java/com/lion/snss/message/UnPairMessage.java new file mode 100644 index 0000000..536c7dc --- /dev/null +++ b/src/main/java/com/lion/snss/message/UnPairMessage.java @@ -0,0 +1,10 @@ +package com.lion.snss.message; + +import lombok.Data; + +@Data +public class UnPairMessage extends AbstractMessage{ + { + messageType = UN_PAIR_MESSAGE; + } +} diff --git a/src/main/java/com/lion/snss/message/lombok.config b/src/main/java/com/lion/snss/message/lombok.config new file mode 100644 index 0000000..8e37527 --- /dev/null +++ b/src/main/java/com/lion/snss/message/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling=true +lombok.equalsAndHashCode.callSuper=call \ No newline at end of file diff --git a/src/main/java/com/lion/snss/pojo/FileNode.java b/src/main/java/com/lion/snss/pojo/FileNode.java new file mode 100644 index 0000000..a7cccf9 --- /dev/null +++ b/src/main/java/com/lion/snss/pojo/FileNode.java @@ -0,0 +1,25 @@ +package com.lion.snss.pojo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.Date; + +@Data +public class FileNode { + public static String FOLDER = "FOLDER"; + public static String FILE = "FILE"; + String name; + String path; + String type; + long size; + long lastModify; + @JsonInclude(JsonInclude.Include.NON_NULL) + String shareCode; + @JsonInclude(JsonInclude.Include.NON_NULL) + Date expireTime; + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + Integer totalCount; + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + Integer availableCount; +} diff --git a/src/main/java/com/lion/snss/pojo/ShareFile.java b/src/main/java/com/lion/snss/pojo/ShareFile.java new file mode 100644 index 0000000..ab200a6 --- /dev/null +++ b/src/main/java/com/lion/snss/pojo/ShareFile.java @@ -0,0 +1,23 @@ +package com.lion.snss.pojo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@JsonInclude(JsonInclude.Include.NON_DEFAULT) +public class ShareFile { + String shareCode; //分享码 + String filePath; //文件路径 + Date expireTime; //过期时间 + int availableCount; //剩余可下载次数 + int totalCount; //总共下载次数 + int sharer; //分享者id + String username; //分享者 + + @JsonProperty("downloadRecords") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + ShareFileDownloadRecord[] shareFileDownloadRecords; +} diff --git a/src/main/java/com/lion/snss/pojo/ShareFileDownloadRecord.java b/src/main/java/com/lion/snss/pojo/ShareFileDownloadRecord.java new file mode 100644 index 0000000..7558348 --- /dev/null +++ b/src/main/java/com/lion/snss/pojo/ShareFileDownloadRecord.java @@ -0,0 +1,11 @@ +package com.lion.snss.pojo; + +import lombok.Data; + +@Data +public class ShareFileDownloadRecord { + String shareCode; + String ip; + long time; + String ua; +} diff --git a/src/main/java/com/lion/snss/pojo/Task.java b/src/main/java/com/lion/snss/pojo/Task.java new file mode 100644 index 0000000..00932f9 --- /dev/null +++ b/src/main/java/com/lion/snss/pojo/Task.java @@ -0,0 +1,119 @@ +package com.lion.snss.pojo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.concurrent.atomic.AtomicLong; + +//打包 传输 +@Data +@NoArgsConstructor +public class Task { + public static final String COMPRESS = "compress"; + public static final String TRANSFER = "transfer"; + public static final String EXTRACT = "extract"; + + int userid; //提交的用户 + int siteId; //进行任务的服务器id + String type; //compress || transfer + String status; //waiting proceeding success failure + boolean complete; //是否完成 + boolean local; //是否为本地 + + //转移 + String sourcePath; + String targetPath; + String filename; + boolean delete; + int receiver; + int sender; + + //打包 + String[] paths; + String relativePath; + + //转移 + public Task(int taskId, int userid, long total, boolean isDelete, String sourcePath, String targetPath, String filename, int receiver, int sender){ + this.taskId = taskId; + this.userid = userid; + this.siteId = sender; + + this.sourcePath = sourcePath; + this.targetPath = targetPath; + this.delete = isDelete; + this.receiver = receiver; + this.sender = sender; + this.filename = filename; + this.total = total; + + + local = false; + complete = false; + status = "waiting"; + type = TRANSFER; + } + + //打包 + public Task(int taskId, int userid, String filename, int siteId, long total, String targetPath, String[] paths, String relativePath){ + this.taskId = taskId; + this.userid = userid; + this.siteId = siteId; + + this.total = total; + this.targetPath = targetPath; + this.paths = paths; + this.relativePath = relativePath; + this.filename = filename; + + local = false; + complete = false; + status = "waiting"; + type = COMPRESS; + } + + //解包 + public Task(int taskId, int userid, String filename, int sideId, long total, String sourcePath, String targetPath, String relativePath){ + this.taskId = taskId; + this.userid = userid; + this.siteId = sideId; + + this.total = total; + this.sourcePath = sourcePath; + this.targetPath = targetPath; + this.relativePath = relativePath; + this.filename = filename; + + local = false; + complete = false; + status = "waiting"; + type = EXTRACT; + } + + @JsonIgnore + public boolean isSuccess(){ + return status.equals("success"); + } + + + @JsonInclude(JsonInclude.Include.NON_NULL) + String cause; //失败原因 + + int taskId; + double percentage; + long speed; + + @JsonIgnore + AtomicLong proceed; + + @JsonIgnore + AtomicLong last; + + long total; + + @JsonIgnore + public boolean isProceeding(){ + return status.equals("proceeding"); + } +} diff --git a/src/main/java/com/lion/snss/pojo/User.java b/src/main/java/com/lion/snss/pojo/User.java new file mode 100644 index 0000000..ae80e8f --- /dev/null +++ b/src/main/java/com/lion/snss/pojo/User.java @@ -0,0 +1,14 @@ +package com.lion.snss.pojo; + +import lombok.Data; + +@Data +public class User { + int id; + String username; + String passcode; //加盐md5 + long availableSpace; + long totalSpace; + String storagePath; //path + int siteId; +} diff --git a/src/main/java/com/lion/snss/service/CommunicateToMainService.java b/src/main/java/com/lion/snss/service/CommunicateToMainService.java new file mode 100644 index 0000000..ecc75ab --- /dev/null +++ b/src/main/java/com/lion/snss/service/CommunicateToMainService.java @@ -0,0 +1,1247 @@ +package com.lion.snss.service; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.RandomUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.lion.snss.ScalableNetworkStorageSiteApplication; +import com.lion.snss.dao.CustomConfigurationMapper; +import com.lion.snss.dao.ShareFileMapper; +import com.lion.snss.interceptor.Interceptor; +import com.lion.snss.message.*; +import com.lion.snss.pojo.*; +import com.lion.snss.util.CustomUtil; +import com.lion.snss.util.IoUtil; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.util.concurrent.DefaultPromise; +import io.netty.util.concurrent.Promise; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; +import org.springframework.stereotype.Service; + +import java.io.*; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.zip.CRC32; + +@Service +@Slf4j +public class CommunicateToMainService { + CustomConfigurationMapper customConfigurationMapper; + + int id; + + boolean isAuth; + + String storagePath; + + EventLoop eventLoop; + + @Resource + ShareFileMapper shareFileMapper; + + @Resource + TaskService taskService; + + ObjectMapper objectMapper = CustomUtil.objectMapper; + + Interceptor interceptor; + + HashMap userid2user; + + ExecutorService threadPool; + + HashMap> promises; + + HashMap ips; + + String ip; + + AtomicInteger messageId; + + ChannelFuture channelFuture; + + ScheduledExecutorService scheduledExecutorService; + + public CommunicateToMainService(CustomConfigurationMapper customConfigurationMapper, Interceptor interceptor) { + messageId = new AtomicInteger(); + promises = new HashMap<>(); + ips = new HashMap<>(); + threadPool = Executors.newFixedThreadPool(4); + scheduledExecutorService = Executors.newScheduledThreadPool(1); + this.customConfigurationMapper = customConfigurationMapper; + this.interceptor = interceptor; + userid2user = new HashMap<>(); + + eventLoop = new DefaultEventLoop(); + ip = customConfigurationMapper.selectValue(CustomConfigurationMapper.IP); + + //未设置ip,开始初始化 + if(ip == null) { + threadPool.submit(() -> initSnss(customConfigurationMapper)); + return; + } + + storagePath = customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH); + isAuth = Boolean.parseBoolean(customConfigurationMapper.selectValue(CustomConfigurationMapper.IS_AUTH)); + + if(connectToMain()) { + log.info("服务器连接成功"); + }else if(isAuth){ + log.info("服务器连接失败"); + id = Integer.parseInt(customConfigurationMapper.selectValue(CustomConfigurationMapper.ID)); + initMonitor(); + return; + }else{ + log.info("服务器连接失败,且未进行过配对"); + System.exit(0); + } + + if(!isAuth) + pair(); + else { + id = Integer.parseInt(customConfigurationMapper.selectValue(CustomConfigurationMapper.ID)); + ConnectMessage message = new ConnectMessage(); + channelFuture.channel().writeAndFlush(message); + } + + initScheduleThread(); + } + + public boolean connectToMain(){ + try { + channelFuture = new Bootstrap() + .channel(NioSocketChannel.class) + .group(new NioEventLoopGroup()) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel channel) { + channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(100000000, 4, 4)); + channel.pipeline().addLast(new MessageCodec()); + channel.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); + channel.pipeline().addLast(new MyChannelInboundHandlerAdapter()); + } + }).connect(ip, 7777).sync(); + return channelFuture != null; + } catch (Exception e) { + return false; + } + } + + public int getMessageId(){ + return messageId.incrementAndGet(); + } + + public Channel connectToSite(String ip){ + try {ChannelFuture site = new Bootstrap() + .channel(NioSocketChannel.class) + .group(new NioEventLoopGroup()) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel channel) { + channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(100000000, 4, 4)); + channel.pipeline().addLast(new MessageCodec()); + channel.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); + channel.pipeline().addLast(new MyChannelInboundHandlerAdapter()); + } + }).connect(ip, 9999).sync(); + return site.channel(); + }catch (InterruptedException e){ + log.error(e.getMessage()); + return null; + } + } + + public void initScheduleThread(){ + scheduledExecutorService.scheduleAtFixedRate(() -> { + if(channelFuture == null) + return; + channelFuture.channel().writeAndFlush(IoUtil.generateStatusMessage(id, storagePath)); + if(!taskService.getTasks().isEmpty()){ + TaskStatusMessage message = new TaskStatusMessage(); + message.setTasks(new HashSet<>(taskService.getTasks().values())); + channelFuture.channel().writeAndFlush(message); + taskService.removeComplete(); + } + }, 0, 1, TimeUnit.SECONDS); + } + + public void initMonitor(){ + log.info("等待唤醒"); + threadPool.submit(() -> { + try(ServerSocket serverSocket = new ServerSocket(9990)){ + while (true) { + Socket socket = serverSocket.accept(); + if(socket.getInetAddress().getHostAddress().equals(ip) || socket.getInetAddress().getHostAddress().equals("192.168.0.110")){ + connectToMain(); + initScheduleThread(); + ConnectMessage message = new ConnectMessage(); + channelFuture.channel().writeAndFlush(message); + break; + } + } + }catch (IOException e){ + log.error(e.getMessage()); + } + }); + } + + public static void initSnss(CustomConfigurationMapper customConfigurationMapper){ + Scanner scan = new Scanner(System.in); + System.out.println("初始化节点,请确保管理员已登录控制面板"); + System.out.print("请输入主服务器ip地址:"); + String ip; + while (true) { + ip = scan.nextLine(); + String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\." + + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." + + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." + + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$"; + if (ip.matches(regex)) + break; + else + System.out.println("ip地址不合法,请重新输入:"); + } + + System.out.print("请输入存储路径:"); + String storagePath; + while(true){ + storagePath = scan.nextLine(); + if(!new File(storagePath).exists()){ + System.out.println("存储路径不存在,请重新输入:"); + }else{ + break; + } + } + customConfigurationMapper.updateValue(CustomConfigurationMapper.IP, ip); + customConfigurationMapper.updateValue(CustomConfigurationMapper.PATH, storagePath); + System.out.println("初始化成功,请留意控制面板的配对信息"); + + ScalableNetworkStorageSiteApplication.restart(); + } + + public void pair(){ + log.info("服务器准备配对"); + String storagePath = customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH); + File file = new File(storagePath); + PairMessage pairMessage = IoUtil.generatePairMessage(); + pairMessage.setAvailableSpace(file.getFreeSpace()); + pairMessage.setMessageId(getMessageId()); + pairMessage.setStoragePath(storagePath); + + channelFuture.channel().writeAndFlush(pairMessage); + DefaultPromise promise = new DefaultPromise<>(eventLoop); + promises.put(pairMessage.getMessageId(), promise); + try { + promise.await(); + PairResultMessage pairResultMessage = (PairResultMessage) promise.get(); + if (pairResultMessage.getId() != -1) { + ips = pairResultMessage.getIps(); + id = pairResultMessage.getId(); + isAuth = true; + customConfigurationMapper.updateValue(CustomConfigurationMapper.IS_AUTH, "true"); + customConfigurationMapper.updateValue(CustomConfigurationMapper.ID, id + ""); + customConfigurationMapper.updateValue(CustomConfigurationMapper.TOTAL_SPACE, pairResultMessage.getTotalSpace() + ""); + customConfigurationMapper.updateValue(CustomConfigurationMapper.AVAILABLE_SPACE, pairResultMessage.getTotalSpace() + ""); + interceptor.insertAdminUser(pairResultMessage.getSessionId()); + log.info("服务器配对成功"); + } else { + log.info("服务器拒绝配对"); + System.exit(0); + } + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + public void sendMessageToMain(AbstractMessage abstractMessage){ + channelFuture.channel().writeAndFlush(abstractMessage); + } + + class MyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter{ + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + AbstractMessage abstractMessage = (AbstractMessage) msg; + switch (abstractMessage.messageType) { + case AbstractMessage.FILE_QUERY_MESSAGE -> { + FileQueryMessage fileQueryMessage = (FileQueryMessage) abstractMessage; + ctx.channel().writeAndFlush(getFiles(fileQueryMessage)); + } + case AbstractMessage.MOVE_FILE_REQUEST_MESSAGE -> threadPool.submit(() -> { + MoveFileRequestMessage moveFileRequestMessage = (MoveFileRequestMessage) abstractMessage; + if (moveFileRequestMessage.getSender() == id && moveFileRequestMessage.getReceiver() != id) + ctx.channel().writeAndFlush(submitSendFileTask(moveFileRequestMessage)); + else if (moveFileRequestMessage.getReceiver() == id && moveFileRequestMessage.getSender() == id) + ctx.channel().writeAndFlush(moveFiles(moveFileRequestMessage)); + else + receiveFile(ctx.channel(), moveFileRequestMessage); + }); + case AbstractMessage.RESPONSE_MESSAGE -> { + ResponseMessage response = (ResponseMessage) abstractMessage; + Promise promise = promises.remove(response.getMessageId()); + if (promise != null) + promise.setSuccess(response); + } + case AbstractMessage.TASK_CANCEL_MESSAGE -> { + TaskCancelMessage taskCancelMessage = (TaskCancelMessage) abstractMessage; + taskService.removeTasks(taskCancelMessage.getTaskIds()); + } + case AbstractMessage.FILE_OPERATE_MESSAGE -> { + FileOperateMessage fileOperateMessage = (FileOperateMessage) abstractMessage; + ResponseMessage response = switch (fileOperateMessage.getOperate()) { + case FileOperateMessage.CREATE -> createDirectory(fileOperateMessage); + case FileOperateMessage.DELETE -> deleteFile(fileOperateMessage); + case FileOperateMessage.RENAME -> renameFile(fileOperateMessage); + default -> throw new IllegalStateException("Unexpected value: " + fileOperateMessage.getOperate()); + }; + ctx.channel().writeAndFlush(response); + } + case AbstractMessage.COMPRESS_FILE_MESSAGE -> { + CompressFileMessage compressFileMessage = (CompressFileMessage) abstractMessage; + ctx.channel().writeAndFlush(submitCompressTask(compressFileMessage)); + } + case AbstractMessage.EXTRACT_FILE_MESSAGE -> { + ExtractFileMessage extractFileMessage = (ExtractFileMessage) abstractMessage; + ctx.channel().writeAndFlush(submitExtractTask(extractFileMessage)); + } + case AbstractMessage.SHARE_FILE_MESSAGE -> { + ShareFileMessage shareFileMessage = (ShareFileMessage) abstractMessage; + ctx.channel().writeAndFlush(shareFile(shareFileMessage)); + } + case AbstractMessage.ADJUST_SHARE_MESSAGE -> { + AdjustShareMessage adjustShareMessage = (AdjustShareMessage) abstractMessage; + ctx.channel().writeAndFlush(adjustShare(adjustShareMessage)); + } + case AbstractMessage.CANCEL_SHARE_MESSAGE -> { + CancelShareMessage cancelShareMessage = (CancelShareMessage) abstractMessage; + ctx.channel().writeAndFlush(cancelShare(cancelShareMessage)); + } + case AbstractMessage.SHARE_FILE_QUERY_MESSAGE -> { + ShareFileQueryMessage shareFileQueryMessage = (ShareFileQueryMessage) abstractMessage; + ctx.channel().writeAndFlush(queryShareFile(shareFileQueryMessage)); + } + case AbstractMessage.CONFIG_MESSAGE -> { + ConfigMessage configMessage = (ConfigMessage) abstractMessage; + ResponseMessage response = new ResponseMessage(configMessage.getMessageId()); + switch (configMessage.getOperate()){ + case ConfigMessage.MOVE_SITE_STORAGE_PATH -> { + //判断目标路径是否存在 + File newDirectory = new File(configMessage.getNewPath()); + File oldDirectory = new File(configMessage.getOldPath()); + + if(newDirectory.getAbsolutePath().contains(oldDirectory.getAbsolutePath())) + ctx.channel().writeAndFlush(response.failure("新的路径不能是之前路径的子路径")); + + if(newDirectory.exists()) { + ctx.channel().writeAndFlush(response.failure("目标路径已存在")); + break; + } + + //判断空间是否足够 + newDirectory.mkdirs(); + long totalSize = CustomUtil.calculateDirectorySize(oldDirectory.getAbsolutePath()); + if(totalSize > newDirectory.getFreeSpace()) { + ctx.channel().writeAndFlush(response.failure("目标路径可用空间小于当前文件夹总大小")); + break; + } + newDirectory.delete(); + + //移动文件 + oldDirectory.renameTo(newDirectory); + + //清除原分享码 + ArrayList shareFiles = shareFileMapper.selectShareFilesByFilePath(configMessage.getOldPath()); + for (ShareFile shareFile : shareFiles) { + shareFileMapper.deleteShareFile(shareFile.getShareCode()); + shareFileMapper.deleteShareFileRecord(shareFile.getShareCode()); + } + ctx.channel().writeAndFlush(response.success()); + } + case ConfigMessage.MOVE_USER_STORAGE_PATH -> { + //删除旧的分享码 + ArrayList shareFiles = shareFileMapper.selectShareFilesByFilePath(storagePath + configMessage.getOldPath()); + for (ShareFile shareFile : shareFiles) { + shareFileMapper.deleteShareFile(shareFile.getShareCode()); + shareFileMapper.deleteShareFileRecord(shareFile.getShareCode()); + } + + //转移旧的文件 + File oldDirectory = new File(storagePath + configMessage.getOldPath()); + File newDirectory = new File(storagePath + configMessage.getNewPath()); + if (newDirectory.exists()) { + ctx.channel().writeAndFlush(response.failure("更换存储路径错误,目标路径已存在")); + return; + } + + FileUtil.move(oldDirectory, newDirectory, false); + ctx.channel().writeAndFlush(response.success()); + } + case ConfigMessage.VERIFY_USER_SPACE -> { + long total = CustomUtil.calculateDirectorySize(storagePath + configMessage.getPath()); + response.put("total", total); + ctx.channel().writeAndFlush(response.success()); + } + } + } + case AbstractMessage.DYNAMIC_CONFIG_MESSAGE -> { + DynamicConfigMessage dynamicConfigMessage = (DynamicConfigMessage) abstractMessage; + ResponseMessage responseMessage = new ResponseMessage(dynamicConfigMessage.getMessageId()); + switch (dynamicConfigMessage.getOperate()){ + case DynamicConfigMessage.ALL -> { + interceptor.updateUsers(dynamicConfigMessage.getUsers()); + ips.putAll(dynamicConfigMessage.getIps()); + dynamicConfigMessage.getUsers().values().forEach((user -> userid2user.put(user.getId(), user))); + } + case DynamicConfigMessage.ADD_IP -> ips.put(dynamicConfigMessage.getIp().getKey(), dynamicConfigMessage.getIp().getValue()); + case DynamicConfigMessage.REMOVE_IP -> ips.remove(dynamicConfigMessage.getIp().getKey()); + case DynamicConfigMessage.ADD_USER -> { + interceptor.addUser(dynamicConfigMessage.getUser()); + userid2user.put(dynamicConfigMessage.getUser().getValue().getId(), dynamicConfigMessage.getUser().getValue()); + } + case DynamicConfigMessage.REMOVE_USER -> { + User user = interceptor.removeUser(dynamicConfigMessage.getUser().getKey()); + if(user != null) + userid2user.remove(user.getId()); + } + } + ctx.channel().writeAndFlush(responseMessage.success()); + } + case AbstractMessage.PAIR_RESULT_MESSAGE -> { + PairResultMessage pairResultMessage = (PairResultMessage) abstractMessage; + promises.remove(pairResultMessage.getMessageId()).setSuccess(pairResultMessage); + } + case AbstractMessage.UN_PAIR_MESSAGE -> { + customConfigurationMapper.updateValue(CustomConfigurationMapper.REVERSE_PROXY_PREFIX, null); + customConfigurationMapper.updateValue(CustomConfigurationMapper.DOMAIN, null); + customConfigurationMapper.updateValue(CustomConfigurationMapper.PATH, null); + customConfigurationMapper.updateValue(CustomConfigurationMapper.IP, null); + customConfigurationMapper.updateValue(CustomConfigurationMapper.IS_AUTH, "false"); + customConfigurationMapper.updateValue(CustomConfigurationMapper.ID, null); + shareFileMapper.deleteAllShareFile(); + shareFileMapper.deleteAllShareFileRecord(); + ResponseMessage response = new ResponseMessage(); + response.success("取消配对成功"); + log.info("服务器取消配对,程序退出"); + try{ + ctx.channel().writeAndFlush(response).sync(); + System.exit(0); + }catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) { + if(channelFuture != null && ctx.channel().equals(channelFuture.channel())) + initMonitor(); + } + + + public ResponseMessage submitSendFileTask(MoveFileRequestMessage moveFileRequestMessage){ + ResponseMessage response = new ResponseMessage(); + response.setMessageId(moveFileRequestMessage.getMessageId()); + int taskId = moveFileRequestMessage.getTaskId(); + for (String filename : moveFileRequestMessage.getFileNames()) + if(!new File(storagePath + moveFileRequestMessage.getSourcePath() + filename).exists()) + return response.failure(filename + "不存在, 提交转移任务失败"); + + for (String filename : moveFileRequestMessage.getFileNames()) { + Task task = new Task(taskId++, moveFileRequestMessage.getUserid(), new File(storagePath + moveFileRequestMessage.getSourcePath() + filename).length(), + moveFileRequestMessage.isDelete(), moveFileRequestMessage.getSourcePath(), + moveFileRequestMessage.getTargetPath(), filename, + moveFileRequestMessage.getReceiver(), id); + taskService.submitTask(task); + } + response.success("转移任务提交成功"); + + if(taskService.isThreadStop()) + threadPool.submit(() -> processTask(taskService.getWaitingTask())); + return response; + } + + public void processTask(Task task){ + if(task.getType().equals(Task.TRANSFER)) + sendFile(task); + else if(task.getType().equals(Task.COMPRESS)) + compressFiles(task); + else + extractFiles(task); + + if(taskService.getWaitingTask() != null) + threadPool.submit(() -> processTask(taskService.getWaitingTask())); + } + + public ResponseMessage getFiles(FileQueryMessage fileQueryMessage){ + ResponseMessage response = new ResponseMessage(fileQueryMessage.messageId); + List fileNodes = new ArrayList<>(); + File directory = new File(storagePath, fileQueryMessage.getPath()); + Calendar now = Calendar.getInstance(); + Calendar expireTime = Calendar.getInstance(); + + if(!directory.isDirectory()) + return response.failure("目标路径不是文件夹"); + + File[] files = directory.listFiles(); + if(files == null) + return response.failure("目标文件夹为空"); + + ArrayList shareFiles = shareFileMapper.selectShareFilesByFilePath(storagePath + fileQueryMessage.getPath()); + for (File file : files) { + FileNode fileNode = new FileNode(); + fileNode.setName(file.getName()); + fileNode.setPath(file.getAbsolutePath().replace(storagePath + fileQueryMessage.getRelativePath(), "")); + try { + BasicFileAttributes attributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class); + fileNode.setLastModify(attributes.lastModifiedTime().toMillis()); + }catch (IOException ignored){ + fileNode.setLastModify(0); + } + + if(file.isDirectory()){ + fileNode.setType(FileNode.FOLDER); + }else { + fileNode.setType(FileNode.FILE); + fileNode.setSize(file.length()); + + Iterator iterator = shareFiles.iterator(); + while(iterator.hasNext()){ + ShareFile shareFile = iterator.next(); + if(shareFile.getFilePath().equals(file.getAbsolutePath())){ + iterator.remove(); + if(shareFile.getExpireTime() != null) { + expireTime.setTime(shareFile.getExpireTime()); + if (now.after(expireTime)) { + shareFileMapper.deleteShareFile(shareFile.getShareCode()); + shareFileMapper.deleteShareFileRecord(shareFile.getShareCode()); + break; + } + } + + fileNode.setShareCode(shareFile.getShareCode()); + fileNode.setExpireTime(shareFile.getExpireTime()); + fileNode.setAvailableCount(shareFile.getAvailableCount()); + fileNode.setTotalCount(shareFile.getTotalCount()); + break; + } + } + } + fileNodes.add(fileNode); + } + + return response.success(objectMapper.valueToTree(fileNodes)); + } + + public void receiveFile(Channel channel, MoveFileRequestMessage moveFileRequestMessage){ + File targetFile = new File(storagePath + moveFileRequestMessage.getTargetPath(), moveFileRequestMessage.getFileName()); + ResponseMessage response = new ResponseMessage(moveFileRequestMessage.getMessageId()); + File[] roots = File.listRoots(); + long freeSpace = 1000000000; + for (File root : roots) + if(targetFile.getAbsolutePath().startsWith(root.getAbsolutePath())){ + freeSpace = root.getFreeSpace(); + break; + } + + try { + if(targetFile.exists()) { + channel.writeAndFlush(response.failure("文件已存在")).sync(); + return; + } else if(!targetFile.getParentFile().isDirectory()) { + channel.writeAndFlush(response.failure("此路径文件夹不存在")).sync(); + return; + } else if (freeSpace < moveFileRequestMessage.getFileSize()) { + channel.writeAndFlush(response.failure("磁盘空间已满")).sync(); + return; + } + channel.writeAndFlush(response.success()).sync(); + Thread.sleep(200); + }catch (InterruptedException e) { + throw new RuntimeException(e); + } + + threadPool.submit(() -> { + ResponseMessage responseMessage = new ResponseMessage(moveFileRequestMessage.getMessageId()); + File tempFile = new File(targetFile.getPath() + "...undone"); + try(FileOutputStream fileOutputStream = new FileOutputStream(tempFile); + Socket socket = new Socket()) { + socket.connect(new InetSocketAddress(ips.get(1), moveFileRequestMessage.getPort())); //此处只接收从主服务器来的文件 + InputStream inputStream = socket.getInputStream(); + byte[] buf = new byte[8192]; + int len; + long received = 0; + CRC32 crc32 = new CRC32(); + while((len = inputStream.read(buf)) != -1){ + fileOutputStream.write(buf, 0, len); + crc32.update(buf, 0, len); + received += len; + } + fileOutputStream.close(); + inputStream.close(); + socket.close(); + if(received != moveFileRequestMessage.getFileSize()){ + tempFile.delete(); + return; + } + Thread.sleep(500); + if(tempFile.renameTo(targetFile) || targetFile.exists()){ + responseMessage.success("文件接收成功"); + responseMessage.put("crc32", String.valueOf(crc32.getValue())); + }else{ + responseMessage.failure("文件接收失败:重命名失败"); + tempFile.delete(); + } + }catch (IOException| InterruptedException e){ + log.error(e.getMessage()); + response.failure("文件接收失败:" + e.getMessage()); + } finally { + if(tempFile.exists()) + tempFile.delete(); + channel.writeAndFlush(responseMessage); + } + }); + } + + public ResponseMessage moveFiles(MoveFileRequestMessage moveFileRequestMessage){ + ResponseMessage response = new ResponseMessage(moveFileRequestMessage.getMessageId()); + + String operation = moveFileRequestMessage.isDelete()?"移动":"复制"; + String[] fileNames = moveFileRequestMessage.getFileNames(); + + //判断源文件是否存在 + for (String fileName : fileNames) + if(!new File(storagePath + moveFileRequestMessage.getSourcePath() + fileName).exists()) + return response.failure(fileName + "文件不存在"); + + //判断目标文件夹是否存在 + if (!new File(storagePath, moveFileRequestMessage.getTargetPath()).exists()) + return response.failure("目标文件夹不存在"); + + //冲突检测 + for (String fileName : fileNames) + if (new File(storagePath + moveFileRequestMessage.getTargetPath() + fileName).exists()) + return response.failure(String.format("%s文件已存在,操作中止", fileName)); + + User user = userid2user.get(moveFileRequestMessage.getUserid()); + if(!moveFileRequestMessage.isDelete() && user.getId() != 1){ //用户进行复制操作 + long totalSize = CustomUtil.calculateFilesSize(storagePath + moveFileRequestMessage.getSourcePath(), moveFileRequestMessage.getFileNames()); + if(user.getAvailableSpace() < totalSize) + return response.failure("当前可用空间不足,无法复制文件"); + else + user.setAvailableSpace(user.getAvailableSpace() - totalSize); + } + + int total = fileNames.length; + int success = 0; + AtomicLong failureSize = new AtomicLong(); + for (String fileName : fileNames) { + File sourceFile = new File(storagePath + moveFileRequestMessage.getSourcePath() + fileName); + File targetFile = new File(storagePath + moveFileRequestMessage.getTargetPath() + fileName); + + if(moveFileRequestMessage.isDelete()){ + if(sourceFile.renameTo(targetFile)) { + success++; + String shareCode = shareFileMapper.selectShareCodeByFilePath(sourceFile.getAbsolutePath()); + if(shareCode != null) { + shareFileMapper.deleteShareFile(shareCode); + shareFileMapper.deleteShareFileRecord(shareCode); + } + } + }else{ + long currentSize = 0; + try { + if(sourceFile.isDirectory()) { + currentSize = CustomUtil.calculateDirectorySize(sourceFile.getPath()); + FileUtil.copyFilesFromDir(sourceFile, targetFile, true); + } + else { + currentSize = sourceFile.length(); + Files.copy(sourceFile.toPath(), targetFile.toPath()); + } + success++; + }catch (IOException ignore){ + failureSize.addAndGet(currentSize); + } + } + } + + if(!moveFileRequestMessage.isDelete() && user.getId() != 1) { //回传用户可用空间 + user.setAvailableSpace(user.getAvailableSpace() + failureSize.get()); + response.put("availableSpace", user.getAvailableSpace()); + } + + if(total == success) //全部成功 + return response.success(String.format("%s文件完成", operation)); + else //部分成功 + return response.success(String.format("%s文件完成,成功:%d个文件,总共%d个文件", operation, total, success)); + } + + public void sendFile(Task task){ + File sourceFile = new File(storagePath, task.getSourcePath() + task.getFilename()); + Channel targetChannel = channelFuture.channel(); + + if(!sourceFile.exists()){ + taskService.failure(task.getTaskId(), "源文件不存在"); + return; + } + + AtomicLong sent = new AtomicLong(); + task.setProceed(sent); + task.setLast(new AtomicLong()); + task.setStatus("proceeding"); + task.setSpeed(task.getTotal()); + + //如果不是发送给主服务器,则与对应服务器建立连接 + if(task.getReceiver() != 1) { + targetChannel = connectToSite(ips.get(task.getReceiver())); + if(targetChannel == null || !targetChannel.isActive()){ + taskService.failure(task.getTaskId(), "子服务器连接失败"); + return; + } + } + + //生成文件传输消息 + int port = CustomUtil.findIdlePort(); + MoveFileRequestMessage moveFileRequestMessage = new MoveFileRequestMessage(); + moveFileRequestMessage.setPort(port); + moveFileRequestMessage.setSender(id); + moveFileRequestMessage.setFileSize(sourceFile.length()); + moveFileRequestMessage.setFileName(sourceFile.getName()); + moveFileRequestMessage.setTargetPath(task.getTargetPath()); + moveFileRequestMessage.setMessageId(getMessageId()); + + CRC32 crc32 = new CRC32(); + //发送至对应主机,并开始监听 + try(FileInputStream fileInputStream = new FileInputStream(sourceFile); + ServerSocket serverSocket = new ServerSocket(port)){ + targetChannel.writeAndFlush(moveFileRequestMessage).sync(); + + DefaultPromise promise = registerPromise(moveFileRequestMessage.getMessageId()); + boolean result = promise.await(5, TimeUnit.SECONDS); + if(result){ + ResponseMessage send = (ResponseMessage) promise.get(); + if(!send.isResult()) { + taskService.failure(task.getTaskId(), send.get("cause")); + return; + } + } + serverSocket.setSoTimeout(5000); + Socket socket = serverSocket.accept(); + OutputStream outputStream = socket.getOutputStream(); + byte[] buf = new byte[8192]; + int len; + while((len = fileInputStream.read(buf)) != -1 && task.getProceed().get() > -1){ + outputStream.write(buf, 0, len); + crc32.update(buf, 0, len); + task.getProceed().addAndGet(len); + } + outputStream.flush(); + outputStream.close(); + socket.close(); + serverSocket.close(); + fileInputStream.close(); + if(task.getProceed().get() < -1) //任务取消 + return; + } catch (IOException | InterruptedException e) { + log.error(e.getMessage()); + taskService.failure(task.getTaskId(), "服务器无法接收文件:" + e.getMessage()); + return; + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + + DefaultPromise promise = registerPromise(moveFileRequestMessage.getMessageId()); + try{ + boolean result = promise.await(10, TimeUnit.SECONDS); + if(!result){ + taskService.failure(task.getTaskId(), "服务器超时未响应"); + return; + } + + ResponseMessage responseMessage = (ResponseMessage) promise.get(); + if(responseMessage.isResult()) { + String realCrc32 = responseMessage.get("crc32"); + if (realCrc32.equals(String.valueOf(crc32.getValue()))) { + taskService.success(task.getTaskId()); + if (task.isDelete()) { + sourceFile.delete(); + String shareCode = shareFileMapper.selectShareCodeByFilePath(sourceFile.getAbsolutePath()); + if(shareCode != null){ + shareFileMapper.deleteShareFile(shareCode); + shareFileMapper.deleteShareFileRecord(shareCode); + } + } + } else { + taskService.failure(task.getTaskId(), "crc32校验失败"); + FileOperateMessage delete = new FileOperateMessage(); + delete.setPath(task.getTargetPath() + task.getFilename()); + delete.setOperate(FileOperateMessage.DELETE); + targetChannel.writeAndFlush(delete); + } + } else + taskService.failure(task.getTaskId(), responseMessage.get("cause")); + }catch (InterruptedException| ExecutionException e){ + log.error(e.getMessage()); + taskService.failure(task.getTaskId(), e.getMessage()); + } + } + + public ResponseMessage renameFile(FileOperateMessage fileOperateMessage){ + File file = new File(storagePath, fileOperateMessage.getPath()); + ResponseMessage response = new ResponseMessage(fileOperateMessage.getMessageId()); + if(!file.exists()) + return response.failure("文件不存在"); + + File newFile = new File(file.getParentFile().getAbsolutePath(), fileOperateMessage.getName()); + if(file.renameTo(newFile)) + return response.success("重命名成功"); + else + return response.failure("重命名失败"); + } + + + public ResponseMessage deleteFile(FileOperateMessage fileOperateMessage){ + ResponseMessage response = new ResponseMessage(fileOperateMessage.getMessageId()); + + String[] paths = fileOperateMessage.getPaths(); + int count = 0; + AtomicLong deleted = new AtomicLong(); + File file; + for (String path : paths) { + file = new File(storagePath, path); + if(file.isFile()){ + deleted.addAndGet(file.length()); + FileUtil.del(file); + count++; + + String shareCode = shareFileMapper.selectShareCodeByFilePath(file.getAbsolutePath()); + if(shareCode != null) { + shareFileMapper.deleteShareFile(shareCode); + shareFileMapper.deleteShareFileRecord(shareCode); + } + } else if(file.isDirectory()){ + long size = CustomUtil.calculateDirectorySize(file.getPath()); + ArrayList shareFiles = shareFileMapper.selectShareFilesByFilePath(file.getAbsolutePath()); + for (ShareFile shareFile : shareFiles) { + shareFileMapper.deleteShareFile(shareFile.getShareCode()); + shareFileMapper.deleteShareFileRecord(shareFile.getShareCode()); + } + FileUtil.del(file); + count++; + deleted.addAndGet(size); + } + } + + if(fileOperateMessage.getUserid() != 1){ + User user = userid2user.get(fileOperateMessage.getUserid()); + user.setAvailableSpace(user.getAvailableSpace() + deleted.get()); + response.put("availableSpace", user.getAvailableSpace()); + } + + return response.success(count); + } + + public ResponseMessage createDirectory(FileOperateMessage fileOperateMessage){ + ResponseMessage response = new ResponseMessage(fileOperateMessage.getMessageId()); + + File file = new File(storagePath, fileOperateMessage.getPath()); + if(file.exists()) + return response.failure("路径已存在"); + + if(file.mkdirs()) + response.success("文件夹创建成功"); + else + response.failure("文件夹创建失败"); + + return response; + } + + public ResponseMessage submitCompressTask(CompressFileMessage compressFileMessage){ + User user = userid2user.get(compressFileMessage.getUserid()); + ResponseMessage responseMessage = new ResponseMessage(compressFileMessage.getMessageId()); + File file; + long totalSize = 0; + for (int i = 0; i < compressFileMessage.getPaths().length; i++) { + if((file = new File(storagePath + compressFileMessage.getRelativePath() + compressFileMessage.getPaths()[i])).isFile()) + totalSize += file.length(); + else if(file.isDirectory()) + totalSize += CustomUtil.calculateDirectorySize(file.getPath()); + else + return responseMessage.failure("文件" + file.getName() + "不存在, 打包取消"); + } + + if(user.getId() != 1) + if(user.getAvailableSpace() < totalSize) + return responseMessage.failure("当前空间不足,无法打包"); + else { + user.setAvailableSpace(user.getAvailableSpace() - totalSize); + responseMessage.put("availableSpace", user.getAvailableSpace()); + } + + file = new File(storagePath, compressFileMessage.getTargetPath()); //只需要文件名字,不需要处理相对路径 + Task task = new Task(compressFileMessage.getTaskId(), compressFileMessage.getUserid(), file.getName(), id, totalSize, + compressFileMessage.getTargetPath(),compressFileMessage.getPaths(), compressFileMessage.getRelativePath()); + taskService.submitTask(task); + if(taskService.isThreadStop()) + threadPool.submit(() -> processTask(taskService.getWaitingTask())); + + return responseMessage.success("打包任务提交成功"); + } + + public void compressFiles(Task task){ + AtomicLong proceed = new AtomicLong(); + task.setProceed(proceed); + task.setLast(new AtomicLong()); + task.setStatus("proceeding"); + task.setSpeed(task.getTotal()); + File targetFile = new File(storagePath + task.getRelativePath(), task.getTargetPath()); + IOException ioE = null; + try(OutputStream bos = new BufferedOutputStream(Files.newOutputStream(Path.of(targetFile.getAbsolutePath() + "..undone"))); + TarArchiveOutputStream aos = new TarArchiveOutputStream(bos)) + { + aos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); //支持长文件名 + //获取目标tar文件的当前文件夹 + // x/y/x.tar x/y + + Path finalPath = targetFile.getParentFile().toPath(); + for (String path : task.getPaths()) { + File file = new File(storagePath + task.getRelativePath() + path); + if(file.isFile()){ + ArchiveEntry entry = new TarArchiveEntry(file, finalPath.relativize(file.toPath()).toString()); + + aos.putArchiveEntry(entry); + InputStream inputStream = Files.newInputStream(file.toPath()); + byte[] buf = new byte[4096]; + int len; + while((len = inputStream.read(buf)) != -1 && proceed.get() > -1){ + aos.write(buf, 0, len); + proceed.addAndGet(len); + } + aos.closeArchiveEntry(); + if(proceed.get() < -1) + return; + }else{ + Files.walkFileTree(file.toPath(), new SimpleFileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + ArchiveEntry entry = new TarArchiveEntry(dir.toFile(), finalPath.relativize(dir).toString()); + aos.putArchiveEntry(entry); + aos.closeArchiveEntry(); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + ArchiveEntry entry = new TarArchiveEntry(file.toFile(), finalPath.relativize(file).toString()); + aos.putArchiveEntry(entry); + InputStream inputStream = Files.newInputStream(file.toFile().toPath()); + byte[] buf = new byte[4096]; + int len; + while((len = inputStream.read(buf)) != -1 && proceed.get() > -1){ + aos.write(buf, 0, len); + proceed.addAndGet(len); + } + inputStream.close(); + if(proceed.get() < -1) + return FileVisitResult.TERMINATE; + aos.closeArchiveEntry(); + return FileVisitResult.CONTINUE; + } + }); + } + } + + if(task.getProceed().get() == task.getTotal()) { + File tempFile = new File(targetFile.getAbsolutePath() + "..undone"); + if (tempFile.renameTo(targetFile)) + taskService.success(task.getTaskId()); + else { + taskService.failure(task.getTaskId(), "打包失败:重命名文件失败"); + tempFile.delete(); + } + } + }catch (IOException e) { + ioE = e; + e.printStackTrace(); + } finally { + User user = userid2user.get(task.getUserid()); + //打包结果 + if(task.getProceed().get() < -1 || !task.isSuccess()){ //用户手动取消 或出错 + if(ioE != null) //出错 + taskService.failure(task.getTaskId(), "打包失败:" + ioE.getMessage()); + + if(user.getId() != 1) + user.setAvailableSpace(user.getAvailableSpace() + task.getTotal()); + + new File(targetFile.getAbsolutePath() + "..undone").delete(); + } + if(user.getId() != 1) + updateUserSpace(user); + } + } + + public ResponseMessage submitExtractTask(ExtractFileMessage extractFileMessage){ + User user = userid2user.get(extractFileMessage.getUserid()); + ResponseMessage responseMessage = new ResponseMessage(extractFileMessage.getMessageId()); + File file = new File(storagePath + extractFileMessage.getRelativePath() + extractFileMessage.getSourcePath()); + File directory; + + if(!file.exists()) + return responseMessage.failure("无法解包:源文件不存在"); + + if((directory = new File(storagePath + extractFileMessage.getRelativePath() + extractFileMessage.getTargetPath())).exists()) + return responseMessage.failure("无法解包:目标文件夹已存在"); + + directory.mkdirs(); + if(user.getId() != 1){ + if(user.getAvailableSpace() < file.length()) + return responseMessage.failure("无法解包:当前空间不足"); + user.setAvailableSpace(user.getAvailableSpace() - file.length()); + } + + Task task = new Task(extractFileMessage.getTaskId(), extractFileMessage.getUserid(), file.getName(), id, file.length(), extractFileMessage.getSourcePath(), extractFileMessage.getTargetPath(), extractFileMessage.getRelativePath()); + taskService.submitTask(task); + if(taskService.isThreadStop()) + threadPool.submit(() -> processTask(taskService.getWaitingTask())); + + return responseMessage.success("解包任务提交成功"); + } + + public void extractFiles(Task task){ + AtomicLong proceed = new AtomicLong(); + task.setProceed(proceed); + task.setLast(new AtomicLong()); + task.setStatus("proceeding"); + task.setSpeed(task.getTotal()); + IOException ioE = null; + try(TarArchiveInputStream tarInput = new TarArchiveInputStream(new BufferedInputStream(new FileInputStream(storagePath + task.getRelativePath() + task.getSourcePath())))) + { + TarArchiveEntry entry; + while ((entry = tarInput.getNextTarEntry()) != null) { + if (entry.isDirectory()) { + // 如果是目录,创建相应的目录结构 + File dir = new File(storagePath + task.getRelativePath() + task.getTargetPath(), entry.getName()); + dir.mkdirs(); + } else { + // 如果是文件,写入目标路径 + File outputFile = new File(storagePath + task.getRelativePath() + task.getTargetPath(), entry.getName()); + try (FileOutputStream outputFileStream = new FileOutputStream(outputFile)) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = tarInput.read(buffer)) != -1 && proceed.get() > -1) { + outputFileStream.write(buffer, 0, bytesRead); + proceed.addAndGet(bytesRead); + } + if(proceed.get() < -1) + return; + } + } + } + taskService.success(task.getTaskId()); + }catch (IOException e){ + ioE = e; + e.printStackTrace(); + } finally { + User user = userid2user.get(task.getUserid()); + if(!task.isSuccess()){ + if(ioE != null) + taskService.failure(task.getTaskId(), "解包错误:" + ioE.getMessage()); + + if(user.getId() != 1) + user.setAvailableSpace(user.getAvailableSpace() + task.getTotal()); + + FileUtil.del(Path.of(storagePath + task.getTargetPath())); + } + //结束后回传空间 空间已经预先扣除 + if(user.getId() != 1) + updateUserSpace(user); + } + } + + public ResponseMessage shareFile(ShareFileMessage shareFileMessage){ + ResponseMessage response = new ResponseMessage(); + response.setMessageId(shareFileMessage.getMessageId()); + + int success = 0; + for (String fileName : shareFileMessage.getFileNames()) { + File file = new File(storagePath, shareFileMessage.getPath() + fileName); + if(!file.exists()) + continue; + + if(file.isDirectory()) + continue; + + ShareFile shareFile = shareFileMapper.selectShareFileByFilePath(file.getAbsolutePath()); + + if(shareFile != null) + continue; + + + shareFile = new ShareFile(); + if(shareFileMessage.getTime() != null) { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.HOUR, shareFileMessage.getTime()); + shareFile.setExpireTime(calendar.getTime()); + } + + if(shareFileMessage.getCount() != null){ + shareFile.setAvailableCount(shareFileMessage.getCount()); + shareFile.setTotalCount(shareFileMessage.getCount()); + } + shareFile.setShareCode(RandomUtil.randomString(8)); + shareFile.setFilePath(file.getAbsolutePath()); + shareFile.setSharer(shareFileMessage.getUserid()); + + shareFileMapper.insertShareFilePojo(shareFile); + success++; + } + + response.success(success); + return response; + } + + public ResponseMessage adjustShare(AdjustShareMessage adjustShareMessage){ + ResponseMessage response = new ResponseMessage(adjustShareMessage.getMessageId()); + + ShareFile shareFile = shareFileMapper.selectShareFileByShareCode(adjustShareMessage.getShareCode()); + if(shareFile == null) + return response.failure("该分享码不存在,可能过期了"); + if(adjustShareMessage.getUserid() != 1 && shareFile.getSharer() != adjustShareMessage.getUserid()) + return response.failure("非法操作"); + + if(adjustShareMessage.getTime() == null && adjustShareMessage.getCount() == null){ + shareFile.setTotalCount(0); + shareFile.setAvailableCount(0); + shareFile.setExpireTime(null); + }else { + if (adjustShareMessage.getTime() != null) { + Calendar expire = Calendar.getInstance(); + Date expireTime; + if (shareFile.getExpireTime() == null) + expireTime = new Date(); + else + expireTime = shareFile.getExpireTime(); + + expire.setTime(expireTime); + expire.add(Calendar.HOUR, adjustShareMessage.getTime()); + + if (expire.before(Calendar.getInstance())) + return response.failure("调整时间后分享码已过期,请重新调整或直接删除"); + + shareFile.setExpireTime(expire.getTime()); + } + if (adjustShareMessage.getCount() != null) { + //如果之前没有次数 + if (shareFile.getTotalCount() == 0) { + shareFile.setTotalCount(adjustShareMessage.getCount()); + shareFile.setAvailableCount(adjustShareMessage.getCount()); + //如果之前有次数 + } else { + shareFile.setAvailableCount(shareFile.getAvailableCount() + adjustShareMessage.getCount()); + shareFile.setTotalCount(shareFile.getTotalCount() + adjustShareMessage.getCount()); + } + if (shareFile.getAvailableCount() < 1) + return response.failure("调整下载次数后分享码已过期,请重新调整或直接删除"); + } + } + + shareFileMapper.updateShareFile(shareFile); + return response.success(); + } + + public ResponseMessage cancelShare(CancelShareMessage cancelShareMessage){ + ResponseMessage response = new ResponseMessage(cancelShareMessage.getMessageId()); + + ShareFile shareFile; + if((shareFile = shareFileMapper.selectShareFileByShareCode(cancelShareMessage.getShareCode())) != null){ + if(cancelShareMessage.getUserid() != 1 && shareFile.getSharer() != cancelShareMessage.getUserid()) + return response.failure("非法操作"); + + shareFileMapper.deleteShareFile(cancelShareMessage.getShareCode()); + shareFileMapper.deleteShareFileRecord(cancelShareMessage.getShareCode()); + response.success("删除分享码成功"); + }else{ + response.failure("分享码不存在,可能已过期"); + } + return response; + } + + public ResponseMessage queryShareFile(ShareFileQueryMessage shareFileQueryMessage){ + ResponseMessage response = new ResponseMessage(shareFileQueryMessage.getMessageId()); + + switch (shareFileQueryMessage.getOperate()){ + case ShareFileQueryMessage.QUERY_SHARE_FILE -> { + ShareFile shareFile = shareFileMapper.selectShareFileByShareCode(shareFileQueryMessage.getShareCode()); + if(shareFile == null) + return response.failure("分享码不存在"); + + File file = new File(shareFile.getFilePath()); + if(!file.exists()) + return response.failure("文件不存在"); + + FileNode fileNode = new FileNode(); + fileNode.setType(FileNode.FILE); + fileNode.setName(file.getName()); + fileNode.setSize(file.length()); + if(storagePath.startsWith(file.getParentFile().getPath())) + fileNode.setPath(""); + else + fileNode.setPath(file.getParentFile().getPath().replace(storagePath, "")); + response.success(objectMapper.valueToTree(fileNode)); + } + case ShareFileQueryMessage.QUERY_SHARE_FILES -> { + ShareFile[] shareFiles; + if(shareFileQueryMessage.getUserid() == 1) + shareFiles = shareFileMapper.selectAllShareFile(); + else + shareFiles = shareFileMapper.selectAllShareFileByUserid(shareFileQueryMessage.getUserid()); + if(shareFiles.length > 0) { + for (ShareFile shareFile : shareFiles) { + shareFile.setFilePath(shareFile.getFilePath().replace(storagePath + shareFileQueryMessage.getRelativePath(), "/")); + shareFile.setShareFileDownloadRecords(shareFileMapper.selectDownloadRecord(shareFile.getShareCode())); + shareFile.setUsername(shareFileQueryMessage.getUsername()); + } + response.success(objectMapper.valueToTree(shareFiles).toString()); + } + else + response.failure("未分享文件"); + } + } + return response; + + } + + public DefaultPromise registerPromise(int messageId){ + DefaultPromise promise = new DefaultPromise<>(eventLoop); + promises.put(messageId, promise); + return promise; + } + + public void updateUserSpace(User user){ + DynamicConfigMessage dynamicConfigMessage = new DynamicConfigMessage(); + dynamicConfigMessage.updateUserStorage(user); + channelFuture.channel().writeAndFlush(dynamicConfigMessage); + } + } +} diff --git a/src/main/java/com/lion/snss/service/CommunicateToSiteService.java b/src/main/java/com/lion/snss/service/CommunicateToSiteService.java new file mode 100644 index 0000000..758795b --- /dev/null +++ b/src/main/java/com/lion/snss/service/CommunicateToSiteService.java @@ -0,0 +1,136 @@ +package com.lion.snss.service; + +import com.lion.snss.dao.CustomConfigurationMapper; +import com.lion.snss.message.FileOperateMessage; +import com.lion.snss.message.MessageCodec; +import com.lion.snss.message.MoveFileRequestMessage; +import com.lion.snss.message.ResponseMessage; +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.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.zip.CRC32; + +@Service +@Slf4j +public class CommunicateToSiteService { + + String storagePath; + + ExecutorService threadPool; + + public CommunicateToSiteService(CustomConfigurationMapper customConfigurationMapper){ + storagePath = customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH); + threadPool = Executors.newFixedThreadPool(4); + new ServerBootstrap().channel(NioServerSocketChannel.class) + .group(new NioEventLoopGroup()) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel ch) { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(10000000, 4, 4)); + ch.pipeline().addLast(new MessageCodec()); + ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); + ch.pipeline().addLast(new MyChannelInBoundHandlerAdapter()); + } + }).bind(9999); + } + + public void receiveFile(MoveFileRequestMessage moveFileRequestMessage, String address, Channel channel){ + File targetFile = new File(storagePath + moveFileRequestMessage.getTargetPath(), moveFileRequestMessage.getFileName()); + ResponseMessage response = new ResponseMessage(moveFileRequestMessage.getMessageId()); + log.info(address); + File[] roots = File.listRoots(); + long freeSpace = 1000000000; + for (File root : roots) + if(targetFile.getPath().startsWith(root.getPath())){ + freeSpace = root.getFreeSpace(); + break; + } + + try { + if(targetFile.exists()) { + channel.writeAndFlush(response.failure("文件已存在")).sync(); + return; + } else if(!targetFile.getParentFile().isDirectory()) { + channel.writeAndFlush(response.failure("此路径文件夹不存在")).sync(); + return; + } else if (freeSpace < moveFileRequestMessage.getFileSize()) { + channel.writeAndFlush(response.failure("磁盘空间已满")).sync(); + return; + } + channel.writeAndFlush(response.success()).sync(); + Thread.sleep(200); + }catch (InterruptedException e) { + throw new RuntimeException(e); + } + + threadPool.submit(() -> { + File tempFile = new File(targetFile.getPath() + "...undone"); + try(FileOutputStream fileOutputStream = new FileOutputStream(targetFile); + Socket socket = new Socket()) { + socket.connect(new InetSocketAddress(address, moveFileRequestMessage.getPort())); + InputStream inputStream = socket.getInputStream(); + byte[] buf = new byte[8192]; + int len; + long received = 0; + CRC32 crc32 = new CRC32(); + while((len = inputStream.read(buf)) != -1){ + fileOutputStream.write(buf, 0, len); + crc32.update(buf, 0, len); + received += len; + } + fileOutputStream.close(); + inputStream.close(); + socket.close(); + if(received != moveFileRequestMessage.getFileSize()){ + tempFile.delete(); + return; + } + Thread.sleep(500); + if(tempFile.renameTo(targetFile) || targetFile.exists()) { + response.success("文件接收成功"); + response.put("crc32", String.valueOf(crc32.getValue())); + } else { + response.failure("文件接收失败:重命名失败"); + tempFile.delete(); + } + }catch (IOException| InterruptedException e){ + log.error(e.getMessage()); + response.failure("文件接收失败:" + e.getMessage()); + } finally { + if(tempFile.exists()) + tempFile.delete(); + channel.writeAndFlush(response); + } + }); + } + + class MyChannelInBoundHandlerAdapter extends ChannelInboundHandlerAdapter{ + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if(msg instanceof MoveFileRequestMessage moveFileRequestMessage){ + log.info(ctx.channel().remoteAddress().toString()); + threadPool.submit(() -> receiveFile(moveFileRequestMessage, ctx.channel().remoteAddress().toString().split(":")[0].replace("/", ""), ctx.channel())); + }else if(msg instanceof FileOperateMessage fileOperateMessage){ //仅删除文件 + File file = new File(storagePath + fileOperateMessage.getPath()); + if(fileOperateMessage.getOperate() == FileOperateMessage.DELETE) + file.delete(); + } + } + } +} diff --git a/src/main/java/com/lion/snss/service/FileService.java b/src/main/java/com/lion/snss/service/FileService.java new file mode 100644 index 0000000..312c574 --- /dev/null +++ b/src/main/java/com/lion/snss/service/FileService.java @@ -0,0 +1,201 @@ +package com.lion.snss.service; + +import com.lion.snss.dao.CustomConfigurationMapper; +import com.lion.snss.dao.ShareFileMapper; +import com.lion.snss.interceptor.Interceptor; +import com.lion.snss.message.DynamicConfigMessage; +import com.lion.snss.pojo.ShareFile; +import com.lion.snss.pojo.User; +import com.lion.snss.util.IoUtil; +import com.lion.snss.util.Response; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Objects; + +@Service +@Slf4j +public class FileService { + String storagePath; + + @Resource + ShareFileMapper shareFileMapper; + + @Resource + CommunicateToMainService communicateToMainService; + + @Resource + Interceptor interceptor; + + HashMap ipAndShareCode; + + + + public FileService(CustomConfigurationMapper customConfigurationMapper){ + storagePath = customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH); + ipAndShareCode = new HashMap<>(); + } + + public void getFileByShareCode(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String shareCode){ + if(shareCode == null) + try { + httpResponse.sendError(404, "ShareCode invalid"); + return; + } catch (IOException e) {throw new RuntimeException(e);} + + ShareFile shareFile = shareFileMapper.selectShareFileByShareCode(shareCode); + if (shareFile == null) + try{ + httpResponse.sendError(404, "ShareCode is not exist"); + return; + }catch (IOException e){ + throw new RuntimeException(e); + } + + String ip; + if(httpRequest.getRemoteAddr().equals("127.0.0.1")) { + ip = httpRequest.getHeader("X-Forwarded-For"); + if(ip == null) + ip = "127.0.0.1"; + if(ip.contains(",")) + ip = ip.split(",")[0].trim(); + if(ip.contains(":")) + ip = ip.split(":")[0].trim(); + } else + ip = httpRequest.getRemoteAddr(); + + String ua = httpRequest.getHeader("User-Agent"); + if(ua == null) + try { + httpResponse.sendError(404, "User-Agent invalid"); + return; + } catch (IOException e) {throw new RuntimeException(e);} + else if(ua.length() > 255) + ua = ua.substring(0, 255); + + synchronized (this) { + //如果是有限次数分享,则判断当前ip是否下载过,如果未下载过,则将可用次数-1 + if (shareFile.getTotalCount() != 0 && shareFileMapper.selectShareFileRecordAmount(shareCode, ip) == 0) { + if(shareFile.getAvailableCount() > 0) { + shareFile.setAvailableCount(shareFile.getAvailableCount() - 1); + shareFileMapper.updateShareFile(shareFile); + }else{ + try { + httpResponse.sendError(404, "ShareCode download count exceed"); + } catch (IOException e) {throw new RuntimeException(e);} + return; + } + } + //如果是有限时间分享 + if(shareFile.getExpireTime() != null) { + Calendar ExpireTime = Calendar.getInstance(); + Calendar now = Calendar.getInstance(); + + ExpireTime.setTime(shareFile.getExpireTime()); + //判断是否超过分享时间 + if (ExpireTime.before(now)) { + try { + httpResponse.sendError(404, "ShareCode is expired or File is not exist"); + return; + } catch (IOException e) {throw new RuntimeException(e);} + } + } + + //判断文件是否存在 + if(!new File(shareFile.getFilePath()).isFile()){ + try { + httpResponse.sendError(404, "File is not exist"); + return; + } catch (IOException e) {throw new RuntimeException(e);} + } + + //ipAndShareCode为下载ip+shareCode,如果包含的话,则说明这个ip不是第一次下载这个文件了 + if(ipAndShareCode.containsKey(ip+shareCode)) { + long before = ipAndShareCode.get(ip+shareCode); + long now = System.currentTimeMillis(); + //30分钟内多次下载不记录,对于支持多线程下载的下载器来说,如果每次下载都记录,则会产生大量下载记录 + if(now - before > 1000 * 1800) { //1800秒 + ipAndShareCode.put(ip+shareCode, now); + shareFileMapper.insertShareFileRecord(shareCode, ip, System.currentTimeMillis(), ua); + } + }else { + shareFileMapper.insertShareFileRecord(shareCode, ip, System.currentTimeMillis(), ua); + ipAndShareCode.put(ip+shareCode, System.currentTimeMillis()); + } + } + + IoUtil.export(httpRequest, httpResponse, shareFile.getFilePath()); + } + + public void getFile(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String path, String sessionId){ + User user = interceptor.sessionId2user.get(sessionId); + if(user.getId() != 1) + path = user.getStoragePath() + path; + + File file = new File(storagePath, path); + if(file.isFile()) + IoUtil.export(httpRequest, httpResponse, file.getAbsolutePath()); + } + + public String uploadFile(String path, MultipartFile file, String sessionId) { + Response response = Response.generateResponse(); + if(path == null || file == null){ + response.failure("参数不完整"); + return response.toJSONString(); + } + if(path.contains("%")) + path = URLDecoder.decode(path, StandardCharsets.UTF_8); + + User user = interceptor.sessionId2user.get(sessionId); + if(user.getId() != 1) { + if(user.getAvailableSpace() < file.getSize()) + return response.failure("当前空间不足").toJSONString(); + else + user.setAvailableSpace(user.getAvailableSpace() - file.getSize()); + + path = user.getStoragePath() + path; + } + + log.info("上传文件:{}, 目标路径:{}", file.getName(), path); + File directory = new File(storagePath, path); + if(directory.isDirectory()){ + File targetFile = new File(storagePath + path, Objects.requireNonNull(file.getOriginalFilename())); + + if(targetFile.exists()) + return response.failure("目标文件已存在").toJSONString(); + else + try { + file.transferTo(Path.of(storagePath, path, Objects.requireNonNull(file.getOriginalFilename()))); + } catch (IOException e) { + response.failure("上传失败"); + log.error(e.getMessage()); + //上传失败 返还空间 + user.setAvailableSpace(user.getAvailableSpace() + file.getSize()); + Path.of(storagePath, path, Objects.requireNonNull(file.getOriginalFilename())).toFile().delete(); + } finally { + if(user.getId() != 1) { + DynamicConfigMessage dynamicConfigMessage = new DynamicConfigMessage(); + dynamicConfigMessage.updateUserStorage(user); + communicateToMainService.sendMessageToMain(dynamicConfigMessage); + } + } + + response.success("上传成功"); + } + else + response.failure("该路径不存在或者不是文件夹"); + + return response.toJSONString(); + } +} diff --git a/src/main/java/com/lion/snss/service/TaskService.java b/src/main/java/com/lion/snss/service/TaskService.java new file mode 100644 index 0000000..9ac1c03 --- /dev/null +++ b/src/main/java/com/lion/snss/service/TaskService.java @@ -0,0 +1,113 @@ +package com.lion.snss.service; + +import com.lion.snss.pojo.Task; +import lombok.Getter; +import org.springframework.stereotype.Service; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +@Service +public class TaskService { + @Getter + ConcurrentHashMap tasks; + + ScheduledExecutorService thread; + + ReentrantLock lock; + + public TaskService(){ + tasks = new ConcurrentHashMap<>(); + lock = new ReentrantLock(); + thread = Executors.newScheduledThreadPool(1); + thread.scheduleAtFixedRate(this::calculateProgress, 0, 1, TimeUnit.SECONDS); + } + + public void calculateProgress(){ + Set taskIds = tasks.keySet(); + + for(Integer taskId: taskIds){ + Task task = tasks.get(taskId); + + if(task.isComplete() || !task.isProceeding()) + continue; + + if(task.getProceed().get() == -1) { + tasks.remove(taskId); + taskIds.remove(task.getTaskId()); + continue; + } + + task.setSpeed(task.getProceed().get() - task.getLast().get()); + task.getLast().set(task.getProceed().get()); + task.setPercentage(((double) task.getProceed().get() / task.getTotal()) * 100); + + if(task.getProceed().get() == task.getTotal()) { + task.setPercentage(100); + task.setComplete(true); + } + } + } + + public void submitTask(Task task){ + tasks.put(task.getTaskId(), task); + } + + //判断工作线程是否结束:当所有本地任务未进行时,即工作线程结束 + public boolean isThreadStop(){ + lock.lock(); + for (Task task : tasks.values()) + if(task.isProceeding()) { + lock.unlock(); + return false; + } + lock.unlock(); + return true; + } + + public Task getWaitingTask(){ + for (Task task : tasks.values()) + if(task.getStatus().equals("waiting")) + return task; + + return null; + } + + public void removeTask(int id){ + Task task; + if((task = tasks.remove(id)) != null) + task.getProceed().set(-10086); + } + public void removeTasks(int[] ids){ + for (int id : ids) + removeTask(id); + } + + public void failure(int taskId, String cause){ + Task task = tasks.get(taskId); + if(task != null) { + task.setStatus("failure"); + task.setCause(cause); + task.setComplete(true); + } + } + + public void success(int taskId){ + if(tasks.containsKey(taskId)) { + tasks.get(taskId).setStatus("success"); + tasks.get(taskId).setComplete(true); + tasks.get(taskId).setPercentage(100); + } + } + + public void removeComplete(){ + tasks.forEach((k, v) -> { + if(v.getStatus().equals("success") || v.getStatus().equals("failure")) + tasks.remove(k); + }); + } +} diff --git a/src/main/java/com/lion/snss/util/CustomUtil.java b/src/main/java/com/lion/snss/util/CustomUtil.java new file mode 100644 index 0000000..2605fac --- /dev/null +++ b/src/main/java/com/lion/snss/util/CustomUtil.java @@ -0,0 +1,72 @@ +package com.lion.snss.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; + +import java.io.File; +import java.io.IOException; +import java.net.ServerSocket; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.atomic.AtomicLong; + + +@Data +public class CustomUtil { + + public static ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 寻找一定数量的可用端口 + * + * @return 可用端口的起始位置 -1为没有(几乎没有可能) + */ + public static short findIdlePort(){ + for(int i=20000; i<65535; i++){ + try(ServerSocket ignored = new ServerSocket(i)){ + return (short) i; + }catch (IOException ignored) { + } + } + return -1; + } + + public static long calculateFilesSize(String directory, String[] files){ + AtomicLong size = new AtomicLong(); + + for (String filename : files) { + File file = new File(directory, filename); + if(file.isDirectory()) + size.addAndGet(calculateDirectorySize(file.getPath())); + else + size.addAndGet(file.length()); + } + + return size.get(); + } + + public static long calculateDirectorySize(String path){ + AtomicLong size = new AtomicLong(); + File file = new File(path); + + if(!file.isDirectory()) + return -1; + + try { + Files.walkFileTree(file.toPath(), new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + size.addAndGet(file.toFile().length()); + return FileVisitResult.CONTINUE; + } + }); + }catch (IOException e){ + return -1; + } + + return size.get(); + } +} diff --git a/src/main/java/com/lion/snss/util/IoUtil.java b/src/main/java/com/lion/snss/util/IoUtil.java new file mode 100644 index 0000000..5ebc308 --- /dev/null +++ b/src/main/java/com/lion/snss/util/IoUtil.java @@ -0,0 +1,270 @@ +package com.lion.snss.util; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.system.SystemUtil; +import cn.hutool.system.oshi.CpuInfo; +import cn.hutool.system.oshi.OshiUtil; +import com.lion.snss.message.StatusMessage; +import com.lion.snss.message.PairMessage; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.connector.ClientAbortException; +import org.springframework.http.HttpHeaders; +import oshi.hardware.CentralProcessor; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HWDiskStore; +import oshi.hardware.NetworkIF; +import oshi.software.os.OperatingSystem; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Data +@Slf4j +public class IoUtil { + static long ioRead; + static long ioWrite; + static long networkSend; + static long networkReceive; + static NetworkIF networkIF; + static List diskStores; + static CpuInfo cpuInfo; + static GlobalMemory globalMemory; + + static long send; + static long sendNow; + static long receive; + static long receiveNow; + + static long read; + static long readNow; + static long write; + static long writeNow; + + static { + init(); + } + + public static void init(){ + ScheduledExecutorService thread = Executors.newScheduledThreadPool(1); + List networkIFs = OshiUtil.getNetworkIFs(); + cpuInfo = OshiUtil.getCpuInfo(); + globalMemory = OshiUtil.getMemory(); + + for (NetworkIF nif : networkIFs) { + if (SystemUtil.getOsInfo().isLinux()) { + if (nif.getName().equals("eth0") || nif.getName().equals("ens33")) + networkIF = nif; + } else if (nif.getName().equals("eth4") || nif.getName().equals("ethernet_32769")) + networkIF = nif; + } + + + + if(networkIF == null) + log.error("网卡 eth0 不存在"); + else{ + send = networkIF.getBytesSent(); + receive = networkIF.getBytesRecv(); + } + + diskStores = OshiUtil.getDiskStores(); + for (HWDiskStore diskStore : diskStores) { + read += diskStore.getReadBytes(); + write += diskStore.getWriteBytes(); + } + thread.scheduleAtFixedRate(IoUtil::monitor, 0, 1, TimeUnit.SECONDS); + } + + private static void monitor(){ + networkIF.updateAttributes(); + sendNow = networkIF.getBytesSent(); + receiveNow = networkIF.getBytesRecv(); + networkSend = sendNow - send; + networkReceive = receiveNow - receive; + send = sendNow; + receive = receiveNow; + + writeNow = 0; + readNow = 0; + for (HWDiskStore diskStore : diskStores) { + diskStore.updateAttributes(); + writeNow += diskStore.getWriteBytes(); + readNow += diskStore.getReadBytes(); + } + ioWrite = writeNow - write; + ioRead = readNow - read; + write = writeNow; + read = readNow; + } + + + public static PairMessage generatePairMessage(){ + PairMessage pairMessage = new PairMessage(); + pairMessage.setIp(networkIF.getIPv4addr()[0].split("/")[0]); + pairMessage.setHostname(NetUtil.getLocalHostName()); + + OperatingSystem os = OshiUtil.getOs(); + pairMessage.setSystem(os.getFamily() + " " + os.getVersionInfo().toString()); + pairMessage.setCpuArch(System.getProperty("os.arch")); + + CentralProcessor processor = OshiUtil.getHardware().getProcessor(); + pairMessage.setCpuName(processor.getProcessorIdentifier().getName().strip()); + pairMessage.setCpuCore(processor.getPhysicalProcessorCount()); + pairMessage.setCpuThread(processor.getLogicalProcessorCount()); + return pairMessage; + } + + public static StatusMessage generateStatusMessage(int id, String path){ + StatusMessage statusMessage = new StatusMessage(); + statusMessage.setId(id); + + statusMessage.setTotalMemory(globalMemory.getTotal()); + statusMessage.setUsedMemory(globalMemory.getTotal() - globalMemory.getAvailable()); + statusMessage.setUsedMemoryPercentage(numberFormat((double) statusMessage.getUsedMemory() / statusMessage.getTotalMemory())); + + File file = new File(path); + statusMessage.setTotalSpace(file.getTotalSpace()); + statusMessage.setUsedSpace(file.getTotalSpace() - file.getFreeSpace()); + statusMessage.setUsedSpacePercentage(numberFormat((double) statusMessage.getUsedSpace() / statusMessage.getTotalSpace())); + + statusMessage.setSystemLoad(OshiUtil.getHardware().getProcessor().getSystemLoadAverage(3)); + statusMessage.setUsedCpuPercentage(numberFormat(100 - OshiUtil.getCpuInfo().getFree())); + + statusMessage.setIoRead(ioRead); + statusMessage.setIoWrite(ioWrite); + statusMessage.setNetworkReceive(networkReceive); + statusMessage.setNetworkSend(networkSend); + + statusMessage.setSystemUpTime(OshiUtil.getOs().getSystemUptime()); + statusMessage.setSystemBootTime(OshiUtil.getOs().getSystemBootTime()); + + return statusMessage; + } + + public static double numberFormat(double number){ + if(number <= 0) + return number; + if(number < 1) + return Double.parseDouble(String.format("%.1f", number * 100)); + else + return Double.parseDouble(String.format("%.1f", number)); + } + + public static void export(HttpServletRequest request, HttpServletResponse response, String path) { + File file = new File(path); + + String fileName = file.getName(); + + String range = request.getHeader(HttpHeaders.RANGE); + + String rangeSeparator = "-"; + // 开始下载位置 + long startByte = 0; + // 结束下载位置 + long endByte = file.length() - 1; + + // 如果是断点续传 + if (range != null && range.contains("bytes=") && range.contains(rangeSeparator)) { + // 设置响应状态码为 206 + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + + range = range.substring(range.lastIndexOf("=") + 1).trim(); + String[] ranges = range.split(rangeSeparator); + try { + // 判断 range 的类型 + if (ranges.length == 1) { + // 类型一:bytes=-2343 + if (range.startsWith(rangeSeparator)) { + endByte = Long.parseLong(ranges[0]); + } + // 类型二:bytes=2343- + else if (range.endsWith(rangeSeparator)) { + startByte = Long.parseLong(ranges[0]); + } + } + // 类型三:bytes=22-2343 + else if (ranges.length == 2) { + startByte = Long.parseLong(ranges[0]); + endByte = Long.parseLong(ranges[1]); + } + } catch (NumberFormatException e) { + // 传参不规范,则直接返回所有内容 + startByte = 0; + endByte = file.length() - 1; + } + } else { + // 没有 ranges 即全部一次性传输,需要用 200 状态码,这一行应该可以省掉,因为默认返回是 200 状态码 + response.setStatus(HttpServletResponse.SC_OK); + } + + //要下载的长度(endByte 为总长度 -1,这时候要加回去) + long contentLength = endByte - startByte + 1; + //文件类型 + String contentType = request.getServletContext().getMimeType(fileName); + + if (StrUtil.isEmpty(contentType)) { + contentType = "attachment"; + } + + response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); + response.setHeader(HttpHeaders.CONTENT_TYPE, contentType); + // 这里文件名换你想要的,inline 表示浏览器可以直接使用 + // 参考资料:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentType + ";filename=\"" + URLUtil.encode(fileName) + "\""); + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength)); + // [要下载的开始位置]-[结束位置]/[文件总大小] + response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + startByte + rangeSeparator + endByte + "/" + file.length()); + + BufferedOutputStream outputStream; + RandomAccessFile randomAccessFile = null; + //已传送数据大小 + long transmitted = 0; + try { + randomAccessFile = new RandomAccessFile(file, "r"); + outputStream = new BufferedOutputStream(response.getOutputStream()); + byte[] buff = new byte[4096]; + int len = 0; + randomAccessFile.seek(startByte); + while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) { + outputStream.write(buff, 0, len); + transmitted += len; + // 本地测试, 防止下载速度过快 +// Thread.sleep(1); + } + // 处理不足 buff.length 部分 + if (transmitted < contentLength) { + len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted)); + outputStream.write(buff, 0, len); + } + + outputStream.flush(); + response.flushBuffer(); + randomAccessFile.close(); + // log.trace("下载完毕: {}-{}, 已传输 {}", startByte, endByte, transmitted); + } catch (ClientAbortException e) { + // ignore 用户停止下载 + // log.trace("用户停止下载: {}-{}, 已传输 {}", startByte, endByte, transmitted); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (randomAccessFile != null) { + randomAccessFile.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/com/lion/snss/util/Response.java b/src/main/java/com/lion/snss/util/Response.java new file mode 100644 index 0000000..4b6b8a5 --- /dev/null +++ b/src/main/java/com/lion/snss/util/Response.java @@ -0,0 +1,87 @@ +package com.lion.snss.util; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class Response { + ObjectNode result; + public Response(){ + result = CustomUtil.objectMapper.createObjectNode(); + } + + public static Response generateResponse(){ + return new Response(); + } + + public void set(String key, String value){ + result.put(key, value); + } + + public String get(String key){ + return result.get(key).asText(); + } + + public void setData(String data){ + result.put("data", data); + } + + public void setResult(String result){ + this.result.put("result", result); + } + + public void success(){ + setResult("success"); + } + + public void success(String result){ + success(); + setData(result); + } + + public void failure(){ + setResult("failure"); + } + + public Response failure(String result){ + failure(); + setData(result); + return this; + } + + public String getResult(){ + return result.get("data").asText(); + } + + public boolean isSuccess(){ + return result.get("result").asText().equals("success"); + } + + + public static String _failure(String result){ + Response response = generateResponse(); + response.failure(result); + return response.toJSONString(); + } + + public static String _success(String result){ + Response response = generateResponse(); + response.success(result); + return response.toJSONString(); + } + + public static String _success(){ + Response response = generateResponse(); + response.success(); + return response.toJSONString(); + } + + public static String _default(){ + Response response = Response.generateResponse(); + response.failure("参数错误"); + + return response.toJSONString(); + } + + public String toJSONString(){ + return result.toString(); + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..1b51bf8 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,26 @@ +spring: + datasource: + driver-class-name: org.sqlite.JDBC + url: jdbc:sqlite:snss.db +# url: jdbc:sqlite:D:\WorkSpace\code\Java\scalable-network-storage-site\src\main\resources\snss.db + mvc: + view: + prefix: /resource/templates/ + suffix: .html + + thymeleaf: + check-template-location: false + + servlet: + multipart: + max-file-size: 10000MB + max-request-size: 10000MB + enabled: true +server: + port: 8080 + error: + include-message: always + +mybatis: + configuration: + map-underscore-to-camel-case: true diff --git a/src/main/resources/snss-empty.db b/src/main/resources/snss-empty.db new file mode 100644 index 0000000000000000000000000000000000000000..55c1f8ba2a59eaa351dcbeeedc9c58c564d0abd8 GIT binary patch literal 36864 zcmeI*&u`mg7zgmzbAB~x=1x-;U{rNGAvJ1go3v?4dw{MjFth_3((tGN=WgMBep{k1Rwwb2tWV=5P-mw5NH$!qp7P`rRFN9 zn^opisLfv0t8C5OHL4~p-e3jOE^5WH#%tEmrRP>}tgqZ$S8uLdxwftz9UGsBtK#Md zX$7;$)E#OU#K9*rv*{_nOt-uU|JEtXwaU0&W34jX;H=E-U>lbPYa3cCtCzg28rQ|M zz)%YP%n&34`dfBxp*G;3}I=QRi#`}e|^V?#3Jyi+dcWBi;Xv3h2Z^k02$w}$$ zpkr-U#hVb#VcRY<3e4$#8ttktX-}FP;%@u_D=U1nW;sgtFY{ulMe}H!%(*e4D{knKV%Mr+doljv= zUJ*O^f3FWpS>4iwgg*onDQDh;_>@X_s9yDkFwx&#(VXpZLFFc=)!vnUW$boPy77(} zAy!tT=F2@4TCp__YPLTR9@zJm^cyGqw&)G)(>CoSPh{-tL5ru8-s{Q`A<7?5;splB zga8B}009U<00Izz00bZa0SG|ggaj^0p^?$BT`E@>DtSAT%}>vYdq(>7;*at*9&y6K^Vma-R$hx5ByYvQ$*g5u5EW+Yu8YY_q;6 zzOs^DABf1$lA>7?b5fLFNmT5ER|tuOaeGJ^mFAOHafKmY;|fB*y_009U*wE}N!hb1X9UpBU>b7yU7 zW0MxPTx;X6AMgJ8#b*y6-2MBj?;qY*rRc~EiNywXIdv9q-`;$EX~QubXM;0`PgBdP z$2|aV{{O#Etpq9#0SG_<0uX=z1Rwwb2tWV=5P(1*frLEL`Gr9H^Z!0!SOozHKmY;| zfB*y_009U<00Izzz!?$1`~Nf2kVpdp5P$##AOHafKmY;|fB*y_aJ;}d5#s%?zsT{S zh=2eDAOHafKmY;|fB*y_009U<;PeSRFNMYB|H)z3|Mx2F{r&$3{#`qL^+$~$009U< z00Izz00bZa0SG_<0;gVJG!%LvJSr~F*SX-H^vO40hQ}5bv-#O-o%FZf-uG$uEk?Tvy~3MjftJ=%&sof6;1f^lj4E OpP$Xn=Q89+GoR&WfC$lygYV-HCP|obtO(X3Gc3 z;h#u2aX>CUo@^%$hMVfu1WnNR*eTl~2?7v+00bZa0SG|gQ3$k3!_m}@8??PH*mjM( zRp#&)j2hpt?wU1=l{R_Na!PusqKmqHdg_Js&FgEouWPs0uHC$@ot`&771!kCXVQyS ziEDezDawOSWi#m+Kh3bc9R75emhYqqqs}{NhADW3JHa*$7OZXQov4<*s9Lw=(&{R0 z`%5RqctU}DqnbP+*W@_|B*W?=XO$i8&SE=GGw(3d!6vO1rUTc(rWkZ)6?qqNQZeKuU~cypv@Yi~`bHjGkl zBhgGtXlA2U^Bb6#CT(2dFxiMSZ#&)IIZX1BZO5oHXJ7NeW({E(X3^nwZi-HGlbd*j z>#3U{$!4y5SDrb3(tIeAdU1jh!z}S;C%RyFa#;^fg0ej6^z6~&u>WF2tWV=5P$##AOHafKmY;|_`eCfyc?!8 zyHGKAnR{6K%>#IITDKvE!*6(_g%O934`FS zm}R!zjLW#F`ZtkxOb~zo1Rwwb2tWV=5P$##AOHaf43I!V89V$yp!@&-0~8)YfdB*` z009U<00Izz00bZa0SG{#PXO=#`#`V=0SG_<0uX=z1Rwwb2tWV=5Exv63owHGjIO*`9gL+Kfh4O7M5hrd{546oVh})vTZ{!@vPn1=*y(} P_ChANkk1x!xut&qJUe?G literal 0 HcmV?d00001