mirror of
https://github.com/CodePhiliaX/Chat2DB.git
synced 2025-07-30 19:22:58 +08:00
Support auto update
This commit is contained in:
@ -1,156 +0,0 @@
|
|||||||
package ai.chat2db.server.start.config.config;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import ai.chat2db.server.tools.common.util.ConfigUtils;
|
|
||||||
import cn.hutool.core.io.FileUtil;
|
|
||||||
import com.dtflys.forest.Forest;
|
|
||||||
import com.dtflys.forest.utils.TypeReference;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
public class AutomaticUpgrade {
|
|
||||||
|
|
||||||
private static final OkHttpClient client = new OkHttpClient();
|
|
||||||
|
|
||||||
private static final String VERSION_URL = "https://sqlgpt.cn/api/version.json";
|
|
||||||
|
|
||||||
@Scheduled(fixedRate = 3600000) // 每小时运行一次
|
|
||||||
public void checkVersionUpdates() {
|
|
||||||
String appPath = ConfigUtils.APP_PATH;
|
|
||||||
|
|
||||||
log.info("appPath: {}", appPath);
|
|
||||||
if (StringUtils.isBlank(appPath) || !appPath.contains("app")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Upgrade dataResult = getUpgrade();
|
|
||||||
|
|
||||||
String oldVersion = ConfigUtils.getLocalVersion();
|
|
||||||
|
|
||||||
if (!needUpdate(dataResult, oldVersion)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File versionFile = new File(
|
|
||||||
appPath + File.separator + "versions" + File.separator + dataResult.getVersion());
|
|
||||||
if (!versionFile.exists()) {
|
|
||||||
versionFile.mkdir();
|
|
||||||
}
|
|
||||||
File oldLib = new File(
|
|
||||||
appPath + File.separator + "versions" + File.separator + oldVersion + File.separator + "static"
|
|
||||||
+ File.separator + "lib");
|
|
||||||
|
|
||||||
File newLib = new File(
|
|
||||||
appPath + File.separator + "versions" + File.separator + dataResult.getVersion() + File.separator
|
|
||||||
+ "static");
|
|
||||||
|
|
||||||
if (oldLib.exists()) {
|
|
||||||
FileUtil.copy(oldLib, newLib, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map<String, String> downloadFile : dataResult.getDownloadFiles()) {
|
|
||||||
downloadUpgrade(downloadFile, versionFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigUtils.updateVersion(dataResult.getVersion());
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("checkVersionUpdates error", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadUpgrade(Map<String, String> downloadFile, File versionFile) throws IOException {
|
|
||||||
String url = downloadFile.get("url");
|
|
||||||
String fileName = downloadFile.get("fileName");
|
|
||||||
String[] paths = fileName.split("/");
|
|
||||||
String filePath = versionFile.getPath();
|
|
||||||
for (int i = 0; i < paths.length; i++) {
|
|
||||||
filePath = filePath + File.separator + paths[i];
|
|
||||||
if (i < paths.length - 1) {
|
|
||||||
File file = new File(filePath);
|
|
||||||
if (!file.exists()) {
|
|
||||||
file.mkdir();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
download(url, filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find upgrade files
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private Upgrade getUpgrade() {
|
|
||||||
return Forest.get(VERSION_URL)
|
|
||||||
.connectTimeout(Duration.ofMillis(5000))
|
|
||||||
.readTimeout(Duration.ofMillis(10000))
|
|
||||||
.execute(new TypeReference<>() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean needUpdate(Upgrade upgrade, String localVersion) {
|
|
||||||
if (upgrade == null || StringUtils.isBlank(upgrade.getVersion()) || StringUtils.isBlank(localVersion)
|
|
||||||
|| upgrade.getVersion().equals(localVersion)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String[] versionArray = upgrade.getVersion().split("\\.");
|
|
||||||
String[] localVersionArray = localVersion.split("\\.");
|
|
||||||
for (int i = 0; i < versionArray.length; i++) {
|
|
||||||
if (Long.parseLong(versionArray[i]) < Long.parseLong(localVersionArray[i])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (Long.parseLong(versionArray[i]) > Long.parseLong(localVersionArray[i])) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void download(String url, String outputPath) throws IOException {
|
|
||||||
File file = new File(outputPath);
|
|
||||||
if (file.exists()) {
|
|
||||||
file.delete();
|
|
||||||
}
|
|
||||||
Request request = new Request.Builder().url(url).build();
|
|
||||||
try (Response response = client.newCall(request).execute()) {
|
|
||||||
if (!response.isSuccessful()) {
|
|
||||||
throw new IOException("Unexpected code " + response);
|
|
||||||
}
|
|
||||||
try (InputStream is = response.body().byteStream(); FileOutputStream fos = new FileOutputStream(
|
|
||||||
outputPath)) {
|
|
||||||
|
|
||||||
byte[] buffer = new byte[2048];
|
|
||||||
int length;
|
|
||||||
while ((length = is.read(buffer)) != -1) {
|
|
||||||
fos.write(buffer, 0, length);
|
|
||||||
}
|
|
||||||
fos.flush();
|
|
||||||
}
|
|
||||||
// System.out.println("File downloaded: " + outputPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class Upgrade implements Serializable {
|
|
||||||
|
|
||||||
private String version;
|
|
||||||
|
|
||||||
private List<Map<String, String>> downloadFiles;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package ai.chat2db.server.start.config.listener;
|
package ai.chat2db.server.start.config.listener;
|
||||||
|
|
||||||
import ai.chat2db.server.start.config.util.SystemUtils;
|
import ai.chat2db.server.web.api.controller.system.util.SystemUtils;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.catalina.LifecycleState;
|
import org.apache.catalina.LifecycleState;
|
||||||
import org.apache.catalina.connector.Connector;
|
import org.apache.catalina.connector.Connector;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package ai.chat2db.server.start.config.listener;
|
package ai.chat2db.server.start.config.listener;
|
||||||
|
|
||||||
import ai.chat2db.server.start.config.util.SystemUtils;
|
import ai.chat2db.server.web.api.controller.system.util.SystemUtils;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.context.event.ApplicationFailedEvent;
|
import org.springframework.boot.context.event.ApplicationFailedEvent;
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package ai.chat2db.server.start.config.util;
|
|
||||||
|
|
||||||
|
|
||||||
import ai.chat2db.spi.ssh.SSHManager;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统工具包
|
|
||||||
*
|
|
||||||
* @author Jiaju Zhuang
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class SystemUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止当前应用
|
|
||||||
*/
|
|
||||||
public static void stop() {
|
|
||||||
new Thread(() -> {
|
|
||||||
log.info("1秒以后退出应用");
|
|
||||||
// 1秒以后自动退出应用
|
|
||||||
try {
|
|
||||||
Thread.sleep(1000L);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
// 直接系统退出
|
|
||||||
log.info("开始退出系统应用");
|
|
||||||
SSHManager.close();
|
|
||||||
try {
|
|
||||||
System.exit(0);
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,21 @@
|
|||||||
|
package ai.chat2db.server.web.api.controller.system;
|
||||||
|
|
||||||
|
import ai.chat2db.server.tools.common.util.ConfigUtils;
|
||||||
|
import ai.chat2db.server.web.api.controller.system.util.SystemUtils;
|
||||||
|
import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class AutomaticUpgrade {
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 3600000) // 每小时运行一次
|
||||||
|
public void checkVersionUpdates() {
|
||||||
|
AppVersionVO appVersion = SystemUtils.getLatestVersion(ConfigUtils.getLocalVersion(), "auto", "");
|
||||||
|
if (appVersion != null) {
|
||||||
|
SystemUtils.upgrade(appVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,20 +4,28 @@
|
|||||||
*/
|
*/
|
||||||
package ai.chat2db.server.web.api.controller.system;
|
package ai.chat2db.server.web.api.controller.system;
|
||||||
|
|
||||||
|
import ai.chat2db.server.domain.api.model.Config;
|
||||||
|
import ai.chat2db.server.domain.api.service.ConfigService;
|
||||||
import ai.chat2db.server.domain.core.cache.CacheManage;
|
import ai.chat2db.server.domain.core.cache.CacheManage;
|
||||||
import ai.chat2db.server.tools.base.wrapper.result.DataResult;
|
import ai.chat2db.server.tools.base.wrapper.result.DataResult;
|
||||||
import ai.chat2db.server.tools.common.config.Chat2dbProperties;
|
import ai.chat2db.server.tools.common.config.Chat2dbProperties;
|
||||||
|
import ai.chat2db.server.tools.common.enums.ModeEnum;
|
||||||
import ai.chat2db.server.tools.common.model.ConfigJson;
|
import ai.chat2db.server.tools.common.model.ConfigJson;
|
||||||
import ai.chat2db.server.tools.common.util.ConfigUtils;
|
import ai.chat2db.server.tools.common.util.ConfigUtils;
|
||||||
|
import ai.chat2db.server.tools.common.util.EasyEnumUtils;
|
||||||
|
import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient;
|
||||||
|
import ai.chat2db.server.web.api.controller.system.util.SystemUtils;
|
||||||
|
import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO;
|
||||||
import ai.chat2db.server.web.api.controller.system.vo.SystemVO;
|
import ai.chat2db.server.web.api.controller.system.vo.SystemVO;
|
||||||
|
import ai.chat2db.server.web.api.util.ApplicationContextUtil;
|
||||||
import ai.chat2db.spi.ssh.SSHManager;
|
import ai.chat2db.spi.ssh.SSHManager;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author jipengfei
|
* @author jipengfei
|
||||||
@ -43,10 +51,42 @@ public class SystemController {
|
|||||||
public DataResult<SystemVO> get() {
|
public DataResult<SystemVO> get() {
|
||||||
ConfigJson configJson = ConfigUtils.getConfig();
|
ConfigJson configJson = ConfigUtils.getConfig();
|
||||||
return DataResult.of(SystemVO.builder()
|
return DataResult.of(SystemVO.builder()
|
||||||
.systemUuid(configJson.getSystemUuid())
|
.systemUuid(configJson.getSystemUuid())
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/get_latest_version")
|
||||||
|
public DataResult<AppVersionVO> getLatestVersion(String currentVersion) {
|
||||||
|
ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class);
|
||||||
|
Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData();
|
||||||
|
AppVersionVO appVersionVO = SystemUtils.getLatestVersion(currentVersion, "manual", keyConfig.getContent());
|
||||||
|
ModeEnum mode = EasyEnumUtils.getEnum(ModeEnum.class, System.getProperty("chat2db.mode"));
|
||||||
|
if (mode == ModeEnum.DESKTOP) {
|
||||||
|
// In this mode, no user login is required, so only local access is available
|
||||||
|
appVersionVO.setDesktop(true);
|
||||||
|
}
|
||||||
|
return DataResult.of(appVersionVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/update_desktop_version")
|
||||||
|
public DataResult<String> updateDesktopVersion(@RequestBody AppVersionVO version) {
|
||||||
|
new Thread(() -> {
|
||||||
|
SystemUtils.upgrade(version);
|
||||||
|
}).start();
|
||||||
|
return DataResult.of(version.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/is_update_success")
|
||||||
|
public DataResult<Boolean> isUpdateSuccess(String version) {
|
||||||
|
String localVersion = ConfigUtils.getLocalVersion();
|
||||||
|
if (StringUtils.isEmpty(localVersion)) {
|
||||||
|
return DataResult.of(false);
|
||||||
|
}
|
||||||
|
return DataResult.of(localVersion.equals(version));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前版本号
|
* 获取当前版本号
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
package ai.chat2db.server.web.api.controller.system.util;
|
||||||
|
|
||||||
|
|
||||||
|
import ai.chat2db.server.tools.base.wrapper.result.DataResult;
|
||||||
|
import ai.chat2db.server.tools.common.util.ConfigUtils;
|
||||||
|
import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO;
|
||||||
|
import ai.chat2db.spi.ssh.SSHManager;
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.util.ZipUtil;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import com.dtflys.forest.Forest;
|
||||||
|
import com.dtflys.forest.utils.TypeReference;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统工具包
|
||||||
|
*
|
||||||
|
* @author Jiaju Zhuang
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class SystemUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止当前应用
|
||||||
|
*/
|
||||||
|
public static void stop() {
|
||||||
|
new Thread(() -> {
|
||||||
|
log.info("1秒以后退出应用");
|
||||||
|
// 1秒以后自动退出应用
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000L);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
// 直接系统退出
|
||||||
|
log.info("开始退出系统应用");
|
||||||
|
SSHManager.close();
|
||||||
|
try {
|
||||||
|
System.exit(0);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
private static final String VERSION_URL = "https://sqlgpt.cn/api/version.json";
|
||||||
|
|
||||||
|
private static final String ZIP_FILE_PATH = ConfigUtils.APP_PATH + File.separator + "versions" + File.separator;
|
||||||
|
|
||||||
|
public static void upgrade(AppVersionVO appVersion) {
|
||||||
|
|
||||||
|
String appPath = ConfigUtils.APP_PATH;
|
||||||
|
|
||||||
|
log.info("appPath: {}", appPath);
|
||||||
|
if (StringUtils.isBlank(appPath) || !appPath.contains("app")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String zipPath = ZIP_FILE_PATH + appVersion.getVersion() + ".zip";
|
||||||
|
|
||||||
|
HttpUtil.downloadFile(appVersion.getHotUpgradeUrl(), ZIP_FILE_PATH + appVersion.getVersion() + ".zip");
|
||||||
|
|
||||||
|
ZipUtil.unzip(zipPath);
|
||||||
|
|
||||||
|
FileUtil.del(zipPath);
|
||||||
|
|
||||||
|
ConfigUtils.updateVersion(appVersion.getVersion());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("checkVersionUpdates error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String LATEST_VERSION_URL = "https://test.sqlgpt.cn/api/client/version/check/v3?version=%s&type=wechat&userId=%s";
|
||||||
|
|
||||||
|
public static AppVersionVO getLatestVersion(String version, String type, String userId) {
|
||||||
|
String url = String.format(LATEST_VERSION_URL, version, type, userId);
|
||||||
|
DataResult<AppVersionVO> result = Forest.get(url)
|
||||||
|
.connectTimeout(Duration.ofMillis(5000))
|
||||||
|
.readTimeout(Duration.ofMillis(10000))
|
||||||
|
.execute(new TypeReference<>() {
|
||||||
|
});
|
||||||
|
return result.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package ai.chat2db.server.web.api.controller.system.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class AppVersionVO {
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime gmtCreate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime gmtModified;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新版本
|
||||||
|
*/
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 哪些版本可以升级到该版本
|
||||||
|
*/
|
||||||
|
private String versionUse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载地址
|
||||||
|
*/
|
||||||
|
private String downloadLink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手工更新,自动强制更新
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 白名单,用于测试
|
||||||
|
*/
|
||||||
|
private String whiteList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 热更新包地址
|
||||||
|
*/
|
||||||
|
private String hotUpgradeUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新日志
|
||||||
|
*/
|
||||||
|
private String updateLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 桌面
|
||||||
|
*/
|
||||||
|
private boolean desktop;
|
||||||
|
}
|
Reference in New Issue
Block a user