0%

从获取微信小程序码说起

微信官方对于小程序码的获取可以查看连接https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/qr-code.html

接口B,可以生成不受数量限制的小程序码,并且支持不太长的path和不超过32字符的参数。这样的话,就可以使用path和对应的参数来做一些事情。

接口B的介绍,可以查看https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.getUnlimited.html

简单来说,生成小程序码,是访问网址https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN,并发起 post 请求,请求参数是一个 json 字符串,json 字符串的具体说明可以查看接口B的说明

我从官网摘下来了

属性 类型 默认值 必填 说明
access_token string 接口调用凭证
scene string 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&’()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)
page string 主页 必须是已经发布的小程序存在的页面(否则报错),例如 pages/index/index, 根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面
width number 430 小程序码宽度,单位是px,最小280px,最大 1280px
auto_color boolean false 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调,默认 false
line_color Object {“r”:0,”g”:0,”b”:0} auto_color 为 false 时生效,使用 rgb 设置颜色 例如 {“r”:”xxx”,”g”:”xxx”,”b”:”xxx”} 十进制表示
is_hyaline boolean false 是否需要透明底色,为 true 时,生成透明底色的小程序

access_token 是使用小程序的 appid 和 appsecret 生成的,可以查看接口调用凭证

简单来说,就是访问使用 appid 和 appsecret 拼接而成的 https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET ,发起 Get 请求,appid和appsecret 正确的情况下,会返回一个 json 字符串。

本文使用 libcurl 来实现访问微信服务器并最后获得小程序码的步骤。

libcurl 发起 post 请求和 get 请求

为了方便操作,建议使用linux操作系统,Windows可能会面临一系列的 libcurl 及其库的编译,不熟悉的情况下,操作是十分繁琐的。

下面的实现是参考了 libcurl 的sample来实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
#include <memory.h>
#include <assert.h>
#include <stdlib.h>

#ifndef __cplusplus
typedef int bool;
#define true 1
#define false 0
#endif

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus


/*存储以POST方式需要发送的数据*/
struct Request
{
char* readptr;
size_t sizeleft;
};


struct Response
{
unsigned char* response;
size_t size;
};

/*发送数据的回调函数*/
static size_t send_callback(char* dest, size_t size, size_t nmemb, void* userp)
{
struct Request* wt = (struct Request*)userp;
size_t buffer_size = size * nmemb;

if (wt->sizeleft) {
/* copy as much as possible from the source to the destination */
size_t copy_this_much = wt->sizeleft;
if (copy_this_much > buffer_size)
copy_this_much = buffer_size;
memcpy(dest, wt->readptr, copy_this_much);

wt->readptr += copy_this_much;
wt->sizeleft -= copy_this_much;
return copy_this_much; /* we copied this many bytes */
}

return 0; /* no more data left to deliver */
}

/*接收数据的回调函数*/
static size_t receive_callback(void* data, size_t size, size_t nmemb, void* userp)
{
size_t realsize = size * nmemb;
struct Response* mem = (struct Response*)userp;

unsigned char* ptr = (char*)realloc(mem->response, mem->size + realsize + 1);
if (ptr == NULL)
return 0; /* out of memory! */

mem->response = ptr;
memcpy(&(mem->response[mem->size]), data, realsize);
mem->size += realsize;
mem->response[mem->size] = 0;

return realsize;
}

/*如果定义了 USE_SPECCIFIC_CACERT_PEM ,那么函数第一个参数应该指向一个 catcert.pem 证书*/
#ifdef USE_SPECCIFIC_CACERT_PEM
int handle_https_callback(const char* certpem, const char* url, const char* parameter, size_t size_send, void** responsedata, size_t* size_receive, char** content_type, bool post, bool verbose)
#else
int handle_https_callback(const char* url, const char* parameter, size_t size_send, void** responsedata, size_t* size_receive, char** content_type, bool post, bool verbose)
#endif // USE_SPECCIFIC_CACERT_PEM
{
assert(url);
CURL* curl;
CURLcode res;
struct Request request;
struct Response response = { 0 };
struct curl_slist* headers = NULL;
int result = 0;

/*如果parameter非空,那么有数据要发送,就申请相应的发送数据的空间*/
bool hasParameter = false;
bool hasParameter = false;
if (parameter != NULL)
{
hasParameter = true;
size_t len = strlen(parameter);
size_t min = len < size_send ? len : size_send;
request.readptr = (char *)parameter;
request.sizeleft = min;
}

res = curl_global_init(CURL_GLOBAL_DEFAULT);
if (res != CURLE_OK)
{
fprintf(stderr, "curl_global_init() failed: %s\n", curl_easy_strerror(res));
return 1;
}
curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_URL, url);
if (post)
{
curl_easy_setopt(curl, CURLOPT_POST, 1L);
}

