.NET生成微信小程式推廣二維碼

追逐時光者發表於2023-11-28

前言

對於小程式大家可能都非常熟悉了,隨著小程式的不斷普及越來越多的公司都開始推廣使用起來了。今天接到一個需求就是生成小程式碼,並且與運營給的推廣圖片合併在一起做成一張漂亮美觀的推廣二維碼,掃碼這種二維碼就可以進入小程式。為了節省伺服器記憶體資源,我想的就是成功呼叫通微信生成小程式碼的介面後直接把微信返回過來的圖片二進位制內容(返回的圖片 Buffer)轉化為二進位制byte[]檔案流,然後再轉成Image這樣就不需要在儲存到本地直接讀取本地的背景圖片透過GDI+(Graphics)繪製圖片。

選擇小程式碼生成方式

首先微信小程式官方文件提供了三種生成小程式碼的方法,如下所示(本文采用的是第三種,需要的碼數量極多的業務場景):

文件詳情地址:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/qr-code/getQRCode.html

1、createwxaqrcode獲取小程式二維碼,適用於需要的碼數量較少的業務場景。透過該介面生成的小程式碼,永久有效,有數量限制。

2、getwxacode獲取小程式碼,適用於需要的碼數量較少的業務場景。透過該介面生成的小程式碼,永久有效,有數量限制。

3、getwxacodeunlimit獲取小程式碼,適用於需要的碼數量極多的業務場景。透過該介面生成的小程式碼,永久有效,數量暫無限制。

獲取全域性唯一後臺介面呼叫憑據

對接開發過微信相關的業務的同學應該都清楚,呼叫微信介面很多情況下都會需要使用到access_token介面呼叫憑證。一般來說access_token的有效時長為2小時,為了不頻繁呼叫該介面我們可以透過快取的方法把呼叫憑證存起來並設定合理的過期時間(redis,cookie,memorycache都是非常不錯的選擇)。

文件詳情地址:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html

請求介面

GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET 

請求引數

屬性型別必填說明
grant_type string 填寫 client_credential
appid string 小程式唯一憑證,即 AppID,可在「微信公眾平臺 - 設定 - 開發設定」頁中獲得。(需要已經成為開發者,且賬號沒有異常狀態)
secret string 小程式唯一憑證金鑰,即 AppSecret,獲取方式同 appid

返回引數

屬性型別說明
access_token string 獲取到的憑證
expires_in number 憑證有效時間,單位:秒。目前是7200秒之內的值。

access_token 的儲存與更新

  • access_token 的儲存至少要保留 512 個字元空間;
  • access_token 的有效期目前為 2 個小時,需定時重新整理,重複獲取將導致上次獲取的 access_token 失效;
  • 建議開發者使用中控伺服器統一獲取和重新整理 access_token,其他業務邏輯伺服器所使用的 access_token 均來自於該中控伺服器,不應該各自去重新整理,否則容易造成衝突,導致 access_token 覆蓋而影響業務;
  • access_token 的有效期透過返回的 expires_in 來傳達,目前是7200秒之內的值,中控伺服器需要根據這個有效時間提前去重新整理。在重新整理過程中,中控伺服器可對外繼續輸出的老 access_token,此時公眾平臺後臺會保證在5分鐘內,新老 access_token 都可用,這保證了第三方業務的平滑過渡;
  • access_token 的有效時間可能會在未來有調整,所以中控伺服器不僅需要內部定時主動重新整理,還需要提供被動重新整理 access_token 的介面,這樣便於業務伺服器在API呼叫獲知 access_token 已超時的情況下,可以觸發 access_token 的重新整理流程。

詳情可參考微信公眾平臺文件 《獲取access_token》

