use std::any::Any;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Weak};
use async_trait::async_trait;
use wx_rust_common::config::TicketType;
use wx_rust_common::error::WxErrorException;
use wx_rust_common::service::WxOAuth2Service;
use wx_rust_common::util::http::{SimpleGetRequestExecutor, SimplePostRequestExecutor};
use wx_rust_mp::api::WxMpService;
use wx_rust_mp::api::r#impl::WxMpOAuth2ServiceImpl;
use wx_rust_mp::config::WxMpConfigStorage;
use wx_rust_mp::config::WxMpHostConfig;
use crate::api::WxOpenService;
use crate::api::r#impl::WxOpenMpOAuth2ServiceImpl;
use crate::config::WxOpenConfigStorage;
pub fn downcast_mp_service(any: Arc<dyn Any + Send + Sync>) -> Option<Arc<dyn WxMpService>> {
any.downcast::<WxOpenMpService>()
.ok()
.map(|svc| svc as Arc<dyn WxMpService>)
}
pub struct WxOpenMpService {
wx_open_service: Weak<dyn WxOpenService>,
app_id: String,
config: Arc<OpenMpConfigBridge>,
http_client: reqwest::Client,
oauth2_service: Mutex<Option<Arc<dyn WxOAuth2Service>>>,
}
impl WxOpenMpService {
pub fn new(wx_open_service: Arc<dyn WxOpenService>, app_id: String) -> Self {
let config = wx_open_service.wx_open_config_storage();
let http_client = wx_open_service.http_client().clone();
let wx_open_service = Arc::downgrade(&wx_open_service);
let bridge_config = Arc::new(OpenMpConfigBridge::new(config, &app_id));
Self {
wx_open_service,
app_id,
config: bridge_config,
http_client,
oauth2_service: Mutex::new(None),
}
}
pub fn new_arc(wx_open_service: Arc<dyn WxOpenService>, app_id: String) -> Arc<Self> {
let arc = Arc::new(Self::new(wx_open_service.clone(), app_id.clone()));
let weak_self: Weak<dyn WxMpService> =
Arc::downgrade(&(arc.clone() as Arc<dyn WxMpService>));
let inner: Arc<dyn WxOAuth2Service> = Arc::new(WxMpOAuth2ServiceImpl::new(weak_self));
let oauth2: Arc<dyn WxOAuth2Service> = Arc::new(WxOpenMpOAuth2ServiceImpl::new(
wx_open_service,
inner,
app_id,
));
*arc.oauth2_service.lock().unwrap() = Some(oauth2);
arc
}
pub fn app_id(&self) -> &str {
&self.app_id
}
pub fn oauth2_service(&self) -> Option<Arc<dyn WxOAuth2Service>> {
self.oauth2_service.lock().unwrap().clone()
}
fn svc(&self) -> Result<Arc<dyn WxOpenService>, WxErrorException> {
self.wx_open_service
.upgrade()
.ok_or_else(|| WxErrorException::from_code(-99, "门面服务已被释放"))
}
}
#[async_trait]
impl WxMpService for WxOpenMpService {
fn wx_mp_config_storage(&self) -> Arc<dyn WxMpConfigStorage> {
self.config.clone()
}
fn http_client(&self) -> &reqwest::Client {
&self.http_client
}
async fn get_access_token_with_force(
&self,
force_refresh: bool,
) -> Result<String, WxErrorException> {
let svc = self.svc()?;
let component = svc.wx_open_component_service().ok_or_else(|| {
WxErrorException::from_code(
-99,
"组件子服务未装配(getWxOpenComponentService 返回 null)",
)
})?;
component
.get_authorizer_access_token(&self.app_id, force_refresh)
.await
}
async fn get(&self, url: &str, query_param: &str) -> Result<String, WxErrorException> {
let executor = SimpleGetRequestExecutor::new(self.http_client().clone());
wx_rust_mp::api::r#impl::base_wx_mp_service_impl::execute_with_retry(
self,
&executor,
url,
query_param.to_string(),
)
.await
}
async fn post(&self, url: &str, post_data: &str) -> Result<String, WxErrorException> {
let executor = SimplePostRequestExecutor::new(self.http_client().clone());
wx_rust_mp::api::r#impl::base_wx_mp_service_impl::execute_with_retry(
self,
&executor,
url,
post_data.to_string(),
)
.await
}
}
struct OpenMpConfigBridge {
open_config: Arc<dyn WxOpenConfigStorage>,
app_id: String,
use_stable_access_token: AtomicBool,
component_token: Option<String>,
component_aes_key: Option<String>,
http_proxy_host: Option<String>,
http_proxy_port: Option<u16>,
host_config: Mutex<WxMpHostConfig>,
}
impl OpenMpConfigBridge {
fn new(open_config: Arc<dyn WxOpenConfigStorage>, app_id: &str) -> Self {
let mut host_config = WxMpHostConfig::new();
if let Some(h) = open_config.wx_open_host_config() {
if !h.api_host.is_empty() {
host_config.api_host = h.api_host;
}
if !h.mp_host.is_empty() {
host_config.mp_host = h.mp_host;
}
if !h.open_host.is_empty() {
host_config.open_host = h.open_host;
}
}
Self {
component_token: open_config.component_token(),
component_aes_key: open_config.component_aes_key(),
http_proxy_host: open_config.http_proxy_host(),
http_proxy_port: match open_config.http_proxy_port() {
p if p > 0 => Some(p as u16),
_ => None,
},
host_config: Mutex::new(host_config),
open_config,
app_id: app_id.to_string(),
use_stable_access_token: AtomicBool::new(false),
}
}
}
impl wx_rust_common::config::WxConfigStorage for OpenMpConfigBridge {
fn app_id(&self) -> &str {
&self.app_id
}
fn secret(&self) -> &str {
""
}
fn access_token(&self) -> Option<String> {
self.open_config.authorizer_access_token(&self.app_id)
}
fn is_access_token_expired(&self) -> bool {
self.open_config
.is_authorizer_access_token_expired(&self.app_id)
}
fn expire_access_token(&self) {
self.open_config
.expire_authorizer_access_token(&self.app_id);
}
fn update_access_token(&self, access_token: &str, expires_in_seconds: i32) {
self.open_config.update_authorizer_access_token_with_expiry(
&self.app_id,
access_token,
expires_in_seconds,
);
}
fn access_token_lock(&self) -> Arc<tokio::sync::Mutex<()>> {
self.open_config
.lock_by_key(&format!("{}:accessTokenLock", self.app_id))
}
fn is_stable_access_token(&self) -> bool {
self.use_stable_access_token.load(Ordering::Relaxed)
}
fn auto_refresh_token(&self) -> bool {
self.open_config.auto_refresh_token()
}
fn ticket(&self, ticket_type: TicketType) -> Option<String> {
match ticket_type {
TicketType::Jsapi => self.open_config.jsapi_ticket(&self.app_id),
TicketType::WxCard => self.open_config.card_api_ticket(&self.app_id),
TicketType::Sdk => None,
}
}
fn is_ticket_expired(&self, ticket_type: TicketType) -> bool {
match ticket_type {
TicketType::Jsapi => self.open_config.is_jsapi_ticket_expired(&self.app_id),
TicketType::WxCard => self.open_config.is_card_api_ticket_expired(&self.app_id),
TicketType::Sdk => false,
}
}
fn update_ticket(&self, ticket_type: TicketType, ticket: &str, expires_in_seconds: i32) {
match ticket_type {
TicketType::Jsapi => {
self.open_config
.update_jsapi_ticket(&self.app_id, ticket, expires_in_seconds)
}
TicketType::WxCard => {
self.open_config
.update_card_api_ticket(&self.app_id, ticket, expires_in_seconds)
}
TicketType::Sdk => {}
}
}
fn ticket_lock(&self, ticket_type: TicketType) -> Arc<tokio::sync::Mutex<()>> {
match ticket_type {
TicketType::Jsapi => self
.open_config
.lock_by_key(&format!("{}:jsapiTicketLock", self.app_id)),
TicketType::WxCard => self
.open_config
.lock_by_key(&format!("{}:cardApiTicketLock", self.app_id)),
TicketType::Sdk => Arc::new(tokio::sync::Mutex::new(())),
}
}
fn expire_ticket(&self, ticket_type: TicketType) {
match ticket_type {
TicketType::Jsapi => self.open_config.expire_jsapi_ticket(&self.app_id),
TicketType::WxCard => self.open_config.expire_card_api_ticket(&self.app_id),
TicketType::Sdk => {}
}
}
fn http_proxy_host(&self) -> Option<&str> {
self.http_proxy_host.as_deref()
}
fn http_proxy_port(&self) -> Option<u16> {
self.http_proxy_port
}
fn tmp_dir(&self) -> Option<&str> {
None
}
}
impl WxMpConfigStorage for OpenMpConfigBridge {
fn use_stable_access_token(&self, use_stable_access_token: bool) {
self.use_stable_access_token
.store(use_stable_access_token, Ordering::Relaxed);
}
fn token(&self) -> Option<&str> {
self.component_token.as_deref()
}
fn aes_key(&self) -> Option<&str> {
self.component_aes_key.as_deref()
}
fn template_id(&self) -> Option<&str> {
None
}
fn oauth2_redirect_url(&self) -> Option<&str> {
None
}
fn qr_connect_redirect_url(&self) -> Option<&str> {
None
}
fn retry_sleep_millis(&self) -> i32 {
self.open_config.retry_sleep_millis()
}
fn max_retry_times(&self) -> i32 {
self.open_config.max_retry_times()
}
fn host_config(&self) -> WxMpHostConfig {
self.host_config.lock().unwrap().clone()
}
fn set_host_config(&self, host_config: WxMpHostConfig) {
*self.host_config.lock().unwrap() = host_config;
}
}