CrmQuoteShareLogServiceImpl.java 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package org.dromara.crm.service.impl;
  2. import cn.hutool.core.util.StrUtil;
  3. import cn.hutool.http.useragent.UserAgent;
  4. import cn.hutool.http.useragent.UserAgentUtil;
  5. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  6. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  7. import lombok.RequiredArgsConstructor;
  8. import lombok.extern.slf4j.Slf4j;
  9. import org.dromara.common.core.utils.MapstructUtils;
  10. import org.dromara.crm.domain.CrmQuoteShareLog;
  11. import org.dromara.crm.domain.vo.CrmQuoteShareLogVo;
  12. import org.dromara.crm.mapper.CrmQuoteShareLogMapper;
  13. import org.dromara.crm.service.ICrmQuoteShareLogService;
  14. import org.springframework.stereotype.Service;
  15. import java.time.LocalDateTime;
  16. import java.util.HashMap;
  17. import java.util.List;
  18. import java.util.Map;
  19. import java.util.stream.Collectors;
  20. /**
  21. * 报价单分享访问日志Service实现
  22. *
  23. * @author 系统管理员
  24. * @date 2025-10-05
  25. */
  26. @Slf4j
  27. @RequiredArgsConstructor
  28. @Service
  29. public class CrmQuoteShareLogServiceImpl implements ICrmQuoteShareLogService {
  30. private final CrmQuoteShareLogMapper baseMapper;
  31. @Override
  32. public Boolean recordVisit(Long quoteId, String shareCode, String ipAddress, String userAgent) {
  33. CrmQuoteShareLog log = new CrmQuoteShareLog();
  34. log.setQuoteId(quoteId);
  35. log.setShareCode(shareCode);
  36. log.setVisitTime(LocalDateTime.now());
  37. log.setIpAddress(ipAddress);
  38. log.setPageViews(1);
  39. log.setStayDuration(0);
  40. // 解析User-Agent
  41. if (StrUtil.isNotBlank(userAgent)) {
  42. try {
  43. UserAgent ua = UserAgentUtil.parse(userAgent);
  44. // 设置浏览器信息
  45. String browser = ua.getBrowser().getName() + " " + ua.getVersion();
  46. log.setBrowser(browser);
  47. // 判断设备类型
  48. if (ua.isMobile()) {
  49. log.setDeviceType("Mobile");
  50. } else {
  51. log.setDeviceType("PC");
  52. }
  53. } catch (Exception e) {
  54. CrmQuoteShareLogServiceImpl.log.warn("解析User-Agent失败: userAgent={}", userAgent, e);
  55. log.setBrowser(userAgent.length() > 100 ? userAgent.substring(0, 100) : userAgent);
  56. log.setDeviceType("Unknown");
  57. }
  58. } else {
  59. log.setDeviceType("Unknown");
  60. }
  61. // TODO: 根据IP地址解析地理位置(可使用ip2region库)
  62. // log.setLocation(getLocationByIp(ipAddress));
  63. CrmQuoteShareLogServiceImpl.log.info("记录报价单访问日志: quoteId={}, shareCode={}, ip={}, device={}",
  64. quoteId, shareCode, ipAddress, log.getDeviceType());
  65. return baseMapper.insert(log) > 0;
  66. }
  67. @Override
  68. public List<CrmQuoteShareLogVo> queryLogsByQuoteId(Long quoteId) {
  69. LambdaQueryWrapper<CrmQuoteShareLog> lqw = Wrappers.lambdaQuery();
  70. lqw.eq(CrmQuoteShareLog::getQuoteId, quoteId);
  71. lqw.orderByDesc(CrmQuoteShareLog::getVisitTime);
  72. List<CrmQuoteShareLog> list = baseMapper.selectList(lqw);
  73. return MapstructUtils.convert(list, CrmQuoteShareLogVo.class);
  74. }
  75. @Override
  76. public Map<String, Object> statisticsVisits(Long quoteId) {
  77. LambdaQueryWrapper<CrmQuoteShareLog> lqw = Wrappers.lambdaQuery();
  78. lqw.eq(CrmQuoteShareLog::getQuoteId, quoteId);
  79. List<CrmQuoteShareLog> logs = baseMapper.selectList(lqw);
  80. Map<String, Object> stats = new HashMap<>();
  81. // 总访问次数
  82. stats.put("totalVisits", logs.size());
  83. // 独立IP数
  84. long uniqueIps = logs.stream()
  85. .filter(log -> StrUtil.isNotBlank(log.getIpAddress()))
  86. .map(CrmQuoteShareLog::getIpAddress)
  87. .distinct()
  88. .count();
  89. stats.put("uniqueIps", uniqueIps);
  90. // 最近访问时间
  91. LocalDateTime lastVisit = logs.stream()
  92. .map(CrmQuoteShareLog::getVisitTime)
  93. .max(LocalDateTime::compareTo)
  94. .orElse(null);
  95. stats.put("lastVisitTime", lastVisit);
  96. // 首次访问时间
  97. LocalDateTime firstVisit = logs.stream()
  98. .map(CrmQuoteShareLog::getVisitTime)
  99. .min(LocalDateTime::compareTo)
  100. .orElse(null);
  101. stats.put("firstVisitTime", firstVisit);
  102. // 设备类型分布
  103. Map<String, Long> deviceStats = logs.stream()
  104. .collect(Collectors.groupingBy(
  105. log -> StrUtil.isNotBlank(log.getDeviceType()) ? log.getDeviceType() : "Unknown",
  106. Collectors.counting()));
  107. stats.put("deviceStats", deviceStats);
  108. // 总停留时长
  109. int totalStayDuration = logs.stream()
  110. .mapToInt(log -> log.getStayDuration() != null ? log.getStayDuration() : 0)
  111. .sum();
  112. stats.put("totalStayDuration", totalStayDuration);
  113. // 平均停留时长
  114. double avgStayDuration = logs.isEmpty() ? 0 : (double) totalStayDuration / logs.size();
  115. stats.put("avgStayDuration", avgStayDuration);
  116. log.info("统计报价单访问数据: quoteId={}, totalVisits={}, uniqueIps={}",
  117. quoteId, stats.get("totalVisits"), stats.get("uniqueIps"));
  118. return stats;
  119. }
  120. // TODO: 实现IP地址解析地理位置功能
  121. // private String getLocationByIp(String ipAddress) {
  122. // // 使用ip2region等库解析IP地址
  123. // return null;
  124. // }
  125. }