WePush Next 发布了全新的多组件自部署版本。它关注的不是把所有能力堆进一个大服务,而是围绕批量任务执行、消息渠道扩展和部署边界进行拆分,让开发者可以按实际场景组合 Core、Provider、Service 和 UI 等组件。
这种设计对批量推送工具尤其重要:任务执行需要并发控制、重试和暂停恢复,渠道接入需要账号校验与消息渲染,而用户界面和网络 API 又属于完全不同的运行边界。将它们拆开后,核心执行能力可以独立测试、复用和演进。
Core 只负责把任务可靠地跑完
Core / Engine 是一个纯 Java 执行内核,主要职责包括:
- 执行批量任务
- 管理并发度
- 处理失败重试
- 支持暂停、恢复和取消
- 保存任务结果
- 产生运行事件
它不直接提供网络 API,也不依赖 Service、UI 或具体 Provider。这个边界很关键:Core 不需要知道任务是从命令行、Web 页面还是其他系统提交的,也不需要把某个具体消息渠道的实现写死在执行逻辑里。
可以把 Core 理解成一个稳定的任务运行时。上层负责把用户输入转换成任务,下层通过 Provider SPI 负责真正发送消息,Core 则只关心任务生命周期和执行过程。
Provider SPI 把渠道差异隔离起来
Provider SPI / Provider 定义了消息渠道的扩展契约,主要覆盖三类能力:
- 账号或配置校验
- 消息渲染
- 实际发送
Core 只面向 SPI,而不是直接依赖某个 HTTP 客户端、平台 SDK 或渠道账号模型。这样做可以避免每增加一种渠道,就修改 Core 的调度与重试逻辑。
一个简化的 SPI 可以这样设计。下面的代码是可独立编译运行的示意实现,实际项目中可以根据消息类型、错误分类和配置模型继续扩展。
import java.util.List;
import java.util.Map;
public class PushSpiDemo {
record Message(String title, String content) {}
record RenderedMessage(String body) {}
interface Provider {
String id();
void validateAccount(Map<String, String> config);
RenderedMessage render(Message message);
SendResult send(RenderedMessage message, String recipient);
}
record SendResult(boolean success, String detail) {}
static final class HttpProvider implements Provider {
@Override
public String id() {
return "http";
}
@Override
public void validateAccount(Map<String, String> config) {
if (!config.containsKey("endpoint")) {
throw new IllegalArgumentException("missing endpoint");
}
}
@Override
public RenderedMessage render(Message message) {
return new RenderedMessage(
"{\"title\":\"" + message.title() +
"\",\"content\":\"" + message.content() + "\"}"
);
}
@Override
public SendResult send(RenderedMessage message, String recipient) {
// 实际实现中在这里调用 HTTP 客户端。
return new SendResult(true, "sent to " + recipient);
}
}
static final class PushEngine {
private final Provider provider;
PushEngine(Provider provider) {
this.provider = provider;
}
void run(Message message, List<String> recipients) {
RenderedMessage rendered = provider.render(message);
for (String recipient : recipients) {
SendResult result = provider.send(rendered, recipient);
System.out.printf("provider=%s recipient=%s success=%s detail=%s%n",
provider.id(), recipient, result.success(), result.detail());
}
}
}
public static void main(String[] args) {
Provider provider = new HttpProvider();
provider.validateAccount(Map.of("endpoint", "https://example.invalid/push"));
new PushEngine(provider).run(
new Message("系统通知", "批量任务已完成"),
List.of("user-001", "user-002")
);
}
}
运行方式:
javac PushSpiDemo.java
java PushSpiDemo
示例中 PushEngine 没有依赖 HttpProvider 的具体类型,只依赖 Provider 接口。新增渠道时,可以实现新的 Provider;调整并发、重试或任务状态时,也不必把渠道代码复制到 Core 中。
需要注意的是,示例为了保持短小,省略了 JSON 转义、网络超时、响应解析和错误分类。生产实现不能直接拼接 JSON,也应把可重试错误与不可重试错误区分开。
多组件自部署意味着边界更清楚
多组件部署并不只是把一个程序拆成多个进程。真正的价值在于明确每个组件的责任和依赖方向:
- Core / Engine:执行任务,不依赖 UI、Service 或具体渠道。
- Provider SPI:定义扩展契约,连接 Core 与渠道实现。
- Provider:负责具体账号、消息格式和发送动作。
- Service:如果部署方案提供该层,可以承担网络 API、任务提交和外部系统集成。
- UI:如果部署方案提供该层,可以承担任务配置、运行状态查看和结果展示。
这种结构允许不同部署形态共存。例如,自动化环境可以只使用 Core 加 Provider;需要团队操作时,再增加 Service 和 UI。组件越少,部署面和维护成本越低;组件越完整,交互和集成能力越强。
但拆分也带来新的工程问题:配置分发、组件版本兼容、任务状态存储、日志关联和故障定位都需要明确约定。自部署用户应特别关注这些边界,而不是只看启动命令是否成功。
可以这样规划一次落地
下面是一份适合评估 WePush Next 部署方式的最小清单:
# 1. 明确运行模式:仅执行内核,或增加 API/UI
export WEPUSH_MODE=core
# 2. 为每个 Provider 准备独立配置
export WEPUSH_PROVIDER=http
export WEPUSH_HTTP_ENDPOINT="https://push.example.com/send"
# 3. 先验证账号与渠道配置,再提交批量任务
./wepush validate --provider "$WEPUSH_PROVIDER"
./wepush run --provider "$WEPUSH_PROVIDER" --input recipients.txt
上面的命令是部署流程示意,具体参数需要以实际发行包提供的命令和配置为准。实践中建议按以下顺序推进:
- 先单独验证 Provider 的账号和发送能力。
- 再验证 Core 的并发、暂停、恢复、取消和重试行为。
- 最后接入 Service 或 UI,确认任务提交与运行事件可以被完整追踪。
- 为每个任务记录 Provider、批次标识、成功数、失败数和失败原因。
- 对发送接口设置超时、限流和幂等策略,避免重试造成重复消息。
采用建议
WePush Next 的核心取舍是:用更清晰的组件边界换取更灵活的部署和扩展能力。对单机、低频使用者,完整组件组合可能显得过重;对需要批量任务管理、多渠道扩展或自有基础设施部署的团队,Core 与 Provider SPI 的解耦更有长期价值。
落地时可以从最小组合开始:先运行 Core 和所需 Provider,确认任务生命周期与失败处理符合预期,再按需要增加 API 和 UI。把渠道实现、任务执行和交互层分开验证,通常比一次性启动全部组件更容易定位问题,也更适合后续升级。