

# 客服聊天流程

对象信息：

- 聊天消息
- 用户与客服Map
- 在线客服Map

## 用户为第一人称

* 用户连接WebSocket：
  * 分配拥有权限客服：
    * 有
      * 获取拥有权限客服列表
      * 分配负担轻的客服绑定用户session
      * 用户与客服Map+1
    * 无
      * 用户与客服Map+1
  * **前端**应主动获取与客服```聊天消息```（分页查询，未读聊天记录接收时间为空，sql查询应为创建时间降序）
  * **前端**获取```聊天消息```后应告诉**服务端**哪些未读```聊天消息```被接收以保证消息确认被接收，更新```聊天消息```接收时间。
  
* **用户端**发送```聊天消息```给**客服端**：
  
  * 有客服session：
  
    * 消息存入```Redis```、```MySql```
  
    * 通过客服session发送```聊天消息```：
      * 默认以**客服端**未接收到```聊天消息```，将```聊天消息```添加至```RabbitMq```队列，并设置延迟通知时间及重发次数
        * 接收到订阅消息，查询```Redis```是否该条```聊天消息```
          * ```Redis```无记录或```Redis```有记录且接收时间不为空
            * 确认该条订阅消息，不再重发
          * ```Redis```有记录且无接收时间
            * 则重新发送```聊天消息```给**客服端**
            * 不确认该订阅消息并让```RabbitMq```延迟通知时间及重发次数-1
        
        * 若重发次数达到阈值，则将```聊天消息```从```Redis```移除
        
      * **客服端**回应```聊天消息```成功接收，更新```Redis```中该条```聊天消息```的接收时间
  
      * 通知**用户端**```消息已读```，默认以**用户端**未接收到```消息已读```为基础，将```消息已读```添加至```RabbitMq```队列，并设置延迟通知时间及重发次数
        * 接收到订阅消息，查询```Redis```是否该条```聊天消息```
          * ```Redis```无记录
            * 确认该条订阅消息，不再重发
          * ```Redis```有记录
            * 则重新发送```已读消息```已读给**用户端**
            * 不确认该订阅消息并让```RabbitMq```延迟通知时间及重发次数-1
          * 若重发次数达到阈值，更新```MySql```（更新接收时间）并将```聊天消息```从```Redis```移除
        
      * **用户端**确认接收```消息已读```后将```Redis```中的该条```聊条消息```保存至```MySql```并从```Redis```中移除
  
  * 无客服session：
    * 消息存入```MySql```，等待**客服端**上线接收所有该**客服端**拥有权限的消息（如：机票、酒店等）

## 客服为第一人称

* 客服连接WebSocket：
  * 遍历**用户与客服Map**，找出未被分配客服的用户并将客服session更新至**用户与客服Map**中（注意客服是否拥有权限）
  * 此时**前端**应主动获取与用户```聊天消息```（分页查询，未读聊天记录接收时间为空，sql查询应为创建时间降序）
  * 获取```聊天消息```后应告诉**服务端**哪些未读```聊天消息```被接收以保证消息确认被接收，更新```聊天消息```客服id、接收时间。
  * **客服端**发送```聊天消息```给**用户端**
    * 用户在线：
      * 同用户发送消息
    * 用户不在线：
      * 同用户发送消息

## 表：chat_record（聊天记录）：

| 字段名        | 字段类型 | 可空 | 说明                              |
| ------------- | -------- | ---- | --------------------------------- |
| id            | bigint   | 否   | 主键                              |
|  |  |  |  |
| user_id       | bigint   | 否   | 用户id                            |
| staff_id      | bigint   | 否   | 员工（客服）id                    |
| msg_id | varchar(125) | 否 | 消息Id（前端生成） |
| msg_tpe       | tinyint  | 否   | 消息类型<br />0：文字消息，1：pdf |
| msg_info    | text     | 否   | 消息内容                          |
| send_receive  | tinyint   | 否   | 收或发（用户为第一人称）          |
| scope         | tinyint  | 否   | 内容范围<br />机票、酒店等        |
| send_time | datetime | 否   | 发送时间（前端设置）                |
| received_time | datetime | 是   | 接收时间（前端设置）                    |
| create_time | datetime | 否 | 创建时间（服务端设置） |
| modify_time | datetime | 是 | 修改时件（服务端设置） |

## 用户与客服Map：

|       |                         | 数据类型             | 说明                    |
| ----- | ----------------------- | -------------------- | ----------------------- |
| key   | ```ChatRecord.userId``` | ```Long```           | 用户ID                  |
| value | ```UserSocketInfo```    | ```UserSocketInfo``` | ```WebSocketInfo```对象 |

## UserSocketInfo：

| 字段名  | 数据类型         | 说明                       |
| ------- | ---------------- | -------------------------- |
| channel | NioSocketChannel | 用户WebSocket连接          |
| staffId | Long             | 客服ID                     |
| userId  | Long             | 用户ID                     |
| scope   | Short            | 内容范围<br />机票、酒店等 |

## 在线客服Map：

|       |                          | 数据类型              | 说明                      |
| ----- | ------------------------ | --------------------- | ------------------------- |
| key   | ```ChatRecord.staffId``` | Long                  | 客服ID                    |
| value | ```StaffSocketInfo```    | ```StaffSocketInfo``` | ```StaffSocketInfo```对象 |

## StaffSocketInfo：

| 字段名  | 数据类型         | 说明                                     |
| ------- | ---------------- | ---------------------------------------- |
| staffId | Long             | 客服ID                                   |
| channel | NioSocketChannel | 客服WebSocket连接                        |
| userIds | Set<Long>        | 用于保存被分配的用户ID<br />用于客服分配 |

## 流程图未完成！

```flow
start=>start: 连接WebSocket
end=>end: 断开WebSocket
addMap=>operation: 用户在线Map + 1
getService=>condition: 根据聊天类型标识分配客服
saveMsg=>operation: 通过RabbitMq异步保存消息状态（未读状态）
addMsg2Mq=>operation: 将消息发布至RabbitMq
sendMsg2Service=>operation: 获取客服Session发送消息
checkMsg=>condition: 消息是否发送成功

start->addMap
addMap->end
```
