springboot+vue webSocket与自定义注解整合使用

 

这一套代码实现的逻辑是:配置一个注解(@Notice),用户可以在自己的service层使用该注解,无需修改service层逻辑,通过一些注解配置,实现调用websocket通知其他用户。

比如:我原先有一个方法是录入一条信息,我只需要在该方法上添加该注解,就可以实时通知别人有一条信息待处理:

   // 在这个进入这个方法后,通过#UserInfoVo.developedUserId拿到入参,获取接收人的id,并发送给它消息(noticeMsg)
  @Notice(receiver = "#UserInfoVO.developUserId", noticeMsg = "您有一条信息待处理。",isShowSender = true) @Override public RetMsg<Null> insertOneMsg(UserInfoVO mineUserInfoVO) {
      //普通业务逻辑
      。。。
  }

根据具体业务需求,该注解的一些可配置参数:

@Documented 
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD)
public @interface Notice {
    /**
     * 消息类型   simple(default):简单模式只需在接口中配置接收人和消息即可
     *           complex:复杂模式需要实现IHandleNotice接口,配置不同的operateType对应不同的实现类
     */
    String noticeType() default "simple";
    /**
     * 接收对象  simple模式配置 (可输入表达式)
     */
    String receiver() default "";
    /**
     * 消息内容  simple模式配置
     */
    String noticeMsg() default "";
    /**
     * 是否显示发送方  simple模式配置
     */
    boolean isShowSender() default true;
    
    
    
    /**
     * 通知类型,在枚举类中添加
     */
    NoticeType operateType() default NoticeType.Default;
    
    
    
    /**
     * 是否开启通知(可输入表达式)
     */
    String condition() default "";
}

 

1.springBoot中webSocket配置

1.1 WebSocketServer

/** 
 * 这里就相当于一个ws协议的controller
 */
@Component
@ServerEndpoint("/websocket/{userName}")
public class WebSocketServer {
        
    //存放每个客户端建立的 链接
    private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();
    
    /**
     * 一个应用建立连接时调用的方法
     * @param session
     * @param userName
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value="userName")String userName) {
        sessionPool.put(userName, session);
        System.out.println(userName+"【websocket消息】有新的连接,总数为:"+sessionPool.size());
        
    }
    
    /**
     * 关闭时调用,删掉一个session
     */
    @OnClose
    public void onClose(@PathParam(value = "userName") String userName) {
        sessionPool.remove(userName);
        System.out.println(userName+"【websocket消息】连接断开,总数为:"+sessionPool.size());
    }
    
    @OnMessage
    public void onMessage(String message) {
        System.out.println("【websocket消息】收到客户端消息:"+message);
    }
    
    //发送消息
    public void sendOneMessage(String userName, String message) throws IOException {
        System.out.println(userName+":发送【websocket消息】:"+message);
        Session session = sessionPool.get(userName);
        if (session != null) {
            synchronized(session) {
                session.getBasicRemote().sendText(message);
            }
        }
    }
}

这里的 @ServerEndpoint 就是初始化和通信消息的链接

1.2 WebSocketServer

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
    
}

 注入一个ServerEndPointExporter

1.3 配置一个向后台获取websockt地址的接口,根据不同的环境返回不同的websocket地址:

@CrossOrigin
@RestController
@RequestMapping("/api/socket")
public class SocketApiController {
	
	//websockt.localaddress=localhost:8080
	@Value("${websockt.localaddress:}")
	private String localAddress;
	
	
	@GetMapping("/getWsAddress")
	public String getWsAddress() {
		if("".equals(localAddress)) return "";
         //user_id可以从spring security上下文中获取,这里也可以随便设置一个测试
          String user_id = "test"; String wsUrl = "ws://" + localAddress + "/websocket/" +user_id ; return wsUrl; } }

  

 

2.Vue中webSocket配置

建一个webSocketcomponent组件

