Skip to main content

wx_rust_open/api/impl/
wx_open_ma_basic_service_impl.rs

1//! 小程序基础信息服务实现。
2//!
3//! 对应 Java `me.chanjar.weixin.open.api.impl.WxOpenMaBasicServiceImpl`
4//! (持有 `WxMaService` 与 `WxOpenComponentService`,构造入参
5//! `new WxOpenMaBasicServiceImpl(this, wxOpenComponentService)`)。
6//!
7//! 依赖表达与 [`crate::api::r#impl::WxOpenMaAuthServiceImpl`] 相同
8//! (`Weak<dyn WxOpenService>` + 按 appid 取回代 ma 桥接服务,ADAPTED);
9//! `getComponentRebindAdminUrl` 需组件服务取 `component_app_id`(Java
10//! `wxOpenComponentService.getWxOpenConfigStorage().getComponentAppId()`)。
11//!
12//! `getComponentRebindAdminUrl` 的 redirect_uri 编码对应 Java
13//! `URLEncoder.encode(redirectUri, "UTF-8")`(application/x-www-form-
14//! urlencoded 语义:空格 → `+`,与 JS encodeURIComponent 不同,原样
15//! 镜像)。
16
17use std::sync::{Arc, Weak};
18
19use async_trait::async_trait;
20
21use wx_rust_common::error::WxErrorException;
22use wx_rust_miniapp::api::WxMaService;
23
24use crate::api::WxOpenMaBasicService;
25use crate::api::WxOpenService;
26use crate::api::r#impl::WxOpenMaService;
27use crate::api::r#impl::base_wx_open_service_impl;
28use crate::bean::WxFastMaAccountBasicInfoResult;
29use crate::bean::WxFastMaBeenSetCategoryResult;
30use crate::bean::WxFastMaCategory;
31use crate::bean::WxFastMaCheckNickameResult;
32use crate::bean::WxFastMaQueryNicknameStatusResult;
33use crate::bean::WxFastMaSetNickameResult;
34use crate::bean::WxOpenGetAllCategoriesByTypeResult;
35use crate::bean::WxOpenMaCategoryNameListResult;
36use crate::bean::WxOpenMaGetOrderPathResult;
37use crate::bean::WxOpenResult;
38use crate::enums::url_ma_domain::{
39    component_rebind_admin_url, ma_add_category_url, ma_check_wx_verify_nickname_url,
40    ma_component_rebind_admin_url, ma_delete_category_url, ma_get_account_basic_info_url,
41    ma_get_all_categories_by_type_url, ma_get_all_categories_url, ma_get_all_category_name_url,
42    ma_get_category_url, ma_get_order_path_info_url, ma_modify_category_url,
43    ma_modify_head_image_url, ma_modify_signature_url, ma_query_nickname_url, ma_set_nickname_url,
44};
45
46/// 小程序基础信息服务实现(对应 Java `WxOpenMaBasicServiceImpl`)。
47pub struct WxOpenMaBasicServiceImpl {
48    wx_open_service: Weak<dyn WxOpenService>,
49    app_id: String,
50}
51
52impl WxOpenMaBasicServiceImpl {
53    /// 构建服务(对应 Java
54    /// `new WxOpenMaBasicServiceImpl(WxMaService, WxOpenComponentService)`;
55    /// Rust 以门面弱引用统一表达两个依赖,ADAPTED)。
56    pub fn new(wx_open_service: Arc<dyn WxOpenService>, app_id: String) -> Self {
57        Self {
58            wx_open_service: Arc::downgrade(&wx_open_service),
59            app_id,
60        }
61    }
62
63    /// 授权方 appid(Java 构造入参,代运营目标账号)。
64    pub fn app_id(&self) -> &str {
65        &self.app_id
66    }
67
68    fn svc(&self) -> Result<Arc<dyn WxOpenService>, WxErrorException> {
69        self.wx_open_service
70            .upgrade()
71            .ok_or_else(|| WxErrorException::from_code(-99, "门面服务已被释放"))
72    }
73
74    /// 取代 ma 桥接服务(同
75    /// [`WxOpenMaAuthServiceImpl::ma_service`])。
76    fn ma_service(&self) -> Result<Arc<dyn WxMaService>, WxErrorException> {
77        let svc = self.svc()?;
78        let component = svc.wx_open_component_service().ok_or_else(|| {
79            WxErrorException::from_code(
80                -99,
81                "组件子服务未装配(getWxOpenComponentService 返回 null)",
82            )
83        })?;
84        let any = component
85            .get_wx_ma_service_by_appid(&self.app_id)
86            .ok_or_else(|| WxErrorException::from_code(-99, "getWxMaServiceByAppid 返回 None"))?;
87        let ma = any.downcast::<WxOpenMaService>().map_err(|_| {
88            WxErrorException::from_code(-99, "代 ma 服务 downcast 失败(缓存类型不匹配)")
89        })?;
90        Ok(ma as Arc<dyn WxMaService>)
91    }
92
93    /// 组件配置存储的 component_appid(对应 Java
94    /// `wxOpenComponentService.getWxOpenConfigStorage().getComponentAppId()`)。
95    fn component_app_id(&self) -> Result<String, WxErrorException> {
96        let svc = self.svc()?;
97        Ok(svc
98            .wx_open_config_storage()
99            .component_app_id()
100            .unwrap_or_default())
101    }
102}
103
104#[async_trait]
105impl WxOpenMaBasicService for WxOpenMaBasicServiceImpl {
106    /// 获取小程序的信息(对应 Java `getAccountBasicInfo()`:GET 空参数 →
107    /// `WxFastMaAccountBasicInfoResult`)。
108    async fn get_account_basic_info(
109        &self,
110    ) -> Result<WxFastMaAccountBasicInfoResult, WxErrorException> {
111        let svc = self.svc()?;
112        let config = svc.wx_open_config_storage();
113        let ma = self.ma_service()?;
114        let response = ma
115            .get(&ma_get_account_basic_info_url(config.as_ref()), "")
116            .await?;
117        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
118        // errcode 字段为 String,镜像 Java 字段类型)
119        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
120        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
121    }
122
123    /// 小程序名称设置及改名(对应 Java `setNickname(...)`:POST
124    /// `{"nick_name","id_card","license","naming_other_stuff_1",
125    /// "naming_other_stuff_2"}` → `WxFastMaSetNickameResult`)。
126    async fn set_nickname(
127        &self,
128        nickname: &str,
129        id_card: &str,
130        license: &str,
131        naming_other_stuff1: &str,
132        naming_other_stuff2: &str,
133    ) -> Result<WxFastMaSetNickameResult, WxErrorException> {
134        let svc = self.svc()?;
135        let config = svc.wx_open_config_storage();
136        let ma = self.ma_service()?;
137        let body = serde_json::json!({
138            "nick_name": nickname,
139            "id_card": id_card,
140            "license": license,
141            "naming_other_stuff_1": naming_other_stuff1,
142            "naming_other_stuff_2": naming_other_stuff2,
143        });
144        let response = ma
145            .post(&ma_set_nickname_url(config.as_ref()), &body.to_string())
146            .await?;
147        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
148        // errcode 字段为 String,镜像 Java 字段类型)
149        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
150        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
151    }
152
153    /// 小程序改名审核状态查询(对应 Java
154    /// `querySetNicknameStatus(String auditId)`:POST `{"audit_id": ...}`)。
155    async fn query_set_nickname_status(
156        &self,
157        audit_id: &str,
158    ) -> Result<WxFastMaQueryNicknameStatusResult, WxErrorException> {
159        let svc = self.svc()?;
160        let config = svc.wx_open_config_storage();
161        let ma = self.ma_service()?;
162        let body = serde_json::json!({ "audit_id": audit_id });
163        let response = ma
164            .post(&ma_query_nickname_url(config.as_ref()), &body.to_string())
165            .await?;
166        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
167        // errcode 字段为 String,镜像 Java 字段类型)
168        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
169        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
170    }
171
172    /// 微信认证名称检测(对应 Java
173    /// `checkWxVerifyNickname(String nickname)`:POST
174    /// `{"nick_name": ...}`)。
175    async fn check_wx_verify_nickname(
176        &self,
177        nickname: &str,
178    ) -> Result<WxFastMaCheckNickameResult, WxErrorException> {
179        let svc = self.svc()?;
180        let config = svc.wx_open_config_storage();
181        let ma = self.ma_service()?;
182        let body = serde_json::json!({ "nick_name": nickname });
183        let response = ma
184            .post(
185                &ma_check_wx_verify_nickname_url(config.as_ref()),
186                &body.to_string(),
187            )
188            .await?;
189        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
190        // errcode 字段为 String,镜像 Java 字段类型)
191        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
192        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
193    }
194
195    /// 修改头像(对应 Java `modifyHeadImage(...)`:POST
196    /// `{"head_img_media_id","x1","y1","x2","y2"}`,坐标以 f32 镜像
197    /// Java float)。
198    async fn modify_head_image(
199        &self,
200        head_img_media_id: &str,
201        x1: f32,
202        y1: f32,
203        x2: f32,
204        y2: f32,
205    ) -> Result<WxOpenResult, WxErrorException> {
206        let svc = self.svc()?;
207        let config = svc.wx_open_config_storage();
208        let ma = self.ma_service()?;
209        let body = serde_json::json!({
210            "head_img_media_id": head_img_media_id,
211            "x1": x1,
212            "y1": y1,
213            "x2": x2,
214            "y2": y2,
215        });
216        let response = ma
217            .post(
218                &ma_modify_head_image_url(config.as_ref()),
219                &body.to_string(),
220            )
221            .await?;
222        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
223        // errcode 字段为 String,镜像 Java 字段类型)
224        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
225        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
226    }
227
228    /// 修改功能介绍(对应 Java `modifySignature(String signature)`:
229    /// POST `{"signature": ...}`)。
230    async fn modify_signature(&self, signature: &str) -> Result<WxOpenResult, WxErrorException> {
231        let svc = self.svc()?;
232        let config = svc.wx_open_config_storage();
233        let ma = self.ma_service()?;
234        let body = serde_json::json!({ "signature": signature });
235        let response = ma
236            .post(&ma_modify_signature_url(config.as_ref()), &body.to_string())
237            .await?;
238        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
239        // errcode 字段为 String,镜像 Java 字段类型)
240        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
241        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
242    }
243
244    /// 获取换绑管理员 URL(对应 Java
245    /// `getComponentRebindAdminUrl(String redirectUri, String appId)`)。
246    ///
247    /// redirect_uri 经 `URLEncoder.encode(..., "UTF-8")` 语义编码(空格 →
248    /// `+`)后按 `URL_COMPONENT_REBIND_ADMIN` 格式化;纯字符串构建
249    /// 不抛错。
250    fn get_component_rebind_admin_url(&self, redirect_uri: &str, app_id: &str) -> String {
251        let component_app_id = self.component_app_id().unwrap_or_default();
252        let encoded = url_encoder_encode(redirect_uri);
253        component_rebind_admin_url(app_id, &component_app_id, &encoded)
254    }
255
256    /// 管理员换绑(对应 Java `componentRebindAdmin(String taskId)`:
257    /// POST `{"taskid": ...}`)。
258    async fn component_rebind_admin(
259        &self,
260        task_id: &str,
261    ) -> Result<WxOpenResult, WxErrorException> {
262        let svc = self.svc()?;
263        let config = svc.wx_open_config_storage();
264        let ma = self.ma_service()?;
265        let body = serde_json::json!({ "taskid": task_id });
266        let response = ma
267            .post(
268                &ma_component_rebind_admin_url(config.as_ref()),
269                &body.to_string(),
270            )
271            .await?;
272        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
273        // errcode 字段为 String,镜像 Java 字段类型)
274        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
275        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
276    }
277
278    /// 获取账号可以设置的所有类目(对应 Java `getAllCategories()`:
279    /// GET 空参数,直接返回原始字符串)。
280    async fn get_all_categories(&self) -> Result<String, WxErrorException> {
281        let svc = self.svc()?;
282        let config = svc.wx_open_config_storage();
283        let ma = self.ma_service()?;
284        ma.get(&ma_get_all_categories_url(config.as_ref()), "")
285            .await
286    }
287
288    /// 获取不同类型主体可设置的类目(对应 Java
289    /// `getAllCategoriesByType(String verifyType)`:POST
290    /// `{"verify_type": ...}`)。
291    async fn get_all_categories_by_type(
292        &self,
293        verify_type: &str,
294    ) -> Result<WxOpenGetAllCategoriesByTypeResult, WxErrorException> {
295        let svc = self.svc()?;
296        let config = svc.wx_open_config_storage();
297        let ma = self.ma_service()?;
298        let body = serde_json::json!({ "verify_type": verify_type });
299        let response = ma
300            .post(
301                &ma_get_all_categories_by_type_url(config.as_ref()),
302                &body.to_string(),
303            )
304            .await?;
305        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
306        // errcode 字段为 String,镜像 Java 字段类型)
307        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
308        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
309    }
310
311    /// 添加类目(对应 Java `addCategory(List<WxFastMaCategory>
312    /// categoryList)`:POST `{"categories": [...]}`)。
313    async fn add_category(
314        &self,
315        category_list: &[WxFastMaCategory],
316    ) -> Result<WxOpenResult, WxErrorException> {
317        let svc = self.svc()?;
318        let config = svc.wx_open_config_storage();
319        let ma = self.ma_service()?;
320        let body = serde_json::json!({ "categories": category_list });
321        let response = ma
322            .post(&ma_add_category_url(config.as_ref()), &body.to_string())
323            .await?;
324        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
325        // errcode 字段为 String,镜像 Java 字段类型)
326        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
327        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
328    }
329
330    /// 删除类目(对应 Java `deleteCategory(int first, int second)`:
331    /// POST `{"first": ..., "second": ...}`)。
332    async fn delete_category(
333        &self,
334        first: i32,
335        second: i32,
336    ) -> Result<WxOpenResult, WxErrorException> {
337        let svc = self.svc()?;
338        let config = svc.wx_open_config_storage();
339        let ma = self.ma_service()?;
340        let body = serde_json::json!({ "first": first, "second": second });
341        let response = ma
342            .post(&ma_delete_category_url(config.as_ref()), &body.to_string())
343            .await?;
344        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
345        // errcode 字段为 String,镜像 Java 字段类型)
346        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
347        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
348    }
349
350    /// 获取账号已经设置的所有类目(对应 Java `getCategory()`:GET 空
351    /// 参数 → `WxFastMaBeenSetCategoryResult`)。
352    async fn get_category(&self) -> Result<WxFastMaBeenSetCategoryResult, WxErrorException> {
353        let svc = self.svc()?;
354        let config = svc.wx_open_config_storage();
355        let ma = self.ma_service()?;
356        let response = ma.get(&ma_get_category_url(config.as_ref()), "").await?;
357        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
358        // errcode 字段为 String,镜像 Java 字段类型)
359        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
360        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
361    }
362
363    /// 修改类目(对应 Java `modifyCategory(WxFastMaCategory category)`:
364    /// POST 实体序列化)。
365    async fn modify_category(
366        &self,
367        category: &WxFastMaCategory,
368    ) -> Result<WxOpenResult, WxErrorException> {
369        let svc = self.svc()?;
370        let config = svc.wx_open_config_storage();
371        let ma = self.ma_service()?;
372        let body =
373            serde_json::to_string(category).map_err(|e| WxErrorException::Serde(e.to_string()))?;
374        let response = ma
375            .post(&ma_modify_category_url(config.as_ref()), &body)
376            .await?;
377        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
378        // errcode 字段为 String,镜像 Java 字段类型)
379        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
380        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
381    }
382
383    /// 获取类目名称信息(对应 Java `getAllCategoryName()`:GET 空参数)。
384    async fn get_all_category_name(
385        &self,
386    ) -> Result<WxOpenMaCategoryNameListResult, WxErrorException> {
387        let svc = self.svc()?;
388        let config = svc.wx_open_config_storage();
389        let ma = self.ma_service()?;
390        let response = ma
391            .get(&ma_get_all_category_name_url(config.as_ref()), "")
392            .await?;
393        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
394        // errcode 字段为 String,镜像 Java 字段类型)
395        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
396        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
397    }
398
399    /// 获取订单页 path 信息(对应 Java `getOrderPathInfo(int infoType)`:
400    /// POST `{"info_type": ...}`;0 线上版,1 审核版)。
401    async fn get_order_path_info(
402        &self,
403        info_type: i32,
404    ) -> Result<WxOpenMaGetOrderPathResult, WxErrorException> {
405        let svc = self.svc()?;
406        let config = svc.wx_open_config_storage();
407        let ma = self.ma_service()?;
408        let body = serde_json::json!({ "info_type": info_type });
409        let response = ma
410            .post(
411                &ma_get_order_path_info_url(config.as_ref()),
412                &body.to_string(),
413            )
414            .await?;
415        // Java Gson 宽松类型转换:数字 errcode 归一化为字符串(bean 的
416        // errcode 字段为 String,镜像 Java 字段类型)
417        let response = base_wx_open_service_impl::normalize_errcode(&response)?;
418        serde_json::from_str(&response).map_err(|e| WxErrorException::Serde(e.to_string()))
419    }
420}
421
422/// `URLEncoder.encode(input, "UTF-8")` 语义编码。
423///
424/// 字母数字与 `_.-*` 保留,空格 → `+`,其余字节按 UTF-8 百分号编码
425/// (与 JS `encodeURIComponent` 不同,严格镜像 Java
426/// `java.net.URLEncoder`)。
427fn url_encoder_encode(input: &str) -> String {
428    let mut out = String::new();
429    for &b in input.as_bytes() {
430        match b {
431            b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'.' | b'-' | b'_' | b'*' => {
432                out.push(b as char);
433            }
434            b' ' => out.push('+'),
435            _ => out.push_str(&format!("%{b:02X}")),
436        }
437    }
438    out
439}