Java使用websocket实现聊天室简单功能

最近有个项目需要实现视频流的信息交换处理,但是之前一直都没有写过有关的代码,所以就想到了websocket接口。

java创建一个socket非常简单,繁琐的可能是日常的业务信息处理,下面看下一段代码:

import model.User;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Created by alan.luo on 2017/9/10.
 */

@ServerEndpoint(value = "/websocket2")
public class TestWebSocket {

    @OnOpen
    public void onOpen(Session session, EndpointConfig endpointConfig) {
        sendMsg(session,"Welcome back.");

        //push list.
        User user = new User();
        user.setId(Integer.parseInt(session.getId()));

        Map<String,List<String>> map = session.getRequestParameterMap();
        user.setUserName(map.get("userName").get(0));
        user.setSession(session);

        //has client come in,so server must be save the session to the application.class.
        Application.getInstance().putSession(user);
    }

    @OnMessage
    public void onMessage(Session session,String message){
        System.out.println("++++++++++:"+message);

        //if has user push message to the server.then server will each all the client and send message too.
        for (User user:Application.getInstance().getUsers()){
            sendMsg(user.getSession(),user.getUserName() + " say:"+message);
        }

    }

    @OnClose
    public void onClose(Session session, CloseReason closeReason){
        List<User> list = Application.getInstance().getUsers();
        //pop list.
        for (int i = 0;i<list.size();i++){
            if (Integer.parseInt(session.getId()) == list.get(i).getId()){
                list.remove(i);
                Application.getInstance().setUsers(list);
                break;
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable thr) {
        System.out.println("+++++++++onError"+thr.getMessage());
    }

    /**
     * send message to session.
     * @param session
     * @param message
     */
    protected void sendMsg(Session session,String message){
        System.out.println(message);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            session.getBasicRemote().sendText(message+" >"+sdf.format(new Date()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

当有客户端进入,则把客户端保存到application这个实例中,application是自定义的一个类,实现了单例,所以保存进去是最合适的,当有消息进入时,server将从application读取session list 并且遍历全部session and send the message。

Application实例的代码:

import model.User;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by alan.luo on 2017/9/10.
 */
public class Application {

    private List<User> users;
    private static Application app;

    public Application(){
        users = new ArrayList<>();
    }

    public static Application getInstance(){
        if (app == null){
            app = new Application();
        }
        return app;
    }

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    public void putSession(User s){
        this.users.add(s);
    }

}

User实体类的代码:

package model;

import javax.websocket.Session;

/**
 * Created by alan.luo on 2017/9/6.
 */
public class User {
    private int id;

    private String userName;

    private String userPassword;

    private Session session;

    public Session getSession() {
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPasswd) {
        this.userPassword = userPasswd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", userPassword='" + userPassword + '\'' +
                '}';
    }
}

可能会有错误,那是因为需要配置一下web.xml

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/websocket2</url-pattern>
</servlet-mapping>

 

剩下的就是javascript客户端的代码了:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <p><input type="text" id="userName" value="" placeholder="输入名字进入聊天室"></p>
</div>
<div>
    <textarea rows="16" cols="40" id="content"></textarea>
</div>
<div>
    <input type="text" id="message" value="" placeholder="输入内容"/>
    <button type="button" id="send">send</button>
</div>
<div>

    <button type="button" id="connect">connect</button>
    <button type="button" id="destroy">destroy</button>
</div>
<script>

        var connect = document.getElementById("connect");
        var content = document.getElementById("content");
        var message = document.getElementById("message");
        var destroy = document.getElementById("destroy");
        var send = document.getElementById("send");
        var userName = document.getElementById("userName");
        var socket = null;

        connect.addEventListener("click",function () {
            console.log("connect")
            if(userName.value.length <= 2){
                return false;
            }
            socket = new WebSocket("ws://localhost:8080/websocket2?userName="+userName.value);

            socket.onopen = function (p1) {
                console.log("onopen",p1)
            }

            socket.onclose = function (p1) {
                console.log("onclose",p1)
            }
            socket.onerror = function (p1) {
                console.log("onerror",p1)
            }
            socket.onmessage = function (p1) {
                console.log("onmessage",p1)
                content.innerHTML = (content.innerHTML + p1.data + "\n")
            }
        });
        destroy.addEventListener("click",function () {
            console.log("destroy")
            socket.close();
        });
        send.addEventListener("click",function () {
            console.log("send")
            socket.send(message.value);
            message.value = "";
        });


</script>

</body>
</html>

最后上传一张效果图吧:

QQ截图20170910223850

SQL联合多表更新和删除

在 MySQL 3.23 中,你可以使用 LIMIT # 来确保只有给定的记录行数目被更改。
如果一个 ORDER BY 子句被使用(从 MySQL 4.0.0 开始支持),记录行将以指定的次序被更新。这实际上只有连同 LIMIT 一起才有用。

从 MySQL 4.0.4 开始,你也可以执行一个包含多个表的 UPDATE or DELETE 的操作:

UPDATE t1,t2SET t1.price = t2.price
WHERE t1.id = t2.id;

delete FROM `t1` using `t1`
left join `t2` on t1.id= t2.id
where 1 and t2.id is null;

注意:多表 UPDATE 不可以使用 ORDER BY 或 LIMIT。 
第一个多表删除格式从 MySQL 4.0.0 开始被支持。第二个多表删除格式从 MySQL 4.0.2 开始被支持。

仅仅在 FROM 或 USING 子句 之前 列出的表中的匹配记录行被删除。效果就是,你要以从多个表中同时删除记录行,并且同样可以有其它的表用于检索。

在表名后的 .* 仅仅是为了兼容 Access:

DELETE t1,t2 FROM t1,t2,t3 WHERE t1.id=t2.id AND t2.id=t3.id

or

DELETE FROM t1,t2 USING t1,t2,t3 WHERE t1.id=t2.id AND t2.id=t3.id

在上面的情况下,我们仅仅从 t1 和 t2 表中删除匹配的记录行。

如果一个 ORDER BY 子句被使用(从 MySQL 4.0.0 开始支持), 记录行将以指定的次序删除。这实际上只有连同 LIMIT 一起才有用。示例如下:

DELETE FROM t1
WHERE name = ‘jcole’
ORDER BY id DESC
LIMIT 1

这将删除匹配 WHERE 子句的,并且最早被插入(通过 id 来确定)的记录行。

DELETE 语句的LIMIT rows 选项是 MySQL 特有的,它告诉服务器在控制权被返回到客户端之前可被删除的最大记录行数目。这可以用来确保一个特定的 DELETE 命令不会占用太长的时间。你可以简单地重复使用 DELETE 命令,直到被影响的记录行数目小于 LIMIT 值。

从 MySQL 4.0 开始,在 DELETE 语句中可以指定多个表,用以从一个表中删除依赖于多表中的特殊情况的记录行。然而,在一个多表删除中,不能使用 ORDER BY 或 LIMIT。
假设有两个表,tab1,tab2,分别有产品名称和产品价格,现在我想用tab2的价格替换tab1的价格,我写的语句是Update tab1 set tab1.产品价格=tab2.产品价格 where tab1.产品名称=tab2.产品名称
结果出现错误,提示为:列前缀 ‘tab2′ 与查询中所用的表名或别名不匹配。请问应该怎样写,

SQL语句是不支持多表同时更新的。

应该这样写 

update tab1 set tab1.产品价格 = (select tab2.产品价格 from tab2 where tab2.产品名称 = tab1.产品名称) where tabl1.产品名称 in (select tab2.产品名称 from tab2)
后面的where tab1.产品名称 in (select tab2.产品名称 from tab2) 这句保证了如果tab1的产品在tab2没有记录时不会出错。
在 开发中,数据库来回换,而有些关键性的语法又各不相同,这是一件让开发人员很头痛的事情.本文总结了Update语句更新多表时在SQL Server,Oracle,MySQL三种数据库中的用法.我也试了SQLite数据库,都没成功,不知是不支持多表更新还是咋的.

在本例中: 

我们要用表gdqlpj中的gqdltks,bztks字段数据去更新landleveldata中的同字段名的数据,条件是当landleveldata 中的GEO_Code字段值与gdqlpj中的lxqdm字段值相等时进行更新.

SQL Server语法:
UPDATE
{
table_name WITH ( < table_hint_limited > [ …n ] )
| view_name
| rowset_function_limited
}
SET
{ column_name = { expression | DEFAULT | NULL }
| @variable = expression
| @variable = column = expression } [ ,…n ]

{ { [ FROM { < table_source > } [ ,…n ] ]

[ WHERE
< search_condition > ] }
|
[ WHERE CURRENT OF
{ { [ GLOBAL ] cursor_name } | cursor_variable_name }
] }
[ OPTION ( < query_hint > [ ,…n ] ) ]

SQL Server示例:
update a
set a.gqdltks=b.gqdltks,a.bztks=b.bztks
from landleveldata a,gdqlpj b
where a.GEO_Code=b.lxqdm

Oracle语法:
UPDATE    updatedtable
SET (col_name1[,col_name2…])=
(SELECT    col_name1,[,col_name2…]
FROM    srctable [WHERE where_definition])

Oracel 示例:
update landleveldata a
set (a.gqdltks, a.bztks)=
(select b.gqdltks, b.bztks    from gdqlpj b
where a.GEO_Code=b.lxqdm)

MySQL语法:
UPDATE table_references
SET col_name1=expr1 [, col_name2=expr2 …]
[WHERE where_definition]

MySQL 示例:
update landleveldata a, gdqlpj b
set a.gqdltks= b.gqdltks,
a.bztks= b.bztks
where a.GEO_Code=b.lxqdm

registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister

解决方法,新建类并且继承org.apache.commons.dbcp.BasicDataSource类

接着重新close的方法即可。

/**
 * registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister
 * Created by alan.luo on 2017/9/5.
 */

public class BasicDataSourceEx extends BasicDataSource {

    public BasicDataSourceEx(){
        super();
    }


    @Override
    public synchronized void close() throws SQLException {
        DriverManager.deregisterDriver(DriverManager.getDriver(url));
        super.close();
    }
}

 

xml把bean对应的class改成新建的类名就可以了。

<bean id="dataSource" class="com.lanxinbase.system.basic.BasicDataSourceEx" destroy-method="close">
    <property name="driverClassName" value="${db.driver}"/>
    <property name="url" value="${db.url}"></property>
    <property name="username" value="${db.username}"></property>
    <property name="password" value="${db.userpasswd}"></property>
    <property name="initialSize" value="${db.initalsize}"></property>
    <property name="maxActive" value="${db.maxActive}"></property>
    <property name="maxIdle" value="${db.maxIdle}"></property>
    <property name="maxOpenPreparedStatements" value="${db.maxOpens}"></property>
    <property name="maxWait" value="${db.maxWait}"></property>
</bean>

 

解决spring mvc跨域的问题

java开发网站是很繁琐的事情,特别是写前端,每次运行都需要进行编译,所以需要配置跨域访问:

服务端

首先要创建一个CrossDomainFilter类,类继承了Servlet Filter接口,如下代码:

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created by alan.luo on 2017/9/5.
 */
public class CrossDomainFilter extends Compact implements Filter {


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

        try {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;

            // 跨域
            String origin = httpRequest.getHeader("Origin");
            if (origin == null) {
                httpResponse.addHeader("Access-Control-Allow-Origin", "http://localhost/");

            } else {
                httpResponse.addHeader("Access-Control-Allow-Origin", origin);
            }
            httpResponse.addHeader("Access-Control-Allow-Headers", "Origin, x-requested-with, Content-Type, Accept,X-Cookie");
            httpResponse.addHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS,DELETE");
            if ( httpRequest.getMethod().equals("OPTIONS") ) {
                httpResponse.setStatus(HttpServletResponse.SC_OK);
                return;
            }
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {
    }
}

类方法主要是配置response的响应信息,告诉客户端,可以传递什么及使用什么方法过来。

接下来就是配置xml文件,打开web.xml文件,在过滤代码中加入以下代码:

<!-- 允许跨域 -->
<filter>
    <filter-name>CrossDomainFilter</filter-name>
    <filter-class>com.test.com.CrossDomainFilter</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CrossDomainFilter</filter-name>
    <url-pattern>/*</url-pattern><!-- 配置允许跨域访问的的url-pattern -->
</filter-mapping>

到这里服务器就已经配置完毕了。

客户端

客户端直接使用ajax发起请求就可以:

  /**
   * 发起ajax请求
   * @param url 请求地址
   * @param data 请求参数(object)对象
   * @param fnScuss 成功回调的函数
   * @param fnError 失败回调的函数
   * @returns {{get: get, post: post}}
   */
  ajax: function (url, data, fnScuss, fnError) {
      
      var _cacheKey = app.md5(url+JSON.stringify(data));
      var _cache = null;
      var _isCanCache = app.isCanCache(url);
      url = this.constant.DOMAIN + url;

      var http = function (method,header) {
          if (method == null){
              method = "POST";
          }
          if(header == null){
              header = new Object();
          }
     
          $.ajax({
              url: url,
              data: data,
              dataType: "json",
              type: method,
              headers: header,
      beforeSend: function(xhr) {
         xhr.withCredentials = true;
      },
      crossDomain:true,
              success: function (e) {

                  if (typeof fnScuss == "function") {
                      //储存缓存
                      if (e.code == "1" && _isCanCache == true){
                          //app.cache.set(_cacheKey,e)
                      }
                      fnScuss(e);
                  }
              },
              error: function (e) {
                  if (typeof fnError == "function") {
                      fnError(e);
                  }
              }

          })
      };

      return {
          get: function () {
              //缓存判断
              if (_isCanCache == true){
                  _cache = app.cache.get(_cacheKey)
                  if (_cache != null && typeof fnScuss == "function"){
                      fnScuss(_cache);
                      return true;
                  }
              }

              http("GET");
          },
          post: function (isJson) {
              //缓存判断
              if (_isCanCache == true){
                  _cache = app.cache.get(_cacheKey)
                  if (_cache != null && typeof fnScuss == "function"){
                      fnScuss(_cache);
                      return true;
                  }
              }
              var header = new Object();
              if (isJson == true){
                  header['Content-Type'] = "application/json";
              }
              http("POST",header);
          },
          delete:function () {
              http("DELETE");
          },
          put:function () {
              http("PUT");
          }
      }

  },

 

app.event("#getToken", "click", function () {
    app.ajax("/project/api/getToken", {type:"1"}, function (e) {
        app.log(e);
        if (e.code == 1) {
            
        }
    }).post();
});

java web原始的图片上传示例

1.表单上传;

先获取到part对象,然后在通过part对象读取输入流(inputStream),最后把文件写到特定的目录上即可;

表单上传的前端代码:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    resp.setContentType("text/html");
    resp.setCharacterEncoding("utf-8");
    resp.getWriter()
            .append("<html>")
            .append("<head>")
            .append("</head>")
            .append("<body>")
            .append("<h1>")
            .append("文件上传示例")
            .append("</h1>")
            .append("<div style='padding: 20px;width: 300px;box-shadow: 0 0 10px #999;'>" +
                    "<form action='/' method='post' enctype='multipart/form-data'>")
            .append("<p><input type='file' name='file' value=\"\"></p>")
            .append("<p><input type='submit' value='提交'></p>")
            .append("</form></div>")
            .append("</body>")
            .append("</html>");

}

表单上传的后台处理代码:

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    resp.setContentType("application/json");
    resp.setCharacterEncoding("utf-8");

    Part part = req.getPart("file");

    String file = "{";
    file += "\"name\":\"" + part.getName()+"\",\n";
    file += "\"contentType\":\"" + part.getContentType()+"\",\n";
    file += "\"size\":\""+part.getSize()+"\",\n";
    file += "\"url\":\"/upload/"+part.getSubmittedFileName()+"\",\n";
    file += "\"submitFileName\":\""+part.getSubmittedFileName()+"\"}";

    BufferedInputStream bufferedInputStream = new BufferedInputStream(part.getInputStream());
    FileOutputStream outputStream = new FileOutputStream("d:/tmp/"+part.getSubmittedFileName());
    int read;
    while ((read = bufferedInputStream.read()) != -1){
        outputStream.write(read);
    }
    outputStream.close();
    bufferedInputStream.close();


    resp.getWriter()
            .append(file);

}

 

2.js上传;

可以把图片通过FileReader对象读取转换成base64的文字,然后通过ajax上传到服务器,这个稍微简单很多.

服务器获取到上传的文本,通过分割及解密的方式,输出到存储目录中即可.

下面是base64处理的方法:

public void uploadBase64(String fieldName){
   String savePath = "d:/tmp/";
   String base64Data = this.request.getParameter(fieldName);
   this.fileName = "test.jpg";
   this.url = savePath + this.fileName;
   BASE64Decoder decoder = new BASE64Decoder();
   try {
      File outFile = new File(this.getPhysicalPath(this.url));
      OutputStream ro = new FileOutputStream(outFile);
      byte[] b = decoder.decodeBuffer(base64Data);
      for (int i = 0; i < b.length; ++i) {
         if (b[i] < 0) {
            b[i] += 256;
         }
      }
      ro.write(b);
      ro.flush();
      ro.close();
      this.state=this.errorInfo.get("SUCCESS");
   } catch (Exception e) {
      this.state = this.errorInfo.get("IO");
   }
}

 

12345674