Browse Source

AppDeployServiceImpl 添加 CDN 刷新功能

reghao 9 months ago
parent
commit
b5d6040913

+ 5 - 0
mgr/pom.xml

@@ -169,6 +169,11 @@
             <artifactId>aliyun-sdk-oss</artifactId>
             <version>3.8.0</version>
         </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>cdn20180510</artifactId>
+            <version>3.2.0</version>
+        </dependency>
     </dependencies>
 
     <profiles>

+ 12 - 0
mgr/src/main/java/cn/reghao/devops/mgr/aliyun/db/repository/AliyunAccountRepository.java

@@ -0,0 +1,12 @@
+package cn.reghao.devops.mgr.aliyun.db.repository;
+
+import cn.reghao.devops.mgr.aliyun.model.po.AliyunAccount;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * @author reghao
+ * @date 2025-06-06 09:46:54
+ */
+public interface AliyunAccountRepository extends JpaRepository<AliyunAccount, Integer> {
+    AliyunAccount findByType(String type);
+}

+ 0 - 11
mgr/src/main/java/cn/reghao/devops/mgr/aliyun/db/repository/OssConfigRepository.java

@@ -1,11 +0,0 @@
-package cn.reghao.devops.mgr.aliyun.db.repository;
-
-import cn.reghao.devops.mgr.aliyun.model.po.OssConfig;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-/**
- * @author reghao
- * @date 2025-06-06 09:46:54
- */
-public interface OssConfigRepository extends JpaRepository<OssConfig, Integer> {
-}

+ 6 - 2
mgr/src/main/java/cn/reghao/devops/mgr/aliyun/model/po/OssConfig.java → mgr/src/main/java/cn/reghao/devops/mgr/aliyun/model/po/AliyunAccount.java

@@ -19,13 +19,17 @@ import javax.persistence.Table;
 @Setter
 @Getter
 @Entity
