websocket session共享

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/athur666/article/details/82904080

单机运行

用户a通过服务器进入房间room,用户b也通过房间进入room,用户之间是通过session来通话的,所以session直接存储在集合中就可以了。

因为session存储在一台服务器的集合中,所以每次发送消息的时候,直接发给房间内的所有人的session就可以了。

多机运行

假如有两台服务器,用户a通过服务器A进入了房间room,用户b通过服务器B也进入了房间room,由于session是用集合存储在各自的服务器中的,所以这种情况下,用户a发的消息只能通过服务器A,但是服务器A中是没有用户b的session的,所以当用户a发送消息的时候,用户b就收不到用户a发送的消息了。

实现共享session

那如何在多台服务器之间实现websocket session共享,其实就是session存在一个公共的地方,但是存在哪里呢,想过用redis存储session,但是session不能转为string,也不能序列化,所以就存不进去。

之后发现redis有个订阅发布的功能,然后我用这个算是变相实现了,思路如下:

当用户a通过服务器A进入房间room,然后将用户存入redis的房间room中,用户b通过服务器B进入房间room,也将用户b存入redis的房间room中,并且都订阅房间room.将各自的session还是存储在各自的服务器中,

然后发消息的时候,发布消息到redis的room中,然后监听redis中的room房间,当有消息发送的时候,判断redis的房间中是否存在本地用户,如果存在则发送给当前服务器对应的用户。

在本机用两个tomcat配置不同的端口号模拟启动,访问测试成功,效果如下:

RedisConfig  这里没有配置JedisConnectionFactory,因为在application.yml中配置集群(windows集群配置参考这篇博客https://www.cnblogs.com/tommy-huang/p/6240083.html)时会自动注入连接工厂。

配置监听容器RedisMessageListenerContainer,用于监听redis发布的消息。

package com.test;/*

*/

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.*;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

import java.util.HashSet;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<?,?> getRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){
      RedisTemplate redisTemplate=  new RedisTemplate<>();
      redisTemplate.setConnectionFactory(redisConnectionFactory);
      redisTemplate.setKeySerializer(new StringRedisSerializer());
      redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }

    @Bean
    public RedisMessageListenerContainer initRedisContainer(@Autowired RedisConnectionFactory redisConnectionFactory){

        RedisMessageListenerContainer container=new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        return container;
    }
}

SocketRest

实现MessageListener接口,重写onMessage方法,接收redis消息并发送。
方法container.addMessageListener(socketRest,topic); socketRest为监听者,topic为监听的房间号。

每当第一个用户进入房间后,就为该房间增加一个监听者,用来监听该房间的消息。

package com.test;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import org.springframework.stereotype.Component;



@ServerEndpoint("/websocket/{roomId}/{username}")
@Component
public class SocketRest implements MessageListener{

    private static final Logger log=LoggerFactory.getLogger(SocketRest.class);
    @Autowired
    private  RedisUtil redisUtil;
    //用户对应session
    private static Map<Object,Session> UserSessions=new ConcurrentHashMap<>();
    //房间号对应SocketRest
    private static  Map<String,SocketRest> sockets=new ConcurrentHashMap<>();
    @Autowired
    private  RedisMessageListenerContainer container;

    private SocketRest socketRest;
    private String roomName;

    public SocketRest(){
     ApplicationContext context = ApplicationContextRegister.getApplicationContext();
     redisUtil=context.getBean(RedisUtil.class);
     container=context.getBean(RedisMessageListenerContainer.class);

    }

