Bladeren bron

refactor: 优化部署控制器、服务和数据访问层代码结构

weipengtao 7 maanden geleden
bovenliggende
commit
d42538e5a0

+ 16 - 35
web/src/main/java/cn/seecoder/web/controller/pipeline/DeploymentController.java

@@ -7,71 +7,52 @@ import cn.seecoder.web.model.vo.pipeline.DeploymentInfoVO;
 import cn.seecoder.web.service.pipeline.DeploymentService;
 import cn.seecoder.web.service.user.UserService;
 import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiImplicitParam;
-import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
-import org.springframework.beans.factory.annotation.Autowired;
+import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
 /**
+ * 流水线部署实例控制器
+ *
  * @author PuHong Weng
- * @date 2021/3/12
- * @description:
  */
 @Api(tags = "流水线部署的实例 API")
 @RestController
 @RequestMapping("/deployments")
+@RequiredArgsConstructor
 public class DeploymentController {
 
     private final DeploymentService deploymentService;
 
-    @Autowired
-    public DeploymentController(DeploymentService deploymentService) {
-        this.deploymentService = deploymentService;
-    }
-
-    @ApiOperation(value = "获取项目所有部署的实例信息", httpMethod = "GET")
-    @ApiImplicitParam(name = "projectId", dataType = "int", paramType = "query")
+    @ApiOperation("获取项目所有部署的实例信息")
     @GetMapping("/list")
-    public Response<List<DeploymentInfoVO>> retrieveDeploymentInfos(@RequestParam("projectId") Integer projectId) throws AccessDeniedException {
+    public Response<List<DeploymentInfoVO>> retrieveDeploymentInfos(@RequestParam Integer projectId)
+            throws AccessDeniedException {
         return Response.buildSuccess(deploymentService.retrieveDeploymentInfos(projectId));
     }
 
-
-    @ApiOperation(value = "执行流水线构建实例", httpMethod = "POST")
-    @ApiImplicitParams({
-            @ApiImplicitParam(name = "projectId", dataType = "int", paramType = "query"),
-            @ApiImplicitParam(name = "pipelineId", dataType = "int", paramType = "query")
-    })
+    @ApiOperation("执行流水线构建实例")
     @PostMapping
-    public Response deploy(@RequestParam("projectId") Integer projectId,
-                           @RequestParam("pipelineId") Integer pipelineId) throws ServiceException {
+    public Response<?> deploy(@RequestParam Integer projectId,
+                              @RequestParam Integer pipelineId) throws ServiceException {
         deploymentService.deploy(projectId, pipelineId, UserService.loginUser().getId());
         return Response.buildSuccess();
     }
 
-    @ApiOperation(value = "删除一个部署的实例", httpMethod = "DELETE")
-    @ApiImplicitParams({
-            @ApiImplicitParam(name = "projectId", dataType = "int", paramType = "query"),
-            @ApiImplicitParam(name = "pipelineId", dataType = "int", paramType = "query")
-    })
+    @ApiOperation("删除一个部署的实例")
     @DeleteMapping
-    public Response delete(@RequestParam("projectId") Integer projectId,
-                           @RequestParam("pipelineId") Integer pipelineId) throws ServiceException {
+    public Response<?> delete(@RequestParam Integer projectId,
+                              @RequestParam Integer pipelineId) throws ServiceException {
         deploymentService.delete(projectId, pipelineId);
         return Response.buildSuccess();
     }
 
-    @ApiOperation(value = "获取一个部署的实例的日志", httpMethod = "GET")
-    @ApiImplicitParams({
-            @ApiImplicitParam(name = "projectId", dataType = "int", paramType = "query"),
-            @ApiImplicitParam(name = "pipelineId", dataType = "int", paramType = "query")
-    })
+    @ApiOperation("获取一个部署的实例的日志")
     @GetMapping("/log")
-    public Response log(@RequestParam("projectId") Integer projectId,
-                        @RequestParam("pipelineId") Integer pipelineId) throws ServiceException {
+    public Response<String> log(@RequestParam Integer projectId,
+                                @RequestParam Integer pipelineId) throws ServiceException {
         return Response.buildSuccess(deploymentService.log(projectId, pipelineId));
     }
 }

+ 79 - 17
web/src/main/java/cn/seecoder/web/dao/pipeline/DeploymentMapper.java

@@ -1,66 +1,128 @@
 package cn.seecoder.web.dao.pipeline;
 
-import org.apache.ibatis.annotations.*;
-import org.springframework.stereotype.Repository;
 import cn.seecoder.web.dao.provider.GeneralInsertUpdateSqlProvider;
 import cn.seecoder.web.model.po.pipeline.DeploymentPO;
+import org.apache.ibatis.annotations.*;
+import org.springframework.stereotype.Repository;
 
 import java.util.List;
 
 /**
+ * 部署实例数据访问层
+ *
  * @author PuHong Weng
- * @date 2021/4/10
- * @description:
  */
 @Repository
 public interface DeploymentMapper {
 
+    /**
+     * 插入部署实例
+     *
+     * @param deploymentPO 部署实例对象
+     * @param ignoredCols  忽略的列
+     * @return 是否插入成功
+     */
     @InsertProvider(type = GeneralInsertUpdateSqlProvider.class, method = "insert")
     @Options(useGeneratedKeys = true, keyProperty = "param1.id", keyColumn = "id")
     boolean insert(DeploymentPO deploymentPO, String... ignoredCols);
 
-    @UpdateProvider(type= GeneralInsertUpdateSqlProvider.class, method="updateById")
+    /**
+     * 更新部署实例
+     *
+     * @param deploymentPO 部署实例对象
+     * @return 更新的行数
+     */
+    @UpdateProvider(type = GeneralInsertUpdateSqlProvider.class, method = "updateById")
     int update(DeploymentPO deploymentPO);
 
+    /**
+     * 根据 ID 查询部署实例
+     *
+     * @param id 部署实例 ID
+     * @return 部署实例对象
+     */
     @Select("select * from deployment where id = #{id}")
     DeploymentPO selectById(Integer id);
 
+    /**
+     * 根据流水线 ID 查询部署实例
+     *
+     * @param pipelineId 流水线 ID
+     * @return 部署实例对象
+     */
     @Select("select * from deployment where pipeline_id = #{pipelineId}")
     DeploymentPO selectByPipelineId(Integer pipelineId);
 
+    /**
+     * 根据关键词搜索部署实例(支持命名空间和部署名称模糊匹配)
+     *
+     * @param keyword  搜索关键词
+     * @param startPos 起始位置
+     * @param pageSize 页大小
+     * @return 部署实例列表
+     */
     @Select("select * from deployment where namespace like CONCAT('%', #{keyword}, '%') or " +
             "deploy_name like CONCAT('%', #{keyword}, '%') " +
             "limit #{startPos}, #{pageSize}")
-    List<DeploymentPO> searchDeployments(@Param("keyword") String keyword, @Param("startPos") Integer startPos, @Param("pageSize") Integer pageSize);
+    List<DeploymentPO> searchDeployments(@Param("keyword") String keyword,
+                                         @Param("startPos") Integer startPos,
+                                         @Param("pageSize") Integer pageSize);
 
+    /**
+     * 根据命名空间查询部署实例
+     *
+     * @param namespace 命名空间
+     * @return 部署实例列表
+     */
     @Select("select * from deployment where namespace = #{namespace}")
     List<DeploymentPO> selectByNamespace(String namespace);
 
-//    @Select("select * from deployment where deploy_name = #{deployName}")
-//    DeploymentPO selectByDeployName(String deployName);
-
+    /**
+     * 分页查询部署实例
+     *
+     * @param startPos 起始位置
+     * @param pageSize 页大小
+     * @return 部署实例列表
+     */
     @Select("select * from deployment limit #{startPos}, #{pageSize}")
-    List<DeploymentPO> selectPagedDeployments(@Param("startPos") Integer startPos, @Param("pageSize") Integer pageSize);
+    List<DeploymentPO> selectPagedDeployments(@Param("startPos") Integer startPos,
+                                              @Param("pageSize") Integer pageSize);
 
     /**
-     * !!For Scheduled Deployments Purge Only
-     * @return DeploymentPO List
+     * 查询所有部署实例(用于定时清理任务)
+     *
+     * @return 所有部署实例列表
      */
     @Select("select * from deployment")
     List<DeploymentPO> selectAllDeployments();
 
+    /**
+     * 查询部署实例总数
+     *
+     * @return 部署实例总数
+     */
     @Select("select count(*) from deployment")
     Integer selectTotalItemCount();
 
+    /**
+     * 删除部署实例
+     *
+     * @param pipelineId 流水线 ID
+     */
     @Delete("delete from deployment where pipeline_id = #{pipelineId}")
     void delete(Integer pipelineId);
 
+    /**
+     * 根据流水线 ID 列表批量查询部署实例
+     *
+     * @param ids 流水线 ID 列表
+     * @return 部署实例列表
+     */
     @Select("<script>" +
-            "SELECT * " +
-            "   FROM deployment" +
-            "   WHERE pipeline_id in" +
-            "   <foreach collection='ids' open='(' item='id' separator=',' close=')'> #{id}</foreach>" +
+            "SELECT * FROM deployment WHERE pipeline_id IN " +
+            "<foreach collection='ids' open='(' item='id' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
             "</script>")
     List<DeploymentPO> selectByPipelineIds(@Param("ids") List<Integer> ids);
-
 }

+ 150 - 89
web/src/main/java/cn/seecoder/web/service/impl/pipeline/DeploymentServiceImpl.java

@@ -31,6 +31,7 @@ import org.springframework.stereotype.Service;
 
 import java.sql.Timestamp;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -52,12 +53,8 @@ public class DeploymentServiceImpl implements DeploymentService {
     private final ApplicationProperties properties;
     private final DeploymentMapper deploymentMapper;
 
-    /**
-     * 获取指定项目的部署信息列表
-     */
     @Override
-    public List<DeploymentInfoVO> retrieveDeploymentInfos(Integer projectId) throws AccessDeniedException {
-        // 获取项目对应的流水线列表
+    public List<DeploymentInfoVO> retrieveDeploymentInfos(Integer projectId) {
         List<PipelinePO> pipelines = pipelineMapper.selectByProjectId(projectId);
         if (pipelines.isEmpty()) {
             return new ArrayList<>();
@@ -69,39 +66,48 @@ public class DeploymentServiceImpl implements DeploymentService {
 
         List<DeploymentPO> deployments = deploymentMapper.selectByPipelineIds(pipelineIds);
 
-        // 构建部署信息列表
-        return deployments.stream().map(deployment -> {
-            PipelineInspector inspector = PipelineInspectorFactory.init(
-                    deployment.getNamespace(),
-                    deployment.getDeployName()
-            );
+        return deployments.stream()
+                .map(this::buildDeploymentInfo)
+                .collect(Collectors.toList());
+    }
 
-            // 获取部署状态
-            String status;
-            V1DeploymentStatus deploymentStatus = inspector.inspectStatus();
-            if (deploymentStatus == null) {
-                status = "部署不存在,请重新部署";
-            } else if (deploymentStatus.getAvailableReplicas() != null
-                    && deploymentStatus.getAvailableReplicas() >= 1) {
-                status = "正常运行";
-            } else {
-                status = "不可用";
-            }
-
-            return DeploymentInfoVO.builder()
-                    .id(deployment.getId())
-                    .pipelineId(deployment.getPipelineId())
-                    .name(deployment.getDeployName() + "." + deployment.getNamespace())
-                    .deployedTime(deployment.getDeployedTime())
-                    .accessUrl(deployment.getAccessUrl())
-                    .status(status)
-                    .build();
-        }).collect(Collectors.toList());
+    /**
+     * 构建部署信息 VO
+     */
+    private DeploymentInfoVO buildDeploymentInfo(DeploymentPO deployment) {
+        PipelineInspector inspector = PipelineInspectorFactory.init(
+                deployment.getNamespace(),
+                deployment.getDeployName()
+        );
+
+        String status = getDeploymentStatus(inspector.inspectStatus());
+
+        return DeploymentInfoVO.builder()
+                .id(deployment.getId())
+                .pipelineId(deployment.getPipelineId())
+                .name(deployment.getDeployName() + "." + deployment.getNamespace())
+                .deployedTime(deployment.getDeployedTime())
+                .accessUrl(deployment.getAccessUrl())
+                .status(status)
+                .build();
     }
 
     /**
-     * 部署指定项目流水线
+     * 获取部署状态描述
      */
+    private String getDeploymentStatus(V1DeploymentStatus deploymentStatus) {
+        if (deploymentStatus == null) {
+            return "部署不存在,请重新部署";
+        }
+
+        if (deploymentStatus.getAvailableReplicas() != null
+                && deploymentStatus.getAvailableReplicas() >= 1) {
+            return "正常运行";
+        }
+
+        return "不可用";
+    }
+
     @Override
     @Async("deployExecutor")
     public void deploy(Integer projectId, Integer pipelineId, Integer userId) throws ServiceException {
@@ -109,6 +115,23 @@ public class DeploymentServiceImpl implements DeploymentService {
         ProjectPO projectPO = projectMapper.getProjectById(projectId);
 
         // 创建部署历史记录
+        PipelineRecordPO record = createDeploymentRecord(pipelinePO, userId);
+        log.info("流水线触发部署,Pipeline ID: {}", record.getPipelineId());
+
+        // 处理部署产物记录
+        handleDeploymentArtifact(pipelinePO, projectPO, pipelineId, record);
+
+        // 执行流水线
+        executePipeline(pipelinePO, projectPO, record);
+
+        // 记录 ANA 日志
+        logDeploymentAnalytics(projectId, pipelineId, userId, record);
+    }
+
+    /**
+     * 创建部署记录
+     */
+    private PipelineRecordPO createDeploymentRecord(PipelinePO pipelinePO, Integer userId) {
         PipelineRecordPO record = PipelineRecordPO.builder()
                 .result(PipelineRecordPO.DEPLOYING)
                 .pipelineId(pipelinePO.getId())
@@ -117,43 +140,58 @@ public class DeploymentServiceImpl implements DeploymentService {
                 .startTime(new Timestamp(System.currentTimeMillis()))
                 .build();
         pipelineRecordMapper.insert(record);
-        log.info("流水线触发部署,对应 Pipeline ID:{}", record.getPipelineId());
+        return record;
+    }
 
+    /**
+     * 处理部署产物记录
+     */
+    private void handleDeploymentArtifact(PipelinePO pipelinePO, ProjectPO projectPO,
+                                          Integer pipelineId, PipelineRecordPO record) {
         // 判断流水线是否产生部署产物
         boolean hasDeployment = !SONAR_JAVA8.equals(pipelinePO.getTemplateName());
+        if (!hasDeployment) {
+            return;
+        }
 
-        // 处理部署产物记录
-        if (hasDeployment) {
-            DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
-            if (deployment == null) {
-                // 第一次部署,生成访问 URL
-                String accessUrl;
-                if (MYSQL.equals(pipelinePO.getTemplateName())) {
-                    // MySQL 内部访问 URL
-                    accessUrl = pipelinePO.getName() + K8sConstants.SERVICE_SUFFIX + "."
-                            + projectPO.getUniqueK8sNamespace() + ".svc.cluster.local";
-                } else {
-                    // 其他模板通过 Ingress 暴露 URL
-                    accessUrl = pipelinePO.getName() + "-" + projectPO.getUniqueK8sNamespace()
-                            + properties.getK8s().getIngressHostSuffix();
-                }
-
-                // 插入部署记录
-                deploymentMapper.insert(DeploymentPO.builder()
-                        .namespace(projectPO.getUniqueK8sNamespace())
-                        .deployName(pipelinePO.getName())
-                        .accessUrl(accessUrl)
-                        .pipelineId(pipelineId)
-                        .deployedTime(record.getStartTime())
-                        .build());
-            } else {
-                // 更新已有部署记录的时间
-                deployment.setDeployedTime(record.getStartTime());
-                deploymentMapper.update(deployment);
-            }
+        DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
+        if (deployment == null) {
+            // 第一次部署,创建部署记录
+            String accessUrl = generateAccessUrl(pipelinePO, projectPO);
+            deploymentMapper.insert(DeploymentPO.builder()
+                    .namespace(projectPO.getUniqueK8sNamespace())
+                    .deployName(pipelinePO.getName())
+                    .accessUrl(accessUrl)
+                    .pipelineId(pipelineId)
+                    .deployedTime(record.getStartTime())
+                    .build());
+        } else {
+            // 更新已有部署记录的时间
+            deployment.setDeployedTime(record.getStartTime());
+            deploymentMapper.update(deployment);
         }
+    }
 
-        // 执行流水线
+    /**
+     * 生成访问 URL
+     */
+    private String generateAccessUrl(PipelinePO pipelinePO, ProjectPO projectPO) {
+        if (MYSQL.equals(pipelinePO.getTemplateName())) {
+            // MySQL 内部访问 URL
+            return pipelinePO.getName() + K8sConstants.SERVICE_SUFFIX + "."
+                    + projectPO.getUniqueK8sNamespace() + ".svc.cluster.local";
+        } else {
+            // 其他模板通过 Ingress 暴露 URL
+            return pipelinePO.getName() + "-" + projectPO.getUniqueK8sNamespace()
+                    + properties.getK8s().getIngressHostSuffix();
+        }
+    }
+
+    /**
+     * 执行流水线
+     */
+    private void executePipeline(PipelinePO pipelinePO, ProjectPO projectPO, PipelineRecordPO record)
+            throws ServiceException {
         Pipeline pipeline = null;
         try {
             pipeline = PipelineFactory.init(
@@ -166,31 +204,54 @@ public class DeploymentServiceImpl implements DeploymentService {
             pipeline.addConfig("pipelineRecordId", record.getId().toString());
             pipeline.start();
 
-            // 部署成功,更新记录
+            // 部署成功
             record.setResult(PipelineRecordPO.DEPLOY_SUCCESS);
             record.setDetails(pipeline.getContext().getResult());
 
         } catch (PipelineException e) {
-            record.setResult(PipelineRecordPO.DEPLOY_FAIL);
+            handlePipelineException(pipeline, record, e);
+        } catch (RuntimeException e) {
+            handleRuntimeException(pipeline, record, e);
+        } finally {
+            pipelineRecordMapper.update(record);
+        }
+    }
+
+    /**
+     * 处理流水线异常
+     */
+    private void handlePipelineException(Pipeline pipeline, PipelineRecordPO record,
+                                         PipelineException e) throws ServiceException {
+        record.setResult(PipelineRecordPO.DEPLOY_FAIL);
+        if (pipeline != null) {
             record.setDetails(pipeline.getContext().getResult());
-            log.error("流水线配置转换发生错误", e);
-            throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "应用部署失败", e);
+        }
+        log.error("流水线配置转换发生错误", e);
+        throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "应用部署失败", e);
+    }
 
-        } catch (RuntimeException e) {
+    /**
+     * 处理运行时异常
+     */
+    private void handleRuntimeException(Pipeline pipeline, PipelineRecordPO record,
+                                        RuntimeException e) throws ServiceException {
+        if (pipeline != null) {
             pipeline.getContext().appendErrorResult("运行时错误: ", e);
-            record.setResult(PipelineRecordPO.DEPLOY_FAIL);
             record.setDetails(pipeline.getContext().getResult());
-            throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "应用部署失败", e);
-
-        } finally {
-            pipelineRecordMapper.update(record);
         }
+        record.setResult(PipelineRecordPO.DEPLOY_FAIL);
+        log.error("流水线运行时错误", e);
+        throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "应用部署失败", e);
+    }
 
-        // ANA 日志记录部署信息
+    /**
+     * 记录部署分析日志
+     */
+    private void logDeploymentAnalytics(Integer projectId, Integer pipelineId,
+                                        Integer userId, PipelineRecordPO record) {
         try {
-            List<Integer> pipelineIds = new ArrayList<>();
-            pipelineIds.add(pipelineId);
-            PipelineRecordPO latestRecord = pipelineRecordMapper.selectPipelinesLatestRecord(pipelineIds);
+            PipelineRecordPO latestRecord = pipelineRecordMapper.selectPipelinesLatestRecord(
+                    Collections.singletonList(pipelineId));
 
             JSONObject logObject = new JSONObject();
             logObject.put("user_id", userId);
@@ -201,40 +262,40 @@ public class DeploymentServiceImpl implements DeploymentService {
             logObject.put("result", record.getResult());
             logObject.put("success", PipelineRecordPO.DEPLOY_SUCCESS.equals(record.getResult()));
 
-            LogTrackingUtil.log(JSONObject.toJSONString(logObject), OpType.DEPLOY_PIPELINE);
-        } catch (Exception ignored) {
-            // ANA 日志异常不影响部署
+            LogTrackingUtil.log(logObject.toJSONString(), OpType.DEPLOY_PIPELINE);
+        } catch (Exception e) {
+            log.warn("记录部署分析日志失败", e);
         }
     }
 
-    /**
-     * 删除指定项目流水线部署
-     */
     @Override
     @Async
     public void delete(Integer projectId, Integer pipelineId) throws AccessDeniedException {
         DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
-
         if (deployment == null) {
+            log.warn("删除部署失败:Pipeline ID {} 对应的部署不存在", pipelineId);
             return;
         }
 
+        // 删除数据库记录
         deploymentMapper.delete(pipelineId);
+
+        // 删除 K8s 部署资源
         PipelineInspector inspector = PipelineInspectorFactory.init(
                 deployment.getNamespace(),
                 deployment.getDeployName()
         );
         inspector.delete();
+
+        log.info("成功删除部署:Pipeline ID {}, Namespace: {}, DeployName: {}",
+                pipelineId, deployment.getNamespace(), deployment.getDeployName());
     }
 
-    /**
-     * 获取指定流水线部署日志
-     */
     @Override
     public String log(Integer projectId, Integer pipelineId) {
         DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
-
         if (deployment == null) {
+            log.warn("获取日志失败:Pipeline ID {} 对应的部署不存在", pipelineId);
             return "";
         }
 

+ 28 - 10
web/src/main/java/cn/seecoder/web/service/pipeline/DeploymentService.java

@@ -1,34 +1,52 @@
 package cn.seecoder.web.service.pipeline;
 
-import cn.seecoder.web.model.vo.pipeline.DeploymentInfoVO;
 import cn.seecoder.common.exceptions.AccessDeniedException;
 import cn.seecoder.common.exceptions.ServiceException;
+import cn.seecoder.web.model.vo.pipeline.DeploymentInfoVO;
 
 import java.util.List;
 
 /**
+ * 流水线实例部署服务
+ *
  * @author PuHong Weng
- * @date 2021/3/11
- * @description: 流水线实例部署相关的service
  */
 public interface DeploymentService {
 
     /**
      * 获取项目部署的实例信息
+     *
+     * @param projectId 项目ID
+     * @return 部署实例信息列表
+     * @throws AccessDeniedException 访问被拒绝异常
      */
     List<DeploymentInfoVO> retrieveDeploymentInfos(Integer projectId) throws AccessDeniedException;
 
-
     /**
-     * 执行一条pipepine
+     * 执行一条流水线
+     *
+     * @param projectId  项目ID
+     * @param pipelineId 流水线ID
+     * @param userId     用户ID
+     * @throws ServiceException 服务异常
      */
-    void deploy(Integer projectId,Integer pipelineId, Integer userId) throws ServiceException;
+    void deploy(Integer projectId, Integer pipelineId, Integer userId) throws ServiceException;
 
     /**
-     * 删除一个实例
-     * todo 异步/异步异常捕获刷新result
+     * 删除一个部署实例
+     *
+     * @param projectId  项目ID
+     * @param pipelineId 流水线ID
+     * @throws AccessDeniedException 访问被拒绝异常
      */
-    void delete(Integer projectId,Integer pipelineId) throws AccessDeniedException;
+    void delete(Integer projectId, Integer pipelineId) throws AccessDeniedException;
 
-    String log(Integer projectId,Integer pipelineId);
+    /**
+     * 获取部署实例的日志
+     *
+     * @param projectId  项目ID
+     * @param pipelineId 流水线ID
+     * @return 日志内容
+     */
+    String log(Integer projectId, Integer pipelineId);
 }