1、概要
- 关于java web 网站访问统计功能,翻阅了很多技术文档没有找到合适的功能需求,为了满足开源项目《RuoYi-fast-cms》集大众之所及,于是根据实际业务逻辑,按照自己的思路开发出来了
- 网站统计是指通过对网站的访问情况、用户行为和其他相关数据进行收集、分析和报告,以便了解网站的运营情况和用户特征。它的作用和优点包括:
-
- 了解用户行为:通过网站统计,可以知道用户在网站上的停留时间、浏览页面、点击链接和交互等行为,从而了解用户的兴趣和偏好,有效地进行用户行为分析。
-
- 优化网站体验:通过对用户行为和反馈的统计分析,可以发现网站的问题和瓶颈,并根据数据做出优化改进,提升网站的用户体验和用户满意度。
-
- 提高转化率:网站统计可以追踪用户的转化路径,包括注册、购买、留言等行为,并通过分析找出影响转化率的因素,从而优化网站的设计、内容和推广策略,提高转化率和销售额。
-
- 评估营销效果:通过网站统计,可以了解到各种营销活动的效果,比如广告点击量、转化率、ROI等,从而为营销决策提供数据支持,优化营销策略,提高投资回报率。
-
- 监控网站安全:网站统计可以监控恶意攻击、异常访问和网络安全事件等,及时发现并解决安全问题,保护网站和用户数据的安全。
- 总之,网站统计可以帮助网站主了解用户行为、优化网站体验、提高转化率、评估营销效果和监控网站安全,从而提升网站的运营效果和用户满意度。
2、来源
- 开源项目 RuoYi-fast-cms是一款 Java CMS 网站管理系统,项目基于 RuoYi-fast 二次开发,网站后台采用 SpringBoot + MyBatis,前端网站模版使用 thymeleaf+bootstrap 开发,多套风格模板,可自由飞翔。
- 代码源自开源项目: https://github.com/huangxing2010/RuoYi-fast-cms
3、需求分析
- 刷新或者访问页面获取访客 ip 地址,
- 初次访问根据站点类型、ip 存储
- 第二次访问根据 ip 如果存在累加 pv 量加 1否则新增
- 每天记录一条数据,防止重复添加做了合并数据
4、数据库表
关于CRUD就不做阐述了,感兴趣的小伙伴请移步开源仓库
drop table if exists sys_portal_browse;
create table sys_portal_browse (
browse_id bigint(20) not null auto_increment comment '浏览id',
browse_type char(1) default '0' comment '类型(0网站A 1网站B ...)',
browse_json json comment '浏览统计(ip+次数)',
remark varchar(512) default null comment '备注',
create_time datetime comment '创建时间',
primary key (browse_id)
) engine=innodb auto_increment=1 comment = '访问统计表';
5、代码分享
高端食材往往采用最朴素的烹饪方式,程序员的每一道菜都凝聚了辛勤的汗。大佬厌倦了屎山上堆屎,往往满足客户应急需求势在必得,若不够优雅请勿喷,欢迎高手指点!
5.1、控制器方法
//访问统计
modelMap.put("browseCount", greenBrowseService.setBrowseCount("0"));
5.2、业务层
/**
* 统计访问量
* @param browseType
* @return
*/
@Transactional
@Override
public Map<String, Object> setBrowseCount(String browseType) {
String ip = ShiroUtils.getIp();
PortalBrowse portalBrowse = new PortalBrowse();
Map<String, Object> params = portalBrowse.getParams();
params.put("browseType", browseType);
//当前日期开始时间
LocalDate today = LocalDate.now();
String todayDate = today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String beginTime = todayDate + " 00:00:00";
String endTime = todayDate + " 23:59:59";
params.put("beginTime", beginTime);
params.put("endTime", endTime);
List<PortalBrowse> portalBrowses = portalBrowseMapper.selectPortalBrowseByParam(params);
//每天统计只有一条数据,访问量大的时候可能会有多条数据出现,所以做了合并
if (portalBrowses.size() > 0) {
//防止当天统计条数多于1
if (portalBrowses.size() > 1) {
//便利出 id集合
List<String> ids = new ArrayList<>();
List<String> ips = new ArrayList<>();
//遍历合并 ip+number集合
List<Map<String, Object>> arrayBrowse = new ArrayList<>();
portalBrowses.forEach(item -> {
ids.add(item.getBrowseId().toString());
String browseJson = item.getBrowseJson(); //字符串转json
JSONArray jsonArr = JSON.parseArray(browseJson);
//遍历 ip+ number集合
for (int i = 0; i < jsonArr.size(); i++) {
Map<String, Object> map1 = new HashMap<>();
JSONObject obj = jsonArr.getJSONObject(i);
map1.put("ip", (String) obj.get("ip"));
map1.put("number", (Integer) obj.get("number"));
ips.add((String) obj.get("ip"));
arrayBrowse.add(map1);
}
});
List<Map<String, Object>> browseList = getBrowseList(arrayBrowse, ip, ips);
//插入新遍历数据,并删除旧数据
PortalBrowse browseObjs = new PortalBrowse();
browseObjs.setBrowseType(browseType);
browseObjs.setBrowseJson(JSON.toJSONString(browseList));
browseObjs.setCreateTime(DateUtils.getNowDate());
if (portalBrowseMapper.insertPortalBrowse(browseObjs) > 0) {
portalBrowseMapper.deletePortalBrowseByBrowseIds(ids.toArray(new String[]{}));
}
} else { // 只有 1 条的时候修改该数据
List<String> ips = new ArrayList<>();
//遍历合并 ip+number集合
List<Map<String, Object>> arrayBrowse1 = new ArrayList<>();
PortalBrowse portalBrowse1 = portalBrowses.get(0);
String browseJson = portalBrowse1.getBrowseJson();
JSONArray jsonArr1 = JSON.parseArray(browseJson);
//遍历 ip+ number集合
for (int i = 0; i < jsonArr1.size(); i++) {
Map<String, Object> map1 = new HashMap<>();
JSONObject obj = jsonArr1.getJSONObject(i);
map1.put("ip", (String) obj.get("ip"));
map1.put("number", (Integer) obj.get("number"));
ips.add((String) obj.get("ip"));
arrayBrowse1.add(map1);
}
//封装 :ip重复的 number 值 累积相加
List<Map<String, Object>> browseList = getBrowseList(arrayBrowse1, ip, ips);
PortalBrowse portalBrowse3 = new PortalBrowse();
portalBrowse3.setBrowseId(portalBrowse1.getBrowseId());
portalBrowse3.setBrowseJson(JSON.toJSONString(browseList));
portalBrowseMapper.updatePortalBrowse(portalBrowse3);
}
} else { //为空的时候,新增一条数据
PortalBrowse browse = new PortalBrowse();
List<Object> objects = new ArrayList<>();
Map<String, Object> obj = new HashMap<>();
obj.put("ip", ip);
obj.put("number", 1);
objects.add(obj);
browse.setBrowseType(browseType);
browse.setBrowseJson(JSON.toJSONString(objects));
browse.setCreateTime(DateUtils.getNowDate());
portalBrowseMapper.insertPortalBrowse(browse);
}
//给模版赋值
Map<String, Object> map = new HashMap<>();
map.put("todayPv", getBrowseCount(browseType, "today").get("pv"));
map.put("todayIp", getBrowseCount(browseType, "today").get("ip"));
map.put("yesterdayPv", getBrowseCount(browseType, "yesterday").get("pv"));
map.put("yesterdayIp", getBrowseCount(browseType, "yesterday").get("ip"));
map.put("weekPv", getBrowseCount(browseType, "week").get("pv"));
map.put("weekIp", getBrowseCount(browseType, "week").get("ip"));
map.put("allPv", getBrowseCount(browseType, "all").get("pv"));
map.put("allIp", getBrowseCount(browseType, "all").get("ip"));
return map;
}
5.3、业务层封装
/**
* 获取pv和ip
* @param browseType 站点,dateType
* @param dateType today=今天 yesterday=昨天 week=本周 all=全部
* @return
*/
private Map<String, Object> getBrowseCount(String browseType, String dateType) {
PortalBrowse portalBrowse = new PortalBrowse();
Map<String, Object> params = portalBrowse.getParams();
params.put("browseType", browseType);
// 获取今天的日期
LocalDate today = LocalDate.now();
if(dateType == "today"){
String todayDate = today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String beginTime = todayDate + " 00:00:00";
String endTime = todayDate + " 23:59:59";
params.put("beginTime", beginTime);
params.put("endTime", endTime);
}else if(dateType == "yesterday"){
// 获取昨天的日期
LocalDate yesterday = today.minusDays(1);
// System.out.println("昨天的日期: " + yesterday);
String yesterdayStr = yesterday.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String beginTime = yesterdayStr + " 00:00:00";
String endTime = yesterdayStr + " 23:59:59";
params.put("beginTime", beginTime);
params.put("endTime", endTime);
}else if(dateType == "week"){
// 获取本周的开始日期(周一)
LocalDate startOfWeek = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
String format = startOfWeek.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
//今天
String format1 = today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String beginTime = format + " 00:00:00";
String endTime = format1 + " 23:59:59";
params.put("beginTime", beginTime);
params.put("endTime", endTime);
} else if(dateType == "all"){
//今天
String format1 = today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
//开始时间是开发之前的任意时间
String beginTime ="2020-01-01 00:00:00";
String endTime = format1 + " 23:59:59";
params.put("beginTime", beginTime);
params.put("endTime", endTime);
}else {
//开始时间是开发之前的任意时间
String beginTime ="2020-01-01 00:00:00";
String endTime = "2220-01-01 23:59:59";
params.put("beginTime", beginTime);
params.put("endTime", endTime);
}
List<PortalBrowse> portalBrowses = portalBrowseMapper.selectPortalBrowseByParam(params);
Map<String, Object> map = new HashMap<>();
if (portalBrowses.size() == 1) {
JSONArray objects = JSON.parseArray(portalBrowses.get(0).getBrowseJson());
List<Integer> numberAll = new ArrayList<>();
for (int i = 0; i < objects.size(); i++) {
numberAll.add(objects.getJSONObject(i).getInteger("number"));
}
int sum = numberAll.stream().mapToInt(Integer::intValue).sum();
map.put("pv", sum);
map.put("ip", objects.size());
}else if(portalBrowses.size() > 1){
List<String> ipValues = new ArrayList<>();
List<Integer> num = new ArrayList<>();
portalBrowses.forEach(item -> {
JSONArray objects = JSON.parseArray(item.getBrowseJson());
for (int i = 0; i < objects.size(); i++){
ipValues.add((String) objects.getJSONObject(i).get("ip"));
num.add(objects.getJSONObject(i).getInteger("number"));
}
});
map.put("pv", num.stream().mapToInt(Integer::intValue).sum());
map.put("ip", ipValues.size());
}else {
map.put("pv", 0);
map.put("ip", 0);
}
return map;
}
/**
* 封装:ip重复的 number 值 累积相加
*
* @param objects
* @param ip
* @param ips
* @return
*/
private List<Map<String, Object>> getBrowseList(List<Map<String, Object>> objects, String ip, List<String> ips) {
//ip重复的 number 值 累积相加
Map<String, Integer> collect1 = objects.stream().collect(Collectors.groupingBy(m -> (String) m.get("ip"), Collectors.summingInt(m -> (Integer) m.get("number"))));
//得到新集合
List<Map<String, Object>> arr = new ArrayList<>();
//如果数据库有当前 ip 则累加1,否则新增一条
if (ips.stream().anyMatch(ip::equals)) {
collect1.forEach((k, v) -> {
Map<String, Object> browseObj = new HashMap<>();
browseObj.put("ip", k);
browseObj.put("number", (k.equals(ip)) ? v + 1 : v); //等于当前 ip + 1
arr.add(browseObj);
});
} else {
collect1.forEach((k, v) -> {
Map<String, Object> browseObj = new HashMap<>();
browseObj.put("ip", k);
browseObj.put("number", v);
arr.add(browseObj);
});
Map<String, Object> browseObj1 = new HashMap<>();
browseObj1.put("ip", ip);
browseObj1.put("number", 1);
arr.add(browseObj1);
}
return arr;
}
6、技术细节
场景中涉及诸多细节,比如:
- 当日无人光顾,明日即为空
- 本周、本月、本年
- ip 量、pv 量
- 站群中个站点访问统计
7、小结
程序员的世界里,思路决定出路,曾经一味的追求优雅,到头来发现一直在优雅的路上却没有到达优雅的终点,在有限的时间空间里务实合作、解放思想、循序渐进,常态化发展才是正道。