首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

微信高級群發接口開發全流程:從素材上傳到 OpenID 精準推送

     編輯:關於C語言

業務場景

管理後台需支援三類群發:文字、圖片、圖文消息。圖文消息包含標題、內容、封面圖、跳轉鏈接,封面圖必須先上傳至微信獲取 media_id,再組裝圖文素材上傳,最後按 OpenID 列表推送。

核心流程圖解

  1. 準備本地素材:資料庫存相對路徑,MapPath 轉物理路徑。
  2. 上傳封面圖 → 取得 media_id:POST /cgi-bin/media/upload?type=image。
  3. 組裝並上傳圖文素材 → 取得 news_id:POST /cgi-bin/media/uploadnews,JSON 含多篇文章。
  4. 拉取全量 OpenID:分頁呼叫 /cgi-bin/user/get。
  5. 構建群發 JSON 並發送:POST /cgi-bin/message/mass/send,msgtype=mpnews。

關鍵程式碼模組

1. 檔案上傳工具(multipart/form-data)

public static string HttpUploadFile(string url, string filePath)
{
    string boundary = DateTime.Now.Ticks.ToString("x");
    var req = (HttpWebRequest)WebRequest.Create(url);
    req.Method = "POST";
    req.ContentType = $"multipart/form-data;charset=utf-8;boundary={boundary}";
    req.CookieContainer = new CookieContainer();
    req.AllowAutoRedirect = true;

    byte[] boundaryBytes = Encoding.UTF8.GetBytes($"\r\n--{boundary}\r\n");
    byte[] endBoundaryBytes = Encoding.UTF8.GetBytes($"\r\n--{boundary}--\r\n");

    string fileName = Path.GetFileName(filePath);
    string header = $"Content-Disposition:form-data;name=\"file\";filename=\"{fileName}\"\r\nContent-Type:application/octet-stream\r\n\r\n";
    byte[] headerBytes = Encoding.UTF8.GetBytes(header);
    byte[] fileBytes = File.ReadAllBytes(filePath);

    using (var postStream = req.GetRequestStream())
    {
        postStream.Write(boundaryBytes, 0, boundaryBytes.Length);
        postStream.Write(headerBytes, 0, headerBytes.Length);
        postStream.Write(fileBytes, 0, fileBytes.Length);
        postStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
    }

    using (var resp = (HttpWebResponse)req.GetResponse())
    using (var stream = resp.GetResponseStream())
    using (var reader = new StreamReader(stream, Encoding.UTF8))
        return reader.ReadToEnd(); // {"media_id":"...","created_at":...}
}

2. 微信 API 封裝

public static class WXApi
{
    // 上傳圖片/縮圖
    public static string UploadMedia(string accessToken, string type, string filePath)
    {
        string url = $"https://file.api.weixin.qq.com/cgi-bin/media/upload?access_token={accessToken}&type={type}";
        return HttpRequestUtil.HttpUploadFile(url, filePath);
    }

    // 上傳圖文素材
    public static string UploadNews(string accessToken, string articlesJson)
    {
        string url = $"https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token={accessToken}";
        return HttpRequestUtil.PostUrl(url, articlesJson);
    }

    // 獲取所有關注者 OpenID(自動分頁)
    public static List GetOpenIDs(string accessToken)
    {
        var result = new List();
        string nextOpenId = null;
        do
        {
            string url = $"https://api.weixin.qq.com/cgi-bin/user/get?access_token={accessToken}&next_openid={nextOpenId ?? ""}";
            string json = HttpRequestUtil.RequestUrl(url);
            int count = int.Parse(Tools.GetJsonValue(json, "count"));
            if (count == 0) break;

            string openIdBlock = Tools.GetJsonValue(json, "openid"); // 簡化:實際需解析 data.openid 陣列
            // 這裡需完整解析 JSON 陣列,建議改用 Json.NET
            // 範例略過,實際專案請用 JObject.Parse(json)["data"]["openid"].ToObject>()
            nextOpenId = count == 10000 ? /* 最後一個 openid */ null : null;
            result.AddRange(/* 解析出的 openid 列表 */);
        } while (nextOpenId != null);
        return result;
    }

