gomyck-tools 1.0.0__py3-none-any.whl → 1.4.7__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.
- ctools/__init__.py +21 -0
- ctools/ai/__init__.py +4 -0
- ctools/ai/llm_chat.py +184 -0
- ctools/ai/llm_client.py +163 -0
- ctools/ai/llm_exception.py +17 -0
- ctools/ai/mcp/__init__.py +4 -0
- ctools/ai/mcp/mcp_client.py +326 -0
- ctools/ai/tools/__init__.py +4 -0
- ctools/ai/tools/json_extract.py +202 -0
- ctools/ai/tools/quick_tools.py +149 -0
- ctools/ai/tools/think_process.py +11 -0
- ctools/ai/tools/tool_use_xml_parse.py +40 -0
- ctools/ai/tools/xml_extract.py +15 -0
- ctools/application.py +50 -47
- ctools/aspect.py +65 -0
- ctools/auto/__init__.py +4 -0
- ctools/{browser_element_tools.py → auto/browser_element.py} +18 -8
- ctools/{plan_area_tools.py → auto/plan_area.py} +5 -7
- ctools/{pty_tools.py → auto/pty_process.py} +6 -3
- ctools/{resource_bundle_tools.py → auto/resource_bundle.py} +4 -4
- ctools/{screenshot_tools.py → auto/screenshot.py} +7 -6
- ctools/{win_canvas.py → auto/win_canvas.py} +10 -4
- ctools/{win_control.py → auto/win_control.py} +14 -5
- ctools/call.py +34 -49
- ctools/cdate.py +84 -0
- ctools/cdebug.py +146 -0
- ctools/cid.py +20 -0
- ctools/cipher/__init__.py +4 -0
- ctools/{aes_tools.py → cipher/aes_util.py} +10 -0
- ctools/{b64.py → cipher/b64.py} +2 -0
- ctools/cipher/czip.py +133 -0
- ctools/cipher/rsa.py +75 -0
- ctools/{sign.py → cipher/sign.py} +2 -1
- ctools/{sm_tools.py → cipher/sm_util.py} +20 -4
- ctools/cjson.py +10 -10
- ctools/cron_lite.py +109 -97
- ctools/database/__init__.py +4 -0
- ctools/{database.py → database/database.py} +93 -26
- ctools/dict_wrapper.py +21 -0
- ctools/ex.py +4 -0
- ctools/geo/__init__.py +4 -0
- ctools/geo/coord_trans.py +127 -0
- ctools/geo/douglas_rarefy.py +139 -0
- ctools/metrics.py +56 -63
- ctools/office/__init__.py +4 -0
- ctools/office/cword.py +30 -0
- ctools/{word_fill.py → office/word_fill.py} +3 -6
- ctools/patch.py +88 -0
- ctools/{work_path.py → path_info.py} +34 -2
- ctools/pkg/__init__.py +4 -0
- ctools/pkg/dynamic_imp.py +38 -0
- ctools/pools/__init__.py +4 -0
- ctools/pools/process_pool.py +41 -0
- ctools/{thread_pool.py → pools/thread_pool.py} +13 -4
- ctools/similar.py +25 -0
- ctools/stream/__init__.py +4 -0
- ctools/stream/ckafka.py +164 -0
- ctools/stream/credis.py +127 -0
- ctools/{mqtt_utils.py → stream/mqtt_utils.py} +20 -12
- ctools/sys_info.py +36 -13
- ctools/sys_log.py +46 -27
- ctools/util/__init__.py +4 -0
- ctools/util/cftp.py +76 -0
- ctools/util/cklock.py +118 -0
- ctools/util/config_util.py +52 -0
- ctools/util/env_config.py +63 -0
- ctools/{html_soup.py → util/html_soup.py} +0 -7
- ctools/{http_utils.py → util/http_util.py} +4 -2
- ctools/{images_tools.py → util/image_process.py} +10 -1
- ctools/util/jb_cut.py +54 -0
- ctools/{id_worker_tools.py → util/snow_id.py} +8 -23
- ctools/web/__init__.py +4 -0
- ctools/web/aio_web_server.py +186 -0
- ctools/web/api_result.py +56 -0
- ctools/web/bottle_web_base.py +239 -0
- ctools/web/bottle_webserver.py +191 -0
- ctools/web/bottle_websocket.py +79 -0
- ctools/web/ctoken.py +103 -0
- ctools/{download_tools.py → web/download_util.py} +14 -12
- ctools/web/params_util.py +46 -0
- ctools/{upload_tools.py → web/upload_util.py} +3 -2
- gomyck_tools-1.4.7.dist-info/METADATA +70 -0
- gomyck_tools-1.4.7.dist-info/RECORD +88 -0
- {gomyck_tools-1.0.0.dist-info → gomyck_tools-1.4.7.dist-info}/WHEEL +1 -1
- gomyck_tools-1.4.7.dist-info/licenses/LICENSE +13 -0
- ctools/bashPath.py +0 -13
- ctools/bottle_server.py +0 -49
- ctools/console.py +0 -55
- ctools/date_utils.py +0 -44
- ctools/enums.py +0 -4
- ctools/excelOpt.py +0 -36
- ctools/imgDialog.py +0 -44
- ctools/license.py +0 -37
- ctools/log.py +0 -28
- ctools/mvc.py +0 -56
- ctools/obj.py +0 -20
- ctools/pacth.py +0 -73
- ctools/ssh.py +0 -9
- ctools/strDiff.py +0 -20
- ctools/string_tools.py +0 -101
- ctools/token_tools.py +0 -13
- ctools/wordFill.py +0 -24
- gomyck_tools-1.0.0.dist-info/METADATA +0 -20
- gomyck_tools-1.0.0.dist-info/RECORD +0 -54
- /ctools/{word_fill_entity.py → office/word_fill_entity.py} +0 -0
- /ctools/{compile_tools.py → util/compile_util.py} +0 -0
- {gomyck_tools-1.0.0.dist-info → gomyck_tools-1.4.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/7/15 11:03'
|
|
5
|
+
|
|
6
|
+
import importlib
|
|
7
|
+
import pkgutil
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_modules_from_package(package, exclude=None, recursive=True):
|
|
11
|
+
"""
|
|
12
|
+
递归加载指定包下所有模块(不包括包本身)
|
|
13
|
+
|
|
14
|
+
:param package: 要加载模块的包对象(如 mypkg.plugins)
|
|
15
|
+
:param exclude: 排除的模块完整路径列表(如 ['mypkg.plugins.demo.mod2'])
|
|
16
|
+
:param recursive: 是否递归子包
|
|
17
|
+
:return: 模块列表(不含子包本身,只包含模块)
|
|
18
|
+
"""
|
|
19
|
+
if exclude is None: exclude = []
|
|
20
|
+
modules = []
|
|
21
|
+
for finder, modname, ispkg in pkgutil.iter_modules(package.__path__):
|
|
22
|
+
full_modname = f"{package.__name__}.{modname}"
|
|
23
|
+
if ispkg and recursive:
|
|
24
|
+
try:
|
|
25
|
+
subpkg = importlib.import_module(full_modname)
|
|
26
|
+
modules.extend(load_modules_from_package(subpkg, exclude, recursive))
|
|
27
|
+
except Exception as e:
|
|
28
|
+
print(f"递归子包 {full_modname} 失败:{e}")
|
|
29
|
+
continue
|
|
30
|
+
if full_modname in exclude:
|
|
31
|
+
continue
|
|
32
|
+
try:
|
|
33
|
+
module = importlib.import_module(full_modname)
|
|
34
|
+
modules.append(module)
|
|
35
|
+
except Exception as e:
|
|
36
|
+
print(f"!!!!!!加载模块 {full_modname} 失败:{e}!!!!!!")
|
|
37
|
+
continue
|
|
38
|
+
return modules
|
ctools/pools/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2024/9/20 12:00'
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
9
|
+
|
|
10
|
+
from ctools import call
|
|
11
|
+
|
|
12
|
+
_process_pool: ProcessPoolExecutor = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@call.init
|
|
16
|
+
def init():
|
|
17
|
+
global _process_pool
|
|
18
|
+
max_workers = min(32, os.cpu_count()) # 最多 32 个
|
|
19
|
+
_process_pool = ProcessPoolExecutor(max_workers=max_workers)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def cb(f, callback):
|
|
23
|
+
exc = f.exception()
|
|
24
|
+
if exc:
|
|
25
|
+
if callback: callback(exc)
|
|
26
|
+
raise exc
|
|
27
|
+
else:
|
|
28
|
+
if callback: callback(f.result())
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def submit(func, *args, callback=None, **kwargs):
|
|
32
|
+
if _process_pool is None: raise Exception('process pool is not init')
|
|
33
|
+
future = _process_pool.submit(func, *args, **kwargs)
|
|
34
|
+
future.add_done_callback(lambda f: cb(f, callback))
|
|
35
|
+
time.sleep(0.01)
|
|
36
|
+
return future
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def shutdown(wait=True):
|
|
40
|
+
if _process_pool is None: raise Exception('process pool is not init')
|
|
41
|
+
_process_pool.shutdown(wait=wait)
|
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import threading
|
|
2
3
|
import time
|
|
3
4
|
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
-
|
|
5
|
+
|
|
5
6
|
from ctools import call
|
|
6
7
|
|
|
7
8
|
thread_local = threading.local()
|
|
8
9
|
|
|
9
10
|
_threadPool: ThreadPoolExecutor = None
|
|
10
11
|
|
|
12
|
+
|
|
11
13
|
@call.init
|
|
12
14
|
def init():
|
|
13
15
|
global _threadPool
|
|
14
|
-
max_work_num = min(32, (os.cpu_count() or 1) + 4)
|
|
15
|
-
_threadPool = ThreadPoolExecutor(max_workers=
|
|
16
|
+
max_work_num = min(32, (os.cpu_count() or 1) + 4) # 最多 32 个
|
|
17
|
+
_threadPool = ThreadPoolExecutor(max_workers=max_work_num, thread_name_prefix='ck-')
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
def cb(f, callback):
|
|
18
21
|
exc = f.exception()
|
|
19
22
|
if exc:
|
|
23
|
+
print(f"Task failed: {exc}")
|
|
20
24
|
if callback: callback(exc)
|
|
21
|
-
raise exc
|
|
22
25
|
else:
|
|
23
26
|
if callback: callback(f.result())
|
|
24
27
|
|
|
28
|
+
|
|
25
29
|
def submit(func, *args, callback=None, **kwargs):
|
|
26
30
|
if _threadPool is None: raise Exception('thread pool is not init')
|
|
27
31
|
future = _threadPool.submit(func, *args, **kwargs)
|
|
28
32
|
future.add_done_callback(lambda f: cb(f, callback))
|
|
29
33
|
time.sleep(0.01)
|
|
30
34
|
return future
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def shutdown(wait=True):
|
|
38
|
+
if _threadPool is None: raise Exception('thread pool is not init')
|
|
39
|
+
_threadPool.shutdown(wait=wait)
|
ctools/similar.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from fuzzywuzzy import fuzz, process
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def best_match(query: str, choices: list[str], score_cutoff: int = 70):
|
|
5
|
+
"""
|
|
6
|
+
获取最接近 query 的匹配项
|
|
7
|
+
:return: (匹配项, 相似度得分) 或 None
|
|
8
|
+
"""
|
|
9
|
+
return process.extractOne(query, choices, scorer=fuzz.ratio, score_cutoff=score_cutoff)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def all_matches(query: str, choices: list[str], limit: int = 5):
|
|
13
|
+
"""
|
|
14
|
+
获取多个相似匹配项
|
|
15
|
+
:return: [(匹配项, 相似度得分), ...]
|
|
16
|
+
"""
|
|
17
|
+
return process.extract(query, choices, scorer=fuzz.ratio, limit=limit)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_similar(s1: str, s2: str, threshold: int = 85):
|
|
21
|
+
"""
|
|
22
|
+
判断两个字符串是否相似
|
|
23
|
+
:return: True / False
|
|
24
|
+
"""
|
|
25
|
+
return fuzz.ratio(s1, s2) >= threshold
|
ctools/stream/ckafka.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2024/9/5 10:39'
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
from threading import Thread
|
|
8
|
+
|
|
9
|
+
from kafka import KafkaProducer, errors, KafkaConsumer
|
|
10
|
+
from kafka.producer.future import FutureRecordMetadata
|
|
11
|
+
|
|
12
|
+
from ctools.cjson import dumps
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
import time
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
|
|
18
|
+
from ctools import thread_pool, string_tools
|
|
19
|
+
from ctools.ckafka import CKafka
|
|
20
|
+
|
|
21
|
+
c = CKafka(kafka_url='192.168.3.160:9094', secure=True)
|
|
22
|
+
|
|
23
|
+
producer = c.init_producer()
|
|
24
|
+
consumer = c.init_consumer(enable_auto_commit=False)
|
|
25
|
+
|
|
26
|
+
def send_msg():
|
|
27
|
+
while True:
|
|
28
|
+
command = input('发送消息: Y/n \n')
|
|
29
|
+
if command.strip() not in ['N', 'n']:
|
|
30
|
+
producer.send_msg('jqxx', '{{"jqid": "{}", "xxxx": "{}"}}'.format(string_tools.get_snowflake_id(), datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')))
|
|
31
|
+
else:
|
|
32
|
+
break
|
|
33
|
+
|
|
34
|
+
thread_pool.submit(send_msg)
|
|
35
|
+
|
|
36
|
+
def consumer_callback(msg):
|
|
37
|
+
print(msg)
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
consumer.receive_msg('jqxx', callBack=consumer_callback)
|
|
41
|
+
|
|
42
|
+
while True: time.sleep(1)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class KafkaInstance:
|
|
47
|
+
def __init__(self, producer: KafkaProducer, consumer: KafkaConsumer):
|
|
48
|
+
self.start_consumer = False
|
|
49
|
+
self.quited = False
|
|
50
|
+
self.producer = producer
|
|
51
|
+
self.consumer = consumer
|
|
52
|
+
self.consumer_callback = {"topic_key": []}
|
|
53
|
+
|
|
54
|
+
# FutureRecordMetadata 可以添加回调, 来监听是否发送成功
|
|
55
|
+
# r.add_callback(lambda x: print(x))
|
|
56
|
+
# r.get() 可以同步获取结果
|
|
57
|
+
def send_msg(self, topic, msg, key: str = None, partition: int = None) -> FutureRecordMetadata:
|
|
58
|
+
if self.producer is None: raise RuntimeError("Producer is not initialized")
|
|
59
|
+
if self.quited: return
|
|
60
|
+
return self.producer.send(topic=topic, value=msg, key=None if key is None else key.encode('utf-8'), partition=partition)
|
|
61
|
+
|
|
62
|
+
def receive_msg(self, topics: str, callBack=print):
|
|
63
|
+
if self.consumer is None: raise RuntimeError("Consumer is not initialized")
|
|
64
|
+
for topic in topics.split(','):
|
|
65
|
+
if topic not in self.consumer_callback.keys():
|
|
66
|
+
self.consumer_callback[topic] = []
|
|
67
|
+
self.consumer.subscribe(self.consumer_callback.keys())
|
|
68
|
+
self.consumer_callback[topic].append(callBack)
|
|
69
|
+
if not self.start_consumer:
|
|
70
|
+
t = Thread(target=self._start_consumer_poll, daemon=True)
|
|
71
|
+
t.start()
|
|
72
|
+
|
|
73
|
+
def _start_consumer_poll(self):
|
|
74
|
+
self.start_consumer = True
|
|
75
|
+
for msg in self.consumer:
|
|
76
|
+
if self.quited: break
|
|
77
|
+
funcList = []
|
|
78
|
+
begin_time = time.time()
|
|
79
|
+
for func in self.consumer_callback[msg.topic]:
|
|
80
|
+
if self.quited: break
|
|
81
|
+
res = func(msg)
|
|
82
|
+
if not self.consumer.config['enable_auto_commit'] and res: self.consumer.commit()
|
|
83
|
+
funcList.append(func.__name__)
|
|
84
|
+
end_time = time.time()
|
|
85
|
+
if end_time - begin_time > 1: print(f"kafka consume too slow!!! {funcList} time cost: ", f'{round(end_time - begin_time, 2)}s')
|
|
86
|
+
funcList.clear()
|
|
87
|
+
|
|
88
|
+
def shutdown(self):
|
|
89
|
+
self.quited = True
|
|
90
|
+
try:
|
|
91
|
+
self.consumer.close()
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
try:
|
|
95
|
+
self.producer.close()
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class CKafka:
|
|
101
|
+
|
|
102
|
+
def __init__(self, kafka_url: str = '127.0.0.1:9092', secure: bool = False, username: str = 'client', password: str = 'hylink_user_password'):
|
|
103
|
+
self.kafka_url = kafka_url
|
|
104
|
+
self.secure = secure
|
|
105
|
+
self.username = username
|
|
106
|
+
self.password = password
|
|
107
|
+
|
|
108
|
+
def init_producer(self, acks=1) -> KafkaInstance:
|
|
109
|
+
print("[ Producer ] Connecting to Kafka [{}]".format(self.kafka_url))
|
|
110
|
+
for i in range(0, 6):
|
|
111
|
+
try:
|
|
112
|
+
if self.secure:
|
|
113
|
+
producer = KafkaProducer(
|
|
114
|
+
acks=acks,
|
|
115
|
+
bootstrap_servers=self.kafka_url,
|
|
116
|
+
value_serializer=lambda x: dumps(x).encode('utf-8'),
|
|
117
|
+
sasl_plain_username=self.username,
|
|
118
|
+
sasl_plain_password=self.password,
|
|
119
|
+
security_protocol='SASL_PLAINTEXT',
|
|
120
|
+
sasl_mechanism='PLAIN'
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
producer = KafkaProducer(
|
|
124
|
+
acks=acks,
|
|
125
|
+
bootstrap_servers=self.kafka_url,
|
|
126
|
+
value_serializer=lambda x: dumps(x).encode('utf-8')
|
|
127
|
+
)
|
|
128
|
+
print("[ Producer ] Success Connected to Kafka [{}]".format(self.kafka_url))
|
|
129
|
+
return KafkaInstance(producer=producer, consumer=None)
|
|
130
|
+
except errors.NoBrokersAvailable:
|
|
131
|
+
print("[ Producer ] Waiting for Kafka [{}] to become available...".format(self.kafka_url))
|
|
132
|
+
time.sleep(3)
|
|
133
|
+
raise RuntimeError("[ Producer ] Failed to connect to Kafka [{}] within 60 seconds".format(self.kafka_url))
|
|
134
|
+
|
|
135
|
+
def init_consumer(self, client_id: str = 'ck-py-kafka-consumer', consumer_group: str = 'ck-py-kafka-consumer', enable_auto_commit: bool = True) -> KafkaInstance:
|
|
136
|
+
print("[ Consumer ] Connecting to Kafka [{}]".format(self.kafka_url))
|
|
137
|
+
for i in range(0, 6):
|
|
138
|
+
try:
|
|
139
|
+
if self.secure:
|
|
140
|
+
consumer = KafkaConsumer(
|
|
141
|
+
client_id=client_id,
|
|
142
|
+
group_id=consumer_group,
|
|
143
|
+
enable_auto_commit=enable_auto_commit,
|
|
144
|
+
bootstrap_servers=self.kafka_url,
|
|
145
|
+
value_deserializer=lambda x: x.decode('utf-8'),
|
|
146
|
+
sasl_plain_username=self.username,
|
|
147
|
+
sasl_plain_password=self.password,
|
|
148
|
+
security_protocol='SASL_PLAINTEXT',
|
|
149
|
+
sasl_mechanism='PLAIN'
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
consumer = KafkaProducer(
|
|
153
|
+
client_id=client_id,
|
|
154
|
+
group_id=consumer_group,
|
|
155
|
+
enable_auto_commit=enable_auto_commit,
|
|
156
|
+
bootstrap_servers=self.kafka_url,
|
|
157
|
+
value_deserializer=lambda x: x.decode('utf-8')
|
|
158
|
+
)
|
|
159
|
+
print("[ Consumer ] Success Connected to Kafka [{}]".format(self.kafka_url))
|
|
160
|
+
return KafkaInstance(producer=None, consumer=consumer)
|
|
161
|
+
except errors.NoBrokersAvailable:
|
|
162
|
+
print("[ Consumer ] Waiting for Kafka [{}] to become available...".format(self.kafka_url))
|
|
163
|
+
time.sleep(3)
|
|
164
|
+
raise RuntimeError("[ Consumer ] Failed to connect to Kafka [{}] within 60 seconds".format(self.kafka_url))
|
ctools/stream/credis.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/2/14 11:09'
|
|
5
|
+
|
|
6
|
+
import redis
|
|
7
|
+
from redis import Redis
|
|
8
|
+
|
|
9
|
+
from ctools import cdate, cid
|
|
10
|
+
from ctools.pools import thread_pool
|
|
11
|
+
|
|
12
|
+
# 最后一次连接的redis
|
|
13
|
+
_ck_redis: Redis = None
|
|
14
|
+
|
|
15
|
+
def get_redis(): return _ck_redis
|
|
16
|
+
|
|
17
|
+
def init_pool(host: str = 'localhost', port: int = 6379, db: int = 0, password: str = None,
|
|
18
|
+
username: str = None, decode_responses: bool = True, max_connections: int = 75,
|
|
19
|
+
health_check_interval: int = 30, retry_count: int = 3) -> Redis:
|
|
20
|
+
for attempt in range(retry_count):
|
|
21
|
+
try:
|
|
22
|
+
r: Redis = redis.StrictRedis(
|
|
23
|
+
host=host, port=port, db=db,
|
|
24
|
+
username=username, password=password,
|
|
25
|
+
retry_on_timeout=True,
|
|
26
|
+
max_connections=max_connections,
|
|
27
|
+
decode_responses=decode_responses,
|
|
28
|
+
health_check_interval=health_check_interval,
|
|
29
|
+
socket_connect_timeout=5,
|
|
30
|
+
socket_timeout=5
|
|
31
|
+
)
|
|
32
|
+
if r.ping():
|
|
33
|
+
print('CRedis connect {} {} success!'.format(host, port))
|
|
34
|
+
global _ck_redis
|
|
35
|
+
_ck_redis = r
|
|
36
|
+
return _ck_redis
|
|
37
|
+
except redis.ConnectionError as e:
|
|
38
|
+
if attempt == retry_count - 1:
|
|
39
|
+
raise Exception(f"Failed to connect to Redis after {retry_count} attempts: {str(e)}")
|
|
40
|
+
print(f"Connection attempt {attempt + 1} failed, retrying...")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def add_lock(r: Redis, key: str, timeout: int = 30):
|
|
44
|
+
if r.exists(key):
|
|
45
|
+
expire_time = r.get(key)
|
|
46
|
+
if expire_time and cdate.time_diff_in_seconds(expire_time, cdate.get_date_time()) > 0:
|
|
47
|
+
return False
|
|
48
|
+
else:
|
|
49
|
+
r.delete(key)
|
|
50
|
+
return r.set(key, cdate.opt_time(seconds=timeout), nx=True, ex=timeout) is not None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def remove_lock(r: Redis, key: str):
|
|
54
|
+
r.delete(key)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def subscribe(r: Redis, channel_name, callback):
|
|
58
|
+
def thread_func():
|
|
59
|
+
pubsub = r.pubsub()
|
|
60
|
+
pubsub.subscribe(channel_name)
|
|
61
|
+
for message in pubsub.listen():
|
|
62
|
+
callback(message)
|
|
63
|
+
|
|
64
|
+
thread_pool.submit(thread_func)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _process_pending_messages(r: Redis, stream_name: str, group_name: str, consumer_name: str, callback):
|
|
68
|
+
"""
|
|
69
|
+
处理未确认的消息
|
|
70
|
+
:param r: Redis 连接
|
|
71
|
+
:param stream_name: 流名称
|
|
72
|
+
:param group_name: 消费者组名称
|
|
73
|
+
:param consumer_name: 消费者名称
|
|
74
|
+
:param callback: 消息处理回调函数
|
|
75
|
+
"""
|
|
76
|
+
# 检查未确认的消息
|
|
77
|
+
pending_messages = r.xpending(stream_name, group_name)
|
|
78
|
+
if pending_messages['pending'] > 0:
|
|
79
|
+
print(f"Found {pending_messages['pending']} pending messages.")
|
|
80
|
+
# 获取未确认的消息列表
|
|
81
|
+
pending_list = r.xpending_range(stream_name, group_name, min='-', max='+', count=pending_messages['pending'])
|
|
82
|
+
for message in pending_list:
|
|
83
|
+
message_id = message['message_id']
|
|
84
|
+
claimed_messages = r.xclaim(stream_name, group_name, consumer_name, min_idle_time=0, message_ids=[message_id])
|
|
85
|
+
if claimed_messages:
|
|
86
|
+
# 处理消息
|
|
87
|
+
for claimed_message in claimed_messages:
|
|
88
|
+
message_id, data = claimed_message
|
|
89
|
+
print(f"Processing pending message: {message_id}, data: {data}")
|
|
90
|
+
try:
|
|
91
|
+
if callback(message_id, data):
|
|
92
|
+
r.xack(stream_name, group_name, message_id)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f"Error processing message {message_id}: {e}")
|
|
95
|
+
else:
|
|
96
|
+
print("No pending messages found.")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def stream_subscribe(r: Redis, stream_name, group_name, callback, from_id: str = '$', noack: bool = False):
|
|
100
|
+
def thread_func():
|
|
101
|
+
try:
|
|
102
|
+
# $表示从最后面消费, 0表示从开始消费
|
|
103
|
+
r.xgroup_create(name=stream_name, groupname=group_name, id=from_id, mkstream=True)
|
|
104
|
+
print(f"Consumer group '{group_name}' created successfully.")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
if "already exists" in str(e):
|
|
107
|
+
print(f"Consumer group '{group_name}' already exists.")
|
|
108
|
+
else:
|
|
109
|
+
print(f"Error creating consumer group '{group_name}': {e}")
|
|
110
|
+
consumer_name = 'consumer-{}'.format(cid.get_uuid())
|
|
111
|
+
# 处理未确认的消息
|
|
112
|
+
_process_pending_messages(r, stream_name, group_name, consumer_name, callback)
|
|
113
|
+
while True:
|
|
114
|
+
messages = r.xreadgroup(group_name, consumer_name, {stream_name: '>'}, block=1000, noack=noack)
|
|
115
|
+
for message in messages:
|
|
116
|
+
try:
|
|
117
|
+
message_id, data = message[1][0]
|
|
118
|
+
res = callback(message_id, data)
|
|
119
|
+
if res: r.xack(stream_name, group_name, message_id)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
print('stream_subscribe error: ', e)
|
|
122
|
+
|
|
123
|
+
thread_pool.submit(thread_func)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def stream_publish(r: Redis, stream_name, message):
|
|
127
|
+
r.xadd(stream_name, message)
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import time
|
|
2
|
+
from enum import Enum
|
|
2
3
|
from typing import Dict
|
|
3
4
|
|
|
4
5
|
from paho.mqtt import client as mqtt
|
|
5
6
|
from paho.mqtt.enums import CallbackAPIVersion
|
|
6
7
|
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from ctools import
|
|
8
|
+
from ctools import cid, cdate
|
|
9
|
+
from ctools import sys_log, cjson, sys_info
|
|
10
|
+
from ctools.cipher import sm_util
|
|
11
|
+
from ctools.dict_wrapper import DictWrapper as DictToObj
|
|
12
|
+
from ctools.pools import thread_pool
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MQTTEvent(Enum):
|
|
16
|
+
pass
|
|
17
|
+
|
|
10
18
|
|
|
11
19
|
'''
|
|
12
20
|
MQTT服务使用示例:
|
|
@@ -37,9 +45,9 @@ class MQTTRequest:
|
|
|
37
45
|
|
|
38
46
|
def __init__(self, **kwargs):
|
|
39
47
|
self.event: str = kwargs.get('event')
|
|
40
|
-
self.trace_id: str = kwargs.get('trace_id') if kwargs.get('trace_id') else
|
|
48
|
+
self.trace_id: str = kwargs.get('trace_id') if kwargs.get('trace_id') else cid.get_uuid()
|
|
41
49
|
self.client_id: str = kwargs.get('client_id')
|
|
42
|
-
self.time: str = kwargs.get('time') if kwargs.get('time') else
|
|
50
|
+
self.time: str = kwargs.get('time') if kwargs.get('time') else cdate.get_date_time()
|
|
43
51
|
self.body = kwargs.get('body')
|
|
44
52
|
|
|
45
53
|
def __str__(self):
|
|
@@ -55,7 +63,7 @@ class MQTTResponse:
|
|
|
55
63
|
self.event: str = kwargs.get('event')
|
|
56
64
|
self.trace_id: str = kwargs.get('trace_id')
|
|
57
65
|
self.status: int = kwargs.get('status')
|
|
58
|
-
self.time: str = kwargs.get('time') if kwargs.get('time') else
|
|
66
|
+
self.time: str = kwargs.get('time') if kwargs.get('time') else cdate.get_date_time()
|
|
59
67
|
self.msg: str = kwargs.get('msg')
|
|
60
68
|
self.body = kwargs.get('body')
|
|
61
69
|
|
|
@@ -89,8 +97,8 @@ class MQTTUtils:
|
|
|
89
97
|
will_msg = {
|
|
90
98
|
"event": "offline",
|
|
91
99
|
"client_id": self.client_id,
|
|
92
|
-
"trace_id":
|
|
93
|
-
"time":
|
|
100
|
+
"trace_id": cid.get_uuid(),
|
|
101
|
+
"time": cdate.get_date_time(),
|
|
94
102
|
"body": {"type": "will_msg"}
|
|
95
103
|
}
|
|
96
104
|
self.client.will_set(self.publish_topic, cjson.dumps(will_msg), 2, False)
|
|
@@ -145,16 +153,16 @@ class MQTTUtils:
|
|
|
145
153
|
def encrypt_body(self, body):
|
|
146
154
|
if self.broker_encrypt_key:
|
|
147
155
|
try:
|
|
148
|
-
return
|
|
156
|
+
return sm_util.encrypt_with_sm4(self.broker_encrypt_key, cjson.dumps(body))
|
|
149
157
|
except Exception:
|
|
150
|
-
return
|
|
158
|
+
return sm_util.encrypt_with_sm4(self.broker_encrypt_key, body)
|
|
151
159
|
|
|
152
160
|
def decrypt_body(self, body):
|
|
153
161
|
if self.broker_encrypt_key:
|
|
154
162
|
try:
|
|
155
|
-
return cjson.loads(
|
|
163
|
+
return cjson.loads(sm_util.decrypt_with_sm4(self.broker_encrypt_key, body))
|
|
156
164
|
except Exception:
|
|
157
|
-
|
|
165
|
+
sm_util.decrypt_with_sm4(self.broker_encrypt_key, body)
|
|
158
166
|
|
|
159
167
|
def connect(self, username=None, password=None, timeout=10):
|
|
160
168
|
if username and password:
|
ctools/sys_info.py
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
import os
|
|
3
3
|
import platform
|
|
4
|
-
import socket
|
|
5
|
-
import uuid
|
|
6
4
|
|
|
7
|
-
import
|
|
8
|
-
from
|
|
9
|
-
import getpass
|
|
10
|
-
from ctools import cjson, sm_tools, work_path
|
|
5
|
+
from ctools import cjson, path_info
|
|
6
|
+
from ctools.cipher import sm_util
|
|
11
7
|
|
|
12
8
|
MACHINE_KEY = b'EnrGffoorbFyTYoS0902YyT1Fhehj4InpbezIDUuPOg='
|
|
13
9
|
|
|
@@ -15,12 +11,15 @@ MACHINE_KEY = b'EnrGffoorbFyTYoS0902YyT1Fhehj4InpbezIDUuPOg='
|
|
|
15
11
|
class MachineInfo:
|
|
16
12
|
machine_code = None
|
|
17
13
|
|
|
14
|
+
|
|
18
15
|
def get_user():
|
|
16
|
+
import getpass
|
|
19
17
|
return getpass.getuser()
|
|
20
18
|
|
|
19
|
+
|
|
21
20
|
def get_machine_code():
|
|
22
21
|
if MachineInfo.machine_code: return MachineInfo.machine_code
|
|
23
|
-
destPath = os.path.join(
|
|
22
|
+
destPath = os.path.join(path_info.get_user_work_path(), "AppData/Local/machine")
|
|
24
23
|
machine_file = os.path.join(destPath, 'machine_code.mc')
|
|
25
24
|
origin_machine_code = get_origin_machine_code()
|
|
26
25
|
if os.path.exists(machine_file):
|
|
@@ -28,7 +27,7 @@ def get_machine_code():
|
|
|
28
27
|
file_code = f.readline()
|
|
29
28
|
dec_code = ''
|
|
30
29
|
try:
|
|
31
|
-
dec_code = cjson.loads(
|
|
30
|
+
dec_code = cjson.loads(sm_util.decrypt_with_sm4(MACHINE_KEY, file_code))
|
|
32
31
|
origin_code = dec_code.get("origin_code")
|
|
33
32
|
if origin_code == origin_machine_code:
|
|
34
33
|
MachineInfo.machine_code = dec_code.get("hash_code")
|
|
@@ -38,7 +37,7 @@ def get_machine_code():
|
|
|
38
37
|
print('machine code file is error: {} {}'.format(file_code, dec_code))
|
|
39
38
|
hash_machine_code = get_hash_machine_code(origin_machine_code)
|
|
40
39
|
machine_code = {"origin_code": origin_machine_code, "hash_code": hash_machine_code}
|
|
41
|
-
enc_code =
|
|
40
|
+
enc_code = sm_util.encrypt_with_sm4(MACHINE_KEY, cjson.dumps(machine_code))
|
|
42
41
|
os.makedirs(destPath, exist_ok=True)
|
|
43
42
|
with open(machine_file, 'w') as f:
|
|
44
43
|
f.write(enc_code)
|
|
@@ -68,26 +67,49 @@ def get_origin_machine_code():
|
|
|
68
67
|
0
|
|
69
68
|
)
|
|
70
69
|
disk_serial = str(volume_serial.value)
|
|
71
|
-
combined_info = cpu_serial + disk_serial + '-
|
|
70
|
+
combined_info = cpu_serial + disk_serial + '-gomyck'
|
|
72
71
|
return hashlib.md5(combined_info.encode()).hexdigest()
|
|
73
72
|
|
|
74
73
|
|
|
75
74
|
def get_hash_machine_code(origin_code):
|
|
75
|
+
import uuid
|
|
76
76
|
code = origin_code + uuid.uuid1().hex
|
|
77
77
|
machine_code = hashlib.md5(code.encode()).hexdigest()
|
|
78
78
|
return machine_code.upper()
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
def get_public_ip():
|
|
82
|
+
import requests
|
|
83
|
+
try:
|
|
84
|
+
response = requests.get("https://api.ipify.org?format=json")
|
|
85
|
+
ip = response.json()["ip"]
|
|
86
|
+
return ip
|
|
87
|
+
except Exception as e:
|
|
88
|
+
return f"Failed to get public IP: {e}"
|
|
89
|
+
|
|
90
|
+
|
|
81
91
|
def get_local_ipv4():
|
|
82
|
-
|
|
92
|
+
import psutil
|
|
93
|
+
import socket
|
|
94
|
+
interfaces = psutil.net_if_addrs()
|
|
95
|
+
for interface, addresses in interfaces.items():
|
|
96
|
+
for address in addresses:
|
|
97
|
+
if address.family == socket.AF_INET and not address.address.startswith("127."):
|
|
98
|
+
return address.address
|
|
99
|
+
print("Failed to get local IPv4 address, try another way...")
|
|
100
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
83
101
|
try:
|
|
84
|
-
|
|
102
|
+
s.connect(("8.8.8.8", 80))
|
|
103
|
+
ip = s.getsockname()[0]
|
|
85
104
|
except Exception:
|
|
86
|
-
|
|
105
|
+
ip = '127.0.0.1'
|
|
106
|
+
finally:
|
|
107
|
+
s.close()
|
|
87
108
|
return ip
|
|
88
109
|
|
|
89
110
|
|
|
90
111
|
def get_remote_ipv4():
|
|
112
|
+
from bottle import request
|
|
91
113
|
try:
|
|
92
114
|
return request.remote_route[0]
|
|
93
115
|
except:
|
|
@@ -95,6 +117,7 @@ def get_remote_ipv4():
|
|
|
95
117
|
|
|
96
118
|
|
|
97
119
|
def get_proc_pid_by(cmdline):
|
|
120
|
+
import psutil
|
|
98
121
|
"""
|
|
99
122
|
根据命令行信息获取进程pid
|
|
100
123
|
:param cmdline:
|