complete campaign to agent

This commit is contained in:
东皇大叔 2020-07-12 15:45:27 +08:00
parent c6c15c3c7e
commit bab4c70c96
47 changed files with 721 additions and 240 deletions

View File

@ -3,8 +3,10 @@ package com.pudonghot.yo.fsagent.controller;
import lombok.extern.slf4j.Slf4j;
import com.wacai.tigon.mybatis.Search;
import com.pudonghot.yo.model.domain.Tenant;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.DigestUtils;
import com.pudonghot.yo.fsagent.service.LocalApiService;
import com.pudonghot.yo.fsagent.service.RecordingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
@ -15,8 +17,10 @@ import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
@Slf4j
@RequestMapping(value = "/xml/dialplan")
public class BaseDialplanController extends BaseXmlController {
@Value("${yo.fsagent.recording.file-ext:.ogg}")
protected String recordingFileExt;
@Autowired
private LocalApiService localApiService;
@Autowired
private RecordingService recordingService;
protected FreeMarkerView view(final String domain, final String viewFile) {
final Tenant tenant = tenantService.find(
@ -28,17 +32,18 @@ public class BaseDialplanController extends BaseXmlController {
return empty();
}
protected String recordingFile(
protected FreeMarkerView recFile(
final FreeMarkerView view,
final String connId,
final String thisDn,
final String otherDn) {
final String callerNumber,
final String calledNumber) {
return "$${recordings_dir}/" +
connId + "_" +
DateFormatUtils.format(System.currentTimeMillis(),
"yyyy-MM-dd_HH-mm-ss") + "_" +
thisDn + "-" +
otherDn + recordingFileExt;
attr(view, "postRecUrl", localApiService.getPostRecUrl());
return attr(view, "recLoc", recordingService.recLoc(connId, callerNumber, calledNumber));
}
protected String md5(final String val) {
return DigestUtils.md5DigestAsHex(val.getBytes());
}
/**

View File

@ -8,12 +8,9 @@ import com.pudonghot.yo.model.domain.Agent;
import com.pudonghot.yo.model.domain.Trunk;
import com.wacai.tigon.sequence.IdSequence;
import com.pudonghot.yo.model.domain.Tenant;
import org.springframework.util.DigestUtils;
import com.pudonghot.yo.model.domain.AgentGroup;
import org.springframework.stereotype.Controller;
import com.pudonghot.yo.cellphone.privacy.PrivacyLevel;
import org.springframework.web.bind.annotation.RequestParam;
import com.pudonghot.yo.cellphone.privacy.NumberPrivacyUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
@ -35,7 +32,7 @@ public class DialplanInboundController extends BaseDialplanController {
@Autowired
private AgentGroupService agentGroupService;
@Autowired
private LocalApiService localApiService;
private NumberPrivacyService numberPrivacyService;
@RequestMapping(params = "Caller-Context=public")
public FreeMarkerView inbound(
@ -119,26 +116,9 @@ public class DialplanInboundController extends BaseDialplanController {
final String connId = idSeq.get();
attr(view, "connId", connId);
attr(view, "recordingLoc",
recordingFile(connId,
DigestUtils.md5DigestAsHex(ani.getBytes()), dnis + "-" + calledAgent.getAgent()));
attr(view, "postRecUrl", localApiService.getPostRecUrl());
recFile(view, connId, md5(ani), dnis + "-" + calledAgent.getAgent());
final AgentGroup agentGroup = agentGroupService.find(
calledAgent.getGroupId());
final AgentGroup.PrivacyLevel privacyLevel = agentGroup != null ?
agentGroup.getPrivacyLevel() : AgentGroup.PrivacyLevel.NONE;
if (privacyLevel != AgentGroup.PrivacyLevel.NONE) {
attr(view, "callerIdNumber", "PRIVACY_INBOUND");
attr(view, "callerIdName", NumberPrivacyUtils.mask(ani,
PrivacyLevel.valueOf(privacyLevel.name())));
}
else {
attr(view, "callerIdNumber", ani);
attr(view, "callerIdName", ani);
}
attr(view, "privacyNumber", numberPrivacyService.generate(calledAgent, ani));
return view;
}

View File

@ -7,8 +7,8 @@ import com.pudonghot.yo.model.domain.Queue;
import com.pudonghot.yo.model.domain.Trunk;
import com.pudonghot.yo.model.domain.Agent;
import com.wacai.tigon.sequence.IdSequence;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.util.DigestUtils;
import com.pudonghot.yo.model.domain.AgentGroup;
import org.springframework.stereotype.Controller;
import com.pudonghot.yo.fsagent.util.CallStrUtils;
@ -32,16 +32,17 @@ public class DialplanInternalController extends BaseDialplanController {
@Autowired
private QueueService queueService;
@Autowired
private LocalApiService localApiService;
@Autowired
private IdSequence idSeq;
@Autowired
private TrunkService trunkService;
@Autowired
private GatewayService gatewayService;
@Autowired
private NumberPrivacyService numberPrivacyService;
@RequestMapping(params = {
"variable_origination_caller_id_number",
"Channel-Name",
"Caller-Context",
"Caller-Callee-ID-Number",
"Caller-Destination-Number"
@ -50,16 +51,46 @@ public class DialplanInternalController extends BaseDialplanController {
@RequestParam("Caller-Context")
final String domain,
@RequestParam("Caller-Callee-ID-Number")
final String user,
final String callerNumber,
@RequestParam("Channel-Name")
final String channel,
@RequestParam("Caller-Destination-Number")
final String calledNumber,
@RequestParam
final Map<String, String> params) {
log.info("XML originate dialplan of domain [{}] [{}] -> [{}].", domain, user, calledNumber);
log.info("XML originate dialplan of domain [{}] [{}] -> [{}].", domain, callerNumber, calledNumber);
log.debug("XML dialplan params [{}].", params);
return genDialplan(domain, user, calledNumber);
final CallStrUtils.ChannelInfo channelInfo =
CallStrUtils.parseChannelName(channel);
// campaign to agent
if (!channelInfo.isAgent()) {
final Pair<Agent, Boolean> targetAgent = findTargetAgent(calledNumber);
// All is OK
if (targetAgent.getRight()) {
final Agent calledAgent = targetAgent.getLeft();
if (calledAgent != null) {
final FreeMarkerView view = view(domain, "campaign-to-agent.xml");
final String connId = idSeq.get();
attr(view, "connId", connId);
attr(view, "calledAgent", calledAgent);
final String otherDn = StringUtils.defaultIfBlank(
params.get("variable_x_called_number"), callerNumber);
attr(view, "privacyNumber",
numberPrivacyService.generate(calledAgent, otherDn));
recFile(view, connId, calledAgent.getAccount(), md5(otherDn));
return view;
}
}
else {
return empty();
}
}
return genCallerAgentDialplan(domain, callerNumber, calledNumber);
}
@RequestMapping(params = {
@ -71,85 +102,45 @@ public class DialplanInternalController extends BaseDialplanController {
@RequestParam("Caller-Context")
final String domain,
@RequestParam("Caller-Username")
final String user,
final String callerUser,
@RequestParam("Caller-Destination-Number")
final String calledNumber,
@RequestParam
final Map<String, String> params) {
log.info("XML dialplan of domain [{}] [{}] -> [{}].", domain, user, calledNumber);
log.info("XML dialplan of domain [{}] [{}] -> [{}].", domain, callerUser, calledNumber);
log.debug("XML dialplan params [{}].", params);
return genDialplan(domain, user, calledNumber);
return genCallerAgentDialplan(domain, callerUser, calledNumber);
}
public FreeMarkerView genDialplan(
public FreeMarkerView genCallerAgentDialplan(
final String domain,
final String user,
final String callerUser,
final String calledNumber) {
final Agent callerAgent =
agentService.findByDomainAndAgent(domain, user);
agentService.findByDomainAndAgent(domain, callerUser);
if (callerAgent == null) {
log.warn("Not caller agent [{}@{}] found.", user, domain);
log.warn("Not caller agent [{}@{}] found.", callerUser, domain);
return empty();
}
if (!callerAgent.getActive()) {
log.warn("Caller agent [{}@{}] is not active.", user, domain);
log.warn("Caller agent [{}@{}] is not active.", callerUser, domain);
return empty();
}
final DialTarget dialTarget =
CallStrUtils.parseDialTarget(calledNumber);
if (dialTarget != null) {
final Integer dialTargetId = dialTarget.getId();
final DialTarget.Type dialTargetType = dialTarget.getType();
if (dialTargetType == DialTarget.Type.AGENT) {
final Agent calledAgent = agentService.find(dialTargetId);
if (calledAgent != null) {
return agentCallAgent(domain, callerAgent, calledAgent);
}
log.warn("No agent found of dial target [{}].", calledNumber);
return empty();
final Pair<Agent, Boolean> targetAgent = findTargetAgent(calledNumber);
// All is OK
if (targetAgent.getRight()) {
final Agent calledAgent = targetAgent.getLeft();
if (calledAgent != null) {
return agentCallAgent(domain, callerAgent, calledAgent);
}
else if (dialTargetType == DialTarget.Type.AGENT_GROUP) {
final AgentGroup agentGroup = agentGroupService.find(dialTargetId);
if (agentGroup != null) {
final Agent calledAgent =
agentService.lockIdleOfGroup(dialTargetId);
if (calledAgent != null) {
return agentCallAgent(domain, callerAgent, calledAgent);
}
log.warn("No idle agent found of group [{}].", calledNumber);
return empty();
}
log.warn("No agent group found of dial target [{}].", calledNumber);
return empty();
}
else if (dialTargetType == DialTarget.Type.QUEUE) {
final Queue queue = queueService.find(dialTargetId);
if (queue != null) {
final Agent calledAgent =
agentService.lockIdleOfQueue(dialTargetId);
if (calledAgent != null) {
return agentCallAgent(domain, callerAgent, calledAgent);
}
log.warn("No idle agent found of queue [{}].", calledNumber);
return empty();
}
log.warn("No queue found of dial target [{}].", calledNumber);
return empty();
}
log.error("Never here!!!");
}
else {
return empty();
}
final Pair<Trunk, String> trunkInfo = trunkService.parseDialTarget(
@ -157,7 +148,6 @@ public class DialplanInternalController extends BaseDialplanController {
if (trunkInfo != null) {
log.info("Trunk outbound call found.");
final Trunk trunk = trunkInfo.getLeft();
final String targetNumber = trunkInfo.getRight();
final FreeMarkerView view = view(domain, "trunk-outbound.xml");
attr(view, "trunk", trunk);
@ -166,12 +156,11 @@ public class DialplanInternalController extends BaseDialplanController {
attr(view, "callerAgent", callerAgent);
attr(view, "gatewayName",
gatewayService.genGatewayName(trunk.getGatewayId()));
attr(view, "calledNumber", calledNumber);
// number without trunk prefix
final String targetNumber = trunkInfo.getRight();
attr(view, "targetNumber", targetNumber);
attr(view, "recordingLoc", recordingFile(connId,
callerAgent.getAccount(),
DigestUtils.md5DigestAsHex(targetNumber.getBytes())));
attr(view, "postRecUrl", localApiService.getPostRecUrl());
recFile(view, connId, callerAgent.getAccount(), md5(targetNumber));
return view;
}
@ -187,16 +176,75 @@ public class DialplanInternalController extends BaseDialplanController {
return empty();
}
private FreeMarkerView agentCallAgent(final String domain, Agent callerAgent, Agent calledAgent) {
final FreeMarkerView view = view(domain, "local-extension.xml");
private FreeMarkerView agentCallAgent(final String domain, final Agent callerAgent, final Agent calledAgent) {
final FreeMarkerView view = view(domain, "agent-to-agent.xml");
final String connId = idSeq.get();
attr(view, "connId", connId);
attr(view, "callerAgent", callerAgent);
attr(view, "calledAgent", calledAgent);
attr(view, "recordingLoc", recordingFile(connId,
callerAgent.getAccount(),
calledAgent.getAccount()));
attr(view, "postRecUrl", localApiService.getPostRecUrl());
recFile(view, connId, callerAgent.getAccount(), calledAgent.getAccount());
return view;
}
/**
* find target agent
* @param calledNumber called number
* @return <Agent found, All is OK>
*/
private Pair<Agent, Boolean> findTargetAgent(final String calledNumber) {
final DialTarget dialTarget =
CallStrUtils.parseDialTarget(calledNumber);
if (dialTarget != null) {
final Integer dialTargetId = dialTarget.getId();
final DialTarget.Type dialTargetType = dialTarget.getType();
if (dialTargetType == DialTarget.Type.AGENT) {
final Agent calledAgent = agentService.find(dialTargetId);
if (calledAgent != null) {
return Pair.of(calledAgent, true);
}
log.warn("No agent found of dial target [{}].", calledNumber);
return Pair.of(null, false);
}
else if (dialTargetType == DialTarget.Type.AGENT_GROUP) {
final AgentGroup agentGroup = agentGroupService.find(dialTargetId);
if (agentGroup != null) {
final Agent calledAgent =
agentService.lockIdleOfGroup(dialTargetId);
if (calledAgent != null) {
return Pair.of(calledAgent, true);
}
log.warn("No idle agent found of group [{}].", calledNumber);
return Pair.of(null, false);
}
log.warn("No agent group found of dial target [{}].", calledNumber);
return Pair.of(null, false);
}
else if (dialTargetType == DialTarget.Type.QUEUE) {
final Queue queue = queueService.find(dialTargetId);
if (queue != null) {
final Agent calledAgent =
agentService.lockIdleOfQueue(dialTargetId);
if (calledAgent != null) {
return Pair.of(calledAgent, true);
}
log.warn("No idle agent found of queue [{}].", calledNumber);
return Pair.of(null, false);
}
log.warn("No queue found of dial target [{}].", calledNumber);
return Pair.of(null, false);
}
log.error("Never here!!!");
}
return Pair.of(null, true);
}
}

View File

@ -1,6 +1,6 @@
package com.pudonghot.yo.fsagent.controller;
import com.pudonghot.yo.fsagent.service.PostRecordingService;
import com.pudonghot.yo.fsagent.service.RecordingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
@ -15,7 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
@Controller
public class PostRecordingController {
@Autowired
private PostRecordingService postRecordingService;
private RecordingService recordingService;
@PostMapping("/post-rec")
public void postRecording(
@ -23,6 +23,6 @@ public class PostRecordingController {
final Integer tenantId,
@RequestParam("loc")
final String location) {
postRecordingService.postRecording(tenantId, location);
recordingService.postRecording(tenantId, location);
}
}

View File

@ -48,10 +48,10 @@ public class ChannelDestroy {
public void onChannelDestroy(final Event event) {
log.debug("On channel destroy event [{}] [{}].", event, event.getHeaders());
final String presenceId = event.getHeader("variable_presence_id");
final String callUuid = event.getCallUuid();
log.info("On channel [{}][{}] destroy event.", presenceId, callUuid);
final ChannelInfo channelInfo = CallStrUtils.getChannelInfo(event);
final String channelName = event.getChannelName();
log.info("On channel [{}][{}] destroy event.", channelName, callUuid);
final ChannelInfo channelInfo = CallStrUtils.parseChannelName(channelName);
if (channelInfo.isAgent()) {
@ -60,7 +60,8 @@ public class ChannelDestroy {
channelInfo.getNumber());
boolean acw = false;
final String strDialType = event.getHeader("x_dial_type");
final String strDialType = event.getHeader("variable_x_dial_type");
if (StringUtils.isNotBlank(strDialType)) {
final DialType dialType = DialType.valueOf(strDialType);

View File

@ -0,0 +1,19 @@
package com.pudonghot.yo.fsagent.service;
import com.pudonghot.yo.model.domain.Agent;
import com.pudonghot.yo.basic.model.PrivacyNumber;
/**
* @author Donghuang
* @date Jul 12, 2020 12:24:54
*/
public interface NumberPrivacyService {
/**
* generate privacy number
* @param agent agent
* @param number raw number
* @return number privacy
*/
PrivacyNumber generate(Agent agent, String number);
}

View File

@ -1,16 +0,0 @@
package com.pudonghot.yo.fsagent.service;
/**
* @author Donghuang <br>
* Jan 06, 2020 18:09:36
*/
public interface PostRecordingService {
/**
* post recording
*
* @param tenantId tenant id
* @param location recording location
*/
void postRecording(Integer tenantId, String location);
}

View File

@ -0,0 +1,26 @@
package com.pudonghot.yo.fsagent.service;
/**
* @author Donghuang <br>
* Jan 06, 2020 18:09:36
*/
public interface RecordingService {
/**
* post recording
*
* @param tenantId tenant id
* @param location recording location
*/
void postRecording(Integer tenantId, String location);
/**
* generate recording location
*
* @param connId conn id
* @param callerNumber caller number
* @param calledNumber called number
* @return recording location
*/
String recLoc(String connId, String callerNumber, String calledNumber);
}

View File

@ -12,8 +12,8 @@ import org.apache.commons.lang3.StringUtils;
import com.pudonghot.yo.mapper.AgentGroupMapper;
import com.pudonghot.yo.model.domain.AgentGroup;
import org.freeswitch.esl.client.inbound.Client;
import org.apache.commons.lang3.time.DateFormatUtils;
import com.pudonghot.yo.model.domain.CallDetailRecord;
import com.pudonghot.yo.fsagent.service.RecordingService;
import com.pudonghot.yo.fsagent.service.LocalApiService;
import static org.apache.commons.lang3.StringUtils.join;
import org.springframework.beans.factory.annotation.Value;
@ -33,14 +33,12 @@ abstract class AbstractDialer<LegB> {
private IdSequence idSeq;
@Autowired
private AgentGroupMapper agentGroupMapper;
@Value("${yo.fsagent.recording.dir:/var/lib/freeswitch/recordings}")
private String recDir;
@Value("${yo.fsagent.recording.file-ext:.ogg}")
private String recordingFileExt;
@Value("${yo.fsagent.default-codec:PCMA}")
private String defaultCodec;
@Autowired
private LocalApiService localApiService;
@Autowired
private RecordingService recordingService;
/**
* dial
@ -93,8 +91,6 @@ abstract class AbstractDialer<LegB> {
* @return dial string
*/
protected String dialStrLegA(final DialArg<LegB> arg) {
// TODO sofia/internal/
// sofia/internal/700001@d1.wacai.info
return "user/" + arg.getAgentLegA().getAgent() + "@" + arg.getTenant().getRealm();
}
@ -281,11 +277,6 @@ abstract class AbstractDialer<LegB> {
}
private String recLoc(final DialArg<LegB> arg) {
return recDir + "/" + arg.getConnId() + "_" +
DateFormatUtils.format(
System.currentTimeMillis(), "yyyy-MM-dd_HH-mm-ss") + "_" +
arg.getAgentLegA().getAccount() + "-" +
recNumLegB(arg) +
recordingFileExt;
return recordingService.recLoc(arg.getConnId(), arg.getAgentLegA().getAccount(), recNumLegB(arg));
}
}

View File

@ -135,7 +135,6 @@ public class DialServiceImpl implements DialService {
Assert.state(otherAgent.getActive(),
() -> "Other agent [" + otherAgentAccount + "] is not active");
req.setCalledNumber(otherAgent.getAgent());
req.setCalledName(otherAgentAccount);
return dialInternal(idSeq.get(), req, tenant, agent, otherAgent);
}

View File

@ -1,11 +1,10 @@
package com.pudonghot.yo.fsagent.service.dubbo.impl;
import java.util.List;
import com.pudonghot.yo.model.domain.Agent;
import com.pudonghot.yo.model.domain.CallDetailRecord;
import lombok.extern.slf4j.Slf4j;
import com.pudonghot.yo.model.domain.Agent;
import org.springframework.stereotype.Service;
import com.pudonghot.yo.model.domain.CallDetailRecord;
/**
* @author Donghuang <br>
@ -30,9 +29,10 @@ public class DialerAgent extends AbstractDialer<Agent> {
protected void extVarsLegA(final DialArg<Agent> arg, final List<String> vars) {
final Agent legB = arg.getLegB();
final String calledNumber = legB.getAgent();
vars.add("sip_contact_user=" + calledNumber);
vars.add("origination_caller_id_number=" + calledNumber);
vars.add("origination_caller_id_name=" + legB.getAccount());
vars.add("sip_contact_user=" + calledNumber);
vars.add(callTypeVar(CallDetailRecord.CallType.INTERNAL));
vars.add(headerCallTypeVar(CallDetailRecord.CallType.INTERNAL));

View File

@ -2,17 +2,15 @@ package com.pudonghot.yo.fsagent.service.dubbo.impl;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import com.pudonghot.yo.model.domain.Agent;
import com.pudonghot.yo.model.domain.Trunk;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.DigestUtils;
import org.springframework.stereotype.Service;
import com.pudonghot.yo.basic.model.PrivacyNumber;
import com.pudonghot.yo.fsagent.service.TrunkService;
import com.pudonghot.yo.model.domain.CallDetailRecord;
import com.pudonghot.yo.cellphone.privacy.PrivacyLevel;
import com.pudonghot.yo.fsagent.api.request.BaseReqDial;
import com.pudonghot.yo.cellphone.privacy.NumberPrivacyUtils;
import com.pudonghot.yo.fsagent.service.NumberPrivacyService;
import org.springframework.beans.factory.annotation.Autowired;
/**
@ -24,6 +22,8 @@ import org.springframework.beans.factory.annotation.Autowired;
public class DialerOutside extends AbstractDialer<Pair<Integer, String>> {
@Autowired
private TrunkService trunkService;
@Autowired
private NumberPrivacyService numberPrivacyService;
/**
* {@inheritDoc}
@ -36,11 +36,13 @@ public class DialerOutside extends AbstractDialer<Pair<Integer, String>> {
vars.add(callTypeVar(CallDetailRecord.CallType.OUTBOUND));
vars.add(headerCallTypeVar(CallDetailRecord.CallType.OUTBOUND));
vars.add(headerCallDirectionVar(CallDetailRecord.CallDirection.OUTGOING));
final PrivacyNumber privacyNumber =
numberPrivacyService.generate(arg.getAgentLegA(), arg.getLegB().getRight());
// hide customer info
vars.add("sip_contact_user=SYSTEM");
vars.add("origination_caller_id_number=SYSTEM");
vars.add("origination_caller_id_name=" +
calledName(arg.getReq(), arg.getAgentLegA(), arg.getLegB().getRight()));
vars.add("origination_caller_id_number=" + privacyNumber.getNumber());
vars.add("origination_caller_id_name=" + privacyNumber.getName());
vars.add("sip_contact_user=" + privacyNumber.getNumber());
}
/**
@ -78,13 +80,4 @@ public class DialerOutside extends AbstractDialer<Pair<Integer, String>> {
protected String dialStrLegB(DialArg<Pair<Integer, String>> arg) {
return ((Pair<Trunk, String>) arg.getData()).getRight();
}
String calledName(final BaseReqDial req, final Agent agent, final String calledNumber) {
final String calledName = req.getCalledName();
if (StringUtils.isNotBlank(calledName)) {
return calledName;
}
return NumberPrivacyUtils.mask(calledNumber,
PrivacyLevel.valueOf(findValidAgentGroup(agent).getPrivacyLevel().name()));
}
}

View File

@ -0,0 +1,46 @@
package com.pudonghot.yo.fsagent.service.impl;
import lombok.extern.slf4j.Slf4j;
import com.pudonghot.yo.model.domain.Agent;
import org.springframework.stereotype.Service;
import com.pudonghot.yo.mapper.AgentGroupMapper;
import com.pudonghot.yo.model.domain.AgentGroup;
import com.pudonghot.yo.basic.model.PrivacyLevel;
import com.pudonghot.yo.basic.model.PrivacyNumber;
import com.pudonghot.yo.cellphone.privacy.NumberPrivacyUtils;
import com.pudonghot.yo.fsagent.service.NumberPrivacyService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author Donghuang
* @date Jul 12, 2020 12:28:01
*/
@Slf4j
@Service
public class NumberPrivacyServiceImpl implements NumberPrivacyService {
@Autowired
private AgentGroupMapper agentGroupMapper;
/**
* {@inheritDoc}
*/
@Override
public PrivacyNumber generate(final Agent agent, final String number) {
final AgentGroup agentGroup =
agentGroupMapper.find(agent.getGroupId());
if (agentGroup != null && agentGroup.getActive()) {
final PrivacyLevel privacyLevel = agentGroup.getPrivacyLevel();
if (privacyLevel != PrivacyLevel.NONE) {
return new PrivacyNumber(PrivacyNumber.PRIVACY,
NumberPrivacyUtils.mask(number, privacyLevel));
}
return new PrivacyNumber(number, number);
}
// default medium
log.warn("No valid agent [{}] group found, privacy number medium.", agent);
return new PrivacyNumber(PrivacyNumber.PRIVACY,
NumberPrivacyUtils.mask(number, PrivacyLevel.MEDIUM));
}
}

View File

@ -3,22 +3,22 @@ package com.pudonghot.yo.fsagent.service.impl;
import java.io.File;
import java.util.Map;
import java.util.HashMap;
import com.pudonghot.yo.fsagent.service.PostRecordingService;
import com.pudonghot.yo.mapper.CallRecordingMapper;
import com.pudonghot.yo.mapper.TenantMapper;
import com.pudonghot.yo.model.domain.CallRecording;
import com.pudonghot.yo.model.domain.Tenant;
import com.pudonghot.yo.common.httpclient.HttpClient;
import com.pudonghot.yo.common.httpclient.HttpRequestResult;
import com.pudonghot.yo.common.httpclient.RequestBodyParams;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import com.pudonghot.yo.mapper.TenantMapper;
import com.pudonghot.yo.model.domain.Tenant;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.pudonghot.yo.mapper.CallRecordingMapper;
import com.pudonghot.yo.model.domain.CallRecording;
import com.pudonghot.yo.common.httpclient.HttpClient;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.scheduling.annotation.Async;
import com.pudonghot.yo.fsagent.service.RecordingService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import com.pudonghot.yo.common.httpclient.HttpRequestResult;
import com.pudonghot.yo.common.httpclient.RequestBodyParams;
import org.springframework.beans.factory.annotation.Autowired;
/**
@ -27,8 +27,8 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
@Slf4j
@Service
public class PostRecordingServiceImpl
implements PostRecordingService, InitializingBean {
public class RecordingServiceImpl
implements RecordingService, InitializingBean {
@Autowired
private HttpClient httpClient;
@Value("${yo.fsagent.recording-server.base-path}")
@ -40,6 +40,10 @@ public class PostRecordingServiceImpl
private TenantMapper tenantMapper;
@Autowired
private CallRecordingMapper callRecordingMapper;
@Value("${yo.fsagent.recording.dir:/var/lib/freeswitch/recordings}")
private String recDir;
@Value("${yo.fsagent.recording.file-ext:.ogg}")
private String recordingFileExt;
/**
* {@inheritDoc}
@ -86,6 +90,19 @@ public class PostRecordingServiceImpl
}
}
/**
* {@inheritDoc}
*/
@Override
public String recLoc(final String connId, final String callerNumber, final String calledNumber) {
return recDir + "/" + connId + "_" +
DateFormatUtils.format(
System.currentTimeMillis(), "yyyy-MM-dd_HH-mm-ss") + "_" +
callerNumber + "-" +
calledNumber +
recordingFileExt;
}
/**
* {@inheritDoc}
*/

View File

@ -6,8 +6,9 @@ spring.jackson.serialization.write-dates-as-timestamps=true
spring.jackson.serialization.fail-on-empty-beans=false
site.context-path=
# spring.freemarker.template-loader-path=classpath:/templates
# spring.freemarker.suffix=.ftl
spring.freemarker.template-loader-path=classpath:/templates
spring.freemarker.suffix=
spring.freemarker.cache=false
# Datasource
yo.datasource.url=jdbc:mysql://172.18.4.35/yoqw?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="freeswitch/xml">
<section name="dialplan" description="Dialplan Agent To Agent">
<context name="${tenant.realm}">
<extension name="AgentToAgent">
<condition>
<!-- <#noparse> -->
<action application="set" data="odbc-cdr-ignore-leg=true" />
<action application="set" data="absolute_codec_string=PCMA" />
<!--<action application="set" data="ringback=%(2000,4000,440,480)"/>-->
<action application="set" data="ringback=$${us-ring}" />
<!--<action application="set" data="transfer_ringback=$${hold_music}" />-->
<action application="set" data="call_timeout=30" />
<action application="set" data="hangup_after_bridge=true" />
<action application="set" data="continue_on_fail=true" />
<!-- </#noparse> -->
<action application="set" data="x_role=CALLER" />
<action application="set" data="x_tenant_id=${tenant.id}" />
<action application="set" data="x_tenant_code=${tenant.code}" />
<action application="set" data="x_account=${callerAgent.account}" />
<action application="set" data="x_conn_id=${connId}" />
<action application="set" data="x_dial_type=MANUAL" />
<action application="set" data="x_call_type=INTERNAL" />
<action application="set" data="x_agent_type=${callerAgent.type}" />
<!-- Recording -->
<#include "/dialplan/rec.xml">
<!-- /Recording -->
<!--
<#assign bridgeVars = [
"originate_timeout=30",
"odbc-cdr-ignore-leg=false",
"origination_uuid=${connId}",
"sip_invite_call_id=${r'${sip_call_id}'}",
"sip_contact_user=${r'${caller_id_number}'}",
"x_role=CALLED",
"x_tenant_id=${tenant.id}",
"x_tenant_code=${tenant.code}",
"x_account=${callerAgent.account}",
"x_agent_type=${callerAgent.type}",
"x_called_number=${calledAgent.agent}",
"x_conn_id=${connId}",
"x_dial_type=MANUAL",
"x_call_type=INTERNAL",
"sip_h_X-Dial-Type=MANUAL",
"sip_h_X-Call-Type=INTERNAL",
"sip_h_X-Call-Direction=INCOMING"
]>
-->
<action application="bridge" data="[${bridgeVars?join(',')}]user/${calledAgent.agent}@${tenant.realm}" />
</condition>
</extension>
</context>
</section>
</document>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="freeswitch/xml">
<section name="dialplan" description="Campain To Agent">
<context name="${tenant.realm}">
<extension name="CampaignToAgent">
<condition>
<!-- <#noparse> -->
<action application="set" data="absolute_codec_string=PCMA" />
<!--<action application="set" data="ringback=$${us-ring}" />-->
<!--<action application="set" data="transfer_ringback=$${hold_music}" />-->
<action application="set" data="call_timeout=30" />
<action application="set" data="hangup_after_bridge=true" />
<action application="set" data="continue_on_fail=true" />
<!-- </#noparse> -->
<action application="set" data="x_conn_id=${connId}" />
<action application="set" data="x_role=CALLED" />
<action application="set" data="x_tenant_id=${tenant.id}" />
<action application="set" data="x_tenant_code=${tenant.code}" />
<action application="set" data="x_account=${calledAgent.account}" />
<action application="set" data="x_dial_type=CAMPAIGN" />
<action application="set" data="x_call_type=OUTBOUND" />
<action application="set" data="x_agent_type=${calledAgent.type}" />
<!-- Recording -->
<#include "/dialplan/rec.xml">
<!-- /Recording -->
<!--
<#assign bridgeVars = [
"sip_auto_answer=true",
"originate_timeout=30",
"odbc-cdr-ignore-leg=true",
"origination_uuid=${connId}",
"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_tenant_id=${tenant.id}",
"x_tenant_code=${tenant.code}",
"x_conn_id=${connId}",
"x_dial_type=CAMPAIGN",
"x_call_type=OUTBOUND",
"sip_h_X-Dial-Type=CAMPAIGN",
"sip_h_X-Call-Type=OUTBOUND",
"sip_h_X-Call-Direction=OUTGOING"
]>
-->
<action application="bridge" data="[${bridgeVars?join(',')}]user/${calledAgent.agent}@${tenant.realm}" />
<action application="hangup" />
</condition>
</extension>
</context>
</section>
</document>

View File

@ -5,54 +5,69 @@
<extension name="InboundToAgent">
<condition>
<!-- <#noparse> -->
<action application="set" data="odbc-cdr-ignore-leg=true" />
<action application="set" data="absolute_codec_string=PCMA" />
<action application="set" data="ringback=${us-ring}" />
<action application="set" data="transfer_ringback=$${hold_music}" />
<action application="set" data="hangup_after_bridge=true" />
<action application="set" data="continue_on_fail=true" />
<action application="set" data="media_bug_answer_req=true" />
<action application="set" data="RECORD_STEREO=true" />
<!-- </#noparse> -->
<!--"origination_caller_id_number=${callerIdNumber}",-->
<!--"origination_caller_id_name=${callerIdName}",-->
<!-- Recording -->
<#include "/dialplan/rec.xml">
<!-- /Recording -->
<action application="set" data="x_conn_id=${connId}" />
<action application="set" data="x_role=CALLER" />
<action application="set" data="x_tenant_id=${tenant.id}" />
<action application="set" data="x_tenant_code=${tenant.code}" />
<action application="set" data="x_account=${calledAgent.account}" />
<action application="set" data="x_called_number=${calledAgent.agent}" />
<action application="set" data="x_trunk_id=${trunk.id}" />
<action application="set" data="x_cpn=${trunk.cpn}" />
<action application="set" data="x_dial_type=INBOUND" />
<action application="set" data="x_call_type=INBOUND" />
<action application="set" data="x_agent_type=${calledAgent.type}" />
<!--
<#assign bridgeVars = [
"originate_timeout=60",
"odbc-cdr-ignore-leg=false",
"odbc-cdr-ignore-leg=true",
"x_conn_id=${connId}",
"x_role=CALLED",
"x_tenant_id=${tenant.id}",
"x_tenant_code=${tenant.code}",
"x_account=${calledAgent.account}",
"x_conn_id=${connId}",
"x_dial_type=INBOUND",
"x_call_type=INBOUND",
"x_agent_type=${calledAgent.type}",
"x_called_number=${calledAgent.agent}",
"x_trunk_id=${trunk.id}",
"x_cpn=${trunk.cpn}",
"x_called_number=${calledAgent.agent}",
"origination_uuid=${connId}",
"sip_invite_call_id=${r'${sip_call_id}'}",
"callee_id_number=${calleeIdNumber}",
"callee_id_name=${calleeIdName}",
"effective_caller_id_number=${callerIdNumber}",
"effective_caller_id_name=${callerIdName}",
"sip_contact_user=${callerIdNumber}",
"callee_id_number=${privacyNumber.number}",
"callee_id_name=${privacyNumber.name}",
"effective_caller_id_number=${privacyNumber.number}",
"effective_caller_id_name=${privacyNumber.name}",
"origination_caller_id_number=${privacyNumber.number}",
"origination_caller_id_name=${privacyNumber.name}",
"sip_contact_user=${privacyNumber.number}",
"x_dial_type=INBOUND",
"x_call_type=INBOUND",
"sip_h_X-Dial-Type=INBOUND",
"sip_h_X-Call-Type=INBOUND"
]>
-->
<!-- Recording -->
<action application="set" data="record_post_process_exec_api=curl:${postRecUrl} post tenantId=${tenant.id}&loc=${recordingLoc}" />
<action application="record_session" data="${recordingLoc}" />
<!-- /Recording -->
<action application="bridge" data="[${bridgeVars?join(',')}]user/${calledAgent.agent}@${tenant.realm}" />
</condition>
</extension>

View File

@ -13,9 +13,6 @@
<action application="set" data="call_timeout=30" />
<action application="set" data="hangup_after_bridge=true" />
<action application="set" data="continue_on_fail=true" />
<action application="set" data="media_bug_answer_req=true" />
<action application="set" data="RECORD_STEREO=true" />
<!-- </#noparse> -->
<action application="set" data="x_role=CALLER" />
@ -29,8 +26,7 @@
<action application="set" data="x_agent_type=${callerAgent.type}" />
<!-- Recording -->
<action application="set" data="record_post_process_exec_api=curl:${postRecUrl} post tenantId=${tenant.id}&loc=${recordingLoc}" />
<action application="record_session" data="${recordingLoc}" />
<#include "/dialplan/rec.xml">
<!-- /Recording -->
<!--

View File

@ -0,0 +1,4 @@
<action application="set" data="media_bug_answer_req=true" />
<action application="set" data="RECORD_STEREO=true" />
<action application="set" data="record_post_process_exec_api=curl:${postRecUrl} post tenantId=${tenant.id}&loc=${recLoc}" />
<action application="record_session" data="${recLoc}" />

View File

@ -4,7 +4,6 @@
<context name="${tenant.realm}">
<extension name="TrunkOutbound">
<condition>
<!-- <#noparse> -->
<action application="set" data="odbc-cdr-ignore-leg=true" />
<action application="set" data="call_timeout=30" />
@ -20,10 +19,7 @@
<action application="set" data="x_call_type=OUTBOUND" />
<!-- Recording -->
<action application="set" data="media_bug_answer_req=true" />
<action application="set" data="RECORD_STEREO=true" />
<action application="set" data="record_post_process_exec_api=curl:${postRecUrl} post tenantId=${tenant.id}&loc=${recordingLoc}" />
<action application="record_session" data="${recordingLoc}" />
<#include "/dialplan/rec.xml">
<!-- /Recording -->
<!--

View File

@ -0,0 +1 @@
# Yo Basic Model

42
lib/basic-model/pom.xml Normal file
View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>yo-basic-model</artifactId>
<name>Yo Basic Model</name>
<description>Yo Basic Model</description>
<packaging>jar</packaging>
<parent>
<groupId>com.pudonghot.yo</groupId>
<artifactId>yo-library</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,12 @@
package com.pudonghot.yo.basic.model;
/**
* @author Donghuang
* @date Jul 12, 2020 14:12:04
*/
public enum PrivacyLevel {
NONE,
MEDIUM,
HEAVY,
ABSOLUTE
}

View File

@ -0,0 +1,19 @@
package com.pudonghot.yo.basic.model;
import lombok.*;
/**
* @author Donghuang
* @date Jul 12, 2020 14:12:04
*/
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class PrivacyNumber {
public static final String PRIVACY = "PRIVACY";
private String number;
private String name;
}

View File

@ -0,0 +1,17 @@
package com.pudonghot.yo.basic.model;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
/**
* @author Donghuang <br>
* Oct 26, 2019 15:41:02
*/
@Slf4j
public class TestDriver {
@Test
public void run() {
log.info("Run.");
}
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="log.level">DEBUG</Property>
<Property name="log.dir">.logs</Property>
<Property name="pattern">%-d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%t][%c{1}] %m%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%highlight{%-d{yyyy-MM-dd HH:mm:ss,SSS}}{FATAL=magenta, ERROR=magenta, WARN=magenta, INFO=magenta, DEBUG=magenta, TRACE=magenta} %highlight{%-5p}{FATAL=red blink, ERROR=red, WARN=yellow bold, INFO=black, DEBUG=green bold, TRACE=blue} [%t][%highlight{%c{1.}}{FATAL=cyan, ERROR=cyan, WARN=cyan, INFO=cyan, DEBUG=cyan, TRACE=cyan}] %m%n"/>
</Console>
<RollingFile name="File"
fileName="${log.dir}/${project.artifactId}.log"
filePattern="${log.dir}/$${date:yyyy-MM}/${project.artifactId}-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="${pattern}" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="16 MB" />
</Policies>
<DefaultRolloverStrategy max="32" />
</RollingFile>
</Appenders>
<Loggers>
<Logger name="org.springframework" level="INFO" additivity="false">
<AppenderRef ref="File" />
</Logger>
<Logger name="org.apache" level="WARN" additivity="false">
<AppenderRef ref="File" />
</Logger>
<Logger name="org.hibernate.validator" level="WARN" additivity="false">
<AppenderRef ref="File" />
</Logger>
<Root level="${log.level}" additivity="false">
<AppenderRef ref="File" level="${log.level}" />
<AppenderRef ref="Console" level="${log.level}" />
</Root>
</Loggers>
</Configuration>

View File

@ -57,6 +57,11 @@
<artifactId>yo-util</artifactId>
<version>${yo.version}</version>
</dependency>
<dependency>
<groupId>com.pudonghot.yo</groupId>
<artifactId>yo-basic-model</artifactId>
<version>${yo.version}</version>
</dependency>
<dependency>
<groupId>com.pudonghot.yo</groupId>
<artifactId>yo-fs-model</artifactId>

View File

@ -21,6 +21,10 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.pudonghot.yo</groupId>
<artifactId>yo-basic-model</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>

View File

@ -3,6 +3,7 @@ package com.pudonghot.yo.cellphone.privacy;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import com.pudonghot.yo.basic.model.PrivacyLevel;
/**
* mask phone number utils

View File

@ -1,12 +0,0 @@
package com.pudonghot.yo.cellphone.privacy;
/**
* @author Donghuang <br>
* Dec 13, 2019 10:13:05
*/
public enum PrivacyLevel {
NONE,
MEDIUM,
HEAVY,
ABSOLUTE
}

View File

@ -2,7 +2,7 @@ package com.pudonghot.yo.cellphone;
import org.junit.Test;
import lombok.extern.slf4j.Slf4j;
import com.pudonghot.yo.cellphone.privacy.PrivacyLevel;
import com.pudonghot.yo.basic.model.PrivacyLevel;
import com.pudonghot.yo.cellphone.privacy.NumberPrivacyUtils;
/**

View File

@ -1 +1 @@
# Yo BR FreeSWITCH Model
# Yo FreeSWITCH Model

View File

@ -5,7 +5,7 @@
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>yo-fs-model</artifactId>
<name>Yo BR FreeSWITCH Model</name>
<name>Yo FreeSWITCH Model</name>
<description>Yo FreeSWITCH Model</description>
<packaging>jar</packaging>

View File

@ -19,7 +19,6 @@ public class BaseReqDial implements Serializable {
private Integer tenantId;
@NotNull
private DialType dialType = DialType.MANUAL;
private String calledName;
private String bizKey;
private String attachedData;

View File

@ -3,7 +3,6 @@ package com.pudonghot.yo.fsagent.api.request;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.validation.constraints.NotNull;
/**

View File

@ -21,6 +21,10 @@
<groupId>com.wacai.tigon</groupId>
<artifactId>tigon-model</artifactId>
</dependency>
<dependency>
<groupId>com.pudonghot.yo</groupId>
<artifactId>yo-basic-model</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@ -7,6 +7,7 @@ import com.wacai.tigon.mybatis.Transient;
import com.wacai.tigon.mybatis.NotUpdate;
import lombok.experimental.FieldNameConstants;
import com.wacai.tigon.mybatis.UseGeneratedKeys;
import com.pudonghot.yo.basic.model.PrivacyLevel;
/**
* @author Donghuang <br>
@ -28,11 +29,4 @@ public class AgentGroup extends TaggableDomain {
private String identifier;
@Transient
private String trunkStrategies;
public enum PrivacyLevel {
NONE,
MEDIUM,
HEAVY,
ABSOLUTE
}
}

View File

@ -19,6 +19,7 @@
<modules>
<module>bom</module>
<module>basic-model</module>
<module>model</module>
<module>mapper</module>
<module>service-common</module>

View File

@ -0,0 +1,17 @@
package com.pudonghot.yo.service;
import com.pudonghot.yo.model.domain.Agent;
/**
* @author Donghuang
* @date Jul 11, 2020 16:40:51
*/
public interface CommonAcwScheduleService {
/**
* mark agent acw
*
* @param agent agent
*/
void acw(Agent agent);
}

View File

@ -0,0 +1,81 @@
package com.pudonghot.yo.service.impl;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMapCache;
import java.util.concurrent.TimeUnit;
import org.springframework.util.Assert;
import org.redisson.api.RedissonClient;
import com.pudonghot.yo.mapper.AgentMapper;
import com.pudonghot.yo.model.domain.Agent;
import org.redisson.api.map.event.EntryEvent;
import java.util.concurrent.atomic.AtomicBoolean;
import org.redisson.api.map.event.EntryExpiredListener;
import com.pudonghot.yo.service.CommonAgentStatusService;
import com.pudonghot.yo.service.CommonAcwScheduleService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author Donghuang
* @date Jul 11, 2020 16:40:42
*/
@Slf4j
public class CommonAcwScheduleServiceImpl implements CommonAcwScheduleService, InitializingBean {
private final RMapCache<Integer, Boolean> ACW_AGENTS;
private final AtomicBoolean started = new AtomicBoolean(false);
@Autowired
private AgentMapper agentMapper;
@Setter(onMethod_ = {@Autowired})
private CommonAgentStatusService agentStatusService;
@Value("${yo.service-common.acw-schedule.enable-expired-listener:false}")
private boolean enableExpiredListener;
/**
* init
*
* @param redisson redisson
*/
public CommonAcwScheduleServiceImpl(
@Autowired
final RedissonClient redisson) {
ACW_AGENTS = redisson.getMapCache("AGENT_ACW");
}
/**
* {@inheritDoc}
*/
@Override
public void acw(final Agent agent) {
ACW_AGENTS.put(agent.getId(), true, agent.getWrapUpTime(), TimeUnit.SECONDS);
}
/**
* {@inheritDoc}
*/
@Override
public void afterPropertiesSet() {
if (enableExpiredListener) {
log.info("Start agent ACW entry expired listener.");
Assert.state(started.compareAndSet(false, true),
"Agent ACW schedule listener added");
ACW_AGENTS.addListener((EntryExpiredListener) this::onAcwEnd);
}
else {
log.info("Ignore start agent ACW entry expired listener.");
}
}
private void onAcwEnd(final EntryEvent<Integer, Boolean> event) {
final Integer agentId = event.getKey();
log.debug("Agent [{}] acw end.", agentId);
final Agent agent = agentMapper.find(agentId);
if (agent != null) {
agentStatusService.endAcw(agent);
}
}
}

View File

@ -1,5 +1,6 @@
package com.pudonghot.yo.service.impl;
import lombok.Setter;
import java.util.Date;
import lombok.extern.slf4j.Slf4j;
import com.wacai.tigon.mybatis.Search;
@ -11,6 +12,7 @@ import com.pudonghot.yo.mapper.AgentStatusMapper;
import com.pudonghot.yo.model.domain.AgentStatus;
import com.pudonghot.yo.model.exception.ErrorCode;
import com.pudonghot.yo.model.exception.AssertUtils;
import com.pudonghot.yo.service.CommonAcwScheduleService;
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;
@ -29,6 +31,8 @@ public class CommonAgentStatusServiceImpl
private AgentStatusMapper agentStatusMapper;
@Autowired
private IdSequence idSeq;
@Setter(onMethod_ = {@Autowired})
private CommonAcwScheduleService acwScheduleService;
/**
* {@inheritDoc}
@ -221,10 +225,11 @@ public class CommonAgentStatusServiceImpl
clear(agentStatus);
agentStatus.setCallUuid(null);
if (agent.getWrapUpTime() > 0) {
agentStatus.setState(AgentStatus.State.ACW);
agentStatus.setAcwTime(new Date());
// TODO clear ACW
acwScheduleService.acw(agent);
}
else {
agentStatus.setState(AgentStatus.State.IDLE);

View File

@ -4,6 +4,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.pudonghot.yo.service.impl.CommonAcwScheduleServiceImpl" />
<bean class="com.pudonghot.yo.service.impl.CommonAgentStatusServiceImpl" />
<bean class="com.pudonghot.yo.service.impl.CommonAgentEventQueueServiceImpl" />
</beans>

View File

@ -1,10 +1,10 @@
package com.pudonghot.yo.state.service.impl;
import com.pudonghot.yo.state.service.TenantService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.pudonghot.yo.mapper.TenantMapper;
import com.pudonghot.yo.model.domain.Tenant;
import com.pudonghot.yo.state.service.TenantService;
import com.wacai.tigon.service.support.BaseCrudServiceSupport;
/**

View File

@ -1,4 +1,4 @@
server.port=8080
server.port=8086
spring.application.name=yo-state
spring.jackson.time-zone=GMT+8
spring.jackson.serialization.write-dates-as-timestamps=true
@ -14,3 +14,5 @@ spring.redis.host=172.16.92.232
spring.redis.port=6379
spring.redis.password=123456
yo.service-common.acw-schedule.enable-expired-listener=true