Metadata-Version: 2.4
Name: lkd_pro
Version: 0.3.1
Summary: 自动注册和认证
Home-page: https://github.com/songhang/lkddb_pro
Author: songhang
Author-email: 1026536804@qq.com
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: pywin32>=311
Requires-Dist: Pillow>=10.0.0
Requires-Dist: requests>=2.32.0
Requires-Dist: cachetools>=5.0.0
Requires-Dist: pandas>=2.0.0
Requires-Dist: openpyxl>=3.1.0
Requires-Dist: psutil>=5.9.0
Requires-Dist: opencv-python>=4.8.0
Requires-Dist: pydirectinput>=1.0.4
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# LKD Python SDK

[![Version](https://img.shields.io/badge/version-0.2.0-blue.svg)](https://pypi.org/project/lkd/)
[![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

## 📖 项目简介

LKD 是电商商品数据处理与自动化上传系统。提供 Excel 表格与图片批量匹配、数据清洗、API 上传、错误日志异步上报等一站式解决方案，帮助电商企业高效管理商品数据。

### 核心特性

- 🔐 **自动认证**: 自动注册/登录、Token 自动刷新、401 自动恢复
- 🔒 **安全存储**: SQLite + DPAPI 字段加密保存账号与 token，流程重启后直接复用

---

## 📦 快速开始

### 1. 安装依赖

```bash
pip install -r requirements.txt
```

或使用 pip 安装（如已发布）：

```bash
pip install lkd_pro
```

### 2. 初始化配置

服务器地址通过 `configure_server()` 显式传入，不再依赖全局变量，也不要求导入顺序：

```python
from lkd.settings.initialization import default as init_default

# 执行完整初始化流程：
#   全局变量 → 日志目录 → 服务器地址/推荐码配置 → 首次认证 → 自动刷新线程
# 首次认证是同步的（库中 token 有效时不会产生网络请求）；
# 本机若还没有账号，会自动注册，此时必须传入 referral_code。
# 本地地址优先，未配置时使用远程地址。
success = init_default(
    local_ip="192.168.2.28:8000",       # 内网服务器
    # remote_ip="https://api.example.com/api",  # 外网服务器（可选）
    referral_code="your-referral-code",  # 推荐码（首次运行注册必填）
)
if success:
    print("LKD 初始化完成")
else:
    print("初始化失败：按日志提示处理后重试")
```

也可以单独配置（例如在初始化流程之外修改地址）：

```python
from lkd.settings import configure_server

configure_server(
    local_ip="192.168.2.28:8000",
    remote_ip="https://api.example.com/api",
)
```

### 3. 使用示例

#### 用户认证

```python
from lkd.api import login, register

# 登录（密码默认使用设备 ID 后 6 位）
result = login("your_password")
if result["success"]:
    print("登录成功")
    access_token = result["data"]["access_token"]
else:
    # 登录失败可尝试注册（推荐码必填）
    register_result = register(
        "your_password", referral_code="your-referral-code"
    )
```

#### 商品上传

```python
from lkd.api import send_products

product_data = {
    "title": "商品名称",
    "price": 99.99,
    "stock": 100,
    "shop_id": "shop_001",
}

result = send_products(product_data)
if result["success"]:
    print("商品上传成功")
else:
    print(f"上传失败：{result['error']}")
```

#### 获取任务列表

```python
from lkd.api import get_task

result = get_task(status="pending", limit=10)
if result["success"]:
    tasks = result["data"]["tasks"]
    for task in tasks:
        print(f"任务 ID: {task['id']}, 状态：{task['status']}")
```

#### 错误日志上报

```python
from lkd.logs.err_logs import send_logs

send_logs(
    task="task_001",
    error_message="商品上传失败：库存不足",
    screenshot_path="error.png"
)
```

---

## 📁 目录结构

```
lkd/
├── api/                  # API 接口与数据上传
│   ├── __init__.py
│   └── api.py            # 所有 API 接口封装
├── auth/                 # 认证凭据存储
│   ├── __init__.py
│   ├── crypto.py         # DPAPI 字段级加密
│   └── store.py          # SQLite 账号 / token 存储
├── cache/                # 内存热缓存
│   ├── __init__.py
│   └── local_cache.py    # 内存缓存 (CACHE)
├── client/               # HTTP 客户端
│   ├── __init__.py
│   ├── client.py         # 请求客户端 (CPRequest)
│   └── request_*.py      # 请求对象封装 (BaseRequest 子类)
├── env/                  # 环境与设备信息
│   ├── __init__.py
│   └── device_info.py    # 设备唯一标识生成
├── human/                # 人工交互工具
├── logs/                 # 日志系统
│   ├── __init__.py       # 日志配置
│   └── err_logs.py       # 错误日志上报
├── mwin/                 # Windows 自动化
├── settings/             # 配置管理
│   ├── __init__.py
│   ├── settings.py       # 系统配置 (单例模式)
│   └── initialization.py # 初始化流程
├── util/                 # 通用工具
│   ├── file/             # 文件操作工具
│   │   ├── file.py       # 文件管理
│   │   ├── folder.py     # 文件夹管理
│   │   └── path.py       # 路径管理
│   └── up_c.py           # Excel 上传主流程
├── var/                  # 全局变量管理
│   └── variables.py      # GlobalVariables 单例
├── __init__.py           # 包入口
└── test.py               # 测试与初始化工具
```

---

## 🔧 架构规范

### 设计模式

#### 单例模式

核心模块必须使用单例模式，确保全局唯一实例：

- `Settings` - 系统配置管理器
- `GlobalVariables` - 全局变量管理器
- `CacheManager` - 缓存管理器
- `DeviceInfo` - 设备信息生成器

```python
# Settings 使用示例
from lkd.settings import get_settings

settings = get_settings()  # 始终返回同一实例
host_url = settings.host_url
```

#### 延迟导入

解决循环依赖问题，在方法内部动态导入：

```python
def _get_api_module():
    """延迟导入 API 模块，避免循环依赖"""
    from lkd.api import api
    return api
```

### 代码规范

#### 类型注解

所有公共 API 必须包含完整的类型注解：

```python
from typing import Dict, Any, Optional

def login(password: str) -> Dict[str, Any]:
    """
    用户登录
  
    Args:
        password: 用户密码
  
    Returns:
        登录结果字典
    """
    pass
```

#### 请求类继承

所有 HTTP 请求类必须继承自 `BaseRequest` 抽象基类：

```python
from lkd.client.request_base import BaseRequest

class RequestPost(BaseRequest):
    def __init__(self, request_url: str) -> None:
        super().__init__(method="POST", request_url=request_url)
```

### 安全规范

#### 线程安全

共享状态访问必须加锁：

```python
import threading

class CacheManager:
    def __init__(self) -> None:
        self._cache: Dict[str, Any] = {}
        self._lock = threading.Lock()
  
    def set(self, key: str, value: Any) -> None:
        with self._lock:
            self._cache[key] = value
```

#### 上下文管理器

文件/网络操作必须使用上下文管理器：

```python
def read_file(path: str) -> Optional[str]:
    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()
    except Exception as e:
        log_error(f"读取文件失败：{e}")
        return None
```

### 容错机制

#### 多级降级策略

密码初始化等关键流程必须实现多级降级：

```python
def _init_password(self) -> None:
    # 优先使用设备 ID 生成的密码
    device_pwd = _get_pwd_from_device()
    if device_pwd:
        self._password = device_pwd
        return
  
    # 尝试从缓存获取
    cached_pwd = CACHE.get("pwd", "")
    if cached_pwd:
        self._password = cached_pwd
        return
  
    # 生成新密码并保存
    self._password = random_pwd()
    CACHE["pwd"] = self._password
```

#### 异常处理

认证/注册流程必须包裹在 try-except 块中：

```python
try:
    result = login(password)
    if result["success"]:
        log_print("登录成功")
except Exception as e:
    log_error(f"登录异常：{e}")
```

### 路径规范

文件路径获取必须实现多层降级策略：

```python
def get_documents_path(self) -> str:
    """获取文档路径（带降级策略）"""
    try:
        # 尝试访问 Documents 目录
        path = get_known_folder_path("Documents")
        if path:
            return os.path.join(path, "0_mt")
    except Exception:
        pass
  
    try:
        # 降级到 APPDATA
        path = os.getenv("APPDATA")
        if path:
            return os.path.join(path, "0_mt")
    except Exception:
        pass
  
    # 最终降级到临时目录
    return tempfile.gettempdir()
```

### 配置加载规范

服务器地址由 `Settings` 统一持有，通过 `configure_server()` 显式写入，
`host_url` 每次访问时按 `ServerAddress` 动态构建（本地优先、远程兜底）：

```python
from lkd.settings import configure_server, get_settings

configure_server(local_ip="192.168.2.28:8000")
host_url = get_settings().host_url  # http://192.168.2.28:8000/api
```

未显式配置时回退到环境变量 `LKD_LOCAL_IP` / `LKD_REMOTE_IP`；
不再使用全局变量传递服务器地址。

---

## 📚 主要模块说明

### API 模块 (`lkd.api`)

提供完整的后端 API 接口封装：

#### 认证相关

- `register(password: str, referral_code: str = None, username: str = None)` - 用户注册（推荐码必填）
- `login(password: str, username: str = None)` - 用户登录
- `refresh_token(ref_token: str, app_name: str)` - 刷新 Token

#### 设备管理

- `register_device(access_token, ref_token, **kwargs)` - 注册或更新设备

#### 任务管理

- `get_task(**kwargs)` - 获取任务列表
- `update_task(id, **kwargs)` - 更新任务
- `logs(task_id, log_content, screenshot)` - 发送任务日志

#### 商品管理

- `send_products(data)` - 发送商品信息
- `get_products(**kwargs)` - 查询商品信息

#### 店铺管理

- `get_shop(shop_id, **kwargs)` - 获取店铺信息
- `update_shop(shop_id, **kwargs)` - 更新店铺信息
- `get_second_categories(**kwargs)` - 获取二级类目
- `create_or_update_second(**kwargs)` - 创建/更新二级类目

#### 采购管理

- `get_purchase(**kwargs)` - 获取拼多多采购信息
- `post_purchase(**kwargs)` - 采购 PDD 商品
- `put_purchase(**kwargs)` - 更新采购信息

#### 绑定关系

- `query_pdd_bind_products(**kwargs)` - 查询商品绑定关系
- `update_pdd_bind_product(id, **kwargs)` - 更新绑定关系

### 缓存模块 (`lkd.cache`)

```python
from lkd.cache import CACHE

# 内存热缓存（字典式访问）
CACHE["key"] = "value"
value = CACHE["key"]

# 或使用标准方法
CACHE.set("key", "value")
value = CACHE.get("key")
```

账号与 token 的持久化请使用 `lkd.auth`：

```python
from lkd.auth import AuthStore

store = AuthStore()          # 默认 %LOCALAPPDATA%/lkd/auth.db
account = store.load_account()
tokens = store.load_tokens()
```

### 日志模块 (`lkd.logs`)

```python
from lkd.logs import log_print, log_error, log_warning, set_log_dir

# 设置日志目录
set_log_dir("/path/to/logs")

# 记录日志
log_print("普通信息")      # INFO 级别
log_error("错误信息")      # ERROR 级别
log_warning("警告信息")    # WARNING 级别
```

### 全局变量模块 (`lkd.var`)

```python
from lkd.var.variables import glv

# 设置变量（业务路径等）
glv.set("gvar_task_path", "/path/to/tasks")

# 获取变量
task_path = glv.get("gvar_task_path")

# 检查是否存在
if glv.get("gvar_task_path"):
    print("任务目录已配置")
```

> 服务器地址不再通过全局变量传递，请使用 `lkd.settings.configure_server()`。

---

## ⚙️ 配置说明

### IP 配置

```python
from lkd.settings import configure_server

# 本地服务器（优先使用）；远程服务器（备用）
configure_server(
    local_ip="192.168.2.28:8000",
    remote_ip="your.remote.ip:port",
)

# Settings 会自动选择本地地址（如果存在），否则使用远程地址：
# - 纯 ip:port → http://{ip}:{port}/api
# - 已带 http(s):// 前缀 → 原样使用
```

### 路径配置

```python
from lkd.var.variables import glv

# 配置关键路径
glv.set("gvar_config_path", "/path/to/config")
glv.set("gvar_task_path", "/path/to/tasks")
glv.set("gvar_img_path", "/path/to/images")
glv.set("gvar_logs_path", "/path/to/logs")
glv.set("gvar_export_path", "/path/to/export")
```

### 自动认证客户端

```python
from lkd.env.auto_client import AutoClient

# 创建自动连接客户端
auto_client = AutoClient(app_name="soldier")

# 启动自动刷新（仅按 token 过期时间刷新，不发送心跳）
auto_client.start_auto_refresh()

# 客户端会自动处理：
# 1. Token 检查和刷新（SQLite 持久化，重启后直接复用）
# 2. 自动注册 / 登录（注册需要先配置推荐码）
# 3. 设备注册
# 4. 401 自动恢复与本地 token 复用
```

#### 凭据存储

- 数据库：`%LOCALAPPDATA%\lkd\auth.db`，可用环境变量 `LKD_AUTH_DB_PATH` 覆盖
- 加密：密码、access_token、refresh_token 经 Windows DPAPI 加密后以 BLOB 存储，
  密钥绑定当前 Windows 用户，库文件被拷到其他用户/机器无法解密
- 写入：同一台设备同一时间只有一个流程在运行，token 直接整行覆盖写，
  不做版本号校验；`journal_mode=WAL` + `busy_timeout` 只用于抗住句柄
  未释放的瞬间
- 账号：首次注册生成 `lkd-{主机名}-{设备指纹前8位}`（如 `lkd-DESKTOP-3f9a2c7b`），
  密码取设备复合 ID 后 8 位；账号名完全由本机硬件信息推导，流程重启前后
  结果一致，不依赖 IP，也不会与其他设备撞名

#### 注册推荐码

推荐码通过参数传递（不再使用环境变量），未配置时不会发起注册请求：

```python
from lkd.settings import configure_referral_code
from lkd.settings.initialization import default as init_default

# 方式一：随初始化一起传入
init_default(local_ip="192.168.2.28:8000", referral_code="your-referral-code")

# 方式二：单独配置（初始化前或运行期均可）
configure_referral_code("your-referral-code")

# 方式三：仅本次注册使用
from lkd.api import register

register("your-password", referral_code="your-referral-code")
```

---

## ⚠️ 注意事项

1. **服务器地址**: 通过 `configure_server()` 或 `init_default(local_ip=...)` 配置，
   不再需要在导入顺序上做特殊处理
2. **Excel 格式规范**: 确保 Excel 表格格式规范，列名与配置一致
3. **图片命名**: 图片文件夹命名需与 Excel 表格对应
4. **权限要求**: Windows 自动化功能需要相应系统权限
5. **Python 版本**: 需要 Python 3.8 或更高版本
6. **线程安全**: 多线程环境下请使用锁机制保护共享状态
7. **SSL 校验证书**: 默认开启证书校验。内网自签名证书需显式关闭：
   `glv.set("SSL_VERIFY", False)`（关闭会记录安全告警日志）
8. **密钥配置**: `cloud_token` / `coze_token` 通过环境变量
   `LKD_CLOUD_TOKEN` / `LKD_COZE_TOKEN` 注入，不再写死在源码中
9. **认证监控**: `lkd.env.get_auth_metrics()` 返回登录/刷新/注册/401 恢复的
   成功与失败次数，可直接接入监控告警
10. **目录不再自动创建**: 初始化只装配 `gvar_*` 路径字符串，不预建目录；
    业务方在真正写入前自行创建（可用 `LocalPathManager.ensure_exists(path)`）
11. **注册推荐码必填**: 未通过 `init_default(referral_code=...)` /
    `configure_referral_code(...)` 配置时，注册直接失败且不发请求

### 升级注意（认证存储改造）

- **破坏性变更**：账号名改为 `lkd-{主机名}-{设备指纹前8位}`，由硬件信息确定性推导。
  升级后首次启动会按新规则注册新账号，旧账号与旧 `local.json` 不再使用（不做迁移）
- 删除 `local.json` 文件缓存与 `local.json.lock` 跨进程文件锁，
  统一由 `%LOCALAPPDATA%\lkd\auth.db` 承载
- 删除 `init_folders()` / `init_resources()`，启动时不再批量创建目录
- 401 恢复不再递归触发；登录/刷新接口（`/oauth2/v1/*`）不携带 Bearer
- 认证失败仅对网络异常 / 5xx / 限流重试（指数退避 + 抖动），
  账号或密码类失败直接结束；5xx / 429 仅对幂等方法重试，POST 不会重复提交
- 首次运行（本地无账号）登录失败后会自动注册，因此必须在初始化时传入
  `referral_code`，否则首次启动无法完成认证

---

## 🔍 常见问题

### Q: 如何修改 API 服务器地址？

A: 通过 `configure_server()` 重新配置，地址会立即生效：

```python
from lkd.settings import configure_server
configure_server(local_ip="new.server.ip:port")
```

### Q: 日志文件在哪里？

A: 默认位于初始化时配置的 `gvar_logs_path` 目录，可通过 `set_log_dir()` 自定义。

### Q: 如何处理大量商品上传？

A: 使用 `ExcelUploader` 类，支持并发上传：

```python
from lkd.util.up_c import ExcelUploader

uploader = ExcelUploader(
    excel_path="products.xlsx",
    shop_name="店铺名称",
    max_workers=10  # 并发数
)
uploader.run()
```

### Q: Token 过期了怎么办？

A: 使用 `AutoClient` 会自动处理 Token 刷新。手动刷新可使用：

```python
from lkd.api import refresh_token

result = refresh_token(
    ref_token="your_refresh_token",
    app_name="soldier"
)
```

### Q: 上一次流程结束后，下次启动还需要重新登录吗？

A: 不需要。token 存放在 SQLite（`%LOCALAPPDATA%\lkd\auth.db`），流程退出后
下一个进程启动会直接读取并复用；只有 token 已过期或 refresh_token 被拒绝时
才重新刷新/登录。

注意：设计前提是同一台设备同一时间只运行一个流程。若确实同时跑多个进程，
它们会共享同一份 token 并可能互相覆盖（能自愈，但会多几次认证请求），
因此不建议双开。

### Q: 账号和 token 存在哪里？安全吗？

A: 存在 `%LOCALAPPDATA%\lkd\auth.db`（可用环境变量 `LKD_AUTH_DB_PATH` 覆盖）。
密码、access_token、refresh_token 均经 Windows DPAPI 加密后以 BLOB 存储，
密钥绑定当前 Windows 用户；库文件被拷到其他用户或机器上无法解密。
解密失败时按“无凭据”处理并重新注册。

### Q: 注册失败并提示未配置推荐码？

A: 后端注册要求 `referral_code` 字段。请通过
`init_default(referral_code=...)` 或 `configure_referral_code(...)` 配置后重试；
未配置时 SDK 会直接返回失败，且不会发起注册请求。

### Q: Token 过期后会自动刷新吗？

A: 会。调用 `AutoClient.start_auto_refresh()` 后会启动一个轻量后台线程，
按 `refresh_time` 自动刷新（默认提前 10%，最少 60 秒、最多 300 秒），
不发送任何心跳请求；同时业务请求遇到 401 也会自动刷新并重试一次。

---

## 📝 更新日志

详见 [CHANGELOG.md](CHANGELOG.md) 和 [版本日志.txt](版本日志.txt)

### v0.2.0 (2026-04-02)

- ✅ 修复 Settings 动态加载 IP 配置问题
- ✅ 优化心跳 URL 拼接逻辑
- ✅ 增强缓存模块线程安全性
- ✅ 改进路径降级策略

---

## 🤝 贡献指南

欢迎提交 Issue 和 Pull Request！贡献代码请遵循以下规范：

1. 所有公共 API 必须包含完整 docstring
2. 保持类型注解风格一致
3. 关键流程必须包含异常处理
4. 新增功能需补充测试用例

---

## 📄 许可证

MIT License

---

## 📧 联系方式

技术支持：请联系项目维护者

---

**最后更新**: 2026-04-02
**版本**: 0.2.0
**作者**: 松涵
