Browse Source

Merge branch 'master' into cpq-dev

chenlei1231 5 months ago
parent
commit
2d98da8c21
15 changed files with 610 additions and 150 deletions
  1. 5 1
      .gitignore
  2. 90 66
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/common/util/AdwebRedisUtil.java
  3. 34 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/common/util/CommonUtil.java
  4. 43 15
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/common/util/DateUtil.java
  5. 1 1
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/SeoKeywordsSerp.java
  6. 2 2
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/SeoKeywordsMapper.java
  7. 5 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/SeoKeywordsSerpMapper.java
  8. 8 10
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/xml/SeoKeywordsMapper.xml
  9. 12 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/xml/SeoKeywordsSerpMapper.xml
  10. 2 1
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISeoKeywordsSerpService.java
  11. 260 44
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/dataforseo/DataForSEOService.java
  12. 66 6
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SeoKeywordsSerpServiceImpl.java
  13. 65 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/xxl/DataForSEOJob.java
  14. 4 1
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/xxl/GAReportJob.java
  15. 13 3
      jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/adweb/seo/service/DataForSEOTest.java

+ 5 - 1
.gitignore

@@ -13,4 +13,8 @@ os_del.cmd
 os_del_doc.cmd
 .svn
 derby.log
-*.log
+*.log
+
+## DS_Store
+.DS_Store
+**/.DS_Store

+ 90 - 66
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/common/util/AdwebRedisUtil.java

@@ -3,7 +3,9 @@ package org.jeecg.modules.adweb.common.util;
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
+
 import lombok.extern.slf4j.Slf4j;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisCallback;
 import org.springframework.data.redis.core.RedisTemplate;
