jjzjj

C#服务号推送微信公众号模板消息

余予渔与鱼腴 2023-12-17 原文

一、准备工作

微信公众平台: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步

  1. 获取access_token(把token存缓存,不用每次请求)

  1. 获取公众号的所有标签,得到目标分组

  1. 获取指定标签下的粉丝列表,得到用户的openid

  1. 给粉丝推送模板消息

三、后端代码

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小时内互动关注过的用户。

有推送图文需求的小伙伴的话,两个笨办法勉强能实现

  1. 开发完推送模板消息接口后,url再加个图片链接,用户应该可以点击模版消息跳转图片。

  1. 编辑完群发图文后存为草稿 ,然后输入用户的微信ID给用户发测试图文 (适合用户不多的情况)

值得注意的是:微信推送消息模板的ID来自于微信后台设置的所在行业,选择不同的行业属性会相应提供对应的消息模板,调用推送消息接口的时候去后台找到相应的模板ID即可。

再再一次值得注意的是:申请的测试号调试推送时,代码里的自定义模板消息不生效,切到正式环境就好啦。

有关C#服务号推送微信公众号模板消息的更多相关文章

  1. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用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请求没有正确的命名空间。任何人都可以建议我

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  4. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的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

  5. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在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

  6. ruby-on-rails - 如何在 Rails View 上显示错误消息? - 2

    我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c

  7. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  8. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  9. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

    您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

  10. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

随机推荐