Sfoglia il codice sorgente

发布鼎芯电信API以及终身免流量、国内云存套餐列表不需要过滤像素

zhangdongming 1 anno fa
parent
commit
3963e56b88

+ 121 - 17
AdminController/UnicomManageController.py

@@ -7,9 +7,9 @@
 import datetime
 import hashlib
 import json
+import random
 import threading
 import time
-import random
 import uuid
 from decimal import Decimal
 
@@ -24,7 +24,9 @@ from Ansjer.config import CONFIG_INFO
 from Ansjer.config import LOGGER
 from Controller.UnicomCombo.UnicomComboController import UnicomComboView
 from Model.models import UnicomDeviceInfo, UnicomCombo, Pay_Type, UnicomComboOrderInfo, Device_User, Order_Model, \
-    ExchangeCode, UnicomFlowPush, SysMsgModel, UnicomComboExperienceHistory, LogModel, SerialNumberPackage
+    ExchangeCode, UnicomFlowPush, SysMsgModel, UnicomComboExperienceHistory, LogModel, SerialNumberPackage, \
+    AccessNumberTaskQueue
+from Object.EIoTClubObject import EIoTClubObject
 from Object.Enums.WXOperatorEnum import WXOperatorEnum
 from Object.RedisObject import RedisObject
 from Object.ResponseObject import ResponseObject
@@ -32,7 +34,7 @@ from Object.TokenObject import TokenObject
 from Object.UnicomObject import UnicomObjeect
 from Object.WXTechObject import WXTechObject
 from Service.CommonService import CommonService
-from Object.EIoTClubObject import EIoTClubObject
+from Service.TelecomService import TelecomService
 
 
 class UnicomManageControllerView(View):
@@ -103,7 +105,7 @@ class UnicomManageControllerView(View):
             elif operation == 'downloadCDK':  # 下载兑换码
                 return self.package_cdk_export_excel(response)
             elif operation == 'uploadSerialNumberFile':  # 上传序列号文件绑定4G套餐
-                return self.upload_file(tko.user, request, request_dict, response)
+                return self.upload_file(userID, tko.user, request, request_dict, response)
             elif operation == 'iccidBatchReset':  # 批量重置流量
                 return self.iccid_batch_reset(tko.user, request, request_dict, response)
             elif operation == 'orderFlowPackage':  # 订购流量套餐包
@@ -158,12 +160,13 @@ class UnicomManageControllerView(View):
                 if device_info_qs.count() > 1:
                     return response.json(177)
                 iccid = device_info_qs.first().iccid
+
                 # 根据序列号重置出厂流量套餐
                 serial_package_qs = SerialNumberPackage.objects.filter(~Q(status=1), serial_number=serial_number)
                 if serial_package_qs.exists():
                     serial_package_qs.update(status=1, updated_time=now_time, updated_by=user)
 
-                if device_info_qs.first().card_type == 1:  # 五兴电信
+                if device_info_qs.first().card_type == 1:  # 五兴电信API删除所有套餐历史
                     data = {'iccids': iccid, 'operator': WXOperatorEnum.TELECOM.value}
                     wx_tech = WXTechObject()
                     res = wx_tech.delete_card_package(**data)
@@ -172,24 +175,31 @@ class UnicomManageControllerView(View):
                         LOGGER.info(f"{serial_number}电信重置流量成功:{json.dumps(res)}")
                         return response.json(0)
                     return response.json(176)
+
                 flow_push_qs = UnicomFlowPush.objects.filter(serial_no=serial_number)
                 if flow_push_qs.exists():  # 删除流量预警推送
                     flow_push_qs.delete()
                 sys_msg_qs = SysMsgModel.objects.filter(uid=serial_number)
                 if sys_msg_qs.exists():  # 删除有关系统消息数据
                     sys_msg_qs.delete()
+
                 # 将4G用户信息状态改为已完成测试状态
                 device_info_qs.update(status=2, updated_time=now_time, user_id='')
                 combo_order_qs = UnicomComboOrderInfo.objects.filter(iccid=iccid)
                 if combo_order_qs.exists():
                     combo_order_qs.delete()
+
+                # 删除免费体验记录
                 combo_experience_history_qs = UnicomComboExperienceHistory.objects.filter(iccid=iccid)
                 if combo_experience_history_qs.exists():
                     combo_experience_history_qs.delete()
-                # UnicomObjeect().change_device_to_disable(iccid)  # 重置流量停用设备
+
+                # 重置流量后,恢复测试流量
                 result = UnicomComboView().activate_test_flow_package(serial_number)
                 ip = CommonService.get_ip_address(request)
                 describe = '重置4G流量序列号{},iccid:{},{}'.format(serial_number, iccid, result)
+
+                # 当前缓存是客户首次扫序列号,激活赠送测试流量生成的,重置流量所以要清除
                 key = f'ASJ:UNICOM:CARD:ACTIVATE:{serial_number}'
                 redis = RedisObject()
                 redis.del_data(key)
@@ -302,7 +312,8 @@ class UnicomManageControllerView(View):
                         'userID': unicom_device.user_id, 'cardType': unicom_device.card_type,
                         'status': unicom_device.status, 'mainCard': unicom_device.main_card,
                         'createdTime': unicom_device.created_time, 'updatedTime': unicom_device.updated_time,
-                        'cardStatus': self.get_device_status_by_iccid(unicom_device.iccid, unicom_device.card_type)}
+                        'cardStatus': self.get_device_status_by_iccid(unicom_device.iccid, unicom_device.card_type,
+                                                                      unicom_device.access_number)}
                 device_user_qs = Device_User.objects.filter(userID=unicom_device.user_id).values('username', 'NickName',
                                                                                                  'phone')
                 data['userName'] = device_user_qs[0]['username'] if device_user_qs.exists() else ''
@@ -479,7 +490,7 @@ class UnicomManageControllerView(View):
                         'expiration_days',
                         'expiration_type', 'price', 'is_unlimited',
                         'updated_time', 'created_time',
-                        'remark', 'is_show', 'sort', 'virtual_price').order_by('sort')
+                        'remark', 'is_show', 'sort', 'virtual_price').order_by('-created_time', 'sort')
             if not combo_qs.exists():
                 return response.json(0, [])
             total = combo_qs.count()
@@ -815,7 +826,7 @@ class UnicomManageControllerView(View):
             return response.json(0, {'packageList': package_list})
         iccid = ud_qs[0]['iccid']
         card_type = ud_qs[0]['card_type']
-        if card_type == 0:
+        if card_type == 0 or card_type == 3:
             o_qs = UnicomComboOrderInfo.objects.filter(iccid=iccid) \
                 .values('status', 'flow_total_usage', 'flow_exceed', 'activation_time', 'expire_time',
                         'combo__combo_name', 'combo__flow_total', 'updated_time') \
@@ -827,8 +838,8 @@ class UnicomManageControllerView(View):
             data = {'iccid': iccid, 'operator': 3}
             return response.json(0, {'package_list': cls.get_wx_package_list(**data)})
         if card_type == 5:
-            data = {'iccid': iccid, 'timestamp': int(time.time()), 'nonce': random.randint(10000, 99999) }
-            original_data = EIoTClubObject.query_order_record_list("v3",**data)
+            data = {'iccid': iccid, 'timestamp': int(time.time()), 'nonce': random.randint(10000, 99999)}
+            original_data = EIoTClubObject.query_order_record_list("v3", **data)
             return response.json(0, {'package_list': cls.get_dx_package_list(original_data)})
         return response.json(0, {'package_list': package_list})
 
@@ -896,20 +907,40 @@ class UnicomManageControllerView(View):
             return []
 
     @staticmethod
-    def get_device_status_by_iccid(iccid, card_type):
+    def get_device_status_by_iccid(iccid, card_type, access_number=''):
         try:
             re_data = {'iccid': iccid}
             if card_type == 0:
+
                 status_dict = {1: '已激活', 2: '可激活', 3: '已停用', 4: '已失效', 5: '可测试', 6: '库存', 7: '已更换', 8: '已清除'}
                 result = UnicomObjeect().query_device_status(**re_data)
                 res_dict = UnicomObjeect().get_text_dict(result)
                 return status_dict.get(int(res_dict['data']['status']), 'N/A')
+
             elif card_type == 1:
+
                 status_dict = {1: '库存', 2: '可激活', 3: '已激活', 4: '已停用', 5: '已失效', 6: '强制停机'}
                 data = {'iccid': iccid, 'operator': 3}
                 wx_tech = WXTechObject()
                 result = wx_tech.get_cards_info(**data)
                 return status_dict.get(int(result['data']['cardStatusCode']), 'N/A')
+
+            elif card_type == 3:
+
+                status_dict = {'connect': '已激活', 'disconnect': '已停用'}
+                i = 0
+                while i < 3:
+                    status_code = TelecomService().get_access_number_network_status(access_number)
+                    if status_code:
+                        return status_dict.get(status_code, 'N/A')
+                    i += 1
+
+                access_info = AccessNumberTaskQueue.objects.filter(iccid=iccid, status=1) \
+                    .order_by('-completion_time').values('action')
+                if not access_info.exists():
+                    return 'N/A'
+                return '已激活' if access_info[0]['action'] == 2 else '已停用'
+
             else:
                 return 'N/A'
         except Exception as e:
@@ -926,16 +957,20 @@ class UnicomManageControllerView(View):
         if not serial_number:
             return response.json(444)
         status = int(request_dict.get('status', 0))
-        unicom_qs = UnicomDeviceInfo.objects.filter(serial_no=serial_number, card_type=0)
+        unicom_qs = UnicomDeviceInfo.objects.filter(serial_no=serial_number, card_type__in=[0, 3])
         if not unicom_qs.exists():
             return response.json(173)
         unicom_service = UnicomObjeect()
         iccid = unicom_qs.first().iccid
+        access_number = unicom_qs.first().access_number
+        card_type = unicom_qs.first().card_type
         if status == 1:  # 激活
-            unicom_service.change_device_to_activate(iccid)
+            unicom_service.change_device_to_activate(iccid=iccid, card_type=card_type, access_number=access_number,
+                                                     reason='后台操作恢复网络')
             return response.json(0)
         elif status == 3:  # 停用
-            unicom_service.change_device_to_disable(iccid)
+            unicom_service.change_device_to_disable(iccid=iccid, card_type=card_type, access_number=access_number,
+                                                    reason='后台操作断网')
             return response.json(0)
         return response.json(902)
 
@@ -1020,7 +1055,10 @@ class UnicomManageControllerView(View):
         获取4G流量套餐列表
         @return: 套餐列表
         """
+        c_type = request_dict.get('type', None)
         combo_qs = UnicomCombo.objects.filter(is_del=False)
+        if c_type:
+            combo_qs = combo_qs.filter(combo_type=int(c_type))
         if not combo_qs.exists():
             return response.json(173)
         combo_qs = combo_qs.values('id', 'combo_name').order_by('-created_time')
@@ -1028,9 +1066,10 @@ class UnicomManageControllerView(View):
         return response.json(0, flow_combo_list)
 
     @classmethod
-    def upload_file(cls, user, request, request_dict, response):
+    def upload_file(cls, user_id, user, request, request_dict, response):
         """
         上传序列号文件绑定套餐id
+        @param user_id: user_id
         @param user: 当前操作用户
         @param request: file txt
         @param request_dict: package_id 4G套餐id
@@ -1046,6 +1085,11 @@ class UnicomManageControllerView(View):
         n_time = int(time.time())
         package_id = int(package_id)
         sn_list = []
+
+        uc_qs = UnicomCombo.objects.filter(id=package_id)
+        if not uc_qs.exists():
+            return response.json(173)
+
         for line in file:
             serial_number = line.decode().strip()[0:9]
             try:
@@ -1065,8 +1109,46 @@ class UnicomManageControllerView(View):
                 error_list.append({'serialNumber': serial_number, 'msg': repr(e)})
         if sn_list:
             SerialNumberPackage.objects.bulk_create(sn_list)
+
+            # 终身免流量时只保留一个有效套餐并自动激活
+            if uc_qs.first().remark == 'LIFETIME_FREE':
+                asy = threading.Thread(target=cls.activate_4G_lifetime_free, args=(sn_list, user_id))
+                asy.start()
+
         return response.json(0, {'total': len(serial_list), 'errData': error_list})
 