    @OnOpen
    public void connect(@PathParam("roomId") String roomId, @PathParam("username")String username, Session session) throws Exception{
        System.out.println("a user is connected");
        //加上前缀room
        roomName="room"+roomId;
        //判断房间是否存在
        if(!redisUtil.sGet("rooms").contains(roomName)){
            log.info("当前房间不存在");
            sendmessageToOne("当前房间不存在",session);
            session.close();
            return;
        }
        //判断用户是否存在

        //判断其他房间是否存在该用户,存在则踢出
        Set<Object> set=redisUtil.sGet("rooms");
        Set<Object> tes=new HashSet(set);
        tes.remove(roomName);
        if(tes.size()>0){
         for (Object room:tes){
          Set<Object>  roomuser= redisUtil.sGet(room.toString());
            for(Object o:roomuser){
                System.out.println("roomuser: "+o.toString());
                if(o.toString().equals(username)){
                Session  ses = UserSessions.get(username);
                    sendMsg(username,"你已再其他房间登陆",ses);
                    ses.close();
                    UserSessions.remove(username);
                    return;
                }
            }
         }
        }
        System.out.println(" --------------");
        //判断当前用户是否存在该房间

        //如果不存在则添加该用户到房间
        redisUtil.sSet(roomName,username);
        //增加用户和session对应集合
        if(!UserSessions.containsKey(username)){
            UserSessions.put(username,session);
            sendMsg(username,"进入直播间",session);
        }
        Topic topic=new ChannelTopic(roomName);
        
        if(!sockets.containsKey(roomName)){
            sockets.put(roomName,this);
        }
        //加入监听消息,为每个房间创建一个监听者
        container.addMessageListener(sockets.get(roomName),topic);
        log.info("当前用户:"+username+" 加入房间"+roomName+" 人数:"+redisUtil.sGetSetSize(roomName));
    }

    /**
     * 断开连接
     * @param username  用户id
     * @param session   用户sesison
     */
    @OnClose
    public void disConnect(@PathParam("username") String username, Session session) {
        try {
            //移除对应的session
            UserSessions.remove(username);
            for(Object s:UserSessions.keySet()){
                System.out.println("我是房间中剩余的人:"+s);
            }
            //移除redis房间中人
            redisUtil.setRemove(roomName,username);
            session.close();
        }catch (Exception e){
            log.info("disConnect Exception");
        }
        log.info("a client has disconnected!");
    }

