網站集成微信公眾號(訂閱號)登錄
前一陣子,想著給我的站點集成一個微信登錄,因為我之前從未有過微信相關的開發,所以我自己跟著網上的資料,一步一步的慢慢的摸索,過程不免的遇到了許多坑,才把我的網站微信登錄集成完成,所以這里分享一下我的摸索的過程。因為我的是訂閱號,所以一下的內容均針對訂閱號而言的。
一、了解微信的交互流程
這里假設我們都已經申請號了微信開發的相關信息,那么我們就要配置微信認證服務器地址(api地址),這一步是必須的,微信與我們交互都是都是通過這一個地址,開始我一直不知道,我以為自己要對每一個功能都要寫一個api,其實不需要的。我們只需要完成我們的微信認證服務器地址,然后里面處理我們相關的業務邏輯。

用我的話就是
假設我的認證api是:/api/check
1.用戶發送消息”你好“到微信公眾號
2.微信公眾號發起Get請求調用 /api/check 進行認證
3.認證成功時微信公眾號再次Post請求調用/api/check,并且攜帶”你好“信息
4./api/check接口里面處理相關業務邏輯,或者返回特定信息給用戶
二、集成相關sdk
對于微信的消息處理,其實有點復雜,這里我網上搜了一下,大部分推薦的是盛派微信sdk,博客園也有相關的教程http://www.rzrgm.cn/szw/archive/2013/05/20/3089479.html,個人用起來我覺得還是可以的,可以省去我們大部分工作,專注處理業務邏輯。
封裝CustomMessageHandler
首先我是要對盛派微信sdk的封裝,所以我新建了一個CustomMessageHandler,然后繼承自MessageHandler
public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
{
var responseMessage = base.CreateResponseMessage
return responseMessage;
}
處理用戶關注事件
當微信用戶關注公眾號時我需要發送問候語給用戶,那么這里就需要重寫OnEvent_SubscribeRequestAsync
public override Task
{
var responseMessage = base.CreateResponseMessage
responseMessage.Content = “歡迎關注”;
return Task.FromResult(responseMessage as IResponseMessageBase);
}
處理用戶關鍵字
當微信用戶給公眾號發送特點消息(關鍵字)時,我需要回復用戶,那么就重寫OnTextRequestAsync
public override Task
{
var responseMessage = base.CreateResponseMessage
return Task.FromResult(responseMessage as IResponseMessageBase);
}
抽離業務邏輯方法
在開發中,我們往往希望封裝一個功能時,不需要牽扯太多業務邏輯,就例如不想在CustomMessageHandler里去寫業務邏輯,那么我這里采用的委托的形式,新建一個CustomParam,然后里面定義一個兩個委托
public class CustomParam
{
/// <summary>
/// 普通文本事件處理
/// </summary>
public Func<RequestMessageText, ResponseMessageText, string, ResponseMessageText> OnTextFunc;
/// <summary>
/// 訂閱事件處理
/// </summary>
public Func<string> OnSubscribeFunc;
}
然后通過CustomMessageHandler的構造函數傳入
private CustomParam customParam;
public CustomMessageHandler(CustomParam customParam)
{
this.customParam = customParam;
}
然后在具體的處理事件里面調用這個委托

