151 lines
5.4 KiB
Java
151 lines
5.4 KiB
Java
package org.dromara.crm.service.impl;
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
import cn.hutool.http.useragent.UserAgent;
|
|
import cn.hutool.http.useragent.UserAgentUtil;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.dromara.common.core.utils.MapstructUtils;
|
|
import org.dromara.crm.domain.CrmQuoteShareLog;
|
|
import org.dromara.crm.domain.vo.CrmQuoteShareLogVo;
|
|
import org.dromara.crm.mapper.CrmQuoteShareLogMapper;
|
|
import org.dromara.crm.service.ICrmQuoteShareLogService;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import java.time.LocalDateTime;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* 报价单分享访问日志Service实现
|
|
*
|
|
* @author 系统管理员
|
|
* @date 2025-10-05
|
|
*/
|
|
@Slf4j
|
|
@RequiredArgsConstructor
|
|
@Service
|
|
public class CrmQuoteShareLogServiceImpl implements ICrmQuoteShareLogService {
|
|
|
|
private final CrmQuoteShareLogMapper baseMapper;
|
|
|
|
@Override
|
|
public Boolean recordVisit(Long quoteId, String shareCode, String ipAddress, String userAgent) {
|
|
CrmQuoteShareLog log = new CrmQuoteShareLog();
|
|
log.setQuoteId(quoteId);
|
|
log.setShareCode(shareCode);
|
|
log.setVisitTime(LocalDateTime.now());
|
|
log.setIpAddress(ipAddress);
|
|
log.setPageViews(1);
|
|
log.setStayDuration(0);
|
|
|
|
// 解析User-Agent
|
|
if (StrUtil.isNotBlank(userAgent)) {
|
|
try {
|
|
UserAgent ua = UserAgentUtil.parse(userAgent);
|
|
|
|
// 设置浏览器信息
|
|
String browser = ua.getBrowser().getName() + " " + ua.getVersion();
|
|
log.setBrowser(browser);
|
|
|
|
// 判断设备类型
|
|
if (ua.isMobile()) {
|
|
log.setDeviceType("Mobile");
|
|
} else {
|
|
log.setDeviceType("PC");
|
|
}
|
|
} catch (Exception e) {
|
|
CrmQuoteShareLogServiceImpl.log.warn("解析User-Agent失败: userAgent={}", userAgent, e);
|
|
log.setBrowser(userAgent.length() > 100 ? userAgent.substring(0, 100) : userAgent);
|
|
log.setDeviceType("Unknown");
|
|
}
|
|
} else {
|
|
log.setDeviceType("Unknown");
|
|
}
|
|
|
|
// TODO: 根据IP地址解析地理位置(可使用ip2region库)
|
|
// log.setLocation(getLocationByIp(ipAddress));
|
|
|
|
CrmQuoteShareLogServiceImpl.log.info("记录报价单访问日志: quoteId={}, shareCode={}, ip={}, device={}",
|
|
quoteId, shareCode, ipAddress, log.getDeviceType());
|
|
|
|
return baseMapper.insert(log) > 0;
|
|
}
|
|
|
|
@Override
|
|
public List<CrmQuoteShareLogVo> queryLogsByQuoteId(Long quoteId) {
|
|
LambdaQueryWrapper<CrmQuoteShareLog> lqw = Wrappers.lambdaQuery();
|
|
lqw.eq(CrmQuoteShareLog::getQuoteId, quoteId);
|
|
lqw.orderByDesc(CrmQuoteShareLog::getVisitTime);
|
|
List<CrmQuoteShareLog> list = baseMapper.selectList(lqw);
|
|
return MapstructUtils.convert(list, CrmQuoteShareLogVo.class);
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Object> statisticsVisits(Long quoteId) {
|
|
LambdaQueryWrapper<CrmQuoteShareLog> lqw = Wrappers.lambdaQuery();
|
|
lqw.eq(CrmQuoteShareLog::getQuoteId, quoteId);
|
|
List<CrmQuoteShareLog> logs = baseMapper.selectList(lqw);
|
|
|
|
Map<String, Object> stats = new HashMap<>();
|
|
|
|
// 总访问次数
|
|
stats.put("totalVisits", logs.size());
|
|
|
|
// 独立IP数
|
|
long uniqueIps = logs.stream()
|
|
.filter(log -> StrUtil.isNotBlank(log.getIpAddress()))
|
|
.map(CrmQuoteShareLog::getIpAddress)
|
|
.distinct()
|
|
.count();
|
|
stats.put("uniqueIps", uniqueIps);
|
|
|
|
// 最近访问时间
|
|
LocalDateTime lastVisit = logs.stream()
|
|
.map(CrmQuoteShareLog::getVisitTime)
|
|
.max(LocalDateTime::compareTo)
|
|
.orElse(null);
|
|
stats.put("lastVisitTime", lastVisit);
|
|
|
|
// 首次访问时间
|
|
LocalDateTime firstVisit = logs.stream()
|
|
.map(CrmQuoteShareLog::getVisitTime)
|
|
.min(LocalDateTime::compareTo)
|
|
.orElse(null);
|
|
stats.put("firstVisitTime", firstVisit);
|
|
|
|
// 设备类型分布
|
|
Map<String, Long> deviceStats = logs.stream()
|
|
.collect(Collectors.groupingBy(
|
|
log -> StrUtil.isNotBlank(log.getDeviceType()) ? log.getDeviceType() : "Unknown",
|
|
Collectors.counting()));
|
|
stats.put("deviceStats", deviceStats);
|
|
|
|
// 总停留时长
|
|
int totalStayDuration = logs.stream()
|
|
.mapToInt(log -> log.getStayDuration() != null ? log.getStayDuration() : 0)
|
|
.sum();
|
|
stats.put("totalStayDuration", totalStayDuration);
|
|
|
|
// 平均停留时长
|
|
double avgStayDuration = logs.isEmpty() ? 0 : (double) totalStayDuration / logs.size();
|
|
stats.put("avgStayDuration", avgStayDuration);
|
|
|
|
log.info("统计报价单访问数据: quoteId={}, totalVisits={}, uniqueIps={}",
|
|
quoteId, stats.get("totalVisits"), stats.get("uniqueIps"));
|
|
|
|
return stats;
|
|
}
|
|
|
|
// TODO: 实现IP地址解析地理位置功能
|
|
// private String getLocationByIp(String ipAddress) {
|
|
// // 使用ip2region等库解析IP地址
|
|
// return null;
|
|
// }
|
|
|
|
}
|