Files
smartmate/backend/services/notification/rpc/handler.go
Losita abe3b4960e Version: 0.9.68.dev.260504
后端:
1. 阶段 3 notification 服务边界落地,新增 `cmd/notification`、`services/notification`、`gateway/notification`、`shared/contracts/notification` 和 notification port,按 userauth 同款最小手搓 zrpc 样板收口
2. notification outbox consumer、relay 和 retry loop 迁入独立服务入口,处理 `notification.feishu.requested`,gateway 改为通过 zrpc client 调用 notification
3. 清退旧单体 notification DAO/model/service/provider/runner 和 `service/events/notification_feishu.go`,旧实现不再作为活跃编译路径
4. 修复 outbox 路由归属、dispatch 启动扫描、Kafka topic 探测/投递超时、sending 租约恢复、毒消息 MarkDead 错误回传和 RPC timeout 边界
5. 同步调整 active-scheduler 触发通知事件、核心 outbox handler、MySQL 迁移边界和 notification 配置

文档:
1. 更新微服务迁移计划,将阶段 3 notification 标记为已完成,并明确下一阶段从 active-scheduler 开始
2026-05-04 18:40:39 +08:00

134 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package rpc
import (
"context"
"errors"
"time"
"github.com/LoveLosita/smartflow/backend/respond"
"github.com/LoveLosita/smartflow/backend/services/notification/rpc/pb"
notificationsv "github.com/LoveLosita/smartflow/backend/services/notification/sv"
contracts "github.com/LoveLosita/smartflow/backend/shared/contracts/notification"
)
type Handler struct {
pb.UnimplementedNotificationServer
svc *notificationsv.Service
}
func NewHandler(svc *notificationsv.Service) *Handler {
return &Handler{svc: svc}
}
// GetFeishuWebhook 负责把配置查询请求从 gRPC 协议转成内部服务调用。
//
// 职责边界:
// 1. 只做 transport -> service 的参数搬运,不碰 DAO/provider/outbox 细节;
// 2. 业务错误统一转成 gRPC status让 client 侧继续使用 `res, err :=`
// 3. 成功时只回传业务数据,不在 payload 里塞 status/info。
func (h *Handler) GetFeishuWebhook(ctx context.Context, req *pb.GetFeishuWebhookRequest) (*pb.ChannelResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("notification service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
resp, err := h.svc.GetFeishuWebhook(ctx, int(req.UserId))
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return channelToPB(resp), nil
}
func (h *Handler) SaveFeishuWebhook(ctx context.Context, req *pb.SaveFeishuWebhookRequest) (*pb.ChannelResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("notification service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
resp, err := h.svc.SaveFeishuWebhook(ctx, int(req.UserId), contracts.SaveFeishuWebhookRequest{
UserID: int(req.UserId),
Enabled: req.Enabled,
WebhookURL: req.WebhookUrl,
AuthType: req.AuthType,
BearerToken: req.BearerToken,
})
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return channelToPB(resp), nil
}
func (h *Handler) DeleteFeishuWebhook(ctx context.Context, req *pb.DeleteFeishuWebhookRequest) (*pb.StatusResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("notification service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
if err := h.svc.DeleteFeishuWebhook(ctx, int(req.UserId)); err != nil {
return nil, grpcErrorFromServiceError(err)
}
return &pb.StatusResponse{}, nil
}
func (h *Handler) TestFeishuWebhook(ctx context.Context, req *pb.TestFeishuWebhookRequest) (*pb.TestResult, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("notification service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
resp, err := h.svc.TestFeishuWebhook(ctx, int(req.UserId))
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return testResultToPB(resp), nil
}
func channelToPB(resp contracts.ChannelResponse) *pb.ChannelResponse {
return &pb.ChannelResponse{
Channel: resp.Channel,
Enabled: resp.Enabled,
Configured: resp.Configured,
WebhookUrlMask: resp.WebhookURLMask,
AuthType: resp.AuthType,
HasBearerToken: resp.HasBearerToken,
LastTestStatus: resp.LastTestStatus,
LastTestError: resp.LastTestError,
LastTestAtUnixNano: timePtrToUnixNano(resp.LastTestAt),
}
}
func testResultToPB(resp contracts.TestResult) *pb.TestResult {
return &pb.TestResult{
Channel: channelToPB(resp.Channel),
Status: resp.Status,
Outcome: resp.Outcome,
Message: resp.Message,
TraceId: resp.TraceID,
SentAtUnixNano: timeToUnixNano(resp.SentAt),
Skipped: resp.Skipped,
Provider: resp.Provider,
}
}
func timePtrToUnixNano(value *time.Time) int64 {
if value == nil || value.IsZero() {
return 0
}
return value.UnixNano()
}
func timeToUnixNano(value time.Time) int64 {
if value.IsZero() {
return 0
}
return value.UnixNano()
}