WebSocket的使用

导入maven依赖

		<dependency>  
           <groupId>org.springframework.boot</groupId>  
           <artifactId>spring-boot-starter-websocket</artifactId>  
       </dependency> 

配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 开启WebSocket支持
 * @author zhengkai.blog.csdn.net
 */
@Configuration  
public class WebSocketConfig {  
	
    @Bean  
    public ServerEndpointExporter serverEndpointExporter() {  
        return new ServerEndpointExporter();  
    }  
  
} 

如果不想写配置类,可以在springboot的启动类里面直接添加配置

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class HFCZApplication
{
    public static void main(String[] args)
    {
        // System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(HFCZApplication.class, args);
    }
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

建立WebSocketServer

package com.hfcz.web.controller.phone;

import com.alibaba.fastjson2.JSONObject;
import com.hfcz.common.core.domain.model.LoginUser;
import com.hfcz.common.core.redis.RedisCache;
import com.hfcz.common.exception.GlobalException;
import com.hfcz.framework.web.service.TokenService;
import com.hfcz.phone.service.impl.PrUserMessageServiceImpl;
import com.hfcz.common.utils.WapUserMessageUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component
@ServerEndpoint(value = "/msg/{token}")
@RestController
public class WebSocketServer{

    private  static PrUserMessageServiceImpl userMessageService;

    private static TokenService tokenService;

    @Autowired
    public void setOgLocationService(PrUserMessageServiceImpl userMessageService) {
        WebSocketServer.userMessageService = userMessageService;
    }

    @Autowired
    public void setWapTokenService(TokenService tokenService) {
        WebSocketServer.tokenService = tokenService;
    }

    /**
     * 连接事件,加入注解
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam(value = "token") String token, Session session) {
        LoginUser user = tokenService.getLoginUserByToken(token);
        if (ObjectUtils.isEmpty(user)){
            throw new GlobalException("未登录");
        }

        // 添加到session的映射关系中
        WapUserMessageUtil.addSession(user.getUserId().toString(), session);

        // 未处理的消息条数,登录之后就会推送
        Long unreadNum = userMessageService.getUnreadNum();

        WapUserMessageUtil.wbApplyMsgAdd(unreadNum.intValue(), 0);
        Map<String, Object> msgMap = new HashMap<>();
        msgMap.put("type", "2");
        if (ObjectUtils.isNotEmpty(unreadNum) && unreadNum > 0){
            msgMap.put("msg", "存在"+String.valueOf(unreadNum)+"条未处理消息");
            msgMap.put("num", String.valueOf(unreadNum));

        }else {
            msgMap.put("num", "0");
        }
        WapUserMessageUtil.sendMessage(session, JSONObject.toJSONString(msgMap));
    }

    /**
     * 连接事件,加入注解
     * 用户断开链接
     *
     * @param token 用户登录标识
     * @param session
     */
    @OnClose
    public void onClose(@PathParam(value = "token") String token, Session session) {
        // 删除映射关系
        LoginUser user = tokenService.getLoginUserByToken(token);
        WapUserMessageUtil.removeSession(String.valueOf(user.getUserId()));
    }

    /**
     * 当接收到用户上传的消息
     * @param session
     */
    @OnMessage
    public void onMessage(@PathParam(value = "token") String token, Session session, String message) {
        LoginUser user = tokenService.getLoginUserByToken(token);
        if (ObjectUtils.isEmpty(user)){
            throw new GlobalException("未登录");
        }

        int num = WapUserMessageUtil.wbApplyMsgAdd(0, 1);
        Map<String, Object> msgMap = new HashMap<>();
        msgMap.put("type", "3");
        msgMap.put("num", String.valueOf(num));
        WapUserMessageUtil.sendMessage(session,JSONObject.toJSONString(msgMap));
    }

    /**
     * 处理用户活连接异常
     *
     * @param session
     * @param throwable
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        throwable.printStackTrace();
    }

}

因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller。需要在权限里面把链接地址放开,传入的是一个登录Token,TokenService 用来解析Token,获取登录信息,验证用户是否登录。

@Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        // 注解标记允许匿名访问的url
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
        permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());

        httpSecurity
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 禁用HTTP响应标头
                .headers().cacheControl().disable().and()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                .antMatchers("/login", "/register", "/captchaImage").permitAll()
                .antMatchers("/msg/*").permitAll()
                // 静态资源,可匿名访问
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        // 添加Logout filter
        httpSecurity.logout().logoutUrl("/**/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
    }

发送消息的工具类

import com.hfcz.common.core.redis.RedisCache;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 *
 */
@Component
public class WapUserMessageUtil {

    private static RedisCache redisCache;

    public WapUserMessageUtil(RedisCache redisCache) {
        WapUserMessageUtil.redisCache = redisCache;
    }

    /**
     * 记录当前在线的Session
     */
    private static final Map<String, Session> ONLINE_SESSION = new ConcurrentHashMap<>();

    public static final String WB_APPLY_MSG = "WB_APPLY_MSG:WAP_";

    // 在缓存中获取未处理的消息
    public static int wbApplyMsgAdd(int num, int type) {
        int sum = 0;
        synchronized ("wbApplyMsgAdd") {
            if (type == 0) {
                redisCache.setCacheObject(WB_APPLY_MSG , String.valueOf(num));
                return num;
            }
            String oldNum = redisCache.getCacheObject(WB_APPLY_MSG );
            if (StringUtils.isBlank(oldNum)) {
                redisCache.setCacheObject(WB_APPLY_MSG , String.valueOf(num));
                return num;
            }
            if (num == 0) {
                return Integer.valueOf(oldNum);
            }
            sum = Integer.valueOf(oldNum) + num;
            if (sum <= 0) {
                sum = 0;
            }
            redisCache.setCacheObject(WB_APPLY_MSG , String.valueOf(sum));
        }
        return sum;
    }

    /**
     * 添加session
     *
     * @param userId
     * @param session
     */
    public static void addSession(String userId, Session session) {
        // 此处只允许一个用户的session链接。一个用户的多个连接,我们视为无效。
        ONLINE_SESSION.putIfAbsent(userId, session);
    }

    /**
     * 获取session
     *
     * @param userId
     */
    public static Session getSession(String userId) {

        Session session = ONLINE_SESSION.get(userId);
        if (ObjectUtils.isEmpty(session)) {
            return null;
        }
        return session;
    }

    /**
     * 关闭session
     *
     * @param userId
     */
    public static void removeSession(String userId) {
        ONLINE_SESSION.remove(userId);
    }

    /**
     * 给单个用户推送消息
     *
     * @param session
     * @param message
     */
    public static void sendMessage(Session session, String message) {
        if (session == null) {
            return;
        }
        // 同步
        RemoteEndpoint.Async async = session.getAsyncRemote();
        async.sendText(message);
    }

    /**
     * 向所有在线人发送消息
     *
     * @param message
     */
    public static void sendMessageForAll(String message) {
        //jdk8 新方法
        ONLINE_SESSION.forEach((sessionId, session) -> sendMessage(session, message));
    }

}

在新增一条消息时,采用的是数据库存储,需要添加相应的消息内容

        // 新增消息内容
        WapUserMessage userMessage = new WapUserMessage();
        userMessage.setWapId(order.getWapId());
        userMessage.setOrderId(order.getId());
        userMessage.setState("0");
        userMessage.setContent("您有一条新订单信息");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        userMessage.setCreateTime(sdf.format(DateUtils.getNowDate()));
        wapUserMessageMapper.insertWapUserMessage(userMessage);

        // 发送消息
        Map<String, Object> msgMap = new HashMap<>();
        int num = WapUserMessageUtil.wbApplyMsgAdd(1, 1);
        msgMap.put("type", "0");
        msgMap.put("num", String.valueOf(num));
        // 有一条新消息
        msgMap.put("msg", "您有一条新订单信息");
        WapUserMessageUtil.sendMessageForAll(JSONObject.toJSONString(msgMap));

处理订单时,未读信息更改为已读,更新缓存,推送新的消息

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            prUserMessageMapper.updateStateByCommentIds(prOrder.getId(), sdf.format(DateUtils.getNowDate()));

            // 更新缓存
            Object oldNum = redisCache.getCacheObject(WapUserMessageUtil.WB_APPLY_MSG);
            Long num = Long.valueOf((String) oldNum) - 1;
            redisCache.setCacheObject(WapUserMessageUtil.WB_APPLY_MSG,String.valueOf(num));

            Map<String, Object> msgMap = new HashMap<>();
            msgMap.put("type", "2");
            msgMap.put("num", String.valueOf(num));
            msgMap.put("msg", "存在"+String.valueOf(num)+"条未处理消息");
            WapUserMessageUtil.sendMessageForAll(JSONObject.toJSONString(msgMap));

运行效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值