Bladeren bron

Merge branch 'dev/wcl' of https://s-20coaj910c.zht2.com/lwh/seo into dev/lwh

lwh 2 weken geleden
bovenliggende
commit
018cd65edb

+ 33 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/MktMgmtPushRecordQueryDTO.java

@@ -0,0 +1,33 @@
+package com.pig4cloud.pig.marketing.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.Min;
+import java.io.Serializable;
+
+/**
+ * @author: wcl
+ * @date: 2025-08-31
+ * @description: 营销推送记录分页查询DTO
+ */
+@Data
+@Schema(description = "营销推送记录分页查询DTO")
+public class MktMgmtPushRecordQueryDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "规则名称", example = "用户激活推送")
+    private String ruleName;
+
+    @Schema(description = "触发信息")
+    private String triggerCondition;
+
+    @Schema(description = "页码", example = "1")
+    @Min(value = 1, message = "页码不能小于1")
+    private Integer pageNum = 1;
+
+    @Schema(description = "每页大小", example = "10")
+    @Min(value = 1, message = "每页大小不能小于1")
+    private Integer pageSize = 10;
+}

+ 32 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/MktMgmtPushRecordSaveDTO.java

@@ -0,0 +1,32 @@
+package com.pig4cloud.pig.marketing.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author: wcl
+ * @date: 2025-08-31
+ * @description: 营销推送记录新增DTO
+ */
+@Data
+@Schema(description = "营销推送记录新增DTO")
+public class MktMgmtPushRecordSaveDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+
+    @Schema(description = "推送内容", example = "欢迎使用我们的应用!")
+    @NotBlank(message = "推送内容不能为空")
+    private String pushContent;
+
+    @Schema(description = "推送IP", example = "127.0.0.1")
+    private String pushIP;
+
+    @Schema(description = "推送域名", example = "baidu.com")
+    private String pushDomain;
+
+
+}

+ 30 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/vo/mongo/UserStatisticsVO.java

@@ -0,0 +1,30 @@
+package com.pig4cloud.pig.marketing.api.vo.mongo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author: wcl
+ * @date: 2025-08-31
+ * @description: 用户统计VO
+ */
+@Data
+@Schema(description = "用户统计信息")
+public class UserStatisticsVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "累计用户数", example = "1000")
+    private Long totalUsers;
+
+    @Schema(description = "活跃用户数", example = "150")
+    private Long activeUsers;
+
+    @Schema(description = "统计时间", example = "2025-08-31 10:00:00")
+    private String statisticsTime;
+
+    @Schema(description = "活跃时间范围(分钟)", example = "10")
+    private Integer activeTimeRange;
+}

+ 28 - 0
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/config/UserStatisticsConfig.java

@@ -0,0 +1,28 @@
+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-08-31
+ * @description: 用户统计配置
+ */
+@Data
+@Component
+@RefreshScope
+@ConfigurationProperties(prefix = "user.statistics")
+public class UserStatisticsConfig {
+
+    /**
+     * 活跃用户统计时间范围(分钟)
+     */
+    private Integer activeTimeRangeMinutes = 10;
+
+    /**
+     * 是否启用用户统计
+     */
+    private Boolean enabled = true;
+}

+ 54 - 4
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/controller/MktMgmtPushRecordController.java

@@ -1,19 +1,27 @@
 package com.pig4cloud.pig.marketing.controller;
 
-
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pig.common.core.util.R;
+import com.pig4cloud.pig.marketing.api.dto.MktMgmtPushRecordQueryDTO;
+import com.pig4cloud.pig.marketing.api.dto.MktMgmtPushRecordSaveDTO;
+import com.pig4cloud.pig.marketing.api.entity.MktMgmtPushRecord;
 import com.pig4cloud.pig.marketing.service.MktMgmtPushRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpHeaders;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 /**
- * @author: lwh
+ * @author: wcl
  * @date: 2025-08-31
  * @description: 营销推送记录
  */
+@Slf4j
 @RestController
 @AllArgsConstructor
 @RequestMapping("/push")
