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的方法,即可發送通知。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM