ce盘优化

This commit is contained in:
q
2025-11-13 19:32:44 +08:00
parent 52e889333b
commit 0e2ca2f1ca
9 changed files with 1810 additions and 149 deletions

View File

@@ -1,73 +0,0 @@
package cn.qaiu.vx.core.verticle.conf;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.impl.JsonUtil;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
/**
* Converter and mapper for {@link cn.qaiu.vx.core.verticle.conf.HttpProxyConf}.
* NOTE: This class has been automatically generated from the {@link cn.qaiu.vx.core.verticle.conf.HttpProxyConf} original class using Vert.x codegen.
*/
public class HttpProxyConfConverter {
private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER;
private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER;
static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, HttpProxyConf obj) {
for (java.util.Map.Entry<String, Object> member : json) {
switch (member.getKey()) {
case "password":
if (member.getValue() instanceof String) {
obj.setPassword((String)member.getValue());
}
break;
case "port":
if (member.getValue() instanceof Number) {
obj.setPort(((Number)member.getValue()).intValue());
}
break;
case "preProxyOptions":
if (member.getValue() instanceof JsonObject) {
obj.setPreProxyOptions(new io.vertx.core.net.ProxyOptions((io.vertx.core.json.JsonObject)member.getValue()));
}
break;
case "timeout":
if (member.getValue() instanceof Number) {
obj.setTimeout(((Number)member.getValue()).intValue());
}
break;
case "username":
if (member.getValue() instanceof String) {
obj.setUsername((String)member.getValue());
}
break;
}
}
}
static void toJson(HttpProxyConf obj, JsonObject json) {
toJson(obj, json.getMap());
}
static void toJson(HttpProxyConf obj, java.util.Map<String, Object> json) {
if (obj.getPassword() != null) {
json.put("password", obj.getPassword());
}
if (obj.getPort() != null) {
json.put("port", obj.getPort());
}
if (obj.getPreProxyOptions() != null) {
json.put("preProxyOptions", obj.getPreProxyOptions().toJson());
}
if (obj.getTimeout() != null) {
json.put("timeout", obj.getTimeout());
}
if (obj.getUsername() != null) {
json.put("username", obj.getUsername());
}
}
}

View File

@@ -59,7 +59,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Versions -->
<vertx.version>4.5.21</vertx.version>
<vertx.version>4.5.22</vertx.version>
<org.reflections.version>0.10.2</org.reflections.version>
<lombok.version>1.18.38</lombok.version>
<slf4j.version>2.0.5</slf4j.version>

View File

