feat(push): 可选、配置式、多厂商的推送通道(HMS 为首个实现)
用户要求:推送密钥必须是可选项(自部署后端不能写死推送方式),且要支持 多厂商配置式接入 —— 每个用户各自部署服务器、自己选厂商、自己配凭证。 所以落地成: · internal/push:通道抽象 + 工厂表(RegisterType),加厂商不改配置层与端点形状; HMS 只是第一个实现(internal/push/hms.go) · 配置在 PUSH_CONFIG(默认 <AGENTMAIL_DATA_DIR>/push.json),一项一个厂商, 凭证走文件(app_secret_file / files.*,建议 600);环境变量只是可选覆盖 · 没配 = 整条推送路径连一次查库都不发生(shouldDispatch 早退); 单项配错(未知类型/密钥读不到/enabled:false)只跳过那一条,不影响启动 · push_tokens 表带 provider 维度 + 三个 /me/devices/push-token 端点; 没配推送时端点照存并回 enabled:false(登记成功 != 服务端开了推送) · notify.Recipients 末尾异步挂钩:收件人名单直接用 SSE 那份 seen(两条通道 共用同一份"谁该收到"的判据);失败只记日志,绝不拖住收信 HMS 的形状是拿真凭证打线上接口问出来的(v1 + message.token[] + testMessage; payload/target 形状 v1 不认、v2 要服务账号 JWT)。未上架应用必须 test_message=true, 单批 ≤10 token(MaxTokensPerRequest 声明)、每日 1000 条兜底(项目级额度)。 实测:App ID + App Secret 能换到 access_token(3600s);形状被线上服务接受。 判据:repo 6 条 + push 12 条 + handler 3 组,全部做过**变异验证** —— 过程中抓出两条假判据(异步分发与 t.Cleanup 赛跑而假绿;密钥文件优先级没被覆盖) 并补掉。Go 全量测试与 go vet 干净。 ★ 未验:端到端真机送达(需要真机 token + 客户端按 com.jianf.agentmail 重编并签名, 签名指纹还要在 AGC 登记)—— 从未真正发出过一条能到达设备的推送。 详见 docs/HMS-PUSH-PLAN.md 的「实现状态」一节。
This commit is contained in:
@ -15,6 +15,7 @@ import (
|
||||
"github.com/agentmail/gateway/internal/db"
|
||||
"github.com/agentmail/gateway/internal/handler"
|
||||
"github.com/agentmail/gateway/internal/middleware"
|
||||
"github.com/agentmail/gateway/internal/push"
|
||||
"github.com/agentmail/gateway/internal/repo"
|
||||
"github.com/agentmail/gateway/internal/scheduler"
|
||||
"github.com/agentmail/gateway/internal/static"
|
||||
@ -53,6 +54,12 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalf("Attachment store failed: %v", err)
|
||||
}
|
||||
|
||||
// 推送是**可选、配置式、多厂商**的旁路:配置在 PUSH_CONFIG(默认
|
||||
// <AGENTMAIL_DATA_DIR>/push.json)里,一项一个厂商,凭证可以是密钥文件。
|
||||
// 文件不存在 = 没配 = 正常状态(自部署实例常常如此),此时一个通道都不会注册。
|
||||
// 单项配错只跳过那一项 —— 推送不该有能力让邮件服务起不来。
|
||||
push.Setup()
|
||||
handler.Blobs = blobs
|
||||
fmt.Printf("附件存储:%s(单个上限 %.0f MB)\n",
|
||||
blobs.Root(), float64(cfg.MaxAttachmentBytes)/(1<<20))
|
||||
@ -180,6 +187,13 @@ func main() {
|
||||
r.Post("/me/appearance/image", handler.UploadAppearanceImage)
|
||||
r.Get("/me/appearance/image", handler.GetAppearanceImage)
|
||||
r.Delete("/me/appearance/image", handler.DeleteAppearanceImage)
|
||||
|
||||
// 设备推送登记(可选通道)。没配推送凭证时也照存:管理员之后把凭证
|
||||
// 配上就立刻生效,客户端不必重新登记。响应里的 enabled 告诉客户端
|
||||
// 「服务端现在开没开推送」。
|
||||
r.Post("/me/devices/push-token", handler.RegisterPushToken)
|
||||
r.Delete("/me/devices/push-token", handler.DeletePushToken)
|
||||
r.Get("/me/devices/push-token", handler.ListPushTokens)
|
||||
r.Post("/me/keys", handler.CreateMyKey)
|
||||
r.Get("/me/keys", handler.ListMyKeys)
|
||||
r.Delete("/me/keys/{id}", handler.DeleteMyKey)
|
||||
|
||||
Reference in New Issue
Block a user