summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorshizhendong <[email protected]>2024-11-21 14:59:54 +0800
committershizhendong <[email protected]>2024-11-21 14:59:54 +0800
commit3cc928d7a7e01a22bc325579ae73a7a0ce15ff4f (patch)
tree44ebf02ac0a47e5c50b0c6b669147cc60038431a
parent70feac12fcb6d7e6cdb2f78c70aee38ebe26e20a (diff)
feat: ASW-182 新增 tag,release 接口
-rw-r--r--pom.xml7
-rw-r--r--src/main/java/net/geedge/asw/common/util/Constants.java4
-rw-r--r--src/main/java/net/geedge/asw/common/util/RCode.java3
-rw-r--r--src/main/java/net/geedge/asw/module/app/controller/ApplicationReleaseController.java78
-rw-r--r--src/main/java/net/geedge/asw/module/app/controller/TagController.java52
-rw-r--r--src/main/java/net/geedge/asw/module/app/dao/ApplicationReleaseDao.java17
-rw-r--r--src/main/java/net/geedge/asw/module/app/entity/ApplicationReleaseEntity.java40
-rw-r--r--src/main/java/net/geedge/asw/module/app/service/IApplicationReleaseService.java21
-rw-r--r--src/main/java/net/geedge/asw/module/app/service/ITagService.java16
-rw-r--r--src/main/java/net/geedge/asw/module/app/service/impl/ApplicationReleaseServiceImpl.java164
-rw-r--r--src/main/java/net/geedge/asw/module/app/service/impl/BranchServiceImpl.java6
-rw-r--r--src/main/java/net/geedge/asw/module/app/service/impl/TagServiceImpl.java194
-rw-r--r--src/main/java/net/geedge/asw/module/app/util/JGitUtils.java62
-rw-r--r--src/main/resources/db/mapper/app/ApplicationReleaseMapper.xml55
-rw-r--r--src/main/resources/db/migration/R__AZ_sys_i18n.sql6
-rw-r--r--src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql19
16 files changed, 733 insertions, 11 deletions
diff --git a/pom.xml b/pom.xml
index 6507b1a..f4fee16 100644
--- a/pom.xml
+++ b/pom.xml
@@ -186,6 +186,13 @@
<version>7.0.0.202409031743-r</version>
</dependency>
+ <!-- https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit.archive -->
+ <dependency>
+ <groupId>org.eclipse.jgit</groupId>
+ <artifactId>org.eclipse.jgit.archive</artifactId>
+ <version>7.0.0.202409031743-r</version>
+ </dependency>
+
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
diff --git a/src/main/java/net/geedge/asw/common/util/Constants.java b/src/main/java/net/geedge/asw/common/util/Constants.java
index 80a11cc..1ab1ef2 100644
--- a/src/main/java/net/geedge/asw/common/util/Constants.java
+++ b/src/main/java/net/geedge/asw/common/util/Constants.java
@@ -146,7 +146,9 @@ public class Constants {
PLAYBOOK("playbook"),
- JOB("job");
+ JOB("job"),
+
+ RELEASE("release");
private String type;
diff --git a/src/main/java/net/geedge/asw/common/util/RCode.java b/src/main/java/net/geedge/asw/common/util/RCode.java
index d43dd33..e25bc3d 100644
--- a/src/main/java/net/geedge/asw/common/util/RCode.java
+++ b/src/main/java/net/geedge/asw/common/util/RCode.java
@@ -67,6 +67,9 @@ public enum RCode {
GIT_BINARY_CONFLICT_ERROR(203004, "Binary file conflict found; resolve conflicts in binary files manually"),
GIT_MERGE_NOT_SUPPORTED(203005, "Cannot merge in the {0} state"),
GIT_MERGE_TARGET_BRANCH_NOT_EXIST(203006, "The target branch {0} does not exist."),
+ GIT_TAG_ALREADY_EXISTS(203007, "Tag {0} already exists"),
+ GIT_TAG_NOT_FOUND(203008, "Tag {0} not found"),
+ GIT_TAG_ALREADY_IN_USE(203009,"Tag is already in use. Choose another tag."),
// Runner
diff --git a/src/main/java/net/geedge/asw/module/app/controller/ApplicationReleaseController.java b/src/main/java/net/geedge/asw/module/app/controller/ApplicationReleaseController.java
new file mode 100644
index 0000000..4184e95
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/app/controller/ApplicationReleaseController.java
@@ -0,0 +1,78 @@
+package net.geedge.asw.module.app.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import jakarta.servlet.http.HttpServletResponse;
+import net.geedge.asw.common.util.*;
+import net.geedge.asw.module.app.entity.ApplicationReleaseEntity;
+import net.geedge.asw.module.app.service.IApplicationReleaseService;
+import net.geedge.asw.module.workspace.entity.WorkspaceEntity;
+import net.geedge.asw.module.workspace.service.IWorkspaceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/v1/workspace")
+public class ApplicationReleaseController {
+
+ @Autowired
+ private IWorkspaceService workspaceService;
+
+ @Autowired
+ private IApplicationReleaseService releaseService;
+
+ @GetMapping("/{workspaceId}/release/{id}")
+ public R detail(@PathVariable("workspaceId") String workspaceId, @PathVariable("id") String id) {
+ ApplicationReleaseEntity record = releaseService.queryInfo(id);
+ return R.ok().putData("record", record);
+ }
+
+ @GetMapping("/{workspaceId}/release")
+ public R list(@PathVariable("workspaceId") String workspaceId, @RequestParam Map<String, Object> params) {
+ // workspaceId
+ params = T.MapUtil.defaultIfEmpty(params, new HashMap<>());
+ params.put("workspaceId", workspaceId);
+
+ Page page = releaseService.queryList(params);
+ return R.ok(page);
+ }
+
+ @PostMapping("/{workspaceId}/release")
+ public R add(@PathVariable("workspaceId") String workspaceId, @RequestBody Map<String, String> requestBody) {
+ String name = T.MapUtil.getStr(requestBody, "name", "");
+ String tagName = T.MapUtil.getStr(requestBody, "tagName", "");
+ String description = T.MapUtil.getStr(requestBody, "description", "");
+ if (T.StrUtil.hasEmpty(name, tagName)) {
+ throw new ASWException(RCode.PARAM_CANNOT_EMPTY);
+ }
+
+ ApplicationReleaseEntity record = releaseService.saveRelease(workspaceId, name, tagName, description);
+ return R.ok().putData("record", record);
+ }
+
+ @DeleteMapping("/{workspaceId}/release/{id}")
+ public R delete(@PathVariable("workspaceId") String workspaceId, @PathVariable("id") String id) {
+ releaseService.removeRelease(id);
+ return R.ok();
+ }
+
+ @GetMapping("/{workspaceId}/release/{id}/file")
+ public void download(@PathVariable("workspaceId") String workspaceId,
+ @PathVariable("id") String id,
+ HttpServletResponse response) throws IOException {
+ ApplicationReleaseEntity release = releaseService.getById(id);
+ T.VerifyUtil.is(release).notNull(RCode.SYS_RECORD_NOT_FOUND);
+
+ WorkspaceEntity workspace = workspaceService.getById(workspaceId);
+ T.VerifyUtil.is(workspace).notNull(RCode.SYS_RECORD_NOT_FOUND);
+
+ String fileName = T.StrUtil.concat(true, workspace.getName(), "-", release.getTagName(), ".zip");
+ byte[] fileBytes = T.FileUtil.readBytes(T.FileUtil.file(release.getPath()));
+ ResponseUtil.downloadFile(response, MediaType.APPLICATION_OCTET_STREAM_VALUE, fileName, fileBytes);
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/app/controller/TagController.java b/src/main/java/net/geedge/asw/module/app/controller/TagController.java
new file mode 100644
index 0000000..83ac810
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/app/controller/TagController.java
@@ -0,0 +1,52 @@
+package net.geedge.asw.module.app.controller;
+
+import net.geedge.asw.common.util.ASWException;
+import net.geedge.asw.common.util.R;
+import net.geedge.asw.common.util.RCode;
+import net.geedge.asw.common.util.T;
+import net.geedge.asw.module.app.service.ITagService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/v1/workspace")
+public class TagController {
+
+ @Autowired
+ private ITagService tagService;
+
+ @GetMapping("/{workspaceId}/tag/{tagName}")
+ public R infoTag(@PathVariable("workspaceId") String workspaceId, @PathVariable("tagName") String name) {
+ Map<Object, Object> record = tagService.infoTag(workspaceId, name);
+ return R.ok().putData("record", record);
+ }
+
+ @GetMapping("/{workspaceId}/tag")
+ public R listTag(@PathVariable("workspaceId") String workspaceId,
+ @RequestParam(value = "search", required = false) String search) {
+ List<Map<Object, Object>> list = tagService.listTag(workspaceId, search);
+ return R.ok().putData("records", list);
+ }
+
+ @PostMapping("/{workspaceId}/tag")
+ public synchronized R newTag(@PathVariable("workspaceId") String workspaceId, @RequestBody Map<String, String> requestBody) {
+ String name = T.MapUtil.getStr(requestBody, "name", "");
+ String ref = T.MapUtil.getStr(requestBody, "ref", "");
+ String description = T.MapUtil.getStr(requestBody, "description", "");
+ if (T.StrUtil.hasEmpty(name, ref)) {
+ throw new ASWException(RCode.PARAM_CANNOT_EMPTY);
+ }
+ Map<Object, Object> record = tagService.newTag(workspaceId, name, ref, description);
+ return R.ok().putData("record", record);
+ }
+
+ @DeleteMapping("/{workspaceId}/tag/{tagName}")
+ public synchronized R deleteTag(@PathVariable("workspaceId") String workspaceId, @PathVariable("tagName") String name) {
+ tagService.deleteTag(workspaceId, name);
+ return R.ok();
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/app/dao/ApplicationReleaseDao.java b/src/main/java/net/geedge/asw/module/app/dao/ApplicationReleaseDao.java
new file mode 100644
index 0000000..05fb2ee
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/app/dao/ApplicationReleaseDao.java
@@ -0,0 +1,17 @@
+package net.geedge.asw.module.app.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import net.geedge.asw.module.app.entity.ApplicationReleaseEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+import java.util.Map;
+
+
+@Mapper
+public interface ApplicationReleaseDao extends BaseMapper<ApplicationReleaseEntity> {
+
+ List<ApplicationReleaseEntity> queryList(Page page, Map<String, Object> params);
+
+}
diff --git a/src/main/java/net/geedge/asw/module/app/entity/ApplicationReleaseEntity.java b/src/main/java/net/geedge/asw/module/app/entity/ApplicationReleaseEntity.java
new file mode 100644
index 0000000..69f288a
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/app/entity/ApplicationReleaseEntity.java
@@ -0,0 +1,40 @@
+package net.geedge.asw.module.app.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import net.geedge.asw.module.sys.entity.SysUserEntity;
+
+@Data
+@TableName(value = "application_release", autoResultMap = true)
+public class ApplicationReleaseEntity {
+
+ @TableId(type = IdType.ASSIGN_UUID)
+ private String id;
+ private String name;
+ private String tagName;
+
+ private String path;
+ private String description;
+
+ private String createUserId;
+ private Long createTimestamp;
+
+ private String updateUserId;
+ private Long updateTimestamp;
+
+ private String workspaceId;
+
+
+ @TableField(exist = false)
+ private SysUserEntity createUser;
+
+ @TableField(exist = false)
+ private SysUserEntity updateUser;
+
+ @TableField(exist = false)
+ private Object commit;
+
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/app/service/IApplicationReleaseService.java b/src/main/java/net/geedge/asw/module/app/service/IApplicationReleaseService.java
new file mode 100644
index 0000000..26f0dc4
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/app/service/IApplicationReleaseService.java
@@ -0,0 +1,21 @@
+package net.geedge.asw.module.app.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import net.geedge.asw.module.app.entity.ApplicationReleaseEntity;
+
+import java.util.Map;
+
+public interface IApplicationReleaseService extends IService<ApplicationReleaseEntity> {
+
+ ApplicationReleaseEntity queryInfo(String id);
+
+ Page queryList(Map<String, Object> params);
+
+ ApplicationReleaseEntity saveRelease(String workspaceId, String name, String tagName, String description);
+
+ void removeRelease(String id);
+
+ void removeRelease(String workspaceId, String tagName);
+
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/app/service/ITagService.java b/src/main/java/net/geedge/asw/module/app/service/ITagService.java
new file mode 100644
index 0000000..1096549
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/app/service/ITagService.java
@@ -0,0 +1,16 @@
+package net.geedge.asw.module.app.service;
+
+import java.util.List;
+import java.util.Map;
+
+public interface ITagService {
+
+ Map<Object, Object> infoTag(String workspaceId, String name);
+
+ List<Map<Object, Object>> listTag(String workspaceId, String search);
+
+ Map<Object, Object> newTag(String workspaceId, String name, String branch, String message);
+
+ void deleteTag(String workspaceId, String name);
+
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationReleaseServiceImpl.java b/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationReleaseServiceImpl.java
new file mode 100644
index 0000000..6d529e3
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/app/service/impl/ApplicationReleaseServiceImpl.java
@@ -0,0 +1,164 @@
+package net.geedge.asw.module.app.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.log.Log;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import net.geedge.asw.common.config.Query;
+import net.geedge.asw.common.util.*;
+import net.geedge.asw.module.app.dao.ApplicationReleaseDao;
+import net.geedge.asw.module.app.entity.ApplicationReleaseEntity;
+import net.geedge.asw.module.app.service.IApplicationReleaseService;
+import net.geedge.asw.module.app.util.JGitUtils;
+import net.geedge.asw.module.sys.entity.SysUserEntity;
+import net.geedge.asw.module.sys.service.ISysUserService;
+import net.geedge.asw.module.workspace.entity.WorkspaceEntity;
+import net.geedge.asw.module.workspace.service.IWorkspaceService;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class ApplicationReleaseServiceImpl extends ServiceImpl<ApplicationReleaseDao, ApplicationReleaseEntity> implements IApplicationReleaseService {
+
+ private final static Log log = Log.get();
+
+ @Value("${asw.resources.path:resources}")
+ private String aswResourcesPath;
+
+ @Autowired
+ private ISysUserService userService;
+
+ @Autowired
+ private IWorkspaceService workspaceService;
+
+ @Override
+ public ApplicationReleaseEntity queryInfo(String id) {
+ ApplicationReleaseEntity entity = this.getById(id);
+ T.VerifyUtil.is(entity).notNull(RCode.SYS_RECORD_NOT_FOUND);
+
+ SysUserEntity createUser = userService.getById(entity.getCreateUserId());
+ SysUserEntity updateUser = userService.getById(entity.getUpdateUserId());
+ createUser.setPwd(null);
+ updateUser.setPwd(null);
+ entity.setCreateUser(createUser);
+ entity.setUpdateUser(updateUser);
+
+ File gitDir = workspaceService.getGitDir(entity.getWorkspaceId());
+ try (Repository repository = JGitUtils.openRepository(gitDir);) {
+ Ref tagRef = repository.findRef(JGitUtils.getFullTagName(entity.getTagName()));
+ String tgtCommitId = tagRef.getTarget().getObjectId().getName();
+ RevCommit revCommit = JGitUtils.infoCommit(repository, tgtCommitId);
+ entity.setCommit(JGitUtils.buildAswCommitInfo(revCommit));
+ return entity;
+ } catch (IOException e) {
+ log.error(e, "[queryInfo] [id: {}]", id);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Page queryList(Map<String, Object> params) {
+ Page page = new Query(ApplicationReleaseEntity.class).getPage(params);
+ List<ApplicationReleaseEntity> entityList = this.getBaseMapper().queryList(page, params);
+ page.setRecords(entityList);
+ return page;
+ }
+
+ @Override
+ public ApplicationReleaseEntity saveRelease(String workspaceId, String name, String tagName, String description) {
+ ApplicationReleaseEntity checkTagInUse = this.getOne(new LambdaQueryWrapper<ApplicationReleaseEntity>()
+ .eq(ApplicationReleaseEntity::getWorkspaceId, workspaceId)
+ .eq(ApplicationReleaseEntity::getTagName, tagName)
+ );
+ if (null != checkTagInUse) {
+ throw new ASWException(RCode.GIT_TAG_ALREADY_IN_USE);
+ }
+
+ String uuid = T.StrUtil.uuid();
+ // release file
+ File releaseFile = FileResourceUtil.createFile(this.aswResourcesPath, workspaceId, Constants.FileTypeEnum.RELEASE.getType(), uuid, uuid + ".zip");
+
+ File gitDir = workspaceService.getGitDir(workspaceId);
+ try (Repository repository = JGitUtils.openRepository(gitDir);) {
+ Ref tagRef = repository.findRef(JGitUtils.getFullTagName(tagName));
+ T.VerifyUtil.is(tagRef).notNull(RCode.GIT_TAG_NOT_FOUND.setParam(tagName));
+
+ // archive
+ WorkspaceEntity workspace = workspaceService.getById(workspaceId);
+ String prefix = T.StrUtil.concat(true, workspace.getName(), "-", tagName, "/");
+ JGitUtils.archive(repository, tagName, releaseFile, prefix, "zip");
+
+ ApplicationReleaseEntity entity = new ApplicationReleaseEntity();
+ entity.setId(uuid);
+ entity.setName(name);
+ entity.setTagName(tagName);
+ entity.setPath(releaseFile.getAbsolutePath());
+
+ entity.setDescription(description);
+ entity.setWorkspaceId(workspaceId);
+
+ entity.setCreateUserId(StpUtil.getLoginIdAsString());
+ entity.setCreateTimestamp(System.currentTimeMillis());
+ entity.setUpdateUserId(StpUtil.getLoginIdAsString());
+ entity.setUpdateTimestamp(System.currentTimeMillis());
+ this.save(entity);
+
+ // build record info
+ SysUserEntity createUser = userService.getById(entity.getCreateUserId());
+ SysUserEntity updateUser = userService.getById(entity.getUpdateUserId());
+ createUser.setPwd(null);
+ updateUser.setPwd(null);
+ entity.setCreateUser(createUser);
+ entity.setUpdateUser(updateUser);
+
+ String tgtCommitId = tagRef.getTarget().getObjectId().getName();
+ RevCommit revCommit = JGitUtils.infoCommit(repository, tgtCommitId);
+ entity.setCommit(JGitUtils.buildAswCommitInfo(revCommit));
+ return entity;
+ } catch (IOException | GitAPIException e) {
+ T.FileUtil.del(releaseFile);
+ log.error(e, "[saveRelease] [error] [workspaceId: {}] [name: {}] [tagName: {}]", workspaceId, name, tagName);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void removeRelease(String id) {
+ ApplicationReleaseEntity entity = this.getById(id);
+ if (null != entity) {
+ // del file
+ T.FileUtil.del(entity.getPath());
+ // del record
+ this.removeById(id);
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void removeRelease(String workspaceId, String tagName) {
+ ApplicationReleaseEntity entity = this.getOne(new LambdaQueryWrapper<ApplicationReleaseEntity>()
+ .eq(ApplicationReleaseEntity::getWorkspaceId, workspaceId)
+ .eq(ApplicationReleaseEntity::getTagName, tagName)
+ );
+ if (null != entity) {
+ // del file
+ T.FileUtil.del(entity.getPath());
+ // del record
+ this.removeById(entity.getId());
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/app/service/impl/BranchServiceImpl.java b/src/main/java/net/geedge/asw/module/app/service/impl/BranchServiceImpl.java
index 42bebc9..6f0109a 100644
--- a/src/main/java/net/geedge/asw/module/app/service/impl/BranchServiceImpl.java
+++ b/src/main/java/net/geedge/asw/module/app/service/impl/BranchServiceImpl.java
@@ -46,8 +46,8 @@ public class BranchServiceImpl implements IBranchService {
String fullBranch = repository.getFullBranch();
String defaultBranch = "main";
- if (fullBranch != null && fullBranch.startsWith(JGitUtils.LOCAL_BRANCH_PREFIX)) {
- defaultBranch = fullBranch.substring(JGitUtils.LOCAL_BRANCH_PREFIX.length());
+ if (fullBranch != null && fullBranch.startsWith(JGitUtils.R_HEADS)) {
+ defaultBranch = fullBranch.substring(JGitUtils.R_HEADS.length());
}
// 默认行为,进查询本地分支
@@ -57,7 +57,7 @@ public class BranchServiceImpl implements IBranchService {
for (Ref ref : call) {
String branchName = ref.getName();
// 返回时去掉前缀
- branchName = branchName.replaceAll(JGitUtils.LOCAL_BRANCH_PREFIX, "");
+ branchName = branchName.replaceAll(JGitUtils.R_HEADS, "");
if (T.StrUtil.isNotEmpty(search)) {
if (!T.StrUtil.contains(branchName, search)) {
continue;
diff --git a/src/main/java/net/geedge/asw/module/app/service/impl/TagServiceImpl.java b/src/main/java/net/geedge/asw/module/app/service/impl/TagServiceImpl.java
new file mode 100644
index 0000000..c5624ee
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/app/service/impl/TagServiceImpl.java
@@ -0,0 +1,194 @@
+package net.geedge.asw.module.app.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.log.Log;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import net.geedge.asw.common.util.ASWException;
+import net.geedge.asw.common.util.RCode;
+import net.geedge.asw.common.util.T;
+import net.geedge.asw.module.app.entity.ApplicationReleaseEntity;
+import net.geedge.asw.module.app.service.IApplicationReleaseService;
+import net.geedge.asw.module.app.service.ITagService;
+import net.geedge.asw.module.app.util.JGitUtils;
+import net.geedge.asw.module.sys.entity.SysUserEntity;
+import net.geedge.asw.module.sys.service.ISysUserService;
+import net.geedge.asw.module.workspace.service.IWorkspaceService;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Service
+public class TagServiceImpl implements ITagService {
+
+ private final static Log log = Log.get();
+
+ @Autowired
+ private ISysUserService userService;
+
+ @Autowired
+ private IWorkspaceService workspaceService;
+
+ @Autowired
+ private IApplicationReleaseService releaseService;
+
+ private String getShortTagName(String name) {
+ return name.startsWith(JGitUtils.R_TAGS) ? name.replaceAll(JGitUtils.R_TAGS, "") : name;
+ }
+
+ @Override
+ public Map<Object, Object> infoTag(String workspaceId, String name) {
+ File gitDir = workspaceService.getGitDir(workspaceId);
+ try (Repository repository = JGitUtils.openRepository(gitDir);) {
+
+ Map<Object, Object> infoTag = this.infoTag(repository, name);
+ return infoTag;
+ } catch (IOException e) {
+ log.error(e, "[infoTag] [error] [workspaceId: {}] [name: {}]", workspaceId, name);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * tag 详情
+ *
+ * @param repository
+ * @param name
+ * @return
+ * @throws IOException
+ */
+ private Map<Object, Object> infoTag(Repository repository, String name) throws IOException {
+ Ref ref = repository.findRef(JGitUtils.getFullTagName(name));
+ T.VerifyUtil.is(ref).notNull(RCode.GIT_TAG_NOT_FOUND.setParam(name));
+
+ // tag
+ RevTag revTag = JGitUtils.infoTag(repository, ref.getObjectId());
+ String message = revTag.getFullMessage();
+
+ // commit
+ RevCommit revCommit = JGitUtils.infoCommit(repository, ref.getObjectId().getName());
+ Map<Object, Object> aswCommitInfo = JGitUtils.buildAswCommitInfo(revCommit);
+
+ // release
+ ApplicationReleaseEntity releaseEntity = releaseService.getOne(new LambdaQueryWrapper<ApplicationReleaseEntity>()
+ .eq(ApplicationReleaseEntity::getWorkspaceId, repository.getDirectory().getName())
+ .eq(ApplicationReleaseEntity::getTagName, this.getShortTagName(name))
+ );
+
+ Map<Object, Object> m = T.MapUtil.builder()
+ .put("name", this.getShortTagName(name))
+ .put("message", message)
+ .put("createdAt", revTag.getTaggerIdent().getWhen().getTime())
+ .put("commit", aswCommitInfo)
+ .put("release", releaseEntity)
+ .build();
+ return m;
+ }
+
+ @Override
+ public List<Map<Object, Object>> listTag(String workspaceId, String search) {
+ File gitDir = workspaceService.getGitDir(workspaceId);
+ try (Repository repository = JGitUtils.openRepository(gitDir);
+ Git git = Git.open(repository.getDirectory())) {
+
+ List<Ref> refList = git.tagList().call();
+ if (T.StrUtil.isNotEmpty(search)) {
+ refList = refList.stream()
+ .filter(ref -> {
+ String name = ref.getName();
+ return T.StrUtil.containsIgnoreCase(this.getShortTagName(name), search);
+ })
+ .collect(Collectors.toList());
+ }
+
+ List<Map<Object, Object>> list = T.ListUtil.list(true);
+ for (Ref ref : refList) {
+ list.add(this.infoTag(repository, ref.getName()));
+ }
+
+ // 默认根据 createdAt 字段排序
+ list = list.stream()
+ .sorted(Comparator.comparing(map -> T.MapUtil.getLong((Map) map, "createdAt")).reversed())
+ .collect(Collectors.toList());
+ return list;
+ } catch (IOException | GitAPIException e) {
+ log.error(e, "[listTag] [error] [workspaceId: {}] [search: {}]", workspaceId, search);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Map<Object, Object> newTag(String workspaceId, String name, String branch, String message) {
+ File gitDir = workspaceService.getGitDir(workspaceId);
+ try (Repository repository = JGitUtils.openRepository(gitDir);
+ Git git = Git.open(repository.getDirectory())) {
+
+ // check tag exists
+ List<Ref> tagListInDb = git.tagList().call();
+ Ref checkRefExists = tagListInDb.parallelStream()
+ .filter(ref -> T.StrUtil.equals(name, this.getShortTagName(ref.getName())))
+ .findFirst()
+ .orElse(null);
+ if (null != checkRefExists) {
+ throw new ASWException(RCode.GIT_TAG_ALREADY_EXISTS.setParam(name));
+ }
+
+ // check branch exists
+ if (T.BooleanUtil.negate(
+ JGitUtils.isBranchExists(repository, branch)
+ )) {
+ throw new ASWException(RCode.GIT_MERGE_TARGET_BRANCH_NOT_EXIST.setParam(branch));
+ }
+
+ SysUserEntity loginUser = userService.getById(StpUtil.getLoginIdAsString());
+ PersonIdent personIdent = JGitUtils.buildPersonIdent(loginUser.getName());
+ RevCommit branchLatestCommit = JGitUtils.getBranchLatestCommit(repository, branch);
+
+ // add tag
+ git.tag()
+ .setName(name)
+ .setMessage(message)
+ .setTagger(personIdent)
+ .setObjectId(branchLatestCommit)
+ .setAnnotated(true)
+ .setForceUpdate(false)
+ .call();
+
+ // return info
+ return this.infoTag(repository, name);
+ } catch (IOException | GitAPIException e) {
+ log.error(e, "[newTag] [error] [workspaceId: {}] [name: {}] [branch: {}]", workspaceId, name, branch);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void deleteTag(String workspaceId, String name) {
+ File gitDir = workspaceService.getGitDir(workspaceId);
+ try (Repository repository = JGitUtils.openRepository(gitDir);
+ Git git = Git.open(repository.getDirectory())) {
+ // del tag
+ git.tagDelete()
+ .setTags(name)
+ .call();
+ // del release
+ releaseService.removeRelease(workspaceId, name);
+ } catch (IOException | GitAPIException e) {
+ log.error(e, "[deleteTag] [error] [workspaceId: {}] [name: {}]", workspaceId, name);
+ throw new RuntimeException(e);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/app/util/JGitUtils.java b/src/main/java/net/geedge/asw/module/app/util/JGitUtils.java
index 08bd526..d23675b 100644
--- a/src/main/java/net/geedge/asw/module/app/util/JGitUtils.java
+++ b/src/main/java/net/geedge/asw/module/app/util/JGitUtils.java
@@ -9,6 +9,7 @@ import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.archive.ArchiveFormats;
import org.eclipse.jgit.diff.*;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -20,6 +21,7 @@ import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.RecursiveMerger;
import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
@@ -30,9 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
+import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -49,7 +49,9 @@ public class JGitUtils {
/**
* 本地分支引用前缀
*/
- public static final String LOCAL_BRANCH_PREFIX = "refs/heads/";
+ public static final String R_HEADS = "refs/heads/";
+
+ public static final String R_TAGS = "refs/tags/";
/**
* 默认分支
@@ -123,7 +125,7 @@ public class JGitUtils {
* @throws IOException
*/
public static boolean isBranchExists(Repository repository, String branch) throws IOException {
- Ref ref = repository.findRef(T.StrUtil.concat(true, LOCAL_BRANCH_PREFIX, branch));
+ Ref ref = repository.findRef(T.StrUtil.concat(true, R_HEADS, branch));
return null != ref;
}
@@ -142,6 +144,24 @@ public class JGitUtils {
}
}
+ public static String getFullTagName(String name) {
+ return name.startsWith(R_TAGS) ? name : T.StrUtil.concat(true, R_TAGS, name);
+ }
+
+ /**
+ * 获取tag对象
+ *
+ * @param repository
+ * @param objectId
+ * @return
+ * @throws IOException
+ */
+ public static RevTag infoTag(Repository repository, ObjectId objectId) throws IOException {
+ try (RevWalk revWalk = new RevWalk(repository)) {
+ RevTag revTag = revWalk.parseTag(objectId);
+ return revTag;
+ }
+ }
/**
* 返回分支最新提交
@@ -357,7 +377,7 @@ public class JGitUtils {
RefUpdate ru = null;
RefUpdate.Result rc = null;
if (null != branchRef) {
- ru = repository.updateRef(T.StrUtil.concat(true, LOCAL_BRANCH_PREFIX, branch));
+ ru = repository.updateRef(T.StrUtil.concat(true, R_HEADS, branch));
ru.setNewObjectId(commitId);
rc = ru.update();
} else {
@@ -422,7 +442,7 @@ public class JGitUtils {
ObjectId commitId = odi.insert(commit);
odi.flush();
- RefUpdate ru = repository.updateRef(T.StrUtil.concat(true, JGitUtils.LOCAL_BRANCH_PREFIX, branch));
+ RefUpdate ru = repository.updateRef(T.StrUtil.concat(true, R_HEADS, branch));
ru.setNewObjectId(commitId);
RefUpdate.Result rc = ru.update();
switch (rc) {
@@ -745,6 +765,34 @@ public class JGitUtils {
}
/**
+ * 归档
+ *
+ * @param repository
+ * @param tagName
+ * @param file
+ * @param prefix
+ * @param format
+ * @throws IOException
+ * @throws GitAPIException
+ */
+ public static void archive(Repository repository, String tagName, File file, String prefix, String format) throws IOException, GitAPIException {
+ ArchiveFormats.registerAll();
+ T.FileUtil.mkdir(T.FileUtil.getParent(file, 1));
+ try (OutputStream out = new FileOutputStream(file)) {
+ try (Git git = new Git(repository)) {
+ git.archive()
+ .setTree(repository.resolve(tagName))
+ .setFormat(format)
+ .setPrefix(prefix)
+ .setOutputStream(out)
+ .call();
+ }
+ } finally {
+ ArchiveFormats.unregisterAll();
+ }
+ }
+
+ /**
* build asw commit info
*
* @param commit
diff --git a/src/main/resources/db/mapper/app/ApplicationReleaseMapper.xml b/src/main/resources/db/mapper/app/ApplicationReleaseMapper.xml
new file mode 100644
index 0000000..84ec5ab
--- /dev/null
+++ b/src/main/resources/db/mapper/app/ApplicationReleaseMapper.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="net.geedge.asw.module.app.dao.ApplicationReleaseDao">
+
+ <!-- 通用查询映射结果 -->
+ <resultMap id="resultMap" type="net.geedge.asw.module.app.entity.ApplicationReleaseEntity">
+ <id column="id" property="id"/>
+ <result column="name" property="name"/>
+ <result column="tag_name" property="tagName"/>
+ <result column="path" property="path"/>
+ <result column="description" property="description"/>
+ <result column="create_timestamp" property="createTimestamp"/>
+ <result column="create_user_id" property="createUserId"/>
+ <result column="update_timestamp" property="updateTimestamp"/>
+ <result column="update_user_id" property="updateUserId"/>
+ <result column="workspace_id" property="workspaceId"/>
+
+ <association property="createUser" columnPrefix="c_" javaType="net.geedge.asw.module.sys.entity.SysUserEntity">
+ <id property="id" column="id"/>
+ <result property="name" column="name"/>
+ <result property="userName" column="user_name"/>
+ </association>
+
+ <association property="updateUser" columnPrefix="u_" javaType="net.geedge.asw.module.sys.entity.SysUserEntity">
+ <id property="id" column="id"/>
+ <result property="name" column="name"/>
+ <result property="userName" column="user_name"/>
+ </association>
+ </resultMap>
+
+ <select id="queryList" resultMap="resultMap">
+ SELECT
+ ar.*,
+ c.id AS c_id,
+ c.name AS c_name,
+ c.user_name AS c_user_name,
+ u.id AS u_id,
+ u.name AS u_name,
+ u.user_name AS u_user_name
+ FROM
+ application_release ar
+ LEFT JOIN sys_user c ON ar.create_user_id = c.id
+ LEFT JOIN sys_user u ON ar.update_user_id = u.id
+ <where>
+ <if test="params.workbookId != null and params.workbookId != ''">
+ ar.workbook_id = #{params.workbookId}
+ </if>
+
+ <if test="params.q != null and params.q != ''">
+ AND ( locate(#{params.q}, ar.name) OR locate(#{params.q}, ar.tag_name) OR locate(#{params.q}, ar.description) )
+ </if>
+ </where>
+ </select>
+
+</mapper> \ No newline at end of file
diff --git a/src/main/resources/db/migration/R__AZ_sys_i18n.sql b/src/main/resources/db/migration/R__AZ_sys_i18n.sql
index 4bb6672..3c4479b 100644
--- a/src/main/resources/db/migration/R__AZ_sys_i18n.sql
+++ b/src/main/resources/db/migration/R__AZ_sys_i18n.sql
@@ -157,5 +157,11 @@ INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_
INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (249, '203005', 'GIT_MERGE_NOT_SUPPORTED', '无法在{0}状态下合并', 'zh', '', 'admin', 1724030366000);
INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (250, '203006', 'GIT_MERGE_TARGET_BRANCH_NOT_EXIST', 'The target branch {0} does not exist.', 'en', '', 'admin', 1724030366000);
INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (251, '203006', 'GIT_MERGE_TARGET_BRANCH_NOT_EXIST', '目标分支 {0} 不存在', 'zh', '', 'admin', 1724030366000);
+INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (253, '203007', 'GIT_TAG_ALREADY_EXISTS', 'Tag {0} already exists', 'en', '', 'admin', 1724030366000);
+INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (255, '203007', 'GIT_TAG_ALREADY_EXISTS', 'Tag {0} 已经存在', 'zh', '', 'admin', 1724030366000);
+INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (257, '203008', 'GIT_TAG_NOT_FOUND', 'Tag {0} not found', 'en', '', 'admin', 1724030366000);
+INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (259, '203008', 'GIT_TAG_NOT_FOUND', 'Tag {0} 不存在', 'zh', '', 'admin', 1724030366000);
+INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (261, '203009', 'GIT_TAG_ALREADY_IN_USE', 'Tag is already in use. Choose another tag.', 'en', '', 'admin', 1724030366000);
+INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (263, '203009', 'GIT_TAG_ALREADY_IN_USE', 'Tag 已在使用中,请选择其他 tag', 'zh', '', 'admin', 1724030366000);
SET FOREIGN_KEY_CHECKS = 1;
diff --git a/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql b/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql
index 4444874..1123f71 100644
--- a/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql
+++ b/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql
@@ -376,6 +376,25 @@ CREATE TABLE `application_merge` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/**
+ * 新增 application_release 表
+ */
+DROP TABLE IF EXISTS `application_release`;
+CREATE TABLE `application_release` (
+ `id` VARCHAR(64) NOT NULL COMMENT '主键',
+ `tag_name` VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'git tag名称',
+ `name` VARCHAR(256) NOT NULL DEFAULT '' COMMENT '标题',
+ `description` text NOT NULL DEFAULT '' COMMENT '描述信息',
+ `path` VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '打包文件路径',
+ `create_timestamp` bigint(20) NOT NULL COMMENT '创建时间戳',
+ `create_user_id` varchar(64) NOT NULL COMMENT '创建人',
+ `update_timestamp` bigint(20) NOT NULL COMMENT '更新时间戳',
+ `update_user_id` varchar(64) NOT NULL COMMENT '更新人',
+ `workspace_id` varchar(64) NOT NULL DEFAULT '' COMMENT '工作空间ID',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `idx_workspace_id` (`workspace_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+/**
* 新增 package 表
*/
DROP TABLE IF EXISTS `package`;