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.
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