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

目录

一、需求分析

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

@OnOpen
    public 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服务端发送消息

@OnMessage
    public 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:服务端接收消息

	@OnMessage
    public 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 {
    //检测登陆状态
    @Override
    protected 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:false
        HttpSession 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,默认就是false
        u.setReason("用户未登陆");
        //3 返回响应数据: 从响应对象获取输出流,打印输出到响应体body
        resp.getWriter().println(Util.serialize(u));
    }
    //登录接口
    @Override
    protected 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.返回响应数据: 从响应对象获取输出流,打印输出到响应体body
        resp.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 获取数据库连接Connection
            c = Util.getConnection();
            //2 通过Connection+sql创建操作命令对象Statement
            String sql = "select * from user where name=?";
            ps = c.prepareStatement(sql);
            //3 执行sql: 执行前替换占位符
            ps.setString(1, name);
            rs = ps.executeQuery();
            //4 如果是查询操作,处理结果集
            while (rs.next()){//移动到下一行,有数据返回true
                u = 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 {
    @Override
    protected 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 获取数据库连接Connection
            c = Util.getConnection();
            //2 通过Connection+sql创建操作命令对象Statement
            String sql = "select * from channel";
            ps = c.prepareStatement(sql);
            //3 执行sql: 执行前替换占位符
            rs = ps.executeQuery();
            //4 如果是查询操作,处理结果集
            while (rs.next()){//移动到下一行,有数据返回true
                Channel 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 {
    @OnOpen
    public 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);
    }

    @OnMessage
    public 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);
    }

    @OnClose
    public void onClose(@PathParam("userId") Integer userId){
        //1.本客户端关闭连接,要在之前保存的session集合中,删除
        MessageCenter.delOnlineUser(userId);
        //2.建立连接要获取用户上次登录以后的消息,所以关闭长连接就是代表用户退出
        //更新用户的上次登录时间
        int n = UserDAO.updateLastLogout(userId);
        System.out.println("关闭连接");
    }

    @OnError
    public 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 获取数据库连接Connection
            c = Util.getConnection();
            //2 通过Connection+sql创建操作命令对象Statement
            String 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()){//移动到下一行,有数据返回true
                Message 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 {

    @Override
    protected 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: true
                Response 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.双重校验锁

  • 6
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值