![logo](/assets/img/robot.jpg)
登录
没有账户?
本项目是基于WebSocket和Servlet实现的网页聊天室。使用到的技术包括:MVC编程思想、WebSocket、Servlet、MySQL、DruidDataSource、Json、Junit、Lombok、Maven
1、通过浏览器访问Tomcat服务器,显示注册、登录、聊天界面,实现聊天室的可视化功能
2、使用MySQL数据库存储用户信息,通过与数据库交互来实现用户登录、注册等功能
3、使用WebSocket协议来实现客户端与服务端之间数据的双向交互,以此完成聊天功能
4、通过遍历在线用户列表,广播发送消息,实现群聊、提醒某用户上线下线的功能
package com.bittech.java7.chatroom.utils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @Description:封装基础的工具方法,如加载配置文件、json序列化等
*/
public class CommUtils {
private static final Gson gson = new GsonBuilder().create();
private CommUtils(){}
/**
* 根据指定的文件名加载配置文件
* @param fileName 配置文件名
* @return
*/
public static Properties loadProperties(String fileName) {
Properties properties = new Properties();
// 获取当前配置文件夹下的文件输入流
InputStream in = CommUtils.class.getClassLoader()
.getResourceAsStream(fileName);
// 加载配置文件中的所有内容
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
return properties;
}
public static String object2Json(Object obj) {
return gson.toJson(obj);
}
public static Object json2Object(String jsonStr,Class objClass) {
return gson.fromJson(jsonStr,objClass);
}
public static boolean strIsNull(String str) {
return str == null || str.equals("");
}
}
AcountDao类:
package com.bittech.java7.chatroom.dao;
import com.bittech.java7.chatroom.entity.User;
import org.apache.commons.codec.digest.DigestUtils;
import java.sql.*;
/**
* @Description:关于用户模块的dao层
*/
public class AccountDao extends BaseDao{
// 用户登录 select
public User userLogin(String userName,String password) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
User user = null;
try {
connection = getConnection();
String sql = "SELECT * FROM user WHERE username = ? AND " +
" password = ?";
statement = connection.prepareStatement(sql);
statement.setString(1,userName);
statement.setString(2, DigestUtils.md5Hex(password));
resultSet = statement.executeQuery();
if (resultSet.next()) {
user = getUserInfo(resultSet);
}
}catch (Exception e) {
System.err.println("查询用户信息出错");
e.printStackTrace();
}finally {
closeResources(connection,statement,resultSet);
}
return user;
}
// 用户注册 insert
public boolean userRegister(User user) {
String userName = user.getUserName();
String password = user.getPassword();
Connection connection = null;
PreparedStatement statement = null;
boolean isSuccess = false;
try {
connection = getConnection();
String sql = "INSERT INTO user(username, password)" +
" VALUES(?,?) ";
statement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
statement.setString(1,userName);
statement.setString(2,DigestUtils.md5Hex(password));
isSuccess = (statement.executeUpdate() == 1);
}catch (Exception e) {
System.err.println("用户注册失败");
e.printStackTrace();
}finally {
closeResources(connection,statement);
}
return isSuccess;
}
// 将数据表信息封装到User类中
public User getUserInfo(ResultSet resultSet) throws SQLException {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
}
BaseDao类:
package com.bittech.java7.chatroom.dao;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.bittech.java7.chatroom.utils.CommUtils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @Description:封装基础操作,数据源、获取连接、关闭资源
*/
public class BaseDao {
private static DataSource dataSource;
static {
Properties properties = CommUtils.
loadProperties("datasource.properties");
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
System.err.println("数据源加载失败");
}
}
// 获取数据库连接
protected Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
System.err.println("获取连接失败");
}
return null;
}
// 关闭资源Statement ResultSet Connection
protected void closeResources(Connection connection,
Statement statement) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
protected void closeResources(Connection connection,
Statement statement,
ResultSet resultSet) {
closeResources(connection,statement);
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
package com.bittech.java7.chatroom.entity;
import lombok.Data;
import java.util.Map;
/**
* @Description:后端发送给前端的信息实体
*/
@Data
public class Message2Client {
// 聊天内容
private String content;
// 服务端登录的所有用户列表
private Map names;
public void setContent(String msg) {
this.content = msg;
}
public void setContent(String userName,String msg) {
this.content = userName + "说:" + msg;
}
}
package com.bittech.java7.chatroom.entity;
import lombok.Data;
/**
* @Description:前端发送给后端的信息实体类
* 群聊:{"msg":"777","type":1}
* 私聊:{"to":"0-","msg":"33333","type":2}
*/
@Data
public class MessageFromClient {
// 聊天信息
private String msg;
// 聊天类别: 1表示群聊 2表示私聊
private String type;
// 私聊的对象SessionID
private String to;
}
package com.bittech.java7.chatroom.entity;
import lombok.Data;
@Data
public class User {
private Integer id;
private String userName;
private String password;
}
package com.bittech.java7.chatroom.controller;
import com.bittech.java7.chatroom.service.AccountService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @Description:
*/
@WebServlet(urlPatterns = "/doRegister")
public class AccountController extends HttpServlet {
private AccountService accountService = new AccountService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("username");
String password = req.getParameter("password");
resp.setContentType("text/html;charset=utf8");
PrintWriter writer = resp.getWriter();
if (accountService.userRegister(userName,password)) {
// 用户注册成功
// 弹框提示,返回登陆界面
writer.println("");
}else {
// 弹框提示失败,保留原页面
writer.println("");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
package com.bittech.java7.chatroom.controller;
import java.util.HashMap;
import com.bittech.java7.chatroom.config.FreeMarkerListener;
import com.bittech.java7.chatroom.service.AccountService;
import com.bittech.java7.chatroom.utils.CommUtils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
/**
* @Description:
*/
@WebServlet(urlPatterns = "/login")
public class LoginController extends HttpServlet {
private AccountService accountService = new AccountService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("username");
String password = req.getParameter("password");
resp.setContentType("text/html;charset=utf8");
PrintWriter out = resp.getWriter();
if (CommUtils.strIsNull(userName) || CommUtils.strIsNull(password)) {
// 登录失败,停留登录页面
out.println(" ");
}
if (accountService.userLogin(userName,password)) {
// 登录成功,跳转到聊天页面
// 加载chat.ftl
Template template = getTemplate(req,"/chat.ftl");
Map map = new HashMap<>();
map.put("username",userName);
try {
template.process(map, out);
} catch (TemplateException e) {
e.printStackTrace();
}
}else {
// 登录失败,停留在登录页面
out.println(" ");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
private Template getTemplate(HttpServletRequest req,String fileName) {
Configuration cfg = (Configuration)
req.getServletContext().getAttribute(FreeMarkerListener.TEMPLATE_KEY);
try {
return cfg.getTemplate(fileName);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
package com.bittech.java7.chatroom.service;
import com.bittech.java7.chatroom.dao.AccountDao;
import com.bittech.java7.chatroom.entity.User;
/**
* @Description:
*/
public class AccountService {
private AccountDao accountDao = new AccountDao();
// 用户登陆
public boolean userLogin(String userName,String password) {
User user = accountDao.userLogin(userName,password);
if (user == null) {
return false;
}
return true;
}
// 用户注册
public boolean userRegister(String userName,String password) {
User user = new User();
user.setUserName(userName);
user.setPassword(password);
return accountDao.userRegister(user);
}
}
package com.bittech.java7.chatroom.service;
import com.bittech.java7.chatroom.entity.Message2Client;
import com.bittech.java7.chatroom.entity.MessageFromClient;
import com.bittech.java7.chatroom.utils.CommUtils;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @Description:
*/
@ServerEndpoint("/websocket")
public class WebSocket {
// 存储所有连接到后端的websocket
private static CopyOnWriteArraySet clients =
new CopyOnWriteArraySet<>();
// 缓存所有的用户列表
private static Map names = new ConcurrentHashMap<>();
// 绑定当前websocket会话
private Session session;
// 当前客户端的用户名
private String userName;
@OnOpen
public void onOpen(Session session) {
this.session = session;
// username=' + '${username}
String userName = session.getQueryString().split("=")[1];
this.userName = userName;
// 将客户端聊天实体保存到clients
clients.add(this);
// 将当前用户以及SessionID保存到用户列表
names.put(session.getId(),userName);
System.out.println("有新的连接,SessionID为"+session.getId() +
",用户名为"+userName);
// 发送给所有在线用户一个上线通知
Message2Client message2Client = new Message2Client();
message2Client.setContent(userName+"上线了!");
message2Client.setNames(names);
// 发送信息
String jsonStr = CommUtils.object2Json(message2Client);
for (WebSocket webSocket : clients) {
webSocket.sendMsg(jsonStr);
}
}
@OnError
public void onError(Throwable e) {
System.err.println("WebSocket连接失败!");
}
@OnMessage
//群聊:{"msg":"777","type":1}
//私聊:{"to":"0-","msg":"33333","type":2}
public void onMessage(String msg) {
// 将msg -> MessageFromClient
MessageFromClient messageFromClient = (MessageFromClient) CommUtils
.json2Object(msg,MessageFromClient.class);
if (messageFromClient.getType().equals("1")) {
// 群聊信息
String content = messageFromClient.getMsg();
Message2Client message2Client = new Message2Client();
message2Client.setContent(content);
message2Client.setNames(names);
// 广播发送
for (WebSocket webSocket : clients) {
webSocket.sendMsg(CommUtils.object2Json(message2Client));
}
}else if (messageFromClient.getType().equals("2")) {
// 私聊信息
// {"to":"0-1-2-","msg":"33333","type":2}
// 私聊内容
String content = messageFromClient.getMsg();
int toL = messageFromClient.getTo().length();
String tos[] = messageFromClient.getTo()
.substring(0,toL-1).split("-");
List lists = Arrays.asList(tos);
// 给指定的SessionID发送信息
for (WebSocket webSocket : clients) {
if (lists.contains(webSocket.session.getId()) &&
this.session.getId() != webSocket.session.getId()) {
// 发送私聊信息
Message2Client message2Client = new Message2Client();
message2Client.setContent(userName,content);
message2Client.setNames(names);
webSocket.sendMsg(CommUtils.object2Json(message2Client));
}
}
}
}
@OnClose
public void onClose() {
// 将客户端聊天实体移除
clients.remove(this);
// 将当前用户以及SessionID保存到用户列表
names.remove(session.getId());
System.out.println("有连接下线了"+
",用户名为"+userName);
// 发送给所有在线用户一个下线通知
Message2Client message2Client = new Message2Client();
message2Client.setContent(userName+"下线了!");
message2Client.setNames(names);
// 发送信息
String jsonStr = CommUtils.object2Json(message2Client);
for (WebSocket webSocket : clients) {
webSocket.sendMsg(jsonStr);
}
}
public void sendMsg(String msg) {
try {
this.session.getBasicRemote().sendText(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.bittech.java7.chatroom.config;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import freemarker.template.Configuration;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* @Description:
*/
@WebListener
public class FreeMarkerListener implements ServletContextListener {
public static final String TEMPLATE_KEY = "_template_";
@Override
public void contextInitialized(ServletContextEvent sce) {
// 配置版本
Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
// 配置加载ftl的路径
try {
cfg.setDirectoryForTemplateLoading(
new File("/Users/liuyiming/Documents/IDEA_Project/java7_chatroom_websocket/src/main/webapp"));
} catch (IOException e) {
e.printStackTrace();
}
// 配置页面编码
cfg.setDefaultEncoding(StandardCharsets.UTF_8.displayName());
sce.getServletContext().setAttribute(TEMPLATE_KEY,cfg);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
java7_chatroom
${username}
log-in
sign-up
websocket demo
请输入要发送的信息:
收到服务端信息为: