Browse Source

合并master并修复冲突

chenlei1231 5 months ago
parent
commit
47d1225fa9
53 changed files with 3294 additions and 28 deletions
  1. 9 0
      jeecg-module-system/jeecg-system-api/jeecg-system-local-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java
  2. 6 0
      jeecg-module-system/jeecg-system-biz/pom.xml
  3. 3 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/common/constant/AdwebConstant.java
  4. 40 12
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/common/util/DateUtil.java
  5. 2 2
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/dmp/service/google/GAReportService.java
  6. 5 1
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/enquiry/controller/AdwebEnquiryController.java
  7. 166 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/HistoryReachStandardSiteController.java
  8. 0 7
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/SEOController.java
  9. 359 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/SeoKeywordsController.java
  10. 167 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/SeoKeywordsSerpController.java
  11. 197 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/SeoKpiStatisticsController.java
  12. 164 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/SeoPlanSubscriptionController.java
  13. 28 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/dto/AvesApiSearchKeywordsDTO.java
  14. 46 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/ComprehensiveStatistics.java
  15. 59 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/HistoryReachStandardSite.java
  16. 162 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/SeoKeywords.java
  17. 82 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/SeoKeywordsSerp.java
  18. 116 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/SeoKpiStatistics.java
  19. 135 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/SeoPlanSubscription.java
  20. 14 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/HistoryReachStandardSiteMapper.java
  21. 69 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/SeoKeywordsMapper.java
  22. 16 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/SeoKeywordsSerpMapper.java
  23. 14 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/SeoKpiStatisticsMapper.java
  24. 22 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/SeoPlanSubscriptionMapper.java
  25. 5 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/xml/HistoryReachStandardSiteMapper.xml
  26. 184 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/xml/SeoKeywordsMapper.xml
  27. 5 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/xml/SeoKeywordsSerpMapper.xml
  28. 5 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/xml/SeoKpiStatisticsMapper.xml
  29. 18 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/mapper/xml/SeoPlanSubscriptionMapper.xml
  30. 14 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/IHistoryReachStandardSiteService.java
  31. 26 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISearchKeywordsService.java
  32. 14 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISeoKeywordsSerpService.java
  33. 37 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISeoKeywordsService.java
  34. 18 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISeoKpiStatisticsService.java
  35. 14 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISeoPlanSubscriptionService.java
  36. 68 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/dataforseo/DataForSEOService.java
  37. 19 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/HistoryReachStandardSiteServiceImpl.java
  38. 471 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SearchKeywordsServiceImpl.java
  39. 19 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SeoKeywordsSerpServiceImpl.java
  40. 100 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SeoKeywordsServiceImpl.java
  41. 97 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SeoKpiStatisticsServiceImpl.java
  42. 19 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SeoPlanSubscriptionServiceImpl.java
  43. 13 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/vo/RankInfoVO.java
  44. 13 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/vo/SeoRankInfoVO.java
  45. 17 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/vo/ServerTimeVO.java
  46. 11 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/site/entity/AdwebSite.java
  47. 129 2
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysBaseApiImpl.java
  48. 9 2
      jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
  49. 8 1
      jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml
  50. 7 1
      jeecg-module-system/jeecg-system-start/src/main/resources/application-test.yml
  51. 2 0
      jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/adweb/dmp/service/google/GoogleServiceTest.java
  52. 40 0
      jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/adweb/seo/service/DataForSEOTest.java
  53. 31 0
      jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/mq/RabbitMQTest.java

+ 9 - 0
jeecg-module-system/jeecg-system-api/jeecg-system-local-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java

@@ -544,4 +544,13 @@ public interface ISysBaseAPI extends CommonAPI {
      */
     boolean dictTableWhiteListCheckByDict(String tableOrDictCode, String... fields);
 
+    boolean isOem();
+
+    boolean isAdmin();
+
+    List<String> getOemGroupUids();
+
+    boolean isNotOwnSite(Integer siteId);
+
+    boolean isNotOwnSite(String siteCode);
 }

+ 6 - 0
jeecg-module-system/jeecg-system-biz/pom.xml

@@ -100,6 +100,12 @@
             <version>5.8.25</version>
             <scope>compile</scope>
         </dependency>
+		<!--dataforseo client https://docs.dataforseo.com/v3/-->
+		<dependency>
+			<groupId>io.github.dataforseo</groupId>
+			<artifactId>dataforseo-client</artifactId>
+			<version>1.0.19</version>
+		</dependency>
     </dependencies>
 
 </project>

+ 3 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/common/constant/AdwebConstant.java

@@ -53,5 +53,8 @@ public interface AdwebConstant {
      */
     public static final String TEMPLATE_TAGS = "template_tags";
 
+    /*SEO关键字类型 1 - 指定词 2 - 长尾词 */
+    public static final int KEYWORD_TYPE_APPOINT = 1;
+    public static final int KEYWORD_TYPE_LONG_TAIL = 2;
 
 }

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

@@ -1,13 +1,14 @@
 package org.jeecg.modules.adweb.common.util;
 
+
 import com.xkcoding.http.util.StringUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.StringUtils;
+
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.LocalDate;
 import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
 import java.util.*;
 
 /**
@@ -19,6 +20,17 @@ import java.util.*;
  */
 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";
+
+    public final static String SUBJECT_DATE = "yyyy/MM/dd";
+
     public static final ZoneId DEFAULT_ZONE_ID = ZoneId.of("Asia/Shanghai");
 
     /** yyyy-MM-dd HH:mm:ss. */