/*如果有数据需要发送,就设置相应的数据发送回调函数,要发送的数据和数据长度*/
if (hasParameter)
{
curl_easy_setopt(curl, CURLOPT_READFUNCTION, send_callback);
curl_easy_setopt(curl, CURLOPT_READDATA, &request);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)request.sizeleft);
}

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receive_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&response);

headers = curl_slist_append(headers, "Content-Type: application/json");

/*如果定义了 USE_SPECCIFIC_CACERT_PEM ,那么设置SSL认证证书*/
#ifdef USE_SPECCIFIC_CACERT_PEM
curl_easy_setopt(curl, CURLOPT_CAINFO, certpem)
#elif defined(DISABLE_CURL_SSL_VERIFY) // 如果定义了 DISABLE_CURL_SSL_VERIFY 证书认证,那么就设置curl不强制认证服务器
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
#endif // USE_SPECCIFIC_CACERT_PEM


if (verbose)
{
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
}

res = curl_easy_perform(curl);
if (res == CURLE_OK)
{
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code == 200)
{
/*curl_easy_cleanup(curl) 会释放掉 tmp_content_type*/
char* tmp_content_type = NULL;
curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &tmp_content_type);
if(tmp_content_type)
{
size_t tmplen = strlen(tmp_content_type);
*content_type = malloc(tmplen + 1);
memset(*content_type, 0, tmplen + 1);
memcpy(*content_type, tmp_content_type, tmplen);
}
/*用户需要自行释放 responsedata */
*responsedata = response.response;
*size_receive = response.size;
}
else
{
result = -2;
fprintf(stderr, "net work error with http code %ld", http_code);
}
}
else
{
result = 2;
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}

curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return result;
}

#ifdef __cplusplus
extern "C" }
#endif // __cplusplus

函数

1
int handle_https_callback(const char* url, const char* parameter, size_t size_send, void** responsedata, size_t* size_receive, char** content_type, bool post, bool verbose)
属性 参数 说明
传入参数 url 想要访问的url
传入参数 post 是否发起 post
传入参数 parameter 如果以 post 方式发起请求,parameter 是想要发送的数据,
如果不是以 post 方式发起,此参数为 NULL
传入参数 size_send 要发送的数据长度,实际上发送的数据长度以 parameter 的
长度和 size_send 中去较小值
传出参数 responsedata 服务器发送回来的数据
传出参数 size_receive 服务器发送回来的数据的长度
传出参数 content_type 如果是获取 access_token ,那么这里的 content_type 应该
是 “application/json; encoding=utf-8”,如果是小程序码,那么这里
应该是”Image/jpeg”或者别的类型的content type
传入参数 verbose 设置是否打印 curl 调用过程中的信息

常见的 Content-Type 可能有以下这些(摘自 cpp-httplib 的 README)

Extension MIME Type Extension MIME Type
css text/css mpga audio/mpeg
csv text/csv weba audio/webm
txt text/plain wav audio/wave
vtt text/vtt otf font/otf
html, htm text/html ttf font/ttf
apng image/apng woff font/woff
avif image/avif woff2 font/woff2
bmp image/bmp 7z application/x-7z-compressed
gif image/gif atom application/atom+xml
png image/png pdf application/pdf
svg image/svg+xml mjs, js application/javascript
webp image/webp json application/json
ico image/x-icon rss application/rss+xml
tif image/tiff tar application/x-tar
tiff image/tiff xhtml, xht application/xhtml+xml
jpeg, jpg image/jpeg xslt application/xslt+xml
mp4 video/mp4 xml application/xml
mpeg video/mpeg gz application/gzip
webm video/webm zip application/zip
mp3 audio/mp3 wasm application/wasm

获取 access_token

基于上面的使用 libcurl 发起 post 和 get 请求,可以很容以的再封装一下,成为根据 appid 和 appsecret 来获取小程序的 access_token 的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

// 为了方便对json处理,这里使用 rapidjson
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"

