rename com.imyeyu.server to com.imyeyu.api
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.api.TimiServerAPI;
|
||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import com.imyeyu.utils.Time;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 抽象镜像同步
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-05-23 14:21
|
||||
*/
|
||||
@Slf4j
|
||||
abstract class AbstractMirror {
|
||||
|
||||
/**
|
||||
* 镜像同步状态
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-05-23 14:27
|
||||
*/
|
||||
public enum Status {
|
||||
|
||||
/** 空闲 */
|
||||
IDLE,
|
||||
|
||||
/** 正在同步 */
|
||||
SYNCING,
|
||||
|
||||
/** 成功 */
|
||||
SUCCESSFUL,
|
||||
|
||||
/** 同步失败 */
|
||||
FAIL
|
||||
}
|
||||
|
||||
/** 上一次同步时间 */
|
||||
long lastSyncAt = -1;
|
||||
|
||||
/** 同步状态 */
|
||||
Status status = Status.IDLE;
|
||||
|
||||
HttpHost proxy = null;
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param mirror 镜像
|
||||
*/
|
||||
final void initialize(Mirror mirror) {
|
||||
status = Status.IDLE;
|
||||
}
|
||||
|
||||
/** @return true 为已初始化 */
|
||||
final boolean isInitialized() {
|
||||
return status != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发同步,由 {@link MirrorSyncTask} 调用
|
||||
*
|
||||
* @param mirror 镜像
|
||||
*/
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
final void sync0(Mirror mirror) {
|
||||
long startAt = lastSyncAt = Time.now();
|
||||
status = Status.SYNCING;
|
||||
try {
|
||||
// 预备同步
|
||||
beforeSync(mirror);
|
||||
|
||||
// 同步
|
||||
sync(mirror);
|
||||
|
||||
// 成功
|
||||
status = Status.SUCCESSFUL;
|
||||
MirrorService service = TimiServerAPI.applicationContext.getBean(MirrorService.class);
|
||||
Mirror dbMirror = service.get(mirror.getId());
|
||||
dbMirror.setLastSyncAt(Time.now());
|
||||
service.update(dbMirror);
|
||||
|
||||
// 同步成功
|
||||
status = Status.SUCCESSFUL;
|
||||
onSuccessful(mirror);
|
||||
} catch (Exception e) {
|
||||
status = Status.FAIL;
|
||||
|
||||
onException(mirror, e);
|
||||
|
||||
if (e instanceof TimiException te) {
|
||||
log.warn("[%s] Fail: %s".formatted(mirror.getBean(), te.getMsg()));
|
||||
} else {
|
||||
log.error("[%s] Error".formatted(mirror.getBean()), e);
|
||||
}
|
||||
} finally {
|
||||
onFinally(mirror);
|
||||
|
||||
String usedTime = Time.Media.toString(Time.now() - startAt);
|
||||
log.info("[{}] synced {} in {}", mirror.getBean(), status, usedTime);
|
||||
|
||||
if (status == Status.SUCCESSFUL) {
|
||||
status = Status.IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预备同步
|
||||
*
|
||||
* @param mirror 镜像
|
||||
* @throws Exception 准备异常
|
||||
*/
|
||||
protected void beforeSync(Mirror mirror) throws Exception {
|
||||
// 子类实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步,子类实现
|
||||
*
|
||||
* @param mirror 镜像
|
||||
* @throws Exception 同步异常
|
||||
*/
|
||||
protected abstract void sync(Mirror mirror) throws Exception;
|
||||
|
||||
/**
|
||||
* 同步成功
|
||||
*
|
||||
* @param mirror 镜像
|
||||
*/
|
||||
protected void onSuccessful(Mirror mirror) {
|
||||
// 子类实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步失败
|
||||
*
|
||||
* @param mirror 镜像
|
||||
* @param e 异常
|
||||
*/
|
||||
protected void onException(Mirror mirror, Exception e) {
|
||||
// 子类实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 最终执行,触发同步后无论同步结果如何都触发此方法
|
||||
*
|
||||
* @param mirror 镜像
|
||||
*/
|
||||
protected void onFinally(Mirror mirror) {
|
||||
// 子类实现
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.imyeyu.api.TimiServerAPI;
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 附件镜像,镜像同步器储存镜像文件时使用此抽象镜像
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-06-07 23:35
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
public abstract class AttachmentMirror extends AbstractMirror {
|
||||
|
||||
/** 同步结果:新增数量 */
|
||||
protected int syncAdded = 0;
|
||||
|
||||
/** 同步结果:移除数量 */
|
||||
protected int syncRemoved = 0;
|
||||
|
||||
/** 为 true 时同步产生新增或移除更新 */
|
||||
protected boolean hasUpdated = false;
|
||||
|
||||
@Override
|
||||
protected void beforeSync(Mirror mirror) throws Exception {
|
||||
syncAdded = syncRemoved = 0;
|
||||
hasUpdated = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 差分新增附件
|
||||
*
|
||||
* @param dbFiles 当前数据库附件
|
||||
* @return 新增附件
|
||||
* @throws Exception 处理异常
|
||||
*/
|
||||
protected abstract List<AttachmentRequest> diffAdd(List<Attachment> dbFiles) throws Exception;
|
||||
|
||||
/**
|
||||
* 差分移除附件
|
||||
*
|
||||
* @param dbFiles 当前数据库附件
|
||||
* @return 移除附件
|
||||
* @throws Exception 处理异常
|
||||
*/
|
||||
protected abstract List<Attachment> diffRemove(List<Attachment> dbFiles) throws Exception;
|
||||
|
||||
/**
|
||||
* 同步,<span style="color: #F30" >子类复现务必继续调用父类方法,保留 super.sync(mirror)</span>,否则附件差分同步无效
|
||||
*
|
||||
* @param mirror 镜像
|
||||
* @throws Exception 同步异常
|
||||
*/
|
||||
@Override
|
||||
protected void sync(Mirror mirror) throws Exception {
|
||||
AttachmentService attachmentService = TimiServerAPI.applicationContext.getBean(AttachmentService.class);
|
||||
List<Attachment> dbFiles = attachmentService.listByBizId(Attachment.BizType.MIRROR, mirror.getId());
|
||||
|
||||
List<Attachment> diffRemoveList = diffRemove(dbFiles);
|
||||
syncRemoved = diffRemoveList.size();
|
||||
for (int i = 0; i < diffRemoveList.size(); i++) {
|
||||
attachmentService.destroy(diffRemoveList.get(i).getId());
|
||||
}
|
||||
|
||||
List<AttachmentRequest> diffAddList = diffAdd(dbFiles);
|
||||
syncAdded = diffAddList.size();
|
||||
for (int i = 0; i < diffAddList.size(); i++) {
|
||||
AttachmentRequest request = diffAddList.get(i);
|
||||
request.setBizType(Attachment.BizType.MIRROR);
|
||||
request.setBizId(mirror.getId());
|
||||
attachmentService.create(request);
|
||||
}
|
||||
hasUpdated = syncAdded != 0 || syncRemoved != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSuccessful(Mirror mirror) {
|
||||
log.info("[{}] attachment synced, added: {}, removed: {}", mirror.getBean(), syncAdded, syncRemoved);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
|
||||
import com.imyeyu.api.modules.mirror.bean.AttachType;
|
||||
import com.imyeyu.api.modules.mirror.data.FabricAPI;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.client5.http.fluent.Request;
|
||||
import org.dom4j.Document;
|
||||
import org.dom4j.Element;
|
||||
import org.dom4j.io.SAXReader;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Minecraft FabricApi 模组镜像
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-05-23 14:41
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FabricAPIMirror extends AttachmentMirror {
|
||||
|
||||
/** 主域名 */
|
||||
private static final String FABRIC_DOMAIN = "https://maven.fabricmc.net/";
|
||||
|
||||
/** 模组列表 */
|
||||
private static final String API_LIST = FABRIC_DOMAIN + "/net/fabricmc/fabric-api/fabric-api/maven-metadata.xml";
|
||||
|
||||
/** 下载地址 */
|
||||
private static final String API_DOWNLOAD = "https://github.com/FabricMC/fabric/releases/download/%s/fabric-api-%s.jar";
|
||||
|
||||
/** 版本匹配正则 */
|
||||
private static final Pattern versionRegex = Pattern.compile("^(\\d+\\.){1,2}(\\*|\\d+)$");
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
private final MirrorService service;
|
||||
private final AttachmentService attachmentService;
|
||||
|
||||
/** 游戏版本: Fabric 版本 */
|
||||
private final Map<String, String> versionMap = new HashMap<>();
|
||||
|
||||
@Override
|
||||
protected void beforeSync(Mirror mirror) throws Exception {
|
||||
super.beforeSync(mirror);
|
||||
|
||||
Document dom = new SAXReader().read(API_LIST);
|
||||
Element root = dom.getRootElement();
|
||||
Element versioning = root.element("versioning");
|
||||
Element versions = versioning.element("versions");
|
||||
List<?> versionList = versions.elements("version");
|
||||
|
||||
versionMap.clear();
|
||||
for (int i = 0; i < versionList.size(); i++) {
|
||||
if (versionList.get(i) instanceof Element el) {
|
||||
String[] args = el.getTextTrim().split("\\+");
|
||||
if (versionRegex.matcher(args[1]).matches()) {
|
||||
versionMap.put(args[1], args[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sync(Mirror mirror) throws Exception {
|
||||
super.sync(mirror);
|
||||
|
||||
if (!hasUpdated) {
|
||||
return;
|
||||
}
|
||||
List<FabricAPI> result = new ArrayList<>();
|
||||
{
|
||||
List<Attachment> attachmentList = attachmentService.listByBizId(Attachment.BizType.MIRROR, mirror.getId(), AttachType.FABRIC_API);
|
||||
for (int i = 0; i < attachmentList.size(); i++) {
|
||||
Attachment attachment = attachmentList.get(i);
|
||||
// 附件名:fabric-api-(fabricVer)+(minecraftVer).jar
|
||||
String[] args = attachment.getName().split("-")[2].replaceAll("\\.jar", "").split("\\+");
|
||||
|
||||
FabricAPI item = new FabricAPI();
|
||||
item.setName(attachment.getName());
|
||||
item.setFabricVer(args[0]);
|
||||
item.setMinecraftVer(args[1]);
|
||||
item.setMongoId(attachment.getMongoId());
|
||||
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
mirror.setData(gson.toJsonTree(result));
|
||||
service.update(mirror);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AttachmentRequest> diffAdd(List<Attachment> dbFiles) throws Exception {
|
||||
Set<String> dbNameSet = dbFiles.stream().map(Attachment::getName).collect(Collectors.toSet());
|
||||
|
||||
List<AttachmentRequest> result = new ArrayList<>();
|
||||
for (Map.Entry<String, String> item : versionMap.entrySet()) {
|
||||
String version = "%s+%s".formatted(item.getValue(), item.getKey());
|
||||
String name = "fabric-api-%s.jar".formatted(version);
|
||||
if (!dbNameSet.contains(name)) {
|
||||
String url = API_DOWNLOAD.formatted(version, version);
|
||||
log.info("Syncing a new fabric-api from {}", url);
|
||||
|
||||
byte[] bytes = Request.get(url).viaProxy(proxy).execute().returnContent().asBytes();
|
||||
|
||||
AttachmentRequest attachment = new AttachmentRequest();
|
||||
attachment.setAttachTypeValue(AttachType.FABRIC_API);
|
||||
attachment.setName(name);
|
||||
attachment.setSize((long) bytes.length);
|
||||
attachment.setInputStream(new ByteArrayInputStream(bytes));
|
||||
result.add(attachment);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Attachment> diffRemove(List<Attachment> dbFiles) {
|
||||
List<String> versionList = new ArrayList<>();
|
||||
for (Map.Entry<String, String> item : versionMap.entrySet()) {
|
||||
String version = "%s+%s".formatted(item.getValue(), item.getKey());
|
||||
versionList.add("fabric-api-%s.jar".formatted(version));
|
||||
}
|
||||
List<Attachment> result = new ArrayList<>();
|
||||
for (int i = 0; i < dbFiles.size(); i++) {
|
||||
Attachment attachment = dbFiles.get(i);
|
||||
if (!versionList.contains(attachment.getName())) {
|
||||
log.info("Syncing a miss fabric-api for {}", attachment.getName());
|
||||
result.add(attachment);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.api.TimiServerAPI;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import com.imyeyu.utils.Time;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.scheduling.support.CronTrigger;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 镜像同步任务触发器
|
||||
* <p>触发器周期为每分钟轮询一次,镜像同步周期由 {@link Mirror#setPeriod(int)} 决定
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-05-23 14:22
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MirrorSyncTask implements SchedulingConfigurer, TimiJava {
|
||||
|
||||
private final MirrorService service;
|
||||
private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.addTriggerTask(() -> {
|
||||
List<Mirror> mirrors = service.listAll();
|
||||
long now = Time.now();
|
||||
for (int i = 0; i < mirrors.size(); i++) {
|
||||
Mirror dbMirror = mirrors.get(i);
|
||||
if (!dbMirror.isEnable()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Object bean = TimiServerAPI.applicationContext.getBean(Class.forName(dbMirror.getBean()));
|
||||
if (bean instanceof AbstractMirror mirror) {
|
||||
if (!mirror.isInitialized()) {
|
||||
mirror.initialize(dbMirror);
|
||||
}
|
||||
if (now - mirror.lastSyncAt < dbMirror.getPeriod() * Time.M) {
|
||||
continue;
|
||||
}
|
||||
switch (mirror.status) {
|
||||
case IDLE -> {
|
||||
log.info("[{}] Starting..", dbMirror.getBean());
|
||||
threadPoolTaskExecutor.execute(() -> mirror.sync0(dbMirror));
|
||||
}
|
||||
case SYNCING -> log.warn("[%s] is syncing, skipped in this period. This task maybe need more time to finish work");
|
||||
case FAIL -> {
|
||||
log.info("[{}] Retrying..", dbMirror.getBean());
|
||||
threadPoolTaskExecutor.execute(() -> mirror.sync0(dbMirror));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.error("[%s] Initialization fail".formatted(dbMirror.getBean()), e);
|
||||
}
|
||||
}
|
||||
}, tc -> new CronTrigger("0 * * * * ?").nextExecution(tc));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.imyeyu.api.modules.mirror.data.OpenJDK;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import com.imyeyu.utils.OS;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.client5.http.fluent.Request;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Github JDK 镜像,仅同步下载链接等信息,不储存文件
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-06-10 10:51
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OpenJDKGithubMirror extends AbstractMirror {
|
||||
|
||||
/** 版本发布列表接口,插值 {@link #REPOS_MAP} 的值 */
|
||||
private static final String API_RELEASE = "https://api.github.com/repos/adoptium/%s/releases?page=1";
|
||||
|
||||
/** 版本仓库映射,key 为版本,value 为对应仓库,只读 */
|
||||
private static final Map<String, String> REPOS_MAP = Collections.unmodifiableMap(new HashMap<>() {{
|
||||
put("8", "temurin8-binaries");
|
||||
put("11", "temurin11-binaries");
|
||||
put("17", "temurin17-binaries");
|
||||
put("21", "temurin21-binaries");
|
||||
}});
|
||||
|
||||
private final Gson gson;
|
||||
private final MirrorService service;
|
||||
|
||||
@Override
|
||||
protected void sync(Mirror mirror) throws Exception {
|
||||
mirror.setData(gson.toJsonTree(fetch()));
|
||||
service.update(mirror);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link OpenJDK} 列表,{@link OpenJDKMirror} 也会使用此接口
|
||||
*
|
||||
* @return jdk 列表
|
||||
* @throws Exception 获取异常
|
||||
*/
|
||||
final List<OpenJDK> fetch() throws Exception {
|
||||
List<OpenJDK> result = new ArrayList<>();
|
||||
for (Map.Entry<String, String> repo : REPOS_MAP.entrySet()) {
|
||||
String respText = Request.get(API_RELEASE.formatted(repo.getValue())).connectTimeout(Timeout.ofSeconds(60)).execute().returnContent().asString();
|
||||
JsonArray root = JsonParser.parseString(respText).getAsJsonArray();
|
||||
JsonObject itemRel = null;
|
||||
for (JsonElement el : root) {
|
||||
itemRel = el.getAsJsonObject();
|
||||
if (itemRel.get("prerelease").getAsBoolean() || itemRel.get("draft").getAsBoolean()) {
|
||||
// 忽略草稿或预发布版本
|
||||
itemRel = null;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (itemRel == null) {
|
||||
throw new TimiException(TimiCode.ERROR, "not found release item for " + repo.getValue());
|
||||
}
|
||||
JsonArray assets = itemRel.get("assets").getAsJsonArray();
|
||||
for (JsonElement asset : assets) {
|
||||
JsonObject itemAsset = asset.getAsJsonObject();
|
||||
// OpenJDK21U-jdk_x64_windows_hotspot_21.0.3_9.zip
|
||||
String name = itemAsset.get("name").getAsString();
|
||||
|
||||
if (!name.contains("-") || !name.contains("_")) {
|
||||
continue;
|
||||
}
|
||||
if (!name.contains("x64")) {
|
||||
continue;
|
||||
}
|
||||
if (!name.endsWith(".zip") && !name.endsWith(".tar.gz")) {
|
||||
continue;
|
||||
}
|
||||
String[] split = name.split("-")[1].split("_");
|
||||
if (split.length < 3) {
|
||||
continue;
|
||||
}
|
||||
OS.Platform platform = Ref.toType(OS.Platform.class, split[2].toUpperCase());
|
||||
OpenJDK.Type type = Ref.toType(OpenJDK.Type.class, split[0].toUpperCase());
|
||||
|
||||
if (platform != null && type != null) {
|
||||
OpenJDK jdk = new OpenJDK();
|
||||
jdk.setPlatform(platform);
|
||||
jdk.setType(type);
|
||||
jdk.setName(name);
|
||||
jdk.setVersion(repo.getKey());
|
||||
jdk.setData(URLDecoder.decode(itemAsset.get("browser_download_url").getAsString(), StandardCharsets.UTF_8));
|
||||
result.add(jdk);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
|
||||
import com.imyeyu.api.modules.mirror.bean.AttachType;
|
||||
import com.imyeyu.api.modules.mirror.data.OpenJDK;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.client5.http.fluent.Request;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 本地 JDK 镜像,使用 {@link OpenJDKGithubMirror} 镜像同步
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-06-11 10:15
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OpenJDKMirror extends AttachmentMirror {
|
||||
|
||||
private final Gson gson;
|
||||
private final MirrorService service;
|
||||
private final AttachmentService attachmentService;
|
||||
private final OpenJDKGithubMirror githubMirror;
|
||||
|
||||
/** Github 镜像结果 */
|
||||
private List<OpenJDK> githubMirrorResult;
|
||||
|
||||
@Override
|
||||
protected void beforeSync(Mirror mirror) throws Exception {
|
||||
super.beforeSync(mirror);
|
||||
|
||||
// 本地镜像来自 Github
|
||||
githubMirrorResult = githubMirror.fetch();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sync(Mirror mirror) throws Exception {
|
||||
super.sync(mirror);
|
||||
|
||||
if (!hasUpdated) {
|
||||
return;
|
||||
}
|
||||
Map<String, OpenJDK> githubNameMap = githubMirrorResult.stream().collect(Collectors.toMap(OpenJDK::getName, item -> item));
|
||||
List<OpenJDK> result = new ArrayList<>();
|
||||
{
|
||||
List<Attachment> attachmentList = attachmentService.listByBizId(Attachment.BizType.MIRROR, mirror.getId(), AttachType.OPEN_JDK);
|
||||
for (int i = 0; i < attachmentList.size(); i++) {
|
||||
Attachment attachment = attachmentList.get(i);
|
||||
OpenJDK jdk = githubNameMap.get(attachment.getName());
|
||||
jdk.setData(attachment.getMongoId());
|
||||
result.add(jdk);
|
||||
}
|
||||
}
|
||||
mirror.setData(gson.toJsonTree(result));
|
||||
service.update(mirror);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AttachmentRequest> diffAdd(List<Attachment> dbFiles) throws Exception {
|
||||
Map<String, OpenJDK> githubNameMap = githubMirrorResult.stream().collect(Collectors.toMap(OpenJDK::getName, item -> item));
|
||||
Set<String> dbNameSet = dbFiles.stream().map(Attachment::getName).collect(Collectors.toSet());
|
||||
|
||||
List<AttachmentRequest> result = new ArrayList<>();
|
||||
for (Map.Entry<String, OpenJDK> item : githubNameMap.entrySet()) {
|
||||
if (!dbNameSet.contains(item.getKey())) {
|
||||
String url = item.getValue().getData();
|
||||
log.info("Syncing a new open-jdk from {}", url);
|
||||
|
||||
byte[] bytes = Request.get(url).viaProxy(proxy).execute().returnContent().asBytes();
|
||||
|
||||
AttachmentRequest attachment = new AttachmentRequest();
|
||||
attachment.setAttachTypeValue(AttachType.OPEN_JDK);
|
||||
attachment.setName(item.getKey());
|
||||
attachment.setSize((long) bytes.length);
|
||||
attachment.setInputStream(new ByteArrayInputStream(bytes));
|
||||
result.add(attachment);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Attachment> diffRemove(List<Attachment> dbFiles) {
|
||||
Set<String> githubNameSet = githubMirrorResult.stream().map(OpenJDK::getName).collect(Collectors.toSet());
|
||||
|
||||
List<Attachment> result = new ArrayList<>();
|
||||
for (int i = 0; i < dbFiles.size(); i++) {
|
||||
Attachment attachment = dbFiles.get(i);
|
||||
if (!githubNameSet.contains(attachment.getName())) {
|
||||
log.info("Syncing a miss open-jdk for {}", attachment.getName());
|
||||
result.add(attachment);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.imyeyu.api.modules.mirror.data.OpenJDK;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import com.imyeyu.utils.OS;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OpenJDK 清华大学源镜像,仅同步下载链接,不储存文件
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-06-11 10:48
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OpenJDKTunaMirror extends AbstractMirror {
|
||||
|
||||
/**
|
||||
* 页面地址模板,插值参数(小写)
|
||||
* <ol>
|
||||
* <li>{@link #VERSIONS}</li>
|
||||
* <li>{@link OpenJDK.Type}</li>
|
||||
* <li>{@link com.imyeyu.utils.OS.Platform}</li>
|
||||
* </ol>
|
||||
*
|
||||
*/
|
||||
private static final String PAGE_URL_TEMPLATE = "https://mirrors.tuna.tsinghua.edu.cn/Adoptium/%s/%s/x64/%s/";
|
||||
|
||||
/** 版本列表 */
|
||||
private static final String[] VERSIONS = {"8", "11", "17", "21"};
|
||||
|
||||
private final Gson gson;
|
||||
private final MirrorService service;
|
||||
|
||||
@Override
|
||||
protected void sync(Mirror mirror) throws Exception {
|
||||
List<OpenJDK> result = new ArrayList<>();
|
||||
for (int i = 0; i < VERSIONS.length; i++) {
|
||||
OpenJDK.Type[] types = OpenJDK.Type.values();
|
||||
for (int j = 0; j < types.length; j++) {
|
||||
OS.Platform[] platforms = OS.Platform.values();
|
||||
for (int k = 0; k < platforms.length; k++) {
|
||||
String url = PAGE_URL_TEMPLATE.formatted(VERSIONS[i], types[j].toString().toLowerCase(), platforms[k].toString().toLowerCase());
|
||||
Document document = Jsoup.connect(url).get();
|
||||
Element fileList = document.getElementById("list");
|
||||
Elements linkTDList = fileList.getElementsByClass("link");
|
||||
for (Element element : linkTDList) {
|
||||
Elements linkA = element.getElementsByTag("a");
|
||||
for (Element a : linkA) {
|
||||
String href = a.attr("href");
|
||||
if (!href.endsWith(".zip") && !href.endsWith(".tar.gz")) {
|
||||
continue;
|
||||
}
|
||||
OpenJDK jdk = new OpenJDK();
|
||||
jdk.setPlatform(platforms[k]);
|
||||
jdk.setType(types[j]);
|
||||
jdk.setName(href);
|
||||
jdk.setVersion(VERSIONS[i]);
|
||||
jdk.setData(url + href);
|
||||
|
||||
result.add(jdk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mirror.setData(gson.toJsonTree(result));
|
||||
service.update(mirror);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.imyeyu.api.modules.mirror.bean;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @version 2024-05-23 16:54
|
||||
*/
|
||||
public enum AttachType {
|
||||
|
||||
FABRIC_API,
|
||||
|
||||
OPEN_JDK,
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.imyeyu.api.modules.mirror.controller;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import com.imyeyu.api.modules.mirror.vo.MirrorView;
|
||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 镜像接口
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-06-11 12:53
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/mirror")
|
||||
@RequiredArgsConstructor
|
||||
public class MirrorController {
|
||||
|
||||
private final MirrorService service;
|
||||
|
||||
/**
|
||||
* 获取镜像信息
|
||||
*
|
||||
* @param mirrorName 镜像名称
|
||||
* @return 镜像信息
|
||||
*/
|
||||
@RequestRateLimit
|
||||
@GetMapping("/{mirrorName}")
|
||||
public JsonElement get(@PathVariable String mirrorName) {
|
||||
return service.getByName(mirrorName).getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取镜像列表
|
||||
*
|
||||
* @return 镜像列表
|
||||
*/
|
||||
@RequestRateLimit
|
||||
@GetMapping("/list")
|
||||
public List<MirrorView> list() {
|
||||
List<MirrorView> result = new ArrayList<>();
|
||||
List<Mirror> mirrors = service.listAll();
|
||||
for (int i = 0; i < mirrors.size(); i++) {
|
||||
MirrorView target = new MirrorView();
|
||||
BeanUtils.copyProperties(mirrors.get(i), target);
|
||||
target.setBean(null);
|
||||
target.setData(null);
|
||||
result.add(target);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.imyeyu.api.modules.mirror.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* FabricAPI
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-06-08 00:03
|
||||
*/
|
||||
@Data
|
||||
public class FabricAPI {
|
||||
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** FabricAPI 版本 */
|
||||
private String fabricVer;
|
||||
|
||||
/** Minecraft 版本 */
|
||||
private String minecraftVer;
|
||||
|
||||
/** 映射文件 mongoId */
|
||||
private String mongoId;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.imyeyu.api.modules.mirror.data;
|
||||
|
||||
import lombok.Data;
|
||||
import com.imyeyu.utils.OS;
|
||||
|
||||
/**
|
||||
* OpenJDK
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-06-10 10:35
|
||||
*/
|
||||
@Data
|
||||
public class OpenJDK {
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-06-10 10:35
|
||||
*/
|
||||
public enum Type {
|
||||
|
||||
/** 集成开发工具 */
|
||||
JDK,
|
||||
|
||||
/** 运行时 */
|
||||
JRE
|
||||
}
|
||||
|
||||
/** 类型 */
|
||||
private Type type;
|
||||
|
||||
/** 平台 */
|
||||
private OS.Platform platform;
|
||||
|
||||
/** 版本 */
|
||||
private String version;
|
||||
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** 数据(可能是下载链接,可能是 mongoId) */
|
||||
private String data;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.imyeyu.api.modules.mirror.entity;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import com.imyeyu.spring.entity.Entity;
|
||||
|
||||
/**
|
||||
* 镜像
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-05-23 15:22
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Mirror extends Entity {
|
||||
|
||||
/** 执行 JavaBean */
|
||||
protected String bean;
|
||||
|
||||
/** 名称 */
|
||||
protected String name;
|
||||
|
||||
/** 同步数据 */
|
||||
protected JsonElement data;
|
||||
|
||||
/** 周期(分钟) */
|
||||
protected int period;
|
||||
|
||||
/** 上一次同步时间 */
|
||||
protected Long lastSyncAt;
|
||||
|
||||
/** true 为启用同步 */
|
||||
protected boolean isEnable;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.imyeyu.api.modules.mirror.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 镜像
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-05-23 15:21
|
||||
*/
|
||||
public interface MirrorMapper extends BaseMapper<Mirror, Long> {
|
||||
|
||||
@Select("SELECT * FROM mirror WHERE id = #{id} LIMIT 1")
|
||||
Mirror select(Long id);
|
||||
|
||||
@Update("UPDATE mirror SET data = #{data}, last_sync_at = #{lastSyncAt} WHERE id = #{id} LIMIT 1")
|
||||
void update(Mirror mirror);
|
||||
|
||||
@Select("SELECT * FROM mirror WHERE name = #{name} LIMIT 1")
|
||||
Mirror queryByName(String name);
|
||||
|
||||
@Select("SELECT * FROM mirror WHERE deleted_at IS NULL")
|
||||
List<Mirror> listAll();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.imyeyu.api.modules.mirror.service;
|
||||
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.spring.service.GettableService;
|
||||
import com.imyeyu.spring.service.UpdatableService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 镜像服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-05-23 15:21
|
||||
*/
|
||||
public interface MirrorService extends GettableService<Mirror, Long>, UpdatableService<Mirror> {
|
||||
|
||||
/**
|
||||
* 根据名称获取
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 镜像
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
Mirror getByName(String name) throws TimiException;
|
||||
|
||||
/**
|
||||
* 获取所有镜像
|
||||
*
|
||||
* @return 镜像列表
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
List<Mirror> listAll() throws TimiException;
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.imyeyu.api.modules.mirror.service.implement;
|
||||
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.mapper.MirrorMapper;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 镜像服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-05-23 15:22
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MirrorServiceImplement extends AbstractEntityService<Mirror, Long> implements MirrorService {
|
||||
|
||||
private final MirrorMapper mapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<Mirror, Long> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mirror getByName(String name) throws TimiException {
|
||||
return mapper.queryByName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Mirror> listAll() throws TimiException {
|
||||
return mapper.listAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.imyeyu.api.modules.mirror.vo;
|
||||
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
|
||||
/**
|
||||
* 镜像视图
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2024-06-12 10:20
|
||||
*/
|
||||
public class MirrorView extends Mirror {
|
||||
}
|
||||
Reference in New Issue
Block a user