Skip to main content

wechat_minapp/qr/
minapp_code.rs

1//! 微信小程序小程序码生成模块
2//!
3//! 该模块提供了生成微信小程序小程序码的功能,支持多种类型的小程序码和自定义参数。
4//!
5//! # 主要功能
6//!
7//! - 生成小程序页面小程序码
8//! - 支持自定义尺寸、颜色、透明度等参数
9//! - 支持不同环境版本(开发版、体验版、正式版)
10//! - 链式参数构建器模式
11//!
12//! # 快速开始
13//!
14//! ```no_run
15//! use wechat_minapp::client::WechatMinapp;
16//! use wechat_minapp::qr::{QrCodeArgs,Qr, MinappEnvVersion};
17//!
18//! #[tokio::main]
19//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
20//!     // 初始化客户端
21//!     let app_id = "your_app_id";
22//!     let secret = "your_app_secret";
23//!     let client = WechatMinapp::new(app_id, secret);
24//!     let qr = Qr::new(client);
25//!
26//!     // 构建小程序码参数
27//!     let args = QrCodeArgs::builder()
28//!         .path("pages/index/index")
29//!         .width(300)
30//!         .env_version(MinappEnvVersion::Release)
31//!         .build()?;
32//!
33//!     // 生成小程序码
34//!     let qr_code = qr.qr_code(args).await?;
35//!     
36//!     // 获取小程序码图片数据
37//!     let buffer = qr_code.buffer();
38//!     println!("生成的小程序码大小: {} bytes", buffer.len());
39//!
40//!     // 可以将 buffer 保存为文件或直接返回给前端
41//!     // std::fs::write("qrcode.png", buffer)?;
42//!     
43//!     Ok(())
44//! }
45//! ```
46//!
47//! # 参数说明
48//!
49//! - `path`: 小程序页面路径,必填,最大长度 1024 字符
50//! - `width`: 小程序码宽度,单位 px,最小 280px,最大 1280px
51//! - `auto_color`: 是否自动配置线条颜色
52//! - `line_color`: 自定义线条颜色,RGB 格式
53//! - `is_hyaline`: 是否透明背景
54//! - `env_version`: 环境版本,默认为正式版
55//!
56//! # 注意事项
57//!
58//! - 生成的小程序码永不过期,数量不限
59//! - 接口只能生成已发布的小程序的小程序码
60//! - 支持带参数路径,如 `pages/index/index?param=value`
61//! - 小程序码大小限制为 128KB,请合理设置 width 参数
62//!
63//! # 示例
64//!
65//! ## 生成带颜色的小程序码
66//!
67//! ```no_run
68//! use wechat_minapp::client::WechatMinapp;
69//! use wechat_minapp::qr::{QrCodeArgs,Qr, MinappEnvVersion};
70//!
71//! #[tokio::main]
72//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
73//!     // 初始化客户端
74//!     let app_id = "your_app_id";
75//!     let secret = "your_app_secret";
76//!     let client = WechatMinapp::new(app_id, secret);
77//!     let qr = Qr::new(client);
78//!
79//!     let args = QrCodeArgs::builder()
80//!     .path("pages/detail/detail?id=123")
81//!     .width(400)
82//!     .line_color(Rgb::new(255, 0, 0)) // 红色线条
83//!     .with_is_hyaline() // 透明背景
84//!     .env_version(MinappEnvVersion::Develop)
85//!     .build()
86//!     .unwrap();
87//!     // 生成小程序码
88//!     let qr_code = qr.qr_code(args).await?;
89//!     
90//!     // 获取小程序码图片数据
91//!     let buffer = qr_code.buffer();
92//!     Ok(())
93//! }
94//! ```
95//!
96//! ## 生成简单小程序码
97//!
98//! ```no_run
99//! use wechat_minapp::client::WechatMinapp;
100//! use wechat_minapp::qr::{QrCodeArgs,Qr, MinappEnvVersion};
101//!
102//! #[tokio::main]
103//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
104//!     // 初始化客户端
105//!     let app_id = "your_app_id";
106//!     let secret = "your_app_secret";
107//!     let client = WechatMinapp::new(app_id, secret);
108//!     let qr = Qr::new(client);
109//!
110//!     let args = QrCodeArgs::builder()
111//!     .path("pages/index/index")
112//!     .build()
113//!     .unwrap();
114//!     // 生成小程序码
115//!     let qr_code = qr.qr_code(args).await?;
116//!     
117//!     // 获取小程序码图片数据
118//!     let buffer = qr_code.buffer();
119//!     Ok(())
120//! }
121//! ```
122//!
123//! # 错误处理
124//!
125//! 小程序码生成可能遇到以下错误:
126//!
127//! - 参数验证错误(路径为空或过长)
128//! - 认证错误(access_token 无效)
129//! - 网络错误
130//! - 微信 API 返回错误
131//!
132//! 建议在生产环境中妥善处理这些错误。
133
134use super::Qr;
135use crate::utils::{RequestBuilder, ResponseExt};
136use crate::{Result, constants, error::Error, new_type::PagePath};
137use serde::{Deserialize, Serialize};
138use tracing::debug;
139
140/// 二维码图片数据
141///
142/// 包含生成的二维码图片的二进制数据,通常是 PNG 格式。
143///
144/// # 示例
145///
146/// ```no_run
147/// use wechat_minapp::client::WechatMinappSDK;
148/// use wechat_minapp::qr::{QrCodeArgs,Qr, MinappEnvVersion};
149///
150/// #[tokio::main]
151/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
152///     // 初始化客户端
153///     let app_id = "your_app_id";
154///     let secret = "your_app_secret";
155///     let client = WechatMinappSDK::new(app_id, secret);
156///     let qr = Qr::new(client);
157///
158///     let args = QrCodeArgs::builder()
159///     .path("pages/index/index")
160///     .build()
161///     .unwrap();
162///     // 生成小程序码
163///     let qr_code = qr.qr_code(args).await?;
164///     
165///     // 获取小程序码图片数据
166///     let buffer = qr_code.buffer();
167///     Ok(())
168/// }
169/// ```
170#[derive(Debug, Serialize, Deserialize, Clone)]
171pub struct QrCode {
172    pub buffer: Vec<u8>,
173}
174
175impl QrCode {
176    /// 获取二维码图片的二进制数据
177    ///
178    /// 返回的字节向量通常是 PNG 格式的图片数据,可以直接写入文件或返回给 HTTP 响应。
179    ///
180    /// # 返回
181    ///
182    /// 二维码图片的二进制数据引用
183    pub fn buffer(&self) -> &Vec<u8> {
184        &self.buffer
185    }
186}
187
188/// 二维码生成参数
189///
190/// 用于配置二维码的生成选项,通过 [`QrCodeArgs::builder()`] 方法创建。
191#[derive(Debug, Deserialize, Serialize)]
192pub struct QrCodeArgs {
193    path: String,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    width: Option<i16>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    auto_color: Option<bool>,
198    #[serde(skip_serializing_if = "Option::is_none")]
199    line_color: Option<Rgb>,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    is_hyaline: Option<bool>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    env_version: Option<MinappEnvVersion>,
204}
205
206/// 二维码参数构建器
207///
208/// 提供链式调用的方式构建二维码参数,确保参数的正确性。
209///
210/// # 示例
211///
212/// ```
213/// use wechat_minapp::qr::{QrCodeArgs, Rgb, MinappEnvVersion};
214///
215/// let args = QrCodeArgs::builder()
216///     .path("pages/index/index")
217///     .width(300)
218///     .with_auto_color()
219///     .line_color(Rgb::new(255, 0, 0))
220///     .with_is_hyaline()
221///     .env_version(MinappEnvVersion::Release)
222///     .build()
223///     .unwrap();
224/// ```
225#[derive(Debug, Deserialize)]
226pub struct QrCodeArgBuilder {
227    path: Option<String>,
228    width: Option<i16>,
229    auto_color: Option<bool>,
230    line_color: Option<Rgb>,
231    is_hyaline: Option<bool>,
232    env_version: Option<MinappEnvVersion>,
233}
234
235// RGB 颜色值
236///
237/// 用于自定义二维码线条颜色。
238///
239/// # 示例
240///
241/// ```
242/// use wechat_minapp::qr::Rgb;
243///
244/// let red = Rgb::new(255, 0, 0);      // 红色
245/// let green = Rgb::new(0, 255, 0);    // 绿色
246/// let blue = Rgb::new(0, 0, 255);     // 蓝色
247/// let black = Rgb::new(0, 0, 0);      // 黑色
248/// ```
249#[derive(Debug, Deserialize, Clone, Serialize)]
250pub struct Rgb {
251    r: i16,
252    g: i16,
253    b: i16,
254}
255
256impl Rgb {
257    /// 创建新的 RGB 颜色
258    ///
259    /// # 参数
260    ///
261    /// - `r`: 红色分量 (0-255)
262    /// - `g`: 绿色分量 (0-255)
263    /// - `b`: 蓝色分量 (0-255)
264    ///
265    /// # 返回
266    ///
267    /// 新的 Rgb 实例
268    pub fn new(r: i16, g: i16, b: i16) -> Self {
269        Rgb { r, g, b }
270    }
271}
272
273impl QrCodeArgs {
274    pub fn builder() -> QrCodeArgBuilder {
275        QrCodeArgBuilder::new()
276    }
277
278    pub fn path(&self) -> String {
279        self.path.clone()
280    }
281
282    pub fn width(&self) -> Option<i16> {
283        self.width
284    }
285
286    pub fn auto_color(&self) -> Option<bool> {
287        self.auto_color
288    }
289
290    pub fn line_color(&self) -> Option<Rgb> {
291        self.line_color.clone()
292    }
293
294    pub fn is_hyaline(&self) -> Option<bool> {
295        self.is_hyaline
296    }
297
298    pub fn env_version(&self) -> Option<MinappEnvVersion> {
299        self.env_version.clone()
300    }
301}
302
303impl Default for QrCodeArgBuilder {
304    fn default() -> Self {
305        Self::new()
306    }
307}
308
309/// 小程序环境版本
310///
311/// 指定二维码生成的环境版本,不同环境版本对应不同的小程序实例。
312#[derive(Debug, Clone, Serialize, Deserialize)]
313#[serde(rename_all = "lowercase")]
314pub enum MinappEnvVersion {
315    /// 开发版,用于开发环境
316    Release,
317    /// 体验版,用于测试环境
318    Trial,
319    /// 正式版,用于生产环境
320    Develop,
321}
322
323impl From<MinappEnvVersion> for String {
324    fn from(value: MinappEnvVersion) -> Self {
325        match value {
326            MinappEnvVersion::Develop => "develop".to_string(),
327            MinappEnvVersion::Release => "release".to_string(),
328            MinappEnvVersion::Trial => "trial".to_string(),
329        }
330    }
331}
332
333impl QrCodeArgBuilder {
334    pub fn new() -> Self {
335        QrCodeArgBuilder {
336            path: None,
337            width: None,
338            auto_color: None,
339            line_color: None,
340            is_hyaline: None,
341            env_version: None,
342        }
343    }
344
345    pub fn path(mut self, path: impl Into<String>) -> Self {
346        self.path = Some(path.into());
347        self
348    }
349
350    pub fn width(mut self, width: i16) -> Self {
351        self.width = Some(width);
352        self
353    }
354
355    pub fn with_auto_color(mut self) -> Self {
356        self.auto_color = Some(true);
357        self
358    }
359
360    pub fn line_color(mut self, color: Rgb) -> Self {
361        self.line_color = Some(color);
362        self
363    }
364
365    pub fn with_is_hyaline(mut self) -> Self {
366        self.is_hyaline = Some(true);
367        self
368    }
369
370    pub fn env_version(mut self, version: MinappEnvVersion) -> Self {
371        self.env_version = Some(version);
372        self
373    }
374
375    pub fn build(self) -> Result<QrCodeArgs> {
376        let path = self.path.map_or_else(
377            || {
378                Err(Error::InvalidParameter(
379                    "小程序页面路径不能为空".to_string(),
380                ))
381            },
382            |v| {
383                let valid_path = PagePath::try_from(v)?;
384                Ok(valid_path.to_string())
385            },
386        )?;
387
388        if self.auto_color.is_some() && self.line_color.is_some() {
389            return Err(Error::InvalidParameter(
390                "auto_color 为 true 时,line_color 不能设置".to_string(),
391            ));
392        }
393
394        Ok(QrCodeArgs {
395            path,
396            width: self.width,
397            auto_color: self.auto_color,
398            line_color: self.line_color,
399            is_hyaline: self.is_hyaline,
400            env_version: self.env_version,
401        })
402    }
403}
404
405impl Qr {
406    /// 生成小程序二维码
407    ///
408    /// 调用微信小程序二维码生成接口,返回包含二维码图片数据的 [`QrCode`] 对象。
409    ///
410    /// # 参数
411    ///
412    /// - `args`: 二维码生成参数
413    ///
414    /// # 返回
415    ///
416    /// 成功返回 `Ok(QrCode)`,失败返回错误信息。
417    ///
418    /// # 示例
419    ///
420    /// ```no_run
421    /// use wechat_minapp::client::WechatMinappSDK;
422    /// use wechat_minapp::qr::{QrCodeArgs,Qr, MinappEnvVersion};
423    ///
424    /// #[tokio::main]
425    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
426    ///     // 初始化客户端
427    ///     let app_id = "your_app_id";
428    ///     let secret = "your_app_secret";
429    ///     let client = WechatMinappSDK::new(app_id, secret);
430    ///     let qr = Qr::new(client);
431    ///
432    ///     // 构建小程序码参数
433    ///     let args = QrCodeArgs::builder()
434    ///         .path("pages/index/index")
435    ///         .width(300)
436    ///         .env_version(MinappEnvVersion::Release)
437    ///         .build()?;
438    ///
439    ///     // 生成小程序码
440    ///     let qr_code = qr.qr_code(args).await?;
441    ///     
442    ///     // 获取小程序码图片数据
443    ///     let buffer = qr_code.buffer();
444    ///     println!("生成的小程序码大小: {} bytes", buffer.len());
445    ///
446    ///     // 可以将 buffer 保存为文件或直接返回给前端
447    ///     // std::fs::write("qrcode.png", buffer)?;
448    ///     
449    ///     Ok(())
450    /// }
451    /// ```
452    ///
453    /// # 错误
454    ///
455    /// - 网络错误
456    /// - 认证错误(access_token 无效)
457    /// - 微信 API 返回错误
458    /// - 参数序列化错误
459    pub async fn qr_code(&self, args: QrCodeArgs) -> Result<QrCode> {
460        debug!("get qr code args {:?}", &args);
461
462        let query = serde_json::json!({
463            "access_token":self.client.token().await?
464        });
465
466        let body = serde_json::to_value(QrCodeArgs {
467            path: args.path,
468            width: args.width,
469            auto_color: args.auto_color,
470            line_color: args.line_color,
471            is_hyaline: args.is_hyaline,
472            env_version: args.env_version,
473        })?;
474
475        let request = RequestBuilder::new(constants::QR_CODE_ENDPOINT)
476            .query(query)
477            .body(body)
478            .build()?;
479
480        let client = &self.client.client;
481
482        let response = client.execute(request).await?;
483
484        debug!("response: {:#?}", response);
485
486        let buffer = response.to_raw()?;
487        if buffer.len() > 2048 {
488            return Ok(QrCode { buffer });
489        }
490        Err(Error::InternalServer(
491            String::from_utf8_lossy(&buffer).to_string(),
492        ))
493    }
494}