對於小程式大家可能都非常熟悉了,隨著小程式的不斷普及越來越多的公司都開始推廣使用起來了。今天接到一個需求就是生成小程式碼,並且與運營給的推廣圖片合併在一起做成一張漂亮美觀的推廣二維條碼,掃碼這種二維條碼就可以進入小程式。為了節省伺服器記憶體資源,我想的就是成功呼叫通微信生成小程式碼的介面後直接把微信返回過來的圖片二進位制內容(返回的圖片 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
的儲存至少要保留 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;
}