@@ -23,5 +31,47 @@ public class MktMgmtPushRecordController {
 
 	private final MktMgmtPushRecordService mktMgmtPushRecordService;
 
+	/**
+	 * 分页查询营销推送记录
+	 * @param queryDTO 查询条件
+	 * @return 分页结果
+	 */
+	@PostMapping("/page")
+	@Operation(summary = "分页查询营销推送记录", description = "根据条件分页查询营销推送记录")
+	public R<Page<MktMgmtPushRecord>> pageQuery(
+			@Parameter(description = "查询条件") @RequestBody @Valid MktMgmtPushRecordQueryDTO queryDTO) {
+		log.info("分页查询营销推送记录,查询条件:{}", queryDTO);
+		
+		try {
+			Page<MktMgmtPushRecord> result = mktMgmtPushRecordService.pageQuery(queryDTO);
+			return R.ok(result);
+		} catch (Exception e) {
+			log.error("分页查询营销推送记录异常", e);
+			return R.failed("查询失败:" + e.getMessage());
+		}
+	}
 
+	/**
+	 * 新增营销推送记录
+	 * @param saveDTO 新增数据
+	 * @return 操作结果
+	 */
+	@PostMapping("/save")
+	@Operation(summary = "新增营销推送记录", description = "新增一条营销推送记录")
+	public R<Boolean> saveRecord(
+			@Parameter(description = "新增数据") @RequestBody @Valid MktMgmtPushRecordSaveDTO saveDTO) {
+		log.info("新增营销推送记录,数据:{}", saveDTO);
+		
+		try {
+			Boolean result = mktMgmtPushRecordService.saveRecord(saveDTO);
+			if (result) {
+				return R.ok(true, "新增成功");
+			} else {
+				return R.failed("新增失败");
+			}
+		} catch (Exception e) {
+			log.error("新增营销推送记录异常", e);
+			return R.failed("新增失败:" + e.getMessage());
+		}
+	}
 }

+ 17 - 0
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/controller/TcpDataController.java

@@ -10,6 +10,7 @@ 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.vo.mongo.PageDeviceInfoVO;
+import com.pig4cloud.pig.marketing.api.vo.mongo.UserStatisticsVO;
 import com.pig4cloud.pig.marketing.service.TcpDataService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -17,6 +18,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotBlank;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springdoc.core.annotations.ParameterObject;
 import org.springframework.http.HttpHeaders;
 import org.springframework.web.bind.annotation.*;
@@ -27,6 +29,7 @@ import org.springframework.web.bind.annotation.*;
  * @description: TCP数据控制器
  */
 
+@Slf4j
 @RestController
 @AllArgsConstructor
 @RequestMapping("/tcp")
@@ -75,4 +78,18 @@ public class TcpDataController {
 	public R<Page<Message>> PageMsgInfo(@Valid @RequestBody PageMessageDTO reqDto) {
 		return R.ok(tcpDataService.pageMsgInfo(reqDto));
 	}
+
+	/******************************************* 统计 *******************************************/
+	@GetMapping("/statistics/users")
+	@Operation(summary = "统计用户数", description = "统计累计用户数和活跃用户数")
+	public R<UserStatisticsVO> getUserStatistics() {
+		log.info("开始统计用户数");
+		try {
+			UserStatisticsVO statistics = tcpDataService.getUserStatistics();
+			return R.ok(statistics);
+		} catch (Exception e) {
+			log.error("统计用户数异常", e);
+			return R.failed("统计失败:" + e.getMessage());
+		}
+	}
 }

+ 19 - 2
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/MktMgmtPushRecordService.java

@@ -1,11 +1,28 @@
 package com.pig4cloud.pig.marketing.service;
 
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pig.marketing.api.dto.MktMgmtPushRecordQueryDTO;
+import com.pig4cloud.pig.marketing.api.dto.MktMgmtPushRecordSaveDTO;
+import com.pig4cloud.pig.marketing.api.entity.MktMgmtPushRecord;
 
 /**
- * @author: lwh
+ * @author: wcl
  * @date: 2025-08-31
  * @description: 营销推送记录Service
  */
