Skip to main content

wechat_minapp/user/
user_info.rs

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