On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru








 

Счетчик посещений с расширенной статистикой,использующий технологии Java Servlets и JDBC.

Сервлет,который я хочу представить в этой статье используется для статистики посещений сайта.Счетчик работает с базой данных MySQL,показывает общее количество посещений и посещения за сегодняшний день,отфильтровывает нажатия Reload с одного IP-адреса в течение 30 секунд,позволяет просматривать статистику посещений за все время,за последний месяц,последние 10,5 и 1 день-как общую,так и уникальные визиты. Вставляется в страницу с помощью SSI-директивы include,может вставляться в любое количество страниц,причем для каждой страницы ведется отдельная статистика.Для работы необходим сервер с поддержкой Java,например,Tomcat.Я использую Apache с модулем JServ,он прекрасно поддерживает сервлеты.

Возможности счетчика описал,теперь перейдем непосредственно к описанию сервлета.

Получение информации из БД и вывод счетчика на страницу.

Сначала нужно войти в БД и создать таблицу,в которую будет записываться статистика.


CREATE TABLE counter (
                      page varchar (150) not null,
                      rem_addr varchar (16) not null,
                      time bigint (15) not null,
                      rem_host varchar (150) not null,
                      date date not null);

В таблицу записывается адрес страницы,который передается в строке запроса,IP-адрес хоста, текущее время в секундах,имя удаленного хоста и текущая дата. Импортируем необходимые классы и объявляем переменные.


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;
import java.util.Date;