-
 public interface MktMgmtPushRecordService {
+
+    /**
+     * 分页查询营销推送记录
+     * @param queryDTO 查询条件
+     * @return 分页结果
+     */
+    Page<MktMgmtPushRecord> pageQuery(MktMgmtPushRecordQueryDTO queryDTO);
+
+    /**
+     * 新增营销推送记录
+     * @param saveDTO 新增数据
+     * @return 是否成功
+     */
+    Boolean saveRecord(MktMgmtPushRecordSaveDTO saveDTO);
 }

+ 9 - 1
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/TcpDataService.java

@@ -9,6 +9,7 @@ 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.vo.mongo.PageDeviceInfoVO;
+import com.pig4cloud.pig.marketing.api.vo.mongo.UserStatisticsVO;
 
 /**
  * @author: lwh
@@ -40,7 +41,8 @@ public interface TcpDataService {
 	Page<PageDeviceInfoVO> pageDeviceInfo(PageDeviceInfoDTO reqDto);
 
 
-	/******************************************* 消息 *******************************************
+	/******************************************* 消息 *******************************************/
+	/**
 	 * 保存上报数据
 	 * @param reqDto 入参
 	 * @return 数据ID
@@ -60,4 +62,10 @@ public interface TcpDataService {
 	 * @return 数据列表
 	 */
 	Page<Message> pageMsgInfo(PageMessageDTO reqDto);
+
+	/**
+	 * 统计用户数
+	 * @return 用户统计信息
+	 */
+	UserStatisticsVO getUserStatistics();
 }

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

@@ -1,11 +1,22 @@
 package com.pig4cloud.pig.marketing.service.impl;
 
-
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pig.marketing.api.dto.MktMgmtPushRecordQueryDTO;
+import com.pig4cloud.pig.marketing.api.dto.MktMgmtPushRecordSaveDTO;
+import com.pig4cloud.pig.marketing.api.entity.MktMgmtKeyword;
+import com.pig4cloud.pig.marketing.api.entity.MktMgmtPushRecord;
+import com.pig4cloud.pig.marketing.api.entity.MktMgmtRule;
+import com.pig4cloud.pig.marketing.mapper.MktMgmtKeywordMapper;
 import com.pig4cloud.pig.marketing.mapper.MktMgmtPushRecordMapper;
