Version: 0.9.69.dev.260504

后端:
1. 阶段 4 active-scheduler 服务边界落地,新增 `cmd/active-scheduler`、`services/active_scheduler`、`shared/contracts/activescheduler` 和 active-scheduler port,迁移 dry-run、trigger、preview、confirm zrpc 能力
2. active-scheduler outbox consumer、relay、retry loop 和 due job scanner 迁入独立服务入口,gateway `/active-schedule/*` 改为通过 zrpc client 调用
3. gateway 目录收口为 `gateway/api` + `gateway/client`,统一归档 userauth、notification、active-scheduler 的 HTTP 门面和 zrpc client
4. 将旧 `backend/active_scheduler` 领域核心下沉到 `services/active_scheduler/core`,清退旧根目录活跃实现,并补充 active-scheduler 启动期跨域依赖表检查
5. 调整单体启动与 outbox 归属,`cmd/all` 不再启动 active-scheduler workflow、scanner 或 handler

文档:
1. 更新微服务迁移计划,将阶段 4 active-scheduler 标记为首轮收口完成,并明确下一阶段进入 schedule / task / course / task-class
This commit is contained in:
Losita
2026-05-04 21:01:00 +08:00
parent abe3b4960e
commit 4d9a5c4d30
66 changed files with 2048 additions and 466 deletions

View File

@@ -0,0 +1,53 @@
syntax = "proto3";
package smartflow.active_scheduler;
option go_package = "github.com/LoveLosita/smartflow/backend/services/active_scheduler/rpc/pb";
service ActiveScheduler {
rpc DryRun(ActiveScheduleRequest) returns (JSONResponse);
rpc Trigger(ActiveScheduleRequest) returns (TriggerResponse);
rpc CreatePreview(ActiveScheduleRequest) returns (JSONResponse);
rpc GetPreview(GetPreviewRequest) returns (JSONResponse);
rpc ConfirmPreview(ConfirmPreviewRequest) returns (JSONResponse);
}
message ActiveScheduleRequest {
int64 user_id = 1;
string trigger_type = 2;
string target_type = 3;
int64 target_id = 4;
string feedback_id = 5;
string idempotency_key = 6;
int64 mock_now_unix_nano = 7;
bytes payload_json = 8;
}
message GetPreviewRequest {
int64 user_id = 1;
string preview_id = 2;
}
message ConfirmPreviewRequest {
int64 user_id = 1;
string preview_id = 2;
string candidate_id = 3;
string action = 4;
bytes edited_changes_json = 5;
string idempotency_key = 6;
int64 requested_at_unix_nano = 7;
string trace_id = 8;
}
message JSONResponse {
bytes data_json = 1;
}
message TriggerResponse {
string trigger_id = 1;
string status = 2;
string preview_id = 3;
bool has_preview_id = 4;
bool dedupe_hit = 5;
string trace_id = 6;
}

View File

