本文為您介紹平臺型企業如何集成呼叫能力。例如您是做CRM系統,為您的客戶提供軟件服務,那么集成呼叫能力,助力您的產品實現場景閉環,能夠為您的客戶提供更優質的服務。
集成架構
流程圖
JWT Token 模式集成接入流程圖
說明JWT Token 模式集成接入流程圖中藍色文字的內容表示該步驟由Fuyun IDP完成。
OAuth模式集成接入流程圖
說明OAuth模式集成接入有兩種方式獲取access_token,一種為前端直接獲取,另一種為客戶后端post訪問access_token置換接口獲取access_token。
前期集成準備
獲取AccessKey ID和AccessKey Secret。
登錄阿里云賬號,單擊右上角人像圖標,選擇AccessKey管理,獲取AccessKey ID和AccessKey Secret。
獲取實例ID。
登錄智能聯絡中心,在實例管理頁面獲取實例ID。
創建坐席和熱線技能組。
把坐席添加到技能組有以下兩種方式,您可以選擇其中任一方式。
PaaS方式:首先建熱線技能組,再創建坐席,把坐席分配到熱線技能組中。
創建坐席可參見CreateAgent,參見示例demo進行創建:
創建熱線技能組可參見CreateSkillGroup,參見示例Demo進行創建:
SaaS方式:直接在智能聯絡中心添加。
創建熱線技能組:選擇
頁面,切換到技能組Tab,單擊...按鈕,單擊添加技能組,選擇應用渠道為熱線,即可創建新的熱線技能組。創建坐席:選擇
頁面,切換到人員授權Tab,單擊頁面右上角新增人員,輸入基本信息,并把該坐席添加到上述步驟中創建的熱線技能組中。
注冊開發者門戶。
SDK集成服務端步驟
JWT Token 模式集成接入指引
IDP身份管理配置。
登錄開發者門戶,單擊設置,進入身份管理配置頁面,單擊右上角添加,進行身份管理配置。
基礎信息設置。
App Name:客戶側應用名,最長10個字符,支持英文、數字。
授權類型:選擇令牌交換模式。
授權范圍:scope,選擇用戶管理和熱線,用于標識頒發的access_token可訪問的API范圍,防止越權調用。
是否免登工作臺:不用勾選。
回調地址:請填寫認證成功后的回調的URL,客服方用來接收并處智能聯絡中心頒發的授權碼。
開始URL:客戶方網站首頁。
憑證信息。
完善基礎信息設置后,單擊下一步,進入憑證信息頁面。此處為智能聯絡中心頒發的client_id和client_secret,用于驗證接入方。
單擊完成,授權類型即設置成功。
單擊上一步按鈕,可修改基礎信息設置(App Name除外)。
免登模式配置。
授權模式為令牌交換模式時,需要配置免登模式。免登模式選擇JWT模式。
免登模式:JWT模式。
公鑰:獲取公鑰,請參見使用OpenSSL生成密鑰對。
簽發網站域名:即iss。更多詳情,請參見JWT token生成規則。
配置完成后,單擊保存,即配置成功。單擊上一步,支持返回憑證信息頁面。
生成JWT Tkoen。
在后端的開發環節生成jwt token,需要用到參數iss,user_name,exp,private_key。payload包含字段:
字段
描述
示例
iss
簽發者網站域名
"iss":"http://signin.rhino****.com"
exp
過期時間戳
-
user_name
JWT token頒發給的用戶,創建坐席時您設置的AccountName(用來映射唯一坐席)。
-
jti
隨機uuid
"b774ef13-a5bc-****-8346-042d879efb1a"
JWT token生成代碼Demo。
// pom依賴 <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>1.1.1.RELEASE</version> </dependency> /** * 生成jwtToken * @return */ public static String generateToken(String userName, String iss, long expireTime, String privateKeyStr) { try { privateKeyStr = privateKeyStr.replaceAll("\\s+", ""); byte[] decodedPrivateKey = Base64.getDecoder().decode(privateKeyStr.getBytes()); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodedPrivateKey); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(spec); RsaSigner signer = new RsaSigner((RSAPrivateKey) privateKey); // generate token Map<String, Object> payloadMap = new HashMap<>(4); payloadMap.put("iss", iss); payloadMap.put("jti", UUID.randomUUID().toString()); payloadMap.put("user_name", userName); payloadMap.put("exp", expireTime); return JwtHelper.encode(new JSONObject(payloadMap).toJSONString(), signer).getEncoded(); } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { e.printStackTrace(); } return null; }
請求頭header和請求參數的封裝。
請求參數的封裝,具體可參考三方賬號授權。
請求URL
URL:https://signin.rhinokeen.com/oauth/token_exchange。
請求類型:POST。
請求HEADER
字段
示例
描述
Authorization
"Authorization: Basic YWxpYmFiYS14aWFvZXI6YmNlMTllZDYtYTFhNC00NzA3LTgwZjAtYTM4OGY3MGUxNWQ3"
授權類型,接口使用http basic authentication認證方式Authorization= Basic Base64.encode(client_id:client_secret)
請求參數
字段
示例
描述
grant_type
"urn:ietf:params:oauth:grant-type:token-exchange"x
授權類型
scope
"user_management,hotline"
訪問范圍(用戶管理和熱線)
redirect_url
"http://****.com/callback"
回調地址
subject_token
-
第三方JWT token
subject_issuer
"http://****.com"
簽發者網站域名
向Fuyun_IDP指定免登地址發出POST請求。
發送post請求的同時并攜帶您封裝好的參數,用表單傳輸格式。
獲取access_token。
Fuyun_IDP會通過response的形式返回access_token等信息。
重要HTTPS的請求的數據是表單傳輸格式,不要用JSON。
創建技能組會返回技能組ID,通過這個ID讓坐席加入相應技能組。
創建坐席設置的AccountName之后會成為生成JWT token的參數。
示例Demo。
/** * 環境:jdk1.8 * 這是一個簡單的獲取access_token的demo * 在此之前,你需要準備的工作: * 1.創建坐席和技能組,并把坐席分配到技能組中(可下載阿里云官網的demo) * 2.用OpenSSL生成private_key和public_key * 3.用iss/public_key/redirect_url去開發者門戶(目前向阿里相關工作人員)獲取client_id和client_secret */ @EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class}) @SpringBootTest public class DemoCostmerServer02ApplicationTests { /** * OpenSSL生成端privateKey */ public final String privateKey = ""; /** * fuyun指定置換token的URL */ public final String FuYun_URL = "https://signin.rhinokeen.com/oauth/token_exchange"; /** * 置換得到的client_id */ public final String client_id = ""; /** * 置換得到的client_secret */ public final String client_secret = ""; /** * 拼接成指定的參數格式 */ public final String client_IdAndSecret = client_id + ":" + client_secret; /** * 請求的參數 */ public final String grant_type = "urn:ietf:params:oauth:grant-type:token-exchange"; public final String scope = "fuyun-dev"; public final String redirect_url = "https://bc.****.com/api/ccs/callback"; /** * 簽發者域名網站iss */ public final String iss = ""; /** * 您創建坐席是設置的AccountName */ public final String accountName = ""; @Test void contextLoads() { /** * 用Base64對header參數進行加密 */ try { final Base64.Encoder encoder = Base64.getEncoder(); final byte[] client_IdAndSecretByte = client_IdAndSecret.getBytes("UTF-8"); //編碼 final String Auth = encoder.encodeToString(client_IdAndSecretByte); /** * 封裝header */ Map<String,String> header = new HashMap<>(); header.put("Authorization","Basic "+Auth); /** * 生成jwt token */ Long time = System.currentTimeMillis(); time = time + 1622797200l; // JwtUtil_AliDemo01是根據上文生成token的工具類 String subject_token = JwtUtil_AliDemo01.generateToken(accountName, iss, time, privateKey); /** * 請求參數的封裝 */ Map<String,String> pram = new HashMap<>(); pram.put("grant_type",grant_type); pram.put("scope",scope); pram.put("redirect_url",redirect_url); pram.put("subject_token",subject_token); pram.put("subject_issuer",iss); /** * 發出post請求在repsonse返回中得到access_token等相關信息 * HttpUtils是一個http工具類 */ Optional<String> userInfoOptional = HttpUtils.post(FuYun_URL, pram, header,null); String userInfo = userInfoOptional.orNull(); System.out.println("userInfo====>" + userInfo); }catch (Exception e){ e.printStackTrace(); } } } /** * http工具類 */ public class HttpUtils { public static Optional<String> get(String uri) { return fetch(HttpUtils.QueryMethod.GET, uri, null, null, null); } public static Optional<String> get(String uri, Map<String, String> params, Map<String, String> headers) { return fetch(HttpUtils.QueryMethod.GET, uri, params, headers, null); } public static Optional<String> post(String uri, Map<String, String> params) { return fetch(HttpUtils.QueryMethod.POST, uri, params, null, null); } public static Optional<String> post(String uri, Map<String, String> params, Map<String, String> headers, String body) { return fetch(HttpUtils.QueryMethod.POST, uri, params, headers, body); } private static Optional<String> fetch(HttpUtils.QueryMethod method, String uri, Map<String, String> params, Map<String, String> headers, String body) { Optional<String> result = Optional.absent(); InputStream inputStream = null; try { String url = uri; if (StringUtils.isBlank(uri)) { return Optional.absent(); } if (params != null && params.size() > 0) { StringBuilder urlBuilder = new StringBuilder(); urlBuilder.append("?"); for (String key : params.keySet()) { String value = URLEncoder.encode(params.get(key), StandardCharsets.UTF_8.toString()); urlBuilder.append(key).append("=").append(value).append("&"); } String s = urlBuilder.toString(); url += s.substring(0, s.length() - 1); } URL u = new URL(url); HttpURLConnection urlConnection = (HttpURLConnection) u.openConnection(); urlConnection.setInstanceFollowRedirects(false); urlConnection.setConnectTimeout(15000); urlConnection.setReadTimeout(15000); if (method != null) { urlConnection.setRequestMethod(method.name()); } if (headers != null) { for (String key : headers.keySet()) { urlConnection.addRequestProperty(key, headers.get(key)); } } if (body != null) { urlConnection.setDoOutput(true); OutputStream out = urlConnection.getOutputStream(); out.write(body.getBytes()); out.flush(); out.close(); } int responseCode = urlConnection.getResponseCode(); inputStream = urlConnection.getInputStream(); if (responseCode == HttpStatus.OK.value()) { result = Optional.fromNullable(streamToString(inputStream)); } else if (responseCode == HttpStatus.MOVED_PERMANENTLY.value() || responseCode == HttpStatus.FOUND.value()) { //TODO:add redirect logic later on result = Optional.of("redirect url found!"); } } catch (MalformedURLException malformedURLException) { } catch (IOException e) { } catch (Exception e) { } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } return result; } static public String streamToString(InputStream in) throws IOException { StringBuilder outputBuilder = new StringBuilder(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in)); String s; while ((s = bufferedReader.readLine()) != null) { outputBuilder.append(s); } return outputBuilder.toString(); } public static enum QueryMethod { POST("post"), GET("get"), PUT("put"), DELETE("delete"); private String name; QueryMethod(String name) { this.name = name; } } }
OAuth模式集成接入指引
IDP身份管理配置。
登錄開發者門戶,單擊設置,進入身份管理配置頁面,單擊右上角添加,進行身份管理配置。
基礎信息設置。
App Name:客戶側應用名,最長10個字符,支持英文、數字。
授權類型:選擇令牌交換模式。
授權范圍:scope,選擇用戶管理和熱線,用于標識頒發的access_token可訪問的API范圍,防止越權調用。
是否免登工作臺:不用勾選。
回調地址:請填寫認證成功后的回調的URL,客服方用來接收并處智能聯絡中心頒發的授權碼。
開始URL:客戶方網站首頁。
憑證信息。
完善基礎信息設置后,單擊下一步,進入憑證信息頁面。此處為智能聯絡中心頒發的client_id和client_secret,用于驗證接入方。
單擊完成,授權類型即設置成功。
單擊上一步,可修改基礎信息設置(App Name除外)。
免登模式配置。
授權模式為令牌交換模式時,需要配置免登模式。免登模式選擇OAuth模式。
免登模式:OAuth模式。
免登訪問fuyun open api的redirect_url:用于在第三方系統中申請OAuth client接入時使用,例如:在Salesforce平臺中申請OAuth Client 可填入此處提供的redirect_url。
免登訪問XP工作臺redirect_url:用于在第三方系統中申請OAuth client接入時使用,例如:在Salesforce平臺中申請OAuth Client 可填入此處提供的redirect_url。
token endpoint:令牌公共端點,例如微軟的token endpoint為https://login.microsoftonline.de/common/oauth2/v2.0/token,可參考微軟官方文檔,其余OAuth平臺文檔類似。
user endpoint:UserInfo 端點,例如微軟的user endpoint為https://graph.microsoft.com/oidc/userinfo,可參考微軟官方文檔,其余OAuth平臺文檔類似。
第三方系統頒發的client id:從第三方系統獲取,例如從Salesforce平臺申請完成OAuth Client會獲得從Salesforce平臺頒發的client id。
第三方系統頒發的client secret:從第三方系統獲取,例如從Salesforce平臺申請完成OAuth Client會獲得從Salesforce平臺頒發的client secret。
scope:訪問范圍(用戶管理和熱線),輸入user_management,hotline,例如:在Salesforce平臺申請OAuth Client時會要求填入scope訪問范圍。
mapping field:客戶方UserInfo里與智能聯絡中心客服工作臺坐席映射的字段,例如:email、moblie、address等。
配置完成后,單擊保存,即配置成功。單擊上一步,支持返回憑證信息頁面。
獲取access_token。
OAuth模式集成接入有兩種方式獲取access_token,一種為前端直接獲取,另一種為客戶后端post訪問access_token置換接口獲取access_token。
前端方式獲取access_token。
以微軟為例,通過調用微軟的OAuth authorize endpoint,回調到智能聯絡中心端,繼而獲取訪問智能聯絡中心API的access token。https://login.microsoftonline.com/zxshirley163.onmicrosoft.com/oauth2/v2.0/authorize?client_id=<microsoft頒發的client_id>&response_type=code&redirect_uri=<注冊microsoft oauth接入時填入的redirect_url>。該次調用最終會將智能聯絡中心的access token放置到瀏覽器Cookie中(key為AC_TOKEN), 亦會返回json response(如下示例),后續集成步驟可根據需求取用cookie中的access_token或者json response中的access token皆可。
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vZGV2ZWxvcGVyLnJoaW5va2Vlbi5jb20iLCJleHAiOjE2MjY5NDY2OTUsInVzZXJfbmFtZSI6ImZ1eXVuLXRlc3QtdXNlciIsImp0aSI6ImQ0YmQ4MTM1LTZmZjYtNDZiNi05YTg2LTQ2MWNkMGVjNDI4NyIsImNsaWVudF9pZCI6ImlkcF90ZXN0XzE2UTRveiIsInNjb3BlIjpbImZ1eXVuLWRldiJdfQ.cDE0EuZaBRwsNf7WrbIPsZhw9juk5dpA2GtCEJ4WHf-iwz8tp9xnX4Kb4jmJqwtWjtrvz0mDeU8uFB31oiz4FzRQb30qhaCeJo7totjwZfTr4bI6bd8afb5C3kypgQUYAyg3wkMzF-6nKgnN_a9YViWp2vO1lq3gH7I5vA5CX6bACWu8OO7LtaD-nKf6JRCcdwY2CWDq_jl43mjz_oek0c8MBcnLL11PAk5VnZRYg7pO6AhPOUkqyAqwbBGcgkEw3pNR1aSTbL8-u69RczKaEgXB_lusshLEXeRK6uNlO8SZzx2BR0AG3nHSG9dAGEdaWMhPUR4gY488k4SYPFNNEQ", "token_type": "bearer", "expires_in": 2591999, "scope": "fuyun-dev", "iss": "http://developer.rhinokeen.com", "jti": "d4bd8135-6ff6-46b6-9a86-461cd0ec4287" }
后端方式獲取access_token。
請求頭header和請求參數的封裝
請求URL
URL:https://signin.rhinokeen.com/oauth/token_exchange。
請求類型:POST。
請求HEADER
字段
示例
描述
Authorization
"Authorization: Basic YWxpYmFiYS14aWFvZXI6YmNlMTllZDYtYTFhNC00NzA3LTgwZjAtYTM4OGY3MGUxNWQ3"
授權類型,接口使用http basic authentication認證方式Authorization= Basic Base64.encode(fuyun_client_id:fuyun_client_secret)
請求參數
字段
示例
描述
grant_type
"urn:ietf:params:oauth:grant-type:token-exchange"
授權類型。
scope
"user_management,hotline"
訪問范圍(用戶管理和熱線)。
redirect_url
"http://****.com/callback"
回調地址。
subject_token
-
第三方OAuth平臺的access token,例如Salesforce的頒發給當前用戶的access_token。
subject_issuer
-
在身份管理的基礎信息設置頁面,配置的APP Name。
返回示例。
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vZGV2ZWxvcGVyLnJoaW5va2Vlbi5jb20iLCJleHAiOjE2MjY5NDY2OTUsInVzZXJfbmFtZSI6ImZ1eXVuLXRlc3QtdXNlciIsImp0aSI6ImQ0YmQ4MTM1LTZmZjYtNDZiNi05YTg2LTQ2MWNkMGVjNDI4NyIsImNsaWVudF9pZCI6ImlkcF90ZXN0XzE2UTRveiIsInNjb3BlIjpbImZ1eXVuLWRldiJdfQ.cDE0EuZaBRwsNf7WrbIPsZhw9juk5dpA2GtCEJ4WHf-iwz8tp9xnX4Kb4jmJqwtWjtrvz0mDeU8uFB31oiz4FzRQb30qhaCeJo7totjwZfTr4bI6bd8afb5C3kypgQUYAyg3wkMzF-6nKgnN_a9YViWp2vO1lq3gH7I5vA5CX6bACWu8OO7LtaD-nKf6JRCcdwY2CWDq_jl43mjz_oek0c8MBcnLL11PAk5VnZRYg7pO6AhPOUkqyAqwbBGcgkEw3pNR1aSTbL8-u69RczKaEgXB_lusshLEXeRK6uNlO8SZzx2BR0AG3nHSG9dAGEdaWMhPUR4gY488k4SYPFNNEQ", "token_type": "bearer", "expires_in": 2591999, "scope": "fuyun-dev", "iss": "http://developer.rhinokeen.com", "jti": "d4bd8135-6ff6-46b6-9a86-461cd0ec4287" }
向Fuyun_IDP指定免登地址發出POST請求。
發送post請求的同時并攜帶您封裝好的參數,用表單傳輸格式。
獲取access_token,
Fuyun_IDP會通過response的形式返回access_token等信息。
SDK集成前端步驟
前端通過access_token對JS SDK的接入,具體可參見熱線SDK接入(新版)。JS SDK初始化成功可以正常調用JS接口。
錄音、通話詳情以及數據拉取
企業內部的坐席在自有的CRM系統上使用集成好的電話條進行通話,在通話結束后,您還可以調用API接口獲取通話詳情和錄音文件。
配置管理
PaaS方式
SaaS方式
坐席管理
創建坐席:選擇
頁面,切換到人員授權Tab,單擊頁面右上角新增人員,輸入基本信息,并把該坐席添加到對應的技能組中。刪除坐席:選擇
頁面,切換到人員授權Tab,在搜索框中輸入姓名/對外展示名,找到要刪除的坐席,單擊更多,選擇刪除,單擊確定,該坐席即被刪除。修改坐席信息:選擇
頁面,切換到人員授權Tab,在搜索框中輸入姓名/對外展示名,找到要修改信息的坐席,單擊小鉛筆圖標,即進入坐席信息修改頁面。支持修改坐席的真實姓名、對外展示名、服務權限、高級權限、上班設置、在線服務設置和熱線服務設置。查詢坐席信息:選擇
頁面,切換到人員授權Tab,在搜索框中輸入姓名/對外展示名,即可找到要查詢的坐席信息。
技能組管理
創建技能組:選擇
頁面,切換到技能組Tab,單擊...,單擊添加技能組,即可創建新的技能組。刪除技能組:選擇
頁面,切換到技能組Tab,搜索框中輸入需要刪除的技能組名稱,鼠標hover到該技能組名稱上,右側出現刪除按鈕,單擊刪除按鈕進行刪除。如果該技能組內還有坐席,請先把人員從該技能組中移除,再刪除技能組。修改技能組信息:選擇
頁面,切換到技能組Tab,搜索框中輸入需要修改信息的技能組名稱,即可修改該技能組名稱、對外展示名、應用渠道、轉交是否可見、實操培訓和描述,修改完后單擊保存,該技能組信息即可修改成功。查詢技能組信息:選擇
頁面,切換到技能組Tab,搜索框中輸入需要查詢信息的技能組名稱,即可查詢對應的技能組信息。
部門管理
創建部門:選擇
頁面,切換到技能組Tab,單擊技能組搜索框右側的+號,即可新建部門,輸入技能組分組名稱和描述,單擊保存按鈕,部門即創建成功。刪除部門:選擇
頁面,切換到技能組Tab,鼠標hover到需要刪除的部門名稱上,右側出現...按鈕,單擊刪除,再按照提示輸入部門名稱,單擊確認刪除即可成功刪除該部門。如果該部門中還有技能組,請先把該部門中的技能組刪除,再刪除部門。修改部門信息:選擇
頁面,切換到技能組Tab,找到需要更新信息的部門,單擊該部門名稱,即可修改該部門的技能組分組名稱和描述,修改后單擊保存即修改成功。查詢部門信息:選擇
頁面,切換到技能組Tab,即可查看所有的部門信息。
號碼管理
新增熱線號碼:選擇
頁面,切換到號碼管理Tab,選擇號碼,單擊新增,即可新增熱線號碼。刪除熱線號碼:
單個刪除:選擇
頁面,切換到號碼管理Tab,選擇號碼,選擇需要刪除的熱線號碼,單擊右側刪除按鈕,輸入該號碼以確認更換,單擊確定,該號碼即刪除成功。批量刪除:選擇
頁面,切換到號碼管理Tab,選擇號碼,在號碼最左側勾選需要刪除的熱線號碼,然后單擊下方批量刪除按鈕,輸入對應的號碼以確認刪除,單擊確定,勾選的所有號碼即刪除成功。
重置熱線號碼:
單個重置:選擇
頁面,切換到號碼管理Tab,選擇號碼,選擇需要重置的熱線號碼,單擊右側編輯按鈕,進入編輯熱線號碼頁面。描述:支持修改描述。
功能:號碼默認有呼入功能,支持設置號碼是否具有呼出功能。
呼入IVR:支持修改呼入的IVR。
呼出生效范圍:支持修改呼出生效范圍。
呼入/呼出滿意度調查:支持開啟/關閉呼入/呼出滿意度調查。
批量重置:選擇
頁面,切換到號碼管理Tab,選擇號碼,在號碼最左側勾選需要重置的熱線號碼,然后單擊下方批量重置,進入批量編輯熱線號碼頁面。描述:支持修改描述。
功能:號碼默認有呼入功能,支持設置號碼是否具有呼出功能。
呼入IVR:支持修改呼入的IVR。
呼出生效范圍:支持修改呼出生效范圍。
呼入/呼出滿意度調查:支持開啟/關閉呼入/呼出滿意度調查。
查詢熱線號碼
選擇
頁面,切換到號碼管理Tab,選擇號碼,支持查看所有的熱線號碼配置列表。