first commit

This commit is contained in:
chuzhongzai
2023-12-08 12:52:26 +08:00
commit ef6d4ed5af
64 changed files with 5195 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lion</groupId>
<artifactId>scalable-network-storage</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>scalable-network-storage</name>
<description>scalable-network-storage</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
</dependency>
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.4.7</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,29 @@
package com.lion.sns;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class ScalableNetworkStorageApplication {
private static ConfigurableApplicationContext context;
public static void main(String[] args) {
context = SpringApplication.run(ScalableNetworkStorageApplication.class, args);
}
public static void restart() {
ApplicationArguments args = context.getBean(ApplicationArguments.class);
Thread thread = new Thread(() -> {
context.close();
context = SpringApplication.run(ScalableNetworkStorageApplication.class, args.getSourceArgs());
});
thread.setDaemon(false);
thread.start();
}
}
@@ -0,0 +1,26 @@
package com.lion.sns.configuration;
import com.lion.sns.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfiguration {
@Resource
UserService userService;
@Bean
public HttpSessionListener getHttpSessionListener(){
return new HttpSessionListener() {
@Override
public void sessionDestroyed(HttpSessionEvent se) {
if(se.getSession().getAttribute("id") != null)
userService.logout(se.getSession().getAttribute("sessionId").toString());
}
};
}
}
@@ -0,0 +1,30 @@
package com.lion.sns.configuration;
import com.lion.sns.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")
.allowedHeaders("*")
.exposedHeaders("*");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/file/**").addPathPatterns("/manage/**").excludePathPatterns("/ws/**");
}
}
@@ -0,0 +1,21 @@
package com.lion.sns.configuration;
import com.lion.sns.service.WebSocketService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebsocketConfiguration implements WebSocketConfigurer {
@Resource
WebSocketService webSocketService;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketService, "/ws/").setAllowedOrigins("*");
}
}
@@ -0,0 +1,97 @@
package com.lion.sns.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.lion.sns.service.FileService;
import com.lion.sns.util.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
@RestController
@RequestMapping("/file/")
public class FileController {
@Resource
FileService fileService;
@GetMapping("get")
public String getFiles(String path, @SessionAttribute("id") Integer userid){
return fileService.getFiles(path, userid);
}
@PostMapping(path="upload/**")
public String uploadFile(HttpServletRequest request, MultipartFile file, @SessionAttribute("id") Integer userid){
String url = request.getRequestURL().toString();
String[] temp = url.split("/upload/");
if(temp.length == 1)
return fileService.uploadFile("", file, userid);
else
return fileService.uploadFile(temp[1], file, userid);
}
//source 源文件夹 target 目标文件夹 files 源文件夹下要操作的文件
@PostMapping("move")
public String moveFile(String source, String target, String[] fileNames, @SessionAttribute("id") Integer userid){
return fileService.move(source, target, fileNames, userid, true);
}
@PostMapping("copy")
public String copyFile(String source, String target, String[] fileNames, @SessionAttribute("id") Integer userid){
return fileService.move(source, target, fileNames, userid, false);
}
@PostMapping("cancel")
public String cancelTask(Integer[] taskIds, @SessionAttribute("id") int userid){
return fileService.cancelOrRemoveTask(Arrays.stream(taskIds).mapToInt(i -> i).toArray(), userid);
}
@PostMapping("rename")
public String renameFile(String path, String name, @SessionAttribute("id") Integer userid){
return fileService.renameFile(path, name, userid);
}
@PostMapping("delete")
public String deleteFile(@RequestParam(required = false, defaultValue = "1") Integer sourceId, String[] paths, @SessionAttribute("id") Integer userid){
return fileService.deleteFileByUserid(sourceId, paths, userid);
}
@PostMapping("create")
public String createDirectory(String path, @SessionAttribute("id") Integer userid){
return fileService.createDirectory(path, userid);
}
@PostMapping("compress")
public String compressFile(String[] paths, String targetPath, @SessionAttribute("id") Integer userid){
return fileService.compressFiles(paths, targetPath, userid);
}
@PostMapping("extract") //fileNames 保持与fileOperation的接口形式一致
public String extract(String source, String target, String fileNames, @SessionAttribute("id") Integer userid){
return fileService.extractFiles(source + fileNames, target , userid);
}
@GetMapping("getFile/**")
public void getFile(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String path, @SessionAttribute("id") int userid){
fileService.getFile(httpRequest, httpResponse, path, userid);
}
@GetMapping("queryShareFile")
public String queryShareFile(String shareCode){
return fileService.queryShareFile(shareCode);
}
@PostMapping("import")
public String importFile(String shareCode, String path, @SessionAttribute("id") int userid){
try {
return fileService.importFile(shareCode, path, userid);
}catch (JsonProcessingException e){
return new Response().failure("导入失败:" + e.getMessage()).toJSONString();
}
}
}
@@ -0,0 +1,32 @@
package com.lion.sns.controller;
import com.lion.sns.service.ShareService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/share/")
public class ShareFileController {
@Resource
ShareService shareService;
@PostMapping("")
public String shareFile(String path, Integer time, Integer count, String[] fileNames, @SessionAttribute("id") Integer userid){
return shareService.shareFile(path, time, count, fileNames, userid);
}
@PostMapping("adjust")
public String adjustShare(String shareCode, Integer time, Integer count, @SessionAttribute("id") Integer userid){
return shareService.adjustShare(shareCode, time, count, userid);
}
@PostMapping("cancel")
public String cancelShare(String shareCode, @SessionAttribute("id") Integer userid){
return shareService.cancelShare(shareCode, userid);
}
@PostMapping("get")
public String getShareFile(@RequestParam(required = false, defaultValue = "0") int sourceId, @SessionAttribute("id") Integer userid){
return shareService.getShareFile(sourceId, userid);
}
}
@@ -0,0 +1,33 @@
package com.lion.sns.controller;
import com.lion.sns.pojo.Site;
import com.lion.sns.service.SiteService;
import com.lion.sns.util.Response;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/manage/site/")
public class SiteManageController {
@Resource
SiteService siteService;
@PostMapping("alter")
public String alterSite(Site site){
return siteService.alterSite(site);
}
@PostMapping("unpair")
public String unpair(Site site){
return siteService.unpairSite(site);
}
@GetMapping("getSites")
public String getSites(){
return siteService.getSites();
}
}
@@ -0,0 +1,40 @@
package com.lion.sns.controller;
import com.lion.sns.pojo.User;
import com.lion.sns.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/manage/user/")
public class UserManageController {
@Resource
UserService userService;
@PostMapping("create")
public String createUser(User user){
return userService.createUser(user);
}
@PostMapping("alter")
public String alterUser(User user){
return userService.alterUser(user);
}
@PostMapping("remove")
public String remove(int userid){
return userService.removeUser(userid);
}
@GetMapping("")
public String getUsers(){
return userService.getUsers();
}
@PostMapping("verifySpace")
public String verify(int userid){
return userService.verifySpace(userid);
}
}
@@ -0,0 +1,68 @@
package com.lion.sns.controller;
import com.lion.sns.pojo.User;
import com.lion.sns.service.FileService;
import com.lion.sns.service.SiteService;
import com.lion.sns.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttribute;
@Controller
public class indexController {
@Resource
UserService userService;
@Resource
SiteService siteService;
@Resource
FileService fileService;
@GetMapping("/")
public String index(){
return "index";
}
@PostMapping("/login")
@ResponseBody
public String login(User user, HttpSession session){
return userService.login(user, session);
}
@GetMapping("/getSites")
@ResponseBody
public String getSites(@SessionAttribute("id") Integer userid){
return siteService.getSites(userid);
}
@GetMapping("/getFileByShareCode/**")
public void getFileByShareCode(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String shareCode){
fileService.getFileByShareCode(httpRequest, httpResponse, shareCode);
}
@PostMapping("/alterPasscode")
@ResponseBody
public String updatePasscode(@SessionAttribute("id") int userid, String passcode){
return userService.alterPasscode(userid, passcode);
}
@GetMapping("/getSelf")
@ResponseBody
public String getSelf(@SessionAttribute("id") int userid){
return userService.getUser(userid);
}
@PostMapping("/verifySpace")
@ResponseBody
public String verifySpace(@SessionAttribute("id") int userid){
return userService.verifySpace(userid);
}
}
@@ -0,0 +1,18 @@
package com.lion.sns.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 {
static final String PATH = "path";
@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);
}
@@ -0,0 +1,58 @@
package com.lion.sns.dao;
import com.lion.sns.pojo.ShareFile;
import com.lion.sns.pojo.ShareFileDownloadRecord;
import org.apache.ibatis.annotations.*;
import java.util.ArrayList;
@Mapper
public interface ShareFileMapper {
// @Insert("insert into shareFile (share_code, file_path, expire_time, available_count) " +
// "values (#{share_code}, #{file_path}, #{expire_time}, #{available_count})")
// void insertShareFile(@Param("share_code")String share_code, @Param("file_path")String file_path,
// @Param("expire_time") Date expire_time, @Param("available_count") int available_count);
@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 share_code from share_file where file_path=#{filePath}")
String selectShareCodeByFilePath(String filePath);
@Select("select * from share_file where file_path like '%' || #{filePath} || '%'")
ArrayList<ShareFile> selectShareFilesByFilePath(String filePath);
@Delete("delete from share_file where share_code=#{shareCode}")
void deleteShareFile(String shareCode);
@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);
}
@@ -0,0 +1,41 @@
package com.lion.sns.dao;
import com.lion.sns.pojo.Site;
import org.apache.ibatis.annotations.*;
import java.util.ArrayList;
import java.util.Date;
@Mapper
public interface SiteMapper {
@Insert("insert into site (ip, domain, reverse_proxy_prefix, hostname, system, cpu_arch, cpu_name, cpu_core, cpu_thread, storage_path, available_space, total_space)" +
"VALUES (#{ip}, #{domain}, #{reverseProxyPrefix}, #{hostname}, #{system}, #{cpuArch}, #{cpuName}, #{cpuCore}, #{cpuThread}, #{storagePath}, #{availableSpace}, #{totalSpace})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
void insertSite(Site site);
@Update("update site set ip=#{ip}, domain=#{domain}, reverse_proxy_prefix=#{reverseProxyPrefix}, hostname=#{hostname}, system=#{system}, cpu_arch=#{cpuArch}," +
"cpu_name=#{cpuName}, cpu_core=#{cpuCore}, cpu_thread=#{cpuThread}, storage_path=#{storagePath}, available_space=#{availableSpace}, total_space=#{totalSpace} where id=#{id}")
void updateSite(Site site);
@Update("update site set last_online=#{last_online} where id=#{id} and id != 1")
void updateLastOnlineTime(@Param("id") int id, @Param("last_online") Date time);
@Select("select * from site")
ArrayList<Site> selectAllSite();
@Select("select count(id) from site")
int selectSiteCount();
@Select("select * from site where id=#{id}")
Site selectSiteById(int id);
@Select("select * from site where ip=#{ip} and id != 1")
Site selectSiteByIp(String ip);
@Select("select ip from site")
String[] selectAllSiteIp();
@Delete("delete from site where id=#{id}")
void deleteSiteById(int id);
}
@@ -0,0 +1,38 @@
package com.lion.sns.dao;
import com.lion.sns.pojo.User;
import org.apache.ibatis.annotations.*;
@Mapper
public interface UserMapper {
@Insert("insert into user (username, passcode, available_space, total_space, storage_path, site_id) " +
"VALUES (#{username}, #{passcode}, #{availableSpace}, #{totalSpace}, #{storagePath}, #{siteId})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
void insertUser(User user);
@Select("select * from user where id = #{id}")
User selectUserById(int id);
@Select("select * from user where username = #{username}")
User selectUserByUsername(String username);
@Select("select * from user")
User[] selectAllUser();
@Select("select * from user where username=#{username} and passcode=#{passcode}")
User loginByUser(User user);
@Select("select id, storage_path, site_id from user where username=#{username} and passcode=#{passcode}")
User login(@Param("username") String username, @Param("passcode") String passcode);
@Update("update user set username=#{username}, passcode=#{passcode}, available_space=#{availableSpace}, total_space=#{totalSpace}, storage_path=#{storagePath} where id=#{id}")
void updateUser(User user);
@Update("update user set available_space=#{availableSpace}, total_space=#{totalSpace} where id=#{id}")
void updateUserSpace(User user);
@Delete("delete from user where id=#{id}")
void deleteUser(int id);
}
@@ -0,0 +1,48 @@
package com.lion.sns.interceptor;
import com.lion.sns.dao.CustomConfigurationMapper;
import com.lion.sns.dao.SiteMapper;
import com.lion.sns.dao.UserMapper;
import com.lion.sns.pojo.User;
import com.lion.sns.util.CustomUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Getter;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;
@Getter
@Component
public class Interceptor implements HandlerInterceptor {
public HashMap<String, User> sessionId2user;
public Interceptor(CustomConfigurationMapper customConfigurationMapper, SiteMapper siteMapper, UserMapper userMapper){
sessionId2user = new HashMap<>();
String path = customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH);
if(path == null || path.isEmpty())
new Thread(() -> {CustomUtil.initSns(customConfigurationMapper, siteMapper, userMapper);}).start();
}
//需要鉴别管理员接口
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
if(request.getRequestURI().startsWith("/manage")) //only admin
return request.getSession().getAttribute("id").equals(1);
String sessionId;
if(request.getRequestURI().startsWith("/file/getFile") && (sessionId = request.getParameter("sessionId")) != null) {
if(sessionId2user.containsKey(sessionId)){
String storagePath = sessionId2user.get(sessionId).getStoragePath();
request.getSession().setAttribute("storagePath", storagePath == null ? "" : storagePath);
return true;
}
}
return request.getSession().getAttribute("id") != null;
}
}
@@ -0,0 +1,39 @@
package com.lion.sns.message;
import com.fasterxml.jackson.annotation.JsonInclude;
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;
//负载信息序列化时去掉无用属性
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public int messageType;
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public int messageId;
}
@@ -0,0 +1,14 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class AdjustShareMessage extends AbstractMessage{
{
messageType = ADJUST_SHARE_MESSAGE;
}
String shareCode;
Integer time;
Integer count;
int userid;
}
@@ -0,0 +1,12 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class CancelShareMessage extends AbstractMessage{
{
messageType = CANCEL_SHARE_MESSAGE;
}
String shareCode;
int userid;
}
@@ -0,0 +1,15 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class CompressFileMessage extends AbstractMessage{
{
messageType = COMPRESS_FILE_MESSAGE;
}
String relativePath;
String targetPath;
String[] paths;
int taskId;
int userid;
}
@@ -0,0 +1,36 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class ConfigMessage extends AbstractMessage{
{
messageType = CONFIG_MESSAGE;
}
final static byte MOVE_SITE_STORAGE_PATH = 1;
final static byte MOVE_USER_STORAGE_PATH = 2;
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;
}
public void verifyUserSpace(String path){
this.path = path;
operate = VERIFY_USER_SPACE;
}
}
@@ -0,0 +1,10 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class ConnectMessage extends AbstractMessage{
{
messageType = CONNECT_MESSAGE;
}
}
@@ -0,0 +1,56 @@
package com.lion.sns.message;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.lion.sns.pojo.User;
import lombok.Data;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
@Data
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
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<Integer, String> ip;
Map.Entry<String, User> user; //sessionId 前八位: userid userStoragePath
public void addIp(int id, String ip){
this.ip = new AbstractMap.SimpleEntry<>(id, ip);
operate = ADD_IP;
}
public void removeIp(int id){
this.ip = new AbstractMap.SimpleEntry<>(id, null);
operate = REMOVE_IP;
}
public void addUser(String sessionId, User user){
this.user = new AbstractMap.SimpleEntry<>(sessionId, user);
operate = ADD_USER;
}
public void removeUser(String sessionId){
this.user = new AbstractMap.SimpleEntry<>(sessionId, null);
operate = REMOVE_USER;
}
public void all(HashMap<Integer, String> ips, HashMap<String, User> users){
this.ips = ips;
this.users = users;
operate = ALL;
}
HashMap<Integer, String> ips;
HashMap<String, User> users;
}
@@ -0,0 +1,15 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class ExtractFileMessage extends AbstractMessage{
{
messageType = EXTRACT_FILE_MESSAGE;
}
String relativePath;
String sourcePath;
String targetPath;
int taskId;
int userid;
}
@@ -0,0 +1,32 @@
package com.lion.sns.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;
public void createFolder(String path){
operate = CREATE;
this.path = path;
}
public void deleteFiles(String[] paths){
operate = DELETE;
this.paths = paths;
}
}
@@ -0,0 +1,13 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class FileQueryMessage extends AbstractMessage{
{
messageType = FILE_QUERY_MESSAGE;
}
String path;
String relativePath; //相对路径,用于去除多余的路径
int userid;
}
@@ -0,0 +1,15 @@
package com.lion.sns.message;
import com.lion.sns.pojo.FileNode;
import lombok.Data;
import java.util.List;
@Data
public class FileResponseMessage extends AbstractMessage {
{
messageType = FILE_RESPONSE_MESSAGE;
}
List<FileNode> files;
String data;
}
@@ -0,0 +1,55 @@
package com.lion.sns.message;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lion.sns.util.CustomUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import java.util.List;
public class MessageCodec extends ByteToMessageCodec<AbstractMessage> {
ObjectMapper objectMapper = CustomUtil.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<Object> 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.PAIR_MESSAGE -> objectMapper.readValue(bytes, PairMessage.class);
case AbstractMessage.PAIR_RESULT_MESSAGE -> objectMapper.readValue(bytes, PairResultMessage.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);
}
}
@@ -0,0 +1,29 @@
package com.lion.sns.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;
}
@@ -0,0 +1,13 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class MoveFileResponseMessage extends AbstractMessage{
{
messageType = MOVE_FILE_RESPONSE_MESSAGE;
}
boolean result;
String cause;
int taskId;
}
@@ -0,0 +1,21 @@
package com.lion.sns.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;
}
@@ -0,0 +1,21 @@
package com.lion.sns.message;
import lombok.Data;
import java.util.HashMap;
@Data
public class PairResultMessage extends AbstractMessage{
{
messageType = PAIR_RESULT_MESSAGE;
}
public PairResultMessage(int messageId){
this.messageId = messageId;
}
int id; //分配的id -1为拒绝
HashMap<Integer, String> ips; //当前连接的所有服务器ip以及id
String sessionId; //管理员的sessionId
long totalSpace; //服务器总共可分配的容量
}
@@ -0,0 +1,54 @@
package com.lion.sns.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<String, String> data;
public ResponseMessage(int messageId){
this.messageId = messageId;
data = new HashMap<>();
}
public ResponseMessage(){
data = new HashMap<>();
}
public String get(String key){
return data.get(key);
}
public ResponseMessage put(String key, String value){
data.put(key, value);
return this;
}
public ResponseMessage success(String data) {
this.data.put("data", data);
result = true;
return this;
}
public ResponseMessage success(){
result = true;
return this;
}
public ResponseMessage failure(String cause){
this.data.put("cause", cause);
result = false;
return this;
}
}
@@ -0,0 +1,15 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class ShareFileMessage extends AbstractMessage{
{
messageType = SHARE_FILE_MESSAGE;
}
String path;
Integer count;
Integer time;
String[] fileNames;
int userid;
}
@@ -0,0 +1,31 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class ShareFileQueryMessage extends AbstractMessage{
{
messageType = SHARE_FILE_QUERY_MESSAGE;
}
final static byte QUERY_SHARE_FILE = 1;
final static byte QUERY_SHARE_FILES = 2;
byte operate;
int userid;
String username;
String relativePath;
String shareCode;
public void queryShareFile(String shareCode){
this.shareCode = shareCode;
operate = QUERY_SHARE_FILE;
}
public void queryShareFiles(int userid, String username, String relativePath){
this.userid = userid;
this.relativePath = relativePath;
this.username = username;
operate = QUERY_SHARE_FILES;
}
}
@@ -0,0 +1,25 @@
package com.lion.sns.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;
}
@@ -0,0 +1,11 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class TaskCancelMessage extends AbstractMessage{
{
messageType = TASK_CANCEL_MESSAGE;
}
int[] taskIds;
}
@@ -0,0 +1,14 @@
package com.lion.sns.message;
import com.lion.sns.pojo.Task;
import lombok.Data;
import java.util.ArrayList;
@Data
public class TaskStatusMessage extends AbstractMessage{
{
messageType = TASK_STATUS_MESSAGE;
}
ArrayList<Task> tasks;
}
@@ -0,0 +1,10 @@
package com.lion.sns.message;
import lombok.Data;
@Data
public class UnPairMessage extends AbstractMessage{
{
messageType = UN_PAIR_MESSAGE;
}
}
@@ -0,0 +1,2 @@
config.stopBubbling=true
lombok.equalsAndHashCode.callSuper=call
@@ -0,0 +1,27 @@
package com.lion.sns.pojo;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.util.Date;
import java.util.TreeMap;
@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;
}
@@ -0,0 +1,32 @@
package com.lion.sns.pojo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import java.util.concurrent.atomic.AtomicLong;
@Data
//打包 计算crc32 传输(单线程) 校验
public class Progress {
public Progress(int progressId, AtomicLong proceed, long total){
this.progressId = progressId;
this.proceed = proceed;
last = new AtomicLong(proceed.get());
this.total = total;
}
int progressId;
double percentage;
long speed;
@JsonIgnore
AtomicLong proceed;
@JsonIgnore
AtomicLong last;
@JsonIgnore
long total;
}
@@ -0,0 +1,10 @@
package com.lion.sns.pojo;
import lombok.Data;
@Data
public class Record {
int id;
int operator;
String operation;
}
@@ -0,0 +1,23 @@
package com.lion.sns.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;
}
@@ -0,0 +1,11 @@
package com.lion.sns.pojo;
import lombok.Data;
@Data
public class ShareFileDownloadRecord {
String shareCode;
String ip;
long time;
String ua;
}
+54
View File
@@ -0,0 +1,54 @@
package com.lion.sns.pojo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.lion.sns.message.PairMessage;
import lombok.Data;
import java.util.Date;
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Site {
public static Site generateSite(PairMessage pairMessage){
Site site = new Site();
site.setIp(pairMessage.getIp());
site.setDomain(pairMessage.getDomain());
site.setReverseProxyPrefix(pairMessage.getReverseProxyPrefix());
site.setHostname(pairMessage.getHostname());
site.setSystem(pairMessage.getSystem());
site.setCpuArch(pairMessage.getCpuArch());
site.setCpuName(pairMessage.getCpuName());
site.setCpuCore(pairMessage.getCpuCore());
site.setCpuThread(pairMessage.getCpuThread());
site.setStoragePath(pairMessage.getStoragePath());
return site;
}
@JsonProperty("host")
public String getHost(){
if(reverseProxyPrefix != null)
return reverseProxyPrefix;
else if(domain != null)
return "https://" + domain + "/";
else
return "http://" + ip + ":8080/";
}
int id;
String ip;
String domain;
String reverseProxyPrefix;
String hostname;
String system;
String cpuArch;
String cpuName;
int cpuCore;
int cpuThread;
Date lastOnline;
boolean isOnline;
String storagePath;
long totalSpace;
long availableSpace;
}
+117
View File
@@ -0,0 +1,117 @@
package com.lion.sns.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;
int sender;
int receiver;
boolean delete; //任务完成后是否删除
//打包
String targetPath;
String[] paths;
String relativePath;
//转移
public Task(int taskId, int userid, long total, boolean isDelete, String sourcePath, String targetPath, String filename, int receiver){
this.taskId = taskId;
this.userid = userid;
this.siteId = 1;
sender = 1;
this.receiver = receiver;
this.total = total;
this.sourcePath = sourcePath;
this.targetPath = targetPath;
this.filename = filename;
this.delete = isDelete;
local = true;
complete = false;
status = "waiting";
type = TRANSFER;
}
//打包
public Task(int taskId, int userid, String filename, long total, String targetPath, String[] paths, String relativePath){
this.taskId = taskId;
this.userid = userid;
this.siteId = 1;
this.total = total;
this.targetPath = targetPath;
this.paths = paths;
this.relativePath = relativePath;
this.filename = filename;
local = true;
complete = false;
status = "waiting";
type = COMPRESS;
}
//解包
public Task(int taskId, int userid, String filename, long total, String sourcePath, String targetPath, String relativePath){
this.taskId = taskId;
this.userid = userid;
this.siteId = 1;
this.total = total;
this.sourcePath = sourcePath;
this.targetPath = targetPath;
this.relativePath = relativePath;
this.filename = filename;
local = true;
complete = false;
status = "waiting";
type = EXTRACT;
}
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");
}
}
+14
View File
@@ -0,0 +1,14 @@
package com.lion.sns.pojo;
import lombok.Data;
@Data
public class User {
int id;
String username;
String passcode; //加盐md5
long availableSpace; //复制 打包 解包 上传- 删除+
long totalSpace; //防止同时进行多个减少操作导致超出限制:先减,完成后再结算实际可用空间
String storagePath; //path
int siteId;
}
@@ -0,0 +1,446 @@
package com.lion.sns.service;
import com.lion.sns.dao.CustomConfigurationMapper;
import com.lion.sns.dao.SiteMapper;
import com.lion.sns.dao.UserMapper;
import com.lion.sns.interceptor.Interceptor;
import com.lion.sns.message.*;
import com.lion.sns.pojo.Site;
import com.lion.sns.pojo.Task;
import com.lion.sns.pojo.User;
import com.lion.sns.util.IoUtil;
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 io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.zip.CRC32;
@Service
@Slf4j
public class CommunicateService {
ChannelFuture channelFuture;
WebSocketService webSocketService;
SiteMapper siteMapper;
@Resource
TaskService taskService;
@Resource
CustomConfigurationMapper customConfigurationMapper;
@Resource
UserMapper userMapper;
HashMap<Integer, Channel> sessions;
HashMap<Integer, String> ips;
HashMap<Integer, DefaultPromise<AbstractMessage>> promises;
HashMap<Integer, StatusMessage> loadInformation;
HashMap<Integer, User> userid2user;
HashMap<Integer, HttpSession> userid2httpSession;
EventLoop eventLoop;
ExecutorService threadPool;
AtomicInteger messageId;
String storagePath;
Interceptor interceptor;
public CommunicateService(WebSocketService webSocketService, CustomConfigurationMapper customConfigurationMapper, SiteMapper siteMapper,
Interceptor interceptor){
storagePath = customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH);
this.customConfigurationMapper = customConfigurationMapper;
this.interceptor = interceptor;
threadPool = Executors.newFixedThreadPool(2);
messageId = new AtomicInteger();
eventLoop = new DefaultEventLoop();
loadInformation = new HashMap<>();
sessions = new HashMap<>();
userid2user = new HashMap<>();
userid2httpSession = new HashMap<>();
ips = new HashMap<>();
ips.put(1, IoUtil.getIp());
promises = new HashMap<>();
channelFuture = new ServerBootstrap().channel(NioServerSocketChannel.class)
.group(new NioEventLoopGroup())
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@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(7777);
this.webSocketService = webSocketService;
this.siteMapper = siteMapper;
webSocketService.initScheduleThread(loadInformation);
webSocketService.logoutFunction = (Function<Integer, Object>) userid -> {
User logoutUser = this.userid2user.remove(userid);
AtomicReference<String> sessionId = new AtomicReference<>();
this.interceptor.sessionId2user.forEach((k, v) -> {
if(v.getId() == logoutUser.getId()){
sessionId.set(k);
}
});
this.interceptor.sessionId2user.remove(sessionId.get());
if(logoutUser.getSiteId() != 1) {
DynamicConfigMessage dynamicConfigMessage = new DynamicConfigMessage();
dynamicConfigMessage.removeUser(sessionId.get());
Channel channel = sessions.get(logoutUser.getSiteId());
if (channel != null && channel.isActive())
channel.writeAndFlush(dynamicConfigMessage);
}
HttpSession httpSession = userid2httpSession.get(logoutUser.getId());
if (httpSession != null)
httpSession.invalidate();
return null;
};
wakeupSites(siteMapper.selectAllSiteIp());
}
public void wakeupSites(String[] ips){
threadPool.submit(() -> {
for (String ip : ips)
try (Socket socket = new Socket()) {
socket.setSoTimeout(500);
socket.connect(new InetSocketAddress(ip, 9990));
} catch (IOException ignored) {}
});
}
public void dynamicConfigNotify(DynamicConfigMessage dynamicConfigMessage){
Set<Integer> ids = sessions.keySet();
ids.forEach((key) -> {
if(dynamicConfigMessage.getOperate() == DynamicConfigMessage.ADD_IP) //如果是添加ip
if (Objects.equals(key, dynamicConfigMessage.getIp().getKey())) //则不将当前ip变更信息发送到该ip
return;
sessions.get(key).writeAndFlush(dynamicConfigMessage);
});
}
public ResponseMessage sendMessageToSite(int targetId, AbstractMessage message){
ResponseMessage response = new ResponseMessage();
Channel channel = sessions.get(targetId);
if(channel == null || !channel.isActive())
return response.failure("子服务器不在线");
DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoop);
message.setMessageId(getMessageId());
channel.writeAndFlush(message);
promises.put(message.getMessageId(), promise);
try{
boolean result = promise.await(10, TimeUnit.SECONDS);
if(result)
return (ResponseMessage) promise.get();
else
return response.failure("操作超时");
}catch (InterruptedException| ExecutionException e){
return response.failure("操作失败:" + e.getMessage());
}
}
public void sendMessageToAllSite(AbstractMessage message){
for (Channel channel : sessions.values())
try {channel.writeAndFlush(message).sync();}catch (Exception ignore){}
}
public void cancelTasks(List<Task> tasks){
HashMap<Integer, ArrayList<Integer>> cancelTasks = new HashMap<>();
for (Task task : tasks) {
int siteId = taskService.getSiteId(task.getTaskId());
ArrayList<Integer> taskIds = cancelTasks.computeIfAbsent(siteId, k -> new ArrayList<>());
taskIds.add(task.getTaskId());
}
cancelTasks.forEach((siteId, taskIds) -> {
TaskCancelMessage message = new TaskCancelMessage();
message.setTaskIds(taskIds.stream().mapToInt(i -> i).toArray());
Channel channel = sessions.get(siteId);
if(channel != null && channel.isActive()){
try {
channel.writeAndFlush(message).sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
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 = 0;
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(() -> {
File tempFile = new File(targetFile.getPath() + "...undone");
try(FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
Socket socket = new Socket()){
socket.connect(new InetSocketAddress(ips.get(moveFileRequestMessage.getSender()), 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);
}
});
}
public void proceedPair(PairMessage pairMessage, Channel channel){
Site site = Site.generateSite(pairMessage);
DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoop);
webSocketService.sendPairMessage(pairMessage, promise);
threadPool.submit(() -> {
try {
promise.await();
PairResultMessage prm = new PairResultMessage(pairMessage.getMessageId());
if(promise.isSuccess()){
ResponseMessage result = (ResponseMessage) promise.get();
if(result.isResult()){
long totalSpace = Long.parseLong(result.get("totalSpace"));
site.setTotalSpace(totalSpace);
site.setAvailableSpace(totalSpace);
siteMapper.insertSite(site);
sessions.put(site.getId(), channel);
ips.put(site.getId(), site.getIp());
prm.setId(site.getId());
prm.setIps(ips);
AtomicReference<String> sessionId = new AtomicReference<>();
interceptor.sessionId2user.forEach((k, v) -> {
if(v.getId() == 1)
sessionId.set(k);
});
prm.setSessionId(sessionId.get());
DynamicConfigMessage dynamicConfigMessage = new DynamicConfigMessage();
dynamicConfigMessage.addIp(site.getId(), site.getIp());
dynamicConfigNotify(dynamicConfigMessage);
}else
prm.setId(-1);
}else {
Throwable cause = promise.cause();
log.error(cause.getMessage());
prm.setId(-1);
}
channel.writeAndFlush(prm);
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
});
}
public DefaultPromise<AbstractMessage> registerPromise(int messageId){
DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoop);
promises.put(messageId, promise);
return promise;
}
public int getMessageId(){
return messageId.getAndIncrement();
}
class MyChannelInBoundHandlerAdapter extends ChannelInboundHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
AbstractMessage abstractMessage = (AbstractMessage) msg;
switch (abstractMessage.messageType){
case AbstractMessage.STATUS_MESSAGE -> {
StatusMessage statusMessage = (StatusMessage) abstractMessage;
statusMessage.setMessageType(0);
statusMessage.setMessageId(0);
loadInformation.put(statusMessage.getId(), statusMessage);
}
case AbstractMessage.TASK_STATUS_MESSAGE -> {
TaskStatusMessage taskStatusMessage = (TaskStatusMessage) abstractMessage;
for (Task task : taskStatusMessage.getTasks()) {
webSocketService.putTask(task);
taskService.putTask(task);
taskService.putSiteTask(task.getSiteId(), task.getTaskId());
}
}
case AbstractMessage.FILE_RESPONSE_MESSAGE -> {
FileResponseMessage fileResponseMessage = (FileResponseMessage) abstractMessage;
Promise<AbstractMessage> promise;
if((promise = promises.remove(fileResponseMessage.getMessageId())) != null)
promise.setSuccess(fileResponseMessage);
else
log.info("未处理查询消息:" + fileResponseMessage);
}
case AbstractMessage.MOVE_FILE_REQUEST_MESSAGE -> {
MoveFileRequestMessage moveFileRequestMessage = (MoveFileRequestMessage) abstractMessage;
threadPool.submit(() -> {
receiveFile(ctx.channel(), moveFileRequestMessage);
});
}
case AbstractMessage.MOVE_FILE_RESPONSE_MESSAGE -> {
MoveFileResponseMessage moveFileResponseMessage = (MoveFileResponseMessage) abstractMessage;
if(moveFileResponseMessage.isResult())
taskService.success(moveFileResponseMessage.getTaskId());
else
taskService.failure(moveFileResponseMessage.getTaskId(), moveFileResponseMessage.getCause());
}
case AbstractMessage.FILE_OPERATE_MESSAGE -> { //用于删除接收到的残缺文件
FileOperateMessage fileOperateMessage = (FileOperateMessage) abstractMessage;
if(fileOperateMessage.getOperate() == FileOperateMessage.DELETE){
File file = new File(storagePath, fileOperateMessage.getPath());
file.delete();
}
}
case AbstractMessage.DYNAMIC_CONFIG_MESSAGE -> {
DynamicConfigMessage dynamicConfigMessage = (DynamicConfigMessage) abstractMessage;
switch (dynamicConfigMessage.getOperate()){
case DynamicConfigMessage.UPDATE_USER_STORAGE -> {
User user = dynamicConfigMessage.getUser().getValue();
userid2user.get(user.getId()).setAvailableSpace(user.getAvailableSpace());
userMapper.updateUserSpace(user);
}
}
}
case AbstractMessage.PAIR_MESSAGE -> {
proceedPair((PairMessage) abstractMessage, ctx.channel());
}
case AbstractMessage.CONNECT_MESSAGE -> {
String ip = ctx.channel().remoteAddress().toString().replace("/", "").split(":")[0];
Site site = siteMapper.selectSiteByIp(ip);
if(site == null)
return;
//不等于null则是已配对了的节点
sessions.put(site.getId(), ctx.channel());
ips.put(site.getId(), site.getIp());
//通知除了当前连接的节点
DynamicConfigMessage config = new DynamicConfigMessage();
config.addIp(site.getId(), site.getIp());
dynamicConfigNotify(config);
//发送绑定用户以及全部ip给刚上线节点
config = new DynamicConfigMessage();
HashMap<String, User> siteUsers = new HashMap<>();
interceptor.sessionId2user.forEach((k, v) -> {
if(v.getId() == 1) //管理员sessionId
siteUsers.put(k, v);
else if(v.getSiteId() == site.getId()) //绑定用户sessionId
siteUsers.put(k, v);
});
config.all(ips, siteUsers);
ctx.channel().writeAndFlush(config);
webSocketService.statusAlter(site.getHostname(), true);
}
case AbstractMessage.RESPONSE_MESSAGE -> {
ResponseMessage responseMessage = (ResponseMessage) abstractMessage;
if(promises.containsKey(responseMessage.getMessageId()))
promises.remove(responseMessage.getMessageId()).setSuccess(responseMessage);
}
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
AtomicInteger id = new AtomicInteger(-1);
sessions.forEach((k,v) -> {
if(v.equals(ctx.channel()))
id.set(k);
});
sessions.remove(id.get());
if(ips.containsKey(id.get())) {
DynamicConfigMessage dynamicConfigMessage = new DynamicConfigMessage();
dynamicConfigMessage.removeIp(id.get());
dynamicConfigNotify(dynamicConfigMessage);
Site site = siteMapper.selectSiteById(id.get());
webSocketService.statusAlter(site.getHostname(), false);
siteMapper.updateLastOnlineTime(id.get(), new Date());
ips.remove(id.get());
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,276 @@
package com.lion.sns.service;
import cn.hutool.core.util.RandomUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lion.sns.dao.CustomConfigurationMapper;
import com.lion.sns.dao.ShareFileMapper;
import com.lion.sns.dao.UserMapper;
import com.lion.sns.message.*;
import com.lion.sns.pojo.ShareFile;
import com.lion.sns.pojo.User;
import com.lion.sns.util.CustomUtil;
import com.lion.sns.util.Response;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.*;
@Service
public class ShareService {
@Resource
ShareFileMapper shareFileMapper;
@Resource
CommunicateService communicateService;
@Resource
UserMapper userMapper;
String storagePath;
ObjectMapper objectMapper;
public ShareService(CustomConfigurationMapper customConfigurationMapper){
storagePath = customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH);
objectMapper = CustomUtil.objectMapper;
}
public String shareFile(String path, Integer time, Integer count, String[] fileNames, int userid){
Response response = new Response();
int sourceId;
User user = communicateService.userid2user.get(userid);
if(user.getId() != 1) {
sourceId = user.getSiteId();
path = user.getStoragePath() + path;
} else {
String[] temp = path.split(":");
sourceId = Integer.parseInt(temp[0]);
if (temp.length == 2)
path = temp[1];
else
path = "";
}
if(sourceId == 1){
int success = 0;
for (String fileName : fileNames) {
File file = new File(storagePath, path + fileName);
if(!file.exists() || file.isDirectory())
continue;
ShareFile shareFile = shareFileMapper.selectShareFileByFilePath(file.getAbsolutePath());
if(shareFile != null)
continue;
shareFile = new ShareFile();
if(time != null) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, time);
shareFile.setExpireTime(calendar.getTime());
}
if(count != null){
shareFile.setAvailableCount(count);
shareFile.setTotalCount(count);
}
shareFile.setShareCode(RandomUtil.randomString(8));
shareFile.setFilePath(file.getAbsolutePath());
shareFile.setSharer(userid);
shareFileMapper.insertShareFilePojo(shareFile);
success++;
}
if(success == fileNames.length)
response.success("分享文件成功");
else
response.success(String.format("总共分享%d个文件,%d个文件分享成功", fileNames.length, success));
}else{
ShareFileMessage shareFileMessage = new ShareFileMessage();
shareFileMessage.setPath(path);
shareFileMessage.setTime(time);
shareFileMessage.setCount(count);
shareFileMessage.setFileNames(fileNames);
shareFileMessage.setUserid(userid);
ResponseMessage responseMessage = communicateService.sendMessageToSite(sourceId, shareFileMessage);
if(responseMessage.isResult())
response.success("分享成功");
else
response.failure(responseMessage.get("cause"));
}
return response.toJSONString();
}
public String adjustShare(String shareCode, Integer time, Integer count, int userid){
Response response = new Response();
int sourceId;
User user = communicateService.userid2user.get(userid);
if(user.getId() != 1){
sourceId = user.getSiteId();
} else {
String[] temp = shareCode.split(":");
sourceId = Integer.parseInt(temp[0]);
shareCode = temp[1];
}
if(sourceId == 1){
ShareFile shareFile = shareFileMapper.selectShareFileByShareCode(shareCode);
if (shareFile == null)
return response.failure("该分享码不存在,可能过期了").toJSONString();
if (userid != 1 && shareFile.getSharer() != userid) //管理员直接操作
return response.failure("非法操作").toJSONString();
//设置为永久
if(time == null && count == null){
shareFile.setAvailableCount(0);
shareFile.setTotalCount(0);
shareFile.setExpireTime(null);
}
//调整时间
else {
if (time != 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, time);
if (expire.before(Calendar.getInstance()))
return response.failure("调整时间后分享码已过期,请重新调整或直接删除").toJSONString();
shareFile.setExpireTime(expire.getTime());
}
//调整次数
if(count != null) {
//如果之前没有次数
if (shareFile.getTotalCount() == 0) {
shareFile.setTotalCount(count);
shareFile.setAvailableCount(count);
//如果之前有次数
} else {
shareFile.setAvailableCount(shareFile.getAvailableCount() + count);
shareFile.setTotalCount(shareFile.getTotalCount() + count);
}
if (shareFile.getAvailableCount() < 1)
return response.failure("调整下载次数后分享码已过期,请重新调整或直接删除").toJSONString();
}
}
shareFileMapper.updateShareFile(shareFile);
response.success("分享码调整成功");
}else{
AdjustShareMessage adjustShareMessage = new AdjustShareMessage();
adjustShareMessage.setShareCode(shareCode);
adjustShareMessage.setTime(time);
adjustShareMessage.setCount(count);
adjustShareMessage.setUserid(userid);
ResponseMessage responseMessage = communicateService.sendMessageToSite(sourceId, adjustShareMessage);
if(responseMessage.isResult())
response.success("分享码调整成功");
else
response.failure(responseMessage.get("cause"));
}
return response.toJSONString();
}
public String cancelShare(String shareCode, int userid){
Response response = new Response();
User user = communicateService.userid2user.get(userid);
int sourceId;
if(user.getId() != 1){
sourceId = user.getSiteId();
} else {
String[] temp = shareCode.split(":");
sourceId = Integer.parseInt(temp[0]);
shareCode = temp[1];
}
if(sourceId == 1){
ShareFile shareFile;
if((shareFile = shareFileMapper.selectShareFileByShareCode(shareCode)) != null){
if(userid != 1 && shareFile.getSharer() != userid)
return response.failure("非法操作").toJSONString();
shareFileMapper.deleteShareFile(shareCode);
shareFileMapper.deleteShareFileRecord(shareCode);
response.success("删除分享码成功");
}else{
response.failure("分享码不存在,可能已过期");
}
}else{
CancelShareMessage cancelShareMessage = new CancelShareMessage();
cancelShareMessage.setShareCode(shareCode);
cancelShareMessage.setUserid(userid);
ResponseMessage responseMessage = communicateService.sendMessageToSite(sourceId, cancelShareMessage);
if(responseMessage.isResult())
response.success("取消分享码成功");
else
response.failure(responseMessage.get("cause"));
}
return response.toJSONString();
}
public String getShareFile(int sourceId, int userid) {
Response response = new Response();
User user = communicateService.userid2user.get(userid);
String path = ""; //用户存储路径,需要加上去进行替换,防止泄露根路径
HashMap<Integer, String> userid2username = new HashMap<>();
if(user.getId() != 1) {
sourceId = user.getSiteId();
path = user.getStoragePath();
}
if(sourceId == 1){
ShareFile[] shareFiles;
if(userid == 1)
shareFiles = shareFileMapper.selectAllShareFile();
else
shareFiles = shareFileMapper.selectAllShareFileByUserid(userid);
if(shareFiles.length > 0) {
for (ShareFile shareFile : shareFiles) {
shareFile.setFilePath(shareFile.getFilePath().replace(storagePath + path, "/"));
shareFile.setShareFileDownloadRecords(shareFileMapper.selectDownloadRecord(shareFile.getShareCode()));
String username = userid2username.get(shareFile.getSharer());
if(username == null){
username = userMapper.selectUserById(shareFile.getSharer()).getUsername();
userid2username.put(shareFile.getSharer(), username);
}
shareFile.setUsername(username);
}
response.success(objectMapper.valueToTree(Arrays.stream(shareFiles).sorted(Comparator.comparing(ShareFile::getFilePath)).toArray()));
}
else
response.failure();
}else {
ShareFileQueryMessage shareFileQueryMessage = new ShareFileQueryMessage();
shareFileQueryMessage.queryShareFiles(userid, user.getUsername(), user.getStoragePath());
ResponseMessage responseMessage = communicateService.sendMessageToSite(sourceId, shareFileQueryMessage);
if (responseMessage.isResult())
try {
response.success(objectMapper.readTree(responseMessage.get("data")));
}catch (Exception e){response.failure("查询失败:" + e.getMessage());}
else
response.failure();
}
return response.toJSONString();
}
}
@@ -0,0 +1,186 @@
package com.lion.sns.service;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.PathUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lion.sns.ScalableNetworkStorageApplication;
import com.lion.sns.dao.CustomConfigurationMapper;
import com.lion.sns.dao.ShareFileMapper;
import com.lion.sns.dao.SiteMapper;
import com.lion.sns.message.ConfigMessage;
import com.lion.sns.message.ResponseMessage;
import com.lion.sns.message.UnPairMessage;
import com.lion.sns.pojo.ShareFile;
import com.lion.sns.pojo.Site;
import com.lion.sns.pojo.User;
import com.lion.sns.util.CustomUtil;
import com.lion.sns.util.Response;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
@Service
public class SiteService {
@Resource
SiteMapper siteMapper;
@Resource
ShareFileMapper shareFileMapper;
@Resource
CommunicateService communicateService;
@Resource
CustomConfigurationMapper customConfigurationMapper;
ObjectMapper objectMapper = CustomUtil.objectMapper;
public String alterSite(Site site){
Response response = new Response();
Site originalSite = siteMapper.selectSiteById(site.getId());
if(originalSite == null)
return response.failure("子服务器不存在").toJSONString();
if(site.getHostname() != null)
originalSite.setHostname(site.getHostname());
if (site.getDomain() != null) {
if (site.getDomain().equals("空"))
originalSite.setDomain(null);
else
originalSite.setDomain(site.getDomain());
}
if(site.getReverseProxyPrefix() != null) {
if (site.getReverseProxyPrefix().equals("空"))
originalSite.setReverseProxyPrefix(null);
else
originalSite.setReverseProxyPrefix(site.getReverseProxyPrefix());
}
if(site.getAvailableSpace() != 0){ //调整容量
if(site.getAvailableSpace() > 0){ //增加容量
File file = new File(originalSite.getStoragePath());
long totalSpace = file.getTotalSpace();
if(originalSite.getTotalSpace() + site.getAvailableSpace() > totalSpace)
return response.failure("容量超出上限,请重新调整").toJSONString();
} else //减少容量
if(originalSite.getAvailableSpace() + site.getAvailableSpace() < 0)
return response.failure("减少空间不能小于可分配空间,可以考虑回收用户空间").toJSONString();
originalSite.setTotalSpace(originalSite.getTotalSpace() + site.getAvailableSpace());
originalSite.setAvailableSpace(originalSite.getAvailableSpace() + site.getAvailableSpace());
}
if(site.getStoragePath() != null){
if(originalSite.getId() == 1){ //本机
//判断目标路径是否存在
File newDirectory = new File(site.getStoragePath());
File oldDirectory = new File(originalSite.getStoragePath());
if(PathUtil.isSub(oldDirectory.toPath(), newDirectory.toPath()))
return response.failure("新的路径不能是之前路径的子路径").toJSONString();
if(newDirectory.exists())
return response.failure("目标路径已存在").toJSONString();
//判断空间是否足够
newDirectory.mkdirs();
long totalSize = CustomUtil.calculateDirectorySize(oldDirectory.getAbsolutePath());
if(totalSize > newDirectory.getFreeSpace())
return response.failure("目标路径可用空间小于当前文件夹总大小").toJSONString();
newDirectory.delete();
//移动文件
oldDirectory.renameTo(newDirectory);
//清除原分享码
ArrayList<ShareFile> shareFiles = shareFileMapper.selectShareFilesByFilePath(originalSite.getStoragePath());
for (ShareFile shareFile : shareFiles) {
shareFileMapper.deleteShareFile(shareFile.getShareCode());
shareFileMapper.deleteShareFileRecord(shareFile.getShareCode());
}
originalSite.setStoragePath(site.getStoragePath());
siteMapper.updateSite(originalSite);
customConfigurationMapper.updateValue(CustomConfigurationMapper.PATH, site.getStoragePath());
new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ScalableNetworkStorageApplication.restart();
}).start();
return response.success("更新配置成功").toJSONString();
} else {
ConfigMessage configMessage = new ConfigMessage();
configMessage.moveSitePath(originalSite.getStoragePath(), site.getStoragePath());
ResponseMessage responseMessage = communicateService.sendMessageToSite(originalSite.getId(), configMessage);
if(!responseMessage.isResult())
return response.failure(responseMessage.get("cause")).toJSONString();
}
//写入
originalSite.setStoragePath(site.getStoragePath());
}
siteMapper.updateSite(originalSite);
response.success("更新配置成功");
return response.toJSONString();
}
public String unpairSite(Site site){
Response response = new Response();
Site originalSite = siteMapper.selectSiteById(site.getId());
if(originalSite == null)
return response.failure("子服务器不存在").toJSONString();
communicateService.sendMessageToSite(site.getId(), new UnPairMessage());
siteMapper.deleteSiteById(site.getId());
response.success("移除子服务器成功");
return response.toJSONString();
}
public String getSites() {
Response response = new Response();
ArrayList<Site> sites = siteMapper.selectAllSite();
HashMap<Integer, String> ips = communicateService.ips;
for (Site site : sites)
if(ips.containsKey(site.getId()))
site.setOnline(true);
response.success(objectMapper.valueToTree(sites));
return response.toJSONString();
}
public String getSites(int userid) {
Response response = new Response();
User user = communicateService.userid2user.get(userid);
ArrayList<Site> sites = siteMapper.selectAllSite();
ArrayList<Site> result = new ArrayList<>();
HashMap<Integer, String> ips = communicateService.ips;
if(user.getId() != 1)
for (Site site : sites) {
if(user.getSiteId() == site.getId()) {
result.add(site);
if (ips.containsKey(site.getId()))
site.setOnline(true);
}
}
else
for (Site site : sites) {
if (ips.containsKey(site.getId()))
site.setOnline(true);
result.add(site);
}
response.success(objectMapper.valueToTree(result));
return response.toJSONString();
}
}
@@ -0,0 +1,146 @@
package com.lion.sns.service;
import com.lion.sns.pojo.Task;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class TaskService {
ConcurrentHashMap<Integer, Task> tasks;
ScheduledExecutorService thread;
@Resource
WebSocketService webSocketService;
ReentrantLock lock;
HashMap<Integer, Integer> taskId2Site;
public TaskService(){
tasks = new ConcurrentHashMap<>();
taskId2Site = new HashMap<>();
lock = new ReentrantLock();
thread = Executors.newScheduledThreadPool(1);
thread.scheduleAtFixedRate(this::calculateProgress, 0, 1, TimeUnit.SECONDS);
}
public void calculateProgress(){
Set<Integer> taskIds = tasks.keySet();
for(Integer taskId: taskIds){
Task task = tasks.get(taskId);
//不处理非本地任务,已完成任务,未在进行中任务 complete指任务进度是否到100 status代表成功或者失败
if(!task.isLocal() || 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 putTask(Task task){
tasks.put(task.getTaskId(), task);
}
public void submitTask(Task task){
tasks.put(task.getTaskId(), task);
}
//判断工作线程是否结束:当所有本地任务未进行时,即工作线程结束
public boolean isThreadStop(){
lock.lock();
for (Task task : tasks.values())
if(task.isProceeding())
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 putSiteTask(int site, int taskId){
taskId2Site.put(taskId, site);
}
public int getSiteId(int taskId){
return taskId2Site.get(taskId);
}
public Task getTask(int taskId){
return tasks.get(taskId);
}
public ArrayList<Task> getTasks(int[] taskIds){
ArrayList<Task> tasks = new ArrayList<>();
for (int taskId : taskIds)
tasks.add(this.tasks.get(taskId));
return tasks;
}
public ArrayList<Task> getTasks(int[] taskIds, int userid){
ArrayList<Task> tasks = new ArrayList<>();
Task task;
for (int taskId : taskIds) {
task = this.tasks.get(taskId);
if(task.getUserid() == userid)
tasks.add(task);
}
return tasks;
}
public void removeTask(int id){
Task task;
if((task = tasks.remove(id)) != null)
if(task.isLocal()) //改为负数
task.getProceed().set(-10086);
}
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){
Task task = tasks.get(taskId);
if(task != null) {
task.setStatus("success");
task.setComplete(true);
task.setPercentage(100);
}
}
}
@@ -0,0 +1,325 @@
package com.lion.sns.service;
import cn.hutool.core.io.FileUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lion.sns.dao.CustomConfigurationMapper;
import com.lion.sns.dao.ShareFileMapper;
import com.lion.sns.dao.SiteMapper;
import com.lion.sns.dao.UserMapper;
import com.lion.sns.message.ConfigMessage;
import com.lion.sns.message.DynamicConfigMessage;
import com.lion.sns.message.FileOperateMessage;
import com.lion.sns.message.ResponseMessage;
import com.lion.sns.pojo.ShareFile;
import com.lion.sns.pojo.Site;
import com.lion.sns.pojo.User;
import com.lion.sns.util.CustomUtil;
import com.lion.sns.util.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.ArrayList;
@Service
public class UserService {
@Resource
UserMapper userMapper;
@Resource
SiteMapper siteMapper;
@Resource
CommunicateService communicateService;
CustomConfigurationMapper customConfigurationMapper;
@Resource
ShareFileMapper shareFileMapper;
ObjectMapper objectMapper = CustomUtil.objectMapper;
String storagePath;
public UserService(CustomConfigurationMapper customConfigurationMapper){
this.customConfigurationMapper = customConfigurationMapper;
storagePath = customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH);
}
public String createUser(User user){
Response response = new Response();
user.setPasscode(CustomUtil.toMD5(user.getPasscode()));
Site site = siteMapper.selectSiteById(user.getSiteId());
if(site.getAvailableSpace() < user.getAvailableSpace())
return response.failure("该用户所绑定的服务器可用空间不足").toJSONString();
site.setAvailableSpace(site.getAvailableSpace() - user.getAvailableSpace());
if(user.getStoragePath().startsWith("/") || user.getStoragePath().startsWith("\\"))
user.setStoragePath(user.getStoragePath().substring(1));
if(site.getId() != 1) {
FileOperateMessage fileOperateMessage = new FileOperateMessage();
fileOperateMessage.createFolder(user.getStoragePath());
ResponseMessage responseMessage = communicateService.sendMessageToSite(user.getSiteId(), fileOperateMessage);
if(!responseMessage.isResult())
return response.failure("创建存储路径失败:" + responseMessage.get("cause")).toJSONString();
} else {
File file = new File(customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH), user.getStoragePath());
if(file.exists())
return response.failure("创建存储路径失败:路径已存在").toJSONString();
file.mkdirs();
}
userMapper.insertUser(user);
siteMapper.updateSite(site);
response.success("创建用户成功");
response.set("user", objectMapper.valueToTree(user).toString());
return response.toJSONString();
}
public String getUsers(){
Response response = new Response();
User[] users = userMapper.selectAllUser();
if(users.length == 0)
response.failure();
else
response.success(objectMapper.valueToTree(users));
return response.toJSONString();
}
public String getUser(int userid) {
Response response = new Response();
User user = userMapper.selectUserById(userid);
response.success(objectMapper.valueToTree(user));
return response.toJSONString();
}
public String alterUser(User user){
Response response = new Response();
User originUser = userMapper.selectUserById(user.getId());
Site oldSite = null;
Site newSite = null;
if(originUser == null){
response.failure("该用户不存在");
return response.toJSONString();
}
if(user.getUsername() != null){
originUser.setUsername(user.getUsername());
}
if(user.getPasscode() != null){
originUser.setPasscode(CustomUtil.toMD5(user.getPasscode()));
}
if(user.getTotalSpace() != 0){
oldSite = siteMapper.selectSiteById(originUser.getSiteId());
if(user.getTotalSpace() > 0) { //增加容量 不超过site可分配容量
if(user.getTotalSpace() > oldSite.getAvailableSpace())
return response.failure("容量调整错误,增加空间不能超过服务器可分配空间,请先释放其他用户空间或增大服务器可分配看见后再尝试").toJSONString();
} else { //减少容量 可用容量最低为0
if (user.getTotalSpace() + originUser.getAvailableSpace() < 0)
return response.failure("容量调整错误,释放空间不能小于用户可用空间,请先删除用户空间并同步后再尝试").toJSONString();
}
//调整可用容量以及总容量
originUser.setAvailableSpace(originUser.getAvailableSpace() + user.getTotalSpace());
originUser.setTotalSpace(originUser.getTotalSpace() + user.getTotalSpace());
oldSite.setAvailableSpace(oldSite.getAvailableSpace() - user.getTotalSpace());
}
if(user.getStoragePath() != null){
if(originUser.getSiteId() == 1) {
//删除旧的分享码
ArrayList<ShareFile> shareFiles = shareFileMapper.selectShareFilesByFilePath(storagePath + originUser.getStoragePath());
for (ShareFile shareFile : shareFiles) {
shareFileMapper.deleteShareFile(shareFile.getShareCode());
shareFileMapper.deleteShareFileRecord(shareFile.getShareCode());
}
//转移旧的文件
File oldDirectory = new File(storagePath + originUser.getStoragePath());
File newDirectory = new File(storagePath + user.getStoragePath());
if (newDirectory.exists())
return response.failure("更换存储路径错误,目标路径已存在").toJSONString();
oldDirectory.renameTo(newDirectory);
} else {
ConfigMessage configMessage = new ConfigMessage();
configMessage.moveUserPath(originUser.getStoragePath(), user.getStoragePath());
ResponseMessage responseMessage = communicateService.sendMessageToSite(originUser.getSiteId(), configMessage);
if(!responseMessage.isResult())
return response.failure(responseMessage.get("cause")).toJSONString();
}
originUser.setStoragePath(user.getStoragePath());
}
if(user.getSiteId() != 0){
oldSite = siteMapper.selectSiteById(originUser.getSiteId());
newSite = siteMapper.selectSiteById(user.getSiteId());
if(newSite.getAvailableSpace() < originUser.getTotalSpace())
return response.failure("目标服务器可分配空间小于用户总空间,请重新调整").toJSONString();
//新建远程文件夹
FileOperateMessage fileOperateMessage = new FileOperateMessage();
fileOperateMessage.createFolder(originUser.getStoragePath());
ResponseMessage responseMessage = communicateService.sendMessageToSite(user.getSiteId(), fileOperateMessage);
if(!responseMessage.isResult()){
return response.failure("目标服务器文件夹创建失败:" + responseMessage.get("cause")).toJSONString();
}
//删除原文件夹
FileUtil.del(new File(storagePath + originUser.getStoragePath()));
//删除原文件夹下文件的分享码以及下载记录
ArrayList<ShareFile> shareFiles = shareFileMapper.selectShareFilesByFilePath(storagePath + originUser.getStoragePath());
for (ShareFile shareFile : shareFiles) {
shareFileMapper.deleteShareFile(shareFile.getShareCode());
shareFileMapper.deleteShareFileRecord(shareFile.getShareCode());
}
//释放原有空间
oldSite.setAvailableSpace(oldSite.getAvailableSpace() + originUser.getTotalSpace());
originUser.setAvailableSpace(originUser.getTotalSpace());
//分配空间
newSite.setAvailableSpace(newSite.getAvailableSpace() - originUser.getTotalSpace());
//更新绑定服务器
originUser.setSiteId(user.getSiteId());
}
userMapper.updateUser(originUser);
if(oldSite != null)
siteMapper.updateSite(oldSite);
if(newSite != null)
siteMapper.updateSite(newSite);
response.success("更新用户配置成功");
return response.toJSONString();
}
public String removeUser(int userid){
Response response = new Response();
User user = userMapper.selectUserById(userid);
if(user == null)
return response.failure("用户不存在").toJSONString();
//清除用户文件
if(user.getSiteId() == 1) {
ArrayList<ShareFile> shareFiles = shareFileMapper.selectShareFilesByFilePath(user.getStoragePath());
for (ShareFile shareFile : shareFiles) {
shareFileMapper.deleteShareFile(shareFile.getShareCode());
shareFileMapper.deleteShareFileRecord(shareFile.getShareCode());
}
FileUtil.del(customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH) + user.getStoragePath());
} else {
FileOperateMessage fileOperateMessage = new FileOperateMessage();
fileOperateMessage.deleteFiles(new String[]{user.getStoragePath()});
communicateService.sendMessageToSite(user.getSiteId(), fileOperateMessage);
}
Site site = siteMapper.selectSiteById(user.getSiteId());
site.setAvailableSpace(site.getAvailableSpace() + user.getTotalSpace());
siteMapper.updateSite(site);
userMapper.deleteUser(userid);
return response.success("移除用户成功,服务器" + site.getHostname() + "释放" + CustomUtil.fileSizeToString(user.getTotalSpace())).toJSONString();
}
public String login(User user, HttpSession session){
Response response = new Response();
user.setPasscode(CustomUtil.toMD5(user.getPasscode()));
if ((user = userMapper.loginByUser(user)) != null) { //id storagePath siteId
if(!communicateService.ips.containsKey(user.getSiteId()))
return response.failure("登陆失败:绑定服务器离线,请联系管理员解决").toJSONString();
String sessionId = session.getId().substring(0, 8);
session.setAttribute("id", user.getId());
session.setAttribute("sessionId", sessionId.substring(0, 8));
response.success("登陆成功");
response.set("sessionId", sessionId);
response.set("isAdmin", user.getUsername().equals("admin"));
DynamicConfigMessage config = new DynamicConfigMessage();
config.addUser(sessionId, user);
if(user.getId() == 1) { //管理员将sessionId发送到全部子服务器
communicateService.sendMessageToAllSite(config);
session.setAttribute("storagePath", "");
} else { //用户则只发送到绑定子服务器
communicateService.sendMessageToSite(user.getSiteId(), config);
session.setAttribute("storagePath", user.getStoragePath());
}
communicateService.interceptor.sessionId2user.put(sessionId, user);
communicateService.userid2user.put(user.getId(), user);
communicateService.userid2httpSession.put(user.getId(), session);
} else
response.failure("登录失败:用户名或密码错误");
return response.toJSONString();
}
public void logout(String sessionId){
sessionId = sessionId.substring(0, 8);
User user = communicateService.interceptor.sessionId2user.get(sessionId);
if(user == null)
return;
DynamicConfigMessage config = new DynamicConfigMessage();
config.removeUser(sessionId);
if(user.getId() == 1) //如果是管理员,则全部子服务器都要移除sessionId
communicateService.sendMessageToAllSite(config);
else if(user.getSiteId() != 1) //如果是绑定子服务器的用户,则对应子服务器登出
communicateService.sendMessageToSite(user.getSiteId(), config);
communicateService.interceptor.sessionId2user.remove(sessionId);
communicateService.userid2user.remove(user.getId());
communicateService.webSocketService.kickUser(user.getId());
}
public String alterPasscode(int userid, String passcode) {
Response response = new Response();
User user = userMapper.selectUserById(userid);
if(user == null)
return response.failure("用户不存在").toJSONString();
user.setPasscode(CustomUtil.toMD5(passcode));
userMapper.updateUser(user);
return response.success("密码修改成功").toJSONString();
}
public String verifySpace(int userid) {
Response response = new Response();
User user = userMapper.selectUserById(userid);
long total;
if(user.getSiteId() == 1){
total = CustomUtil.calculateDirectorySize(storagePath + user.getStoragePath());
} else {
ConfigMessage configMessage = new ConfigMessage();
configMessage.verifyUserSpace(user.getStoragePath());
ResponseMessage responseMessage = communicateService.sendMessageToSite(user.getSiteId(), configMessage);
if(!responseMessage.isResult())
return response.failure(responseMessage.get("cause")).toJSONString();
total = Long.parseLong(responseMessage.get("total"));
}
if((user.getTotalSpace() - user.getAvailableSpace()) != total) {
user.setAvailableSpace(user.getTotalSpace() - total);
userMapper.updateUserSpace(user);
response.success("校准完成,现可用空间为:" + CustomUtil.fileSizeToString(user.getAvailableSpace()));
} else {
response.success("校准完成,空间一致");
}
return response.toJSONString();
}
}
@@ -0,0 +1,292 @@
package com.lion.sns.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.lion.sns.dao.CustomConfigurationMapper;
import com.lion.sns.dao.UserMapper;
import com.lion.sns.message.AbstractMessage;
import com.lion.sns.message.StatusMessage;
import com.lion.sns.message.PairMessage;
import com.lion.sns.message.ResponseMessage;
import com.lion.sns.pojo.Task;
import com.lion.sns.pojo.User;
import com.lion.sns.util.CustomUtil;
import com.lion.sns.util.IoUtil;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
@Slf4j
@Service
public class WebSocketService implements WebSocketHandler {
/**
* websocket主要发送配对请求,负载消息,进度
* 配对请求只发给管理员
* 负载消息发给全部
* 进度发给发起的那个session
*/
@Resource
UserMapper userMapper;
HashMap<Integer, Task> tasks;
HashMap<Integer, WebSocketSession> userid2session;
HashMap<Integer, User> onlineUsers;
String storagePath;
CustomConfigurationMapper customConfigurationMapper;
Function<Integer, ?> logoutFunction;
private WebSocketSession session;
public final Map<Integer, Promise<AbstractMessage>> pairMessages;
private Map<Integer, StatusMessage> loadMap;
private final ScheduledExecutorService scheduledThread;
private final ObjectMapper objectMapper;
public WebSocketService(CustomConfigurationMapper customConfigurationMapper) {
this.customConfigurationMapper = customConfigurationMapper;
storagePath = customConfigurationMapper.selectValue(CustomConfigurationMapper.PATH);
objectMapper = CustomUtil.objectMapper;
pairMessages = new HashMap<>();
tasks = new HashMap<>();
scheduledThread = Executors.newScheduledThreadPool(1);
userid2session = new HashMap<>();
onlineUsers = new HashMap<>();
}
@Override
public void afterConnectionEstablished(WebSocketSession session) {
log.info(session.getId());
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
log.info("{}:{}", session.getId(), message.getPayload());
JsonNode jsonNode = objectMapper.readTree(message.getPayload().toString());
String messageType = jsonNode.get("type").asText();
switch (messageType) {
case MESSAGE_TYPE.INIT -> {
String username = jsonNode.get("username").asText();
String passcode = jsonNode.get("passcode").asText();
passcode = CustomUtil.toMD5(passcode);
User user = userMapper.login(username, passcode);
if (user == null) {
session.sendMessage(new TextMessage("{\"result\": \"failure\"}"));
session.close();
} else {
session.sendMessage(new TextMessage("{\"result\": \"success\"}"));
userid2session.put(user.getId(), session);
onlineUsers.put(user.getId(), user);
if(username.equals("admin"))
this.session = session;
}
}
case MESSAGE_TYPE.PAIR -> {
int id = Integer.parseInt(jsonNode.get("messageId").asText());
Promise<AbstractMessage> promise = pairMessages.remove(id);
if (promise == null)
session.sendMessage(new TextMessage("{\"type\": \"error\", \"data\": \"配对消息不存在\"}"));
else {
ResponseMessage response = new ResponseMessage();
if(jsonNode.get("result").asText().equals("true"))
promise.setSuccess(response.put("totalSpace", jsonNode.get("totalSpace").asText()).success());
else
promise.setSuccess(response.failure("拒绝配对"));
}
}
default -> log.error("消息类型错误, id:" + session.getId());
}
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
if(this.session != null && session.getId().equals(this.session.getId()))
this.session = null;
AtomicInteger id = new AtomicInteger(-1);
userid2session.forEach((k, v) -> {
if(v.getId().equals(session.getId()))
id.set(k);
});
if(id.get() != -1) {
userid2session.remove(id.get());
onlineUsers.remove(id.get());
logoutFunction.apply(id.get());
}
}
@Override
public boolean supportsPartialMessages() {
return false;
}
public void statusAlter(String hostname, boolean isOnline){
ObjectNode message = objectMapper.createObjectNode();
message.put("type", MESSAGE_TYPE.STATUS_ALTER);
message.put("status", isOnline?"online":"offline");
message.put("hostname", hostname);
if(session != null && session.isOpen())
try {
session.sendMessage(new TextMessage(objectMapper.valueToTree(message).toString()));
}catch (Exception ignored){}
}
public void sendPairMessage(PairMessage pairMessage, DefaultPromise<AbstractMessage> result){
ObjectNode message = objectMapper.createObjectNode();
message.set("data", objectMapper.valueToTree(pairMessage));
message.put("type", MESSAGE_TYPE.PAIR);
try {
session.sendMessage(new TextMessage(objectMapper.valueToTree(message).toString()));
pairMessages.put(pairMessage.getMessageId(), result);
}catch (IOException e){
result.setFailure(e);
}catch (NullPointerException e){
e.printStackTrace();
result.setFailure(new Exception("auth is offline"));
}
}
public void sendLoadInformation(){
//两种状态:1.管理员看全部状态 2.用户看自己所绑定的服务器状态
if(session == null && onlineUsers.isEmpty())
return;
loadMap.put(1, IoUtil.generateLoadMessage(1, storagePath));
ObjectNode totalStatus = objectMapper.createObjectNode();
HashMap<Integer, ObjectNode> userid2status = new HashMap<>();
totalStatus.set("1", objectMapper.valueToTree(loadMap.get(1)));
totalStatus.put("type", MESSAGE_TYPE.STATUS);
loadMap.forEach((k, v) -> totalStatus.set(String.valueOf(k), objectMapper.valueToTree(v)));
for (User user : onlineUsers.values()) {
if(loadMap.containsKey(user.getSiteId())) {
ObjectNode status = objectMapper.createObjectNode();
status.put("type", MESSAGE_TYPE.STATUS);
status.set("1", objectMapper.valueToTree(loadMap.get(user.getSiteId())));
userid2status.put(user.getId(), status);
}
}
try {
if(session != null && session.isOpen())
session.sendMessage(new TextMessage(totalStatus.toString()));
userid2session.forEach((k, v) -> {
if(k != 1 && v != null && v.isOpen()){
try {
v.sendMessage(new TextMessage(userid2status.get(k).toString()));
} catch (IOException e) {log.error(e.getMessage());}
}
});
}catch (IOException e){log.error(e.getMessage());}
}
public void sendTaskInformation(){
if(tasks.isEmpty() || (session == null && onlineUsers.isEmpty()))
return;
//两种信息: 1.管理员查看全部任务 2.用户查看自己提交的任务
ObjectNode totalTask = objectMapper.createObjectNode();
totalTask.put("type", MESSAGE_TYPE.TASK);
totalTask.set("tasks", objectMapper.valueToTree(tasks));
HashMap<Integer, ArrayList<Task>> userid2tasks = new HashMap<>();
tasks.forEach((k, v) -> {
ArrayList<Task> tasks = userid2tasks.computeIfAbsent(v.getUserid(), k1 -> new ArrayList<>());
tasks.add(v);
});
try {
if(session != null)
session.sendMessage(new TextMessage(totalTask.toString()));
WebSocketSession webSocketSession;
for (Integer userid : userid2tasks.keySet())
if(userid != 1 && (webSocketSession = userid2session.get(userid)) != null && webSocketSession.isOpen()){
ObjectNode userTask = objectMapper.createObjectNode();
userTask.put("type", MESSAGE_TYPE.TASK);
userTask.set("tasks", objectMapper.valueToTree(userid2tasks.get(userid)));
webSocketSession.sendMessage(new TextMessage(userTask.toString()));
}
} catch (IOException e) {
log.error(e.getMessage());
}
}
public void initScheduleThread(HashMap<Integer, StatusMessage> loadMap){
this.loadMap = loadMap;
scheduledThread.scheduleAtFixedRate(() -> {
try {
sendLoadInformation();
sendTaskInformation();
}catch (Exception e){
e.printStackTrace();
}
}, 0, 2, TimeUnit.SECONDS);
}
public void putTask(Task task){
tasks.put(task.getTaskId(), task);
}
public void removeTask(int taskId){
tasks.remove(taskId);
}
public void removeTasks(int[] taskIds){
for (int taskId : taskIds)
removeTask(taskId);
}
public void removeTasks(int[] taskIds, int userid){
Task task;
for (int taskId : taskIds)
if((task = tasks.get(taskId)) != null && task.getUserid() == userid)
tasks.remove(taskId);
}
public void kickUser(int userid){
WebSocketSession webSocketSession = userid2session.remove(userid);
if(webSocketSession != null && webSocketSession.isOpen())
try {
webSocketSession.close();
}catch (IOException e){
log.error(e.getMessage());
}
}
static class MESSAGE_TYPE{
final static String PAIR = "pair";
final static String INIT = "init";
final static String STATUS = "status";
final static String STATUS_ALTER = "statusAlter";
final static String TASK = "task";
}
}
@@ -0,0 +1,228 @@
package com.lion.sns.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lion.sns.ScalableNetworkStorageApplication;
import com.lion.sns.dao.CustomConfigurationMapper;
import com.lion.sns.dao.SiteMapper;
import com.lion.sns.dao.UserMapper;
import com.lion.sns.message.PairMessage;
import com.lion.sns.pojo.Site;
import com.lion.sns.pojo.User;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import org.springframework.util.DigestUtils;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.nio.charset.StandardCharsets;
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Data
public class CustomUtil {
public static final double ONE_KB = 1024;
public static final double ONE_MB = ONE_KB * ONE_KB;
public static final double ONE_GB = ONE_KB * ONE_MB;
public static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static AtomicInteger id;
public static ObjectMapper objectMapper = new ObjectMapper();
static {
id = new AtomicInteger();
}
public static String fileSizeToString(long fileSize){
if (fileSize < ONE_KB) {
return fileSize + "B";
} else if (fileSize >= ONE_KB && fileSize < ONE_MB) {
return String.format("%.2f", (fileSize / ONE_KB)) + "KB";
} else if (fileSize >= ONE_MB && fileSize < ONE_GB) {
return String.format("%.2f", (fileSize / ONE_MB)) + "MB";
} else{
return String.format("%.2f", (fileSize / ONE_GB)) + "GB";
}
}
public static long stringToFileSize(String fileSizeString){
String preNum = "";
String unit = "";
Pattern patternWithDot = Pattern.compile("(\\d+.\\d+)([A-Z]+)");
Matcher matcherWithDot = patternWithDot.matcher(fileSizeString);
if(matcherWithDot.find()){
preNum = matcherWithDot.group(1);
unit = matcherWithDot.group(2);
}
else {
Pattern patternWithoutDot = Pattern.compile("(\\d+)([A-Z]+)");
Matcher matcherWithoutDot = patternWithoutDot.matcher(fileSizeString);
if (matcherWithoutDot.find()) {
preNum = matcherWithoutDot.group(1);
unit = matcherWithoutDot.group(2);
}
}
double num = Double.parseDouble(preNum);
return switch (unit) {
case "B" -> (long) num;
case "KB", "KiB" -> (long) (ONE_KB * num);
case "MB", "MiB" -> (long) (ONE_MB * num);
case "GB", "GiB" -> (long) (ONE_GB * num);
default -> 0;
};
}
public static DateTimeFormatter dateTimeFormatter(){
return dateTimeFormatter;
}
public static String now(){
return dateTimeFormatter.format(LocalDateTime.now());
}
/**
* 寻找一定数量的可用端口
*
* @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 int getId(){
return id.getAndIncrement();
}
//id偏移
public static int getId(int count){
return CustomUtil.id.getAndAdd(count);
}
public static void initSns(CustomConfigurationMapper customConfigurationMapper, SiteMapper siteMapper, UserMapper userMapper) {
//1.路径
Scanner scanner = new Scanner(System.in);
System.out.println("请输入存储路径:");
String path = scanner.nextLine();
//2.passcode
System.out.println("请输入管理员密码");
String passcode = scanner.nextLine();
passcode = toMD5(passcode);
User admin = new User();
admin.setSiteId(1);
admin.setUsername("admin");
admin.setPasscode(passcode);
admin.setStoragePath("/");
admin.setAvailableSpace(999999999);
admin.setTotalSpace(999999999);
//3.
File storage = new File(path);
PairMessage pairMessage = IoUtil.generatePairMessage();
System.out.println("当前服务器信息:" + pairMessage);
System.out.println("当前路径可用空间:" + fileSizeToString(storage.getFreeSpace()));
System.out.println("请输入总可分配空间单位:(KB MB GB)");
String unit = scanner.nextLine();
if(!(unit.equals("KB") || unit.equals("MB") || unit.equals("GB"))){
System.out.println("单位输入错误,程序退出");
System.exit(0);
}
System.out.println("请输入总可分配空间:(数值)");
int size = Integer.parseInt(scanner.nextLine());
long totalSpace = switch (unit) {
case "KB" -> size * 1024L;
case "MB" -> size * 1024 * 1024L;
case "GB" -> size * 1024 * 1024 * 1024L;
default -> 0;
};
if(totalSpace > storage.getFreeSpace()){
System.out.println("输入可用空间大于服务器可用空间, 程序退出");
System.exit(0);
}
Site site = Site.generateSite(IoUtil.generatePairMessage());
site.setId(1);
site.setLastOnline(new Date());
site.setStoragePath(path);
site.setDomain(null);
site.setReverseProxyPrefix(null);
site.setTotalSpace(totalSpace);
site.setAvailableSpace(totalSpace);
customConfigurationMapper.updateValue(CustomConfigurationMapper.PATH, path);
userMapper.insertUser(admin);
siteMapper.insertSite(site);
System.out.println("初始化完成");
ScalableNetworkStorageApplication.restart();
}
public static String toMD5(String str){
return DigestUtils.md5DigestAsHex((str + "salt").getBytes(StandardCharsets.UTF_8));
}
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) throws IOException {
size.addAndGet(file.toFile().length());
return FileVisitResult.CONTINUE;
}
});
}catch (IOException e){
return -1;
}
return size.get();
}
}
+272
View File
@@ -0,0 +1,272 @@
package com.lion.sns.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.sns.message.StatusMessage;
import com.lion.sns.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.*;
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;
@Slf4j
@Data
public class IoUtil {
static long ioRead;
static long ioWrite;
static long networkSend;
static long networkReceive;
static NetworkIF networkIF;
static List<HWDiskStore> 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<NetworkIF> 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("ens3"))
networkIF = nif;
}
else
if(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 generateLoadMessage(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());
statusMessage.setMessageId(0);
statusMessage.setMessageType(0);
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 String getIp(){
return networkIF.getIPv4addr()[0].split("/")[0];
}
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();
}
}
}
}
@@ -0,0 +1,102 @@
package com.lion.sns.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.HashMap;
public class Response {
// HashMap<String, String> result;
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 void set(String key, Object value){
result.put(key, String.valueOf(value));
}
public String get(String key){
return result.get(key).toString();
}
public void setResult(String result){
this.result.put("result", result);
}
public Response success(){
setResult("success");
return this;
}
public Response success(String result){
success();
this.result.put("data", result);
return this;
}
public Response success(JsonNode objectNode){
success();
this.result.set("data", objectNode);
return this;
}
public void failure(){
setResult("failure");
}
public Response failure(String result){
failure();
this.result.put("data", result);
return this;
}
public String getResult(){
return result.get("data").toString();
}
public boolean isSuccess(){
return result.get("result").toString().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();
}
}
+21
View File
@@ -0,0 +1,21 @@
spring:
datasource:
driver-class-name: org.sqlite.JDBC
# url: jdbc:sqlite:D:\WorkSpace\code\Java\scalable-network-storage\src\main\resources\sns.db
url: jdbc:sqlite:sns.db
servlet:
multipart:
max-file-size: 10000MB
max-request-size: 10000MB
enabled: true
server:
tomcat:
max-http-form-post-size: 100000MB
max-swallow-size: 100000MB
error:
include-message: always
mybatis:
configuration:
map-underscore-to-camel-case: true
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+18
View File
@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SNS</title>
<script type="module" crossorigin src="/index.js"></script>
<link rel="stylesheet" href="/index.css">
</head>
<body>
<div class="dom"></div>
<div id="app">
</div>
</body>
</html>