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

news/2025/2/12 8:53:50/文章来源:https://www.cnblogs.com/MrHanBlog/p/18706316

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

一、了解微信的交互流程

这里假设我们都已经申请号了微信开发的相关信息,那么我们就要配置微信认证服务器地址(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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/881296.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET周刊【2月第1期 2025-02-02】

国内文章 dotnet 9 已知问题 默认开启 CET 导致进程崩溃 https://www.cnblogs.com/lindexi/p/18700406 本文记录 dotnet 9 的一个已知且当前已修问题。默认开启 CET 导致一些模块执行时触发崩溃。 dotnet 使用 ColorCode 做代码着色器 https://www.cnblogs.com/lindexi/p/18687…

浅谈操作分块:从 Div.3 F 到 Ynoi

前言 分块是一种常见的处理信息的思想。 序列分块通常以 \(\mathcal O(q\sqrt n)\) 左右的时间复杂度对询问进行处理。观察序列分块的本质,其实是控制无法快速统计的部分较少,以至于可以暴力统计,剩下的部分采取诸如提前维护好每个块的答案再合并的方式快速统计。 而在答案具…

如何使用 DataX 连接 Easysearch

DataX DataX 是阿里开源的一款离线数据同步工具,致力于实现包括关系型数据库(MySQL、Oracle 等)、HDFS、Hive、ODPS、HBase、FTP 等各种异构数据源之间稳定高效的数据同步功能。 本篇主要介绍 DataX 如何将数据写入到 Easysearch,对于各种数据源的连接不会做深入的探讨,感兴…

计算机组成原理图解趣味版《程序是怎样跑起来的(第3版)》PDF、EPUB免费下载

计算机组成原理图解趣味版,蹲马桶便能看懂的计算机和编程原理入门知识,网络是怎样连接的,计算机是怎样跑起来的姊妹篇,日文版重印41次 单册数量100本以上。 本书从计算机的内部结构开始讲起,以图配文的形式详细讲解了二进制、内存、数据压缩、源文件和可执行文件、操作系统…

自然语言处理与词嵌入

自然语言处理与词嵌入传统的词汇使用词汇表(Vocabulary)来存储,并用one-hot向量表示,向量长度等于词汇表大小,每个单词对应一个独特的索引,只有索引处的值为1,其余全部为0。如果 “max” 在词汇表里的索引是5391,那么对应的one-hot向量为 \(O_{5391}\)。 但是这种表示方法…

[20052006-ptz] Decoding Martian Messages

前言 有点神奇的一个题, 不太会做 思路给定 \(p_{i, c}\) 表示位置 \(i\) 是字符 \(c\) 的概率, 确定 \(\displaystyle\sum_{c = 1}^{t} p_{i, c} = 1\) 一个有效的信息被定义为任意长度为 \(k\) 的子序列都在集合 \(\mathbb{D}\) 中出现 求一个有效的信息 \(c_1c_2c_3c_4\cdot…

2.3 ~ 2.11

长达九天的寒假集训2.3 上午 模拟 欢乐赛。 ACM 赛制,10 道题; 开题。发现 A 是水,直接通过。 看 B,发现 B 就是个背包,爆了两发之后过了。 看 jijidawang 过了 G 就去看 G,发现是线段树,写了一发,没过。 发现了一些问题,改了改,没过。 开始写拍,拍出来发现自己 que…

Joker 智能开发平台再放大招,新作将彻底重塑开发模式

—— 突破传统枷锁,引领开发模式全面革新自前端可视化智能平台重磅发布后,其在行业内的影响力便如涟漪般迅速扩散。凭借着创新的设计理念和过硬的性能表现,这个平台为无数开发者和企业提供了高效且便捷的开发解决方案,收获了满满的赞誉与认可。 如今,Joker 智能开发平台又…

docker/docker-compose下存储卷的使用(CIFS和NFS)

前面说到Linux下的挂载CIFS和NFS,这里就顺道简单记一下docker/docker-compose去挂载CIFS和NFS的方式,当个笔记记录一下。docker去挂载CIFS和NFS,需要使用存储卷,稍微懂一点docker,就知道这个是什么,所以这里我们长话短说。首先,docker存储卷分为管理卷、绑定卷、临时卷。…

Deep深度算命App

Deep深度算命App应用背景与开发动机 最近,DeepSeek R1模型在AI领域引起了广泛的关注。尽管它不是当前最强大的模型之一,但在中文处理方面表现出色。春节期间,我原本计划开发一个基于AI的图像识别程序,但被周围人对DeepSeek R1用于算命应用的高度评价所吸引,尤其是我的小舅…

javaj进阶(中)

java进阶(中) 集合集合的特点可以动态保存任意多个对象,使用比较方便 提供了一系列方便的操作对象的方法:add、remove、set、getjava集合类很多,主要分为两大类,如图LInk(接口) ArrayList(Link实现类)ArrayList 是 Java 中最常用的集合类之一,它实现了 List 接口,基于…

java中awit和sleep的区别和线程安全性问题

awit和sleep的区别 从名称上来讲: awit:等待。 sleep:休眠。 从属关系上来讲: awit:awit这个方法是在对象上,只要是对象,就有这个方法 sleep:sleep是在Thread上,它是在线程上,是一个静态方法使用方式上来讲: awit只能够在同步代码中去使用 sleep可以在任意的地方中去使用从…