@@ -48,20 +60,20 @@ public class DateUtil {
         Date start = null;
         Date end = null;
         if ("yesterday".equals(dateType)) {
-            start = getTodayZeroTime(DateUtil.addDay2(now, -1));
+            start = getTodayZeroTime(DateUtil.addDays(now, -1));
             end = getTodayZeroTime(now);
         }
         if ("today".equals(dateType)) {
             start = getTodayZeroTime(now);
-            end = getTomZeroTime(now);
+            end = getTmrZeroTime(now);
         }
         if ("sevenDay".equals(dateType)) {
-            end = getTomZeroTime(now);
-            start = addDay2(end, -7);
+            end = getTmrZeroTime(now);
+            start = addDays(end, -7);
         }
         if ("thirtyDay".equals(dateType)) {
-            end = getTomZeroTime(now);
-            start = addDay2(end, -30);
+            end = getTmrZeroTime(now);
+            start = addDays(end, -30);
         }
         Map<String, Date> map = new HashMap<>();
         map.put("start", start);
@@ -71,6 +83,7 @@ public class DateUtil {
 
     /**
      * 获取当日零点
+     *
      * @param date
      * @return
      */
@@ -86,10 +99,11 @@ public class DateUtil {
 
     /**
      * 获取明日零点
+     *
      * @param date
      * @return
      */
-    public static Date getTomZeroTime(Date date) {
+    public static Date getTmrZeroTime(Date date) {
         Calendar calendar = Calendar.getInstance();
         calendar.setTime(date);
         calendar.set(Calendar.HOUR_OF_DAY, 0);
@@ -102,11 +116,12 @@ public class DateUtil {
 
     /**
      * 在当前日期上追加N天
+     *
      * @param date 当前日期
      * @param num 添加天数
      * @return 日期字符串形式
      */
-    public static Date addDay2(Date date, int num) {
+    public static Date addDays(Date date, int num) {
         Calendar c = Calendar.getInstance();
         c.setTime(date);
         c.add(Calendar.DAY_OF_MONTH, num);
@@ -151,5 +166,18 @@ public class DateUtil {
         df.setLenient(false);
         return df.format(date);
     }
-}
 
+    /**
+     * 计算两个Date的日期差
+     *
+     * @param start
+     * @param end
+     * @return
+     */
+    public static int diffDays(Date start, Date end) {
+        return (int)
+                ChronoUnit.DAYS.between(
+                        LocalDate.ofInstant(end.toInstant(), DEFAULT_ZONE_ID),
+                        LocalDate.ofInstant(start.toInstant(), DEFAULT_ZONE_ID));
+    }
+}

+ 2 - 2
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/dmp/service/google/GAReportService.java

@@ -329,10 +329,10 @@ public class GAReportService {
         Date maxDate = commonMapper.getMaxDate(tableName, "date", "site_id = " + siteId);
         if (Objects.isNull(maxDate)) {
             // 1. 一年前
-            return DateUtil.plusDays(new Date(), -365);
+            return DateUtil.addDays(new Date(), -365);
         }
         // 2. 最大时间减一天
-        return DateUtil.plusDays(maxDate, -1);
+        return DateUtil.addDays(maxDate, -1);
     }
 
     /** 生成{@link QueryWrapper}, 删除GA报表历史数据 */

+ 5 - 1
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/enquiry/controller/AdwebEnquiryController.java

@@ -3,12 +3,16 @@ package org.jeecg.modules.adweb.enquiry.controller;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
+
 import lombok.extern.slf4j.Slf4j;
+
 import org.apache.commons.lang.StringUtils;
 import org.apache.shiro.SecurityUtils;
 import org.jeecg.common.api.vo.Result;
@@ -143,7 +147,7 @@ public class AdwebEnquiryController extends JeecgController<AdwebEnquiry, IAdweb
 			end = map.get("end");
 		} else {
 			if (end != null) {
-				end = DateUtil.addDay2(end, 1);
+				end = DateUtil.addDays(end, 1);
 			}
 		}
 		if(ListUtil.isEmpty(codeList)){

+ 166 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/HistoryReachStandardSiteController.java

@@ -0,0 +1,166 @@
+package org.jeecg.modules.adweb.seo.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.aspect.annotation.AutoLog;
+import org.jeecg.common.system.base.controller.JeecgController;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.modules.adweb.seo.entity.HistoryReachStandardSite;
+import org.jeecg.modules.adweb.seo.service.IHistoryReachStandardSiteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.util.Arrays;
+
+/**
+ * @Description: 历史达标站点关键词达标数据表
+ * @Author: jeecg-boot
+ * @Date:   2024-10-16
+ * @Version: V1.0
+ */
+@Tag(name="历史达标站点关键词达标数据表")
+@RestController
+@RequestMapping("/serp/historyReachStandardSite")
+@Slf4j
+public class HistoryReachStandardSiteController extends JeecgController<HistoryReachStandardSite, IHistoryReachStandardSiteService> {
+	@Autowired
+	private IHistoryReachStandardSiteService historyReachStandardSiteService;
+	
+	/**
+	 * 分页列表查询
+	 *
+	 * @param historyReachStandardSite
+	 * @param pageNo
+	 * @param pageSize
+	 * @param req
+	 * @return
+	 */
+	//@AutoLog(value = "历史达标站点关键词达标数据表-分页列表查询")
+	@Operation(summary="历史达标站点关键词达标数据表-分页列表查询")
+	@GetMapping(value = "/list")
+	public Result<IPage<HistoryReachStandardSite>> queryPageList(HistoryReachStandardSite historyReachStandardSite,
+								   @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+								   @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
+								   HttpServletRequest req) {
+		QueryWrapper<HistoryReachStandardSite> queryWrapper = QueryGenerator.initQueryWrapper(historyReachStandardSite, req.getParameterMap());
+		Page<HistoryReachStandardSite> page = new Page<HistoryReachStandardSite>(pageNo, pageSize);
+		IPage<HistoryReachStandardSite> pageList = historyReachStandardSiteService.page(page, queryWrapper);
+		return Result.OK(pageList);
+	}
+	
+	/**
+	 *   添加
+	 *
+	 * @param historyReachStandardSite
+	 * @return
+	 */
+	@AutoLog(value = "历史达标站点关键词达标数据表-添加")
+	@Operation(summary="历史达标站点关键词达标数据表-添加")
+	@PreAuthorize("@jps.requiresPermissions('serp:history_reach_standard_site:add')")
+	@PostMapping(value = "/add")
+	public Result<String> add(@RequestBody HistoryReachStandardSite historyReachStandardSite) {
+		historyReachStandardSiteService.save(historyReachStandardSite);
+		return Result.OK("添加成功!");
+	}
+	
+	/**
+	 *  编辑
+	 *
+	 * @param historyReachStandardSite
+	 * @return
+	 */
+	@AutoLog(value = "历史达标站点关键词达标数据表-编辑")
+	@Operation(summary="历史达标站点关键词达标数据表-编辑")
+    @PreAuthorize("@jps.requiresPermissions('serp:history_reach_standard_site:edit')")
+	@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
+	public Result<String> edit(@RequestBody HistoryReachStandardSite historyReachStandardSite) {
+		historyReachStandardSiteService.updateById(historyReachStandardSite);
+		return Result.OK("编辑成功!");
+	}
+	
+	/**
+	 *   通过id删除
+	 *
+	 * @param id
+	 * @return
+	 */
+	@AutoLog(value = "历史达标站点关键词达标数据表-通过id删除")
+	@Operation(summary="历史达标站点关键词达标数据表-通过id删除")
+    @PreAuthorize("@jps.requiresPermissions('serp:history_reach_standard_site:delete')")
+	@DeleteMapping(value = "/delete")
+	public Result<String> delete(@RequestParam(name="id",required=true) String id) {
+		historyReachStandardSiteService.removeById(id);
+		return Result.OK("删除成功!");
+	}
+	
+	/**
+	 *  批量删除
+	 *
+	 * @param ids
+	 * @return
+	 */
+	@AutoLog(value = "历史达标站点关键词达标数据表-批量删除")
+	@Operation(summary="历史达标站点关键词达标数据表-批量删除")
+    @PreAuthorize("@jps.requiresPermissions('serp:history_reach_standard_site:deleteBatch')")
+	@DeleteMapping(value = "/deleteBatch")
+	public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
+		this.historyReachStandardSiteService.removeByIds(Arrays.asList(ids.split(",")));
+		return Result.OK("批量删除成功!");
+	}
+	
+	/**
+	 * 通过id查询
+	 *
+	 * @param id
+	 * @return
+	 */
+	//@AutoLog(value = "历史达标站点关键词达标数据表-通过id查询")
+	@Operation(summary="历史达标站点关键词达标数据表-通过id查询")
+	@GetMapping(value = "/queryById")
+	public Result<HistoryReachStandardSite> queryById(@RequestParam(name="id",required=true) String id) {
+		HistoryReachStandardSite historyReachStandardSite = historyReachStandardSiteService.getById(id);
+		if(historyReachStandardSite==null) {
+			return Result.error("未找到对应数据");
+		}
+		return Result.OK(historyReachStandardSite);
+	}
+
+    /**
+    * 导出excel
+    *
+    * @param request
+    * @param historyReachStandardSite
+    */
+    @PreAuthorize("@jps.requiresPermissions('serp:history_reach_standard_site:exportXls')")
+    @RequestMapping(value = "/exportXls")
+    public ModelAndView exportXls(HttpServletRequest request, HistoryReachStandardSite historyReachStandardSite) {
+        return super.exportXls(request, historyReachStandardSite, HistoryReachStandardSite.class, "历史达标站点关键词达标数据表");
+    }
+
+    /**
+      * 通过excel导入数据
+    *
+    * @param request
+    * @param response
+    * @return
+    */
+    @PreAuthorize("@jps.requiresPermissions('serp:history_reach_standard_site:importExcel')")
+    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
+    public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
+        return super.importExcel(request, response, HistoryReachStandardSite.class);
+    }
+
+}

+ 0 - 7
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/SEOController.java

@@ -1,7 +0,0 @@
-package org.jeecg.modules.adweb.seo.controller;
-
-/**
- * @author wfansh
- */
-public class SEOController {
-}

+ 359 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/SeoKeywordsController.java

@@ -0,0 +1,359 @@
+package org.jeecg.modules.adweb.seo.controller;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.poi.excel.ExcelUtil;
+import cn.hutool.poi.excel.ExcelWriter;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.xkcoding.http.util.StringUtil;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.apache.poi.ss.usermodel.Sheet;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.aspect.annotation.AutoLog;
+import org.jeecg.common.system.api.ISysBaseAPI;
+import org.jeecg.common.system.base.controller.JeecgController;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.modules.adweb.common.util.ListUtil;
+import org.jeecg.modules.adweb.seo.dto.AvesApiSearchKeywordsDTO;
+import org.jeecg.modules.adweb.seo.entity.SeoKeywords;
+import org.jeecg.modules.adweb.seo.entity.SeoKpiStatistics;
+import org.jeecg.modules.adweb.seo.service.ISearchKeywordsService;
+import org.jeecg.modules.adweb.seo.service.ISeoKeywordsService;
+import org.jeecg.modules.adweb.seo.service.ISeoKpiStatisticsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+/**
+ * @Description: 关键词
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+@Tag(name="关键词")
+@RestController
+@RequestMapping("/serp/seoKeywords")
+@Slf4j
+public class SeoKeywordsController extends JeecgController<SeoKeywords, ISeoKeywordsService> {
+	@Autowired
+	private ISeoKeywordsService seoKeywordsService;
+
+	@Autowired
+	ISeoKpiStatisticsService seoKpiStatisticsService;
+
+	@Autowired
+	ISearchKeywordsService searchKeywordsService;
+
+	@Autowired
+	private ISysBaseAPI sysBaseAPI;
+	
+	/**
+	 * 分页列表查询
+	 *
+	 * @param seoKeywords
+	 * @param pageNo
+	 * @param pageSize
+	 * @param req
+	 * @return
+	 */
+	//@AutoLog(value = "关键词-分页列表查询")
+	@Operation(summary="关键词-分页列表查询")
+	@GetMapping(value = "/list")
+	public Result<IPage<SeoKeywords>> queryPageList(SeoKeywords seoKeywords,
+								   @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+								   @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
+								   HttpServletRequest req) {
+		QueryWrapper<SeoKeywords> queryWrapper = QueryGenerator.initQueryWrapper(seoKeywords, req.getParameterMap());
+		Page<SeoKeywords> page = new Page<SeoKeywords>(pageNo, pageSize);
+		IPage<SeoKeywords> pageList = seoKeywordsService.page(page, queryWrapper);
+		return Result.OK(pageList);
+	}
+	
+	/**
+	 *   添加
+	 *
+	 * @param seoKeywords
+	 * @return
+	 */
+	@AutoLog(value = "关键词-添加")
+	@Operation(summary="关键词-添加")
+	@PreAuthorize("@jps.requiresPermissions('serp:seo_keywords:add')")
+	@PostMapping(value = "/add")
+	public Result<String> add(@RequestBody SeoKeywords seoKeywords) {
+		seoKeywordsService.save(seoKeywords);
+		return Result.OK("添加成功!");
+	}
+	
+	/**
+	 *  编辑
+	 *
+	 * @param seoKeywords
+	 * @return
+	 */
+	@AutoLog(value = "关键词-编辑")
+	@Operation(summary="关键词-编辑")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords:edit')")
+	@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
+	public Result<String> edit(@RequestBody SeoKeywords seoKeywords) {
+		seoKeywordsService.updateById(seoKeywords);
+		return Result.OK("编辑成功!");
+	}
+	
+	/**
+	 *   通过id删除
+	 *
+	 * @param id
+	 * @return
+	 */
+	@AutoLog(value = "关键词-通过id删除")
+	@Operation(summary="关键词-通过id删除")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords:delete')")
+	@DeleteMapping(value = "/delete")
+	public Result<String> delete(@RequestParam(name="id",required=true) String id) {
+		seoKeywordsService.removeById(id);
+		return Result.OK("删除成功!");
+	}
+	
+	/**
+	 *  批量删除
+	 *
+	 * @param ids
+	 * @return
+	 */
+	@AutoLog(value = "关键词-批量删除")
+	@Operation(summary="关键词-批量删除")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords:deleteBatch')")
+	@DeleteMapping(value = "/deleteBatch")
+	public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
+		this.seoKeywordsService.removeByIds(Arrays.asList(ids.split(",")));
+		return Result.OK("批量删除成功!");
+	}
+	
+	/**
+	 * 通过id查询
+	 *
+	 * @param id
+	 * @return
+	 */
+	//@AutoLog(value = "关键词-通过id查询")
+	@Operation(summary="关键词-通过id查询")
+	@GetMapping(value = "/queryById")
+	public Result<SeoKeywords> queryById(@RequestParam(name="id",required=true) String id) {
+		SeoKeywords seoKeywords = seoKeywordsService.getById(id);
+		if(seoKeywords==null) {
+			return Result.error("未找到对应数据");
+		}
+		return Result.OK(seoKeywords);
+	}
+
+    /**
+    * 导出excel
+    *
+    * @param request
+    * @param seoKeywords
+    */
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords:exportXls')")
+    @RequestMapping(value = "/exportXls")
+    public ModelAndView exportXls(HttpServletRequest request, SeoKeywords seoKeywords) {
+        return super.exportXls(request, seoKeywords, SeoKeywords.class, "关键词");
+    }
+
+    /**
+      * 通过excel导入数据
+    *
+    * @param request
+    * @param response
+    * @return
+    */
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords:importExcel')")
+    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
+    public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
+        return super.importExcel(request, response, SeoKeywords.class);
+    }
+
+	 /**
+	  * 关键词排名页面 - 列表数据
+	  */
+	 @AutoLog(value = "营销推广-SEO-关键词排名统计查询接口")
+	 @RequestMapping(value = "/keywordList")
+	 public Result<?> queryRankInfoList(AvesApiSearchKeywordsDTO avesApiSearchKeywordsDTO,
+										@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
+										@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
+										HttpServletRequest req) throws Exception {
+		 if (sysBaseAPI.isNotOwnSite(avesApiSearchKeywordsDTO.getUserFlag())) {
+			 return Result.OK(new Page<>());
+		 }
+		 // 参数判断
+		 IPage<SeoKeywords> pageList = new Page<>();
+		 String userFlag = req.getParameter("userFlag");
+		 if (StringUtil.isEmpty(userFlag)) {
+			 userFlag = avesApiSearchKeywordsDTO.getUserFlag();
+		 }
+		 if (StringUtil.isEmpty(userFlag)) {
+			 log.info("缺少用户flag参数");
+			 return Result.OK(pageList);
+		 }
+
+		 String historyId = avesApiSearchKeywordsDTO.getHistoryId();
+
+		 // 查询是否具有关键词优化指标
+		 boolean haveAppointKeyword = false;
+		 boolean haveLongTail = false;
+		 QueryWrapper<SeoKpiStatistics> statisticsQueryWrapper = new QueryWrapper<>();
+		 statisticsQueryWrapper.eq("host_code", userFlag);
+		 statisticsQueryWrapper.eq("del_flag", 0);
+		 statisticsQueryWrapper.eq("type", 1);
+		 statisticsQueryWrapper.eq("history_id", historyId);
+		 List<SeoKpiStatistics> seoKpiStatisticsList = seoKpiStatisticsService.list(statisticsQueryWrapper);
+		 if (ListUtil.isEmpty(seoKpiStatisticsList)) {
+			 return Result.OK(pageList);
+		 }
+		 for (SeoKpiStatistics kpiStatistics : seoKpiStatisticsList) {
+			 if (kpiStatistics.getKpiTarget() == null || kpiStatistics.getKpiTarget() == 0) {
+				 continue;
+			 }
+			 if ("APPOINT_KEYWORDS".equals(kpiStatistics.getKpiCode())) {
+				 haveAppointKeyword = true;
+			 } else if ("LONG_TAIL_KEYWORDS".equals(kpiStatistics.getKpiCode())) {
+				 haveLongTail = true;
+			 }
+		 }
+
+		 if (!haveAppointKeyword && !haveLongTail) {
+			 return Result.OK(pageList);
+		 }
+
+		 Integer keywordType = avesApiSearchKeywordsDTO.getKeywordType();
+		 if (haveAppointKeyword && !haveLongTail) {
+			 keywordType = 1;
+		 } else if (!haveAppointKeyword && haveLongTail) {
+			 keywordType = 2;
+		 }
+
+		 // 查询关键词
+		 String column = req.getParameter("column");
+		 String order = req.getParameter("order");
+		 String buttonColumn = req.getParameter("buttonColmn");
+		 String buttonSort = req.getParameter("buttonSort");
+		 Page<SeoKeywords> page = new Page<SeoKeywords>(pageNo, pageSize);
+		 List<String> dateList = seoKeywordsService.getKeywordsLastSevenDays(userFlag,keywordType,historyId);
+		 pageList = seoKeywordsService.getKeywordList(page,
+				 avesApiSearchKeywordsDTO.getRankStart(),
+				 avesApiSearchKeywordsDTO.getRankEnd(),
+				 avesApiSearchKeywordsDTO.getKeywords(),
+				 userFlag,
+				 keywordType,
+				 avesApiSearchKeywordsDTO.getReachStandard() != null && avesApiSearchKeywordsDTO.getReachStandard() == 1,
+				 historyId,
+				 column,
+				 order,
+				 buttonColumn,
+				 buttonSort,
+				 dateList);
+
+		 // 给关键词对象增加排名信息
+		 if(pageList == null){
+			 return Result.error("未获取到关键词数据");
+		 }
+		 searchKeywordsService.addRankInfoByIntervalTimeByType(pageList.getRecords(), avesApiSearchKeywordsDTO.getKeywordType(),userFlag,historyId,dateList);
+		 log.info(pageList.toString());
+		 return Result.OK(pageList);
+	 }
+
+	/**
+	 * Excel导出
+	 * 关键词排名的导出
+	 * @param avesApiSearchKeywordsDTO 查询参数
+	 * @return 询盘列表
+	 */
+	@RequestMapping(value = "/exportExcel")
+	public void exportExcel(AvesApiSearchKeywordsDTO avesApiSearchKeywordsDTO,
+							HttpServletRequest req,
+							HttpServletResponse response) throws Exception {
+		Result res = this.queryRankInfoList(avesApiSearchKeywordsDTO, 1, 5000, req);
+		if (res == null || res.getResult() == null) {
+			return;
+		}
+		IPage<SeoKeywords> pageList = (IPage<SeoKeywords>) res.getResult();
+		if (pageList.getRecords().size() == 0) {
+			return;
+		}
+		ArrayList<String> columns = null;
+		// 表头信息
+		if (sysBaseAPI.isAdmin()) {
+			columns = new ArrayList<>(Arrays.asList("序号", "关键词", "url", "关键词类型"));
+		} else {
+			columns = new ArrayList<>(Arrays.asList("序号", "关键词", "关键词类型"));
+		}
+
+		// 将关键词信息改为可以导出的格式
+		List<Map<String, Object>> list = new ArrayList<>();
+		List<SeoKeywords> enquiryList = pageList.getRecords();
+
+		List<String> dates = pageList.getRecords().get(0).getDateList();
+		columns.addAll(dates);
+
+		boolean isAdmin = sysBaseAPI.isAdmin();
+
+		for (int i = 0; i < enquiryList.size(); i++) {
+			SeoKeywords seoKeywords = enquiryList.get(i);
+			Map<String, Object> map = new LinkedHashMap<>();
+			map.put("序号", i + 1);
+			map.put("关键词", seoKeywords.getKeywords());
+			if (isAdmin) {
+				map.put("url", seoKeywords.getPositionUrl());
+			}
+			if(seoKeywords.getKeywordType() == 1) {
+				map.put("关键词类型", "指定词");
+			} else if(seoKeywords.getKeywordType() == 2) {
+				map.put("关键词类型", "长尾词");
+			}
+			// 遍历日期
+			for (String date : dates) {
+				map.put(date,Integer.valueOf(seoKeywords.getRankInfo().get(date) == null ? "0" : seoKeywords.getRankInfo().get(date)));
+			}
+			list.add(map);
+		}
+		StringBuilder fileName = new StringBuilder();
+		fileName.append("关键词排名导出");
+
+		response.setContentType("application/vnd.ms-excel;charset=utf-8");
+		// test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
+		ServletOutputStream out = null;
+		ExcelWriter writer = ExcelUtil.getWriter(true);
+		try {
+			Sheet sheet = writer.getSheet();
+			sheet.setDefaultColumnWidth(12);
+			sheet.setDefaultRowHeightInPoints(20);
+			// 设置请求头属性
+			response.setHeader("Content-Disposition", "attachment;filename=" + new String((fileName + ".xlsx").getBytes(), StandardCharsets.ISO_8859_1));
+			out = response.getOutputStream();
+			writer.write(list, true);
+			searchKeywordsService.setSizeColumn(sheet, list.get(0).size());
+			// 写出到文件
+			writer.flush(out, true);
+			// 关闭writer,释放内存
+			writer.close();
+			// 此处记得关闭输出Servlet流
+			IoUtil.close(out);
+		} catch (IOException e) {
+			log.error(e.getMessage());
+			e.printStackTrace();
+		}
+	}
+}

+ 167 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/SeoKeywordsSerpController.java

@@ -0,0 +1,167 @@
+package org.jeecg.modules.adweb.seo.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.aspect.annotation.AutoLog;
+import org.jeecg.common.system.base.controller.JeecgController;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.modules.adweb.seo.entity.SeoKeywordsSerp;
+import org.jeecg.modules.adweb.seo.service.ISeoKeywordsSerpService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.util.Arrays;
+
+/**
+ * @Description: SEO关键词搜索排名 @Author: jeecg-boot @Date: 2024-10-15 @Version: V1.0
+ */
+@Tag(name = "SEO关键词搜索排名")
+@RestController
+@RequestMapping("/serp/seoKeywordsSerp")
+@Slf4j
+public class SeoKeywordsSerpController
+        extends JeecgController<SeoKeywordsSerp, ISeoKeywordsSerpService> {
+    @Autowired private ISeoKeywordsSerpService seoKeywordsSerpService;
+
+    /**
+     * 分页列表查询
+     *
+     * @param seoKeywordsSerp
+     * @param pageNo
+     * @param pageSize
+     * @param req
+     * @return
+     */
+    // @AutoLog(value = "SEO关键词搜索排名-分页列表查询")
+    @Operation(summary = "SEO关键词搜索排名-分页列表查询")
+    @GetMapping(value = "/list")
+    public Result<IPage<SeoKeywordsSerp>> queryPageList(
+            SeoKeywordsSerp seoKeywordsSerp,
+            @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
+            @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
+            HttpServletRequest req) {
+        QueryWrapper<SeoKeywordsSerp> queryWrapper =
+                QueryGenerator.initQueryWrapper(seoKeywordsSerp, req.getParameterMap());
+        Page<SeoKeywordsSerp> page = new Page<SeoKeywordsSerp>(pageNo, pageSize);
+        IPage<SeoKeywordsSerp> pageList = seoKeywordsSerpService.page(page, queryWrapper);
+        return Result.OK(pageList);
+    }
+
+    /**
+     * 添加
+     *
+     * @param seoKeywordsSerp
+     * @return
+     */
+    @AutoLog(value = "SEO关键词搜索排名-添加")
+    @Operation(summary = "SEO关键词搜索排名-添加")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords_serp:add')")
+    @PostMapping(value = "/add")
+    public Result<String> add(@RequestBody SeoKeywordsSerp seoKeywordsSerp) {
+        seoKeywordsSerpService.save(seoKeywordsSerp);
+        return Result.OK("添加成功!");
+    }
+
+    /**
+     * 编辑
+     *
+     * @param seoKeywordsSerp
+     * @return
+     */
+    @AutoLog(value = "SEO关键词搜索排名-编辑")
+    @Operation(summary = "SEO关键词搜索排名-编辑")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords_serp:edit')")
+    @RequestMapping(
+            value = "/edit",
+            method = {RequestMethod.PUT, RequestMethod.POST})
+    public Result<String> edit(@RequestBody SeoKeywordsSerp seoKeywordsSerp) {
+        seoKeywordsSerpService.updateById(seoKeywordsSerp);
+        return Result.OK("编辑成功!");
+    }
+
+    /**
+     * 通过id删除
+     *
+     * @param id
+     * @return
+     */
+    @AutoLog(value = "SEO关键词搜索排名-通过id删除")
+    @Operation(summary = "SEO关键词搜索排名-通过id删除")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords_serp:delete')")
+    @DeleteMapping(value = "/delete")
+    public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
+        seoKeywordsSerpService.removeById(id);
+        return Result.OK("删除成功!");
+    }
+
+    /**
+     * 批量删除
+     *
+     * @param ids
+     * @return
+     */
+    @AutoLog(value = "SEO关键词搜索排名-批量删除")
+    @Operation(summary = "SEO关键词搜索排名-批量删除")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords_serp:deleteBatch')")
+    @DeleteMapping(value = "/deleteBatch")
+    public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
+        this.seoKeywordsSerpService.removeByIds(Arrays.asList(ids.split(",")));
+        return Result.OK("批量删除成功!");
+    }
+
+    /**
+     * 通过id查询
+     *
+     * @param id
+     * @return
+     */
+    // @AutoLog(value = "SEO关键词搜索排名-通过id查询")
+    @Operation(summary = "SEO关键词搜索排名-通过id查询")
+    @GetMapping(value = "/queryById")
+    public Result<SeoKeywordsSerp> queryById(
+            @RequestParam(name = "id", required = true) String id) {
+        SeoKeywordsSerp seoKeywordsSerp = seoKeywordsSerpService.getById(id);
+        if (seoKeywordsSerp == null) {
+            return Result.error("未找到对应数据");
+        }
+        return Result.OK(seoKeywordsSerp);
+    }
+
+    /**
+     * 导出excel
+     *
+     * @param request
+     * @param seoKeywordsSerp
+     */
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords_serp:exportXls')")
+    @RequestMapping(value = "/exportXls")
+    public ModelAndView exportXls(HttpServletRequest request, SeoKeywordsSerp seoKeywordsSerp) {
+        return super.exportXls(request, seoKeywordsSerp, SeoKeywordsSerp.class, "SEO关键词搜索排名");
+    }
+
+    /**
+     * 通过excel导入数据
+     *
+     * @param request
+     * @param response
+     * @return
+     */
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_keywords_serp:importExcel')")
+    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
+    public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
+        return super.importExcel(request, response, SeoKeywordsSerp.class);
+    }
+}

