Files
dygou_go/handlers.go
T
2026-06-20 05:54:25 +08:00

198 lines
6.2 KiB
Go

package main
import (
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
)
// registerRoutes 注册前端需要的所有接口。
func (s *appState) registerRoutes(mux *http.ServeMux) {
mux.HandleFunc("/api/products", s.handleProducts)
mux.HandleFunc("/api/load-products", s.handleLoadProducts)
mux.HandleFunc("/api/accounts", s.handleAccounts)
mux.HandleFunc("/api/reload-accounts", s.handleReloadAccounts)
mux.HandleFunc("/api/seckill", s.handleSeckill)
mux.HandleFunc("/api/schedule", s.handleSchedule)
mux.HandleFunc("/api/cancel-schedule", s.handleCancelSchedule)
mux.HandleFunc("/api/results", s.handleResults)
mux.HandleFunc("/api/logs", s.handleLogs)
mux.HandleFunc("/api/time", s.handleTime)
mux.HandleFunc("/api/clear-results", s.handleClearResults)
mux.HandleFunc("/api/query-score", s.handleQueryScore)
mux.Handle("/", http.FileServer(http.Dir(staticDir)))
}
// handleProducts 返回商品列表;带 refresh=1 时会从斗鱼实时刷新,失败不使用旧库存冒充成功。
func (s *appState) handleProducts(w http.ResponseWriter, r *http.Request) {
refresh := r.URL.Query().Get("refresh") == "1"
if refresh {
products, err := s.client.Products()
if err != nil {
logEvent("商品", "error", "商品刷新失败: %v", err)
writeError(w, http.StatusBadGateway, err)
return
}
fetchedAt := time.Now()
s.setProducts(products, fetchedAt)
logEvent("商品", "success", "商品刷新完成: %d 个,刷新时间=%s", len(products), fetchedAt.In(cst).Format("15:04:05.000"))
writeJSON(w, s.productPayload("live"))
return
}
writeJSON(w, s.productPayload("cache"))
}
// handleLoadProducts 兼容旧前端入口;新前端直接调用 /api/products?refresh=1。
func (s *appState) handleLoadProducts(w http.ResponseWriter, _ *http.Request) {
products, err := s.client.Products()
if err != nil {
logEvent("商品", "error", "商品刷新失败: %v", err)
writeError(w, http.StatusBadGateway, err)
return
}
fetchedAt := time.Now()
s.setProducts(products, fetchedAt)
logEvent("商品", "success", "商品刷新完成: %d 个,刷新时间=%s", len(products), fetchedAt.In(cst).Format("15:04:05.000"))
writeJSON(w, s.productPayload("live"))
}
// handleAccounts 返回账号列表。
func (s *appState) handleAccounts(w http.ResponseWriter, _ *http.Request) {
accounts := s.accountSnapshot()
list := make([]AccountView, 0, len(accounts))
for i, account := range accounts {
list = append(list, AccountView{
Index: i,
Name: account.Name,
Cookie: maskCookie(account.Cookie),
})
}
writeJSON(w, list)
}
// handleReloadAccounts 重新加载账号文件。
func (s *appState) handleReloadAccounts(w http.ResponseWriter, _ *http.Request) {
s.loadAccounts()
writeJSON(w, map[string]any{"success": true, "count": s.accountCount()})
}
// handleSeckill 立即启动抢购。
func (s *appState) handleSeckill(w http.ResponseWriter, r *http.Request) {
var req SeckillRequest
if err := readJSON(r, &req); err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
if len(req.AccountProducts) == 0 {
writeError(w, http.StatusBadRequest, errors.New("请选择账号和商品"))
return
}
if !s.tryStartRun() {
writeError(w, http.StatusConflict, errors.New("抢购任务正在运行,请勿重复提交"))
return
}
go s.runNow(cloneStringMap(req.AccountProducts))
writeJSON(w, map[string]any{"success": true, "msg": "抢购已启动"})
}
// handleSchedule 设置定时抢购。
func (s *appState) handleSchedule(w http.ResponseWriter, r *http.Request) {
var req SeckillRequest
if err := readJSON(r, &req); err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
resp, err := s.scheduleRun(req)
if err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
writeJSON(w, resp)
}
// handleCancelSchedule 取消未触发的定时抢购。
func (s *appState) handleCancelSchedule(w http.ResponseWriter, _ *http.Request) {
stopped := s.cancelSchedule()
writeJSON(w, map[string]any{"success": true, "stopped": stopped})
}
// handleResults 返回当前抢购结果。
func (s *appState) handleResults(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, s.resultsSnapshot())
}
// handleLogs 返回后端内存日志,支持用 since 参数增量获取。
func (s *appState) handleLogs(w http.ResponseWriter, r *http.Request) {
var since int64
if raw := strings.TrimSpace(r.URL.Query().Get("since")); raw != "" {
parsed, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
writeError(w, http.StatusBadRequest, fmt.Errorf("since 参数错误: %w", err))
return
}
since = parsed
}
entries := s.logs.Since(since)
nextSince := since
if len(entries) > 0 {
nextSince = entries[len(entries)-1].ID
}
writeJSON(w, map[string]any{
"success": true,
"logs": entries,
"since": nextSince,
"file": s.logs.FilePath(),
})
}
// handleClearResults 清空抢购结果。
func (s *appState) handleClearResults(w http.ResponseWriter, _ *http.Request) {
s.clearResults()
writeJSON(w, map[string]any{"success": true})
}
// handleTime 返回服务端时间和本地时间。
func (s *appState) handleTime(w http.ResponseWriter, _ *http.Request) {
serverTime, err := s.client.ServerTime()
source := "currentTime"
if err != nil {
source = "localFallback"
logEvent("时间", "warn", "时间接口降级: %v", err)
}
writeJSON(w, map[string]any{
"serverTime": serverTime.In(cst).Format("2006-01-02 15:04:05.000"),
"localTime": time.Now().In(cst).Format("2006-01-02 15:04:05.000"),
"source": source,
})
}
// handleQueryScore 查询单个账号的积分。
func (s *appState) handleQueryScore(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
accountName := strings.TrimSpace(string(body))
if accountName == "" {
writeError(w, http.StatusBadRequest, errors.New("缺少账号名"))
return
}
cookie, ok := s.accountCookieByName(accountName)
if !ok || strings.TrimSpace(cookie) == "" {
writeError(w, http.StatusBadRequest, fmt.Errorf("账号不存在或 Cookie 为空: %s", accountName))
return
}
score, err := s.client.Score(cookie)
if err != nil {
writeJSON(w, map[string]any{"score": "查询失败: " + err.Error()})
return
}
writeJSON(w, map[string]any{"score": score})
}