写完了

This commit is contained in:
smallxy 2025-04-23 21:51:08 +08:00
commit 8a52961ccd
1 changed files with 422 additions and 0 deletions

View File

@ -0,0 +1,422 @@
package cn.cyanbukkit;
import okhttp3.*;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class AliyahPanBackup {
private static final Logger logger = LoggerFactory.getLogger(AliyahPanBackup.class);
// 阿里云盘 API 配置
private static final String CLIENT_ID = "8a9342f6826a49508dc7911ca70c4d86";
private static final String CLIENT_SECRET = "0873fada20744a6e849c830d95cc7ee9";
private static final String REDIRECT_URI = "http://mc.lanternmc.cn";
private static final String AUTH_URL = "https://openapi.alipan.com/oauth/authorize";
private static final String TOKEN_URL = "https://openapi.alipan.com/oauth/access_token";
private static final String API_URL = "https://openapi.alipan.com";
private static final String TARGET_FOLDER = "lanternmc"; // 目标目录名称
private static String ACCESS_TOKEN;
private static String DRIVE_ID;
public static void main(String[] args) {
logger.info("开始上传到阿里云盘...");
// 1. 获取 ACCESS_TOKEN
ACCESS_TOKEN = getAccessToken();
if (ACCESS_TOKEN == null) {
logger.error("获取 ACCESS_TOKEN 失败");
System.exit(1);
}
// 2. 获取 drive_id
DRIVE_ID = getDriveId();
if (DRIVE_ID == null) {
logger.error("获取 drive_id 失败");
System.exit(1);
}
// 3. 创建备份文件
String zipFilePath = createBackupZip();
// 4. 上传文件
boolean uploadSuccess = uploadToAlipan(zipFilePath);
if (uploadSuccess) {
logger.info("备份文件已成功上传到阿里云盘目录: {}", TARGET_FOLDER);
} else {
logger.error("上传到阿里云盘失败");
System.exit(1);
}
}
private static String getAccessToken() {
String scope = "file:all:read,file:all:write,user:base";
String authLink = AUTH_URL + "?client_id=" + CLIENT_ID + "&auto_login=true&redirect_uri=" + REDIRECT_URI + "&response_type=code&scope=" + scope;
logger.info("请按以下步骤操作:");
logger.info("1. 访问链接 {} 授权", authLink);
logger.info("2. 复制 LanternMC 的特有的 code");
// 生成二维码并直接渲染到终端
try {
QRCodeWriter qrCodeWriter = new QRCodeWriter();
// 使用更小的尺寸(100x100)和更简单的字符(和空格)
BitMatrix bitMatrix = qrCodeWriter.encode(authLink, BarcodeFormat.QR_CODE, 100, 100);
// 使用紧凑ASCII字符在终端显示二维码(每两行合并为一行)
for (int y = 0; y < bitMatrix.getHeight(); y += 2) {
StringBuilder sb = new StringBuilder();
for (int x = 0; x < bitMatrix.getWidth(); x++) {
boolean upper = bitMatrix.get(x, y);
boolean lower = y + 1 < bitMatrix.getHeight() && bitMatrix.get(x, y + 1);
if (upper && lower) sb.append("");
else if (upper) sb.append("");
else if (lower) sb.append("");
else sb.append(" ");
}
System.out.println(sb.toString());
}
logger.info("二维码已显示在终端");
} catch (WriterException e) {
logger.error("生成二维码失败", e);
}
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.print("请输入获取到的 code: ");
String authCode;
try {
authCode = reader.readLine();
} catch (IOException e) {
logger.error("读取 code 失败", e);
return null;
}
if (authCode.isEmpty()) {
logger.error("错误:未提供 code");
return null;
}
// 请求 ACCESS_TOKEN
JSONObject tokenRequest = new JSONObject();
tokenRequest.put("client_id", CLIENT_ID);
tokenRequest.put("client_secret", CLIENT_SECRET);
tokenRequest.put("grant_type", "authorization_code");
tokenRequest.put("code", authCode);
String tokenResponse = sendPostRequest(TOKEN_URL, tokenRequest.toString(), "application/json");
try {
JSONObject jsonResponse = new JSONObject(tokenResponse);
String accessToken = jsonResponse.getString("access_token");
logger.info("授权成功!已获取 ACCESS_TOKEN");
return accessToken;
} catch (JSONException e) {
logger.error("获取 ACCESS_TOKEN 失败,响应: {}", tokenResponse);
logger.error("请检查授权码是否正确并重试");
return null;
}
}
private static String getDriveId() {
String driveInfoUrl = API_URL + "/adrive/v1.0/user/getDriveInfo";
String driveResponse = sendPostRequest(driveInfoUrl, "", "application/json", ACCESS_TOKEN);
try {
JSONObject jsonResponse = new JSONObject(driveResponse);
String backupDriveId = jsonResponse.optString("backup_drive_id");
if (!backupDriveId.isEmpty()) {
return backupDriveId;
} else {
String defaultDriveId = jsonResponse.optString("default_drive_id");
if (!defaultDriveId.isEmpty()) {
logger.warn("未获取到 backup_drive_id将使用 default_drive_id");
return defaultDriveId;
} else {
return null;
}
}
} catch (JSONException e) {
logger.error("获取 drive_id 失败,响应: {}", driveResponse);
return null;
}
}
private static String createBackupZip() {
String zipFileName = "backup_" + System.currentTimeMillis() + ".zip";
File zipFile = new File(zipFileName);
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
// 只备份特定目录避免系统文件
List<String> backupDirs = List.of(
"/lanterntea",
"/opt/mcsmanager/daemon/data/InstanceConfig",
"/opt/mcsmanager/web/logs"
);
for (String dirPath : backupDirs) {
File dir = new File(dirPath);
if (dir.exists() && dir.isDirectory()) {
String prefix = "";
if (dirPath.equals("/lanterntea")) {
prefix = "服务端/";
} else if (dirPath.equals("/opt/mcsmanager/daemon/data/InstanceConfig")) {
prefix = "MCSM/";
} else if (dirPath.equals("/opt/mcsmanager/web/logs")) {
prefix = "日志/";
}
addDirectoryToZip(zos, dir, prefix);
} else {
logger.warn("{} 目录不存在", dirPath);
}
}
logger.info("备份完成ZIP 文件已保存为: {}", zipFileName);
return zipFileName;
} catch (IOException e) {
logger.error("压缩失败", e);
return null;
}
}
private static void addDirectoryToZip(ZipOutputStream zos, File dir, String parentDir) throws IOException {
for (File file : Objects.requireNonNull(dir.listFiles())) {
if (file.isDirectory()) {
// 跳过备份目录自身
if (!file.getAbsolutePath().equals(dir.getAbsolutePath())) {
logger.info("正在压缩目录: {}", parentDir + file.getName() + "/");
addDirectoryToZip(zos, file, parentDir + file.getName() + "/");
}
} else {
// 跳过已存在的备份文件
if (!file.getName().endsWith(".zip")) {
logger.info("正在压缩文件: {}", parentDir + file.getName());
try (FileInputStream fis = new FileInputStream(file)) {
ZipEntry zipEntry = new ZipEntry(parentDir + file.getName());
zos.putNextEntry(zipEntry);
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
zos.closeEntry();
}
}
}
}
}
private static boolean uploadToAlipan(String zipFilePath) {
File file = new File(zipFilePath);
String fileName = file.getName();
// 创建上传任务
String createUrl = API_URL + "/adrive/v1.0/openFile/create";
JSONObject createJson = new JSONObject();
createJson.put("drive_id", DRIVE_ID);
createJson.put("parent_file_id", "root");
createJson.put("name", fileName);
createJson.put("type", "file");
createJson.put("check_name_mode", "refuse");
// 计算分片信息
JSONArray partInfoList = new JSONArray();
long fileSize = file.length();
long minPartSize = 100 * 1024; // 100KB最小分片
long maxPartSize = 5L * 1024 * 1024 * 1024; // 5GB最大分片
long partSize = Math.min(Math.max(fileSize / 10000, minPartSize), maxPartSize); // 自动计算分片大小
int partCount = (int) Math.ceil((double) fileSize / partSize);
if (partCount > 10000) {
partCount = 10000;
partSize = (long) Math.ceil((double) fileSize / partCount);
}
for (int i = 1; i <= partCount; i++) {
JSONObject partInfo = new JSONObject();
partInfo.put("part_number", i);
partInfo.put("part_size", i == partCount ? fileSize - ((i - 1) * partSize) : partSize);
partInfoList.put(partInfo);
}
createJson.put("part_info_list", partInfoList);
createJson.put("size", file.length());
// 发送创建上传任务请求
String createResponse = sendPostRequest(createUrl, createJson.toString(), "application/json", ACCESS_TOKEN);
try {
JSONObject jsonResponse = new JSONObject(createResponse);
if (!jsonResponse.has("file_id") || !jsonResponse.has("upload_id")) {
logger.error("创建上传任务失败,响应缺少必要字段: {}", createResponse);
return false;
}
String fileId = jsonResponse.getString("file_id");
String uploadId = jsonResponse.getString("upload_id");
boolean rapidUpload = jsonResponse.optBoolean("rapid_upload");
if (rapidUpload) {
System.out.println("文件秒传成功!");
return true;
}
// 获取上传地址
String getUploadUrl = API_URL + "/adrive/v1.0/openFile/getUploadUrl";
JSONObject uploadParams = new JSONObject();
uploadParams.put("drive_id", DRIVE_ID);
uploadParams.put("file_id", fileId);
uploadParams.put("upload_id", uploadId);
uploadParams.put("part_info_list", partInfoList);
String uploadUrlResponse = sendPostRequest(getUploadUrl, uploadParams.toString(), "application/json", ACCESS_TOKEN);
JSONObject uploadUrlJson = new JSONObject(uploadUrlResponse);
if (!uploadUrlJson.has("part_info_list")) {
logger.error("获取上传地址失败,响应缺少必要字段: {}", uploadUrlResponse);
return false;
}
JSONArray receivedPartInfoList = uploadUrlJson.getJSONArray("part_info_list");
// 上传文件分片
for (int i = 0; i < receivedPartInfoList.length(); i++) {
JSONObject partInfo = receivedPartInfoList.getJSONObject(i);
String uploadUrl = partInfo.getString("upload_url");
int partNumber = partInfo.getInt("part_number");
// 处理URL中的特殊字符
if (uploadUrl.contains(" ")) {
uploadUrl = uploadUrl.replace(" ", "%20");
}
// 计算分片位置和实际大小
long pos = (partNumber - 1) * partSize;
long actualSize = partNumber == partCount ? fileSize - pos : partSize;
byte[] partContent = new byte[(int) actualSize];
// 读取分片内容
try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
randomAccessFile.seek(pos);
randomAccessFile.readFully(partContent, 0, (int) actualSize);
} catch (IOException e) {
logger.error("读取文件分片失败", e);
return false;
}
// 上传分片
RequestBody body = RequestBody.create(partContent, null); // 不指定MediaType
Request request = new Request.Builder()
.url(uploadUrl)
.put(body)
.removeHeader("Content-Type") // 移除Content-Type以避免签名问题
.build();
try (Response response = new OkHttpClient().newCall(request).execute()) {
if (!response.isSuccessful()) {
logger.error("上传分片失败partNumber: {}, 响应: {}", partNumber, response.body().string());
return false;
}
logger.info("上传分片成功,进度: {}/{}", partNumber, partCount);
} catch (IOException e) {
logger.error("上传分片失败", e);
return false;
}
}
// 标记文件上传完成
String completeUrl = API_URL + "/adrive/v1.0/openFile/complete";
JSONObject completeJson = new JSONObject();
completeJson.put("drive_id", DRIVE_ID);
completeJson.put("file_id", fileId);
completeJson.put("upload_id", uploadId);
String completeResponse = sendPostRequest(completeUrl, completeJson.toString(), "application/json", ACCESS_TOKEN);
try {
JSONObject completeResponseJson = new JSONObject(completeResponse);
if (completeResponseJson.has("status") && "available".equals(completeResponseJson.getString("status")) ||
completeResponseJson.has("file_id")) {
logger.info("文件上传成功文件ID: {}", completeResponseJson.optString("file_id"));
return true;
}
logger.error("标记文件上传完毕失败,响应: {}", completeResponse);
return false;
} catch (JSONException e) {
logger.error("标记文件上传完毕失败,响应: {}", completeResponse);
return false;
}
} catch (JSONException e) {
logger.error("上传失败,响应: {}", createResponse);
return false;
}
}
private static String sendPostRequest(String url, String data, String contentType) {
return sendPostRequest(url, data, contentType, null);
}
private static String sendPostRequest(String url, String data, String contentType, String accessToken) {
int maxRetries = 3;
int retryDelay = 1000; // 1秒
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
// 使用 URI 来构建 URL避免 URL(String) 构造函数的弃用问题
URI uri = URI.create(url);
URL newUrl = uri.toURL();
HttpURLConnection conn = (HttpURLConnection) newUrl.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", contentType);
conn.setRequestProperty("Accept", "application/json");
if (accessToken != null) {
conn.setRequestProperty("Authorization", "Bearer " + accessToken);
}
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write(data.getBytes(StandardCharsets.UTF_8));
}
int responseCode = conn.getResponseCode();
if (responseCode == 400) {
// 记录400错误的详细日志
logger.error("请求失败(尝试 {}/{}): Server returned HTTP response code: {} for URL: {}", attempt, maxRetries, responseCode, url);
logger.error("响应头: {}", conn.getHeaderFields());
logger.error("响应体: {}", new String(conn.getErrorStream().readAllBytes(), StandardCharsets.UTF_8));
} else if (responseCode == 404) {
logger.error("服务器返回404错误: {}", url);
} else if (responseCode == 200) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
response.append(line);
}
in.close();
return response.toString();
} else {
logger.error("请求失败(尝试 {}/{}): {}", attempt, maxRetries, conn.getResponseMessage());
}
if (attempt < maxRetries) {
try {
Thread.sleep(retryDelay);
retryDelay *= 2; // 指数退避
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return "";
}
}
} catch (IOException ex) {
logger.error("请求失败(尝试 {}/{}): {}", attempt, maxRetries, ex.getMessage());
}
}
return "";
}
}