『博客开发日记-后台』之 获取访问趋势 和 获取浏览量概览 接口的实现

本文最后更新于 2026年5月17日 中午

获取访问趋势 和 获取浏览量概览 接口的实现


获取访问趋势 和 获取浏览量概览 接口的需求

获取访问趋势

解析日期并校验 确保开始日期 ≤ 结束日期

生成日期列表

将日期转换成字符串方便前端显示

生成每天的开始时间和结束时间

查询统计当天所有访问记录数量 PV

查询统计当天所有 IP

给ip去重并统计UV


获取浏览量概览

从 Redis 中获取累计 PV 和 UV

计算今天和昨天的时间范围 用于查询今天和昨天的数据

查询今天和昨日天的 PV

查询今天和昨天的 IP 列表

查询今天和昨天去重后的 UV

计算增长率


代码实现

创建 StatisticController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
@RequestMapping("/statistics")
@Api(tags = "浏览量统计", description = "统计接口")
public class StatisticController
{
@Autowired
private SiteStatisticsService siteStatisticsService;

@GetMapping("/visits/trend")
@ApiOperation(value = "获取访问趋势", notes = "返回指定日期范围内的 PV、UV、IP 趋势")
public ResponseResult getVisitTrend(
@RequestParam String startDate,
@RequestParam String endDate
) {
return siteStatisticsService.getVisitTrend(startDate, endDate);
}

@GetMapping("/visits/overview")
@ApiOperation(value = "获取访问概览", notes = "返回今日与总访问统计概览")
public ResponseResult getVisitOverview() {
return siteStatisticsService.getVisitOverview();
}
}

创建 VisitOverviewVo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "访问概览响应对象")
public class VisitOverviewVo
{
@ApiModelProperty(value = "今天访客UV数", example = "92")
private Integer todayUvCount;

@ApiModelProperty(value = "总访客UV数", example = "12030")
private Integer totalUvCount;

@ApiModelProperty(value = "访客UV增长率", example = "12.5")
private Double uvGrowthRate;

@ApiModelProperty(value = "今天浏览量PV数", example = "150")
private Integer todayPvCount;

@ApiModelProperty(value = "总浏览量PV数", example = "34210")
private Integer totalPvCount;

@ApiModelProperty(value = "浏览量PV增长率", example = "8.3")
private Double pvGrowthRate;
}

创建 VisitTrendVo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "访问趋势响应对象")
public class VisitTrendVo
{
@ApiModelProperty(value = "时间范围", example = "[\"2026-03-24\", \"2026-03-25\"]")
private List<String> dates;

@ApiModelProperty(value = "浏览量PV", example = "[120, 150]")
private List<Integer> pvList;

@ApiModelProperty(value = "访客数UV", example = "[80, 92]")
private List<Integer> uvList;

@ApiModelProperty(value = "ip数", example = "[70, 85]")
private List<Integer> ipList;
}

在 SiteStatisticsServiceImpl 中 注意这个是和前台共用的

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
//获取访问趋势
@Override
public ResponseResult getVisitTrend(String startDate, String endDate)
{
//解析日期并校验 确保开始日期 ≤ 结束日期
LocalDate start = LocalDate.parse(startDate, DateTimeFormatter.ISO_LOCAL_DATE);
LocalDate end = LocalDate.parse(endDate, DateTimeFormatter.ISO_LOCAL_DATE);
if (start.isAfter(end)) {
throw new SystemException(AppHttpCodeEnum.PARAM_INVALID);
}

//生成日期列表
List<LocalDate> dates = new ArrayList<>();
for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
dates.add(date);
}

//将日期转换成字符串方便前端显示
List<String> dateStrings = dates.stream()
.map(d -> d.format(DateTimeFormatter.ISO_LOCAL_DATE))
.collect(Collectors.toList());

//生成每天的开始时间和结束时间
List<Date> startTimes = dates.stream()
.map(d -> Date.from(d.atStartOfDay(ZoneId.systemDefault()).toInstant()))
.toList();
List<Date> endTimes = dates.stream()
.map(d -> Date.from(d.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant()))
.toList();

//初始化 PV、UV、IP
List<Integer> pvList = new ArrayList<>(dates.size());
List<Integer> uvList = new ArrayList<>(dates.size());
List<Integer> ipList = new ArrayList<>(dates.size());

