第一个版本

This commit is contained in:
chuzhongzai 2023-07-30 17:53:03 +08:00
commit 59fde72a18
71 changed files with 4612 additions and 0 deletions

136
pom.xml Normal file
View File

@ -0,0 +1,136 @@
<?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.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lion</groupId>
<artifactId>LionWebsite</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>LionWebsite</name>
<description>LionWebsite</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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<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.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.14</version>
</dependency>
<dependency>
<groupId>org.im4java</groupId>
<artifactId>im4java</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.86.Final</version>
</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,18 @@
package com.lion.lionwebsite.Configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.exposedHeaders("*");
}
}

View File

@ -0,0 +1,46 @@
package com.lion.lionwebsite.Configuration;
import com.lion.lionwebsite.Domain.MaskDomain;
import com.lion.lionwebsite.Interceptor.HumanInterceptor;
import com.lion.lionwebsite.Interceptor.PersonalInterceptor;
import com.lion.lionwebsite.Interceptor.TaskHandlerInterceptor;
import com.lion.lionwebsite.Util.CustomUtil;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {
@Resource
TaskHandlerInterceptor taskHandlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getPersonalInterceptor()).addPathPatterns("/personal/**", "/remote/**");
registry.addInterceptor(taskHandlerInterceptor).addPathPatterns("/GalleryManage", "/validate");
registry.addInterceptor(getHumanInterceptor()).addPathPatterns("/", "/mobile");
}
@Bean
public HandlerInterceptor getPersonalInterceptor(){
return new PersonalInterceptor();
}
@Bean
public HandlerInterceptor getHumanInterceptor(){
return new HumanInterceptor();
}
@Bean
public CustomUtil getParameterUtil(){
CustomUtil customUtil = new CustomUtil();
MaskDomain[] maskDomains = new MaskDomain[2];
maskDomains[0] = new MaskDomain("exhentai.org", "element-plus.org");
maskDomains[1] = new MaskDomain("e-hentai.org", "element.org");
customUtil.setMaskDomains(maskDomains);
return customUtil;
}
}

View File

@ -0,0 +1,126 @@
package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Service.CollectService;
import com.lion.lionwebsite.Service.GalleryManageService;
import com.lion.lionwebsite.Service.UserServiceImpl;
import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/GalleryManage")
@Slf4j
public class GalleryManageController {
@Resource
GalleryManageService galleryManageService;
@Resource
CollectService collectService;
@Resource
CustomUtil customUtil;
@Resource
UserServiceImpl userService;
@PostMapping("")
public String create_task(String link, String targetResolution, String AuthCode,
@RequestParam(value = "tags", required = false) List<Integer> tags,
@RequestParam(value = "mode", defaultValue = "3", required = false)String mode){
if(link == null || targetResolution == null)
return Response._failure("参数不全");
link = customUtil.restoreUrl(link);
if(link == null)
return Response._failure("链接错误");
return galleryManageService.createTask(link, targetResolution, AuthCode, tags, Byte.parseByte(mode));
}
@PostMapping("/update")
public String updateGallery(String link){
return galleryManageService.updateGallery(link);
}
@GetMapping("")
public String selectGallery(String param, String type, String AuthCode) {
int userId = userService.getUserId(AuthCode); //能调到这里的授权码对应用户不可能为空
if(type == null)
return Response._failure("参数不全");
switch (type) {
case "link" -> {
param = customUtil.restoreUrl(param);
if (param == null)
return Response._failure("链接错误");
return galleryManageService.selectTaskByLink(param);
}
case "gid" -> {
return galleryManageService.selectTaskByGid(Integer.parseInt(param));
}
case "all" -> {
return galleryManageService.selectAllGallery(userId);
}
case "name" -> {
return galleryManageService.selectGalleryByName(param);
}
case "undone" -> {
return galleryManageService.selectUnDoneGallery();
}
case "downloader" -> {
return galleryManageService.selectGalleryByDownloader(AuthCode);
}
default -> {
return Response._default();
}
}
}
@DeleteMapping("")
public String deleteTask(Integer gid, String AuthCode, @RequestParam(value = "mode", defaultValue = "3", required = false)String mode){
if(gid == null)
return Response._failure("参数不全");
return galleryManageService.deleteGalleryByGid(gid, AuthCode, Byte.parseByte(mode));
}
@PostMapping("/collect")
public String collectGallery(Integer gid, String AuthCode){
return collectService.collectGallery(gid, userService.getUserId(AuthCode));
}
@PostMapping("/disCollect")
public String disCollectGallery(Integer gid, String AuthCode){
return collectService.disCollectGallery(gid, userService.getUserId(AuthCode));
}
@GetMapping("/weekUsedAmount")
public String getWeekUsedAmount(){
return galleryManageService.getWeekUsedAmount();
}
@GetMapping("/thumbnail/{name}")
public void getThumbnail(HttpServletRequest request, HttpServletResponse response, @PathVariable("name") String name){
galleryManageService.getThumbnail(request, response, name.replace(".webp", ""));
}
@GetMapping("/onlineImage/{page}")
public void getOnlineImage(Integer gid, @PathVariable("page") Short page, HttpServletRequest request, HttpServletResponse response){
galleryManageService.getOnlineImage(gid, page, request, response);
}
@PostMapping("/share")
public String shareGallery(Integer gid, Integer userId, Integer expireHour){
if(userId.equals(3))
return galleryManageService.shareGallery(gid, expireHour);
return Response._failure("非法访问");
}
}

View File

@ -0,0 +1,32 @@
package com.lion.lionwebsite.Controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class NavigationController {
@GetMapping("/personal/")
public String personal_index(){
return "self";
}
@GetMapping("/personal/mobile")
public String personal_mobile(){
return "selfMobile";
}
@GetMapping("/")
public String index(){
return "index";
}
@GetMapping("/mobile")
public String mobile(){
return "mobile";
}
@GetMapping("/publish/")
public String publish_index(){
return "publish";
}
}

View File

@ -0,0 +1,96 @@
package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Service.LocalServiceImpl;
import com.lion.lionwebsite.Service.PersonalServiceImpl;
import com.lion.lionwebsite.Util.FileDownload;
import com.lion.lionwebsite.Util.Response;
import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
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;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@RestController
@Slf4j
@RequestMapping("/personal")
public class PersonalController {
@Resource
PersonalServiceImpl personalService;
@Resource
LocalServiceImpl localService;
@GetMapping("/sub/self")
public void sub(HttpServletResponse response, HttpServletRequest request){
FileDownload.export(request, response, "sub/sub.txt");
}
@GetMapping("/files")
public String file(String path) throws IOException {
return personalService.getFiles(path);
}
@PostMapping("/uploadFile")
public String uploadFile(String path, String fileName, MultipartFile file){
return personalService.uploadFile(path, fileName, file);
}
@GetMapping("/private/**")
public void getFile(HttpServletRequest request, HttpServletResponse response, String path){
personalService.download(request, response, path);
}
@PostMapping("/share")
public String shareFile(String path, Integer expireHour) {
return personalService.shareFile(path, expireHour);
}
@PostMapping("/compress")
public String compress(String path){
return personalService.compress(path);
}
@PostMapping("/delete")
public String deleteFile(String path){
return personalService.deleteFile(path);
}
@PostMapping("/extendShareTime")
public String extendShareTime(String path, Integer extendHour) throws JsonProcessingException {
return personalService.extendShareTime(path, extendHour);
}
@PostMapping("/cancelShare")
public String cancelShare(String path){
return personalService.cancelShare(path);
}
@PostMapping("/updateSub")
public String updateSub() throws IOException {
Response response = Response.generateResponse();
if(localService.updateSub(true))
response.success();
else
response.failure();
return response.toJSONString();
}
@GetMapping("/lastUpdate")
public String lastUpdate(){
return personalService.lastUpdate();
}
@GetMapping("/ip")
public String ip() throws JsonProcessingException {
return personalService.getIp();
}
}

View File

@ -0,0 +1,97 @@
package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Domain.User;
import com.lion.lionwebsite.Service.PublicServiceImpl;
import com.lion.lionwebsite.Util.FileDownload;
import com.lion.lionwebsite.Util.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
@RestController
@Slf4j
public class PublicController {
final List<String> black_share_codes = new LinkedList<>();
@Resource
PublicServiceImpl publicService;
@GetMapping("/ip")
public void ip(HttpServletRequest request, String auth, HttpServletResponse response) throws IOException {
String ip = request.getHeader("X-Forwarded-For");
if(ip == null)
ip = request.getRemoteAddr();
if(auth != null && auth.equals("ip"))
publicService.logIpAddress(ip);
log.info("ip地址:" + ip + " auth=" + auth);
response.getOutputStream().write(ip.getBytes(StandardCharsets.UTF_8));
}
@GetMapping("/sub/{client}/{AuthCode}")
public void publicSub(@PathVariable("AuthCode") String AuthCode,
@PathVariable("client") String client,
HttpServletResponse response,
HttpServletRequest request) throws IOException {
if(AuthCode == null || client == null || !AuthCode.equals("covid"))
return;
switch (client){
case "v2ray" -> FileDownload.export(request, response, "sub/DouNaiV2ray.txt");
case "clash" -> FileDownload.export(request, response, "sub/DouNaiClash.txt");
default -> response.getOutputStream().write("client error".getBytes(StandardCharsets.UTF_8));
}
}
@GetMapping("/GetFile/{path}")
public void getFile(HttpServletRequest request, HttpServletResponse response, String ShareCode, @PathVariable("path") String path) throws IOException {
synchronized (black_share_codes) {
if (black_share_codes.contains(ShareCode)) {
String ip = request.getHeader("X-Forwarded-For") == null ? request.getRemoteAddr() : request.getHeader("X-Forwarded-For");
log.info("dispatch request ip:{} file:{}", ip, path);
return;
}
}
log.info("ShareCode:{}", ShareCode);
log.info("Path:{}", path);
boolean result = publicService.GetFile(request, response, ShareCode);
if(!result)
black_share_codes.add(ShareCode);
if(black_share_codes.size() > 100)
black_share_codes.remove(0);
}
@PostMapping("/validate")
public String validate(String AuthCode){
Response response = Response.generateResponse();
User user = publicService.getUserId(AuthCode);
response.success(String.format("{\"userId\": %d, \"username\": \"%s\"}", user.getId(), user.getUsername()));
return response.toJSONString();
}
@PutMapping("/AuthCode")
public String alterAuthCode(String AuthCode, String newAuthCode){
return publicService.alterAuthCode(AuthCode, newAuthCode);
}
@GetMapping("/GalleryManage/ehThumbnail")
public void getEhThumbnail(String path, HttpServletResponse response){
publicService.getEhThumbnail(path, response);
}
}

View File

@ -0,0 +1,29 @@
package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Service.QueryService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/query")
public class QueryController {
@Resource
QueryService queryService;
@GetMapping("")
public String query(String keyword, String prev, String next){
return queryService.query(keyword, prev, next);
}
@GetMapping("/image")
public void image(HttpServletResponse response, String path){
queryService.image(response, path);
}
}

View File

@ -0,0 +1,44 @@
package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Service.TagService;
import com.lion.lionwebsite.Util.Response;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/GalleryManage")
public class TagController {
@Resource
TagService tagService;
@PostMapping("/tag")
public String createTag(String tag){
return tagService.createTag(tag);
}
@PostMapping("/tagAndMark")
public String createTagAndMark(Integer gid, String tag){
return tagService.createTagAndMark(gid, tag);
}
@DeleteMapping("/tag")
public String deleteTag(Integer tid){
return tid == null ? Response._failure("参数错误") : tagService.deleteTag(tid);
}
@PostMapping("/mark")
public String markTag(Integer gid, Integer tid){
return tagService.markTag(gid, tid);
}
@PostMapping("/disMark")
public String disMarkTag(Integer gid, Integer tid){
return tagService.disMarkTag(gid, tid);
}
@GetMapping("/allTag")
public String allTag(){
return tagService.selectAllTag();
}
}

View File

@ -0,0 +1,43 @@
package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Service.UserServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/personal/user")
public class UserController {
@Resource
UserServiceImpl userService;
@GetMapping("")
public String getAllUser(){
return userService.getAllUser();
}
@PostMapping("")
public String addAuthCode(String targetAuthCode, String username){
return userService.addAuthCode(targetAuthCode, username);
}
@PutMapping("/AuthCode")
public String alterAuthCode(String targetAuthCode, String newAuthCode){
return userService.alterAuthCode(targetAuthCode, newAuthCode);
}
@PutMapping("/Username")
public String alterUsername(String targetAuthCode, String newUsername){
return userService.alterUsername(targetAuthCode, newUsername);
}
@DeleteMapping ("")
public String deleteAuthCode(String targetAuthCode){
return userService.deleteAuthCode(targetAuthCode);
}
@PutMapping("/status")
public String alterStatus(String AuthCode, boolean isEnable){
return userService.alterStatus(AuthCode, isEnable);
}
}

View File

@ -0,0 +1,26 @@
package com.lion.lionwebsite.Dao;
import com.lion.lionwebsite.Domain.CRC32Cache;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface CRC32CacheMapper {
@Insert("insert into CRC32Cache (path, lastModifyTime, crc32) values (#{path}, #{lastModifyTime}, #{crc32})")
void insertCache(CRC32Cache crc32Cache);
@Select("select * from CRC32Cache where path=#{path}")
CRC32Cache selectCache(String path);
@Select("select * from CRC32Cache where path like '%' || #{name} || '%'")
CRC32Cache selectCacheByName(String name);
// @Update("update CRC32Cache set lastModifyTime=#{lastModifyTime}, crc32=#{crc32} where path=#{path}")
// void updateCache(CRC32Cache crc32Cache);
@Delete("delete from CRC32Cache where path=#{path}")
void deleteCacheByPath(String path);
}

View File

