|
|
@@ -5,19 +5,22 @@ import com.fs.common.core.domain.R;
|
|
|
import com.fs.common.core.redis.RedisCache;
|
|
|
import com.fs.common.utils.DateUtils;
|
|
|
import com.fs.common.utils.spring.SpringUtils;
|
|
|
-import com.fs.live.domain.LiveData;
|
|
|
-import com.fs.live.domain.LiveUserFavorite;
|
|
|
-import com.fs.live.domain.LiveUserFollow;
|
|
|
-import com.fs.live.domain.LiveUserLike;
|
|
|
-import com.fs.live.mapper.LiveDataMapper;
|
|
|
+import com.fs.company.domain.Company;
|
|
|
+import com.fs.company.domain.CompanyUser;
|
|
|
+import com.fs.company.mapper.CompanyMapper;
|
|
|
+import com.fs.company.mapper.CompanyUserMapper;
|
|
|
+import com.fs.live.domain.*;
|
|
|
+import com.fs.live.mapper.*;
|
|
|
+import com.fs.live.param.LiveDataParam;
|
|
|
import com.fs.live.service.ILiveDataService;
|
|
|
import com.fs.live.service.ILiveUserFavoriteService;
|
|
|
import com.fs.live.service.ILiveUserFollowService;
|
|
|
import com.fs.live.service.ILiveUserLikeService;
|
|
|
-import com.fs.live.vo.ColumnsConfigVo;
|
|
|
-import com.fs.live.vo.DateRange;
|
|
|
-import com.fs.live.vo.RecentLiveDataVo;
|
|
|
-import com.fs.live.vo.TrendDataVO;
|
|
|
+import com.fs.live.vo.*;
|
|
|
+import com.fs.store.domain.FsStoreProduct;
|
|
|
+import com.fs.store.domain.FsUser;
|
|
|
+import com.fs.store.mapper.FsStoreProductMapper;
|
|
|
+import com.fs.store.mapper.FsUserMapper;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
@@ -31,6 +34,7 @@ import java.time.LocalDate;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.*;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
import static com.fs.common.constant.LiveKeysConstant.*;
|
|
|
|
|
|
@@ -62,6 +66,18 @@ public class LiveDataServiceImpl implements ILiveDataService {
|
|
|
private ILiveUserFavoriteService liveUserFavoriteService;
|
|
|
@Autowired
|
|
|
private LiveDataMapper baseMapper;
|
|
|
+ @Autowired
|
|
|
+ private FsUserMapper fsUserMapper;
|
|
|
+ @Autowired
|
|
|
+ private LiveWatchUserMapper liveWatchUserMapper;
|
|
|
+ @Autowired
|
|
|
+ private CompanyMapper companyMapper;
|
|
|
+ @Autowired
|
|
|
+ private CompanyUserMapper companyUserMapper;
|
|
|
+ @Autowired
|
|
|
+ private LiveOrderMapper liveOrderMapper;
|
|
|
+ @Autowired
|
|
|
+ private LiveMapper liveMapper;
|
|
|
/**
|
|
|
* 查询直播数据
|
|
|
*
|
|
|
@@ -554,4 +570,466 @@ public class LiveDataServiceImpl implements ILiveDataService {
|
|
|
return column;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public R getLiveDataDetailBySql(Long liveId) {
|
|
|
+ LiveDataDetailVo detailVo = liveDataMapper.selectLiveDataDetailBySql(liveId);
|
|
|
+ if (detailVo == null) {
|
|
|
+ detailVo = new LiveDataDetailVo();
|
|
|
+ }
|
|
|
+ // 查询单品销量统计
|
|
|
+ List<ProductSalesVo> productSalesList = getProductSalesList(liveId);
|
|
|
+ detailVo.setProductSalesList(productSalesList);
|
|
|
+ return R.ok().put("data", detailVo);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R getLiveUserDetailListBySql(Long liveId) {
|
|
|
+ List<LiveUserDetailVo> userDetailList = liveDataMapper.selectLiveUserDetailListBySql(liveId);
|
|
|
+ return R.ok().put("data", userDetailList);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R getLiveDataDetailByServer(Long liveId) {
|
|
|
+ // 查询数据服务器处理方式:通过查询各个表的数据,在内存中计算统计
|
|
|
+ LiveDataDetailVo detailVo = calculateLiveDataDetailByServer(liveId);
|
|
|
+ // 查询单品销量统计
|
|
|
+ List<ProductSalesVo> productSalesList = getProductSalesList(liveId);
|
|
|
+ detailVo.setProductSalesList(productSalesList);
|
|
|
+ return R.ok().put("data", detailVo);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R getLiveUserDetailListByServer(Long liveId) {
|
|
|
+ // 查询数据服务器处理方式:通过查询各个表的数据,在内存中计算
|
|
|
+ List<LiveUserDetailVo> userDetailList = calculateLiveUserDetailListByServer(liveId);
|
|
|
+ return R.ok().put("data", userDetailList);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过查询数据服务器处理方式计算直播间详情数据
|
|
|
+ */
|
|
|
+ private LiveDataDetailVo calculateLiveDataDetailByServer(Long liveId) {
|
|
|
+ LiveDataDetailVo detailVo = new LiveDataDetailVo();
|
|
|
+
|
|
|
+ // 查询视频时长
|
|
|
+ LiveVideoMapper liveVideoMapper = SpringUtils.getBean(LiveVideoMapper.class);
|
|
|
+ List<LiveVideo> videos = liveVideoMapper.selectByLiveId(liveId);
|
|
|
+ Long videoDuration = videos.stream()
|
|
|
+ .filter(v -> v.getVideoType() != null && (v.getVideoType() == 1 || v.getVideoType() == 2))
|
|
|
+ .mapToLong(v -> v.getDuration() != null ? v.getDuration() : 0L)
|
|
|
+ .sum();
|
|
|
+ detailVo.setVideoDuration(videoDuration);
|
|
|
+
|
|
|
+ // 查询观看用户数据
|
|
|
+ LiveWatchUser queryParam = new LiveWatchUser();
|
|
|
+ queryParam.setLiveId(liveId);
|
|
|
+ List<LiveWatchUser> watchUsers = liveWatchUserMapper.selectLiveWatchUserList(queryParam);
|
|
|
+
|
|
|
+ // 累计观看人数
|
|
|
+ long totalViewers = watchUsers.stream().map(LiveWatchUser::getUserId).distinct().count();
|
|
|
+ detailVo.setTotalViewers(totalViewers);
|
|
|
+
|
|
|
+ // 直播观看人数
|
|
|
+ long liveViewers = watchUsers.stream()
|
|
|
+ .filter(w -> w.getLiveFlag() != null && w.getLiveFlag() == 1 && (w.getReplayFlag() == null || w.getReplayFlag() == 0))
|
|
|
+ .map(LiveWatchUser::getUserId)
|
|
|
+ .distinct()
|
|
|
+ .count();
|
|
|
+ detailVo.setLiveViewers(liveViewers);
|
|
|
+
|
|
|
+ // 回放观看人数
|
|
|
+ long playbackViewers = watchUsers.stream()
|
|
|
+ .filter(w -> w.getReplayFlag() != null && w.getReplayFlag() == 1 && (w.getLiveFlag() == null || w.getLiveFlag() == 0))
|
|
|
+ .map(LiveWatchUser::getUserId)
|
|
|
+ .distinct()
|
|
|
+ .count();
|
|
|
+ detailVo.setPlaybackViewers(playbackViewers);
|
|
|
+
|
|
|
+ // 累计完课人数(观看时长 >= 视频时长)
|
|
|
+ long totalCompletedCourses = watchUsers.stream()
|
|
|
+ .filter(w -> w.getOnlineSeconds() != null && videoDuration > 0 && w.getOnlineSeconds() >= videoDuration)
|
|
|
+ .map(LiveWatchUser::getUserId)
|
|
|
+ .distinct()
|
|
|
+ .count();
|
|
|
+ detailVo.setTotalCompletedCourses(totalCompletedCourses);
|
|
|
+
|
|
|
+ // 计算到课完课率
|
|
|
+ if (totalViewers > 0) {
|
|
|
+ detailVo.setTotalCompletionRate(BigDecimal.valueOf(totalCompletedCourses * 100.0 / totalViewers).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 直播相关统计
|
|
|
+ List<LiveWatchUser> liveWatchUsers = watchUsers.stream()
|
|
|
+ .filter(w -> w.getLiveFlag() != null && w.getLiveFlag() == 1 && (w.getReplayFlag() == null || w.getReplayFlag() == 0))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ long liveOver20Minutes = liveWatchUsers.stream()
|
|
|
+ .filter(w -> w.getOnlineSeconds() != null && w.getOnlineSeconds() >= 1200)
|
|
|
+ .map(LiveWatchUser::getUserId)
|
|
|
+ .distinct()
|
|
|
+ .count();
|
|
|
+ detailVo.setLiveOver20Minutes(liveOver20Minutes);
|
|
|
+
|
|
|
+ long liveOver30Minutes = liveWatchUsers.stream()
|
|
|
+ .filter(w -> w.getOnlineSeconds() != null && w.getOnlineSeconds() >= 1800)
|
|
|
+ .map(LiveWatchUser::getUserId)
|
|
|
+ .distinct()
|
|
|
+ .count();
|
|
|
+ detailVo.setLiveOver30Minutes(liveOver30Minutes);
|
|
|
+
|
|
|
+ if (liveViewers > 0) {
|
|
|
+ detailVo.setLiveCompletionRate20(BigDecimal.valueOf(liveOver20Minutes * 100.0 / liveViewers).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ detailVo.setLiveCompletionRate30(BigDecimal.valueOf(liveOver30Minutes * 100.0 / liveViewers).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 直播平均时长
|
|
|
+ double liveAvgDuration = liveWatchUsers.stream()
|
|
|
+ .filter(w -> w.getOnlineSeconds() != null)
|
|
|
+ .mapToLong(LiveWatchUser::getOnlineSeconds)
|
|
|
+ .average()
|
|
|
+ .orElse(0.0);
|
|
|
+ detailVo.setLiveAvgDuration((long) liveAvgDuration);
|
|
|
+
|
|
|
+ // 回放相关统计
|
|
|
+ List<LiveWatchUser> playbackWatchUsers = watchUsers.stream()
|
|
|
+ .filter(w -> w.getReplayFlag() != null && w.getReplayFlag() == 1 && (w.getLiveFlag() == null || w.getLiveFlag() == 0))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ long playbackOver20Minutes = playbackWatchUsers.stream()
|
|
|
+ .filter(w -> w.getOnlineSeconds() != null && w.getOnlineSeconds() >= 1200)
|
|
|
+ .map(LiveWatchUser::getUserId)
|
|
|
+ .distinct()
|
|
|
+ .count();
|
|
|
+ detailVo.setPlaybackOver20Minutes(playbackOver20Minutes);
|
|
|
+
|
|
|
+ long playbackOver30Minutes = playbackWatchUsers.stream()
|
|
|
+ .filter(w -> w.getOnlineSeconds() != null && w.getOnlineSeconds() >= 1800)
|
|
|
+ .map(LiveWatchUser::getUserId)
|
|
|
+ .distinct()
|
|
|
+ .count();
|
|
|
+ detailVo.setPlaybackOver30Minutes(playbackOver30Minutes);
|
|
|
+
|
|
|
+ if (playbackViewers > 0) {
|
|
|
+ detailVo.setPlaybackCompletionRate20(BigDecimal.valueOf(playbackOver20Minutes * 100.0 / playbackViewers).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ detailVo.setPlaybackCompletionRate30(BigDecimal.valueOf(playbackOver30Minutes * 100.0 / playbackViewers).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 回放平均时长
|
|
|
+ double playbackAvgDuration = playbackWatchUsers.stream()
|
|
|
+ .filter(w -> w.getOnlineSeconds() != null)
|
|
|
+ .mapToLong(LiveWatchUser::getOnlineSeconds)
|
|
|
+ .average()
|
|
|
+ .orElse(0.0);
|
|
|
+ detailVo.setPlaybackAvgDuration((long) playbackAvgDuration);
|
|
|
+
|
|
|
+ // 回放完播率
|
|
|
+ if (videoDuration > 0) {
|
|
|
+ detailVo.setPlaybackFinishRate(BigDecimal.valueOf(playbackAvgDuration * 100.0 / videoDuration).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询直播峰值
|
|
|
+ LiveData liveData = liveDataMapper.selectLiveDataByLiveId(liveId);
|
|
|
+ if (liveData != null && liveData.getPeakConcurrentViewers() != null) {
|
|
|
+ detailVo.setLivePeak(liveData.getPeakConcurrentViewers());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询订单数据
|
|
|
+ LiveOrderMapper liveOrderMapper = SpringUtils.getBean(LiveOrderMapper.class);
|
|
|
+ LiveOrder orderQuery = new LiveOrder();
|
|
|
+ orderQuery.setLiveId(liveId);
|
|
|
+ List<LiveOrder> orders = liveOrderMapper.selectLiveOrderList(orderQuery);
|
|
|
+
|
|
|
+ BigDecimal gmv = orders.stream()
|
|
|
+ .filter(o -> "1".equals(o.getIsPay()))
|
|
|
+ .map(LiveOrder::getPayPrice)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ detailVo.setGmv(gmv);
|
|
|
+
|
|
|
+ long paidUsers = orders.stream()
|
|
|
+ .filter(o -> "1".equals(o.getIsPay()))
|
|
|
+ .filter(o -> o.getUserId() != null && !o.getUserId().isEmpty())
|
|
|
+ .map(o -> {
|
|
|
+ try {
|
|
|
+ return Long.parseLong(o.getUserId());
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .distinct()
|
|
|
+ .count();
|
|
|
+ detailVo.setPaidUsers(paidUsers);
|
|
|
+
|
|
|
+ long paidOrders = orders.stream()
|
|
|
+ .filter(o -> "1".equals(o.getIsPay()))
|
|
|
+ .count();
|
|
|
+ detailVo.setPaidOrders(paidOrders);
|
|
|
+
|
|
|
+ // 计算转化率
|
|
|
+ if (detailVo.getLivePeak() > 0) {
|
|
|
+ detailVo.setPeakConversionRate(BigDecimal.valueOf(paidUsers * 100.0 / detailVo.getLivePeak()).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ detailVo.setPeakRValue(gmv.divide(BigDecimal.valueOf(detailVo.getLivePeak()), 2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (totalViewers > 0) {
|
|
|
+ detailVo.setTotalViewerConversionRate(BigDecimal.valueOf(paidUsers * 100.0 / totalViewers).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (liveOver30Minutes > 0) {
|
|
|
+ detailVo.setCompletion30MinConversionRate(BigDecimal.valueOf(paidUsers * 100.0 / liveOver30Minutes).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (totalCompletedCourses > 0) {
|
|
|
+ detailVo.setCompletionRValue(gmv.divide(BigDecimal.valueOf(totalCompletedCourses), 2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+
|
|
|
+ return detailVo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过查询数据服务器处理方式计算用户详情列表
|
|
|
+ */
|
|
|
+ private List<LiveUserDetailVo> calculateLiveUserDetailListByServer(Long liveId) {
|
|
|
+ // 查询观看用户
|
|
|
+ List<LiveWatchUser> watchUsers = liveWatchUserMapper.selectLiveWatchUserListByLiveId(liveId);
|
|
|
+
|
|
|
+ LiveOrder orderQuery = new LiveOrder();
|
|
|
+ orderQuery.setLiveId(liveId);
|
|
|
+ List<LiveOrder> orders = liveOrderMapper.selectLiveOrderList(orderQuery);
|
|
|
+
|
|
|
+
|
|
|
+ // 按用户ID分组统计
|
|
|
+ Map<Long, LiveUserDetailVo> userDetailMap = new HashMap<>();
|
|
|
+
|
|
|
+ for (LiveWatchUser watchUser : watchUsers) {
|
|
|
+ Long userId = watchUser.getUserId();
|
|
|
+ if (userId == null) continue;
|
|
|
+
|
|
|
+ LiveUserDetailVo userDetail = userDetailMap.computeIfAbsent(userId, k -> {
|
|
|
+ LiveUserDetailVo vo = new LiveUserDetailVo();
|
|
|
+ vo.setUserId(userId);
|
|
|
+ // 查询用户信息
|
|
|
+ FsUser user = fsUserMapper.selectFsUserByUserId(userId);
|
|
|
+ if (user != null) {
|
|
|
+ vo.setUserName(user.getNickname() != null ? user.getNickname() : (user.getNickname() != null ? user.getNickname() : "未知用户"));
|
|
|
+ } else {
|
|
|
+ vo.setUserName("未知用户");
|
|
|
+ }
|
|
|
+ return vo;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 累加观看时长
|
|
|
+ if (watchUser.getOnlineSeconds() != null) {
|
|
|
+ if (watchUser.getLiveFlag() != null && watchUser.getLiveFlag() == 1 && (watchUser.getReplayFlag() == null || watchUser.getReplayFlag() == 0)) {
|
|
|
+ userDetail.setLiveWatchDuration(userDetail.getLiveWatchDuration() + watchUser.getOnlineSeconds());
|
|
|
+ } else if (watchUser.getReplayFlag() != null && watchUser.getReplayFlag() == 1 && (watchUser.getLiveFlag() == null || watchUser.getLiveFlag() == 0)) {
|
|
|
+ userDetail.setPlaybackWatchDuration(userDetail.getPlaybackWatchDuration() + watchUser.getOnlineSeconds());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统计订单数据
|
|
|
+ Map<Long, List<LiveOrder>> userOrdersMap = orders.stream()
|
|
|
+ .filter(o -> o.getUserId() != null && !o.getUserId().isEmpty())
|
|
|
+ .filter(o -> {
|
|
|
+ try {
|
|
|
+ Long.parseLong(o.getUserId());
|
|
|
+ return true;
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .collect(Collectors.groupingBy(o -> {
|
|
|
+ try {
|
|
|
+ return Long.parseLong(o.getUserId());
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+ }));
|
|
|
+
|
|
|
+ for (Map.Entry<Long, List<LiveOrder>> entry : userOrdersMap.entrySet()) {
|
|
|
+ Long userId = entry.getKey();
|
|
|
+ List<LiveOrder> userOrders = entry.getValue();
|
|
|
+
|
|
|
+ LiveUserDetailVo userDetail = userDetailMap.computeIfAbsent(userId, k -> {
|
|
|
+ LiveUserDetailVo vo = new LiveUserDetailVo();
|
|
|
+ vo.setUserId(userId);
|
|
|
+ FsUser user = fsUserMapper.selectFsUserByUserId(userId);
|
|
|
+ if (user != null) {
|
|
|
+ vo.setUserName(user.getNickname() != null ? user.getNickname() : (user.getNickname() != null ? user.getNickname() : "未知用户"));
|
|
|
+ } else {
|
|
|
+ vo.setUserName("未知用户");
|
|
|
+ }
|
|
|
+ return vo;
|
|
|
+ });
|
|
|
+
|
|
|
+ userDetail.setOrderCount((long) userOrders.size());
|
|
|
+ BigDecimal orderAmount = userOrders.stream()
|
|
|
+ .filter(o -> "1".equals(o.getIsPay()))
|
|
|
+ .map(LiveOrder::getPayPrice)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ userDetail.setOrderAmount(orderAmount);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换为列表并排序
|
|
|
+ List<LiveUserDetailVo> result = new ArrayList<>(userDetailMap.values());
|
|
|
+ result.sort((a, b) -> {
|
|
|
+ int compare = b.getOrderAmount().compareTo(a.getOrderAmount());
|
|
|
+ if (compare != 0) return compare;
|
|
|
+ return Long.compare(b.getLiveWatchDuration(), a.getLiveWatchDuration());
|
|
|
+ });
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询单品销量统计
|
|
|
+ */
|
|
|
+ private List<ProductSalesVo> getProductSalesList(Long liveId) {
|
|
|
+
|
|
|
+ List<LiveOrder> orders = liveOrderMapper.selectOrderByLiveId(liveId);
|
|
|
+
|
|
|
+ // 按商品ID分组统计
|
|
|
+ Map<Long, ProductSalesVo> productSalesMap = new HashMap<>();
|
|
|
+
|
|
|
+ for (LiveOrder order : orders) {
|
|
|
+ if (!"1".equals(order.getIsPay()) || order.getProductId() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ ProductSalesVo productSales = productSalesMap.computeIfAbsent(order.getProductId(), k -> {
|
|
|
+ ProductSalesVo vo = new ProductSalesVo();
|
|
|
+ vo.setProductId(order.getProductId());
|
|
|
+ // 查询商品名称
|
|
|
+ FsStoreProductMapper productMapper = SpringUtils.getBean(FsStoreProductMapper.class);
|
|
|
+ FsStoreProduct product = productMapper.selectFsStoreProductById(order.getProductId());
|
|
|
+ if (product != null) {
|
|
|
+ vo.setProductName(product.getProductName());
|
|
|
+ } else {
|
|
|
+ vo.setProductName("未知商品");
|
|
|
+ }
|
|
|
+ return vo;
|
|
|
+ });
|
|
|
+
|
|
|
+ productSales.setSalesCount(productSales.getSalesCount() + 1);
|
|
|
+ if (order.getPayPrice() != null) {
|
|
|
+ productSales.setSalesAmount(productSales.getSalesAmount().add(order.getPayPrice()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<ProductSalesVo> result = new ArrayList<>(productSalesMap.values());
|
|
|
+ result.sort((a, b) -> b.getSalesAmount().compareTo(a.getSalesAmount()));
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 导出直播间用户详情数据
|
|
|
+ * @param liveId 直播间ID
|
|
|
+ * @return 导出VO列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<LiveUserDetailExportVO> exportLiveUserDetail(Long liveId) {
|
|
|
+ // 查询用户详情列表
|
|
|
+ List<LiveUserDetailVo> userDetailList = liveDataMapper.selectLiveUserDetailListBySql(liveId);
|
|
|
+ if (userDetailList == null || userDetailList.isEmpty()) {
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换为导出VO列表
|
|
|
+ List<LiveUserDetailExportVO> exportList = new ArrayList<>();
|
|
|
+ for (LiveUserDetailVo userDetail : userDetailList) {
|
|
|
+ LiveUserDetailExportVO exportVO = new LiveUserDetailExportVO();
|
|
|
+
|
|
|
+ // 用户基本信息
|
|
|
+ exportVO.setUserId(userDetail.getUserId());
|
|
|
+ exportVO.setUserName(userDetail.getUserName());
|
|
|
+
|
|
|
+ // 观看时长(秒转分钟)
|
|
|
+ exportVO.setLiveWatchDuration(formatSecondsToMinutes(userDetail.getLiveWatchDuration()));
|
|
|
+ exportVO.setPlaybackWatchDuration(formatSecondsToMinutes(userDetail.getPlaybackWatchDuration()));
|
|
|
+
|
|
|
+ // 计算总观看时长
|
|
|
+ Long totalSeconds = (userDetail.getLiveWatchDuration() != null ? userDetail.getLiveWatchDuration() : 0L) +
|
|
|
+ (userDetail.getPlaybackWatchDuration() != null ? userDetail.getPlaybackWatchDuration() : 0L);
|
|
|
+// exportVO.setTotalWatchDuration(formatSecondsToMinutes(totalSeconds));
|
|
|
+
|
|
|
+ // 订单信息
|
|
|
+ exportVO.setOrderCount(Math.toIntExact(userDetail.getOrderCount()));
|
|
|
+ exportVO.setOrderAmount(formatMoney(userDetail.getOrderAmount()));
|
|
|
+
|
|
|
+ // 公司和销售信息
|
|
|
+ exportVO.setCompanyName(userDetail.getCompanyName());
|
|
|
+ exportVO.setSalesName(userDetail.getSalesName());
|
|
|
+
|
|
|
+ // 是否完课(根据观看时长判断,假设30分钟以上为完课)
|
|
|
+ if (totalSeconds >= 1800) {
|
|
|
+ exportVO.setIsCompleted("是");
|
|
|
+ } else {
|
|
|
+ exportVO.setIsCompleted("否");
|
|
|
+ }
|
|
|
+
|
|
|
+ exportList.add(exportVO);
|
|
|
+ }
|
|
|
+
|
|
|
+ return exportList;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R listLiveData(LiveDataParam param) {
|
|
|
+ // 第一步:查询当前公司的直播间数据
|
|
|
+ // 直播类型 只展示已结束和直播回放的数据 录播展示直播中和已结束的直播数据
|
|
|
+ List<Live> lives = liveMapper.listLiveData(param);
|
|
|
+ int total = liveMapper.listLiveDataCount(param);
|
|
|
+
|
|
|
+ if (lives == null || lives.isEmpty()) {
|
|
|
+ LiveDataStatisticsVo statistics = new LiveDataStatisticsVo();
|
|
|
+ return R.ok().put("list", Collections.emptyList()).put("data", statistics);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取直播间ID列表
|
|
|
+ List<Long> liveIds = lives.stream()
|
|
|
+ .map(Live::getLiveId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 查询统计数据(根据live_watch_user表查询用户的在线时长,计算平均时长
|
|
|
+ // 根据live_video的文件时长,判断用户的完课情况
|
|
|
+ // 根据live_order查询直播间的销量额和订单数)
|
|
|
+ LiveDataStatisticsVo statistics = baseMapper.selectLiveDataStatistics(liveIds);
|
|
|
+ if (statistics == null) {
|
|
|
+ statistics = new LiveDataStatisticsVo();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询列表数据(每个直播间的详细统计数据)
|
|
|
+ List<LiveDataListVo> liveDataList = baseMapper.selectLiveDataListByLiveIds(liveIds);
|
|
|
+ if (liveDataList == null) {
|
|
|
+ liveDataList = Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ return R.ok().put("list", liveDataList).put("data", statistics).put("total", total);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化秒数为分钟(保留2位小数)
|
|
|
+ */
|
|
|
+ private String formatSecondsToMinutes(Long seconds) {
|
|
|
+ if (seconds == null || seconds == 0) {
|
|
|
+ return "0.00";
|
|
|
+ }
|
|
|
+ BigDecimal minutes = new BigDecimal(seconds).divide(new BigDecimal(60), 2, RoundingMode.HALF_UP);
|
|
|
+ return minutes.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化金额
|
|
|
+ */
|
|
|
+ private String formatMoney(BigDecimal value) {
|
|
|
+ if (value == null) {
|
|
|
+ return "0.00";
|
|
|
+ }
|
|
|
+ return value.setScale(2, RoundingMode.HALF_UP).toString();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
}
|