    /**
     *  关闭房间
     *  @param roomId  房间号
     *  @param username  用户id
     */
    public  void closeLiveRoom(@PathParam("roomId") String roomId,@PathParam("username") String username){
        try {
            //移除redis中的房间
            Topic topic=new ChannelTopic("room"+roomId);
            Set<Object> set=redisUtil.sGet(roomId);
            //获取交集,移除当前服务器存在的session
            set.retainAll(UserSessions.keySet());
            for(Object userkey:set){
                UserSessions.remove(userkey);
            }
            //取消订阅房间
            container.removeMessageListener(socketRest,topic);
        }catch (Exception e){
            log.info("closeLiveRoom exception");
        }

    }
    /**
     发送消息
     */
    @OnMessage
    public void sendMsg(@PathParam("username") String username,String msg, Session session){
        try {
            HashMap  message=new HashMap();
            message.put("msg",msg);
            message.put("username",username);
            if(redisUtil.sGet(roomName).size()==1){
               HashMap map=new HashMap();
               map.put("msg",msg);
               map.put("username",username);
                sendmessageToOne(JSON.toJSONString(map),session);
                return;
            }
          redisUtil.convertAndSend(roomName,JSON.toJSONString(message));
          //  broadcast(roomName,message,username);
        }catch (Exception e){
            e.printStackTrace();
            log.info("sendMsg exception:"+e);
            return;
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }
    /**
     * 发给一个人
     */
    private static void sendmessageToOne(String msg, Session session) {
        try {
            session.getAsyncRemote().sendText(msg);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     *   发给所有人
     * @param roomName
     * @param msg
     * @param username
     * @throws Exception
     */
    public  void  broadcast(String roomName, HashMap msg,String username) {
        try {
            if(!redisUtil.sHasKey(roomName,username)){
                System.out.println("当前房间不存在该用户");
                return;
            }
            //获取本服务器房间所有人和redis中存储的房间的人取交集,然后发送消息
            Set<Object> users = redisUtil.sGet(roomName);
            Set<Object> userkey= UserSessions.keySet();
            System.out.println("userkey:"+JSON.toJSONString(userkey));
            HashSet set=new HashSet(userkey);
            set.retainAll(users);
            for(Object user:set){
                System.out.println("user: "+user.toString());
                Session sess=UserSessions.get(user.toString());
                System.out.println("session: "+sess);
                sendmessageToOne(JSON.toJSONString(msg),sess);
            }
        }catch (Exception e){
            e.printStackTrace();
            log.info("bd exception");
        }

    }

    //接收redis中的消息
    @Override
    public void onMessage(Message msgs, byte[] pattern) {
        try {
            //消息内容
            byte[] body=msgs.getBody();
            System.out.println("body: "+body);
            //订阅房间
            String topic=new String(pattern);
            //获取存在的房间中的用户
        String result= new String(body,"utf-8");
            System.out.println("msg: "+result);
            JSONObject js= JSON.parseObject(result);
            String username=js.getString("username");
            String msg=js.getString("msg");
            Set<Object> SessionKeys= UserSessions.keySet();
            HashMap message=new HashMap();
            message.put("msg",msg);
            message.put("username",username);
           broadcast(topic,message,username);
        }catch (Exception e){
            log.info("onMessage exception");
        }
    }

}

application.yml

server:
  port: 8088

logging:
  config: classpath:log4j2.yml
spring:
  profiles:
       active: test
---
spring:
  profiles:  test

  datasource:
        url: jdbc:mysql://localhost:3306/test
        driver-class-name: com.mysql.jdbc.Driver
        data-password: 1234
        data-username: root
  redis:
    cluster:
      nodes: 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003
    database: 0
    host: 127.0.0.1:7001
    port: 6379
    
  session:
  store-type: redis





WebsocketConfig  打包成war用外置tomcat部署时需要注释,不注释会和tomcat自带的websocket注入bean时冲突。
@Configuration
public class WebsocketConfig {

  @Bean
    public ServerEndpointExporter config(){
      return new ServerEndpointExporter();
  }

}

启动时写入房间

@Component
public class StartupRunner implements CommandLineRunner {


    @Autowired
    private RedisUtil redisUtil;

    @Override
    public void run(String... strings) throws Exception{
        if(!redisUtil.sGet("room111").isEmpty()){
            System.out.println("清空数据");
            redisUtil.del("room111");
        }
        redisUtil.sSet("rooms","room111","room222");
       Set<Object> set=redisUtil.sGet("rooms");
       for (Object o:set){
           System.out.println("------>"+o.toString());
       }

    }
}

ApplicationContextRegister   解决websocket中不能注入bean.

package com.test;/*

*/

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextRegister implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

RedisUtil  在网上找的个比较全的工具类。

增加订阅发布方法, channel订阅房间号,message发送给该房间的消息。

public  void convertAndSend(String channel ,Object message){
    redisTemplate.convertAndSend(channel,message);
}
package com.test;/*

*/

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import lombok.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.JedisPoolConfig;

@Component
public class RedisUtil{

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    //========================redis订阅发布===============================
    public  void convertAndSend(String channel ,Object message){
        redisTemplate.convertAndSend(channel,message);
    }
    //=============================common============================
    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key,long time){
        try {
            if(time>0){
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key){
        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key){
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String ... key){
        if(key!=null&&key.length>0){
            if(key.length==1){
                redisTemplate.delete(key[0]);
            }else{
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key){
        return key==null?null:redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key,Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key,Object value,long time){
        try {
            if(time>0){
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            }else{
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @return
     */
    public long incr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     * @param key 键
     * @return
     */
    public long decr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    //================================Map=================================
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key,String item){
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object,Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String,Object> map){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String,Object> map, long time){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key,String item,Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key,String item,Object value,long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item){
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item,double by){
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item,double by){
        return redisTemplate.opsForHash().increment(key, item,-by);
    }

    //============================set=============================
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key){
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key,Object value){
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object...values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key,long time,Object...values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if(time>0) expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key){
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object ...values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===============================list=================================

    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束  0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key,long start, long end){
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */
    public long lGetListSize(String key){
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key,long index){
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index,Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key,long count,Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}


启动类Application

package com.test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

@EnableAutoConfiguration
@SpringBootApplication
public class Application extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder.sources(Application.class);
	}

	public static void main(String[] args) {
		 SpringApplication.run(Application.class, args);
	}

}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>com.test</groupId>
	<artifactId>test</artifactId>
	<version>1.0</version>
	<packaging>war</packaging>

	<name>test</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.9.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<!-- 支持@Slf4j -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<!-- provide 表示打包时可以不添加
			<scope>provided</scope>-->
		</dependency>
		<!-- javaee -->
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-api</artifactId>
			<version>8.0</version>
			<scope>provided</scope>
		</dependency>
		<!-- 导入websocket-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>
		<!-- 导入redis-->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>

		<dependency> <!-- exclude掉spring-boot的默认log配置 -->
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
			<exclusions>
				<!-- 去掉内置logging-->
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.1</version>
		</dependency>

		<!-- springboot 启动配置-->

		<!-- 引入log4j2依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>

		<dependency>  <!-- 加上这个才能辨认到log4j2.yml文件 -->
			<groupId>com.fasterxml.jackson.dataformat</groupId>
			<artifactId>jackson-dataformat-yaml</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<!-- 单元测试-->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.4</version>
        </dependency>

    </dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

用tomcat部署时,如果不是默认的8080,则需要添加端口号,在server.xml中配置,增加端口以及设置编码。

<Connector port="8088" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" URIEncoding="UTF-8" />

设置项目启动路径,docBase即项目名称

 <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
     <Context path="" docBase="whatsup" debug="0" reloadable="true" />
        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>

log4j.yml

Appenders:
    Console:  #输出到控制台
      name: CONSOLE #Appender命名
      target: SYSTEM_OUT
      PatternLayout:
        pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
    RollingFile: # 输出到文件,超过256MB归档
      - name: ROLLING_FILE
        ignoreExceptions: false
        fileName: springboot.log
        filePattern: "/springboot/logs/$${date:yyyy-MM}/springboot -%d{yyyy-MM-dd}-%i.log.gz"
        PatternLayout:
          pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
        Policies:
          SizeBasedTriggeringPolicy:
            size: "256 MB"
        DefaultRolloverStrategy:
          max: 1000
Loggers:
    Root:
      level: info
      AppenderRef:
        - ref: CONSOLE
    Logger: #单独设置某些包的输出级别
      - name: app.com.kenho.mapper #复数加上-
        additivity: false #去除重复的log
        level: trace
        AppenderRef:
          - ref: CONSOLE #复数加上-
          - ref: ROLLING_FILE #复数加上-

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    <script type="text/javascript">
        var ws;
        function  connect() {
            var address = document.getElementById("address").value;
            console.log(address);
            ws = new WebSocket("ws://" + address);

            /*
          *监听三种状态的变化 。js会回调
          */
            ws.onopen = function(message) {
                console.log(message);
            };
            ws.onclose = function (message) {

            };
            ws.onmessage = function (message) {
                showMessage(message.data);
            };
            //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。

            window.onbeforeunload = function () {
                ws.close();
            };
        }
            //关闭连接
            function closeWebSocket(){
                ws.close();
            }
            //发送消息
            function send(){
                var input = document.getElementById("msg").value;
                ws.send(input);
                input.value = "";
            }
            function showMessage(message) {
                console.log("message"+message);
                var  obj= eval('(' + message + ')');
                var br = document.createElement("br")
                var div = document.getElementById("showChatMessage");
                var ms=obj.msg;
               var name=obj.username;
               console.log("ms: "+ms);
               console.log("name "+name);
                if(ms=="进入直播间"){
                    var ms= document.createTextNode(name+ms);
                    div.appendChild(ms);
                }else{
                    var  sendmsg=name+":"+ms;
                    var text = document.createTextNode(sendmsg);
                    div.appendChild(text);
                }
                    div.appendChild(br);
            }
    </script>
</head>
<style>
    .main{
        text-align: center; /*让div内部文字居中*/
        background-color: #fff;
        width: 450px;
        margin: auto;
        position: absolute;
        top: 0;

        border: 1px solid deepskyblue;

    }
</style>
<body>
    <div class="main">
        <div style="width: 450px">
            <input type="text" id="address" name="address" placeholder="请输入地址" style="height:25px;width:350px">
            <input type="button" value="连接" id="connect" onclick="connect()"  name="connect">
        </div>
        <div id="showChatMessage" style="width: 450px;height: 300px;border: 1px solid "></div>
        <div>
            <input style="width: 200px" type="text" size="80" id="msg" name="msg" placeholder="输入聊天内容" />
            <input type="button" onclick="send()" value="发送">
        </div>
    </div>
</body>
</html>

猜你喜欢

转载自blog.csdn.net/athur666/article/details/82904080