add calling list

This commit is contained in:
Shaun Chyxion 2020-09-14 00:08:22 +08:00
parent 109532b21a
commit 38c4be39b8
28 changed files with 441 additions and 338 deletions

View File

@ -3,10 +3,7 @@ package com.pudonghot.yo.campaign.feign.response;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.apache.commons.lang3.tuple.Pair;
import com.pudonghot.yo.util.PhoneNumberUtils;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Donghuang
@ -16,35 +13,25 @@ import com.fasterxml.jackson.annotation.JsonProperty;
@Setter
@ToString
public class RespCallingList {
@JsonProperty("retcode")
@JsonAlias("retcode")
private String code;
@JsonProperty("message")
@JsonAlias("message")
private String error;
@JsonProperty("taskid")
@JsonAlias("taskid")
private String campaignKey;
@JsonAlias("taskName")
@JsonProperty("taskname")
@JsonAlias({"taskName", "taskname"})
private String campaignName;
@JsonProperty("taskdata")
@JsonAlias("taskdata")
private CallingData[] data;
@Getter
@Setter
public static class CallingData {
@JsonProperty("outid")
@JsonAlias("outid")
private String caseKey;
private String phone;
/**
* to pair (phone, caseKey)
*
* @return pair
*/
public Pair<String, String> toPair() {
return Pair.of(PhoneNumberUtils.cleanupMobile(phone), caseKey);
}
/**
* {@inheritDoc}
*/

View File

@ -1,29 +0,0 @@
package com.pudonghot.yo.campaign.feign.response;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Donghuang
* @date Jul 22, 2020 14:17:40
*/
@Getter
@Setter
@ToString
public class RespCallingListWrapper {
private String code;
@JsonProperty("message")
private String error;
private RespCallingList data;
/**
* is response success
*
* @return true if code == 000000
*/
public boolean isSuccess() {
return "000000".equals(code);
}
}

View File

@ -1,6 +1,7 @@
package com.pudonghot.yo.campaign.service;
import java.util.List;
import com.pudonghot.yo.model.domain.Campaign;
import com.wacai.tigon.service.BaseCrudService;
import com.pudonghot.yo.model.domain.CallingList;
@ -10,6 +11,12 @@ import com.pudonghot.yo.model.domain.CallingList;
*/
public interface CallingListService extends BaseCrudService<Integer, CallingList> {
/**
* fetch remote calling list
* @param campaign campaign
*/
void fetchRemote(Campaign campaign);
/**
* list limit ready records
*
@ -17,11 +24,5 @@ public interface CallingListService extends BaseCrudService<Integer, CallingList
* @param limit limit
* @return CallingLists
*/
List<CallingList> listReady(final Integer campaignId, final int limit);
/**
* release calling list lock
* @param callingList callingList
*/
void releaseCallingListLock(final CallingList callingList);
List<CallingList> listReady(Integer campaignId, int limit);
}

View File

@ -8,4 +8,10 @@ import com.wacai.tigon.service.BaseQueryService;
* @date Jul 18, 2020 17:24:50
*/
public interface CampaignService extends BaseQueryService<Integer, Campaign> {
/**
* dial campaign call
* @param campaign campaign
*/
void dial(final Campaign campaign);
}

View File

@ -3,20 +3,31 @@ package com.pudonghot.yo.campaign.service.impl;
import java.util.Map;
import java.util.Date;
import java.util.List;
import lombok.Setter;
import java.util.HashMap;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import com.pudonghot.yo.util.TimeUtils;
import java.util.stream.Collectors;
import com.wacai.tigon.mybatis.Search;
import com.pudonghot.yo.util.TimeUtils;
import com.wacai.tigon.sequence.IdSequence;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.StringUtils;
import com.pudonghot.yo.model.domain.Campaign;
import com.pudonghot.yo.util.PhoneNumberUtils;
import org.springframework.stereotype.Service;
import org.apache.commons.lang3.time.DateUtils;
import com.pudonghot.yo.mapper.CallingListMapper;
import com.pudonghot.yo.model.domain.CallingList;
import org.springframework.context.annotation.Lazy;
import com.pudonghot.yo.campaign.service.CampaignService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import com.pudonghot.yo.campaign.service.CallingListService;
import com.wacai.tigon.service.support.BaseCrudServiceSupport;
import org.springframework.beans.factory.annotation.Autowired;
import com.pudonghot.yo.campaign.feign.response.RespCallingList;
import static com.pudonghot.yo.model.domain.Campaign.TargetType.QUEUE;
import com.pudonghot.yo.campaign.feign.service.FeignCallingListService;
/**
* @author Donghuang
@ -28,23 +39,48 @@ public class CallingListServiceImpl
extends BaseCrudServiceSupport<Integer, CallingList, CallingListMapper>
implements CallingListService {
@Setter(onMethod_ = {@Lazy, @Autowired})
private CampaignService campaignService;
@Autowired
private IdSequence idSeq;
@Autowired
private FeignCallingListService callingListService;
@Value("${yo.br.campaign.callinglist-lock-expire.seconds:300}")
@Value("${yo.campaign.calling-list-lock-expire.seconds:300}")
private int expireLockDuration;
@Value("${yo.campaign.calling-list.fetch-size:24}")
private int fetchSize;
@Scheduled(fixedRateString = "${yo.br.campaign.callinglist-lock-expire.rate:180000}")
@Scheduled(fixedRateString = "${yo.campaign.callinglist-lock-expire.rate:120000}")
public void expireLock() {
log.info("Expire calling list lock task");
final Date expireTime = DateUtils.addSeconds(new Date(), -expireLockDuration);
scan(new Search(CallingList.ACTIVE, true)
.gt(CallingList.CREATED_TIME, DateUtils.addDays(new Date(), -1))
.notNull(CallingList.LAST_CONN_ID)
.lt(CallingList.LOCK_TIME, expireTime), callingList -> {
callingList.setLockTime(null);
callingList.setLockKey(null);
mapper.update(callingList);
log.info("Expire calling list lock task.");
final Map<String, Object> update = new HashMap<>(8);
update.put(CallingList.LOCK_KEY, null);
update.put(CallingList.LOCK_TIME, null);
update.put(CallingList.UPDATED_TIME, new Date());
mapper.update(update,
new Search(CallingList.ACTIVE, true)
.gt(CallingList.ADDED_TIME, DateUtils.addDays(new Date(), -1))
.lt(CallingList.LOCK_TIME,
DateUtils.addSeconds(new Date(), -expireLockDuration)));
}
@Scheduled(fixedRateString = "${yo.campaign.task-scheduler.fixed-rate:180000}",
initialDelayString = "${yo.campaign.task-scheduler.init-delay:32000}")
public void fetchRemoteTaskScheduler() {
log.debug("Campaign fetch remote calling list task.");
final int secondOfDay = TimeUtils.secondOfDay(new Date());
campaignService.scan(new Search(Campaign.ACTIVE, true)
.eq(Campaign.STATUS, Campaign.Status.RUNNING)
.lt(Campaign.DAILY_FROM, secondOfDay)
.gt(Campaign.DAILY_TO, secondOfDay), campaign -> {
if (campaign.getTargetType() != QUEUE) {
log.info("Campaign [{}] target is not queue, ignore.", campaign);
return;
}
fetchRemote(campaign);
});
}
@ -52,34 +88,71 @@ public class CallingListServiceImpl
* {@inheritDoc}
*/
@Override
public List<CallingList> listReady(final Integer campaignId, int limit) {
final String lockKey = idSeq.get();
final Map<String, Object> update = new HashMap<>(8);
update.put(CallingList.LOCK_KEY, lockKey);
update.put(CallingList.LOCK_TIME, new Date());
update.put(CallingList.STATUS, CallingList.Status.ACHIEVED);
public void fetchRemote(final Campaign campaign) {
log.info("campaign [{}] fetch calling list data.", campaign);
final int time = TimeUtils.secondOfDay(new Date());
final Integer campaignId = campaign.getId();
final String campaignKey = campaign.getCampaignKey();
final RespCallingList callingList =
callingListService.fetchCallingList(fetchSize,
campaignKey,
campaign.getName());
final RespCallingList.CallingData[] data =
callingList.getData();
mapper.update(update,
new Search(CallingList.CAMPAIGN_ID, campaignId)
.eq(CallingList.STATUS, CallingList.Status.READY)
.isNull(CallingList.LOCK_KEY)
.lt(CallingList.DAILY_FROM, time)
.gt(CallingList.DAILY_TO, time)
.limit(limit));
if (data == null || data.length == 0) {
log.error("Fetch calling list data error caused. code [{}], error [{}].",
callingList.getCode(), callingList.getError());
return;
}
return mapper.list(new Search(CallingList.CAMPAIGN_ID, campaignId)
.eq(CallingList.LOCK_KEY, lockKey).limit(limit));
if (log.isInfoEnabled()) {
log.info("CallingListData: [{}][{}][{}]",
campaignId, campaignKey, StringUtils.join(data, "|"));
}
mapper.insert(Stream.of(data).map(it -> {
final CallingList cl = new CallingList();
cl.setTenantId(campaign.getTenantId());
cl.setTenantCode(campaign.getTenantCode());
cl.setCampaignId(campaignId);
cl.setCampaignKey(campaignKey);
cl.setDailyFrom(campaign.getDailyFrom());
cl.setDailyTo(campaign.getDailyTo());
cl.setStatus(CallingList.Status.READY);
cl.setCaseKey(it.getCaseKey());
cl.setPhone(PhoneNumberUtils.cleanupMobile(it.getPhone()));
cl.setLastConnected(false);
final Date now = new Date();
cl.setAddedTime(now);
cl.setCalledTimes(0);
cl.setCreatedTime(now);
return cl;
}).collect(Collectors.toList()));
}
/**
* {@inheritDoc}
*/
@Override
public void releaseCallingListLock(final CallingList callingList) {
callingList.setLockKey(null);
callingList.setLockTime(null);
mapper.update(callingList);
public List<CallingList> listReady(final Integer campaignId, final int limit) {
final String lockKey = idSeq.get();
final Map<String, Object> update = new HashMap<>(8);
update.put(CallingList.LOCK_KEY, lockKey);
update.put(CallingList.LOCK_TIME, new Date());
update.put(CallingList.STATUS, CallingList.Status.ACHIEVED);
final int secondOfDay = TimeUtils.secondOfDay(new Date());
mapper.update(update,
new Search(CallingList.CAMPAIGN_ID, campaignId)
.eq(CallingList.STATUS, CallingList.Status.READY)
.isNull(CallingList.LOCK_KEY)
.lt(CallingList.DAILY_FROM, secondOfDay)
.gt(CallingList.DAILY_TO, secondOfDay)
.limit(limit));
return mapper.list(new Search(CallingList.CAMPAIGN_ID, campaignId)
.eq(CallingList.LOCK_KEY, lockKey).limit(limit));
}
}

View File

@ -1,34 +1,33 @@
package com.pudonghot.yo.campaign.service.impl;
import lombok.val;
import lombok.Setter;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.concurrent.TimeUnit;
import com.pudonghot.yo.util.LogMDC;
import lombok.RequiredArgsConstructor;
import com.pudonghot.yo.util.ListUtils;
import com.wacai.tigon.mybatis.Search;
import com.pudonghot.yo.util.TimeUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.StringUtils;
import com.pudonghot.yo.mapper.CampaignMapper;
import org.springframework.stereotype.Service;
import com.pudonghot.yo.model.domain.Campaign;
import com.pudonghot.yo.mapper.AgentStatusMapper;
import org.springframework.context.annotation.Lazy;
import com.pudonghot.yo.service.CommonCallDataService;
import com.pudonghot.yo.fsagent.api.CampaignDialService;
import com.pudonghot.yo.campaign.service.CampaignService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import com.pudonghot.yo.fsagent.api.request.ReqCampaignDial;
import com.pudonghot.yo.campaign.service.CallingListService;
import org.springframework.beans.factory.annotation.Autowired;
import com.wacai.tigon.service.support.BaseQueryServiceSupport;
import com.pudonghot.yo.campaign.feign.response.RespCallingList;
import static com.pudonghot.yo.model.domain.Campaign.TargetType.QUEUE;
import com.pudonghot.yo.campaign.feign.service.FeignCallingListService;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import com.pudonghot.yo.fsagent.api.request.ReqCampaignDial.CallingRecord;
/**
* @author Donghuang
@ -48,8 +47,8 @@ public class CampaignServiceImpl
private int dialBatchSleep;
@Autowired
private AgentStatusMapper agentStatusMapper;
@Autowired
private FeignCallingListService callingListService;
@Setter(onMethod_ = {@Lazy, @Autowired})
private CallingListService callingListService;
@Autowired
private CampaignDialService dialService;
@Autowired
@ -57,7 +56,7 @@ public class CampaignServiceImpl
@Scheduled(fixedRateString = "${yo.campaign.task-scheduler.fixed-rate:180000}", initialDelayString = "${yo.campaign.task-scheduler.init-delay:32000}")
public void taskScheduler() {
final int secondOfDay = TimeUtils.secondOfDay(new Date());
val secondOfDay = TimeUtils.secondOfDay(new Date());
scan(new Search(Campaign.ACTIVE, true)
.eq(Campaign.STATUS, Campaign.Status.RUNNING)
.lt(Campaign.DAILY_FROM, secondOfDay)
@ -73,19 +72,23 @@ public class CampaignServiceImpl
});
}
void dial(final Campaign campaign) {
final String campaignKey = campaign.getCampaignKey();
/**
* {@inheritDoc}
*/
@Override
public void dial(final Campaign campaign) {
val campaignKey = campaign.getCampaignKey();
LogMDC.setTraceId(campaignKey);
log.info("Campaign [{}] dial.", campaign);
final Integer queueId = campaign.getTargetId();
final int countOnlineAgentOfQueue =
val queueId = campaign.getTargetId();
val countOnlineAgentOfQueue =
agentStatusMapper.countOnlineOfQueue(queueId);
if (countOnlineAgentOfQueue == 0) {
log.warn("Campaign has no online agent, ignore.");
return;
}
final int countIdleAgentOfQueue =
val countIdleAgentOfQueue =
agentStatusMapper.countIdleOfQueue(queueId);
if (countIdleAgentOfQueue == 0) {
@ -93,9 +96,9 @@ public class CampaignServiceImpl
return;
}
final Integer campaignId = campaign.getId();
final int maxChannels = campaign.getNumOfChannels();
int campaignChannel =
val campaignId = campaign.getId();
val maxChannels = campaign.getNumOfChannels();
val campaignChannel =
commonCallDataService.getCampaignChannel(campaignId);
if (campaignChannel >= maxChannels) {
@ -103,7 +106,7 @@ public class CampaignServiceImpl
return;
}
final int optimizationValue = campaign.getOptimizationValue();
val optimizationValue = campaign.getOptimizationValue();
int fetchCount = 0;
if (countIdleAgentOfQueue > 4) {
if (optimizationValue > 0) {
@ -112,35 +115,24 @@ public class CampaignServiceImpl
}
}
final RespCallingList callingList =
callingListService.fetchCallingList(
fetchCount > 0 ? fetchCount : countIdleAgentOfQueue,
campaignKey,
campaign.getName());
final RespCallingList.CallingData[] data =
callingList.getData();
val callingLists = callingListService.listReady(
campaignId, fetchCount > 0 ? fetchCount : countIdleAgentOfQueue);
if (data == null || data.length == 0) {
log.error("Fetch calling list data error caused. code [{}], message [{}].",
callingList.getCode(), callingList.getError());
if (callingLists.isEmpty()) {
log.info("No ready calling list found, ignore.");
return;
}
if (log.isInfoEnabled()) {
log.info("CallingListData: [{}][{}][{}]",
campaignId, campaignKey, StringUtils.join(data, "|"));
}
final ReqCampaignDial req= new ReqCampaignDial();
val req = new ReqCampaignDial();
req.setCampaignId(campaignId);
final List<Pair<String, String>> dataList = Stream.of(data)
.map(RespCallingList.CallingData::toPair)
val dataList = callingLists.stream()
.map(it -> new CallingRecord(it.getId(), it.getCaseKey(), it.getPhone()))
.collect(Collectors.toList());
eachBatch(dataList, dialBatchSize, cl -> {
ListUtils.eachBatch(dataList, dialBatchSize, cl -> {
req.setCallingList(cl);
dialService.queueDial(req);
try {
log.debug("Sleep [{}] milliseconds.", dialBatchSize);
log.debug("Sleep [{}] milliseconds.", dialBatchSleep);
TimeUnit.MILLISECONDS.sleep(dialBatchSleep);
}
catch (InterruptedException e) {
@ -148,15 +140,4 @@ public class CampaignServiceImpl
}
});
}
<T> void eachBatch(
final List<T> list,
final int batch,
final Consumer<List<T>> consumer) {
final int size = list.size();
for (int i = 0; i < size; i += batch) {
consumer.accept(list.subList(i, Math.min(size, i + batch)));
}
}
}

View File

@ -29,6 +29,6 @@ dubbo.registry.file=${user.home}/dubbo-cache/${spring.application.name}/dubbo.ca
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
yo.campaign.feign.calling-list.base-url=http://localhost:8093/callinglist
yo.campaign.feign.calling-list.channel=dx-hzqw

View File

@ -23,8 +23,8 @@ public class FeignCallingListServiceTest {
@Test
public void testFetchCallingList() {
final val callingList =
callingListService.fetchCallingList(1, "11223", "44556");
val callingList =
callingListService.fetchCallingList(1, "5694", "5694");
log.info("Calling list [{}].", callingList);
}
}

View File

@ -0,0 +1,41 @@
package com.pudonghot.yo.campaign.service;
import lombok.val;
import org.junit.Test;
import org.junit.runner.RunWith;
import lombok.extern.slf4j.Slf4j;
import com.wacai.tigon.mybatis.Search;
import com.pudonghot.yo.campaign.YoCampaign;
import com.pudonghot.yo.model.domain.Campaign;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Donghuang
* @date Sep 13, 2020 22:58:41
*/
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = YoCampaign.class)
public class CallingListServiceTest {
@Autowired
private CampaignService campaignService;
@Autowired
private CallingListService callingListService;
@Test
public void testFetchRemote() {
val campaign = campaignService.find(
new Search(Campaign.CAMPAIGN_KEY, "5694"));
callingListService.fetchRemote(campaign);
}
@Test
public void testList() {
val campaign = campaignService.find(
new Search(Campaign.CAMPAIGN_KEY, "5694"));
val callingLists = callingListService.listReady(campaign.getId(), 2);
log.info("Calling list [{}]", callingLists);
}
}

View File

@ -0,0 +1,31 @@
package com.pudonghot.yo.campaign.service;
import lombok.val;
import org.junit.Test;
import org.junit.runner.RunWith;
import lombok.extern.slf4j.Slf4j;
import com.wacai.tigon.mybatis.Search;
import com.pudonghot.yo.campaign.YoCampaign;
import com.pudonghot.yo.model.domain.Campaign;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Donghuang
* @date Sep 13, 2020 23:48:58
*/
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = YoCampaign.class)
public class CampaignServiceTest {
@Autowired
private CampaignService campaignService;
@Test
public void testDial() {
val campaign = campaignService.find(
new Search(Campaign.CAMPAIGN_KEY, "donghuang"));
campaignService.dial(campaign);
}
}

17
fetch-event2.sh Executable file
View File

@ -0,0 +1,17 @@
#!/bin/bash
if [ -z "$1" ]; then
echo 'Usage: ./fetch-event.sh guid'
exit 1
fi
url="http://localhost:8093/openapi/resource/agentevent/donghuang?guid=$1"
echo "Listen URL: $url"
count=0
while : ; do
((++count))
curl $url
echo
echo "[$count] Event fetched."
done

View File

@ -41,14 +41,16 @@ public class DialController {
public List<String> campaignDial(
@RequestParam("campaignId")
final Integer campaignId,
@RequestParam("recId")
final Integer recId,
@RequestParam("caseKey")
final String caseKey,
@RequestParam("phone")
final String calledNumber) {
final val req = new ReqCampaignDial();
val req = new ReqCampaignDial();
req.setCampaignId(campaignId);
req.setCallingList(Arrays.asList(Pair.of(calledNumber, caseKey)));
req.setCallingList(Arrays.asList(new ReqCampaignDial.CallingRecord(recId, caseKey, calledNumber)));
return campaignDialService.queueDial(req);
}
}

View File

@ -1,8 +1,12 @@
package com.pudonghot.yo.fsagent.listener;
import java.util.Date;
import lombok.extern.slf4j.Slf4j;
import com.pudonghot.yo.util.LogMDC;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.pudonghot.yo.mapper.CallingListMapper;
import com.pudonghot.yo.model.domain.CallingList;
import com.pudonghot.yo.fsagent.util.EslEventUtils;
import com.pudonghot.yo.service.CommonCallDataService;
import org.freeswitch.esl.client.transport.event.Event;
@ -20,14 +24,15 @@ public class CampaignChannelCreate {
@Autowired
private CommonCallDataService commonCallDataService;
@Autowired
private CallingListMapper callingListMapper;
/**
* {@inheritDoc}
*/
@Async
@EventListener(value = Event.class,
condition = "#root.args[0].getName() == 'CHANNEL_DESTROY'" +
condition = "#root.args[0].getName() == 'CHANNEL_CREATE'" +
" and #root.args[0].getDialType() == 'CAMPAIGN'")
public void onChannelCreate(final Event event) {
final String callId = event.getCallId();
@ -39,8 +44,38 @@ public class CampaignChannelCreate {
if (campaignId != null) {
log.info("Increase campaign [{}] dial number.", campaignId);
commonCallDataService.incrCampaignChannel(campaignId);
commonCallDataService.incrCampaignDailyDialed(campaignId);
final String strRecId = event.getRecId();
if (StringUtils.isNotBlank(strRecId)) {
final Integer recId = Integer.parseInt(strRecId);
final String calledNumber = event.getCalledNumber();
log.info("Channel created update case [{}:{}] calling list.", strRecId, calledNumber);
final CallingList rec = callingListMapper.find(recId);
if (rec == null) {
log.warn("No case [{}:{}] calling list found, ignore.", strRecId, calledNumber);
return;
}
rec.setLastCallStartTime(getCallStartTime(event));
rec.setCalledTimes(rec.getCalledTimes() + 1);
rec.setStatus(CallingList.Status.DIALING);
rec.setLockKey(null);
rec.setLockTime(null);
rec.setUpdatedTime(new Date());
rec.setLastConnId(callId);
callingListMapper.update(rec);
}
}
}
}
private Date getCallStartTime(final Event event) {
final String startEpoch = event.getHeader("variable_start_epoch");
if (StringUtils.isNotBlank(startEpoch)) {
return new Date(Long.parseLong(startEpoch) * 1000);
}
return new Date();
}
}

View File

@ -3,7 +3,6 @@ package com.pudonghot.yo.fsagent.listener;
import java.util.Date;
import lombok.extern.slf4j.Slf4j;
import com.pudonghot.yo.util.LogMDC;
import com.wacai.tigon.mybatis.Search;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.pudonghot.yo.mapper.CallingListMapper;
@ -49,43 +48,29 @@ public class CampaignChannelDestroy {
callDataService.decrCampaignChannel(campaignId);
final String caseKey = event.getHeader("variable_x_case_key");
if (StringUtils.isNotBlank(caseKey)) {
log.info("Update case [{}] calling list call result.", caseKey);
final CallingList rec = callingListMapper.find(
new Search(CallingList.CASE_KEY, caseKey)
.desc(CallingList.ID)
.limit(1));
final String strRecId = event.getRecId();
if (StringUtils.isNotBlank(strRecId)) {
final Integer recId = Integer.parseInt(strRecId);
final String calledNumber = event.getCalledNumber();
log.info("Channel destroy update case [{}:{}] calling list call result.",
recId, calledNumber);
final CallingList rec = callingListMapper.find(recId);
if (rec == null) {
log.warn("No case [{}] calling list found, ignore.", caseKey);
log.warn("No case [{}:{}] calling list found, ignore.", strRecId, calledNumber);
return;
}
rec.setCalledTimes(rec.getCalledTimes() + 1);
rec.setLastConnId(event.getConnId());
rec.setLastCallStartTime(getCallStartTime(event));
final String strBillSec = event.getHeader("variable_billsec");
final Long billSec = StringUtils.isNotBlank(strBillSec) ?
Long.parseLong(strBillSec) : 0;
rec.setLastCallDuration(billSec);
rec.setLastConnected(billSec > 0);
rec.setLastHangupCause(event.getHangupCause());
rec.setLockKey(null);
rec.setLockTime(null);
rec.setStatus(CallingList.Status.CALLED);
rec.setUpdatedTime(new Date());
callingListMapper.update(rec);
}
}
}
private Date getCallStartTime(final Event event) {
final String startEpoch = event.getHeader("variable_start_epoch");
if (StringUtils.isNotBlank(startEpoch)) {
return new Date(Long.parseLong(startEpoch) * 1000);
}
return new Date();
}
}

View File

@ -94,11 +94,6 @@ public class ChannelCreate {
agent.getId(),
agent.getAccount(), null));
}
final Integer campaignId = event.getCampaignId();
if (campaignId != null) {
commonCallDataService.incrCampaignDailyEstablished(campaignId);
}
}
}
}