通過接口調用微信CustomMessageHandler的方法
我接著在建一個接口,里面包含了認證,消息處理,創建CustomMessageHandler的方法,來給業務調用
using Core.WeXin.WxOfficial;
using Senparc.Weixin.AspNet.MvcExtension;
using Senparc.Weixin.MP.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.WeXin
{
public interface IWxCommonHandler
{
/// <summary>
/// 校驗服務
/// </summary>
/// <returns></returns>
bool CheckSign(string signature, string timestamp, string nonce, string echostr);
/// <summary>
/// 創建消息處理器
/// </summary>
/// <param name="inputStream"></param>
/// <param name="messageAbsService"></param>
void CreateMessageHandler(Stream inputStream, CustomParam param);
/// <summary>
/// 公眾號消息處理
/// </summary>
/// <param name="inputStream"></param>
/// <returns></returns>
Task<WeixinResult> ExecuteMessageHandler();
/// <summary>
/// 獲取當前opentid
/// </summary>
/// <returns></returns>
string GetOpenId();
/// <summary>
/// 獲取微信消息
/// </summary>
/// <returns></returns>
string GetMessgeText();
}
}
using Core.Log;
using Core.WeXin.WxOfficial;
using Microsoft.Extensions.Configuration;
using Senparc.NeuChar;
using Senparc.NeuChar.Entities;
using Senparc.Weixin.AspNet.MvcExtension;
using Senparc.Weixin.MP;
using Senparc.Weixin.MP.Entities;
using System;
namespace Core.WeXin
{
internal class WxCommonHandler:IWxCommonHandler
{
private CustomMessageHandler customMessageHandler = null;
private IConfiguration configuration;
public WxCommonHandler(IConfiguration configuration)
{
this.configuration = configuration;
}
public bool CheckSign(string signature, string timestamp, string nonce, string echostr)
{
string token = configuration.GetSection("SenparcWeixinSetting:Token").Value;
return CheckSignature.Check(signature, timestamp, nonce, token);
}
public void CreateMessageHandler(Stream inputStream,CustomParam customParam)
{
customMessageHandler = new CustomMessageHandler(inputStream, null, customParam);
customMessageHandler.OmitRepeatedMessage = true;
}
public async Task<WeixinResult> ExecuteMessageHandler()
{
await customMessageHandler.ExecuteAsync(new CancellationToken());
string result = "";
if (customMessageHandler.ResponseDocument != null)
{
result = customMessageHandler.ResponseDocument.ToString();
}
return new WeixinResult(result);
}
public string GetOpenId()
{
return customMessageHandler.OpenId;
}
public string GetMessgeText()
{
var requestMsg= customMessageHandler.RequestMessage;
if (requestMsg.MsgType == RequestMsgType.Text)
{
RequestMessageText requestText = requestMsg as RequestMessageText;
return requestText.Content;
}
return string.Empty;
}
}
}
注冊微信sdk相關內容
using Core.Log;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Senparc.CO2NET;
using Senparc.CO2NET.AspNet;
using Senparc.Weixin.AspNet;
using Senparc.Weixin.Entities;
using Senparc.Weixin.MP;
using Senparc.Weixin.RegisterServices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.WeXin
{
public static class ConfigureWxService
{
public static IServiceCollection AddWxOfficialServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<IWxCommonHandler, WxCommonHandler>();
services.AddSenparcWeixinServices(configuration);
return services;
}
public static void UseWxOfficial(this WebApplication app, ConfigurationManager configuration)
{
var registerService = app.UseSenparcWeixin(app.Environment, null, null, register => { },
(register, weixinSetting) =>
{
register.RegisterMpAccount(weixinSetting, configuration.GetSection("WxOfficialName").Value);
});
}
}
}
二、新建認證api(Get請求)
前面說到,我們和微信打交道始終是這一個微信,所以我們需要用一個api既能接收認證又能接收消息,所以我先建一個Get請求的方法
[HttpGet("handler")]
public ContentResult Handler()
{
string signature = Request.Query["signature"];
string timestamp = Request.Query["timestamp"];
string nonce = Request.Query["nonce"];
string echostr = Request.Query["echostr"];
if (wxCommonHandler.CheckSign(signature, timestamp, nonce, echostr))
{
return new ContentResult()
{
Content = echostr
};
}
else
{
return new ContentResult()
{
Content = "false"
};
}
}
三、新建微信處理的Service
關注事件業務
我想在用戶關注時發送對應的問候語,則我直接新建一個方法OnSubscribeEvent
public string OnSubscribeEvent()
{
StringBuilder sb = new StringBuilder();
sb.Append("您好!歡迎關注【閑蛋】??\r\n");
sb.Append("回復[登錄]即可獲取驗證碼登錄網頁<a href=\"https://www.xiandanplay.com\">閑蛋</a>??\r\n");
sb.Append("更好的網站體驗請用電腦登錄????\r\n");
sb.Append("關于閑蛋,點這里??:<a href=\"https://mp.weixin.qq.com/s/TNBr9XiLbPJU3ZFRT3cCbg\">關于閑蛋</a>\r\n");
sb.Append("關于站長,點這里??:<a href=\"https://mp.weixin.qq.com/s/4KvydAbogv2KZGBfNmRveA\">關于站長</a>\r\n");
sb.Append("祝大家生活平平安安,不求多財但求無疾????");
return sb.ToString();
}
關鍵字登錄
因為我的是訂閱號,無法獲取實現微信登錄,于是我只好通過回復關鍵字的形式,獲取登錄驗證碼,然后獲取到openid來注冊用戶,所以我的處理步驟如下:
1.匹配到關鍵字“登錄”
2.根據當前的當前的openid判斷redis的Set是否已有對應的驗證嗎,如果已存在直接返回“請勿重復獲取驗證碼”
3.為了防止生成重復的有效的驗證碼,所以我新建一個Key,值為loginCode的Set類型的緩存,當生成的驗證碼可以從redis取到,則從新生成。(目前木有想到更好的辦法,請賜教...)
4.將Set的Key、 驗證碼loginCode,放入redis,存為Set類型,過期時間為1分鐘,此處是為了步驟3的校驗
5.再新建一個Set的Key、 openid,放入redis,過期時間為1分鐘,此處是為了步驟2的校驗,
6.再新建一個HashSet的Key、loginCode、 openid,放入redis,過期時間為1分鐘,然后輸入驗證碼登錄的時候直接根據這個緩存來
public ResponseMessageText OnTextEvent(RequestMessageText requestMessageText,ResponseMessageText responseMessageText,string openId)
{
try
{
requestMessageText.StartHandler().Keyword("登錄", () =>
{
if (cacheClient.ExistsSet(CheckRepeatGetLoginCodeKey, openId))
responseMessageText.Content = $"您好:請勿重復獲取驗證碼";
else
{
string verCode = OnLoginVerCode(openId);
responseMessageText.Content = $"您好:您的網站登錄驗證碼是 {verCode} 有效期60秒";
}
return responseMessageText;
});
return responseMessageText;
}
catch (Exception ex)
{
LogUtils.LogError(ex);
throw;
}
}
private string OnLoginVerCode(string openId)
{
string loginCode = StringUtil.RandomNumber(4);
string cacheKey = "login_wx_code";
bool existCode = false;
do
{
existCode = cacheClient.ExistsSet(cacheKey, loginCode);
if (!existCode)
{
TimeSpan expire = TimeSpan.FromMinutes(1);
cacheClient.AddSet(cacheKey, loginCode, expire);
cacheClient.AddSet(CheckRepeatGetLoginCodeKey, openId, expire);
cacheClient.AddHash(CacheKey.WxLoginCodeKey, loginCode, openId, expire);
}
}
while (existCode);
return loginCode;
}
四、新建認證api(Post請求)
[HttpPost("handler")]
public async Task<ContentResult> Handler(string signature, string timestamp, string nonce, string echostr)
{
if (!wxCommonHandler.CheckSign(signature, timestamp, nonce,echostr))
{
return new ContentResult()
{
Content = "參數錯誤"
};
}
CustomParam customParam = new CustomParam()
{
OnSubscribeFunc = wxService.OnSubscribeEvent,
OnTextFunc = wxService.OnTextEvent
};
wxCommonHandler.CreateMessageHandler(Request.Body, customParam);
return await wxCommonHandler.ExecuteMessageHandler();
}
wxService就是前面的封裝的方法
五、登錄驗證
比較簡單,就是根據驗證碼,能否從緩存取到對應的信息
Regex regex = new Regex(RegexPattern.IsNumberPattern);
if (!regex.IsMatch(code.Trim()) || code.Length != 4)
throw new ValidationException("code 無效");
CacheClient cacheClient = CacheClient.CreateClient();
string openId= cacheClient.GetHashValue("Wx_Login_Hash_Key", code);
if (string.IsNullOrEmpty(openId))
throw new AuthException("驗證碼無效");
WeXinUserInfo weXinUserInfo = new WeXinUserInfo();
weXinUserInfo.OpenAuthEnum = OpenAuthEnum.Wexin;
weXinUserInfo.Nickname = "wx_" + StringUtil.RandomNumber(10);
weXinUserInfo.OpenID = openId;
weXinUserInfo.Sex = "男";
cacheClient.DeleteHashField("Wx_Login_Hash_Key", code);
return await Task.FromResult(weXinUserInfo);
六、成果展示


目前為止,微信公眾號已經開發完成,因為我沒有過相關開發的經驗,所以如果有不合適的地方,大家可以指出來。
同時我有個疑問請教一下大家,就是發布到服務器上后我用明文模式可以正常處理微信消息,但是安全模式卻不行了,也正確的配置了相關的EncodingAESKey
作者:程序員奶牛
個人開源網站:https://www.xiandanplay.com
源碼地址:https://gitee.com/MrHanchichi/xian-dan

浙公網安備 33010602011771號