网站集成微信公众号(订阅号)登录

前一阵子,想着给我的站点集成一个微信登录,因为我之前从未有过微信相关的开发,所以我自己跟着网上的资料,一步一步的慢慢的摸索,过程不免的遇到了许多坑,才把我的网站微信登录集成完成,所以这里分享一下我的摸索的过程。因为我的是订阅号,所以一下的内容均针对订阅号而言的

一、了解微信的交互流程

这里假设我们都已经申请号了微信开发的相关信息,那么我们就要配置微信认证服务器地址(api地址),这一步是必须的,微信与我们交互都是都是通过这一个地址,开始我一直不知道,我以为自己要对每一个功能都要写一个api,其实不需要的。我们只需要完成我们的微信认证服务器地址,然后里面处理我们相关的业务逻辑。

sequenceDiagram 用户->>微信: 发送消息“hello!” 微信->>我的网站: 认证服务api校验 我的网站-)微信: 校验成功 微信->>我的网站: 发送“hello!”到认证服务api 我的网站-)业务逻辑: 认证服务api接收“hello!”并且处理业务逻辑 我的网站-->> 用户: 也可返回给用户相关信息

用我的话就是
假设我的认证api是:/api/check
1.用户发送消息”你好“到微信公众号
2.微信公众号发起Get请求调用 /api/check 进行认证
3.认证成功时微信公众号再次Post请求调用/api/check,并且携带”你好“信息
4./api/check接口里面处理相关业务逻辑,或者返回特定信息给用户

二、集成相关sdk

对于微信的消息处理,其实有点复杂,这里我网上搜了一下,大部分推荐的是盛派微信sdk,博客园也有相关的教程https://www.cnblogs.com/szw/archive/2013/05/20/3089479.html,个人用起来我觉得还是可以的,可以省去我们大部分工作,专注处理业务逻辑。

封装CustomMessageHandler

首先我是要对盛派微信sdk的封装,所以我新建了一个CustomMessageHandler,然后继承自MessageHandler,然后重新DefaultResponseMessage方法,这里比较简单,直接

public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
{
var responseMessage = base.CreateResponseMessage();
return responseMessage;
}

处理用户关注事件

当微信用户关注公众号时我需要发送问候语给用户,那么这里就需要重写OnEvent_SubscribeRequestAsync
public override Task OnEvent_SubscribeRequestAsync(RequestMessageEvent_Subscribe requestMessage)
{
var responseMessage = base.CreateResponseMessage();
responseMessage.Content = “欢迎关注”;
return Task.FromResult(responseMessage as IResponseMessageBase);
}

处理用户关键字

当微信用户给公众号发送特点消息(关键字)时,我需要回复用户,那么就重写OnTextRequestAsync
public override Task OnTextRequestAsync(RequestMessageText requestMessage)
{
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

posted @ 2025-02-09 17:18  灬丶  阅读(49)  评论(0编辑  收藏  举报