UnicomObject.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. # -*- encoding: utf-8 -*-
  2. """
  3. @File : UnicomObject.py
  4. @Time : 2022/6/17 11:03
  5. @Author : stephen
  6. @Email : zhangdongming@asj6.wecom.work
  7. @Software: PyCharm
  8. """
  9. import base64
  10. import json
  11. import logging
  12. import time
  13. from decimal import Decimal
  14. import requests
  15. from Crypto.Cipher import AES
  16. from Ansjer.config import unicomAppUrl, unicomAppId, unicomAppSecret, unicomTenantId, \
  17. unicomEncodeKey, unicomIvKey, unicomUserName, unicomPassword, unicomPushKey, SERVER_DOMAIN_SSL, UNICOM_KEY
  18. from Model.models import UnicomDeviceInfo
  19. from Object.RedisObject import RedisObject
  20. from Object.utils import SM3Util
  21. from Object.utils.SymmetricCryptoUtil import AESencrypt
  22. """
  23. 联通4Gapi
  24. 具体参数查看接口文档
  25. https://www.showdoc.com.cn/unicomJYHapi/8158648460007467
  26. """
  27. logger = logging.getLogger('info')
  28. class UnicomObjeect:
  29. def __init__(self):
  30. self.appUrl = unicomAppUrl
  31. self.appId = unicomAppId
  32. self.appSecret = unicomAppSecret
  33. self.tenantId = unicomTenantId
  34. self.encodeKey = unicomEncodeKey
  35. self.ivKey = unicomIvKey
  36. self.username = unicomUserName
  37. self.password = unicomPassword
  38. self.pushKey = unicomPushKey
  39. self.headers = {'Tenant': self.tenantId, 'content-type': 'application/x-www-form-urlencoded'}
  40. # pip install snowland-smx
  41. def createSign(self, reverse=False, **sign_params):
  42. """
  43. 调用接口(API)时需要对请求参数进行签名(sign)验证,
  44. 算法:
  45. 根据参数名称将你的所有请求参数按照字母先后顺序排序: key = value & key = value,对除签名外的所有请求参数按
  46. key
  47. 做的升序排列。
  48. 如:将
  49. foo = 1, bar = 2, baz = 3
  50. 排序为
  51. bar = 2, baz = 3, foo = 1
  52. 参数名和参数值链接后,得到拼装字符串(注意:参数名与参数值左右两边不能包含空格)
  53. bar = 2 & baz = 3 & foo = 1
  54. pushkey拼接到参数字符尾部进行
  55. SM3
  56. 加密,再转化成大写,格式是
  57. (SM3(key1=value1 & key2=value2 &...& key= pushKey)).upcase
  58. @param reverse:
  59. @param sign_params:
  60. @return:
  61. """
  62. dict_2 = dict(sorted(sign_params.items(), key=lambda item: item[0], reverse=reverse))
  63. data_list = []
  64. for item in dict_2.items():
  65. if item[0] and item[1]:
  66. data_list.append("{}={}".format(item[0], item[1]))
  67. val = '&'.join(data_list)
  68. push_key = '&key={}'.format(self.pushKey)
  69. val = val + push_key
  70. return SM3Util.Hash_sm3(val).upper()
  71. def get_login_authorization(self):
  72. """
  73. 获取登录授权
  74. 注意登录认证Authorization和登录后的请求认证不一样
  75. 算法:appId+":"+appSecret base64转码生成 前面加Basic
  76. @return: "Basic " + base64_data
  77. """
  78. voucher = self.appId + ':' + self.appSecret
  79. base64_data = str(base64.b64encode(voucher.encode("utf-8")), "utf-8")
  80. return "Basic " + base64_data
  81. def get_encode_password(self):
  82. """
  83. 获取对称加密AES
  84. @return: encrypt_pwd
  85. """
  86. aes = AESencrypt(self.encodeKey.encode('utf-8'), AES.MODE_CBC, self.ivKey.encode('utf-8'),
  87. paddingMode="ZeroPadding",
  88. characterSet='utf-8')
  89. encrypt_pwd = aes.encryptFromString(self.password)
  90. return encrypt_pwd
  91. def generate_token(self):
  92. """
  93. 生成令牌
  94. @return: token
  95. """
  96. redis = RedisObject()
  97. token = redis.get_data(UNICOM_KEY)
  98. if token:
  99. return token
  100. url = self.appUrl + '/auc/oauth/token'
  101. pwd = self.get_encode_password()
  102. body = {'username': self.username, 'password': pwd, 'grant_type': 'password', 'scope': 'server'}
  103. headers = self.headers
  104. headers['Authorization'] = self.get_login_authorization()
  105. response_data = requests.post(url, data=body, headers=headers)
  106. response_data = json.loads(response_data.text)
  107. token = response_data['access_token']
  108. expires_in = response_data['expires_in']
  109. redis.CONN.setnx(UNICOM_KEY, token)
  110. redis.CONN.expire(UNICOM_KEY, int(expires_in))
  111. return token
  112. def refresh_token(self, refresh_token):
  113. """
  114. 刷新令牌
  115. @param refresh_token:
  116. @return:
  117. """
  118. url = self.appUrl + '/auc/oauth/token?grant_type=refresh_token'
  119. body = {'refresh_token': refresh_token, 'grant_type': 'refresh_token'}
  120. headers = self.headers
  121. headers['Authorization'] = self.get_login_authorization()
  122. response_data = requests.post(url, data=body, headers=headers)
  123. return response_data.text
  124. def business_unify_headers(self):
  125. """
  126. 业务统一headers
  127. 在请求头中增加key为Authorization,value为"Bearer " + token
  128. @return: headers
  129. """
  130. token = self.generate_token()
  131. headers = self.headers
  132. headers['Authorization'] = 'Bearer ' + token
  133. return headers
  134. # 业务api 注意 get与post 请求数据类型不同 post使用:application/json
  135. def verify_device(self, **re_params):
  136. """
  137. 验证设备
  138. @param re_params:
  139. @return:
  140. """
  141. re_params['appId'] = self.appId
  142. url = self.appUrl + '/cop-platform/api/device/verify-device'
  143. return requests.get(url, params=re_params, headers=self.business_unify_headers())
  144. def query_device_status(self, **re_params):
  145. """
  146. 查询设备状态
  147. @param re_params:
  148. @return:
  149. """
  150. url = self.appUrl + '/cop-platform/api/device/detail'
  151. re_params['appId'] = self.appId
  152. headers = self.business_unify_headers()
  153. headers['content-type'] = 'application/json'
  154. return requests.get(url, params=re_params, headers=headers)
  155. def update_device_state(self, **re_data):
  156. """
  157. 修改设备状态
  158. @param re_data:
  159. @return:
  160. """
  161. url = self.appUrl + '/cop-platform/api/device/async-device-state'
  162. headers = self.business_unify_headers()
  163. headers['content-type'] = 'application/json'
  164. re_data['appId'] = self.appId
  165. re_data['callbackUrl'] = SERVER_DOMAIN_SSL + 'unicom/api/device-status-change'
  166. return requests.post(url, data=json.dumps(re_data), headers=headers)
  167. def query_device_usage_history(self, **re_params):
  168. """
  169. 查询设备用量历史
  170. @param re_params:
  171. @return:
  172. """
  173. headers = self.business_unify_headers()
  174. re_params['appId'] = self.appId
  175. url = self.appUrl + '/cop-platform/api/usage/device-usage-history'
  176. return requests.get(url, params=re_params, headers=headers)
  177. def query_current_renew_list_usage_details(self, **re_params):
  178. """
  179. 查询设备当前队列用量详情
  180. @param re_params:
  181. @return:
  182. """
  183. url = self.appUrl + '/cop-platform/api/device/current-package-usage-details'
  184. re_params['appId'] = self.appId
  185. return requests.get(url, params=re_params, headers=self.business_unify_headers())
  186. def get_device_batch_detail(self, **re_data):
  187. """
  188. 查询设备当前队列用量详情
  189. @return:
  190. """
  191. url = self.appUrl + '/platform/api/device/batch-detail'
  192. headers = self.business_unify_headers()
  193. headers['content-type'] = 'application/json'
  194. return requests.post(url, data=json.dumps(re_data), headers=headers)
  195. def query_package_list(self, **re_params):
  196. """
  197. 查询套餐列表
  198. @return:
  199. """
  200. url = self.appUrl + '/platform/api/package/list'
  201. return requests.get(url, params=re_params, headers=self.business_unify_headers())
  202. def query_renewal_list(self, **re_params):
  203. """
  204. 续费套餐列表
  205. @param re_params:
  206. @return:
  207. """
  208. url = self.appUrl + '/platform/api/package/list'
  209. return requests.get(url, params=re_params, headers=self.business_unify_headers())
  210. def async_buy_package(self, **re_data):
  211. """
  212. 查询设备当前队列用量详情
  213. @return:
  214. """
  215. url = self.appUrl + '/platform/api/package/async-buy-package'
  216. headers = self.business_unify_headers()
  217. headers['content-type'] = 'application/json'
  218. return requests.post(url, data=json.dumps(re_data), headers=headers)
  219. @staticmethod
  220. def get_text_dict(result):
  221. """
  222. 响应结果转字典
  223. @param result:
  224. @return:
  225. """
  226. if result.status_code == 200:
  227. return json.loads(result.text)
  228. return None
  229. @staticmethod
  230. def get_flow_total_usage(key, expire=600, **usage_data):
  231. """
  232. 设备当前队列用量详情(实现缓存)
  233. @param key: 缓存key
  234. @param expire: 失效时间
  235. @param usage_data: 查询参数
  236. @return: 返回结果dict
  237. """
  238. redis = RedisObject()
  239. sim_flow_used_total = redis.get_data(key)
  240. if sim_flow_used_total:
  241. return Decimal(sim_flow_used_total).quantize(Decimal('0.00'))
  242. else:
  243. # API查询设备信息其中包含周期流量使用
  244. flow_usage_details = UnicomObjeect().query_device_status(**usage_data)
  245. flow_usage_details = UnicomObjeect().get_text_dict(flow_usage_details)
  246. if flow_usage_details and flow_usage_details.get('success'):
  247. cycle_total = flow_usage_details['data'].get('simCycleUsedFlow', 0)
  248. else:
  249. cycle_total = 0
  250. # 查询SIM卡信息
  251. sim_qs = UnicomDeviceInfo.objects.filter(iccid=usage_data['iccid'])
  252. if not sim_qs:
  253. return cycle_total
  254. cycle_total = Decimal(cycle_total).quantize(Decimal('0.00'))
  255. sim_vo = sim_qs.first()
  256. n_time = int(time.time())
  257. # 判断数据库周期流量用量 是否大于API查询出来的周期用量 如果是则判定进入了下一个周期
  258. if sim_vo.sim_cycle_used_flow != 0 and sim_vo.sim_cycle_used_flow > cycle_total:
  259. sim_used_flow = sim_vo.sim_used_flow + sim_vo.sim_cycle_used_flow
  260. sim_qs.update(
  261. sim_used_flow=sim_used_flow,
  262. sim_cycle_used_flow=cycle_total,
  263. updated_time=n_time
  264. )
  265. # 队列用量历史总量 + 上一个周期流量 + 当前周期流量 = 总消耗流量
  266. sim_flow_used_total = sim_used_flow + cycle_total
  267. elif cycle_total > sim_vo.sim_cycle_used_flow: # API周期用量大于当前数据库用量则更新记录
  268. sim_qs.update(sim_cycle_used_flow=cycle_total, updated_time=n_time)
  269. # 队列用量历史总量 + 当前周期流量 = 总消耗流量
  270. sim_flow_used_total = sim_vo.sim_used_flow + cycle_total
  271. else:
  272. sim_flow_used_total = sim_vo.sim_used_flow + sim_vo.sim_cycle_used_flow
  273. redis.CONN.setnx(key, str(sim_flow_used_total))
  274. redis.CONN.expire(key, expire)
  275. return sim_flow_used_total
  276. @staticmethod
  277. def get_flow_usage_total(iccid):
  278. """
  279. 获取实时当前套餐队列总已用流量
  280. @param iccid: 联通id
  281. @return: flow_total_usage 当前套餐总已使用流量
  282. """
  283. flow_key = 'ASJ:UNICOM:FLOW:{}'
  284. usage_data = {'iccid': iccid}
  285. expire_time = 60 * 10 + 60
  286. return UnicomObjeect().get_flow_total_usage(flow_key.format(iccid), expire_time, **usage_data)
  287. @staticmethod
  288. def change_device_to_activate(iccid):
  289. """
  290. 根据iccid判断是否激活,未激活则修改为激活状态
  291. @param iccid:
  292. @return:
  293. """
  294. if iccid:
  295. re_data = {'iccid': iccid}
  296. result = UnicomObjeect().query_device_status(**re_data)
  297. res_dict = UnicomObjeect().get_text_dict(result)
  298. # 状态不等于1(激活)时进行激活 1:激活;3:停用
  299. if res_dict['data']['status'] != 1:
  300. re_data = {"iccid": iccid, "status": 1}
  301. UnicomObjeect().update_device_state(**re_data)
  302. return True
  303. return None
  304. @staticmethod
  305. def change_device_to_disable(iccid):
  306. """
  307. 修改设备为停用,并查看是否修改成功
  308. @param iccid:
  309. @return:
  310. """
  311. if iccid:
  312. re_data = {"iccid": iccid, "status": 3}
  313. response = UnicomObjeect().update_device_state(**re_data)
  314. logger.info('停用iccid响应结果:{}'.format(response.text))
  315. # 查询是否停用成功
  316. re_data.pop('status')
  317. result = UnicomObjeect().query_device_status(**re_data)
  318. res_dict = UnicomObjeect().get_text_dict(result)
  319. logger.info('查询iccid状态:{}'.format(res_dict))
  320. if res_dict['data']['status'] != 3:
  321. re_data['status'] = 3
  322. response = UnicomObjeect().update_device_state(**re_data)
  323. logger.info('再次停卡:{}'.format(response.text))
  324. return True
  325. return None
  326. @staticmethod
  327. def current_sim_traffic_usage_details(icc_id):
  328. """
  329. 当前sim卡流量使用详情
  330. @param icc_id:20位数字iccid
  331. @return: traffic
  332. """
  333. redis = RedisObject()
  334. traffic_key = 'ASJ:UNICOM:FLOW:{}'.format(icc_id)
  335. traffic_sys = 'ASJ:SIM:TRAFFIC:{}'.format(icc_id)
  336. traffic_val = redis.get_data(traffic_key)
  337. if traffic_val:
  338. traffic_dict = json.loads(traffic_val)
  339. redis.set_data(key=traffic_sys, val=traffic_val, expire=60 * 60 * 24)
  340. else:
  341. traffic_val = redis.get_data(traffic_sys)
  342. if not traffic_val:
  343. return 0
  344. traffic_dict = json.loads(traffic_val)
  345. return traffic_dict['data']['flowTotalUsage']