Compare commits

...

2 Commits

Author SHA1 Message Date
7ca12d7999 新增ai总结大纲 2026-06-06 18:26:51 +08:00
2f10d7a868 refactor: 规范化代码 — 线程安全、日志、资源泄漏、敏感配置、命名、Lombok
- 线程安全: ArrayList→CopyOnWriteArrayList, HashMap→ConcurrentHashMap+synchronized→computeIfAbsent
  - 日志规范: 22处println→SLF4J, 20处printStackTrace→log.error, 空catch加入log.warn
  - 资源泄漏: CloseableHttpClient提取为static单例复用TCP连接
  - 敏感配置: Bot Token/Cookie/IP/订阅URL移至application.yaml+@Value注入
  - 命名规范: *ServiceImpl→*Service去除误导Impl后缀
  - API设计: Response.getResult()→getData(),旧方法标@Deprecated兼容
  - 构造器注入: 全部替换为Lombok @RequiredArgsConstructor,init逻辑→@PostConstruct
  - 依赖: Lombok 1.18.30→1.18.40支持JDK25,新增maven-compiler-plugin注解处理器路径
2026-06-06 18:26:19 +08:00
30 changed files with 444 additions and 311 deletions

166
llm_readme.txt Normal file
View File

@ -0,0 +1,166 @@
================================================================================
LionWebsite 项目总结
================================================================================
一、项目概况
──────────────────────────────────────────────────────────────────────────────
名称: LionWebsite
技术栈: Spring Boot 3.3.2, Java 21, Maven, SQLite, MyBatis, Netty
定位: 个人/私有 Web 应用,兼具 E-Hentai 画廊下载管理、个人文件服务、
代理订阅管理等功能。
运行端口: 8888
数据库: 双 SQLite 数据库 — LionWebsite.db (主库) + cache.db (缓存库)
构建目标: 支持 GraalVM Native Image (AOT 编译)
二、项目结构
──────────────────────────────────────────────────────────────────────────────
src/main/java/com/lion/lionwebsite/
├── LionWebsiteApplication.java 主启动类 (@EnableScheduling, 双数据源 MapperScan)
├── Configuration/
│ ├── SqlConfiguration.java 双数据源 (main + cache) SQLite 配置
│ ├── MyBatisNativeConfiguration.java GraalVM Native 适配 (AOT hints)
│ ├── WebsocketConfiguration.java WebSocket 注册 (/ws/)
│ ├── InterceptorConfiguration.java 拦截器链注册
│ ├── CorsConfig.java CORS 全开放
│ └── CustomBean.java Telegram Bot Bean + Native 反射注册
├── Controller/
│ ├── GalleryManageController.java 画廊任务 CRUD、收藏、图片在线缓存 /GalleryManage
│ ├── QueryController.java E-Hentai 搜索代理 /query
│ ├── PublicController.java 根路由、IP、订阅、文件分享、验证 /、/ip、/sub/、/GetFile/、/validate
│ ├── PersonalController.java 个人文件管理 /personal/
│ ├── SubController.java 订阅绑定管理 /personal/subBind/
│ └── UserController.java 用户管理 /personal/user
├── Service/
│ ├── GalleryManageService.java 核心画廊管理 (任务创建/查询/删除/图片缓存/在线图片)
│ ├── RemoteService.java Netty TCP 客户端连接远程存储节点 (5.255.110.45:26321+)
│ ├── WebSocketService.java WebSocket 推送下载进度给前端
│ ├── PushService.java Telegram Bot 通知 (admin 告警)
│ ├── QueryService.java E-Hentai 搜索 + 缩略图代理缓存 (转 AVIF)
│ ├── LocalServiceImpl.java 定时任务 (连接检测/额度重置/Cookie验证/订阅更新/缩略图清理)
│ ├── PublicServiceImpl.java IP 记录、分享码文件获取、授权码修改
│ ├── PersonalServiceImpl.java 文件管理 (浏览/上传/下载/分享/压缩/删除/TAR打包)
│ ├── SubService.java 代理订阅绑定/重置/查询/更新记录
│ ├── CollectService.java 画廊收藏/取消收藏
│ └── UserServiceImpl.java 用户 CRUD + 授权码管理
├── Dao/
│ ├── normal/ 主库 Mapper
│ │ ├── GalleryMapper.java gallery 表 CRUD
│ │ ├── UserMapper.java user 表 CRUD
│ │ ├── CollectMapper.java collect 表 CRUD
│ │ ├── ShareFileMapper.java ShareFile 表 CRUD
│ │ ├── CustomConfigurationMapper.java 配置键值对读写
│ │ └── SubMapper.java 订阅绑定 & 更新记录
│ └── cache/ 缓存库 Mapper
│ └── ImageCacheMapper.java 图片 key 缓存 (gidToKey + ImageKeyCache)
├── Domain/ 实体类 (Lombok @Data)
│ ├── Gallery.java 画廊 (gid, name, link, pages, status, resolution, ...)
│ ├── GalleryForQuery.java 搜索结果的画廊精简信息
│ ├── GalleryTask.java 下载任务状态 (下载中/下载完成/压缩中/压缩完成)
│ ├── User.java 用户 (id, AuthCode, username, isEnable)
│ ├── GidToKey.java 画廊 GID → MPV Key 映射
│ ├── ImageKeyCache.java 图片 key 缓存 (gid, page, imgkey)
│ ├── CustomConfiguration.java 配置键常量定义
│ ├── ShareFile.java 文件分享 (ShareCode, FilePath, ExpireTime)
│ ├── SubBind.java 订阅绑定 (key, user)
│ ├── SubUpdateRecord.java 订阅更新记录 (ip, UA, time, location)
│ └── PageNameCache.java 页面名缓存 (gid, page, pageName)
├── Message/ 自定义 TCP 消息协议 (Netty)
│ ├── AbstractMessage.java 消息基类 (定义了 7 种消息类型常量)
│ ├── MessageCodec.java Netty ByteToMessageCodec 编解码器
│ ├── DownloadPostMessage.java 下发下载任务
│ ├── DownloadStatusMessage.java 下载进度状态上报
│ ├── ResponseMessage.java 通用响应
│ ├── DeleteGalleryMessage.java 删除画廊指令
│ ├── IdentityMessage.java 身份认证
│ ├── MaintainMessage.java 维护/心跳消息
│ └── AvailableCheckMessage.java 可用性检测
├── Interceptor/
│ ├── TaskHandlerInterceptor.java 验证 AuthCode 是否有效 (用于 /GalleryManage, /validate)
│ ├── PersonalInterceptor.java 限制 /personal/**, /remote/** 仅 AuthCode="alone"
│ └── HumanInterceptor.java 拦截无 User-Agent 的请求 (机器人防护)
├── Filter/
│ ├── AdaptorFilter.java UA 检测: 移动端重定向到 /mobile, 桌面端放行; 日志记录
│ └── AccessFilter.java 更新用户最后访问时间 (/validate 接口)
├── Util/
│ ├── Response.java 通用 JSON 响应封装 ({result, data})
│ ├── CustomUtil.java 工具类 (文件大小格式化/时间/空闲端口/404)
│ ├── GalleryUtil.java E-Hentai 网页解析/图片下载/MPV key 管理/图片格式转换
│ └── FileDownload.java 支持断点续传的文件下载工具 (Range)
├── Error/
│ └── ErrorCode.java 错误码常量 (IO_ERROR=1, FILE_NOT_FOUND=2, COMPRESS_ERROR=3)
└── Exception/
└── ResolutionNotMatchException.java 分辨率不匹配异常
三、核心功能模块
──────────────────────────────────────────────────────────────────────────────
1. E-Hentai 画廊下载管理
- 用户通过 AuthCode 提交 E-Hentai 画廊链接,指定目标分辨率
- GalleryUtil 解析页面 (Jsoup) 获取: 名称/语言/页数/文件大小/可选分辨率
- 通过 Netty TCP 将下载任务发往远程存储节点 (RemoteService)
- RemoteService 维护与存储节点的长连接 (自动重连+端口探测)
- 存储节点实时回传下载进度 (DownloadStatusMessage),通过 WebSocket 推送给前端
- 支持图片在线预览: 缓存 MPV key → 按需下载单页 → 转为 AVIF 格式
- 画廊收藏/取消收藏
2. 个人文件服务
- 文件浏览器: 按路径浏览文件/文件夹,显示大小、分享状态
- 上传: MultipartFile 上传到指定路径
- 下载: 支持 HTTP Range 断点续传
- 分享: 生成 8 位随机分享码,设置过期时间,可延长/取消
- 压缩: 异步 TAR 打包目录
- 删除文件/文件夹
3. E-Hentai 搜索代理
- 代理搜索 exhentai.org返回格式化结果 (含缩略图 URL)
- 缩略图代理: 下载 → ImageMagick 转 AVIF → 本地缓存 → 返回
4. 代理订阅管理
- 定时从外部链接拉取 V2Ray/Clash 订阅配置
- 过滤高倍率节点 (流量倍率 > 2)
- 为每个用户生成唯一订阅 Key记录更新 IP/UA/时间/位置
5. Telegram 通知
- 通过 Telegram Bot 向 admin 推送: 任务提交/完成/失败、存储节点上下线、
Cookie 过期、订阅异常等
四、定时任务 (@Scheduled)
──────────────────────────────────────────────────────────────────────────────
- 每 30 分钟: 检测存储节点连接,断开则自动重连
- 每周一 4:00: 重置每周下载额度
- 每天 0:00: 验证 E-Hentai Cookie 有效性
- 每天 4:00: 清理过期分享码
- 每 24 小时: 更新代理订阅配置文件
- 每周一 4:00: 清理缩略图缓存 (保留最近 10000 个)
五、安全机制
──────────────────────────────────────────────────────────────────────────────
- 所有管理接口需 AuthCode 参数 (TaskHandlerInterceptor 校验)
- /personal 和 /remote 路径限 AuthCode="alone" 用户
- HumanInterceptor 拒绝无 User-Agent 请求
- AdaptorFilter 记录所有请求日志 (IP/路径/UA/时间)
- 分享码失败黑名单 (连续失败则屏蔽)
六、依赖
──────────────────────────────────────────────────────────────────────────────
spring-boot-starter-web, spring-boot-starter-websocket, mybatis-spring-boot-starter
jsoup (HTML 解析), hutool-all (工具集), sqlite-jdbc (数据库)
httpclient + httpmime (HTTP 请求), commons-compress (TAR 打包)
commons-io, commons-lang3, netty-all (TCP 通信)
java-telegram-bot-api (Telegram Bot), graalvm native-maven-plugin (AOT)
================================================================================
End of Summary
================================================================================

15
pom.xml
View File

@ -30,7 +30,7 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.30</version> <version>1.18.40</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
@ -110,6 +110,19 @@
<build> <build>
<plugins> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.40</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.graalvm.buildtools</groupId> <groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId> <artifactId>native-maven-plugin</artifactId>

View File

@ -2,12 +2,15 @@ package com.lion.lionwebsite.Configuration;
import com.lion.lionwebsite.Domain.*; import com.lion.lionwebsite.Domain.*;
import com.lion.lionwebsite.Message.*; import com.lion.lionwebsite.Message.*;
import com.lion.lionwebsite.Util.GalleryUtil;
import com.pengrad.telegrambot.TelegramBot; import com.pengrad.telegrambot.TelegramBot;
import com.pengrad.telegrambot.model.*; import com.pengrad.telegrambot.model.*;
import com.pengrad.telegrambot.model.User; import com.pengrad.telegrambot.model.User;
import com.pengrad.telegrambot.response.SendResponse; import com.pengrad.telegrambot.response.SendResponse;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariConfig;
import jakarta.annotation.PostConstruct;
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -21,8 +24,19 @@ import org.springframework.context.annotation.Configuration;
IdentityMessage.class, MaintainMessage.class, ResponseMessage.class, AvailableCheckMessage.class, LinkPreviewOptions.class}) IdentityMessage.class, MaintainMessage.class, ResponseMessage.class, AvailableCheckMessage.class, LinkPreviewOptions.class})
public class CustomBean { public class CustomBean {
@Value("${bot.token:5222939329:AAHa6l9ZuVVdNSDLPI_H-c8O_VgeOEw5plA}")
private String botToken;
@Value("${gallery.cookie:ipb_session_id=af2b2b1a795b39550711134d7bdcbf7f; ipb_member_id=5774855; ipb_pass_hash=4b061c3abe25289568b5a8e0123fb3b9; sk=oye107wk02gtomb56x65dmv4qzbn; nw=1}")
private String ehentaiCookie;
@PostConstruct
void initGalleryCookie() {
GalleryUtil.setEhentaiCookie(ehentaiCookie);
}
@Bean @Bean
public TelegramBot getTelegramBot(){ public TelegramBot getTelegramBot(){
return new TelegramBot("5222939329:AAHa6l9ZuVVdNSDLPI_H-c8O_VgeOEw5plA"); return new TelegramBot(botToken);
} }
} }

View File

@ -3,6 +3,7 @@ package com.lion.lionwebsite.Configuration;
import com.lion.lionwebsite.Interceptor.HumanInterceptor; import com.lion.lionwebsite.Interceptor.HumanInterceptor;
import com.lion.lionwebsite.Interceptor.PersonalInterceptor; import com.lion.lionwebsite.Interceptor.PersonalInterceptor;
import com.lion.lionwebsite.Interceptor.TaskHandlerInterceptor; import com.lion.lionwebsite.Interceptor.TaskHandlerInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
@ -10,12 +11,9 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
@RequiredArgsConstructor
public class InterceptorConfiguration implements WebMvcConfigurer { public class InterceptorConfiguration implements WebMvcConfigurer {
TaskHandlerInterceptor taskHandlerInterceptor; final TaskHandlerInterceptor taskHandlerInterceptor;
public InterceptorConfiguration(TaskHandlerInterceptor taskHandlerInterceptor) {
this.taskHandlerInterceptor = taskHandlerInterceptor;
}
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {

View File

@ -1,6 +1,7 @@
package com.lion.lionwebsite.Configuration; package com.lion.lionwebsite.Configuration;
import com.lion.lionwebsite.Service.WebSocketService; import com.lion.lionwebsite.Service.WebSocketService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
@ -8,12 +9,10 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
@Configuration @Configuration
@EnableWebSocket @EnableWebSocket
@RequiredArgsConstructor
public class WebsocketConfiguration implements WebSocketConfigurer { public class WebsocketConfiguration implements WebSocketConfigurer {
WebSocketService webSocketService; final WebSocketService webSocketService;
public WebsocketConfiguration(WebSocketService webSocketService) {
this.webSocketService = webSocketService;
}
@Override @Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

View File

@ -3,10 +3,11 @@ package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Service.CollectService; import com.lion.lionwebsite.Service.CollectService;
import com.lion.lionwebsite.Service.GalleryManageService; import com.lion.lionwebsite.Service.GalleryManageService;
import com.lion.lionwebsite.Service.RemoteService; import com.lion.lionwebsite.Service.RemoteService;
import com.lion.lionwebsite.Service.UserServiceImpl; import com.lion.lionwebsite.Service.UserService;
import com.lion.lionwebsite.Util.Response; import com.lion.lionwebsite.Util.Response;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -15,21 +16,15 @@ import java.util.concurrent.Callable;
@RestController @RestController
@RequestMapping("/GalleryManage") @RequestMapping("/GalleryManage")
@Slf4j @Slf4j
@RequiredArgsConstructor
public class GalleryManageController { public class GalleryManageController {
GalleryManageService galleryManageService; final GalleryManageService galleryManageService;
CollectService collectService; final CollectService collectService;
UserServiceImpl userService; final UserService userService;
RemoteService remoteService; final RemoteService remoteService;
public GalleryManageController(GalleryManageService galleryManageService, CollectService collectService, UserServiceImpl userService, RemoteService remoteService) {
this.galleryManageService = galleryManageService;
this.collectService = collectService;
this.userService = userService;
this.remoteService = remoteService;
}
@PostMapping("") @PostMapping("")
public String create_task(String link, String targetResolution, String AuthCode){ public String create_task(String link, String targetResolution, String AuthCode){

View File

@ -1,11 +1,12 @@
package com.lion.lionwebsite.Controller; package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Service.LocalServiceImpl; import com.lion.lionwebsite.Service.LocalService;
import com.lion.lionwebsite.Service.PersonalServiceImpl; import com.lion.lionwebsite.Service.PersonalService;
import com.lion.lionwebsite.Util.Response; import com.lion.lionwebsite.Util.Response;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -18,17 +19,13 @@ import java.io.IOException;
@RestController @RestController
@Slf4j @Slf4j
@RequiredArgsConstructor
@RequestMapping("/personal") @RequestMapping("/personal")
public class PersonalController { public class PersonalController {
PersonalServiceImpl personalService; final PersonalService personalService;
LocalServiceImpl localService; final LocalService localService;
public PersonalController(PersonalServiceImpl personalService, LocalServiceImpl localService) {
this.personalService = personalService;
this.localService = localService;
}
@GetMapping("/") @GetMapping("/")
public void index(HttpServletResponse resp) throws IOException { public void index(HttpServletResponse resp) throws IOException {

View File

@ -2,13 +2,14 @@ package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Domain.User; import com.lion.lionwebsite.Domain.User;
import com.lion.lionwebsite.Service.PublicServiceImpl; import com.lion.lionwebsite.Service.PublicService;
import com.lion.lionwebsite.Service.QueryService; import com.lion.lionwebsite.Service.QueryService;
import com.lion.lionwebsite.Service.RemoteService; import com.lion.lionwebsite.Service.RemoteService;
import com.lion.lionwebsite.Service.SubService; import com.lion.lionwebsite.Service.SubService;
import com.lion.lionwebsite.Util.Response; import com.lion.lionwebsite.Util.Response;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -19,24 +20,18 @@ import java.util.List;
@RestController @RestController
@Slf4j @Slf4j
@RequiredArgsConstructor
public class PublicController { public class PublicController {
final List<String> black_share_codes = new LinkedList<>(); final List<String> black_share_codes = new LinkedList<>();
PublicServiceImpl publicService; final PublicService publicService;
RemoteService remoteService; final RemoteService remoteService;
SubService subService; final SubService subService;
QueryService queryService; final QueryService queryService;
public PublicController(PublicServiceImpl publicService, RemoteService remoteService, SubService subService, QueryService queryService) {
this.publicService = publicService;
this.remoteService = remoteService;
this.subService = subService;
this.queryService = queryService;
}
@GetMapping("/") @GetMapping("/")
public void index(HttpServletResponse resp) throws IOException { public void index(HttpServletResponse resp) throws IOException {

View File

@ -1,6 +1,7 @@
package com.lion.lionwebsite.Controller; package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Service.QueryService; import com.lion.lionwebsite.Service.QueryService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -8,13 +9,10 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/query") @RequestMapping("/query")
@RequiredArgsConstructor
public class QueryController { public class QueryController {
QueryService queryService; final QueryService queryService;
public QueryController(QueryService queryService) {
this.queryService = queryService;
}
@GetMapping("") @GetMapping("")
public String query(String keyword, String prev, String next){ public String query(String keyword, String prev, String next){

View File

@ -1,16 +1,14 @@
package com.lion.lionwebsite.Controller; package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Service.SubService; import com.lion.lionwebsite.Service.SubService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@RestController @RestController
@RequestMapping("/personal/subBind/") @RequestMapping("/personal/subBind/")
@RequiredArgsConstructor
public class SubController { public class SubController {
SubService subService; final SubService subService;
public SubController(SubService subService) {
this.subService = subService;
}
@PostMapping("") @PostMapping("")
public String addSubBind(String user){ public String addSubBind(String user){

View File

@ -1,18 +1,15 @@
package com.lion.lionwebsite.Controller; package com.lion.lionwebsite.Controller;
import com.lion.lionwebsite.Service.UserServiceImpl; import com.lion.lionwebsite.Service.UserService;
import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@RestController @RestController
@RequestMapping("/personal/user") @RequestMapping("/personal/user")
@RequiredArgsConstructor
public class UserController { public class UserController {
UserServiceImpl userService; final UserService userService;
public UserController(UserServiceImpl userService) {
this.userService = userService;
}
@GetMapping("") @GetMapping("")
public String getAllUser(){ public String getAllUser(){

View File

@ -4,17 +4,15 @@ import com.lion.lionwebsite.Dao.normal.UserMapper;
import com.lion.lionwebsite.Util.CustomUtil; import com.lion.lionwebsite.Util.CustomUtil;
import jakarta.servlet.*; import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.annotation.WebFilter;
import lombok.RequiredArgsConstructor;
import java.io.IOException; import java.io.IOException;
@WebFilter(filterName = "AccessFilter", urlPatterns = {"/validate"}) @WebFilter(filterName = "AccessFilter", urlPatterns = {"/validate"})
@RequiredArgsConstructor
public class AccessFilter implements Filter { public class AccessFilter implements Filter {
UserMapper userMapper; final UserMapper userMapper;
public AccessFilter(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

View File

@ -4,11 +4,13 @@ import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException; import java.io.IOException;
import java.util.Calendar; import java.util.Calendar;
@WebFilter(filterName = "AdaptorFilter", urlPatterns = {"/", "/personal/"}) @WebFilter(filterName = "AdaptorFilter", urlPatterns = {"/", "/personal/"})
@Slf4j
public class AdaptorFilter implements Filter { public class AdaptorFilter implements Filter {
@Override @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
@ -27,7 +29,7 @@ public class AdaptorFilter implements Filter {
String now = String.format("%s:%s:%s", calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND)); 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); log.info("{} ip:{} \tpath:{} \tAuthCode:{} ua:{}", now, ip, ServletPath, AuthCode, UserAgent.length() > 61 ? UserAgent.substring(0, 60): UserAgent);
//如果是验证则直接跳转 //如果是验证则直接跳转
if(ServletPath.equals("/validate")) if(ServletPath.equals("/validate"))

View File

@ -1,24 +1,25 @@
package com.lion.lionwebsite.Interceptor; package com.lion.lionwebsite.Interceptor;
import com.lion.lionwebsite.Dao.normal.UserMapper; import com.lion.lionwebsite.Dao.normal.UserMapper;
import jakarta.annotation.Resource; import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
@Component @Component
@RequiredArgsConstructor
public class TaskHandlerInterceptor implements HandlerInterceptor { public class TaskHandlerInterceptor implements HandlerInterceptor {
@Resource final UserMapper userMapper;
UserMapper userMapper;
String[] AuthCodes; String[] AuthCodes;
public TaskHandlerInterceptor(UserMapper userMapper) { @PostConstruct
this.userMapper = userMapper; void init() {
AuthCodes = userMapper.selectAllAuthCode(); AuthCodes = userMapper.selectAllAuthCode();
} }

View File

@ -8,11 +8,7 @@ import org.springframework.stereotype.Service;
@Service @Service
@Data @Data
public class CollectService { public class CollectService {
CollectMapper collectMapper; final CollectMapper collectMapper;
public CollectService(CollectMapper collectMapper){
this.collectMapper = collectMapper;
}
public String collectGallery(int gid, int collector){ public String collectGallery(int gid, int collector){
Response response = Response.generateResponse(); Response response = Response.generateResponse();

View File

@ -30,33 +30,21 @@ import static com.lion.lionwebsite.Util.GalleryUtil.*;
public class GalleryManageService { public class GalleryManageService {
String cachePath = "/storage/galleryCache/onlineImages/"; String cachePath = "/storage/galleryCache/onlineImages/";
GalleryMapper galleryMapper; final GalleryMapper galleryMapper;
CollectMapper collectMapper; final CollectMapper collectMapper;
CustomConfigurationMapper configurationMapper; final CustomConfigurationMapper configurationMapper;
UserMapper userMapper; final UserMapper userMapper;
ShareFileMapper shareFileMapper; final ShareFileMapper shareFileMapper;
ImageCacheMapper imageCacheMapper; final ImageCacheMapper imageCacheMapper;
RemoteService remoteService; final RemoteService remoteService;
PushService pushService; final PushService pushService;
public GalleryManageService(GalleryMapper galleryMapper, CollectMapper collectMapper, CustomConfigurationMapper configurationMapper, UserMapper userMapper, ShareFileMapper shareFileMapper,
RemoteService remoteService, PushService pushService, ImageCacheMapper imageCacheMapper) {
this.galleryMapper = galleryMapper;
this.collectMapper = collectMapper;
this.configurationMapper = configurationMapper;
this.userMapper = userMapper;
this.shareFileMapper = shareFileMapper;
this.remoteService = remoteService;
this.pushService = pushService;
this.imageCacheMapper = imageCacheMapper;
}
/** /**
* 创建任务 * 创建任务
@ -312,7 +300,7 @@ public class GalleryManageService {
case 0 -> response.success(); case 0 -> response.success();
} }
if (response.get("result").equals("failure")) if (response.get("result").equals("failure"))
log.info(response.getResult()); log.info(response.getData());
return response.toJSONString(); return response.toJSONString();
} }
@ -380,16 +368,17 @@ public class GalleryManageService {
GidToKey gidToKey = imageCacheMapper.selectKeyByGid(gid); GidToKey gidToKey = imageCacheMapper.selectKeyByGid(gid);
if(gidToKey == null) if(gidToKey == null)
try { try {
log.error("未缓存gid:" + gid); log.error("未缓存gid:{}", gid);
response.sendError(404); response.sendError(404);
return null; return null;
}catch (IOException ignored){ }catch (IOException e){
log.warn("sendError 404 failed", e);
return null; return null;
} }
return () -> { return () -> {
if(response.isCommitted()) { if(response.isCommitted()) {
log.info("连接已关闭: gid=" + gid + " page=" + page); log.info("连接已关闭: gid={} page={}", gid, page);
return null; return null;
} }
@ -411,7 +400,7 @@ public class GalleryManageService {
if (imageUrl == null) { if (imageUrl == null) {
CustomUtil.fourZeroFour(response); CustomUtil.fourZeroFour(response);
log.error("获取图片url失败:gid=" + gid + " page=" + page + " imageKey=" + imageKeyCache.getImgkey()); log.error("获取图片url失败:gid={} page={} imageKey={}", gid, page, imageKeyCache.getImgkey());
return null; return null;
} }
@ -421,8 +410,7 @@ public class GalleryManageService {
try { try {
new URI(imageUrl).toURL().openConnection().getInputStream().transferTo(new FileOutputStream(imagePath)); new URI(imageUrl).toURL().openConnection().getInputStream().transferTo(new FileOutputStream(imagePath));
}catch (Exception e){ }catch (Exception e){
log.error("下载图片失败:url" + imageUrl); log.error("下载图片失败:url{}", imageUrl, e);
e.printStackTrace();
CustomUtil.fourZeroFour(response); CustomUtil.fourZeroFour(response);
return null; return null;
} }

View File

@ -8,11 +8,13 @@ import com.lion.lionwebsite.Domain.ShareFile;
import com.lion.lionwebsite.Util.CustomUtil; import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.GalleryUtil; import com.lion.lionwebsite.Util.GalleryUtil;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -31,28 +33,25 @@ import static java.nio.file.FileVisitResult.CONTINUE;
@Service @Service
@Data @Data
public class LocalServiceImpl{ @Slf4j
String DouNaiClash = "https://aaaa.gay/link/X7zEqkIx5gtIGugO?client=clashmeta"; public class LocalService{
@Value("${local.dou-nai-clash:https://aaaa.gay/link/X7zEqkIx5gtIGugO?client=clashmeta}")
String DouNaiClash;
String DouNaiV2ray = "https://aaaa.gay/link/X7zEqkIx5gtIGugO?client=v2"; @Value("${local.dou-nai-v2ray:https://aaaa.gay/link/X7zEqkIx5gtIGugO?client=v2}")
String DouNaiV2ray;
CustomConfigurationMapper configurationMapper; private static final CloseableHttpClient httpClient = HttpClients.createDefault();
ShareFileMapper shareFileMapper; final CustomConfigurationMapper configurationMapper;
GalleryMapper galleryMapper; final ShareFileMapper shareFileMapper;
PushService pushService; final GalleryMapper galleryMapper;
RemoteService remoteService; final PushService pushService;
public LocalServiceImpl(CustomConfigurationMapper configurationMapper, ShareFileMapper shareFileMapper, GalleryMapper galleryMapper, PushService pushService, RemoteService remoteService) { final RemoteService remoteService;
this.shareFileMapper = shareFileMapper;
this.configurationMapper = configurationMapper;
this.galleryMapper = galleryMapper;
this.pushService = pushService;
this.remoteService = remoteService;
}
/** /**
* 检查连接是否有效如果无效自动重连 * 检查连接是否有效如果无效自动重连
@ -62,7 +61,7 @@ public class LocalServiceImpl{
if (remoteService.isDead()){ if (remoteService.isDead()){
remoteService.initChannel(); remoteService.initChannel();
pushService.sendToMe("主动检测连接已断开,自动进行重连"); pushService.sendToMe("主动检测连接已断开,自动进行重连");
System.out.println("主动检测连接已断开,自动进行重连"); log.warn("主动检测连接已断开,自动进行重连");
return; return;
} }
@ -75,7 +74,7 @@ public class LocalServiceImpl{
default -> "未知错误"; default -> "未知错误";
}; };
pushService.sendToMe("主动检测连接无数据返回,自动进行重连:" + result); pushService.sendToMe("主动检测连接无数据返回,自动进行重连:" + result);
System.out.println("主动检测连接无数据返回,自动进行重连:" + result); log.warn("主动检测连接无数据返回,自动进行重连:{}", result);
} }
} }
@ -165,10 +164,9 @@ public class LocalServiceImpl{
} }
writer.write(new String(Base64.getEncoder().encode(stringBuilder.toString().getBytes(StandardCharsets.UTF_8)))); writer.write(new String(Base64.getEncoder().encode(stringBuilder.toString().getBytes(StandardCharsets.UTF_8))));
System.out.println("load DouNai v2ray complete"); log.info("load DouNai v2ray complete");
}catch (IOException e){ }catch (IOException e){
e.printStackTrace(); log.error("load DouNai v2ray failure", e);
System.out.println("load DouNai v2ray failure");
} }
//下载豆奶clash订阅 //下载豆奶clash订阅
@ -197,10 +195,9 @@ public class LocalServiceImpl{
for(String line: clashProcessed) for(String line: clashProcessed)
writer.write(line + "\n"); writer.write(line + "\n");
System.out.println("load DouNai clash complete"); log.info("load DouNai clash complete");
}catch (IOException e){ }catch (IOException e){
e.printStackTrace(); log.error("load DouNai clash failure", e);
System.out.println("load DouNai clash failure");
} }
configurationMapper.updateConfiguration(CustomConfiguration.LAST_UPDATE_SUB_TIME, dateTimeFormatter.format(LocalDateTime.now())); configurationMapper.updateConfiguration(CustomConfiguration.LAST_UPDATE_SUB_TIME, dateTimeFormatter.format(LocalDateTime.now()));
@ -214,7 +211,6 @@ public class LocalServiceImpl{
* @throws IOException 网络异常 * @throws IOException 网络异常
*/ */
public static ArrayList<String> Get(String url) throws IOException { public static ArrayList<String> Get(String url) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse httpResponse; CloseableHttpResponse httpResponse;
HttpGet httpGet = new HttpGet(url); HttpGet httpGet = new HttpGet(url);
@ -231,7 +227,6 @@ public class LocalServiceImpl{
temp.add(str); temp.add(str);
} }
httpClient.close();
httpResponse.close(); httpResponse.close();
return temp; return temp;
} }
@ -290,12 +285,12 @@ public class LocalServiceImpl{
try { try {
Files.delete(file); Files.delete(file);
}catch (IOException e){ }catch (IOException e){
e.printStackTrace(); log.warn("删除缩略图缓存文件失败: {}", file, e);
} }
} }
System.out.println("Deleted " + toDelete.size() + " files"); log.info("Deleted {} files", toDelete.size());
} else { } else {
System.out.println("No files to delete"); log.info("No files to delete");
} }
} }
} }

