gomyck-tools 1.3.1__py3-none-any.whl → 1.3.2__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 +0 -0
- ctools/aes_tools.py +35 -0
- ctools/api_result.py +55 -0
- ctools/application.py +386 -0
- ctools/b64.py +7 -0
- ctools/bashPath.py +13 -0
- ctools/bottle_web_base.py +169 -0
- ctools/bottle_webserver.py +143 -0
- ctools/bottle_websocket.py +75 -0
- ctools/browser_element_tools.py +314 -0
- ctools/call.py +71 -0
- ctools/cftp.py +74 -0
- ctools/cjson.py +54 -0
- ctools/ckafka.py +159 -0
- ctools/compile_tools.py +18 -0
- ctools/console.py +55 -0
- ctools/coord_trans.py +127 -0
- ctools/credis.py +111 -0
- ctools/cron_lite.py +252 -0
- ctools/ctoken.py +34 -0
- ctools/cword.py +30 -0
- ctools/czip.py +130 -0
- ctools/database.py +185 -0
- ctools/date_utils.py +43 -0
- ctools/dict_wrapper.py +20 -0
- ctools/douglas_rarefy.py +136 -0
- ctools/download_tools.py +57 -0
- ctools/enums.py +4 -0
- ctools/ex.py +31 -0
- ctools/excelOpt.py +36 -0
- ctools/html_soup.py +35 -0
- ctools/http_utils.py +24 -0
- ctools/images_tools.py +27 -0
- ctools/imgDialog.py +44 -0
- ctools/metrics.py +131 -0
- ctools/mqtt_utils.py +289 -0
- ctools/obj.py +20 -0
- ctools/pacth.py +74 -0
- ctools/plan_area_tools.py +97 -0
- ctools/process_pool.py +36 -0
- ctools/pty_tools.py +72 -0
- ctools/resource_bundle_tools.py +121 -0
- ctools/rsa.py +70 -0
- ctools/screenshot_tools.py +127 -0
- ctools/sign.py +20 -0
- ctools/sm_tools.py +49 -0
- ctools/snow_id.py +76 -0
- ctools/str_diff.py +20 -0
- ctools/string_tools.py +85 -0
- ctools/sys_info.py +157 -0
- ctools/sys_log.py +89 -0
- ctools/thread_pool.py +35 -0
- ctools/upload_tools.py +40 -0
- ctools/win_canvas.py +83 -0
- ctools/win_control.py +106 -0
- ctools/word_fill.py +562 -0
- ctools/word_fill_entity.py +46 -0
- ctools/work_path.py +69 -0
- {gomyck_tools-1.3.1.dist-info → gomyck_tools-1.3.2.dist-info}/METADATA +1 -1
- gomyck_tools-1.3.2.dist-info/RECORD +62 -0
- gomyck_tools-1.3.2.dist-info/top_level.txt +1 -0
- gomyck_tools-1.3.1.dist-info/RECORD +0 -4
- gomyck_tools-1.3.1.dist-info/top_level.txt +0 -1
- {gomyck_tools-1.3.1.dist-info → gomyck_tools-1.3.2.dist-info}/WHEEL +0 -0
ctools/cjson.py
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
import jsonpickle
|
2
|
+
|
3
|
+
# 需要转换成str的属性
|
4
|
+
str_value_keys = []
|
5
|
+
jsonpickle.set_preferred_backend('json')
|
6
|
+
jsonpickle.set_encoder_options('json', ensure_ascii=False)
|
7
|
+
jsonpickle.set_decoder_options('json')
|
8
|
+
|
9
|
+
def dumps(obj, **kwargs) -> str:
|
10
|
+
"""
|
11
|
+
将对象转换为json字符串
|
12
|
+
:param obj: 对象
|
13
|
+
:return: json 字符串
|
14
|
+
"""
|
15
|
+
# indent = 2 可以美化输出
|
16
|
+
if obj is None: return None
|
17
|
+
if type(obj) == str: return obj
|
18
|
+
return f'{jsonpickle.encode(obj, unpicklable=False, make_refs=False, **kwargs)}'
|
19
|
+
|
20
|
+
def loads(json_str: str, **kwargs) -> dict:
|
21
|
+
"""
|
22
|
+
将json字符串转换为对象
|
23
|
+
:param json_str: json 字符串
|
24
|
+
:return: 对象
|
25
|
+
"""
|
26
|
+
return jsonpickle.decode(json_str, **kwargs)
|
27
|
+
|
28
|
+
def unify_to_str(json_str: str) -> str:
|
29
|
+
if not str_value_keys and len(str_value_keys) == 0: return json_str
|
30
|
+
obj = loads(json_str)
|
31
|
+
if isinstance(obj, list):
|
32
|
+
_handle_list(obj)
|
33
|
+
elif isinstance(obj, dict):
|
34
|
+
_handle_dict(obj)
|
35
|
+
return dumps(obj)
|
36
|
+
|
37
|
+
def _handle_list(data):
|
38
|
+
for o in data:
|
39
|
+
if isinstance(o, list):
|
40
|
+
_handle_list(o)
|
41
|
+
elif isinstance(o, dict):
|
42
|
+
_handle_dict(o)
|
43
|
+
|
44
|
+
def _handle_dict(data):
|
45
|
+
for k, v in data.items():
|
46
|
+
if isinstance(v, list):
|
47
|
+
_handle_list(v)
|
48
|
+
elif isinstance(v, dict):
|
49
|
+
_handle_dict(v)
|
50
|
+
elif k in str_value_keys:
|
51
|
+
try:
|
52
|
+
data[k] = str(v)
|
53
|
+
except Exception:
|
54
|
+
pass
|
ctools/ckafka.py
ADDED
@@ -0,0 +1,159 @@
|
|
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, Lock
|
8
|
+
|
9
|
+
from kafka import KafkaProducer, errors, KafkaConsumer
|
10
|
+
from kafka.producer.future import FutureRecordMetadata
|
11
|
+
|
12
|
+
from ctools import thread_pool
|
13
|
+
from ctools.cjson import dumps
|
14
|
+
|
15
|
+
"""
|
16
|
+
import time
|
17
|
+
from datetime import datetime
|
18
|
+
|
19
|
+
from ctools import thread_pool, string_tools
|
20
|
+
from ctools.ckafka import CKafka
|
21
|
+
|
22
|
+
c = CKafka(kafka_url='192.168.3.160:9094', secure=True)
|
23
|
+
|
24
|
+
producer = c.init_producer()
|
25
|
+
consumer = c.init_consumer(enable_auto_commit=False)
|
26
|
+
|
27
|
+
def send_msg():
|
28
|
+
while True:
|
29
|
+
command = input('发送消息: Y/n \n')
|
30
|
+
if command.strip() not in ['N', 'n']:
|
31
|
+
producer.send_msg('jqxx', '{{"jqid": "{}", "xxxx": "{}"}}'.format(string_tools.get_snowflake_id(), datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')))
|
32
|
+
else:
|
33
|
+
break
|
34
|
+
|
35
|
+
thread_pool.submit(send_msg)
|
36
|
+
|
37
|
+
def consumer_callback(msg):
|
38
|
+
print(msg)
|
39
|
+
return True
|
40
|
+
|
41
|
+
consumer.receive_msg('jqxx', callBack=consumer_callback)
|
42
|
+
|
43
|
+
while True: time.sleep(1)
|
44
|
+
"""
|
45
|
+
class KafkaInstance:
|
46
|
+
def __init__(self, producer: KafkaProducer, consumer: KafkaConsumer):
|
47
|
+
self.start_consumer = False
|
48
|
+
self.quited = False
|
49
|
+
self.producer = producer
|
50
|
+
self.consumer = consumer
|
51
|
+
self.consumer_callback = {"topic_key": []}
|
52
|
+
|
53
|
+
# FutureRecordMetadata 可以添加回调, 来监听是否发送成功
|
54
|
+
# r.add_callback(lambda x: print(x))
|
55
|
+
# r.get() 可以同步获取结果
|
56
|
+
def send_msg(self, topic, msg, key: str=None, partition:int=None) -> FutureRecordMetadata:
|
57
|
+
if self.producer is None: raise RuntimeError("Producer is not initialized")
|
58
|
+
if self.quited: return
|
59
|
+
return self.producer.send(topic=topic, value=msg, key=None if key is None else key.encode('utf-8'), partition=partition)
|
60
|
+
|
61
|
+
def receive_msg(self, topics: str, callBack=print):
|
62
|
+
if self.consumer is None: raise RuntimeError("Consumer is not initialized")
|
63
|
+
for topic in topics.split(','):
|
64
|
+
if topic not in self.consumer_callback.keys():
|
65
|
+
self.consumer_callback[topic] = []
|
66
|
+
self.consumer.subscribe(self.consumer_callback.keys())
|
67
|
+
self.consumer_callback[topic].append(callBack)
|
68
|
+
if not self.start_consumer:
|
69
|
+
t = Thread(target=self._start_consumer_poll, daemon=True)
|
70
|
+
t.start()
|
71
|
+
|
72
|
+
def _start_consumer_poll(self):
|
73
|
+
self.start_consumer = True
|
74
|
+
for msg in self.consumer:
|
75
|
+
if self.quited: break
|
76
|
+
funcList = []
|
77
|
+
begin_time = time.time()
|
78
|
+
for func in self.consumer_callback[msg.topic]:
|
79
|
+
if self.quited: break
|
80
|
+
res = func(msg)
|
81
|
+
if not self.consumer.config['enable_auto_commit'] and res: self.consumer.commit()
|
82
|
+
funcList.append(func.__name__)
|
83
|
+
end_time = time.time()
|
84
|
+
if end_time - begin_time > 1: print(f"kafka consume too slow!!! {funcList} time cost: ", f'{round(end_time - begin_time, 2)}s')
|
85
|
+
funcList.clear()
|
86
|
+
|
87
|
+
def shutdown(self):
|
88
|
+
self.quited = True
|
89
|
+
try: self.consumer.close()
|
90
|
+
except Exception: pass
|
91
|
+
try: self.producer.close()
|
92
|
+
except Exception: pass
|
93
|
+
|
94
|
+
|
95
|
+
class CKafka:
|
96
|
+
|
97
|
+
def __init__(self, kafka_url: str = '127.0.0.1:9092', secure: bool = False, username: str = 'client', password: str = 'hylink_user_password'):
|
98
|
+
self.kafka_url = kafka_url
|
99
|
+
self.secure = secure
|
100
|
+
self.username = username
|
101
|
+
self.password = password
|
102
|
+
|
103
|
+
def init_producer(self, acks=1) -> KafkaInstance:
|
104
|
+
print("[ Producer ] Connecting to Kafka [{}]".format(self.kafka_url))
|
105
|
+
for i in range(0, 6):
|
106
|
+
try:
|
107
|
+
if self.secure:
|
108
|
+
producer = KafkaProducer(
|
109
|
+
acks=acks,
|
110
|
+
bootstrap_servers=self.kafka_url,
|
111
|
+
value_serializer=lambda x: dumps(x).encode('utf-8'),
|
112
|
+
sasl_plain_username=self.username,
|
113
|
+
sasl_plain_password=self.password,
|
114
|
+
security_protocol='SASL_PLAINTEXT',
|
115
|
+
sasl_mechanism='PLAIN'
|
116
|
+
)
|
117
|
+
else:
|
118
|
+
producer = KafkaProducer(
|
119
|
+
acks=acks,
|
120
|
+
bootstrap_servers=self.kafka_url,
|
121
|
+
value_serializer=lambda x: dumps(x).encode('utf-8')
|
122
|
+
)
|
123
|
+
print("[ Producer ] Success Connected to Kafka [{}]".format(self.kafka_url))
|
124
|
+
return KafkaInstance(producer=producer, consumer=None)
|
125
|
+
except errors.NoBrokersAvailable:
|
126
|
+
print("[ Producer ] Waiting for Kafka [{}] to become available...".format(self.kafka_url))
|
127
|
+
time.sleep(3)
|
128
|
+
raise RuntimeError("[ Producer ] Failed to connect to Kafka [{}] within 60 seconds".format(self.kafka_url))
|
129
|
+
|
130
|
+
def init_consumer(self, client_id: str = 'ck-py-kafka-consumer', consumer_group: str = 'ck-py-kafka-consumer', enable_auto_commit: bool = True) -> KafkaInstance:
|
131
|
+
print("[ Consumer ] Connecting to Kafka [{}]".format(self.kafka_url))
|
132
|
+
for i in range(0, 6):
|
133
|
+
try:
|
134
|
+
if self.secure:
|
135
|
+
consumer = KafkaConsumer(
|
136
|
+
client_id=client_id,
|
137
|
+
group_id=consumer_group,
|
138
|
+
enable_auto_commit=enable_auto_commit,
|
139
|
+
bootstrap_servers=self.kafka_url,
|
140
|
+
value_deserializer=lambda x: x.decode('utf-8'),
|
141
|
+
sasl_plain_username=self.username,
|
142
|
+
sasl_plain_password=self.password,
|
143
|
+
security_protocol='SASL_PLAINTEXT',
|
144
|
+
sasl_mechanism='PLAIN'
|
145
|
+
)
|
146
|
+
else:
|
147
|
+
consumer = KafkaProducer(
|
148
|
+
client_id=client_id,
|
149
|
+
group_id=consumer_group,
|
150
|
+
enable_auto_commit=enable_auto_commit,
|
151
|
+
bootstrap_servers=self.kafka_url,
|
152
|
+
value_deserializer=lambda x: x.decode('utf-8')
|
153
|
+
)
|
154
|
+
print("[ Consumer ] Success Connected to Kafka [{}]".format(self.kafka_url))
|
155
|
+
return KafkaInstance(producer=None, consumer=consumer)
|
156
|
+
except errors.NoBrokersAvailable:
|
157
|
+
print("[ Consumer ] Waiting for Kafka [{}] to become available...".format(self.kafka_url))
|
158
|
+
time.sleep(3)
|
159
|
+
raise RuntimeError("[ Consumer ] Failed to connect to Kafka [{}] within 60 seconds".format(self.kafka_url))
|
ctools/compile_tools.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
import importlib
|
2
|
+
import os
|
3
|
+
import time
|
4
|
+
|
5
|
+
|
6
|
+
def code_to_pyc(code: str, out_file_path: str):
|
7
|
+
file_name = os.path.split(out_file_path)[-1]
|
8
|
+
compiled_code = compile(code, file_name, 'exec')
|
9
|
+
bytecode = importlib._bootstrap_external._code_to_timestamp_pyc(compiled_code, time.time(), len(code))
|
10
|
+
with open(out_file_path, 'wb') as f:
|
11
|
+
f.write(bytecode)
|
12
|
+
|
13
|
+
|
14
|
+
def file_to_pyc(file_path: str, out_file_path: str):
|
15
|
+
with open(file_path, 'r') as f:
|
16
|
+
code = f.read()
|
17
|
+
if code:
|
18
|
+
code_to_pyc(code, out_file_path)
|
ctools/console.py
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
import logging
|
2
|
+
import sys
|
3
|
+
import tkinter as tk
|
4
|
+
|
5
|
+
|
6
|
+
class Console:
|
7
|
+
|
8
|
+
def __init__(self, master):
|
9
|
+
self.master = master
|
10
|
+
|
11
|
+
# 创建文本框和滚动条
|
12
|
+
self.textbox = tk.Text(self.master, wrap=tk.NONE)
|
13
|
+
|
14
|
+
self.vertical_scrollbar = tk.Scrollbar(self.textbox, command=self.textbox.yview)
|
15
|
+
self.horizontal_scrollbar = tk.Scrollbar(self.textbox, command=self.textbox.xview, orient=tk.HORIZONTAL)
|
16
|
+
|
17
|
+
self.textbox.configure(yscrollcommand=self.vertical_scrollbar.set, xscrollcommand=self.horizontal_scrollbar.set)
|
18
|
+
self.textbox.pack(side=tk.LEFT, pady=10, padx=10, ipadx=10, ipady=10, fill=tk.BOTH, expand=True)
|
19
|
+
|
20
|
+
self.vertical_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
21
|
+
self.horizontal_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
|
22
|
+
|
23
|
+
# 将标准输出和标准错误输出重定向到文本框中
|
24
|
+
sys.stdout = self
|
25
|
+
sys.stderr = self
|
26
|
+
|
27
|
+
# # 创建输入框和按钮
|
28
|
+
# self.entry = tk.Entry(self.master)
|
29
|
+
# self.entry.pack(side=tk.BOTTOM, fill=tk.X, expand=True)
|
30
|
+
# self.button = tk.Button(self.master, text="Send", command=self.send)
|
31
|
+
# self.button.pack(side=tk.BOTTOM)
|
32
|
+
|
33
|
+
# 将日志输出到文本框中
|
34
|
+
self.log_handler = logging.StreamHandler(self)
|
35
|
+
self.log_handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
|
36
|
+
logging.getLogger().addHandler(self.log_handler)
|
37
|
+
# logging.getLogger().setLevel(logging.INFO)
|
38
|
+
|
39
|
+
def write(self, message):
|
40
|
+
# 在文本框中输出消息
|
41
|
+
self.textbox.insert(tk.END, message + '\n')
|
42
|
+
self.textbox.see(tk.END)
|
43
|
+
|
44
|
+
def flush(self):
|
45
|
+
pass
|
46
|
+
|
47
|
+
def send(self):
|
48
|
+
# 获取输入框中的文本并打印到控制台
|
49
|
+
text = self.entry.get()
|
50
|
+
print(text)
|
51
|
+
self.entry.delete(0, tk.END)
|
52
|
+
|
53
|
+
def __del__(self):
|
54
|
+
# 关闭日志处理器
|
55
|
+
logging.getLogger().removeHandler(self.log_handler)
|
ctools/coord_trans.py
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
import math
|
3
|
+
|
4
|
+
x_pi = 3.14159265358979324 * 3000.0 / 180.0
|
5
|
+
pi = 3.1415926535897932384626 # π
|
6
|
+
a = 6378245.0 # 长半轴
|
7
|
+
ee = 0.00669342162296594323 # 偏心率平方
|
8
|
+
|
9
|
+
def gcj02_to_bd09(lng, lat):
|
10
|
+
"""
|
11
|
+
火星坐标系(GCJ-02)转百度坐标系(BD-09)
|
12
|
+
谷歌、高德——>百度
|
13
|
+
:param lng:火星坐标经度
|
14
|
+
:param lat:火星坐标纬度
|
15
|
+
:return:
|
16
|
+
"""
|
17
|
+
z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * x_pi)
|
18
|
+
theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * x_pi)
|
19
|
+
bd_lng = z * math.cos(theta) + 0.0065
|
20
|
+
bd_lat = z * math.sin(theta) + 0.006
|
21
|
+
return [bd_lng, bd_lat]
|
22
|
+
|
23
|
+
|
24
|
+
def bd09_to_gcj02(bd_lon, bd_lat):
|
25
|
+
"""
|
26
|
+
百度坐标系(BD-09)转火星坐标系(GCJ-02)
|
27
|
+
百度——>谷歌、高德
|
28
|
+
:param bd_lat:百度坐标纬度
|
29
|
+
:param bd_lon:百度坐标经度
|
30
|
+
:return:转换后的坐标列表形式
|
31
|
+
"""
|
32
|
+
x = bd_lon - 0.0065
|
33
|
+
y = bd_lat - 0.006
|
34
|
+
z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi)
|
35
|
+
theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi)
|
36
|
+
gg_lng = z * math.cos(theta)
|
37
|
+
gg_lat = z * math.sin(theta)
|
38
|
+
return [gg_lng, gg_lat]
|
39
|
+
|
40
|
+
|
41
|
+
def wgs84_to_gcj02(lng, lat):
|
42
|
+
"""
|
43
|
+
WGS84转GCJ02(火星坐标系)
|
44
|
+
:param lng:WGS84坐标系的经度
|
45
|
+
:param lat:WGS84坐标系的纬度
|
46
|
+
:return:
|
47
|
+
"""
|
48
|
+
if out_of_china(lng, lat): # 判断是否在国内
|
49
|
+
return [lng, lat]
|
50
|
+
dlat = _transformlat(lng - 105.0, lat - 35.0)
|
51
|
+
dlng = _transformlng(lng - 105.0, lat - 35.0)
|
52
|
+
radlat = lat / 180.0 * pi
|
53
|
+
magic = math.sin(radlat)
|
54
|
+
magic = 1 - ee * magic * magic
|
55
|
+
sqrtmagic = math.sqrt(magic)
|
56
|
+
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
|
57
|
+
dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
|
58
|
+
mglat = lat + dlat
|
59
|
+
mglng = lng + dlng
|
60
|
+
return [mglng, mglat]
|
61
|
+
|
62
|
+
|
63
|
+
def gcj02_to_wgs84(lng, lat):
|
64
|
+
"""
|
65
|
+
GCJ02(火星坐标系)转GPS84
|
66
|
+
:param lng:火星坐标系的经度
|
67
|
+
:param lat:火星坐标系纬度
|
68
|
+
:return:
|
69
|
+
"""
|
70
|
+
if out_of_china(lng, lat):
|
71
|
+
return [lng, lat]
|
72
|
+
dlat = _transformlat(lng - 105.0, lat - 35.0)
|
73
|
+
dlng = _transformlng(lng - 105.0, lat - 35.0)
|
74
|
+
radlat = lat / 180.0 * pi
|
75
|
+
magic = math.sin(radlat)
|
76
|
+
magic = 1 - ee * magic * magic
|
77
|
+
sqrtmagic = math.sqrt(magic)
|
78
|
+
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
|
79
|
+
dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
|
80
|
+
mglat = lat + dlat
|
81
|
+
mglng = lng + dlng
|
82
|
+
return [lng * 2 - mglng, lat * 2 - mglat]
|
83
|
+
|
84
|
+
|
85
|
+
def bd09_to_wgs84(bd_lon, bd_lat):
|
86
|
+
lon, lat = bd09_to_gcj02(bd_lon, bd_lat)
|
87
|
+
return gcj02_to_wgs84(lon, lat)
|
88
|
+
|
89
|
+
|
90
|
+
def wgs84_to_bd09(lon, lat):
|
91
|
+
lon, lat = wgs84_to_gcj02(lon, lat)
|
92
|
+
return gcj02_to_bd09(lon, lat)
|
93
|
+
|
94
|
+
|
95
|
+
def _transformlat(lng, lat):
|
96
|
+
ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + \
|
97
|
+
0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
|
98
|
+
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
|
99
|
+
math.sin(2.0 * lng * pi)) * 2.0 / 3.0
|
100
|
+
ret += (20.0 * math.sin(lat * pi) + 40.0 *
|
101
|
+
math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
|
102
|
+
ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 *
|
103
|
+
math.sin(lat * pi / 30.0)) * 2.0 / 3.0
|
104
|
+
return ret
|
105
|
+
|
106
|
+
|
107
|
+
def _transformlng(lng, lat):
|
108
|
+
ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \
|
109
|
+
0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
|
110
|
+
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
|
111
|
+
math.sin(2.0 * lng * pi)) * 2.0 / 3.0
|
112
|
+
ret += (20.0 * math.sin(lng * pi) + 40.0 *
|
113
|
+
math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
|
114
|
+
ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 *
|
115
|
+
math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
|
116
|
+
return ret
|
117
|
+
|
118
|
+
|
119
|
+
def out_of_china(lng, lat):
|
120
|
+
"""
|
121
|
+
判断是否在国内,不在国内不做偏移
|
122
|
+
:param lng:
|
123
|
+
:param lat:
|
124
|
+
:return:
|
125
|
+
"""
|
126
|
+
return not (lng > 73.66 and lng < 135.05 and lat > 3.86 and lat < 53.55)
|
127
|
+
|
ctools/credis.py
ADDED
@@ -0,0 +1,111 @@
|
|
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 date_utils, thread_pool, string_tools
|
10
|
+
|
11
|
+
def init_pool(host: str = 'localhost', port: int = 6379, db: int = 0, password: str = None,
|
12
|
+
username: str = None, decode_responses: bool = True, max_connections: int = 75,
|
13
|
+
health_check_interval: int = 30, retry_count: int = 3) -> Redis:
|
14
|
+
for attempt in range(retry_count):
|
15
|
+
try:
|
16
|
+
r: Redis = redis.StrictRedis(
|
17
|
+
host=host, port=port, db=db,
|
18
|
+
username=username, password=password,
|
19
|
+
retry_on_timeout=True,
|
20
|
+
max_connections=max_connections,
|
21
|
+
decode_responses=decode_responses,
|
22
|
+
health_check_interval=health_check_interval,
|
23
|
+
socket_connect_timeout=5,
|
24
|
+
socket_timeout=5
|
25
|
+
)
|
26
|
+
if r.ping():
|
27
|
+
print('CRedis connect {} {} success!'.format(host, port))
|
28
|
+
return r
|
29
|
+
except redis.ConnectionError as e:
|
30
|
+
if attempt == retry_count - 1:
|
31
|
+
raise Exception(f"Failed to connect to Redis after {retry_count} attempts: {str(e)}")
|
32
|
+
print(f"Connection attempt {attempt + 1} failed, retrying...")
|
33
|
+
|
34
|
+
def add_lock(r: Redis, key: str, timeout: int = 30):
|
35
|
+
if r.exists(key):
|
36
|
+
expire_time = r.get(key)
|
37
|
+
if date_utils.time_diff_in_seconds(expire_time, date_utils.get_date_time()) > 0:
|
38
|
+
return True
|
39
|
+
else:
|
40
|
+
r.delete(key)
|
41
|
+
return r.set(key, date_utils.opt_time(seconds=timeout), nx=True, ex=timeout) is not None
|
42
|
+
|
43
|
+
def remove_lock(r: Redis, key: str):
|
44
|
+
r.delete(key)
|
45
|
+
|
46
|
+
def subscribe(r: Redis, channel_name, callback):
|
47
|
+
def thread_func():
|
48
|
+
pubsub = r.pubsub()
|
49
|
+
pubsub.subscribe(channel_name)
|
50
|
+
for message in pubsub.listen():
|
51
|
+
callback(message)
|
52
|
+
thread_pool.submit(thread_func)
|
53
|
+
|
54
|
+
def _process_pending_messages(r: Redis, stream_name: str, group_name: str, consumer_name: str, callback):
|
55
|
+
"""
|
56
|
+
处理未确认的消息
|
57
|
+
:param r: Redis 连接
|
58
|
+
:param stream_name: 流名称
|
59
|
+
:param group_name: 消费者组名称
|
60
|
+
:param consumer_name: 消费者名称
|
61
|
+
:param callback: 消息处理回调函数
|
62
|
+
"""
|
63
|
+
# 检查未确认的消息
|
64
|
+
pending_messages = r.xpending(stream_name, group_name)
|
65
|
+
if pending_messages['pending'] > 0:
|
66
|
+
print(f"Found {pending_messages['pending']} pending messages.")
|
67
|
+
# 获取未确认的消息列表
|
68
|
+
pending_list = r.xpending_range(stream_name, group_name, min='-', max='+', count=pending_messages['pending'])
|
69
|
+
for message in pending_list:
|
70
|
+
message_id = message['message_id']
|
71
|
+
claimed_messages = r.xclaim(stream_name, group_name, consumer_name, min_idle_time=0, message_ids=[message_id])
|
72
|
+
if claimed_messages:
|
73
|
+
# 处理消息
|
74
|
+
for claimed_message in claimed_messages:
|
75
|
+
message_id, data = claimed_message
|
76
|
+
print(f"Processing pending message: {message_id}, data: {data}")
|
77
|
+
try:
|
78
|
+
if callback(message_id, data):
|
79
|
+
r.xack(stream_name, group_name, message_id)
|
80
|
+
except Exception as e:
|
81
|
+
print(f"Error processing message {message_id}: {e}")
|
82
|
+
else:
|
83
|
+
print("No pending messages found.")
|
84
|
+
|
85
|
+
def stream_subscribe(r: Redis, stream_name, group_name, callback, from_id: str='$', noack: bool = False):
|
86
|
+
def thread_func():
|
87
|
+
try:
|
88
|
+
# $表示从最后面消费, 0表示从开始消费
|
89
|
+
r.xgroup_create(name=stream_name, groupname=group_name, id=from_id, mkstream=True)
|
90
|
+
print(f"Consumer group '{group_name}' created successfully.")
|
91
|
+
except Exception as e:
|
92
|
+
if "already exists" in str(e):
|
93
|
+
print(f"Consumer group '{group_name}' already exists.")
|
94
|
+
else:
|
95
|
+
print(f"Error creating consumer group '{group_name}': {e}")
|
96
|
+
consumer_name = 'consumer-{}'.format(string_tools.get_uuid())
|
97
|
+
# 处理未确认的消息
|
98
|
+
_process_pending_messages(r, stream_name, group_name, consumer_name, callback)
|
99
|
+
while True:
|
100
|
+
messages = r.xreadgroup(group_name, consumer_name, {stream_name: '>'}, block=1000, noack=noack)
|
101
|
+
for message in messages:
|
102
|
+
try:
|
103
|
+
message_id, data = message[1][0]
|
104
|
+
res = callback(message_id, data)
|
105
|
+
if res: r.xack(stream_name, group_name, message_id)
|
106
|
+
except Exception as e:
|
107
|
+
print('stream_subscribe error: ', e)
|
108
|
+
thread_pool.submit(thread_func)
|
109
|
+
|
110
|
+
def stream_publish(r: Redis, stream_name, message):
|
111
|
+
r.xadd(stream_name, message)
|