This commit is contained in:
Donghuang 2021-09-12 21:17:45 +08:00
parent 33e026a92d
commit 3ef8fc4799
22 changed files with 466 additions and 144 deletions

View File

@ -21,6 +21,7 @@ import com.pudonghot.yo.mapper.CallRecordingMapper;
import com.pudonghot.yo.cms.model.HangupCauseMapping;
import com.pudonghot.yo.model.domain.CallDetailRecord;
import com.pudonghot.yo.model.dbobject.CallDetailReport;
import com.pudonghot.yo.model.domain.CallDetailRecordAll;
import org.springframework.beans.factory.annotation.Value;
import com.pudonghot.yo.cms.form.FormListCallDetailRecord;
import com.wacai.tigon.web.controller.BaseQueryController;
@ -51,7 +52,7 @@ filterCols = {
@Slf4j
@RequestMapping("/cms/api/call-detail-record")
public class CallDetailRecordController
extends BaseQueryController<Integer, CallDetailRecord, FormListCallDetailRecord>
extends BaseQueryController<Integer, CallDetailRecordAll, FormListCallDetailRecord>
implements SessionAbility {
@Autowired

View File

@ -2,8 +2,8 @@ package com.pudonghot.yo.cms.service;
import java.util.List;
import com.wacai.tigon.service.BaseQueryService;
import com.pudonghot.yo.model.domain.CallDetailRecord;
import com.pudonghot.yo.model.dbobject.CallDetailReport;
import com.pudonghot.yo.model.domain.CallDetailRecordAll;
import com.pudonghot.yo.model.request.ReqCallDetailRecordAccountReport;
import com.pudonghot.yo.cms.service.request.CallDetailRecordDeleteServReq;
@ -11,7 +11,7 @@ import com.pudonghot.yo.cms.service.request.CallDetailRecordDeleteServReq;
* @author Donghuang
* @date Oct 21, 2020 21:10:19
*/
public interface CallDetailRecordService extends BaseQueryService<Integer, CallDetailRecord> {
public interface CallDetailRecordService extends BaseQueryService<Integer, CallDetailRecordAll> {
/**
* account report

View File

@ -13,9 +13,9 @@ 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.model.domain.CallDetailRecord;
import com.pudonghot.yo.mapper.CallDetailRecordMapper;
import com.pudonghot.yo.model.dbobject.CallDetailReport;
import com.pudonghot.yo.mapper.CallDetailRecordAllMapper;
import com.pudonghot.yo.model.domain.CallDetailRecordAll;
import com.pudonghot.yo.cms.service.CallDetailRecordService;
import org.springframework.beans.factory.annotation.Autowired;
import com.wacai.tigon.service.support.BaseQueryServiceSupport;
@ -29,8 +29,8 @@ import com.pudonghot.yo.cms.service.request.CallDetailRecordDeleteServReq;
@Slf4j
@Service
public class CallDetailRecordServiceImpl
extends BaseQueryServiceSupport<Integer, CallDetailRecord, CallDetailRecordMapper>
implements CallDetailRecordService{
extends BaseQueryServiceSupport<Integer, CallDetailRecordAll, CallDetailRecordAllMapper>
implements CallDetailRecordService {
@Autowired
private CallRecordingMapper recordingMapper;

View File

@ -0,0 +1,33 @@
package com.pudonghot.yo.mapper;
import java.util.List;
import me.chyxion.tigon.mybatis.BaseMapper;
import org.apache.ibatis.annotations.Param;
import com.pudonghot.yo.model.dbobject.CallDetailReport;
import com.pudonghot.yo.model.domain.CallDetailRecordAll;
import com.pudonghot.yo.model.domain.CallDetailRecordBase;
import com.pudonghot.yo.model.request.ReqCallDetailRecordAccountReport;
/**
* @author Donghuang
* @date Sep 09, 2021 01:06:07
*/
public interface CallDetailRecordAllMapper extends BaseMapper<Integer, CallDetailRecordAll> {
/**
* insert base model
*
* @param model model
* @return insert row
*/
int insert(@Param(PARAM_MODEL_KEY) CallDetailRecordBase model);
/**
* account report
*
* @param arg arg
* @return account report
*/
List<CallDetailReport> accountReport(
@Param("arg") ReqCallDetailRecordAccountReport arg);
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
/**
* @author Donghuang
* @date Sep 09, 2021 01:06:00
*/
-->
<mapper namespace="com.pudonghot.yo.mapper.CallDetailRecordAllMapper">
<select id="accountReport" resultType="com.pudonghot.yo.model.dbobject.CallDetailReport">
select account,
dial_type,
call_type,
count(id) total,
count(answer_stamp) answered,
sec_to_time(sum(billsec)) duration,
sec_to_time(max(billsec)) max_duration,
sec_to_time(round(avg(billsec), 0)) avg_duration,
min(start_stamp) min_start_stamp,
max(start_stamp) max_start_stamp
from br_call_detail_record
where start_stamp between #{arg.dateFrom} and #{arg.dateTo}
<if test="arg.account != null">
and account = #{arg.account}
</if>
and tenant_id = #{arg.tenantId}
group by account, dial_type, call_type
</select>
</mapper>

View File

@ -1,14 +1,10 @@
package com.pudonghot.yo.mapper;
import java.util.List;
import lombok.val;
import me.chyxion.tigon.mybatis.BaseMapper;
import me.chyxion.tigon.mybatis.Search;
import org.apache.ibatis.annotations.Param;
import me.chyxion.tigon.mybatis.BaseMapper;
import com.pudonghot.yo.model.domain.CallDetailRecord;
import com.pudonghot.yo.model.dbobject.CallDetailReport;
import com.pudonghot.yo.model.request.ReqCallDetailRecordAccountReport;
import com.pudonghot.yo.model.domain.CallDetailRecordBase;
/**
* @author Donghuang <br>
@ -16,6 +12,14 @@ import com.pudonghot.yo.model.request.ReqCallDetailRecordAccountReport;
*/
public interface CallDetailRecordMapper extends BaseMapper<Integer, CallDetailRecord> {
/**
* insert base model
*
* @param model model
* @return insert row
*/
int insert(@Param(PARAM_MODEL_KEY) CallDetailRecordBase model);
/**
* 呼叫在灰名单中有效
*
@ -27,14 +31,6 @@ public interface CallDetailRecordMapper extends BaseMapper<Integer, CallDetailRe
@Param("tenantId") Integer tenantId,
@Param("calledNumber") String calledNumber);
/**
* account report
* @param arg arg
* @return account report
*/
List<CallDetailReport> accountReport(
@Param("arg") ReqCallDetailRecordAccountReport arg);
/**
* find by conn id
*

View File

@ -34,24 +34,4 @@
) t
</select>
<select id="accountReport" resultType="com.pudonghot.yo.model.dbobject.CallDetailReport">
select account,
dial_type,
call_type,
count(id) total,
count(answer_stamp) answered,
sec_to_time(sum(billsec)) duration,
sec_to_time(max(billsec)) max_duration,
sec_to_time(round(avg(billsec), 0)) avg_duration,
min(start_stamp) min_start_stamp,
max(start_stamp) max_start_stamp
from br_call_detail_record
where start_stamp between #{arg.dateFrom} and #{arg.dateTo}
<if test="arg.account != null">
and account = #{arg.account}
</if>
and tenant_id = #{arg.tenantId}
group by account, dial_type, call_type
</select>
</mapper>

View File

@ -30,6 +30,8 @@ import java.util.Scanner;
public class CallDetailRecordMapperTest {
@Autowired
private CallDetailRecordMapper mapper;
@Autowired
private CallDetailRecordAllMapper allMapper;
@Test
public void testList() {
@ -48,7 +50,7 @@ public class CallDetailRecordMapperTest {
arg.setTenantId(1);
arg.setDateFrom(DateUtils.parseDate("2020-10-10", "yyyy-MM-dd"));
arg.setDateTo(DateUtils.parseDate("2020-10-11", "yyyy-MM-dd"));
val result = mapper.accountReport(arg);
val result = allMapper.accountReport(arg);
log.info("List [{}].", result);
}

View File

@ -38,6 +38,7 @@ public class Agent extends TaggableDomain {
private String queues;
public enum Type {
SYSTEM,
PERSON,
ROBOT,
IVR

View File

@ -2,12 +2,7 @@ package com.pudonghot.yo.model.domain;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import com.wacai.tigon.model.M0;
import me.chyxion.tigon.mybatis.Table;
import lombok.RequiredArgsConstructor;
import me.chyxion.tigon.mybatis.NotUpdate;
import lombok.experimental.FieldNameConstants;
/**
* @author Donghuang <br>
@ -16,88 +11,7 @@ import lombok.experimental.FieldNameConstants;
@Getter
@Setter
@Table("br_call_detail_record")
@FieldNameConstants(prefix = "")
public class CallDetailRecord extends M0<Integer> {
@NotUpdate
private Integer tenantId;
@NotUpdate
private String tenantCode;
@NotUpdate
private String account;
@NotUpdate
private String connId;
public class CallDetailRecord extends CallDetailRecordBase {
private static final long serialVersionUID = 1L;
@NotUpdate
private DialType dialType;
@NotUpdate
private CallType callType;
@NotUpdate
private Agent.Type agentType;
@NotUpdate
private Integer campaignId;
@NotUpdate
private String caseKey;
@NotUpdate
private String uuid;
@NotUpdate
private String callUuid;
@NotUpdate
private String callerNumber;
@NotUpdate
private String calledNumber;
@NotUpdate
private Integer trunkId;
@NotUpdate
private String callingPartyNumber;
@NotUpdate
private Date startStamp;
@NotUpdate
private Date answerStamp;
@NotUpdate
private Date endStamp;
@NotUpdate
private Long duration;
@NotUpdate
private Long mduration;
@NotUpdate
private Long billsec;
@NotUpdate
private Long billmsec;
@NotUpdate
private String endpointDisposition;
@NotUpdate
private String hangupCause;
@NotUpdate
private String hangupDisposition;
private Boolean deleted;
@RequiredArgsConstructor
public enum DialType {
MANUAL(""),
INBOUND(""),
CAMPAIGN("");
@Getter
private final String text;
}
@RequiredArgsConstructor
public enum CallDirection {
INCOMING("来电"),
OUTGOING("去电");
@Getter
private final String text;
}
@RequiredArgsConstructor
public enum CallType {
INTERNAL("内线"),
INBOUND("进线"),
OUTBOUND("外呼");
@Getter
private final String text;
}
}

View File

@ -0,0 +1,16 @@
package com.pudonghot.yo.model.domain;
import lombok.Getter;
import lombok.Setter;
import me.chyxion.tigon.mybatis.Table;
/**
* @author Donghuang <br>
* Nov 14, 2019 11:32:42
*/
@Getter
@Setter
@Table("br_call_detail_record_all")
public class CallDetailRecordAll extends CallDetailRecordBase {
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,120 @@
package com.pudonghot.yo.model.domain;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import com.wacai.tigon.model.M0;
import lombok.RequiredArgsConstructor;
import me.chyxion.tigon.mybatis.NotUpdate;
import lombok.experimental.FieldNameConstants;
/**
* @author Donghuang
* @date Sep 09, 2021 22:47:13
*/
@Getter
@Setter
@FieldNameConstants(prefix = "")
public class CallDetailRecordBase extends M0<Integer> {
@NotUpdate
private Integer tenantId;
@NotUpdate
private String tenantCode;
@NotUpdate
private String account;
@NotUpdate
private String connId;
@NotUpdate
private DialType dialType;
@NotUpdate
private CallType callType;
@NotUpdate
private Agent.Type agentType;
@NotUpdate
private Integer campaignId;
@NotUpdate
private String caseKey;
@NotUpdate
private String uuid;
@NotUpdate
private String callUuid;
@NotUpdate
private String callerNumber;
@NotUpdate
private String calledNumber;
@NotUpdate
private Integer trunkId;
@NotUpdate
private String callingPartyNumber;
@NotUpdate
private Date startStamp;
@NotUpdate
private Date answerStamp;
@NotUpdate
private Date endStamp;
@NotUpdate
private Long duration;
@NotUpdate
private Long mduration;
@NotUpdate
private Long billsec;
@NotUpdate
private Long billmsec;
@NotUpdate
private String endpointDisposition;
@NotUpdate
private String hangupCause;
@NotUpdate
private String hangupDisposition;
private Boolean deleted;
/**
* {@inheritDoc}
*/
@Override
public void beforeInsert() {
if (agentType == null) {
if ("SYSTEM".equals(account)) {
agentType = Agent.Type.SYSTEM;
}
else {
agentType = Agent.Type.PERSON;
}
}
if (deleted == null) {
deleted = Boolean.FALSE;
}
}
@RequiredArgsConstructor
public enum DialType {
MANUAL(""),
INBOUND(""),
CAMPAIGN("");
@Getter
private final String text;
}
@RequiredArgsConstructor
public enum CallDirection {
INCOMING("来电"),
OUTGOING("去电");
@Getter
private final String text;
}
@RequiredArgsConstructor
public enum CallType {
INTERNAL("内线"),
INBOUND("进线"),
OUTBOUND("外呼");
@Getter
private final String text;
}
}

View File

@ -1,10 +1,15 @@
package com.pudonghot.yo.fsagent.controller;
import java.util.Map;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pudonghot.yo.model.domain.CallDetailRecordBase;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.beans.factory.annotation.Autowired;
import com.pudonghot.yo.fsagent.service.CallDetailRecordService;
import com.pudonghot.yo.fsagent.controller.request.CdrCtrlrReqDTO;
/**
* @author Donghuang <br>
@ -14,10 +19,38 @@ import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class PostCdrController {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private CallDetailRecordService callDetailRecordService;
@PostMapping("/fsa/api/cdr/post")
public void postCdr(
@RequestParam
final Map<String, String> params) {
log.info("Post cdr params: {}", params);
public void postCdr(@RequestParam("cdr") final String cdr) {
log.info("Post cdr params: {}", cdr);
val cdrDTO = readObj(cdr);
if (Boolean.TRUE.equals(cdrDTO.getIgnore())) {
log.debug("CDR ignored.");
return;
}
callDetailRecordService.insert(convert(cdrDTO, CallDetailRecordBase.class));
}
@SneakyThrows
private CdrCtrlrReqDTO readObj(final String strCdr) {
val jsonNode = objectMapper.readTree(strCdr);
log.info("Json Node [{}].", jsonNode);
val dto = objectMapper.treeToValue(
jsonNode.get("variables"), CdrCtrlrReqDTO.class);
log.debug("CDR DTO [{}] read.", dto);
return dto;
}
private <T> T convert(final Object obj, final Class<T> type) {
log.debug("Convert object [{}] to [{}].", obj, type);
return objectMapper.convertValue(obj, type);
}
}

View File

@ -0,0 +1,79 @@
package com.pudonghot.yo.fsagent.controller.request;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import lombok.ToString;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.codehaus.jackson.schema.JsonSerializableSchema;
/**
* @author Donghuang
* @date Sep 08, 2021 10:50:55
*/
@Setter
@Getter
@ToString
@JsonSerializableSchema
public class CdrCtrlrReqDTO implements Serializable {
private static final long serialVersionUID = 1L;
@JsonAlias("odbc-cdr-ignore-leg")
private Boolean ignore;
@JsonAlias("x_tenant_id")
private Integer tenantId;
@JsonAlias("x_tenant_code")
private String tenantCode;
@JsonAlias("x_account")
private String account;
@JsonAlias("x_conn_id")
private String connId;
@JsonAlias("x_dial_type")
private String dialType;
@JsonAlias("x_call_type")
private String callType;
@JsonAlias("x_agent_type")
private String agentType;
@JsonAlias("x_campaign_id")
private Integer campaignId;
@JsonAlias("x_case_key")
private String caseKey;
private String uuid;
@JsonAlias("call_uuid")
private String callUuid;
@JsonAlias("sip_call_id")
private String sipCallId;
@JsonAlias({"caller_id_number", "sip_from_user"})
private String callerNumber;
@JsonAlias("x_called_number")
private String calledNumber;
@JsonAlias("x_trunk_id")
private Integer trunkId;
@JsonAlias("x_cpn")
private String callingPartyNumber;
@JsonAlias("start_stamp")
@Getter(onMethod_ = @JsonFormat(shape = JsonFormat.Shape.NUMBER))
@Setter(onMethod_ = @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss"))
private Date startStamp;
@JsonAlias("answer_stamp")
@Getter(onMethod_ = @JsonFormat(shape = JsonFormat.Shape.NUMBER))
@Setter(onMethod_ = @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss"))
private Date answerStamp;
@JsonAlias("end_stamp")
@Getter(onMethod_ = @JsonFormat(shape = JsonFormat.Shape.NUMBER))
@Setter(onMethod_ = @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss"))
private Date endStamp;
private Long duration;
private Long mduration;
private Long billsec;
private Long billmsec;
@JsonAlias("endpoint_disposition")
private String endpointDisposition;
@JsonAlias("hangup_cause")
private String hangupCause;
@JsonAlias("sip_hangup_disposition")
private String hangupDisposition;
}

View File

@ -16,7 +16,7 @@ import org.freeswitch.esl.client.transport.event.Event;
import com.pudonghot.yo.service.CommonAgentStatusService;
import com.pudonghot.yo.service.CommonAgentEventQueueService;
import org.springframework.beans.factory.annotation.Autowired;
import com.pudonghot.yo.model.domain.CallDetailRecord.DialType;
import com.pudonghot.yo.model.domain.CallDetailRecordBase.DialType;
import static com.pudonghot.yo.model.agentevent.EventType.AgentOther_PhoneRelease;
import static com.pudonghot.yo.model.agentevent.EventType.AgentEvent_Customer_Release;

View File

@ -0,0 +1,17 @@
package com.pudonghot.yo.fsagent.service;
import com.pudonghot.yo.model.domain.CallDetailRecordBase;
/**
* @author Donghuang
* @date Sep 11, 2021 20:27:22
*/
public interface CallDetailRecordService {
/**
* insert cdr
*
* @param req req
*/
void insert(CallDetailRecordBase req);
}

View File

@ -103,8 +103,9 @@ public class CampaignDialServiceImpl implements CampaignDialService {
LogMDC.setTraceId(uuid);
callUuidList.add(uuid);
final List<String> channelVars = Arrays.asList(
val channelVars = Arrays.<String>asList(
"x_account=SYSTEM",
"x_agent_type=SYSTEM",
"origination_uuid=" + uuid,
"sip_invite_call_id=" + uuid,
"originate_timeout=30",

View File

@ -0,0 +1,34 @@
package com.pudonghot.yo.fsagent.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.pudonghot.yo.mapper.CallDetailRecordMapper;
import com.pudonghot.yo.mapper.CallDetailRecordAllMapper;
import com.pudonghot.yo.model.domain.CallDetailRecordBase;
import org.springframework.beans.factory.annotation.Autowired;
import com.pudonghot.yo.fsagent.service.CallDetailRecordService;
/**
* @author Donghuang
* @date Sep 11, 2021 20:32:27
*/
@Slf4j
@Service
public class CallDetailRecordServiceImpl
implements CallDetailRecordService {
@Autowired
private CallDetailRecordMapper callDetailRecordMapper;
@Autowired
private CallDetailRecordAllMapper callDetailRecordAllMapper;
/**
* {@inheritDoc}
*/
@Override
public void insert(final CallDetailRecordBase req) {
log.debug("Insert call detail [{}].", req);
callDetailRecordAllMapper.insert(req);
// callDetailRecordMapper.insert(req);
}
}

View File

@ -0,0 +1,46 @@
package com.pudonghot.yo.fsagent.service.request;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import lombok.ToString;
import java.io.Serializable;
/**
* @author Donghuang
* @date Sep 08, 2021 10:50:55
*/
@Setter
@Getter
@ToString
public class CallDetailtRecordCreateServReq implements Serializable {
private static final long serialVersionUID = 1L;
private Integer tenantId;
private String tenantCode;
private String account;
private String connId;
private String dialType;
private String callType;
private String agentType;
private Integer campaignId;
private String caseKey;
private String uuid;
private String callUuid;
private String sipCallId;
private String callerNumber;
private String calledNumber;
private Integer trunkId;
private String callingPartyNumber;
private Date startStamp;
private Date answerStamp;
private Date endStamp;
private Long duration;
private Long mduration;
private Long billsec;
private Long billmsec;
private String endpointDisposition;
private String hangupCause;
private String hangupDisposition;
}

View File

@ -7,15 +7,15 @@
<profile name="default">
<settings>
<!-- the format of data to send, defaults to xml -->
<!-- <param name="format" value="json|xml"/> -->
<!-- the format json|xml of data to send, defaults to xml -->
<param name="format" value="json"/>
<!-- the url to post to if blank web posting is disabled -->
<param name="url" value="${model.postUrl}"/>
<!-- <#noparse> -->
<!-- optional: credentials to send to web server -->
<!-- <param name="cred" value="user:pass"/> -->
<!-- <param name="cred" value="user:pass"/> -->
<!-- the total number of retries (not counting the first 'try') to post to webserver incase of failure -->
<param name="retries" value="2"/>
@ -80,8 +80,8 @@
<!-- <param name="cookie-file" value="/tmp/cookie-mod_format_cdr_curl.txt"/> -->
<!-- Whether to URL encode the individual JSON values. Defaults to true, set to false for standard JSON. -->
<param name="encode-values" value="true"/>
<param name="encode-values" value="false"/>
<!-- </#noparse> -->
</settings>
</profile>
</profiles>

File diff suppressed because one or more lines are too long

View File

@ -24,7 +24,7 @@ WORK_DIR=$(pwd)
echo "Work dir [$WORK_DIR]"
if [ -z "$1" ]; then
echo 'Usage: ./start.sh module(cms,fsagent...)'
echo 'Usage: ./start.sh module(cms,server)'
exit 1
fi