纷享CRM与ERP对接的ParentClass
# ! /usr/bin/env python3
# _*_ coding:utf-8 _*_
'''
Created on 2018年10月16日
@author: Andy
'''
from os import (path, remove)
from sys import (_getframe, exc_info)
from subprocess import(Popen, PIPE)
from time import (sleep, mktime)
from datetime import (datetime, timedelta)
from urllib.request import (Request, urlopen)
from json import (dumps, loads)
from importlib import (import_module)
from LogObject import (LogObject)
class DataObject(object):
__LogObject = None
__DBObject = None
__CRMDict = {
'appOpen': False,
'appNo': 'fxiaoke',
'appName': '纷享销客',
'appURL': 'open.fxiaoke.com',
'appCode': 'utf-8',
'downloadPath': 'V:\\crm\\download\\',
'urlAgent': 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko',
'urlType': 'application/json',
'urlCode': 'utf8',
'appId': 'FSAID_abcdefg',
'appSecret': 'abcdefg',
'permanentCode': 'abcdefg',
'openUserId': 'FSUID_abcdefg',
'currentOpenUserId': 'FSUID_abcdefg',
'expiresIn': None,
'corpId': None,
'corpAccessToken': None,
'errorCode': None,
'errorMessage': None,
'errorDescription': None,
'urlCount': 0,
'offset': 0,
'limit': 100,
'apiName': None,
'datas': None,
}
__ERRORDict = {
'-2': '系统错误',
'-1': '系统繁忙',
'0': '请求成功',
'10001': '缺少参数appId',
'10002': '缺少参数appSecret',
'10003': '缺少参数appAccessToken',
'10004': '缺少参数redirectUri',
'10005': '缺少参数responseType',
'10006': '缺少参数scope',
'10007': '缺少参数state',
'10008': '缺少参数code',
'10009': '缺少参数appAccount',
'10010': '缺少参数openUserId',
'10012': '缺少参数permanentCode',
'10013': '缺少参数corpAccessToken',
'10014': '缺少参数corpId',
'10015': '缺少参数toUser',
'11001': '参数appId不合法',
'11002': '参数appSecret不合法',
'11003': '参数appAccessToken不合法',
'11004': '参数redirectUri不合法',
'11005': '参数responseType不合法',
'11006': '参数scope不合法',
'11007': '参数state不合法',
'11008': '参数openUserId不合法',
'11009': '参数departmentId不合法',
'11010': '参数code不合法',
'11011': '参数appAccount不合法',
'11013': '参数permanentCode不合法',
'11014': '参数corpAccessToken不合法',
'11015': '参数corpId不合法',
'11016': '参数toUser不合法',
'11017': '参数msgType不合法',
'11018': '参数templateId不合法',
'12002': '登录状态错误',
'12003': '未支持的消息类型',
'12004': 'POST的数据包为空',
'12005': '文本消息内容为空',
'14001': '接口调用超过限制',
'15002': '参数不合法',
'15003': 'APP没有访问权限',
'20005': 'accessToken不存在或者已经过期',
'20006': 'appId或appSecret错误',
'20010': 'CODE不存在或者已经过期',
'20012': 'openUserId未找到',
'20014': '应用没有获取该员工的数据的权限',
'20015': '永久授权码错误',
'20016': 'corpAccessToken不存在或者已经过期',
'20017': 'corpId未找到',
'20020': '应用没有获取该企业的数据的权限',
'20021': '在当前企业下,该app的状态为停用',
'20022': '企业没有对该app授权',
'20023': 'APP没有访问department的权限',
'30007': '部门不存在',
'30027': '员工不存在',
'32000': '参数错误',
'40010': 'templateId不合法'
}
def __init__(self):
object.__init__(self)
@property
def LogObject(self):
return self.__LogObject
@LogObject.setter
def LogObject(self, logObject):
self.__LogObject = logObject
@property
def DBObject(self):
return self.__DBObject
@DBObject.setter
def DBObject(self, dbObject):
self.__DBObject = dbObject
@property
def CRMDict(self):
return self.__CRMDict
@CRMDict.setter
def CRMDict(self, crmDict):
self.__CRMDict = crmDict
@property
def ERRORDict(self):
return self.__ERRORDict
def ReloadObject(self, objectNo):
CRMModule = import_module('data.{}'.format(objectNo))
CRMObject = getattr(CRMModule, 'DataObject')
DataObject = CRMObject()
DataObject.CRMDict = self.CRMDict
DataObject.LogObject = self.LogObject
DataObject.DBObject = self.DBObject
try:
DataObject.CreateObject()
finally:
del DataObject
def PingDevice(self, ThreadID, langNo = 'GBK'):
FunName = _getframe().f_code.co_name
self.LogInfo = 'Thread-{}, DeviceNo={}, DeviceName={}, DeviceIP={}'.format(ThreadID, self.CRMDict['appNo'], self.CRMDict['appName'], self.CRMDict['appURL'])
try:
with Popen('ping -l 1 -n 1 {}'.format(self.CRMDict['appURL']), stdin = PIPE, stdout = PIPE, stderr = PIPE, shell = True) as file:
dataStr = file.stdout.read().decode(langNo)
if langNo == 'GBK':
if dataStr.find('已接收 = 1') > 0:
self.LogObject.warning('{} Success: {}'.format(FunName, self.LogInfo))
return True
except:
self.LogObject.error('{} Except: {}, {}'.format(FunName, self.LogInfo, exc_info()[0], exc_info()[1]))
return False
# 获取 CorpAccessToken
def OpenDevice(self):
FunName = _getframe().f_code.co_name
self.CRMDict['appOpen'] = False
urlData = {
'url': 'https://open.fxiaoke.com/cgi/corpAccessToken/get/V2',
'data': {
'appId': self.CRMDict['appId'],
'appSecret': self.CRMDict['appSecret'],
'permanentCode': self.CRMDict['permanentCode']
}
}
if self.OpenDeviceLoadURL(urlData['url'], urlData['data']):
# 获取 AppAccessToken
urlData = {
'url': 'https://open.fxiaoke.com/cgi/appAccessToken/get',
'data': {
'appId': self.CRMDict['appId'],
'appSecret': self.CRMDict['appSecret']
}
}
if self.OpenDeviceLoadURL(urlData['url'], urlData['data']):
sleep(1)
self.CRMDict['appOpen'] = True
self.LogObject.warning('{} Success: {}'.format(FunName, self.CRMDict['appOpen']))
return True
# # 获取 JsapiTicket
# urlData = {
# 'url': 'https://open.fxiaoke.com/cgi/jsApiTicket/get',
# 'data': {
# 'corpAccessToken': self.CRMDict['corpAccessToken'],
# 'corpId': self.CRMDict['corpId'],
# }
# }
# if self.LoadURL(urlData['url'], urlData['data']):
# pass
return False
def CloseDevice(self):
return False
def OpenDeviceLoadURL(self, urlStr, urlData):
FunName = _getframe().f_code.co_name
if urlStr is None or urlData is None: return False
self.CRMDict['errorCode'] = None
self.CRMDict['errorMessage'] = None
self.CRMDict['errorDescription'] = None
try:
headerStr = {
'User-Agent': self.CRMDict['urlAgent'],
'Content-Type': self.CRMDict['urlType']
}
jsonData = bytes(dumps(urlData), self.CRMDict['urlCode'])
req = Request(url=urlStr, data=jsonData, headers=headerStr, method='POST')
res = urlopen(req)
if not res is None and res.reason == 'OK':
resData = res.read().decode(self.CRMDict['appCode'])
jsonDict = loads(resData)
if isinstance(jsonDict, dict):
self.CRMDict.update(jsonDict)
if self.CRMDict['errorCode'] == 0 and self.CRMDict['errorMessage'] == 'success':
self.LogObject.info('{} Success: {}, {}'.format(FunName, urlStr, jsonDict))
return True
else:
self.LogObject.warning('{} Error: {}, {}'.format(FunName, urlStr, jsonDict))
return False
except:
self.LogObject.error('{} Except: {}, {}'.format(FunName, urlStr, exc_info()[0], exc_info()[1]))
return False
def PostURL(self, urlStr = None, urlData = None, urlMethod = 'POST', userAgent = 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko', contentType = 'application/json'):
FunName = _getframe().f_code.co_name
if urlStr is None or urlData is None:
return False, None
try:
headerStr = {
'User-Agent': userAgent,
'Content-Type': contentType
}
jsonData = bytes(dumps(urlData),'utf8')
req = Request(url=urlStr, data=jsonData, headers=headerStr, method=urlMethod)
res = urlopen(req)
self.LogObject.warning('{} Success: {}'.format(FunName, urlStr))
return True, res
except:
self.LogObject.error('{} Except: {}, {}'.format(FunName, urlStr, exc_info()[0], exc_info()[1]))
return False, None
def LoadURL(self, urlStr, urlData):
FunName = _getframe().f_code.co_name
if urlStr is None or urlData is None: return False
self.CRMDict['errorCode'] = None
self.CRMDict['errorMessage'] = None
self.CRMDict['errorDescription'] = None
if self.CRMDict['urlCount'] == 800:
self.CRMDict['urlCount'] = 0
sleep(24) # 速度太快,会被踢
self.CRMDict['urlCount'] = self.CRMDict['urlCount'] + 1
try:
headerStr = {
'User-Agent': self.CRMDict['urlAgent'],
'Content-Type': self.CRMDict['urlType']
}
jsonData = bytes(dumps(urlData), self.CRMDict['urlCode'])
req = Request(url=urlStr, data=jsonData, headers=headerStr, method='POST')
res = urlopen(req)
if not res is None and res.reason == 'OK':
resData = res.read().decode(self.CRMDict['appCode'])
jsonDict = loads(resData)
if isinstance(jsonDict, dict):
self.CRMDict.update(jsonDict)
if self.IsSuccess():
self.LogObject.info('{} Success: {}, {}'.format(FunName, urlStr, jsonDict))
return True
else:
self.LogObject.warning('{} Error: {}, {}'.format(FunName, urlStr, jsonDict))
if self.CRMDict['errorCode'] == 20016 and self.CRMDict['appOpen']:
if self.PingDevice(1):
if self.OpenDevice():
return self.OpenDeviceLoadURL(urlStr, urlData)
return False
except:
self.LogObject.error('{} Except: {}, {}'.format(FunName, urlStr, exc_info()[0], exc_info()[1]))
return False
def DownLoadURL(self, urlStr, urlData, urlFile):
FunName = _getframe().f_code.co_name
if urlStr is None or urlData is None: return False
self.CRMDict['errorCode'] = None
self.CRMDict['errorMessage'] = None
self.CRMDict['errorDescription'] = None
if self.CRMDict['urlCount'] == 800:
self.CRMDict['urlCount'] = 0
sleep(24) # 速度太快,会被踢
self.CRMDict['urlCount'] = self.CRMDict['urlCount'] + 1
try:
headerStr = {
'User-Agent': self.CRMDict['urlAgent'],
'Content-Type': self.CRMDict['urlType']
}
jsonData = bytes(dumps(urlData), self.CRMDict['urlCode'])
req = Request(url=urlStr, data=jsonData, headers=headerStr, method='POST')
res = urlopen(req)
if not res is None and res.reason == 'OK':
if path.exists(urlFile) or path.isfile(urlFile):
remove(urlFile)
resData = res.read()
with open(urlFile, 'wb') as file:
file.write(resData)
if path.getsize(urlFile) == 0:
remove(urlFile)
if path.exists(urlFile) and path.isfile(urlFile):
self.LogObject.warning('{} Success: {}'.format(FunName, urlFile))
return True
self.LogObject.warning('{} Error: {} {}'.format(FunName, urlFile, res.reason))
return False
except:
self.LogObject.error('{} Except: {}, {}'.format(FunName, urlStr, exc_info()[0], exc_info()[1]))
return False
def DownLoadDataObject(self, mediaId = None, mediaFile = None):
if mediaId is None: return False
if not isinstance(mediaId, str): return False
urlData = {
'url': 'https://open.fxiaoke.com/media/download',
'data': {
'corpAccessToken': self.CRMDict['corpAccessToken'],
'corpId': self.CRMDict['corpId'],
"mediaId": mediaId,
}
}
return self.DownLoadURL(urlData['url'], urlData['data'], mediaFile)
def IsSuccess(self):
if self.CRMDict['errorCode'] == 0 and self.CRMDict['errorMessage'] == 'success': return True
return False
def GetDictData(self, dataDict, dataKey, dataType="string"):
if dataKey in dataDict.keys():
if isinstance(dataDict[dataKey], list):
if len(dataDict[dataKey]) > 0: return str(dataDict[dataKey])
else:
data = dataDict.get(dataKey,'')
if not data is None:
if isinstance(data, bool): return 1 if data else 0
elif isinstance(data, int):
if dataType == 'timestamp': return (datetime.utcfromtimestamp(data/1000)).strftime("%Y-%m-%d %H:%M:%S.%f")
return str(data)
return data
return ''
def QueryObjectDescribe(self, objectNo = None):
if not objectNo is None: self.CRMDict['apiName'] = objectNo
urlData = {
'url': ' https://open.fxiaoke.com/cgi/crm/object/describe',
'data': {
'corpAccessToken': self.CRMDict['corpAccessToken'],
'corpId': self.CRMDict['corpId'],
'currentOpenUserId': self.CRMDict['currentOpenUserId'],
'apiName': self.CRMDict['apiName'],
}
}
return self.LoadURL(urlData['url'], urlData['data'])
def QueryDataObject(self, objectNo = None, objectOffset = 0):
if not objectNo is None: self.CRMDict['apiName'] = objectNo
if not objectOffset is None: self.CRMDict['offset'] = objectOffset
urlData = {
'url': 'https://open.fxiaoke.com/cgi/crm/data/query',
'data': {
'corpAccessToken': self.CRMDict['corpAccessToken'],
'corpId': self.CRMDict['corpId'],
'currentOpenUserId': self.CRMDict['currentOpenUserId'],
'apiName': self.CRMDict['apiName'],
'searchQuery': {'offset': self.CRMDict['offset'], 'limit': self.CRMDict['limit']}
}
}
return self.LoadURL(urlData['url'], urlData['data'])
def QueryDataObjectRange(self, objectNo = None, objectOffset = 0, queryRange = dict):
if not objectNo is None: self.CRMDict['apiName'] = objectNo
if not objectOffset is None: self.CRMDict['offset'] = objectOffset
urlData = {
'url': 'https://open.fxiaoke.com/cgi/crm/data/query',
'data': {
'corpAccessToken': self.CRMDict['corpAccessToken'],
'corpId': self.CRMDict['corpId'],
'currentOpenUserId': self.CRMDict['currentOpenUserId'],
'apiName': self.CRMDict['apiName'],
'searchQuery': {'offset': self.CRMDict['offset'], 'limit': self.CRMDict['limit'], 'rangeConditions': [{'fieldName': 'last_modified_time', 'from': queryRange['from'], 'to': queryRange['to']}]}
}
}
return self.LoadURL(urlData['url'], urlData['data'])
def QueryDataObjectConditions(self, objectNo = None, queryRange = dict):
if not objectNo is None: self.CRMDict['apiName'] = objectNo
urlData = {
'url': 'https://open.fxiaoke.com/cgi/crm/data/query',
'data': {
'corpAccessToken': self.CRMDict['corpAccessToken'],
'corpId': self.CRMDict['corpId'],
'currentOpenUserId': self.CRMDict['currentOpenUserId'],
'apiName': self.CRMDict['apiName'],
'searchQuery': {'offset': 0, 'limit': self.CRMDict['limit'], 'conditions': [{'conditionType': 'term_condition', 'conditions': queryRange}]}
}
}
return self.LoadURL(urlData['url'], urlData['data'])
def CreateDataObject(self, objectNo = None, dataDict = None):
if dataDict is None: return False
if not isinstance(dataDict, dict): return False
if not objectNo is None: self.CRMDict['apiName'] = objectNo
urlData = {
'url': 'https://open.fxiaoke.com/cgi/crm/data/create',
'data': {
'corpAccessToken': self.CRMDict['corpAccessToken'],
'corpId': self.CRMDict['corpId'],
'currentOpenUserId': self.CRMDict['currentOpenUserId'],
'apiName': self.CRMDict['apiName'],
'data': dataDict
}
}
return self.LoadURL(urlData['url'], urlData['data'])
def DropDataObject(self, objectNo = None, dataID = None):
if dataID is None: return False
if not isinstance(dataID, str): return False
if not objectNo is None: self.CRMDict['apiName'] = objectNo
urlData = {
'url': 'https://open.fxiaoke.com/cgi/crm/data/drop',
'data': {
'corpAccessToken': self.CRMDict['corpAccessToken'],
'corpId': self.CRMDict['corpId'],
'currentOpenUserId': self.CRMDict['currentOpenUserId'],
'apiName': self.CRMDict['apiName'],
"dataId": dataID
}
}
return self.LoadURL(urlData['url'], urlData['data'])
def DeleteDataObject(self, objectNo = None, dataID = None):
if dataID is None: return False
if not isinstance(dataID, str): return False
if not objectNo is None: self.CRMDict['apiName'] = objectNo
urlData = {
'url': 'https://open.fxiaoke.com/cgi/crm/data/delete',
'data': {
'corpAccessToken': self.CRMDict['corpAccessToken'],
'corpId': self.CRMDict['corpId'],
'currentOpenUserId': self.CRMDict['currentOpenUserId'],
'apiName': self.CRMDict['apiName'],
"dataId": dataID
}
}
return self.LoadURL(urlData['url'], urlData['data'])
def UpdateDataObject(self, objectNo = None, dataID = None, dataDict = None):
if dataID is None: return False
if not isinstance(dataID, str): return False
if dataDict is None: return False
if not isinstance(dataDict, dict): return False
if not objectNo is None: self.CRMDict['apiName'] = objectNo
urlData = {
'url': 'https://open.fxiaoke.com/cgi/crm/data/update',
'data': {
'corpAccessToken': self.CRMDict['corpAccessToken'],
'corpId': self.CRMDict['corpId'],
'currentOpenUserId': self.CRMDict['currentOpenUserId'],
'apiName': self.CRMDict['apiName'],
"dataId": dataID,
"data": dataDict
}
}
return self.LoadURL(urlData['url'], urlData['data'])
def QueryDataOption(self, tableNo = None, fieldList = list()):
FunName = _getframe().f_code.co_name
dataSQL = list()
dataList = list()
dataDict = dict()
maxTuple = tuple()
#0
dataSQL.append('''
SELECT NVL(MAX(A10.tc_dict05),0) AS datacount
FROM tc_dict_file A10
WHERE (1 = 1)
AND A10.tc_dict01 = 'crm' AND A10.tc_dict02 = :v1
''')
#1
dataSQL.append('''
UPDATE tc_dict_file A10
SET A10.tc_dict04 = :v3
WHERE (1 = 1)
AND A10.tc_dict01 = 'crm' AND A10.tc_dict02 = :v1 AND tc_dict03 = :v2
''')
#2
dataSQL.append('''
INSERT INTO tc_dict_file(
tc_dict01, tc_dict02, tc_dict05, tc_dict03, tc_dict04,
tc_dictcuser, tc_dictcdate)
SELECT 'crm', :1, :2, :3, :4,
'AUTO', SYSDATE
FROM dual
WHERE NOT EXISTS(SELECT 1 FROM tc_dict_file C1 WHERE C1.tc_dict01 = 'crm' AND C1.tc_dict02 = :5 AND C1.tc_dict03 = :6)
''')
try:
if self.QueryObjectDescribe(tableNo):
for fieldNo in self.CRMDict['objectDesc']['fields']:
if self.CRMDict['objectDesc']['fields'][fieldNo]['type'] == 'select_one':
dataDict = {
'v1': '{}.{}'.format(tableNo, fieldNo),
}
maxTuple = self.DBObject.DBQueryOne(True, dataSQL[0], dataDict)
if maxTuple is None or isinstance(maxTuple, tuple) or len(maxTuple) < 1 or maxTuple[0] < 1: maxTuple = (0,)
for data in self.CRMDict['objectDesc']['fields'][fieldNo]['options']:
dataDict = {
'v1': '{}.{}'.format(tableNo, fieldNo),
'v2': data['value'],
'v3': data['label'],
}
if not self.DBObject.DBExecute(True, dataSQL[1], dataDict):
self.LogObject.error("{} Error: {}, {}, {}".format(FunName, '{}.{}'.format(tableNo, fieldNo), data['value'], data['label']))
dataTupe = (
'{}.{}'.format(tableNo, fieldNo),
maxTuple[0]+len(dataList),
data['value'],
data['label'],
'{}.{}'.format(tableNo, fieldNo),
data['value'],
)
dataList.append(dataTupe)
if len(dataList) > 0:
if self.DBObject.DBExecuteMany(True, dataSQL[2], dataList):
if self.DBObject.DBDataCount > 0: self.LogObject.warning('{} Success, 【{}】 CreateCount={}'.format(FunName, '{}.{}'.format(tableNo, fieldNo), self.DBObject.DBDataCount))
except:
self.LogObject.error('{} Except: {}, {}'.format(FunName, exc_info()[0], exc_info()[1]))
def GetTimestamp(self, today = None, sec = 0):
if today is None:
return int(mktime((datetime.now()+timedelta(seconds=sec)).timetuple())*1000)
d = datetime.strptime(today,'%Y-%m-%d %H:%M:%S.%f')+timedelta(seconds=sec)
return int((mktime(d.timetuple())+d.microsecond/1000000)*1000)