1、流程:用户在公众号发送文本消息,通过公众号消息模板推送通知给客服,客服点击通知,跳转小程序与用户对话。用户可以直接在公众号内与客服对话。
2、所用技术栈:
"php": "^7.3|^8.0",
"laravel/framework": "^8.54",
"overtrue/laravel-wechat": "^6.0",
"overtrue/wechat": "~5.0",
"workerman/gateway-worker": "^3.0",
"workerman/workerman": "^4.0",
3、首先截获用户在公众号发送的消息(workerman搭建不再赘述,详情可查看官方文档)
WechatController.php
public function wechetCheck()
{
$app = Factory::officialAccount($this->config);
$app->server->push(function ($message) use ($app) {
switch ($message['MsgType']) {
case 'event' :
{
// do something
}
break;
case 'text':
{
//检查用户是否咨询过,咨询过则用原message_id
$openId = $message['FromUserName'];
$LogServerMessage = new LogServerMessage();
$checkMessage = $LogServerMessage->getIdByOpenId($openId);
if (!$checkMessage) {
$messageId = $LogServerMessage->newMessage($openId)->id;
} else {
$messageId = $checkMessage->id;
}
$LogServer = new LogServer();
$serverRes = $LogServer->addLog($messageId, $openId, 0, $message['Content']);
// 发送消息模板,按自己的实际情况编写
// do something
}
//由于我的服务器同时运行了多个workerman,需要指定registerAddress发送消息
Gateway::$registerAddress = '127.0.0.1:1237';
Gateway::sendToGroup("server_{$messageId}", json_encode([
'type' => 'message',
'content' => $message['Content'],
'id' => $serverRes->id
]));
return "消息提示";
}
}
});
return $app->server->serve();
}
//发送消息给用户,配置路由为 /server-message-list
public function sendServerMsgToUser(Request $request)
{
$messageId = $request->input('message_id');
$content = $request->input('content');
$toUserOpenId = $request->input('to_user_open_id');
$fromUserOpenId = $request->input('from_user_open_id');
$app = Factory::officialAccount($this->config);
$app->customer_service->message($content)->to($toUserOpenId)->send();
$LogServer = new LogServer();
$server = $LogServer->addLog($messageId, $fromUserOpenId, 1, $content);
$data['status'] = 'success';
$data['content'] = $server->id;
return $data;
}
LogServerMessage.php(表结构很简单,就只保存open_id)
public function newMessage($openId)
{
return $this->create([
'open_id' => $openId
]);
}
public function getIdByOpenId($openId)
{
return $this->where('open_id', $openId)->first(['id']);
}
LogServer.php(记录消息详情)
/***
*$userType 0:用户;1:客服;
***/
public function addLog($messageId, $openId, $userType, $content)
{
return $this->create([
'message_id' => $messageId,
'open_id' => $openId,
'user_type' => $userType,
'content' => $content
]);
}
public function getListByMessageId($messageId)
{
return $this->where('message_id', $messageId)->orderBy('id', 'asc')->get(['id', 'user_type', 'content', 'open_id']);
}
4、前端连接websocket,我这里使用的是uniapp
<template>
<view class="h-100 w-100 p-1">
<scroll-view class="content-wrap bg p-1" :scroll-y="true" :scroll-into-view="scrollViewItem">
<view v-for="(item, index) in list" :key="index" class="w-90p"
:class="[parseInt(item.user_type) === 0 ? '' : 'msg-server-flex']">
<view class="msg border-round border p-05 m-b-05" :id="`msg_${item.id}`"
:class="[parseInt(item.user_type) === 0 ? 'msg-customer' : 'msg-server']">
<span>
{{ item.content }}
</span>
</view>
</view>
</scroll-view>
<view class="flex flex-center flex-col footer w-90p">
<u-input :border="true" style="width: 100%" type="textarea" v-model="content"/>
<u-button :custom-style="longPop" size="mini" type="primary-color" :hair-line="false" @click="sendMessage">
发送
</u-button>
</view>
</view>
</template>
<script>
export default {
name: "index",
data() {
return {
messageId: '',
list: [],
user: this.$common.getUserInfo(),
content: '',
longPop: {
width: '100%',
margin: '0.5rem auto'
},
toUserOpenId: '',
fromUserOpenId: '',
scrollViewItem: ''
}
},
methods: {
getMessage() {
const data = {
uid: this.user.uid,
token: this.user.token,
message_id: this.messageId
}
this.$ajax.post(`${this.$url}/server-message-list`, data).then(res => {
this.list = []
if (res.data.status === 'success') {
this.list = res.data.content.list
this.toUserOpenId = res.data.content.open_id
this.fromUserOpenId = res.data.content.server_open_id
this.scrollToBottom(this.list[this.list.length - 1].id)
}
})
},
sendMessage() {
const data = {
uid: this.user.uid,
token: this.user.token,
content: this.content,
to_user_open_id: this.toUserOpenId,
from_user_open_id: this.fromUserOpenId,
message_id: this.messageId
}
this.$ajax.post(`${this.$url}/send-server-msg-to-user`, data).then(res => {
this.list.push({
content: this.content,
user_type: 1,
id: res.data.content
})
this.content = ''
this.scrollToBottom(res.data.content)
})
},
scrollToBottom(id){
this.scrollViewItem = `msg_${id}`
}
},
onl oad(option) {
this.messageId = option.message_id
this.getMessage()
uni.connectSocket({
url: `wss://your-wesocket?message_id=${this.messageId}`,
success: res => console.log('success:', res),
error: res => console.log('error:', res),
complete: res => console.log('complete:', res),
})
const _this = this
uni.onSocketMessage(function (res) {
const data = JSON.parse(res.data)
if (data.type === 'message') {
_this.list.push({
content: data.content,
user_type: 0,
id: data.id
})
_this.scrollToBottom(data.id)
}
});
}
}
</script>
<style scoped lang="scss">
@import "src/static/style/common.scss";
.bg {
background-color: #fcfcfc;
}
.p-1 {
padding: 25rpx;
}
.h-100 {
height: 100vh;
}
.w-100 {
width: 100vw;
}
.w-90p {
width: 90%;
}
.p-05 {
padding: 15rpx;
}
.m-b-05 {
margin-bottom: 15rpx;
}
.border-round {
border-radius: 5px;
}
.border{
border: 1px solid #efefef;
}
.flex {
display: flex;
}
.flex-center {
justify-content: center;
align-items: center;
}
.flex-col {
flex-direction: column;
}
.content-wrap {
height: calc(100vh - 300rpx);
overflow-y: auto;
}
.footer {
position: fixed;
left: 5%;
bottom: 30rpx;
background-color: white;
}
.msg {
max-width: 45%;
display: inline-block;
}
.msg-customer {
background-color: white;
}
.msg-server {
background-color: $green;
color: white;
}
.msg-server-flex {
display: flex;
justify-content: flex-end;
}
</style>