Ajax实现在线聊天室

时间:2021-02-17 10:07:44

功能实现及开发过程可能遇到的问题

  • 发送聊天的消息和实时显示聊天的消息一定要分开处理!
  • 发送聊天消息如何实现?
    ①在多行文本框上面绑定“按下键盘[keypress]”事件,在用户按回车键时发送Ajax请求
    ②接收到Ajax请求后,保存聊天记录信息
  • 实时显示聊天记录如何实现?
    ①困难:
    [1]服务器端不会主动的告诉浏览器有新的聊天记录了,而是只能被动的响应浏览器的请求
    [2]浏览器不知道服务器端什么时候产生了新的聊天记录
    ②在浏览器端不间断的发送请求,询问服务器现在是否有新的聊天记录了
    如何实现?setTimeout(回调函数的引用,毫秒值);
    ③服务器端接收到浏览器询问后,检查当前是否有新的聊天记录,如果有,返回true
    ④如果询问的结果是true,再发送请求,从服务器端获取新的聊天记录内容
    ⑤如何知道哪些记录是新的呢?
    • 在浏览器端维护一个全局变量,保存本地最新的记录的ID值
    • 在询问服务器时,将本地ID值发送过去
    • 服务器端根据ID值判断哪些记录是最新的

1. 新建AjaxChat,代码目录图如图所示

Ajax实现在线聊天室

2.主界面index.jsp的代码如下,代码中已带有注释

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<base href="http://${pageContext.request.serverName }:${pageContext.request.serverPort }${pageContext.request.contextPath }/" />
<link rel="stylesheet" type="text/css" href="style/css.css" />
<script type="text/javascript" src="script/jquery-1.7.2.js"></script>
<script type="text/javascript" src="script/dateFormate.js"></script>
<script type="text/javascript">

$(function(){

//用于保存当前本地最新的聊天记录ID值,初始值为0,目的是第一次加载时获取全部聊天记录
var finalMessageId = 0;

askForNew();

//声明函数:询问服务器端是否存在新的聊天记录
function askForNew() {

$.post("ServletAsk",{"finalMessageId":finalMessageId},function(hasNew){

//注意:这里hasNew是字符串,即使服务器返回的是"false",在if中判断也为true
if(hasNew == "true") {

//给服务器端发送请求,获取最新的聊天记录
getNew();

}

},"text");

//注意:一定要使用函数的引用,不能加()
setTimeout(askForNew, 1000);

}

//声明函数:获取新的聊天记录内容
function getNew() {

var $showMessage = $("#showMessage");

$.post("ServletGetNew",{"finalMessageId":finalMessageId},function(newMessage){

/* private Integer messageId;
private String message;
private Date messageTime; */


for(var i = 0; i < newMessage.length; i++) {

var messageId = newMessage[i].messageId;
var message = newMessage[i].message;
var messageTime = newMessage[i].messageTime;

messageTime = new Date(messageTime).Format("yyyy年MM月dd日 hh:mm:ss");

var htmlStr = "<div>" + messageTime + " " + message + "</div>";

//console.log(htmlStr);
$showMessage.append(htmlStr);

finalMessageId = messageId;

}

//获取#showMessage对应的DOM对象,通过scrollTop属性设置滚动条的显示位置
$showMessage[0].scrollTop = 10000000;

},"json");

}

//给多行文本框绑定键盘按下事件
$("#sendMessage").keypress(function(event){

//在用户按下回车键时,发送聊天消息
//通过事件对象的keyCode属性获取当前按下的键对应的ASCII码
if(event.keyCode == 13) {

//获取聊天消息的内容
var message = $.trim(this.value);

//使用Ajax技术将聊天消息发送到服务器端
$.post("ServletSay",{"message":message});

//清空多行文本框
this.value = "";

}

});

});

</script>
</head>
<body>

<img src="image/logo.gif" />

