diff --git a/README.md b/README.md index 9688e7e..c5c83b4 100644 --- a/README.md +++ b/README.md @@ -279,13 +279,13 @@ worker///__admin__ - **单一事实来源**:`count` 存于 cache 键 `worker:cm:counts:`,在线网关 id 列表存 `worker:cm:gateways`,各网关分摊配额存 `worker:cm:quota::`(可观测)。 - **网关登记/注销 + 心跳租约**:网关启动时 `register_gateway()` 加入在线列表,并每 15s `heartbeat_gateway()` 刷新自身时间戳;超过租约(60s)未心跳的死网关会被自动清理(crash/kill -9 也不残留,避免配额失真)。当前由 `CHONGMING_GATEWAY_ID` 环境变量指定(多实例需唯一,尤其**同宿主多网关必须显式指定**;缺省回退 `hostname`,稳定且重启不变,不残留);关闭时 `unregister_gateway()` 移除,其余网关实时重算分摊。 - **分摊算法**:`own_quota()` 将全局总量按在线网关 sorted(id) 均分,余量按序补前若干。`/admin/workers//scale`(body `{count:<全局总量>}`)写全局 count 后,各网关把自己的 session 副本调到自己的 quota。 -- **共享方式与后端**:单实例本机 `diskcache` 即可;**多实例必须共享同一 cache 后端**(同一共享卷的 diskcache 或 redis),否则各写各的 cache 无法收敛成全局量。 +- **共享方式与后端**:单实例本机 `sqlite` 即可;**多实例必须共享同一 cache 后端**(同一共享卷的 sqlite 或 redis),否则各写各的 cache 无法收敛成全局量。 - **启动优先级开关**:根 `config.toml` 的 `[cache].launch_worker_count` 决定网关启动时副本数的来源: ```toml [cache] - backend = "diskcache" # "diskcache"(默认) | "redis" - cache_store_path = "./cache_data" + backend = "sqlite" # "sqlite"(默认,本机共享) | "redis"(跨主机) + path = "./cache_data/cache.db" launch_worker_count = "toml" # "toml"(默认)=启动用 toml 的 count 覆盖并写回 cache;"cache"=cache 为单一事实来源,toml 仅在缺失时引导 ``` @@ -293,7 +293,7 @@ worker///__admin__ - **实例登记 + 心跳租约**:每个 external 进程启动时向 `worker:cm:instances:` 登记自身参与方(`[instance_id, ts]`),并每 15s 心跳续约;超过租约(60s)未心跳的死实例会被清理。`divmod` 按存活参与方 N 均分全局 count(余量补靠前实例),`N==1` 时退化为全额(单部署零影响)。 - **实例标识默认唯一**:`--instance` / 环境变量 `CHONGMING_WORKER_INSTANCE` 显式指定(跨重启稳定、逐实例精细扩缩可持续命中);缺省回退 `{hostname}:{pid}`——**同一机器并行多个未指定 `--instance` 的进程也能靠 pid 区分**,不会因为 id 相同被并成一个参与方导致副本翻倍或前端只显示一个实例。注意缺省 id 随进程重启变化。 - **广播 + 幂等收敛**:`/scale` 写全局 count 后向该类型**全部存活实例**广播 `rescale`,各实例读同一全局 count + 同一份参与方列表算出的 quota 一致,重复 rescale 也幂等,因此任意地收敛到同一预期状态。 - - **前提**:external 需共用共享 `[cache]`(多实例 redis 或共享卷 diskcache);`cache is None` 时退化为全额(仅单实例安全)。 + - **前提**:external 需共用共享 `[cache]`(多实例 redis 或共享卷 sqlite);`cache is None` 时退化为全额(仅单实例安全)。 ### 外部进程实例粒度扩缩(逐实例精细扩缩) @@ -312,7 +312,7 @@ inline worker 的生命周期交给**基于分布式锁的宿主级领导选举* - **多 worker(同网关)**:同一时刻只有一个网关进程托管 inline worker(副本数精确等于 `count`,不再是 `worker 数 × count`);其余进程只做 HTTP,经 Zenoh liveliness 发现并调用 inline worker。HTTP 层仍由所有 gunicorn worker 分担吞吐。 - **多实例(不同网关)**:各网关按自己的 `gateway_id` 各自选举,互不抢占。 - **持有者崩溃自愈**:非持有者进程经 `refresh_workers` 周期性巡检(`_maybe_heal_inline`)——若发现某 inline 类型既无线活副本又无本地副本,就经分布式锁抢得领导权并拉起这些 worker(防 gunicorn 复活死 slot 却因租约未过期而跳过的问题)。接管同样 `set_nx` 唯一胜出,无双持有。 -- 前提:同一共享 `[cache]`(见「全局副本数协调」);单实例本机 `diskcache` 即可,多实例需共享后端。 +- 前提:同一共享 `[cache]`(见「全局副本数协调」);单实例本机 `sqlite` 即可,多实例需共享后端。 ### 多 worker 的边界(转发 vs 处理) @@ -628,10 +628,10 @@ MQTT Bridge 是**网关进程内**的 MQTT 客户端组件,负责订阅主题 ```toml [cache] -backend = "redis" # 跨主机收敛必须用 redis(或共享卷 diskcache) +backend = "redis" # 跨主机收敛必须用 redis(或共享卷 sqlite) ``` -- **关键条件**:跨机器收敛依赖**共享缓存后端**(与「全局副本数协调」同一 `[cache]`,单一事实来源)。仅 `redis`(或共享卷的 `diskcache`)后端时,不同物理机上的网关才会看到同一把锁,真正全局单 Bridge。 +- **关键条件**:跨机器收敛依赖**共享缓存后端**(与「全局副本数协调」同一 `[cache]`,单一事实来源)。仅 `redis`(或共享卷的 `sqlite`)后端时,不同物理机上的网关才会看到同一把锁,真正全局单 Bridge。 - **`use_embedded_broker = true`(各机内嵌独立 Broker)**:各机 Broker 互不相连、本身不产生重复响应;此时全局单 Bridge 只会连到持锁网关那台机器的 Broker。 - **`use_embedded_broker = false`(共享外部 Broker)**:原先 N 台网关的 N 个 Bridge 会共同订阅同一批主题,导致一条请求被各消费一次、各回一份响应;改用分布式锁后全局只有一个 Bridge 消费,彻底规避重复响应。 @@ -639,12 +639,12 @@ backend = "redis" # 跨主机收敛必须用 redis(或共享卷 dis | 部署形态 | MQTT Broker | `[cache]` 后端 | 状态 | | -------- | ----------- | ------------- | ---- | -| 单机(含多 worker) | 内嵌 amqtt | 本地 `diskcache` | ✅ 完全够用 | -| 多实例分布式 | **外部 Broker**(必须,内嵌各机不聚合、接管会断设备) | **redis**(或共享卷 `diskcache`) | ✅ 推荐 | +| 单机(含多 worker) | 内嵌 amqtt | 本地 `sqlite` | ✅ 完全够用 | +| 多实例分布式 | **外部 Broker**(必须,内嵌各机不聚合、接管会断设备) | **redis**(或共享卷 `sqlite`) | ✅ 推荐 | > 关键:多实例是否收敛,**MQTT 只是其中一环**,真正的收敛前提是**所有网关共享同一 `[cache]` 后端**。Bridge 全局锁、inline 领导选举锁、在线网关清单/quota 全部工作在同一共享 cache 上;若各实例用各自本机 cache,`mqtt_bridge` 锁跨不了主机,每台都能抢到 Bridge,又会退化成 N 个 Bridge 各连各的 Broker → 单请求多响应,问题回归。 > -> 即:**单机 = 内嵌 Broker + 本地 diskcache;多实例 = 外部 Broker(必须)+ redis/共享卷 diskcache(选一)**。 +> 即:**单机 = 内嵌 Broker + 本地 sqlite;多实例 = 外部 Broker(必须)+ redis/共享卷 sqlite(选一)**。 ### 请求负载格式(信封协议) diff --git a/config.toml b/config.toml index ba60802..87ba05f 100644 --- a/config.toml +++ b/config.toml @@ -11,8 +11,9 @@ upload_path = "uploads" json5_path = "./config/zenoh_config.json5" [cache] -backend = "diskcache" # "diskcache"(默认) | "redis" -cache_store_path = "./cache_data" +# backend = "sqlite" # "sqlite"(默认,本机共享) | "redis"(跨主机) +backend = "redis" +# path = "./cache_data/cache.db" # 启动时该类 worker 的全局副本数优先来源(指 config/workers//config.toml 的 count): # "toml" (默认) = 经典模式:启动时用 toml 的 count 覆盖 cache 并写回为新的全局总量 @@ -20,11 +21,11 @@ cache_store_path = "./cache_data" launch_worker_count = "toml" # redis 后端专用(backend = "redis" 时生效,均有默认值) -# redis_host = "127.0.0.1" -# redis_port = 6379 -# redis_db = 0 -# redis_password = "" -# redis_prefix = "chongming:" +redis_host = "127.0.0.1" +redis_port = 6379 +redis_db = 0 +redis_password = "" +redis_prefix = "chongming:" [security] algorithm = "HS256" @@ -63,13 +64,15 @@ allow_origins = [ [development.mqtt] enabled = true -host = "0.0.0.0" +# host = "0.0.0.0" +host = "127.0.0.1" port = 1883 # 是否由网关启动内嵌 amqtt Broker 子进程: # true (默认)= 网关自带 amqtt Broker,host/port 为 Broker 监听地址 # false = 使用外部 MQTT Broker,网关仅启动 Bridge 客户端连接 # host/port 需指向外部 Broker 地址(如 "192.168.1.10") -use_embedded_broker = true +# use_embedded_broker = true +use_embedded_broker = false # MQTT Broker 认证(可选,默认匿名): # - 内嵌 Broker(use_embedded_broker=true):配置后要求所有客户端(含网关 Bridge) # 用该账号密码连接,未配置则保持匿名 @@ -110,10 +113,9 @@ base = [ "fastapi.middleware.cors", "fastapi.middleware.gzip", "uvicorn", - "diskcache", "chongming_cache", "chongming_cache.backends", - "chongming_cache.backends.disk", + "chongming_cache.backends.sqlite_", "orjson", "python_multipart", "volcenginesdkarkruntime", diff --git a/deploy/.env b/deploy/.env new file mode 100644 index 0000000..670bd7a --- /dev/null +++ b/deploy/.env @@ -0,0 +1,15 @@ +# redis +REDIS_PORT=6379 + +# postgres +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=chongming + +# minio +MINIO_ROOT_USER=minioadmin +MINIO_ROOT_PASSWORD=minioadmin + +# minio 对外端口 +MINIO_API_PORT=9000 +MINIO_CONSOLE_PORT=9001 \ No newline at end of file diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..5f4f5d1 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,177 @@ +# deploy 部署目录说明 + +本目录包含 chongming 应用在宿主机上的部署与运维相关文件。 + +## 目录内容 + +| 文件 | 作用 | +|------|------| +| [install_docker.sh](install_docker.sh) | 一键安装 Docker 引擎并配置国内公网镜像加速(Ubuntu 24.04 / amd64) | +| [docker-compose.yml](docker-compose.yml) | Redis / PostgreSQL / MinIO / Mosquitto(MQTT) / Zenoh Router / Caddy 六类服务的编排定义 | +| [.env](.env) | Compose 使用的连接凭据与环境变量(PostgreSQL 密码、MinIO 账号等,勿提交到仓库) | +| [chongming.service](chongming.service) | chongming 应用的 systemd 服务单元文件 | +| [caddy/Caddyfile](caddy/Caddyfile) | Caddy 反向代理配置(本地 IP + 自签证书,统一 HTTP/HTTPS 入口) | +| [data/](data) | Compose 声明挂载的数据持久化目录(redis / postgres / minio / mosquitto / caddy 子目录),由容器产生,勿手动编辑 | + +--- + +## 1. 安装 Docker + +首次在新机器上部署时,先安装 Docker: + +```bash +sudo ./install_docker.sh +``` + +脚本会安装 `docker-ce` 及 Compose 插件,并将国内公网加速源写入 `/etc/docker/daemon.json`(多源容灾)。安装完成后即可使用 `docker compose`。 + +> 说明:脚本使用的 `mirrors.cloud.tencent.com` 为腾讯云公网镜像站;原内网域名 +> `mirrors.tencentyun.com` / `mirror.ccs.tencentyun.com` 仅腾讯云 VPC 内访问,本机不可用。 + +--- + +## 2. 部署基础服务(Redis / PostgreSQL / MinIO / Mosquitto / Zenoh) + +五个服务分别用于 chongming 的缓存、元数据、对象存储、MQTT 消息与 Zenoh 路由汇聚。 + +### 部署 + +> 说明:Mosquitto 与 Zenoh Router 为按需启用项。若只想启动基础三服务(redis/postgres/minio),可加 `--no-recreate` 仅针对目标服务: + +```bash +cd deploy +# 启动全部服务(首次会自动拉取镜像并创建容器) +sudo docker compose up -d + +# 仅启动消息/路由服务(保留已在运行的 redis/pg/minio) +sudo docker compose up -d mosquitto zenoh + +# 仅启动基础数据服务 +sudo docker compose up -d redis postgres minio +``` + +### 服务清单(默认端口) + +| 服务 | 容器名 | 端口 | 说明 | +|------|--------|------|------| +| Redis 7 | chongming-redis | 6379 | 支持 appendonly 持久化 | +| PostgreSQL 16 | chongming-postgres | 5432 | 元数据存储 | +| MinIO | chongming-minio | 9000(S3) / 9001(Console) | 对象存储,Web 控制台 | +| Mosquitto 2 | chongming-mosquitto | 1883 | MQTT Broker(WebSocket 9001 默认关闭,避免与 MinIO Console 冲突) | +| Zenoh Router | chongming-zenoh | 7447/tcp、7447/udp | Zenoh 路由节点 | +| Caddy | chongming-caddy | 80 / 443 | 统一反向代理入口(chongming Web / MinIO Console / MinIO API) | + +### 连接凭据(见 [.env](.env)) + +- PostgreSQL:用户 `postgres` / 密码 `postgres` / 库 `chongming` +- MinIO:`minioadmin` / `minioadmin` +- Mosquitto:匿名连接(如需账号,在 [mosquitto.conf](data/mosquitto/config/mosquitto.conf) 配置) + +### 数据持久化 + +数据保存在 `deploy/data/{redis,postgres,minio,mosquitto}`,通过绑定挂载持久化,容器删除后数据不丢失。所有服务均设置 `restart: unless-stopped`(开机自启)。 + +### 日常管理 + +```bash +sudo docker compose ps # 查看状态与健康度 +sudo docker compose logs -f <服务名> # 查看日志 +sudo docker compose restart <服务名> # 重启单个服务 +sudo docker compose down # 停止并移除容器(保留数据卷) +sudo docker compose down -v # 停止并移除容器 + 数据卷(慎用,会清空数据) +``` + +### 验证 + +```bash +sudo docker exec chongming-redis redis-cli ping # 期望 PONG +sudo docker exec chongming-postgres pg_isready -U postgres -d chongming # 期望 accepting connections +sudo docker exec chongming-mosquitto mosquitto_sub -p 1883 -t '$SYS/broker/version' -W 2 -C 1 + # 期望打印版本号 +``` + +### 接入 chongming 说明 + +**Zenoh Router**:项目 [zenoh_config.json5](../config/zenoh_config.json5) 默认以 `peer` 模式连接 `tcp/127.0.0.1:7447`。本 router 容器把 7447 映射到宿主机,同机部署时应用即可直接连到该 router;若跨机部署,需把 `connect.endpoints` 改为 router 机器地址。 + +**Mosquitto**:项目 [config.toml](../config.toml) 默认 `use_embedded_broker = true`(内嵌 amqtt)。要切到本外部 broker,需改为: + +```toml +[development.mqtt] +enabled = true +host = "127.0.0.1" +port = 1883 +use_embedded_broker = false +``` + +--- + +## 2.5 Caddy 反向代理(本地 IP + 自签证书) + +Caddy 作为统一 HTTP/HTTPS 入口,反代三个 Web 服务: + +| 入口域名 | 反向代理到 | +|----------|-----------| +| `gateway.chongming.local` | 宿主机 chongming Web(`host.docker.internal:8000`,systemd 托管) | +| `s3.chongming.local` | MinIO S3 API(容器 `minio:9000`) | +| `minio.chongming.local` | MinIO Web Console(容器 `minio:9001`) | + +> 采用**本地 IP + 自签证书**方案:三站均使用 `tls internal`,由 Caddy 内置根证书签发 +> 自签证书,**不走公网 ACME**,适合内网/测试环境。 + +### 部署 + +```bash +cd deploy +sudo docker compose up -d caddy +``` + +首次会自动拉取 `caddy:2-alpine` 镜像;Caddyfile 改动后: +`sudo docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile` + +### 访问方式(重要) + +1. 先把三个本地域名指向机器 IP,追加到**访问端**(含宿主机、内网其它机器)的 `/etc/hosts`: + +``` +<本机IP> gateway.chongming.local s3.chongming.local minio.chongming.local +``` + +2. 浏览器访问(会提示证书不受信任,自签属正常,添加例外即可): + - `https://gateway.chongming.local` —— chongming Web + - `https://minio.chongming.local` —— MinIO Console(账号见 [.env](.env)) + - S3 SDK 配置 Endpoint 为 `https://s3.chongming.local` + +命令行自测(自签需 `-k`): + +```bash +curl -k https://minio.chongming.local/minio/health/live # 期望结果含账号名(匿名则 403 也正常) +``` + +> 说明:MinIO 容器仍保留 9000/9001 对宿主端口映射;若只想统一走 Caddy 入口, +> 可删除 compose 中 minio 的 `ports` 段。 + +--- + +## 3. systemd 管理 chongming 应用 + +[chongming.service](chongming.service) 以非特权用户 `linaro` 运行打包后的 chongming 二进制(Nuitka 产物,`/data/chongming`)。 + +安装: + +```bash +sudo cp deploy/chongming.service /etc/systemd/system/chongming.service +sudo systemctl daemon-reload +sudo systemctl enable --now chongming +``` + +常用运维命令: + +```bash +sudo systemctl status chongming # 状态 +sudo systemctl restart chongming # 重启(优雅排空,最长 30s) +journalctl -u chongming -f # 系统日志;应用自身日志在 /data/chongming/logs/ +``` + +> 注意:安装目录、用户、worker 数等以 service 文件注释为准;如部署路径不同, +> 需同步修改 `WorkingDirectory` / `ExecStart`。 \ No newline at end of file diff --git a/deploy/caddy/Caddyfile b/deploy/caddy/Caddyfile new file mode 100644 index 0000000..752b9c5 --- /dev/null +++ b/deploy/caddy/Caddyfile @@ -0,0 +1,32 @@ +# Caddy 反向代理配置 — 本地 IP + 自签证书方案 +# +# 通过三个本地主机名统一入口(80/443),需在访问端 /etc/hosts 追加: +# <本机IP> gateway.chongming.local s3.chongming.local minio.chongming.local +# +# 证书方案:每站均用 `tls internal` 由 Caddy 内置根证书签发自签证书, +# 不走公网 ACME,适合内网/测试环境。浏览器首次访问会提示不受信任, +# 无需理会本机(curl 可加 -k,浏览器可加例外)。 + +{ + # 全局默认启用自签(即使某站忘记写 tls 指令,也不会误触公网 ACME) + auto_https disable_redirects +} + +# chongming Web —— 反代宿主机 systemd 托管的 HTTP 8000 +# 容器经 host.docker.internal(extra_hosts: host-gateway)访问宿主机 +gateway.chongming.local { + tls internal + reverse_proxy host.docker.internal:8000 +} + +# MinIO S3 Object Storage API(9000) +s3.chongming.local { + tls internal + reverse_proxy minio:9000 +} + +# MinIO Web Console(9001) +minio.chongming.local { + tls internal + reverse_proxy minio:9001 +} \ No newline at end of file diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..3a8fb38 --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,123 @@ +# Redis / PostgreSQL / MinIO / Mosquitto(MQTT) / Zenoh Router / Caddy 服务 Compose 配置 +# 数据目录: ./data/{redis,postgres,minio,mosquitto,zenoh,caddy} (相对本文件所在目录,持久化) +# 默认端口: redis 6379, postgres 5432, minio 9000/9001, +# mosquitto 1883, zenoh router 7447(tcp)/7447(udp)/8000, +# caddy 80/443(HTTP/HTTPS 统一入口) +services: + redis: + image: docker.io/library/redis:7-alpine + container_name: chongming-redis + restart: unless-stopped + command: redis-server --appendonly yes + ports: + - "6379:6379" + volumes: + - ./data/redis:/data + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + interval: 5s + timeout: 3s + retries: 5 + + postgres: + image: docker.io/library/postgres:16-alpine + container_name: chongming-postgres + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - "5432:5432" + volumes: + - ./data/postgres:/var/lib/postgresql/data + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ] + interval: 5s + timeout: 3s + retries: 5 + + minio: + image: docker.io/minio/minio:latest + container_name: chongming-minio + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} + ports: + - "9000:9000" # S3 API + - "9001:9001" # Web Console + volumes: + - ./data/minio:/data + healthcheck: + test: [ "CMD", "mc", "ready", "local" ] + interval: 5s + timeout: 3s + retries: 5 + + # MQTT Broker —— 项目默认内嵌 amqtt,若要切换为外部 broker, + # 需把 config.toml 的 [development.mqtt].use_embedded_broker 置为 false + # 并指向宿主 1883(如 host = "127.0.0.1")。 + mosquitto: + image: docker.io/library/eclipse-mosquitto:2 + container_name: chongming-mosquitto + restart: unless-stopped + ports: + - "1883:1883" # MQTT + volumes: + # 官方镜像默认要求 /mosquitto/config/mosquitto.conf 存在; + # 匿名 + 不持久化时,改用镜像自带的空配置文件即可。 + - ./data/mosquitto:/mosquitto + environment: + # 匿名启动,无需授权;如需账号可在 config/mosquitto.conf 中配置 + - MOSQUITTO_DISABLE_TLS=true + healthcheck: + test: [ "CMD-SHELL", "pkill -0 $$(pgrep mosquitto) || exit 1" ] + interval: 10s + timeout: 5s + retries: 3 + + # Zenoh Router —— 项目 zenoh 以 peer 模式连接 tcp/127.0.0.1:7447, + # 本容器作为 router 在 7447(tcp) 提供路由汇聚。 + zenoh: + image: docker.io/eclipse/zenoh:latest + container_name: chongming-zenoh + restart: unless-stopped + ports: + - "7447:7447/tcp" # Zenoh 数据面 + - "7447:7447/udp" # Zenoh scouting + # 使用镜像默认入口即可:eclipse/zenoh 默认以 router 模式启动 zenohd, + # 监听默认端口(7447 tcp/udp、8000)。旧写法硬编码 /usr/local/bin/zenohd + # 在 latest 镜像中路径不存在,会报 exec: no such file or directory。 + healthcheck: + test: [ "CMD", "zenohd", "--help" ] + interval: 10s + timeout: 5s + retries: 3 + + # Caddy —— 统一 HTTP/HTTPS 反向代理入口(本地 IP + 自签证书) + # 反代: chongming Web(8000, 宿主机) / MinIO Console(9001) / MinIO API(9000) + # 配置在 ./caddy/Caddyfile,证书状态持久化于 ./data/caddy + caddy: + image: docker.io/library/caddy:2-alpine + container_name: chongming-caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + - "443:443/udp" # HTTP/3 (QUIC, 可选) + volumes: + - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro + - ./data/caddy:/data # 自签证书 / ACME 状态持久化 + - ./data/caddy/config:/config + extra_hosts: + # Linux 下从容器访问宿主机(反代 systemd 托管的 chongming:8000) + - "host.docker.internal:host-gateway" + depends_on: + - minio + healthcheck: + test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:80 >/dev/null 2>&1 || exit 1" ] + interval: 10s + timeout: 3s + retries: 5 diff --git a/deploy/install_docker.sh b/deploy/install_docker.sh new file mode 100755 index 0000000..47327a8 --- /dev/null +++ b/deploy/install_docker.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# Docker 安装 + 国内公网镜像加速配置脚本 (Ubuntu 24.04 / amd64) +# 注: mirrors.tencentyun.com / mirror.ccs.tencentyun.com 仅腾讯云 VPC 内网可用, +# 本机不在腾讯云内网,故 apt 源使用腾讯云公网镜像站,registry 加速使用公网加速源。 +set -e + +echo "==> [1/5] 安装基础依赖" +apt-get update -y +apt-get install -y ca-certificates curl gnupg + +echo "==> [2/5] 添加 Docker 官方 GPG key 和仓库(腾讯云公网镜像)" +install -m 0755 -d /etc/apt/keyrings +rm -f /etc/apt/keyrings/docker.gpg +curl -fsSL https://mirrors.cloud.tencent.com/docker-ce/linux/ubuntu/gpg \ + | gpg --dearmor -o /etc/apt/keyrings/docker.gpg +chmod a+r /etc/apt/keyrings/docker.gpg + +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ + https://mirrors.cloud.tencent.com/docker-ce/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + > /etc/apt/sources.list.d/docker.list + +echo "==> [3/5] 安装 Docker 引擎" +apt-get update -y +apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +echo "==> [4/5] 配置镜像加速(公网加速源,多源容灾)" +mkdir -p /etc/docker +cat > /etc/docker/daemon.json <<'EOF' +{ + "registry-mirrors": [ + "https://docker.m.daocloud.io", + "https://docker.1ms.run", + "https://hub.rat.dev" + ] +} +EOF + +echo "==> [5/5] 启动并配置开机自启" +systemctl enable docker +systemctl restart docker + +echo "==> 完成,验证:" +docker --version +docker run --rm hello-world +docker info | grep -A5 "Registry Mirrors" \ No newline at end of file diff --git a/docs/multi-language-plugin-plan.md b/docs/multi-language-plugin-plan.md index 277051d..38777aa 100644 --- a/docs/multi-language-plugin-plan.md +++ b/docs/multi-language-plugin-plan.md @@ -126,11 +126,14 @@ chongming-worker-rs/ # 独立仓库(或 utils/worker-rs/,与 Pyth | admin Queryable | `worker///__admin__`,指令 status/restart/stop/drain/get_config/config_set/rescale | 同一 keyexpr,`admin.rs::handle_admin` 全量分派 | ✅ keyexpr 与指令一致 | | 优雅排空 | drain:隔离新请求 → 等在途归零(30s 超时强制关闭) | `draining` 标记 + `wait_inflight_zero`(同 30s);先摘 liveliness/业务/admin 再等在途 | ✅ 行为一致 | | 配置热更 | config_set:仅 index0 落盘(原子写)+ 触发重启,`[workers.config]` 保留键校验 | `persist_worker_config_section`(tmp+rename 原子写)+ `merge_confirmed`/`count_allowed` 保留键校验 | ✅ 语义一致 | -| 配置缓存 | chongming-cache(redis / diskcache)作为 counts/configs 单一事实来源 | `Cache` 抽象:Redis(feature `redis-cache`)+ 无缓存回退 | ✅ Redis 路径一致;本地 diskcache 因格式特性不可复用(多实例走 Redis) | +| 配置缓存 | chongming-cache(redis / sqlite)作为 counts/configs 单一事实来源 | `Cache` 抽象:`sqlite`(feature `local-cache`,本机与 Python 共享同一 .db)+ Redis(feature `redis-cache`)+ 无缓存回退 | ✅ schema 一致,凡 JSON 值均可互通 | | quota 分摊 | 按在线网关/存活实例均分全局 count(余数给序较早方) | `distribute_quota` / `resolve_worker_count` 同算法 | ✅ 同协议键 `worker:cm:*` | | CLI | `--config` / `--instance` / `--count` | `examples/demo.rs`:`--config` / `--instance` / `--count` | ✅ 对齐 | -> 注:Python 默认的本地 `diskcache` 后端为 Python 库,Rust 无法读其存储文件格式;故 Rust 侧多实例协同统一走 Redis(`redis-cache` feature),单实例走 `Cache::None`,与 Python「多实例必须 redis/共享卷」约束一致。 +> 注:Rust 侧共享缓存已统一为与 Python `chongming-cache` 完全一致的 JSON 协议——本机走 +> `sqlite`(`local-cache` feature,跨语言共享同一 `.db`),跨主机走 Redis(`redis-cache` +> feature);缺省无缓存走 `Cache::None`。原 Python 本地 diskcache 后端已被 SQLite 后端 +> 直接替换,不再存在「Rust 不可读其本地格式」的限制。 ### 3.7 数据库对齐(`chongming_database` ↔ Rust `database.rs`) diff --git a/docs/rust-plugin.md b/docs/rust-plugin.md index 8f2e6e0..0b31b83 100644 --- a/docs/rust-plugin.md +++ b/docs/rust-plugin.md @@ -150,14 +150,30 @@ tokio::runtime::Builder::new_multi_thread().enable_all().build()?.block_on(async ### 5.2 多实例共享缓存 `[workers.cache]` -多实例部署(多进程/多机)**必须**启用 Redis 共享缓存,使副本数与配置跨实例收敛为单一事实来源: +Rust 进程插件支持两种共享缓存后端(schema 与 Python `chongming-cache` 完全一致): -```toml -[workers.cache] -backend = "redis" # 仅支持 redis(Python 本地 diskcache 格式 Rust 不可读) -url = "redis://127.0.0.1:6379/0" -launch_worker_count = "cache" # 缓存为 count 单一事实来源 -``` +1. **本机共享 `sqlite`(feature `local-cache`)**:同一份 SQLite 文件由 Python 与 Rust 读写, + 实现单机多实例/跨语言收敛,无需 Redis。需 `cargo build --features local-cache`: + ```toml + [workers.cache] + backend = "sqlite" # schema 与 Python SqliteBackend 一致(旧名 diskcache 兼容) + path = "./cache_data/cache.db" # 与网关该实例 `[cache].path` 指向同一文件 + launch_worker_count = "cache" + ``` +2. **跨主机 `redis`(feature `redis-cache`)**:多机部署必须用 Redis 共享缓存: + ```toml + [workers.cache] + backend = "redis" + url = "redis://127.0.0.1:6379/0" + prefix = "chongming:" # 与网关 config.toml [cache].redis_prefix 对齐(缺省空) + launch_worker_count = "cache" + ``` + 未指定 backend(缺省)时 Rust 走 `Cache::None`,退化为本地 toml 语义。 + +> `prefix` 必须与网关 `[cache].redis_prefix`(如 `"chongming:"`)一致,Rust 插件才会读写 +> 与网关同一批共享键。若网关未设前缀可省略。缓存值统一以 JSON 存储(Python 侧用 +> orjson、Rust 侧用 serde_json),**任意键**均可跨语言互通;与 Rust 共享的键请使用 +> JSON 兼容类型(dict/list/int/str/bool/null)。 编译启用:`bash build.sh dist "redis-cache"`(或 Cargo 依赖加 `chongming-worker-rs/redis-cache`)。 未配置时单实例直接读写本地 `config.toml`(默认无缓存),行为一致。 @@ -268,7 +284,7 @@ unzip myplugin.zip -d /app && cd /app && ./start.sh | 契约校验 | Python 进程内三方校验(硬失败);Rust 为**发布前自检**:无 handler 端点告警跳过,不发布残缺路由 | | 模型生成 | Rust 用 gen_models 生成强类型结构体 + **启动时指纹校验**(TOML 变更未重生成即告警,比 Python 更强) | | handler 注入 | worker_id 经 `Request.worker_id`、user_info 经 `Request.user_info` 注入(非函数参数) | -| 配置缓存后端 | Rust 仅 Redis(无法读 Python 本地 diskcache);单实例走无缓存回退 | +| 配置缓存后端 | Rust 支持 sqlite(`local-cache`,本机与 Python 共享同一 .db)+ Redis(`redis-cache`,跨主机);缺省无缓存回退 | | 数据库 | Rust 走 sqlx(见 §7),配置键与 Python `chongming_database` 对齐 | --- diff --git a/public/config.toml b/public/config.toml index 683d960..f29143e 100644 --- a/public/config.toml +++ b/public/config.toml @@ -11,8 +11,8 @@ upload_path = "uploads" json5_path = "./config/zenoh_config.json5" [cache] -backend = "diskcache" -cache_store_path = "./" +backend = "sqlite" +path = "./cache_data/cache.db" launch_worker_count = "cache" [security] diff --git a/pyproject.toml b/pyproject.toml index a19cbd2..fee9b30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ "chongming-mqtt", "httptools", "bytedance", + "chongming-cache", ] description = "基于 modules-bank 的 fastapi 打包脚手架" name = "chongming-zenoh" @@ -34,13 +35,7 @@ requires-python = ">=3.12" version = "0.1.0" [dependency-groups] -dev = [ - "chongming-cli", - "nuitka", - "pyarmor", - "pyinstaller", - "ruff", -] +dev = ["chongming-cli", "nuitka", "pyarmor", "pyinstaller", "ruff"] [tool.hatch.build.targets.wheel] packages = ["src/chongming"] diff --git a/src/chongming/app/core/auth.py b/src/chongming/app/core/auth.py index 262e1f9..681dbc7 100644 --- a/src/chongming/app/core/auth.py +++ b/src/chongming/app/core/auth.py @@ -12,7 +12,7 @@ import orjson security_scheme = HTTPBearer(auto_error=False) # JWT 解析共享缓存:与 WorkerManager 共用 [cache] 配置段。 -# 默认 diskcache(本地);横向拓展时 config.toml 切 backend = "redis", +# 默认 sqlite(本机);横向拓展时 config.toml 切 backend = "redis", # 多网关实例共享同一 Redis 即可全局只解密一次,代码零改动。 # 注意:缓存存的是"已验证结果",信任模型与 worker 回复缓存一致 # (缓存基础设施可信;Redis 请配置密码并置于内网)。 @@ -69,7 +69,7 @@ def jwt_auth_dependency( token = credentials.credentials try: payload, _ = _cached_decode(token, secret, algorithm) - # 缓存后端为 pickle 序列化,返回的是独立对象,调用方修改不影响缓存 + # 缓存后端返回的是独立对象(JSON 反序列化后为新副本),调用方修改不影响缓存 return payload except Exception as e: raise HTTPException( diff --git a/src/chongming/app/core/lifespan_setup.py b/src/chongming/app/core/lifespan_setup.py index 2414913..483e9cc 100644 --- a/src/chongming/app/core/lifespan_setup.py +++ b/src/chongming/app/core/lifespan_setup.py @@ -576,7 +576,7 @@ async def setup_mqtt_broker(app: FastAPI, worker_manager: WorkerManager): if worker_manager.cache is None: logger.warning( "MQTT 内嵌 Broker 启用但未配置共享 cache([cache]),无法获取排他锁;" - "请配置 redis 或共享卷 diskcache,或改用外部 Broker" + "请配置 redis 或共享卷 sqlite,或改用外部 Broker" ) return None diff --git a/test/test_ip_sound_column.py b/test/test_ip_sound_column.py new file mode 100644 index 0000000..550445a --- /dev/null +++ b/test/test_ip_sound_column.py @@ -0,0 +1,30 @@ +from chongming_request import Client +import asyncio +import logging + +async def test_tts(): + async with Client() as client: + try: + result = await client.call( + worker_type="ip_sound_column", + subject="ip_sound_column.tts", + method="POST", + payload={ + "command": "play_local_tts", + "volume": 5, + "play_count": 1, + "queue": False, + "sync_response": True, + "play_interval": 3, + "tts_pitch": 50, + "tts_speed": 50, + "tts_voice_name": "xiaoyan", + "tts_text": "你好,欢迎使用语音合成服务!", + } + ) + print("TTS Response:", result) + except Exception as e: + print("Error during TTS call:", e) + +if __name__ == "__main__": + asyncio.run(test_tts()) \ No newline at end of file diff --git a/uv.lock b/uv.lock index 608127d..b4ea614 100644 --- a/uv.lock +++ b/uv.lock @@ -72,15 +72,15 @@ wheels = [ [[package]] name = "anyio" -version = "4.14.2" +version = "4.15.0" source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } dependencies = [ { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, ] -sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/9a/c15a60547004a3f3cea20296c934f827ddd7bdba225a2e7e9fcb5ec48c80/anyio-4.15.0.tar.gz", hash = "sha256:b5c620ed540725e2579c31b17bb995b3bf02c9281c9cace04c7d186380bab85e", size = 276504, upload-time = "2026-09-02T21:46:36.957Z" } wheels = [ - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/a6/2b21ce5ebe4d8938a247c9b0dbb7271566ae559b01795c83ea4bb2660ed7/anyio-4.15.0-py3-none-any.whl", hash = "sha256:7ecd9937369ffce8bba0b5ccb9b3a9507b101b0ed50256aecfbab27e6c2acb99", size = 131908, upload-time = "2026-09-02T21:46:35.485Z" }, ] [[package]] @@ -431,15 +431,15 @@ wheels = [ [[package]] name = "chongming-cache" -version = "0.2.0" +version = "0.2.1" source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } dependencies = [ - { name = "diskcache" }, + { name = "orjson" }, { name = "redis" }, ] -sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.0/chongming_cache-0.2.0.tar.gz", hash = "sha256:7b092c6b96d67f50aac1754c364e4a647efa089bf9dbbc989d0f5993925dec66" } +sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.1/chongming_cache-0.2.1.tar.gz", hash = "sha256:4945a21b6a53c38a263955fddae85ca8036e3ba62e69b117fcab5fd7cb38cd22" } wheels = [ - { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.0/chongming_cache-0.2.0-py3-none-any.whl", hash = "sha256:4e95eddbb691479b23559020375c8d041a03b4a4c6163df1a7f6b5150b9e469e" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.1/chongming_cache-0.2.1-py3-none-any.whl", hash = "sha256:1ca873e4c069ff24b728bb1b90342d66cb6fa6dd994851a1d0cddf2604f0693d" }, ] [[package]] @@ -472,16 +472,16 @@ wheels = [ [[package]] name = "chongming-database" -version = "0.2.0" +version = "0.2.1" source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } dependencies = [ { name = "aiosqlite" }, { name = "asyncpg" }, { name = "sqlmodel" }, ] -sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-database/0.2.0/chongming_database-0.2.0.tar.gz", hash = "sha256:e327ceef0cf4c63b1d21bcb86b42583447c2696cf632a7c8b7b9ab284291d992" } +sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-database/0.2.1/chongming_database-0.2.1.tar.gz", hash = "sha256:f5c5dab6b4630567135f5284c718611b86b0e8aa68c11b64db609470a9b5c69f" } wheels = [ - { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-database/0.2.0/chongming_database-0.2.0-py3-none-any.whl", hash = "sha256:e123cae2744915ed789a050859d30ff7b693726ab68d1493489f25f15c091a31" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-database/0.2.1/chongming_database-0.2.1-py3-none-any.whl", hash = "sha256:64fa23f1d7221806fd3c71d3b6e29e7ffa6c2a35af84233688ca3b35196f0a30" }, ] [[package]] @@ -565,6 +565,7 @@ dependencies = [ { name = "aiofiles" }, { name = "amqtt" }, { name = "bytedance" }, + { name = "chongming-cache" }, { name = "chongming-jwt" }, { name = "chongming-mqtt" }, { name = "chongming-password" }, @@ -600,6 +601,7 @@ requires-dist = [ { name = "aiofiles" }, { name = "amqtt" }, { name = "bytedance" }, + { name = "chongming-cache" }, { name = "chongming-jwt" }, { name = "chongming-mqtt" }, { name = "chongming-password" }, @@ -707,15 +709,6 @@ wheels = [ { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/35/386550fd60316d1e37eccdda609b074113298f23cef5bddb2049823fe666/dacite-1.9.2-py3-none-any.whl", hash = "sha256:053f7c3f5128ca2e9aceb66892b1a3c8936d02c686e707bee96e19deef4bc4a0", size = 16600, upload-time = "2025-02-05T09:27:24.345Z" }, ] -[[package]] -name = "diskcache" -version = "5.6.3" -source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } -sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc" } -wheels = [ - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19" }, -] - [[package]] name = "eclipse-zenoh" version = "1.10.0" @@ -1096,7 +1089,7 @@ sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/f7/30 [[package]] name = "openai" -version = "3.7.0" +version = "3.8.0" source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } dependencies = [ { name = "anyio" }, @@ -1106,9 +1099,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/ba/6d46da4232f80cb5842280e024242e6fed163418ab81bebc1c83f693bb0b/openai-3.7.0.tar.gz", hash = "sha256:e836eb7effee89df802cd0c7d1bad8de8c993976cf238c44d5b5b844f5aefd38", size = 1455615, upload-time = "2026-09-02T01:30:54.555Z" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/b0/1100c93f93e1c174205ce8d15a049a446f0dc88e9262c1f1f223fe6b9493/openai-3.8.0.tar.gz", hash = "sha256:6138a5a1333a1be9e4d1edea2d160b311542787b029543f87de4961c66358d16", size = 1473819, upload-time = "2026-09-03T19:51:10.495Z" } wheels = [ - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/9d/77e0f43f04ae51a3d21bb08bd372a752856b86ee22648b1458c93a623927/openai-3.7.0-py3-none-any.whl", hash = "sha256:008fa33e0a01dc71039c27355ef8f476eed690e8e48bec18183878dcd32fb841", size = 1699594, upload-time = "2026-09-02T01:30:52.445Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/a4/c7e89d3bfb8b7c7ffa38ec9fe527bccf4df723f752fb11bdb3da6c750b76/openai-3.8.0-py3-none-any.whl", hash = "sha256:514736aa1e4ef1033c1209ad53897392845ccd4f2c4fae6413b2cf5f91c2c926", size = 1740349, upload-time = "2026-09-03T19:51:08.596Z" }, ] [[package]] @@ -1232,6 +1225,7 @@ dependencies = [ ] wheels = [ { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/86/1a8937731f28e0fe6fe603da1fc4a8d27525dcd26f7b723326e8e83a562c/pyarmor-9.2.7-2-py3-none-any.whl", hash = "sha256:7cf7659f822b4da2434b2e19c3adc9f17868c5610d0e53f4dc2321ff9eefbf5d", size = 3074479, upload-time = "2026-08-29T04:16:04.021Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/0a/a95f56b2ecb352080d9f36d3ace313cdcc2b6662cc66ecbdebd137833f8d/pyarmor-9.2.7-3-py3-none-any.whl", hash = "sha256:7dc03740cd31ab38d6064fbd822a1b62aae7d2267aa1dd99be3055f2107b1909", size = 3075842, upload-time = "2026-09-03T08:02:02.361Z" }, ] [[package]] @@ -1596,27 +1590,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.5" +version = "0.16.6" source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } -sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/85/c8e12473c93018f92d19dd988a294202e1c27426c47ec4de53ffb847b8d8/ruff-0.16.5.tar.gz", hash = "sha256:1b88500f9ffbcab3dedb0082c9f9492e91ec3d618aac1236a3e0189938f7040b", size = 4912003, upload-time = "2026-08-27T16:34:18.258Z" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/7c/6adb35d70e7c027e308274557901c7e00fb3407750faf3620c184ae058cb/ruff-0.16.6.tar.gz", hash = "sha256:dcf8a73d2ff77e99dde91244b4da16feba7f14e6beeb4015dee7c5a909e99050", size = 4921251, upload-time = "2026-09-03T16:57:29.037Z" } wheels = [ - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c6/b6/77c90a970fe2dae17a723acbd011043ea97c98d7deacccefdc4ba74ec512/ruff-0.16.5-py3-none-linux_armv6l.whl", hash = "sha256:12e5f673e774c35fbb62f288809c7653b73445f8ecec6b6063fd6ea3521aa14b", size = 10011941, upload-time = "2026-08-27T16:33:41.287Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/46/6cf67cf6411885a1d6f7f6d801682f155536a85176d10b605e2ceffed8bd/ruff-0.16.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eda58a5802de40e7ed5b32b64e0b32539338cc6fcd2c78f61e3ad6a0d79f51c3", size = 10204049, upload-time = "2026-08-27T16:33:44.056Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/fd/c8720ca7a090abf0c2fef4abe8a5ef6e5127ed15196d8886ff75a2b370e2/ruff-0.16.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5ae9a7b9a8875131f40f8fe967cc86abf899779efd663cb7ce3d572d01da7eb", size = 9809037, upload-time = "2026-08-27T16:33:46.257Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/45/a684caacdedaca180f52bacccc40bf0789d2c5a7c75f25324853e9eaedb5/ruff-0.16.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b719b0a1f4d59710d283ab2965f621684a108a9e41da622e3b23f0326cd0025", size = 9964129, upload-time = "2026-08-27T16:33:48.352Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/f2/5d2bcdaca6b5b93d1b4dfc166cd2aebf7680143a1b38a28759df13a94d31/ruff-0.16.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2298f2780ed1be0c5cb1361e32ab7b1467f3cce7dabe101d2210a314f2fe42e9", size = 9821518, upload-time = "2026-08-27T16:33:50.57Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/ff/011cce29accf9257d5974145b733fc653a37985ed6825413a3987cefbfe0/ruff-0.16.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:258f29035a2dd021e7861e631b227a5b3f14e50c1184c9a6a122c5f4576154d7", size = 10534835, upload-time = "2026-08-27T16:33:52.522Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/5a/f0cf109bada9bba0e96c90c21c9f9251803f57225c32d293327a03c710d6/ruff-0.16.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9a4f0432966834019c74d1b7e5c51224305d7713f3d7faf3e7451f1a3be3cde", size = 11252550, upload-time = "2026-08-27T16:33:54.521Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/4d/1d481aaea2046c6a7ed7c291f9004c669cce3c087b6b376ed5b08271e3fe/ruff-0.16.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5eb3a8c3d0ade9cea42b591fd530368e8798380e30e0a308b85a5cf718f09ea", size = 10777949, upload-time = "2026-08-27T16:33:56.88Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/34/ee245ca55f64443233034b3d02b03236b19242004281247c079390b7facd/ruff-0.16.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef0f69e191a13a3c9816f63163c88790cb12cd157bbbb384e9c44745702ab105", size = 10311656, upload-time = "2026-08-27T16:33:59.12Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/4d/c33a333e341c0a2b96c715b52d89a606f5a34cd4ac493cd9b8d0187186b8/ruff-0.16.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0eeab41fbea2c42f98dfb9822cdccda9d24ba38d49f6dc945b5c236d48f0ef29", size = 10532125, upload-time = "2026-08-27T16:34:01.166Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/e1/a64cef78b40192497bb98a27a8aa8f2c98ee9ee15bc97f7712d94ef32937/ruff-0.16.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f0768e9df4300713fff30733c87575f68b6f1d8de41184e505b7fdd9c0c95eaf", size = 10097648, upload-time = "2026-08-27T16:34:03.16Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/4e/4cdc9ed3c3e109d2f71e62572a37457298d7bc7501ec3138babb7ed32bbd/ruff-0.16.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:95cc70cdc7aa80c338de356279d2adbeb2de0f520b9ecd8aba75b94e95e02f91", size = 9829344, upload-time = "2026-08-27T16:34:05.134Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/4a/31ed35ce31729955fc583ee0d176d6e784c1290cb0b0a75cb2134c1ab72a/ruff-0.16.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d185c8398ded1bfd91c0c2cb258346307571eccc473a8490af8c3977399c384a", size = 10277117, upload-time = "2026-08-27T16:34:07.425Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/a0/60356d86687b4b666d593df213f4dc3041750d024cb7bf2cfa81cfd65c2e/ruff-0.16.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fb8e3a3c4c6a784150a7ced53b015f4b253fc2bf97a610886419ead64b4756ef", size = 10711653, upload-time = "2026-08-27T16:34:09.712Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ed/20/656d67f5b25ca9bda4e02b1de25867b2954e1d19e03648060f167ad0f4cc/ruff-0.16.5-py3-none-win32.whl", hash = "sha256:288b0a5f080492fe5635db849f9e2e84aa3cce7b7f0e955997d416c507c76a26", size = 10034250, upload-time = "2026-08-27T16:34:11.8Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/42/ee8e68a207b9127fcde6c3d7e197def432f346cb1af159e1fa14ca0d1cdc/ruff-0.16.5-py3-none-win_amd64.whl", hash = "sha256:ddc6385fb2137f616357ca03d6c74f4be987f80fed4008566b754f6032b8546f", size = 10516714, upload-time = "2026-08-27T16:34:13.963Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/e3/7df5a396e445b9ba49ce9a9437439a4d80042c61c0ade199abf8d16de1ac/ruff-0.16.5-py3-none-win_arm64.whl", hash = "sha256:a64abe90968719b851bb7cedffaa8753fbdbdadab483089682db623f3edc587e", size = 10391564, upload-time = "2026-08-27T16:34:16.064Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/28/9cc1b79639e284ec103f43c88c644db4eb58cbd0ea1ca11f1193435369ac/ruff-0.16.6-py3-none-linux_armv6l.whl", hash = "sha256:61c368c26bf8e973e5ab14a2772de587bc068ea3f9a277f673380749b4898fb8", size = 10015638, upload-time = "2026-09-03T16:56:40.986Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/11/627d342ef727ea7794edf74fe23d60a074b02c3acc2e9436684e782286ca/ruff-0.16.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ecf4f068e2e123e43a26e9db4e19524cc56563912404e83bbfca375757e45a32", size = 10220762, upload-time = "2026-09-03T16:56:44.681Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/d9/b75668ce41e4c8d073d18d6d08672ba6906ce45d5c06ea4fdb2e84ce3853/ruff-0.16.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99b62ea33baf130f50368798d841f0d95527b6d817bf31817b65dd058f1d314c", size = 9835082, upload-time = "2026-09-03T16:56:47.142Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/97/123ab10b05cde889c107c20f5a9774955104b5552796a2a8584b089ae8eb/ruff-0.16.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fbf89013f2bb3f6835a6038ff658dc8a1b38c98dc8e724b964168ad4e881876", size = 9949304, upload-time = "2026-09-03T16:56:49.813Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/58/a4a2c59dd2e5b85929c912d9cac3056eb9ee8c7e75e9b9fe3e109174966b/ruff-0.16.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56a67065e22efa6bc4d498299d3bb06c0c90aace8fac2068b5a12f9dc4d8d51d", size = 9840612, upload-time = "2026-09-03T16:56:52.368Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/6a/ff8c8626a786c4f49d48ced4a752dadbca65f5263005f9c2416578194694/ruff-0.16.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e25cc89174874b176a157e4428d66761c2c0c006654419bf384f967f361ff1b1", size = 10543465, upload-time = "2026-09-03T16:56:55.089Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/bb/c47535923365f337b82e28192e4e9eef2176511007cfd99a62fc22df5dad/ruff-0.16.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700580ed5303723cb3c11c2f1d2a8913ce77b7ea86646dddb887f5417a9ba70", size = 11267576, upload-time = "2026-09-03T16:56:57.791Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/50/e5119a5212b5cd63b51e1f4b25e7bd636a6668fc069a3160b108ad7e3c16/ruff-0.16.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15f1d0b6e165a6e56567befb6629f8209271311d990bae0f37e6d065035ef5f3", size = 10781993, upload-time = "2026-09-03T16:57:00.666Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/98/083d8b4ef3c51a0d19db84367791cbe9f44e4b53343d19dfa83556e1cd9a/ruff-0.16.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d72c591a96986ee4268860e2b7235082129ca5e4cb9cbba653a4b57c11893757", size = 10317748, upload-time = "2026-09-03T16:57:03.428Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/29/68f7ff2c5ad95f19f00627ac2de95644e25fe47371ea60b2db1fd952315e/ruff-0.16.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:65a006baa18f33324325814c864daef03541d51564b98c517610ea756ab7003e", size = 10540096, upload-time = "2026-09-03T16:57:06.182Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c4/f9/79a8f6de85968641d68a7863aeec577551924ef066a990a48ff93167beab/ruff-0.16.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cd02a7bf1a21a8735228a3e8c95a9dc5cf86bd2a52194f4aaae2a5755b4de0f4", size = 10100494, upload-time = "2026-09-03T16:57:09.194Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/e8/b81a22d9b90c00b892ccf2fa2ac36fa95de4c13ab85aea3e73795cfe4651/ruff-0.16.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:31b36f1e5ad85e0737f09d2be4e512e2e283583c14015da3b9dc07359ac0fc88", size = 9843663, upload-time = "2026-09-03T16:57:12.168Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/aa/54f516ec5e5a11c4afdceb1c454ebb054ffb96e4f4a1705580b4346abd35/ruff-0.16.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:61029b4ab4aa723fd3064fab96b1d814492596bf0c792679fffcbde1e1679953", size = 10282461, upload-time = "2026-09-03T16:57:15.077Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/0b/38d0aa8aa32372b96dc44f97b22e576c4147808271aab7b2cb1e353d4445/ruff-0.16.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9ac8998457832c2061709d900856b7ad271dace0cb41f346588d540162bfa718", size = 10728808, upload-time = "2026-09-03T16:57:17.797Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/e5/9e274e24eeb027640ffc7442f21239f16d17f47acec15ae34f32e03a5c79/ruff-0.16.6-py3-none-win32.whl", hash = "sha256:0b87d9d16fcb63e8018423ca1d50b7260f15cb2da33e30db4baad4183a948c25", size = 10049212, upload-time = "2026-09-03T16:57:20.55Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/31/72472449414223ed1a2da236b992adbb1a2ae59e34794574810f60ce068e/ruff-0.16.6-py3-none-win_amd64.whl", hash = "sha256:10d21c51c3495d8eaea7b703a16592117ea6eb1d649e36335aa965ff1173eb39", size = 10556402, upload-time = "2026-09-03T16:57:23.501Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/07/d781f8f8e1ac24bef9f3269cf62ffb1407ca24c3a8f12e5e22874f90528c/ruff-0.16.6-py3-none-win_arm64.whl", hash = "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5", size = 10412850, upload-time = "2026-09-03T16:57:26.416Z" }, ] [[package]] @@ -1884,7 +1878,7 @@ wheels = [ [[package]] name = "volcengine-python-sdk" -version = "5.0.47" +version = "5.0.48" source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } dependencies = [ { name = "certifi" }, @@ -1892,9 +1886,9 @@ dependencies = [ { name = "six" }, { name = "urllib3" }, ] -sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/2a/25dcb01d4f2a4629bfd46dee0972d57c590e29e9d7271a918985593ba414/volcengine_python_sdk-5.0.47.tar.gz", hash = "sha256:64dc6f4c591e27972f5331f097f0d5926adf96f3643c264df0cc0007f6bf0d8d", size = 10748617, upload-time = "2026-08-27T12:07:11.774Z" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/26/33282a2954cfd0cac2f596846953cb04ccbc6fe6e439499fe993c86c19fc/volcengine_python_sdk-5.0.48.tar.gz", hash = "sha256:bb78995e35c34ad7a24e535826a1cb197e1209783c8a7dc11b001a09cb4bc48c", size = 10754352, upload-time = "2026-09-03T12:18:26.556Z" } wheels = [ - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cf/d6/8ddd4f13716af4fb86057b48d0166e915ab013a1e287d29b6b11cdbde891/volcengine_python_sdk-5.0.47-py2.py3-none-any.whl", hash = "sha256:f2605e9a2a22143557fb9a58c9f1523032448cd6282add6387ac57df0510519b", size = 43168897, upload-time = "2026-08-27T12:07:04.749Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/be/29eefbbdae397908c8a05eee73208229065481f2d797f5f2c720a6a02193/volcengine_python_sdk-5.0.48-py2.py3-none-any.whl", hash = "sha256:05c15f9138e016e479cdf29971d1e5fedbb9e8f4378930c68e2a9622d087995c", size = 43152725, upload-time = "2026-09-03T12:18:20.217Z" }, ] [package.optional-dependencies]