wechat_minapp/user/
user_info.rs

1use super::User;
2use super::credential::Credential;
3use crate::{
4    Result, constants, error::Error::InternalServer, response::Response,
5    user::credential::CredentialBuilder, utils::build_request,
6};
7use http::Method;
8use serde::{Deserialize, Serialize};
9use tracing::{debug, instrument};
10
11/// 微信用户基本信息
12///
13/// 包含用户的昵称、性别、地区、头像等基本信息。
14/// 这些数据通常通过前端 `wx.getUserInfo()` 获取并解密得到。
15///
16/// # 示例
17///
18/// ```no_run
19/// use wechat_minapp::client::WechatMinappSDK;
20/// use wechat_minapp::user::{User, Contact};
21///
22///  #[tokio::main]
23/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
24///     let client = WechatMinappSDK::new("app_id", "secret");
25///     let user = User::new(client);
26///     let code = "0816abc123def456";
27///     let credential = user.login(code).await?;
28///     let info = credential.decrypt(&encrypted_data, &iv)?;
29///     println!("昵称: {}", info.nickname());
30///     println!("性别: {}", info.gender());
31///     println!("地区: {}-{}-{}", info.country(), info.province(), info.city());
32///     println!("头像: {}", info.avatar());
33///     println!("AppID: {}", info.app_id());
34///     println!("时间戳: {}", info.timestamp());
35///     
36///     Ok(())
37/// }
38/// ```
39///
40/// # 数据来源
41///
42/// 用户信息需要通过以下步骤获取:
43///
44/// 1. 前端调用 `wx.getUserInfo()` 获取加密数据
45/// 2. 后端使用会话密钥解密数据
46/// 3. 解析为 `User` 结构体
47///
48/// # 字段说明
49///
50/// - `gender`: 性别,0-未知,1-男性,2-女性
51#[derive(Debug, Serialize, Deserialize, Clone)]
52pub struct UserInfo {
53    nickname: String,
54    gender: u8,
55    country: String,
56    province: String,
57    city: String,
58    avatar: String,
59    watermark: Watermark,
60}
61
62impl UserInfo {
63    pub fn nickname(&self) -> &str {
64        &self.nickname
65    }
66
67    pub fn gender(&self) -> u8 {
68        self.gender
69    }
70
71    pub fn country(&self) -> &str {
72        &self.country
73    }
74
75    pub fn province(&self) -> &str {
76        &self.province
77    }
78
79    pub fn city(&self) -> &str {
80        &self.city
81    }
82
83    pub fn avatar(&self) -> &str {
84        &self.avatar
85    }
86
87    pub fn app_id(&self) -> &str {
88        &self.watermark.app_id
89    }
90
91    pub fn timestamp(&self) -> u64 {
92        self.watermark.timestamp
93    }
94}
95
96#[derive(Debug, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub(crate) struct UserBuilder {
99    #[serde(rename = "nickName")]
100    nickname: String,
101    gender: u8,
102    country: String,
103    province: String,
104    city: String,
105    #[serde(rename = "avatarUrl")]
106    avatar: String,
107    watermark: WatermarkBuilder,
108}
109
110impl UserBuilder {
111    pub(crate) fn build(self) -> UserInfo {
112        UserInfo {
113            nickname: self.nickname,
114            gender: self.gender,
115            country: self.country,
116            province: self.province,
117            city: self.city,
118            avatar: self.avatar,
119            watermark: self.watermark.build(),
120        }
121    }
122}
123
124#[derive(Debug, Serialize, Deserialize, Clone)]
125pub struct Contact {
126    phone_number: String,
127    pure_phone_number: String,
128    country_code: String,
129    watermark: Watermark,
130}
131
132impl Contact {
133    pub fn phone_number(&self) -> &str {
134        &self.phone_number
135    }
136
137    pub fn pure_phone_number(&self) -> &str {
138        &self.pure_phone_number
139    }
140
141    pub fn country_code(&self) -> &str {
142        &self.country_code
143    }
144
145    pub fn app_id(&self) -> &str {
146        &self.watermark.app_id
147    }
148
149    pub fn timestamp(&self) -> u64 {
150        self.watermark.timestamp
151    }
152}
153
154#[derive(Debug, Deserialize, Clone)]
155pub(crate) struct ContactBuilder {
156    #[serde(rename = "phone_info")]
157    inner: PhoneInner,
158}
159
160impl ContactBuilder {
161    pub(crate) fn build(self) -> Contact {
162        Contact {
163            phone_number: self.inner.phone_number,
164            pure_phone_number: self.inner.pure_phone_number,
165            country_code: self.inner.country_code,
166            watermark: self.inner.watermark.build(),
167        }
168    }
169}
170
171#[derive(Debug, Deserialize, Clone)]
172#[serde(rename_all = "camelCase")]
173struct PhoneInner {
174    #[serde(rename = "phoneNumber")]
175    phone_number: String,
176    #[serde(rename = "purePhoneNumber")]
177    pure_phone_number: String,
178    country_code: String,
179    watermark: WatermarkBuilder,
180}
181
182#[derive(Debug, Serialize, Deserialize, Clone)]
183struct Watermark {
184    app_id: String,
185    timestamp: u64,
186}
187
188#[derive(Debug, Deserialize, Clone)]
189struct WatermarkBuilder {
190    #[serde(rename = "appid")]
191    app_id: String,
192    timestamp: u64,
193}
194
195impl WatermarkBuilder {
196    fn build(self) -> Watermark {
197        Watermark {
198            app_id: self.app_id,
199            timestamp: self.timestamp,
200        }
201    }
202}
203
204impl User {
205    /// 用户登录凭证校验
206    ///
207    /// 通过微信前端获取的临时登录凭证 code,换取用户的唯一标识 OpenID 和会话密钥。
208    ///
209    /// # 参数
210    ///
211    /// - `code`: 微信前端通过 `wx.login()` 获取的临时登录凭证
212    ///
213    /// # 返回
214    ///
215    /// 成功返回 `Ok(Credential)`,包含用户身份信息
216    ///
217    /// # 错误
218    ///
219    /// - 网络错误
220    /// - 微信 API 返回错误
221    /// - 响应解析错误
222    ///
223    /// # 示例
224    ///
225    /// ```no_run
226    /// use wechat_minapp::client::WechatMinappSDK;
227    /// use wechat_minapp::user::{User, Contact};
228    ///
229    ///  #[tokio::main]
230    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
231    ///     let client = WechatMinappSDK::new("app_id", "secret");
232    ///     let user = User::new(client);
233    ///     let code = "0816abc123def456";
234    ///     let credential = user.login(code).await?;
235    ///     println!("用户OpenID: {}", credential.open_id());
236    ///     println!("会话密钥: {}", credential.session_key());
237    ///     
238    ///     Ok(())
239    /// }
240    /// ```
241    ///
242    /// # API 文档
243    ///
244    /// [微信官方文档 - code2Session](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html)
245    #[instrument(skip(self, code))]
246    pub async fn login(&self, code: &str) -> Result<Credential> {
247        debug!("code: {}", code);
248        let config = self.client.app_config();
249        let query = serde_json::json!({
250        "appid": &config.app_id,
251        "secret":&config.secret,
252        "js_code": code,
253        "grant_type": "authorization_code"
254        });
255        let request = build_request(
256            constants::AUTHENTICATION_END_POINT,
257            Method::GET,
258            None,
259            Some(query),
260            None,
261        )?;
262
263        let client = &self.client.client;
264
265        let response = client.execute(request).await?;
266        debug!("authentication response: {:#?}", &response);
267
268        if response.status().is_success() {
269            let (_parts, body) = response.into_parts();
270            let json = serde_json::from_slice::<Response<CredentialBuilder>>(&body.to_vec())?;
271
272            let credential = json.extract()?.build();
273
274            debug!("credential: {:#?}", credential);
275
276            Ok(credential)
277        } else {
278            let (_parts, body) = response.into_parts();
279            let message = String::from_utf8_lossy(&body.to_vec()).to_string();
280            Err(InternalServer(message))
281        }
282    }
283
284    /// 获取用户手机号信息
285    ///
286    /// 通过前端获取的临时凭证 code 换取用户的手机号信息。
287    ///
288    /// # 参数
289    ///
290    /// - `code`: 前端通过 `wx.getPhoneNumber` 获取的临时凭证
291    /// - `open_id`: 用户 OpenID(可选),如果提供可以提升安全性
292    ///
293    /// # 返回
294    ///
295    /// 成功返回 `Ok(Contact)`,包含用户手机号信息
296    ///
297    /// # 错误
298    ///
299    /// - 网络错误
300    /// - 微信 API 返回错误
301    /// - 访问令牌无效或过期
302    ///
303    /// # 示例
304    ///
305    /// ```no_run
306    /// use wechat_minapp::client::WechatMinappSDK;
307    /// use wechat_minapp::user::{User, Contact};
308    ///
309    ///  #[tokio::main]
310    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
311    ///     let client = WechatMinappSDK::new("app_id", "secret");
312    ///     let user = User::new(client);
313    ///     let code = "0816abc123def456";
314    ///     let contact = user.get_contact(code, None).await?;
315    ///     println!("用户手机号: {}", contact.phone_number());
316    ///     
317    ///     Ok(())
318    /// }
319    /// ```
320    ///
321    /// # 前端配合
322    ///
323    /// 前端需要调用 `wx.getPhoneNumber` 获取临时凭证:
324    ///
325    /// ```javascript
326    /// wx.getPhoneNumber({
327    ///   success: (res) => {
328    ///     console.log(res.code); // 将这个 code 发送到后端
329    ///   },
330    ///   fail: (err) => {
331    ///     console.error(err);
332    ///   }
333    /// });
334    /// ```
335    ///
336    /// # API 文档
337    ///
338    /// [获取手机号](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html)
339    pub async fn get_contact(&self, code: &str, open_id: Option<&str>) -> Result<Contact> {
340        debug!("code: {}, open_id: {:?}", code, open_id);
341        let query = serde_json::json!({
342            "access_token":self.client.token().await?
343        });
344
345        let mut body = serde_json::json!({
346            "code":code
347        });
348
349        if let Some(open_id) = open_id {
350            body.as_object_mut()
351                .unwrap()
352                .insert("openid".to_string(), serde_json::json!(open_id));
353        }
354
355        let request = build_request(
356            constants::PHONE_END_POINT,
357            Method::POST,
358            None,
359            Some(query),
360            Some(body),
361        )?;
362
363        let client = &self.client.client;
364
365        let response = client.execute(request).await?;
366        debug!("authentication response: {:#?}", &response);
367
368        if response.status().is_success() {
369            let (_parts, body) = response.into_parts();
370            let json = serde_json::from_slice::<Response<ContactBuilder>>(&body.to_vec())?;
371
372            let builder = json.extract()?;
373
374            debug!("contact builder: {:#?}", builder);
375
376            Ok(builder.build())
377        } else {
378            let (_parts, body) = response.into_parts();
379            let message = String::from_utf8_lossy(&body.to_vec()).to_string();
380            Err(InternalServer(message))
381        }
382    }
383}