+    @classmethod
+    def activate_4G_lifetime_free(cls, sn_list, user_id):
+        """
+        激活4G终身免流量
+        """
+
+        for item in sn_list:
+            try:
+                serial_number = item.serial_number
+
+                # 查询ICCID
+                iccid_qs = UnicomDeviceInfo.objects.filter(serial_no=serial_number).values('iccid')
+                if not iccid_qs.exists():
+                    LOGGER.info(f'*****激活终身免流量iccid不存在serial_number:{serial_number}')
+                    continue
+
+                now_time = int(time.time())
+                # 写入套餐订购记录
+                experience_history_vo = {'iccid': iccid_qs[0]['iccid'], 'experience_type': 1, 'do_time': now_time}
+                UnicomComboExperienceHistory.objects.create(**experience_history_vo)
+
+                # 根据ICCID删除套餐记录
+                UnicomComboOrderInfo.objects.filter(iccid=iccid_qs[0]['iccid']).delete()
+
+                # 激活终身流量
+                UnicomComboView().generate_flow_package(serial_number, item.package_id, user_id)
+
+            except Exception as e:
+                LOGGER.info('UnicomManageControllerView.activate_4G_lifetime_free,'
+                            'errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+                continue
+
     @classmethod
     def iccid_batch_reset(cls, user, request, request_dict, response):
         """
@@ -1131,9 +1213,11 @@ class UnicomManageControllerView(View):
                 combo_experience_history_qs = UnicomComboExperienceHistory.objects.filter(iccid=iccid)
                 if combo_experience_history_qs.exists():  # 删除免费体验流量记录
                     combo_experience_history_qs.delete()
-                result = UnicomComboView().activate_test_flow_package(serial_number)  # 订购新的100M测试流量
 
+                result = UnicomComboView().activate_test_flow_package(serial_number)  # 订购新的100M测试流量
                 describe = '批量重置4G流量序列号{},iccid:{},{}'.format(serial_number, iccid, result)
+
+                # 当前缓存是客户首次扫序列号,激活赠送测试流量生成的,重置流量所以要清除
                 key = f'ASJ:UNICOM:CARD:ACTIVATE:{serial_number}'
                 redis = RedisObject()
                 redis.del_data(key)
@@ -1282,6 +1366,8 @@ class UnicomManageControllerView(View):
 
             else:
                 UnicomComboView.create_combo_order_info(now_order, 0, iccid, package_id)
+            # 激活新卡
+            unicom_api.change_device_to_activate(iccid)
 
             describe = f'{order_id}套餐转移成功,操作人{user}'
             cls.create_operation_log('unicom/manage/transferDevicePackage', '127.0.0.1', request_dict, describe)
@@ -1290,3 +1376,21 @@ class UnicomManageControllerView(View):
             LOGGER.info('UnicomManageControllerView.transfer_device_package, errLine:{}, errMsg:{}'.format(
                 e.__traceback__.tb_lineno, repr(e)))
             return response.json(500)
+
+    def get_dx_package_list(original_data):
+        """
+        鼎芯数据处理
+        """
+        package_list = []
+        for package in original_data["data"]:
+            new_package = {
+                "packageName": package.get("packageName", ""),
+                "status": package.get("state"),  # Assuming state 2 means 'In Use'
+                "flowTotal": package.get("flowTotal", 0),
+                "used": package.get("flowTotal", 0) - package.get("flowRemain", 0),
+                "activationTime": package.get("startDate", ""),
+                "expireTime": package.get("endDate", ""),
+                "updatedTime": ""  # 没有这个数据接口
+            }
+            package_list.append(new_package)
+        return package_list

+ 6 - 3
Controller/CloudStorage.py

@@ -204,13 +204,16 @@ class CloudStorageView(View):
         # 没体验过的设备只返回体验套餐,体验过的不返回体验套餐
         if experience_context_qs.exists():
             if not is_ai:  # 返回云存套餐
-                store_qs = store_qs.filter(~Q(pay_type='10'), Q(pixel_level=pixel_level), Q(is_ai=0))  # 筛选像素等级
-            else:          # 返回云存+AI套餐
+                store_qs = store_qs.filter(~Q(pay_type='10'), Q(is_ai=0))  # 筛选像素等级
+            else:  # 返回云存+AI套餐
                 is_ai = uid_set_qs[0]['is_ai']
                 is_ai = 1 if is_ai != 2 and CONFIG_INFO != CONFIG_CN else 0  # 国内不支持AI服务
                 if not is_ai:
                     return response.json(0)
-                store_qs = store_qs.filter(~Q(pay_type='10'), Q(pixel_level=pixel_level), Q(is_ai=1))  # 筛选像素等级
+                store_qs = store_qs.filter(~Q(pay_type='10'), Q(is_ai=1))  # 筛选像素等级
+
+            if CONFIG_INFO != CONFIG_CN:  # 国内生产环境不筛选像素 加载所有上架套餐
+                store_qs = store_qs.filter(Q(pixel_level=pixel_level))
         else:
             store_qs = store_qs.filter(pay_type='10')  # 体验套餐不区分像素等级
 

+ 57 - 13
Controller/UnicomCombo/UnicomComboController.py

@@ -138,10 +138,13 @@ class UnicomComboView(View):
                 serial_no = unicom_device_info_qs[0].serial_no
                 cls.experience_order_4G(iccid, serial_no, user_id)  # 生成4G体验订单
             unicom_api = UnicomObjeect()
+
+            # 查询正在生效套餐
             combo_order_qs = UnicomComboOrderInfo.objects.filter(iccid=iccid, status=1, is_del=False) \
                 .values('iccid', 'status', 'combo__status', 'combo__combo_name', 'combo__combo_type',
                         'combo__flow_total', 'combo__remark', 'combo__expiration_days', 'combo__expiration_type',
                         'year', 'month', 'flow_total_usage', 'expire_time', 'activation_time', 'combo__is_unlimited')
+
             if combo_order_qs.exists():
                 combo_order = combo_order_qs.first()
                 flow_details = {
@@ -157,7 +160,7 @@ class UnicomComboView(View):
                     'expirationType': combo_order['combo__expiration_type'],
                     'flowTotalUsage': combo_order['flow_total_usage'],
                     'activationTime': combo_order['activation_time'],
-                    'expireTime': combo_order['expire_time'],
+                    'expireTime': -1 if combo_order['combo__remark'] == 'LIFETIME_FREE' else combo_order['expire_time'],
                     'year': combo_order['year'],
                     'month': combo_order['month'],
                 }
@@ -171,8 +174,10 @@ class UnicomComboView(View):
                     else flow_details['usableFlow']
                 flow_details['usableFlow'] = Decimal(flow_details['usableFlow']).quantize(Decimal('0.00'))
                 flow_details.pop('flowTotalUsage')
-                cls.update_combo_order_sort(iccid)
-                flow_details['comboList'] = cls.get_combo_order_list(iccid)
+
+                cls.update_combo_order_sort(iccid)  # 排序
+
+                flow_details['comboList'] = cls.get_combo_order_list(iccid)  # 获取当前上架套餐
                 return response.json(0, flow_details)
             else:
                 flow_details = {'iccid': iccid, 'flowInvalid': 1, 'comboList': cls.get_combo_order_list(iccid)}
@@ -252,9 +257,9 @@ class UnicomComboView(View):
                 'isUnlimited': item['combo__is_unlimited'],
                 'flowTotal': item['combo__flow_total'],
                 'comboRemark': item['combo__remark'],
-                'expirationDays': item['combo__expiration_days'],
+                'expirationDays': 0,
                 'expirationType': item['combo__expiration_type'],
-                'expireTime': item['expire_time'],
+                'expireTime': -1 if item['combo__remark'] == 'LIFETIME_FREE' else item['expire_time']
             })
         return combo_list
 
@@ -407,7 +412,15 @@ class UnicomComboView(View):
             elif cls.is_telecom_sim(iccid):  # 鼎芯电信
                 params['card_type'] = 3
                 params['status'] = 2
+                access_number = cls.get_access_number(iccid)
+                if access_number:
+                    params['access_number'] = access_number
                 UnicomDeviceInfo.objects.create(**params)
+
+                result = cls.activate_test_flow_package(serial_no)  # 激活测试流量套餐
+                cls.create_operation_log('unicom/api/device-bind',
+                                         ip, request_dict,
+                                         '4G序列号{}绑定{},testFlowPackage{}'.format(serial_no, iccid, result))
                 return response.json(0)
             elif cls.is_dingxin_iot(iccid):  # 鼎芯物联卡
                 params['card_type'] = 5  # 国际
@@ -427,6 +440,21 @@ class UnicomComboView(View):
                         .format(serial_no, e.__traceback__.tb_lineno, repr(e)))
             return response.json(177, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
 
+    @classmethod
+    def get_access_number(cls, iccid):
+        """
+        根据19位数ICCID查询接入号码
+        @param iccid: 20位ICCID
+        @return: 11位接入号码
+        """
+        telecom = TelecomObject()
+        result = telecom.get_telephone(iccid[0:19])
+        if not result:
+            return None
+        if result['RESULT'] == '0':
+            return result['SMSG']
+        return None
+
     @classmethod
     def is_telecom_sim(cls, iccid):
         """
@@ -631,7 +659,7 @@ class UnicomComboView(View):
                 if not unicom_combo_qs.exists():
                     return response.json(173)
                 unicom_device_qs = UnicomDeviceInfo.objects.filter(iccid=iccid) \
-                    .values('serial_no')
+                    .values('serial_no', 'card_type')
                 if not unicom_device_qs.exists():
                     return response.json(173)
                 unicom_combo_qs = unicom_combo_qs.first()
@@ -643,11 +671,12 @@ class UnicomComboView(View):
                 order_id = CommonService.createOrderID()
                 rank_id, ai_rank_id = cls.get_cloud_or_ai_combo()
 
+                order_type = 2 if unicom_device_qs['card_type'] == 0 else 5
                 order_dict = {'rank_id': rank_id, 'ai_rank_id': ai_rank_id, 'orderID': order_id, 'UID': device_uid,
                               'userID_id': user_id, 'desc': unicom_combo_qs['combo_name'], 'payType': pay_type,
                               'payTime': now_time, 'price': price, 'currency': 'CNY', 'addTime': now_time,
                               'updTime': now_time,
-                              'unify_combo_id': str(unicom_combo_qs['id']), 'order_type': 2,
+                              'unify_combo_id': str(unicom_combo_qs['id']), 'order_type': order_type,
                               'store_meal_name': unicom_combo_qs['combo_name']
                               }
 
@@ -867,9 +896,14 @@ class UnicomComboView(View):
                         logger.info('激活类型{}'.format(activate_type))
                     # 调用联通API查询物联卡是否已激活,未激活则修改为激活状态
                     unicom_api = UnicomObjeect()
-                    if status == 1:
-                        unicom_api.change_device_to_activate(iccid)
-                        flow_total_usage = unicom_api.get_flow_usage_total(iccid)
+                    if status == 1:  # 激活当前使用的套餐,检查卡商网络状态是否为激活
+                        card_info = UnicomDeviceInfo.objects.filter(iccid=iccid).values('card_type', 'access_number')
+                        if card_info[0]['card_type'] == 3:
+                            reason = '激活' + unicom_combo['combo_name']
+                            unicom_api.change_device_to_activate(iccid, 3, card_info[0]['access_number'], reason)
+                        else:
+                            unicom_api.change_device_to_activate(iccid)
+                        flow_total_usage = unicom_api.get_flow_usage_total(iccid, card_info[0]['card_type'])
                         if flow_total_usage > 0:
                             flow_total_usage = Decimal(flow_total_usage)
                             flow_total_usage = flow_total_usage.quantize(Decimal('0.00'))
@@ -1053,7 +1087,8 @@ class UnicomComboView(View):
         @return: True | False
         """
         try:
-            u_device_info_qs = UnicomDeviceInfo.objects.filter(serial_no=serial_number, card_type=0)
+            # 查询珠海联通与鼎芯电信4G卡
+            u_device_info_qs = UnicomDeviceInfo.objects.filter(serial_no=serial_number, card_type__in=[0, 3])
             if not u_device_info_qs.exists():
                 LOGGER.info(f'{serial_number}生成联通流量套餐iccid未绑定')
                 return False
@@ -1062,6 +1097,8 @@ class UnicomComboView(View):
             if not combo_qs.exists():
                 LOGGER.info(f'{serial_number}生成联通流量套餐不存在')
                 return False
+
+            # 创建订单信息记录
             now_time = int(time.time())
             combo_info_vo = combo_qs[0]
             u_device_info = u_device_info_qs.first()
@@ -1070,19 +1107,26 @@ class UnicomComboView(View):
             if u_device_info.user_id:
                 user_id = u_device_info.user_id
             order_id = CommonService.createOrderID()
+            order_type = 2 if u_device_info.card_type == 0 else 5
+
             order_dict = {'orderID': order_id, 'UID': uid, 'rank_id': rank_id, 'ai_rank_id': ai_rank_id,
                           'userID_id': user_id, 'desc': combo_info_vo['combo_name'], 'payType': 10,
                           'payTime': now_time, 'price': combo_info_vo['price'], 'addTime': now_time,
                           'updTime': now_time, 'status': 1,
-                          'unify_combo_id': str(combo_info_vo['id']), 'order_type': 2,
+                          'unify_combo_id': str(combo_info_vo['id']), 'order_type': order_type,
                           'store_meal_name': combo_info_vo['combo_name']
                           }
-            Order_Model.objects.create(**order_dict)
+
+            orderID = Order_Model.objects.create(**order_dict)
+            LOGGER.info(f'4G套餐订单创建成功 orderID:{orderID}, orders表写入数据')
+
+            # 创建4G套餐信息记录
             UnicomComboView.create_combo_order_info(order_id, 0, u_device_info.iccid, package_id)
             if u_device_info.status != 2:
                 UnicomDeviceInfo.objects.filter(iccid=u_device_info.iccid).update(status=2, updated_time=now_time)
             LOGGER.info(f'{serial_number}生成流量套餐包成功,createdBy:{user_id}')
             return True
+
         except Exception as e:
             LOGGER.info('UnicomManageControllerView.order_flow_package,{}errLine:{}, errMsg:{}'
                         .format(serial_number, e.__traceback__.tb_lineno, repr(e)))

+ 87 - 7
Controller/UnicomCombo/UnicomComboTaskController.py

@@ -7,6 +7,7 @@
 @Software: PyCharm
 """
 import datetime
+import json
 import logging
 import threading
 import time
@@ -17,10 +18,12 @@ from django.db.models import Q
 from django.views import View
 
 from Model.models import UnicomComboOrderInfo, UnicomCombo, Order_Model, UnicomDeviceInfo, UnicomFlowPush, \
-    IotCardUsageHistory
+    IotCardUsageHistory, AccessNumberTaskQueue
 from Object.RedisObject import RedisObject
 from Object.ResponseObject import ResponseObject
+from Object.TelecomObject import TelecomObject
 from Object.UnicomObject import UnicomObjeect
+from Service.TelecomService import TelecomService
 
 logger = logging.getLogger('info')
 
@@ -63,6 +66,8 @@ class UnicomComboTaskView(View):
             return self.get_device_usage_history(response)
         elif operation == 'updateCardCycleFlow':
             return self.update_device_cycle_flow(response)
+        elif operation == 'executeAccessTask':
+            return self.get_access_number_change_task(response)
         else:
             return response.json(414)
 
@@ -152,6 +157,8 @@ class UnicomComboTaskView(View):
                 if not u_device_info_qs.exists():
                     continue
                 u_device_info_qs = u_device_info_qs.first()
+                card_type = u_device_info_qs.card_type
+
                 activate_usage_flow = float(item['flow_total_usage']) if item['flow_total_usage'] else 0.0
                 combo_id = item['combo_id']
                 combo_qs = UnicomCombo.objects.filter(id=combo_id).values()
@@ -160,10 +167,10 @@ class UnicomComboTaskView(View):
                 combo_qs = combo_qs.first()
                 flow_total = combo_qs['flow_total']
                 # 队列已使用总流量总量
-                flow_total_usage = unicom_api.get_flow_usage_total(iccid)
+                flow_total_usage = unicom_api.get_flow_usage_total(iccid, card_type)
                 flow_total_usage = float(flow_total_usage)
                 is_expire = False
-                flow = activate_usage_flow + flow_total
+                flow = activate_usage_flow + flow_total  # 激活套餐时ICCID历史用量+当前套餐流量
                 if flow_total_usage > 0:
                     # 初始套餐已使用流量 + 套餐总流量
                     if flow_total_usage >= flow:
@@ -180,9 +187,13 @@ class UnicomComboTaskView(View):
                     activate_status = cls.query_unused_combo_and_activate(iccid, year, month,
                                                                           flow_total_usage)
                     logger.info('-->当前卡{}流量已用完,是否有生效套餐:{}'.format(iccid, activate_status))
-                    if not activate_status:
-                        # 停用
-                        unicom_api.change_device_to_disable(iccid)
+                    if not activate_status:  # 停用或断网
+                        if card_type == 3:  # 鼎芯电信
+                            TelecomService().update_access_number_network(iccid, u_device_info_qs.access_number, 'ADD',
+                                                                          '套餐流量已用完')
+                        else:
+                            unicom_api.change_device_to_disable(iccid=iccid, reason='套餐流量已用完')
+
         except Exception as e:
             logger.info('异步~检测流量用量详情异常,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
 
@@ -317,7 +328,7 @@ class UnicomComboTaskView(View):
                 result = UnicomComboTaskView().query_unused_combo_and_activate(item, year, month, usage_flow)
                 if not result:  # 没有可用套餐进行停卡
                     # 停用设备
-                    unicom_api.change_device_to_disable(item)
+                    unicom_api.change_device_to_disable(iccid=item, reason='没有可用套餐')
                     logger.info('调用停卡API successful,iccid:{}'.format(item))
                     combo_order_info_qs = UnicomComboOrderInfo.objects.filter(iccid=item, status=2) \
                         .values('id').order_by('-updated_time')
@@ -529,3 +540,72 @@ class UnicomComboTaskView(View):
         except Exception as e:
             logger.info('更新账期流量异常,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
             return None
+
+    @staticmethod
+    def get_access_number_change_task(response):
+        """
+        获取接入号码变更任务
+        """
+        task_qs = AccessNumberTaskQueue.objects.exclude(status=1).filter(count__lt=4).order_by('created_time')
+        if not task_qs.exists():
+            return response.json(0)
+        logger.info('接入卡号变更任务总数:{}'.format(task_qs.count()))
+        asy = threading.Thread(target=UnicomComboTaskView.async_update_access_number_status, args=(task_qs,))
+        asy.start()
+        return response.json(0)
+
+    @staticmethod
+    def async_update_access_number_status(qs):
+        """
+        异步更新设备账期流量
+        """
+        try:
+            logger.info('进入异步更新接入卡号网络状态~~~~')
+            redis = RedisObject()
+
+            iccid_list = []
+            for item in qs:
+                try:
+                    key = f'ASJ:TELECOM:CHANGE:{item.iccid}'
+                    change_data = redis.get_data(key)
+                    if change_data:
+                        iccid_list.append(item.iccid)
+                        continue
+                    if item.iccid in iccid_list:  # 避免同一ICCID多次执行
+                        continue
+
+                    action_value = 'DEL' if item.action == 2 else 'ADD'
+
+                    result = TelecomObject().single_cut_net(item.access_number, action_value)  # 变更网络状态
+                    now_time = int(time.time())
+                    count = item.count + 1
+
+                    cache_data = {'iccid': item.iccid, 'actionValue': action_value}
+                    if not result:  # 失败修改执行次数
+                        iccid_list.append(item.iccid)
+
+                        data = {'status': 2, 'count': count, 'updated_time': now_time}
+                        AccessNumberTaskQueue.objects.filter(id=item.id).update(**data)
+
+                        redis.CONN.setnx(key, json.dumps(cache_data))
+                        redis.CONN.expire(key, 130)  # 130 秒不再变更当前ICCID网络状态
+                        continue
+
+                    data = {'status': 1, 'count': count, 'completion_time': now_time, 'updated_time': now_time}
+                    if result == '-5':  # 已符合变更后状态
+                        data['result'] = {'-5': '该号码未订购单独断网功能!;流水号:1000000190202402201640359500'}
+                    else:
+                        data['result'] = result
+
+                    iccid_list.append(item.iccid)
+
+                    AccessNumberTaskQueue.objects.filter(id=item.id).update(**data)  # 成功后修改任务记录状态
+
+                    redis.CONN.setnx(key, json.dumps(cache_data))
+                    redis.CONN.expire(key, 130)  # 130 秒不再变更当前ICCID网络状态
+
+                except Exception as e:
+                    print(repr(e))
+                    continue
+        except Exception as e:
+            logger.info('异步变更卡网络状态异常,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))

+ 15 - 10
Controller/UnicomCombo/WXTechController.py

@@ -157,7 +157,7 @@ class WXTechControllerView(View):
     @classmethod
     def create_order_package(cls, user_id, request_dict, response, request):
         """
-        五兴电信领取1G15天流量体验包
+        珠海联通、鼎芯电信、五兴电信 领取1G15天流量体验包
         """
         try:
             LOGGER.info('*****WXTechController.create_order_package:params:{}'.format(request_dict))
@@ -165,12 +165,14 @@ class WXTechControllerView(View):
             operator = request_dict.get('operator', None)
             if not all([operator, serial_no]):
                 return response.json(444)
+
             serial_no = serial_no[0:9]
             unicom_device_qs = UnicomDeviceInfo.objects.filter(serial_no=serial_no) \
                 .values('serial_no', 'user_id', 'iccid', 'card_type')
             if not unicom_device_qs.exists():
                 return response.json(173)
             unicom_device_qs = unicom_device_qs.first()
+
             iccid = unicom_device_qs['iccid']
             # 查看是否体验过免费套餐
             experience_history_qs = UnicomComboExperienceHistory.objects.filter(iccid=iccid)
@@ -179,7 +181,7 @@ class WXTechControllerView(View):
             ip = CommonService.get_ip_address(request)
             experience_history_vo = {'iccid': iccid, 'experience_type': 0, 'do_time': int(time.time())}
             app_user_id = unicom_device_qs['user_id'] if unicom_device_qs['user_id'] else user_id
-            if unicom_device_qs['card_type'] == 0:  # 珠海联通
+            if unicom_device_qs['card_type'] == 0 or unicom_device_qs['card_type'] == 3:  # 珠海联通 鼎芯电信
 
                 flow_combo_qs = UnicomCombo.objects.filter(combo_type=1, is_del=False, status=0).order_by('sort')
 
@@ -313,9 +315,10 @@ class WXTechControllerView(View):
             iccid = device_info_qs[0]['iccid']
             # 兑换码套餐类型
             card_type = device_info_qs[0]['card_type']
-            if card_type == 0:  # 联通无限流量兑换码
+            if card_type == 0 or card_type == 3:  # 联通联通和鼎芯电信无限流量兑换码
+                order_type = 2 if card_type == 0 else 5
                 result = cls.exchange_unicom_package(serial_number, iccid,
-                                                     user_id, combo_id)
+                                                     user_id, combo_id, order_type)
                 if result:
                     exchange_code_qs.update(status=True, updated_time=int(time.time()))
                     # 校验是否两个重叠无限流量套餐 修改套餐数据
@@ -345,10 +348,10 @@ class WXTechControllerView(View):
             return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
 
     @classmethod
-    def exchange_unicom_package(cls, serial_number, iccid, user_id, combo_id):
+    def exchange_unicom_package(cls, serial_number, iccid, user_id, combo_id, order_type=2):
         try:
             # 通过兑换码创建订单记录
-            order_id = cls.created_order(serial_number, user_id, 'ansjer', combo_id, 11, 2)
+            order_id = cls.created_order(serial_number, user_id, 'ansjer', combo_id, 11, order_type)
             # 根据订单信息创建流量套餐包
             UnicomComboView.create_combo_order_info(order_id, 0, iccid, combo_id)
             return True
@@ -441,9 +444,10 @@ class WXTechControllerView(View):
             n_time = int(time.time())
             # 兑换码套餐类型
             card_type = device_info_qs[0]['card_type']
-            if card_type == 0:  # 订购联通流量套餐
+            if card_type == 0 or card_type == 3:  # 订购联通流量套餐
+                order_type = 2 if card_type == 0 else 5
                 result = cls.exchange_unicom_package(serial_number, iccid,
-                                                     user_id, combo_id)
+                                                     user_id, combo_id, order_type)
                 serial_package_qs.update(status=2, updated_time=n_time, updated_by=user_id)
                 if result:
                     cls.save_log(ip, 10071, '{}领取{}成功{}'.format(serial_number, combo_name, user_id))
@@ -505,9 +509,10 @@ class WXTechControllerView(View):
 
             # 兑换码套餐类型
             card_type = device_info_qs[0]['card_type']
-            if card_type == 0:  # 订购联通流量套餐
+            if card_type == 0 or card_type == 3:  # 订购联通流量套餐
+                order_type = 2 if card_type == 0 else 5
                 result = cls.exchange_unicom_package(serial_number, iccid,
-                                                     user_id, combo_id)
+                                                     user_id, combo_id, order_type)
                 serial_package_qs.update(status=2, updated_time=n_time, updated_by=user_id)
                 if result:
                     cls.save_log(ip, 10071, '{}领取{}成功{}'.format(serial_number, combo_name, user_id))

+ 23 - 1
Model/models.py

@@ -3294,11 +3294,12 @@ class UnicomDeviceInfo(models.Model):
     status = models.SmallIntegerField(default=0, verbose_name=u'状态{0:可测试,1:测试完成,2:已使用}')
     serial_no = models.CharField(default='', db_index=True, max_length=32, verbose_name=u'设备序列号')
     user_id = models.CharField(blank=True, max_length=32, verbose_name=u'用户id')
-    card_type = models.SmallIntegerField(default=0, verbose_name=u'状态{0:联通,1:电信,2:移动,4:其它,5:国际}')
+    card_type = models.SmallIntegerField(default=0, verbose_name=u'状态{0:联通,1:五兴电信,2:移动,3:鼎芯电信,4:其它,5:鼎芯国际}')
     main_card = models.SmallIntegerField(default=0, verbose_name=u'SIM卡类型{0:拔插卡,1:贴片卡}')
     sim_used_flow = models.DecimalField(default=0, max_digits=10, decimal_places=2, verbose_name=u'sim卡已用总流量(MB)')
     sim_cycle_used_flow = models.DecimalField(default=0, max_digits=10, decimal_places=2,
                                               verbose_name=u'设备账期已用流量(MB)')
+    access_number = models.CharField(blank=True, max_length=32, verbose_name=u'接入号码')
     updated_time = models.IntegerField(default=0, verbose_name='更新时间')
     created_time = models.IntegerField(default=0, verbose_name='创建时间')
 
@@ -4158,3 +4159,24 @@ class AlexaOauth(models.Model):
         verbose_name = 'Alexa认证信息表'
 
 
+class AccessNumberTaskQueue(models.Model):
+    id = models.AutoField(primary_key=True)
+    iccid = models.CharField(db_index=True, max_length=32, verbose_name=u'完整的20位纯数字ICCID')
+    access_number = models.CharField(max_length=32, verbose_name=u'接入号码')
+    type = models.SmallIntegerField(default=1, verbose_name='操作类型,1:设备状态 2:网络状态')
+    action = models.SmallIntegerField(default=1, verbose_name='操作类型,1:断网 2:恢复网络')
+    status = models.SmallIntegerField(default=0, verbose_name='任务状态,0:未执行,1:成功,2:失败')
+    previous_status = models.SmallIntegerField(default=0, verbose_name='修改前状态,1:断网 2:未断网')
+    new_status = models.SmallIntegerField(default=0, verbose_name='修改后状态,1:断网 2:未断网')
+    count = models.IntegerField(default=0, verbose_name='执行次数')
+    result = models.JSONField(null=True, verbose_name='调用结果')
+    reason = models.CharField(max_length=256, verbose_name=u'变更原因')
+    completion_time = models.IntegerField(default=0, verbose_name='完成时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+    updated_time = models.IntegerField(default=0, verbose_name='修改时间')
+
+    class Meta:
+        db_table = 'access_number_task_queue'
+        verbose_name = '接入号码任务队列'
+        verbose_name_plural = verbose_name
+

+ 50 - 1
Object/TelecomObject.py

@@ -197,7 +197,7 @@ class TelecomObject:
             msg = response.text
             if not msg:
                 return None
-
+            LOGGER.info(f"*****singleCutNet access_number:{access_number},response:{msg}")
             # 检查响应类型
             content_type = response.headers.get('Content-Type', '')
             if 'application/xml' in content_type or 'text/xml' in content_type:
@@ -206,6 +206,8 @@ class TelecomObject:
                 result = json.loads(msg)
                 LOGGER.info(f"***TelecomObject.query_card_main_status停机/复机异常{access_number},error{result}")
                 return None
+            elif 'text/plain;charset=utf-8' in content_type:
+                return '-5'  # 已执行过的操作
             else:
                 return None
 
@@ -424,3 +426,50 @@ class TelecomObject:
                         .format(e.__traceback__.tb_lineno, repr(e)))
             return None
 
+    def off_net_action_by_access_number(self, access_number, action, quota, a_type):
+        """
+        表示达量断网新增、修改及取消接口
+        :param access_number: 接入号码access_number。
+        :param action: action=1,表示新增达量断网阈值;action=2,表示修改达量断网阈值;action=3,表示取消达量断网功能。
+                注:已达量断网的物联网卡无法通过取消达量断网功能实现恢复上网。
+        :param quota: 要添加或调整的断网阈值(单位:M)比如 1024,注意:1)设为-1表示无限制2)设为0表示有上网流量产生就会立即断网3)只能设置为-1,0或正整数
+        :param a_type: type表示要添加或调整的断网类型:设置为1:表示用户总使用量 设置为2:表示超出套餐外使用量
+        :return: 修改结果
+        :raises ValueError: 如果响应为空或HTTP请求失败。
+        """
+        try:
+            if not access_number:
+                raise ValueError("*****TelecomObject.off_net_action_by_access_number error****access_number不能为空")
+            method = 'offNetAction'
+            arr = [method, self.user_id, access_number, self.password, action, quota, a_type]
+            re_params = self.get_params_dict_by_access_number(method, access_number, arr)
+            re_params['action'] = action
+            re_params['quota'] = quota
+            re_params['type'] = a_type
+            response = self.session.post(self.url, data=re_params)
+
+            if response.status_code != 200:
+                LOGGER.info(
+                    f"*****TelecomObject.off_net_action_by_access_number error HTTP请求失败,状态码: {response.status_code}")
+                return None
+
+            msg = response.text
+            if not msg:
+                return None
+
+            content_type = response.headers.get('Content-Type', '')
+            if 'application/xml' in content_type or 'text/xml' in content_type:
+                result = xmltodict.parse(msg)
+            elif 'application/json' in content_type or 'text/json' in content_type:
+                result = json.loads(msg)
+                LOGGER.info(f"***TelecomObject.off_net_action_by_access_number 查询产品资料异常:{access_number},error{result}")
+                return None
+            else:
+                LOGGER.info("***TelecomObject.off_net_action_by_access_number 无法识别的响应类型: {}".format(content_type))
+                return None
+            return result
+
+        except Exception as e:
+            LOGGER.info('***TelecomObject.off_net_action_by_access_number:errLine:{}, errMsg:{}'
+                        .format(e.__traceback__.tb_lineno, repr(e)))
+            return None

+ 34 - 7
Object/UnicomObject.py

@@ -21,6 +21,7 @@ from Model.models import UnicomDeviceInfo
 from Object.RedisObject import RedisObject
 from Object.utils import SM3Util
 from Object.utils.SymmetricCryptoUtil import AESencrypt
+from Service.TelecomService import TelecomService
 
 """
 联通4Gapi
@@ -300,24 +301,39 @@ class UnicomObjeect:
         return sim_flow_used_total
 
     @staticmethod
-    def get_flow_usage_total(iccid):
+    def get_flow_usage_total(iccid, card_type=0):
         """
-        获取实时当前套餐队列总已用流量
-        @param iccid: 联通id
+        查询当前卡号历史总用量,默认查询珠海联通卡总使用量,单位MB
+        @param card_type: 卡类型,0:珠海联通,3:鼎芯电信
+        @param iccid: 20位卡号
         @return: flow_total_usage 当前套餐总已使用流量
         """
         flow_key = 'ASJ:UNICOM:FLOW:{}'
         usage_data = {'iccid': iccid}
         expire_time = 60 * 10 + 60
+        if card_type == 3:  # 鼎芯电信
+            return TelecomService().query_total_usage_by_access_number(iccid, flow_key.format(iccid), expire_time)
+        # 珠海联通
         return UnicomObjeect().get_flow_total_usage(flow_key.format(iccid), expire_time, **usage_data)
 
     @staticmethod
-    def change_device_to_activate(iccid):
+    def change_device_to_activate(iccid, card_type=0, access_number='', reason=''):
         """
         根据iccid判断是否激活,未激活则修改为激活状态
-        @param iccid:
+        @param reason: 原因
+        @param access_number: 11位接入号码
+        @param card_type: 0:联通,1:鼎芯电信
+        @param iccid: 20位ICCID
         @return:
         """
+        if card_type == 3 and access_number:
+            return TelecomService.update_access_number_network(iccid, access_number, 'DEL', reason)
+        card_info = UnicomDeviceInfo.objects.filter(iccid=iccid).values('card_type', 'access_number')
+        if not card_info.exists():
+            return None
+        if card_info[0]['card_type'] == 3:
+            TelecomService.update_access_number_network(iccid, card_info[0]['access_number'], 'DEL', reason)
+            return True
         if iccid:
             re_data = {'iccid': iccid}
             result = UnicomObjeect().query_device_status(**re_data)
@@ -330,12 +346,23 @@ class UnicomObjeect:
         return None
 
     @staticmethod
-    def change_device_to_disable(iccid):
+    def change_device_to_disable(iccid, card_type=0, access_number='', reason=''):
         """
         修改设备为停用,并查看是否修改成功
-        @param iccid:
+        @param reason: 原因
+        @param access_number: 11位接入号码
+        @param card_type: 0:联通,1:鼎芯电信
+        @param iccid: 20位ICCID
         @return:
         """
+        if card_type == 3 and access_number:
+            return TelecomService.update_access_number_network(iccid, access_number, 'ADD', reason)
+        card_info = UnicomDeviceInfo.objects.filter(iccid=iccid).values('card_type', 'access_number')
+        if not card_info.exists():
+            return None
+        if card_info[0]['card_type'] == 3:
+            TelecomService.update_access_number_network(iccid, card_info[0]['access_number'], 'ADD', reason)
+            return True
         if iccid:
             re_data = {"iccid": iccid, "status": 3}
             response = UnicomObjeect().update_device_state(**re_data)

+ 222 - 0
Service/TelecomService.py

@@ -0,0 +1,222 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : TelecomService.py
+@Time    : 2024/1/24 15:12
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+import json
+import re
+import time
+from decimal import Decimal
+
+from Ansjer.config import LOGGER
+from Model.models import UnicomDeviceInfo, LogModel, AccessNumberTaskQueue
+from Object.RedisObject import RedisObject
+from Object.TelecomObject import TelecomObject
+
+
+class TelecomService:
+
+    @classmethod
+    def query_total_usage_by_access_number(cls, iccid, key, expire=600):
+        """
+        根据接入号码查询设备总流量使用量(实现缓存)
+        @param iccid: 20位ICCID
+        @param key: 缓存key
+        @param expire: 失效时间
+        @return: 查询流量结果
+        """
+        redis = RedisObject()
+        sim_flow_used_total = redis.get_data(key)
+        if sim_flow_used_total:
+            return Decimal(sim_flow_used_total).quantize(Decimal('0.00'))
+        else:
+            # 查询SIM卡信息
+            sim_qs = UnicomDeviceInfo.objects.filter(iccid=iccid)
+            if not sim_qs:
+                return None
+            sim_vo = sim_qs.first()
+
+            telecom = TelecomObject()
+            # 查询电信当月流量用量
+            data = telecom.query_total_usage_by_date(sim_vo.access_number)
+            if data and data['SvcCont']['resultCode'] == '0' and 'dataCumulationTotal' in data['SvcCont']['result']:
+                cycle_total = data['SvcCont']['result'].get('dataCumulationTotal')
+            else:
+                LOGGER.info(f'query_total_usage_by_access_number查询流量异常,iccid:{iccid}')
+                return sim_vo.sim_used_flow + sim_vo.sim_cycle_used_flow
+
+            cycle_total = Decimal(cycle_total).quantize(Decimal('0.00'))
+
+            n_time = int(time.time())
+
+            # 判断数据库周期流量用量 是否大于API查询出来的周期用量 如果是则判定进入了下一个周期
+            if sim_vo.sim_cycle_used_flow != 0 and sim_vo.sim_cycle_used_flow > cycle_total:
+                sim_used_flow = sim_vo.sim_used_flow + sim_vo.sim_cycle_used_flow
+                sim_qs.update(
+                    sim_used_flow=sim_used_flow,
+                    sim_cycle_used_flow=cycle_total,
+                    updated_time=n_time
+                )
+                # 队列用量历史总量 + 上一个周期流量 + 当前周期流量 = 总消耗流量
+                sim_flow_used_total = sim_used_flow + cycle_total
+            elif cycle_total > sim_vo.sim_cycle_used_flow:  # API周期用量大于当前数据库用量则更新记录
+                sim_qs.update(sim_cycle_used_flow=cycle_total, updated_time=n_time)
+                # 队列用量历史总量 + 当前周期流量 = 总消耗流量
+                sim_flow_used_total = sim_vo.sim_used_flow + cycle_total
+            else:
+                sim_flow_used_total = sim_vo.sim_used_flow + sim_vo.sim_cycle_used_flow
+            # 缓存当月流量数据
+            redis.CONN.setnx(key, str(sim_flow_used_total))
+            redis.CONN.expire(key, expire)
+        return sim_flow_used_total
+
+    @classmethod
+    def update_access_number_network(cls, iccid, access_number, action_value, reason=''):
+        """
+        根据接入号码修改卡网络状态
+        @param reason:
+        @param iccid:
+        @param access_number: 11位接入号码
+        @param action_value: ADD:单独添加断网,DEL:单独恢复上网
+        @return: 修改后结果 None | success
+        """
+        try:
+            # 获取当前卡号是否有过变更网络状态缓存记录
+            redis = RedisObject()
+            key = f'ASJ:TELECOM:CHANGE:{iccid}'
+            change_data = redis.get_data(key)
+
+            action = 1 if action_value == 'ADD' else 2
+            now_time = int(time.time())
+            # 变更网络存表数据
+            data = {'iccid': iccid, 'access_number': access_number, 'type': 2, 'action': action, 'previous_status': 0,
+                    'new_status': 0, 'count': 0, 'reason': reason,
+                    'status': 0, 'created_time': now_time, 'updated_time': now_time}
+
+            if change_data:
+                c_data = json.loads(change_data)
+                if c_data['actionValue'] == action_value:  # 判断是否重复操作
+                    return None
+                AccessNumberTaskQueue.objects.create(**data)
+                LOGGER.info(f'{iccid}未执行变更网络,数据已加入任务队列记录')
+                return None
+
+            # 获取当前卡号网络状态 接口有调用限制并发 15000次/秒 所以尝试多次调用
+            now_network_status = 0
+            i = 0
+            while i < 3:
+                network_result = cls.get_access_number_network_status(access_number)
+                if network_result:
+                    now_network_status = 2 if network_result == 'connect' else 1
+                    break
+                i += 1
+
+            if 0 < now_network_status == action:  # 当前状态与要变更的状态相同则不调用变更API
+                ip = '127.0.0.1'
+                params = {'action': action_value, 'result': 'No need for changes', 'access_number': access_number}
+                explain = f'{iccid}单独断网或恢复网络'
+                cls.create_operation_log('updateNetworkStatus', ip, params, explain)
+                return None
+
+            result = TelecomObject().single_cut_net(access_number, action_value)
+
+            # 更新变更状态
+            data['previous_status'] = now_network_status
+            data['new_status'] = action
+            data['count'] = 1
+
+            cache_data = {'iccid': iccid, 'actionValue': action_value}
+            if not result:  # 变更电信卡网络状态 失败处理
+                data['status'] = 2
+                data['result'] = {'code': '101007', 'message': '该接入号码高频次受理此业务,调用暂时受限,请稍后再试'}
+
+                AccessNumberTaskQueue.objects.create(**data)  # 失败后保存数据记录,走重试机制
+                redis.CONN.setnx(key, json.dumps(cache_data))  # 缓存当前卡号130秒不再调用变更网络状态接口,防止调用限制
+                redis.CONN.expire(key, 130)
+                return None
+
+            data['status'] = 1
+            data['completion_time'] = now_time
+            if result == '-5':  # 已符合变更后状态
+                data['result'] = {'-5': '该号码未订购单独断网功能!;流水号:1000000190202402201640359500'}
+            else:
+                data['result'] = result
+
+            AccessNumberTaskQueue.objects.create(**data)
+            redis.CONN.setnx(key, json.dumps(cache_data))
+            redis.CONN.expire(key, 130)
+
+            return 'success'
+        except Exception as e:
+            LOGGER.info('***{} TelecomService.update_access_number_network:errLine:{}, errMsg:{}'
+                        .format(access_number, e.__traceback__.tb_lineno, repr(e)))
+            return None
+
+    @classmethod
+    def create_operation_log(cls, url, ip, request_dict, describe):
+        """
+        创建操作日志
+        @param url: 请求路径
+        @param describe: 描述
+        @param ip: 当前IP
+        @param request_dict: 请求参数
+        @return: True | False
+        """
+        try:
+            # 记录操作日志
+            content = json.loads(json.dumps(request_dict))
+            log = {
+                'ip': ip,
+                'user_id': 1,
+                'status': 200,
+                'time': int(time.time()),
+                'content': json.dumps(content),
+                'url': url,
+                'operation': describe,
+            }
+            LogModel.objects.create(**log)
+            return True
+        except Exception as e:
+            print('日志异常,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+            return False
+
+    @classmethod
+    def get_access_number_network_status(cls, access_number):
+        """
+        根据access_number查询是否单独断网
+        @param access_number: 11位接入号码
+        @return: 当前网络数据
+        """
+        try:
+            result = TelecomObject().query_product_information(access_number)
+            if not result:
+                return None
+
+            if result and result['SvcCont']['resultCode'] == '0':
+                # 获取产品信息
+                prod_infos = result["SvcCont"]["result"]["prodInfos"]
+
+                # 获取 funProdInfos 列表
+                fun_prod_infos = prod_infos["funProdInfos"]
+
+                # 遍历 funProdInfos 列表
+                for prod_info in fun_prod_infos:
+
+                    # 获取是否已单独断网信息
+                    if isinstance(prod_info, dict) and "productName" in prod_info:
+                        is_disconnect = prod_info.get("productName")
+                        if "是否已单独断网" in is_disconnect:
+                            is_disconnect_value = re.search(r"(?<=:).+", is_disconnect).group(0)
+                            # 进行判断
+                            if is_disconnect_value == "否":
+                                return 'connect'
+                            else:
+                                return 'disconnect'
+            return None
+        except Exception as e:
+            print('***{}get_access_number_network_status,errLine:{}, errMsg:{}'
+                  .format(access_number, e.__traceback__.tb_lineno, repr(e)))
+            return None