From bd6635f5be53065cca2e9ebeaca40c0efff0ea73 Mon Sep 17 00:00:00 2001 From: 97694731 <97694731@qq.com> Date: Mon, 15 Jun 2026 13:29:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A5=97=E8=A3=85=E4=B9=A6=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- es/es_search.go | 3 +- main.go | 2 +- md/API.md | 1657 ++++++++++++++++++++++++++++++++++------------- service/book.go | 260 ++++++-- 4 files changed, 1429 insertions(+), 493 deletions(-) diff --git a/es/es_search.go b/es/es_search.go index cc86d6e..df3e785 100644 --- a/es/es_search.go +++ b/es/es_search.go @@ -26,6 +26,7 @@ import ( ) const ESIndex = "books-from-mysql-v2" +const ESIndexV3 = "books-from-mysql-v3" // ESBookResponse 用于返回给Java客户端的格式,ID为简单的int64 type ESBookResponse struct { @@ -2585,7 +2586,7 @@ func (svc *ESSearchService) SearchBookByISBNHandlerToPsi(c *gin.Context) { endpoint := c.FullPath() // Redis 查询(使用监控) - db1Client, err := redisClient.GetClientByName("db1") + db1Client, err := redisClient.GetClientByName("db10") if err == nil { monitoredRedis := monitor.NewMonitoredRedisClient(db1Client, endpoint) val, _, err := monitoredRedis.Get(ctx, isbn) diff --git a/main.go b/main.go index 44be78a..a0097ef 100644 --- a/main.go +++ b/main.go @@ -315,7 +315,7 @@ func main() { r.POST("/api/es/addBookToES", bookController.AddBookToESHandler) // 更新:根据ISBN通用更新图书字段 r.POST("/api/es/updateBookFieldsByISBN", bookController.UpdateBookFieldsByISBNHandler) - // 更新:根据ISBN通用更新图书字段 + // 更新:根据ISBN更新商品分类字段 r.POST("/api/es/updateBookCatIdByISBN", bookController.UpdateBookCatIdByISBNHandler) // 删除:根据ISBN删除ES数据 r.GET("/api/es/DeleteBookByISBN", bookController.DeleteBookHandler) diff --git a/md/API.md b/md/API.md index 507dce8..fc53473 100644 --- a/md/API.md +++ b/md/API.md @@ -1,28 +1,37 @@ -# 图书中心系统 API 文档 +# 选品中心系统 API 文档 ## 基础信息 +- **项目名称**: 选品中心 (centerBook) - **Base URL**: `http://localhost:9009` -- **API 版本**: v1.0 +- **API 版本**: v2.0 - **数据格式**: JSON - **字符编码**: UTF-8 +- **基础技术栈**: Go 1.24 + Gin + Elasticsearch + MySQL + Redis --- ## 目录 -- [图书管理](#图书管理) -- [搜索服务](#搜索服务) -- [销量管理](#销量管理) -- [图片管理](#图片管理) -- [系统监控](#系统监控) -- [Elasticsearch 操作](#elasticsearch-操作) +- [通用说明](#通用说明) +- [一、ES 图书搜索接口](#一es-图书搜索接口) +- [二、ES 图书管理接口(新版 Controller)](#二es-图书管理接口新版-controller) +- [三、ES 图书管理接口(旧版 Service)](#三es-图书管理接口旧版-service) +- [四、API 监控接口](#四api-监控接口) +- [五、ERP 接口](#五erp-接口) +- [六、WebSocket](#六websocket) +- [七、定价加密/解密接口(已注释)](#七定价加密解密接口已注释) +- [八、SQL 健康监控接口(已注释)](#八sql-健康监控接口已注释) +- [九、图书管理接口 MySQL 版(已注释)](#九图书管理接口-mysql-版已注释) +- [附录:ESBook 完整字段说明](#附录esbook-完整字段说明) +- [附录:通用错误码](#附录通用错误码) +- [附录:curl 请求示例汇总](#附录curl-请求示例汇总) --- -## 通用响应格式 +## 通用说明 -### 成功响应 +### 成功响应格式 ```json { @@ -32,7 +41,7 @@ } ``` -### 错误响应 +### 错误响应格式 ```json { @@ -41,7 +50,7 @@ } ``` -### 分页响应 +### 分页响应格式 ```json { @@ -52,53 +61,425 @@ } ``` +### 全局中间件 + +所有请求经过以下中间件处理: + +| 中间件 | 说明 | +|--------|------| +| **CORS** | 允许来源: `localhost:82`, `103.236.91.138:9009`, `test.centerbook.buzhiyushu.cn`, `centerbook.buzhiyushu.cn` | +| **RequestAuditLogger** | 统一请求审计日志(记录 endpoint、URI、状态码、IP、UA、耗时) | +| **NotFoundHandler** | 未匹配路由统一处理(记录 404 来源) | + +### 响应字段说明 + +| 字段 | 说明 | +|------|------| +| `fix_price` | 定价,单位为 **分**(需除以 100 转换为元) | +| `update_time` | 更新时间,Unix 时间戳(秒) | +| `publication_time` | 出版时间,格式为 `2006-01` | +| `page` | 页码,从 **1** 开始 | +| `per_page` / `pageSize` | 每页数量,最大支持 **1000** | +| `book_pic` | 图片对象,格式: `{"localPath": "", "pddPath": "http://..."}` | +| `book_pic_s` | 小图对象,格式: `{"localPath": "", "pddResponse": "http://..."}` | + --- -## 图书管理 +## 一、ES 图书搜索接口 -### 1. 根据 ISBN 查询图书 +### 1.1 ISBN 模糊搜索 -**请求**: -```http -GET /api/book/isbn?isbn=9787111111111 +``` +GET /api/es/searchByISBNLike ``` -**参数**: +**功能说明**: 根据 ISBN 进行模糊搜索,优先查 Redis,未命中则查询 ES,并异步从孔夫子抓取补充数据。 + +**请求参数**: + | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| -| isbn | string | 是 | 图书ISBN号 | +| isbn | string | 是 | ISBN 号(支持模糊匹配) | **响应示例**: + +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "id": 123456, + "book_name": "Go语言编程", + "isbn": "9787111111111", + "author": "作者名", + "publisher": "清华大学出版社", + "category": "图书/计算机/编程", + "fix_price": 9900, + "book_pic": {"localPath": "", "pddPath": "http://example.com/image.jpg"}, + "book_pic_s": {"localPath": "", "pddResponse": "http://example.com/image_s.jpg"}, + "sell_counts": 100, + "buy_counts": 500, + "day_sale_7": 10, + "day_sale_15": 25, + "day_sale_30": 60, + "total_sale": 1000 + } + ] +} +``` + +--- + +### 1.2 ISBN 精确搜索 + +``` +GET /api/es/searchByISBN +``` + +**功能说明**: 根据 ISBN 精确查询图书信息,支持 Redis 缓存、数据库和外部 API 三级回退查询。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| isbn | string | 是 | ISBN 号(精确匹配) | + +**响应示例**: + ```json { "code": 200, "message": "success", "data": { "id": 123456, - "isbn": "9787111111111", "book_name": "Go语言编程", + "isbn": "9787111111111", "author": "作者名", "publisher": "清华大学出版社", - "fix_price": 99.00, - "book_pic": "http://example.com/image.jpg", + "fix_price": 9900, + "book_pic": {"localPath": "", "pddPath": "http://example.com/image.jpg"}, + "book_pic_s": {"localPath": "", "pddResponse": ""}, + "sell_counts": 100, + "buy_counts": 500, + "day_sale_7": 10, + "day_sale_15": 25, + "day_sale_30": 60, + "day_sale_180": 200, + "day_sale_365": 800, + "total_sale": 1000, + "is_suit": 0, + "is_illegal": 0, + "is_return": 0, "update_time": "1705228800" + }, + "source": "es" +} +``` + +> **数据来源说明**: `source` 字段标识数据来源,取值: `redis` / `database` / `external_api` / `es` + +--- + +### 1.3 咸鱼商品下架回调搜索 + +``` +GET /api/es/searchByISBNByXyCallBack +``` + +**功能说明**: 为咸鱼(Xianyu)商品下架回调提供的 ISBN 精确搜索接口。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| isbn | string | 是 | ISBN 号 | + +**响应**: 同 [1.2 ISBN 精确搜索](#12-isbn-精确搜索) + +--- + +### 1.4 PSI 系统搜索 + +``` +GET /api/es/searchByISBNtoPsi +``` + +**功能说明**: 为 PSI 系统提供的 ISBN 精确搜索接口,返回包含 `cat_id` (类目信息)的完整数据。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| isbn | string | 是 | ISBN 号 | + +**响应示例**: + +```json +{ + "code": 200, + "message": "success", + "data": { + "id": 123456, + "book_name": "Go语言编程", + "isbn": "9787111111111", + "cat_id": { + "pin_duo_duo_cat_id": "12345", + "kong_fu_zi_cat_id": "67890", + "xian_yu_cat_id": "11121" + }, + "...": "..." } } ``` -### 2. 添加图书到 ES +--- -**请求**: -```http -POST /api/es/book -Content-Type: application/json +### 1.5 书名搜索 +``` +GET /api/es/searchByBookName +``` + +**功能说明**: 根据书名进行模糊搜索。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| book_name | string | 是 | 书名(支持模糊匹配) | +| page | int | 否 | 页码,默认 1 | +| size | int | 否 | 每页数量,默认 10 | + +**响应示例**: + +```json +{ + "code": 200, + "message": "success", + "data": [...], + "count": 50 +} +``` + +--- + +### 1.6 全字段搜索 + +``` +GET /api/es/searchAll +``` + +**功能说明**: 在所有文本字段(书名、作者、ISBN、出版社等)中进行关键词搜索。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| q | string | 是 | 搜索关键词 | +| page | int | 否 | 页码,默认 1 | +| size | int | 否 | 每页数量,默认 10 | + +**响应示例**: + +```json +{ + "code": 200, + "message": "success", + "data": [...], + "count": 100 +} +``` + +--- + +### 1.7 条件搜索图书基础信息(新版) + +``` +GET /api/es/getBookBaseInfoES +``` + +**功能说明**: 根据多条件查询 ES 图书信息(新版 Controller 重构版本)。支持精确匹配、范围查询、模糊搜索等。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| page | int | 否 | 页码,默认 1 | +| pageSize / per_page | int | 否 | 每页数量,默认 10 | +| saleSelect | string | 否 | 销量维度选择: `7`(7天), `15`, `30`, `60`, `90`, `180`, `365`, `0`(今年), `1`(去年) | +| book_name | string | 否 | 书名(精确匹配) | +| isbn | string | 否 | ISBN(精确匹配) | +| author | string | 否 | 作者(精确匹配) | +| category | string | 否 | 分类(模糊匹配) | +| publisher | string | 否 | 出版社(精确匹配) | +| publication_time | string | 否 | 出版时间范围,逗号分隔,如 `2000,2020` | +| binding_layout | string | 否 | 装帧 | +| fix_price | string | 否 | 定价范围,如 `1000,5000`(分为单位) | +| isSuit | string | 否 | 是否套装: `0`(否), `1`(是) | +| is_return | string | 否 | 是否驳回: `0`(否), `1`(是) | +| is_filter | string | 否 | 过滤字段 | +| book_pic | string | 否 | 是否有图: `0`(无图), `1`(有图) | +| picType | string | 否 | 图片类型: `1`(官图), `2`(小图) | +| buy_counts | string | 否 | 购买次数范围,如 `100,1000`(受 saleSelect 影响) | +| sell_counts | string | 否 | 在售数量范围,如 `10,1000` | +| day_sale_7 ~ day_sale_365 | string | 否 | 各维度销量范围 | +| this_year_sale | string | 否 | 今年销量范围 | +| last_year_sale | string | 否 | 去年销量范围 | +| totalSale_range | string | 否 | 总销量范围 | +| id | string | 否 | ES 文档 ID | +| page_count | string | 否 | 页数 | +| word_count | string | 否 | 字数 | +| kongfz_categories | string | 否 | 孔夫子分类列表,多个用逗号分隔 | +| kongfz_include | int8 | 否 | 是否涵盖: `1`(是), `2`(否) | +| shopType | string | 否 | 店铺类型 | + +**响应示例**: + +```json +{ + "current_page": 1, + "data": [ + { + "id": 123456, + "book_name": "Go语言编程", + "book_pic": {"localPath": "", "pddPath": "http://example.com/image.jpg"}, + "book_pic_s": {"localPath": "", "pddResponse": ""}, + "book_pic_b": "", + "book_pic_w": {}, + "book_def_pic": {"localPath": "", "pddPath": ""}, + "isbn": "9787111111111", + "author": "作者名", + "category": "图书/计算机/编程", + "publisher": "清华大学出版社", + "publication_time": "2020-01", + "binding_layout": "平装", + "fix_price": 9900, + "content": "图书简介内容", + "is_suit": 0, + "day_sale_7": 10, + "day_sale_15": 25, + "day_sale_30": 60, + "day_sale_60": 120, + "day_sale_90": 200, + "day_sale_180": 400, + "day_sale_365": 800, + "this_year_sale": 50, + "last_year_sale": 800, + "total_sale": 1500, + "buy_counts": 500, + "sell_counts": 100, + "book_pic_obj": {}, + "book_pic_obj_s": {}, + "update_time": "1705228800", + "is_illegal": 0, + "is_return": 0, + "is_filter": "", + "page_count": "300", + "word_count": "200000", + "book_format": "16", + "other": {} + } + ], + "per_page": 10, + "total": 156 +} +``` + +--- + +### 1.8 批量获取图书基础信息 + +``` +GET /api/es/batchGetBookBaseInfoES +``` + +**功能说明**: 为核价软件提供的批量获取接口,根据条件批量查询 ES 图书信息。 + +**请求参数**: 同 [1.7](#17-条件搜索图书基础信息新版),增加以下参数: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| isbns | string | 否 | ISBN 列表,逗号分隔(最多 50 个) | + +**响应示例**: + +```json +{ + "code": 200, + "message": "success", + "data": [...], + "count": 50 +} +``` + +--- + +### 1.9 多条件高级搜索 + +``` +GET /api/es/searchAdvanced +``` + +**功能说明**: 多条件 AND 逻辑高级搜索,所有参数同时参与过滤。 + +**请求参数**: 同 [1.7](#17-条件搜索图书基础信息新版) + +**响应**: 同 [1.7](#17-条件搜索图书基础信息新版) + +--- + +### 1.10 ID 范围计数 + +``` +GET /api/es/countByIDRange +``` + +**功能说明**: 统计指定 ID 范围内的文档数量。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| minID | int | 是 | 最小 ID | +| maxID | int | 是 | 最大 ID | + +**响应示例**: + +```json +{ + "code": 200, + "minID": 1, + "maxID": 100000, + "count": 50000 +} +``` + +--- + +## 二、ES 图书管理接口(新版 Controller) + +### 2.1 添加/更新图书 + +``` +POST /api/es/addBookToES +``` + +**功能说明**: 根据 ISBN 查询 ES 中是否存在,不存在则新增数据,存在则根据参数更新。同时同步数据到 Redis。 + +**请求体** (JSON): + +```json { "book_name": "Go语言编程实战", "isbn": "9787111122222", "author": "作者名", "publisher": "出版社", - "fix_price": 89.00, + "category": "图书/计算机/编程", + "fix_price": 8900, + "binding_layout": "平装", + "publication_time": "2023-01", + "content": "图书简介", "book_pic": { "localPath": "", "pddPath": "http://example.com/image.jpg" @@ -106,48 +487,373 @@ Content-Type: application/json "book_pic_s": { "localPath": "", "pddResponse": "http://example.com/image_s.jpg" + }, + "is_suit": 0, + "day_sale_7": 0, + "day_sale_15": 0, + "day_sale_30": 0, + "day_sale_60": 0, + "day_sale_90": 0, + "day_sale_180": 0, + "day_sale_365": 0, + "this_year_sale": 0, + "last_year_sale": 0, + "total_sale": 0, + "buy_counts": 0, + "sell_counts": 0 +} +``` + +**响应示例**: + +```json +{ + "data": { + "id": 123457, + "book_name": "Go语言编程实战", + "isbn": "9787111122222", + "...": "..." + }, + "source": "service" +} +``` + +> `source` 字段: `es`(已存在,从 ES 返回) / `service`(新增插入) + +--- + +### 2.2 更新图书字段 + +``` +POST /api/es/updateBookFieldsByISBN +``` + +**功能说明**: 根据 ISBN 通用更新图书字段,支持动态指定要更新的字段。 + +- data 中的所有字段(含 `is_suit`)都会更新到 v2 索引 +- `is_suit=1` 时,额外将文档同步到 `books-from-mysql-v3` 索引(存在则覆盖,不存在则新增) +- `is_suit=0` 时,额外从 `books-from-mysql-v3` 索引中删除该 ISBN 的文档 +- 同步 Redis(db10),包含完整数据含 `cat_id` + +**请求体** (JSON): + +```json +{ + "isbn": "9787111111111", + "data": { + "fix_price": 8800, + "author": "新作者名", + "is_suit": 1 + } +} +``` + +**参数说明**: + +| 参数 | 位置 | 类型 | 必需 | 说明 | +|------|------|------|------|------| +| `isbn` | body | string | 是 | ISBN 号 | +| `data` | body | object | 是 | 要更新的字段键值对 | +| `data.is_suit` | body.data | int | 否 | 1→同步到v3,0→从v3删除,不传→不操作v3 | + +**响应示例**: + +```json +{ + "code": 200, + "message": "success", + "isbn": "9787111111111", + "updated": 1, + "fields_updated": 3, + "updated_fields": ["fix_price", "author", "total_sale"] +} +``` + +--- + +### 2.3 更新商品分类 + +``` +POST /api/es/updateBookCatIdByISBN +``` + +**功能说明**: 根据 ISBN 更新商品分类字段(cat_id),支持拼多多、孔夫子、闲鱼三个平台的分类同时更新或只更新传入平台的分类。 + +**请求体** (JSON): + +```json +{ + "isbn": "9787111111111", + "data": { + "cat_id": { + "pin_duo_duo_cat_id": "12345", + "kong_fu_zi_cat_id": "67890", + "xian_yu_cat_id": "11121" + } } } ``` **响应示例**: + +```json +{ + "code": 200, + "message": "success", + "isbn": "9787111111111", + "updated": 1, + "fields_updated": 1, + "updated_fields": ["cat_id"] +} +``` + +--- + +### 2.4 根据 ISBN 删除图书 + +``` +GET /api/es/DeleteBookByISBN +``` + +**功能说明**: 根据 ISBN 删除 ES 中的图书数据。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| isbn | string | 是 | ISBN 号 | + +**响应示例**: + +```json +{ + "code": 200, + "message": "删除成功" +} +``` + +--- + +### 2.5 根据 ID 删除图书 + +``` +GET /api/es/DeleteBookByID +``` + +**功能说明**: 根据 ES 文档 ID 删除图书数据。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | string | 是 | ES 文档 ID | + +**响应示例**: + +```json +{ + "code": 200, + "message": "删除成功", + "data": { + "id": "123456", + "deleted": true + } +} +``` + +--- + +## 三、ES 图书管理接口(旧版 Service) + +### 3.1 更新在售数量(自动获取) + +``` +POST /api/es/updateSellCountsByISBN +``` + +**功能说明**: 根据 ISBN 调用 Tail 外部 API 获取在售数量并更新到 ES。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| isbn | string | 是 | ISBN 号 | + +**响应示例**: + +```json +{ + "code": 200, + "message": "success", + "data": { + "isbn": "9787111111111", + "on_sale_count": 150, + "async": true + } +} +``` + +> `async: true` 表示操作为异步执行 + +--- + +### 3.2 直接更新在售数量 + +``` +POST /api/es/updateSellCountsDirect +``` + +**功能说明**: 按入参直接更新在售数量到 ES。 + +**请求体** (JSON): + +```json +{ + "isbn": "9787111111111", + "sell_counts": 100 +} +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "success", + "data": { + "isbn": "9787111111111", + "sell_counts": 100, + "async": true + } +} +``` + +--- + +### 3.3 更新套装标记 + +``` +POST /api/es/updateBookSuitByISBN +``` + +**功能说明**: 根据 ISBN 自动判断书名是否包含套装关键字并更新 `is_suit` 字段。 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| isbn | string | 是 | ISBN 号 | + +**响应示例**: + +```json +{ + "code": 200, + "message": "success", + "data": { + "isbn": "9787111111111", + "book_name": "Go语言编程(套装全三册)", + "is_suit": 1, + "updated": 1, + "contains_suit_keyword": true + } +} +``` + +--- + +### 3.4 完整插入图书 + +``` +POST /api/es/addBookFullToES +``` + +**功能说明**: 完整插入接口,支持所有 ESBook 字段,`book_name` 强制要求 string 类型。 + +**请求体** (JSON): + +```json +{ + "book_name": "Go语言编程", + "isbn": "9787111111111", + "author": "作者名", + "publisher": "清华大学出版社", + "category": "图书/计算机/编程", + "publication_time": "2020-01", + "binding_layout": "平装", + "fix_price": 9900, + "content": "图书简介内容", + "is_suit": 0, + "book_pic": {"localPath": "", "pddPath": "http://example.com/image.jpg"}, + "book_pic_s": {"localPath": "", "pddResponse": "http://example.com/image_s.jpg"}, + "book_pic_b": "", + "book_pic_w": {}, + "day_sale_7": 0, + "day_sale_15": 0, + "day_sale_30": 0, + "day_sale_60": 0, + "day_sale_90": 0, + "day_sale_180": 0, + "day_sale_365": 0, + "this_year_sale": 0, + "last_year_sale": 0, + "total_sale": 0, + "buy_counts": 0, + "sell_counts": 0, + "is_illegal": 0, + "is_return": 0, + "is_filter": "" +} +``` + +**响应示例**: + ```json { "code": 200, "message": "success", "data": { "id": 123457, - "source": "service" + "result": "created" } } ``` -### 3. 批量添加图书 +--- -**请求**: -```http -POST /api/es/books/batch -Content-Type: application/json +### 3.5 批量插入图书 +``` +POST /api/es/batchAddBookToES +``` + +**功能说明**: 批量插入多本图书到 ES,单次最多 100 本。 + +**请求体** (JSON): + +```json { "books": [ { "book_name": "Go语言编程", "isbn": "9787111111111", "author": "作者1", - "publisher": "出版社1" + "publisher": "出版社1", + "fix_price": 9900 }, { "book_name": "Python实战", "isbn": "9787111133333", "author": "作者2", - "publisher": "出版社2" + "publisher": "出版社2", + "fix_price": 8900 } ] } ``` **响应示例**: + ```json { "code": 200, @@ -174,14 +880,33 @@ Content-Type: application/json } ``` -### 4. 检查图书是否存在 +--- -**请求**: -```http -GET /api/es/book/exists?isbn=9787111111111 +### 3.6 检查图书是否存在 + +``` +GET /api/es/checkBookExists +POST /api/es/checkBookExists +``` + +**功能说明**: 根据 ISBN 检查书籍是否在 ES 中存在,支持 GET 和 POST 两种方式。 + +**请求参数** (GET): + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| isbn | string | 是 | ISBN 号 | + +**请求体** (POST): + +```json +{ + "isbn": "9787111111111" +} ``` **响应示例**: + ```json { "code": 200, @@ -196,469 +921,517 @@ GET /api/es/book/exists?isbn=9787111111111 } ``` -### 5. 更新图书字段 - -**请求**: -```http -PUT /api/es/book/fields -Content-Type: application/json - -{ - "isbn": "9787111111111", - "data": { - "fix_price": 88.00, - "author": "新作者名", - "total_sale": 1000 - } -} -``` - -**响应示例**: -```json -{ - "code": 200, - "message": "success", - "data": { - "isbn": "9787111111111", - "updated": 1, - "fields_updated": 3, - "updated_fields": ["fix_price", "author", "total_sale"] - } -} -``` - -### 6. 删除图书 - -**请求**: -```http -DELETE /api/es/book?isbn=9787111111111 -``` - -或 - -```http -DELETE /api/es/book?id=123456 -``` - -**响应示例**: -```json -{ - "code": 200, - "message": "success", - "data": { - "deleted": true - } -} -``` - --- -## 搜索服务 +### 3.7 检查套装书 -### 1. 关键词搜索 - -**请求**: -```http -GET /api/search?keyword=Go语言&page=1&pageSize=10 +``` +GET /api/es/checkBookSuit ``` -**参数**: +**功能说明**: 检查书名是否包含套装关键字(如"套装"、"全册"等)。 + +**请求参数**: + | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| -| keyword | string | 是 | 搜索关键词 | -| page | int | 否 | 页码,默认1 | -| pageSize | int | 否 | 每页数量,默认10 | +| bookName | string | 是 | 书名 | **响应示例**: -```json -{ - "count": 100, - "data": [ - { - "id": 123456, - "book_name": "Go语言编程", - "author": "作者名", - "isbn": "9787111111111", - "publisher": "清华大学出版社", - "fix_price": 99.00 - } - ] -} -``` -### 2. 多条件搜索 - -**请求**: -```http -GET /api/search/conditions?book_name=Go&author=张三&page=1&pageSize=20 -``` - -**支持的查询参数**: -- `book_name` - 书名 -- `isbn` - ISBN -- `author` - 作者 -- `category` - 分类 -- `publisher` - 出版社 -- `publication_time` - 出版时间范围(逗号分隔) -- `day_sale_7` - 7天销量范围 -- `total_sale` - 总销量范围 -- `sell_counts` - 在售数量范围 -- `is_suit` - 是否套装 (0/1) -- `is_return` - 是否驳回 (0/1) -- `is_filter` - 过滤字段 -- `book_pic` - 是否有图 (0/1) -- `picType` - 图片类型 (1-官图, 2-小图) -- `saleSelect` - 销量维度选择 (7/15/30/60/90/180/365/0/1) -- `categoryType` - 分类类型 (1-教材, 其他-非教材) - -**响应示例**: -```json -{ - "code": 200, - "current_page": 1, - "data": [...], - "per_page": 20, - "total": 156 -} -``` - -### 3. 全字段搜索 - -**请求**: -```http -GET /api/search/all?q=Go语言编程 -``` - -**参数**: -- `q` - 搜索关键词(搜索所有字段) - ---- - -## 销量管理 - -### 1. 更新在售数量 - -**请求**: -```http -POST /api/sellcounts/update?isbn=9787111111111&onSaleCount=100 -``` - -**参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| isbn | string | 是 | 图书ISBN | -| onSaleCount | int | 是 | 在售数量(非负整数) | - -**响应示例**: ```json { "code": 200, "message": "success", "data": { - "isbn": "9787111111111", - "sell_counts": 100, - "async": true - } -} -``` - -### 2. 从外部API更新在售数量 - -**请求**: -```http -POST /api/sellcounts/fetch?isbn=9787111111111 -``` - -**说明**: 自动调用 tail API 获取在售数量并更新 - -**响应示例**: -```json -{ - "code": 200, - "message": "success", - "data": { - "isbn": "9787111111111", - "on_sale_count": 150, - "async": true - } -} -``` - ---- - -## 图片管理 - -### 1. 更新图书图片 - -**请求**: -```http -POST /api/book/pic/update?isbn=9787111111111&book_pic=http://example.com/new.jpg&book_pic_s=http://example.com/new_s.jpg -``` - -**参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| isbn | string | 是 | 图书ISBN | -| book_pic | string | 否 | 大图URL | -| book_pic_s | string | 否 | 小图URL | - -**响应示例**: -```json -{ - "code": 200, - "message": "success", - "data": { - "updated": 1 - } -} -``` - ---- - -## 系统监控 - -### 1. 健康检查 - -**请求**: -```http -GET /health -``` - -**响应示例**: -```json -{ - "status": "healthy", - "mysql": "connected", - "redis": "connected", - "elasticsearch": "connected", - "timestamp": "2025-01-14T10:30:00Z" -} -``` - -### 2. 服务就绪检查 - -**请求**: -```http -GET /ready -``` - -**响应示例**: -```json -{ - "ready": true -} -``` - ---- - -## Elasticsearch 操作 - -### 1. 查询所有索引 - -**请求**: -```http -GET /api/es/indices -``` - -**响应示例**: -```json -{ - "code": 200, - "message": "success", - "data": [ - "books-from-mysql-v2", - "books-from-mysql-new", - "test-go-index" - ] -} -``` - -### 2. 获取索引详情 - -**请求**: -```http -GET /api/es/index/detail?indexName=books-from-mysql-v2 -``` - -**响应示例**: -```json -{ - "code": 200, - "message": "success", - "data": { - "books-from-mysql-v2": { - "aliases": {}, - "mappings": { ... }, - "settings": { ... } - } - } -} -``` - -### 3. 获取文档数量 - -**请求**: -```http -GET /api/es/index/count?indexName=books-from-mysql-v2 -``` - -**响应示例**: -```json -{ - "code": 200, - "message": "success", - "data": { - "count": 1000000 - } -} -``` - -### 4. 检查套装书 - -**请求**: -```http -GET /api/book/check-suit?bookName=Go语言编程(套装) -``` - -**响应示例**: -```json -{ - "code": 200, - "message": "success", - "data": { - "book_name": "Go语言编程(套装)", + "book_name": "Go语言编程(套装全三册)", "is_suit": true } } ``` -### 5. 更新套装标记 +--- -**请求**: -```http -PUT /api/book/suit -Content-Type: application/json +## 四、API 监控接口 +> API 监控用于追踪 ES 和 Redis 调用的性能指标,支持实时统计和历史查询。 + +### 4.1 获取指定 API 统计 + +``` +GET /api/api-monitor/stats +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| endpoint | string | 是 | 接口端点名称,如 `/api/es/searchByISBN` | + +**响应示例**: + +```json { - "isbn": "9787111111111" + "status": "success", + "data": { + "endpoint": "/api/es/searchByISBN", + "total_calls": 1500, + "success_calls": 1490, + "failed_calls": 10, + "avg_duration_ms": 45.5, + "max_duration_ms": 1200, + "min_duration_ms": 5, + "total_es_calls": 800, + "total_redis_calls": 700, + "qps": 2.5 + }, + "timestamp": "2025-01-14 10:30:00" } ``` -**说明**: 自动根据书名判断是否为套装书并更新 +--- + +### 4.2 获取所有 API 统计 + +``` +GET /api/api-monitor/all-stats +``` + +**功能说明**: 获取所有被监控接口的统计信息,按接口名排序。 **响应示例**: + ```json { - "code": 200, - "message": "success", + "status": "success", + "data": [ + { + "endpoint": "/api/es/searchByISBN", + "total_calls": 1500, + "...": "..." + } + ], + "count": 5, + "timestamp": "2025-01-14 10:30:00" +} +``` + +--- + +### 4.3 获取 ES 调用记录 + +``` +GET /api/api-monitor/es-calls +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| endpoint | string | 是 | 接口端点名称 | +| page | int | 否 | 页码,默认 1 | +| page_size | int | 否 | 每页数量,默认 50,最大 500 | + +**响应示例**: + +```json +{ + "status": "success", "data": { - "isbn": "9787111111111", - "book_name": "Go语言编程(套装)", - "is_suit": 1, - "updated": 1, - "contains_suit_keyword": true + "calls": [ + { + "id": 1, + "timestamp": "2025-01-14 10:30:00", + "duration_ms": 35, + "success": true, + "operation": "search", + "index": "books-from-mysql-v2", + "query": "{...}" + } + ], + "count": 50, + "total": 800, + "page": 1, + "page_size": 50, + "total_pages": 16 + }, + "timestamp": "2025-01-14 10:30:00" +} +``` + +--- + +### 4.4 获取 Redis 调用记录 + +``` +GET /api/api-monitor/redis-calls +``` + +**请求参数**: 同 [4.3](#43-获取-es-调用记录) + +**响应**: 同 [4.3](#43-获取-es-调用记录)(返回 Redis 调用记录) + +--- + +### 4.5 获取调用详情 + +``` +GET /api/api-monitor/call-detail +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| endpoint | string | 是 | 接口端点名称 | +| call_id | string | 是 | 调用记录 ID | +| type | string | 是 | 调用类型: `es` 或 `redis` | + +**响应示例**: + +```json +{ + "status": "success", + "data": { + "id": 1, + "type": "ES", + "timestamp": "2025-01-14 10:30:00", + "duration_ms": 35, + "success": true, + "error": "", + "operation": "search", + "key_or_index": "books-from-mysql-v2", + "query": "{\"query\":{\"match\":{\"isbn\":\"...\"}}}", + "request": "...", + "response": "..." } } ``` -### 6. 统计ID范围数量 +--- -**请求**: -```http -GET /api/es/count/range?minID=1&maxID=100000 +### 4.6 监控仪表板 + +``` +GET /api/api-monitor/dashboard ``` -**响应示例**: -```json -{ - "code": 200, - "minID": 1, - "maxID": 100000, - "count": 50000 -} +**功能说明**: 返回 HTML 格式的 API 监控仪表板页面,包含可视化统计数据。 + +**响应**: HTML 页面(`Content-Type: text/html`) + +--- + +## 五、ERP 接口 + +### 5.1 插入筛选集合 + +``` +POST /api/erp/insertFilterSet +``` + +**功能说明**: 插入 ERP 筛选集合数据。 + +**请求参数**: 通过 Query 参数传递 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| (各类参数) | - | - | 按 ERP 模块定义 | + +--- + +## 六、WebSocket + +### 6.1 WebSocket 连接 + +``` +GET /ws +``` + +**功能说明**: 升级 HTTP 连接为 WebSocket,用于实时消息推送。 + +**协议**: 支持文本消息通信,服务器端维护活跃客户端列表并支持广播。 + +--- + +## 七、定价加密/解密接口(已注释) + +> ⚠️ 以下接口代码仍存在但路由已注释,未在线上启用。 + +### 定价链接加密 + +``` +GET /pricingLink +``` + +### 定价链接解密 + +``` +GET /pricingLinkDec +``` + +**加密方式**: RSA-OAEP + AES-256-GCM 混合加密 + +--- + +## 八、SQL 健康监控接口(已注释) + +> ⚠️ 以下接口代码仍存在但路由已注释,未在线上启用。 + +### 8.1 获取 SQL 统计 + +``` +GET /api/sql-health/stats +``` + +### 8.2 获取最近 SQL 记录 + +``` +GET /api/sql-health/recent +``` + +**参数**: `limit`(默认 50,最大 500) + +### 8.3 获取慢查询 + +``` +GET /api/sql-health/slow-queries +``` + +**参数**: `threshold`(阈值 ms,默认 1000) + +### 8.4 获取失败查询 + +``` +GET /api/sql-health/failed-queries +``` + +### 8.5 清除 SQL 记录 + +``` +POST /api/sql-health/clear +``` + +### 8.6 SQL 监控仪表板 + +``` +GET /api/sql-health/dashboard ``` --- -## 错误码说明 +## 九、图书管理接口 MySQL 版(已注释) + +> ⚠️ 以下接口基于 MySQL 数据库,所有路由已注释,功能已迁移至 ES。 + +| 方法 | 路径 | Handler 函数 | 说明 | +|------|------|-------------|------| +| GET | `/api/bookBase/getBookBaseInfo` | `GetBookBaseInfo` | 条件查询(含缓存) | +| GET | `/api/bookBase/getRandomBookBaseInfo` | `GetRandomBookBaseInfo` | 随机查询 | +| GET | `/api/bookBase/GetBookBaseInfoOptimized` | `GetBookBaseInfoOptimized` | 优化查询 | +| GET | `/api/bookBase/getBookByISBN` | `GetBookByISBN` | ISBN 查询(Redis → MySQL → 外部API) | +| GET | `/api/bookBase/getBookById` | `GetBookByID` | ID 查询 | +| POST | `/api/bookBase/putBookBaseInfoToIll` | `SetBookBaseInfoToIll` | 批量设置违规(按ID) | +| POST | `/api/bookBase/putBookBaseInfoToIllByISBN` | `SetBookBaseInfoToIllByISBN` | 批量设置违规(按ISBN) | +| POST | `/api/bookBase/putBookBaseName` | `SetBookBaseInfoBookName` | 修改书名 | +| POST | `/api/bookBase/addBookBaseInfo` | `InsertBaseInfo` | 新增图书(ISBN 去重) | +| POST | `/api/bookBase/updateSales` | `UpdateSales` | 更新销量 | +| GET | `/api/bookBase/getBookPicByISBN` | `GetBookPicByISBN` | 获取图片 | +| POST | `/api/bookBase/uploadBookPic` | `UploadBookPic` | 上传图片 | +| GET/POST | `/api/bookBase/getBookByIsbnXcx` | `getBookByIsbnXcx` | 小程序查询 | +| GET | `/api/bookBase/deleterIsbn` | `exportISBNs` | 导出 ISBN 到 Excel | +| GET | `/api/bookBase/updateBooks` | `updateBooks` | 批量更新图书 | + +--- + +## 附录:ESBook 完整字段说明 + +### 响应字段 (ESBookResponse) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| `id` | int64 | ES 文档 ID | +| `book_name` | string | 书名 | +| `isbn` | string | ISBN | +| `author` | string | 作者 | +| `publisher` | string | 出版社 | +| `category` | string | 分类路径,如 `图书/计算机/编程` | +| `publication_time` | string | 出版时间,格式化后为 `2006-01` | +| `binding_layout` | string | 装帧(平装/精装) | +| `fix_price` | float64 | 定价,单位:分 | +| `content` | string | 内容简介 | +| `book_pic` | object | 大图对象 `{localPath, pddPath}` | +| `book_pic_s` | object | 小图对象 `{localPath, pddResponse}` | +| `book_pic_b` | string | 大图地址 | +| `book_pic_w` | object | 水印图对象 | +| `book_def_pic` | object | 自制官图 `{localPath, pddPath}` | +| `book_pic_obj` | object | 图片对象(扩展) | +| `book_pic_obj_s` | object | 小图对象(扩展) | +| `is_suit` | int | 是否套装: 0-否, 1-是 | +| `is_illegal` | int | 是否非法 | +| `is_return` | int | 是否驳回: 0-否, 1-是 | +| `is_filter` | string | 过滤字段 | +| `day_sale_7` ~ `day_sale_365` | int | 各维度天数销量 | +| `this_year_sale` | int | 今年销量 | +| `last_year_sale` | int | 去年销量 | +| `total_sale` | int | 总销量 | +| `buy_counts` | int64 | 购买次数 | +| `sell_counts` | int64 | 在售/售卖次数 | +| `page_count` | number/string | 页数 | +| `word_count` | number/string | 字数 | +| `book_format` | number/string | 开本 | +| `update_time` | number/string | 更新时间戳 | +| `cat_id` | object | 类目信息(仅 PSI 响应包含) | +| `other` | object | 扩展字段 | + +### 类目对象 (CatIdObject) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| `pin_duo_duo_cat_id` | string | 拼多多分类 ID | +| `kong_fu_zi_cat_id` | string | 孔夫子分类 ID | +| `xian_yu_cat_id` | string | 闲鱼分类 ID | + +--- + +## 附录:通用错误码 | 错误码 | 说明 | |--------|------| | 200 | 成功 | -| 400 | 请求参数错误 | -| 404 | 资源不存在 | -| 500 | 服务器内部错误 | +| 400 | 请求参数错误(缺少必填参数、参数格式错误) | +| 404 | 资源不存在(未找到指定数据) | +| 500 | 服务器内部错误(数据库查询失败、ES 操作异常等) | + +### 常见错误信息 + +| 错误信息 | 可能原因 | +|---------|---------| +| `ISBN不能为空` | 未提供 isbn 参数 | +| `缺少 endpoint 参数` | 监控接口未指定接口名 | +| `该接口暂无监控数据` | 指定的 endpoint 未被监控 | +| `处理失败` / `更新失败` / `删除失败` | ES 操作异常或服务端错误 | --- -## 请求示例 - -### 使用 curl +## 附录:curl 请求示例汇总 ```bash -# 查询图书 -curl "http://localhost:9009/api/book/isbn?isbn=9787111111111" +# ========== ES 搜索 ========== -# 搜索图书 -curl "http://localhost:9009/api/search?keyword=Go语言&page=1&pageSize=10" +# ISBN 模糊搜索 +curl "http://localhost:9009/api/es/searchByISBNLike?isbn=978711" -# 添加图书 -curl -X POST http://localhost:9009/api/es/book \ +# ISBN 精确搜索 +curl "http://localhost:9009/api/es/searchByISBN?isbn=9787111111111" + +# 书名搜索 +curl "http://localhost:9009/api/es/searchByBookName?book_name=Go语言&page=1&size=10" + +# 全字段搜索 +curl "http://localhost:9009/api/es/searchAll?q=Go语言编程" + +# 条件搜索 +curl "http://localhost:9009/api/es/getBookBaseInfoES?book_name=Go语言&page=1&pageSize=10" + +# 高级搜索 +curl "http://localhost:9009/api/es/searchAdvanced?author=张三&publisher=清华大学出版社&page=1&pageSize=20" + +# ID 范围计数 +curl "http://localhost:9009/api/es/countByIDRange?minID=1&maxID=100000" + +# ========== ES 图书管理 ========== + +# 添加/更新图书 +curl -X POST "http://localhost:9009/api/es/addBookToES" \ -H "Content-Type: application/json" \ - -d '{"book_name":"Go语言编程","isbn":"9787111111111","author":"作者名"}' + -d '{"book_name":"Go语言编程","isbn":"9787111111111","author":"作者名","publisher":"出版社","fix_price":9900}' -# 更新在售数量 -curl -X POST "http://localhost:9009/api/sellcounts/update?isbn=9787111111111&onSaleCount=100" +# 更新图书字段(普通更新,不操作v3) +curl -X POST "http://localhost:9009/api/es/updateBookFieldsByISBN" \ + -H "Content-Type: application/json" \ + -d '{"isbn":"9787111111111","data":{"fix_price":8800,"author":"新作者名"}}' + +# 更新图书字段(is_suit=1,同步到v3) +curl -X POST "http://localhost:9009/api/es/updateBookFieldsByISBN" \ + -H "Content-Type: application/json" \ + -d '{"isbn":"9787111111111","data":{"fix_price":8800,"is_suit":1}}' + +# 更新图书字段(is_suit=0,从v3删除) +curl -X POST "http://localhost:9009/api/es/updateBookFieldsByISBN" \ + -H "Content-Type: application/json" \ + -d '{"isbn":"9787111111111","data":{"fix_price":8800,"is_suit":0}}' + +# 更新类目 +curl -X POST "http://localhost:9009/api/es/updateBookCatIdByISBN" \ + -H "Content-Type: application/json" \ + -d '{"isbn":"9787111111111","data":{"cat_id":{"pin_duo_duo_cat_id":"12345","kong_fu_zi_cat_id":"67890","xian_yu_cat_id":"11121"}}}' + +# 删除图书(按 ISBN) +curl "http://localhost:9009/api/es/DeleteBookByISBN?isbn=9787111111111" + +# 删除图书(按 ID) +curl "http://localhost:9009/api/es/DeleteBookByID?id=123456" + +# 完整插入 +curl -X POST "http://localhost:9009/api/es/addBookFullToES" \ + -H "Content-Type: application/json" \ + -d '{"book_name":"Go语言编程","isbn":"9787111111111","author":"作者名","publisher":"出版社","fix_price":9900}' + +# 批量插入 +curl -X POST "http://localhost:9009/api/es/batchAddBookToES" \ + -H "Content-Type: application/json" \ + -d '{"books":[{"book_name":"Go语言","isbn":"9787111111111","author":"作者1"},{"book_name":"Python","isbn":"9787111133333","author":"作者2"}]}' + +# 检查图书存在 +curl "http://localhost:9009/api/es/checkBookExists?isbn=9787111111111" + +# 检查套装书 +curl "http://localhost:9009/api/es/checkBookSuit?bookName=Go语言编程(套装)" + +# ========== 在售数量 ========== + +# 自动获取更新 +curl -X POST "http://localhost:9009/api/es/updateSellCountsByISBN?isbn=9787111111111" + +# 直接更新 +curl -X POST "http://localhost:9009/api/es/updateSellCountsDirect" \ + -H "Content-Type: application/json" \ + -d '{"isbn":"9787111111111","sell_counts":100}' + +# 更新套装标记 +curl -X POST "http://localhost:9009/api/es/updateBookSuitByISBN?isbn=9787111111111" + +# ========== API 监控 ========== + +# 获取指定 API 统计 +curl "http://localhost:9009/api/api-monitor/stats?endpoint=/api/es/searchByISBN" + +# 获取所有 API 统计 +curl "http://localhost:9009/api/api-monitor/all-stats" + +# 获取 ES 调用记录 +curl "http://localhost:9009/api/api-monitor/es-calls?endpoint=/api/es/searchByISBN&page=1&page_size=50" + +# 获取 Redis 调用记录 +curl "http://localhost:9009/api/api-monitor/redis-calls?endpoint=/api/es/searchByISBN" + +# 获取调用详情 +curl "http://localhost:9009/api/api-monitor/call-detail?endpoint=/api/es/searchByISBN&call_id=1&type=es" + +# 监控仪表板(浏览器访问) +curl "http://localhost:9009/api/api-monitor/dashboard" ``` -### 使用 Go - -```go -import "net/http" - -// 查询图书 -resp, err := http.Get("http://localhost:9009/api/book/isbn?isbn=9787111111111") -if err != nil { - log.Fatal(err) -} -defer resp.Body.Close() - -// 解析响应 -var result map[string]interface{} -json.NewDecoder(resp.Body).Decode(&result) -fmt.Println(result) -``` - -### 使用 JavaScript - -```javascript -// 查询图书 -fetch('http://localhost:9009/api/book/isbn?isbn=9787111111111') - .then(response => response.json()) - .then(data => console.log(data)) - .catch(error => console.error(error)); -``` - ---- - -## 注意事项 - -1. **所有时间戳格式**: Unix 时间戳(秒) -2. **价格单位**: 分(需要除以100转换为元) -3. **分页参数**: page 从 1 开始 -4. **异步操作**: 部分更新操作为异步执行,返回 `async: true` -5. **CORS**: 默认允许跨域请求 - --- ## 更新日志 -### v1.0.0 (2025-01-14) -- 初始 API 文档 -- 完整的图书管理接口 -- Elasticsearch 操作接口 -- 销量管理接口 +| 版本 | 日期 | 更新内容 | +|------|------|---------| +| v2.0 | 2025-06-13 | 全面重构,新增新版 Controller 接口、API 监控接口,统一响应格式说明 | +| v1.0 | 2025-01-14 | 初始 API 文档 | --- -**最后更新**: 2025-01-14 +**最后更新**: 2025-06-13 diff --git a/service/book.go b/service/book.go index ae975e6..e501ae5 100644 --- a/service/book.go +++ b/service/book.go @@ -270,56 +270,14 @@ func (svc *BookService) UpdateBookFieldsByISBN(request *request.BookUpdateReques params := make(map[string]interface{}) //svc.AddFilterSet(request.ISBN) - // 判断 is_suit 是否已传递,如果没传则自动检测 - if isSuitValue, exists := request.Data["is_suit"]; exists { - // 定义一个辅助函数来检查值是否为 1 - isOne := func(v interface{}) bool { - switch val := v.(type) { - case int: - return val == 1 - case int8: - return val == 1 - case int16: - return val == 1 - case int32: - return val == 1 - case int64: - return val == 1 - case float32: - return val == 1.0 - case float64: - return val == 1.0 - case string: - return val == "1" - default: - return false - } - } - // 如果手动传递了 is_suit,检查是否为 1 - if isOne(isSuitValue) { - params["is_filter"] = "100100" - scriptParts = append(scriptParts, fmt.Sprintf("ctx._source.is_filter = params.is_filter;")) - } else { - // is_suit 不为 1 时,清空 is_filter - params["is_filter"] = "000000" - scriptParts = append(scriptParts, fmt.Sprintf("ctx._source.is_filter = params.is_filter;")) - } - } else { - // 未传递 is_suit,自动检测并设置 - isSuitValue := map[bool]int{true: 1, false: 0}[es.CheckBookSuit(book.BookName.Value)] - params["is_suit"] = isSuitValue - scriptParts = append(scriptParts, fmt.Sprintf("ctx._source.is_suit = params.is_suit;")) - - // 如果 is_suit 为 1,同时更新 is_filter 为 100100 - if isSuitValue == 1 { - params["is_filter"] = "100100" - scriptParts = append(scriptParts, fmt.Sprintf("ctx._source.is_filter = params.is_filter;")) - } + // 先捕获 is_suit 的值,v3 同步/删除在 v2 更新完成后执行 + var hasIsSuitInData bool + var isSuitInData interface{} + if val, exists := request.Data["is_suit"]; exists { + hasIsSuitInData = true + isSuitInData = val } for field, value := range request.Data { - if field == "is_suit" { - continue - } // 使用配置检查字段是否允许更新 if !fieldConfig.IsAllowUpdate(field) { fmt.Printf("[UpdateBookFieldsByISBN] 字段 %s 不允许更新,已跳过", field) @@ -396,7 +354,18 @@ func (svc *BookService) UpdateBookFieldsByISBN(request *request.BookUpdateReques // 同步 Redis _ = svc.SyncRedisByISBN(request.ISBN, "update") - //svc.AddFilterSet(request.ISBN) + // v2 已更新完成,此时操作 v3 索引 + if hasIsSuitInData { + if isOneValue(isSuitInData) { + if err := svc.syncBookToV3Index(request.ISBN); err != nil { + log.Printf("[WARN] 同步到v3索引失败: %v", err) + } + } else if isZeroValue(isSuitInData) { + if err := svc.deleteBookFromV3Index(request.ISBN); err != nil { + log.Printf("[WARN] 从v3索引删除失败: %v", err) + } + } + } return &UpdateBookResult{ ISBN: request.ISBN, Updated: parsed.Updated, @@ -1093,6 +1062,199 @@ func (svc *BookService) indexDocumentToES(ctx context.Context, doc map[string]in return nil } +// isOneValue 判断 interface{} 类型的值是否为 1 +func isOneValue(v interface{}) bool { + switch val := v.(type) { + case int: + return val == 1 + case int8: + return val == 1 + case int16: + return val == 1 + case int32: + return val == 1 + case int64: + return val == 1 + case float32: + return val == 1.0 + case float64: + return val == 1.0 + case string: + return val == "1" + default: + return false + } +} + +// isZeroValue 判断 interface{} 类型的值是否为 0 +func isZeroValue(v interface{}) bool { + switch val := v.(type) { + case int: + return val == 0 + case int8: + return val == 0 + case int16: + return val == 0 + case int32: + return val == 0 + case int64: + return val == 0 + case float32: + return val == 0.0 + case float64: + return val == 0.0 + case string: + return val == "0" + default: + return false + } +} + +// searchBookByISBNInIndex 在指定索引中按 ISBN 查询文档 +func (svc *BookService) searchBookByISBNInIndex(isbn string, index string) (*es.ESBook, error) { + log.Printf("[SearchBookByISBNInIndex] 开始查询 | ISBN=%s | index=%s", isbn, index) + + query := map[string]interface{}{ + "query": map[string]interface{}{ + "term": map[string]interface{}{ + "isbn": isbn, + }, + }, + "_source": true, + "size": 1, + } + + body, err := json.Marshal(query) + if err != nil { + log.Printf("[SearchBookByISBNInIndex] 构建查询 JSON 失败:%v", err) + return nil, fmt.Errorf("构建查询 JSON 失败:%v", err) + } + + res, err := svc.esClient.Client.Search( + svc.esClient.Client.Search.WithIndex(index), + svc.esClient.Client.Search.WithBody(bytes.NewReader(body)), + svc.esClient.Client.Search.WithTrackTotalHits(true), + ) + if err != nil { + log.Printf("[SearchBookByISBNInIndex] ES 查询失败:%v", err) + return nil, fmt.Errorf("ES 查询失败:%v", err) + } + defer res.Body.Close() + + if res.IsError() { + log.Printf("[SearchBookByISBNInIndex] ES 返回错误:%s", res.String()) + return nil, fmt.Errorf("ES 返回错误:%s", res.String()) + } + + var parsed esHitsWrapper + + if err := json.NewDecoder(res.Body).Decode(&parsed); err != nil { + log.Printf("[SearchBookByISBNInIndex] 解析 ES 响应失败:%v", err) + return nil, fmt.Errorf("解析 ES 响应失败:%v", err) + } + + if len(parsed.Hits.Hits) == 0 { + log.Printf("[SearchBookByISBNInIndex] 未找到 ISBN=%s 对应文档", isbn) + return nil, nil + } + + book := parsed.Hits.Hits[0].Source + log.Printf("[SearchBookByISBNInIndex] 查询到文档: %+v", book) + + return &book, nil +} + +// indexDocumentToIndex 将文档写入指定索引(幂等:存在则覆盖,不存在则创建) +func (svc *BookService) indexDocumentToIndex(ctx context.Context, doc map[string]interface{}, id string, index string) error { + jsonData, _ := json.Marshal(doc) + + esReq := esapi.IndexRequest{ + Index: index, + DocumentID: id, + Body: bytes.NewReader(jsonData), + Refresh: "true", + } + + res, err := esReq.Do(ctx, svc.esClient.Client.Transport) + if err != nil { + return fmt.Errorf("写入索引 %s 失败:%w", index, err) + } + defer res.Body.Close() + + if res.IsError() { + return fmt.Errorf("写入索引 %s 错误:%s", index, res.String()) + } + + log.Printf("[IndexDocumentToIndex] 成功 | index=%s | id=%s", index, id) + return nil +} + +// syncBookToV3Index 将 v2 索引中的文档同步到 v3 索引 +// 先检查 v3 中是否已存在同 ISBN 文档,存在则覆盖更新,不存在则新增 +func (svc *BookService) syncBookToV3Index(isbn string) error { + // 1. 从 v2 索引查询完整文档 + book, err := svc.SearchBookByISBN(isbn) + if err != nil { + return fmt.Errorf("查询v2索引失败: %w", err) + } + if book == nil { + return fmt.Errorf("v2索引中未找到ISBN=%s的文档", isbn) + } + + // 2. 构建文档,追加 v3 专有字段 + doc := svc.buildBookMapForSerialization(book) + doc["fid"] = 0 + + // 3. 检查 v3 中是否已存在 + existing, err := svc.searchBookByISBNInIndex(isbn, es.ESIndexV3) + if err != nil { + return fmt.Errorf("查询v3索引失败: %w", err) + } + + ctx := context.Background() + if existing != nil { + log.Printf("[SyncToV3] ISBN=%s 在v3中已存在,执行覆盖更新", isbn) + } else { + log.Printf("[SyncToV3] ISBN=%s 在v3中不存在,执行新增", isbn) + } + + // 4. 写入 v3 索引(IndexRequest 是幂等的:存在则覆盖,不存在则创建) + return svc.indexDocumentToIndex(ctx, doc, isbn, es.ESIndexV3) +} + +// deleteBookFromV3Index 从 v3 索引中删除指定 ISBN 的文档 +func (svc *BookService) deleteBookFromV3Index(isbn string) error { + // 先检查 v3 中是否存在 + existing, err := svc.searchBookByISBNInIndex(isbn, es.ESIndexV3) + if err != nil { + return fmt.Errorf("查询v3索引失败: %w", err) + } + if existing == nil { + log.Printf("[DeleteFromV3] ISBN=%s 在v3中不存在,无需删除", isbn) + return nil + } + + ctx := context.Background() + req := esapi.DeleteRequest{ + Index: es.ESIndexV3, + DocumentID: isbn, + Refresh: "true", + } + + res, err := req.Do(ctx, svc.esClient.Client.Transport) + if err != nil { + return fmt.Errorf("从v3索引删除失败: %w", err) + } + defer res.Body.Close() + + if res.IsError() { + return fmt.Errorf("从v3索引删除错误: %s", res.String()) + } + + log.Printf("[DeleteFromV3] 成功删除 ISBN=%s 从v3索引", isbn) + return nil +} + // GetLastID 获取最后一条 ID func (svc *BookService) GetLastID() (int, error) { query := `{