|
|
@@ -0,0 +1,293 @@
|
|
|
+package com.fs.live.util;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.fs.common.utils.ServletUtils;
|
|
|
+import com.fs.common.utils.StringUtils;
|
|
|
+import com.fs.common.utils.ip.IpUtils;
|
|
|
+import com.fs.live.domain.LiveUserBehaviorTrack;
|
|
|
+import com.fs.live.enums.LiveBehaviorTypeEnum;
|
|
|
+import com.fs.live.enums.LiveBehaviorResourceTypeEnum;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 直播用户行为轨迹埋点工具类
|
|
|
+ * 提供统一的行为记录方法,支持数据量大的情况下根据userId尾数进行分表
|
|
|
+ *
|
|
|
+ * @author xw
|
|
|
+ * @date 2026-01-05
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+public class LiveBehaviorTrackUtil {
|
|
|
+
|
|
|
+ private static final int TABLE_COUNT = 10;
|
|
|
+
|
|
|
+ public static String getTableSuffix(Long userId) {
|
|
|
+ if (userId == null) {
|
|
|
+ return "0";
|
|
|
+ }
|
|
|
+ int suffix = (int) (userId % TABLE_COUNT);
|
|
|
+ return String.valueOf(suffix);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建行为轨迹记录对象
|
|
|
+ *
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param liveId 直播间ID
|
|
|
+ * @param behaviorType 行为类型
|
|
|
+ * @return LiveUserBehaviorTrack
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack buildBehaviorTrack(Long userId, Long liveId, LiveBehaviorTypeEnum behaviorType) {
|
|
|
+ return buildBehaviorTrack(userId, liveId, null, null, behaviorType, null, null, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建行为轨迹记录对象(带资源信息)
|
|
|
+ *
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param liveId 直播间ID
|
|
|
+ * @param behaviorType 行为类型
|
|
|
+ * @param resourceId 资源ID
|
|
|
+ * @param resourceType 资源类型
|
|
|
+ * @return LiveUserBehaviorTrack
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack buildBehaviorTrack(Long userId, Long liveId,
|
|
|
+ LiveBehaviorTypeEnum behaviorType,
|
|
|
+ Long resourceId,
|
|
|
+ LiveBehaviorResourceTypeEnum resourceType) {
|
|
|
+ return buildBehaviorTrack(userId, liveId, null, null, behaviorType, resourceId, resourceType, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建行为轨迹记录对象(完整版)
|
|
|
+ *
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param liveId 直播间ID
|
|
|
+ * @param companyId 企业ID
|
|
|
+ * @param companyUserId 企业用户ID(销售ID)
|
|
|
+ * @param behaviorType 行为类型
|
|
|
+ * @param resourceId 资源ID
|
|
|
+ * @param resourceType 资源类型
|
|
|
+ * @param extData 扩展数据
|
|
|
+ * @return LiveUserBehaviorTrack
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack buildBehaviorTrack(Long userId, Long liveId,
|
|
|
+ Long companyId, Long companyUserId,
|
|
|
+ LiveBehaviorTypeEnum behaviorType,
|
|
|
+ Long resourceId,
|
|
|
+ LiveBehaviorResourceTypeEnum resourceType,
|
|
|
+ Map<String, Object> extData) {
|
|
|
+ LiveUserBehaviorTrack track = new LiveUserBehaviorTrack();
|
|
|
+ track.setUserId(userId);
|
|
|
+ track.setLiveId(liveId);
|
|
|
+ track.setCompanyId(companyId);
|
|
|
+ track.setCompanyUserId(companyUserId);
|
|
|
+ track.setBehaviorType(behaviorType.getCode());
|
|
|
+ track.setBehaviorDesc(behaviorType.getDesc());
|
|
|
+ track.setResourceId(resourceId);
|
|
|
+ if (resourceType != null) {
|
|
|
+ track.setResourceType(resourceType.getCode());
|
|
|
+ }
|
|
|
+ if (extData != null && !extData.isEmpty()) {
|
|
|
+ track.setExtData(JSON.toJSONString(extData));
|
|
|
+ }
|
|
|
+ track.setBehaviorTime(System.currentTimeMillis());
|
|
|
+
|
|
|
+ // 设置分表后缀
|
|
|
+ track.setTableSuffix(getTableSuffix(userId));
|
|
|
+
|
|
|
+ // 自动获取请求信息
|
|
|
+ try {
|
|
|
+ HttpServletRequest request = ServletUtils.getRequest();
|
|
|
+ if (request != null) {
|
|
|
+ track.setIpAddress(IpUtils.getIpAddr(request));
|
|
|
+ track.setUserAgent(request.getHeader("User-Agent"));
|
|
|
+ // 可以从User-Agent解析设备信息
|
|
|
+ track.setDeviceInfo(parseDeviceInfo(request.getHeader("User-Agent")));
|
|
|
+ } else {
|
|
|
+ // WebSocket环境下无法获取HTTP请求上下文,这是正常现象
|
|
|
+ log.debug("当前非HTTP请求上下文(可能是WebSocket),无法获取请求信息");
|
|
|
+ track.setDeviceInfo("Unknown");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.debug("获取请求信息失败", e);
|
|
|
+ track.setDeviceInfo("Unknown");
|
|
|
+ }
|
|
|
+
|
|
|
+ return track;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从User-Agent解析简单的设备信息
|
|
|
+ */
|
|
|
+ private static String parseDeviceInfo(String userAgent) {
|
|
|
+ if (StringUtils.isEmpty(userAgent)) {
|
|
|
+ return "Unknown";
|
|
|
+ }
|
|
|
+ if (userAgent.contains("MicroMessenger")) {
|
|
|
+ return "WeChat";
|
|
|
+ } else if (userAgent.contains("Android")) {
|
|
|
+ return "Android";
|
|
|
+ } else if (userAgent.contains("iPhone") || userAgent.contains("iPad")) {
|
|
|
+ return "iOS";
|
|
|
+ } else if (userAgent.contains("Windows")) {
|
|
|
+ return "Windows";
|
|
|
+ } else if (userAgent.contains("Mac")) {
|
|
|
+ return "Mac";
|
|
|
+ }
|
|
|
+ return "Other";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 快速记录行为 - 简单版(仅行为类型)
|
|
|
+ *
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param liveId 直播间ID
|
|
|
+ * @param behaviorType 行为类型
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack quickTrack(Long userId, Long liveId, LiveBehaviorTypeEnum behaviorType) {
|
|
|
+ return buildBehaviorTrack(userId, liveId, behaviorType);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 快速记录行为 - 带资源信息
|
|
|
+ *
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param liveId 直播间ID
|
|
|
+ * @param behaviorType 行为类型
|
|
|
+ * @param resourceId 资源ID
|
|
|
+ * @param resourceType 资源类型
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack quickTrack(Long userId, Long liveId,
|
|
|
+ LiveBehaviorTypeEnum behaviorType,
|
|
|
+ Long resourceId,
|
|
|
+ LiveBehaviorResourceTypeEnum resourceType) {
|
|
|
+ return buildBehaviorTrack(userId, liveId, behaviorType, resourceId, resourceType);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录进入直播间
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackEnterLive(Long userId, Long liveId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.ENTER_LIVE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录退出直播间
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackLeaveLive(Long userId, Long liveId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.LEAVE_LIVE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录点赞
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackLike(Long userId, Long liveId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.LIKE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录发送消息
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackSendMessage(Long userId, Long liveId, Long messageId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.SEND_MESSAGE,
|
|
|
+ messageId, LiveBehaviorResourceTypeEnum.MESSAGE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录点击商品
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackClickGoods(Long userId, Long liveId, Long goodsId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.CLICK_GOODS,
|
|
|
+ goodsId, LiveBehaviorResourceTypeEnum.GOODS);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录加入购物车
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackAddCart(Long userId, Long liveId, Long goodsId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.ADD_CART,
|
|
|
+ goodsId, LiveBehaviorResourceTypeEnum.GOODS);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录购买商品
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackPurchase(Long userId, Long liveId, Long orderId, Map<String, Object> orderInfo) {
|
|
|
+ return buildBehaviorTrack(userId, liveId, null, null,
|
|
|
+ LiveBehaviorTypeEnum.PURCHASE,
|
|
|
+ orderId, LiveBehaviorResourceTypeEnum.ORDER, orderInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录收藏商品
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackCollectGoods(Long userId, Long liveId, Long goodsId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.COLLECT_GOODS,
|
|
|
+ goodsId, LiveBehaviorResourceTypeEnum.GOODS);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录关注主播
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackFollowAnchor(Long userId, Long liveId, Long anchorId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.FOLLOW_ANCHOR,
|
|
|
+ anchorId, LiveBehaviorResourceTypeEnum.ANCHOR);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录领取优惠券
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackReceiveCoupon(Long userId, Long liveId, Long couponId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.RECEIVE_COUPON,
|
|
|
+ couponId, LiveBehaviorResourceTypeEnum.COUPON);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录参与抽奖
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackJoinLottery(Long userId, Long liveId, Long lotteryId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.JOIN_LOTTERY,
|
|
|
+ lotteryId, LiveBehaviorResourceTypeEnum.LOTTERY);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录领取红包
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackReceiveRedPacket(Long userId, Long liveId, Long redPacketId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.RECEIVE_RED_PACKET,
|
|
|
+ redPacketId, LiveBehaviorResourceTypeEnum.RED_PACKET);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录分享直播间
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackShareLive(Long userId, Long liveId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.SHARE_LIVE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录观看时长
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackWatchDuration(Long userId, Long liveId, Long duration) {
|
|
|
+ Map<String, Object> extData = new HashMap<>();
|
|
|
+ extData.put("duration", duration);
|
|
|
+ return buildBehaviorTrack(userId, liveId, null, null,
|
|
|
+ LiveBehaviorTypeEnum.WATCH_DURATION,
|
|
|
+ null, null, extData);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录完课
|
|
|
+ */
|
|
|
+ public static LiveUserBehaviorTrack trackCompletion(Long userId, Long liveId) {
|
|
|
+ return quickTrack(userId, liveId, LiveBehaviorTypeEnum.COMPLETION);
|
|
|
+ }
|
|
|
+}
|