一:Websocket的使用
websocket使用场景:客户端与服务端建立长连接,通过传输协议完成前后端信息实时共享互通。常用场景有:WEB端简短聊天室功能;后端完成消息推送功能;
注:websocket连接方式与正常的HTTP连接不同,需要特殊的路径和前端代码完成
- maven依赖包:
<!--websocket--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring.version}</version> </dependency> #spring版本: <spring.version>4.3.7.RELEASE</spring.version>
-
springboot中使用websocket
通过核心注解@ServerEndpoint(value="/webSocket")完成前后端的连接完成
- 首先完成ServerEndpointExporter的配置类BEAN:注入ServerEndpointExporter,这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint。要注意,如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理。
@Configuration
public class WebSocketConfig extends ServerEndpointConfig.Configurator{
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 然后完成websocket的连接服务类,其中包含了连接的地址配置、连接成功、失败、单发消息、群发消息的方法配置,废话不多说,直接上代码:
package com.kangce.task.configuration.WebSocketConfig;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.kangce.task.configuration.log.GwsLogger;
import com.kangce.task.entity.PO.BaseMessagePO;
import com.kangce.task.enums.MessageReadFlagEnum;
import com.kangce.task.repository.master.BaseMessageRepositoryMaster;
import com.kangce.task.service.Message.MessageService;
import com.kangce.task.tools.PrimaryKeyGeneratorTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author hengtao.wu
* @Date 2019/6/20 15:08
**/
//客户端连接websocket接口地址
@ServerEndpoint(value = "/websocket/{userId}")
@Component
public class WebSocketServer {
/**
*1. 以下注入的bean是一些JDBC的查询保存类
* 2. 该消息的使用,是通过消息发送的配置表,然后根据配置完成什么时候推送消息,直接在接口中调用发送消息的方法
* 3. 另外数据库中保存了所有的消息数据,存为消息表,表中可以根据需求完成消息的是否发送,是否已读的参数
* 4. 此处做了消息的已读未读,因为接口中调用此类的消息发送方法是针对在线的用户完成消息发送,对于未在线的用户
* 实行用户登录之后,对未读消息进行推送的方式完成。
* 5. 通过一个map来存放所有与服务器建立连接的客户端,key值用userId完成。
* 6. websocket的连接地址中可以接受参数,通过/{param}表示,方法中通过@PathParam(value="userId")获取路径中的参数
*/
private static MessageService messageService;
@Autowired
public void setMessageService(MessageService messageService){
this.messageService = messageService;
}
private static BaseMessageRepositoryMaster baseMessageRepositoryMaster;
@Autowired
public void setBaseMessageRepositoryMaster(BaseMessageRepositoryMaster baseMessageRepositoryMaster){
this.baseMessageRepositoryMaster = baseMessageRepositoryMaster;
}
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
*/
private static int onlineCount = 0;
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private static Map<String,Session> webSocketMap = new HashMap<>();
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(@PathParam(value="userId") String userId, Session session) {
//将当前登录对话,以userId为key值存入
webSocketMap.put(userId,session);
addOnlineCount(); //在线数加1
GwsLogger.info("有新连接加入!当前在线人数为={},", getOnlineCount());
GwsLogger.info("map长度:" + webSocketMap.size());
//推送 未发送 消息
messageService.sendUnsentMsg(userId);
}
/**
* 连接关闭调用的方法*/
@OnClose
public void onClose(@PathParam(value="userId") String userId) {
webSocketMap.remove(userId);
subOnlineCount(); //在线数减1
GwsLogger.info("有一连接关闭!当前在线人数为={}", getOnlineCount());
GwsLogger.info("map长度:" + webSocketMap.size());
}
/**
* @param
* @param error
*/
@OnError
public void onError(Throwable error) {
GwsLogger.error("异常", error.getMessage());
}
/**
* 发送未读消息方法
*
* @param
*/
public void sendUnsentMessage(BaseMessagePO baseMessagePO){
Date date = new Date();
Session session = webSocketMap.get(baseMessagePO.getReceiveUserId());
try {
if(null != session){
JSONArray jsonArray = new JSONArray();
jsonArray = getJSON(baseMessagePO);
session.getBasicRemote().sendText(jsonArray.toJSONString());
}
}catch (Exception e) {
GwsLogger.error("sendMessage异常:", e.getMessage());
}
}
/**
*@Description: 接口调用发送消息
*@Param:
*@return:
*@Author: weishi.wang
*@date: 2019/6/24
*/
public void sendMessage(BaseMessagePO baseMessagePO){
Date date = new Date();
Session session = webSocketMap.get(baseMessagePO.getReceiveUserId());
try {
baseMessagePO.setMessageId(PrimaryKeyGeneratorTool.generateKey32());
baseMessagePO.setSendFlag(MessageReadFlagEnum.UN_READ.getVal());
if(null != session){
JSONArray jsonArray = new JSONArray();
jsonArray = getJSON(baseMessagePO);
session.getBasicRemote().sendText(jsonArray.toJSONString());
}
baseMessagePO.setCreateTime(date);
baseMessagePO.setUpdateTime(date);
baseMessageRepositoryMaster.save(baseMessagePO);
}catch (Exception e) {
GwsLogger.error("sendMessage异常:", e.getMessage());
}
}
/**
* 群发消息
* @param msg
*/
public void sendToAllUsersMessage(String msg) {
Set<String> set = webSocketMap.keySet();
for (String key : set) {
Session session = webSocketMap.get(key);
try {
session.getBasicRemote().sendText(msg);
} catch (IOException e) {
GwsLogger.error("sendToAllUsersMessage异常:", e.getMessage());
}
}
}
/**
* 群发某些人消息
* @param userIds
* @param baseMessagePO
*/
public void sendToSomeUsersMessage(String userIds, BaseMessagePO baseMessagePO) {
String[] userIDs = userIds.split(",");
for (int i = 0; i < userIDs.length; i++) {
Session session = webSocketMap.get(userIDs[i]);
baseMessagePO.setSendFlag(MessageReadFlagEnum.UN_READ.getVal());
try {
if(null != session) {
JSONArray jsonArray = new JSONArray();
jsonArray = getJSON(baseMessagePO);
session.getBasicRemote().sendText(jsonArray.toJSONString());
}
} catch (IOException e) {
GwsLogger.error("sendToAllUsersMessage异常:", e.getMessage());
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
private JSONArray getJSON(BaseMessagePO baseMessagePO) {
JSONArray jsonArray = new JSONArray();
JSONObject jsonObject = new JSONObject();
jsonObject.put("senderUserId", baseMessagePO.getSenderUserId());
jsonObject.put("senderUserName", baseMessagePO.getSenderUserName());
jsonObject.put("messageTitle", baseMessagePO.getMessageTitle());
jsonObject.put("messageContent", baseMessagePO.getMessageContent());
jsonObject.put("projectId", baseMessagePO.getProjectId());
jsonObject.put("projectName", baseMessagePO.getProjectName());
jsonObject.put("taskId", baseMessagePO.getTaskId());
jsonObject.put("taskName", baseMessagePO.getTaskName());
jsonObject.put("createTime", baseMessagePO.getCreateTime());
jsonArray.add(jsonObject);
return jsonArray;
}
public synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
发送消息,已经创建连接成功中的业务逻辑可以根据实际的需求来完成修改。
创建完成后,可以通过http://coolaf.com/tool/chattest这个在线测试工具连接本地的websocket进行测试是否成功。
成功之后,就可以在需要给前端发送消息的业务逻辑中尽情的使用发送消息的方法了,可以通过直接注入该WebSocketServer 类完成调用方法。
二:spring定时调度类:quartz的使用
- pom依赖:
<!--spring定时调度类:quartz依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<!--去除log back的依赖重复-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
2. 直接创建定时调度任务类:
@Component
public class SchedulerTask {
/**
* @Description 设置每天0点01分执行一次
*此计划任务每天执行,推送当项目为计划结束日期以及当项目为临近点时
* 以及任务/里程碑的当天超时提醒推送
**/
/*@Scheduled(cron = "0 1 0 * * ?")*/
@Scheduled(cron = "0 05 13 * * ?")
private void proErveyDay(){
/**
业务逻辑
*/
}
}
此时,一个定时任务就创建了,启动服务,该方法就会按照cron表达式完成任务的自动运行。就是这么简单。