1. 后台结构:
2.需求的jar 包
3. WSConfig.java
/**
*
*/
package com.ws.config;
import java.util.Set;
import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
/**
* @author zhong
*
*/
public class WSConfig implements ServerApplicationConfig {
/**
* 用注解的方式实现
*/
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scand) {
System.out.println("=======> WSConfig:"+scand.size());
return scand;
}
/**
* 用接口的方式实现
*/
@Override
public Set<ServerEndpointConfig> getEndpointConfigs(
Set<Class<? extends Endpoint>> arg0) {
return null;
}
}
4.ChatSocket.java
/**
*
*/
package com.ws.socket;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSON;
import com.ws.bean.Content;
import com.ws.bean.Message;
import com.ws.bean.User;
/**
* @author zhong
*
*/
@ServerEndpoint(value="/echo")
public class ChatSocket {
private static String user = null;
private static List<Session> sessions = new ArrayList<Session>();
private static List<Map<String,Object>> userList = new ArrayList<Map<String,Object>>();
private static Map<String, Session> map = new HashMap<String, Session>();
public ChatSocket(){
}
@OnOpen
public void open(Session session){
user = getSessionUser(session);
Session ms = map.get(user);
if(ms == null){
map.put(user, session);
sessions.add(session);
User u = new User();
u.setGroup("myfriend");
u.setNickname(user);
u.setSignature("我就是我,大名叫"+user+"的我~");
userList.add(getUserMap(u));
Message message = new Message();
message.setType("online");
message.setUsers(userList);
message.setFrom(user);
broadcast(sessions,JSON.toJSONString(message));
System.out.println("===== 欢迎 "+user+" 进入IM, 当前共 "+userList.size() +" 人在线!");
}
}
/**
* 消息广播
* @param sessions
* @param json
*/
private void broadcast(List<Session> sessions, String msg) {
for(Iterator<Session> iterator = sessions.iterator(); iterator.hasNext();){
Session session = (Session) iterator.next();
try {
if(session != null){
session.getBasicRemote().sendText(msg);
}else{
System.out.println("======> broadcast session is NUll. by "+user);
}
} catch (IOException e) {
System.out.println("=======> broadcast:"+user+" 已退出");
e.printStackTrace();
}
}
}
@OnClose
public void close(Session session){
user = getSessionUser(session);
sessions.remove(session);
if(userList != null && userList.size() > 0){
for (int i=0; i<userList.size(); i++) {
Map<String, Object> map = userList.get(i);
if(map.get(user) != null){
userList.remove(map);
}
}
}
map.remove(user);
Message message = new Message();
message.setType("offline");
message.setUsers(userList);
System.out.println("======> "+user +" 已离线, 当前在线共 "+userList.size() +" 人!");
broadcast(sessions,JSON.toJSONString(message));
}
/**
* 消息响应方法
* @param session
* @param json
*/
@OnMessage
public void message(Session session,String json){
String fromUser = getSessionUser(session);
String toUser = null;
Content content = (Content) JSON.parseArray(json, Content.class);
if(content == null){
return;
}
System.out.println("===== message:"+content.getMsg());
//null 或 single
if(content.getMode() == null){
String to = content.getTo();
if(to != null && to != ""){
String toArr[] = to.split(",");
if(toArr == null || toArr.length == 0 ){
return;
}
for(int i=0; i<toArr.length; i++){
Session toSession = map.get(toArr[i]);
if(toSession == null){
continue;
}
toUser = getSessionUser(toSession);
try {
Message message = new Message();
message.setType("message");
message.setFrom(fromUser);
message.setTo(toUser);
message.setLayer(content.getLayer());
message.setContent(content.getMsg());
toSession.getBasicRemote().sendText(JSON.toJSONString(message));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}else if(("model").equalsIgnoreCase(content.getMode())){
Message message = new Message();
message.setLayer(content.getLayer());
message.setFrom(user);
message.setType("message");
message.setContent(content.getMsg());
broadcast(sessions, JSON.toJSONString(message));
}
}
/**
* 获取用户map
* @author zhong
* @param u
* @return
*/
public static Map<String, Object> getUserMap(User u){
Map<String, Object> umap = null;
if(u != null){
umap = new HashMap<String, Object>();
umap.put(user, JSON.toJSONString(u));
}
return umap;
}
/**
* 获取session用户
* @author zhong
* @param session
* @return
*/
public static String getSessionUser(Session session){
String result = null;
if(session != null){
String parms = session.getQueryString();
String[] parmArr = parms.split("=");
if(parmArr != null && parmArr.length > 1){
result = parmArr[1];
}
}
return result;
}
}
另外的消息类我就不贴代码了
6.前台核心代码:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
pageContext.setAttribute("basePath", basePath);
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>PQ - 内聊版</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<%@ include file="include.jsp" %>
<style type="text/css">
form {
margin:0 auto;
margin-top:10%;
}
</style>
</head>
<body>
<div class="content">
</div>
<script type="text/javascript">
var ws = null;
var userName = "${userName}"; //当前登录用户
var host = "192.168.1.100:8080";
var isOpen = false; //主窗口是否打开
var sidx; //im主窗口index
var slayero;
var wsMsg;
var wins = []; //存放当前打开的layer窗口
$(function(){
initWs();
});
function initWs(){
//页面打开后,连接socket
var target = "ws://"+host+"/pwtchat/echo?userName="+userName;
if ('WebSocket' in window) {
ws = new WebSocket(target);
console.log("WebSocket 请求成功");
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket(target);
console.log("MozWebSocket 请求成功");
} else {
alert('WebSocket is not supported by this browser.');
return;
}
//注册接收消息监听方法
ws.onmessage = function(event){
console.log(event);
wsMsg = eval("("+event.data+")");
var type = wsMsg.type;
if(!isOpen){
//显示im主窗口
isOpen = true;
showIm(wsMsg);
}else{
//更新好友上线或下线
if("online" == type || "offline" == type){
var imWin = window[slayero.find('iframe')[0]['name']];
imWin.updateFirendGroup(wsMsg.users);
}
}
//好友消息接收
if(undefined != wsMsg.content && "message" == type){
console.log(wsMsg);
var from = wsMsg.from;
var idx = getLayerIdx(from);
var body = layer.getChildFrame('body', idx);
if(undefined != body){
var $show = body.find("#msg-show");
if(undefined != $show && $show.length> 0){
var msg = {
nickname: from,
msg: wsMsg.content,
time: FY.getFormatLongDate(new Date())
};
$show.append(getMsgTemplate(msg));
//滚动条保持底部
$show.scrollTop( $show[0].scrollHeight );
}
}
}
};
ws.onclose = function (event) {
console.log(event);
};
};
//发送一条消息
function wsSend(msgs){
ws.send(JSON.stringify(msgs));
}
/* 显示主窗口 */
function showIm(msg){
return layer.open({
type: 2,
id: "im-main",
title: ['PQ - 内聊版', 'font-size:14px;'],
area: ['270px', '430px'],
fix: false, //不固定
maxmin: true,
shade :0.1,
content: "chat/chat-main-template.jsp",
success: function(layero, index){
var win = {
name: "im-main",
idx: index
};
wins.push(win);
slayero = layero;
sidx = index;
},
end: function(){
window.location.href= "login.jsp";
}
});
};
/* 显示聊天窗口 */
function showMsgWindow(user){
if(userName == user.nickname){
FY.alert({
type: "warning",msg : "跟自家自儿发消息就没意思了!"
});
return false;
}
var smidx = layer.open({
type: 2,
id: user.nickname,
title: ['PQ - 内聊版', 'font-size:14px;'],
area: ['450px', '370px'],
fix: false, //不固定
maxmin: false,
shade :0,
content: 'chat/chat-msg-template.jsp',
success: function(layero, index){
var win = {
name: "im-msg",
idx: index,
user: user.nickname == null ? "pwt_" : user.nickname
};
wins.push(win); console.log(wins);
var body = layer.getChildFrame('body', index);
//更新当前用户信息
body.find("#nickname").text(user.nickname);
body.find("#signature").text(user.signature);
//动态绑定send事件
body.find("#msg-send").bind("click",function(){
var $inp = body.find("#msg-input");
var msg = $inp.text();
if(msg){
var msgs = {
layer: index,
msg: msg,
to: user.nickname
};
//提交给socket
wsSend(msgs);
//显示在自己的界面上
var msg = {
nickname : userName,
msg: msg,
time: FY.getFormatLongDate(new Date())
};
var $show = body.find("#msg-show");
$show.append(getMsgTemplate(msg));
$show.scrollTop($show[0].scrollHeight );
//清空输入框
$inp.text("");
}
});
},
end: function(){
removeLayerWin(smidx);
}
});
};
function getMsgTemplate(msg){
var html="";
if(msg){
html+="<div class='msg-item'>"+
" <div class='msg-title'>"+
" <img class='mt-face' alt='' src='${basePath}images/face.jpg'>"+
" <div class='mt-name'>"+
" <span class='nickname'>"+msg.nickname+"</span>"+
" <span class='datetime'>"+msg.time+"</span>"+
" </div>"+
" </div>"+
" <div class='msg-content'>"+msg.msg+"</div>"+
"</div>";
}
return html;
}
/* 根据用户信息获取layer的index */
function getLayerIdx(user){
var idx = -1;
if(FY.isEmpty(user)){
return;
}
for(w in wins){
if(wins[w].user == user){
return wins[w].idx;
}
}
return idx;
}
/* 关闭窗口时删除wins中的窗口信息 */
function removeLayerWin(smidx){
if(FY.isEmpty(smidx)){
return;
}
for(w in wins){
if(wins[w].idx == smidx){
wins.splice(w,1);
}
}
}
</script>
</body>
</html>
chat/chat-main-template.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
pageContext.setAttribute("basePath", basePath);
%>
<link rel="stylesheet" type="text/css" href="${basePath}css/bootstrap/bootstrap.css">
<link rel="stylesheet" type="text/css" href="${basePath}css/bootstrap/bootstrap-theme.css">
<link rel="stylesheet" type="text/css" href="${basePath}css/bootstrap/fileinput.min.css">
<link rel="stylesheet" type="text/css" href="${basePath}js/layer.2.4/layui.css">
<script type="text/javascript" src='${basePath}js/jquery.min.js'></script>
<script type="text/javascript" src='${basePath}js/bootstrap/bootstrap.min.js'></script>
<script type="text/javascript" src='${basePath}js/layer.2.4/layer.js'></script>
<script type="text/javascript" src='${basePath}js/fy.utils.js'></script>
<style>
body,html {
padding: 0;
margin: 0;
}
.im-header {
width:100%;
height: 75px;
background: #F9f9f9;
border-bottom:1px solid #eee;
}
.im-header .im-face-box,.im-info-box {
float: left;
}
.im-header .im-face-box {
width: 80px;
height: 54px;
border-radius: 54px;
padding: 5px 5px 5px 20px;
}
.im-header .im-face-box>img {
width: 54px;
height: 54px;
border-radius: 54px;
}
.im-header .im-info-box {
width:165px;
margin-left: 10px;
overflow: hidden;
}
.im-header .im-info-box p {
font-size: 12px;
line-height: 20px;
height: 20px;
padding: 2px;
margin: 2px;
}
/* 好友列表 */
.im-friends {
}
.im-friends .friend-group {
height:auto;
margin:0 10px;
line-height:30px;
cursor: pointer;
}
.im-group-collapse {
/* 分组折叠 */
}
.im-friends .friend-group .iconfont {
padding-right:2px;
font-size:10px;
}
.im-friends .im-ul-box {
display:none;
}
.im-friends .im-ul-box .im-li {
width:100%;
height:50px;
overflow: hidden;
padding-left:30px;
list-style: none;
padding-top:3px;
padding-bottom:3px;
}
.im-friends .im-li:hover {
background: #eee;
}
.im-friends .im-li:hover .nickname {
color:#000;
}
.im-friends .im-li .im-li-img,.im-li-text {
float:left;
}
.im-friends .im-li .im-li-img {
width:40px;
border-radius:40px;
}
.im-friends .im-li .im-li-text {
margin-left:10px;
color:#888;
}
.im-friends .im-li-text .nickname,.signature {
height:20px;
line-height:15px;
font-size:8px;
cursor: default;
}
.im-friends .im-li-text .nickname {
font-size:14px;
}
</style>
<div class="im-header">
<div class="im-face-box">
<img alt="" src="${basePath}images/face.jpg" width="100%">
</div>
<div class="im-info-box">
<p>在线 <i class="iconfont icon-nryc"></i></p>
<p id="nickname">vip 88888</p>
<p>IM实验田,来搞一个自己的聊天工具</p>
</div>
</div>
<div class="im-friends">
<div class="friend-group" data-idx="0" data-name="myfriend">
<span class="iconfont icon-nextpage"></span> 我的好友
</div>
<div class="im-ul-box" data-name="myfriend">
<ul class="im-group-ul collapse">
<li class="im-li">
<img class="im-li-img" alt="" src="${basePath}images/face.jpg">
<div class="im-li-text">
<div class="nickname">zeke</div>
<div class="signature">每天进步一点</div>
</div>
</li>
<li class="im-li">
<img class="im-li-img" alt="" src="${basePath}images/face.jpg">
<div class="im-li-text">
<div class="nickname">zzw</div>
<div class="signature">最近爱好js了</div>
</div>
</li>
<li class="im-li">
<img class="im-li-img" alt="" src="${basePath}images/face.jpg">
<div class="im-li-text">
<div class="nickname">kobe</div>
<div class="signature">牛哄哄</div>
</div>
</li>
</ul>
</div>
<div class="friend-group" data-idx="1" data-name="myworker">
<span class="iconfont icon-nextpage"></span> 我的同事
</div>
<div class="im-ul-box" data-name="myworker">
<ul class="im-group-ul collapse">
<li class="im-li">
<img class="im-li-img" alt="" src="${basePath}images/face.jpg">
<div class="im-li-text">
<div class="nickname">zeke</div>
<div class="signature">每天进步一点</div>
</div>
</li>
<li class="im-li">
<img class="im-li-img" alt="" src="${basePath}images/face.jpg">
<div class="im-li-text">
<div class="nickname">zzw</div>
<div class="signature">最近爱好js了</div>
</div>
</li>
<li class="im-li">
<img class="im-li-img" alt="" src="${basePath}images/face.jpg">
<div class="im-li-text">
<div class="nickname">kobe</div>
<div class="signature">牛哄哄</div>
</div>
</li>
</ul>
</div>
</div>
<script type="text/javascript">
$(function(){
$("#nickname").text("${nickname}");
var wsMsg = parent.wsMsg;//console.log(wsMsg);
var users = wsMsg.users;//console.log(users);
/* 更新用户自己的信息 */
updateInfo(wsMsg.from);
/* 更新好友列表 */
updateFirendGroup(users);
});
/* 更新用户信息方法 */
function updateInfo(name){
$("#nickname").text(name);
}
/* 更新好友列表方法 */
function updateFirendGroup(users){
$(".im-group-ul").html("");
var myfriends = '';
var myworkers = '';
if(users && users.length > 0){
for(var i=0; i<users.length; i++){
for(u in users[i]){
var key = u;
var user = FY.strToJson(users[i][u]);
if(user && user.group){
if(user.group == "myfriend"){
myfriends += friendTempate(key,user);
}else if(user.group == "myworker"){
myworkers +=friendTempate(key,user);
}
}
}
}
//更新列表
var $uls = $(".im-ul-box");
for(var g=0; g<$uls.length ; g++){
var $ul = $uls.eq(g);
var gname = $ul.data("name");
if(gname == "myfriend"){
$ul.find("ul").html(myfriends).fadeIn();
}else if(gname == "myworder"){
$ul.find("ul").html(myworkers).fadeIn();
}
}
//绑定事件
$(".im-li").each(function(){
$(this).bind("dblclick",function(evt){
var nickname = $(this).find(".nickname").text().trim();
var signature = $(this).find(".signature").text().trim();
var user = {
nickname: nickname,
signature: signature
};
parent.showMsgWindow(user);
});
});
}
}
//好友列表模板
function friendTempate(key,user){
return "<li class='im-li'>"+
" <img class='im-li-img' alt='' src='${basePath}images/face.jpg'>"+
" <div class='im-li-text'>"+
" <div class='nickname'>"+key+"</div>"+
" <div class='signature'>"+user.signature+"</div>"+
" </div>"+
"</li>";
}
/* 折叠分组 */
$(".friend-group").on("click",function(evt){
var name = $(this).data("name");
var idx=0;
$(".friend-group").each(function(){
if($(this).data("name") == name){
return false;
}
idx++;
});
firendsCollapse(idx);
});
/* 统一折叠好友分组的方法 */
function firendsCollapse(idx){
var $uls = $(".im-ul-box");
var $groups = $(".friend-group");
var clsName = "group-collapse";
var $group = $groups.eq(idx);
var $group_ul = $uls.eq(idx).find("ul");
$group.toggleClass(clsName);
if($group.hasClass(clsName)){
$group.children("span").removeClass("iconfont icon-nextpage")
.addClass("iconfont icon-xla");
$uls.eq(idx).css({"display":"block"});
$group_ul.slideDown("fast");
}else{
$group.children("span").removeClass("iconfont icon-xla")
.addClass("iconfont icon-nextpage");
$uls.eq(idx).css({"display":"block"});
$group_ul.slideUp("fast");
}
}
</script>
chat/chat-msg-template.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
pageContext.setAttribute("basePath", basePath);
%>
<link rel="stylesheet" type="text/css" href="${basePath}css/bootstrap/bootstrap.css">
<link rel="stylesheet" type="text/css" href="${basePath}css/bootstrap/bootstrap-theme.css">
<link rel="stylesheet" type="text/css" href="${basePath}css/bootstrap/fileinput.min.css">
<link rel="stylesheet" type="text/css" href="${basePath}js/layer.2.4/layui.css">
<script type="text/javascript" src='${basePath}js/jquery.min.js'></script>
<script type="text/javascript" src='${basePath}js/bootstrap/bootstrap.min.js'></script>
<script type="text/javascript" src='${basePath}js/layer.2.4/layer.js'></script>
<script type="text/javascript" src='${basePath}js/fy.utils.js'></script>
<style>
body,html {
padding: 0;
margin: 0;
}
.im-header {
width:100%;
height: 65px;
background: #F9f9f9;
border-bottom:1px solid #eee;
}
.im-header .im-face-box,.im-info-box {
float: left;
}
.im-header .im-face-box {
width: 80px;
height: 54px;
border-radius: 54px;
padding: 5px 5px 5px 20px;
}
.im-header .im-face-box>img {
width: 54px;
height: 54px;
border-radius: 54px;
}
.im-header .im-info-box {
width:205px;
margin-left: 10px;
overflow: hidden;
}
.im-header .im-info-box p {
font-size: 12px;
line-height: 20px;
height: 20px;
padding: 2px;
margin: 2px;
}
/* 消息显示框样式 */
.im-content {
}
.im-content #msg-show,#msg-input,#msg-btns {
width:100%;
}
.im-content #msg-show {
height:160px;
padding:5px;
overflow-y: auto;
}
.im-content #msg-show .msg-item {
height:auto;
}
#msg-show .msg-item .msg-title,.msg-content {
min-height:40px;
}
#msg-show .msg-item .msg-content {
padding:5px;
margin-bottom:10px;
}
#msg-show .msg-title .mt-face {
width:32px;
height:32px;
border-radius:32px;
margin-right:5px;
}
#msg-show .msg-title .mt-face,.mt-name {
float:left;
}
#msg-show .msg-title .mt-name {
line-height:35px;
font-size:14px;
}
#msg-show .msg-title .mt-name span {
padding-left:5px;
}
/* 消息输入框格式 */
.im-content #msg-input {
height:60px;
position: absolute;
bottom:40px;
padding:5px;
border-top:5px solid #eee;
overflow-y: auto;
word-wrap: break-word;
outline:none;
}
.im-content #msg-btns {
height:40px;
position: absolute;
bottom:0;
padding-right:5px;
padding-top:2px;
}
.im-content #msg-btns button {
font-size:12px !important;
}
</style>
<div class="im-header">
<div class="im-face-box">
<img alt="" src="${basePath}images/face.jpg" width="100%">
</div>
<div class="im-info-box">
<p id="nickname">zeke</p>
<p id="signature">PQ 实验田,来搞一个自己的聊天工具</p>
</div>
</div>
<div class="im-content">
<div id="msg-show"></div>
<div id="msg-input" contenteditable="true"> </div>
<div id="msg-btns" >
<button type="button" id="msg-send" class="btn btn-success btn-xs pull-right">
<i class="iconfont icon-qtsfkuan"></i> 发 送
</button>
</div>
</div>
<script type="text/javascript">
$(function(){
/* 打开窗口光标设置在输入框 */
$("#msg-input").focus();
});
</script>