一、准备工作
微信公众平台: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即可。
再再一次值得注意的是:申请的测试号调试推送时,代码里的自定义模板消息不生效,切到正式环境就好啦。
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这
最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru
在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo
我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除
我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha