## Feat - Top 30 (直推人数/直推金额)
This commit is contained in:
parent
63574c6fb9
commit
a23fd0fe07
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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为设置numberOfPeople,2为设置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));
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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:";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue