Procházet zdrojové kódy

websocket及及鉴权

‘huochunsong’ před 1 rokem
rodič
revize
2cd430a82a

+ 22 - 0
inspur-admin/src/main/java/com/inspur/web/controller/work/WebSocketController.java

@@ -0,0 +1,22 @@
+package com.inspur.web.controller.work;
+
+import com.inspur.common.annotation.Anonymous;
+import com.inspur.websoket.client.PBXWebSocketClient;
+import com.inspur.websoket.client.util.PBXWebSocketUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RequestMapping("/wsclient")
+@RestController
+public class WebSocketController {
+
+    @RequestMapping("/sendhello")
+    @Anonymous
+    public void sendfirst() {
+        PBXWebSocketClient webSocketClient = PBXWebSocketUtils.getWBClient();
+        if(webSocketClient != null){
+            webSocketClient.send("hello," + "!");
+        }
+    }
+
+}

+ 7 - 0
inspur-framework/pom.xml

@@ -59,6 +59,13 @@
             <artifactId>inspur-system</artifactId>
         </dependency>
 
+        <!--websocket-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+
+
     </dependencies>
 
 </project>

+ 1 - 1
inspur-framework/src/main/java/com/inspur/framework/config/SecurityConfig.java

@@ -114,7 +114,7 @@ public class SecurityConfig
                 requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
                     // 静态资源,可匿名访问
                     .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
-                    .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
+                    .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**", "/ws/**").permitAll()
                     // 除上面外的所有请求全部需要鉴权认证
                     .anyRequest().authenticated();
             })

+ 22 - 0
inspur-framework/src/main/java/com/inspur/framework/config/WebSocketConfig.java

@@ -0,0 +1,22 @@
+package com.inspur.framework.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * @Description: 配置类
+ */
+@Configuration
+public class WebSocketConfig {
+
+    /**
+     * 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
+     */
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+
+}

+ 14 - 0
inspur-work/pom.xml

@@ -27,11 +27,25 @@
             <scope>compile</scope>
         </dependency>
 
+        <!-- 核心模块-->
         <dependency>
             <groupId>com.inspur</groupId>
             <artifactId>inspur-framework</artifactId>
         </dependency>
 
+        <!--websocket作为客户端-->
+        <dependency>
+            <groupId>org.java-websocket</groupId>
+            <artifactId>Java-WebSocket</artifactId>
+            <version>1.3.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
 
     </dependencies>
 

+ 28 - 0
inspur-work/src/main/java/com/inspur/domain/websocket/BaseInfo.java

@@ -0,0 +1,28 @@
+package com.inspur.domain.websocket;
+
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * 	通话基本信息
+ */
+
+@Data
+@ToString
+public class BaseInfo {
+
+    /** 通话中每个成员的通话通道 ID */
+    private String channel_id;
+
+    /** 该成员的通话状态
+     * ALERT:该成员作为主叫,发起呼叫后,处于回铃状态。
+     * RING:该成员作为被叫,处于响铃状态。
+     * ANSWERED:该成员作为主叫,发起呼叫后,电话被接听。
+     * ANSWER:该成员作为被叫,接听来电。
+     * HOLD:该成员的通话被保持。
+     * BYE:该成员主动挂断通话 */
+    private String member_status;
+
+    /** 通话建立使用的路径 */
+    private String call_path;
+}

+ 19 - 0
inspur-work/src/main/java/com/inspur/domain/websocket/Bound_Info.java

@@ -0,0 +1,19 @@
+package com.inspur.domain.websocket;
+
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * 	外线呼出呼入的信息
+ */
+
+@Data
+@ToString
+public class Bound_Info extends BaseInfo {
+    /** 主叫号码 */
+    private String from;
+    /** 被叫号码 */
+    private String to;
+    /** 通过哪条中继呼入 */
+    private String trunk_name;
+}

+ 22 - 0
inspur-work/src/main/java/com/inspur/domain/websocket/CallReceiveMsg.java

@@ -0,0 +1,22 @@
+package com.inspur.domain.websocket;
+
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * 通话的信息
+ */
+
+@Data
+@ToString
+public class CallReceiveMsg {
+
+    /** 事件 ID */
+    private Integer type;
+
+    /** PBX 序列号 (SN 码) */
+    private String sn;
+
+    /** 通话详情 */
+    private Call_Info msg;
+}

+ 21 - 0
inspur-work/src/main/java/com/inspur/domain/websocket/Call_Info.java

@@ -0,0 +1,21 @@
+package com.inspur.domain.websocket;
+
+import lombok.Data;
+import lombok.ToString;
+
+import java.util.List;
+
+/**
+ * 	通话状态变更、呼叫转移、呼叫转移
+ */
+
+@Data
+@ToString
+public class Call_Info {
+
+    /** 通话的唯一 ID */
+    private String call_id;
+
+    /** 通话中的不同类型成员的信息列表 */
+    private List<Members> members;
+}

+ 10 - 0
inspur-work/src/main/java/com/inspur/domain/websocket/Extension_Info.java

@@ -0,0 +1,10 @@
+package com.inspur.domain.websocket;
+
+/**
+ * 分机的信息
+ */
+public class Extension_Info extends BaseInfo {
+
+    /** 分机号码 */
+    private String number;
+}

+ 22 - 0
inspur-work/src/main/java/com/inspur/domain/websocket/Members.java

@@ -0,0 +1,22 @@
+package com.inspur.domain.websocket;
+
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * 通话中的不同类型成员的信息
+ */
+
+@Data
+@ToString
+public class Members {
+
+    /** 分机的信息 */
+    private Extension_Info extension;
+
+    /** 外线来电的信息 */
+    private Bound_Info inbound;
+
+    /** 外线呼出的信息 */
+    private Bound_Info outbound;
+}

+ 6 - 0
inspur-work/src/main/java/com/inspur/thrid/IPBXService.java

@@ -7,4 +7,10 @@ public interface IPBXService {
      * @return
      */
     public String getAccessToken();
+
+    /**
+     * 刷新访问 Token
+     * @return
+     */
+    public String getRefreshToken(String refreshToken);
 }

+ 63 - 10
inspur-work/src/main/java/com/inspur/thrid/impl/PBXServiceImpl.java

@@ -1,5 +1,6 @@
 package com.inspur.thrid.impl;
 
+import cn.hutool.http.HttpRequest;
 import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson2.JSONObject;
 import com.inspur.common.core.redis.RedisCache;