<template>
</template>
<script>
import { axios } from '@/utils/request'
export default {
  name: 'WebSocketComponents',
  data () {
    return {
      socket: ''
   }
  },
  methods: {
    initWebSocket() {
        this.socket.onerror = this.setErrorMessage
        this.socket.onopen = this.setOnopenMessage
        console.log("连接建立成功:" + this.wsUrl)
        this.socket.onmessage = this.setOnMessage
        this.socket.onclose = this.setOncloseMessage
        window.onbeforeunload = this.onbeforeunload
    },
    setOnMessage(event) {
        const noticeMsg = JSON.parse(event.data);
        // this.msgs[Number(noticeMsg.msgNo)-1].msg = noticeMsg.msg
        // this.showNotice[Number(noticeMsg.msgNo)-1].show = !(noticeMsg.msg === undefined)
        this.$notification.info( { message: noticeMsg.noticeMsg , description: noticeMsg.noticeDescription})
        this.$emit('noticeInit');
    },
    setErrorMessage () {
      console.log('WebSocket连接发生错误   状态码:' + this.socket.readyState)
    },
    setOnopenMessage () {
      console.log('WebSocket连接成功    状态码:' + this.socket.readyState)
    },
    setOnmessageMessage (event) {
      console.log('服务端返回:' + event.data)
    },
    setOncloseMessage () {
      console.log('WebSocket连接关闭    状态码:' + this.socket.readyState)
    },
    onbeforeunload () {
      this.closeWebSocket()
    },
    closeWebSocket () {
      this.socket.close()
    },
 },
//如果嵌入了该组件,向后台请求websocket地址 不为空即建立websocket连接 created() {
const that = this; axios.get(`/socket/getWsAddress`).then( (response) => { if ("" == response) { return; } // Return a string that indicates how binary data from the WebSocket object is exposed to scripts that.socket = new WebSocket(response); that.initWebSocket();
    //这里可以在websocket连接成功后,去调用父组件的初始化的方法 that.$emit(
'noticeInit'); }); } } </script>

 

3.实现注解调用websocket

3.1实现@notice注解的具体逻辑:

/**
 * 在被注解的方法后 去通知用户
 * @author xjx
 *  简单模式发送:只需要在注解中配置接收人和发送的信息即可
   复杂模式发送:实现IHandleNotice接口,配置不同的operateType,发送对应的消息
*/ @Aspect @Component public class NoticeConfig { //表达式解析类 final ExpressionParser parser = new SpelExpressionParser(); //该类可以从反射的方法中拿到参数名称 final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); @Autowired private WebSocketServer webSocketServer;
    //看后面策略模式代码 @Autowired
private NoticeChooser noticeChooser; @Pointcut("@annotation(com.tongdatech.winterspring.zczx.webSocketConfig.NoticeAnnotation.Notice)" ) public void noticeConfig() { } @AfterReturning(pointcut = "noticeConfig()", returning = "returnObject") public void doNotice(JoinPoint joinPoint,Object returnObject) throws IOException{ if("".equals(localAddress)) return; Method method = ((MethodSignature)joinPoint.getSignature()).getMethod(); Object[] args = joinPoint.getArgs(); Notice noticeAnnotation = method.getAnnotation(Notice.class); /** * 判断是否发送通知 */ if(!noticeAnnotation.condition().equals("")) { if(!generateKeyBySpEL(noticeAnnotation.condition(),joinPoint,Boolean.class)) { return; } } /** * 简单模式发送 */ if("simple".equals(noticeAnnotation.noticeType())) {
        //从注解的参数中获得收信人 String receiver
= generateKeyBySpEL(noticeAnnotation.receiver(),joinPoint,String.class); String sender = "";
        //判断通知是否带发送人名称
if(noticeAnnotation.isShowSender()) { sender = "来自:"+getUserName(); } NoticeMsg noticeMsg = new NoticeMsg(null,null,noticeAnnotation.noticeMsg(),sender,null,null); webSocketServer.sendOneMessage(receiver,JSON.toJSONString(noticeMsg)); return; } /** * 复杂模式发送
       根据不同的operateType 调用不同的实现类
       我们给IHandleNotice接口中,传入我们在@Notice注解的方法中获取到的参数和返回值,并获取它返回的 Map<接收人,消息>
*/ IHandleNotice iHandleNotice = noticeChooser.choose(noticeAnnotation.operateType()); Map<String, NoticeMsg> receiversAndMsgs = iHandleNotice.handelNotice(getUserId(), args, returnObject); for (Map.Entry<String, NoticeMsg> entry : receiversAndMsgs.entrySet()) { String jsonMsg = JSON.toJSONString(entry.getValue()); webSocketServer.sendOneMessage(entry.getKey(),jsonMsg); } } /** * 获取发送人用户信息,看情况,这里可以从security架构中,上下文中获取 * @return */ protected String getUserName() {

      Authentication auth = SecurityContextHolder.getContext().getAuthentication();

      。。。
} /** * 解析表达式工具类   返回注解中配置的参数获取到的值 * @param spELString * @param joinPoint * @param clazz * @return */ private <T> T generateKeyBySpEL(String spELString, JoinPoint joinPoint, Class<T> clazz) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); String[] paramNames = nameDiscoverer.getParameterNames(method); Expression expression = parser.parseExpression(spELString); EvaluationContext context = new StandardEvaluationContext(); Object[] args = joinPoint.getArgs(); for(int i = 0 ; i < args.length ; i++) { context.setVariable(paramNames[i], args[i]); } return expression.getValue(context,clazz); }

3.1利用策略模式实现注解复杂情况发送:

