|
|
@@ -1,5 +1,6 @@
|
|
|
package shop.alien.store.service.impl;
|
|
|
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
@@ -7,16 +8,17 @@ import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
+import shop.alien.entity.store.*;
|
|
|
import shop.alien.entity.store.StoreTrackEvent;
|
|
|
import shop.alien.entity.store.StoreTrackStatistics;
|
|
|
-import shop.alien.mapper.StoreTrackEventMapper;
|
|
|
-import shop.alien.mapper.StoreTrackStatisticsMapper;
|
|
|
+import shop.alien.mapper.*;
|
|
|
import shop.alien.store.config.BaseRedisService;
|
|
|
import shop.alien.store.service.TrackEventService;
|
|
|
|
|
|
-import java.util.Date;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Map;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* 埋点事件服务实现类
|
|
|
@@ -34,6 +36,16 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
|
|
|
private final StoreTrackStatisticsMapper trackStatisticsMapper;
|
|
|
private final ObjectMapper objectMapper;
|
|
|
|
|
|
+ // 新增依赖:用于查询其他表数据
|
|
|
+ private final StoreUserMapper storeUserMapper;
|
|
|
+ private final LifeFansMapper lifeFansMapper;
|
|
|
+ private final LifeUserDynamicsMapper lifeUserDynamicsMapper;
|
|
|
+ private final CommonRatingMapper commonRatingMapper;
|
|
|
+ private final StoreCommentAppealMapper storeCommentAppealMapper;
|
|
|
+ private final LifeDiscountCouponUserMapper lifeDiscountCouponUserMapper;
|
|
|
+ private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
|
|
|
+ private final LifeDiscountCouponStoreFriendMapper lifeDiscountCouponStoreFriendMapper;
|
|
|
+
|
|
|
private static final String REDIS_QUEUE_KEY = "track:event:queue";
|
|
|
|
|
|
@Override
|
|
|
@@ -68,28 +80,60 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
|
|
|
|
|
|
try {
|
|
|
// 计算统计日期范围
|
|
|
- Date startDate = statDate;
|
|
|
- Date endDate = statDate;
|
|
|
+ java.util.Calendar cal = java.util.Calendar.getInstance();
|
|
|
+ Date startDate;
|
|
|
+ Date endDate;
|
|
|
|
|
|
// 根据统计类型确定日期范围
|
|
|
- if ("WEEKLY".equals(statType)) {
|
|
|
- // 周统计:从周一(statDate)到周日
|
|
|
- java.util.Calendar cal = java.util.Calendar.getInstance();
|
|
|
+ if ("DAILY".equals(statType)) {
|
|
|
+ // 日统计:从当天 00:00:00 到 23:59:59
|
|
|
+ cal.setTime(statDate);
|
|
|
+ cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
|
|
|
+ cal.set(java.util.Calendar.MINUTE, 0);
|
|
|
+ cal.set(java.util.Calendar.SECOND, 0);
|
|
|
+ cal.set(java.util.Calendar.MILLISECOND, 0);
|
|
|
+ startDate = cal.getTime();
|
|
|
+
|
|
|
+ cal.add(java.util.Calendar.DAY_OF_MONTH, 1);
|
|
|
+ endDate = cal.getTime(); // 下一天的 00:00:00,使用 .lt() 查询
|
|
|
+ } else if ("WEEKLY".equals(statType)) {
|
|
|
+ // 周统计:从周一(statDate)00:00:00 到周日 23:59:59
|
|
|
cal.setTime(statDate);
|
|
|
cal.set(java.util.Calendar.DAY_OF_WEEK, java.util.Calendar.MONDAY);
|
|
|
+ cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
|
|
|
+ cal.set(java.util.Calendar.MINUTE, 0);
|
|
|
+ cal.set(java.util.Calendar.SECOND, 0);
|
|
|
+ cal.set(java.util.Calendar.MILLISECOND, 0);
|
|
|
startDate = cal.getTime();
|
|
|
- cal.add(java.util.Calendar.DAY_OF_WEEK, 6);
|
|
|
- endDate = cal.getTime();
|
|
|
+
|
|
|
+ cal.add(java.util.Calendar.DAY_OF_WEEK, 7);
|
|
|
+ endDate = cal.getTime(); // 下周一的 00:00:00,使用 .lt() 查询
|
|
|
} else if ("MONTHLY".equals(statType)) {
|
|
|
- // 月统计:从月初到月末
|
|
|
- java.util.Calendar cal = java.util.Calendar.getInstance();
|
|
|
+ // 月统计:从月初 00:00:00 到月末 23:59:59
|
|
|
cal.setTime(statDate);
|
|
|
cal.set(java.util.Calendar.DAY_OF_MONTH, 1);
|
|
|
+ cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
|
|
|
+ cal.set(java.util.Calendar.MINUTE, 0);
|
|
|
+ cal.set(java.util.Calendar.SECOND, 0);
|
|
|
+ cal.set(java.util.Calendar.MILLISECOND, 0);
|
|
|
startDate = cal.getTime();
|
|
|
+
|
|
|
cal.add(java.util.Calendar.MONTH, 1);
|
|
|
- cal.add(java.util.Calendar.DAY_OF_MONTH, -1);
|
|
|
+ endDate = cal.getTime(); // 下个月1号的 00:00:00,使用 .lt() 查询
|
|
|
+ } else {
|
|
|
+ // 默认:日统计
|
|
|
+ cal.setTime(statDate);
|
|
|
+ cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
|
|
|
+ cal.set(java.util.Calendar.MINUTE, 0);
|
|
|
+ cal.set(java.util.Calendar.SECOND, 0);
|
|
|
+ cal.set(java.util.Calendar.MILLISECOND, 0);
|
|
|
+ startDate = cal.getTime();
|
|
|
+
|
|
|
+ cal.add(java.util.Calendar.DAY_OF_MONTH, 1);
|
|
|
endDate = cal.getTime();
|
|
|
}
|
|
|
+
|
|
|
+ log.info("统计日期范围: startDate={}, endDate={}", startDate, endDate);
|
|
|
|
|
|
// 查询或创建统计记录
|
|
|
LambdaQueryWrapper<StoreTrackStatistics> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
@@ -105,22 +149,14 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
|
|
|
statistics.setStatType(statType);
|
|
|
}
|
|
|
|
|
|
- // 从store_track_event表统计基础数据
|
|
|
- LambdaQueryWrapper<StoreTrackEvent> eventWrapper = new LambdaQueryWrapper<>();
|
|
|
- eventWrapper.eq(StoreTrackEvent::getStoreId, storeId)
|
|
|
- .ge(StoreTrackEvent::getEventTime, startDate)
|
|
|
- .le(StoreTrackEvent::getEventTime, endDate)
|
|
|
- .eq(StoreTrackEvent::getDeleteFlag, 0);
|
|
|
-
|
|
|
- List<StoreTrackEvent> events = this.list(eventWrapper);
|
|
|
-
|
|
|
+ // 优化:直接使用数据库聚合查询,不再加载所有事件到内存
|
|
|
// 按事件分类统计(符合文档格式)
|
|
|
- Map<String, Object> trafficData = calculateTrafficData(events, storeId, startDate);
|
|
|
- Map<String, Object> interactionData = calculateInteractionData(events, storeId);
|
|
|
- Map<String, Object> couponData = calculateCouponData(events, storeId);
|
|
|
- Map<String, Object> voucherData = calculateVoucherData(events, storeId);
|
|
|
- Map<String, Object> serviceData = calculateServiceData(events, storeId);
|
|
|
- List<Map<String, Object>> priceData = calculatePriceRankingData(events);
|
|
|
+ Map<String, Object> trafficData = calculateTrafficData(storeId, startDate, endDate);
|
|
|
+ Map<String, Object> interactionData = calculateInteractionData(storeId, startDate, endDate);
|
|
|
+ Map<String, Object> couponData = calculateCouponData(storeId, startDate, endDate);
|
|
|
+ Map<String, Object> voucherData = calculateVoucherData(storeId, startDate, endDate);
|
|
|
+ Map<String, Object> serviceData = calculateServiceData(storeId, startDate, endDate);
|
|
|
+ List<Map<String, Object>> priceData = calculatePriceRankingData(storeId, startDate, endDate);
|
|
|
|
|
|
// 设置统计数据(JSON格式)
|
|
|
statistics.setTrafficData(objectMapper.writeValueAsString(trafficData));
|
|
|
@@ -129,6 +165,13 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
|
|
|
statistics.setVoucherData(objectMapper.writeValueAsString(voucherData));
|
|
|
statistics.setServiceData(objectMapper.writeValueAsString(serviceData));
|
|
|
statistics.setPriceRankingData(objectMapper.writeValueAsString(priceData));
|
|
|
+
|
|
|
+ // 输出统计结果(用于调试)
|
|
|
+ log.info("统计结果 - trafficData: {}", objectMapper.writeValueAsString(trafficData));
|
|
|
+ log.info("统计结果 - interactionData: {}", objectMapper.writeValueAsString(interactionData));
|
|
|
+ log.info("统计结果 - voucherData: {}", objectMapper.writeValueAsString(voucherData));
|
|
|
+ log.info("统计结果 - serviceData: {}", objectMapper.writeValueAsString(serviceData));
|
|
|
+ log.info("统计结果 - priceRankingData: {}", objectMapper.writeValueAsString(priceData));
|
|
|
|
|
|
// 保存统计记录
|
|
|
if (statistics.getId() == null) {
|
|
|
@@ -145,247 +188,839 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 计算流量数据(符合文档格式)
|
|
|
+ * 计算流量数据- 优化:使用数据库聚合查询
|
|
|
*/
|
|
|
- private Map<String, Object> calculateTrafficData(List<StoreTrackEvent> events, Integer storeId, Date startDate) {
|
|
|
- Map<String, Object> result = new java.util.HashMap<>();
|
|
|
+ private Map<String, Object> calculateTrafficData(Integer storeId, Date startDate, Date endDate) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+
|
|
|
+ try {
|
|
|
+ StoreTrackEventMapper mapper = this.getBaseMapper();
|
|
|
|
|
|
- List<StoreTrackEvent> trafficEvents = events.stream()
|
|
|
- .filter(e -> "TRAFFIC".equals(e.getEventCategory()))
|
|
|
- .collect(java.util.stream.Collectors.toList());
|
|
|
+ // 使用数据库聚合查询统计流量数据
|
|
|
+ Map<String, Object> trafficStats = mapper.calculateTrafficStatistics(storeId, startDate, endDate);
|
|
|
|
|
|
// 搜索量
|
|
|
- long searchCount = trafficEvents.stream()
|
|
|
- .filter(e -> "SEARCH".equals(e.getEventType()))
|
|
|
- .count();
|
|
|
+ Object searchCountObj = trafficStats.get("searchCount");
|
|
|
+ long searchCount = searchCountObj != null ? ((Number) searchCountObj).longValue() : 0L;
|
|
|
result.put("searchCount", searchCount);
|
|
|
|
|
|
// 浏览量
|
|
|
- long viewCount = trafficEvents.stream()
|
|
|
- .filter(e -> "VIEW".equals(e.getEventType()))
|
|
|
- .count();
|
|
|
+ Object viewCountObj = trafficStats.get("viewCount");
|
|
|
+ long viewCount = viewCountObj != null ? ((Number) viewCountObj).longValue() : 0L;
|
|
|
result.put("viewCount", viewCount);
|
|
|
-
|
|
|
- // 访客数(去重userId)
|
|
|
- long visitorCount = trafficEvents.stream()
|
|
|
- .filter(e -> e.getUserId() != null)
|
|
|
- .map(StoreTrackEvent::getUserId)
|
|
|
- .distinct()
|
|
|
- .count();
|
|
|
- result.put("visitorCount", visitorCount);
|
|
|
-
|
|
|
- // 新增访客数(在统计日期之前没有访问记录的用户)
|
|
|
- // TODO: 需要查询历史数据,暂时返回0
|
|
|
- result.put("newVisitorCount", 0L);
|
|
|
-
|
|
|
- // 总访问时长(所有浏览事件duration字段的总和)
|
|
|
- long totalDuration = trafficEvents.stream()
|
|
|
- .filter(e -> "VIEW".equals(e.getEventType()) && e.getDuration() != null)
|
|
|
- .mapToLong(StoreTrackEvent::getDuration)
|
|
|
- .sum();
|
|
|
+
|
|
|
+ // 访客数(去重userId)- 使用数据库聚合查询
|
|
|
+ Long visitorCount = mapper.countDistinctVisitors(storeId, startDate, endDate);
|
|
|
+ result.put("visitorCount", visitorCount != null ? visitorCount : 0L);
|
|
|
+
|
|
|
+ // 新增访客数 - 使用数据库聚合查询
|
|
|
+ Long newVisitorCount = mapper.countNewVisitors(storeId, startDate, endDate);
|
|
|
+ result.put("newVisitorCount", newVisitorCount != null ? newVisitorCount : 0L);
|
|
|
+
|
|
|
+ // 总访问时长
|
|
|
+ Object totalDurationObj = trafficStats.get("totalDuration");
|
|
|
+ long totalDuration = totalDurationObj != null ? ((Number) totalDurationObj).longValue() : 0L;
|
|
|
result.put("totalDuration", totalDuration);
|
|
|
|
|
|
// 平均访问时长
|
|
|
- long viewEventsWithDuration = trafficEvents.stream()
|
|
|
- .filter(e -> "VIEW".equals(e.getEventType()) && e.getDuration() != null)
|
|
|
- .count();
|
|
|
- long avgDuration = viewEventsWithDuration > 0 ? totalDuration / viewEventsWithDuration : 0L;
|
|
|
+ Object avgDurationObj = trafficStats.get("avgDuration");
|
|
|
+ long avgDuration = avgDurationObj != null ? ((Number) avgDurationObj).longValue() : 0L;
|
|
|
result.put("avgDuration", avgDuration);
|
|
|
+
|
|
|
+ log.debug("流量数据统计完成: searchCount={}, viewCount={}, visitorCount={}, newVisitorCount={}",
|
|
|
+ searchCount, viewCount, visitorCount, newVisitorCount);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算流量数据失败: storeId={}", storeId, e);
|
|
|
+ // 返回默认值,避免统计失败
|
|
|
+ result.put("searchCount", 0L);
|
|
|
+ result.put("viewCount", 0L);
|
|
|
+ result.put("visitorCount", 0L);
|
|
|
+ result.put("newVisitorCount", 0L);
|
|
|
+ result.put("totalDuration", 0L);
|
|
|
+ result.put("avgDuration", 0L);
|
|
|
+ }
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 计算互动数据(符合文档格式)
|
|
|
+ * 计算互动数据- 优化:使用数据库聚合查询
|
|
|
*/
|
|
|
- private Map<String, Object> calculateInteractionData(List<StoreTrackEvent> events, Integer storeId) {
|
|
|
- Map<String, Object> result = new java.util.HashMap<>();
|
|
|
+ private Map<String, Object> calculateInteractionData(Integer storeId, Date startDate, Date endDate) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
|
- List<StoreTrackEvent> interactionEvents = events.stream()
|
|
|
- .filter(e -> "INTERACTION".equals(e.getEventCategory()))
|
|
|
- .collect(java.util.stream.Collectors.toList());
|
|
|
-
|
|
|
- result.put("collectCount", countByEventType(interactionEvents, "COLLECT"));
|
|
|
- result.put("shareCount", countByEventType(interactionEvents, "SHARE"));
|
|
|
- result.put("checkinCount", countByEventType(interactionEvents, "CHECKIN"));
|
|
|
- result.put("consultCount", countByEventType(interactionEvents, "CONSULT"));
|
|
|
- result.put("postLikeCount", countByEventType(interactionEvents, "POST_LIKE"));
|
|
|
- result.put("postCommentCount", countByEventType(interactionEvents, "POST_COMMENT"));
|
|
|
- result.put("postRepostCount", countByEventType(interactionEvents, "POST_REPOST"));
|
|
|
- result.put("reportCount", countByEventType(interactionEvents, "REPORT"));
|
|
|
- result.put("blockCount", countByEventType(interactionEvents, "BLOCK"));
|
|
|
-
|
|
|
- // TODO: 需要从其他表查询的数据,暂时返回0
|
|
|
+ try {
|
|
|
+ StoreTrackEventMapper mapper = this.getBaseMapper();
|
|
|
+
|
|
|
+ // 使用数据库聚合查询统计互动事件(从埋点事件表store_track_event中统计)
|
|
|
+ List<Map<String, Object>> interactionStats = mapper.calculateInteractionStatistics(storeId, startDate, endDate);
|
|
|
+
|
|
|
+ // 将查询结果转换为Map,便于查找
|
|
|
+ // 注意:SQL返回的字段名是下划线命名(event_type),而非驼峰命名
|
|
|
+ Map<String, Long> eventTypeCountMap = new HashMap<>();
|
|
|
+ for (Map<String, Object> stat : interactionStats) {
|
|
|
+ String eventType = (String) stat.get("event_type");
|
|
|
+ Object countObj = stat.get("count");
|
|
|
+ long count = countObj != null ? ((Number) countObj).longValue() : 0L;
|
|
|
+ if (eventType != null) {
|
|
|
+ eventTypeCountMap.put(eventType, count);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从聚合查询结果中获取各类型事件数量(从埋点事件表store_track_event中统计)
|
|
|
+ result.put("collectCount", eventTypeCountMap.getOrDefault("COLLECT", 0L));
|
|
|
+ result.put("shareCount", eventTypeCountMap.getOrDefault("SHARE", 0L));
|
|
|
+ result.put("checkinCount", eventTypeCountMap.getOrDefault("CHECKIN", 0L));
|
|
|
+ result.put("consultCount", eventTypeCountMap.getOrDefault("CONSULT", 0L));
|
|
|
+ result.put("postLikeCount", eventTypeCountMap.getOrDefault("POST_LIKE", 0L));
|
|
|
+ result.put("postCommentCount", eventTypeCountMap.getOrDefault("POST_COMMENT", 0L));
|
|
|
+ result.put("postRepostCount", eventTypeCountMap.getOrDefault("POST_REPOST", 0L));
|
|
|
+ result.put("reportCount", eventTypeCountMap.getOrDefault("REPORT", 0L));
|
|
|
+ result.put("blockCount", eventTypeCountMap.getOrDefault("BLOCK", 0L));
|
|
|
+
|
|
|
+ // 记录调试日志
|
|
|
+ log.debug("互动数据统计结果: storeId={}, 统计到的事件类型={}, 各类型数量={}",
|
|
|
+ storeId, eventTypeCountMap.keySet(), eventTypeCountMap);
|
|
|
+
|
|
|
+ // 从其他表查询的数据(life_fans表使用phoneId关联)
|
|
|
+ String storePhoneId = getStorePhoneId(storeId);
|
|
|
+ log.debug("获取店铺phoneId: storeId={}, storePhoneId={}", storeId, storePhoneId);
|
|
|
+ if (storePhoneId != null) {
|
|
|
+ long friendCount = calculateFriendCount(storePhoneId, startDate, endDate);
|
|
|
+ long followCount = calculateFollowCount(storePhoneId, startDate, endDate);
|
|
|
+ long fansCount = calculateFansCount(storePhoneId, startDate, endDate);
|
|
|
+ result.put("friendCount", friendCount);
|
|
|
+ result.put("followCount", followCount);
|
|
|
+ result.put("fansCount", fansCount);
|
|
|
+ log.debug("好友/关注/粉丝数量: storeId={}, phoneId={}, friendCount={}, followCount={}, fansCount={}",
|
|
|
+ storeId, storePhoneId, friendCount, followCount, fansCount);
|
|
|
+ } else {
|
|
|
+ log.warn("无法获取店铺phoneId: storeId={}", storeId);
|
|
|
+ result.put("friendCount", 0L);
|
|
|
+ result.put("followCount", 0L);
|
|
|
+ result.put("fansCount", 0L);
|
|
|
+ }
|
|
|
+ result.put("postCount", calculatePostCount(storeId, startDate, endDate));
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算互动数据失败: storeId={}", storeId, e);
|
|
|
+ // 返回默认值
|
|
|
+ result.put("collectCount", 0L);
|
|
|
+ result.put("shareCount", 0L);
|
|
|
+ result.put("checkinCount", 0L);
|
|
|
+ result.put("consultCount", 0L);
|
|
|
+ result.put("postLikeCount", 0L);
|
|
|
+ result.put("postCommentCount", 0L);
|
|
|
+ result.put("postRepostCount", 0L);
|
|
|
+ result.put("reportCount", 0L);
|
|
|
+ result.put("blockCount", 0L);
|
|
|
result.put("friendCount", 0L);
|
|
|
result.put("followCount", 0L);
|
|
|
result.put("fansCount", 0L);
|
|
|
result.put("postCount", 0L);
|
|
|
+ }
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 计算优惠券数据(符合文档格式)
|
|
|
+ * 获取店铺的phoneId(格式:store_手机号)
|
|
|
+ */
|
|
|
+ private String getStorePhoneId(Integer storeId) {
|
|
|
+ try {
|
|
|
+ LambdaQueryWrapper<StoreUser> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreUser::getStoreId, storeId)
|
|
|
+ .eq(StoreUser::getDeleteFlag, 0)
|
|
|
+ .last("LIMIT 1");
|
|
|
+ StoreUser storeUser = storeUserMapper.selectOne(wrapper);
|
|
|
+ if (storeUser != null && storeUser.getPhone() != null) {
|
|
|
+ return "store_" + storeUser.getPhone();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取店铺phoneId失败: storeId={}", storeId, e);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算好友数量(互相关注的用户数)
|
|
|
+ * 注意:好友数量是累计数据,统计截止到endDate的所有互相关注关系
|
|
|
+ */
|
|
|
+ private long calculateFriendCount(String storePhoneId, Date startDate, Date endDate) {
|
|
|
+ try {
|
|
|
+ // 查询店铺关注的所有用户(截止到统计日期)
|
|
|
+ LambdaQueryWrapper<LifeFans> followWrapper = new LambdaQueryWrapper<>();
|
|
|
+ followWrapper.eq(LifeFans::getFansId, storePhoneId)
|
|
|
+ .lt(LifeFans::getCreatedTime, endDate)
|
|
|
+ .eq(LifeFans::getDeleteFlag, 0);
|
|
|
+ Set<String> followedIds = lifeFansMapper.selectList(followWrapper).stream()
|
|
|
+ .map(LifeFans::getFollowedId)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+
|
|
|
+ if (followedIds.isEmpty()) {
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询关注店铺的所有用户(截止到统计日期)
|
|
|
+ LambdaQueryWrapper<LifeFans> fansWrapper = new LambdaQueryWrapper<>();
|
|
|
+ fansWrapper.eq(LifeFans::getFollowedId, storePhoneId)
|
|
|
+ .lt(LifeFans::getCreatedTime, endDate)
|
|
|
+ .eq(LifeFans::getDeleteFlag, 0);
|
|
|
+ Set<String> fansIds = lifeFansMapper.selectList(fansWrapper).stream()
|
|
|
+ .map(LifeFans::getFansId)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+
|
|
|
+ // 互相关注的用户数 = 交集
|
|
|
+ followedIds.retainAll(fansIds);
|
|
|
+ return followedIds.size();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算好友数量失败: storePhoneId={}", storePhoneId, e);
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算关注数量(店铺用户关注的人数)
|
|
|
+ * 注意:关注数量是累计数据,统计截止到endDate的所有关注关系
|
|
|
+ */
|
|
|
+ private long calculateFollowCount(String storePhoneId, Date startDate, Date endDate) {
|
|
|
+ try {
|
|
|
+ LambdaQueryWrapper<LifeFans> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(LifeFans::getFansId, storePhoneId)
|
|
|
+ .lt(LifeFans::getCreatedTime, endDate)
|
|
|
+ .eq(LifeFans::getDeleteFlag, 0);
|
|
|
+ return lifeFansMapper.selectCount(wrapper);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算关注数量失败: storePhoneId={}", storePhoneId, e);
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算粉丝数量(关注店铺用户的人数)
|
|
|
+ * 注意:粉丝数量是累计数据,统计截止到endDate的所有粉丝关系
|
|
|
*/
|
|
|
- private Map<String, Object> calculateCouponData(List<StoreTrackEvent> events, Integer storeId) {
|
|
|
- Map<String, Object> result = new java.util.HashMap<>();
|
|
|
+ private long calculateFansCount(String storePhoneId, Date startDate, Date endDate) {
|
|
|
+ try {
|
|
|
+ LambdaQueryWrapper<LifeFans> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(LifeFans::getFollowedId, storePhoneId)
|
|
|
+ .lt(LifeFans::getCreatedTime, endDate)
|
|
|
+ .eq(LifeFans::getDeleteFlag, 0);
|
|
|
+ return lifeFansMapper.selectCount(wrapper);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算粉丝数量失败: storePhoneId={}", storePhoneId, e);
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算发布动态数量(从life_user_dynamics表查询,type=2商家社区)
|
|
|
+ */
|
|
|
+ private long calculatePostCount(Integer storeId, Date startDate, Date endDate) {
|
|
|
+ try {
|
|
|
+ String storePhoneId = getStorePhoneId(storeId);
|
|
|
+ if (storePhoneId == null) {
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+
|
|
|
+ LambdaQueryWrapper<LifeUserDynamics> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(LifeUserDynamics::getPhoneId, storePhoneId)
|
|
|
+ .eq(LifeUserDynamics::getType, "2") // 商家社区
|
|
|
+ .eq(LifeUserDynamics::getDraft, 0) // 非草稿
|
|
|
+ .ge(LifeUserDynamics::getCreatedTime, startDate)
|
|
|
+ .lt(LifeUserDynamics::getCreatedTime, endDate)
|
|
|
+ .eq(LifeUserDynamics::getDeleteFlag, 0);
|
|
|
+ return lifeUserDynamicsMapper.selectCount(wrapper);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算发布动态数量失败: storeId={}", storeId, e);
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算优惠券数据
|
|
|
+ */
|
|
|
+ private Map<String, Object> calculateCouponData(Integer storeId, Date startDate, Date endDate) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
|
- // TODO: 需要从 life_discount_coupon_user 等表查询,暂时返回0
|
|
|
- result.put("giveToFriendCount", 0L);
|
|
|
- result.put("giveToFriendAmount", java.math.BigDecimal.ZERO);
|
|
|
- result.put("giveToFriendUseCount", 0L);
|
|
|
- result.put("giveToFriendUseAmount", java.math.BigDecimal.ZERO);
|
|
|
- result.put("giveToFriendUseAmountPercent", 0.0);
|
|
|
- result.put("friendGiveCount", 0L);
|
|
|
- result.put("friendGiveAmount", java.math.BigDecimal.ZERO);
|
|
|
- result.put("friendGiveUseCount", 0L);
|
|
|
- result.put("friendGiveUseAmount", java.math.BigDecimal.ZERO);
|
|
|
- result.put("friendGiveUseAmountPercent", 0.0);
|
|
|
+ try {
|
|
|
+ // 赠送好友相关数据(从life_discount_coupon_user表统计,关联店铺的优惠券,type=1优惠券)
|
|
|
+ String storeIdStr = String.valueOf(storeId);
|
|
|
+ List<LifeDiscountCouponUser> couponUsers = queryCouponUsersByStore(storeIdStr, startDate, endDate);
|
|
|
+
|
|
|
+ // 赠送好友数量
|
|
|
+ long giveToFriendCount = couponUsers.size();
|
|
|
+ result.put("giveToFriendCount", giveToFriendCount);
|
|
|
+
|
|
|
+ // 赠送好友金额合计(优惠券面值的总和)
|
|
|
+ BigDecimal giveToFriendAmount = couponUsers.stream()
|
|
|
+ .map(cu -> {
|
|
|
+ LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(cu.getCouponId());
|
|
|
+ return coupon != null && coupon.getNominalValue() != null ? coupon.getNominalValue() : BigDecimal.ZERO;
|
|
|
+ })
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ result.put("giveToFriendAmount", giveToFriendAmount);
|
|
|
+
|
|
|
+ // 赠送好友使用数量(status=1已使用的数量)
|
|
|
+ long giveToFriendUseCount = couponUsers.stream()
|
|
|
+ .filter(cu -> cu.getStatus() != null && cu.getStatus() == 1)
|
|
|
+ .count();
|
|
|
+ result.put("giveToFriendUseCount", giveToFriendUseCount);
|
|
|
+
|
|
|
+ // 赠送好友使用金额合计(已使用优惠券的面值总和)
|
|
|
+ BigDecimal giveToFriendUseAmount = couponUsers.stream()
|
|
|
+ .filter(cu -> cu.getStatus() != null && cu.getStatus() == 1)
|
|
|
+ .map(cu -> {
|
|
|
+ LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(cu.getCouponId());
|
|
|
+ return coupon != null && coupon.getNominalValue() != null ? coupon.getNominalValue() : BigDecimal.ZERO;
|
|
|
+ })
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ result.put("giveToFriendUseAmount", giveToFriendUseAmount);
|
|
|
+
|
|
|
+ // 赠送好友使用金额占比
|
|
|
+ double giveToFriendUseAmountPercent = 0.0;
|
|
|
+ if (giveToFriendAmount.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ giveToFriendUseAmountPercent = giveToFriendUseAmount
|
|
|
+ .divide(giveToFriendAmount, 4, RoundingMode.HALF_UP)
|
|
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
+ .doubleValue();
|
|
|
+ }
|
|
|
+ result.put("giveToFriendUseAmountPercent", giveToFriendUseAmountPercent);
|
|
|
+
|
|
|
+ // 好友赠送相关数据(从life_discount_coupon_store_friend表统计,type=1优惠券)
|
|
|
+ List<LifeDiscountCouponStoreFriend> friendCoupons = queryFriendCouponsByStore(storeId, startDate, endDate, 1);
|
|
|
+
|
|
|
+ // 好友赠送数量
|
|
|
+ long friendGiveCount = friendCoupons.size();
|
|
|
+ result.put("friendGiveCount", friendGiveCount);
|
|
|
+
|
|
|
+ // 好友赠送金额合计(好友赠送的优惠券面值总和)
|
|
|
+ BigDecimal friendGiveAmount = friendCoupons.stream()
|
|
|
+ .map(fc -> {
|
|
|
+ LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(fc.getCouponId());
|
|
|
+ return coupon != null && coupon.getNominalValue() != null ? coupon.getNominalValue() : BigDecimal.ZERO;
|
|
|
+ })
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ result.put("friendGiveAmount", friendGiveAmount);
|
|
|
+
|
|
|
+ // 好友赠送使用数量(已使用的数量)
|
|
|
+ // 需要通过coupon_id和friend_store_user_id查询life_discount_coupon_user表
|
|
|
+ long friendGiveUseCount = calculateFriendCouponUseCount(friendCoupons, startDate, endDate);
|
|
|
+ result.put("friendGiveUseCount", friendGiveUseCount);
|
|
|
+
|
|
|
+ // 好友赠送使用金额合计(已使用的优惠券面值总和)
|
|
|
+ BigDecimal friendGiveUseAmount = calculateFriendCouponUseAmount(friendCoupons, startDate, endDate);
|
|
|
+ result.put("friendGiveUseAmount", friendGiveUseAmount);
|
|
|
+
|
|
|
+ // 好友赠送使用金额占比
|
|
|
+ double friendGiveUseAmountPercent = 0.0;
|
|
|
+ if (friendGiveAmount.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ friendGiveUseAmountPercent = friendGiveUseAmount
|
|
|
+ .divide(friendGiveAmount, 4, RoundingMode.HALF_UP)
|
|
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
+ .doubleValue();
|
|
|
+ }
|
|
|
+ result.put("friendGiveUseAmountPercent", friendGiveUseAmountPercent);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算优惠券数据失败: storeId={}", storeId, e);
|
|
|
+ // 返回默认值,避免统计失败
|
|
|
+ result.put("giveToFriendCount", 0L);
|
|
|
+ result.put("giveToFriendAmount", BigDecimal.ZERO);
|
|
|
+ result.put("giveToFriendUseCount", 0L);
|
|
|
+ result.put("giveToFriendUseAmount", BigDecimal.ZERO);
|
|
|
+ result.put("giveToFriendUseAmountPercent", 0.0);
|
|
|
+ result.put("friendGiveCount", 0L);
|
|
|
+ result.put("friendGiveAmount", BigDecimal.ZERO);
|
|
|
+ result.put("friendGiveUseCount", 0L);
|
|
|
+ result.put("friendGiveUseAmount", BigDecimal.ZERO);
|
|
|
+ result.put("friendGiveUseAmountPercent", 0.0);
|
|
|
+ }
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 计算代金券数据(符合文档格式)
|
|
|
+ * 查询店铺关联的优惠券用户数据
|
|
|
*/
|
|
|
- private Map<String, Object> calculateVoucherData(List<StoreTrackEvent> events, Integer storeId) {
|
|
|
- Map<String, Object> result = new java.util.HashMap<>();
|
|
|
-
|
|
|
- List<StoreTrackEvent> voucherEvents = events.stream()
|
|
|
- .filter(e -> "VOUCHER".equals(e.getEventCategory()))
|
|
|
- .collect(java.util.stream.Collectors.toList());
|
|
|
-
|
|
|
- // 赠送好友数量
|
|
|
- long giveToFriendCount = countByEventType(voucherEvents, "VOUCHER_GIVE");
|
|
|
- result.put("giveToFriendCount", giveToFriendCount);
|
|
|
-
|
|
|
- // 赠送好友金额合计
|
|
|
- java.math.BigDecimal giveToFriendAmount = voucherEvents.stream()
|
|
|
- .filter(e -> "VOUCHER_GIVE".equals(e.getEventType()) && e.getAmount() != null)
|
|
|
- .map(StoreTrackEvent::getAmount)
|
|
|
- .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
|
|
|
- result.put("giveToFriendAmount", giveToFriendAmount);
|
|
|
-
|
|
|
- // 赠送好友使用数量
|
|
|
- long giveToFriendUseCount = countByEventType(voucherEvents, "VOUCHER_USE");
|
|
|
- result.put("giveToFriendUseCount", giveToFriendUseCount);
|
|
|
+ private List<LifeDiscountCouponUser> queryCouponUsersByStore(String storeId, Date startDate, Date endDate) {
|
|
|
+ try {
|
|
|
+ // 先查询店铺的优惠券(type=1优惠券)
|
|
|
+ LambdaQueryWrapper<LifeDiscountCoupon> couponWrapper = new LambdaQueryWrapper<>();
|
|
|
+ couponWrapper.eq(LifeDiscountCoupon::getStoreId, storeId)
|
|
|
+ .eq(LifeDiscountCoupon::getType, 1) // 优惠券
|
|
|
+ .eq(LifeDiscountCoupon::getDeleteFlag, 0);
|
|
|
+ List<LifeDiscountCoupon> coupons = lifeDiscountCouponMapper.selectList(couponWrapper);
|
|
|
+
|
|
|
+ if (coupons.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Integer> couponIds = coupons.stream()
|
|
|
+ .map(LifeDiscountCoupon::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 查询优惠券用户数据
|
|
|
+ LambdaQueryWrapper<LifeDiscountCouponUser> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.in(LifeDiscountCouponUser::getCouponId, couponIds)
|
|
|
+ .ge(LifeDiscountCouponUser::getReceiveTime, startDate)
|
|
|
+ .lt(LifeDiscountCouponUser::getReceiveTime, endDate)
|
|
|
+ .eq(LifeDiscountCouponUser::getDeleteFlag, 0);
|
|
|
+ return lifeDiscountCouponUserMapper.selectList(wrapper);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("查询店铺优惠券用户数据失败: storeId={}", storeId, e);
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询好友赠送的优惠券/代金券数据
|
|
|
+ */
|
|
|
+ private List<LifeDiscountCouponStoreFriend> queryFriendCouponsByStore(Integer storeId, Date startDate, Date endDate, Integer type) {
|
|
|
+ try {
|
|
|
+ // 先查询店铺用户
|
|
|
+ LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
|
|
|
+ userWrapper.eq(StoreUser::getStoreId, storeId)
|
|
|
+ .eq(StoreUser::getDeleteFlag, 0);
|
|
|
+ List<StoreUser> storeUsers = storeUserMapper.selectList(userWrapper);
|
|
|
+
|
|
|
+ if (storeUsers.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Integer> storeUserIds = storeUsers.stream()
|
|
|
+ .map(StoreUser::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 查询好友赠送的优惠券/代金券
|
|
|
+ // 需要通过coupon_id关联查询优惠券类型
|
|
|
+ LambdaQueryWrapper<LifeDiscountCouponStoreFriend> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.in(LifeDiscountCouponStoreFriend::getFriendStoreUserId, storeUserIds)
|
|
|
+ .ge(LifeDiscountCouponStoreFriend::getCreatedTime, startDate)
|
|
|
+ .lt(LifeDiscountCouponStoreFriend::getCreatedTime, endDate)
|
|
|
+ .eq(LifeDiscountCouponStoreFriend::getDeleteFlag, 0);
|
|
|
+ List<LifeDiscountCouponStoreFriend> storeFriends = lifeDiscountCouponStoreFriendMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ // 过滤指定类型的优惠券
|
|
|
+ return storeFriends.stream()
|
|
|
+ .filter(sf -> {
|
|
|
+ LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(sf.getCouponId());
|
|
|
+ return coupon != null && coupon.getType() != null && coupon.getType().equals(type);
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("查询好友赠送优惠券/代金券数据失败: storeId={}, type={}", storeId, type, e);
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算好友赠送优惠券使用数量
|
|
|
+ */
|
|
|
+ private long calculateFriendCouponUseCount(List<LifeDiscountCouponStoreFriend> friendCoupons, Date startDate, Date endDate) {
|
|
|
+ if (friendCoupons == null || friendCoupons.isEmpty()) {
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
|
|
|
- // 赠送好友使用金额合计
|
|
|
- java.math.BigDecimal giveToFriendUseAmount = voucherEvents.stream()
|
|
|
- .filter(e -> "VOUCHER_USE".equals(e.getEventType()) && e.getAmount() != null)
|
|
|
- .map(StoreTrackEvent::getAmount)
|
|
|
- .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
|
|
|
- result.put("giveToFriendUseAmount", giveToFriendUseAmount);
|
|
|
+ try {
|
|
|
+ List<Integer> couponIds = friendCoupons.stream()
|
|
|
+ .map(LifeDiscountCouponStoreFriend::getCouponId)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ LambdaQueryWrapper<LifeDiscountCouponUser> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.in(LifeDiscountCouponUser::getCouponId, couponIds)
|
|
|
+ .eq(LifeDiscountCouponUser::getStatus, 1) // 已使用
|
|
|
+ .ge(LifeDiscountCouponUser::getUseTime, startDate)
|
|
|
+ .lt(LifeDiscountCouponUser::getUseTime, endDate)
|
|
|
+ .eq(LifeDiscountCouponUser::getDeleteFlag, 0);
|
|
|
+ return lifeDiscountCouponUserMapper.selectCount(wrapper);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算好友赠送优惠券使用数量失败", e);
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算好友赠送优惠券使用金额
|
|
|
+ */
|
|
|
+ private BigDecimal calculateFriendCouponUseAmount(List<LifeDiscountCouponStoreFriend> friendCoupons, Date startDate, Date endDate) {
|
|
|
+ if (friendCoupons == null || friendCoupons.isEmpty()) {
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
|
|
|
- // 赠送好友使用金额占比
|
|
|
- double giveToFriendUseAmountPercent = 0.0;
|
|
|
- if (giveToFriendAmount.compareTo(java.math.BigDecimal.ZERO) > 0) {
|
|
|
- giveToFriendUseAmountPercent = giveToFriendUseAmount.divide(giveToFriendAmount, 4, java.math.RoundingMode.HALF_UP)
|
|
|
- .multiply(new java.math.BigDecimal("100")).doubleValue();
|
|
|
+ try {
|
|
|
+ List<Integer> couponIds = friendCoupons.stream()
|
|
|
+ .map(LifeDiscountCouponStoreFriend::getCouponId)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ LambdaQueryWrapper<LifeDiscountCouponUser> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.in(LifeDiscountCouponUser::getCouponId, couponIds)
|
|
|
+ .eq(LifeDiscountCouponUser::getStatus, 1) // 已使用
|
|
|
+ .ge(LifeDiscountCouponUser::getUseTime, startDate)
|
|
|
+ .lt(LifeDiscountCouponUser::getUseTime, endDate)
|
|
|
+ .eq(LifeDiscountCouponUser::getDeleteFlag, 0);
|
|
|
+ List<LifeDiscountCouponUser> usedCoupons = lifeDiscountCouponUserMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ return usedCoupons.stream()
|
|
|
+ .map(cu -> {
|
|
|
+ LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(cu.getCouponId());
|
|
|
+ return coupon != null && coupon.getNominalValue() != null ? coupon.getNominalValue() : BigDecimal.ZERO;
|
|
|
+ })
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算好友赠送优惠券使用金额失败", e);
|
|
|
+ return BigDecimal.ZERO;
|
|
|
}
|
|
|
- result.put("giveToFriendUseAmountPercent", giveToFriendUseAmountPercent);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算代金券数据- 优化:使用数据库聚合查询
|
|
|
+ */
|
|
|
+ private Map<String, Object> calculateVoucherData(Integer storeId, Date startDate, Date endDate) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
|
- // TODO: 需要从 life_discount_coupon_store_friend 表查询的数据,暂时返回0
|
|
|
+ try {
|
|
|
+ StoreTrackEventMapper mapper = this.getBaseMapper();
|
|
|
+
|
|
|
+ // 使用数据库聚合查询统计代金券事件
|
|
|
+ List<Map<String, Object>> voucherStats = mapper.calculateVoucherStatistics(storeId, startDate, endDate);
|
|
|
+
|
|
|
+ // 将查询结果转换为Map,便于查找
|
|
|
+ // 注意:SQL返回的字段名是下划线命名(event_type),而非驼峰命名
|
|
|
+ Map<String, Map<String, Object>> eventTypeMap = new HashMap<>();
|
|
|
+ for (Map<String, Object> stat : voucherStats) {
|
|
|
+ String eventType = (String) stat.get("event_type");
|
|
|
+ if (eventType != null) {
|
|
|
+ eventTypeMap.put(eventType, stat);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 赠送好友数量
|
|
|
+ Map<String, Object> giveStat = eventTypeMap.getOrDefault("VOUCHER_GIVE", new HashMap<>());
|
|
|
+ Object giveCountObj = giveStat.get("count");
|
|
|
+ long giveToFriendCount = giveCountObj != null ? ((Number) giveCountObj).longValue() : 0L;
|
|
|
+ result.put("giveToFriendCount", giveToFriendCount);
|
|
|
+
|
|
|
+ // 赠送好友金额合计
|
|
|
+ Object giveAmountObj = giveStat.get("totalAmount");
|
|
|
+ BigDecimal giveToFriendAmount = giveAmountObj != null ?
|
|
|
+ new BigDecimal(giveAmountObj.toString()) : BigDecimal.ZERO;
|
|
|
+ result.put("giveToFriendAmount", giveToFriendAmount);
|
|
|
+
|
|
|
+ // 赠送好友使用数量
|
|
|
+ Map<String, Object> useStat = eventTypeMap.getOrDefault("VOUCHER_USE", new HashMap<>());
|
|
|
+ Object useCountObj = useStat.get("count");
|
|
|
+ long giveToFriendUseCount = useCountObj != null ? ((Number) useCountObj).longValue() : 0L;
|
|
|
+ result.put("giveToFriendUseCount", giveToFriendUseCount);
|
|
|
+
|
|
|
+ // 赠送好友使用金额合计
|
|
|
+ Object useAmountObj = useStat.get("totalAmount");
|
|
|
+ BigDecimal giveToFriendUseAmount = useAmountObj != null ?
|
|
|
+ new BigDecimal(useAmountObj.toString()) : BigDecimal.ZERO;
|
|
|
+ result.put("giveToFriendUseAmount", giveToFriendUseAmount);
|
|
|
+
|
|
|
+ // 赠送好友使用金额占比
|
|
|
+ double giveToFriendUseAmountPercent = 0.0;
|
|
|
+ if (giveToFriendAmount.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ giveToFriendUseAmountPercent = giveToFriendUseAmount
|
|
|
+ .divide(giveToFriendAmount, 4, RoundingMode.HALF_UP)
|
|
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
+ .doubleValue();
|
|
|
+ }
|
|
|
+ result.put("giveToFriendUseAmountPercent", giveToFriendUseAmountPercent);
|
|
|
+
|
|
|
+ // 好友赠送代金券相关数据(从life_discount_coupon_store_friend表统计,type=2代金券)
|
|
|
+ List<LifeDiscountCouponStoreFriend> friendVouchers = queryFriendCouponsByStore(storeId, startDate, endDate, 2); // type=2代金券
|
|
|
+
|
|
|
+ // 好友赠送数量
|
|
|
+ long friendGiveCount = friendVouchers.size();
|
|
|
+ result.put("friendGiveCount", friendGiveCount);
|
|
|
+
|
|
|
+ // 好友赠送金额合计(好友赠送的代金券面值总和)
|
|
|
+ BigDecimal friendGiveAmount = friendVouchers.stream()
|
|
|
+ .map(fv -> {
|
|
|
+ LifeDiscountCoupon voucher = lifeDiscountCouponMapper.selectById(fv.getCouponId());
|
|
|
+ return voucher != null && voucher.getNominalValue() != null ? voucher.getNominalValue() : BigDecimal.ZERO;
|
|
|
+ })
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ result.put("friendGiveAmount", friendGiveAmount);
|
|
|
+
|
|
|
+ // 好友赠送使用数量(已使用的数量)
|
|
|
+ long friendGiveUseCount = calculateFriendCouponUseCount(friendVouchers, startDate, endDate);
|
|
|
+ result.put("friendGiveUseCount", friendGiveUseCount);
|
|
|
+
|
|
|
+ // 好友赠送使用金额合计(已使用的代金券面值总和)
|
|
|
+ BigDecimal friendGiveUseAmount = calculateFriendCouponUseAmount(friendVouchers, startDate, endDate);
|
|
|
+ result.put("friendGiveUseAmount", friendGiveUseAmount);
|
|
|
+
|
|
|
+ // 好友赠送使用金额占比
|
|
|
+ double friendGiveUseAmountPercent = 0.0;
|
|
|
+ if (friendGiveAmount.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ friendGiveUseAmountPercent = friendGiveUseAmount
|
|
|
+ .divide(friendGiveAmount, 4, RoundingMode.HALF_UP)
|
|
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
+ .doubleValue();
|
|
|
+ }
|
|
|
+ result.put("friendGiveUseAmountPercent", friendGiveUseAmountPercent);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算代金券数据失败: storeId={}", storeId, e);
|
|
|
+ // 返回默认值
|
|
|
+ result.put("giveToFriendCount", 0L);
|
|
|
+ result.put("giveToFriendAmount", BigDecimal.ZERO);
|
|
|
+ result.put("giveToFriendUseCount", 0L);
|
|
|
+ result.put("giveToFriendUseAmount", BigDecimal.ZERO);
|
|
|
+ result.put("giveToFriendUseAmountPercent", 0.0);
|
|
|
result.put("friendGiveCount", 0L);
|
|
|
- result.put("friendGiveAmount", java.math.BigDecimal.ZERO);
|
|
|
+ result.put("friendGiveAmount", BigDecimal.ZERO);
|
|
|
result.put("friendGiveUseCount", 0L);
|
|
|
- result.put("friendGiveUseAmount", java.math.BigDecimal.ZERO);
|
|
|
+ result.put("friendGiveUseAmount", BigDecimal.ZERO);
|
|
|
result.put("friendGiveUseAmountPercent", 0.0);
|
|
|
+ }
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 计算服务质量数据(符合文档格式)
|
|
|
+ * 计算服务质量数据- 优化:使用数据库聚合查询
|
|
|
*/
|
|
|
- private Map<String, Object> calculateServiceData(List<StoreTrackEvent> events, Integer storeId) {
|
|
|
- Map<String, Object> result = new java.util.HashMap<>();
|
|
|
-
|
|
|
- List<StoreTrackEvent> serviceEvents = events.stream()
|
|
|
- .filter(e -> "SERVICE".equals(e.getEventCategory()))
|
|
|
- .collect(java.util.stream.Collectors.toList());
|
|
|
+ private Map<String, Object> calculateServiceData(Integer storeId, Date startDate, Date endDate) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
|
- // 差评申诉次数
|
|
|
- long appealCount = countByEventType(serviceEvents, "APPEAL");
|
|
|
- result.put("appealCount", appealCount);
|
|
|
+ try {
|
|
|
+ StoreTrackEventMapper mapper = this.getBaseMapper();
|
|
|
+
|
|
|
+ // 使用数据库聚合查询统计服务事件
|
|
|
+ List<Map<String, Object>> serviceStats = mapper.calculateServiceStatistics(storeId, startDate, endDate);
|
|
|
+
|
|
|
+ // 将查询结果转换为Map,便于查找
|
|
|
+ // 注意:SQL返回的字段名是下划线命名(event_type),而非驼峰命名
|
|
|
+ Map<String, Long> eventTypeCountMap = new HashMap<>();
|
|
|
+ for (Map<String, Object> stat : serviceStats) {
|
|
|
+ String eventType = (String) stat.get("event_type");
|
|
|
+ Object countObj = stat.get("count");
|
|
|
+ long count = countObj != null ? ((Number) countObj).longValue() : 0L;
|
|
|
+ if (eventType != null) {
|
|
|
+ eventTypeCountMap.put(eventType, count);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // TODO: 需要从 common_rating 和 store_comment_appeal 表查询的数据,暂时返回0或null
|
|
|
- result.put("storeScore", null);
|
|
|
- result.put("tasteScore", null);
|
|
|
- result.put("environmentScore", null);
|
|
|
- result.put("serviceScore", null);
|
|
|
- result.put("ratingCount", 0L);
|
|
|
- result.put("goodRatingCount", 0L);
|
|
|
- result.put("midRatingCount", 0L);
|
|
|
- result.put("badRatingCount", 0L);
|
|
|
- result.put("badRatingPercent", 0.0);
|
|
|
- result.put("appealSuccessCount", 0L);
|
|
|
- result.put("appealSuccessPercent", 0.0);
|
|
|
+ // 差评申诉次数
|
|
|
+ long appealCount = eventTypeCountMap.getOrDefault("APPEAL", 0L);
|
|
|
+ result.put("appealCount", appealCount);
|
|
|
+
|
|
|
+ // 从 common_rating 表查询评价数据
|
|
|
+ List<CommonRating> ratings = queryRatings(storeId, startDate, endDate);
|
|
|
+
|
|
|
+ // 店铺评分(计算平均分)
|
|
|
+ Double storeScore = calculateStoreScore(ratings);
|
|
|
+ result.put("storeScore", storeScore);
|
|
|
+
|
|
|
+ // 口味评分、环境评分、服务评分
|
|
|
+ Map<String, Double> otherScores = calculateOtherScores(ratings);
|
|
|
+ result.put("tasteScore", otherScores.getOrDefault("口味", null));
|
|
|
+ result.put("environmentScore", otherScores.getOrDefault("环境", null));
|
|
|
+ result.put("serviceScore", otherScores.getOrDefault("服务", null));
|
|
|
+
|
|
|
+ // 评价数量
|
|
|
+ long ratingCount = ratings.size();
|
|
|
+ result.put("ratingCount", ratingCount);
|
|
|
+
|
|
|
+ // 好评数量(score >= 4.5)
|
|
|
+ long goodRatingCount = ratings.stream()
|
|
|
+ .filter(r -> r.getScore() != null && r.getScore() >= 4.5)
|
|
|
+ .count();
|
|
|
+ result.put("goodRatingCount", goodRatingCount);
|
|
|
+
|
|
|
+ // 中评数量(3.0 <= score <= 4.0)
|
|
|
+ long midRatingCount = ratings.stream()
|
|
|
+ .filter(r -> r.getScore() != null && r.getScore() >= 3.0 && r.getScore() <= 4.0)
|
|
|
+ .count();
|
|
|
+ result.put("midRatingCount", midRatingCount);
|
|
|
+
|
|
|
+ // 差评数量(0.5 <= score <= 2.5)
|
|
|
+ long badRatingCount = ratings.stream()
|
|
|
+ .filter(r -> r.getScore() != null && r.getScore() >= 0.5 && r.getScore() <= 2.5)
|
|
|
+ .count();
|
|
|
+ result.put("badRatingCount", badRatingCount);
|
|
|
+
|
|
|
+ // 差评占比
|
|
|
+ double badRatingPercent = 0.0;
|
|
|
+ if (ratingCount > 0) {
|
|
|
+ badRatingPercent = (double) badRatingCount / ratingCount * 100;
|
|
|
+ badRatingPercent = BigDecimal.valueOf(badRatingPercent)
|
|
|
+ .setScale(2, RoundingMode.HALF_UP)
|
|
|
+ .doubleValue();
|
|
|
+ }
|
|
|
+ result.put("badRatingPercent", badRatingPercent);
|
|
|
+
|
|
|
+ // 差评申诉成功次数
|
|
|
+ long appealSuccessCount = calculateAppealSuccessCount(storeId, startDate, endDate);
|
|
|
+ result.put("appealSuccessCount", appealSuccessCount);
|
|
|
+
|
|
|
+ // 差评申诉成功占比
|
|
|
+ double appealSuccessPercent = 0.0;
|
|
|
+ if (appealCount > 0) {
|
|
|
+ appealSuccessPercent = (double) appealSuccessCount / appealCount * 100;
|
|
|
+ appealSuccessPercent = BigDecimal.valueOf(appealSuccessPercent)
|
|
|
+ .setScale(2, RoundingMode.HALF_UP)
|
|
|
+ .doubleValue();
|
|
|
+ }
|
|
|
+ result.put("appealSuccessPercent", appealSuccessPercent);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算服务质量数据失败: storeId={}", storeId, e);
|
|
|
+ // 返回默认值,避免统计失败
|
|
|
+ result.put("storeScore", null);
|
|
|
+ result.put("tasteScore", null);
|
|
|
+ result.put("environmentScore", null);
|
|
|
+ result.put("serviceScore", null);
|
|
|
+ result.put("ratingCount", 0L);
|
|
|
+ result.put("goodRatingCount", 0L);
|
|
|
+ result.put("midRatingCount", 0L);
|
|
|
+ result.put("badRatingCount", 0L);
|
|
|
+ result.put("badRatingPercent", 0.0);
|
|
|
+ result.put("appealCount", 0L);
|
|
|
+ result.put("appealSuccessCount", 0L);
|
|
|
+ result.put("appealSuccessPercent", 0.0);
|
|
|
+ }
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 计算价目表排名数据(符合文档格式)
|
|
|
+ * 查询评价数据
|
|
|
*/
|
|
|
- private List<Map<String, Object>> calculatePriceRankingData(List<StoreTrackEvent> events) {
|
|
|
- List<StoreTrackEvent> priceEvents = events.stream()
|
|
|
- .filter(e -> "PRICE".equals(e.getEventCategory()))
|
|
|
- .collect(java.util.stream.Collectors.toList());
|
|
|
+ private List<CommonRating> queryRatings(Integer storeId, Date startDate, Date endDate) {
|
|
|
+ try {
|
|
|
+ LambdaQueryWrapper<CommonRating> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(CommonRating::getBusinessId, storeId)
|
|
|
+ .eq(CommonRating::getBusinessType, 1) // 商铺评价
|
|
|
+ .ge(CommonRating::getCreatedTime, startDate)
|
|
|
+ .lt(CommonRating::getCreatedTime, endDate)
|
|
|
+ .eq(CommonRating::getDeleteFlag, 0);
|
|
|
+ return commonRatingMapper.selectList(wrapper);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("查询评价数据失败: storeId={}", storeId, e);
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算店铺评分(平均分)
|
|
|
+ */
|
|
|
+ private Double calculateStoreScore(List<CommonRating> ratings) {
|
|
|
+ if (ratings == null || ratings.isEmpty()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
|
|
|
- // 按 targetId (priceId) 分组统计
|
|
|
- Map<Integer, List<StoreTrackEvent>> eventsByPriceId = priceEvents.stream()
|
|
|
- .filter(e -> e.getTargetId() != null)
|
|
|
- .collect(java.util.stream.Collectors.groupingBy(StoreTrackEvent::getTargetId));
|
|
|
+ double sum = ratings.stream()
|
|
|
+ .filter(r -> r.getScore() != null)
|
|
|
+ .mapToDouble(CommonRating::getScore)
|
|
|
+ .sum();
|
|
|
+ long count = ratings.stream()
|
|
|
+ .filter(r -> r.getScore() != null)
|
|
|
+ .count();
|
|
|
|
|
|
- List<Map<String, Object>> result = new java.util.ArrayList<>();
|
|
|
+ if (count == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
|
|
|
- for (Map.Entry<Integer, List<StoreTrackEvent>> entry : eventsByPriceId.entrySet()) {
|
|
|
- Integer priceId = entry.getKey();
|
|
|
- List<StoreTrackEvent> priceIdEvents = entry.getValue();
|
|
|
-
|
|
|
- Map<String, Object> priceData = new java.util.HashMap<>();
|
|
|
- priceData.put("priceId", priceId);
|
|
|
-
|
|
|
- // 浏览量(PRICE_VIEW事件数量)
|
|
|
- long viewCount = priceIdEvents.stream()
|
|
|
- .filter(e -> "PRICE_VIEW".equals(e.getEventType()))
|
|
|
- .count();
|
|
|
- priceData.put("viewCount", (int) viewCount);
|
|
|
-
|
|
|
- // 访客数(去重后的用户ID数量)
|
|
|
- long visitorCount = priceIdEvents.stream()
|
|
|
- .filter(e -> e.getUserId() != null)
|
|
|
- .map(StoreTrackEvent::getUserId)
|
|
|
- .distinct()
|
|
|
- .count();
|
|
|
- priceData.put("visitorCount", (int) visitorCount);
|
|
|
-
|
|
|
- // 分享数(PRICE_SHARE事件数量)
|
|
|
- long shareCount = priceIdEvents.stream()
|
|
|
- .filter(e -> "PRICE_SHARE".equals(e.getEventType()))
|
|
|
- .count();
|
|
|
- priceData.put("shareCount", (int) shareCount);
|
|
|
+ double avg = sum / count;
|
|
|
+ return BigDecimal.valueOf(avg)
|
|
|
+ .setScale(1, RoundingMode.HALF_UP)
|
|
|
+ .doubleValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算多维度评分(口味、环境、服务)
|
|
|
+ */
|
|
|
+ private Map<String, Double> calculateOtherScores(List<CommonRating> ratings) {
|
|
|
+ Map<String, Double> result = new HashMap<>();
|
|
|
+ Map<String, List<Double>> scoresMap = new HashMap<>();
|
|
|
+
|
|
|
+ for (CommonRating rating : ratings) {
|
|
|
+ if (rating.getOtherScore() == null || rating.getOtherScore().trim().isEmpty()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- result.add(priceData);
|
|
|
+ try {
|
|
|
+ JSONObject otherScoreJson = JSONObject.parseObject(rating.getOtherScore());
|
|
|
+ for (String key : otherScoreJson.keySet()) {
|
|
|
+ Object value = otherScoreJson.get(key);
|
|
|
+ if (value instanceof Number) {
|
|
|
+ scoresMap.computeIfAbsent(key, k -> new ArrayList<>())
|
|
|
+ .add(((Number) value).doubleValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.debug("解析otherScore失败: {}", rating.getOtherScore(), e);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // 按 viewCount 降序排列
|
|
|
- result.sort((a, b) -> {
|
|
|
- Integer viewCountA = (Integer) a.get("viewCount");
|
|
|
- Integer viewCountB = (Integer) b.get("viewCount");
|
|
|
- return viewCountB.compareTo(viewCountA);
|
|
|
- });
|
|
|
+ // 计算每个维度的平均分
|
|
|
+ for (Map.Entry<String, List<Double>> entry : scoresMap.entrySet()) {
|
|
|
+ List<Double> scores = entry.getValue();
|
|
|
+ if (!scores.isEmpty()) {
|
|
|
+ double avg = scores.stream().mapToDouble(Double::doubleValue).sum() / scores.size();
|
|
|
+ result.put(entry.getKey(), BigDecimal.valueOf(avg)
|
|
|
+ .setScale(1, RoundingMode.HALF_UP)
|
|
|
+ .doubleValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 统计指定事件类型的数量
|
|
|
+ * 计算差评申诉成功次数
|
|
|
*/
|
|
|
- private long countByEventType(List<StoreTrackEvent> events, String eventType) {
|
|
|
- return events.stream()
|
|
|
- .filter(e -> eventType.equals(e.getEventType()))
|
|
|
- .count();
|
|
|
+ private long calculateAppealSuccessCount(Integer storeId, Date startDate, Date endDate) {
|
|
|
+ try {
|
|
|
+ LambdaQueryWrapper<StoreCommentAppeal> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreCommentAppeal::getStoreId, storeId)
|
|
|
+ .eq(StoreCommentAppeal::getAppealStatus, 2) // 已同意
|
|
|
+ .ge(StoreCommentAppeal::getCreatedTime, startDate)
|
|
|
+ .lt(StoreCommentAppeal::getCreatedTime, endDate)
|
|
|
+ .eq(StoreCommentAppeal::getDeleteFlag, 0);
|
|
|
+ return storeCommentAppealMapper.selectCount(wrapper);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算差评申诉成功次数失败: storeId={}", storeId, e);
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算价目表排名数据- 优化:使用数据库聚合查询
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> calculatePriceRankingData(Integer storeId, Date startDate, Date endDate) {
|
|
|
+ try {
|
|
|
+ StoreTrackEventMapper mapper = this.getBaseMapper();
|
|
|
+
|
|
|
+ // 使用数据库聚合查询统计价目表排名数据(已按viewCount降序排列)
|
|
|
+ List<Map<String, Object>> result = mapper.calculatePriceRanking(storeId, startDate, endDate);
|
|
|
+
|
|
|
+ // 转换数据类型,确保与文档格式一致
|
|
|
+ for (Map<String, Object> priceData : result) {
|
|
|
+ // priceId 保持原样
|
|
|
+ // viewCount, visitorCount, shareCount 转换为 Integer
|
|
|
+ Object viewCountObj = priceData.get("viewCount");
|
|
|
+ if (viewCountObj instanceof Number) {
|
|
|
+ priceData.put("viewCount", ((Number) viewCountObj).intValue());
|
|
|
+ }
|
|
|
+ Object visitorCountObj = priceData.get("visitorCount");
|
|
|
+ if (visitorCountObj instanceof Number) {
|
|
|
+ priceData.put("visitorCount", ((Number) visitorCountObj).intValue());
|
|
|
+ }
|
|
|
+ Object shareCountObj = priceData.get("shareCount");
|
|
|
+ if (shareCountObj instanceof Number) {
|
|
|
+ priceData.put("shareCount", ((Number) shareCountObj).intValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算价目表排名数据失败: storeId={}", storeId, e);
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
}
|