UnicomObject.py 16 KB

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