pudongliang 4 месяцев назад
Родитель
Сommit
ee07b26587
35 измененных файлов с 547 добавлено и 16 удалено
  1. 1 1
      portal/dist/assets/accountGroup-Cz4YPxUV.js
  2. 1 1
      portal/dist/assets/adPlanConf-Sbxw-QBw.js
  3. 1 1
      portal/dist/assets/auth-Cf-AYAzf.js
  4. 1 1
      portal/dist/assets/conversion-DOWsVskV.js
  5. 0 0
      portal/dist/assets/detail-NhQkrzJ0.js
  6. 1 1
      portal/dist/assets/drama-CYRg_X4b.js
  7. 1 1
      portal/dist/assets/index-B1dsX4tL.js
  8. 0 0
      portal/dist/assets/index-BIdPv_mk.js
  9. 0 0
      portal/dist/assets/index-Bgj8dxGh.js
  10. 0 0
      portal/dist/assets/index-CAA8no_4.js
  11. 0 0
      portal/dist/assets/index-CFXN3tcM.js
  12. 0 0
      portal/dist/assets/index-Cd65RcBX.js
  13. 0 0
      portal/dist/assets/index-Cs5ap3KM.js
  14. 0 0
      portal/dist/assets/index-CurNI__P.js
  15. 0 0
      portal/dist/assets/index-T-BCdjHk.js
  16. 0 0
      portal/dist/assets/index-i4LOmCNJ.js
  17. 0 0
      portal/dist/assets/index-kFsd_NxD.js
  18. 0 0
      portal/dist/assets/index-tMGS7vsd.js
  19. 1 1
      portal/dist/assets/ossFile-B8qWQdRd.js
  20. 0 0
      portal/dist/assets/request-Cz8fs3pn.js
  21. 1 1
      portal/dist/assets/user-DB6iQKRs.js
  22. 1 1
      portal/dist/index.html
  23. 10 4
      portal/src/views/ad-plan-conf/detail.vue
  24. 151 0
      src/main/java/com/moka/gdtauto/common/Constant.java
  25. 4 0
      src/main/java/com/moka/gdtauto/controller/AdPlanController.java
  26. 3 0
      src/main/java/com/moka/gdtauto/entity/AdPlan.java
  27. 8 0
      src/main/java/com/moka/gdtauto/entity/AdPlanCreative.java
  28. 10 0
      src/main/java/com/moka/gdtauto/mapper/AdPlanMapper.java
  29. 2 0
      src/main/java/com/moka/gdtauto/service/AdPlanService.java
  30. 137 1
      src/main/java/com/moka/gdtauto/service/impl/AdPlanServiceImpl.java
  31. 53 0
      src/main/java/com/moka/gdtauto/task/SyncAdgroupSystemStatusTask.java
  32. 1 1
      src/main/resources/application-pro.yml
  33. 1 1
      src/main/resources/application-test.yml
  34. 16 0
      src/main/resources/mapper/AdPlanMapper.xml
  35. 142 0
      src/test/java/com/moka/gdtauto/service/GetAdGroupStatusTest.java

+ 1 - 1
portal/dist/assets/accountGroup-gxywsH4V.js → portal/dist/assets/accountGroup-Cz4YPxUV.js

@@ -1 +1 @@
-import{r as u}from"./request-Db_y4xFI.js";function c(t){return u({url:"/api/account-group/create",method:"post",data:t})}function e(t){return u({url:"/api/account-group/update",method:"post",data:t})}function n(t){return u({url:`/api/account-group/${t}`,method:"delete"})}function a(t=1,o=10){return u({url:"/api/account-group/list",method:"get",params:{pageNum:t,pageSize:o}})}function p(){return u({url:"/api/account-group/all",method:"get"})}function i(t){return u({url:`/api/account-group/${t}/accounts`,method:"get"})}export{i as a,p as b,c,n as d,a as g,e as u};
+import{r as u}from"./request-Cz8fs3pn.js";function c(t){return u({url:"/api/account-group/create",method:"post",data:t})}function e(t){return u({url:"/api/account-group/update",method:"post",data:t})}function n(t){return u({url:`/api/account-group/${t}`,method:"delete"})}function a(t=1,o=10){return u({url:"/api/account-group/list",method:"get",params:{pageNum:t,pageSize:o}})}function p(){return u({url:"/api/account-group/all",method:"get"})}function i(t){return u({url:`/api/account-group/${t}/accounts`,method:"get"})}export{i as a,p as b,c,n as d,a as g,e as u};

+ 1 - 1
portal/dist/assets/adPlanConf-BCIxiJ0v.js → portal/dist/assets/adPlanConf-Sbxw-QBw.js

@@ -1 +1 @@
-import{r as a}from"./request-Db_y4xFI.js";function r(n){return a({url:"/api/ad-plan-conf/reCreate",method:"post",data:n})}function o(n){return a({url:"/api/ad-plan-conf/generate-plan",method:"post",data:n})}function l(n){return a({url:`/api/ad-plan-conf/reGeneratePlan/${n}`,method:"get"})}function d(n){return a({url:"/api/ad-plan-conf/create",method:"post",data:n})}function u(n){return a({url:"/api/ad-plan-conf/update",method:"post",data:n})}function p(n){return a({url:`/api/ad-plan-conf/${n}`,method:"delete"})}function i(n=1,e=10){return a({url:"/api/ad-plan-conf/list",method:"get",params:{pageNum:n,pageSize:e}})}function f(n){return a({url:`/api/ad-plan-conf/${n}`,method:"get"})}function c(n){return a({url:"/api/ad-plan/list",method:"get",params:n})}function s(n){return a({url:`/api/ad-plan-creative/plan/${n}`,method:"get"})}export{o as a,f as b,d as c,p as d,c as e,l as f,i as g,s as h,r,u};
+import{r as a}from"./request-Cz8fs3pn.js";function r(n){return a({url:"/api/ad-plan-conf/reCreate",method:"post",data:n})}function o(n){return a({url:"/api/ad-plan-conf/generate-plan",method:"post",data:n})}function l(n){return a({url:`/api/ad-plan-conf/reGeneratePlan/${n}`,method:"get"})}function d(n){return a({url:"/api/ad-plan-conf/create",method:"post",data:n})}function u(n){return a({url:"/api/ad-plan-conf/update",method:"post",data:n})}function p(n){return a({url:`/api/ad-plan-conf/${n}`,method:"delete"})}function i(n=1,e=10){return a({url:"/api/ad-plan-conf/list",method:"get",params:{pageNum:n,pageSize:e}})}function f(n){return a({url:`/api/ad-plan-conf/${n}`,method:"get"})}function c(n){return a({url:"/api/ad-plan/list",method:"get",params:n})}function s(n){return a({url:`/api/ad-plan-creative/plan/${n}`,method:"get"})}export{o as a,f as b,d as c,p as d,c as e,l as f,i as g,s as h,r,u};

