From 58d40084fe2049bc3a6365984ad6329cc5ad6fd5 Mon Sep 17 00:00:00 2001 From: Camsyn <65994555+Camsyn@users.noreply.github.com> Date: Fri, 22 Apr 2022 23:47:16 +0800 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E6=9B=B4?= =?UTF-8?q?=E6=96=B0,=20issue#1675=201.=20=E4=BB=85=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=8E=A7=E5=88=B6=E7=9A=84=E5=90=8E=E7=AB=AF?= =?UTF-8?q?api=202.=20=E5=9F=BA=E4=BA=8Egithub=20api=E5=AE=9E=E7=8E=B0,=20?= =?UTF-8?q?=E5=BC=82=E6=AD=A5=E4=B8=8B=E8=BD=BD=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E7=9A=84jar=E5=8C=85=203.=20=E6=9E=84=E9=80=A0=E4=B8=8E?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=BD=93=E5=89=8Djar=E5=8C=85=E7=9B=B8?= =?UTF-8?q?=E4=BC=BC=E7=9A=84=E5=90=AF=E5=8A=A8=E5=91=BD=E4=BB=A4=E6=9D=A5?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=96=B0=E7=89=88=E6=9C=AChalo.jar=204.=20?= =?UTF-8?q?=E5=A4=87=E4=BB=BD=E5=B9=B6=E5=88=A0=E9=99=A4=E5=8E=9F=E7=89=88?= =?UTF-8?q?jar=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/api/VersionCtrlController.java | 82 ++++ .../halo/app/model/dto/VersionInfoDTO.java | 41 ++ .../run/halo/app/model/entity/Assets.java | 34 ++ .../run/halo/app/model/entity/Author.java | 45 ++ .../model/entity/GithubApiVersionJson.java | 53 +++ .../run/halo/app/model/entity/Reactions.java | 26 ++ .../run/halo/app/model/entity/Uploader.java | 45 ++ .../run/halo/app/model/enums/SystemType.java | 10 + .../app/service/HaloVersionCtrlService.java | 123 ++++++ .../impl/HaloVersionCtrlServiceImpl.java | 414 ++++++++++++++++++ src/main/java/run/halo/app/utils/VmUtils.java | 195 +++++++++ .../impl/HaloVersionCtrlServiceImplTest.java | 111 +++++ .../java/run/halo/app/utils/VmUtilsTest.java | 66 +++ 13 files changed, 1245 insertions(+) create mode 100644 src/main/java/run/halo/app/controller/admin/api/VersionCtrlController.java create mode 100644 src/main/java/run/halo/app/model/dto/VersionInfoDTO.java create mode 100644 src/main/java/run/halo/app/model/entity/Assets.java create mode 100644 src/main/java/run/halo/app/model/entity/Author.java create mode 100644 src/main/java/run/halo/app/model/entity/GithubApiVersionJson.java create mode 100644 src/main/java/run/halo/app/model/entity/Reactions.java create mode 100644 src/main/java/run/halo/app/model/entity/Uploader.java create mode 100644 src/main/java/run/halo/app/model/enums/SystemType.java create mode 100644 src/main/java/run/halo/app/service/HaloVersionCtrlService.java create mode 100644 src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java create mode 100644 src/main/java/run/halo/app/utils/VmUtils.java create mode 100644 src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java create mode 100644 src/test/java/run/halo/app/utils/VmUtilsTest.java diff --git a/src/main/java/run/halo/app/controller/admin/api/VersionCtrlController.java b/src/main/java/run/halo/app/controller/admin/api/VersionCtrlController.java new file mode 100644 index 0000000000..777c35f41f --- /dev/null +++ b/src/main/java/run/halo/app/controller/admin/api/VersionCtrlController.java @@ -0,0 +1,82 @@ +package run.halo.app.controller.admin.api; + +import io.swagger.annotations.ApiOperation; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +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 run.halo.app.model.dto.VersionInfoDTO; +import run.halo.app.service.HaloVersionCtrlService; + +@Slf4j +@RestController +@RequestMapping("/api/admin/version") +public class VersionCtrlController { + @Autowired + HaloVersionCtrlService versionCtrlService; + + @GetMapping("releases") + @ApiOperation("Lists all release info of halo jar") + List getAllReleaseInfo() { + return versionCtrlService.getAllReleasesInfo(); + } + + @GetMapping("releases/latest") + @ApiOperation("Lists the latest release info of halo") + VersionInfoDTO getLatestReleaseInfo() { + return versionCtrlService.getLatestReleaseInfo(); + } + + @GetMapping("releases/tags/{tagName:.+}") + @ApiOperation("Lists the release info with specified version") + VersionInfoDTO getReleaseInfo(@PathVariable(name = "tagName") String tagName) { + return versionCtrlService.getReleaseInfoByTag(tagName); + } + + @GetMapping("download/latest") + @ApiOperation("Downloads the latest release jar") + String downloadLatest() { + versionCtrlService.downloadLatestJar(); + return "success"; + } + + @GetMapping("download/tags/{tagName:.+}") + @ApiOperation("Downloads the specified jar") + String download(@PathVariable(name = "tagName") String tagName) { + versionCtrlService.downloadSpecifiedJarToRepo(tagName); + return "success"; + } + + @GetMapping("switch/latest") + @ApiOperation("Switch halo to latest version") + String switchLatestVersion() { + versionCtrlService.switchLatest(); + return "success"; + } + + @GetMapping("switch/tags/{tagName:.+}") + @ApiOperation("Switch halo to specified version") + String switchVersion(@PathVariable(name = "tagName") String tagName) { + versionCtrlService.switchVersion(tagName); + return "success"; + } + + @GetMapping("downloadswitch/latest") + @ApiOperation("Downloads latest version to local and switch halo to it") + String downloadSwitchLatestVersion() { + versionCtrlService.downloadAndSwitchLatest(); + return "success"; + } + + @GetMapping("downloadswitch/tags/{tagName:.+}") + @ApiOperation("Downloads specified version to local and switch halo to it") + String downLoadSwitchVersion(@PathVariable(name = "tagName") String tagName) { + versionCtrlService.downloadAndSwitch(tagName); + return "success"; + } + + +} diff --git a/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java b/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java new file mode 100644 index 0000000000..962c33c2e0 --- /dev/null +++ b/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java @@ -0,0 +1,41 @@ +package run.halo.app.model.dto; + +import lombok.Builder; +import lombok.Data; +import lombok.ToString; +import run.halo.app.model.entity.Assets; +import run.halo.app.model.entity.GithubApiVersionJson; + +/** + * Version information of a release. + * + *