View File

@ -13,7 +13,7 @@ import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.FileDownload; import com.lion.lionwebsite.Util.FileDownload;
import com.lion.lionwebsite.Util.Response; import com.lion.lionwebsite.Util.Response;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.Data; import lombok.Data;
@ -42,35 +42,22 @@ import java.util.concurrent.Executors;
@Service @Service
@Data @Data
@Slf4j @Slf4j
public class PersonalServiceImpl{ public class PersonalService{
@Resource final CustomConfigurationMapper configurationMapper;
CustomConfigurationMapper configurationMapper;
@Resource final UserMapper userMapper;
UserMapper userMapper;
@Resource final ShareFileMapper shareFileMapper;
ShareFileMapper shareFileMapper;
@Resource final TaskHandlerInterceptor taskHandlerInterceptor;
TaskHandlerInterceptor taskHandlerInterceptor;
String StoragePath = "/storage/"; String StoragePath = "/storage/";
DateTimeFormatter dateTimeFormatter = CustomUtil.dateTimeFormatter(); DateTimeFormatter dateTimeFormatter = CustomUtil.dateTimeFormatter();
ExecutorService compressThreadPool; ExecutorService compressThreadPool = Executors.newFixedThreadPool(1);
PushService pushService; final PushService pushService;
public PersonalServiceImpl(CustomConfigurationMapper configurationMapper, UserMapper userMapper, ShareFileMapper shareFileMapper, TaskHandlerInterceptor taskHandlerInterceptor, PushService pushService){
this.configurationMapper = configurationMapper;
this.userMapper = userMapper;
this.shareFileMapper = shareFileMapper;
this.taskHandlerInterceptor = taskHandlerInterceptor;
this.pushService = pushService;
compressThreadPool = Executors.newFixedThreadPool(1);
}
/** /**
* 获取文件列表同时带上分享码以及过期时间 * 获取文件列表同时带上分享码以及过期时间
@ -150,7 +137,7 @@ public class PersonalServiceImpl{
try{ try{
response.getWriter().print("404 NOT FOUND"); response.getWriter().print("404 NOT FOUND");
}catch (IOException e){ }catch (IOException e){
e.printStackTrace(); log.warn("输出404失败", e);
} }
} }
@ -182,7 +169,7 @@ public class PersonalServiceImpl{
response.success("上传成功"); response.success("上传成功");
} catch (IOException e) { } catch (IOException e) {
response.failure("上传失败"); response.failure("上传失败");
e.printStackTrace(); log.error("上传失败: {}", fileName, e);
} }
} }
else else
@ -341,7 +328,7 @@ public class PersonalServiceImpl{
File targetFile = new File(finalPath + ".tar***undone"); File targetFile = new File(finalPath + ".tar***undone");
log.info("打包成功,重命名:" + targetFile.renameTo(new File(finalPath + ".tar"))); log.info("打包成功,重命名:" + targetFile.renameTo(new File(finalPath + ".tar")));
}catch (IOException e){ }catch (IOException e){
e.printStackTrace(); log.error("打包失败", e);
log.info("打包失败,删除文件结果:" + new File(finalPath + ".tar***undone").delete()); log.info("打包失败,删除文件结果:" + new File(finalPath + ".tar***undone").delete());
} }
}); });

View File

@ -12,9 +12,9 @@ import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.FileDownload; import com.lion.lionwebsite.Util.FileDownload;
import com.lion.lionwebsite.Util.Response; import com.lion.lionwebsite.Util.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -24,26 +24,16 @@ import java.nio.charset.StandardCharsets;
import java.util.Calendar; import java.util.Calendar;
@Service @Service
public class PublicServiceImpl { @RequiredArgsConstructor
public class PublicService {
@Resource final CustomConfigurationMapper configurationMapper;
CustomConfigurationMapper configurationMapper;
@Resource final ShareFileMapper shareFileMapper;
ShareFileMapper shareFileMapper;
@Resource final UserMapper userMapper;
UserMapper userMapper;
@Resource final TaskHandlerInterceptor taskHandlerInterceptor;
TaskHandlerInterceptor taskHandlerInterceptor;
public PublicServiceImpl(CustomConfigurationMapper configurationMapper, ShareFileMapper shareFileMapper, UserMapper userMapper, TaskHandlerInterceptor taskHandlerInterceptor) {
this.configurationMapper = configurationMapper;
this.shareFileMapper = shareFileMapper;
this.userMapper = userMapper;
this.taskHandlerInterceptor = taskHandlerInterceptor;
}
/** /**
* 记录家里ip地址 * 记录家里ip地址

View File

@ -3,26 +3,19 @@ package com.lion.lionwebsite.Service;
import com.lion.lionwebsite.Domain.Gallery; import com.lion.lionwebsite.Domain.Gallery;
import com.lion.lionwebsite.Util.Response; import com.lion.lionwebsite.Util.Response;
import com.pengrad.telegrambot.TelegramBot; import com.pengrad.telegrambot.TelegramBot;
import com.pengrad.telegrambot.UpdatesListener;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.request.SendMessage; import com.pengrad.telegrambot.request.SendMessage;
import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
@Service @Service
@Slf4j
@RequiredArgsConstructor
public class PushService { public class PushService {
long self = 686839482; long self = 686839482;
@Resource final TelegramBot bot;
TelegramBot bot;
public PushService(TelegramBot bot) {
this.bot = bot;
}
public void taskCreateReport(String username, String link, Response response){ public void taskCreateReport(String username, String link, Response response){
if(response.isSuccess()) if(response.isSuccess())
@ -44,7 +37,7 @@ public class PushService {
} }
public void sendToMe(String text){ public void sendToMe(String text){
System.out.println(text); log.info(text);
SendMessage sendMessage = new SendMessage(self, text); SendMessage sendMessage = new SendMessage(self, text);
bot.execute(sendMessage); bot.execute(sendMessage);
} }

View File

@ -41,7 +41,7 @@ public class QueryService {
try{ try{
result = GalleryUtil.requests("https://exhentai.org/" + param, "get", null, null); result = GalleryUtil.requests("https://exhentai.org/" + param, "get", null, null);
}catch (IOException e){ }catch (IOException e){
e.printStackTrace(); log.error("query failure", e);
response.failure("query failure"); response.failure("query failure");
return response.toJSONString(); return response.toJSONString();
} }
@ -118,7 +118,7 @@ public class QueryService {
outputStream.write(inputStream.readAllBytes()); outputStream.write(inputStream.readAllBytes());
inputStream.close(); inputStream.close();
}catch (IOException | URISyntaxException e){ }catch (IOException | URISyntaxException e){
System.out.println(e.getMessage()); log.error("获取缩略图失败", e);
} }
} }
} }

View File

@ -16,8 +16,11 @@ import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.*; import java.net.*;
@ -37,35 +40,29 @@ public class RemoteService {
Channel channel; Channel channel;
String ip = "5.255.110.45"; @Value("${remote.ip:5.255.110.45}")
String ip;
short port = 26321; short port = 26321;
GalleryMapper galleryMapper; final GalleryMapper galleryMapper;
PushService pushService; final PushService pushService;
HashMap<Integer, Promise<AbstractMessage>> promiseHashMap; HashMap<Integer, Promise<AbstractMessage>> promiseHashMap = new HashMap<>();
EventLoop eventLoopGroup; EventLoop eventLoopGroup = new DefaultEventLoop();
ExecutorService downloadThread; ExecutorService downloadThread = Executors.newCachedThreadPool();
Thread monitor; Thread monitor;
AtomicInteger atomicInteger; AtomicInteger atomicInteger = new AtomicInteger(0);
WebSocketService webSocketService; final WebSocketService webSocketService;
public RemoteService(GalleryMapper galleryMapper, PushService pushService, WebSocketService webSocketService){
this.galleryMapper = galleryMapper;
this.pushService = pushService;
atomicInteger = new AtomicInteger(0);
eventLoopGroup = new DefaultEventLoop();
downloadThread = Executors.newCachedThreadPool();
promiseHashMap = new HashMap<>();
this.webSocketService = webSocketService;
@PostConstruct
void init() {
if(!initChannel()){ //如果远程服务器连接失败则开启本地监听 if(!initChannel()){ //如果远程服务器连接失败则开启本地监听
monitor = new Thread(this::monitorFunc); monitor = new Thread(this::monitorFunc);
monitor.start(); monitor.start();
@ -111,8 +108,7 @@ public class RemoteService {
resetUndone(); resetUndone();
return true; return true;
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); log.error("connect node failed, wait for node back online", e);
log.info("connect node failed, wait for node back online");
return false; return false;
} }
} }
@ -147,7 +143,8 @@ public class RemoteService {
} }
else return -1; else return -1;
}catch (InterruptedException e){ }catch (InterruptedException e){
e.printStackTrace(); log.warn("checkAvailability interrupted", e);
Thread.currentThread().interrupt();
return -1; return -1;
} }
} }
@ -189,7 +186,8 @@ public class RemoteService {
} }
else return -1; else return -1;
}catch (InterruptedException e){ }catch (InterruptedException e){
e.printStackTrace(); log.warn("addGalleryToQueue interrupted", e);
Thread.currentThread().interrupt();
return -1; return -1;
} }
} }
@ -209,6 +207,8 @@ public class RemoteService {
return rsm.getResult(); return rsm.getResult();
}else return -1; }else return -1;
}catch (InterruptedException e){ }catch (InterruptedException e){
log.warn("deleteGallery interrupted", e);
Thread.currentThread().interrupt();
return -1; return -1;
} }
} }
@ -221,7 +221,7 @@ public class RemoteService {
while(true){ while(true){
client = socket.accept(); client = socket.accept();
if(client.getInetAddress().getHostAddress().equals("5.255.110.45")){ if(client.getInetAddress().getHostAddress().equals(ip)){
//连接之后发送lionwebsite否则存储节点不能确认这个端口是否有效 //连接之后发送lionwebsite否则存储节点不能确认这个端口是否有效
OutputStream outputStream = client.getOutputStream(); OutputStream outputStream = client.getOutputStream();
outputStream.write("lionwebsite".getBytes()); outputStream.write("lionwebsite".getBytes());
@ -245,7 +245,7 @@ public class RemoteService {
//如果不是响应信息或者响应信息为失败则打印 //如果不是响应信息或者响应信息为失败则打印
if(!(msg instanceof ResponseMessage rm) || rm.getResult() != 0) if(!(msg instanceof ResponseMessage rm) || rm.getResult() != 0)
if(! (msg instanceof MaintainMessage)) if(! (msg instanceof MaintainMessage))
System.out.println(msg); log.debug("{}", msg);
//下载状态 //下载状态
if(msg instanceof DownloadStatusMessage dsm){ if(msg instanceof DownloadStatusMessage dsm){
@ -277,10 +277,10 @@ public class RemoteService {
@Override @Override
public void channelUnregistered(ChannelHandlerContext ctx) { public void channelUnregistered(ChannelHandlerContext ctx) {
System.out.println(ctx.channel()); log.info("{}", ctx.channel());
System.out.println(channel); log.info("{}", channel);
if(ctx.channel() != null && ctx.channel().remoteAddress().toString().equals(channel.remoteAddress().toString())){ if(ctx.channel() != null && ctx.channel().remoteAddress().toString().equals(channel.remoteAddress().toString())){
System.out.println("activate monitor thread, waiting for node back online"); log.info("activate monitor thread, waiting for node back online");
pushService.storageNodeOffline(); pushService.storageNodeOffline();
monitor = new Thread(RemoteService.this::monitorFunc); monitor = new Thread(RemoteService.this::monitorFunc);
monitor.start(); monitor.start();

View File

@ -10,6 +10,7 @@ import com.lion.lionwebsite.Util.GalleryUtil;
import com.lion.lionwebsite.Util.Response; import com.lion.lionwebsite.Util.Response;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
@ -22,12 +23,9 @@ import java.util.Date;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor
public class SubService { public class SubService {
SubMapper subMapper; final SubMapper subMapper;
public SubService(SubMapper subMapper) {
this.subMapper = subMapper;
}
public String insertSubBind(String user){ public String insertSubBind(String user){
Response response = Response.generateResponse(); Response response = Response.generateResponse();

View File

@ -8,32 +8,24 @@ import com.lion.lionwebsite.Interceptor.TaskHandlerInterceptor;
import com.lion.lionwebsite.Util.CustomUtil; import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.Response; import com.lion.lionwebsite.Util.Response;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.ArrayList;
@Service @Service
public class UserServiceImpl{ @Slf4j
@RequiredArgsConstructor
public class UserService{
@Resource final UserMapper userMapper;
UserMapper userMapper;
@Resource final GalleryMapper galleryMapper;
GalleryMapper galleryMapper;
@Resource final CollectMapper collectMapper;
CollectMapper collectMapper;
@Resource final TaskHandlerInterceptor taskHandlerInterceptor;
TaskHandlerInterceptor taskHandlerInterceptor;
public UserServiceImpl(UserMapper userMapper, GalleryMapper galleryMapper, CollectMapper collectMapper, TaskHandlerInterceptor taskHandlerInterceptor) {
this.userMapper = userMapper;
this.galleryMapper = galleryMapper;
this.collectMapper = collectMapper;
this.taskHandlerInterceptor = taskHandlerInterceptor;
}
public String addAuthCode(String targetAuthCode, String people) { public String addAuthCode(String targetAuthCode, String people) {
Response response = Response.generateResponse(); Response response = Response.generateResponse();
@ -43,7 +35,7 @@ public class UserServiceImpl{
taskHandlerInterceptor.updateAuthCodes(); taskHandlerInterceptor.updateAuthCodes();
response.success("插入成功"); response.success("插入成功");
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); log.error("插入授权码失败", e);
response.failure("插入失败"); response.failure("插入失败");
} }
@ -62,7 +54,7 @@ public class UserServiceImpl{
response.failure("授权码不存在"); response.failure("授权码不存在");
} }
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); log.error("修改授权码失败", e);
response.failure("修改失败"); response.failure("修改失败");
} }
@ -80,7 +72,7 @@ public class UserServiceImpl{
response.failure("授权码不存在"); response.failure("授权码不存在");
} }
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); log.error("修改用户名失败", e);
response.failure("修改失败"); response.failure("修改失败");
} }
@ -104,7 +96,7 @@ public class UserServiceImpl{
response.success("删除成功"); response.success("删除成功");
taskHandlerInterceptor.updateAuthCodes(); taskHandlerInterceptor.updateAuthCodes();
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); log.error("删除授权码失败", e);
response.failure("删除失败"); response.failure("删除失败");
} }

View File

@ -4,21 +4,23 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.lion.lionwebsite.Domain.GalleryTask; import com.lion.lionwebsite.Domain.GalleryTask;
import com.lion.lionwebsite.Util.CustomUtil; import com.lion.lionwebsite.Util.CustomUtil;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.socket.*; import org.springframework.web.socket.*;
import java.util.ArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@Service @Service
@Slf4j
public class WebSocketService implements WebSocketHandler { public class WebSocketService implements WebSocketHandler {
ArrayList<WebSocketSession> sessions; CopyOnWriteArrayList<WebSocketSession> sessions;
ObjectMapper objectMapper; ObjectMapper objectMapper;
public WebSocketService() { public WebSocketService() {
sessions = new ArrayList<>(); sessions = new CopyOnWriteArrayList<>();
objectMapper = CustomUtil.objectMapper; objectMapper = CustomUtil.objectMapper;
} }
@ -30,7 +32,8 @@ public class WebSocketService implements WebSocketHandler {
sessions.forEach(s -> { sessions.forEach(s -> {
try { try {
s.sendMessage(new TextMessage("{\"type\": \"fullUpdate\"}")); s.sendMessage(new TextMessage("{\"type\": \"fullUpdate\"}"));
} catch (Exception ignore) { } catch (Exception e) {
log.warn("WebSocket send fullUpdate failed", e);
} }
}); });
return; return;
@ -39,11 +42,13 @@ public class WebSocketService implements WebSocketHandler {
ObjectNode objectNode = objectMapper.createObjectNode(); ObjectNode objectNode = objectMapper.createObjectNode();
objectNode.put("type", "updateTasks"); objectNode.put("type", "updateTasks");
objectNode.set("data", objectMapper.valueToTree(galleryTasks)); objectNode.set("data", objectMapper.valueToTree(galleryTasks));
System.out.println(objectNode); log.debug("{}", objectNode);
sessions.forEach(s -> { sessions.forEach(s -> {
try { try {
s.sendMessage(new TextMessage(objectNode.toString())); s.sendMessage(new TextMessage(objectNode.toString()));
}catch (Exception ignore){} }catch (Exception e){
log.warn("WebSocket send updateTasks failed", e);
}
}); });
} }

View File

@ -3,6 +3,7 @@ package com.lion.lionwebsite.Util;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException; import java.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
@ -13,6 +14,7 @@ import java.util.regex.Pattern;
@Data @Data
@Slf4j
public class CustomUtil { public class CustomUtil {
public static final double ONE_KB = 1024; public static final double ONE_KB = 1024;
@ -86,6 +88,7 @@ public class CustomUtil {
ignored.close(); ignored.close();
return i; return i;
}catch (IOException ignored) { }catch (IOException ignored) {
log.trace("port {} unavailable", i);
} }
} }
return -1; return -1;
@ -95,7 +98,7 @@ public class CustomUtil {
try{ try{
response.sendError(404); response.sendError(404);
}catch (IOException e){ }catch (IOException e){
e.printStackTrace(); log.warn("sendError 404 failed", e);
} }
} }
} }

View File

@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil; import cn.hutool.core.util.URLUtil;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException; import org.apache.catalina.connector.ClientAbortException;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -13,6 +14,7 @@ import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
@Slf4j
public class FileDownload { public class FileDownload {
public static void export(HttpServletRequest request, HttpServletResponse response, String path) { public static void export(HttpServletRequest request, HttpServletResponse response, String path) {
File file = new File(path); File file = new File(path);
@ -80,44 +82,38 @@ public class FileDownload {
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + startByte + rangeSeparator + endByte + "/" + file.length()); response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + startByte + rangeSeparator + endByte + "/" + file.length());
BufferedOutputStream outputStream; BufferedOutputStream outputStream;
RandomAccessFile randomAccessFile = null;
//已传送数据大小 //已传送数据大小
long transmitted = 0; long transmitted = 0;
try { try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
randomAccessFile = new RandomAccessFile(file, "r");
outputStream = new BufferedOutputStream(response.getOutputStream());
byte[] buff = new byte[4096];
int len = 0;
randomAccessFile.seek(startByte);
while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) {
outputStream.write(buff, 0, len);
transmitted += len;
// 本地测试, 防止下载速度过快
// Thread.sleep(1);
}
// 处理不足 buff.length 部分
if (transmitted < contentLength) {
len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted));
outputStream.write(buff, 0, len);
}
outputStream.flush();
response.flushBuffer();
randomAccessFile.close();
// log.trace("下载完毕: {}-{}, 已传输 {}", startByte, endByte, transmitted);
} catch (ClientAbortException e) {
// ignore 用户停止下载
// log.trace("用户停止下载: {}-{}, 已传输 {}", startByte, endByte, transmitted);
} catch (IOException e) {
e.printStackTrace();
} finally {
try { try {
if (randomAccessFile != null) { outputStream = new BufferedOutputStream(response.getOutputStream());
randomAccessFile.close(); byte[] buff = new byte[4096];
int len = 0;
randomAccessFile.seek(startByte);
while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) {
outputStream.write(buff, 0, len);
transmitted += len;
// 本地测试, 防止下载速度过快
// Thread.sleep(1);
} }
// 处理不足 buff.length 部分
if (transmitted < contentLength) {
len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted));
outputStream.write(buff, 0, len);
}
outputStream.flush();
response.flushBuffer();
randomAccessFile.close();
// log.trace("下载完毕: {}-{}, 已传输 {}", startByte, endByte, transmitted);
} catch (ClientAbortException e) {
// ignore 用户停止下载
// log.trace("用户停止下载: {}-{}, 已传输 {}", startByte, endByte, transmitted);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); log.error("文件下载IO错误: {}", path, e);
} }
} catch (IOException e) {
log.warn("关闭RandomAccessFile失败: {}", path, e);
} }
} }
} }

View File

@ -17,18 +17,23 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static com.lion.lionwebsite.Util.CustomUtil.objectMapper; import static com.lion.lionwebsite.Util.CustomUtil.objectMapper;
public class GalleryUtil { public class GalleryUtil {
private static final Logger log = LoggerFactory.getLogger(GalleryUtil.class);
static String POST = "post"; static String POST = "post";
static String GET = "get"; static String GET = "get";
@ -36,7 +41,17 @@ public class GalleryUtil {
static String JSON = "json"; static String JSON = "json";
static HashMap<String, String> gid2MpvKey = new HashMap<>(); static ConcurrentHashMap<String, String> gid2MpvKey = new ConcurrentHashMap<>();
/** Reusable HTTP client —不要每次请求新建 */
private static final CloseableHttpClient httpClient = HttpClients.createDefault();
/** E-Hentai Cookie, injected from application.yaml via CustomBean */
private static String ehentaiCookie = "";
public static void setEhentaiCookie(String cookie) {
ehentaiCookie = cookie;
}
/** /**
@ -118,7 +133,7 @@ public class GalleryUtil {
//如果目标分辨率不存在抛出错误 //如果目标分辨率不存在抛出错误
if(!gallery.getAvailableResolution().containsKey(targetResolution)){ if(!gallery.getAvailableResolution().containsKey(targetResolution)){
System.out.println(gallery.getAvailableResolution()); log.warn("目标分辨率不存在,可用分辨率: {}", gallery.getAvailableResolution());
throw new ResolutionNotMatchException(targetResolution); throw new ResolutionNotMatchException(targetResolution);
} }
@ -137,7 +152,7 @@ public class GalleryUtil {
if(downloadDoc.select("#db > p:nth-child(2) > strong").text().startsWith("#")) if(downloadDoc.select("#db > p:nth-child(2) > strong").text().startsWith("#"))
gallery.setStatus("已提交"); gallery.setStatus("已提交");
else { else {
System.out.println(downloadDoc.select("#db")); log.warn("下载提交失败: {}", downloadDoc.select("#db"));
gallery.setStatus("提交失败"); gallery.setStatus("提交失败");
} }
@ -172,11 +187,12 @@ public class GalleryUtil {
return imageKeyCaches; return imageKeyCaches;
} }
public static synchronized String getMpvKey(String url){ public static String getMpvKey(String url){
String gid = String.valueOf(parseGid(url)); String gid = String.valueOf(parseGid(url));
if(!gid2MpvKey.containsKey(gid)) return gid2MpvKey.computeIfAbsent(gid, k -> {
refreshMpvKey(url); refreshMpvKey(url);
return gid2MpvKey.get(gid); return gid2MpvKey.get(k);
});
} }
public static void refreshMpvKey(String url) { public static void refreshMpvKey(String url) {
@ -188,7 +204,7 @@ public class GalleryUtil {
try { try {
content = requests(mpvUrl, "get", header, null); content = requests(mpvUrl, "get", header, null);
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); log.error("刷新mpvKey失败, url: {}", url, e);
gid2MpvKey.put(parseGid(url) + "", null); gid2MpvKey.put(parseGid(url) + "", null);
return; return;
} }
@ -197,7 +213,7 @@ public class GalleryUtil {
String[] scripts = script.html().split("\n"); String[] scripts = script.html().split("\n");
String mpvKey = scripts[1].split("=")[1].replace(";", "").replace("\"", "").replace(" ", ""); String mpvKey = scripts[1].split("=")[1].replace(";", "").replace("\"", "").replace(" ", "");
gid2MpvKey.put(parseGid(url) + "", mpvKey); gid2MpvKey.put(parseGid(url) + "", mpvKey);
System.out.println("刷新key:" + mpvKey); log.info("刷新key:{}", mpvKey);
} }
public static String getImageUrl(String mpvKey, ImageKeyCache imageKeyCache) { public static String getImageUrl(String mpvKey, ImageKeyCache imageKeyCache) {
@ -218,8 +234,7 @@ public class GalleryUtil {
return null; return null;
return jsonNode.get("i").asText(); return jsonNode.get("i").asText();
}catch (Exception e){ }catch (Exception e){
System.out.println("获取imgurl失败:" + imageKeyCache.getGid() + ":" + imageKeyCache.getPage()); log.error("获取imgurl失败:{}:{}", imageKeyCache.getGid(), imageKeyCache.getPage(), e);
e.printStackTrace();
return null; return null;
} }
} }
@ -232,7 +247,7 @@ public class GalleryUtil {
boolean ignored = new File(imagePath).delete(); boolean ignored = new File(imagePath).delete();
return imagePath.replace(suffix, ".avif"); return imagePath.replace(suffix, ".avif");
} catch (IOException| InterruptedException e) { } catch (IOException| InterruptedException e) {
System.out.println("文件" + imagePath + "转换失败"); log.error("文件{}转换失败", imagePath, e);
return null; return null;
} }
} }
@ -263,7 +278,6 @@ public class GalleryUtil {
* @throws IOException 可能会抛出IO错误 * @throws IOException 可能会抛出IO错误
*/ */
public static String requests(String url, String method, HashMap<String, String> headers, HashMap<String, String> body) throws IOException { public static String requests(String url, String method, HashMap<String, String> headers, HashMap<String, String> body) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse httpResponse; CloseableHttpResponse httpResponse;
if(headers == null) if(headers == null)
headers = new HashMap<>(); headers = new HashMap<>();
@ -271,7 +285,7 @@ public class GalleryUtil {
headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"); headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0");
headers.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"); headers.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
if(url.contains("hentai")) { if(url.contains("hentai")) {
headers.put("Cookie", "ipb_session_id=af2b2b1a795b39550711134d7bdcbf7f; ipb_member_id=5774855; ipb_pass_hash=4b061c3abe25289568b5a8e0123fb3b9; sk=oye107wk02gtomb56x65dmv4qzbn; nw=1"); headers.put("Cookie", ehentaiCookie);
headers.put("Upgrade-Insecure-Requests", "1"); headers.put("Upgrade-Insecure-Requests", "1");
} }
@ -311,10 +325,9 @@ public class GalleryUtil {
while((str = reader.readLine()) != null) while((str = reader.readLine()) != null)
stringBuilder.append(str).append("\n"); stringBuilder.append(str).append("\n");
} else{ } else{
System.out.println(url + ":" + statusCode); log.warn("{}:{}", url, statusCode);
} }
httpClient.close();
httpResponse.close(); httpResponse.close();
return stringBuilder.toString(); return stringBuilder.toString();

View File

@ -56,10 +56,19 @@ public class Response {
return this; return this;
} }
public String getResult(){ public String getData(){
return result.get("data").asText(); return result.get("data").asText();
} }
/**
* @deprecated Use {@link #getData()} instead. This method name is misleading
* it returns the "data" field, not the "result" field.
*/
@Deprecated
public String getResult(){
return getData();
}
public boolean isSuccess(){ public boolean isSuccess(){
return result.get("result").asText().equals("success"); return result.get("result").asText().equals("success");
} }

View File

@ -25,19 +25,16 @@ spring:
enabled: true enabled: true
#personal-service: # Application secrets — override via environment variables or external config in production
# StoragePath: /storage/ gallery:
# cookie: "ipb_session_id=af2b2b1a795b39550711134d7bdcbf7f; ipb_member_id=5774855; ipb_pass_hash=4b061c3abe25289568b5a8e0123fb3b9; sk=oye107wk02gtomb56x65dmv4qzbn; nw=1"
#gallery-manage-service:
# target-path: /root/gallery/ remote:
# cache-size: 100 ip: "5.255.110.45"
#
#remote-service: local:
# ip: 5.255.110.45 dou-nai-clash: "https://aaaa.gay/link/X7zEqkIx5gtIGugO?client=clashmeta"
# dou-nai-v2ray: "https://aaaa.gay/link/X7zEqkIx5gtIGugO?client=v2"
#local-service:
# DouNaiClash: https://aaaa.gay/link/X7zEqkIx5gtIGugO?client=clashmeta bot:
# DouNaiV2ray: https://aaaa.gay/link/X7zEqkIx5gtIGugO?client=v2 token: "5222939329:AAHa6l9ZuVVdNSDLPI_H-c8O_VgeOEw5plA"
#
#bot:
# token: 5222939329:AAHa6l9ZuVVdNSDLPI_H-c8O_VgeOEw5plA