Сервлет,который я хочу представить в этой статье используется для статистики посещений сайта.Счетчик работает с базой данных 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);
//
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; //
int today=1; //
long differ=0; //
while (rs.next ()) {
String address=rs.getString ("rem_addr");
long Time=rs.getLong ("time");
differ=time-Time; //
if (!remote.equals (address)) {
count++;
} else if (remote.equals (address) && differ>30) {
count ++;
}
if (!remote.equals (address) && differ<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 > 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 {
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;
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");
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
|