WePush Documentation

专注批量推送的小而美的工具,目前支持:模板消息-公众号、模板消息-小程序、微信客服消息、微信企业号/企业微信消息、阿里云短信、阿里大于模板短信 、腾讯云短信、云片网短信、E-Mail、HTTP请求、钉钉、华为云短信、百度云短信、又拍云短信、七牛云短信

4,687 stars Java Java 36 files · ~50,977 tokens #gui#java#mail#message#push#send#sender#sms
RAW Doc

Technical Documentation: rememberber/WePush

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`readme.md`)

WePush

专注批量推送的小而美的工具




产品线与版本选择

WePush 采用 Classic 与 Next 双轨发展。两条产品线彼此独立,允许按各自需要演进和拥有重复代码;Next 不会影响 Classic 的安装与使用。

产品线 当前定位 适合场景 入口
WePush Classic 稳定桌面客户端 微信、短信、邮件、HTTP 等成熟批量推送场景 Classic 下载
WePush Next 1.2.0 Stable 三平台离线自部署、1.x 兼容承诺、Service/Agent、资源治理、AI 助手接入、可恢复运维、WebUI/Desktop 与可扩展 Provider Next 1.2.0 下载

当前源码的 Classic 与 Next 均要求 JDK 25+ 构建;不含运行时的发行包及 Java SDK 要求 Java 25+。完整安装包内置 Java 25 运行时。

AI 助手接入:Codex、MCP 与 Skill

Classic 和 Next 均支持让 Codex 等 AI 助手使用 WePush。可一键安装 Codex MCP + Skill、仅安装 Skill,或生成通用 stdio MCP 配置给其他兼容客户端。两版独立实现,安装名称不同,可以同时使用。

此功能随 Classic 当前发行版和 Next 1.2.0 提供。更早的 Next 1.1.0 及不含该入口的旧 Classic 安装包需要升级后再使用。

版本 接入入口 MCP / Skill 名称 主要能力
Classic 应用 → AI 助手接入 wepush-classic 查询已有任务、查看发送预览、空跑、授权发送、查询进度与历史
Next 设置 → AI 助手接入 wepush 发现渠道与 Schema、查询资源、创建消息/受众/任务、空跑、授权发送、查询结果与控制运行

快速开始:

  1. 在 WePush 中配置渠道账号。Classic 还需保存消息、人群和任务;Next 需启动 Service 并核对 Service URL 与 Workspace。
  2. 在 Classic 或 Next Desktop 的接入页面点击 一键接入 Codex,再重新连接 Codex MCP,必要时重启 Codex。两版桌面安装均使用各自自带的运行时,无需额外安装 Node.js 或 Java。
  3. 让助手先查询并空跑任务,例如:“用 WePush Classic 列出任务,空跑任务 1,并告诉我结果。”正式发送前核对预览中的消息与人群,按用户授权执行,再查询实际运行结果。

使用 Next WebUI 时,在接入页面下载安装器并执行页面生成的命令,需要 Node.js 24+。仅使用 Skill 可点击 仅安装 Skill;其他支持 stdio MCP 的客户端可使用 Classic 的 复制通用 MCP 配置 或 Next Desktop 的 导出通用 MCP 配置。

  • **Classic 执行范围:**已保存的手动固定线程任务,线程数 1~100,关闭任务的结果邮件提醒;使用期间保持 Classic 运行。账号、消息、人群及任务的编辑仍在 Classic 界面完成。
  • **Next 运行要求:**保持 Service 可达;启用认证后,在 AI 客户端环境中设置 WEPUSH_API_TOKEN。安装器不复制登录 Token,调用遵循已有 Workspace 权限。
  • **发送防重:**正式发送使用预览确认令牌和请求编号;超时后查询原运行,重试时复用原请求编号与参数,结果不明时不自动重发。

安装目录、客户端配置、完整工具列表及排错说明见 Classic 接入指南 和 Next 接入指南。

WePush Next Stable

Next 是位于 next/ 的完整新架构产品线,包含 Core Engine、Provider SPI、可安装 Service、远程 Agent、Remote/Embedded Java SDK、React WebUI 和 Electron Desktop。1.2.0 是兼容 1.1.0 的稳定 Minor 发行版,在既有 1.x 兼容承诺之上提供 Codex MCP/Skill 接入,并把 Java 运行基线升级到 25。

Next 的长期定位是由用户自行下载、安装、部署和运维的开源产品。项目不建设承载用户业务数据的官方公共 SaaS,不提供注册、计费、订阅或公共租户平台,也不规划云 KMS/Secret Manager 集成。用户可以在自己的环境中运行 Standalone,或自行搭建 Server/HA 和远程 Agent。

`1.2.0` 重点更新

  • Desktop 一键安装 Codex MCP 与 Skill;WebUI 和发行包提供 wepush-ai.mjs。正式发送仍使用预览确认、幂等键和 Workspace 权限。
  • Java 运行基线升级到 25。完整包携带 Java 25 运行时;精简包和 Java SDK 需要系统 Java 25+。
  • 不新增数据库迁移。运营商插件、Workspace 资源治理、脱敏诊断、跨 Run 熔断、PostgreSQL 通知、Multipart Artifact 和 WebUI 主题继续可用。

Next 组件

组件 主要职责 形态与边界
Core / Engine 执行批量任务,管理并发、重试、暂停、恢复、取消、结果与运行事件 纯 Java 无界面执行内核,不直接提供网络 API,也不依赖 Service、UI 或具体 Provider
Provider SPI / Provider 定义消息渠道扩展契约,负责账号校验、消息渲染和实际发送 Core 只面向 SPI;内置 HTTP 和 8 个标准渠道,另有 CMPP/SMGP/SGIP/SMPP 独立签名插件
Service 提供配置、调度、运行控制、Secret、Artifact、审计、REST/SSE、OpenAPI 和 Agent 控制面 可前台运行或安装为 Linux systemd、macOS launchd、Windows Service;本机模式可内嵌 Core Engine
Agent 在独立主机接收 Lease,使用 Core Engine 执行任务并回传事件、结果和 Artifact 可独立安装,通过 gRPC 主动连接 Service;本机内嵌执行时不需要 Agent
Remote Java SDK 让 Java 应用通过强类型客户端调用远程 Service API 已实现;只依赖公开的 service-api 契约,不依赖 Core、Engine 或具体 Provider
Embedded Java SDK 让 Java 应用在自己的进程内直接装配 Core Engine 和选定 Provider,无需启动 Service 1.2.0 Java SDK 附件包含 HTTP 与标准渠道 Provider;业务应用仍须显式选择
WebUI 提供可视化配置、任务与调度、运行中心、Agent 观察和动态调试 API 文档 TypeScript + Vite + React,可由 Service 直接托管,也可在开发环境独立运行
Desktop UI 提供与 WebUI 一致的桌面管理体验和安全 Electron 外壳 连接并管理已安装的本机 Service,使用系统安全存储保存 Token;不把 Service 内嵌进 UI

当前典型调用关系为:WebUI / Desktop UI / Remote Java SDK → Service API → Service → 内嵌 Core Engine;远程执行时则由 Service → Agent → Core Engine → Provider 完成发送。进程内集成使用 业务 Java 应用 → Embedded Java SDK → Core Engine → Provider,不经过 Service。

第一次使用请从《WePush Next 对外使用指南》开始。macOS/Windows 发行物按项目约定不使用商业代码签名,请只从 next-v1.2.0 GitHub Release 下载,并使用同一 Release 中的 SHA256SUMS 校验完整性。

WePush Classic

Classic 是现有稳定客户端,继续在仓库原有目录中独立维护和演进。

支持的平台

Windows • Linux • macOS

目前已经支持的消息类型

  • 模板消息-公众号
  • 模板消息-小程序
  • 订阅消息-小程序
  • 微信客服消息(文本、图文、图片、语音、视频、音乐、小程序卡片)
  • 微信企业号/企业微信消息
  • 企业微信小程序通知消息
  • 小程序统一服务消息
  • 钉钉
  • 飞书群自定义机器人(文本、富文本、消息卡片、原始 JSON;支持签名校验)
  • 阿里云短信
  • 阿里大于模板短信
  • 腾讯云短信
  • 华为云短信
  • 百度云短信
  • 又拍云短信
  • 七牛云短信
  • 云片网短信
  • 网易云信短信
  • 榛子云短信
  • Luosimao短信
  • 极光短信
  • 极光推送
  • E-Mail
  • HTTP请求(单次、批量、压测)

功能&亮点

  1. 支持自定义消息内容并批量推送
  2. 支持变量消息(可实现根据发送目标用户不同每条消息内容不一样)
  3. 支持消息编辑、预览、消息管理
  4. 支持通过文件导入用户(txt、csv、excel)
  5. 支持通过MySQL导入用户
  6. 支持微信公众号全员推送
  7. 支持微信全家桶消息(公众号、小程序、企业号)
  8. 支持各种粒度的定时推送
  9. 支持推送历史管理和失败重新推送
  10. 支持多账号管理和切换(微信)
  11. 支持各种搜索、导入、导出
  12. 小而美的可视化界面,支持亮暗多种外观风格
  13. 支持全局字体字号设置
  14. 支持推送结果邮件通知
  15. 支持一键接入 Codex MCP / Skill,供 AI 助手查询任务、空跑校验和按授权发送(接入说明)
    ……

截图速览

安装文件下载

WePush Classic 下载地址

使用到的一些小技术点

  • Java
  • Java Swing
  • 线程池
  • 连接池(HTTP:按通道/账号共享 OkHttp 连接池)
  • HTTP/2(ALPN 自动协商,并自动回退 HTTP/1.1)
  • 定时任务
  • SQLite
  • MyBatis

Classic HTTP 性能对照

Classic 默认使用 HTTP/2 + HTTP/1.1 自动协商,任务结束时会在日志中输出协议命中、新建/复用连接、429 以及 p50/p95/p99 耗时。可用同一批数据强制 HTTP/1.1 作为对照组:

bash
java -Dwepush.http.protocol=http1 -jar WePush.jar

并发上限默认沿用全局最大线程数,也可按消息类型覆盖(<msgType> 为消息类型数值):

bash
java -Dwepush.http.maxConcurrency.<msgType>=20 -jar WePush.jar

遇到的麻烦和挑战

  • Swing界面不好控制,导致需要投入较多精力和耐心
  • 工作过于饱和,经常到半夜很晚才挤出一点时间
  • 要做的事情有很多,比如WePush中间件及其附属的集消息中心、通知报警、任务、批量、重试、统计等于一身的方便部署的Web管理应用
  • 陪家人时间变少或无
  • 锻炼身体时间变少或无
  • 越来越发现需要不断学习源码和底层的重要性

特别感谢

WxJava
Hutool
FlatLaf

开发&构建

https://gitee.com/zhoubochina/WePush/wikis/build

使用帮助

https://gitee.com/zhoubochina/WePush/wikis/help
QQ交流群:

鼓励&赞赏

如果WePush对您有所帮助或便利,
欢迎对我每天下班和周末时光的努力进行肯定,
您的赞赏将会给我带来更多动力


Overview (`next/README.md`)

WePush Next

WePush Next 是与 Classic 完全独立的新产品线。Classic 与 Next 可以按需拥有重复代码,彼此不建立共享源码依赖。

产品定位:WePush Next 是开源、可下载、可安装、由用户自行部署和运维的消息推送产品。项目不建设官方公共 SaaS、注册计费订阅、公共租户平台或云 KMS/Secret Manager 集成。正式范围和分版本计划见《产品目标、边界与路线图》。

当前版本:1.2.0 Stable。下载 next-v1.2.0。Desktop 与安装包按项目约定不使用商业代码签名;下载后必须验证同一 Release 中的 SHA256SUMS。发行边界和升级承诺见 UNSIGNED-NOTICE.md、《兼容性策略》、《升级与回滚指南》与 docs/releases/1.2.0.md。

第一次下载和使用请从《WePush Next 对外使用指南》开始。渠道账号、模板、Recipient、SecretRef、限流和错误语义见《内置 Provider 指南》。

1.2.0 提供 Codex MCP / Skill 接入,支持查询和创建资源、空跑、授权发送及运行控制,快速配置见下方「AI 助手接入」。

下载与安装

正式发行物统一位于 next-v1.2.0 GitHub Release:

使用方式 选择的发行物
推荐的 Standalone 安装 与操作系统、架构匹配的 wepush-next-1.2.0-<os>-<arch> 完整包,已包含 Java Runtime
已有 Java 25+ 或分组件部署 wepush-next-1.2.0.tar.gz 或 wepush-next-1.2.0.zip 精简包
只使用管理界面 与操作系统、架构匹配的 wepush-next-desktop-1.2.0-<os>-<arch>
Java 集成 wepush-next-java-sdk-1.2.0.zip
运营商短信 Agent 插件 wepush-provider-{cmpp,smgp,sgip,smpp}-1.2.0.zip 及 wepush-provider-trusted-key-1.2.0.env

下载后先验证 SHA256SUMS。安装运营商插件时,还必须校验信任公钥文件及各插件的旁路 .sha256;不要从插件 ZIP 或第三方页面获取替代公钥。完整资产说明和升级步骤见 1.2.0 Release Notes。

完整包解压后执行统一 Standalone 安装入口:

bash
# Linux / macOS
sudo ./install/install.sh
powershell
# Windows 管理员 PowerShell
Set-ExecutionPolicy -Scope Process Bypass
& .\install\install.ps1

安装完成前会检查 Service Readiness、Flyway 数据库版本和本地 Provider Dry Run。需要便携运行或已有 Java 25+ 时,可选择精简包;分组件部署仍可直接使用 install/<os>/install.* service|agent|all。

AI 助手接入(MCP / Skill)

启动 Next Service 并配置渠道账号后,打开 设置 → AI 助手接入,核对 Service URL 与当前 Workspace:

  • **Desktop:**点击 一键接入 Codex 安装 MCP + Skill,或选择 仅安装 Skill;使用 Desktop 自带运行时,无需另装 Node.js。其他支持 stdio MCP 的客户端可使用 导出通用 MCP 配置。
  • **WebUI / 新构建发行包:**下载页面提供的独立安装器,或使用发行包中的 ai/wepush-ai.mjs,通过 Node.js 24+ 执行安装命令:
bash
node ./wepush-ai.mjs install --target codex --url http://127.0.0.1:18990 --workspace ws_default

安装后重新连接 Codex MCP,必要时重启 Codex。MCP 名称为 wepush,Skill 位于 ~/.agents/skills/wepush,可与 Classic 的 wepush-classic 同时安装。启用 Service 认证时,在 AI 客户端环境中设置 WEPUSH_API_TOKEN;安装器不复制登录 Token。

助手可以发现 Provider Schema、查询现有资源、创建消息/受众/任务、执行 Dry Run、准备与提交正式发送,以及查询结果、暂停、恢复或取消运行。正式发送需核对预览并取得覆盖内容与受众的用户授权;网络超时后保留原请求编号,先查询运行结果。

完整工具列表、权限要求、其他客户端配置及排错见《AI 助手接入指南》。Classic 的入口与执行范围见《Classic 接入指南》。

验证 Java 工程

bash
cd next
./mvnw verify

完整构建、三平台安装、Server/HA、Agent Enrollment、插件升级和恢复步骤见 docs/deployment-and-operations.md。安全问题请按 SECURITY.md 私下报告。

启动 Service

bash
cd next
./mvnw -pl service/service-app -am package -DskipTests
java -jar service/service-app/target/wepush-next-service.jar

Service 默认只监听 127.0.0.1:18990。启动后可访问:

  • http://127.0.0.1:18990/actuator/health
  • http://127.0.0.1:18990/api/v1/system/info
  • http://127.0.0.1:18990/api/v1/providers
  • http://127.0.0.1:18990/api/v1/agents
  • http://127.0.0.1:18990/openapi.yaml

本地无认证模式只允许监听回环地址;将 WEPUSH_BIND_ADDRESS 改为非回环地址时必须同时开启 WEPUSH_SECURITY_ENABLED=true 并配置足够强度的 WEPUSH_BOOTSTRAP_TOKEN,否则 Service 拒绝启动。

Standalone 数据默认保存到 .local/data/wepush-next.db,可通过 WEPUSH_DATABASE_PATH 指定其他位置。Service 首次启动会运行 Flyway 迁移并创建 ws_default 工作区。

Secret 默认使用本地信封加密:密文进入 SQLite,主密钥单独保存到 .local/secrets/master-key.json。可通过 WEPUSH_MASTER_KEY_PATH 修改路径,或使用 WEPUSH_MASTER_KEY_BASE64 注入 32-byte Base64 主密钥。已有密文但主密钥缺失、权限不安全或认证失败时,Service 会失败关闭,不会生成新密钥覆盖。

Artifact 默认保存到 .local/artifacts,SQLite 只保存元数据、SHA-256、大小和生命周期状态。可通过 WEPUSH_ARTIFACT_ROOT 修改根目录;临时结果导出默认保留 24 小时,可通过 WEPUSH_EXPORT_RETENTION 使用 ISO-8601 Duration 调整。Service 会按 WEPUSH_RETENTION_INTERVAL 周期回收过期且未 Pin/Legal Hold 的文件。

启动 WebUI 开发环境

bash
cd next/ui
pnpm install
pnpm check
pnpm dev

WebUI 默认运行在 http://127.0.0.1:5173,开发代理连接本地 Service。界面、API Client、Schema Renderer、设计 Token 与 Electron Desktop 外壳均在同一个 pnpm Workspace 内。

启动 Agent

bash
cd next
./mvnw -pl agent/agent-app -am package -DskipTests
java -jar agent/agent-app/target/wepush-next-agent.jar

Agent 默认主动连接 127.0.0.1:19090 的 gRPC 双向控制流,发送 Hello 和周期心跳,断线后使用带抖动的指数退避重连;Sequence 与 Lease Fence Journal 默认保存在 .local/agent/agent-state.properties。

常用环境变量:

  • WEPUSH_AGENT_ID:稳定 Agent 身份,默认 local-agent。
  • WEPUSH_SERVICE_HOST / WEPUSH_AGENT_GRPC_PORT:Service gRPC 地址,默认 127.0.0.1:19090。
  • WEPUSH_AGENT_GRPC_TOKEN:仅用于回环开发/Bootstrap 的共享 Token;正式 Agent 使用 Enrollment Credential。
  • WEPUSH_AGENT_GRPC_PLAINTEXT:本地开发默认 true;远端部署应关闭并使用 TLS。
  • WEPUSH_AGENT_STATE_PATH:Agent Journal 文件位置。

Service 的 gRPC 端口默认只绑定回环地址。暴露到非回环地址时强制 TLS,HTTP Lease/Artifact 与 gRPC 都拒绝匿名 Agent;正式生产通过一次性 Enrollment 获取长期 Credential 和客户端证书,并使用 mTLS。共享 Token 只保留为回环开发兼容入口。

要把 Run 交给独立 Agent 执行,Service 使用以下配置启动:

bash
WEPUSH_EXECUTION_MODE=remote \
WEPUSH_AGENT_PUBLIC_BASE_URL=http://127.0.0.1:18990 \
java -jar service/service-app/target/wepush-next-service.jar

远端模式会按 Provider ID/版本和可用容量选择在线 Agent,持久化带 Epoch/Fencing Token 的 Lease。Agent 校验冻结 Execution Spec 与 Audience 的 SHA-256 后 ACK,随后使用同一 Core Engine 执行,并经 gRPC 回传事件、Item Result、命令确认和 Run Summary。

需要 Secret 的远端 Run 会使用 Agent 在 Hello 中发布的会话级 X25519 公钥。Service 只解析冻结配置中实际引用的最小 Secret 集,使用一次性 X25519、HKDF-SHA-256 和 AES-256-GCM 加密,并绑定 Agent、Run、Lease、Epoch、Fencing Token 与过期时间。Agent 仅在内存中解密,运行结束立即清零,不写入 Journal。正式跨主机部署仍应关闭明文 gRPC 并配置 TLS;Secret Envelope 不替代 Agent 身份认证和传输层安全。

Java SDKs

Next 提供两种独立 SDK。Remote SDK 通过 HTTP 调用 Service,只依赖公开 service-api,不依赖 Core、Engine 或具体 Provider:

稳定发行包在 sdk/ 中附带可安装到本地 Maven Repository 的 POM/JAR,步骤见 sdk/README.md。

java
try (var client = WePushClient.builder()
        .endpoint(URI.create("http://127.0.0.1:18990"))
        .build()) {
    var system = client.system().info();
    var providers = client.providers().list();
    var agents = client.agents().list();
    var workspace = client.workspace("ws_default");
    var runs = workspace.runs();
    var artifacts = workspace.runArtifacts(runs.getFirst().id());
}

Embedded SDK 则在调用方 JVM 内直接运行 Engine,显式装配所需 Provider,不启动 Service、Agent 或数据库:

java
var store = new InMemoryExecutionStore();
try (var engine = WePushEngine.builder()
        .provider(new HttpProviderFactory())
        .resultSink(store)
        .eventSink(store)
        .build()) {
    var summary = engine.start(spec, recipients)
            .completion().toCompletableFuture().join();
}

从源码安装、依赖声明和完整示例见 sdk/README.md 与 sdk/embedded-java/README.md。1.2.0 发行包和独立 Java SDK 附件同时提供 Remote SDK、Embedded SDK、HTTP Provider 与标准渠道 Provider;CMPP、SMGP、SGIP、SMPP 则作为独立签名 Agent 插件发布,不进入 Embedded SDK 的默认依赖。

`1.2.0` 已交付基线

  • Core API、Provider SPI、虚拟线程 Engine,以及 HTTP、SMTP Email、飞书/钉钉/企微机器人、阿里云短信、微信公众号、小程序和企业微信应用消息 Provider;CMPP、SMGP、SGIP、SMPP 以四个独立签名插件交付。
  • Agent Protocol、Protobuf/gRPC 双向控制流、Sequence/Fencing Runtime、持久 Journal、加密 Secret Envelope、远端 Core 执行适配与常驻 Agent 包。
  • Service 分层、SQLite/PostgreSQL/Flyway、Workspace 资源治理、跨 Run 账号认证熔断、资源编辑/不可变修订/分页、流式 Audience Import、正式发送确认、关联重发、真实总览、Agent 注册/心跳/持久 Lease、信封加密 Secret Store、Result/Command/Artifact 持久化、SSE,以及可切换的内嵌/远端执行器。
  • PostgreSQL LISTEN/NOTIFY 低延迟唤醒、结构化脱敏诊断包、手动版本检查、Nginx/Traefik/Kubernetes 自建模板,以及 Agent Presigned Multipart Artifact(最大 5 TiB)。
  • 相互独立的 Remote Java SDK、Embedded Java SDK 和 TypeScript API Client。
  • React WebUI、亮色/暗色/跟随系统主题、低分辨率和键盘可访问性、动态 Provider/SecretRef Schema、渠道消息示例、资源生命周期、CSV/TXT 导入、Dry Run/正式确认/结果/失败重发闭环、真实 Workspace 和运行总览、动态 API 文档、Electron 安全外壳和共享前端 packages。
  • Codex MCP / Skill 接入:Desktop 一键安装,WebUI 与发行包提供 wepush-ai.mjs;正式发送仍走既有确认令牌和 Workspace 权限。
  • Java 25 构建与运行基线;完整包内置 Java 25 运行时,精简包和 Java SDK 需要系统 Java 25+。
  • 一体化 Standalone/离线 Windows 安装、含 Java Runtime 完整包、正式备份恢复、升级健康门与自动回退;Desktop 提供本机 Service 运维、系统安全 Token 存储和签名插件 Stage/Activate/Rollback。
  • 架构、单元、契约、浏览器 E2E、Desktop 冒烟、三平台安装/恢复/失败升级以及定时长稳矩阵。

产品范围和迭代优先级以 docs/product-scope-and-roadmap.md 为准;模块边界和实现细节以 docs/architecture-and-high-level-design.md、docs/detailed-design.md、docs/implementation-status.md、docs/deployment-and-operations.md 及 docs/adr/ 为准。


Overview (`next/plugins/README.md`)

WePush Next Carrier SMS Provider Plugins

provider-cmpp、provider-smgp、provider-sgip 和 provider-smpp 是四个独立 PF4J Provider 插件。它们只通过公开 Provider SPI 和 Agent Plugin API 接入,不是 Service 或 Agent 的内置依赖。共同的协议适配代码位于 provider-carrier-common,与 Classic 不存在源码或构建依赖。

每个插件支持协议登录、单号码 MT 文本提交、长短信分片、SUBMIT_RESP 判定、连接测试和无网络 Dry Run。网关受理不代表终端送达;本版本不处理 DLR、MO 或 SGIP 反向监听。

构建签名包

打包脚本不会内置或生成发布私钥。提供 Ed25519 PKCS#8 DER 的 Base64 和可信 Key ID 后分别构建:

首次配置时,可用 JDK 25+ 生成 Agent 所需的 PKCS#8 私钥和 X.509 公钥(Base64):

bash
java scripts/GeneratePluginSigningKey.java private-key.b64 public-key.b64

私钥文件只提供给离线构建/发布环境;Agent 仅配置 public-key.b64 的内容。

bash
export WEPUSH_PLUGIN_SIGNING_KEY_ID=release-2026
export WEPUSH_PLUGIN_SIGNING_KEY_PKCS8_BASE64='...'
./scripts/package-carrier-provider.sh cmpp ./target/provider-plugins
./scripts/package-carrier-provider.sh smgp ./target/provider-plugins
./scripts/package-carrier-provider.sh sgip ./target/provider-plugins
./scripts/package-carrier-provider.sh smpp ./target/provider-plugins

Agent/Service 使用对应 Ed25519 X.509 公钥配置 WEPUSH_PLUGIN_TRUSTED_KEYS。插件包先经过 SHA-256 清单、Ed25519 签名、共享 API、Zip Slip 和压缩大小校验,再由 PF4J 加载。


Overview (`next/sdk/README.md`)

WePush Next Java SDKs

WePush Next 提供两种边界不同、彼此独立的 Java SDK:

SDK Maven Artifact 执行位置 依赖边界 适用场景
Remote Java SDK sdk-java WePush Service / Agent 只依赖公开 service-api,不依赖 Core、Engine 或 Provider 应用通过 HTTP 调用已部署的 WePush
Embedded Java SDK embedded-java 调用方 JVM 进程内 依赖 Core API、Engine、Provider SPI;Provider 由应用显式选择 不启动 Service,直接把 Engine 嵌入 Java 应用

两者不是上下级关系,也不会合并成一个 Artifact。Remote SDK 保持稳定的网络契约边界;Embedded SDK 提供进程内执行能力。

1.2.0 发行包和独立 Java SDK 附件同时提供 Remote Java SDK 与 Embedded Java SDK。

从源码安装

需要 Java 25 或更高版本。在仓库根目录执行:

bash
cd next
./mvnw -pl sdk/sdk-java,sdk/embedded-java,providers/provider-http,providers/provider-standard -am install

从稳定发行附件安装

进入包含 POM/JAR 的 sdk/ 目录(独立 Java SDK 压缩包则进入其根目录),先安装 Parent,再按需要安装 Remote 或 Embedded 依赖闭包:

bash
VERSION=1.2.0

mvn install:install-file \
  -Dfile="wepush-next-parent-$VERSION.pom" \
  -DpomFile="wepush-next-parent-$VERSION.pom"

install_jar() {
  artifact="$1"
  mvn install:install-file \
    -Dfile="$artifact-$VERSION.jar" \
    -DpomFile="$artifact-$VERSION.pom"
}

# Remote Java SDK
install_jar service-api
install_jar sdk-java

# Embedded Java SDK 与内置 Provider
install_jar core-api
install_jar provider-spi
install_jar engine
install_jar embedded-java
install_jar provider-http
install_jar provider-standard

Windows PowerShell 可以对相同文件逐条执行 mvn install:install-file。

Remote Java SDK

xml
<dependency>
    <groupId>com.fangxuele.wepush.next</groupId>
    <artifactId>sdk-java</artifactId>
    <version>1.2.0</version>
</dependency>

Remote SDK 使用 WePushClient 连接 Service;建议 Service 和客户端使用相同 1.x Minor,兼容范围见《兼容性策略》:

java
try (var client = WePushClient.builder()
        .endpoint(URI.create("http://127.0.0.1:18990"))
        .build()) {
    var system = client.system().info();
    var workspace = client.workspace("ws_default");
    var runs = workspace.runs();
}

Remote SDK 支持资源分页/编辑、CSV/TXT 文件上传、正式发送确认、筛选重发和真实总览。例如上传文件不会先把完整内容读入 SDK 内存:

java
var workspace = client.workspace("ws_default");
var preview = workspace.uploadAudience(
        Path.of("recipients.csv"), "Imported", null, "CSV", "itemId",
        Map.of("mobile", "mobile"), null);
var audience = workspace.commitAudienceImport(preview.id());

var confirmation = workspace.confirmRun("job-id");
var run = workspace.createRun("job-id", UUID.randomUUID().toString(),
        new ControlPlaneApi.CreateRunRequest(false, Map.of(), "manual",
                confirmation.confirmationToken()));

每个 Cursor Page 最多 100 条;正式发送和失败重发的确认令牌只短期有效,并绑定当前资源或来源 Run 状态。

Embedded Java SDK

xml
<dependency>
    <groupId>com.fangxuele.wepush.next</groupId>
    <artifactId>embedded-java</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>com.fangxuele.wepush.next</groupId>
    <artifactId>provider-http</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>com.fangxuele.wepush.next</groupId>
    <artifactId>provider-standard</artifactId>
    <version>1.2.0</version>
</dependency>

provider-standard 提供 SMTP Email、飞书/钉钉/企微机器人、阿里云短信、微信公众号、小程序和企业微信应用消息。Embedded SDK 不创建数据库、不启动 Web Server、不读取 Service 配置,也不自动扫描第三方 Provider。应用必须通过 .provider(new SmtpProviderFactory()) 等方式显式注册允许的实现,并负责持久化结果、事件、Artifact 和 Secret 的适配器。渠道配置见内置 Provider 指南。

完整示例、资源所有权和生产接入说明见 Embedded Java SDK README。

公开 API 遵循 1.x 兼容承诺;新增能力优先通过新方法、Builder 或新类型提供,详见《兼容性策略》。


Overview (`next/ui/README.md`)

WePush Next UI

Web 与 Desktop 共用 React Features、API Client、Schema Renderer、UI primitives 和 Design Tokens。

bash
cd next/ui
corepack pnpm install
corepack pnpm check
corepack pnpm dev

开发服务器把 /api、/actuator 和 /openapi.yaml 代理到 http://127.0.0.1:18990。


Overview (`next/deployment/templates/README.md`)

WePush Next self-host templates

These templates extend the production-oriented deployment/container/compose.server.yaml baseline.
They intentionally contain no usable credentials. Copy the relevant example, replace every
REPLACE_ME/hostname, and keep PostgreSQL, S3 and Agent gRPC on private networks.

  • nginx/wepush-next.conf: TLS reverse proxy for the HTTP API/Web UI and long-lived SSE streams.
  • traefik/compose.override.yaml: labels for an existing Traefik v3 deployment.
  • kubernetes/: Service Deployment, internal Services, ingress and Pod disruption budget.

Server mode still requires PostgreSQL, S3, API security and Agent gRPC mTLS. Following the controlled
upgrade guide, let one Service instance finish Flyway migration before rolling the remaining pods; verify /actuator/health/readiness and
/actuator/health/installation before sending traffic.


Overview (`next/docs/adr/README.md`)

WePush Next 架构决策索引

本目录保存 WePush Next 已接受的跨组件架构决策。架构与详细设计描述当前整体状态;ADR 记录选择原因、边界和后果。发生冲突时,应先确认 ADR 是否已被后续决策替代,再同步更新设计文档。

产品定位、长期非目标和迭代优先级以《产品目标、边界与路线图》为准;ADR 不得以技术扩展名义改变该产品边界。

ADR 状态 决策摘要
ADR-0001 已接受 Classic 与 Next 双轨独立发展,允许按需重复代码
ADR-0002 已接受 Java 25、Spring Boot 4.1.x、TypeScript/Vite/React、Electron 技术基线
ADR-0003 已接受 默认并正式使用本地 AES-256-GCM 信封加密 Secret Store,不规划云密钥适配
ADR-0004 已接受 Agent 使用 gRPC 双向流,UI/SDK 事件使用 SSE
ADR-0005 已接受 PF4J、签名插件、ClassLoader 隔离和滚动重启更新
ADR-0006 已接受 PostgreSQL 18、无状态 Service、Advisory Lock 和 Fencing
ADR-0007 已接受 Standalone 本地制品、用户自建 Server S3-compatible 制品及保留策略
ADR-0008 已接受 Workspace 只作为同一自建实例信任域内的逻辑隔离

新增或替代决策时使用下一个连续编号,并在状态中明确“提议、已接受、已替代或已拒绝”。替代已有 ADR 时,旧 ADR 保留历史并链接到新 ADR。


Overview (`next/sdk/embedded-java/README.md`)

WePush Next Embedded Java SDK

Embedded Java SDK 让 Java 应用在自己的 JVM 进程内直接运行 WePush Core Engine,无需安装或启动 Service、Agent、数据库和 WebUI。

当前实现提供:

  • WePushEngine.builder() 显式注册一个或多个 ProviderFactory。
  • 从 List<RecipientRecord> 或流式 RecipientSource 启动 Run。
  • 返回 Core RunHandle,支持查询状态、提交暂停/恢复/取消/并发调整命令以及等待 RunSummary。
  • 共享的 Engine 级 Sink,或由工厂创建的 Run 级 Result/Event/Artifact Sink。
  • 可替换 SecretResolver 与 ExecutionClock。
  • 面向测试和小任务的线程安全 InMemoryExecutionStore。

它刻意不包含 Service API、数据库、Spring、Agent、调度器、多租户控制面和 Provider 插件发现。应用只获得执行内核,其他生命周期由应用自己管理。

1.2.0 发行包和独立 Java SDK 附件包含 Embedded SDK 及所需 POM/JAR。

要求与依赖

需要 Java 25 或更高版本。从源码安装当前模块、HTTP Provider 和标准渠道 Provider:

bash
cd next
./mvnw -pl sdk/embedded-java,providers/provider-http,providers/provider-standard -am install

调用方声明 SDK,并只加入自己允许使用的 Provider:

xml
<dependency>
    <groupId>com.fangxuele.wepush.next</groupId>
    <artifactId>embedded-java</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>com.fangxuele.wepush.next</groupId>
    <artifactId>provider-http</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>com.fangxuele.wepush.next</groupId>
    <artifactId>provider-standard</artifactId>
    <version>1.2.0</version>
</dependency>

provider-standard 中包含 SMTP、飞书/钉钉/企微机器人、阿里云短信、微信公众号、小程序和企业微信应用消息。它只加入 Classpath,不会被 Embedded SDK 自动启用;调用方仍须显式 .provider(new SmtpProviderFactory()) 或注册其他允许的 Factory。Account、Message、Recipient 和 SecretRef 示例见内置 Provider 指南。

最小可运行示例

下面使用 HTTP Provider 的 Dry Run,因此不会访问 example.com:

text
/* Detailed source-code truncated for AI context efficiency. */

正式发送只应在完成小范围测试、目标服务限流和幂等校验后把 dryRun 改为 false。HTTP Provider 默认阻止私网目标;只有明确受信的内部地址才应开启 allowPrivateAddresses。

Secret

默认 SecretResolver 只在 Provider 实际请求 Secret 时失败。需要认证的 Provider 必须显式配置:

java
var engine = WePushEngine.builder()
        .provider(new HttpProviderFactory())
        .secretResolver(ref -> loadSecretFromYourStore(ref))
        .resultSink(resultSink)
        .build();

SecretValue 是可关闭资源。自定义 Resolver 不应记录明文;Secret Store、轮换和访问控制由宿主应用负责。

Sink 与资源所有权

共享 Sink 的生命周期与 Engine 一致,必须支持多个 Run 并发调用:

java
WePushEngine.builder()
        .resultSink(sharedResultSink)
        .eventSink(sharedEventSink)
        .artifactSink(sharedArtifactSink);

按 Run 隔离时使用工厂;工厂返回的资源在 Run 完成或启动被拒绝后由 Engine 关闭:

java
WePushEngine.builder()
        .resultSinkFactory(spec -> openResultSink(spec.runId()))
        .eventSinkFactory(spec -> openEventSink(spec.runId()))
        .artifactSinkFactory(spec -> openArtifactSink(spec.runId()));

start(spec, RecipientSource) 会接管 RecipientSource,完成或拒绝时关闭它。start(spec, List) 会先复制列表。WePushEngine.close() 会取消仍在运行的 Run、等待执行器退出并关闭共享 Sink;建议始终使用 try-with-resources。

InMemoryExecutionStore 不限容量,只适合测试和可控的小任务。长期进程和大批量发送应实现有界或持久化的 ResultSink / RunEventSink,并根据需要实现 ArtifactSink。

Provider 边界

SDK 不通过 Classpath 或 PF4J 自动发现 Provider。每个允许使用的实现都必须通过 .provider(...) 注册,RunExecutionSpec.provider 的 ID 和实现版本必须精确匹配。自定义渠道只需实现 provider-spi 的 ProviderFactory,无需依赖 Embedded SDK。


Classic Ai Integration (`docs/classic-ai-integration.md`)

Classic 的 MCP 与 Skill 接入

此功能随包含本次改动的 Classic 安装包提供;已发布的旧安装包不会自动获得该入口。Classic 的实现独立于 Next,不需要启动 Next Service,也不依赖 Next 的源码、Node.js 或构建产物。

一键接入

  1. 启动 Classic,先在界面中配置账号、消息、人群和任务。
  2. 打开 应用 → AI 助手接入。
  3. 点击 一键接入 Codex,安装器会写入 Codex MCP 配置和 wepush-classic Skill,同时开启本机桥接。重新启动 Codex 或新建会话加载接入。
  4. 对助手说:“用 WePush Classic 查看任务,空跑任务 1,告诉我校验结果。”

使用期间保持 Classic 运行。桥接默认关闭,首次开启后会在下次启动 Classic 时自动恢复。关闭本机接入会停止接受新调用,已启动的任务继续执行;退出 Classic 会中断本机任务。

安装名称均为 wepush-classic,与 Next 的 wepush 可以共存:

内容 位置
Codex MCP $CODEX_HOME/config.toml,未设置时为 ~/.codex/config.toml,表名 [mcp_servers.wepush-classic]
Skill ~/.agents/skills/wepush-classic/SKILL.md
Skill 启动脚本 Skill 目录中的 scripts/wepush-classic.sh / scripts/wepush-classic.ps1
通用 MCP JSON 安装后的 Skill 目录中的 mcp.json
本机连接与防重记录 ~/.WePush5/ai/

安装器保留其他 MCP、模型和 Profile 配置,并在修改已有 Codex 配置前生成 config.toml.wepush-classic-.bak。遇到同名的手动 MCP 或非本安装器创建的 Skill 时停止,避免覆盖。重复安装会更新自己管理的文件;自定义 Skill 请使用另一个目录名称。

启动配置引用当前 Classic 的 Java 运行时和应用目录,发行版无需另装 Java。移动应用、切换开发目录或升级导致路径变化后,请在新位置再执行一次安装。

其他助手与 Skill 单独使用

支持 stdio MCP 的客户端:点击 复制通用 MCP 配置,把 JSON 中 mcpServers.wepush-classic 合并到客户端配置中,再重新连接。客户端可能有自己的配置格式,保留生成的 command 和 args。

仅使用 Skill:点击 仅安装 Skill。Codex 会从用户 Skill 目录发现它;其他支持 Skill 的助手可加载或复制整个目录。脚本使用与 MCP 相同的工具和本机桥接。使用 tools 查询参数,call <工具名> 从标准输入读取一个 UTF-8 JSON 对象:

sh
sh ~/.agents/skills/wepush-classic/scripts/wepush-classic.sh tools
printf '%s' '{"taskId":1}' | sh ~/.agents/skills/wepush-classic/scripts/wepush-classic.sh call wepush_classic_get_task

Windows 示例:

powershell
'{"taskId":1}' | powershell -NoProfile -ExecutionPolicy Bypass -File "$HOME/.agents/skills/wepush-classic/scripts/wepush-classic.ps1" call wepush_classic_get_task

工具与执行范围

工具 能力
wepush_classic_system_info 检查连接与能力
wepush_classic_list_tasks 分页查询已保存的任务
wepush_classic_get_task 查看消息模板、账号名称、人群名称及数量
wepush_classic_prepare_run 返回发送预览与有效期 10 分钟的确认令牌,不投递消息
wepush_classic_dry_run 使用 Classic 原有渠道逻辑空跑校验
wepush_classic_send_run 执行已授权的正式发送
wepush_classic_get_run 按 runId 查询进度与结果
wepush_classic_list_history 分页查询指定任务的 Classic 历史

分页参数为 offset(默认 0)和 limit(默认 30,最大 100)。工具只操作已有资源,账号、消息、人群和任务的创建及编辑仍在 Classic 界面中完成。

当前执行范围为已保存的手动固定线程任务:线程数 1~100,已关闭任务的结果邮件提醒,每个 Classic 实例最多同时执行 4 个由 AI 启动的任务。计划、触发、变速任务和结果邮件提醒仍由 Classic 原有界面执行。各渠道的成功语义、限流和空跑行为沿用现有发送器;空跑不证明真实渠道认证或投递一定成功。

正式发送前,助手必须核对 prepare_run 的预览,确保已有用户授权覆盖内容、人群及发送时机,再传入 taskId、confirmationToken、新的标准小写 UUID idempotencyKey 和 userConfirmed: true。任务、消息、账号或人群在预览后变化时,旧预览失效。接入在同一个数据库锁内核对资源并准备发送器,接受运行后保留该次任务和人群快照。

本次 AI 接入的“发送预览”是只读操作。Classic 原有消息编辑界面中的“预览发送”仍按原行为向预览目标实际发送。

防重与结果

runId 与调用者提供的 idempotencyKey 相同。请求在启动发送前持久化,重复使用相同请求编号和相同参数会返回原运行记录。确认令牌只允许启动一次正式发送,但已接受请求的原样重试不受令牌过期影响。

超时或响应丢失时先查询原 runId,必要时原样重试;不要换 UUID 重发。Classic 进程退出或执行异常后可能返回 UNKNOWN,必须先核对 Classic 历史和渠道结果,不会自动恢复投递。防重记录长期保留在 ai/requests;删除这些记录会失去对应请求的防重保护。

FINISHED 只表示执行结束,应继续核对 totalCount、successCount 和 failCount。它不保证所有目标发送成功,也不等同于终端已送达。

本机边界

桥接只监听 127.0.0.1 随机端口,每次开启生成新的令牌;拒绝带 Origin 的浏览器请求。连接文件在支持 POSIX 权限的系统上仅当前用户可读写。MCP 和 Skill 配置只保存连接文件路径,不包含令牌或渠道凭据。不要共享 ~/.WePush5/ai/connection.json;同一系统用户下的可信本机客户端可以通过此文件调用桥接。

工具不返回账号配置、原始日志、完整人群记录或结果文件。消息模板里的常见凭据字段(如 Header、Cookie、Token)会隐藏;自定义正文中的敏感文本仍属于消息内容,应按实际使用范围处理。

开发验证

使用 Java 25+ 和 Maven 执行 mvn test。新增测试覆盖 TOML 保留与冲突、重复安装、脚本路径转义、MCP 协议、鉴权与令牌轮换、配置变化、确认过期、持久防重及并发重试。真实链路测试在隔离子进程中使用临时数据库与本机 HTTP 接收器,断言空跑零投递、正式发送使用已接受的消息快照、重复请求不增加投递。

配置依据:Codex MCP、Codex Skills、MCP 生命周期及工具协议。


Classic Carrier Sms Protocol Design (`docs/classic-carrier-sms-protocol-design.md`)

WePush Classic 运营商短信协议支持设计

1. 目标与结论

Classic 版新增统一的“运营商协议短信”消息类型,支持 CMPP 2.0/3.0、SMGP 3.0、SGIP 1.2 和 SMPP 3.4 的 MT 短信提交。这四种协议均为长连接二进制协议,不应继续套用现有 HTTP 短信供应商的“每条消息创建客户端”模型。

结论:在 Classic 现有账号、消息和任务模型上实现,不需要修改数据库表结构。账号参数和消息内容继续分别保存于 TAccount.accountConfig 和 TMsg.content JSON 中。

2. 产品范围

交付范围:

  • 四协议的单号码 MT 文本短信发送,长短信由协议层自动分片。
  • 一个统一消息类型,账号中选择具体协议。
  • 每个账号共享一个协议客户端,复用 TCP 长连接,支持断线重连、窗口控制和应答超时。
  • 将 SUBMIT_RESP 是否成功映射为 WePush 的发送成功/失败,返回协议消息 ID 或错误码。
  • 模板变量替换、测试登录、测试发送、批量任务、无限推送和模拟发送与现有 Classic 能力保持一致。
  • 账号删除、重新配置或应用退出时关闭对应连接。

明确不支持:

  • 状态报告(DLR)的请求、解析、落库和发送历史回填。所有协议的状态报告请求位固定为 0;SUBMIT_RESP 成功只表示网关已受理,不代表终端已收到。
  • MO 上行短信的解析、存储或业务处理。
  • SGIP 反向 TCP 监听服务。
  • 群发指令中一次携带多个目标号码;Classic 现有任务粒度保持一人一次提交。

部分网关可能无视请求位,在现有双向连接上推送 DELIVER。客户端只发送协议要求的确认包并丢弃消息,不将其交给 WePush 业务层;这是维持连接稳定所需的传输层行为,不属于状态报告或上行业务支持。

3. 技术选型

使用 Maven Central 中的 com.chinamobile.cmos:sms-client:0.1 作为协议客户端。它在同一抽象下支持 CMPP、SMGP、SGIP 和 SMPP,并提供连接池、心跳/重连、窗口和长短信分片能力。该组件及其核心声明为 Apache-2.0。

引入前后必须通过下列门槛:

  • Java 25 下编译、建连、发送和关闭均无需额外 JVM --add-opens。无用的 FST/持久化路径从运行时依赖中排除。
  • 检查运行时依赖和安装包体积;对文本短信无用的 WAP/MMS/持久化依赖仅在确认不会触发类加载后才能排除。
  • 连接不可达和应答丢失时,WePush 设置的超时必须真正结束当次等待。
  • 最终分发前对全部运行时依赖做许可证和安全审计。文本编码必需的 smsj 声明为 MPL-1.1;WAP/MMS 才使用的 GPL-2.0 wbxml-stream 已从 Classic 运行时依赖排除。Netty 保持 4.1 二进制兼容线并由根 POM 统一锁定安全修复版本,不使用协议库原始锁定的 4.1.86.Final。协议库带入的 Commons Pool、BeanUtils 和 Codec 也由根 POM 锁定到仍兼容其调用方式的正式维护版本。

如上述门槛无法满足,保留 CarrierSmsGatewayClient 内部接口,可以替换协议库而不影响 UI、配置和任务层。

4. 整体设计

text
Classic 任务工作线程
        |
CarrierSmsMsgSender
        |
CarrierSmsSessionRegistry -- accountId --> 共享 CarrierSmsGatewayClient
        |                                      |
        +--------------------------------------+
                                               |
                                  sms-client / TCP 长连接池
                                               |
                             CMPP | SMGP | SGIP | SMPP 网关

CarrierSmsSessionRegistry 是进程级单例,以账号 ID 为键。任务线程只获取共享客户端,不直接创建或关闭 TCP 连接。同一账号配置发生变化时,通过配置指纹识别并原子替换旧客户端。

模拟发送不创建网络连接。真实发送在首次使用账号时惰性建连。

5. 配置模型

5.1 账号配置

字段 作用 适用协议
protocol CMPP / SMGP / SGIP / SMPP 全部
host, port 网关地址 全部
username, password 鉴权信息 全部
version 协议版本 CMPP/SMGP/SMPP
maxChannels 账号最大 TCP 连接数,默认 1 全部
windowSize 单连接未应答窗口,默认 16 全部
requestTimeoutMillis SUBMIT_RESP 超时,默认 10000 ms 全部
sourceAddress 企业号码/SP 号/接入号 全部,协议字段名不同
serviceId 业务代码 CMPP/SMGP/SGIP/SMPP
msgSrc 企业代码 CMPP/SMGP
nodeId SGIP 企业编号 SGIP
corpId SGIP 企业代码 SGIP
heartbeatIntervalSeconds 空闲连接心跳间隔,默认 30 秒 全部

端口默认值仅用于 UI 快速填写,不作为协议规则强制:CMPP 7890、SMGP 8900、SGIP 8801、SMPP 2775。实际以运营商/网关商提供的参数为准。

密码存储延续 Classic 现有账号机制;本功能不在日志、异常信息或 toString() 中输出密码。

5.2 消息配置

消息 JSON 只保存 content 文本。内容支持 Classic 现有 Velocity 变量。号码仍来自人员表第一列,发送前去除首尾空格,不擅自改写国家码或号段。

6. 协议映射和成功判定

协议 提交请求 接收人 内容 提交成功
CMPP CMPP_SUBMIT Dest_terminal_Id Msg_Content CMPP_SUBMIT_RESP.Result == 0
SMGP SMGP_SUBMIT DestTermID MsgContent SMGP_SUBMIT_RESP.Status == 0
SGIP SGIP_SUBMIT UserNumber MessageContent SGIP_SUBMIT_RESP.Result == 0
SMPP submit_sm destination_addr short_message PDU command_status == 0

应答类型不符、超时、连接失败、编码/分片失败或非零结果码均返回失败。失败描述包含协议和数值错误码,但不包含凭据。

对长短信,所有分片的提交应答均为成功才认为该接收人提交成功。任一分片失败时不自动重发整条长短信,避免收件人获得重复内容。

7. 并发、生命周期与可观测性

  • Classic 任务线程数不等于 TCP 连接数。实际协议并发上限为 maxChannels * windowSize,两层都需有界。
  • 断线后下一次发送重新建连;协议层和 MsgSender 上层均不对结果不明的提交自动重试。
  • 账号删除时调用 close(accountId),应用关闭时调用 shutdown()。
  • 日志记录协议、账号 ID、网关地址、连接状态、应答耗时和错误码。号码按现有 Classic 行为展示,密码绝不记录。

8. UI 行为

  • 消息类型页新增“运营商协议短信”。
  • 账号页选择协议后只显示通用字段和当前协议专属字段,切换协议不丢失已输入的其他字段。
  • 保存前校验必填项、端口范围、连接数、窗口和超时。保存账号不主动连接运营商网关。
  • 账号页提供“测试登录”,只验证 TCP 和协议鉴权,不提交短信。
  • 消息编辑页提供多行文本内容、变量提示和字符数显示。

9. 测试与验收

自动化测试:

  • 四协议账号配置的 JSON 序列化/反序列化、默认值、边界和协议专属校验。
  • 四协议提交请求字段映射。
  • 四类成功应答、非零错误、错误应答类型、异常和超时映射。
  • 同账号并发只创建一个共享客户端;配置变更、账号删除和应用退出正确关闭客户端。
  • 模拟发送不创建客户端或 TCP 连接。
  • 本地模拟网关覆盖四协议登录、中文提交、长短信分片和提交应答。
  • 现有 Classic 单元测试和打包构建不回归。

联调验收:每种协议至少需要一个测试网关账号,覆盖登录、心跳、中文短信、长短信、网关拒绝、断线重连和应答超时。无真实网关时只能完成编译、单测和本地仿真验收,不能宣称已通过运营商现网联调。

10. 完成边界

实现以获取并校验 SUBMIT_RESP 为终点。任务统计中的“成功”统一表示网关受理;产品不推断、不展示终端送达状态,也不规划 DLR、MO 或 SGIP 反向监听能力。


Next Docs Ai Integration (`next/docs/ai-integration.md`)

用 Codex 等 AI 助手操作 WePush

本功能随 1.2.0 的 WebUI、Desktop 和完整发行包交付,使用同一个独立安装器 wepush-ai.mjs。

MCP 桥接器由 AI 客户端通过 stdio 启动,再调用用户自己的 WePush Next REST API,不需要新开服务端口或部署公共服务。它遵循已有 Workspace RBAC、Provider 校验、发送确认和 Idempotency-Key 语义。

桌面端一键接入

  1. 启动 WePush Next Service,先在 WePush 中配置渠道账号及 SecretRef。
  2. 打开 设置 → AI 助手接入,核对 Service URL 和当前 Workspace。
  3. 点击 一键接入 Codex,安装 MCP 和 Skill;或点击 仅安装 Skill。
  4. 重新连接 Codex 的 MCP,必要时重启 Codex。可以说:“用 WePush 列出已有任务,并对我选择的任务执行 Dry Run。”

桌面安装使用随 Desktop 提供的 Electron Node Runtime,无需另外安装 Node.js 或 Codex CLI。安装的 MCP 指向该 Desktop 可执行文件,移动或卸载 Desktop 后需要重新安装接入。

安装器会在 ~/.agents/skills/wepush 写入 Skill、独立工具脚本和连接信息。Codex MCP 配置写入 $CODEX_HOME/config.toml,未设置时使用 ~/.codex/config.toml;修改前保留同目录 .wepush-.bak 备份。重复安装仅更新自己的配置块,保留其他 MCP、模型设置及注释。如果已有同名手动配置或自定义 Skill,安装会报告冲突并保留原文件。

“仅安装 Skill”不修改已有 MCP 配置,也不移除此前安装的 MCP;Skill 使用其自己的连接文件。需要同步修改已有 MCP 的连接时,选择“一键接入 Codex”。

浏览器或发行包安装

浏览器不能直接写入客户端配置。在 设置 → AI 助手接入 下载安装器,然后在下载目录执行界面生成的命令。也可使用新构建发行包的 ai/wepush-ai.mjs。这两种方式需要 Node.js 24+:

bash
node ./wepush-ai.mjs install --target codex --url http://127.0.0.1:18990 --workspace ws_default

以上命令也适用于 Windows PowerShell。--target skill 只安装 Codex Skill;--target generic 把工具和通用 mcpServers JSON 写入 ~/.wepush/ai,不更改 Codex 配置。安装后不需要保留下载的原文件。

Service URL 是 AI 客户端运行环境 能访问的地址。容器、WSL、远端主机中的 127.0.0.1 不指向桌面所在机器;需要配置实际可达的 HTTPS 地址。桥接器仅允许回环地址使用 HTTP,拒绝 URL 中的密码或查询参数,并拒绝跟随 API 重定向。

Token 与权限

本地 Service 未启用认证时可直接使用。启用认证后,在启动 AI 客户端的环境中设置 WEPUSH_API_TOKEN,使用当前 Workspace 的 VIEWER(查询)或 OPERATOR(创建和发送)Token。已有系统管理员 Token 不应作为日常发送身份。

安装器不读取或复制 WePush 登录 Token,不将 Token 写入 Skill、MCP 配置或导出文件。Codex 配置通过 env_vars = ["WEPUSH_API_TOKEN"] 将客户端的环境变量传给 MCP 子进程。GUI 客户端的环境与终端可能不同,设置后应重启客户端;其他客户端按其环境变量设置方式传入该变量。

独立脚本同时支持 WEPUSH_SERVICE_URL、WEPUSH_WORKSPACE_ID 覆盖连接文件;MCP 安装配置中指定的 URL/Workspace 优先于客户端外部的同名环境变量。修改连接最直接的方式是重新安装。

其他 MCP / Skill 客户端

Desktop 的 导出通用 MCP 配置 会准备本机运行文件并打开 JSON 保存对话框;取消保存时,配置仍在 ~/.wepush/ai/mcp.json。将其中 mcpServers.wepush 合并到支持 stdio 的客户端(例如 Claude Desktop 或 Cursor)的配置中,保留其他服务器条目。导出包含当前机器的绝对路径;迁移到另一台机器时应在那台机器运行安装器。

其他支持 Agent Skills 的客户端可以使用生成的 SKILL.md 与整个 scripts/ 目录。脚本封装了已检测到的运行时路径,跨机器移动需要重新生成安装文件。此版本不提供远程 HTTP MCP 端点或 OAuth,云端仅支持远程 HTTP MCP 的客户端不能直接使用该 stdio 配置。

工具与发送流程

工具 能力
wepush_system_info 测试连接、版本及当前 Workspace
wepush_list_providers / wepush_provider_schema 发现渠道与账号、消息、收件人 Schema
wepush_list_resources / wepush_get_resource 分页查询或读取账号、消息、受众、任务及 Run
wepush_create_message / wepush_create_audience / wepush_create_job 创建草稿及任务,不发送
wepush_dry_run 执行测试运行
wepush_prepare_run / wepush_send_run 预览正式发送并使用确认令牌创建 Run
wepush_run_results / wepush_run_command 查询逐条结果、暂停、恢复或取消

先读取现有资源及渠道 Schema,准备消息、受众、任务,再执行 Dry Run。大于 1000 条的受众使用 WePush CSV/TXT 导入。账号、密钥、调度和失败重发在本版本仍由 WePush 界面管理。

正式发送需要先取得预览、核对消息内容和受众,然后在用户授权范围内提交 confirmationToken、同一个 idempotencyKey 和 userConfirmed: true。userConfirmed 是供助手表达已有授权的参数,不是独立的人工身份验证;实际权限和令牌有效性由 Service 校验。

返回 Run ID 表示已创建运行,不表示收件人已收到消息。应继续查询 Run 状态和结果。网络超时不自动重试;需要重试时复用原幂等键与请求内容。UNKNOWN 不等于失败,不自动重发。

Skill 在 MCP 不可用时可以直接调用同一套工具:

bash
# 在安装后的 Skill 目录中执行;Windows 使用 scripts/wepush-ai.ps1
./scripts/wepush-ai.sh tools
printf '%s' '{}' | ./scripts/wepush-ai.sh call wepush_system_info
./scripts/wepush-ai.sh call wepush_create_message < message-arguments.json

tools 返回工具描述和完整 JSON Schema。调用参数走 stdin,避免把消息内容拼接成 shell 命令。

移除与排错

  • 移除 Codex MCP 可运行 codex mcp remove wepush,或在客户端中移除该服务器;这不会删除 WePush 的业务数据。
  • 不再使用 Skill 时删除安装器管理的 ~/.agents/skills/wepush。通用安装文件位于 ~/.wepush/ai。
  • 401:检查 AI 客户端实际继承的 WEPUSH_API_TOKEN;403:检查 Token 的 Workspace 和角色。
  • 安装提示同名冲突:保留原配置,重命名自定义条目或先移除不再使用的条目,再安装。
  • 安装中断后若残留 .install-lock 目录,确认没有安装进程运行后移除该空目录再重试。

实现接入格式参考 Codex MCP 文档、Codex Skills 文档和 MCP stdio 传输规范。

开发验证:在 next/ui 运行 pnpm check 和 pnpm --filter @wepush-next/web e2e。构建 Service JAR 后,可运行 pnpm --filter @wepush-next/ai-integration test:service;该测试创建临时数据库和仅本机可达的 Service,通过 MCP 验证认证、Schema、草稿、发送预览、幂等 Dry Run 与结果,结束后清理,不实际投递消息。


Next Docs Architecture And High Level Design (`next/docs/architecture-and-high-level-design.md`)

WePush Next 架构与概要设计

1. 文档目的

本文档描述 WePush Next 的目标架构和概要设计,作为后续模块创建、接口设计、技术选型、任务拆分和验收的共同基线。

WePush Next 是独立于当前 Classic 客户端的新产品线。本文档不要求 Classic 按照本架构改造,也不限制 Classic 继续独立增加消息类型和功能。

2. 建设目标

WePush Next 的目标是从单机桌面推送工具发展为同时支持本地使用、用户自建服务化部署和分布式执行的消息推送产品。产品始终由用户自行下载、安装和运维,不建设官方公共 SaaS 或承载用户业务数据的集中平台。

主要目标如下:

  • 提供与 UI、数据库和 Web 框架无关的 Core Engine。
  • 提供可独立运行的 Agent,支持本地或远程执行推送任务。
  • 提供可安装到 Linux、Windows 和 macOS 的 Service。
  • Service 对外提供稳定、可版本化的 HTTP API。
  • 提供轻量的远程 Java SDK 和依赖 Core 的嵌入式 Java SDK。
  • 提供 WebUI 和 Desktop UI,两者使用相同的 Service API。
  • 通过 Schema 驱动可视化配置,减少新增 Provider 时的 UI 开发量。
  • 提供任务运行监控、实时日志、动态并发调整和交互式 API 文档。
  • 支持从单机 SQLite 平滑发展到 PostgreSQL 和多 Agent 部署。
  • 保证 Classic 与 Next 可以在同一台机器上并行安装和运行。

3. 非目标

以下项目不属于当前架构目标:

  • 不追求与 Classic 的内部代码、数据模型或 UI 实现兼容。
  • 不要求首个版本迁移 Classic 的全部消息类型。
  • 不承诺外部消息渠道的严格 Exactly Once 语义。
  • Standalone 首个版本不要求多节点;正式 Server 模式按 PostgreSQL 控制面高可用基线建设。
  • 不在首期建设通用工作流编排平台。
  • 不在 Core 中实现用户、权限、租户、HTTP API 或数据库管理。
  • 不要求 WebUI 与 Desktop UI 使用完全相同的外壳技术,但应尽量共享前端能力。
  • 不建设官方公共 SaaS、用户注册、计费、套餐、订阅、账单或公共市场。
  • 不提供面向互不信任或恶意公共租户的物理隔离和跨区域 Active-Active 控制面。
  • 不建设云 KMS、云 Secret Manager、Vault 或操作系统凭据库形式的 Service Secret Store 官方适配器。
  • 不引入强制自动更新、远程配置下发或使用遥测回传。

长期产品边界及其变更规则以《产品目标、边界与路线图》为准。

4. 架构原则

4.1 明确执行位置

所有推送动作最终由 Agent 中的 Core Engine 执行。Service 负责任务定义、调度和控制,不直接包含消息渠道的发送业务逻辑。

单机模式可以将 Agent 嵌入 Service 进程,但逻辑边界保持不变。

4.2 依赖指向稳定抽象

Core Engine 依赖 Core API 和 Provider SPI,不依赖 Service、Agent、UI、MyBatis、Spring 或具体 Provider。

4.3 配置和运行快照分离

账号、消息、受众和任务定义可以被修改;一次 Run 启动后必须形成不可变执行快照,避免运行过程中读取到被修改的配置。

4.4 Schema 驱动扩展

Provider 使用 JSON Schema 描述账号、消息和受众配置,UI、API 校验和 SDK 辅助能力围绕同一份 Schema 工作。

4.5 事件驱动运行状态

Core 不直接更新 UI。进度、日志、告警和状态变化都以结构化 Run Event 输出,由 Agent 和 Service 负责传输与持久化。

4.6 默认安全

本地模式默认只监听回环地址;远程模式必须启用认证。Secret 不出现在日志、普通查询响应和运行事件中。

4.7 渐进式分布

先完成 Service 内嵌 Agent 的单机闭环,再引入远程 Agent。分布式能力建立在稳定的运行状态机和租约模型之上。

5. 系统上下文

mermaid
flowchart LR
    Operator[操作人员] --> WebUI[WebUI]
    Operator --> Desktop[Desktop UI]
    JavaApp[Java 应用] --> SDK[Remote Java SDK]

    WebUI --> API[Service API]
    Desktop --> API
    SDK --> API

    API --> Service[WePush Service<br/>控制面]
    Service --> Database[(业务数据库)]
    Service --> ArtifactStore[(制品存储)]
    Service --> Agent[WePush Agent<br/>执行面]
    Agent --> Engine[Core Engine]
    Engine --> Providers[Provider 插件]
    Providers --> Channels[微信 / 短信 / 邮件 / HTTP 等]

6. 总体组件

组件 核心职责 明确不负责
Core API 执行命令、状态、事件、结果和策略的稳定模型 数据库、HTTP、UI、Provider 实现
Core Engine 执行流水线、并发、限流、重试、取消、结果汇总 调度、用户权限、REST API
Provider SPI Provider 生命周期、配置 Schema、发送和能力接口 具体渠道实现
Provider 渠道协议适配、认证、请求构建和响应解释 Service CRUD、UI 控件
Agent 注册、心跳、任务租约、Core 执行、事件上报 面向用户的业务管理 API
Service 配置管理、调度、Run 编排、Agent 管理、持久化、认证 直接调用渠道发送消息
Remote Java SDK Service API 的类型安全客户端 本地执行引擎
Embedded Java SDK 在 Java 进程内使用 Core 和 Provider 远程 Service 管理能力
WebUI 浏览器管理、配置、监控和 API 调试 直接访问数据库或 Core
Desktop UI 桌面入口、本地 Service 管理、远程 Service 连接 直接执行 Provider
Distribution 三平台安装、服务注册、升级和卸载 业务逻辑

7. 建议目录与模块结构

text
next/
├── pom.xml
├── docs/
│   ├── architecture-and-high-level-design.md
│   └── adr/
├── platform/
│   └── wepush-bom/
├── core/
│   ├── core-api/
│   ├── provider-spi/
│   └── engine/
├── providers/
│   ├── provider-http/
│   ├── provider-standard/
│   └── ...
├── agent/
│   ├── agent-protocol/
│   ├── agent-runtime/
│   └── agent-app/
├── service/
│   ├── service-api/
│   ├── service-domain/
│   ├── service-application/
│   ├── service-infrastructure/
│   └── service-app/
├── sdk/
│   ├── sdk-java/
│   └── embedded-java/
├── ui/
│   ├── web/
│   └── desktop/
├── distributions/
│   ├── linux/
│   ├── windows/
│   └── macos/
└── tests/
    ├── architecture-tests/
    ├── contract-tests/
    ├── integration-tests/
    └── end-to-end-tests/

模块可以随实现进展合并或拆细,但组件依赖方向不得被破坏。

8. 模块依赖规则

mermaid
flowchart BT
    CoreAPI[core-api]
    SPI[provider-spi] --> CoreAPI
    Engine[engine] --> CoreAPI
    Engine --> SPI
    Provider[providers/*] --> SPI
    AgentRuntime[agent-runtime] --> Engine
    AgentApp[agent-app] --> AgentRuntime
    AgentApp --> Provider
    ServiceDomain[service-domain] --> CoreAPI
    ServiceApp[service-application] --> ServiceDomain
    ServiceInfra[service-infrastructure] --> ServiceApp
    ServiceBoot[service-app] --> ServiceInfra
    ServiceBoot --> AgentRuntime
    ServiceBoot --> Provider
    SDK[sdk-java] --> ServiceAPI[service-api]
    Embedded[embedded-java] --> Engine

强制规则:

  • core/* 不得依赖 agent/*、service/*、sdk/* 或 ui/*。
  • Provider 不得直接访问 Service 数据库。
  • sdk-java 不得依赖 Engine 或具体 Provider。
  • embedded-java 可以依赖 Engine 和选定 Provider。
  • UI 只能通过公开 API 或 SDK 访问 Service。
  • Service 的领域层不得依赖 Controller、数据库实现或操作系统服务实现。
  • 可使用架构测试阻止禁止的包依赖进入主分支。

9. 部署形态

9.1 Standalone 单机模式

mermaid
flowchart LR
    UI[WebUI / Desktop UI] --> Service[Service]
    subgraph Process[单个 Service 进程]
        Service --> EmbeddedAgent[Embedded Agent]
        EmbeddedAgent --> Engine[Core Engine]
    end
    Service --> SQLite[(SQLite)]
    Service --> LocalFiles[(本地制品目录)]

适用个人电脑、开发环境和轻量服务器。Service、调度器和内嵌 Agent 在同一进程内运行,使用 SQLite 和本地文件系统。

9.2 Server + Remote Agent 模式

mermaid
flowchart TB
    UI[WebUI / SDK] --> LB[HTTP/2 负载均衡器]
    Agent1[Agent A] --> LB
    Agent2[Agent B] --> LB
    Agent3[Agent C] --> LB
    LB --> Service1[Service A]
    LB --> Service2[Service B]
    Service1 --> PostgreSQL[(PostgreSQL 18 HA)]
    Service2 --> PostgreSQL
    Service1 --> ObjectStore[(S3-compatible Artifact Store)]
    Service2 --> ObjectStore
    Agent1 --> Channels[外部渠道]
    Agent2 --> Channels
    Agent3 --> Channels

远程 Agent 主动通过负载均衡器建立出站 gRPC 双向流,便于跨防火墙部署。正式 Server HA 至少包含两个无状态 Service 实例;PostgreSQL 是业务和协调状态的事实源,对象制品不得依赖某个 Service 的本地磁盘。具体约束见 ADR-0006。

9.3 Embedded 模式

业务 Java 应用通过 embedded-java 直接创建 Engine,并显式注册所需 Provider。该模式不依赖 Service,但也不自动获得 Service 的用户管理、调度、持久化和 WebUI 能力。

10. Core Engine 概要设计

10.1 核心输入

Engine 接收不可变的 RunExecutionSpec,主要包含:

  • runId
  • Provider 标识和版本
  • 账号配置或 Secret 引用
  • 消息模板快照
  • 受众快照或可流式读取的 Recipient Source
  • 并发、限流和重试策略
  • Dry Run、结果保存和超时策略
  • 执行环境及扩展属性

Engine 不接受数据库主键后自行查询数据库,所有执行所需信息由调用方准备并显式传入。

10.2 执行流水线

mermaid
flowchart LR
    Validate[校验执行快照] --> Prepare[初始化 Provider]
    Prepare --> Read[流式读取 Recipient]
    Read --> Render[渲染消息]
    Render --> Limit[并发与限流]
    Limit --> Send[Provider 发送]
    Send --> Classify[结果分类]
    Classify --> Retry{是否可重试}
    Retry -- 是 --> Backoff[退避等待]
    Backoff --> Send
    Retry -- 否 --> Persist[写入结果流]
    Persist --> Event[发布进度事件]

10.3 执行策略

  • ConcurrencyPolicy:固定并发或可动态调整并发。
  • RateLimitPolicy:全局、Provider、账号或任务级速率限制。
  • RetryPolicy:最大次数、指数退避、抖动和可重试错误分类。
  • TimeoutPolicy:单次请求和整个 Run 的超时。
  • ResultPolicy:结果明细、响应体、失败样本和制品保存策略。
  • CancellationPolicy:协作式取消和取消后的未发送数据处理。

Java 25 虚拟线程可以作为 I/O 型发送的默认执行单元,但必须通过并发闸门和限流器限制实际外部请求数量,不能把虚拟线程数量直接等同于渠道并发能力。

10.4 事件接口

Core 通过 RunEventSink 输出结构化事件,例如:

  • RUN_STARTED
  • PROGRESS_UPDATED
  • ITEM_FAILED
  • CONCURRENCY_CHANGED
  • RETRY_SCHEDULED
  • RUN_CANCELLING
  • RUN_COMPLETED
  • RUN_FAILED

高频进度在 Agent 内聚合后批量上报,避免每条消息产生一次数据库写入。

10.5 结果与内存模型

  • Recipient、发送结果和日志均采用流式或批量处理。
  • 不在内存中长期保存完整的成功、失败和未发送列表。
  • 大结果集写入 Artifact Store,数据库只保存摘要、索引和制品引用。
  • Run 结束时生成成功、失败、未发送和可选响应体制品。

11. Provider SPI 概要设计

11.1 Provider 能力

每个 Provider 至少暴露:

  • 唯一 providerId 和实现版本。
  • 展示名称、分类和能力列表。
  • 账号配置 JSON Schema。
  • 消息配置 JSON Schema。
  • 受众字段定义及可选受众配置 Schema。
  • 配置校验和连接测试能力。
  • 同步发送接口;异步渠道可通过统一结果句柄扩展。
  • 错误分类、是否可重试和建议退避时间。
  • 可选 Dry Run、预览和动态限流能力。

11.2 Schema 示例

json
{
  "providerId": "wepush.http",
  "version": "1.0.0",
  "capabilities": ["preview", "dry-run", "response-body"],
  "accountSchema": {"$ref": "schemas/account.json"},
  "messageSchema": {"$ref": "schemas/message.json"},
  "uiSchema": {"$ref": "schemas/ui.json"}
}

敏感字段通过 writeOnly 及 x-wepush-secret 标识。UI 只显示“已配置”状态,Service 查询接口不得返回原值。

11.3 Provider 生命周期

Provider 实例应按账号和执行上下文创建,禁止使用可变全局单例保存 Token、连接和运行进度。连接池可以共享,但必须具有明确的关闭和隔离策略。

外部 Provider 使用 PF4J 3.15.x 发现,每个插件使用独立 ClassLoader 隔离私有厂商 SDK。正式环境仅装载清单与 Ed25519 签名均通过验证的版本。更新采用版本化目录、Agent Drain、重启、验证和激活流程,多 Agent 场景滚动执行;不在 JVM 内热替换正在使用的 Provider。ClassLoader 隔离不是恶意代码沙箱;如果用户自建安全场景需要运行非受信插件,必须单独评审独立进程 Runner,当前不将其列为既定路线图。详见 ADR-0005。

12. Agent 概要设计

12.1 职责

  • 向 Service 注册 Agent 身份、版本和能力。
  • 周期性上报心跳、负载、Provider 清单和运行状态。
  • 领取有时限的 Run Lease。
  • 下载执行快照和所需制品。
  • 调用 Core Engine 执行。
  • 批量上报事件、指标和结果制品。
  • 接收取消、暂停、恢复和动态调整并发等命令。
  • 在进程退出时尽力完成优雅停止。

12.2 租约与恢复

mermaid
sequenceDiagram
    participant A as Agent
    participant S as Service
    A->>S: Connect + Hello(gRPC 双向流)
    S-->>A: Welcome + 协议窗口
    loop 心跳/调度
        A->>S: Heartbeat(capacity, providers)
        S-->>A: LeaseOffer + executionSpecRef
        A->>S: LeaseAck(epoch, fencingToken)
    end
    A->>A: Core 执行
    loop 运行期间
        A->>S: Heartbeat + EventBatch
        S-->>A: EventAck + RunCommand
    end
    A->>S: complete(summary, artifacts)
  • Lease 有明确到期时间并通过心跳续约。
  • Agent 失联后,Run 先进入 LOST 或 RECOVERING,不能立即无条件重发。
  • 如果外部渠道已接收请求、但结果尚未持久化,系统可能无法确定发送结果,应标记为 UNKNOWN。
  • 支持渠道幂等键时应传递 runId + itemId;不支持时明确接受极端故障下的重复风险。

12.3 Agent 与 Service 协议

远程 Agent 的长期控制协议固定为 HTTP/2 上的 gRPC 双向流,契约使用 Protobuf。连接承载 Hello、心跳、能力、Lease、命令、事件确认和终态;每个方向具有单调 Sequence,Lease 仍使用 Epoch 和 Fencing Token。

Enrollment 和凭据轮换使用 HTTPS REST;WebUI、Desktop UI 和远程 SDK 的 Run 实时事件使用 SSE;大体积 Artifact 通过短期签名 HTTPS URL 传输。正式远程 Agent 不发布长轮询过渡协议,也不默认使用 WebSocket。详见 ADR-0004。

13. Service 概要设计

13.1 分层

  • service-api:公开 DTO、API 契约和错误模型。
  • service-domain:领域实体、状态机和仓储接口。
  • service-application:用例编排、事务边界和权限检查。
  • service-infrastructure:数据库、Secret、Artifact、Agent 通信等适配器。
  • service-app:Web 框架、配置、启动和部署入口。

Service 基线固定为 Java 25 和 Spring Boot 4.1.x,初始实现版本为 4.1.1;数据库迁移使用显式版本化迁移工具,依赖版本由 BOM 统一管理。Spring 只存在于 Service App、Web 和 Infrastructure 层,不进入 Core、Provider SPI、Agent Runtime、Remote Java SDK 或 Embedded SDK。详见 ADR-0002。

13.2 主要领域对象

对象 说明
ProviderDescriptor 已安装 Provider、版本、能力和 Schema
Account 渠道账号元数据及 Secret 引用
MessageTemplate 消息模板及版本
Audience 受众定义
AudienceSnapshot 一次物化后的不可变受众版本
JobDefinition 账号、消息、受众和执行策略的组合
Schedule Cron、时区、启停和错过执行策略
Run 一次执行实例和状态摘要
RunSnapshot Run 启动时冻结的完整执行配置
Agent 执行节点身份、能力和状态
AgentLease Agent 对 Run 的限时所有权
RunEvent 状态、进度、日志和控制事件
Artifact 输入快照、结果、日志等大对象引用

13.3 Run 状态机

mermaid
stateDiagram-v2
    [*] --> PENDING
    PENDING --> LEASED
    LEASED --> RUNNING
    LEASED --> PENDING: 租约未确认
    RUNNING --> PAUSED
    PAUSED --> RUNNING
    RUNNING --> CANCELLING
    PAUSED --> CANCELLING
    CANCELLING --> CANCELLED
    RUNNING --> SUCCEEDED
    RUNNING --> PARTIAL
    RUNNING --> FAILED
    RUNNING --> LOST: Agent 失联
    LOST --> RECOVERING
    RECOVERING --> PENDING: 允许重新执行
    LOST --> FAILED: 无法安全恢复

所有状态变更由应用服务执行并校验前置状态,Controller、UI 和 Agent 不能直接修改数据库状态字段。

13.4 调度

  • Schedule 归 Service 管理,不属于 UI。
  • 每次调度触发创建独立 Run 和 Run Snapshot。
  • 明确时区和夏令时行为。
  • 支持错过触发策略:跳过、立即补一次或按限制补偿。
  • Standalone 由单 Service 调度;Server HA 由持有 PostgreSQL Session Advisory Lock 的实例运行 Schedule Scanner,连接失效即释放领导权。

14. 对外 API 设计

14.1 基本约定

  • API 前缀:/api/v1;Workspace 业务资源前缀为 /api/v1/workspaces/{workspaceId}。
  • OpenAPI 作为公开契约和 SDK 生成来源。
  • 资源标识使用不透明 ID,建议 UUID 或 UUIDv7。
  • 创建 Run 等非幂等操作支持 Idempotency-Key。
  • 列表接口统一分页、排序和过滤。
  • 错误响应使用统一 Problem Detail 模型,并包含稳定错误码和 Trace ID。
  • Secret 字段只写不读,更新时支持“保持原值”和“替换”。

14.2 主要资源

text
/* Detailed source-code truncated for AI context efficiency. */

14.3 实时事件

运行监控首期采用 Server-Sent Events。客户端可以携带事件游标或 Last-Event-ID 断线续传;历史事件仍可通过普通分页 API 查询。

命令通过 REST 提交,事件通过 SSE 下发。浏览器端不默认引入 WebSocket。

14.4 交互式 API 文档

Service 提供:

  • /openapi.json 或等价 OpenAPI 描述地址。
  • /docs 交互式文档页面。
  • 当前登录身份的认证信息注入。
  • 请求示例、响应示例和错误码说明。
  • Try It Out 动态调用能力。

生产环境中,API 调试页面必须经过权限控制。危险操作需要二次确认,Secret 不得写入浏览器持久化存储或出现在生成的示例中。

15. Remote Java SDK 设计

15.1 远程 SDK

sdk-java 面向 Service API:

  • 从 OpenAPI 生成底层模型和客户端。
  • 在生成代码之上提供稳定的手写 Facade。
  • 支持认证、超时、重试、分页和 SSE 订阅。
  • 不依赖 Core Engine、Provider 或 Service 内部实体。
  • SDK 的兼容性以公开 API 为准,而不是以 Service 内部版本为准。

15.2 Embedded SDK

embedded-java 面向本地进程内执行:

  • 依赖 Core Engine 和用户选择的 Provider。
  • 使用显式 Builder 注册 Provider、Secret Resolver、事件和结果适配器。
  • 不自动启动 Service,不包含 WebUI 和 Service 数据库。
  • 可以为测试、第三方 Java 应用嵌入和自定义执行环境提供能力。

16. WebUI 概要设计

主要功能区域:

  • 总览:Service、Agent、Provider 和近期 Run 状态。
  • 账号管理:Schema 动态表单、连接测试和 Secret 状态。
  • 消息管理:可视化配置、模板变量、预览和 Dry Run。
  • 受众管理:文件、数据库、第三方来源导入及快照管理。
  • 任务与调度:任务组合、执行策略、Cron 和时区。
  • 运行中心:进度、吞吐、成功率、失败分类、并发动态调整。
  • 日志与制品:实时日志、结果下载、失败重试入口。
  • Agent 管理:在线状态、版本、能力、负载和禁用。
  • API 文档:动态接口说明和在线调试。

动态表单由 Provider Schema 驱动。JSON Schema 负责数据类型和校验,UI Schema 负责分组、顺序、控件提示和 WePush 扩展控件。

前端固定采用 TypeScript 6.0.x、Vite 8.1.x 和 React 19.2.x,以 Node.js 24 LTS、pnpm 和单一 Lockfile 管理工程。样式采用 Tailwind CSS;shadcn/ui 只在能降低复杂组件成本时按需引入,复制进仓库的组件源码由项目自行维护。WebUI 是纯 SPA,正式运行不依赖 Node.js 服务端。详见 ADR-0002。

17. Desktop UI 概要设计

Desktop UI 是 Service API 的薄客户端,不直接依赖 Core 或数据库。

目标能力:

  • 连接本机 Standalone Service 或远程 Service。
  • 首次运行引导和本地 Service 状态管理。
  • 提供系统托盘、通知、自动启动和桌面更新体验。
  • 尽量复用 WebUI 页面、Schema 渲染器和 API 客户端。
  • 本地连接使用回环地址和短期引导凭据,避免无认证端口。

Desktop 外壳固定为 Electron 43.x,并复用 WebUI 的 React 页面、Schema Renderer、API Client 和设计 Token。Main、Preload、Renderer 严格分层;Renderer 禁用 Node.js 集成,启用 Context Isolation 和 Sandbox,只通过固定白名单 IPC 使用本机 Service 状态/启停/日志/诊断、签名插件生命周期和 Token safeStorage,不提供任意命令入口。浏览器 Token 只保存在标签页会话,Desktop 在系统安全后端不可用时拒绝弱持久化。该选择不改变 Desktop 只能通过 Service API 访问业务能力的边界。详见 ADR-0002。

18. 数据与存储设计

18.1 数据库

  • Standalone 默认 SQLite。
  • Server 模式固定使用 PostgreSQL 18.x;数据库自身复制、故障转移和备份由部署者选择并负责运维。
  • 通过 Repository Port 隔离数据库实现,但不以支持任意数据库为目标。
  • 所有 Schema 变化必须使用版本化迁移,不允许运行时拼接临时升级逻辑。
  • 数据库事务以应用用例为边界,不共享全局 Session。

18.2 Artifact Store

Artifact Store 保存:

  • Audience Snapshot 数据文件。
  • 成功、失败、未发送结果。
  • 可选 Provider 响应体。
  • 大体积日志和导出文件。

Standalone 默认使用 LocalFileArtifactStore;Server 默认使用受控 S3-compatible API 的 S3ArtifactStore。Agent 使用 Service 签发的短期 Presigned URL 直传,数据库保存元数据、SHA-256、大小、状态和对象键。对象键按 Workspace 分区;保留期由 Service 清理任务执行,对象存储 Lifecycle 只作兜底。详见 ADR-0007。

18.3 Secret Store

  • 默认实现为 LocalEnvelopeSecretStore,每个 Secret 使用独立随机 DEK 和 AES-256-GCM 加密,AAD 绑定 Workspace、Secret ID、类型和版本。
  • 数据库保存密文、Nonce、加密后的 DEK 和主密钥版本;主密钥只来自独立受保护文件或显式外部注入。
  • Standalone 首启可创建仅当前用户可读的主密钥文件;Server 不得静默生成新主密钥,缺失或权限不安全时 Fail Closed。
  • SecretStore 保持 Port 以维持模块边界和可测试性;官方产品实现固定为本地信封加密,不规划 Vault、云 KMS、云 Secret Manager 或操作系统凭据存储适配器。Electron safeStorage 不作为 Service Secret 实现。
  • Agent 只在执行期间获取最小范围 Secret,并在内存中短期持有。

密钥轮换默认只重包裹 DEK,不重写业务密文。详见 ADR-0003。

19. 安全设计

  • Standalone 默认绑定 127.0.0.1 或 ::1。
  • 远程访问必须使用 TLS。
  • 用户 API 支持管理员、操作员和只读角色。
  • Agent 使用独立身份,不复用用户 Token。
  • 关键操作记录审计事件,包括操作者、时间、对象和结果。
  • 所有日志、事件、异常和 API 响应经过 Secret Redactor。
  • 文件上传限制大小、类型和解析资源,防止压缩炸弹和超大内存占用。
  • HTTP Provider 对目标地址提供可配置的 SSRF 防护策略。
  • 机器人 Webhook 必须匹配厂商官方 HTTPS 域名与路径;短信和微信系生产 API 固定官方端点,测试回环端点不进入公开构造接口。
  • API 调试功能遵循当前用户权限,不提供绕过权限的内部调用通道。

Workspace 逻辑隔离进入正式 Server 产品范围;Standalone 自动使用隐藏的 Default Workspace。Account、Secret、Message、Audience、Job、Schedule、Run、Artifact、Agent Pool 和 API Token 均必须归属 Workspace,所有 Repository、唯一索引、缓存键和授权检查显式携带 workspaceId。Workspace 只服务于同一用户自建实例内的团队和权限分组,不演进为公共 SaaS 租户;计费、自助注册和恶意租户物理隔离属于长期非目标。详见 ADR-0008。

20. 可观测性

Service 和 Agent 应提供:

  • Health、Readiness 和版本信息。
  • 结构化日志和统一 Trace ID、Run ID、Agent ID。
  • Run 级吞吐、成功、失败、重试、限流和延迟指标。
  • Agent 心跳延迟、可用容量和任务数。
  • Provider 请求耗时和错误分类,但不记录敏感请求内容。
  • 审计日志与普通运行日志分离。

监控接口默认不公开到非受信网络。

21. 交付与跨平台安装

21.1 Service 制品

  • 通用可执行 JAR 或应用镜像。
  • Linux:tar/deb/rpm,并提供 systemd 服务单元。
  • Windows:zip/msi/exe,并通过 Windows Service Wrapper 或等价机制注册服务。
  • macOS:pkg/dmg,并通过 launchd 注册服务或用户级 Agent。
  • 可选 Docker/OCI 镜像作为 Linux Server 部署补充。

21.2 独立标识

Next 必须使用独立于 Classic 的:

  • 应用名称和 Service 名称,例如 WePush Next、wepush-next。
  • 默认配置目录和数据目录。
  • 默认端口。
  • 日志目录。
  • 用户可控的版本检查标识和发布资产名称;不使用与 Classic 混合的更新元数据。

安装、升级和卸载不得读取、覆盖或删除 Classic 的文件。

22. 一致性与投递语义

WePush Next 对外声明 At Least Once 为基础投递语义。

当外部渠道已经接受请求,但 Agent 在持久化结果前崩溃时,系统无法可靠判断是否已发送。此类消息应进入 UNKNOWN,由用户或策略决定是否重试。只有 Provider 原生支持幂等键时,才可以提供更强的去重保证。

Run 的状态摘要、事件和结果制品允许短暂最终一致,但终态必须经过校验,确保计数与已持久化结果可追溯。

23. 测试策略

测试类型 重点
单元测试 状态机、模板、重试、限流、结果分类
架构测试 禁止依赖、包边界、Core 纯净性
Provider 契约测试 Schema、配置校验、错误分类、Dry Run
API 契约测试 OpenAPI、错误模型、兼容性和 SDK 生成
集成测试 SQLite、PostgreSQL 18、S3-compatible Artifact、Secret、SSE、Agent gRPC
故障测试 Agent 失联、Service 重启、租约到期、重复上报
性能测试 大受众流式处理、并发、内存上限和事件聚合
E2E 测试 WebUI/SDK 创建任务到结果下载完整链路
安装测试 Linux、Windows、macOS 安装、启动、升级和卸载

发布流水线必须先完成测试和契约校验,再生成平台安装包。安装包构建不得通过跳过测试来代替独立的测试阶段。

24. Classic 数据迁移

迁移是可选辅助能力,不是两个产品线的运行时连接。

  • 迁移工具只读 Classic 数据库和配置。
  • 迁移前创建可恢复备份。
  • 将 Classic 账号、消息、受众和任务转换为 Next 模型。
  • Secret 在导入过程中重新加密。
  • 不保证所有 Classic 配置都能无损转换,无法转换的内容生成报告。
  • 不提供持续双向同步。
  • Next 运行时不得直接访问 Classic 数据库。

25. 版本与兼容策略

  • Next 在架构建设期使用独立的 0.x 版本。
  • 公开 API 使用路径主版本,例如 /api/v1。
  • Remote Java SDK 与 API 兼容矩阵独立于 Service 内部模块版本。
  • Agent 协议单独版本化,Service 应允许一个受控范围内的新旧 Agent 共存。
  • Provider SPI 的不兼容变化必须提升 SPI 主版本。
  • Run Snapshot 保存必要的 Provider 和 Schema 版本,便于结果追溯。

26. 首个纵向里程碑

第一阶段不追求创建所有 Provider 和所有 UI 页面,而是完成 HTTP Provider 的完整闭环:

  1. HTTP Provider 提供账号和消息 Schema。
  2. Core Engine 能流式执行、限流、取消并输出事件。
  3. Embedded Agent 能领取并执行 Run。
  4. Service 能管理账号、消息、受众、任务和 Run。
  5. Remote Java SDK 能创建任务、启动 Run 和订阅事件。
  6. WebUI 能动态配置 HTTP Provider、启动 Run 并查看实时结果。
  7. Standalone 安装包能在至少一个平台完成安装和重启恢复。

该纵向链路已完成;0.1.0-alpha.4 进一步交付 SMTP、三类群机器人、阿里云短信、微信公众号、小程序和企业微信应用消息,详见内置 Provider 指南。

27. 阶段规划

阶段 A:工程和契约基线

  • 创建 Maven 聚合工程和模块边界。
  • 定义 Core API、Provider SPI 和架构测试。
  • 定义 OpenAPI、错误模型和 Provider Schema 约定。

阶段 B:单机运行闭环

  • 实现 Core Engine、HTTP Provider 和 Embedded Agent。
  • 实现 Service、SQLite、Artifact 和基础 Secret 存储。
  • 完成 Remote Java SDK 和最小 WebUI。

阶段 C:产品化单机版本

  • 完善可视化配置、任务调度、运行监控和 API 文档。
  • 增加 Desktop UI 和三平台 Service 安装。
  • 完成升级、备份和故障恢复。

阶段 D:服务器与分布式执行

  • 增加 PostgreSQL、远程 Agent 和租约恢复。
  • 增加 Agent 管理、RBAC、审计和远程 TLS。
  • 落实 S3-compatible Artifact Store 和 PostgreSQL 控制面高可用。

阶段 E:Provider 扩展

  • 按业务优先级迁移或重新实现消息类型。
  • 为每个 Provider 建立契约测试和模拟服务。
  • 完善 Schema 组件库和 Provider 开发文档。

截至 2026-08-23,阶段 A–D 的 0.1.0 架构基线已经实现并进入持续验证;阶段 E 是按业务优先级持续增加消息类型的长期产品迭代,不阻塞当前 Next 目标架构成立。实现证据见 实现状态,安装、HA、升级和恢复见 部署与运维。

28. 已接受的架构决策

这些决策是 Next 初始实现的正式基线。公共 SaaS、计费订阅、外部 Vault/云 KMS/Secret Manager、恶意租户物理隔离和跨地域控制面属于长期非目标,不得通过临时代码或普通功能 ADR 隐式引入。非受信 Provider 的进程级隔离只有在明确服务于用户自建安全场景时才可以单独评审,且不得改变产品定位。

29. 架构验收条件

以下条件用于判断首个架构闭环是否成立:

  • Core 模块不存在 Swing、AWT、Spring、MyBatis 和具体数据库依赖。
  • Core 可以在纯单元测试中使用内存适配器执行完整 Run。
  • Provider 不通过数据库 ID 自行加载账号或消息配置。
  • 同一个 HTTP Run 可以由 Embedded Agent 和 Remote Agent 使用相同 Core 行为执行。
  • WebUI、Desktop UI 和 Remote Java SDK 通过同一公开 API 管理任务。
  • Service 重启后能够恢复非终态 Run,并明确区分可重试和结果未知状态。
  • Agent 失联不会导致 Service 无限等待,也不会未经判断立即重复发送。
  • 大受众执行不要求将全部 Recipient 和结果加载到内存。
  • Secret 不出现在查询响应、日志、事件和结果制品中。
  • OpenAPI 能生成 Remote Java SDK,并通过 API 契约测试。
  • Classic 与 Next 能在同一台机器上同时安装和运行,配置和数据互不影响。

30. 文档维护

本文档描述当前目标架构,不替代详细设计、API 契约和 ADR。

  • 会改变跨组件边界的决策必须新增或更新 ADR。
  • 单个模块内部的详细设计应放在对应模块的 docs/ 或 README 中。
  • OpenAPI、JSON Schema 和数据库迁移文件是可执行契约,应与实现一同评审。
  • 当实现与本文档不一致时,应先确认是实现偏离还是架构演进,再更新代码或文档,不能长期保持隐式差异。

Next Docs Compatibility Policy (`next/docs/compatibility-policy.md`)

WePush Next 兼容性策略

  • 适用版本:1.0.0 及后续 1.x(当前稳定版 1.2.0)
  • 首个稳定兼容基线:1.0.0
  • 最低直接升级版本:0.1.0-beta.1
  • 1.0.0 最低可回滚版本:0.1.0-beta.1
  • 1.1.0 推荐回滚版本:1.0.0(必须使用升级前完整备份)
  • 1.2.0 推荐回滚版本:1.1.0(必须使用升级前完整备份)

1. 版本规则

WePush Next 从 1.0.0 起使用语义化版本:

  • Patch(1.0.x)只包含兼容的修复、安全更新和文档调整。
  • Minor(1.x.0)可以增加可选 API、配置和 Provider 能力,但不能破坏已有用法。
  • Major(2.0.0)才允许移除或改变已经公开的兼容契约,并必须提供迁移说明。
  • 带连字符的版本仍是预览版;不带连字符的版本是稳定版。

2. REST、SSE 与 OpenAPI

/api/v1 和发行包中的 OpenAPI 3.1 文档是 1.x 公共 HTTP 契约。1.x 内承诺:

  • 不删除已有路径、HTTP 方法、响应状态或必需响应字段。
  • 不新增已有请求必须提供的字段,不收窄字段类型、长度或合法取值。
  • 可以增加可选请求字段、响应字段、端点和错误码;客户端应忽略未知响应字段。
  • 已发布字段或端点至少经过一个 Minor 版本的弃用期后,才可在下一个 Major 移除。
  • SSE 事件保持事件 ID 可恢复、事件类型可识别;可以增加新的事件类型和 Payload 字段。

1.0.0 发行门禁对 0.1.0-beta.1 OpenAPI 做忽略版本号后的精确摘要校验,因此 Beta 用户既有 HTTP 调用在稳定版中没有契约漂移。1.1.0 只增加 Workspace Policy、系统运维、认证熔断和 Agent Multipart 等端点与可选字段;已有路径继续由兼容性摘要门禁保护。1.2.0 不新增 REST/SSE 路径或必填字段,AI 助手通过既有 API 接入。

3. Java SDK 与 Provider SPI

  • Remote Java SDK 与 Embedded Java SDK 的公开类型在 1.x 内保持源码兼容;新增能力优先使用新方法、Builder 或新类型。
  • SDK 与 Service 必须使用相同 Major;Service 接受同 Major 的旧 Minor SDK 请求。
  • Provider SPI 以 spiMajorVersion=1 为稳定边界。1.x Agent 可加载 SPI Major 1 且通过清单、摘要和 Ed25519 签名校验的插件。
  • Provider ID 与实现版本属于 Run Snapshot。升级不会悄悄把既有 Run 改到另一个 Provider 实现版本。
  • Provider 行为发生不可兼容变化时必须发布新的实现版本,不能覆盖旧 Snapshot 语义。

4. 配置兼容

  • 0.1.0-beta.1 已公开的 WEPUSH_* 配置键在整个 1.x 保持可解析;发行门禁逐项验证这些键仍存在。
  • Workspace Artifact 保留策略在首次显式更新前继续采用既有 WEPUSH_EXPORT_RETENTION 与 WEPUSH_AGENT_ARTIFACT_RETENTION 的分类默认值;管理员保存 Workspace Policy 后,由该 Workspace 的统一保留期接管新 Artifact。
  • 新配置必须有安全默认值。升级不能默认启用外部网络访问、遥测、公开监听或弱认证。
  • 配置弃用必须记录替代键,并在至少一个 Minor 周期内继续接受旧键;非法或冲突配置启动时失败关闭。
  • Service/Agent 配置文件由用户控制,安装和升级脚本不能覆盖已存在文件。

5. 数据库迁移与回滚

  • Flyway 迁移只向前执行,已经发布的迁移文件不可修改、重排或复用版本号。
  • SQLite 与 PostgreSQL 使用相同的有序迁移历史,并在 CI 中分别验证。
  • 0.1.0-beta.1 的数据库版本为 V13;1.0.0 增加只新增元数据表的 V14,不删除或改写用户数据。
  • 1.0.0 可以直接从 0.1.0-beta.1 升级。自动化测试会先创建 V13 数据,再迁移到 V14 并核对原数据。
  • V14 是附加迁移,因此 1.0.0 最低可回滚到 0.1.0-beta.1。仍建议使用升级前自动备份回滚,以同时恢复数据库、Master Key、Artifact 和 Agent 状态。
  • 1.1.0 继续增加附加迁移:V15 workspace_policy、V16 account_auth_circuit、V17 artifact_multipart_upload。这些迁移不删除、重命名或改写 V14 及以前的业务数据;SQLite 与 PostgreSQL 都从 V13 带用户数据迁移到 V17 做自动验证。
  • 1.0.0 不会读取 V15–V17 新表,但 1.1.0 人工回滚仍必须恢复升级前完整备份,避免新策略、熔断、上传会话及对象存储状态遗留。不能把“旧程序暂时可启动”等同于受支持的数据回滚。
  • 1.2.0 不新增 Flyway 迁移,数据库版本保持 V17。它仍应通过升级前完整备份回滚到 1.1.0,以便同时恢复程序、配置和数据。
  • 未来版本一旦包含旧版本无法读取的迁移,Release Notes 必须提高最低可回滚版本,并明确只能通过备份恢复。

6. Agent 协议

  • Agent Hello 声明协议 Major/Minor;1.x Service 与 Agent 使用协议 Major 1。
  • 同 Major 内允许新增可忽略字段和帧类型,不改变 Lease/Fence、Sequence、ACK 和结果未知语义。
  • 建议 Service 与 Agent 使用相同版本;滚动升级期间支持当前稳定版与上一稳定 Minor 的短期混合运行。
  • Agent 失联不会直接触发无条件重发。Lease 到期后进入恢复流程;外部发送结果无法确认时记录 UNKNOWN。
  • 1.1.0 在 Major 1 内增加 Multipart Plan/Part/Complete/Abort 交互;单次 Presigned Put 继续兼容。超过 1 GiB 的 Agent Artifact 只有在 Service 和 Agent 都升级到 1.1.0 后才可用。
  • 1.2.0 不改变 Agent 协议。Service 与 Agent 可在 1.1.0 和 1.2.0 之间短期混合运行;运营商插件的实现版本随发行包更新为 1.2.0,新 Run 应使用同一 Release 的签名插件。
  • 精简包和 Java SDK 从 1.2.0 起要求 Java 25+。完整安装包携带 Java 25 运行时,不要求宿主机另行安装 JDK。这是运行基线调整,不改变已发布的 HTTP 契约。

7. 不属于兼容承诺的内容

  • 内部 Java 包、数据库表的直接访问、未记录环境变量和测试端点不是公共接口。
  • 第三方消息平台自身的接口变更不受 WePush 控制;Provider 会按错误分类和版本规则处理。
  • 0.x 早期 Alpha 版本不提供直升 1.0.0 的保证,必须先升级到 0.1.0-beta.1 或导出后重建。
  • WePush 不提供公共 SaaS 或官方托管控制面,因此不存在云端租户、计费或订阅数据的兼容承诺。

Next Docs Deployment And Operations (`next/docs/deployment-and-operations.md`)

WePush Next 部署与运维

本文是 1.2.0 稳定自部署版的可执行部署说明。Standalone 使用 SQLite 和本地 Artifact;用户自建 Server 使用 PostgreSQL、S3-compatible Artifact、两个以上 Service 实例以及外部负载均衡器。Classic 不参与 Next 的构建、安装或运行。WePush 不提供官方托管控制面,所有部署、数据、密钥和备份均由用户掌控;产品边界见《产品目标、边界与路线图》。

Service 的无认证开发模式仅允许绑定回环地址。任何非回环 HTTP 监听都必须启用 API Security;server 模式还会强制 PostgreSQL、S3-compatible Artifact Store 和 Agent gRPC TLS,缺少任一项均启动失败。

1. 构建与发行物

要求 JDK 25+、Node.js 24 和 pnpm 11.22.0:

bash
cd next/ui
pnpm install --frozen-lockfile
pnpm check

cd ..
./scripts/prepare-winsw.sh
./mvnw verify
./mvnw -DskipTests package

测试必须先独立通过,-DskipTests 只用于之后的发行打包阶段。输出位于:

  • distribution/target/wepush-next-1.2.0.tar.gz
  • distribution/target/wepush-next-1.2.0.zip
  • 对应 .sha256 文件

上面两个归档是使用系统 Java 25+ 的精简包。Release 流水线还在 Linux、macOS、Windows Runner 上用 JDK 25 jlink 生成对应架构的完整包,目录内多出 runtime/;两个变体都携带固定摘要的 WinSW 2.12.0,因此 Windows 用户安装时不联网。prepare-winsw.sh 只在源码发行构建阶段从上游下载并验证固定长度与 SHA-256。

归档内包含 Service/Agent Fat JAR、生产 WebUI、统一/分组件安装脚本、正式 Backup/Restore/Upgrade、配置模板和 Provider 插件生命周期工具。Desktop 原生目录包在当前操作系统执行:

bash
cd next/ui
pnpm --filter @wepush-next/desktop package

Desktop 打包器使用锁定版本的 Electron,不引入带 Git 构建依赖的额外打包链;缺少 Electron Runtime 时会运行该锁定版本随附的安装脚本。macOS、Windows、Linux 必须分别在目标操作系统打包。
pnpm 已显式信任固定版本 Electron 的原生运行时下载脚本;无法访问 GitHub Release Asset 的网络可在安装依赖时设置受信 ELECTRON_MIRROR。按项目明确边界,1.2.0 不使用商业签名:macOS 只做 ad-hoc 签名且不提交 Apple Notarization,Windows 不做 Authenticode 签名。系统可能显示未知开发者警告,请只从项目 GitHub Releases 下载并验证 SHA256SUMS;详见 UNSIGNED-NOTICE.md。

2. Linux Standalone

解压、校验并安装:

bash
sha256sum wepush-next-1.2.0-linux-x64.tar.gz
tar -xzf wepush-next-1.2.0-linux-x64.tar.gz
sudo ./wepush-next-1.2.0/install/install.sh

安装布局:

  • 版本目录:/opt/wepush-next/releases/<version>
  • 原子当前链接:/opt/wepush-next/current
  • 配置:/etc/wepush-next/service.env、agent.env
  • 数据:/var/lib/wepush-next/service、agent
  • systemd:默认安装 wepush-next-service;高级 all 模式另安装 wepush-next-agent

环境文件由 root 持有,只向相应服务组开放读取。修改配置后执行:

bash
sudo systemctl restart wepush-next-service
curl --fail http://127.0.0.1:18990/actuator/health/installation

备份会停止两个进程以获得 SQLite、Journal、Outbox 和配置的一致快照,并恢复原先处于运行状态的服务:

bash
sudo /opt/wepush-next/current/install/linux/backup.sh
sudo /opt/wepush-next/current/install/linux/restore.sh --validate-only "$BACKUP_FILE"
sudo /opt/wepush-next/current/install/linux/restore.sh "$BACKUP_FILE" "$BACKUP_SHA256"
sudo /opt/wepush-next/current/install/linux/upgrade.sh release.tar.gz <sha256>
sudo /opt/wepush-next/current/install/linux/uninstall.sh
sudo /opt/wepush-next/current/install/linux/uninstall.sh --purge  # 明确删除配置与数据

3. macOS Standalone

bash
sudo ./wepush-next-1.2.0/install/install.sh
launchctl print system/com.fangxuele.wepush-next.service
curl --fail http://127.0.0.1:18990/actuator/health/installation

版本、数据、配置和日志分别位于 /Library/WePushNext、/Library/Preferences/wepush-next 与 /Library/Logs/WePushNext。安装器用 LaunchDaemon 托管 Service/Agent,并拒绝覆盖非符号链接的 current 路径。
LaunchDaemon 默认以执行 sudo 的非 root 用户运行;无人值守安装必须显式设置已存在的 WEPUSH_SERVICE_USER,安装器拒绝回退到 root。

bash
sudo /Library/WePushNext/current/install/macos/backup.sh
sudo /Library/WePushNext/current/install/macos/restore.sh --validate-only "$BACKUP_FILE"
sudo /Library/WePushNext/current/install/macos/restore.sh "$BACKUP_FILE" "$BACKUP_SHA256"
sudo /Library/WePushNext/current/install/macos/upgrade.sh release.zip <sha256>
sudo /Library/WePushNext/current/install/macos/uninstall.sh

4. Windows Standalone

以管理员 PowerShell 执行:

powershell
Expand-Archive .\wepush-next-1.2.0-windows-x64.zip .\release
Set-ExecutionPolicy -Scope Process Bypass
& .\release\wepush-next-1.2.0\install\install.ps1
Get-Service WePushNextService
Invoke-WebRequest http://127.0.0.1:18990/actuator/health/installation

安装器只使用发行包内的 WinSW 2.12.0,并在安装前再次校验固定长度和 SHA-256;缺失或被修改时失败关闭,不进行在线下载。Service 以低权限 LocalService 运行。版本目录位于 %ProgramFiles%\WePush Next,配置和数据位于 %ProgramData%\WePush Next;该目录移除继承 ACL,只允许 LocalService、SYSTEM 和 Administrators。备份默认写到独立的 %ProgramData%\WePush Next Backups。

powershell
& "$env:ProgramFiles\WePush Next\current\install\windows\backup.ps1"
& "$env:ProgramFiles\WePush Next\current\install\windows\restore.ps1" -ValidateOnly -Archive '<backup>.zip'
& "$env:ProgramFiles\WePush Next\current\install\windows\restore.ps1" -Archive '<backup>.zip' -ExpectedSha256 '<sha256>'
& "$env:ProgramFiles\WePush Next\current\install\windows\upgrade.ps1" -Archive release.zip -ExpectedSha256 <sha256>
& "$env:ProgramFiles\WePush Next\current\install\windows\uninstall.ps1"

只有显式 -Purge 才删除持久数据。

5. Agent Enrollment 与远程运行

Server 管理员在 WebUI 设置页或 API 创建一次性 Enrollment Token,并绑定 Workspace。Agent 第一次启动设置:

text
WEPUSH_AGENT_ID=agent-east-1
WEPUSH_AGENT_ENROLLMENT_TOKEN=<one-time-token>
WEPUSH_SERVICE_BASE_URL=https://wepush.example.com
WEPUSH_SERVICE_HOST=wepush.example.com
WEPUSH_AGENT_GRPC_PORT=19090
WEPUSH_AGENT_GRPC_PLAINTEXT=false

Agent 生成 P-256 私钥,在 HTTPS Enrollment 后保存长期 Credential、客户端证书与 CA;证书或 Credential 距到期不足 14 天时自动轮换。身份文件、私钥、Event Outbox、Completion Outbox 和 Lease Journal 均必须位于持久卷。非回环部署不允许匿名 Agent;gRPC 必须启用 TLS,生产基线要求 mTLS。

6. Provider 插件

HTTP、SMTP Email、飞书/钉钉/企微机器人、阿里云短信、微信公众号、小程序和企业微信应用消息是发行包内置 Provider,不需要放入插件目录。内置渠道的账号、SecretRef、最小消息和验证步骤见《内置 Provider 指南》。

CMPP、SMGP、SGIP 和 SMPP 从 1.1.0 起作为独立 Release Asset 交付,文件名为 wepush-provider--1.2.0.zip 及对应 .sha256。官方签名 Key ID/公钥在同一 Release 的 wepush-provider-trusted-key-1.2.0.env 中,并带独立 .sha256;它们也全部列入统一 SHA256SUMS。插件不会随核心发行包自动启用。先从同一 GitHub Release 下载并同时校验这些文件,再执行 Stage/Activate;不要信任插件 ZIP 内或第三方页面提供的替代公钥。能力边界、源码构建与发布签名说明见 plugins/README.md。

正式模式必须设置 WEPUSH_PLUGIN_TRUSTED_KEYS,格式是 keyId:Base64Ed25519PublicKey,多个发布者用逗号分隔。插件 ZIP 包内 plugin.json 的 SHA-256 清单与 signature.ed25519 必须通过验证;ZIP Slip、共享 API 重复打包、未知签名者和 SPI 不兼容都会导致 Agent 失败关闭。

Linux 应以 Agent 服务账号执行 Stage/Activate,确保文件所有权正确:

bash
sudo -u wepush-agent /opt/wepush-next/current/plugins/stage.sh provider.zip
sudo -u wepush-agent /opt/wepush-next/current/plugins/activate.sh <pluginId>.zip
sudo -u wepush-agent /opt/wepush-next/current/plugins/rollback.sh <pluginId>.zip

Stage 先调用 Agent 的生产校验器读取 plugin.json,验证摘要、SPI 和 Ed25519 签名,再以 .zip 作为稳定文件名落盘。激活采用版本备份、Supervisor 重启和健康观察;新插件导致 Agent 退出时自动恢复旧包并再次启动。Desktop 的 Providers 页提供相同的本地选择、校验、Stage、Activate 和 Rollback 操作,并通过系统授权写入插件目录。该机制是受控滚动更新,不在 JVM 内热卸载 ClassLoader。

7. Server/HA 容器拓扑

deployment/container/compose.server.yaml 是本地/验收拓扑:PostgreSQL 18、MinIO、两个无状态 Service 和 HAProxy。HTTP API 通过 127.0.0.1:18990 暴露;Agent gRPC 通过 TCP 透传的 19090 暴露,因此 mTLS 端到端保持在 Agent 与 Service 之间。

首次运行:

bash
cd next/deployment/container
./generate-dev-secrets.sh
docker compose --env-file .env.server.local -f compose.server.yaml up --build -d
curl --fail http://127.0.0.1:18990/actuator/health/readiness
curl --fail http://127.0.0.1:18990/actuator/prometheus

开发脚本生成的 CA/密钥和 .env.server.local 已被 .gitignore 排除,且拒绝覆盖已有文件。两个 Service 共享:

  • PostgreSQL 业务状态、Lease、Service→Agent Outbox、Schedule 与审计事实源;
  • Agent CA、API Bootstrap Token、Secret 主密钥和 Artifact 签名密钥;
  • MinIO Artifact 对象。

Schedule Scanner 使用 PostgreSQL Advisory Lock 单 Leader;Agent 命令和 Lease Offer 使用持久 outbox,只有持有当前 gRPC 流的实例发送;SSE 以数据库事件日志和周期轮询跨实例补偿。

1.1.0 另对 wepush_run_pending、wepush_agent_outbox、wepush_run_event 三个 PostgreSQL Channel 执行 LISTEN/NOTIFY,只用于缩短调度、Agent 命令和 SSE 的唤醒延迟。连接断开、通知丢失或重复不得影响正确性,周期扫描、持久 Outbox 与事件游标必须始终保持启用。

除 Compose 验收拓扑外,deployment/templates/ 提供可复制的 Nginx、Traefik 和 Kubernetes 用户自建模板。模板只描述网络入口、Service 与配置挂载,不创建受项目托管的数据库、对象存储或密钥服务;部署者必须根据自己的域名、证书、StorageClass、Secret 管理和备份方案完成替换。

该 Compose 为本地协议验收拓扑,明确使用 WEPUSH_S3_SERVER_SIDE_ENCRYPTION=NONE。正式自建环境应配置对象存储原生 AES256,或由部署者在其存储层保证等价的静态加密;WePush 不对接或管理云 KMS。正式环境还必须:

  • 使用由部署者负责的 PostgreSQL HA 和备份恢复;
  • 在 HTTP API 前终止受信 TLS,保留 gRPC TLS 透传或等价的受控 mTLS 方案;
  • 将 Master Key、Agent CA 和其他密钥放入权限受控的只读挂载文件,不使用提交到源码或共享目录的 Compose env 文件;
  • 配置 S3 Lifecycle 作为未完成 Multipart 与误删保护的兜底;
  • 至少两个 Service 跨故障域部署并配置 Readiness/Prometheus 告警。

停止本地环境:

bash
docker compose --env-file .env.server.local -f compose.server.yaml down

只有确认不再需要测试数据时才执行带 --volumes 的删除。

8. `1.1.0` 资源、诊断与 Artifact 运维

  • 系统管理员在 WebUI 设置页或 /api/v1/workspaces/{workspaceId}/policy 管理 Agent 数、活动 Run、总发送并发、Artifact 容量和默认保留期。首次显式保存策略前,新 Artifact 继续采用已有导出/Agent 分类保留配置;保存后统一使用 Workspace 保留期。调整上限不会删除既有资源;当前使用量超过新上限时拒绝新增操作,直到使用量回落。
  • /api/v1/system/diagnostics 只允许系统管理员手动生成 ZIP;包内配置、日志摘要和运行状态会移除 Token、Secret、完整 Recipient 与 Provider 敏感响应。诊断包仍应按敏感运维数据保存和传输。
  • /api/v1/system/version-check 只在用户点击或显式调用时访问 GitHub Releases,筛选稳定的 next-v* 版本。它没有后台调度、遥测或自动下载/安装行为;完全离线环境无需配置例外。
  • 账号认证熔断可在 Account 对应管理页检查或复位。先确认渠道凭据已经修正;直接复位但不修正根因会再次熔断。
  • Agent Presigned Multipart 会话记录在 V17。数据库清理会 Abort 失败/过期会话;S3-compatible Bucket 还必须配置 24 小时终止未完成 Multipart 的 Lifecycle 兜底。不要只删除数据库行而保留对象存储上传会话。

9. 升级、恢复与验收

Standalone 升级顺序:一致性备份 → 校验发行 SHA-256 → 展开新版本目录 → 原子切换 current → 重启 → Installation Health(Readiness、Flyway 当前版本、内置 Provider 本地 Dry Run)→ 成功保留备份。任一步失败都会切回旧 current,用刚生成的备份恢复配置和数据,再验证旧版本;命令以失败状态退出并保留恢复目录供人工审计。Server 滚动升级先执行单实例 Migration Job,再逐个替换 Service,最后滚动 Drain/Restart Agent。

Backup Archive 包含 BACKUP-MANIFEST、逐文件 SHA256SUMS 和配置/数据 Payload。Restore 在展开前拒绝路径穿越,并要求实际 Payload 文件集合与摘要清单完全一致;替换前保存 pre-restore-*,恢复后执行相同 Installation Health,失败时恢复原目录。不要分别恢复数据库和 Master Key。

最低验收项:

  1. Readiness 和 Prometheus 可访问。
  2. WebUI 能保存 Account/Message/Audience/Job 并启动 Dry Run。
  3. SSE 能重连并从 Last-Event-ID 回放。
  4. Agent Enrollment、Hello/Welcome、Lease、Event Ack、Artifact Commit 和 Run Completion 成功。
  5. 停掉一个 Service 后 API、Schedule、SSE 和 Agent 重连仍可恢复。
  6. 备份恢复后 SQLite/PostgreSQL、Artifact、Agent Journal/Outbox 与 Secret 主密钥一致。
  7. Workspace 配额拒绝与审计一致,认证熔断可观察/复位,诊断包抽查不存在 Token、Secret、完整 Recipient 或未脱敏 Provider 响应。
  8. S3 Presigned Multipart 可完成和中止;模拟丢失 PostgreSQL 通知后,轮询仍可推进 Run、Agent Outbox 与 SSE。

Next Docs Detailed Design (`next/docs/detailed-design.md`)

WePush Next 详细设计

1. 文档目的

本文档在架构与概要设计基础上,进一步定义 WePush Next 各组件的模块、接口、状态、数据、协议、异常、并发和部署细节,使开发人员可以据此拆分任务并开始实现。

文中的 Java 接口和 JSON 结构用于表达设计契约,正式实现时可以调整语法和类型名称,但不得未经评审改变组件职责、依赖方向和对外协议语义。

2. 已确定的设计基线

项目 首期设计基线
Java Java 25
构建 next/pom.xml 独立 Maven 聚合工程
Service Java 25、Spring Boot 4.1.x,初始版本 4.1.1
API HTTPS REST;Workspace 资源前缀 /api/v1/workspaces/{workspaceId}
实时事件 Server-Sent Events
API 契约 Contract First OpenAPI 3.1
Provider 配置 JSON Schema 2020-12 + UI Schema
WebUI TypeScript 6.0.x、Vite 8.1.x、React 19.2.x、Tailwind CSS、按需 shadcn/ui
Desktop Electron 43.x,复用 WebUI Renderer
Agent 协议 HTTP/2 + Protobuf + gRPC 双向流;Enrollment 使用 HTTPS REST
Provider 插件 PF4J 3.15.x、签名包、ClassLoader 隔离、滚动重启更新
Standalone 数据库 SQLite
Server 数据库 PostgreSQL 18.x;至少两个无状态 Service 实例构成正式 HA
Secret LocalEnvelopeSecretStore,AES-256-GCM 信封加密
大对象 Standalone 本地文件;Server 使用 S3-compatible Artifact Store
租户边界 Workspace 逻辑多租户进入正式 Server 范围
投递语义 At Least Once;支持时使用 Provider 幂等键
单机执行 Service 内嵌 Agent
分布式执行 远程 Agent 主动连接 Service

上述基线分别由 ADR-0002 至 ADR-0008 固化。WePush Next 只面向用户自建和自运维场景;公共 SaaS、计费订阅、外部 Vault/云 KMS/Secret Manager、恶意公共租户物理隔离和跨区域控制面属于长期非目标。

3. 命名与代码约定

3.1 Maven 坐标

建议使用以下坐标:

text
groupId:    com.fangxuele.wepush.next
artifactId: wepush-next-<module>

Next 不复用 Classic 的包名,防止类路径混淆和误依赖。

3.2 Java 包名

text
com.fangxuele.wepush.next.core.api
com.fangxuele.wepush.next.core.engine
com.fangxuele.wepush.next.provider.spi
com.fangxuele.wepush.next.provider.http
com.fangxuele.wepush.next.agent.protocol
com.fangxuele.wepush.next.agent.runtime
com.fangxuele.wepush.next.service.api
com.fangxuele.wepush.next.service.domain
com.fangxuele.wepush.next.service.application
com.fangxuele.wepush.next.service.infrastructure
com.fangxuele.wepush.next.sdk
com.fangxuele.wepush.next.embedded

3.3 领域术语

术语 含义
Provider 一个具体消息渠道能力实现
Account Provider 账号配置及 Secret 引用
Message 可版本化的消息模板
Audience 受众定义
Audience Snapshot 不可变、可执行的受众快照
Job Account、Message、Audience 和执行策略的组合
Schedule Job 的定时触发规则
Run Job 的一次执行实例
Run Snapshot Run 启动时冻结的全部执行配置
Recipient 一条目标数据及模板变量
Item Run 中一条 Recipient 的执行单元
Artifact 输入、日志、结果等大体积制品
Lease Agent 在限定时间内对 Run 的执行权

4. 代码模块和职责

4.1 Core 模块

text
core/core-api
    ID、值对象、RunExecutionSpec、RunEvent、RunSummary、执行策略

core/provider-spi
    ProviderFactory、ProviderSession、配置 Schema、错误分类

core/engine
    DefaultExecutionEngine、调度循环、并发、限流、重试、事件聚合

4.2 Agent 模块

text
agent/agent-protocol
    Agent 与 Service 的版本化 DTO,不直接复用 Service 数据库实体

agent/agent-runtime
    Agent 状态机、Lease 管理、Core 调用、事件缓冲、命令执行

agent/agent-app
    远程 Agent 启动入口、Provider 组合、配置和操作系统集成

4.3 Service 模块

text
service/service-api
    OpenAPI 文件、生成的 Server 接口、公开 DTO、错误码

service/service-domain
    领域对象、状态机、Repository Port、领域规则

service/service-application
    用例、权限、事务编排、DTO 映射

service/service-infrastructure
    JDBC、Artifact、Secret、Agent 协议、系统时间等适配器

service/service-app
    Spring Boot、Controller、Security、配置和内嵌 Agent 组合

4.4 组合原则

  • agent-runtime 只依赖 Provider SPI,不编译依赖某个具体 Provider。
  • agent-app 和 service-app 是具体 Provider 的组合入口。
  • embedded-java 只提供 Engine 装配能力,业务应用自行添加 Provider 依赖。
  • Service 的公开 DTO、Agent 协议 DTO、Core 对象和数据库记录是四类不同模型,通过 Mapper 显式转换。
  • 禁止为了减少转换而让数据库记录穿透到 Controller、Agent 或 Core。

5. 标识、时间和版本

5.1 标识

  • 对外资源 ID 使用不透明字符串,首期建议 UUIDv7。
  • 数据库主键与 API ID 使用同一逻辑 ID,避免暴露自增序号。
  • Recipient Item ID 由 runId + audienceSnapshotId + sequence 稳定生成。
  • Agent、Lease、Event 和 Artifact 都使用独立 ID。

5.2 时间

  • Java 内部使用 Instant 表示绝对时间。
  • API 使用 UTC ISO-8601,例如 2026-08-22T08:30:00Z。
  • Schedule 单独保存 IANA 时区,例如 Asia/Shanghai。
  • 数据库统一保存 UTC;仅在 API 和 UI 边界转换时区。
  • 所有超时使用 Duration,禁止使用无单位的裸整数。

5.3 版本

  • Provider 具有实现版本和 SPI 主版本。
  • Schema 具有独立 schemaVersion。
  • Agent 协议通过 URL 和消息字段双重版本化。
  • Run Snapshot 保存 Provider、Schema、模板和 Audience Snapshot 版本。

6. Core API 数据模型

6.1 配置文档

Core 不依赖 Jackson JsonNode。跨 Core 边界的 Provider 配置使用不可变文档:

java
public record ConfigDocument(
        String schemaId,
        String schemaVersion,
        String mediaType,
        byte[] canonicalContent
) {
    public ConfigDocument {
        canonicalContent = canonicalContent.clone();
    }

    @Override
    public byte[] canonicalContent() {
        return canonicalContent.clone();
    }
}

默认 mediaType 为 application/json。Provider 在自己的模块中解析为强类型配置。

6.2 Secret 引用

java
public record SecretRef(String namespace, String name, String version) {}

public interface SecretResolver {
    SecretValue resolve(SecretRef ref);
}

public interface SecretValue extends AutoCloseable {
    char[] copyChars();
    byte[] copyBytes();
    @Override void close();
}

SecretValue 的 toString() 必须返回固定掩码,关闭时尽力清理内部缓冲区。

6.3 Recipient

java
public record RecipientRecord(
        String itemId,
        long sequence,
        Map<String, RecipientValue> fields
) {}

public sealed interface RecipientValue
        permits TextValue, NumberValue, BooleanValue, NullValue, BinaryRefValue {}

字段名区分大小写;Provider Schema 必须声明必填字段和字段用途。

6.4 RunExecutionSpec

java
public record RunExecutionSpec(
        String runId,
        ProviderRef provider,
        ConfigDocument accountConfig,
        ConfigDocument messageConfig,
        ExecutionPolicies policies,
        Map<String, String> attributes,
        boolean dryRun,
        Instant createdAt
) {}

Recipient Source、Secret、Artifact 和 Event Sink 是运行端口,不直接序列化进 RunExecutionSpec。

6.5 执行策略

java
public record ExecutionPolicies(
        ConcurrencyPolicy concurrency,
        RateLimitPolicy rateLimit,
        RetryPolicy retry,
        TimeoutPolicy timeout,
        ResultPolicy result
) {}
  • 并发范围必须有最小值和最大值。
  • 动态调整只能落在允许范围内。
  • Retry 的总耗时不得超过 Run 或 Item Timeout。
  • Result Policy 决定是否保存响应摘要或完整响应 Artifact。

6.6 Run 状态

java
public enum RunState {
    PENDING,
    LEASED,
    RUNNING,
    PAUSED,
    CANCELLING,
    CANCELLED,
    SUCCEEDED,
    PARTIAL,
    FAILED,
    LOST,
    RECOVERING
}
状态 含义
PENDING 已创建,等待可用 Agent
LEASED Service 已授予 Lease,等待 Agent 确认或启动
RUNNING Agent 和 Core 正在执行
PAUSED 暂停派发新 Item,保留执行上下文
CANCELLING 已接受取消,正在结束在途请求和 Flush 结果
CANCELLED 取消处理完成
SUCCEEDED 全部 Item 成功或按策略跳过
PARTIAL 存在明确失败、未知或未发送 Item,但 Run 正常完成收尾
FAILED Run 级错误导致无法继续或无法正确完成收尾
LOST Agent 失联,当前执行结果无法立即确认
RECOVERING Service 正在判断是否恢复、重新领取或终止

Service 管理完整状态机;Core 主要处理 RUNNING、PAUSED、CANCELLING 和执行终态。Core 不自行把 LOST 转为 RECOVERING。

7. Core Engine 接口

7.1 外部接口

java
public interface ExecutionEngine {
    RunHandle start(RunExecutionSpec spec, ExecutionPorts ports);
}

public record ExecutionPorts(
        RecipientSource recipientSource,
        SecretResolver secretResolver,
        ResultSink resultSink,
        ArtifactSink artifactSink,
        RunEventSink eventSink,
        ExecutionClock clock
) {}

public interface RunHandle {
    String runId();
    RunState state();
    CommandResult submit(RunCommand command);
    CompletionStage<RunSummary> completion();
}

start() 在完成参数校验和资源预留后返回。完整执行结果通过 completion() 获取。

7.2 命令模型

java
public sealed interface RunCommand
        permits PauseRun, ResumeRun, CancelRun, ChangeConcurrency {}

public record PauseRun(String commandId) implements RunCommand {}
public record ResumeRun(String commandId) implements RunCommand {}
public record CancelRun(String commandId, String reason) implements RunCommand {}
public record ChangeConcurrency(String commandId, int target) implements RunCommand {}

命令按 commandId 幂等。同一个命令重复提交返回原处理结果。

7.3 Engine 内部组件

text
DefaultExecutionEngine
├── RunCoordinator
├── RecipientDispatcher
├── ConcurrencyGate
├── RateLimiter
├── RetryExecutor
├── ProviderSessionManager
├── ProgressAggregator
├── ResultBatcher
├── EventDispatcher
└── ResourceScope

每个 Run 创建独立 RunCoordinator,Engine 自身不保存跨 Run 的可变业务状态。

8. Core 执行生命周期

8.1 主流程

mermaid
sequenceDiagram
    participant Caller as Agent/Embedded SDK
    participant Engine as ExecutionEngine
    participant Source as RecipientSource
    participant Provider as ProviderSession
    participant Sink as Result/Event Sink

    Caller->>Engine: start(spec, ports)
    Engine->>Engine: 校验策略和配置
    Engine->>Provider: open(context)
    Engine->>Sink: RUN_STARTED
    loop 流式读取
        Engine->>Source: nextBatch()
        Source-->>Engine: Recipient batch
        Engine->>Provider: send(item)
        Provider-->>Engine: ProviderResult
        Engine->>Sink: result batch + progress
    end
    Engine->>Provider: close()
    Engine->>Sink: flush + RUN_COMPLETED
    Engine-->>Caller: RunSummary

8.2 启动校验顺序

  1. Run ID、Provider 和端口非空。
  2. 执行策略内部一致。
  3. Provider 存在且 SPI 兼容。
  4. Account 和 Message Schema 版本受支持。
  5. Provider 执行配置校验通过。
  6. Recipient Source 可打开。
  7. Result 和 Artifact Sink 可写。
  8. Provider Session 创建成功。

校验失败不进入 RUNNING,返回稳定错误分类。

8.3 资源关闭

资源按与创建相反的顺序关闭。无论正常结束、取消还是异常,都必须尝试:

  1. 停止读取新 Recipient。
  2. 等待或终止在途发送。
  3. Flush Result Sink。
  4. Flush Event Sink。
  5. 关闭 Provider Session。
  6. 关闭 Recipient Source。
  7. 完成 RunSummary。

单个资源关闭失败不能阻止其他资源关闭,但会进入 Run 的 suppressedErrors。

9. 并发模型

9.1 调度结构

  • 每个 Run 有一个协调线程或协调任务。
  • Recipient 按批次读取,不预加载完整 Audience。
  • 每个 Item 使用虚拟线程执行 I/O 发送。
  • ConcurrencyGate 使用许可数限制真实在途请求。
  • RateLimiter 在获取并发许可之后、调用 Provider 之前生效。
  • Provider 可以声明比用户配置更小的并发上限。

最终有效并发:

text
min(
  用户目标并发,
  Job 最大并发,
  Provider 最大并发,
  Agent 可用容量,
  Service 下发容量
)

9.2 背压

  • Recipient Source 只有在执行队列低于高水位时才读取下一批。
  • Result Sink 阻塞或积压时,Engine 减缓新的 Item 派发。
  • Event Sink 使用有界缓冲区;进度事件可以合并,终态和错误事件不得丢弃。
  • 队列容量全部可配置且有安全默认值。

9.3 动态并发

ChangeConcurrency 修改目标许可数:

  • 增大时立即释放新许可。
  • 减小时不强制中断在途请求,待请求结束后自然收缩。
  • 目标值越界时拒绝命令并返回允许范围。
  • 每次变化产生 CONCURRENCY_CHANGED 事件。

9.4 Provider 线程安全

Provider Descriptor 和只读 Schema 可以跨 Run 共享。Provider Session 默认是 Run 级实例,必须明确声明:

  • THREAD_SAFE:同一 Session 可被并发调用。
  • SERIALIZED:Engine 对同一 Session 串行调用。
  • SESSION_PER_WORKER:Provider 为工作单元创建子 Session。

未声明时按 SERIALIZED 处理。

10. 暂停、取消和停止

10.1 暂停

  • 暂停停止派发新 Item。
  • 已经调用 Provider 的请求继续完成。
  • Recipient Source 保持打开,但不得无限占用不可续期资源。
  • 超过 maxPauseDuration 后由策略决定自动恢复或取消。

10.2 取消

  • 状态先进入 CANCELLING。
  • 不再读取或派发新 Item。
  • 可取消的在途请求收到 Cancellation Token。
  • 不支持取消的 Provider 请求等待 Item Timeout。
  • 尚未派发的 Recipient 记录为 UNSENT,不是 FAILED。
  • 所有结果 Flush 后进入 CANCELLED。

10.3 Agent 关闭

Agent 收到操作系统停止信号后:

  1. 停止领取新 Lease。
  2. 向 Service 上报 DRAINING。
  3. 在 Grace Period 内等待 Run 完成。
  4. 超时后对 Run 发起取消并 Flush 本地事件。
  5. 上报最终心跳后退出。

11. 重试、超时和错误分类

11.1 Provider 错误分类

java
public enum ErrorCategory {
    AUTHENTICATION,
    AUTHORIZATION,
    INVALID_REQUEST,
    RECIPIENT_INVALID,
    RATE_LIMITED,
    TEMPORARY_REMOTE,
    PERMANENT_REMOTE,
    NETWORK,
    TIMEOUT,
    CANCELLED,
    INTERNAL,
    UNKNOWN
}

ProviderResult 必须同时给出:

  • 是否成功。
  • 稳定 Provider 错误码。
  • ErrorCategory。
  • 是否建议重试。
  • 可选 retryAfter。
  • 已脱敏的诊断摘要。
  • 外部请求 ID。

11.2 默认重试规则

  • RATE_LIMITED:按 Provider retryAfter,否则指数退避。
  • TEMPORARY_REMOTE、NETWORK:允许重试。
  • TIMEOUT:仅当 Provider 调用语义允许时重试,否则转 UNKNOWN。
  • AUTHENTICATION、AUTHORIZATION:不逐条重试,可触发 Run 级快速失败。
  • INVALID_REQUEST、RECIPIENT_INVALID、PERMANENT_REMOTE:不重试。
  • INTERNAL、UNKNOWN:由显式策略决定,默认不自动重试。

11.3 退避

text
delay = min(maxDelay, initialDelay * multiplier^(attempt - 1)) + jitter

重试必须同时受最大次数、Item Deadline 和 Run Deadline 限制。

11.4 熔断和快速失败

同一 Account 在短时间内连续发生认证失败时,Engine 停止继续发送并将 Run 标记为 FAILED 或 PARTIAL。熔断统计属于 Run 级,跨 Run 熔断由 Agent 或 Service 后续扩展。

12. 结果和进度设计

12.1 Item 状态

text
PENDING     尚未派发
IN_FLIGHT   已调用 Provider,结果未确定
SUCCEEDED   Provider 明确成功
FAILED      Provider 明确失败且不再重试
UNKNOWN     可能已发送,但无法确认结果
UNSENT      Run 结束前未调用 Provider
SKIPPED     因校验或策略主动跳过

12.2 RunSummary

java
public record RunSummary(
        String runId,
        RunState finalState,
        long total,
        long succeeded,
        long failed,
        long unknown,
        long unsent,
        long skipped,
        long retried,
        Instant startedAt,
        Instant endedAt,
        List<ArtifactRef> artifacts,
        List<ExecutionError> suppressedErrors
) {}

计数必须满足:

text
total = succeeded + failed + unknown + unsent + skipped

retried 是尝试次数统计,不参与总数恒等式。

12.3 Result Sink

java
public interface ResultSink extends AutoCloseable {
    void append(List<ItemResult> batch);
    void flush();
}
  • 默认批量大小建议 100~1000,可配置。
  • 同一 itemId + attempt 重复写入必须幂等。
  • CSV 适合用户下载,内部恢复建议使用 JSON Lines 或结构化二进制格式。
  • 响应体默认只保存摘要,完整内容由 Result Policy 显式开启。

12.4 ProgressAggregator

  • 内部计数实时更新。
  • 对 Agent 的进度事件按时间间隔或数量阈值合并。
  • 默认每 500ms 至 1s 产生一次进度事件。
  • 终态前强制发送最后一条完整计数。

13. Provider SPI

13.1 ProviderFactory

java
public interface ProviderFactory {
    ProviderDescriptor descriptor();
    ValidationResult validateAccount(ConfigDocument account);
    ValidationResult validateMessage(ConfigDocument message);
    ConnectionTestResult testConnection(
            ConfigDocument account,
            SecretResolver secrets,
            Duration timeout);
    ProviderSession open(ProviderOpenContext context);
}

13.2 ProviderSession

java
public interface ProviderSession extends AutoCloseable {
    ProviderResult send(ProviderSendRequest request, CancellationToken token);
    default PreviewResult preview(ProviderPreviewRequest request) {
        throw new UnsupportedOperationException("preview");
    }
    @Override void close();
}

13.3 ProviderSendRequest

java
public record ProviderSendRequest(
        String runId,
        String itemId,
        int attempt,
        RecipientRecord recipient,
        ConfigDocument messageConfig,
        String idempotencyKey,
        Instant deadline
) {}

Provider 不得通过 runId、accountId 或 messageId 查询 Service 数据库。

13.4 ProviderDescriptor

Descriptor 至少包含:

  • providerId
  • 展示名称、类别、说明和图标引用
  • 实现版本、SPI 版本
  • Account、Message、Recipient 和 UI Schema
  • 能力:Preview、Dry Run、Idempotency、Response Body 等
  • 并发上限和默认超时建议
  • Thread Safety Mode
  • 支持的平台和架构

14. Provider Schema 规范

14.1 文件布局

text
META-INF/wepush/provider.json
META-INF/wepush/schemas/account.schema.json
META-INF/wepush/schemas/message.schema.json
META-INF/wepush/schemas/recipient.schema.json
META-INF/wepush/schemas/ui.schema.json

一个内置模块包含多个 Provider 时,可以使用 META-INF/wepush/providers/<slug>/ 分目录保存各自 Descriptor 和三类 Schema;Factory 仍通过标准 ServiceLoader 文件发现。provider-standard 使用该布局。

14.2 扩展字段

text
x-wepush-secret             Secret 字段,不可回读
x-wepush-widget             自定义 UI 控件
x-wepush-variable           允许使用 Recipient 变量
x-wepush-multiline          多行编辑器
x-wepush-code-language      JSON、HTML、SQL 等编辑模式
x-wepush-display-order      展示顺序
x-wepush-advanced           放入高级设置
x-wepush-capability         依赖的 Provider 能力

14.3 校验层次

  1. UI 本地 Schema 校验,用于即时提示。
  2. Service Schema 校验,作为保存前权威校验。
  3. Provider 语义校验,例如签名算法、字段组合和渠道限制。
  4. 可选连接测试,不作为普通保存的隐式步骤。

UI 校验结果不能替代 Service 校验。

15. Provider 发现、隔离和更新

15.1 发现

  • 外部 Provider 插件使用 PF4J 3.15.x;Provider SPI 作为 Extension Point,Core Engine 不依赖 PF4J。
  • Agent App 的 ProviderCatalogAdapter 把 PF4J Extension 转换为 Core 的 ProviderFactory。
  • 内置 Provider 作为系统 Extension 进入相同 Catalog,避免执行路径出现两套语义。
  • Agent 启动时扫描 plugins//<version>/,校验 plugin.json、SPI 兼容范围、文件 SHA-256 和 Ed25519 签名,再装载被激活的版本。
  • 重复 Provider ID、缺失 Schema、签名失败或 SPI 不兼容时,插件进入 REJECTED,原因写入健康状态和审计日志。

插件包固定包含实现 JAR、Descriptor、JSON Schema、UI Schema、图标、许可证和签名清单。正式发行默认只接受受信发布者签名的包;未签名包仅能在显式 Developer Mode 中装载。

15.2 隔离

  • 每个插件使用独立 PF4J ClassLoader;JDK、Provider SPI、Core API 和统一日志 API 由 Parent ClassLoader 提供。
  • 插件私有的厂商 SDK 和三方依赖由插件 ClassLoader 加载,插件不得打包 SPI/Core API 的重复副本。
  • Provider 不能直接访问 Service Repository、Spring Bean 或其他 Workspace 数据。
  • ClassLoader 只解决依赖冲突,不构成恶意代码沙箱。非受信插件必须通过未来的独立进程 Provider Runner 执行。

15.3 更新和回滚

更新按 STAGED → VERIFIED → DRAINING → RESTARTING → ACTIVE 推进:先下载到版本目录并验证,再停止向目标 Agent 分配新 Run,等待或按策略处理现有 Run,重启 Agent 后验证能力清单,最后激活新版本。多 Agent 环境逐台滚动;旧版本保留到回滚窗口结束。

不在 JVM 内卸载、替换正在使用的 Provider ClassLoader。Run Snapshot 固定 Provider ID、版本和 Schema 版本;只有存在兼容 Agent 时才调度新 Run。详见 ADR-0005。

16. 首个 HTTP Provider 详细设计

HTTP Provider 用于验证完整纵向链路。

16.1 Account 配置

json
{
  "baseUrl": "https://api.example.com",
  "defaultHeaders": {
    "User-Agent": "WePush-Next"
  },
  "auth": {
    "type": "BEARER",
    "token": {"$secret": "http-account-token"}
  },
  "connectTimeout": "PT5S"
}

16.2 Message 配置

json
{
  "method": "POST",
  "path": "/notify",
  "headers": {
    "Content-Type": "application/json"
  },
  "query": {},
  "bodyTemplate": "{\"mobile\":\"{{mobile}}\",\"content\":\"{{content}}\"}",
  "successCondition": {
    "status": [200, 201],
    "jsonPath": "$.success",
    "equals": true
  },
  "saveResponseBody": false
}

16.3 安全限制

  • 默认只允许 HTTP/HTTPS,生产建议仅 HTTPS。
  • URL 由 Base URL 和受控 Path 组合,默认不允许 Recipient 控制 Host。
  • DNS 解析后执行私网、环回、链路本地和云元数据地址策略检查。
  • 禁止自动将 Secret 写入日志或响应 Artifact。
  • 响应体设最大字节数,超出部分截断并标记。
  • 重定向默认关闭;开启时每次跳转重新执行地址安全检查。

16.4 错误映射

HTTP 情况 ErrorCategory 默认重试
2xx 且成功条件成立 成功 否
400、422 INVALID_REQUEST 否
401 AUTHENTICATION 否,Run 快速失败
403 AUTHORIZATION 否
404 PERMANENT_REMOTE 否
408、504 TIMEOUT 依幂等策略
429 RATE_LIMITED 是
500、502、503 TEMPORARY_REMOTE 是
DNS/连接失败 NETWORK 是

17. Agent Runtime 组件

text
AgentRuntime
├── AgentIdentityStore
├── GrpcAgentClient
├── RegistrationManager
├── HeartbeatLoop
├── AgentControlStream
├── LeaseSupervisor
├── RunExecutor
├── CommandInbox
├── EventOutbox
├── ArtifactTransfer
├── LocalJournal
└── ProviderCatalog

17.1 Agent 状态

text
STARTING
UNREGISTERED
CONNECTING
ONLINE
DRAINING
DEGRADED
OFFLINE
STOPPED
  • Provider 装载失败但核心仍可运行时进入 DEGRADED。
  • 无有效身份时进入 UNREGISTERED,只允许 Enrollment。
  • Service 不可达时进入 OFFLINE,不得领取新任务。

17.2 本地 Agent 标识

本地保存:

  • agentId
  • Agent credential 或证书引用
  • Service endpoint
  • 最后确认的 Event Sequence
  • 未完成 Lease Journal
  • Provider 清单摘要

文件权限必须限制为 Agent 运行用户可读。

18. Agent 注册和身份

18.1 Enrollment

管理员在 Service 创建一次性 Enrollment Token。Agent 首次启动调用:

http
POST /internal/agent/v1/enroll
Authorization: Enrollment <one-time-token>

请求:

json
{
  "displayName": "worker-shanghai-01",
  "agentVersion": "0.1.0",
  "protocolVersions": ["1"],
  "platform": {
    "os": "linux",
    "arch": "x86_64",
    "java": "21"
  },
  "providers": [
    {"id": "wepush.http", "version": "1.0.0", "spi": "1"}
  ]
}

响应包含 agentId 和长期 Agent Credential。Enrollment Token 使用一次后立即失效。

18.2 后续认证

  • 首期可使用独立 Agent Token,通过 TLS 传输。
  • 支持证书后优先使用 mTLS。
  • Agent Token 与用户 API Token 使用不同签发域、权限和过期策略。
  • Agent Credential 支持轮换和吊销。

19. Agent gRPC 控制流和租约协议

19.1 服务契约

正式契约位于 agent/agent-protocol/src/main/proto/。核心形态如下,具体字段以版本化 Proto 为准:

proto
service AgentControlService {
  rpc Connect(stream AgentToService) returns (stream ServiceToAgent);
}

message AgentToService {
  string agent_id = 1;
  uint64 sequence = 2;
  oneof payload {
    Hello hello = 10;
    Heartbeat heartbeat = 11;
    LeaseAck lease_ack = 12;
    EventBatch event_batch = 13;
    CommandAck command_ack = 14;
    RunCompleted run_completed = 15;
    Draining draining = 16;
  }
}

message ServiceToAgent {
  uint64 sequence = 1;
  oneof payload {
    Welcome welcome = 10;
    LeaseOffer lease_offer = 11;
    RunCommand command = 12;
    EventAck event_ack = 13;
    DrainRequest drain = 14;
  }
}

同一方向的 sequence 单调递增。gRPC 保证单条活跃流内有序,但 Agent 和 Service 仍持久化最后确认位置,以处理断线、重连和重复帧。应用消息设置大小上限;Audience、结果、日志和插件包不进入控制流。

19.2 建连和心跳

Agent 使用 Enrollment 获得的身份建立 Connect,第一帧必须是 Hello,包含 Agent 版本、协议范围、平台、Provider 清单摘要、容量和恢复游标。Service 返回 Welcome,确认选定协议版本、Server 时间、心跳间隔、消息上限和双方已确认 Sequence。

Heartbeat 包含 Agent 状态、最大/活跃 Run、可用并发和当前 Lease 摘要。心跳间隔默认 10 秒;超过三个周期未收到有效帧时 Service 将连接标记为失联并启动 Lease 恢复,但具体 Lease 到期以数据库时间为准。

19.3 Lease Offer 和确认

Service 根据 Agent 能力和容量主动发送 LeaseOffer,其中包含:

  • leaseId、runId、epoch、fencingToken、expiresAt。
  • Execution Snapshot 元数据和短期下载 URL。
  • Audience Artifact 元数据和短期下载 URL。
  • 仅限本次 Run 的 Secret Envelope。

Agent 下载并校验 Snapshot 后发送 LeaseAck。未在 Ack Deadline 内确认的 Offer 失效;Service 只有在校验 Agent、Lease、Epoch 和 Fencing Token 后才允许 Run 进入 RUNNING。

当前实现可通过 wepush.execution.mode=remote 启用该链路:agent_lease 保存会话归属、Epoch、Fencing Token、状态和最后连续 Event Sequence;Execution Spec 与 Audience 由受 Agent Credential/Bootstrap Token 保护的 /internal/agent/v1/leases/{leaseId}/... JSON API 提供,并在 Offer 中携带 SHA-256。Secret Envelope 已采用一次性 X25519 + HKDF-SHA-256 + AES-256-GCM 实现,Agent 会话公钥由 Hello 发布并持久化,密文同时绑定 Agent/Run/Lease/Epoch/Fence/过期时间,明文只在 Agent 运行内存短期存在。Agent Artifact 使用 Lease 绑定的短期上传计划,可经 Service 流式上传或 S3 Presigned Put,Commit 前执行 Head、大小和 SHA-256 完整性校验;Service 直接写入达到 100 MiB 时使用 Multipart。

19.4 Fencing 和重连

每次重新分配 Lease 都递增 epoch 并生成新的不透明 Fencing Token。所有 LeaseAck、Heartbeat Lease、EventBatch、CommandAck 和 RunCompleted 都携带 leaseId + epoch + fencingToken,旧 Epoch 写入一律拒绝。

Agent 断线后以指数退避和抖动重连负载均衡器,并在 Hello 中报告未完成 Lease、双方 Sequence 和 Outbox 范围。任意 Service 实例从 PostgreSQL 恢复状态并返回继续、停止或重新同步指令;连接到原实例不是恢复前提。

20. Agent 命令和事件上报

20.1 命令结构

RunCommand 包含 Agent 方向 Sequence、Command ID、Run ID、Lease Fencing 信息、命令类型、Payload 和创建时间。

  • Sequence 在单 Agent 范围内单调递增。
  • Agent 按顺序处理并持久化最后确认 Sequence。
  • 命令按 commandId 幂等。
  • 命令结果通过 CommandAck 和 Run Event 同时体现。

20.2 事件批次

Agent 使用 EventBatch 帧发送 firstSequence、lastSequence 和事件集合。Service 持久化后发送 EventAck,给出最后连续确认 Sequence。Agent 可以安全重传整个批次,Service 通过 runId + eventSequence 去重;发现缺口时要求从已确认位置重发。

Event Outbox 有内存和本地磁盘上限。达到高水位时优先聚合进度类事件并对 Core 施加背压,状态转换、错误和终态事件不得静默丢弃。

20.3 完成和 Artifact

RunCompleted 包含 RunSummary、Artifact ID/校验值/大小和最后 Event Sequence。大文件由 Agent 使用 Service 签发的短期 HTTPS URL 直接上传;Service 通过 Head 和 Checksum 验证所有必需 Artifact 均为 READY 后才提交 Run 终态。缺失内容时发送可重试拒绝,Agent 保留 Journal 和 Outbox。

21. Agent 本地恢复

Local Journal 不保存明文 Secret,至少记录:

  • Lease ID、Run ID、Epoch。
  • Execution Snapshot 校验值。
  • 已确认事件 Sequence。
  • 本地待上传 Artifact。
  • Agent 退出原因和时间。

Agent 重启后:

  1. 读取 Journal。
  2. 向 Service 查询 Lease 当前所有权。
  3. 仍有效且允许恢复时继续上报或恢复执行。
  4. Lease 已失效时停止旧执行,只允许上传诊断信息。
  5. 无法确认 Provider 调用结果的 Item 标记为 UNKNOWN。

首期可以不支持从中间 Recipient 位置继续执行,但必须正确终止和报告,不允许静默丢失 Run。

22. Service 领域和应用服务

22.1 主要应用服务

text
ProviderQueryService
AccountApplicationService
MessageApplicationService
AudienceApplicationService
JobApplicationService
ScheduleApplicationService
RunApplicationService
RunCommandService
AgentApplicationService
AgentLeaseService
ArtifactApplicationService
SecretApplicationService
AuditApplicationService

22.2 典型用例:创建 Run

事务内完成:

  1. 校验 Idempotency Key。
  2. 加载 Job 并检查权限。
  3. 加载 Account、Message Revision 和 Audience Snapshot。
  4. 校验 Provider 存在且配置版本兼容。
  5. 创建不可变 Run Snapshot。
  6. 创建 PENDING Run。
  7. 写入 RUN_CREATED 事件和审计记录。
  8. 保存 Idempotency 结果。

事务提交后发出可用性提示,由已连接的 Service 实例尝试生成 LeaseOffer。提示丢失不影响正确性,数据库扫描仍会发现待调度 Run。

22.3 典型用例:下发命令

  1. 校验用户和 Run 权限。
  2. 根据状态机判断命令是否合法。
  3. 生成 Command ID 和 Agent Sequence。
  4. 持久化命令。
  5. 返回 ACCEPTED,不等待 Agent 完成命令。
  6. Agent Ack 后更新命令状态并产生 Run Event。

23. Service 事务边界

23.1 原则

  • 一个应用服务方法对应一个明确事务边界。
  • 不在数据库事务内调用外部 Provider、上传大 Artifact 或等待 Agent。
  • Repository 方法不自行提交事务。
  • 事务提交后事件通过本地通知或 Outbox 触发后续处理。
  • 数据库连接和 Session 按事务获取,不使用全局共享 Session。

23.2 乐观锁

可修改聚合包含 version 字段:

sql
UPDATE job_definition
SET ..., version = version + 1
WHERE id = ? AND version = ?

受影响行数为 0 时返回并发修改错误。

23.3 Run 状态并发

Run 状态变化使用状态条件和版本双重保护:

sql
UPDATE run
SET state = ?, version = version + 1
WHERE id = ? AND state IN (...) AND version = ?

禁止先读后无条件写。

24. Service 公开 API

24.1 OpenAPI 文件拆分

text
service/service-api/src/main/openapi/
├── openapi.yaml
├── paths/
│   ├── accounts.yaml
│   ├── messages.yaml
│   ├── audiences.yaml
│   ├── jobs.yaml
│   ├── schedules.yaml
│   ├── runs.yaml
│   └── agents.yaml
└── schemas/
    ├── common.yaml
    ├── problem.yaml
    ├── account.yaml
    ├── run.yaml
    └── event.yaml

构建时先校验和打包 OpenAPI,再生成 Server Stub 与 Remote Java SDK。生成代码不得手工修改。

24.2 创建 Run

http
POST /api/v1/workspaces/{workspaceId}/jobs/{jobId}/runs
Idempotency-Key: 20260822-manual-001
Content-Type: application/json
json
{
  "dryRun": false,
  "policyOverrides": {
    "concurrency": {"target": 50}
  },
  "reason": "manual",
  "confirmationToken": "token-from-run-confirmation"
}

手工正式发送必须先调用 POST /jobs/{jobId}/run-confirmation。Service 返回 Provider、Account、Audience 数量、策略、并发、限速、预计规模和五分钟令牌;令牌绑定 Workspace、Job 及 Account/Message/Audience/Policy 版本,资源发生变化或令牌过期后必须重新确认。Scheduler 使用内部可信入口,不依赖交互令牌。

成功返回 202 Accepted:

json
{
  "id": "run-id",
  "state": "PENDING",
  "createdAt": "2026-08-22T08:30:00Z",
  "links": {
    "self": "/api/v1/workspaces/workspace-id/runs/run-id",
    "events": "/api/v1/workspaces/workspace-id/runs/run-id/events"
  }
}

24.3 运行详情

Run Detail 包含:

  • 状态和状态原因。
  • Job、Provider 和 Snapshot 版本。
  • Agent 与 Lease 摘要。
  • 总量及各结果计数。
  • 当前并发、速率和重试数。
  • 开始、结束和持续时间。
  • Artifact 链接。
  • 允许的下一步命令列表。

24.4 SSE

http
GET /api/v1/workspaces/{workspaceId}/runs/{runId}/events
Accept: text/event-stream
Last-Event-ID: 120
text
id: 121
event: progress
data: {"runId":"...","succeeded":300,"failed":2,"inFlight":50}
  • SSE 心跳注释间隔默认 15 秒。
  • 连接断开后客户端指数退避重连。
  • Event ID 只在单 Run 内有序。
  • 请求过旧且事件已被清理时返回明确的游标过期错误,客户端改用 Run Detail 恢复快照。

25. API 通用约定

25.1 分页

首期使用 Cursor Pagination:

json
{
  "items": [],
  "page": {
    "nextCursor": "opaque",
    "hasMore": true
  }
}

Cursor 不暴露 SQL 结构,使用 HMAC 保护,并绑定名称、状态和时间筛选。公开资源页 limit 为 1..100;服务端只读取 limit + 1 条判断 hasMore,篡改、跨资源或跨筛选复用 Cursor 会被拒绝。

25.1.1 资源版本、文件导入与关联重发

  • Account、Message、Audience、Job 和 Schedule 使用乐观版本更新;Message 内容变更创建新 Revision,Audience Recipient 变更创建新 Snapshot。
  • CSV/TXT 使用流式 Reader 分批写入 staging 表,数据库保留首个 itemId 并标记重复行;预览最多返回 20 条接受/错误记录,完整错误通过流式 CSV 下载。
  • Import Commit 使用 INSERT ... SELECT 创建 Snapshot 和 Recipient,不把完整文件载入 JVM;更新已有 Audience 时再次校验资源版本。
  • FAILED、UNKNOWN、UNSENT 重发先返回匹配 Item 数量和短期确认令牌,再创建带 sourceRunId 的新 Run;run_retry_item 限定执行集合,新 Run 复制来源 Run Snapshot,不读取当前可变资源。

25.2 错误响应

json
{
  "type": "https://wepush.example/errors/run-state-conflict",
  "title": "Run state conflict",
  "status": 409,
  "code": "RUN_STATE_CONFLICT",
  "detail": "Run cannot be paused from SUCCEEDED",
  "traceId": "...",
  "errors": []
}

稳定错误码至少包含:

text
VALIDATION_FAILED
AUTHENTICATION_REQUIRED
ACCESS_DENIED
RESOURCE_NOT_FOUND
VERSION_CONFLICT
IDEMPOTENCY_CONFLICT
PROVIDER_NOT_AVAILABLE
PROVIDER_CONFIG_INVALID
RUN_STATE_CONFLICT
AGENT_NOT_AVAILABLE
LEASE_EXPIRED
ARTIFACT_NOT_READY
RATE_LIMITED
INTERNAL_ERROR

25.3 HTTP 状态

  • 200 查询或同步操作成功。
  • 201 资源创建完成。
  • 202 异步命令已接受。
  • 204 删除或无响应体操作成功。
  • 400 请求格式或字段错误。
  • 401 未认证。
  • 403 无权限。
  • 404 资源不存在或不可见。
  • 409 状态、版本或幂等冲突。
  • 422 Provider 语义校验失败。
  • 429 调用频率超限。
  • 503 暂无可用能力或服务未就绪。

26. 数据库逻辑模型

所有业务表包含:

text
id               不透明 ID
workspace_id     工作空间边界
created_at       UTC 创建时间
updated_at       UTC 更新时间
version          乐观锁版本

即使首期只有一个 Workspace,也保留 workspace_id。

26.1 身份和权限

表 关键字段
workspace name、status、settings_json
app_user username、display_name、status、password_hash 或 external_subject
role_binding user_id、workspace_id、role
api_token owner_id、token_hash、scopes、expires_at、last_used_at
agent_credential agent_id、credential_hash/cert_fingerprint、expires_at、revoked_at

Token 只保存不可逆 Hash,明文只在创建时返回一次。

26.2 Provider 和账号

表 关键字段
provider_catalog provider_id、implementation_version、spi_version、descriptor_json、status
account provider_id、name、config_json、secret_set_id、status、last_test_at
secret_record secret_set_id、secret_name、ciphertext、key_version、updated_at

account.config_json 不包含明文 Secret,只保存普通配置和 Secret 引用。

26.3 消息和受众

表 关键字段
message_template provider_id、name、current_revision、status
message_revision message_id、revision、schema_version、content_json、content_hash
audience name、source_type、source_config_json、status
audience_snapshot audience_id、revision、artifact_id、record_count、content_hash、state

Message Revision 和 Audience Snapshot 创建后不可修改。

26.4 Job、Schedule 和 Run

表 关键字段
job_definition name、account_id、message_id、audience_id、policies_json、enabled
schedule job_id、cron、timezone、misfire_policy、enabled、next_fire_at
run job_id、snapshot_id、state、state_reason、agent_id、计数、时间、version
run_snapshot run_id、provider_ref、account_config、message_content、policies、hash
run_command run_id、agent_id、sequence、type、payload_json、state、ack_at
run_event run_id、sequence、type、occurred_at、payload_json、severity

26.5 Agent 和 Lease

表 关键字段
agent name、status、version、protocol_version、platform_json、last_seen_at
agent_provider agent_id、provider_id、version、spi_version、status
agent_lease run_id、agent_id、epoch、token_hash、state、expires_at、acked_at

约束:同一 Run 同时最多一个 ACTIVE Lease。

26.6 Artifact、幂等和审计

表 关键字段
artifact type、backend、location、size、sha256、content_type、state、expires_at
idempotency_record scope、key_hash、request_hash、response_status、response_body、expires_at
audit_event actor_type、actor_id、action、resource_type、resource_id、result、details_json

27. 索引和保留策略

建议索引:

text
run(workspace_id, state, created_at)
run(job_id, created_at)
run_event(run_id, sequence) UNIQUE
schedule(enabled, next_fire_at)
agent(status, last_seen_at)
agent_lease(run_id, state) UNIQUE WHERE state = ACTIVE
agent_lease(agent_id, expires_at)
message_revision(message_id, revision) UNIQUE
audience_snapshot(audience_id, revision) UNIQUE
idempotency_record(scope, key_hash) UNIQUE

SQLite 不支持的条件索引能力通过事务校验和等价唯一字段实现。

默认保留基线:

  • Run 摘要作为业务记录保存,具体归档期由 Workspace 策略配置。
  • 高频 Run Event 和运行日志保存 30 天后压缩或删除。
  • Audit Event 保存时间长于普通日志。
  • Audience Snapshot 在被引用期间保留,解除引用后 30 天;运行结果 90 天;完整 Provider 响应 7 天;临时导出 24 小时。
  • 删除前验证没有有效引用;PINNED 或 LEGAL_HOLD 跳过普通 TTL。
  • Secret 不进入备份外的普通导出。

28. SQLite 与 PostgreSQL 18 适配

28.1 统一语义

  • Repository 接口和领域行为保持一致。
  • JSON 在 PostgreSQL 使用 JSONB,在 SQLite 使用规范化 JSON TEXT。
  • 枚举使用可读字符串,不使用数据库特有 Enum。
  • 时间以 UTC 文本或数据库支持的时间类型保存,由适配器统一转换。

28.2 调度和 Lease Claim 差异

PostgreSQL 使用行锁和 SKIP LOCKED 领取候选 Run。SQLite 使用短事务和单写者模型完成领取,Standalone 限制单 Service 实例。

Server 中只有持有 PostgreSQL Session-level Advisory Lock 的 Service 实例运行 Schedule Scanner;锁使用专用连接持有,连接失效自动释放。Run Claim 在短事务中执行并同时写入递增 Epoch 和 Fencing Token,不能只依赖进程内锁。

LISTEN/NOTIFY 仅作为新 Run、新命令和新事件的低延迟唤醒提示;所有消费者都必须有数据库轮询和游标恢复路径,正确性不依赖通知必达。

28.3 连接管理

  • 使用连接池或 DataSource,每个事务独立获取连接。
  • SQLite 池规模保持较小并启用 WAL、busy timeout。
  • PostgreSQL 根据 Service 实例和负载配置池大小。
  • 禁止全局共享 Connection、Session 或 Mapper 实例承载事务状态。

28.4 控制面高可用

  • 正式 Server HA 至少运行两个无状态 Service 实例,并置于支持 HTTP/2 与 gRPC 的负载均衡器后。
  • 业务状态、Agent Sequence、Lease、Command、Event 和幂等记录都持久化在 PostgreSQL;Service 本地缓存不是事实源。
  • PostgreSQL 自身复制、备份和故障转移由部署者选择并负责运维,WePush 安装包不自行组建数据库集群。
  • Schema Migration 以集群级单例执行,采用 Expand → Migrate → Contract,滚动升级期间保持相邻版本兼容。
  • 一个 Service 退出时 REST/SSE 客户端重连,Agent gRPC 自动重连;系统不要求连接粘滞。

详见 ADR-0006。

29. Artifact Store

29.1 接口

java
public interface ArtifactStore {
    ArtifactUploadPlan beginUpload(ArtifactCreateCommand command);
    ArtifactMetadata completeUpload(ArtifactCompleteCommand command);
    ArtifactDownloadPlan authorizeDownload(ArtifactRef ref, Optional<ByteRange> range);
    ArtifactMetadata stat(ArtifactRef ref);
    void delete(ArtifactRef ref);
}

ArtifactUploadPlan 可以是本地受控 Stream,也可以是带到期时间的 S3 Presigned URL/Multipart Plan。Upload 先进入 UPLOADING,写入完成并校验 SHA-256 后进入 READY。失败或超时的上传由清理任务回收。

29.2 本地目录

text
data/artifacts/
├── audiences/<snapshot-id>/recipients.jsonl
├── runs/<run-id>/success.jsonl
├── runs/<run-id>/failed.jsonl
├── runs/<run-id>/unknown.jsonl
├── runs/<run-id>/unsent.jsonl
└── runs/<run-id>/logs.jsonl

路径由 Artifact Store 生成,不能直接使用用户输入的任务名称,避免路径穿越和非法字符问题。

29.3 Server 对象存储

  • Server 默认实现 S3ArtifactStore,只依赖 Put/Get/Head/Delete、Range、Presigned URL 和 Multipart Upload 等受控 S3-compatible 子集。
  • 对象键格式为 workspaces/{workspaceId}/{artifactType}/{yyyy}/{mm}/{artifactId},不含用户文件名和 Secret。
  • Agent 不持有长期对象存储 Credential;Service 签发最小权限、短期有效的上传或下载 URL。
  • Service 托管流式写入达到 100 MiB 时默认使用 Multipart。Agent 对不超过 1 GiB 的对象继续使用绑定长度和 SHA-256 的单次 Presigned Put,超过该阈值时使用 Presigned Multipart Plan,最大单 Artifact 为 5 TiB。Service 每批最多签发 100 个 Part URL,并显式约束 S3 的 10,000 Part 上限;Agent 可独立重试 Part,提交 ETag 列表后由 Service Complete 并用 Head 元数据核对总大小与 SHA-256。未完成会话由 V17 事实表驱动主动 Abort,对象存储 Lifecycle 在 24 小时后终止孤儿 Multipart 作为兜底。
  • Server 模式启用对象存储原生 AES256 或由部署方自行保证等价的存储端加密;WePush 不对接或管理云 KMS Key。
  • 数据库元数据是生命周期事实源,对象存储 List 只能用于对账和孤儿清理。

29.4 下载

  • Service 校验权限后返回流或短期签名 URL。
  • 支持 Range 请求。
  • 下载文件名通过响应头提供,存储路径不暴露给客户端。
  • 敏感 Artifact 可要求再次认证或更高权限。

29.5 保留和删除

Service 清理任务根据 Workspace 策略把到期对象从 READY 标记为 DELETING,执行幂等删除后置为 DELETED。失败使用退避重试;对象不存在视为删除成功。默认保留期采用第 27 节基线,对象存储 Lifecycle 仅作为兜底。详见 ADR-0007。

30. Secret 详细设计

30.1 默认实现和加密信封

默认 SecretStore 实现为 LocalEnvelopeSecretStore。每条 Secret 生成独立 256-bit 随机 DEK,使用 AES-256-GCM 和随机 Nonce 加密;AAD 至少包含 workspaceId + secretId + secretType + recordVersion,从而阻止跨记录替换密文。

text
plaintext secret
    ↓ 使用随机 DEK 加密
ciphertext + nonce + algorithm
    ↓ 使用 Master Key 加密 DEK
encrypted DEK + key version

数据库保存 Ciphertext、Nonce、Encrypted DEK、算法和 Key Version。Master Key 不存入业务数据库。

  • Standalone 首启可创建主密钥文件,并限制为当前用户可读:Unix 权限 0600,Windows 使用专用 Service Account ACL。
  • Server 从只读挂载文件读取 Master Key;允许环境变量显式注入但不作为推荐生产方式。
  • Server HA 的所有 Service 实例挂载相同的 Active/Retained Key Ring;轮换由单一管理操作生成新版本,再通过受控部署传播到所有实例。
  • Server 发现密钥缺失、权限过宽或 Key Version 不可解析时 Fail Closed,不静默生成新密钥。
  • Master Key 轮换默认只重新包裹各 Secret 的 DEK,并保留旧 Key Version 直到验证和回滚窗口结束。
  • Electron safeStorage 只可用于 Desktop 自身的本地 UI Credential,不用于 Service Secret。Service 不规划 Vault、云 KMS、云 Secret Manager 或操作系统凭据存储 Adapter。

30.2 API 行为

创建或更新账号时:

json
{
  "token": {"operation": "REPLACE", "value": "secret"},
  "password": {"operation": "KEEP"}
}

查询时只返回:

json
{
  "token": {"configured": true, "updatedAt": "..."}
}

不提供读取 Secret 明文的普通 API。

30.3 Agent Secret Envelope

  • Service 只下发 Run 所需的最小 Secret 集。
  • Envelope 绑定 Agent ID、Run ID、Lease ID、Epoch 和过期时间。
  • 使用 Agent 公钥或受 TLS 保护的短期会话密钥加密。
  • Agent 解密后只在内存持有,不写 Local Journal。
  • Lease 失效后 Envelope 不再接受重新获取。

当前实现使用 Agent 进程启动时生成的 X25519 密钥对,Hello 只发布 X.509 编码公钥;Service 为每个 Envelope 生成一次性 X25519 密钥,使用 HKDF-SHA-256 派生 AES-256-GCM 密钥。冻结 Account/Message 中形如 {namespace,name,version} 的标准 SecretRef 被递归去重扫描,未引用的 Secret 不会进入 Envelope。Agent 在 ACK 前完成认证解密,运行完成/失败后清零 Secret Material。Envelope 上限为 512 KiB,单 Secret 上限为 64 KiB,并且不替代 TLS、Enrollment 和 Agent 身份认证。

31. Scheduler 详细设计

31.1 Schedule 字段

text
cron
timezone
enabled
misfirePolicy: SKIP | FIRE_ONCE | CATCH_UP_LIMITED
catchUpLimit
startAt
endAt
lastFireAt
nextFireAt

31.2 触发幂等

每个计划触发使用以下幂等键:

text
schedule:<scheduleId>:<scheduledInstant>

即使调度线程重启或重复扫描,也只创建一个 Run。

31.3 扫描

  • 周期性查询 enabled = true AND next_fire_at <= now。
  • 在短事务内锁定 Schedule、创建 Run、更新下次时间。
  • 不在调度事务中物化大型 Audience;Run 创建后异步准备 Snapshot。
  • Cron 解析和 Next Fire 计算使用同一库和版本,保存测试样例防止升级行为变化。

32. Remote 与 Embedded Java SDK 详细设计

32.1 包结构

text
sdk-java
├── generated/              OpenAPI 生成代码
├── WePushClient.java       统一入口
├── WorkspaceClient.java    Workspace 范围入口
├── AccountsClient.java
├── MessagesClient.java
├── AudiencesClient.java
├── JobsClient.java
├── RunsClient.java
├── AgentsClient.java
├── EventSubscription.java
└── WePushException.java

32.2 使用方式

java
try (WePushClient client = WePushClient.builder()
        .endpoint(URI.create("https://wepush.example.com"))
        .token(() -> tokenProvider.currentToken())
        .connectTimeout(Duration.ofSeconds(5))
        .requestTimeout(Duration.ofSeconds(30))
        .build()) {

    WorkspaceClient workspace = client.workspace(workspaceId);
    Run run = workspace.runs().start(jobId,
            StartRunRequest.builder().dryRun(false).build(),
            "manual-20260822-001");

    try (EventSubscription events = workspace.runs().events(run.id())) {
        events.forEach(System.out::println);
    }
}

32.3 SDK 重试

  • GET 和明确幂等请求可对网络错误自动重试。
  • POST 创建操作只有携带 Idempotency Key 时才能自动重试。
  • SDK 不对 Service 返回的业务失败盲目重试。
  • 429 和 503 遵循 Retry-After,并有最大等待限制。

32.4 Embedded SDK

java
try (WePushEngine engine = WePushEngine.builder()
        .provider(new HttpProviderFactory())
        .secretResolver(secretResolver)
        .resultSink(resultSink)
        .eventSink(eventSink)
        .build()) {
    RunHandle handle = engine.start(spec, recipients);
    handle.submit(new RunCommand.ChangeConcurrency(commandId, 50));
    RunSummary summary = handle.completion().toCompletableFuture().join();
}

Embedded SDK 不创建数据库、不读取 Next Service 配置目录,也不自动发现未显式允许的第三方 Provider。共享 Sink 与 Engine 同生命周期且必须支持并发 Run;使用 resultSinkFactory、eventSinkFactory 或 artifactSinkFactory 创建的 Sink 归单个 Run 所有,并在完成或启动拒绝后关闭。

33. WebUI 详细设计

33.1 技术与工程基线

  • TypeScript 6.0.x,开启 strict、noUncheckedIndexedAccess 和未使用代码检查。
  • Vite 8.1.x、React 19.2.x、Node.js 24 LTS;pnpm 通过 Corepack 固定精确版本并使用单一 Lockfile。
  • Tailwind CSS 提供设计 Token 和布局原语;shadcn/ui 仅按需引入复杂可访问组件,复制后的源码视为项目代码并纳入测试。
  • WebUI 是纯 SPA,由 Service 提供静态资源或独立 CDN 部署;生产环境不需要 Node.js Server,也不使用 React Server Components。

33.1.1 视觉语言

WebUI 与 Desktop Renderer 采用接近 Codex 客户端的克制型工作台风格,但不复制其品牌标识或专有资产:

  • 使用暖灰应用背景、浅灰紧凑侧栏、白色内容面和低对比细边框组织层级。
  • 左侧导航保持稳定,顶部区域只保留页面标题、搜索/命令入口和连接状态。
  • 字号、圆角和阴影偏小,依靠留白、对齐和字重形成视觉层级,避免装饰性渐变和大面积高饱和色。
  • 默认以单色交互为主;绿色、黄色、红色和蓝色只表达成功、警告、失败和信息状态。
  • 总览优先显示真实 Service、Provider、Agent 和 Run 数据;没有数据时使用明确空状态,不填充伪造趋势或示例成功率。
  • WebUI 与 Desktop 必须复用 design-tokens 和共享 UI primitives,平台外壳不得另建一套视觉变量。
  • 保持键盘操作、可见焦点、语义标签、颜色对比和窄屏退化能力;后续暗色主题仍由同一语义 Token 映射实现。

前端 Workspace 结构固定为:

text
ui/
├── apps/
│   ├── web/
│   └── desktop/
└── packages/
    ├── api-client/
    ├── schema-renderer/
    ├── ui/
    ├── features/
    └── design-tokens/

33.2 路由

text
/login
/overview
/providers
/accounts
/accounts/:id
/messages
/messages/:id
/audiences
/audiences/:id
/jobs
/jobs/:id
/schedules
/runs
/runs/:id
/agents
/agents/:id
/docs
/settings

33.3 前端分层

text
app/               路由、认证、全局错误处理
api/               生成客户端和 Facade
features/          按业务功能组织页面和用例
schema-renderer/   JSON Schema 与 UI Schema 表单
components/        通用展示组件
stores/            登录身份、连接状态和少量全局状态
events/            SSE 连接、游标和重连

Server State 以 API 缓存为主,不复制成长期全局可变 Store。

33.4 Schema 表单

Schema 默认控件
string 文本框
string + format=password 或 Secret 密钥输入框
string + enum 下拉框
boolean 开关
integer/number 数字输入
array 可增删列表或表格
object 分组表单
x-wepush-code-language 代码编辑器
Recipient Variable 变量选择和插入器

表单保存时发送原始 Schema 数据,不把 UI 布局字段混入 Provider 配置。

33.5 Run 页面

Run Detail 页面展示:

  • 状态、进度条和结果计数。
  • 实时吞吐和延迟图。
  • 当前 Agent、Lease 和 Provider。
  • 当前并发、允许范围、动态调整控件。
  • 结构化实时日志和错误分类。
  • Pause、Resume、Cancel 等状态相关操作。
  • 成功、失败、未知、未发送 Artifact 下载。

SSE 断开时显示连接状态并自动重连;不能将“监控连接断开”误显示为“Run 失败”。

33.6 API 文档页

  • 展示当前 Service 的实时 OpenAPI。
  • 使用当前登录身份调用,但不在 Local Storage 保存长期 Token。
  • Secret 示例只使用占位符。
  • 对删除、取消、真实发送等操作增加风险提示。
  • 只读角色禁用 Try It Out 的写操作。

34. Desktop UI 详细设计边界

Desktop 固定采用 Electron 43.x。apps/desktop 只实现 Main/Preload、窗口、托盘、通知、自动启动和更新能力,Renderer 复用 packages/features 中的 WebUI 页面。

34.1 启动流程

mermaid
flowchart TD
    Start[Desktop 启动] --> Config[读取连接配置]
    Config --> Mode{本地还是远程}
    Mode -- 远程 --> Login[连接远程 Service 并登录]
    Mode -- 本地 --> Health[探测本机 Service Health]
    Health -->|在线| Bootstrap[获取本地短期会话]
    Health -->|离线| StartService[启动或提示安装 Service]
    StartService --> Health
    Bootstrap --> UI[加载共享 UI]
    Login --> UI

34.2 本地认证

  • Service 不因来自 localhost 就跳过认证。
  • Desktop 使用受操作系统权限保护的 Bootstrap Channel 获取一次性 Token。
  • 一次性 Token 换取短期会话后立即失效。
  • 不把管理员长期 Token 写入普通配置文件。

34.3 进程边界

  • Main、Preload 和 Renderer 严格分层;Renderer 设置 nodeIntegration: false、contextIsolation: true、sandbox: true。
  • Renderer 只加载随安装包发布的本地内容,不执行任意远程代码;CSP 禁止 unsafe-eval。
  • Preload 只暴露版本化、最小白名单 IPC,参数按 Schema 校验;不得暴露通用文件系统、Shell 或进程执行能力。
  • Desktop 退出不等于停止 Service 或正在运行的 Run。
  • Desktop 更新和 Service 更新是两个独立过程。
  • Desktop 可以显示 Service 版本不兼容并引导升级。
  • Desktop 不加载 Provider JAR、不读取数据库文件。

35. 认证、授权和审计

35.1 角色

角色 权限摘要
ADMIN 用户、Agent、Secret、系统配置及全部业务操作
OPERATOR 账号、消息、受众、Job、Run 和调度操作
VIEWER 查询配置摘要、运行状态、日志和文档

读取账号不等于读取 Secret。即使 ADMIN 也不能通过普通 API 取回明文 Secret。

ADMIN、OPERATOR、VIEWER 角色绑定在 Workspace 范围内。另设显式 SYSTEM_ADMIN,只用于 Workspace 生命周期、全局 Agent 视图和跨 Workspace 应急治理;它可以越过 Workspace 角色,但每次操作仍携带目标 workspaceId 并进入审计。普通 Workspace ADMIN 不能签发、查看或吊销其他 Workspace 的 API Token/Enrollment Token。Standalone 的 Default Workspace 在普通 UI 中隐藏,但 API 和 Repository 仍使用其真实 ID。

35.2 权限检查

  • Controller 只完成认证和基础 Scope 检查。
  • Application Service 根据 Workspace、资源所有权和操作执行最终授权。
  • Agent Internal API 仅接受 Agent 身份。
  • Artifact 下载再次校验 Run 或 Audience 权限。

35.3 审计事件

必须审计:

  • 登录、Token 创建和吊销。
  • Account、Secret、Job、Schedule 的创建和修改。
  • Run 启动、取消、暂停、恢复和并发调整。
  • Agent Enrollment、禁用和 Credential 轮换。
  • Artifact 下载和敏感配置导出。
  • 权限失败和关键系统配置改变。

审计 Details 只保存字段名和变更摘要,不保存 Secret 原值。

36. 输入与网络安全

  • 所有 API 请求设置 Body 大小上限。
  • 文件导入流式解析并限制解压后大小、行数、列数和单字段长度。
  • CSV/Excel 导出防止公式注入。
  • 模板引擎禁止任意类访问、文件访问和反射。
  • HTTP Provider 实施 SSRF 保护和响应大小限制。
  • 标准渠道生产实现固定或严格校验厂商官方端点;响应体有界,诊断不包含 Secret、Token URL 或完整远端响应。
  • 上传文件名仅作为展示信息,不作为真实路径。
  • 日志字段使用结构化参数,不拼接未清理的控制字符。
  • OpenAPI 文档和 UI Schema 中的富文本需要清理危险 HTML。
  • Service 默认安全响应头,并明确 CORS Allowlist。

37. 可观测性详细设计

37.1 日志字段

text
timestamp
level
service
instanceId
traceId
workspaceId
runId
agentId
leaseId
providerId
event
message

日志必须通过统一 Redactor 处理 Header、URL Query、JSON 字段和异常信息中的 Secret。

37.2 指标

text
wepush_runs_total{state,provider}
wepush_run_items_total{result,provider}
wepush_run_inflight{run,provider}
wepush_provider_requests_total{provider,result,error_category}
wepush_provider_request_duration_seconds{provider}
wepush_provider_retries_total{provider,category}
wepush_agent_heartbeat_age_seconds{agent}
wepush_agent_active_runs{agent}
wepush_lease_expired_total
wepush_event_outbox_size{agent}
wepush_artifact_upload_bytes_total{type}

Run ID、Agent ID 等高基数字段不作为默认 Metrics Label,应通过日志和 Trace 查询。

37.3 Health

  • Liveness:进程和核心线程是否存活。
  • Readiness:数据库、迁移、Secret、Artifact 是否可用。
  • Agent Readiness:身份、Service 连接和 Provider Catalog 是否正常。
  • Provider Health 不对每次 Health 请求调用外部渠道,连接测试由用户显式触发。

38. 配置设计

示例:

yaml
wepush:
  mode: standalone
  server:
    bind-address: 127.0.0.1
    port: 18990
    public-base-url: http://127.0.0.1:18990
  database:
    type: sqlite
    url: jdbc:sqlite:${WEPUSH_DATA_DIR}/wepush-next.db
  artifacts:
    type: local
    directory: ${WEPUSH_DATA_DIR}/artifacts
  secrets:
    type: local-envelope
    master-key-file: ${WEPUSH_CONFIG_DIR}/master.key
  agent:
    embedded:
      enabled: true
      max-runs: 2
      max-concurrency: 200
  security:
    remote-access: false
  events:
    progress-interval: PT1S
    batch-size: 100

规则:

  • 配置支持文件、环境变量和命令行覆盖,优先级明确。
  • Secret 不直接写入普通 YAML,使用环境变量引用或 Secret Store。
  • 启动时打印生效配置摘要,但敏感字段必须掩码。
  • 未识别配置项默认报错或告警,不能静默忽略拼写错误。
  • 配置变更是否支持热加载由字段明确声明,默认需要重启。

39. 默认端口和目录

以下为初始发行基线,安装包落地时允许用发行 ADR 调整未公开的具体值:

平台 配置 数据 日志
Linux /etc/wepush-next/ /var/lib/wepush-next/ /var/log/wepush-next/
Windows %ProgramData%\WePush Next\service.env、agent.env %ProgramData%\WePush Next\service、agent %ProgramData%\WePush Next\logs
macOS Service /Library/Preferences/wepush-next/ /Library/WePushNext/data/ /Library/Logs/WePushNext/
开发模式 next/.local/config next/.local/data next/.local/logs

默认 HTTP 端口建议 18990,不得与 Classic 使用的资源发生冲突。

开发模式目录必须加入 .gitignore。

40. Service 安装和生命周期

40.1 Linux

  • Service 使用专用系统用户 wepush,Agent 使用独立的 wepush-agent,配置组权限和数据目录彼此分离。
  • systemd Unit 以非 root 用户运行。
  • ExecStart 指向版本化发行目录;完整发行变体携带 JDK 25 jlink Runtime Image,精简变体校验并使用系统 Java 25+,启动脚本按此顺序自动选择。
  • 配置 Restart=on-failure 和合理的 Stop Timeout。
  • 安装后不自动开放公网防火墙端口。

40.2 Windows

  • 使用独立 Windows Service 名称 WePushNextService。
  • 通过发行包离线携带且固定长度/SHA-256 的 WinSW 启动包内 Runtime 或系统 Java 25+;WinSW 使用低权限 LocalService,ProgramData ACL 只授予 LocalService、SYSTEM 和 Administrators。
  • 配置滚动日志、失败重启和优雅停止。
  • 安装、卸载和 Service 控制需要管理员权限。

40.3 macOS

  • 系统级 Service 使用 LaunchDaemon;用户级 Standalone 可使用 LaunchAgent。
  • 安装包明确选择一种运行级别,不能混用数据目录。
  • LaunchDaemon 必须使用明确存在的非 root 账号;安装器默认使用 sudo 的发起用户,也允许显式设置 WEPUSH_SERVICE_USER。
  • 未签名告知、Release SHA-256、SBOM 和升级流程单独验证。

40.4 升级

  1. 拒绝或等待新 Run。
  2. Service 进入 Draining。
  3. 生成带 Manifest 和逐文件 SHA-256 的数据库、Master Key、Artifact、Agent Identity、Journal、Outbox、插件与配置一致性备份,并调用 Restore 校验。
  4. 停止旧进程。
  5. 替换应用并执行迁移。
  6. 启动并完成 Readiness、Flyway 当前版本和本地无网络 Provider Dry Run 检查。
  7. 失败时原子切回旧版本、恢复升级前备份、验证旧版本并保留可诊断的恢复目录。

数据库迁移一旦包含不可逆变化,必须明确最低可回滚版本。

41. 测试详细设计

41.1 Core 单元测试

使用 Fake Clock、Fake Provider、In-Memory Recipient Source 和 Result Sink,覆盖:

  • 空 Audience、单条和大批量。
  • 并发增减和背压。
  • 暂停、恢复和取消竞态。
  • Retry、Retry-After 和 Deadline。
  • Provider Session 打开或关闭失败。
  • Result/Event Sink 暂时失败。
  • 最终计数恒等式。
  • Secret 清理和日志脱敏。

测试不能依赖真实时间 Sleep,应通过可控 Clock 和调度器推进。

41.2 Provider 契约测试

所有 ProviderFactory 必须通过同一套测试:

  • Descriptor 和 Schema 可加载。
  • Provider ID 唯一且合法。
  • Secret 字段正确标记。
  • 非法配置返回结构化 Validation Error。
  • Session 线程安全声明与行为一致。
  • ErrorCategory 映射完整。
  • Close 幂等。

41.3 Agent 故障测试

  • LeaseOffer 后未 Ack。
  • Heartbeat 超时。
  • Event Batch 重传和乱序。
  • 旧 Epoch Agent 上报。
  • Service 不可达时 Event Outbox 满。
  • Agent 在 Provider 调用前、调用中和调用后崩溃。
  • 重启后 Journal 与 Service 状态冲突。

41.4 Service 集成测试

  • SQLite 和 PostgreSQL 使用同一 Repository 契约测试。
  • 创建 Run 事务和幂等键。
  • 并发 Lease Claim 只能产生一个 Active Lease,并产生唯一有效 Fencing Token。
  • 两个 Service 实例竞争调度锁时只能有一个 Schedule Scanner。
  • LISTEN/NOTIFY 丢失后数据库扫描仍能推进状态。
  • Run 状态机非法转换。
  • Schedule 重复扫描不重复创建 Run。
  • Secret 查询不返回明文。
  • Artifact 未完成、损坏和越权下载。
  • SSE 游标续传和过期。

41.5 E2E

首条流水线:

text
启动 Standalone Service
→ Web/API 创建 HTTP Account
→ 创建 Message 和 Audience Snapshot
→ 创建 Job
→ 启动 Run
→ Embedded Agent 领取
→ Mock HTTP Server 接收请求
→ SSE 观察进度
→ 下载结果 Artifact
→ 重启 Service 验证历史

42. 性能和容量验证

首期不承诺所有 Provider 的统一吞吐,但必须建立可重复基线:

  • 100,000 Recipient 不全部加载进内存。
  • 结果 Artifact 流式写入,内存随 Recipient 总数近似恒定。
  • 进度事件数量由时间窗口控制,不与 Item 数量线性增长。
  • 并发从低到高调整时不丢失 Item。
  • Service 同时维持多个 SSE 连接时不会阻塞 Run 更新。
  • Agent Event Outbox 在网络抖动下有明确磁盘和内存上限。

性能报告记录硬件、JVM、Provider Mock 延迟、并发、吞吐、P50/P95/P99 延迟、GC 和最大内存。

43. CI/CD 详细流程

text
validate
├── 格式和静态检查
├── OpenAPI 校验
├── JSON Schema 校验
└── 依赖与许可证检查

test
├── 单元测试
├── 架构测试
├── Provider 契约测试
└── Service 集成测试

package
├── Java 制品
├── WebUI
├── Service/Agent 应用镜像
└── Java SDK

platform-package
├── Linux
├── Windows
└── macOS

smoke
├── 安装
├── 启动和 Health
├── HTTP Provider 最小 Run
└── 卸载/升级验证

Next CI 使用 next/** 路径过滤,Classic 和 Next 互不依赖对方构建产物。

44. 首个纵向实现拆分

44.1 Iteration 1:纯 Core

  • 创建模块和架构测试。
  • 实现 Core 类型、ExecutionEngine 和 Fake Provider。
  • 完成并发、取消、结果和事件单元测试。

44.2 Iteration 2:HTTP Provider

  • 完成 Descriptor、Schema、Session 和 Mock Server 契约测试。
  • 实现 SSRF、超时、响应限制和错误分类。
  • 通过 Embedded SDK 运行。

44.3 Iteration 3:Service 单机闭环

  • SQLite、迁移、Account/Message/Audience/Job/Run API。
  • Embedded Agent 复用同一 Lease/状态语义,但通过 Java 接口直连,不经过 gRPC。
  • OpenAPI、Java SDK 和 SSE。

44.4 Iteration 4:最小 WebUI

  • Account Schema 表单。
  • Message、Audience 和 Job 创建。
  • Run Detail、SSE 和结果下载。
  • API 文档页面。

44.5 Iteration 5:远程 Agent

  • Enrollment、身份、gRPC Connect、Heartbeat、LeaseOffer/LeaseAck 和 EventBatch/EventAck。
  • Fencing Token 与 Agent Journal。
  • Agent 失联和恢复测试。

当前进度:Iteration 5 基线已完成。Hello/Heartbeat、持久 Lease/Fence、Snapshot/Audience 下载校验、加密 Secret Envelope、远端 Core 执行、磁盘 Event/Completion Outbox、EventBatch/EventAck、RunCommand/CommandAck、Agent Artifact 上传/完整性提交、RunCompleted、重复 Offer/Event 去重、进程重启恢复、一次性 Enrollment、Credential/证书轮换以及 TLS/mTLS 已形成纵向闭环;Service→Agent 消息使用数据库 outbox 支持 Agent 在任意 Service 实例重连。

44.6 Iteration 6:产品化

  • Workspace 权限、审计、LocalEnvelopeSecretStore。
  • Scheduler。
  • Desktop 和三平台 Service 安装。
  • PostgreSQL 18 HA、S3-compatible Artifact Store 和部署文档。

当前进度:Iteration 10(1.2.0)已完成。1.1.0 的运营商插件、资源治理、诊断、熔断、PostgreSQL 通知、Multipart Artifact 和 WebUI 可访问性继续有效;1.2.0 增加 Codex MCP/Skill 接入,并将 Java 基线升级到 25,且不新增数据库迁移。具体版本边界见产品路线图和 1.2.0 Release Notes。

44.7 Iteration 7:日常使用闭环

  • Account/Message/Audience/Job/Schedule 编辑、修订、复制、状态管理。
  • CSV/TXT 流式导入、字段映射、预览、去重、错误行和不可变 Snapshot。
  • Dry Run、正式发送影响确认、结果查看和失败项关联重发。
  • 资源/Run/审计签名分页筛选、真实总览和 Standalone/Server Workspace UX。

当前进度:Iteration 7 已在 0.1.0-alpha.3 完成。OpenAPI、Remote Java SDK、TypeScript Client、WebUI、SQLite V12/V13 和纵向集成测试同步交付;启动恢复也使用有界分页扫描。

44.8 Iteration 8:真实消息渠道

  • 内置 SMTP、飞书/钉钉/企微机器人、阿里云短信、微信公众号、小程序和企业微信应用消息。
  • 每个 Provider 同步交付 Account/Message/Recipient Schema、SecretRef、Dry Run、真实发送、错误分类和本地协议测试。
  • Service/Agent 通过 ServiceLoader 发现,Embedded SDK 由调用方显式选择;WebUI 继续按实时 Schema 渲染。

当前进度:Iteration 8 已在 0.1.0-alpha.4 完成。标准渠道集中在独立 provider-standard 模块,生产端点受限于厂商官方域名;微信系 Token 只在 Session 内缓存并在明确失效后刷新一次。协议、重试、限流、幂等和最小配置见内置 Provider 指南。

45. 完成定义

一个模块或功能只有同时满足以下条件才视为完成:

  • 实现符合依赖规则和领域状态机。
  • 公开契约、Schema 或配置已记录。
  • 单元、契约和必要的集成测试通过。
  • 错误、日志和指标可诊断且不泄露 Secret。
  • API 和 UI 具备明确的权限行为。
  • 资源关闭、取消和进程退出路径经过测试。
  • 文档、示例和升级说明同步更新。
  • 不需要 Classic 源码或构建产物即可编译和运行。

46. 已决 ADR 与后续事项

本轮技术基线已由 ADR-0002 至 ADR-0008 确认,包括 Web/Desktop/Service、Secret、Agent 协议、Provider 插件、PostgreSQL HA、Artifact 和 Workspace 多租户。

以下事项可以在对应能力进入迭代前继续形成独立 ADR,但不阻塞当前架构:

  1. OpenAPI Generator、数据库迁移工具、路由和 Server State 库的具体版本。
  2. Desktop 本地 Bootstrap Channel、平台安装和未签名发行完整性验证。
  3. 非受信 Provider 的独立进程 Runner;只有明确服务于用户自建安全场景时才进入评审。

公共 SaaS、计费订阅、外部 Vault/云 KMS/Secret Manager、恶意公共租户物理隔离和跨地域控制面不是“后续非基线事项”,而是长期产品非目标。任何后续 ADR 不得把它们重新引入。允许评审的扩展不得反向污染 Core API,也不得削弱 Workspace、Lease Fencing、Secret 和 Artifact 的既定安全边界。

47. 文档演进规则

  • 本文档负责跨模块的实现级设计。
  • Java API、OpenAPI、JSON Schema 和数据库迁移文件落地后,以可执行契约为准,并保持与本文一致。
  • 仅影响模块内部的实现细节记录在模块 README 或模块设计文档。
  • 改变组件职责、依赖方向、运行语义或安全边界时,先提交 ADR,再修改本文档。
  • 文档中的示例不得包含真实 Token、Secret、生产地址或个人数据。

Next Docs Implementation Status (`next/docs/implementation-status.md`)

WePush Next 实现状态

更新时间:2026-09-26

产品范围和后续优先级以《产品目标、边界与路线图》为准。Next 的长期定位是用户自行下载、安装、部署和运维;不建设官方公共 SaaS、注册计费订阅、云 KMS/Secret Manager 或恶意公共租户物理隔离。

1. 里程碑结论

next/ 的当前稳定基线为 1.2.0:在 1.1.0 的运营商签名插件、Workspace 资源治理、脱敏诊断、跨 Run 认证熔断、PostgreSQL 低延迟唤醒、Agent Presigned Multipart 与 WebUI 主题/可访问性之上,增加 Codex MCP/Skill 接入,并把 Java 运行基线升级到 25。数据库仍为 V17,REST/SSE 与 Agent 协议保持兼容。Classic 源码和构建保持不动;两条产品线不共享源码依赖,允许各自存在相似实现。

text
React WebUI / Electron Desktop / Remote Java SDK
                  │ REST + Bearer RBAC + SSE
                  ▼
       Spring Boot Service API(1..N 实例)
          │             │              │
   SQLite/PostgreSQL  Secret Store   Local/S3 Artifact
          │             │              ▲ Presigned/Commit
          └──── durable control ────────┘
                         │ gRPC 双向流
                         ▼
             Enrolled Remote Agent + PF4J
                         │
                    Core Engine
                         │
                   Provider SPI

业务 Java 应用 → Embedded Java SDK → Core Engine → Provider SPI

Standalone 默认是单 Service + SQLite + Local Artifact + Embedded Engine;Server 默认模型是 PostgreSQL 18 + S3-compatible Store + 多 Service + Remote Agent。PostgreSQL 和 S3 不是使用 Next 的前置条件,只是 Server/HA 形态的共享事实源。

2. 组件完成状态

组件 当前基线
Core Framework-free API、虚拟线程 Engine、并发/限速/重试/暂停/取消、流式 Result/Event/Artifact 端口
Provider 独立 SPI;内置 HTTP、SMTP、飞书/钉钉/企微机器人、阿里云短信、微信系渠道;CMPP/SMGP/SGIP/SMPP 独立 Ed25519 签名插件;Schema、SecretRef、Dry Run、错误分类与端点约束
Agent gRPC 双向流、Sequence/Fence Journal、磁盘 Event/Completion Outbox、重连恢复、Secret Envelope、Presigned Put/Multipart Artifact、Enrollment/轮换、TLS/mTLS
Service Spring Boot 4.1.1、SQLite/PostgreSQL、资源修订/分页、Workspace Policy、认证熔断、脱敏诊断、手动版本检查、Local/S3 Artifact、RBAC/审计/Scheduler、Agent HA Outbox、PostgreSQL 通知与跨实例 SSE 补偿
Remote Java SDK 只依赖公开 service-api;覆盖 System、Provider、Agent、Workspace Policy、资源生命周期/分页、Audience 上传、Run、Artifact、Schedule、Security、诊断、版本检查与认证熔断
Embedded Java SDK Framework-free 进程内 Engine 门面;显式 Provider、SecretResolver、Result/Event/Artifact Sink,支持列表或流式 Recipient 与完整 RunHandle 控制
WebUI TypeScript/Vite/React;资源编辑/修订、导入/确认/重发、总览/分页/调度、Security、Workspace Policy、诊断/版本/熔断管理、动态 Schema、API 调试、亮暗主题、低分辨率与可访问性、AI 助手安装器下载
Desktop Electron 安全外壳,共用 WebUI;本机 Service 检测/启停/日志/诊断、系统原生 API Token 安全存储、签名插件生命周期、一键安装 Codex MCP/Skill;目标系统原生目录打包,不依赖 Core 或 Service 内部实现
Distribution 系统 Java 精简包 + 三平台 jlink Runtime 完整包;统一 Standalone/高级分组件安装、离线 WinSW、正式备份/恢复、Beta 升级、升级健康门、自动回退与安全卸载;容器 Server/HA 拓扑

3. Core、Provider 与 Agent

  • Core API 不依赖 Spring、数据库、HTTP Server、PF4J 或 UI;Service 和 Agent 通过端口适配同一个 Engine。
  • 内置标准渠道位于独立 provider-standard 模块,通过 Provider SPI 与 ServiceLoader 接入 Service/Agent;Embedded 应用按需要显式声明并注册它,不把渠道依赖强加给 Engine。
  • SMTP 使用 Angus Mail;机器人、短信和微信系使用 JDK HttpClient。生产实现固定官方端点或严格校验用户提供的官方 Webhook,本地 mock 入口只对同包测试可见。
  • 微信系 Access Token 按 Session 缓存并提前刷新;只有远端明确拒绝 Token 时安全重放一次。提交后超时、I/O 或 5xx 均按 UNKNOWN 处理,不假定外部渠道 Exactly Once。
  • Run Snapshot 固定 Provider ID/实现版本,调度只选择上报精确兼容版本且有容量的 Agent。
  • PF4J 只位于 Agent App 边界。每个插件独立 ClassLoader,Provider SPI/Core API/日志 API 由 Parent 提供;这解决依赖冲突,不宣称是恶意代码沙箱。
  • 正式插件包必须包含 SHA-256 清单和 Ed25519 签名。未知发布者、清单篡改、Zip Slip、压缩炸弹、捆绑共享 API 或重复 Provider 版本都会失败关闭。
  • CMPP、SMGP、SGIP、SMPP 分别以独立插件发布,共享代码只存在 Next 内部的 provider-carrier-common 构建模块;插件没有 Classic 源码或构建依赖,Release 流水线在上传前用 Agent 生产校验器重新验签。
  • 插件更新采用 Stage → 原子替换 → Supervisor Restart → Health Verify;失败自动恢复旧包。运行中不热卸载 ClassLoader。
  • Agent 持久化 Lease Fence、双向 Sequence、Event Batch 与 Run Completion。Service ACK 前数据不会从 Outbox 删除;重连后按 Fence/Sequence 重放。
  • 同一权威 Lease Offer 重放只推进 Service Sequence,不重复启动 Run。进程重启发现曾处于 RUNNING 的 Lease 时按 Unknown/Unsent 安全语义生成可恢复 Completion,而不假定外部渠道 Exactly Once。
  • Secret 只扫描冻结 Account/Message 中实际引用的 SecretRef;Service 使用一次性 X25519 + HKDF-SHA-256 + AES-256-GCM 生成绑定 Agent/Run/Lease/Epoch/Fence/过期时间的 Envelope。Agent 明文只存在运行内存并主动清零。
  • Agent 的 Core ArtifactSink 先写 owner-only 临时文件并计算大小/SHA-256,再获取 Lease 绑定的上传计划;不超过 1 GiB 使用单次 Put,更大文件使用可重试 Presigned Multipart,Complete/Abort 后再 Commit;READY Artifact 引用进入 RunCompleted。
  • Enrollment Token 一次性消费并绑定 Workspace。Agent 生成 P-256 私钥,保存长期 Credential、客户端证书和 CA,在到期窗口内自动轮换。匿名 Agent 仅允许回环开发;非回环 HTTP/gRPC 必须认证,gRPC 强制 TLS,生产要求 mTLS。

4. Service、HA、安全与数据

  • SQLite 开启 Foreign Key、WAL、Busy Timeout 和原子 Flyway 迁移;V10/V11 收口 Enrollment Workspace 和系统管理员,V12/V13 增加资源生命周期、关联重发和流式 Audience Import,并由迁移测试验证实际约束。
  • PostgreSQL 使用 Hikari;相同有序迁移由 PostgreSQL 18 CI 实库验证。Server 模式启动校验 PostgreSQL、S3、API Security 和 Agent TLS 均已启用。
  • Schedule Scanner 使用 PostgreSQL Session Advisory Lock 单 Leader;触发 Run 仍使用持久幂等键。
  • Lease Offer 和 Run Command 先写 agent_message_outbox。任意 Service 扫描待发送消息,只有持有 Agent 当前 gRPC 流的实例实际投递,Agent ACK 后关闭消息。
  • SSE 历史从 run_event 回放;本实例实时发布,同时每个实例轮询有本地订阅者的游标,补偿其他实例提交的事件。
  • PostgreSQL Server/HA 对新 Run、Agent Outbox 和 Run Event 使用三个固定 LISTEN/NOTIFY Channel 加速唤醒;通知丢失、断线或重复时仍由周期扫描、持久 Outbox 和事件游标保证推进。
  • Workspace Policy 限制 Agent 数量、活动 Run、总发送并发、Artifact 总量和默认保留期;账号认证失败在 V16 事实表中跨 Run 累积并熔断,冷却到期或管理员复位后恢复。
  • API Token 只保存 SHA-256,明文仅签发时返回一次。VIEWER/OPERATOR/ADMIN 按 Workspace 授权;Bootstrap SYSTEM_ADMIN 只承担全局治理。启用安全时按系统管理员是否存在而非“任意 Token 是否存在”初始化 Bootstrap,避免先创建普通 Token 后锁死全局治理。Token 与 Enrollment 管理 API 进入 Workspace 路径,普通 ADMIN 无法跨 Workspace 签发、枚举或吊销;写操作和拒绝结果进入审计日志。
  • 无认证开发模式只能绑定回环地址;HTTP 对外监听强制 API Security,Server 模式进一步强制 PostgreSQL、S3 与 Agent gRPC TLS,误配置时失败关闭。
  • Workspace 有正式列表/创建/详情 API。Account、Secret、Message、Audience、Job、Schedule、Run、Artifact、API Token 与 Agent Enrollment Binding 均显式带 Workspace 边界;旧的未绑定开发 Agent 只兼容 ws_default。
  • 默认且正式的 Secret Store 为 Local Envelope:随机 DEK + AES-256-GCM + 主密钥封装。主密钥来自 owner-only 文件或显式注入;密文存在而主密钥丢失/权限不安全时失败关闭。SecretStore Port 用于模块隔离和测试,官方产品不规划 OS Keychain、Vault、云 KMS 或 Secret Manager 形式的 Service Adapter。
  • Local Artifact 采用受控路径、临时写、fsync、SHA-256 与原子移动。S3 Store 支持 Put/Get/Range/Head/Delete、Presigned Put、服务端与 Agent Multipart、Complete/Abort、原生 AES256 和对象元数据校验;不包含云 KMS 集成。
  • Artifact 状态是 UPLOADING → READY → DELETING → DELETED,失败进入 FAILED;TTL 清理尊重 Pin/Legal Hold。对象存储 Lifecycle 是兜底,不替代数据库事实源。
  • Actuator 暴露 Health/Readiness/Prometheus,指标带稳定 application 标签;Server HAProxy 对 Readiness 做后端摘除。

5. API、SDK 与 UI

  • OpenAPI 3.1 覆盖资源编辑/分页、Audience Import、发送确认/重发/总览、Schedule/Security/Workspace 与 Agent Internal Enrollment/Lease/Artifact API;全局 Bearer Security 和内部自定义认证显式声明。
  • Maven 契约测试拒绝 YAML 重复键、重复/缺失 operationId、无 Response 和不可解析的本地 $ref。
  • Remote Java SDK 只有 service-api 依赖,绝不依赖 Core/Engine/Provider;支持签名分页、资源编辑、Schedule CRUD、发送确认/重发、总览和使用文件 BodyPublisher 的流式 multipart Audience 上传。
  • Embedded Java SDK 依赖 Core API、Provider SPI 和 Engine,但不依赖 Service、Agent、Spring 或具体 Provider;应用显式注册允许的 Provider,并选择共享或按 Run 创建的 Sink。
  • TypeScript Client 使用可更新 Bearer Token;SSE 使用自定义 Fetch Parser,因此 Server 安全模式不受原生 EventSource 无法设置 Authorization Header 的限制。
  • WebUI 接入 Account/Message/Audience/Job/Run/Artifact 全链路、资源编辑/复制/状态、修订历史/Diff、CSV/TXT 导入、Schedule 完整编辑、发送确认/重发、Workspace 选择、API Token、Agent Enrollment、审计与动态 API 调试;Schema Renderer 支持本地 $ref 和嵌套 SecretRef 默认示例。
  • Desktop 主进程保持 contextIsolation=true、nodeIntegration=false,只暴露固定 IPC:本机 Service 状态/启停/日志/诊断、签名插件 Stage/Activate/Rollback 和 API Token safeStorage。渲染页不能执行任意命令;浏览器 Token 仅使用 sessionStorage。

6. 当前数据库事实源

在原有 Workspace、Account、Message Revision、Audience Snapshot、Job、Run Snapshot/Event/Result/Command、Secret 和 Artifact 表之外,当前迁移还包含:

表 用途
agent_registration Agent 当前会话、平台、Provider、容量、Sequence、X25519 公钥
agent_lease Run/Agent 归属、Epoch、Fencing Token、Event Cursor 和恢复状态
agent_message_outbox 跨 Service 实例的 Lease Offer/Run Command 持久消息
agent_enrollment_token 一次性 Enrollment、Workspace、过期和消费状态
agent_credential 哈希长期 Credential、证书指纹、轮换/撤销/使用时间
agent_workspace_binding Agent 与可执行 Workspace 的正式绑定
api_principal / api_token / role_binding API 身份、Token 和 Workspace RBAC
audit_event 控制面安全与变更审计
schedule_definition Cron、时区、Misfire、启停和下次触发时间
audience_import_session / audience_import_row 流式文件导入会话、预览计数、数据库去重和错误行
run_retry_item 关联重发 Run 的精确 Item 集合;来源与状态同时保存在 run_instance
workspace_policy Workspace 的 Agent、Run、发送并发、Artifact 容量和默认保留期策略
account_auth_circuit 账号跨 Run 的认证失败计数、熔断状态、冷却时间和管理员复位状态
artifact_multipart_upload Agent Presigned Multipart 会话、大小、摘要、Part 规划、到期和状态

7. 验证与发布门禁

本地/常规门禁:

  • ./mvnw verify:Core、Provider、Agent、Service、SDK、架构和纵向集成测试。
  • pnpm check:全部 Workspace TypeScript、Vitest、Web Vite 构建与 Desktop TypeScript 构建。
  • 标准渠道测试使用本地 SMTP/HTTP mock 验证真实协议、签名、错误映射、Token 缓存/刷新和 Dry Run;Engine 纵向测试逐一执行全部 8 个标准渠道。
  • SQLite 空库迁移断言真实列和 Foreign Key,而不只相信 Flyway 版本号。
  • 真实 gRPC 纵向链路覆盖 Hello/Welcome、Lease Ack、受保护文档、Secret、Command Ack、Event 去重、Agent Artifact 上传/Commit、Run Completion。
  • 插件测试覆盖有效签名、未知签名者、Zip Slip 和空目录。
  • 安装脚本通过 POSIX shell 语法、PowerShell AST、launchd plist、WinSW XML 与 Compose 配置校验;三平台自测实际覆盖备份内容摘要、完整恢复和强制升级失败回退。
  • 浏览器 Playwright E2E 覆盖 Standalone/Provider/会话 Token;三平台打包后启动 Desktop 冒烟;定时矩阵在 Java 25、SQLite/PostgreSQL 上执行长稳健康检查。
  • 发行归档生成系统 Java 精简包和含 Runtime 的平台完整包,并检查离线 WinSW、Service/Agent/WebUI/安装/恢复脚本及 Remote/Embedded Java SDK 均存在。

.github/workflows/next-ci.yml 另外使用真实 PostgreSQL 18 和固定 MinIO 版本验证:

  • PostgreSQL 从 V13 用户数据迁移到 V17,并验证 Workspace Policy、认证熔断、Multipart 表、控制面通知和已有数据;
  • S3 Presigned Put、Presigned Multipart Complete/Abort、Range、Head/Checksum、Delete 和 100 MiB Service Multipart;
  • Java/UI 门禁通过后才生成发行归档。
  • next-v* Tag 由独立发行流水线生成三平台未签名 Desktop 包、CycloneDX SBOM 和统一 SHA256SUMS;无后缀版本发布为稳定 GitHub Release,带后缀版本才标记为 Pre-release。

8. 稳定版完成边界与后续演进

1.2.0 在 1.1.0 上完成兼容增量:不新增 Flyway 迁移,不改变 REST/SSE 与 Agent 协议;Codex MCP/Skill 复用既有 API、确认令牌和 Workspace 权限。Java 基线为 25,完整包携带对应运行时。1.1.0 已完成的 Provider 插件、资源治理、诊断、可靠性、大 Artifact 与 UI 门禁继续有效。

以下属于 1.x 内可兼容增加的产品增量,不是稳定版缺口:

  • 以独立签名插件继续增加其他短信、推送和运营商协议,不扩大内置核心依赖。
  • 在兼容策略范围内增加可选 API、运维诊断和用户自建部署模板。
  • 更新始终由用户主动触发;macOS/Windows 应用保持未使用商业代码签名,并继续以 Release、SHA-256、SBOM 和明确告知提供完整性证据。

公共 SaaS、自助注册、计费订阅、公共市场、云 KMS/Secret Manager、恶意公共租户物理隔离、跨区域 Active-Active、强制自动更新和遥测回传属于长期非目标,不进入后续产品增量。

路线图内的增量继续遵守 Classic/Next 双轨独立、Remote Java SDK 不依赖 Core、Embedded Java SDK 不依赖 Service、Workspace 显式隔离、Lease Fencing、Secret 最小暴露和 Artifact 完整性边界。


Next Docs Product Scope And Roadmap (`next/docs/product-scope-and-roadmap.md`)

WePush Next 产品目标、边界与路线图

  • 文档状态:已确认产品方向
  • 更新日期:2026-08-29
  • 适用范围:next/

1. 产品定位

WePush Next 是一套开源、可下载、可安装、可由用户自行部署和运维的消息推送产品。项目的目标是让个人、团队或组织在自己控制的计算、网络、数据库和存储环境中运行 WePush,并自行掌控账号、消息、受众、运行结果、Secret 和 Artifact。

WePush 项目不运营承载用户业务数据的官方集中服务,也不把公共云平台化作为商业或技术演进方向。Standalone、用户自建 Server/HA、远程 Agent、Remote Java SDK 和 Embedded Java SDK 都服务于同一个目标:让用户在自己的环境中完成安装、集成、发送、升级、备份和恢复。

本文档是 WePush Next 产品范围和迭代优先级的主文档。架构、详细设计、ADR、实现状态和部署文档中的范围描述必须与本文一致。

2. 产品原则

2.1 用户掌控

  • 用户自行选择部署主机、网络入口、数据库、对象存储和备份位置。
  • 用户数据和密钥默认保存在用户控制的环境中。
  • 默认安装不依赖 WePush 官方在线控制面、账号系统或托管后端。
  • 除用户配置的消息 Provider、远程 Agent、对象存储和数据库连接外,运行时不产生非必要外部网络请求。
  • 不采集或上传业务数据、受众数据、Secret、运行结果和使用遥测。

2.2 可安装、可升级、可恢复

  • Standalone 必须保持低依赖、低门槛和开箱可用。
  • Server/HA 必须提供清晰的用户自建参考拓扑,而不是依赖官方托管服务。
  • 每次升级必须以可验证备份、版本化迁移、健康检查和明确回滚边界为前提。
  • 安装、升级、备份、恢复和卸载都属于正式产品能力,不是文档外的运维责任。

2.3 单一管理信任域

  • Workspace 用于同一个自建实例内的团队、资源和权限分组。
  • Workspace RBAC、审计和数据 Scope 是正式安全边界。
  • WePush 不把 Workspace 定义为公共 SaaS 租户,不承诺对互不信任或恶意租户提供进程级、数据库级或基础设施级物理隔离。

2.4 可控发布

  • 用户决定何时下载和安装新版本,不进行强制自动更新。
  • 可以提供手动触发或显式启用的版本检查,但不得成为运行前提。
  • Provider 通过发行包或用户本地安装的签名插件交付,不建设官方集中插件市场。

3. 正式产品范围

以下能力属于 WePush Next 的长期正式范围:

  • Windows、Linux 和 macOS Standalone 安装与便携运行。
  • SQLite、本地 Artifact Store 和本地信封加密 Secret Store。
  • 用户自建 PostgreSQL、S3-compatible Artifact Store、多 Service 和远程 Agent。
  • WebUI、Desktop UI、REST/SSE API、OpenAPI、Remote Java SDK 和 Embedded Java SDK。
  • Account、Message、Audience、Job、Schedule、Run、Result、Artifact 和审计管理。
  • 邮件、短信、微信、企业 IM、HTTP 等 Provider 及签名插件扩展机制。
  • 并发、限流、重试、暂停、恢复、取消、幂等、结果未知和失败重发语义。
  • Workspace 逻辑隔离、VIEWER/OPERATOR/ADMIN 权限和 Agent 授权。
  • TLS/mTLS、Secret 最小暴露、Artifact 完整性、安装包来源/校验和/SBOM 和明确签名状态;应用商业代码签名不是发布前提。
  • 用户可执行的升级、备份、恢复、回滚、日志、指标和故障诊断。

PostgreSQL、S3-compatible Store 和多节点 HA 是可选的用户自建形态,不是使用 WePush 的前置条件。S3-compatible 只表示协议兼容,可以由用户在本地或私有环境中运行。

4. 明确且长期不做

以下能力不进入 WePush Next 路线图,也不能以“后续扩展”名义隐式引入:

  • WePush 官方公共 SaaS、集中控制面或承载用户业务数据的托管平台。
  • SaaS 用户自助注册、组织开户、计费、套餐、订阅、账单和支付。
  • 公共租户市场、公共 Provider 市场或依赖官方账号的插件分发。
  • 面向互不信任或恶意租户的进程级、数据库级、主机级物理隔离。
  • 为每个 Workspace 自动创建独立数据库、集群或云资源。
  • 跨区域 Active-Active 公共控制面和全球租户调度。
  • AWS KMS、其他云 KMS、云 Secret Manager、HashiCorp Vault 或操作系统凭据库形式的 Service Secret Store 官方适配器。
  • 强制自动更新、未经用户同意的远程配置下发和使用遥测回传。

SecretStore、数据库和 Artifact Store 保持端口抽象,是为了模块边界、测试和本地实现可替换性,不表示项目计划建设云厂商适配层。Service 的正式 Secret 方案是本地信封加密,Master Key 使用用户控制的受保护文件或显式注入。对象存储建议使用存储端原生 AES256 或由部署方自行管理的存储安全能力;WePush 不负责对接或管理云 KMS。

如果未来有人提出上述能力,应视为改变产品定位,而不是普通功能迭代;在当前产品方向下直接拒绝进入路线图。

5. 当前基线

1.0.0 已形成独立构建的稳定自部署产品:

  • Core API、Provider SPI、虚拟线程 Engine,以及 HTTP、SMTP Email、三类群机器人、阿里云短信、微信公众号、小程序和企业微信应用消息 Provider。
  • Standalone Service、SQLite、Local Artifact、Local Envelope Secret Store。
  • PostgreSQL/S3-compatible Server/HA 参考拓扑和远程 Agent。
  • REST/SSE、OpenAPI、Remote Java SDK 和 Embedded Java SDK。
  • React WebUI、Electron Desktop 和三平台安装脚本。
  • Agent Enrollment、Lease/Fencing、Secret Envelope、Artifact 上传和断线恢复。
  • Account/Message/Audience/Job/Schedule 的编辑、修订、复制、启停与归档。
  • CSV/TXT 流式受众导入、正式发送二次确认、失败项关联重发、分页筛选与真实总览。
  • 三平台含 Java Runtime 完整包、系统 Java 精简包、统一 Standalone/高级分组件安装和离线 Windows Service Wrapper。
  • 正式 Backup/Restore、Payload 文件集合与逐文件内容校验、升级 Installation Health 与失败自动回退。
  • Desktop 本机 Service 运维、原生 Token 安全存储,以及签名插件 Stage/Activate/Rollback。
  • 1.x REST/SSE、Java SDK、配置、数据库迁移、Provider SPI 和 Agent 协议兼容承诺。
  • V13→V14 无损迁移、Beta 成功升级/失败回退、100,000 Recipient 流式执行和跨平台故障门禁。
  • 浏览器 E2E、Desktop 冒烟、三平台恢复/失败升级和 Java 25 长稳矩阵。

当前稳定基线不依赖商业代码签名,不需要也不会扩大公共平台能力。后续 1.x 只在兼容边界内改进自部署体验、可靠性和 Provider 生态。

6. 迭代路线图

版本名称用于表达建议发布边界;如果实际版本号调整,里程碑内容和验收条件保持不变。

6.1 `0.1.0-alpha.2`:方向收口与当前成果发布

目标:在不扩大功能范围的前提下,修正产品边界、安全问题和发布元数据,发布当前已经完成的 Embedded Java SDK。

计划内容:

  • 同步本文档所定义的产品边界到 README、架构、详细设计、ADR、实现状态和部署文档。
  • 清理现有 AWS KMS 配置和实现路径,S3-compatible Store 保留原生 AES256 配置。
  • 修复安全模式下 Artifact 下载未携带 Bearer Token 的问题,使用认证请求下载文件。
  • 将 Embedded Java SDK、Core、Provider SPI、Engine 和 HTTP Provider POM/JAR 纳入正式发行附件。
  • 更新版本号、OpenAPI、CHANGELOG、Release Notes 和 Preview Notice。
  • 通过 Java、UI、SQLite、PostgreSQL、MinIO、三平台 Desktop 和发行归档门禁。

验收条件:

  • 文档不再把云 KMS、外部 Secret Manager 或公共 SaaS 描述为未来产品增量。
  • 安全模式下可以完成 Run、生成结果导出并成功下载 Artifact。
  • alpha.2 发行附件包含 Remote 和 Embedded 两种 Java SDK。
  • Standalone 默认不依赖任何官方在线服务。

6.2 `0.1.0-alpha.3`:日常使用闭环

目标:让普通用户不编写 JSON、不直接调用 API,也能完成完整发送和结果处理。

状态:已完成(2026-08-27)。

已交付内容:

  • Account 编辑、连接测试、启停和归档。
  • Message 编辑并生成新 Revision、历史查看、Diff 和复制。
  • Audience CSV/TXT 流式导入、字段映射、预览、去重、错误行下载和新 Snapshot。
  • Job 编辑、复制、启停和归档;Schedule 支持完整编辑。
  • 正式发送前展示 Provider、账号、受众数量、策略、并发、限速和预计执行规模,并要求二次确认。
  • 从 FAILED、UNKNOWN 和 UNSENT Item 创建有来源关联的新 Run。
  • Run、资源和审计日志增加游标分页、名称、状态和时间筛选。
  • 总览、活动 Run、最近运行和趋势全部接入真实数据。
  • Standalone 隐藏不必要的 Workspace 复杂度;Server UI 支持真实 Workspace 选择。

验收结果:

  • 集成测试已覆盖 UI 背后的 CSV 导入、去重、错误行下载、Dry Run、正式确认发送、结果查看和失败项重发 API 链路。
  • 集成测试直接断言 Message/Audience 更新后,既有 Run Snapshot 仍保存原内容和原 Audience Snapshot ID。
  • Run、资源、Schedule 和审计使用最多 100 条的签名 Cursor Page;启动恢复也按 100 条批次扫描,不再无界载入历史 Run。

6.3 `0.1.0-alpha.4`:真实消息渠道

目标:在 HTTP Provider 之外建立可实际使用的消息渠道组合。

状态:已完成(2026-08-28)。

已交付内容:

  • wepush.email.smtp:无认证或账号认证 SMTP,支持 NONE/STARTTLS/TLS、文本/HTML Multipart、Reply-To、CC/BCC。
  • wepush.bot.feishu、wepush.bot.dingtalk、wepush.bot.wecom:固定官方 Webhook、厂商签名、常用消息类型、Raw JSON 和本地安全限流。
  • wepush.sms.aliyun:用户自有 AccessKey、POP HMAC-SHA1 签名、模板变量、OutId 追踪和业务错误映射。
  • wepush.wechat.official、wepush.wechat.mini、wepush.wecom.app:用户自有 App/Corp 凭据、Session Token 缓存、失效后单次安全刷新,以及任意受支持业务 Payload。
  • 全部 Provider 随 Service、Agent 和发行包内置;Embedded SDK 仍要求调用方显式选择 Provider。
  • WebUI 通过实时 Schema 渲染 SecretRef 和消息默认示例;配置、Recipient 和最小验证步骤见《内置 Provider 指南》。

验收结果:

  • 每个 Provider 都提供 Account、Message、Recipient JSON Schema、SecretRef、结构化字段校验、Dry Run 和真实发送。
  • Provider 模块使用本地 SMTP/HTTP mock 覆盖协议、签名、Token 刷新、错误分类、响应上限和 Secret 不解析 Dry Run;不依赖真实第三方账号。
  • Engine 纵向测试通过 ServiceLoader 发现并逐一执行 8 个标准渠道;Service 和 Agent 测试断言完整 9 Provider Catalog。
  • 重试、幂等、限流、未知结果和 Provider Code 映射已按渠道记录;提交后不确定失败不会自动重发。
  • 生产实现固定厂商官方端点,只有包内测试构造器允许回环 mock;远端响应和带 Token URL 不进入诊断。

Classic 与 Next 可以复用业务需求、测试数据和验收经验,但不建立共享源码依赖。

6.4 `0.1.0-beta.1`:自部署运维成熟

目标:让用户能够长期、可预测地安装和维护 WePush,而不依赖项目维护者远程介入。

状态:已完成(2026-08-28)。

已交付内容:

  • 提供一体化 Standalone 安装体验,并保留便携包和高级分组件安装。
  • 提供包含 Java Runtime 的完整发行包,同时保留使用系统 Java 的精简包。
  • Windows 发行包离线携带经过校验的 Service Wrapper,不在安装时临时下载。
  • Desktop 检测本地 Service,并提供启动、停止、日志和连接诊断入口。
  • 增加正式 Restore 工具和备份内容校验。
  • 升级后自动执行 Readiness、数据库版本和最小 Dry Run 验证;失败时切回旧版本并给出恢复指引。
  • UI 支持本地上传、验证、Stage、Activate 和 Rollback 签名 Provider 插件。
  • Desktop 使用本机安全存储保存 UI Token;浏览器默认只在会话范围保存 Token。
  • 增加浏览器 E2E、Desktop 冒烟、安装/升级/恢复和长时间运行测试矩阵。
  • 清理 JDK 未来兼容警告、Shade 重复资源和发行依赖技术债。

验收条件:

  • Windows、Linux 和 macOS 均能在文档支持的离线条件下完成 Standalone 安装。
  • 从上一预览版本升级失败时,用户数据不丢失并可恢复旧版本运行。
  • 备份恢复后数据库、Master Key、Artifact、Agent Identity、Journal 和 Outbox 一致。

验收结果:

  • Release 流水线在 Linux、macOS、Windows 分别用 JDK 25 jlink 生成完整包;精简包保留系统 Java 路径,Windows 两种包都离线携带固定摘要的 WinSW 2.12.0。
  • 统一安装入口在安装后检查 Installation Health;该健康组同时断言 Flyway 当前版本和内置 HTTP Provider 的本地无网络 Dry Run。
  • 三平台运维自测创建代表数据库、Master Key、Artifact、Agent Identity、Journal、Event/Completion Outbox 和插件的快照,验证文件集合/逐文件摘要、完整恢复,以及强制新版本健康失败后旧版本/数据回退。
  • Desktop IPC 只允许固定的服务、插件和安全存储操作;浏览器 E2E 断言 Token 不进入 localStorage,Desktop 三平台包执行启动冒烟。
  • Java 25 × SQLite/PostgreSQL 的定时长稳矩阵,以及 JDK Native Access 和 Shade 重复资源清理进入发行门禁。

6.5 `1.0.0`:稳定发行

目标:对自部署用户提供明确的兼容性、安全和运维承诺。

状态:已完成(2026-08-29)。

已交付内容:

  • 明确 API、配置和数据库迁移兼容策略。
  • 至少支持从最近一个 Beta 无损升级,并记录最低可回滚版本。
  • 三平台安装、升级、备份、恢复和卸载自动化验收通过。
  • 大受众、长时间任务、断网重连、磁盘不足、进程崩溃和 Agent 失联测试通过。
  • 核心 Provider 具备稳定的限流、幂等、错误分类和重试语义。
  • macOS 和 Windows 发行物明确保持未使用商业代码签名;通过项目 GitHub Release、统一 SHA-256、SBOM 和未签名告知建立可验证交付,更新仍由用户主动触发。
  • 默认运行不存在未声明的外部网络依赖或数据回传。
  • 安全、部署、升级、恢复、Provider 开发和用户指南完整且相互一致。

验收结果:

  • 0.1.0-beta.1 OpenAPI 忽略版本号后保持精确一致,Beta 配置键逐项进入自动兼容门禁。
  • SQLite 和 PostgreSQL 都从 V13 创建用户数据后迁移到 V14,并验证原数据、版本和兼容元数据。
  • Linux、macOS、Windows 运维自测覆盖成功升级、失败自动回退、完整恢复、默认卸载保留数据和显式 Purge。
  • Core 自动执行 100,000 Recipient 流式任务;Agent 测试覆盖断网重放、进程崩溃后的 UNKNOWN、Outbox 上限和模拟磁盘写失败。
  • Java 25 × SQLite/PostgreSQL 长稳、浏览器 E2E、三平台 Desktop 启动和发行归档验证进入 CI/Release 门禁。

6.6 `1.1.0`:自部署治理与 Provider 生态

目标:在保持 1.x 兼容承诺、默认离线和用户自建边界的前提下,增强运营商短信接入、Server 资源治理、故障诊断、HA 唤醒、大 Artifact 和 WebUI 使用体验。

状态:已完成(2026-08-30)。

已交付内容:

  • 将 CMPP、SMGP、SGIP 和 SMPP 实现为独立签名 Provider 插件;可以参考 Classic 的业务需求和测试经验,但不建立源码或构建依赖。
  • 增加 Workspace 级 Agent 数量、并发 Run、总发送并发、Artifact 容量和默认保留期策略,并在 API、SDK、WebUI、调度与运行入口执行一致门禁。
  • 提供可下载的脱敏诊断包、更多用户自建部署模板,以及默认关闭、仅由用户手动触发的版本检查。
  • 增加跨 Run 的账号认证失败熔断;在 PostgreSQL Server/HA 中以 LISTEN/NOTIFY 优化新 Run、Agent 命令和 SSE 事件唤醒,同时保留数据库轮询和 Outbox 作为正确性事实源。
  • 将 Agent Artifact 上传从单次 Presigned Put 扩展为可恢复的 Presigned Multipart Plan;原有单次上传契约继续兼容。
  • 增加暗色主题,改善低分辨率布局、键盘操作、可见焦点、语义标签和颜色对比。

验收条件:

  • 四种运营商协议均有独立插件包、协议编解码、连接测试、Dry Run、错误分类、边界校验和本地模拟网关测试;未安装插件时不增加 Service/Agent 核心依赖。
  • Workspace 配额在单 Service 和多 Service 竞争下都不会超发;拒绝结果可诊断且进入审计,策略更新保持向后兼容。
  • 诊断包不包含 Token、Secret、完整 Recipient 或未脱敏 Provider 响应;版本检查不会后台运行或回传遥测。
  • 跨 Run 熔断可自动恢复和由管理员安全复位;丢失 PostgreSQL 通知时轮询仍能推进所有状态。
  • Agent 可上传超过 1 GiB 的 Artifact,支持分片重试、Complete/Abort、SHA-256/大小校验和进程失败清理。
  • WebUI 在亮色/暗色、1366×768 和键盘操作下通过自动化与人工验收,关键文本和控件满足既定对比度要求。
  • OpenAPI、Remote Java SDK、配置、数据库迁移和 Agent 协议变更满足 1.x 兼容策略,并通过 Java、UI、SQLite、PostgreSQL、MinIO、Desktop 与发行归档门禁。

验收结果:

  • 四种运营商协议均以独立 Maven/PF4J 模块构建,签名包由 Agent 生产校验器验签;本地网关测试覆盖登录、短短信和中文长短信,核心发行物不增加这些协议依赖。
  • V15 Workspace Policy 在 Agent 注册、Run 创建/领取、总发送并发、Artifact 创建和保留期清理入口执行;API、SDK 和 WebUI 提供一致的查看与更新能力。
  • 脱敏诊断、手动版本检查、Nginx/Traefik/Kubernetes 模板、V16 跨 Run 认证熔断及管理员复位已经交付。
  • PostgreSQL 对待调度 Run、Agent Outbox 和 Run Event 发布通知,所有订阅者继续保留周期扫描、持久 Outbox 和事件游标恢复。
  • V17 记录 Multipart 会话;Agent 支持分批获取 Part URL、重试、Complete/Abort、完整性检查与孤儿清理,S3 单对象上限为 5 TiB。
  • WebUI 的主题、低分辨率、键盘焦点、语义和减少动画进入类型检查、组件测试、构建与浏览器 E2E 门禁。

6.7 `1.2.0`:AI 助手接入与 Java 25

目标:在不改变 1.x HTTP、数据库和 Agent 协议的前提下,让用户用自己的 AI 客户端操作已部署的 WePush,并把运行基线统一到 Java 25。

状态:已完成(2026-09-26)。

已交付内容:

  • Desktop 一键安装 Codex MCP 与 Skill,WebUI 和发行包提供同一个 wepush-ai.mjs。桥接器由 AI 客户端以 stdio 启动,只调用用户自己的 REST API。
  • 查询、创建草稿、Dry Run、预览确认后的正式发送,以及结果查询和运行控制。正式发送继续要求确认令牌、幂等键和 Workspace 权限;安装器不复制登录 Token。
  • Java 构建与运行基线升级到 25。完整包携带 Java 25 运行时;精简包和 Java SDK 要求系统 Java 25+。

验收结果:

  • 不新增 Flyway 迁移,OpenAPI 在忽略版本号后保持既有兼容摘要。
  • AI 安装、连接和工具调用进入安装器测试;发行归档包含 ai/wepush-ai.mjs。
  • 浏览器 E2E 覆盖设置页的 AI 助手接入入口。

7. 优先级规则

后续任务使用以下顺序决策:

  1. 数据安全、发送正确性和可恢复性问题优先于新增功能。
  2. 安装、升级、备份和恢复优先于公共平台能力。
  3. 完整用户闭环优先于一次增加大量 Provider。
  4. 每个 Provider 的可验证质量优先于渠道数量。
  5. Standalone 简单体验优先,同时保持用户自建 Server/HA 的清晰路径。
  6. 默认离线、自包含和无遥测优先于依赖外部控制服务的便利性。

8. 完成定义

路线图中的功能只有同时满足以下条件才算完成:

  • 有明确的用户场景、权限行为和失败语义。
  • API、Schema、SDK 和 UI 同步更新。
  • 单元、契约、集成或 E2E 测试覆盖与风险匹配。
  • 不泄露 Secret、受众数据和敏感 Provider 响应。
  • Standalone 不增加未声明的在线依赖。
  • Server/HA 仍可由用户在自己的环境中部署和恢复。
  • 安装、升级和数据迁移影响已经记录。
  • 用户指南、部署文档、实现状态和 CHANGELOG 同步更新。

9. 规划维护

  • 每次版本发布后更新本文件的完成状态和下一里程碑。
  • 新功能进入实现前必须确认它属于第 3 节正式范围,且不违反第 4 节长期非目标。
  • 产品范围与其他文档冲突时,先按本文纠正文档,再开始实现。
  • 任何会把 WePush 变成官方托管公共平台的提案,均视为产品定位变更,不进入普通需求评审。

Next Docs Provider Guide (`next/docs/provider-guide.md`)

WePush Next 内置 Provider 指南

本文适用于 1.2.0,说明内置 Provider 的最小配置、Recipient 字段、发送语义和故障处理。所有渠道都连接用户自己的服务或渠道账号;WePush 不代注册账号、不转售消息额度、不代收款,也不把凭据上传到官方公共平台。CMPP、SMGP、SGIP、SMPP 不属于内置 Provider,而是独立签名 Agent 插件;协议能力、构建和安装见 plugins/README.md及《部署与运维》。

1. 共同行为

Service 和 Agent 随发行包内置以下 Provider:

Provider ID 渠道 实现版本
wepush.http 通用 HTTP 0.1.0
wepush.email.smtp SMTP Email 0.1.0
wepush.bot.feishu 飞书 / Lark 群机器人 0.1.0
wepush.bot.dingtalk 钉钉群机器人 0.1.0
wepush.bot.wecom 企业微信群机器人 0.1.0
wepush.sms.aliyun 阿里云短信 0.1.0
wepush.wechat.official 微信公众号 0.1.0
wepush.wechat.mini 微信小程序 0.1.0
wepush.wecom.app 企业微信应用消息 0.1.0

Provider 实现版本与 WePush 产品版本独立。创建 Account、Message 或 Run 时,应使用 Provider Catalog 返回的精确版本。

1.1 SecretRef

密码、Webhook URL、签名密钥、AccessKey Secret、AppSecret 和 CorpSecret 必须保存到当前自部署实例的 Secret Store,并在 Account 中只写引用:

json
{"namespace":"smtp","name":"password","version":"v1"}

本地 Secret API 路径为 /api/v1/workspaces/{workspaceId}/secrets/{namespace}/{name}/versions/{version}。响应、日志、Provider metadata 和错误诊断都不返回 Secret 明文或带令牌的完整远端响应。

1.2 模板、Dry Run 与重试

  • 文本字段使用 {{fieldName}} 引用 Recipient 字段;JSON 模板中的变量应放在 JSON 字符串内。
  • 所有本页渠道支持 Dry Run。Dry Run 会完成配置、Recipient、模板和 Payload 校验,但不会解析 Secret、获取 Access Token 或访问网络。
  • 除明确声明外,外部渠道不提供 Exactly Once。Engine 只重试 Provider 明确标记为 retryable 的确定失败;提交后发生超时、连接中断或服务端 5xx 时结果记为 UNKNOWN,不会自动重发,以避免重复消息。
  • idempotencyKey 只是运行追踪信息。阿里云短信会把最多 64 个字符写入 OutId;它不等同于服务端去重。企业微信应用消息默认启用渠道自身的短窗口重复检查。
  • Provider Descriptor 的最大并发是安全上限,Job 的并发和速率策略仍应根据用户自己的渠道配额进一步收紧。

2. SMTP Email

最小 Account:

json
{
  "host":"smtp.example.com",
  "port":587,
  "security":"STARTTLS",
  "username":"[email protected]",
  "password":{"namespace":"smtp","name":"password","version":"v1"},
  "fromAddress":"[email protected]",
  "fromName":"WePush"
}

不需要认证的内网 SMTP 必须同时省略 username 和 password。security 可取 NONE、STARTTLS、TLS;不要在不受信网络使用 NONE。

Message 与 Recipient:

json
{"subjectTemplate":"Hello {{name}}","textBodyTemplate":"Welcome {{name}}","htmlBodyTemplate":"<strong>Welcome {{name}}</strong>"}
json
{"email":"[email protected]","name":"Alice"}

同时提供文本和 HTML 时发送 multipart/alternative。连接测试会执行 SMTP 握手和认证,但不发送邮件。Provider 使用单一串行 Transport,避免同一 SMTP 会话被并发破坏;服务端拒绝 Recipient 归类为 RECIPIENT_INVALID,认证失败不重试,连接建立前失败可重试,提交后 I/O 不确定记为 UNKNOWN。SMTP 不提供幂等保证。

3. 群机器人

三个机器人都把完整 Webhook URL 存为 SecretRef,生产实现只接受各厂商官方 HTTPS 域名和路径,从而避免把 Secret 或消息转发到任意主机。

3.1 飞书 / Lark

json
{
  "webhook":{"namespace":"feishu","name":"webhook","version":"v1"},
  "signingSecret":{"namespace":"feishu","name":"signing-secret","version":"v1"},
  "keyword":"optional-keyword"
}
json
{"type":"TEXT","contentTemplate":"Build {{build}} completed","mentionField":"openId"}

支持 TEXT、POST、INTERACTIVE 和 RAW;INTERACTIVE / RAW 使用 rawJsonTemplate。Recipient 可提供 openId。本地安全限流为每秒 5 次且每分钟 100 次,Payload 上限 20 KiB。签名按飞书自定义机器人协议生成。参见飞书自定义机器人文档。

3.2 钉钉

json
{
  "webhook":{"namespace":"dingtalk","name":"webhook","version":"v1"},
  "signingSecret":{"namespace":"dingtalk","name":"signing-secret","version":"v1"}
}
json
{"type":"MARKDOWN","titleTemplate":"Build {{build}}","contentTemplate":"Deployment completed","mentionField":"mobile"}

支持 TEXT、MARKDOWN、LINK、ACTION_CARD 和 RAW。Recipient 可提供 mobile。本地安全限流为每分钟 20 次,Payload 上限 20 KiB。启用加签时,时间戳和 HMAC-SHA256 签名只在请求时生成。

3.3 企业微信群机器人

json
{"webhook":{"namespace":"wecom-bot","name":"webhook","version":"v1"}}
json
{"type":"TEXT","contentTemplate":"Hello {{name}}","mentionField":"mobile"}

支持 TEXT、MARKDOWN 和 RAW,Recipient 可提供 mobile。本地安全限流为每分钟 20 次,Payload 上限 20 KiB。企业微信群机器人不使用独立 signingSecret。

机器人 HTTP 429 或业务响应中的频率限制归类为 RATE_LIMITED;签名、Token 或 Key 被拒绝归类为认证错误;提交后超时、I/O 或 5xx 记为 UNKNOWN。机器人渠道不提供幂等保证。

4. 阿里云短信

Account:

json
{
  "accessKeyId":"LTAI...",
  "accessKeySecret":{"namespace":"aliyun","name":"access-key-secret","version":"v1"},
  "regionId":"cn-hangzhou"
}

建议为自建 WePush 实例创建最小权限 RAM 用户,不要使用主账号 AccessKey。生产端点固定为 https://dysmsapi.aliyuncs.com/。

Message 与 Recipient:

json
{
  "signName":"你的短信签名",
  "templateCode":"SMS_123456789",
  "templateParamJsonTemplate":"{\"name\":\"{{name}}\",\"code\":\"{{code}}\"}"
}
json
{"phoneNumber":"13800138000","name":"Alice","code":"7312"}

Provider 使用 SendSms 2017-05-25 RPC API 和 HMAC-SHA1 POP 签名。连接测试只验证 Secret 存在和官方端点可达,不消耗短信,也不代表签名、模板或余额一定可用;首次正式发送前必须对测试号码做小范围验证。

MOBILE_* 等号码错误归类为 RECIPIENT_INVALID,模板/签名/参数错误归类为 INVALID_REQUEST,AccessKey/签名错误归类为认证错误,权限错误归类为授权错误,业务频控归类为 RATE_LIMITED。OutId 只用于关联追踪,不承诺去重。参见阿里云发送短信 API和阿里云 RPC 签名机制。

5. 微信公众号与小程序

两者 Account 结构相同,appSecret 必须使用 SecretRef:

json
{
  "appId":"wx...",
  "appSecret":{"namespace":"wechat","name":"app-secret","version":"v1"}
}

公众号支持:

  • TEMPLATE:模板消息;
  • SUBSCRIBE:订阅通知;
  • CUSTOM:客服消息。

小程序支持:

  • SUBSCRIBE:订阅消息;
  • UNIFORM:统一服务消息。

Message 的 payloadJsonTemplate 是各微信接口的业务 JSON,Provider 会覆盖 touser,因此不要把 Access Token 或目标 OpenID 写进模板:

json
{
  "type":"TEMPLATE",
  "payloadJsonTemplate":"{\"template_id\":\"TEMPLATE_ID\",\"data\":{\"name\":{\"value\":\"{{name}}\"}}}"
}

Recipient:

json
{"openId":"openid...","name":"Alice"}

Provider 在 Session 内缓存 Access Token,并提前 5 分钟刷新。只有远端明确返回 Token 无效或过期代码时,才刷新 Token 并安全重放一次;普通超时或 5xx 不重放消息。无效 OpenID、未关注或未订阅归类为 RECIPIENT_INVALID,API 权限/IP 白名单问题归类为授权错误,模板数据问题归类为 INVALID_REQUEST,配额限制归类为 RATE_LIMITED。连接测试会真实获取一次 Access Token,但不会发送消息。参见微信公众号 Access Token、公众号模板消息和小程序订阅消息。

6. 企业微信应用消息

Account:

json
{
  "corpId":"ww...",
  "corpSecret":{"namespace":"wecom","name":"corp-secret","version":"v1"},
  "agentId":100001
}

payloadJsonTemplate 可使用企业微信应用消息接口支持的任意 msgtype 和对应内容;Provider 负责覆盖目标、agentid 和重复检查字段:

json
{
  "type":"APP",
  "payloadJsonTemplate":"{\"msgtype\":\"text\",\"text\":{\"content\":\"Hello {{name}}\"}}",
  "enableDuplicateCheck":true,
  "duplicateCheckInterval":1800
}

每条 Recipient 至少包含 userId、partyId 或 tagId 之一,分别映射为 touser、toparty、totag:

json
{"userId":"alice","name":"Alice"}

Token 缓存、单次安全刷新和错误策略与微信 Provider 相同。默认开启企业微信 30 分钟重复检查;该机制由企业微信定义,不等同于跨时间、跨模板的 Exactly Once。参见企业微信发送应用消息。

7. 验证顺序

对每个新账号按以下顺序操作:

  1. 在 Secret Store 写入凭据,只在 Account 中保存 SecretRef。
  2. 在 Provider 页面保存 Account,执行连接测试并阅读返回代码;阿里云短信连接测试不验证模板和额度。
  3. 在 Message 页面从 Schema 默认示例开始,替换真实模板 ID、签名和内容。
  4. 导入 1 至 3 个自有测试 Recipient,先运行 Dry Run。
  5. 将并发和速率设置为渠道配额以内,核对正式发送确认页后小范围发送。
  6. 在 Run Item 中按 providerCode、category、retryable 和 UNKNOWN 语义处理失败;不要盲目重发 UNKNOWN。

本地 mock、协议签名、错误映射、Secret 不解析 Dry Run、Service/Agent 发现和 Engine 纵向测试均随源码测试套件执行。Classic 只作为需求与验收经验来源,Next Provider 不依赖 Classic 源码。


Next Docs Upgrade Guide (`next/docs/upgrade-guide.md`)

WePush Next 升级与回滚指南

本文以 1.1.0 升级到 1.2.0 为当前路径,也适用于 0.1.0-beta.1 或 1.0.0 经稳定版升级和后续 1.x Minor/Patch 升级。完整兼容承诺见《兼容性策略》。

1.2.0 将 Java 基线升级至 25,且不新增数据库迁移。精简包部署与 Java SDK 使用方须先将系统 Java 升级至 25+;完整包使用随包提供的 Java 25 运行时。旧版发布说明中的 Java 21 要求仅适用于 1.1.0 及更早版本。

1. 升级前检查

  1. 确认当前版本至少为 0.1.0-beta.1,推荐先处于 1.1.0,且 Installation Health 为 UP。使用精简包或 Java SDK 时,先安装 Java 25+。
  2. 确认磁盘有足够空间同时容纳当前数据、备份和新发行包;建议可用空间不低于数据目录大小的两倍加发行包大小。
  3. 从 GitHub Release 下载正确平台/架构的 1.2.0 包和 SHA256SUMS,完成 SHA-256 校验。
  4. 阅读 UNSIGNED-NOTICE.md。macOS/Windows 发行物没有商业代码签名。
  5. 暂停新 Schedule 或选择业务低峰。正在运行的外部发送必须先完成或由操作员确认结果处理方式。

2. 自动备份与升级

升级脚本会在切换版本前创建完整备份,安装新版本后检查 Readiness、Flyway 当前版本和内置 HTTP Provider 的无网络 Dry Run。任一步失败都会恢复旧版本链接和升级前数据。

Linux:

bash
sudo /opt/wepush-next/current/install/linux/upgrade.sh \
  wepush-next-1.2.0-linux-x64.tar.gz <sha256>

macOS:

bash
sudo /Library/WePushNext/current/install/macos/upgrade.sh \
  wepush-next-1.2.0-macos-arm64.zip <sha256>

Windows(管理员 PowerShell):

powershell
& "$env:ProgramFiles\WePush Next\current\install\windows\upgrade.ps1" `
  -Archive .\wepush-next-1.2.0-windows-x64.zip `
  -ExpectedSha256 <sha256>

3. 升级后验证

  • GET /actuator/health/readiness 返回 UP。
  • GET /actuator/health/installation 显示数据库迁移和内置 Provider Dry Run 通过。
  • GET /api/v1/system/info 返回 1.2.0。
  • Installation Health 显示 Flyway 当前版本为 V17;workspace_policy、account_auth_circuit 和 artifact_multipart_upload 已创建。1.2.0 不新增迁移。
  • 原 Workspace、Account、Message、Audience、Job、Schedule、历史 Run 和 Artifact 可读取。
  • Agent 重新连接,Provider Catalog 完整;如使用超过 1 GiB Artifact 或运营商插件,确认 Agent 也为 1.2.0 并已配置本版本发行签名公钥。先执行 Dry Run,再用自有测试目标执行小规模真实发送。
  • 需要 AI 助手时,在 Desktop 设置 → AI 助手接入 重新安装 MCP/Skill,或使用发行包中的 ai/wepush-ai.mjs。旧的 1.1.0 安装包没有该安装器。
  • Server/HA 确认周期扫描持续工作,并在 PostgreSQL 日志/指标中检查通知链路;LISTEN/NOTIFY 失效不得阻断 Run、Agent 命令或 SSE 推进。
  • 备份文件仍保留在默认备份目录,且可用 Restore 的 --validate-only / -ValidateOnly 验证。

4. 回滚

升级健康检查失败时脚本会自动回滚。升级成功后如需人工回滚,先停止 Service/Agent,再使用升级前备份恢复;不要只替换 JAR 而保留未经确认的新数据库。

1.2.0 不新增表。从 1.1.0 升级时,受支持的回滚目标是 1.1.0;若数据库曾经过 1.1.0 的 V15–V17,不能把只替换 JAR 当成回滚到 1.0.0。无论目标版本如何,都必须通过升级前完整备份恢复。备份同时覆盖数据库、Master Key、Artifact、Agent Identity、Journal、Event/Completion Outbox 和插件。

Restore 会:

  • 拒绝路径穿越、非普通文件、未列入摘要的 Payload 和内容摘要不一致;
  • 在替换前保留 pre-restore-* 原目录;
  • 恢复后运行 Installation Health;
  • 健康检查失败时恢复操作前数据。

5. 卸载

默认卸载只删除服务注册和程序文件,保留配置与数据。只有显式使用 --purge(Linux/macOS)或 -Purge(Windows)才删除用户数据。执行 Purge 前必须自行保存需要保留的备份。

三平台默认卸载、数据保留和显式 Purge 都进入发行自动化门禁。


Next Docs User Guide (`next/docs/user-guide.md`)

WePush Next 对外使用指南

WePush Next 是由用户自行下载、安装、部署和运维的开源产品,不依赖官方公共 SaaS、账号注册、计费订阅或云 KMS/Secret Manager。数据、密钥和运行环境均由用户掌控;完整定位和后续计划见《产品目标、边界与路线图》。

本文面向第一次下载和使用 WePush Next 的用户,适用于 1.2.0 稳定自部署版。

1.2.0 提供明确的 1.x API、配置、数据库和 Agent 协议兼容承诺。Desktop 与发行包未使用商业代码签名;请只从项目 GitHub Release 下载,并在运行前校验 SHA256SUMS。详见《兼容性策略》和 UNSIGNED-NOTICE.md。

1. 先选择需要的组件

WePush Next 由多个可以独立使用的组件组成:

组件 用途 第一次体验是否需要
Service 保存配置和运行数据,提供 WebUI、OpenAPI 与 Service API,并可在本机执行任务 必需
WebUI 浏览器中的可视化配置、运行中心、动态 API 文档 已包含在 Service 发行包中
Desktop UI Electron 桌面管理界面,连接并管理已安装的本机 127.0.0.1:18990 Service 可选
Agent 从 Service 接收任务并在独立节点执行,适合远程或分布式部署 本地体验不需要
Remote Java SDK 供其他 Java 应用通过 Service API 集成 WePush 按需,1.2.0 附件已包含
Embedded Java SDK 在 Java 应用进程内直接装配 Engine 和 Provider,不需要 Service 按需,1.2.0 附件已包含
Core / Engine Service 与 Agent 内部的执行引擎 不需要单独安装

发行包的统一脚本负责安装 Standalone Service;Desktop UI 不重复内嵌 Service,但可以检测、启动、停止已安装的本机 Service,读取日志和生成脱敏诊断。

Classic 与 Next 是彼此独立的两条产品线。Classic 继续提供成熟的桌面批量推送能力;Next 当前内置 HTTP、SMTP Email、飞书/钉钉/企微机器人、阿里云短信、微信公众号、小程序和企业微信应用消息。两者没有自动迁移或共享数据要求,可以并行安装和使用。

2. 下载与系统要求

官方下载页:

发行附件:

文件 内容或平台
wepush-next-1.2.0-linux-<arch>.tar.gz Linux 完整包,内含 Java Runtime,推荐首次安装
wepush-next-1.2.0-macos-<arch>.zip macOS 完整包,内含 Java Runtime,推荐首次安装
wepush-next-1.2.0-windows-<arch>.zip Windows 完整包,内含 Java Runtime 和离线 WinSW
wepush-next-1.2.0.tar.gz / .zip 使用系统 Java 25+ 的精简便携包;内容不含 Java Runtime
wepush-next-desktop-1.2.0-<os>-<arch>.* 对应平台的 Desktop 管理界面
wepush-next-java-sdk-1.2.0.zip Remote Java SDK、Embedded Java SDK 及其 POM/JAR 依赖闭包
wepush-provider-{cmpp,smgp,sgip,smpp}-1.2.0.zip 可选运营商短信 Agent 签名插件;各自附带 .sha256
wepush-provider-trusted-key-1.2.0.env 官方插件签名 Key ID/公钥的 Agent 环境配置;附带 .sha256 并列入统一 SHA256SUMS
SHA256SUMS 全部附件的 SHA-256
wepush-next-1.2.0-sbom.cdx.json CycloneDX SBOM

完整包运行 Service/Agent 不要求预装 Java。精简包、从源码构建和任一种 Java SDK 需要 Java 25 或更高版本:

bash
java -version

发行工作流按目标 Runner 架构生成附件;文件名中的 <arch> 以 Release 实际列出的 x64 或 arm64 为准。

校验下载文件

下载 SHA256SUMS 后,计算目标文件的 SHA-256,并与其中同名行比较:

Linux:

bash
sha256sum wepush-next-1.2.0.tar.gz
grep 'wepush-next-1.2.0.tar.gz$' SHA256SUMS

macOS:

bash
shasum -a 256 wepush-next-1.2.0.tar.gz
grep 'wepush-next-1.2.0.tar.gz$' SHA256SUMS

Windows PowerShell:

powershell
Get-FileHash .\wepush-next-1.2.0.zip -Algorithm SHA256
Select-String -Path .\SHA256SUMS -Pattern 'wepush-next-1.2.0.zip$'

两个值必须完全一致。Desktop 包也使用相同方法校验。

3. 五分钟安装:Standalone

Standalone 默认只监听 127.0.0.1:18990,使用 SQLite、本地 Artifact Store 和内嵌 Engine,不需要 PostgreSQL、对象存储或 Agent。

下载当前操作系统的完整包并解压。统一入口默认只安装 Standalone Service;安装末尾会验证 Readiness、数据库迁移版本和不会访问网络的 Provider Dry Run。

Linux

bash
tar -xzf wepush-next-1.2.0-linux-x64.tar.gz
cd wepush-next-1.2.0
sudo ./install/install.sh

macOS

bash
ditto -x -k wepush-next-1.2.0-macos-arm64.zip .
cd wepush-next-1.2.0
sudo ./install/install.sh

Windows PowerShell

powershell
Expand-Archive .\wepush-next-1.2.0-windows-x64.zip .\wepush-next
Set-Location .\wepush-next\wepush-next-1.2.0
Set-ExecutionPolicy -Scope Process Bypass
& .\install\install.ps1

安装完成后打开:

  • WebUI:http://127.0.0.1:18990/
  • 健康检查:http://127.0.0.1:18990/actuator/health/readiness
  • OpenAPI:http://127.0.0.1:18990/openapi.yaml

也可以在另一个终端验证:

bash
curl --fail http://127.0.0.1:18990/actuator/health/readiness
curl --fail http://127.0.0.1:18990/api/v1/system/info

需要便携运行而不安装系统服务时,Linux/macOS 在解压目录执行 ./bin/wepush-service;Windows 执行 install\windows\run-service.ps1。便携模式应从解压目录启动,使默认 .local 数据与该目录保持在一起。完整包优先使用内含 Runtime,精简包自动使用系统 Java。

4. 可选:使用 Desktop UI

先按上一节安装 Service,再解压对应平台的 Desktop 附件:

  • Linux:运行 wepush-next-desktop-1.2.0-linux-x64/WePush Next/wepush-next。
  • macOS:打开 wepush-next-desktop-1.2.0-macos-arm64/WePush Next.app。
  • Windows:运行 wepush-next-desktop-1.2.0-windows-x64\WePush Next\WePush Next.exe。

Desktop 会连接本机 http://127.0.0.1:18990。设置页可以检测、启动、停止 Service,读取最近日志并生成不包含 Token/Secret 的诊断;涉及系统服务或插件目录的写操作会请求操作系统管理员授权。

Desktop 的 API Token 通过 Electron safeStorage 写入操作系统原生安全存储;如果 Linux 桌面没有可用 Keyring,Desktop 会拒绝以弱后端持久化。浏览器 WebUI 默认只写 sessionStorage,关闭标签页后即清除,不再使用长期 localStorage。

由于发行包没有商业签名,macOS Gatekeeper 或 Windows SmartScreen 可能显示未知开发者/发布者。只有在文件来自项目 Release 且 SHA-256 校验一致时,才使用系统提供的“打开”流程;不要全局关闭系统安全能力。

不想安装 Desktop 时,直接使用浏览器 WebUI 即可,两种 UI 连接的是同一套 Service API 和数据。

4.1 `1.2.0` 设置与主题

  • 顶部主题选择支持亮色、暗色和跟随系统;选择保存在本地浏览器偏好中,不会上传。
  • 系统管理员可在设置页为当前 Workspace 设置 Agent、活动 Run、总发送并发、Artifact 容量和默认保留期。降低上限不会自动删除资源;超过新上限时,新增操作会给出可诊断拒绝。
  • “生成诊断包”会由 Service 返回结构化脱敏 ZIP;仍应把它视为内部运维资料,不要公开上传。
  • “检查版本”只有点击时才查询 GitHub 的稳定 next-v* Release,不会后台检查、自动下载或发送遥测。
  • Account 的认证熔断面板显示跨 Run 认证失败和冷却状态。先修正 Secret/凭据,再由管理员复位;网络失败、限流或 Recipient 错误不会计入认证熔断。
  • 运营商短信使用 Release 中独立的 CMPP/SMGP/SGIP/SMPP 签名插件。安装前校验 SHA256SUMS 并配置发行公钥;这些插件当前处理主动提交和长短信,不处理状态报告或上行短信。
  • 设置 → AI 助手接入 可安装 Codex MCP 与 Skill,或下载 wepush-ai.mjs。正式发送仍需预览确认。说明见《AI 助手接入指南》。

5. 完成第一次安全 Dry Run

下面的流程只验证 Account → Message → Audience → Job → Engine → Provider 全链路。HTTP Provider 的 Dry Run 不会向配置的远端地址发送 HTTP 请求。

5.1 创建 HTTP 账号

  1. 打开 Providers。
  2. 选择内置的 HTTP Provider。
  3. 账号名称填写 Preview HTTP。
  4. baseUrl 填写 https://example.com。
  5. connectTimeout 保持 PT5S。
  6. allowPrivateAddresses 保持关闭,auth 保持 NONE 或不配置。
  7. 点击“保存账号”。

只有明确受信的内部 HTTP 服务才应启用 allowPrivateAddresses。默认关闭可避免任务访问本机、局域网和云元数据地址。

5.2 创建消息

进入“消息”,选择 HTTP Provider,并填写:

  • 名称:Preview message
  • 方法:POST
  • 路径:/messages
  • Headers:{"Content-Type":"application/json"}
  • Body Template:
json
{
  "name": "{{name}}",
  "mobile": "{{mobile}}"
}

模板变量使用 {{fieldName}},运行时从每个 Recipient 的 fields 中取值。保存消息后会形成不可变修订。

5.3 创建受众

进入“受众”,新建 UTF-8 CSV 文件:

csv
itemId,mobile,name
alice,13000000000,Alice
bob,13100000000,Bob
  1. 填写受众名称并选择 CSV;TXT 也受支持,每个非空行作为一条 Recipient。
  2. 选择 itemId 列,把 mobile、name 映射到同名 Recipient 字段。
  3. 点击“上传并预览”,核对总行数、接受数、错误数和重复数。
  4. 有错误时先下载错误行 CSV;重复 itemId 只保留首次出现的记录。
  5. 点击“确认并生成 Snapshot”。

itemId 应在同一受众中保持稳定且唯一;字段必须覆盖消息模板引用的变量。更新已有受众时请选择目标受众,提交后会生成新的不可变 Snapshot,不会改变既有 Run。

5.4 创建任务并运行

  1. 进入“任务与调度”。
  2. 选择刚创建的账号、消息和受众。
  3. 保持较小的目标并发,例如 2。
  4. 保存任务。
  5. 在右侧任务卡片点击 Dry Run。
  6. 进入“运行中心”,查看状态、计数、事件和每条 Recipient 的结果。

正常情况下,两条结果会以 Provider Code DRY_RUN 成功完成。该过程不会请求 example.com/messages。

6. 正式 HTTP 发送

完成 Dry Run 后,在任务卡片点击“正式发送”。界面会先展示 Provider/版本、账号、受众及数量、完整策略、目标并发、限速和预计执行规模。只有再次点击“我已核对,开始发送”才会创建真实 Run;确认令牌五分钟有效,并绑定当前资源版本,资源变化后必须重新核对。

将 dryRun 设为 false 会真实访问 HTTP 目标。请先使用专用测试端点、小范围受众和服务端幂等机制验证,不要把上面的 example.com 示例用于正式运行。

运行结束后进入“运行中心”。PARTIAL 或 FAILED Run 可以选择 FAILED、UNKNOWN、UNSENT 状态并点击失败重发;Service 会显示匹配 Item 数量,再创建带来源 Run ID 的新 Run。重发使用来源 Run 的冻结 Snapshot,不会混入之后编辑的 Message 或 Audience。

直接调用 API 时同样必须先获取确认上下文:

bash
curl --request POST \
  http://127.0.0.1:18990/api/v1/workspaces/ws_default/jobs/<JOB_ID>/run-confirmation

curl --request POST \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: replace-with-a-unique-value' \
  --data '{"dryRun":false,"policyOverrides":{},"reason":"manual-live-run","confirmationToken":"<TOKEN_FROM_PREVIOUS_RESPONSE>"}' \
  http://127.0.0.1:18990/api/v1/workspaces/ws_default/jobs/<JOB_ID>/runs

开启 API Security 后还需添加:

text
Authorization: Bearer <TOKEN>

HTTP Provider 不跟随重定向,默认阻止私有地址,限制响应体大小,并支持配置成功状态码与幂等 Header。正式接入前应按目标系统的认证、限流、超时和幂等约定完成配置。

SMTP、机器人、短信和微信系渠道使用相同的 Account → Message → Audience → Job → Dry Run → 正式确认流程。每个渠道的最小 Account、Message、Recipient、SecretRef、连接测试边界、限流和 Provider Code 处理见《内置 Provider 指南》。所有正式发送都使用用户自己的渠道账号与额度,WePush 不提供集中账号、代充值或平台代理发送。

7. API 文档与 Java SDKs

当前源码还提供 设置 → AI 助手接入,可一键安装 Codex MCP + Skill 或导出其他客户端的 MCP 配置。完整步骤见《AI 助手接入指南》,需要使用包含此功能的新构建。

WebUI 的“API 文档”会读取 Service 提供的 OpenAPI,支持查看请求 Schema、生成请求和动态调试。写操作发送前会二次确认;权限、审计和同源限制仍由 Service 强制执行。

Remote Java SDK 是 Service API 客户端,不依赖 Core、Engine 或具体 Provider。beta.1 发行包和独立 SDK 压缩包都包含它,安装说明见 Java SDK README。

最小示例:

java
try (var client = WePushClient.builder()
        .endpoint(URI.create("http://127.0.0.1:18990"))
        .build()) {
    var system = client.system().info();
    var providers = client.providers().list();
    var workspace = client.workspace("ws_default");
    var runs = workspace.runs();
}

Embedded Java SDK 依赖 Core Engine,但不依赖 Service、Agent、数据库或 Spring;应用显式注册允许的 Provider,并直接传入执行快照和 Recipient:

java
var store = new InMemoryExecutionStore();
try (var engine = WePushEngine.builder()
        .provider(new HttpProviderFactory())
        .resultSink(store)
        .eventSink(store)
        .build()) {
    var summary = engine.start(spec, recipients)
            .completion().toCompletableFuture().join();
}

beta.1 发行包和独立 Java SDK 附件同时包含 Embedded SDK 与所需依赖。完整依赖、配置、资源所有权和示例见 Embedded Java SDK README。

8. 何时需要 Agent

默认内嵌执行模式下,Service 自己调用 Core Engine,本地体验不需要 Agent。以下情况再部署 Agent:

  • 任务需要在另一台主机或受控网络中执行。
  • 希望 Service 只承担控制面,执行资源独立扩缩容。
  • 不同执行节点需要安装不同 Provider 插件。
  • 需要借助 Lease、Fencing、心跳和远端 Artifact 回传管理分布式运行。

Agent 使用 Service 的 19090 gRPC 控制流。跨主机部署必须使用 Enrollment、长期 Credential 和 TLS/mTLS,不应沿用回环开发的明文配置。完整步骤见部署与运维文档。

9. 安装为系统服务

统一 install/install.sh 或 install/install.ps1 默认安装 Standalone Service。高级部署可直接调用平台目录下的安装器并传入 service、agent 或 all。

Linux / systemd

bash
tar -xzf wepush-next-1.2.0-linux-x64.tar.gz
sudo ./wepush-next-1.2.0/install/install.sh
systemctl status wepush-next-service

macOS / launchd

从普通登录用户的终端使用 sudo,安装器会将该用户作为非 root 服务用户:

bash
ditto -x -k wepush-next-1.2.0-macos-arm64.zip .
sudo ./wepush-next-1.2.0/install/install.sh
launchctl print system/com.fangxuele.wepush-next.service

无人值守安装需要显式设置已经存在的 WEPUSH_SERVICE_USER。

Windows Service

以管理员 PowerShell 执行:

powershell
Expand-Archive .\wepush-next-1.2.0-windows-x64.zip .\release
Set-ExecutionPolicy -Scope Process Bypass
& .\release\wepush-next-1.2.0\install\install.ps1
Get-Service WePushNextService

Windows 发行包已经携带并再次校验固定版本 WinSW;安装阶段不访问网络。精简包也携带 WinSW,只有 Java 由系统提供。

配置路径、备份、升级、卸载、Server/HA、PostgreSQL、S3 与插件步骤见部署与运维文档。

10. 数据、备份与安全

便携 Standalone 默认保存:

  • SQLite:.local/data/wepush-next.db
  • Secret 主密钥:.local/secrets/master-key.json
  • Artifact:.local/artifacts

系统服务安装后的路径因操作系统而异,具体以部署文档和生成的 service.env 为准。正式 backup 会停止相关服务,写入格式/版本/内容清单和每个文件的 SHA-256,再调用 Restore 校验;restore 会先保留恢复前副本,替换后执行安装健康门,失败则自动恢复原数据。数据库、Secret 主密钥、Artifact、Agent Identity、Journal、Event/Completion Outbox 和插件必须作为一个一致备份恢复。

默认无认证模式仅允许 Service 监听回环地址。不要直接把 127.0.0.1 改成 0.0.0.0。非回环监听必须同时启用 API Security、设置强 Bootstrap Token,并在网络入口配置 TLS;Server 模式还会强制 PostgreSQL、S3-compatible Artifact Store 和 Agent gRPC TLS。

11. 常见问题

UI 显示 Service 未连接

  • 确认 http://127.0.0.1:18990/actuator/health/readiness 可访问。
  • 确认 Java 版本是 21 或更高。
  • 检查 18990 是否被其他程序占用。
  • Desktop 当前只连接本机 127.0.0.1:18990。

Service 启动后看不到 WebUI

  • 使用发行包中的 bin/wepush-service,或按 Windows 便携命令设置 WEPUSH_WEB_ROOT。
  • 确认解压目录下存在 web/index.html。
  • 直接从源码 JAR 启动时需要先构建 WebUI,或单独设置静态资源目录。

HTTP 运行报告 `SSRF_BLOCKED`

目标解析到了私有、回环或受限地址。只有目标确实是受信内部服务时,才在 Account 中开启 allowPrivateAddresses;不要为了绕过未知错误而开启。

API 返回 `401` 或 `403`

Service 已开启安全模式。把 Bootstrap Token 或已签发 Token 填入 WebUI“设置 → 当前 API Token”;命令行请求使用 Authorization: Bearer <TOKEN>。

操作系统阻止 Desktop 首次启动

先核对来源和 SHA-256。校验不一致时立即删除文件;校验一致时使用操作系统针对单个应用提供的确认流程,不要关闭 Gatekeeper、SmartScreen 或终端安全软件。

12. 反馈与进一步文档

普通问题和功能建议可提交 GitHub Issue。安全漏洞不要公开披露,请按 SECURITY.md 中的方式私下报告。


Next Docs Adr 0001 Dual Track Development (`next/docs/adr/0001-dual-track-development.md`)

ADR-0001:Classic 与 Next 双轨独立发展

  • 状态:已接受
  • 日期:2026-08-22
  • 决策者:WePush 项目维护者

背景

当前 WePush 是一个已经稳定运行和发布的 Java Swing 客户端,具备完整的消息配置、用户导入、任务调度和批量推送能力。项目计划在 next/ 目录中建设新的目标架构,包括 Core Engine、Agent、Java SDK、Service、Desktop UI 和 WebUI。

如果直接在现有客户端中持续拆分,会同时影响现有用户、发布节奏和新架构设计。另一方面,为了强制消除重复代码而建立跨项目公共模块,也会重新产生构建、版本和架构耦合,限制两条产品线的独立演进。

因此,需要明确 Classic 与 Next 之间的长期关系,以及对代码重复的处理原则。

决策

WePush 采用 Classic 与 Next 双轨独立发展的模式。

  1. 当前仓库根目录下的客户端视为 Classic,继续按照现有产品定位、技术架构和发布流程独立发展。
  2. next/ 是自包含的新项目,用于实现新的目标架构,并拥有独立的构建入口、模块结构、依赖管理、测试和发布流程。
  3. Classic 与 Next 之间不建立运行时或编译期依赖。Next 不依赖 Classic 的 Java 包、数据库实体、静态工具类或内部实现,Classic 也不依赖 Next 的模块。
  4. 不为了复用代码而建立跨两条产品线的 shared、common 等公共模块,也不使用软链接、共享源码目录或其他隐式源码复用方式。
  5. 两边允许出现相同或相似代码。复制代码是被接受的工程选择;代码一旦复制到另一条产品线,即成为该产品线独立维护的代码,不要求后续自动同步。
  6. 新功能、缺陷修复和安全修复根据实际需要决定进入 Classic、Next 或同时进入两边,不设置强制双向移植规则。
  7. Classic 与 Next 可以采用不同的数据模型、API、依赖版本和具体实现,也允许产品行为逐渐产生差异,不以内部实现兼容为目标。
  8. 两边可以共享需求理解、协议资料、供应商行为样例、设计经验和测试思路,但这些内容按需分别落入各自项目,不形成共享运行时制品。
  9. Classic 主要承担稳定桌面客户端和新消息类型快速实验场景;Next 按新的组件化、服务化目标发展,不为兼容 Classic 的内部架构而妥协。
  10. 两套应用使用独立的版本号、配置目录、数据存储、网络端口和安装标识,确保可以在同一环境中并行安装和运行。

结果

正面影响

  • Classic 的稳定发布不会被 Next 的架构建设阻塞。
  • Next 可以自由选择适合目标架构的模型、协议和技术实现。
  • 两条产品线的依赖、版本和发布边界清晰,局部改动不会隐式影响另一边。
  • 消息类型可以先在 Classic 快速验证,再按 Next 的 Provider SPI 和配置 Schema 重新实现。
  • 避免公共模块逐渐演变为新的耦合中心。

接受的代价

  • 某些 Provider、工具函数、协议适配和缺陷修复可能需要分别实现。
  • 两边的相似代码可能随时间产生差异。
  • 同时维护两条产品线会增加测试、发布和问题跟踪成本。
  • 不能假设在一边完成的修复会自动覆盖另一边。

这些代价是本决策明确接受的,不应仅以“消除重复代码”为理由重新引入跨项目依赖。

实施约束

  • Classic 保持当前根目录构建方式;Next 使用 next/ 下的独立构建入口。
  • CI 按目录和产品线分别执行,任何一边都不能依赖另一边的构建产物才能完成测试或发布。
  • Issue、提交和发布说明应标明适用范围:classic、next 或 both。
  • 从一边复制实现到另一边时,应同时复制或重写必要测试,并按照目标产品线的架构规范完成适配。
  • 数据迁移通过显式迁移工具完成,Next 不直接操作或依赖 Classic 的运行时数据库。
  • 如果未来需要改变本决策、引入共享模块或建立编译期依赖,必须提交新的 ADR 说明收益、边界和迁移方案。

非目标

  • 不要求 Classic 与 Next 在任意时间点保持完整功能对等。
  • 不要求相同消息类型在两边使用相同实现。
  • 不建设自动代码同步机制。
  • 不将 Classic 逐步改造成 Next;两者是独立产品线,而不是临时分支关系。

Next Docs Adr 0002 Technology Baseline (`next/docs/adr/0002-technology-baseline.md`)

ADR-0002:Service、WebUI 与 Desktop 技术基线

  • 状态:已接受
  • 日期:2026-08-22
  • 决策者:WePush 项目维护者

背景

WePush Next 需要同时提供 Service、WebUI 和 Desktop UI。技术基线必须兼顾 Java 团队的开发效率、交互式管理界面的复杂度、Web 与 Desktop 复用、跨平台发布和长期安全更新。

决策

Service

  • Java 基线固定为 Java 25。
  • Service Web 框架固定为 Spring Boot 4.1.x,初始实现版本固定为 4.1.1。
  • 使用 Spring MVC、Spring Security、Actuator、Validation 和 JDBC/DataSource 集成。
  • Service 以可执行 JAR 或应用镜像运行,不部署传统 WAR。
  • Spring 依赖只允许存在于 Service App、Web 和 Infrastructure 层,不进入 Core API、Core Engine、Provider SPI、Agent Runtime、Java SDK 或 Embedded SDK。
  • Spring Boot 小版本和补丁版本可以在兼容测试通过后更新;升级 4.2 或更高特性线必须单独评审。

WebUI

  • 语言固定为 TypeScript,初始基线为 TypeScript 6.0.x,并启用严格模式。
  • 构建工具固定为 Vite 8.1.x。
  • UI 框架固定为 React 19.2.x。
  • 构建运行时固定为 Node.js 24 LTS,不使用非 LTS Node 作为 CI 和正式构建基线。
  • 包管理器固定为 pnpm,通过 Corepack 固定项目使用的精确版本,并提交唯一 Lockfile。
  • 样式基线使用 Tailwind CSS。
  • shadcn/ui 按需引入,不把完整组件集合一次性加入项目。通过 shadcn CLI 引入的组件源码归 WePush Next 自己维护。
  • WebUI 是纯 SPA,通过 Service API 工作,不使用 React Server Components,也不要求 Node.js 作为正式运行时。
  • OpenAPI 生成 TypeScript API Client,业务代码通过手写 Facade 使用生成客户端。

Desktop UI

  • Desktop 外壳固定为 Electron。
  • 初始 Electron 基线固定为 43.x,在创建工程时锁定当时最新的 43.x 安全补丁版本。
  • Desktop 复用 WebUI 的 React 页面、Schema Renderer、API Client 和设计 Token。
  • Electron Main、Preload 和 Renderer 分层;Renderer 不直接获得 Node.js 权限。
  • 强制启用 contextIsolation 和 Renderer Sandbox,关闭 nodeIntegration。
  • Preload 只暴露经过白名单定义、参数校验和来源校验的最小 IPC API。
  • Desktop 只加载发行包内的本地 UI 资源,并通过 GitHub Release 与 SHA256SUMS 验证包完整性;不得加载带 Node 能力的远程页面。应用不以商业代码签名作为发行前提。
  • Electron 每个受支持主版本生命周期较短,项目持续跟随受支持稳定版本,至少每季度评估一次主版本升级。

前端目录

text
next/ui/
├── pnpm-workspace.yaml
├── package.json
├── pnpm-lock.yaml
├── apps/
│   ├── web/
│   └── desktop/
└── packages/
    ├── api-client/
    ├── schema-renderer/
    ├── ui/
    ├── features/
    └── design-tokens/

apps/web 和 apps/desktop 可以共享 packages/*,但共享仅发生在 Next 内部,不影响 Classic 的独立性。

结果

正面影响

  • WebUI 和 Desktop 可以共享大部分交互与业务前端代码。
  • Vite 和 React 适合动态 Schema 表单、运行监控和交互式 API 文档。
  • Electron 提供成熟的跨平台桌面能力和系统集成能力。
  • Spring Boot 统一 Service 的 API、安全、事务、健康检查和部署生命周期。
  • Core、Agent Runtime 和 Provider 保持框架无关,未来替换 Service 框架不会推翻执行架构。

接受的代价

  • Electron 安装包和内存占用高于轻量 WebView 外壳。
  • Electron 需要跟随快速发布节奏持续升级。
  • 项目同时维护 Maven 和 pnpm 两套构建体系。
  • shadcn/ui 组件是项目源码,升级和无障碍质量由项目自行负责。

参考


Next Docs Adr 0003 Default Secret Store (`next/docs/adr/0003-default-secret-store.md`)

ADR-0003:默认 Secret Store

  • 状态:已接受
  • 日期:2026-08-22
  • 修订:2026-08-27,明确外部 Secret Store 属于长期非目标
  • 决策者:WePush 项目维护者

背景

WePush 需要保存短信、微信、邮件、HTTP 等 Provider 的 Token、密码和私钥。默认实现必须同时适用于 Windows、Linux、macOS、Headless Service 和容器环境,且不能依赖 Desktop 进程或某个特定云厂商。

决策

默认实现采用本地信封加密 Secret Store,名称为 LocalEnvelopeSecretStore。

数据加密

  • 每次 Secret 写入生成随机 Data Encryption Key(DEK)。
  • Secret 使用 AES-256-GCM、随机 Nonce 和认证附加数据加密。
  • DEK 使用版本化 Master Key(KEK)加密。
  • 数据库只保存 Ciphertext、Nonce、Encrypted DEK、算法、Key Version 和更新时间。
  • 认证附加数据绑定 Workspace ID、Secret Set ID、Secret Name 和记录版本,防止密文被移动到其他记录后解密。

Master Key

  • Master Key 与业务数据库分开存放。
  • Standalone 初始化时生成 256 位随机 Master Key,保存在独立 Key File。
  • Linux 和 macOS Key File 权限必须为 Service 用户独占,目标权限为 0600。
  • Windows 使用显式 ACL,只允许 Service 身份和受控管理员访问。
  • Server 或容器模式优先从只读挂载文件读取 Master Key;允许显式环境变量注入,但不作为推荐方式。
  • Server HA 的所有 Service 实例必须挂载同一份 Active/Retained Key Ring;Key Ring 版本变化通过受控部署传播,不能由各实例独立生成。
  • Server 模式在未执行显式初始化时不得静默生成新 Master Key,防止重建容器后产生无法解密的数据。

密钥轮换

  • 每个 Master Key 具有 keyVersion。
  • 新写入使用当前 Active Key。
  • 读取支持仍在保留期内的旧 Key。
  • 轮换任务逐批重新封装 DEK,不需要重新加密完整 Secret 内容。
  • 删除旧 Key 前必须证明不存在引用,并生成审计记录。

API 和运行时

  • Secret 普通查询只返回 configured、updatedAt 和版本摘要,不返回明文。
  • Secret 不进入日志、Run Event、Artifact、OpenAPI 示例或浏览器持久化存储。
  • Agent 只获取当前 Run 所需的最小 Secret Envelope,并绑定 Agent、Run、Lease、Epoch 和过期时间。
  • Agent 不将解密 Secret 写入 Local Journal。
  • Electron 的 safeStorage 不作为 Service Secret Store,因为 Service 必须能在无 Desktop 会话的 Headless 环境运行。

产品边界

SecretStore 保持 Port 接口,用于维持模块边界、测试替身和本地实现可替换性。WePush Next 的官方产品实现固定为 LocalEnvelopeSecretStore,不规划 HashiCorp Vault、云 KMS、云 Secret Manager 或操作系统凭据存储形式的 Service Adapter。Master Key 由用户控制的受保护文件或显式注入提供。

失败策略

  • Master Key 缺失、权限不安全、Key Version 未知或认证标签校验失败时,Service Fail Closed。
  • 不允许自动创建新 Key 后继续启动已有数据库。
  • 解密失败不得把 Ciphertext 或密钥材料写入异常消息。
  • Readiness 显示 Secret Store 不可用,但不暴露具体密钥路径和内容。

结果

该方案跨平台、可离线、适合安装包和 Headless Service,并确保默认安装不依赖官方或第三方托管密钥服务。代价是项目需要提供完整的密钥初始化、备份、权限检查和轮换流程,部署者也必须把 Master Key 纳入自己的备份与恢复制度。


Next Docs Adr 0004 Agent Communication Protocol (`next/docs/adr/0004-agent-communication-protocol.md`)

ADR-0004:Agent 长期通信协议

  • 状态:已接受
  • 日期:2026-08-22
  • 决策者:WePush 项目维护者

背景

Agent 与 Service 之间需要长期传输注册信息、心跳、能力、Lease、运行命令、事件批次和完成结果。该连接是双向、长生命周期、需要顺序和背压的机器间协议,与浏览器运行监控和大文件传输的需求不同。

决策

不同类型流量使用不同协议,不建立一个包办所有流量的通道。

Agent 控制流

  • 远程 Agent 的正式长期协议使用 gRPC 双向流,承载于 HTTP/2。
  • 使用 Protocol Buffers 定义 AgentControlService.Connect 双向流。
  • Agent 主动建立出站连接,Service 不主动连接 Agent。
  • 同一逻辑流承载 Hello、Capability、Heartbeat、Lease Offer/Ack、Command/Ack、Run Event Batch、Run Summary 和 Drain 状态。
  • 每个方向具有独立单调 Sequence;应用层仍进行幂等和断线恢复,不能仅依赖单次 gRPC 流的顺序保证。
  • Lease 继续使用 Epoch 和 Fencing Token,gRPC 连接本身不等于 Lease 所有权。
  • Agent 断线后指数退避并带抖动重连,可连接任一健康 Service 实例。
  • 设置明确 Deadline、Keepalive、最大消息大小和流控上限。

注册和管理

  • Agent Enrollment、Credential 轮换、插件目录查询等低频管理操作使用 HTTPS REST 或 gRPC Unary;首期优先复用 Service HTTPS REST 管理入口。
  • Agent 正式连接使用独立 Agent 身份,目标安全基线为 mTLS;Bootstrap 阶段可以使用一次性 Enrollment Token。

浏览器和 SDK

  • WebUI、Desktop UI 和远程 SDK 的 Run 实时事件继续使用 SSE。
  • 用户命令继续使用 REST API。
  • 不使用 WebSocket 作为 Agent 或浏览器的默认协议。

Artifact

  • Audience Snapshot、结果、日志和插件包不通过 gRPC 控制流传输。
  • Agent 使用 Service 签发的短期 HTTPS URL 直接上传或下载 Artifact。
  • gRPC 消息只携带 Artifact ID、校验值、大小、状态和短期传输描述。

版本兼容

  • Proto Package 使用主版本,例如 wepush.agent.v1。
  • 已发布字段编号永不复用;删除字段必须 reserved。
  • 新字段优先使用 optional 或新消息类型,旧 Agent 忽略未知字段。
  • Service 维护明确的 Agent 协议兼容窗口。

为什么不选择其他方案

  • 长轮询适合快速 MVP,但命令、事件、心跳和背压最终会形成多个相互协调的 HTTP 循环。
  • SSE 是服务端到客户端单向流,不能独立承担 Agent 双向控制。
  • WebSocket 可以双向通信,但缺少 Protobuf/gRPC 的服务契约、生成代码、Deadline 和标准流控能力。
  • gRPC 不适合直接服务浏览器交互文档和大 Artifact,因此不替代 REST、SSE 和对象存储。

实施策略

Embedded Agent 直接调用 Java 接口,不经过网络。远程 Agent 功能开始实现时直接建设 gRPC,不先发布需要长期兼容的长轮询协议。

参考


Next Docs Adr 0005 Provider Plugin Lifecycle (`next/docs/adr/0005-provider-plugin-lifecycle.md`)

ADR-0005:Provider 插件发现、隔离和更新

  • 状态:已接受
  • 日期:2026-08-22
  • 决策者:WePush 项目维护者

背景

WePush Provider 会引入不同厂商 SDK。它们可能依赖不同版本的 HTTP、JSON、加密和日志库。Provider 还需要携带 Descriptor、JSON Schema、UI Schema 和图标。插件机制需要减少依赖冲突,同时保证运行中任务不会被不安全的代码替换破坏。

决策

插件框架

  • 外部 Provider 插件框架采用 PF4J 3.15.x。
  • Provider SPI 是 PF4J Extension Point,但 Core Engine 不依赖 PF4J。
  • Agent App 的 Provider Catalog Adapter 负责把 PF4J Extension 转换为 Core 的 ProviderFactory。
  • 内置 Provider 可以作为系统 Extension 参与同一个 Provider Catalog。

插件包

text
wepush-provider-http-1.0.0.zip
├── plugin.jar
├── lib/
├── plugin.json
├── schemas/
├── assets/
├── LICENSES/
└── signature.ed25519

plugin.json 包含 Plugin ID、Provider ID、版本、SPI 版本、主类、依赖、平台限制、SHA-256 清单和签名 Key ID。

依赖隔离

  • 每个插件使用独立 PF4J ClassLoader。
  • JDK、Provider SPI、Core API 和统一日志 API 由 Parent ClassLoader 提供。
  • 插件私有 SDK 和三方依赖由插件 ClassLoader 加载。
  • 插件不得捆绑 Provider SPI、Core API 或日志 API 的重复副本。
  • 构建阶段检测 Split Package、重复共享类和禁止依赖。

ClassLoader 隔离只解决依赖冲突,不是恶意代码安全沙箱。JVM 内 Provider 被视为受信代码。需要运行不受信第三方插件时,必须增加独立进程 Provider Runner,不能宣称 PF4J 已提供安全隔离。

签名和信任

  • 正式发行环境默认只加载签名有效且发布者受信的插件。
  • 插件包使用 SHA-256 清单和 Ed25519 签名。
  • Agent 发行包内置官方公钥,并允许管理员添加受信发布者。
  • 未签名插件只允许在显式 Developer Mode 中加载,Service 和 UI 必须显示安全警告。
  • ZIP 解包采用路径白名单、大小上限和 Zip Slip 防护。

安装和激活

首期发行工具使用显式 Staging/Active/Rollback 目录;插件 ZIP 文件名必须包含稳定 Plugin ID 和版本:

text
plugins/
├── staging/
│   └── wepush-provider-http-1.2.0.zip
├── active/
│   └── wepush-provider-http-1.2.0.zip
└── rollback/
    └── wepush-provider-http-1.0.0.zip.20260823T010203Z
  • 下载到 Staging 目录并记录包级 SHA-256。
  • 激活时先把旧包移动到 Rollback,再把新包原子移动到 Active;Agent 启动在 PF4J 解包前验证 ZIP 边界、清单、签名、Descriptor、共享包和 SPI 兼容性。
  • Agent 启动/健康验证失败时,Supervisor 工具恢复 Rollback 旧包并再次启动。
  • Service 保存期望插件版本,Agent 上报实际插件清单和状态。

更新策略

  • 不进行 JVM 内热替换,不在运行中卸载或替换 Provider ClassLoader。
  • 单 Agent 工具执行 Stage、Activate、Restart、Verify、Rollback;多 Agent 编排在激活前先把目标 Agent 从可调度池摘除,等待当前 Run 完成或达到 Drain Timeout。
  • 首期命令行工具不伪造进程内热更新;Drain 编排由 Service/运维层控制,Supervisor 只负责重启、健康验证和回滚。
  • 重启后加载新版本并通过 Health 和自检;失败时恢复旧 ZIP 并再次重启。
  • 多 Agent 环境逐台滚动,确保始终保留可执行旧任务的 Agent。
  • Provider 版本由 Run Snapshot 固定;调度新 Run 前必须存在兼容 Agent。

Provider 删除

  • 被 Run Snapshot、Job 或历史诊断引用的版本不能立即物理删除。
  • 删除先进入 DEPRECATED,禁止新配置使用。
  • 没有运行和保留引用后才允许从 Agent 清理文件。
  • 历史 Run 仍保存 Provider ID、版本和 Schema 快照,即使代码已经删除。

结果

该方案提供清晰的发现和依赖隔离,同时避免热替换引发线程、连接池、静态缓存和 ClassLoader 泄漏。代价是插件升级需要 Agent 重启,但滚动升级可以避免整体停机。

参考


Next Docs Adr 0006 Postgresql Control Plane Ha (`next/docs/adr/0006-postgresql-control-plane-ha.md`)

ADR-0006:PostgreSQL Server 模式控制面高可用

  • 状态:已接受
  • 日期:2026-08-22
  • 修订:2026-08-27,明确 HA 只服务于用户自建信任域
  • 决策者:WePush 项目维护者

背景

Standalone 模式使用单 Service 和 SQLite,不需要控制面集群。Server 模式需要支持 Service 实例故障、Agent 重连、调度互斥、并发领取 Run 和任意实例提供 API/SSE。方案应尽量减少必须部署的中间件。

决策

基线拓扑

  • Server 模式数据库基线为 PostgreSQL 18.x。
  • 正式 HA 至少部署两个无状态 Service 实例,位于支持 HTTP/2 和 gRPC 的负载均衡器后。
  • PostgreSQL 是业务状态和协调状态的唯一持久化事实源。
  • PostgreSQL 自身的复制、故障转移和备份由部署者自行选择并运维,不由 WePush 安装包组建数据库集群,也不依赖 WePush 官方托管服务。
  • Artifact 使用 S3-compatible Store,不使用 Service 本地磁盘共享状态。
  • 首期 HA 不强制引入 Redis、Kafka、ZooKeeper 或独立任务队列。

API 和 Agent 连接

  • REST 请求可以落到任意 Service 实例,不要求 Sticky Session。
  • SSE 可以连接任意实例,历史游标从 PostgreSQL 读取,新事件通过本地发布加数据库轮询补偿获得。
  • Agent gRPC 流连接一个 Service 实例;实例失败时 Agent 重连到负载均衡器并恢复 Sequence、Lease 和事件上报。
  • 当前 Agent 连接的 Owner Instance 只作为可丢失的路由提示,不成为持久化真相。

调度互斥

  • 只有持有 PostgreSQL Session-level Advisory Lock 的 Service 实例运行 Schedule Scanner。
  • 使用专用数据库连接持有锁,连接断开时 PostgreSQL 自动释放锁。
  • 其他实例持续尝试获取锁,成为新的调度 Leader。
  • 每次 Schedule 触发仍使用数据库幂等键,Leader Lock 不是防重复的唯一手段。

Run 领取和 Lease

  • 候选 Run 通过短事务、Run 状态条件更新和“每个 Run 仅一个 Active Lease”的数据库唯一索引领取;并发冲突失败后由下一次扫描重试。批量吞吐需要提高时可以在 PostgreSQL Repository 内加入 FOR UPDATE SKIP LOCKED,不改变应用层语义。
  • 创建 Lease 时递增 Epoch 并生成随机 Lease Token。
  • 同一 Run 同时最多一个 Active Lease。
  • 所有 Agent 写入校验 Lease ID、Epoch 和 Token,拒绝旧 Agent 的过期写入。
  • Service 实例故障不会自动宣布 Run 失败;由 Lease Timeout 和恢复状态机判断。

实例间通知与持久消息

  • Service→Agent 的 Lease Offer 和 Run Command 先写 agent_message_outbox,所有 Service 周期扫描;只有持有 Agent 当前 gRPC 流的实例发送,ACK 后关闭消息。
  • SSE 以 run_event 为事实源,每个实例只轮询本机存在订阅者的 Run Cursor,从而观察其他实例提交的事件。
  • 1.1.0 使用 PostgreSQL LISTEN/NOTIFY 作为待调度 Run、Agent Outbox 和 Run Event 的低延迟唤醒优化,但不得替代 outbox、事件表和周期扫描。
  • 禁止把 LISTEN/NOTIFY 当成持久消息队列。

数据库迁移

  • 发布前或滚动启动前只运行一个 Migration Job。
  • Service Readiness 在数据库版本不兼容时失败。
  • 迁移采用 Expand、Migrate、Contract,保证滚动期间相邻 Service 版本可短期共存。
  • 破坏性 Contract 迁移只能在旧版本实例全部退出后执行。

故障行为

故障 预期行为
一个 Service 实例退出 REST/SSE 重连;Agent gRPC 重连;其他实例继续服务
调度 Leader 退出 Advisory Lock 自动释放,其他实例接任
一个轮询周期未观察到新事件/消息 下一周期继续从持久游标扫描补偿
PostgreSQL 暂时不可用 Service Readiness 失败,不创建新 Run;Agent 保留有界 Outbox
PostgreSQL 主从切换 Service 重建连接,重新竞选调度 Leader
Agent 连接到新实例 使用 Sequence 和 Lease Epoch 恢复,不重复接受旧写入

非目标

  • 不在 WePush 内实现 PostgreSQL 选主和数据复制。
  • 跨区域 Active-Active 控制面不属于产品范围。
  • 不通过分布式锁实现外部渠道 Exactly Once。
  • SQLite Standalone 不支持多 Service 实例并发访问同一个数据库文件。

参考


Next Docs Adr 0007 Artifact Store And Retention (`next/docs/adr/0007-artifact-store-and-retention.md`)

ADR-0007:Artifact Store 协议和保留策略

  • 状态:已接受
  • 日期:2026-08-22
  • 修订:2026-08-27,移除云 KMS 产品方向
  • 决策者:WePush 项目维护者

背景

Audience Snapshot、运行结果、完整响应、日志和插件包可能远大于数据库适合保存的范围。Standalone 需要零依赖本地存储,Server 和 HA 模式需要所有 Service 与 Agent 可访问的共享存储。

决策

存储实现

  • Standalone 默认使用 LocalFileArtifactStore。
  • Server 默认使用 S3ArtifactStore,协议限定为 S3-compatible API 的受控子集。
  • 数据库保存 Artifact 元数据和生命周期状态,不把对象存储 List 结果作为事实源。

S3-compatible 最小能力:

  • Put Object
  • Get Object 和 Range
  • Head Object
  • Delete Object
  • Multipart Upload、Complete 和 Abort
  • Presigned Upload/Download URL
  • 对象校验和与原生服务端加密配置

对象命名

text
<environment>/<workspaceId>/<artifactType>/<yyyy>/<mm>/<artifactId>
  • Object Key 不包含用户名、任务名、手机号、邮箱或原始上传文件名。
  • Artifact ID 全局唯一且不可变。
  • 原始文件名只作为经过清理的下载展示元数据。

上传和下载

  • Agent 不持有长期对象存储 Credential。
  • Service 在权限、Lease 和配额校验后签发短期 Presigned URL。
  • URL 绑定方法、Key、Content Length 范围、Checksum 和短期过期时间。
  • 上传完成后 Agent 通知 Service,Service通过 Head/Checksum 验证后把 Artifact 置为 READY。
  • Service 托管流式写入达到 100 MiB 时默认使用 Multipart Upload。Agent 对不超过 1 GiB 的 Artifact 使用带长度与校验和约束的单次 Presigned Put;1.1.0 对更大对象使用可恢复 Presigned Multipart Plan,单对象最大 5 TiB,每批最多签发 100 个 Part URL 并约束 10,000 Part 上限。Complete 后必须核对总大小和 SHA-256,失败或过期会话主动 Abort,对象存储 24 小时 Lifecycle 负责孤儿兜底。
  • 下载支持 Range,用户下载仍先经过 Service 权限检查。

完整性和加密

  • Artifact 元数据保存 SHA-256、大小和 Content Type。
  • 传输使用 TLS。
  • Server 模式启用对象存储原生 AES256,或由部署者在其存储层保证等价加密。WePush 不对接或管理云 KMS Key。
  • 极敏感 Artifact 可以在客户端加密后再上传,具体类型另行配置。

生命周期状态

text
UPLOADING → READY → DELETING → DELETED
          ↘ FAILED
  • 删除采用两阶段流程,先标记 DELETING,对象删除成功后标记 DELETED。
  • Object Store 操作幂等,清理任务可以安全重试。
  • 数据库引用和 Pin/Legal Hold 阻止进入删除流程。

默认保留策略

Artifact 类型 默认保留
Audience Snapshot 被 Job/Run 引用期间保留,最后引用释放后再保留 30 天
成功、失败、未知、未发送结果 Run 结束后 90 天
结构化运行日志 Run 结束后 30 天
完整 Provider 响应体 Run 结束后 7 天
导出文件和临时下载 创建后 24 小时
未完成 Multipart Upload 24 小时后 Abort(对象存储 Lifecycle 兜底)
Provider 插件包 存在 Agent、Job 或 Run Snapshot 引用期间保留
  • Workspace 管理员可以在系统允许范围内缩短或延长策略。
  • PINNED 和 LEGAL_HOLD Artifact 不受普通 TTL 删除。
  • 对象存储 Lifecycle Rule 可作为兜底,但 Service 数据库清理任务是业务生命周期的主控制器。

Local Store 等价语义

Local Store 使用相同 Artifact ID、状态、校验和和保留规则。写入先进入临时文件,Flush、fsync 和校验后原子移动到最终路径。路径由 Store 生成,不使用用户输入拼接。

结果

该方案使 Standalone 保持自包含,同时让 Server 能使用兼容对象存储。Agent 直传避免 Service 成为大文件带宽瓶颈。代价是需要管理 Presigned URL、Multipart 清理和数据库/对象存储的最终一致性。

参考


Next Docs Adr 0008 Workspace Multitenancy Scope (`next/docs/adr/0008-workspace-multitenancy-scope.md`)

ADR-0008:Workspace 多租户范围

  • 状态:已接受
  • 日期:2026-08-22
  • 修订:2026-08-27,明确 Workspace 不是公共 SaaS 租户
  • 决策者:WePush 项目维护者

背景

Standalone 通常只有一个用户,但用户自建的 Server 模式可能出现多个团队、账号、Agent 和权限边界。如果完全按单 Workspace 实现,后续增加团队 Scope 需要修改所有表、唯一索引、Repository、API、Artifact 和 Secret。Workspace 因此是同一部署信任域内的逻辑边界,不是公共 SaaS 租户模型。

决策

Workspace 逻辑隔离进入 WePush Next 正式产品范围,但公共多租户 SaaS 长期不进入产品范围。

Standalone

  • 初始化时自动创建一个 Default Workspace。
  • UI 默认隐藏 Workspace 切换和管理复杂度。
  • 数据、API 和 Repository 内部仍显式携带 Workspace ID。

Server

  • 支持多个 Workspace。
  • 用户通过 Role Binding 获得某个 Workspace 的 ADMIN、OPERATOR 或 VIEWER 权限。
  • Account、Secret、Message、Audience、Job、Schedule、Run、Artifact、Agent Pool 和 API Token 都归属 Workspace。
  • Agent 可以加入一个或多个明确授权的 Agent Pool,Lease 只能分配给 Run 所属 Workspace 可用的 Pool。

API 范围

Workspace 业务资源使用显式路径:

text
/api/v1/workspaces/{workspaceId}/accounts
/api/v1/workspaces/{workspaceId}/messages
/api/v1/workspaces/{workspaceId}/audiences
/api/v1/workspaces/{workspaceId}/jobs
/api/v1/workspaces/{workspaceId}/schedules
/api/v1/workspaces/{workspaceId}/runs
/api/v1/workspaces/{workspaceId}/agents

身份、公开 Provider Catalog、系统 Health 和 Agent Internal API 不使用该业务路径,但仍按自身规则校验 Scope。

Java SDK 创建 WorkspaceClient 后执行资源操作,避免每个方法重复传 Workspace ID:

java
WorkspaceClient workspace = client.workspace(workspaceId);
workspace.jobs().startRun(jobId, request);

数据隔离

  • 所有 Workspace 资源表包含非空 workspace_id。
  • 唯一索引包含 workspace_id,例如 (workspace_id, provider_id, name)。
  • Repository 方法必须接收 WorkspaceId,禁止提供无 Scope 的通用 findById(id) 给业务用例。
  • API ID 即使全局唯一,也不能替代 Workspace 权限检查。
  • 跨 Workspace 管理操作集中在独立 System Administration 用例中。
  • PostgreSQL Row Level Security 可以作为未来防御层,但不作为首期正确性的唯一保障。

Secret、Artifact 和指标

  • Secret 认证附加数据绑定 Workspace ID。
  • Artifact Object Key 以 Workspace ID 分区。
  • Presigned URL 在签发前校验 Workspace 权限。
  • 审计事件记录 Workspace ID。
  • 指标默认不把 Workspace ID 作为高基数 Label;按 Workspace 的统计通过查询模型提供。

配额

Server 支持 Workspace 级策略入口,包括最大 Agent、并发 Run、总并发、Artifact 容量和保留期。早期版本可以只实现默认策略,但模型和校验入口必须存在。这些限制用于自建实例的资源治理,不用于计费套餐。

明确不做

  • 不提供计费、订阅套餐、租户自助注册和公共市场。
  • 不提供恶意租户之间的进程级、数据库级、主机级物理隔离。
  • 不为每个 Workspace 自动创建独立数据库。
  • 不允许 Provider 插件执行任意跨 Workspace 查询。

结果

Workspace 从第一张表、第一条 API 和第一个 Artifact 开始成为正式边界,可以避免团队资源相互混淆。Standalone 通过 Default Workspace 保持简单体验。代价是每个用例、Repository 和测试都必须验证 Workspace Scope;这一成本只服务于用户自建实例内的权限和资源管理,不为公共 SaaS 铺路。


Next Docs Releases 0.1.0 Alpha.1 (`next/docs/releases/0.1.0-alpha.1.md`)

WePush Next 0.1.0-alpha.1 — Public Preview

这是 WePush Next 新架构产品线的首个公开预览版。Classic 客户端不受影响,仍在原有目录中独立演进。

下载内容

  • wepush-next-0.1.0-alpha.1.tar.gz / .zip:Service、Agent、WebUI 和 Linux/macOS/Windows 服务安装脚本。
  • wepush-next-java-sdk-0.1.0-alpha.1.zip:Java SDK、公开 API 契约和本地 Maven 安装说明。
  • wepush-next-desktop-0.1.0-alpha.1-<os>-<arch>.*:对应平台的 Electron Desktop 应用。
  • wepush-next-0.1.0-alpha.1-sbom.cdx.json:CycloneDX 软件物料清单。
  • SHA256SUMS:全部发布附件的 SHA-256 校验值。

运行 Service、Agent 与 Java SDK 需要 Java 21。具体安装、配置、备份、升级和卸载步骤见分发包中的 docs/deployment-and-operations.md。

第一次使用请阅读《WePush Next 对外使用指南》,其中包含发行物选择、三平台便携启动与服务安装、Desktop 连接、首个安全 Dry Run、正式 API 调用和常见问题。

重要提示

这是未签名的预览发行:macOS 没有 Developer ID/Notarization,Windows 没有 Authenticode。操作系统可能显示未知开发者警告。请只从本 Release 下载并校验 SHA256SUMS;不要将本版本直接用于关键生产业务。

API、数据结构和升级路径仍可能变化。升级前请备份数据库、Secret 主密钥、Agent 身份、插件与 Artifact。完整边界见 PREVIEW-NOTICE.md,安全问题请按 SECURITY.md 私下报告。

主要能力

  • Core Engine 与 Provider SPI;内置 HTTP Provider。
  • 可独立安装的 Service 与 Agent,支持本机执行和远端 gRPC 执行。
  • SQLite Standalone、PostgreSQL Server/HA 基线,以及 Local/S3 Artifact Store。
  • Java SDK、OpenAPI、React WebUI、可视化配置和运行中心。
  • Electron Desktop 外壳和 Linux、macOS、Windows 服务脚本。

详细变化见 CHANGELOG.md。


Next Docs Releases 0.1.0 Alpha.2 (`next/docs/releases/0.1.0-alpha.2.md`)

WePush Next 0.1.0-alpha.2 — Public Preview

0.1.0-alpha.2 是一次产品方向和安全收口发布。WePush Next 的长期目标已明确为开源、可下载、可安装、由用户自行部署和运维;项目不建设官方公共 SaaS、注册计费订阅、公共租户平台或云 KMS/Secret Manager 集成。Classic 客户端不受影响,继续在原有目录中独立演进。

主要变化

  • 新增产品目标、永久边界和 alpha.2 至 1.0.0 的分版本路线图,并同步 README、架构、详细设计、ADR、部署和实现状态文档。
  • 新增 Embedded Java SDK,Java 应用可在自身 JVM 中显式装配 Core Engine 和允许的 Provider,无需启动 Service、Agent 或数据库。
  • 发行包和独立 Java SDK 附件同时携带 Remote SDK、Embedded SDK、Core API、Provider SPI、Engine 和 HTTP Provider 的 POM/JAR。
  • 修复开启 API Security 时 WebUI Artifact 下载没有携带 Bearer Token 的问题。
  • S3-compatible Artifact Store 只保留 AUTO/AES256/NONE 服务端加密配置,移除 AWS KMS 配置与实现路径。
  • 修正 macOS Desktop 主可执文件和应用版本元数据,三平台发布工作流会校验包内产品版本。

完整产品边界与后续计划见《产品目标、边界与路线图》。

下载内容

  • wepush-next-0.1.0-alpha.2.tar.gz / .zip:Service、Agent、WebUI、OpenAPI、Java SDK 和 Linux/macOS/Windows 服务安装脚本。
  • wepush-next-java-sdk-0.1.0-alpha.2.zip:Remote/Embedded Java SDK 与它们的本地 Maven 安装依赖。
  • wepush-next-desktop-0.1.0-alpha.2-<os>-<arch>.*:Linux x64、macOS Apple Silicon 和 Windows x64 Desktop 应用。
  • wepush-next-0.1.0-alpha.2-sbom.cdx.json:CycloneDX 软件物料清单。
  • SHA256SUMS:全部发布附件的 SHA-256 校验值。

运行 Service、Agent 或 Java SDK 需要 Java 21 或更高版本。安装、便携启动、备份、升级和卸载步骤见《对外使用指南》和《部署与运维》。

预览版提示

本版本仍不是稳定版,API、配置、数据结构和升级路径可能变化,不建议直接用于关键生产业务。Desktop 和安装包未使用商业代码签名:macOS 没有 Developer ID/Notarization,Windows 没有 Authenticode。请只从本 Release 下载并校验 SHA256SUMS。

升级前请备份数据库、Secret 主密钥、Agent 身份、插件与 Artifact。安全问题请按 SECURITY.md 私下报告。


Next Docs Releases 0.1.0 Alpha.3 (`next/docs/releases/0.1.0-alpha.3.md`)

WePush Next 0.1.0-alpha.3 — Public Preview

0.1.0-alpha.3 完成日常使用闭环。用户现在可以在 WebUI 或 Desktop UI 中导入受众、配置资源、先做 Dry Run、核对正式发送影响、查看结果并只重发失败项;不需要手写 Recipient JSON 或直接调用 API。

WePush Next 仍只面向用户自行下载、安装、部署和运维。项目不提供官方公共 SaaS、注册、计费、订阅、公共租户平台、云 KMS/Secret Manager 集成或面向恶意公共租户的物理隔离。

主要变化

  • Account 支持编辑、Provider 连接测试、启停和归档。
  • Message 内容更新生成不可变 Revision,并支持历史、Diff 和复制;Audience 内容更新生成不可变 Snapshot。
  • Audience 支持 CSV/TXT 流式导入、字段映射、最多 20 行预览、数据库去重、错误行 CSV 下载,以及新建或更新快照。
  • Job 支持编辑、复制、启停和归档;Schedule 可编辑名称、Job、Cron、时区、Misfire 和启停状态。
  • 正式发送前展示 Provider/版本、账号、受众、策略、并发、限速和预计规模,并要求使用短期确认令牌。
  • 可筛选 FAILED、UNKNOWN、UNSENT Item 创建关联的新 Run;新 Run 复用来源 Run 的冻结 Snapshot,只执行选中 Item。
  • Run、Account、Message、Audience、Job、Schedule 和审计统一使用签名 Cursor Page,并支持名称、状态和时间筛选。
  • 总览接入真实 Run 总数、活动数、成功数、问题数、活动/最近 Run 和 14 天趋势。
  • Standalone 隐藏 Workspace 复杂度;用户自建 Server 使用真实 Workspace 选择。
  • OpenAPI、TypeScript Client 和 Remote Java SDK 已同步;Java SDK 的受众上传使用流式 multipart 文件 BodyPublisher。

验证

  • 完整 Maven 测试矩阵通过,包括 OpenAPI、SQLite 13 个迁移、Service/API、Agent/gRPC、架构、SDK 和 HTTP Provider 纵向测试。
  • 集成测试覆盖 CSV 导入/去重/错误下载、不可变 Snapshot、正式发送确认、真实 HTTP 部分失败和筛选重发。
  • pnpm TypeScript、Vitest、Web Vite 和 Desktop 构建通过。
  • PostgreSQL 与 S3-compatible 集成仍由带对应服务的 CI 门禁验证。

下载内容

  • wepush-next-0.1.0-alpha.3.tar.gz / .zip:Service、Agent、WebUI、OpenAPI、Java SDK 和三平台服务安装脚本。
  • wepush-next-java-sdk-0.1.0-alpha.3.zip:Remote/Embedded Java SDK 与本地 Maven 安装依赖。
  • wepush-next-desktop-0.1.0-alpha.3-<os>-<arch>.*:Linux x64、macOS Apple Silicon 和 Windows x64 Desktop。
  • wepush-next-0.1.0-alpha.3-sbom.cdx.json 与 SHA256SUMS。

本版本仍不是稳定版,API、配置、数据结构和升级路径可能变化。Desktop 与安装包未使用商业代码签名;请只从本 Release 下载并校验 SHA256SUMS。升级前必须同时备份数据库、Secret 主密钥、Agent 身份、插件和 Artifact。


Next Docs Releases 0.1.0 Alpha.4 (`next/docs/releases/0.1.0-alpha.4.md`)

WePush Next 0.1.0-alpha.4 — Public Preview

0.1.0-alpha.4 把 Next 从“只有通用 HTTP”推进到可使用真实渠道的自部署消息系统。用户在自己的 Service、Agent 或 Embedded Java 应用中,使用自己的 SMTP、企业 IM、阿里云和微信账号完成配置与发送;WePush 不提供公共 SaaS、账号注册、计费订阅、平台代理或云 KMS。

本版新增渠道

Provider ID 能力
wepush.email.smtp NONE/STARTTLS/TLS、可选认证、文本/HTML Multipart、Reply-To、CC/BCC
wepush.bot.feishu Text/Post/Interactive/Raw、自定义机器人签名、关键词与双窗口安全限流
wepush.bot.dingtalk Text/Markdown/Link/Action Card/Raw、时间戳 HMAC 签名与安全限流
wepush.bot.wecom Text/Markdown/Raw、官方 Webhook 校验与安全限流
wepush.sms.aliyun SendSms、POP HMAC-SHA1 签名、模板变量、OutId 追踪与业务错误映射
wepush.wechat.official 模板、订阅、客服消息;Access Token 缓存和安全刷新
wepush.wechat.mini 订阅、统一服务消息;Access Token 缓存和安全刷新
wepush.wecom.app 企业微信任意应用消息 Payload、用户/部门/标签目标和重复检查

原有 wepush.http 继续内置,因此 Service 和 Agent 共发现 9 个内置 Provider。每个新 Provider 的实现版本为 0.1.0,与产品版本独立。

统一交付边界

  • 每个渠道都有 Account、Message、Recipient Schema,WebUI 从实时 Schema 渲染表单和默认消息示例。
  • 密码、Webhook、签名 Secret、AccessKey Secret、AppSecret 和 CorpSecret 只以 SecretRef 出现在配置中。
  • Dry Run 完成配置、Recipient、模板和 Payload 校验,但不解析 Secret、不获取 Token、不访问网络。
  • 本地 mock 测试覆盖实际 SMTP/HTTP 协议、厂商签名、Token 刷新、响应上限和错误分类;Engine 纵向测试逐一执行 8 个标准渠道。
  • Service 与 Agent 通过 ServiceLoader 自动装载;Embedded Java SDK 仍要求调用方显式选择 Factory。
  • 机器人、短信和微信系生产实现固定或严格校验厂商官方端点,防止把 Secret 与消息发送到任意主机。

完整最小配置、Recipient 字段和 Provider Code 处理见《内置 Provider 指南》。

发送语义

外部消息渠道通常不提供 Exactly Once。本版本明确区分:

  • 连接建立前的确定失败可以按策略重试;
  • 厂商显式频控返回 RATE_LIMITED,可携带建议等待时间;
  • 提交后超时、I/O 或 5xx 返回 UNKNOWN,默认不自动重发;
  • 阿里云 OutId 只用于追踪,不代表服务端去重;
  • 企业微信应用消息默认使用厂商提供的短窗口重复检查。

WebUI 与 SDK

  • Schema Renderer 新增本地 $ref 解析和嵌套 SecretRef 默认对象,渠道配置不再需要手写完整引用骨架。
  • 标准渠道消息 Schema 带可编辑示例;正式值仍需替换为用户自己的模板、签名和账号参数。
  • 主发行包和独立 Java SDK 附件加入 provider-standard POM/JAR;SMTP 的 Angus Mail 依赖由 Maven 根据 POM 解析。
  • OpenAPI 与 Remote SDK 的 Provider Catalog/Schema 通用契约保持不变,客户端无需针对每个渠道增加硬编码 API。

发行物

  • wepush-next-0.1.0-alpha.4.tar.gz / .zip:Service、Agent、WebUI、OpenAPI、Java SDK、内置 Provider 和三平台服务安装脚本。
  • wepush-next-java-sdk-0.1.0-alpha.4.zip:Remote/Embedded Java SDK、HTTP/标准渠道 Provider 与本地 Maven 安装依赖。
  • wepush-next-desktop-0.1.0-alpha.4-<os>-<arch>.*:Linux x64、macOS Apple Silicon 和 Windows x64 Desktop。
  • wepush-next-0.1.0-alpha.4-sbom.cdx.json 与 SHA256SUMS。

预览限制

  • 本版本不会替用户验证第三方账号额度、模板审核状态、手机号/关注/订阅授权或厂商侧配额;首次正式发送必须使用自有测试目标小范围验证。
  • API、Schema、Provider 行为和升级路径在预览期仍可能不兼容变化。
  • Desktop 和安装包未使用商业代码签名或 Apple Notarization,请只从项目 Release 下载并校验 SHA-256。
  • 本版本不建设公共 SaaS、自助注册、计费订阅、公共租户平台、云 KMS/Secret Manager 或托管更新服务。

Next Docs Releases 0.1.0 Beta.1 (`next/docs/releases/0.1.0-beta.1.md`)

WePush Next 0.1.0-beta.1 — Public Preview

0.1.0-beta.1 把 Next 的重点从功能扩展转向自部署运维成熟:用户可以在 Windows、Linux 或 macOS 离线安装 Standalone,验证升级、备份和恢复,并通过 Desktop 管理本机 Service 与签名 Provider 插件。产品定位不变:WePush 只提供用户自行部署的开源产品,不提供公共 SaaS、注册计费订阅、云 KMS 或公共租户平台。

安装与发行

  • 新增统一 install/install.sh / install/install.ps1,默认安装 Standalone Service;平台目录仍保留 service、agent、all 高级分组件入口。
  • Release 同时提供使用系统 Java 21+ 的精简包,以及 Linux、macOS、Windows 对应架构的 jlink Java Runtime 完整包。
  • Windows 两类发行包都离线携带 WinSW 2.12.0;发行构建锁定来源、文件长度和 SHA-256,安装时再次校验,不临时联网下载。
  • 启动脚本优先使用包内 Runtime,并显式允许 SQLite 所需的 JDK Native Access。

升级、备份与恢复

  • 三平台 Backup 生成格式化 Manifest、逐文件 SHA256SUMS 和一致的配置/数据 Payload,归档完成后立即调用 Restore 校验。
  • Restore 在替换前拒绝路径穿越、未列入摘要的额外 Payload 和内容不一致,保留 pre-restore-* 原数据,恢复后执行 Installation Health;失败自动恢复原目录。
  • Upgrade 在切换新版本后验证 Readiness、Flyway 当前数据库版本和内置 Provider 无网络 Dry Run;失败时切回旧版本并恢复升级前备份。
  • 跨平台自测实际比对数据库、Master Key、Artifact、Agent Identity、Journal、Event/Completion Outbox 和插件,并模拟新版本验证失败。

Desktop 与插件

  • Desktop 设置页可检测、启动、停止本机 Service,读取最近日志并生成不包含 Token/Secret 的诊断。
  • Desktop API Token 使用 Electron safeStorage;Linux 没有安全 Keyring 时拒绝弱持久化。浏览器只使用当前标签页的 sessionStorage,并清理旧 localStorage Token。
  • Providers 页支持选择本机插件 ZIP、调用 Agent 生产校验器验证清单/摘要/SPI/Ed25519 签名、Stage、Activate 和 Rollback。
  • 插件按清单 pluginId 使用稳定文件名;激活或回滚后 Agent 不健康会恢复先前版本。

验证与构建质量

  • 新增 Playwright 浏览器 E2E、三平台 Desktop 打包后启动冒烟、POSIX/PowerShell 安装运维自测。
  • 新增长稳工作流,覆盖 Java 21/25 × SQLite/PostgreSQL,并持续请求 Installation Health 和 System API。
  • 清理 SQLite 在未来 JDK 的 Native Access 警告、Agent Shade 的模块/签名/许可证重复资源警告,并为直接基础设施测试补齐日志实现。

发行附件

  • wepush-next-0.1.0-beta.1-linux-<arch>.tar.gz、macos-<arch>.zip、windows-<arch>.zip:包含 Java Runtime 的完整自部署包。
  • wepush-next-0.1.0-beta.1.tar.gz / .zip:使用系统 Java 21+ 的精简包。
  • wepush-next-desktop-0.1.0-beta.1-<os>-<arch>.*:三平台未签名 Desktop 包。
  • wepush-next-java-sdk-0.1.0-beta.1.zip:Remote/Embedded Java SDK、HTTP/标准渠道 Provider 与本地 Maven 依赖。
  • wepush-next-0.1.0-beta.1-sbom.cdx.json 与统一 SHA256SUMS。

预览期边界

本版本仍未使用 Apple Developer ID/Notarization 或 Windows Authenticode,不是稳定生产版本。Server/HA 的 PostgreSQL、S3-compatible Store、TLS 和备份由部署者自建运维;WePush 不运行集中控制面,不上传数据或遥测。完整风险见 PREVIEW-NOTICE.md,操作步骤见《对外使用指南》和《部署与运维》。


Next Docs Releases 1.0.0 (`next/docs/releases/1.0.0.md`)

WePush Next 1.0.0 — Stable Self-hosted Release

1.0.0 是 WePush Next 首个稳定自部署版本。它把 Beta 的安装运维闭环提升为明确的 1.x API、配置、数据库和 Agent 协议兼容承诺,并补齐大受众、长时间运行、断线恢复、磁盘写失败、进程崩溃、Agent 失联、三平台升级与卸载门禁。

产品定位保持不变:WePush 提供用户自行下载、安装、部署和运维的开源产品,不运营官方公共 SaaS、集中控制面、注册计费订阅、云 KMS/Secret Manager 或公共租户平台。

稳定性与兼容性

  • /api/v1 OpenAPI 与 0.1.0-beta.1 保持契约一致,发行门禁对忽略版本号后的公开文档做精确摘要校验。
  • Beta 已公开的 WEPUSH_* 配置键进入 1.x 兼容基线。
  • SQLite/PostgreSQL 从 Beta V13 迁移到 V14;迁移只新增稳定兼容元数据,不改写用户数据。
  • 最低直接升级版本和最低可回滚版本均为 0.1.0-beta.1。
  • Provider SPI Major 1、Remote/Embedded Java SDK 和 Agent 协议 Major 1 进入稳定承诺。

可靠性与故障验证

  • Core 自动执行 100,000 Recipient 流式用例,限制读取批大小且不在测试 Sink 中保存全量结果。
  • Agent Event/Completion Outbox 在模拟磁盘不可写时保持内存与已持久化状态一致,不跳过事件序号或误删未确认结果。
  • Agent 断网后的业务事件重放、进程崩溃后的 UNKNOWN Completion、Lease/Fence 恢复和失联超时继续由自动化测试覆盖。
  • Java 21/25 × SQLite/PostgreSQL 长稳矩阵持续检查 Installation Health 和 System API。
  • Linux、macOS、Windows 运维自测覆盖 Beta 成功升级、升级失败自动回退、可连续创建且不会同名碰撞的备份恢复、默认卸载保留数据以及显式 Purge。

安装与发行物

  • 三平台完整包内置 Java Runtime;精简包使用用户提供的 Java 21+。
  • Windows 包离线携带固定摘要的 WinSW,不在安装时下载服务包装器。
  • 提供三平台 Desktop、Remote/Embedded Java SDK、CycloneDX SBOM、独立归档摘要和统一 SHA256SUMS。
  • GitHub Release 是稳定 Release,不标记为 prerelease。

未签名边界

按项目既定选择,macOS/Windows Desktop 和安装包仍不使用商业代码签名或 Apple Notarization。用户必须从项目 GitHub Releases 下载并核对 SHA256SUMS;详见 UNSIGNED-NOTICE.md。Provider 插件仍强制 SHA-256 清单和 Ed25519 签名,二者不能混为一谈。

升级

从 0.1.0-beta.1 升级前先阅读《升级与回滚指南》。升级脚本会自动备份,并在数据库迁移和 Installation Health 失败时恢复旧版本和数据。


Next Docs Releases 1.1.0 (`next/docs/releases/1.1.0.md`)

WePush Next 1.1.0 — Self-hosted Governance and Provider Ecosystem

1.1.0 是兼容 1.0.0 的稳定 Minor 发行版,重点补齐运营商短信插件、Workspace 资源治理、自建运维、跨 Run 可靠性、大 Artifact 与 WebUI 可用性。WePush 仍然只提供用户自部署软件,不提供托管控制面、遥测或强制自动更新。

主要更新

  • CMPP、SMGP、SGIP、SMPP 以四个独立、Ed25519 签名的 Agent Provider 插件交付。插件不依赖 Classic 源码,也不会把 Core API、Provider SPI、PF4J 或日志 API 打入私有依赖目录。
  • Workspace 可限制 Agent 数、活动 Run 数、总发送并发、Artifact 总容量和默认保留期。策略在运行入口、Agent 绑定、Artifact 创建和清理路径统一执行,拒绝结果进入审计。
  • 系统管理员可下载结构化脱敏诊断包、使用 Nginx/Traefik/Kubernetes 自建模板,并主动检查新版本。版本检查默认不运行,不上传使用数据。
  • 相同账号连续出现认证失败时会跨 Run 熔断;冷却到期自动恢复,管理员也可检查并安全复位。
  • PostgreSQL Server/HA 使用 LISTEN/NOTIFY 加速待调度 Run、Agent Outbox 和 SSE Event 唤醒。通知只是提示;持久 Outbox、事件游标和周期扫描仍是事实源。
  • Agent Artifact 在保留不超过 1 GiB 单次 Presigned Put 的同时,支持 Presigned Multipart、Part 重试、Complete/Abort 和失败清理,最大单文件扩展到 5 TiB。
  • WebUI 增加亮色、暗色和跟随系统主题,并改善 1366×768、键盘焦点、语义标签、颜色对比和减少动画体验。

数据与兼容性

  • REST/SSE 保持 /api/v1;新增端点和字段均为向后兼容扩展。
  • Remote Java SDK 新增 Workspace Policy、诊断包、版本检查和账号熔断接口;已有 1.0.0 SDK 调用继续可用。
  • Provider SPI 与 Agent 协议 Major 仍为 1,支持 1.0.0 Service/Agent 在滚动升级窗口内短期混合运行;新功能需双方升级到 1.1.0。
  • Flyway 从 V14 升至 V17:V15 为 Workspace Policy,V16 为 Account Auth Circuit,V17 为 Artifact Multipart Session。三次迁移只新增表和索引,不改写已有业务数据。

升级步骤

  1. 下载 1.1.0 对应平台发行包和 SHA256SUMS,先校验 SHA-256。
  2. 使用发行包内升级脚本;脚本会在切换前创建完整备份,并在切换后检查 Readiness、Flyway V17 和本地 Provider Dry Run。
  3. 确认原 Workspace、资源、历史 Run 和 Artifact 可读取,Agent 已重连,再执行自有测试目标的 Dry Run 与小规模真实发送。
  4. Server/HA 部署应为 S3 Bucket 配置“24 小时后终止未完成 Multipart Upload”的 Lifecycle 规则;WePush 的会话清理仍会主动 Abort,此规则只做孤儿兜底。
  5. 安装运营商插件前,校验 wepush-provider-trusted-key-1.1.0.env、它的旁路 .sha256 和统一 SHA256SUMS,再把文件中的 WEPUSH_PLUGIN_TRUSTED_KEYS=keyId:Base64Ed25519PublicKey 原样加入 Agent 环境并重启。不要从插件 ZIP 内或第三方页面获取替代公钥。

完整流程见《升级与回滚指南》;配置和兼容承诺见《兼容性策略》。

回滚

V15–V17 为附加迁移,旧 1.0.0 不会使用新增表;仍应通过升级前完整备份回滚,不要只替换 JAR。恢复会同时还原数据库、Master Key、Artifact、Agent Identity/Journal/Outbox 和插件,避免新旧程序状态错配。

发行边界

  • macOS、Windows Desktop 和安装包继续不使用商业代码签名;只从项目 GitHub Release 获取文件,并验证 SHA256SUMS 与 CycloneDX SBOM。
  • 运营商 Provider 插件与应用发行物不同,必须经过 Ed25519 签名和 Agent 内置生产校验器验签。
  • 四个运营商协议当前覆盖主动短信提交、长短信分片和同步提交响应;状态报告与上行短信仍由后续兼容版本扩展。
  • Workspace 配额是自建环境的资源治理功能,不是公共多租户的恶意隔离或计费系统。

Next Docs Releases 1.2.0 (`next/docs/releases/1.2.0.md`)

WePush Next 1.2.0 — AI Assistant Access and Java 25

1.2.0 是兼容 1.1.0 的稳定 Minor 发行版。它让 Codex 等 AI 助手通过用户自己的 WePush 完成查询、准备和授权发送,并把 Java 运行基线升级到 25。WePush 仍然只提供用户自部署软件,不提供托管控制面、遥测或强制自动更新。

主要更新

  • Desktop 可在 设置 → AI 助手接入 一键安装 Codex MCP 与 Skill,也可只安装 Skill,或导出通用 stdio MCP 配置。WebUI 和完整发行包提供同一个 ai/wepush-ai.mjs,用 Node.js 24+ 安装。
  • MCP 由 AI 客户端通过 stdio 启动,再调用本机或用户指定的 WePush REST API。它不新开服务端口,也不把请求转到官方公共平台。
  • 助手可以发现 Provider Schema、查询资源、创建消息/受众/任务、执行 Dry Run、在用户授权后提交正式发送,并查询结果或暂停、恢复、取消运行。正式发送继续使用预览确认令牌、Idempotency-Key 和 Workspace RBAC。
  • 安装器不读取或复制登录 Token。启用认证时,在 AI 客户端环境中设置 WEPUSH_API_TOKEN。
  • Java 构建与运行基线由 21 升级到 25。完整安装包携带 Java 25 运行时;精简包、分组件部署和 Java SDK 需要系统 Java 25+。

数据与兼容性

  • REST/SSE 保持 /api/v1,本版本不新增路径或必填字段。
  • 不新增 Flyway 迁移,数据库版本保持 V17。
  • Provider SPI 与 Agent 协议 Major 仍为 1。1.1.0 与 1.2.0 的 Service/Agent 可在滚动升级窗口内短期混合运行。
  • 运营商插件实现版本更新为 1.2.0。新 Run 应安装同一 Release 的签名插件,并使用该 Release 的信任公钥文件。

升级步骤

  1. 使用精简包或 Java SDK 时,先安装 Java 25+。完整包用户使用包内运行时。
  2. 下载 1.2.0 对应平台发行包和 SHA256SUMS,先校验 SHA-256。
  3. 使用发行包内升级脚本。脚本会在切换前创建完整备份,并在切换后检查 Readiness、Flyway V17 和本地 Provider Dry Run。
  4. 确认 GET /api/v1/system/info 返回 1.2.0,原资源、历史 Run 和 Artifact 可读取,Agent 已重连。
  5. 需要 AI 助手时重新安装 MCP/Skill。1.1.0 安装包没有 wepush-ai.mjs。
  6. 安装运营商插件前,校验 wepush-provider-trusted-key-1.2.0.env、它的旁路 .sha256 和统一 SHA256SUMS,再把其中的 WEPUSH_PLUGIN_TRUSTED_KEYS 原样加入 Agent 环境并重启。

完整流程见《升级与回滚指南》;接入步骤见《AI 助手接入指南》。

回滚

1.2.0 不新增数据表。受支持的回滚目标是 1.1.0,且必须恢复升级前完整备份,不要只替换 JAR。若环境曾经升级过 1.1.0,数据库中已有 V15–V17,不能把当前数据直接交给 1.0.0。

发行边界

  • macOS、Windows Desktop 和安装包继续不使用商业代码签名;只从项目 GitHub Release 获取文件,并验证 SHA256SUMS 与 CycloneDX SBOM。
  • 运营商 Provider 插件仍必须经过 Ed25519 签名和 Agent 内置生产校验器验签。
  • AI 桥接器只允许回环地址使用 HTTP,拒绝 URL 中的密码或查询参数,并拒绝跟随 API 重定向。它不是远程 HTTP MCP,也不提供 OAuth。

--- METRICS ---