由于最近有个需求,产品即将到期(不同时间段到期)时给后台用户按角色推送,功能完成之后在此做个小结
1. 在启动类中添加注解@EnableScheduling
packagecom.hsfw.backyard.websocket333;/*** @Description
* @Author: liucq
* @Date: 2019/1/25*/
importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.boot.builder.SpringApplicationBuilder;importorg.springframework.boot.web.support.SpringBootServletInitializer;importorg.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@MapperScan("com.siwei.insurance.*.dao")/*** //该注解是开启定时任务的支持*/@EnableSchedulingpublic class lifeInsuranceApplication extendsSpringBootServletInitializer {
@OverrideprotectedSpringApplicationBuilder configure(SpringApplicationBuilder builder) {return builder.sources(lifeInsuranceApplication.class);
}public static voidmain(String[] args) {
SpringApplication.run(lifeInsuranceApplication.class, args);
}
}
2. 写定时器
packagecom.hsfw.backyard.websocket333;/*** @Description
* @Author: liucq
* @Date: 2019/1/25*/
importcom.siwei.insurance.permission.dao.RolePermissionDao;importcom.siwei.insurance.productManage.dao.ExpirePushMsgMapper;importcom.siwei.insurance.productManage.service.ProductService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.scheduling.annotation.Scheduled;importjava.util.ArrayList;importjava.util.List;
@ComponentpublicclassProductExpireTask {
@AutowiredprivateRolePermissionDao rolePermissionDao;
@AutowiredprivateProductService productService;
@AutowiredprivateExpirePushMsgMapper expirePushMsgMapper;/*** 每天早上0点执行*/@Scheduled(cron= "0 0 0 1/1 * ?")public voidproductExpire() {//距离到期还有一个月提醒
String oneMonthExpireDate =DateUtil.addOneMonth();
dealExpireProduct(oneMonthExpireDate);//距离到期还有一天提醒
String oneDayExpireDate =DateUtil.addOneDay();
dealExpireProduct(oneDayExpireDate);//距离到期还有一周提醒
String oneWeekExpireDate = DateUtil.addFewDays(7);
dealExpireProduct(oneWeekExpireDate);
}private voiddealExpireProduct(String expireDate) {
List> expireProductMapList =productService.findExpireProducts(expireDate);if (expireProductMapList != null && !expireProductMapList.isEmpty()) {//根据路径查询需要推送的角色
List needPushRoleIds =rolePermissionDao.findNeedPushRoleIdByUrl(TotalConstant.PRODUCT_PUSH_URL);
List expirePushMsgs = new ArrayList<>();for (MapexpireProductMap : expireProductMapList) {
ExpirePushMsg expirePushMsg= newExpirePushMsg();
expirePushMsg.setNeedPushEntityId((int) expireProductMap.get("id"));
expirePushMsg.setNeedPushEntityNo((String) expireProductMap.get("insuranceNo"));
String productName= (String) expireProductMap.get("insuranceName");
expirePushMsg.setNeedPushEntityName(productName);//设置此推送消息的到期时间
expirePushMsg.setExpireDate(DateUtil.stringToDate(DateUtil.addOneDay()));
expirePushMsg.setPushType(2);
StringBuffer needPushRoleIdString= newStringBuffer();
needPushRoleIds.forEach(e-> needPushRoleIdString.append(e + ";"));
expirePushMsg.setPushRoleId(needPushRoleIdString.toString().trim());
String productExpireDateString= DateUtil.dateToShotString((Date) expireProductMap.get("expiryDate"));
expirePushMsg.setPushMsg("您的产品:" + productName + ",将于" + productExpireDateString + "即将过期,请及时处理!");
expirePushMsgs.add(expirePushMsg);
}
expirePushMsgMapper.insertAll(expirePushMsgs);
}
}
@Scheduled(cron= "0 0 0 1/1 * ?")public voidpushMsgExpire() {
String oneDayExpireDate=DateUtil.getZeroTime(DateUtil.addOneDay());//推送消息只存在一天,根据到期时间将数据删除
expirePushMsgMapper.deleteByExpireDate(oneDayExpireDate);
}
}
DateUtil工具类
packagecom.hsfw.backyard.websocket333;/*** @Description
* @Author: liucq
* @Date: 2019/1/25*/
importorg.apache.commons.lang3.StringUtils;importjava.text.ParsePosition;importjava.text.SimpleDateFormat;importjava.util.Calendar;importjava.util.Date;/*** @Description 时间处理工具类 *@authorlinxiunan * @date 2018年9月3日*/
public classDateUtil {private static final SimpleDateFormat dayOfDateFormat = new SimpleDateFormat("yyyy-MM-dd");private static final SimpleDateFormat secondOfDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");/***@return当天时间加一天,返回"yyyy-MM-dd"格式*/
public staticString addOneDay() {
Calendar calendar=Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH,1);returndayOfDateFormat.format(calendar.getTime());
}/***@return当天时间加一月,返回"yyyy-MM-dd"格式*/
public staticString addOneMonth() {
Calendar calendar=Calendar.getInstance();
calendar.add(Calendar.MONTH,1);returndayOfDateFormat.format(calendar.getTime());
}/***@paramdayNumber 加的天数 *@return返回当天时间添加几天之后的时间,返回"yyyy-MM-dd"格式*/
public static String addFewDays(intdayNumber) {
Calendar calendar=Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, dayNumber);returndayOfDateFormat.format(calendar.getTime());
}/***@paramdateString 需要转换成时间格式的日期字符串 *@return返回字符串转换成的时间*/
public staticDate stringToDate(String dateString) {
ParsePosition parsePosition= new ParsePosition(0);if (dateString.contains(" ")) {returnsecondOfDateFormat.parse(dateString, parsePosition);
}else{returndayOfDateFormat.parse(dateString, parsePosition);
}
}/***@paramdate 需要转换成字符串格式的日期 *@return返回"yyyy-MM-dd"格式的转换后的字符串*/
public staticString dateToShotString(Date date) {returndayOfDateFormat.format(date);
}/***@paramdate 需要转换成字符串格式的日期 *@return返回"yyyy-MM-dd HH:mm:ss"格式的转换后的字符串*/
public staticString dateToLongString(Date date) {returnsecondOfDateFormat.format(date);
}/***@paramdateString 需要获取0点的时间字符串,如果获取当天0点,传null即可 *@return返回"yyyy-MM-dd HH:mm:ss"格式的某天0点字符串*/
public staticString getZeroTime(String dateString) {if(StringUtils.isBlank(dateString)) {
Calendar calendar=Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY,0);
calendar.set(Calendar.MINUTE,0);
calendar.set(Calendar.SECOND,0);returnsecondOfDateFormat.format(calendar.getTime());
}else{
Date date=stringToDate(dateString);returndateToLongString(date);
}
}
}
3. 引入websocket所需jar包
org.springframework.boot
spring-boot-starter-websocket
4. 配置websocket
编写MyEndpointConfigure类
packagecom.hsfw.backyard.websocket333;/*** @Description
* @Author: liucq
* @Date: 2019/1/25*/
importorg.springframework.beans.BeansException;importorg.springframework.beans.factory.BeanFactory;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;importjavax.websocket.server.ServerEndpointConfig;public class MyEndpointConfigure extends ServerEndpointConfig.Configurator implementsApplicationContextAware {private static volatileBeanFactory context;
@Overridepublic T getEndpointInstance(Class clazz) throwsInstantiationException {returncontext.getBean(clazz);
}
@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throwsBeansException {
MyEndpointConfigure.context=applicationContext;
}
}
websocket配置类
packagecom.hsfw.backyard.websocket333;/*** @Description
* @Author: liucq
* @Date: 2019/1/25*/
importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configurationpublic classWebSocketConfig {
@BeanpublicServerEndpointExporter serverEndpointExporter() {return newServerEndpointExporter();
}
@BeanpublicMyEndpointConfigure newConfigure() {return newMyEndpointConfigure();
}
}
这里需要重点说明一下,在websocket配置类中,第一个配置是因为使用springboot内置容器,自己开发时需要配置,如果有独立的容器需要将其注释掉,也就意味着,如果将项目打成WAR包,部署到服务器,使用Tomcat启动时,需要注释掉ServerEndpointExporter配置;MyEndpointConfigure配置是因为我的需求需要,需要在websocket类中注入service层或者dao层的接口,MyEndpointConfigure配置就是为了解决websocket无法注入的问题,如果没有需要可以不用配置
---------------------
5. websocket类
packagecom.hsfw.backyard.websocket333;/*** @Description
* @Author: liucq
* @Date: 2019/1/25*/
importorg.apache.commons.lang.StringUtils;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importjavax.websocket.OnClose;importjavax.websocket.OnError;importjavax.websocket.OnMessage;importjavax.websocket.OnOpen;importjavax.websocket.server.PathParam;importjavax.websocket.server.ServerEndpoint;importjava.io.IOException;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.CopyOnWriteArraySet;
@Component
@ServerEndpoint(value= "/productWebSocket/{userId}", configurator = MyEndpointConfigure.class)public classProductWebSocket {//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;//concurrent包的线程安全Set,用来存放每个客户端对应的ProductWebSocket对象。
private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();//与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session;
@AutowiredprivateUserRoleDao userRoleDao;
@AutowiredprivateExpirePushMsgMapper expirePushMsgMapper;private Logger log = LoggerFactory.getLogger(ProductWebSocket.class);/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(@PathParam("userId") String userId, Session session) {
log.info("新客户端连入,用户id:" +userId);this.session =session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1//相关业务处理,根据拿到的用户ID判断其为那种角色,根据角色ID去查询是否有需要推送给该角色的消息,有则推送
if(StringUtils.isNotBlank(userId)) {
List roleIds =userRoleDao.findRoleIdByUserId(userId);
List totalPushMsgs = new ArrayList();for(String roleId : roleIds) {
List pushMsgs =expirePushMsgMapper.findPushMsgByRoleId(roleId);if (pushMsgs != null && !pushMsgs.isEmpty()) {
totalPushMsgs.addAll(pushMsgs);
}
}if (totalPushMsgs != null && !totalPushMsgs.isEmpty()) {
totalPushMsgs.forEach(e->sendMessage(e));
}
}
}/*** 连接关闭调用的方法*/@OnClosepublic voidonClose() {
log.info("一个客户端关闭连接");
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
}/*** 收到客户端消息后调用的方法
* *@parammessage 客户端发送过来的消息*/@OnMessagepublic voidonMessage(String message, Session session) {
}/*** 发生错误时调用*/@OnErrorpublic voidonError(Session session, Throwable error) {
log.error("websocket出现错误");
error.printStackTrace();
}public voidsendMessage(String message) {try{this.session.getBasicRemote().sendText(message);
log.info("推送消息成功,消息为:" +message);
}catch(IOException e) {
e.printStackTrace();
}
}/*** 群发自定义消息*/
public static void sendInfo(String message) throwsIOException {for(ProductWebSocket productWebSocket : webSocketSet) {
productWebSocket.sendMessage(message);
}
}public static synchronized intgetOnlineCount() {returnonlineCount;
}public static synchronized voidaddOnlineCount() {
ProductWebSocket.onlineCount++;
}public static synchronized voidsubOnlineCount() {
ProductWebSocket.onlineCount--;
}
}
这样后台的功能基本上就算是写完了,前端配合测试一下
6. 前端测试
写一个页面,代码如下
My WebSocketSend Close