From 363d52d91e1c0f2040f2e340ad10ad5a42bc2818 Mon Sep 17 00:00:00 2001 From: haown <454902499@qq.com> Date: Fri, 30 Aug 2024 17:11:03 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BE=E5=BA=A6=E5=A4=96=E5=91=BC=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=AF=B9=E6=8E=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 17 +- .../controller/aibo/AIOBController.java | 80 ++++++ .../manage/domain/scriptInfo/ScriptInfo.java | 5 + .../ScriptInfoTaskInfo.java | 47 ++++ .../xinelu/manage/dto/aibo/CreateTaskDto.java | 67 ++++++ .../manage/dto/aibo/CustomerInfoDto.java | 33 +++ .../xinelu/manage/dto/aibo/ImportTaskDto.java | 45 ++++ .../manage/dto/aibo/TaskCallbackDataDto.java | 169 +++++++++++++ .../manage/dto/aibo/TaskCallbackDto.java | 22 ++ .../ScriptInfoTaskInfoMapper.java | 23 ++ .../manage/service/aibo/IAIOBService.java | 54 +++++ .../service/aibo/impl/AIOBServiceImpl.java | 227 ++++++++++++++++++ .../xinelu/manage/vo/aibo/ImportTaskVo.java | 32 +++ .../manage/scriptInfo/ScriptInfoMapper.xml | 14 +- .../ScriptInfoTaskInfoMapper.xml | 54 +++++ .../quartz/task/UploadRobotPublishTask.java | 126 +++++++++- 16 files changed, 1009 insertions(+), 6 deletions(-) create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/controller/aibo/AIOBController.java create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/domain/scriptinfotaskinfo/ScriptInfoTaskInfo.java create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/CreateTaskDto.java create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/CustomerInfoDto.java create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/ImportTaskDto.java create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/TaskCallbackDataDto.java create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/TaskCallbackDto.java create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/mapper/scriptinfotaskinfo/ScriptInfoTaskInfoMapper.java create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/service/aibo/IAIOBService.java create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/service/aibo/impl/AIOBServiceImpl.java create mode 100644 postdischarge-manage/src/main/java/com/xinelu/manage/vo/aibo/ImportTaskVo.java create mode 100644 postdischarge-manage/src/main/resources/mapper/manage/scriptinfotaskinfo/ScriptInfoTaskInfoMapper.xml diff --git a/postdischarge-admin/src/main/resources/application.yml b/postdischarge-admin/src/main/resources/application.yml index 1289c1fd..5406b468 100644 --- a/postdischarge-admin/src/main/resources/application.yml +++ b/postdischarge-admin/src/main/resources/application.yml @@ -107,7 +107,7 @@ token: # 令牌有效期(默认30分钟) expireTime: 30 # 请求拦截白名单 - ant-matchers: /postDischarge/**,/testMobile/**,/postDischargeApplet/** + ant-matchers: /postDischarge/**,/testMobile/**,/postDischargeApplet/**,/api/** ## MyBatis-Plus配置 mybatis-plus: @@ -274,3 +274,18 @@ push-message-restrictions: number: 1 #时间 : 单位,天 time: 2 + +# 爱医生平台对接 +aifuv-robots: + # 接口地址 + url: https://aifuv2cloud.aihealthx.cn/ + # 密钥 + secrectKey: lJayvsWsVUWDmWnNan1WzmH9OEOiwB + +# 百度客悦·智能外呼平台 +aiob: + # 请求地址 + url: https://aiob-open.baidu.com/api + accessKey: b6d2d73cdf2d4d7ca2ccc84968accdb3 + secretKey: 9262d2fe32544eaabce18331bd5ca2c5 + diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/controller/aibo/AIOBController.java b/postdischarge-manage/src/main/java/com/xinelu/manage/controller/aibo/AIOBController.java new file mode 100644 index 00000000..d23841b0 --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/controller/aibo/AIOBController.java @@ -0,0 +1,80 @@ +package com.xinelu.manage.controller.aibo; + +import com.xinelu.common.core.controller.BaseController; +import com.xinelu.common.core.domain.R; +import com.xinelu.manage.dto.aibo.CreateTaskDto; +import com.xinelu.manage.dto.aibo.TaskCallbackDto; +import com.xinelu.manage.service.aibo.IAIOBService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import javax.annotation.Resource; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @description: 百度客悦·智能外呼平台接口 + * @author: haown + * @create: 2024-08-22 09:50 + **/ +@Api(tags = "百度客悦·智能外呼平台接口") +@RestController +@RequestMapping("/api") +public class AIOBController extends BaseController { + @Resource + public IAIOBService aiobService; + + /** + * @description 获取外呼平台accessToken + * @return 智能外呼平台 accessToken + * @Author haown + * @Date 2024-8-22 15:30 + */ + @ApiOperation("获取accessToken") + @PostMapping("/getAccessToken") + public R getAccessToken() { + return R.ok(aiobService.getAccessToken()); + } + + /** + * @description 创建任务 + * @param createTaskDto 创建任务传输对象 + * @return taskId 客悦·智能外呼平台任务唯一标识 + * @Author haown + * @Date 2024-8-22 15:34 + */ + @ApiOperation("创建任务") + @PostMapping("/createTask") + public R createTask(CreateTaskDto createTaskDto) { + return R.ok(aiobService.createTask(createTaskDto)); + } + + /** + * @description 任务状态变更 + * @param taskId 任务id + * @param taskStatus 修改状态 2:执行中,3:已暂停,4:已完成(如点击「终止」 + * @return 状态码 + * @Author haown + * @Date 2024-8-23 11:49 + */ + @ApiOperation("任务状态变更") + @PostMapping("/updateTaskStatus") + public R updateTaskStatus(String taskId, Integer taskStatus) { + int flag = aiobService.updateTaskStatus(taskId, taskStatus); + return flag > 0 ? R.ok() : R.fail(); + } + + /** + * @description 任务单通电话回调 + * @param taskCallbackDto 回调数据类型, 0-任务呼叫单通电话回调 1-号码组终态回调 2-任务状态变更回调 3-实时呼叫单通电话回调 + * @return null + * @Author haown + * @Date 2024-8-29 11:15 + */ + @ApiOperation("任务单通电话回调") + @PostMapping("/taskCallBack") + public R taskCallBack(@RequestBody TaskCallbackDto taskCallbackDto) { + return R.ok(aiobService.taskCallBack(taskCallbackDto.getCallbackType(), taskCallbackDto.getData())); + } +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/domain/scriptInfo/ScriptInfo.java b/postdischarge-manage/src/main/java/com/xinelu/manage/domain/scriptInfo/ScriptInfo.java index 6390bc8f..0d21a1f4 100644 --- a/postdischarge-manage/src/main/java/com/xinelu/manage/domain/scriptInfo/ScriptInfo.java +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/domain/scriptInfo/ScriptInfo.java @@ -145,6 +145,11 @@ public class ScriptInfo extends BaseEntity { */ private String robotPublishId; + /** + * 话术中用到的变量,竖线隔开。来源于字段标签表的key + */ + private String variables; + @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/domain/scriptinfotaskinfo/ScriptInfoTaskInfo.java b/postdischarge-manage/src/main/java/com/xinelu/manage/domain/scriptinfotaskinfo/ScriptInfoTaskInfo.java new file mode 100644 index 00000000..a89ab777 --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/domain/scriptinfotaskinfo/ScriptInfoTaskInfo.java @@ -0,0 +1,47 @@ +package com.xinelu.manage.domain.scriptinfotaskinfo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @description: 百度外呼话术任务临时表 + * @author: haown + * @create: 2024-08-30 14:29 + **/ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ApiModel(value = "百度外呼话术任务临时表", description = "script_info_task_info") +public class ScriptInfoTaskInfo { + + /** + * 主键 + */ + private Long id; + + /** + * 话术表主键 + */ + private Long scriptInfoId; + + /** + * 机器人ID + */ + private String robotId; + + /** + * 任务标识 + */ + private String taskId; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/CreateTaskDto.java b/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/CreateTaskDto.java new file mode 100644 index 00000000..619bdf95 --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/CreateTaskDto.java @@ -0,0 +1,67 @@ +package com.xinelu.manage.dto.aibo; + +import java.util.List; +import lombok.Data; + +/** + * @description: 创建任务传输对象 + * @author: haown + * @create: 2024-08-22 14:52 + **/ +@Data +public class CreateTaskDto { + /** + * 任务名称 + */ + private String taskName; + /** + * 外呼机器人唯一标识 + */ + private String robotId; + + /** + * 任务呼叫开始日期 + */ + private String dialStartDate; + + /** + * 任务呼叫结束日期 + */ + private String dialEndDate; + + /** + * 呼叫开始时间 + */ + private String dialStartTime = "09:00"; + + /** + * 呼叫结束时间 + */ + private String dialEndTime = "18:00"; + + /** + * 禁呼日期, 99-节假日;1-周一;2-周二;3-周三;4-周四;5-周五;6-周六;0-周日,默认为空,表示不限制禁呼日期 + */ + private List forbidDialDate; + + /** + * 是否开启24小时空号检测 + */ + private Boolean isOpenEmptyNum = false; + + /** + * 是否开启12小时内停机检测 + */ + private Boolean isOpenPhoneDown = false; + + /** + * 号码类型过滤 1-400号码;2-800号码;3-手机号码;4-固话;5-95号码;6-96号码;7-其他 + */ + private List numTypeFilterList; + + /** + * 任务拨号完成后是否进入完成状态, 0:拨号完成后任务不结束 1:拨号完成后任务结束 默认值为1 + */ + private Integer callFinishTaskEnd = 0; + +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/CustomerInfoDto.java b/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/CustomerInfoDto.java new file mode 100644 index 00000000..25c38ab7 --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/CustomerInfoDto.java @@ -0,0 +1,33 @@ +package com.xinelu.manage.dto.aibo; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +/** + * @description: 客户名单传输对象 + * @author: haown + * @create: 2024-08-23 15:52 + **/ +@Data +public class CustomerInfoDto { + + /** + * 调用方的业务随路数据, 字符串,百度侧原文回传 + */ + private String extJson; + + /** + * 号码1;号码2;号码3, 最多5个号码,英文分号分隔,支持明文或密文 + */ + private String mobile; + + /** + * 用户指定归属地,为空则默认使用号码实际归属地,实际行政区域即可无格式要求,"-"分隔开即可 + */ + private String address; + + /** + * 名单的变量var map,名单外呼时需要用到的变量,{"key1": "value1","key2": "value2"} + */ + private JSONObject var; +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/ImportTaskDto.java b/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/ImportTaskDto.java new file mode 100644 index 00000000..4938daee --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/ImportTaskDto.java @@ -0,0 +1,45 @@ +package com.xinelu.manage.dto.aibo; + +import java.util.List; +import lombok.Data; + +/** + * @description: 导入名单传输对象 + * @author: haown + * @create: 2024-08-23 15:10 + **/ +@Data +public class ImportTaskDto { + + /** + * 任务Id + */ + private String taskId; + + /** + * 加密类型, 1:系统加密,即被叫号码采用系统的加密方式,AES128加密; + * 2:不加密,即被叫号码为明文; + * 3:自定义加密,即被叫号码采用客户自定义的加密方式,需在配置台-隐私配置-其他配置中配置自定义加解密服务地址,便于请求解密 + */ + private Integer secretType; + + /** + * 密钥ID,加密类型为系统加密时,必填 + */ + private Integer secretId; + + /** + * 明文, 字符串,长度小于等于20, 如果加密类型为系统加密,则必填 + */ + private String plainText; + + /** + * 密文,字符串,加密类型为系统加密时,必填 + */ + private String cipherText; + + /** + * 客户名单list,每一次最多只能导入1000个号码组,可以多次导入同一个任务 + */ + private List customerInfoList; +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/TaskCallbackDataDto.java b/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/TaskCallbackDataDto.java new file mode 100644 index 00000000..cbe89b9f --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/TaskCallbackDataDto.java @@ -0,0 +1,169 @@ +package com.xinelu.manage.dto.aibo; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import java.util.List; +import lombok.Data; + +/** + * @description: 任务单通电话回调传输data + * @author: haown + * @create: 2024-08-29 09:36 + **/ +@Data +public class TaskCallbackDataDto { + + /** + * 通话唯一标识 + */ + private String sessionId; + + /** + * 租户唯一标识 + */ + private Long tenantId; + + /** + * 任务标识 + */ + private String taskId; + + /** + * 任务名称 + */ + private String taskName; + + /** + * 机器人ID + */ + private String robotId; + + /** + * 外呼机器人名称 + */ + private String robotName; + + /** + * 号码组唯一标识,导入名单后,返回的名单号码组对应标识 + */ + private Long memberId; + + /** + * 被叫号码 + */ + private String mobile; + + /** + * 拨打次数 + */ + private Integer callTimes; + + /** + * 主叫号码 + */ + private String callerNum; + + /** + * 接通状态,1-已接通 0-未接通 + */ + private Integer endType; + /** + * 呼叫类型,0-首次呼叫;1-重试;2-预约呼叫;3-实时呼叫 + */ + private Integer callType; + + /** + * 未接通原因 + */ + private String endTypeReason; + + /** + * 生成通话录音唯一标识,可通过该标识,获取录音 + */ + private String contactUUID; + + /** + * 文件id,名单导入任务时生成的文件ID + */ + private Long field; + + /** + * 信息收集内容 + */ + private JSONObject collectInfo; + + /** + * 会话还原记录 + */ + private JSONArray record; + + /** + * 拨号总时长,单位为秒 + */ + private Integer durationTimeLen; + + /** + * 振铃时长,单位为秒 + */ + private Integer ringingTimeLen; + + /** + * 对话时长,单位为秒 + */ + private Integer talkingTimeLen; + + /** + * 呼叫开始时间-Unix时间戳(单位:毫秒) + */ + private Long startTime; + + /** + * 振铃开始时间-Unix时间戳(单位:毫秒) + */ + private Long ringStartTime; + + /** + * 通话开始时间-Unix时间戳(单位:毫秒),未接通显示'-',已接通会有值 + */ + private Long talkingStartTime; + + /** + * 呼叫结束时间-Unix时间戳(单位:毫秒) + */ + private Long endTime; + + /** + * 意向,在外呼机器人-流程节点-信息收集,配置key为「意向」,对应的value值 + */ + private String intent; + + /** + * 动作,HUNGUP:挂机 + */ + private List action; + + /** + * 是否机器人主动挂机 + */ + private Boolean isRobotHangup; + + /** + * 变量,导入名单的变量 + */ + private JSONObject dialogVar; + + /** + * 短信变量 + */ + private JSONObject smsVar; + + /** + * 来自导入信息 + */ + private String extJson; + + /** + * 转接结果,字段为空或值为0未发起,1成功,-1失败 + */ + private Integer transResult; +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/TaskCallbackDto.java b/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/TaskCallbackDto.java new file mode 100644 index 00000000..e04dab74 --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/dto/aibo/TaskCallbackDto.java @@ -0,0 +1,22 @@ +package com.xinelu.manage.dto.aibo; + +import lombok.Data; + +/** + * @description: 任务单通电话回调传输对象 + * @author: haown + * @create: 2024-08-29 14:06 + **/ +@Data +public class TaskCallbackDto { + + /** + * 回调数据类型, 0-任务呼叫单通电话回调 1-号码组终态回调 2-任务状态变更回调 3-实时呼叫单通电话回调 + */ + private Integer callbackType; + + /** + * 任务单通电话回调传输data + */ + private TaskCallbackDataDto data; +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/mapper/scriptinfotaskinfo/ScriptInfoTaskInfoMapper.java b/postdischarge-manage/src/main/java/com/xinelu/manage/mapper/scriptinfotaskinfo/ScriptInfoTaskInfoMapper.java new file mode 100644 index 00000000..9ee9e6da --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/mapper/scriptinfotaskinfo/ScriptInfoTaskInfoMapper.java @@ -0,0 +1,23 @@ +package com.xinelu.manage.mapper.scriptinfotaskinfo; + +import com.xinelu.manage.domain.scriptinfotaskinfo.ScriptInfoTaskInfo; + +/** + * 百度外呼话术任务临时表Mapper接口 + * + * @author haown + * @date 2024-08-30 + */ +public interface ScriptInfoTaskInfoMapper { + + String getByScriptInfoId(Long scriptInfoId); + + /** + * 新增百度外呼话术任务临时表 + * + * @param scriptInfoTaskInfo 百度外呼话术任务临时表 + * @return 结果 + */ + int insertScriptInfoTaskInfo(ScriptInfoTaskInfo scriptInfoTaskInfo); + +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/service/aibo/IAIOBService.java b/postdischarge-manage/src/main/java/com/xinelu/manage/service/aibo/IAIOBService.java new file mode 100644 index 00000000..c88f25bc --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/service/aibo/IAIOBService.java @@ -0,0 +1,54 @@ +package com.xinelu.manage.service.aibo; + +import com.xinelu.manage.dto.aibo.CreateTaskDto; +import com.xinelu.manage.dto.aibo.ImportTaskDto; +import com.xinelu.manage.dto.aibo.TaskCallbackDataDto; +import com.xinelu.manage.vo.aibo.ImportTaskVo; +import java.util.List; + +/** + * 百度客悦·智能外呼平台Service接口 + * + * @author: haown + * @create: 2024-08-22 09:50 + */ +public interface IAIOBService { + + /** + * @description 获取外呼平台accessToken + * @return 智能外呼平台 accessToken + * @Author haown + * @Date 2024-8-22 15:30 + */ + String getAccessToken(); + + /** + * @description 创建任务 + * @param createTaskDto 创建任务传输对象 + * @return taskId 客悦·智能外呼平台任务唯一标识 + * @Author haown + * @Date 2024-8-22 15:34 + */ + String createTask(CreateTaskDto createTaskDto); + + /** + * @description 任务状态变更 + * @param taskId 任务id + * @param taskStatus 修改状态 2:执行中,3:已暂停,4:已完成(如点击「终止」 + * @return 状态码 + * @Author haown + * @Date 2024-8-23 11:49 + */ + Integer updateTaskStatus(String taskId, Integer taskStatus); + + /** + * @description 任务导入客户名单 + * @param importTaskDto 导入名单传输对象 + * @return 名单导入结果 + * @Author haown + * @Date 2024-8-23 17:34 + */ + List importTask(ImportTaskDto importTaskDto); + + String taskCallBack(Integer callbackType, TaskCallbackDataDto data); +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/service/aibo/impl/AIOBServiceImpl.java b/postdischarge-manage/src/main/java/com/xinelu/manage/service/aibo/impl/AIOBServiceImpl.java new file mode 100644 index 00000000..131cde9b --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/service/aibo/impl/AIOBServiceImpl.java @@ -0,0 +1,227 @@ +package com.xinelu.manage.service.aibo.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.xinelu.common.core.redis.RedisCache; +import com.xinelu.common.exception.ServiceException; +import com.xinelu.common.utils.StringUtils; +import com.xinelu.manage.dto.aibo.CreateTaskDto; +import com.xinelu.manage.dto.aibo.ImportTaskDto; +import com.xinelu.manage.dto.aibo.TaskCallbackDataDto; +import com.xinelu.manage.service.aibo.IAIOBService; +import com.xinelu.manage.vo.aibo.ImportTaskVo; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +/** + * @description: 百度客悦·智能外呼平台Service实现 + * @author: haown + * @create: 2024-08-22 09:54 + **/ +@Service +public class AIOBServiceImpl implements IAIOBService { + @Value("${aiob.url}") + private String url; + + @Value("${aiob.accessKey}") + private String accessKey; + + @Value("${aiob.secretKey}") + private String secretKey; + + @Resource + private RedisCache redisCache; + + /** + * @description 获取外呼平台accessToken + * @return 智能外呼平台 accessToken + * @Author haown + * @Date 2024-8-22 15:30 + */ + @Override + public String getAccessToken() { + String accessToken = redisCache.getCacheObject("AIOBAssessToken"); + if (StringUtils.isBlank(accessToken)) { + JSONObject requestBodyJson = new JSONObject(); + requestBodyJson.put("secretKey", secretKey); + requestBodyJson.put("accessKey", accessKey); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity requestEntity = new HttpEntity<>(requestBodyJson, headers); + + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity responseEntity = restTemplate.exchange(url + "/v2/getToken", HttpMethod.POST, requestEntity, String.class); + JSONObject object = JSON.parseObject(responseEntity.getBody()); + if (object == null || object.getInteger("code") == null || object.getInteger("code") != 200) { + throw new ServiceException("获取accessToken失败!"); + } + JSONObject data = object.getJSONObject("data"); + // accessToken放入redis缓存 + accessToken = data.getString("accessToken"); + // 有效期30天 + int expires_in = object.getInteger("expires_in"); + redisCache.setCacheObject("AIOBAssessToken", accessToken, expires_in, TimeUnit.MINUTES); + } + return accessToken; + //{ + // "time" : 1724293180993, + // "code" : 200, + // "msg" : "OK", + // "data" : { + // "tenantId" : 3849515568332800, + // "accessToken" : "cc-api-auth-v2/10.B88F964FF8AEF605D75872F1B0EDFE57B28609A057F5E24104B4A7FB74CD63EC8199731D637E6EDFD195A82D5DFF454E", + // "expiresTime" : 43200, + // "accessKey" : "b6d2d73cdf2d4d7ca2ccc84968accdb3", + // "secretKey" : "9262d2fe32544eaabce18331bd5ca2c5" + // } + //} + } + + /** + * @description 创建任务 + * @param createTaskDto 创建任务传输对象 + * @return taskId 客悦·智能外呼平台任务唯一标识 + * @Author haown + * @Date 2024-8-22 15:34 + */ + @Override + public String createTask(CreateTaskDto createTaskDto) { + String accessToken = getAccessToken(); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", accessToken); + + HttpEntity requestEntity = new HttpEntity<>((JSONObject)JSON.toJSON(createTaskDto), headers); + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity responseEntity = restTemplate.exchange(url + "/v3/console/apitask/create", HttpMethod.POST, requestEntity, String.class); + JSONObject object = JSON.parseObject(responseEntity.getBody()); + if (object == null || object.getInteger("code") == null || object.getInteger("code") != 200) { + throw new ServiceException("创建任务失败," + object.getString("msg")); + } + JSONObject data = object.getJSONObject("data"); + return data.getString("taskId"); + // { + // "code": 200, + // "msg": "success", + // "data": { + // "taskId": "731346584781056" //任务ID,必填 + // } + // } + } + + /** + * @description 任务状态变更 + * @param taskId 任务id + * @param taskStatus 修改状态 2:执行中,3:已暂停,4:已完成(如点击「终止」 + * @return 状态码 + * @Author haown + * @Date 2024-8-23 11:49 + */ + @Override + public Integer updateTaskStatus(String taskId, Integer taskStatus) { + String accessToken = getAccessToken(); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", accessToken); + + JSONObject jsonObject = new JSONObject(); + jsonObject.fluentPut("taskId", taskId) + .fluentPut("taskStatus", taskStatus); + HttpEntity requestEntity = new HttpEntity<>(jsonObject, headers); + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity responseEntity = restTemplate.exchange(url + "/v3/console/realtime/status/create", HttpMethod.POST, requestEntity, String.class); + JSONObject object = JSON.parseObject(responseEntity.getBody()); + if (object == null || object.getInteger("code") == null || object.getInteger("code") != 200) { + throw new ServiceException("修改任务状态失败," + object.getString("msg")); + } + return 1; + // { + // "requestId": "c323dc9e5f67427c9c9d735a76e14164", + // "time": 1724375025670, + // "code": 200, + // "msg": "OK", + // "data": null + //} + } + + /** + * @description 任务导入客户名单 + * @param importTaskDto 导入名单传输对象 + * @return 名单导入结果 + * @Author haown + * @Date 2024-8-23 17:34 + */ + @Override + public List importTask(ImportTaskDto importTaskDto) { + String accessToken = getAccessToken(); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", accessToken); + + HttpEntity requestEntity = new HttpEntity<>((JSONObject)JSON.toJSON(importTaskDto), headers); + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity responseEntity = restTemplate.exchange(url + "/v3/console/apitask/import", HttpMethod.POST, requestEntity, String.class); + JSONObject object = JSON.parseObject(responseEntity.getBody()); + if (object == null || object.getInteger("code") == null || object.getInteger("code") != 200) { + throw new ServiceException("导入名单失败," + object.getString("msg")); + } + JSONObject data = object.getJSONObject("data"); + + JSONArray resList = data.getJSONArray("retList"); + List importTaskList = Arrays.asList(resList.toArray(ImportTaskVo.class)); + return importTaskList; + // { + // "code": 200, + // "msg": "success", + // "data": { + // "successNum": 2, // 名单导入成功条数 + // "failedNum": 0, // 名单导入失败条数 + // "resList": [ // 导入明细 + // { + // "status": true, // 导入的状态,true为 成功 bool + // "extJson": "",//调用方的业务随路数据, 字符串,百度侧原文回传 + // "reason": null, // 失败原因 + // "taskMemberId": 734116037099777 // 百度侧memberID,用户 用户号码组详情查询 + // }, + // { + // "status": true, + // "extJson": "",//调用方的业务随路数据, 字符串,百度侧原文回传 + // "reason": null, + // "taskMemberId": 734116037116160 + // } + // ] + // } + // } + } + + @Override + public String taskCallBack(Integer callbackType, TaskCallbackDataDto data) { + //TaskCallbackDataDto(sessionId=3874887150206976_2e1ae4e5ee0a4513b61d2411523f7b9b, tenantId=3849515568332800, + // taskId=3874887150206976, taskName=测试20240829, robotId=11a05202-12a4-437a-b989-43235b920a2f, + // robotName=慢病用药回访, memberId=3875110220070912, mobile=15166940975, callTimes=1, + // callerNum=02110001062, endType=1, callType=0, endTypeReason=null, contactUUID=1724917686303674_278162, + // field=null, collectInfo={}, + // record=[{"role":"speech","timestamp":1724917698521,"contextText":"您好,我是山东省立医院的电话客服,请问您是李四本人或家属吗?", "sn":"19a09431-1ac6-43a1-8f9f-4b6f617bed73","intent":"NOINTENT","start":"00:00.033","stop":"00:06.974","timeLen":6941,"interrupted":false,"silent":false,"nodeInfo":"eyJwcm9jZXNzTmFtZSI6IuaFoueXheeUqOiNr+Wbnuiuv+ivneacryIsIm5vZGVOYW1lIjoi5byA5Zy655m96IqC54K5X3RwcnZtc2g4IiwiaW50ZW50IjoiIiwiZW50aXR5IjpbXSwic3lzdGVtRXZlbnQiOiIiLCJmYXEiOiIifQ=="}, + // {"role":"voice","timestamp":1724917707542,"content":"是的","contextText":"是的","sn":"0e5c9c4e65db11ef_2_1","start":"00:06.930","stop":"00:08.330","timeLen":1400,"interrupted":false,"silent":false}, + // {"role":"speech","timestamp":1724917708359,"content":"是的","contextText":"好的,您之前在我们医院住过院,想了解一下您近期的健康状况。","sn":"0e5c9c4e65db11ef_2_1","intent":"patient_self","start":"00:09.871","stop":"00:15.793","timeLen":5922,"interrupted":false,"silent":false,"nodeInfo":"eyJwcm9jZXNzTmFtZSI6IuaFoueXheeUqOiNr+Wbnuiuv+ivneacryIsIm5vZGVOYW1lIjoi5pys5Lq66IqC54K5X3Nyd2Q5MTR3IiwiaW50ZW50Ijoi5piv5pys5Lq6IiwiZW50aXR5IjpbXSwic3lzdGVtRXZlbnQiOiIiLCJmYXEiOiIifQ=="}, + // {"role":"voice","timestamp":1724917718191,"content":"好的嗯","contextText":"好的嗯","sn":"0e5c9c4e65db11ef_4_1","start":"00:16.650","stop":"00:18.970","timeLen":2320,"interrupted":false,"silent":false}, + // {"role":"speech","timestamp":1724917718873,"content":"好的嗯","contextText":"您是否正常按时服药呢?","sn":"0e5c9c4e65db11ef_4_1","intent":"NOINTENT","start":"00:20.385","stop":"00:22.917","timeLen":2532,"interrupted":false,"silent":false,"nodeInfo":"eyJwcm9jZXNzTmFtZSI6IuaFoueXheeUqOiNr+Wbnuiuv+ivneacryIsIm5vZGVOYW1lIjoi6IKv5a6a6IqC54K5X3F6aWpzb2g2IiwiaW50ZW50IjoiIiwiZW50aXR5IjpbXSwic3lzdGVtRXZlbnQiOiIiLCJmYXEiOiIifQ=="}, + // {"role":"voice","timestamp":1724917725425,"content":"是的","contextText":"是的","sn":"0e5c9c4e65db11ef_6_1","start":"00:24.810","stop":"00:26.210","timeLen":1400,"interrupted":false,"silent":false}, + // {"role":"speech","timestamp":1724917725993,"content":"是的","contextText":"祝您早日康复。","sn":"0e5c9c4e65db11ef_6_1","intent":"sys_yes","start":"00:27.505","stop":"00:29.637","timeLen":2132,"interrupted":false,"silent":false,"nodeInfo":"eyJwcm9jZXNzTmFtZSI6IuaFoueXheeUqOiNr+Wbnuiuv+ivneacryIsIm5vZGVOYW1lIjoi5oyJ5pe25pyN6I2v6IqC54K5X3Bxd2RkcDBpIiwiaW50ZW50Ijoi6IKv5a6a5oSP5Zu+IiwiZW50aXR5IjpbXSwic3lzdGVtRXZlbnQiOiIiLCJmYXEiOiIifQ=="}], + // durationTimeLen=42, ringingTimeLen=9, talkingTimeLen=29, startTime=1724917686000, ringStartTime=1724917688000, talkingStartTime=1724917698000, endTime=1724917728000, intent=null, action=[HANGUP], isRobotHangup=true, + // dialogVar={"departmentName":"山东省立医院","patientName":"李四"}, smsVar=null, extJson=测试2, transResult=0) + return data.getTaskId(); + } +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/vo/aibo/ImportTaskVo.java b/postdischarge-manage/src/main/java/com/xinelu/manage/vo/aibo/ImportTaskVo.java new file mode 100644 index 00000000..1d4642ed --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/vo/aibo/ImportTaskVo.java @@ -0,0 +1,32 @@ +package com.xinelu.manage.vo.aibo; + +import lombok.Data; + +/** + * @description: 导入任务名单返回视图类 + * @author: haown + * @create: 2024-08-23 16:19 + **/ +@Data +public class ImportTaskVo { + + /** + * 导入的状态,true为 成功 + */ + private Boolean status; + + /** + * 调用方的业务随路数据, 字符串,百度侧原文回传 + */ + private String extJson; + + /** + * 失败原因 + */ + private String reason; + + /** + * 百度侧memberID,用户 用户号码组详情查询 + */ + private Integer taskMemberId; +} diff --git a/postdischarge-manage/src/main/resources/mapper/manage/scriptInfo/ScriptInfoMapper.xml b/postdischarge-manage/src/main/resources/mapper/manage/scriptInfo/ScriptInfoMapper.xml index 7989e7d6..95fd9f67 100644 --- a/postdischarge-manage/src/main/resources/mapper/manage/scriptInfo/ScriptInfoMapper.xml +++ b/postdischarge-manage/src/main/resources/mapper/manage/scriptInfo/ScriptInfoMapper.xml @@ -26,6 +26,7 @@ + @@ -56,7 +57,8 @@ create_time, update_by, update_time, - robot_publish_id + robot_publish_id, + variables from script_info @@ -80,7 +82,8 @@ si.create_time, si.update_by, si.update_time, - si.robot_publish_id + si.robot_publish_id, + si.variables from script_info si left join department d on d.id = si.department_id @@ -200,6 +203,8 @@ robot_publish_id, + variables, + #{departmentId}, @@ -242,6 +247,8 @@ #{robotPublishId}, + #{variables}, + @@ -304,6 +311,9 @@ robot_publish_id = #{robotPublishId}, + variables = + #{variables}, + where id = #{id} diff --git a/postdischarge-manage/src/main/resources/mapper/manage/scriptinfotaskinfo/ScriptInfoTaskInfoMapper.xml b/postdischarge-manage/src/main/resources/mapper/manage/scriptinfotaskinfo/ScriptInfoTaskInfoMapper.xml new file mode 100644 index 00000000..ebbab86a --- /dev/null +++ b/postdischarge-manage/src/main/resources/mapper/manage/scriptinfotaskinfo/ScriptInfoTaskInfoMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + select id, + script_info_id, + robot_id, + task_id, + create_time, + from script_info_task_info + + + + + + insert into script_info_task_info + + script_info_id, + + robot_id, + + task_id, + + create_time, + + + + #{scriptInfoId}, + + #{robotId}, + + #{taskId}, + + #{createTime}, + + + + \ No newline at end of file diff --git a/postdischarge-quartz/src/main/java/com/xinelu/quartz/task/UploadRobotPublishTask.java b/postdischarge-quartz/src/main/java/com/xinelu/quartz/task/UploadRobotPublishTask.java index 9aa3e79f..8734d6d3 100644 --- a/postdischarge-quartz/src/main/java/com/xinelu/quartz/task/UploadRobotPublishTask.java +++ b/postdischarge-quartz/src/main/java/com/xinelu/quartz/task/UploadRobotPublishTask.java @@ -1,26 +1,45 @@ package com.xinelu.quartz.task; +import com.alibaba.fastjson2.JSONObject; import com.xinelu.common.enums.NodeExecuteStatusEnum; import com.xinelu.common.enums.PhoneDialMethodEnum; import com.xinelu.common.enums.TaskNodeTypeEnum; +import com.xinelu.common.utils.StringUtils; +import com.xinelu.manage.domain.patientinfo.PatientInfo; import com.xinelu.manage.domain.patientvisitrecord.PatientVisitRecord; +import com.xinelu.manage.domain.scriptInfo.ScriptInfo; +import com.xinelu.manage.domain.scriptinfotaskinfo.ScriptInfoTaskInfo; import com.xinelu.manage.domain.signpatientmanageroute.SignPatientManageRoute; import com.xinelu.manage.domain.signpatientmanageroutenode.SignPatientManageRouteNode; import com.xinelu.manage.domain.signpatientrecord.SignPatientRecord; +import com.xinelu.manage.dto.aibo.CreateTaskDto; +import com.xinelu.manage.dto.aibo.CustomerInfoDto; +import com.xinelu.manage.dto.aibo.ImportTaskDto; import com.xinelu.manage.dto.signpatientmanageroutenode.SignPatientManageRouteNodeDto; +import com.xinelu.manage.mapper.labelfieldcontent.LabelFieldContentMapper; +import com.xinelu.manage.mapper.patientinfo.PatientInfoMapper; import com.xinelu.manage.mapper.patientvisitrecord.PatientVisitRecordMapper; +import com.xinelu.manage.mapper.scriptInfo.ScriptInfoMapper; +import com.xinelu.manage.mapper.scriptinfotaskinfo.ScriptInfoTaskInfoMapper; import com.xinelu.manage.mapper.signpatientmanageroute.SignPatientManageRouteMapper; import com.xinelu.manage.mapper.signpatientmanageroutenode.SignPatientManageRouteNodeMapper; import com.xinelu.manage.mapper.signpatientrecord.SignPatientRecordMapper; -import com.xinelu.manage.service.signpatientmanageroutenode.IRobotPublishService; +import com.xinelu.manage.service.aibo.IAIOBService; import com.xinelu.manage.service.signpatientmanageroutenode.ISignPatientManageRouteNodeService; +import com.xinelu.manage.vo.labelfieldcontent.LabelFieldInfoContentVo; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; import org.springframework.stereotype.Component; /** @@ -43,10 +62,20 @@ public class UploadRobotPublishTask { @Resource private PatientVisitRecordMapper patientVisitRecordMapper; @Resource - private IRobotPublishService robotPublishService; + private ScriptInfoTaskInfoMapper scriptInfoTaskInfoMapper; + @Resource + private IAIOBService aiobService; + @Resource + private ScriptInfoMapper scriptInfoMapper; + @Resource + private PatientInfoMapper patientInfoMapper; + @Resource + private LabelFieldContentMapper labelFieldContentMapper; + + public void uploadRobotPublishTask() { - log.info("开始执行爱医声上传任务定时任务......"); + log.info("开始执行百度智能外呼创建任务定时任务......"); // 查找需要当天执行的AI打电话任务 SignPatientManageRouteNodeDto signPatientManageRouteNodeDto = new SignPatientManageRouteNodeDto(); signPatientManageRouteNodeDto.setTaskNodeType(TaskNodeTypeEnum.PHONE_OUTBOUND.getInfo()); @@ -65,7 +94,98 @@ public class UploadRobotPublishTask { } }); if (CollectionUtils.isNotEmpty(executeNodeList)) { + // 按照话术id分组,每个话术id对应不同的机器人id, 每个机器人每天创建一条任务 + Map> groupByScriptInfo = executeNodeList.stream().collect(Collectors.groupingBy(SignPatientManageRouteNode::getScriptInfoId)); + // 根据机器人id查询智能外呼系统的任务id + for (Long scriptInfoId : groupByScriptInfo.keySet()) { + ScriptInfo scriptInfo = scriptInfoMapper.selectScriptInfoById(scriptInfoId); + String taskId = scriptInfoTaskInfoMapper.getByScriptInfoId(scriptInfoId); + if (StringUtils.isBlank(taskId)) { + // 没有任务则创建任务 + if (ObjectUtils.isNotEmpty(scriptInfo)) { + CreateTaskDto createTaskDto = new CreateTaskDto(); + createTaskDto.setTaskName(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + scriptInfo.getScriptName()); + createTaskDto.setRobotId(scriptInfo.getRobotPublishId()); + createTaskDto.setDialStartDate(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); + createTaskDto.setDialEndDate(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); + createTaskDto.setForbidDialDate(Arrays.asList(99)); + createTaskDto.setNumTypeFilterList(Arrays.asList(1,2)); + taskId = aiobService.createTask(createTaskDto); + + ScriptInfoTaskInfo scriptInfoTaskInfo = new ScriptInfoTaskInfo(); + scriptInfoTaskInfo.setScriptInfoId(scriptInfoId); + scriptInfoTaskInfo.setTaskId(taskId); + scriptInfoTaskInfo.setRobotId(scriptInfo.getRobotPublishId()); + scriptInfoTaskInfo.setCreateTime(LocalDateTime.now()); + scriptInfoTaskInfoMapper.insertScriptInfoTaskInfo(scriptInfoTaskInfo); + } + } + // 客户名单list + List scriptNodeList = groupByScriptInfo.get(scriptInfoId); + List customerInfoList = new ArrayList<>(); + scriptNodeList.forEach(node -> { + CustomerInfoDto customerInfoDto = new CustomerInfoDto(); + customerInfoDto.setExtJson(node.getId() + ""); + SignPatientManageRoute signPatientManageRoute = signPatientManageRouteMapper.selectSignPatientManageRouteById(node.getManageRouteId()); + PatientInfo patientInfo = patientInfoMapper.selectPatientInfoById(signPatientManageRoute.getPatientId()); + customerInfoDto.setMobile(patientInfo.getPatientPhone()); + // 查询患者画像信息 + List labelFieldContentList = labelFieldContentMapper.selectByPatientId(patientInfo.getId()); + // 处理变量 + JSONObject jsonObject = new JSONObject(); + if (StringUtils.isNotBlank(scriptInfo.getVariables())) { + List variables = Arrays.asList(scriptInfo.getVariables().split("|")); + variables.forEach(variable -> { + LabelFieldInfoContentVo labelFieldContent = labelFieldContentList.stream().filter(s -> Objects.equals(s.getFieldCode(), variable.replaceAll("_", "").toUpperCase())).findFirst().orElse(null); + jsonObject.fluentPut(variable, ObjectUtils.isEmpty(labelFieldContent) ? "" : labelFieldContent.getFieldValue()); + }); + } + + customerInfoDto.setVar(jsonObject); + customerInfoList.add(customerInfoDto); + }); + + // 上传名单 + ImportTaskDto importTaskDto = new ImportTaskDto(); + importTaskDto.setTaskId(taskId); + importTaskDto.setSecretType(2); + importTaskDto.setCustomerInfoList(customerInfoList); + aiobService.importTask(importTaskDto); + + // 开启任务 + aiobService.updateTaskStatus(taskId, 2); + } + + // 开启任务 //robotPublishService.uploadRobotPublishTask(executeNodeList); } } + + public static String convertToCamelCase(String s) { + if (s == null || !s.contains("_")){ + return s; + } + StringBuffer sb = new StringBuffer(); + //用来判断大写的标志 + boolean nextUpperCase = false; + for (int i = 0; i < s.length(); i++) { + if ("_".equals(String.valueOf(s.charAt(i)))) { + nextUpperCase = true; + } else { + if (nextUpperCase) { + sb = sb.append(String.valueOf(s.charAt(i)).toUpperCase()); + nextUpperCase = false; + }else { + sb = sb.append(s.charAt(i)); + } + } + } + return sb.toString(); + } + + public static void main(String[] args) { + String underscoreCase = "user_name"; + String camelCase = convertToCamelCase(underscoreCase); + System.out.println(camelCase); // 输出 "UserName" + } }