Skip to main content

wechat_minapp/mp_message/
send_message.rs

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