first commit
This commit is contained in:
commit
ef6d4ed5af
106
pom.xml
Normal file
106
pom.xml
Normal 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("*");
|
||||
}
|
||||
}
|
||||
97
src/main/java/com/lion/sns/controller/FileController.java
Normal file
97
src/main/java/com/lion/sns/controller/FileController.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
68
src/main/java/com/lion/sns/controller/indexController.java
Normal file
68
src/main/java/com/lion/sns/controller/indexController.java
Normal file
@ -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);
|
||||
}
|
||||
58
src/main/java/com/lion/sns/dao/ShareFileMapper.java
Normal file
58
src/main/java/com/lion/sns/dao/ShareFileMapper.java
Normal file
@ -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);
|
||||
}
|
||||
41
src/main/java/com/lion/sns/dao/SiteMapper.java
Normal file
41
src/main/java/com/lion/sns/dao/SiteMapper.java
Normal file
@ -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);
|
||||
}
|
||||
38
src/main/java/com/lion/sns/dao/UserMapper.java
Normal file
38
src/main/java/com/lion/sns/dao/UserMapper.java
Normal file
@ -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);
|
||||
|
||||
}
|
||||
48
src/main/java/com/lion/sns/interceptor/Interceptor.java
Normal file
48
src/main/java/com/lion/sns/interceptor/Interceptor.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
39
src/main/java/com/lion/sns/message/AbstractMessage.java
Normal file
39
src/main/java/com/lion/sns/message/AbstractMessage.java
Normal file
@ -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;
|
||||
}
|
||||
14
src/main/java/com/lion/sns/message/AdjustShareMessage.java
Normal file
14
src/main/java/com/lion/sns/message/AdjustShareMessage.java
Normal file
@ -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;
|
||||
}
|
||||
12
src/main/java/com/lion/sns/message/CancelShareMessage.java
Normal file
12
src/main/java/com/lion/sns/message/CancelShareMessage.java
Normal file
@ -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;
|
||||
}
|
||||
15
src/main/java/com/lion/sns/message/CompressFileMessage.java
Normal file
15
src/main/java/com/lion/sns/message/CompressFileMessage.java
Normal file
@ -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;
|
||||
}
|
||||
36
src/main/java/com/lion/sns/message/ConfigMessage.java
Normal file
36
src/main/java/com/lion/sns/message/ConfigMessage.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
10
src/main/java/com/lion/sns/message/ConnectMessage.java
Normal file
10
src/main/java/com/lion/sns/message/ConnectMessage.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.lion.sns.message;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ConnectMessage extends AbstractMessage{
|
||||
{
|
||||
messageType = CONNECT_MESSAGE;
|
||||
}
|
||||
}
|
||||
56
src/main/java/com/lion/sns/message/DynamicConfigMessage.java
Normal file
56
src/main/java/com/lion/sns/message/DynamicConfigMessage.java
Normal file
@ -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;
|
||||
}
|
||||
15
src/main/java/com/lion/sns/message/ExtractFileMessage.java
Normal file
15
src/main/java/com/lion/sns/message/ExtractFileMessage.java
Normal file
@ -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;
|
||||
}
|
||||
32
src/main/java/com/lion/sns/message/FileOperateMessage.java
Normal file
32
src/main/java/com/lion/sns/message/FileOperateMessage.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
13
src/main/java/com/lion/sns/message/FileQueryMessage.java
Normal file
13
src/main/java/com/lion/sns/message/FileQueryMessage.java
Normal file
@ -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;
|
||||
}
|
||||
15
src/main/java/com/lion/sns/message/FileResponseMessage.java
Normal file
15
src/main/java/com/lion/sns/message/FileResponseMessage.java
Normal file
@ -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;
|
||||
}
|
||||
55
src/main/java/com/lion/sns/message/MessageCodec.java
Normal file
55
src/main/java/com/lion/sns/message/MessageCodec.java
Normal file
@ -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;
|
||||
}
|
||||
21
src/main/java/com/lion/sns/message/PairMessage.java
Normal file
21
src/main/java/com/lion/sns/message/PairMessage.java
Normal file
@ -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;
|
||||
}
|
||||
21
src/main/java/com/lion/sns/message/PairResultMessage.java
Normal file
21
src/main/java/com/lion/sns/message/PairResultMessage.java
Normal file
@ -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; //服务器总共可分配的容量
|
||||
}
|
||||
54
src/main/java/com/lion/sns/message/ResponseMessage.java
Normal file
54
src/main/java/com/lion/sns/message/ResponseMessage.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
15
src/main/java/com/lion/sns/message/ShareFileMessage.java
Normal file
15
src/main/java/com/lion/sns/message/ShareFileMessage.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
25
src/main/java/com/lion/sns/message/StatusMessage.java
Normal file
25
src/main/java/com/lion/sns/message/StatusMessage.java
Normal file
@ -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;
|
||||
}
|
||||
11
src/main/java/com/lion/sns/message/TaskCancelMessage.java
Normal file
11
src/main/java/com/lion/sns/message/TaskCancelMessage.java
Normal file
@ -0,0 +1,11 @@
|
||||
package com.lion.sns.message;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TaskCancelMessage extends AbstractMessage{
|
||||
{
|
||||
messageType = TASK_CANCEL_MESSAGE;
|
||||
}
|
||||
int[] taskIds;
|
||||
}
|
||||
14
src/main/java/com/lion/sns/message/TaskStatusMessage.java
Normal file
14
src/main/java/com/lion/sns/message/TaskStatusMessage.java
Normal file
@ -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;
|
||||
}
|
||||
10
src/main/java/com/lion/sns/message/UnPairMessage.java
Normal file
10
src/main/java/com/lion/sns/message/UnPairMessage.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.lion.sns.message;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UnPairMessage extends AbstractMessage{
|
||||
{
|
||||
messageType = UN_PAIR_MESSAGE;
|
||||
}
|
||||
}
|
||||
2
src/main/java/com/lion/sns/message/lombok.config
Normal file
2
src/main/java/com/lion/sns/message/lombok.config
Normal file
@ -0,0 +1,2 @@
|
||||
config.stopBubbling=true
|
||||
lombok.equalsAndHashCode.callSuper=call
|
||||
27
src/main/java/com/lion/sns/pojo/FileNode.java
Normal file
27
src/main/java/com/lion/sns/pojo/FileNode.java
Normal file
@ -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;
|
||||
}
|
||||
32
src/main/java/com/lion/sns/pojo/Progress.java
Normal file
32
src/main/java/com/lion/sns/pojo/Progress.java
Normal file
@ -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;
|
||||
}
|
||||
10
src/main/java/com/lion/sns/pojo/Record.java
Normal file
10
src/main/java/com/lion/sns/pojo/Record.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.lion.sns.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Record {
|
||||
int id;
|
||||
int operator;
|
||||
String operation;
|
||||
}
|
||||
23
src/main/java/com/lion/sns/pojo/ShareFile.java
Normal file
23
src/main/java/com/lion/sns/pojo/ShareFile.java
Normal file
@ -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;
|
||||
}
|
||||
11
src/main/java/com/lion/sns/pojo/ShareFileDownloadRecord.java
Normal file
11
src/main/java/com/lion/sns/pojo/ShareFileDownloadRecord.java
Normal file
@ -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
src/main/java/com/lion/sns/pojo/Site.java
Normal file
54
src/main/java/com/lion/sns/pojo/Site.java
Normal 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
src/main/java/com/lion/sns/pojo/Task.java
Normal file
117
src/main/java/com/lion/sns/pojo/Task.java
Normal 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
src/main/java/com/lion/sns/pojo/User.java
Normal file
14
src/main/java/com/lion/sns/pojo/User.java
Normal 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;
|
||||
}
|
||||
446
src/main/java/com/lion/sns/service/CommunicateService.java
Normal file
446
src/main/java/com/lion/sns/service/CommunicateService.java
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1285
src/main/java/com/lion/sns/service/FileService.java
Normal file
1285
src/main/java/com/lion/sns/service/FileService.java
Normal file
File diff suppressed because it is too large
Load Diff
276
src/main/java/com/lion/sns/service/ShareService.java
Normal file
276
src/main/java/com/lion/sns/service/ShareService.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
186
src/main/java/com/lion/sns/service/SiteService.java
Normal file
186
src/main/java/com/lion/sns/service/SiteService.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
146
src/main/java/com/lion/sns/service/TaskService.java
Normal file
146
src/main/java/com/lion/sns/service/TaskService.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
325
src/main/java/com/lion/sns/service/UserService.java
Normal file
325
src/main/java/com/lion/sns/service/UserService.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
292
src/main/java/com/lion/sns/service/WebSocketService.java
Normal file
292
src/main/java/com/lion/sns/service/WebSocketService.java
Normal file
@ -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";
|
||||
}
|
||||
}
|
||||
228
src/main/java/com/lion/sns/util/CustomUtil.java
Normal file
228
src/main/java/com/lion/sns/util/CustomUtil.java
Normal file
@ -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
src/main/java/com/lion/sns/util/IoUtil.java
Normal file
272
src/main/java/com/lion/sns/util/IoUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/main/java/com/lion/sns/util/Response.java
Normal file
102
src/main/java/com/lion/sns/util/Response.java
Normal file
@ -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
src/main/resources/application.yaml
Normal file
21
src/main/resources/application.yaml
Normal 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
|
||||
BIN
src/main/resources/sns-empty.db
Normal file
BIN
src/main/resources/sns-empty.db
Normal file
Binary file not shown.
BIN
src/main/resources/sns.db
Normal file
BIN
src/main/resources/sns.db
Normal file
Binary file not shown.
1
src/main/resources/static/index.css
Normal file
1
src/main/resources/static/index.css
Normal file
File diff suppressed because one or more lines are too long
65
src/main/resources/static/index.js
Normal file
65
src/main/resources/static/index.js
Normal file
File diff suppressed because one or more lines are too long
1
src/main/resources/static/vite.svg
Normal file
1
src/main/resources/static/vite.svg
Normal 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
src/main/resources/templates/index.html
Normal file
18
src/main/resources/templates/index.html
Normal 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>
|
||||
Loading…
Reference in New Issue
Block a user