网站的统计数据有很多个,其中比较重要的是PV(Page View)、UV(Unique View)、页面停留时间、网站访问时间。
PV:指网站的页面浏览量,每刷一次页面,算一次
PV:指网站的独立访客数,同一个IP在同一天的多次访问只算一次。
页面停留时间:指用户在页面上停留的时间
网站访问时间:指用户退出网站时间与用户进入网站的时间差
其中页面停留时间的统计有多种方法
1、定时刷新后台
打开页面后,每隔一段时间刷新后台,以第一次刷新和最后一次刷新的时间差作为页面停留时间,同时设置最大页面停留时间。
优点:统计的页面停留时间相对比较准确。
缺点:当网站的访问量上来后,这种方式有很大的代价,后台日志的负载会非常大。
2、计算两个页面的访问时间差
比如T1时刻访问页面P1,T2时刻访问页面P2,那么访问页面P1的时间T=T2-T1,但访问最后一个页面的时间为0。
优点:统计方式比较简单明了。
缺点:统计的结果可能不太准确,比如打开P1页面后,再打开P2页面,然后再返回P1页面,这时统计的结果就不准确。
3、通过鼠标的滑动来刷新后台
当页面有鼠标的滑动时,就向后台刷新日志,以第一次滑动和最后一次滑动的时间差当作页面停留时间。
优点:统计的页面停留时间比较准确
缺点:后台日志的负载会比较大
下面用Java+Spring+Mysql实现第二种方式的统计
1、项目的程序结构如下:
2、核心代码
2.1 日志拦截器LogFilter.java
package com.filter;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.bean.LogBean;
import com.service.LogService;
public class LogFilter implements Filter {
Logger logger = Logger.getLogger(LogFilter.class);
@Autowired
private LogService logService;
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
if (!(servletRequest instanceof HttpServletRequest)
|| !(servletResponse instanceof HttpServletResponse)) {
throw new ServletException(
"OncePerRequestFilter just supports HTTP requests");
}
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpRequest.getSession();
String ip = httpRequest.getRemoteAddr();
String page = httpRequest.getRequestURI();
String contextPath = httpRequest.getContextPath();
String servletPath = httpRequest.getServletPath();
logger.info("doFilter sessionId="+session.getId()+",ip="+ip+",page="+page+",contextPath="+contextPath+",servletPath="+servletPath);
LogBean logBean = new LogBean();
logBean.setId(UUID.randomUUID().toString());
logBean.setSessionId(session.getId());
logBean.setIp(ip);
logBean.setPage(page);
logBean.setAccessTime(new Timestamp(new Date().getTime()));
logBean.setStayTime(0);
//通过session id 和 ip,查出最近的一条访问记录
LogBean bean = null;
try {
bean = logService.getLatestLog(session.getId(), ip);
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage());
}
//更改最近访问记录的停留时间,这里把两次访问记录的间隔时间算成上一次页面访问的停留时间
if(bean != null){
long stayTime = (System.currentTimeMillis() - bean.getAccessTime().getTime())/1000;
try {
logService.updateLog(bean.getId(), stayTime);
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage());
}
}
//保存当前访问记录
try {
logService.saveLog(logBean);
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage());
}
//统计网站今天的PV(页面浏览量),UV(独立访客数)
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String format = df.format(new Date());
Date parse = null;
try {
parse = df.parse(format);
} catch (ParseException e1) {
e1.printStackTrace();
}
Timestamp startTime = new Timestamp(parse.getTime());
Timestamp endTime = new Timestamp(parse.getTime() + 24*3600*1000);
try {
int pv = logService.getPV(startTime, endTime);
int uv = logService.getUV(startTime, endTime);
logger.info("pv="+pv);
logger.info("uv="+uv);
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage());
}
filterChain.doFilter(servletRequest, servletResponse);
return;
}
public void init(FilterConfig filterConfig) throws ServletException {
ServletContext context = filterConfig.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
logService = (LogService) ctx.getBean("logService");
logger.info("init");
}
public void destroy() {
}
}
2.2 web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>logFilter</filter-name>
<filter-class>com.filter.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>logFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
3、日志表设计
CREATE TABLE log (
id varchar(64) NOT NULL,
session_id varchar(64) NOT NULL,
ip varchar(64) NULL,
page varchar(200) NULL,
access_time timestamp NULL,
stay_time mediumtext NULL,
PRIMARY KEY(id)
)
ENGINE = InnoDB
AUTO_INCREMENT = 0
;
CREATE INDEX log_index
ON log(session_id, ip)
;
4、项目的下载地址如下
http://download.youkuaiyun.com/detail/brushli/7518709
5、评价与展望
该系统的设计能基本满足中小网站的数据统计,对于对准确性要求比较高的网站来说,关键统计数据的统计方法应做改进;
对于并发量比较高的网站来说,直接将日志插入数据库的方式也应做改进,比如改用消息队列MQ来处理。