目录

  • 一、需求分析
  • 二、业务背景
    • 1.张三要发消息给李四
    • 2.WebSocket实现消息推送流程
  • 三、前后端接口和数据库系统设计
    • 1.用户相关的接口
    • 2.频道相关接口
    • 3.数据库表的设计
  • 四、功能交互实现原理及代码展示
    • 1.输入url访问主页
    • 2.调用检查登陆状态接口
      • 2.1参数ok:true时显示登录信息
    • 3.响应ok:false不包含用户信息显示未登录界面
    • 4.点击登录按钮显示登录框
    • 5.输入账号密码,点击登录按钮调用login接口,创建session
    • 6.参数ok:true表示登陆成功跳转至频道列表页面
    • 7.查找频道信息初始化websocket
      • 7.1查找频道接口
      • 7.2调用初始化websocket方法,筛选频道
    • 8.历史记录和新消息的区别
  • 五、开发过程及结果展示
    • 1.工具类Util类开发Json序列化与数据库操作
    • 2.model层实体类设计与数据库相关联
      • 2.1用户实体类
      • 2.2消息实体类
      • 2.3频道实体类
      • 2.4信息返回实体类
    • 3.service层根据前后端接口编写Servlet
      • 3.1登录功能开发
        • 3.1.1根据前端登录接口调用LoginServlet
        • 3.1.2LoginServlet调用UserDAO操作数据库验证用户合法性
      • 3.2登陆成功后查找频道信息功能
        • 3.2.1前端跳转接口调用ChannelServlet
        • 3.2.2ChannelServlet调用ChannelDAO查询
      • 3.3频道进入后进行消息发送和接收
        • 3.3.1MessageWebsocketServlet关联TestWebSocket
        • 3.3.2调用MessageDAO层操作数据库
      • 3.4退出登录功能
        • 3.4.1前端接口跳转执行LogoutServlet
        • 3.4.2LogoutServlet删除session会话信息并进行更新登陆时间操作
  • 六、项目开发重难点
    • 1.Session会话
    • 2.ajax
    • 4.涉及Web技术
    • 5.认识WebSocket
    • 6.序列化与反序列化
    • 7.JDBC操作
  • 七、项目亮点
    • 1.阻塞队列
    • 2.双重校验锁

一、需求分析

1.登录:同一个浏览器,即使多个标签页,保持相同的session。
2.对应消息频道可以收发消息,不同的消息频道不能收发。
3.历史记录查看,新登陆用户可以查看退出登陆后的消息

二、业务背景

1.张三要发消息给李四

实现
1.客户端点到点发消息
2.服务端转发消息(本项目方法)

使用http协议是不可以的因为
服务端的IP是开放在公网的,所有人都能访问
客户端的IP是不开放的

2.WebSocket实现消息推送流程

一.客户端和服务器端建立连接,只能客户发起请求,双方建立链接。

//客户端js代码
websocket = new WebSocket("ws://localhost:8080/java_chatroom/test/1");

1.1双方建立连接(双方通知打开连接的回调函数执行)
1.2客户端建立连接回调

//连接成功建立的回调方法websocket.onopen = function(event){setMessageInnerHTML("open");}

1.3服务端建立连接回调,同时建立长连接Session

@OnOpenpublic void onOpen(@PathParam("userId") String userId, Session session) {this.userId = userId;System.out.println("打开连接: " + userId);}

二、双方的通信都是全双工,客户端和服务端都可以主动收发消息
2.1客户端发送消息

//发送消息function send(){var message = document.getElementById('text').value;websocket.send(message);}

2.2服务端发送消息

@OnMessagepublic void onMessage(String message, Session session) throws IOException {System.out.println("收到消息! " + userId + ": " + message);session.getBasicRemote().sendText(message);}

建立连接以后,就会有的会话对象,只要有sesiion引用就可以发消息,区别于Servlet的session

三、双方接收消息:被动接受(事件驱动的异步回调)
3.1:客户端接收消息

 //接收到消息的回调方法websocket.onmessage = function(event){setMessageInnerHTML(event.data);}

