Spring+Websocket+Redis
阿新 • • 發佈:2018-12-31
基於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;