Browse Source

New: 新增应用营销数据统计功能,以及域名、IP格式校验

lwh 1 week ago
parent
commit
84ef8e18ec
13 changed files with 360 additions and 11 deletions
  1. 2 1
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/BusinessException.java
  2. 7 1
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/app/PageMarketingAppsDTO.java
  3. 51 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/app/PageStatAppMktDataDTO.java
  4. 0 5
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/config/AddMarketingConfigIpListDTO.java
  5. 0 1
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/valid/IPValidator.java
  6. 39 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/vo/app/PageStatAppFirstMktDataVO.java
  7. 39 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/vo/app/PageStatAppSecondMktDataVO.java
  8. 27 0
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/controller/MarketingAppsController.java
  9. 19 0
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/mapper/MarketingAppsMapper.java
  10. 15 0
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/MarketingAppsService.java
  11. 43 3
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MarketingAppsServiceImpl.java
  12. 22 0
      pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MarketingConfigServiceImpl.java
  13. 96 0
      pig-marketing/pig-marketing-biz/src/main/resources/mapper/MarketingAppsMapper.xml

+ 2 - 1
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/BusinessException.java

@@ -22,7 +22,8 @@ public class BusinessException extends CheckedException {
 	 * @param code 错误码(对应messages配置)
 	 */
 	public BusinessException(String code) {
-		super(MsgUtils.getMessage(code));
+//		super(MsgUtils.getMessage(code));
+		super(code);
 	}
 
 	/**

+ 7 - 1
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/app/PageMarketingAppsDTO.java

@@ -4,6 +4,9 @@ package com.pig4cloud.pig.marketing.api.dto.app;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.io.Serial;
+import java.io.Serializable;
+
 /**
  * @author: lwh
  * @date: 2025-07-22
@@ -11,7 +14,10 @@ import lombok.Data;
  */
 @Data
 @Schema(description = "分页查询应用列表入参")
-public class PageMarketingAppsDTO {
+public class PageMarketingAppsDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
 
 	/**
 	 * 每页显示条数,默认 10

+ 51 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/app/PageStatAppMktDataDTO.java

@@ -0,0 +1,51 @@
+package com.pig4cloud.pig.marketing.api.dto.app;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-07-30
+ * @description: 分页统计应用营销数据入参
+ */
+
+@Data
+@Schema(description = "分页统计应用营销数据入参")
+public class PageStatAppMktDataDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 每页显示条数,默认 10
+	 */
+	@Schema(description = "每页显示条数")
+	private long size = 10;
+
+	/**
+	 * 当前页
+	 */
+	@Schema(description = "当前页")
+	private long current = 1;
+
+	/**
+	 * 应用ID
+	 */
+	@Schema(description = "应用ID")
+	@NotBlank(message = "应用ID不能为空")
+	private String appId;
+
+	/**
+	 * 时间范围
+	 */
+	@Schema(description = "时间范围,0-全部,1-24小时,2-15分钟")
+	@NotNull(message = "时间范围不能为空")
+	private Integer timeRange;
+
+}

+ 0 - 5
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/dto/config/AddMarketingConfigIpListDTO.java

@@ -4,14 +4,9 @@ package com.pig4cloud.pig.marketing.api.dto.config;
 import com.pig4cloud.pig.marketing.api.dto.common.BaseIpDTO;
 import com.pig4cloud.pig.marketing.api.valid.IPValidation;
 import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotEmpty;
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Size;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
-import java.io.Serial;
-import java.io.Serializable;
 
 /**
  * @author: lwh

+ 0 - 1
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/valid/IPValidator.java

@@ -1,7 +1,6 @@
 package com.pig4cloud.pig.marketing.api.valid;
 
 
-import com.pig4cloud.pig.marketing.api.dto.app.MarketingAppsIpDTO;
 import com.pig4cloud.pig.marketing.api.dto.common.BaseIpDTO;
 import com.pig4cloud.pig.marketing.api.util.IpValidationUtil;
 import jakarta.validation.ConstraintValidator;

+ 39 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/vo/app/PageStatAppFirstMktDataVO.java

@@ -0,0 +1,39 @@
+package com.pig4cloud.pig.marketing.api.vo.app;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * @author: lwh
+ * @date: 2025-07-30
+ * @description: 分页统计应用一级营销数据
+ */
+@Data
+@Schema(description = "分页统计应用一级营销数据")
+public class PageStatAppFirstMktDataVO implements Serializable {
+
+	@Schema(description = "ip")
+	private String ip;
+
+	@Schema(description = "首次访问时间")
+	private LocalDateTime firstTime;
+
+	@Schema(description = "最后访问时间")
+	private LocalDateTime lastTime;
+
+	@Schema(description = "访问来源")
+	private String utmSource;
+
+	@Schema(description = "最后受访页面")
+	private String url;
+
+	@Schema(description = "地区")
+	private String region;
+
+	@Schema(description = "访问量")
+	private Integer  count;
+}

+ 39 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/vo/app/PageStatAppSecondMktDataVO.java

@@ -0,0 +1,39 @@
+package com.pig4cloud.pig.marketing.api.vo.app;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author: lwh
+ * @date: 2025-07-30
+ * @description: 分页统计应用二级营销数据
+ */
+@Data
+@Schema(description = "分页统计应用二级营销数据")
+public class PageStatAppSecondMktDataVO {
+
+	@Schema(description = "浏览器指纹")
+	private String fingerprint;
+
+	@Schema(description = "系统类型")
+	private String osType;
+
+	@Schema(description = "系统版本")
+	private String osVersion;
+
+	@Schema(description = "首次访问时间")
+	private LocalDateTime firstTime;
+
+	@Schema(description = "最后访问时间")
+	private LocalDateTime lastTime;
+
+	@Schema(description = "最后受访页面")
+	private String url;
+
+	@Schema(description = "访问量")
+	private Integer  count;
+
+}

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

@@ -8,9 +8,11 @@ import com.pig4cloud.pig.marketing.api.dto.app.*;
 import com.pig4cloud.pig.marketing.api.vo.app.PageMarketingAppsVO;
 import com.pig4cloud.pig.marketing.service.MarketingAppsService;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
 import lombok.AllArgsConstructor;
 import org.springdoc.core.annotations.ParameterObject;
 import org.springframework.http.HttpHeaders;
@@ -131,4 +133,29 @@ public class MarketingAppsController {
 	public R modMarketingAppsIp(@Valid @RequestBody ModMarketingAppsDomainDTO reqDto) {
 		return marketingAppsService.modMarketingAppsDomains(reqDto);
 	}
+
+	/**
+	 * 分页统计应用一级营销数据
+	 * @param reqDto 查询参数
+	 * @return 一级营销数据
+	 */
+	@GetMapping("/stat/page")
+	@Operation(summary = "分页统计应用一级营销数据")
+	public R pageStatAppFirstMktData(@ParameterObject PageStatAppMktDataDTO reqDto) {
+		return R.ok(marketingAppsService.pageStatAppFirstMktData(reqDto));
+	}
+
+	/**
+	 * 分页统计应用二级营销数据
+	 * @param reqDto 统计参数
+	 * @return 二级营销数据
+	 */
+	@GetMapping("/stat/second/page")
+	@Operation(summary = "分页统计应用二级营销数据")
+	public R pageStatAppSecondMktData(@ParameterObject PageStatAppMktDataDTO reqDto,
+									  @ParameterObject
+									  @NotBlank(message = "ip不能为空")
+									  @Schema(description = "ip") String ip) {
+		return R.ok(marketingAppsService.pageStatAppSecondMktData(reqDto, ip));
+	}
 }

+ 19 - 0
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/mapper/MarketingAppsMapper.java

@@ -2,8 +2,15 @@ package com.pig4cloud.pig.marketing.mapper;
 
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.pig4cloud.pig.marketing.api.entity.MarketingApps;
+import com.pig4cloud.pig.marketing.api.vo.app.PageStatAppFirstMktDataVO;
+import com.pig4cloud.pig.marketing.api.vo.app.PageStatAppSecondMktDataVO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * @author: lwh
@@ -12,4 +19,16 @@ import org.apache.ibatis.annotations.Mapper;
  */
 @Mapper
 public interface MarketingAppsMapper extends BaseMapper<MarketingApps> {
+
+	List<PageStatAppFirstMktDataVO> statAppFirstMktData(
+			@Param("page") Page<PageStatAppFirstMktDataVO> page,
+			@Param("appId") String appId,
+			@Param("startTime") LocalDateTime startTime
+	);
+
+	List<PageStatAppSecondMktDataVO> statAppSecondMktData(
+			@Param("page") Page<PageStatAppSecondMktDataVO> page,
+			@Param("appId") String appId,
+			@Param("ip") String ip,
+			@Param("startTime") LocalDateTime startTime);
 }

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

@@ -72,4 +72,19 @@ public interface MarketingAppsService {
 	 * @return R
 	 */
 	R modMarketingAppsDomains(@Valid ModMarketingAppsDomainDTO reqDto);
+
+	/**
+	 * 分页统计应用一级营销数据
+	 * @param reqDto 查询参数
+	 * @return 一级营销数据
+	 */
+	Page pageStatAppFirstMktData(PageStatAppMktDataDTO reqDto);
+
+	/**
+	 * 分页统计应用二级营销数据
+	 * @param reqDto 统计参数
+	 * @param ip ip
+	 * @return 二级营销数据
+	 */
+	Page pageStatAppSecondMktData(PageStatAppMktDataDTO reqDto, String ip);
 }

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

@@ -12,9 +12,7 @@ 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.entity.*;
-import com.pig4cloud.pig.marketing.api.vo.app.MarketingAppsDomainVO;
-import com.pig4cloud.pig.marketing.api.vo.app.MarketingAppsIpVO;
-import com.pig4cloud.pig.marketing.api.vo.app.PageMarketingAppsVO;
+import com.pig4cloud.pig.marketing.api.vo.app.*;
 import com.pig4cloud.pig.marketing.api.vo.config.GetMarketingGlobalConfigVO;
 import com.pig4cloud.pig.marketing.mapper.*;
 import com.pig4cloud.pig.marketing.service.MarketingAppsService;
@@ -27,6 +25,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.io.IOException;
+import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -369,6 +368,47 @@ public class MarketingAppsServiceImpl implements MarketingAppsService {
 		return R.ok();
 	}
 
+	/**
+	 * 分页统计应用一级营销数据
+	 * @param reqDto 查询参数
+	 * @return 一级营销数据
+	 */
+	@Override
+	public Page pageStatAppFirstMktData(PageStatAppMktDataDTO reqDto) {
+		Page<PageStatAppFirstMktDataVO> page = new Page<>(reqDto.getCurrent(), reqDto.getSize());
+
+		LocalDateTime startTime = switch (reqDto.getTimeRange()) {
+			case 1 -> LocalDateTime.now().minusHours(24);
+			case 2 -> LocalDateTime.now().minusMinutes(15);
+			default -> null;
+		};
+		List<PageStatAppFirstMktDataVO> records = appsMapper.statAppFirstMktData(page, reqDto.getAppId(), startTime);
+		page.setRecords(records);
+		return page;
+	}
+
+	/**
+	 * 分页统计应用二级营销数据
+	 * @param reqDto 统计参数
+	 * @return 二级营销数据
+	 */
+	@Override
+	public Page pageStatAppSecondMktData(PageStatAppMktDataDTO reqDto, String ip) {
+		Page<PageStatAppSecondMktDataVO> page = new Page<>(reqDto.getCurrent(), reqDto.getSize());
+
+		// 根据时间范围计算开始时间
+		LocalDateTime startTime = switch (reqDto.getTimeRange()) {
+			case 1 -> LocalDateTime.now().minusHours(24);
+			case 2 -> LocalDateTime.now().minusMinutes(15);
+			default -> null;
+		};
+
+		// 调用mapper查询,自动填充分页信息
+		List<PageStatAppSecondMktDataVO> records = appsMapper.statAppSecondMktData(page, reqDto.getAppId(), ip, startTime);
+		page.setRecords(records);
+		return page;
+	}
+
 	/**
 	 * 从lafa服务获取应用列表
 	 * @return 应用列表

+ 22 - 0
pig-marketing/pig-marketing-biz/src/main/java/com/pig4cloud/pig/marketing/service/impl/MarketingConfigServiceImpl.java

@@ -10,6 +10,8 @@ import com.pig4cloud.pig.common.core.util.R;
 import com.pig4cloud.pig.marketing.api.dto.app.MarketingAppsIpDTO;
 import com.pig4cloud.pig.marketing.api.dto.config.*;
 import com.pig4cloud.pig.marketing.api.entity.*;
+import com.pig4cloud.pig.marketing.api.util.DomainValidationUtil;
+import com.pig4cloud.pig.marketing.api.util.IpValidationUtil;
 import com.pig4cloud.pig.marketing.api.vo.config.*;
 import com.pig4cloud.pig.marketing.mapper.*;
 import com.pig4cloud.pig.marketing.service.MarketingConfigService;
@@ -181,6 +183,12 @@ public class MarketingConfigServiceImpl implements MarketingConfigService {
 		// 更新域名信息
 		List<MarketingGroupDomainDTO> domains = reqDto.getDomains();
 		for (MarketingGroupDomainDTO domainDto : domains) {
+
+			// 域名格式校验
+			if(!DomainValidationUtil.isValidDomain(domainDto.getDomain())){
+				throw new BusinessException("域名格式不合法(支持字母、数字、下划线、横线、点)");
+			}
+
 			// 新增、修改
 			MarketingGroupDomain domainExist = groupDomainMapper.selectOne(Wrappers.<MarketingGroupDomain>lambdaQuery()
 					.eq(MarketingGroupDomain::getDomain, domainDto.getDomain()));
@@ -269,6 +277,20 @@ public class MarketingConfigServiceImpl implements MarketingConfigService {
 				}
 				throw new BusinessException("ip:"+ipDto.getStartIp()+"/"+ipDto.getEndIp()+"已存在");
 			}
+
+			// ip格式校验
+			if (!IpValidationUtil.isValidIp(ipDto.getStartIp())){
+				throw new BusinessException("IP格式不合法(IPv4)");
+			}
+			if (ipDto.getIpMode() == 2){
+				if (!IpValidationUtil.isValidIp(ipDto.getEndIp())){
+					throw new BusinessException("结束IP格式不合法(IPv4)");
+				}
+				if (!IpValidationUtil.isEndIpGreater(ipDto.getStartIp(), ipDto.getEndIp())){
+					throw new BusinessException("结束IP不能小于开始IP");
+				}
+			}
+
 			 if (ipDto.getId() == null){
 				 // 新增
 				 MarketingGroupIp groupIp = new MarketingGroupIp();

+ 96 - 0
pig-marketing/pig-marketing-biz/src/main/resources/mapper/MarketingAppsMapper.xml

@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~
+  ~      Copyright (c) 2018-2025, lengleng All rights reserved.
+  ~
+  ~  Redistribution and use in source and binary forms, with or without
+  ~  modification, are permitted provided that the following conditions are met:
+  ~
+  ~ Redistributions of source code must retain the above copyright notice,
+  ~  this list of conditions and the following disclaimer.
+  ~  Redistributions in binary form must reproduce the above copyright
+  ~  notice, this list of conditions and the following disclaimer in the
+  ~  documentation and/or other materials provided with the distribution.
+  ~  Neither the name of the pig4cloud.com developer nor the names of its
+  ~  contributors may be used to endorse or promote products derived from
+  ~  this software without specific prior written permission.
+  ~  Author: lengleng ([email protected])
+  ~
+  -->
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.pig4cloud.pig.marketing.mapper.MarketingAppsMapper">
+
+
+	<select id="statAppFirstMktData" resultType="com.pig4cloud.pig.marketing.api.vo.app.PageStatAppFirstMktDataVO">
+		SELECT
+			ip,
+			MIN(create_time) AS firstTime,
+			MAX(create_time) AS lastTime,
+			COUNT(*) AS count,
+			(SELECT utm_source FROM marketing_data
+			WHERE ip = md.ip
+			AND app_id = #{appId}
+			<if test="startTime != null">AND create_time >= #{startTime}</if>
+			AND del_flag = '0'
+			ORDER BY create_time DESC LIMIT 1) AS utmSource,
+			(SELECT url FROM marketing_data
+			WHERE ip = md.ip
+			AND app_id = #{appId}
+			<if test="startTime != null">AND create_time >= #{startTime}</if>
+			AND del_flag = '0'
+			AND url IS NOT NULL
+			ORDER BY create_time DESC, id DESC LIMIT 1) AS url
+		FROM marketing_data md
+		WHERE
+			md.app_id = #{appId}
+			AND md.del_flag = '0'
+			<if test="startTime != null">
+				AND md.create_time >= #{startTime}
+			</if>
+		GROUP BY md.ip
+		ORDER BY lastTime DESC
+	</select>
+	<select id="statAppSecondMktData" resultType="com.pig4cloud.pig.marketing.api.vo.app.PageStatAppSecondMktDataVO">
+		SELECT
+			fingerprint,
+			MIN(create_time) AS firstTime,
+			MAX(create_time) AS lastTime,
+			COUNT(*) AS count,
+
+			(SELECT url FROM marketing_data
+			WHERE fingerprint = md.fingerprint
+			AND ip = #{ip}
+			AND app_id = #{appId}
+			<if test="startTime != null">AND create_time >= #{startTime}</if>
+			AND del_flag = '0'
+			ORDER BY create_time DESC, id DESC LIMIT 1) AS url,
+
+			(SELECT os_type FROM marketing_data
+			WHERE fingerprint = md.fingerprint
+			AND ip = #{ip}
+			AND app_id = #{appId}
+			<if test="startTime != null">AND create_time >= #{startTime}</if>
+			AND del_flag = '0'
+			ORDER BY create_time DESC, id DESC LIMIT 1) AS osType,
+
+			(SELECT os_version FROM marketing_data
+			WHERE fingerprint = md.fingerprint
+			AND ip = #{ip}
+			AND app_id = #{appId}
+			<if test="startTime != null">AND create_time >= #{startTime}</if>
+			AND del_flag = '0'
+			ORDER BY create_time DESC, id DESC LIMIT 1) AS osVersion
+		FROM marketing_data md
+		WHERE
+			md.app_id = #{appId}
+			AND md.ip = #{ip}
+			AND md.del_flag = '0'
+			<if test="startTime != null">
+				AND md.create_time >= #{startTime}
+			</if>
+			AND md.fingerprint IS NOT NULL
+		GROUP BY md.fingerprint
+		ORDER BY lastTime DESC
+	</select>
+</mapper>