IvrDomainService.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. using CallCenter.Caches;
  2. using CallCenter.Calls;
  3. using CallCenter.Devices;
  4. using CallCenter.Settings;
  5. using CallCenter.Share.Dtos;
  6. using CallCenter.Share.Enums;
  7. using MapsterMapper;
  8. using Microsoft.Extensions.Logging;
  9. using Microsoft.Extensions.Options;
  10. using XF.Domain.Cache;
  11. using XF.Domain.Dependency;
  12. using XF.Domain.Exceptions;
  13. namespace CallCenter.Ivrs;
  14. public class IvrDomainService : IIvrDomainService, IScopeDependency
  15. {
  16. private readonly IDeviceManager _deviceManager;
  17. private readonly IIvrRepository _ivrRepository;
  18. private readonly ITypedCache<List<Ivr>> _cacheIvrList;
  19. private readonly IIvrCacheManager _ivrCacheManager;
  20. private readonly ITelCacheManager _telCacheManager;
  21. private readonly IUserCacheManager _userCacheManager;
  22. private readonly IMapper _mapper;
  23. private readonly ILogger<IvrDomainService> _logger;
  24. private readonly IOptionsSnapshot<WorkTimeSettings> _worktimeOptions;
  25. private readonly ITypedCache<WorkTimeSettings> _worktimeCache;
  26. public IvrDomainService(
  27. IDeviceManager deviceManager,
  28. IIvrRepository ivrRepository,
  29. ITypedCache<List<Ivr>> cacheIvrList,
  30. IIvrCacheManager ivrCacheManager,
  31. ITelCacheManager telCacheManager,
  32. IUserCacheManager userCacheManager,
  33. IMapper mapper,
  34. ILogger<IvrDomainService> logger,
  35. IOptionsSnapshot<WorkTimeSettings> worktimeOptions,
  36. ITypedCache<WorkTimeSettings> worktimeCache)
  37. {
  38. _deviceManager = deviceManager;
  39. _ivrRepository = ivrRepository;
  40. _cacheIvrList = cacheIvrList;
  41. _ivrCacheManager = ivrCacheManager;
  42. _telCacheManager = telCacheManager;
  43. _userCacheManager = userCacheManager;
  44. _mapper = mapper;
  45. _logger = logger;
  46. _worktimeOptions = worktimeOptions;
  47. _worktimeCache = worktimeCache;
  48. }
  49. /// <summary>
  50. /// 语音播放结束
  51. /// </summary>
  52. /// <param name="menuId"></param>
  53. /// <param name="cancellationToken"></param>
  54. /// <returns></returns>
  55. public async Task<IvrAnswer?> GetVoiceEndAnswerAsync(string menuId, CancellationToken cancellationToken = default)
  56. {
  57. var ivrs = _ivrCacheManager.GetIvrs();
  58. if (!ivrs.Any())
  59. throw new UserFriendlyException("未查到任何ivr配置");
  60. var ivr = ivrs.FirstOrDefault(d => d.No == menuId);
  61. if (ivr is null)
  62. throw new UserFriendlyException("未知语音菜单编号");
  63. var strategy = ivr.IvrStrategies.FirstOrDefault(d => d.IvrStrategeType == EIvrStrategeType.VoiceEnd);
  64. var answer = await ValuationStrategyAnswer(strategy, ivr, cancellationToken);
  65. _logger.LogInformation("语音播放结束响应策略: type: {type}, content: {content}, preVoice: {voice}", answer?.IvrAnswerTypeText, answer?.Content, answer?.PreVoice);
  66. return answer;
  67. }
  68. /// <summary>
  69. /// 获得按键输入
  70. /// </summary>
  71. /// <param name="menuId"></param>
  72. /// <param name="input"></param>
  73. /// <param name="cancellationToken"></param>
  74. /// <returns></returns>
  75. public async Task<IvrAnswer?> GetDtmfAnswerAsync(string menuId, string input, CancellationToken cancellationToken = default)
  76. {
  77. var ivrs = _ivrCacheManager.GetIvrs();
  78. if (!ivrs.Any())
  79. throw new UserFriendlyException("未查到任何ivr配置");
  80. if (string.IsNullOrEmpty(input))
  81. throw new UserFriendlyException("无效输入参数");
  82. var ivr = ivrs.FirstOrDefault(d => d.No == menuId);
  83. if (ivr is null)
  84. throw new UserFriendlyException("未知语音菜单编号");
  85. if (input.Length > ivr.InfoLength)
  86. throw new UserFriendlyException("按键输入内容超出配置允许长度");
  87. IvrAnswer? answer;
  88. //超时
  89. if (input.EndsWith('T'))
  90. {
  91. var overTimeStrategy = ivr.IvrStrategies.FirstOrDefault(d => d.IvrStrategeType == EIvrStrategeType.OverTime);
  92. if (overTimeStrategy is null)
  93. {
  94. answer = new IvrAnswer { IvrAnswerType = EIvrAnswerType.HangUp };
  95. _logger.LogInformation($"未配置IVR超时策略, menuId: {menuId}");
  96. return answer;
  97. }
  98. overTimeStrategy.Answer.PreVoice = GetStrategyVoice(overTimeStrategy, ivr);
  99. answer = overTimeStrategy.Answer;
  100. _logger.LogInformation("按键响应策略: type: {type}, content: {content}, preVoice: {voice}", answer?.IvrAnswerTypeText, answer?.Content, answer?.PreVoice);
  101. return answer;
  102. }
  103. var inputValue = input;
  104. if (!string.IsNullOrEmpty(ivr.Exit) && input.EndsWith(ivr.Exit))
  105. {
  106. var length = ivr.Exit.Length;
  107. inputValue = input.Remove(input.Length - length, length);
  108. }
  109. if (ivr.IvrType == EIvrType.Normal)
  110. {
  111. var strategy = ivr.IvrStrategies.FirstOrDefault(d => d.Input == inputValue);
  112. answer = await ValuationStrategyAnswer(strategy, ivr, cancellationToken);
  113. _logger.LogInformation("按键响应策略: type: {type}, content: {content}, preVoice: {voice}", answer?.IvrAnswerTypeText, answer?.Content, answer?.PreVoice);
  114. return answer;
  115. }
  116. else if (ivr.IvrType == EIvrType.Score)//score
  117. {
  118. //todo 评分
  119. //评分成功返回第一个策略,否则直接挂机
  120. return ivr.IvrStrategies.FirstOrDefault()?.Answer ?? new IvrAnswer { IvrAnswerType = EIvrAnswerType.HangUp };
  121. }
  122. else
  123. {
  124. throw new ArgumentOutOfRangeException("无效IvrType");
  125. }
  126. }
  127. private async Task<IvrAnswer?> ValuationStrategyAnswer(IvrStrategy? strategy, Ivr ivr, CancellationToken cancellationToken)
  128. {
  129. if (strategy is null)
  130. {
  131. var errorStrategy = ivr.IvrStrategies.FirstOrDefault(d => d.IvrStrategeType == EIvrStrategeType.Error);
  132. return errorStrategy?.Answer ?? null;
  133. }
  134. if (strategy.IvrStrategeType == EIvrStrategeType.PreStep)
  135. {
  136. strategy.Answer.Content = ivr.PrevIvrNo;
  137. strategy.Answer.PreVoice = null;
  138. }
  139. if (strategy.Answer.IvrAnswerType == EIvrAnswerType.TelGroup)
  140. {
  141. var groupNo = await GetCurrentTelGroupAsync(strategy, cancellationToken);
  142. if (string.IsNullOrEmpty(groupNo)) return null;
  143. strategy.Answer.Content = groupNo;
  144. }
  145. return strategy.Answer;
  146. }
  147. public async Task<string> AddIvrAsync(Ivr ivr, CancellationToken cancellationToken = default)
  148. {
  149. var existNo = await _ivrRepository.AnyAsync(d => d.No == ivr.No, cancellationToken);
  150. if (existNo)
  151. throw new UserFriendlyException("IVR编号已存在");
  152. await _deviceManager.AssginConfigMenuAsync(ivr.No, ivr.Voice, ivr.Repeat.ToString(), ivr.InfoLength.ToString(), ivr.Exit, cancellationToken);
  153. var exists = await _ivrRepository.AnyAsync(d => d.IvrCategoryId == ivr.IvrCategoryId, cancellationToken);
  154. ivr.IsRoot = !exists;
  155. var id = await _ivrRepository.AddAsync(ivr, cancellationToken);
  156. _cacheIvrList.Remove(Ivr.Key);
  157. return id;
  158. }
  159. public async Task UpdateIvrAsync(Ivr ivr, CancellationToken cancellationToken = default)
  160. {
  161. await _deviceManager.AssginConfigMenuAsync(ivr.No, ivr.Voice, ivr.Repeat.ToString(), ivr.InfoLength.ToString(), ivr.Exit, cancellationToken);
  162. await _ivrRepository.UpdateWithoutStrategiesAsync(ivr, cancellationToken);
  163. _cacheIvrList.Remove(Ivr.Key);
  164. }
  165. /// <summary>
  166. /// 构建IVR关系
  167. /// </summary>
  168. /// <param name="dto"></param>
  169. /// <param name="cancellationToken"></param>
  170. /// <returns></returns>
  171. public async Task StructureIvrAsync(StructureIvrDto dto, CancellationToken cancellationToken)
  172. {
  173. var ivr = await _ivrRepository.GetAsync(dto.Id, cancellationToken);
  174. if (ivr == null)
  175. throw new UserFriendlyException("无效IVR编号");
  176. //if (!ivr.IsRoot && string.IsNullOrEmpty(dto.PrevIvrNo))
  177. // throw new UserFriendlyException("上级IVR编号不能为空");
  178. //转下级IVR
  179. if (dto.IvrStrategies.Any(d => d.Answer.IvrAnswerType == EIvrAnswerType.Voice))
  180. {
  181. var voiceStrategy = dto.IvrStrategies.First(d => d.Answer.IvrAnswerType == EIvrAnswerType.Voice);
  182. if (string.IsNullOrEmpty(voiceStrategy.Answer.Content))
  183. throw new UserFriendlyException("未正确配置下一级IVR");
  184. var nextIvr = await _ivrRepository.GetAsync(d => d.No == voiceStrategy.Answer.Content);
  185. if (nextIvr == null)
  186. throw new UserFriendlyException("无效IVRNo");
  187. //支持插播语音以后转回自身
  188. if (nextIvr.No != ivr.No)
  189. {
  190. nextIvr.PrevIvrNo = ivr.No;
  191. await _ivrRepository.UpdateAsync(nextIvr, cancellationToken);
  192. }
  193. }
  194. //转分机组置空content
  195. if (dto.IvrStrategies.Any(d => d.Answer.IvrAnswerType == EIvrAnswerType.TelGroup))
  196. {
  197. var groupStrategy = dto.IvrStrategies.First(d => d.Answer.IvrAnswerType == EIvrAnswerType.TelGroup);
  198. groupStrategy.Answer.Content = null;
  199. }
  200. _mapper.Map(dto, ivr);
  201. await _ivrRepository.UpdateAsync(ivr, cancellationToken);
  202. _cacheIvrList.Remove(Ivr.Key);
  203. }
  204. /// <summary>
  205. /// 删除IVR关系(并非删除IVR)
  206. /// </summary>
  207. /// <param name="ivrId"></param>
  208. /// <param name="cancellationToken"></param>
  209. /// <returns></returns>
  210. public async Task DeStructureIvrAsync(string ivrId, CancellationToken cancellationToken)
  211. {
  212. var ivr = await _ivrRepository.GetAsync(ivrId, cancellationToken);
  213. if (ivr == null)
  214. throw new UserFriendlyException("无效IVR编号");
  215. ivr.PrevIvrNo = null;
  216. ivr.IvrStrategies = new();
  217. await _ivrRepository.UpdateAsync(ivr, false, cancellationToken);
  218. }
  219. /// <summary>
  220. /// 替换某个IVR分组下的起始IVR(根节点)
  221. /// </summary>
  222. /// <param name="ivrId"></param>
  223. /// <param name="cancellationToken"></param>
  224. /// <returns></returns>
  225. public async Task ReplaceRootAsync(string ivrId, CancellationToken cancellationToken)
  226. {
  227. var ivr = await _ivrRepository.GetAsync(ivrId, cancellationToken);
  228. if (ivr == null)
  229. throw new UserFriendlyException("无效IVR编号");
  230. var root = await _ivrRepository.GetAsync(d => d.IvrCategoryId == ivr.IvrCategoryId && d.IsRoot, cancellationToken);
  231. if (root == null)
  232. throw new UserFriendlyException($"异常起始IVR, IvrCategoryId: {ivr.IvrCategoryId}");
  233. root.IsRoot = false;
  234. ivr.IsRoot = true;
  235. ivr.PrevIvrNo = null;
  236. await _ivrRepository.UpdateAsync(ivr, false, cancellationToken);
  237. await _ivrRepository.UpdateAsync(root, cancellationToken);
  238. }
  239. #region private
  240. private async Task<string?> GetCurrentTelGroupAsync(IvrStrategy strategy, CancellationToken cancellationToken)
  241. {
  242. if (!strategy.GroupSorts.Any())
  243. return null;
  244. foreach (var groupSort in strategy.GroupSorts.OrderBy(d => d.Sort))
  245. {
  246. var group = _telCacheManager.GetTelGroup(groupSort.GroupNo);
  247. if (!group.Tels.Any()) continue;
  248. foreach (var tel in group.Tels)
  249. {
  250. var working = await _userCacheManager.IsWorkingByTelAsync(tel.No, cancellationToken);
  251. if (working)
  252. return group.No;
  253. }
  254. }
  255. return null;
  256. }
  257. private string? GetStrategyVoice(IvrStrategy strategy, Ivr ivr)
  258. {
  259. return !string.IsNullOrEmpty(strategy.Answer.PreVoice) ? $"{strategy.Answer.PreVoice}+{ivr.Voice}" : null;
  260. }
  261. #endregion
  262. #region IVR流程处理
  263. private Ivr GetCorrectIvr()
  264. {
  265. var worktimeSettings = _worktimeCache.GetOrAdd("worktimesettings", d => _worktimeOptions.Value, ExpireMode.Absolute, TimeSpan.FromDays(1));
  266. var categoryId = GetCorrectCategory(worktimeSettings);
  267. var ivrList = _ivrCacheManager.GetIvrs();
  268. var ivr = ivrList.First(x => x.IvrCategoryId == categoryId && x.IsRoot);
  269. return ivr;
  270. }
  271. public CorrectIvr GetCorrectIvr(string to,bool isEvaluate = false)
  272. {
  273. var worktimeSettings = _worktimeCache.GetOrAdd("worktimesettings", d => _worktimeOptions.Value, ExpireMode.Absolute, TimeSpan.FromDays(1));
  274. var correct = GetCorrectCategory(worktimeSettings.LineSetting.First(x => x.NumNo == to),isEvaluate);
  275. return correct;
  276. }
  277. private CorrectIvr GetCorrectCategory(LineSetting settings,bool isEvaluate)
  278. {
  279. if (isEvaluate)
  280. {
  281. return new CorrectIvr() { eCorrectIvr = ECorrectIvr.Ivr, ReturnValue = settings.EvaluateCategory };
  282. }
  283. if (!settings.WorkDay.Contains((int)DateTime.Now.DayOfWeek))
  284. return new CorrectIvr() { eCorrectIvr = ECorrectIvr.Group, ReturnValue = settings.RestToGroup };
  285. var time = TimeOnly.FromDateTime(DateTime.Now);
  286. if ((time >= TimeOnly.Parse(settings.MorningBegin) && time <= TimeOnly.Parse(settings.MorningEnd))
  287. || (time >= TimeOnly.Parse(settings.AfterBegin) && time <= TimeOnly.Parse(settings.AfterEnd)))
  288. {
  289. if (!string.IsNullOrEmpty(settings.WorkCategory))
  290. {
  291. return new CorrectIvr() { eCorrectIvr = ECorrectIvr.Ivr, ReturnValue = settings.WorkCategory };
  292. }
  293. else
  294. {
  295. return new CorrectIvr() { eCorrectIvr = ECorrectIvr.Group, ReturnValue = settings.WorkToGroup };
  296. }
  297. }
  298. else
  299. {
  300. if (!string.IsNullOrEmpty(settings.RestCategory))
  301. {
  302. return new CorrectIvr() { eCorrectIvr = ECorrectIvr.Ivr, ReturnValue = settings.RestCategory };
  303. }
  304. else
  305. {
  306. return new CorrectIvr() { eCorrectIvr = ECorrectIvr.Group, ReturnValue = settings.RestToGroup };
  307. }
  308. }
  309. }
  310. public class CorrectIvr
  311. {
  312. public string ReturnValue { get; set; }
  313. public ECorrectIvr eCorrectIvr { get; set; }
  314. }
  315. private string GetCorrectCategory(WorkTimeSettings settings)
  316. {
  317. if (!settings.WorkDay.Contains((int)DateTime.Now.DayOfWeek))
  318. return settings.RestCategory;
  319. var time = TimeOnly.FromDateTime(DateTime.Now);
  320. if ((time >= TimeOnly.Parse(settings.MorningBegin) && time <= TimeOnly.Parse(settings.MorningEnd))
  321. || (time >= TimeOnly.Parse(settings.AfterBegin) && time <= TimeOnly.Parse(settings.AfterEnd))
  322. )
  323. return settings.WorkCategory;
  324. return settings.RestCategory;
  325. }
  326. #endregion
  327. }