# 商品定价服务（kfz-goods-pricing）项目交接文档

> 生成时间：2026-05-27
> 项目路径：D:\work\newProject\kfz-goods-pricing

---

## 一、项目概述

**项目名称**：商品定价服务（kfz-goods-pricing）
**技术栈**：Go 1.25 + SQLite + HTTP API + 原生 GUI（无 Vue/Wails，纯 Go）
**输出可执行文件**：kfz-goods-pricing.exe
**核心功能**：接收商品 ISBN 查询请求，通过孔网搜索 API 获取市场定价，计算最优价格并回调通知下游系统

> ⚠️ **重要区别**：本项目与之前生成的 kfz-move-kfz / xy-verify-price / kfz-verify-price 项目完全不同——不是 Wails 桌面应用，而是纯 Go HTTP 服务 + 原生 GUI 窗口，没有前端框架，没有子进程 B 程序。

### 目录结构

`
kfz-goods-pricing/
├── cmd/
│   ├── server/
│   │   ├── main.go         # 程序入口（HTTP服务 + GUI窗口 + 定时器）
│   │   └── gui_windows.go  # GUI 窗口实现
├── internal/
│   ├── config/             # 配置加载（YAML + 全局单例）
│   │   └── config.go
│   ├── database/           # SQLite 数据库初始化
│   │   └── db.go
│   ├── handler/            # HTTP 处理器（4个文件）
│   │   ├── goods_handler.go     # /api/goods/query — 商品查询
│   │   ├── token_handler.go     # /api/token/* — Token 管理（增删改查/启用）
│   │   ├── kfz_handler.go       # /api/kfz/login — 孔网登录
│   │   └── config_handler.go    # /api/config/price/* — 价格配置
│   ├── model/
│   │   └── book.go             # BookInfo 图书信息结构体
│   ├── repository/          # 数据访问层
│   │   ├── token_repository.go   # Token CRUD + 启用状态
│   │   ├── goods_repository.go  # 商品记录 CRUD + 失败计数
│   │   └── config_repository.go  # kfz_config 表（永远只有 ID=1 一条）
│   └── service/
│       └── goods_service.go     # 核心业务逻辑（定时器/限速/爬虫/回调）
├── pkg/                     # 公共库目录（当前为空）
├── config/
│   └── config.yaml          # 程序配置文件
├── data/
│   └── goods_pricing.db     # SQLite 数据库文件
├── go.mod / go.sum
├── kfz-goods-pricing.exe   # 编译后的可执行文件
└── README.md
`

---

## 二、技术栈

| 组件 | 依赖 | 版本 | 说明 |
|------|------|------|------|
| HTTP 服务 | Go 标准库 | 内置 | 
et/http，无框架，裸serveMux路由 |
| 数据库 | modernc.org/sqlite | v1.50.0 | 纯 Go SQLite，无 CGO |
| HTTP 客户端 | github.com/parnurzeal/gorequest | v0.3.0 | 孔网 API 调用 |
| 配置解析 | gopkg.in/yaml.v3 | v3.0.1 | config.yaml 读取 |
| 日志 | log | 内置 | 输出到 GUI 窗口（guiLogWriter） |
| GUI | 原生实现 | — | gui_windows.go 中的 GUI 窗口（GTK/原生混合） |

---

## 三、架构设计

`
┌─────────────────────────────────────────────────────────────────┐
│                     kfz-goods-pricing.exe                        │
│                                                                  │
│  ┌────────────┐     ┌──────────────┐     ┌─────────────────┐    │
│  │ GUI 窗口   │     │ HTTP 服务    │     │ 定时器 (Timer)   │    │
│  │(gui_windows)│    │(net/http)   │     │ 每 N 秒执行一次   │    │
│  └────────────┘     └──────┬───────┘     └────────┬────────┘    │
│                            │                      │              │
│         ┌──────────────────┬┴─────────────────────┘              │
│         ▼                  ▼                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────────────────┐  │
│  │ TokenHandler │  │GoodsHandler  │  │   GoodsService         │  │
│  │ /api/token/* │  │/api/goods/...│  │  (核心业务逻辑)         │  │
│  └──────┬───────┘  └──────┬───────┘  └─────────┬──────────────┘  │
│         │                │                    │                  │
│  ┌──────▼────────────────▼────────────────────▼──────────────┐  │
│  │                   Repository 层                          │  │
│  │  TokenRepo  │  GoodsRepo  │  ConfigRepo                    │  │
│  └────────────────────────┬───────────────────────────────────┘  │
│                           ▼                                       │
│  ┌─────────────────────────────────────────────────────────────────┐
│  │                     SQLite (goods_pricing.db)                   │
│  │  kfz_token (Token管理) │ kfz_goods (商品记录) │ kfz_config     │
│  └─────────────────────────────────────────────────────────────────┘
│                                                                  │
│  外部调用：                                                    │
│    ① 上游系统 → POST /api/goods/query → 写入 kfz_goods         │
│    ② 定时器 → 查询 kfz_goods（按失败次数/更新时间排序）        │
│       → 爬孔网搜索API → 计算价格 → POST callbackURL             │
│    ③ 回调 URL: http://192.168.101.213:9090/api/product/updatePrice│
└─────────────────────────────────────────────────────────────────┘
`