//统计每天的 PV、UV、IP
for (int i = 0; i < dates.size(); i++)
{
Date dayStart = startTimes.get(i);
Date dayEnd = endTimes.get(i);

//查询当天所有访问记录数量 PV
long pvCount = visitLogMapper.selectCount(new LambdaQueryWrapper<VisitLog>()
.ge(VisitLog::getVisitTime, dayStart)
.lt(VisitLog::getVisitTime, dayEnd));
pvList.add((int) pvCount);

//查询当天所有 IP
List<VisitLog> logs = visitLogMapper.selectList(new LambdaQueryWrapper<VisitLog>()
.ge(VisitLog::getVisitTime, dayStart)
.lt(VisitLog::getVisitTime, dayEnd)
.select(VisitLog::getIp));

//给ip去重并统计UV
Set<String> uniqueIpSet = logs.stream()
.map(VisitLog::getIp)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
uvList.add(uniqueIpSet.size());
ipList.add(uniqueIpSet.size());
}

//封装成vo并返回结果
VisitTrendVo visitTrendVo = new VisitTrendVo(dateStrings, pvList, uvList, ipList);
return ResponseResult.okResult(visitTrendVo);
}

//获取浏览量概览
@Override
public ResponseResult getVisitOverview()
{
//从 Redis 中获取累计 PV 和 UV
Long totalPvCount = getCachedLong(SystemConstants.SITE_TOTAL_VIEWS, SystemConstants.SITE_TOTAL_VIEWS_FIELD);
Long totalUvCount = getCachedLong(SystemConstants.SITE_TOTAL_VISITORS, SystemConstants.SITE_TOTAL_VISITORS_FIELD);

//计算今天和昨天的时间范围 用于查询今天和昨天的数据
LocalDate today = LocalDate.now();
LocalDate yesterday = today.minusDays(1);
Date todayStart = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
Date tomorrowStart = Date.from(today.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
Date yesterdayStart = Date.from(yesterday.atStartOfDay(ZoneId.systemDefault()).toInstant());

//查询今天和昨日天的 PV
long todayPvCount = visitLogMapper.selectCount(new LambdaQueryWrapper<VisitLog>()
.ge(VisitLog::getVisitTime, todayStart)
.lt(VisitLog::getVisitTime, tomorrowStart));
long yesterdayPvCount = visitLogMapper.selectCount(new LambdaQueryWrapper<VisitLog>()
.ge(VisitLog::getVisitTime, yesterdayStart)
.lt(VisitLog::getVisitTime, todayStart));

//查询今天和昨天的 IP 列表
List<VisitLog> todayLogs = visitLogMapper.selectList(new LambdaQueryWrapper<VisitLog>()
.ge(VisitLog::getVisitTime, todayStart)
.lt(VisitLog::getVisitTime, tomorrowStart)
.select(VisitLog::getIp));
List<VisitLog> yesterdayLogs = visitLogMapper.selectList(new LambdaQueryWrapper<VisitLog>()
.ge(VisitLog::getVisitTime, yesterdayStart)
.lt(VisitLog::getVisitTime, todayStart)
.select(VisitLog::getIp));

//查询今天和昨天去重后的 UV
long todayUvCount = todayLogs.stream()
.map(VisitLog::getIp)
.filter(Objects::nonNull)
.distinct()
.count();
long yesterdayUvCount = yesterdayLogs.stream()
.map(VisitLog::getIp)
.filter(Objects::nonNull)
.distinct()
.count();

//计算增长率
double pvGrowthRate = calcGrowthRate(todayPvCount, yesterdayPvCount);
double uvGrowthRate = calcGrowthRate(todayUvCount, yesterdayUvCount);

//封装成vo
VisitOverviewVo visitOverviewVo = new VisitOverviewVo(
Math.toIntExact(todayUvCount),
Math.toIntExact(totalUvCount),
uvGrowthRate,
Math.toIntExact(todayPvCount),
Math.toIntExact(totalPvCount),
pvGrowthRate
);

return ResponseResult.okResult(visitOverviewVo);
}

//缓存
private Long getCachedLong(String key, String field)
{
Integer value = redisCache.getCacheMapValue(key, field);
return value == null ? 0L : value.longValue();
}

//计算增长率方法
private double calcGrowthRate(long current, long previous) {
if (previous == 0) {
return current == 0 ? 0.0 : 100.0;
}
return Math.round(((current - previous) * 1000.0 / previous)) / 10.0;
}




PS:该系列只做为作者学习开发项目做的笔记用

不一定符合读者来学习,仅供参考


预告

后续会记录博客的开发过程

每次学习会做一份笔记来进行发表

“一花一世界,一叶一菩提”


版权所有 © 2026 云梦泽
欢迎访问我的个人网站:https://hgt12.github.io/


『博客开发日记-后台』之 获取访问趋势 和 获取浏览量概览 接口的实现
http://example.com/2026/05/16/『博客开发日记-后台』之 获取访问趋势 和 获取浏览量概览 接口的实现/
作者
云梦泽
发布于
2026年5月16日
更新于
2026年5月17日
许可协议