198 lines
6.2 KiB
Go
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})
|
|
}
|