From 12318114f5663dc2a16094bcd0db9beb048dfeb2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=3F=2E=2E=E6=BF=A1=2E=2E?= <2324281453@qq.com>
Date: Thu, 9 Jan 2025 07:00:06 +0800
Subject: [PATCH] refactor(wechat): update WeChatClient to WeChatAPI and
 enhance functionality

---
 mooc/api/v1/endpoints/wechat.py |  4 +-
 mooc/utils/wechat_client.py     | 98 ++++++++++++++++++++++++++++++++-
 2 files changed, 97 insertions(+), 5 deletions(-)

diff --git a/mooc/api/v1/endpoints/wechat.py b/mooc/api/v1/endpoints/wechat.py
index 89926a2..1169d89 100644
--- a/mooc/api/v1/endpoints/wechat.py
+++ b/mooc/api/v1/endpoints/wechat.py
@@ -1,9 +1,9 @@
 from fastapi import APIRouter, Depends, HTTPException
 from typing import Dict, Any
-from mooc.utils.wechat_client import WeChatClient
+from mooc.utils.wechat_client import WeChatAPI
 
 router = APIRouter()
-wechat_client = WeChatClient()
+wechat_client = WeChatAPI()
 
 @router.get("/access_token")
 async def get_access_token() -> Dict[str, Any]:
diff --git a/mooc/utils/wechat_client.py b/mooc/utils/wechat_client.py
index cde7a25..ed5592c 100644
--- a/mooc/utils/wechat_client.py
+++ b/mooc/utils/wechat_client.py
@@ -1,9 +1,9 @@
 import json
 import httpx
-from typing import Optional, Dict, Any
+from typing import Optional, Dict, Any, Union
 from mooc.core.config import settings
 
-class WeChatClient:
+class WeChatAPI:
     def __init__(self, appid: str = None, appsecret: str = None):
         self.appid = appid or settings.WECHAT_APPID
         self.appsecret = appsecret or settings.WECHAT_APPSECRET
@@ -51,4 +51,96 @@ class WeChatClient:
         if not access_token:
             access_token = await self.get_access_token()
         url = f"https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token={access_token}"
-        return await self._post(url, data) 
\ No newline at end of file
+        return await self._post(url, data)
+
+    async def get_callback_ip(self, access_token: str = None) -> Dict[str, Any]:
+        """获取微信服务器IP地址"""
+        if not access_token:
+            access_token = await self.get_access_token()
+        url = f"https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token={access_token}"
+        return await self._get(url)
+
+    async def check_callback(self, access_token: str = None, action: str = "all") -> Dict[str, Any]:
+        """检查回调配置"""
+        if not access_token:
+            access_token = await self.get_access_token()
+        url = f"https://api.weixin.qq.com/cgi-bin/callback/check?access_token={access_token}"
+        data = {"action": action, "check_operator": "DEFAULT"}
+        return await self._post(url, data)
+
+    async def create_menu(self, menu_data: Dict[str, Any], access_token: str = None) -> Dict[str, Any]:
+        """创建自定义菜单"""
+        if not access_token:
+            access_token = await self.get_access_token()
+        url = f"https://api.weixin.qq.com/cgi-bin/menu/create?access_token={access_token}"
+        return await self._post(url, menu_data)
+
+    async def get_menu(self, access_token: str = None) -> Dict[str, Any]:
+        """获取自定义菜单"""
+        if not access_token:
+            access_token = await self.get_access_token()
+        url = f"https://api.weixin.qq.com/cgi-bin/menu/get?access_token={access_token}"
+        return await self._get(url)
+
+    def get_oauth_url(self, redirect_uri: str, scope: str, state: str = "") -> str:
+        """生成网页授权URL"""
+        url = (f"https://open.weixin.qq.com/connect/oauth2/authorize?"
+               f"appid={self.appid}&redirect_uri={redirect_uri}&response_type=code&"
+               f"scope={scope}#wechat_redirect")
+        if state:
+            url += f"&state={state}"
+        return url
+
+    async def get_user_token(self, code: str) -> Dict[str, Any]:
+        """获取用户访问令牌"""
+        url = (f"https://api.weixin.qq.com/sns/oauth2/access_token?"
+               f"appid={self.appid}&secret={self.appsecret}&code={code}&"
+               f"grant_type=authorization_code")
+        result = await self._get(url)
+        if "access_token" not in result:
+            return {"code": 4001, "message": "获取用户授权失败"}
+        return result
+
+    async def refresh_token(self, refresh_token: str) -> Dict[str, Any]:
+        """刷新访问令牌"""
+        url = (f"https://api.weixin.qq.com/sns/oauth2/refresh_token?"
+               f"appid={self.appid}&grant_type=refresh_token&refresh_token={refresh_token}")
+        return await self._get(url)
+
+    async def check_user_token(self, access_token: str, openid: str) -> bool:
+        """检查用户访问令牌是否有效"""
+        url = f"https://api.weixin.qq.com/sns/auth?access_token={access_token}&openid={openid}"
+        result = await self._get(url)
+        return result.get("errcode", -1) == 0
+
+    async def get_user_info(self, access_token: str, openid: str, 
+                           refresh_token: str = "") -> Dict[str, Any]:
+        """获取用户信息"""
+        if await self.check_user_token(access_token, openid):
+            url = (f"https://api.weixin.qq.com/sns/userinfo?"
+                  f"access_token={access_token}&openid={openid}&lang=zh_CN")
+            return await self._get(url)
+        elif refresh_token:
+            result = await self.refresh_token(refresh_token)
+            new_token = result.get("access_token")
+            if new_token:
+                return await self.get_user_info(new_token, openid)
+        return {"code": 4002, "message": "获取用户详细信息失败,请重新获取code"}
+
+    async def save_unlimited_qrcode(self, uid: Union[str, int], 
+                                  path: str, filename: str, 
+                                  access_token: str = None) -> bool:
+        """生成并保存小程序码到文件"""
+        if not access_token:
+            access_token = await self.get_access_token()
+        url = f"https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={access_token}"
+        data = {"scene": f"uid={uid}"}
+        
+        async with httpx.AsyncClient() as client:
+            response = await client.post(url, json=data, verify=False)
+            if response.status_code == 200:
+                full_path = f"{path}{filename}"
+                with open(full_path, 'wb') as f:
+                    f.write(response.content)
+                return True
+        return False 
\ No newline at end of file