@@ -0,0 +1,122 @@
package rpc
import (
"errors"
"log"
"strings"
"github.com/LoveLosita/smartflow/backend/respond"
activeapply "github.com/LoveLosita/smartflow/backend/services/active_scheduler/core/apply"
contracts "github.com/LoveLosita/smartflow/backend/shared/contracts/activescheduler"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
activeSchedulerErrorDomain = "smartflow.active_scheduler"
activeSchedulerApplyErrorDomain = "smartflow.active_scheduler.apply"
)
// grpcErrorFromServiceError 负责把 active-scheduler 内部错误收口成 gRPC status。
//
// 职责边界:
// 1. apply 业务错误保留 error_code供 gateway 恢复 confirm/apply 的 HTTP 语义;
// 2. respond.Response 继续按项目内业务码传输;
// 3. 未分类错误只暴露通用内部错误,详细信息留在服务日志。
func grpcErrorFromServiceError(err error) error {
if err == nil {
return nil
}
if applyErr, ok := activeapply.AsApplyError(err); ok {
return grpcErrorFromApplyError(applyErr)
}
var resp respond.Response
if errors.As(err, &resp) {
return grpcErrorFromResponse(resp)
}
log.Printf("active-scheduler rpc internal error: %v", err)
return status.Error(codes.Internal, "active-scheduler service internal error")
}
func grpcErrorFromApplyError(applyErr *activeapply.ApplyError) error {
if applyErr == nil {
return status.Error(codes.Internal, "active-scheduler apply error")
}
message := strings.TrimSpace(applyErr.Message)
if message == "" {
message = string(applyErr.Code)
}
st := status.New(grpcCodeFromApplyErrorCode(applyErr.Code), message)
detail := &errdetails.ErrorInfo{
Domain: activeSchedulerApplyErrorDomain,
Reason: string(applyErr.Code),
Metadata: map[string]string{
"info": message,
},
}
withDetails, err := st.WithDetails(detail)
if err != nil {
return st.Err()
}
return withDetails.Err()
}
func grpcErrorFromResponse(resp respond.Response) error {
code := grpcCodeFromRespondStatus(resp.Status)
message := strings.TrimSpace(resp.Info)
if message == "" {
message = strings.TrimSpace(resp.Status)
}
st := status.New(code, message)
detail := &errdetails.ErrorInfo{
Domain: activeSchedulerErrorDomain,
Reason: resp.Status,
Metadata: map[string]string{
"info": resp.Info,
},
}
withDetails, err := st.WithDetails(detail)
if err != nil {
return st.Err()
}
return withDetails.Err()
}
func grpcCodeFromApplyErrorCode(code activeapply.ErrorCode) codes.Code {
switch contracts.ApplyErrorCode(code) {
case contracts.ApplyErrorCodeForbidden:
return codes.PermissionDenied
case contracts.ApplyErrorCodeTargetNotFound:
return codes.NotFound
case contracts.ApplyErrorCodeDBError:
return codes.Internal
case contracts.ApplyErrorCodeExpired,
contracts.ApplyErrorCodeIdempotencyConflict,
contracts.ApplyErrorCodeBaseVersionChanged,
contracts.ApplyErrorCodeTargetCompleted,
contracts.ApplyErrorCodeTargetAlreadySchedule,
contracts.ApplyErrorCodeSlotConflict,
contracts.ApplyErrorCodeAlreadyApplied:
return codes.FailedPrecondition
default:
return codes.InvalidArgument
}
}
func grpcCodeFromRespondStatus(statusValue string) codes.Code {
switch strings.TrimSpace(statusValue) {
case respond.MissingToken.Status, respond.InvalidToken.Status, respond.InvalidClaims.Status,
respond.ErrUnauthorized.Status, respond.WrongTokenType.Status, respond.UserLoggedOut.Status:
return codes.Unauthenticated
case respond.MissingParam.Status, respond.WrongParamType.Status, respond.ParamTooLong.Status:
return codes.InvalidArgument
}
if strings.HasPrefix(strings.TrimSpace(statusValue), "5") {
return codes.Internal
}
return codes.InvalidArgument
}

View File

