项目:JavaWeb聊天室(问题汇总)
项目需求:
建立一个网页聊天室
1.用户可以登录进来
2.用户进入聊天室之后可以互相发送消息
3.聊天室管理员有权限将普通用户踢下线
项目框架:
myeclipse建立基本的javaweb项目,采用基本的mvc三层架构:servlet,dao,service;
项目设计:
1.设计登录页面index.jsp,
index.jsp
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@page contentType="text/html" pageEncoding="UTF-8"%> <html > <title>聊天室</title> <link href="CSS/style.css" rel="stylesheet"> <script type="text/javascript"> function check(){ if(document.getElementById("form1").username.value==""){ alert("请输入用户名!"); document.getElementById("form1").username.focus(); return false; } if(document.getElementById("form1").password.value==""){ alert("请输入密码名!"); document.getElementById("form1").password.focus(); return false; } } </script> <body> <br> <center><h3 style="color:red;">${ msg }</h3></center> <form id="form1" name="form1" method="post" action="${pageContext.request.contextPath }/user" onSubmit="return check()"> <input type="hidden" name="method" value="login"> <table width="371" height="230" border="0" align="center" cellpadding="0" cellspacing="0" background="images/login.jpg"> <tr> <td height="120" colspan="3" class="word_dark"> </td> </tr> <tr> <td width="53" align="center" valign="top" class="word_dark"> </td> <td width="216" align="center" valign="top" class="word_dark"> <table width="100%" height="100%"> <tr> <td>用户名:</td> <td><input type="text" name="username" class="login"></td> </tr> </table> </td> <td width="94" valign="top" class="word_dark"></td> </tr> <tr> <td width="53" align="center" valign="top" class="word_dark"> </td> <td width="216" align="center" valign="top" class="word_dark"> <table width="100%" height="100%"> <tr> <td>密 码:</td> <td><input type="password" name="password" class="login"></td> </tr> </table> </td> <td width="94" valign="top" class="word_dark"></td> </tr> <tr> <td width="53" align="center" valign="top" class="word_dark"> </td> <td width="216" align="center" valign="top" class="word_dark"> <input name="Submit" type="submit" class="btn_bg" value="进 入"> </td> <td width="94" valign="top" class="word_dark"> </td> </tr> </table> </form> </body> </html>
2.设计主页面main.jsp,
<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ include file="safe.jsp"%> <html> <head> <title>聊天室</title> <link href="CSS/style.css" rel="stylesheet"> <script type="text/javascript" src="${ pageContext.request.contextPath }/js/jquery-1.8.3.js"></script> <script type="text/javascript"> var sysBBS = "<span style='font-size:14px; line-height:30px;'>欢迎光临心之语聊天室,请遵守聊天室规则,不要使用不文明用语。</span><br><span style='line-height:22px;'>";var sysBBS = "<span style='font-size:14px; line-height:30px;'>欢迎光临心之语聊天室,请遵守聊天室规则,不要使用不文明用语。</span><br><span style='line-height:22px;'>"; window.setInterval("showContent();",1000); window.setInterval("showOnLine();",10000); window.setInterval("check();",1000); // Jquery:JS框架. // 相当于window.onload $(function(){ showOnLine(); showContent(); check(); }); function check(){ $.post("${pageContext.request.contextPath}/user?method=check",function(data){ if(data == 1){ // 提示用户下线了 alert("用户已经被踢下线了!"); // 回到登录页面! window.location = "index.jsp"; } }); } // 显示在线人员列表 function showOnLine(){ // 异步发送请求 获取在线人员列表 // Jquery发送异步请求 $.post("${pageContext.request.contextPath}/online.jsp?"+new Date().getTime(),function(data){ // $("#online") == document.getElementById("online"); $("#online").html(data); }); } // 显示聊天的内容 function showContent(){ $.post("${pageContext.request.contextPath}/user?"+new Date().getTime(),{'method':'getMessage'},function(data){ $("#content").html(sysBBS+data); }); } function set(selectPerson){ //自动添加聊天对象 if(selectPerson != "${existUser.username}"){ form1.to.value=selectPerson; }else{ alert("请重新选择聊天对象!"); } } function send(){ if(form1.to.value==""){ alert("请选择聊天对象!"); return false; } if(form1.content.value==""){ alert("发送信息不可以为空!"); form1.content.focus(); return false; } // $("#form1").serialize():让表单中所有的元素都提交. // jquery提交数据.{id:1,name:aa,age:25} $.post("${pageContext.request.contextPath}/user?"+new Date().getTime(),$("#form1").serialize(),function(data){ $("#content").html(sysBBS+data+"</span>"); }); } function exit(){ alert("欢迎您下次光临!"); window.location.href="${pageContext.request.contextPath}/user?method=exit"; } function checkScrollScreen(){ if(!$("#scrollScreen").attr("checked")){ $("#content").css("overflow","scroll"); }else{ $("#content").css("overflow","hidden"); //当聊天信息超过一屏时,设置最先发送的聊天信息不显示 //alert($("#content").height()); $("#content").scrollTop($("#content").height()*2); } setTimeout('checkScrollScreen()',500); } </script> </head> <body> <table width="778" height="150" border="0" align="center" cellpadding="0" cellspacing="0" background="images/top.jpg"> <tr> <td> </td> </tr> </table> <table width="778" height="276" border="0" align="center" cellpadding="0" cellspacing="0"> <tr> <td width="165" valign="top" bgcolor="#f6fded" id="online" style="padding:5px">在线人员列表</td> <td width="613" height="200px" valign="top" background="images/main_bj.jpg" bgcolor="#FFFFFF" style="padding:5px; "> <div style="height:290px; overflow:hidden" id="content">聊天内容</div></td> </tr> </table> <table width="778" height="95" border="0" align="center" cellpadding="0" cellspacing="0" bordercolor="#D6D3CE" background="images/bottom.jpg"> <form action="" id="form1" name="form1" method="post"> <input type="hidden" name="method" value="sendMessage"/> <tr> <td height="30" align="left"> </td> <td height="37" align="left"> <input name="from" type="hidden" value="${existUser.username}">[${existUser.username} ]对 <input name="to" type="text" value="" size="35" readonly="readonly"> 表情 <select name="face" class="wenbenkuang"> <option value="无表情的">无表情的</option> <option value="微笑着" selected>微笑着</option> <option value="笑呵呵地">笑呵呵地</option> <option value="热情的">热情的</option> <option value="温柔的">温柔的</option> <option value="红着脸">红着脸</option> <option value="幸福的">幸福的</option> <option value="嘟着嘴">嘟着嘴</option> <option value="热泪盈眶的">热泪盈眶的</option> <option value="依依不舍的">依依不舍的</option> <option value="得意的">得意的</option> <option value="神秘兮兮的">神秘兮兮的</option> <option value="恶狠狠的">恶狠狠的</option> <option value="大声的">大声的</option> <option value="生气的">生气的</option> <option value="幸灾乐祸的">幸灾乐祸的</option> <option value="同情的">同情的</option> <option value="遗憾的">遗憾的</option> <option value="正义凛然的">正义凛然的</option> <option value="严肃的">严肃的</option> <option value="慢条斯理的">慢条斯理的</option> <option value="无精打采的">无精打采的</option> </select> 说:</td> <td width="189" align="left"> 字体颜色: <select name="color" size="1" class="wenbenkuang" id="select"> <option selected>默认颜色</option> <option style="color:#FF0000" value="FF0000">红色热情</option> <option style="color:#0000FF" value="0000ff">蓝色开朗</option> <option style="color:#ff00ff" value="ff00ff">桃色浪漫</option> <option style="color:#009900" value="009900">绿色青春</option> <option style="color:#009999" value="009999">青色清爽</option> <option style="color:#990099" value="990099">紫色拘谨</option> <option style="color:#990000" value="990000">暗夜兴奋</option> <option style="color:#000099" value="000099">深蓝忧郁</option> <option style="color:#999900" value="999900">卡其制服</option> <option style="color:#ff9900" value="ff9900">镏金岁月</option> <option style="color:#0099ff" value="0099ff">湖波荡漾</option> <option style="color:#9900ff" value="9900ff">发亮蓝紫</option> <option style="color:#ff0099" value="ff0099">爱的暗示</option> <option style="color:#006600" value="006600">墨绿深沉</option> <option style="color:#999999" value="999999">烟雨蒙蒙</option> </select> </td> <td width="19" align="left"><input name="scrollScreen" type="checkbox" class="noborder" id="scrollScreen" onClick="checkScrollScreen()" value="1" checked> </td> </tr> <tr> <td width="21" height="30" align="left"> </td> <td width="549" align="left"> <input name="content" type="text" size="70" onKeyDown="if(event.keyCode==13 && event.ctrlKey){send();}"> <input name="Submit2" type="button" class="btn_grey" value="发送" onClick="send()"> </td> <td align="right"><input name="button_exit" type="button" class="btn_grey" value="退出聊天室" onClick="exit()"> </td> <td align="center"> </td> </tr> <tr> <td height="30" align="left"> </td> <td colspan="2" align="center" class="word_dark"> All </td> <td align="center"> </td> </tr> </form> </table> </body> </html>
3.显示在线人数页面online.jsp
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@page import="cn.itcast.service.UserService"%> <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ page import="java.util.*"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> <table width="100%" border="0" cellpadding="0" cellspacing="0"> <tr><td height="32" align="center" class="word_orange ">欢迎来到心之语聊天室!</td></tr> <tr> <td height="23" align="center"><a href="#" onclick="set('所有人')">所有人</a></td> </tr> <c:forEach var="entry" items="${ userMap }"> <tr> <td height="23" align="center"> <a href="#" onclick="set('${ entry.key.username }')">${ entry.key.username }</a> <c:if test="${ existUser.type == 'admin' and entry.key.type != 'admin'}"> <a href="${ pageContext.request.contextPath }/user?method=kick&id=${ entry.key.id }">踢下线</a> </c:if> </td> </tr> </c:forEach> <tr><td height="30" align="center">当前在线[<font color="#FF6600">${ fn:length(userMap) }</font>]人</td></tr> </table>
4.userServlet.java 实现聊天室基本的逻辑处理
package cn.itcast.action; import java.io.IOException; import java.util.Date; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.beanutils.BeanUtils; import cn.itcast.service.UserService; import cn.itcast.utils.BaseServlet; import cn.itcast.vo.User; public class UserServlet extends BaseServlet { /** * 检查session是否过期 * @throws IOException */ public String check(HttpServletRequest req,HttpServletResponse resp) throws IOException{ // 从session中获得用户的信息 User existUser = (User) req.getSession().getAttribute("existUser"); // 判断session中的用户是否过期 if(existUser == null){ // 登录的信息已经过期了! resp.getWriter().println("1"); }else{ // 登录的信息没有过期 resp.getWriter().println("2"); } return null; } /** * 退出聊天室 * @throws IOException */ public String exit(HttpServletRequest request,HttpServletResponse response) throws IOException{ // 获得session对象 HttpSession session = request.getSession(); // 将session销毁. session.invalidate(); // 页面转向. response.sendRedirect(request.getContextPath()+"/index.jsp"); return null; } /** * 发送聊天内容 * @throws IOException */ public String sendMessage(HttpServletRequest req,HttpServletResponse resp) throws IOException{ // 1.接收数据 。 System.out.println("sendMessage invoke...."); String from = req.getParameter("from"); // 发言人 String face = req.getParameter("face"); // 表情 String to = req.getParameter("to"); // 接收者 String color = req.getParameter("color"); // 字体颜色 String content = req.getParameter("content"); // 发言内容 // 发言时间 正常情况下使用SimpleDateFormat String sendTime = new Date().toLocaleString(); // 发言时间 // 2.获得ServletContext对象. ServletContext application = getServletContext(); // 从ServletContext中获取消息 String sourceMessage = (String) application.getAttribute("message"); // 3.拼接发言的内容:xx 对 yy 说 xxx sourceMessage += "<font color='blue'><strong>" + from + "</strong></font><font color='#CC0000'>" + face + "</font>对<font color='green'>[" + to + "]</font>说:" + "<font color='" + color + "'>" + content + "</font>(" + sendTime + ")<br>"; // 4.将消息存入到application的范围 application.setAttribute("message", sourceMessage); return getMessage(req, resp); } /** * 获取消息的方法 * @throws IOException */ public String getMessage(HttpServletRequest req,HttpServletResponse resp) throws IOException{ String message = (String) getServletContext().getAttribute("message"); if(message != null){ resp.getWriter().println(message); } return null; } /** * 踢人的功能 * @throws IOException */ public String kick(HttpServletRequest req,HttpServletResponse resp) throws IOException{ // 1.接收参数 int id = Integer.parseInt(req.getParameter("id")); // 2.踢人:从userMap中将用户对应的session销毁. // 获得userMap集合(在线列表) Map<User, HttpSession> userMap = (Map<User, HttpSession>) getServletContext() .getAttribute("userMap"); // 获得这个用户对应的session.如何知道是哪个用户呢? id已经传递过来.去数据库中查询. // 重写user的equals 和 hashCode 方法 那么只要用户的id相同就认为是同一个用户. User user = new User(); user.setId(id); // 从map集合中获得用户的对应的session HttpSession session = userMap.get(user); // 销毁session session.invalidate(); // 3.重定向到页面 resp.sendRedirect(req.getContextPath()+"/main.jsp"); return null; } /** * 登录的功能 */ public String login(HttpServletRequest req,HttpServletResponse resp){ // 接收数据 Map<String, String[]> map = req.getParameterMap(); User user = new User(); // 封装数据 try { BeanUtils.populate(user, map); // 调用Service层处理数据 UserService us = new UserService(); User existUser = us.login(user); if (existUser == null) { // 用户登录失败 req.setAttribute("msg", "用户名或密码错误!"); return "/index.jsp"; } else { // 用户登录成功 // 第一个BUG的解决:第二个用户登录后将之前的session销毁! req.getSession().invalidate(); // 第二个BUG的解决:判断用户是否已经在Map集合中,存在:已经在列表中.销毁其session. // 获得到ServletCOntext中存的Map集合. Map<User, HttpSession> userMap = (Map<User, HttpSession>) getServletContext() .getAttribute("userMap"); // 判断用户是否已经在map集合中' if(userMap.containsKey(existUser)){ // 说用map中有这个用户. HttpSession session = userMap.get(existUser); // 将这个session销毁. session.invalidate(); } // 使用监听器:HttpSessionBandingListener作用在JavaBean上的监听器. req.getSession().setAttribute("existUser", existUser); ServletContext application = getServletContext(); String sourceMessage = ""; if (null != application.getAttribute("message")) { sourceMessage = application.getAttribute("message") .toString(); } sourceMessage += "系统公告:<font color='gray'>" + existUser.getUsername() + "走进了聊天室!</font><br>"; application.setAttribute("message", sourceMessage); resp.sendRedirect(req.getContextPath() + "/main.jsp"); return null; } } catch (Exception e) { e.printStackTrace(); } return null; } }
5.userDao.java与userDaoImple.java分别是创建数据库接口和从数据库获取相关属性值,
package cn.itcast.dao; import cn.itcast.vo.User; public interface UserDao { public User login(User user); }
package cn.itcast.dao; import java.sql.SQLException; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import cn.itcast.utils.JDBCUtils; import cn.itcast.vo.User; public class UserDaoImple implements UserDao { public User login(User user) { QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource()); String sql = "select * from user where username = ? and password = ?"; User existUser; try { existUser = queryRunner.query(sql, new BeanHandler<User>(User.class), user.getUsername(),user.getPassword()); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException("用户登录失败!"); } return existUser; } }
6.userService.java是用户业务需求,
package cn.itcast.service; import cn.itcast.dao.UserDao; import cn.itcast.dao.UserDaoImple; import cn.itcast.vo.User; public class UserService { public User login(User user) { UserDao dao = new UserDaoImple(); return dao.login(user); } }
(附:
1.MyServletContextListener.java是监听器,监听ServletContext对象创建和销毁,
package cn.itcast.listener; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.HttpSession; import cn.itcast.vo.User; /** * 监听ServletContext对象创建和销毁 * @author 姜涛 * */ public class MyServletContextListener implements ServletContextListener{ // ServletContext对象创建 下面这个方法就会执行 // ServletContextEvent事件对象. 监听器对象---》ServletContext对象.(事件源) public void contextInitialized(ServletContextEvent sce) { Map<User,HttpSession> userMap = new HashMap<User,HttpSession>(); sce.getServletContext().setAttribute("userMap", userMap); } public void contextDestroyed(ServletContextEvent sce) { } }
2.baseServlet.java可以让一个servlet同时处理多个请求,
package cn.itcast.utils; import java.io.IOException; import java.lang.reflect.Method; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class BaseServlet extends HttpServlet { /* * 它会根据请求中的m,来决定调用本类的哪个方法 */ protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); res.setContentType("text/html;charset=utf-8"); // 例如:http://localhost:8080/demo1/xxx?method=login String methodName = req.getParameter("method");// 它是一个方法名称 // 当没用指定要调用的方法时,那么默认请求的是execute()方法。 if(methodName == null || methodName.isEmpty()) { methodName = "execute"; } Class c = this.getClass(); try { // 通过方法名称获取方法的反射对象 Method m = c.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); // 反射方法目标方法,也就是说,如果methodName为add,那么就调用add方法。 String result = (String) m.invoke(this, req, res); // 通过返回值完成请求转发 if(result != null && !result.isEmpty()) { req.getRequestDispatcher(result).forward(req, res); } } catch (Exception e) { throw new ServletException(e); } } }
3.JDBCUtils.java数据库连接池工具类,
package cn.itcast.utils; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; import java.util.ResourceBundle; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; /** * JDBC工具类: * * 加载驱动 * * 获得连接 * * 释放资源 * * 代码都重复. * @author 姜涛 * */ public class JDBCUtils { private static ComboPooledDataSource dataSource = new ComboPooledDataSource(); // 获得连接池: public static DataSource getDataSource(){ return dataSource; } // 获得连接 public static Connection getConnection() { Connection conn = null; try { conn = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return conn; } }
4.user.java实体类,创建对象,
package cn.itcast.vo; import java.util.Map; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; public class User implements HttpSessionBindingListener { private int id; private String username; private String password; private String type; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; User other = (User) obj; if (id != other.id) return false; return true; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getType() { return type; } public void setType(String type) { this.type = type; } public void valueBound(HttpSessionBindingEvent event) { System.out.println("进入了...."); HttpSession session = event.getSession(); Map<User, HttpSession> userMap = (Map<User, HttpSession>) session .getServletContext().getAttribute("userMap"); userMap.put(this, session); } // 当session和对象解除绑定的时候 public void valueUnbound(HttpSessionBindingEvent event) { System.out.println("退出了...."); HttpSession session = event.getSession(); // 获得人员列表 Map<User, HttpSession> userMap = (Map<User, HttpSession>) session .getServletContext().getAttribute("userMap"); // 将用户移除了 userMap.remove(this); } }
5.c3p0-config.xml数据库连接配置文件
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///mychatroom</property> <property name="user">root</property> <property name="password">123456</property> </default-config> </c3p0-config>
6.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"> <display-name></display-name> <!-- 注册监听器 --> <listener> <listener-class>cn.itcast.listener.MyServletContextListener</listener-class> </listener> <servlet> <servlet-name>UserServlet</servlet-name> <servlet-class>cn.itcast.action.UserServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>UserServlet</servlet-name> <url-pattern>/user</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
)