- 8888 下载服务:加读超时、接受循环单次失败不退出;畸形请求行回 400 且不泄漏连接; Range 越界按文件末端夹取或回 416,非法 Range 不再中断处理 - 删除任务改为按根目录规范化校验,拦截 ../ 与绝对路径越界删除 - MessageCodec:先序列化再写帧头(不再产生半帧),并限制单帧长度上限 - 节点绑定端口等待结果,失败即抛出而非静默不监听 - 配置加载支持指定路径并对缺文件记录告警;完成通知对消息做 URL 编码并加超时 - 测试 28 → 76:新增协议编解码、节点消息处理与上报循环、备机订阅分发、 gid 匹配、删除边界、配置加载等用例
183 lines
8.8 KiB
Plaintext
183 lines
8.8 KiB
Plaintext
# storageNode 项目结构
|
||
|
||
## 概述
|
||
分布式存储/下载节点服务端,用于画廊网站。职责包括:从远程源下载图片集、压缩为 ZIP 归档、通过 HTTP 提供下载(支持断点续传)、管理代理订阅配置(V2Ray/Clash),并通过 Netty 自定义 TCP 协议与中心服务器通信。
|
||
|
||
- **Group ID:** `org.lion`
|
||
- **Version:** `1.0`
|
||
- **Java 版本:** 21 字节码(生产使用 GraalVM JDK 25)
|
||
- **构建工具:** Maven(单模块)
|
||
- **编译目标:** 当前以 JVM/JAR + `lib/` 运行;保留 GraalVM 原生配置但尚未完成 JDK 25 原生验证
|
||
|
||
---
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
storageNode/
|
||
├── pom.xml # Maven 构建配置
|
||
└── src/
|
||
├── main/
|
||
│ ├── java/
|
||
│ │ └── lion/
|
||
│ │ ├── Main.java # 程序入口
|
||
│ │ ├── storageNode.java # Netty TCP 核心节点
|
||
│ │ ├── CustomUtil.java # 工具方法(HTTP 通知、端口查找、ObjectMapper)
|
||
│ │ ├── MultiThreadedHTTPServer.java # HTTP 文件下载服务(端口 8888)
|
||
│ │ ├── Config/
|
||
│ │ │ └── Config.java # 加载 config.properties
|
||
│ │ ├── Domain/
|
||
│ │ │ └── GalleryTask.java # 下载任务领域模型
|
||
│ │ ├── ErrorCode/
|
||
│ │ │ └── ErrorCode.java # 错误码常量
|
||
│ │ ├── Externel/
|
||
│ │ │ └── BackupSubServer.java # 代理订阅文件服务(端口 8889)
|
||
│ │ ├── Message/
|
||
│ │ │ ├── AbstractMessage.java # 消息基类 + 消息类型常量
|
||
│ │ │ ├── MessageCodec.java # Netty 编解码器(ByteBuf ↔ AbstractMessage,JSON 格式)
|
||
│ │ │ └── Main/
|
||
│ │ │ ├── AvailableCheckMessage.java # 可用性检查(type=8)
|
||
│ │ │ ├── DeleteGalleryMessage.java # 删除画廊请求(type=3)
|
||
│ │ │ ├── DownloadPostMessage.java # 提交下载任务(type=1)
|
||
│ │ │ ├── DownloadStatusMessage.java # 上报任务状态(type=2)
|
||
│ │ │ ├── IdentityMessage.java # 身份认证握手(type=6)
|
||
│ │ │ ├── MaintainMessage.java # 心跳维持(type=7)
|
||
│ │ │ └── ResponseMessage.java # 通用响应(type=0)
|
||
│ │ └── Service/
|
||
│ │ ├── DeleteService.java # 删除画廊目录
|
||
│ │ ├── DownloadCheckService.java # 下载监控与压缩服务
|
||
│ │ ├── PrimaryChannelTracker.java # 主站通道引用登记与自愈
|
||
│ │ └── RekickPolicy.java # 主动重新唤起主站的限流判定
|
||
│ └── resources/
|
||
│ ├── config.properties # DouNai 订阅地址配置
|
||
│ ├── simplelogger.properties # SLF4J 日志配置(输出到 run.out)
|
||
│ └── reflect-config.json # GraalVM 反射配置(Jackson 序列化)
|
||
└── test/
|
||
└── java/ # 协议编解码、节点消息处理与上报循环、
|
||
# 订阅快照与分发、下载/压缩恢复、
|
||
# 主站通道自愈、删除与工具方法测试
|
||
```
|
||
|
||
---
|
||
|
||
## 关键依赖
|
||
|
||
| 依赖 | 版本 | 用途 |
|
||
|---|---|---|
|
||
| `io.netty:netty-all` | 4.1.138.Final | TCP 服务端/客户端 |
|
||
| `com.fasterxml.jackson.core:jackson-databind` | 2.22.2 | JSON 序列化 |
|
||
| `org.projectlombok:lombok` | 1.18.48 | 简化样板代码(`@Data`, `@Slf4j`) |
|
||
| `ch.qos.logback:logback-classic` | 1.5.38 | 日志实现 |
|
||
| `cn.hutool:hutool-all` | 5.8.47 | 文件操作、ZIP 压缩、HTTP 请求 |
|
||
| `org.apache.commons:commons-compress` | 1.28.0 | 压缩归档 |
|
||
| `org.graalvm.buildtools:native-maven-plugin` | 1.1.8 | 原生镜像配置(待验证) |
|
||
|
||
---
|
||
|
||
## 启动流程
|
||
|
||
1. `Main.main()` → 调用 `boot()`(遗留的 Netty Bootstrap),然后执行 `Config.loadConfig()`
|
||
2. 在新线程中启动 `BackupSubServer`(端口 8889)— 提供代理订阅文件下载
|
||
3. 在新线程中启动 `MultiThreadedHTTPServer`(端口 8888)— 提供画廊 ZIP 文件下载
|
||
4. 主线程创建 `storageNode()` 实例(阻塞构造函数,永不返回):
|
||
- 从端口 26321 开始查找空闲端口,绑定 Netty TCP 服务端
|
||
- 作为 TCP 客户端连接 `lionwebsite.xyz:26322~26342`,唤醒中心服务器
|
||
- 启动 `DownloadCheckService` 和 5 秒定时任务 `mainThread()`
|
||
|
||
---
|
||
|
||
## 通信协议(Netty TCP)
|
||
|
||
自定义协议格式:`[1字节类型] + [4字节长度] + [JSON 负载]`
|
||
|
||
| 类型字节 | 消息类 | 方向 |
|
||
|---|---|---|
|
||
| 0 | `ResponseMessage` | 响应 |
|
||
| 1 | `DownloadPostMessage` | 服务端 → 节点 |
|
||
| 2 | `DownloadStatusMessage` | 节点 → 服务端 |
|
||
| 3 | `DeleteGalleryMessage` | 服务端 → 节点 |
|
||
| 6 | `IdentityMessage` | 握手 |
|
||
| 7 | `MaintainMessage` | 心跳 |
|
||
| 8 | `AvailableCheckMessage` | 可用性检查 |
|
||
| 9 | `SubscriptionSnapshotMessage` | 主站 → 节点,完整订阅备机快照 |
|
||
|
||
---
|
||
|
||
## HTTP 服务
|
||
|
||
### MultiThreadedHTTPServer(端口 8888)
|
||
- 提供压缩后的画廊 ZIP 文件下载
|
||
- 仅接受来自 `lionwebsite.xyz` IP 的连接
|
||
- 支持 HTTP 206 Partial Content(断点续传)
|
||
- 参数:`AuthCode`(管理员访问)、`gid`(画廊 ID)
|
||
|
||
### BackupSubServer(端口 8889)
|
||
- 提供 V2Ray 和 Clash 代理订阅文件
|
||
- 不直接访问上游;主站完成下载和倍率过滤后,通过 Netty 类型 9 推送完整快照
|
||
- 按公开 Key 的 SHA-256 查找用户绑定的子账号,未知 Key 返回 404
|
||
- 快照经 SHA-256 和 HMAC 校验后原子落盘,主站离线时继续分发最后成功版本
|
||
- 文件存储于 `sub/snapshots/{revision}/accounts/{accountId}/`
|
||
- 没有有效快照或快照超过最大有效期时返回 503,不回退旧共享订阅
|
||
- `GET /health/subscription` 提供不含 Key 和订阅正文的快照状态
|
||
|
||
---
|
||
|
||
## 核心服务
|
||
|
||
### DownloadCheckService
|
||
- 扫描下载目录(`/root/gallery/hentai/download/`)监控进度
|
||
- 通过检测 `galleryinfo.txt` 文件判断下载完成
|
||
- 将完成的下载任务移入压缩队列
|
||
- 后台线程每 5 秒执行 ZIP 压缩,完成后删除源目录
|
||
|
||
### DeleteService
|
||
- 按名称删除画廊目录
|
||
- 失败时返回 `ErrorCode.IO_ERROR` 或 `ErrorCode.FILE_NOT_FOUND`
|
||
|
||
### PrimaryChannelTracker
|
||
- 登记「哪条通道是主站」,供任务状态上报与心跳使用
|
||
- 只有主站会发的消息类型(身份/任务下发/探活/订阅快照)才可用于认领引用;
|
||
备机的身份消息与节点自己的出站类型都不算证据
|
||
- 首选通道断开时立即回退到其它仍可用的已认证通道;引用被清空时,
|
||
已认证通道上的下一条消息即可恢复它,避免任务状态静默停止上报
|
||
|
||
### RekickPolicy
|
||
- 判定「有待上报任务、却无可用通道」时是否应主动重新唤起主站
|
||
- 以 30 秒为最小间隔限流,既能快速自愈,又不会在主站确实离线时形成重连风暴
|
||
|
||
---
|
||
|
||
## 配置说明
|
||
|
||
### config.properties(从 `/root/gallery/storageNode/config.properties` 加载)
|
||
```properties
|
||
SubscriptionSyncEnabled=false
|
||
SubscriptionSyncSecret=
|
||
SubscriptionDataDir=/root/gallery/storageNode/sub
|
||
SubscriptionMaxStaleSeconds=604800
|
||
SubscriptionMaxPayloadBytes=52428800
|
||
SubscriptionHttpPort=8889
|
||
SubscriptionHttpWorkers=4
|
||
SubscriptionSocketTimeoutMs=10000
|
||
```
|
||
|
||
生产同步密钥优先通过 `SUBSCRIPTION_SYNC_SECRET` 环境变量提供,不得提交到仓库或写入日志。
|
||
|
||
### simplelogger.properties
|
||
- 日志级别:`info`
|
||
- 时间戳格式:`yyyy-MM-dd HH:mm:ss`
|
||
- 输出文件:`run.out`
|
||
|
||
---
|
||
|
||
## 架构说明
|
||
|
||
- 单模块 Maven 项目,现有 62 个测试用例:协议编解码(MessageCodec)、节点消息处理与
|
||
状态上报循环、备机订阅分发 HTTP(8889)、订阅快照存取、下载/压缩恢复与 gid 匹配、
|
||
主站通道自愈、删除与工具方法
|
||
- 硬编码文件系统路径(`/root/gallery/...`)→ 仅限 Linux 部署
|
||
- 外部连接:`lionwebsite.xyz`、`personal.lionwebsite.xyz`、`aaaa.gay`
|
||
- GraalVM 原生镜像编译,包含 Jackson 反射配置
|
||
- 大量使用 Lombok(`@Data`、`@Slf4j`)
|
||
- 使用 Hutool 工具库处理文件/ZIP/HTTP 操作
|