@ -0,0 +1,23 @@
package com.lion.lionwebsite.Dao;
import org.apache.ibatis.annotations.*;
import java.util.ArrayList;
@Mapper
public interface CollectMapper {
@Insert("insert into collect (gid, collector) values (#{gid}, #{collector})")
void collect(@Param("gid") int gid, @Param("collector") int collector);
@Delete("delete from collect where collector=#{collector} and gid=#{gid}")
void disCollect(@Param("gid") int gid, @Param("collector") int collector);
@Select("select count(id) from collect where collector=#{collector} and gid=#{gid}")
int isCollect(@Param("gid") int gid, @Param("collector") int collector);
@Select("select gid from collect where collector=#{collector}")
ArrayList<Integer> selectGidByCollector(int collector);
@Select("select collector from collect where gid=#{gid}")
ArrayList<Integer> selectCollectorByGid(int gid);
}

View File

@ -0,0 +1,21 @@
package com.lion.lionwebsite.Dao;
import com.lion.lionwebsite.Domain.CustomConfiguration;
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 {
// @Insert("insert into customConfiguration values (#{parameter}, #{value})")
// void insertConfiguration(CustomConfiguration configuration);
@Update("update customConfiguration set value=#{value} where parameter=#{parameter}")
void updateConfiguration(@Param("parameter")String parameter, @Param("value")String value);
// @Delete("delete from customConfiguration where parameter=#{parameter}")
// void deleteConfiguration(CustomConfiguration configuration);
@Select("select * from customConfiguration where parameter=#{parameter}")
CustomConfiguration selectConfiguration(String parameter);
}

View File

@ -0,0 +1,50 @@
package com.lion.lionwebsite.Dao;
import com.lion.lionwebsite.Domain.Gallery;
import org.apache.ibatis.annotations.*;
@Mapper
public interface GalleryMapper {
@Insert("insert into gallery" +
" (gid, name, link, language, pages, status, fileSize, createTime, proceeding, resolution, displayFileSize, downloader, mode)\n" +
" values (#{gid}, #{name}, #{link}, #{language}, #{pages}, #{status}, #{fileSize}, #{createTime}, #{proceeding}, #{resolution}, #{displayFileSize}, #{downloader}, #{mode})")
void insertGallery(Gallery gallery);
@Select("select * from gallery where link=#{link}")
Gallery selectGalleryByLink(String link);
@Select("select * from gallery where gid=#{gid}")
Gallery selectGalleryByGid(int gid);
@Select("select name from gallery where gid=#{gid}")
String selectGalleryNameByGid(int gid);
@Select("select * from gallery where downloader=#{downloader}")
Gallery[] selectGalleryByDownloader(int downloader);
@Select("select * from gallery where status in ('已提交', '下载中')")
Gallery[] selectUnDoneGalleries();
@Select("select * from gallery")
Gallery[] selectAllGallery();
@Select("select * from gallery where name like #{name} limit 1")
Gallery selectGalleryByName(String name);
@Update("""
update gallery set name=#{name}, link=#{link}, language=#{language},
pages=#{pages}, status=#{status}, fileSize=#{fileSize}, createTime=#{createTime},
proceeding=#{proceeding}, resolution=#{resolution}, displayFileSize=#{displayFileSize}, mode=#{mode}
where gid=#{gid}""")
void updateGallery(Gallery gallery);
@Update("update gallery set downloader=#{downloader} where downloader=#{oldDownloader}")
void updateGalleryDownloader(@Param("downloader") int downloader, @Param("oldDownloader") int oldDownloader);
@Delete("delete from gallery where gid=#{gid}")
void deleteGalleryByGid(Integer gid);
}

View File

@ -0,0 +1,22 @@
package com.lion.lionwebsite.Dao;
import com.lion.lionwebsite.Domain.PageNameCache;
import org.apache.ibatis.annotations.*;
@Mapper
public interface PageNameCacheMapper {
@Insert("insert into pageName (gid, page, pageName) values (#{gid}, #{page}, #{pageName})")
void insertPageNameCache(PageNameCache pageNameCache);
@Select("select pageName from pageName where gid=#{gid} and page=#{page}")
String selectPageName(@Param("gid") int gid, @Param("page") short page);
@Delete("delete from pageName where gid=#{gid}")
void deletePageNameByGid(int gid);
@Delete("delete from pageName where gid=#{gid} and page=#{page}")
void deletePageName(PageNameCache pageNameCache);
}

View File

@ -0,0 +1,31 @@
package com.lion.lionwebsite.Dao;
import com.lion.lionwebsite.Domain.ShareFile;
import org.apache.ibatis.annotations.*;
import java.util.ArrayList;
import java.util.Date;
@Mapper
public interface ShareFileMapper {
@Insert("insert into ShareFile (ShareCode, FilePath, ExpireTime) values (#{ShareCode}, #{FilePath}, #{ExpireTime})")
void insertShareFile(@Param("ShareCode")String ShareCode, @Param("FilePath")String FilePath, @Param("ExpireTime") Date ExpireTime);
@Select("select * from ShareFile where ShareCode=#{ShareCode}")
ShareFile selectShareFileByShareCode(String ShareCode);
@Select("select * from ShareFile where FilePath=#{FilePath}")
ShareFile selectShareFileByFilePath(String FilePath);
@Select("select * from ShareFile where FilePath like '%' || #{FilePath} || '%'")
ArrayList<ShareFile> selectShareFilesByFilePath(String FilePath);
@Delete("delete from ShareFile where ShareCode=#{ShareCode}")
void deleteShareFile(String ShareCode);
@Select("select * from ShareFile")
ShareFile[] selectAllShareFile();
@Update("update ShareFile set ExpireTime=#{ExpireTime}, ShareCode=#{ShareCode} where FilePath=#{FilePath}")
void updateShareFile(ShareFile ShareFile);
}

View File

@ -0,0 +1,60 @@
package com.lion.lionwebsite.Dao;
import com.lion.lionwebsite.Domain.Tag;
import com.lion.lionwebsite.Domain.TagMark;
import org.apache.ibatis.annotations.*;
import java.util.ArrayList;
@Mapper
public interface TagMapper {
@Insert("insert into tag (tag) values (#{tag})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
void insertTag(Tag tag);
// @Select("select id, tag, usage from tag where id=#{id}")
// Tag selectTagById(int id);
//
// @Select("select id from tag where tag=#{tag}")
// int selectTidByTag(String tag);
@Select("select count(id) from tag where tag=#{tag}")
int selectTagExistByTag(String tag);
@Select("select count(id) from tag where id=#{id}")
int selectTagExistById(int id);
@Select("select id, tag, usage from tag")
ArrayList<Tag> selectAllTag();
@Delete("delete from tag where id=#{id}")
void deleteTagById(int id);
@Select("select count(id) from galleryTag where tid=#{tid}")
int selectTagUsage(int tid);
@Select("select count(id) from galleryTag where gid=#{gid} and tid=#{tid}")
int selectIsMark(@Param("gid") int gid, @Param("tid") int tid);
@Select("select tid from galleryTag where gid=#{gid}")
ArrayList<Integer> selectTagByGid(int gid);
@Insert("insert into galleryTag (gid, tid) values (#{gid}, #{tid})")
int markTag(@Param("gid") int gid, @Param("tid") int tid);
@Delete("delete from galleryTag where gid=#{gid} and tid=#{tid}")
int disMarkTag(@Param("gid") int gid, @Param("tid") int tid);
@Select("select gid, tid from galleryTag")
ArrayList<TagMark> selectAllMark();
@Update("update tag set usage=usage+1 where id=#{tid}")
void incrTagUsage(int tid);
@Update("update tag set usage=usage-1 where id=#{tid}")
void decrTagUsage(int tid);
@Delete("delete from galleryTag where gid=#{gid}")
void disMarkTagByGid(int gid);
}

View File

@ -0,0 +1,40 @@
package com.lion.lionwebsite.Dao;
import com.lion.lionwebsite.Domain.User;
import org.apache.ibatis.annotations.*;
@Mapper
public interface UserMapper {
@Insert("insert into User (AuthCode, username) values (#{AuthCode}, #{username})")
void insertUser(User user);
@Select("select * from User where AuthCode=#{AuthCode}")
User selectUserByAuthCode(String AuthCode);
@Select("select AuthCode from User")
String[] selectAllAuthCode();
@Select("select AuthCode from User")
String[] selectEnableAuthCode();
@Select("select * from User")
User[] selectAllUser();
@Update("update User set AuthCode=#{newAuthCode} where AuthCode=#{AuthCode}")
void updateAuthCode(@Param("AuthCode") String AuthCode, @Param("newAuthCode") String newAuthCode);
@Update("update User set username=#{newUsername} where AuthCode=#{AuthCode}")
void updateUsername(@Param("AuthCode") String AuthCode, @Param("newUsername") String newUsername);
@Update("update User set lastAccessTime=#{lastAccessTime} where AuthCode=#{AuthCode}")
void updateLastAccessTime(@Param("lastAccessTime") String lastAccessTime, @Param("AuthCode") String AuthCode);
@Update("update User set isEnable=#{isEnable} where id=#{id}")
void updateIsEnableById(@Param("id") int id, @Param("isEnable") boolean isEnable);
@Delete("delete from User where AuthCode=#{AuthCode}")
void deleteUserByAuthCode(String AuthCode);
@Select("select count(AuthCode) from User where AuthCode=#{AuthCode}")
int isExist(String AuthCode);
}

View File

@ -0,0 +1,14 @@
package com.lion.lionwebsite.Domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CRC32Cache {
String path;
long lastModifyTime;
String crc32;
}

View File

@ -0,0 +1,16 @@
package com.lion.lionwebsite.Domain;
import lombok.Data;
@Data
public class CustomConfiguration {
public static final String AUTH = "auth";
public static final String LAST_UPDATE_SUB_TIME = "lastUpdateSubTime";
public static final String CURRENT_IP_ADDRESS = "currentIpAddress";
public static final String LAST_UPDATE_IP_ADDRESS_TIME = "lastUpdateIpAddressTime";
public static final String WEEK_USED_AMOUNT = "weekUsedAmount";
public static final String LAST_RESET_AMOUNT_TIME = "lastResetAmountTime";
String parameter;
String value;
}

View File

@ -0,0 +1,70 @@
package com.lion.lionwebsite.Domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.Map;
@Data
public class Gallery {
@JsonProperty("name")
private String name; //本子名字
@JsonProperty("gid")
private int gid; //gid
@JsonProperty("link")
private String link; //本子链接
@JsonProperty("language")
private String language; //本子语言
@JsonProperty("pages")
private int pages; //本子页数
@JsonProperty("status")
private String status; //本子当前状态
@JsonIgnore
private long fileSize; //文件大小
@JsonProperty("fileSize")
private String displayFileSize; //展示文件大小
@JsonProperty("createTime")
private Long createTime; //创建时间
@JsonProperty("resolution")
private String resolution; //分辨率
@JsonProperty("proceeding")
private int proceeding; //进度
@JsonProperty("downloader")
private int downloader; //下载人
@JsonProperty("collector")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private String collector; //收藏人
@JsonProperty("isCollect") //是否收藏
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
private boolean isCollect;
@JsonProperty("tags")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private ArrayList<Integer> tags; //标签
@JsonProperty("availableResolution")
@JsonInclude(JsonInclude.Include.NON_NULL)
private Map<String, String> availableResolution; //可选分辨率
@JsonProperty("mode")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private byte mode; //rd 可读可下载 r可读 d可下载
}

View File

@ -0,0 +1,26 @@
package com.lion.lionwebsite.Domain;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class GalleryForQuery {
@JsonProperty("name")
String name;
@JsonProperty("link")
String link;
@JsonProperty("type")
String type;
@JsonProperty("page")
int page;
@JsonProperty("uploadTime")
String uploadTime;
@JsonProperty("thumbnailUrl")
String thumbnailUrl;
}

View File

@ -0,0 +1,48 @@
package com.lion.lionwebsite.Domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@Data
public class GalleryTask {
public static byte DOWNLOADING = 1;
public static byte DOWNLOAD_COMPLETE = 2;
public static byte DOWNLOAD_QUEUED = 3;
public static byte COMPRESS_COMPLETE = 4;
public static byte DOWNLOAD_ALL = 3;
public static byte DOWNLOAD_PREVIEW = 2;
public static byte DOWNLOAD_SOURCE = 1;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String name;
private int gid;
private int pages;
private byte status;
private int proceeding;
private byte type;
@JsonIgnore
private String path;
@JsonIgnore
public boolean is_download_complete(){
return status == DOWNLOAD_COMPLETE;
}
@JsonIgnore
public boolean is_compress_complete(){
return status == COMPRESS_COMPLETE;
}
}

View File

@ -0,0 +1,11 @@
package com.lion.lionwebsite.Domain;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class MaskDomain {
String raw;
String mask;
}

View File

@ -0,0 +1,16 @@
package com.lion.lionwebsite.Domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageNameCache {
int gid;
short page;
String pageName;
}

View File

@ -0,0 +1,12 @@
package com.lion.lionwebsite.Domain;
import lombok.Data;
import java.util.Date;
@Data
public class ShareFile {
String ShareCode;
String FilePath;
Date ExpireTime;
}

View File

@ -0,0 +1,14 @@
package com.lion.lionwebsite.Domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Tag {
int id;
String tag;
int usage;
}

View File

@ -0,0 +1,11 @@
package com.lion.lionwebsite.Domain;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class TagMark {
int gid;
int tid;
}

View File

@ -0,0 +1,28 @@
package com.lion.lionwebsite.Domain;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@JsonProperty("id")
int id;
@JsonProperty("AuthCode")
String AuthCode;
@JsonProperty("username")
String username;
@JsonProperty("lastAccessTime")
String lastAccessTime;
@JsonProperty("isEnable")
boolean isEnable;
public String getUniqueId(){
return id < 10 ? "0" + id: "" + id;
}
}

