Skip to main content

wechat_minapp/template_message/
send_message.rs

1//! 微信小程序模板消息发送模块
2//!
3//! 提供发送模板消息和订阅消息的功能,用于向用户发送服务通知。
4//! [官方文档](https://developers.weixin.qq.com/miniprogram/dev/server/API/mp-message-management/subscribe-message/api_sendmessage.html)
5//!
6//! ## 示例
7//!
8//! ```no_run
9//! use wechat_minapp::client::WechatMinapp;
10//! use wechat_minapp::template_message::{TemplateMessage, SendMessageArgs};
11//! use serde_json::json;
12//!
13//! #[tokio::main]
14//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
15//!     // 初始化客户端
16//!     let app_id = "your_app_id";
17//!     let secret = "your_app_secret";
18//!     let client = WechatMinapp::new(app_id, secret);
19//!     let message = TemplateMessage::new(client);
20//!
21//!     // 构建模板消息数据
22//!     let data = json!({
23//!         "thing1": {"value": "订单支付成功"},
24//!         "amount2": {"value": "¥99.00"},
25//!         "date3": {"value": "2024-01-01 12:00:00"}
26//!     });
27//!
28//!     let args = SendMessageArgs::builder()
29//!         .touser("openid")
30//!         .template_id("template_id")
31//!         .page("pages/index/index")
32//!         .data(data)
33//!         .miniprogram_state("formal")
34//!         .lang("zh_CN")
35//!         .build()?;
36//!
37//!     // 发送模板消息
38//!     let result = message.send_message(args).await?;
39//!     
40//!     Ok(())
41//! }
42//! ```
43
44use super::TemplateMessage;
45use crate::utils::{RequestBuilder, ResponseExt};
46use crate::{Result, constants, error::Error};
47use serde::{Deserialize, Serialize};
48use serde_json::Value;
49use tracing::debug;
50
51/// 订阅消息
52///
53/// 用于发送一次性订阅消息的结构
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct SendMessageArgs {
56    pub touser: String,                    // 接收者openid
57    pub template_id: String,               // 模板ID
58    pub page: Option<String>,              // 点击跳转页面
59    pub data: serde_json::Value,           // 模板数据
60    pub miniprogram_state: Option<String>, // 小程序状态
61    pub lang: Option<String>,              // 语言
62}
63
64/// 订阅消息发送响应
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct SendMessageResponse {
67    pub msgid: Option<String>,  // 消息ID
68    pub errcode: Option<i32>,   // 错误码
69    pub errmsg: Option<String>, // 错误信息
70}
71
72/// 订阅消息参数构建器
73#[derive(Debug, Default)]
74pub struct SendMessageArgsBuilder {
75    touser: Option<String>,
76    template_id: Option<String>,
77    page: Option<String>,
78    data: Option<serde_json::Value>,
79    miniprogram_state: Option<String>,
80    lang: Option<String>,
81}
82
83impl SendMessageArgs {
84    /// 创建订阅消息构建器
85    pub fn builder() -> SendMessageArgsBuilder {
86        SendMessageArgsBuilder::new()
87    }
88
89    /// 获取接收者openid
90    pub fn touser(&self) -> &str {
91        &self.touser
92    }
93
94    /// 获取模板ID
95    pub fn template_id(&self) -> &str {
96        &self.template_id
97    }
98
99    /// 获取跳转页面
100    pub fn page(&self) -> Option<&String> {
101        self.page.as_ref()
102    }
103
104    /// 获取模板数据
105    pub fn data(&self) -> &Value {
106        &self.data
107    }
108
109    /// 获取小程序状态
110    pub fn miniprogram_state(&self) -> Option<&String> {
111        self.miniprogram_state.as_ref()
112    }
113
114    /// 获取语言
115    pub fn lang(&self) -> Option<&String> {
116        self.lang.as_ref()
117    }
118}
119
120impl SendMessageArgsBuilder {
121    pub fn new() -> Self {
122        Self::default()
123    }
124
125    /// 设置接收者openid
126    pub fn touser(mut self, touser: impl Into<String>) -> Self {
127        self.touser = Some(touser.into());
128        self
129    }
130
131    /// 设置模板ID
132    pub fn template_id(mut self, template_id: impl Into<String>) -> Self {
133        self.template_id = Some(template_id.into());
134        self
135    }
136
137    /// 设置跳转页面
138    pub fn page(mut self, page: impl Into<String>) -> Self {
139        self.page = Some(page.into());
140        self
141    }
142
143    /// 设置模板数据
144    pub fn data(mut self, data: impl Into<serde_json::Value>) -> Self {
145        self.data = Some(data.into());
146        self
147    }
148
149    /// 设置小程序状态
150    ///
151    /// 可选值:formal(正式版),trial(体验版),developer(开发版)
152    pub fn miniprogram_state(mut self, state: impl Into<String>) -> Self {
153        self.miniprogram_state = Some(state.into());
154        self
155    }
156
157    /// 设置语言
158    ///
159    /// 可选值:zh_CN(简体中文),en_US(英文),zh_HK(繁体中文),zh_TW(繁体中文)
160    pub fn lang(mut self, lang: impl Into<String>) -> Self {
161        self.lang = Some(lang.into());
162        self
163    }
164
165    /// 构建订阅消息参数
166    pub fn build(self) -> Result<SendMessageArgs> {
167        let touser = self
168            .touser
169            .ok_or_else(|| Error::InvalidParameter("接收者openid不能为空".to_string()))?;
170
171        let template_id = self
172            .template_id
173            .ok_or_else(|| Error::InvalidParameter("模板ID不能为空".to_string()))?;
174
175        let data = self
176            .data
177            .ok_or_else(|| Error::InvalidParameter("模板数据不能为空".to_string()))?;
178
179        // 验证数据格式
180        Self::validate_data(&data)?;
181
182        Ok(SendMessageArgs {
183            touser,
184            template_id,
185            page: self.page,
186            data,
187            miniprogram_state: self.miniprogram_state,
188            lang: self.lang,
189        })
190    }
191
192    /// 验证模板数据格式
193    fn validate_data(data: &Value) -> Result<()> {
194        if let Value::Object(map) = data {
195            for (key, value) in map {
196                if key.chars().count() > 20 {
197                    return Err(Error::InvalidParameter(format!(
198                        "字段名'{}'长度不能超过20个字符",
199                        key
200                    )));
201                }
202
203                if let Value::Object(item) = value {
204                    if let Some(val) = item.get("value") {
205                        if let Value::String(s) = val
206                            && s.chars().count() > 50
207                        {
208                            return Err(Error::InvalidParameter(format!(
209                                "字段'{}'的值长度不能超过50个字符",
210                                key
211                            )));
212                        }
213                    } else {
214                        return Err(Error::InvalidParameter(format!(
215                            "字段'{}'缺少value属性",
216                            key
217                        )));
218                    }
219                } else {
220                    return Err(Error::InvalidParameter(format!(
221                        "字段'{}'格式不正确,应为{{value: string}}",
222                        key
223                    )));
224                }
225            }
226            Ok(())
227        } else {
228            Err(Error::InvalidParameter(
229                "模板数据必须是对象类型".to_string(),
230            ))
231        }
232    }
233}
234
235impl TemplateMessage {
236    /// 发送模板消息
237    ///
238    /// 调用微信小程序模板消息发送接口
239    ///
240    /// # 参数
241    ///
242    /// - `args`: 模板消息参数
243    ///
244    /// # 返回
245    ///
246    /// 成功返回 `Ok(SendMessageResponse)`,失败返回错误信息
247    ///
248    /// # 示例
249    ///
250    /// ```no_run
251    /// use wechat_minapp::client::WechatMinapp;
252    /// use wechat_minapp::template_message::{TemplateMessage, SendMessageArgs};
253    /// use serde_json::json;
254    ///
255    /// #[tokio::main]
256    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
257    ///     let client = WechatMinapp::new("app_id", "secret");
258    ///     let message = TemplateMessage::new(client);
259    ///
260    ///     let data = json!({
261    ///         "thing1": {"value": "订单支付成功"},
262    ///         "amount2": {"value": "¥99.00"},
263    ///     });
264    ///
265    ///     let args = SendMessageArgs::builder()
266    ///         .touser("openid")
267    ///         .template_id("template_id")
268    ///         .data(data)
269    ///         .build()?;
270    ///     let result = message.send_message(args).await?;
271    ///     
272    ///     Ok(())
273    /// }
274    /// ```
275    pub async fn send_message(&self, args: SendMessageArgs) -> Result<SendMessageResponse> {
276        debug!("send template message args {:?}", &args);
277
278        let query = serde_json::json!({
279            "access_token": self.client.token().await?
280        });
281
282        let body = serde_json::to_value(args)?;
283
284        let request = RequestBuilder::new(constants::TEMPLATE_MESSAGE_SEND_END_POINT)
285            .query(query)
286            .body(body)
287            .build()?;
288
289        let client = &self.client.client;
290        let response = client.execute(request).await?;
291
292        debug!("response: {:#?}", response);
293        response.to_json::<SendMessageResponse>()
294    }
295}