請求示例程式碼

               /// <summary>
        /// 獲取小程式全域性唯一後臺介面呼叫憑據(access_token)
        /// </summary>
        /// <returns></returns>
        public string GetWechatAccessToken()
        {
            var appId = "你的小程式AppID";//小程式唯一憑證,即 AppID
            var secret = "你的小程式AppSecret"; //小程式唯一憑證金鑰,即 AppSecret
            string Url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appId, secret);

            string Result = HttpWebRequest(Url, "GET", "", Encoding.UTF8);

            var obj = JsonConvert.DeserializeObject<AccessToken>(Result);

            if (obj != null && obj.access_token != null)
            {
                return obj.access_token;
            }
            else
            {
                return "";
            }
        }


        /// <summary>
        /// WebRequest網路請求
        /// </summary>
        /// <param name="requestUrl">請求地址</param>
        /// <param name="method">請求方式(GET/POST)</param>
        /// <param name="data">請求引數(method="POST"需要攜帶)</param>
        /// <param name="encoding">字元編碼</param>
        /// <param name="contentType">請求資料的內容型別</param>
        /// <returns></returns>
        public string HttpWebRequest(string requestUrl, string method, string data, Encoding encoding,string contentType="application/json;charset=UTF-8")
        {
            WebRequest webRequest = WebRequest.Create(requestUrl);
            webRequest.Method = method;
            if (method == "POST")
            {
                byte[] bytes = Encoding.Default.GetBytes(data);
                webRequest.ContentType = contentType;
                webRequest.ContentLength = bytes.Length;
                Stream requestStream = webRequest.GetRequestStream();
                requestStream.Write(bytes, 0, bytes.Length);
                requestStream.Close();
            }

            WebResponse response = webRequest.GetResponse();
            Stream responseStream = response.GetResponseStream();
            if (responseStream == null)
            {
                return "";
            }

            StreamReader streamReader = new StreamReader(responseStream, encoding);
            string result = streamReader.ReadToEnd();
            responseStream.Close();
            streamReader.Close();
            return result;
        }



    /// <summary>
    /// 響應模型
    /// </summary>
    public class AccessToken
    {
        /// <summary>
        /// 獲取到的憑證
        /// </summary>
        public string access_token { get; set; }

        /// <summary>
        /// 憑證有效時間,單位:秒。目前是7200秒之內的值
        /// </summary>
        public int expires_in { get; set; }

        /// <summary>
        /// 錯誤碼
        /// </summary>
        public int errcode { get; set; }

        /// <summary>
        /// 錯誤資訊
        /// </summary>
        public string errmsg { get; set; }
    }

小程式碼獲取

請求地址

POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN 

請求引數

屬性型別必填說明
  access_token string 介面呼叫憑證,該引數為 URL 引數,非 Body 引數。使用getAccessToken 或者 authorizer_access_token
  scene string 最大32個可見字元,只支援數字,大小寫英文以及部分特殊字元:!#$&'()*+,/:;=?@-._~,其它字元請自行編碼為合法字元(因不支援%,中文無法使用 urlencode 處理,請使用其他編碼方式)
  page string 預設是主頁,頁面 page,例如 pages/index/index,根路徑前不要填加 /,不能攜帶引數(引數請放在scene欄位裡),如果不填寫這個欄位,預設跳主頁面。scancode_time為系統保留引數,不允許配置
  check_path bool 預設是true,檢查page 是否存在,為 true 時 page 必須是已經發布的小程式存在的頁面(否則報錯);為 false 時允許小程式未釋出或者 page 不存在, 但page 有數量上限(60000個)請勿濫用。
  env_version string 要開啟的小程式版本。正式版為 "release",體驗版為 "trial",開發版為 "develop"。預設是正式版。
  width number 預設430,二維碼的寬度,單位 px,最小 280px,最大 1280px
  auto_color bool 自動配置線條顏色,如果顏色依然是黑色,則說明不建議配置主色調,預設 false
  line_color object 預設是{"r":0,"g":0,"b":0} 。auto_color 為 false 時生效,使用 rgb 設定顏色 例如 {"r":"xxx","g":"xxx","b":"xxx"} 十進位制表示
  is_hyaline bool 預設是false,是否需要透明底色,為 true 時,生成透明底色的小程式

返回引數

屬性型別說明
buffer buffer 圖片 Buffer
errcode number 錯誤碼
errmsg string 錯誤資訊

介面請求成功會返回的圖片 Buffer(如果呼叫成功,會直接返回圖片二進位制內容(圖片檔案流),如果請求失敗,會返回 JSON 格式的資料。)

請求程式碼