+ 197 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/SeoKpiStatisticsController.java

@@ -0,0 +1,197 @@
+package org.jeecg.modules.adweb.seo.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.aspect.annotation.AutoLog;
+import org.jeecg.common.system.base.controller.JeecgController;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.modules.adweb.seo.entity.ComprehensiveStatistics;
+import org.jeecg.modules.adweb.seo.entity.SeoKpiStatistics;
+import org.jeecg.modules.adweb.seo.service.ISearchKeywordsService;
+import org.jeecg.modules.adweb.seo.service.ISeoKpiStatisticsService;
+import org.jeecg.modules.adweb.site.entity.AdwebSite;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * @Description: seo_kpi_statistics @Author: jeecg-boot @Date: 2024-10-09 @Version: V1.0
+ */
+@Tag(name = "seo_kpi_statistics")
+@RestController
+@RequestMapping("/serp/seoKpiStatistics")
+@Slf4j
+public class SeoKpiStatisticsController
+        extends JeecgController<SeoKpiStatistics, ISeoKpiStatisticsService> {
+    @Autowired private ISeoKpiStatisticsService seoKpiStatisticsService;
+
+    @Autowired private ISearchKeywordsService searchKeywordsService;
+    /**
+     * 分页列表查询
+     *
+     * @param seoKpiStatistics
+     * @param pageNo
+     * @param pageSize
+     * @param req
+     * @return
+     */
+    @AutoLog(value = "seo_kpi_statistics-分页列表查询")
+    @Operation(summary = "seo_kpi_statistics-分页列表查询")
+    @GetMapping(value = "/list")
+    public Result<IPage<SeoKpiStatistics>> queryPageList(
+            SeoKpiStatistics seoKpiStatistics,
+            @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
+            @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
+            HttpServletRequest req) {
+        QueryWrapper<SeoKpiStatistics> queryWrapper =
+                QueryGenerator.initQueryWrapper(seoKpiStatistics, req.getParameterMap());
+        Page<SeoKpiStatistics> page = new Page<SeoKpiStatistics>(pageNo, pageSize);
+        IPage<SeoKpiStatistics> pageList = seoKpiStatisticsService.page(page, queryWrapper);
+        return Result.OK(pageList);
+    }
+
+    /**
+     * 添加
+     *
+     * @param seoKpiStatistics
+     * @return
+     */
+    @AutoLog(value = "seo_kpi_statistics-添加")
+    @Operation(summary = "seo_kpi_statistics-添加")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_kpi_statistics:add')")
+    @PostMapping(value = "/add")
+    public Result<String> add(@RequestBody SeoKpiStatistics seoKpiStatistics) {
+        seoKpiStatisticsService.save(seoKpiStatistics);
+        return Result.OK("添加成功!");
+    }
+
+    /**
+     * 编辑
+     *
+     * @param seoKpiStatistics
+     * @return
+     */
+    @AutoLog(value = "seo_kpi_statistics-编辑")
+    @Operation(summary = "seo_kpi_statistics-编辑")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_kpi_statistics:edit')")
+    @RequestMapping(
+            value = "/edit",
+            method = {RequestMethod.PUT, RequestMethod.POST})
+    public Result<String> edit(@RequestBody SeoKpiStatistics seoKpiStatistics) {
+        seoKpiStatisticsService.updateById(seoKpiStatistics);
+        return Result.OK("编辑成功!");
+    }
+
+    /**
+     * 通过id删除
+     *
+     * @param id
+     * @return
+     */
+    @AutoLog(value = "seo_kpi_statistics-通过id删除")
+    @Operation(summary = "seo_kpi_statistics-通过id删除")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_kpi_statistics:delete')")
+    @DeleteMapping(value = "/delete")
+    public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
+        seoKpiStatisticsService.removeById(id);
+        return Result.OK("删除成功!");
+    }
+
+    /**
+     * 批量删除
+     *
+     * @param ids
+     * @return
+     */
+    @AutoLog(value = "seo_kpi_statistics-批量删除")
+    @Operation(summary = "seo_kpi_statistics-批量删除")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_kpi_statistics:deleteBatch')")
+    @DeleteMapping(value = "/deleteBatch")
+    public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
+        this.seoKpiStatisticsService.removeByIds(Arrays.asList(ids.split(",")));
+        return Result.OK("批量删除成功!");
+    }
+
+    /**
+     * 通过id查询
+     *
+     * @param id
+     * @return
+     */
+    @AutoLog(value = "seo_kpi_statistics-通过id查询")
+    @Operation(summary = "seo_kpi_statistics-通过id查询")
+    @GetMapping(value = "/queryById")
+    public Result<SeoKpiStatistics> queryById(
+            @RequestParam(name = "id", required = true) String id) {
+        SeoKpiStatistics seoKpiStatistics = seoKpiStatisticsService.getById(id);
+        if (seoKpiStatistics == null) {
+            return Result.error("未找到对应数据");
+        }
+        return Result.OK(seoKpiStatistics);
+    }
+
+    /**
+     * 导出excel
+     *
+     * @param request
+     * @param seoKpiStatistics
+     */
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_kpi_statistics:exportXls')")
+    @RequestMapping(value = "/exportXls")
+    public ModelAndView exportXls(HttpServletRequest request, SeoKpiStatistics seoKpiStatistics) {
+        return super.exportXls(
+                request, seoKpiStatistics, SeoKpiStatistics.class, "seo_kpi_statistics");
+    }
+
+    /**
+     * 通过excel导入数据
+     *
+     * @param request
+     * @param response
+     * @return
+     */
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_kpi_statistics:importExcel')")
+    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
+    public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
+        return super.importExcel(request, response, SeoKpiStatistics.class);
+    }
+
+    /** 获取当前用户管理的 父 站点信息 @CopyFrom: getAllSites */
+    @GetMapping("/getAllSitesBySeo")
+    public Result<List<AdwebSite>> getAllSitesBySeo() {
+        final List<AdwebSite> allSites = seoKpiStatisticsService.getAllSites();
+        return Result.OK(allSites);
+    }
+
+    /**
+     * 获得综合统计信息
+     */
+    @GetMapping(value = "/comprehensiveInfo")
+    public Result<?> getComprehensiveInfo(@RequestParam String userFlag, @RequestParam String historyId) throws ParseException {
+        ComprehensiveStatistics statistics = searchKeywordsService.getComprehensiveInfo(userFlag, historyId);
+        return Result.OK(statistics);
+    }
+
+    /**
+     * 获取关键字排名信息
+     */
+    @GetMapping(value = "/getRankInfo")
+    public Result<?> getRankInfo(@RequestParam("siteCode") String siteCode, @RequestParam("historyId") String historyId) throws ParseException {
+        Map map = searchKeywordsService.getRankInfo(siteCode, historyId);
+        return Result.OK(map);
+    }
+}

+ 164 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/controller/SeoPlanSubscriptionController.java

@@ -0,0 +1,164 @@
+package org.jeecg.modules.adweb.seo.controller;
+
+import java.util.Arrays;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.modules.adweb.seo.entity.SeoPlanSubscription;
+import org.jeecg.modules.adweb.seo.service.ISeoPlanSubscriptionService;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.extern.slf4j.Slf4j;
+
+import org.jeecg.common.system.base.controller.JeecgController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.jeecg.common.aspect.annotation.AutoLog;
+import org.springframework.security.access.prepost.PreAuthorize;
+
+ /**
+ * @Description: seo套餐订购
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+@Tag(name="seo套餐订购")
+@RestController
+@RequestMapping("/serp/seoPlanSubscription")
+@Slf4j
+public class SeoPlanSubscriptionController extends JeecgController<SeoPlanSubscription, ISeoPlanSubscriptionService> {
+	@Autowired
+	private ISeoPlanSubscriptionService seoPlanSubscriptionService;
+	
+	/**
+	 * 分页列表查询
+	 *
+	 * @param seoPlanSubscription
+	 * @param pageNo
+	 * @param pageSize
+	 * @param req
+	 * @return
+	 */
+	//@AutoLog(value = "seo套餐订购-分页列表查询")
+	@Operation(summary="seo套餐订购-分页列表查询")
+	@GetMapping(value = "/list")
+	public Result<IPage<SeoPlanSubscription>> queryPageList(SeoPlanSubscription seoPlanSubscription,
+								   @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+								   @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
+								   HttpServletRequest req) {
+		QueryWrapper<SeoPlanSubscription> queryWrapper = QueryGenerator.initQueryWrapper(seoPlanSubscription, req.getParameterMap());
+		Page<SeoPlanSubscription> page = new Page<SeoPlanSubscription>(pageNo, pageSize);
+		IPage<SeoPlanSubscription> pageList = seoPlanSubscriptionService.page(page, queryWrapper);
+		return Result.OK(pageList);
+	}
+	
+	/**
+	 *   添加
+	 *
+	 * @param seoPlanSubscription
+	 * @return
+	 */
+	@AutoLog(value = "seo套餐订购-添加")
+	@Operation(summary="seo套餐订购-添加")
+	@PreAuthorize("@jps.requiresPermissions('serp:seo_plan_subscription:add')")
+	@PostMapping(value = "/add")
+	public Result<String> add(@RequestBody SeoPlanSubscription seoPlanSubscription) {
+		seoPlanSubscriptionService.save(seoPlanSubscription);
+		return Result.OK("添加成功!");
+	}
+	
+	/**
+	 *  编辑
+	 *
+	 * @param seoPlanSubscription
+	 * @return
+	 */
+	@AutoLog(value = "seo套餐订购-编辑")
+	@Operation(summary="seo套餐订购-编辑")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_plan_subscription:edit')")
+	@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
+	public Result<String> edit(@RequestBody SeoPlanSubscription seoPlanSubscription) {
+		seoPlanSubscriptionService.updateById(seoPlanSubscription);
+		return Result.OK("编辑成功!");
+	}
+	
+	/**
+	 *   通过id删除
+	 *
+	 * @param id
+	 * @return
+	 */
+	@AutoLog(value = "seo套餐订购-通过id删除")
+	@Operation(summary="seo套餐订购-通过id删除")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_plan_subscription:delete')")
+	@DeleteMapping(value = "/delete")
+	public Result<String> delete(@RequestParam(name="id",required=true) String id) {
+		seoPlanSubscriptionService.removeById(id);
+		return Result.OK("删除成功!");
+	}
+	
+	/**
+	 *  批量删除
+	 *
+	 * @param ids
+	 * @return
+	 */
+	@AutoLog(value = "seo套餐订购-批量删除")
+	@Operation(summary="seo套餐订购-批量删除")
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_plan_subscription:deleteBatch')")
+	@DeleteMapping(value = "/deleteBatch")
+	public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
+		this.seoPlanSubscriptionService.removeByIds(Arrays.asList(ids.split(",")));
+		return Result.OK("批量删除成功!");
+	}
+	
+	/**
+	 * 通过id查询
+	 *
+	 * @param id
+	 * @return
+	 */
+	//@AutoLog(value = "seo套餐订购-通过id查询")
+	@Operation(summary="seo套餐订购-通过id查询")
+	@GetMapping(value = "/queryById")
+	public Result<SeoPlanSubscription> queryById(@RequestParam(name="id",required=true) String id) {
+		SeoPlanSubscription seoPlanSubscription = seoPlanSubscriptionService.getById(id);
+		if(seoPlanSubscription==null) {
+			return Result.error("未找到对应数据");
+		}
+		return Result.OK(seoPlanSubscription);
+	}
+
+    /**
+    * 导出excel
+    *
+    * @param request
+    * @param seoPlanSubscription
+    */
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_plan_subscription:exportXls')")
+    @RequestMapping(value = "/exportXls")
+    public ModelAndView exportXls(HttpServletRequest request, SeoPlanSubscription seoPlanSubscription) {
+        return super.exportXls(request, seoPlanSubscription, SeoPlanSubscription.class, "seo套餐订购");
+    }
+
+    /**
+      * 通过excel导入数据
+    *
+    * @param request
+    * @param response
+    * @return
+    */
+    @PreAuthorize("@jps.requiresPermissions('serp:seo_plan_subscription:importExcel')")
+    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
+    public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
+        return super.importExcel(request, response, SeoPlanSubscription.class);
+    }
+
+}

