opt(openlist): 新增OpenList多存储同步支持,完善心跳检测字段
1. 为wp_user_openlist表新增心跳检测相关字段 2. 新增获取OpenList存储列表API 3. 重构OpenList同步逻辑,支持多存储同步 4. 优化网盘创建逻辑,按存储ID匹配复用已有网盘
This commit is contained in:
parent
bd921cc161
commit
d8aab63d85
@ -350,6 +350,53 @@ public class OpenListClient {
|
||||
return config != null ? config.getOpenlistUrl() : null;
|
||||
}
|
||||
|
||||
// ==================== 存储管理 API ====================
|
||||
|
||||
/**
|
||||
* 获取用户的所有存储列表
|
||||
* OpenList 支持多个存储(storage),每个存储有独立的挂载路径
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 存储列表,每个存储包含 id、mount_path、driver 等信息
|
||||
*/
|
||||
public List<Map<String, Object>> listStorages(Long userId) {
|
||||
UserOpenlist config = getUserOpenlist(userId);
|
||||
if (config == null) {
|
||||
throw new RuntimeException("用户未绑定 OpenList");
|
||||
}
|
||||
|
||||
String url = config.getOpenlistUrl() + "/api/me/storages";
|
||||
|
||||
try {
|
||||
String token = getValidToken(userId);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
HttpEntity<Void> entity = new HttpEntity<>(headers);
|
||||
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);
|
||||
Map<String, Object> result = response.getBody();
|
||||
|
||||
if (result == null || (int) result.get("code") != 200) {
|
||||
log.warn("用户 {} 获取存储列表失败: {}", userId, result);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> storages = (List<Map<String, Object>>) result.get("data");
|
||||
if (storages == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
log.info("用户 {} 共有 {} 个存储", userId, storages.size());
|
||||
return storages;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("用户 {} 获取存储列表异常", userId, e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 同步相关 API ====================
|
||||
|
||||
/**
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -124,6 +124,18 @@ public class DatabaseInitializer {
|
||||
SchemaUtil.ensureColumnExists(conn, "wp_icon", "link_type",
|
||||
"VARCHAR(20) NOT NULL DEFAULT 'external' COMMENT '链接类型:external(仅外网)/ internal(仅内网)/ both(内外网)'");
|
||||
|
||||
// 检查 wp_user_openlist 表是否有 last_heartbeat_time 字段(心跳检测时间)
|
||||
SchemaUtil.ensureColumnExists(conn, "wp_user_openlist", "last_heartbeat_time",
|
||||
"DATETIME DEFAULT NULL COMMENT '最后一次心跳检测时间'");
|
||||
|
||||
// 检查 wp_user_openlist 表是否有 heartbeat_status 字段(心跳状态)
|
||||
SchemaUtil.ensureColumnExists(conn, "wp_user_openlist", "heartbeat_status",
|
||||
"VARCHAR(20) DEFAULT NULL COMMENT '心跳状态:success/failed'");
|
||||
|
||||
// 检查 wp_user_openlist 表是否有 heartbeat_error 字段(心跳失败原因)
|
||||
SchemaUtil.ensureColumnExists(conn, "wp_user_openlist", "heartbeat_error",
|
||||
"VARCHAR(500) DEFAULT NULL COMMENT '心跳检测失败原因'");
|
||||
|
||||
// 移除已废弃的 icon 列(已被 logo_url 替代)
|
||||
try (var checkStmt = conn.createStatement();
|
||||
var checkRs = checkStmt.executeQuery("SHOW COLUMNS FROM wp_drive LIKE 'icon'")) {
|
||||
|
||||
@ -94,51 +94,94 @@ public class OpenListSyncService {
|
||||
|
||||
log.info("用户 {} 开始从 OpenList 同步: {}", userId, config.getOpenlistUrl());
|
||||
|
||||
// 2. 查找或创建关联网盘
|
||||
Drive drive = findOrCreateDrive(userId, config);
|
||||
// 2. 获取 OpenList 的所有存储
|
||||
List<Map<String, Object>> storages = openListClient.listStorages(userId);
|
||||
if (storages.isEmpty()) {
|
||||
log.warn("用户 {} 的 OpenList 没有可用存储,尝试从根目录同步", userId);
|
||||
// 降级处理:从根目录同步,创建单个 Drive
|
||||
Drive drive = findOrCreateDrive(userId, config, null, null);
|
||||
if (drive == null) {
|
||||
result.errors.add("创建网盘失败");
|
||||
return result;
|
||||
}
|
||||
result.driveCount = 1;
|
||||
log.info("用户 {} 使用网盘: {} (ID={})", userId, drive.getName(), drive.getId());
|
||||
syncDirectory(userId, drive.getId(), "/", 0L, 0, result);
|
||||
updateDriveStats(drive.getId(), userId);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 3. 从根目录开始递归同步
|
||||
String rootPath = "/";
|
||||
log.info("用户 {} 的 OpenList 共有 {} 个存储", userId, storages.size());
|
||||
|
||||
// 3. 为每个存储创建或查找对应的 Drive,并同步内容
|
||||
for (Map<String, Object> storage : storages) {
|
||||
String storageId = String.valueOf(storage.get("id"));
|
||||
String mountPath = (String) storage.get("mount_path");
|
||||
String driver = (String) storage.get("driver");
|
||||
|
||||
log.info("用户 {} 同步存储: id={}, mountPath={}, driver={}", userId, storageId, mountPath, driver);
|
||||
|
||||
// 查找或创建该存储对应的 Drive
|
||||
Drive drive = findOrCreateDrive(userId, config, storageId, mountPath);
|
||||
if (drive == null) {
|
||||
result.errors.add("创建存储 " + mountPath + " 的网盘失败");
|
||||
continue;
|
||||
}
|
||||
result.driveCount++;
|
||||
|
||||
// 从该存储的挂载路径开始同步
|
||||
String rootPath = mountPath != null ? mountPath : "/";
|
||||
syncDirectory(userId, drive.getId(), rootPath, 0L, 0, result);
|
||||
|
||||
// 4. 更新网盘统计信息
|
||||
int totalFolders = folderRepository.countByDrive(drive.getId(), userId);
|
||||
int totalMovies = movieRepository.countByDrive(drive.getId(), userId);
|
||||
driveRepository.updateStats(drive.getId(), totalFolders, totalMovies, 0L);
|
||||
// 更新该网盘的统计信息
|
||||
updateDriveStats(drive.getId(), userId);
|
||||
}
|
||||
|
||||
log.info("用户 {} 同步完成: 网盘={}, 文件夹={}, 电影={}, 跳过文件={}, 跳过文件夹={}, 错误={}",
|
||||
userId, drive.getName(), result.folderCount, result.movieCount,
|
||||
userId, result.driveCount, result.folderCount, result.movieCount,
|
||||
result.skippedFileCount, result.skippedFolderCount, result.errors.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找或创建关联 OpenList 的网盘
|
||||
* 如果用户已有 openlistMountPath 不为空的网盘,则复用;否则创建新的
|
||||
* 更新网盘统计信息
|
||||
*/
|
||||
private Drive findOrCreateDrive(Long userId, UserOpenlist config) {
|
||||
// 查询用户所有网盘,查找已关联 OpenList 的
|
||||
private void updateDriveStats(Long driveId, Long userId) {
|
||||
int totalFolders = folderRepository.countByDrive(driveId, userId);
|
||||
int totalMovies = movieRepository.countByDrive(driveId, userId);
|
||||
driveRepository.updateStats(driveId, totalFolders, totalMovies, 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找或创建关联网盘
|
||||
* 根据 storageId 查找已有的 Drive,如果没有则创建新的
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param config OpenList 配置
|
||||
* @param storageId OpenList 存储ID(可为 null)
|
||||
* @param mountPath 挂载路径(可为 null)
|
||||
* @return Drive 对象
|
||||
*/
|
||||
private Drive findOrCreateDrive(Long userId, UserOpenlist config, String storageId, String mountPath) {
|
||||
// 查询用户所有网盘,查找匹配 storageId 的
|
||||
var drives = driveRepository.queryByUserId(userId, 1, 100);
|
||||
for (var driveMap : drives) {
|
||||
String mountPath = (String) driveMap.get("openlistMountPath");
|
||||
if (mountPath != null && !mountPath.isBlank()) {
|
||||
String existingStorageId = (String) driveMap.get("openlistStorageId");
|
||||
if (storageId != null && storageId.equals(existingStorageId)) {
|
||||
// 复用已有网盘
|
||||
log.info("复用已有网盘: id={}, storageId={}", driveMap.get("id"), storageId);
|
||||
return driveRepository.findById(((Number) driveMap.get("id")).longValue());
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新网盘
|
||||
Drive drive = new Drive(userId, "OpenList 网盘", "从 OpenList 同步的网盘");
|
||||
drive.setOpenlistMountPath(config.getMountPath() != null ? config.getMountPath() : "/");
|
||||
String driveName = mountPath != null ? "OpenList: " + mountPath : "OpenList 网盘";
|
||||
Drive drive = new Drive(userId, driveName, "从 OpenList 同步的网盘");
|
||||
drive.setOpenlistStorageId(storageId);
|
||||
drive.setOpenlistMountPath(mountPath != null ? mountPath : "/");
|
||||
Long driveId = driveRepository.insert(drive);
|
||||
drive.setId(driveId);
|
||||
log.info("创建新网盘: id={}, storageId={}, mountPath={}", driveId, storageId, mountPath);
|
||||
return drive;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user