消息实体类 noticeMsg:

public class NoticeMsg implements Serializable{
    private static final long serialVersionUID = 1L;
    private String msgNo;//消息编号 对应前台消息的位置
    private String msg;//消息
    private String noticeMsg;
    private String noticeDescription;
    private String url;
    private Object object;//预留对象
    
    


    
    public NoticeMsg(String msg, String url) {
        super();
        this.msg = msg;
        this.url = url;
    }
    public NoticeMsg(String msg, String url,Object object) {
        super();
        this.msg = msg;
        this.url = url;
        this.object = object;
    }
    public NoticeMsg(String msgNo, String msg, String noticeMsg, String noticeDescription, String url,
            Object object) {
        super();
        this.msgNo = msgNo;
        this.msg = msg;
        this.noticeMsg = noticeMsg;
        this.noticeDescription = noticeDescription;
        this.url = url;
        this.object = object;
    }


    public String getUrl() {
        return url;
    }


    public void setUrl(String url) {
        this.url = url;
    }


    public String getNoticeMsg() {
        return noticeMsg;
    }


    public void setNoticeMsg(String noticeMsg) {
        this.noticeMsg = noticeMsg;
    }


    public String getNoticeDescription() {
        return noticeDescription;
    }


    public void setNoticeDescription(String noticeDescription) {
        this.noticeDescription = noticeDescription;
    }


    public Object getObject() {
        return object;
    }


    public void setObject(Object object) {
        this.object = object;
    }


    public String getMsgNo() {
        return msgNo;
    }
    
    public void setMsgNo(String msgNo) {
        this.msgNo = msgNo;
    }

    public String getMsg() {
        return msg;
    }
    
    public void setMsg(String msg) {
        this.msg = msg;
    }
    @Override
    public String toString() {
        return "NoticeMsg [msgNo=" + msgNo + ", msg=" + msg + ", noticeMsg=" + noticeMsg + ", noticeDescription="
                + noticeDescription + ", url=" + url + ", object=" + object + "]";
    }    
    
    
}
View Code

 

先写一个枚举类,存放所有的策略(对应@Notice中operateType)

public enum NoticeType {
    Default,CountyPaiDan,ReAddr,AddCustomer
}

定义一个策略接口,包含对策略的抽象和 自己所属的枚举标记:

public interface IHandleNotice {
    //获得 @Notice注解的方法中的参数,发送人 和 返回参数  返回 Map<接收人,信息>
    Map<String, NoticeMsg> handelNotice(String userId, Object[] paramObject, Object returnObject);
    //返回实现类所属枚举类中的值
    public NoticeType noticeType();
}

写一些类去实现该接口:

@Service
public class AddCustomerNoticeImpl implements IHandleNotice{


    @Override
    public NoticeType noticeType() {
        // TODO Auto-generated method stub
        return NoticeType.AddCustomer;
    }

    @Override
    public Map<String, NoticeMsg> handelNotice(String userId, Object[] paramObject,
            Object returnObject) {
    。。。
}
@Service
public class ReaddrNoticeImpl implements IHandleNotice{


   
    @Override
    public NoticeType noticeType() {
        // TODO Auto-generated method stub
        return NoticeType.ReAddr;
    }

    @Override
    public Map<String, NoticeMsg> handelNotice(String userId, Object[] paramObject,
            Object returnObject) {
    。。。
  
    }

我们将实现类在spring容器初始化后存入内存中 NoticeChooser:

/**
 * ApplicationContextAware 通过实现该接口可以拿到容器
 * @author xjx
 *
 */
@Component
public class NoticeChooser implements ApplicationContextAware{
    
    private Map<NoticeType, IHandleNotice> noticeMap = new ConcurrentHashMap<>();
    
    
    //成员变量来接收容器
    private ApplicationContext applicationContext;
    
    
    public IHandleNotice choose(NoticeType noticeType) {
        return noticeMap.get(noticeType);
    }
    
    @PostConstruct
    public void initNoticeTypes() {
        //根据类型拿到容器
        Map<String, IHandleNotice> noticeTypes = applicationContext.getBeansOfType(IHandleNotice.class);
        noticeTypes.forEach((String t, IHandleNotice handle) -> {
            noticeMap.put(handle.noticeType(), handle);
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // TODO Auto-generated method stub
        this.applicationContext = applicationContext;
    }

}
这样,我们在之前的代码:IHandleNotice iHandleNotice = noticeChooser.choose(noticeAnnotation.operateType());
通过注解中的operateType(),可以拿到对应的对IHandleNotice的实现,这样发送的通知就是注解配置的实现类。

最后,只要调用带@Notice的方法,即可发送通知。

 

posted @ 2020-09-04 17:29  戏言xjx  阅读(967)  评论(0编辑  收藏  举报