1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
| /**
* 基於 Snowflake ID 的用戶行為追踪系統
*/
@Service
@Slf4j
public class UserTrackingService {
private final IdGenerator idGenerator;
private final UserActionRepository actionRepository;
private final RedisTemplate<String, Object> redisTemplate;
public UserTrackingService(
IdGenerator idGenerator,
UserActionRepository actionRepository,
RedisTemplate<String, Object> redisTemplate) {
this.idGenerator = idGenerator;
this.actionRepository = actionRepository;
this.redisTemplate = redisTemplate;
}
/**
* 記錄用戶行為
*/
@Async
public CompletableFuture<Void> trackUserAction(UserActionRequest request) {
long actionId = idGenerator.generateId();
UserAction action = UserAction.builder()
.id(actionId)
.userId(request.getUserId())
.actionType(request.getActionType())
.resource(request.getResource())
.timestamp(Instant.now())
.metadata(request.getMetadata())
.sessionId(request.getSessionId())
.userAgent(request.getUserAgent())
.ipAddress(request.getIpAddress())
.build();
// 異步保存到數據庫
actionRepository.save(action);
// 更新實時統計(Redis)
updateRealTimeStats(action);
log.debug("Tracked user action: id={}, userId={}, type={}",
actionId, request.getUserId(), request.getActionType());
return CompletableFuture.completedFuture(null);
}
/**
* 批量追踪(高吞吐量場景)
*/
@Async
public CompletableFuture<Void> trackUserActionsBatch(List<UserActionRequest> requests) {
List<UserAction> actions = requests.parallelStream()
.map(request -> {
long actionId = idGenerator.generateId();
return UserAction.builder()
.id(actionId)
.userId(request.getUserId())
.actionType(request.getActionType())
.resource(request.getResource())
.timestamp(Instant.now())
.metadata(request.getMetadata())
.build();
})
.collect(Collectors.toList());
// 批量保存
actionRepository.saveAll(actions);
// 批量更新統計
actions.forEach(this::updateRealTimeStats);
log.info("Tracked {} user actions in batch", actions.size());
return CompletableFuture.completedFuture(null);
}
/**
* 查詢用戶行為軌跡
*/
public List<UserAction> getUserActionTrail(Long userId, LocalDateTime start, LocalDateTime end) {
// 利用 Snowflake ID 的時間順序性進行高效查詢
long startId = convertTimestampToSnowflakeIdRange(start).getFirst();
long endId = convertTimestampToSnowflakeIdRange(end).getSecond();
return actionRepository.findByUserIdAndIdBetween(userId, startId, endId);
}
private void updateRealTimeStats(UserAction action) {
String key = "user_stats:" + action.getUserId() + ":" +
LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
redisTemplate.opsForHash().increment(key, "total_actions", 1);
redisTemplate.opsForHash().increment(key, action.getActionType().name(), 1);
redisTemplate.expire(key, Duration.ofDays(7));
}
private Pair<Long, Long> convertTimestampToSnowflakeIdRange(LocalDateTime dateTime) {
long timestamp = dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
long baseId = (timestamp - 1420041600000L) << 22;
return Pair.of(baseId, baseId | 0x3FFFFF);
}
}
@Entity
@Table(name = "user_actions", indexes = {
@Index(name = "idx_user_id_id", columnList = "userId,id"),
@Index(name = "idx_action_type", columnList = "actionType"),
@Index(name = "idx_timestamp", columnList = "timestamp")
})
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserAction {
@Id
private Long id; // Snowflake ID
@Column(nullable = false)
private Long userId;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private ActionType actionType;
@Column(nullable = false)
private String resource;
@Column(nullable = false)
private Instant timestamp;
@Column(columnDefinition = "TEXT")
private String metadata;
private String sessionId;
private String userAgent;
private String ipAddress;
}
public enum ActionType {
LOGIN, LOGOUT, VIEW_PAGE, CLICK_BUTTON, SUBMIT_FORM,
PURCHASE, SEARCH, DOWNLOAD, UPLOAD, SHARE
}
|
留言討論