├── README.en.md
├── README.md
├── inbound
├── README.md
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── cn
│ │ └── ch3nnn
│ │ ├── InboundApplication.java
│ │ ├── common
│ │ ├── InitCacheLineDataRunner.java
│ │ └── ResultCode.java
│ │ ├── config
│ │ └── RedisConfig.java
│ │ ├── controller
│ │ └── OutboundController.java
│ │ ├── dto
│ │ ├── LineCountParam.java
│ │ ├── LineDataDto.java
│ │ └── OutboundParam.java
│ │ ├── esl
│ │ └── EslEventListener.java
│ │ ├── handle
│ │ ├── ExampleInboundClientOptionHandler.java
│ │ ├── HeartbeatEslEventHandler.java
│ │ ├── ReScheduleEslEventHandler.java
│ │ └── ServerConnectionListenerImpl.java
│ │ └── utils
│ │ ├── JsonUtils.java
│ │ ├── RedisUtils.java
│ │ └── Utils.java
│ └── resources
│ ├── LineTest.json
│ └── application.yml
└── outbound
├── pom.xml
└── src
└── main
├── java
└── cn
│ └── ch3nnn
│ ├── AbstractOutboundClientEventHandler.java
│ ├── OutboundApplication.java
│ ├── OutboundHandler.java
│ ├── PipelineFactory.java
│ ├── annotation
│ └── OutBoundEventName.java
│ ├── config
│ └── OutboundServerConfig.java
│ ├── handle
│ ├── ChannelAnswerOutboundEventHandler.java
│ ├── OutBoundEventHandler.java
│ └── PlayBackSTOPOutBoundEventHandler.java
│ ├── service
│ └── BridgeCallService.java
│ └── utils
│ └── ClassUtil.java
└── resources
└── test.wav
/README.en.md:
--------------------------------------------------------------------------------
1 | # springboot-freeswitch
2 |
3 | #### Description
4 | {**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
5 |
6 | #### Software Architecture
7 | Software architecture description
8 |
9 | #### Installation
10 |
11 | 1. xxxx
12 | 2. xxxx
13 | 3. xxxx
14 |
15 | #### Instructions
16 |
17 | 1. xxxx
18 | 2. xxxx
19 | 3. xxxx
20 |
21 | #### Contribution
22 |
23 | 1. Fork the repository
24 | 2. Create Feat_xxx branch
25 | 3. Commit your code
26 | 4. Create Pull Request
27 |
28 |
29 | #### Gitee Feature
30 |
31 | 1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
32 | 2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
33 | 3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
34 | 4. The most valuable open source project [GVP](https://gitee.com/gvp)
35 | 5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
36 | 6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 说明文档
2 |
3 | ## inbound 代码示例
4 | ### pom依赖
5 | ```xml
6 |
7 |
23 |
24 |
26 | 4.0.0
27 |
28 | freeswitch-esl-all
29 | link.thingscloud
30 | 1.6.4.RELEASE
31 |
32 |
33 |
34 | UTF-8
35 | UTF-8
36 |
37 | 4.1.65.Final
38 | 2.3.1.RELEASE
39 |
40 | 1.8
41 |
42 |
43 | 1.8
44 | 1.8
45 |
46 |
47 | freeswitch-esl-spring-boot-starter-example
48 | freeswitch-esl-spring-boot-starter-example-${project.version}
49 |
50 | Example project for Freeswitch Esl Spring Boot Starter
51 |
52 |
53 |
54 | ${project.groupId}
55 | freeswitch-esl-spring-boot-starter
56 | ${project.version}
57 |
58 |
59 | org.springframework.boot
60 | spring-boot-starter-web
61 |
62 |
63 | link.thingscloud
64 | spring-boot-common-aop
65 | 1.0.0-RELEASE
66 |
67 |
68 | org.springframework.boot
69 | spring-boot-devtools
70 | runtime
71 | true
72 |
73 |
74 | org.projectlombok
75 | lombok
76 | true
77 |
78 |
79 | org.springframework.boot
80 | spring-boot-starter-test
81 | test
82 |
83 |
84 |
85 | org.freeswitch.esl.client
86 | org.freeswitch.esl.client
87 | 0.9.2
88 |
89 |
90 |
91 |
92 | com.alibaba
93 | fastjson
94 | 1.2.78
95 |
96 |
97 |
98 |
99 | org.springframework.boot
100 | spring-boot-starter-data-redis
101 | 2.2.6.RELEASE
102 |
103 |
104 |
105 | org.apache.commons
106 | commons-pool2
107 | 2.6.2
108 |
109 |
110 |
111 |
112 |
113 | com.fasterxml.jackson.core
114 | jackson-core
115 |
116 |
117 | com.fasterxml.jackson.core
118 | jackson-databind
119 |
120 |
121 | com.fasterxml.jackson.core
122 | jackson-annotations
123 |
124 |
125 |
126 |
127 |
128 |
129 | ```
130 |
131 |
132 |
133 | ## outbound示例
134 |
135 | ### 修改dialplan配置
136 |
137 | 出于演示目的,这里修改/usr/local/freeswitch/conf/dialplan/default.xml,在文件开头部分添加一段:
138 |
139 | ```
140 |
141 |
142 |
143 |
144 |
145 | ```
146 | 即:当来电的被叫号码为400开头时,fs将利用socket,连接到localhost:8040
147 |
148 |
149 | ## 接口文档
150 | ### 一、机器人外呼发起
151 |
152 | * 接口地址: /callcenter/api/startOutbound
153 | * 请求方法: POST
154 | * 请求参数:
155 |
156 | * 返回数据:
157 | ```
158 | {
159 | code: 200,
160 | message: "success"
161 | data: null
162 | }
163 | message: 请求处理消息
164 | code = 200 请求处理成功
165 | code != 200 请求处理失败,警告消息提示:message内容
166 | ```
167 |
168 |
169 |
170 | ## 相关资料
171 | [freeswitch笔记](https://www.cnblogs.com/yjmyzz/p/freeswitch-esl-java-client-turorial.html)
172 |
173 | [github: freeswitch 事件套接字基于 netty 4 并具有一些新功能](https://github.com/zhouhailin/freeswitch-esl-all)
174 |
--------------------------------------------------------------------------------
/inbound/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch3nnn/springboot-freeswitch/d3ed2a9d55da36fdbb56398ae2cdd86b1e34f64e/inbound/README.md
--------------------------------------------------------------------------------
/inbound/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
21 | 4.0.0
22 |
23 | freeswitch-esl-all
24 | link.thingscloud
25 | 1.6.4.RELEASE
26 |
27 |
28 |
29 | UTF-8
30 | UTF-8
31 |
32 | 4.1.65.Final
33 | 2.3.1.RELEASE
34 |
35 | 1.8
36 |
37 |
38 | 1.8
39 | 1.8
40 |
41 |
42 | freeswitch-esl-spring-boot-starter-example
43 | freeswitch-esl-spring-boot-starter-example-${project.version}
44 |
45 | Example project for Freeswitch Esl Spring Boot Starter
46 |
47 |
48 |
49 | ${project.groupId}
50 | freeswitch-esl-spring-boot-starter
51 | ${project.version}
52 |
53 |
54 | org.springframework.boot
55 | spring-boot-starter-web
56 |
57 |
58 | link.thingscloud
59 | spring-boot-common-aop
60 | 1.0.0-RELEASE
61 |
62 |
63 | org.springframework.boot
64 | spring-boot-devtools
65 | runtime
66 | true
67 |
68 |
69 | org.projectlombok
70 | lombok
71 | true
72 |
73 |
74 | org.springframework.boot
75 | spring-boot-starter-test
76 | test
77 |
78 |
79 |
80 | org.freeswitch.esl.client
81 | org.freeswitch.esl.client
82 | 0.9.2
83 |
84 |
85 |
86 |
87 | com.alibaba
88 | fastjson
89 | 1.2.78
90 |
91 |
92 |
93 |
94 | org.springframework.boot
95 | spring-boot-starter-data-redis
96 | 2.2.6.RELEASE
97 |
98 |
99 |
100 | org.apache.commons
101 | commons-pool2
102 | 2.6.2
103 |
104 |
105 |
106 |
107 |
108 | com.fasterxml.jackson.core
109 | jackson-core
110 |
111 |
112 | com.fasterxml.jackson.core
113 | jackson-databind
114 |
115 |
116 | com.fasterxml.jackson.core
117 | jackson-annotations
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/InboundApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package cn.ch3nnn;
19 |
20 | import org.springframework.boot.SpringApplication;
21 | import org.springframework.boot.autoconfigure.SpringBootApplication;
22 |
23 |
24 | // @EnableFreeswitchEslAutoConfiguration
25 | @SpringBootApplication
26 | public class InboundApplication {
27 |
28 | public static void main(String[] args) {
29 | SpringApplication.run(InboundApplication.class, args);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/common/InitCacheLineDataRunner.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.common;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.boot.CommandLineRunner;
6 | import org.springframework.data.redis.core.RedisTemplate;
7 | import org.springframework.stereotype.Component;
8 |
9 | /**
10 | * spring容器加载完自动监听 初始化线路分配数据到缓存
11 | *
12 | * @Author ChenTong
13 | * @Date 2021/11/1 09:53
14 | */
15 | @Slf4j
16 | @Component
17 | public class InitCacheLineDataRunner implements CommandLineRunner {
18 |
19 | @Autowired
20 | private RedisTemplate redisTemplate;
21 |
22 | @Override
23 | public void run(String... args) throws Exception {
24 | log.info("Start Cache LineData ....");
25 | // TODO 初始化加载线路数据
26 | // String path = "src/main/resources/LineTest.json";
27 | // final String jsonData = JsonUtil.readJsonFile(path);
28 | // final LineDataDto parse = JSON.parseObject(jsonData, LineDataDto.class);
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/common/ResultCode.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.common;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 |
6 | import java.io.Serializable;
7 |
8 | /**
9 | * SpringBoot 统一响应格式
10 | *
11 | * @Author ChenTong
12 | * @Date 2021/8/27 13:55
13 | */
14 | @Data
15 | @AllArgsConstructor
16 | public class ResultCode implements Serializable {
17 |
18 | private final static int SUCCESS_CODE = 1;
19 | private final static int ERROR_CODE = 0;
20 | private final static String SUCCESS_MESSAGE = "请求成功";
21 | private final static String ERROR_MESSAGE = "请求失败";
22 |
23 | private Integer code;
24 | private String message;
25 | private Object data;
26 |
27 |
28 | /**
29 | * 请求成功
30 | *
31 | * @return 视图模型实例
32 | */
33 | public static ResultCode success() {
34 | return success(SUCCESS_MESSAGE);
35 | }
36 |
37 | /**
38 | * 请求成功
39 | *
40 | * @param data 响应数据
41 | * @return 视图模型实例
42 | */
43 | public static ResultCode success(Object data) {
44 | return success(SUCCESS_MESSAGE, data);
45 | }
46 |
47 | /**
48 | * 请求成功
49 | *
50 | * @param message 响应信息
51 | * @return 视图模型实例
52 | */
53 | public static ResultCode success(String message) {
54 | return success(message, null);
55 | }
56 |
57 | /**
58 | * 请求成功
59 | *
60 | * @param message 响应信息
61 | * @param data 响应数据
62 | * @return 视图模型实例
63 | */
64 | public static ResultCode success(String message, Object data) {
65 | return new ResultCode(SUCCESS_CODE, message, data);
66 | }
67 |
68 |
69 | /**
70 | * 请求失败
71 | *
72 | * @return 视图模型实例
73 | */
74 | public static ResultCode error() {
75 | return error(ERROR_MESSAGE);
76 | }
77 |
78 |
79 | /**
80 | * 请求失败
81 | *
82 | * @param message 异常信息
83 | * @return 视图模型实例
84 | */
85 | public static ResultCode error(String message) {
86 | return error(message, null);
87 | }
88 |
89 | /**
90 | * 请求失败
91 | *
92 | * @param message 异常信息
93 | * @param data 响应数据
94 | * @return 视图模型实例
95 | */
96 | public static ResultCode error(String message, Object data) {
97 | return new ResultCode(ERROR_CODE, message, data);
98 | }
99 |
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/config/RedisConfig.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.config;
2 |
3 | import com.fasterxml.jackson.annotation.JsonAutoDetect;
4 | import com.fasterxml.jackson.annotation.PropertyAccessor;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.data.redis.connection.RedisConnectionFactory;
9 | import org.springframework.data.redis.core.RedisTemplate;
10 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
11 | import org.springframework.data.redis.serializer.StringRedisSerializer;
12 |
13 | /**
14 | * @Author ChenTong
15 | * @Date 2021/11/1 09:59
16 | */
17 | @Configuration
18 | public class RedisConfig {
19 | @Bean
20 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
21 | RedisTemplate redisTemplate = new RedisTemplate<>();
22 | //设置工厂链接
23 | redisTemplate.setConnectionFactory(redisConnectionFactory);
24 | //设置自定义序列化方式
25 | setSerializeConfig(redisTemplate, redisConnectionFactory);
26 | return redisTemplate;
27 | }
28 |
29 | private void setSerializeConfig(RedisTemplate redisTemplate, RedisConnectionFactory redisConnectionFactory) {
30 | //对字符串采取普通的序列化方式 适用于key 因为我们一般采取简单字符串作为key
31 | StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
32 | //普通的string类型的key采用 普通序列化方式
33 | redisTemplate.setKeySerializer(stringRedisSerializer);
34 | //普通hash类型的key也使用 普通序列化方式
35 | redisTemplate.setHashKeySerializer(stringRedisSerializer);
36 | //解决查询缓存转换异常的问题 大家不能理解就直接用就可以了 这是springboot自带的jackson序列化类,但是会有一定问题
37 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
38 | ObjectMapper om = new ObjectMapper();
39 | om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
40 | om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
41 | jackson2JsonRedisSerializer.setObjectMapper(om);
42 | //普通的值采用jackson方式自动序列化
43 | redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
44 | //hash类型的值也采用jackson方式序列化
45 | redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
46 | //属性设置完成afterPropertiesSet就会被调用,可以对设置不成功的做一些默认处理
47 | redisTemplate.afterPropertiesSet();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/controller/OutboundController.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.controller;
2 |
3 | import cn.ch3nnn.common.ResultCode;
4 | import cn.ch3nnn.dto.OutboundParam;
5 | import link.thingscloud.freeswitch.esl.InboundClient;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.data.redis.core.RedisTemplate;
8 | import org.springframework.web.bind.annotation.RequestBody;
9 | import org.springframework.web.bind.annotation.RequestMapping;
10 | import org.springframework.web.bind.annotation.RequestMethod;
11 | import org.springframework.web.bind.annotation.RestController;
12 |
13 |
14 | /**
15 | * 外呼中心接口
16 | *
17 | * @Author ChenTong
18 | * @Date 2021/10/28 15:03
19 | */
20 | @RestController
21 | @RequestMapping("/callcenter/api")
22 | public class OutboundController {
23 |
24 | @Autowired
25 | private RedisTemplate redisTemplate;
26 |
27 | @Autowired
28 | private InboundClient inboundClient;
29 |
30 |
31 | /**
32 | * 机器人外呼发起
33 | *
34 | * @param outboundParam
35 | * @return
36 | */
37 | @RequestMapping(value = "/startOutbound", method = RequestMethod.POST)
38 | public ResultCode outBound(@RequestBody(required = false) OutboundParam outboundParam) {
39 | // 测试本地
40 | final String originate = inboundClient.sendAsyncApiCommand("127.0.0.1:8021", "originate", "user/1010 &echo outbound");
41 | return ResultCode.success(originate);
42 |
43 | }
44 |
45 | /**
46 | * 当前主叫号可用线路数量
47 | *
48 | * @param
49 | * @return
50 | */
51 | @RequestMapping(value = "/currentLineCount", method = RequestMethod.GET)
52 | public ResultCode lineCount() {
53 | return ResultCode.success();
54 |
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/dto/LineCountParam.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.dto;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * 当前主叫号可用线路数量
7 | *
8 | * @Author ChenTong
9 | * @Date 2021/11/4 14:36
10 | */
11 | public class LineCountParam {
12 |
13 | /**
14 | * 授权 id
15 | */
16 | @JSONField(name = "appId")
17 | private String appId;
18 |
19 | /**
20 | * 授权 key(已加密过的 key)
21 | */
22 | @JSONField(name = "appKey")
23 | private String appKey;
24 |
25 | /**
26 | * 主叫号码即电话线路的号码 (请求 caller 下面线路号码为空闲线路)
27 | */
28 | @JSONField(name = "caller")
29 | private String caller;
30 |
31 | /**
32 | * 时间戳,精确到毫秒(1550645131000)
33 | */
34 | @JSONField(name = "timeStamp")
35 | private String timeStamp;
36 |
37 |
38 | /**
39 | * 按顺序拼接字符串后 md5 加密
40 | */
41 | @JSONField(name = "sign")
42 | private String sign;
43 |
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/dto/LineDataDto.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.dto;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | /**
8 | * @Author ChenTong
9 | * @Date 2021/10/28 21:55
10 | */
11 | @NoArgsConstructor
12 | @Data
13 | public class LineDataDto {
14 |
15 | /**
16 | * $0
17 | */
18 | @JSONField(name = "0")
19 | public _$0DTO $0;
20 | /**
21 | * $1
22 | */
23 | @JSONField(name = "1")
24 | public _$1DTO $1;
25 | /**
26 | * $2
27 | */
28 | @JSONField(name = "2")
29 | public _$2DTO $2;
30 | /**
31 | * $3
32 | */
33 | @JSONField(name = "3")
34 | public _$3DTO $3;
35 | /**
36 | * $4
37 | */
38 | @JSONField(name = "4")
39 | public _$4DTO $4;
40 | /**
41 | * $5
42 | */
43 | @JSONField(name = "5")
44 | public _$5DTO $5;
45 |
46 | /**
47 | * _$0DTO
48 | */
49 | @NoArgsConstructor
50 | @Data
51 | public static class _$0DTO {
52 | /**
53 | * trunkId
54 | */
55 | @JSONField(name = "trunk_id")
56 | public Integer trunkId;
57 | /**
58 | * trunkState
59 | */
60 | @JSONField(name = "trunk_state")
61 | public Integer trunkState;
62 | /**
63 | * appId
64 | */
65 | @JSONField(name = "app_id")
66 | public String appId;
67 | /**
68 | * trunkPhoneNumber
69 | */
70 | @JSONField(name = "trunk_phone_number")
71 | public String trunkPhoneNumber;
72 | /**
73 | * trunkLineType
74 | */
75 | @JSONField(name = "trunk_line_type")
76 | public Integer trunkLineType;
77 | /**
78 | * srcIps
79 | */
80 | @JSONField(name = "src_ips")
81 | public String srcIps;
82 | /**
83 | * gatewayName
84 | */
85 | @JSONField(name = "gateway_name")
86 | public String gatewayName;
87 | /**
88 | * webHost
89 | */
90 | @JSONField(name = "web_host")
91 | public String webHost;
92 | /**
93 | * webPort
94 | */
95 | @JSONField(name = "web_port")
96 | public String webPort;
97 | /**
98 | * callUuid
99 | */
100 | @JSONField(name = "call_uuid")
101 | public Integer callUuid;
102 | /**
103 | * useOss
104 | */
105 | @JSONField(name = "USE_OSS")
106 | public String useOss;
107 | /**
108 | * remoteRecordPath
109 | */
110 | @JSONField(name = "REMOTE_RECORD_PATH")
111 | public String remoteRecordPath;
112 | /**
113 | * remoteTtsPath
114 | */
115 | @JSONField(name = "REMOTE_TTS_PATH")
116 | public String remoteTtsPath;
117 | }
118 |
119 | /**
120 | * _$1DTO
121 | */
122 | @NoArgsConstructor
123 | @Data
124 | public static class _$1DTO {
125 | /**
126 | * trunkId
127 | */
128 | @JSONField(name = "trunk_id")
129 | public Integer trunkId;
130 | /**
131 | * trunkState
132 | */
133 | @JSONField(name = "trunk_state")
134 | public Integer trunkState;
135 | /**
136 | * appId
137 | */
138 | @JSONField(name = "app_id")
139 | public String appId;
140 | /**
141 | * trunkPhoneNumber
142 | */
143 | @JSONField(name = "trunk_phone_number")
144 | public String trunkPhoneNumber;
145 | /**
146 | * trunkLineType
147 | */
148 | @JSONField(name = "trunk_line_type")
149 | public Integer trunkLineType;
150 | /**
151 | * srcIps
152 | */
153 | @JSONField(name = "src_ips")
154 | public String srcIps;
155 | /**
156 | * gatewayName
157 | */
158 | @JSONField(name = "gateway_name")
159 | public String gatewayName;
160 | /**
161 | * webHost
162 | */
163 | @JSONField(name = "web_host")
164 | public String webHost;
165 | /**
166 | * webPort
167 | */
168 | @JSONField(name = "web_port")
169 | public String webPort;
170 | /**
171 | * callUuid
172 | */
173 | @JSONField(name = "call_uuid")
174 | public Integer callUuid;
175 | /**
176 | * useOss
177 | */
178 | @JSONField(name = "USE_OSS")
179 | public String useOss;
180 | /**
181 | * remoteRecordPath
182 | */
183 | @JSONField(name = "REMOTE_RECORD_PATH")
184 | public String remoteRecordPath;
185 | /**
186 | * remoteTtsPath
187 | */
188 | @JSONField(name = "REMOTE_TTS_PATH")
189 | public String remoteTtsPath;
190 | }
191 |
192 | /**
193 | * _$2DTO
194 | */
195 | @NoArgsConstructor
196 | @Data
197 | public static class _$2DTO {
198 | /**
199 | * trunkId
200 | */
201 | @JSONField(name = "trunk_id")
202 | public Integer trunkId;
203 | /**
204 | * trunkState
205 | */
206 | @JSONField(name = "trunk_state")
207 | public Integer trunkState;
208 | /**
209 | * appId
210 | */
211 | @JSONField(name = "app_id")
212 | public String appId;
213 | /**
214 | * trunkPhoneNumber
215 | */
216 | @JSONField(name = "trunk_phone_number")
217 | public String trunkPhoneNumber;
218 | /**
219 | * trunkLineType
220 | */
221 | @JSONField(name = "trunk_line_type")
222 | public Integer trunkLineType;
223 | /**
224 | * srcIps
225 | */
226 | @JSONField(name = "src_ips")
227 | public String srcIps;
228 | /**
229 | * gatewayName
230 | */
231 | @JSONField(name = "gateway_name")
232 | public String gatewayName;
233 | /**
234 | * webHost
235 | */
236 | @JSONField(name = "web_host")
237 | public String webHost;
238 | /**
239 | * webPort
240 | */
241 | @JSONField(name = "web_port")
242 | public String webPort;
243 | /**
244 | * callUuid
245 | */
246 | @JSONField(name = "call_uuid")
247 | public Integer callUuid;
248 | /**
249 | * useOss
250 | */
251 | @JSONField(name = "USE_OSS")
252 | public String useOss;
253 | /**
254 | * remoteRecordPath
255 | */
256 | @JSONField(name = "REMOTE_RECORD_PATH")
257 | public String remoteRecordPath;
258 | /**
259 | * remoteTtsPath
260 | */
261 | @JSONField(name = "REMOTE_TTS_PATH")
262 | public String remoteTtsPath;
263 | }
264 |
265 | /**
266 | * _$3DTO
267 | */
268 | @NoArgsConstructor
269 | @Data
270 | public static class _$3DTO {
271 | /**
272 | * trunkId
273 | */
274 | @JSONField(name = "trunk_id")
275 | public Integer trunkId;
276 | /**
277 | * trunkState
278 | */
279 | @JSONField(name = "trunk_state")
280 | public Integer trunkState;
281 | /**
282 | * appId
283 | */
284 | @JSONField(name = "app_id")
285 | public String appId;
286 | /**
287 | * trunkPhoneNumber
288 | */
289 | @JSONField(name = "trunk_phone_number")
290 | public String trunkPhoneNumber;
291 | /**
292 | * trunkLineType
293 | */
294 | @JSONField(name = "trunk_line_type")
295 | public Integer trunkLineType;
296 | /**
297 | * srcIps
298 | */
299 | @JSONField(name = "src_ips")
300 | public String srcIps;
301 | /**
302 | * gatewayName
303 | */
304 | @JSONField(name = "gateway_name")
305 | public String gatewayName;
306 | /**
307 | * webHost
308 | */
309 | @JSONField(name = "web_host")
310 | public String webHost;
311 | /**
312 | * webPort
313 | */
314 | @JSONField(name = "web_port")
315 | public String webPort;
316 | /**
317 | * callUuid
318 | */
319 | @JSONField(name = "call_uuid")
320 | public Integer callUuid;
321 | /**
322 | * useOss
323 | */
324 | @JSONField(name = "USE_OSS")
325 | public String useOss;
326 | /**
327 | * remoteRecordPath
328 | */
329 | @JSONField(name = "REMOTE_RECORD_PATH")
330 | public String remoteRecordPath;
331 | /**
332 | * remoteTtsPath
333 | */
334 | @JSONField(name = "REMOTE_TTS_PATH")
335 | public String remoteTtsPath;
336 | }
337 |
338 | /**
339 | * _$4DTO
340 | */
341 | @NoArgsConstructor
342 | @Data
343 | public static class _$4DTO {
344 | /**
345 | * trunkId
346 | */
347 | @JSONField(name = "trunk_id")
348 | public Integer trunkId;
349 | /**
350 | * trunkState
351 | */
352 | @JSONField(name = "trunk_state")
353 | public Integer trunkState;
354 | /**
355 | * appId
356 | */
357 | @JSONField(name = "app_id")
358 | public String appId;
359 | /**
360 | * trunkPhoneNumber
361 | */
362 | @JSONField(name = "trunk_phone_number")
363 | public String trunkPhoneNumber;
364 | /**
365 | * trunkLineType
366 | */
367 | @JSONField(name = "trunk_line_type")
368 | public Integer trunkLineType;
369 | /**
370 | * srcIps
371 | */
372 | @JSONField(name = "src_ips")
373 | public String srcIps;
374 | /**
375 | * gatewayName
376 | */
377 | @JSONField(name = "gateway_name")
378 | public String gatewayName;
379 | /**
380 | * useOss
381 | */
382 | @JSONField(name = "USE_OSS")
383 | public String useOss;
384 | /**
385 | * remoteRecordPath
386 | */
387 | @JSONField(name = "REMOTE_RECORD_PATH")
388 | public String remoteRecordPath;
389 | /**
390 | * remoteTtsPath
391 | */
392 | @JSONField(name = "REMOTE_TTS_PATH")
393 | public String remoteTtsPath;
394 | /**
395 | * webHost
396 | */
397 | @JSONField(name = "web_host")
398 | public String webHost;
399 | /**
400 | * webPort
401 | */
402 | @JSONField(name = "web_port")
403 | public String webPort;
404 | /**
405 | * callUuid
406 | */
407 | @JSONField(name = "call_uuid")
408 | public Integer callUuid;
409 | }
410 |
411 | /**
412 | * _$5DTO
413 | */
414 | @NoArgsConstructor
415 | @Data
416 | public static class _$5DTO {
417 | /**
418 | * trunkId
419 | */
420 | @JSONField(name = "trunk_id")
421 | public Integer trunkId;
422 | /**
423 | * trunkState
424 | */
425 | @JSONField(name = "trunk_state")
426 | public Integer trunkState;
427 | /**
428 | * appId
429 | */
430 | @JSONField(name = "app_id")
431 | public String appId;
432 | /**
433 | * trunkPhoneNumber
434 | */
435 | @JSONField(name = "trunk_phone_number")
436 | public String trunkPhoneNumber;
437 | /**
438 | * trunkLineType
439 | */
440 | @JSONField(name = "trunk_line_type")
441 | public Integer trunkLineType;
442 | /**
443 | * srcIps
444 | */
445 | @JSONField(name = "src_ips")
446 | public String srcIps;
447 | /**
448 | * gatewayName
449 | */
450 | @JSONField(name = "gateway_name")
451 | public String gatewayName;
452 | /**
453 | * webHost
454 | */
455 | @JSONField(name = "web_host")
456 | public String webHost;
457 | /**
458 | * webPort
459 | */
460 | @JSONField(name = "web_port")
461 | public String webPort;
462 | /**
463 | * callUuid
464 | */
465 | @JSONField(name = "call_uuid")
466 | public Integer callUuid;
467 | /**
468 | * useOss
469 | */
470 | @JSONField(name = "USE_OSS")
471 | public String useOss;
472 | /**
473 | * remoteRecordPath
474 | */
475 | @JSONField(name = "REMOTE_RECORD_PATH")
476 | public String remoteRecordPath;
477 | /**
478 | * remoteTtsPath
479 | */
480 | @JSONField(name = "REMOTE_TTS_PATH")
481 | public String remoteTtsPath;
482 | }
483 |
484 | }
485 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/dto/OutboundParam.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.dto;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | /**
8 | * 机器人外呼发起
9 | *
10 | * @Author ChenTong
11 | * @Date 2021/10/28 15:12
12 | */
13 |
14 | @NoArgsConstructor
15 | @Data
16 | public class OutboundParam {
17 |
18 |
19 | /**
20 | * 主叫号码即电话线路的号码
21 | */
22 | @JSONField(name = "caller")
23 | private String caller;
24 | /**
25 | * 授权id
26 | */
27 | @JSONField(name = "appId")
28 | private String appId;
29 | /**
30 | * 授权 key(已加密过的 key)
31 | */
32 | @JSONField(name = "appKey")
33 | private String appKey;
34 | /**
35 | * 语义模板xml
36 | */
37 | @JSONField(name = "xmlFileData")
38 | private String xmlFileData;
39 | /**
40 | * 被叫号码即被呼叫的用户的号码
41 | */
42 | @JSONField(name = "callee")
43 | private String callee;
44 | /**
45 | * 模板名称
46 | */
47 | @JSONField(name = "xmlFileName")
48 | private String xmlFileName;
49 | /**
50 | * 模板uuid(业务uuid)
51 | */
52 | @JSONField(name = "uuid")
53 | private String uuid;
54 | /**
55 | * 按顺序拼接字符串后 md5 加密
56 | */
57 | @JSONField(name = "sign")
58 | private String sign;
59 |
60 | /**
61 | * 时间戳,精确到毫秒(1550645131000)
62 | */
63 | @JSONField(name = "timeStamp")
64 | private String timeStamp;
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/esl/EslEventListener.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.esl;
2 |
3 | import link.thingscloud.freeswitch.esl.IEslEventListener;
4 | import link.thingscloud.freeswitch.esl.InboundClient;
5 | import link.thingscloud.freeswitch.esl.spring.boot.starter.annotation.EslEventName;
6 | import link.thingscloud.freeswitch.esl.spring.boot.starter.handler.EslEventHandler;
7 | import link.thingscloud.freeswitch.esl.transport.event.EslEvent;
8 | import link.thingscloud.freeswitch.esl.util.ArrayUtils;
9 | import link.thingscloud.freeswitch.esl.util.StringUtils;
10 | import lombok.extern.slf4j.Slf4j;
11 | import org.springframework.beans.factory.InitializingBean;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.stereotype.Component;
14 | import org.springframework.util.CollectionUtils;
15 |
16 | import java.util.*;
17 |
18 | /**
19 | * Esl 事件监听器
20 | *
21 | * @Author ChenTong
22 | * @Date 2021/11/1 11:13
23 | */
24 | @Slf4j
25 | @Component
26 | public class EslEventListener implements IEslEventListener, InitializingBean {
27 |
28 | @Autowired
29 | private InboundClient inboundClient;
30 |
31 | @Autowired
32 | private final List eslEventHandlers = Collections.emptyList();
33 |
34 | private final Map> handlerTable = new HashMap<>(16);
35 |
36 | private void handleEslEvent(String addr, EslEvent event) {
37 | String eventName = event.getEventName();
38 | List handlers = handlerTable.get(eventName);
39 | if (!CollectionUtils.isEmpty(handlers)) {
40 | handlers.forEach(eventHandler -> eventHandler.handle(addr, event));
41 | }
42 | }
43 |
44 | @Override
45 | public void eventReceived(String addr, EslEvent event) {
46 | handleEslEvent(addr, event);
47 | }
48 |
49 | @Override
50 | public void backgroundJobResultReceived(String addr, EslEvent event) {
51 | handleEslEvent(addr, event);
52 | }
53 |
54 | @Override
55 | public void afterPropertiesSet() {
56 | log.info("IEslEventListener init ...");
57 | for (EslEventHandler eventHandler : eslEventHandlers) {
58 | EslEventName eventName = eventHandler.getClass().getAnnotation(EslEventName.class);
59 | if (eventName == null) {
60 | // FIXED : AOP
61 | eventName = eventHandler.getClass().getSuperclass().getAnnotation(EslEventName.class);
62 | }
63 | if (eventName == null || ArrayUtils.isEmpty(eventName.value())) {
64 | continue;
65 | }
66 | for (String value : eventName.value()) {
67 | if (StringUtils.isBlank(value)) {
68 | continue;
69 | }
70 | log.info("IEslEventListener add EventName[{}], EventHandler[{}] ...", value, eventHandler.getClass());
71 | handlerTable.computeIfAbsent(value, k -> new ArrayList<>(4)).add(eventHandler);
72 |
73 | }
74 | }
75 | inboundClient.option().addListener(this);
76 | }
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/handle/ExampleInboundClientOptionHandler.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.handle;
2 |
3 | import link.thingscloud.freeswitch.esl.inbound.option.InboundClientOption;
4 | import link.thingscloud.freeswitch.esl.spring.boot.starter.handler.AbstractInboundClientOptionHandler;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.springframework.stereotype.Component;
7 |
8 |
9 | @Slf4j
10 | @Component
11 | public class ExampleInboundClientOptionHandler extends AbstractInboundClientOptionHandler {
12 |
13 | /**
14 | * {@inheritDoc}
15 | */
16 | @Override
17 | protected void intercept(InboundClientOption inboundClientOption) {
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/handle/HeartbeatEslEventHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package cn.ch3nnn.handle;
19 |
20 | import link.thingscloud.freeswitch.esl.constant.EventNames;
21 | import link.thingscloud.freeswitch.esl.spring.boot.starter.annotation.EslEventName;
22 | import link.thingscloud.freeswitch.esl.spring.boot.starter.handler.EslEventHandler;
23 | import link.thingscloud.freeswitch.esl.transport.event.EslEvent;
24 | import lombok.extern.slf4j.Slf4j;
25 | import org.springframework.stereotype.Component;
26 |
27 | @Slf4j
28 | @EslEventName(EventNames.HEARTBEAT)
29 | @Component
30 | public class HeartbeatEslEventHandler implements EslEventHandler {
31 |
32 | @Override
33 | public void handle(String addr, EslEvent event) {
34 | log.info("HeartbeatEslEventHandler handle addr[{}] EslEvent[{}].", addr, event);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/handle/ReScheduleEslEventHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package cn.ch3nnn.handle;
19 |
20 | import link.thingscloud.freeswitch.esl.InboundClient;
21 | import link.thingscloud.freeswitch.esl.constant.EventNames;
22 | import link.thingscloud.freeswitch.esl.spring.boot.starter.annotation.EslEventName;
23 | import link.thingscloud.freeswitch.esl.spring.boot.starter.handler.AbstractEslEventHandler;
24 | import link.thingscloud.freeswitch.esl.transport.event.EslEvent;
25 | import link.thingscloud.spring.boot.common.aop.Logging;
26 | import lombok.extern.slf4j.Slf4j;
27 | import org.springframework.beans.factory.annotation.Autowired;
28 | import org.springframework.stereotype.Component;
29 |
30 |
31 | @Slf4j
32 | @EslEventName({EventNames.CHANNEL_HANGUP, EventNames.CHANNEL_HANGUP_COMPLETE})
33 | @Component
34 | public class ReScheduleEslEventHandler extends AbstractEslEventHandler {
35 |
36 | @Autowired
37 | private InboundClient inboundClient;
38 |
39 | @Logging
40 | @Override
41 | public void handle(String addr, EslEvent event) {
42 | log.info("ReScheduleEslEventHandler handle addr[{}] EslEvent[{}].", addr, event);
43 | log.info("{}", inboundClient);
44 | // EslMessage eslMessage = inboundClient.sendSyncApiCommand(addr, "version", null);
45 | // log.info("{}", EslHelper.formatEslMessage(eslMessage));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/handle/ServerConnectionListenerImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package cn.ch3nnn.handle;
19 |
20 | import link.thingscloud.freeswitch.esl.ServerConnectionListener;
21 | import link.thingscloud.freeswitch.esl.inbound.option.ServerOption;
22 | import lombok.extern.slf4j.Slf4j;
23 | import org.springframework.stereotype.Service;
24 |
25 |
26 | @Slf4j
27 | @Service
28 | public class ServerConnectionListenerImpl implements ServerConnectionListener {
29 | /**
30 | * {@inheritDoc}
31 | */
32 | @Override
33 | public void onOpened(ServerOption serverOption) {
34 | log.info("onOpened serverOption : {}", serverOption);
35 | }
36 |
37 | /**
38 | * {@inheritDoc}
39 | */
40 | @Override
41 | public void onClosed(ServerOption serverOption) {
42 | log.info("onClosed serverOption : {}", serverOption);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/utils/JsonUtils.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.utils;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.FileReader;
5 | import java.io.IOException;
6 |
7 | /**
8 | * @Author ChenTong
9 | * @Date 2021/10/28 22:35
10 | */
11 | public class JsonUtils {
12 |
13 |
14 | /**
15 | * 读取json文件
16 | *
17 | * @param jsonFilePath 文件路径
18 | * @return 文件内容数据
19 | */
20 | public static String readJsonFile(String jsonFilePath) {
21 | StringBuilder jsonStr = new StringBuilder();
22 | try {
23 | String lineTxt;
24 | final FileReader fileReader = new FileReader(jsonFilePath);
25 | BufferedReader bufferedReader = new BufferedReader(fileReader);
26 | while ((lineTxt = bufferedReader.readLine()) != null) {
27 | jsonStr.append(lineTxt);
28 | }
29 | // 格式化json存在换行符需要替换
30 | return jsonStr.toString().replaceAll("[ \n\r]","");
31 | } catch (IOException e) {
32 | e.printStackTrace();
33 | }
34 | return null;
35 | }
36 |
37 |
38 |
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/inbound/src/main/java/cn/ch3nnn/utils/RedisUtils.java:
--------------------------------------------------------------------------------
1 | package cn.ch3nnn.utils;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.data.redis.core.RedisTemplate;
6 | import org.springframework.stereotype.Component;
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 | import java.util.concurrent.TimeUnit;
11 |
12 | /**
13 | * @author chentong
14 | */
15 | @Component
16 | @Slf4j
17 | public class RedisUtils {
18 |
19 | @Autowired
20 | private RedisTemplate redisTemplate;
21 |
22 | public boolean expire(String key, long expire) {
23 | try {
24 | // 这儿没有ops什么的是因为每种数据类型都能设置过期时间
25 | redisTemplate.expire(key, expire, TimeUnit.SECONDS);
26 | return true;
27 | } catch (Exception e) {
28 | log.error("redis set key expire exception:" + e);
29 | return false;
30 | }
31 | }
32 |
33 | /*hash数据类型方法 opsForHash 表示是操作字符串类型*/
34 |
35 | /**
36 | * @param key 健
37 | * @param field 属性
38 | * @param value 值
39 | * @return
40 | */
41 | public boolean hset(String key, String field, Object value) {
42 | try {
43 | redisTemplate.opsForHash().put(key, field, value);
44 | return true;
45 | } catch (Exception e) {
46 | log.error("redis hset eror,key:{},field:{},value:{}", key, field, value);
47 | return false;
48 | }
49 | }
50 |
51 | /**
52 | * @param key
53 | * @param field
54 | * @param value
55 | * @param seconds(秒) 过期时间
56 | * @return
57 | */
58 | public boolean hset(String key, String field, Object value, long seconds) {
59 | try {
60 | redisTemplate.opsForHash().put(key, field, value);
61 | // 调用通用方法设置过期时间
62 | expire(key, seconds);
63 | return true;
64 | } catch (Exception e) {
65 | log.error("redis hset and expire eror,key:{},field:{},value:{},exception:{}", key, field, value, e);
66 | return false;
67 | }
68 | }
69 |
70 | /**
71 | * 获取key中field属性的值
72 | *
73 | * @param key
74 | * @param field
75 | * @return
76 | */
77 | public Object hget(String key, String field) {
78 | return redisTemplate.opsForHash().get(key, field);
79 | }
80 |
81 | /**
82 | * 获取key中多个属性的键值对,这儿使用map来接收
83 | *
84 | * @param key
85 | * @param fields
86 | * @return
87 | */
88 | public Map hmget(String key, String... fields) {
89 | Map map = new HashMap<>(10);
90 | for (String field : fields) {
91 | map.put(field, hget(key, field));
92 | }
93 | return map;
94 | }
95 |
96 | /**
97 | * @param key 获得该key下的所有键值对
98 | * @return
99 | */
100 | public Map