---

## 四、HTTP API 接口

### 4.1 商品查询（上游系统调用入口）

**POST** /api/goods/query

**请求参数**（form-data 或 x-www-form-urlencoded）：

| 参数                 | 类型 | 必填 | 说明                |
|--------------------|------|--|-------------------|
| isbn               | string |  | 商品 ISBN           |
| book_name          | string |  | 商品 名称             |
| author             | string |  | 商品 作者             |
| publishing         | string |  | 商品 出版社            |
| out_id             | string | | 输出 ID（回调时透传）      |
| quality            | string | | 品相（100=全新，90=九品等） |
| query_index        | int | | 想排第几位（默认取最后一条）    |
| user_id            | string | | 用户 ID（回调时透传）      |
| placeholder_down_price | float | | 占位降价              |
| min_shipping_fee   | float | | 最低运费              |
| min_price          | float | | 最低书价              |

**响应**：
`json
{"code":200,"message":"success","id":123}
`

> 说明：只写入数据库，返回插入记录的 ID。实际核价由定时器异步完成。

### 4.2 Token 管理

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | /api/token/add | 批量添加 Token（JSON数组） |
| GET | /api/token/list | 查询所有 Token |
| POST | /api/token/delete | 删除 Token（id 参数） |
| POST | /api/token/update | 修改 Token（id/username/token/is_enable） |
| GET | /api/token/enabled | 获取所有启用的 Token |

**批量添加请求体**：
`json
[{"username":"孔网账号1","token":"PHPSESSID_xxx"},{"username":"孔网账号2","token":"PHPSESSID_yyy"}]
`

### 4.3 孔网登录

**POST** /api/kfz/login

| 参数 | 说明 |
|------|------|
| username | 孔网用户名 |
| password | 孔网密码 |

**响应**：
`json
{"code":200,"message":"success","data":{"userId":123456,"nickname":"xxx","mobile":"138xxxx","token":"PHPSESSID_xxx"}}
`

### 4.4 价格配置

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/config/price/get | 获取完整配置（yaml + DB 覆盖层合并） |
| POST | /api/config/price/set | 修改价格配置（只更新入参字段） |

**配置合并逻辑**（GetConfigPrice）：
`
yaml配置（Port / TimerInterval / APIRateLimit / CallbackURL）
  + DB配置（NewPrice / PlaceholderDownPrice / MinShippingFee / MinPrice / QueryIndex）
= 最终配置（DB 优先覆盖 yaml 的价格字段）
`

---

## 五、定时器核心逻辑（GoodsService.syncGoodsPricing）

`
每 N 秒（默认 5 秒）触发一次：

1. 从 kfz_config 表读取价格参数
2. 从 kfz_goods 表取一条记录（按 fail_count ASC, updated_at DESC）
   └→ 优先处理失败次数少的记录
3. 调用 outGetAllGoods(isbn, quality, queryIndex) 爬孔网搜索 API
   ├→ 使用轮询方式从 kfz_token 表选一个启用 Token
   └→ URL: https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list
4. 计算最终价格：
   finalPrice = totalPrice - placeholderDownPrice - minShippingFee
   if (finalPrice < minPrice) → finalPrice = minPrice
5. 更新 kfz_goods 表（price / shipping_fee / final_price / updated_at）
6. 调用 sendCallback(outID, userID, finalPrice, minShippingFee)
   └→ POST 
      product_id=xxx&user_id=xxx&sale_price=xxx&cost=xxx
      （sale_price 和 cost 均为整数，单位：分 = 原价 × 100）
7. 失败处理：MarkFailed → fail_count++，下次优先重试
`

---

## 六、数据库设计

### 6.1 kfz_token 表

`sql
CREATE TABLE kfz_token (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT NOT NULL,
  token TEXT NOT NULL,
  is_enable INTEGER DEFAULT 1
);
`

**用途**：存储多个孔网账号 Token，支持轮询切换，防止单个 Token 请求过快被限

### 6.2 kfz_goods 表

`sql
CREATE TABLE kfz_goods (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  isbn TEXT NOT NULL,
  out_id TEXT,
  quality TEXT,
  user_id TEXT,
  query_index INTEGER DEFAULT 1,
  placeholder_down_price REAL DEFAULT 0.01,
  min_shipping_fee REAL DEFAULT 5.0,
  min_price REAL DEFAULT 1.0,
  price REAL DEFAULT 0,
  shipping_fee REAL DEFAULT 0,
  final_price REAL DEFAULT 0,
  fail_count INTEGER DEFAULT 0,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`

**用途**：上游调用 QueryGoods 时写入记录，定时器按 fail_count ASC 排序依次处理

### 6.3 kfz_config 表

