Build-in Tencent Wechat channel.
This commit is contained in:
parent
47a94e89b8
commit
dc6db83e57
30
BotSharp.Channel.Weixin/BotSharp.Channel.Weixin.csproj
Normal file
30
BotSharp.Channel.Weixin/BotSharp.Channel.Weixin.csproj
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>Haiping Chen</Authors>
|
||||
<RepositoryUrl>https://github.com/Oceania2018/botsharp-channel-weixin</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageProjectUrl>https://github.com/Oceania2018/botsharp-channel-weixin</PackageProjectUrl>
|
||||
<Copyright>Apache 2.0</Copyright>
|
||||
<PackageTags>botsharp, wechat, wexin, chatbot</PackageTags>
|
||||
<AssemblyVersion>0.1.1.0</AssemblyVersion>
|
||||
<FileVersion>0.1.1.0</FileVersion>
|
||||
<Version>0.1.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.3" />
|
||||
<PackageReference Include="Senparc.Weixin.MP.MVC" Version="7.1.11" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BotSharp.Platform.Dialogflow\BotSharp.Platform.Dialogflow.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
142
BotSharp.Channel.Weixin/Controllers/WeixinAsyncController.cs
Normal file
142
BotSharp.Channel.Weixin/Controllers/WeixinAsyncController.cs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
using BotSharp.Channel.Weixin.Models;
|
||||
using BotSharp.Platform.Abstraction;
|
||||
using BotSharp.Platform.Dialogflow;
|
||||
using BotSharp.Platform.Dialogflow.Models;
|
||||
using BotSharp.Platform.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Senparc.CO2NET;
|
||||
using Senparc.CO2NET.HttpUtility;
|
||||
using Senparc.Weixin.MP;
|
||||
using Senparc.Weixin.MP.Entities.Request;
|
||||
using Senparc.Weixin.MP.MvcExtension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Channel.Weixin.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 此Controller为异步Controller(Action),使用异步线程处理并发请求。
|
||||
/// 为了方便演示,此Controller中没有加入多余的日志记录等示例,保持了最简单的Controller写法。日志等其他操作可以参考WeixinController.cs。
|
||||
/// 提示:异步Controller并不是在任何情况下都能提升效率(响应时间),当请求量非常小的时候反而会增加一定的开销。
|
||||
/// </summary>
|
||||
[Route("weixin")]
|
||||
public class WeixinAsyncController : ControllerBase
|
||||
{
|
||||
readonly Func<string> _getRandomFileName = () => DateTime.Now.ToString("yyyyMMdd-HHmmss") + "_Async_" + Guid.NewGuid().ToString("n").Substring(0, 6);
|
||||
|
||||
readonly IConfiguration config;
|
||||
private IPlatformBuilder<AgentModel> builder;
|
||||
|
||||
public WeixinAsyncController(IPlatformBuilder<AgentModel> platform, IConfiguration configuration)
|
||||
{
|
||||
config = configuration;
|
||||
builder = platform;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public Task<ActionResult> Get(string signature, string timestamp, string nonce, string echostr)
|
||||
{
|
||||
var token = config.GetValue<string>("weixinChannel:token");
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
if (CheckSignature.Check(signature, timestamp, nonce, token))
|
||||
{
|
||||
return echostr; //返回随机字符串则表示验证通过
|
||||
}
|
||||
else
|
||||
{
|
||||
return "failed:" + signature + "," + Senparc.Weixin.MP.CheckSignature.GetSignature(timestamp, nonce, token) + "。" +
|
||||
"如果你在浏览器中看到这句话,说明此地址可以被作为微信公众账号后台的Url,请注意保持Token一致。";
|
||||
}
|
||||
}).ContinueWith<ActionResult>(task => Content(task.Result));
|
||||
}
|
||||
|
||||
public CustomMessageHandler MessageHandler = null;//开放出MessageHandler是为了做单元测试,实际使用过程中不需要
|
||||
|
||||
/// <summary>
|
||||
/// 最简化的处理流程
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Post(PostModel postModel)
|
||||
{
|
||||
var token = config.GetValue<string>("weixinChannel:token");
|
||||
|
||||
if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, token))
|
||||
{
|
||||
return new WeixinResult("参数错误!");
|
||||
}
|
||||
|
||||
postModel.Token = token;
|
||||
postModel.EncodingAESKey = config.GetValue<string>("weixinChannel:encodingAESKey");
|
||||
postModel.AppId = config.GetValue<string>("weixinChannel:appId");
|
||||
|
||||
var messageHandler = new CustomMessageHandler(builder, config, Request.GetRequestMemoryStream(), postModel, 10);
|
||||
|
||||
messageHandler.DefaultMessageHandlerAsyncEvent = Senparc.NeuChar.MessageHandlers.DefaultMessageHandlerAsyncEvent.SelfSynicMethod;//没有重写的异步方法将默认尝试调用同步方法中的代码(为了偷懒)
|
||||
|
||||
#region 设置消息去重
|
||||
|
||||
/* 如果需要添加消息去重功能,只需打开OmitRepeatedMessage功能,SDK会自动处理。
|
||||
* 收到重复消息通常是因为微信服务器没有及时收到响应,会持续发送2-5条不等的相同内容的RequestMessage*/
|
||||
messageHandler.OmitRepeatedMessage = true;//默认已经开启,此处仅作为演示,也可以设置为false在本次请求中停用此功能
|
||||
|
||||
#endregion
|
||||
|
||||
#region 记录 Request 日志
|
||||
|
||||
var logPath = Server.GetMapPath(string.Format("~/App_Data/MP/{0}/", DateTime.Now.ToString("yyyy-MM-dd")));
|
||||
if (!Directory.Exists(logPath))
|
||||
{
|
||||
Directory.CreateDirectory(logPath);
|
||||
}
|
||||
|
||||
//测试时可开启此记录,帮助跟踪数据,使用前请确保App_Data文件夹存在,且有读写权限。
|
||||
messageHandler.RequestDocument.Save(Path.Combine(logPath, string.Format("{0}_Request_{1}_{2}.txt", _getRandomFileName(),
|
||||
messageHandler.RequestMessage.FromUserName,
|
||||
messageHandler.RequestMessage.MsgType)));
|
||||
if (messageHandler.UsingEcryptMessage)
|
||||
{
|
||||
messageHandler.EcryptRequestDocument.Save(Path.Combine(logPath, string.Format("{0}_Request_Ecrypt_{1}_{2}.txt", _getRandomFileName(),
|
||||
messageHandler.RequestMessage.FromUserName,
|
||||
messageHandler.RequestMessage.MsgType)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
await messageHandler.ExecuteAsync(); //执行微信处理过程
|
||||
|
||||
#region 记录 Response 日志
|
||||
|
||||
//测试时可开启,帮助跟踪数据
|
||||
|
||||
//if (messageHandler.ResponseDocument == null)
|
||||
//{
|
||||
// throw new Exception(messageHandler.RequestDocument.ToString());
|
||||
//}
|
||||
if (messageHandler.ResponseDocument != null)
|
||||
{
|
||||
messageHandler.ResponseDocument.Save(Path.Combine(logPath, string.Format("{0}_Response_{1}_{2}.txt", _getRandomFileName(),
|
||||
messageHandler.ResponseMessage.ToUserName,
|
||||
messageHandler.ResponseMessage.MsgType)));
|
||||
}
|
||||
|
||||
if (messageHandler.UsingEcryptMessage && messageHandler.FinalResponseDocument != null)
|
||||
{
|
||||
//记录加密后的响应信息
|
||||
messageHandler.FinalResponseDocument.Save(Path.Combine(logPath, string.Format("{0}_Response_Final_{1}_{2}.txt", _getRandomFileName(),
|
||||
messageHandler.ResponseMessage.ToUserName,
|
||||
messageHandler.ResponseMessage.MsgType)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
MessageHandler = messageHandler;//开放出MessageHandler是为了做单元测试,实际使用过程中不需要
|
||||
|
||||
return new FixWeixinBugWeixinResult(messageHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
BotSharp.Channel.Weixin/LocationService.cs
Normal file
94
BotSharp.Channel.Weixin/LocationService.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/*----------------------------------------------------------------
|
||||
Copyright (C) 2018 Senparc
|
||||
|
||||
文件名:LocationService.cs
|
||||
文件功能描述:地理位置信息处理
|
||||
|
||||
|
||||
创建标识:Senparc - 20150312
|
||||
----------------------------------------------------------------*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Senparc.Weixin.MP.Entities;
|
||||
using Senparc.CO2NET.Helpers.BaiduMap;
|
||||
using Senparc.CO2NET.Helpers.GoogleMap;
|
||||
using Senparc.Weixin.MP.Helpers;
|
||||
using Senparc.CO2NET.Helpers;
|
||||
using Senparc.NeuChar.Entities;
|
||||
|
||||
namespace BotSharp.Channel.Weixin
|
||||
{
|
||||
public class LocationService
|
||||
{
|
||||
public ResponseMessageNews GetResponseMessage(RequestMessageLocation requestMessage)
|
||||
{
|
||||
var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageNews>(requestMessage);
|
||||
|
||||
#region 百度地图
|
||||
|
||||
{
|
||||
var markersList = new List<BaiduMarkers>();
|
||||
markersList.Add(new BaiduMarkers()
|
||||
{
|
||||
Longitude = requestMessage.Location_X,
|
||||
Latitude = requestMessage.Location_Y,
|
||||
Color = "red",
|
||||
Label = "S",
|
||||
Size = BaiduMarkerSize.m
|
||||
});
|
||||
|
||||
var mapUrl = BaiduMapHelper.GetBaiduStaticMap(requestMessage.Location_X, requestMessage.Location_Y, 1, 6, markersList);
|
||||
responseMessage.Articles.Add(new Article()
|
||||
{
|
||||
Description = string.Format("【来自百度地图】您刚才发送了地理位置信息。Location_X:{0},Location_Y:{1},Scale:{2},标签:{3}",
|
||||
requestMessage.Location_X, requestMessage.Location_Y,
|
||||
requestMessage.Scale, requestMessage.Label),
|
||||
PicUrl = mapUrl,
|
||||
Title = "定位地点周边地图",
|
||||
Url = mapUrl
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GoogleMap
|
||||
|
||||
{
|
||||
var markersList = new List<GoogleMapMarkers>();
|
||||
markersList.Add(new GoogleMapMarkers()
|
||||
{
|
||||
X = requestMessage.Location_X,
|
||||
Y = requestMessage.Location_Y,
|
||||
Color = "red",
|
||||
Label = "S",
|
||||
Size = GoogleMapMarkerSize.Default,
|
||||
});
|
||||
var mapSize = "480x600";
|
||||
var mapUrl = GoogleMapHelper.GetGoogleStaticMap(19 /*requestMessage.Scale*//*微信和GoogleMap的Scale不一致,这里建议使用固定值*/,
|
||||
markersList, mapSize);
|
||||
responseMessage.Articles.Add(new Article()
|
||||
{
|
||||
Description = string.Format("【来自GoogleMap】您刚才发送了地理位置信息。Location_X:{0},Location_Y:{1},Scale:{2},标签:{3}",
|
||||
requestMessage.Location_X, requestMessage.Location_Y,
|
||||
requestMessage.Scale, requestMessage.Label),
|
||||
PicUrl = mapUrl,
|
||||
Title = "定位地点周边地图",
|
||||
Url = mapUrl
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
responseMessage.Articles.Add(new Article()
|
||||
{
|
||||
Title = "微信公众平台SDK 官网链接",
|
||||
Description = "Senparc.Weixin.MK SDK地址",
|
||||
PicUrl = "http://sdk.weixin.senparc.com/images/logo.jpg",
|
||||
Url = "http://sdk.weixin.senparc.com"
|
||||
});
|
||||
|
||||
return responseMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
BotSharp.Channel.Weixin/Models/CustomMessageContext.cs
Normal file
51
BotSharp.Channel.Weixin/Models/CustomMessageContext.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*----------------------------------------------------------------
|
||||
Copyright (C) 2018 Senparc
|
||||
|
||||
文件名:CustomMessageContext.cs
|
||||
文件功能描述:微信消息上下文
|
||||
|
||||
|
||||
创建标识:Senparc - 20150312
|
||||
----------------------------------------------------------------*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Senparc.NeuChar.Context;
|
||||
using Senparc.NeuChar.Entities;
|
||||
|
||||
namespace BotSharp.Channel.Weixin.Models
|
||||
{
|
||||
public class CustomMessageContext : MessageContext<IRequestMessageBase, IResponseMessageBase>
|
||||
{
|
||||
public CustomMessageContext()
|
||||
{
|
||||
base.MessageContextRemoved += CustomMessageContext_MessageContextRemoved;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 当上下文过期,被移除时触发的时间
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
void CustomMessageContext_MessageContextRemoved(object sender, Senparc.NeuChar.Context.WeixinContextRemovedEventArgs<IRequestMessageBase, IResponseMessageBase> e)
|
||||
{
|
||||
/* 注意,这个事件不是实时触发的(当然你也可以专门写一个线程监控)
|
||||
* 为了提高效率,根据WeixinContext中的算法,这里的过期消息会在过期后下一条请求执行之前被清除
|
||||
*/
|
||||
|
||||
var messageContext = e.MessageContext as CustomMessageContext;
|
||||
if (messageContext == null)
|
||||
{
|
||||
return;//如果是正常的调用,messageContext不会为null
|
||||
}
|
||||
|
||||
//TODO:这里根据需要执行消息过期时候的逻辑,下面的代码仅供参考
|
||||
|
||||
//Log.InfoFormat("{0}的消息上下文已过期",e.OpenId);
|
||||
//api.SendMessage(e.OpenId, "由于长时间未搭理客服,您的客服状态已退出!");
|
||||
}
|
||||
}
|
||||
}
|
||||
380
BotSharp.Channel.Weixin/Models/CustomMessageHandler.cs
Normal file
380
BotSharp.Channel.Weixin/Models/CustomMessageHandler.cs
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
/*----------------------------------------------------------------
|
||||
Copyright (C) 2018 Senparc
|
||||
|
||||
文件名:CustomMessageHandler.cs
|
||||
文件功能描述:微信公众号自定义MessageHandler
|
||||
|
||||
|
||||
创建标识:Senparc - 20150312
|
||||
|
||||
修改标识:Senparc - 20171027
|
||||
修改描述:v14.8.3 添加OnUnknownTypeRequest()方法Demo
|
||||
|
||||
----------------------------------------------------------------*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using Senparc.Weixin.MP.Agent;
|
||||
using Senparc.NeuChar.Context;
|
||||
using Senparc.Weixin.Exceptions;
|
||||
using Senparc.Weixin.Helpers;
|
||||
using Senparc.Weixin.MP.Entities;
|
||||
using Senparc.Weixin.MP.Entities.Request;
|
||||
using Senparc.Weixin.MP.MessageHandlers;
|
||||
using Senparc.Weixin.MP.Helpers;
|
||||
using System.Xml.Linq;
|
||||
using Senparc.Weixin.MP.AdvancedAPIs;
|
||||
using System.Threading.Tasks;
|
||||
using Senparc.NeuChar.Entities.Request;
|
||||
using Senparc.CO2NET.Helpers;
|
||||
using Senparc.NeuChar.Helpers;
|
||||
using Senparc.NeuChar.Entities;
|
||||
using Senparc.Weixin;
|
||||
using Senparc.Weixin.MP;
|
||||
using BotSharp.Platform.Models.AiRequest;
|
||||
using BotSharp.Platform.Abstraction;
|
||||
using BotSharp.Platform.Dialogflow.Models;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
#if NET45
|
||||
using System.Web;
|
||||
using System.Configuration;
|
||||
using System.Web.Configuration;
|
||||
using Senparc.Weixin.MP.Sample.CommonService.Utilities;
|
||||
#else
|
||||
using Microsoft.AspNetCore.Http;
|
||||
#endif
|
||||
|
||||
namespace BotSharp.Channel.Weixin.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义MessageHandler
|
||||
/// 把MessageHandler作为基类,重写对应请求的处理方法
|
||||
/// </summary>
|
||||
public partial class CustomMessageHandler : MessageHandler<CustomMessageContext>
|
||||
{
|
||||
/*
|
||||
* 重要提示:v1.5起,MessageHandler提供了一个DefaultResponseMessage的抽象方法,
|
||||
* DefaultResponseMessage必须在子类中重写,用于返回没有处理过的消息类型(也可以用于默认消息,如帮助信息等);
|
||||
* 其中所有原OnXX的抽象方法已经都改为虚方法,可以不必每个都重写。若不重写,默认返回DefaultResponseMessage方法中的结果。
|
||||
*/
|
||||
|
||||
|
||||
#if !DEBUG || NETSTANDARD1_6 || NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1
|
||||
string agentUrl = "http://localhost:12222/App/Weixin/4";
|
||||
string agentToken = "27C455F496044A87";
|
||||
string wiweihiKey = "CNadjJuWzyX5bz5Gn+/XoyqiqMa5DjXQ";
|
||||
#else
|
||||
//下面的Url和Token可以用其他平台的消息,或者到www.weiweihi.com注册微信用户,将自动在“微信营销工具”下得到
|
||||
private string agentUrl = Config.SenparcWeixinSetting.AgentUrl;//这里使用了www.weiweihi.com微信自动托管平台
|
||||
private string agentToken = Config.SenparcWeixinSetting.AgentToken;//Token
|
||||
private string wiweihiKey = Config.SenparcWeixinSetting.SenparcWechatAgentKey;//WeiweihiKey专门用于对接www.Weiweihi.com平台,获取方式见:http://www.weiweihi.com/ApiDocuments/Item/25#51
|
||||
#endif
|
||||
|
||||
#if NET45
|
||||
private string appId = Config.SenparcWeixinSetting.WeixinAppId;
|
||||
private string appSecret = Config.SenparcWeixinSetting.WeixinAppSecret;
|
||||
#else
|
||||
private string appId = "appId";
|
||||
private string appSecret = "appSecret";
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 模板消息集合(Key:checkCode,Value:OpenId)
|
||||
/// </summary>
|
||||
public static Dictionary<string, string> TemplateMessageCollection = new Dictionary<string, string>();
|
||||
|
||||
private IPlatformBuilder<AgentModel> nluPlatform = null;
|
||||
private readonly IConfiguration config;
|
||||
|
||||
public CustomMessageHandler(IPlatformBuilder<AgentModel> nluPlatform, IConfiguration configuration, Stream inputStream, PostModel postModel, int maxRecordCount = 0)
|
||||
: base(inputStream, postModel, maxRecordCount)
|
||||
{
|
||||
this.nluPlatform = nluPlatform;
|
||||
this.config = configuration;
|
||||
|
||||
//这里设置仅用于测试,实际开发可以在外部更全局的地方设置,
|
||||
//比如MessageHandler<MessageContext>.GlobalGlobalMessageContext.ExpireMinutes = 3。
|
||||
GlobalMessageContext.ExpireMinutes = 3;
|
||||
|
||||
if (!string.IsNullOrEmpty(postModel.AppId))
|
||||
{
|
||||
appId = postModel.AppId;//通过第三方开放平台发送过来的请求
|
||||
}
|
||||
|
||||
//在指定条件下,不使用消息去重
|
||||
base.OmitRepeatedMessageFunc = requestMessage =>
|
||||
{
|
||||
var textRequestMessage = requestMessage as RequestMessageText;
|
||||
if (textRequestMessage != null && textRequestMessage.Content == "容错")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnExecuting()
|
||||
{
|
||||
//测试MessageContext.StorageData
|
||||
if (CurrentMessageContext.StorageData == null)
|
||||
{
|
||||
CurrentMessageContext.StorageData = 0;
|
||||
}
|
||||
base.OnExecuting();
|
||||
}
|
||||
|
||||
public override void OnExecuted()
|
||||
{
|
||||
base.OnExecuted();
|
||||
CurrentMessageContext.StorageData = ((int)CurrentMessageContext.StorageData) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理文字请求
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override IResponseMessageBase OnTextRequest(RequestMessageText requestMessage)
|
||||
{
|
||||
var defaultResponseMessage = base.CreateResponseMessage<ResponseMessageText>();
|
||||
|
||||
var requestHandler =
|
||||
requestMessage.StartHandler()
|
||||
.Keyword("BotSharp",() =>
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
result.AppendFormat("{0}", "你好!欢迎使用BotSharp NLU服务,BotSharp通过强大的自然语言理解技术让你更轻松的处理智能回复。");
|
||||
defaultResponseMessage.Content = result.ToString();
|
||||
return defaultResponseMessage;
|
||||
})
|
||||
.Keyword("再见", () =>
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
result.AppendFormat("{0}", "谢谢使用,再见!");
|
||||
defaultResponseMessage.Content = result.ToString();
|
||||
return defaultResponseMessage;
|
||||
})
|
||||
.Default(() =>
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
|
||||
// send text to BotSharp platform emulator
|
||||
var aIResponse = nluPlatform.TextRequest<AIResponseResult>(new AiRequest
|
||||
{
|
||||
Text = requestMessage.Content,
|
||||
AgentId = config.GetValue<string>("weixinChannel:agentId"),
|
||||
SessionId = requestMessage.FromUserName
|
||||
});
|
||||
|
||||
result.AppendFormat("{0}", aIResponse.Result.Fulfillment.Speech);
|
||||
|
||||
defaultResponseMessage.Content = result.ToString();
|
||||
return defaultResponseMessage;
|
||||
});
|
||||
|
||||
return requestHandler.GetResponseMessage() as IResponseMessageBase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理位置请求
|
||||
/// </summary>
|
||||
/// <param name="requestMessage"></param>
|
||||
/// <returns></returns>
|
||||
public override IResponseMessageBase OnLocationRequest(RequestMessageLocation requestMessage)
|
||||
{
|
||||
var locationService = new LocationService();
|
||||
var responseMessage = locationService.GetResponseMessage(requestMessage as RequestMessageLocation);
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
public override IResponseMessageBase OnShortVideoRequest(RequestMessageShortVideo requestMessage)
|
||||
{
|
||||
var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
|
||||
responseMessage.Content = "您刚才发送的是小视频";
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理图片请求
|
||||
/// </summary>
|
||||
/// <param name="requestMessage"></param>
|
||||
/// <returns></returns>
|
||||
public override IResponseMessageBase OnImageRequest(RequestMessageImage requestMessage)
|
||||
{
|
||||
//一隔一返回News或Image格式
|
||||
if (base.GlobalMessageContext.GetMessageContext(requestMessage).RequestMessages.Count() % 2 == 0)
|
||||
{
|
||||
var responseMessage = CreateResponseMessage<ResponseMessageNews>();
|
||||
|
||||
responseMessage.Articles.Add(new Article()
|
||||
{
|
||||
Title = "您刚才发送了图片信息",
|
||||
Description = "您发送的图片将会显示在边上",
|
||||
PicUrl = requestMessage.PicUrl,
|
||||
Url = "http://sdk.weixin.senparc.com"
|
||||
});
|
||||
responseMessage.Articles.Add(new Article()
|
||||
{
|
||||
Title = "第二条",
|
||||
Description = "第二条带连接的内容",
|
||||
PicUrl = requestMessage.PicUrl,
|
||||
Url = "http://sdk.weixin.senparc.com"
|
||||
});
|
||||
|
||||
return responseMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
var responseMessage = CreateResponseMessage<ResponseMessageImage>();
|
||||
responseMessage.Image.MediaId = requestMessage.MediaId;
|
||||
return responseMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理语音请求
|
||||
/// </summary>
|
||||
/// <param name="requestMessage"></param>
|
||||
/// <returns></returns>
|
||||
public override IResponseMessageBase OnVoiceRequest(RequestMessageVoice requestMessage)
|
||||
{
|
||||
var responseMessage = CreateResponseMessage<ResponseMessageMusic>();
|
||||
//上传缩略图
|
||||
//var accessToken = Containers.AccessTokenContainer.TryGetAccessToken(appId, appSecret);
|
||||
var uploadResult = Senparc.Weixin.MP.AdvancedAPIs.MediaApi.UploadTemporaryMedia(appId, UploadMediaFileType.image,
|
||||
Server.GetMapPath("~/Images/Logo.jpg"));
|
||||
|
||||
//设置音乐信息
|
||||
responseMessage.Music.Title = "天籁之音";
|
||||
responseMessage.Music.Description = "播放您上传的语音";
|
||||
responseMessage.Music.MusicUrl = "http://sdk.weixin.senparc.com/Media/GetVoice?mediaId=" + requestMessage.MediaId;
|
||||
responseMessage.Music.HQMusicUrl = "http://sdk.weixin.senparc.com/Media/GetVoice?mediaId=" + requestMessage.MediaId;
|
||||
responseMessage.Music.ThumbMediaId = uploadResult.media_id;
|
||||
|
||||
//推送一条客服消息
|
||||
try
|
||||
{
|
||||
CustomApi.SendText(appId, WeixinOpenId, "本次上传的音频MediaId:" + requestMessage.MediaId);
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return responseMessage;
|
||||
}
|
||||
/// <summary>
|
||||
/// 处理视频请求
|
||||
/// </summary>
|
||||
/// <param name="requestMessage"></param>
|
||||
/// <returns></returns>
|
||||
public override IResponseMessageBase OnVideoRequest(RequestMessageVideo requestMessage)
|
||||
{
|
||||
var responseMessage = CreateResponseMessage<ResponseMessageText>();
|
||||
responseMessage.Content = "您发送了一条视频信息,ID:" + requestMessage.MediaId;
|
||||
|
||||
#region 上传素材并推送到客户端
|
||||
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
//上传素材
|
||||
var dir = Server.GetMapPath("~/App_Data/TempVideo/");
|
||||
var file = await MediaApi.GetAsync(appId, requestMessage.MediaId, dir);
|
||||
var uploadResult = await MediaApi.UploadTemporaryMediaAsync(appId, UploadMediaFileType.video, file, 50000);
|
||||
await CustomApi.SendVideoAsync(appId, base.WeixinOpenId, uploadResult.media_id, "这是您刚才发送的视频", "这是一条视频消息");
|
||||
}).ContinueWith(async task =>
|
||||
{
|
||||
if (task.Exception != null)
|
||||
{
|
||||
WeixinTrace.Log("OnVideoRequest()储存Video过程发生错误:", task.Exception.Message);
|
||||
|
||||
var msg = string.Format("上传素材出错:{0}\r\n{1}",
|
||||
task.Exception.Message,
|
||||
task.Exception.InnerException != null
|
||||
? task.Exception.InnerException.Message
|
||||
: null);
|
||||
await CustomApi.SendTextAsync(appId, base.WeixinOpenId, msg);
|
||||
}
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 处理链接消息请求
|
||||
/// </summary>
|
||||
/// <param name="requestMessage"></param>
|
||||
/// <returns></returns>
|
||||
public override IResponseMessageBase OnLinkRequest(RequestMessageLink requestMessage)
|
||||
{
|
||||
var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageText>(requestMessage);
|
||||
responseMessage.Content = string.Format(@"您发送了一条连接信息:
|
||||
Title:{0}
|
||||
Description:{1}
|
||||
Url:{2}", requestMessage.Title, requestMessage.Description, requestMessage.Url);
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
public override IResponseMessageBase OnFileRequest(RequestMessageFile requestMessage)
|
||||
{
|
||||
var responseMessage = requestMessage.CreateResponseMessage<ResponseMessageText>();
|
||||
responseMessage.Content = string.Format(@"您发送了一个文件:
|
||||
文件名:{0}
|
||||
说明:{1}
|
||||
大小:{2}
|
||||
MD5:{3}", requestMessage.Title, requestMessage.Description, requestMessage.FileTotalLen, requestMessage.FileMd5);
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理事件请求(这个方法一般不用重写,这里仅作为示例出现。除非需要在判断具体Event类型以外对Event信息进行统一操作
|
||||
/// </summary>
|
||||
/// <param name="requestMessage"></param>
|
||||
/// <returns></returns>
|
||||
public override IResponseMessageBase OnEventRequest(IRequestMessageEventBase requestMessage)
|
||||
{
|
||||
var eventResponseMessage = base.OnEventRequest(requestMessage);//对于Event下属分类的重写方法,见:CustomerMessageHandler_Events.cs
|
||||
//TODO: 对Event信息进行统一操作
|
||||
return eventResponseMessage;
|
||||
}
|
||||
|
||||
public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
|
||||
{
|
||||
/* 所有没有被处理的消息会默认返回这里的结果,
|
||||
* 因此,如果想把整个微信请求委托出去(例如需要使用分布式或从其他服务器获取请求),
|
||||
* 只需要在这里统一发出委托请求,如:
|
||||
* var responseMessage = MessageAgent.RequestResponseMessage(agentUrl, agentToken, RequestDocument.ToString());
|
||||
* return responseMessage;
|
||||
*/
|
||||
|
||||
var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
|
||||
responseMessage.Content = "这条消息来自DefaultResponseMessage。";
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
|
||||
public override IResponseMessageBase OnUnknownTypeRequest(RequestMessageUnknownType requestMessage)
|
||||
{
|
||||
/*
|
||||
* 此方法用于应急处理SDK没有提供的消息类型,
|
||||
* 原始XML可以通过requestMessage.RequestDocument(或this.RequestDocument)获取到。
|
||||
* 如果不重写此方法,遇到未知的请求类型将会抛出异常(v14.8.3 之前的版本就是这么做的)
|
||||
*/
|
||||
var msgType = Senparc.NeuChar.Helpers.MsgTypeHelper.GetRequestMsgTypeString(requestMessage.RequestDocument);
|
||||
var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
|
||||
responseMessage.Content = "未知消息类型:" + msgType;
|
||||
|
||||
WeixinTrace.SendCustomLog("未知请求消息类型", requestMessage.RequestDocument.ToString());//记录到日志中
|
||||
|
||||
return responseMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
BotSharp.Channel.Weixin/ModuleInjector.cs
Normal file
32
BotSharp.Channel.Weixin/ModuleInjector.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using BotSharp.Core.Modules;
|
||||
using BotSharp.Platform.Abstraction;
|
||||
using BotSharp.Platform.Models;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Senparc.CO2NET;
|
||||
using Senparc.CO2NET.RegisterServices;
|
||||
using Senparc.Weixin.RegisterServices;
|
||||
using System;
|
||||
|
||||
namespace BotSharp.Channel.Weixin
|
||||
{
|
||||
public class ModuleInjector : IModule
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
// Senparc.CO2NET, Senparc.Weixin
|
||||
services.AddSenparcGlobalServices(config)
|
||||
.AddSenparcWeixinServices(config);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env/*, IOptions<SenparcSetting> senparcSetting, IOptions<SenparcWeixinSetting> senparcWeixinSetting*/)
|
||||
{
|
||||
|
||||
//https://github.com/Senparc/Senparc.CO2NET/blob/master/Sample/Senparc.CO2NET.Sample.netcore/Startup.cs
|
||||
/* IRegisterService register = RegisterService.Start(env, senparcSetting.Value)
|
||||
.UseSenparcGlobal();*/
|
||||
}
|
||||
}
|
||||
}
|
||||
47
BotSharp.Channel.Weixin/README.md
Normal file
47
BotSharp.Channel.Weixin/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# botsharp-channel-weixin
|
||||
A channel module of BotSharp for Tencent Weixin
|
||||
|
||||
### How to install through NuGet
|
||||
|
||||
```
|
||||
PM> Install-Package BotSharp.Channel.Weixin
|
||||
```
|
||||
|
||||
### How to run locally
|
||||
```
|
||||
git clone https://github.com/dotnetcore/BotSharp
|
||||
```
|
||||
|
||||
Check app.json to use DialogflowAi
|
||||
```
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"assemblies": "BotSharp.Core",
|
||||
|
||||
"platformModuleName": "DialogflowAi"
|
||||
}
|
||||
```
|
||||
|
||||
Update channels.weixin.json to set the corresponding KEY
|
||||
```
|
||||
{
|
||||
"weixinChannel": {
|
||||
"token": "botsharp",
|
||||
"encodingAESKey": "",
|
||||
"appId": "",
|
||||
"agentId": "60bee6f9-ba58-4fe8-8b95-94af69d6fd41"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
F5 run BotSharp.WebHost
|
||||
|
||||
Access http://localhost:3112
|
||||
|
||||
Import demo (Spotify.zip) agent located at App_Data
|
||||
|
||||
Train agent (id: 60bee6f9-ba58-4fe8-8b95-94af69d6fd41)
|
||||
|
||||
Or refer [BotSharp docs](https://botsharp.readthedocs.io) to design your new chatbot.
|
||||
|
||||
Setup Wechat webhood from https://mp.weixin.qq.com/.
|
||||
101
BotSharp.Channel.Weixin/Server.cs
Normal file
101
BotSharp.Channel.Weixin/Server.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
#if NET45
|
||||
using System.Web;
|
||||
#else
|
||||
using Microsoft.AspNetCore.Http;
|
||||
#endif
|
||||
|
||||
namespace BotSharp.Channel.Weixin
|
||||
{
|
||||
public static class Server
|
||||
{
|
||||
private static string _appDomainAppPath;
|
||||
public static string AppDomainAppPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_appDomainAppPath == null)
|
||||
{
|
||||
#if NET45
|
||||
_appDomainAppPath = HttpRuntime.AppDomainAppPath;
|
||||
#else
|
||||
_appDomainAppPath = AppContext.BaseDirectory; //dll所在目录:;
|
||||
#endif
|
||||
}
|
||||
return _appDomainAppPath;
|
||||
}
|
||||
set
|
||||
{
|
||||
_appDomainAppPath = value;
|
||||
#if NETSTANDARD1_6 || NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1
|
||||
if (!_appDomainAppPath.EndsWith("/"))
|
||||
{
|
||||
_appDomainAppPath += "/";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private static string _webRootPath;
|
||||
/// <summary>
|
||||
/// wwwroot文件夹目录(专供ASP.NET Core MVC使用)
|
||||
/// </summary>
|
||||
public static string WebRootPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_webRootPath == null)
|
||||
{
|
||||
#if NET45
|
||||
_webRootPath = AppDomainAppPath;
|
||||
#else
|
||||
_webRootPath = AppDomainAppPath + "wwwroot/";//asp.net core的wwwroot文件目录结构不一样
|
||||
#endif
|
||||
}
|
||||
return _webRootPath;
|
||||
}
|
||||
set { _webRootPath = value; }
|
||||
}
|
||||
|
||||
public static string GetMapPath(string virtualPath)
|
||||
{
|
||||
if (virtualPath == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else if (virtualPath.StartsWith("~/"))
|
||||
{
|
||||
return virtualPath.Replace("~/", AppDomainAppPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(AppDomainAppPath, virtualPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpContext HttpContext
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET45
|
||||
HttpContext context = HttpContext.Current;
|
||||
if (context == null)
|
||||
{
|
||||
HttpRequest request = new HttpRequest("Default.aspx", "http://sdk.weixin.senparc.com/default.aspx", null);
|
||||
StringWriter sw = new StringWriter();
|
||||
HttpResponse response = new HttpResponse(sw);
|
||||
context = new HttpContext(request, response);
|
||||
}
|
||||
#else
|
||||
HttpContext context = new DefaultHttpContext();
|
||||
#endif
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,3 +23,7 @@ var aIResponse = nluPlatform.TextRequest<AIResponseResult>(new AiRequest
|
|||
});
|
||||
|
||||
```
|
||||
### How to run locally
|
||||
```
|
||||
git clone https://github.com/dotnetcore/BotSharp
|
||||
```
|
||||
|
|
@ -84,6 +84,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BotSharp.Channel.Weixin\BotSharp.Channel.Weixin.csproj" />
|
||||
<ProjectReference Include="..\BotSharp.Platform.Dialogflow\BotSharp.Platform.Dialogflow.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@
|
|||
{
|
||||
"Name": "DialogflowAi",
|
||||
"Type": "BotSharp.Platform.Dialogflow"
|
||||
},
|
||||
{
|
||||
"Name": "WeixinChannel",
|
||||
"Type": "BotSharp.Channel.Weixin"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"weixinChannel": {
|
||||
"token": "botsharp",
|
||||
"encodingAESKey": "",
|
||||
"appId": ""
|
||||
"appId": "",
|
||||
"agentId": ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
BotSharp.sln
26
BotSharp.sln
|
|
@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Platform.Models",
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Platform.Dialogflow", "BotSharp.Platform.Dialogflow\BotSharp.Platform.Dialogflow.csproj", "{8C82E35D-DC94-45C9-90FD-1FF62E75CD9B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Channel.Weixin", "BotSharp.Channel.Weixin\BotSharp.Channel.Weixin.csproj", "{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
ARTICULATE|Any CPU = ARTICULATE|Any CPU
|
||||
|
|
@ -179,6 +181,30 @@ Global
|
|||
{8C82E35D-DC94-45C9-90FD-1FF62E75CD9B}.Test|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C82E35D-DC94-45C9-90FD-1FF62E75CD9B}.Test|x64.ActiveCfg = Debug|Any CPU
|
||||
{8C82E35D-DC94-45C9-90FD-1FF62E75CD9B}.Test|x64.Build.0 = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.ARTICULATE|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.ARTICULATE|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.ARTICULATE|x64.ActiveCfg = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.ARTICULATE|x64.Build.0 = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.DIALOGFLOW|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.DIALOGFLOW|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.DIALOGFLOW|x64.ActiveCfg = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.DIALOGFLOW|x64.Build.0 = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.RASA|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.RASA|Any CPU.Build.0 = Release|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.RASA|x64.ActiveCfg = Release|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.RASA|x64.Build.0 = Release|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Test|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Test|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Test|x64.ActiveCfg = Debug|Any CPU
|
||||
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Test|x64.Build.0 = Debug|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
Loading…
Reference in a new issue