client.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. """
  2. API客户端
  3. """
  4. import requests
  5. from typing import Dict, Any, Optional
  6. from src.utils.logger import get_logger
  7. from src.config.constants import API_BASE_URL, API_TIMEOUT, SOFTWARE_CODE, ENCRYPT_ENABLED
  8. from src.api.encrypt import encryptor
  9. logger = get_logger(__name__)
  10. class APIClient:
  11. """API客户端"""
  12. def __init__(self, base_url: str = API_BASE_URL):
  13. self.base_url = base_url.rstrip('/')
  14. self.session = requests.Session()
  15. self.session.timeout = API_TIMEOUT
  16. def _request(self, method: str, endpoint: str, data: Optional[Dict[str, Any]] = None,
  17. headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
  18. """发送请求"""
  19. url = f"{self.base_url}{endpoint}"
  20. # 准备请求数据
  21. request_data = data or {}
  22. request_data['software_code'] = SOFTWARE_CODE
  23. # 确保公钥已加载
  24. self.ensure_public_key_loaded()
  25. # 加密数据
  26. encrypted_data = encryptor.encrypt(request_data)
  27. # 请求头
  28. request_headers = {
  29. 'Content-Type': 'application/json',
  30. **(headers or {})
  31. }
  32. try:
  33. response = self.session.request(
  34. method=method,
  35. url=url,
  36. json={'encrypted': encrypted_data},
  37. headers=request_headers
  38. )
  39. response.raise_for_status()
  40. return response.json()
  41. except requests.exceptions.Timeout:
  42. logger.error(f"API请求超时: {url}")
  43. raise Exception("请求超时,请检查网络连接")
  44. except requests.exceptions.ConnectionError:
  45. logger.error(f"API连接失败: {url}")
  46. raise Exception("无法连接到服务器,请检查网络连接")
  47. except requests.exceptions.HTTPError as e:
  48. logger.error(f"API HTTP错误: {e.response.status_code} - {url}")
  49. try:
  50. error_data = e.response.json()
  51. error_msg = error_data.get("msg", f"请求失败: {e.response.status_code}")
  52. except:
  53. error_msg = f"请求失败: {e.response.status_code}"
  54. raise Exception(error_msg)
  55. except requests.exceptions.RequestException as e:
  56. logger.error(f"API请求失败: {e} - {url}")
  57. raise Exception(f"请求失败: {str(e)}")
  58. def register(self, username: str, password: str, email: str,
  59. card_code: str, machine_code: str) -> Dict[str, Any]:
  60. """用户注册"""
  61. return self._request('POST', '/api/user/register', {
  62. 'username': username,
  63. 'password': password,
  64. 'email': email,
  65. 'card_code': card_code,
  66. 'machine_code': machine_code
  67. })
  68. def login(self, username: str, password: str, machine_code: str) -> Dict[str, Any]:
  69. """用户登录"""
  70. return self._request('POST', '/api/user/login', {
  71. 'username': username,
  72. 'password': password,
  73. 'machine_code': machine_code
  74. })
  75. def unbind(self, username: str, password: str) -> Dict[str, Any]:
  76. """机器解绑"""
  77. return self._request('POST', '/api/user/unbind', {
  78. 'username': username,
  79. 'password': password
  80. })
  81. def heartbeat(self, user_id: int) -> Dict[str, Any]:
  82. """心跳检测"""
  83. return self._request('POST', '/api/user/heartbeat', {
  84. 'user_id': user_id
  85. })
  86. def activate_card(self, user_id: int, card_code: str) -> Dict[str, Any]:
  87. """激活卡密(充值)"""
  88. return self._request('POST', '/api/user/activate-card', {
  89. 'user_id': user_id,
  90. 'card_code': card_code
  91. })
  92. def get_recharge_records(self, user_id: int) -> Dict[str, Any]:
  93. """获取充值记录"""
  94. # GET请求需要特殊处理,因为需要传递加密数据
  95. url = f"{self.base_url}/api/user/recharge-records"
  96. # 准备请求数据
  97. request_data = {
  98. 'user_id': user_id,
  99. 'software_code': SOFTWARE_CODE
  100. }
  101. # 加密数据
  102. encrypted_data = encryptor.encrypt(request_data)
  103. try:
  104. response = self.session.get(
  105. url,
  106. params={'encrypted': encrypted_data},
  107. timeout=API_TIMEOUT
  108. )
  109. response.raise_for_status()
  110. return response.json()
  111. except requests.exceptions.Timeout:
  112. logger.error(f"API请求超时: {url}")
  113. raise Exception("请求超时,请检查网络连接")
  114. except requests.exceptions.ConnectionError:
  115. logger.error(f"API连接失败: {url}")
  116. raise Exception("无法连接到服务器,请检查网络连接")
  117. except requests.exceptions.HTTPError as e:
  118. logger.error(f"API HTTP错误: {e.response.status_code} - {url}")
  119. try:
  120. error_data = e.response.json()
  121. error_msg = error_data.get("msg", f"请求失败: {e.response.status_code}")
  122. except:
  123. error_msg = f"请求失败: {e.response.status_code}"
  124. raise Exception(error_msg)
  125. except requests.exceptions.RequestException as e:
  126. logger.error(f"API请求失败: {e} - {url}")
  127. raise Exception(f"请求失败: {str(e)}")
  128. def get_public_key(self) -> Dict[str, Any]:
  129. """获取公钥"""
  130. # 心跳接口不需要加密
  131. url = f"{self.base_url}/api/keys/public"
  132. response = self.session.get(url, timeout=API_TIMEOUT)
  133. response.raise_for_status()
  134. return response.json()
  135. def refresh_public_key(self) -> bool:
  136. """主动刷新公钥"""
  137. if not ENCRYPT_ENABLED:
  138. return True
  139. try:
  140. result = self.get_public_key()
  141. if result.get("code") == 200:
  142. data = result.get("data", {})
  143. public_key = data.get("public_key")
  144. if public_key:
  145. encryptor.load_public_key(public_key)
  146. logger.info("公钥刷新成功")
  147. return True
  148. raise ValueError("公钥数据为空")
  149. raise ValueError(result.get("msg", "获取公钥失败"))
  150. except Exception as e:
  151. logger.error(f"刷新公钥失败: {e}")
  152. raise Exception("获取公钥失败,请稍后重试")
  153. def ensure_public_key_loaded(self):
  154. """确保公钥已加载(必要时自动刷新)"""
  155. if not ENCRYPT_ENABLED:
  156. return
  157. if encryptor.public_key:
  158. return
  159. self.refresh_public_key()
  160. def get_announcement(self, page: int = 1, page_size: int = 10, keyword: Optional[str] = None) -> Dict[str, Any]:
  161. """获取公告(支持分页与检索)"""
  162. # 公告接口不需要加密,使用查询参数
  163. url = f"{self.base_url}/api/announcement"
  164. params: Dict[str, Any] = {
  165. "page": page,
  166. "page_size": page_size,
  167. }
  168. if keyword:
  169. params["keyword"] = keyword
  170. response = self.session.get(url, params=params, timeout=API_TIMEOUT)
  171. response.raise_for_status()
  172. return response.json()
  173. def get_steam_accounts(self, user_id: int) -> Dict[str, Any]:
  174. """获取Steam账号列表"""
  175. url = f"{self.base_url}/api/user/steam-accounts"
  176. # 准备请求数据
  177. request_data = {
  178. 'user_id': user_id,
  179. 'software_code': SOFTWARE_CODE
  180. }
  181. # 加密数据
  182. encrypted_data = encryptor.encrypt(request_data)
  183. try:
  184. response = self.session.get(
  185. url,
  186. params={'encrypted': encrypted_data},
  187. timeout=API_TIMEOUT
  188. )
  189. response.raise_for_status()
  190. return response.json()
  191. except requests.exceptions.Timeout:
  192. logger.error(f"API请求超时: {url}")
  193. raise Exception("请求超时,请检查网络连接")
  194. except requests.exceptions.ConnectionError:
  195. logger.error(f"API连接失败: {url}")
  196. raise Exception("无法连接到服务器,请检查网络连接")
  197. except requests.exceptions.HTTPError as e:
  198. logger.error(f"API HTTP错误: {e.response.status_code} - {url}")
  199. try:
  200. error_data = e.response.json()
  201. error_msg = error_data.get("msg", f"请求失败: {e.response.status_code}")
  202. except:
  203. error_msg = f"请求失败: {e.response.status_code}"
  204. raise Exception(error_msg)
  205. except requests.exceptions.RequestException as e:
  206. logger.error(f"API请求失败: {e} - {url}")
  207. raise Exception(f"请求失败: {str(e)}")
  208. def create_steam_account(self, user_id: int, steam_username: str,
  209. shared_secret: str, identity_secret: str,
  210. steam_id: Optional[str] = None) -> Dict[str, Any]:
  211. """添加Steam账号"""
  212. return self._request('POST', '/api/user/steam-accounts', {
  213. 'user_id': user_id,
  214. 'steam_username': steam_username,
  215. 'steam_id': steam_id,
  216. 'shared_secret': shared_secret,
  217. 'identity_secret': identity_secret
  218. })
  219. def update_steam_account(self, user_id: int, account_id: int,
  220. steam_username: Optional[str] = None,
  221. shared_secret: Optional[str] = None,
  222. identity_secret: Optional[str] = None,
  223. steam_id: Optional[str] = None,
  224. is_active: Optional[int] = None) -> Dict[str, Any]:
  225. """更新Steam账号"""
  226. data = {'user_id': user_id}
  227. if steam_username is not None:
  228. data['steam_username'] = steam_username
  229. if shared_secret is not None:
  230. data['shared_secret'] = shared_secret
  231. if identity_secret is not None:
  232. data['identity_secret'] = identity_secret
  233. if steam_id is not None:
  234. data['steam_id'] = steam_id
  235. if is_active is not None:
  236. data['is_active'] = is_active
  237. return self._request('PUT', f'/api/user/steam-accounts/{account_id}', data)
  238. def delete_steam_account(self, user_id: int, account_id: int) -> Dict[str, Any]:
  239. """删除Steam账号"""
  240. # DELETE请求需要特殊处理
  241. url = f"{self.base_url}/api/user/steam-accounts/{account_id}"
  242. # 准备请求数据
  243. request_data = {
  244. 'user_id': user_id,
  245. 'software_code': SOFTWARE_CODE
  246. }
  247. # 加密数据
  248. encrypted_data = encryptor.encrypt(request_data)
  249. try:
  250. response = self.session.delete(
  251. url,
  252. json={'encrypted': encrypted_data},
  253. timeout=API_TIMEOUT
  254. )
  255. response.raise_for_status()
  256. return response.json()
  257. except requests.exceptions.Timeout:
  258. logger.error(f"API请求超时: {url}")
  259. raise Exception("请求超时,请检查网络连接")
  260. except requests.exceptions.ConnectionError:
  261. logger.error(f"API连接失败: {url}")
  262. raise Exception("无法连接到服务器,请检查网络连接")
  263. except requests.exceptions.HTTPError as e:
  264. logger.error(f"API HTTP错误: {e.response.status_code} - {url}")
  265. try:
  266. error_data = e.response.json()
  267. error_msg = error_data.get("msg", f"请求失败: {e.response.status_code}")
  268. except:
  269. error_msg = f"请求失败: {e.response.status_code}"
  270. raise Exception(error_msg)
  271. except requests.exceptions.RequestException as e:
  272. logger.error(f"API请求失败: {e} - {url}")
  273. raise Exception(f"请求失败: {str(e)}")
  274. # 全局API客户端实例
  275. api_client = APIClient()