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}