`sql
CREATE TABLE kfz_config (
  id INTEGER PRIMARY KEY CHECK (id = 1),
  new_price REAL DEFAULT 0,
  placeholder_down_price REAL DEFAULT 0.01,
  min_shipping_fee REAL DEFAULT 5.0,
  min_price REAL DEFAULT 1.0,
  query_index INTEGER DEFAULT 1
);
`

**用途**：始终只有一条记录（ID=1），与 config.yaml 共同构成配置体系。yaml 负责基础配置（Port/Timer/APIRateLimit/CallbackURL），DB 负责价格参数

---

## 七、关键设计

### Token 轮询机制

`
tokens = GetEnabledTokens()  // 从 DB 取出所有启用 Token
currentTokenIndex = 0        // 原子操作的轮询索引

每次爬孔网 API 时：
  token = tokens[currentTokenIndex % len(tokens)]
  currentTokenIndex++
`

> 目的：多个孔网账号 Token 轮询使用，防止单个账号请求过快被限

### 价格计算公式

`
totalPrice = price + shippingFee
finalPrice = totalPrice - placeholderDownPrice - minShippingFee
if finalPrice < minPrice → finalPrice = minPrice
`

最终回调中的 sale_price 和 cost 均为整数（单位：分 = float × 100）

### 配置双层机制

| 来源 | 字段 | 说明 |
|------|------|------|
| config/config.yaml | Port, TimerInterval, APIRateLimit, CallbackURL | 基础运行时配置 |
| kfz_config DB | NewPrice, PlaceholderDownPrice, MinShippingFee, MinPrice, QueryIndex | 价格参数 |

yaml 不可动态修改，DB 配置可通过 API 动态修改（实时生效）。

---

## 八、第三方接口

### 孔网登录

| 步骤 | URL | 方法 |
|------|-----|------|
| 登录 | https://login.kongfz.com/Pc/Login/account | POST (form-data) |
| 获取用户信息 | https://user.kongfz.com/User/Index/getUserInfo/ | GET (Cookie: PHPSESSID=xxx) |

### 孔网商品搜索

| 用途 | URL |
|------|-----|
| 搜索商品 | https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list?dataType=0&page=1&sortType=7&quaSelect=2&keyword=ISBN&quality=品相~ |

### 回调

| 回调 URL | 参数 |
|---------|------|
| http://192.168.101.213:9090/api/product/updatePrice | product_id, user_id, sale_price（分）, cost（分） |

---

## 九、运行与构建

`ash
# 运行（开发）
go run cmd/server/main.go

# 编译
go build -o kfz-goods-pricing.exe ./cmd/server
`

**启动后**：
1. 加载 config/config.yaml
2. 初始化 data/goods_pricing.db（SQLite，自动建表）
3. 启动 HTTP 服务（默认端口 8080）
4. 启动 GUI 窗口（阻塞主线程）
5. 启动定时器（5秒间隔，开始首次同步）

**默认配置**（config/config.yaml）：
`yaml
port: "8080"
timer_interval: 5          # 每5秒执行一次定时任务
api_rate_limit: 2          # API 请求间隔2秒
callback_url: "http://192.168.101.213:9090/api/product/updatePrice"
`

---

## 十、交接注意事项

| 文件/目录 | 说明 | 注意点 |
|-----------|------|--------|
| config/config.yaml | 基础配置 | Port/TimerInterval/APIRateLimit/CallbackURL |
| data/goods_pricing.db | SQLite 数据库 | 运行时自动创建，无需手动初始化 |
| internal/handler/kfz_handler.go | 孔网登录逻辑 | HTTP 直接调用孔网官网，无需 kfz.dll（与之前的 Wails 项目不同） |
| internal/service/goods_service.go | 定时器核心 | 异步处理，依赖 Token 池 |
| kfz-goods-pricing.exe | 可执行文件 | 需与 config/、data/ 目录同目录运行 |

### 快速排查

- **上游调用无响应**：检查 HTTP 服务是否正常（默认端口 8080），检查防火墙
- **定时器无动作**：检查 kfz_goods 是否有数据；检查 Token 是否为空或已过期
- **核价结果全为默认值**：孔网搜索 API 无返回，kfzConfig.NewPrice 作为兜底
- **回调失败**：检查 callbackURL 是否可达（192.168.101.213:9090）
- **Token 失效**：上游系统调用 /api/kfz/login 获取新 Token，或手动 /api/token/add 添加

### 与 Wails 桌面项目的核心区别

| 对比项 | 本项目（kfz-goods-pricing） | Wails 项目们 |
|--------|----------------------------|-------------|
| 类型 | **纯 Go HTTP 服务 + GUI 窗口** | Wails 桌面应用 |
| 前端 | **无**（无 Vue/HTML） | Vue 3 + Element Plus |
| 子进程 | **无** | 有（verify-price-b） |
| Token 来源 | HTTP 直接登录孔网 | kfz.dll（Windows DLL） |
| 数据库 | SQLite（本地文件） | SQLite（同） |
| 部署方式 | 后台 HTTP 服务（可远程调用） | 桌面 exe（本地运行） |

