Parcourir la source

营销系统-手动推送

wangcl il y a 1 semaine
Parent
commit
517a3a75ee
23 fichiers modifiés avec 847 ajouts et 59 suppressions
  1. BIN
      pig-common/pig-common-core/src/main/resources/ip/ip2region.xdb
  2. 15 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/MarketingConfigConstants.java
  3. 2 2
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/MktMgmtHandPushQueryDTO.java
  4. 2 2
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/MktMgmtHandPushSaveDTO.java
  5. 21 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/app/PushAPPDTO.java
  6. 46 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/config/PushAPPDTO.java
  7. 19 1
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/config/SaveGlobalRuleDTO.java
  8. 5 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/entity/MarketingApps.java
  9. 11 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/entity/MktMgmtPushRecord.java
  10. 2 2
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/service/MktMgmtHandPushService.java
  11. 47 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/vo/app/MarketingAppsVO.java
  12. 68 0
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/config/PushValidationConfig.java
  13. 12 0
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/controller/MarketingAppsController.java
  14. 2 2
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/controller/MktMgmtHandPushController.java
  15. 6 0
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/MarketingAppsService.java
  16. 23 3
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MarketingAppsServiceImpl.java
  17. 103 4
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MarketingConfigServiceImpl.java
  18. 162 15
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MktMgmtHandPushServiceImpl.java
  19. 1 1
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MktMgmtPushRecordServiceImpl.java
  20. 8 10
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/TcpDataServiceImpl.java
  21. 81 0
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/util/DeviceInfoUtil.java
  22. 187 0
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/util/IPLocationUtil.java
  23. 24 17
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/util/PushFrequencyUtil.java

BIN
pig-common/pig-common-core/src/main/resources/ip/ip2region.xdb


+ 15 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/MarketingConfigConstants.java

@@ -50,4 +50,19 @@ public interface MarketingConfigConstants {
 	 * 推送域名
 	 */
 	String PUSH_DOMAIN = "PUSH_DOMAIN";
+
+	/**
+	 * 推送地址
+	 */
+	String PUSH_ADDR = "PUSH_ADDR";
+
+	/**
+	 * 推送应用
+	 */
+	String PUSH_APP = "PUSH_APP";
+
+	/**
+	 * 推送包名
+	 */
+	String PUSH_BUNDLE = "PUSH_BUNDLE";
 }

+ 2 - 2
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/MktMgmtHandPushQueryDTO.java

@@ -9,8 +9,8 @@ import jakarta.validation.constraints.NotNull;
 /**
  * 手动推送查询DTO
  * 
- * @author pig4cloud
- * @date 2025-01-20
+ * @author WCL
+ * @date 2025-09-03
  * @description 手动推送记录查询参数
  */
 @Data

+ 2 - 2
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/MktMgmtHandPushSaveDTO.java

@@ -9,8 +9,8 @@ import jakarta.validation.constraints.NotNull;
 /**
  * 手动推送保存DTO
  * 
- * @author pig4cloud
- * @date 2025-01-20
+ * @author WCL
+ * @date 2025-09-03
  * @description 手动推送记录保存参数
  */
 @Data

+ 21 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/app/PushAPPDTO.java

@@ -0,0 +1,21 @@
+package com.pig4cloud.pig.marketing.api.dto.app;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * @author wcl
+ * @date 2025/9/4 18:14
+ * @description: 推送APP参数
+ */
+@Data
+@Schema(description = "推送APP参数")
+public class PushAPPDTO {
+
+	@Schema(description = "id")
+	private String id;
+
+    @Schema(description = "appid")
+	private String appId;
+}

+ 46 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/config/PushAPPDTO.java

@@ -0,0 +1,46 @@
+package com.pig4cloud.pig.marketing.api.dto.config;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 推送应用DTO
+ * 
+ * @author WCL
+ * @date 2025-09-04
+ * @description 推送应用配置信息
+ */
+@Data
+@Schema(description = "推送应用DTO")
+public class PushAPPDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 应用ID
+     */
+    @Schema(description = "应用ID")
+    private String id;
+
+    /**
+     * 应用标识
+     */
+    @Schema(description = "应用标识")
+    private String appId;
+
+    /**
+     * 应用名称
+     */
+    @Schema(description = "应用名称")
+    private String appName;
+
+    /**
+     * 应用描述
+     */
+    @Schema(description = "应用描述")
+    private String description;
+}

+ 19 - 1
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/config/SaveGlobalRuleDTO.java

@@ -52,7 +52,6 @@ public class SaveGlobalRuleDTO implements Serializable {
 	 * 推送频率
 	 */
 	@Schema(description = "推送频率")
-	@NotBlank(message = "推送频率不能为空")
 	private String pushFrequency;
 
 	/**
@@ -76,4 +75,23 @@ public class SaveGlobalRuleDTO implements Serializable {
 	@Schema(description = "自动推送")
 	private Boolean autoPush;
 
+	/**
+	 * 地址
+	 */
+	@Schema(description = "推送地址")
+	private List<String> pushAddr;
+
+	/**
+	 * 推送应用
+	 */
+	@Schema(description = "推送应用")
+	private List<PushAPPDTO> pushApp;
+
+	/**
+	 * 推送包名
+	 */
+	@Schema(description = "推送包名")
+	private List<String> pushBundle;
+
+
 }

+ 5 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/entity/MarketingApps.java