注意:這個與前面獲取授權憑證的網路請求不同的是因為要接收請求返回過來的圖片二進位制內容(buffer),然後需要把二進位制檔案流轉化為byte[]二進位制位元組流,然後在轉化Image。

               /// <summary>
        /// 獲取小程式碼圖片
        /// </summary>
        /// <param name="access_token">介面呼叫憑據</param>
        /// <param name="param">攜帶引數</param>
        private Image GetWetchatAppletQRCodeImage(string access_token, string param)
        {
            string requestData = "{\"scene\":\"" + param + "\"}";
            string requestUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + access_token;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
            request.Method = "POST";
            request.ContentType = "application/json;charset=UTF-8";
            byte[] payload = System.Text.Encoding.UTF8.GetBytes(requestData);
            request.ContentLength = payload.Length;
            Stream writer = request.GetRequestStream();
            writer.Write(payload, 0, payload.Length);
            writer.Close();
            HttpWebResponse response;
            response = (HttpWebResponse)request.GetResponse();
            Stream stream = response.GetResponseStream();//獲取返回的圖片 Buffer(檔案流)
            byte[] imageBuffer = StreamToBytes(stream);

            return ByteArrayConvertToImage(imageBuffer);
        }

        /// <summary>
        /// 將檔案資料流轉為二進位制byte[]位元組流
        /// </summary>
        /// <param name="stream">檔案流</param>
        /// <returns></returns>
        private byte[] StreamToBytes(Stream stream)
        {
            List<byte> bytes = new List<byte>();
            int temp = stream.ReadByte();
            while (temp != -1)
            {
                bytes.Add((byte)temp);
                temp = stream.ReadByte();
            }
            return bytes.ToArray();
        }


        /// <summary>
        /// byte [] 轉化為Iamge
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public static Image ByteArrayConvertToImage(byte[] buffer)
        {
            using (MemoryStream ms = new MemoryStream(buffer))
            {
                // 直接呼叫Image庫類中自帶的方法使用MemoryStream例項物件獲取Image
                return Image.FromStream(ms);
            }
        }

小程式碼和背景圖合併

               /// <summary>
        /// 小程式推廣二維碼獲取
        /// </summary>
        /// <param name="userId">小程式碼攜帶的使用者引數</param>
        /// <returns></returns>
        public JsonResult GetCompositePictureUrl(int userId)
        {
            //圖片存放物理路徑
            var savePhysicalPath = HttpContext.Request.MapPath("~/qrcode/");

            var imgBack = Image.FromFile(savePhysicalPath + "ewm.jpg");//合成背景圖片
            var wechatQrcodeImg = GetWetchatAppletQRCodeImage(GetWechatAccessToken(),userId.ToString());//獲取小程式碼圖片
            var compositePictureUrl = CompositePicture(imgBack, wechatQrcodeImg, savePhysicalPath, 232, 719, 290, 290);

            return Json(new { code = 0, compositePictureUrl = compositePictureUrl });
        }

        /// <summary>
        /// 合成圖片
        /// </summary>
        /// <param name="backgroundImage">背景圖</param>
        /// <param name="qrCodeImg">二維碼圖片</param>
        /// <param name="savePhysicalPath">圖片存放物理路徑</param>
        /// <param name="xDeviation">繪製影像X軸偏差</param>
        /// <param name="yDeviation">繪製影像Y軸偏差</param>
        /// <param name="width">繪製影像寬</param>
        /// <param name="height">繪製影像高</param>
        /// <returns></returns>
        public string CompositePicture(Image backgroundImage, Image qrCodeImg, string savePhysicalPath, int xDeviation = 0, int yDeviation = 0, int width = 0, int height = 0)
        {
            Bitmap bitmap = new Bitmap(backgroundImage.Width, backgroundImage.Height);
            Graphics graphics = Graphics.FromImage(bitmap);//繪圖
            graphics.Clear(Color.White);
            SolidBrush surush = new SolidBrush(Color.White);
            graphics.DrawImage(backgroundImage, 0, 0, backgroundImage.Width, backgroundImage.Height);
            graphics.DrawImage(qrCodeImg, xDeviation, yDeviation, width, height);
            GC.Collect();//垃圾清理

            string compositePictureUrl = savePhysicalPath + Guid.NewGuid().ToString() + ".jpg";
            //合成圖片儲存
            bitmap.Save(compositePictureUrl, System.Drawing.Imaging.ImageFormat.Jpeg);

            return compositePictureUrl;
        }

合成效果圖

DotNetGuide技術社群交流群

  • DotNetGuide技術社群是一個面向.NET開發者的開源技術社群,旨在為開發者們提供全面的C#/.NET/.NET Core相關學習資料、技術分享和諮詢、專案推薦、招聘資訊和解決問題的平臺。
  • 在這個社群中,開發者們可以分享自己的技術文章、專案經驗、遇到的疑難技術問題以及解決方案,並且還有機會結識志同道合的開發者。
  • 我們致力於構建一個積極向上、和諧友善的.NET技術交流平臺,為廣大.NET開發者帶來更多的價值和成長機會。

歡迎加入DotNetGuide技術社群微信交流群?

相關文章