客服工作台的实践总结

文摘   2024-10-18 09:01   北京  

客服工作台是什么?它能做解决什么问题?系统设计很复杂吗?带着这些问题,我们一起揭开转转客服工作台的面纱。

  • 系统整体介绍
  • 客服 IM 与第三屏数据通信
  • 第三屏多页签
  • 客服会话缓存
  • 全埋点统计
  • 最后的思考

前言

随着我司业务的拓展,用户咨询或反馈问题的场景和诉求也越来越多,客服团队不断壮大。客服工作台是客服团队用来解答和处理用户问题的操作平台。客服团队分为一线和二线,其中一线客服(后面统称**在线客服**)主要接待用户通过客服入口的进线咨询,二线客服主要通过信息查询、电话外呼等方式处理工单流转进一步解决用户问题。本文提到的客服工作台特指为在线客服服务的系统。

一、系统整体介绍

在线客服包括两部分,图中的左侧两屏属于客服与用户的聊天区域(后面统称客服 IM),其中第一屏展示客服的部分重要服务指标(满意度、首解率和接待量)、客服基本信息(昵称/头像/状态/时长)、当前会话和历史会话列表,第二屏为具体某个会话的聊天内容,而图中的右侧为当前会话对应的所有重要信息(后面统称第三屏),包括用户信息、订单信息、工单信息、售后信息、知识库等。

1.1 客服 IM

客服 IM 采用 iframe 内嵌的独立系统,主要负责当前会话的实时通讯、历史会话的消息展示、与第三屏数据通信的能力,后面会介绍客服 IM 与第三屏数据通信的消息分类和实现。

1.2 第三屏

第三屏是在线客服工作台至关重要的部分,因为它承载了大部分协助客服解答用户问题的信息。在系统实现层面,第三屏有两个核心的问题要解决——多页签会话缓存。多页签满足客服打开不同页面,并完成信息查阅和操作的需要,会话缓存是客服在同时处理多个进线且需要来回切换会话时,缓存不同会话状态的技术。

同时为了分析系统设计对于客服费力度(评价使用某个产品、服务来解决问题的困难程度)的影响,需要对在线客服工作台做用户行为的采集和分析。

二、客服 IM 与第三屏数据通信

上面提到第三屏需要提供解决问题的相关信息,但这些信息需要和进线的用户相关,比如用户信息、用户进线时咨询的订单或商品、用户命中的售后单或工单等。那么就需要 iframe(客服 IM)和外层系统(工单系统)通过 postMessage 进行数据通信。

以工单系统收到消息为例,看下第三屏的代码设计。

第三屏部分将数据通信和视图进行了隔离,Message 为通信层,IframeImpage 为视图层。通信层对 iframe 的 postMessage 消息进行监听收发,并将这些消息存储在 model 中共享和管理数据。

// 在线客服页面的入口
import React from 'react'
import Message from './message'
import IframeImPage from './IframePage'
import type { IframeImPagePropsType } from './@types'

