|
|
@@ -119,4 +119,93 @@ public class AnalyticsService {
|
|
|
if (date == null || date.isBlank()) return null;
|
|
|
return LocalDate.parse(date).atStartOfDay(ZoneOffset.UTC).toInstant();
|
|
|
}
|
|
|
+
|
|
|
+ // ==================== 统计概览 ====================
|
|
|
+
|
|
|
+ /** 概览数字:支持三种场景——累计、今天、最近7天 */
|
|
|
+ public Map<String, Object> queryOverview(String range) {
|
|
|
+ String today = LocalDate.now().toString();
|
|
|
+ Instant todayStart = parseDate(today);
|
|
|
+ Instant todayEnd = LocalDate.parse(today).plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant();
|
|
|
+
|
|
|
+ Instant startTime = null;
|
|
|
+ Instant endTime = null;
|
|
|
+ String label;
|
|
|
+
|
|
|
+ if ("today".equals(range)) {
|
|
|
+ startTime = todayStart;
|
|
|
+ endTime = todayEnd;
|
|
|
+ label = "today";
|
|
|
+ } else if ("week".equals(range)) {
|
|
|
+ startTime = todayStart.minus(6, java.time.temporal.ChronoUnit.DAYS);
|
|
|
+ endTime = todayEnd;
|
|
|
+ label = "week";
|
|
|
+ } else {
|
|
|
+ label = "total";
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> overview = new LinkedHashMap<>();
|
|
|
+ overview.put("range", label);
|
|
|
+ overview.put("total_events", repo.countTotal(startTime, endTime));
|
|
|
+ overview.put("active_users", repo.countDistinctUsers(startTime, endTime));
|
|
|
+ overview.put("page_views", repo.countByType("page_view", startTime, endTime));
|
|
|
+ overview.put("searches", repo.countByType("search", startTime, endTime));
|
|
|
+ overview.put("ai_qa", repo.countByType("chat", startTime, endTime));
|
|
|
+ return overview;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 统计维度 ====================
|
|
|
+
|
|
|
+ /** 五个维度统计,支持按维度切换,概览支持 range=today/week/total;
|
|
|
+ * 如果日期都不传,调用方默认填了当天 */
|
|
|
+ public Map<String, Object> queryStats(String startDate, String endDate, String dimension,
|
|
|
+ String range) {
|
|
|
+ Instant startTime = parseDate(startDate);
|
|
|
+ Instant endTime = endDate != null && !endDate.isBlank()
|
|
|
+ ? LocalDate.parse(endDate).plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant()
|
|
|
+ : null;
|
|
|
+
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ // 概览数字(range 控制累计/今天/7天)
|
|
|
+ result.put("overview", queryOverview(range));
|
|
|
+
|
|
|
+ // 指定维度只查一个,不查全部
|
|
|
+ boolean all = (dimension == null || dimension.isBlank());
|
|
|
+ if (all || "ip".equals(dimension)) {
|
|
|
+ result.put("by_ip", mapGroupResult(repo.countByIp(startTime, endTime)));
|
|
|
+ }
|
|
|
+ if (all || "url".equals(dimension)) {
|
|
|
+ result.put("by_url", mapGroupResult(repo.countByPageUrl(startTime, endTime)));
|
|
|
+ }
|
|
|
+ if (all || "type".equals(dimension)) {
|
|
|
+ result.put("by_type", mapGroupResult(repo.countByEventType(startTime, endTime)));
|
|
|
+ }
|
|
|
+ if (all || "user".equals(dimension)) {
|
|
|
+ List<Map<String, Object>> byUser = mapGroupResult(repo.countByUserKey(startTime, endTime));
|
|
|
+ result.put("by_user", byUser);
|
|
|
+ result.put("total_users", byUser.size());
|
|
|
+ }
|
|
|
+ if (all || "user_ip".equals(dimension)) {
|
|
|
+ result.put("by_user_ip", mapUserIpResult(repo.countByUserIp(startTime, endTime)));
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> mapGroupResult(List<Object[]> rows) {
|
|
|
+ return rows.stream()
|
|
|
+ .map(row -> Map.of("key", row[0] != null ? row[0] : "", "count", (Long) row[1]))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> mapUserIpResult(List<Object[]> rows) {
|
|
|
+ return rows.stream()
|
|
|
+ .map(row -> {
|
|
|
+ Map<String, Object> m = new LinkedHashMap<>();
|
|
|
+ m.put("user_key", row[0] != null ? row[0] : "");
|
|
|
+ m.put("ip", row[1] != null ? row[1] : "");
|
|
|
+ m.put("count", (Long) row[2]);
|
|
|
+ return m;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
}
|