@@ -13,6 +14,7 @@ import org.springframework.stereotype.Service;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 云上协同API
@@ -33,15 +35,20 @@ public class PBXServiceImpl implements IPBXService {
     @Value("${pbx.tokenUrl:http://192.168.5.150:8088/openapi/v1.0/get_token}")
     private String tokenUrl;
 
+    // 获取token的url
+    @Value("${pbx.refreshTokenUrl:http://192.168.5.150:8088/openapi/v1.0/refresh_token}")
+    private String refreshTokenUrl;
+
     @Autowired
     private RedisCache redisCache;
     private String tokenKey = "pbx" + ":" + "token";
+    private String refreshTokenKey = "pbx" + ":" + "refreshToken";
     private String SUCCESS = "0";
     private String TOKEN_FAIL = "0201";
 
     @Override
     public String getAccessToken() {
-       /* log.debug("getAccessToken start");
+        log.debug("getAccessToken start");
         // 从redis获取token
         String token  = (String)redisCache.getCacheObject(tokenKey);
         if(!StringUtils.isEmpty(token)){
@@ -54,8 +61,17 @@ public class PBXServiceImpl implements IPBXService {
         params.put("password", secret);
         String url = tokenUrl;
         log.debug("getAccessToken,url:" + url + "param:" + params);
-        // 执行请求
-        String res = HttpUtil.post(url, params);
+
+        HttpRequest postRequest = HttpUtil.createPost(url);
+        // 添加头部信息
+        postRequest.header("Content-Type", "application/json");
+        postRequest.header("User-Agent", "OpenAPI");
+        //postRequest.header("Authorization", "Bearer your_token_here");
+        // 设置请求体
+        String body = params.toString();
+        // 发送请求
+        String res = postRequest.body(body).execute().body();
+
         log.debug("getAccessToken rsp: " + res);
         JSONObject jsonObject = JSONObject.parseObject(res);
         if(SUCCESS.equals(jsonObject.getString("errcode"))){
@@ -63,29 +79,66 @@ public class PBXServiceImpl implements IPBXService {
             String tokenStr = jsonObject.getString("access_token");
             // 获取过期时间
             Integer expire_time = jsonObject.getInteger("access_token_expire_time");
-            // 将获取的token放入redis,失效时间为云上协同失效时间-1分钟
-            redisCache.setCacheObject(tokenKey, tokenStr, expire_time-60);
 
             // 获取刷新token
             String refreshTokenStr = jsonObject.getString("refresh_token");
             // 获取刷新过期时间
             Integer refreshExpireTime = jsonObject.getInteger("refresh_token_expire_time");
 
+            // 将获取的token放入redis,失效时间-1分钟
+            redisCache.setCacheObject(tokenKey, tokenStr, expire_time-60, TimeUnit.SECONDS);
+            redisCache.setCacheObject(refreshTokenKey, refreshTokenStr);
+
             log.debug("getAccessToken end");
             return tokenStr;
         }
-        log.error("getAccessToken failed");*/
+        log.error("getAccessToken failed");
         return null;
     }
 
     /**
-     * 获取用户user_id
-     * @param mobile 手机号
+     * 刷新访问 Token
+     * @param refreshToken token信息
      * @return
      */
-    public String getUserIdByMobile(String mobile){
-        log.debug("getUserIdByMobile start");
+    @Override
+    public String getRefreshToken(String refreshToken){
+        log.debug("refreshToken start");
+        Map<String, String> params = new HashMap<String, String>(6);
+        // 应用key
+        params.put("refreshToken", refreshToken);
+        String url = refreshTokenUrl;
+        log.debug("refreshToken,url:" + url + "param:" + params);
+
+        HttpRequest postRequest = HttpUtil.createPost(url);
+        // 添加头部信息
+        postRequest.header("Content-Type", "application/json");
+        // 设置请求体
+        String body = params.toString();
+        // 发送请求
+        String res = postRequest.body(body).execute().body();
+
+        log.debug("refreshToken rsp: " + res);
+        JSONObject jsonObject = JSONObject.parseObject(res);
+        if(SUCCESS.equals(jsonObject.getString("errcode"))){
+            // 获取token
+            String tokenStr = jsonObject.getString("access_token");
+            // 获取过期时间
+            Integer expire_time = jsonObject.getInteger("access_token_expire_time");
+            // 获取刷新token
+            String refreshTokenStr = jsonObject.getString("refresh_token");
+            // 获取刷新过期时间
+            Integer refreshExpireTime = jsonObject.getInteger("refresh_token_expire_time");
+
+            // 将获取的token放入redis,失效时间-1分钟
+            redisCache.setCacheObject(tokenKey, tokenStr, expire_time-60, TimeUnit.SECONDS);
 
+            redisCache.setCacheObject(refreshTokenKey, refreshTokenStr);
+
+            log.debug("refreshToken end");
+            return tokenStr;
+        }
+        log.error("refreshToken failed");
         return null;
     }
 }

+ 38 - 0
inspur-work/src/main/java/com/inspur/websoket/client/PBXWebSocketClient.java

@@ -0,0 +1,38 @@
+package com.inspur.websoket.client;
+
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+
+import java.net.URI;
+
+@Slf4j
+public class PBXWebSocketClient extends WebSocketClient {
+
+    public PBXWebSocketClient(URI serverUri) {
+        super(serverUri);
+    }
+
+    //连接服务端时触发
+    @Override
+    public void onOpen(ServerHandshake handshakedata) {
+
+        log.info("websocket客户端和服务器连接成功");
+    }
+    //收到服务端消息时触发
+    @Override
+    public void onMessage(String message) {
+        log.info("websocket客户端收到消息={}", message);
+    }
+    //和服务端断开连接时触发
+    @Override
+    public void onClose(int code, String reason, boolean remote) {
+        log.info("websocket客户端退出连接");
+    }
+    //连接异常时触发
+    @Override
+    public void onError(Exception ex) {
+        log.info("websocket客户端和服务器连接发生错误={}", ex.getMessage());
+    }
+
+}

+ 60 - 0
inspur-work/src/main/java/com/inspur/websoket/client/util/PBXWebSocketUtils.java

@@ -0,0 +1,60 @@
+package com.inspur.websoket.client.util;
+
+import com.inspur.thrid.IPBXService;
+import com.inspur.websoket.client.PBXWebSocketClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+@Slf4j
+public class PBXWebSocketUtils {
+
+    @Value("${pbx.ws.path:ws://{pbx_ip}:{http_port}/{api_path}/subscribe?access_token=}")
+    private String wsPath;
+
+    @Autowired
+    private IPBXService ipbxService;
+
+    private PBXWebSocketClient client;
+
+    public PBXWebSocketClient webSocketClient() {
+        try {
+            // 获取token
+            String accessToken = ipbxService.getAccessToken();
+            PBXWebSocketClient webSocketClient = new PBXWebSocketClient(new URI(wsPath + accessToken));
+            webSocketClient.connect();
+            client = webSocketClient;
+            return webSocketClient;
+        } catch (URISyntaxException e) {
+            e.printStackTrace();
+            log.error("创建websocket连接出现异常!", e);
+        }
+        return null;
+    }
+
+    /**
+     * 获取websocket客户端
+     * @return
+     */
+    public PBXWebSocketClient getWBClient() {
+        if(null != client){
+            return client;
+        }
+        return webSocketClient();
+    }
+
+
+    /**
+     * 订阅事件
+     */
+    public void subscribe() {
+        //(30011) 通话记录更新
+        //(30012) 新通话记录
+        //(30013) 呼叫转移
+
+    }
+
+}

+ 131 - 0
inspur-work/src/main/java/com/inspur/websoket/server/PBXWebSocketServer.java

@@ -0,0 +1,131 @@
+package com.inspur.websoket.server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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.concurrent.CopyOnWriteArraySet;
+
+// @ServerEndpoint 声明并创建了webSocket端点, 并且指明了请求路径
+// id 为客户端请求时携带的参数, 用于服务端区分客户端使用
+@ServerEndpoint("/ws/{sid}")
+@Component
+public class PBXWebSocketServer {
+
+    // 日志对象
+    private static final Logger log = LoggerFactory.getLogger(PBXWebSocketServer.class);
+
+    // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
+    private static int onlineCount = 0;
+
+    // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
+    private static CopyOnWriteArraySet<PBXWebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
+    // private static ConcurrentHashMap<String,PBXWebSocketServer> websocketList = new ConcurrentHashMap<>();
+
+    // 与某个客户端的连接会话,需要通过它来给客户端发送数据
+    private Session session;
+
+    // 接收sid
+    private String sid = "";
+
+    /*
+     * 客户端创建连接时触发
+     * */
+    @OnOpen
+    public void onOpen(Session session, @PathParam("sid") String sid) {
+        this.session = session;
+        webSocketSet.add(this); // 加入set中
+        addOnlineCount(); // 在线数加1
+        log.info("有新窗口开始监听:" + sid + ", 当前在线人数为" + getOnlineCount());
+        this.sid = sid;
+        try {
+            sendMessage("连接成功");
+        } catch (IOException e) {
+            log.error("websocket IO异常");
+        }
+    }
+
+    /**
+     * 客户端连接关闭时触发
+     **/
+    @OnClose
+    public void onClose() {
+        webSocketSet.remove(this); // 从set中删除
+        subOnlineCount(); // 在线数减1
+        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
+    }
+
+    /**
+     * 接收到客户端消息时触发
+     */
+    @OnMessage
+    public void onMessage(String message, Session session) {
+        log.info("收到来自窗口" + sid + "的信息:" + message);
+        // 群发消息
+        for (PBXWebSocketServer item : webSocketSet) {
+            try {
+                item.sendMessage(message);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 连接发生异常时候触发
+     */
+    @OnError
+    public void onError(Session session, Throwable error) {
+        log.error("发生错误");
+        error.printStackTrace();
+    }
+
+    /**
+     * 实现服务器主动推送(向浏览器发消息)
+     */
+    public void sendMessage(String message) throws IOException {
+        log.info("服务器消息推送:"+message);
+        this.session.getBasicRemote().sendText(message);
+    }
+
+    /**
+     * 发送消息到所有客户端
+     * 指定sid则向指定客户端发消息
+     * 不指定sid则向所有客户端发送消息
+     * */
+    public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
+        log.info("推送消息到窗口" + sid + ",推送内容:" + message);
+        for (PBXWebSocketServer item : webSocketSet) {
+            try {
+                // 这里可以设定只推送给这个sid的,为null则全部推送
+                if (sid == null) {
+                    item.sendMessage(message);
+                } else if (item.sid.equals(sid)) {
+                    item.sendMessage(message);
+                }
+            } catch (IOException e) {
+                continue;
+            }
+        }
+    }
+
+    public static synchronized int getOnlineCount() {
+        return onlineCount;
+    }
+
+    public static synchronized void addOnlineCount() {
+        PBXWebSocketServer.onlineCount++;
+    }
+
+    public static synchronized void subOnlineCount() {
+        PBXWebSocketServer.onlineCount--;
+    }
+
+    public static CopyOnWriteArraySet<PBXWebSocketServer> getWebSocketSet() {
+        return webSocketSet;
+    }
+
+}