async_wechat/official_account/
core.rs1use crate::OfficialAccount;
2
3use deadpool_redis::redis::cmd;
4use serde::{Deserialize, Serialize};
5use url::{Url, form_urlencoded};
6
7pub(crate) const OAUTH2_URL: &str = "https://open.weixin.qq.com/connect/oauth2/authorize";
8
9#[cfg(test)]
10mod tests {
11 use std::env;
12
13 use crate::{Config, OfficialAccount};
14
15 #[test]
16 fn get_redirect_url() {
17 dotenv::dotenv().ok();
18
19 let appid = env::var("APPID").expect("APPID not set");
20 let app_secret = env::var("APP_SECRET").expect("APP_SECRET not set");
21 let redis_url = env::var("REDIS_URL").expect("REDIS_URL not set");
22 let redirect_uri = env::var("REDIRECT_URI").expect("REDIRECT_URI not set");
23
24 let config = Config {
25 appid: appid.clone(),
26 app_secret: app_secret.clone(),
27 token: "wechat".to_string(),
28 encoding_aes_key: None,
29 };
30 let account = OfficialAccount::new(config, redis_url);
31
32 let url = account.get_redirect_url(redirect_uri, "snsapi_userinfo".to_string(), None);
33 println!("url: {:#?}", url);
34 }
35}
36
37#[derive(Serialize, Deserialize, Debug)]
38pub struct AccessTokenResponse {
39 access_token: String,
40 expires_in: u64,
41 refresh_token: String,
42 #[serde(rename = "openid")]
43 pub open_id: String,
44 scope: String,
45 #[serde(rename = "unionid")]
46 pub union_id: Option<String>,
47}
48
49#[derive(Debug, Deserialize)]
50pub struct BasicResponse {
51 pub errcode: i64,
52 pub errmsg: String,
53}
54
55#[derive(Debug, Deserialize)]
56pub struct UserInfoResponse {
57 #[serde(rename = "openid")]
58 pub open_id: String,
59 pub nickname: Option<String>,
60 pub sex: Option<i64>,
61 pub province: Option<String>,
62 pub city: Option<String>,
63 pub country: Option<String>,
64 #[serde(rename = "headimgurl")]
65 pub profile_url: Option<String>,
66 pub privilege: Option<Vec<String>>,
67 #[serde(rename = "unionid")]
68 pub union_id: Option<String>,
69}
70
71impl OfficialAccount {
72 pub fn get_redirect_url(
74 &self,
75 redirect_uri: String,
76 scope: String,
77 state: Option<String>,
78 ) -> String {
79 let mut url = Url::parse(OAUTH2_URL).unwrap();
80 let query = form_urlencoded::Serializer::new(String::new())
81 .append_pair("appid", &self.config.appid)
82 .append_pair("redirect_uri", &redirect_uri)
83 .append_pair("response_type", "code")
84 .append_pair("scope", &scope)
85 .append_pair("state", state.as_deref().unwrap_or(""))
86 .finish();
87
88 url.set_query(Some(&query));
89 format!("{}#wechat_redirect", url.to_string())
90 }
91
92 pub async fn get_oauth2_token(
112 &self,
113 code: String,
114 ) -> Result<AccessTokenResponse, Box<dyn std::error::Error>> {
115 let url = format!(
116 "https://api.weixin.qq.com/sns/oauth2/access_token?appid={}&secret={}&code={}&grant_type=authorization_code",
117 self.config.appid, self.config.app_secret, code
118 );
119
120 println!("url: {}", url);
121
122 let response = self.client.get(&url).send().await?;
123 let at: AccessTokenResponse = response.json::<AccessTokenResponse>().await?;
124
125 let mut rdb = self.rdb_pool.get().await.unwrap();
126 cmd("SETEX")
127 .arg(&at.open_id)
128 .arg(24 * 60)
129 .arg(serde_json::to_string(&at).unwrap())
130 .query_async::<()>(&mut rdb)
131 .await
132 .unwrap();
133
134 Ok(at)
135 }
136
137 pub async fn get_userinfo(
157 &self,
158 openid: String,
159 ) -> Result<UserInfoResponse, Box<dyn std::error::Error>> {
160 let mut rdb = self.rdb_pool.get().await?;
161
162 let access_token = match cmd("GET")
163 .arg(&openid)
164 .query_async::<String>(&mut rdb)
165 .await
166 {
167 Ok(access_token) => access_token,
168 Err(_) => {
169 return Err("access token not found".into());
170 }
171 };
172
173 let url = format!(
174 "https://api.weixin.qq.com/sns/userinfo?access_token={}&openid={}",
175 access_token, openid
176 );
177
178 let response = self.client.get(&url).send().await?;
179 let status = response.status();
180
181 if !status.is_success() {
183 return Err(format!("HTTP error: {}", status).into());
184 }
185
186 let response_text = response.text().await?;
188 if let Ok(api_error) = serde_json::from_str::<BasicResponse>(&response_text) {
189 return Err(format!(
190 "Wechat API error: code={}, message={}",
191 api_error.errcode, api_error.errmsg
192 )
193 .into());
194 }
195
196 let user_info: UserInfoResponse = match serde_json::from_str(&response_text) {
198 Ok(info) => info,
199 Err(e) => {
200 eprintln!(
201 "Failed to deserialize UserInfoResponse: {}, response: {}",
202 e, response_text
203 );
204 return Err(format!("Error decoding response body: {}", e).into());
205 }
206 };
207
208 Ok(user_info)
209 }
210}