ADD: auto order
This commit is contained in:
195
main.go
195
main.go
@@ -29,6 +29,12 @@ type Task struct {
|
||||
NotifyEnabled bool `json:"notify_enabled"`
|
||||
Order int `json:"order"`
|
||||
History []HistoryItem `json:"history"`
|
||||
// 新增:自动下单相关
|
||||
AutoOrder bool `json:"auto_order"` // 是否启用自动下单
|
||||
OrderConfigPath string `json:"order_config"` // 下单配置路径
|
||||
Ordering bool `json:"ordering"` // 是否正在下单中
|
||||
LastOrderTime time.Time `json:"last_order_time"` // 最后下单时间
|
||||
OrderSuccess bool `json:"order_success"` // 是否下单成功过
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -44,6 +50,16 @@ type HistoryItem struct {
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
// OrderResult 结构体
|
||||
type OrderResult struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
OrderID string `json:"order_id"`
|
||||
Price string `json:"price"`
|
||||
Location string `json:"location"`
|
||||
Screenshot string `json:"screenshot"`
|
||||
}
|
||||
|
||||
var (
|
||||
tasks = make(map[string]*Task)
|
||||
config = &Config{Interval: 60, Timeout: 20, NotifyEnabled: true}
|
||||
@@ -51,6 +67,8 @@ var (
|
||||
configMu sync.RWMutex
|
||||
authToken = os.Getenv("AUTH_TOKEN")
|
||||
client *http.Client
|
||||
orderLocks = make(map[string]*sync.Mutex)
|
||||
orderLocksMu sync.Mutex
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -564,6 +582,9 @@ func checkTask(task *Task) {
|
||||
wasInStock := task.InStock
|
||||
wasNotified := task.Notified
|
||||
taskNotifyEnabled := task.NotifyEnabled
|
||||
taskAutoOrder := task.AutoOrder
|
||||
orderConfigPath := task.OrderConfigPath
|
||||
|
||||
task.Status = status
|
||||
task.InStock = inStock
|
||||
task.LastCheck = now
|
||||
@@ -586,8 +607,15 @@ func checkTask(task *Task) {
|
||||
globalNotifyEnabled := config.NotifyEnabled
|
||||
configMu.RUnlock()
|
||||
|
||||
// 有货且之前无货且未通知过
|
||||
if globalNotifyEnabled && taskNotifyEnabled && inStock && !wasInStock && !wasNotified {
|
||||
notify(task.Name + " 有货了!", task.URL)
|
||||
|
||||
// 自动下单(异步执行,不阻塞监控)
|
||||
if taskAutoOrder && orderConfigPath != "" {
|
||||
go executeAutoOrder(task)
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
task.Notified = true
|
||||
mu.Unlock()
|
||||
@@ -674,6 +702,173 @@ func handleTestNotification(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// 获取任务专属的锁
|
||||
func getOrderLock(taskID string) *sync.Mutex {
|
||||
orderLocksMu.Lock()
|
||||
defer orderLocksMu.Unlock()
|
||||
|
||||
if _, exists := orderLocks[taskID]; !exists {
|
||||
orderLocks[taskID] = &sync.Mutex{}
|
||||
}
|
||||
return orderLocks[taskID]
|
||||
}
|
||||
|
||||
func executeAutoOrder(task *Task) {
|
||||
// 获取该任务的专属锁
|
||||
taskLock := getOrderLock(task.ID)
|
||||
|
||||
// 尝试获取锁,如果获取失败说明已经有下单任务在执行
|
||||
if !taskLock.TryLock() {
|
||||
log.Printf("[%s] 已有下单任务正在执行,跳过", task.Name)
|
||||
return
|
||||
}
|
||||
defer taskLock.Unlock()
|
||||
|
||||
// 检查是否已经下单成功过
|
||||
mu.RLock()
|
||||
if task.OrderSuccess {
|
||||
mu.RUnlock()
|
||||
log.Printf("[%s] 该任务已成功下单,跳过", task.Name)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查最近是否下过单(防止频繁重试)
|
||||
if !task.LastOrderTime.IsZero() && time.Since(task.LastOrderTime) < 5*time.Minute {
|
||||
mu.RUnlock()
|
||||
log.Printf("[%s] 距离上次下单不足5分钟,跳过", task.Name)
|
||||
return
|
||||
}
|
||||
mu.RUnlock()
|
||||
|
||||
// 标记为下单中
|
||||
mu.Lock()
|
||||
task.Ordering = true
|
||||
task.LastOrderTime = time.Now()
|
||||
mu.Unlock()
|
||||
saveTasks()
|
||||
|
||||
log.Printf("[%s] 开始自动下单...", task.Name)
|
||||
|
||||
// 重试逻辑
|
||||
maxRetries := 3
|
||||
var lastResult *OrderResult
|
||||
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
log.Printf("[%s] 第 %d/%d 次尝试下单", task.Name, attempt, maxRetries)
|
||||
|
||||
result := runOrder(task)
|
||||
lastResult = result
|
||||
|
||||
if result.Success {
|
||||
// 下单成功
|
||||
mu.Lock()
|
||||
task.Ordering = false
|
||||
task.OrderSuccess = true
|
||||
mu.Unlock()
|
||||
saveTasks()
|
||||
|
||||
msg := fmt.Sprintf("✅ 自动下单成功!\n商品: %s\n订单号: %s\n价格: %s\n机房: %s\n截图: %s\n尝试次数: %d",
|
||||
task.Name, result.OrderID, result.Price, result.Location, result.Screenshot, attempt)
|
||||
notify("🎉 "+task.Name+" 下单成功", msg)
|
||||
log.Printf("[%s] %s", task.Name, msg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 下单失败,判断是否需要重试
|
||||
log.Printf("[%s] 第 %d 次尝试失败: %s", task.Name, attempt, result.Message)
|
||||
|
||||
// 判断失败原因,某些情况不需要重试
|
||||
if shouldSkipRetry(result.Message) {
|
||||
log.Printf("[%s] 失败原因不适合重试,停止", task.Name)
|
||||
break
|
||||
}
|
||||
|
||||
// 如果不是最后一次,等待后重试
|
||||
if attempt < maxRetries {
|
||||
waitTime := time.Duration(attempt*5) * time.Second // 递增等待:5s, 10s, 15s
|
||||
log.Printf("[%s] 等待 %v 后重试...", task.Name, waitTime)
|
||||
time.Sleep(waitTime)
|
||||
}
|
||||
}
|
||||
|
||||
// 所有重试都失败
|
||||
mu.Lock()
|
||||
task.Ordering = false
|
||||
mu.Unlock()
|
||||
saveTasks()
|
||||
|
||||
msg := fmt.Sprintf("❌ 自动下单失败(已重试 %d 次)\n商品: %s\n最后错误: %s\n截图: %s",
|
||||
maxRetries, task.Name, lastResult.Message, lastResult.Screenshot)
|
||||
notify("⚠️ "+task.Name+" 下单失败", msg)
|
||||
log.Printf("[%s] %s", task.Name, msg)
|
||||
}
|
||||
// 执行单次下单
|
||||
func runOrder(task *Task) *OrderResult {
|
||||
result := &OrderResult{Success: false}
|
||||
|
||||
// 检查配置文件是否存在
|
||||
if task.OrderConfigPath == "" {
|
||||
result.Message = "未配置下单配置文件"
|
||||
return result
|
||||
}
|
||||
|
||||
if _, err := os.Stat(task.OrderConfigPath); os.IsNotExist(err) {
|
||||
result.Message = fmt.Sprintf("配置文件不存在: %s", task.OrderConfigPath)
|
||||
return result
|
||||
}
|
||||
|
||||
// 执行下单脚本
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "./auto_order", task.OrderConfigPath)
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
result.Message = "下单超时(120秒)"
|
||||
return result
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
result.Message = fmt.Sprintf("执行失败: %v, 输出: %s", err, string(output))
|
||||
log.Printf("[%s] 命令执行错误: %v", task.Name, err)
|
||||
log.Printf("[%s] 命令输出: %s", task.Name, string(output))
|
||||
return result
|
||||
}
|
||||
|
||||
// 解析结果
|
||||
if err := json.Unmarshal(output, result); err != nil {
|
||||
result.Message = fmt.Sprintf("解析结果失败: %v, 原始输出: %s", err, string(output))
|
||||
log.Printf("[%s] JSON 解析错误: %v", task.Name, err)
|
||||
log.Printf("[%s] 原始输出: %s", task.Name, string(output))
|
||||
return result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
// 判断是否应该跳过重试
|
||||
func shouldSkipRetry(message string) bool {
|
||||
// 这些情况不需要重试
|
||||
skipKeywords := []string{
|
||||
"价格超出预算",
|
||||
"机房不匹配",
|
||||
"未配置",
|
||||
"配置文件不存在",
|
||||
"Cookie",
|
||||
"认证失败",
|
||||
}
|
||||
|
||||
messageLower := strings.ToLower(message)
|
||||
for _, keyword := range skipKeywords {
|
||||
if strings.Contains(messageLower, strings.ToLower(keyword)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func updateClient() {
|
||||
configMu.RLock()
|
||||
timeout := config.Timeout
|
||||
|
||||
Reference in New Issue
Block a user