enconia-utils 0.1.0__tar.gz

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.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Huanai Technology Co., Ltd.
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: enconia-utils
3
+ Version: 0.1.0
4
+ Summary: utils for enconia
5
+ Author-email: "Huanai Technology Co., Ltd" <zhou-jinyuan@foxmail.com>
6
+ License: BSD-3-Clause
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: BSD License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: requests>=2.25.0
18
+ Requires-Dist: pytz>=2021.1
19
+ Requires-Dist: deprecated>=1.2.0
20
+ Requires-Dist: oss2>=2.15.0
21
+ Requires-Dist: aliyun-log-python-sdk>=0.8.0
22
+ Requires-Dist: chinese_calendar>=1.6.0
23
+ Dynamic: license-file
24
+
25
+ # enconia-utils
26
+
27
+ Enconia 工具包,提供常用的 Python 工具函数集合。
28
+
29
+ ## 安装
30
+
31
+ ```bash
32
+ pip install enconia-utils
33
+ ```
34
+
35
+ ## 功能模块
36
+
37
+ ### ID 生成器 (`id_generator`)
38
+ - `get_random_short_id()` - 生成 8 位随机短 ID
39
+ - `get_random_long_id()` - 生成 22 位随机长 ID
40
+ - `get_fix_short_id(base_str)` - 根据输入字符串生成固定的短 ID
41
+ - `get_fix_long_id(base_str)` - 根据输入字符串生成固定的长 ID
42
+ - `get_file_md5(filepath)` - 计算文件 MD5
43
+
44
+ ### 日期工具 (`util_date`)
45
+ - `get_orbit_std_datetime()` - 获取标准格式的当前日期时间
46
+ - `get_orbit_std_date()` - 获取标准格式的当前日期
47
+ - `get_date_range_list_v2(start, end)` - 获取日期范围列表
48
+ - `get_next_day(day, interval)` - 获取下一天
49
+ - `get_next_workday_cn(day)` - 获取下一个中国工作日
50
+
51
+ ### 飞书推送 (`feishu_push`)
52
+ - `FeiShuTalkChatBot` - 飞书机器人消息推送
53
+
54
+ ### 阿里云 SLS 日志 (`aliyun_sls_log`)
55
+ - `send_log_to_sls()` - 发送日志到阿里云 SLS
56
+
57
+ ### 阿里云 OSS (`util_oss`)
58
+ - `OssCloudObjectProvider` - OSS 文件操作(上传、下载、复制、删除等)
59
+
60
+ ### 文件类型映射 (`util_type_mapping`)
61
+ - `get_content_type_4_filename(filename)` - 根据文件名获取 MIME 类型
62
+ - `file2content_type(ext)` - 扩展名转 MIME 类型
63
+ - `content2file_type(mime)` - MIME 类型转扩展名
64
+
65
+ ## 依赖
66
+
67
+ - requests
68
+ - pytz
69
+ - deprecated
70
+ - oss2
71
+ - aliyun-log-python-sdk
72
+ - chinese_calendar
73
+
74
+ ## License
75
+
76
+ BSD-3-Clause
@@ -0,0 +1,52 @@
1
+ # enconia-utils
2
+
3
+ Enconia 工具包,提供常用的 Python 工具函数集合。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install enconia-utils
9
+ ```
10
+
11
+ ## 功能模块
12
+
13
+ ### ID 生成器 (`id_generator`)
14
+ - `get_random_short_id()` - 生成 8 位随机短 ID
15
+ - `get_random_long_id()` - 生成 22 位随机长 ID
16
+ - `get_fix_short_id(base_str)` - 根据输入字符串生成固定的短 ID
17
+ - `get_fix_long_id(base_str)` - 根据输入字符串生成固定的长 ID
18
+ - `get_file_md5(filepath)` - 计算文件 MD5
19
+
20
+ ### 日期工具 (`util_date`)
21
+ - `get_orbit_std_datetime()` - 获取标准格式的当前日期时间
22
+ - `get_orbit_std_date()` - 获取标准格式的当前日期
23
+ - `get_date_range_list_v2(start, end)` - 获取日期范围列表
24
+ - `get_next_day(day, interval)` - 获取下一天
25
+ - `get_next_workday_cn(day)` - 获取下一个中国工作日
26
+
27
+ ### 飞书推送 (`feishu_push`)
28
+ - `FeiShuTalkChatBot` - 飞书机器人消息推送
29
+
30
+ ### 阿里云 SLS 日志 (`aliyun_sls_log`)
31
+ - `send_log_to_sls()` - 发送日志到阿里云 SLS
32
+
33
+ ### 阿里云 OSS (`util_oss`)
34
+ - `OssCloudObjectProvider` - OSS 文件操作(上传、下载、复制、删除等)
35
+
36
+ ### 文件类型映射 (`util_type_mapping`)
37
+ - `get_content_type_4_filename(filename)` - 根据文件名获取 MIME 类型
38
+ - `file2content_type(ext)` - 扩展名转 MIME 类型
39
+ - `content2file_type(mime)` - MIME 类型转扩展名
40
+
41
+ ## 依赖
42
+
43
+ - requests
44
+ - pytz
45
+ - deprecated
46
+ - oss2
47
+ - aliyun-log-python-sdk
48
+ - chinese_calendar
49
+
50
+ ## License
51
+
52
+ BSD-3-Clause
@@ -0,0 +1,71 @@
1
+ """工具函数集合"""
2
+
3
+ from .aliyun_sls_log import send_log_to_sls
4
+ from .feishu_push import FeiShuTalkChatBot, NotifyLevel
5
+ from .id_generator import (
6
+ get_random_short_id,
7
+ get_fix_short_id,
8
+ get_random_long_id,
9
+ get_fix_long_id,
10
+ get_random_uuid,
11
+ get_fix_uuid,
12
+ get_file_md5,
13
+ )
14
+ from .util_date import (
15
+ DateTimeFormat,
16
+ DateMaxMin,
17
+ DateEncoder,
18
+ get_orbit_std_datetime,
19
+ get_orbit_std_datetime_utc,
20
+ get_orbit_std_date,
21
+ get_orbit_std_date_utc,
22
+ get_date_range_by_base,
23
+ get_date_range_list_v1,
24
+ get_date_range_list_v2,
25
+ get_next_day,
26
+ get_next_workday_cn,
27
+ )
28
+ from .util_oss import OssCloudObjectProvider
29
+ from .util_type_mapping import (
30
+ ExtenCons,
31
+ content2file_type,
32
+ file2content_type,
33
+ get_content_type_4_filename,
34
+ )
35
+
36
+ __version__ = "0.1.0"
37
+ __all__ = [
38
+ # aliyun_sls_log
39
+ "send_log_to_sls",
40
+ # feishu_push
41
+ "FeiShuTalkChatBot",
42
+ "NotifyLevel",
43
+ # id_generator
44
+ "get_random_short_id",
45
+ "get_fix_short_id",
46
+ "get_random_long_id",
47
+ "get_fix_long_id",
48
+ "get_random_uuid",
49
+ "get_fix_uuid",
50
+ "get_file_md5",
51
+ # util_date
52
+ "DateTimeFormat",
53
+ "DateMaxMin",
54
+ "DateEncoder",
55
+ "get_orbit_std_datetime",
56
+ "get_orbit_std_datetime_utc",
57
+ "get_orbit_std_date",
58
+ "get_orbit_std_date_utc",
59
+ "get_date_range_by_base",
60
+ "get_date_range_list_v1",
61
+ "get_date_range_list_v2",
62
+ "get_next_day",
63
+ "get_next_workday_cn",
64
+ # util_oss
65
+ "OssCloudObjectProvider",
66
+ # util_type_mapping
67
+ "ExtenCons",
68
+ "content2file_type",
69
+ "file2content_type",
70
+ "get_content_type_4_filename",
71
+ ]
@@ -0,0 +1,133 @@
1
+ """
2
+ 集成阿里云SLS日志服务
3
+
4
+ 使用说明:
5
+ 1. 安装依赖:
6
+ pip install aliyun-log-python-sdk
7
+
8
+ 2. 配置阿里云SLS:
9
+ - 在阿里云控制台创建Project和Logstore
10
+ - 获取AccessKey ID和AccessKey Secret
11
+ - 设置环境变量或修改下方配置:
12
+ ALIBABA_CLOUD_ACCESS_KEY_ID=你的AccessKeyID
13
+ ALIBABA_CLOUD_ACCESS_KEY_SECRET=你的AccessKeySecret
14
+
15
+ 3. 配置信息获取:
16
+ - Endpoint: 在SLS控制台查看,如 cn-hangzhou.log.aliyuncs.com
17
+ - Project: 在SLS控制台创建的项目名称
18
+ - Logstore: 在项目下创建的日志库名称
19
+
20
+ 4. 验证配置:
21
+ - 运行脚本后查看SLS控制台是否收到日志
22
+ - 检查本地日志文件 logs/*.log
23
+
24
+ 5. 查看日志:
25
+ - SLS控制台: https://sls.console.aliyun.com
26
+ - 本地日志: logs/*.log
27
+
28
+ 当前实现:
29
+ - 使用阿里云官方SLS Python SDK写入日志
30
+ - 支持SDK和HTTP API两种发送方式(自动降级)
31
+ - 所有关键操作都会同时记录到本地日志和SLS
32
+ """
33
+ import logging
34
+
35
+ import requests
36
+ import time
37
+ import os
38
+ from datetime import datetime
39
+ from aliyun.log import LogClient, LogItem, PutLogsRequest
40
+
41
+ # 阿里云SLS日志服务配置
42
+ # try:
43
+ # from aliyun.log import LogClient, LogItem, PutLogsRequest
44
+ # SLS_SDK_AVAILABLE = True
45
+ # except ImportError:
46
+ # SLS_SDK_AVAILABLE = False
47
+ # logging.warning("阿里云SLS SDK未安装,将使用HTTP API方式发送日志")
48
+
49
+
50
+ # 阿里云SLS配置信息 - 请根据实际情况修改
51
+ SLS_SDK_AVAILABLE = True
52
+ SLS_ENDPOINT = 'cn-shanghai.log.aliyuncs.com' # 您的日志服务Endpoint
53
+ SLS_PROJECT = 'mineru-logs' # 您的Project名称
54
+ SLS_LOGSTORE = 'mineru-3log' # 您的Logstore名称
55
+ SLS_ACCESS_KEY_ID = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID', 'LTAI5tLtyiy18UAWL626k1wC')
56
+ SLS_ACCESS_KEY_SECRET = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET', 'T6VSV1QFn7hsc8wjrOGG8K4meDyXbX')
57
+
58
+ logger = logging.getLogger(__name__)
59
+
60
+ def send_log_to_sls(script_name,level, message, **kwargs):
61
+ """发送日志到阿里云SLS"""
62
+ try:
63
+ if SLS_SDK_AVAILABLE:
64
+ # 使用SDK方式发送日志
65
+ client = LogClient(SLS_ENDPOINT, SLS_ACCESS_KEY_ID, SLS_ACCESS_KEY_SECRET)
66
+
67
+ # 创建日志项
68
+ log_item = LogItem()
69
+ log_item.set_time(int(time.time()))
70
+
71
+ # 添加日志内容
72
+ log_item.set_contents([
73
+ ('level', level),
74
+ ('message', message),
75
+ ('timestamp', datetime.now().isoformat()),
76
+ ('script', script_name),
77
+ ('extra_info', str(kwargs) if kwargs else '')
78
+ ])
79
+
80
+ # 构建请求
81
+ request = PutLogsRequest(SLS_PROJECT, SLS_LOGSTORE, '', '', [log_item])
82
+
83
+ # 发送日志
84
+ response = client.put_logs(request)
85
+
86
+ # put_logs在成功时不会抛出异常,如果失败会抛出异常
87
+ logger.info(f"SLS日志发送成功: {message}")
88
+
89
+ else:
90
+ # 使用HTTP API方式发送日志
91
+ send_log_via_http(script_name,level, message, **kwargs)
92
+
93
+ except Exception as e:
94
+ logger.error(f"SLS日志发送异常: {str(e)}")
95
+ # 如果SDK方式失败,尝试使用HTTP方式
96
+ try:
97
+ send_log_via_http(script_name,level, message, **kwargs)
98
+ except Exception as http_e:
99
+ logger.error(f"HTTP方式也发送失败: {str(http_e)}")
100
+
101
+
102
+ def send_log_via_http(script_name, level, message, **kwargs):
103
+ """通过HTTP API发送日志到SLS"""
104
+ try:
105
+
106
+ # 构建日志数据
107
+ log_data = {
108
+ 'level': level,
109
+ 'message': message,
110
+ 'timestamp': datetime.now().isoformat(),
111
+ 'script': script_name,
112
+ 'extra_info': kwargs if kwargs else {}
113
+ }
114
+
115
+ # 构建请求头
116
+ headers = {
117
+ 'Content-Type': 'application/json',
118
+ 'x-log-apiversion': '0.6.0',
119
+ 'x-log-signaturemethod': 'hmac-sha1'
120
+ }
121
+
122
+ # 发送HTTP请求到SLS API
123
+ url = f"https://{SLS_ENDPOINT}/logstores/{SLS_LOGSTORE}/shards/lb"
124
+
125
+ response = requests.post(url, headers=headers, json=log_data, timeout=5)
126
+
127
+ if response.status_code == 200:
128
+ logger.info(f"HTTP方式SLS日志发送成功: {message}")
129
+ else:
130
+ logger.error(f"HTTP方式SLS日志发送失败: 状态码={response.status_code}, 响应={response.text[:200]}")
131
+
132
+ except Exception as e:
133
+ logger.error(f"HTTP方式SLS日志发送异常: {str(e)}")
@@ -0,0 +1,173 @@
1
+ import requests
2
+ import json
3
+ import time
4
+ import logging
5
+ from enum import Enum
6
+ from typing import Optional, List, Dict
7
+
8
+ try:
9
+ JSONDecodeError = json.decoder.JSONDecodeError
10
+ except AttributeError:
11
+ JSONDecodeError = ValueError
12
+
13
+
14
+ class NotifyLevel(Enum):
15
+ SUCCESS = 'green'
16
+ INFO = 'wathet'
17
+ WARNING = 'yellow'
18
+ DANGER = 'red'
19
+
20
+
21
+ class FeiShuTalkChatBot:
22
+ def __init__(self, webhook, secret=None, pc_slide=False, fail_notice=False):
23
+ '''
24
+ 机器人初始化
25
+ Reference doc: https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
26
+
27
+ :param webhook: 飞书群自定义机器人webhook地址
28
+ :param secret: 机器人安全设置页面勾选“加签”时需要传入的密钥
29
+ :param pc_slide: 消息链接打开方式,默认False为浏览器打开,设置为True时为PC端侧边栏打开
30
+ :param fail_notice: 消息发送失败提醒,默认为False不提醒,开发者可以根据返回的消息发送结果自行判断和处理
31
+ '''
32
+ super(FeiShuTalkChatBot, self).__init__()
33
+ self.headers = {'Content-Type': 'application/json; charset=utf-8'}
34
+ self.webhook = webhook
35
+ self.secret = secret
36
+ self.pc_slide = pc_slide
37
+ self.fail_notice = fail_notice
38
+
39
+ def success(self, message, title: str = 'Successfully', ats: Optional[List] = None, action=None):
40
+ self.send_text(title=title, message=message, level=NotifyLevel.SUCCESS.value, ats=ats, action=action)
41
+
42
+ def info(self, message, title: str = 'Notification', ats: Optional[List] = None, action=None):
43
+ self.send_text(title=title, message=message, level=NotifyLevel.INFO.value, ats=ats, action=action)
44
+
45
+ def warning(self, message, title: str = 'Warning', ats: Optional[List] = None, action=None):
46
+ self.send_text(title=title, message=message, level=NotifyLevel.WARNING.value, ats=ats, action=action)
47
+
48
+ def error(self, message, title: str = 'Error', ats: Optional[List] = None, action=None):
49
+ self.send_text(title=title, message=message, level=NotifyLevel.DANGER.value, ats=ats, action=action)
50
+
51
+ def send_text(self, title, message, level, ats: Optional[List] = None, action: Optional[Dict] = None):
52
+ """
53
+ :param title:
54
+ :param message:
55
+ :param level:
56
+ :param ats:
57
+ :param action: { "direction": "DOWN", actions: {} }
58
+ :return:
59
+ """
60
+ data = {
61
+ "msg_type": "interactive",
62
+ "card": {
63
+ "elements": [
64
+ {
65
+ "tag": "hr"
66
+ },
67
+ {
68
+ "tag": "div",
69
+ "text": {
70
+ "tag": "lark_md",
71
+ "content": message
72
+ }
73
+ }
74
+ ],
75
+ "header": {
76
+ "title": {
77
+ "content": title,
78
+ "tag": "plain_text"
79
+ },
80
+ "template": level
81
+ }
82
+ }
83
+ }
84
+
85
+ # Put @ people
86
+ if ats:
87
+ if len(ats) == 1 and ats[0] == "all":
88
+ data["card"]["elements"].append({
89
+ "tag": "div",
90
+ "text": {
91
+ "tag": "lark_md",
92
+ "content": "At <at id=all></at>",
93
+ }
94
+ })
95
+ elif len(ats) > 0:
96
+ at_list_template = []
97
+ for at in ats:
98
+ at_list_template.append({
99
+ "tag": "div",
100
+ "text": {
101
+ "tag": "lark_md",
102
+ "content": "At <at id=" + at + "></at>",
103
+ }
104
+ })
105
+ data["card"]["elements"].extend(at_list_template)
106
+ else:
107
+ raise Exception("Invalid ats.")
108
+
109
+ if action:
110
+ if action["direction"] == "UP":
111
+ direction = "UP"
112
+ else:
113
+ direction = "DOWN"
114
+
115
+ if direction == "DOWN":
116
+ data["card"]["elements"].append({"tag": "hr"})
117
+ data["card"]["elements"].append(action["actions"])
118
+ if direction == "UP":
119
+ data["card"]["elements"].insert(0, action["actions"])
120
+
121
+ return self.post(data)
122
+
123
+ def post(self, data):
124
+ """
125
+ 发送消息(内容UTF-8编码)
126
+ :param data: 消息数据(字典)
127
+ :return: 返回消息发送结果
128
+ """
129
+ try:
130
+ post_data = json.dumps(data)
131
+ response = requests.post(self.webhook, headers=self.headers, data=post_data, verify=False)
132
+
133
+ except requests.exceptions.HTTPError as exc:
134
+ logging.error("消息发送失败, HTTP error: %d, reason: %s" % (exc.response.status_code, exc.response.reason))
135
+ raise
136
+ except requests.exceptions.ConnectionError:
137
+ logging.error("消息发送失败,HTTP connection error!")
138
+ raise
139
+ except requests.exceptions.Timeout:
140
+ logging.error("消息发送失败,Timeout error!")
141
+ raise
142
+ except requests.exceptions.RequestException:
143
+ logging.error("消息发送失败, Request Exception!")
144
+ raise
145
+ else:
146
+ try:
147
+ result = response.json()
148
+ except JSONDecodeError:
149
+ logging.error("服务器响应异常,状态码:%s,响应内容:%s" % (response.status_code, response.text))
150
+ return {'errcode': 500, 'errmsg': '服务器响应异常'}
151
+ else:
152
+ logging.debug('发送结果:%s' % result)
153
+ # 消息发送失败提醒(errcode 不为 0,表示消息发送异常),默认不提醒,开发者可以根据返回的消息发送结果自行判断和处理
154
+ if self.fail_notice and result.get('errcode', True):
155
+ time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
156
+ error_data = {
157
+ "msgtype": "text",
158
+ "text": {
159
+ "content": "[注意-自动通知]飞书机器人消息发送失败,时间:%s,原因:%s,请及时跟进,谢谢!" % (
160
+ time_now, result['errmsg'] if result.get('errmsg', False) else '未知异常')
161
+ },
162
+ "at": {
163
+ "isAtAll": False
164
+ }
165
+ }
166
+ logging.error("消息发送失败,自动通知:%s" % error_data)
167
+ requests.post(self.webhook, headers=self.headers, data=json.dumps(error_data))
168
+ return result
169
+
170
+
171
+ if __name__ == '__main__':
172
+ lark_client = FeiShuTalkChatBot(webhook='https://open.feishu.cn/open-apis/bot/v2/hook/bba5b285-ca56-488c-9142-d8bcbcaee3c1')
173
+ #lark_client.success(message='testtesttesttesttesttest', title='测试标题')
@@ -0,0 +1,114 @@
1
+ import hashlib
2
+ import uuid
3
+ from typing import Optional
4
+
5
+ """
6
+ 版本1 vs 版本2
7
+
8
+ 特性 版本1(8位62进制) 版本2(22位62进制)
9
+ 组合数 ~2.18e14(218万亿) ~3.2e35(远超UUID空间)
10
+ 碰撞阈值 14.77百万(50%概率) 5.2e19(理论极高)
11
+ 适用场景 小规模、临时ID 大规模、高唯一性需求
12
+ 性能开销 低 中等(大整数转换)
13
+ 扩展性 不可扩展 支持更高并发
14
+
15
+ 版本2显著优于版本1:通过扩展ID长度至22位,将碰撞概率降至理论安全极限,适合金融、分布式系统等关键场景。
16
+ 推荐实践:结合数据库唯一索引或Redis缓存,实时检测并重试碰撞ID,确保万无一失
17
+ """
18
+
19
+ printable_char = [
20
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
21
+ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w",
22
+ "x", "y", "z",
23
+ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
24
+ "X", "Y", "Z",
25
+ ]
26
+
27
+ """版本2,生成最多 22 位的 ID
28
+ 参考链接:# https://github.com/skorokithakis/shortuuid/tree/master
29
+ 生成思路:根据 UUID3 或者 UUID4 得到的 32 位 16 进制数,转换成 62 进制的数
30
+ """
31
+
32
+
33
+ def get_random_long_id(padding: Optional[int] = 22):
34
+ _tmp_id = get_random_uuid(with_dash=True)
35
+ return _convert_10_2_62(_tmp_id, padding)
36
+
37
+
38
+ def get_fix_long_id(base_str: str, padding: Optional[int] = 22):
39
+ _tmp_id = get_fix_uuid(long_id=base_str, with_dash=True)
40
+ return _convert_10_2_62(_tmp_id, padding)
41
+
42
+
43
+ def _convert_10_2_62(id_16: str, padding: Optional[int]):
44
+ _tmp_id_10 = int(id_16, 16)
45
+
46
+ output = ""
47
+ while _tmp_id_10:
48
+ _tmp_id_10, digit = divmod(_tmp_id_10, 62)
49
+ output += printable_char[digit]
50
+ if padding:
51
+ remainder = max(padding - len(output), 0)
52
+ output += printable_char[0] * remainder
53
+
54
+ return output[::-1]
55
+
56
+
57
+ """版本1,生成 8 位数的短 ID
58
+ 参考链接:https://www.cnblogs.com/shouke/archive/2020/08/02/13423073.html
59
+ 生成思路:根据 UUID3 或者 UUID4 得到的 32 位 16 进制数进行分组,每组 4 个,一共 8 组。对每一组的数字对 62 取余。
60
+ """
61
+
62
+
63
+ def get_random_short_id():
64
+ _tmp_id = get_random_uuid(with_dash=True)
65
+ _buffer = []
66
+ # 循环 8 次,每次取出 4 个
67
+ for i in range(0, 8):
68
+ start = i * 4
69
+ end = i * 4 + 4
70
+ # 将 16 进制的 uuid 转换成 10 进制的数字
71
+ val = int(_tmp_id[start:end], 16)
72
+ _buffer.append(printable_char[val % 62])
73
+ return "".join(_buffer)
74
+
75
+
76
+ def get_fix_short_id(long_id):
77
+ _tmp_id = get_fix_uuid(long_id=long_id, with_dash=True)
78
+ _buffer = []
79
+ for i in range(0, 8):
80
+ start = i * 4
81
+ end = i * 4 + 4
82
+ val = int(_tmp_id[start:end], 16)
83
+ _buffer.append(printable_char[val % 62])
84
+ short_id = "".join(_buffer)
85
+ return short_id
86
+
87
+
88
+ """使用 UUID3 和 UUID4 分别得到固定的和随机的 UUID
89
+ Python 的 UUID3 生成的 ID 是 128 位的十进制数,可以表示为 32 位的十六进制数位。
90
+ Python 的 UUID3 生成的 ID 在给定相同的命名空间和名称的情况下是唯一的,它是通过将命名空间和名称作为输入,应用 MD5 哈希算法生成的。
91
+ 由于 MD5 算法的特性,即使输入的命名空间和名称有微小的变化,生成的 ID 也会有很大的差异,从而保证了唯一性。
92
+ """
93
+
94
+
95
+ def get_random_uuid(with_dash=False):
96
+ if with_dash:
97
+ return str(uuid.uuid4()).replace("-", '')
98
+ else:
99
+ return str(uuid.uuid4())
100
+
101
+
102
+ def get_fix_uuid(long_id, with_dash=False):
103
+ if with_dash:
104
+ return str(uuid.uuid3(uuid.NAMESPACE_DNS, long_id)).replace("-", '')
105
+ else:
106
+ return str(uuid.uuid3(uuid.NAMESPACE_DNS, long_id))
107
+
108
+
109
+ def get_file_md5(filepath):
110
+ with open(filepath, 'rb+') as f:
111
+ md5obj = hashlib.md5()
112
+ md5obj.update(f.read())
113
+ hash_ = md5obj.hexdigest()
114
+ return str(hash_).upper()