public class counter extends HttpServlet {

public void service (HttpServletRequest req, HttpServletResponse res) 
throws ServletException, IOException
{
	
String driver,url,user,password,query;
Connection con;
PrintStream out;
Date date=new Date ();
long time=date.getTime ();
time=time/1000;
String remote=req.getRemoteAddr ();
String host=req.getRemoteHost ();
String page=req.getParameter ("page");
String select[]={"All","Since last 30 days","Since last 10 days","Since last 5 days","Today"};
String action=req.getParameter ("action");
String login=req.getParameter ("login");
String pass=req.getParameter ("pass");
String sel=req.getParameter ("sel");
String ex=req.getParameter ("ex");

driver="org.gjt.mm.mysql.Driver";
url="jdbc:mysql://localhost:3306/mydb";
user="user";
password="password";
res.setContentType ("text/html");
out=new PrintStream (res.getOutputStream ());

Я использую драйвер для БД MySQL,хотя это может быть и другая БД.Драйверы JDBC существуют почти для всех БД.Их можно найти на сервере Sun. Основной метод service состоит из нескольких блоков кода,каждый из которых выполняет разные функции. Рассмотрим,как извлечь показания счетчика из БД и вывести их на страницу.


else if (action.equals ("show")) {
		 
        try {
//Регистрируем драйвер и пытаемся соединиться с БД.

        Class.forName (driver);	
        con=DriverManager.getConnection (url,user,password);

//Извлекаем информацию из таблицы counter.
        query="select rem_addr,time from counter where page='"+page+"'";
        Statement stmt = con.createStatement();
        ResultSet rs = stmt.executeQuery(query);
        pageHeader (out);
        showCounter (out,rs,time,remote,page,date,stmt,host); 
        rs.close ();
        stmt.close ();

 
//Если же не удалось соединиться с БД или проблемы с драйвером,генерируется исключение.
} catch (SQLException exe) {
        out.println("<hr>*** SQLException caught ***");
        while (exe != null) {
        out.println("SQLState: " + exe.getSQLState() + "<br>");
        out.println("Message: " + exe.getMessage() + "<br>");
        out.println("Vendor: " + exe.getErrorCode() + "<br>");
        exe = exe.getNextException();
   }
} catch(Exception e) {
        out.println("Unable to load driver.");	
        e.printStackTrace ();
       }
     }  

В приведенном блоке кода используются 2 метода:pageHeader () и showCounter ().Первый это заголовок HTML-страницы,а второй непосредственно формирует вывод счетчика на страницу.

Метод pageHeader ().


public void pageHeader (PrintStream out) {
	
out.println ("<html><head><title>Counter</title></head>"+ "<style>a:link {font-family:arial;font-size:9pt;text-decoration:none; color:#000080;}"+ "a:visited {font-family:arial;font-size:9pt;text-decoration:none;color:#000080;}"+ "TD {font-family:arial;font-size:10pt;color:#333300;}"+ "H2 {text-align:center;color:blue;}</style><body bgcolor=\"e6e8fa\">");
 }

Метод showCounter ().


public void showCounter (PrintStream out,ResultSet rs,long time,String remote,
String page,Date date,Statement stmt,String host) throws SQLException {
	
 int count=1; // Если это первый визит,присваиваем переменной значение 1.
 int today=1; // Аналогично для визитов за сегодня.
 long differ=0; // Разница времени между визитами с одного хоста.
 
Извлекаем данные.
 while (rs.next ()) {
 	String address=rs.getString ("rem_addr");
 	long Time=rs.getLong ("time");
differ=time-Time; // Вычисляем разность между текущим временем и временем,полученным из таблицы. Сравниваем адрес удаленного хоста с адресом,полученным из таблицы и если они не равны,увеличиваем показания счетчика на 1.
	
 	  if (!remote.equals (address)) {	
 	   count++;
Если это один и тот же IP-адрес,проверяем разницу времени и если она больше 30 секунд- увеличиваем показания счетчика на 1.

     } else if (remote.equals (address) && differ>30) {
       count ++;
     }

Теперь визиты за сегодня.Если IP-адреса не совпадают и разность времени меньше 86400 секунд- увеличиваем переменную today на 1.
    
     if (!remote.equals (address) && differ<86400) {
     	today++;

Если IP-адреса совпадают и разность времени между 30 и 86400,также увеличиваем показания today.
     } else if (remote.equals (address) && differ>30 && differ<86400) {
     	today++;
     }
 }
В противном случае не делаем ничего.
Теперь можно вывести показания счетчика на страницу.

 
out.println ("<table bgcolor=\"0000ff\" cellspacing=0 cellpadding=0 align=right width=100 height=30>"+ "<tr><td rowspan=2 bgcolor=\"ff0000\" width=40 align=center><font color=\"yellow\" face=\"impact\" size=5 >&nbsp;TOP</font>"+ "<a href=\"/servlet/counter?action=stat&page="+page+"\" target=\"_blank\">.</a><td height=10 bgcolor=\"cccccc\" align=center>"+ "<font color=\"ff0000\" size=1 face=\"impact\">Counter</font>"); out.println ("<tr><td align=right><font color=\"ffffff\" size=1 face=\"impact\">"+today+"</font><br>");

Преобразовываем показания счетчика в строку и вычисляем ее длину.Показания записываются в виде 6 цифр,причем недостающие позиции заполняются нулями.


 String s=new String ();
 s=""+ count;
 int len=s.length ();
 
  while (len<6) {
  out.print ("<font color=\"ffffff\" size=1 face=\"impact\">0</font>");
  len++;
}
 
out.print ("<font color=\"ffffff\" size=1 face=\"impact\">"+s+"</font></td></tr></table>");
writeToDB (date,count,differ,stmt,page,remote,time,host); 
  
}

После вывода показаний счетчика на экран,нужно записать новую информацию в БД.Это делает метод writeToDB ().

Метод writeToDB ().

Этот метод объявлен как synchronized,так как возможно одновременное обращение к сервлету в один промежуток времени. protected synchronized void writeToDB (Date date,int count,long differ,
Statement stmt,String page,String remote,long time,String host) throws SQLException {
Преобразуем дату в строку,пригодную для записи в БД (формата 0000-00-00)

 
  String ds=""+date.toLocaleString ();
  String year=ds.substring (6,10);
  String month=ds.substring (3,5);
  String day=ds.substring (0,2);
  
  String date1=year+"-"+month+"-"+day;

Если это первый визит или разность времени больше 30 секунд,записываем информацию в таблицу.
   if (count==1 || differ>30) {
   
String query="insert into counter values ('"+page+"','"+remote+"','"+time+"','"+host+"','"+date1+"')";
stmt.executeUpdate (query);
  }
  }

Просмотр статистики посещений.

Просматривать статистику посещений может только администратор сайта или лицо,имеющее доступ к данной БД.Для этого нужно ввести правильные логин и пароль.


if (action.equals ("stat")) {
         pageHeader (out);
         printFormPage (out,select,page);
}

Метод printFormPage ()-форма для ввода логина и пароля.


public void printFormPage (PrintStream out,String select [],String page) {
out.println ("<h2>Statistics for page <font color=\"000080\">"+page+"</font></h2>"+ "<p><form action=\"counter\" method=\"POST\"><input type=\"hidden\" name=\"action\" value=\"view\">"+ "<input type=\"hidden\" name=\"page\" value=\""+page+"\"><table align=center><tr><td><b>Login:</b><td><input type=\"text\" name=\"login\" size=15>"+ "<tr><td><b>Password:</b><td><input type=\"password\" name=\"pass\" size=15>"+ "<tr><td colspan=2><b>Show visits:</b><tr><td align=right><input type=\"radio\" name=\"ex\" value=\"all\" checked>"+ "<br><input type=\"radio\" name=\"ex\" value=\"unique\"><td>All<br>Unique"+ "<tr><td><b>View statistics:</b><td><select name=\"sel\">"); for (int i=0;i<select.length;i++) { out.println ("<option>"+select[i]+"</option>");
 }
                
out.println ("</select><tr><td colspan=2><input type=\"submit\" value=\"Submit\"></table></form>");
}                       
 }

Это просто HTML-форма,которая позволяет выбрать тип статистики-общая или уникальная,или за какой срок показывать статистику.Если логин и пароль правильные,соединяемся с БД и выводим информацию.


else if (action.equals ("view")) {
 	 	 
 	    if (login.equals (user) && pass.equals (password)) {  
 	 	try {
 	 	con=DriverManager.getConnection (url,user,password);
 	 	query="";
 		String begin=req.getParameter ("num");

 Будем выводить по 20 записей на страницу.

 		if (begin==null) {
 			begin="0";
 		   }
 		int size=20;
 		
 		pageHeader (out);
 		query=setQuery (select,sel,page,ex,query,size,begin,time);
 		Statement stmt = con.createStatement();
        ResultSet rs = stmt.executeQuery(query);
        ResultSetMetaData rsmd=rs.getMetaData ();
 		showAll (out,page,query,rs);
Формируем ссылки для навигации по страницам.

 		String query2=null;
        String qw=setQuery2 (query2,select,sel,page,ex,query,size,begin,time);

 Подсчитываем количество строк в таблице.

        rs = stmt.executeQuery(qw);
          int number=0;
          while (rs.next ()) {
           number=rs.getInt ("number");
           }
           
          double p=(double)number/size;
          int pages=(int) Math.ceil (p);
                    
          if (number>size) {
          	out.println ("<p align=right>");
          	for (int i=0;i<pages;i++) {
          	int	c=i*20;
          	int b=i+1;
          		
out.println (" <a href=\"counter?action=view&login="+login+"&pass="+pass+ "&ex="+ex+"&sel="+sel+"&num="+c+"&page="+page+"\">"+b+"</a>");
}
        }
 
В случае проблем,генерируется исключение.

    } catch (SQLException exe) {
      out.println("<hr>*** SQLException caught ***");
        while (exe != null) {
         out.println("SQLState: " + exe.getSQLState() + "<br>");
         out.println("Message: " + exe.getMessage() + "<br>");
         out.println("Vendor: " + exe.getErrorCode() + "<br>");
         exe = exe.getNextException();
     }
   } catch(Exception e) {
        out.print (e.toString ());	
        e.printStackTrace ();
  }

Если логин или пароль неверны,отсылаем посетителя назад.
} else {
	   
out.println ("<p align=center><font size=4>Incorrect login or password!</font>"); out.println ("<br>Please,try <a href=\"javascript:history.back ()\">again</a>");
}
}

В этом блоке кода использован ряд методов,которые нужно описать.

Метод setQuery ().


public String setQuery (String select [],String sel,String page,String ex,String query,int size,String begin,long time) {
	
 		
if (sel.equals (select[0])) {
if (ex.equals ("all")) {
query="select rem_addr,rem_host,date from counter where page='"+page+"' order by date desc limit "+begin+","+size;
 		    
} else {
query="select rem_addr,rem_host,MAX(date) as date from counter where page='"+page+"' group by rem_addr order by date desc limit "+begin+","+size;
}
} else if (sel.equals (select[1])) {
if (ex.equals ("all")) {
query="select rem_addr,rem_host,date from counter where page='"+page+"' AND time>"+(time-2592000)+" order by date desc limit "+begin+","+size;
} else {
query="select rem_addr,rem_host,MAX(date) as date from counter where page="+page+"' AND time>"+(time-2592000)+
" group by rem_addr order by date desc limit "+begin+","+size;
} 
} else if (sel.equals (select[2])) {
if (ex.equals ("all")) {
query="select rem_addr,rem_host,date from counter where page='"+page+"' AND time>"+(time-864000)+" order by date desc limit "+begin+","+size;
} else {
query="select rem_addr,rem_host,MAX(date) as date from counter where page='"+page+"' AND time>"+(time-864000)+" group by rem_addr order by date desc limit "+begin+","+size;
}  
} else if (sel.equals (select[3])) {
if (ex.equals ("all")) {
query="select rem_addr,rem_host,date from counter where page='"+page+"' AND time>"+(time-432000)+" order by date desc limit "+begin+","+size;
} else {
query="select rem_addr,rem_host,MAX(date) as date from counter where page='"+page+"' AND time>"+(time-432000)+" group by rem_addr order by date desc limit "+begin+","+size;

} 
} else if (sel.equals (select[4])) {
if (ex.equals ("all")) {
query="select rem_addr,rem_host,date from counter where page='"+page+"' AND time>"+(time-86400)+" order by date desc limit "+begin+","+size;
} else {
query="select rem_addr,rem_host,MAX(date) as date from counter where page='"+page+"' AND time>"+(time-86400)+" group by rem_addr order by date desc limit "+begin+","+size;
}   
} 
return query;  
}

Этот метод просто формирует строку запроса в зависимости от выбранного типа статистики-общей или уникальной,и от выбранного промежутка времени-все,последние 30,10,5 или 1 день. Когда строка запроса сформирована,из БД извлекается информация.

Метод showAll ().

Этот метод непосредственно выводит информацию на страницу.


public void showAll (PrintStream out,String page,String query,ResultSet rs)
throws SQLException {
	 		 
out.println ("<p align=right><a href=\"counter?action=stat&page="+page+"\"><font color=\"000080\">Back</font></a>"); out.println ("<p><table cellspacing=0 border=1 align=center width=70%><tr bgcolor=\"bbbbbb\">"+ "<th>Address<th>Host name<th>Date");
                  
while (rs.next ()) {
out.println ("<tr>");	
String remAddr=rs.getString ("rem_addr");
String remHost=rs.getString ("rem_host");
String date2=rs.getString ("date");
out.println ("<td>"+remAddr+"<td>"+remHost+"<td>"+date2);
}
out.println ("</table>");
}       

Метод setQuery2 ().

Этот метод формирует строку запроса для подсчета количества выводимых строк,в зависимости от выбранного типа статистики и интервала времени.


public String setQuery2 (String query2,String select [],String sel,String page,String ex,String query,int size,String begin,long time) {
if (sel.equals (select[0])) {
if (ex.equals ("all")) {
query2="select COUNT(rem_addr) as number from counter where page='"+page+"'"; } else {
query2="select COUNT(distinct rem_addr) as number from counter where page='"+page+"'"; }
} else if (sel.equals (select[1])) {
if (ex.equals ("all")) {
query2="select COUNT(rem_addr) as number from counter where page='"+page+"' AND time>"+(time-2592000);
} else {
query2="select COUNT(distinct rem_addr) as number from counter where page='"+page+"' AND time>"+(time-2592000);
} 
} else if (sel.equals (select[2])) {
if (ex.equals ("all")) {
query2="select COUNT(rem_addr) as number from counter where page='"+page+"' AND time>"+(time-864000);
} else {
query2="select COUNT(distinct rem_addr) as number from counter where page='"+page+"' AND time>"+(time-864000);
}
} else if (sel.equals (select[3])) {
if (ex.equals ("all")) {
query2="select COUNT(rem_addr) as number from counter where page='"+page+"' AND time>"+(time-432000);
} else {
query2="select COUNT(distinct rem_addr) as number from counter where page='"+page+"' AND time>"+(time-432000);
}
}  else if (sel.equals (select[4])) {
if (ex.equals ("all")) {
query2="select COUNT(rem_addr) as number from counter where page='"+page+"' AND time>"+(time-86400);
} else {
query2="select COUNT(distinct rem_addr) as number from counter where page='"+page+"' AND time>"+(time-86400);
}
}
return query2;
}

Как вставить счетчик в страницу.

Вставляется счетчик в страницу директивой SSI:

<include virtual="/servlet/counter?action=show&page=index.html -->

Как видно,в строке запроса передается адрес страницы,который записывается в БД.Поэтому,данный счетчик можно вставлять в любое количество страниц,для каждой будет показываться отдельная статистика,хоть и вся информация пишется в 1 таблицу. Размещается сервлет в директории,содержащей сервлеты в зависимости от типа используемого сервера.

Если это Tomcat:

%TOMCAT_HOME%/webapps/%app_name%/Web-inf/classes

Если это модуль JServ с Apache:

%JSERV_HOME%/servlets



Литература по Javascript