+ 1 - 1
portal/dist/assets/auth-D2VlC4E-.js → portal/dist/assets/auth-Cf-AYAzf.js

@@ -1 +1 @@
-import{r as t}from"./request-Db_y4xFI.js";function i(){return t({url:"/api/gdt/auth/authorize-url",method:"get"})}function n(r){return t({url:"/api/gdt/auth/refresh",method:"post",params:r})}function u(){return t({url:"/api/gdt/auth/list",method:"get"})}function a(){return u()}function o(){return t({url:"/api/gdt/auth/expiring",method:"get"})}export{o as a,a as b,i as g,u as l,n as r};
+import{r as t}from"./request-Cz8fs3pn.js";function i(){return t({url:"/api/gdt/auth/authorize-url",method:"get"})}function n(r){return t({url:"/api/gdt/auth/refresh",method:"post",params:r})}function u(){return t({url:"/api/gdt/auth/list",method:"get"})}function a(){return u()}function o(){return t({url:"/api/gdt/auth/expiring",method:"get"})}export{o as a,a as b,i as g,u as l,n as r};

+ 1 - 1
portal/dist/assets/conversion-DckJKH-q.js → portal/dist/assets/conversion-DOWsVskV.js

@@ -1 +1 @@
-import{r as t}from"./request-Db_y4xFI.js";function e(n,o){return t({url:"/api/gdt/conversion/sync",method:"post",params:{orgAccountId:n,accountId:o}})}function s(n=1,o=20){return t({url:"/api/gdt/conversion/list",method:"get",params:{page:n,pageSize:o}})}function i(n){return t({url:"/api/gdt/conversion/detail",method:"get",params:{conversionId:n}})}function a(){return t({url:"/api/gdt/conversion/all",method:"get"})}export{s as a,i as b,a as g,e as s};
+import{r as t}from"./request-Cz8fs3pn.js";function e(n,o){return t({url:"/api/gdt/conversion/sync",method:"post",params:{orgAccountId:n,accountId:o}})}function s(n=1,o=20){return t({url:"/api/gdt/conversion/list",method:"get",params:{page:n,pageSize:o}})}function i(n){return t({url:"/api/gdt/conversion/detail",method:"get",params:{conversionId:n}})}function a(){return t({url:"/api/gdt/conversion/all",method:"get"})}export{s as a,i as b,a as g,e as s};

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/detail-NhQkrzJ0.js


+ 1 - 1
portal/dist/assets/drama-DraB1Q15.js → portal/dist/assets/drama-CYRg_X4b.js

@@ -1 +1 @@
-import{r as t}from"./request-Db_y4xFI.js";function n(a){return t({url:"/api/gdt/asset/drama/sync",method:"post",params:{accountId:a}})}function m(a=1,s=20,r=""){return t({url:"/api/gdt/asset/drama/list",method:"get",params:{dramaName:r,page:a,pageSize:s}})}function d(a){return t({url:"/api/gdt/asset/drama/myDramaAssets",method:"get",params:{dramaName:a}})}function o(){return t({url:"/api/gdt/asset/drama/sync/conf/myConf",method:"get"})}function u(a){return t({url:"/api/gdt/asset/drama/sync/conf/add",method:"post",data:a})}function i(a){return t({url:"/api/gdt/asset/drama/sync/conf/delete",method:"post",params:{id:a}})}export{o as a,u as b,i as d,m as g,d as m,n as s};
+import{r as t}from"./request-Cz8fs3pn.js";function n(a){return t({url:"/api/gdt/asset/drama/sync",method:"post",params:{accountId:a}})}function m(a=1,s=20,r=""){return t({url:"/api/gdt/asset/drama/list",method:"get",params:{dramaName:r,page:a,pageSize:s}})}function d(a){return t({url:"/api/gdt/asset/drama/myDramaAssets",method:"get",params:{dramaName:a}})}function o(){return t({url:"/api/gdt/asset/drama/sync/conf/myConf",method:"get"})}function u(a){return t({url:"/api/gdt/asset/drama/sync/conf/add",method:"post",data:a})}function i(a){return t({url:"/api/gdt/asset/drama/sync/conf/delete",method:"post",params:{id:a}})}export{o as a,u as b,i as d,m as g,d as m,n as s};

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
portal/dist/assets/index-B1dsX4tL.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-BIdPv_mk.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-Bgj8dxGh.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-CAA8no_4.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-CFXN3tcM.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-Cd65RcBX.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-Cs5ap3KM.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-CurNI__P.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-T-BCdjHk.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-i4LOmCNJ.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-kFsd_NxD.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/index-tMGS7vsd.js


+ 1 - 1
portal/dist/assets/ossFile-BJHL-jrG.js → portal/dist/assets/ossFile-B8qWQdRd.js

@@ -1 +1 @@
-import{r as t}from"./request-Db_y4xFI.js";function i(e){return t({timeout:3e5,url:"/api/oss-file/upload",method:"post",data:e,headers:{"Content-Type":"multipart/form-data"}})}function o(e){return t({url:"/api/oss-file/list",method:"get",params:e})}function r(e){return t({url:`/api/oss-file/${e}`,method:"delete"})}function l(e){return t({url:"/api/oss-file/batch",method:"delete",params:{fileIds:e}})}export{l as b,r as d,o as g,i as u};
+import{r as t}from"./request-Cz8fs3pn.js";function i(e){return t({timeout:3e5,url:"/api/oss-file/upload",method:"post",data:e,headers:{"Content-Type":"multipart/form-data"}})}function o(e){return t({url:"/api/oss-file/list",method:"get",params:e})}function r(e){return t({url:`/api/oss-file/${e}`,method:"delete"})}function l(e){return t({url:"/api/oss-file/batch",method:"delete",params:{fileIds:e}})}export{l as b,r as d,o as g,i as u};

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
portal/dist/assets/request-Cz8fs3pn.js


