From 1497a29c75ff474761e3cff5038753ef4f3c8969 Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Wed, 23 Apr 2025 13:50:05 +0800 Subject: [PATCH] =?UTF-8?q?2025.4.23=20filebrowser=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=88=86=E4=BA=AB=E9=93=BE=E6=8E=A5=EF=BC=8C?= =?UTF-8?q?=E6=99=AE=E9=80=9A=E4=BA=BA=E4=B9=9F=E5=8F=AF=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=EF=BC=8C=E6=97=A0=E9=9C=80token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/sky/utils/FileBrowserUtil.java | 151 +++++++++++------- 1 file changed, 97 insertions(+), 54 deletions(-) diff --git a/sky-common/src/main/java/com/sky/utils/FileBrowserUtil.java b/sky-common/src/main/java/com/sky/utils/FileBrowserUtil.java index 69b5d94..ef48566 100644 --- a/sky-common/src/main/java/com/sky/utils/FileBrowserUtil.java +++ b/sky-common/src/main/java/com/sky/utils/FileBrowserUtil.java @@ -1,4 +1,6 @@ package com.sky.utils; + +import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import lombok.AllArgsConstructor; 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.HttpClients; import org.apache.http.util.EntityUtils; + import java.io.IOException; import java.net.URLConnection; import java.net.URLEncoder; @@ -27,46 +30,38 @@ public class FileBrowserUtil { /** * —— 第一步:登录拿 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… + * 调用 /api/login 接口,返回纯 JWT 字符串,或 {"token":"..."} 结构 */ - public String login() throws IOException { String url = domain + "/api/login"; - // 创建 HttpClient 实例 - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - // 构造 POST 请求 - HttpPost httpPost = new HttpPost(url); - httpPost.setHeader("Content-Type", "application/json"); + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpPost post = new HttpPost(url); + post.setHeader("Content-Type", "application/json"); - // 构造 JSON body - JSONObject json = new JSONObject(); - json.put("username", username); - json.put("password", password); + // 构造登录参数 + JSONObject cred = new JSONObject(); + cred.put("username", username); + cred.put("password", password); + post.setEntity(new StringEntity(cred.toString(), StandardCharsets.UTF_8)); - // 设置请求体 - StringEntity requestEntity = new StringEntity(json.toString(), StandardCharsets.UTF_8); - requestEntity.setContentType("application/json"); - httpPost.setEntity(requestEntity); + try (CloseableHttpResponse resp = client.execute(post)) { + int status = resp.getStatusLine().getStatusCode(); + String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8).trim(); - // 发送请求并处理响应 - try (CloseableHttpResponse response = httpClient.execute(httpPost)) { - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { - HttpEntity respEntity = response.getEntity(); - String body = EntityUtils.toString(respEntity, StandardCharsets.UTF_8); - log.info("token:{}",body); - // 返回原始返回值(假设就是 JWT token 字符串) + if (status >= 200 && status < 300) { + // 如果返回 JSON 对象,则解析出 token 字段 + if (body.startsWith("{") && body.endsWith("}")) { + JSONObject obj = JSONObject.parseObject(body); + String token = obj.getString("token"); + log.info("登录成功,token={}", token); + return token; + } + // 否则直接当成原始 JWT 返回 + log.info("登录成功,token={}", body); return body; } else { - log.error("Login failed, HTTP status code: " + statusCode); - return ""; + log.error("Login failed: HTTP {} - {}", status, body); + throw new IOException("Login failed: HTTP " + status); } } } @@ -74,49 +69,97 @@ public class FileBrowserUtil { /** * —— 第二步:上传文件 —— - + * curl -v -X POST \ - + * "$DOMAIN/api/resources/$REMOTE_PATH?override=true" \ // 服务器上相对路径 - + * -H "X-Auth: $TOKEN" \ - + * -H "Content-Type: image/jpeg" \ // 根据文件类型替换 - + * --data-binary "@/path/to/local/photo.jpg" // 以 raw body 方式上传 + * POST {domain}/api/resources/{encodedPath}?override=true + * Header: X-Auth: token */ - - public String uploadAndGetUrl(byte[] fileBytes, String fileName) throws IOException { - // 1. 登录拿 token + public String uploadFile(byte[] fileBytes, String fileName) throws IOException { String token = login(); - - // 2. 确定远端存储路径和编码(保留斜杠) String remotePath = "store/" + fileName; - String encodedPath = URLEncoder.encode(remotePath, StandardCharsets.UTF_8.toString()) + String encodedPath = URLEncoder + .encode(remotePath, StandardCharsets.UTF_8) .replace("%2F", "/"); - // 3. 根据文件名猜 MIME 类型,fallback 到 application/octet-stream + // 根据后缀猜 MIME 类型 String mimeType = URLConnection.guessContentTypeFromName(fileName); if (mimeType == null) { mimeType = "application/octet-stream"; } - // 4. 构造上传 URL String uploadUrl = domain + "/api/resources/" + encodedPath + "?override=true"; - - // 5. 执行 upload try (CloseableHttpClient client = HttpClients.createDefault()) { HttpPost post = new HttpPost(uploadUrl); post.setHeader("X-Auth", token); post.setEntity(new ByteArrayEntity(fileBytes, ContentType.create(mimeType))); + try (CloseableHttpResponse resp = client.execute(post)) { 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) { - log.error("文件上传失败: HTTP {},{}", status, body); - return ""; + log.error("文件上传失败: HTTP {} - {}", status, respBody); + throw new IOException("Upload failed: HTTP " + status); } + log.info("文件上传成功,remotePath={}", remotePath); + return remotePath; } } + } - // 6. 拼接 raw 下载链接并返回 - String downloadUrl = domain + "/api/raw/" + encodedPath; - log.info("文件下载链接:{}", downloadUrl); - return downloadUrl; + /** + * 第三步:生成公开分享链接 + * 模拟浏览器的 POST /api/share/{encodedPath} 请求,body 为 "{}" + */ + 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); } }