2025.4.23 filebrowser生成文件分享链接,普通人也可直接下载,无需token

This commit is contained in:
zhangsan 2025-04-23 13:50:05 +08:00
parent 0b58fa9969
commit 1497a29c75

View File

@ -1,4 +1,6 @@
package com.sky.utils; package com.sky.utils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@ -12,6 +14,7 @@ import org.apache.http.entity.StringEntity;
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.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import java.io.IOException; import java.io.IOException;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLEncoder; import java.net.URLEncoder;
@ -27,46 +30,38 @@ public class FileBrowserUtil {
/** /**
* 第一步登录拿 token * 第一步登录拿 token
* 调用 /api/login 接口返回原始的 JWT token 字符串 * 调用 /api/login 接口返回纯 JWT 字符串 {"token":"..."} 结构
* curl -X POST "https://fshare.bitday.top/api/login" \
* -H "Content-Type: application/json" \
* -d '{
* "username":"admin",
* "password":"asdf14789"
* }'
* 返回值 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
*/ */
public String login() throws IOException { public String login() throws IOException {
String url = domain + "/api/login"; String url = domain + "/api/login";
// 创建 HttpClient 实例 try (CloseableHttpClient client = HttpClients.createDefault()) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpPost post = new HttpPost(url);
// 构造 POST 请求 post.setHeader("Content-Type", "application/json");
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json");
// 构造 JSON body // 构造登录参数
JSONObject json = new JSONObject(); JSONObject cred = new JSONObject();
json.put("username", username); cred.put("username", username);
json.put("password", password); cred.put("password", password);
post.setEntity(new StringEntity(cred.toString(), StandardCharsets.UTF_8));
// 设置请求体 try (CloseableHttpResponse resp = client.execute(post)) {
StringEntity requestEntity = new StringEntity(json.toString(), StandardCharsets.UTF_8); int status = resp.getStatusLine().getStatusCode();
requestEntity.setContentType("application/json"); String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8).trim();
httpPost.setEntity(requestEntity);
// 发送请求并处理响应 if (status >= 200 && status < 300) {
try (CloseableHttpResponse response = httpClient.execute(httpPost)) { // 如果返回 JSON 对象则解析出 token 字段
int statusCode = response.getStatusLine().getStatusCode(); if (body.startsWith("{") && body.endsWith("}")) {
if (statusCode >= 200 && statusCode < 300) { JSONObject obj = JSONObject.parseObject(body);
HttpEntity respEntity = response.getEntity(); String token = obj.getString("token");
String body = EntityUtils.toString(respEntity, StandardCharsets.UTF_8); log.info("登录成功token={}", token);
log.info("token:{}",body); return token;
// 返回原始返回值假设就是 JWT token 字符串 }
// 否则直接当成原始 JWT 返回
log.info("登录成功token={}", body);
return body; return body;
} else { } else {
log.error("Login failed, HTTP status code: " + statusCode); log.error("Login failed: HTTP {} - {}", status, body);
return ""; throw new IOException("Login failed: HTTP " + status);
} }
} }
} }
@ -74,49 +69,97 @@ public class FileBrowserUtil {
/** /**
* 第二步上传文件 * 第二步上传文件
+ * curl -v -X POST \ * POST {domain}/api/resources/{encodedPath}?override=true
+ * "$DOMAIN/api/resources/$REMOTE_PATH?override=true" \ // 服务器上相对路径 * Header: X-Auth: token
+ * -H "X-Auth: $TOKEN" \
+ * -H "Content-Type: image/jpeg" \ // 根据文件类型替换
+ * --data-binary "@/path/to/local/photo.jpg" // raw body 方式上传
*/ */
public String uploadFile(byte[] fileBytes, String fileName) throws IOException {
public String uploadAndGetUrl(byte[] fileBytes, String fileName) throws IOException {
// 1. 登录拿 token
String token = login(); String token = login();
// 2. 确定远端存储路径和编码保留斜杠
String remotePath = "store/" + fileName; String remotePath = "store/" + fileName;
String encodedPath = URLEncoder.encode(remotePath, StandardCharsets.UTF_8.toString()) String encodedPath = URLEncoder
.encode(remotePath, StandardCharsets.UTF_8)
.replace("%2F", "/"); .replace("%2F", "/");
// 3. 根据文件名 MIME 类型fallback application/octet-stream // 根据后缀 MIME 类型
String mimeType = URLConnection.guessContentTypeFromName(fileName); String mimeType = URLConnection.guessContentTypeFromName(fileName);
if (mimeType == null) { if (mimeType == null) {
mimeType = "application/octet-stream"; mimeType = "application/octet-stream";
} }
// 4. 构造上传 URL
String uploadUrl = domain + "/api/resources/" + encodedPath + "?override=true"; String uploadUrl = domain + "/api/resources/" + encodedPath + "?override=true";
// 5. 执行 upload
try (CloseableHttpClient client = HttpClients.createDefault()) { try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpPost post = new HttpPost(uploadUrl); HttpPost post = new HttpPost(uploadUrl);
post.setHeader("X-Auth", token); post.setHeader("X-Auth", token);
post.setEntity(new ByteArrayEntity(fileBytes, ContentType.create(mimeType))); post.setEntity(new ByteArrayEntity(fileBytes, ContentType.create(mimeType)));
try (CloseableHttpResponse resp = client.execute(post)) { try (CloseableHttpResponse resp = client.execute(post)) {
int status = resp.getStatusLine().getStatusCode(); int status = resp.getStatusLine().getStatusCode();
String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8); String respBody = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
if (status < 200 || status >= 300) { if (status < 200 || status >= 300) {
log.error("文件上传失败: HTTP {}{}", status, body); log.error("文件上传失败: HTTP {} - {}", status, respBody);
return ""; throw new IOException("Upload failed: HTTP " + status);
} }
log.info("文件上传成功remotePath={}", remotePath);
return remotePath;
} }
} }
}
// 6. 拼接 raw 下载链接并返回 /**
String downloadUrl = domain + "/api/raw/" + encodedPath; * 第三步生成公开分享链接
log.info("文件下载链接:{}", downloadUrl); * 模拟浏览器的 POST /api/share/{encodedPath} 请求body "{}"
return downloadUrl; */
public String createShareLink(String remotePath) throws IOException {
String token = login();
// URL encode 并保留斜杠
String encodedPath = URLEncoder
.encode(remotePath, StandardCharsets.UTF_8)
.replace("%2F", "/");
String shareUrl = domain + "/api/share/" + encodedPath;
log.info("准备创建分享链接POST {}", shareUrl);
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpPost post = new HttpPost(shareUrl);
post.setHeader("Cookie", "auth=" + token);
post.setHeader("X-Auth", token);
post.setHeader("Content-Type", "text/plain;charset=UTF-8");
post.setEntity(new StringEntity("{}", StandardCharsets.UTF_8));
try (CloseableHttpResponse resp = client.execute(post)) {
int status = resp.getStatusLine().getStatusCode();
String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8).trim();
if (status < 200 || status >= 300) {
log.error("创建分享失败 HTTP {} - {}", status, body);
throw new IOException("Share failed: HTTP " + status);
}
// ========= 这里改为先检测是对象还是数组 =========
JSONObject json;
if (body.startsWith("[")) {
// 如果真返回数组老版本可能是 [{...}]
json = JSONArray.parseArray(body).getJSONObject(0);
} else if (body.startsWith("{")) {
// 当前版本直接返回对象
json = JSONObject.parseObject(body);
} else {
throw new IOException("Unexpected share response: " + body);
}
String hash = json.getString("hash");
String publicUrl = domain + "/api/public/dl/" + hash + "/" + remotePath;
log.info("创建分享链接成功:{}", publicUrl);
return publicUrl;
}
}
}
/**
* 整合示例上传并立即返回分享链接
*/
public String uploadAndGetUrl(byte[] fileBytes, String fileName) throws IOException {
String remotePath = uploadFile(fileBytes, fileName);
return createShareLink(remotePath);
} }
} }