feat(grpc): add gRPC server infrastructure and schedule sync service
Add gRPC server support with configurable TLS, environment-based settings, and Wire injection. Implement schedule synchronization service with task runner integration for external course data fetching. - Add gRPC configuration with env var overrides (APP_GRPC_ENABLED, APP_GRPC_PORT, etc.) - Create gRPC server infrastructure with runner task management - Implement ScheduleSyncService for course data synchronization - Add sync endpoint to schedule handler for external system integration - Update repository with context-aware batch delete operation - Wire all dependencies including zap logger for structured logging
This commit is contained in:
1835
proto/runner/runner.pb.go
Normal file
1835
proto/runner/runner.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
224
proto/runner/runner.proto
Normal file
224
proto/runner/runner.proto
Normal file
@@ -0,0 +1,224 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package runner;
|
||||
|
||||
option go_package = "carrot_bbs/proto/runner;runner";
|
||||
|
||||
// ============================================================
|
||||
// 服务定义
|
||||
// ============================================================
|
||||
|
||||
// RunnerHub 服务 - 由 Backend 服务器实现
|
||||
// Runner 作为客户端主动连接,建立双向流通信
|
||||
service RunnerHub {
|
||||
// Connect 建立双向流连接
|
||||
// Runner 通过此连接注册、接收任务、返回结果
|
||||
rpc Connect(stream StreamMessage) returns (stream StreamMessage);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 流消息包装 - 所有消息通过 oneof 包装
|
||||
// ============================================================
|
||||
|
||||
// StreamMessage 双向流消息包装
|
||||
message StreamMessage {
|
||||
oneof message {
|
||||
RegisterRequest register = 1; // Runner 注册请求
|
||||
RegisterResponse register_response = 2; // 注册响应
|
||||
Heartbeat heartbeat = 3; // 心跳请求
|
||||
HeartbeatAck heartbeat_ack = 4; // 心跳响应
|
||||
Task task = 5; // 下发的任务
|
||||
TaskResult result = 6; // 任务结果
|
||||
TaskCancel task_cancel = 7; // 取消任务
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 注册相关消息
|
||||
// ============================================================
|
||||
|
||||
// RegisterRequest Runner 注册请求
|
||||
message RegisterRequest {
|
||||
string runner_id = 1; // Runner 唯一标识 (UUID)
|
||||
string version = 2; // Runner 版本号
|
||||
map<string, string> capabilities = 3; // 能力标签
|
||||
// 例如: {"login": "true", "schedule": "true", "grades": "true"}
|
||||
}
|
||||
|
||||
// RegisterResponse 注册响应
|
||||
message RegisterResponse {
|
||||
bool success = 1; // 是否成功
|
||||
string session_id = 2; // 会话ID,用于标识本次连接
|
||||
string message = 3; // 消息说明
|
||||
int32 heartbeat_interval = 4; // 心跳间隔 (秒)
|
||||
int32 task_timeout = 5; // 默认任务超时时间 (秒)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 心跳消息
|
||||
// ============================================================
|
||||
|
||||
// Heartbeat 心跳请求
|
||||
message Heartbeat {
|
||||
int64 timestamp = 1; // 客户端时间戳 (毫秒)
|
||||
}
|
||||
|
||||
// HeartbeatAck 心跳响应
|
||||
message HeartbeatAck {
|
||||
int64 timestamp = 1; // 服务端时间戳 (毫秒)
|
||||
int64 latency = 2; // 往返延迟 (毫秒)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 任务定义
|
||||
// ============================================================
|
||||
|
||||
// TaskType 任务类型枚举
|
||||
enum TaskType {
|
||||
TASK_TYPE_UNKNOWN = 0; // 未知类型
|
||||
TASK_TYPE_LOGIN = 1; // 登录验证
|
||||
TASK_TYPE_GET_SCHEDULE = 2; // 获取课表
|
||||
TASK_TYPE_GET_GRADES = 3; // 获取成绩
|
||||
TASK_TYPE_GET_EXAMS = 4; // 获取考试安排
|
||||
TASK_TYPE_GET_USER_INFO = 5; // 获取用户信息
|
||||
}
|
||||
|
||||
// TaskStatus 任务状态枚举
|
||||
enum TaskStatus {
|
||||
TASK_STATUS_UNKNOWN = 0; // 未知状态
|
||||
TASK_STATUS_SUCCESS = 1; // 成功
|
||||
TASK_STATUS_FAILED = 2; // 失败
|
||||
TASK_STATUS_TIMEOUT = 3; // 超时
|
||||
TASK_STATUS_CANCELLED = 4; // 已取消
|
||||
}
|
||||
|
||||
// Task 任务定义
|
||||
message Task {
|
||||
string task_id = 1; // 任务唯一ID (UUID)
|
||||
TaskType type = 2; // 任务类型
|
||||
int64 created_at = 3; // 创建时间戳 (毫秒)
|
||||
int32 timeout_seconds = 4; // 超时时间 (秒)
|
||||
bytes payload = 5; // 任务载荷 (JSON 编码)
|
||||
}
|
||||
|
||||
// TaskCancel 取消任务
|
||||
message TaskCancel {
|
||||
string task_id = 1; // 要取消的任务ID
|
||||
string reason = 2; // 取消原因
|
||||
}
|
||||
|
||||
// TaskResult 任务结果
|
||||
message TaskResult {
|
||||
string task_id = 1; // 任务ID
|
||||
TaskStatus status = 2; // 执行状态
|
||||
int64 completed_at = 3; // 完成时间戳 (毫秒)
|
||||
bytes data = 4; // 结果数据 (JSON 编码)
|
||||
string error_code = 5; // 错误码
|
||||
string error_message = 6; // 错误消息
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 任务载荷定义 - 用于 JSON 序列化到 bytes
|
||||
// ============================================================
|
||||
|
||||
// LoginPayload 登录任务载荷
|
||||
message LoginPayload {
|
||||
string username = 1; // 学号
|
||||
string password = 2; // 密码
|
||||
string twfid = 3; // 初始 Cookie (可选)
|
||||
}
|
||||
|
||||
// GetSchedulePayload 获取课表任务载荷
|
||||
message GetSchedulePayload {
|
||||
string username = 1; // 学号
|
||||
string password = 2; // 密码
|
||||
string semester = 3; // 学期,如 "2025-20262"
|
||||
}
|
||||
|
||||
// GetGradesPayload 获取成绩任务载荷
|
||||
message GetGradesPayload {
|
||||
string username = 1; // 学号
|
||||
string password = 2; // 密码
|
||||
string semester = 3; // 学期 (可选,为空则获取全部)
|
||||
}
|
||||
|
||||
// GetExamsPayload 获取考试安排任务载荷
|
||||
message GetExamsPayload {
|
||||
string username = 1; // 学号
|
||||
string password = 2; // 密码
|
||||
string semester = 3; // 学期
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 结果数据定义 - 用于 JSON 序列化到 bytes
|
||||
// ============================================================
|
||||
|
||||
// ScheduleResultData 课表结果数据
|
||||
message ScheduleResultData {
|
||||
string semester = 1; // 学期
|
||||
string student_id = 2; // 学号
|
||||
string student_name = 3; // 学生姓名
|
||||
repeated Course courses = 4; // 课程列表
|
||||
}
|
||||
|
||||
// Course 课程信息
|
||||
message Course {
|
||||
string code = 1; // 课程代码
|
||||
string name = 2; // 课程名称
|
||||
string teacher = 3; // 授课教师
|
||||
string room = 4; // 教室
|
||||
float credit = 5; // 学分
|
||||
repeated ScheduleTime schedule = 6; // 上课时间安排
|
||||
}
|
||||
|
||||
// ScheduleTime 上课时间安排
|
||||
message ScheduleTime {
|
||||
string day = 1; // 星期几,"0"-"6" 表示周日到周六
|
||||
repeated int32 sections = 2; // 节次,如 [1, 2, 3]
|
||||
repeated int32 weeks = 3; // 周次,如 [1, 2, 3, 4, 5]
|
||||
}
|
||||
|
||||
// GradesResultData 成绩结果数据
|
||||
message GradesResultData {
|
||||
string semester = 1; // 学期
|
||||
string student_id = 2; // 学号
|
||||
repeated Grade grades = 3; // 成绩列表
|
||||
}
|
||||
|
||||
// Grade 成绩信息
|
||||
message Grade {
|
||||
string course_code = 1; // 课程代码
|
||||
string course_name = 2; // 课程名称
|
||||
float credit = 3; // 学分
|
||||
string grade = 4; // 成绩 (可能是等级或分数)
|
||||
float grade_point = 5; // 绩点
|
||||
}
|
||||
|
||||
// ExamsResultData 考试安排结果数据
|
||||
message ExamsResultData {
|
||||
string semester = 1; // 学期
|
||||
string student_id = 2; // 学号
|
||||
repeated Exam exams = 3; // 考试列表
|
||||
}
|
||||
|
||||
// Exam 考试信息
|
||||
message Exam {
|
||||
string course_code = 1; // 课程代码
|
||||
string course_name = 2; // 课程名称
|
||||
string exam_type = 3; // 考试类型 (期末/期中/补考)
|
||||
string date = 4; // 考试日期 (YYYY-MM-DD)
|
||||
string time = 5; // 考试时间 (HH:MM-HH:MM)
|
||||
string room = 6; // 考场
|
||||
string seat = 7; // 座位号
|
||||
}
|
||||
|
||||
// UserInfoResultData 用户信息结果数据
|
||||
message UserInfoResultData {
|
||||
string student_id = 1; // 学号
|
||||
string name = 2; // 姓名
|
||||
string gender = 3; // 性别
|
||||
string college = 4; // 学院
|
||||
string major = 5; // 专业
|
||||
string class_name = 6; // 班级
|
||||
string grade = 7; // 年级
|
||||
}
|
||||
145
proto/runner/runner_grpc.pb.go
Normal file
145
proto/runner/runner_grpc.pb.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc v6.33.1
|
||||
// source: proto/runner/runner.proto
|
||||
|
||||
package runner
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
const (
|
||||
RunnerHub_Connect_FullMethodName = "/runner.RunnerHub/Connect"
|
||||
)
|
||||
|
||||
// RunnerHubClient is the client API for RunnerHub service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type RunnerHubClient interface {
|
||||
// Connect 建立双向流连接
|
||||
// Runner 通过此连接注册、接收任务、返回结果
|
||||
Connect(ctx context.Context, opts ...grpc.CallOption) (RunnerHub_ConnectClient, error)
|
||||
}
|
||||
|
||||
type runnerHubClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewRunnerHubClient(cc grpc.ClientConnInterface) RunnerHubClient {
|
||||
return &runnerHubClient{cc}
|
||||
}
|
||||
|
||||
func (c *runnerHubClient) Connect(ctx context.Context, opts ...grpc.CallOption) (RunnerHub_ConnectClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &RunnerHub_ServiceDesc.Streams[0], RunnerHub_Connect_FullMethodName, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &runnerHubConnectClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type RunnerHub_ConnectClient interface {
|
||||
Send(*StreamMessage) error
|
||||
Recv() (*StreamMessage, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type runnerHubConnectClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *runnerHubConnectClient) Send(m *StreamMessage) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *runnerHubConnectClient) Recv() (*StreamMessage, error) {
|
||||
m := new(StreamMessage)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// RunnerHubServer is the server API for RunnerHub service.
|
||||
// All implementations must embed UnimplementedRunnerHubServer
|
||||
// for forward compatibility
|
||||
type RunnerHubServer interface {
|
||||
// Connect 建立双向流连接
|
||||
// Runner 通过此连接注册、接收任务、返回结果
|
||||
Connect(RunnerHub_ConnectServer) error
|
||||
mustEmbedUnimplementedRunnerHubServer()
|
||||
}
|
||||
|
||||
// UnimplementedRunnerHubServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedRunnerHubServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedRunnerHubServer) Connect(RunnerHub_ConnectServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Connect not implemented")
|
||||
}
|
||||
func (UnimplementedRunnerHubServer) mustEmbedUnimplementedRunnerHubServer() {}
|
||||
|
||||
// UnsafeRunnerHubServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to RunnerHubServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeRunnerHubServer interface {
|
||||
mustEmbedUnimplementedRunnerHubServer()
|
||||
}
|
||||
|
||||
func RegisterRunnerHubServer(s grpc.ServiceRegistrar, srv RunnerHubServer) {
|
||||
s.RegisterService(&RunnerHub_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _RunnerHub_Connect_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(RunnerHubServer).Connect(&runnerHubConnectServer{stream})
|
||||
}
|
||||
|
||||
type RunnerHub_ConnectServer interface {
|
||||
Send(*StreamMessage) error
|
||||
Recv() (*StreamMessage, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type runnerHubConnectServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *runnerHubConnectServer) Send(m *StreamMessage) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *runnerHubConnectServer) Recv() (*StreamMessage, error) {
|
||||
m := new(StreamMessage)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// RunnerHub_ServiceDesc is the grpc.ServiceDesc for RunnerHub service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var RunnerHub_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "runner.RunnerHub",
|
||||
HandlerType: (*RunnerHubServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Connect",
|
||||
Handler: _RunnerHub_Connect_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "proto/runner/runner.proto",
|
||||
}
|
||||
Reference in New Issue
Block a user