+ 1 - 1
portal/dist/assets/user-BhSiUb9o.js → portal/dist/assets/user-DB6iQKRs.js

@@ -1 +1 @@
-import{r as e}from"./request-Db_y4xFI.js";function s(){return e({url:"/api/user/list",method:"get"})}function u(r){return e({url:"/api/user/info",method:"get",params:r})}function n(r){return e({url:"/api/user/user-token-url",method:"get",params:r})}function o(r){return e({url:"/api/user/login",method:"post",params:r})}function a(r){return e({url:"/api/user/change-password",method:"post",params:r})}export{u as a,s as b,a as c,n as g,o as l};
+import{r as e}from"./request-Cz8fs3pn.js";function s(){return e({url:"/api/user/list",method:"get"})}function u(r){return e({url:"/api/user/info",method:"get",params:r})}function n(r){return e({url:"/api/user/user-token-url",method:"get",params:r})}function o(r){return e({url:"/api/user/login",method:"post",params:r})}function a(r){return e({url:"/api/user/change-password",method:"post",params:r})}export{u as a,s as b,a as c,n as g,o as l};

+ 1 - 1
portal/dist/index.html

@@ -5,7 +5,7 @@
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>腾讯广告自动化管理后台</title>
-    <script type="module" crossorigin src="/assets/index-ClkJ0dy1.js"></script>
+    <script type="module" crossorigin src="/assets/index-B1dsX4tL.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BeI5MTU7.css">
   </head>
   <body>

+ 10 - 4
portal/src/views/ad-plan-conf/detail.vue

@@ -196,7 +196,7 @@
           <el-option label="待执行" :value="1" />
           <el-option label="执行中" :value="2" />
           <el-option label="失败" :value="3" />
-          <el-option label="已结束" :value="4" />
+          <el-option label="已提交" :value="4" />
         </el-select>
       </div>
 
@@ -303,8 +303,8 @@
             ¥{{ (row.bidAmount / 100).toFixed(2) }}
           </template>
         </el-table-column>
-        <el-table-column prop="target" label="定向模版ID" width="100" show-overflow-tooltip />
-        <el-table-column prop="site" label="版位模版ID" width="100" show-overflow-tooltip />
+        <!-- <el-table-column prop="target" label="定向模版ID" width="100" show-overflow-tooltip /> -->
+        <!-- <el-table-column prop="site" label="版位模版ID" width="100" show-overflow-tooltip /> -->
         <el-table-column label="状态" width="100">
           <template #default="{ row }">
             <el-tag :type="getPlanStatusColor(row.status)" size="small">
@@ -317,6 +317,12 @@
             <span class="text-red-500">{{ row.failReason || '-' }}</span>
           </template>
         </el-table-column>
+        <el-table-column label="系统状态" prop="systemStatus" width="100" show-overflow-tooltip />
+        <el-table-column label="系统删除" prop="systemDelete" width="100" >
+          <template #default="{ row }">
+            {{ row.systemDelete == 'true' ? '是' : '否'}}
+          </template>
+          </el-table-column>
         <el-table-column label="创建时间" width="180">
           <template #default="{ row }">
             {{ formatDate(row.createTime) }}
@@ -506,7 +512,7 @@ const getPlanStatusText = (status) => {
     1: '待执行',
     2: '执行中',
     3: '失败',
-    4: '已结束'
+    4: '已提交'
   }
   return map[status] || '未知'
 }

+ 151 - 0
src/main/java/com/moka/gdtauto/common/Constant.java

@@ -1,6 +1,157 @@
 package com.moka.gdtauto.common;
 
