1. 程式人生 > >Spring+Websocket+Redis

Spring+Websocket+Redis

基於websocket和redis的偽簡單QQ設計

1.先上截圖

2.環境要求

首先,websocket要求是spring4.0以上,網上你找spring redis的例子的話,它配的是spring3.0,要是你把spring-websocket和spring-redis-data結合的話要是版本不一致會出問題。
主要是spring-bean,spring-context,與spring-websocket,spring-redis-data匹配問題造成的。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>org.springframework.samples.service.service</groupId> <artifactId
>
online_qq</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <!-- Generic properties --> <java.version>1.6</java.version> <project.build.sourceEncoding
>
UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- Web --> <jsp.version>2.2</jsp.version> <jstl.version>1.2</jstl.version> <servlet.version>2.5</servlet.version> <!-- Spring --> <spring-framework.version>3.2.3.RELEASE</spring-framework.version> <!-- Hibernate / JPA --> <hibernate.version>4.2.1.Final</hibernate.version> <!-- Logging --> <logback.version>1.0.13</logback.version> <slf4j.version>1.7.5</slf4j.version> <!-- Test --> <junit.version>4.11</junit.version> </properties> <dependencies> <!-- cofig spring jar --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.0.6.RELEASE</version> </dependency> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>4.0.6.RELEASE</version> </dependency> <!-- web socket --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.0.6.RELEASE</version> </dependency> <!-- Spring redis --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.6.RELEASE</version> </dependency> <!-- Other Web dependencies --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>${jstl.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>${servlet.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>${jsp.version}</version> <scope>provided</scope> </dependency> <!-- Spring and Transactions --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring-framework.version}</version> </dependency> <!-- Logging with SLF4J & LogBack --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> <scope>runtime</scope> </dependency> <!-- https://mvnrepository.com/artifact/commons-pool/commons-pool --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.4.2</version> </dependency> <!-- Test Artifacts --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-framework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.0</version> </dependency> <!-- jackson spring通過response向前臺傳送資料是轉成json格式要用到--> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-core-asl</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.0</version> </dependency> <!-- http://mvnrepository.com/artifact/org.json/json --> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20160212</version> </dependency> <!-- https://mvnrepository.com/artifact/net.sf.json-lib/json-lib --> <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> <classifier>jdk15</classifier> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>1.7</source> <target>1.7</target> <encoding>utf8</encoding> </configuration> </plugin> </plugins> </build> </project>

依賴的外掛截圖

spring的application-config.xml配置

<?xml version="1.0" encoding="UTF-8"?>

    <beans 
        xmlns="http://www.springframework.org/schema/beans"  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:p="http://www.springframework.org/schema/p"  
        xmlns:context="http://www.springframework.org/schema/context"  
        xmlns:jee="http://www.springframework.org/schema/jee" 
        xmlns:tx="http://www.springframework.org/schema/tx"  
        xmlns:aop="http://www.springframework.org/schema/aop"  
        xsi:schemaLocation="  
                http://www.springframework.org/schema/beans 
                http://www.springframework.org/schema/beans/spring-beans.xsd  
                http://www.springframework.org/schema/context 
                http://www.springframework.org/schema/context/spring-context.xsd">  

         <context:component-scan base-package="com.hainan.cs"/>  
    <context:property-placeholder location="classpath:redis.properties" />  
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  
            <property name="maxIdle" value="${redis.maxIdle}" />  
            <property name="testOnBorrow" value="${redis.testOnBorrow}" />  
        </bean>  

        <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"  
            p:host-name="${redis.host}" p:port="${redis.port}"   p:pool-config-ref="poolConfig" p:password="${redis.pass}"/>  

        <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">  
            <property name="connectionFactory"   ref="connectionFactory" />  
        </bean>     
        <bean id="userDao" class="com.hainan.cs.dao.UserDaoImp"></bean>  
    </beans>

redis連線設定redis.properties,當然不在這裡面寫,直接在application-config中寫配置資訊也是可以的。redis有桌面編輯軟體,redis desktop manager

# Redis settings  
redis.host=127.0.0.1
redis.port=6379  
redis.pass=
redis.maxIdle=300  
redis.maxActive=600  
redis.maxWait=1000  
redis.testOnBorrow=true  

springmvc配置 mvc-config.xml配置

    <?xml version="1.0" encoding="UTF-8"?>

    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans   
            http://www.springframework.org/schema/beans/spring-beans.xsd   
            http://www.springframework.org/schema/context   
            http://www.springframework.org/schema/context/spring-context.xsd   
            http://www.springframework.org/schema/mvc   
            http://www.springframework.org/schema/mvc/spring-mvc.xsd  ">

         <context:component-scan base-package="com.hainan.cs"/>  

        <!-- 讓springmvc放過存放靜態資源的請求 -->
        <mvc:resources mapping="/resources/**" location="/resources/" />

        <mvc:annotation-driven />

        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <!-- Example: a logical view name of 'showMessage' is mapped to '/WEB-INF/jsp/showMessage.jsp' -->
                <property name="prefix" value="/WEB-INF/view/"/>
                <property name="suffix" value=".jsp"/>
        </bean>

    </beans>

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        version="3.0">

        <display-name>online_qq</display-name>

       <!--
            - Location of the XML file that defines the root application context.
            - Applied by ContextLoaderListener.
        -->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/application-config.xml</param-value>
        </context-param>

        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>


        <!--
            - Servlet that dispatches request to registered handlers (Controller implementations).
        -->
        <servlet>
            <servlet-name>dispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>/WEB-INF/mvc-config.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>

        <servlet-mapping>
            <servlet-name>dispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>

    </web-app>

3.redis操作部分

redis操作部分主要管使用者的註冊,登陸,好友搜尋,好友新增等

RedisGeneratorDao.java

package com.hainan.cs.dao;

    import java.io.Serializable;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.RedisSerializer;

    public class RedisGeneratorDao<k extends Serializable, v extends Serializable> {

        @Autowired
        protected RedisTemplate<k,v> redisTemplate;

        public void setRedisTemplate(RedisTemplate<k,v> redisTemplate){
            this.redisTemplate=redisTemplate;
        }
        protected RedisSerializer<String> getRedisSerializer(){
            return redisTemplate.getStringSerializer();
        }
    }

@Autowired 和@Resource都是依賴注入的方式,當然也可以使用

    ClassPathXmlApplicationContext context=new ClassPaxthXmlApplicationContext("application-config.xml");
    UserDaoImp udi=context.getBean(UserDaoImp.class);

來獲取spring容器中的物件。

UserDaoImp.java

package com.hainan.cs.dao;

    import java.util.List;
    import java.util.Map;
    import java.util.UUID;

    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.dao.DataAccessException;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.core.BoundHashOperations;
    import org.springframework.data.redis.core.BoundListOperations;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.stereotype.Repository;

    import com.hainan.cs.bean.User;
    @Repository(value="userDao")
    public class UserDaoImp extends RedisGeneratorDao<String,String> implements UserDao{
        //新增user Hash hsetNX
        @Override
        public boolean addUser(final User user){
            boolean result=redisTemplate.execute(new RedisCallback<Boolean>(){
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    RedisSerializer<String> serializer=getRedisSerializer();
                    byte[] key=serializer.serialize(user.getId());
                    byte[] name=serializer.serialize("name");
                    byte[] namea=serializer.serialize(user.getUsername());
                    byte[] passworda=serializer.serialize(user.getPassword());
                    byte[] password=serializer.serialize("password");
                    byte[] email=serializer.serialize("email");
                    byte[] emaila=serializer.serialize(user.getEmial());
                    byte[] phone=serializer.serialize("phone");
                    byte[] phonea=serializer.serialize(user.getPhone());
                    byte[] address=serializer.serialize("address");
                    byte[] addressa=serializer.serialize(user.getAdress());
                    connection.hSetNX(key, name,namea);//hash型別的提交<key,hash的key,hash的value>
                    connection.hSetNX(key, password, passworda);
                    connection.hSetNX(key, email, emaila);
                    connection.hSetNX(key, phone, phonea);
                    connection.hSetNX(key, address, addressa);
                    return true;
                }
            });
            return result;
        }
        //獲取使用者資訊
        public Map<String,String> getUser(String userid){
            BoundHashOperations<String,String,String> bhops=redisTemplate.boundHashOps(userid);
            return bhops.entries();
        }
        //新增使用者好友
        @Override
        public void addFriends(final String userid, final String friendid, final String group){
            Boolean result=redisTemplate.execute(new RedisCallback<Boolean>(){
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    RedisSerializer<String> serializer=redisTemplate.getStringSerializer();
                    String key="friend"+userid;
                    byte[] fkey=serializer.serialize(key);
                    byte[] ffriendid=serializer.serialize(friendid);
                    byte[] fgroup=serializer.serialize(group);
                    Boolean r= connection.hSet(fkey, ffriendid, fgroup);
                    return r;
                }

            });
        }
        //刪除使用者好友
        @Override
        public void deleteFriend(User user, String friendsid){
            BoundHashOperations<String,String,String> bhops=redisTemplate.boundHashOps("friend"+user.getId());
            bhops.delete(friendsid);
        }
        //新增使用者好友組
        @Override
        public void addFriendsGroup(final User user, final String group){
            redisTemplate.execute(new RedisCallback<Boolean>(){

                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    RedisSerializer<String> serializer=redisTemplate.getStringSerializer();
                    byte[] key=serializer.serialize("group"+user.getId());
                    byte[] value=serializer.serialize(group);
                    connection.lPush(key, value);
                    return true;
                }

            });
        }
        //新增使用者的列表的hashmap
        @Override
        public void addUserToList(final String key,final String userid,final String username){
            redisTemplate.execute(new RedisCallback<Boolean>(){
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    RedisSerializer<String> serializer=getRedisSerializer();
                    byte[] a=serializer.serialize(key);
                    byte[] c=serializer.serialize(userid);
                    byte[] b=serializer.serialize(username);
                    connection.hSet(a, c, b);
                    return true;
                }
                });
        }
        //獲取使用者列表
        public Map<String,String> getUserList(){
            BoundHashOperations<String,String,String> hlops=redisTemplate.boundHashOps("userlist");
            return hlops.entries();
        }
        //刪除物件
        @Override
        public void deleteUser(String userid){
            redisTemplate.delete(userid);
        }
        //刪除hash中的一條記錄
        @Override
        public void deleteUserFromList(String key,String id){
            BoundHashOperations<String,Object,Object> bhops=redisTemplate.boundHashOps(key);
            bhops.delete(id);
        }
        //獲取使用者的好友分組
        public List<String> getFriendGroups(String userid){
            BoundListOperations<String,String> blops=redisTemplate.boundListOps("group"+userid);
            System.out.println(userid);
            return blops.range(0, blops.size());
        }
        //獲取使用者的好友列表
        public Map<String,String> getFriends(String userid){
            BoundHashOperations<String,String,String> bhops=redisTemplate.boundHashOps("friend"+userid);
            return bhops.entries();
        }

        //測試
        public static void main(String args[]){
            ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring/application-config.xml");
            UserDaoImp udi=context.getBean(UserDaoImp.class);
            User u=new User();
            String id=UUID.randomUUID().toString();//自動生成一個id
            u.setId(id);
            u.setUsername("zhangsan");
            u.setPassword("123456");
            u.setEmial("[email protected]");
            u.setPhone("234556");
            u.setAdress("china haikou");
            udi.addUser(u);
            udi.addUserToList("userlist", id, "zhangsan");
            context.close();
        }
    }