3.2:服务端接收消息

 @OnMessagepublic void onMessage(String message, Session session) throws IOException {System.out.println("收到消息! " + userId + ": " + message);//session.getBasicRemote().sendText(message);}

三、前后端接口和数据库系统设计

1.用户相关的接口

1.注册

请求:
POST /register
{name: xxx,password: xxx,nickName: "蔡徐坤",signature: "我擅长唱跳rap篮球", }
响应:
HTTP/1.1 200 OK
{ok: 1,reason: xxx
}

2.登录 输入账号密码,点击登录按钮,调用的接口

请求:
POST /login
{name: xxx,password: xxx
}
响应:
HTTP/1.1 200 OK
{ok: 1,reason: xxx,userId: xxx,name: xxx,nickName: xxx,signature: xxx
}

3.检查登陆状态 页面初始化,调用的接口

请求:
GET /login
响应:
响应:
HTTP/1.1 200 OK
{ok: 1,userId: xxx,name: xxx,nickName: xxx,signature: xxx
}

注销

请求:
GET /logout
响应:
HTTP/1.1 200 OK
{ok: 1,reason: xxx
}

2.频道相关接口

查找频道

请求:
GET /channel
响应:
HTTP/1.1 200 OK
[{channelId: 1,channelName: xxx},{channelId: 2,channelName: xxx}
]

3.数据库表的设计

user表

create table user (userId int primary key auto_increment,name varchar(50) unique,password varchar(50),nickName varchar(50),   -- 昵称iconPath varchar(2048), -- 头像路径signature varchar(100),lastLogout DateTime -- 上次登录时间
); -- 个性签名

频道表

create table channel (channelId int primary key auto_increment,channelName varchar(50)
);
insert into channel values(null, '体坛赛事');
insert into channel values(null, '娱乐八卦');
insert into channel values(null, '时事新闻');
insert into channel values(null, '午夜情感');

信息表

create table message (messageId int primary key auto_increment,userId int, -- 谁发的channelId int, -- 发到哪个频道中content text, -- 消息内容sendTime DateTime default now()    -- 发送时间
);insert into message values (null, 1, 1, 'hehe1', now());
insert into message values (null, 1, 1, 'hehe2', now());
insert into message values (null, 1, 1, 'hehe3', now());

四、功能交互实现原理及代码展示

1.输入url访问主页

2.调用检查登陆状态接口

2.1参数ok:true时显示登录信息

3.响应ok:false不包含用户信息显示未登录界面

4.点击登录按钮显示登录框

5.输入账号密码,点击登录按钮调用login接口,创建session

请求:
POST /login
{name: xxx,password: xxx
}
响应:
HTTP/1.1 200 OK
{ok: 1,reason: xxx,userId: xxx,name: xxx,nickName: xxx,signature: xxx
}

6.参数ok:true表示登陆成功跳转至频道列表页面

7.查找频道信息初始化websocket

         app.getChannels();app.initWebSocket();

7.1查找频道接口

请求:
GET /channel
响应:
HTTP/1.1 200 OK
[{channelId: 1,channelName: xxx},{channelId: 2,channelName: xxx}
]

7.2调用初始化websocket方法,筛选频道

         if('WebSocket' in window){this.websocket = new WebSocket("ws://localhost:8080/java_chatroom/message/" + this.user.userId);console.log("link success")}else{alert('Not support websocket')}//连接发生错误的回调方法this.websocket.onerror = function () {alert("连接发生错误!");};//连接成功建立的回调方法this.websocket.onopen = function (event) {console.log("连接建立成功");}//接收到消息的回调方法this.websocket.onmessage = function (event) {let message = JSON.parse(event.data);app.messages.push(message);//筛选频道app.curChannelMessages = app.messages.filter((message, i) => {if (message.channelId == app.curChannelId) {return true;}return false;});}//连接关闭的回调方法this.websocket.onclose = function () {console.log("服务器断开连接");}

8.历史记录和新消息的区别

五、开发过程及结果展示

1.工具类Util类开发Json序列化与数据库操作

public class Util {//序列化private  static  final ObjectMapper M = new ObjectMapper();//数据库连接池private  static final MysqlDataSource DS = new MysqlDataSource();//初始化static {//设置json序列化/反序列化的日期格式DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");M.setDateFormat(df);DS.setURL("jdbc:mysql://localhost:3306/java_chatroom");DS.setUser("root");DS.setPassword("111111");DS.setUseSSL(false);DS.setCharacterEncoding("UTF-8");//解决插入修改数据,如果是中文,乱码的问题}/*** json序列化:java对象转换为json字符串*/public static String serialize(Object o){try {return M.writeValueAsString(o);} catch (JsonProcessingException e) {throw new AppException("json序列化失败:"+o, e);}}/*** 反序列化:把json字符串转换为java对象*/public static <T> T deserialize(String s, Class<T> c){try {return M.readValue(s, c);} catch (JsonProcessingException e) {//如果出现这个异常,一般都是json字符串中的键,在class中没有找到对应的属性throw new AppException("json反序列化失败", e);}}public static <T> T deserialize(InputStream is, Class<T> c){try {return M.readValue(is, c);} catch (IOException e) {//如果出现这个异常,一般都是json字符串中的键,在class中没有找到对应的属性throw new AppException("json反序列化失败", e);}}/*** 获取数据库连接*/public static Connection getConnection(){try {return DS.getConnection();} catch (SQLException e) {throw new AppException("获取数据库连接失败", e);}}/*** 释放jdbc资源*/public static void close(Connection c, Statement s, ResultSet r){try {if(r != null) r.close();if(s != null) s.close();if(c != null) c.close();} catch (SQLException e) {throw new AppException("释放数据库资源出错", e);}}public static void close(Connection c, Statement s){close(c, s, null);}public static void main(String[] args) {//测试json序列化Map<String, Object> map = new HashMap<>();map.put("ok", true);map.put("d", new Date());System.out.println(serialize(map));//测试数据库连接: 需要把init.sql在cmd执行,初始化数据库,表,数据System.out.println(getConnection());}
}

2.model层实体类设计与数据库相关联

2.1用户实体类

//数据库使用,前后端ajax,session保存时基于对象和二进制数据转换(这里要实现串行化接口)
@Getter
@Setter
@ToString
public class User extends Response implements Serializable {private static final Long serialVersionUID = 1L;private Integer userId;private String name;private String password;private String nickName;private String iconPath;private String signature;private java.util.Date lastLogout;}

2.2消息实体类

@Setter
@Getter
@ToString
public class Message {private Integer messageId;private Integer userId;private Integer channelId;private String content;private java.util.Date sendTime;//接收客户端发送的消息,转发到所有客户端的消息,需要昵称private String nickName;
}

2.3频道实体类

@ToString
@Getter
@Setter
public class Channel {private Integer channelId;private String channelName;}

2.4信息返回实体类

@Setter
@Getter
@ToString
public class Response {//当前接口响应是否操作成功private boolean ok;//操作失败时,前端要展示的错误信息private String reason;private Object data;//保存业务数据
}

3.service层根据前后端接口编写Servlet

1.请求url,对应Servlet的路径
2.一次init,多次service根据请求方法调用doxxx方法,

3.1登录功能开发

3.1.1根据前端登录接口调用LoginServlet
@WebServlet("/login")
public class LoginServlet extends HttpServlet {//检测登陆状态@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("UTF-8");resp.setCharacterEncoding("UTF-8");resp.setContentType("application/json");//响应的数据:根据接口文档,user类中都包含了约定的字段User u = new User();//获取当前请求的session,并再获取用户信息,如果获取不到,返回ok:falseHttpSession session = req.getSession(false);if(session != null){User get = (User) session.getAttribute("user");if(get != null){//已经登录,并获取到用户信息u = get;u.setOk(true);resp.getWriter().println(Util.serialize(u));return;}}u.setOk(false);//其实不用设置,该字段为boolean,默认就是falseu.setReason("用户未登陆");//3 返回响应数据: 从响应对象获取输出流,打印输出到响应体bodyresp.getWriter().println(Util.serialize(u));}//登录接口@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("UTF-8");resp.setCharacterEncoding("UTF-8");resp.setContentType("application/json");//响应的数据:根据接口文档,user类中都包含了约定的字段User u = new User();try {//1.解析请求数据:根据接口文档,需要使用反序列化操作User input = Util.deserialize(req.getInputStream(), User.class);//2.业务处理: 数据库验证账号密码,如果验证通过,创建session,保存用户信息//根据账号查询用户User query = UserDAO.queryByName(input.getName());if(query == null){throw new AppException("用户不存在");}if(!query.getPassword().equals(input.getPassword())){throw new AppException("账号或密码错误");}//账号密码验证成功HttpSession session = req.getSession();session.setAttribute("user", query);u = query;//构造操作成功正常返回数据:ok:true, 业务字段u.setOk(true);}catch (Exception e){e.printStackTrace();//构造操作失败的错误信息 ok;false reason:错误信息。u.setOk(false);//自定义异常,自己抛,为中文信息,可以给用户看if(e instanceof AppException){u.setReason(e.getMessage());}else{//非自定义异常,英文信息,转一下u.setReason("未知的错误,请联系管理员");}}//3.返回响应数据: 从响应对象获取输出流,打印输出到响应体bodyresp.getWriter().println(Util.serialize(u));}
}
3.1.2LoginServlet调用UserDAO操作数据库验证用户合法性
public static User queryByName(String name) {Connection c = null;PreparedStatement ps = null;ResultSet rs = null;//定义返回数据User u = null;try {//1 获取数据库连接Connectionc = Util.getConnection();//2 通过Connection+sql创建操作命令对象StatementString sql = "select * from user where name=?";ps = c.prepareStatement(sql);//3 执行sql: 执行前替换占位符ps.setString(1, name);rs = ps.executeQuery();//4 如果是查询操作,处理结果集while (rs.next()){//移动到下一行,有数据返回trueu = new User();//设置结果集字段到用户对象的属性中u.setUserId(rs.getInt("userId"));u.setName(name);u.setPassword(rs.getString("password"));u.setNickName(rs.getString("nickName"));u.setIconPath(rs.getString("iconPath"));u.setSignature(rs.getString("signature"));java.sql.Timestamp lastLogout = rs.getTimestamp("lastLogout");u.setLastLogout(new Date(lastLogout.getTime()));}return u;}catch (Exception e){throw new AppException("查询用户账号出错", e);}finally {//5 释放资源Util.close(c, ps, rs);}}

3.2登陆成功后查找频道信息功能

3.2.1前端跳转接口调用ChannelServlet
@WebServlet("/channel")
public class ChannelServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("UTF-8");resp.setCharacterEncoding("UTF-8");resp.setContentType("application/json");Response response = new Response();try{//查询所有频道列表返回List<Channel> list = ChannelDAO.query();response.setOk(true);response.setData(list);//ok:true, data: [{}, {}]}catch (Exception e){e.printStackTrace();//目前,前端的实现,在后端报错,要返回空的List//改造前端为解析ok, reason//参考LoginServlet改造:response.setReason(e.getMessage());//ok:false, reason: ""}resp.getWriter().println(Util.serialize(response));}
}
3.2.2ChannelServlet调用ChannelDAO查询
public class ChannelDAO {public static List<Channel> query() {Connection c = null;PreparedStatement ps = null;ResultSet rs = null;//定义返回数据List<Channel> list = new ArrayList<>();try {//1 获取数据库连接Connectionc = Util.getConnection();//2 通过Connection+sql创建操作命令对象StatementString sql = "select * from channel";ps = c.prepareStatement(sql);//3 执行sql: 执行前替换占位符rs = ps.executeQuery();//4 如果是查询操作,处理结果集while (rs.next()){//移动到下一行,有数据返回trueChannel channel = new Channel();//设置属性channel.setChannelId(rs.getInt("channelId"));channel.setChannelName(rs.getString("channelName"));list.add(channel);}return list;}catch (Exception e){throw new AppException("查询频道列表出错", e);}finally {//5 释放资源Util.close(c, ps, rs);}}
}

3.3频道进入后进行消息发送和接收

3.3.1MessageWebsocketServlet关联TestWebSocket
@ServerEndpoint("/message/{userId}")
public class MessageWebsocket {@OnOpenpublic void onOpen(@PathParam("userId") Integer userId,Session session) throws IOException {//1.把每个客户端的session都保存起来,之后转发消息到所有客户端要用MessageCenter.addOnlineUser(userId, session);//2.查询本客户端(用户)上次登录前的消息(数据库查)List<Message> list = MessageDAO.queryByLastLogout(userId);//3.发送当前用户在上次登录后的消息for(Message m : list){session.getBasicRemote().sendText(Util.serialize(m));}System.out.println("建立连接:"+userId);}@OnMessagepublic void onMessage(Session session,String message){//1.遍历保存的所有session,每个都发送消息
//        MessageCenter.sendMessage(message);MessageCenter.getInstance().addMessage(message);//2.消息还要保存在数据库:// (1)反序列化json字符串为message对象Message msg = Util.deserialize(message, Message.class);// (2)插入数据库int n = MessageDAO.insert(msg);System.out.printf("接收到消息:%s\n", message);}@OnClosepublic void onClose(@PathParam("userId") Integer userId){//1.本客户端关闭连接,要在之前保存的session集合中,删除MessageCenter.delOnlineUser(userId);//2.建立连接要获取用户上次登录以后的消息,所以关闭长连接就是代表用户退出//更新用户的上次登录时间int n = UserDAO.updateLastLogout(userId);System.out.println("关闭连接");}@OnErrorpublic void onError(@PathParam("userId") Integer userId, Throwable t){System.out.println("出错了");MessageCenter.delOnlineUser(userId);t.printStackTrace();//和关闭连接的操作一样}}
3.3.2调用MessageDAO层操作数据库
public static int insert(Message msg) {Connection c = null;PreparedStatement ps = null;try{c = Util.getConnection();String sql = "insert into message values(null,?,?,?,?)";ps = c.prepareStatement(sql);ps.setInt(1, msg.getUserId());ps.setInt(2, msg.getChannelId());ps.setString(3, msg.getContent());ps.setTimestamp(4, new Timestamp(System.currentTimeMillis()));return ps.executeUpdate();}catch (Exception e){throw new AppException("保存消息出错", e);}finally {Util.close(c, ps);}}public static List<Message> queryByLastLogout(Integer userId) {Connection c = null;PreparedStatement ps = null;ResultSet rs = null;//定义返回数据List<Message> list = new ArrayList<>();try {//1 获取数据库连接Connectionc = Util.getConnection();//2 通过Connection+sql创建操作命令对象StatementString sql = "select m.*,u.nickName from message m join user u on u.userId=m.userId where m.sendTime>(select lastLogout from user where userId=?)";ps = c.prepareStatement(sql);//3 执行sql: 执行前替换占位符ps.setInt(1, userId);rs = ps.executeQuery();//4 如果是查询操作,处理结果集while (rs.next()){//移动到下一行,有数据返回trueMessage m = new Message();//获取结果集字段,设置对象属性m.setUserId(userId);m.setNickName(rs.getString("nickName"));m.setContent(rs.getString("content"));m.setChannelId(rs.getInt("channelId"));list.add(m);}return list;}catch (Exception e){throw new AppException("查询用户["+userId+"]的消息出错", e);}finally {//5 释放资源Util.close(c, ps, rs);}}
}

3.4退出登录功能

3.4.1前端接口跳转执行LogoutServlet
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("UTF-8");resp.setCharacterEncoding("UTF-8");resp.setContentType("application/json");HttpSession session = req.getSession(false);if(session != null){User user = (User) session.getAttribute("user");if(user != null){//用户已登录:删除session中保存的用户信息(注销)session.removeAttribute("user");//注销成功,返回ok: trueResponse r = new Response();r.setOk(true);resp.getWriter().println(Util.serialize(r));return;}}//用户未登陆Response r = new Response();r.setReason("用户未登陆,不允许访问");resp.getWriter().println(Util.serialize(r));}
}
3.4.2LogoutServlet删除session会话信息并进行更新登陆时间操作
public static int updateLastLogout(Integer userId) {Connection c = null;PreparedStatement ps = null;try{c = Util.getConnection();String sql = "update user set lastLogout=? where userId=?";ps = c.prepareStatement(sql);ps.setTimestamp(1, new Timestamp(System.currentTimeMillis()));ps.setInt(2, userId);return ps.executeUpdate();}catch (Exception e){throw new AppException("修改用户上次登录时间出错", e);}finally {Util.close(c, ps);}}

六、项目开发重难点

1.Session会话

Session:登陆后,注销前或超时前都是一个会话,用来解决未登录的敏感资源访问问题
代码:
登陆时:账号密码验证通过后request.getSession();
登陆后,都可以获取到Session对象

2.ajax

异步请求回调(等请求的过程中干别的)
过程:
1.HTTP请求数据:路径,请求方法,请求数据
2.通过路径调用后端Servlet中的相关类,找到对应的请求方法doPost
3.对象序列化为json字符串
4.异步回掉函数,不是傻乎乎的等

//接口:
请求:
POST /login
{name: xxx,password: xxx
}
响应:
HTTP/1.1 200 OK
{ok: 1,reason: xxx,userId: xxx,name: xxx,nickName: xxx,signature: xxx
}
//前端ajax代码——登录功能
loginAccount() {console.log("login");$.ajax({url: 'login',type: 'post',contentType: 'application/json',data: JSON.stringify({name: app.login.inputUsername,password: app.login.inputPassword,}),success: function(data, status) {if (!data.ok) {alert('登陆失败! ' + data.reason);app.login.isLogin = false;return;}app.user.userId = data.userId;app.user.name = data.name;app.user.nickName = data.nickName;app.user.signature = data.signature;app.login.isLogin = true;app.login.showLoginDialog = false;app.getChannels();app.initWebSocket();}})},

4.涉及Web技术

1.开发流程:开发——打包——部署——运行——验证
打包:maven package
部署:复制war文件到tomcat/webapps下
运行:运行tomcat自动解压webapps目录下的war文件,每个文件夹就是一个web项目(应用上下文路径)
2.IDEA部署Tomcat应用上下文路径保持一致。

5.认识WebSocket

服务端主动向客户端发送。

消息推送方法:

轮询方式:客户端定时向服务端发送ajax请求,服务器接收到请求后马上返回消息并关闭连接。
优点:后端程序编写比较容易。
缺点:TCP的建立和关闭操作浪费时间和带宽,请求中有大半是无用,浪费带宽和服务器资源。
实例:适于小型应用。
长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息
并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
优点:在无消息的情况下不会频繁的请求,耗费资源小。
缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。
实例:WebQQ、Hi网页版、Facebook IM。
长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用
xhr请求,服务器端就能源源不断地往客户端输入数据。
优点:消息即时到达,不发无用请求;管理起来也相对方便。
缺点:服务器维护一个长连接会增加开销,当客户端越来越多的时候,server压力大!
实例:Gmail聊天
webSocket:HTML5 WebSocket设计出来的目的就是取代轮询和长连接,使客户端浏览器具备像C/S
框架下桌面系统的即时通讯能力,实现了浏览器和服务器全双工通信,建立在TCP之上,虽然
WebSocket和HTTP一样通过TCP来传输数据,但WebSocket可以主动的向对方发送或接收数据,就像
Socket一样;并且WebSocket需要类似TCP的客户端和服务端通过握手连接,连接成功后才能互相通
信。
优点:双向通信、事件驱动、异步、使用ws或wss协议的客户端能够真正实现意义上的推送功能。
缺点:少部分浏览器不支持。
示例:社交聊天(微信、QQ)、弹幕、多玩家玩游戏、协同编辑、股票基金实时报价、体育实况更
新、视频会议/聊天、基于位置的应用、在线教育、智能家居等高实时性的场景。

6.序列化与反序列化

public class Util {//序列化private  static  final ObjectMapper M = new ObjectMapper();//数据库连接池private  static final MysqlDataSource DS = new MysqlDataSource();//初始化static {//设置json序列化/反序列化的日期格式DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");M.setDateFormat(df);DS.setURL("jdbc:mysql://localhost:3306/java_chatroom");DS.setUser("root");DS.setPassword("111111");DS.setUseSSL(false);DS.setCharacterEncoding("UTF-8");//解决插入修改数据,如果是中文,乱码的问题}/*** json序列化:java对象转换为json字符串*/public static String serialize(Object o){try {return M.writeValueAsString(o);} catch (JsonProcessingException e) {throw new AppException("json序列化失败:"+o, e);}}/*** 反序列化:把json字符串转换为java对象*/public static <T> T deserialize(String s, Class<T> c){try {return M.readValue(s, c);} catch (JsonProcessingException e) {//如果出现这个异常,一般都是json字符串中的键,在class中没有找到对应的属性throw new AppException("json反序列化失败", e);}}public static <T> T deserialize(InputStream is, Class<T> c){try {return M.readValue(is, c);} catch (IOException e) {//如果出现这个异常,一般都是json字符串中的键,在class中没有找到对应的属性throw new AppException("json反序列化失败", e);}}
}

7.JDBC操作

1.占位符替换:使用变量,对象中的属性
2.查询操作,返回的结果集,多行数据转换为list,一行数据转换为一个对象

七、项目亮点

1.阻塞队列

2.双重校验锁

web聊天室项目开发过程及重难点整理相关推荐

  1. 【软件测试】使用selenium工具对Web聊天室项目进行UI自动化测试

    文章目录 用户注册功能 注册成功 注册失败 测试套件 HTML测试报告 异常截图 用户登陆注册功能 登陆成功 账号不存在,登陆失败 密码错误,登陆失败 测试套件 HTML测试报告 异常截图 本次自动化 ...

  2. 听书项目开发过程及重难点总结(用户管理)

    文章目录 一.需求分析 二.前后端交互 1. 注册 **前端** **后端** 2.登录 前端 后端 三.项目开发重难点 1.编写一个Servlet 2.session和session会话机制 1)S ...

  3. 聊天室项目开发过程总结

    项目介绍 该项目简单模仿QQ的聊天模块.项目功能模块包括用户注册.登陆.点击私聊和创建群聊 项目详情 为提升用户体验采用Druid数据源.使用Swing技术开发用户界面.对于客户端服务端的通信采用套接 ...

  4. 使用nodejs搭建你自己的专属web聊天室

    前言 前断时间在学习nodejs,自己闲来无事,在网上搜索了一些资料自己搭建了一个属于自己的web聊天室项目.现在把自己的开发过程和心得和大家分享,希望其中涉及到的一些知识对你有用. 项目开源地址:h ...

  5. Django项目--web聊天室

    需求 做一个web聊天室,主要练习前端ajax与后台的交互: 一对一聊天和群组聊天 添加用户为好友 搜索并添加群组 管理员可以审批用户加群请求,群管理员可以有多个,群管理员可以删除,添加禁言群友 与聊 ...

  6. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(一) 之 基层数据搭建,让数据活起来(数据获取)...

    大家好,本篇是接上一篇 ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(零) 前言  ASP.NET SignalR WebIM系列第二篇.本篇会带领大家将 LayIM ...

  7. LayIM 3.9.1与ASP.NET SignalR实现Web聊天室快速入门(四)之ASP.NET SignalR核心功能介绍

    前言 本系列文章特点:使用ASP.NET SignalR和LayIM快速入门对接,实现一对一聊天,群聊,添加聊天群组,查找聊天记录等功能.源代码不包含LayIM的源代码,因为官方并没开源属于收费资源, ...

  8. 御神楽的学习记录之Springboot+netty实现Web聊天室

    文章目录 前言 一.Netty简介 1.介绍 二.Web聊天室实现 1.Idea项目创建 2.java类编写 3.html测试 总结 参考 前言 WebSocket是一种在单个TCP连接上进行全双工通 ...

  9. 使用FastHttpApi构建多人Web聊天室

    为什么80%的码农都做不了架构师?>>>    一般在dotnet core下构建使用web服务应用都使用asp.net core,但通过FastHttpApi组建也可以方便地构建w ...

最新文章

  1. python二叉树遍历算法_分享python实现的二叉树定义与遍历
  2. java虚拟机栈帧_Java虚拟机,运行时栈帧结构
  3. 中医点滴 2 --- 保和丸 + 口气重
  4. 小米扫地机器人 自动关机_小米扫地机器人1S 真的好用吗?
  5. hadoop商业版本选择对比
  6. JVM发生频繁 CMS GC,罪魁祸首是这个参数!
  7. Python的dnspython库使用指南
  8. java quartz 数据库_SpringBoot+Quartz+数据库存储
  9. anaconda来创建python环境
  10. vue-cli 脚手架移除、安装(最新版安装)、检测安装结果 - npm篇
  11. 50 - 算法 -二叉树 - 递归 - LeetCode 101
  12. hdu4405 掷骰子走格子
  13. ubuntu环境下,ubuntu16.04装机到nvdia显卡驱动安装、cuda8安装、cudnn安装
  14. 10 LVS负载均衡群集-NAT
  15. 【图像融合】基于matlab GUI拉普拉斯金字塔+小波变换图像融合【含Matlab源码 857期】
  16. SSM博客 点赞和文章浏览量实现
  17. 尚硅谷JVM笔记(宋红康主讲)
  18. .NetCore获取拼多多平台优惠券
  19. HTML5期末大作业:商城后台网站设计——网站商城后台通用模板(30页) 大学生后台模板网页作品商城网页设计作业模板 学生网页制作源代码下载
  20. dota2api的介绍与使用

热门文章

  1. BUUCTF Url编码
  2. Opencv:如何调用IP摄像头
  3. 通过OpenCv的HoughCircles函数获取图片中的圆形
  4. LeetCode: 173. Binary Search Tree Iterator
  5. 知云文献翻译打不开_学用系列|自带翻译功能的PDF文献阅读器——知云文献翻译3.0...
  6. 响铃:跑错了道的VR,如何知途迷返
  7. 模板的完全特例化和部分特例化
  8. Javaweb_文件上传
  9. 机器学习 绘制决策边界
  10. Python开发【Django】:组合搜索、JSONP、XSS过滤