## Feat - Top 30 (直推人数/直推金额)

This commit is contained in:
sangelxiu1 2025-09-23 16:52:09 +08:00
parent 63574c6fb9
commit a23fd0fe07
13 changed files with 1023 additions and 0 deletions

View File

@ -0,0 +1,36 @@
package com.hzs.member.statis.controller.api;
import com.hzs.common.core.constant.CacheConstants;
import com.hzs.common.core.enums.ETop30Type;
import com.hzs.common.core.service.RedisService;
import com.hzs.common.core.web.domain.AjaxResult;
import com.hzs.common.domain.member.ext.CuMemberHonorWallExt;
import com.hzs.common.security.utils.SecurityUtils;
import com.hzs.member.statis.param.DirectStatisticsTop30QueryParam;
import com.hzs.member.statis.service.IMemberStatisticsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 统计
*/
@Slf4j
@RequestMapping("/api/member-statistics")
@RestController
public class MemberStatisticsController {
@Resource
private IMemberStatisticsService memberStatisticsService;
@PostMapping("/topPeople")
public AjaxResult topPeople(@RequestBody DirectStatisticsTop30QueryParam param) {
return AjaxResult.success(memberStatisticsService.getDirectStatisticsNumberOfPeople(param));
}
@PostMapping("/topAmount")
public AjaxResult topAmount(@RequestBody DirectStatisticsTop30QueryParam param) {
return AjaxResult.success(memberStatisticsService.getDirectStatisticsNumberOfAmount(param));
}
}

View File

@ -0,0 +1,54 @@
package com.hzs.member.statis.controller.manager;
import com.hzs.common.core.constant.CacheConstants;
import com.hzs.common.core.enums.ETop30Type;
import com.hzs.common.core.service.RedisService;
import com.hzs.common.core.web.domain.AjaxResult;
import com.hzs.member.statis.param.DirectStatisticsTop30QueryParam;
import com.hzs.member.statis.service.IMemberStatisticsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 统计
*/
@Slf4j
@RequestMapping("/manage/member-statistics")
@RestController
public class BackMemberStatisticsController {
@Resource
private IMemberStatisticsService memberStatisticsService;
@Resource
private RedisService redisService;
@GetMapping("/setShowPeople/{flag}")
public AjaxResult setShowPeople(@PathVariable("flag") String flag) {
redisService.setCacheObject(CacheConstants.TOP_30_ACTIVITY + ETop30Type.PEOPLE.getKey(), flag);
return AjaxResult.success();
}
@GetMapping("/setShowAmount/{flag}")
public AjaxResult setShowAmount(@PathVariable("flag") String flag) {
redisService.setCacheObject(CacheConstants.TOP_30_ACTIVITY + ETop30Type.AMOUNT.getKey(), flag);
return AjaxResult.success();
}
@PostMapping("/topPeople")
public AjaxResult topPeople(@RequestBody DirectStatisticsTop30QueryParam param) {
param.setFaker(false);
return AjaxResult.success(memberStatisticsService.getDirectStatisticsNumberOfPeople(param));
}
@PostMapping("/topAmount")
public AjaxResult topAmount(@RequestBody DirectStatisticsTop30QueryParam param) {
param.setFaker(false);
return AjaxResult.success(memberStatisticsService.getDirectStatisticsNumberOfAmount(param));
}
}

View File

@ -0,0 +1,16 @@
package com.hzs.member.statis.mapper;
import com.hzs.common.domain.member.statis.DirectStatisticsTop30VO;
import com.hzs.member.statis.param.DirectStatisticsTop30QueryParam;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
public interface MemberStatisticsMapper {
List<DirectStatisticsTop30VO> getDirectStatisticsNumberOfPeople(@Param("param") DirectStatisticsTop30QueryParam param);
List<DirectStatisticsTop30VO> getDirectStatisticsNumberOfAmount(@Param("param") DirectStatisticsTop30QueryParam param);
}

View File

@ -0,0 +1,30 @@
package com.hzs.member.statis.param;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class DirectStatisticsTop30QueryParam implements Serializable {
/**
* 统计年
*/
private String year;
/**
* 统计月
*/
private String month;
private Boolean faker;
private Date startDate;
private Date endDate;
private Long memberId;
}