    // 發送群發
    public static string Send(string accessToken, string postData)
    {
        string url = $"https://api.weixin.qq.com/cgi-bin/message/mass/send?access_token={accessToken}";
        return HttpRequestUtil.PostUrl(url, postData);
    }
}

3. 圖文素材 JSON 組裝

public static string GetArticlesJsonStr(PageBase page, string accessToken, DataTable dt)
{
    var sb = new StringBuilder("{\"articles\":[");
    for (int i = 0; i < dt.Rows.Count; i++)
    {
        var dr = dt.Rows[i];
        string physPath = page.MapPath(dr["ImgUrl"].ToString());
        if (!File.Exists(physPath)) return "{\"code\":0,\"msg\":\"圖片不存在\"}";

        string mediaJson = WXApi.UploadMedia(accessToken, "image", physPath);
        string mediaId = Tools.GetJsonValue(mediaJson, "media_id");

        sb.Append("{");
        sb.Append($"\"thumb_media_id\":\"{mediaId}\",");
        sb.Append($"\"author\":\"{dr["Author"]}\",");
        sb.Append($"\"title\":\"{dr["Title"]}\",");
        sb.Append($"\"content_source_url\":\"{dr["TextUrl"]}\",");
        sb.Append($"\"content\":\"{dr["Content"]}\",");
        sb.Append($"\"digest\":\"{dr["Content"]}\",");
        sb.Append($"\"show_cover_pic\":\"1\"");
        sb.Append(i == dt.Rows.Count - 1 ? "}" : "},");
    }
    sb.Append("]}");
    return sb.ToString();
}

4. 群發 JSON 構建與發送入口

public static class WXMsgUtil
{
    public static string CreateNewsJson(string mediaId, List openIds)
    {
        var sb = new StringBuilder();
        sb.Append("{\"touser\":[");
        sb.Append(string.Join(",", openIds.Select(id => $"\"{id}\"")));
        sb.Append("],\"msgtype\":\"mpnews\",\"mpnews\":{\"media_id\":\"");
        sb.Append(mediaId);
        sb.Append("\"}}");
        return sb.ToString();
    }
}

// 頁面按鈕事件
public string Send()
{
    string type = Request["type"]; // 1=文字 2=圖片 3=圖文
    string data = Request["data"];
    string token = AdminUtil.GetAccessToken(this);
    var openIds = WXApi.GetOpenIDs(token);
    string resultMsg = null;

    if (type == "3") // 圖文
    {
        var dt = ImgItemDal.GetImgItemTable(loginUser.OrgID, data);
        string articlesJson = ImgItemDal.GetArticlesJsonStr(this, token, dt);
        string newsJson = WXApi.UploadNews(token, articlesJson);
        string newsId = Tools.GetJsonValue(newsJson, "media_id");
        resultMsg = WXApi.Send(token, WXMsgUtil.CreateNewsJson(newsId, openIds));
    }
    // ... 文字、圖片類似邏輯略

    // 統一結果處理
    string errcode = Tools.GetJsonValue(resultMsg, "errcode");
    return errcode == "0" 
        ? "{\"code\":1,\"msg\":\"\"}" 
        : $"{{\"code\":0,\"msg\":\"errcode:{errcode}, errmsg:{Tools.GetJsonValue(resultMsg, "errmsg")}\"}}";
}

避坑指南

  • 圖片大小限制:封面圖 ≤ 64 KB、640×320,超規格會上傳失敗。
  • media_id 時效:圖片/圖文 media_id 3 天有效,群發需在有效期內完成。
  • OpenID 分頁:單次最多 10000 個,務必用 next_openid 迴圈拉取全量。
  • JSON 轉義:手動拼接 JSON 風險極高,**強烈建議改用 Json.NET 序列化強型別模型**,避免逗號、引號、換行導致 40013/40014 錯誤。
  • 頻率限制:群發介面每日調用上限(認證號 100 次/日),做好配額檢查與用戶提示。

結語

本文梳理了微信高級群發的完整鏈路:本地素材 → 微信媒體庫 → 圖文素材 → 精準推送,並給出可直接落地的程式碼骨架。實際專案中請務必引入 Json.NET 替換手工字串拼接,並加入重試、日誌、配額監控,構建生產級穩定服務。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved