diff --git a/campaign/pom.xml b/campaign/pom.xml index 9ace46fd..3d3a28c9 100644 --- a/campaign/pom.xml +++ b/campaign/pom.xml @@ -42,6 +42,19 @@ com.pudonghot.yo yo-web-common + + org.springframework.cloud + spring-cloud-starter-openfeign + + + io.github.openfeign + feign-httpclient + + + io.github.openfeign + feign-jackson + + org.springframework.boot spring-boot-starter-actuator diff --git a/campaign/src/main/java/com/pudonghot/yo/campaign/YoCampaign.java b/campaign/src/main/java/com/pudonghot/yo/campaign/YoCampaign.java index 647ee098..6161f051 100644 --- a/campaign/src/main/java/com/pudonghot/yo/campaign/YoCampaign.java +++ b/campaign/src/main/java/com/pudonghot/yo/campaign/YoCampaign.java @@ -1,12 +1,14 @@ package com.pudonghot.yo.campaign; import org.springframework.boot.SpringApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Donghuang * @date Jul 18, 2020 17:25:36 */ +@EnableFeignClients @SpringBootApplication public class YoCampaign { diff --git a/campaign/src/main/java/com/pudonghot/yo/campaign/feign/config/FeignClientConfiguration.java b/campaign/src/main/java/com/pudonghot/yo/campaign/feign/config/FeignClientConfiguration.java new file mode 100644 index 00000000..87f5ec56 --- /dev/null +++ b/campaign/src/main/java/com/pudonghot/yo/campaign/feign/config/FeignClientConfiguration.java @@ -0,0 +1,54 @@ +package com.pudonghot.yo.campaign.feign.config; + +import feign.Retryer; +import feign.codec.Decoder; +import feign.codec.Encoder; +import feign.RequestInterceptor; +import lombok.extern.slf4j.Slf4j; +import feign.jackson.JacksonDecoder; +import feign.form.spring.SpringFormEncoder; +import org.springframework.context.annotation.Bean; +import com.fasterxml.jackson.databind.ObjectMapper; +import static java.util.concurrent.TimeUnit.SECONDS; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.openfeign.support.SpringEncoder; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.support.ResponseEntityDecoder; + +/** + * @author Donghuang + * @date Jul 22, 2020 14:08:16 + */ +@Slf4j +@Configuration +public class FeignClientConfiguration { + + @Autowired + private ObjectFactory messageConverters; + + @Bean + public RequestInterceptor requestInterceptor() { + return reqTpl -> { + log.debug("Add OpenAPI access token [{}]."); + reqTpl.header("x-ac-token", "YoQinwei"); + }; + } + + @Bean + public Encoder feignEncoder() { + return new SpringFormEncoder(new SpringEncoder(messageConverters)); + } + + @Bean + public Decoder feignDecoder(final ObjectMapper objectMapper) { + return new ResponseEntityDecoder(new JacksonDecoder(objectMapper)); + } + + @Bean + public Retryer feignRetryer() { + return new Retryer.Default(100, SECONDS.toMillis(1), 4); + } +} + diff --git a/campaign/src/main/java/com/pudonghot/yo/campaign/feign/response/RespCallingList.java b/campaign/src/main/java/com/pudonghot/yo/campaign/feign/response/RespCallingList.java new file mode 100644 index 00000000..d1940b65 --- /dev/null +++ b/campaign/src/main/java/com/pudonghot/yo/campaign/feign/response/RespCallingList.java @@ -0,0 +1,60 @@ +package com.pudonghot.yo.campaign.feign.response; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.tuple.Pair; + +/** + * @author Donghuang + * @date Jul 22, 2020 14:17:40 + */ +@Getter +@Setter +@ToString +public class RespCallingList { + @JsonAlias("rtncode") + @JsonProperty("rtncode") + private String code; + @JsonAlias("message") + @JsonProperty("message") + private String error; + + @JsonAlias("taskid") + @JsonProperty("taskid") + private String campaignKey; + @JsonAlias("taskname") + @JsonProperty("taskname") + private String campaignName; + @JsonAlias("taskdata") + @JsonProperty("taskdata") + private CallingData[] data; + + /** + * is response success + * @return true if code == 0 + */ + public boolean isSuccess() { + return "0".equals(code); + } + + @Getter + @Setter + @ToString + public static class CallingData { + private String phone; + @JsonAlias("outid") + @JsonProperty("outid") + private String id; + + /** + * to pair (phone, id) + * @return pair + */ + public Pair toPair() { + return Pair.of(phone, id); + } + } +} diff --git a/campaign/src/main/java/com/pudonghot/yo/campaign/feign/service/FeignCallingListService.java b/campaign/src/main/java/com/pudonghot/yo/campaign/feign/service/FeignCallingListService.java new file mode 100644 index 00000000..efc7aee0 --- /dev/null +++ b/campaign/src/main/java/com/pudonghot/yo/campaign/feign/service/FeignCallingListService.java @@ -0,0 +1,34 @@ +package com.pudonghot.yo.campaign.feign.service; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestMapping; +import com.pudonghot.yo.campaign.feign.response.RespCallingList; +import com.pudonghot.yo.campaign.feign.config.FeignClientConfiguration; + +/** + * @author Donghuang
+ * donghuang@wacai.com
+ * Jan 07, 2020 14:36:47 + */ +@FeignClient(url = "${yo.campaign.feign.calling-list.base-url}", + name = "CampaignFeign", + configuration = FeignClientConfiguration.class) +public interface FeignCallingListService { + + /** + * fetch calling list + * @param numData num data + * @param campaignKey campaign key + * @param campaignName campaign name + * @return calling list + */ + @RequestMapping("/${yo.campaign.feign.calling-list.channel}") + RespCallingList fetchCallingList( + @RequestParam("datanum") + int numData, + @RequestParam("taskid") + String campaignKey, + @RequestParam("taskname") + String campaignName); +} diff --git a/campaign/src/main/java/com/pudonghot/yo/campaign/service/CampaignService.java b/campaign/src/main/java/com/pudonghot/yo/campaign/service/CampaignService.java index a09dc644..a80a3df7 100644 --- a/campaign/src/main/java/com/pudonghot/yo/campaign/service/CampaignService.java +++ b/campaign/src/main/java/com/pudonghot/yo/campaign/service/CampaignService.java @@ -1,8 +1,11 @@ package com.pudonghot.yo.campaign.service; +import com.pudonghot.yo.model.domain.Campaign; +import com.wacai.tigon.service.BaseQueryService; + /** * @author Donghuang * @date Jul 18, 2020 17:24:50 */ -public interface CampaignService { +public interface CampaignService extends BaseQueryService { } diff --git a/campaign/src/main/java/com/pudonghot/yo/campaign/service/impl/CampaignServiceImpl.java b/campaign/src/main/java/com/pudonghot/yo/campaign/service/impl/CampaignServiceImpl.java index d891b9cd..aa690e1d 100644 --- a/campaign/src/main/java/com/pudonghot/yo/campaign/service/impl/CampaignServiceImpl.java +++ b/campaign/src/main/java/com/pudonghot/yo/campaign/service/impl/CampaignServiceImpl.java @@ -1,12 +1,89 @@ package com.pudonghot.yo.campaign.service.impl; +import java.util.stream.Stream; +import lombok.extern.slf4j.Slf4j; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import com.wacai.tigon.mybatis.Search; +import com.pudonghot.yo.mapper.CampaignMapper; +import com.pudonghot.yo.model.domain.Campaign; import org.springframework.stereotype.Service; +import com.pudonghot.yo.mapper.AgentStatusMapper; +import com.pudonghot.yo.fsagent.api.CampaignDialService; import com.pudonghot.yo.campaign.service.CampaignService; +import org.springframework.scheduling.annotation.Scheduled; +import com.pudonghot.yo.fsagent.api.request.ReqCampaignDial; +import org.springframework.beans.factory.annotation.Autowired; +import com.wacai.tigon.service.support.BaseQueryServiceSupport; +import com.pudonghot.yo.campaign.feign.response.RespCallingList; +import com.pudonghot.yo.campaign.feign.service.FeignCallingListService; +import static com.pudonghot.yo.model.domain.Campaign.TargetType.QUEUE; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** * @author Donghuang * @date Jul 18, 2020 17:25:17 */ +@Slf4j @Service -public class CampaignServiceImpl implements CampaignService { +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class CampaignServiceImpl + extends BaseQueryServiceSupport + implements CampaignService { + private final ThreadPoolTaskExecutor taskExecutor; + + @Autowired + private AgentStatusMapper agentStatusMapper; + @Autowired + private FeignCallingListService callingListService; + @Autowired + private CampaignDialService dialService; + + @Scheduled(fixedRateString = "${yo.campaign.task-scheduler.fixed-rate:180000}", initialDelayString = "${yo.campaign.task-scheduler.init-delay:32000}") + public void taskScheduler() { + scan(new Search(Campaign.ACTIVE, true) + .eq(Campaign.STATUS, Campaign.Status.RUNNING), campaign -> { + if (campaign.getTargetType() == QUEUE) { + log.info("Queue campaign [{}] is running, dial.", campaign); + taskExecutor.execute(() -> dial(campaign)); + } + else { + log.info("Campaign [{}] target is not queue, ignore.", campaign); + } + }); + } + + void dial(final Campaign campaign) { + log.info("Campaign [{}] dial.", campaign); + final Integer queueId = campaign.getTargetId(); + final int countOnlineAgentOfQueue = + agentStatusMapper.countOnlineOfQueue(queueId); + if (countOnlineAgentOfQueue == 0) { + log.warn("Campaign [{}] has no online agent, ignore."); + return; + } + + final int countIdleAgentOfQueue = + agentStatusMapper.countIdleOfQueue(queueId); + + if (countIdleAgentOfQueue == 0) { + log.warn("Campaign [{}] has no idle agent, ignore."); + return; + } + + // TODO campaign dial + // Outbound = (readyAgent + expNum) * z * speedAdjust - taskNum; + final RespCallingList callingList = + callingListService.fetchCallingList( + countIdleAgentOfQueue, campaign.getCampaignKey(), campaign.getName()); + if (callingList.isSuccess()) { + final ReqCampaignDial req= new ReqCampaignDial(); + req.setCampaignId(campaign.getId()); + req.setCallingList(Stream.of(callingList.getData()).map(RespCallingList.CallingData::toPair).collect(Collectors.toList())); + dialService.queueDial(req); + } + else { + log.error("Fetch calling list error caused. code [{}], message [{}].", callingList.getCode(), callingList.getError()); + } + } } diff --git a/campaign/src/main/resources/application.properties b/campaign/src/main/resources/application.properties index fd14a56c..d0e12940 100644 --- a/campaign/src/main/resources/application.properties +++ b/campaign/src/main/resources/application.properties @@ -21,3 +21,14 @@ spring.redis.host=172.18.4.35 spring.redis.port=6379 spring.redis.password=123456 +# Dubbo + +## Dubbo Registry +dubbo.registry.address=zookeeper://172.18.4.35:2181 +dubbo.registry.file=${user.home}/dubbo-cache/${spring.application.name}/dubbo.cache +yo.fsagent.dubbo.service.version=1.0.0 + +# Calling List +yo.campaign.feign.calling-list.base-url=http://localhost:1116 +yo.campaign.feign.calling-list.channel=campaign.json + diff --git a/campaign/src/main/resources/spring/spring-yo-campaign.xml b/campaign/src/main/resources/spring/spring-yo-campaign.xml index 3cad6df1..1b95140a 100644 --- a/campaign/src/main/resources/spring/spring-yo-campaign.xml +++ b/campaign/src/main/resources/spring/spring-yo-campaign.xml @@ -2,10 +2,13 @@ + http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/task + http://www.springframework.org/schema/task/spring-task.xsd + http://dubbo.apache.org/schema/dubbo + http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> + + + diff --git a/campaign/src/test/java/com/pudonghot/yo/campaign/FeignCallingListServiceTest.java b/campaign/src/test/java/com/pudonghot/yo/campaign/FeignCallingListServiceTest.java new file mode 100644 index 00000000..4bff890d --- /dev/null +++ b/campaign/src/test/java/com/pudonghot/yo/campaign/FeignCallingListServiceTest.java @@ -0,0 +1,30 @@ +package com.pudonghot.yo.campaign; + +import org.junit.Test; +import org.junit.runner.RunWith; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.beans.factory.annotation.Autowired; +import com.pudonghot.yo.campaign.feign.response.RespCallingList; +import com.pudonghot.yo.campaign.feign.service.FeignCallingListService; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Donghuang
+ * Dec 12, 2019 23:59:37 + */ +@Slf4j +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = YoCampaign.class) +public class FeignCallingListServiceTest { + + @Autowired + private FeignCallingListService callingListService; + + @Test + public void testFetchCallingList() { + final RespCallingList callingList = + callingListService.fetchCallingList(1, "11223", "44556"); + log.info("Calling list [{}].", callingList); + } +} diff --git a/campaign/src/test/resources/spring/spring-test.xml b/campaign/src/test/resources/spring/spring-test.xml index e569cc31..678e2c60 100644 --- a/campaign/src/test/resources/spring/spring-test.xml +++ b/campaign/src/test/resources/spring/spring-test.xml @@ -1,14 +1,6 @@ - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd"> diff --git a/cms/local-repo/com/pudonghot/yo/yo-cms-web/0.0.1-SNAPSHOT/yo-cms-web-0.0.1-SNAPSHOT.jar b/cms/local-repo/com/pudonghot/yo/yo-cms-web/0.0.1-SNAPSHOT/yo-cms-web-0.0.1-SNAPSHOT.jar index d3947aa3..696a5494 100644 Binary files a/cms/local-repo/com/pudonghot/yo/yo-cms-web/0.0.1-SNAPSHOT/yo-cms-web-0.0.1-SNAPSHOT.jar and b/cms/local-repo/com/pudonghot/yo/yo-cms-web/0.0.1-SNAPSHOT/yo-cms-web-0.0.1-SNAPSHOT.jar differ diff --git a/cms/src/main/java/com/pudonghot/yo/cms/service/impl/TrunkServiceImpl.java b/cms/src/main/java/com/pudonghot/yo/cms/service/impl/TrunkServiceImpl.java index d9e79e20..0bce348f 100644 --- a/cms/src/main/java/com/pudonghot/yo/cms/service/impl/TrunkServiceImpl.java +++ b/cms/src/main/java/com/pudonghot/yo/cms/service/impl/TrunkServiceImpl.java @@ -9,12 +9,12 @@ import com.wacai.tigon.mybatis.Search; import com.pudonghot.yo.model.domain.*; import com.wacai.tigon.model.ViewModel; import org.springframework.util.Assert; +import com.pudonghot.yo.util.PhoneNumberUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.StringUtils; import com.pudonghot.yo.cms.form.SessionForm; import org.springframework.stereotype.Service; import com.pudonghot.yo.cellphone.CellphoneInfo; -import com.pudonghot.yo.common.util.MobileUtils; import com.pudonghot.yo.cms.service.TrunkService; import com.pudonghot.yo.cellphone.CellphoneService; import com.pudonghot.yo.cms.service.AreaCodeService; @@ -172,7 +172,7 @@ public class TrunkServiceImpl } private String getTrunk(final String callerNumber) { - if (MobileUtils.checkMobile(callerNumber)) { + if (PhoneNumberUtils.isMobile(callerNumber)) { final CellphoneInfo cellphoneInfo = cellphoneService.lookup(callerNumber); Assert.state(cellphoneInfo != null, diff --git a/deploy.sh b/deploy.sh index 7d216c9f..5ce36599 100755 --- a/deploy.sh +++ b/deploy.sh @@ -19,11 +19,17 @@ deploy_server() { local server="$1" local app="$2" local jar="$3" + local port="$4" + if [ -z "$port" ]; then + port='22' + fi + local target_dir="/data/program/$app" local target_jar="$target_dir/lib/main.jar" - scp $jar $server:"${target_jar}_new" - ssh $server "$target_dir/bin/stop.sh; mv $target_jar {$target_jar}_prev" - ssh $server "mv ${target_jar}_new $target_jar && $target_dir/bin/start.sh" + scp -P $port $jar $server:"${target_jar}_new" + ssh -p $port $server "$target_dir/bin/stop.sh; + mv $target_jar {$target_jar}_prev; + mv ${target_jar}_new $target_jar && $target_dir/bin/start.sh" } prg_path=$(get_real_path "$0") @@ -35,7 +41,7 @@ WORK_DIR=$(pwd) echo "Work dir [$WORK_DIR]" if [ -z "$1" ] && [ -z "$2" ]; then - echo 'Usage: ./deploy.sh cms|fsagent|openapi test|prod' + echo 'Usage: ./deploy.sh cms|fsagent|openapi test|prod [nb]' exit 1 fi @@ -58,8 +64,9 @@ echo "JAR [$JAR] found" if [ "$2" == "test" ]; then echo "Deploy test." deploy_server 'appweb@118.24.251.131' $APP $JAR -elif ["$2" == "prod" ]; then +elif [ "$2" == "prod" ]; then echo "Deploy prod." + deploy_server 'xiandou@113.87.175.72' $APP $JAR '51022' fi popd > /dev/null diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/DialController.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/DialController.java index 0ebf6a02..82d6d4c1 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/DialController.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/DialController.java @@ -1,12 +1,18 @@ package com.pudonghot.yo.fsagent.controller; +import java.util.List; +import java.util.Arrays; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; +import org.apache.commons.lang3.tuple.Pair; import com.pudonghot.yo.fsagent.api.DialService; +import org.springframework.stereotype.Controller; import com.pudonghot.yo.fsagent.api.response.RespDial; +import com.pudonghot.yo.fsagent.api.CampaignDialService; +import com.pudonghot.yo.fsagent.api.request.ReqAgentDial; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import com.pudonghot.yo.fsagent.api.request.ReqAgentDial; +import org.springframework.web.bind.annotation.RequestParam; +import com.pudonghot.yo.fsagent.api.request.ReqCampaignDial; import org.springframework.beans.factory.annotation.Autowired; /** @@ -18,9 +24,24 @@ import org.springframework.beans.factory.annotation.Autowired; public class DialController { @Autowired private DialService dialService; + @Autowired + private CampaignDialService campaignDialService; @PostMapping("/dial") public RespDial dial(@RequestBody final ReqAgentDial req) { return dialService.agentDial(req); } + + @PostMapping("/campaign-dial") + public List campaignDial( + @RequestParam("campaignId") + final Integer campaignId, + @RequestParam("phone") + final String calledNumber) { + + final ReqCampaignDial req = new ReqCampaignDial(); + req.setCampaignId(campaignId); + req.setCallingList(Arrays.asList(Pair.of(calledNumber, calledNumber))); + return campaignDialService.queueDial(req); + } } diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/DialplanController.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/DialplanController.java index 3cc07536..cd7e5583 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/DialplanController.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/DialplanController.java @@ -38,6 +38,7 @@ public class DialplanController extends BaseDialplanController { final Map params) { log.info("XML dialplan of domain [{}] [{}] -> [{}].", domain, channel, calledNumber); + log.debug("XML dialplan params [{}].", params); for (final DialplanService dialplanService : dialplanServices) { final DialplanConfig dialplanConfig = dialplanService.find(params); diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/IvrController.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/IvrController.java index 44812b65..1ed30511 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/IvrController.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/controller/IvrController.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Controller; import com.pudonghot.yo.model.agentevent.AgentEvent; import com.pudonghot.yo.service.CommonChannelService; import com.pudonghot.yo.fsagent.service.AgentService; +import com.pudonghot.yo.service.CommonCallDataService; import org.freeswitch.esl.client.transport.event.Event; import com.pudonghot.yo.fsagent.api.IvrTransferService; import org.springframework.web.bind.annotation.PostMapping; @@ -36,6 +37,8 @@ public class IvrController { private CommonAgentEventQueueService agentEventQueueService; @Autowired private CommonChannelService channelService; + @Autowired + private CommonCallDataService commonCallDataService; @PostMapping("/ivr/confirm") public void confirm( @@ -103,7 +106,8 @@ public class IvrController { } eventData.put("callid", callId); - eventData.put("calldata", ivrTransferService.getCallData(strAgentId)); + eventData.put("calldata", + commonCallDataService.getIvrCallData(strAgentId)); agentEventQueueService.publish(new AgentEvent( AgentEvent_IVR_Result, diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/event/CampaignCallEstablishedEvent.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/event/CampaignCallEstablishedEvent.java new file mode 100644 index 00000000..782b0445 --- /dev/null +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/event/CampaignCallEstablishedEvent.java @@ -0,0 +1,29 @@ +package com.pudonghot.yo.fsagent.event; + +import lombok.Getter; +import com.pudonghot.yo.model.domain.Agent; +import org.springframework.context.ApplicationEvent; + +/** + * @author Donghuang + * @date Jul 22, 2020 19:53:59 + */ +public class CampaignCallEstablishedEvent extends ApplicationEvent { + @Getter + private final String callUuid; + + /** + * Create a new {@code ApplicationEvent}. + * + * @param agent the object on which the event initially occurred or with + * which the event is associated (never {@code null}) + */ + public CampaignCallEstablishedEvent(final Agent agent, String callUuid) { + super(agent); + this.callUuid = callUuid; + } + + public Agent getAgent() { + return (Agent) getSource(); + } +} diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/CampaignCallEstablishedListener.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/CampaignCallEstablishedListener.java new file mode 100644 index 00000000..ef1a6a14 --- /dev/null +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/CampaignCallEstablishedListener.java @@ -0,0 +1,42 @@ +package com.pudonghot.yo.fsagent.listener; + +import lombok.extern.slf4j.Slf4j; +import com.pudonghot.yo.model.domain.Agent; +import org.springframework.stereotype.Component; +import com.pudonghot.yo.model.agentevent.AgentEvent; +import com.pudonghot.yo.service.CommonCallDataService; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import com.pudonghot.yo.service.CommonAgentEventQueueService; +import static com.pudonghot.yo.model.agentevent.EventType.*; +import org.springframework.beans.factory.annotation.Autowired; +import com.pudonghot.yo.fsagent.event.CampaignCallEstablishedEvent; + +/** + * @author Donghuang + * @date Jul 23, 2020 09:50:06 + */ +@Slf4j +@Component +public class CampaignCallEstablishedListener { + + @Autowired + private CommonAgentEventQueueService agentEventQueueService; + @Autowired + private CommonCallDataService commonCallDataService; + + /** + * {@inheritDoc} + */ + @Async + @EventListener(value = CampaignCallEstablishedEvent.class) + public void onCampaignCallEstablished(final CampaignCallEstablishedEvent event) { + final Agent agent = event.getAgent(); + final String callUuid = event.getCallUuid(); + agentEventQueueService.publish( + new AgentEvent(AgentOther_TaskData, + agent.getId(), + agent.getAccount(), + commonCallDataService.getCampaignCallData(callUuid))); + } +} diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/CampaignChannelDestroy.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/CampaignChannelDestroy.java new file mode 100644 index 00000000..90aae8a7 --- /dev/null +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/CampaignChannelDestroy.java @@ -0,0 +1,39 @@ +package com.pudonghot.yo.fsagent.listener; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import com.pudonghot.yo.service.CommonCallDataService; +import org.freeswitch.esl.client.transport.event.Event; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author Donghuang + * @date Jul 23, 2020 19:51:09 + */ +@Slf4j +@Component +public class CampaignChannelDestroy { + @Autowired + private CommonCallDataService callDataService; + + /** + * {@inheritDoc} + */ + @Async + @EventListener(value = Event.class, + condition = "#root.args[0].getName() == 'CHANNEL_DESTROY'" + + " and #root.args[0].getDialType() == 'CAMPAIGN'") + public void onCampaignChannelDestroy(final Event event) { + log.debug("On campaign channel destroy event [{}].", event.getHeaders()); + + final String strCampaignId = event.getHeader("variable_x_campaign_id"); + if (StringUtils.isNotBlank(strCampaignId)) { + log.info("Campaign [{}] call hangup, decrease campaign channel.", strCampaignId); + final Integer campaignId = Integer.parseInt(strCampaignId); + callDataService.decrCampaignChannel(campaignId); + } + } +} diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelAnswer.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelAnswer.java index d49d5897..8ed75278 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelAnswer.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelAnswer.java @@ -4,11 +4,12 @@ import java.util.Map; import java.util.HashMap; import lombok.extern.slf4j.Slf4j; import com.pudonghot.yo.model.domain.Agent; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; +import com.pudonghot.yo.fsagent.util.CallStrUtils; import com.pudonghot.yo.fsagent.util.EslEventUtils; import com.pudonghot.yo.model.agentevent.AgentEvent; import com.pudonghot.yo.fsagent.service.AgentService; -import com.pudonghot.yo.fsagent.util.CallStrUtils; import org.freeswitch.esl.client.transport.event.Event; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; @@ -37,7 +38,7 @@ public class ChannelAnswer { @EventListener(value = Event.class, condition = "#root.args[0].getName() == 'CHANNEL_ANSWER'") public void onChannelAnswer(final Event event) { - log.debug("On channel answer event [{}] [{}].", event, event.getHeaders()); + log.debug("On channel answer event [{}].", event.getHeaders()); final CallStrUtils.ChannelInfo channelInfo = CallStrUtils.getChannelInfo(event); @@ -58,7 +59,12 @@ public class ChannelAnswer { // feature:0普通呼入,7普通外呼, 51内部求助 eventData.put("feature", "7"); eventData.put("caller", channelInfo.getNumber()); - eventData.put("called", event.getOtherNumber()); + + String calledNumber = event.getCalledNumber(); + if (StringUtils.isBlank(calledNumber)) { + calledNumber = event.getOtherNumber(); + } + eventData.put("called", calledNumber); } else { // feature:0普通呼入,7普通外呼, 51内部求助 diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelCreate.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelCreate.java index 922433c6..11659578 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelCreate.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelCreate.java @@ -27,7 +27,7 @@ public class ChannelCreate { @EventListener(value = Event.class, condition = "#root.args[0].getName() == 'CHANNEL_CREATE'") public void onChannelCreate(final Event event) { - log.debug("On channel create event [{}] [{}].", event, event.getHeaders()); + log.debug("On channel create event [{}].", event.getHeaders()); final CallStrUtils.ChannelInfo channelInfo = CallStrUtils.getChannelInfo(event); diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelDestroy.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelDestroy.java index ffb7c4ea..b591bceb 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelDestroy.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelDestroy.java @@ -44,7 +44,7 @@ public class ChannelDestroy { @EventListener(value = Event.class, condition = "#root.args[0].getName() == 'CHANNEL_DESTROY'") public void onChannelDestroy(final Event event) { - log.debug("On channel destroy event [{}] [{}].", event, event.getHeaders()); + log.debug("On channel destroy event [{}].", event.getHeaders()); final String callUuid = event.getCallUuid(); final String channelName = event.getChannelName(); diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelHangupComplete.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelHangupComplete.java index 5a7b8980..46e304d6 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelHangupComplete.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelHangupComplete.java @@ -52,7 +52,9 @@ public class ChannelHangupComplete { final Map eventData = new HashMap<>(8); eventData.put("callid", event.getCallUuid()); - if (EslEventUtils.isCaller(event)) { + final String hangupCause = event.getHangupCause(); + if (EslEventUtils.isCaller(event) && + !"NORMAL_UNSPECIFIED".equalsIgnoreCase(hangupCause)) { agentEventQueueService.publish( new AgentEvent( AgentEvent_Connect_Fail, @@ -60,7 +62,7 @@ public class ChannelHangupComplete { agent.getAccount(), eventData)); } - else if ("NO_ANSWER".equals(event.getHangupCause())) { + else if ("NO_ANSWER".equalsIgnoreCase(hangupCause)) { agentEventQueueService.publish( new AgentEvent( AgentEvent_No_Answer, diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelProgress.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelProgress.java index 7c018a4d..a2eb385a 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelProgress.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/listener/ChannelProgress.java @@ -36,7 +36,7 @@ public class ChannelProgress { @EventListener(value = Event.class, condition = "#root.args[0].getName() == 'CHANNEL_PROGRESS'") public void onChannelProgress(final Event event) { - log.debug("On channel progress event [{}] [{}].", event, event.getHeaders()); + log.debug("On channel progress event [{}].", event.getHeaders()); final String channelName = event.getChannelName(); log.info("On channel [{}] answer event.", channelName); diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/BaseDialplanService.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/BaseDialplanService.java index 8aac5e69..1206b637 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/BaseDialplanService.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/BaseDialplanService.java @@ -65,15 +65,15 @@ public abstract class BaseDialplanService implements DialplanService { protected String getParam(final Map params, final String name, final String... names) { - String val = params.get(name); + final String val = params.get(name); if (StringUtils.isNotBlank(val)) { return val; } - for (String n : names) { - val = params.get(n); - if (StringUtils.isNotBlank(val)) { - return val; + for (final String alterName : names) { + final String alterVal = params.get(alterName); + if (StringUtils.isNotBlank(alterVal)) { + return alterVal; } } @@ -88,6 +88,10 @@ public abstract class BaseDialplanService implements DialplanService { return getParam(params, "Channel-Presence-ID", "variable_presence_id"); } + public String getConnId(final Map params) { + return getParam(params, "variable_x_conn_id", "variable_call_uuid", "variable_uuid"); + } + protected ChannelInfo getChannelInfo(final Map params) { return CallStrUtils.parseChannelName(getChannelName(params), getPresenceId(params)); } diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/DialplanService20CampaignToIdAndType.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/DialplanService20CampaignToIdAndType.java index 0e1488c1..1cd9418b 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/DialplanService20CampaignToIdAndType.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/DialplanService20CampaignToIdAndType.java @@ -3,12 +3,16 @@ package com.pudonghot.yo.fsagent.service.dialplan.impl; import java.util.Map; import java.util.HashMap; import com.pudonghot.yo.model.domain.Agent; +import com.pudonghot.yo.util.PhoneNumberUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.stereotype.Service; import org.springframework.core.annotation.Order; +import org.springframework.context.ApplicationContext; import com.pudonghot.yo.fsagent.service.dialplan.Call; import com.pudonghot.yo.model.domain.CallDetailRecord; +import com.pudonghot.yo.fsagent.event.CampaignCallEstablishedEvent; +import org.springframework.beans.factory.annotation.Autowired; import com.pudonghot.yo.fsagent.service.dialplan.DialplanConfig; /** @@ -18,6 +22,8 @@ import com.pudonghot.yo.fsagent.service.dialplan.DialplanConfig; @Order(20) @Service public class DialplanService20CampaignToIdAndType extends BaseDialplanService { + @Autowired + private ApplicationContext appContext; /** * {@inheritDoc} @@ -36,14 +42,20 @@ public class DialplanService20CampaignToIdAndType extends BaseDialplanService { model.put("connId", connId); model.put("calledAgent", calledAgent); - final String otherDn = StringUtils.defaultIfBlank( - getVarCalledNumber(call), - call.getCallerChannel().getNumber()); + String otherDn = getVarCalledNumber(call); + if (StringUtils.isBlank(otherDn)) { + otherDn = PhoneNumberUtils.cleanupMobile( + call.getCallerChannel().getNumber()); + } + model.put("calledNumber", otherDn); model.put("privacyNumber", numberPrivacyService.generate(calledAgent, otherDn)); recFile(model, connId, calledAgent.getAccount(), md5(otherDn)); + appContext.publishEvent( + new CampaignCallEstablishedEvent( + calledAgent, getConnId(call.getParams()))); return new DialplanConfig(call.getTenant(), "campaign-to-agent.xml", model); } diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/DialplanService28AgentToTrunkPrefix.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/DialplanService28AgentToTrunkPrefix.java index 8a584033..a10f2b9c 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/DialplanService28AgentToTrunkPrefix.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dialplan/impl/DialplanService28AgentToTrunkPrefix.java @@ -27,10 +27,10 @@ public class DialplanService28AgentToTrunkPrefix extends BaseDialplanService { return onCallerAgent(call, callerAgent -> { - final String calledNumber = call.getCalledNumber(); + final String dialedNumber = call.getCalledNumber(); final Pair trunkInfo = trunkService.parseDialTarget( - callerAgent.getTenantId(), calledNumber); + callerAgent.getTenantId(), dialedNumber); if (trunkInfo != null) { final Trunk trunk = trunkInfo.getLeft(); @@ -45,9 +45,10 @@ public class DialplanService28AgentToTrunkPrefix extends BaseDialplanService { gatewayService.genGatewayName(trunk.getGatewayId())); // number without trunk prefix - final String targetNumber = trunkInfo.getRight(); - model.put("targetNumber", targetNumber); - recFile(model, connId, callerAgent.getAccount(), md5(targetNumber)); + final String calledNumber = trunkInfo.getRight(); + model.put("calledNumber", calledNumber); + model.put("targetNumber", trunkService.calledNumber(trunk, calledNumber)); + recFile(model, connId, callerAgent.getAccount(), md5(calledNumber)); return new DialplanConfig(call.getTenant(), "trunk-outbound.xml", model); } return null; diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/CampaignDialServiceImpl.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/CampaignDialServiceImpl.java new file mode 100644 index 00000000..8969f8b6 --- /dev/null +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/CampaignDialServiceImpl.java @@ -0,0 +1,148 @@ +package com.pudonghot.yo.fsagent.service.dubbo.impl; + +import java.util.*; +import com.pudonghot.yo.mapper.*; +import com.wacai.tigon.json.JSON; +import lombok.extern.slf4j.Slf4j; +import com.pudonghot.yo.model.domain.*; +import org.springframework.util.Assert; +import com.pudonghot.yo.model.domain.Queue; +import com.wacai.tigon.sequence.IdSequence; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.StringUtils; +import com.pudonghot.yo.fsagent.api.request.*; +import org.freeswitch.esl.client.inbound.Client; +import org.apache.dubbo.config.annotation.Service; +import com.pudonghot.yo.fsagent.service.TrunkService; +import com.pudonghot.yo.service.CommonCallDataService; +import com.pudonghot.yo.fsagent.api.CampaignDialService; +import static org.slf4j.helpers.MessageFormatter.arrayFormat; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author Donghuang
+ * Dec 09, 2019 16:30:25 + */ +@Slf4j +@Service(version = "${yo.fsagent.dubbo.service.version}", register = false) +public class CampaignDialServiceImpl implements CampaignDialService { + + @Autowired + private CampaignMapper campaignMapper; + @Autowired + private Client fsClient; + @Autowired + private IdSequence idSeq; + @Autowired + private TenantMapper tenantMapper; + @Autowired + private TrunkStrategyMapper trunkStrategyMapper; + @Autowired + private QueueMapper queueMapper; + @Autowired + private TrunkService trunkService; + @Autowired + private CommonCallDataService commonCallDataService; + @Autowired + private JSON json; + + /** + * {@inheritDoc} + */ + @Override + public List queueDial(final ReqCampaignDial req) { + final Integer campaignId = req.getCampaignId(); + final Campaign campaign = campaignMapper.find(campaignId); + Assert.state(campaign != null, + () -> "No campaign [" + campaignId + "] found"); + Assert.state(campaign.getActive(), + () -> "Campaign [" + campaignId + "] is not active"); + Assert.state(campaign.getStatus() == Campaign.Status.RUNNING, + () -> "Campaign [" + campaignId + "] is not running"); + Assert.state(campaign.getTargetType() == Campaign.TargetType.QUEUE, + () -> "Campaign [" + campaignId + "] target type is not QUEUE"); + + final Queue queue = queueMapper.find(campaign.getTargetId()); + Assert.state(queue != null, + () -> "Campaign [" + campaignId + "] queue not found"); + Assert.state(queue.getActive(), + () -> "Campaign [" + campaignId + "] queue is not active"); + + final Tenant tenant = tenantMapper.find(campaign.getTenantId()); + Assert.state(tenant != null, + () -> "Campaign [" + campaignId + "] tenant not found"); + Assert.state(tenant.getActive(), + () -> "Campaign [" + campaignId + "] tenant is not active"); + + final List> callingList = req.getCallingList(); + final List callUuidList = new ArrayList<>(callingList.size()); + + for (final Pair callingRec : callingList) { + final String calledNumber = callingRec.getLeft(); + + final List trunkStrategies = + trunkStrategyMapper.listOfCampaign(campaignId); + Assert.state(!trunkStrategies.isEmpty(), + () -> "Campaign [" + campaignId + "] has no trunk strategies configured"); + + final TrunkStrategy trunkStrategy = + trunkStrategies.size() == 1 ? + trunkStrategies.iterator().next() : + trunkStrategies.get( + RandomUtils.nextInt(0, trunkStrategies.size())); + + final Pair trunkDialStr = + trunkService.dialStr(trunkStrategy.getId(), calledNumber, null); + final Trunk trunk = trunkDialStr.getLeft(); + final List globalVars = Arrays.asList( + "ignore_early_media=true", + "x_dial_type=CAMPAIGN", + "x_call_type=OUTBOUND", + "x_tenant_id=" + tenant.getId(), + "x_tenant_code=" + tenant.getCode(), + "x_account=SYSTEM", + "x_called_number=" + calledNumber, + "x_trunk_id=" + trunk.getId(), + "x_cpn=" + trunk.getCpn() + ); + + final String uuid = idSeq.get(); + callUuidList.add(uuid); + + final List channelVars = Arrays.asList( + "x_campaign_id=" + campaignId, + "x_conn_id=" + uuid, + "origination_uuid=" + uuid, + "sip_invite_call_id=" + uuid, + "originate_timeout=30", + "x_role=CALLED", + "origination_caller_id_number=" + trunk.getCpn(), + "continue_on_fail=true", + "hangup_after_bridge=true" + ); + + // originate {x_dial_type=CAMPAIGN,x_tenant_id=13,x_tenant_code=GOBLIN,x_account=SYSTEM,x_called_number=13764268709,x_trunk_id=12,x_cpn=28165766}[originate_timeout=30,x_role=CALLED,origination_caller_id_number=28165766]sofia/gateway/GW000001/013764268709 4.Q XML d1.wacai.info + fsClient.bgApi(format("originate {{}}[{}]{} {}.Q XML {}", + StringUtils.join(globalVars, ","), + StringUtils.join(channelVars, ","), + trunkDialStr.getRight(), + queue.getId(), + tenant.getRealm())); + + final Map callData = new HashMap<>(4); + callData.put("outid", callingRec.getRight()); + callData.put("phone", calledNumber); + callData.put("taskid", campaign.getCampaignKey()); + callData.put("taskname", campaign.getName()); + commonCallDataService.saveCampaignCallData(uuid, + json.toJSONString(callData)); + commonCallDataService.incrCampaignChannel(campaignId); + } + return callUuidList; + } + + String format(final String tpl, final Object... args) { + return arrayFormat(tpl, args).getMessage(); + } +} diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/DialServiceImpl.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/DialServiceImpl.java index 3bd7991d..d9af7a8b 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/DialServiceImpl.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/DialServiceImpl.java @@ -43,8 +43,6 @@ public class DialServiceImpl implements DialService { private QueueMapper queueMapper; @Autowired private AgentService agentService; - @Value("${yo.fsagent.recording.file-ext:.ogg}") - private String recordingFileExt; @Autowired private DialerAgent dialerAgent; diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/IvrTransferServiceImpl.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/IvrTransferServiceImpl.java index 94288f52..dcf82594 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/IvrTransferServiceImpl.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/dubbo/impl/IvrTransferServiceImpl.java @@ -1,9 +1,6 @@ package com.pudonghot.yo.fsagent.service.dubbo.impl; import lombok.extern.slf4j.Slf4j; -import org.redisson.api.RMapCache; -import java.util.concurrent.TimeUnit; -import org.redisson.api.RedissonClient; import com.pudonghot.yo.model.domain.Agent; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.StringUtils; @@ -11,8 +8,8 @@ import com.pudonghot.yo.fs.model.domain.Channel; import org.freeswitch.esl.client.inbound.Client; import org.apache.dubbo.config.annotation.Service; import com.pudonghot.yo.service.CommonChannelService; +import com.pudonghot.yo.service.CommonCallDataService; import com.pudonghot.yo.fsagent.api.IvrTransferService; -import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Autowired; import com.pudonghot.yo.fsagent.service.dialplan.DialplanService; @@ -23,21 +20,13 @@ import com.pudonghot.yo.fsagent.service.dialplan.DialplanService; @Slf4j @Service(version = "${yo.fsagent.dubbo.service.version}", register = false) public class IvrTransferServiceImpl implements IvrTransferService { - private final RMapCache CALL_DATA_CACHE; @Autowired private Client fsClient; @Autowired private CommonChannelService commonChannelService; - @Value("${yo.fsagent.ivr-transfer.call-data-timeout-seconds:120}") - private long callDataTimeoutSeconds; - - public IvrTransferServiceImpl( - @Autowired - final RedissonClient redisson) { - - this.CALL_DATA_CACHE = redisson.getMapCache("CALL_DATA_CACHE"); - } + @Autowired + private CommonCallDataService commonCallDataService; /** * {@inheritDoc} @@ -82,7 +71,7 @@ public class IvrTransferServiceImpl implements IvrTransferService { "confirm.IVR/XML/" + realm); if (StringUtils.isNotBlank(callData)) { - saveCallData(agentId, callData); + commonCallDataService.saveIvrCallData(agentId, callData); } } @@ -102,26 +91,6 @@ public class IvrTransferServiceImpl implements IvrTransferService { return agentFifo(realm, agentId, DialplanService.Suffix.CHANNEL_UNHOLD); } - /** - * {@inheritDoc} - */ - @Override - public void saveCallData(final String agentId, final String callData) { - CALL_DATA_CACHE.put( - cacheKey(agentId), - callData, - callDataTimeoutSeconds, - TimeUnit.SECONDS); - } - - /** - * {@inheritDoc} - */ - @Override - public String getCallData(final String agentId) { - return CALL_DATA_CACHE.get(cacheKey(agentId)); - } - /** * {@inheritDoc} */ @@ -131,8 +100,4 @@ public class IvrTransferServiceImpl implements IvrTransferService { final DialplanService.Suffix suffix) { return "AGENT_" + agentId + suffix.getSuffix() + "/XML/" + realm; } - - String cacheKey(final String agentId) { - return "IVR_CALL_DATA:" + agentId; - } } diff --git a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/impl/TrunkServiceImpl.java b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/impl/TrunkServiceImpl.java index f0f0a443..a026ca35 100644 --- a/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/impl/TrunkServiceImpl.java +++ b/fsagent/src/main/java/com/pudonghot/yo/fsagent/service/impl/TrunkServiceImpl.java @@ -2,26 +2,27 @@ package com.pudonghot.yo.fsagent.service.impl; import java.util.List; -import com.pudonghot.yo.fsagent.service.GatewayService; -import com.pudonghot.yo.fsagent.service.TrunkService; -import com.pudonghot.yo.mapper.GatewayMapper; -import com.pudonghot.yo.mapper.SequenceMapper; -import com.pudonghot.yo.mapper.TrunkMapper; -import com.pudonghot.yo.mapper.TrunkStrategyMapper; -import com.pudonghot.yo.model.domain.Gateway; -import com.pudonghot.yo.model.domain.Sequence; -import com.pudonghot.yo.model.domain.Trunk; -import com.pudonghot.yo.model.domain.TrunkStrategy; -import com.wacai.tigon.mybatis.Search; +import com.pudonghot.yo.util.PhoneNumberUtils; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; +import com.wacai.tigon.mybatis.Search; import org.springframework.util.Assert; +import com.pudonghot.yo.mapper.TrunkMapper; +import com.pudonghot.yo.model.domain.Trunk; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.RandomUtils; +import com.pudonghot.yo.mapper.GatewayMapper; +import com.pudonghot.yo.model.domain.Gateway; +import com.pudonghot.yo.mapper.SequenceMapper; +import com.pudonghot.yo.model.domain.Sequence; import org.springframework.stereotype.Service; import com.pudonghot.yo.cellphone.CellphoneInfo; +import com.pudonghot.yo.mapper.TrunkStrategyMapper; +import com.pudonghot.yo.model.domain.TrunkStrategy; import com.pudonghot.yo.cellphone.CellphoneService; +import com.pudonghot.yo.fsagent.service.TrunkService; +import com.pudonghot.yo.fsagent.service.GatewayService; +import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Autowired; import com.wacai.tigon.service.support.BaseQueryServiceSupport; @@ -69,7 +70,7 @@ public class TrunkServiceImpl Assert.state(TrunkStrategy.Strategy.RANDOM == trunkStrategy.getStrategy(), "Random trunk strategy supports only"); - final List trunks = mapper.listOfStrategyId(strategyId); + final List trunks = mapper.listOfStrategy(strategyId); Assert.state(!trunks.isEmpty(), () -> "No trunk found of strategy [" + trunkStrategy.getName() + "]"); @@ -127,8 +128,11 @@ public class TrunkServiceImpl new Search(Trunk.TENANT_ID, tenantId) .eq(Trunk.PREFIX, prefix) .eq(Trunk.ACTIVE, true)); + if (trunk != null) { - return Pair.of(trunk, calledNumber.substring(prefixLength)); + return Pair.of(trunk, + PhoneNumberUtils.cleanupMobile( + calledNumber.substring(prefixLength))); } } diff --git a/fsagent/src/main/resources/application.properties b/fsagent/src/main/resources/application.properties index 7bb99262..f612fad6 100644 --- a/fsagent/src/main/resources/application.properties +++ b/fsagent/src/main/resources/application.properties @@ -41,7 +41,7 @@ dubbo.application.qos-port=22222 dubbo.application.qos-accept-foreign-ip=false ## Dubbo Registry -dubbo.registry.address=zookeeper://172.16.67.223:2181 +dubbo.registry.address=zookeeper://172.18.4.35:2181 dubbo.registry.file=${user.home}/dubbo-cache/${spring.application.name}/dubbo.cache # Service Version diff --git a/fsagent/src/main/resources/templates/dialplan/campaign-to-agent.xml b/fsagent/src/main/resources/templates/dialplan/campaign-to-agent.xml index 8b9b8346..ab43b519 100644 --- a/fsagent/src/main/resources/templates/dialplan/campaign-to-agent.xml +++ b/fsagent/src/main/resources/templates/dialplan/campaign-to-agent.xml @@ -5,22 +5,11 @@ - - - - - - - - - - - @@ -33,16 +22,16 @@ "originate_timeout=30", "odbc-cdr-ignore-leg=true", "origination_uuid=${connId}", + "sip_invite_call_id=${r'${sip_call_id}'}", "origination_caller_id_number=${privacyNumber.number}", "origination_caller_id_name=${privacyNumber.name}", "sip_contact_user=${privacyNumber.number}", - "sip_invite_call_id=${r'${sip_call_id}'}", - "x_role=CALLER", "x_account=${calledAgent.account}", "x_agent_type=${calledAgent.type}", + "x_called_number=${calledNumber}", "x_tenant_id=${tenant.id}", "x_tenant_code=${tenant.code}", diff --git a/fsagent/src/main/resources/templates/dialplan/trunk-outbound.xml b/fsagent/src/main/resources/templates/dialplan/trunk-outbound.xml index a5215a91..bd9cd029 100644 --- a/fsagent/src/main/resources/templates/dialplan/trunk-outbound.xml +++ b/fsagent/src/main/resources/templates/dialplan/trunk-outbound.xml @@ -18,6 +18,9 @@ + + + <#include "rec.xml"> @@ -34,20 +37,16 @@ "x_dial_type=MANUAL", "x_call_type=OUTBOUND", "x_agent_type=${callerAgent.type}", - "x_called_number=${targetNumber}", + "x_called_number=${calledNumber}", "x_trunk_id=${trunk.id}", "x_cpn=${trunk.cpn}", "origination_uuid=${connId}", "sip_invite_call_id=${r'${sip_call_id}'}", - "sip_contact_user=${callerAgent.agent}", - "callee_id_number=${callerAgent.agent}", - "callee_id_name=${callerAgent.account}", - "sip_h_Call-Info=${callerAgent.agent}@${tenant.realm}", - + "sip_from_user=${trunk.cpn}", "origination_caller_id_number=${trunk.cpn}", - "origination_caller_id_name=${callerAgent.account}" + "origination_caller_id_name=${trunk.cpn}" ]> --> diff --git a/lib/fsagent-api/src/main/java/com/pudonghot/yo/fsagent/api/CampaignDialService.java b/lib/fsagent-api/src/main/java/com/pudonghot/yo/fsagent/api/CampaignDialService.java new file mode 100644 index 00000000..68475289 --- /dev/null +++ b/lib/fsagent-api/src/main/java/com/pudonghot/yo/fsagent/api/CampaignDialService.java @@ -0,0 +1,18 @@ +package com.pudonghot.yo.fsagent.api; + +import java.util.List; +import com.pudonghot.yo.fsagent.api.request.ReqCampaignDial; + +/** + * @author Donghuang
+ * Dec 09, 2019 16:25:19 + */ +public interface CampaignDialService { + + /** + * campaign dial + * @param req request + * @return call uuid + */ + List queueDial(ReqCampaignDial req); +} diff --git a/lib/fsagent-api/src/main/java/com/pudonghot/yo/fsagent/api/IvrTransferService.java b/lib/fsagent-api/src/main/java/com/pudonghot/yo/fsagent/api/IvrTransferService.java index 1fb00dfd..2f5ebbef 100644 --- a/lib/fsagent-api/src/main/java/com/pudonghot/yo/fsagent/api/IvrTransferService.java +++ b/lib/fsagent-api/src/main/java/com/pudonghot/yo/fsagent/api/IvrTransferService.java @@ -33,20 +33,4 @@ public interface IvrTransferService { * @return agent unhold fifo dialplan name */ String agentUnholdFifo(String realm, String agentId); - - /** - * save call data - * - * @param agentId agent id - * @param callData call data - */ - void saveCallData(String agentId, String callData); - - /** - * get call data - * - * @param agentId agent id - * @return call data - */ - String getCallData(String agentId); } diff --git a/lib/fsagent-api/src/main/java/com/pudonghot/yo/fsagent/api/request/ReqCampaignDial.java b/lib/fsagent-api/src/main/java/com/pudonghot/yo/fsagent/api/request/ReqCampaignDial.java new file mode 100644 index 00000000..8948c56f --- /dev/null +++ b/lib/fsagent-api/src/main/java/com/pudonghot/yo/fsagent/api/request/ReqCampaignDial.java @@ -0,0 +1,20 @@ +package com.pudonghot.yo.fsagent.api.request; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import java.util.List; +import java.io.Serializable; +import org.apache.commons.lang3.tuple.Pair; + +/** + * @author Donghuang + * @date Mar 24, 2020 15:22:13 + */ +@Getter +@Setter +@ToString +public class ReqCampaignDial implements Serializable { + private Integer campaignId; + private List> callingList; +} diff --git a/lib/fsesl-api/src/main/java/org/freeswitch/esl/client/transport/event/Event.java b/lib/fsesl-api/src/main/java/org/freeswitch/esl/client/transport/event/Event.java index a51a86c7..878ab002 100644 --- a/lib/fsesl-api/src/main/java/org/freeswitch/esl/client/transport/event/Event.java +++ b/lib/fsesl-api/src/main/java/org/freeswitch/esl/client/transport/event/Event.java @@ -141,6 +141,26 @@ public class Event { return getHeader("variable_x_conn_id"); } + /** + * Convenience method. + * get called number + * + * @return called number + */ + public String getCalledNumber() { + return getHeader("variable_x_called_number"); + } + + /** + * Convenience method. + * get dial type + * + * @return dial type + */ + public String getDialType() { + return getHeader("variable_x_dial_type"); + } + /** * Convenience method. * get other number diff --git a/lib/mapper/src/main/java/com/pudonghot/yo/mapper/AgentStatusMapper.java b/lib/mapper/src/main/java/com/pudonghot/yo/mapper/AgentStatusMapper.java index 4cc013a6..d1ec20b8 100644 --- a/lib/mapper/src/main/java/com/pudonghot/yo/mapper/AgentStatusMapper.java +++ b/lib/mapper/src/main/java/com/pudonghot/yo/mapper/AgentStatusMapper.java @@ -32,6 +32,34 @@ public interface AgentStatusMapper extends BaseMapper { @Param("lockKey") String lockKey, @Param("limit") int limit); + /** + * count online agent of queue + * @param queueId queue id + * @return online agent count of queue + */ + int countOnlineOfQueue(@Param("queueId") Integer queueId); + + /** + * count idle agent of queue + * @param queueId queue id + * @return idle agent count of queue + */ + int countIdleOfQueue(@Param("queueId") Integer queueId); + + /** + * count online agent of group + * @param groupId group id + * @return online agent count of group + */ + int countOnlineOfGroup(@Param("groupId") Integer groupId); + + /** + * count idle agent of group + * @param groupId group id + * @return idle agent count of group + */ + int countIdleOfGroup(@Param("groupId") Integer groupId); + /** * ACW clean up * diff --git a/lib/mapper/src/main/java/com/pudonghot/yo/mapper/AgentStatusMapper.xml b/lib/mapper/src/main/java/com/pudonghot/yo/mapper/AgentStatusMapper.xml index 8af0f326..28cffae4 100644 --- a/lib/mapper/src/main/java/com/pudonghot/yo/mapper/AgentStatusMapper.xml +++ b/lib/mapper/src/main/java/com/pudonghot/yo/mapper/AgentStatusMapper.xml @@ -32,27 +32,7 @@ update s join ( select s.id - from s - - join br_agent a - on a.id = s.agent_id - - join br_queue_agent qa - on qa.agent_id = a.id - - where qa.queue_id = #{queueId} - and a.active = 1 - and s.registered = 1 - and s.state = 'IDLE' - and (s.status = 'READY' or - (s.status = 'ACW' and - - ) - ) - and s.lock_key is null - + order by rand() limit #{limit} @@ -60,7 +40,6 @@ ) t on s.id = t.id set s.lock_key = #{lockKey}, - s.status = 'READY', s.acw_time = null, s.updated_time = now() @@ -69,23 +48,7 @@ update s join ( select s.id - from s - - join br_agent a - on a.id = s.agent_id - - where a.group_id = #{groupId} - and a.active = 1 - and s.registered = 1 - and s.state = 'IDLE' - and (s.status = 'READY' or - (s.status = 'ACW' and - - ) - ) - and s.lock_key is null + order by rand() @@ -94,11 +57,49 @@ ) t on s.id = t.id set s.lock_key = #{lockKey}, - s.status = 'READY', s.acw_time = null, s.updated_time = now() + + + + + + + + update s @@ -115,4 +116,41 @@ where s.state = 'ACW' and s.acw_time is not null + + + from s + + join br_agent a + on a.id = s.agent_id + + join br_queue_agent qa + on qa.agent_id = a.id + + where qa.queue_id = #{queueId} + + + + + from s + + join br_agent a + on a.id = s.agent_id + + where a.group_id = #{groupId} + + + + + and a.active = 1 + and s.registered = 1 + and s.status = 'READY' + and s.lock_key is null + and (s.state = 'IDLE' or + (s.status = 'ACW' and + + ) + ) + diff --git a/lib/mapper/src/main/java/com/pudonghot/yo/mapper/TrunkMapper.java b/lib/mapper/src/main/java/com/pudonghot/yo/mapper/TrunkMapper.java index 2fa4db72..6050b839 100644 --- a/lib/mapper/src/main/java/com/pudonghot/yo/mapper/TrunkMapper.java +++ b/lib/mapper/src/main/java/com/pudonghot/yo/mapper/TrunkMapper.java @@ -12,22 +12,43 @@ import com.pudonghot.yo.model.domain.Trunk; public interface TrunkMapper extends BaseMapper { /** - * list trunks of agent group id + * list trunks of strategy * * @param strategyId strategy id - * @return outbound prefixes + * @return trunks */ - List listOfStrategyId( + List listOfStrategy( + @Param("strategyId") + Integer strategyId); + + + /** + * find trunk of strategy + * + * @param strategyId strategy id + * @return trunk found + */ + Trunk findOfStrategy( @Param("strategyId") Integer strategyId); /** - * list trunks of agent group id + * list trunks of agent group * * @param agentGroupId agent group id - * @return outbound prefixes + * @return trunks found */ - List listOfAgentGroupId( + List listOfAgentGroup( + @Param("agentGroupId") + Integer agentGroupId); + + /** + * find trunk of agent group + * + * @param agentGroupId agent group id + * @return trunk found + */ + Trunk findOfAgentGroup( @Param("agentGroupId") Integer agentGroupId); } diff --git a/lib/mapper/src/main/java/com/pudonghot/yo/mapper/TrunkMapper.xml b/lib/mapper/src/main/java/com/pudonghot/yo/mapper/TrunkMapper.xml index 07d95406..c6afdd3c 100644 --- a/lib/mapper/src/main/java/com/pudonghot/yo/mapper/TrunkMapper.xml +++ b/lib/mapper/src/main/java/com/pudonghot/yo/mapper/TrunkMapper.xml @@ -9,7 +9,17 @@ --> - + + + + + + select t.* from br_trunk t join br_gateway gw @@ -30,9 +40,19 @@ where ts.id = #{strategyId} and t.active = 1 + + + - + + order by rand() + limit 1 + + + select t.* from br_trunk t join br_gateway gw @@ -55,7 +75,7 @@ where agts.agent_group_id = #{agentGroupId} and t.active = 1 - + + select t.* from + t + join br_campaign_trunk_strategy cts + on t.id = cts.trunk_strategy_id + where cts.campaign_id = #{campaignId} + and t.active = 1 + + + + diff --git a/lib/mapper/src/test/java/com/pudonghot/yo/mapper/AgentStatusMapperTest.java b/lib/mapper/src/test/java/com/pudonghot/yo/mapper/AgentStatusMapperTest.java index aee01fe8..47c0a400 100644 --- a/lib/mapper/src/test/java/com/pudonghot/yo/mapper/AgentStatusMapperTest.java +++ b/lib/mapper/src/test/java/com/pudonghot/yo/mapper/AgentStatusMapperTest.java @@ -32,6 +32,10 @@ public class AgentStatusMapperTest { @Test public void testAcwCleanup() { + mapper.countOnlineOfQueue(1); + mapper.countIdleOfQueue(1); + mapper.countOnlineOfGroup(1); + mapper.countIdleOfGroup(1); mapper.acwCleanup(); } } diff --git a/lib/model/src/main/java/com/pudonghot/yo/model/DailyTime.java b/lib/model/src/main/java/com/pudonghot/yo/model/DailyTime.java index 3cb1383d..876f60e4 100644 --- a/lib/model/src/main/java/com/pudonghot/yo/model/DailyTime.java +++ b/lib/model/src/main/java/com/pudonghot/yo/model/DailyTime.java @@ -1,7 +1,10 @@ package com.pudonghot.yo.model; import lombok.Getter; +import java.util.Date; +import java.time.ZoneId; import java.io.Serializable; +import java.time.LocalDateTime; import com.pudonghot.yo.util.TimeUtils; import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonValue; @@ -34,6 +37,16 @@ public class DailyTime implements Serializable { this.val = val; } + public boolean before(final Date date) { + return secondOfDay < LocalDateTime.ofInstant(date.toInstant(), + ZoneId.systemDefault()).toLocalTime().toSecondOfDay(); + } + + public boolean after(final Date date) { + return secondOfDay > LocalDateTime.ofInstant(date.toInstant(), + ZoneId.systemDefault()).toLocalTime().toSecondOfDay(); + } + /** * {@inheritDoc} */ diff --git a/lib/model/src/test/java/com/pudonghot/yo/TestDriver.java b/lib/model/src/test/java/com/pudonghot/yo/TestDriver.java index c73c8d22..7fa0ee5d 100644 --- a/lib/model/src/test/java/com/pudonghot/yo/TestDriver.java +++ b/lib/model/src/test/java/com/pudonghot/yo/TestDriver.java @@ -1,10 +1,14 @@ package com.pudonghot.yo; -import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.Test; +import java.util.Date; +import java.time.LocalTime; +import lombok.extern.slf4j.Slf4j; +import com.pudonghot.yo.model.DailyTime; import com.fasterxml.jackson.databind.ObjectMapper; import com.pudonghot.yo.model.agentevent.AgentEvent; -import org.junit.Test; -import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.DateFormatUtils; +import com.fasterxml.jackson.core.JsonProcessingException; /** * @author Donghuang
@@ -26,4 +30,20 @@ public class TestDriver { final String result = objectMapper.writeValueAsString(agentEvent); log.info("Result: [{}].", result); } + + @Test + public void testDailyTime() { + log.info("ToSecondOfDay: [{}].", LocalTime.now().toSecondOfDay()); + log.info("SecondOfDay: [{}].", + new DailyTime(DateFormatUtils.format(new Date(), "HH:mm")).getSecondOfDay()); + + log.info("DailyTime: [{}].", new DailyTime("09:11").getSecondOfDay()); + log.info("DailyTime: [{}].", new DailyTime("00:00")); + log.info("DailyTime: [{}].", new DailyTime("19:00:04")); + log.info("DailyTime: [{}].", new DailyTime("23:00").getSecondOfDay()); + log.info("DailyTime: [{}].", new DailyTime("09:00:04").getSecondOfDay()); + log.info("DailyTime: [{}].", new DailyTime("00:00").getSecondOfDay()); + log.info("DailyTime: [{}].", new DailyTime("09:00").before(new Date())); + log.info("DailyTime: [{}].", new DailyTime("23:00:03").after(new Date())); + } } diff --git a/lib/ops/install-springboot-app/cas.zip b/lib/ops/install-springboot-app/cas.zip new file mode 100644 index 00000000..2617d7d2 Binary files /dev/null and b/lib/ops/install-springboot-app/cas.zip differ diff --git a/lib/ops/install-springboot-app/fs-conf.zip b/lib/ops/install-springboot-app/fs-conf.zip new file mode 100644 index 00000000..6e4dada3 Binary files /dev/null and b/lib/ops/install-springboot-app/fs-conf.zip differ diff --git a/lib/ops/install-springboot-app/install-springboot-app.sh b/lib/ops/install-springboot-app/install-springboot-app.sh index 6f7c5b2a..22864398 100755 --- a/lib/ops/install-springboot-app/install-springboot-app.sh +++ b/lib/ops/install-springboot-app/install-springboot-app.sh @@ -16,13 +16,20 @@ get_real_path() { } install_server() { - SERVER="$1" - APP="$2" - TARGET_DIR="/data/program/$APP" - LOGS_DIR="/data/program/logs/$APP" - ssh "$SERVER" "mkdir -p $TARGET_DIR" - ssh "$SERVER" "tar -xzv -C $TARGET_DIR" < springboot-app.tar.gz - ssh "$SERVER" "mkdir -p $LOGS_DIR && ln -sf $LOGS_DIR $TARGET_DIR/logs" + local SERVER="$1" + local APP="$2" + local PORT="$3" + if [ -z "$PORT" ]; then + PORT='22' + fi + + local BASE_DIR='/data/program' + local TARGET_DIR="$BASE_DIR/$APP" + local LOGS_DIR="$BASE_DIR/logs/$APP" + + ssh -p "$PORT" "$SERVER" "mkdir -p $TARGET_DIR" + ssh -p "$PORT" "$SERVER" "tar -xzv -C $TARGET_DIR" < springboot-app.tar.gz + ssh -p "$PORT" "$SERVER" "mkdir -p $LOGS_DIR && ln -sf $LOGS_DIR $TARGET_DIR/logs" } @@ -42,8 +49,9 @@ fi if [ "$2" == "test" ]; then echo "Install test." install_server 'appweb@118.24.251.131' $1 -elif ["$2" == "prod" ]; then - echo "Deploy prod." +elif [ "$2" == "prod" ]; then + echo "Install prod." + install_server 'xiandou@113.87.175.72' $1 '51022' fi popd > /dev/null diff --git a/lib/service-common/src/main/java/com/pudonghot/yo/service/CommonCallDataService.java b/lib/service-common/src/main/java/com/pudonghot/yo/service/CommonCallDataService.java new file mode 100644 index 00000000..c140998d --- /dev/null +++ b/lib/service-common/src/main/java/com/pudonghot/yo/service/CommonCallDataService.java @@ -0,0 +1,63 @@ +package com.pudonghot.yo.service; + +/** + * @author Donghuang + * @date Jul 23, 2020 09:57:09 + */ +public interface CommonCallDataService { + + /** + * save call data + * + * @param agentId agent id + * @param callData call data + */ + void saveIvrCallData(String agentId, String callData); + + /** + * get call data + * + * @param agentId agent id + * @return call data + */ + String getIvrCallData(String agentId); + + + /** + * save call data + * + * @param callUuid call uuid + * @param callData call data + */ + void saveCampaignCallData(String callUuid, String callData); + + /** + * get call data + * + * @param callUuid call uuid + * @return call data + */ + String getCampaignCallData(String callUuid); + + /** + * get campaign channel + * + * @param campaignId campaign id + * @return count of campaign channel + */ + int getCampaignChannel(Integer campaignId); + + /** + * increase campaign channel + * + * @param campaignId campaign id + */ + int incrCampaignChannel(Integer campaignId); + + /** + * decrease campaign channel + * + * @param campaignId campaign id + */ + int decrCampaignChannel(Integer campaignId); +} diff --git a/lib/service-common/src/main/java/com/pudonghot/yo/service/impl/CommonAgentStatusServiceImpl.java b/lib/service-common/src/main/java/com/pudonghot/yo/service/impl/CommonAgentStatusServiceImpl.java index fa84f9aa..e0aa4c34 100644 --- a/lib/service-common/src/main/java/com/pudonghot/yo/service/impl/CommonAgentStatusServiceImpl.java +++ b/lib/service-common/src/main/java/com/pudonghot/yo/service/impl/CommonAgentStatusServiceImpl.java @@ -16,7 +16,6 @@ import com.pudonghot.yo.service.CommonChannelService; import com.pudonghot.yo.service.CommonDelayScheduleService; import com.pudonghot.yo.service.CommonAgentStatusService; import org.springframework.beans.factory.annotation.Autowired; -import static com.pudonghot.yo.model.exception.ErrorCode.AGENT_UPDATE_STATUS_FAIL; /** * @author Donghuang
@@ -198,7 +197,7 @@ public class CommonAgentStatusServiceImpl @Override public void inACall(final Agent agent) { log.info("Agent [{}] in a call.", agent); - updateState(agent); + updateState(agent, AgentStatus.State.IN_A_CALL); } /** @@ -216,8 +215,11 @@ public class CommonAgentStatusServiceImpl public void acw(final Agent agent) { log.info("ACW agent [{}].", agent); - final AgentStatus agentStatus = findInACallAgentStatus(agent); + final AgentStatus agentStatus = + findInACallAgentStatus(agent); + if (agentStatus == null) { + log.info("No agent status found, ignore."); return; } @@ -227,7 +229,7 @@ public class CommonAgentStatusServiceImpl delayScheduleService.acw(agent); } else { - setState(agent, agentStatus); + agentStatus.setState(AgentStatus.State.IDLE); } log.info("Update agent status [{}].", agentStatus); @@ -260,8 +262,12 @@ public class CommonAgentStatusServiceImpl log.info("End ACW agent [{}].", agent); final AgentStatus agentStatus = findValidAgentStatus(agent); - AssertUtils.state(agentStatus.getState() == AgentStatus.State.ACW, - AGENT_UPDATE_STATUS_FAIL); + + if (agentStatus.getState() != AgentStatus.State.ACW) { + log.info("Current state is not ACW, ignore."); + return; + } + clear(agentStatus); setState(agent, agentStatus); agentStatus.setAcwTime(null); @@ -283,8 +289,7 @@ public class CommonAgentStatusServiceImpl @Override public void idle(final Agent agent) { log.info("Idle agent [{}] call [{}].", agent); - - updateState(agent); + updateState(agent, AgentStatus.State.IDLE); } /** @@ -319,10 +324,10 @@ public class CommonAgentStatusServiceImpl return agentStatus; } - private void updateState(final Agent agent) { + private void updateState(final Agent agent, final AgentStatus.State state) { final AgentStatus agentStatus = findValidAgentStatus(agent); clear(agentStatus); - setState(agent, agentStatus); + agentStatus.setState(state); log.info("Update agent status [{}].", agentStatus); agentStatusMapper.update(agentStatus); diff --git a/lib/service-common/src/main/java/com/pudonghot/yo/service/impl/CommonCallDataServiceImpl.java b/lib/service-common/src/main/java/com/pudonghot/yo/service/impl/CommonCallDataServiceImpl.java new file mode 100644 index 00000000..6737e7e5 --- /dev/null +++ b/lib/service-common/src/main/java/com/pudonghot/yo/service/impl/CommonCallDataServiceImpl.java @@ -0,0 +1,101 @@ +package com.pudonghot.yo.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RMapCache; +import java.util.concurrent.TimeUnit; +import org.redisson.api.RedissonClient; +import com.pudonghot.yo.service.CommonCallDataService; +import org.springframework.beans.factory.annotation.Value; + +/** + * @author Donghuang + * @date Jul 23, 2020 10:04:23 + */ +@Slf4j +public class CommonCallDataServiceImpl implements CommonCallDataService { + private final RMapCache CACHE; + private final RMapCache CAMPAIGN_CHANNEL; + @Value("${yo.common-service.call-data.timeout-seconds:120}") + private long callDataTimeoutSeconds; + + public CommonCallDataServiceImpl(final RedissonClient redisson) { + this.CACHE = redisson.getMapCache("CALL_DATA"); + this.CAMPAIGN_CHANNEL = redisson.getMapCache("CAMPAIGN_CHANNEL"); + } + + /** + * {@inheritDoc} + */ + @Override + public void saveIvrCallData(final String agentId, final String callData) { + log.info("Save IVR call data [{}] -> [{}].", agentId, callData); + put(ivrCacheKey(agentId), callData); + } + + /** + * {@inheritDoc} + */ + @Override + public String getIvrCallData(final String agentId) { + log.debug("Get IVR call data [{}].", agentId); + return CACHE.get(ivrCacheKey(agentId)); + } + + /** + * {@inheritDoc} + */ + @Override + public void saveCampaignCallData(String callUuid, String callData) { + log.info("Save campaign call data [{}] -> [{}].", callUuid, callData); + put(callUuid, callData); + } + + /** + * {@inheritDoc} + */ + @Override + public String getCampaignCallData(final String callUuid) { + log.debug("Get campaign call data [{}].", callUuid); + return CACHE.get(callUuid); + } + + /** + * {@inheritDoc} + */ + @Override + public int getCampaignChannel(final Integer campaignId) { + final int val = CAMPAIGN_CHANNEL.getOrDefault(campaignId, 0); + log.debug("Get campaign [{}] channel [{}].", campaignId, val); + return val; + } + + /** + * {@inheritDoc} + */ + @Override + public int incrCampaignChannel(final Integer campaignId) { + final int val = CAMPAIGN_CHANNEL.addAndGet(campaignId, 1); + log.debug("Increase campaign [{}] channel [{}].", campaignId, val); + return val; + } + + /** + * {@inheritDoc} + */ + @Override + public int decrCampaignChannel(final Integer campaignId) { + final int val = CAMPAIGN_CHANNEL.addAndGet(campaignId, -1); + log.debug("Decrease campaign [{}] channel [{}].", campaignId, val); + return val; + } + + private void put(final String key, final String value) { + log.debug("Call data cache [{}] -> [{}].", key, value); + CACHE.put( + key, value, callDataTimeoutSeconds, TimeUnit.SECONDS); + } + + private String ivrCacheKey(final String agentId) { + return "IVR_CALL_DATA:" + agentId; + } +} diff --git a/lib/service-common/src/main/resources/spring/spring-yo-service-common.xml b/lib/service-common/src/main/resources/spring/spring-yo-service-common.xml index 7cfbf221..d0df1395 100644 --- a/lib/service-common/src/main/resources/spring/spring-yo-service-common.xml +++ b/lib/service-common/src/main/resources/spring/spring-yo-service-common.xml @@ -8,4 +8,5 @@ + diff --git a/lib/service-common/src/test/java/com/pudonghot/yo/service/CommonCallDataServiceTest.java b/lib/service-common/src/test/java/com/pudonghot/yo/service/CommonCallDataServiceTest.java new file mode 100644 index 00000000..e6a8c624 --- /dev/null +++ b/lib/service-common/src/test/java/com/pudonghot/yo/service/CommonCallDataServiceTest.java @@ -0,0 +1,26 @@ +package com.pudonghot.yo.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Donghuang + * @date Jul 23, 2020 19:44:47 + */ +@Slf4j +@SpringBootTest(classes = TestDriver.class) +@RunWith(SpringJUnit4ClassRunner.class) +public class CommonCallDataServiceTest { + @Autowired + private CommonCallDataService callDataService; + + @Test + public void testRun() { + log.info("Channels [{}].", callDataService.incrCampaignChannel(1)); + log.info("Channels [{}].", callDataService.decrCampaignChannel(2)); + } +} diff --git a/lib/util/src/main/java/com/pudonghot/yo/common/util/MobileUtils.java b/lib/util/src/main/java/com/pudonghot/yo/common/util/MobileUtils.java deleted file mode 100644 index 0bd348f3..00000000 --- a/lib/util/src/main/java/com/pudonghot/yo/common/util/MobileUtils.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.pudonghot.yo.common.util; - -import org.apache.commons.lang3.StringUtils; - - -/** - * 手机号工具类 - * Created by bingpo on 17/12/28. - */ -public class MobileUtils { - - public static final int MOBILE_LENGTH = 11; - private static final String MOBILE_REG_EXP = "^1(3[0-9]|4[579]|5[0-35-9]|7[0135678]|8[0-9])\\d{8}$"; - - /** - * 验证手机号码 - *

- * 移动号码段:134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188 - * 联通号码段:130,131,132,145,155,156,171,175,176,185,186 - * 电信号码段:133,149,153,170,173,177,180,181,189 - * - * @param mobile 手机号 - * @return 是否为手机号 - */ - public static boolean checkMobile(String mobile) { - return StringUtils.length(mobile) == MOBILE_LENGTH && mobile.matches(MOBILE_REG_EXP); - } - - /** - * 处理手机号 - * - * @param mobile 手机号 - * @return 处理后的手机号 - */ - public static String formatMobile(String mobile) { - if (StringUtils.length(mobile) > MOBILE_LENGTH) { - return mobile.substring(mobile.length() - MOBILE_LENGTH); - } - return mobile; - } - - /** - * 去掉手机号的前缀 - * - * @param mobileStr mobileStr - * @return 去掉前缀后的手机号 - */ - public static String trimMobilePrefix(String mobileStr) { - if (StringUtils.length(mobileStr) > MOBILE_LENGTH) { - final String mobile = mobileStr.substring(mobileStr.length() - MOBILE_LENGTH); - return checkMobile(mobile) ? mobile : mobileStr; - } - return mobileStr; - } - -} diff --git a/lib/util/src/main/java/com/pudonghot/yo/util/PhoneNumberUtils.java b/lib/util/src/main/java/com/pudonghot/yo/util/PhoneNumberUtils.java new file mode 100644 index 00000000..5bad999d --- /dev/null +++ b/lib/util/src/main/java/com/pudonghot/yo/util/PhoneNumberUtils.java @@ -0,0 +1,41 @@ +package com.pudonghot.yo.util; + +/** + * @author Donghuang + * @date Jul 20, 2020 18:27:52 + */ +public class PhoneNumberUtils { + + public static final int MOBILE_LENGTH = 11; + private static final String MOBILE_REG_EXP = "^1[3-9]\\d{8}$"; + + /** + * 验证手机号码 + *

+ * 移动号码段:134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188 + * 联通号码段:130,131,132,145,155,156,171,175,176,185,186 + * 电信号码段:133,149,153,170,173,177,180,181,189 + * + * @param mobile 手机号 + * @return 是否为手机号 + */ + public static boolean isMobile(final String mobile) { + return mobile.length() == MOBILE_LENGTH + && mobile.matches(MOBILE_REG_EXP); + } + + /** + * cleanup mobile + * 01378888666 -> 1378888666 + * @param mobile mobile number + * @return mobile cleaned + */ + public static String cleanupMobile(final String mobile) { + if (mobile.length() > MOBILE_LENGTH) { + final String trim = mobile.substring( + mobile.length() - MOBILE_LENGTH); + return isMobile(trim) ? trim : mobile; + } + return mobile; + } +} diff --git a/lib/util/src/main/java/com/pudonghot/yo/util/TimeUtils.java b/lib/util/src/main/java/com/pudonghot/yo/util/TimeUtils.java index 51675094..40645f4d 100644 --- a/lib/util/src/main/java/com/pudonghot/yo/util/TimeUtils.java +++ b/lib/util/src/main/java/com/pudonghot/yo/util/TimeUtils.java @@ -13,7 +13,7 @@ public class TimeUtils { * 00:00 - 23:59 */ public static final Pattern TIME_PATTERN = - Pattern.compile("^((?:[01]?[0-9])|(?:2[0-3])):([0-5]?[0-9])$"); + Pattern.compile("^([01]?[0-9]|2[0-3]):([0-5]?[0-9])(?::([0-5]?[0-9]))?$"); /** * @param time HH:mm @@ -23,8 +23,10 @@ public class TimeUtils { if (StringUtils.isNotBlank(time)) { final Matcher matcher = TIME_PATTERN.matcher(time); if (matcher.find()) { + final String sec = matcher.group(3); return Integer.parseInt(matcher.group(1)) * 60 * 60 + - Integer.parseInt(matcher.group(2)) * 60; + Integer.parseInt(matcher.group(2)) * 60 + + (StringUtils.isNotBlank(sec) ? Integer.parseInt(sec) : 0); } } return null; diff --git a/openapi/src/main/java/com/pudonghot/yo/openapi/controller/CallController.java b/openapi/src/main/java/com/pudonghot/yo/openapi/controller/CallController.java index bd7919d0..0b24d146 100644 --- a/openapi/src/main/java/com/pudonghot/yo/openapi/controller/CallController.java +++ b/openapi/src/main/java/com/pudonghot/yo/openapi/controller/CallController.java @@ -1,6 +1,5 @@ package com.pudonghot.yo.openapi.controller; -import com.pudonghot.yo.fsagent.api.IvrTransferService; import lombok.extern.slf4j.Slf4j; import com.pudonghot.yo.model.domain.Agent; import org.apache.commons.lang3.StringUtils; @@ -13,6 +12,7 @@ import com.pudonghot.yo.model.exception.AssertUtils; import com.pudonghot.yo.openapi.service.AgentService; import com.pudonghot.yo.openapi.auth.SessionAbility; import com.pudonghot.yo.service.CommonChannelService; +import com.pudonghot.yo.fsagent.api.IvrTransferService; import com.pudonghot.yo.openapi.request.ReqAgentDialout; import com.pudonghot.yo.fsagent.api.request.ReqAgentDial; import com.pudonghot.yo.service.CommonAgentStatusService; diff --git a/openapi/src/main/java/com/pudonghot/yo/openapi/controller/CampaignController.java b/openapi/src/main/java/com/pudonghot/yo/openapi/controller/CampaignController.java new file mode 100644 index 00000000..6cd38213 --- /dev/null +++ b/openapi/src/main/java/com/pudonghot/yo/openapi/controller/CampaignController.java @@ -0,0 +1,48 @@ +package com.pudonghot.yo.openapi.controller; + +import javax.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import com.pudonghot.yo.model.domain.Agent; +import org.springframework.stereotype.Controller; +import com.pudonghot.yo.openapi.request.ReqCampaign; +import com.pudonghot.yo.openapi.auth.SessionAbility; +import com.pudonghot.yo.openapi.service.AgentService; +import com.pudonghot.yo.service.CommonAgentStatusService; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author Donghuang + * @date Jul 02, 2020 11:53:21 + */ +@Slf4j +@Controller +public class CampaignController implements SessionAbility { + + @Autowired + private AgentService agentService; + @Autowired + private CommonAgentStatusService agentStatusService; + + @RequestMapping("/resource/task/{account}") + public void addToQueue( + @PathVariable("account") + final String account, + @Valid final ReqCampaign form) { + final Agent agent = agentService.findValid( + form.getTenantId(), account); + agentStatusService.findValidAgentStatus(agent); + // TODO Add agent to queue + } + + @RequestMapping("/resource/task/start") + public void start(@Valid final ReqCampaign form) { + // TODO start campaign + } + + @RequestMapping("/resource/task/stop") + public void stop(@Valid final ReqCampaign form) { + // TODO stop campaign + } +} diff --git a/openapi/src/main/java/com/pudonghot/yo/openapi/controller/DialController.java b/openapi/src/main/java/com/pudonghot/yo/openapi/controller/DialController.java deleted file mode 100644 index e326e2c9..00000000 --- a/openapi/src/main/java/com/pudonghot/yo/openapi/controller/DialController.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.pudonghot.yo.openapi.controller; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import com.pudonghot.yo.fsagent.api.DialService; -import com.pudonghot.yo.openapi.dto.request.AgentDial; -import com.pudonghot.yo.fsagent.api.request.ReqAgentDial; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import com.pudonghot.yo.openapi.dto.response.AgentDialResp; - -/** - * @author Donghuang
- * Jan 03, 2020 18:20:59 - */ -@Slf4j -@Controller -@RequestMapping("/v1") -public class DialController { - @Autowired - private DialService dialService; - - @RequestMapping("/dial") - public AgentDialResp dial(final AgentDial req) { - return new AgentDialResp(dialService.agentDial( - req.copy(new ReqAgentDial())).getUuid()); - } -} diff --git a/openapi/src/main/java/com/pudonghot/yo/openapi/request/ReqCampaign.java b/openapi/src/main/java/com/pudonghot/yo/openapi/request/ReqCampaign.java new file mode 100644 index 00000000..236c6858 --- /dev/null +++ b/openapi/src/main/java/com/pudonghot/yo/openapi/request/ReqCampaign.java @@ -0,0 +1,27 @@ +package com.pudonghot.yo.openapi.request; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import javax.validation.constraints.NotBlank; + +/** + * @author Donghuang + * @date Jul 09, 2020 11:09:01 + */ +@Getter +@Setter +@ToString +public class ReqCampaign extends BaseForm { + @NotBlank + private String campaignKey; + private String campaignName; + + public void setTaskid(final String taskId) { + this.campaignKey = taskId; + } + + public void setTaskname(final String taskName) { + this.campaignName = taskName; + } +} diff --git a/openapi/src/main/resources/application.properties b/openapi/src/main/resources/application.properties index 7a11dac0..14745ebd 100644 --- a/openapi/src/main/resources/application.properties +++ b/openapi/src/main/resources/application.properties @@ -31,6 +31,6 @@ yo.openapi.default-tenant=GOBLIN # Dubbo ## Dubbo Registry -dubbo.registry.address=zookeeper://172.16.67.223:2181 +dubbo.registry.address=zookeeper://172.18.4.35:2181 dubbo.registry.file=${user.home}/dubbo-cache/${spring.application.name}/dubbo.cache yo.fsagent.dubbo.service.version=1.0.0 diff --git a/state/src/main/java/com/pudonghot/yo/state/service/impl/AgentStatusScheduleServiceImpl.java b/state/src/main/java/com/pudonghot/yo/state/service/impl/AgentStatusScheduleServiceImpl.java index 684a784f..0e384ccd 100644 --- a/state/src/main/java/com/pudonghot/yo/state/service/impl/AgentStatusScheduleServiceImpl.java +++ b/state/src/main/java/com/pudonghot/yo/state/service/impl/AgentStatusScheduleServiceImpl.java @@ -1,7 +1,7 @@ package com.pudonghot.yo.state.service.impl; -import java.util.Date; import java.util.Map; +import java.util.Date; import java.util.HashMap; import lombok.extern.slf4j.Slf4j; import com.wacai.tigon.mybatis.Search; @@ -60,6 +60,8 @@ public class AgentStatusScheduleServiceImpl implements AgentStatusScheduleServic update.put(AgentStatus.REGISTERED, false); agentStatusMapper.update(update, new Search(AgentStatus.CHECK_REG_KEY, checkRegKey)); + log.info("ACW cleanup."); + agentStatusMapper.acwCleanup(); } private Map checkRegKeyUpdate(final String checkRegKey) {