use wx_rust_common::api::wx_consts::ACCESS_TOKEN_ERROR_CODES;
use wx_rust_common::enums::WxType;
use wx_rust_common::error::{WxError, WxErrorException};
use wx_rust_common::util::http::{RequestExecutor, SimplePostRequestExecutor};
use crate::api::WxOpenService;
use crate::bean::WxOpenComponentAccessToken;
use crate::enums::url_core::api_component_token_url;
fn build_uri_with_token(uri: &str, access_token_key: &str, token: &str) -> String {
if uri.contains('?') {
format!("{uri}&{access_token_key}={token}")
} else {
format!("{uri}?{access_token_key}={token}")
}
}
pub async fn execute_with_retry<S, T, E>(
svc: &S,
executor: &dyn RequestExecutor<T, E>,
uri: &str,
data: E,
access_token_key: &str,
) -> Result<T, WxErrorException>
where
S: WxOpenService + ?Sized,
T: Send,
E: Send + Clone,
{
let config = svc.wx_open_config_storage();
let max_retry_times = config.max_retry_times();
let retry_sleep_millis = config.retry_sleep_millis();
let mut retry_times = 0;
loop {
match execute_internal(svc, executor, uri, &data, access_token_key, false).await {
Ok(result) => return Ok(result),
Err(e) => {
if e.error_code() == Some(-1) {
if retry_times + 1 > max_retry_times {
return Err(WxErrorException::from_code(
-99,
"微信服务端异常,超出重试次数",
));
}
let sleep_millis = retry_sleep_millis * (1 << retry_times);
tokio::time::sleep(std::time::Duration::from_millis(sleep_millis as u64)).await;
} else {
return Err(e);
}
}
}
retry_times += 1;
if retry_times > max_retry_times {
break;
}
}
Err(WxErrorException::from_code(
-99,
"微信服务端异常,超出重试次数",
))
}
pub async fn execute_internal<S, T, E>(
svc: &S,
executor: &dyn RequestExecutor<T, E>,
uri: &str,
data: &E,
access_token_key: &str,
do_not_auto_refresh: bool,
) -> Result<T, WxErrorException>
where
S: WxOpenService + ?Sized,
T: Send,
E: Send + Clone,
{
if uri.contains(&format!("{access_token_key}=")) {
return Err(WxErrorException::from_code(
-99,
format!("uri参数中不允许有{access_token_key}: {uri}"),
));
}
let config = svc.wx_open_config_storage();
let component_access_token = svc.get_component_access_token(false).await?;
let mut component_access_token = component_access_token;
let mut uri_with_token = build_uri_with_token(uri, access_token_key, &component_access_token);
let mut do_not_auto_refresh = do_not_auto_refresh;
loop {
match executor
.execute(&uri_with_token, data.clone(), WxType::Open)
.await
{
Ok(result) => return Ok(result),
Err(e) => {
if let Some(code) = e.error_code() {
if ACCESS_TOKEN_ERROR_CODES.contains(&code) {
{
let lock = config.component_access_token_lock();
let _guard = lock.lock().await;
if config.component_access_token().as_deref()
== Some(component_access_token.as_str())
{
config.expire_component_access_token();
}
}
if config.auto_refresh_token() && !do_not_auto_refresh {
do_not_auto_refresh = true;
component_access_token = svc.get_component_access_token(false).await?;
uri_with_token = build_uri_with_token(
uri,
access_token_key,
&component_access_token,
);
continue;
}
}
if code != 0 {
return Err(e);
}
return Err(e);
}
return Err(e);
}
}
}
}
pub async fn get_component_access_token_with_lock<S>(
svc: &S,
force_refresh: bool,
) -> Result<String, WxErrorException>
where
S: WxOpenService + ?Sized,
{
let config = svc.wx_open_config_storage();
if !force_refresh && !config.is_component_access_token_expired() {
return config
.component_access_token()
.ok_or_else(|| WxErrorException::from_code(-99, "component access token 为空"));
}
let lock = config.component_access_token_lock();
let timeout_at = std::time::Instant::now() + std::time::Duration::from_millis(3000);
let _guard = loop {
if !force_refresh && !config.is_component_access_token_expired() {
return config
.component_access_token()
.ok_or_else(|| WxErrorException::from_code(-99, "component access token 为空"));
}
match lock.try_lock() {
Ok(guard) => break guard,
Err(_) => {
if std::time::Instant::now() > timeout_at {
return Err(WxErrorException::from_code(
-99,
"获取componentAccessToken超时:获取时间超时",
));
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
}
};
let body = serde_json::json!({
"component_appid": config.component_app_id().unwrap_or_default(),
"component_appsecret": config.component_app_secret().unwrap_or_default(),
"component_verify_ticket": config.component_verify_ticket().unwrap_or_default(),
});
let executor = SimplePostRequestExecutor::new(svc.http_client().clone());
let uri = api_component_token_url(config.as_ref());
let response = executor
.execute(&uri, body.to_string(), WxType::Open)
.await?;
let component_access_token = extract_component_access_token(&response)?;
config.update_component_access_token(&component_access_token);
Ok(config
.component_access_token()
.unwrap_or_else(|| component_access_token.component_access_token().to_string()))
}
pub fn extract_component_access_token(
result_content: &str,
) -> Result<WxOpenComponentAccessToken, WxErrorException> {
let error = WxError::from_json_with_type(result_content, Some(WxType::Open));
if error.error_code != 0 {
return Err(WxErrorException::from_code(
error.error_code,
error.error_msg.unwrap_or_default(),
));
}
WxOpenComponentAccessToken::from_json(result_content)
.map_err(|e| WxErrorException::Serde(e.to_string()))
}
pub fn normalize_errcode(json: &str) -> Result<String, WxErrorException> {
let mut value: serde_json::Value =
serde_json::from_str(json).map_err(|e| WxErrorException::Serde(e.to_string()))?;
if let Some(errcode) = value.get("errcode") {
if errcode.is_number() {
value["errcode"] = serde_json::Value::String(errcode.to_string());
}
}
serde_json::to_string(&value).map_err(|e| WxErrorException::Serde(e.to_string()))
}