@@ -19,14 +21,13 @@ import java.util.concurrent.TimeUnit;
 /**
  * redis 工具类
  *
- * @Author Scott
+ * @author Scott
  */
 @Component
 @Slf4j
 public class AdwebRedisUtil {
 
-    @Autowired
-    private RedisTemplate<String, Object> redisTemplate;
+    @Autowired private RedisTemplate<String, Object> redisTemplate;
 
     @Autowired(required = false)
     public void setRedisTemplate(RedisTemplate redisTemplate) {
@@ -41,7 +42,8 @@ public class AdwebRedisUtil {
 
     private Jackson2JsonRedisSerializer jacksonSerializer() {
         ObjectMapper objectMapper = new ObjectMapper();
-        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(objectMapper, Object.class);
+        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
+                new Jackson2JsonRedisSerializer(objectMapper, Object.class);
         objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
 
@@ -51,7 +53,7 @@ public class AdwebRedisUtil {
     /**
      * 指定缓存失效时间
      *
-     * @param key  
+     * @param key 键
      * @param time 时间(秒)
      * @return
      */
@@ -108,6 +110,16 @@ public class AdwebRedisUtil {
         }
     }
 
+    /**
+     * 根据pattern查询所有keys
+     *
+     * @param pattern 如key_prefix.*
+     * @return
+     */
+    public Set<String> keys(String pattern) {
+        return redisTemplate.keys(pattern);
+    }
+
     // ============================String=============================
 
     /**
@@ -134,7 +146,7 @@ public class AdwebRedisUtil {
     /**
      * 普通缓存放入
      *
-     * @param key   
+     * @param key 键
      * @param value 值
      * @return true成功 false失败
      */
@@ -146,15 +158,14 @@ public class AdwebRedisUtil {
             e.printStackTrace();
             return false;
         }
-
     }
 
     /**
      * 普通缓存放入并设置时间
      *
-     * @param key   
+     * @param key 键
      * @param value 值
-     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
+     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
      * @return true成功 false 失败
      */
     public boolean set(String key, Object value, long time) {
@@ -175,7 +186,7 @@ public class AdwebRedisUtil {
      * 递增
      *
      * @param key 键
-     * @param by  要增加几(大于0)
+     * @param delta 要增加几(大于0)
      * @return
      */
     public long incr(String key, long delta) {
@@ -189,7 +200,7 @@ public class AdwebRedisUtil {
      * 递减
      *
      * @param key 键
-     * @param by  要减少几(小于0)
+     * @param delta 要减少几(小于0)
      * @return
      */
     public long decr(String key, long delta) {
@@ -204,7 +215,7 @@ public class AdwebRedisUtil {
     /**
      * HashGet
      *
-     * @param key  键 不能为null
+     * @param key 键 不能为null
      * @param item 项 不能为null
      * @return 值
      */
@@ -242,8 +253,8 @@ public class AdwebRedisUtil {
     /**
      * HashSet 并设置时间
      *
-     * @param key  
-     * @param map  对应多个键值
+     * @param key 键
+     * @param map 对应多个键值
      * @param time 时间(秒)
      * @return true成功 false失败
      */
@@ -263,8 +274,8 @@ public class AdwebRedisUtil {
     /**
      * 向一张hash表中放入数据,如果不存在将创建
      *
-     * @param key   
-     * @param item  
+     * @param key 键
+     * @param item 项
      * @param value 值
      * @return true 成功 false失败
      */
@@ -281,10 +292,10 @@ public class AdwebRedisUtil {
     /**
      * 向一张hash表中放入数据,如果不存在将创建
      *
-     * @param key   
-     * @param item  
+     * @param key 键
+     * @param item 项
      * @param value 值
-     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+     * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
      * @return true 成功 false失败
      */
     public boolean hset(String key, String item, Object value, long time) {
@@ -303,7 +314,7 @@ public class AdwebRedisUtil {
     /**
      * 删除hash表中的值
      *
-     * @param key  键 不能为null
+     * @param key 键 不能为null
      * @param item 项 可以使多个 不能为null
      */
     public void hdel(String key, Object... item) {
@@ -313,7 +324,7 @@ public class AdwebRedisUtil {
     /**
      * 判断hash表中是否有该项的值
      *
-     * @param key  键 不能为null
+     * @param key 键 不能为null
      * @param item 项 不能为null
      * @return true 存在 false不存在
      */
@@ -324,9 +335,9 @@ public class AdwebRedisUtil {
     /**
      * hash递增 如果不存在,就会创建一个 并把新增后的值返回
      *
-     * @param key  
+     * @param key 键
      * @param item 项
-     * @param by   要增加几(大于0)
+     * @param by 要增加几(大于0)
      * @return
      */
     public double hincr(String key, String item, double by) {
@@ -336,9 +347,9 @@ public class AdwebRedisUtil {
     /**
      * hash递减
      *
-     * @param key  
+     * @param key 键
      * @param item 项
-     * @param by   要减少记(小于0)
+     * @param by 要减少记(小于0)
      * @return
      */
     public double hdecr(String key, String item, double by) {
@@ -365,7 +376,7 @@ public class AdwebRedisUtil {
     /**
      * 根据value从一个set中查询,是否存在
      *
-     * @param key   
+     * @param key 键
      * @param value 值
      * @return true 存在 false不存在
      */
@@ -381,7 +392,7 @@ public class AdwebRedisUtil {
     /**
      * 将数据放入set缓存
      *
-     * @param key    
+     * @param key 键
      * @param values 值 可以是多个
      * @return 成功个数
      */
@@ -397,8 +408,8 @@ public class AdwebRedisUtil {
     /**
      * 将set数据放入缓存
      *
-     * @param key    
-     * @param time   时间(秒)
+     * @param key 键
+     * @param time 时间(秒)
      * @param values 值 可以是多个
      * @return 成功个数
      */
@@ -433,7 +444,7 @@ public class AdwebRedisUtil {
     /**
      * 移除值为value的
      *
-     * @param key    
+     * @param key 键
      * @param values 值 可以是多个
      * @return 移除的个数
      */
@@ -446,14 +457,15 @@ public class AdwebRedisUtil {
             return 0;
         }
     }
+
     // ===============================list=================================
 
     /**
      * 获取list缓存的内容
      *
-     * @param key   
+     * @param key 键
      * @param start 开始
-     * @param end   结束 0 到 -1代表所有值
+     * @param end 结束 0 到 -1代表所有值
      * @return
      */
     public List<Object> lGet(String key, long start, long end) {
@@ -483,7 +495,7 @@ public class AdwebRedisUtil {
     /**
      * 通过索引 获取list中的值
      *
-     * @param key   
+     * @param key 键
      * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
      * @return
      */
@@ -499,9 +511,8 @@ public class AdwebRedisUtil {
     /**
      * 将list放入缓存
      *
-     * @param key   
+     * @param key 键
      * @param value 值
-     * @param time  时间(秒)
      * @return
      */
     public boolean lSet(String key, Object value) {
@@ -517,9 +528,9 @@ public class AdwebRedisUtil {
     /**
      * 将list放入缓存
      *
-     * @param key   
+     * @param key 键
      * @param value 值
-     * @param time  时间(秒)
+     * @param time 时间(秒)
      * @return
      */
     public boolean lSet(String key, Object value, long time) {
@@ -538,9 +549,8 @@ public class AdwebRedisUtil {
     /**
      * 将list放入缓存
      *
-     * @param key   
+     * @param key 键
      * @param value 值
-     * @param time  时间(秒)
      * @return
      */
     public boolean lSet(String key, List<Object> value) {
@@ -556,9 +566,9 @@ public class AdwebRedisUtil {
     /**
      * 将list放入缓存
      *
-     * @param key   
+     * @param key 键
      * @param value 值
-     * @param time  时间(秒)
+     * @param time 时间(秒)
      * @return
      */
     public boolean lSet(String key, List<Object> value, long time) {
@@ -577,7 +587,7 @@ public class AdwebRedisUtil {
     /**
      * 根据索引修改list中的某条数据
      *
-     * @param key   
+     * @param key 键
      * @param index 索引
      * @param value 值
      * @return
@@ -595,7 +605,7 @@ public class AdwebRedisUtil {
     /**
      * 移除N个值为value
      *
-     * @param key   
+     * @param key 键
      * @param count 移除多少个
      * @param value 值
      * @return 移除的个数
@@ -610,34 +620,50 @@ public class AdwebRedisUtil {
         }
     }
 
-
     /**
      * 获取锁
      *
-     * @param lockKey        锁的键
+     * @param lockKey 锁的键
      * @param lockExpireMils 获取锁的时间
      * @return 是否获取到锁
      */
     public boolean lock(String lockKey, long lockExpireMils) {
-        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
-            long nowTime = System.currentTimeMillis();
-            Boolean acquire = connection.setNX(lockKey.getBytes(), String.valueOf(nowTime + lockExpireMils + 1).getBytes());
-            if (acquire) {
-                return Boolean.TRUE;
-            } else {
-                byte[] value = connection.get(lockKey.getBytes());
-                if (Objects.nonNull(value) && value.length > 0) {
-                    long oldTime = Long.parseLong(new String(value));
-                    if (oldTime < nowTime) {
-                        //connection.getSet:返回这个key的旧值并设置新值。
-                        byte[] oldValue = connection.getSet(lockKey.getBytes(), String.valueOf(nowTime + lockExpireMils + 1).getBytes());
-                        //当key不存时会返回空,表示key不存在或者已在管道中使用
-                        return oldValue == null ? false : Long.parseLong(new String(oldValue)) < nowTime;
-                    }
-                }
-            }
-            return Boolean.FALSE;
-        });
+        return (Boolean)
+                redisTemplate.execute(
+                        (RedisCallback)
+                                connection -> {
+                                    long nowTime = System.currentTimeMillis();
+                                    Boolean acquire =
+                                            connection.setNX(
+                                                    lockKey.getBytes(),
+                                                    String.valueOf(nowTime + lockExpireMils + 1)
+                                                            .getBytes());
+                                    if (acquire) {
+                                        return Boolean.TRUE;
+                                    } else {
+                                        byte[] value = connection.get(lockKey.getBytes());
+                                        if (Objects.nonNull(value) && value.length > 0) {
+                                            long oldTime = Long.parseLong(new String(value));
+                                            if (oldTime < nowTime) {
+                                                // connection.getSet:返回这个key的旧值并设置新值。
+                                                byte[] oldValue =
+                                                        connection.getSet(
+                                                                lockKey.getBytes(),
+                                                                String.valueOf(
+                                                                                nowTime
+                                                                                        + lockExpireMils
+                                                                                        + 1)
+                                                                        .getBytes());
+                                                // 当key不存时会返回空,表示key不存在或者已在管道中使用
+                                                return oldValue == null
+                                                        ? false
+                                                        : Long.parseLong(new String(oldValue))
+                                                                < nowTime;
+                                            }
+                                        }
+                                    }
+                                    return Boolean.FALSE;
+                                });
     }
 
     /**
@@ -650,6 +676,4 @@ public class AdwebRedisUtil {
             this.del(lockKey);
         }
     }
-
-
 }

+ 34 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/common/util/CommonUtil.java

@@ -0,0 +1,34 @@
+package org.jeecg.modules.adweb.common.util;
+
+import com.google.common.net.InternetDomainName;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * 常用工具类
+ *
+ * @author wfansh
+ */
+@Slf4j
+public class CommonUtil {
+
+    /**
+     * 解析URL的顶级域名
+     *
+     * @param url 例如 https://www.essaynerdie.com, www.essaynerdie.com,
+     *     https://www.essaynerdie.com/products
+     * @return essaynerdie.com
+     */
+    public static String getTopPrivateDomain(String url) {
+        String host = url;
+        try {
+            host = new URL(url).getHost();
+        } catch (MalformedURLException e) {
+        } finally {
+            return InternetDomainName.from(host).topPrivateDomain().toString();
+        }
+    }
+}

+ 43 - 15
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/common/util/DateUtil.java

@@ -1,8 +1,8 @@
 package org.jeecg.modules.adweb.common.util;
 
-
 import com.xkcoding.http.util.StringUtil;
 
+import lombok.extern.slf4j.Slf4j;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -18,18 +18,19 @@ import java.util.*;
  *
  * @author wfansh
  */
+@Slf4j
 public class DateUtil {
 
-    /**
-     * 时间格式(yyyy-MM-dd)
-     */
-    public final static String DATE_PATTERN = "yyyy-MM-dd";
-    /**
-     * 时间格式(yyyy-MM-dd HH:mm:ss)
-     */
-    public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+    /** 时间格式(yyyy-MM-dd) */
+    public static final String DATE_PATTERN = "yyyy-MM-dd";
+
+    /** 时间格式(yyyy-MM-dd HH:mm:ss) */
+    public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
 
-    public final static String SUBJECT_DATE = "yyyy/MM/dd";
+    /** 带时区的时间格式(yyyy-MM-dd HH:mm:ss +00:00) */
+    public static final String ZONED_DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss X";
+
+    public static final String SUBJECT_DATE = "yyyy/MM/dd";
 
     public static final ZoneId DEFAULT_ZONE_ID = ZoneId.of("Asia/Shanghai");
 
@@ -133,9 +134,7 @@ public class DateUtil {
      *
      * @param strDate String
      * @param strFormat String
-     *
      * @return FormatDate
-     *
      * @throws ParseException ParseException
      */
     public static Date getFormatDate(String strDate, String strFormat) throws ParseException {
@@ -153,7 +152,6 @@ public class DateUtil {
      *
      * @param date Date
      * @param toFormat String
-     *
      * @return FormatDate String
      */
     public static String dateToString(Date date, String toFormat) {
@@ -172,9 +170,7 @@ public class DateUtil {
      *
      * @param date String
      * @param strFormat String
-     *
      * @return FormatDate
-     *
      * @throws ParseException ParseException
      */
     public static Date formatDate(Date date, String strFormat) {
@@ -203,4 +199,36 @@ public class DateUtil {
                         LocalDate.ofInstant(end.toInstant(), DEFAULT_ZONE_ID),
                         LocalDate.ofInstant(start.toInstant(), DEFAULT_ZONE_ID));
     }
+
+    /**
+     * 根据指定格式,将Date转化为为字符串
+     *
+     * @param date
+     * @param format
+     * @return
+     */
+    public static String formatDateStr(Date date, String format) {
+        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+        dateFormat.setLenient(false);
+        return dateFormat.format(date);
+    }
+
+    /**
+     * 根据指定格式,将字符串解析为Date
+     *
+     * @param dateStr
+     * @param format
+     * @return
+     * @throws ParseException
+     */
+    public static Date parseDate(String dateStr, String format) {
+        try {
+            SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+            dateFormat.setLenient(false);
+            return dateFormat.parse(dateStr);
+        } catch (ParseException e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
 }

+ 1 - 1
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/SeoKeywordsSerp.java

@@ -61,7 +61,7 @@ public class SeoKeywordsSerp implements Serializable {
 	/**结果类型分组排名*/
 	@Excel(name = "结果类型分组排名", width = 15)
     @Schema(description = "结果类型分组排名")
-    private Integer rankType;
+    private Integer rankGroup;
 	/**自然全局排名*/
 	@Excel(name = "自然全局排名", width = 15)
     @Schema(description = "自然全局排名")

+ 2 - 2
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/SeoKeywordsMapper.java

@@ -60,10 +60,10 @@ public interface SeoKeywordsMapper extends BaseMapper<SeoKeywords> {
                                       String buttonSort);
 
     /**
-     * 获取当日需要DateForSEO serp查询的关键词列表
+     * 获取当日需要DateForSEO Serp查询的关键词列表
      *
      * @return 关键词列表
      */
-    List<SeoKeywords> getKeywordsToSerp(int keywordType);
+    List<SeoKeywords> getKeywordsToSerp(List<String> sideCodes, int keywordType, int limit);
 
 }

+ 5 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/SeoKeywordsSerpMapper.java

@@ -13,4 +13,9 @@ import org.jeecg.modules.adweb.seo.entity.SeoKeywordsSerp;
  */
 public interface SeoKeywordsSerpMapper extends BaseMapper<SeoKeywordsSerp> {
 
+    /**
+     * 返回指定关键词的最后一次Serp记录
+     */
+    SeoKeywordsSerp getLatestSeoKeywordSerp(int keywordId);
+
 }

+ 8 - 10
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/xml/SeoKeywordsMapper.xml

@@ -167,19 +167,17 @@
         FROM
         seo_keywords
         WHERE
-        `status` = 1
-        AND site_code IN (
-        SELECT
-        `code`
-        FROM
-        adweb_site
-        WHERE
-        run_status = 1
-        AND `status` = 1
-        )
+        status = 1
+        AND
+        search_status != 1
+        AND site_code IN
+        <foreach collection="sideCodes" item="sideCode" open="(" separator="," close=")">
+            #{sideCode}
+        </foreach>
         <if test = "keywordType != null">
             AND keyword_type = #{keywordType}
         </if>
         AND last_search_time <![CDATA[ <]]> CURDATE()
+        LIMIT #{limit}
     </select>
 </mapper>

+ 12 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/xml/SeoKeywordsSerpMapper.xml

@@ -2,4 +2,16 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.jeecg.modules.adweb.seo.mapper.SeoKeywordsSerpMapper">
 
+    <select id="getLatestSeoKeywordSerp" resultType="org.jeecg.modules.adweb.seo.entity.SeoKeywordsSerp">
+        SELECT
+        *
+        FROM
+        seo_keywords_serp
+        WHERE
+        keywords_id = #{keywordId}
+        ORDER BY
+        se_date DESC
+        LIMIT 1
+    </select>
+
 </mapper>

+ 2 - 1
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISeoKeywordsSerpService.java

@@ -2,6 +2,7 @@ package org.jeecg.modules.adweb.seo.service;
 
 import org.jeecg.modules.adweb.seo.entity.SeoKeywordsSerp;
 import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.Date;
 
 /**
  * @Description: SEO关键词搜索排名
@@ -10,5 +11,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
  * @Version: V1.0
  */
 public interface ISeoKeywordsSerpService extends IService<SeoKeywordsSerp> {
-
+    boolean fillKeywordsSerpHistory(int keywordId, Date seDatetime);
 }

+ 260 - 44
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/dataforseo/DataForSEOService.java

@@ -1,5 +1,12 @@
 package org.jeecg.modules.adweb.seo.service.dataforseo;
 
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.json.JSONUtil;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.google.common.collect.Lists;
+
 import io.github.dataforseo.client.ApiClient;
 import io.github.dataforseo.client.ApiException;
 import io.github.dataforseo.client.api.SerpApi;
@@ -10,25 +17,44 @@ import jakarta.annotation.PostConstruct;
 
 import lombok.extern.slf4j.Slf4j;
 
+import org.apache.commons.lang3.StringUtils;
+import org.jeecg.modules.adweb.common.util.AdwebRedisUtil;
+import org.jeecg.modules.adweb.common.util.CommonUtil;
 import org.jeecg.modules.adweb.common.util.DateUtil;
 import org.jeecg.modules.adweb.common.util.ListUtil;
 import org.jeecg.modules.adweb.seo.entity.SeoKeywords;
 import org.jeecg.modules.adweb.seo.entity.SeoKeywordsSerp;
 import org.jeecg.modules.adweb.seo.mapper.SeoKeywordsMapper;
+import org.jeecg.modules.adweb.seo.service.ISeoKeywordsSerpService;
 import org.jeecg.modules.adweb.seo.service.ISeoKeywordsService;
+import org.jeecg.modules.adweb.site.entity.AdwebSite;
+import org.jeecg.modules.adweb.site.service.IAdwebSiteService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 
 /**
+ * DataForSEO Serp查询 - 基于Redis定时同步
+ *
+ * <p>1. 暂不使用DataForSEO callback实时更新方式,考虑AdWeb API稳定性
+ *
+ * <p>2. 暂不使用DataForSEO serp/google/organic/tasks_ready API - 每次仅返回一个task,与文档不符
+ *
  * @author wfansh
  */
 @Slf4j
 @Service
 public class DataForSEOService {
+    private static final int MAX_TASKS_PER_SERP_REQUEST = 100;
+    private static final int SERP_STATUS_CODE_SUCCESS = 20000;
+    private static final int SERP_STATUS_CODE_TASK_CREATED = 20100;
+    private static final int SERP_STATUS_CODE_TASK_HANDED = 40601;
+    private static final int SERP_STATUS_CODE_TASK_IN_QUEUE = 40602;
+
+    // Google一页显示搜索结果数量
+    private static final int GOOGLE_SEARCH_PAGE_SIZE = 10;
 
     @Value("${dataforseo.username}")
     private String username;
@@ -39,18 +65,24 @@ public class DataForSEOService {
     @Value("${dataforseo.api-path}")
     private String apiPath;
 
+    @Autowired private IAdwebSiteService adwebSiteService;
+
     @Autowired private SeoKeywordsMapper seoKeywordsMapper;
 
     @Autowired private ISeoKeywordsService seoKeywordsService;
 
+    @Autowired private ISeoKeywordsSerpService seoKeywordsSerpService;
+
+    @Autowired private AdwebRedisUtil redisUtil;
+
     private SerpApi serpApi;
 
     @PostConstruct
     private void init() {
         ApiClient defaultClient = io.github.dataforseo.client.Configuration.getDefaultApiClient();
         defaultClient.setBasePath(apiPath);
-        // HTTP超时 - 30秒
-        defaultClient.setConnectTimeout(30 * 1000);
+        // API超时 - 60秒
+        defaultClient.setConnectTimeout(60 * 1000);
 
         // API认证方式 - basicAuth
         HttpBasicAuth basicAuth = (HttpBasicAuth) defaultClient.getAuthentication("basicAuth");
@@ -61,50 +93,234 @@ public class DataForSEOService {
     }
 
     /**
-     * 从DataForSEO拉取keywords serp数据,同步到{@link SeoKeywordsSerp}表
+     * 全局查询更新DataForSEO keywords Serp数据 - 启动Serp任务并保存到Redis
      *
      * @param keywordType 1 - 指定词; 2 - 长尾词
+     * @param limit 最大查询条数
+     */
+    public void runKeywordsSerpTasks(List<String> siteCodes, int keywordType, int limit) {
+        if (ListUtil.isEmpty(siteCodes)) {
+            siteCodes =
+                    adwebSiteService
+                            .list(
+                                    new LambdaQueryWrapper<AdwebSite>()
+                                            .eq(AdwebSite::getStatus, 1)
+                                            .eq(AdwebSite::getRunStatus, 1))
+                            .stream()
+                            .map(AdwebSite::getCode)
+                            .toList();
+        }
+
+        List<SeoKeywords> seoKeywords =
+                seoKeywordsMapper.getKeywordsToSerp(siteCodes, keywordType, limit);
+
+        if (ListUtil.isEmpty(seoKeywords)) {
+            log.info("暂无需要Serp查询的关键词");
+        } else {
+            // DataForSEO - each POST call containing no more than 100 tasks
+            // https://docs.dataforseo.com/v3/serp/google/organic/task_post
+            Lists.partition(seoKeywords, MAX_TASKS_PER_SERP_REQUEST).forEach(this::sendSerpRequest);
+        }
+    }
+
+    /** 同步DataForSEO Serp结果 - 从Redis中获取正在运行的任务 */
+    public void syncKeywordsSerpResults() {
+        Set<String> serpTaskRedisKeys = redisUtil.keys(this.getSerpTaskRedisKey("*"));
+
+        if (CollectionUtil.isEmpty(serpTaskRedisKeys)) {
+            log.info("Redis中暂无需要同步的Serp关键词");
+        } else {
+            for (String serpTaskRedisKey : serpTaskRedisKeys) {
+                this.onSerpResult(serpTaskRedisKey);
+            }
+        }
+    }
+
+    /**
+     * 向DataForSEO发送Serp请求
+     *
+     * <p>1. 将taskId保存到Redis
+     *
+     * <p>2. 更新{@link SeoKeywords}表
+     */
+    private void sendSerpRequest(List<SeoKeywords> seoKeywords) {
+        try {
+            Date now = new Date();
+
+            // 1. 创建DataForSEO Serp查询请求,每个请求最多包含100个任务
+            List<SerpTaskRequestInfo> serpTaskRequestInfoList = Lists.newArrayList();
+            for (SeoKeywords seoKeyword :
+                    seoKeywords.subList(
+                            0, Math.min(seoKeywords.size(), MAX_TASKS_PER_SERP_REQUEST))) {
+                SerpTaskRequestInfo serpTaskRequestInfo = new SerpTaskRequestInfo();
+                serpTaskRequestInfo.setKeyword(seoKeyword.getKeywords());
+                serpTaskRequestInfo.setSeDomain("google.com");
+                serpTaskRequestInfo.setLanguageCode(
+                        StringUtils.defaultIfEmpty(seoKeyword.getLang(), "en"));
+                serpTaskRequestInfo.setLocationCode(2840); // 美国
+                serpTaskRequestInfo.setTag(Integer.toString(seoKeyword.getId())); // tag = keywordId
+                serpTaskRequestInfoList.add(serpTaskRequestInfo);
+            }
+
+            // 2. 发送DataForSEO Serp查询请求,验证并记录响应结果
+            SerpGoogleOrganicTaskPostResponseInfo serpTaskPostResponseInfo =
+                    serpApi.googleOrganicTaskPost(serpTaskRequestInfoList);
+            log.info(
+                    "创建DataForSEO Serp任务,response = {}",
+                    JSONUtil.toJsonStr(serpTaskPostResponseInfo));
+            if (serpTaskPostResponseInfo.getStatusCode() != SERP_STATUS_CODE_SUCCESS) {
+                throw new ApiException(serpTaskPostResponseInfo.getStatusMessage());
+            }
+
+            // 3. 过滤创建成功的Serp tasks,保存到Redis
+            List<SerpGoogleOrganicTaskPostTaskInfo> serpingTasks =
+                    serpTaskPostResponseInfo.getTasks().stream()
+                            .filter(task -> task.getStatusCode() == SERP_STATUS_CODE_TASK_CREATED)
+                            .toList();
+            List<Integer> serpingKeywordIds = Lists.newArrayList();
+
+            for (SerpGoogleOrganicTaskPostTaskInfo serpingTask : serpingTasks) {
+                Map<String, String> data = (Map<String, String>) serpingTask.getData();
+                String keywordId = data.get("tag");
+                redisUtil.set(this.getSerpTaskRedisKey(keywordId), serpingTask.getId());
+
+                serpingKeywordIds.add(Integer.parseInt(keywordId));
+            }
+
+            // 4. 更新seo_keywords表
+            List<SeoKeywords> serpingKeywords =
+                    seoKeywords.stream()
+                            .filter(keyword -> serpingKeywordIds.contains(keyword.getId()))
+                            .toList();
+            serpingKeywords.forEach(
+                    seoKeyword -> {
+                        seoKeyword.setTimerLastSearchTime(now); // 定时器执行时间
+                        seoKeyword.setSearchStatus(1); // 状态 -> 正在查询
+                    });
+            seoKeywordsService.updateBatchById(serpingKeywords);
+
+            log.info(
+                    "{}个关键词Serp查询任务创建完成 - {}",
+                    serpingKeywords.size(),
+                    serpingKeywords.stream().map(SeoKeywords::getId).toList());
+        } catch (ApiException e) {
+            log.error("创建DataForSEO Serp任务失败", e);
+        }
+    }
+
+    /**
+     * 处理DataForSEO Serp查询结果
+     *
+     * <p>1. 更新{@link SeoKeywords}表
+     *
+     * <p>2. 更新{@link SeoKeywordsSerp}表
+     *
+     * <p>3. 从Redis删除taskId
      */
-    public void syncKeywordsSerp(int keywordType, int limit) throws ApiException {
-        Date now = new Date();
-
-        // 1. 查询待更新keywords
-        List<SeoKeywords> seoKeywordsList =
-                seoKeywordsMapper.getKeywordsToSerp(keywordType).subList(0, limit);
-        if (ListUtil.isEmpty(seoKeywordsList)) {
-            log.info("没有待Serp查询的关键词");
-            return;
+    private boolean onSerpResult(String serpTaskRedisKey) {
+        try {
+            // 1. 查询Serp task
+            String taskId = redisUtil.getString(serpTaskRedisKey);
+            SerpGoogleOrganicTaskGetRegularTaskInfo serpTask =
+                    serpApi.googleOrganicTaskGetRegular(taskId).getTasks().get(0);
+            log.info("获取DataForSEO Serp任务,response = {}", JSONUtil.toJsonStr(serpTask));
+            if (serpTask.getStatusCode() != SERP_STATUS_CODE_SUCCESS) {
+                log.info(
+                        "DataForSEO Serp任务 {} 状态为 {} {}",
+                        taskId,
+                        serpTask.getStatusCode(),
+                        serpTask.getStatusMessage());
+                // Serp task正在处理中...
+                if (Arrays.asList(SERP_STATUS_CODE_TASK_HANDED, SERP_STATUS_CODE_TASK_IN_QUEUE)
+                        .contains(serpTask.getStatusCode())) {
+                    return true;
+                }
+                throw new ApiException(serpTask.getStatusMessage());
+            }
+            SerpGoogleOrganicTaskGetRegularResultInfo serpResult = serpTask.getResult().get(0);
+
+            // 2. 查询seo_keywords表,根据域名过滤Serp result items
+            int keywordId = Integer.parseInt(this.getKeywordIdFromRedisKey(serpTaskRedisKey));
+            SeoKeywords seoKeyword = seoKeywordsService.getById(keywordId);
+            if (Objects.isNull(seoKeyword)) {
+                log.info("无法获取关键词 ID = {}", keywordId);
+
+                redisUtil.del(serpTaskRedisKey);
+                return false;
+            }
+
+            String topPrivateDomain =
+                    CommonUtil.getTopPrivateDomain(seoKeyword.getDomain()); // 顶级域名
+            OrganicSerpElementItem serpItem =
+                    serpResult.getItems().stream()
+                            .filter(OrganicSerpElementItem.class::isInstance)
+                            .map(OrganicSerpElementItem.class::cast)
+                            .filter(item -> item.getDomain().contains(topPrivateDomain)) // 根据域名匹配
+                            .findAny()
+                            .orElse(null);
+
+            // 读取Serp相关数据
+            Date seDatetime =
+                    DateUtil.parseDate(serpResult.getDatetime(), DateUtil.ZONED_DATE_TIME_PATTERN);
+            String positionUrl =
+                    Objects.nonNull(serpItem)
+                            ? StringUtils.removeEnd(serpItem.getUrl(), "/")
+                            : null;
+            int rankGroup = Objects.nonNull(serpItem) ? serpItem.getRankGroup() : 0;
+            int rankAbsolute = Objects.nonNull(serpItem) ? serpItem.getRankAbsolute() : 0;
+
+            // 3.更新seo_keywords表
+            UpdateWrapper<SeoKeywords> seoKeywordsUpdateWrapper = new UpdateWrapper<>();
+            seoKeywordsUpdateWrapper.eq("id", keywordId);
+            seoKeywordsUpdateWrapper.set("last_search_time", seDatetime);
+            seoKeywordsUpdateWrapper.set("position_url", positionUrl);
+            seoKeywordsUpdateWrapper.set("last_rank", rankGroup);
+            seoKeywordsUpdateWrapper.set("search_status", 0); // 状态 -> 查询结束
+            seoKeywordsService.update(seoKeywordsUpdateWrapper);
+
+            // 4. 更新seo_keywords_serp表
+            // 4.1 填充与上次更新时间之间的数据, 截止到seDatetime的前一天
+            seoKeywordsSerpService.fillKeywordsSerpHistory(keywordId, seDatetime);
+            // 4.2 更新Serp表
+            SeoKeywordsSerp keywordSerp =
+                    seoKeywordsSerpService
+                            // 如果seDatetime当天有数据,则覆盖
+                            .list(
+                                    new LambdaQueryWrapper<SeoKeywordsSerp>()
+                                            .eq(SeoKeywordsSerp::getKeywordsId, keywordId)
+                                            .eq(
+                                                    SeoKeywordsSerp::getSeDate,
+                                                    DateUtil.formatDate(
+                                                            seDatetime, DateUtil.DATE_PATTERN)))
+                            .stream()
+                            .findFirst()
+                            .orElse(new SeoKeywordsSerp());
+
+            keywordSerp.setKeywordsId(keywordId);
+            keywordSerp.setSearchUrl(serpResult.getCheckUrl());
+            keywordSerp.setSeDomain(serpResult.getSeDomain());
+            keywordSerp.setLanguageCode(serpResult.getLanguageCode());
+            keywordSerp.setType("organic_results");
+            keywordSerp.setPageNumber(rankGroup > 0 ? rankGroup / GOOGLE_SEARCH_PAGE_SIZE + 1 : 0);
+            keywordSerp.setRankGroup(rankGroup);
+            keywordSerp.setRankAbsolute(rankAbsolute);
+            keywordSerp.setSeDate(DateUtil.formatDateStr(seDatetime, DateUtil.DATE_PATTERN));
+            keywordSerp.setSeDatetime(seDatetime);
+            seoKeywordsSerpService.save(keywordSerp);
+
+            redisUtil.del(serpTaskRedisKey);
+            return true;
+        } catch (ApiException e) {
+            log.error("同步DataForSEO Serp任务失败", e);
+            return false;
         }
+    }
+
+    private String getSerpTaskRedisKey(String keywordId) {
+        return String.format("serp_task:%s", keywordId);
+    }
 
-        // 2. 发送DataForSEO Serp查询请求
-        //        List<SerpGoogleOrganicLiveAdvancedRequestInfo> serpTasks =
-        //                seoKeywordsList.stream()
-        //                        .map(
-        //                                seoKeyword -> {
-        //                                    SerpGoogleOrganicLiveAdvancedRequestInfo serpTask =
-        //                                            new
-        // SerpGoogleOrganicLiveAdvancedRequestInfo();
-        //                                    serpTask.setKeyword(seoKeyword.getKeywords());
-        //                                    serpTask.setTag(Integer.toString(seoKeyword.getId()));
-        //                                    return serpTask;
-        //                                })
-        //                        .toList();
-        //
-        //        SerpGoogleOrganicLiveAdvancedResponseInfo serpResults =
-        //                serpApi.googleOrganicLiveAdvanced(serpTasks);
-
-        // 3. 更新SeoKeywords表
-        seoKeywordsList.forEach(
-                seoKeyword -> {
-                    // TODO: why?
-                    seoKeyword.setTimerLastSearchTime(DateUtil.getTodayZeroTime(now));
-                    // On search.
-                    seoKeyword.setSearchStatus(1);
-                });
-        seoKeywordsService.updateBatchById(seoKeywordsList);
-        log.info(
-                "{}个关键词serp查询任务创建完成 {}",
-                seoKeywordsList.size(),
-                seoKeywordsList.stream().map(SeoKeywords::getId).toList());
+    private String getKeywordIdFromRedisKey(String serpTaskRedisKey) {
+        return serpTaskRedisKey.split(":")[1];
     }
 }

+ 66 - 6
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SeoKeywordsSerpServiceImpl.java

@@ -1,19 +1,79 @@
 package org.jeecg.modules.adweb.seo.service.impl;
 
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jeecg.modules.adweb.common.util.DateUtil;
+import org.jeecg.modules.adweb.common.util.ListUtil;
 import org.jeecg.modules.adweb.seo.entity.SeoKeywordsSerp;
 import org.jeecg.modules.adweb.seo.mapper.SeoKeywordsSerpMapper;
 import org.jeecg.modules.adweb.seo.service.ISeoKeywordsSerpService;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
 
 /**
- * @Description: SEO关键词搜索排名
- * @Author: jeecg-boot
- * @Date:   2024-10-15
- * @Version: V1.0
+ * @Description: SEO关键词搜索排名 @Author: jeecg-boot @Date: 2024-10-15 @Version: V1.0
  */
+@Slf4j
 @Service
-public class SeoKeywordsSerpServiceImpl extends ServiceImpl<SeoKeywordsSerpMapper, SeoKeywordsSerp> implements ISeoKeywordsSerpService {
+public class SeoKeywordsSerpServiceImpl extends ServiceImpl<SeoKeywordsSerpMapper, SeoKeywordsSerp>
+        implements ISeoKeywordsSerpService {
+
+    @Autowired private SeoKeywordsSerpMapper seoKeywordsSerpMapper;
+
+    /**
+     * 向seo_keywords_serp表中补充数据,截止到seDatetime的前一天
+     *
+     * @param keywordId 关键词ID
+     * @param seDatetime serp返回时间,此日期不需要填充
+     */
+    @Override
+    public boolean fillKeywordsSerpHistory(int keywordId, Date seDatetime) {
+        SeoKeywordsSerp latestSerp = seoKeywordsSerpMapper.getLatestSeoKeywordSerp(keywordId);
+        if (Objects.isNull(latestSerp)) {
+            return false;
+        }
+
+        // Serp最后更新时间 + 一天
+        Date startDate =
+                DateUtil.getTmrZeroTime(
+                        DateUtil.parseDate(latestSerp.getSeDate(), DateUtil.DATE_PATTERN));
+        // DateForSEO返回的SearchEngine时间
+        Date endDate = DateUtil.getTodayZeroTime(seDatetime);
+
+        List<SeoKeywordsSerp> serpsToFill = new ArrayList<>();
+        for (Date currentDate = startDate;
+                currentDate.compareTo(endDate) < 0;
+                currentDate = DateUtil.addDays(currentDate, 1)) {
+            // 设置成跟latestSerp相同的值
+            SeoKeywordsSerp serp = new SeoKeywordsSerp();
+            serp.setKeywordsId(keywordId);
+            serp.setSearchUrl(latestSerp.getSearchUrl());
+            serp.setSeDomain(latestSerp.getSeDomain());
+            serp.setLanguageCode(latestSerp.getLanguageCode());
+            serp.setType(
+                    StringUtils.appendIfMissing(latestSerp.getType(), "_copy")); // 标识出是复制的Serp值
+            serp.setPageNumber(latestSerp.getPageNumber());
+            serp.setRankGroup(latestSerp.getRankGroup());
+            serp.setRankAbsolute(latestSerp.getRankAbsolute());
+            serp.setSeDate(
+                    DateUtil.formatDateStr(currentDate, DateUtil.DATE_PATTERN)); // 复制Serp值的目标日期
+            serp.setSeDatetime(latestSerp.getSeDatetime()); // 真实Serp返回的SearchingEngine时间
+            serpsToFill.add(serp);
+        }
+
+        if (ListUtil.notEmpty(serpsToFill)) {
+            log.info("复制Serp记录,keyword ID = {}, 时间范围 = {} to {}", keywordId, startDate, endDate);
+            return this.saveBatch(serpsToFill);
+        }
 
+        return true;
+    }
 }

+ 65 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/xxl/DataForSEOJob.java

@@ -0,0 +1,65 @@
+package org.jeecg.modules.xxl;
+
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.jeecg.modules.adweb.common.constant.AdwebConstant;
+import org.jeecg.modules.adweb.seo.service.dataforseo.DataForSEOService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * DataForSEO Serp查询及同步任务,{@link DataForSEOService}
+ *
+ * @author wfansh
+ */
+@Slf4j
+@Component
+public class DataForSEOJob {
+
+    @Autowired private DataForSEOService dataForSEOService;
+
+    @XxlJob("runLongTailKeywordsSerpTasksHandler")
+    public ReturnT<String> runLongTailKeywordsSerpTasksHandler(String param) {
+        log.info("执行长尾词Serp查询..., param = {}", param);
+        dataForSEOService.runKeywordsSerpTasks(
+                this.parseSiteCodes(param),
+                AdwebConstant.KEYWORD_TYPE_LONG_TAIL,
+                Integer.MAX_VALUE);
+        log.info("执行长尾词Serp查询结束");
+
+        return ReturnT.SUCCESS;
+    }
+
+    @XxlJob("runAppointKeywordsSerpTasksHandler")
+    public ReturnT<String> runAppointKeywordsSerpTasksHandler(String param) {
+        log.info("执行指定词Serp查询..., param = {}", param);
+        dataForSEOService.runKeywordsSerpTasks(
+                this.parseSiteCodes(param), AdwebConstant.KEYWORD_TYPE_APPOINT, Integer.MAX_VALUE);
+        log.info("执行指定词Serp查询结束");
+
+        return ReturnT.SUCCESS;
+    }
+
+    @XxlJob("syncKeywordsSerpResultsHandler")
+    public ReturnT<String> syncKeywordsSerpResultsHandler(String param) {
+        log.info("同步关键词Serp查询结果...");
+        dataForSEOService.syncKeywordsSerpResults();
+        log.info("同步关键词Serp查询结果结束");
+
+        return ReturnT.SUCCESS;
+    }
+
+    private List<String> parseSiteCodes(String param) {
+        return Objects.nonNull(param)
+                ? Arrays.stream(param.split(",")).map(siteCode -> siteCode.trim()).toList()
+                : Collections.EMPTY_LIST;
+    }
+}

+ 4 - 1
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/xxl/GAReportJob.java

@@ -1,5 +1,6 @@
 package org.jeecg.modules.xxl;
 
+import com.xxl.job.core.biz.model.ReturnT;
 import com.xxl.job.core.handler.annotation.XxlJob;
 
 import lombok.extern.slf4j.Slf4j;
@@ -20,9 +21,11 @@ public class GAReportJob {
     @Autowired private GAReportService gaReportService;
 
     @XxlJob("syncGAReportHandler")
-    public void syncGAReportHandler() {
+    public ReturnT<String> syncGAReportHandler(String param) {
         log.info("同步GA报表数据...");
         gaReportService.syncGAReport();
         log.info("同步GA报表数据结束");
+
+        return ReturnT.SUCCESS;
     }
 }

+ 13 - 3
jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/adweb/seo/service/DataForSEOTest.java

@@ -1,13 +1,15 @@
 package org.jeecg.modules.adweb.seo.service;
 
-
 import org.jeecg.modules.adweb.common.constant.AdwebConstant;
 import org.jeecg.modules.adweb.seo.service.dataforseo.DataForSEOService;
+import org.junit.jupiter.api.Disabled;
 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.Collections;
+
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
 @ActiveProfiles("dev")
 public class DataForSEOTest {
@@ -15,7 +17,15 @@ public class DataForSEOTest {
     @Autowired private DataForSEOService dataForSEOService;
 
     @Test
-    public void testSerpGoogleOrganic() throws Exception {
-        dataForSEOService.syncKeywordsSerp(AdwebConstant.KEYWORD_TYPE_APPOINT, 3);
+    @Disabled
+    public void testRunKeywordsSerpTasks() {
+        dataForSEOService.runKeywordsSerpTasks(
+                Collections.EMPTY_LIST, AdwebConstant.KEYWORD_TYPE_APPOINT, 10);
+    }
+
+    @Test
+    @Disabled
+    public void testSyncKeywordsSerpResults() {
+        dataForSEOService.syncKeywordsSerpResults();
     }
 }