+import java.util.HashMap;
+import java.util.Map;
 
 public class Constant {
     public static final Long MAIN_ORG_ACCOUNT_ID = 41644744L;
+
+    /**
+     * ADGROUP_STATUS_FROZEN, // 已冻结
+     * ADGROUP_STATUS_SUSPEND, // 暂停中
+     * ADGROUP_STATUS_NOT_IN_DELIVERY_TIME, // 广告未到投放时间
+     * ADGROUP_STATUS_ACTIVE, // 投放中
+     * ADGROUP_STATUS_DELETED, // 已删除
+     * ADGROUP_STATUS_ACCOUNT_BALANCE_NOT_ENOUGH, // 账户余额不足
+     * ADGROUP_STATUS_DAILY_BUDGET_REACHED, // 广告达到日预算上限
+     * ADGROUP_STATUS_PARTIAL_ACTIVE, // 部分投放中
+     * ADGROUP_STATUS_WECHAT_CHANNELS_STOP, // 投放结束(超过 14 天广告未上线)
+     * ADGROUP_STATUS_CREATIVE_STATUS_PENDING, // 创意未投放
+     * ADGROUP_STATUS_CREATIVE_EMPTY, // 创意准备中
+     * ADGROUP_STATUS_JOINT_BUDGET_REACHED, // 广告被暂停(联合预算达上限)
+     * ADGROUP_STATUS_TOTAL_BUDGET_REACHED, // 广告达到总预算上限
+     * ADGROUP_STATUS_PRE_LOCK, // 待锁定预算
+     * ADGROUP_STATUS_UNLOCKING, // 解锁预算中
+     * ADGROUP_STATUS_STOP, // 投放结束
+     * ADGROUP_STATUS_LIVE_NOT_ACTIVE, // 已到达投放时间(直播未开启)
+     * ADGROUP_STATUS_NOT_ACTIVE_PRODUCT_AUDIT_FAIL, // 广告未投放(推广产品审核不通过)
+     * ADGROUP_STATUS_LIVE_VIOLATION, // 广告未投放(直播间违规)
+     * ADGROUP_STATUS_WECHAT_STORE_PENDING, // 广告未投放(店铺审核中)
+     * ADGROUP_STATUS_WECHAT_STORE_CLOSING, // 广告未投放(店铺关闭中)
+     * ADGROUP_STATUS_WECHAT_STORE_CLOSED, // 广告未投放(店铺已关闭)
+     * ADGROUP_STATUS_NOT_ACTIVE_PRODUCT_REMOVED, // 广告未投放(商品已下架)
+     * ADGROUP_STATUS_WECHAT_STORE_NOT_AVAILABLE, // 广告未投放(店铺不可投)
+     * SMART_ADGROUP_STATUS_DELETED, // 已删除
+     * SMART_ADGROUP_STATUS_SUSPEND, // 已暂停
+     * SMART_ADGROUP_STATUS_JOINT_BUDGET_REACHED, // 项目被暂停(联合预算达上限)
+     * SMART_ADGROUP_STATUS_ACCOUNT_BALANCE_NOT_ENOUGH, // 账户余额不足
+     * SMART_ADGROUP_STATUS_DAILY_BUDGET_REACHED, // 项目日预算到达上限
+     * SMART_ADGROUP_STATUS_ADGROUP_STATUS_STOP, // 投放结束
+     * SMART_ADGROUP_STATUS_NOT_IN_DELIVERY_TIME, // 未到投放时间
+     * SMART_ADGROUP_STATUS_ACTIVE, // 投放中
+     * SMART_ADGROUP_STATUS_USING, // 项目启用中
+     * SMART_ADGROUP_STATUS_INVALID_LIVE_NOT_ACTIVE, // 项目异常(直播未开启)
+     * SMART_ADGROUP_STATUS_INVALID_LIVE_VIOLATION, // 项目异常(直播间违规)
+     * SMART_ADGROUP_STATUS_INVALID_PRODUCT_AUDIT_FAIL, // 项目异常(推广产品审核不通过)
+     * SMART_ADGROUP_STATUS_INVALID_CHANNELS_STOP, // 项目异常(直播已结束)
+     * SMART_ADGROUP_STATUS_INVALID_LIVE_NOT_ACTIVE_PRODUCT_AUDIT_FAIL, //
+     * 项目异常(直播未开启 + 推广产品审核不通过)
+     * SMART_ADGROUP_STATUS_INVALID_LIVE_VIOLATION_PRODUCT_AUDIT_FAIL, // 项目异常(直播间违规
+     * + 推广产品审核不通过)
+     * SMART_ADGROUP_STATUS_INVALID_CHANNELS_STOP_PRODUCT_AUDIT_FAIL, // 项目异常(直播已结束
+     * + 推广产品审核不通过)
+     * SMART_ADGROUP_STATUS_USING_LIVE_NOT_ACTIVE, // 项目启用中(直播未开启)
+     * SMART_ADGROUP_STATUS_USING_LIVE_VIOLATION, // 项目启用中(直播间违规)
+     * SMART_ADGROUP_STATUS_USING_PRODUCT_AUDIT_FAIL, // 项目启用中(推广产品审核不通过)
+     * SMART_ADGROUP_STATUS_USING_CHANNELS_STOP, // 项目启用中(直播已结束)
+     * SMART_ADGROUP_STATUS_USING_PRODUCT_REMOVED, // 项目启用中(商品已下架)
+     * SMART_ADGROUP_STATUS_INVALID_LIVE_NOT_ACTIVE_PRODUCT_REMOVED, // 项目异常(直播未开启 +
+     * 商品已下架)
+     * SMART_ADGROUP_STATUS_INVALID_LIVE_VIOLATION_PRODUCT_REMOVED, // 项目异常(直播间违规 +
+     * 商品已下架)
+     * SMART_ADGROUP_STATUS_INVALID_CHANNELS_STOP_PRODUCT_REMOVED, // 项目异常(直播已结束 +
+     * 商品已下架)
+     * SMART_ADGROUP_STATUS_INVALID_PRODUCT_REMOVED, // 项目异常(商品已下架)
+     * SMART_ADGROUP_STATUS_USING_PART_PRODUCT_INVALID, // 项目启用中(部分商品异常)
+     * SMART_ADGROUP_STATUS_INVALID_PRODUCT_REMOVED_PRODUCT_AUDIT_FAIL, //
+     * 项目异常(商品已下架 + 推广产品审核不通过)
+     * SMART_ADGROUP_STATUS_BOOST_PROJECT_SYNC, // 助推项目异常
+     */
+
+    // 广告状态
+    private static final Map<String, String> adGroupStatusMap = new HashMap<>();
+
+    static {
+        adGroupStatusMap.put("ADGROUP_STATUS_FROZEN", "已冻结");
+        adGroupStatusMap.put("ADGROUP_STATUS_SUSPEND", "暂停中");
+        adGroupStatusMap.put("ADGROUP_STATUS_NOT_IN_DELIVERY_TIME", "广告未到投放时间");
+        adGroupStatusMap.put("ADGROUP_STATUS_ACTIVE", "投放中");
+        adGroupStatusMap.put("ADGROUP_STATUS_DELETED", "已删除");
+        adGroupStatusMap.put("ADGROUP_STATUS_ACCOUNT_BALANCE_NOT_ENOUGH", "账户余额不足");
+        adGroupStatusMap.put("ADGROUP_STATUS_DAILY_BUDGET_REACHED", "广告达到日预算上限");
+        adGroupStatusMap.put("ADGROUP_STATUS_PARTIAL_ACTIVE", "部分投放中");
+        adGroupStatusMap.put("ADGROUP_STATUS_WECHAT_CHANNELS_STOP", "投放结束(超过 14 天广告未上线)");
+        adGroupStatusMap.put("ADGROUP_STATUS_CREATIVE_STATUS_PENDING", "创意未投放");
+        adGroupStatusMap.put("ADGROUP_STATUS_CREATIVE_EMPTY", "创意准备中");
+        adGroupStatusMap.put("ADGROUP_STATUS_JOINT_BUDGET_REACHED", "广告被暂停(联合预算达上限)");
+        adGroupStatusMap.put("ADGROUP_STATUS_TOTAL_BUDGET_REACHED", "广告达到总预算上限");
+        adGroupStatusMap.put("ADGROUP_STATUS_PRE_LOCK", "待锁定预算");
+        adGroupStatusMap.put("ADGROUP_STATUS_UNLOCKING", "解锁预算中");
+        adGroupStatusMap.put("ADGROUP_STATUS_STOP", "投放结束");
+        adGroupStatusMap.put("ADGROUP_STATUS_LIVE_NOT_ACTIVE", "已到达投放时间(直播未开启)");
+        adGroupStatusMap.put("ADGROUP_STATUS_NOT_ACTIVE_PRODUCT_AUDIT_FAIL", "广告未投放(推广产品审核不通过)");
+        adGroupStatusMap.put("ADGROUP_STATUS_LIVE_VIOLATION", "广告未投放(直播间违规)");
+        adGroupStatusMap.put("ADGROUP_STATUS_WECHAT_STORE_PENDING", "广告未投放(店铺审核中)");
+        adGroupStatusMap.put("ADGROUP_STATUS_WECHAT_STORE_CLOSING", "广告未投放(店铺关闭中)");
+        adGroupStatusMap.put("ADGROUP_STATUS_WECHAT_STORE_CLOSED", "广告未投放(店铺已关闭)");
+        adGroupStatusMap.put("ADGROUP_STATUS_NOT_ACTIVE_PRODUCT_REMOVED", "广告未投放(商品已下架)");
+        adGroupStatusMap.put("ADGROUP_STATUS_WECHAT_STORE_NOT_AVAILABLE", "广告未投放(店铺不可投)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_DELETED", "已删除");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_SUSPEND", "已暂停");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_JOINT_BUDGET_REACHED", "项目被暂停(联合预算达上限)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_ACCOUNT_BALANCE_NOT_ENOUGH", "账户余额不足");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_DAILY_BUDGET_REACHED", "项目日预算到达上限");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_ADGROUP_STATUS_STOP", "投放结束");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_NOT_IN_DELIVERY_TIME", "未到投放时间");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_ACTIVE", "投放中");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_USING", "项目启用中");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_LIVE_NOT_ACTIVE", "项目异常(直播未开启)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_LIVE_VIOLATION", "项目异常(直播间违规)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_PRODUCT_AUDIT_FAIL", "项目异常(推广产品审核不通过)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_CHANNELS_STOP", "项目异常(直播已结束)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_LIVE_NOT_ACTIVE_PRODUCT_AUDIT_FAIL",
+                "项目异常(直播未开启 + 推广产品审核不通过)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_LIVE_VIOLATION_PRODUCT_AUDIT_FAIL",
+                "项目异常(直播间违规 + 推广产品审核不通过)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_CHANNELS_STOP_PRODUCT_AUDIT_FAIL",
+                "项目异常(直播已结束 + 推广产品审核不通过)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_USING_LIVE_NOT_ACTIVE", "项目启用中(直播未开启)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_USING_LIVE_VIOLATION", "项目启用中(直播间违规)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_USING_PRODUCT_AUDIT_FAIL", "项目启用中(推广产品审核不通过)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_USING_CHANNELS_STOP", "项目启用中(直播已结束)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_USING_PRODUCT_REMOVED", "项目启用中(商品已下架)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_LIVE_NOT_ACTIVE_PRODUCT_REMOVED", "项目异常(直播未开启 + 商品已下架)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_LIVE_VIOLATION_PRODUCT_REMOVED", "项目异常(直播间违规 + 商品已下架)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_CHANNELS_STOP_PRODUCT_REMOVED", "项目异常(直播已结束 + 商品已下架)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_PRODUCT_REMOVED", "项目异常(商品已下架)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_USING_PART_PRODUCT_INVALID", "项目启用中(部分商品异常)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_INVALID_PRODUCT_REMOVED_PRODUCT_AUDIT_FAIL",
+                "项目异常(商品已下架 + 推广产品审核不通过)");
+        adGroupStatusMap.put("SMART_ADGROUP_STATUS_BOOST_PROJECT_SYNC", "助推项目异常");
+    }
+
+    public static String getAdGroupStatusValue(String status) {
+        return adGroupStatusMap.get(status);
+    }
+
+    // 创意审核状态
+    /**
+     * CREATIVE_SET_APPROVAL_STATUS_UNKNOWN, // 未知
+     * CREATIVE_SET_APPROVAL_STATUS_PENDING, // 审核中
+     * CREATIVE_SET_APPROVAL_STATUS_NORMAL, // 投放中
+     * CREATIVE_SET_APPROVAL_STATUS_DENIED, // 审核拒绝
+     * CREATIVE_SET_APPROVAL_STATUS_PARTIAL_NORMAL, // 部分投放中
+     */
+    private static final Map<String, String> creativeSetApprovalStatusMap = new HashMap<>();
+    static {
+        creativeSetApprovalStatusMap.put("CREATIVE_SET_APPROVAL_STATUS_UNKNOWN", "未知");
+        creativeSetApprovalStatusMap.put("CREATIVE_SET_APPROVAL_STATUS_PENDING", "审核中");
+        creativeSetApprovalStatusMap.put("CREATIVE_SET_APPROVAL_STATUS_NORMAL", "投放中");
+        creativeSetApprovalStatusMap.put("CREATIVE_SET_APPROVAL_STATUS_DENIED", "审核拒绝");
+        creativeSetApprovalStatusMap.put("CREATIVE_SET_APPROVAL_STATUS_PARTIAL_NORMAL", "部分投放中");
+    }
+
+    public static String getCreativeSetApprovalStatusValue(String status) {
+        return creativeSetApprovalStatusMap.get(status);
+    }
 }

