feat: 增加详细日志, 覆盖所有接口和核心业务逻辑

This commit is contained in:
ShenQiLun 2026-06-26 16:23:44 +08:00
parent 2c90c95b55
commit 05bec7f1d0
8 changed files with 197 additions and 42 deletions

View File

@ -3,6 +3,7 @@ package database
import (
"database/sql"
"fmt"
"log"
"os"
_ "modernc.org/sqlite"
@ -14,9 +15,12 @@ var DB *sql.DB
func InitDB(dbPath string) error {
var err error
log.Printf("[DB] 初始化数据库: path=%s", dbPath)
// 确保数据库目录存在
dir := "./data"
if _, err := os.Stat(dir); os.IsNotExist(err) {
log.Printf("[DB] 数据库目录不存在, 创建: %s", dir)
os.MkdirAll(dir, 0755)
}
@ -32,6 +36,7 @@ func InitDB(dbPath string) error {
return fmt.Errorf("创建表失败: %w", err)
}
log.Printf("[DB] 数据库初始化完成")
return nil
}

View File

@ -28,13 +28,19 @@ func NewConfigHandler(configPath string) *ConfigHandler {
// GetConfigPrice 获取配置
// 先读 yamlPort/TimerInterval/APIRateLimit/CallbackURL再用数据库值覆盖价格字段
func (h *ConfigHandler) GetConfigPrice(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
clientIP = forwarded
}
log.Printf("[GetConfigPrice] 收到请求, 来源IP: %s", clientIP)
h.mu.Lock()
defer h.mu.Unlock()
// 1. 加载yaml配置
cfg, err := config.Load(h.configPath)
if err != nil {
log.Printf("[GetConfigPrice] 读取配置失败: %s", err.Error())
log.Printf("[GetConfigPrice] 读取配置失败: %s, 来源IP: %s", err.Error(), clientIP)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(fmt.Sprintf(`{"code":500,"message":"读取配置失败: %s"}`, err.Error())))
return
@ -56,6 +62,9 @@ func (h *ConfigHandler) GetConfigPrice(w http.ResponseWriter, r *http.Request) {
// 同步全局配置
config.SetGlobal(cfg)
log.Printf("[GetConfigPrice] 返回配置: port=%s, new_price=%.2f, placeholder_down_price=%.2f, min_shipping_fee=%.2f, min_price=%.2f, query_index=%d",
cfg.Port, cfg.NewPrice, cfg.PlaceholderDownPrice, cfg.MinShippingFee, cfg.MinPrice, cfg.QueryIndex)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"code": 200,
@ -70,6 +79,12 @@ func (h *ConfigHandler) GetConfigPrice(w http.ResponseWriter, r *http.Request) {
func (h *ConfigHandler) SetConfigPrice(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(32 << 20)
clientIP := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
clientIP = forwarded
}
log.Printf("[SetConfigPrice] 收到请求, 来源IP: %s", clientIP)
h.mu.Lock()
defer h.mu.Unlock()

View File

@ -25,8 +25,15 @@ func NewGoodsHandler(goodsService *service.GoodsService) *GoodsHandler {
// QueryGoods 查询商品接口
func (h *GoodsHandler) QueryGoods(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
clientIP = forwarded
}
log.Printf("[QueryGoods] 收到请求, 来源IP: %s, Method: %s", clientIP, r.Method)
// 只支持POST请求
if r.Method != http.MethodPost {
log.Printf("[QueryGoods] 方法不允许: %s, 来源IP: %s", r.Method, clientIP)
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
@ -34,8 +41,7 @@ func (h *GoodsHandler) QueryGoods(w http.ResponseWriter, r *http.Request) {
// 读取原始body用于调试
bodyBytes, _ := io.ReadAll(r.Body)
bodyStr := string(bodyBytes)
log.Printf("收到原始请求 Body: [%s]", bodyStr)
log.Printf("Content-Type: [%s]", r.Header.Get("Content-Type"))
log.Printf("[QueryGoods] 原始请求 Body: [%s], Content-Type: [%s]", bodyStr, r.Header.Get("Content-Type"))
// 重新创建body供ParseForm使用
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
@ -44,33 +50,43 @@ func (h *GoodsHandler) QueryGoods(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(32 << 20); err != nil {
// 尝试纯表单解析
if err := r.ParseForm(); err != nil {
log.Printf("ParseForm 失败: %v", err)
log.Printf("[QueryGoods] 参数解析失败: %v, 来源IP: %s", err, clientIP)
http.Error(w, "Invalid request form", http.StatusBadRequest)
return
}
}
// 调试日志
log.Printf("解析结果 - isbn: [%s], book_name: [%s], author: [%s], publishing: [%s], out_id: [%s], quality: [%s], query_index: [%s], user_id: [%s], placeholder_down_price: [%s], min_shipping_fee: [%s], min_price: [%s] ",
r.FormValue("isbn"), r.FormValue("book_name"), r.FormValue("author"), r.FormValue("publishing"),
r.FormValue("out_id"), r.FormValue("quality"),
r.FormValue("query_index"), r.FormValue("user_id"), r.FormValue("placeholder_down_price"), r.FormValue("min_shipping_fee"), r.FormValue("min_price"))
isbn := r.FormValue("isbn")
bookName := r.FormValue("book_name")
author := r.FormValue("author")
publishing := r.FormValue("publishing")
outID := r.FormValue("out_id")
quality := r.FormValue("quality")
queryIndex := r.FormValue("query_index")
userID := r.FormValue("user_id")
placeholderDownPrice := r.FormValue("placeholder_down_price")
minShippingFee := r.FormValue("min_shipping_fee")
minPrice := r.FormValue("min_price")
log.Printf("[QueryGoods] 解析参数完成 - isbn=[%s], book_name=[%s], author=[%s], publishing=[%s], out_id=[%s], quality=[%s], query_index=[%s], user_id=[%s], placeholder_down_price=[%s], min_shipping_fee=[%s], min_price=[%s]",
isbn, bookName, author, publishing, outID, quality, queryIndex, userID, placeholderDownPrice, minShippingFee, minPrice)
var req service.QueryRequest
req.ISBN = r.FormValue("isbn")
req.BookName = r.FormValue("book_name")
req.Author = r.FormValue("author")
req.Publishing = r.FormValue("publishing")
req.OutID = r.FormValue("out_id")
req.Quality = r.FormValue("quality")
req.QueryIndex, _ = strconv.Atoi(r.FormValue("query_index"))
req.UserID = r.FormValue("user_id")
req.PlaceholderDownPrice, _ = strconv.ParseFloat(r.FormValue("placeholder_down_price"), 64)
req.MinShippingFee, _ = strconv.ParseFloat(r.FormValue("min_shipping_fee"), 64)
req.MinPrice, _ = strconv.ParseFloat(r.FormValue("min_price"), 64)
req.ISBN = isbn
req.BookName = bookName
req.Author = author
req.Publishing = publishing
req.OutID = outID
req.Quality = quality
req.QueryIndex, _ = strconv.Atoi(queryIndex)
req.UserID = userID
req.PlaceholderDownPrice, _ = strconv.ParseFloat(placeholderDownPrice, 64)
req.MinShippingFee, _ = strconv.ParseFloat(minShippingFee, 64)
req.MinPrice, _ = strconv.ParseFloat(minPrice, 64)
// 调用服务层
resp := h.goodsService.QueryGoods(&req)
log.Printf("[QueryGoods] 处理完成, ID=%d, Code=%d, Message=%s", resp.ID, resp.Code, resp.Message)
// 返回响应
w.Header().Set("Content-Type", "application/json")

View File

@ -3,10 +3,12 @@ package handler
import (
"encoding/json"
"fmt"
"github.com/parnurzeal/gorequest"
"log"
"net/http"
"strings"
"time"
"github.com/parnurzeal/gorequest"
)
// KfzHandler Kfz处理器
@ -20,31 +22,43 @@ func NewKfzHandler() *KfzHandler {
// KfzLogin 登录孔网并返回用户信息
func (h *KfzHandler) KfzLogin(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
clientIP = forwarded
}
log.Printf("[KfzLogin] 收到登录请求, 来源IP: %s", clientIP)
r.ParseMultipartForm(32 << 20)
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
if username == "" || password == "" {
log.Printf("[KfzLogin] username或password为空, 来源IP: %s", clientIP)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"code":500,"message":"username和password不能为空"}`))
return
}
log.Printf("[KfzLogin] 开始登录孔网, username=%s, 来源IP: %s", username, clientIP)
token, err := outKfzLogin(username, password)
if err != nil {
log.Printf("[KfzLogin] 孔网登录失败: username=%s, 错误=%v, 来源IP: %s", username, err, clientIP)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(fmt.Sprintf(`{"code":500,"message":"%s"}`, err.Error())))
return
}
log.Printf("[KfzLogin] 孔网登录成功: username=%s, token=%s..., 来源IP: %s", username, token[:min(len(token), 10)], clientIP)
userInfo, err := outKfzGetUserInfo(token)
if err != nil {
log.Printf("[KfzLogin] 获取用户信息失败: username=%s, 错误=%v, 来源IP: %s", username, err, clientIP)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(fmt.Sprintf(`{"code":500,"message":"%s"}`, err.Error())))
return
}
userInfo.Token = token
log.Printf("[KfzLogin] 登录成功: username=%s, userId=%d, nickname=%s, 来源IP: %s", username, userInfo.UserID, userInfo.Nickname, clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
@ -54,6 +68,13 @@ func (h *KfzHandler) KfzLogin(w http.ResponseWriter, r *http.Request) {
})
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
/*
* 孔网登录
* param username[string] 孔网用户名

View File

@ -36,22 +36,27 @@ type TokenInput struct {
// BatchAddTokens 批量添加TokenJSON数组: [{"username":"","token":""},...]
func (h *TokenHandler) BatchAddTokens(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
clientIP = forwarded
}
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
var inputs []TokenInput
if err := json.NewDecoder(r.Body).Decode(&inputs); err != nil {
log.Printf("BatchAddTokens - decode error: %v", err)
log.Printf("[Token/BatchAdd] 请求体解析失败: %v, 来源IP: %s", err, clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Code: 400, Message: "invalid request body"})
return
}
log.Printf("BatchAddTokens - received %d tokens", len(inputs))
log.Printf("[Token/BatchAdd] 收到请求, 来源IP: %s, token数量: %d", clientIP, len(inputs))
if len(inputs) == 0 {
log.Printf("[Token/BatchAdd] 空数组, 来源IP: %s", clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Code: 400, Message: "empty array"})
return
@ -59,21 +64,25 @@ func (h *TokenHandler) BatchAddTokens(w http.ResponseWriter, r *http.Request) {
// 逐条插入
var failed []TokenInput
for _, input := range inputs {
for i, input := range inputs {
if input.Username == "" || input.Token == "" {
log.Printf("[Token/BatchAdd] 第%d条数据不完整(username或token为空), 跳过", i+1)
failed = append(failed, input)
continue
}
_, err := h.tokenRepo.Insert(input.Username, input.Token, true)
id, err := h.tokenRepo.Insert(input.Username, input.Token, true)
if err != nil {
log.Printf("BatchAddTokens - insert error: %v", err)
log.Printf("[Token/BatchAdd] 第%d条插入失败: username=%s, 错误=%v", i+1, input.Username, err)
failed = append(failed, input)
} else {
log.Printf("[Token/BatchAdd] 第%d条插入成功: id=%d, username=%s", i+1, id, input.Username)
}
}
w.Header().Set("Content-Type", "application/json")
if len(failed) > 0 {
log.Printf("[Token/BatchAdd] 部分成功: 成功%d条, 失败%d条, 来源IP: %s", len(inputs)-len(failed), len(failed), clientIP)
json.NewEncoder(w).Encode(TokenResponse{
Code: 207,
Message: "partial success",
@ -82,31 +91,44 @@ func (h *TokenHandler) BatchAddTokens(w http.ResponseWriter, r *http.Request) {
return
}
log.Printf("[Token/BatchAdd] 全部成功: %d条, 来源IP: %s", len(inputs), clientIP)
json.NewEncoder(w).Encode(TokenResponse{Code: 200, Message: "success"})
}
// GetAllTokens 查询所有Token
func (h *TokenHandler) GetAllTokens(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
clientIP = forwarded
}
log.Printf("[Token/GetAll] 收到请求, 来源IP: %s", clientIP)
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
records, err := h.tokenRepo.GetAll()
if err != nil {
log.Printf("[Token/GetAll] 查询失败: %v, 来源IP: %s", err, clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Code: 500, Message: err.Error()})
return
}
log.Printf("[Token/GetAll] 查询成功: 共%d条记录, 来源IP: %s", len(records), clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Code: 200, Message: "success", Data: records})
}
// DeleteToken 删除Token
func (h *TokenHandler) DeleteToken(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
clientIP = forwarded
}
log.Printf("[Token/Delete] 收到请求, 来源IP: %s", clientIP)
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
@ -122,25 +144,33 @@ func (h *TokenHandler) DeleteToken(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
log.Printf("[Token/Delete] 参数id无效: %s, 来源IP: %s", idStr, clientIP)
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
err = h.tokenRepo.Delete(id)
if err != nil {
log.Printf("[Token/Delete] 删除失败: id=%d, 错误=%v, 来源IP: %s", id, err, clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Code: 500, Message: err.Error()})
return
}
log.Printf("[Token/Delete] 删除成功: id=%d, 来源IP: %s", id, clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Code: 200, Message: "success"})
}
// UpdateToken 修改Token
func (h *TokenHandler) UpdateToken(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
clientIP = forwarded
}
log.Printf("[Token/Update] 收到请求, 来源IP: %s", clientIP)
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
@ -155,6 +185,7 @@ func (h *TokenHandler) UpdateToken(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
log.Printf("[Token/Update] 参数id无效: %s, 来源IP: %s", idStr, clientIP)
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
@ -164,31 +195,41 @@ func (h *TokenHandler) UpdateToken(w http.ResponseWriter, r *http.Request) {
isEnable = false
}
log.Printf("[Token/Update] 更新: id=%d, username=%s, is_enable=%v, 来源IP: %s", id, username, isEnable, clientIP)
err = h.tokenRepo.Update(id, username, token, isEnable)
if err != nil {
log.Printf("[Token/Update] 更新失败: id=%d, 错误=%v, 来源IP: %s", id, err, clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Code: 500, Message: err.Error()})
return
}
log.Printf("[Token/Update] 更新成功: id=%d, 来源IP: %s", id, clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Code: 200, Message: "success"})
}
// GetEnabledTokens 获取所有启用的Token
func (h *TokenHandler) GetEnabledTokens(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
clientIP = forwarded
}
log.Printf("[Token/GetEnabled] 收到请求, 来源IP: %s", clientIP)
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
records, err := h.tokenRepo.GetEnabledTokens()
if err != nil {
log.Printf("[Token/GetEnabled] 查询失败: %v, 来源IP: %s", err, clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Code: 500, Message: err.Error()})
return
}
log.Printf("[Token/GetEnabled] 查询成功: 共%d条启用token, 来源IP: %s", len(records), clientIP)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Code: 200, Message: "success", Data: records})
}

View File

@ -3,6 +3,7 @@ package repository
import (
"database/sql"
"fmt"
"log"
"kfz-goods-pricing/internal/database"
)
@ -26,11 +27,15 @@ func GetKfzConfig() (*KfzConfig, error) {
).Scan(&cfg.ID, &cfg.NewPrice, &cfg.PlaceholderDownPrice, &cfg.MinShippingFee, &cfg.MinPrice, &cfg.QueryIndex)
if err == sql.ErrNoRows {
log.Printf("[Repo/Config] kfz_config表无数据")
return nil, nil // 无数据
}
if err != nil {
log.Printf("[Repo/Config] 查询kfz_config失败: %v", err)
return nil, fmt.Errorf("查询kfz_config失败: %w", err)
}
log.Printf("[Repo/Config] 查询成功: new_price=%.2f, placeholder_down_price=%.2f, min_shipping_fee=%.2f, min_price=%.2f, query_index=%d",
cfg.NewPrice, cfg.PlaceholderDownPrice, cfg.MinShippingFee, cfg.MinPrice, cfg.QueryIndex)
return &cfg, nil
}
@ -40,6 +45,7 @@ func SaveKfzConfig(cfg *KfzConfig) error {
var count int
err := database.DB.QueryRow("SELECT COUNT(*) FROM kfz_config").Scan(&count)
if err != nil {
log.Printf("[Repo/Config] 查询kfz_config数量失败: %v", err)
return fmt.Errorf("查询kfz_config失败: %w", err)
}
@ -49,16 +55,22 @@ func SaveKfzConfig(cfg *KfzConfig) error {
cfg.NewPrice, cfg.PlaceholderDownPrice, cfg.MinShippingFee, cfg.MinPrice, cfg.QueryIndex,
)
if err != nil {
log.Printf("[Repo/Config] 插入配置失败: %v", err)
return fmt.Errorf("插入kfz_config失败: %w", err)
}
log.Printf("[Repo/Config] 插入配置成功: new_price=%.2f, placeholder_down_price=%.2f, min_shipping_fee=%.2f, min_price=%.2f, query_index=%d",
cfg.NewPrice, cfg.PlaceholderDownPrice, cfg.MinShippingFee, cfg.MinPrice, cfg.QueryIndex)
} else {
_, err = database.DB.Exec(
`UPDATE kfz_config SET new_price=?, placeholder_down_price=?, min_shipping_fee=?, min_price=?, query_index=? WHERE id=1`,
cfg.NewPrice, cfg.PlaceholderDownPrice, cfg.MinShippingFee, cfg.MinPrice, cfg.QueryIndex,
)
if err != nil {
log.Printf("[Repo/Config] 更新配置失败: %v", err)
return fmt.Errorf("更新kfz_config失败: %w", err)
}
log.Printf("[Repo/Config] 更新配置成功: new_price=%.2f, placeholder_down_price=%.2f, min_shipping_fee=%.2f, min_price=%.2f, query_index=%d",
cfg.NewPrice, cfg.PlaceholderDownPrice, cfg.MinShippingFee, cfg.MinPrice, cfg.QueryIndex)
}
return nil
}

View File

@ -3,6 +3,7 @@ package repository
import (
"database/sql"
"fmt"
"log"
"kfz-goods-pricing/internal/database"
)
@ -40,14 +41,17 @@ func (r *GoodsRepository) Insert(isbn, bookName, author, publishing, outID, qual
result, err := database.DB.Exec(query, isbn, bookName, author, publishing, outID, quality, queryIndex, userID, placeholderDownPrice, minShippingFee, minPrice)
if err != nil {
log.Printf("[Repo/Goods] 插入记录失败: isbn=%s, out_id=%s, 错误=%v", isbn, outID, err)
return 0, fmt.Errorf("插入记录失败: %w", err)
}
id, err := result.LastInsertId()
if err != nil {
log.Printf("[Repo/Goods] 获取自增ID失败: %v", err)
return 0, fmt.Errorf("获取自增ID失败: %w", err)
}
log.Printf("[Repo/Goods] 插入成功: id=%d, isbn=%s, out_id=%s, min_price=%.2f", id, isbn, outID, minPrice)
return id, nil
}
@ -57,16 +61,23 @@ func (r *GoodsRepository) UpdatePrice(id int64, price, shippingFee, finalPrice f
_, err := database.DB.Exec(query, price, shippingFee, finalPrice, id)
if err != nil {
log.Printf("[Repo/Goods] 更新价格失败: id=%d, 错误=%v", id, err)
return fmt.Errorf("更新价格失败: %w", err)
}
log.Printf("[Repo/Goods] 更新价格成功: id=%d, price=%.2f, shipping_fee=%.2f, final_price=%.2f", id, price, shippingFee, finalPrice)
return nil
}
// MarkFailed 标记查询失败fail_count+1更新updated_at
func (r *GoodsRepository) MarkFailed(id int64) {
query := `UPDATE goods_pricing SET fail_count = fail_count + 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?`
database.DB.Exec(query, id)
_, err := database.DB.Exec(query, id)
if err != nil {
log.Printf("[Repo/Goods] 标记失败: id=%d, 错误=%v", id, err)
} else {
log.Printf("[Repo/Goods] 标记失败成功: id=%d", id)
}
}
// GetByID 根据ID查询记录
@ -144,6 +155,7 @@ func (r *GoodsRepository) GetAllOrderByUpdatedAt() (*GoodsPricing, error) {
if err == sql.ErrNoRows {
return nil, nil
}
log.Printf("[Repo/Goods] 查询待处理记录失败: %v", err)
return nil, fmt.Errorf("查询记录失败: %w", err)
}
@ -166,5 +178,6 @@ func (r *GoodsRepository) GetAllOrderByUpdatedAt() (*GoodsPricing, error) {
record.MinPrice = minPrice.Float64
}
log.Printf("[Repo/Goods] 查询到待处理记录: id=%d, isbn=%s, book_name=%s, fail_count=%d", record.ID, record.ISBN, record.BookName, record.FailCount)
return &record, nil
}

View File

@ -67,6 +67,7 @@ type QueryResponse struct {
// QueryGoods 查询商品信息
func (s *GoodsService) QueryGoods(req *QueryRequest) *QueryResponse {
if req.ISBN == "0" {
log.Printf("[QueryGoods] ISBN为0, 跳过处理")
return &QueryResponse{
Code: 200,
Message: "success",
@ -76,12 +77,15 @@ func (s *GoodsService) QueryGoods(req *QueryRequest) *QueryResponse {
// 插入入参记录到数据库
id, err := s.goodsRepository.Insert(req.ISBN, req.BookName, req.Author, req.Publishing, req.OutID, req.Quality, req.UserID, req.QueryIndex, req.PlaceholderDownPrice, req.MinShippingFee, req.MinPrice)
if err != nil {
log.Printf("[QueryGoods] 保存记录失败: isbn=%s, out_id=%s, user_id=%s, 错误=%v", req.ISBN, req.OutID, req.UserID, err)
return &QueryResponse{
Code: 500,
Message: fmt.Sprintf("保存记录失败: %v", err),
}
}
log.Printf("[QueryGoods] 记录保存成功: id=%d, isbn=%s, book_name=%s, out_id=%s, user_id=%s", id, req.ISBN, req.BookName, req.OutID, req.UserID)
return &QueryResponse{
Code: 200,
Message: "success",
@ -135,6 +139,7 @@ func (s *GoodsService) sendCallback(outID, userID string, price, shippingFee flo
// StartTimerScheduler 启动定时器
func (s *GoodsService) StartTimerScheduler(intervalSeconds int) {
log.Printf("[TimerScheduler] 定时器启动, 间隔=%d秒", intervalSeconds)
ticker := time.NewTicker(time.Duration(intervalSeconds) * time.Second)
go func() {
for range ticker.C {
@ -145,23 +150,29 @@ func (s *GoodsService) StartTimerScheduler(intervalSeconds int) {
// syncGoodsPricing 定时同步商品价格
func (s *GoodsService) syncGoodsPricing() {
//cfg := config.GetGlobal()
log.Printf("[syncGoodsPricing] ===== 开始执行定时同步任务 =====")
// 查询数据库数据
kfzConfig, err := repository.GetKfzConfig()
if err != nil {
log.Printf("获取config数据库数据失败: %v", err)
log.Printf("[syncGoodsPricing] 获取config数据库数据失败: %v", err)
return
}
log.Printf("[syncGoodsPricing] 全局配置: new_price=%.2f, placeholder_down_price=%.2f, min_shipping_fee=%.2f, min_price=%.2f, query_index=%d",
kfzConfig.NewPrice, kfzConfig.PlaceholderDownPrice, kfzConfig.MinShippingFee, kfzConfig.MinPrice, kfzConfig.QueryIndex)
// 查询一条记录按fail_count升序、updated_at倒序
record, err := s.goodsRepository.GetAllOrderByUpdatedAt()
if err != nil {
log.Printf("定时任务查询失败: %v", err)
log.Printf("[syncGoodsPricing] 查询待处理记录失败: %v", err)
return
}
if record == nil {
log.Printf("[syncGoodsPricing] 没有待处理的记录, 跳过本次同步")
return
}
log.Printf("[syncGoodsPricing] 获取到待处理记录: id=%d, isbn=%s, book_name=%s, out_id=%s, fail_count=%d",
record.ID, record.ISBN, record.BookName, record.OutID, record.FailCount)
// 限流等待
s.rateLimitWait()
@ -172,33 +183,45 @@ func (s *GoodsService) syncGoodsPricing() {
var finalPrice float64
// 查询孔网数据
log.Printf("[syncGoodsPricing] 开始查询孔网数据: id=%d, isbn=%s, book_name=%s", record.ID, record.ISBN, record.BookName)
bookInfo, err := s.outGetAllGoods(record.ISBN, record.BookName, record.Author, record.Publishing, record.Quality, record.QueryIndex)
if err != nil {
log.Printf("[syncGoodsPricing] 查询孔网失败: id=%d, 错误=%v", record.ID, err)
if kfzConfig.NewPrice == 0 {
s.goodsRepository.MarkFailed(record.ID)
log.Printf("定时任务[%d]查询孔网失败(fail_count=%d): %v", record.ID, record.FailCount+1, err)
log.Printf("[syncGoodsPricing] 标记失败: id=%d, fail_count=%d, new_price=0不启用兜底", record.ID, record.FailCount+1)
return
}
finalPrice = kfzConfig.NewPrice
log.Printf("[syncGoodsPricing] 查询失败, 启用兜底价格: finalPrice=%.2f (new_price)", finalPrice)
} else {
// 查询成功,更新价格
price, _ = strconv.ParseFloat(bookInfo.Price, 64)
shippingFee, _ = strconv.ParseFloat(bookInfo.ShippingFee, 64)
totalPrice := price + shippingFee
log.Printf("[syncGoodsPricing] 孔网数据: id=%d, price=%.2f, shipping_fee=%.2f, total=%.2f", record.ID, price, shippingFee, totalPrice)
log.Printf("[syncGoodsPricing] 计算参数: placeholder_down_price=%.2f, min_shipping_fee=%.2f, kfzConfig.min_price=%.2f",
record.PlaceholderDownPrice, record.MinShippingFee, kfzConfig.MinPrice)
finalPrice = totalPrice - record.PlaceholderDownPrice - record.MinShippingFee
log.Printf("[syncGoodsPricing] 减去占位降价和运费后: finalPrice=%.2f", finalPrice)
if finalPrice < kfzConfig.MinPrice {
log.Printf("[syncGoodsPricing] 价格低于最低书价, 使用最低书价: %.2f -> %.2f", finalPrice, kfzConfig.MinPrice)
finalPrice = kfzConfig.MinPrice
}
// 保留两位小数
beforeRound := finalPrice
finalPrice, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", finalPrice), 64)
log.Printf("[syncGoodsPricing] 最终价格取两位小数: %.2f -> %.2f", beforeRound, finalPrice)
}
if err := s.goodsRepository.UpdatePrice(record.ID, price, shippingFee, finalPrice); err != nil {
log.Printf("定时任务[%d]更新价格失败: %v", record.ID, err)
log.Printf("[syncGoodsPricing] 更新价格失败: id=%d, 错误=%v", record.ID, err)
return
}
log.Printf("定时任务[%d]更新成功: price=%.2f, shipping_fee=%.2f", record.ID, price, shippingFee)
log.Printf("[syncGoodsPricing] 更新成功: id=%d, price=%.2f, shipping_fee=%.2f, final_price=%.2f", record.ID, price, shippingFee, finalPrice)
// 调用回调
s.sendCallback(record.OutID, record.UserID, finalPrice, record.MinShippingFee, kfzConfig.MinPrice)
@ -211,8 +234,10 @@ func (s *GoodsService) outGetAllGoods(isbn string, bookName string, author strin
// 从数据库获取启用的token列表
tokens, err := s.tokenRepository.GetEnabledTokens()
if err != nil || len(tokens) == 0 {
log.Printf("[outGetAllGoods] 没有可用的token, 查询终止")
return nil, fmt.Errorf("没有可用的token")
}
log.Printf("[outGetAllGoods] 可用token数量: %d", len(tokens))
// 轮询选择token
s.tokenMu.Lock()
@ -222,8 +247,8 @@ func (s *GoodsService) outGetAllGoods(isbn string, bookName string, author strin
s.tokenMu.Unlock()
token := tokens[currentIdx].Token
log.Printf("[outGetAllGoods] 使用token索引: %d/%d, username=%s", currentIdx, len(tokens), tokens[currentIdx].Username)
//kfzUrl := "https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list?dataType=0&page=1&sortType=7&quaSelect=2"
kfzUrl := "https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list?dataType=0&page=1&sortType=7&userArea=13003000000&quaSelect=2"
// 整理查询参数-加上排序
@ -260,6 +285,8 @@ func (s *GoodsService) outGetAllGoods(isbn string, bookName string, author strin
// 加入查询参数
kfzUrl = kfzUrl + "&actionPath=" + actionPath
log.Printf("[outGetAllGoods] 请求孔网URL: %s", kfzUrl)
// 创建HTTP客户端
requestSpt := gorequest.New()
@ -267,7 +294,6 @@ func (s *GoodsService) outGetAllGoods(isbn string, bookName string, author strin
if s.proxy != "" {
requestSpt.Proxy(s.proxy)
}
fmt.Println("请求地址:", kfzUrl)
// 发送请求
respSpt, bodySpt, errsSpt := requestSpt.Get(kfzUrl).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
@ -280,16 +306,16 @@ func (s *GoodsService) outGetAllGoods(isbn string, bookName string, author strin
// 错误处理
if len(errsSpt) > 0 {
log.Printf("[outGetAllGoods] 孔网请求失败: %v", errsSpt)
return nil, fmt.Errorf("请求失败: %v", errsSpt)
}
// 检查HTTP状态码
if respSpt.StatusCode != http.StatusOK {
log.Printf("[outGetAllGoods] 孔网HTTP错误: %s", respSpt.Status)
return nil, fmt.Errorf("HTTP错误: %s", respSpt.Status)
}
fmt.Println("响应数据:", bodySpt)
// 解析响应
var apiSptResp struct {
Status int `json:"status"`
@ -318,16 +344,19 @@ func (s *GoodsService) outGetAllGoods(isbn string, bookName string, author strin
// 解析JSON
if err := json.Unmarshal([]byte(bodySpt), &apiSptResp); err != nil {
log.Printf("[outGetAllGoods] 解析孔网响应JSON失败: %v", err)
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
if apiSptResp.Status != 1 {
log.Printf("[outGetAllGoods] 孔网API返回错误: message=%s, errType=%s", apiSptResp.Message, apiSptResp.ErrType)
return nil, fmt.Errorf("错误信息: %v状态码: %s", apiSptResp.Message, apiSptResp.ErrType)
}
bookInfo := &model.BookInfo{}
if apiSptResp.Data.ItemResponse.Total > 0 && len(apiSptResp.Data.ItemResponse.List) > 0 {
log.Printf("[outGetAllGoods] 孔网搜索成功: 总共%d条结果, query_index=%d", apiSptResp.Data.ItemResponse.Total, queryIndex)
if queryIndex > 0 && queryIndex <= len(apiSptResp.Data.ItemResponse.List) {
goodsInfo := apiSptResp.Data.ItemResponse.List[queryIndex-1]
bookInfo.BookName = goodsInfo.Title
@ -339,6 +368,7 @@ func (s *GoodsService) outGetAllGoods(isbn string, bookName string, author strin
for _, shipping := range goodsInfo.Postage.ShippingList {
bookInfo.ShippingFee = fmt.Sprintf("%.2f", shipping.ShippingFee)
}
log.Printf("[outGetAllGoods] 取第%d条结果: title=%s, price=%s, shipping=%s", queryIndex, bookInfo.BookName, bookInfo.Price, bookInfo.ShippingFee)
} else {
goodsInfo := apiSptResp.Data.ItemResponse.List[len(apiSptResp.Data.ItemResponse.List)-1]
bookInfo.BookName = goodsInfo.Title
@ -350,9 +380,11 @@ func (s *GoodsService) outGetAllGoods(isbn string, bookName string, author strin
for _, shipping := range goodsInfo.Postage.ShippingList {
bookInfo.ShippingFee = fmt.Sprintf("%.2f", shipping.ShippingFee)
}
log.Printf("[outGetAllGoods] query_index无效, 取最后一条: title=%s, price=%s, shipping=%s", bookInfo.BookName, bookInfo.Price, bookInfo.ShippingFee)
}
return bookInfo, nil
}
log.Printf("[outGetAllGoods] 孔网搜索无结果: total=%d", apiSptResp.Data.ItemResponse.Total)
return nil, fmt.Errorf("查询失败,没有数据!")
}