View File

@ -0,0 +1,8 @@
package com.lion.lionwebsite.Error;
public class ErrorCode {
public static final byte IO_ERROR = 1;
public static final byte FILE_NOT_FOUND = 2;
public static final byte COMPRESS_ERROR = 3;
}

View File

@ -0,0 +1,7 @@
package com.lion.lionwebsite.Exception;
public class ResolutionNotMatchException extends Exception{
public ResolutionNotMatchException(String targetResolution) {
super("TargetResolution " + targetResolution + " not exist");
}
}

View File

@ -0,0 +1,28 @@
package com.lion.lionwebsite.Filter;
import com.lion.lionwebsite.Dao.UserMapper;
import com.lion.lionwebsite.Util.CustomUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(filterName = "AccessFilter", urlPatterns = {"/validate"})
public class AccessFilter implements Filter {
@Resource
UserMapper userMapper;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String authCode = servletRequest.getParameter("AuthCode");
if(authCode == null)
return;
userMapper.updateLastAccessTime(CustomUtil.now(), authCode);
filterChain.doFilter(servletRequest, servletResponse);
}
}

View File

@ -0,0 +1,78 @@
package com.lion.lionwebsite.Filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.scheduling.annotation.Scheduled;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
@WebFilter(filterName = "AdaptorFilter", urlPatterns = {"/", "/personal/"})
public class AdaptorFilter implements Filter {
FileWriter writer;
AdaptorFilter(){
Calendar calendar = Calendar.getInstance();
try {
writer = new FileWriter(String.format("log/AccessLog_%s-%s-%s.log",
calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH)));
}catch (IOException e){
e.printStackTrace();
}
}
@Scheduled(cron = "0 0 0 * * *")
void changeDate(){
Calendar calendar = Calendar.getInstance();
try {
if(writer != null)
writer.close();
writer = new FileWriter(String.format("log/AccessLog_%s-%s-%s.log",
calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH)));
}catch (IOException e){
e.printStackTrace();
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String UserAgent = request.getHeader("User-Agent");
//屏蔽UA为null的请求
if(UserAgent == null)
return;
String AuthCode = request.getParameter("AuthCode") == null ? "null" : request.getParameter("AuthCode");
String ServletPath = request.getServletPath();
String ip = request.getHeader("X-Forwarded-For") == null ? request.getRemoteAddr(): request.getHeader("X-Forwarded-For");
Calendar calendar = Calendar.getInstance();
String now = String.format("%s:%s:%s", calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND));
//日志
System.out.printf("%s ip:%s \tpath:%s \tAuthCode:%s ua:%s\n", now, ip, ServletPath, AuthCode, UserAgent.length() > 61 ? UserAgent.substring(0, 60): UserAgent);
writer.write(String.format("%s ip:%s \tpath:%s \tAuthCode:%s ua:%s\n", now, ip, ServletPath, AuthCode, UserAgent));
writer.flush();
//如果是验证则直接跳转
if(ServletPath.equals("/validate"))
filterChain.doFilter(servletRequest, servletResponse);
//如果不是则根据UA判断是否跳转
else if ((UserAgent.contains("Android") || UserAgent.contains("iPhone")))
if (ServletPath.equals("/personal/") && AuthCode.equals("alone"))
response.sendRedirect("/mobile?AuthCode=alone");
else
response.sendRedirect("/mobile");
else
filterChain.doFilter(servletRequest, servletResponse);
}
}

View File

@ -0,0 +1,13 @@
package com.lion.lionwebsite.Interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
public class HumanInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
return request.getHeader("User-Agent") != null;
}
}

View File

@ -0,0 +1,14 @@
package com.lion.lionwebsite.Interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
public class PersonalInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
return request.getParameter("AuthCode") != null && request.getParameter("AuthCode").equals("alone");
}
}

View File

@ -0,0 +1,36 @@
package com.lion.lionwebsite.Interceptor;
import com.lion.lionwebsite.Dao.UserMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class TaskHandlerInterceptor implements HandlerInterceptor {
@Resource
UserMapper userMapper;
String[] AuthCodes = null;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
if(AuthCodes == null)
AuthCodes = userMapper.selectAllAuthCode();
String auth = request.getParameter("AuthCode");
if(auth != null)
for(String AuthCode: AuthCodes)
if(auth.equals(AuthCode))
return true;
return false;
}
public void updateAuthCodes(){
AuthCodes = userMapper.selectEnableAuthCode();
}
}

View File

@ -0,0 +1,18 @@
package com.lion.lionwebsite;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@ServletComponentScan(value = "com.lion.lionwebsite.Filter")
public class LionWebsiteApplication {
public static void main(String[] args) {
SpringApplication.run(LionWebsiteApplication.class, args);
}
}

View File

@ -0,0 +1,22 @@
package com.lion.lionwebsite.Message;
public class AbstractMessage {
public static final byte DOWNLOAD_POST_MESSAGE = 1;
public static final byte DOWNLOAD_STATUS_MESSAGE = 2;
public static final byte DELETE_GALLERY_MESSAGE = 3;
public static final byte RESPONSE_MESSAGE = 0;
public static final byte UPDATE_GALLERY_MESSAGE = 4;
public static final byte GALLERY_PAGE_QUERY_MESSAGE = 5;
public static final byte IDENTITY_MESSAGE = 6;
public static final byte GALLERY_REQUEST_MESSAGE = 101;
public byte messageType;
public int messageId;
}

View File

@ -0,0 +1,20 @@
package com.lion.lionwebsite.Message;
import lombok.Data;
@Data
public class DeleteGalleryMessage extends AbstractMessage{
{
messageType = DELETE_GALLERY_MESSAGE;
}
public static final byte DELETE_ALL = 3;
public static final byte DELETE_PREVIEW = 2;
public static final byte DELETE_SOURCE = 1;
byte deleteType;
String galleryName;
}

View File

@ -0,0 +1,14 @@
package com.lion.lionwebsite.Message;
import com.lion.lionwebsite.Domain.GalleryTask;
import lombok.Data;
@Data
public class DownloadPostMessage extends AbstractMessage{
{
messageType = DOWNLOAD_POST_MESSAGE;
}
GalleryTask galleryTask;
}

View File

@ -0,0 +1,13 @@
package com.lion.lionwebsite.Message;
import com.lion.lionwebsite.Domain.GalleryTask;
import lombok.Data;
@Data
public class DownloadStatusMessage extends AbstractMessage{
GalleryTask[] galleryTasks;
{
messageType = DOWNLOAD_STATUS_MESSAGE;
}
}

View File

@ -0,0 +1,21 @@
package com.lion.lionwebsite.Message;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@Data
public class GalleryPageQueryMessage extends AbstractMessage{
{
messageType = GALLERY_PAGE_QUERY_MESSAGE;
}
@JsonInclude(JsonInclude.Include.NON_NULL)
String name;
int page;
@JsonInclude(JsonInclude.Include.NON_NULL)
String pageName;
byte result;
}

View File

@ -0,0 +1,25 @@
package com.lion.lionwebsite.Message;
import lombok.Data;
@Data
//请求预览/压缩包
public class GalleryRequestMessage extends AbstractMessage{
public static final byte SOURCE = 1;
public static final byte PREVIEW = 2;
public static final byte COMPRESS_SOURCE = 3;
{
messageType = GALLERY_REQUEST_MESSAGE;
}
String galleryName;
byte type;
short page;
short port;
}

View File

@ -0,0 +1,10 @@
package com.lion.lionwebsite.Message;
import lombok.Data;
@Data
public class IdentityMessage extends AbstractMessage{
{
messageType = IDENTITY_MESSAGE;
}
}

View File

@ -0,0 +1,57 @@
package com.lion.lionwebsite.Message;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Slf4j
public class MessageCodec extends ByteToMessageCodec<AbstractMessage> {
ObjectMapper objectMapper;
public MessageCodec(){
objectMapper = new ObjectMapper();
}
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, AbstractMessage abstractMessage, ByteBuf byteBuf) {
byteBuf.writeByte(abstractMessage.messageType);
byte[] bytes = objectMapper.valueToTree(abstractMessage).toString().getBytes(StandardCharsets.UTF_8);
byteBuf.writeInt(bytes.length);
byteBuf.writeBytes(bytes);
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
byte messageType = byteBuf.readByte();
int length = byteBuf.readInt();
byte[] bytes = new byte[length];
byteBuf.readBytes(bytes);
final String metadata = new String(bytes, StandardCharsets.UTF_8);
AbstractMessage abstractMessage = switch (messageType){
case AbstractMessage.DOWNLOAD_POST_MESSAGE -> objectMapper.readValue(metadata, DownloadPostMessage.class);
case AbstractMessage.DOWNLOAD_STATUS_MESSAGE -> objectMapper.readValue(metadata, DownloadStatusMessage.class);
case AbstractMessage.GALLERY_REQUEST_MESSAGE -> objectMapper.readValue(metadata, GalleryRequestMessage.class);
case AbstractMessage.RESPONSE_MESSAGE -> objectMapper.readValue(metadata, ResponseMessage.class);
case AbstractMessage.UPDATE_GALLERY_MESSAGE -> objectMapper.readValue(metadata, UpdateGalleryMessage.class);
case AbstractMessage.DELETE_GALLERY_MESSAGE -> objectMapper.readValue(metadata, DeleteGalleryMessage.class);
case AbstractMessage.GALLERY_PAGE_QUERY_MESSAGE -> objectMapper.readValue(metadata, GalleryPageQueryMessage.class);
case AbstractMessage.IDENTITY_MESSAGE -> objectMapper.readValue(metadata, IdentityMessage.class);
default -> null;
};
if (abstractMessage == null){
log.error("decode error, messageType: " + messageType + "ip:" + channelHandlerContext.channel().remoteAddress().toString());
return;
}
list.add(abstractMessage);
}
}

View File

@ -0,0 +1,21 @@
package com.lion.lionwebsite.Message;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class ResponseMessage extends AbstractMessage{
{
messageType = RESPONSE_MESSAGE;
}
static final byte SUCCESS = 0;
byte result;
public ResponseMessage(int messageId, byte result){
this.messageId = messageId;
this.result = result;
}
}

View File

@ -0,0 +1,15 @@
package com.lion.lionwebsite.Message;
import com.lion.lionwebsite.Domain.GalleryTask;
import lombok.Data;
@Data
public class UpdateGalleryMessage extends AbstractMessage{
{
messageType = UPDATE_GALLERY_MESSAGE;
}
GalleryTask galleryTask;
}

View File

@ -0,0 +1,2 @@
config.stopBubbling=true
lombok.equalsAndHashCode.callSuper=call

View File

@ -0,0 +1,37 @@
package com.lion.lionwebsite.Service;
import com.lion.lionwebsite.Dao.CollectMapper;
import com.lion.lionwebsite.Util.Response;
import jakarta.annotation.Resource;
import lombok.Data;
import org.springframework.stereotype.Service;
@Service
@Data
public class CollectService {
@Resource
CollectMapper collectMapper;
public String collectGallery(int gid, int collector){
Response response = Response.generateResponse();
if(collectMapper.isCollect(gid, collector) == 0) { //没有收藏
collectMapper.collect(gid, collector);
response.success("收藏成功");
}else{
response.failure("已经收藏了");
}
return response.toJSONString();
}
public String disCollectGallery(int gid, int collector){
Response response = Response.generateResponse();
if(collectMapper.isCollect(gid, collector) == 0) { //没有收藏
response.failure("没有收藏该本子");
}else{
collectMapper.disCollect(gid, collector);
response.success("取消收藏成功");
}
return response.toJSONString();
}
}

View File