+ 4 - 0
src/main/java/com/moka/gdtauto/controller/AdPlanController.java

@@ -1,6 +1,7 @@
 package com.moka.gdtauto.controller;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.moka.gdtauto.common.Constant;
 import com.moka.gdtauto.common.Result;
 import com.moka.gdtauto.entity.AdPlan;
 import com.moka.gdtauto.service.AdPlanService;
@@ -45,6 +46,9 @@ public class AdPlanController {
             @RequestParam(defaultValue = "10") Integer pageSize) {
         try {
             Page<AdPlan> page = adPlanService.getAdPlans(confId,accountId, status, pageNum, pageSize);
+            page.getRecords().forEach(r -> {
+                r.setSystemStatus(Constant.getAdGroupStatusValue(r.getSystemStatus()));
+            });
             return Result.success(page, "查询成功");
         } catch (Exception e) {
             log.error("查询广告计划列表失败", e);

+ 3 - 0
src/main/java/com/moka/gdtauto/entity/AdPlan.java

@@ -96,6 +96,9 @@ public class AdPlan {
      */
     private Integer status;
 
+    private String systemStatus;
+    private String systemDelete;
+
     /**
      * 失败原因
      */

+ 8 - 0
src/main/java/com/moka/gdtauto/entity/AdPlanCreative.java

@@ -70,6 +70,14 @@ public class AdPlanCreative {
      * 计划状态:1-待执行,2-执行中,3-失败,4-已结束
      */
     private Integer status;
+    /**
+     * 审批状态
+     */
+    private String approvalStatus;
+    /**
+     * 是否删除(广告侧)
+     */
+    private String systemDelete;
 
     /**
      * 失败原因

+ 10 - 0
src/main/java/com/moka/gdtauto/mapper/AdPlanMapper.java

@@ -3,6 +3,9 @@ package com.moka.gdtauto.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.moka.gdtauto.entity.AdPlan;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 /**
  * 广告计划Mapper接口
@@ -12,4 +15,11 @@ import org.apache.ibatis.annotations.Mapper;
  */
 @Mapper
 public interface AdPlanMapper extends BaseMapper<AdPlan> {
+
+    /**
+     * 批量更新广告计划的 system_status
+     *
+     * @param plans 包含 id 和 systemStatus 字段的广告计划列表
+     */
+    void batchUpdateSystemStatus(@Param("plans") List<AdPlan> plans);
 }

+ 2 - 0
src/main/java/com/moka/gdtauto/service/AdPlanService.java

@@ -58,4 +58,6 @@ public interface AdPlanService extends IService<AdPlan> {
     AdPlan getAdPlanById(Long planId);
 
     List<AdPlan> getPlansByConfId(Long confId);
+
+    void syncAdPlanStatus(Long confId);
 }

+ 137 - 1
src/main/java/com/moka/gdtauto/service/impl/AdPlanServiceImpl.java

@@ -3,15 +3,26 @@ package com.moka.gdtauto.service.impl;
 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 com.moka.gdtauto.client.TencentAdsApiClientFactory;
+import com.moka.gdtauto.common.Constant;
 import com.moka.gdtauto.entity.AdPlan;
 import com.moka.gdtauto.mapper.AdPlanMapper;
 import com.moka.gdtauto.service.AdPlanService;
+import com.tencent.ads.model.v3.AdgroupsGetResponseData;
+import com.tencent.ads.model.v3.FilterOperator;
+import com.tencent.ads.model.v3.FilteringStruct;
+import com.tencent.ads.v3.TencentAds;
+
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 广告计划服务实现类
@@ -21,7 +32,10 @@ import java.util.List;
  */
 @Slf4j
 @Service
+@RequiredArgsConstructor
 public class AdPlanServiceImpl extends ServiceImpl<AdPlanMapper, AdPlan> implements AdPlanService {
+    private final AdPlanMapper adPlanMapper;
+    private final TencentAdsApiClientFactory clientFactory;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -91,7 +105,7 @@ public class AdPlanServiceImpl extends ServiceImpl<AdPlanMapper, AdPlan> impleme
             queryWrapper.eq(AdPlan::getStatus, status);
         }
         
-        queryWrapper.orderByDesc(AdPlan::getCreateTime);
+        queryWrapper.orderByDesc(AdPlan::getId);
         
         return this.page(page, queryWrapper);
     }
@@ -109,4 +123,126 @@ public class AdPlanServiceImpl extends ServiceImpl<AdPlanMapper, AdPlan> impleme
                 .eq(AdPlan::getAdPlanConfId, confId)
                 .eq(AdPlan::getDeleted, 0).orderByAsc(AdPlan::getId));
     }
+
+
+
+    private static final int PAGE_SIZE = 500;
+    private static final int BATCH_LIMIT = 100; // 腾讯广告 adgroup_id IN 最大 100
+    private static final List<String> FIELDS = List.of("adgroup_id", "system_status", "is_deleted");
+
+    @Override
+    public void syncAdPlanStatus(Long confId) {
+        log.info("[定时任务] 开始同步广告计划 system_status");
+        int totalUpdated = 0;
+        try {
+            int pageNum = 1;
+            while (true) {
+                // 1. 分页查询 status=4 的广告计划,ID 降序
+                LambdaQueryWrapper<AdPlan> queryWrapper = new LambdaQueryWrapper<>();
+                queryWrapper.eq(confId != null, AdPlan::getAdPlanConfId, confId);
+                queryWrapper.eq(AdPlan::getDeleted, 0);
+                queryWrapper.eq(AdPlan::getStatus, 4);
+                queryWrapper.orderByDesc(AdPlan::getId);
+                Page<AdPlan> page = this.page(new Page<>(pageNum, PAGE_SIZE), queryWrapper);
+                List<AdPlan> records = page.getRecords();
+                if (records == null || records.isEmpty()) {
+                    break;
+                }
+
+                // 2. 按 accountId 分组
+                Map<Long, List<AdPlan>> groupByAccount = records.stream()
+                        .filter(p -> p.getAdgroupId() != null && p.getAccountId() != null)
+                        .collect(Collectors.groupingBy(AdPlan::getAccountId));
+
+                for (Map.Entry<Long, List<AdPlan>> entry : groupByAccount.entrySet()) {
+                    Long accountId = entry.getKey();
+                    List<AdPlan> plansOfAccount = entry.getValue();
+
+                    // 3. 每组按 100 条分批调用 API
+                    int batchCount = (int) Math.ceil((double) plansOfAccount.size() / BATCH_LIMIT);
+                    for (int i = 0; i < batchCount; i++) {
+                        int fromIdx = i * BATCH_LIMIT;
+                        int toIdx = Math.min(fromIdx + BATCH_LIMIT, plansOfAccount.size());
+                        List<AdPlan> batch = plansOfAccount.subList(fromIdx, toIdx);
+
+                        try {
+                            int updated = syncBatch(accountId, batch);
+                            totalUpdated += updated;
+                        } catch (Exception e) {
+                            log.error("[定时任务] 同步 accountId={} 的广告状态失败,batch index={}", accountId, i, e);
+                        }
+                    }
+                }
+
+                if (pageNum >= page.getPages()) {
+                    break;
+                }
+                pageNum++;
+            }
+
+        } catch (Exception e) {
+            log.error("[定时任务] 同步广告计划 system_status 发生异常", e);
+        }
+
+        log.info("[定时任务] 同步广告计划 system_status 完成,共更新 {} 条", totalUpdated);
+    }
+
+    /**
+     * 针对同一 accountId 下的一批广告计划,调用 API 获取 system_status 并批量更新
+     *
+     * @param accountId 广告账户 ID
+     * @param batch     该账户下的广告计划列表(不超过 100 条)
+     * @return 实际更新条数
+     */
+    private int syncBatch(Long accountId, List<AdPlan> batch) throws Exception {
+        // 构建 adgroup_id IN [...] 过滤条件
+        List<String> adgroupIdValues = batch.stream()
+                .map(p -> String.valueOf(p.getAdgroupId()))
+                .collect(Collectors.toList());
+
+        FilteringStruct filter = new FilteringStruct();
+        filter.setField("adgroup_id");
+        filter.setOperator(FilterOperator.IN);
+        filter.setValues(adgroupIdValues);
+
+        List<FilteringStruct> filtering = new ArrayList<>();
+        filtering.add(filter);
+
+        // 获取 TencentAds 实例(使用主 orgAccountId)
+        TencentAds tencentAds = clientFactory.getTencentAds(null, Constant.MAIN_ORG_ACCOUNT_ID);
+
+        AdgroupsGetResponseData response = tencentAds.adgroups().adgroupsGet(
+                accountId,
+                filtering,
+                1L,                        // page
+                (long) BATCH_LIMIT,
+                null,              // is_deleted
+                FIELDS,
+                "PAGINATION_MODE_NORMAL",
+                null                         // cursor(首次请求)
+        );
+
+        if (response == null || response.getList() == null || response.getList().isEmpty()) {
+            log.warn("[定时任务] accountId={} 未返回广告状态数据", accountId);
+            return 0;
+        }
+
+        // 构建待更新列表
+        List<AdPlan> toUpdate = new ArrayList<>();
+        response.getList().stream().forEach(item -> {
+            AdPlan update = new AdPlan();
+            update.setAdgroupId(item.getAdgroupId());
+            update.setSystemStatus(item.getSystemStatus().getValue());
+            update.setSystemDelete(item.isIsDeleted() != null ? String.valueOf(item.isIsDeleted()) : null);
+            toUpdate.add(update);
+        });
+
+        if (toUpdate.isEmpty()) {
+            return 0;
+        }
+
+        adPlanMapper.batchUpdateSystemStatus(toUpdate);
+        log.info("[定时任务] accountId={} 批量更新 {} 条 system_status", accountId, toUpdate.size());
+        return toUpdate.size();
+    }
 }