// Message为通信层,IframeImpage为视图层
const IframeIndex: React.FC<IframeImPagePropsType> = (props) => {
  return (
    <Message {...props}>
      <IframeImPage location={props?.location} />
    </Message>

  )
}
export default IframeIndex
// Message
import React, { useRef, useEffect, useState } from 'react'
import { useModel } from 'umi'
import { PostMsg } from '@/utils/PostMsg'
import { useDebounceFn } from 'ahooks'
import type { CovCoreInfoType } from '@/models/@types'
import type {
  IframeImPagePropsType,
  PcimPostMsgContent
from './@types'
const IframeImPage: React.FC<IframeImPagePropsType> = ({ children, location, history }) => {
    const {
      ...
      setCovCoreInfo,
      ...
   } = useModel<'imWorkPlateform'>('imWorkPlateform')
  // 监听postMessage消息
  const chatWrap = useRef<PostMsg>(new PostMsg(handleOpenChatWrap, 'createWrap'))
  // 避免客服连续快速切换--防抖200ms
  const { run: handleOpenChatWrap } = useDebounceFn(handleOpenChatWrapFn, {
    wait200
  })
  // 点击用户会话,打开对应的第三屏
  const handleOpenChatWrapFn = async(content: PcimPostMsgContent): Promise<void> => {
    const {
      convId,
      contactUid,
      ...
    } = content
    ... ...
    const targetUrl: string = `/home?uid=${contactUid}***`
    // 在第三屏打开指定路由页面
    handleAddChangeTab({
      url: targetUrl,
      covCoreInfoData: {
        convId,
        contactUid,
        ...
      }
    })
    // model保存会话信息
    setCovCoreInfo({
      convId,
      contactUid,
      ...
    })
  }

  useEffect(() => {
    ...
    return () => {
      // 注销postMessage监听
      chatWrap.current?.destroyPostMsg && chatWrap.current?.destroyPostMsg()
    }
  })

 return <div>{children ? children : null}</div>
}

三、第三屏多页签

针对 umi 多页签的实现,我们采用的是 antd 的 Tabs 组件,而具体需要渲染哪些页面,则需要通过自定义路由实现。目前多页签实现的功能如下:

功能
关闭页签
刷新当前页
更新页面 title
页签数量最大数
支持动态参数
常驻页面配置

从下面代码实现中可以看到,我们仿照 umi 写了一份路由对象和解析渲染的方法,把即将打开的页面地址,与路由中的 path 进行匹配,拿到对应的 component,交由 TabPannel 进行组件渲染。也可以参考《umi4.0 多页签设计》

//自定义路由
const baseConfig = [
  {
    title'首页',
    component(params: UserInfoParamsType) => import('../components/home/index'),
    path'/home',
    keepAlivePermanentTabtrue
  },
  {
    title'推荐信息',
    component(params: UserInfoParamsType) => import('../components/NewHome/index'),
    path'/newhome',
    keepAlivePermanentTabtrue
  },
  ...
]

// 多页签实现RouterView.tsx
const RouterView: React.FC<RouteViewParamsType> = (props) => {
 return (
    <div>
      <Tabs>
        {panels.map((curPathKey, idx) => {
          return (
           <Tabs.TabPane>
             // View是动态获取的路由中的组件
               <Bundle component={View} />
            </Tabs.TabPane>
          )
        })}
      </Tabs>
    </div>

}

// Bundle.tsx
import React from 'react'
import { useRef, useEffect, useState } from 'react'
import ErrorBoundary from './error-boundary'
import type { BundleParamsType } from '../../@types'

const Bundle: React.FC<BundleParamsType> = (props) => {
  const [CompCache, setCompCache] = useState<any>(null)
  const initCompCache = async() => {
    if (typeof props.component === 'function' && props.component() instanceof Promise) {
      const CurComp = await props.component()
      setCompCache(
        <ErrorBoundary type={2}>
          <CurComp.default {...props} />
        </ErrorBoundary>

      )
    } else {
      const CurComp = props.component
      setCompCache(
        <ErrorBoundary type={2}>
          <CurComp {...props} />
        </ErrorBoundary>

      )
    }
  }
  useEffect(() => {
    initCompCache()
  }, [props.component])

  return CompCache ? CompCache : null
}

export default Bundle

四、客服会话缓存

先解释下为什么需要缓存会话。根据客服的熟练程度,可能出现 1Vn 的服务场景,即 1 个客服同时与 n 个用户进行沟通会话,那么第三屏信息必然会频繁的切换。客服在与每个用户沟通中操作的页面交互状态,需要在切换中保持不变。否则,客服又得重新打开或查询重复的信息,极大增加了操作费力度。因此系统需要具备能将数据和交互(即会话数据)缓存下来的能力。

以下是会话缓存的示意图。

图中包括四部分:LRU 操作、即将更新的会话数据、第三屏的渲染区域、当前会话数据;

  • LRU是我们常见的通过链表实现的最大数值为n的缓存算法;
  • 会话数据分为当前会话数据和即将更新的会话数据,包含以下四部分信息:
    • 包含一个基于会话id的缓存唯一值cacheKey
    • 以及代表第三屏渲染的真实dom的childern
    • 用于动态挂载第三屏真实dom的 $ref(也就是图中的最右侧renderRef)
    • 用于挂载 conRef
  • 第三屏的渲染区域是一个固定的dom,它会随着会话的切换,替换为对应cacheKey的dom容器。

现在简单说下切换会话的时序逻辑。

  • ①表示将第三屏渲染的dom挂载回到所属缓存的会话数据
  • ② 表示将当前会话重新更新到 LRU 中
  • ③ 表示新建或切换会话时,基于新的会话 id,创建或从 LRU 中获取会话数据
  • ④ 表示把即将更新的会话数据的挂载容器替换第三屏当前的容器,实现动态挂载真实 dom
  • ⑤ 表示将新会话数据更新到 LRU 中。

最终的动态挂载真实 dom,在 KeepAliveScope 组件中完成(见下面代码)。第三屏的 dom 数据是从 LRU 缓冲中获取,并通过 createPortal 渲染在 renderRef 对应的实际节点上。

// IframeImPage 第三屏代码
import { KeepAliveScope, KeepAlive } from '@/components/KeepAlive'
const IframeImPage: React.FC<IframeImPagePropsType> = (props) => {
  return <KeepAliveScope>
    <KeepAlive name="iframeIMNew" cacheKey={covCoreInfo.childConvId} maxLimit={5}>
        <RootRouter
          url={covCoreInfo.fullUrl}
          needReplace={needReplace}
          keepAliveCacheKey={covCoreInfo.childConvId}
         />

      </KeepAlive>
  </KeepAliveScope>

}

export default IframeImPage


// KeepAliveScope
const KeepAliveScope: React.FC<KeepAliveScopePropsType> = ({ dispatch, keepAlive, children }) => {
  return   return (
    <KeepAliveContext.Provider value={contextState}>
      <>
        {/* 子内容渲染 */}
        {children}

        {/* 渲染每个缓存空间的内容 */}
        {Object.keys(keepAlive.cache).map((namespace: string) => {
          // 渲染缓存内容
          return keepAlive.cache[namespace].map((data: LRUDataType) => {
            // 每个缓存内容渲染到独立的容器中(第三个参数为key)
            return ReactDOM.createPortal(
              <div data-cache-namespace={namespace} data-cache-key={data.key}>
                {data.val.children}
              </div>,
              data.val.$ref,
              `${namespace}_${data.key}`
            )
          })
        })}
      </>

    </KeepAliveContext.Provider>
  )
}

export default connect(({ keepAlive }: { keepAlive: KeepAliveStateType }) => ({
  keepAlive
}))(KeepAliveScope)

五、全埋点统计

作为服务客服的产研团队,我们希望能提供更利于客服操作的系统,其中操作费力度就是我们参考的重要指标,基于此目标,也在不断的迭代和升级产品。

费力度的数据表现有很多维度,本篇文章以客服操作的次数来介绍具体的技术实现。比如客服从接待用户进线到最后创建工单的整个操作周期中,客服点击页面的次数。但是由于第三屏数据量过大,我们无法对每个点击事件都进行埋点上报,这无疑是一个很大的工作量,在当下这种场景,全埋点就是一个比较理想的选择。

大致流程:

1.1 通过监听点击事件,导致 dom 变化、请求接口或打开新浏览器页签的行为数据,都需要埋点上报。

1.2 dom 变化、请求接口和开启浏览器页签,通过发布-订阅进行事件通信,触发埋点上报。

这套方案把客服点击操作的场景分为三部分:

  • 点击事件造成 dom 变化的,通过 mutationObserver 监听判断;
  • 点击事件重新开启新系统页签(工单系统中其他页签)或浏览器页签的,或者单纯的切到其他系统页签或浏览器页签的,通过监听 visibilitychange 判断;
  • 点击事件后造成了接口请求的,比如一些加密信息,如手机号、地址等,在客服点击后请求接口可以脱敏展示。

需要说明的一点是,埋点上报的数据中要携带点击区域的位置信息,具体规则是页面-区域-当前元素的文本,这样我们可以推测客服具体操作了什么动作。当然这种方案还是要做一些特殊处理的,比如 iframe 中的客服操作、统计的维度数据——会话 id、弹窗中监听事件等等。

六、最后的思考

在线客服工作台作为一线客服最重要的系统,在收集用户反馈、处理用户问题方面起着举足轻重的作用。本篇文章主要从工作台的核心功能的角度做了介绍。基于目前现状,从系统开发成本、系统扩展灵活性、业务降本增效三个角度看,谈下未来有价值的事情。

1、由于历史原因,客服 IM 是通过 iframe 内嵌的方式存在于工作台系统中,虽然这种方式在目前没有带来明显的功能问题,但开发成本和性能方面,它仍然是一个重要的优化方向。

2、快速提供排查问题的信息、提供统一且便利的交互操作、适配转转各种复杂业务场景的诉求,是工作台扩展灵活性的目标。比如,针对售前售中售后、手机平板奢侈品等不同时机、不同品类的咨询,能提供灵活的 sop 流程操作,对系统的价值提升无疑是巨大的。

3、从业务角度出发,客服工作台除了要助力客服高质量的解决用户问题,还应该高效的解决问题。随着人工智能技术的普及,在客服领域的应用得到大力的施展,工作台目前已经接入 AI 辅助、智能推荐回复等功能,未来系统可以在更多方面拥抱人工智能,大幅提升客服的工作效率。



想了解更多转转公司的业务实践,点击关注下方的公众号吧!


大转转FE
定期分享一些团队对前端的想法与沉淀
 最新文章