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}