+ 53 - 0
src/main/java/com/moka/gdtauto/task/SyncAdgroupSystemStatusTask.java

@@ -0,0 +1,53 @@
+package com.moka.gdtauto.task;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.moka.gdtauto.common.Constant;
+import com.moka.gdtauto.client.TencentAdsApiClientFactory;
+import com.moka.gdtauto.entity.AdPlan;
+import com.moka.gdtauto.mapper.AdPlanMapper;
+import com.moka.gdtauto.service.AdPlanService;
+import com.tencent.ads.model.v3.AdgroupsGetListStruct;
+import com.tencent.ads.model.v3.AdgroupsGetResponseData;
+import com.tencent.ads.model.v3.FilterOperator;
+import com.tencent.ads.model.v3.FilteringStruct;
+import com.tencent.ads.v3.TencentAds;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 同步广告计划 system_status 定时任务
+ * <p>
+ * 流程:
+ * 1. 分页查询 ad_plan 表(status=2 执行中,ID 降序)
+ * 2. 按 accountId 分组,每组最多 100 条(腾讯广告 IN 过滤上限)
+ * 3. 调用 adgroups/get 接口批量获取 system_status
+ * 4. 批量更新回 ad_plan 表
+ * </p>
+ *
+ * @author moka
+ * @since 2026-03-05
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SyncAdgroupSystemStatusTask {
+
+    private final AdPlanService adPlanService;
+    /**
+     * 每小时同步一次广告状态
+     */
+    @Scheduled(cron = "0 0 * * * *")
+    @SchedulerLock(name = "syncAdgroupSystemStatus", lockAtMostFor = "9m", lockAtLeastFor = "1m")
+    public void syncAdgroupSystemStatus() {
+        adPlanService.syncAdPlanStatus(11L);
+    }
+
+}

