ADD: auto order

This commit is contained in:
XOF
2025-12-12 04:21:50 +08:00
parent c17d85d805
commit b8b19f7535

195
main.go
View File

@@ -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