从原始API到SDK:手机号归属地查询工具化封装实践

📅 2026/7/30 13:47:39
从原始API到SDK:手机号归属地查询工具化封装实践
场景导入为什么需要封装一层在日常开发中我们经常需要根据手机号判断用户所在省份、运营商用于风控、营销、客服分配等场景。直接调用原始API虽然快速但若多个业务模块散落地发起请求会造成鉴权混乱、重复报错、缺乏统一回退策略。因此将API调用封装成内部工具类SDK是工程化的必要步骤。本文以手机号归属地查询API为例演示从接口分析到封装完成的全流程。能力边界接口支持什么不支持什么该API仅支持11位中国大陆手机号严格匹配正则^1[3-9]\\d{9}$覆盖移动/联通/电信主流号段及部分虚拟运营商号段如170/171/174等。若输入非法号码如少于11位或首位非1直接返回错误码4000。若合法但号段未被收录如新放号段则返回is_foundfalse其他字段为空——注意这不是错误业务层可通过此标志决定是否使用其他渠道或暂存为“未知”。请务必知晓API不会返回具体的区号或邮政编码仅提供省份和运营商且结果数据基于公开号段库不支持实时查询SIM卡状态或位置。缓存策略为成功结果7天、未查询到结果1小时适用于号段相对稳定的特性。接口参数与鉴权方式请求方式GET请求地址https://v1.apizero.cn/api/mobileQuery参数参数名必填类型说明示例值mobile是string11位中国大陆手机号13800138000Header参数参数名必填类型说明示例值Authorization否stringAPI Key鉴权头格式Bearer sk_live_xxx匿名调用每日50次Bearer sk_live_xxxxxxxxxxxxxx注意文档中同时提到X-API-Key头方式实际以最新文档为准。若你使用匿名调用可不传Header但需注意每日额度。建议正式项目申请API Key并放入环境变量。可复制的curl示例以下命令可直接在终端执行需将YOUR_API_KEY替换为真实Key或省略Header使用匿名模式curl -sS \ -H Authorization: Bearer YOUR_API_KEY \ https://v1.apizero.cn/api/mobile?mobile13800138000若使用匿名调用curl -sS \ https://v1.apizero.cn/api/mobile?mobile13800138000成功响应示例JSON格式{ code: 0, data: { carrier: 中国移动, is_found: true, mobile: 13800138000, province: 北京 }, msg: 成功, request_id: abc123def456 }代码接入用Python封装一个查询函数1. 基础调用无缓存import requests def query_mobile(mobile: str, api_key: str None) - dict: 查询手机号归属地 :param mobile: 11位手机号 :param api_key: API Key可为None使用匿名 :return: 解析后的data字典若错误则抛出异常 url https://v1.apizero.cn/api/mobile params {mobile: mobile} headers {} if api_key: headers[Authorization] fBearer {api_key} resp requests.get(url, paramsparams, headersheaders, timeout10) resp.raise_for_status() # 非2XX抛出HTTPError json_data resp.json() if json_data.get(code) ! 0: raise RuntimeError(fAPI错误: {json_data.get(msg)}) return json_data[data]2. 异常与边界处理实际生产环境中还需要处理网络超时或连接失败返回状态码非200如429限流502网关错误响应JSON解析异常手机号格式校验前置拦截无效请求下面是一个更健壮的版本import re def safe_query_mobile(mobile: str, api_key: str None) - dict: # 1. 手机号正则校验 if not re.match(r^1[3-9]\\d{9}$, mobile): raise ValueError(f无效手机号格式: {mobile}) # 2. 带重试的请求指数退避 import time max_retries 3 for attempt in range(1, max_retries 1): try: data query_mobile(mobile, api_key) return data except requests.exceptions.RequestException as e: if attempt max_retries: raise wait 2 ** attempt print(f请求失败{wait}秒后重试...) time.sleep(wait)返回值解读与错误码含义成功响应code0时data字段如下字段类型说明mobilestring原始手机号provincestring归属省份如北京carrierstring运营商名称如中国移动is_foundbooleantrue表示成功查询到数据当code ! 0时常见错误码错误码含义处理建议4000非法手机号非11位或首位非1检查输入校验正则是否正确4001参数缺失或格式错误确认请求URL带正确query403鉴权失败Key无效或已过期检查Authorization头格式429请求次数超限加入限流机制降低调用频率500服务端内部错误等待并重试若持续可反馈注意若is_foundfalse但code0属于正常情况号段未收录业务层应视作“未知”而非错误。工程化注意事项封装工具类的核心策略1. 缓存策略由于号段分配是静态的成功结果缓存7天完全合理。未查询到的结果缓存1小时避免反复请求同一未收录号段。实现时可用内存缓存如functools.lru_cache或外部缓存Redis。下面是一个带TTL的简单缓存示例from datetime import datetime, timedelta class MobileCache: def __init__(self): self._store {} # key: mobile, value: (timestamp, data) def get(self, mobile: str): entry self._store.get(mobile) if not entry: return None cached_time, data entry # 根据是否查到决定TTL ttl timedelta(days7) if data.get(is_found) else timedelta(hours1) if datetime.now() - cached_time ttl: del self._store[mobile] return None return data def set(self, mobile: str, data: dict): self._store[mobile] (datetime.now(), data)2. 日志脱敏错误日志中不应输出完整手机号避免隐私泄露。可使用masked_mobile mobile[:3] **** mobile[-4:]。3. 限流与并发控制API QPS为10/s若业务瞬间并发较高应使用信号量或令牌桶限制实际请求速率。例如import threading class RateLimiter: def __init__(self, max_qps10): self._lock threading.Lock() self._last_request 0.0 self._interval 1.0 / max_qps def wait(self): with self._lock: now time.time() if now - self._last_request self._interval: sleep_time self._interval - (now - self._last_request) time.sleep(sleep_time) self._last_request time.time()4. 幂等与重试策略查询API是幂等的但网络抖动可能导致失败。推荐采用指数退避重试最多3次并记录request_id到日志中用于排查。5. 统一错误封装不要将原始错误暴露给业务调用方而是定义内部异常类class MobileQueryError(Exception): def __init__(self, code: int, msg: str): self.code code self.msg msg这样业务层只需 catch 该异常即可。完整工具类代码片段将上述思想合并成一个类省略部分细节class MobileLookup: def __init__(self, api_key: str None, max_qps: int 10): self._api_key api_key self._cache MobileCache() self._rate_limiter RateLimiter(max_qps) def lookup(self, mobile: str) - dict: # 1. 从缓存获取 cached self._cache.get(mobile) if cached: return cached # 2. 限流等待 self._rate_limiter.wait() # 3. 请求API带重试 data safe_query_mobile(mobile, self._api_key) # 4. 写缓存 self._cache.set(mobile, data) # 5. 返回 return data常见问题排查收到4000错误检查mobile参数是否包含空格或非数字字符且长度是否为11。收到403错误检查Authorization头格式是否为Bearer sk_live_...注意Bearer后有空格。is_found false并非错误应检查输入的手机号是否属于最新号段例如175/176等。可先通过其他途径验证。响应时间过长或超时检查本地网络是否能访问外网或是否被防火墙拦截。可尝试在命令行执行curl测试。参考文档手机号归属地API文档https://apizero.cn/aidocs/mobile原始文档rawhttps://apizero.cn/aidocs/mobile/raw.md