py-lmobile-toolkit 1.0.0__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.
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
3
+ """
4
+ py_lmobile - 微网通联短信服务 Python SDK
5
+
6
+ 本模块提供接入微网通联(51welink)短信服务的完整功能,包括:
7
+ - 同步和异步短信发送能力
8
+ - 请求签名认证机制
9
+ - 响应数据模型
10
+ - 工具函数支持
11
+
12
+ 主要组件:
13
+ - Sms: 短信客户端核心类,提供同步和异步发送接口
14
+ - utils: 工具函数模块,包含签名生成、时间戳等功能
15
+ - responses: 响应数据模型,用于标准化API响应
16
+
17
+ 官方 API 文档:https://www.lmobile.cn/ApiPages/index.html
18
+
19
+ 使用示例:
20
+ from py_lmobile.sms import Sms
21
+
22
+ sms = Sms(account_id="your_account", password="your_password", product_id="your_product")
23
+ response = sms.send(phone_nos="13800138000", content="您的验证码是:123456")
24
+ """
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
3
+ """
4
+ 短信客户端模块
5
+
6
+ 本模块提供微网通联短信服务的客户端实现,支持同步和异步两种发送方式。
7
+
8
+ 核心功能:
9
+ - 客户端配置与初始化
10
+ - 同步/异步HTTP请求封装
11
+ - 短信发送接口(支持批量发送)
12
+ - 请求签名生成与认证
13
+
14
+ 官方 API 文档:https://www.lmobile.cn/ApiPages/index.html
15
+
16
+ 使用示例:
17
+ # 同步发送
18
+ sms = Sms(account_id="xxx", password="xxx", product_id="xxx")
19
+ response = sms.send(phone_nos="13800138000", content="测试短信")
20
+
21
+ # 异步发送
22
+ response = await sms.async_send(phone_nos=["13800138000", "13900139000"], content="测试短信")
23
+ """
24
+
25
+ from typing import Optional, Union
26
+
27
+ import httpx
28
+ from py_httpx_toolkit import Httpx
29
+
30
+ from .utils import timestamp, random_digits, sha256_signature
31
+
32
+
33
+ class Sms(Httpx):
34
+ """
35
+ 微网通联短信客户端
36
+
37
+ 提供同步和异步两种方式发送短信,支持请求签名认证机制。
38
+
39
+ 设计架构:
40
+ - 配置层:初始化时配置账号信息、服务器地址等参数
41
+ - 客户端层:提供同步/异步HTTP客户端的创建与管理
42
+ - 请求层:封装底层HTTP请求,支持外部传入客户端实例
43
+ - 业务层:短信发送接口,处理签名生成和参数组装
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ base_url: Optional[str] = "https://api.51welink.com/",
49
+ account_id: Optional[str] = None,
50
+ password: Optional[str] = None,
51
+ product_id: Optional[Union[str, int]] = None,
52
+ smms_encrypt_key: Optional[str] = "SMmsEncryptKey",
53
+ client_kwargs: Optional[dict] = None
54
+ ):
55
+ """
56
+ 初始化短信客户端
57
+
58
+ Args:
59
+ base_url: API服务器地址,默认为微网通联官方地址
60
+ account_id: 账号ID,从微网通联控制台获取
61
+ password: 账号密码,用于签名生成
62
+ product_id: 产品ID,标识具体的短信产品
63
+ smms_encrypt_key: 加密密钥,用于密码的MD5加密盐值
64
+ client_kwargs: 传递给httpx.Client的额外配置参数
65
+
66
+ 配置处理逻辑:
67
+ 1. 处理base_url末尾斜杠,确保统一格式
68
+ 2. 将None值转换为空字符串,避免后续处理异常
69
+ 3. 合并默认客户端配置与用户传入配置
70
+ 4. 默认禁用SSL验证(针对部分内部环境),超时60秒
71
+ """
72
+ # 处理base_url,确保末尾无斜杠
73
+ self.base_url = base_url or ""
74
+ self.base_url = self.base_url[:-1] if self.base_url.endswith("/") else self.base_url
75
+
76
+ # 存储账号信息,None值转换为空字符串
77
+ self.account_id = account_id or ""
78
+ self.password = password or ""
79
+ self.product_id = product_id or ""
80
+ self.smms_encrypt_key = smms_encrypt_key or ""
81
+
82
+ # 合并客户端配置,用户配置可覆盖默认配置
83
+ self.client_kwargs = client_kwargs or {}
84
+ self.client_kwargs = {
85
+ **{
86
+ "base_url": self.base_url,
87
+ "verify": False, # 默认禁用SSL验证
88
+ "timeout": 60, # 默认超时60秒
89
+ },
90
+ **self.client_kwargs,
91
+ }
92
+
93
+ def send(
94
+ self,
95
+ phone_nos: Optional[Union[str, list, tuple]] = None,
96
+ content: Optional[str] = None,
97
+ client: Optional[httpx.Client] = None,
98
+ client_kwargs: Optional[dict] = None,
99
+ **kwargs
100
+ ) -> httpx.Response:
101
+ """
102
+ 同步发送短信
103
+
104
+ 组装短信发送请求参数,生成签名,调用底层HTTP请求发送短信。
105
+
106
+ Args:
107
+ client: 可选的HTTP客户端实例,若不传则自动创建
108
+ phone_nos: 接收短信的手机号码,支持单个字符串或列表/元组
109
+ content: 短信内容
110
+ **kwargs: 传递给httpx.Client.request的额外参数
111
+
112
+ Returns:
113
+ httpx.Response: HTTP响应对象
114
+
115
+ Raises:
116
+ Exception: 发送过程中可能抛出的异常(网络错误、超时等)
117
+
118
+ 请求组装逻辑:
119
+ 1. 处理手机号格式:列表/元组转换为逗号分隔的字符串
120
+ 2. 生成时间戳(毫秒级)用于请求时效性验证
121
+ 3. 生成随机数用于签名唯一性
122
+ 4. 使用SHA256算法生成请求签名
123
+ 5. 组装完整的请求参数并发送
124
+
125
+ 签名生成规则:
126
+ 1. 密码先进行MD5加密(带盐值smms_encrypt_key)
127
+ 2. 按顺序拼接:AccountId、PhoneNos(第一个)、Password(MD5后)、Random、Timestamp
128
+ 3. 使用&连接所有参数
129
+ 4. 对拼接结果进行SHA256加密
130
+ """
131
+ # 处理手机号格式:列表/元组转换为逗号分隔的字符串
132
+ if isinstance(phone_nos, (list, tuple)):
133
+ phone_nos = ",".join(phone_nos)
134
+
135
+ # 生成时间戳和随机数
136
+ ts = timestamp()
137
+ digits = random_digits()
138
+ client = client or None
139
+ client_kwargs = client_kwargs or {}
140
+ # 处理None值,确保kwargs是字典类型
141
+ kwargs = kwargs or {}
142
+
143
+ # 组装请求参数
144
+ kwargs = {
145
+ **{
146
+ "method": "POST",
147
+ "url": "/EncryptionSubmit/SendSms.ashx",
148
+ "data": {
149
+ "AccountID": self.account_id,
150
+ "ProductID": self.product_id,
151
+ "PhoneNos": phone_nos,
152
+ "Timestamp": ts,
153
+ "Random": digits,
154
+ "Content": content,
155
+ "AccessKey": sha256_signature(
156
+ account_id=self.account_id,
157
+ password=self.password,
158
+ phone_nos=phone_nos,
159
+ random_digits=digits,
160
+ timestamp=ts,
161
+ smms_encrypt_key=self.smms_encrypt_key,
162
+ )
163
+ },
164
+ },
165
+ **kwargs
166
+ }
167
+
168
+ # 调用同步请求方法发送短信
169
+ return self.request(client=client, client_kwargs=client_kwargs, **kwargs)
170
+
171
+ async def async_send(
172
+ self,
173
+ phone_nos: Optional[Union[str, list, tuple]] = None,
174
+ content: Optional[str] = None,
175
+ client: Optional[httpx.AsyncClient] = None,
176
+ client_kwargs: Optional[dict] = None,
177
+ **kwargs
178
+ ) -> httpx.Response:
179
+ """
180
+ 异步发送短信
181
+
182
+ 组装短信发送请求参数,生成签名,调用底层异步HTTP请求发送短信。
183
+
184
+ Args:
185
+ client: 可选的异步HTTP客户端实例,若不传则自动创建
186
+ phone_nos: 接收短信的手机号码,支持单个字符串或列表/元组
187
+ content: 短信内容
188
+ **kwargs: 传递给httpx.AsyncClient.request的额外参数
189
+
190
+ Returns:
191
+ httpx.Response: HTTP响应对象
192
+
193
+ Raises:
194
+ Exception: 发送过程中可能抛出的异常(网络错误、超时等)
195
+
196
+ 请求组装逻辑:
197
+ 与同步版本相同,详见send()方法的注释
198
+ """
199
+ # 处理手机号格式:列表/元组转换为逗号分隔的字符串
200
+ if isinstance(phone_nos, (list, tuple)):
201
+ phone_nos = ",".join(phone_nos)
202
+
203
+ # 生成时间戳和随机数
204
+ ts = timestamp()
205
+ digits = random_digits()
206
+ client = client or None
207
+ client_kwargs = client_kwargs or {}
208
+
209
+ # 处理None值,确保kwargs是字典类型
210
+ kwargs = kwargs or {}
211
+
212
+ # 组装请求参数
213
+ kwargs = {
214
+ **{
215
+ "method": "POST",
216
+ "url": "/EncryptionSubmit/SendSms.ashx",
217
+ "data": {
218
+ "AccountID": self.account_id,
219
+ "ProductID": self.product_id,
220
+ "PhoneNos": phone_nos,
221
+ "Timestamp": ts,
222
+ "Random": digits,
223
+ "Content": content,
224
+ "AccessKey": sha256_signature(
225
+ account_id=self.account_id,
226
+ password=self.password,
227
+ phone_nos=phone_nos,
228
+ random_digits=digits,
229
+ timestamp=ts,
230
+ smms_encrypt_key=self.smms_encrypt_key,
231
+ )
232
+ },
233
+ },
234
+ **kwargs
235
+ }
236
+
237
+ # 调用异步请求方法发送短信
238
+ return await self.async_request(client=client, client_kwargs=client_kwargs, **kwargs)
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
3
+ """
4
+ 响应模型模块
5
+
6
+ 本模块定义了微网通联API响应的数据模型,使用Pydantic进行数据验证和序列化。
7
+
8
+ 核心模型:
9
+ - Base: 所有响应的基类,包含Result字段
10
+ - Success: 成功响应模型,Result字段值固定为"succ"
11
+
12
+ 设计说明:
13
+ - 使用Pydantic BaseModel作为基类,提供自动数据验证
14
+ - 支持额外字段(extra="allow"),兼容API返回的未知字段
15
+ - 使用Field描述字段的标题和说明,便于生成文档
16
+
17
+ 官方 API 文档:https://www.lmobile.cn/ApiPages/index.html
18
+ """
19
+
20
+ from pydantic import BaseModel, Field
21
+ from typing import Literal, Union, Any, Optional
22
+
23
+
24
+ class Base(BaseModel):
25
+ """
26
+ 微网通联API响应基类
27
+
28
+ 所有API响应都继承自此基类,包含统一的错误码字段。
29
+
30
+ 字段说明:
31
+ Result: 错误码字段,用于标识请求处理结果
32
+ - "succ" 表示成功
33
+ - 其他值表示失败,具体含义参考API文档
34
+
35
+ 配置说明:
36
+ extra: "allow" - 允许响应中包含模型未定义的额外字段
37
+ 这是因为API可能返回一些未在文档中明确说明的字段
38
+ """
39
+ Result: str = Field(..., title="错误码", description="错误码,succ表示成功,其他表示失败")
40
+
41
+ model_config = {
42
+ "extra": "allow" # 允许额外动态字段,兼容API返回的扩展字段
43
+ }
44
+
45
+
46
+ class Success(Base):
47
+ """
48
+ 成功响应模型
49
+
50
+ 继承自Base,专门用于表示API请求成功的响应。
51
+
52
+ 字段约束:
53
+ Result: 固定为"succ",使用Literal类型进行严格类型约束
54
+
55
+ 使用场景:
56
+ - 验证响应是否为成功状态
57
+ - 作为响应数据的标准化容器
58
+ - 与utils中的build_success_instance函数配合使用
59
+
60
+ 代码演示:
61
+ >>> from py_lmobile_toolkit.sms.responses import Success
62
+ >>> result = Success(Result="succ")
63
+ >>> print(result.Result)
64
+ 'succ'
65
+ >>> result.model_dump()
66
+ {'Result': 'succ'}
67
+ """
68
+ Result: Literal["succ"] = Field(..., title="错误码", description="成功,值为succ")
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
3
+ """
4
+ 工具函数模块
5
+
6
+ 本模块提供短信服务所需的辅助工具函数,包括:
7
+ - JSON数据处理工具
8
+ - 时间戳生成
9
+ - 随机数生成
10
+ - SHA256签名生成
11
+ - 响应模型转换
12
+ - JSON Schema校验
13
+
14
+ 这些工具函数被Sms客户端内部使用,也可单独导出供外部使用。
15
+
16
+ 官方 API 文档:https://www.lmobile.cn/ApiPages/index.html
17
+ """
18
+
19
+ import hashlib
20
+ import random
21
+ import string
22
+ from datetime import datetime
23
+ from typing import Any, Optional, Union
24
+
25
+ import httpx
26
+ from jsonpath_ng import parse
27
+ from jsonschema.validators import Draft202012Validator
28
+
29
+ from .responses import Success
30
+
31
+
32
+ def json_find_first(expression: str, data: Any) -> Any:
33
+ """
34
+ 使用 JSONPath 表达式从数据中查找第一个匹配项
35
+
36
+ JSONPath是一种用于在JSON数据中定位和提取信息的查询语言,
37
+ 类似于XPath用于XML文档的查询。
38
+
39
+ Args:
40
+ expression: JSONPath表达式,用于指定要查找的数据路径
41
+ data: 待查询的JSON数据(字典或列表)
42
+
43
+ Returns:
44
+ Any: 第一个匹配的值,如果没有匹配项则返回None
45
+
46
+ 支持的JSONPath语法示例:
47
+ - $.key: 获取根节点下的key字段
48
+ - $..key: 递归查找所有名为key的字段
49
+ - $.list[0]: 获取列表的第一个元素
50
+ - $.list[*].name: 获取列表所有元素的name字段
51
+
52
+ 代码演示:
53
+ >>> data = {"store": {"books": [{"title": "Python"}, {"title": "Java"}]}}
54
+ >>> json_find_first("$.store.books[0].title", data)
55
+ 'Python'
56
+ """
57
+ # 使用jsonpath_ng解析表达式并查找匹配项
58
+ results = [i.value for i in parse(expression).find(data)]
59
+
60
+ # 返回第一个匹配结果,若无匹配则返回None
61
+ if isinstance(results, list) and len(results) > 0:
62
+ return results[0]
63
+ return None
64
+
65
+
66
+ def json_is_valid(schema: Optional[dict], data: Any) -> bool:
67
+ """
68
+ 校验 JSON 数据是否符合指定的 JSON Schema
69
+
70
+ JSON Schema是一种用于定义JSON数据结构的规范,可用于数据验证、
71
+ 文档生成和代码生成等场景。本函数使用Draft2020-12版本的规范。
72
+
73
+ Args:
74
+ schema: JSON Schema字典,定义了数据的结构约束
75
+ data: 待校验的JSON数据
76
+
77
+ Returns:
78
+ bool: 校验是否成功,True表示符合Schema,False表示不符合
79
+
80
+ 代码演示:
81
+ >>> schema = {"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}
82
+ >>> json_is_valid(schema, {"name": "test"})
83
+ True
84
+ >>> json_is_valid(schema, {"age": 18})
85
+ False
86
+ """
87
+ # 使用Draft2020-12版本的Validator进行校验
88
+ return Draft202012Validator(schema).is_valid(data)
89
+
90
+
91
+ def timestamp() -> int:
92
+ """
93
+ 生成当前时间戳(毫秒)
94
+
95
+ 返回当前时间距离1970年1月1日00:00:00 UTC的毫秒数,
96
+ 用于请求的时效性验证和签名生成。
97
+
98
+ Returns:
99
+ int: 当前时间戳(毫秒)
100
+
101
+ 设计说明:
102
+ - 使用datetime.now().timestamp()获取秒级时间戳
103
+ - 乘以1000转换为毫秒
104
+ - 使用int()取整,确保返回整数类型
105
+
106
+ 代码演示:
107
+ >>> ts = timestamp()
108
+ >>> print(ts) # 输出示例: 1630000000000
109
+ >>> isinstance(ts, int)
110
+ True
111
+ """
112
+ return int((datetime.now().timestamp() * 1000))
113
+
114
+
115
+ def random_digits(length: int = 10) -> int:
116
+ """
117
+ 生成指定长度的随机数字
118
+
119
+ 从数字字符集'0123456789'中随机选取指定长度的字符,
120
+ 拼接后转换为整数返回。
121
+
122
+ Args:
123
+ length: 随机数的长度,默认为10位
124
+
125
+ Returns:
126
+ int: 指定长度的随机数字
127
+
128
+ 实现细节:
129
+ - 使用random.sample()确保不会出现重复字符
130
+ - 使用string.digits获取数字字符集
131
+ - 转换为整数而非字符串,便于后续计算和比较
132
+
133
+ 代码演示:
134
+ >>> digits = random_digits(6)
135
+ >>> print(digits) # 输出示例: 123456
136
+ >>> len(str(digits))
137
+ 6
138
+ """
139
+ # 从数字字符集中随机选取length个字符,拼接后转换为整数
140
+ return int("".join(random.sample(string.digits, length)))
141
+
142
+
143
+ def sha256_signature(
144
+ account_id: str = "",
145
+ password: str = "",
146
+ phone_nos: str = "",
147
+ random_digits: Optional[Union[int, str]] = "",
148
+ timestamp: Optional[Union[int, str]] = "",
149
+ smms_encrypt_key: str = ""
150
+ ) -> str:
151
+ """
152
+ 生成SHA256签名
153
+
154
+ 按照微网通联API要求的格式生成签名,用于短信发送请求的认证。
155
+
156
+ 签名生成算法(严格按照API要求的顺序):
157
+ 1. 密码MD5加密:password + smms_encrypt_key -> MD5 -> 转大写
158
+ 2. 参数拼接:AccountId={account_id}&PhoneNos={first_phone}&Password={md5_password}&Random={random}&Timestamp={timestamp}
159
+ 3. SHA256加密:对拼接后的字符串进行SHA256哈希
160
+
161
+ Args:
162
+ account_id: 账号ID
163
+ password: 原始密码
164
+ phone_nos: 手机号,多个手机号用逗号分隔(签名时只取第一个)
165
+ random_digits: 随机数字(由random_digits()生成)
166
+ timestamp: 时间戳(由timestamp()生成)
167
+ smms_encrypt_key: 加密密钥,作为MD5加密的盐值
168
+
169
+ Returns:
170
+ str: SHA256签名结果(64位十六进制字符串)
171
+
172
+ 代码演示:
173
+ >>> signature = sha256_signature(
174
+ ... account_id="dljtwy00",
175
+ ... password="g07KjuLN1",
176
+ ... phone_nos="13800138000",
177
+ ... random_digits="1234567890",
178
+ ... timestamp="1630000000000",
179
+ ... smms_encrypt_key="SMmsEncryptKey"
180
+ ... )
181
+ >>> len(signature)
182
+ 64
183
+ """
184
+ # 步骤1:对密码进行MD5加密,使用加密密钥作为盐值
185
+ # 格式:MD5(password + smms_encrypt_key),结果转大写
186
+ password_md5 = hashlib.md5(f"{password}{smms_encrypt_key}".encode('utf-8')).hexdigest()
187
+
188
+ # 步骤2:按照API要求的顺序拼接参数
189
+ # 注意:PhoneNos只取第一个手机号用于签名
190
+ temp_string = "&".join([
191
+ f"AccountId={account_id}",
192
+ f"PhoneNos={phone_nos.split(',')[0]}", # 只取第一个手机号
193
+ f"Password={password_md5.upper()}", # MD5结果转大写
194
+ f"Random={random_digits}",
195
+ f"Timestamp={timestamp}",
196
+ ])
197
+
198
+ # 步骤3:对拼接后的字符串进行SHA256加密
199
+ return hashlib.sha256(temp_string.encode("utf-8")).hexdigest()
200
+
201
+
202
+ def build_success_instance(response: Union[httpx.Response, dict]) -> Success:
203
+ """
204
+ 将 HTTP 响应或字典转换为 Success 模型对象
205
+
206
+ 统一处理httpx.Response对象和字典,将其转换为标准化的Success响应模型,
207
+ 便于后续的响应数据处理和校验。
208
+
209
+ Args:
210
+ response: HTTP响应对象或字典数据
211
+
212
+ Returns:
213
+ Success: 标准化的响应模型对象,包含Result字段
214
+
215
+ 处理逻辑:
216
+ - 如果输入是httpx.Response对象,先调用json()方法解析响应体
217
+ - 如果输入是字典,直接传入Success构造函数
218
+
219
+ 代码演示:
220
+ >>> response = httpx.Response(200, json={"Result": "succ"})
221
+ >>> result = build_success_instance(response)
222
+ >>> print(result.Result)
223
+ 'succ'
224
+ >>> isinstance(result, Success)
225
+ True
226
+ """
227
+ if isinstance(response, httpx.Response):
228
+ return Success(**response.json())
229
+ return Success(**response)
230
+
231
+
232
+ def success__is_valid(
233
+ response: Union[httpx.Response, dict] = None,
234
+ schema: dict = {
235
+ "type": "object",
236
+ "properties": {
237
+ "Result": {
238
+ "oneOf": [
239
+ {"type": "string", "const": "succ"}
240
+ ]
241
+ },
242
+ },
243
+ "required": ["Result"]
244
+ }
245
+ ):
246
+ """
247
+ 校验 HTTP 响应或字典是否符合成功响应的 JSON Schema
248
+
249
+ 用于验证API响应是否为预期的成功格式,确保响应数据的正确性。
250
+
251
+ Args:
252
+ response: HTTP响应对象或字典数据
253
+ schema: JSON Schema字典,默认为成功响应的Schema
254
+
255
+ Returns:
256
+ bool: 校验是否成功,True表示符合Schema,False表示不符合
257
+
258
+ 默认Schema说明:
259
+ - 必须是对象类型
260
+ - 必须包含Result字段
261
+ - Result字段的值必须是字符串"succ"
262
+
263
+ 代码演示:
264
+ >>> response = httpx.Response(200, json={"Result": "succ"})
265
+ >>> success__is_valid(response)
266
+ True
267
+ >>> success__is_valid({"Result": "fail"})
268
+ False
269
+ """
270
+ # 根据输入类型选择不同的处理方式
271
+ if isinstance(response, httpx.Response):
272
+ return json_is_valid(schema, response.json())
273
+ return json_is_valid(schema, response)
@@ -0,0 +1,326 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-lmobile-toolkit
3
+ Version: 1.0.0
4
+ Summary: 微网通联(51welink)短信服务 Python SDK,提供同步和异步两种发送方式,支持请求签名认证
5
+ Author-email: Guolei <174000902@qq.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 郭磊
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://gitee.com/guolei19850528/py_lmobile_toolkit
29
+ Project-URL: Repository, https://gitee.com/guolei19850528/py_lmobile_toolkit.git
30
+ Project-URL: Documentation, https://www.lmobile.cn/ApiPages/index.html
31
+ Keywords: lmobile,python,client,api,微网通联,短信,sms,51welink
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Operating System :: OS Independent
41
+ Classifier: Topic :: Communications
42
+ Classifier: Topic :: Communications :: Telephony
43
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
44
+ Requires-Python: >=3.10
45
+ Description-Content-Type: text/markdown
46
+ License-File: LICENSE
47
+ Requires-Dist: httpx>=0.27.0
48
+ Requires-Dist: pydantic>=2.0
49
+ Requires-Dist: jsonpath-ng>=1.5.3
50
+ Requires-Dist: jsonschema>=4.21.0
51
+ Requires-Dist: py-httpx-toolkit>=1.0.1
52
+ Dynamic: license-file
53
+
54
+ # py-lmobile-toolkit
55
+
56
+ 微网通联(51welink)短信服务 Python SDK,提供同步和异步两种发送方式,支持请求签名认证机制。
57
+
58
+ ## 作者
59
+
60
+ **Guolei**
61
+
62
+ - 邮箱:174000902@qq.com
63
+
64
+ ## 项目主页
65
+
66
+ - **Gitee**: https://gitee.com/guolei19850528/py_lmobile_toolkit
67
+ - **官方 API 文档**: https://www.lmobile.cn/ApiPages/index.html
68
+
69
+ ## 功能特性
70
+
71
+ - ✅ 同步短信发送接口
72
+ - ✅ 异步短信发送接口
73
+ - ✅ 请求签名认证机制(SHA256)
74
+ - ✅ 响应数据模型验证(Pydantic)
75
+ - ✅ 完整的工具函数支持
76
+
77
+ ## 安装
78
+
79
+ 使用 `uv` 安装:
80
+
81
+ ```bash
82
+ uv add py-lmobile-toolkit
83
+ ```
84
+
85
+ 或从pip安装:
86
+
87
+ ```bash
88
+ pip install py-lmobile-toolkit
89
+ ```
90
+
91
+ ## 依赖包
92
+
93
+ | 依赖 | 版本要求 | 说明 |
94
+ |------|----------|------|
95
+ | httpx | >=0.27.0 | HTTP 客户端 |
96
+ | pydantic | >=2.0 | 数据验证 |
97
+ | jsonpath-ng | >=1.5.3 | JSONPath 查询 |
98
+ | jsonschema | >=4.21.0 | JSON Schema 校验 |
99
+ | py-httpx-toolkit | >=1.0.1 | HTTP 工具封装 |
100
+
101
+ ## API 接口
102
+
103
+ ### Sms 类
104
+
105
+ 短信客户端核心类,提供同步和异步发送接口。
106
+
107
+ #### 初始化
108
+
109
+ ```python
110
+ from py_lmobile_toolkit.sms import Sms
111
+
112
+ sms = Sms(
113
+ account_id="your_account",
114
+ password="your_password",
115
+ product_id="your_product"
116
+ )
117
+ ```
118
+
119
+ **参数说明**:
120
+
121
+ | 参数 | 类型 | 默认值 | 说明 |
122
+ |------|------|--------|------|
123
+ | base_url | str | https://api.51welink.com/ | API 服务器地址 |
124
+ | account_id | str | None | 账号 ID |
125
+ | password | str | None | 账号密码 |
126
+ | product_id | str/int | None | 产品 ID |
127
+ | smms_encrypt_key | str | SMmsEncryptKey | 加密密钥 |
128
+ | client_kwargs | dict | None | httpx.Client 配置 |
129
+
130
+ #### 同步发送
131
+
132
+ ```python
133
+ response = sms.send(
134
+ phone_nos="13800138000",
135
+ content="您的验证码是:123456"
136
+ )
137
+ ```
138
+
139
+ #### 异步发送
140
+
141
+ ```python
142
+ response = await sms.async_send(
143
+ phone_nos=["13800138000", "13900139000"],
144
+ content="测试短信"
145
+ )
146
+ ```
147
+
148
+ **参数说明**:
149
+
150
+ | 参数 | 类型 | 说明 |
151
+ |------|------|------|
152
+ | phone_nos | str/list/tuple | 手机号码,支持单个或多个 |
153
+ | content | str | 短信内容 |
154
+ | client | httpx.Client/AsyncClient | 可选的客户端实例 |
155
+ | client_kwargs | dict | 客户端配置 |
156
+
157
+ ## Utils 工具函数
158
+
159
+ ### 时间戳生成
160
+
161
+ ```python
162
+ from py_lmobile_toolkit.sms.utils import timestamp
163
+
164
+ ts = timestamp() # 返回毫秒级时间戳
165
+ ```
166
+
167
+ ### 随机数生成
168
+
169
+ ```python
170
+ from py_lmobile_toolkit.sms.utils import random_digits
171
+
172
+ digits = random_digits(length=10) # 生成10位随机数
173
+ ```
174
+
175
+ ### SHA256 签名生成
176
+
177
+ ```python
178
+ from py_lmobile_toolkit.sms.utils import sha256_signature
179
+
180
+ signature = sha256_signature(
181
+ account_id="dljtwy00",
182
+ password="g07KjuLN1",
183
+ phone_nos="13800138000",
184
+ random_digits="1234567890",
185
+ timestamp="1630000000000",
186
+ smms_encrypt_key="SMmsEncryptKey"
187
+ )
188
+ ```
189
+
190
+ ### JSONPath 查询
191
+
192
+ ```python
193
+ from py_lmobile_toolkit.sms.utils import json_find_first
194
+
195
+ data = {"store": {"books": [{"title": "Python"}, {"title": "Java"}]}}
196
+ title = json_find_first("$.store.books[0].title", data) # 返回 "Python"
197
+ ```
198
+
199
+ ### JSON Schema 校验
200
+
201
+ ```python
202
+ from py_lmobile_toolkit.sms.utils import json_is_valid
203
+
204
+ schema = {"type": "object", "properties": {"name": {"type": "string"}}}
205
+ valid = json_is_valid(schema, {"name": "test"}) # 返回 True
206
+ ```
207
+
208
+ ### 响应模型转换
209
+
210
+ ```python
211
+ from py_lmobile_toolkit.sms.utils import build_success_instance
212
+
213
+ result = build_success_instance(response) # 转换为 Success 模型
214
+ ```
215
+
216
+ ## 响应模型
217
+
218
+ ### Base
219
+
220
+ 所有响应的基类:
221
+
222
+ ```python
223
+ from py_lmobile_toolkit.sms.responses import Base
224
+
225
+ class Base(BaseModel):
226
+ Result: str # 错误码,succ 表示成功
227
+ ```
228
+
229
+ ### Success
230
+
231
+ 成功响应模型:
232
+
233
+ ```python
234
+ from py_lmobile_toolkit.sms.responses import Success
235
+
236
+ result = Success(Result="succ")
237
+ ```
238
+
239
+ ## 使用示例
240
+
241
+ ### 同步发送示例
242
+
243
+ ```python
244
+ from py_lmobile_toolkit.sms import Sms
245
+
246
+ # 初始化客户端
247
+ sms = Sms(
248
+ account_id="your_account",
249
+ password="your_password",
250
+ product_id="your_product"
251
+ )
252
+
253
+ # 发送单条短信
254
+ response = sms.send(
255
+ phone_nos="13800138000",
256
+ content="您的验证码是:123456"
257
+ )
258
+
259
+ # 解析响应
260
+ data = response.json()
261
+ print(f"发送结果: {data.get('Result')}")
262
+ ```
263
+
264
+ ### 异步发送示例
265
+
266
+ ```python
267
+ import asyncio
268
+ from py_lmobile_toolkit.sms import Sms
269
+
270
+ async def main():
271
+ # 初始化客户端
272
+ sms = Sms(
273
+ account_id="your_account",
274
+ password="your_password",
275
+ product_id="your_product"
276
+ )
277
+
278
+ # 批量发送短信
279
+ response = await sms.async_send(
280
+ phone_nos=["13800138000", "13900139000", "13700137000"],
281
+ content="【通知】您的订单已发货"
282
+ )
283
+
284
+ # 解析响应
285
+ data = response.json()
286
+ print(f"发送结果: {data.get('Result')}")
287
+
288
+ asyncio.run(main())
289
+ ```
290
+
291
+ ### 签名验证示例
292
+
293
+ ```python
294
+ from py_lmobile_toolkit.sms.utils import sha256_signature, timestamp, random_digits
295
+
296
+ # 生成签名所需参数
297
+ ts = timestamp()
298
+ digits = random_digits()
299
+
300
+ # 生成签名
301
+ signature = sha256_signature(
302
+ account_id="your_account",
303
+ password="your_password",
304
+ phone_nos="13800138000",
305
+ random_digits=digits,
306
+ timestamp=ts,
307
+ smms_encrypt_key="SMmsEncryptKey"
308
+ )
309
+
310
+ print(f"生成的签名: {signature}")
311
+ ```
312
+
313
+ ## 签名生成算法
314
+
315
+ 签名生成遵循以下步骤:
316
+
317
+ 1. **密码 MD5 加密**:`MD5(password + smms_encrypt_key)`,结果转大写
318
+ 2. **参数拼接**(按顺序):
319
+ ```
320
+ AccountId={account_id}&PhoneNos={first_phone}&Password={md5_password}&Random={random}&Timestamp={timestamp}
321
+ ```
322
+ 3. **SHA256 加密**:对拼接后的字符串进行 SHA256 哈希
323
+
324
+ ## 许可证
325
+
326
+ MIT License
@@ -0,0 +1,9 @@
1
+ py_lmobile_toolkit/__init__.py,sha256=BMXppKx35yJaI1CLgvFtEotzOEheeZmTAwSNTTIpOfE,813
2
+ py_lmobile_toolkit/sms/__init__.py,sha256=ObWMMFmjMcXjuhlxR-jB5SZ9fTf4FCWWc2U4WucZS18,8845
3
+ py_lmobile_toolkit/sms/responses.py,sha256=3FR5JuJcq7KGZ3074P3BgZYO14TfPXYfkjsXHgJqask,2203
4
+ py_lmobile_toolkit/sms/utils.py,sha256=-tYocYe_AUH-hdg2cuDHGDnC-kaXCZboPx4lsgrdHFI,8975
5
+ py_lmobile_toolkit-1.0.0.dist-info/licenses/LICENSE,sha256=HazFJNaQP3rbY1dvg3hAmO-FZ6v7DwM0YHwXvWsGG3A,1063
6
+ py_lmobile_toolkit-1.0.0.dist-info/METADATA,sha256=QyPZFtNTBH2QnAmoC83CNN3gf4KBl1oEDFqySG7UAp0,8268
7
+ py_lmobile_toolkit-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
8
+ py_lmobile_toolkit-1.0.0.dist-info/top_level.txt,sha256=cOZV6kg2o6nx4OrH7Qs3zP_Svy1zijgBt0xN9RuBb3A,19
9
+ py_lmobile_toolkit-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 郭磊
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ py_lmobile_toolkit