This is a simplified representation of + * {@linkplain run.halo.app.model.entity.GithubApiVersionJson}. + * + * @author Chen_Kunqiu + */ +@Data +@ToString +@Builder +public class VersionInfoDTO { + private String version; + private String jarName; + private String desc; + private String githubUrl; + private String downloadUrl; + private Boolean inLocal; + private Long size; + + /** + * Initially convert the JSON given by Github into VO. + * + * @param json the json data given by github api + * @return the simplified VO object + */ + public static VersionInfoDTO convertFrom(GithubApiVersionJson json) { + final Assets asset = json.getAssets().get(0); + return VersionInfoDTO.builder().version(json.getTagName()).desc(json.getBody()) + .githubUrl(json.getHtmlUrl()).jarName(asset.getName()).size(asset.getSize()) + .downloadUrl(asset.getBrowserDownloadUrl()).build(); + } +} diff --git a/src/main/java/run/halo/app/model/entity/Assets.java b/src/main/java/run/halo/app/model/entity/Assets.java new file mode 100644 index 0000000000..d72ff3ef9e --- /dev/null +++ b/src/main/java/run/halo/app/model/entity/Assets.java @@ -0,0 +1,34 @@ + +package run.halo.app.model.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Date; +import lombok.Data; + +/** + * The Java class relevant to the Json returned by github api. + * + * @author Chen_Kunqiu + */ +@Data +public class Assets { + private String url; + private int id; + @JsonProperty("node_id") + private String nodeId; + private String name; + private String label; + private Uploader uploader; + @JsonProperty("content_type") + private String contentType; + private String state; + private long size; + @JsonProperty("download_count") + private int downloadCount; + @JsonProperty("created_at") + private Date createdAt; + @JsonProperty("updated_at") + private Date updatedAt; + @JsonProperty("browser_download_url") + private String browserDownloadUrl; +} \ No newline at end of file diff --git a/src/main/java/run/halo/app/model/entity/Author.java b/src/main/java/run/halo/app/model/entity/Author.java new file mode 100644 index 0000000000..61a592f3b0 --- /dev/null +++ b/src/main/java/run/halo/app/model/entity/Author.java @@ -0,0 +1,45 @@ +package run.halo.app.model.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * The Java class relevant to the Json returned by github api. + * + * @author Chen_Kunqiu + */ +@Data +public class Author { + private String login; + private int id; + @JsonProperty("node_id") + private String nodeId; + @JsonProperty("avatar_url") + private String avatarUrl; + @JsonProperty("gravatar_id") + private String gravatarId; + private String url; + @JsonProperty("html_url") + private String htmlUrl; + @JsonProperty("followers_url") + private String followersUrl; + @JsonProperty("following_url") + private String followingUrl; + @JsonProperty("gists_url") + private String gistsUrl; + @JsonProperty("starred_url") + private String starredUrl; + @JsonProperty("subscriptions_url") + private String subscriptionsUrl; + @JsonProperty("organizations_url") + private String organizationsUrl; + @JsonProperty("repos_url") + private String reposUrl; + @JsonProperty("events_url") + private String eventsUrl; + @JsonProperty("received_events_url") + private String receivedEventsUrl; + private String type; + @JsonProperty("site_admin") + private boolean siteAdmin; +} diff --git a/src/main/java/run/halo/app/model/entity/GithubApiVersionJson.java b/src/main/java/run/halo/app/model/entity/GithubApiVersionJson.java new file mode 100644 index 0000000000..448a6e95da --- /dev/null +++ b/src/main/java/run/halo/app/model/entity/GithubApiVersionJson.java @@ -0,0 +1,53 @@ +package run.halo.app.model.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Date; +import java.util.List; +import lombok.Data; +import lombok.ToString; + +/** + * The Java class relevant to the Json returned by github api. + * + *

The json structure refers to the response of + * + * https://api.github.com/repos/halo-dev/halo/releases/latest + * . + * + * @author Chen_Kunqiu + */ +@Data +@ToString +public class GithubApiVersionJson { + private String url; + @JsonProperty("assets_url") + private String assetsUrl; + @JsonProperty("upload_url") + private String uploadUrl; + @JsonProperty("html_url") + private String htmlUrl; + private int id; + private Author author; + @JsonProperty("node_id") + private String nodeId; + @JsonProperty("tag_name") + private String tagName; + @JsonProperty("target_commitish") + private String targetCommitish; + private String name; + private boolean draft; + private boolean prerelease; + @JsonProperty("created_at") + private Date createdAt; + @JsonProperty("published_at") + private Date publishedAt; + private List assets; + @JsonProperty("tarball_url") + private String tarballUrl; + @JsonProperty("zipball_url") + private String zipballUrl; + private String body; + private Reactions reactions; + @JsonProperty("mentions_count") + private int mentionsCount; +} \ No newline at end of file diff --git a/src/main/java/run/halo/app/model/entity/Reactions.java b/src/main/java/run/halo/app/model/entity/Reactions.java new file mode 100644 index 0000000000..0cf539a3ba --- /dev/null +++ b/src/main/java/run/halo/app/model/entity/Reactions.java @@ -0,0 +1,26 @@ +package run.halo.app.model.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * The Java class relevant to the Json returned by github api. + * + * @author Chen_Kunqiu + */ +@Data +public class Reactions { + private String url; + @JsonProperty("total_count") + private int totalCount; + @JsonProperty("+1") + private int plusOne; + @JsonProperty("-1") + private int minusOne; + private int laugh; + private int hooray; + private int confused; + private int heart; + private int rocket; + private int eyes; +} \ No newline at end of file diff --git a/src/main/java/run/halo/app/model/entity/Uploader.java b/src/main/java/run/halo/app/model/entity/Uploader.java new file mode 100644 index 0000000000..81904ab53a --- /dev/null +++ b/src/main/java/run/halo/app/model/entity/Uploader.java @@ -0,0 +1,45 @@ +package run.halo.app.model.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * The Java class relevant to the Json returned by github api. + * + * @author Chen_Kunqiu + */ +@Data +public class Uploader { + private String login; + private int id; + @JsonProperty("node_id") + private String nodeId; + @JsonProperty("avatar_url") + private String avatarUrl; + @JsonProperty("gravatar_id") + private String gravatarId; + private String url; + @JsonProperty("html_url") + private String htmlUrl; + @JsonProperty("followers_url") + private String followersUrl; + @JsonProperty("following_url") + private String followingUrl; + @JsonProperty("gists_url") + private String gistsUrl; + @JsonProperty("starred_url") + private String starredUrl; + @JsonProperty("subscriptions_url") + private String subscriptionsUrl; + @JsonProperty("organizations_url") + private String organizationsUrl; + @JsonProperty("repos_url") + private String reposUrl; + @JsonProperty("events_url") + private String eventsUrl; + @JsonProperty("received_events_url") + private String receivedEventsUrl; + private String type; + @JsonProperty("site_admin") + private boolean siteAdmin; +} \ No newline at end of file diff --git a/src/main/java/run/halo/app/model/enums/SystemType.java b/src/main/java/run/halo/app/model/enums/SystemType.java new file mode 100644 index 0000000000..758634d381 --- /dev/null +++ b/src/main/java/run/halo/app/model/enums/SystemType.java @@ -0,0 +1,10 @@ +package run.halo.app.model.enums; + +/** + * Represents the type of operating system. + * + * @author Chen_Kunqiu + */ +public enum SystemType { + WINDOWS, LINUX, MACOS, ELSE +} diff --git a/src/main/java/run/halo/app/service/HaloVersionCtrlService.java b/src/main/java/run/halo/app/service/HaloVersionCtrlService.java new file mode 100644 index 0000000000..4a1441e39b --- /dev/null +++ b/src/main/java/run/halo/app/service/HaloVersionCtrlService.java @@ -0,0 +1,123 @@ +package run.halo.app.service; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.springframework.scheduling.annotation.Async; +import run.halo.app.model.dto.VersionInfoDTO; + + +/** + * The service to control halo version. + * + * @author Chen_Kunqiu + */ +public interface HaloVersionCtrlService { + + + /** + * check whether the halo jar of the specified version exists in local repository. + * + * @param tagName the specified version + * @return exist in local or not + */ + boolean isInLocal(String tagName); + + /** + * Get all the release info of halo through github api. + * + * @return List of release info organized in json. + */ + List getAllReleasesInfo(); + + /** + * Get specified release info by tagName of the release. + * + * @param tagName the tag name of a github release + * @return single release info + */ + VersionInfoDTO getReleaseInfoByTag(String tagName); + + /** + * Get the release info of the latest release. + * + * @return the release info + */ + VersionInfoDTO getLatestReleaseInfo(); + + /** + * Download the specified jar by tagName into local repository. + * + * @param tagName the specified tag name of the release + */ + @Async + void downloadSpecifiedJarToRepo(String tagName); + + + /** + * Download the latest halo jar into local repository. + * + * @return the version of downloaded jar. + */ + @Async + CompletableFuture downloadLatestJar(); + + /** + * Switch the version of the running halo app into the specified version. + * + *

The general mechanism is shown as follows. + *

    + *
  1. Get the specified halo jar
  2. + * + *
  3. Backup the current halo jar into halo-ori-bak.jar.
  4. + *
  5. Construct the same command which launched the current halo jar, + * and modify the target halo jar in the command into the new version halo jar.
  6. + *
  7. Register a JVM exit hook which use the constructed command to + * launch new version halo jar in subprocess.
  8. + *
  9. Terminate current JVM and trigger relevant hook.
  10. + *
  11. After original halo app exit, delete the original halo jar in few seconds.
  12. + *
+ * + * @param tagName the version to switch + */ + @Async + void switchVersion(String tagName); + + + /** + * Switch version of the running halo app into latest. + * + *

If specified version not exist in local repository, + * directly download it into user dir. + */ + @Async + void switchLatest(); + + + /** + * Similar to {@linkplain #switchVersion switchVersion}, but the halo jar would be downloaded + * into local repository. + * + * @param tagName the version to download and switch + */ + @Async + void downloadAndSwitch(String tagName); + + /** + * Similar to {@linkplain #switchLatest switchLatest}, but the halo jar would be downloaded + * into local repository. + */ + @Async + void downloadAndSwitchLatest(); + + /** + * Get the version of current running halo app. + * + * @return the current halo version + */ + String getCurVersion(); + + +} diff --git a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java new file mode 100644 index 0000000000..5f2c97e385 --- /dev/null +++ b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java @@ -0,0 +1,414 @@ +package run.halo.app.service.impl; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.system.ApplicationHome; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; +import run.halo.app.Application; +import run.halo.app.exception.ServiceException; +import run.halo.app.model.dto.VersionInfoDTO; +import run.halo.app.model.enums.SystemType; +import run.halo.app.model.entity.GithubApiVersionJson; +import run.halo.app.model.support.HaloConst; +import run.halo.app.service.HaloVersionCtrlService; +import run.halo.app.utils.VmUtils; + +/** + * Halo version control service implementation. + * + * @author Chen_Kunqiu + */ +@Service +@Slf4j +public class HaloVersionCtrlServiceImpl implements HaloVersionCtrlService, ApplicationContextAware { + public static final String GITHUB_RELEASES_API = + "https://api.github.com/repos/halo-dev/halo/releases"; + public static final String GITHUB_RELEASES_LATEST_API = + "https://api.github.com/repos/halo-dev/halo/releases/latest"; + public static final String GITHUB_RELEASES_TAG_API_BASE = + "https://api.github.com/repos/halo-dev/halo/releases/tags/"; + /** + * The dir of local repository for halo jars of diverse versions. + */ + public static final String REPO_DIR = ".jar"; + + /** + * The directory where user launch the JVM. + */ + private static final Path USER_DIR = Paths.get(VmUtils.getUserDir()); + + /** + * The directory where current running halo-jar exists. + */ + private static final Path JAR_DIR = VmUtils.CURR_JAR_DIR; + + /** + * Operating system type. + */ + private static final SystemType SYSTEM_TYPE = VmUtils.getSystemType(); + + + private ApplicationContext context; + @Autowired + private RestTemplate restTemplate; + // @Autowired + // private RestTemplateBuilder builder; + + @Override + public boolean isInLocal(String tagName) { + final Path dir = JAR_DIR.resolve(REPO_DIR); + if (!Files.exists(dir)) { + return false; + } + try { + Path target = Files.list(dir).filter(i -> + !StringUtils.hasText(tagName) || i.getFileName().toString().equals(tagName)) + .findFirst().orElse(null); + if (target == null) { + return false; + } + target = + Files.list(target).filter(i -> i.toString().endsWith(".jar")).findFirst() + .orElse(null); + if (target == null) { + return false; + } + } catch (IOException e) { + throw new ServiceException("读取本地目录异常"); + } + + return true; + } + + @Override + public List getAllReleasesInfo() { + // final RestTemplate restTemplate = builder.build(); + final GithubApiVersionJson[] data = + restTemplate.getForObject(GITHUB_RELEASES_API, GithubApiVersionJson[].class); + if (data == null) { + throw new ServiceException("从github api中拉取版本信息失败, url: " + GITHUB_RELEASES_API); + } + return Arrays.stream(data).map(json -> { + final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(json); + versionInfo.setInLocal(isInLocal(json.getTagName())); + return versionInfo; + }).collect(Collectors.toList()); + } + + @Override + public VersionInfoDTO getReleaseInfoByTag(String tagName) { + if (!tagName.startsWith("v")) { + tagName = "v" + tagName; + } + String url = GITHUB_RELEASES_TAG_API_BASE + tagName; + // final RestTemplate restTemplate = builder.build(); + final GithubApiVersionJson json = + restTemplate.getForObject(url, GithubApiVersionJson.class); + if (json == null) { + throw new ServiceException("从github api中拉取版本信息失败, url: " + url); + } + final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(json); + versionInfo.setInLocal(isInLocal(tagName)); + return versionInfo; + } + + @Override + public VersionInfoDTO getLatestReleaseInfo() { + // final RestTemplate restTemplate = builder.build(); + final GithubApiVersionJson json = + restTemplate.getForObject(GITHUB_RELEASES_LATEST_API, GithubApiVersionJson.class); + if (json == null) { + throw new ServiceException("从github api中拉取版本信息失败, url: " + GITHUB_RELEASES_LATEST_API); + } + final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(json); + versionInfo.setInLocal(isInLocal(json.getTagName())); + return versionInfo; + } + + @Override + public void downloadSpecifiedJarToRepo(String tagName) { + if (!tagName.startsWith("v")) { + tagName = "v" + tagName; + } + if (isInLocal(tagName)) { + return; + } + final Path dstDir = Paths.get(REPO_DIR).resolve(tagName); + if (!Files.exists(dstDir)) { + try { + Files.createDirectories(dstDir); + } catch (IOException e) { + throw new ServiceException("创建本地仓库失败, 仓库路径: " + dstDir); + } + } + downloadSpecifiedJar(tagName, dstDir.toString()); + } + + + @Override + public CompletableFuture downloadLatestJar() { + final VersionInfoDTO info = getLatestReleaseInfo(); + log.info("Downloading the jar with tagName: {}", info.getVersion()); + downloadSpecifiedJarToRepo(info.getVersion()); + return CompletableFuture.completedFuture(info.getVersion()); + } + + @Override + public void switchVersion(String tagName) { + if (!tagName.startsWith("v")) { + tagName = "v" + tagName; + } + // if the current version equals to the specified version, then return. + if (tagName.equals("v" + getCurVersion())) { + return; + } + final Path curJar = VmUtils.CURR_JAR; + // If not launched in jar, return. + if (curJar == null) { + return; + } + // If local storage exist specified jar, copy to work dir and get the Path of the copy. + Path target; + try { + if (isInLocal(tagName)) { + target = copyTargetFromLocal(tagName); + } else { + // else, directly download the specified through network into work dir + // and get the Path of it. + target = copyTargetFromRemote(tagName); + } + if (target == null) { + throw new IOException(); + } + } catch (IOException e) { + throw new ServiceException("获取目标版本的halo jar包失败, tagName: " + tagName); + } + + final Path backupTarget = JAR_DIR.resolve("halo-ori-bak.jar"); + log.info("Path of target jar get: {}", target); + // backup and delete original jar + try { + backupAndDeleteSpecifiedJar(curJar, backupTarget); + } catch (IOException e) { + throw new ServiceException("备份失败, 备份路径: " + backupTarget); + } + log.info("backup finish: {}, OS: {}", backupTarget, SYSTEM_TYPE); + + // launch the new version jar with the same arguments + startNewVersionApp(target); + // Close the Spring app + int exitCode = SpringApplication.exit(context, () -> 0); + + System.exit(exitCode); + } + + @Override + public void downloadAndSwitch(String tagName) { + downloadSpecifiedJarToRepo(tagName); + switchVersion(tagName); + } + + @Override + public void downloadAndSwitchLatest() { + log.info("latest jar downloading"); + String tagName; + try { + tagName = downloadLatestJar().get(); + } catch (InterruptedException | ExecutionException e) { + throw new ServiceException("下载失败"); + } + log.info("latest jar download successfully: {}", tagName); + switchVersion(tagName); + } + + @Override + public String getCurVersion() { + return HaloConst.HALO_VERSION; + } + + @Override + public void switchLatest() { + final String tagName = getLatestReleaseInfo().getVersion(); + switchVersion(tagName); + } + + @Override + public void setApplicationContext(@NotNull ApplicationContext applicationContext) { + context = applicationContext; + } + + /** + * Download the specified jar by tagName into the given destination directory. + * + * @param tagName the specified tag name of the release + * @param dstDir destination directory + * @return the final path of the downloaded jar + */ + private String downloadSpecifiedJar(String tagName, String dstDir) { + final VersionInfoDTO releaseInfo = + Objects.requireNonNull(getReleaseInfoByTag(tagName)); + final String jarUrl = releaseInfo.getDownloadUrl(); + final String jarName = releaseInfo.getJarName(); + Assert.hasText(jarUrl, "Jar url must not be blank"); + log.info("Downloading [{}]", jarUrl); + + // final RestTemplate restTemplate = builder.build(); + final ResponseEntity downloadResp = + restTemplate.getForEntity(jarUrl, byte[].class); + log.info("Download response: [{}]", downloadResp.getStatusCode()); + if (downloadResp.getStatusCode().isError() || downloadResp.getBody() == null) { + throw new ServiceException("下载失败 " + + jarUrl + + ", 状态码: " + + downloadResp.getStatusCode()); + } + + log.info("Downloaded [{}]", jarUrl); + Path tar = Paths.get(dstDir).resolve(jarName); + try { + Files.write(tar, Objects.requireNonNull(downloadResp.getBody()), + StandardOpenOption.CREATE); + } catch (IOException e) { + throw new ServiceException("jar包存储失败, 目标路径 " + + tar); + } + return tar.toString(); + } + + + /** + * Copy the target jar file in local repo with specified tagName to work dir + * and get the Path of the copy. + * + * @param tagName version of target jar, such as v1.1, v2.2.1... + * @return the Path of target jar file + * @throws IOException exception may happen in copying. + */ + private Path copyTargetFromLocal(String tagName) throws IOException { + final Path dir = JAR_DIR.resolve(REPO_DIR); + Path target = + Files.list(dir).filter(i -> + !StringUtils.hasText(tagName) || i.getFileName().toString().equals(tagName)) + .findFirst().orElse(null); + if (target == null) { + return null; + } + target = + Files.list(target).filter(i -> i.toString().endsWith(".jar")).findFirst().orElse(null); + if (target == null) { + return null; + } + final Path newJar = JAR_DIR.resolve(target.getFileName()); + return Files.copy(target, newJar, StandardCopyOption.REPLACE_EXISTING); + } + + + /** + * Download the target jar file in github repo with specified tagName to work dir + * and get the Path of the target jar. + * + * @param tagName version of target jar, such as v1.1, v2.2.1... + * @return the Path of target jar file + */ + private Path copyTargetFromRemote(String tagName) { + return Paths.get(downloadSpecifiedJar(tagName, JAR_DIR.toString())); + } + + + /** + * Backup and delete the specified jar file. + * The implementation in Windows differs as the file lock in Windows. + * + * @param curJar the specified jar path + * @param backupTarget the path of the backup + * @throws IOException the exception may arise in the process of backup + */ + private void backupAndDeleteSpecifiedJar(Path curJar, Path backupTarget) throws IOException { + final Path backupDir = backupTarget.getParent(); + assert backupDir != null; + Files.createDirectories(backupDir); + Files.copy(curJar, backupTarget, StandardCopyOption.REPLACE_EXISTING); + ProcessBuilder pb = new ProcessBuilder(); + pb.directory(USER_DIR.toFile()); + switch (SYSTEM_TYPE) { + case WINDOWS: + pb.command("cmd", "/c", + "ping localhost -n 10 > nul && del " + curJar.toAbsolutePath()); + break; + case LINUX: + case MACOS: + /* + * On Unix-like operating systems, there is no file locking, + * thus you can directly change the name of the current JAR. + * If you do so, however, `SpringApplication.exit` will not + * terminate the program properly + * */ + pb.command("sh", "-c", "sleep 10s && rm -f " + curJar.toAbsolutePath()); + break; + case ELSE: + default: + break; + } + Runtime.getRuntime().addShutdownHook(new Thread( + () -> { + try { + pb.start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + )); + } + + /** + * Start the selected new version application of halo by launch another process. + * + * @param target the target jar file of new version app + */ + private void startNewVersionApp(Path target) { + final List cmd = VmUtils.getNewLaunchCommand(target.toString()); + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.directory(USER_DIR.toFile()); + log.info("Cmd to launch new version halo app: {}", String.join(" ", cmd)); + // Registers a new virtual-machine shutdown hook to launch new version halo app. + // At the time of JVM exiting, the network resource it owning would be released, + // hence, starting new halo-app at that moment could avoid port conflict. + Runtime.getRuntime().addShutdownHook(new Thread( + () -> { + try { + final Process process = pb.inheritIO().start(); + log.info("New process PID: {}", process.pid()); + } catch (IOException e) { + e.printStackTrace(); + } + } + )); + + } + + + + +} diff --git a/src/main/java/run/halo/app/utils/VmUtils.java b/src/main/java/run/halo/app/utils/VmUtils.java new file mode 100644 index 0000000000..9c70272b27 --- /dev/null +++ b/src/main/java/run/halo/app/utils/VmUtils.java @@ -0,0 +1,195 @@ +package run.halo.app.utils; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import run.halo.app.exception.ServiceException; +import run.halo.app.model.enums.SystemType; + +/** + * The utils to get some info of JVM. + * + * @author Chen_Kunqiu + */ +public class VmUtils { + private static RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean(); + public static final List PROGRAM_ARGS = new ArrayList<>(); + public static final Path CURR_JAR; + public static final Path CURR_JAR_DIR; + + static { + final String path = + VmUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + // If not containing ".jar", it is test scenario. + final int idx = path.contains(".jar") ? path.indexOf(".jar") + 4 : path.length(); + CURR_JAR = new File(path.substring(0, idx)).toPath(); + CURR_JAR_DIR = CURR_JAR.getParent(); + } + + /** + * Get OS type. + * + *

The type of the operating system (Windows, Linux, MacOS) + * + * @return system type + */ + public static SystemType getSystemType() { + final String osName = System.getProperty("os.name").toLowerCase(); + if (osName.startsWith("window")) { + return SystemType.WINDOWS; + } + if (osName.startsWith("linux")) { + return SystemType.LINUX; + } + if (osName.startsWith("macos")) { + return SystemType.MACOS; + } + return SystemType.ELSE; + } + + /** + * Get the command to launch the halo jar. + * + *

Get the same command as the command to launch this Java program. + * Use this command to restart the Java program after halo updates. + * + * @return the full command + */ + public static String getSameLaunchCommand() { + List cmd = new ArrayList<>(); + cmd.add(getJvmExecutablePath()); + cmd.addAll(getVmArguments()); + cmd.add("-classpath"); + cmd.add(getClassPath()); + cmd.add("-jar"); + cmd.add(getRunningJar()); + cmd.addAll(PROGRAM_ARGS); + return String.join(" ", cmd); + } + + /** + * Get the new command to launch halo jar with new version. + * + *

Get the same new launch command as the command to launch this Java program + * except for the Java target to launch. + * Use this command to restart the Java program after halo updates. + * + * @return the full command + */ + public static List getNewLaunchCommand(String newTarget) { + List cmd = new ArrayList<>(); + cmd.add(getJvmExecutablePath()); + cmd.addAll(getVmArguments()); + cmd.add("-classpath"); + String classPath = getClassPath(); + final String nonVmPartOfCmd = getNonVmPartOfCmd(); + if (CURR_JAR == null || CURR_JAR.getFileName() == null) { + throw new ServiceException("无法获取当前运行的JAR"); + } + final String jarName = CURR_JAR.getFileName().toString(); + + final int endIdx = nonVmPartOfCmd.indexOf(jarName) + jarName.length(); + /* + * Since cannot determine whether user use relative or absolute path to launch halo.jar, + * and CURR_JAR is absolute path, + * use this approach to obtain the real form of halo jar's path when launching. + */ + String originalJar = nonVmPartOfCmd.substring(0, endIdx); + // replace the class path + classPath = classPath.replace(originalJar, newTarget); + cmd.add(classPath); + cmd.add("-jar"); + cmd.add(newTarget); + cmd.addAll(PROGRAM_ARGS); + return cmd; + } + + /** + * Get the absolute path of java / javaw. + * + *

Get the full path of the JVM executable which runs the current Java program. + * As the JVM is not always specified as JAVA_HOME, cannot get it simply by + * {@code $JAVA_HOME/bin/java} + * + * @return the full path of current JVM + */ + public static String getJvmExecutablePath() { + // need JAVA 9+ + return ProcessHandle.current() + .info() + .command() + .orElseThrow(); + } + + /** + * Get the VM arguments passed to JVM. + * + *

For example,
+ * {@code java -jar -Da=1 Test.jar b=2}
+ * -->
+ * {@code -Da=1} + * + * @return the VM arguments + */ + public static List getVmArguments() { + return bean.getInputArguments(); + } + + /** + * Get the class path which the JVM relies on. + * + * @return the class path + */ + public static String getClassPath() { + return bean.getClassPath(); + } + + /** + * Get the program arguments including the target class or jar. + * + *

For example,
+ * {@code java -jar -Da=1 Test.jar b=2}
+ * -->
+ * {@code Test.jar b=2} + * + * @return the program arguments including the target class or jar + */ + public static String getNonVmPartOfCmd() { + return System.getProperty("sun.java.command"); + } + + /** + * Get the full path of the running jar. + * + *

If running class file without jar, then return the directory contains the root package. + * + * @return the full path of running jar. + */ + public static String getRunningJar() { + return CURR_JAR.toString(); + } + + /** + * Get the full path of the directory of the running jar. + * + * @return the full path of the directory of the running jar. + */ + public static String getRunningJarDir() { + return CURR_JAR_DIR.toString(); + } + + /** + * Get the work directory of JVM. + * + * @return work directory (aka. user dir) + */ + public static String getUserDir() { + return System.getProperty("user.dir"); + } + + + +} diff --git a/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java b/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java new file mode 100644 index 0000000000..1bb9b65c95 --- /dev/null +++ b/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java @@ -0,0 +1,111 @@ +package run.halo.app.service.impl; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.commons.util.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpMethod; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import run.halo.app.exception.ServiceException; +import run.halo.app.model.dto.VersionInfoDTO; +import run.halo.app.model.support.HaloConst; +import run.halo.app.service.HaloVersionCtrlService; +import run.halo.app.utils.VmUtils; + +@ExtendWith(SpringExtension.class) +@SpringBootTest +class HaloVersionCtrlServiceImplTest { + + @Autowired + private RestTemplate restTemplate; + + @Autowired + HaloVersionCtrlService versionCtrlService; + + @Test + void testIsInLocal() { + assertFalse(this.versionCtrlService.isInLocal("Tag Name")); + Path DIR = VmUtils.CURR_JAR_DIR; + Path repo = DIR.resolve(HaloVersionCtrlServiceImpl.REPO_DIR); + Path test = null, tempFile = null; + final boolean exists = Files.exists(repo); + try { + if (!exists) { + Files.createDirectories(repo); + } + test = Files.createTempDirectory(repo, "test"); + assertFalse(versionCtrlService.isInLocal(test.getFileName().toString())); + tempFile = Files.createTempFile(test, "halo", ".jar"); + assertTrue(versionCtrlService.isInLocal(test.getFileName().toString())); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (tempFile != null) { + Files.deleteIfExists(tempFile); + } + if (test != null) { + Files.deleteIfExists(test); + } + if (!exists) { + Files.deleteIfExists(repo); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + @Test + void testGetLatestReleaseInfo() throws RestClientException { + + final VersionInfoDTO latestReleaseInfo = versionCtrlService.getLatestReleaseInfo(); + + assertNotNull(latestReleaseInfo); + assertFalse(StringUtils.isBlank(latestReleaseInfo.getDownloadUrl())); + } + + @Test + void testDownload() throws RestClientException { + + // 仅仅分片下载 10 bytes以测试下载通道是否顺畅 + assertDoesNotThrow(() -> { + final VersionInfoDTO latestReleaseInfo = versionCtrlService.getLatestReleaseInfo(); + final String downloadUrl = latestReleaseInfo.getDownloadUrl(); + + final byte[] data = restTemplate.execute(downloadUrl, HttpMethod.GET, + + req -> req.getHeaders() + .set("Range", String.format("bytes=%d-%d", 0, 9)), + resp -> { + try { + return resp.getBody().readAllBytes(); + } catch (Exception e) { + throw new ServiceException("下载失败, url: " + downloadUrl); + } + }); + assertNotNull(data); + assertEquals(10, data.length); + } + ); + + } + + @Test + void testGetCurVersion() { + assertEquals(HaloConst.HALO_VERSION, versionCtrlService.getCurVersion()); + } +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/utils/VmUtilsTest.java b/src/test/java/run/halo/app/utils/VmUtilsTest.java new file mode 100644 index 0000000000..769c1a1b37 --- /dev/null +++ b/src/test/java/run/halo/app/utils/VmUtilsTest.java @@ -0,0 +1,66 @@ +package run.halo.app.utils; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.StringUtils; + +class VmUtilsTest { + private static RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean(); + + @Test + void testGetSameLaunchCommand() { + String cmd = VmUtils.getSameLaunchCommand(); + assertTrue(StringUtils.isNotBlank(cmd)); + // 测试命令的可执行性 + assertDoesNotThrow(()->{ + final Process exec = Runtime.getRuntime().exec(cmd); + exec.destroy(); + }); + } + + @Test + void testGetJvmExecutablePath() { + String actualJvmExecutablePath = VmUtils.getJvmExecutablePath(); + assertTrue(StringUtils.isNotBlank(actualJvmExecutablePath)); + assertDoesNotThrow(()->{ + new ProcessBuilder(actualJvmExecutablePath).start(); + }); + } + + + @Test + void testGetVmArguments() { + final List vmArguments = VmUtils.getVmArguments(); + assertEquals(bean.getInputArguments(), vmArguments); + } + @Test + void testGetClassPath() { + String actualClassPath = VmUtils.getClassPath(); + assertEquals(bean.getClassPath(), actualClassPath); + } + + @Test + void testGetNonVmPartOfCmd() { + assertEquals(System.getProperty("sun.java.command"), VmUtils.getNonVmPartOfCmd()); + } + + @Test + void testGetRunningJar() { + final String runningJar = VmUtils.getRunningJar(); + final File file = new File(runningJar); + assertTrue(file.exists()); + } + + @Test + void testGetUserDir() { + assertEquals(System.getProperty("user.dir"), VmUtils.getUserDir()); + } +} + From 35162437e4882e0eddccae4f3f829fd4e516c96b Mon Sep 17 00:00:00 2001 From: camsyn <2742046922@qq.com> Date: Sat, 23 Apr 2022 14:55:51 +0800 Subject: [PATCH 02/10] Fix some bugs in this enhancement --- src/main/java/run/halo/app/Application.java | 5 ++ .../impl/HaloVersionCtrlServiceImpl.java | 75 ++++++++++--------- src/main/java/run/halo/app/utils/VmUtils.java | 17 +++-- .../java/run/halo/app/utils/VmUtilsTest.java | 13 ++-- 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/src/main/java/run/halo/app/Application.java b/src/main/java/run/halo/app/Application.java index ba8420eb68..8b4f87ac15 100755 --- a/src/main/java/run/halo/app/Application.java +++ b/src/main/java/run/halo/app/Application.java @@ -1,7 +1,9 @@ package run.halo.app; +import java.util.List; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import run.halo.app.utils.VmUtils; /** * Halo main class. @@ -13,6 +15,9 @@ public class Application { public static void main(String[] args) { + // Store the program args to construct launch command in version switch. + VmUtils.PROGRAM_ARGS.addAll(List.of(args)); + // Customize the spring config location System.setProperty("spring.config.additional-location", "optional:file:${user.home}/.halo/,optional:file:${user.home}/halo-dev/"); diff --git a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java index 5f2c97e385..410fd9441f 100644 --- a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java @@ -1,12 +1,13 @@ package run.halo.app.service.impl; -import java.io.File; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -17,20 +18,17 @@ import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; -import org.springframework.boot.system.ApplicationHome; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.http.ResponseEntity; -import org.springframework.scheduling.annotation.Async; +import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; -import run.halo.app.Application; import run.halo.app.exception.ServiceException; import run.halo.app.model.dto.VersionInfoDTO; -import run.halo.app.model.enums.SystemType; import run.halo.app.model.entity.GithubApiVersionJson; +import run.halo.app.model.enums.SystemType; import run.halo.app.model.support.HaloConst; import run.halo.app.service.HaloVersionCtrlService; import run.halo.app.utils.VmUtils; @@ -71,6 +69,7 @@ public class HaloVersionCtrlServiceImpl implements HaloVersionCtrlService, Appli private ApplicationContext context; + @Autowired private RestTemplate restTemplate; // @Autowired @@ -84,7 +83,7 @@ public boolean isInLocal(String tagName) { } try { Path target = Files.list(dir).filter(i -> - !StringUtils.hasText(tagName) || i.getFileName().toString().equals(tagName)) + !StringUtils.hasText(tagName) || i.getFileName().toString().equals(tagName)) .findFirst().orElse(null); if (target == null) { return false; @@ -186,7 +185,7 @@ public void switchVersion(String tagName) { } final Path curJar = VmUtils.CURR_JAR; // If not launched in jar, return. - if (curJar == null) { + if (!curJar.toString().endsWith(".jar")) { return; } // If local storage exist specified jar, copy to work dir and get the Path of the copy. @@ -272,32 +271,40 @@ private String downloadSpecifiedJar(String tagName, String dstDir) { final String jarUrl = releaseInfo.getDownloadUrl(); final String jarName = releaseInfo.getJarName(); Assert.hasText(jarUrl, "Jar url must not be blank"); - log.info("Downloading [{}]", jarUrl); - - // final RestTemplate restTemplate = builder.build(); - final ResponseEntity downloadResp = - restTemplate.getForEntity(jarUrl, byte[].class); - log.info("Download response: [{}]", downloadResp.getStatusCode()); - if (downloadResp.getStatusCode().isError() || downloadResp.getBody() == null) { - throw new ServiceException("下载失败 " - + jarUrl - + ", 状态码: " - + downloadResp.getStatusCode()); - } - - log.info("Downloaded [{}]", jarUrl); Path tar = Paths.get(dstDir).resolve(jarName); - try { - Files.write(tar, Objects.requireNonNull(downloadResp.getBody()), - StandardOpenOption.CREATE); - } catch (IOException e) { - throw new ServiceException("jar包存储失败, 目标路径 " - + tar); - } + download(jarUrl, tar); return tar.toString(); } + /** + * Download resource to specified file. + * + *

As the jar file is very big, it is not appropriate to load it as byte[] in memory, + * so that directly forwarded it to the file system. + * + * @param url the url to download resource + * @param tarFile target file + */ + private void download(String url, Path tarFile) { + restTemplate.execute(url, HttpMethod.GET, null, + resp -> { + log.info("Downloading [{}]", url); + try (final BufferedInputStream is = new BufferedInputStream(resp.getBody()); + final BufferedOutputStream os = new BufferedOutputStream( + new FileOutputStream(tarFile.toFile()))) { + is.transferTo(os); + } catch (Exception e) { + throw new ServiceException("下载失败 " + + url + + ", 状态码: " + + resp.getStatusCode()); + } + return tarFile; + }); + } + + /** * Copy the target jar file in local repo with specified tagName to work dir * and get the Path of the copy. @@ -310,7 +317,7 @@ private Path copyTargetFromLocal(String tagName) throws IOException { final Path dir = JAR_DIR.resolve(REPO_DIR); Path target = Files.list(dir).filter(i -> - !StringUtils.hasText(tagName) || i.getFileName().toString().equals(tagName)) + !StringUtils.hasText(tagName) || i.getFileName().toString().equals(tagName)) .findFirst().orElse(null); if (target == null) { return null; @@ -399,7 +406,9 @@ private void startNewVersionApp(Path target) { () -> { try { final Process process = pb.inheritIO().start(); - log.info("New process PID: {}", process.pid()); + System.out.println( + "\n------------------------------\nNew process PID: " + process.pid() + + "\n------------------------------\n"); } catch (IOException e) { e.printStackTrace(); } @@ -409,6 +418,4 @@ private void startNewVersionApp(Path target) { } - - } diff --git a/src/main/java/run/halo/app/utils/VmUtils.java b/src/main/java/run/halo/app/utils/VmUtils.java index 9c70272b27..e8553a89ea 100644 --- a/src/main/java/run/halo/app/utils/VmUtils.java +++ b/src/main/java/run/halo/app/utils/VmUtils.java @@ -15,16 +15,21 @@ * @author Chen_Kunqiu */ public class VmUtils { - private static RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean(); + private static final RuntimeMXBean RUNTIME_MX_BEAN = ManagementFactory.getRuntimeMXBean(); public static final List PROGRAM_ARGS = new ArrayList<>(); public static final Path CURR_JAR; public static final Path CURR_JAR_DIR; static { - final String path = + String path = VmUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + // As the special package form of Spring, the path would start with "file:" + if (path.startsWith("file:")) { + path = path.substring(5); + } // If not containing ".jar", it is test scenario. final int idx = path.contains(".jar") ? path.indexOf(".jar") + 4 : path.length(); + CURR_JAR = new File(path.substring(0, idx)).toPath(); CURR_JAR_DIR = CURR_JAR.getParent(); } @@ -58,7 +63,7 @@ public static SystemType getSystemType() { * * @return the full command */ - public static String getSameLaunchCommand() { + public static List getSameLaunchCommand() { List cmd = new ArrayList<>(); cmd.add(getJvmExecutablePath()); cmd.addAll(getVmArguments()); @@ -67,7 +72,7 @@ public static String getSameLaunchCommand() { cmd.add("-jar"); cmd.add(getRunningJar()); cmd.addAll(PROGRAM_ARGS); - return String.join(" ", cmd); + return cmd; } /** @@ -135,7 +140,7 @@ public static String getJvmExecutablePath() { * @return the VM arguments */ public static List getVmArguments() { - return bean.getInputArguments(); + return RUNTIME_MX_BEAN.getInputArguments(); } /** @@ -144,7 +149,7 @@ public static List getVmArguments() { * @return the class path */ public static String getClassPath() { - return bean.getClassPath(); + return RUNTIME_MX_BEAN.getClassPath(); } /** diff --git a/src/test/java/run/halo/app/utils/VmUtilsTest.java b/src/test/java/run/halo/app/utils/VmUtilsTest.java index 769c1a1b37..f0d9ac4244 100644 --- a/src/test/java/run/halo/app/utils/VmUtilsTest.java +++ b/src/test/java/run/halo/app/utils/VmUtilsTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; @@ -16,20 +17,15 @@ class VmUtilsTest { @Test void testGetSameLaunchCommand() { - String cmd = VmUtils.getSameLaunchCommand(); - assertTrue(StringUtils.isNotBlank(cmd)); - // 测试命令的可执行性 - assertDoesNotThrow(()->{ - final Process exec = Runtime.getRuntime().exec(cmd); - exec.destroy(); - }); + final List cmd = VmUtils.getSameLaunchCommand(); + assertFalse(cmd.isEmpty()); } @Test void testGetJvmExecutablePath() { String actualJvmExecutablePath = VmUtils.getJvmExecutablePath(); assertTrue(StringUtils.isNotBlank(actualJvmExecutablePath)); - assertDoesNotThrow(()->{ + assertDoesNotThrow(() -> { new ProcessBuilder(actualJvmExecutablePath).start(); }); } @@ -40,6 +36,7 @@ void testGetVmArguments() { final List vmArguments = VmUtils.getVmArguments(); assertEquals(bean.getInputArguments(), vmArguments); } + @Test void testGetClassPath() { String actualClassPath = VmUtils.getClassPath(); From 06b32bf757555851b087d3260f312ab4bb82e3fb Mon Sep 17 00:00:00 2001 From: camsyn <2742046922@qq.com> Date: Sun, 24 Apr 2022 11:33:46 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E6=9A=82=E5=AD=98=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=BB=A5=E6=B5=8B=E8=AF=951.4.x=E7=9A=84=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/HaloVersionCtrlServiceImpl.java | 2 +- .../java/run/halo/app/utils/VmUtilsTest.java | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java index 410fd9441f..6519208a17 100644 --- a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java @@ -294,7 +294,7 @@ private void download(String url, Path tarFile) { final BufferedOutputStream os = new BufferedOutputStream( new FileOutputStream(tarFile.toFile()))) { is.transferTo(os); - } catch (Exception e) { + } catch (IOException e) { throw new ServiceException("下载失败 " + url + ", 状态码: " diff --git a/src/test/java/run/halo/app/utils/VmUtilsTest.java b/src/test/java/run/halo/app/utils/VmUtilsTest.java index f0d9ac4244..4c1c2ce752 100644 --- a/src/test/java/run/halo/app/utils/VmUtilsTest.java +++ b/src/test/java/run/halo/app/utils/VmUtilsTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; @@ -19,6 +20,15 @@ class VmUtilsTest { void testGetSameLaunchCommand() { final List cmd = VmUtils.getSameLaunchCommand(); assertFalse(cmd.isEmpty()); + // In junit test. its size should > 3 + assertTrue(cmd.size() > 3); + } + + @Test + void testGetSameLaunchCommand2() { + final List cmd = VmUtils.getSameLaunchCommand(); + System.out.println(cmd.get(0)); + assertTrue(cmd.get(0).matches(".*java.*")); } @Test @@ -30,9 +40,20 @@ void testGetJvmExecutablePath() { }); } + @Test + void testGetJvmExecutablePath2() { + String actualJvmExecutablePath = VmUtils.getJvmExecutablePath(); + assertTrue(actualJvmExecutablePath.matches(".*java.*")); + } + @Test void testGetVmArguments() { + final List vmArguments = VmUtils.getVmArguments(); + assertNotNull(vmArguments); + } + @Test + void testGetVmArguments2() { final List vmArguments = VmUtils.getVmArguments(); assertEquals(bean.getInputArguments(), vmArguments); } @@ -43,13 +64,33 @@ void testGetClassPath() { assertEquals(bean.getClassPath(), actualClassPath); } + @Test + void testGetClassPath2() { + String actualClassPath = VmUtils.getClassPath(); + final String separator = System.getProperty("path.separator"); + final String[] classSource = actualClassPath.split(separator); + for (String s : classSource) { + assertTrue(new File(s).exists()); + } + } + @Test void testGetNonVmPartOfCmd() { assertEquals(System.getProperty("sun.java.command"), VmUtils.getNonVmPartOfCmd()); } + @Test + void testGetNonVmPartOfCmd2() { + final String args = VmUtils.getNonVmPartOfCmd(); + assertTrue(StringUtils.isNotBlank(args)); + } @Test void testGetRunningJar() { + final String runningJar = VmUtils.getRunningJar(); + assertTrue(StringUtils.isNotBlank(runningJar)); + } + @Test + void testGetRunningJar2() { final String runningJar = VmUtils.getRunningJar(); final File file = new File(runningJar); assertTrue(file.exists()); @@ -59,5 +100,10 @@ void testGetRunningJar() { void testGetUserDir() { assertEquals(System.getProperty("user.dir"), VmUtils.getUserDir()); } + @Test + void testGetUserDir2() { + final String userDir = VmUtils.getUserDir(); + assertTrue(new File(userDir).exists()); + } } From 20561e578a42a06d16d459cb2893888a871d7c96 Mon Sep 17 00:00:00 2001 From: camsyn <2742046922@qq.com> Date: Sun, 24 Apr 2022 19:23:03 +0800 Subject: [PATCH 04/10] Fix checkstyle and add some unit tests. --- .../impl/HaloVersionCtrlServiceImpl.java | 60 +++++++++++-------- .../impl/HaloVersionCtrlServiceImplTest.java | 7 ++- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java index 6519208a17..51bb87e836 100644 --- a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java @@ -15,6 +15,7 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.SystemUtils; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; @@ -359,34 +360,41 @@ private void backupAndDeleteSpecifiedJar(Path curJar, Path backupTarget) throws Files.copy(curJar, backupTarget, StandardCopyOption.REPLACE_EXISTING); ProcessBuilder pb = new ProcessBuilder(); pb.directory(USER_DIR.toFile()); - switch (SYSTEM_TYPE) { - case WINDOWS: - pb.command("cmd", "/c", - "ping localhost -n 10 > nul && del " + curJar.toAbsolutePath()); - break; - case LINUX: - case MACOS: - /* - * On Unix-like operating systems, there is no file locking, - * thus you can directly change the name of the current JAR. - * If you do so, however, `SpringApplication.exit` will not - * terminate the program properly - * */ - pb.command("sh", "-c", "sleep 10s && rm -f " + curJar.toAbsolutePath()); - break; - case ELSE: - default: - break; + if (SystemUtils.IS_OS_WINDOWS) { + pb.command("cmd", "/c", + "ping localhost -n 10 > nul && del " + curJar.toAbsolutePath()); + } else if (SystemUtils.IS_OS_LINUX) { + /* + * On Unix-like operating systems, there is no file locking, + * thus you can directly change the name of the current JAR. + * If you do so, however, `SpringApplication.exit` will not + * terminate the program properly + * */ + pb.command("sh", "-c", "sleep 10s && rm -f " + curJar.toAbsolutePath()); + } else if (SystemUtils.IS_OS_MAC) { + /* + * On Unix-like operating systems, there is no file locking, + * thus you can directly change the name of the current JAR. + * If you do so, however, `SpringApplication.exit` will not + * terminate the program properly + * */ + pb.command("sh", "-c", "sleep 10s && rm -f " + curJar.toAbsolutePath()); + } else { + pb = null; } - Runtime.getRuntime().addShutdownHook(new Thread( - () -> { - try { - pb.start(); - } catch (IOException e) { - e.printStackTrace(); + if (pb != null) { + ProcessBuilder finalPb = pb; + Runtime.getRuntime().addShutdownHook(new Thread( + () -> { + try { + finalPb.start(); + } catch (IOException e) { + e.printStackTrace(); + } } - } - )); + )); + } + } /** diff --git a/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java b/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java index 1bb9b65c95..5b081f46c9 100644 --- a/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java +++ b/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java @@ -37,9 +37,10 @@ class HaloVersionCtrlServiceImplTest { @Test void testIsInLocal() { assertFalse(this.versionCtrlService.isInLocal("Tag Name")); - Path DIR = VmUtils.CURR_JAR_DIR; - Path repo = DIR.resolve(HaloVersionCtrlServiceImpl.REPO_DIR); - Path test = null, tempFile = null; + Path dir = VmUtils.CURR_JAR_DIR; + Path repo = dir.resolve(HaloVersionCtrlServiceImpl.REPO_DIR); + Path test = null; + Path tempFile = null; final boolean exists = Files.exists(repo); try { if (!exists) { From 50d7a6c0a016f5e4e5fa548d3020c0a020f85890 Mon Sep 17 00:00:00 2001 From: camsyn <2742046922@qq.com> Date: Sun, 24 Apr 2022 19:25:54 +0800 Subject: [PATCH 05/10] Fix checkstyle and add some unit tests. --- .../impl/HaloVersionCtrlServiceImpl.java | 7 +------ src/main/java/run/halo/app/utils/VmUtils.java | 20 ------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java index 51bb87e836..a17fc9c448 100644 --- a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java @@ -29,7 +29,6 @@ import run.halo.app.exception.ServiceException; import run.halo.app.model.dto.VersionInfoDTO; import run.halo.app.model.entity.GithubApiVersionJson; -import run.halo.app.model.enums.SystemType; import run.halo.app.model.support.HaloConst; import run.halo.app.service.HaloVersionCtrlService; import run.halo.app.utils.VmUtils; @@ -63,10 +62,6 @@ public class HaloVersionCtrlServiceImpl implements HaloVersionCtrlService, Appli */ private static final Path JAR_DIR = VmUtils.CURR_JAR_DIR; - /** - * Operating system type. - */ - private static final SystemType SYSTEM_TYPE = VmUtils.getSystemType(); private ApplicationContext context; @@ -214,7 +209,7 @@ public void switchVersion(String tagName) { } catch (IOException e) { throw new ServiceException("备份失败, 备份路径: " + backupTarget); } - log.info("backup finish: {}, OS: {}", backupTarget, SYSTEM_TYPE); + log.info("backup finish: {}", backupTarget); // launch the new version jar with the same arguments startNewVersionApp(target); diff --git a/src/main/java/run/halo/app/utils/VmUtils.java b/src/main/java/run/halo/app/utils/VmUtils.java index e8553a89ea..2136613cbe 100644 --- a/src/main/java/run/halo/app/utils/VmUtils.java +++ b/src/main/java/run/halo/app/utils/VmUtils.java @@ -34,26 +34,6 @@ public class VmUtils { CURR_JAR_DIR = CURR_JAR.getParent(); } - /** - * Get OS type. - * - *

The type of the operating system (Windows, Linux, MacOS) - * - * @return system type - */ - public static SystemType getSystemType() { - final String osName = System.getProperty("os.name").toLowerCase(); - if (osName.startsWith("window")) { - return SystemType.WINDOWS; - } - if (osName.startsWith("linux")) { - return SystemType.LINUX; - } - if (osName.startsWith("macos")) { - return SystemType.MACOS; - } - return SystemType.ELSE; - } /** * Get the command to launch the halo jar. From 0a01a494941bf4f397b5d627eea80fb441f265e1 Mon Sep 17 00:00:00 2001 From: camsyn <2742046922@qq.com> Date: Sat, 21 May 2022 17:14:35 +0800 Subject: [PATCH 06/10] Fix issue 1675 --- CHANGELOG.md | 25 ++++ CONTRIBUTING.md | 113 ++++++++++++------ README.md | 22 +--- build.gradle | 5 + gradle.properties | 2 +- src/main/java/run/halo/app/Application.java | 3 - .../admin/api/VersionCtrlController.java | 5 + .../controller/content/MainController.java | 13 +- .../app/handler/file/LocalFileHandler.java | 3 +- .../comment/CommentEventListener.java | 8 +- .../run/halo/app/model/entity/BasePost.java | 6 +- .../app/service/impl/BasePostServiceImpl.java | 40 ++++++- .../impl/StaticStorageServiceImpl.java | 7 +- src/main/java/run/halo/app/utils/VmUtils.java | 42 +++++-- src/test/java/Test.java | 12 ++ .../app/service/impl/HTMLWordCountTest.java | 54 ++++++++- .../java/run/halo/app/utils/VmUtilsTest.java | 4 +- 17 files changed, 278 insertions(+), 86 deletions(-) create mode 100644 src/test/java/Test.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c151476d19..77a01fcc8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # CHANGELOG +# 1.5.3 + +## Improvements + +- 优化邮件发送异常信息处理。 halo-dev/halo#1860 @ntdgy @superdgy +- 优化静态存储的资源映射处理逻辑,支持手动操作 `.halo/static` 目录后,在后台通过刷新按钮更新资源映射。 halo-dev/halo#1907 @Yhcrown @muyunil +- 优化文章字数统计的算法。将中文和其他字符分开统计,中文按照字数计数,其他的语言默认按照标点分割来计数。 halo-dev/halo#1865 @Yhcrown @Tanhex +- 优化后台部分弹窗中表单在移动端的布局。 halo-dev/halo-admin#564 @ruibaby + +## Bug Fixes + +- 修复在 Windows 平台下,因为 H2 Database 文件被占用导致无法全站备份的问题。 halo-dev/halo#1867 @anshangPro +- 修复在 1.5.x 版本中,文章搜索没有关联查询内容(contents)的问题。 halo-dev/halo#1873 @Yhcrown @guqing +- 修复本地上传附件过程中如果发生异常,没有完整打印异常信息栈的问题。 halo-dev/halo#1913 @JohnNiang +- 修复在系统初始化之后,仍然可以通过 `/install` 跳转到登录页面的问题。 halo-dev/halo#1908 @Ljfanny @littlesleep +- 修复评论通知无法正常发送邮件的问题。 halo-dev/halo#1916 @JohnNiang @hapke24 +- 修复后台仪表盘中最近文章的标题过长导致样式异常的问题。 halo-dev/halo-admin#545 @Aanko @hotspring-zwb +- 修复后台带有分页的数据列表中,删除最后一页的所有数据后导致分页页码异常的问题。 halo-dev/halo-admin#550 @QuentinHsu @luohongqu +- 修复后台修复因为缓存数据,重新安装会出现循环进入 install 路由的问题。 halo-dev/halo-admin#558 @ruibaby @Ljfanny + +## Dependencies + +- 更新后台 @halo-dev/editor 版本。 halo-dev/halo-admin#562 @ruibaby + - 修复在改变编辑器布局后导致重复初始化编辑器的问题。 + # 1.5.2 ## Improvements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb827c9c61..9638493f30 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,64 +1,107 @@ -> 欢迎你参与 Halo 的开发,下面是参与代码贡献的指南,以供参考。 +# 开源参与指南 -### 代码贡献步骤 +欢迎关注并有想法参与 Halo 的开发,以下是关于如何参与到 Halo 项目的指南,仅供参考。 -#### 0. 提交 issue +## 发现 Issue -任何新功能或者功能改进建议都先提交 issue 讨论一下再进行开发,bug 修复可以直接提交 pull request。 +所有的代码尽可能都有依据(Issue),不是凭空产生。 -#### 1. Fork 此仓库 +### 寻找一个 Good First Issue -点击右上角的 `fork` 按钮即可。 +> 这个步骤非常适合首次贡献者。 -#### 2. Clone 仓库到本地 +在 [halo-dev](https://github.com/halo-dev) 组织下,有非常多的仓库。每个仓库下都有可能包含一些“首次贡献者”友好的 Issue,主要是为了给贡献者提供一个友好的体验。 该类 Issue +一般会用 `good-first-issue` 标签标记。标签 `good-first-issue` 表示该 Issue 不需要对 Halo 有深入的理解也能够参与。 -```bash -git clone https://github.com/{YOUR_USERNAME}/halo +请点击:[good-first-issue](https://github.com/issues?q=org%3Ahalo-dev+is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+no%3Aassignee+) +查看关于 Halo 的 Good First Issue。 -git submodule init +### 认领 Issue -git submodule update -``` +若对任何一个 Issue 感兴趣,请尝试在 Issue 进行回复,讨论解决 Issue 的思路。确定后可直接通过 `/assign` 或者 `/assign @GitHub 用户名` 认领这个 +Issue。这样可避免两位贡献者在同一个问题上花时间。 -#### 3. 创建新的开发分支 +## 代码贡献步骤 -```bash -git checkout -b {BRANCH_NAME} -``` +1. Fork 此仓库 -#### 4. 提交代码 + 点击 Halo 仓库主页右上角的 `Fork` 按钮即可。 -```bash -git push origin {BRANCH_NAME} -``` +2. Clone 仓库到本地 -#### 5. 提交 pull request + ```bash + git clone https://github.com/{YOUR_USERNAME}/halo --recursive + # 或者 git clone git@github.com:{YOUR_USERNAME}/halo.git --recursive + ``` -回到自己的仓库页面,选择 `New pull request` 按钮,创建 `Pull request` 到原仓库的 `master` 分支。 +3. 添加主仓库 -然后等待我们 Review 即可,如有 `Change Request`,再本地修改之后再次提交即可。 + 添加主仓库方便未来同步主仓库最新的 commits 以及创建新的分支。 -#### 6. 更新主仓库代码到自己的仓库 + ```bash + git remote add upstream https://github.com/halo-dev/halo.git + # 或者 git remote add upstream git@github.com:halo-dev/halo.git + git fetch upstream master + ``` -```bash -git remote add upstream git@github.com:halo-dev/halo.git +6. 创建新的开发分支 -git pull upstream master + 我们需要从主仓库的主分支创建一个新的开发分支。 -git push -``` + ```bash + git checkout upstream/master + git checkout -b {BRANCH_NAME} + ``` -### 开发规范 +7. 提交代码 + + ```bash + git add . + git commit -s -m "Fix a bug king" + git push origin {BRANCH_NAME} + ``` -请参考 [https://docs.halo.run/developer-guide/core/code-style](https://docs.halo.run/developer-guide/core/code-style),请确保所有代码格式化之后再提交。 +8. 合并主分支 -### Usage of Cherry Pick Script + 在提交 Pull Request 之前,尽量保证当前分支和主分支的代码尽可能同步,这时需要我们手动操作。示例: -We can use the cherry pick script to cherry-pick commits in pull request as follows: + ```bash + git fetch upstream/master + git merge upstream/master + git push origin {BRANCH_NAME} + ``` + +## Pull Request + +进入此阶段说明已经完成了代码的编写,测试和自测,并且准备好接受 Code Review。 + +### 创建 Pull Request + +回到自己的仓库页面,选择 `New pull request` 按钮,创建 `Pull request` 到原仓库的 `master` 分支。 +然后等待我们 Review 即可,如有 `Change Request`,再本地修改之后再次提交即可。 + +提交 Pull Request 的注意事项: + +- 提交 Pull Request 请充分自测。 +- 每个 Pull Request 尽量只解决一个 Issue,特殊情况除外。 +- 应尽可能多的添加单元测试,其他测试(集成测试和 E2E 测试)可看情况添加。 +- 不论需要解决的 Issue 发生在哪个版本,提交 Pull Request 的时候,请将主仓库的主分支设置为 `master`。例如:即使某个 Bug 于 Halo 1.4.x 被发现,但是提交 Pull Request 仍只针对 + `master` 分支,等待 Pull Request 合并之后,我们会通过 `/cherrypick release-1.4` 或者 `/cherry-pick release-1.4` 指令将此 Pull Request + 的修改应用到 `release-1.4` 和 `release-1.5` 分支上。 + +### 更新 commits + +Code Review 阶段可能需要 Pull Request 作者重新修改代码,请直接在当前分支 commit 并 push 即可,无需关闭并重新提交 Pull Request。示例: ```bash -GITHUB_USER={your_github_user} hack/cherry_pick_pull.sh upstream/{target_branch} {pull_request_number} +git add . +git commit -s -m "Refactor some code according code review" +git push origin bug/king ``` -> This script is from . +同时,若已经进入 Code Review 阶段,请不要强制推送 commits 到当前分支。否则 Reviewers 需要从头开始 Code Review。 + +### 开发规范 +请参考 [https://docs.halo.run/developer-guide/core/code-style](https://docs.halo.run/developer-guide/core/code-style) +,请确保所有代码格式化之后再提交。 diff --git a/README.md b/README.md index 9696f7de5d..46d67164ec 100755 --- a/README.md +++ b/README.md @@ -24,27 +24,7 @@ ## 快速开始 -### Fat Jar - -下载最新的 Halo 运行包: - -```bash -curl -L https://github.com/halo-dev/halo/releases/download/v1.5.2/halo-1.5.2.jar --output halo.jar -``` - -其他地址: - -```bash -java -jar halo.jar -``` - -### Docker - -```bash -docker run -it -d --name halo -p 8090:8090 -v ~/.halo:/root/.halo --restart=always halohub/halo:1.5.2 -``` - -详细部署文档请查阅: +详细部署文档请查阅: ## 在线体验 diff --git a/build.gradle b/build.gradle index 1ed2616255..4521519e71 100644 --- a/build.gradle +++ b/build.gradle @@ -75,6 +75,8 @@ ext { jsoupVersion = '1.14.3' embeddedRedisVersion = '0.6' diffUtilsVersion = '4.11' + githubApiVersion = '1.306' + githubClientVersion = '0.1.32' } dependencies { @@ -126,6 +128,9 @@ dependencies { implementation "com.google.zxing:core:$zxingVersion" implementation "io.github.java-diff-utils:java-diff-utils:$diffUtilsVersion" + implementation "org.kohsuke:github-api:$githubApiVersion" + implementation "com.spotify:github-client:$githubClientVersion" + implementation "org.iq80.leveldb:leveldb:$levelDbVersion" runtimeOnly "com.h2database:h2:$h2Version" runtimeOnly "mysql:mysql-connector-java" diff --git a/gradle.properties b/gradle.properties index 9eaa2f5c6b..625fdf5a81 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=1.5.3-SNAPSHOT +version=1.6.0-SNAPSHOT diff --git a/src/main/java/run/halo/app/Application.java b/src/main/java/run/halo/app/Application.java index 8b4f87ac15..af13934a54 100755 --- a/src/main/java/run/halo/app/Application.java +++ b/src/main/java/run/halo/app/Application.java @@ -15,9 +15,6 @@ public class Application { public static void main(String[] args) { - // Store the program args to construct launch command in version switch. - VmUtils.PROGRAM_ARGS.addAll(List.of(args)); - // Customize the spring config location System.setProperty("spring.config.additional-location", "optional:file:${user.home}/.halo/,optional:file:${user.home}/halo-dev/"); diff --git a/src/main/java/run/halo/app/controller/admin/api/VersionCtrlController.java b/src/main/java/run/halo/app/controller/admin/api/VersionCtrlController.java index 777c35f41f..8005a6d9c2 100644 --- a/src/main/java/run/halo/app/controller/admin/api/VersionCtrlController.java +++ b/src/main/java/run/halo/app/controller/admin/api/VersionCtrlController.java @@ -11,6 +11,11 @@ import run.halo.app.model.dto.VersionInfoDTO; import run.halo.app.service.HaloVersionCtrlService; +/** + * Version Control Controller + * + * @author Chen_Kunqiu + */ @Slf4j @RestController @RequestMapping("/api/admin/version") diff --git a/src/main/java/run/halo/app/controller/content/MainController.java b/src/main/java/run/halo/app/controller/content/MainController.java index 70e2437d63..5b5ac12ffc 100644 --- a/src/main/java/run/halo/app/controller/content/MainController.java +++ b/src/main/java/run/halo/app/controller/content/MainController.java @@ -10,6 +10,7 @@ import run.halo.app.exception.ServiceException; import run.halo.app.model.entity.User; import run.halo.app.model.properties.BlogProperties; +import run.halo.app.model.properties.PrimaryProperties; import run.halo.app.model.support.HaloConst; import run.halo.app.service.OptionService; import run.halo.app.service.UserService; @@ -63,10 +64,14 @@ public String version() { @GetMapping("install") public void installation(HttpServletResponse response) throws IOException { - String installRedirectUri = - StringUtils.appendIfMissing(this.haloProperties.getAdminPath(), "/") - + INSTALL_REDIRECT_URI; - response.sendRedirect(installRedirectUri); + boolean isInstalled = optionService + .getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false); + if (!isInstalled) { + String installRedirectUri = + StringUtils.appendIfMissing(this.haloProperties.getAdminPath(), "/") + + INSTALL_REDIRECT_URI; + response.sendRedirect(installRedirectUri); + } } @GetMapping("avatar") diff --git a/src/main/java/run/halo/app/handler/file/LocalFileHandler.java b/src/main/java/run/halo/app/handler/file/LocalFileHandler.java index 64360fa0bf..7919a5e57c 100644 --- a/src/main/java/run/halo/app/handler/file/LocalFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/LocalFileHandler.java @@ -152,7 +152,8 @@ public UploadResult upload(@NonNull MultipartFile file) { file.getOriginalFilename(), uploadFilePath.getFullPath()); return uploadResult; } catch (IOException e) { - throw new FileOperationException("上传附件失败").setErrorData(uploadFilePath.getFullPath()); + throw new FileOperationException("上传附件失败", e) + .setErrorData(uploadFilePath.getFullPath()); } } diff --git a/src/main/java/run/halo/app/listener/comment/CommentEventListener.java b/src/main/java/run/halo/app/listener/comment/CommentEventListener.java index bba9913b82..779233afb2 100644 --- a/src/main/java/run/halo/app/listener/comment/CommentEventListener.java +++ b/src/main/java/run/halo/app/listener/comment/CommentEventListener.java @@ -4,9 +4,9 @@ import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionalEventListener; import run.halo.app.event.comment.CommentNewEvent; import run.halo.app.event.comment.CommentReplyEvent; import run.halo.app.exception.ServiceException; @@ -88,12 +88,12 @@ public CommentEventListener(MailService mailService, OptionService optionService } /** - * Received a new new comment event. + * Received a new comment event. * * @param newEvent new comment event. */ @Async - @EventListener + @TransactionalEventListener public void handleCommentNewEvent(CommentNewEvent newEvent) { Boolean newCommentNotice = optionService .getByPropertyOrDefault(CommentProperties.NEW_NOTICE, Boolean.class, false); @@ -181,7 +181,7 @@ public void handleCommentNewEvent(CommentNewEvent newEvent) { * @param replyEvent reply comment event. */ @Async - @EventListener + @TransactionalEventListener public void handleCommentReplyEvent(CommentReplyEvent replyEvent) { Boolean replyCommentNotice = optionService .getByPropertyOrDefault(CommentProperties.REPLY_NOTICE, Boolean.class, false); diff --git a/src/main/java/run/halo/app/model/entity/BasePost.java b/src/main/java/run/halo/app/model/entity/BasePost.java index 94a480fc25..de485fe498 100644 --- a/src/main/java/run/halo/app/model/entity/BasePost.java +++ b/src/main/java/run/halo/app/model/entity/BasePost.java @@ -143,7 +143,7 @@ public class BasePost extends BaseEntity { private Integer topPriority; /** - * Likes + * Likes. */ @Column(name = "likes") @ColumnDefault("0") @@ -169,7 +169,7 @@ public class BasePost extends BaseEntity { private String metaDescription; /** - * Content word count + * Content word count. */ @Column(name = "word_count") @ColumnDefault("0") @@ -188,6 +188,7 @@ public class BasePost extends BaseEntity { @Transient private PatchedContent content; + @Override public void prePersist() { super.prePersist(); @@ -243,6 +244,7 @@ public void prePersist() { if (version == null || version < 0) { version = 1; } + // Clear the value of the deprecated attributes this.originalContent = ""; this.formatContent = ""; diff --git a/src/main/java/run/halo/app/service/impl/BasePostServiceImpl.java b/src/main/java/run/halo/app/service/impl/BasePostServiceImpl.java index 157d13316d..5e148a2c08 100644 --- a/src/main/java/run/halo/app/service/impl/BasePostServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/BasePostServiceImpl.java @@ -64,6 +64,10 @@ public abstract class BasePostServiceImpl private static final Pattern BLANK_PATTERN = Pattern.compile("\\s"); + private static final String CHINESE_REGEX = "[^\\x00-\\xff]"; + + private static final String PUNCTUATION_REGEX = "[\\p{P}\\p{S}\\p{Z}\\s]+"; + public BasePostServiceImpl(BasePostRepository basePostRepository, OptionService optionService, ContentService contentService, @@ -301,7 +305,6 @@ public POST createOrUpdateBy(POST post) { PatchedContent postContent = post.getContent(); // word count stat post.setWordCount(htmlFormatWordCount(postContent.getContent())); - POST savedPost; // Create or update post if (ServiceUtils.isEmptyId(post.getId())) { @@ -484,7 +487,7 @@ protected void generateAndSetSummaryIfAbsent(POST } } - // CS304 issue link : https://github.com/halo-dev/halo/issues/1224 + // CS304 issue link : https://github.com/halo-dev/halo/issues/1759 /** * @param htmlContent the markdown style content @@ -498,6 +501,39 @@ public static long htmlFormatWordCount(String htmlContent) { String cleanContent = HaloUtils.cleanHtmlTag(htmlContent); + String tempString = cleanContent.replaceAll(CHINESE_REGEX, ""); + + String otherString = cleanContent.replaceAll(CHINESE_REGEX, " "); + + int chineseWordCount = cleanContent.length() - tempString.length(); + + String[] otherWords = otherString.split(PUNCTUATION_REGEX); + + int otherWordLength = otherWords.length; + + if (otherWordLength > 0 && otherWords[0].length() == 0) { + otherWordLength--; + } + + if (otherWords.length > 1 && otherWords[otherWords.length - 1].length() == 0) { + otherWordLength--; + } + + return chineseWordCount + otherWordLength; + } + + /** + * @param htmlContent the markdown style content + * @return character count except space and line separator + */ + + public static long htmlFormatCharacterCount(String htmlContent) { + if (htmlContent == null) { + return 0; + } + + String cleanContent = HaloUtils.cleanHtmlTag(htmlContent); + Matcher matcher = BLANK_PATTERN.matcher(cleanContent); int count = 0; diff --git a/src/main/java/run/halo/app/service/impl/StaticStorageServiceImpl.java b/src/main/java/run/halo/app/service/impl/StaticStorageServiceImpl.java index 341406f0ad..1e1367ced9 100644 --- a/src/main/java/run/halo/app/service/impl/StaticStorageServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/StaticStorageServiceImpl.java @@ -53,7 +53,12 @@ public StaticStorageServiceImpl(HaloProperties haloProperties, @Override public List listStaticFolder() { - return listStaticFileTree(staticDir); + + List staticFiles = listStaticFileTree(staticDir); + + onChange(); // To update the mapping of local files. + + return staticFiles; } @Nullable diff --git a/src/main/java/run/halo/app/utils/VmUtils.java b/src/main/java/run/halo/app/utils/VmUtils.java index 2136613cbe..209dc4f145 100644 --- a/src/main/java/run/halo/app/utils/VmUtils.java +++ b/src/main/java/run/halo/app/utils/VmUtils.java @@ -5,20 +5,24 @@ import java.lang.management.RuntimeMXBean; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.stereotype.Component; import run.halo.app.exception.ServiceException; -import run.halo.app.model.enums.SystemType; /** * The utils to get some info of JVM. * * @author Chen_Kunqiu */ +@Component public class VmUtils { private static final RuntimeMXBean RUNTIME_MX_BEAN = ManagementFactory.getRuntimeMXBean(); - public static final List PROGRAM_ARGS = new ArrayList<>(); public static final Path CURR_JAR; public static final Path CURR_JAR_DIR; + private static ApplicationArguments appArgs; static { String path = @@ -34,6 +38,10 @@ public class VmUtils { CURR_JAR_DIR = CURR_JAR.getParent(); } + @Autowired + private void setAppArgs(ApplicationArguments appArgs) { + VmUtils.appArgs = appArgs; + } /** * Get the command to launch the halo jar. @@ -46,12 +54,12 @@ public class VmUtils { public static List getSameLaunchCommand() { List cmd = new ArrayList<>(); cmd.add(getJvmExecutablePath()); - cmd.addAll(getVmArguments()); + cmd.addAll(getVmOptions()); cmd.add("-classpath"); cmd.add(getClassPath()); cmd.add("-jar"); cmd.add(getRunningJar()); - cmd.addAll(PROGRAM_ARGS); + cmd.addAll(getProgramArgs()); return cmd; } @@ -67,14 +75,15 @@ public static List getSameLaunchCommand() { public static List getNewLaunchCommand(String newTarget) { List cmd = new ArrayList<>(); cmd.add(getJvmExecutablePath()); - cmd.addAll(getVmArguments()); + cmd.addAll(getVmOptions()); cmd.add("-classpath"); String classPath = getClassPath(); final String nonVmPartOfCmd = getNonVmPartOfCmd(); - if (CURR_JAR == null || CURR_JAR.getFileName() == null) { + final Path fileName = CURR_JAR.getFileName(); + if (fileName == null) { throw new ServiceException("无法获取当前运行的JAR"); } - final String jarName = CURR_JAR.getFileName().toString(); + final String jarName = fileName.toString(); final int endIdx = nonVmPartOfCmd.indexOf(jarName) + jarName.length(); /* @@ -88,7 +97,7 @@ public static List getNewLaunchCommand(String newTarget) { cmd.add(classPath); cmd.add("-jar"); cmd.add(newTarget); - cmd.addAll(PROGRAM_ARGS); + cmd.addAll(getProgramArgs()); return cmd; } @@ -119,10 +128,25 @@ public static String getJvmExecutablePath() { * * @return the VM arguments */ - public static List getVmArguments() { + public static List getVmOptions() { return RUNTIME_MX_BEAN.getInputArguments(); } + + /** + * Get the program arguments passed to JVM. + * + *

For example,
+ * {@code java -jar -Da=1 Test.jar b=2}
+ * -->
+ * {@code b=2} + * + * @return the VM arguments + */ + public static List getProgramArgs() { + return Arrays.asList(appArgs.getSourceArgs()); + } + /** * Get the class path which the JVM relies on. * diff --git a/src/test/java/Test.java b/src/test/java/Test.java new file mode 100644 index 0000000000..7c0d6de50b --- /dev/null +++ b/src/test/java/Test.java @@ -0,0 +1,12 @@ +import java.io.IOException; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; + +public class Test { + public static void main(String[] args) throws IOException { + final GitHub github = GitHub.connect(); + final GHRepository repo = github.createRepository("halo").owner("halo-dev") + .homepage("https://github.com/halo-dev/halo").create(); + System.out.println(repo.getReleases()); + } +} diff --git a/src/test/java/run/halo/app/service/impl/HTMLWordCountTest.java b/src/test/java/run/halo/app/service/impl/HTMLWordCountTest.java index 6d6d7d50e5..3624663914 100644 --- a/src/test/java/run/halo/app/service/impl/HTMLWordCountTest.java +++ b/src/test/java/run/halo/app/service/impl/HTMLWordCountTest.java @@ -59,6 +59,20 @@ public class HTMLWordCountTest { String emptyString = ""; + String englishString = "I have a red apple"; + + String hybridString = "I have a red apple哈哈"; + + + String complexText2 = "Hi,Jessica!这个project的schedule有些问题。"; + + String complexText3 = "The company had a meeting yesterday。Why did you ask for leave?"; + + String complexText4 = "这是一个句子,但是只有中文。"; + + String complexText5 = + "The wind and the moon are all beautiful, love and hate are all romantic."; + @Test void pictureTest() { assertEquals("图片字数测试".length(), @@ -128,4 +142,42 @@ void emptyTest() { assertEquals(0, BasePostServiceImpl.htmlFormatWordCount(MarkdownUtils.renderHtml(emptyString))); } -} + + @Test + void englishTest() { + assertEquals(5, + BasePostServiceImpl.htmlFormatWordCount(MarkdownUtils.renderHtml(englishString))); + } + + @Test + void hybridTest() { + assertEquals(7, + BasePostServiceImpl.htmlFormatWordCount(MarkdownUtils.renderHtml(hybridString))); + } + + @Test + void englishCharacterTest() { + assertEquals(14, + BasePostServiceImpl.htmlFormatCharacterCount(MarkdownUtils.renderHtml(englishString))); + } + + @Test + void hybridCharacterTest() { + assertEquals(16, + BasePostServiceImpl.htmlFormatCharacterCount(MarkdownUtils.renderHtml(hybridString))); + } + + @Test + void moreComplexTest() { + assertEquals(14, + BasePostServiceImpl.htmlFormatWordCount(MarkdownUtils.renderHtml(complexText2))); + assertEquals(14, + BasePostServiceImpl.htmlFormatWordCount(MarkdownUtils.renderHtml(complexText3))); + assertEquals(14, + BasePostServiceImpl.htmlFormatWordCount(MarkdownUtils.renderHtml(complexText4))); + assertEquals(14, + BasePostServiceImpl.htmlFormatWordCount(MarkdownUtils.renderHtml(complexText5))); + } + + +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/utils/VmUtilsTest.java b/src/test/java/run/halo/app/utils/VmUtilsTest.java index 4c1c2ce752..765cb4cbd7 100644 --- a/src/test/java/run/halo/app/utils/VmUtilsTest.java +++ b/src/test/java/run/halo/app/utils/VmUtilsTest.java @@ -49,12 +49,12 @@ void testGetJvmExecutablePath2() { @Test void testGetVmArguments() { - final List vmArguments = VmUtils.getVmArguments(); + final List vmArguments = VmUtils.getVmOptions(); assertNotNull(vmArguments); } @Test void testGetVmArguments2() { - final List vmArguments = VmUtils.getVmArguments(); + final List vmArguments = VmUtils.getVmOptions(); assertEquals(bean.getInputArguments(), vmArguments); } From e983cca3b45a7fd6366f5d80641cfd93587f6eff Mon Sep 17 00:00:00 2001 From: camsyn <2742046922@qq.com> Date: Sat, 21 May 2022 19:54:24 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E6=94=B9=E7=94=A8Github=20Client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../halo/app/config/HaloConfiguration.java | 19 +++++- .../halo/app/model/dto/VersionInfoDTO.java | 27 +++++--- .../run/halo/app/model/entity/Assets.java | 34 ---------- .../run/halo/app/model/entity/Author.java | 45 -------------- .../model/entity/GithubApiVersionJson.java | 53 ---------------- .../run/halo/app/model/entity/Reactions.java | 26 -------- .../run/halo/app/model/entity/Uploader.java | 45 -------------- .../impl/HaloVersionCtrlServiceImpl.java | 62 ++++++++++--------- src/test/java/Test.java | 49 +++++++++++++-- 9 files changed, 113 insertions(+), 247 deletions(-) delete mode 100644 src/main/java/run/halo/app/model/entity/Assets.java delete mode 100644 src/main/java/run/halo/app/model/entity/Author.java delete mode 100644 src/main/java/run/halo/app/model/entity/GithubApiVersionJson.java delete mode 100644 src/main/java/run/halo/app/model/entity/Reactions.java delete mode 100644 src/main/java/run/halo/app/model/entity/Uploader.java diff --git a/src/main/java/run/halo/app/config/HaloConfiguration.java b/src/main/java/run/halo/app/config/HaloConfiguration.java index def9dfaff8..59819d2a87 100644 --- a/src/main/java/run/halo/app/config/HaloConfiguration.java +++ b/src/main/java/run/halo/app/config/HaloConfiguration.java @@ -1,10 +1,14 @@ package run.halo.app.config; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import lombok.extern.slf4j.Slf4j; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; @@ -49,7 +53,7 @@ public class HaloConfiguration { private final StringRedisTemplate stringRedisTemplate; public HaloConfiguration(HaloProperties haloProperties, - StringRedisTemplate stringRedisTemplate) { + StringRedisTemplate stringRedisTemplate) { this.haloProperties = haloProperties; this.stringRedisTemplate = stringRedisTemplate; } @@ -70,6 +74,19 @@ RestTemplate httpsRestTemplate(RestTemplateBuilder builder) return httpsRestTemplate; } + @Bean + GHRepository haloRepo() { + final GitHub github; + GHRepository repo = null; + try { + github = new GitHubBuilder().build(); + repo = github.getRepository("halo-dev/halo"); + } catch (IOException e) { + log.info("无法连接Github,请检查网络"); + } + return repo; + } + @Bean @ConditionalOnMissingBean AbstractStringCacheStore stringCacheStore() { diff --git a/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java b/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java index 962c33c2e0..75cea3241d 100644 --- a/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java +++ b/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java @@ -1,16 +1,18 @@ package run.halo.app.model.dto; +import java.io.IOException; import lombok.Builder; import lombok.Data; import lombok.ToString; -import run.halo.app.model.entity.Assets; -import run.halo.app.model.entity.GithubApiVersionJson; +import org.kohsuke.github.GHAsset; +import org.kohsuke.github.GHRelease; +import run.halo.app.exception.ServiceException; /** * Version information of a release. * *

This is a simplified representation of - * {@linkplain run.halo.app.model.entity.GithubApiVersionJson}. + * {@linkplain org.kohsuke.github.GHRelease}. * * @author Chen_Kunqiu */ @@ -29,13 +31,20 @@ public class VersionInfoDTO { /** * Initially convert the JSON given by Github into VO. * - * @param json the json data given by github api + * @param release the json data given by github api * @return the simplified VO object */ - public static VersionInfoDTO convertFrom(GithubApiVersionJson json) { - final Assets asset = json.getAssets().get(0); - return VersionInfoDTO.builder().version(json.getTagName()).desc(json.getBody()) - .githubUrl(json.getHtmlUrl()).jarName(asset.getName()).size(asset.getSize()) - .downloadUrl(asset.getBrowserDownloadUrl()).build(); + public static VersionInfoDTO convertFrom(GHRelease release) { + final VersionInfoDTO versionInfoDTO = + VersionInfoDTO.builder().version(release.getTagName()).desc(release.getBody()) + .githubUrl(release.getHtmlUrl().toString()).jarName(release.getName()).build(); + try { + final GHAsset asset = release.listAssets().iterator().next(); + versionInfoDTO.setSize(asset.getSize()); + versionInfoDTO.setDownloadUrl(asset.getBrowserDownloadUrl()); + } catch (IOException e) { + throw new ServiceException("This release has no assert."); + } + return versionInfoDTO; } } diff --git a/src/main/java/run/halo/app/model/entity/Assets.java b/src/main/java/run/halo/app/model/entity/Assets.java deleted file mode 100644 index d72ff3ef9e..0000000000 --- a/src/main/java/run/halo/app/model/entity/Assets.java +++ /dev/null @@ -1,34 +0,0 @@ - -package run.halo.app.model.entity; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Date; -import lombok.Data; - -/** - * The Java class relevant to the Json returned by github api. - * - * @author Chen_Kunqiu - */ -@Data -public class Assets { - private String url; - private int id; - @JsonProperty("node_id") - private String nodeId; - private String name; - private String label; - private Uploader uploader; - @JsonProperty("content_type") - private String contentType; - private String state; - private long size; - @JsonProperty("download_count") - private int downloadCount; - @JsonProperty("created_at") - private Date createdAt; - @JsonProperty("updated_at") - private Date updatedAt; - @JsonProperty("browser_download_url") - private String browserDownloadUrl; -} \ No newline at end of file diff --git a/src/main/java/run/halo/app/model/entity/Author.java b/src/main/java/run/halo/app/model/entity/Author.java deleted file mode 100644 index 61a592f3b0..0000000000 --- a/src/main/java/run/halo/app/model/entity/Author.java +++ /dev/null @@ -1,45 +0,0 @@ -package run.halo.app.model.entity; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * The Java class relevant to the Json returned by github api. - * - * @author Chen_Kunqiu - */ -@Data -public class Author { - private String login; - private int id; - @JsonProperty("node_id") - private String nodeId; - @JsonProperty("avatar_url") - private String avatarUrl; - @JsonProperty("gravatar_id") - private String gravatarId; - private String url; - @JsonProperty("html_url") - private String htmlUrl; - @JsonProperty("followers_url") - private String followersUrl; - @JsonProperty("following_url") - private String followingUrl; - @JsonProperty("gists_url") - private String gistsUrl; - @JsonProperty("starred_url") - private String starredUrl; - @JsonProperty("subscriptions_url") - private String subscriptionsUrl; - @JsonProperty("organizations_url") - private String organizationsUrl; - @JsonProperty("repos_url") - private String reposUrl; - @JsonProperty("events_url") - private String eventsUrl; - @JsonProperty("received_events_url") - private String receivedEventsUrl; - private String type; - @JsonProperty("site_admin") - private boolean siteAdmin; -} diff --git a/src/main/java/run/halo/app/model/entity/GithubApiVersionJson.java b/src/main/java/run/halo/app/model/entity/GithubApiVersionJson.java deleted file mode 100644 index 448a6e95da..0000000000 --- a/src/main/java/run/halo/app/model/entity/GithubApiVersionJson.java +++ /dev/null @@ -1,53 +0,0 @@ -package run.halo.app.model.entity; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Date; -import java.util.List; -import lombok.Data; -import lombok.ToString; - -/** - * The Java class relevant to the Json returned by github api. - * - *

The json structure refers to the response of - * - * https://api.github.com/repos/halo-dev/halo/releases/latest - * . - * - * @author Chen_Kunqiu - */ -@Data -@ToString -public class GithubApiVersionJson { - private String url; - @JsonProperty("assets_url") - private String assetsUrl; - @JsonProperty("upload_url") - private String uploadUrl; - @JsonProperty("html_url") - private String htmlUrl; - private int id; - private Author author; - @JsonProperty("node_id") - private String nodeId; - @JsonProperty("tag_name") - private String tagName; - @JsonProperty("target_commitish") - private String targetCommitish; - private String name; - private boolean draft; - private boolean prerelease; - @JsonProperty("created_at") - private Date createdAt; - @JsonProperty("published_at") - private Date publishedAt; - private List assets; - @JsonProperty("tarball_url") - private String tarballUrl; - @JsonProperty("zipball_url") - private String zipballUrl; - private String body; - private Reactions reactions; - @JsonProperty("mentions_count") - private int mentionsCount; -} \ No newline at end of file diff --git a/src/main/java/run/halo/app/model/entity/Reactions.java b/src/main/java/run/halo/app/model/entity/Reactions.java deleted file mode 100644 index 0cf539a3ba..0000000000 --- a/src/main/java/run/halo/app/model/entity/Reactions.java +++ /dev/null @@ -1,26 +0,0 @@ -package run.halo.app.model.entity; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * The Java class relevant to the Json returned by github api. - * - * @author Chen_Kunqiu - */ -@Data -public class Reactions { - private String url; - @JsonProperty("total_count") - private int totalCount; - @JsonProperty("+1") - private int plusOne; - @JsonProperty("-1") - private int minusOne; - private int laugh; - private int hooray; - private int confused; - private int heart; - private int rocket; - private int eyes; -} \ No newline at end of file diff --git a/src/main/java/run/halo/app/model/entity/Uploader.java b/src/main/java/run/halo/app/model/entity/Uploader.java deleted file mode 100644 index 81904ab53a..0000000000 --- a/src/main/java/run/halo/app/model/entity/Uploader.java +++ /dev/null @@ -1,45 +0,0 @@ -package run.halo.app.model.entity; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * The Java class relevant to the Json returned by github api. - * - * @author Chen_Kunqiu - */ -@Data -public class Uploader { - private String login; - private int id; - @JsonProperty("node_id") - private String nodeId; - @JsonProperty("avatar_url") - private String avatarUrl; - @JsonProperty("gravatar_id") - private String gravatarId; - private String url; - @JsonProperty("html_url") - private String htmlUrl; - @JsonProperty("followers_url") - private String followersUrl; - @JsonProperty("following_url") - private String followingUrl; - @JsonProperty("gists_url") - private String gistsUrl; - @JsonProperty("starred_url") - private String starredUrl; - @JsonProperty("subscriptions_url") - private String subscriptionsUrl; - @JsonProperty("organizations_url") - private String organizationsUrl; - @JsonProperty("repos_url") - private String reposUrl; - @JsonProperty("events_url") - private String eventsUrl; - @JsonProperty("received_events_url") - private String receivedEventsUrl; - private String type; - @JsonProperty("site_admin") - private boolean siteAdmin; -} \ No newline at end of file diff --git a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java index a17fc9c448..03bfa8d8e4 100644 --- a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java @@ -8,7 +8,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -17,6 +16,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.SystemUtils; import org.jetbrains.annotations.NotNull; +import org.kohsuke.github.GHRelease; +import org.kohsuke.github.GHRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; @@ -28,7 +29,6 @@ import org.springframework.web.client.RestTemplate; import run.halo.app.exception.ServiceException; import run.halo.app.model.dto.VersionInfoDTO; -import run.halo.app.model.entity.GithubApiVersionJson; import run.halo.app.model.support.HaloConst; import run.halo.app.service.HaloVersionCtrlService; import run.halo.app.utils.VmUtils; @@ -41,6 +41,10 @@ @Service @Slf4j public class HaloVersionCtrlServiceImpl implements HaloVersionCtrlService, ApplicationContextAware { + + @Autowired + GHRepository haloRepo; + public static final String GITHUB_RELEASES_API = "https://api.github.com/repos/halo-dev/halo/releases"; public static final String GITHUB_RELEASES_LATEST_API = @@ -63,7 +67,6 @@ public class HaloVersionCtrlServiceImpl implements HaloVersionCtrlService, Appli private static final Path JAR_DIR = VmUtils.CURR_JAR_DIR; - private ApplicationContext context; @Autowired @@ -99,17 +102,18 @@ public boolean isInLocal(String tagName) { @Override public List getAllReleasesInfo() { - // final RestTemplate restTemplate = builder.build(); - final GithubApiVersionJson[] data = - restTemplate.getForObject(GITHUB_RELEASES_API, GithubApiVersionJson[].class); - if (data == null) { + + try { + final List releases = haloRepo.listReleases().toList(); + return releases.stream().map(data -> { + final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(data); + versionInfo.setInLocal(isInLocal(data.getTagName())); + return versionInfo; + }).collect(Collectors.toList()); + } catch (IOException e) { throw new ServiceException("从github api中拉取版本信息失败, url: " + GITHUB_RELEASES_API); } - return Arrays.stream(data).map(json -> { - final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(json); - versionInfo.setInLocal(isInLocal(json.getTagName())); - return versionInfo; - }).collect(Collectors.toList()); + } @Override @@ -117,29 +121,29 @@ public VersionInfoDTO getReleaseInfoByTag(String tagName) { if (!tagName.startsWith("v")) { tagName = "v" + tagName; } - String url = GITHUB_RELEASES_TAG_API_BASE + tagName; - // final RestTemplate restTemplate = builder.build(); - final GithubApiVersionJson json = - restTemplate.getForObject(url, GithubApiVersionJson.class); - if (json == null) { + try { + final GHRelease release = haloRepo.getReleaseByTagName(tagName); + final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(release); + versionInfo.setInLocal(isInLocal(tagName)); + return versionInfo; + } catch (IOException e) { + String url = GITHUB_RELEASES_TAG_API_BASE + tagName; throw new ServiceException("从github api中拉取版本信息失败, url: " + url); } - final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(json); - versionInfo.setInLocal(isInLocal(tagName)); - return versionInfo; } @Override public VersionInfoDTO getLatestReleaseInfo() { // final RestTemplate restTemplate = builder.build(); - final GithubApiVersionJson json = - restTemplate.getForObject(GITHUB_RELEASES_LATEST_API, GithubApiVersionJson.class); - if (json == null) { + try { + final GHRelease release = haloRepo.getLatestRelease(); + final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(release); + versionInfo.setInLocal(isInLocal(release.getTagName())); + return versionInfo; + } catch (IOException e) { throw new ServiceException("从github api中拉取版本信息失败, url: " + GITHUB_RELEASES_LATEST_API); } - final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(json); - versionInfo.setInLocal(isInLocal(json.getTagName())); - return versionInfo; + } @Override @@ -258,7 +262,7 @@ public void setApplicationContext(@NotNull ApplicationContext applicationContext * Download the specified jar by tagName into the given destination directory. * * @param tagName the specified tag name of the release - * @param dstDir destination directory + * @param dstDir destination directory * @return the final path of the downloaded jar */ private String downloadSpecifiedJar(String tagName, String dstDir) { @@ -279,7 +283,7 @@ private String downloadSpecifiedJar(String tagName, String dstDir) { *

As the jar file is very big, it is not appropriate to load it as byte[] in memory, * so that directly forwarded it to the file system. * - * @param url the url to download resource + * @param url the url to download resource * @param tarFile target file */ private void download(String url, Path tarFile) { @@ -344,7 +348,7 @@ private Path copyTargetFromRemote(String tagName) { * Backup and delete the specified jar file. * The implementation in Windows differs as the file lock in Windows. * - * @param curJar the specified jar path + * @param curJar the specified jar path * @param backupTarget the path of the backup * @throws IOException the exception may arise in the process of backup */ diff --git a/src/test/java/Test.java b/src/test/java/Test.java index 7c0d6de50b..6892038127 100644 --- a/src/test/java/Test.java +++ b/src/test/java/Test.java @@ -1,12 +1,51 @@ +import com.spotify.github.v3.clients.GitDataClient; +import com.spotify.github.v3.clients.GitHubClient; +import com.spotify.github.v3.clients.GithubAppClient; +import com.spotify.github.v3.clients.RepositoryClient; +import com.spotify.github.v3.git.Tag; +import com.spotify.github.v3.repos.Repository; import java.io.IOException; +import java.net.URI; +import java.util.concurrent.ExecutionException; +import org.kohsuke.github.GHAsset; +import org.kohsuke.github.GHRelease; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; +import org.kohsuke.github.PagedIterable; public class Test { - public static void main(String[] args) throws IOException { - final GitHub github = GitHub.connect(); - final GHRepository repo = github.createRepository("halo").owner("halo-dev") - .homepage("https://github.com/halo-dev/halo").create(); - System.out.println(repo.getReleases()); + + public static void testGithubClient() throws ExecutionException, InterruptedException { + final GitHubClient gitHubClient = GitHubClient.create(URI.create("https://api.github.com"), + "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"); + final RepositoryClient halo = gitHubClient.createRepositoryClient("Camsyn", "halo"); + System.out.println(halo.getRepository().get()); + final GitDataClient gitDataClient = gitHubClient.createGitDataClient("Camsyn", "halo"); + final Repository repository = halo.getRepository().get(); + System.out.println(repository.releasesUrl()); + System.out.println(repository.downloadsUrl()); + + +// final Tag tag = gitDataClient.getTag("v1.5.3").get(); +// System.out.println(tag); + + } + + public static void main(String[] args) + throws IOException, ExecutionException, InterruptedException { +// testGithubClient(); + testGithubAPI(); + } + + private static void testGithubAPI() throws IOException { + final GitHub github = new GitHubBuilder().build(); + final GHRepository repo = github.getRepository("halo-dev/halo"); + System.out.println(github.getApiUrl()); + final GHRelease release = repo.getReleaseByTagName("v1.5.3"); + final PagedIterable assets = release.listAssets(); + System.out.println(assets.toList().get(0).getBrowserDownloadUrl()); +// System.out.println(github.getApiUrl()); + } } From f88a4a41fe158ccddb39c987d44b8d348c5a264b Mon Sep 17 00:00:00 2001 From: camsyn <2742046922@qq.com> Date: Sun, 22 May 2022 14:51:58 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E6=94=B9=E7=94=A8Github=20Client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../halo/app/config/HaloConfiguration.java | 30 +++++---- .../halo/app/model/dto/VersionInfoDTO.java | 4 +- .../impl/HaloVersionCtrlServiceImpl.java | 64 ++++++++++++++++--- .../impl/HaloVersionCtrlServiceImplTest.java | 2 + 5 files changed, 80 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index 4521519e71..dfc35d55d9 100644 --- a/build.gradle +++ b/build.gradle @@ -129,7 +129,7 @@ dependencies { implementation "io.github.java-diff-utils:java-diff-utils:$diffUtilsVersion" implementation "org.kohsuke:github-api:$githubApiVersion" - implementation "com.spotify:github-client:$githubClientVersion" + implementation "org.iq80.leveldb:leveldb:$levelDbVersion" runtimeOnly "com.h2database:h2:$h2Version" diff --git a/src/main/java/run/halo/app/config/HaloConfiguration.java b/src/main/java/run/halo/app/config/HaloConfiguration.java index 59819d2a87..02bb0c6ed1 100644 --- a/src/main/java/run/halo/app/config/HaloConfiguration.java +++ b/src/main/java/run/halo/app/config/HaloConfiguration.java @@ -2,13 +2,29 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.util.Timer; +import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; +import okhttp3.Cache; +import okhttp3.OkHttpClient; +import org.jetbrains.annotations.NotNull; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHubBuilder; +import org.kohsuke.github.GitHubRateLimitHandler; +import org.kohsuke.github.HttpConnector; +import org.kohsuke.github.connector.GitHubConnector; +import org.kohsuke.github.connector.GitHubConnectorRequest; +import org.kohsuke.github.connector.GitHubConnectorResponse; +import org.kohsuke.github.extras.OkHttp3Connector; +import org.kohsuke.github.extras.okhttp3.OkHttpGitHubConnector; +import org.kohsuke.github.internal.GitHubConnectorHttpConnectorAdapter; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; @@ -20,6 +36,7 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.client.RestTemplate; @@ -29,6 +46,7 @@ import run.halo.app.cache.RedisCacheStore; import run.halo.app.config.attributeconverter.AttributeConverterAutoGenerateConfiguration; import run.halo.app.config.properties.HaloProperties; +import run.halo.app.exception.ServiceException; import run.halo.app.repository.base.BaseRepositoryImpl; import run.halo.app.utils.HttpClientUtils; @@ -74,18 +92,6 @@ RestTemplate httpsRestTemplate(RestTemplateBuilder builder) return httpsRestTemplate; } - @Bean - GHRepository haloRepo() { - final GitHub github; - GHRepository repo = null; - try { - github = new GitHubBuilder().build(); - repo = github.getRepository("halo-dev/halo"); - } catch (IOException e) { - log.info("无法连接Github,请检查网络"); - } - return repo; - } @Bean @ConditionalOnMissingBean diff --git a/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java b/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java index 75cea3241d..53ee2ad546 100644 --- a/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java +++ b/src/main/java/run/halo/app/model/dto/VersionInfoDTO.java @@ -37,9 +37,11 @@ public class VersionInfoDTO { public static VersionInfoDTO convertFrom(GHRelease release) { final VersionInfoDTO versionInfoDTO = VersionInfoDTO.builder().version(release.getTagName()).desc(release.getBody()) - .githubUrl(release.getHtmlUrl().toString()).jarName(release.getName()).build(); + .githubUrl(release.getHtmlUrl().toString()) + .jarName("halo.jar").build(); try { final GHAsset asset = release.listAssets().iterator().next(); + versionInfoDTO.setJarName(asset.getName()); versionInfoDTO.setSize(asset.getSize()); versionInfoDTO.setDownloadUrl(asset.getBrowserDownloadUrl()); } catch (IOException e) { diff --git a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java index 03bfa8d8e4..3eec29611f 100644 --- a/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/HaloVersionCtrlServiceImpl.java @@ -12,12 +12,18 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; import org.apache.commons.lang3.SystemUtils; import org.jetbrains.annotations.NotNull; import org.kohsuke.github.GHRelease; import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHubBuilder; +import org.kohsuke.github.GitHubRateLimitHandler; +import org.kohsuke.github.connector.GitHubConnectorResponse; +import org.kohsuke.github.extras.okhttp3.OkHttpGitHubConnector; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; @@ -42,8 +48,7 @@ @Slf4j public class HaloVersionCtrlServiceImpl implements HaloVersionCtrlService, ApplicationContextAware { - @Autowired - GHRepository haloRepo; + private GHRepository haloRepo; public static final String GITHUB_RELEASES_API = "https://api.github.com/repos/halo-dev/halo/releases"; @@ -71,8 +76,18 @@ public class HaloVersionCtrlServiceImpl implements HaloVersionCtrlService, Appli @Autowired private RestTemplate restTemplate; - // @Autowired - // private RestTemplateBuilder builder; + + + public HaloVersionCtrlServiceImpl(ApplicationContext context, + RestTemplate restTemplate) { + this.context = context; + this.restTemplate = restTemplate; + try { + haloRepo = connect2github(); + } catch (ServiceException e) { + haloRepo = null; + } + } @Override public boolean isInLocal(String tagName) { @@ -104,6 +119,9 @@ public boolean isInLocal(String tagName) { public List getAllReleasesInfo() { try { + if (haloRepo == null) { + haloRepo = connect2github(); + } final List releases = haloRepo.listReleases().toList(); return releases.stream().map(data -> { final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(data); @@ -122,6 +140,9 @@ public VersionInfoDTO getReleaseInfoByTag(String tagName) { tagName = "v" + tagName; } try { + if (haloRepo == null) { + haloRepo = connect2github(); + } final GHRelease release = haloRepo.getReleaseByTagName(tagName); final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(release); versionInfo.setInLocal(isInLocal(tagName)); @@ -136,6 +157,9 @@ public VersionInfoDTO getReleaseInfoByTag(String tagName) { public VersionInfoDTO getLatestReleaseInfo() { // final RestTemplate restTemplate = builder.build(); try { + if (haloRepo == null) { + haloRepo = connect2github(); + } final GHRelease release = haloRepo.getLatestRelease(); final VersionInfoDTO versionInfo = VersionInfoDTO.convertFrom(release); versionInfo.setInLocal(isInLocal(release.getTagName())); @@ -258,6 +282,28 @@ public void setApplicationContext(@NotNull ApplicationContext applicationContext context = applicationContext; } + private GHRepository connect2github() { + try { + log.info("Connect to Github."); + final OkHttpClient httpClient = + new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS).build(); + return new GitHubBuilder() + .withConnector(new OkHttpGitHubConnector(httpClient)) + .withRateLimitHandler(new GitHubRateLimitHandler() { + @Override + public void onError(@NotNull GitHubConnectorResponse githubResp) + throws IOException { + throw new ServiceException("无法访问Github API,可能访问频次过高. URL: " + + githubResp.request().url()); + } + }) + .build().getRepository("halo-dev/halo"); + } catch (IOException e) { + throw new ServiceException("无法连接Github,请检查网络."); + } + } + /** * Download the specified jar by tagName into the given destination directory. * @@ -271,9 +317,9 @@ private String downloadSpecifiedJar(String tagName, String dstDir) { final String jarUrl = releaseInfo.getDownloadUrl(); final String jarName = releaseInfo.getJarName(); Assert.hasText(jarUrl, "Jar url must not be blank"); - Path tar = Paths.get(dstDir).resolve(jarName); - download(jarUrl, tar); - return tar.toString(); + Path target = Paths.get(dstDir).resolve(jarName); + download(jarUrl, target); + return target.toString(); } @@ -414,7 +460,9 @@ private void startNewVersionApp(Path target) { try { final Process process = pb.inheritIO().start(); System.out.println( - "\n------------------------------\nNew process PID: " + process.pid() + "\n------------------------------\n" + + "New process PID: " + process.pid() + "\n" + + "Command to launch: " + String.join(" ", cmd) + "\n------------------------------\n"); } catch (IOException e) { e.printStackTrace(); diff --git a/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java b/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java index 5b081f46c9..6b56f75df3 100644 --- a/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java +++ b/src/test/java/run/halo/app/service/impl/HaloVersionCtrlServiceImplTest.java @@ -34,6 +34,8 @@ class HaloVersionCtrlServiceImplTest { @Autowired HaloVersionCtrlService versionCtrlService; + + @Test void testIsInLocal() { assertFalse(this.versionCtrlService.isInLocal("Tag Name")); From 9d76925b4b10112e82d90f1434b4ed728db1777c Mon Sep 17 00:00:00 2001 From: camsyn <2742046922@qq.com> Date: Sun, 22 May 2022 14:53:08 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E6=94=B9=E7=94=A8Github=20Client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/Test.java | 51 ----------------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 src/test/java/Test.java diff --git a/src/test/java/Test.java b/src/test/java/Test.java deleted file mode 100644 index 6892038127..0000000000 --- a/src/test/java/Test.java +++ /dev/null @@ -1,51 +0,0 @@ -import com.spotify.github.v3.clients.GitDataClient; -import com.spotify.github.v3.clients.GitHubClient; -import com.spotify.github.v3.clients.GithubAppClient; -import com.spotify.github.v3.clients.RepositoryClient; -import com.spotify.github.v3.git.Tag; -import com.spotify.github.v3.repos.Repository; -import java.io.IOException; -import java.net.URI; -import java.util.concurrent.ExecutionException; -import org.kohsuke.github.GHAsset; -import org.kohsuke.github.GHRelease; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GitHub; -import org.kohsuke.github.GitHubBuilder; -import org.kohsuke.github.PagedIterable; - -public class Test { - - public static void testGithubClient() throws ExecutionException, InterruptedException { - final GitHubClient gitHubClient = GitHubClient.create(URI.create("https://api.github.com"), - "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"); - final RepositoryClient halo = gitHubClient.createRepositoryClient("Camsyn", "halo"); - System.out.println(halo.getRepository().get()); - final GitDataClient gitDataClient = gitHubClient.createGitDataClient("Camsyn", "halo"); - final Repository repository = halo.getRepository().get(); - System.out.println(repository.releasesUrl()); - System.out.println(repository.downloadsUrl()); - - -// final Tag tag = gitDataClient.getTag("v1.5.3").get(); -// System.out.println(tag); - - } - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { -// testGithubClient(); - testGithubAPI(); - } - - private static void testGithubAPI() throws IOException { - final GitHub github = new GitHubBuilder().build(); - final GHRepository repo = github.getRepository("halo-dev/halo"); - System.out.println(github.getApiUrl()); - final GHRelease release = repo.getReleaseByTagName("v1.5.3"); - final PagedIterable assets = release.listAssets(); - System.out.println(assets.toList().get(0).getBrowserDownloadUrl()); -// System.out.println(github.getApiUrl()); - - } -} From a9a2ab35638d4800a7480dae5fcbc2f6273a4ef5 Mon Sep 17 00:00:00 2001 From: camsyn <2742046922@qq.com> Date: Mon, 23 May 2022 21:27:23 +0800 Subject: [PATCH 10/10] Fix checkstyle. --- .../java/run/halo/app/utils/VmUtilsTest.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/test/java/run/halo/app/utils/VmUtilsTest.java b/src/test/java/run/halo/app/utils/VmUtilsTest.java index 765cb4cbd7..3956f8025a 100644 --- a/src/test/java/run/halo/app/utils/VmUtilsTest.java +++ b/src/test/java/run/halo/app/utils/VmUtilsTest.java @@ -16,21 +16,6 @@ class VmUtilsTest { private static RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean(); - @Test - void testGetSameLaunchCommand() { - final List cmd = VmUtils.getSameLaunchCommand(); - assertFalse(cmd.isEmpty()); - // In junit test. its size should > 3 - assertTrue(cmd.size() > 3); - } - - @Test - void testGetSameLaunchCommand2() { - final List cmd = VmUtils.getSameLaunchCommand(); - System.out.println(cmd.get(0)); - assertTrue(cmd.get(0).matches(".*java.*")); - } - @Test void testGetJvmExecutablePath() { String actualJvmExecutablePath = VmUtils.getJvmExecutablePath(); @@ -52,6 +37,7 @@ void testGetVmArguments() { final List vmArguments = VmUtils.getVmOptions(); assertNotNull(vmArguments); } + @Test void testGetVmArguments2() { final List vmArguments = VmUtils.getVmOptions(); @@ -78,6 +64,7 @@ void testGetClassPath2() { void testGetNonVmPartOfCmd() { assertEquals(System.getProperty("sun.java.command"), VmUtils.getNonVmPartOfCmd()); } + @Test void testGetNonVmPartOfCmd2() { final String args = VmUtils.getNonVmPartOfCmd(); @@ -100,6 +87,7 @@ void testGetRunningJar2() { void testGetUserDir() { assertEquals(System.getProperty("user.dir"), VmUtils.getUserDir()); } + @Test void testGetUserDir2() { final String userDir = VmUtils.getUserDir();