Support auto update

This commit is contained in:
SwallowGG
2023-10-20 16:17:09 +08:00
parent 03ded257bc
commit 4a3f89e1f5
8 changed files with 227 additions and 201 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
/** /**
* 获取当前版本号 * 获取当前版本号
* *

View File

@ -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();
}
}

View File

@ -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;
}