-@Table(name = "devops_oss_config")
-public class OssConfig extends BaseEntity {
+@Table(name = "devops_aliyun_account")
+public class AliyunAccount extends BaseEntity {
     @Column(nullable = false, unique = true)
     private String endpoint;
     @Column(nullable = false)
     private String accessKeyId;
     @Column(nullable = false)
     private String accessKeySecret;
+    @Column(nullable = false)
+    private String ramUser;
+    @Column(nullable = false, unique = true)
+    private String type;
     private String bucketName;
 }

+ 21 - 0
mgr/src/main/java/cn/reghao/devops/mgr/aliyun/model/vo/RefreshStatus.java

@@ -0,0 +1,21 @@
+package cn.reghao.devops.mgr.aliyun.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author reghao
+ * @date 2025-07-31 15:52:47
+ */
+@AllArgsConstructor
+@Getter
+public class RefreshStatus {
+    private String taskId;
+    private String type;
+    private String domain;
+    private LocalDateTime startAt;
+    private String status;
+    private String process;
+}

+ 120 - 0
mgr/src/main/java/cn/reghao/devops/mgr/aliyun/service/AliyunCdn.java

@@ -0,0 +1,120 @@
+package cn.reghao.devops.mgr.aliyun.service;
+
+import cn.reghao.devops.mgr.aliyun.db.repository.AliyunAccountRepository;
+import cn.reghao.devops.mgr.aliyun.model.po.AliyunAccount;
+import cn.reghao.devops.mgr.aliyun.model.vo.RefreshStatus;
+import cn.reghao.jutil.jdk.converter.DateTimeConverter;
+import com.aliyun.cdn20180510.Client;
+import com.aliyun.cdn20180510.models.DescribeRefreshTasksRequest;
+import com.aliyun.cdn20180510.models.DescribeRefreshTasksResponse;
+import com.aliyun.cdn20180510.models.RefreshObjectCachesRequest;
+import com.aliyun.cdn20180510.models.RefreshObjectCachesResponse;
+import com.aliyun.tea.TeaModel;
+import com.aliyun.teaopenapi.models.Config;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2025-07-31 09:09:36
+ */
+@Slf4j
+@Component
+public class AliyunCdn {
+    private Client client;
+    private final AliyunAccountRepository aliyunAccountRepository;
+
+    public AliyunCdn(AliyunAccountRepository aliyunAccountRepository) {
+        this.aliyunAccountRepository = aliyunAccountRepository;
+    }
+
+    @PostConstruct
+    public void initClient() throws Exception {
+        AliyunAccount aliyunAccount = aliyunAccountRepository.findByType("cdn");
+        String endpoint = aliyunAccount.getEndpoint();
+        String accessKeyId = aliyunAccount.getAccessKeyId();
+        String accessKeySecret = aliyunAccount.getAccessKeySecret();
+        Config config = new Config()
+                // 必填,您的 AccessKey ID
+                .setAccessKeyId(accessKeyId)
+                // 必填,您的 AccessKey Secret
+                .setAccessKeySecret(accessKeySecret)
+                .setEndpoint(endpoint);
+        this.client = new Client(config);
+    }
+
+    public String refreshCdn(String domain) {
+        String objectType = "Directory";
+        String objectPath = String.format("https://%s/", domain);
+        return refreshObjectCaches(objectType, objectPath);
+    }
+
+    /**
+     * 刷新 aliyun-cdn 缓存
+     *
+     * @param
+     * @return
+     * @date 2023-12-18 16:24:01
+     */
+    private String refreshObjectCaches(String objectType, String objectPath) {
+        RefreshObjectCachesRequest req = new RefreshObjectCachesRequest();
+        // 此参数为刷新的类型, 其值可以为File或Directory。默认值:File。
+        req.objectType = objectType;
+        // 加速的文件位置,wdtest.licai.cn为配置的域名,后加加速的文件名
+        req.objectPath = objectPath;
+        try {
+            RefreshObjectCachesResponse resp = client.refreshObjectCaches(req);
+            Map<String, Object> map = TeaModel.buildMap(resp);
+            Map<String, Object> map1 = (Map<String, Object>)map.get("body");
+            String taskId = (String) map1.get("RefreshTaskId");
+            return taskId;
+        } catch (Exception e) {
+            log.error("{}", e.getMessage());
+        }
+
+        return null;
+    }
+
+    public RefreshStatus getRefreshStatus(String taskId) {
+        RefreshStatus refreshStatus = describeRefreshTasks(taskId);
+        return refreshStatus;
+    }
+
+    /**
+     * 获取 aliyun-cdn 缓存缓存刷新任务状态
+     *
+     * @param
+     * @return
+     * @date 2023-12-18 16:24:19
+     */
+    private RefreshStatus describeRefreshTasks(String taskId) {
+        DescribeRefreshTasksRequest request = new DescribeRefreshTasksRequest();
+        request.taskId = taskId;
+        try {
+            DescribeRefreshTasksResponse resp = client.describeRefreshTasks(request);
+            Map<String, Object> map = TeaModel.buildMap(resp);
+            Map<String, Object> bodyMap = (Map)map.get("body");
+            List list = (List) ((Map) bodyMap.get("Tasks")).get("CDNTask");
+            for (Object item : list) {
+                Map itemMap = (Map) item;
+                String taskId1 = (String) itemMap.get("TaskId");
+                String objectType = (String) itemMap.get("ObjectType");
+                String objectPath = (String) itemMap.get("ObjectPath");
+                String createTime = (String) itemMap.get("CreationTime");
+                LocalDateTime localDateTime = DateTimeConverter.localDateTime(createTime);
+                String status = (String) itemMap.get("Status");
+                String process = (String) itemMap.get("Process");
+                return new RefreshStatus(taskId, objectType, objectPath, localDateTime, status, process);
+            }
+        } catch (Exception error) {
+            log.error("{}", error.getMessage());
+        }
+
+        return null;
+    }
+}

+ 21 - 70
mgr/src/main/java/cn/reghao/devops/mgr/aliyun/service/AliyunOss.java

@@ -1,22 +1,21 @@
 package cn.reghao.devops.mgr.aliyun.service;
 
-import cn.reghao.devops.mgr.aliyun.db.repository.OssConfigRepository;
-import cn.reghao.devops.mgr.aliyun.model.po.OssConfig;
+import cn.reghao.devops.mgr.aliyun.db.repository.AliyunAccountRepository;
+import cn.reghao.devops.mgr.aliyun.model.po.AliyunAccount;
 import com.aliyun.oss.OSS;
 import com.aliyun.oss.OSSClientBuilder;
 import com.aliyun.oss.model.*;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
+import org.springframework.stereotype.Component;
 
+import javax.annotation.PostConstruct;
 import java.io.*;
-import java.net.URL;
 import java.nio.file.FileVisitResult;
 import java.nio.file.FileVisitor;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 
 /**
@@ -24,27 +23,22 @@ import java.util.List;
  * @date 2024-08-30 16:13:38
  */
 @Slf4j
-@Service
+@Component
 public class AliyunOss {
     private OSS ossClient;
-    private String bucketName;
-    private final OssConfigRepository ossConfigRepository;
+    private final AliyunAccountRepository aliyunAccountRepository;
 
-    public AliyunOss(OssConfigRepository ossConfigRepository) {
-        this.ossConfigRepository = ossConfigRepository;
+    public AliyunOss(AliyunAccountRepository aliyunAccountRepository) {
+        this.aliyunAccountRepository = aliyunAccountRepository;
     }
 
-    private OSS getOssClient() {
-        if (ossClient == null) {
-            OssConfig ossConfig = ossConfigRepository.findAll().get(0);
-            String endpoint = ossConfig.getEndpoint();
-            String accessKeyId = ossConfig.getAccessKeyId();
-            String accessKeySecret = ossConfig.getAccessKeySecret();
-            this.bucketName = ossConfig.getBucketName();
-            this.ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
-        }
-
-        return ossClient;
+    @PostConstruct
+    public void initOssClient() {
+        AliyunAccount aliyunAccount = aliyunAccountRepository.findByType("oss");
+        String endpoint = aliyunAccount.getEndpoint();
+        String accessKeyId = aliyunAccount.getAccessKeyId();
+        String accessKeySecret = aliyunAccount.getAccessKeySecret();
+        this.ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
     }
 
     public void uploadObject(String bucketName, String objectName, File file) throws IOException {
@@ -52,15 +46,7 @@ public class AliyunOss {
         ObjectMetadata objectMetadata = new ObjectMetadata();
         objectMetadata.setObjectAcl(CannedAccessControlList.PublicRead);
         putObjectRequest.setMetadata(objectMetadata);
-        getOssClient().putObject(putObjectRequest);
-    }
-
-    public void uploadObject(String objectName, File file) throws IOException {
-        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, file);
-        ObjectMetadata objectMetadata = new ObjectMetadata();
-        objectMetadata.setObjectAcl(CannedAccessControlList.PublicRead);
-        putObjectRequest.setMetadata(objectMetadata);
-        getOssClient().putObject(putObjectRequest);
+        ossClient.putObject(putObjectRequest);
     }
 
     /**
@@ -81,7 +67,7 @@ public class AliyunOss {
         return failedList;
     }
 
-    void walkDir(String bucketName, Path path, List<String> failedList) throws IOException {
+    private void walkDir(String bucketName, Path path, List<String> failedList) throws IOException {
         final String BLANK = "";
         if (path.toString().equals(BLANK)) {
             throw new IOException("CAN NOT specify a BLANK path");
@@ -111,7 +97,7 @@ public class AliyunOss {
         });
     }
 
-    void uploadObject(String bucketName, File file, String baseDir, List<String> failedList) {
+    private void uploadObject(String bucketName, File file, String baseDir, List<String> failedList) {
         String absolutePath = file.getAbsolutePath();
         String objectName = absolutePath.replace(baseDir + "/", "");
         try {
@@ -122,46 +108,11 @@ public class AliyunOss {
         }
     }
 
-    public void downloadObject(String objectName) throws IOException {
-        OSSObject object = getOssClient().getObject(bucketName, objectName);
-        log.info("Content-Type: {}", object.getObjectMetadata().getContentType());
-        displayTextInputStream(object.getObjectContent());
-    }
-
-    private void displayTextInputStream(InputStream input) throws IOException {
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
-        while (true) {
-            String line = reader.readLine();
-            if (line == null) break;
-        }
-        reader.close();
-    }
-
-    public void deleteObject(String objectName) {
-        getOssClient().deleteObject(bucketName, objectName);
-    }
-
-    public void listObjects(String prefix) {
-        ObjectListing objectListing = getOssClient().listObjects(bucketName, prefix);
-        for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
-            String objectName = objectSummary.getKey();
-            long size = objectSummary.getSize();
-            if (size != 0) {
-                //deleteObject(objectName);
-            }
-            log.info(" - {} (size={})", objectSummary.getKey(), objectSummary.getSize());
-        }
+    public void deleteObject(String bucketName, String objectName) {
+        ossClient.deleteObject(bucketName, objectName);
     }
 
     public void close() {
-        getOssClient().shutdown();
-    }
-
-    public String getSignedUrl(String objectName) {
-        int expireSecond = 3600;
-        long timestamp = System.currentTimeMillis() + expireSecond*1000L;
-        Date expiration = new Date(timestamp);
-        URL url = getOssClient().generatePresignedUrl(bucketName, objectName, expiration);
-        return url.toString();
+        ossClient.shutdown();
     }
 }

+ 37 - 0
mgr/src/main/java/cn/reghao/devops/mgr/app/model/vo/msg/CdnRereshNotifyMsg.java

@@ -0,0 +1,37 @@
+package cn.reghao.devops.mgr.app.model.vo.msg;
+
+import cn.reghao.devops.mgr.admin.service.notifier.ding.DingMsg;
+import cn.reghao.jutil.jdk.converter.DateTimeConverter;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.time.LocalDateTime;
+
+/**
+ * 应用部署通知消息
+ *
+ * @author reghao
+ * @date 2020-06-24 10:32:34
+ */
+@AllArgsConstructor
+@Getter
+public class CdnRereshNotifyMsg {
+    private String buildId;
+    private String appId;
+    private String domain;
+    private LocalDateTime refreshTime;
+    private String result;
+
+    public DingMsg dingMsg() {
+        String title = "CDN 刷新通知";
+        StringBuilder sb = new StringBuilder();
+        sb.append("# CDN 刷新").append(System.lineSeparator());
+        sb.append("### 构建 ID: ").append(buildId).append(System.lineSeparator());
+        sb.append("### 应用: ").append(appId).append(System.lineSeparator());
+        sb.append("### 域名: ").append(domain).append(System.lineSeparator());
+        sb.append("### 刷新时间: ").append(DateTimeConverter.format(refreshTime)).append(System.lineSeparator());
+        sb.append("### 刷新结果: ").append(result);
+        String text = sb.toString();
+        return new DingMsg(title, text);
+    }
+}

+ 2 - 0
mgr/src/main/java/cn/reghao/devops/mgr/app/service/bd/BuildDeployNotify.java

@@ -2,6 +2,7 @@ package cn.reghao.devops.mgr.app.service.bd;
 
 import cn.reghao.devops.mgr.app.model.po.AppBuilding;
 import cn.reghao.devops.mgr.app.model.po.log.DeployLog;
+import cn.reghao.devops.mgr.app.model.vo.msg.CdnRereshNotifyMsg;
 
 /**
  * 构建部署通知
@@ -12,4 +13,5 @@ import cn.reghao.devops.mgr.app.model.po.log.DeployLog;
 public interface BuildDeployNotify {
     void buildNotify(AppBuilding appBuilding);
     void deployNotify(DeployLog deployLog);
+    void refreshNotify(CdnRereshNotifyMsg cdnRereshNotifyMsg);
 }

+ 6 - 0
mgr/src/main/java/cn/reghao/devops/mgr/app/service/bd/impl/BuildDeployNotifyImpl.java

@@ -4,6 +4,7 @@ import cn.reghao.devops.mgr.app.db.query.AppBuildQuery;
 import cn.reghao.devops.mgr.app.model.po.AppBuilding;
 import cn.reghao.devops.mgr.app.model.po.log.BuildLog;
 import cn.reghao.devops.mgr.app.model.po.log.DeployLog;
+import cn.reghao.devops.mgr.app.model.vo.msg.CdnRereshNotifyMsg;
 import cn.reghao.devops.mgr.app.service.bd.BuildDeployNotify;
 import cn.reghao.devops.mgr.app.model.vo.msg.BuildNotifyMsg;
 import cn.reghao.devops.mgr.app.model.vo.msg.DeployNotifyMsg;
@@ -39,4 +40,9 @@ public class BuildDeployNotifyImpl implements BuildDeployNotify {
         DeployNotifyMsg deployNotifyMsg = new DeployNotifyMsg(buildLog, deployLog);
         notifyService.notify(deployNotifyMsg.dingMsg());
     }
+
+    @Override
+    public void refreshNotify(CdnRereshNotifyMsg cdnRereshNotifyMsg) {
+        notifyService.notify(cdnRereshNotifyMsg.dingMsg());
+    }
 }

+ 3 - 0
mgr/src/main/java/cn/reghao/devops/mgr/app/service/bd/task/OssDeployTask.java

@@ -13,6 +13,8 @@ import java.time.LocalDateTime;
 import java.util.List;
 
 /**
+ * OSS 部署前端静态文件任务
+ *
  * @author reghao
  * @date 2025-06-05 10:26:31
  */
@@ -39,6 +41,7 @@ public class OssDeployTask implements Runnable {
         String destDirPath = LocalBuildDir.packDir + File.separator + appId + File.separator + dirname;
         File destDir = new File(destDirPath);
         if (destDir.exists()) {
+            log.error("start oss deploy task with appId {}", appId);
             String bucketName = machineId.split("\\.")[1];
             List<String> failedList = aliyunOss.uploadDir(bucketName, destDirPath);
 

+ 42 - 1
mgr/src/main/java/cn/reghao/devops/mgr/app/service/impl/AppDeployServiceImpl.java

@@ -3,6 +3,7 @@ package cn.reghao.devops.mgr.app.service.impl;
 import cn.reghao.devops.common.docker.model.Config;
 import cn.reghao.devops.common.msg.constant.PackType;
 import cn.reghao.devops.mgr.admin.service.SysMessageService;
+import cn.reghao.devops.mgr.aliyun.service.AliyunCdn;
 import cn.reghao.devops.mgr.app.db.query.AppBuildQuery;
 import cn.reghao.devops.mgr.app.db.query.AppDeployQuery;
 import cn.reghao.devops.mgr.app.db.repository.AppDeployingRepository;
@@ -19,6 +20,7 @@ import cn.reghao.devops.mgr.app.model.po.log.BuildLog;
 import cn.reghao.devops.mgr.app.model.po.log.DeployLog;
 import cn.reghao.devops.mgr.app.service.bd.BuildDeployNotify;
 import cn.reghao.devops.mgr.app.service.AppDeployService;
+import cn.reghao.devops.mgr.app.task.RereshCheckTask;
 import cn.reghao.devops.mgr.machine.db.query.MachineQuery;
 import cn.reghao.devops.mgr.machine.model.po.MachineHost;
 import cn.reghao.devops.common.msg.event.EvtAppStatResult;
@@ -26,11 +28,15 @@ import cn.reghao.jutil.jdk.converter.DateTimeConverter;
 import cn.reghao.jutil.jdk.result.Result;
 import cn.reghao.jutil.jdk.result.ResultStatus;
 import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.jutil.jdk.thread.ThreadPoolWrapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
 
 import java.util.*;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -48,11 +54,13 @@ public class AppDeployServiceImpl implements AppDeployService {
     private final AppBuildQuery appBuildQuery;
     private final AppDeployQuery appDeployQuery;
     private final SysMessageService sysMessageService;
+    private final AliyunCdn aliyunCdn;
+    private final ScheduledExecutorService scheduler;
 
     public AppDeployServiceImpl(AppDeployConfigRepository deployConfigRepository, AppDeployingRepository deployingRepository,
                                 DeployLogRepository deployLogRepository, BuildDeployNotify buildDeployNotify,
                                 MachineQuery machineQuery, AppBuildQuery appBuildQuery,
-                                AppDeployQuery appDeployQuery, SysMessageService sysMessageService) {
+                                AppDeployQuery appDeployQuery, SysMessageService sysMessageService, AliyunCdn aliyunCdn) {
         this.deployConfigRepository = deployConfigRepository;
         this.deployingRepository = deployingRepository;
         this.deployLogRepository = deployLogRepository;
@@ -61,6 +69,8 @@ public class AppDeployServiceImpl implements AppDeployService {
         this.appBuildQuery = appBuildQuery;
         this.appDeployQuery = appDeployQuery;
         this.sysMessageService = sysMessageService;
+        this.aliyunCdn = aliyunCdn;
+        this.scheduler = ThreadPoolWrapper.scheduledThreadPool("cdn-checker", 4);
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -318,6 +328,37 @@ public class AppDeployServiceImpl implements AppDeployService {
         });
 
         deployingRepository.save(appDeploying);
+
+        BuildLog buildLog = appDeploying.getBuildLog();
+        AppConfig appConfig = appDeploying.getAppConfig();
+        refreshCdn(appConfig, buildLog);
+    }
+
+    private void refreshCdn(AppConfig appConfig, BuildLog buildLog) {
+        String buildId = buildLog.getBuildLogId();
+        String appId = appConfig.getAppId();
+        String packType = appConfig.getPackerConfig().getType();
+        if (packType.equals(PackType.staticFiles.getName())) {
+            String domains = appConfig.getDomains();
+            if (domains == null) {
+                return;
+            }
+
+            String[] domainArr = StringUtils.trimAllWhitespace(domains).split(",");
+            for (String domain : domainArr) {
+                try {
+                    String taskId = aliyunCdn.refreshCdn(domain);
+                    if (taskId != null) {
+                        log.info("taskId -> {}", taskId);
+                        RereshCheckTask rereshCheckTask =
+                                new RereshCheckTask(aliyunCdn, buildDeployNotify, taskId, buildId, appId, domain);
+                        scheduler.schedule(rereshCheckTask, 20, TimeUnit.SECONDS);
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
     }
 
     @Override

+ 68 - 0
mgr/src/main/java/cn/reghao/devops/mgr/app/task/RereshCheckTask.java

@@ -0,0 +1,68 @@
+package cn.reghao.devops.mgr.app.task;
+
+import cn.reghao.devops.mgr.aliyun.model.vo.RefreshStatus;
+import cn.reghao.devops.mgr.aliyun.service.AliyunCdn;
+import cn.reghao.devops.mgr.app.model.vo.msg.CdnRereshNotifyMsg;
+import cn.reghao.devops.mgr.app.service.bd.BuildDeployNotify;
+import lombok.extern.slf4j.Slf4j;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author reghao
+ * @date 2025-07-31 16:58:28
+ */
+@Slf4j
+public class RereshCheckTask implements Runnable {
+    private final AliyunCdn aliyunCdn;
+    private final BuildDeployNotify buildDeployNotify;
+    private final String taskId;
+    private final String buildId;
+    private final String appId;
+    private final String domain;
+
+    public RereshCheckTask(AliyunCdn aliyunCdn, BuildDeployNotify buildDeployNotify, String taskId,
+                           String buildId, String appId, String domain) {
+        this.aliyunCdn = aliyunCdn;
+        this.buildDeployNotify = buildDeployNotify;
+        this.taskId = taskId;
+        this.buildId = buildId;
+        this.appId = appId;
+        this.domain = domain;
+    }
+
+    @Override
+    public void run() {
+        try {
+            RefreshStatus refreshStatus = aliyunCdn.getRefreshStatus(taskId);
+            if (refreshStatus == null) {
+                Thread.sleep(10_000);
+                refreshStatus = aliyunCdn.getRefreshStatus(taskId);
+                if (refreshStatus == null) {
+                    log.error("can't get RefreshStatus with task id -> {}", taskId);
+                    return;
+                }
+            }
+
+            int count = 1;
+            while (!refreshStatus.getStatus().equals("Complete") && count < 10) {
+                Thread.sleep(6_000);
+                refreshStatus = aliyunCdn.getRefreshStatus(taskId);
+                count++;
+            }
+
+            LocalDateTime refreshTime = refreshStatus.getStartAt();
+            String result = "";
+            if (refreshStatus.getStatus().equals("Complete")) {
+                result = "CDN 刷新完成";
+            } else {
+                result = String.format("等待 60s 后 CDN 尚未刷新完成, 当前进度 %s", refreshStatus.getProcess());
+            }
+
+            CdnRereshNotifyMsg cdnRereshNotifyMsg = new CdnRereshNotifyMsg(buildId, appId, domain, refreshTime, result);
+            buildDeployNotify.refreshNotify(cdnRereshNotifyMsg);
+        } catch (Exception e) {
+            log.error("{}", e.getMessage());
+        }
+    }
+}