+import com.pig4cloud.pig.marketing.mapper.MktMgmtRuleMapper;
 import com.pig4cloud.pig.marketing.service.MktMgmtPushRecordService;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
 
 /**
  * @author: lwh
@@ -18,4 +29,213 @@ import org.springframework.stereotype.Service;
 public class MktMgmtPushRecordServiceImpl implements MktMgmtPushRecordService {
 
 	private final MktMgmtPushRecordMapper mktMgmtPushRecordMapper;
+	private final MktMgmtKeywordMapper mktMgmtKeywordMapper;
+	private final MktMgmtRuleMapper mktMgmtRuleMapper;
+
+	@Override
+	public Page<MktMgmtPushRecord> pageQuery(MktMgmtPushRecordQueryDTO queryDTO) {
+		log.info("分页查询营销推送记录,查询条件:{}", queryDTO);
+		
+		// 构建查询条件
+		LambdaQueryWrapper<MktMgmtPushRecord> queryWrapper = new LambdaQueryWrapper<>();
+		
+		// 规则名称模糊查询
+		if (StringUtils.hasText(queryDTO.getRuleName())) {
+			queryWrapper.like(MktMgmtPushRecord::getRuleName, queryDTO.getRuleName());
+		}
+		
+		// 触发条件模糊查询
+		if (StringUtils.hasText(queryDTO.getTriggerCondition())) {
+			queryWrapper.like(MktMgmtPushRecord::getTriggerCondition, queryDTO.getTriggerCondition());
+		}
+
+		queryWrapper.eq(MktMgmtPushRecord::getDelFlag, "0");
+		
+		// 按创建时间倒序排列
+		queryWrapper.orderByDesc(MktMgmtPushRecord::getCreateTime);
+		
+		// 执行分页查询
+		Page<MktMgmtPushRecord> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
+		Page<MktMgmtPushRecord> result = mktMgmtPushRecordMapper.selectPage(page, queryWrapper);
+		
+		log.info("分页查询完成,总记录数:{},当前页记录数:{}", result.getTotal(), result.getRecords().size());
+		return result;
+	}
+
+	@Override
+	public Boolean saveRecord(MktMgmtPushRecordSaveDTO saveDTO) {
+		log.info("新增营销推送记录,数据:{}", saveDTO);
+		
+		try {
+			// 1. 根据推送内容匹配关键字
+			List<MktMgmtKeyword> keywords = findMatchingKeywords(saveDTO.getPushContent());
+			if (keywords.isEmpty()) {
+				log.warn("推送内容未匹配到任何关键字:{}", saveDTO.getPushContent());
+				return false;
+			}
+			
+			// 2. 遍历匹配的关键字,查找对应的规则
+			for (MktMgmtKeyword keyword : keywords) {
+				MktMgmtRule rule = mktMgmtRuleMapper.selectById(keyword.getRuleId());
+				if (rule == null || "1".equals(rule.getDelFlag())) {
+					log.warn("关键字对应的规则不存在或已删除,ruleId:{}", keyword.getRuleId());
+					continue;
+				}
+				
+				// 3. 验证IP地址
+				if (!validateIPAddress(rule, saveDTO.getPushIP())) {
+					log.warn("IP地址验证失败,规则:{},推送IP:{}", rule.getRuleName(), saveDTO.getPushIP());
+					continue;
+				}
+				
+				// 4. 验证域名
+				if (!validateDomain(rule, saveDTO.getPushDomain())) {
+					log.warn("域名验证失败,规则:{},推送域名:{}", rule.getRuleName(), saveDTO.getPushDomain());
+					continue;
+				}
+				
+				// 5. 构建触发条件描述
+				String triggerCondition = buildTriggerCondition(keyword.getKeyword(), rule, saveDTO.getPushIP(), saveDTO.getPushDomain());
+				
+				// 6. 构建推送详情
+				String pushDetail = buildPushDetail(triggerCondition, saveDTO.getPushContent());
+				
+				// 7. 创建推送记录
+				MktMgmtPushRecord record = new MktMgmtPushRecord();
+				record.setRuleName(rule.getRuleName());
+				record.setPushFrequency(rule.getPushFrequency());
+				record.setPushStatus(true); // 总为1-成功
+				record.setTriggerCondition(triggerCondition);
+				record.setPushDetail(pushDetail);
+				record.setDelFlag("0"); // 未删除
+				
+				// 8. 保存到数据库
+				int result = mktMgmtPushRecordMapper.insert(record);
+				if (result > 0) {
+					log.info("营销推送记录新增成功,ID:{},规则:{}", record.getId(), rule.getRuleName());
+					return true;
+				}
+			}
+			
+			log.warn("未找到匹配的规则或保存失败");
+			return false;
+			
+		} catch (Exception e) {
+			log.error("新增营销推送记录异常", e);
+			return false;
+		}
+	}
+	
+	/**
+	 * 根据推送内容查找匹配的关键字
+	 */
+	private List<MktMgmtKeyword> findMatchingKeywords(String pushContent) {
+		if (!StringUtils.hasText(pushContent)) {
+			return List.of();
+		}
+		
+		// 查询所有未删除的关键字
+		LambdaQueryWrapper<MktMgmtKeyword> queryWrapper = new LambdaQueryWrapper<>();
+		queryWrapper.eq(MktMgmtKeyword::getDelFlag, "0");
+		List<MktMgmtKeyword> allKeywords = mktMgmtKeywordMapper.selectList(queryWrapper);
+		
+		// 过滤匹配的关键字
+		return allKeywords.stream()
+				.filter(keyword -> pushContent.contains(keyword.getKeyword()))
+				.toList();
+	}
+	
+	/**
+	 * 验证IP地址
+	 */
+	private boolean validateIPAddress(MktMgmtRule rule, String pushIP) {
+		if (rule.getIpMode() == null || rule.getIpMode() == 0) {
+			// IP模式为0-空,不判断IP
+			return true;
+		}
+		
+		if (!StringUtils.hasText(pushIP)) {
+			return false;
+		}
+		
+		if (rule.getIpMode() == 1) {
+			// IP模式为1-单IP,判断推送IP是否为start_ip
+			return pushIP.equals(rule.getStartIp());
+		} else if (rule.getIpMode() == 2) {
+			// IP模式为2-IP段,判断推送IP是否在start_ip和end_ip之间
+			if (!StringUtils.hasText(rule.getStartIp()) || !StringUtils.hasText(rule.getEndIp())) {
+				return false;
+			}
+			
+			try {
+				long pushIpLong = ipToLong(pushIP);
+				long startIpLong = ipToLong(rule.getStartIp());
+				long endIpLong = ipToLong(rule.getEndIp());
+				
+				return pushIpLong >= startIpLong && pushIpLong <= endIpLong;
+			} catch (Exception e) {
+				log.error("IP地址转换异常,pushIP:{},startIp:{},endIp:{}", pushIP, rule.getStartIp(), rule.getEndIp(), e);
+				return false;
+			}
+		}
+		
+		return false;
+	}
+	
+	/**
+	 * 验证域名
+	 */
+	private boolean validateDomain(MktMgmtRule rule, String pushDomain) {
+		// 如果规则中域名为空,则不判断域名
+		if (!StringUtils.hasText(rule.getDomain())) {
+			return true;
+		}
+		
+		// 如果推送域名为空,则验证失败
+		if (!StringUtils.hasText(pushDomain)) {
+			return false;
+		}
+		
+		// 判断推送域名是否为规则的域名
+		return rule.getDomain().equals(pushDomain);
+	}
+	
+	/**
+	 * 构建触发条件描述
+	 */
+	private String buildTriggerCondition(String keyword, MktMgmtRule rule, String pushIP, String pushDomain) {
+		StringBuilder triggerCondition = new StringBuilder();
+		triggerCondition.append("关键字:").append(keyword);
+		
+		// 添加IP信息
+		if (rule.getIpMode() != null && rule.getIpMode() > 0 && StringUtils.hasText(pushIP)) {
+			triggerCondition.append(",IP:").append(pushIP);
+		}
+		
+		// 添加域名信息
+		if (StringUtils.hasText(rule.getDomain()) && StringUtils.hasText(pushDomain)) {
+			triggerCondition.append(",域名:").append(pushDomain);
+		}
+		
+		return triggerCondition.toString();
+	}
+	
+	/**
+	 * 构建推送详情
+	 */
+	private String buildPushDetail(String triggerCondition, String pushContent) {
+		return "触发条件:" + triggerCondition + ",推送内容:" + pushContent;
+	}
+	
+	/**
+	 * IP地址转Long(便于比较大小)
+	 */
+	private long ipToLong(String ip) {
+		String[] parts = ip.split("\\.");
+		long result = 0;
+		for (int i = 0; i < 4; i++) {
+			result |= Long.parseLong(parts[i]) << (24 - i * 8);
+		}
+		return result;
+	}
 }