<div id="showMessage"></div>
<textarea id="sendMessage"></textarea>

</body>
</html>

3.Message类的代码如下

package com.atguigu.liaotian.bean;

import java.util.Date;

public class Message {

private Integer messageId;
private String message;
private Date messageTime;

public Message() {

}

public Message(Integer messageId, String message, Date messageTime) {
super();
this.messageId = messageId;
this.message = message;
this.messageTime = messageTime;
}

@Override
public String toString() {
return "Message [messageId=" + messageId + ", message=" + message
+ ", messageTime=" + messageTime + "]";
}

public Date getMessageTime() {
return messageTime;
}

public void setMessageTime(Date messageTime) {
this.messageTime = messageTime;
}

public Integer getMessageId() {
return messageId;
}

public void setMessageId(Integer messageId) {
this.messageId = messageId;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

}

4.Dao基类使用反射机制实现对象的增删改查

package com.atguigu.liaotian.dao;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import com.atguigu.liaotian.utils.JDBCUtils;

/**
* Dao基类
* 增删改查
* @author Administrator
*
*/

public class BaseDao<T> {

private QueryRunner runner = new QueryRunner();

private Class<T> beanType = null;

public BaseDao() {
Class clazz = this.getClass();

Class superclass = clazz.getSuperclass();

Type type = clazz.getGenericSuperclass();

if(type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Type[] typeArguments = pt.getActualTypeArguments();
Type realType = typeArguments[0];
if(realType instanceof Class) {
beanType = (Class<T>) realType;
}
}

}

public Integer insertWithId(String sql, Object ... param) {
Integer id = null;

//1.获取数据库连接
Connection connection = JDBCUtils.getConnection();

//2.获取PreparedStatement对象
PreparedStatement ps = null;

//3.获取ResultSet对象用来保存返回的自增ID的值
ResultSet rs = null;

try {
//在获取PreparedStatement对象时,通过附加另外一个参数的方式将PreparedStatement对象
//设置为返回自增主键的模式
ps = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
for (int i = 0; i < param.length; i++) {
ps.setObject(i+1, param[i]);
}
ps.execute();

//自增的主键是以结果集形式返回的
rs = ps.getGeneratedKeys();

if(rs.next()) {
id = rs.getInt(1);
}

} catch (SQLException e) {
e.printStackTrace();
} finally {

//4.释放资源
JDBCUtils.releaseConnection(connection);

try {
DbUtils.close(ps);
} catch (SQLException e) {
e.printStackTrace();
}

try {
DbUtils.close(rs);
} catch (SQLException e) {
e.printStackTrace();
}
}

return id;
}

/**
* 执行批量更新操作
* @param sql
* @param params 执行批量操作时的SQL语句的参数,是二维数组类型
* 在非批量处理的情况下,SQL语句的参数是一维数组,对应SQL语句的一次执行
* 在批量处理的情况下,SQL语句的参数是二维数组,对应SQL语句的多次执行
* 其中每一个一维数组和一条SQL语句是对应的
* 第二维数组的每一个元素分别对应SQL语句中的一个参数
*/

public void batchUpdate(String sql, Object [] ... params ) {
Connection connection = JDBCUtils.getConnection();
try {
runner.batch(connection, sql, params);
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtils.releaseConnection(connection);
}

/**
* 获取单一值的方法,声明的泛型参数是根据接收返回值的变量的类型传入的
* 如果执行的是COUNT()函数需要注意它返回的是Long包装类型
* @param sql
* @param params
* @return
*/

public <E> E getSingleValue(String sql, Object ... params) {
E e = null;

Connection connection = JDBCUtils.getConnection();
try {
e = (E) runner.query(connection, sql, new ScalarHandler(), params);
} catch (SQLException e1) {
e1.printStackTrace();
}
JDBCUtils.releaseConnection(connection);

return e;
}

/**
* 查询数据库返回实体类对象的集合
* @param sql
* @param params
* @return实体类对象的集合
*/

public List<T> getBeanList(String sql, Object ... params) {

Connection connection = JDBCUtils.getConnection();

List<T> list = null;

try {
list = runner.query(connection, sql, new BeanListHandler<T>(beanType), params);
} catch (SQLException e) {
e.printStackTrace();
}

JDBCUtils.releaseConnection(connection);

return list;

}

/**
* 返回单一对象的查询方法
* @param sql
* @param params
* @return 将数据库查询结果封装得到的对象
*/

public T getBean(String sql, Object ... params) {
T t = null;

Connection connection = JDBCUtils.getConnection();
try {
t = runner.query(connection, sql, new BeanHandler<T>(beanType), params);
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtils.releaseConnection(connection);

return t;
}

/**
* 执行增删改的通用方法
* @param sql
* @param params
*/

public void update(String sql, Object ... params) {

Connection connection = JDBCUtils.getConnection();
try {
runner.update(connection, sql, params);
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtils.releaseConnection(connection);

}

}

5.BaseDao基类的实现类MessageDao类,实现Message对象的增删改查

package com.atguigu.liaotian.dao;

import java.util.List;

import com.atguigu.liaotian.bean.Message;

public class MessageDao extends BaseDao<Message>{

//将聊天记录信息保存到数据库中
public void saveMessage(Message message) {
String sql = "INSERT INTO MESSAGE (MESSAGE_CONTENT,MESSAGE_TIME) VALUES(?,?)";
this.update(sql, message.getMessage(), message.getMessageTime());
}

//根据浏览器传入的本地最新消息ID查询比本地消息还要新的聊天记录
public List<Message> getNewMessage(String finalMessageId) {
String sql = "SELECT `MESSAGE_ID` messageId,`MESSAGE_CONTENT` message,`MESSAGE_TIME` messageTime FROM `message` WHERE MESSAGE_ID>? ORDER BY MESSAGE_ID";
return this.getBeanList(sql, finalMessageId);
}

//根据浏览器传入的本地最新消息ID查询是否存在新的聊天记录
public boolean hasNew(String finalMessageId) {
String sql = "SELECT COUNT(*) FROM `message` WHERE `MESSAGE_ID`>?";
long count = this.getSingleValue(sql, finalMessageId);
return count > 0;
}

}

6.将聊天记录的值保存到数据库中

public class ServletSay extends HttpServlet {

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 获取聊天记录消息的值
String msg = request.getParameter("message");

Message message = new Message(null, msg, new Date());

System.out.println(message);
// 2. 将聊天记录的值保存到数据库中
MessageDao messageDao = new MessageDao();
messageDao.saveMessage(message);
}

}

7.询问是否有新消息的ServletAsk类代码如下

public class ServletAsk extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

//1.获取请求参数:finalMessageId
String finalMessageId = request.getParameter("finalMessageId");

//2.根据finalMessageId查询是否存在最新的聊天记录
MessageDao messageDao = new MessageDao();
boolean hasNew = messageDao.hasNew(finalMessageId);

//3.将布尔类型的返回值以Ajax响应的形式返回给浏览器
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write(hasNew+"");

}

}

8.获取新聊天记录内容,并转换为json格式数据传递到前台

public class ServletGetNew extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

//1.获取finalMessageId
String finalMessageId = request.getParameter("finalMessageId");

//2.根据finalMessageId获取新聊天记录内容
MessageDao messageDao = new MessageDao();
List<Message> newMessage = messageDao.getNewMessage(finalMessageId);

//3.转换为JSON字符串返回给浏览器
Gson gson = new Gson();
String json = gson.toJson(newMessage);

System.out.println(json);

response.setContentType("text/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write(json);

}

}

运行界面效果图

Ajax实现在线聊天室
Ajax实现在线聊天室
Ajax实现在线聊天室

项目源代码(点击下面链接)

AjaxChat源代码