@@ -324,10 +324,10 @@ public enum PanDomainTemplate {
// Cloudreve自定义域名解析, 解析器CeTool兜底策略, 即任意域名如果匹配不到对应的规则, 则由CeTool统一处理,
// 如果不属于Cloudreve盘 则调用下一个自定义域名解析器, 若都处理不了则抛出异常, 这种匹配模式类似责任链
// https://pan.huang1111.cn/s/xxx
// http(s)://pan.huang1111.cn/s/xxx
// 通用域名([a-z\\d]+(-[a-z\\d]+)*\.)+[a-z]{2,}
CE("Cloudreve",
compile("http(s)?://([a-zA-Z\\d]+(-[a-zA-Z\\d]+)*\\.)+[a-zA-Z]{2,}(/s)?/(?<KEY>.+)"),
compile("http(s)?://([a-zA-Z\\d]+(-[a-zA-Z\\d]+)*\\.)+[a-zA-Z]{2,}(:\\d{1,5})?(/s)?/(?<KEY>.+)"),
"https://{any}/s/{shareKey}",
"https://cloudreve.org/",
CeTool.class),

View File

@@ -1,5 +1,6 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.FileInfo;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
@@ -9,6 +10,10 @@ import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
/**
* <a href="https://github.com/cloudreve/Cloudreve">Cloudreve 4.x 自建网盘解析</a> <br>
@@ -18,13 +23,14 @@ import java.net.URL;
public class Ce4Tool extends PanBase {
// Cloudreve 4.x uses /api/v3/ prefix for most APIs
private static final String FILE_URL_API_PATH = "/api/v3/file/url";
private static final String SHARE_API_PATH = "/api/v3/share/info/";
private static final String FILE_URL_API_PATH = "/api/v4/file/url";
private static final String SHARE_API_PATH = "/api/v4/share/info/";
public Ce4Tool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@Override
public Future<String> parse() {
String key = shareLinkInfo.getShareKey();
String pwd = shareLinkInfo.getSharePassword();
@@ -32,6 +38,10 @@ public class Ce4Tool extends PanBase {
try {
URL url = new URL(shareLinkInfo.getShareUrl());
String baseUrl = url.getProtocol() + "://" + url.getHost();
// 如果有端口,拼接上端口
if (url.getPort() != -1) {
baseUrl += ":" + url.getPort();
}
// 获取分享信息
getShareInfo(baseUrl, key, pwd);
@@ -45,6 +55,59 @@ public class Ce4Tool extends PanBase {
* 获取Cloudreve 4.x分享信息
*/
private void getShareInfo(String baseUrl, String key, String pwd) {
// 第一步请求分享URL获取302跳转地址
String shareUrl = shareLinkInfo.getShareUrl();
clientNoRedirects.getAbs(shareUrl).send().onSuccess(res -> {
try {
if (res.statusCode() == 302 || res.statusCode() == 301) {
String location = res.headers().get("Location");
if (location == null || location.isEmpty()) {
fail("获取重定向地址失败: Location头为空");
return;
}
// 从Location URL中提取path参数
String path = extractPathFromUrl(location);
if (path == null || path.isEmpty()) {
fail("从重定向URL中提取path参数失败: {}", location);
return;
}
// 解码URI
String decodedPath = URLDecoder.decode(path, StandardCharsets.UTF_8);
// 第二步:请求分享详情接口,获取文件名
requestShareDetail(baseUrl, key, pwd, decodedPath);
} else {
fail("分享URL请求失败: 期望302/301重定向实际状态码 {}", res.statusCode());
}
} catch (Exception e) {
fail(e, "解析重定向响应失败");
}
}).onFailure(handleFail(shareUrl));
}
/**
* 从URL中提取path参数
*/
private String extractPathFromUrl(String url) {
try {
// 解析查询参数
String[] keyValue = url.split("=", 2);
if (keyValue.length == 2 && keyValue[0].contains("path")) {
return keyValue[1];
}
return null;
} catch (Exception e) {
log.error("解析URL失败: {}", url, e);
return null;
}
}
/**
* 请求分享详情接口,获取文件名
*/
private void requestShareDetail(String baseUrl, String key, String pwd, String path) {
String shareApiUrl = baseUrl + SHARE_API_PATH + key;
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
@@ -56,18 +119,24 @@ public class Ce4Tool extends PanBase {
try {
if (res.statusCode() == 200) {
JsonObject jsonObject = asJson(res);
setFileInfo(jsonObject);
if (jsonObject.containsKey("code")) {
int code = jsonObject.getInteger("code");
if (code == 0) {
// 成功,获取文件信息和下载链接
JsonObject data = jsonObject.getJsonObject("data");
if (data != null) {
// 获取文件路径,如果没有则使用默认路径
String filePath = "/";
if (data.containsKey("path")) {
filePath = data.getString("path");
// 获取文件
String fileName = data.getString("name");
if (fileName == null || fileName.isEmpty()) {
fail("分享信息中缺少name字段");
return;
}
// 对于4.x需要通过 POST /api/v3/file/url 获取下载链接
// 拼接path和文件名
String filePath = path + "/" + fileName;
// 对于4.x需要通过 POST /api/v4/file/url 获取下载链接
getDownloadUrl(baseUrl, filePath);
} else {
fail("分享信息获取失败: data字段为空");
@@ -91,6 +160,66 @@ public class Ce4Tool extends PanBase {
}).onFailure(handleFail(shareApiUrl));
}
private void setFileInfo(JsonObject jsonObject) {
try {
JsonObject data = jsonObject.getJsonObject("data");
if (data == null) {
return;
}
FileInfo fileInfo = new FileInfo();
// 设置文件ID
if (data.containsKey("id")) {
fileInfo.setFileId(data.getString("id"));
}
// 设置文件名
if (data.containsKey("name")) {
fileInfo.setFileName(data.getString("name"));
}
// 设置下载次数
if (data.containsKey("downloaded")) {
fileInfo.setDownloadCount(data.getInteger("downloaded"));
}
// 设置访问次数visited
// 注意FileInfo 没有 visited 字段,可以放在 extParameters 中
// 设置创建者(从 owner 对象中获取)
if (data.containsKey("owner")) {
JsonObject owner = data.getJsonObject("owner");
if (owner != null && owner.containsKey("nickname")) {
fileInfo.setCreateBy(owner.getString("nickname"));
}
}
// 设置创建时间(格式化 ISO 8601 为 yyyy-MM-dd HH:mm:ss
if (data.containsKey("created_at")) {
String createdAt = data.getString("created_at");
if (createdAt != null && !createdAt.isEmpty()) {
try {
String formattedTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.format(OffsetDateTime.parse(createdAt).toLocalDateTime());
fileInfo.setCreateTime(formattedTime);
} catch (Exception e) {
log.warn("日期格式化失败: {}", createdAt, e);
// 如果格式化失败,直接使用原始值
fileInfo.setCreateTime(createdAt);
}
}
}
// 设置网盘类型
fileInfo.setPanType(shareLinkInfo.getType());
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
} catch (Exception e) {
log.warn("设置文件信息失败", e);
}
}
/**
* 通过 POST /api/v3/file/url 获取下载链接 (Cloudreve 4.x API)
*/
@@ -109,9 +238,9 @@ public class Ce4Tool extends PanBase {
try {
if (res.statusCode() == 200) {
JsonObject jsonObject = asJson(res);
if (jsonObject.containsKey("urls")) {
JsonArray urls = jsonObject.getJsonArray("urls");
if (urls != null && urls.size() > 0) {
if (jsonObject.containsKey("data") && jsonObject.getJsonObject("data").containsKey("urls")) {
JsonArray urls = jsonObject.getJsonObject("data").getJsonArray("urls");
if (urls != null && !urls.isEmpty()) {
JsonObject urlObj = urls.getJsonObject(0);
String downloadUrl = urlObj.getString("url");
if (downloadUrl != null && !downloadUrl.isEmpty()) {

View File

@@ -1,5 +1,6 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.FileInfo;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
@@ -8,6 +9,9 @@ import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest;
import java.net.URL;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
/**
* <a href="https://github.com/cloudreve/Cloudreve">Cloudreve自建网盘解析</a> <br>
@@ -32,12 +36,17 @@ public class CeTool extends PanBase {
}
@Override
public Future<String> parse() {
String key = shareLinkInfo.getShareKey();
String pwd = shareLinkInfo.getSharePassword();
try {
URL url = new URL(shareLinkInfo.getShareUrl());
String baseUrl = url.getProtocol() + "://" + url.getHost();
// 如果有端口,拼接上端口
if (url.getPort() != -1) {
baseUrl += ":" + url.getPort();
}
// 先检测API版本
detectVersionAndParse(baseUrl, key, pwd);
@@ -49,96 +58,145 @@ public class CeTool extends PanBase {
/**
* 检测Cloudreve版本并选择合适的解析器
* 先调用 /api/v3/site/ping 判断哪个API 如果/v3 或者/v4 能查询到json响应可以判断是哪个版本
* 不然返回404说明不是ce盘直接nextParser
* 检测策略:
* 1. 优先检测 v4 ping如果成功且返回有效JSON使用Ce4Tool
* 2. 如果 v4 ping 失败,检测 v3 ping
* 3. 如果 v3 ping 成功,尝试调用 v3 share API 来确认是否为 v3
* 4. 如果 v3 share API 成功,使用 v3 逻辑
* 5. 否则尝试下一个解析器
*/
private void detectVersionAndParse(String baseUrl, String key, String pwd) {
String pingUrlV3 = baseUrl + PING_API_V3_PATH;
// 先尝试v3 ping
clientSession.getAbs(pingUrlV3).send().onSuccess(res -> {
if (res.statusCode() == 200) {
try {
asJson(res);
// v3 ping成功可能是3.x或4.x尝试3.x的download API来判断
String shareApiUrl = baseUrl + SHARE_API_PATH + key;
String downloadApiUrl = baseUrl + DOWNLOAD_API_PATH + key + "?path=undefined/undefined;";
checkIfV3(shareApiUrl, downloadApiUrl, pwd);
} catch (Exception e) {
// JSON解析失败尝试v4 ping
// 优先检测 v4
tryV4Ping(baseUrl, key, pwd);
}
} else if (res.statusCode() == 404) {
// v3 ping不存在尝试v4
tryV4Ping(baseUrl, key, pwd);
} else {
// 其他错误不是Cloudreve盘
nextParser();
}
}).onFailure(t -> {
// 网络错误或不可达尝试v4 ping
tryV4Ping(baseUrl, key, pwd);
});
}
/**
* 尝试 v4 ping如果成功则使用 Ce4Tool
*/
private void tryV4Ping(String baseUrl, String key, String pwd) {
String pingUrlV4 = baseUrl + PING_API_V4_PATH;
clientSession.getAbs(pingUrlV4).send().onSuccess(res -> {
if (res.statusCode() == 200) {
try {
asJson(res);
// v4 ping成功使用Ce4Tool
JsonObject json = asJson(res);
// v4 ping 成功且返回有效JSON,使用 Ce4Tool
if (json != null && !json.isEmpty()) {
log.debug("检测到Cloudreve 4.x (通过v4 ping)");
delegateToCe4Tool();
return;
}
} catch (Exception e) {
// JSON解析失败继续尝试 v3
log.debug("v4 ping返回非JSON响应尝试v3");
}
}
// v4 ping失败或返回非JSON尝试 v3
tryV3Ping(baseUrl, key, pwd);
}).onFailure(t -> {
// v4 ping 网络错误,尝试 v3
log.debug("v4 ping请求失败尝试v3: {}", t.getMessage());
tryV3Ping(baseUrl, key, pwd);
});
}
/**
* 尝试 v3 ping如果成功则验证是否为真正的 v3
*/
private void tryV3Ping(String baseUrl, String key, String pwd) {
String pingUrlV3 = baseUrl + PING_API_V3_PATH;
clientSession.getAbs(pingUrlV3).send().onSuccess(res -> {
if (res.statusCode() == 200) {
try {
JsonObject json = asJson(res);
// v3 ping 成功且返回有效JSON进一步验证是否为 v3
if (json != null && !json.isEmpty()) {
// 尝试调用 v3 share API 来确认
verifyV3AndParse(baseUrl, key, pwd);
return;
}
} catch (Exception e) {
// JSON解析失败不是Cloudreve盘
nextParser();
log.debug("v3 ping返回非JSON响应不是Cloudreve盘");
}
} else {
// v4 ping失败不是Cloudreve盘
nextParser();
}
// v3 ping失败不是Cloudreve盘
log.debug("v3 ping失败尝试下一个解析器");
nextParser();
}).onFailure(t -> {
// 网络错误,尝试下一个解析器
// v3 ping 网络错误不是Cloudreve盘
log.debug("v3 ping请求失败尝试下一个解析器: {}", t.getMessage());
nextParser();
});
}
/**
* 检查是否是3.x版本通过尝试调用3.x的API
* 验证是否为 v3 版本并解析
* 通过调用 v3 share API 来确认,如果成功则使用 v3 逻辑
*/
private void checkIfV3(String shareApiUrl, String downloadApiUrl, String pwd) {
private void verifyV3AndParse(String baseUrl, String key, String pwd) {
String shareApiUrl = baseUrl + SHARE_API_PATH + key;
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
if (pwd != null) {
if (pwd != null && !pwd.isEmpty()) {
httpRequest.addQueryParam("password", pwd);
}
httpRequest.send().onSuccess(res -> {
try {
if (res.statusCode() == 200 && res.bodyAsJsonObject().containsKey("code")) {
// share API成功尝试download API
clientSession.putAbs(downloadApiUrl).send().onSuccess(res2 -> {
if (res2.statusCode() == 200 || res2.statusCode() == 400) {
// 3.x版本的download API存在
if (res.statusCode() == 200) {
JsonObject jsonObject = asJson(res);
// 检查响应格式是否符合 v3 API
if (jsonObject.containsKey("code") && jsonObject.getInteger("code") == 0) {
// v3 share API 成功,确认是 v3 版本
// 设置文件信息
setFileInfo(jsonObject);
log.debug("确认是Cloudreve 3.x使用v3下载API");
String downloadApiUrl = baseUrl + DOWNLOAD_API_PATH + key + "?path=undefined/undefined;";
getDownURL(downloadApiUrl);
} else if (res2.statusCode() == 404 || res2.statusCode() == 405) {
// download API不存在说明是4.x
delegateToCe4Tool();
} else {
// 其他错误可能是4.x
delegateToCe4Tool();
return;
}
}).onFailure(t -> {
// 请求失败尝试4.x
delegateToCe4Tool();
});
} else {
nextParser();
}
} catch (Exception e) {
nextParser();
log.debug("v3 share API解析失败: {}", e.getMessage());
}
}).onFailure(t -> {
log.debug("v3 share API请求失败: {}", t.getMessage());
// 请求失败,尝试 v4 或下一个解析器
tryV4ShareApi(baseUrl, key, pwd);
});
}
/**
* 尝试 v4 share API如果成功则使用 Ce4Tool
*/
private void tryV4ShareApi(String baseUrl, String key, String pwd) {
String shareApiUrl = baseUrl + "/api/v4/share/info/" + key;
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
if (pwd != null && !pwd.isEmpty()) {
httpRequest.addQueryParam("password", pwd);
}
httpRequest.send().onSuccess(res -> {
try {
if (res.statusCode() == 200) {
JsonObject jsonObject = asJson(res);
// 检查响应格式是否符合 v4 API
if (jsonObject.containsKey("code") && jsonObject.getInteger("code") == 0) {
// v4 share API 成功,使用 Ce4Tool
log.debug("确认是Cloudreve 4.x (通过v4 share API)");
delegateToCe4Tool();
return;
}
}
} catch (Exception e) {
log.debug("v4 share API解析失败: {}", e.getMessage());
}
// v4 share API 也失败不是Cloudreve盘
log.debug("v4 share API验证失败尝试下一个解析器");
nextParser();
}).onFailure(t -> {
log.debug("v4 share API请求失败尝试下一个解析器: {}", t.getMessage());
nextParser();
});
}
@@ -152,10 +210,87 @@ public class CeTool extends PanBase {
}
/**
* 设置文件信息Cloudreve 3.x
*/
private void setFileInfo(JsonObject jsonObject) {
try {
JsonObject data = jsonObject.getJsonObject("data");
if (data == null) {
return;
}
FileInfo fileInfo = new FileInfo();
// 设置文件ID
if (data.containsKey("key")) {
fileInfo.setFileId(data.getString("key"));
}
// 设置文件名(从 source 对象中获取)
if (data.containsKey("source")) {
JsonObject source = data.getJsonObject("source");
if (source != null) {
if (source.containsKey("name")) {
fileInfo.setFileName(source.getString("name"));
}
if (source.containsKey("size")) {
fileInfo.setSize(source.getLong("size"));
}
}
}
// 设置下载次数
if (data.containsKey("downloads")) {
fileInfo.setDownloadCount(data.getInteger("downloads"));
}
// 设置创建者(从 creator 对象中获取)
if (data.containsKey("creator")) {
JsonObject creator = data.getJsonObject("creator");
if (creator != null && creator.containsKey("nick")) {
fileInfo.setCreateBy(creator.getString("nick"));
}
}
// 设置创建时间(格式化 ISO 8601 为 yyyy-MM-dd HH:mm:ss
if (data.containsKey("create_date")) {
String createDate = data.getString("create_date");
if (createDate != null && !createDate.isEmpty()) {
try {
String formattedTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.format(OffsetDateTime.parse(createDate).toLocalDateTime());
fileInfo.setCreateTime(formattedTime);
} catch (Exception e) {
log.warn("日期格式化失败: {}", createDate, e);
// 如果格式化失败,直接使用原始值
fileInfo.setCreateTime(createDate);
}
}
}
// 设置访问次数views到扩展参数中
if (data.containsKey("views")) {
if (fileInfo.getExtParameters() == null) {
fileInfo.setExtParameters(new HashMap<>());
}
fileInfo.getExtParameters().put("views", data.getInteger("views"));
}
// 设置网盘类型
fileInfo.setPanType(shareLinkInfo.getType());
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
} catch (Exception e) {
log.warn("设置文件信息失败", e);
}
}
private void getDownURL(String shareApiUrl) {
clientSession.putAbs(shareApiUrl).send().onSuccess(res -> {
clientSession.putAbs(shareApiUrl)
.putHeader("Referer", shareLinkInfo.getShareUrl())
.send().onSuccess(res -> {
JsonObject jsonObject = asJson(res);
System.out.println(jsonObject.encodePrettily());
if (jsonObject.containsKey("code") && jsonObject.getInteger("code") == 0) {
promise.complete(jsonObject.getString("data"));
} else {

View File

@@ -25,7 +25,7 @@
<packageDirectory>${project.basedir}/web-service/target/package</packageDirectory>
<vertx.version>4.5.21</vertx.version>
<vertx.version>4.5.22</vertx.version>
<org.reflections.version>0.10.2</org.reflections.version>
<lombok.version>1.18.38</lombok.version>
<slf4j.version>2.0.5</slf4j.version>

View File

@@ -91,7 +91,7 @@
<el-button style="margin-left: 20px" @click="generateMarkdown">生成Markdown</el-button>
<el-button style="margin-left: 20px" @click="generateQRCode">扫码下载</el-button>
<el-button style="margin-left: 20px" @click="getStatistics">分享统计</el-button>
<el-button style="margin-left: 20px" @click="goToClientLinks" type="primary">客户端链接(实验)</el-button>
<el-button v-if="false" style="margin-left: 20px" @click="goToClientLinks" type="primary">客户端链接(实验)</el-button>
</p>
</div>

89
web-service/doc/README.md Normal file
View File

@@ -0,0 +1,89 @@
# API 文档说明
本目录包含网盘快速下载服务的完整 API 文档。
## 文件说明
### 1. API_DOCUMENTATION.md
详细的接口说明文档,包含:
- 所有接口的详细说明
- 请求参数和响应格式
- 使用示例
- 错误处理说明
- 注意事项
### 2. openapi.json
OpenAPI 3.0 规范的 JSON 文件,可用于:
- 导入到 API 测试工具(如 Apifox、Postman、Swagger UI 等)
- 生成客户端 SDK
- API 文档自动生成
### 3. CLIENT_LINKS_API.md
客户端下载链接 API 的详细说明文档(已存在)
## 如何使用
### 导入到 Apifox
1. 打开 Apifox
2. 选择项目 → 导入
3. 选择 "OpenAPI" 格式
4. 选择 `openapi.json` 文件
5. 点击导入
### 导入到 Postman
1. 打开 Postman
2. 点击 Import
3. 选择 "File" 标签
4. 选择 `openapi.json` 文件
5. 点击 Import
### 使用 Swagger UI 查看
1. 访问 [Swagger Editor](https://editor.swagger.io/)
2.`openapi.json` 的内容复制粘贴到编辑器中
3. 即可查看和测试所有接口
## 接口分类
### 解析相关接口
- `/parser` - 解析分享链接(重定向)
- `/json/parser` - 解析分享链接JSON
- `/:type/:key` - 根据类型和Key解析重定向
- `/json/:type/:key` - 根据类型和Key解析JSON
- `/v2/linkInfo` - 获取链接信息
### 文件列表接口
- `/v2/getFileList` - 获取文件列表
### 预览接口
- `/v2/view/:type/:key` - 预览媒体文件按类型和Key
- `/v2/preview` - 预览媒体文件按URL
- `/v2/viewUrl/:type/:param` - 预览URL目录预览
### 客户端下载链接接口
- `/v2/clientLinks` - 获取所有客户端下载链接
- `/v2/clientLink` - 获取指定类型的客户端下载链接
### 统计信息接口
- `/v2/statisticsInfo` - 获取统计信息
### 网盘列表接口
- `/v2/getPanList` - 获取支持的网盘列表
### 版本信息接口
- `/v2/build-version` - 获取版本号
### 隔空喊话接口
- `/v2/shout/submit` - 提交消息
- `/v2/shout/retrieve` - 检索消息
### 快捷下载接口
- `/d/:type/:key` - 下载重定向(短链)
- `/v2/redirectUrl/:type/:param` - 重定向下载URL目录文件
## 更新日志
- 2025-01-21: 初始版本,包含所有接口的完整文档

1381
web-service/doc/openapi.json Normal file

File diff suppressed because it is too large Load Diff