571
社区成员
发帖
与我相关
我的任务
分享该系统实现了一个简单的聊天室,包含用户注册和登录、消息群发和私发、上线通知等功能;通过websocket实现消息群发;通过mysql存储用户信息,持久层使用mybatis框架;前端使用html、css、JavaScript以及FreeMarker模板引擎实现;最终部署在tomcat服务器上。
HTML5 WebSocket设计出来的目的就是取代轮询和长连接,使客户端浏览器具备像C/S框架下桌面系统的即时通讯能力,实现了浏览器和服务器全双工通信,建立在TCP之上,虽然WebSocket和HTTP一样通过TCP来传输数据,但WebSocket可以主动的向对方发送或接收数据,就像Socket一样;并且WebSocket需要类似TCP的客户端和服务端通过握手连接,连接成功后才能互相通信。
java websocket实现(主要函数实现):
//有连接时的触发函数
@OnOpen
public void onOpen(Session session) throws IOException{ }
//连接关闭时的调用方法。
@OnClose
public void onClose(){ }
//收到消息时调用的函数,其中Session是每个websocket特有的数据成员
@OnMessage
public void onMessage(String message, Session session) { }
//发生意外错误时调用的函数
@OnError
public void onError(Session session, Throwable error) { }
FreeMarker是一个使用纯Java编写的基于模板生成文本输出的通用工具,通常它的文件都是.ftl结尾。FreeMarker最初被用在MVC模式的Web开发框架中生成HTML页面,但是它的功能可不仅仅可以用到Web开发生成模板文件,它也可以用于非Web应用环境中,如:生成Java代码、xml、html等。它是一款轻量级的模板文件,不像JSP那样是Servlet要嵌入到应用程序中。该系统中使用它构建了聊天室,来实现与后端的信息交互。
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。使用它可以很方便地进行数据库的增删查改。
1)该系统使用maven管理,首先新建一个maven项目,然后导入相关依赖包。
<!-- websocket-->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<!-- freemarker模版引擎 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
<!-- mysql -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
2) 实现用户的登录注册功能。
在pojo包中声明user实体类:(省略了get和set方法)
public class User {
private int id;
private String username;
private String password;
public User(String usernamem, String password){
this.username = usernamem;
this.password = password;
}
}
在resources包下新建mybatis-config.xml,定义数据库的连接参数;在mapper包中新建UserMapper接口,声明数据库的查询和插入接口方法;在resources包下新建UserMapper.xml,实现接口对应方法的sql语句。(注:UserMapper.xml应与相应接口同名,并且需要在相同的包路径下)。
// UserMapper.xml
<mapper namespace="com.chatroom.mapper.UserMapper">
<select id="selectUser" resultType="com.chatroom.pojo.User">
select * from user where username = #{username} and password = #{password};
</select>
<insert id="addUser">
insert into user (id, username, password) values (null, #{username}, #{password});
</insert>
</mapper>
// UserMapper 接口
public interface UserMapper {
User selectUser(@Param("username") String username, @Param("password") String password);
void addUser(User user);
}
在service包下新建UserService类实现用户查询和插入:
public class UserService {
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
public User selectUser(String username, String pasword){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectUser(username, pasword);
sqlSession.close();
return user;
}
public boolean addUser(User user){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(user);
sqlSession.close();
return true;
}
}
最后在web包下新建LoginServlet和RegisterServlet来实现登录和注册:
登录时将用户呢称传递到前端模板
@WebServlet(urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
private UserService userService = new UserService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws 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("<script>\n" +
" alert(\"用户名或密码为空!\");\n" +
" window.location.href = \"/index.html\";\n" +
"</script>");
}
// 到数据库中对比 username 和 password是否一致
if(userService.selectUser(userName,password) != null) {
// 登陆成功,跳转到聊天页面
Template template = getTemplate(req,"/chat.ftl");
Map<String,String> map = new HashMap<>();
map.put("username", userName);
try {
//通过 map集合传入到前端
template.process(map, out);
} catch (TemplateException e) {
e.printStackTrace();
}
}else {
// 登陆失败,跳转到登录页面
out.println("<script>\n" +
" alert(\"用户名或密码不正确!\");\n" +
" window.location.href = \"/index.html\";\n" +
"</script>");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
// 加载 chat.ftl
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;
}
}
@WebServlet(urlPatterns = {"/doRegister"})
public class RegisterServlet extends HttpServlet {
private UserService userService = new UserService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 获取用户输入的 username,password(在表单中提交的值)
String userName = req.getParameter("username");
String password = req.getParameter("password");
resp.setContentType("text/html;charset=utf8");
//获取页面输出流
PrintWriter writer = resp.getWriter();
User user = new User(userName, password);
user.setId(100);
if(userService.addUser(user)) {
// 用户注册成功
//提示返回登录页面
writer.println("<script>\n" +
" alert(\"注册成功\");\n" +
" window.location.href =\"/index.html\";\n" +
"</script>");
}else {
// 提示注册失败,保留原页面(还停留在注册页面)
writer.println("<script>\n" +
" alert(\"注册失败!!!\");\n" +
" window.location.href =\"/registration.html\";\n" +
"</script>");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
3)实现消息发送功能:
在pojo包中定义MessageFromClient以及MessageToClient消息实体类:
public class MessageFromClient {
// 聊天信息
private String msg;
// 聊天类别: 1 表示群聊 2 表示私聊
private String type;
// 私聊对象的 SessionID, 根据SessionID到服务端找对应的 WebSocket
private String to;
}
public class MessageToClient {
// 聊天内容
private String content;
// 服务端要登录的所有用户列表
private Map<String,String> names;
}
在service中实现websocket类:(主要为实现上述websocket四个方法)
@ServerEndpoint("/websocket") // 把当前类标记为 Websocket类
public class WebSocket {
// 存储所有连接到后端的 websocket
private static CopyOnWriteArraySet<WebSocket> clients = new CopyOnWriteArraySet<>();
// 缓存所用的用户列表
private static Map<String,String> namespaces = new ConcurrentHashMap<>();
// 绑定当前websocket会话
private Session session;
// 当前客户端的用户名
private String userName;
@OnOpen
// 浏览器与 websocket建立连接
public void onOpen(Session session) {
// 绑定当前 session
this.session = session;
// username =' + '${username}'字符串拆分
String userName = session.getQueryString().split("=")[1];
this.userName = userName;
// 将客户端聊天实体保存到 clients
clients.add(this);
//将当前用户以及SessionID保存到用户列表
namespaces.put(session.getId(),userName);
//发送给所有用户上线通知(QQ好友上线通知)
MessageToClient messageToClient = new MessageToClient();
messageToClient.setContent(userName + "上线了!");
// 更新当前所有用户列表
messageToClient.setNames(namespaces);
// 给当前所有用户发送登录信息
String JSONStr = CommUtils.object2Json(messageToClient);
for(WebSocket webSocket : clients) {
webSocket.sendMsg(JSONStr);
}
}
@OnError
// 浏览器与后端连接失败
public void onError(Throwable e) {
System.err.println("WebSocket连接失败!");
e.printStackTrace();
}
@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);
//根据type的类型来做响应的区分
if (messageFromClient.getType().equals("1")) {
// 群聊信息
String content = messageFromClient.getMsg();
MessageToClient messageToClient = new MessageToClient();
messageToClient.setContent(content);
messageToClient.setNames(namespaces);
// 广播发送
for (WebSocket webSocket : clients) {
webSocket.sendMsg(CommUtils.object2Json(messageToClient));
}
}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<String> lists = Arrays.asList(tos);
// 给指定的SessionID发送信息
for (WebSocket webSocket : clients) {
if (lists.contains(webSocket.session.getId()) &&
this.session.getId() != webSocket.session.getId()) {
// 发送私聊信息
MessageToClient messageToClient = new MessageToClient();
messageToClient.setContent(userName,content);
messageToClient.setNames(namespaces);
webSocket.sendMsg(CommUtils.object2Json(messageToClient));
}
}
}
}
@OnClose
// 关闭连接
public void onClose() {
// 将客户端聊天实体移除
clients.remove(this);
// 将当前用户以及SessionID保存到用户列表
namespaces.remove(session.getId());
System.out.println("有连接下线了"+
",用户名为"+userName);
// 发送给所有在线用户一个下线通知
MessageToClient messageToClient = new MessageToClient();
messageToClient.setContent(userName+"下线了!");
messageToClient.setNames(namespaces);
// 发送信息
String jsonStr = CommUtils.object2Json(messageToClient);
for (WebSocket webSocket : clients) {
webSocket.sendMsg(jsonStr);
}
}
// 发送信息
public void sendMsg(String msg) {
try {
this.session.getBasicRemote().sendText(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
前段代码chat.ftl:通过嵌入html代码将消息展示到界面上
<script type="text/javascript">
var webSocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
webSocket = new WebSocket('ws://127.0.0.1:8080/websocket?username=' + '${username}');
} else {
alert("当前浏览器不支持WebSocket");
}
//连接发生错误的回调方法
webSocket.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误!");
};
webSocket.onopen = function () {
setMessageInnerHTML("WebSocket连接成功!")
};
webSocket.onmessage = function (event) {
$("#userList").html("");
eval("var msg=" + event.data + ";");
if (undefined != msg.content)
setMessageInnerHTML(msg.content);
// 添加当前在线用户到左侧
if (undefined != msg.names) {
$.each(msg.names, function (key, value) {
var htmlstr = '<li>'
+ '<div class="checkbox checkbox-success checkbox-inline">'
+ '<input type="checkbox" class="styled" id="' + key + '" value="' + key + '" checked>'
+ '<label for="' + key + '"></label>'
+ '</div>'
+ '<div class="liLeft"><img src="assets/img/robot2.jpg"/></div>'
+ '<div class="liRight">'
+ '<span class="intername">' + value + '</span>'
+ '</div>'
+ '</li>'
$("#userList").append(htmlstr);
})
}
};
webSocket.onclose = function () {
setMessageInnerHTML("WebSocket连接关闭");
};
window.onbeforeunload = function () {
closeWebSocket();
};
function closeWebSocket() {
webSocket.close();
}
function send() {
var time = new Date().toLocaleString();
var message = $("#dope").val();
$("#dope").val("");
//发送消息
var htmlstr = '<li><div class="answerHead"><img src="assets/img/2.png"></div><div class="answers">'
+ '[我]' + ' ' + time + '<br/>' + message + '</div></li>';
webSocketSend(htmlstr,message,"");
};
function webSocketSend(htmlstr,message,re){
$("#message").append(htmlstr);
// 发送消息给选中的用户
var ss = $("#userList :checked");
var to = "";
$.each(ss, function (key, value) {
to += value.getAttribute("value") + "-";
});
console.info(to);
if (ss.size() == 0) {
var obj = {
msg: message,
type: 1
}
} else {
var obj = {
to: to,
msg: message,
type: 2
}
}
var msg = JSON.stringify(obj);
webSocket.send(msg);
if(re){
$("#jsonImg").attr("src", string.data);
// loadDiv(re);
}
}
//回车键发送消息
$(document).keypress(function (e) {
if ((e.keyCode || e.which) == 13) {
$("#fasong").click();
}
});
//局部刷新div
function loadDiv(sJ) {
$("#delayImgPer").html('<img src="'+sJ+'" class="delayImg" >');
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
var msg = '<li>'
+ '<div class="nesHead">'
+ '<img src="assets/img/administrator-male.png">'
+ ' </div>'
+ '<div class="news">'
+ innerHTML
+ '</div>'
+ '</li>';
$("#message").append(msg);
}
</script>
1)登录注册界面

2)聊天室界面(群聊)



3)私聊(通过用户前面的复选框控制发送的对象)



该系统实现了基本的聊天功能,但还有很多不完整的地方,例如没有实现用户登出、不能查询历史聊天记录、不能更改用户信息等。
作者:NP497
、