2026-03-13 20:43:44 +08:00
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"flag"
|
2026-03-15 21:49:10 +08:00
|
|
|
|
"io"
|
2026-03-13 20:43:44 +08:00
|
|
|
|
"log"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"os/signal"
|
|
|
|
|
|
"syscall"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"jwts/auth"
|
|
|
|
|
|
"jwts/captcha"
|
|
|
|
|
|
"jwts/client"
|
|
|
|
|
|
"jwts/config"
|
|
|
|
|
|
grpcClient "jwts/grpc"
|
|
|
|
|
|
"jwts/parser"
|
2026-03-15 21:49:10 +08:00
|
|
|
|
"jwts/recognizer"
|
2026-03-13 20:43:44 +08:00
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
mode := flag.String("mode", "http", "运行模式: http 或 grpc")
|
|
|
|
|
|
grpcAddr := flag.String("grpc-addr", "localhost:50051", "gRPC 服务器地址")
|
|
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
|
|
|
|
if *mode == "grpc" {
|
|
|
|
|
|
runGRPCClient(*grpcAddr)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
runHTTPServer()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// runGRPCClient 运行 gRPC 客户端模式
|
|
|
|
|
|
func runGRPCClient(serverAddr string) {
|
|
|
|
|
|
runnerID := os.Getenv("RUNNER_ID")
|
|
|
|
|
|
if runnerID == "" {
|
|
|
|
|
|
hostname, _ := os.Hostname()
|
|
|
|
|
|
runnerID = "runner-" + hostname
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
version := os.Getenv("RUNNER_VERSION")
|
|
|
|
|
|
if version == "" {
|
|
|
|
|
|
version = "1.0.0"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Printf("启动 gRPC 客户端, Runner ID: %s, 版本: %s", runnerID, version)
|
|
|
|
|
|
|
|
|
|
|
|
client := grpcClient.NewClient(serverAddr, runnerID, version)
|
|
|
|
|
|
handler := grpcClient.NewTaskHandler()
|
|
|
|
|
|
client.SetHandler(handler)
|
|
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
|
|
// 处理信号
|
|
|
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
|
|
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
|
|
|
|
|
|
|
|
if err := client.Connect(ctx); err != nil {
|
|
|
|
|
|
log.Printf("连接失败: %v", err)
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
<-sigChan
|
|
|
|
|
|
log.Println("正在关闭...")
|
|
|
|
|
|
client.Close()
|
|
|
|
|
|
cancel()
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
// 启动心跳
|
|
|
|
|
|
go client.HeartbeatLoop(ctx, 30*time.Second)
|
|
|
|
|
|
|
|
|
|
|
|
// 运行主循环
|
|
|
|
|
|
if err := client.Run(ctx); err != nil {
|
|
|
|
|
|
log.Printf("客户端错误: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// runHTTPServer 运行 HTTP 服务器模式
|
|
|
|
|
|
func runHTTPServer() {
|
|
|
|
|
|
// 初始化客户端
|
|
|
|
|
|
client.InitClient()
|
|
|
|
|
|
|
|
|
|
|
|
r := gin.Default()
|
|
|
|
|
|
|
|
|
|
|
|
// 健康检查接口
|
|
|
|
|
|
r.GET("/ping", func(c *gin.Context) {
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
|
|
"message": "pong",
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-03-15 21:49:10 +08:00
|
|
|
|
// 课表图片识别接口
|
|
|
|
|
|
r.POST("/recognize", func(c *gin.Context) {
|
|
|
|
|
|
file, header, err := c.Request.FormFile("image")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请上传图片文件", "details": err.Error()})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
|
|
log.Printf("收到图片上传: %s, 大小: %d bytes", header.Filename, header.Size)
|
|
|
|
|
|
|
2026-03-16 13:02:57 +08:00
|
|
|
|
if header.Size > 10*1024*1024 {
|
|
|
|
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "图片文件过大", "max_size": "10MB"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-15 21:49:10 +08:00
|
|
|
|
imageData, err := io.ReadAll(file)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "读取图片失败", "details": err.Error()})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
courses, err := recognizer.RecognizeFromBytes(imageData)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "识别失败", "details": err.Error()})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
recognizer.PrintCourses(courses)
|
|
|
|
|
|
|
|
|
|
|
|
err = recognizer.SaveToJSON(courses, "schedule_result.json")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf("保存JSON失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
|
|
"message": "识别成功",
|
|
|
|
|
|
"courses": courses,
|
|
|
|
|
|
"count": len(courses),
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-03-13 20:43:44 +08:00
|
|
|
|
// 任务处理接口
|
|
|
|
|
|
r.POST("/task", func(c *gin.Context) {
|
|
|
|
|
|
// 这里可以根据请求体参数决定执行什么任务
|
|
|
|
|
|
// 目前默认执行原有的登录和获取课表逻辑
|
|
|
|
|
|
if config.Username == "" || config.Password == "" {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "环境变量未设置"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行任务
|
|
|
|
|
|
err := runTask()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "任务执行成功"})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 启动服务
|
|
|
|
|
|
log.Println("Server starting on :8080")
|
|
|
|
|
|
if err := r.Run(":8080"); err != nil {
|
|
|
|
|
|
log.Fatalf("Failed to run server: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func runTask() error {
|
|
|
|
|
|
// 步骤1: 获取初始Cookie
|
|
|
|
|
|
err := client.GetInitialCookies()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf("获取Cookie失败: %v", err)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重试登录
|
|
|
|
|
|
maxRetries := 3
|
|
|
|
|
|
for attempt := 1; attempt <= maxRetries; attempt++ {
|
|
|
|
|
|
log.Printf("尝试登录 (第%d次)...", attempt)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取并识别验证码
|
|
|
|
|
|
captchaCode, err := captcha.GetAndRecognize(client.Client)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf(" 验证码获取失败: %v", err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 登录
|
|
|
|
|
|
success, err := auth.Login(captchaCode)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf(" 登录失败: %v", err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if success {
|
|
|
|
|
|
// 获取课表
|
|
|
|
|
|
_, err = parser.FetchSchedule(client.Client)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf("获取课表失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Println(" 登录失败,3秒后重试...")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|