View File

@ -0,0 +1,14 @@
package com.hzs.member.statis.service;
import com.hzs.common.domain.member.statis.DirectStatisticsTop30VO;
import com.hzs.member.statis.param.DirectStatisticsTop30QueryParam;
import java.util.List;
public interface IMemberStatisticsService {
List<DirectStatisticsTop30VO> getDirectStatisticsNumberOfPeople(DirectStatisticsTop30QueryParam param);
List<DirectStatisticsTop30VO> getDirectStatisticsNumberOfAmount(DirectStatisticsTop30QueryParam param);
}

View File

@ -0,0 +1,225 @@
package com.hzs.member.statis.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.hzs.common.core.constant.CacheConstants;
import com.hzs.common.core.enums.ETop30Type;
import com.hzs.common.core.service.RedisService;
import com.hzs.common.core.utils.DataMaskingUtil;
import com.hzs.common.core.utils.DateUtils;
import com.hzs.common.domain.member.statis.DirectStatisticsTop30VO;
import com.hzs.member.statis.mapper.MemberStatisticsMapper;
import com.hzs.member.statis.param.DirectStatisticsTop30QueryParam;
import com.hzs.member.statis.service.IMemberStatisticsService;
import com.hzs.member.statis.util.DataGeneratorUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.*;
import java.util.*;
@Service
@Slf4j
public class MemberStatisticsServiceImpl implements IMemberStatisticsService {
@Resource
private MemberStatisticsMapper memberStatisticsMapper;
@Resource
private RedisService redisService;
@Override
public List<DirectStatisticsTop30VO> getDirectStatisticsNumberOfPeople(DirectStatisticsTop30QueryParam param) {
String show = redisService.getCacheObject(CacheConstants.TOP_30_ACTIVITY + ETop30Type.PEOPLE.getKey());
if(ObjectUtil.isEmpty(show) || "false".equals(show)){
return null;
}
if(DateUtils.isYearMonthExceedCurrent(param.getYear() + "/" + param.getMonth())) {
return null;
}
setStartAndEndDate(param);
List<DirectStatisticsTop30VO> list = memberStatisticsMapper.getDirectStatisticsNumberOfPeople(param);
return desensitizationAndFaker(list, param, 1);
}
@Override
public List<DirectStatisticsTop30VO> getDirectStatisticsNumberOfAmount(DirectStatisticsTop30QueryParam param) {
String show = redisService.getCacheObject(CacheConstants.TOP_30_ACTIVITY + ETop30Type.AMOUNT.getKey());
if(ObjectUtil.isEmpty(show) || "false".equals(show)){
return null;
}
if(DateUtils.isYearMonthExceedCurrent(param.getYear() + "/" + param.getMonth())) {
return null;
}
setStartAndEndDate(param);
List<DirectStatisticsTop30VO> list = memberStatisticsMapper.getDirectStatisticsNumberOfAmount(param);
return desensitizationAndFaker(list, param, 2);
}
/**
* 数据脱敏&格式化数字&假数据
* @param list
* @param param
* @param type
*/
private List<DirectStatisticsTop30VO> desensitizationAndFaker(List<DirectStatisticsTop30VO> list, DirectStatisticsTop30QueryParam param, Integer type){
List<DirectStatisticsTop30VO> fakerList = DataGeneratorUtils.generateFakeData(
new ArrayList<>(), 30, 1, DataGeneratorUtils.TimeGranularity.DAY);
List<DirectStatisticsTop30VO> result = list;
if(param.getFaker()){
result = generateFakeData(list, 30, fakerList, type);
}
for (DirectStatisticsTop30VO directStatisticsTop30VO : result) {
directStatisticsTop30VO.setMemberCode(DataMaskingUtil.mask(directStatisticsTop30VO.getMemberCode(), 2, 2));
directStatisticsTop30VO.setMemberName(DataMaskingUtil.mask(directStatisticsTop30VO.getMemberName(), 1, 0));
directStatisticsTop30VO.setNumberOfAmount(directStatisticsTop30VO.getNumberOfAmount().divide(new BigDecimal(10000)));
}
return result;
}
private void setStartAndEndDate(DirectStatisticsTop30QueryParam param) {
// 参数验证
if (ObjectUtil.isEmpty(param.getYear()) || ObjectUtil.isEmpty(param.getMonth())) {
throw new IllegalArgumentException("年份和月份不能为空");
}
try {
int year = Integer.parseInt(param.getYear());
int month = Integer.parseInt(param.getMonth());
YearMonth yearMonth = YearMonth.of(year, month);
LocalDateTime firstDay = yearMonth.atDay(1).atStartOfDay();
LocalDateTime lastDay = yearMonth.atEndOfMonth().atTime(23, 59, 59);
// 获取系统默认时区
ZoneId zoneId = ZoneId.systemDefault();
// 转换为Date
param.setStartDate(Date.from(firstDay.atZone(zoneId).toInstant()));
param.setEndDate(Date.from(lastDay.atZone(zoneId).toInstant()));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("年份或月份格式不正确", e);
} catch (Exception e) {
throw new RuntimeException("获取时间范围失败", e);
}
}
/**
* 生成假数据并填充到列表
*
* @param list 原始数据列表
* @param totalCount 总数
* @param fakerList 假数据列表
* @param type 类型1为设置numberOfPeople, 2为设置numberOfAmount
* @return 填充后的列表
*/
public static List<DirectStatisticsTop30VO> generateFakeData(
List<DirectStatisticsTop30VO> list,
int totalCount,
List<DirectStatisticsTop30VO> fakerList,
int type) {
if (list == null) {
list = new ArrayList<>();
}
if (fakerList == null || fakerList.isEmpty()) {
throw new IllegalArgumentException("假数据列表不能为空");
}
if (totalCount < list.size()) {
throw new IllegalArgumentException("总数不能小于原始列表大小");
}
// 计算需要生成的假数据数量
int needGenerateCount = totalCount - list.size();
// 检查假数据列表是否有足够的数据
if (needGenerateCount > fakerList.size()) {
throw new IllegalArgumentException("假数据列表数据不足");
}
// 从假数据列表中移除并获取需要的假数据
List<DirectStatisticsTop30VO> selectedFakerData = new ArrayList<>();
for (int i = 0; i < needGenerateCount; i++) {
// 随机选择一个假数据
int randomIndex = new Random().nextInt(fakerList.size());
DirectStatisticsTop30VO fakerData = fakerList.remove(randomIndex);
selectedFakerData.add(fakerData);
}
// 根据类型设置数据
if (type == 1) {
setNumberOfPeople(selectedFakerData, list);
} else if (type == 2) {
setNumberOfAmount(selectedFakerData, list);
} else {
throw new IllegalArgumentException("类型参数无效只能是1或2");
}
// 将生成的假数据添加到原始列表
list.addAll(selectedFakerData);
// 按照对应类型降序排序
if (type == 1) {
list.sort(Comparator.comparing(DirectStatisticsTop30VO::getNumberOfPeople).reversed());
} else {
list.sort(Comparator.comparing(DirectStatisticsTop30VO::getNumberOfAmount).reversed());
}
return list;
}
/**
* 设置numberOfPeople
*/
private static void setNumberOfPeople(List<DirectStatisticsTop30VO> selectedFakerData, List<DirectStatisticsTop30VO> originalList) {
// 找出原始列表中最小的numberOfPeople
int minPeopleCount = Integer.MAX_VALUE;
for (DirectStatisticsTop30VO data : originalList) {
if (data.getNumberOfPeople() != null && data.getNumberOfPeople() < minPeopleCount) {
minPeopleCount = data.getNumberOfPeople();
}
}
if (minPeopleCount == Integer.MAX_VALUE) {
minPeopleCount = 10; // 默认值可以根据需要调整
}
Random random = new Random();
for (DirectStatisticsTop30VO fakerData : selectedFakerData) {
if (minPeopleCount == 1) {
fakerData.setNumberOfPeople(1);
} else {
int value = random.nextInt(minPeopleCount - 1) + 1;
fakerData.setNumberOfPeople(value);
}
}
}
/**
* 设置numberOfAmount
*/
private static void setNumberOfAmount(List<DirectStatisticsTop30VO> selectedFakerData, List<DirectStatisticsTop30VO> originalList) {
// 找出原始列表中最小的numberOfAmount
BigDecimal minAmount = null;
for (DirectStatisticsTop30VO data : originalList) {
if (data.getNumberOfAmount() != null) {
if (minAmount == null || data.getNumberOfAmount().compareTo(minAmount) < 0) {
minAmount = data.getNumberOfAmount();
}
}
}
if (minAmount == null) {
minAmount = new BigDecimal(188); // 默认值可以根据需要调整
}
Random random = new Random();
for (DirectStatisticsTop30VO fakerData : selectedFakerData) {
if (minAmount.compareTo(new BigDecimal(188)) == 0) {
BigDecimal value = new BigDecimal(188);
fakerData.setNumberOfAmount(value);
} else {
BigDecimal range = minAmount.subtract(BigDecimal.ONE);
BigDecimal randomValue = new BigDecimal(random.nextDouble() * range.doubleValue()).setScale(4, BigDecimal.ROUND_HALF_UP);
if (randomValue.compareTo(BigDecimal.ZERO) <= 0) {
randomValue = BigDecimal.ONE;
}
fakerData.setNumberOfAmount(randomValue);
}
}
}
}