+ 28 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/dto/AvesApiSearchKeywordsDTO.java

@@ -0,0 +1,28 @@
+package org.jeecg.modules.adweb.seo.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * @author Zenas
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AvesApiSearchKeywordsDTO {
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private Date startTime;
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private Date endTime;
+    private Integer keywordType;
+    private String keywords;
+    private Integer rankStart;
+    private Integer rankEnd;
+    private Integer reachStandard;
+    private String userFlag;
+    private String historyId;
+}

+ 46 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/ComprehensiveStatistics.java

@@ -0,0 +1,46 @@
+package org.jeecg.modules.adweb.seo.entity;
+
+import lombok.Data;
+
+/**
+ * @author Zenas
+ * 综合统计类
+ */
+
+@Data
+public class ComprehensiveStatistics {
+    /**
+     * 指定词
+     */
+    private String appointKeywordNum;
+
+    /**
+     * 长尾词
+     */
+    private String longTailKeywordNum;
+
+    /**
+     * 外链数
+     */
+    private String outerLinkNum;
+
+    /**
+     * 文章数
+     */
+    private String articleNum;
+
+    /**
+     * 询盘数
+     */
+    private String enquiryNum;
+
+    /**
+     * 访问量(UV)
+     */
+    private String uvNum;
+
+    /**
+     * 是否展示访问量
+     */
+    private boolean showUvNum;
+}

+ 59 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/HistoryReachStandardSite.java

