customer.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. 'use strict';
  2. const { CrudService } = require('naf-framework-mongoose/lib/service');
  3. const { BusinessError, ErrorCode } = require('naf-core').Error;
  4. const _ = require('lodash');
  5. const assert = require('assert');
  6. const moment = require('moment');
  7. // 客服相关
  8. class CustomerService extends CrudService {
  9. constructor(ctx) {
  10. super(ctx, 'customer');
  11. this.mq = this.ctx.mq;
  12. this.status = this.ctx.model.Status;
  13. this.record = this.ctx.model.Record;
  14. this.chatRecord = this.ctx.model.CustomerChat;
  15. // 1分钟检查1次
  16. this.time = 1;
  17. this.unit = 'm';
  18. }
  19. /**
  20. * 发送消息
  21. * @param {Object} body 参数
  22. * @property _tenant 项目
  23. * @property client_id 客户id
  24. * @property client_name 客户名
  25. * @property customer_id 客服id
  26. * @property customer_name 客服名
  27. * @property sender_id 发送人id
  28. * @property sender_name 发送人
  29. * @property content 内容
  30. */
  31. async chat(body) {
  32. const { _tenant, client_id, customer_id } = body;
  33. assert(_tenant, '缺少项目标识');
  34. assert(client_id, '缺少客户信息');
  35. assert(customer_id, '缺少客服信息');
  36. if (!this.mq) throw new BusinessError(ErrorCode.SERVICE_FAULT, 'mq配置错误');
  37. body.send_time = moment().format('YYYY-MM-DD HH:mm:ss');
  38. // 向项目存这条对话数据,再mq执行
  39. await this.chatRecord.create(body);
  40. // await this.ctx.service.util.httpUtil.$post('/customerChat', 'hnhmain', body);
  41. const exchange = _tenant;
  42. const routeKey = `customer.${client_id}.${customer_id}`;
  43. const mqBody = JSON.stringify(body);
  44. const param = { durable: true };
  45. await this.mq.topic(exchange, routeKey, mqBody, param);
  46. }
  47. /**
  48. * 客服获取正在服务的客户列表
  49. * @param {Object} query 参数
  50. */
  51. async getRecord(query) {
  52. const { _tenant, customer_id } = query;
  53. assert(_tenant, '缺少项目标识');
  54. assert(customer_id, '缺少客服信息');
  55. const list = await this.record.find(query);
  56. return list;
  57. }
  58. /**
  59. * 客服登陆/续时,此函数不处理工作状态,只处理时间失效与否
  60. * @param {Object} query 参数
  61. */
  62. async customerLog(query) {
  63. const { _tenant, customer_id } = query;
  64. assert(_tenant, '缺少项目标识');
  65. assert(customer_id, '缺少客服信息');
  66. let customer = await this.status.findOne({ _tenant, customer_id });
  67. if (customer) {
  68. // 该客服已经上线
  69. customer.logout_time = this.addTime();
  70. await customer.save();
  71. } else {
  72. // 没上线
  73. const obj = { ...query, status: '0', logout_time: this.addTime() };
  74. customer = await this.status.create(obj);
  75. }
  76. return customer;
  77. }
  78. /**
  79. * 客户咨询,查找指定项目的空闲客服,如果没有,那就找到所有客服,
  80. * 并且找其同时接待最少的客服,让他接待
  81. * @param {Object} query 参数
  82. */
  83. async clientApply(query) {
  84. const { _tenant, client_id, client_name } = query;
  85. assert(_tenant, '缺少项目标识');
  86. assert(client_id, '缺少客户信息');
  87. const freeList = await this.status.find({ _tenant, status: '0' });
  88. let customer;
  89. if (freeList.length > 0) {
  90. customer = _.head(freeList);
  91. // 修改状态,加次时间
  92. customer.status = '1';
  93. customer.logout_time = this.addTime();
  94. await customer.save();
  95. } else {
  96. // 没有闲的了,那就找不闲的
  97. let list = await this.status.find({ _tenant });
  98. if (list.length > 0) {
  99. list = JSON.parse(JSON.stringify(list));
  100. for (const i of list) {
  101. const { customer_id } = i;
  102. const num = await this.record.count({ customer_id, _tenant });
  103. i.num = num;
  104. }
  105. list = _.orderBy(list, [ 'num' ], [ 'asc' ]);
  106. customer = _.head(list);
  107. customer.status = '1';
  108. customer.logout_time = this.addTime();
  109. await this.customerLog({ _tenant, customer_id: customer.customer_id });
  110. } else return '当前没有客服在线';
  111. }
  112. // 添加记录
  113. const obj = { customer_id: customer.customer_id, customer_name: customer.customer_name, _tenant, client_id, client_name };
  114. let record = await this.record.findOne(obj);
  115. if (record) {
  116. record.out_time = this.addTime();
  117. await record.save();
  118. } else {
  119. obj.out_time = this.addTime();
  120. record = await this.record.create(obj);
  121. }
  122. if (this.mq) {
  123. const exchange = _tenant;
  124. const routeKey = `customer.${customer.customer_id}`;
  125. const mqBody = 'research';
  126. const param = { durable: true };
  127. await this.mq.topic(exchange, routeKey, mqBody, param);
  128. }
  129. return record;
  130. }
  131. /**
  132. * 客户续时
  133. * @param {Object} query 参数
  134. */
  135. async clientContinue(query) {
  136. const { _tenant, client_id, customer_id } = query;
  137. assert(_tenant, '缺少项目标识');
  138. assert(client_id, '缺少客户信息');
  139. assert(customer_id, '缺少客服信息');
  140. const record = await this.record.findOne({ _tenant, client_id, customer_id });
  141. if (!record) return '服务已经结束';
  142. record.out_time = this.addTime();
  143. const r = await record.save();
  144. if (r) {
  145. // 同时给客服续航
  146. await this.customerLog({ _tenant, customer_id });
  147. }
  148. return 'ok';
  149. }
  150. // 结束服务
  151. async serverEnd(query) {
  152. const { _tenant, client_id, customer_id } = query;
  153. assert(_tenant, '缺少项目标识');
  154. assert(client_id, '缺少客户信息');
  155. assert(customer_id, '缺少客服信息');
  156. await this.record.deleteOne({ _tenant, client_id, customer_id });
  157. this.checkStatus();
  158. return 'ok';
  159. }
  160. // 查询并修改客服状态
  161. async checkStatus() {
  162. const list = await this.status.find();
  163. for (const i of list) {
  164. const num = await this.record.count({ customer_id: i._id });
  165. if (num <= 0) {
  166. i.status = '0';
  167. i.save();
  168. }
  169. }
  170. }
  171. // 定时清理
  172. async clear() {
  173. // 所有状态和工作记录,对比失效时间,过了就删了
  174. console.log('in function:clear');
  175. await this.status.deleteMany({ logout_time: { $lte: moment().format('YYYY-MM-DD HH:mm:ss') } });
  176. // await this.record.deleteMany({ out_time: { $lte: moment().format('YYYY-MM-DD HH:mm:ss') } });
  177. }
  178. // 续
  179. addTime() {
  180. const t = moment().add(this.time, this.unit).format('YYYY-MM-DD HH:mm:ss');
  181. return t;
  182. }
  183. }
  184. module.exports = CustomerService;