@@ -0,0 +1,155 @@
package rpc
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/LoveLosita/smartflow/backend/respond"
"github.com/LoveLosita/smartflow/backend/services/active_scheduler/rpc/pb"
activeschedulersv "github.com/LoveLosita/smartflow/backend/services/active_scheduler/sv"
contracts "github.com/LoveLosita/smartflow/backend/shared/contracts/activescheduler"
)
type Handler struct {
pb.UnimplementedActiveSchedulerServer
svc *activeschedulersv.Service
}
func NewHandler(svc *activeschedulersv.Service) *Handler {
return &Handler{svc: svc}
}
// DryRun 负责把 gRPC 请求转换为主动调度 dry-run 服务调用。
func (h *Handler) DryRun(ctx context.Context, req *pb.ActiveScheduleRequest) (*pb.JSONResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("active-scheduler service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
data, err := h.svc.DryRun(ctx, activeScheduleRequestFromPB(req))
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return jsonResponse(data), nil
}
func (h *Handler) Trigger(ctx context.Context, req *pb.ActiveScheduleRequest) (*pb.TriggerResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("active-scheduler service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
resp, err := h.svc.Trigger(ctx, activeScheduleRequestFromPB(req))
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return triggerResponseToPB(resp), nil
}
func (h *Handler) CreatePreview(ctx context.Context, req *pb.ActiveScheduleRequest) (*pb.JSONResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("active-scheduler service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
data, err := h.svc.CreatePreview(ctx, activeScheduleRequestFromPB(req))
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return jsonResponse(data), nil
}
func (h *Handler) GetPreview(ctx context.Context, req *pb.GetPreviewRequest) (*pb.JSONResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("active-scheduler service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
data, err := h.svc.GetPreview(ctx, contracts.GetPreviewRequest{
UserID: int(req.UserId),
PreviewID: req.PreviewId,
})
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return jsonResponse(data), nil
}
func (h *Handler) ConfirmPreview(ctx context.Context, req *pb.ConfirmPreviewRequest) (*pb.JSONResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("active-scheduler service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
data, err := h.svc.ConfirmPreview(ctx, confirmRequestFromPB(req))
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return jsonResponse(data), nil
}
func activeScheduleRequestFromPB(req *pb.ActiveScheduleRequest) contracts.ActiveScheduleRequest {
var mockNow *time.Time
if req.MockNowUnixNano > 0 {
value := time.Unix(0, req.MockNowUnixNano)
mockNow = &value
}
return contracts.ActiveScheduleRequest{
UserID: int(req.UserId),
TriggerType: req.TriggerType,
TargetType: req.TargetType,
TargetID: int(req.TargetId),
FeedbackID: req.FeedbackId,
IdempotencyKey: req.IdempotencyKey,
MockNow: mockNow,
Payload: json.RawMessage(req.PayloadJson),
}
}
func confirmRequestFromPB(req *pb.ConfirmPreviewRequest) contracts.ConfirmPreviewRequest {
requestedAt := time.Time{}
if req.RequestedAtUnixNano > 0 {
requestedAt = time.Unix(0, req.RequestedAtUnixNano)
}
return contracts.ConfirmPreviewRequest{
UserID: int(req.UserId),
PreviewID: req.PreviewId,
CandidateID: req.CandidateId,
Action: req.Action,
EditedChanges: json.RawMessage(req.EditedChangesJson),
IdempotencyKey: req.IdempotencyKey,
RequestedAt: requestedAt,
TraceID: req.TraceId,
}
}
func triggerResponseToPB(resp *contracts.TriggerResponse) *pb.TriggerResponse {
if resp == nil {
return &pb.TriggerResponse{}
}
previewID := ""
hasPreviewID := false
if resp.PreviewID != nil {
previewID = *resp.PreviewID
hasPreviewID = previewID != ""
}
return &pb.TriggerResponse{
TriggerId: resp.TriggerID,
Status: resp.Status,
PreviewId: previewID,
HasPreviewId: hasPreviewID,
DedupeHit: resp.DedupeHit,
TraceId: resp.TraceID,
}
}
func jsonResponse(data json.RawMessage) *pb.JSONResponse {
return &pb.JSONResponse{DataJson: []byte(data)}
}

View File

@@ -0,0 +1,82 @@
package pb
import proto "github.com/golang/protobuf/proto"
var _ = proto.Marshal
const _ = proto.ProtoPackageIsVersion3
type ActiveScheduleRequest struct {
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
TriggerType string `protobuf:"bytes,2,opt,name=trigger_type,json=triggerType,proto3" json:"trigger_type,omitempty"`
TargetType string `protobuf:"bytes,3,opt,name=target_type,json=targetType,proto3" json:"target_type,omitempty"`
TargetId int64 `protobuf:"varint,4,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty"`
FeedbackId string `protobuf:"bytes,5,opt,name=feedback_id,json=feedbackId,proto3" json:"feedback_id,omitempty"`
IdempotencyKey string `protobuf:"bytes,6,opt,name=idempotency_key,json=idempotencyKey,proto3" json:"idempotency_key,omitempty"`
MockNowUnixNano int64 `protobuf:"varint,7,opt,name=mock_now_unix_nano,json=mockNowUnixNano,proto3" json:"mock_now_unix_nano,omitempty"`
PayloadJson []byte `protobuf:"bytes,8,opt,name=payload_json,json=payloadJson,proto3" json:"payload_json,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ActiveScheduleRequest) Reset() { *m = ActiveScheduleRequest{} }
func (m *ActiveScheduleRequest) String() string { return proto.CompactTextString(m) }
func (*ActiveScheduleRequest) ProtoMessage() {}
type GetPreviewRequest struct {
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
PreviewId string `protobuf:"bytes,2,opt,name=preview_id,json=previewId,proto3" json:"preview_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *GetPreviewRequest) Reset() { *m = GetPreviewRequest{} }
func (m *GetPreviewRequest) String() string { return proto.CompactTextString(m) }
func (*GetPreviewRequest) ProtoMessage() {}
type ConfirmPreviewRequest struct {
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
PreviewId string `protobuf:"bytes,2,opt,name=preview_id,json=previewId,proto3" json:"preview_id,omitempty"`
CandidateId string `protobuf:"bytes,3,opt,name=candidate_id,json=candidateId,proto3" json:"candidate_id,omitempty"`
Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"`
EditedChangesJson []byte `protobuf:"bytes,5,opt,name=edited_changes_json,json=editedChangesJson,proto3" json:"edited_changes_json,omitempty"`
IdempotencyKey string `protobuf:"bytes,6,opt,name=idempotency_key,json=idempotencyKey,proto3" json:"idempotency_key,omitempty"`
RequestedAtUnixNano int64 `protobuf:"varint,7,opt,name=requested_at_unix_nano,json=requestedAtUnixNano,proto3" json:"requested_at_unix_nano,omitempty"`
TraceId string `protobuf:"bytes,8,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ConfirmPreviewRequest) Reset() { *m = ConfirmPreviewRequest{} }
func (m *ConfirmPreviewRequest) String() string { return proto.CompactTextString(m) }
func (*ConfirmPreviewRequest) ProtoMessage() {}
type JSONResponse struct {
DataJson []byte `protobuf:"bytes,1,opt,name=data_json,json=dataJson,proto3" json:"data_json,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *JSONResponse) Reset() { *m = JSONResponse{} }
func (m *JSONResponse) String() string { return proto.CompactTextString(m) }
func (*JSONResponse) ProtoMessage() {}
type TriggerResponse struct {
TriggerId string `protobuf:"bytes,1,opt,name=trigger_id,json=triggerId,proto3" json:"trigger_id,omitempty"`
Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
PreviewId string `protobuf:"bytes,3,opt,name=preview_id,json=previewId,proto3" json:"preview_id,omitempty"`
HasPreviewId bool `protobuf:"varint,4,opt,name=has_preview_id,json=hasPreviewId,proto3" json:"has_preview_id,omitempty"`
DedupeHit bool `protobuf:"varint,5,opt,name=dedupe_hit,json=dedupeHit,proto3" json:"dedupe_hit,omitempty"`
TraceId string `protobuf:"bytes,6,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *TriggerResponse) Reset() { *m = TriggerResponse{} }
func (m *TriggerResponse) String() string { return proto.CompactTextString(m) }
func (*TriggerResponse) ProtoMessage() {}

View File

@@ -0,0 +1,201 @@
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
const (
ActiveScheduler_DryRun_FullMethodName = "/smartflow.active_scheduler.ActiveScheduler/DryRun"
ActiveScheduler_Trigger_FullMethodName = "/smartflow.active_scheduler.ActiveScheduler/Trigger"
ActiveScheduler_CreatePreview_FullMethodName = "/smartflow.active_scheduler.ActiveScheduler/CreatePreview"
ActiveScheduler_GetPreview_FullMethodName = "/smartflow.active_scheduler.ActiveScheduler/GetPreview"
ActiveScheduler_ConfirmPreview_FullMethodName = "/smartflow.active_scheduler.ActiveScheduler/ConfirmPreview"
)
type ActiveSchedulerClient interface {
DryRun(ctx context.Context, in *ActiveScheduleRequest, opts ...grpc.CallOption) (*JSONResponse, error)
Trigger(ctx context.Context, in *ActiveScheduleRequest, opts ...grpc.CallOption) (*TriggerResponse, error)
CreatePreview(ctx context.Context, in *ActiveScheduleRequest, opts ...grpc.CallOption) (*JSONResponse, error)
GetPreview(ctx context.Context, in *GetPreviewRequest, opts ...grpc.CallOption) (*JSONResponse, error)
ConfirmPreview(ctx context.Context, in *ConfirmPreviewRequest, opts ...grpc.CallOption) (*JSONResponse, error)
}
type activeSchedulerClient struct {
cc grpc.ClientConnInterface
}
func NewActiveSchedulerClient(cc grpc.ClientConnInterface) ActiveSchedulerClient {
return &activeSchedulerClient{cc}
}
func (c *activeSchedulerClient) DryRun(ctx context.Context, in *ActiveScheduleRequest, opts ...grpc.CallOption) (*JSONResponse, error) {
out := new(JSONResponse)
err := c.cc.Invoke(ctx, ActiveScheduler_DryRun_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *activeSchedulerClient) Trigger(ctx context.Context, in *ActiveScheduleRequest, opts ...grpc.CallOption) (*TriggerResponse, error) {
out := new(TriggerResponse)
err := c.cc.Invoke(ctx, ActiveScheduler_Trigger_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *activeSchedulerClient) CreatePreview(ctx context.Context, in *ActiveScheduleRequest, opts ...grpc.CallOption) (*JSONResponse, error) {
out := new(JSONResponse)
err := c.cc.Invoke(ctx, ActiveScheduler_CreatePreview_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *activeSchedulerClient) GetPreview(ctx context.Context, in *GetPreviewRequest, opts ...grpc.CallOption) (*JSONResponse, error) {
out := new(JSONResponse)
err := c.cc.Invoke(ctx, ActiveScheduler_GetPreview_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *activeSchedulerClient) ConfirmPreview(ctx context.Context, in *ConfirmPreviewRequest, opts ...grpc.CallOption) (*JSONResponse, error) {
out := new(JSONResponse)
err := c.cc.Invoke(ctx, ActiveScheduler_ConfirmPreview_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
type ActiveSchedulerServer interface {
DryRun(context.Context, *ActiveScheduleRequest) (*JSONResponse, error)
Trigger(context.Context, *ActiveScheduleRequest) (*TriggerResponse, error)
CreatePreview(context.Context, *ActiveScheduleRequest) (*JSONResponse, error)
GetPreview(context.Context, *GetPreviewRequest) (*JSONResponse, error)
ConfirmPreview(context.Context, *ConfirmPreviewRequest) (*JSONResponse, error)
}
type UnimplementedActiveSchedulerServer struct{}
func (UnimplementedActiveSchedulerServer) DryRun(context.Context, *ActiveScheduleRequest) (*JSONResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DryRun not implemented")
}
func (UnimplementedActiveSchedulerServer) Trigger(context.Context, *ActiveScheduleRequest) (*TriggerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Trigger not implemented")
}
func (UnimplementedActiveSchedulerServer) CreatePreview(context.Context, *ActiveScheduleRequest) (*JSONResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreatePreview not implemented")
}
func (UnimplementedActiveSchedulerServer) GetPreview(context.Context, *GetPreviewRequest) (*JSONResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetPreview not implemented")
}
func (UnimplementedActiveSchedulerServer) ConfirmPreview(context.Context, *ConfirmPreviewRequest) (*JSONResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConfirmPreview not implemented")
}
func RegisterActiveSchedulerServer(s grpc.ServiceRegistrar, srv ActiveSchedulerServer) {
s.RegisterService(&ActiveScheduler_ServiceDesc, srv)
}
func _ActiveScheduler_DryRun_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ActiveScheduleRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ActiveSchedulerServer).DryRun(ctx, in)
}
info := &grpc.UnaryServerInfo{Server: srv, FullMethod: ActiveScheduler_DryRun_FullMethodName}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ActiveSchedulerServer).DryRun(ctx, req.(*ActiveScheduleRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ActiveScheduler_Trigger_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ActiveScheduleRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ActiveSchedulerServer).Trigger(ctx, in)
}
info := &grpc.UnaryServerInfo{Server: srv, FullMethod: ActiveScheduler_Trigger_FullMethodName}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ActiveSchedulerServer).Trigger(ctx, req.(*ActiveScheduleRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ActiveScheduler_CreatePreview_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ActiveScheduleRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ActiveSchedulerServer).CreatePreview(ctx, in)
}
info := &grpc.UnaryServerInfo{Server: srv, FullMethod: ActiveScheduler_CreatePreview_FullMethodName}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ActiveSchedulerServer).CreatePreview(ctx, req.(*ActiveScheduleRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ActiveScheduler_GetPreview_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetPreviewRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ActiveSchedulerServer).GetPreview(ctx, in)
}
info := &grpc.UnaryServerInfo{Server: srv, FullMethod: ActiveScheduler_GetPreview_FullMethodName}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ActiveSchedulerServer).GetPreview(ctx, req.(*GetPreviewRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ActiveScheduler_ConfirmPreview_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ConfirmPreviewRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ActiveSchedulerServer).ConfirmPreview(ctx, in)
}
info := &grpc.UnaryServerInfo{Server: srv, FullMethod: ActiveScheduler_ConfirmPreview_FullMethodName}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ActiveSchedulerServer).ConfirmPreview(ctx, req.(*ConfirmPreviewRequest))
}
return interceptor(ctx, in, info, handler)
}
var ActiveScheduler_ServiceDesc = grpc.ServiceDesc{
ServiceName: "smartflow.active_scheduler.ActiveScheduler",
HandlerType: (*ActiveSchedulerServer)(nil),
Methods: []grpc.MethodDesc{
{MethodName: "DryRun", Handler: _ActiveScheduler_DryRun_Handler},
{MethodName: "Trigger", Handler: _ActiveScheduler_Trigger_Handler},
{MethodName: "CreatePreview", Handler: _ActiveScheduler_CreatePreview_Handler},
{MethodName: "GetPreview", Handler: _ActiveScheduler_GetPreview_Handler},
{MethodName: "ConfirmPreview", Handler: _ActiveScheduler_ConfirmPreview_Handler},
},
Streams: []grpc.StreamDesc{},
Metadata: "services/active_scheduler/rpc/active_scheduler.proto",
}

View File

@@ -0,0 +1,60 @@
package rpc
import (
"errors"
"strings"
"time"
"github.com/LoveLosita/smartflow/backend/services/active_scheduler/rpc/pb"
activeschedulersv "github.com/LoveLosita/smartflow/backend/services/active_scheduler/sv"
"github.com/zeromicro/go-zero/core/service"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
)
const (
defaultListenOn = "0.0.0.0:9083"
defaultTimeout = 8 * time.Second
)
type ServerOptions struct {
ListenOn string
Timeout time.Duration
Service *activeschedulersv.Service
}
// NewServer 创建 active-scheduler zrpc 服务端。
//
// 职责边界:
// 1. 只负责 zrpc server 配置与 gRPC handler 注册;
// 2. 不创建数据库、LLM、outbox 或 worker它们由 cmd/active-scheduler 管理;
// 3. 返回 listenOn 供进程入口打印启动日志。
func NewServer(opts ServerOptions) (*zrpc.RpcServer, string, error) {
if opts.Service == nil {
return nil, "", errors.New("active-scheduler service dependency not initialized")
}
listenOn := strings.TrimSpace(opts.ListenOn)
if listenOn == "" {
listenOn = defaultListenOn
}
timeout := opts.Timeout
if timeout <= 0 {
timeout = defaultTimeout
}
server, err := zrpc.NewServer(zrpc.RpcServerConf{
ServiceConf: service.ServiceConf{
Name: "active-scheduler.rpc",
Mode: service.DevMode,
},
ListenOn: listenOn,
Timeout: int64(timeout / time.Millisecond),
}, func(grpcServer *grpc.Server) {
pb.RegisterActiveSchedulerServer(grpcServer, NewHandler(opts.Service))
})
if err != nil {
return nil, "", err
}
return server, listenOn, nil
}