+ 1 - 1
src/main/resources/application-pro.yml

@@ -15,7 +15,7 @@ spring:
       datasource:
         master:
           driver-class-name: com.p6spy.engine.spy.P6SpyDriver
-          url: jdbc:p6spy:mysql://rm-uf65d6xsxrp743w3a.mysql.rds.aliyuncs.com:3306/gdt_auto?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
+          url: jdbc:p6spy:mysql://rm-uf65d6xsxrp743w3a.mysql.rds.aliyuncs.com:3306/gdt_auto?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
           username: gdt_auto
           password: QA_GDT_AUTO%$PDL
   

+ 1 - 1
src/main/resources/application-test.yml

@@ -15,7 +15,7 @@ spring:
       datasource:
         master:
           driver-class-name: com.p6spy.engine.spy.P6SpyDriver
-          url: jdbc:p6spy:mysql://rm-uf65d6xsxrp743w3aio.mysql.rds.aliyuncs.com:3306/gdt_auto?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
+          url: jdbc:p6spy:mysql://rm-uf65d6xsxrp743w3aio.mysql.rds.aliyuncs.com:3306/gdt_auto?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
           username: gdt_auto
           password: QA_GDT_AUTO%$PDL
   

+ 16 - 0
src/main/resources/mapper/AdPlanMapper.xml

