async_wechat/
open_platform.rs1use crate::{
2 model::{AccessTokenResponse, AuthResponse, UserInfoResponse},
3 WechatType,
4};
5use async_trait::async_trait;
6use deadpool_redis::redis::cmd;
7use deadpool_redis::{Config, Pool, Runtime};
8use reqwest::Client;
9use std::error::Error;
10use std::sync::Arc;
11use url::{form_urlencoded, Url};
12
13pub struct OpenPlatform {
14 appid: String,
15 app_secret: String,
16 rdb: Option<Arc<Pool>>,
17}
18
19impl OpenPlatform {
20 pub fn new(appid: String, app_secret: String, cfg: Option<String>) -> Self {
21 let rdb = match cfg {
22 Some(cfg) => {
23 let pool_config = Config::from_url(cfg);
24 let pool = pool_config.create_pool(Some(Runtime::Tokio1)).unwrap();
25 Some(Arc::new(pool))
26 }
27 None => None,
28 };
29 OpenPlatform {
30 appid,
31 app_secret,
32 rdb,
33 }
34 }
35}
36
37#[async_trait]
38impl WechatType for OpenPlatform {
39 fn get_redirect_url(&self, redirect_uri: String, state: Option<String>) -> String {
40 let mut url = Url::parse("https://open.weixin.qq.com/connect/qrconnect").unwrap();
41
42 let _state = state.unwrap_or("".to_string());
43 let query = form_urlencoded::Serializer::new(String::new())
44 .append_pair("appid", self.appid.as_ref())
45 .append_pair("redirect_uri", &redirect_uri)
46 .append_pair("response_type", "code")
47 .append_pair("scope", "snsapi_login")
48 .append_pair("state", &_state)
49 .append_pair("lang", "")
50 .finish();
51
52 url.set_query(Some(&query));
53
54 url.to_string()
55 }
56
57 async fn get_access_token(
59 &self,
60 code: String,
61 ) -> Result<crate::model::AccessTokenResponse, Box<dyn std::error::Error>> {
62 let url = format!(
63 "https://api.weixin.qq.com/sns/oauth2/access_token?appid={}&secret={}&code={}&grant_type=authorization_code", self.appid, self.app_secret, code);
64
65 let client = Client::new();
66 let response = client.get(&url).send().await?;
67 let at: AccessTokenResponse = response.json::<AccessTokenResponse>().await?;
68
69 if let Some(pool) = &self.rdb {
70 let mut rdb = pool.get().await.unwrap();
71 cmd("SETEX")
73 .arg(&at.openid)
74 .arg(2 * 60 * 60)
75 .arg(&at.access_token)
76 .query_async::<()>(&mut rdb)
77 .await
78 .unwrap();
79
80 cmd("SETEX")
82 .arg(&self.appid)
83 .arg(24 * 60 * 60 * 7)
84 .arg(&at.refresh_token)
85 .query_async::<()>(&mut rdb)
86 .await
87 .unwrap()
88 }
89
90 Ok(at)
91 }
92
93 async fn refresh_access_token(
95 &self,
96 appid: String,
97 ) -> Result<AccessTokenResponse, Box<dyn Error>> {
98 let mut rdb = match &self.rdb {
99 Some(rdb) => rdb.get().await.unwrap(),
100 None => {
101 return Err("redis pool is not initialized".into());
102 }
103 };
104
105 let refresh_token = match cmd("GET").arg(&appid).query_async::<String>(&mut rdb).await {
106 Ok(refresh_token) => refresh_token,
107 Err(_) => {
108 return Err("redis key not found".into());
109 }
110 };
111
112 let url = format!(
113 "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid={}&grant_type=refresh_token&refresh_token={}",
114 appid, refresh_token
115 );
116
117 let client = Client::new();
118 let response = client.get(&url).send().await?;
119 let access_token: AccessTokenResponse = response.json::<AccessTokenResponse>().await?;
120
121 Ok(access_token)
122 }
123
124 async fn check_access_token(&self, openid: String) -> Result<AuthResponse, Box<dyn Error>> {
125 let mut rdb = match &self.rdb {
126 Some(rdb) => rdb.get().await.unwrap(),
127 None => {
128 return Err("redis pool is not initialized".into());
129 }
130 };
131
132 let access_token = match cmd("GET")
133 .arg(&openid)
134 .query_async::<String>(&mut rdb)
135 .await
136 {
137 Ok(access_token) => access_token,
138 Err(_) => {
139 return Err("redis key not found".into());
140 }
141 };
142
143 let url = format!(
144 "https://api.weixin.qq.com/sns/auth?access_token={}&openid={}",
145 access_token, openid
146 );
147
148 let client = Client::new();
149 let response = client.get(&url).send().await?;
150 let auth: AuthResponse = response.json::<AuthResponse>().await?;
151 Ok(auth)
152 }
153
154 async fn get_user_info(&self, openid: String) -> Result<UserInfoResponse, Box<dyn Error>> {
155 let mut rdb = match &self.rdb {
156 Some(rdb) => rdb.get().await.unwrap(),
157 None => {
158 return Err("redis pool is not initialized".into());
159 }
160 };
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("redis key 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 client = Client::new();
179 let response = client.get(&url).send().await?;
180 let status = response.status();
181
182 if !status.is_success() {
184 return Err(format!("HTTP error: {}", status).into());
185 }
186
187 let response_text = response.text().await?;
189 if let Ok(api_error) = serde_json::from_str::<AuthResponse>(&response_text) {
190 return Err(format!(
191 "Wechat API error: code={}, message={}",
192 api_error.errcode, api_error.errmsg
193 )
194 .into());
195 }
196
197 let user_info: UserInfoResponse = match serde_json::from_str(&response_text) {
199 Ok(info) => info,
200 Err(e) => {
201 eprintln!(
202 "Failed to deserialize UserInfoResponse: {}, response: {}",
203 e, response_text
204 );
205 return Err(format!("Error decoding response body: {}", e).into());
206 }
207 };
208
209 Ok(user_info)
210 }
211}