4.websocket通訊部分

主要管,使用者向指定好用傳送訊息,訊息之間的同步

ChatConfig.java

package com.hainan.cs.websocket;

    import javax.annotation.Resource;

    import org.springframework.stereotype.Component;

    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

    import org.springframework.web.socket.config.annotation.EnableWebSocket;

    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;

    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;



    @Component

    @EnableWebSocket

    public class ChatConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{

        @Resource

        private ChatHandler handler;

        @Override

        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

            // TODO Auto-generated method stub

            registry.addHandler(handler, "/ws").addInterceptors(new ChatHandShake());

        }
    }

ChatHandler.java

package com.hainan.cs.websocket;

    import java.util.HashMap;

    import java.util.Map;



    import org.springframework.stereotype.Component;

    import org.springframework.web.socket.CloseStatus;

    import org.springframework.web.socket.WebSocketHandler;

    import org.springframework.web.socket.WebSocketMessage;

    import org.springframework.web.socket.WebSocketSession;



    import net.sf.json.JSONObject;



    @Component

    public class ChatHandler implements WebSocketHandler{



        private  Map<String, WebSocketSession> usermap=new HashMap<String, WebSocketSession>();



        //連線關閉後的操作

        @Override

        public void afterConnectionClosed(WebSocketSession wsession, CloseStatus status) throws Exception {

            System.out.println("使用者"+wsession.getAttributes().get("username")+"已經關閉");

            for(Map.Entry<String, WebSocketSession>entry:usermap.entrySet()){

                String key=entry.getKey();

                WebSocketSession value=entry.getValue();

                if(key.equals(wsession.getAttributes().get("username"))){

                    usermap.remove(key);

                    System.out.println("使用者"+key+"已經移除");

                }

            }

        }

        //再handshake建立連線後,username存到map中了,wsession怎麼拿到的map的值

        @Override

        public void afterConnectionEstablished(WebSocketSession wsession) throws Exception {

            String uname=(String) wsession.getAttributes().get("username");

            System.out.println("使用者"+uname+"已經加入");

            if(usermap.get(uname)==null){

                usermap.put(uname, wsession);

            }

        }



        // message 是前臺傳過來的json資料,包括使用者名稱,和傳送的訊息,要將訊息轉發給所有的其他使用者,重新轉發的話message格式和原來是一樣的

        @Override

        public void handleMessage(WebSocketSession wsession, WebSocketMessage<?> message) throws Exception {

            //TextMessage tm=new TextMessage(message.toString());

            System.out.println("開始傳送訊息"+usermap.size());

            //這是json字串

            String jm=message.getPayload().toString();

            JSONObject json=JSONObject.fromObject(jm);

            System.out.println(jm);

            System.out.println(json.get("to"));

            for(Map.Entry<String,WebSocketSession>entry:usermap.entrySet()){

                String key=entry.getKey();

                WebSocketSession value=entry.getValue();

                //給指定的好友發訊息

                if(key.equals(json.get("to"))){

                    if(value!=null&&value.isOpen()){

                        value.sendMessage(message);

                    }

                }

            }

        }



        @Override

        public void handleTransportError(WebSocketSession wsession, Throwable th) throws Exception {

            if(wsession.isOpen()){

                wsession.close();

            }

            for(Map.Entry<String, WebSocketSession>entry:usermap.entrySet()){

                String key=entry.getKey();

                WebSocketSession value=entry.getValue();

                if(key.equals(wsession.getAttributes().get("username"))){

                    usermap.remove(key);

                    System.out.println("傳輸出錯!使用者"+key+"已經移除");

                }

            }

        }



        @Override

        public boolean supportsPartialMessages() {

            return false;

        }



    }

ChatHandShake.java

package com.hainan.cs.websocket;

    import java.util.Map;

    import org.springframework.http.server.ServerHttpRequest;

    import org.springframework.http.server.ServerHttpResponse;

    import org.springframework.http.server.ServletServerHttpRequest;