#ifdef USE_SPECCIFIC_CACERT_PEM
int get_wxacode_access_token(const char* certpem, const char* appid, const char* appsecret, char** access_token, bool verbose)
#else
int get_wxacode_access_token(const char* appid, const char* appsecret, char** access_token, bool verbose)
#endif // USE_SPECCIFIC_CACERT_PEM
{
assert(appid);
assert(appsecret);
const char* wxacode_domain = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=";
const char* secret_param = "&secret=";
size_t url_len = strlen(wxacode_domain) + strlen(appid) + strlen(secret_param) + strlen(appsecret);
char* url = (char *)malloc(url_len + 1);
if (!url)
{
return -1;/*out of mempry*/
}
memset(url, 0, url_len + 1);
sprintf_s(url, url_len + 1, "%s%s%s%s", wxacode_domain, appid, secret_param, appsecret);
if (verbose)
{
printf("access to url:%s\r\n", url);
}

char* response = NULL;
char* content_type = NULL;
size_t len = 0;
#ifdef USE_SPECCIFIC_CACERT_PEM
int result = handle_https_callback(certpem, url, false, NULL, 0, (void**)&response, &len, &content_type, verbose);
#else
int result = handle_https_callback(url, false, NULL, 0, (void **)&response, &len, &content_type, verbose);
#endif // USE_SPECCIFIC_CACERT_PEM
bool get_access_token = false;
if (result == 0)
{
if (strstr(content_type, "application/json"))
{
rapidjson::Document doc;
doc.Parse(response);
assert(doc.IsObject());
if (doc.HasMember("access_token"))
{
/*如果返回内容包含 access_token,则提取对应的字段*/
const char* token = doc["access_token"].GetString();
size_t token_len = strlen(token);
*access_token = (char *)malloc(token_len + 1);
if (*access_token == NULL)
{
return -1;/*out of memory*/;
}
memset(*access_token, 0, token_len + 1);
memcpy(*access_token, token, token_len);
get_access_token = true;
}
}

if (!get_access_token)
{
/*如果返回的 response 不是 json ,就直接将 response 内容传递回去*/
size_t token_len = strlen(response);
*access_token = (char *)malloc(token_len + 1);
if (*access_token == NULL)
{
return -1;/*out of memory*/;
}
memset(*access_token, 0, token_len + 1);
memcpy(*access_token, response, token_len);
result = 4;
}
}
if (url)
{
free(url);
url = NULL;
}
if (response)
{
free(response);
response = NULL;
}
if (content_type)
{
free(content_type);
content_type = NULL;
}
return result;
}

获取小程序二维码

和获取 access_token 类似,稍微封装一下就可以改写成为根据 access_token 获取小程序码的接口了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#ifdef USE_SPECCIFIC_CACERT_PEM
int get_wxacode_unlimited(const char* certpem, const char* access_token, unsigned char** qrcode, size_t* size, bool verbose)
#else
int get_wxacode_unlimited(const char* access_token,const char* parameter, unsigned char** qrcode, size_t* size, bool verbose)
#endif // USE_SPECCIFIC_CACERT_PEM
{
assert(access_token);
assert(parameter);
const char* wxacodedomain = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=";
size_t url_len = strlen(wxacodedomain) + strlen(access_token);
char* url = (char *)malloc(url_len + 1);
if (url == NULL)
{
return -1;/*out of mempry*/
}
sprintf_s(url, url_len + 1, "%s%s", wxacodedomain, access_token);
if (verbose)
{
printf("access to url:%s\r\n", url);
}
unsigned char* response = NULL;
char* content_type = NULL;
size_t len = 0;
#ifdef USE_SPECCIFIC_CACERT_PEM
int result = handle_https_callback(certpem, url, true, parameter, strlen(parameter), (void**)&response, &len, &content_type, verbose);
#else
int result = handle_https_callback(url, true, parameter, strlen(parameter), (void **)&response, &len, &content_type, verbose);
#endif // USE_SPECCIFIC_CACERT_PEM

if (result == 0)
{
if (!(strstr(content_type, "image/jpeg")))
{
result = 4;
}
*qrcode = (unsigned char*)malloc(len+1);
if (*qrcode == NULL)
{
return -1;/*out of memory*/;
}
memset(*qrcode, 0, len + 1);
memcpy(*qrcode, response, len);
*size = len;
}
return result;
}

使用示范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//main.cc
int main()
{
const char* access_token = NULL;
const char* appid = "your_appid";
const char* appsecret = "your_appsecret";
int result = get_wxacode_access_token(appid, appsecret, &access_token, true);
if (result > 0)
{
printf("in main func:%s\r\n", access_token);
}
else if(result == 0)
{
printf("access_token:%s\r\n", access_token);
unsigned char* qrcode = NULL;
size_t size = 0;
const char* parameter = "{\"scene\":\"testtest\"}";
result = get_wxacode_unlimited(access_token, parameter, &qrcode, &size, true);
if (result > 0)
{
printf("in main func qrcode:%s\r\n", qrcode);
}
else if (result == 0)
{
FILE* file;
if (fopen_s(&file, "wxacode.jpg", "wb+") == 0)
{
fwrite(qrcode, 1, size, file);
printf("success write wxacode.jpg\r\n");
}
else
{
printf("failed to open wxacode.jpg\r\n");
}
}
if (qrcode)
{
free(qrcode);
qrcode = NULL;
}
}
if (access_token)
{
free(access_token);
access_token = NULL;
}

return 0;
}

依赖

在 ubuntu 上,由于使用了 libcurl 访问https的链接,可以尝试安装这个包 libcurl4-openssl-dev
在对返回值的处理中,使用了 rapidjson 作为处理json的库,可以尝试安装这个包rapidjson-dev
编译时需要使用 -lssl -lcurl作为链接库

Windows 上,尝试使用 vcpkg 来安装 libcurl