@ -0,0 +1,606 @@
package com.lion.lionwebsite.Service;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import com.lion.lionwebsite.Dao.*;
import com.lion.lionwebsite.Domain.*;
import com.lion.lionwebsite.Exception.ResolutionNotMatchException;
import com.lion.lionwebsite.Error.ErrorCode;
import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.FileDownload;
import com.lion.lionwebsite.Util.GalleryUtil;
import com.lion.lionwebsite.Util.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import static com.lion.lionwebsite.Util.CustomUtil.dateTimeFormatter;
import static com.lion.lionwebsite.Util.CustomUtil.fourZeroFour;
@Service
@Data
@Slf4j
@ConfigurationProperties(prefix = "gallery-manage-service")
public class GalleryManageService {
String TargetPath;
int cacheSize;
Map<Integer, String> gid2name_cache = new HashMap<>();
Map<Integer, Integer> gid2page_cache = new HashMap<>();
Map<String, String> page2pageNameCache = new HashMap<>();
@Resource
GalleryMapper galleryMapper;
@Resource
CollectMapper collectMapper;
@Resource
CustomConfigurationMapper configurationMapper;
@Resource
UserMapper userMapper;
@Resource
ShareFileMapper shareFileMapper;
@Resource
TagMapper tagMapper;
@Resource
PageNameCacheMapper pageNameCacheMapper;
@Resource
RemoteService remoteService;
/**
* 创建任务
* @param link 任务链接
* @param targetResolution 目标分辨率
* @param AuthCode 授权码用于记录是谁下载的
* @return 提交结果
*/
public String createTask(String link, String targetResolution, String AuthCode, List<Integer> tidS, byte mode){
Response response = Response.generateResponse();
// return Response._failure("调试中,请勿提交任务");
int gid;
try {
gid = Integer.parseInt(link.split("/")[4]);
}catch (NumberFormatException e){
response.failure("链接错误");
return response.toJSONString();
}
Gallery gallery;
//判断数据库中是否有这个任务有则返回状态
if((gallery = galleryMapper.selectGalleryByGid(gid)) != null){
response.failure("任务队列已有此任务,任务状态: " + gallery.getStatus() + " 请点击查找任务");
return response.toJSONString();
}
//尝试下载本子返回结果
try {
gallery = GalleryUtil.parse(link, true, targetResolution);
if(gallery == null){
log.error("创建任务: {},解析失败", link);
return Response._failure("任务解析失败,未知原因,请检查链接是否正常");
}else{
log.info("创建任务: {} 目标分辨率:{}", link, targetResolution);
if(remoteService.addGalleryToQueue(gallery, mode) != 0){
log.error("传送任务{}失败, 未知原因", gallery.getName());
return Response._failure("任务传送失败,未知原因");
}
}
}catch (ResolutionNotMatchException e){
e.printStackTrace();
response.failure("提交失败,分辨率不存在");
return response.toJSONString();
}catch (IOException e){
e.printStackTrace();
response.failure("IO错误可能是网络波动");
return response.toJSONString();
}
//处理下载结果将任务插入数据库并且更新每周用量
if(gallery.getStatus().equals("已提交")) {
response.success(gallery.toString());
gallery.setDownloader(userMapper.selectUserByAuthCode(AuthCode).getId());
gallery.setMode(mode);
galleryMapper.insertGallery(gallery);
long usedAmount = Long.parseLong(configurationMapper.selectConfiguration(CustomConfiguration.WEEK_USED_AMOUNT).getValue());
usedAmount += gallery.getFileSize();
configurationMapper.updateConfiguration(CustomConfiguration.WEEK_USED_AMOUNT, String.valueOf(usedAmount));
if(tidS != null)
for (Integer tid : tidS)
if(tagMapper.selectTagExistById(tid) > 0)
tagMapper.markTag(gallery.getGid(), tid);
}
else{
response.failure("提交失败,未知原因");
galleryMapper.deleteGalleryByGid(gallery.getGid());
}
return response.toJSONString();
}
/**
* 根据链接查询本子
* @param link 链接
* @return 查询结果
*/
public String selectTaskByLink(String link) {
Response response = Response.generateResponse();
Integer gid = parseGid(link);
Gallery gallery;
if(gid != null)
gallery = galleryMapper.selectGalleryByGid(gid);
else {
response.failure("链接错误");
return response.toJSONString();
}
log.info("查询{}", link);
//判断数据库中是否有这个任务如果有则返回如果没有则直接查询
if(gallery == null)
try{
gallery = GalleryUtil.parse(link, false, null);
if(gallery != null)
response.success(new ObjectMapper().valueToTree(gallery).toString());
else
response.failure("查询失败");
} catch (Exception e) {
e.printStackTrace();
response.failure("查询失败");
}
else
response.success(new ObjectMapper().valueToTree(gallery).toString());
return response.toJSONString();
}
/**
* 通过gid查询本子
* @param gid gid
* @return 查询结果
*/
public String selectTaskByGid(int gid){
Response response = Response.generateResponse();
Gallery gallery = galleryMapper.selectGalleryByGid(gid);
if(gallery == null)
response.failure("未找到该本子,请使用链接");
else
response.success(gallery.toString());
return response.toJSONString();
}
/**
* 查询所有本子
* @return 查询结果
*/
public String selectAllGallery(int userId) {
Response response = Response.generateResponse();
Gallery[] galleries = galleryMapper.selectAllGallery();
if(galleries == null){
response.failure("没有找到本子");
return response.toJSONString();
}
ArrayList<Integer> galleryIds = collectMapper.selectGidByCollector(userId);
Iterator<Integer> idIterator;
if(!galleryIds.isEmpty()) //如果该用户收藏了本子
galleryLoop: for (Gallery gallery : galleries) { //遍历本子
idIterator = galleryIds.iterator();
while (idIterator.hasNext()){ //遍历收藏的gid
Integer id = idIterator.next();
if(id.equals(gallery.getGid())){ //如果找到对应的gid,修改对应本子的属性删除当前gid判断是否需要跳出或者结束循环
gallery.setCollect(true);
idIterator.remove();
if(galleryIds.isEmpty())
break galleryLoop;
else
continue galleryLoop;
}
}
}
HashMap<Integer, ArrayList<Integer>> tagsMap = new HashMap<>();
ArrayList<TagMark> tags = tagMapper.selectAllMark();
for (TagMark tagMark : tags) { //从数据库取出所有标记然后筛选出 gid -> tids 后面加个缓存
tagsMap.computeIfAbsent(tagMark.getGid(), k -> new ArrayList<>());
tagsMap.get(tagMark.getGid()).add(tagMark.getTid());
}
ArrayList<Integer> temp;
for (Gallery gallery : galleries) {
if((temp = tagsMap.get(gallery.getGid())) != null) {
gallery.setTags(new ArrayList<>());
gallery.getTags().addAll(temp);
}
}
response.success(new ObjectMapper().valueToTree(galleries).toString());
return response.toJSONString();
}
/**
* 查询未完成的本子
* @return 查询结果
*/
public String selectUnDoneGallery() {
Response response = Response.generateResponse();
Gallery[] galleries = galleryMapper.selectUnDoneGalleries();
if(galleries.length > 0)
response.success(new ObjectMapper().valueToTree(galleries).toString());
else
response.failure();
return response.toJSONString();
}
/**
* 通过本子名查询本子
* @param name 名字
* @return 查询结果
*/
public String selectGalleryByName(String name) {
Response response = Response.generateResponse();
Gallery gallery = galleryMapper.selectGalleryByName("%" + name + "%");
if(gallery != null)
response.success(new ObjectMapper().valueToTree(gallery).toString());
else
response.failure("没有找到该名字的本子");
return response.toJSONString();
}
/**
* 查询用户下载的本子
* @param AuthCode 授权码用于查询用户名
* @return 查询结果
*/
public String selectGalleryByDownloader(String AuthCode){
Response response = Response.generateResponse();
Gallery[] galleries = galleryMapper.selectGalleryByDownloader(userMapper.selectUserByAuthCode(AuthCode).getId());
if(galleries.length > 0)
response.success(new ObjectMapper().valueToTree(galleries).toString());
else
response.failure("您未下载本子");
return response.toJSONString();
}
// /**
// * 获取本子图片的名字
// * @param gid gid
// * @return 名字数组
// */
// public String selectOnlineFilename(Integer gid){
// Response response = Response.generateResponse();
// String path;
//
// if((path = gid2name_cache.get(gid)) == null) {
// Gallery gallery = galleryMapper.selectGalleryByGid(gid);
//
// if (gallery == null) {
// response.failure("本子不存在");
// return response.toJSONString();
// }
//
// path = TargetPath + gallery.getName();
// gid2name_cache.put(gid, path);
// if(gid2name_cache.size() > cacheSize)
// gid2name_cache.remove(gid2name_cache.keySet().iterator().next());
// }
//
// File file = new File(path);
// if(!file.isDirectory()){
// response.failure("本子文件已被删除");
// return response.toJSONString();
// }
//
// File[] files = file.listFiles(pathname -> pathname.getName().endsWith(".webp") && !pathname.getName().startsWith("thumbnail"));
// if(files == null || files.length == 0){
// response.failure("本子文件丢失");
// return response.toJSONString();
// }
//
// ArrayList<String> images = new ArrayList<>();
// for(File image: files){
// images.add(image.getName().replace(".webp", ""));
// }
// //字符串长度相等时比较字典顺序不相等时比较长度
// images.sort((s1, s2) ->
// s1.length() == s2.length() ? s1.compareTo(s2): s1.length() - s2.length()
// );
//
// response.success(new ObjectMapper().valueToTree(images).toString());
// return response.toJSONString();
// }
/**
* 删除本子以及对应的文件如果存在的话
* @param gid gid
* @return 删除结果
*/
public String deleteGalleryByGid(Integer gid, String AuthCode, byte mode) {
Response response = Response.generateResponse();
String deleteType;
Gallery gallery = galleryMapper.selectGalleryByGid(gid);
User user = userMapper.selectUserByAuthCode(AuthCode);
if(gallery == null){
response.failure("删除失败,该本子不存在");
return response.toJSONString();
}
ArrayList<Integer> collector = collectMapper.selectCollectorByGid(gallery.getGid());
if(!(collector.isEmpty() || collector.size() == 1 && collector.get(0).equals(user.getId()) //判断收藏
&& gallery.getDownloader() == user.getId())) //判断下载
response.failure("删除失败,该本子已被别人收藏或你不是下载人");
else{
if((gallery.getMode() & mode) == 0) { //不能删除对应的因为对应的没有
if (mode == 1)
response.failure("无法删除该本子的源文件,不存在");
else if (mode == 2)
response.failure("无法删除该本子的预览图,不存在");
} else {
log.info("删除本子{}, mode:{}, delete_mode:{}", gallery.getName(), gallery.getMode(), mode);
gallery.setMode((byte) (gallery.getMode() ^ mode));
if(gallery.getMode() == 0) { //删整个本子
galleryMapper.deleteGalleryByGid(gallery.getGid()); //删除本子记录
ArrayList<Integer> tidS = tagMapper.selectTagByGid(gallery.getGid());
for (Integer tid : tidS)
tagMapper.decrTagUsage(tid);
tagMapper.disMarkTagByGid(gallery.getGid()); //删除本子标记
gid2name_cache.remove(gallery.getGid()); //删除本子缓存
page2pageNameCache.remove(gallery.getName()); //移除页数缓存
if(new File(TargetPath + "/" + gallery.getName()).isDirectory()) //删除本地缓存
FileUtil.del(TargetPath + "/" + gallery.getName());
deleteType = "全部";
}else{
if(mode == 2 && new File(TargetPath + "/" + gallery.getName()).isDirectory()) { //删除预览缓存
FileUtil.del(TargetPath + "/" + gallery.getName());
gid2name_cache.remove(gallery.getGid()); //删除本子缓存
page2pageNameCache.remove(gallery.getName()); //移除页数缓存
}
galleryMapper.updateGallery(gallery);
deleteType = (mode == 1? "源文件": "预览图"); //用于提示
}
switch (remoteService.deleteGallery(gallery, mode)){
case ErrorCode.IO_ERROR -> response.failure("本子:" + gallery.getName() + deleteType + "删除失败IO错误");
case ErrorCode.FILE_NOT_FOUND -> response.failure("本子:" + gallery.getName() + deleteType + "删除失败,文件不存在");
case 0 -> response.success();
}
if(response.get("result").equals("failure"))
log.info(response.getResult());
}
}
return response.toJSONString();
}
/**
* 更新本子
* @param link 目标链接
* @return 更新结果
*/
public String updateGallery(String link){
Response response = Response.generateResponse();
Integer gid = parseGid(link);
if(gid == null) {
response.failure("链接格式出错");
return response.toJSONString();
}
Gallery oldGallery = galleryMapper.selectGalleryByGid(gid);
if(oldGallery == null) {
response.failure("数据库不存在该本子,请提交下载");
return response.toJSONString();
}
try {
String newLink = GalleryUtil.queryUpdateLink(oldGallery.getLink());
if(newLink == null)
response.failure("本子没有更新");
else{
Gallery newGallery = GalleryUtil.parse(newLink, true, oldGallery.getResolution());
if(newGallery != null) {
galleryMapper.deleteGalleryByGid(oldGallery.getGid());
remoteService.updateGallery(newGallery, oldGallery.getMode());
newGallery.setDownloader(oldGallery.getDownloader());
newGallery.setCollector(oldGallery.getCollector());
long usedAmount = Long.parseLong(configurationMapper.selectConfiguration(CustomConfiguration.WEEK_USED_AMOUNT).getValue());
usedAmount += newGallery.getFileSize();
configurationMapper.updateConfiguration(CustomConfiguration.WEEK_USED_AMOUNT, String.valueOf(usedAmount));
galleryMapper.insertGallery(newGallery);
response.success("提交更新成功,更新页数:" + newGallery.getPages());
}
else {
response.failure("存在更新,但更新失败");
}
}
}catch (Exception e){
response.failure("获取更新失败");
}
return response.toJSONString();
}
/**
* 获取每周额度以及上次重置时间
* @return 查询结果
*/
public String getWeekUsedAmount() {
Response response = Response.generateResponse();
CustomConfiguration weekUsedAmount = configurationMapper.selectConfiguration(CustomConfiguration.WEEK_USED_AMOUNT);
CustomConfiguration lastResetAmountTime = configurationMapper.selectConfiguration(CustomConfiguration.LAST_RESET_AMOUNT_TIME);
Map<String, String> data = new HashMap<>();
data.put("weekUsedAmount", CustomUtil.fileSizeToString(Long.parseLong(weekUsedAmount.getValue())));
data.put("lastResetAmountTime", lastResetAmountTime.getValue());
response.success(new ObjectMapper().valueToTree(data).toString());
return response.toJSONString();
}
/**
* 获取缩略图 直接按照名字路径获取后期可能会加入缓存
* @param request 请求对象
* @param response 响应对象
* @param name 本子名字
*/
public void getThumbnail(HttpServletRequest request, HttpServletResponse response, String name){
File thumbnail = new File(TargetPath + name, "thumbnail.webp");
if(!thumbnail.exists())
if(remoteService.cachePreview(name, (short) 0, thumbnail) != 0){
fourZeroFour(response);
return;
}
FileDownload.export(request, response, thumbnail.getAbsolutePath());
}
/**
* 获取在线图片
* @param gid gid
* @param page 文件名
* @param request 请求对象
* @param response 响应对象
*/
public void getOnlineImage(Integer gid, Short page, HttpServletRequest request, HttpServletResponse response){
String name;
//本子名缓存
if((name = gid2name_cache.get(gid)) == null) //内存
if((name = galleryMapper.selectGalleryNameByGid(gid)) == null) { //数据库
fourZeroFour(response);
return;
}
else
gid2name_cache.put(gid, name);
//页数缓存
Integer real_page;
if((real_page = gid2page_cache.get(gid)) == null)
real_page = galleryMapper.selectGalleryByGid(gid).getPages();
//判断页数是否超出
if(real_page < page || page < 0){
fourZeroFour(response);
return;
}
//页名缓存
String pageName;
if((pageName = page2pageNameCache.get(gid + String.valueOf(page))) == null) //内存
if((pageName = pageNameCacheMapper.selectPageName(gid, page)) == null) //数据库
if ((pageName = remoteService.queryPageName(name, page)) == null) { //远程查询
fourZeroFour(response);
return;
}
//插入数据库
else
pageNameCacheMapper.insertPageNameCache(new PageNameCache(gid, page, pageName));
//插入缓存
else
page2pageNameCache.put(gid + String.valueOf(page), pageName);
//图片缓存
File file = new File(TargetPath, name + "/" + pageName);
if(!file.exists()) //硬盘
if(remoteService.cachePreview(name, page, file) != 0) { //远程
fourZeroFour(response);
return;
}
FileDownload.export(request, response, file.getAbsolutePath());
}
public String shareGallery(Integer gid, Integer expireHour){
Response response = Response.generateResponse();
Gallery gallery = galleryMapper.selectGalleryByGid(gid);
Map<String, String> jsonObject = new HashMap<>();
if(gallery == null){
response.failure("本子不存在");
return response.toJSONString();
}
File file = new File(TargetPath + gallery.getName() + ".zip");
if(!file.isFile()){
response.failure("本子文件不存在");
return response.toJSONString();
}
ShareFile shareFile = shareFileMapper.selectShareFileByFilePath(file.getAbsolutePath());
if(shareFile != null){
jsonObject.put("shareCode", shareFile.getShareCode());
jsonObject.put("expireTime", dateTimeFormatter.format(LocalDateTime.ofInstant(shareFile.getExpireTime().toInstant(), ZoneId.systemDefault())));
response.success(new ObjectMapper().valueToTree(jsonObject).toString());
return response.toJSONString();
}
String ShareCode = RandomUtil.randomString(8);
Calendar expireTime = Calendar.getInstance();
expireTime.add(Calendar.HOUR, expireHour);
shareFileMapper.insertShareFile(ShareCode, file.getAbsolutePath(), expireTime.getTime());
jsonObject.put("shareCode", ShareCode);
jsonObject.put("expireTime", dateTimeFormatter.format(expireTime.getTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()));
response.success(new ObjectMapper().valueToTree(jsonObject).toString());
return response.toJSONString();
}
public static Integer parseGid(String link){
try {
return Integer.parseInt(link.split("/g/")[1].split("/")[0]);
}catch (IndexOutOfBoundsException e){
return null;
}
}
}

View File

@ -0,0 +1,299 @@
package com.lion.lionwebsite.Service;
import com.lion.lionwebsite.Dao.CustomConfigurationMapper;
import com.lion.lionwebsite.Dao.GalleryMapper;
import com.lion.lionwebsite.Dao.ShareFileMapper;
import com.lion.lionwebsite.Domain.CustomConfiguration;
import com.lion.lionwebsite.Domain.ShareFile;
import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.GalleryUtil;
import jakarta.annotation.Resource;
import lombok.Data;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.io.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static java.nio.file.FileVisitResult.CONTINUE;
@Service
@ConfigurationProperties(prefix = "local-service")
@Data
public class LocalServiceImpl{
String fires;
String DouNaiClash;
String DouNaiV2ray;
@Resource
CustomConfigurationMapper configurationMapper;
@Resource
ShareFileMapper shareFIleMapper;
@Resource
GalleryMapper galleryMapper;
/**
* 每周周一四点重置额度
*/
@Scheduled(cron = "0 0 4 * * MON")
public void reset() {
configurationMapper.updateConfiguration(CustomConfiguration.WEEK_USED_AMOUNT, "0");
configurationMapper.updateConfiguration(CustomConfiguration.LAST_RESET_AMOUNT_TIME, CustomUtil.now());
}
/**
* 定时更新订阅
* @throws IOException 下载以及保存异常
*/
@Scheduled(fixedRate = 86400000)
public void updateSubScheduler() throws IOException {
updateSub(false);
}
/**
* 更新订阅链接的实际方法
*/
public boolean updateSub(boolean isManual) throws IOException {
DateTimeFormatter dateTimeFormatter = CustomUtil.dateTimeFormatter();
CustomConfiguration customConfiguration = configurationMapper.selectConfiguration(CustomConfiguration.LAST_UPDATE_SUB_TIME);
//如果不是手动则判断更新间隔是否满足不满足则取消更新
if (!isManual) {
LocalDateTime lastUpdate = LocalDateTime.parse(customConfiguration.getValue(), CustomUtil.dateTimeFormatter());
LocalDateTime now = LocalDateTime.now();
now = now.plusHours(-3);
if(now.isBefore(lastUpdate))
return false;
}
File mixin = new File("sub/sub.txt");
File firesFile = new File("sub/fires.txt");
File DouNaiClashFile = new File("sub/DouNaiClash.txt");
File DouNaiV2rayFile = new File("sub/DouNaiV2ray.txt");
File directory = new File("sub");
if(!directory.isDirectory())
Files.createDirectory(Paths.get("sub"));
List<String> fires_profile = null;
List<String> DouNaiClash_profile = null;
OutputStream outputStream;
//下载薯条订阅
try(FileWriter writer = new FileWriter(firesFile)) {
fires_profile = Get(fires);
for (String i : fires_profile)
writer.write(i + "\n");
System.out.println("load fires complete");
}catch (IOException e) {
e.printStackTrace();
System.out.println("load fires failure");
}
//下载豆奶v2ray订阅
try(FileWriter writer = new FileWriter(DouNaiV2rayFile)) {
String DouNaiV2rayRaw = Get(DouNaiV2ray).get(0);
String[] v2rayPlain = new String(Base64.getDecoder().decode(DouNaiV2rayRaw)).split("\n");
StringBuilder stringBuilder = new StringBuilder();
//过滤高倍率节点
for(String node: v2rayPlain){
String name = URLDecoder.decode(node.split("#")[1], StandardCharsets.UTF_8);
if(name.startsWith("") && name.contains("流量")){
float ratio = Float.parseFloat(name.substring(name.indexOf("(") + 1, name.indexOf(")")).replace("倍流量", ""));
if(ratio <= 1)
stringBuilder.append(node).append("\n");
}
else{
stringBuilder.append(node).append("\n");
}
}
writer.write(new String(Base64.getEncoder().encode(stringBuilder.toString().getBytes(StandardCharsets.UTF_8))));
System.out.println("load DouNai v2ray complete");
}catch (IOException e){
e.printStackTrace();
System.out.println("load DouNai v2ray failure");
}
//下载豆奶clash订阅
try(FileWriter writer = new FileWriter(DouNaiClashFile)) {
DouNaiClash_profile = Get(DouNaiClash);
//过滤高倍率节点
ArrayList<String> clashProcessed = new ArrayList<>();
boolean isProxies = false;
boolean skip = false;
for(String line: DouNaiClash_profile){
if(line.equals("proxies:"))
isProxies = true;
else if(line.equals("proxy-groups:") && isProxies)
isProxies = false;
if(isProxies) {
if (line.contains("name"))
skip = line.contains("流量");
if (!skip)
clashProcessed.add(line);
}
else
if (!line.contains("流量"))
clashProcessed.add(line);
}
for(String line: clashProcessed)
writer.write(line + "\n");
System.out.println("load DouNai clash complete");
}catch (IOException e){
e.printStackTrace();
System.out.println("load DouNai clash failure");
}
//处理合并
assert DouNaiClash_profile != null;
int side_start = DouNaiClash_profile.indexOf("proxies:");
int side_end = DouNaiClash_profile.indexOf("proxy-groups:");
List<String> side_proxy = new ArrayList<>();
List<String> side_name = new ArrayList<>();
for (int i = side_start + 1; i < side_end; i++) {
side_proxy.add(DouNaiClash_profile.get(i));
if (DouNaiClash_profile.get(i).contains("name"))
side_name.add(DouNaiClash_profile.get(i).replace("name:", ""));
}
List<String> final_profile = new ArrayList<>();
assert fires_profile != null;
for (String i : fires_profile) {
if (i.equals("proxy-groups:"))
final_profile.addAll(side_proxy);
final_profile.add(i);
if (i.equals("proxy-groups:")) {
final_profile.add(" -");
final_profile.add(" name: e站流量");
final_profile.add(" type: select");
final_profile.add(" proxies:");
for (String x : side_name)
final_profile.add(" " + x);
}
if (i.equals("rules:"))
final_profile.add(" - DOMAIN-KEYWORD,hentai,e站流量");
}
outputStream = Files.newOutputStream(mixin.toPath());
for(String i: final_profile)
outputStream.write((i + "\n").getBytes(StandardCharsets.UTF_8));
outputStream.close();
configurationMapper.updateConfiguration(CustomConfiguration.LAST_UPDATE_SUB_TIME, dateTimeFormatter.format(LocalDateTime.now()));
return true;
}
/**
* 封装好的get方法
* @param url url
* @return 请求结果
* @throws IOException 网络异常
*/
public static ArrayList<String> Get(String url) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse httpResponse;
HttpGet httpGet = new HttpGet(url);
httpResponse = httpClient.execute(httpGet);
HttpEntity responseEntity = httpResponse.getEntity();
int statusCode = httpResponse.getStatusLine().getStatusCode();
ArrayList<String> temp = new ArrayList<>();
if (statusCode == 200) {
BufferedReader reader = new BufferedReader(new InputStreamReader(responseEntity.getContent()));
String str;
while ((str = reader.readLine()) != null)
temp.add(str);
}
httpClient.close();
httpResponse.close();
return temp;
}
/**
* 每天四点清理过期分享码
*/
@Scheduled(cron = "0 0 4 * * *")
public void checkShareCode(){
ShareFile[] shareFiles = shareFIleMapper.selectAllShareFile();
Calendar now;
Calendar expireTime;
for(ShareFile shareFile: shareFiles){
now = Calendar.getInstance();
expireTime = Calendar.getInstance();
expireTime.setTime(shareFile.getExpireTime());
if(now.after(expireTime))
shareFIleMapper.deleteShareFile(shareFile.getShareCode());
}
}
/**
* 每周查看缩略图数量按照访问时间排序删除超出1w的部分
*/
@Scheduled(cron = "0 0 4 1 * *")
public void clearThumbnailCache(){
String cachePath = "/storage/hentaiCache/";
File directory = new File(cachePath);
List<Path> files = new ArrayList<>();
try {
Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (attrs.isRegularFile()) {
files.add(file);
}
return CONTINUE;
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
files.sort((f1, f2) -> {
try {
return Files.readAttributes(f1, BasicFileAttributes.class).lastAccessTime()
.compareTo(Files.readAttributes(f2, BasicFileAttributes.class).lastAccessTime());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
if (files.size() > 10000) {
List<Path> toDelete = files.subList(0, files.size() - 10000);
for (Path file : toDelete) {
try {
Files.delete(file);
}catch (IOException e){
e.printStackTrace();
}
}
System.out.println("Deleted " + toDelete.size() + " files");
} else {
System.out.println("No files to delete");
}
}
}

View File

@ -0,0 +1,365 @@
package com.lion.lionwebsite.Service;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import com.lion.lionwebsite.Dao.CustomConfigurationMapper;
import com.lion.lionwebsite.Dao.ShareFileMapper;
import com.lion.lionwebsite.Dao.UserMapper;
import com.lion.lionwebsite.Domain.CustomConfiguration;
import com.lion.lionwebsite.Domain.ShareFile;
import com.lion.lionwebsite.Interceptor.TaskHandlerInterceptor;
import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.FileDownload;
import com.lion.lionwebsite.Util.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Service
@Data
@ConfigurationProperties(prefix = "personal-service")
@Slf4j
public class PersonalServiceImpl{
@Resource
CustomConfigurationMapper configurationMapper;
@Resource
UserMapper userMapper;
@Resource
ShareFileMapper shareFileMapper;
@Resource
TaskHandlerInterceptor taskHandlerInterceptor;
String StoragePath;
DateTimeFormatter dateTimeFormatter = CustomUtil.dateTimeFormatter();
ExecutorService compressThreadPool;
PersonalServiceImpl(){
compressThreadPool = Executors.newFixedThreadPool(1);
}
/**
* 获取文件列表同时带上分享码以及过期时间
* @param path 路径
* @return 文件列表
*/
public String getFiles(String path) {
Response response = Response.generateResponse();
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
File root_file = new File(StoragePath + path);
Calendar now = Calendar.getInstance();
Calendar expireTime = Calendar.getInstance();
//如果目标路径是文件夹
if(root_file.isDirectory()) {
ArrayList<Map<String, String>> fileMaps = new ArrayList<>();
File[] originalFiles = root_file.listFiles();
//如果文件夹里的文件不为空
if (originalFiles != null) {
originalFiles = Arrays.stream(originalFiles).sorted(Comparator.comparing(File::getName)).toArray(File[]::new);
ArrayList<ShareFile> shareFiles = shareFileMapper.selectShareFilesByFilePath(root_file.getAbsolutePath());
//遍历文件放入文件信息以及查询对应的分享码
for (File file : originalFiles) {
Map<String, String> fileMap = new LinkedHashMap<>();
fileMap.put("name", file.getName());
fileMap.put("path", file.getAbsolutePath());
if (file.isDirectory()) {
fileMap.put("type", "FOLDER");
} else if (file.isFile()) {
fileMap.put("type", "FILE"); //处理文件大小单位
String fileSize = CustomUtil.fileSizeToString(file.length());
fileMap.put("size", fileSize);
Iterator<ShareFile> iterator = shareFiles.iterator();
while(iterator.hasNext()){
ShareFile shareFile = iterator.next();
if(shareFile.getFilePath().equals(file.getAbsolutePath())){
expireTime.setTime(shareFile.getExpireTime());
if(now.after(expireTime)){
shareFileMapper.deleteShareFile(shareFile.getShareCode());
}
else {
fileMap.put("shareCode", shareFile.getShareCode());
fileMap.put("expireTime", dateTimeFormatter.format(shareFile.getExpireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()));
}
iterator.remove();
break;
}
}
}
fileMaps.add(fileMap);
}
response.success(new ObjectMapper().valueToTree(fileMaps).toString());
}
else
response.failure("文件夹为空");
}
return response.toJSONString();
}
/**
* 下载文件
* @param request 请求对象
* @param response 响应对象
* @param path 目标路径
*/
public void download(HttpServletRequest request, HttpServletResponse response, String path){
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
File file = new File(path);
if(file.exists())
FileDownload.export(request, response, path);
else
try{
response.getWriter().print("404 NOT FOUND");
}catch (IOException e){
e.printStackTrace();
}
}
/**
* 上传文件
* @param path 目标路径
* @param fileName 文件名称
* @param file 文件对象
* @return 上传结果
*/
public String uploadFile(String path, String fileName, MultipartFile file) {
Response response = Response.generateResponse();
log.info("上传文件:{}, 目标路径:{}", fileName, path);
if(path == null || fileName == null || file == null){
response.failure("参数不完整");
return response.toJSONString();
}
File directory = new File(StoragePath + path);
if(directory.isDirectory()){
File targetFile = new File(StoragePath + path, fileName);
if(targetFile.exists())
response.failure("目标文件已存在");
else
try {
file.transferTo(Path.of(StoragePath + path, fileName));
response.success("上传成功");
} catch (IOException e) {
response.failure("上传失败");
e.printStackTrace();
}
}
else
response.failure("该路径不存在或者不是文件夹");
return response.toJSONString();
}
/**
* 创建分享码
* @param path 目标路径
* @param expireHour 过期时间
* @return 如果成功则是分享码以及过期时间失败则是失败原因
*/
public String shareFile(String path, Integer expireHour) {
Response response = Response.generateResponse();
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
Map<String, String> jsonObject = new HashMap<>();
File file = new File(path);
if(file.isFile()){ //如果是文件,则生成分享码调用此接口时不需要考虑分享已分享文件以及分享已过期文件
String ShareCode;
ShareCode = RandomUtil.randomString(8);
Calendar expireTime = Calendar.getInstance();
expireTime.add(Calendar.HOUR, expireHour);
shareFileMapper.insertShareFile(ShareCode, path, expireTime.getTime());
jsonObject.put("shareCode", ShareCode);
jsonObject.put("expireTime", dateTimeFormatter.format(expireTime.getTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()));
response.success(new ObjectMapper().valueToTree(jsonObject).toString());
}
else
response.failure("此路径为文件夹或不存在");
return response.toJSONString();
}
/**
* 延长分享时间
* @param path 目标文件路径
* @param extendHour 延长小时数
* @return 延长结果
*/
public String extendShareTime(String path, Integer extendHour) {
Response response = Response.generateResponse();
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
Map<String, String> data = new LinkedHashMap<>();
ShareFile shareFile = shareFileMapper.selectShareFileByFilePath(path);
if(shareFile != null){
Calendar calendar = Calendar.getInstance();
calendar.setTime(shareFile.getExpireTime());
calendar.add(Calendar.HOUR, extendHour);
shareFile.setExpireTime(calendar.getTime());
shareFileMapper.updateShareFile(shareFile);
data.put("expireTime", dateTimeFormatter.format(shareFile.getExpireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()));
data.put("path", shareFile.getFilePath());
response.success(new ObjectMapper().valueToTree(data).toString());
}
else{
response.failure("该文件未被分享");
}
return response.toJSONString();
}
/**
* 取消分享
* @param path 目标路径
* @return 取消结果
*/
public String cancelShare(String path) {
Response response = Response.generateResponse();
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
ShareFile shareFile = shareFileMapper.selectShareFileByFilePath(path);
if(shareFile != null){
shareFileMapper.deleteShareFile(shareFile.getShareCode());
response.success("取消分享成功");
}
else{
response.failure("该文件未被分享");
}
return response.toJSONString();
}
/**
* 获取订阅文件上次更新时间
* @return 订阅文件上次更新时间
*/
public String lastUpdate() {
Response response = Response.generateResponse();
CustomConfiguration configuration = configurationMapper.selectConfiguration(CustomConfiguration.LAST_UPDATE_SUB_TIME);
response.success(configuration.getValue());
return response.toJSONString();
}
/**
* 获取家里的ip
* @return 家里的ip
*/
public String getIp(){
Response response = Response.generateResponse();
Map<String, String> jsonObject = new HashMap<>();
String ip = configurationMapper.selectConfiguration(CustomConfiguration.CURRENT_IP_ADDRESS).getValue();
String updateTime = configurationMapper.selectConfiguration(CustomConfiguration.LAST_UPDATE_IP_ADDRESS_TIME).getValue();
jsonObject.put("ip", ip);
jsonObject.put("lastUpdateTime", updateTime);
response.success(new ObjectMapper().valueToTree(jsonObject).toString().replace("\"", " ").replace("\\", " "));
return response.toJSONString();
}
/**
* 打包文件
* @param path 目标路径
* @return 响应提交成功因为该方法为异步执行未完成时后辍为undone
*/
public String compress(String path) {
Response response = Response.generateResponse();
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
File file = new File(path);
String finalPath = path;
if(!file.isDirectory() || !file.exists()){
response.failure("选中的路径不是文件夹");
return response.toJSONString();
}
compressThreadPool.submit(() -> {
try(OutputStream bos = new BufferedOutputStream(Files.newOutputStream(Paths.get(finalPath + ".tar***undone")));
TarArchiveOutputStream aos = new TarArchiveOutputStream(bos)) {
aos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); //解除文件名长度限制
Path dirPath = Paths.get(finalPath);
Files.walkFileTree(dirPath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
ArchiveEntry entry = new TarArchiveEntry(dir.toFile(), dirPath.relativize(dir).toString());
aos.putArchiveEntry(entry);
aos.closeArchiveEntry();
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
ArchiveEntry entry = new TarArchiveEntry(file.toFile(), dirPath.relativize(file).toString());
aos.putArchiveEntry(entry);
IOUtils.copy(Files.newInputStream(file.toFile().toPath()), aos);
aos.closeArchiveEntry();
return super.visitFile(file, attrs);
}
});
File targetFile = new File(finalPath + ".tar***undone");
log.info("打包成功,重命名:" + targetFile.renameTo(new File(finalPath + ".tar")));
}catch (IOException e){
e.printStackTrace();
log.info("打包失败,删除文件结果:" + new File(finalPath + ".tar***undone").delete());
}
});
response.success("加入队列成功");
return response.toJSONString();
}
/**
* 删除文件
* @param path 目标路径
* @return 删除结果
*/
public String deleteFile(String path) {
Response response = Response.generateResponse();
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
File file = new File(path);
if(FileUtil.del(file))
response.success("删除成功");
else
response.failure("删除失败");
return response.toJSONString();
}
}

View File

@ -0,0 +1,161 @@
package com.lion.lionwebsite.Service;
import com.lion.lionwebsite.Dao.CustomConfigurationMapper;
import com.lion.lionwebsite.Dao.ShareFileMapper;
import com.lion.lionwebsite.Dao.UserMapper;
import com.lion.lionwebsite.Domain.CustomConfiguration;
import com.lion.lionwebsite.Domain.ShareFile;
import com.lion.lionwebsite.Domain.User;
import com.lion.lionwebsite.Interceptor.TaskHandlerInterceptor;
import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.FileDownload;
import com.lion.lionwebsite.Util.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import static com.lion.lionwebsite.Util.CustomUtil.fourZeroFour;
@Service
public class PublicServiceImpl {
@Resource
CustomConfigurationMapper configurationMapper;
@Resource
ShareFileMapper shareFIleMapper;
@Resource
UserMapper userMapper;
@Resource
TaskHandlerInterceptor taskHandlerInterceptor;
/**
* 记录家里ip地址
* @param ip ip地址
*/
public void logIpAddress(String ip) {
configurationMapper.updateConfiguration(CustomConfiguration.CURRENT_IP_ADDRESS, ip);
configurationMapper.updateConfiguration(CustomConfiguration.LAST_UPDATE_IP_ADDRESS_TIME, CustomUtil.now());
}
/**
* 通过分享码获取文件
* @param httpRequest 请求对象
* @param httpResponse 响应对象
* @param ShareCode 分享码
* @throws IOException 响应时的异常
*/
public boolean GetFile(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String ShareCode) throws IOException {
Response response = Response.generateResponse();
//参数为空的情况
if(ShareCode == null) {
response.failure("ShareCode invalid");
} else {
ShareFile shareFile = shareFIleMapper.selectShareFileByShareCode(ShareCode);
Calendar ExpireTime = Calendar.getInstance();
Calendar now = Calendar.getInstance();
if (shareFile != null) {
ExpireTime.setTime(shareFile.getExpireTime());
if (ExpireTime.after(now) && new File(shareFile.getFilePath()).isFile()) {
FileDownload.export(httpRequest, httpResponse, shareFile.getFilePath());
return true;
} else {
shareFIleMapper.deleteShareFile(shareFile.getShareCode());
response.failure("ShareCode is expired or File is not exist");
}
} else
response.failure("ShareCode is not exist or expired");
}
httpResponse.getOutputStream().write(response.toJSONString().getBytes(StandardCharsets.UTF_8));
return false;
}
/**
* 修改授权码如果能够执行此方法则授权码一定存在
* @param AuthCode 原来的授权码
* @param newAuthCode 新的授权码
* @return 修改结果
*/
public String alterAuthCode(String AuthCode, String newAuthCode) {
Response response = Response.generateResponse();
userMapper.updateAuthCode(AuthCode, newAuthCode);
taskHandlerInterceptor.updateAuthCodes();
response.success("修改成功");
return response.toJSONString();
}
public User getUserId(String AuthCode){
return userMapper.selectUserByAuthCode(AuthCode);
}
public void getEhThumbnail(String path, HttpServletResponse response){
String url;
if(!path.contains("/")){
fourZeroFour(response);
return;
}
url = "https://ehgt.org/" + path;
try{
byte[] imageBytes = getImageBytesFromUrl(url);
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(imageBytes);
outputStream.close();
}catch (IOException e){
e.printStackTrace();
try {
response.sendError(503);
}catch (IOException ex){
ex.printStackTrace();
}
}
}
public static byte[] getImageBytesFromUrl(String imageUrl) throws IOException {
URL url = new URL(imageUrl);
InputStream inputStream = null;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
// 打开URL连接
inputStream = url.openStream();
byte[] buffer = new byte[1024];
int bytesRead;
// 从输入流读取数据并写入输出流
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
} finally {
// 关闭流
if (inputStream != null) {
inputStream.close();
}
outputStream.close();
}
// 返回图片的字节数组
return outputStream.toByteArray();
}
}

View File

@ -0,0 +1,174 @@
package com.lion.lionwebsite.Service;
import com.lion.lionwebsite.Domain.GalleryForQuery;
import com.lion.lionwebsite.Util.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Service;
import java.io.*;
import java.util.*;
@Service
@Slf4j
public class QueryService {
String CachePath = "/storage/hentaiCache/";
public String query(String keyword, String prev, String next) {
Response response = Response.generateResponse();
String get;
String param = "?f_search=" + keyword.replace(" ", "+");
if(prev != null)
param += "&prev=" + prev;
else if(next != null)
param += "&next=" + next;
try{
get = requests("https://exhentai.org/" + param, false, null, null);
}catch (IOException e){
e.printStackTrace();
response.failure("query failure");
return response.toJSONString();
}
Document parse = Jsoup.parse(get);
Elements elements = parse.select("body > div.ido > div:nth-child(2) > table > tbody > tr");
ArrayList<GalleryForQuery> galleries = new ArrayList<>();
ListIterator<Element> elementListIterator = elements.listIterator();
try {
elementListIterator.next(); //skip firstlink
}catch (NullPointerException | NoSuchElementException e){
response.failure("没有搜索到结果");
return response.toJSONString();
}
while (elementListIterator.hasNext()){
Element element = elementListIterator.next();
GalleryForQuery gallery = new GalleryForQuery();
gallery.setType(element.child(0).text()); //type
String src = element.child(1).select("img").attr("src");
if (src.startsWith("data"))
src = element.child(1).select("img").attr("data-src");
src = src.replace("https://s.exhentai.org", "");
gallery.setThumbnailUrl(src); //thumbnailSrc
gallery.setUploadTime(element.child(1).select("div > div [onclick]").get(1).text()); //uploadTime
gallery.setLink(element.child(2).child(0).attr("href")); //link
gallery.setName(element.child(2).child(0).child(0).text()); //name
gallery.setPage(Integer.parseInt(element.child(3).child(1).text().split(" ")[0])); //page
galleries.add(gallery);
}
response.success(new ObjectMapper().valueToTree(galleries).toString());
Elements nextLink = parse.select("#unext");
if(nextLink.hasAttr("href"))
response.set("next", nextLink.attr("href"));
Elements previousLink = parse.select("#uprev");
if(previousLink.hasAttr("href"))
response.set("previous", previousLink.attr("href"));
Elements firstLink = parse.select("#ufirst");
if(firstLink.hasAttr("href"))
response.set("first", firstLink.attr("href"));
Elements lastLink = parse.select("#ulast");
if(lastLink.hasAttr("href"))
response.set("last", lastLink.attr("href"));
return response.toJSONString();
}
public void image(HttpServletResponse response, String path){
String imageName = path.substring(path.lastIndexOf("/") + 1);
File image = new File(CachePath, imageName);
if(image.isFile()){ //hit cache
log.info("hit cache:{}", imageName);
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(image));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(response.getOutputStream())){
bufferedInputStream.transferTo(bufferedOutputStream);
}catch (IOException e){
e.printStackTrace();
}
}
else{ // transfer image and save it as cache
log.info("miss cache:{}", imageName);
try {
requests("https://s.exhentai.org" + path, true, response, new FileOutputStream(image));
}catch (IOException e){
e.printStackTrace();
}
}
}
public String requests(String url, boolean isDirect, HttpServletResponse response, OutputStream local) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse httpResponse;
HashMap<String, String> headers = new HashMap<>();
headers.put("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36");
headers.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
headers.put("Upgrade-Insecure-Requests", "1");
headers.put("Cookie", "ipb_member_id=5774855; ipb_pass_hash=4b061c3abe25289568b5a8e0123fb3b9; igneous=cea2e08fb; sk=oye107wk02gtomb56x65dmv4qzbn; nw=1");
HttpGet httpGet = new HttpGet(url);
for (Map.Entry<String, String> header: headers.entrySet()){
httpGet.addHeader(header.getKey(), header.getValue());
}
httpResponse = httpClient.execute(httpGet);
HttpEntity responseEntity = httpResponse.getEntity();
int statusCode = httpResponse.getStatusLine().getStatusCode();
try {
if (statusCode == 200) {
if (isDirect) {
InputStream inputStream = new BufferedInputStream(responseEntity.getContent());
byte[] bytes = inputStream.readAllBytes();
if(response != null) {
OutputStream outputStream = response.getOutputStream();
outputStream.write(bytes);
outputStream.close();
}
if(local != null){
local.write(bytes);
local.close();
}
inputStream.close();
return null;
} else {
StringBuilder stringBuilder = new StringBuilder();
String str;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(responseEntity.getContent()));
while ((str = bufferedReader.readLine()) != null) {
stringBuilder.append(str).append("\n");
}
return stringBuilder.toString();
}
} else {
System.out.println(statusCode);
return null;
}
}finally {
httpClient.close();
httpResponse.close();
}
}
}

