diff --git a/code/bark-notify/.gitignore b/code/bark-notify/.gitignore deleted file mode 100644 index a18610e..0000000 --- a/code/bark-notify/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.venv/ -__pycache__/ -*.pyc -*.egg-info/ -dist/ -build/ diff --git a/code/bark-notify/README.md b/code/bark-notify/README.md deleted file mode 100644 index 51f165b..0000000 --- a/code/bark-notify/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# bark-notify - -> 📍 工作区位置:`code/bark-notify/`(推送工具)。整体目录规范见根 `CLAUDE.md`。 - -向手机发送 **Bark 通知** 的 Python 模块,覆盖全部通知类型与参数。uv 管理环境,**零第三方依赖**(仅标准库)。 - -- 服务器:标准 Bark 服务(`Server: Bark`),默认 `http://47.108.226.213:10086`,可用 `BARK_BASE_URL` 覆盖。 -- 设备密钥:默认 `pvd3kWwHNFjKXNjiC2sMe7`,可用 `BARK_DEVICE_KEY` 覆盖。 - -## 安装 - -```bash -cd bark-notify -uv sync # 创建 .venv 并以可编辑方式安装本项目 -uv run bark-notify --help # 验证 CLI 可用 -``` - -## 命令行用法 - -```bash -# 基础通知 -uv run bark-notify "标题" "正文" - -# 各级别(default / active / timeSensitive / critical / passive) -uv run bark-notify "紧急" "服务器告警" --level critical --sound alarm.caf --volume 10 -uv run bark-notify "勿扰也提醒" "会议还有 5 分钟" --level timeSensitive -uv run bark-notify "只进通知中心" "静默内容" --level passive - -# 特殊功能 -uv run bark-notify "Markdown" "### 标题\n- 项1\n- 项2" # 正文原生支持 Markdown -uv run bark-notify "分组" "第1条" --group 演示 -uv run bark-notify "跳转" "点击打开百度" --url https://www.baidu.com -uv run bark-notify "复制" "长按复制此文本" --copy "OPC 云超服" -uv run bark-notify "带图" "附带图片" --image https://picsum.photos/seed/x/400/300 -uv run bark-notify "角标" "数字角标" --badge 5 --ttl 60 -``` - -## Python API - -```python -from bark_notify import BarkClient, Level, Notification, Sound - -client = BarkClient() # 也可 BarkClient(base_url=..., device_key=...) - -client.push(Notification( - title="部署完成", - body="**v2.4.0** 已上线 ✅", - level=Level.ACTIVE, - group="部署", - url="https://example.com/status", -)) - -# GET 快捷方式(适合快速测试,参数走查询串) -client.notify("测试", "正文", level="critical", group="x") -``` - -## 参数一览 - -| 参数 | CLI | 说明 | -|------|-----|------| -| `title` / `body` | 位置参数 | 标题 / 正文(正文支持 Markdown) | -| `level` | `--level` | `default` `active` `timeSensitive` `critical` `passive` | -| `sound` | `--sound` | 内置铃声名或远程铃声 URL | -| `badge` | `--badge` | 应用角标数字 | -| `group` | `--group` | 同组通知折叠 | -| `icon` | `--icon` | 通知图标 URL | -| `image` | `--image` | 附带图片 URL | -| `copy` | `--copy` | 长按复制的文本 | -| `url` | `--url` | 点击跳转 URL | -| `is_archive` | `--archive` | 存入历史记录 | -| `call` | `--call` | 连续响铃(呼叫声) | -| `ttl` | `--ttl` | 过期秒数,超时自动删除 | -| `volume` | `--volume` | critical 通知音量 0-10 | - -## 级别差异速查 - -| 级别 | 横幅 | 声音 | 勿扰可送达 | 说明 | -|------|:----:|:----:|:---------:|------| -| `default` | ✅ | ✅ | ❌ | 跟随系统 | -| `active` | ✅ | ✅ | ❌ | 标准活跃通知 | -| `timeSensitive` | ✅ | ✅ | ✅ | 时效性 | -| `critical` | ✅ | ✅ | ✅ | 绕过静音强制响铃,需授权 | -| `passive` | ❌ | ❌ | — | 仅通知中心 | - -## 内置铃声 - -`alarm.caf` `alarms.caf` `anticipate.caf` `bell.caf` `bloom.caf` `calypso.caf` `chime.caf` `choir.caf` `complete.caf` `completion.caf` `electronic.caf` `emergency.caf` `fanfare.caf` `glass.caf` `goblin.caf` `healthnotification.caf` `horn.caf` `ladders.caf` `mario.caf` `minuet.caf` `multiwayinvitation.caf` `newmail.caf` `newsflash.caf` `noir.caf` `payment.caf` `shake.caf` `sherwoodfanfare.caf` `silence.caf` `spell.caf` `suspense.caf` `telegraph.caf` `tiptoes.caf` `typewriters.caf` `update.caf` - -## 环境变量 - -| 变量 | 默认值 | 说明 | -|------|--------|------| -| `BARK_BASE_URL` | `http://47.108.226.213:10086` | 服务器地址 | -| `BARK_DEVICE_KEY` | `pvd3kWwHNFjKXNjiC2sMe7` | 设备密钥 | diff --git a/code/bark-notify/examples/send_all_types.py b/code/bark-notify/examples/send_all_types.py deleted file mode 100644 index 33818e0..0000000 --- a/code/bark-notify/examples/send_all_types.py +++ /dev/null @@ -1,85 +0,0 @@ -"""发送全部类型的 Bark 通知,用于在手机上逐项核对效果。 - -用法:: - - uv run python examples/send_all_types.py - -可选参数: - - --skip active critical 跳过部分级别 - --device-key 指定设备密钥 - --base-url 指定服务器地址 - -每发一条会在终端打印进度,手机端请对照标题核对。 -""" - -from __future__ import annotations - -import argparse -import time -from typing import List, Optional - -from bark_notify import BarkClient, Level, Notification - - -def main(argv: Optional[List[str]] = None) -> int: - parser = argparse.ArgumentParser(description="发送全部类型的 Bark 通知") - parser.add_argument( - "--skip", - nargs="*", - choices=[level.value for level in Level], - default=[], - help="要跳过的级别(可多个)", - ) - parser.add_argument("--device-key", help="Bark 设备密钥") - parser.add_argument("--base-url", help="Bark 服务器地址") - args = parser.parse_args(argv) - - client = BarkClient(base_url=args.base_url, device_key=args.device_key) - - # 第一波:五种级别,逐项核对打扰程度差异 - cases = [ - ("基础通知(default)", Level.DEFAULT, {}), - ("活跃通知(active)", Level.ACTIVE, {}), - ("时效性通知(timeSensitive)", Level.TIME_SENSITIVE, {}), - ("紧急通知(critical)", Level.CRITICAL, {"sound": "alarm.caf", "volume": 10}), - ("静默通知(passive)", Level.PASSIVE, {}), - ] - - for i, (title, level, extra) in enumerate(cases, start=1): - if level.value in args.skip: - print(f"[{i}/{len(cases)}] 跳过 {level.value}") - continue - notification = Notification( - title=f"[{i}] {title}", - body=f"级别: {level.value}\n由 examples/send_all_types.py 发送", - level=level, - **extra, - ) - client.push(notification) - print(f"[{i}/{len(cases)}] 已发送 {level.value}") - time.sleep(1) - - # 第二波:特殊功能 - features = [ - ("[6] Markdown 富文本", "### 任务清单\n- [x] 发基础通知\n- [x] 发特殊通知\n- [ ] 核对效果\n\n**加粗** 与 *斜体* 测试 ✅", {}), - ("[7] 自定义铃声", "使用 bells 铃声,不同于默认铃声。", {"sound": "bell.caf"}), - ("[8] 分组折叠", "属于「演示分组」组,下一条同组。", {"group": "演示分组"}), - ("[9] 分组折叠", "属于「演示分组」组,会与上一条折叠。", {"group": "演示分组"}), - ("[10] 跳转链接", "点击这条会打开百度。", {"url": "https://www.baidu.com"}), - ("[11] 复制文本", "长按这条可复制「OPC 云超服」。", {"copy": "OPC 云超服"}), - ("[12] 带图片", "附带一张示例图片,需设备可加载外网图。", {"image": "https://picsum.photos/seed/bark/400/300"}), - ] - - for i, (title, body, extra) in enumerate(features, start=6): - notification = Notification(title=title, body=body, **extra) - client.push(notification) - print(f"[{i}] 已发送 {title}") - time.sleep(1) - - print("全部发送完成,请到手机端核对。") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/code/bark-notify/pyproject.toml b/code/bark-notify/pyproject.toml deleted file mode 100644 index c5a7fa7..0000000 --- a/code/bark-notify/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ -[project] -name = "bark-notify" -version = "0.1.0" -description = "向手机发送 Bark 通知的 Python 模块,覆盖全部通知类型与参数(uv 管理)" -readme = "README.md" -requires-python = ">=3.11" -dependencies = [] - -[project.scripts] -bark-notify = "bark_notify.cli:main" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.wheel] -packages = ["src/bark_notify"] diff --git a/code/bark-notify/src/bark_notify/__init__.py b/code/bark-notify/src/bark_notify/__init__.py deleted file mode 100644 index 0a54cc1..0000000 --- a/code/bark-notify/src/bark_notify/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -"""bark_notify — 向手机发送 Bark 通知,覆盖全部通知类型与参数。 - -用法示例:: - - from bark_notify import BarkClient, Level, Notification - - client = BarkClient() - client.push(Notification(title="你好", body="世界", level=Level.CRITICAL, sound="alarm.caf")) -""" - -from .client import BarkClient, BarkError -from .types import Level, Notification, Sound - -__version__ = "0.1.0" -__all__ = ["BarkClient", "BarkError", "Level", "Notification", "Sound"] diff --git a/code/bark-notify/src/bark_notify/cli.py b/code/bark-notify/src/bark_notify/cli.py deleted file mode 100644 index 7b926d8..0000000 --- a/code/bark-notify/src/bark_notify/cli.py +++ /dev/null @@ -1,87 +0,0 @@ -"""bark-notify 命令行入口。""" - -from __future__ import annotations - -import argparse -import json -import sys -from typing import List, Optional - -from .client import BarkClient, BarkError -from .types import Level, Notification - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - prog="bark-notify", - description="向手机发送 Bark 通知,覆盖全部通知类型与参数", - ) - parser.add_argument("title", help="通知标题") - parser.add_argument("body", nargs="?", default="", help="通知正文(支持 Markdown)") - parser.add_argument("--subtitle", help="副标题") - parser.add_argument( - "--level", - choices=[level.value for level in Level], - default=Level.DEFAULT.value, - help="通知级别(默认 default)", - ) - parser.add_argument("--sound", help="铃声名(如 alarm.caf)或远程铃声 URL") - parser.add_argument("--badge", type=int, help="应用角标数字") - parser.add_argument("--group", help="分组名:同名通知折叠显示") - parser.add_argument("--icon", help="通知图标 URL") - parser.add_argument("--image", help="附带图片 URL") - parser.add_argument("--copy", help="长按通知复制的文本") - parser.add_argument("--url", help="点击通知跳转的 URL") - parser.add_argument("--archive", action="store_true", help="存入历史记录") - parser.add_argument("--call", action="store_true", help="连续响铃提醒(呼叫声)") - parser.add_argument("--ttl", type=int, help="过期时间(秒)") - parser.add_argument("--volume", type=int, choices=range(0, 11), help="关键通知音量 0-10") - parser.add_argument("--device-key", help="Bark 设备密钥(默认读 BARK_DEVICE_KEY)") - parser.add_argument("--base-url", help="Bark 服务器地址(默认读 BARK_BASE_URL)") - parser.add_argument("--json", dest="raw_json", action="store_true", help="输出服务器原始 JSON") - return parser - - -def main(argv: Optional[List[str]] = None) -> int: - args = build_parser().parse_args(argv) - - client_kwargs = {} - if args.base_url: - client_kwargs["base_url"] = args.base_url - if args.device_key: - client_kwargs["device_key"] = args.device_key - client = BarkClient(**client_kwargs) - - notification = Notification( - title=args.title, - body=args.body, - subtitle=args.subtitle, - level=Level(args.level), - sound=args.sound, - badge=args.badge, - group=args.group, - icon=args.icon, - image=args.image, - copy=args.copy, - url=args.url, - is_archive=args.archive, - call=args.call, - ttl=args.ttl, - volume=args.volume, - ) - - try: - result = client.push(notification) - except BarkError as exc: - print(f"发送失败: {exc}", file=sys.stderr) - return 1 - - if args.raw_json: - print(json.dumps(result, ensure_ascii=False)) - else: - print(f"发送成功: {result.get('message')}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/code/bark-notify/src/bark_notify/client.py b/code/bark-notify/src/bark_notify/client.py deleted file mode 100644 index 197e2fe..0000000 --- a/code/bark-notify/src/bark_notify/client.py +++ /dev/null @@ -1,137 +0,0 @@ -"""Bark 推送客户端:基于标准 Bark 服务器 HTTP API,零第三方依赖。""" - -from __future__ import annotations - -import json -import os -import urllib.error -import urllib.parse -import urllib.request -from typing import Any, Dict, Optional, Union - -from .types import Level, Notification, Sound - -DEFAULT_BASE_URL = os.environ.get("BARK_BASE_URL", "http://47.108.226.213:10086") -DEFAULT_DEVICE_KEY = os.environ.get("BARK_DEVICE_KEY", "pvd3kWwHNFjKXNjiC2sMe7") - - -class BarkError(RuntimeError): - """Bark 服务器返回失败或网络异常。""" - - -def _value_of(value: Union[Level, Sound, str, None]) -> Optional[str]: - """枚举成员取其值,普通字符串原样返回。""" - if isinstance(value, (Level, Sound)): - return value.value - return value - - -def _normalize_query(params: Dict[str, Any]) -> Dict[str, Any]: - """把 Python 值转成 GET 查询参数可接受的类型。""" - out: Dict[str, Any] = {} - for key, value in params.items(): - if value is None: - continue - if isinstance(value, bool): - out[key] = "true" if value else "false" - else: - out[key] = _value_of(value) - return out - - -class BarkClient: - """Bark 通知客户端。 - - 支持两种推送方式: - - - :meth:`push`:通过 ``POST /push`` 发送,支持全部参数(推荐)。 - - :meth:`notify`:通过 ``GET ///<body>`` 发送,适合快速测试。 - - 地址与密钥默认从环境变量读取,也可在构造时显式传入: - - - ``BARK_BASE_URL``:服务器地址,默认 ``http://47.108.226.213:10086``。 - - ``BARK_DEVICE_KEY``:设备密钥。 - """ - - def __init__( - self, - base_url: Optional[str] = None, - device_key: Optional[str] = None, - timeout: float = 10.0, - ) -> None: - self.base_url = (base_url or DEFAULT_BASE_URL).rstrip("/") - self.device_key = device_key or DEFAULT_DEVICE_KEY - self.timeout = timeout - - def push(self, notification: Notification) -> Dict[str, Any]: - """通过 ``POST /push`` 推送一条完整通知,返回服务器 JSON 响应。""" - payload = self._payload(notification) - payload["device_key"] = self.device_key - data = json.dumps(payload, ensure_ascii=False).encode("utf-8") - return self._request("/push", data=data) - - def notify(self, title: str, body: str = "", **kwargs: Any) -> Dict[str, Any]: - """GET 快捷推送:``/<key>/<title>/<body>?<params>``。 - - ``kwargs`` 中除 ``title/body`` 外的键会转成查询参数, - 布尔值转 ``true/false``,例如 ``level="critical"``、``group="x"``。 - """ - encoded_title = urllib.parse.quote(title) - encoded_body = urllib.parse.quote(body) - path = f"/{self.device_key}/{encoded_title}/{encoded_body}" - params = _normalize_query(kwargs) - params.pop("title", None) - params.pop("body", None) - if params: - path += "?" + urllib.parse.urlencode(params) - return self._request(path) - - # ---------- 内部工具 ---------- - - @staticmethod - def _payload(notification: Notification) -> Dict[str, Any]: - """把 Notification 转成 POST /push 的 JSON body(camelCase 键)。""" - payload: Dict[str, Any] = { - "title": notification.title, - "body": notification.body, - "level": _value_of(notification.level) or Level.DEFAULT.value, - } - optional: Dict[str, Any] = { - "subtitle": notification.subtitle, - "sound": _value_of(notification.sound), - "badge": notification.badge, - "group": notification.group, - "icon": notification.icon, - "image": notification.image, - "copy": notification.copy, - "url": notification.url, - "isArchive": notification.is_archive, - "call": notification.call, - "ttl": notification.ttl, - "volume": notification.volume, - } - for key, value in optional.items(): - if value is not None: - payload[key] = value - return payload - - def _request(self, path: str, data: Optional[bytes] = None) -> Dict[str, Any]: - url = self.base_url + path - req = urllib.request.Request(url, data=data) - if data is not None: - req.add_header("Content-Type", "application/json") - try: - with urllib.request.urlopen(req, timeout=self.timeout) as resp: - raw = resp.read().decode("utf-8") - except urllib.error.HTTPError as exc: - raw = exc.read().decode("utf-8", errors="replace") - except urllib.error.URLError as exc: - raise BarkError(f"网络错误: {exc}") from exc - - try: - result = json.loads(raw) - except json.JSONDecodeError: - result = {"code": -1, "message": raw.strip()} - if result.get("code") != 200: - raise BarkError(f"Bark 返回异常: code={result.get('code')} message={result.get('message')}") - return result diff --git a/code/bark-notify/src/bark_notify/types.py b/code/bark-notify/src/bark_notify/types.py deleted file mode 100644 index 387c29e..0000000 --- a/code/bark-notify/src/bark_notify/types.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Bark 通知的类型定义:级别枚举、铃声枚举与通知数据类。""" - -from __future__ import annotations - -import enum -from dataclasses import dataclass -from typing import Optional - - -class Level(enum.Enum): - """通知级别:决定在 iOS 上的打扰程度。""" - - DEFAULT = "default" - #: 跟随系统默认设置,标准横幅 + 默认铃声。 - ACTIVE = "active" - #: 标准活跃通知。 - TIME_SENSITIVE = "timeSensitive" - #: 时效性通知:即使开启勿扰模式也能送达。 - CRITICAL = "critical" - #: 关键通知:绕过静音/勿扰开关,强制响铃(需设备授权此应用)。 - PASSIVE = "passive" - #: 被动通知:仅进通知中心,无横幅、无声音。 - - -class Sound(enum.Enum): - """Bark 内置铃声(也可直接传自定义远程铃声 URL)。""" - - DEFAULT = "default" - ALARM = "alarm.caf" - ALARMS = "alarms.caf" - ANTICIPATE = "anticipate.caf" - BELL = "bell.caf" - BLOOM = "bloom.caf" - CALYPSO = "calypso.caf" - CHIME = "chime.caf" - CHOIR = "choir.caf" - COMPLETE = "complete.caf" - COMPLETION = "completion.caf" - ELECTRONIC = "electronic.caf" - EMERGENCY = "emergency.caf" - FANFARE = "fanfare.caf" - GLASS = "glass.caf" - GOBLIN = "goblin.caf" - HEALTH_NOTIFICATION = "healthnotification.caf" - HORN = "horn.caf" - LADDERS = "ladders.caf" - MARIO = "mario.caf" - MINUET = "minuet.caf" - MULTIWAY_INVITATION = "multiwayinvitation.caf" - NEW_MAIL = "newmail.caf" - NEWS_FLASH = "newsflash.caf" - NOIR = "noir.caf" - PAYMENT = "payment.caf" - SHAKE = "shake.caf" - SHERWOOD_FANFARE = "sherwoodfanfare.caf" - SILENCE = "silence.caf" - SPELL = "spell.caf" - SUSPENSE = "suspense.caf" - TELEGRAPH = "telegraph.caf" - TIPTOES = "tiptoes.caf" - TYPEWRITERS = "typewriters.caf" - UPDATE = "update.caf" - - -@dataclass -class Notification: - """一条完整的 Bark 通知,覆盖服务器支持的全部参数。""" - - title: str - body: str = "" - #: 副标题。 - subtitle: Optional[str] = None - #: 通知级别,默认 ``Level.DEFAULT``。 - level: Level = Level.DEFAULT - #: 铃声名(如 ``alarm.caf``)或远程铃声 URL。 - sound: Optional[str] = None - #: 应用角标数字。 - badge: Optional[int] = None - #: 分组名:同名的通知会在通知中心折叠成一组。 - group: Optional[str] = None - #: 通知图标 URL。 - icon: Optional[str] = None - #: 通知附带的图片 URL。 - image: Optional[str] = None - #: 长按通知时复制的文本。 - copy: Optional[str] = None - #: 点击通知跳转的 URL。 - url: Optional[str] = None - #: 是否存入 Bark 历史记录。 - is_archive: bool = False - #: 是否连续响铃提醒(呼叫声)。 - call: bool = False - #: 过期时间(秒),超时后通知自动删除。 - ttl: Optional[int] = None - #: 关键通知的音量(0-10),需要 critical 级别。 - volume: Optional[int] = None diff --git a/code/bark-notify/uv.lock b/code/bark-notify/uv.lock deleted file mode 100644 index 4f9acee..0000000 --- a/code/bark-notify/uv.lock +++ /dev/null @@ -1,8 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.11" - -[[package]] -name = "bark-notify" -version = "0.1.0" -source = { editable = "." } diff --git a/云超服一体化架构设计方案.md b/云超服一体化架构设计方案.md index fa86b64..2b15ff4 100644 --- a/云超服一体化架构设计方案.md +++ b/云超服一体化架构设计方案.md @@ -447,6 +447,8 @@ dpm 后端新增数据源层,以**园区账号 token** 拉取**本园区**数 - 领域层是核心模型与规则,**不依赖**业务层 / 接口层 / 基础设施层的具体实现(只面向其接口)。 - 基础设施层实现接口,**不得反向依赖**上层。 +**全异步**:接口层 `async def` → 业务层 async → 仓储 async(SQLAlchemy async),事件循环内杜绝阻塞调用;外部 HTTP 用 `httpx.AsyncClient`。见 13.4。 + ### 13.2 算力通信规范 - **内部**:FastAPI(server-core)与**算力调度服务**(compute-engine / 算力引擎)经**内部 IP / loopback** 交互(`http://127.0.0.1:3000`),不经过公网。 @@ -460,6 +462,22 @@ server-core 的工程化(数据目录 / 容器启动 / 运维脚本)参考 ` - **`serverrun/`**:容器与微服务启动(`start-all.sh` 编排、各服务 `docker-compose.yml`、`envcopy.sh` 配置加密、`encrypt-certs.sh` 证书加密)。 - **`ops/`**:运维脚本(`init_admin` / `db_backup` / 数据迁移 / `clear_redis_tokens` / 算力套餐与分区初始化等)。 +### 13.4 异步与并发(设计之初即考虑) + +**全异步调用链**:接口层 `async def` → 业务层 async → 仓储 async(SQLAlchemy async)。事件循环内**杜绝阻塞**(不用 `time.sleep` / 同步 IO;外部调用统一 `httpx.AsyncClient` 带超时 / 重试 / 熔断)。 + +| 服务 | 异步方案 | 要点 | +|------|---------|------| +| **MySQL** | SQLAlchemy 2.0 `create_async_engine` + async session(`asyncmy` / `aiomysql`) | 连接池(`pool_size` / `max_overflow` / `pool_pre_ping`);**每请求独立 async session**(`async with` 管事务边界);开发环境 `aiosqlite` | +| **Redis** | `redis.asyncio` | 会话 / 缓存 / 限流 / **分布式锁**(`SET NX EX`);连接池 | +| **OSS 对象存储** | 异步封装(阿里云 OSS / MinIO,`aioboto3` 或 oss2 线程池包装) | 上传异步、断点续传、回调通知,不阻塞请求线程 | + +**并发正确性**: +- 写操作(抢单 / 扣款 / 报名 / 领券)用**行锁**(`SELECT ... FOR UPDATE`)+ 幂等键,防并发重复。 +- 乐观锁(`version` 字段)处理并发更新冲突。 +- 事务边界清晰:多表写(如 `_adopt_phone`、结算 + 佣金)包在一个事务内。 +- 外部依赖(算力引擎 / 云厂商)异步调用 + 超时重试,失败可降级。 + --- ## 附:与既有设计文档的关系