@@ -37,6 +37,11 @@ public class MarketingApps extends Model<MarketingApps> {
 	@Schema(description = "应用ID")
 	private String appId;
 
+	/**
+	 * 应用包名
+	 */
+	private String bundle;
+
 	/**
 	 * 应用名称
 	 */

+ 11 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/entity/MktMgmtPushRecord.java

@@ -103,7 +103,18 @@ public class MktMgmtPushRecord extends Model<MktMgmtPushRecord> {
 	 */
 	@Schema(description = "规则ID")
 	private Long ruleId;
+	
+	/**
+	 * 推送app
+	 * */
+	@Schema(description = "推送app")
+	private String pushApp;
 
+	/**
+	 * 推送地址
+	 * */
+	@Schema(description = "推送地址")
+	private String pushAddr;
 
 	/**
 	 * 删除标记

+ 2 - 2
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/service/MktMgmtHandPushService.java

@@ -8,8 +8,8 @@ import com.pig4cloud.pig.marketing.api.vo.rule.push.HandPushVO;
 /**
  * 手动推送服务接口
  * 
- * @author pig4cloud
- * @date 2025-01-20
+ * @author WCL
+ * @date 2025-09-03
  * @description 手动推送相关业务接口
  */
 public interface MktMgmtHandPushService {

+ 47 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/vo/app/MarketingAppsVO.java

@@ -0,0 +1,47 @@
+package com.pig4cloud.pig.marketing.api.vo.app;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: wcl
+ * @date: 2025-09-04
+ * @description: 查询应用列表出参
+ */
+@Data
+@Schema(description = "分页查询应用列表出参")
+public class MarketingAppsVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * id
+	 */
+	@Schema(description = "id")
+	Long id;
+
+	/**
+	 * 应用ID
+	 */
+	@Schema(description = "应用ID")
+	String appId;
+
+	/**
+	 * 应用名称
+	 */
+	@Schema(description = "应用名称")
+	String appName;
+
+	/**
+	 * 包名
+	 */
+	@Schema(description = "包名")
+	String bundle;
+
+	
+}

+ 68 - 0
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/config/PushValidationConfig.java

@@ -0,0 +1,68 @@
+package com.pig4cloud.pig.marketing.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Component;
+
+/**
+ * 推送校验配置
+ * 
+ * @author WCL
+ * @date 2025-09-03
+ * @description 推送功能相关配置
+ */
+@Data
+@Component
+@RefreshScope
+@ConfigurationProperties(prefix = "push.validation")
+public class PushValidationConfig {
+
+    /**
+     * 是否启用IP校验
+     */
+    private Boolean enableIpCheck = true;
+
+    /**
+     * 是否启用地址校验
+     */
+    private Boolean enableAddressCheck = true;
+
+    /**
+     * 是否启用应用校验
+     */
+    private Boolean enableAppCheck = true;
+
+    /**
+     * 是否启用域名校验
+     */
+    private Boolean enableDomainCheck = true;
+
+    /**
+     * 是否启用频率校验
+     */
+    private Boolean enableFrequencyCheck = true;
+
+    /**
+     * IP定位配置
+     */
+    private IpLocation ipLocation = new IpLocation();
+
+    @Data
+    public static class IpLocation {
+        /**
+         * 是否启用IP定位(使用项目内部的ip2region数据库)
+         */
+        private Boolean enabled = true;
+
+        /**
+         * 是否启用缓存
+         */
+        private Boolean enableCache = true;
+
+        /**
+         * 缓存过期时间(秒)
+         */
+        private Integer cacheExpireSeconds = 3600;
+    }
+}

+ 12 - 0
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/controller/MarketingAppsController.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.pig4cloud.pig.common.core.exception.BusinessException;
 import com.pig4cloud.pig.common.core.util.R;
 import com.pig4cloud.pig.marketing.api.dto.app.*;
+import com.pig4cloud.pig.marketing.api.vo.app.MarketingAppsVO;
 import com.pig4cloud.pig.marketing.api.vo.app.PageMarketingAppsVO;
 import com.pig4cloud.pig.marketing.api.vo.app.PageStatAppFirstMktDataVO;
 import com.pig4cloud.pig.marketing.api.vo.app.PageStatAppSecondMktDataVO;
@@ -50,6 +51,17 @@ public class MarketingAppsController {
 		return R.ok(page);
 	}
 
+	/**
+	 * 查询应用列表
+	 * @return 查询结果
+	 */
+	@GetMapping("/list")
+	@Operation(summary = "查询应用列表")
+	public R<List<MarketingAppsVO>> MarketingApps() {
+		List<MarketingAppsVO> marketingAppsVOS = marketingAppsService.marketingApps();
+		return R.ok(marketingAppsVOS);
+	}
+
 	@GetMapping("/detail")
 	@Operation(summary = "查询应用详情")
 	public R<PageMarketingAppsVO> getMarketingAppById(@ParameterObject Long id) {

+ 2 - 2
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/controller/MktMgmtHandPushController.java

@@ -17,8 +17,8 @@ import jakarta.validation.Valid;
 /**
  * 手动推送管理控制器
  * 
- * @author pig4cloud
- * @date 2025-01-20
+ * @author WCL
+ * @date 2025-09-03
  * @description 手动推送相关接口
  */
 @Slf4j

+ 6 - 0
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/MarketingAppsService.java

@@ -4,6 +4,7 @@ package com.pig4cloud.pig.marketing.service;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.pig4cloud.pig.common.core.util.R;
 import com.pig4cloud.pig.marketing.api.dto.app.*;
+import com.pig4cloud.pig.marketing.api.vo.app.MarketingAppsVO;
 import com.pig4cloud.pig.marketing.api.vo.app.PageMarketingAppsVO;
 import com.pig4cloud.pig.marketing.api.vo.app.PageStatAppFirstMktDataVO;
 import com.pig4cloud.pig.marketing.api.vo.app.PageStatAppSecondMktDataVO;
@@ -27,6 +28,11 @@ public interface MarketingAppsService {
 	 */
 	Page pageMarketingApps(PageMarketingAppsDTO reqDto, Boolean status);
 
+	/**
+	 * 分页查询应用列表
+	 */
+	List<MarketingAppsVO> marketingApps();
+
 	/**
 	 * 根据ID获取应用详情
 	 * @param id 应用ID

+ 23 - 3
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MarketingAppsServiceImpl.java

@@ -18,7 +18,6 @@ import com.pig4cloud.pig.marketing.api.entity.*;
 import com.pig4cloud.pig.marketing.api.vo.app.*;
 import com.pig4cloud.pig.marketing.mapper.*;
 import com.pig4cloud.pig.marketing.service.MarketingAppsService;
-import com.pig4cloud.pig.marketing.service.MarketingConfigService;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import okhttp3.*;
@@ -32,7 +31,8 @@ import java.util.*;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
-import static com.pig4cloud.pig.marketing.api.MarketingConfigConstants.*;
+import static com.pig4cloud.pig.marketing.api.MarketingConfigConstants.TRIGGER_NUM;
+import static com.pig4cloud.pig.marketing.api.MarketingConfigConstants.TRIGGER_RULE;
 
 /**
  * @author: lwh
@@ -47,7 +47,7 @@ public class MarketingAppsServiceImpl implements MarketingAppsService {
 
 	// 从配置文件读取URL
 //	@Value("${marketing.app.url}")
-	private static final String getAppListUrl = "http://192.168.3.17:2888/ipa/getApps";
+	private static final String getAppListUrl = "http://192.168.3.17:8000/ipa/getApps";
 
 	// 从配置文件读取accessKey
 //	@Value("${marketing.app.access-key}")
@@ -127,6 +127,25 @@ public class MarketingAppsServiceImpl implements MarketingAppsService {
 		return page;
 	}
 
+	/**
+	 * 分页查询应用列表
+	 */
+	@Override
+	public List<MarketingAppsVO> marketingApps() {
+
+		List<MarketingAppsVO> marketingAppsVOList = new ArrayList<>();
+		List<MarketingApps> apps = appsMapper.selectList(new QueryWrapper<MarketingApps>());
+		for (MarketingApps app : apps) {
+			MarketingAppsVO appsVO = new MarketingAppsVO();
+			BeanUtils.copyProperties(app, appsVO);
+
+			marketingAppsVOList.add(appsVO);
+		}
+
+
+		return marketingAppsVOList;
+	}
+
 	/**
 	 * 根据ID获取应用详情
 	 * @param id 应用ID
@@ -464,6 +483,7 @@ public class MarketingAppsServiceImpl implements MarketingAppsService {
 					marketingApps.setAppId(app.getString("id"));
 					marketingApps.setAppName(app.getString("name"));
 					marketingApps.setAppImg(app.getString("img"));
+					marketingApps.setBundle(app.getString("bundle"));
 					marketingApps.setAppUrl("https://"+app.getString("download_url")+"/"+marketingApps.getAppId());
 					marketingApps.setBackupUrl("https://"+app.getString("backup_url")+"/"+marketingApps.getAppId());
 					marketingApps.setDomainType(app.getInteger("type"));

+ 103 - 4
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MarketingConfigServiceImpl.java

@@ -20,13 +20,13 @@ import com.pig4cloud.pig.marketing.service.MarketingConfigService;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 import static com.pig4cloud.pig.marketing.api.MarketingConfigConstants.*;
 
@@ -627,7 +627,10 @@ public class MarketingConfigServiceImpl implements MarketingConfigService {
 				PUSH_CONTENT,
 				AUTO_PUSH,
 				PUSH_IP,
-				PUSH_DOMAIN
+				PUSH_DOMAIN,
+				PUSH_ADDR,
+				PUSH_APP,
+				PUSH_BUNDLE
 		));
 		ParamDTO.getData().forEach(item -> {
 			switch (item.getPublicKey()) {
@@ -665,6 +668,43 @@ public class MarketingConfigServiceImpl implements MarketingConfigService {
 						res.setDomain(new ArrayList<>());
 					}
 					break;
+				case PUSH_ADDR:
+					if (StringUtils.isNotBlank(item.getPublicValue())) {
+						String[] addr = item.getPublicValue().split("&&");
+						res.setPushAddr(List.of(addr));
+					} else {
+						res.setPushAddr(new ArrayList<>());
+					}
+					break;
+				case PUSH_APP:
+					if (StringUtils.isNotBlank(item.getPublicValue())) {
+						String[] appArray = item.getPublicValue().split("&&");
+						List<PushAPPDTO> pushAppList = new ArrayList<>();
+						for (String appStr : appArray) {
+							if (appStr.contains("@")) {
+								String[] parts = appStr.split("@", 2);
+								if (parts.length == 2) {
+									PushAPPDTO pushApp = new PushAPPDTO();
+									pushApp.setId(parts[0]);
+									pushApp.setAppId(parts[1]);
+									pushAppList.add(pushApp);
+								}
+							}
+						}
+						res.setPushApp(pushAppList);
+					} else {
+						res.setPushApp(new ArrayList<>());
+					}
+					break;
+				case PUSH_BUNDLE:
+					if (StringUtils.isNotBlank(item.getPublicValue())) {
+						String[] bundle = item.getPublicValue().split("&&");
+						res.setPushBundle(List.of(bundle));
+					} else {
+						res.setPushBundle(new ArrayList<>());
+					}
+					break;
+
 			}
 		});
 		return res;
@@ -765,6 +805,65 @@ public class MarketingConfigServiceImpl implements MarketingConfigService {
 			sysParam.setPublicValue(reqDto.getAutoPush() ? "0" : "1");
 			sysParamList.add(sysParam);
 		}
+
+		//处理推送app
+		if (reqDto.getPushApp() != null && !reqDto.getPushApp().isEmpty()){
+			StringBuilder appBuilder = new StringBuilder();
+
+			for (PushAPPDTO app : reqDto.getPushApp()){
+				if (app != null && app.getId() != null && app.getAppId() != null) {
+					appBuilder.append(app.getId()).append("@").append(app.getAppId());
+					appBuilder.append("&&");
+				}
+			}
+			if (appBuilder.length() > 2) {
+				appBuilder.delete(appBuilder.length()-2, appBuilder.length());
+			}
+			sysParam = new SysPublicParamDTO();
+			sysParam.setPublicKey(PUSH_APP);
+			sysParam.setPublicValue(appBuilder.toString());
+			sysParamList.add(sysParam);
+		}
+
+
+		//处理推送地址
+		if (reqDto.getPushAddr() != null && !reqDto.getPushAddr().isEmpty()){
+			StringBuilder addrBuilder = new StringBuilder();
+			for (String addr : reqDto.getPushAddr()){
+				if ("All".equals(addr)){
+					addrBuilder.append("All");
+					break;
+				}
+				addrBuilder.append(addr);
+				addrBuilder.append("&&");
+			}
+			addrBuilder.delete(addrBuilder.length()-2, addrBuilder.length());
+			sysParam = new SysPublicParamDTO();
+			sysParam.setPublicKey(PUSH_ADDR);
+			sysParam.setPublicValue(addrBuilder.toString());
+			sysParamList.add(sysParam);
+		}
+
+
+		//处理推送包名
+		StringBuilder bundleBuilder = new StringBuilder();
+		if (reqDto.getPushAddr() != null && !reqDto.getPushAddr().isEmpty()){
+			for (String bundle : reqDto.getPushBundle()){
+				if ("All".equals(bundle)){
+					bundleBuilder.append("All");
+					break;
+				}
+				bundleBuilder.append(bundle);
+				bundleBuilder.append("&&");
+			}
+			bundleBuilder.delete(bundleBuilder.length()-2, bundleBuilder.length());
+			sysParam = new SysPublicParamDTO();
+			sysParam.setPublicKey(PUSH_BUNDLE);
+			sysParam.setPublicValue(bundleBuilder.toString());
+			sysParamList.add(sysParam);
+		}
+
+
 		remoteParamService.setByKey(sysParamList);
 		return null;
 	}

+ 162 - 15
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MktMgmtHandPushServiceImpl.java

@@ -4,15 +4,23 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.pig4cloud.pig.marketing.api.dto.MktMgmtHandPushQueryDTO;
 import com.pig4cloud.pig.marketing.api.dto.MktMgmtPushRecordSaveDTO;
+import com.pig4cloud.pig.marketing.api.dto.config.SaveGlobalRuleDTO;
+import com.pig4cloud.pig.marketing.api.dto.config.PushAPPDTO;
 import com.pig4cloud.pig.marketing.api.entity.MktMgmtPushRecord;
+import com.pig4cloud.pig.marketing.api.entity.mongo.Device;
 import com.pig4cloud.pig.marketing.api.service.MktMgmtHandPushService;
-import com.pig4cloud.pig.marketing.api.dto.config.SaveGlobalRuleDTO;
-import com.pig4cloud.pig.marketing.service.MarketingConfigService;
 import com.pig4cloud.pig.marketing.api.vo.rule.push.HandPushVO;
+import com.pig4cloud.pig.marketing.config.PushValidationConfig;
 import com.pig4cloud.pig.marketing.mapper.MktMgmtPushRecordMapper;
+import com.pig4cloud.pig.marketing.service.MarketingConfigService;
+import com.pig4cloud.pig.marketing.service.TcpDataService;
+import com.pig4cloud.pig.marketing.util.DeviceInfoUtil;
+import com.pig4cloud.pig.marketing.util.IPLocationUtil;
 import com.pig4cloud.pig.marketing.util.PushFrequencyUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.beans.BeanUtils;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
@@ -28,8 +36,8 @@ import java.util.stream.Collectors;
 /**
  * 手动推送服务实现类
  * 
- * @author pig4cloud
- * @date 2025-01-20
+ * @author WCL
+ * @date 2025-09-04
  * @description 手动推送相关业务实现
  */
 @Slf4j
@@ -39,6 +47,12 @@ public class MktMgmtHandPushServiceImpl implements MktMgmtHandPushService {
 
     private final MktMgmtPushRecordMapper mktMgmtPushRecordMapper;
     private final MarketingConfigService marketingConfigService;
+    @Autowired
+    @Lazy
+    private TcpDataService tcpDataService;
+    private final IPLocationUtil ipLocationUtil;
+    private final DeviceInfoUtil deviceInfoUtil;
+    private final PushValidationConfig pushValidationConfig;
     private final RedisTemplate<String, Object> redisTemplate;
 
     @Override
@@ -79,7 +93,7 @@ public class MktMgmtHandPushServiceImpl implements MktMgmtHandPushService {
     @Override
     public String handPush(MktMgmtPushRecordSaveDTO saveDTO) {
         try {
-            log.info("开始手动推送,推送内容:{}", saveDTO.getPushContent());
+            log.info("开始手动推送,推送内容:{},客户端ID:{}", saveDTO.getPushContent(), saveDTO.getClientId());
             
             // 1. 获取全局手动推送规则
             SaveGlobalRuleDTO globalRule = marketingConfigService.getGlobalRule();
@@ -87,35 +101,120 @@ public class MktMgmtHandPushServiceImpl implements MktMgmtHandPushService {
                 return "获取全局手动推送规则失败";
             }
             
-            // 2. 校验IP
-            if (!validateGlobalRuleIP(globalRule.getIp(), saveDTO.getPushIP())) {
+            // 2. IP校验(配置化控制)
+            if (pushValidationConfig.getEnableIpCheck() && !validateGlobalRuleIP(globalRule.getIp(), saveDTO.getPushIP())) {
                 return "推送IP【" + saveDTO.getPushIP() + "】不在允许的IP范围内";
             }
             
-            // 3. 校验域名
-            if (!validateGlobalRuleDomain(globalRule.getDomain(), saveDTO.getPushDomain())) {
+            // 3. 地址校验 - 根据IP获取三级地址信息并匹配pushAddr
+            if (pushValidationConfig.getEnableAddressCheck() && 
+                globalRule.getPushAddr() != null && !globalRule.getPushAddr().isEmpty()) {
+
+				if ("All".equals(globalRule.getPushAddr().get(0))){
+					return "推送地址【All】";
+				}
+
+                String fullAddress = ipLocationUtil.getCityByIP(saveDTO.getPushIP());
+                if (fullAddress == null) {
+                    return "无法解析推送IP【" + saveDTO.getPushIP() + "】的地址信息";
+                }
+                
+                // 检查是否匹配pushAddr中的任一地址
+                boolean addressMatched = false;
+                for (String allowedAddr : globalRule.getPushAddr()) {
+                    if (isAddressMatched(fullAddress, allowedAddr)) {
+                        addressMatched = true;
+                        break;
+                    }
+                }
+                
+                if (!addressMatched) {
+                    return "推送地址【" + fullAddress + "】不在允许的地址范围内,允许的地址:" + globalRule.getPushAddr();
+                }
+                log.info("地址校验通过,IP:{},地址:{}", saveDTO.getPushIP(), fullAddress);
+            }
+            
+            // 4. 应用校验 - 根据客户端ID获取设备信息并匹配pushApp和pushBundle
+            if (pushValidationConfig.getEnableAppCheck()) {
+                Device device = tcpDataService.getDeviceByClientID(saveDTO.getClientId());
+                if (device == null) {
+                    return "未找到客户端ID【" + saveDTO.getClientId() + "】对应的设备信息";
+                }
+                
+                String[] bundleAndAppId = deviceInfoUtil.extractBundleAndAppId(device.getDeviceInfo());
+                String bundle = bundleAndAppId[0];
+                String appId = bundleAndAppId[1];
+                
+                boolean appMatched = false;
+                String matchedType = "";
+                
+                // 检查pushApp(appId匹配)
+                if (globalRule.getPushApp() != null && !globalRule.getPushApp().isEmpty() && appId != null) {
+                    // 检查是否为All
+                    if (globalRule.getPushApp().stream()
+                            .anyMatch(pushApp -> pushApp != null && "All".equals(pushApp.getAppId()))) {
+                        appMatched = true;
+                        matchedType = "appId(All)";
+                    } else {
+                        // 检查appId是否匹配
+                        if (globalRule.getPushApp().stream()
+                                .anyMatch(pushApp -> pushApp != null && appId.equals(pushApp.getAppId()))) {
+                            appMatched = true;
+                            matchedType = "appId";
+                        }
+                    }
+                }
+                
+                // 检查pushBundle(bundle匹配)
+                if (!appMatched && globalRule.getPushBundle() != null && !globalRule.getPushBundle().isEmpty() && bundle != null) {
+                    if (globalRule.getPushBundle().contains(bundle)) {
+                        appMatched = true;
+                        matchedType = "bundle";
+                    }
+                }
+                
+                if (!appMatched) {
+                    StringBuilder allowedApps = new StringBuilder();
+                    if (globalRule.getPushApp() != null && !globalRule.getPushApp().isEmpty()) {
+                        allowedApps.append("允许的appId:").append(globalRule.getPushApp().stream().map(PushAPPDTO::getAppId).toList());
+                    }
+                    if (globalRule.getPushBundle() != null && !globalRule.getPushBundle().isEmpty()) {
+                        if (allowedApps.length() > 0) {
+                            allowedApps.append(",");
+                        }
+                        allowedApps.append("允许的bundle:").append(globalRule.getPushBundle());
+                    }
+                    return "应用校验失败,bundle【" + bundle + "】和appId【" + appId + "】都不在允许的应用范围内," + allowedApps.toString();
+                }
+                log.info("应用校验通过,匹配类型:{},bundle:{},appId:{}", matchedType, bundle, appId);
+            }
+            
+            // 5. 校验域名
+            if (pushValidationConfig.getEnableDomainCheck() && 
+                !validateGlobalRuleDomain(globalRule.getDomain(), saveDTO.getPushDomain())) {
                 return "推送域名【" + saveDTO.getPushDomain() + "】不在允许的域名范围内";
             }
             
-            // 4. 校验推送频率
-            if (!PushFrequencyUtil.checkPushFrequency(globalRule.getPushFrequency(), 0L, saveDTO.getClientId(), redisTemplate)) {
+            // 6. 校验推送频率
+            if (pushValidationConfig.getEnableFrequencyCheck() && 
+                !PushFrequencyUtil.checkPushFrequency(globalRule.getPushFrequency(), 0L, saveDTO.getClientId(), redisTemplate)) {
                 return "推送频率检查未通过,推送频率:" + globalRule.getPushFrequency();
             }
             
-            // 5. 构建推送记录
+            // 7. 构建推送记录
             MktMgmtPushRecord pushRecord = buildHandPushRecord(saveDTO, globalRule);
             
-            // 6. 保存推送记录
+            // 8. 保存推送记录
             int result = mktMgmtPushRecordMapper.insert(pushRecord);
             if (result > 0) {
-                log.info("手动推送成功,推送内容:{}", saveDTO.getPushContent());
+                log.info("手动推送成功,推送内容:{},客户端ID:{}", saveDTO.getPushContent(), saveDTO.getClientId());
                 return "手动推送成功";
             } else {
                 return "手动推送失败,数据库保存异常";
             }
             
         } catch (Exception e) {
-            log.error("手动推送异常,推送内容:{}", saveDTO.getPushContent(), e);
+            log.error("手动推送异常,推送内容:{},客户端ID:{}", saveDTO.getPushContent(), saveDTO.getClientId(), e);
             return "手动推送异常:" + e.getMessage();
         }
     }
@@ -253,6 +352,7 @@ public class MktMgmtHandPushServiceImpl implements MktMgmtHandPushService {
 		pushRecord.setPushType(globalRule.getPushType());
 		pushRecord.setPushAction(globalRule.getAction());
 		pushRecord.setDelayPush(Integer.valueOf(globalRule.getDelayPush()));
+		pushRecord.setPushAddr(saveDTO.getPushIP());
         pushRecord.setCreateTime(LocalDateTime.now());
         pushRecord.setUpdateTime(LocalDateTime.now());
         
@@ -293,4 +393,51 @@ public class MktMgmtHandPushServiceImpl implements MktMgmtHandPushService {
         BeanUtils.copyProperties(record, vo);
         return vo;
     }
+    
+    /**
+     * 检查地址是否匹配
+     * 
+     * @param fullAddress 完整的三级地址,格式:国家-省-市
+     * @param allowedAddr 允许的地址配置
+     * @return 是否匹配
+     */
+    private boolean isAddressMatched(String fullAddress, String allowedAddr) {
+        if (fullAddress == null || allowedAddr == null) {
+            return false;
+        }
+        
+        // 如果允许的地址配置为空,则不匹配
+        if (allowedAddr.trim().isEmpty()) {
+            return false;
+        }
+        
+        String trimmedAllowedAddr = allowedAddr.trim();
+        
+        // 1. 完全匹配
+        if (fullAddress.equals(trimmedAllowedAddr)) {
+            return true;
+        }
+        
+        // 2. 检查是否匹配国家级别(如果允许的地址只有国家)
+        if (!trimmedAllowedAddr.contains("-")) {
+            return fullAddress.startsWith(trimmedAllowedAddr + "-");
+        }
+        
+        // 3. 检查是否匹配国家-省级别
+        String[] allowedParts = trimmedAllowedAddr.split("-");
+        String[] fullParts = fullAddress.split("-");
+        
+        if (allowedParts.length == 2 && fullParts.length >= 2) {
+            return allowedParts[0].equals(fullParts[0]) && allowedParts[1].equals(fullParts[1]);
+        }
+        
+        // 4. 检查是否匹配国家-省-市级别
+        if (allowedParts.length == 3 && fullParts.length >= 3) {
+            return allowedParts[0].equals(fullParts[0]) && 
+                   allowedParts[1].equals(fullParts[1]) && 
+                   allowedParts[2].equals(fullParts[2]);
+        }
+        
+        return false;
+    }
 }

+ 1 - 1
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MktMgmtPushRecordServiceImpl.java

@@ -104,7 +104,7 @@ public class MktMgmtPushRecordServiceImpl implements MktMgmtPushRecordService {
 				
 				if (validationResult.isValid()) {
 					// 验证推送频率
-					boolean shouldPush = PushFrequencyUtil.checkPushFrequency(rule.getPushFrequency(), rule.getId(), saveDTO.getPushContent(), redisTemplate);
+					boolean shouldPush = PushFrequencyUtil.checkPushFrequency(rule.getPushFrequency(), rule.getId(), saveDTO.getClientId(), redisTemplate);
 					if (shouldPush) {
 						selectedRule = rule;
 						selectedRuleId = ruleId;

+ 8 - 10
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/TcpDataServiceImpl.java

@@ -10,9 +10,9 @@ import com.pig4cloud.pig.marketing.api.dto.mongo.SaveTcpMessageDTO;
 import com.pig4cloud.pig.marketing.api.entity.mongo.Device;
 import com.pig4cloud.pig.marketing.api.entity.mongo.Message;
 import com.pig4cloud.pig.marketing.api.service.MktMgmtHandPushService;
+import com.pig4cloud.pig.marketing.api.vo.mongo.OnlineUserVO;
 import com.pig4cloud.pig.marketing.api.vo.mongo.PageDeviceInfoVO;
 import com.pig4cloud.pig.marketing.api.vo.mongo.UserStatisticsVO;
-import com.pig4cloud.pig.marketing.api.vo.mongo.OnlineUserVO;
 import com.pig4cloud.pig.marketing.config.UserStatisticsConfig;
 import com.pig4cloud.pig.marketing.repository.MessageRepository;
 import com.pig4cloud.pig.marketing.service.MktMgmtPushRecordService;
@@ -33,11 +33,7 @@ import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -60,7 +56,9 @@ import java.util.stream.Collectors;
 		@Autowired
 		private MktMgmtPushRecordService mktMgmtPushRecordService;
 
-		private final MktMgmtHandPushService mktMgmtHandPushService;
+		@Autowired
+		private MktMgmtHandPushService mktMgmtHandPushService;
+
 
 	// 定义时间格式器
 	private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@@ -225,9 +223,7 @@ import java.util.stream.Collectors;
 			hand = mktMgmtHandPushService.handPush(saveRecordDTO);
 		}
 
-
 		log.info("保存推送记录:{}", s);
-		log.info("保存手动推送记录:{}", hand);
 		return save.getId();
 	}
 
@@ -473,7 +469,9 @@ import java.util.stream.Collectors;
 				.collect(Collectors.toList());
 			
 			log.info("在线用户列表查询完成,共找到 {} 个在线用户", onlineUsers.size());
-			return onlineUsers;
+			// 只返回前十条记录
+			int limit = Math.min(onlineUsers.size(), 10);
+			return onlineUsers.subList(0, limit);
 			
 		} catch (Exception e) {
 			log.error("查询在线用户列表异常", e);

+ 81 - 0
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/util/DeviceInfoUtil.java

@@ -0,0 +1,81 @@
+package com.pig4cloud.pig.marketing.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 设备信息解析工具类
+ * 
+ * @author WCL
+ * @date 2025-09-04
+ * @description 解析设备信息JSON字符串,提取bundle和appId
+ */
+@Slf4j
+@Component
+public class DeviceInfoUtil {
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * 从设备信息JSON字符串中提取bundle
+     * 
+     * @param deviceInfoJson 设备信息JSON字符串
+     * @return bundle值
+     */
+    public String extractBundle(String deviceInfoJson) {
+        if (deviceInfoJson == null || deviceInfoJson.trim().isEmpty()) {
+            return null;
+        }
+        
+        try {
+            JsonNode rootNode = objectMapper.readTree(deviceInfoJson);
+            JsonNode bundleNode = rootNode.get("bundle");
+            if (bundleNode != null && !bundleNode.isNull()) {
+                return bundleNode.asText();
+            }
+        } catch (Exception e) {
+            log.error("解析设备信息bundle失败,deviceInfo:{}", deviceInfoJson, e);
+        }
+        
+        return null;
+    }
+    
+    /**
+     * 从设备信息JSON字符串中提取appId
+     * 
+     * @param deviceInfoJson 设备信息JSON字符串
+     * @return appId值
+     */
+    public String extractAppId(String deviceInfoJson) {
+        if (deviceInfoJson == null || deviceInfoJson.trim().isEmpty()) {
+            return null;
+        }
+        
+        try {
+            JsonNode rootNode = objectMapper.readTree(deviceInfoJson);
+            JsonNode appIdNode = rootNode.get("appId");
+            if (appIdNode != null && !appIdNode.isNull()) {
+                return appIdNode.asText();
+            }
+        } catch (Exception e) {
+            log.error("解析设备信息appId失败,deviceInfo:{}", deviceInfoJson, e);
+        }
+        
+        return null;
+    }
+
+    /**
+     * 从设备信息JSON字符串中提取bundle和appId
+     * 
+     * @param deviceInfoJson 设备信息JSON字符串
+     * @return 包含bundle和appId的数组,[0]为bundle,[1]为appId
+     */
+    public String[] extractBundleAndAppId(String deviceInfoJson) {
+        String[] result = new String[2];
+        result[0] = extractBundle(deviceInfoJson);
+        result[1] = extractAppId(deviceInfoJson);
+        return result;
+    }
+}

+ 187 - 0
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/util/IPLocationUtil.java

@@ -0,0 +1,187 @@
+package com.pig4cloud.pig.marketing.util;
+
+import com.pig4cloud.pig.common.core.util.ip.IPUtils;
+import com.pig4cloud.pig.marketing.config.PushValidationConfig;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.lionsoul.ip2region.xdb.Searcher;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * IP地址解析工具类
+ * 
+ * @author WCL
+ * @date 2025-09-04
+ * @description 根据IP地址获取地理位置信息
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class IPLocationUtil {
+
+    private final PushValidationConfig pushValidationConfig;
+    private final RedisTemplate<String, Object> redisTemplate;
+    
+    // ip2region搜索器,用于获取完整的三级地址信息
+    private static Searcher searcher;
+    
+    static {
+        initIpSearcher();
+    }
+    
+    /**
+     * 初始化IP地址索引
+     */
+    private static void initIpSearcher() {
+        try {
+            // 基于内存查询-从资源文件加载xdb数据库
+            String dbPath = IPLocationUtil.class.getClassLoader().getResource("ip/ip2region.xdb").getPath();
+            byte[] cBuff = Searcher.loadContentFromFile(dbPath);
+            searcher = Searcher.newWithBuffer(cBuff);
+            log.info("IPLocationUtil ip2region初始化成功");
+        } catch (Exception e) {
+            log.error("IPLocationUtil ip2region初始化失败", e);
+            searcher = null;
+        }
+    }
+
+    /**
+     * 根据IP地址获取三级地址信息
+     * 
+     * @param ip IP地址
+     * @return 三级地址信息,格式:国家-省-市,如:中国-广东省-深圳市
+     */
+    public String getCityByIP(String ip) {
+        if (ip == null || ip.trim().isEmpty()) {
+            return null;
+        }
+        
+        try {
+            // 检查是否启用IP定位
+            if (!pushValidationConfig.getIpLocation().getEnabled()) {
+                return null;
+            }
+            
+            // 检查缓存
+            String cacheKey = "ip:location:" + ip;
+            if (pushValidationConfig.getIpLocation().getEnableCache()) {
+                String cachedResult = (String) redisTemplate.opsForValue().get(cacheKey);
+                if (cachedResult != null) {
+                    log.debug("从缓存获取IP地址信息,IP:{},地址:{}", ip, cachedResult);
+                    return cachedResult;
+                }
+            }
+            
+            // 检查IP合法性
+            if (!IPUtils.isValidIp(ip)) {
+                log.warn("IP地址格式不正确:{}", ip);
+                return null;
+            }
+            
+            // 检查是否为内网IP
+            if (IPUtils.isIntranetIp(ip)) {
+                String result = "内网IP";
+                cacheResult(cacheKey, result);
+                return result;
+            }
+            
+            // 使用ip2region获取完整的三级地址信息
+            String fullAddress = getFullAddressFromIp2region(ip);
+            if (fullAddress != null) {
+                cacheResult(cacheKey, fullAddress);
+                return fullAddress;
+            }
+            
+            // 如果无法解析,返回null表示匹配不通过
+            log.warn("无法解析IP地址:{}", ip);
+            return null;
+            
+        } catch (Exception e) {
+            log.error("IP地址解析异常,IP:{}", ip, e);
+            return null;
+        }
+    }
+    
+    /**
+     * 使用ip2region获取完整的三级地址信息
+     * 
+     * @param ip IP地址
+     * @return 三级地址信息,格式:国家-省-市
+     */
+    private String getFullAddressFromIp2region(String ip) {
+        if (searcher == null) {
+            log.warn("ip2region搜索器未初始化,无法查询IP归属地");
+            return null;
+        }
+        
+        try {
+            String region = searcher.search(ip);
+            if (region == null || region.trim().isEmpty()) {
+                return null;
+            }
+            
+            // ip2region返回格式:国家|0|省份|城市|ISP
+            List<String> arr = cn.hutool.core.util.StrUtil.split(region, '|');
+            if (arr.size() >= 4) {
+                String country = "0".equals(arr.get(0)) ? null : arr.get(0);
+                String province = "0".equals(arr.get(2)) ? null : arr.get(2);
+                String city = "0".equals(arr.get(3)) ? null : arr.get(3);
+                
+                // 构建三级地址
+                StringBuilder address = new StringBuilder();
+                
+                if (country != null && !country.trim().isEmpty()) {
+                    address.append(country);
+                    
+                    if (province != null && !province.trim().isEmpty()) {
+                        address.append("-").append(province);
+                        
+                        if (city != null && !city.trim().isEmpty()) {
+                            address.append("-").append(city);
+                        } else {
+                            // 没有城市信息,使用省份作为城市
+                            address.append("-").append(province);
+                        }
+                    } else {
+                        // 没有省份信息,使用国家作为省份和城市
+                        address.append("-").append(country).append("-").append(country);
+                    }
+                } else {
+                    // 没有国家信息,无法解析
+                    return null;
+                }
+                
+                return address.toString();
+            }
+            
+        } catch (Exception e) {
+            log.warn("查询IP[{}]归属地出错", ip, e);
+        }
+        
+        return null;
+    }
+    
+    /**
+     * 缓存结果
+     * 
+     * @param cacheKey 缓存键
+     * @param result 结果
+     */
+    private void cacheResult(String cacheKey, String result) {
+        if (pushValidationConfig.getIpLocation().getEnableCache()) {
+            try {
+                redisTemplate.opsForValue().set(cacheKey, result, 
+                    pushValidationConfig.getIpLocation().getCacheExpireSeconds(), TimeUnit.SECONDS);
+            } catch (Exception e) {
+                log.warn("缓存IP地址信息失败,key:{},result:{}", cacheKey, result, e);
+            }
+        }
+    }
+    
+    
+    
+}

+ 24 - 17
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/util/PushFrequencyUtil.java

@@ -10,8 +10,8 @@ import java.util.concurrent.TimeUnit;
 /**
  * 推送频率工具类
  * 
- * @author pig4cloud
- * @date 2025-01-20
+ * @author WCL
+ * @date 2025-09-04
  * @description 提供推送频率检查的公共方法
  */
 @Slf4j
@@ -73,36 +73,43 @@ public class PushFrequencyUtil {
             return false;
         }
         
+        if (ruleId == null || ruleId <= 0) {
+            ruleId = 6L;
+        }
 
-        // 构建Redis key,使用规则ID和推送内容作为唯一标识
         String countKey = String.format("mkt:push:count:%d:%s", ruleId, clientId);
         
         try {
-            // Redis原子自增计数
-            Long currentCount = redisTemplate.opsForValue().increment(countKey);
-            if (currentCount == null) {
-                log.warn("Redis自增失败,规则ID:{},推送内容:{}", ruleId, clientId);
-                return false;
+            // 获取当前计数
+            String countStr = (String) redisTemplate.opsForValue().get(countKey);
+            Long currentCount = null;
+            
+            if (countStr != null) {
+                try {
+                    currentCount = Long.parseLong(countStr);
+                } catch (NumberFormatException e) {
+                    redisTemplate.delete(countKey);
+                }
             }
             
-            // 首次访问设置过期时间(24小时)
-            if (currentCount == 1) {
-                redisTemplate.expire(countKey, 24, TimeUnit.HOURS);
+            if (currentCount == null) {
+                // 第一次访问,设置计数为1
+                redisTemplate.opsForValue().set(countKey, "1", 24, TimeUnit.HOURS);
+                return true;
             }
             
-            // 达到周期次数时返回true并重置计数
-            if (currentCount % cycle == 0) {
+            if (currentCount == cycle) {
+                // 达到周期,重置并推送
                 redisTemplate.delete(countKey);
-                log.debug("规则{}达到推送周期{},重置计数", ruleId, cycle);
                 return true;
             }
             
-            log.debug("规则{}当前计数{},周期{},未达到推送条件", ruleId, currentCount, cycle);
+            // 未达到周期,计数+1
+            redisTemplate.opsForValue().set(countKey, String.valueOf(currentCount + 1), 24, TimeUnit.HOURS);
             return false;
             
         } catch (Exception e) {
-            log.error("Redis操作异常,规则ID:{},推送内容:{}", ruleId, clientId, e);
-            // Redis异常时默认推送,避免影响业务
+            log.error("Redis操作异常,规则ID:{},客户端ID:{}", ruleId, clientId, e);
             return true;
         }
     }