|
@@ -0,0 +1,202 @@
|
|
|
|
+'use strict';
|
|
|
|
+const { CrudService } = require('naf-framework-mongoose/lib/service');
|
|
|
|
+const { BusinessError, ErrorCode } = require('naf-core').Error;
|
|
|
|
+const _ = require('lodash');
|
|
|
|
+const assert = require('assert');
|
|
|
|
+const moment = require('moment');
|
|
|
|
+
|
|
|
|
+// 客服相关
|
|
|
|
+class CustomerService extends CrudService {
|
|
|
|
+ constructor(ctx) {
|
|
|
|
+ super(ctx, 'customer');
|
|
|
|
+ this.mq = this.ctx.mq;
|
|
|
|
+ this.status = this.ctx.model.Status;
|
|
|
|
+ this.record = this.ctx.model.Record;
|
|
|
|
+ this.chatRecord = this.ctx.model.CustomerChat;
|
|
|
|
+
|
|
|
|
+ // 1分钟检查1次
|
|
|
|
+ this.time = 1;
|
|
|
|
+ this.unit = 'm';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 发送消息
|
|
|
|
+ * @param {Object} body 参数
|
|
|
|
+ * @property _tenant 项目
|
|
|
|
+ * @property client_id 客户id
|
|
|
|
+ * @property client_name 客户名
|
|
|
|
+ * @property customer_id 客服id
|
|
|
|
+ * @property customer_name 客服名
|
|
|
|
+ * @property sender_id 发送人id
|
|
|
|
+ * @property sender_name 发送人
|
|
|
|
+ * @property content 内容
|
|
|
|
+ */
|
|
|
|
+ async chat(body) {
|
|
|
|
+ const { _tenant, client_id, customer_id } = body;
|
|
|
|
+ assert(_tenant, '缺少项目标识');
|
|
|
|
+ assert(client_id, '缺少客户信息');
|
|
|
|
+ assert(customer_id, '缺少客服信息');
|
|
|
|
+ if (!this.mq) throw new BusinessError(ErrorCode.SERVICE_FAULT, 'mq配置错误');
|
|
|
|
+ body.send_time = moment().format('YYYY-MM-DD HH:mm:ss');
|
|
|
|
+ // 向项目存这条对话数据,再mq执行
|
|
|
|
+ await this.chatRecord.create(body);
|
|
|
|
+ // await this.ctx.service.util.httpUtil.$post('/customerChat', 'hnhmain', body);
|
|
|
|
+ const exchange = _tenant;
|
|
|
|
+ const routeKey = `customer.${client_id}.${customer_id}`;
|
|
|
|
+ const mqBody = JSON.stringify(body);
|
|
|
|
+ const param = { durable: true };
|
|
|
|
+ await this.mq.topic(exchange, routeKey, mqBody, param);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 客服获取正在服务的客户列表
|
|
|
|
+ * @param {Object} query 参数
|
|
|
|
+ */
|
|
|
|
+ async getRecord(query) {
|
|
|
|
+ const { _tenant, customer_id } = query;
|
|
|
|
+ assert(_tenant, '缺少项目标识');
|
|
|
|
+ assert(customer_id, '缺少客服信息');
|
|
|
|
+ const list = await this.record.find(query);
|
|
|
|
+ return list;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 客服登陆/续时,此函数不处理工作状态,只处理时间失效与否
|
|
|
|
+ * @param {Object} query 参数
|
|
|
|
+ */
|
|
|
|
+ async customerLog(query) {
|
|
|
|
+ const { _tenant, customer_id } = query;
|
|
|
|
+ assert(_tenant, '缺少项目标识');
|
|
|
|
+ assert(customer_id, '缺少客服信息');
|
|
|
|
+ let customer = await this.status.findOne({ _tenant, customer_id });
|
|
|
|
+ if (customer) {
|
|
|
|
+ // 该客服已经上线
|
|
|
|
+ customer.logout_time = this.addTime();
|
|
|
|
+ await customer.save();
|
|
|
|
+ } else {
|
|
|
|
+ // 没上线
|
|
|
|
+ const obj = { ...query, status: '0', logout_time: this.addTime() };
|
|
|
|
+ customer = await this.status.create(obj);
|
|
|
|
+ }
|
|
|
|
+ return customer;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 客户咨询,查找指定项目的空闲客服,如果没有,那就找到所有客服,
|
|
|
|
+ * 并且找其同时接待最少的客服,让他接待
|
|
|
|
+ * @param {Object} query 参数
|
|
|
|
+ */
|
|
|
|
+ async clientApply(query) {
|
|
|
|
+ const { _tenant, client_id, client_name } = query;
|
|
|
|
+ assert(_tenant, '缺少项目标识');
|
|
|
|
+ assert(client_id, '缺少客户信息');
|
|
|
|
+ const freeList = await this.status.find({ _tenant, status: '0' });
|
|
|
|
+ let customer;
|
|
|
|
+ if (freeList.length > 0) {
|
|
|
|
+ customer = _.head(freeList);
|
|
|
|
+ // 修改状态,加次时间
|
|
|
|
+ customer.status = '1';
|
|
|
|
+ customer.logout_time = this.addTime();
|
|
|
|
+ await customer.save();
|
|
|
|
+ } else {
|
|
|
|
+ // 没有闲的了,那就找不闲的
|
|
|
|
+ let list = await this.status.find({ _tenant });
|
|
|
|
+ if (list.length > 0) {
|
|
|
|
+ list = JSON.parse(JSON.stringify(list));
|
|
|
|
+ for (const i of list) {
|
|
|
|
+ const { customer_id } = i;
|
|
|
|
+ const num = await this.record.count({ customer_id, _tenant });
|
|
|
|
+ i.num = num;
|
|
|
|
+ }
|
|
|
|
+ list = _.orderBy(list, [ 'num' ], [ 'asc' ]);
|
|
|
|
+ customer = _.head(list);
|
|
|
|
+ customer.status = '1';
|
|
|
|
+ customer.logout_time = this.addTime();
|
|
|
|
+ await this.customerLog({ _tenant, customer_id: customer.customer_id });
|
|
|
|
+ } else return '当前没有客服在线';
|
|
|
|
+ }
|
|
|
|
+ // 添加记录
|
|
|
|
+ const obj = { customer_id: customer.customer_id, customer_name: customer.customer_name, _tenant, client_id, client_name };
|
|
|
|
+ let record = await this.record.findOne(obj);
|
|
|
|
+ if (record) {
|
|
|
|
+ record.out_time = this.addTime();
|
|
|
|
+ await record.save();
|
|
|
|
+ } else {
|
|
|
|
+ obj.out_time = this.addTime();
|
|
|
|
+ record = await this.record.create(obj);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this.mq) {
|
|
|
|
+ const exchange = _tenant;
|
|
|
|
+ const routeKey = `customer.${customer.customer_id}`;
|
|
|
|
+ const mqBody = 'research';
|
|
|
|
+ const param = { durable: true };
|
|
|
|
+ await this.mq.topic(exchange, routeKey, mqBody, param);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return record;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 客户续时
|
|
|
|
+ * @param {Object} query 参数
|
|
|
|
+ */
|
|
|
|
+ async clientContinue(query) {
|
|
|
|
+ const { _tenant, client_id, customer_id } = query;
|
|
|
|
+ assert(_tenant, '缺少项目标识');
|
|
|
|
+ assert(client_id, '缺少客户信息');
|
|
|
|
+ assert(customer_id, '缺少客服信息');
|
|
|
|
+ const record = await this.record.findOne({ _tenant, client_id, customer_id });
|
|
|
|
+ if (!record) return '服务已经结束';
|
|
|
|
+ record.out_time = this.addTime();
|
|
|
|
+ const r = await record.save();
|
|
|
|
+ if (r) {
|
|
|
|
+ // 同时给客服续航
|
|
|
|
+ await this.customerLog({ _tenant, customer_id });
|
|
|
|
+ }
|
|
|
|
+ return 'ok';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 结束服务
|
|
|
|
+ async serverEnd(query) {
|
|
|
|
+ const { _tenant, client_id, customer_id } = query;
|
|
|
|
+ assert(_tenant, '缺少项目标识');
|
|
|
|
+ assert(client_id, '缺少客户信息');
|
|
|
|
+ assert(customer_id, '缺少客服信息');
|
|
|
|
+ await this.record.deleteOne({ _tenant, client_id, customer_id });
|
|
|
|
+ this.checkStatus();
|
|
|
|
+ return 'ok';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 查询并修改客服状态
|
|
|
|
+ async checkStatus() {
|
|
|
|
+ const list = await this.status.find();
|
|
|
|
+ for (const i of list) {
|
|
|
|
+ const num = await this.record.count({ customer_id: i._id });
|
|
|
|
+ if (num <= 0) {
|
|
|
|
+ i.status = '0';
|
|
|
|
+ i.save();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 定时清理
|
|
|
|
+ async clear() {
|
|
|
|
+ // 所有状态和工作记录,对比失效时间,过了就删了
|
|
|
|
+ console.log('in function:clear');
|
|
|
|
+ await this.status.deleteMany({ logout_time: { $lte: moment().format('YYYY-MM-DD HH:mm:ss') } });
|
|
|
|
+ // await this.record.deleteMany({ out_time: { $lte: moment().format('YYYY-MM-DD HH:mm:ss') } });
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 续
|
|
|
|
+ addTime() {
|
|
|
|
+ const t = moment().add(this.time, this.unit).format('YYYY-MM-DD HH:mm:ss');
|
|
|
|
+ return t;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+module.exports = CustomerService;
|