+ 99 - 6
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/TcpDataServiceImpl.java

@@ -9,6 +9,8 @@ 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.vo.mongo.PageDeviceInfoVO;
+import com.pig4cloud.pig.marketing.api.vo.mongo.UserStatisticsVO;
+import com.pig4cloud.pig.marketing.config.UserStatisticsConfig;
 import com.pig4cloud.pig.marketing.repository.MessageRepository;
 import com.pig4cloud.pig.marketing.service.TcpDataService;
 import lombok.AllArgsConstructor;
@@ -36,14 +38,16 @@ import java.util.Objects;
  * @description: TCP数据服务实现类
  */
 
-@Slf4j
-@Service
-@AllArgsConstructor
-public class TcpDataServiceImpl implements TcpDataService {
+	@Slf4j
+	@Service
+	@AllArgsConstructor
+	public class TcpDataServiceImpl implements TcpDataService {
 
-	private final MongoTemplate mongoTemplate;
+		private final MongoTemplate mongoTemplate;
 
-	private final MessageRepository messageRepository;
+		private final MessageRepository messageRepository;
+
+		private final UserStatisticsConfig userStatisticsConfig;
 
 	// 定义时间格式器
 	private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@@ -281,4 +285,93 @@ public class TcpDataServiceImpl implements TcpDataService {
 		return mongoTemplate.count(query, Message.class);
 
 	}
+
+	/**
+	 * 统计用户数
+	 * @return 用户统计信息
+	 */
+	@Override
+	public UserStatisticsVO getUserStatistics() {
+		log.info("开始统计用户数");
+		
+		try {
+			// 1. 统计累计用户数
+			Long totalUsers = getTotalUsers();
+			
+			// 2. 统计活跃用户数
+			Long activeUsers = getActiveUsers();
+			
+			// 3. 构建返回结果
+			UserStatisticsVO statisticsVO = new UserStatisticsVO();
+			statisticsVO.setTotalUsers(totalUsers);
+			statisticsVO.setActiveUsers(activeUsers);
+			statisticsVO.setStatisticsTime(LocalDateTime.now().format(DATE_TIME_FORMATTER));
+			statisticsVO.setActiveTimeRange(userStatisticsConfig.getActiveTimeRangeMinutes());
+			
+			log.info("用户统计完成,累计用户数:{},活跃用户数:{}", totalUsers, activeUsers);
+			return statisticsVO;
+			
+		} catch (Exception e) {
+			log.error("统计用户数异常", e);
+			// 返回默认值
+			UserStatisticsVO statisticsVO = new UserStatisticsVO();
+			statisticsVO.setTotalUsers(0L);
+			statisticsVO.setActiveUsers(0L);
+			statisticsVO.setStatisticsTime(LocalDateTime.now().format(DATE_TIME_FORMATTER));
+			statisticsVO.setActiveTimeRange(userStatisticsConfig.getActiveTimeRangeMinutes());
+			return statisticsVO;
+		}
+	}
+	
+	/**
+	 * 获取累计用户数
+	 * @return 累计用户数
+	 */
+	private Long getTotalUsers() {
+		// 使用MongoDB的distinct操作获取去重的clientID数量
+		List<String> distinctClientIDs = mongoTemplate.findDistinct(
+			new Query(), 
+			"clientID", 
+			Device.class, 
+			String.class
+		);
+		
+		// 过滤掉null和空字符串
+		long count = distinctClientIDs.stream()
+			.filter(clientID -> clientID != null && !clientID.trim().isEmpty())
+			.count();
+			
+		log.debug("累计用户数统计完成,去重后的clientID数量:{}", count);
+		return count;
+	}
+	
+	/**
+	 * 获取活跃用户数
+	 * @return 活跃用户数
+	 */
+	private Long getActiveUsers() {
+		// 计算活跃时间范围
+		LocalDateTime activeStartTime = LocalDateTime.now().minusMinutes(userStatisticsConfig.getActiveTimeRangeMinutes());
+		String activeStartTimeStr = activeStartTime.format(DATE_TIME_FORMATTER);
+		
+		// 创建查询条件:时间在活跃时间范围内
+		Criteria criteria = Criteria.where("reportTime").gte(activeStartTimeStr);
+		Query query = new Query(criteria);
+		
+		// 使用distinct操作获取去重的clientID数量
+		List<String> distinctClientIDs = mongoTemplate.findDistinct(
+			query, 
+			"clientID", 
+			Message.class, 
+			String.class
+		);
+		
+		// 过滤掉null和空字符串
+		long count = distinctClientIDs.stream()
+			.filter(clientID -> clientID != null && !clientID.trim().isEmpty())
+			.count();
+			
+		log.debug("活跃用户数统计完成,时间范围:{} 至现在,去重后的clientID数量:{}", activeStartTimeStr, count);
+		return count;
+	}
 }