View File

@ -0,0 +1,287 @@
package com.lion.lionwebsite.Service;
import com.lion.lionwebsite.Dao.GalleryMapper;
import com.lion.lionwebsite.Domain.Gallery;
import com.lion.lionwebsite.Domain.GalleryTask;
import com.lion.lionwebsite.Message.*;
import com.lion.lionwebsite.Util.CustomUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Service
@Data
@Slf4j
@ConfigurationProperties(prefix = "remote-service")
public class RemoteService {
ChannelFuture channelFuture;
Channel channel;
String ip = "5.255.110.45";
short port = 26321;
String storagePath;
@Resource
GalleryMapper galleryMapper;
HashMap<Integer, Promise<AbstractMessage>> promiseHashMap;
EventLoop eventLoopGroup;
ExecutorService downloadThread;
AtomicInteger atomicInteger;
public RemoteService(){
atomicInteger = new AtomicInteger(0);
eventLoopGroup = new DefaultEventLoop();
downloadThread = Executors.newCachedThreadPool();
promiseHashMap = new HashMap<>();
try {
channelFuture = new Bootstrap()
.channel(NioSocketChannel.class)
.group(new NioEventLoopGroup())
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel){
channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 1, 4));
channel.pipeline().addLast(new MessageCodec());
channel.pipeline().addLast(new LoggingHandler());
channel.pipeline().addLast(new MyChannelInboundHandlerAdapter());
}
}).connect(new InetSocketAddress(ip, port)).sync();
log.info("connect success");
channel = channelFuture.channel();
channel.writeAndFlush(new IdentityMessage());
}catch (InterruptedException e){
e.printStackTrace();
}
}
public boolean isAlive(){
return !(channelFuture.channel() == null) || channelFuture.channel().isActive();
}
public byte addGalleryToQueue(Gallery gallery, byte type){
GalleryTask galleryTask = new GalleryTask();
galleryTask.setGid(gallery.getGid());
galleryTask.setType(type);
DownloadPostMessage dpm = new DownloadPostMessage();
dpm.messageId = atomicInteger.getAndIncrement();
dpm.setGalleryTask(galleryTask);
channel.writeAndFlush(dpm);
DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoopGroup);
promiseHashMap.put(dpm.messageId, promise);
try {
boolean result = promise.await(10, TimeUnit.SECONDS);
if(result){
ResponseMessage rsm = (ResponseMessage)promise.getNow();
return rsm.getResult();
}
else return -1;
}catch (InterruptedException e){
e.printStackTrace();
return -1;
}
}
public byte deleteGallery(Gallery gallery, byte type){
DeleteGalleryMessage dgm = new DeleteGalleryMessage();
dgm.setGalleryName(gallery.getName());
dgm.setDeleteType(type);
dgm.messageId = atomicInteger.getAndIncrement();
channel.writeAndFlush(dgm);
DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoopGroup);
promiseHashMap.put(dgm.messageId, promise);
try{
boolean result = promise.await(10, TimeUnit.SECONDS);
if(result){
ResponseMessage rsm = (ResponseMessage) promise.getNow();
return rsm.getResult();
}else return -1;
}catch (InterruptedException e){
return -1;
}
}
public byte cachePreview(String galleryName, short page, File file){
File parentFile = file.getParentFile();
if(!parentFile.isDirectory())
if(parentFile.mkdirs())
log.info("创建文件夹{}成功", parentFile.getAbsolutePath());
else
log.error("创建文件夹{}失败", parentFile.getAbsolutePath());
GalleryRequestMessage grm = new GalleryRequestMessage();
grm.setGalleryName(galleryName);
grm.setPage(page);
grm.setType(GalleryRequestMessage.PREVIEW);
grm.messageId = atomicInteger.getAndIncrement();
DefaultPromise<Object> downloadPromise = new DefaultPromise<>(eventLoopGroup);
DefaultPromise<Object> readyPromise = new DefaultPromise<>(eventLoopGroup);
downloadThread.submit(() -> {
try {
short port = CustomUtil._findIdlePort();
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.bind(new InetSocketAddress("0.0.0.0", port));
grm.setPort(port);
readyPromise.setSuccess("");
SocketChannel socketChannel = ssChannel.accept();
FileChannel fileChannel = FileChannel.open(file.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (socketChannel.read(byteBuffer) != -1){
byteBuffer.flip();
fileChannel.write(byteBuffer);
byteBuffer.clear();
}
fileChannel.close();
socketChannel.close();
ssChannel.close();
log.info("缓存预览图:" + galleryName + " " + file.getName());
downloadPromise.setSuccess("");
}catch (IOException e){
e.printStackTrace();
}
});
try{
readyPromise.await();
channel.writeAndFlush(grm);
DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoopGroup);
promiseHashMap.put(grm.messageId, promise);
boolean result = promise.await(10, TimeUnit.SECONDS);
if(result){
ResponseMessage rsm = (ResponseMessage) promise.getNow();
if(rsm.getResult() == 0){
downloadPromise.await(10, TimeUnit.SECONDS);
return (byte) (downloadPromise.isSuccess()?0:1);
}else{
return rsm.getResult();
}
}else {
return -1;
}
}catch (InterruptedException e){
return -1;
}
}
public String queryPageName(String name, int page){
GalleryPageQueryMessage gpqm = new GalleryPageQueryMessage();
gpqm.setName(name);
gpqm.setPage(page);
gpqm.messageId = atomicInteger.getAndIncrement();
channel.writeAndFlush(gpqm);
DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoopGroup);
promiseHashMap.put(gpqm.messageId, promise);
try{
boolean result = promise.await(10, TimeUnit.SECONDS);
if(result){
gpqm = (GalleryPageQueryMessage) promise.getNow();
if(gpqm.getResult() == 0)
return gpqm.getPageName();
else {
log.error("galleryPageQuery error:" + gpqm.getResult());
return null;
}
}else{
return null;
}
}catch (InterruptedException e){
return null;
}
}
public void updateGallery(Gallery gallery, byte type){
GalleryTask galleryTask = new GalleryTask();
galleryTask.setGid(gallery.getGid());
galleryTask.setType(type);
galleryTask.setPages(gallery.getPages());
UpdateGalleryMessage ugm = new UpdateGalleryMessage();
ugm.messageId = atomicInteger.getAndIncrement();
ugm.setGalleryTask(galleryTask);
channel.writeAndFlush(ugm);
DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoopGroup);
promiseHashMap.put(ugm.messageId, promise);
try {
boolean result = promise.await(10, TimeUnit.SECONDS);
if(result){
ResponseMessage rsm = (ResponseMessage)promise.getNow();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
class MyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//如果不是响应信息或者响应信息为失败则打印
if(!(msg instanceof ResponseMessage rm) || rm.getResult() != 0)
System.out.println(msg);
//下载状态
if(msg instanceof DownloadStatusMessage dsm){
GalleryTask[] galleryTasks = dsm.getGalleryTasks();
for (GalleryTask galleryTask : galleryTasks) {
Gallery gallery = galleryMapper.selectGalleryByGid(galleryTask.getGid());
if(galleryTask.getProceeding() == gallery.getPages()){
gallery.setStatus("下载完成");
gallery.setProceeding(galleryTask.getProceeding());
galleryMapper.updateGallery(gallery);
}else if(galleryTask.getProceeding() != 0){
gallery.setStatus("下载中");
gallery.setProceeding(galleryTask.getProceeding());
if(!gallery.getName().equals(galleryTask.getName()))
gallery.setName(galleryTask.getName());
galleryMapper.updateGallery(gallery);
log.info(gallery.getName() + "下载进度:" + gallery.getProceeding() + "/" + gallery.getPages());
}
}
}
else if(msg instanceof ResponseMessage rsm)
promiseHashMap.get(rsm.messageId).setSuccess(rsm);
else if(msg instanceof GalleryPageQueryMessage gpqm)
promiseHashMap.get(gpqm.messageId).setSuccess(gpqm);
}
}
}

View File

@ -0,0 +1,124 @@
package com.lion.lionwebsite.Service;
import com.lion.lionwebsite.Dao.GalleryMapper;
import com.lion.lionwebsite.Dao.TagMapper;
import com.lion.lionwebsite.Domain.Gallery;
import com.lion.lionwebsite.Domain.Tag;
import com.lion.lionwebsite.Util.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
@Service
public class TagService {
@Resource
TagMapper tagMapper;
@Resource
GalleryMapper galleryMapper;
public String createTag(String tagStr){
Response response = Response.generateResponse();
//查看tag是否存在
if(tagMapper.selectTagExistByTag(tagStr) != 0){
response.failure("该tag:" + tagStr + "已存在");
}else{
Tag tag = new Tag(0, tagStr, 0);
tagMapper.insertTag(tag);
response.success("创建tag:" + tagStr + "成功");
response.set("tid", String.valueOf(tag.getId()));
}
return response.toJSONString();
}
public String selectAllTag(){
Response response = Response.generateResponse();
ArrayList<Tag> tags = tagMapper.selectAllTag();
HashMap<Integer, Tag> tagHashMap = new HashMap<>();
for (Tag tag : tags) {
tagHashMap.put(tag.getId(), tag);
}
if(tags.isEmpty())
response.failure("当前还没有标签");
else
response.success(new ObjectMapper().valueToTree(tagHashMap).toString());
return response.toJSONString();
}
public String deleteTag(int tagId){
Response response = Response.generateResponse();
if(tagMapper.selectTagUsage(tagId) == 0){
tagMapper.deleteTagById(tagId);
response.success("标签删除成功");
}else{
response.failure("标签删除失败,还有本子引用该标签(如果还显示可以删除,请刷新)");
}
return response.toJSONString();
}
public String markTag(int gid, int tid){
Response response = Response.generateResponse();
Gallery gallery = galleryMapper.selectGalleryByGid(gid);
if(gallery == null)//查看本子是否存在
response.failure("本子不存在,无法标记标签");
else if (tagMapper.selectTagExistById(tid) == 0) //查看标签是否存在
response.failure("标签不存在,无法标记标签");
else
if(tagMapper.selectIsMark(gid, tid) != 0) //查看是否已有该条记录
response.failure("当前本子已有该标签");
else {
if (tagMapper.markTag(gid, tid) == 1) {
response.success("标记本子成功");
tagMapper.incrTagUsage(tid);
}
else
response.failure("未知错误");
}
return response.toJSONString();
}
public String createTagAndMark(int gid, String tagString){
Response response = Response.generateResponse();
Gallery gallery = galleryMapper.selectGalleryByGid(gid);
//查看tag是否存在
if(tagMapper.selectTagExistByTag(tagString) != 0){
response.failure("该tag:" + tagString + "已存在,请刷新再尝试");
}else if(gallery == null) {//查看本子是否存在
response.failure("本子不存在,取消创建标签,请刷新再尝试");
}else{
Tag tag = new Tag(0, tagString, 0);
tagMapper.insertTag(tag);
if (tagMapper.markTag(gid, tag.getId()) == 1) {
response.success("创建标签并标记成功");
response.set("tid", String.valueOf(tag.getId()));
tagMapper.incrTagUsage(tag.getId());
}
}
return response.toJSONString();
}
public String disMarkTag(int gid, int tid){
Response response = Response.generateResponse();
if(tagMapper.disMarkTag(gid, tid) == 0)
response.failure("取消标记失败,可能并没有标记");
else {
response.success("取消标记成功");
tagMapper.decrTagUsage(tid);
}
return response.toJSONString();
}
}

View File