@@ -0,0 +1,59 @@
+package org.jeecg.modules.adweb.seo.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import org.jeecgframework.poi.excel.annotation.Excel;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @Description: 历史达标站点关键词达标数据表
+ * @Author: jeecg-boot
+ * @Date:   2024-10-16
+ * @Version: V1.0
+ */
+@Data
+@TableName("history_reach_standard_site")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description="历史达标站点关键词达标数据表")
+public class HistoryReachStandardSite implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+	/**主键*/
+	@TableId(type = IdType.ASSIGN_ID)
+    @Schema(description = "主键")
+    private String id;
+	/**站点code*/
+	@Excel(name = "站点code", width = 15)
+    @Schema(description = "站点code")
+    private String siteCode;
+	/**达标时间*/
+	@Excel(name = "达标时间", width = 15)
+    @Schema(description = "达标时间")
+    private String reachStandardTime;
+	/**达标天数*/
+	@Excel(name = "达标天数", width = 15)
+    @Schema(description = "达标天数")
+    private Integer reachStandardDays;
+	/**服务天数*/
+	@Excel(name = "服务天数", width = 15)
+    @Schema(description = "服务天数")
+    private Integer remainServerDays;
+	/**创建时间*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "创建时间")
+    private Date createTime;
+}

+ 162 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/SeoKeywords.java

@@ -0,0 +1,162 @@
+package org.jeecg.modules.adweb.seo.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.jeecgframework.poi.excel.annotation.Excel;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Description: 关键词
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+@Data
+@TableName("seo_keywords")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description="关键词")
+public class SeoKeywords implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+	/**关键词表主键*/
+	@TableId(type = IdType.ASSIGN_ID)
+    @Schema(description = "关键词表主键")
+    private Integer id;
+	/**平台标识*/
+	@Excel(name = "平台标识", width = 15)
+    @Schema(description = "平台标识")
+    private String appKey;
+	/**用户标识*/
+	@Excel(name = "网站code", width = 15)
+    @Schema(description = "网站code")
+    private String siteCode;
+	/**关键词*/
+	@Excel(name = "关键词", width = 15)
+    @Schema(description = "关键词")
+    private String keywords;
+	/**关键词所属域名*/
+	@Excel(name = "关键词所属域名", width = 15)
+    @Schema(description = "关键词所属域名")
+    private String domain;
+	/**语种*/
+	@Excel(name = "语种", width = 15)
+    @Schema(description = "语种")
+    private String lang;
+	/**创建时间*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "创建时间")
+    private Date createTime;
+	/**最新搜索时间*/
+	@Excel(name = "最新搜索时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "最新搜索时间")
+    private Date lastSearchTime;
+	/**定时器的最新搜索时间*/
+	@Excel(name = "定时器的最新搜索时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "定时器的最新搜索时间")
+    private Date timerLastSearchTime;
+	/**0删除,1有效,2过期*/
+	@Excel(name = "0删除,1有效,2过期", width = 15)
+    @Schema(description = "0删除,1有效,2过期")
+    private Integer status;
+	/**数据复制源ID*/
+	@Excel(name = "数据复制源ID", width = 15)
+    @Schema(description = "数据复制源ID")
+    private Integer sourceId;
+	/**关键词优先级(1:高;2:中;3:低)*/
+	@Excel(name = "关键词优先级(1:高;2:中;3:低)", width = 15)
+    @Schema(description = "关键词优先级(1:高;2:中;3:低)")
+    private Integer priority;
+	/**SEO关键词优化完成时间*/
+	@Excel(name = "SEO关键词优化完成时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "SEO关键词优化完成时间")
+    private Date finishTime;
+	/**优化进度(默认0)*/
+	@Excel(name = "优化进度(默认0)", width = 15)
+    @Schema(description = "优化进度(默认0)")
+    private Integer optimizeProcess;
+	/**优化状态(0:未完成;1:已完成)*/
+	@Excel(name = "优化状态(0:未完成;1:已完成)", width = 15)
+    @Schema(description = "优化状态(0:未完成;1:已完成)")
+    private Integer optimizeStatus;
+	/**关键词类型(1:指定;2:长尾)*/
+	@Excel(name = "关键词类型(1:指定;2:长尾)", width = 15)
+    @Schema(description = "关键词类型(1:指定;2:长尾)")
+    private Integer keywordType;
+	/**最近一次搜索排名*/
+	@Excel(name = "最近一次搜索排名", width = 15)
+    @Schema(description = "最近一次搜索排名")
+    private Integer lastRank;
+	/**搜索状态,0为普通状态,1为搜索进行中状态*/
+	@Excel(name = "搜索状态,0为普通状态,1为搜索进行中状态", width = 15)
+    @Schema(description = "搜索状态,0为普通状态,1为搜索进行中状态")
+    private Integer searchStatus;
+	/**关键词所在URL*/
+	@Excel(name = "关键词所在URL", width = 15)
+    @Schema(description = "关键词所在URL")
+    private String positionUrl;
+	/**相关关键词关联的关键词的id*/
+	@Excel(name = "相关关键词关联的关键词的id", width = 15)
+    @Schema(description = "相关关键词关联的关键词的id")
+    private Integer relatedKeywordId;
+	/**关键词对应套餐的ID*/
+	@Excel(name = "关键词对应套餐的ID", width = 15)
+    @Schema(description = "关键词对应套餐的ID")
+    private String planId;
+	/**对应套餐绑定表ID*/
+	@Excel(name = "对应套餐绑定表ID", width = 15)
+    @Schema(description = "对应套餐绑定表ID")
+    private String historyId;
+    @TableField(exist = false)
+    private String ipAddress;
+    @TableField(exist = false)
+    private String latestRank;
+    @TableField(exist = false)
+    private String searchUrl;
+    @TableField(exist = false)
+    private Map<String, String> rankInfo;
+    @TableField(exist = false)
+    private Integer siteId;
+    @TableField(exist = false)
+    private String value;
+    @TableField(exist = false)
+    private String order;
+    @TableField(exist = false)
+    private String column;
+    /**
+     * 关键词排名
+     */
+    @TableField(exist = false)
+    private Integer keywordRank;
+
+    /**
+     * 关键词字符长度
+     */
+    @TableField(exist = false)
+    private Integer keywordsLength;
+
+    /**
+     * 关键词时间范围
+     */
+    @TableField(exist = false)
+    private List<String> dateList;
+}

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

@@ -0,0 +1,82 @@
+package org.jeecg.modules.adweb.seo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import org.jeecgframework.poi.excel.annotation.Excel;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @Description: SEO关键词搜索排名
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+@Data
+@TableName("seo_keywords_serp")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description="SEO关键词搜索排名")
+public class SeoKeywordsSerp implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+	/**关键词搜索排名主键ID*/
+	@Excel(name = "关键词搜索排名主键ID", width = 15)
+    @Schema(description = "关键词搜索排名主键ID")
+    private Integer serpId;
+	/**关键词ID*/
+	@Excel(name = "关键词ID", width = 15)
+    @Schema(description = "关键词ID")
+    private Integer keywordsId;
+	/**搜索地址*/
+	@Excel(name = "搜索地址", width = 15)
+    @Schema(description = "搜索地址")
+    private String searchUrl;
+	/**搜索引擎域名*/
+	@Excel(name = "搜索引擎域名", width = 15)
+    @Schema(description = "搜索引擎域名")
+    private String seDomain;
+	/**搜索语言*/
+	@Excel(name = "搜索语言", width = 15)
+    @Schema(description = "搜索语言")
+    private String languageCode;
+	/**结果类型,如:organic、image、video等*/
+	@Excel(name = "结果类型,如:organic、image、video等", width = 15)
+    @Schema(description = "结果类型,如:organic、image、video等")
+    private String type;
+	/**所在页数*/
+	@Excel(name = "所在页数", width = 15)
+    @Schema(description = "所在页数")
+    private Integer pageNumber;
+	/**结果类型分组排名*/
+	@Excel(name = "结果类型分组排名", width = 15)
+    @Schema(description = "结果类型分组排名")
+    private Integer rankType;
+	/**自然全局排名*/
+	@Excel(name = "自然全局排名", width = 15)
+    @Schema(description = "自然全局排名")
+    private Integer rankAbsolute;
+	/**搜索日期*/
+	@Excel(name = "搜索日期", width = 15)
+    @Schema(description = "搜索日期")
+    private String seDate;
+	/**搜索时间*/
+	@Excel(name = "搜索时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "搜索时间")
+    private Date seDatetime;
+
+    @TableField(exist = false)
+    private String key;
+}

+ 116 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/SeoKpiStatistics.java

@@ -0,0 +1,116 @@
+package org.jeecg.modules.adweb.seo.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.jeecgframework.poi.excel.annotation.Excel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Description: seo_kpi_statistics
+ * @Author: jeecg-boot
+ * @Date:   2024-10-09
+ * @Version: V1.0
+ */
+@Data
+@TableName("seo_kpi_statistics")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description="seo_kpi_statistics")
+public class SeoKpiStatistics implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+	/**id*/
+	@TableId(type = IdType.ASSIGN_ID)
+    @Schema(description = "id")
+    private Integer id;
+	/**站点code或营销方案code*/
+	@Excel(name = "站点code或营销方案code", width = 15)
+    @Schema(description = "站点code或营销方案code")
+    private String hostCode;
+	/**营销方案name*/
+	@Excel(name = "营销方案name", width = 15)
+    @Schema(description = "营销方案name")
+    private String planName;
+	/**0营销方案,1站点*/
+	@Excel(name = "0营销方案,1站点", width = 15)
+    @Schema(description = "0营销方案,1站点")
+    private Integer type;
+	/**指标名称*/
+	@Excel(name = "指标名称", width = 15)
+    @Schema(description = "指标名称")
+    private String kpiName;
+	/**指标code*/
+	@Excel(name = "指标code", width = 15)
+    @Schema(description = "指标code")
+    private String kpiCode;
+	/**指标目标*/
+	@Excel(name = "指标目标", width = 15)
+    @Schema(description = "指标目标")
+    private Integer kpiTarget;
+	/**已完成目标*/
+	@Excel(name = "已完成目标", width = 15)
+    @Schema(description = "已完成目标")
+    private Integer finishTarget;
+	/**0未完成、1完成*/
+	@Excel(name = "0未完成、1完成", width = 15)
+    @Schema(description = "0未完成、1完成")
+    private Integer finishStatus;
+	/**完成时间*/
+	@Excel(name = "完成时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "完成时间")
+    private Date finishTime;
+	/**完成人的id*/
+	@Excel(name = "完成人的id", width = 15)
+    @Schema(description = "完成人的id")
+    private String finishUid;
+	/**服务开始时间*/
+	@Excel(name = "服务开始时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "服务开始时间")
+    private Date startTime;
+	/**服务结束时间*/
+	@Excel(name = "服务结束时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "服务结束时间")
+    private Date endTime;
+	/**比重,总和为100*/
+	@Excel(name = "比重,总和为100", width = 15)
+    @Schema(description = "比重,总和为100")
+    private Integer proportion;
+	/**排序*/
+	@Excel(name = "排序", width = 15)
+    @Schema(description = "排序")
+    private Double sort;
+	/**此纪录创建时间*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "此纪录创建时间")
+    private Date createTime;
+	/**0未删除、1已删除*/
+	@Excel(name = "0未删除、1已删除", width = 15)
+    @Schema(description = "0未删除、1已删除")
+    @TableLogic
+    private Integer delFlag;
+	/**套餐id*/
+	@Excel(name = "套餐id", width = 15)
+    @Schema(description = "套餐id")
+    private String planId;
+	/**对应套餐绑定表ID*/
+	@Excel(name = "对应套餐绑定表ID", width = 15)
+    @Schema(description = "对应套餐绑定表ID")
+    private String historyId;
+}

+ 135 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/entity/SeoPlanSubscription.java

@@ -0,0 +1,135 @@
+package org.jeecg.modules.adweb.seo.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.jeecgframework.poi.excel.annotation.Excel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Description: seo套餐订购
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+@Data
+@TableName("seo_plan_subscription")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description="seo套餐订购")
+public class SeoPlanSubscription implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+	/**主键*/
+	@TableId(type = IdType.ASSIGN_ID)
+    @Schema(description = "主键")
+    private String id;
+	/**站点*/
+	@Excel(name = "站点", width = 15)
+    @Schema(description = "站点")
+    private String siteId;
+	/**套餐*/
+	@Excel(name = "套餐", width = 15)
+    @Schema(description = "套餐")
+    private String planId;
+	/**创建人*/
+    @Schema(description = "创建人")
+    private String createBy;
+	/**创建日期*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "创建日期")
+    private Date createTime;
+	/**状态*/
+	@Excel(name = "状态", width = 15)
+    @Schema(description = "状态")
+    private Integer status;
+	/**方案使用者ID*/
+	@Excel(name = "方案使用者ID", width = 15)
+    @Schema(description = "方案使用者ID")
+    private String usedUid;
+	/**方案类型*/
+	@Excel(name = "方案类型", width = 15)
+    @Schema(description = "方案类型")
+    private String planType;
+	/**方案价格*/
+	@Excel(name = "方案价格", width = 15)
+    @Schema(description = "方案价格")
+    private Double planPrice;
+	/**方案编码*/
+	@Excel(name = "方案编码", width = 15)
+    @Schema(description = "方案编码")
+    private String planCode;
+	/**方案名称*/
+	@Excel(name = "方案名称", width = 15)
+    @Schema(description = "方案名称")
+    private String planName;
+	/**指定关键词最早达标时间*/
+	@Excel(name = "指定关键词最早达标时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "指定关键词最早达标时间")
+    private Date keywordsAchieveTime;
+	/**是否续费*/
+	@Excel(name = "是否续费", width = 15)
+    @Schema(description = "是否续费")
+    private Integer isRenew;
+	/**是否自定义套餐开始时间*/
+	@Excel(name = "是否自定义套餐开始时间", width = 15)
+    @Schema(description = "是否自定义套餐开始时间")
+    private Integer isCustom;
+	/**套餐开启时间*/
+	@Excel(name = "套餐开启时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "套餐开启时间")
+    private Date planStartTime;
+	/**服务开启时间*/
+	@Excel(name = "服务开启时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "服务开启时间")
+    private Date serviceStartTime;
+	/**服务到期时间*/
+	@Excel(name = "服务到期时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "服务到期时间")
+    private Date serviceEndTime;
+	/**套餐详情的服务时长的按钮切换*/
+	@Excel(name = "套餐详情的服务时长的按钮切换", width = 15)
+    @Schema(description = "套餐详情的服务时长的按钮切换")
+    private Integer serviceStatus;
+	/**套餐时长(单位:月份)*/
+	@Excel(name = "套餐时长(单位:月份)", width = 15)
+    @Schema(description = "套餐时长(单位:月份)")
+    private Integer serviceMonth;
+	/**0不需要提醒,1需要提醒*/
+	@Excel(name = "0不需要提醒,1需要提醒", width = 15)
+    @Schema(description = "0不需要提醒,1需要提醒")
+    private Integer remind;
+	/**备注*/
+	@Excel(name = "备注", width = 15)
+    @Schema(description = "备注")
+    private String remark;
+	/**优化人uid*/
+	@Excel(name = "优化人uid", width = 15)
+    @Schema(description = "优化人uid")
+    private String optimizerUid;
+	/**赠送天数*/
+	@Excel(name = "赠送天数", width = 15)
+    @Schema(description = "赠送天数")
+    private Integer giveDay;
+	/**补偿天数*/
+	@Excel(name = "补偿天数", width = 15)
+    @Schema(description = "补偿天数")
+    private Integer compensateDay;
+}

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

@@ -0,0 +1,14 @@
+package org.jeecg.modules.adweb.seo.mapper;
+
+import org.jeecg.modules.adweb.seo.entity.HistoryReachStandardSite;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @Description: 历史达标站点关键词达标数据表
+ * @Author: jeecg-boot
+ * @Date:   2024-10-16
+ * @Version: V1.0
+ */
+public interface HistoryReachStandardSiteMapper extends BaseMapper<HistoryReachStandardSite> {
+
+}

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

@@ -0,0 +1,69 @@
+package org.jeecg.modules.adweb.seo.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+import org.apache.ibatis.annotations.Param;
+import org.jeecg.modules.adweb.seo.entity.SeoKeywords;
+import org.jeecg.modules.adweb.seo.vo.RankInfoVO;
+
+import java.util.List;
+
+/**
+ * @Description: 关键词
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+public interface SeoKeywordsMapper extends BaseMapper<SeoKeywords> {
+    /**
+     * 根据站点和套餐id
+     * @param siteCode
+     * @param subscriptionId
+     * @return
+     */
+    List<String> getKeywordsLastSevenDays(String siteCode, String subscriptionId, Integer keywordType);
+
+    /**
+     * 查询关键词排名数据
+     *
+     * @param siteCode siteCode
+     * @return 当日排名在前n页的数量
+     */
+    RankInfoVO queryKeywordNumByRankRange(@Param("siteCode") String siteCode, @Param("subscriptionId") String subscriptionId, @Param("rankStart") Integer rankStart, @Param("rankEnd") Integer rankEnd, @Param("dateStr") String dateStr, @Param("keywordType") String keywordType);
+
+    /**
+     * 根据站点code获取关键词达标标准
+     *
+     * @param siteCode 站点code
+     * @return 达标标准
+     */
+    int getKeywordStandard(String siteCode);
+
+    /**
+     * 获取关键词列表
+     *
+     * @return 关键词列表
+     */
+    IPage<SeoKeywords> getKeywordList(IPage<SeoKeywords> page,
+                                      Integer rankStart,
+                                      Integer rankEnd,
+                                      boolean isCustomer,
+                                      String keyword,
+                                      String siteCode,
+                                      Integer keywordType,
+                                      String datesStr,
+                                      String subscriptionId,
+                                      String column,
+                                      String order,
+                                      String buttonColumn,
+                                      String buttonSort);
+
+    /**
+     * 获取当日需要DateForSEO serp查询的关键词列表
+     *
+     * @return 关键词列表
+     */
+    List<SeoKeywords> getKeywordsToSerp(int keywordType);
+
+}

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

@@ -0,0 +1,16 @@
+package org.jeecg.modules.adweb.seo.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import org.jeecg.modules.adweb.seo.entity.SeoKeywordsSerp;
+
+/**
+ * @Description: SEO关键词搜索排名
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+public interface SeoKeywordsSerpMapper extends BaseMapper<SeoKeywordsSerp> {
+
+}

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

@@ -0,0 +1,14 @@
+package org.jeecg.modules.adweb.seo.mapper;
+
+import org.jeecg.modules.adweb.seo.entity.SeoKpiStatistics;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @Description: seo_kpi_statistics
+ * @Author: jeecg-boot
+ * @Date:   2024-10-09
+ * @Version: V1.0
+ */
+public interface SeoKpiStatisticsMapper extends BaseMapper<SeoKpiStatistics> {
+
+}

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

@@ -0,0 +1,22 @@
+package org.jeecg.modules.adweb.seo.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import org.jeecg.modules.adweb.seo.entity.SeoPlanSubscription;
+
+/**
+ * @Description: seo套餐订购
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+public interface SeoPlanSubscriptionMapper extends BaseMapper<SeoPlanSubscription> {
+    /**
+     * 根据站点code获取关键词达标标准
+     *
+     * @param siteCode 站点code
+     * @return 达标标准
+     */
+    int getKeywordStandard(String siteCode);
+}

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

@@ -0,0 +1,5 @@
+<?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="org.jeecg.modules.adweb.seo.mapper.HistoryReachStandardSiteMapper">
+
+</mapper>

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

@@ -0,0 +1,184 @@
+<?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="org.jeecg.modules.adweb.seo.mapper.SeoKeywordsMapper">
+    <select id="getKeywordsLastSevenDays" resultType="java.lang.String">
+        SELECT t1.se_date
+        FROM seo_keywords_serp t1,
+             seo_keywords t2
+        WHERE t2.`status` = 1
+          AND t1.keywords_id = t2.id
+          AND t2.app_key = 'adweb'
+          AND t2.site_code = #{siteCode}
+          AND t2.keyword_type = #{keywordType}
+          AND t2.subscription_id = #{subscriptionId}
+        GROUP BY t1.se_date
+        ORDER BY t1.se_date DESC
+        LIMIT 7
+    </select>
+    <select id="queryKeywordNumByRankRange" resultType="org.jeecg.modules.adweb.seo.vo.RankInfoVO">
+        SELECT
+        t.type keywordType,
+        count(t.id) keywordNum
+        FROM
+        (
+        SELECT
+        t1.id,
+        t1.keyword_type type
+        FROM
+        seo_keywords t1
+        WHERE
+        t1.site_code = #{siteCode}
+        AND `status` = 1
+        AND keyword_type = #{keywordType}
+        AND subscription_id = #{subscriptionId}
+        <if test="rankStart != null">
+            AND t1.last_rank >= #{rankStart}
+        </if>
+        <if test="rankEnd != null">
+            AND #{rankEnd} >= t1.last_rank
+        </if>
+        AND t1.app_key = 'adweb'
+        AND
+        (
+        id IN
+        (
+        SELECT
+        t3.keywords_id
+        FROM
+        (
+        SELECT
+        keywords_id,MAX(t1.rank_absolute) max_rank
+        FROM
+        seo_keywords_serp t1,
+        seo_keywords t2
+        WHERE
+        t2.`status` = 1
+        AND t1.keywords_id = t2.id
+        AND t2.app_key = 'adweb'
+        <if test="siteCode != null and siteCode != ''">
+            AND t2.site_code = #{siteCode}
+        </if>
+        <if test="keywordType != null">
+            AND t2.keyword_type = #{keywordType}
+        </if>
+        <if test="!subscriptionId">
+            AND t2.subscription_id = #{subscriptionId}
+        </if>
+        AND se_date IN ${dateStr}
+        GROUP BY keywords_id
+        ) t3
+        WHERE t3.max_rank <![CDATA[ <>]]> 0
+        )
+        )
+        ) t
+        GROUP BY
+        t.type
+    </select>
+    <select id="getKeywordStandard" resultType="java.lang.Integer">
+        SELECT
+            IF(COUNT(*) = 0 OR t1.target IS NULL, 10, t1.target)
+        FROM
+            seo_market_plan t1,
+            seo_plan_subscription t2,
+            adweb_site t3
+        WHERE
+            t2.`status` = 1
+          AND t1.id = t2.plan_id
+          AND t2.site_id = t3.id
+          AND t3.`code` = #{siteCode}
+    </select>
+    <select id="getKeywordList" resultType="org.jeecg.modules.adweb.seo.entity.SeoKeywords">
+        SELECT
+        DISTINCT t1.*,CHAR_LENGTH( keywords ) keywordsLength
+        FROM
+        seo_keywords t1
+        <where>
+
+            <if test="keyword != null and keyword != ''">
+                AND t1.keywords LIKE CONCAT('%', #{keyword}, '%')
+            </if>
+            <if test="siteCode != null and siteCode != ''">
+                AND t1.site_code = #{siteCode}
+            </if>
+            <if test="keywordType != null">
+                AND t1.keyword_type = #{keywordType}
+            </if>
+            <if test="rankStart != null">
+                AND t1.last_rank >= #{rankStart}
+            </if>
+            <if test="rankEnd != null">
+                AND #{rankEnd} >= t1.last_rank
+            </if>
+            AND t1.app_key = 'adweb'
+            AND t1.`status` =  1
+            <if test="!subscriptionId">
+                AND t1.subscription_id = #{subscriptionId}
+            </if>
+            AND
+            (
+            id IN
+            (
+            SELECT
+            t3.keywords_id
+            FROM
+            (
+            SELECT
+            keywords_id,MAX(t1.rank_absolute) max_rank
+            FROM
+            seo_keywords_serp t1,
+            seo_keywords t2
+            WHERE
+            t2.`status` = 1
+            AND t1.keywords_id = t2.id
+            AND t2.app_key = 'adweb'
+            <if test="siteCode != null and siteCode != ''">
+                AND t2.site_code = #{siteCode}
+            </if>
+            <if test="keywordType != null">
+                AND t2.keyword_type = #{keywordType}
+            </if>
+            <if test="!subscriptionId">
+                AND t2.subscription_id = #{subscriptionId}
+            </if>
+            AND se_date IN ${datesStr}
+            GROUP BY keywords_id
+            ) t3
+            WHERE t3.max_rank <![CDATA[ <>]]> 0
+            )
+            <if test="!isCustomer">
+                OR t1.`status` = 1
+            </if>
+            )
+        </where>
+        ORDER BY
+        last_rank + IF(t1.status = 0, 10000, 0) + IF(t1.last_rank = 0, 1000, 0)+ IF(t1.keyword_type = 2, 100, 0)
+        <if test="buttonColumn != null and buttonColumn == 'ranking'">
+            ${buttonSort}
+        </if>
+        <if test="buttonColumn != null and buttonColumn == 'words'">
+            ,CHAR_LENGTH(keywords) ${buttonSort}
+        </if>
+        ,create_time
+    </select>
+
+    <select id="getKeywordsToSerp" resultType="org.jeecg.modules.adweb.seo.entity.SeoKeywords">
+        SELECT
+        *
+        FROM
+        seo_keywords
+        WHERE
+        `status` = 1
+        AND site_code IN (
+        SELECT
+        `code`
+        FROM
+        adweb_site
+        WHERE
+        run_status = 1
+        AND `status` = 1
+        )
+        <if test = "keywordType != null">
+            AND keyword_type = #{keywordType}
+        </if>
+    </select>
+</mapper>

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

@@ -0,0 +1,5 @@
+<?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="org.jeecg.modules.adweb.seo.mapper.SeoKeywordsSerpMapper">
+
+</mapper>

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

@@ -0,0 +1,5 @@
+<?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="org.jeecg.modules.adweb.seo.mapper.SeoKpiStatisticsMapper">
+
+</mapper>

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

@@ -0,0 +1,18 @@
+<?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="org.jeecg.modules.adweb.seo.mapper.SeoPlanSubscriptionMapper">
+    <select id="getKeywordStandard" resultType="java.lang.Integer">
+        SELECT
+            IF(COUNT(*) = 0 OR t1.target IS NULL, 10, t1.target)
+        FROM
+            subscribe_plan t1,
+            seo_plan_subscription t2,
+            adweb_site t3
+        WHERE
+            t2.`status` = 1
+          AND t1.id = t2.plan_id
+          AND t2.site_id = t3.id
+          AND t3.`code` = #{siteCode}
+        GROUP BY t1.target
+    </select>
+</mapper>

+ 14 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/IHistoryReachStandardSiteService.java

@@ -0,0 +1,14 @@
+package org.jeecg.modules.adweb.seo.service;
+
+import org.jeecg.modules.adweb.seo.entity.HistoryReachStandardSite;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * @Description: 历史达标站点关键词达标数据表
+ * @Author: jeecg-boot
+ * @Date:   2024-10-16
+ * @Version: V1.0
+ */
+public interface IHistoryReachStandardSiteService extends IService<HistoryReachStandardSite> {
+
+}

+ 26 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISearchKeywordsService.java

@@ -0,0 +1,26 @@
+package org.jeecg.modules.adweb.seo.service;
+
+import org.apache.poi.ss.usermodel.Sheet;
+import org.jeecg.modules.adweb.seo.entity.ComprehensiveStatistics;
+import org.jeecg.modules.adweb.seo.entity.SeoKeywords;
+import org.jeecg.modules.adweb.seo.vo.SeoRankInfoVO;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Zenas
+ */
+public interface ISearchKeywordsService {
+
+    ComprehensiveStatistics getComprehensiveInfo(String siteCode, String historyId) throws ParseException;
+
+    Map getRankInfo(String siteCode, String historyId) throws ParseException;
+
+    List<SeoRankInfoVO> getSeoRankInfo(String siteCode, String historyId) throws ParseException;
+
+    void addRankInfoByIntervalTimeByType(List<SeoKeywords> keywordList, Integer keywordType, String siteCode, String historyId, List<String> dateList) throws Exception;
+
+    void setSizeColumn(Sheet sheet, int size);
+}

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

@@ -0,0 +1,14 @@
+package org.jeecg.modules.adweb.seo.service;
+
+import org.jeecg.modules.adweb.seo.entity.SeoKeywordsSerp;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * @Description: SEO关键词搜索排名
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+public interface ISeoKeywordsSerpService extends IService<SeoKeywordsSerp> {
+
+}

+ 37 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISeoKeywordsService.java

@@ -0,0 +1,37 @@
+package org.jeecg.modules.adweb.seo.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import org.jeecg.modules.adweb.seo.entity.SeoKeywords;
+import org.jeecg.modules.adweb.seo.vo.RankInfoVO;
+
+import java.text.ParseException;
+import java.util.List;
+
+/**
+ * @Description: 关键词
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+public interface ISeoKeywordsService extends IService<SeoKeywords> {
+
+    List<String> getKeywordsLastSevenDays(String userFlag, Integer keywordType, String historyId);
+
+    RankInfoVO getSeoRankInfo(String siteCode, String historyId, Integer rankStart, Integer rankEnd, String dateStr, String keywordType);
+
+    IPage<SeoKeywords> getKeywordList(IPage<SeoKeywords> page,
+                                      Integer rankStart,
+                                      Integer rankEnd,
+                                      String keyword,
+                                      String siteCode,
+                                      Integer keywordType,
+                                      boolean reachStandard,
+                                      String historyId,
+                                      String column,
+                                      String order,
+                                      String buttonColumn,
+                                      String buttonSort,
+                                      List<String> dateList) throws ParseException;
+}

+ 18 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISeoKpiStatisticsService.java

@@ -0,0 +1,18 @@
+package org.jeecg.modules.adweb.seo.service;
+
+import org.jeecg.modules.adweb.seo.entity.SeoKpiStatistics;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.jeecg.modules.adweb.site.entity.AdwebSite;
+
+import java.util.List;
+
+/**
+ * @Description: seo_kpi_statistics
+ * @Author: jeecg-boot
+ * @Date:   2024-10-09
+ * @Version: V1.0
+ */
+public interface ISeoKpiStatisticsService extends IService<SeoKpiStatistics> {
+
+    List<AdwebSite> getAllSites();
+}

+ 14 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/ISeoPlanSubscriptionService.java

@@ -0,0 +1,14 @@
+package org.jeecg.modules.adweb.seo.service;
+
+import org.jeecg.modules.adweb.seo.entity.SeoPlanSubscription;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * @Description: seo套餐订购
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+public interface ISeoPlanSubscriptionService extends IService<SeoPlanSubscription> {
+
+}

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

@@ -0,0 +1,68 @@
+package org.jeecg.modules.adweb.seo.service.dataforseo;
+
+import io.github.dataforseo.client.ApiClient;
+import io.github.dataforseo.client.api.SerpApi;
+import io.github.dataforseo.client.auth.HttpBasicAuth;
+
+import jakarta.annotation.PostConstruct;
+
+import org.jeecg.modules.adweb.seo.entity.SeoKeywordsSerp;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author wfansh
+ */
+@Service
+public class DataForSEOService {
+
+    @Value("${dataforseo.username}")
+    private String username;
+
+    @Value("${dataforseo.password}")
+    private String password;
+
+    @Value("${dataforseo.api-path}")
+    private String apiPath;
+
+    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认证方式 - basicAuth
+        HttpBasicAuth basicAuth = (HttpBasicAuth) defaultClient.getAuthentication("basicAuth");
+        basicAuth.setUsername(username);
+        basicAuth.setPassword(password);
+
+        this.serpApi = new SerpApi(defaultClient);
+    }
+
+    /**
+     * 从DataForSEO拉取keywords serp数据,同步到{@link SeoKeywordsSerp}表
+     *
+     * @param keywordType 1 - 指定词; 2 - 长尾词
+     */
+    public void syncKeywordsSerp(int keywordType) {
+        // 1. 查询待更新keywords
+
+
+//        // TODO: 判断网站状态
+//        List<GoogleGTM> googleGTMS = googleGTMService.list();
+//
+//        for (GoogleGTM googleGTM : googleGTMS) {
+//            // 每个帐号同步更新三张报表
+//            try {
+//                this.syncGACountryReport(googleGTM);
+//                this.syncGASourceMediumReport(googleGTM);
+//                this.syncGAPagePathReport(googleGTM);
+//            } catch (RuntimeException e) {
+//                log.warn("同步GA报表异常, siteId = {], error = {}", googleGTM.getSiteId(), e);
+//            }
+//        }
+    }
+}

+ 19 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/HistoryReachStandardSiteServiceImpl.java

@@ -0,0 +1,19 @@
+package org.jeecg.modules.adweb.seo.service.impl;
+
+import org.jeecg.modules.adweb.seo.entity.HistoryReachStandardSite;
+import org.jeecg.modules.adweb.seo.mapper.HistoryReachStandardSiteMapper;
+import org.jeecg.modules.adweb.seo.service.IHistoryReachStandardSiteService;
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+/**
+ * @Description: 历史达标站点关键词达标数据表
+ * @Author: jeecg-boot
+ * @Date:   2024-10-16
+ * @Version: V1.0
+ */
+@Service
+public class HistoryReachStandardSiteServiceImpl extends ServiceImpl<HistoryReachStandardSiteMapper, HistoryReachStandardSite> implements IHistoryReachStandardSiteService {
+
+}

+ 471 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SearchKeywordsServiceImpl.java

@@ -0,0 +1,471 @@
+package org.jeecg.modules.adweb.seo.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.xkcoding.http.util.StringUtil;
+
+import jakarta.annotation.Resource;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.jeecg.common.util.FastJsonUtil;
+import org.jeecg.modules.adweb.common.util.DateUtil;
+import org.jeecg.modules.adweb.common.util.ListUtil;
+import org.jeecg.modules.adweb.seo.entity.*;
+import org.jeecg.modules.adweb.seo.mapper.SeoKeywordsMapper;
+import org.jeecg.modules.adweb.seo.mapper.SeoPlanSubscriptionMapper;
+import org.jeecg.modules.adweb.seo.service.*;
+import org.jeecg.modules.adweb.seo.vo.RankInfoVO;
+import org.jeecg.modules.adweb.seo.vo.SeoRankInfoVO;
+import org.jeecg.modules.adweb.seo.vo.ServerTimeVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author Zenas
+ */
+@Slf4j
+@Service
+public class SearchKeywordsServiceImpl implements ISearchKeywordsService {
+
+    @Autowired private ISeoKpiStatisticsService seoKpiStatisticsService;
+
+    @Resource private SeoPlanSubscriptionMapper seoPlanSubscriptionMapper;
+
+    @Resource private SeoKeywordsMapper seoKeywordsMapper;
+
+    @Autowired private ISeoKeywordsService seoKeywordsService;
+
+    @Autowired private ISeoKeywordsSerpService seoKeywordsSerpService;
+
+    @Autowired private ISeoPlanSubscriptionService seoPlanSubscriptionService;
+
+    @Autowired private IHistoryReachStandardSiteService historyReachStandardSiteService;
+
+    /**
+     * 获得综合统计的数据
+     *
+     * @param siteCode 站点code
+     * @param historyId 营销方案id
+     * @return 综合统计的数据
+     */
+    @Override
+    public ComprehensiveStatistics getComprehensiveInfo(String siteCode, String historyId) {
+        ComprehensiveStatistics comprehensiveStatistics = new ComprehensiveStatistics();
+
+        // 查询指标
+        QueryWrapper<SeoKpiStatistics> statisticsQueryWrapper = new QueryWrapper<>();
+        statisticsQueryWrapper.eq("host_code", siteCode);
+        statisticsQueryWrapper.eq("del_flag", 0);
+        statisticsQueryWrapper.eq("type", 1);
+        statisticsQueryWrapper.eq("history_id", historyId);
+        List<SeoKpiStatistics> seoKpiStatisticsList =
+                seoKpiStatisticsService.list(statisticsQueryWrapper);
+        if (ListUtil.isEmpty(seoKpiStatisticsList)) {
+            return comprehensiveStatistics;
+        }
+
+        // 查询每个指标对应的完成数据
+        for (SeoKpiStatistics kpiStatistics : seoKpiStatisticsList) {
+            if (kpiStatistics.getKpiTarget() == null || kpiStatistics.getKpiTarget() == 0) {
+                continue;
+            }
+            if ("APPOINT_KEYWORDS".equals(kpiStatistics.getKpiCode())) {
+                Integer standard = seoPlanSubscriptionMapper.getKeywordStandard(siteCode);
+                String datesStr = "";
+                List<String> dateList =
+                        seoKeywordsService.getKeywordsLastSevenDays(siteCode, 1, historyId);
+                if (ListUtil.isEmpty(dateList)) {
+                    comprehensiveStatistics.setAppointKeywordNum(
+                            0 + " / " + kpiStatistics.getKpiTarget());
+                    continue;
+                } else {
+                    for (int i = 0; i < dateList.size(); i++) {
+                        if (i == dateList.size() - 1) {
+                            datesStr += "'" + dateList.get(i) + "'";
+                        } else {
+                            datesStr += "'" + dateList.get(i) + "'" + ",";
+                        }
+                    }
+                    datesStr = "(" + datesStr + ")";
+                }
+                RankInfoVO rankInfo =
+                        seoKeywordsService.getSeoRankInfo(
+                                siteCode, historyId, 1, standard, datesStr, "1");
+                if (rankInfo != null) {
+                    comprehensiveStatistics.setAppointKeywordNum(
+                            rankInfo.getKeywordNum() + " / " + kpiStatistics.getKpiTarget());
+                } else {
+                    comprehensiveStatistics.setAppointKeywordNum(
+                            0 + " / " + kpiStatistics.getKpiTarget());
+                }
+            } else if ("LONG_TAIL_KEYWORDS".equals(kpiStatistics.getKpiCode())) {
+                String datesStr = "";
+                List<String> dateList =
+                        seoKeywordsService.getKeywordsLastSevenDays(siteCode, 2, historyId);
+                if (ListUtil.isEmpty(dateList)) {
+                    comprehensiveStatistics.setLongTailKeywordNum(
+                            0 + " / " + kpiStatistics.getKpiTarget());
+                    continue;
+                } else {
+                    for (int i = 0; i < dateList.size(); i++) {
+                        if (i == dateList.size() - 1) {
+                            datesStr += "'" + dateList.get(i) + "'";
+                        } else {
+                            datesStr += "'" + dateList.get(i) + "'" + ",";
+                        }
+                    }
+                    datesStr = "(" + datesStr + ")";
+                }
+                RankInfoVO rankInfo =
+                        seoKeywordsService.getSeoRankInfo(
+                                siteCode, historyId, 1, 10, datesStr, "2");
+                if (rankInfo != null) {
+                    comprehensiveStatistics.setLongTailKeywordNum(
+                            rankInfo.getKeywordNum() + " / " + kpiStatistics.getKpiTarget());
+                } else {
+                    comprehensiveStatistics.setLongTailKeywordNum(
+                            0 + " / " + kpiStatistics.getKpiTarget());
+                }
+            } else if ("OUTER_CHAIN".equals(kpiStatistics.getKpiCode())) {
+                comprehensiveStatistics.setOuterLinkNum(
+                        kpiStatistics.getFinishTarget() + " / " + kpiStatistics.getKpiTarget());
+            } else if ("ARTICLE_INSITE".equals(kpiStatistics.getKpiCode())) {
+                log.info(
+                        "getComprehenInfo ---- ARTICLE_INSITE - kpiStatistics:{}",
+                        FastJsonUtil.toJSONString(kpiStatistics));
+
+                // 为了保持数据一致,只走定时器获取数据库中的数据 2023-09-08
+                comprehensiveStatistics.setArticleNum(
+                        kpiStatistics.getFinishTarget() + " / " + kpiStatistics.getKpiTarget());
+            }
+        }
+        return comprehensiveStatistics;
+    }
+
+    @Override
+    public Map getRankInfo(String siteCode, String historyId) throws ParseException {
+        List<SeoRankInfoVO> seoRankInfos = this.getSeoRankInfo(siteCode, historyId);
+        ServerTimeVO ServerTimeVO = this.getServerTime(siteCode, historyId);
+
+        Map map = new HashMap<>();
+        map.put("appointKeyword", seoRankInfos.get(0));
+        map.put("longTailKeyword", seoRankInfos.get(1));
+        map.put("ServerTime", ServerTimeVO);
+        return map;
+    }
+
+    /**
+     * 获取一个站点的关键词排名数量
+     *
+     * @param siteCode 站点code
+     * @return 关键词排名数量
+     */
+    @Override
+    public List<SeoRankInfoVO> getSeoRankInfo(String siteCode, String historyId)
+            throws ParseException {
+        // 排名信息
+        SeoRankInfoVO appointRankInfo = new SeoRankInfoVO();
+        SeoRankInfoVO longTailRankInfo = new SeoRankInfoVO();
+        // 1-10
+        Map<String, Integer> first = dealKeywordsRankData(siteCode, historyId, 1, 10);
+        if (first.containsKey("appoint")) {
+            appointRankInfo.setFirstNum(first.get("appoint"));
+        }
+        if (first.containsKey("longTail")) {
+            longTailRankInfo.setFirstNum(first.get("longTail"));
+        }
+
+        // 11-30
+        Map<String, Integer> second = dealKeywordsRankData(siteCode, historyId, 11, 30);
+        if (second.containsKey("appoint")) {
+            appointRankInfo.setSecondNum(second.get("appoint"));
+        }
+        if (second.containsKey("longTail")) {
+            longTailRankInfo.setSecondNum(second.get("longTail"));
+        }
+        // 31-100
+        Map<String, Integer> third = dealKeywordsRankData(siteCode, historyId, 31, 100);
+        if (third.containsKey("appoint")) {
+            appointRankInfo.setThirdType(third.get("appoint"));
+        }
+        if (third.containsKey("longTail")) {
+            longTailRankInfo.setThirdType(third.get("longTail"));
+        }
+        List<SeoRankInfoVO> list = new ArrayList<>();
+        list.add(appointRankInfo);
+        list.add(longTailRankInfo);
+        return list;
+    }
+
+    /**
+     * 获取服务时间
+     *
+     * @param siteCode 站点code
+     * @param historyId 营销方案id
+     * @return 服务情况,达标时间以及剩余时间
+     */
+    private ServerTimeVO getServerTime(String siteCode, String historyId) {
+        ServerTimeVO ServerTimeVO = new ServerTimeVO();
+
+        // 获取套餐
+        SeoPlanSubscription history = seoPlanSubscriptionService.getById(historyId);
+        if (history == null) {
+            return ServerTimeVO;
+        }
+
+        // 判断是否自定义套餐结束,如果是自定义套餐,需要获得套餐开启时间
+        if (history.getServiceStatus() == 1) {
+            ServerTimeVO.setPlanServiceEndStatus(1);
+            ServerTimeVO.setPlanStartTime(history.getPlanStartTime());
+        }
+
+        // 达成时间
+        Date achieveTime = history.getKeywordsAchieveTime();
+        if (achieveTime != null) {
+            ServerTimeVO.setReachStandardTime(
+                    new SimpleDateFormat(DateUtil.DATE_PATTERN).format(achieveTime));
+        }
+
+        // 达成天数
+        int reachStandardDays = 0;
+        if (achieveTime != null) {
+            QueryWrapper<HistoryReachStandardSite> historyReachStandardSiteQueryWrapper =
+                    new QueryWrapper<>();
+            historyReachStandardSiteQueryWrapper.eq("site_code", siteCode);
+            List<HistoryReachStandardSite> historyReachStandardSiteList =
+                    historyReachStandardSiteService.list(historyReachStandardSiteQueryWrapper);
+            if (ListUtil.isEmpty(historyReachStandardSiteList)) {
+                reachStandardDays = (int) DateUtil.diffDays(achieveTime, new Date());
+            } else {
+                HistoryReachStandardSite historyReachStandardSite =
+                        historyReachStandardSiteList.get(0);
+                int newDays =
+                        (int)
+                                DateUtil.diffDays(
+                                        historyReachStandardSite.getCreateTime(), new Date());
+                reachStandardDays = historyReachStandardSite.getReachStandardDays() + newDays;
+            }
+        }
+        ServerTimeVO.setReachStandardDays(reachStandardDays);
+
+        // 剩余服务天数,0指定服务时长,1指定服务结束日期
+        int remainServerDays;
+        if (history.getServiceStatus() == 0) {
+            int serverMonth = history.getServiceMonth();
+            int serverDay = (serverMonth * 30) + (serverMonth / 2);
+            int giveDay = history.getGiveDay();
+            int compensateDay = history.getCompensateDay();
+            int totalGiveAndCompensateDay = giveDay + compensateDay;
+            log.info(
+                    "getServerTime -- historyId:{},giveDay:{},compensateDay:{},totalGiveAndCompensateDay:{}",
+                    history.getId(),
+                    giveDay,
+                    compensateDay,
+                    totalGiveAndCompensateDay);
+            remainServerDays = serverDay + totalGiveAndCompensateDay - reachStandardDays;
+            log.info(
+                    "getServerTime -- 剩余服务天数:{},服务天数:{},赠、补天数:{},达成天数:{}",
+                    remainServerDays,
+                    serverDay,
+                    totalGiveAndCompensateDay,
+                    reachStandardDays);
+            if (remainServerDays < 0) {
+                remainServerDays = 0;
+            }
+        } else {
+            int giveDay = history.getGiveDay();
+            int compensateDay = history.getCompensateDay();
+            int totalGiveAndCompensateDay = giveDay + compensateDay;
+            log.info(
+                    "getServerTime -- historyId:{},giveDay:{},compensateDay:{},totalGiveAndCompensateDay:{}",
+                    history.getId(),
+                    giveDay,
+                    compensateDay,
+                    totalGiveAndCompensateDay);
+            Date serverEndTime = history.getServiceEndTime();
+            log.info(
+                    "getServerTime -- 原来的serverEndTime:{}",
+                    new SimpleDateFormat(DateUtil.DATE_PATTERN).format(serverEndTime));
+            serverEndTime = DateUtil.addDays(serverEndTime, totalGiveAndCompensateDay);
+            log.info(
+                    "getServerTime -- 增加赠、补天数:{},赠送天数、补偿天数后的serverEndTime:{}",
+                    totalGiveAndCompensateDay,
+                    new SimpleDateFormat(DateUtil.DATE_PATTERN).format(serverEndTime));
+            if (serverEndTime.compareTo(new Date()) <= 0) {
+                remainServerDays = 0;
+            } else {
+                remainServerDays = (int) DateUtil.diffDays(serverEndTime, new Date());
+            }
+            log.info(
+                    "getServerTime -- 剩余服务天数:{},赠、补天数:{},达成天数:{}",
+                    remainServerDays,
+                    totalGiveAndCompensateDay,
+                    reachStandardDays);
+        }
+        ServerTimeVO.setRemainServerDays(remainServerDays);
+        return ServerTimeVO;
+    }
+
+    private Map<String, Integer> dealKeywordsRankData(
+            String siteCode, String historyId, Integer rankStart, Integer rankEnd) {
+        Map<String, Integer> KeywordsRankMap = new HashMap<>();
+
+        // 指定词
+        List<String> appointDateList =
+                seoKeywordsService.getKeywordsLastSevenDays(siteCode, 1, historyId);
+        String appointDatesStr = "";
+        if (ListUtil.notEmpty(appointDateList)) {
+            for (int i = 0; i < appointDateList.size(); i++) {
+                if (i == appointDateList.size() - 1) {
+                    appointDatesStr += "'" + appointDateList.get(i) + "'";
+                } else {
+                    appointDatesStr += "'" + appointDateList.get(i) + "'" + ",";
+                }
+            }
+            appointDatesStr = "(" + appointDatesStr + ")";
+
+            // 指定词
+            RankInfoVO appointRankInfo =
+                    seoKeywordsMapper.queryKeywordNumByRankRange(
+                            siteCode, historyId, rankStart, rankEnd, appointDatesStr, "1");
+            if (appointRankInfo != null) {
+                KeywordsRankMap.put("appoint", appointRankInfo.getKeywordNum());
+            }
+        }
+
+        // 长尾词
+        List<String> dateList = seoKeywordsService.getKeywordsLastSevenDays(siteCode, 2, historyId);
+        String dateStr = "";
+        if (ListUtil.notEmpty(dateList)) {
+            for (int i = 0; i < dateList.size(); i++) {
+                if (i == dateList.size() - 1) {
+                    dateStr += "'" + dateList.get(i) + "'";
+                } else {
+                    dateStr += "'" + dateList.get(i) + "'" + ",";
+                }
+            }
+            dateStr = "(" + dateStr + ")";
+            RankInfoVO longRankInfo =
+                    seoKeywordsMapper.queryKeywordNumByRankRange(
+                            siteCode, historyId, rankStart, rankEnd, dateStr, "2");
+            if (longRankInfo != null) {
+                KeywordsRankMap.put("longTail", longRankInfo.getKeywordNum());
+            }
+        }
+        return KeywordsRankMap;
+    }
+
+    /**
+     * 给列表中的关键字增加排名信息
+     * 根据关键词类型
+     * @param keywordList 关键词列表
+     *
+     */
+    @Override
+    public void addRankInfoByIntervalTimeByType(List<SeoKeywords> keywordList, Integer keywordType, String siteCode, String historyId, List<String> dateList) throws Exception {
+        if (ListUtil.isEmpty(keywordList)) {
+            return;
+        }
+
+        String datesStr = "";
+        if(ListUtil.isEmpty(dateList)){
+            return;
+        }else{
+            for(int i = 0; i < dateList.size(); i++){
+                if(i == dateList.size() - 1){
+                    datesStr +=  "'" + dateList.get(i) + "'";
+                }else{
+                    datesStr +=  "'" + dateList.get(i) + "'" + ",";
+                }
+            }
+            datesStr = "(" + datesStr + ")";
+        }
+        List<Integer> keywordIds = keywordList.stream().map(SeoKeywords::getId).collect(Collectors.toList());
+        List<SeoKeywordsSerp> serpList = seoKeywordsSerpService.list(new LambdaQueryWrapper<SeoKeywordsSerp>()
+                .in(SeoKeywordsSerp::getKeywordsId, keywordIds)
+                .in(SeoKeywordsSerp::getSeDate, dateList));
+        List<SeoKeywordsSerp> noRepeatSerpList = new ArrayList<>();
+        Map<String, Integer> serpMap = new HashMap<>();
+        if(ListUtil.notEmpty(serpList)){
+            for (SeoKeywordsSerp seoKeywordsSerp : serpList) {
+                seoKeywordsSerp.setKey(seoKeywordsSerp.getKeywordsId() + "_" + seoKeywordsSerp.getSeDate());
+                if(ListUtil.notEmpty(noRepeatSerpList)){
+                    List<SeoKeywordsSerp> have = noRepeatSerpList.stream().filter(o -> o.getKey().equals(seoKeywordsSerp.getKey())).collect(Collectors.toList());
+                    if(ListUtil.isEmpty(have)){
+                        serpMap.put(seoKeywordsSerp.getKey(),seoKeywordsSerp.getRankAbsolute());
+                        noRepeatSerpList.add(seoKeywordsSerp);
+                    }else{
+                        log.info("重复的数据:" + seoKeywordsSerp.getKey());
+                    }
+                }else {
+                    serpMap.put(seoKeywordsSerp.getKey(),seoKeywordsSerp.getRankAbsolute());
+                    noRepeatSerpList.add(seoKeywordsSerp);
+                }
+            }
+        }
+
+        for (SeoKeywords keyword : keywordList) {
+            Map<String, String> rankInfo = new LinkedHashMap<>();
+            for (String date : dateList) {
+
+                // 从Map中获取那天的排名信息
+                Integer rankAbsolute = serpMap.get(keyword.getId() + "_" + date);
+
+                if(rankAbsolute == null){
+                    rankAbsolute = 0;
+                }
+                rankInfo.put(date, rankAbsolute + "");
+            }
+
+            keyword.setRankInfo(rankInfo);
+            keyword.setIpAddress("223.112.18.226");
+
+            keyword.setDateList(dateList);
+            if(StringUtil.isEmpty(keyword.getSearchUrl())){
+                keyword.setSearchUrl("https://www.google.com/");
+            }
+        }
+    }
+
+    /**
+     * 自适应宽度(中文支持)
+     * @param sheet
+     * @param size 因为for循环从0开始,size值为 列数-1
+     */
+    @Override
+    public void setSizeColumn(Sheet sheet, int size) {
+        for (int columnNum = 0; columnNum <= size; columnNum++) {
+            int columnWidth = sheet.getColumnWidth(columnNum) / 256;
+            for (int rowNum = 0; rowNum <= sheet.getLastRowNum(); rowNum++) {
+                Row currentRow;
+                //当前行未被使用过
+                if (sheet.getRow(rowNum) == null) {
+                    currentRow = sheet.createRow(rowNum);
+                } else {
+                    currentRow = sheet.getRow(rowNum);
+                }
+
+                if (currentRow.getCell(columnNum) != null) {
+                    Cell currentCell = currentRow.getCell(columnNum);
+                    if (currentCell.getCellType() == CellType.STRING) {
+                        int length = currentCell.getStringCellValue().getBytes().length;
+                        if (columnWidth < length) {
+                            columnWidth = length;
+                        }
+                    }
+                }
+            }
+            sheet.setColumnWidth(columnNum, columnWidth * 256);
+        }
+    }
+}

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

@@ -0,0 +1,19 @@
+package org.jeecg.modules.adweb.seo.service.impl;
+
+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.stereotype.Service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+/**
+ * @Description: SEO关键词搜索排名
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+@Service
+public class SeoKeywordsSerpServiceImpl extends ServiceImpl<SeoKeywordsSerpMapper, SeoKeywordsSerp> implements ISeoKeywordsSerpService {
+
+}

+ 100 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SeoKeywordsServiceImpl.java

@@ -0,0 +1,100 @@
+package org.jeecg.modules.adweb.seo.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+import jakarta.annotation.Resource;
+
+import org.jeecg.modules.adweb.common.util.ListUtil;
+import org.jeecg.modules.adweb.seo.entity.SeoKeywords;
+import org.jeecg.modules.adweb.seo.mapper.SeoKeywordsMapper;
+import org.jeecg.modules.adweb.seo.service.ISeoKeywordsService;
+import org.jeecg.modules.adweb.seo.vo.RankInfoVO;
+import org.springframework.stereotype.Service;
+
+import java.text.ParseException;
+import java.util.List;
+
+/**
+ * @Description: 关键词
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+@Service
+public class SeoKeywordsServiceImpl extends ServiceImpl<SeoKeywordsMapper, SeoKeywords> implements ISeoKeywordsService {
+
+    @Resource private SeoKeywordsMapper seoKeywordsMapper;
+
+    /**
+     * 获取对应站点关键词最新七天时间
+     * @param userFlag
+     * @param keywordType
+     * @param historyId
+     * @return
+     */
+    @Override
+    public List<String> getKeywordsLastSevenDays(String userFlag, Integer keywordType, String historyId){
+        //获取对应站点最近七天
+        List<String> dateList = seoKeywordsMapper.getKeywordsLastSevenDays(userFlag, historyId,keywordType);
+        return dateList;
+    }
+
+    /**
+     * 获取一个站点的达标关键词排名数量
+     *
+     * @param siteCode 站点code
+     * @return 关键词排名数量
+     */
+    @Override
+    public RankInfoVO getSeoRankInfo(String siteCode, String historyId, Integer rankStart, Integer rankEnd, String dateStr, String keywordType) {
+        //指定词
+        return seoKeywordsMapper.queryKeywordNumByRankRange(siteCode, historyId,rankStart,rankEnd,dateStr,keywordType);
+    }
+
+    /**
+     * 获取关键词列表
+     * @return 关键词列表
+     */
+    @Override
+    public IPage<SeoKeywords> getKeywordList(IPage<SeoKeywords> page,
+                                             Integer rankStart,
+                                             Integer rankEnd,
+                                             String keyword,
+                                             String siteCode,
+                                             Integer keywordType,
+                                             boolean reachStandard,
+                                             String historyId,
+                                             String column,
+                                             String order,
+                                             String buttonColumn,
+                                             String buttonSort,
+                                             List<String> dateList) throws ParseException {
+        if (reachStandard) {
+            rankStart = 1;
+            int standard = 10;
+            if (keywordType != null && keywordType == 1) {
+                standard = seoKeywordsMapper.getKeywordStandard(siteCode);
+            }
+            rankEnd = standard;
+        }
+
+        boolean isCustomer = true;
+
+        String datesStr = "";
+        if(ListUtil.isEmpty(dateList)){
+            return null;
+        }else{
+            for(int i = 0; i < dateList.size(); i++){
+                if(i == dateList.size() - 1){
+                    datesStr +=  "'" + dateList.get(i) + "'";
+                }else{
+                    datesStr +=  "'" + dateList.get(i) + "'" + ",";
+                }
+            }
+            datesStr = "(" + datesStr + ")";
+        }
+
+        return seoKeywordsMapper.getKeywordList(page, rankStart, rankEnd, isCustomer, keyword, siteCode, keywordType, datesStr, historyId,column,order,buttonColumn,buttonSort);
+    }
+}

+ 97 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SeoKpiStatisticsServiceImpl.java

@@ -0,0 +1,97 @@
+package org.jeecg.modules.adweb.seo.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shiro.SecurityUtils;
+import org.jeecg.common.system.api.ISysBaseAPI;
+import org.jeecg.common.system.vo.LoginUser;
+import org.jeecg.modules.adweb.common.constant.AdwebConstant;
+import org.jeecg.modules.adweb.common.util.ListUtil;
+import org.jeecg.modules.adweb.seo.entity.SeoKpiStatistics;
+import org.jeecg.modules.adweb.seo.entity.SeoPlanSubscription;
+import org.jeecg.modules.adweb.seo.mapper.SeoKpiStatisticsMapper;
+import org.jeecg.modules.adweb.seo.service.ISeoKpiStatisticsService;
+import org.jeecg.modules.adweb.seo.service.ISeoPlanSubscriptionService;
+import org.jeecg.modules.adweb.site.entity.AdwebSite;
+import org.jeecg.modules.adweb.site.service.IAdwebSiteService;
+import org.jeecg.modules.adweb.site.service.ISiteUserPermissionService;
+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.List;
+
+/**
+ * @Description: seo_kpi_statistics @Author: jeecg-boot @Date: 2024-10-09 @Version: V1.0
+ */
+@Service
+@Slf4j
+public class SeoKpiStatisticsServiceImpl
+        extends ServiceImpl<SeoKpiStatisticsMapper, SeoKpiStatistics>
+        implements ISeoKpiStatisticsService {
+
+    @Autowired private IAdwebSiteService adwebSiteService;
+
+    @Autowired private ISysBaseAPI sysBaseAPI;
+
+    @Autowired private ISiteUserPermissionService siteUserPermissionService;
+
+    @Autowired private ISeoPlanSubscriptionService seoPlanSubscriptionService;
+
+    @Override
+    public List<AdwebSite> getAllSites() {
+        log.info("获取当前用户管理的 父 站点信息");
+        List<AdwebSite> siteList = null;
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+
+        QueryWrapper<AdwebSite> queryWrapper = new QueryWrapper<>();
+        queryWrapper.select("id", "name", "code");
+        queryWrapper.eq("site_type", AdwebConstant.SITE).orderByDesc("create_time");
+        queryWrapper.eq("status", AdwebConstant.STATUS).isNull("parent_group_code");
+        if (sysBaseAPI.isOem()) {
+            List<String> oemGroupUids = sysBaseAPI.getOemGroupUids();
+            List<String> codeList = siteUserPermissionService.getSiteCodeListByUids(oemGroupUids);
+            queryWrapper.in("code", codeList);
+        } else if (!sysBaseAPI.isAdmin()) {
+            List<String> codeList = siteUserPermissionService.getSiteCodeList(sysUser.getId());
+            queryWrapper.in("code", codeList);
+        }
+        siteList = adwebSiteService.list(queryWrapper);
+        this.setPlanName(siteList);
+        return siteList;
+    }
+
+    /** 放入营销方案名称 */
+    private void setPlanName(List<AdwebSite> siteList) {
+        if (ListUtil.isEmpty(siteList)) {
+            return;
+        }
+
+        List<Integer> siteIds = new ArrayList<>();
+        for (AdwebSite site : siteList) {
+            siteIds.add(site.getId());
+        }
+
+        QueryWrapper<SeoPlanSubscription> historyQueryWrapper = new QueryWrapper<>();
+        historyQueryWrapper.select("id", "plan_id", "site_id", "plan_name", "plan_type");
+        historyQueryWrapper.in("site_id", siteIds);
+        historyQueryWrapper.eq("status", 1);
+        List<SeoPlanSubscription> histories = seoPlanSubscriptionService.list(historyQueryWrapper);
+        if (ListUtil.isEmpty(histories)) {
+            return;
+        }
+
+        for (AdwebSite site : siteList) {
+            for (SeoPlanSubscription history : histories) {
+                if (history.getSiteId().equals(site.getId() + "")) {
+                    site.setPlanId(history.getPlanId());
+                    site.setPlanName(history.getPlanName());
+                    site.setHistoryId(history.getId());
+                    site.setPlanType(history.getPlanType());
+                }
+            }
+        }
+    }
+}

+ 19 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/service/impl/SeoPlanSubscriptionServiceImpl.java

@@ -0,0 +1,19 @@
+package org.jeecg.modules.adweb.seo.service.impl;
+
+import org.jeecg.modules.adweb.seo.entity.SeoPlanSubscription;
+import org.jeecg.modules.adweb.seo.mapper.SeoPlanSubscriptionMapper;
+import org.jeecg.modules.adweb.seo.service.ISeoPlanSubscriptionService;
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+/**
+ * @Description: seo套餐订购
+ * @Author: jeecg-boot
+ * @Date:   2024-10-15
+ * @Version: V1.0
+ */
+@Service
+public class SeoPlanSubscriptionServiceImpl extends ServiceImpl<SeoPlanSubscriptionMapper, SeoPlanSubscription> implements ISeoPlanSubscriptionService {
+
+}

+ 13 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/vo/RankInfoVO.java

@@ -0,0 +1,13 @@
+package org.jeecg.modules.adweb.seo.vo;
+
+import lombok.Data;
+
+/**
+ * @author Zenas
+ */
+@Data
+public class RankInfoVO {
+    private int pageNum;
+    private int keywordNum;
+    private int keywordType;
+}

+ 13 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/vo/SeoRankInfoVO.java

@@ -0,0 +1,13 @@
+package org.jeecg.modules.adweb.seo.vo;
+
+import lombok.Data;
+
+/**
+ * @author Zenas
+ */
+@Data
+public class SeoRankInfoVO {
+    private int firstNum;
+    private int secondNum;
+    private int thirdType;
+}

+ 17 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/seo/vo/ServerTimeVO.java

@@ -0,0 +1,17 @@
+package org.jeecg.modules.adweb.seo.vo;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class ServerTimeVO {
+	private String reachStandardTime;
+	private int reachStandardDays;
+	private int remainServerDays;
+
+	// 是否自定义结束日期 0:否 1:是
+	private int planServiceEndStatus;
+	//套餐开启时间
+	private Date planStartTime;
+}

+ 11 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/site/entity/AdwebSite.java

@@ -378,4 +378,15 @@ public class AdwebSite implements Serializable {
 
     @TableField(exist = false)
     private Date enquiryMessageTime;
+    
+    private String planId;
+
+    @TableField(exist = false)
+    private String planName;
+
+    @TableField(exist = false)
+    private String planType;
+
+    @TableField(exist = false)
+    private String historyId;
 }

+ 129 - 2
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysBaseApiImpl.java

@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.google.common.base.Joiner;
+import com.xkcoding.http.util.StringUtil;
 import freemarker.core.TemplateClassResolver;
 import freemarker.template.Configuration;
 import freemarker.template.Template;
@@ -17,10 +18,10 @@ import freemarker.template.TemplateException;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang3.ObjectUtils;
+import org.apache.shiro.SecurityUtils;
 import org.jeecg.common.api.dto.DataLogDTO;
 import org.jeecg.common.api.dto.OnlineAuthDTO;
 import org.jeecg.common.api.dto.message.*;
-import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.aspect.UrlMatchEnum;
 import org.jeecg.common.constant.*;
 import org.jeecg.common.constant.enums.EmailTemplateEnum;
@@ -40,6 +41,12 @@ import org.jeecg.common.util.oConvertUtils;
 import org.jeecg.config.firewall.SqlInjection.IDictTableWhiteListHandler;
 import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
 import org.jeecg.config.security.utils.SecureUtil;
+import org.jeecg.modules.adweb.common.constant.AdwebConstant;
+import org.jeecg.modules.adweb.common.constant.NumConstant;
+import org.jeecg.modules.adweb.common.util.ListUtil;
+import org.jeecg.modules.adweb.site.entity.AdwebSite;
+import org.jeecg.modules.adweb.site.service.IAdwebSiteService;
+import org.jeecg.modules.adweb.site.service.ISiteUserPermissionService;
 import org.jeecg.modules.message.entity.SysMessageTemplate;
 import org.jeecg.modules.message.handle.impl.DdSendMsgHandle;
 import org.jeecg.modules.message.handle.impl.EmailSendMsgHandle;
@@ -55,7 +62,6 @@ import org.jeecg.modules.system.vo.lowapp.SysDictVo;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.Cacheable;
-import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Service;
 import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
 import org.springframework.util.AntPathMatcher;
@@ -137,6 +143,10 @@ public class SysBaseApiImpl implements ISysBaseAPI {
 	private ISysUserPositionService sysUserPositionService;
 
 	@Autowired
+	private ISiteUserPermissionService siteUserPermissionService;
+	@Autowired
+	private IAdwebSiteService adwebSiteService;
+	@Autowired
 	private IDictTableWhiteListHandler dictTableWhiteListHandler;
 
 	@Override
@@ -1862,4 +1872,121 @@ public class SysBaseApiImpl implements ISysBaseAPI {
 		sysUserService.setLoginTenant(sysUser, obj, username, null);
 		return obj;
 	}
+
+	/**
+	 * 根据username判断一个用户是否是渠道账户
+	 *
+	 * @return
+	 */
+	@Override
+	public boolean isOem() {
+		LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+		String username = sysUser.getUsername();
+		List<String> adminRoleIdList = new ArrayList<>();    // 管理员角色id列表
+		adminRoleIdList.add("adweb_oem");
+		List<String> roles = this.getRolesByUsername(username);
+		for (String role : roles) {
+			if ("adweb_oem".equals(role)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * 根据username判断一个用户是否是管理员
+	 *
+	 * @return
+	 */
+	@Override
+	public boolean isAdmin() {
+		LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+		String username = sysUser.getUsername();
+		List<String> adminRoleIdList = new ArrayList<>();    // 管理员角色id列表
+		adminRoleIdList.add("admin");
+		adminRoleIdList.add("adweb_admin");
+		adminRoleIdList.add("seo_admin");
+		adminRoleIdList.add("adweb_site_manager");
+		adminRoleIdList.add("adweb_seo_manager");
+		List<String> roles = this.getRolesByUsername(username);
+		for (String role : roles) {
+			if (adminRoleIdList.contains(role)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public List<String> getOemGroupUids() {
+		LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+		SysUser sysUser = userMapper.getUserByName(loginUser.getUsername());
+		String workNo = sysUser.getWorkNo();
+		List<String> list = null;
+		try {
+			list = userMapper.selectList(new LambdaQueryWrapper<SysUser>()
+					.eq(SysUser::getOemCode, workNo)).stream().map(SysUser::getId).collect(Collectors.toList());
+		} catch (Exception e) {
+			log.error("获取当前登录渠道下客户群的uid");
+		}
+		if (ListUtil.isEmpty(list)) {
+			list = new ArrayList<>();
+			list.add("-1");
+		}
+		list.add(sysUser.getId());
+		return list;
+	}
+
+	@Override
+	public boolean isNotOwnSite(Integer siteId) {
+		if(siteId == null){
+			return true;
+		}
+		QueryWrapper<AdwebSite> queryWrapper = new QueryWrapper<>();
+		LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+		if (this.isOem()){
+			List<String> oemGroupUids = this.getOemGroupUids();
+			List<String> codeList = siteUserPermissionService.getSiteCodeListByUids(oemGroupUids);
+			queryWrapper.in("code", codeList);
+		}else if (!this.isAdmin()) {
+			LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+			List<String> codeList = siteUserPermissionService.getSiteCodeList(loginUser.getId());
+			queryWrapper.in("code", codeList);
+		}
+		queryWrapper.eq("id", siteId);
+		queryWrapper.ne("status", NumConstant.ZERO);
+		List<AdwebSite> list = adwebSiteService.list(queryWrapper);
+		if (ListUtil.notEmpty(list)) {
+			return false;
+		}
+		return true;
+	}
+
+	@Override
+	public boolean isNotOwnSite(String siteCode) {
+		if (this.isAdmin()) {
+			return false;
+		}
+		if(StringUtil.isEmpty(siteCode)){
+			return true;
+		}
+		QueryWrapper<AdwebSite> queryWrapper = new QueryWrapper<>();
+		LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+		if (this.isOem()){
+			List<String> oemGroupUids = this.getOemGroupUids();
+			List<String> codeList = siteUserPermissionService.getSiteCodeListByUids(oemGroupUids);
+			queryWrapper.in("code", codeList);
+		} else if (!this.isAdmin()) {
+			LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+			List<String> codeList = siteUserPermissionService.getSiteCodeList(loginUser.getId());
+			queryWrapper.in("code", codeList);
+		}
+		queryWrapper.eq("code", siteCode);
+		queryWrapper.ne("status", AdwebConstant.SITE_DEL);
+		List<AdwebSite> list = adwebSiteService.list(queryWrapper);
+		if (ListUtil.notEmpty(list)) {
+			return false;
+		}
+		return true;
+	}
 }

+ 9 - 2
jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml

@@ -191,7 +191,7 @@ spring:
     port: 5672
     publisher-confirm-type: correlated
     publisher-returns: true
-    virtual-host: /adweb3
+    virtual-host: /adweb3-dev
 #mybatis plus 设置
 mybatis-plus:
   mapper-locations: classpath*:org/jeecg/**/xml/*Mapper.xml
@@ -383,4 +383,11 @@ judge_waste_enquiry:
     oneDayNum: 5 # 一天内最多发送次数 >
     notBlackListNum: 3 #不在Ip黑名单中垃圾询盘计数次数 >
     notBlackListDate: 7 #不在Ip黑名单中垃圾询盘计数的天数
-    delOrdIpDate: 90 #删除时效性低的Ip时间
+    delOrdIpDate: 90 #删除时效性低的Ip时间
+
+### dataforseo
+dataforseo:
+  username: advichdev@gmail.com
+  password: 0845d3de83295cca
+  api-path: https://api.dataforseo.com
+

+ 8 - 1
jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml

@@ -371,4 +371,11 @@ judge_waste_enquiry:
     oneDayNum: 5 # 一天内最多发送次数 >
     notBlackListNum: 3 #不在Ip黑名单中垃圾询盘计数次数 >
     notBlackListDate: 7 #不在Ip黑名单中垃圾询盘计数的天数
-    delOrdIpDate: 90 #删除时效性低的Ip时间
+    delOrdIpDate: 90 #删除时效性低的Ip时间
+
+### dataforseo
+dataforseo:
+  username: advichdev@gmail.com
+  password: 0845d3de83295cca
+  api-path: https://api.dataforseo.com
+

+ 7 - 1
jeecg-module-system/jeecg-system-start/src/main/resources/application-test.yml

@@ -190,7 +190,7 @@ spring:
     port: 5672
     publisher-confirm-type: correlated
     publisher-returns: true
-    virtual-host: /adweb3
+    virtual-host: /adweb3-test
 #mybatis plus 设置
 mybatis-plus:
   mapper-locations: classpath*:org/jeecg/**/xml/*Mapper.xml
@@ -340,3 +340,9 @@ data-bridge:
   api:
     host: http://data-bridge.v3.adwebcloud.com:9002
     token: lgoXX9APqgPLGMPECiNoxaPx
+
+### dataforseo
+dataforseo:
+  username: advichdev@gmail.com
+  password: 0845d3de83295cca
+  api-path: https://api.dataforseo.com

+ 2 - 0
jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/adweb/dmp/service/google/GoogleServiceTest.java

@@ -7,10 +7,12 @@ import org.jeecg.modules.adweb.dmp.dto.google.analytics.report.data.CountryChart
 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.List;
 
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ActiveProfiles("dev")
 public class GoogleServiceTest {
 
     @Autowired GAReportService gaReportService;

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

@@ -0,0 +1,40 @@
+package org.jeecg.modules.adweb.seo.service;
+
+import io.github.dataforseo.client.api.SerpApi;
+import io.github.dataforseo.client.model.SerpGoogleOrganicLiveAdvancedRequestInfo;
+import io.github.dataforseo.client.model.SerpGoogleOrganicLiveAdvancedResponseInfo;
+
+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.ArrayList;
+import java.util.List;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ActiveProfiles("dev")
+public class DataForSEOTest {
+
+    @Autowired private SerpApi serpApi;
+
+    @Test
+    @Disabled("DataForSEO API charges")
+    public void testSerpGoogleOrganic() throws Exception {
+        SerpGoogleOrganicLiveAdvancedRequestInfo task =
+                new SerpGoogleOrganicLiveAdvancedRequestInfo();
+
+        task.setLocationCode(2840);
+        task.setLanguageCode("en");
+        task.setKeyword("Aluminum Plastic Composite Panel Extrusion Line");
+
+        List<SerpGoogleOrganicLiveAdvancedRequestInfo> serpTaskRequestInfo =
+                new ArrayList<SerpGoogleOrganicLiveAdvancedRequestInfo>();
+        serpTaskRequestInfo.add(task);
+
+        SerpGoogleOrganicLiveAdvancedResponseInfo result =
+                serpApi.googleOrganicLiveAdvanced(serpTaskRequestInfo);
+        System.out.println(result);
+    }
+}

+ 31 - 0
jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/mq/RabbitMQTest.java

@@ -0,0 +1,31 @@
+package org.jeecg.modules.mq;
+
+import cn.hutool.core.util.RandomUtil;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+
+/**
+ * @author wfansh
+ */
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ActiveProfiles("dev")
+public class RabbitMQTest {
+
+    @Autowired private RabbitTemplate rabbitTemplate;
+
+    @Test
+    @Disabled
+    public void testSendMessage() {
+        int num = 10;
+        for (int i = 0; i < num; i++) {
+            rabbitTemplate.convertAndSend("site_enquiry", RandomUtil.randomString(12));
+        }
+
+        System.out.println("Sent messages to site_enquiry queue num = " + num);
+    }
+}