@@ -0,0 +1,16 @@
+<?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="com.moka.gdtauto.mapper.AdPlanMapper">
+
+    <!-- 批量更新广告计划 system_status,依赖 allowMultiQueries=true -->
+    <update id="batchUpdateSystemStatus" parameterType="java.util.List">
+        <foreach collection="plans" item="item" separator=";">
+            UPDATE ad_plan
+            SET system_status = #{item.systemStatus},
+                system_delete = #{item.systemDelete},
+                update_time   = NOW()
+            WHERE adgroup_id = #{item.adgroupId}
+        </foreach>
+    </update>
+
+</mapper>

+ 142 - 0
src/test/java/com/moka/gdtauto/service/GetAdGroupStatusTest.java

@@ -0,0 +1,142 @@
+package com.moka.gdtauto.service;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.moka.gdtauto.GdtAutoApplication;
+import com.moka.gdtauto.client.TencentAdsApiClientFactory;
+import com.moka.gdtauto.common.Constant;
+import com.moka.gdtauto.task.SyncAdgroupSystemStatusTask;
+import com.tencent.ads.model.v3.AdgroupsGetResponseData;
+import com.tencent.ads.model.v3.FilteringStruct;
+import com.tencent.ads.model.v3.FilterOperator;
+import com.tencent.ads.v3.TencentAds;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 获取广告状态测试
+ * https://developers.e.qq.com/v3.0/docs/api/adgroups/get
+ *  mvn test -Dtest=GetAdGroupStatusTest#testGetAdGroupStatusWithCursorPagination -Dspring.profiles.active=test
+ *  mvn test -Dtest=GetAdGroupStatusTest#testSyncAdgroupSystemStatus -Dspring.profiles.active=test
+ * @author moka
+ * @since 2026-03-05
+ */
+@SpringBootTest(classes = GdtAutoApplication.class)
+@ActiveProfiles("test")
+class GetAdGroupStatusTest {
+
+    @Autowired
+    private TencentAdsApiClientFactory clientFactory;
+    @Autowired
+    private SyncAdgroupSystemStatusTask syncAdgroupSystemStatusTask;
+
+
+    @Test
+    @DisplayName("同步广告状态")
+    void testSyncAdgroupSystemStatus() {
+        syncAdgroupSystemStatusTask.syncAdgroupSystemStatus();
+    }
+
+    private static final Long ORG_ACCOUNT_ID = 41644744L;
+    private static final Long TEST_ACCOUNT_ID = 75323409L;
+    private static final String TEST_JOB_NUMBER = "12458";
+
+    /**
+     * 100 adgroup IDs to query
+     */
+    private static final List<Long> ADGROUP_IDS = List.of(
+        86787637171L,86790720528L
+    );
+
+    @Test
+    @DisplayName("使用游标分页获取广告状态")
+    void testGetAdGroupStatusWithCursorPagination() {
+        System.out.println("\n========================================");
+        System.out.println("开始获取广告状态");
+        System.out.println("账户ID: " + TEST_ACCOUNT_ID);
+        System.out.println("广告ID数量: " + ADGROUP_IDS.size());
+        System.out.println("========================================\n");
+
+        try {
+            TencentAds tencentAds = clientFactory.getTencentAds(TEST_JOB_NUMBER, ORG_ACCOUNT_ID);
+            tencentAds.setDebug(true);
+
+            // Build filtering: adgroup_id IN [100 adgroup IDs]
+            List<FilteringStruct> filtering = new ArrayList<>();
+            FilteringStruct filter = new FilteringStruct();
+            filter.setField("adgroup_id");
+            filter.setOperator(FilterOperator.IN);
+            List<String> values = new ArrayList<>();
+            for (Long id : ADGROUP_IDS) {
+                values.add(String.valueOf(id));
+            }
+            filter.setValues(values);
+            filtering.add(filter);
+
+            // Fields to retrieve
+            List<String> fields = List.of(
+                "adgroup_id",
+                "system_status"
+            );
+
+            // Cursor pagination: query all 100 IDs in a single request (API max is 100 per IN filter)
+            String paginationMode = "PAGINATION_MODE_CURSOR";
+            Long pageSize = 100L;
+
+            Gson gson = new GsonBuilder().setPrettyPrinting().create();
+
+            System.out.println("========== 请求参数(cursor模式,首次请求无cursor)==========");
+
+            AdgroupsGetResponseData response = tencentAds.adgroups()
+                .adgroupsGet(
+                    TEST_ACCOUNT_ID,
+                    filtering,
+                    null,       // page (not used in cursor mode)
+                    pageSize,
+                    false,      // is_deleted
+                    fields,
+                    paginationMode,
+                    null        // cursor: null for first request
+                );
+
+            System.out.println("========== 响应结果 ==========");
+            System.out.println(gson.toJson(response));
+            System.out.println("========================================\n");
+
+            if (response == null || response.getList() == null || response.getList().isEmpty()) {
+                System.out.println("⚠️  返回结果为空");
+            } else {
+                int totalFetched = response.getList().size();
+                System.out.println("本次返回: " + totalFetched + " 条");
+
+                // Print adgroup_id + configured_status summary
+                response.getList().forEach(item ->
+                    System.out.println("  广告ID: " + item.getAdgroupId()
+                        + "  名称: " + item.getAdgroupName()
+                        + "  状态: " + item.getSystemStatus()
+                        + "  状态: " + Constant.getAdGroupStatusValue(item.getSystemStatus().getValue()))
+                );
+
+                if (response.getPageInfo() != null) {
+                    System.out.println("📊 总数: " + response.getPageInfo().getTotalNumber());
+                    System.out.println("📊 总页数: " + response.getPageInfo().getTotalPage());
+                }
+            }
+
+            System.out.println("\n✅ 查询完成");
+
+        } catch (Exception e) {
+            System.err.println("\n❌ API调用失败");
+            System.err.println("错误信息: " + e.getMessage());
+            e.printStackTrace();
+            throw new RuntimeException("测试失败", e);
+        }
+    }
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов