一、准备工作
微信公众平台:https://mp.weixin.qq.com/
申请测试账号:https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
鉴于一些兄弟需要jsonhelper,特放百度网盘,需要自取
链接:https://pan.baidu.com/s/1fceTTPQUqiYHL6QU7s1row
提取码:gv4v
微信推送消息模板不需要发布服务器,也不需要填写授权回调域名,只需要两个微信后台的参数
在公众号后台设置与开发——>基本配置中获取AppID、AppSecret
二、实现思路
我的需求是给特定分组的用户推送模板消息,分为以下4步
获取access_token(把token存缓存,不用每次请求)
获取公众号的所有标签,得到目标分组
获取指定标签下的粉丝列表,得到用户的openid
给粉丝推送模板消息
三、后端代码
1.获取AccessToken
/// <summary>
/// Access_token 的摘要说明
/// </summary>
public class Access_token
{
public string access_token{ get;set; }
public int expires_in { get; set; } //凭证有效时间
public string errcode { get; set; } //返回码
public string errmsg { get; set; } //返回说明
public DateTime CreateTime { get; set; }
}
//token缓存键值对
private static Dictionary<string, Access_token> tokenCache = new Dictionary<string, Access_token>();
//获取access_toke
/// <summary>
/// 获取缓存令牌
/// </summary>
public static string GetAccessToken(string appid, string secret)
{
//token缓存
Access_token result = null;
//判断缓存是否存在键:appid,就将缓存中的token赋给result
if (tokenCache.ContainsKey(appid))
{
result = tokenCache[appid];
}
//不存在则获取token
if (result == null)
{
result = GetToken(appid, secret);
result.CreateTime = DateTime.Now;
tokenCache.Add(appid, result);
}
//判断是否在有效期内,过期重新获取token 给10s延迟时间
else if (System.DateTime.Compare(result.CreateTime.AddSeconds(result.expires_in), System.DateTime.Now) < 7200)
{
result = GetToken(appid, secret);
result.CreateTime = DateTime.Now;
tokenCache[appid] = result;
}
return result.access_token;
}
/// <summary>
/// 获取AccessToken
/// </summary>
/// <returns></returns>
public static Access_token GetToken(string appid, string secret)
{
string grant_type = "client_credential";
string tokenUrl = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&appid={1}&secret={2}", grant_type, appid, secret);
var wc = new WebClient();
var strReturn = wc.DownloadString(tokenUrl);
//得到token
Access_token tokeninfo = JsonHelper.ToJson<Access_token>(strReturn);
return tokeninfo;
}
2.得到所有标签
static JavaScriptSerializer jszer = new JavaScriptSerializer();
/// <summary>
/// 得到所有标签
/// </summary>
/// <param name="accesstoken"></param>
/// <returns></returns>
public static int GetTagList(string accesstoken)
{
int pagecode=0;
//得到所有标签
string tagUrl = string.Format("https://api.weixin.qq.com/cgi-bin/tags/get?access_token={0}", accesstoken);
var wc = new WebClient();
wc.Encoding = System.Text.Encoding.UTF8;
var strReturn = wc.DownloadString(tagUrl);
Root list = jszer.Deserialize<Root>(strReturn);
//循环遍历所有标签,如果是移动时则赋值pagecode,循环停止
if (list != null)
{
foreach (var item in list.tags)
{
if (item.name == "移动")
{
pagecode = item.id;
break;
}
}
return pagecode;
}
else
{
return pagecode;
}
}
public class Tags
{
/// <summary>
///
/// </summary>
public int id { get; set; }
/// <summary>
/// 星标组
/// </summary>
public string name { get; set; }
/// <summary>
///
/// </summary>
public int count { get; set; }
}
public class Root
{
/// <summary>
///
/// </summary>
public List<Tags> tags { get; set; }
}
3.获取标签下粉丝列表,得到用户的openid
/// <summary>
/// 获取标签下粉丝列表
/// </summary>
/// <returns></returns>
public static List<string> GetTagUserList(string token)
{
List<string> list = new List<string>();
int tagecode = GetTagList(token);
string tokenUrl = string.Format("https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token={0}", token);
PostTage strpost = new PostTage();
strpost.tagid = tagecode;
strpost.next_openid = "";
HttpWebRequest hwr = WebRequest.Create(tokenUrl) as HttpWebRequest;
hwr.Method = "POST";
hwr.ContentType = "application/x-www-form-urlencoded";
byte[] payload;
payload = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(strpost)); //通过UTF-8编码
hwr.ContentLength = payload.Length;
Stream writer = hwr.GetRequestStream();
writer.Write(payload, 0, payload.Length);
writer.Close();
var result = hwr.GetResponse() as HttpWebResponse; //此句是获得上面URl返回的数据
var responseReader = new StreamReader(result.GetResponseStream());
TagUserList codestate = JsonHelper.ToJson<TagUserList>(responseReader.ReadToEnd());
if (codestate != null && codestate.data != null)
{
list.AddRange(codestate.data.openid);
}
return list;
}
public class PostTage
{
//最后一个用户的openid
public int tagid { get; set; }
//第一个拉取的OPENID,不填默认从头开始拉取
public string next_openid { get; set; }
}
public class Data
{
/// <summary>
///
/// </summary>
public List<string> openid { get; set; }
}
public class TagUserList
{
/// <summary>
///
/// </summary>
public int count { get; set; }
/// <summary>
///
/// </summary>
public Data data { get; set; }
/// <summary>
///
/// </summary>
public string next_openid { get; set; }
}
4.给用户推送模板消息
/// <summary>
/// 公众号发送推送消息
/// </summary>
public async Task<bool> SendMessage(string strtoken, List<string> oplist) //string strtoken)
{
bool istrue = false;
//根据access_token构建推送接口
string sendUrl = $@"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={strtoken}";
System.Net.Http.HttpClient sendclient = new System.Net.Http.HttpClient();
MessageTemplateSendDto mts = new MessageTemplateSendDto();
mts.template_id = ConfigurationManager.AppSettings["template_id"];
//创建list 循环这里
//必填 接收者openid
foreach (var item in oplist)
{
mts.touser = item;
//构建请求数据对象
//必填 模板数据
mts.data = new MessageTemplateSendDataDto
{
first = new MessageTemplateSendDataContentDto()
{
value = "工作任务提示消息",
color = "#173177"
},
keyword1 = new MessageTemplateSendDataContentDto()
{
value = "通知消息",
color = "#173177"
},
keyword2 = new MessageTemplateSendDataContentDto()
{
value = "张三",
color = "#173177"
},
keyword3 = new MessageTemplateSendDataContentDto()
{
value = "截止:02月14日,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
color = "#173177"
},
keyword4 = new MessageTemplateSendDataContentDto()
{
value = DateTime.Now.ToShortDateString(),
color = "#173177"
},
remark = new MessageTemplateSendDataContentDto()
{
value = "请及时查看",
color = "#173177"
}
};
try
{
HttpWebRequest hwr = WebRequest.Create(sendUrl) as HttpWebRequest;
hwr.Method = "POST";
hwr.ContentType = "application/x-www-form-urlencoded";
byte[] payload;
payload = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(mts)); //通过UTF-8编码
hwr.ContentLength = payload.Length;
Stream writer = hwr.GetRequestStream();
writer.Write(payload, 0, payload.Length);
writer.Close();
var result = hwr.GetResponse() as HttpWebResponse; //此句是获得上面URl返回的数据
var responseReader = new StreamReader(result.GetResponseStream());
WxOpenidCodeState codestate = JsonHelper.ToJson<WxOpenidCodeState>(responseReader.ReadToEnd());
if (codestate.errcode == "0")
{
istrue = true;
}
}
catch (Exception e)
{
var t = e.Message;
istrue = false;
}
}
return istrue;
}
public class MessageTemplateSendDto
{
/// <summary>
/// 必填
/// 接收者openid
/// </summary>
public string touser { get; set; }
/// <summary>
/// 必填
/// 模板ID
/// </summary>
public string template_id { get; set; }
/// <summary>
/// 必填
/// 模板数据
/// </summary>
public MessageTemplateSendDataDto data { get; set; }
/// <summary>
/// 模板内容字体颜色,不填默认为黑色
/// </summary>
public string color { get; set; }
/// <summary>
/// 防重入id。对于同一个openid + client_msg_id, 只发送一条消息,10分钟有效,超过10分钟不保证效果。若无防重入需求,可不填
/// </summary>
public string client_msg_id { get; set; }
}
public class MessageTemplateSendDataDto
{
public MessageTemplateSendDataContentDto first { get; set; }
public MessageTemplateSendDataContentDto keyword1 { get; set; }
public MessageTemplateSendDataContentDto keyword2 { get; set; }
public MessageTemplateSendDataContentDto keyword3 { get; set; }
public MessageTemplateSendDataContentDto keyword4 { get; set; }
public MessageTemplateSendDataContentDto remark { get; set; }
}
public class MessageTemplateSendDataContentDto
{
/// <summary>
/// 文本内容
/// </summary>
public string value { get; set; }
/// <summary>
/// 文本颜色
/// </summary>
public string color { get; set; }
}
public class WxOpenidCodeState
{
//返回code
public string errcode { get; set; }
// 返回消息
public string errmsg { get; set; }
}
5.调用方法
//获取access_token
string strtoken = WXApi.GetAccessToken(appid, secret);
//获取关注用户列表
List<string> oplist = WXApi.GetTagUserList(strtoken);
//发送模板消息
Task<bool> istrue = SendMessage(strtoken, oplist);
6.配置文件
AppID和AppSecret及模板ID为了安全尽量别写在页面上,建议写在配置文件再在页面调用
配置文件
<appSettings>
<!--微信的Token-->
<add key="WeixinToken" value="weiphp" />
<!--开发者ID(AppID)-->
<add key="AppId" value="wxe9XXXXXXXXXX" />
<!--开发者密匙-->
<add key="AppSecret" value="804XXXXXXXXXXXXXXXXXXXXX" />
<!--模板ID-->
<add key="template_id" value="0hXXXXXXXXXXXXXXXX" />
</appSettings>
页面
//公众号的appid|secret
static string appid = ConfigurationManager.AppSettings["AppId"];
static string secret = ConfigurationManager.AppSettings["AppSecret"];
一开始纠结了好久推送图文模板消息,研究了下发现
1.微信只提供发送文字模板功能,未提供推送图文的接口,图文消息只能通过后台的群发功能,但是一个月只能发4次。
2.开通高级群发功能,一个月可以有400次推送机会,但单个用户也只能接收4次。
3.可以通过第三方平台可以实现每天的服务号群发功能,但第三方平台服务内容包含无限次推送微信模版消息和群发图文给48小时内互动关注过的用户。
有推送图文需求的小伙伴的话,两个笨办法勉强能实现
开发完推送模板消息接口后,url再加个图片链接,用户应该可以点击模版消息跳转图片。
编辑完群发图文后存为草稿 ,然后输入用户的微信ID给用户发测试图文 (适合用户不多的情况)
值得注意的是:微信推送消息模板的ID来自于微信后台设置的所在行业,选择不同的行业属性会相应提供对应的消息模板,调用推送消息接口的时候去后台找到相应的模板ID即可。
再再一次值得注意的是:申请的测试号调试推送时,代码里的自定义模板消息不生效,切到正式环境就好啦。