微信小程序和公众号打通,实现用户关注公众号送优惠券

前提

小程序
公众号
微信开放平台
小程序和公众号都需要绑定到同一个微信开放平台,因为要获取Unionid,unionid是什么

如果开发者拥有多个移动应用、网站应用、和公众账号(包括小程序),可通过 UnionID 来区分用户的唯一性,因为只要是同一个微信开放平台账号下的移动应用、网站应用和公众账号(包括小程序),用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,UnionID是相同的。

微信公众号获取unionid
在这里插入图片描述

小程序获取unionid
在这里插入图片描述

获取参数

公众号appid和appsecret
在这里插入图片描述
小程序appid和appsecret
在这里插入图片描述

微信小程序关注公众号组件

https://developers.weixin.qq.com/miniprogram/dev/component/official-account.html
在这里插入图片描述
在这里插入图片描述
小程序测试需要设置场景值
在这里插入图片描述
在这里插入图片描述
这个展示有条件,不过基本能满足,如果没有关注就是显示关注,因为关注了所以显示的是查看,模拟器上没法点击,需要发布体验版去真机上使用,如果场景下不支持,则可以模拟写一个,不过功能没法实现,最终方案是通过webview跳转公众号文件引导关注

<template>
	<view class="offical-account-container">
		<view class="top-text">
			xxx小程序关联的公众号
		</view>
		<view class="content">
			<view class="logo">
				<image src="../../static/images/officalaccount/logo.png"></image>
			</view>
			<view class="info">
				<view class="name">公众号名字</view>
				<view class="detial">
					公众号简介。
				</view>
			</view>
			<view class="action">
				<!--未关注-->
				<view class="action-item">关注</view>
				<!--已关注-->
				<!-- <view class="action-item">查看</view> -->
			</view>
		</view>
	</view>
</template>

<script>
	export default {
		name:"follow-official-account",
		data() {
			return {
				
			};
		}
	}
</script>

<style lang="scss" scoped>
.offical-account-container{
	padding: 16rpx;
	width: 750rpx;
	height: 168rpx;
	overflow: hidden;
	background-color: #fff;
	
	.top-text{
		color: #b2b2b2;
		font-size: 24rpx;
		height: 24rpx;
		line-height: 24rpx;
	}
	.content{
		display: flex;
		flex-direction: row;
		justify-content: space-between;
		align-items: center;
		width: 100%;
		margin-top:32rpx;
		height: 72rpx;
		.info{
			// padding-left: 20rpx;
			// padding-right: 20rpx;
			height: 80rpx;
			max-width: 474rpx;
			.name{
				font-size: 32rpx;
				color: #303133;
				overflow: hidden;
				white-space: nowrap;
				text-overflow: ellipsis;
			}
			.detial{
				font-size: 24rpx;
				color: #7f7f7f;
				overflow: hidden;
				white-space: nowrap;
				text-overflow: ellipsis;
			}
		}
		.logo{
			width: 80rpx;
			height: 80rpx;
			min-width: 80rpx;
			image{
				width: 100%;
				height: 100%;
			}
		}
		.action{
			// margin-right: 24rpx;
			padding-right: 8rpx;
			display: flex;
			align-items: center;
			justify-content: center;
			width: 130rpx;
			height: 80rpx;
			.action-item{
				width: 130rpx;
				height:56rpx;
				border:2rpx solid #1aad19;
				color: #1aad19;
				text-align: center;
				line-height: 56rpx;
				font-size: 28rpx;
				border-radius: 6rpx;
			}
		}
	}
}
</style>

后端需要安装SKIT.FlurlHttpClient.Wechat.Api组件
在这里插入图片描述

后端初始化client

小程序

/// <summary>
/// 客户端
/// </summary>
private WechatApiClient client;

public WeChatMiniProgramClient(IOptions<xxx> config)
{
    var options = new WechatApiClientOptions()
    {
        AppId = config.Value.AppId,
        AppSecret = config.Value.AppSecret,
        ImmeDeliveryAppKey = "",
        ImmeDeliveryAppSecret = "",
        VirtualPaymentAppKey = "",
        MidasOfferId = "",
        MidasAppKey = "",
        MidasOfferIdV2 = "",
        MidasAppKeyV2 = ""
    };
    client = WechatApiClientBuilder.Create(options).Build();
}

公众号

/// <summary>
/// 客户端
/// </summary>
private WechatApiClient client;

/// <summary>
/// 构造函数注入
/// </summary>
public OfficalAccountClient(IOptions<xxxx> config)
{
    var options = new WechatApiClientOptions()
    {
        AppId = config.Value.AppId,
        AppSecret = config.Value.AppSecret,
        ImmeDeliveryAppKey = "",
        ImmeDeliveryAppSecret = "",
        VirtualPaymentAppKey = "",
        MidasOfferId = "",
        MidasAppKey = "",
        MidasOfferIdV2 = "",
        MidasAppKeyV2 = "",
        PushToken = config.Value.Token,
        PushEncodingAESKey  =config.Value.EncodingAesKey
    };
    client = WechatApiClientBuilder.Create(options).Build();
}

小程序获取unionid

uni-app和小程序获取wx.login下的code

uni.login({
	provider:'weixin',
	success: function(res) {
		console.log(res);
		if (res.code) {
			console.log(res.code);
		} else {
			//login成功,但是没有取到code
			_this.$refs.uToast.show({
				title: '未取得code,请重试',
				type: 'error',
			})
		}
	},
	fail: function(res) {
		_this.$refs.uToast.show({
			title: '获取wx.login失败,请重试',
			type: 'error',
		})
	}
})
wx.login({
	success(res) {
		if (res.code) {
		//TODO 根据code获取openid以及unionid
		} else {
			wx.showToast({
				icon: "none",
				title: '获取信息失败',
			})
		}
	}
})

后端解析

/// <summary>
/// jscode获取openid
/// </summary>
/// <param name="jsCode"></param>
/// <returns></returns>
public async Task<(string openId,string unionId)> WeChatLogin(string jsCode)
{
    try
    {
        var userLoginRequest = new SnsJsCode2SessionRequest();
        userLoginRequest.JsCode = jsCode;
        var loginInfo = await client.ExecuteSnsJsCode2SessionAsync(userLoginRequest);
        if (loginInfo.IsSuccessful())
        {
            return (loginInfo.OpenId,loginInfo.UnionId ?? "");
        }
        return (string.Empty,string.Empty);
    }
    catch (Exception ex)
    {
        return (string.Empty,string.Empty);
    }
}

公众号获取unionid

根据openid获取unionid

/// <summary>
/// 获取关注公众号的用户详情
/// </summary>
/// <param name="openId"></param>
public async Task<(bool isSub,string errMsg,string openId,string unionId,string remark)> GetOfficalAccountFollowUserInfo(string openId)
{
    var accessToken = await WeChatGetOfficalAccountToken();
    var response = await _officalAccountClient.GetOfficalFllowAccountUserInfo(accessToken, openId);
    if (!response.IsSuccessful())
    {
        if (response.ErrorCode == 42001)
        {
            accessToken = await WeChatGetOfficalAccountToken(true);
            response = await _officalAccountClient.GetOfficalFllowAccountUserInfo(accessToken, openId);
            if (!response.IsSuccessful())
            {
                return (false,response.ErrorMessage, string.Empty, string.Empty,string.Empty);
            }
        }
        else
        {
            return (false,response.ErrorMessage, string.Empty, string.Empty,string.Empty);
        }
    }
    return (response.IsSubscribed, string.Empty, response.OpenId,response.UnionId,response.Remark);
}

/// <summary>
/// 获取关注公众号的多个用户详情
/// </summary>
/// <param name="openIds"></param>
public async Task<(bool isSuccess,string errMsg,List<CgibinUserInfoBatchGetResponse.Types.User> list)> GetOfficalAccountFollowUsersInfo(List<string> openIds)
{
    var accessToken = await WeChatGetOfficalAccountToken();
    var response = await _officalAccountClient.GetOfficalFllowAccountUsersInfo(accessToken, openIds);
    if (!response.IsSuccessful())
    {
        if (response.ErrorCode == 42001)
        {
            accessToken = await WeChatGetOfficalAccountToken(true);
            response = await _officalAccountClient.GetOfficalFllowAccountUsersInfo(accessToken, openIds);
            if (!response.IsSuccessful())
            {
                return (false,response.ErrorMessage,null);
            }
        }
        else
        {
            return (false,response.ErrorMessage,null);
        }
    }
    return (true,response.ErrorMessage,response.UserList.ToList());
}

在这里插入图片描述
在这里插入图片描述

用户关注/取消关注公众号监听

申请测试号,主要是提前写好接口
在这里插入图片描述
接入指南
在这里插入图片描述

/// <summary>
/// 微信公众号服务器传递过来的消息验证
/// </summary>
/// <returns></returns>
public bool ValidateMsg(string timestamp,string nonce,string signature)
{
    /* 验证微信服务器 */
    bool ret = client.VerifyEventSignatureForEcho(
        webhookTimestamp: timestamp,
        webhookNonce: nonce,
        webhookSignature: signature
    );
    return ret;
}

class XXXParamModel 
{
    /// <summary>
    /// 微信加密签名,signature
    /// 结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
    /// </summary>
    public string Signature { get; set; }
    
    /// <summary>
    /// 时间戳
    /// </summary>
    public string Timestamp { get; set; }

    /// <summary>
    /// 随机数
    /// </summary>
    public string Nonce { get; set; }
    
    /// <summary>
    /// 随机字符串
    /// </summary>
    public string Echostr { get; set; }
}

/// <summary>
/// 微信公众号验证
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
[HttpGet]
public IActionResult OfficalAccountValidate([FromQuery]XXXParamModel param)
{
    var isValid = _officalAccountWechatHelper.ValidateMsg(param.Timestamp, param.Nonce, param.Nonce);
    if (isValid)
    {
        //返回随机字符串则表示验证通过
        return Content(param.Echostr);
    }
    return Content("");
}

本地测试做好内网穿透

点击提交即可,验证通过即可
在这里插入图片描述
通过之后可以点击启用
在这里插入图片描述
需要处理菜单
在这里插入图片描述
在这里插入图片描述

//允许多次读取
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
    //允许body重用
    app.Use(next => context =>
    {
        context.Request.EnableBuffering();
        return next(context);
    })
}

/// <summary>
/// 微信公众号消息处理
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> OfficalAccountMessageHandler([FromQuery]XXXParamModel param)
{
    var isValid = _officalAccountWechatHelper.ValidateMsg(param.Timestamp, param.Nonce, param.Signature);
    if (!isValid)
    {
        return Content("验证失败");
    }

    //过滤器或者中间件中调用
    var body = Request.Body;
    if (body.CanSeek)
    {
        body.Seek(0L, SeekOrigin.Begin);
    }
    string bodyData = string.Empty;
    bodyData =await new StreamReader(body, Encoding.UTF8).ReadToEndAsync();
    Console.WriteLine(bodyData);
    return Content("");
}

在这里插入图片描述
解密

/// <summary>
/// 解密xml数据
/// </summary>
/// <param name="xmlData"></param>
/// <returns></returns>
public WechatApiEvent DecryptXmlData(string xmlData)
{
    var xml = client.DeserializeEventFromXml(xmlData);
    return xml;
}

消息处理的完整的代码

/// <summary>
/// 微信公众号消息处理
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> OfficalAccountMessageHandler(
[FromQuery] OfficalAccountMessageHandlerParamModel param)
{
string responseData = string.Empty;
var isValid = _officalAccountWechatHelper.ValidateMsg(param.Timestamp, param.Nonce, param.Signature);
if (!isValid)
{
    return Content("验证失败");
}

//过滤器或者中间件中调用
var body = Request.Body;
if (body.CanSeek)
{
    body.Seek(0L, SeekOrigin.Begin);
}
string bodyData = string.Empty;
bodyData = await new StreamReader(body, Encoding.UTF8).ReadToEndAsync();
var decryptXmlData = _officalAccountWechatHelper.DecryptXmlData(bodyData);
switch (decryptXmlData.MessageType.ToLower())
{
    case RequestMsgType.Text:
        break;
    case RequestMsgType.Location:
        break;
    case RequestMsgType.Image:
        break;
    case RequestMsgType.Voice:
        break;
    case RequestMsgType.Video:
        break;
    case RequestMsgType.ShortVideo:
        break;
    case RequestMsgType.Link:
        break;
    case RequestMsgType.MessageEvent:
        var eventType = decryptXmlData.Event;
        var openId = decryptXmlData.FromUserName;
        if (!string.IsNullOrEmpty(eventType))
        {
            switch (eventType)
            {
                //关注
                case EventType.Subscribe:
                    //TODO 关注之后的处理
                    //发送被动回复消息
                    responseData =_officalAccountWechatHelper.SendTxtMsg("欢迎关注~~", openId, decryptXmlData.ToUserName);
                    Console.WriteLine(responseData);
                    break;
                //取消关注
                case EventType.Unsubscribe:
                    //TODO 取消关注之后的处理
                    break;
                case EventType.Localtion:
                    break;
                default:
                    break;
            }
        }
        break;
    default:
        break;
}
return Content(responseData);
}

已有数据如何处理

公众号有获取关注的列表,根据列表中的openid获取unionid,小程序也有保存unionid,根据unionid关联起来就能实现之前数据的处理,不过没有登陆过的客户应该没法处理了,需要再客户登陆的时候获取unionid然后进行判定

公众号获取关注的列表

