codosdk 1.0.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codosdk-1.0.0.data/data/share/doc/codo_sdk/README.md +50 -0
- codosdk-1.0.0.dist-info/LICENSE +674 -0
- codosdk-1.0.0.dist-info/METADATA +35 -0
- codosdk-1.0.0.dist-info/RECORD +45 -0
- codosdk-1.0.0.dist-info/WHEEL +6 -0
- codosdk-1.0.0.dist-info/top_level.txt +2 -0
- opssdk/__init__.py +0 -0
- opssdk/utils/__init__.py +24 -0
- websdk2/__init__.py +0 -0
- websdk2/api_set.py +43 -0
- websdk2/apis/__init__.py +9 -0
- websdk2/apis/admin_apis.py +160 -0
- websdk2/apis/agent_apis.py +44 -0
- websdk2/apis/cmdb_apis.py +22 -0
- websdk2/apis/kerrigan_apis.py +21 -0
- websdk2/apis/mgv4_apis.py +95 -0
- websdk2/apis/task_apis.py +80 -0
- websdk2/application.py +77 -0
- websdk2/base_handler.py +275 -0
- websdk2/cache.py +128 -0
- websdk2/cache_context.py +38 -0
- websdk2/client.py +173 -0
- websdk2/cloud/__init__.py +0 -0
- websdk2/cloud/qcloud_api.py +57 -0
- websdk2/cloud/ucloud_api.py +73 -0
- websdk2/cloud_utils.py +26 -0
- websdk2/configs.py +97 -0
- websdk2/consts.py +212 -0
- websdk2/db_context.py +150 -0
- websdk2/error.py +50 -0
- websdk2/fetch_coroutine.py +12 -0
- websdk2/jwt_token.py +135 -0
- websdk2/ldap.py +117 -0
- websdk2/logger.py +42 -0
- websdk2/model_utils.py +156 -0
- websdk2/mqhelper.py +120 -0
- websdk2/program.py +20 -0
- websdk2/salt_api.py +107 -0
- websdk2/sqlalchemy_pagination.py +73 -0
- websdk2/tools.py +198 -0
- websdk2/utils/__init__.py +257 -0
- websdk2/utils/cc_crypto.py +85 -0
- websdk2/utils/date_format.py +35 -0
- websdk2/utils/pydantic_utils.py +55 -0
- websdk2/web_logs.py +127 -0
websdk2/salt_api.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8-*-
|
|
3
|
+
'''
|
|
4
|
+
Author : SS
|
|
5
|
+
date : 2017年12月29日14:43:24
|
|
6
|
+
role : 集中化管理工具的使用
|
|
7
|
+
'''
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
import json
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import cookielib
|
|
15
|
+
except:
|
|
16
|
+
import http.cookiejar as cookielib
|
|
17
|
+
|
|
18
|
+
import ssl
|
|
19
|
+
|
|
20
|
+
context = ssl._create_unverified_context()
|
|
21
|
+
import urllib3
|
|
22
|
+
|
|
23
|
+
urllib3.disable_warnings()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SaltApi:
|
|
27
|
+
"""
|
|
28
|
+
定义salt api接口的类
|
|
29
|
+
初始化获得token
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, url='https://127.0.0.1:8001/', username="saltapi", password="shenshuo"):
|
|
33
|
+
self.__url = url
|
|
34
|
+
self.__username = username
|
|
35
|
+
self.__password = password
|
|
36
|
+
self.headers = {
|
|
37
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
|
38
|
+
"Content-type": "application/json"
|
|
39
|
+
# "Content-type": "application/x-yaml"
|
|
40
|
+
}
|
|
41
|
+
self.params = {'client': 'local', 'fun': '', 'tgt': ''}
|
|
42
|
+
self.login_url = self.__url + "login"
|
|
43
|
+
self.login_params = {'username': self.__username, 'password': self.__password, 'eauth': 'pam'}
|
|
44
|
+
self.token = self.get_data(self.login_url, self.login_params)['token']
|
|
45
|
+
self.headers['X-Auth-Token'] = self.token
|
|
46
|
+
|
|
47
|
+
def get_data(self, url, params):
|
|
48
|
+
send_data = json.dumps(params)
|
|
49
|
+
request = requests.post(url, data=send_data, headers=self.headers, verify=False)
|
|
50
|
+
response = request.json()
|
|
51
|
+
result = dict(response)
|
|
52
|
+
return result['return'][0]
|
|
53
|
+
|
|
54
|
+
def salt_command(self, tgt, method, arg=None):
|
|
55
|
+
"""远程执行命令,相当于salt 'client1' cmd.run 'free -m'"""
|
|
56
|
+
if arg:
|
|
57
|
+
params = {'client': 'local', 'fun': method, 'tgt': tgt, 'arg': arg}
|
|
58
|
+
else:
|
|
59
|
+
params = {'client': 'local', 'fun': method, 'tgt': tgt}
|
|
60
|
+
result = self.get_data(self.__url, params)
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
def salt_async_command(self, tgt, method, arg=None): # 异步执行salt命令,根据jid查看执行结果
|
|
64
|
+
|
|
65
|
+
"""远程异步执行命令"""
|
|
66
|
+
if arg:
|
|
67
|
+
params = {'client': 'local_async', 'fun': method, 'tgt': tgt, 'arg': arg}
|
|
68
|
+
else:
|
|
69
|
+
params = {'client': 'local_async', 'fun': method, 'tgt': tgt}
|
|
70
|
+
jid = self.get_data(self.__url, params).get('jid', None)
|
|
71
|
+
return jid
|
|
72
|
+
|
|
73
|
+
def look_jid(self, jid): # 根据异步执行命令返回的jid查看事件结果
|
|
74
|
+
params = {'client': 'runner', 'fun': 'jobs.lookup_jid', 'jid': jid}
|
|
75
|
+
result = self.get_data(self.__url, params)
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
def run(self, salt_client='*', salt_method='cmd.run_all', salt_params='w', timeout=1800):
|
|
79
|
+
try:
|
|
80
|
+
if not self.salt_command(salt_client, 'test.ping')[salt_client]:
|
|
81
|
+
return -98, 'test.ping error 98', ''
|
|
82
|
+
except Exception as e:
|
|
83
|
+
return -99, 'test.ping error 99', str(e)
|
|
84
|
+
|
|
85
|
+
t = 0
|
|
86
|
+
jid = self.salt_async_command(salt_client, salt_method, salt_params)
|
|
87
|
+
if not jid:
|
|
88
|
+
return -100, '连接失败', '连接失败或主机不存在'
|
|
89
|
+
|
|
90
|
+
while True:
|
|
91
|
+
time.sleep(5)
|
|
92
|
+
if t == timeout:
|
|
93
|
+
print('exec timeout!')
|
|
94
|
+
break
|
|
95
|
+
else:
|
|
96
|
+
t += 5
|
|
97
|
+
result = self.look_jid(jid)
|
|
98
|
+
for i in result.keys():
|
|
99
|
+
return result[i]['retcode'], result[i]['stdout'], result[i]['stderr']
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == '__main__':
|
|
103
|
+
pass
|
|
104
|
+
# salt1 = SaltApi()
|
|
105
|
+
# req = salt1.run('*', 'cmd.run_all', 'w')
|
|
106
|
+
# status, stdout, stderr = req[0], req[1], req[2]
|
|
107
|
+
# print(status, stdout, stderr)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Version : 0.0.1
|
|
5
|
+
Contact : 191715030@qq.com
|
|
6
|
+
Author : shenshuo
|
|
7
|
+
Date : 2023/3/2 18:23
|
|
8
|
+
Desc : 分页
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
from sqlalchemy import desc
|
|
13
|
+
from .model_utils import queryset_to_list
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Page(object):
|
|
17
|
+
|
|
18
|
+
def __init__(self, items, page, page_size, total):
|
|
19
|
+
self.items = items
|
|
20
|
+
self.previous_page = None
|
|
21
|
+
self.next_page = None
|
|
22
|
+
self.has_previous = page > 1
|
|
23
|
+
if self.has_previous: self.previous_page = page - 1
|
|
24
|
+
previous_items = (page - 1) * page_size
|
|
25
|
+
self.has_next = previous_items + len(items) < total
|
|
26
|
+
if self.has_next: self.next_page = page + 1
|
|
27
|
+
self.total = total
|
|
28
|
+
self.pages = int(math.ceil(total / float(page_size)))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# def paginate(query, order_by: str = None, **query_params):
|
|
32
|
+
# page = int(query_params.get('page', 1)) if 'page' in query_params else int(query_params.get('page_number', 1))
|
|
33
|
+
# page_size = int(query_params.get('limit')) if 'limit' in query_params else int(query_params.get('page_size', 10))
|
|
34
|
+
#
|
|
35
|
+
# if 'order_by' in query_params: order_by = query_params.get('order_by')
|
|
36
|
+
# items_not_to_list = query_params.get('items_not_to_list') # 如果不序列化要额外加参数,主要为了连表查询
|
|
37
|
+
#
|
|
38
|
+
# if page <= 0: raise AttributeError('page needs to be >= 1')
|
|
39
|
+
# if page_size <= 0: raise AttributeError('page_size needs to be >= 1')
|
|
40
|
+
# if order_by:
|
|
41
|
+
# items = query.order_by(order_by).all() if page_size >= 200 else query.order_by(order_by).limit(
|
|
42
|
+
# page_size).offset((page - 1) * page_size).all()
|
|
43
|
+
# else:
|
|
44
|
+
# items = query.all() if page_size >= 200 else query.limit(page_size).offset((page - 1) * page_size).all()
|
|
45
|
+
#
|
|
46
|
+
# total = query.order_by(order_by).count()
|
|
47
|
+
# if not items_not_to_list: items = queryset_to_list(items)
|
|
48
|
+
# return Page(items, page, page_size, total)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def paginate(query, order_by: str = None, **query_params):
|
|
52
|
+
page = int(query_params.get('page', 1)) if 'page' in query_params else int(query_params.get('page_number', 1))
|
|
53
|
+
page_size = int(query_params.get('limit')) if 'limit' in query_params else int(query_params.get('page_size', 10))
|
|
54
|
+
|
|
55
|
+
if 'order_by' in query_params: order_by = query_params.get('order_by') # 排序字段
|
|
56
|
+
order = query_params.get('order', 'ascend') # 正序 倒序 order descend ascend
|
|
57
|
+
items_not_to_list = query_params.get('items_not_to_list') # 如果不序列化要额外加参数,主要为了连表查询
|
|
58
|
+
|
|
59
|
+
if page <= 0: raise AttributeError('page needs to be >= 1')
|
|
60
|
+
if page_size <= 0: raise AttributeError('page_size needs to be >= 1')
|
|
61
|
+
|
|
62
|
+
if order_by and order != 'descend':
|
|
63
|
+
items = query.order_by(order_by).all() if page_size >= 200 else query.order_by(order_by).limit(
|
|
64
|
+
page_size).offset((page - 1) * page_size).all()
|
|
65
|
+
elif order_by and order == 'descend':
|
|
66
|
+
items = query.order_by(desc(order_by)).all() if page_size >= 200 else query.order_by(desc(order_by)).limit(
|
|
67
|
+
page_size).offset((page - 1) * page_size).all()
|
|
68
|
+
else:
|
|
69
|
+
items = query.all() if page_size >= 200 else query.limit(page_size).offset((page - 1) * page_size).all()
|
|
70
|
+
|
|
71
|
+
total = query.count()
|
|
72
|
+
if not items_not_to_list: items = queryset_to_list(items)
|
|
73
|
+
return Page(items, page, page_size, total)
|
websdk2/tools.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8-*-
|
|
3
|
+
"""
|
|
4
|
+
Author : ss
|
|
5
|
+
date : 2018年4月12日
|
|
6
|
+
role : 工具类
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import re
|
|
11
|
+
import time
|
|
12
|
+
import redis
|
|
13
|
+
import subprocess
|
|
14
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
15
|
+
from .consts import const
|
|
16
|
+
|
|
17
|
+
def singleton(class_):
|
|
18
|
+
instances = {}
|
|
19
|
+
|
|
20
|
+
def getinstance(*args, **kwargs):
|
|
21
|
+
if class_ not in instances:
|
|
22
|
+
instances[class_] = class_(*args, **kwargs)
|
|
23
|
+
return instances[class_]
|
|
24
|
+
|
|
25
|
+
return getinstance
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def bytes_to_unicode(input_bytes):
|
|
29
|
+
if sys.version_info.major >= 3:
|
|
30
|
+
return str(input_bytes, encoding='utf-8')
|
|
31
|
+
else:
|
|
32
|
+
return (input_bytes).decode('utf-8')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def convert(data):
|
|
36
|
+
if isinstance(data, bytes): return data.decode('utf8')
|
|
37
|
+
if isinstance(data, dict): return dict(map(convert, data.items()))
|
|
38
|
+
if isinstance(data, tuple): return map(convert, data)
|
|
39
|
+
return data
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def check_password(data):
|
|
43
|
+
return True if re.search("^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$", data) and len(data) >= 8 else False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_mail(text, login_mail=None):
|
|
47
|
+
if login_mail:
|
|
48
|
+
if re.match(r'[0-9a-zA-Z_]{0,19}@%s' % login_mail, text):
|
|
49
|
+
return True
|
|
50
|
+
else:
|
|
51
|
+
return False
|
|
52
|
+
p = re.compile(r"[^@]+@[^@]+\.[^@]+")
|
|
53
|
+
# if re.match(r'^[0-9a-zA-Z_]{0,19}@[0-9a-zA-Z]{1,13}\.[com,cn,net]{1,3}$', text):
|
|
54
|
+
if p.match(text):
|
|
55
|
+
return True
|
|
56
|
+
else:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def is_tel(tel):
|
|
61
|
+
### 检查是否是手机号
|
|
62
|
+
ret = re.match(r"^1[35678]\d{9}$", tel)
|
|
63
|
+
if ret:
|
|
64
|
+
return True
|
|
65
|
+
else:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def check_contain_chinese(check_str):
|
|
70
|
+
### 检查是否包含汉字
|
|
71
|
+
"""
|
|
72
|
+
:param check_str:
|
|
73
|
+
:return:
|
|
74
|
+
"""
|
|
75
|
+
for ch in check_str:
|
|
76
|
+
if u'\u4e00' <= ch <= u'\u9fff':
|
|
77
|
+
return True
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Executor(ThreadPoolExecutor):
|
|
82
|
+
""" 线程执行类 """
|
|
83
|
+
_instance = None
|
|
84
|
+
|
|
85
|
+
def __new__(cls, *args, **kwargs):
|
|
86
|
+
if not getattr(cls, '_instance', None):
|
|
87
|
+
cls._instance = ThreadPoolExecutor(max_workers=10)
|
|
88
|
+
return cls._instance
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def exec_shell(cmd):
|
|
92
|
+
'''执行shell命令函数'''
|
|
93
|
+
sub = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
94
|
+
stdout, stderr = sub.communicate()
|
|
95
|
+
ret = sub.returncode
|
|
96
|
+
if ret == 0:
|
|
97
|
+
return ret, stdout.decode('utf-8').split('\n')
|
|
98
|
+
else:
|
|
99
|
+
return ret, stdout.decode('utf-8').replace('\n', '')
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RunningProcess:
|
|
103
|
+
def __init__(self, process):
|
|
104
|
+
self.process = process
|
|
105
|
+
self.start_time = time.time()
|
|
106
|
+
|
|
107
|
+
def is_running(self):
|
|
108
|
+
return bool(self.process.poll() is None)
|
|
109
|
+
|
|
110
|
+
def read_line(self):
|
|
111
|
+
return self.process.stdout.readline()
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def unread_lines(self):
|
|
115
|
+
lines = self.process.stdout.readlines()
|
|
116
|
+
self.process.stdout.close()
|
|
117
|
+
return lines
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def run_state(self):
|
|
121
|
+
return bool(self.process.poll() == 0)
|
|
122
|
+
|
|
123
|
+
def is_timeout(self, exec_time=600):
|
|
124
|
+
duration = time.time() - self.start_time
|
|
125
|
+
if duration > exec_time:
|
|
126
|
+
self.process.terminate()
|
|
127
|
+
self.process.wait()
|
|
128
|
+
self.process.communicate()
|
|
129
|
+
# print("execute timeout, execute time {}, it's killed.".format(duration))
|
|
130
|
+
return True
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class RedisLock(object):
|
|
135
|
+
def __init__(self, key, **conf):
|
|
136
|
+
if not conf:
|
|
137
|
+
from .configs import configs
|
|
138
|
+
__redis_info = configs.get(const.REDIS_CONFIG_ITEM, None).get(const.DEFAULT_RD_KEY, None)
|
|
139
|
+
conf = dict(host=__redis_info.get(const.RD_HOST_KEY), port=__redis_info.get(const.RD_PORT_KEY, 6379),
|
|
140
|
+
db=__redis_info.get(const.RD_DB_KEY, 0), password=__redis_info.get(const.RD_PASSWORD_KEY, None))
|
|
141
|
+
|
|
142
|
+
self.rdcon = redis.Redis(host=conf.get('host'), port=conf.get('port'), password=conf.get('password'),
|
|
143
|
+
db=conf.get('db', 0))
|
|
144
|
+
self._lock = 0
|
|
145
|
+
self.lock_key = "{}_dynamic_test".format(key)
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def get_lock(cls, key_timeout=59, func_timeout=59):
|
|
149
|
+
### key过期时间为一分钟,30秒内key任务没有完成则返回失败
|
|
150
|
+
start_time = time.time()
|
|
151
|
+
while cls._lock != 1:
|
|
152
|
+
timestamp = time.time() + key_timeout + 1
|
|
153
|
+
cls._lock = cls.rdcon.setnx(cls.lock_key, timestamp)
|
|
154
|
+
lock_key = cls.rdcon.get(cls.lock_key)
|
|
155
|
+
|
|
156
|
+
if time.time() - start_time > func_timeout:
|
|
157
|
+
return False
|
|
158
|
+
if cls._lock == 1 or (
|
|
159
|
+
time.time() > float(lock_key) and time.time() > float(cls.rdcon.getset(cls.lock_key, timestamp))):
|
|
160
|
+
return True
|
|
161
|
+
else:
|
|
162
|
+
time.sleep(1)
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def release(cls):
|
|
166
|
+
### 释放lock
|
|
167
|
+
lock_key = cls.rdcon.get(cls.lock_key)
|
|
168
|
+
if lock_key and time.time() < float(lock_key): cls.rdcon.delete(cls.lock_key)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def deco(cls, release=False):
|
|
172
|
+
""" 示例
|
|
173
|
+
@deco(RedisLock("redis_lock_key", **dict(host='127.0.0.1', port=6379, password="", db=1)))
|
|
174
|
+
def do_func():
|
|
175
|
+
print("the func called.")
|
|
176
|
+
time.sleep(50)
|
|
177
|
+
print("the func end")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
do_func()
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
def _deco(func):
|
|
184
|
+
def __deco(*args, **kwargs):
|
|
185
|
+
if not cls.get_lock(cls): return False
|
|
186
|
+
try:
|
|
187
|
+
return func(*args, **kwargs)
|
|
188
|
+
finally:
|
|
189
|
+
### 执行完就释放key,默认不释放
|
|
190
|
+
if release: cls.release(cls)
|
|
191
|
+
|
|
192
|
+
return __deco
|
|
193
|
+
|
|
194
|
+
return _deco
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def now_timestamp() -> int:
|
|
198
|
+
return int(round(time.time() * 1000))
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Contact : 191715030@qq.com
|
|
5
|
+
Author : shenshuo
|
|
6
|
+
Date : 2018/12/11
|
|
7
|
+
Desc :
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
import json
|
|
13
|
+
import socket
|
|
14
|
+
import smtplib
|
|
15
|
+
from ..consts import const
|
|
16
|
+
from email.mime.text import MIMEText
|
|
17
|
+
from email.mime.multipart import MIMEMultipart
|
|
18
|
+
import uuid
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SendMail(object):
|
|
22
|
+
def __init__(self, mail_host, mail_port, mail_user, mail_password, mail_ssl=False, mail_tls=False):
|
|
23
|
+
"""
|
|
24
|
+
:param mail_host: SMTP主机
|
|
25
|
+
:param mail_port: SMTP端口
|
|
26
|
+
:param mail_user: SMTP账号
|
|
27
|
+
:param mail_password: SMTP密码
|
|
28
|
+
:param mail_ssl: SSL=True, 如果SMTP端口是465,通常需要启用SSL, 如果SMTP端口是587,通常需要启用TLS
|
|
29
|
+
"""
|
|
30
|
+
self.mail_host = mail_host
|
|
31
|
+
self.mail_port = mail_port
|
|
32
|
+
self.__mail_user = mail_user
|
|
33
|
+
self.__mail_password = mail_password
|
|
34
|
+
self.mail_ssl = mail_ssl
|
|
35
|
+
self.mail_tls = mail_tls
|
|
36
|
+
|
|
37
|
+
def send_mail(self, to_list, subject, content, subtype='plain', att=None):
|
|
38
|
+
"""
|
|
39
|
+
:param to_list: 收件人,多收件人半角逗号分割, 必填
|
|
40
|
+
:param subject: 标题, 必填
|
|
41
|
+
:param content: 内容, 必填
|
|
42
|
+
:param subtype: 格式,默认:plain, 可选html
|
|
43
|
+
:param att: 附件,支持单附件,选填
|
|
44
|
+
"""
|
|
45
|
+
msg = MIMEMultipart()
|
|
46
|
+
msg['Subject'] = subject ## 标题
|
|
47
|
+
msg['From'] = self.__mail_user ## 发件人
|
|
48
|
+
msg['To'] = to_list # 收件人,必须是一个字符串
|
|
49
|
+
# 邮件正文内容
|
|
50
|
+
msg.attach(MIMEText(content, subtype, 'utf-8'))
|
|
51
|
+
if att:
|
|
52
|
+
if not os.path.isfile(att):
|
|
53
|
+
raise FileNotFoundError('{0} file does not exist'.format(att))
|
|
54
|
+
|
|
55
|
+
dirname, filename = os.path.split(att)
|
|
56
|
+
# 构造附件1,传送当前目录下的 test.txt 文件
|
|
57
|
+
att1 = MIMEText(open(att, 'rb').read(), 'base64', 'utf-8')
|
|
58
|
+
att1["Content-Type"] = 'application/octet-stream'
|
|
59
|
+
# 这里的filename可以任意写,写什么名字,邮件中显示什么名字
|
|
60
|
+
att1["Content-Disposition"] = 'attachment; filename="{0}"'.format(filename)
|
|
61
|
+
msg.attach(att1)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
if self.mail_ssl:
|
|
65
|
+
'''SSL加密方式,通信过程加密,邮件数据安全, 使用端口465'''
|
|
66
|
+
# print('Use SSL SendMail')
|
|
67
|
+
server = smtplib.SMTP_SSL(host=self.mail_host)
|
|
68
|
+
server.connect(self.mail_host, self.mail_port) # 连接服务器
|
|
69
|
+
server.login(self.__mail_user, self.__mail_password) # 登录操作
|
|
70
|
+
server.sendmail(self.__mail_user, to_list.split(','), msg.as_string())
|
|
71
|
+
server.close()
|
|
72
|
+
elif self.mail_tls:
|
|
73
|
+
# print('Use TLS SendMail')
|
|
74
|
+
'''使用TLS模式'''
|
|
75
|
+
server = smtplib.SMTP()
|
|
76
|
+
server.connect(self.mail_host, self.mail_port) # 连接服务器
|
|
77
|
+
server.starttls()
|
|
78
|
+
server.login(self.__mail_user, self.__mail_password) # 登录操作
|
|
79
|
+
server.sendmail(self.__mail_user, to_list.split(','), msg.as_string())
|
|
80
|
+
server.close()
|
|
81
|
+
return True
|
|
82
|
+
else:
|
|
83
|
+
'''使用普通模式'''
|
|
84
|
+
server = smtplib.SMTP()
|
|
85
|
+
server.connect(self.mail_host, self.mail_port) # 连接服务器
|
|
86
|
+
server.login(self.__mail_user, self.__mail_password) # 登录操作
|
|
87
|
+
server.sendmail(self.__mail_user, to_list.split(','), msg.as_string())
|
|
88
|
+
server.close()
|
|
89
|
+
return True
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(str(e))
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def mail_login(user, password, mail_server='smtp.exmail.qq.com'):
|
|
97
|
+
### 模拟登录来验证邮箱
|
|
98
|
+
try:
|
|
99
|
+
server = smtplib.SMTP()
|
|
100
|
+
server.connect(mail_server)
|
|
101
|
+
server.login(user, password)
|
|
102
|
+
return True
|
|
103
|
+
except Exception as e:
|
|
104
|
+
print(user, e)
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# def get_contain_dict(src_data: dict, dst_data: dict) -> bool:
|
|
109
|
+
# if not isinstance(src_data, dict):
|
|
110
|
+
# try:
|
|
111
|
+
# src_data = json.loads(src_data)
|
|
112
|
+
# except Exception as err:
|
|
113
|
+
# return False
|
|
114
|
+
#
|
|
115
|
+
# if not isinstance(dst_data, dict):
|
|
116
|
+
# try:
|
|
117
|
+
# dst_data = json.loads(dst_data)
|
|
118
|
+
# except Exception as err:
|
|
119
|
+
# return False
|
|
120
|
+
#
|
|
121
|
+
# # src_key = list(src_data.keys())
|
|
122
|
+
# # dst_key = list(dst_data.keys())
|
|
123
|
+
# pd = [False for c in src_data.keys() if c not in dst_data]
|
|
124
|
+
# if pd:
|
|
125
|
+
# return False
|
|
126
|
+
# else:
|
|
127
|
+
# src_val = list(src_data.values())
|
|
128
|
+
# dst_val = list(dst_data.values())
|
|
129
|
+
# pds = [False for c in src_val if c not in dst_val]
|
|
130
|
+
# if pds:
|
|
131
|
+
# return False
|
|
132
|
+
# else:
|
|
133
|
+
# return True
|
|
134
|
+
|
|
135
|
+
def get_contain_dict(src_data: dict, dst_data: dict) -> bool:
|
|
136
|
+
if not isinstance(src_data, dict):
|
|
137
|
+
try:
|
|
138
|
+
src_data = json.loads(src_data)
|
|
139
|
+
except Exception as err:
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
if not isinstance(dst_data, dict):
|
|
143
|
+
try:
|
|
144
|
+
dst_data = json.loads(dst_data)
|
|
145
|
+
except Exception as err:
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
### 判断键是否存在
|
|
149
|
+
pd = [False for c in src_data.keys() if c not in dst_data]
|
|
150
|
+
if pd:
|
|
151
|
+
return False
|
|
152
|
+
else:
|
|
153
|
+
src_val = list(src_data.values())
|
|
154
|
+
dst_val = list(dst_data.values())
|
|
155
|
+
pds = [False for c in src_val if c not in dst_val]
|
|
156
|
+
if pds:
|
|
157
|
+
try:
|
|
158
|
+
for d in dst_val:
|
|
159
|
+
if isinstance(d, dict) and src_val and isinstance(src_val[0], dict):
|
|
160
|
+
pds1 = [True for sv in src_val[0].keys() if sv in d and d.get(sv) == src_val[0].get(sv)]
|
|
161
|
+
if True in pds1: return True
|
|
162
|
+
except:
|
|
163
|
+
return False
|
|
164
|
+
return False
|
|
165
|
+
else:
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
def now_time_stamp() -> int:
|
|
169
|
+
"""
|
|
170
|
+
秒时间戳
|
|
171
|
+
:return: int
|
|
172
|
+
"""
|
|
173
|
+
return int(time.time())
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
### 这个地址具有唯一性
|
|
177
|
+
def get_node_address():
|
|
178
|
+
node_name = os.getenv(const.NODE_ADDRESS) if os.getenv(const.NODE_ADDRESS) else socket.gethostname()
|
|
179
|
+
mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
|
|
180
|
+
return f'{node_name}--mac-{mac}'
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
### 这个地址是默认可以通配的
|
|
184
|
+
def get_node_topic(node=False):
|
|
185
|
+
if not node:
|
|
186
|
+
if os.getenv(const.NODE_ADDRESS): return os.getenv(const.NODE_ADDRESS) + '#'
|
|
187
|
+
mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
|
|
188
|
+
return f'{socket.gethostname()}--mac-{mac}#'
|
|
189
|
+
else:
|
|
190
|
+
if os.getenv(const.NODE_ADDRESS): return os.getenv(const.NODE_ADDRESS)
|
|
191
|
+
mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
|
|
192
|
+
return f'{socket.gethostname()}--mac-{mac}'
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
### 令牌桶限流
|
|
196
|
+
'''
|
|
197
|
+
示例
|
|
198
|
+
import time
|
|
199
|
+
from settings import settings
|
|
200
|
+
from websdk2.configs import configs
|
|
201
|
+
from websdk2.cache_context import cache_conn
|
|
202
|
+
|
|
203
|
+
if configs.can_import: configs.import_dict(**settings)
|
|
204
|
+
|
|
205
|
+
redis_conn = cache_conn()
|
|
206
|
+
obj = TokenBucket(redis_conn, 'ss', 5, 60)
|
|
207
|
+
for i in range(120):
|
|
208
|
+
time.sleep(0.5)
|
|
209
|
+
status = obj.can_access('tuanzi')
|
|
210
|
+
print(status)
|
|
211
|
+
'''
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class TokenBucket:
|
|
215
|
+
"""令牌桶限流"""
|
|
216
|
+
|
|
217
|
+
# bucket_key 用来标记令牌
|
|
218
|
+
# func_name 第二段标记
|
|
219
|
+
# capacity = 5 # 桶容量
|
|
220
|
+
# rate = 1 # 速率 每分增加一个令牌
|
|
221
|
+
|
|
222
|
+
def __init__(self, cache, bucket_key, capacity: int = 5, rate: int = 1):
|
|
223
|
+
self.bucket_key = bucket_key # 用来标记令牌桶
|
|
224
|
+
self.capacity = capacity # 桶容量
|
|
225
|
+
self.rate = rate # 速率 每分钟增加的令牌
|
|
226
|
+
self.cache = cache
|
|
227
|
+
if not isinstance(rate, int): raise Exception('Rate must be int')
|
|
228
|
+
|
|
229
|
+
def can_access(self, func_name) -> bool:
|
|
230
|
+
"""令牌桶限流"""
|
|
231
|
+
redis_key = self.bucket_key + func_name
|
|
232
|
+
|
|
233
|
+
now = int(time.time())
|
|
234
|
+
current_tokens = self.cache.hget(redis_key, 'current_tokens')
|
|
235
|
+
last_time = self.cache.hget(redis_key, 'last_time')
|
|
236
|
+
|
|
237
|
+
current_tokens = current_tokens if current_tokens else self.capacity
|
|
238
|
+
try:
|
|
239
|
+
current_tokens = int(current_tokens)
|
|
240
|
+
except:
|
|
241
|
+
current_tokens = float(current_tokens)
|
|
242
|
+
self.capacity = float(self.capacity)
|
|
243
|
+
|
|
244
|
+
last_time = int(last_time) if last_time else now
|
|
245
|
+
|
|
246
|
+
increase_tokens = (now - last_time) * self.rate / 60 # 增加的令牌桶 按分钟计算
|
|
247
|
+
current_tokens = min(self.capacity, current_tokens + increase_tokens)
|
|
248
|
+
if current_tokens > 0:
|
|
249
|
+
self.cache.hset(redis_key, 'current_tokens', current_tokens - 1)
|
|
250
|
+
self.cache.hset(redis_key, 'last_time', int(time.time()))
|
|
251
|
+
return True
|
|
252
|
+
else:
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
if __name__ == '__main__':
|
|
257
|
+
pass
|