View File

@ -1,14 +1,13 @@
package com.pudonghot.yo.fsagent.service.dubbo.impl;
import lombok.val;
import java.util.*;
import com.pudonghot.yo.mapper.*;
import lombok.extern.slf4j.Slf4j;
import com.pudonghot.yo.util.LogMDC;
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.StringUtils;
import com.pudonghot.yo.fsagent.api.request.*;
import org.freeswitch.esl.client.inbound.Client;
@ -49,8 +48,8 @@ public class CampaignDialServiceImpl implements CampaignDialService {
*/
@Override
public List<String> queueDial(final ReqCampaignDial req) {
final Integer campaignId = req.getCampaignId();
final Campaign campaign = campaignMapper.find(campaignId);
val campaignId = req.getCampaignId();
val campaign = campaignMapper.find(campaignId);
Assert.state(campaign != null,
() -> "No campaign [" + campaignId + "] found");
Assert.state(campaign.getActive(),
@ -60,32 +59,33 @@ public class CampaignDialServiceImpl implements CampaignDialService {
Assert.state(campaign.getTargetType() == Campaign.TargetType.QUEUE,
() -> "Campaign [" + campaignId + "] target type is not QUEUE");
final Queue queue = queueMapper.find(campaign.getTargetId());
val 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());
val 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<Pair<String, String>> callingList = req.getCallingList();
final List<String> callUuidList = new ArrayList<>(callingList.size());
val callingList = req.getCallingList();
val callUuidList = new ArrayList<String>(callingList.size());
for (final Pair<String, String> callingRec : callingList) {
final String calledNumber = callingRec.getLeft();
final String caseKey = callingRec.getRight();
for (val callingRec : callingList) {
val recId = callingRec.getId();
val calledNumber = callingRec.getPhone();
val caseKey = callingRec.getCaseKey();
final Trunk trunk = trunkMapper.findOfCampaign(campaignId);
val trunk = trunkMapper.findOfCampaign(campaignId);
Assert.state(trunk != null,
() -> "No trunk of campaign [" + campaignId + "] found");
final String uuid = idSeq.get();
val uuid = idSeq.get();
final List<String> globalVars = Arrays.asList(
val globalVars = Arrays.<String>asList(
"x_conn_id=" + uuid,
"ignore_early_media=true",
"x_dial_type=CAMPAIGN",
@ -93,6 +93,7 @@ public class CampaignDialServiceImpl implements CampaignDialService {
"x_tenant_id=" + tenant.getId(),
"x_tenant_code=" + tenant.getCode(),
"x_campaign_id=" + campaignId,
"x_rec_id=" + recId,
"x_case_key=" + caseKey,
"x_called_number=" + calledNumber,
"x_trunk_id=" + trunk.getId(),
@ -111,7 +112,6 @@ public class CampaignDialServiceImpl implements CampaignDialService {
"origination_caller_id_number=" + trunk.getCpn()
);
// 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_logic_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, ","),
@ -119,9 +119,9 @@ public class CampaignDialServiceImpl implements CampaignDialService {
queue.getId(),
tenant.getRealm()));
log.info("CampaignCall [{}:{}].", caseKey, calledNumber);
log.info("CampaignCall [{}:{}:{}].", recId, caseKey, calledNumber);
final Map<String, Object> callData = new HashMap<>(4);
val callData = new HashMap<String, Object>(4);
callData.put("outid", caseKey);
callData.put("phone", calledNumber);
callData.put("taskid", campaign.getCampaignKey());

View File

@ -33,7 +33,7 @@ public class DialServiceTest {
@Test
public void testDial() {
final val req = new ReqAgentDial();
val req = new ReqAgentDial();
req.setTenantId(1);
req.setAccount("donghuang");
req.setCalledNumber("13764268709");
@ -43,7 +43,7 @@ public class DialServiceTest {
@Test
public void testDial2() {
final ReqDial req = new ReqDial();
val req = new ReqDial();
req.setTenantId(13);
req.setCallerLeg(CallLeg.queue("queue1"));
req.setCalledLeg(CallLeg.outside(3, "13764268709"));
@ -55,10 +55,12 @@ public class DialServiceTest {
@Test
public void testCampaignDial() {
final val req = new ReqCampaignDial();
val req = new ReqCampaignDial();
req.setCampaignId(4);
final String calledNumber = "13764268709";
req.setCallingList(Arrays.asList(Pair.of(calledNumber, calledNumber)));
req.setCallingList(
Arrays.asList(new ReqCampaignDial.CallingRecord(
1, calledNumber, calledNumber)));
campaignDialService.queueDial(req);
}
}

View File

@ -1,11 +1,8 @@
package com.pudonghot.yo.fsagent.api.request;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.*;
import java.util.List;
import java.io.Serializable;
import org.apache.commons.lang3.tuple.Pair;
/**
* @author Donghuang
@ -18,5 +15,18 @@ public class ReqCampaignDial implements Serializable {
private static final long serialVersionUID = 1L;
private Integer campaignId;
private List<Pair<String, String>> callingList;
private List<CallingRecord> callingList;
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public static class CallingRecord implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String caseKey;
private String phone;
}
}

View File

@ -119,8 +119,6 @@ public class DataWrapper implements Serializable {
return getVal("variable_x_called_number");
}
/**
* Convenience method.
* get dial type
@ -145,6 +143,26 @@ public class DataWrapper implements Serializable {
return null;
}
/**
* Convenience method.
* get calling list record id
*
* @return calling list record id
*/
public String getRecId() {
return getVal("variable_x_rec_id");
}
/**
* Convenience method.
* get case key
*
* @return case key
*/
public String getCaseKey() {
return getVal("variable_x_case_key");
}
/**
* Convenience method.
* get other number

View File

@ -33,25 +33,25 @@ public class TrunkMapperTest {
@Test
public void testListOfStrategy() {
final val trunk = mapper.listOfStrategy(Arrays.asList(1, 2, 3));
val trunk = mapper.listOfStrategy(Arrays.asList(1, 2, 3));
log.info("Trunk [{}].", trunk);
}
@Test
public void testFindOfStrategy() {
final val trunk = mapper.findOfStrategy(Arrays.asList(1, 2, 3));
val trunk = mapper.findOfStrategy(Arrays.asList(1, 2, 3));
log.info("Trunk [{}].", trunk);
}
@Test
public void testFindOfAgentGroup() {
final val trunk = mapper.findOfAgentGroup(5);
val trunk = mapper.findOfAgentGroup(5);
log.info("Trunk [{}].", trunk);
}
@Test
public void testFindOfCampaign() {
final val trunk = mapper.findOfCampaign(3);
val trunk = mapper.findOfCampaign(3);
log.info("Trunk [{}].", trunk);
}
}

View File

@ -4,9 +4,10 @@ import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import com.wacai.tigon.mybatis.Table;
import com.pudonghot.yo.model.DailyTime;
import com.wacai.tigon.mybatis.NotUpdate;
import org.apache.commons.lang3.tuple.Pair;
import lombok.experimental.FieldNameConstants;
import com.wacai.tigon.mybatis.UseGeneratedKeys;
/**
* @author Donghuang <br>
@ -14,7 +15,6 @@ import com.wacai.tigon.mybatis.UseGeneratedKeys;
*/
@Getter
@Setter
@UseGeneratedKeys
@Table("br_calling_list")
@FieldNameConstants(prefix = "")
public class CallingList extends BaseDomain {
@ -28,11 +28,11 @@ public class CallingList extends BaseDomain {
private String campaignKey;
@NotUpdate
private Date addedTime;
private DailyTime dailyFrom;
private DailyTime dailyTo;
@NotUpdate
private String phone;
private Status status;
private Integer dailyFrom;
private Integer dailyTo;
private Integer calledTimes;
private String lastConnId;
private Date lastCallStartTime;
@ -51,6 +51,6 @@ public class CallingList extends BaseDomain {
private Date lockTime;
public enum Status {
READY, ACHIEVED, CALLED, CANCELED
READY, ACHIEVED, DIALING, CALLED, CANCELED
}
}

View File

@ -24,7 +24,6 @@ public interface CommonCallDataService {
*/
String getIvrCallData(String agentId);
/**
* save call data
*
@ -47,7 +46,7 @@ public interface CommonCallDataService {
* @param campaignId campaign id
* @return count of campaign channel
*/
int getCampaignChannel(Integer campaignId);
Integer getCampaignChannel(Integer campaignId);
/**
* increase campaign channel
@ -55,7 +54,7 @@ public interface CommonCallDataService {
* @param campaignId campaign id
* @return count of campaign channel
*/
int incrCampaignChannel(Integer campaignId);
Integer incrCampaignChannel(Integer campaignId);
/**
* decrease campaign channel
@ -63,51 +62,12 @@ public interface CommonCallDataService {
* @param campaignId campaign id
* @return count of campaign channel
*/
int decrCampaignChannel(Integer campaignId);
Integer decrCampaignChannel(Integer campaignId);
/**
* get campaign daily dialed
*
* @param campaignId campaign id
* @return count of campaign daily dialed
*/
int getCampaignDailyDialed(Integer campaignId);
/**
* increase campaign daily dialed
*
* @param campaignId campaign id
* @return count of campaign daily dialed
*/
int incrCampaignDailyDialed(Integer campaignId);
/**
* reset campaign daily dialed
* reset campaign channel
*
* @param campaignId campaign id
*/
void resetCampaignDailyDialed(Integer campaignId);
/**
* get campaign daily established
*
* @param campaignId campaign id
* @return count of campaign daily established
*/
int getCampaignDailyEstablished(Integer campaignId);
/**
* increase campaign daily established
*
* @param campaignId campaign id
* @return count of campaign daily established
*/
int incrCampaignDailyEstablished(Integer campaignId);
/**
* reset campaign daily established
*
* @param campaignId campaign id
*/
void resetCampaignDailyEstablished(Integer campaignId);
Integer resetCampaignChannel(Integer campaignId);
}

View File

@ -24,7 +24,7 @@ public class CommonAgentEventQueueServiceImpl implements CommonAgentEventQueueSe
*/
@Override
public void publish(final AgentEvent event) {
log.debug("Publish agent event [{}].", event);
log.info("Publish agent event [{}].", event);
AGENT_EVENT_QUEUE.offer(event);
}

View File

@ -72,8 +72,8 @@ public class CommonCallDataServiceImpl implements CommonCallDataService {
* {@inheritDoc}
*/
@Override
public int getCampaignChannel(final Integer campaignId) {
final int val = CAMPAIGN_DATA.getOrDefault(channelKey(campaignId), 0);
public Integer getCampaignChannel(final Integer campaignId) {
final Integer val = CAMPAIGN_DATA.getOrDefault(channelKey(campaignId), 0);
log.debug("Get campaign [{}] channel [{}].", campaignId, val);
return val;
}
@ -82,8 +82,8 @@ public class CommonCallDataServiceImpl implements CommonCallDataService {
* {@inheritDoc}
*/
@Override
public int incrCampaignChannel(final Integer campaignId) {
final int val = CAMPAIGN_DATA.addAndGet(channelKey(campaignId), 1);
public Integer incrCampaignChannel(final Integer campaignId) {
final Integer val = CAMPAIGN_DATA.addAndGet(channelKey(campaignId), 1);
log.info("Increase campaign [{}] channel [{}].", campaignId, val);
return val;
}
@ -92,8 +92,8 @@ public class CommonCallDataServiceImpl implements CommonCallDataService {
* {@inheritDoc}
*/
@Override
public int decrCampaignChannel(final Integer campaignId) {
final int val = CAMPAIGN_DATA.addAndGet(channelKey(campaignId), -1);
public Integer decrCampaignChannel(final Integer campaignId) {
final Integer val = CAMPAIGN_DATA.addAndGet(channelKey(campaignId), -1);
log.info("Decrease campaign [{}] channel [{}].", campaignId, val);
return val;
}
@ -102,60 +102,12 @@ public class CommonCallDataServiceImpl implements CommonCallDataService {
* {@inheritDoc}
*/
@Override
public int getCampaignDailyDialed(final Integer campaignId) {
final int val = CAMPAIGN_DATA.getOrDefault(dialKey(campaignId), 0);
log.debug("Get campaign [{}] daily dialed [{}].", campaignId, val);
public Integer resetCampaignChannel(final Integer campaignId) {
final Integer val = CAMPAIGN_DATA.remove(channelKey(campaignId));
log.info("Reset campaign [{}] channel [{}].", campaignId, val);
return val;
}
/**
* {@inheritDoc}
*/
@Override
public int incrCampaignDailyDialed(final Integer campaignId) {
final int val = CAMPAIGN_DATA.addAndGet(dialKey(campaignId), 1);
log.debug("Increase campaign [{}] daily dialed [{}].", campaignId, val);
return val;
}
/**
* {@inheritDoc}
*/
@Override
public void resetCampaignDailyDialed(final Integer campaignId) {
final Integer val = CAMPAIGN_DATA.remove(dialKey(campaignId));
log.debug("Reset campaign [{}] daily dialed [{}].", campaignId, val);
}
/**
* {@inheritDoc}
*/
@Override
public int getCampaignDailyEstablished(final Integer campaignId) {
final int val = CAMPAIGN_DATA.getOrDefault(establishedKey(campaignId), 0);
log.debug("Get campaign [{}] daily established [{}].", campaignId, val);
return val;
}
/**
* {@inheritDoc}
*/
@Override
public int incrCampaignDailyEstablished(final Integer campaignId) {
final int val = CAMPAIGN_DATA.addAndGet(establishedKey(campaignId), 1);
log.debug("Increase campaign [{}] daily established [{}].", campaignId, val);
return val;
}
/**
* {@inheritDoc}
*/
@Override
public void resetCampaignDailyEstablished(final Integer campaignId) {
final Integer val = CAMPAIGN_DATA.remove(establishedKey(campaignId));
log.debug("Reset campaign [{}] daily established [{}].", campaignId, val);
}
private void put(final String key, final String value) {
log.debug("Call data cache [{}] -> [{}].", key, value);
CACHE.put(key, value, callDataTimeoutSeconds, TimeUnit.SECONDS);
@ -168,12 +120,4 @@ public class CommonCallDataServiceImpl implements CommonCallDataService {
private String channelKey(final Integer campaignId) {
return "CC:" + campaignId;
}
private String dialKey(final Integer campaignId) {
return "CD:" + campaignId;
}
private String establishedKey(final Integer campaignId) {
return "CE:" + campaignId;
}
}

View File

@ -100,7 +100,7 @@ public class CommonCampaignServiceImpl
log.info("Update campaign [{}] status [{}].", campaign, status);
campaign.setStatus(status);
mapper.update(campaign);
resetData(campaign);
commonCallDataService.resetCampaignChannel(campaign.getId());
}
private Campaign findValid(final String campaignKey) {
@ -112,10 +112,4 @@ public class CommonCampaignServiceImpl
() -> "Campaign [" + campaignKey + "] is not active");
return campaign;
}
private void resetData(final Campaign campaign) {
final Integer campaignId = campaign.getId();
commonCallDataService.resetCampaignDailyDialed(campaignId);
commonCallDataService.resetCampaignDailyEstablished(campaignId);
}
}

View File

@ -0,0 +1,30 @@
package com.pudonghot.yo.util;
import java.util.List;
import java.util.function.Consumer;
/**
* @author Donghuang
* @date Sep 13, 2020 14:11:09
*/
public class ListUtils {
/**
* list each batch
*
* @param list list
* @param batch batch size
* @param consumer consumer
* @param <T> element type
*/
public static <T> void eachBatch(
final List<T> list,
final int batch,
final Consumer<List<T>> consumer) {
final int size = list.size();
for (int i = 0; i < size; i += batch) {
consumer.accept(list.subList(i, Math.min(size, i + batch)));
}
}
}

View File

@ -1,17 +1,16 @@
package com.pudonghot.yo.campaign.service.impl;
package com.pudonghot.yo.util;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.ArrayList;
import lombok.extern.slf4j.Slf4j;
/**
* @author Donghuang
* @date Sep 13, 2020 10:25:44
*/
@Slf4j
public class CampaignServiceImplTest {
public class ListUtilsTest {
@Test
public void testEachBatch() {
@ -19,6 +18,6 @@ public class CampaignServiceImplTest {
for (int i = 0; i < 100; i++) {
list.add(i);
}
new CampaignServiceImpl(null).eachBatch(list, 8, sub -> log.info("{}", sub));
ListUtils.eachBatch(list, 8, sub -> log.info("{}", sub));
}
}

View File

@ -0,0 +1,21 @@
package com.pudonghot.yo.util;
import org.junit.Test;
import java.util.Date;
import java.util.Calendar;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
/**
* @author Donghuang
* @date Sep 13, 2020 23:12:06
*/
@Slf4j
public class TimeUtilsTest {
@Test
public void testEachBatch() {
log.info("val: {}", TimeUtils.secondOfDay(
DateUtils.addSeconds(DateUtils.truncate(DateUtils.addDays(new Date(), 1), Calendar.DATE), -1)));
}
}