/// <summary>
/// 获取关注的用户列表
/// </summary>
/// <param name="token"></param>
/// <param name="nextOpenid"></param>
public async Task<CgibinUserGetResponse> GetOfficalAccountFollowUserList(string token,string nextOpenid="")
{
    var request = new CgibinUserGetRequest();
    request.AccessToken = token;
    if (!string.IsNullOrEmpty(nextOpenid))
    {
        request.NextOpenId = nextOpenid;
    }
    var userGetResponse = await client.ExecuteCgibinUserGetAsync(request);
    return userGetResponse;
}

关注公众号之后给用户发送消息

在这里插入图片描述
进行xml拼接加密处理,然后在微信调用关注的事件通知的时候返回即可

/// <summary>
/// 获取unix时间戳,扩展方法
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
public static long GetUnixTimeStampSeconds(this DateTime dt)
{
    long unixTime = ((DateTimeOffset)dt).ToUnixTimeSeconds();
    return unixTime;
}

/// <summary>
/// 解密文本的xml数据
/// </summary>
/// <param name="xmlData"></param>
/// <returns></returns>
public string SendTxtMsg(string content, string toOpenId, string fromOpenId)
{
    var replyModel = new SKIT.FlurlHttpClient.Wechat.Api.Events.TextMessageReply()
    {
        ToUserName = toOpenId,
        FromUserName = fromOpenId,
        MessageType = "text",
        Content = content,
        CreateTimestamp = DateTime.Now.GetUnixTimeStampSeconds()
    };
    string replyXml = client.SerializeEventToXml(replyModel);
    return replyXml;
}

参考
uni-app使用微信小程序原生组件

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】驱动_2_字符驱动

1. Linux设备分类 字符设备: 指应用程序按字节/字符来读写数据的设备。通常为传真、虚拟终端和串口调制解调器、键盘之类设备提供流通信服务&#xff0c;通常不支持随机存取数据。字符设备在实现时大多不使用缓存器。系统直接从设备读/写每一个字符。块设备: 通常支持随机存取…

Jenkins 打包报错记录 error: index-pack died of signal 15

问题背景&#xff0c;打包每次到92%时就会报错&#xff0c;试了好几次都是同样的错误 14:56:53 fatal: index-pack failed 14:56:53 14:56:53 at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandIn(CliGitAPIImpl.java:2734) 14:56:53 at org.jenkinsci.plugi…

GoLand远程开发IDE:使用SSH远程连接服务器进行云端编程

目录 ⛳️推荐 1. 安装配置GoLand 2. 服务器开启SSH服务 3. GoLand本地服务器远程连接测试 4. 安装cpolar内网穿透远程访问服务器端 4.1 服务器端安装cpolar 4.2 创建远程连接公网地址 5. 使用固定TCP地址远程开发 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&am…

【复现】金和OA-jc6 RCE漏洞_74

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 金和C6协同管理平台包括协同办公管理,人力资源管理,项目管理,客户关系管理,企业目标管理,费用管理,移动办公,微信办公等多个业务范…

Java——内存溢出如何排查

1、模拟内存移除场景 public class OOMTest {public static void main(String[] args) {List<byte[]> memoryLeakArray new ArrayList<>();for (int i 0; i<1024; i){byte[] bytes new byte[1024 * 1024];memoryLeakArray.add(bytes);}} }初始化启动参数最大…

小心!那个走了的员工可能带走了公司的秘密

数据泄露是企业安全的一大隐患&#xff0c;尤其是离职员工带走公司数据的问题&#xff0c;这是一种常被忽视的内部威胁。离职员工可能因为种种原因&#xff0c;带走了他们曾经可以访问的公司数据。而这些数据如果落入了不当的地方&#xff0c;可能会给企业带来严重的损害。那么…

力扣数据库题库学习(4.24日)

1068. 产品销售分析 I 问题链接 思路分析 编写解决方案&#xff0c;以获取 Sales 表中所有 sale_id 对应的 product_name 以及该产品的所有 year 和 price 。返回结果表 无顺序要求 。 这个问题很简单&#xff0c;查询两张表内的指定字段。这个考的其实就是数据库的连接&am…

23种设计模式(Java版,超详细!)

文章目录 一、什么是设计模式二、设计模式的分类三、设计模式的基本要素四、23种设计模式概览五、设计模式间的关系六、设计模式详解6.1. 工厂方法模式&#xff08;Factory Method&#xff09;6.2. 抽象工厂模式&#xff08;Abstract Factory&#xff09;6.3. 建造者模式&#…

屏幕状态自动检测+鼠标自动操作

目录 一、写在前面 1.1适用场景 1.2涉及到的库 二、函数库 2.1pyautogui-屏幕截图&鼠标操作 2.1.1屏幕截图screenshot函数 2.1.2鼠标移动及单击 2.2Opencv-模板匹配 2.2.1matchTemplate函数 2.2.2minMaxLoc函数 2.2.3相关代码 2.3base64-图片转base64 2.3.1在线…

【行为型模式】模板方法模式

一、模板方法模式概述 模板方法模式定义&#xff1a;在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。(类对象型模式) 模板方法中的基本方法是实现算法的各个步骤&#xff0c;是模板方法的…

谷歌搜索SEO优化需要做什么?

最基本的要求&#xff0c;网站基础要优化好&#xff0c;让你的网站更加友好地服务于用户和搜索引擎&#xff0c;首先你要保证你的网站也适配手机端&#xff0c;现在手机端&#xff0c;如果你的网站在手机上打开慢&#xff0c;或者没有适配手机端&#xff0c;让用户用手机看着电…

Echarts X轴类目名太长时隐藏显示全部

echarts图表X轴 在柱状图中,X轴类目名如果数据太长; echarts会默认进行隐藏部分字段; 如果我们想让每一个类目名都显示出来,需要进行额外的处理X轴类目名太长时,默认只显示一部分类目名 <!DOCTYPE html> <html lang="en"> <head><meta ch…

硬实力!神工坊团队在首届开放原子开源大赛中斩获一二等奖

日前&#xff0c;首届开放原子开源大赛苏州站在苏州工业园区顺利开赛&#xff0c;神工坊团队在“大规模非对称不定带宽线性代数方程组求解算法赛”中表现非凡&#xff0c;斩获一二等奖&#xff01; “大规模非对称不定带宽线性代数方程组求解算法赛”是“开放原子开源大赛”工业…

画机柜布置图就这么简单,你学会了吗?

你还在使用excel画机柜布置图&#xff1f; 你还在使用CAD画机柜布置图&#xff1f; 你还在使用Visio画机柜布置图&#xff1f; 我们今天都在使用nVisual画机柜布置图&#xff01; 第一步&#xff1a;登录注册cloud.nVisual.com云平台&#xff0c;免费使用Visual&#xff1b; 第…

C语言趣味代码(三)

这一篇主要围绕写一个程序---寻找数字 来写&#xff0c;在这篇我会详细和大家介绍基本实现以及它的改良版&#xff0c;还有相关知识的拓展&#xff0c;干货绝对满满。 1. 寻找数字 在这一主题下&#xff0c;我们会编写一些代码&#xff0c;来锻炼玩家的反应力&#xff0c;同时…

第6章 Mybatis高级查询(详解篇)

@[TOC](第6章 Mybatis高级查询(详解篇)) 1. 一对一映射 1.1 自动映射(关联的嵌套结果映射) <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd…

【总结】hbase master重启恢复失败问题修复

问题现象 最近hbase master 莫名其妙宕机了&#xff0c;查看最后输出日志&#xff0c;也没有发现有效信息。 于是想着先重启一把&#xff0c;在hbase master 选主成active状态的过程中&#xff0c;发现重启多次都很漫长&#xff0c;且最终因重启时间过长&#xff0c;被hbase-…

【NMPA-国家药品监督管理局】

NMPA-国家药品监督管理局 ■ NMPA简介■ (1) 监管逻辑■ 1.1&#xff09;注册检验■ 1.2&#xff09;临床试验■ 1.3&#xff09;体系考核■ 1.4&#xff09;专家评审■ 1.5&#xff09;飞行检查 ■ (2) 上市流程■ 2.1&#xff09;注册申请&#xff1a;■ 2.2&#xff09;注册…

水滴式粉碎机:高效、精细破碎利器

水滴式粉碎机是一种适用于多种物料的粉碎设备。它能够处理硬质物料如石头、陶瓷、玻璃等&#xff0c;也能粉碎食品和饲料原料如大米、小麦、玉米等。此外&#xff0c;水滴式粉碎机还适用于秸秆、木材等物料的粉碎。部分水滴式粉碎机还具备粗粉碎和细粉碎两种功能&#xff0c;可…

重发布实验:

要求&#xff1a; 配置&#xff1a; 配置IP地址&#xff1a; Ar1&#xff1a; [a1]int g 0/0/0 [a1-GigabitEthernet0/0/0]ip add 100.1.1.1 24 [a1-GigabitEthernet0/0/0]int l 0 [a1-LoopBack0]ip add 192.168.0.1 32 [a1-LoopBack0]int l1 [a1-LoopBack1]ip add 192…