View File

@ -0,0 +1,316 @@
package com.hzs.member.statis.util;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.hzs.common.core.constant.SysConstants;
import com.hzs.common.domain.member.statis.DirectStatisticsTop30VO;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
public class DataGeneratorUtils {
// 时间粒度枚举
public enum TimeGranularity {
DAY, // 按天
HOUR // 按小时
}
private static final String prefix = "BD";
// 常见姓氏数组
private static final String[] COMMON_SURNAMES = {
"", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", ""
};
// 当前种子用于生成数据
private static int currentSeed = getSeed(TimeGranularity.DAY); // 默认按天
private static TimeGranularity currentGranularity = TimeGranularity.DAY;
// 预先生成的30条基础数据
private static List<DirectStatisticsTop30VO> PERIODIC_PRE_GENERATED_DATA = generatePeriodicPredefinedData();
/**
* 生成假数据并添加到列表中
*
* @param list 已有数据列表
* @param totalCount 需要生成的总数据量
* @param type 类型1为设置numberOfPeople2为设置numberOfAmount
* @param granularity 时间粒度DAY按天HOUR按小时
* @return 包含新生成数据的列表
*/
public static List<DirectStatisticsTop30VO> generateFakeData(
List<DirectStatisticsTop30VO> list, int totalCount, int type, TimeGranularity granularity) {
// 检查时间是否已变化如果变化则重新生成数据
checkAndUpdatePeriodicData(granularity);
// 创建返回列表复制原列表避免修改原列表
List<DirectStatisticsTop30VO> resultList = new ArrayList<>(list);
// 如果总数小于等于已有列表大小直接返回原列表
if (totalCount <= list.size()) {
return resultList;
}
// 需要生成的新数据数量
int needGenerateCount = totalCount - list.size();
// 从预生成数据中获取所需数量的数据
List<DirectStatisticsTop30VO> newData = getPeriodicPreGeneratedDataByCount(needGenerateCount, type);
// 添加新数据到结果列表
resultList.addAll(newData);
// 按照对应字段降序排序
if (type == 1) {
resultList.sort((a, b) -> b.getNumberOfPeople().compareTo(a.getNumberOfPeople()));
} else if (type == 2) {
resultList.sort((a, b) -> b.getNumberOfAmount().compareTo(a.getNumberOfAmount()));
}
return resultList;
}
/**
* 兼容旧版本按天生成数据
*/
public static List<DirectStatisticsTop30VO> generateFakeData(
List<DirectStatisticsTop30VO> list, int totalCount, int type) {
return generateFakeData(list, totalCount, type, TimeGranularity.DAY);
}
/**
* 检查时间是否已变化如果变化则更新数据
*/
private static void checkAndUpdatePeriodicData(TimeGranularity granularity) {
int newSeed = getSeed(granularity);
if (newSeed != currentSeed || granularity != currentGranularity) {
currentSeed = newSeed;
currentGranularity = granularity;
PERIODIC_PRE_GENERATED_DATA = generatePeriodicPredefinedData();
}
}
/**
* 根据时间粒度获取种子
* @param granularity 时间粒度
* @return 对应的种子
*/
private static int getSeed(TimeGranularity granularity) {
if (granularity == TimeGranularity.HOUR) {
// 按小时格式为YYYYMMDDHH
LocalDateTime now = LocalDateTime.now();
return Integer.parseInt(now.format(DateTimeFormatter.ofPattern("yyyyMMddHH")));
} else {
// 按天格式为YYYYMMDD
LocalDate today = LocalDate.now();
return Integer.parseInt(today.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
}
}
/**
* 生成当前周期的30条预定义数据
*/
private static List<DirectStatisticsTop30VO> generatePeriodicPredefinedData() {
List<DirectStatisticsTop30VO> dataList = new ArrayList<>(30);
// 使用当前周期的种子创建Random对象
Random periodRandom = new Random(currentSeed);
// 生成30条基础数据
for (int i = 0; i < 30; i++) {
DirectStatisticsTop30VO vo = new DirectStatisticsTop30VO();
// 设置基础信息包含时间粒度标识
long baseId = 4000000L + currentSeed % 1000000L;
vo.setMemberId(baseId + i);
// 根据时间粒度生成不同的用户编号前缀
// String prefix = currentGranularity == TimeGranularity.HOUR ? "HOUR" : "DAY";
// String prefix = SysConstants.HAI_FUN_PREFIX;
vo.setMemberCode(generateMemberCode(currentSeed, 10));
// 生成随机中文姓名
vo.setMemberName(generateRandomChineseName(i, periodRandom));
// 使用周期随机数生成数据值
vo.setNumberOfPeople(1 + periodRandom.nextInt(100)); // 1-100之间的随机值
// 生成金额值
BigDecimal amount = new BigDecimal(100 + periodRandom.nextInt(10000))
.add(new BigDecimal(periodRandom.nextDouble()))
.setScale(4, BigDecimal.ROUND_HALF_UP);
vo.setNumberOfAmount(amount);
dataList.add(vo);
}
return dataList;
}
public static String generateMemberCode(long currentSeed, int i) {
int prefixLength = prefix.length();
int numPartLength = 10 - prefixLength;
long seedPart = generateRandomSeedPart(numPartLength);
String memberCode = prefix + seedPart;
return memberCode.length() > 10 ? memberCode.substring(0, 10) : memberCode;
}
/**
* 生成指定位数的随机seedPart
*
* @param digits 位数
* @return 随机数
*/
private static long generateRandomSeedPart(int digits) {
if (digits <= 0) {
return 0;
}
// 计算最小值和最大值
int min = (int) Math.pow(10, digits - 1);
int max = (int) Math.pow(10, digits) - 1;
// 如果digits为0返回0
if (min == 0 && max == 0) {
return 0;
}
return RandomUtil.randomInt(min, max);
}
/**
* 根据需要的数量从预生成数据中获取数据
*/
private static List<DirectStatisticsTop30VO> getPeriodicPreGeneratedDataByCount(int count, int type) {
List<DirectStatisticsTop30VO> result = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
// 从预生成数据中循环获取
DirectStatisticsTop30VO originalVo = PERIODIC_PRE_GENERATED_DATA.get(i % 30);
// 创建副本以避免修改预生成的数据
DirectStatisticsTop30VO newVo = new DirectStatisticsTop30VO();
newVo.setMemberId(originalVo.getMemberId());
newVo.setMemberName(originalVo.getMemberName());
newVo.setMemberCode(originalVo.getMemberCode());
// 根据类型设置相应的值
if (type == 1) {
// 类型1设置numberOfPeople
newVo.setNumberOfPeople(originalVo.getNumberOfPeople());
newVo.setNumberOfAmount(BigDecimal.ZERO);
} else if (type == 2) {
// 类型2设置numberOfAmount
newVo.setNumberOfPeople(0);
newVo.setNumberOfAmount(originalVo.getNumberOfAmount());
}
result.add(newVo);
}
return result;
}
/**
* 生成随机中文姓名
*/
private static String generateRandomChineseName(int index, Random random) {
// 随机选择姓氏
String surname = COMMON_SURNAMES[random.nextInt(COMMON_SURNAMES.length)];
// 随机决定是二字姓名还是三字姓名
boolean isTwoCharacters = random.nextBoolean();
if (isTwoCharacters) {
// 二字姓名姓氏 + 1个随机汉字
return surname + generateRandomChineseChar(random);
} else {
// 三字姓名姓氏 + 2个随机汉字
return surname + generateRandomChineseChar(random) + generateRandomChineseChar(random);
}
}
/**
* 生成随机汉字
*/
private static char generateRandomChineseChar(Random random) {
// 中文汉字Unicode范围大致在0x4E00到0x9FA5之间
int start = 0x4E00;
int end = 0x9FA5;
return (char) (start + random.nextInt(end - start + 1));
}
/**
* 获取当前周期预生成的30条完整数据
* @param granularity 时间粒度
* @return 预生成的数据列表
*/
public static List<DirectStatisticsTop30VO> getPeriodicPreGeneratedData(TimeGranularity granularity) {
// 检查时间是否已变化
checkAndUpdatePeriodicData(granularity);
// 返回预生成数据的副本避免外部修改
List<DirectStatisticsTop30VO> copyList = new ArrayList<>(PERIODIC_PRE_GENERATED_DATA.size());
for (DirectStatisticsTop30VO vo : PERIODIC_PRE_GENERATED_DATA) {
DirectStatisticsTop30VO copy = new DirectStatisticsTop30VO();
copy.setMemberId(vo.getMemberId());
copy.setMemberName(vo.getMemberName());
copy.setMemberCode(vo.getMemberCode());
copy.setNumberOfPeople(vo.getNumberOfPeople());
copy.setNumberOfAmount(vo.getNumberOfAmount());
copyList.add(copy);
}
return copyList;
}
/**
* 用于测试根据指定日期和时间粒度获取数据
*/
public static List<DirectStatisticsTop30VO> getPreGeneratedDataByDate(
int year, int month, int day, int hour, TimeGranularity granularity) {
// 保存当前状态
int originalSeed = currentSeed;
TimeGranularity originalGranularity = currentGranularity;
List<DirectStatisticsTop30VO> originalData = PERIODIC_PRE_GENERATED_DATA;
try {
// 设置指定日期的种子
Calendar calendar = new GregorianCalendar(year, month - 1, day, hour, 0, 0);
Date date = calendar.getTime();
if (granularity == TimeGranularity.HOUR) {
LocalDateTime localDateTime = date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime();
currentSeed = Integer.parseInt(localDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHH")));
} else {
LocalDate localDate = date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
currentSeed = Integer.parseInt(localDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
}
currentGranularity = granularity;
// 重新生成指定日期的数据
PERIODIC_PRE_GENERATED_DATA = generatePeriodicPredefinedData();
// 返回生成的数据
return getPeriodicPreGeneratedData(granularity);
} finally {
// 恢复原始状态
currentSeed = originalSeed;
currentGranularity = originalGranularity;
PERIODIC_PRE_GENERATED_DATA = originalData;
}
}
public static void main(String[] args) {
// 按天生成数据
List<DirectStatisticsTop30VO> dailyData = DataGeneratorUtils.generateFakeData(
new ArrayList<>(), 30, 1, DataGeneratorUtils.TimeGranularity.DAY);
System.out.println(JSONUtil.toJsonPrettyStr(dailyData));
// 按小时生成数据
// List<DirectStatisticsTop30VO> hourlyData = DataGeneratorUtils.generateFakeData(
// new ArrayList<>(), 50, 2, DataGeneratorUtils.TimeGranularity.HOUR);
// System.out.println(JSONUtil.toJsonPrettyStr(hourlyData));
}
}

View File

@ -0,0 +1,53 @@
<?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="com.hzs.member.statis.mapper.MemberStatisticsMapper">
<select id="getDirectStatisticsNumberOfPeople"
resultType="com.hzs.common.domain.member.statis.DirectStatisticsTop30VO">
select
PK_ID memberId,
MEMBER_CODE memberCode,
member_name memberName,
numberOfPeople,
numberOfAmount
from (
select
PK_REFERENCE,
count(PK_REFERENCE) numberOfPeople,
sum(order_amount) numberOfAmount
from sa_order
where
del_flag = 0
and order_status = 1
and pay_time between #{param.startDate} and #{param.endDate}
group by PK_REFERENCE
) r
left join cu_member cm on r.PK_REFERENCE = cm.pk_id
WHERE ROWNUM <![CDATA[ <=]]> 30
order by numberOfPeople desc
</select>
<select id="getDirectStatisticsNumberOfAmount"
resultType="com.hzs.common.domain.member.statis.DirectStatisticsTop30VO">
select
PK_ID memberId,
MEMBER_CODE memberCode,
member_name memberName,
numberOfPeople,
numberOfAmount
from (
select
PK_REFERENCE,
count(PK_REFERENCE) numberOfPeople,
sum(order_amount) numberOfAmount
from sa_order
where
del_flag = 0
and order_status = 1
and pay_time between #{param.startDate} and #{param.endDate}
group by PK_REFERENCE
) r
left join cu_member cm on r.PK_REFERENCE = cm.pk_id
WHERE ROWNUM <![CDATA[ <=]]> 30
order by numberOfAmount desc
</select>
</mapper>

View File

@ -330,4 +330,5 @@ public class CacheConstants {
*/
public final static String AUTO_LOGIN = CACHE_PREFIX + "auth:login:";
public final static String TOP_30_ACTIVITY = CACHE_PREFIX + "top:30:activity:";
}

View File

@ -0,0 +1,84 @@
package com.hzs.common.core.enums;
import com.hzs.common.core.constant.EnumsPrefixConstants;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
/**
* 订单类型枚举类
*/
@AllArgsConstructor
@Getter
public enum ETop30Type {
/**
* 注册订单
*/
PEOPLE(1, "直推人数", "people"),
/**
* 升级订单
*/
AMOUNT(2, "直推金额", "amount")
;
/**
* 实际值
*/
private final int value;
/**
* 显示标签
*/
private final String label;
/**
* 显示标签
*/
private final String key;
/**
* 根据值返回枚举
*
* @param value
* @return
*/
public static ETop30Type getEnumByValue(int value) {
for (ETop30Type enums : ETop30Type.values()) {
if (enums.getValue() == value) {
return enums;
}
}
return null;
}
/**
* 根据值返回名称
*
* @param value
* @return
*/
public static String getLabelByValue(Integer value) {
if (null == value) {
return "";
}
for (ETop30Type enums : ETop30Type.values()) {
if (enums.getValue() == value) {
return enums.getLabel();
}
}
return "";
}
public static List<ETop30Type> getOrderTypeSpecial() {
List<ETop30Type> resultList = new ArrayList<>();
resultList.add(ETop30Type.PEOPLE);
resultList.add(ETop30Type.AMOUNT);
return resultList;
}
}

View File

@ -0,0 +1,98 @@
package com.hzs.common.core.utils;
/**
* 数据脱敏工具类
* 提供字符串脱敏功能可指定保留前几位和后几位字符
*/
public class DataMaskingUtil {
// 默认脱敏字符
private static final char DEFAULT_MASK_CHAR = '*';
/**
* 对字符串进行脱敏处理
* @param input 原始字符串
* @param keepPrefix 保留的前缀位数
* @param keepSuffix 保留的后缀位数
* @return 脱敏后的字符串
*/
public static String mask(String input, int keepPrefix, int keepSuffix) {
// 参数验证
if (input == null) {
return null;
}
// 处理边界情况
if (keepPrefix < 0 || keepSuffix < 0) {
throw new IllegalArgumentException("保留位数不能为负数");
}
int length = input.length();
// 如果字符串长度小于等于需要保留的位数之和直接返回原字符串
if (length <= keepPrefix + keepSuffix) {
return input;
}
// 构建StringBuilder用于高效拼接字符串
StringBuilder result = new StringBuilder(length);
// 添加前缀保留字符
result.append(input, 0, keepPrefix);
// 添加脱敏字符
int maskCount = length - keepPrefix - keepSuffix;
for (int i = 0; i < maskCount; i++) {
result.append(DEFAULT_MASK_CHAR);
}
// 添加后缀保留字符
result.append(input, length - keepSuffix, length);
return result.toString();
}
/**
* 对字符串进行脱敏处理可自定义脱敏字符
* @param input 原始字符串
* @param keepPrefix 保留的前缀位数
* @param keepSuffix 保留的后缀位数
* @param maskChar 自定义脱敏字符
* @return 脱敏后的字符串
*/
public static String mask(String input, int keepPrefix, int keepSuffix, char maskChar) {
// 参数验证
if (input == null) {
return null;
}
// 处理边界情况
if (keepPrefix < 0 || keepSuffix < 0) {
throw new IllegalArgumentException("保留位数不能为负数");
}
int length = input.length();
// 如果字符串长度小于等于需要保留的位数之和直接返回原字符串
if (length <= keepPrefix + keepSuffix) {
return input;
}
// 构建StringBuilder用于高效拼接字符串
StringBuilder result = new StringBuilder(length);
// 添加前缀保留字符
result.append(input, 0, keepPrefix);
// 添加脱敏字符
int maskCount = length - keepPrefix - keepSuffix;
for (int i = 0; i < maskCount; i++) {
result.append(maskChar);
}
// 添加后缀保留字符
result.append(input, length - keepSuffix, length);
return result.toString();
}
}

View File

@ -6,6 +6,7 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
@ -895,5 +896,58 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return LocalDate.parse(date, fmt);
}
/**
* 判断输入的年/月字符串是否超过当前月份
*
* @param yearMonthStr /月字符串格式为"yyyy/MM""2023/12"
* @return 如果输入的年月超过当前月份则返回true否则返回false
* @throws IllegalArgumentException 如果输入的字符串格式不正确
*/
public static boolean isYearMonthExceedCurrent(String yearMonthStr) {
// 参数校验
if (yearMonthStr == null || yearMonthStr.trim().isEmpty()) {
throw new IllegalArgumentException("年/月字符串不能为空");
}
try {
// 解析输入的年/月字符串
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM");
YearMonth inputYearMonth = YearMonth.parse(yearMonthStr, formatter);
// 获取当前年月
YearMonth currentYearMonth = YearMonth.now();
// 比较输入年月和当前年月
// 如果输入年月大于当前年月则返回true表示超过当前月份
return inputYearMonth.isAfter(currentYearMonth);
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("年/月字符串格式不正确,应为'yyyy/MM'格式,如'2023/12'", e);
}
}
/**
* 重载方法支持自定义日期格式
*
* @param yearMonthStr /月字符串
* @param pattern 日期格式"yyyy-MM""yyyyMM"
* @return 如果输入的年月超过当前月份则返回true否则返回false
* @throws IllegalArgumentException 如果输入参数不合法
*/
public static boolean isYearMonthExceedCurrent(String yearMonthStr, String pattern) {
if (pattern == null || pattern.trim().isEmpty()) {
throw new IllegalArgumentException("日期格式不能为空");
}
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
YearMonth inputYearMonth = YearMonth.parse(yearMonthStr, formatter);
YearMonth currentYearMonth = YearMonth.now();
return inputYearMonth.isAfter(currentYearMonth);
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("年/月字符串格式不正确,无法使用模式'" + pattern + "'解析", e);
}
}
}

View File

@ -0,0 +1,42 @@
package com.hzs.common.domain.member.statis;
import com.hzs.common.core.annotation.BigDecimalFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DirectStatisticsTop30VO implements Serializable {
/**
* 用户id
*/
private Long memberId;
/**
* 用户名称
*/
private String memberName;
/**
* 用户编号
*/
private String memberCode;
/**
* 直推数量
*/
private Integer numberOfPeople;
/**
* 直推金额
*/
@BigDecimalFormat(value = "#0.0000")
private BigDecimal numberOfAmount;
}