refactor(grpc): restructure task handling and migrate to slog
All checks were successful
Build / build (push) Successful in 5m28s
All checks were successful
Build / build (push) Successful in 5m28s
Refactor the gRPC client and task handling logic to improve modularity, error handling, and observability. - Split `grpc/handler.go` into specialized handler files (login, classroom, exams, grades, schedule) to reduce complexity. - Replace standard `log` with `log/slog` throughout the project for structured logging. - Refactor `grpc/client.go` to use a thread-safe connection management pattern with `sync.RWMutex`. - Remove the legacy `service` package and HTTP server implementation, transitioning the application to a pure gRPC runner mode. - Clean up `config` and `pkg/errors` to remove unused utilities and simplify the API. - Improve error reporting in parsers and HTTP clients by checking response status codes.
This commit is contained in:
146
grpc/client.go
146
grpc/client.go
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -14,17 +14,20 @@ import (
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
const defaultHeartbeatInterval = 30 * time.Second
|
||||
|
||||
// Client gRPC 客户端
|
||||
type Client struct {
|
||||
serverAddr string
|
||||
runnerID string
|
||||
version string
|
||||
conn *grpc.ClientConn
|
||||
stream pb.RunnerHub_ConnectClient
|
||||
mu sync.Mutex
|
||||
handler *TaskHandler
|
||||
|
||||
// 配置
|
||||
mu sync.RWMutex
|
||||
conn *grpc.ClientConn
|
||||
stream pb.RunnerHub_ConnectClient
|
||||
connected bool
|
||||
|
||||
handler *TaskHandler
|
||||
heartbeatInterval time.Duration
|
||||
}
|
||||
|
||||
@@ -34,7 +37,7 @@ func NewClient(serverAddr, runnerID, version string) *Client {
|
||||
serverAddr: serverAddr,
|
||||
runnerID: runnerID,
|
||||
version: version,
|
||||
heartbeatInterval: 30 * time.Second,
|
||||
heartbeatInterval: defaultHeartbeatInterval,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,27 +48,31 @@ func (c *Client) SetHandler(handler *TaskHandler) {
|
||||
|
||||
// Connect 连接到 gRPC 服务器
|
||||
func (c *Client) Connect(ctx context.Context) error {
|
||||
log.Printf("正在连接到 gRPC 服务器: %s", c.serverAddr)
|
||||
slog.Info("正在连接 gRPC 服务器", "server", c.serverAddr)
|
||||
|
||||
conn, err := grpc.Dial(c.serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接失败: %w", err)
|
||||
}
|
||||
c.conn = conn
|
||||
|
||||
client := pb.NewRunnerHubClient(conn)
|
||||
stream, err := client.Connect(ctx)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return fmt.Errorf("创建流失败: %w", err)
|
||||
}
|
||||
c.stream = stream
|
||||
|
||||
// 发送注册请求
|
||||
c.mu.Lock()
|
||||
c.conn = conn
|
||||
c.stream = stream
|
||||
c.connected = true
|
||||
c.mu.Unlock()
|
||||
|
||||
if err := c.register(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("gRPC 连接成功")
|
||||
slog.Info("gRPC 连接已建立")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -80,33 +87,41 @@ func (c *Client) register() error {
|
||||
"TASK_TYPE_GET_SCHEDULE": "true",
|
||||
"TASK_TYPE_GET_GRADES": "true",
|
||||
"TASK_TYPE_GET_EXAMS": "true",
|
||||
"TASK_TYPE_LOGIN": "true",
|
||||
"TASK_TYPE_GET_EMPTY_CLASSROOM": "true",
|
||||
"TASK_TYPE_LOGIN": "true",
|
||||
"TASK_TYPE_GET_EMPTY_CLASSROOM": "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return c.stream.Send(msg)
|
||||
return c.sendMessage(msg)
|
||||
}
|
||||
|
||||
// Run 运行主循环,接收消息
|
||||
func (c *Client) Run(ctx context.Context) error {
|
||||
defer c.markDisconnected()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
msg, err := c.stream.Recv()
|
||||
if err == io.EOF {
|
||||
log.Println("服务器关闭了连接")
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("接收消息失败: %w", err)
|
||||
}
|
||||
|
||||
c.handleMessage(msg)
|
||||
}
|
||||
|
||||
stream := c.getStream()
|
||||
if stream == nil {
|
||||
return fmt.Errorf("流未连接")
|
||||
}
|
||||
|
||||
msg, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
slog.Info("服务器关闭了连接")
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("接收消息失败: %w", err)
|
||||
}
|
||||
|
||||
c.handleMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,63 +129,52 @@ func (c *Client) Run(ctx context.Context) error {
|
||||
func (c *Client) handleMessage(msg *pb.StreamMessage) {
|
||||
switch m := msg.Message.(type) {
|
||||
case *pb.StreamMessage_RegisterResponse:
|
||||
log.Printf("注册成功: %s, 会话ID: %s, 心跳间隔: %d秒",
|
||||
m.RegisterResponse.Message,
|
||||
m.RegisterResponse.SessionId,
|
||||
m.RegisterResponse.HeartbeatInterval)
|
||||
slog.Info("注册成功",
|
||||
"message", m.RegisterResponse.Message,
|
||||
"session_id", m.RegisterResponse.SessionId,
|
||||
"heartbeat_interval_sec", m.RegisterResponse.HeartbeatInterval,
|
||||
)
|
||||
if m.RegisterResponse.HeartbeatInterval > 0 {
|
||||
c.heartbeatInterval = time.Duration(m.RegisterResponse.HeartbeatInterval) * time.Second
|
||||
}
|
||||
case *pb.StreamMessage_HeartbeatAck:
|
||||
// 心跳确认,可以用于计算延迟
|
||||
log.Printf("心跳确认,延迟: %dms", m.HeartbeatAck.Latency)
|
||||
slog.Debug("心跳确认", "latency_ms", m.HeartbeatAck.Latency)
|
||||
case *pb.StreamMessage_Task:
|
||||
log.Printf("收到任务: %s, 类型: %v", m.Task.TaskId, m.Task.Type)
|
||||
slog.Info("收到任务", "task_id", m.Task.TaskId, "type", m.Task.Type.String())
|
||||
go c.handleTask(m.Task)
|
||||
case *pb.StreamMessage_TaskCancel:
|
||||
log.Printf("收到取消任务请求: %s, 原因: %s", m.TaskCancel.TaskId, m.TaskCancel.Reason)
|
||||
slog.Info("收到取消任务请求", "task_id", m.TaskCancel.TaskId, "reason", m.TaskCancel.Reason)
|
||||
if c.handler != nil {
|
||||
c.handler.CancelTask(m.TaskCancel.TaskId)
|
||||
}
|
||||
default:
|
||||
log.Printf("收到未知消息类型: %T", msg.Message)
|
||||
slog.Warn("收到未知消息类型", "type", fmt.Sprintf("%T", msg.Message))
|
||||
}
|
||||
}
|
||||
|
||||
// handleTask 处理任务
|
||||
func (c *Client) handleTask(task *pb.Task) {
|
||||
if c.handler == nil {
|
||||
log.Println("错误: 未设置任务处理器")
|
||||
slog.Error("未设置任务处理器")
|
||||
return
|
||||
}
|
||||
|
||||
result := c.handler.Execute(task)
|
||||
|
||||
c.mu.Lock()
|
||||
err := c.stream.Send(&pb.StreamMessage{
|
||||
Message: &pb.StreamMessage_Result{
|
||||
Result: result,
|
||||
},
|
||||
err := c.sendMessage(&pb.StreamMessage{
|
||||
Message: &pb.StreamMessage_Result{Result: result},
|
||||
})
|
||||
c.mu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("发送任务结果失败: %v", err)
|
||||
} else {
|
||||
log.Printf("任务结果已发送: %s, 状态: %v", result.TaskId, result.Status)
|
||||
slog.Error("发送任务结果失败", "task_id", result.TaskId, "error", err)
|
||||
return
|
||||
}
|
||||
slog.Info("任务结果已发送", "task_id", result.TaskId, "status", result.Status.String())
|
||||
}
|
||||
|
||||
// sendHeartbeat 发送心跳
|
||||
func (c *Client) sendHeartbeat() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.stream == nil {
|
||||
return fmt.Errorf("流未连接")
|
||||
}
|
||||
|
||||
return c.stream.Send(&pb.StreamMessage{
|
||||
return c.sendMessage(&pb.StreamMessage{
|
||||
Message: &pb.StreamMessage_Heartbeat{
|
||||
Heartbeat: &pb.Heartbeat{
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
@@ -190,7 +194,7 @@ func (c *Client) HeartbeatLoop(ctx context.Context, interval time.Duration) {
|
||||
return
|
||||
case <-ticker.C:
|
||||
if err := c.sendHeartbeat(); err != nil {
|
||||
log.Printf("发送心跳失败: %v", err)
|
||||
slog.Warn("发送心跳失败", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,11 +202,43 @@ func (c *Client) HeartbeatLoop(ctx context.Context, interval time.Duration) {
|
||||
|
||||
// Close 关闭连接
|
||||
func (c *Client) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.connected = false
|
||||
if c.stream != nil {
|
||||
c.stream.CloseSend()
|
||||
_ = c.stream.CloseSend()
|
||||
}
|
||||
if c.conn != nil {
|
||||
return c.conn.Close()
|
||||
err := c.conn.Close()
|
||||
c.conn = nil
|
||||
c.stream = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendMessage 加锁发送消息,确保 stream 仍然可用
|
||||
func (c *Client) sendMessage(msg *pb.StreamMessage) error {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if !c.connected || c.stream == nil {
|
||||
return fmt.Errorf("流未连接")
|
||||
}
|
||||
return c.stream.Send(msg)
|
||||
}
|
||||
|
||||
// getStream 读锁获取 stream
|
||||
func (c *Client) getStream() pb.RunnerHub_ConnectClient {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.stream
|
||||
}
|
||||
|
||||
// markDisconnected 标记连接已断开,避免后续发送
|
||||
func (c *Client) markDisconnected() {
|
||||
c.mu.Lock()
|
||||
c.connected = false
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user