├── src ├── main │ ├── resources │ │ ├── application.properties │ │ ├── application-dev.properties │ │ └── static │ │ │ └── index.html │ └── java │ │ └── online │ │ └── wenbei │ │ └── webchat │ │ ├── enums │ │ └── ChatType.java │ │ ├── WebchatApplication.java │ │ ├── constance │ │ └── CacheKeys.java │ │ ├── controller │ │ └── PageController.java │ │ ├── util │ │ ├── MD5Util.java │ │ ├── ResultData.java │ │ └── JsonUtil.java │ │ ├── entity │ │ └── Chat.java │ │ ├── service │ │ ├── UserService.java │ │ └── BaseRedisService.java │ │ ├── listener │ │ ├── SocketHandshake.java │ │ └── SocketHandler.java │ │ └── configuration │ │ └── WebSocketConfiguration.java └── test │ └── java │ └── online │ └── wenbei │ └── webchat │ └── WebchatApplicationTests.java ├── .gitignore ├── pom.xml ├── mvnw.cmd └── mvnw /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=dev 2 | server.port=8099 3 | server.servlet.context-path=/webchat 4 | spring.mvc.view.suffix=.html 5 | spring.mvc.static-path-pattern=/** 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/enums/ChatType.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.enums; 2 | 3 | /** 4 | * @Author: cityuu#163.com 5 | * @Date: 2019-08-29 11:55 6 | * @version: v1.0 7 | * @Description: 会话类型 8 | */ 9 | public enum ChatType { 10 | 11 | SYSTEM_CHAT,//系统会话,不可展示 12 | USER_CHAT,//用户会话 13 | NOTICE_CHAT,//通知会话 14 | FORCED_LOGOUT//强制下线 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/WebchatApplication.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class WebchatApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(WebchatApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/online/wenbei/webchat/WebchatApplicationTests.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class WebchatApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/constance/CacheKeys.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.constance; 2 | 3 | /** 4 | * @Author: cityuu#163.com 5 | * @Date: 2019-08-29 11:30 6 | * @version: v1.0 7 | * @Description: 8 | */ 9 | public class CacheKeys { 10 | 11 | public static final String DOMAIN = "webchat"; 12 | 13 | 14 | /** 15 | * 获取单个用户1对1会话的Key 16 | * @param code 17 | * @return 18 | */ 19 | public static final String getChatMapKey(String code){ 20 | return DOMAIN+":p2p:"+code; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/controller/PageController.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | /** 7 | * @Author: cityuu#163.com 8 | * @Date: 2019-09-09 12:52 9 | * @version: v1.0 10 | * @Description: 11 | */ 12 | @Controller 13 | @RequestMapping("/") 14 | public class PageController { 15 | 16 | 17 | @RequestMapping("/") 18 | public String login(){ 19 | return "index"; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/util/MD5Util.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.util; 2 | 3 | import org.springframework.util.DigestUtils; 4 | 5 | /** 6 | * @Author: cityuu#163.com 7 | * @Date: 2019-08-29 11:40 8 | * @version: v1.0 9 | * @Description: 10 | */ 11 | public class MD5Util { 12 | 13 | //盐,用于混交md5 14 | private static final String slat = "webchat@2019"; 15 | 16 | /** 17 | * 生成md5 18 | * @return 19 | */ 20 | public static String getMD5(String str) { 21 | String base = str +"/"+slat; 22 | String md5 = DigestUtils.md5DigestAsHex(base.getBytes()); 23 | return md5; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | #redis 2 | spring.redis.host=172.18.1.7 3 | #spring.redis.database=2 4 | spring.redis.port=6379 5 | spring.redis.lettuce.pool.max-idle=50 6 | # 连接池最大连接数(使用负值表示没有限制) 7 | spring.redis.lettuce.pool.max-active=8 8 | # 连接池最大阻塞等待时间(使用负值表示没有限制) 9 | spring.redis.lettuce.pool.max-wait=-1 10 | # 连接池中的最小空闲连接 11 | spring.redis.lettuce.pool.min-idle=0 12 | 13 | #MQ的配置 14 | rocketmq.name-server=172.18.1.28:9876 15 | rocketmq.producer.group=webchat-group 16 | rocketmq.producer.send-message-timeout=300000 17 | rocketmq.producer.compress-message-body-threshold=4096 18 | rocketmq.producer.max-message-size=4194304 19 | rocketmq.producer.retry-times-when-send-async-failed=0 20 | rocketmq.producer.retry-next-server=true 21 | rocketmq.producer.retry-times-when-send-failed=2 -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/entity/Chat.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.entity; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * @Author: cityuu#163.com 11 | * @Date: 2019-08-29 11:59 12 | * @version: v1.0 13 | * @Description: 消息结构 14 | */ 15 | @Data 16 | @ToString 17 | @Accessors(chain = true) 18 | public class Chat { 19 | 20 | private Integer chatType;//会话类型 21 | 22 | private String text;//消息内容 23 | 24 | private Integer messageType;//消息类型 1.文本、2.图片 3.文件 25 | 26 | private Date sendTime;//消息发送时间 27 | 28 | /** 29 | * 文本类消息构建 30 | * @param chatType 31 | * @param messageType 32 | * @param text 33 | * @return 34 | */ 35 | public static Chat textBuilder(Integer chatType,Integer messageType,String text){ 36 | return new Chat().setChatType(chatType) 37 | .setMessageType(messageType) 38 | .setSendTime(new Date()) 39 | .setText(text); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/service/UserService.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.service; 2 | 3 | import online.wenbei.webchat.constance.CacheKeys; 4 | import online.wenbei.webchat.util.MD5Util; 5 | import online.wenbei.webchat.util.ResultData; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.util.StringUtils; 9 | 10 | /** 11 | * @Author: cityuu#163.com 12 | * @Date: 2019-08-29 11:27 13 | * @version: v1.0 14 | * @Description: 15 | */ 16 | 17 | @Slf4j 18 | @Service("userService") 19 | public class UserService extends BaseRedisService { 20 | 21 | //@Resource 22 | //private RocketMQTemplate rocketMQTemplate; 23 | /** 24 | * 通过识别码登录的用户 25 | * @param code 26 | * @param type 0 发起端 1被聊端 27 | * @return 28 | * @throws Exception 29 | */ 30 | public ResultData login(String code,Integer type, String deviceId) throws Exception { 31 | 32 | Object value = this.hget(CacheKeys.getChatMapKey(code),type.toString()); 33 | 34 | //如果用户的MD5值不相等,则踢掉之前会话的人 35 | if(!StringUtils.isEmpty(value)){ 36 | if(!MD5Util.getMD5(value.toString()).equals(MD5Util.getMD5(deviceId))){ 37 | //先给该用户发一条强制下线的消息 然后让新客户端连接 38 | //rocketMQTemplate.convertAndSend(Chat.textBuilder(ChatType.FORCED_LOGOUT.ordinal(),1,"您已经被强制下线!")); 39 | this.hset(CacheKeys.getChatMapKey(code),type.toString(),deviceId); 40 | } 41 | return ResultData.error("您已经登录过了,无需重复登录!"); 42 | }else{ 43 | this.hset(CacheKeys.getChatMapKey(code),type.toString(),deviceId); 44 | return ResultData.success(); 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/listener/SocketHandshake.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.listener; 2 | 3 | import online.wenbei.webchat.util.JsonUtil; 4 | import eu.bitwalker.useragentutils.UserAgent; 5 | import org.springframework.http.server.ServerHttpRequest; 6 | import org.springframework.http.server.ServerHttpResponse; 7 | import org.springframework.http.server.ServletServerHttpRequest; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.web.socket.WebSocketHandler; 10 | import org.springframework.web.socket.server.HandshakeInterceptor; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.util.Map; 14 | 15 | /** 16 | * @Author: cityuu#163.com 17 | * @Date: 2019-08-29 13:51 18 | * @version: v1.0 19 | * @Description: 20 | */ 21 | 22 | @Component 23 | public class SocketHandshake implements HandshakeInterceptor { 24 | 25 | 26 | @Override 27 | public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map map) throws Exception { 28 | if (serverHttpRequest instanceof ServletServerHttpRequest) { 29 | HttpServletRequest servletRequest = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest(); 30 | // 从session中获取到当前登录的用户信息. 作为socket的账号信息. session的的WEBSOCKET_USERNAME信息,在用户打开页面的时候设置. 31 | 32 | System.out.println(JsonUtil.parseToJSON(servletRequest.getHeaderNames())); 33 | UserAgent userAgent = UserAgent.parseUserAgentString(servletRequest.getHeader("User-Agent")); 34 | String device = servletRequest.getHeader("device"); 35 | 36 | map.put("ws_user",device!=null?device:userAgent.getOperatingSystem().getName().replaceAll(" ","")+userAgent.getId()); 37 | } 38 | return true; 39 | } 40 | 41 | @Override 42 | public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) { 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/configuration/WebSocketConfiguration.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.configuration; 2 | 3 | import online.wenbei.webchat.listener.SocketHandler; 4 | import online.wenbei.webchat.listener.SocketHandshake; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.web.socket.config.annotation.EnableWebSocket; 10 | import org.springframework.web.socket.config.annotation.WebSocketConfigurer; 11 | import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; 12 | import org.springframework.web.socket.server.standard.ServerEndpointExporter; 13 | 14 | /** 15 | * @Author: cityuu#163.com 16 | * @Date: 2019-08-29 13:52 17 | * @version: v1.0 18 | * @Description: 19 | */ 20 | 21 | @Slf4j 22 | @Configuration 23 | @EnableWebSocket 24 | public class WebSocketConfiguration implements WebSocketConfigurer { 25 | 26 | 27 | @Autowired 28 | private SocketHandshake handshake; 29 | 30 | @Autowired 31 | private SocketHandler handler; 32 | 33 | /** 34 | * 实现 WebSocketConfigurer 接口,重写 registerWebSocketHandlers 方法,这是一个核心实现方法,配置 websocket 入口,允许访问的域、注册 Handler、SockJs 支持和拦截器。 35 | *

36 | * registry.addHandler()注册和路由的功能,当客户端发起 websocket 连接,把 /path 交给对应的 handler 处理,而不实现具体的业务逻辑,可以理解为收集和任务分发中心。 37 | *

38 | * addInterceptors,顾名思义就是为 handler 添加拦截器,可以在调用 handler 前后加入我们自己的逻辑代码。 39 | *

40 | * setAllowedOrigins(String[] domains),允许指定的域名或 IP (含端口号)建立长连接,如果只允许自家域名访问,这里轻松设置。如果不限时使用”*”号,如果指定了域名,则必须要以 http 或 https 开头。 41 | * 42 | * @param registry 43 | */ 44 | @Override 45 | public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { 46 | //部分 支持websocket 的访问链接,允许跨域 47 | registry.addHandler(handler, "/echo").addInterceptors(handshake).setAllowedOrigins("*"); 48 | //部分 支持websocket 的访问链接,允许跨域 49 | registry.addHandler(handler, "/webchat/echo").addInterceptors(handshake).setAllowedOrigins("*"); 50 | //部分 不支持websocket的访问链接,允许跨域 51 | //registry.addHandler(handler, "/sockjs/echo").addInterceptors(handshake).setAllowedOrigins("*").withSockJS(); 52 | } 53 | 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.7.RELEASE 9 | 10 | 11 | com.suiyueyule 12 | webchat 13 | 0.0.1-SNAPSHOT 14 | webchat 15 | webchat project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | 24 | com.alibaba 25 | fastjson 26 | 1.2.25 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-freemarker 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-websocket 40 | 41 | 42 | 43 | org.projectlombok 44 | lombok 45 | true 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-tomcat 50 | provided 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-test 55 | test 56 | 57 | 58 | 59 | org.apache.commons 60 | commons-pool2 61 | 2.0 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-data-redis 67 | 68 | 69 | eu.bitwalker 70 | UserAgentUtils 71 | 1.21 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-maven-plugin 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/util/ResultData.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.util; 2 | 3 | /** 4 | * @Author: cityuu#163.com 5 | * @Date: 2019-08-29 11:43 6 | * @version: v1.0 7 | * @Description: 8 | */ 9 | public class ResultData { 10 | private static final int SUCCESS_CODE = 0; 11 | private static final int ERROR_CODE = 1; 12 | private static final String SUCCESS_MSG = "success"; 13 | private static final String ERROR_MSG = "error"; 14 | private int code; 15 | private String msg; 16 | private Object data; 17 | private Long timestamp; 18 | 19 | private ResultData() { 20 | } 21 | 22 | public static ResultData success() { 23 | ResultData resultData = new ResultData(); 24 | resultData.setCode(0); 25 | resultData.setMsg("success"); 26 | resultData.setTimestamp(System.currentTimeMillis()); 27 | return resultData; 28 | } 29 | 30 | public static ResultData success(Object data) { 31 | ResultData resultData = new ResultData(); 32 | resultData.setCode(0); 33 | resultData.setMsg("success"); 34 | resultData.setTimestamp(System.currentTimeMillis()); 35 | resultData.setData(data); 36 | return resultData; 37 | } 38 | 39 | public static ResultData success(Object data, String msg) { 40 | ResultData resultData = new ResultData(); 41 | resultData.setMsg(msg); 42 | resultData.setCode(0); 43 | resultData.setTimestamp(System.currentTimeMillis()); 44 | resultData.setData(data); 45 | return resultData; 46 | } 47 | 48 | public static ResultData error() { 49 | ResultData resultData = new ResultData(); 50 | resultData.setCode(1); 51 | resultData.setTimestamp(System.currentTimeMillis()); 52 | resultData.setMsg("error"); 53 | return resultData; 54 | } 55 | 56 | public static ResultData error(String msg) { 57 | ResultData resultData = new ResultData(); 58 | resultData.setCode(1); 59 | resultData.setMsg(msg); 60 | resultData.setTimestamp(System.currentTimeMillis()); 61 | return resultData; 62 | } 63 | 64 | public static ResultData result(Integer code, Object data, String msg) { 65 | ResultData resultData = new ResultData(); 66 | resultData.setCode(code); 67 | resultData.setData(data); 68 | resultData.setMsg(msg); 69 | resultData.setTimestamp(System.currentTimeMillis()); 70 | return resultData; 71 | } 72 | 73 | public static ResultData result(Integer code, String msg) { 74 | return result(code, "", msg); 75 | } 76 | 77 | public int getCode() { 78 | return this.code; 79 | } 80 | 81 | public void setCode(int code) { 82 | this.code = code; 83 | } 84 | 85 | public String getMsg() { 86 | return this.msg; 87 | } 88 | 89 | public void setMsg(String msg) { 90 | this.msg = msg; 91 | } 92 | 93 | public Object getData() { 94 | return this.data; 95 | } 96 | 97 | public void setData(Object data) { 98 | this.data = data; 99 | } 100 | 101 | public Long getTimestamp() { 102 | return this.timestamp; 103 | } 104 | 105 | public void setTimestamp(Long timestamp) { 106 | this.timestamp = timestamp; 107 | } 108 | 109 | 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/util/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.alibaba.fastjson.serializer.SerializeConfig; 6 | import com.alibaba.fastjson.serializer.SerializerFeature; 7 | import com.alibaba.fastjson.serializer.SimpleDateFormatSerializer; 8 | import com.alibaba.fastjson.serializer.SimplePropertyPreFilter; 9 | import org.springframework.util.StringUtils; 10 | 11 | import java.util.*; 12 | 13 | /** 14 | * @Author: cityuu#163.com 15 | * @Date: 2018-12-10 16:11 16 | * @version: v1.0 17 | * @Description: JSON 格式化工具 18 | */ 19 | public class JsonUtil { 20 | 21 | private static SerializeConfig mapping = new SerializeConfig(); 22 | private static SimpleDateFormatSerializer formatSerializer = new SimpleDateFormatSerializer("yyyy-MM-dd HH:mm:ss"); 23 | private static final SerializerFeature[] features; 24 | 25 | static { 26 | features = new SerializerFeature[]{SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteNullNumberAsZero, SerializerFeature.WriteNullBooleanAsFalse, SerializerFeature.WriteNullStringAsEmpty}; 27 | } 28 | 29 | public JsonUtil() { 30 | } 31 | 32 | public static T parseToClass(String jsonStr, Class clazz) { 33 | T javaObject = (T) JSON.toJavaObject(JSON.parseObject(jsonStr), clazz); 34 | return javaObject; 35 | } 36 | 37 | public static String parseToJSON(Object object) { 38 | return JSON.toJSONString(object, configMapping(), new SerializerFeature[0]); 39 | } 40 | 41 | public static String parseUnicodeJSON(Object object) { 42 | return JSON.toJSONString(object, new SerializerFeature[]{SerializerFeature.BrowserCompatible}); 43 | } 44 | 45 | public static String parseJSONString(Object object, SimplePropertyPreFilter filter) { 46 | return JSON.toJSONString(object, filter, new SerializerFeature[0]); 47 | } 48 | 49 | public static String parseJSONString(Object object, String formatDate) { 50 | return JSON.toJSONString(object, configMapping(formatDate), new SerializerFeature[0]); 51 | } 52 | 53 | public static List parseJSONList(String jsonString, Class pojoClass) { 54 | return JSONObject.parseArray(jsonString,pojoClass); 55 | } 56 | 57 | public static Map getMapJSON(String jsonString) { 58 | JSONObject jsonObject = JSONObject.parseObject(jsonString); 59 | Map parseJSONMap = new LinkedHashMap(jsonObject); 60 | return parseJSONMap; 61 | } 62 | 63 | public static Map getByteMapByJSON(String jsonString) { 64 | JSONObject jsonObject = JSONObject.parseObject(jsonString); 65 | Map parseJSONMap = new LinkedHashMap(jsonObject); 66 | return parseJSONMap; 67 | } 68 | 69 | 70 | private static SerializeConfig configMapping() { 71 | mapping.put(Date.class, formatSerializer); 72 | return mapping; 73 | } 74 | 75 | private static SerializeConfig configMapping(String format) { 76 | SerializeConfig mapping = new SerializeConfig(); 77 | mapping.put(Date.class, new SimpleDateFormatSerializer(format)); 78 | return mapping; 79 | } 80 | 81 | public static SimplePropertyPreFilter filterProperty(Class className, String... param) { 82 | SimplePropertyPreFilter filter = new SimplePropertyPreFilter(className, param); 83 | return filter; 84 | } 85 | 86 | /** 87 | * 字符串处理成Long list 88 | * @param values 89 | * @return 90 | */ 91 | public static List convertToLongList(String values){ 92 | List userIdsLongArr = new ArrayList<>(); 93 | String[] userIdsStringArr = values.split(","); 94 | for(String ss:userIdsStringArr){ 95 | if(!StringUtils.isEmpty(ss)){ 96 | userIdsLongArr.add(Long.valueOf(ss)); 97 | } 98 | } 99 | return userIdsLongArr; 100 | } 101 | 102 | /** 103 | * String list 104 | * @param values 105 | * @return 106 | */ 107 | public static List convertToStringList(String values){ 108 | List userIdsLongArr = new ArrayList<>(); 109 | String[] userIdsStringArr = values.split(","); 110 | for(String ss:userIdsStringArr){ 111 | if(!StringUtils.isEmpty(ss)){ 112 | userIdsLongArr.add(ss); 113 | } 114 | } 115 | return userIdsLongArr; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 妙传Web 5 | 6 | 8 | 9 | 10 | 11 | 18 | 19 | 20 | 21 |

22 |
23 |
24 |
25 | 对话列表 26 | 27 |
28 |
29 |
30 | 31 | 32 | 33 |
34 |
35 | 36 |
37 | 发送给谁? 38 |
39 |
40 | 41 |
42 |
43 | 44 | 45 | 46 | 47 | 138 | 139 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/listener/SocketHandler.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.listener; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.socket.*; 8 | 9 | import java.io.IOException; 10 | import java.util.*; 11 | 12 | /** 13 | * @Author: cityuu#163.com 14 | * @Date: 2019-08-29 15:03 15 | * @version: v1.0 16 | * @Description: 17 | */ 18 | @Slf4j 19 | @Service 20 | public class SocketHandler implements WebSocketHandler { 21 | /** 22 | * 为了保存在线用户信息,在方法中新建一个list存储一下【实际项目依据复杂度,可以存储到数据库或者缓存】 23 | */ 24 | private final static Set SESSIONS = new HashSet(); 25 | 26 | private final static Set msgIdFilter = new LinkedHashSet<>();// 27 | 28 | @Override 29 | public void afterConnectionEstablished(WebSocketSession session) throws Exception { 30 | log.info("链接成功......"); 31 | 32 | String userName = (String) session.getAttributes().get("ws_user"); 33 | 34 | this.sessionFilter(userName); 35 | 36 | SESSIONS.add(session); 37 | 38 | if (userName != null) { 39 | JSONObject obj = new JSONObject(); 40 | // 统计一下当前登录系统的用户有多少个 41 | obj.put("count", SESSIONS.size()-1); 42 | obj.put("currentUserId",session.getId()); 43 | obj.put("currentUserName",userName); 44 | users(obj,session.getId()); 45 | session.sendMessage(new TextMessage(obj.toJSONString())); 46 | } 47 | } 48 | //过滤掉以前相同设备但是没有断开的缓存 49 | private void sessionFilter(String currentUserName) { 50 | for(WebSocketSession history:SESSIONS){ 51 | String userName = (String) history.getAttributes().get("ws_user"); 52 | if(userName.equals(currentUserName)){ 53 | SESSIONS.remove(history); 54 | } 55 | } 56 | } 57 | 58 | private boolean msgIdFilter(String msgId){ 59 | if(msgIdFilter.contains(msgId)){ 60 | log.info("此消息被拒绝!"); 61 | return false; 62 | } 63 | //给个人 64 | msgIdFilter.add(msgId); 65 | if(msgIdFilter.size()>=200){ 66 | msgIdFilter.clear(); 67 | } 68 | return true; 69 | } 70 | 71 | @Override 72 | public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { 73 | log.info("处理要发送的消息"); 74 | JSONObject msg = JSON.parseObject(message.getPayload().toString()); 75 | JSONObject obj = new JSONObject(); 76 | if (msg.getInteger("type") == 1) { 77 | //给所有人 78 | obj.put("msg", msg.getString("msg")); 79 | sendMessageToUsers(new TextMessage(obj.toJSONString())); 80 | } else { 81 | if(!this.msgIdFilter(msg.getString("msgId"))){ 82 | log.error("重复消息,不与发送!"); 83 | return; 84 | } 85 | 86 | String to = msg.getString("to"); 87 | obj.put("msg", msg.getString("msg")); 88 | obj.put("msgId", msg.getString("msgId")); 89 | obj.put("fromSessionId", msg.getString("fromSessionId")); 90 | obj.put("fromUserName", msg.getString("fromUserName")); 91 | obj.put("to",to); 92 | obj.put("type", msg.getIntValue("type")); 93 | sendMessageToUser(to, new TextMessage(obj.toJSONString())); 94 | } 95 | } 96 | 97 | @Override 98 | public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { 99 | if (session.isOpen()) { 100 | session.close(); 101 | } 102 | log.info("链接出错,关闭链接......"); 103 | SESSIONS.remove(session); 104 | } 105 | 106 | @Override 107 | public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { 108 | log.info("链接关闭......" + closeStatus.toString()); 109 | SESSIONS.remove(session); 110 | } 111 | 112 | @Override 113 | public boolean supportsPartialMessages() { 114 | return false; 115 | } 116 | 117 | /** 118 | * 给所有在线用户发送消息 119 | * 120 | * @param message 121 | */ 122 | public void sendMessageToUsers(TextMessage message) { 123 | for (WebSocketSession user : SESSIONS) { 124 | try { 125 | if (user.isOpen()) { 126 | user.sendMessage(message); 127 | } 128 | } catch (IOException e) { 129 | e.printStackTrace(); 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * 给某个用户发送消息 136 | * 137 | * @param userName 138 | * @param message 139 | */ 140 | public void sendMessageToUser(String userName, TextMessage message) { 141 | for (WebSocketSession user : SESSIONS) { 142 | if (user.getAttributes().get("ws_user").equals(userName)) { 143 | try { 144 | if (user.isOpen()) { 145 | user.sendMessage(message); 146 | } 147 | } catch (IOException e) { 148 | e.printStackTrace(); 149 | } 150 | return; 151 | } 152 | } 153 | } 154 | 155 | /** 156 | * 将系统中的用户传送到前端 157 | * 158 | * @param obj 159 | */ 160 | private void users(JSONObject obj,String sessionId) { 161 | Set userNames = new HashSet<>(); 162 | for (WebSocketSession webSocketSession : SESSIONS) { 163 | 164 | if(!webSocketSession.getId().equals(sessionId)){ 165 | userNames.add(webSocketSession.getAttributes().get("ws_user").toString()); 166 | } 167 | } 168 | obj.put("userList", userNames); 169 | } 170 | 171 | 172 | } 173 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /src/main/java/online/wenbei/webchat/service/BaseRedisService.java: -------------------------------------------------------------------------------- 1 | package online.wenbei.webchat.service; 2 | 3 | import online.wenbei.webchat.util.JsonUtil; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Scope; 9 | import org.springframework.dao.DataAccessException; 10 | import org.springframework.data.redis.connection.RedisConnection; 11 | import org.springframework.data.redis.core.RedisCallback; 12 | import org.springframework.data.redis.core.RedisTemplate; 13 | import org.springframework.data.redis.core.ZSetOperations; 14 | import org.springframework.data.redis.serializer.RedisSerializer; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.util.Assert; 17 | 18 | import java.util.*; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | /** 22 | * @Author: cityuu#163.com 23 | * @Date: 2018-12-10 15:36 24 | * @version: v1.0 25 | * @Description: Redis 缓存操作接口 26 | */ 27 | @Slf4j 28 | @Service("redisService") 29 | public abstract class BaseRedisService { 30 | 31 | 32 | private static int seconds = 3600 * 24; 33 | 34 | private static String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";// lua脚本,用来释放分布式锁 35 | 36 | 37 | @Qualifier("stringRedisTemplate") 38 | @Autowired 39 | private RedisTemplate redisTemplate; 40 | 41 | private RedisSerializer serializer; 42 | 43 | public class CacheZSet{ 44 | private String value; 45 | 46 | private String score; 47 | 48 | public String getValue() { 49 | return value; 50 | } 51 | 52 | public void setValue(String value) { 53 | this.value = value; 54 | } 55 | 56 | public String getScore() { 57 | return score; 58 | } 59 | 60 | public void setScore(String score) { 61 | this.score = score; 62 | } 63 | } 64 | 65 | 66 | @Bean 67 | @Scope("singleton") 68 | public RedisSerializer initSerializer() { 69 | serializer = redisTemplate.getStringSerializer(); 70 | return serializer; 71 | } 72 | 73 | 74 | 75 | public boolean set(final String key, final String value) throws Exception { 76 | Assert.hasText(key, "Key is not empty."); 77 | boolean result = redisTemplate.execute((RedisCallback) connection -> { 78 | //serializer = redisTemplate.getStringSerializer(); 79 | connection.set(serializer.serialize(key), serializer.serialize(value)); 80 | return true; 81 | }); 82 | return result; 83 | } 84 | 85 | public String get(final String key) throws Exception { 86 | Assert.hasText(key, "Key is not empty."); 87 | String result = redisTemplate.execute((RedisCallback) connection -> { 88 | //serializer = redisTemplate.getStringSerializer(); 89 | byte[] value = connection.get(serializer.serialize(key)); 90 | return serializer.deserialize(value); 91 | }); 92 | return result; 93 | } 94 | 95 | public void del(final String key) throws Exception { 96 | 97 | Assert.hasText(key, "Key is not empty."); 98 | 99 | redisTemplate.execute((RedisCallback) conn -> { 100 | 101 | serializer = redisTemplate.getStringSerializer(); 102 | 103 | return conn.del(serializer.serialize(key)); 104 | }); 105 | } 106 | 107 | 108 | 109 | public boolean expire(final String key, long expire) { 110 | return redisTemplate.expire(key, expire, TimeUnit.MILLISECONDS); 111 | } 112 | 113 | 114 | public boolean expire(long expire, String... key) throws Exception { 115 | for (String k : key) { 116 | expire(k, expire); 117 | } 118 | return true; 119 | } 120 | 121 | 122 | public boolean setList(String key, List list) throws Exception { 123 | Assert.hasText(key, "Key is not empty."); 124 | 125 | String value = JsonUtil.parseToJSON(list); 126 | return set(key, value); 127 | } 128 | 129 | 130 | public List getList(String key, Class clz) throws Exception { 131 | 132 | Assert.hasText(key, "Key is not empty."); 133 | 134 | String json = get(key); 135 | if (json != null) { 136 | List list = JsonUtil.parseJSONList(json, clz); 137 | return list; 138 | } 139 | return null; 140 | } 141 | 142 | 143 | public long lpush(final String key, String... valueArray) throws Exception { 144 | Assert.hasText(key, "Key is not empty."); 145 | 146 | return redisTemplate.execute(new RedisCallback() { 147 | 148 | public Long doInRedis(RedisConnection redisConnection) throws DataAccessException { 149 | byte[] bytes[] = new byte[valueArray.length][]; 150 | for (int i = 0; i < valueArray.length; i++) { 151 | bytes[i] = serializer.serialize(valueArray[i]); 152 | } 153 | 154 | return redisConnection.lPush(serializer.serialize(key), bytes); 155 | } 156 | }); 157 | } 158 | 159 | 160 | public long rpush(final String key, String... valueArray) throws Exception { 161 | Assert.hasText(key, "Key is not empty."); 162 | 163 | return redisTemplate.execute(new RedisCallback() { 164 | 165 | public Long doInRedis(RedisConnection redisConnection) throws DataAccessException { 166 | byte[] bytes[] = new byte[valueArray.length][]; 167 | for (int i = 0; i < valueArray.length; i++) { 168 | bytes[i] = serializer.serialize(valueArray[i]); 169 | } 170 | 171 | return redisConnection.rPush(serializer.serialize(key), bytes); 172 | } 173 | }); 174 | } 175 | 176 | 177 | public void hmset(String key, Map map) throws Exception { 178 | Assert.hasText(key, "Key is not empty."); 179 | 180 | redisTemplate.opsForHash().putAll(key, map); 181 | } 182 | 183 | 184 | public void hset(String key,String field, String value) throws Exception { 185 | redisTemplate.opsForHash().put(key,field,value); 186 | } 187 | 188 | 189 | public long hDel(String key, String... field) throws Exception { 190 | return redisTemplate.opsForHash().delete(key, field); 191 | } 192 | 193 | 194 | public T hget(String key, Class clz) throws Exception { 195 | Assert.hasText(key, "Key is not empty."); 196 | 197 | return redisTemplate.execute((RedisCallback) connection -> { 198 | RedisSerializer serializer = redisTemplate.getStringSerializer(); 199 | 200 | Map result; 201 | 202 | Map data = connection.hGetAll(serializer.serialize(key)); 203 | result = new HashMap<>(); 204 | for (Map.Entry entry : data.entrySet()) { 205 | result.put(serializer.deserialize(entry.getKey()), serializer.deserialize(entry.getValue())); 206 | } 207 | 208 | return JsonUtil.parseToClass(JsonUtil.parseToJSON(result), clz); 209 | }); 210 | } 211 | 212 | 213 | public Object hget(String key, String field) throws Exception { 214 | return redisTemplate.opsForHash().get(key, field); 215 | } 216 | 217 | 218 | public Map hGetAll(String key) throws Exception { 219 | Assert.hasText(key, "Key is not empty."); 220 | 221 | return redisTemplate.execute((RedisCallback>) connection -> { 222 | 223 | RedisSerializer serializer = redisTemplate.getStringSerializer(); 224 | 225 | Map data = connection.hGetAll(serializer.serialize(key)); 226 | Map result = new HashMap<>(); 227 | 228 | for (Map.Entry entry : data.entrySet()) { 229 | result.put(serializer.deserialize(entry.getKey()), serializer.deserialize(entry.getValue())); 230 | } 231 | 232 | return result; 233 | }); 234 | } 235 | 236 | 237 | public String lpop(final String key) throws Exception { 238 | Assert.hasText(key, "Key is not empty."); 239 | 240 | String result = redisTemplate.execute(new RedisCallback() { 241 | 242 | public String doInRedis(RedisConnection connection) throws DataAccessException { 243 | RedisSerializer serializer = redisTemplate.getStringSerializer(); 244 | byte[] res = connection.lPop(serializer.serialize(key)); 245 | return serializer.deserialize(res); 246 | } 247 | }); 248 | return result; 249 | } 250 | 251 | 252 | public List lRange(String key, Long start, Long end) throws Exception { 253 | return redisTemplate.execute(new RedisCallback>() { 254 | 255 | public List doInRedis(RedisConnection redisConnection) throws DataAccessException { 256 | List list = redisConnection.lRange(serializer.serialize(key), start, end); 257 | 258 | List resultList = new ArrayList<>(); 259 | 260 | for (byte[] bytes : list) { 261 | resultList.add(serializer.deserialize(bytes)); 262 | } 263 | return resultList; 264 | } 265 | }); 266 | } 267 | 268 | 269 | 270 | public Long incr(String key) throws Exception { 271 | return redisTemplate.execute(new RedisCallback() { 272 | 273 | public Long doInRedis(RedisConnection redisConnection) throws DataAccessException { 274 | return redisConnection.incr(serializer.serialize(key)); 275 | } 276 | }); 277 | } 278 | 279 | 280 | public Long decr(String key) throws Exception { 281 | return redisTemplate.execute(new RedisCallback() { 282 | 283 | public Long doInRedis(RedisConnection redisConnection) throws DataAccessException { 284 | return redisConnection.decr(serializer.serialize(key)); 285 | } 286 | }); 287 | } 288 | 289 | 290 | 291 | public boolean getLuaLock(String key, Long expire) throws Exception { 292 | return false; 293 | } 294 | 295 | 296 | 297 | public int sadd(String key, String data) { 298 | return redisTemplate.execute(new RedisCallback() { 299 | 300 | public Integer doInRedis(RedisConnection redisConnection) throws DataAccessException { 301 | redisConnection.sAdd(serializer.serialize(key), serializer.serialize(data)); 302 | return 1; 303 | } 304 | 305 | }); 306 | } 307 | 308 | 309 | public int sadd(String key,String [] data) { 310 | return redisTemplate.execute(new RedisCallback() { 311 | 312 | public Integer doInRedis(RedisConnection redisConnection) throws DataAccessException { 313 | for(String value:data) { 314 | redisConnection.sAdd(serializer.serialize(key), serializer.serialize(value)); 315 | } 316 | return data.length; 317 | } 318 | 319 | }); 320 | } 321 | 322 | 323 | public Set sMembers(String key) throws Exception { 324 | return redisTemplate.opsForSet().members(key); 325 | } 326 | 327 | 328 | public Set getRandomSet(String key, Long size) { 329 | return (Set) redisTemplate.opsForSet().distinctRandomMembers(key,size); 330 | 331 | } 332 | 333 | 334 | public Long sRem(String key , String field) { 335 | return redisTemplate.opsForSet().remove(key,field); 336 | } 337 | 338 | 339 | public boolean exists(String key) { 340 | return redisTemplate.hasKey(key); 341 | } 342 | 343 | 344 | public boolean lRem(String key) { 345 | return redisTemplate.delete(key); 346 | } 347 | 348 | 349 | public Long lRem(String key, Object value) { 350 | return redisTemplate.opsForList().remove(key, 0, value); 351 | } 352 | 353 | 354 | public boolean sExistis(String key, Object value) { 355 | return redisTemplate.opsForSet().isMember(key,value); 356 | } 357 | 358 | 359 | public Long llen(String key) throws Exception { 360 | return redisTemplate.opsForList().size(key); 361 | } 362 | 363 | 364 | public void ltrim(String key, long start, long end) throws Exception { 365 | redisTemplate.opsForList().trim(key,start,end); 366 | } 367 | 368 | 369 | public void setNX(String key, String value) { 370 | redisTemplate.execute(new RedisCallback() { 371 | 372 | public Integer doInRedis(RedisConnection redisConnection) throws DataAccessException { 373 | String cmd = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" + 374 | "then\n" + 375 | " return redis.call(\"del\",KEYS[1])\n" + 376 | "else\n" + 377 | " return 0\n" + 378 | "end\n"; 379 | return 1; 380 | } 381 | 382 | }); 383 | } 384 | 385 | 386 | public boolean zadd(String key, String value, Double score) { 387 | if (score == null) { 388 | score = 0.0; 389 | } 390 | Double finalScore = score; 391 | return redisTemplate.execute((RedisCallback) redisConnection -> redisConnection.zAdd(serializer.serialize(key), finalScore, serializer.serialize(value))); 392 | } 393 | 394 | 395 | public Long zadd(String key, Set set) throws Exception { 396 | if(set.size()<=0){ 397 | return Long.valueOf(set.size()); 398 | } 399 | 400 | return redisTemplate.opsForZSet().add(key, set); 401 | } 402 | 403 | 404 | public Set zRange(String key, Long start, Long end) throws Exception { 405 | return redisTemplate.opsForZSet().range(key, start, end); 406 | } 407 | 408 | 409 | public Long zRank(String key, String id) throws Exception { 410 | return redisTemplate.opsForZSet().rank(key,id); 411 | } 412 | 413 | 414 | public Long zRevRank(String key, String id) throws Exception { 415 | return redisTemplate.opsForZSet().reverseRank(key,id); 416 | } 417 | 418 | 419 | public Long zremRangeByScore(String key, Double start, Double end) { 420 | return redisTemplate.execute((RedisCallback) redisConnection -> redisConnection.zRemRangeByScore(serializer.serialize(key), start, end)); 421 | 422 | } 423 | 424 | 425 | public Long zrem(String key, String ... value) throws Exception { 426 | return redisTemplate.opsForZSet().remove(key,value); 427 | } 428 | 429 | 430 | public Double zScore(String key, String member) throws Exception { 431 | return redisTemplate.opsForZSet().score(key,member); 432 | } 433 | 434 | 435 | public Long getExpireTime(String key) { 436 | if (key == null) { 437 | return null; 438 | } 439 | return redisTemplate.getExpire(key)* 1000; 440 | } 441 | 442 | 443 | public List zrevrange(String key, Long start, Long end, Class tClass) { 444 | return redisTemplate.execute((RedisCallback>) redisConnection -> { 445 | Set set = redisConnection.zRevRange(serializer.serialize(key), start, end); 446 | List list = new ArrayList<>(); 447 | for (byte[] bytes : set) { 448 | list.add(serializer.deserialize(bytes)); 449 | } 450 | return (List) list; 451 | }); 452 | 453 | } 454 | 455 | 456 | public Set> zrevrangeWithScores(String key, Long start, Long end) throws Exception { 457 | Set> set = redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, start, end); 458 | return (Set>) set; 459 | } 460 | 461 | 462 | public Long zCard(String key) throws Exception { 463 | if(redisTemplate.hasKey(key)) { 464 | return redisTemplate.opsForZSet().zCard(key); 465 | }else{ 466 | return -1L; 467 | } 468 | } 469 | 470 | 471 | public Long zCount(String key, double min, double max) throws Exception { 472 | return redisTemplate.opsForZSet().count(key,min,max); 473 | } 474 | 475 | 476 | } 477 | --------------------------------------------------------------------------------