准备账号
- 已微信认证的公众号(需要使用到客服消息):https://mp.weixin.qq.com/
- FastGpt账号,及知识库:https://fastgpt.run.
- Laf账号: https://laf.run/
Step1: Laf 准备
1. 进入Laf官网,注册账号
2. 新建应用,直接新建免费的进行测试
3. 复制代码
直接复制,先不需要改动任何内容
import cloud from '@lafjs/cloud';import * as crypto from 'crypto';
// 公众号配置
const appid ='wxb1833715d8f0809d'
const appsecret ='fd76ce714a8083112100c2160b2f2c5d'
const wxToken ='test';
// fastgpt配置
const apikey ="63f9a14228d2a688d8dc9e1b-skmzjonmv1gyno2iyky1z"
const modelId ="642adec15f01d67d4613efdb"
// 创建数据库连接并获取Message集合
const db = cloud.database();
const _ = db.command
const Message = db.collection('messages')
// 处理接收到的微信公众号消息
export async function main(event){
// const res = await cloud.fetch.post(` https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${await getAccess_token()}`, {
// button: [
// {
// "type":"click",
// "name":"清空记录",
// "key":"CLEAR"
// },
// ]
// })
const { signature, timestamp, nonce, echostr }= event.query;
// 验证消息是否合法,若不合法则返回错误信息
if(!verifySignature(signature, timestamp, nonce, wxToken)){return'Invalid signature';}
// 如果是首次验证,则返回 echostr 给微信服务器
if(echostr){return echostr;}
// -------------- 正文开始
const payload = event.body.xml;
const sessionId = payload.fromusername[0]
console.log(payload)
// 点击了清空记录
if(payload.msgtype[0]==='event'&& payload.eventkey[0]==='CLEAR'){
console.log(1111)
await Message.where({ sessionId: sessionId }).remove({ multi: true})
await replyBykefu('记录已清空', sessionId)return'clear record'}
// 仅做文本消息例子
if(payload.msgtype[0]!=='text')return'no text'
const newMessage ={
msgid: payload.msgid[0],
question: payload.content[0].trim(),
username: payload.fromusername[0],
sessionId,
createdAt: Date.now()}
await replyText(newMessage, payload.fromusername[0])return'success'}
// 处理文本回复消息
async function replyText(message, touser){
const { question, sessionId, msgid }= message;
// 重复的内容,不回复
const { data: msg }= await Message.where({ msgid: message.msgid }).getOne()if(msg)return
console.log("收到用户消息", touser, message)
// 立即添加一条待回复记录
await Message.add(message);
// 回复提示
await replyBykefu("🤖机器人正在思考🤔中...", sessionId)
await changesState(sessionId)
const reply = await getFastGptReply(question, sessionId);
const { answer }= reply;
await Message.where({ msgid: message.msgid }).update({
answer,
});
// return answer;
await replyBykefu(answer, touser)}
// 获取微信公众号ACCESS_TOKEN
async functiongetAccess_token(){
const shared_access_token = await cloud.shared.get("mp_access_token")if(shared_access_token){if(shared_access_token.exp > Date.now()){return shared_access_token.access_token
}}
// ACCESS_TOKEN不存在或者已过期
// 获取微信公众号ACCESS_TOKEN
const mp_access_token = await cloud.fetch.get(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${appsecret}`)
cloud.shared.set("mp_access_token", {
access_token: mp_access_token.data.access_token,
exp: Date.now() + 7100 * 1000})return mp_access_token.data.access_token
}
// 公众号客服回复文本消息
export async function replyBykefu(message, touser){
// 判断是否为中文字符
function isChinese(char){return /[\u4e00-\u9fa5]/.test(char) // 判断是否是中文字符
}
// 拆分文本长度
function splitText(text){let result =[]let len = text.length
let index =0while(index < len){let part =''let charCount =0while(charCount <800&& index < len){let char = text[index]
charCount++
part += char
if(isChinese(char)) charCount++ // 中文字符计数+1
index++
}
result.push(part)}return result
}
// 定义休眠函数
function sleep(ms){return new Promise(resolve => setTimeout(resolve, ms))};
const access_token = await getAccess_token()let text = splitText(message)let len = splitText(message).length
try{for(let i =0; i < len; i++){let part = text[i] // 获取第 i 段
await sleep(1000)
// 回复消息
const res = await cloud.fetch.post(`https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${access_token}`, {"touser": touser,
"msgtype":"text",
"text":{"content": part
}})}}catch(err){
console.log(err)}}
// 修改公众号回复状态
export async function changesState(touser){
const access_token = await getAccess_token()
// 修改正在输入的状态
const res = await cloud.fetch.post(`https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=${access_token}`, {"touser": touser,
"command":"Typing"})}
// 校验微信服务器发送的消息是否合法
exportfunction verifySignature(signature, timestamp, nonce, token){
const arr =[token, timestamp, nonce].sort();
const str = arr.join('');
const sha1 = crypto.createHash('sha1');
sha1.update(str);return sha1.digest('hex')=== signature;}
// 返回组装 xml
exportfunction toXML(payload, content){
const timestamp = Date.now();
const { tousername: fromUserName, fromusername: toUserName }= payload;return`<xml><ToUserName><![CDATA[${toUserName}]]></ToUserName><FromUserName><![CDATA[${fromUserName}]]></FromUserName><CreateTime>${timestamp}</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[${content}]]></Content></xml>`}
// 调用 fastgpt 回答
async function getFastGptReply(question, sessionId){
const res = await db.collection('messages')
.where({ sessionId })
.get()
// 获取最多10组上下文
const list = res.data.slice(-10)
const prompts = list.map((item)=>[{
obj: "Human",
value: item.question ||''}, {
obj: "AI",
value: item.answer ||''}]).concat({
obj: "Human",
value: question
}).flat()
const config ={
method: 'post', // 设置请求方法为POST
url: 'https://fastgpt.run/api/openapi/chat/chat', // 设置请求地址
headers: { // 设置请求头信息
apikey,
'Content-Type':'application/json'},
data: { // 设置请求体数据
modelId,
isStream: false,
prompts
}}
try {
const ret = await cloud.fetch(config)
console.log("fastgpt响应", ret.data)return{ answer: ret.data.data || ret.data ||''}} catch (e){
console.log("出错了", e.response)return{
error: "问题太难了 出错了. (uДu〃).",
}}}
5.点击发布
6.复制地址
Step2 公众号准备
务必是要认证后的公众号,否则无效。我没有认证的号,所以视频是用测试号展示的。下面截图是完整步骤:
1.给公众号设置laf地址
2. 获取wx appId和secret
3. 验证laf地址
把前面两步获取到的3个内容,填写到laf
Step3 FastGpt准备
1. 获取apikey
2. 获取modelId
3. 替换Laf变量
测试
给公众号发送一条消息,看是否有回复。
QA
发送消息后无响应
先去laf日志检查是否收到用户消息,有下面的提示代表正常。 可能需要点下搜索才能刷新出来。
如果收到了消息,但是没有回复,八成是公众号没有发送客服消息权限。对应是下图的权限
私信加入技术讨论群
版权归原作者 DevonL77 所有, 如有侵权,请联系我们删除。