375 lines
15 KiB
Markdown
375 lines
15 KiB
Markdown
# 商品定价服务(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(本地运行) |
|
||
|