@ -0,0 +1,139 @@
package com.lion.lionwebsite.Service;
import com.lion.lionwebsite.Dao.CollectMapper;
import com.lion.lionwebsite.Dao.GalleryMapper;
import com.lion.lionwebsite.Dao.UserMapper;
import com.lion.lionwebsite.Domain.User;
import com.lion.lionwebsite.Interceptor.TaskHandlerInterceptor;
import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Service
public class UserServiceImpl{
@Resource
UserMapper userMapper;
@Resource
GalleryMapper galleryMapper;
@Resource
CollectMapper collectMapper;
@Resource
TaskHandlerInterceptor taskHandlerInterceptor;
public String addAuthCode(String targetAuthCode, String people) {
Response response = Response.generateResponse();
try {
userMapper.insertUser(new User(-1, targetAuthCode, people, CustomUtil.now(), true));
taskHandlerInterceptor.updateAuthCodes();
response.success("插入成功");
}catch (Exception e){
e.printStackTrace();
response.failure("插入失败");
}
return response.toJSONString();
}
public String alterAuthCode(String targetAuthCode, String newAuthCode) {
Response response = Response.generateResponse();
try{
if(userMapper.isExist(targetAuthCode) != 0) {
userMapper.updateAuthCode(targetAuthCode, newAuthCode);
taskHandlerInterceptor.updateAuthCodes();
response.success("修改成功");
} else {
response.failure("授权码不存在");
}
}catch (Exception e){
e.printStackTrace();
response.failure("修改失败");
}
return response.toJSONString();
}
public String alterUsername(String targetAuthCode, String newUsername){
Response response = Response.generateResponse();
try{
if(userMapper.isExist(targetAuthCode) != 0){
userMapper.updateUsername(targetAuthCode, newUsername);
response.success("修改成功");
}else {
response.failure("授权码不存在");
}
}catch (Exception e){
e.printStackTrace();
response.failure("修改失败");
}
return response.toJSONString();
}
public String deleteAuthCode(String targetAuthCode) {
Response response = Response.generateResponse();
if(userMapper.isExist(targetAuthCode) != 0)
try{
User user = userMapper.selectUserByAuthCode(targetAuthCode);
//修改下载人以及取消收藏
ArrayList<Integer> galleries = collectMapper.selectGidByCollector(user.getId());
for(Integer gallery: galleries)
collectMapper.disCollect(gallery, user.getId());
galleryMapper.updateGalleryDownloader(3, user.getId());
userMapper.deleteUserByAuthCode(targetAuthCode);
response.success("删除成功");
taskHandlerInterceptor.updateAuthCodes();
}catch (Exception e){
e.printStackTrace();
response.failure("删除失败");
}
else
response.failure("授权码不存在");
return response.toJSONString();
}
public String alterStatus(String AuthCode, boolean isEnable){
Response response = Response.generateResponse();
if(userMapper.isExist(AuthCode) == 1){
int id = userMapper.selectUserByAuthCode(AuthCode).getId();
userMapper.updateIsEnableById(id, isEnable);
galleryMapper.updateGalleryDownloader(3, id);
response.success("修改用户状态成功");
taskHandlerInterceptor.updateAuthCodes();
}
else{
response.failure("该用户不存在");
}
return response.toJSONString();
}
public String getAllUser() {
Response response = Response.generateResponse();
User[] users = userMapper.selectAllUser();
response.success(new ObjectMapper().valueToTree(users).toString());
return response.toJSONString();
}
public int getUserId(String AuthCode){
return userMapper.selectUserByAuthCode(AuthCode).getId();
}
}

View File

@ -0,0 +1,122 @@
package com.lion.lionwebsite.Util;
import com.lion.lionwebsite.Domain.MaskDomain;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
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");
private MaskDomain[] maskDomains;
public String restoreUrl(String link){
URL url;
try {
url = new URL(link);
}catch (MalformedURLException e){
return null;
}
for(MaskDomain maskDomain: maskDomains){
if(url.getHost().equals(maskDomain.getMask())){
return link.replace(maskDomain.getMask(), maskDomain.getRaw());
}
else if(url.getHost().equals(maskDomain.getRaw())){
return link;
}
}
return null;
}
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" -> (long) (ONE_KB * num);
case "MB" -> (long) (ONE_MB * num);
case "GB" -> (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 void fourZeroFour(HttpServletResponse response){
try{
response.sendError(404);
}catch (IOException e){
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,124 @@
package com.lion.lionwebsite.Util;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.http.HttpHeaders;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
public class FileDownload {
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);
transmitted += 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();
}
}
}
}

View File

@ -0,0 +1,237 @@
package com.lion.lionwebsite.Util;
import com.lion.lionwebsite.Domain.Gallery;
import com.lion.lionwebsite.Exception.ResolutionNotMatchException;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class GalleryUtil {
static String EX_HENTAI = "https://exhentai.org";
static String E_HENTAI = "https://e-hentai.org";
static String POST = "post";
static String GET = "get";
public static ArrayList<Gallery> galleriesForDownload;
static {
galleriesForDownload = new ArrayList<>();
}
/**
* 解析/下载本子
* @param url 本子链接
* @param isDownload 是否下载 否则仅解析
* @param targetResolution 目标下载分辨率 不下载时改值为空
* @return 解析/下载的本子对象
* @throws IOException io问题
* @throws ResolutionNotMatchException 没有目标分辨率
*/
public static Gallery parse(String url, boolean isDownload, String targetResolution) throws IOException, ResolutionNotMatchException {
String origin = verifyLink(url);
if(origin == null){
return null;
}
//初始化本子
Gallery gallery = new Gallery();
gallery.setLink(url);
gallery.setCreateTime(System.currentTimeMillis()/1000);
gallery.setGid(Integer.parseInt(url.split("/")[4]));
gallery.setProceeding(0);
//访问本子页面
String galleryPage = requests(url, null, GET, null);
Document galleryDoc = Jsoup.parse(galleryPage);
//收集本子基本信息
gallery.setName(galleryDoc.select("#gn").text() + " [" + gallery.getGid() + ']');
gallery.setLanguage(galleryDoc.select("#gdd > table > tbody > tr:nth-child(4) > td.gdt2").text().replace("%nbps", ""));
gallery.setPages(Integer.parseInt(galleryDoc.select("#gdd > table > tbody > tr:nth-child(6) > td.gdt2").text().split(" ")[0]));
gallery.setFileSize(CustomUtil.stringToFileSize(galleryDoc.select("#gdd > table > tbody > tr:nth-child(5) > td.gdt2").text().replace(" ", "")));
gallery.setDisplayFileSize(CustomUtil.fileSizeToString(gallery.getFileSize()));
//查找下载页面链接并初始化参数
String download_link = galleryDoc.select("#gd5 > p:nth-child(2) > a").attr("onclick").split("'")[1];
HashMap<String, String> extraProperties = new HashMap<>();
extraProperties.put("origin", origin);
extraProperties.put("referer", download_link);
//访问下载页面获取分辨率
String downloadPage = requests(download_link, extraProperties, GET, null);
Document downloadDoc = Jsoup.parse(downloadPage);
Elements resolutions = downloadDoc.select("#db > div > table > tbody > tr > td");
download_link = downloadDoc.select("#hathdl_form").attr("action");
Map<String, String> availableResolution = new LinkedHashMap<>();
Map<String, String> tempResolutions = new LinkedHashMap<>();
ArrayList<Integer> tempResolutionKeys = new ArrayList<>();
for(Element element: resolutions){
String[] parameter = element.select("td > p").text().split(" ");
if(parameter[1].contains("N/A"))
continue;
tempResolutions.put(parameter[0], parameter[1] + parameter[2]);
if(parameter[0].contains("x")){
tempResolutionKeys.add(Integer.valueOf(parameter[0].replace("x", "")));
}
}
tempResolutionKeys.sort(Comparator.comparingInt(o -> o));
for(Integer resolution: tempResolutionKeys){
availableResolution.put(resolution.toString()+"x", tempResolutions.get(resolution+"x"));
}
availableResolution.put("Original", tempResolutions.get("Original"));
gallery.setAvailableResolution(availableResolution);
//如果不是下载则直接返回
if(!isDownload){
gallery.setStatus("等待确认下载");
return gallery;
}
//如果目标分辨率不存在抛出错误
if(!gallery.getAvailableResolution().containsKey(targetResolution)){
System.out.println(gallery.getAvailableResolution());
throw new ResolutionNotMatchException(targetResolution);
}
gallery.setResolution(targetResolution);
gallery.setFileSize(CustomUtil.stringToFileSize(availableResolution.get(targetResolution).replace(" ", "")));
gallery.setDisplayFileSize(CustomUtil.fileSizeToString(gallery.getFileSize()));
//提交下载请求
downloadPage = requests(download_link, extraProperties, POST, targetResolution);
downloadDoc = Jsoup.parse(downloadPage);
//判断下载请求是否提交成功
if(downloadDoc.select("#db > p:nth-child(2) > strong").text().startsWith("#"))
gallery.setStatus("已提交");
else {
System.out.println(downloadDoc.select("#db"));
gallery.setStatus("提交失败");
}
return gallery;
}
public static String queryUpdateLink(String link) throws IOException{
String page = requests(link, null, GET, null);
Document document = Jsoup.parse(page);
Elements select = document.select("#gnd");
if(select.size() > 0)
return select.select("a").get(0).attributes().get("href");
return null;
}
/**
* 验证链接
* @param url 链接
* @return origin
*/
public static String verifyLink(String url){
if(url == null)
return null;
if(url.length() < 40)
return null;
if(!url.contains("/g/") || !url.contains("hentai"))
return null;
else if (url.contains(EX_HENTAI))
return EX_HENTAI;
else if (url.contains(E_HENTAI))
return E_HENTAI;
else{
System.out.println(url);
return null;
}
}
/**
* 自用request
* @param url 链接
* @param extraProperties 额外参数
* @param method 请求方法
* @param targetResolution 目标分辨率
* @return 请求页面
* @throws IOException 可能会抛出IO错误
*/
public static String requests(String url, HashMap<String, String> extraProperties, String method, String targetResolution) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse httpResponse;
HashMap<String, String> headers = new HashMap<>();
headers.put("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36");
headers.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
headers.put("Upgrade-Insecure-Requests", "1");
headers.put("Cookie", "ipb_member_id=5774855; ipb_pass_hash=4b061c3abe25289568b5a8e0123fb3b9; igneous=cea2e08fb; sk=oye107wk02gtomb56x65dmv4qzbn; nw=1");
if(extraProperties != null)
headers.putAll(extraProperties);
if(method.equals(GET)){
HttpGet httpGet = new HttpGet(url);
for (Map.Entry<String, String> header: headers.entrySet()){
httpGet.addHeader(header.getKey(), header.getValue());
}
httpResponse = httpClient.execute(httpGet);
} else {
HttpPost httpPost = new HttpPost(url);
for (Map.Entry<String, String> header: headers.entrySet()){
httpPost.addHeader(header.getKey(), header.getValue());
}
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.addTextBody("hathdl_xres",
targetResolution.replace("x", "").replace("Original", "org"));
httpPost.setEntity(multipartEntityBuilder.build());
httpResponse = httpClient.execute(httpPost);
}
HttpEntity responseEntity = httpResponse.getEntity();
int statusCode = httpResponse.getStatusLine().getStatusCode();
StringBuilder stringBuilder = new StringBuilder();
if(statusCode == 200){
BufferedReader reader = new BufferedReader(new InputStreamReader(responseEntity.getContent()));
String str;
while((str = reader.readLine()) != null)
stringBuilder.append(str).append("\n");
}
else{
System.out.println(statusCode);
}
httpClient.close();
httpResponse.close();
return stringBuilder.toString();
}
}

View File

@ -0,0 +1,74 @@
package com.lion.lionwebsite.Util;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
public class Response {
HashMap<String, String> result;
public Response(){
result = new HashMap<>();
}
public static Response generateResponse(){
return new Response();
}
public void set(String key, String value){
result.put(key, value);
}
public String get(String key){
return result.get(key);
}
public void setData(String data){
result.put("data", data);
}
public void setResult(String result){
this.result.put("result", result);
}
public void success(){
setResult("success");
}
public void success(String result){
success();
setData(result);
}
public void failure(){
setResult("failure");
}
public void failure(String result){
failure();
setData(result);
}
public String getResult(){
return result.get("data");
}
public static String _failure(String result){
Response response = generateResponse();
response.failure(result);
return response.toJSONString();
}
public static String _default(){
Response response = Response.generateResponse();
response.failure("参数错误");
return response.toJSONString();
}
public String toJSONString(){
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.valueToTree(result).toString();
}
}

Binary file not shown.

View File

@ -0,0 +1,41 @@
server:
port: 8888
tomcat:
max-swallow-size: 10000MB
spring:
datasource:
driver-class-name: org.sqlite.JDBC
url: jdbc:sqlite:/root/LionWebsite/LionWebsite.db
mvc:
view:
prefix: /resources/templates/
suffix: .html
async:
request-timeout: 180000
servlet:
multipart:
max-file-size: 10000MB
max-request-size: 10000MB
enabled: true
mybatis:
type-aliases-package: com.lion.lionwebsite.Dao
personal-service:
StoragePath: /root/
gallery-manage-service:
target-path: /root/gallery/
cache-size: 100
remote-service:
ip: 5.255.110.45
port: 8080
local-service:
fires: https://api.dler.io/sub?target=clash&new_name=true&url=https%3A%2F%2Ffast.losadhwselfff2332dasd.xyz%2Flink%2Fz0pfwyTvC5naXkbb%3Fclash%3D1&insert=false&config=https%3A%2F%2Fraw.githubusercontent.com%2FACL4SSR%2FACL4SSR%2Fmaster%2FClash%2Fconfig%2FACL4SSR_Online.ini
DouNaiClash: https://aaaa.gay/link/A5CXg2cJATerEEoe?client=clashv2
DouNaiV2ray: https://aaaa.gay/link/A5CXg2cJATerEEoe?client=v2

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,42 @@
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" />
<link href="/reset.css" type="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lion</title>
<script type="module" src="/index/index.js"></script>
<link rel="stylesheet" href="/index/index.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
<title>Vite + Vue</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<script type="module" crossorigin src="/mobile/index.js"></script>
<link rel="stylesheet" href="/mobile/index.css">
</head>
<body>
<div id="app"></div>
</body>
</html>