Compare commits

...

2 Commits

Author SHA1 Message Date
93bde0a6b6 Merge branch 'feat/xuanzhi-service' of http://10.1.0.1:3000/xuanzhi/xuanzhi-service into feat/xuanzhi-service
Some checks failed
CI / init (pull_request) Has been cancelled
CI / Frontend node 18.16.0 (pull_request) Has been cancelled
CI / Backend go (1.22) (pull_request) Has been cancelled
CI / release-pr (pull_request) Has been cancelled
CI / devops-test (1.22, 18.16.0) (pull_request) Has been cancelled
CI / release-please (pull_request) Has been cancelled
CI / devops-prod (1.22, 18.x) (pull_request) Has been cancelled
CI / docker (pull_request) Has been cancelled
2026-04-27 10:12:23 +08:00
13db6e89f0 书籍模块 2026-04-27 10:12:21 +08:00
33 changed files with 8820 additions and 738 deletions

View File

@@ -35,6 +35,7 @@
- 新增业务 `admin` 模块时,如无明确例外,先提供这 6 个接口,再叠加业务特有接口。
- 路由统一放在 `router/<module>`,接口统一放在 `api/v1/<module>`,业务统一放在 `service/<module>`,模型统一放在 `model/<module>`
- 新增业务路由后,必须同步在 `initialize/router_biz.go` 注册。
- 新增或修改 `.ai-specs/doc-api/<端>/<resource>.md` 时,必须遵循 `.ai-specs/sys-specs/doc-api-doc-spec.md`
- 单个业务模块包含多个独立资源或多张业务表时,`api/v1/<module>``service/<module>``router/<module>` 必须按资源拆分文件;禁止把多个资源的 CRUD 长期堆在同一个大文件。
- 单个业务模块包含多个独立资源或多张业务表时,`.ai-specs/doc-api/<端>/<resource>.md` 必须按资源拆分文档;禁止把多个资源的接口 contract 长期堆在同一个 doc-api 大文档。
- 每个资源文件只承载该资源的 `API``Service``Router` 方法;跨资源复用逻辑只能放在明确命名的 `common.go``helper.go` 等公共文件,且公共文件禁止承载具体资源 CRUD 主流程。

View File

@@ -1,29 +1,243 @@
# 书籍信息 admin 接口
# 书籍信息 admin 接口
## 基本信息
- 模块book
- 资源:书籍
- 资源:书籍信息
-admin
- 鉴权:挂载 `PrivateGroup`,需要 `JWT + Casbin`
- 操作审计:创建、更新、单删、批量删除写操作启用 `OperationRecord`
- 路由前缀:`/book`
- 鉴权:`PrivateGroup`,需要 `JWT + Casbin`
- 审计:创建、更新、单删、批量删除启用 `OperationRecord`
- 前缀:`/book`
- 实体模型:`book.Book`
- 搜索入参:`bookReq.BookSearch`
- 详情响应:`bookRes.BookResponse`
- 列表项:`book.Book`
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
- 删除策略:硬删
## 默认 CRUD
## CRUD
| 动作 | Method | 路径 | API 方法 | Service 方法 |
|:---|:---|:---|:---|:---|
| 创建 | `POST` | `/book/createBook` | `CreateBook` | `CreateBook` |
| 单删 | `DELETE` | `/book/deleteBook` | `DeleteBook` | `DeleteBook` |
| 批量删除 | `DELETE` | `/book/deleteBookByIds` | `DeleteBookByIds` | `DeleteBookByIds` |
| 更新 | `PUT` | `/book/updateBook` | `UpdateBook` | `UpdateBook` |
| 详情 | `GET` | `/book/findBook` | `FindBook` | `GetBook` |
| 分页列表 | `GET` | `/book/getBookList` | `GetBookList` | `GetBookInfoList` |
### 创建书籍信息
## 参数与返回
- Method`POST`
- Path`/book/createBook`
- Handler`BookApi.CreateBook`
- Service`BookService.CreateBook`
- 审计:是
- 创建、更新:`body` 使用 `book.Book`
- 单删、详情:`query id`
- 批量删除:`query ids[]`
- 分页列表:`query` 使用 `bookReq.BookSearch`,返回 `response.PageResult`
- 详情返回:`bookRes.BookResponse`
#### Request Body `book.Book`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| title | string | 是 | 非空,最大 `255` | 书名主标题 |
| subtitle | string | 否 | 最大 `255` | 书籍副标题 |
| bookType | string | 是 | 非空,值必须存在于系统字典 `book_type` | 书籍类型字典值 |
| eraTag | string | 否 | `book_era_tag``unknown/ancient/han/tang/song/yuan/ming/qing/modern/contemporary`;默认 `unknown` | 时代标签字典值 |
| coverUrl | string | 否 | 最大 `500` | 封面图片 URL |
| publisher | string | 否 | 最大 `128` | 出版社名称 |
| publishedAt | string | 否 | 日期类型 | 出版日期 |
| intro | string | 否 | 文本 | 书籍简介 |
| hotScore | int64 | 否 | `>= 0`;默认 `0` | 热度聚合值 |
| rating | float64 | 否 | `0 <= rating <= 10`;默认 `0.0` | 书籍评分 |
| commentCount | int64 | 否 | `>= 0`;默认 `0` | 点评数聚合值 |
| wordCount | int64 | 否 | `>= 0`;默认 `0` | 书籍总字数 |
| completionStatus | string | 否 | `book_completion_status``completed/serializing`;默认 `serializing` | 书籍完结状态 |
| publishStatus | string | 否 | `book_publish_status``draft/off_shelf/on_shelf`;默认 `draft` | 书籍上下架状态 |
| seriesId | uint | 否 | 可为空;存在时应为有效系列 ID | 所属系列 ID |
| seriesSort | int | 否 | `>= 0`;默认 `0` | 同系列内展示排序 |
| rawTxtUrl | string | 否 | 最大 `500` | 原始 txt 文件 URL |
#### Response
- `response.Response{msg}`
#### 规则
- 创建请求禁止传服务端生成字段:`id/createdAt/updatedAt`
- `bookType` 是动态值域字典,业务侧只校验值存在性,不参与状态流转。
### 删除书籍信息
- Method`DELETE`
- Path`/book/deleteBook`
- Handler`BookApi.DeleteBook`
- Service`BookService.DeleteBook`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 书籍 ID |
#### Response
- `response.Response{msg}`
### 批量删除书籍信息
- Method`DELETE`
- Path`/book/deleteBookByIds`
- Handler`BookApi.DeleteBookByIds`
- Service`BookService.DeleteBookByIds`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| ids | int[] | 二选一 | 非空,元素均 `> 0` | 书籍 ID 数组 |
| ids[] | int[] | 二选一 | 非空,元素均 `> 0` | 书籍 ID 数组,兼容数组写法 |
#### Response
- `response.Response{msg}`
#### 规则
- `ids``ids[]` 二选一。
### 更新书籍信息
- Method`PUT`
- Path`/book/updateBook`
- Handler`BookApi.UpdateBook`
- Service`BookService.UpdateBook`
- 审计:是
#### Request Body `book.Book`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | uint | 是 | `> 0` | 书籍 ID |
| title | string | 是 | 非空,最大 `255` | 书名主标题 |
| subtitle | string | 否 | 最大 `255` | 书籍副标题 |
| bookType | string | 是 | 非空,值必须存在于系统字典 `book_type` | 书籍类型字典值 |
| eraTag | string | 否 | `book_era_tag``unknown/ancient/han/tang/song/yuan/ming/qing/modern/contemporary` | 时代标签字典值 |
| coverUrl | string | 否 | 最大 `500` | 封面图片 URL |
| publisher | string | 否 | 最大 `128` | 出版社名称 |
| publishedAt | string | 否 | 日期类型 | 出版日期 |
| intro | string | 否 | 文本 | 书籍简介 |
| hotScore | int64 | 否 | `>= 0` | 热度聚合值 |
| rating | float64 | 否 | `0 <= rating <= 10` | 书籍评分 |
| commentCount | int64 | 否 | `>= 0` | 点评数聚合值 |
| wordCount | int64 | 否 | `>= 0` | 书籍总字数 |
| completionStatus | string | 否 | `book_completion_status``completed/serializing` | 书籍完结状态 |
| publishStatus | string | 否 | `book_publish_status``draft/off_shelf/on_shelf` | 书籍上下架状态 |
| seriesId | uint | 否 | 可为空;存在时应为有效系列 ID | 所属系列 ID |
| seriesSort | int | 否 | `>= 0` | 同系列内展示排序 |
| rawTxtUrl | string | 否 | 最大 `500` | 原始 txt 文件 URL |
#### Response
- `response.Response{msg}`
#### 规则
- 更新请求禁止传服务端维护字段:`createdAt/updatedAt`
- 更新使用实体 `Save`,前端应传完整可编辑实体,避免字段被零值覆盖。
- `bookType` 是动态值域字典,业务侧只校验值存在性,不参与状态流转。
### 查询书籍信息详情
- Method`GET`
- Path`/book/findBook`
- Handler`BookApi.FindBook`
- Service`BookService.GetBook`
- 审计:否
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 书籍 ID |
#### Response `bookRes.BookResponse`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| book | object | 书籍实体 |
| book.id | uint | 主键 ID |
| book.createdAt | string | 创建时间 |
| book.updatedAt | string | 更新时间 |
| book.title | string | 书名主标题 |
| book.subtitle | string | 书籍副标题 |
| book.bookType | string | 书籍类型字典值,对应 `book_type` |
| book.eraTag | string | 时代标签字典值,对应 `book_era_tag` |
| book.coverUrl | string | 封面图片 URL |
| book.publisher | string | 出版社名称 |
| book.publishedAt | string | 出版日期 |
| book.intro | string | 书籍简介 |
| book.hotScore | int64 | 热度聚合值 |
| book.rating | float64 | 书籍评分,范围 `0-10` |
| book.commentCount | int64 | 点评数聚合值 |
| book.wordCount | int64 | 书籍总字数 |
| book.completionStatus | string | 书籍完结状态字典值,对应 `book_completion_status` |
| book.publishStatus | string | 书籍上下架状态字典值,对应 `book_publish_status` |
| book.seriesId | uint | 所属系列 ID |
| book.seriesSort | int | 同系列内展示排序 |
| book.rawTxtUrl | string | 原始 txt 文件 URL |
### 分页查询书籍信息列表
- Method`GET`
- Path`/book/getBookList`
- Handler`BookApi.GetBookList`
- Service`BookService.GetBookInfoList`
- 审计:否
#### Request Query `bookReq.BookSearch`
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| page | int | 否 | 默认 `1`,最小 `1` | 页码 |
| pageSize | int | 否 | 默认 `10`,最大 `100` | 每页数量 |
| keyword | string | 否 | 模糊匹配 `title``subtitle` | 关键字 |
| title | string | 否 | 模糊匹配 `title` | 书名主标题 |
| bookType | string | 否 | 值必须存在于系统字典 `book_type` | 书籍类型 |
| eraTag | string | 否 | `book_era_tag``unknown/ancient/han/tang/song/yuan/ming/qing/modern/contemporary` | 时代标签 |
| completionStatus | string | 否 | `book_completion_status``completed/serializing` | 完结状态 |
| publishStatus | string | 否 | `book_publish_status``draft/off_shelf/on_shelf` | 上下架状态 |
| seriesId | uint | 否 | `> 0` | 系列 ID |
#### Response `response.PageResult`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| list | array | `book.Book` 列表 |
| list[].id | uint | 主键 ID |
| list[].createdAt | string | 创建时间 |
| list[].updatedAt | string | 更新时间 |
| list[].title | string | 书名主标题 |
| list[].subtitle | string | 书籍副标题 |
| list[].bookType | string | 书籍类型字典值,对应 `book_type` |
| list[].eraTag | string | 时代标签字典值,对应 `book_era_tag` |
| list[].coverUrl | string | 封面图片 URL |
| list[].publisher | string | 出版社名称 |
| list[].publishedAt | string | 出版日期 |
| list[].intro | string | 书籍简介 |
| list[].hotScore | int64 | 热度聚合值 |
| list[].rating | float64 | 书籍评分,范围 `0-10` |
| list[].commentCount | int64 | 点评数聚合值 |
| list[].wordCount | int64 | 书籍总字数 |
| list[].completionStatus | string | 书籍完结状态字典值,对应 `book_completion_status` |
| list[].publishStatus | string | 书籍上下架状态字典值,对应 `book_publish_status` |
| list[].seriesId | uint | 所属系列 ID |
| list[].seriesSort | int | 同系列内展示排序 |
| list[].rawTxtUrl | string | 原始 txt 文件 URL |
| total | int64 | 总数 |
| page | int | 当前页 |
| pageSize | int | 每页数量 |
#### 规则
- 列表默认排序:`id desc`
## 规则
- admin 端书籍信息 CRUD 统一挂载到 `PrivateGroup`,需要 `JWT + Casbin`
- 写操作启用 `OperationRecord`;读操作不启用操作审计。
- 删除策略为硬删,实体使用 `book.HardDeleteModel`,不维护 `deleted_at`
- `eraTag/completionStatus/publishStatus` 必须符合对应固定值域字典。
- `hotScore/commentCount/wordCount/seriesSort` 不能小于 `0``rating` 范围为 `0-10`
- `seriesId` 可为空;非空时表示书籍挂载到对应系列,展示排序使用 `seriesSort`

View File

@@ -1,29 +1,194 @@
# 书籍作者 admin 接口
# 书籍作者 admin 接口
## 基本信息
- 模块book
- 资源:作者
- 资源:书籍作者
-admin
- 鉴权:挂载 `PrivateGroup`,需要 `JWT + Casbin`
- 操作审计:创建、更新、单删、批量删除写操作启用 `OperationRecord`
- 路由前缀:`/book`
- 鉴权:`PrivateGroup`,需要 `JWT + Casbin`
- 审计:创建、更新、单删、批量删除启用 `OperationRecord`
- 前缀:`/book`
- 实体模型:`book.BookAuthor`
- 搜索入参:`bookReq.BookAuthorSearch`
- 详情响应:`bookRes.BookAuthorResponse`
- 列表项:`bookRes.BookAuthorListItem`
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
- 删除策略:硬删
## 默认 CRUD
## CRUD
| 动作 | Method | 路径 | API 方法 | Service 方法 |
|:---|:---|:---|:---|:---|
| 创建 | `POST` | `/book/createBookAuthor` | `CreateBookAuthor` | `CreateBookAuthor` |
| 单删 | `DELETE` | `/book/deleteBookAuthor` | `DeleteBookAuthor` | `DeleteBookAuthor` |
| 批量删除 | `DELETE` | `/book/deleteBookAuthorByIds` | `DeleteBookAuthorByIds` | `DeleteBookAuthorByIds` |
| 更新 | `PUT` | `/book/updateBookAuthor` | `UpdateBookAuthor` | `UpdateBookAuthor` |
| 详情 | `GET` | `/book/findBookAuthor` | `FindBookAuthor` | `GetBookAuthor` |
| 分页列表 | `GET` | `/book/getBookAuthorList` | `GetBookAuthorList` | `GetBookAuthorInfoList` |
### 创建书籍作者
## 参数与返回
- Method`POST`
- Path`/book/createBookAuthor`
- Handler`BookAuthorApi.CreateBookAuthor`
- Service`BookAuthorService.CreateBookAuthor`
- 审计:是
- 创建、更新:`body` 使用 `book.BookAuthor`
- 单删、详情:`query id`
- 批量删除:`query ids[]`
- 分页列表:`query` 使用 `bookReq.BookAuthorSearch`,返回 `response.PageResult`
- 详情返回:`bookRes.BookAuthorResponse`
#### Request Body `book.BookAuthor`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| name | string | 是 | 非空,最大 `128` 字符,唯一 | 作者名称 |
| authorStatus | string | 否 | `common_enabled_status``enabled`/`disabled`;默认 `enabled` | 作者启用状态 |
| intro | string | 否 | 可为空 | 作者简介 |
| coverUrl | string | 否 | 可为空,最大 `500` 字符 | 作者头像或封面 URL |
#### Response
- `response.Response{msg}`
#### 规则
- 创建请求禁止传服务端生成字段:`id/createdAt/updatedAt`
- `name` 必须唯一,对应唯一索引 `uk_book_author_name`
- `authorStatus` 为空时按数据库默认值 `enabled` 落库;传值时必须符合 `common_enabled_status`
### 删除书籍作者
- Method`DELETE`
- Path`/book/deleteBookAuthor`
- Handler`BookAuthorApi.DeleteBookAuthor`
- Service`BookAuthorService.DeleteBookAuthor`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 作者 ID |
#### Response
- `response.Response{msg}`
#### 规则
- 删除策略为硬删。
### 批量删除书籍作者
- Method`DELETE`
- Path`/book/deleteBookAuthorByIds`
- Handler`BookAuthorApi.DeleteBookAuthorByIds`
- Service`BookAuthorService.DeleteBookAuthorByIds`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| ids | int[] | 二选一 | 非空,元素均 `> 0` | 作者 ID 数组 |
| ids[] | int[] | 二选一 | 非空,元素均 `> 0` | 作者 ID 数组,兼容数组写法 |
#### Response
- `response.Response{msg}`
#### 规则
- `ids``ids[]` 二选一。
- 删除策略为硬删。
### 更新书籍作者
- Method`PUT`
- Path`/book/updateBookAuthor`
- Handler`BookAuthorApi.UpdateBookAuthor`
- Service`BookAuthorService.UpdateBookAuthor`
- 审计:是
#### Request Body `book.BookAuthor`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | uint | 是 | `> 0` | 作者 ID |
| name | string | 是 | 非空,最大 `128` 字符,唯一 | 作者名称 |
| authorStatus | string | 否 | `common_enabled_status``enabled`/`disabled` | 作者启用状态 |
| intro | string | 否 | 可为空 | 作者简介 |
| coverUrl | string | 否 | 可为空,最大 `500` 字符 | 作者头像或封面 URL |
#### Response
- `response.Response{msg}`
#### 规则
- 更新请求禁止传服务端维护字段:`createdAt/updatedAt`
- 更新为实体全量保存,前端应传完整可编辑实体,避免字段被零值覆盖。
- `name` 必须唯一,对应唯一索引 `uk_book_author_name`
- `authorStatus` 为空时会按空值保存;传值时必须符合 `common_enabled_status`
### 查询书籍作者详情
- Method`GET`
- Path`/book/findBookAuthor`
- Handler`BookAuthorApi.FindBookAuthor`
- Service`BookAuthorService.GetBookAuthor`
- 审计:否
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 作者 ID |
#### Response `bookRes.BookAuthorResponse`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| bookAuthor | object | 作者实体 |
| bookAuthor.id | uint | 作者 ID |
| bookAuthor.createdAt | time | 创建时间 |
| bookAuthor.updatedAt | time | 更新时间 |
| bookAuthor.name | string | 作者名称 |
| bookAuthor.authorStatus | string | 作者启用状态,见 `common_enabled_status` |
| bookAuthor.intro | string | 作者简介 |
| bookAuthor.coverUrl | string | 作者头像或封面 URL |
### 分页查询书籍作者列表
- Method`GET`
- Path`/book/getBookAuthorList`
- Handler`BookAuthorApi.GetBookAuthorList`
- Service`BookAuthorService.GetBookAuthorInfoList`
- 审计:否
#### Request Query `bookReq.BookAuthorSearch`
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| page | int | 否 | 默认 `1`,最小 `1` | 页码 |
| pageSize | int | 否 | 默认 `10`,最大 `100` | 每页数量 |
| keyword | string | 否 | 模糊匹配 `name` | 通用关键字 |
| name | string | 否 | 模糊匹配 `name` | 作者名称 |
| authorStatus | string | 否 | `common_enabled_status``enabled`/`disabled` | 作者启用状态 |
#### Response `response.PageResult`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| list | array | `bookRes.BookAuthorListItem` 列表 |
| list[].id | uint | 作者 ID |
| list[].createdAt | time | 创建时间 |
| list[].updatedAt | time | 更新时间 |
| list[].name | string | 作者名称 |
| list[].authorStatus | string | 作者启用状态,见 `common_enabled_status` |
| list[].intro | string | 作者简介 |
| list[].coverUrl | string | 作者头像或封面 URL |
| list[].authorName | string | 作者名称展示字段,来源于 `book_author.name AS author_name` |
| total | int64 | 总数 |
| page | int | 当前页 |
| pageSize | int | 每页数量 |
#### 规则
- 列表默认排序:`id desc`
- `keyword``name` 同时传入时,会叠加为两个 `name LIKE` 条件。
## 规则
- admin 端书籍作者 CRUD 挂载到 `PrivateGroup`,需要 `JWT + Casbin`
- 写操作挂载 `middleware.OperationRecord()`;读操作不挂操作审计。
- `authorStatus` 固定值域来自 `common_enabled_status``enabled``disabled`
- 表结构、字段长度、唯一约束和索引以 `.ai-specs/doc-sql/book_author.sql` 为准。

View File

@@ -1,29 +1,180 @@
# 书籍作者关系 admin 接口
# 书籍作者关系 admin 接口
## 基本信息
- 模块book
- 资源:书籍作者关系
-admin
- 鉴权:挂载 `PrivateGroup`,需要 `JWT + Casbin`
- 操作审计:创建、更新、单删、批量删除写操作启用 `OperationRecord`
- 路由前缀:`/book`
- 鉴权:`PrivateGroup`,需要 `JWT + Casbin`
- 审计:创建、更新、单删、批量删除启用 `OperationRecord`
- 前缀:`/book`
- 实体模型:`book.BookAuthorRelation`
- 搜索入参:`bookReq.BookAuthorRelationSearch`
- 详情响应:`bookRes.BookAuthorRelationResponse`
- 列表项:`bookRes.BookAuthorRelationListItem`
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
- 删除策略:硬删
## 默认 CRUD
## CRUD
| 动作 | Method | 路径 | API 方法 | Service 方法 |
|:---|:---|:---|:---|:---|
| 创建 | `POST` | `/book/createBookAuthorRelation` | `CreateBookAuthorRelation` | `CreateBookAuthorRelation` |
| 单删 | `DELETE` | `/book/deleteBookAuthorRelation` | `DeleteBookAuthorRelation` | `DeleteBookAuthorRelation` |
| 批量删除 | `DELETE` | `/book/deleteBookAuthorRelationByIds` | `DeleteBookAuthorRelationByIds` | `DeleteBookAuthorRelationByIds` |
| 更新 | `PUT` | `/book/updateBookAuthorRelation` | `UpdateBookAuthorRelation` | `UpdateBookAuthorRelation` |
| 详情 | `GET` | `/book/findBookAuthorRelation` | `FindBookAuthorRelation` | `GetBookAuthorRelation` |
| 分页列表 | `GET` | `/book/getBookAuthorRelationList` | `GetBookAuthorRelationList` | `GetBookAuthorRelationInfoList` |
### 创建书籍作者关系
## 参数与返回
- Method`POST`
- Path`/book/createBookAuthorRelation`
- Handler`BookAuthorRelationApi.CreateBookAuthorRelation`
- Service`BookAuthorRelationService.CreateBookAuthorRelation`
- 审计:是
- 创建、更新:`body` 使用 `book.BookAuthorRelation`
- 单删、详情:`query id`
- 批量删除:`query ids[]`
- 分页列表:`query` 使用 `bookReq.BookAuthorRelationSearch`,返回 `response.PageResult`
- 详情返回:`bookRes.BookAuthorRelationResponse`
#### Request Body `book.BookAuthorRelation`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| bookId | uint | 是 | `> 0` | 关联书籍 ID |
| authorId | uint | 是 | `> 0` | 关联作者 ID |
| authorSort | int | 否 | 未传或为 `0` 时默认 `1`;最终值必须 `> 0` | 展示顺序 |
#### Response
- `response.Response{msg}`
#### 规则
- 创建请求禁止传 `id/createdAt/updatedAt`
- 创建前必须校验 `bookId + authorId` 未被占用。
### 删除书籍作者关系
- Method`DELETE`
- Path`/book/deleteBookAuthorRelation`
- Handler`BookAuthorRelationApi.DeleteBookAuthorRelation`
- Service`BookAuthorRelationService.DeleteBookAuthorRelation`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 关系 ID |
#### Response
- `response.Response{msg}`
### 批量删除书籍作者关系
- Method`DELETE`
- Path`/book/deleteBookAuthorRelationByIds`
- Handler`BookAuthorRelationApi.DeleteBookAuthorRelationByIds`
- Service`BookAuthorRelationService.DeleteBookAuthorRelationByIds`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| ids | int[] | 二选一 | 非空,元素均 `> 0` | 关系 ID 数组 |
| ids[] | int[] | 二选一 | 非空,元素均 `> 0` | 关系 ID 数组,兼容数组写法 |
#### Response
- `response.Response{msg}`
#### 规则
- `ids``ids[]` 二选一。
### 更新书籍作者关系
- Method`PUT`
- Path`/book/updateBookAuthorRelation`
- Handler`BookAuthorRelationApi.UpdateBookAuthorRelation`
- Service`BookAuthorRelationService.UpdateBookAuthorRelation`
- 审计:是
#### Request Body `book.BookAuthorRelation`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | uint | 是 | `> 0` | 关系 ID |
| bookId | uint | 是 | `> 0` | 关联书籍 ID |
| authorId | uint | 是 | `> 0` | 关联作者 ID |
| authorSort | int | 是 | `> 0` | 展示顺序 |
#### Response
- `response.Response{msg}`
#### 规则
- 更新请求禁止传 `createdAt/updatedAt`
- 更新前必须校验 `bookId + authorId` 未被其他记录占用。
- 更新为实体全量保存,前端应传完整可编辑实体,避免字段被零值覆盖。
### 查询书籍作者关系详情
- Method`GET`
- Path`/book/findBookAuthorRelation`
- Handler`BookAuthorRelationApi.FindBookAuthorRelation`
- Service`BookAuthorRelationService.GetBookAuthorRelation`
- 审计:否
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 关系 ID |
#### Response `bookRes.BookAuthorRelationResponse`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| bookAuthorRelation | object | 关系实体 |
| bookAuthorRelation.id | uint | 关系 ID |
| bookAuthorRelation.bookId | uint | 书籍 ID |
| bookAuthorRelation.authorId | uint | 作者 ID |
| bookAuthorRelation.authorSort | int | 作者展示顺序 |
| bookAuthorRelation.createdAt | string | 创建时间 |
| bookAuthorRelation.updatedAt | string | 更新时间 |
### 分页查询书籍作者关系列表
- Method`GET`
- Path`/book/getBookAuthorRelationList`
- Handler`BookAuthorRelationApi.GetBookAuthorRelationList`
- Service`BookAuthorRelationService.GetBookAuthorRelationInfoList`
- 审计:否
#### Request Query `bookReq.BookAuthorRelationSearch`
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| page | int | 否 | 默认 `1`,最小 `1` | 页码 |
| pageSize | int | 否 | 默认 `10`,最大 `100` | 每页数量 |
| bookId | uint | 否 | `> 0` | 按书籍 ID 精确筛选 |
| authorId | uint | 否 | `> 0` | 按作者 ID 精确筛选 |
#### Response `response.PageResult`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| list | array | `bookRes.BookAuthorRelationListItem` 列表 |
| list[].id | uint | 关系 ID |
| list[].bookId | uint | 书籍 ID |
| list[].authorId | uint | 作者 ID |
| list[].authorSort | int | 作者展示顺序 |
| list[].authorName | string | 作者名称展示字段 |
| total | int64 | 总数 |
| page | int | 当前页 |
| pageSize | int | 每页数量 |
#### 规则
- 列表默认排序:`bookId asc, authorSort asc, id desc`
## 规则
- 默认鉴权为 `PrivateGroup`,需要 `JWT + Casbin`
- `bookId + authorId` 唯一,对应数据库唯一索引 `uk_book_author_relation_book_id_author_id`
- `authorSort` 必须大于 `0`
- 创建时 `authorSort` 未传或为 `0` 时默认 `1`
- 删除策略为硬删。

View File

@@ -1,29 +1,201 @@
# 书籍章节 admin 接口
# 书籍章节 admin 接口
## 基本信息
- 模块book
- 资源:章节
- 资源:书籍章节
-admin
- 鉴权:挂载 `PrivateGroup`,需要 `JWT + Casbin`
- 操作审计:创建、更新、单删、批量删除写操作启用 `OperationRecord`
- 路由前缀:`/book`
- 鉴权:`PrivateGroup`,需要 `JWT + Casbin`
- 审计:创建、更新、单删、批量删除启用 `OperationRecord`
- 前缀:`/book`
- 实体模型:`book.BookChapter`
- 搜索入参:`bookReq.BookChapterSearch`
- 详情响应:`bookRes.BookChapterResponse`
- 列表项:`book.BookChapter`
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
- 删除策略:硬删
## 默认 CRUD
## CRUD
| 动作 | Method | 路径 | API 方法 | Service 方法 |
|:---|:---|:---|:---|:---|
| 创建 | `POST` | `/book/createBookChapter` | `CreateBookChapter` | `CreateBookChapter` |
| 单删 | `DELETE` | `/book/deleteBookChapter` | `DeleteBookChapter` | `DeleteBookChapter` |
| 批量删除 | `DELETE` | `/book/deleteBookChapterByIds` | `DeleteBookChapterByIds` | `DeleteBookChapterByIds` |
| 更新 | `PUT` | `/book/updateBookChapter` | `UpdateBookChapter` | `UpdateBookChapter` |
| 详情 | `GET` | `/book/findBookChapter` | `FindBookChapter` | `GetBookChapter` |
| 分页列表 | `GET` | `/book/getBookChapterList` | `GetBookChapterList` | `GetBookChapterInfoList` |
### 创建书籍章节
## 参数与返回
- Method`POST`
- Path`/book/createBookChapter`
- Handler`BookChapterApi.CreateBookChapter`
- Service`BookChapterService.CreateBookChapter`
- 审计:是
- 创建、更新:`body` 使用 `book.BookChapter`
- 单删、详情:`query id`
- 批量删除:`query ids[]`
- 分页列表:`query` 使用 `bookReq.BookChapterSearch`,返回 `response.PageResult`
- 详情返回:`bookRes.BookChapterResponse`
#### Request Body `book.BookChapter`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| bookId | uint | 是 | `> 0` | 所属书籍 ID |
| title | string | 是 | 非空,最长 `255` | 章节标题 |
| chapterNo | int | 是 | `> 0`;同一 `bookId` 下唯一 | 同书内章节顺序编号 |
| isReadable | bool | 否 | 默认 `false`;为 `true``totalLines > 0` | 是否对 app 端开放阅读 |
| contentFileUrl | string | 是 | 非空,最长 `500` | 章节内容文件 URL |
| totalLines | int | 否 | 默认 `0``>= 0` | 章节正文总行数 |
| isEnabled | bool | 否 | 默认 `true` | 章节是否启用 |
#### Response
- `response.Response{msg}`
#### 规则
- 创建请求禁止传服务端生成字段:`id/createdAt/updatedAt`
- `bookId + chapterNo` 唯一。
- `isReadable=true` 时,`totalLines` 必须大于 `0`
### 删除书籍章节
- Method`DELETE`
- Path`/book/deleteBookChapter`
- Handler`BookChapterApi.DeleteBookChapter`
- Service`BookChapterService.DeleteBookChapter`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 章节 ID |
#### Response
- `response.Response{msg}`
### 批量删除书籍章节
- Method`DELETE`
- Path`/book/deleteBookChapterByIds`
- Handler`BookChapterApi.DeleteBookChapterByIds`
- Service`BookChapterService.DeleteBookChapterByIds`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| ids | int[] | 二选一 | 非空,元素均 `> 0` | 章节 ID 数组 |
| ids[] | int[] | 二选一 | 非空,元素均 `> 0` | 章节 ID 数组,兼容数组写法 |
#### Response
- `response.Response{msg}`
#### 规则
- `ids``ids[]` 二选一。
### 更新书籍章节
- Method`PUT`
- Path`/book/updateBookChapter`
- Handler`BookChapterApi.UpdateBookChapter`
- Service`BookChapterService.UpdateBookChapter`
- 审计:是
#### Request Body `book.BookChapter`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | uint | 是 | `> 0` | 章节 ID |
| bookId | uint | 是 | `> 0` | 所属书籍 ID |
| title | string | 是 | 非空,最长 `255` | 章节标题 |
| chapterNo | int | 是 | `> 0`;同一 `bookId` 下唯一 | 同书内章节顺序编号 |
| isReadable | bool | 否 | 为 `true``totalLines > 0` | 是否对 app 端开放阅读 |
| contentFileUrl | string | 是 | 非空,最长 `500` | 章节内容文件 URL |
| totalLines | int | 否 | `>= 0` | 章节正文总行数 |
| isEnabled | bool | 否 | 布尔值 | 章节是否启用 |
#### Response
- `response.Response{msg}`
#### 规则
- 更新请求禁止传服务端维护字段:`createdAt/updatedAt`
- 更新为实体全量保存,前端应传完整可编辑实体,避免字段被零值覆盖。
- `bookId + chapterNo` 唯一。
- `isReadable=true` 时,`totalLines` 必须大于 `0`
### 查询书籍章节详情
- Method`GET`
- Path`/book/findBookChapter`
- Handler`BookChapterApi.FindBookChapter`
- Service`BookChapterService.GetBookChapter`
- 审计:否
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 章节 ID |
#### Response `bookRes.BookChapterResponse`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| bookChapter | object | 章节实体 |
| bookChapter.id | uint | 章节 ID |
| bookChapter.createdAt | string | 创建时间 |
| bookChapter.updatedAt | string | 更新时间 |
| bookChapter.bookId | uint | 所属书籍 ID |
| bookChapter.title | string | 章节标题 |
| bookChapter.chapterNo | int | 同书内章节顺序编号 |
| bookChapter.isReadable | bool | 是否对 app 端开放阅读 |
| bookChapter.contentFileUrl | string | 章节内容文件 URL |
| bookChapter.totalLines | int | 章节正文总行数 |
| bookChapter.isEnabled | bool | 章节是否启用 |
### 分页查询书籍章节列表
- Method`GET`
- Path`/book/getBookChapterList`
- Handler`BookChapterApi.GetBookChapterList`
- Service`BookChapterService.GetBookChapterInfoList`
- 审计:否
#### Request Query `bookReq.BookChapterSearch`
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| page | int | 否 | 默认 `1`,最小 `1` | 页码 |
| pageSize | int | 否 | 默认 `10`,最大 `100` | 每页数量 |
| keyword | string | 否 | 章节标题模糊查询 | 关键字 |
| bookId | uint | 否 | `> 0` | 所属书籍 ID |
| isReadable | bool | 否 | 布尔值 | 是否对 app 端开放阅读 |
| isEnabled | bool | 否 | 布尔值 | 章节是否启用 |
#### Response `response.PageResult`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| list | array | `book.BookChapter` 列表 |
| list[].id | uint | 章节 ID |
| list[].createdAt | string | 创建时间 |
| list[].updatedAt | string | 更新时间 |
| list[].bookId | uint | 所属书籍 ID |
| list[].title | string | 章节标题 |
| list[].chapterNo | int | 同书内章节顺序编号 |
| list[].isReadable | bool | 是否对 app 端开放阅读 |
| list[].contentFileUrl | string | 章节内容文件 URL |
| list[].totalLines | int | 章节正文总行数 |
| list[].isEnabled | bool | 章节是否启用 |
| total | int64 | 总数 |
| page | int | 当前页 |
| pageSize | int | 每页数量 |
#### 规则
- 列表默认排序:`book_id asc, chapter_no asc`
## 规则
- 章节使用硬删;删除后数据库不保留软删标记。
- `bookId + chapterNo` 是章节唯一约束;新增或更新时不能与同书已有章节冲突。
- `chapterNo` 必须大于 `0`
- `totalLines` 不能小于 `0``isReadable=true``totalLines` 必须大于 `0`
- 创建、更新复用实体 `book.BookChapter`;列表查询复用 `bookReq.BookChapterSearch`;详情返回 `bookRes.BookChapterResponse`

View File

@@ -1,29 +1,199 @@
# 书籍评论 admin 接口
# 书籍评论 admin 接口
## 基本信息
- 模块book
- 资源:评论
- 资源:书籍评论
-admin
- 鉴权:挂载 `PrivateGroup`,需要 `JWT + Casbin`
- 操作审计:创建、更新、单删、批量删除写操作启用 `OperationRecord`
- 路由前缀:`/book`
- 鉴权:`PrivateGroup`,需要 `JWT + Casbin`
- 审计:创建、更新、单删、批量删除启用 `OperationRecord`
- 前缀:`/book`
- 实体模型:`book.BookComment`
- 搜索入参:`bookReq.BookCommentSearch`
- 详情响应:`bookRes.BookCommentResponse`
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
- 删除策略:硬删
## 默认 CRUD
## CRUD
| 动作 | Method | 路径 | API 方法 | Service 方法 |
|:---|:---|:---|:---|:---|
| 创建 | `POST` | `/book/createBookComment` | `CreateBookComment` | `CreateBookComment` |
| 单删 | `DELETE` | `/book/deleteBookComment` | `DeleteBookComment` | `DeleteBookComment` |
| 批量删除 | `DELETE` | `/book/deleteBookCommentByIds` | `DeleteBookCommentByIds` | `DeleteBookCommentByIds` |
| 更新 | `PUT` | `/book/updateBookComment` | `UpdateBookComment` | `UpdateBookComment` |
| 详情 | `GET` | `/book/findBookComment` | `FindBookComment` | `GetBookComment` |
| 分页列表 | `GET` | `/book/getBookCommentList` | `GetBookCommentList` | `GetBookCommentInfoList` |
### 创建书籍评论
## 参数与返回
- Method`POST`
- Path`/book/createBookComment`
- Handler`BookCommentApi.CreateBookComment`
- Service`BookCommentService.CreateBookComment`
- 审计:是
- 创建、更新:`body` 使用 `book.BookComment`
- 单删、详情:`query id`
- 批量删除:`query ids[]`
- 分页列表:`query` 使用 `bookReq.BookCommentSearch`,返回 `response.PageResult`
- 详情返回:`bookRes.BookCommentResponse`
#### Request Body `book.BookComment`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| memberUserId | uint | 是 | 数据库非空 | 评论用户的会员 ID |
| bookId | uint | 是 | 数据库非空 | 所属书籍 ID |
| chapterId | uint | 否 | 默认 `0``>= 0` | 评论目标章节 ID`0` 表示整本书 |
| lineIndex | int | 否 | 默认 `0``>= 0`;整书评论时必须为 `0` | 评论目标文本行下标,`0` 表示整章或整本书 |
| content | string | 是 | 数据库非空 | 评论正文内容 |
| likeCount | int64 | 否 | 默认 `0``>= 0` | 评论点赞聚合值 |
| commentStatus | string | 否 | 默认 `normal`;仅允许 `normal/hidden` | 评论状态字典值,对应 `book_comment_status` |
#### Response
- `response.Response{msg}`
#### 规则
- 创建请求禁止传服务端生成字段:`id/createdAt/updatedAt`
- `chapterId=0` 表示整书评论,此时 `lineIndex` 必须为 `0`
- `chapterId>0` 时允许 `lineIndex>=0`,其中 `lineIndex=0` 表示整章评论。
### 删除书籍评论
- Method`DELETE`
- Path`/book/deleteBookComment`
- Handler`BookCommentApi.DeleteBookComment`
- Service`BookCommentService.DeleteBookComment`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 评论 ID |
#### Response
- `response.Response{msg}`
### 批量删除书籍评论
- Method`DELETE`
- Path`/book/deleteBookCommentByIds`
- Handler`BookCommentApi.DeleteBookCommentByIds`
- Service`BookCommentService.DeleteBookCommentByIds`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| ids | int[] | 二选一 | 非空,元素均为主键 ID | 评论 ID 数组 |
| ids[] | int[] | 二选一 | 非空,元素均为主键 ID | 评论 ID 数组,兼容数组写法 |
#### Response
- `response.Response{msg}`
#### 规则
- `ids``ids[]` 二选一。
### 更新书籍评论
- Method`PUT`
- Path`/book/updateBookComment`
- Handler`BookCommentApi.UpdateBookComment`
- Service`BookCommentService.UpdateBookComment`
- 审计:是
#### Request Body `book.BookComment`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | uint | 是 | `> 0` | 评论 ID |
| memberUserId | uint | 是 | 数据库非空 | 评论用户的会员 ID |
| bookId | uint | 是 | 数据库非空 | 所属书籍 ID |
| chapterId | uint | 否 | 默认 `0``>= 0` | 评论目标章节 ID`0` 表示整本书 |
| lineIndex | int | 否 | 默认 `0``>= 0`;整书评论时必须为 `0` | 评论目标文本行下标,`0` 表示整章或整本书 |
| content | string | 是 | 数据库非空 | 评论正文内容 |
| likeCount | int64 | 否 | 默认 `0``>= 0` | 评论点赞聚合值 |
| commentStatus | string | 否 | 默认 `normal`;仅允许 `normal/hidden` | 评论状态字典值,对应 `book_comment_status` |
#### Response
- `response.Response{msg}`
#### 规则
- 更新请求禁止传服务端维护字段:`createdAt/updatedAt`
- 更新为实体全量保存,前端应传完整可编辑实体,避免字段被零值覆盖。
- `chapterId=0` 表示整书评论,此时 `lineIndex` 必须为 `0`
- `chapterId>0` 时允许 `lineIndex>=0`,其中 `lineIndex=0` 表示整章评论。
### 查询书籍评论详情
- Method`GET`
- Path`/book/findBookComment`
- Handler`BookCommentApi.FindBookComment`
- Service`BookCommentService.GetBookComment`
- 审计:否
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 评论 ID |
#### Response `bookRes.BookCommentResponse`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| bookComment | object | 评论实体 |
| bookComment.id | uint | 评论 ID |
| bookComment.createdAt | time | 创建时间 |
| bookComment.updatedAt | time | 更新时间 |
| bookComment.memberUserId | uint | 评论用户的会员 ID |
| bookComment.bookId | uint | 所属书籍 ID |
| bookComment.chapterId | uint | 评论目标章节 ID`0` 表示整本书 |
| bookComment.lineIndex | int | 评论目标文本行下标,`0` 表示整章或整本书 |
| bookComment.content | string | 评论正文内容 |
| bookComment.likeCount | int64 | 评论点赞聚合值 |
| bookComment.commentStatus | string | 评论状态字典值,对应 `book_comment_status` |
### 分页查询书籍评论列表
- Method`GET`
- Path`/book/getBookCommentList`
- Handler`BookCommentApi.GetBookCommentList`
- Service`BookCommentService.GetBookCommentInfoList`
- 审计:否
#### Request Query `bookReq.BookCommentSearch`
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| page | int | 否 | 默认 `1`,最小 `1` | 页码 |
| pageSize | int | 否 | 默认 `10`,最大 `100` | 每页数量 |
| memberUserId | uint | 否 | 精确匹配 | 评论用户的会员 ID |
| bookId | uint | 否 | 精确匹配 | 所属书籍 ID |
| chapterId | uint | 否 | 精确匹配 | 评论目标章节 ID`0` 表示整本书 |
| commentStatus | string | 否 | 仅允许 `normal/hidden` | 评论状态字典值,对应 `book_comment_status` |
#### Response `response.PageResult`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| list | array | `book.BookComment` 列表 |
| list[].id | uint | 评论 ID |
| list[].createdAt | time | 创建时间 |
| list[].updatedAt | time | 更新时间 |
| list[].memberUserId | uint | 评论用户的会员 ID |
| list[].bookId | uint | 所属书籍 ID |
| list[].chapterId | uint | 评论目标章节 ID`0` 表示整本书 |
| list[].lineIndex | int | 评论目标文本行下标,`0` 表示整章或整本书 |
| list[].content | string | 评论正文内容 |
| list[].likeCount | int64 | 评论点赞聚合值 |
| list[].commentStatus | string | 评论状态字典值,对应 `book_comment_status` |
| total | int64 | 总数 |
| page | int | 当前页 |
| pageSize | int | 每页数量 |
#### 规则
- 列表默认排序:`id desc`
## 规则
- `commentStatus` 必须符合 `book_comment_status``normal` 正常,`hidden` 隐藏。
- `lineIndex/likeCount` 不能小于 `0`
- 整书评论 `chapterId=0``lineIndex` 必须为 `0`
- 删除策略为硬删。

View File

@@ -1,29 +1,180 @@
# 评论点赞记录 admin 接口
# 评论点赞记录 admin 接口
## 基本信息
- 模块book
- 资源:评论点赞记录
-admin
- 鉴权:挂载 `PrivateGroup`,需要 `JWT + Casbin`
- 操作审计:创建、更新、单删、批量删除写操作启用 `OperationRecord`
- 路由前缀:`/book`
- 鉴权:`PrivateGroup`,需要 `JWT + Casbin`
- 审计:创建、更新、单删、批量删除启用 `OperationRecord`
- 前缀:`/book`
- 实体模型:`book.BookCommentLikeRecord`
- 搜索入参:`bookReq.BookCommentLikeRecordSearch`
- 详情响应:`bookRes.BookCommentLikeRecordResponse`
- 列表项:`book.BookCommentLikeRecord`
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
- 删除策略:硬删
## 默认 CRUD
## CRUD
| 动作 | Method | 路径 | API 方法 | Service 方法 |
|:---|:---|:---|:---|:---|
| 创建 | `POST` | `/book/createBookCommentLikeRecord` | `CreateBookCommentLikeRecord` | `CreateBookCommentLikeRecord` |
| 单删 | `DELETE` | `/book/deleteBookCommentLikeRecord` | `DeleteBookCommentLikeRecord` | `DeleteBookCommentLikeRecord` |
| 批量删除 | `DELETE` | `/book/deleteBookCommentLikeRecordByIds` | `DeleteBookCommentLikeRecordByIds` | `DeleteBookCommentLikeRecordByIds` |
| 更新 | `PUT` | `/book/updateBookCommentLikeRecord` | `UpdateBookCommentLikeRecord` | `UpdateBookCommentLikeRecord` |
| 详情 | `GET` | `/book/findBookCommentLikeRecord` | `FindBookCommentLikeRecord` | `GetBookCommentLikeRecord` |
| 分页列表 | `GET` | `/book/getBookCommentLikeRecordList` | `GetBookCommentLikeRecordList` | `GetBookCommentLikeRecordInfoList` |
### 创建评论点赞记录
## 参数与返回
- Method`POST`
- Path`/book/createBookCommentLikeRecord`
- Handler`BookCommentLikeRecordApi.CreateBookCommentLikeRecord`
- Service`BookCommentLikeRecordService.CreateBookCommentLikeRecord`
- 审计:是
- 创建、更新:`body` 使用 `book.BookCommentLikeRecord`
- 单删、详情:`query id`
- 批量删除:`query ids[]`
- 分页列表:`query` 使用 `bookReq.BookCommentLikeRecordSearch`,返回 `response.PageResult`
- 详情返回:`bookRes.BookCommentLikeRecordResponse`
#### Request Body `book.BookCommentLikeRecord`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| commentId | uint | 是 | `> 0`;与 `memberUserId` 组成唯一约束 | 被点赞评论 ID |
| memberUserId | uint | 是 | `> 0`;与 `commentId` 组成唯一约束 | 点赞用户的会员 ID |
| likedAt | string | 否 | RFC3339 时间;不传使用数据库默认 `CURRENT_TIMESTAMP` | 点赞发生时间 |
#### Response
- `response.Response{msg}`
#### 规则
- 创建请求禁止传服务端生成字段:`id/createdAt/updatedAt`
- `commentId + memberUserId` 必须唯一;重复创建应返回失败,不得生成重复有效点赞关系。
### 删除评论点赞记录
- Method`DELETE`
- Path`/book/deleteBookCommentLikeRecord`
- Handler`BookCommentLikeRecordApi.DeleteBookCommentLikeRecord`
- Service`BookCommentLikeRecordService.DeleteBookCommentLikeRecord`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 点赞记录 ID |
#### Response
- `response.Response{msg}`
### 批量删除评论点赞记录
- Method`DELETE`
- Path`/book/deleteBookCommentLikeRecordByIds`
- Handler`BookCommentLikeRecordApi.DeleteBookCommentLikeRecordByIds`
- Service`BookCommentLikeRecordService.DeleteBookCommentLikeRecordByIds`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| ids | int[] | 二选一 | 非空,元素均 `> 0` | 点赞记录 ID 数组 |
| ids[] | int[] | 二选一 | 非空,元素均 `> 0` | 点赞记录 ID 数组,兼容数组写法 |
#### Response
- `response.Response{msg}`
#### 规则
- `ids``ids[]` 二选一。
### 更新评论点赞记录
- Method`PUT`
- Path`/book/updateBookCommentLikeRecord`
- Handler`BookCommentLikeRecordApi.UpdateBookCommentLikeRecord`
- Service`BookCommentLikeRecordService.UpdateBookCommentLikeRecord`
- 审计:是
#### Request Body `book.BookCommentLikeRecord`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | uint | 是 | `> 0` | 点赞记录 ID |
| commentId | uint | 是 | `> 0`;与 `memberUserId` 组成唯一约束 | 被点赞评论 ID |
| memberUserId | uint | 是 | `> 0`;与 `commentId` 组成唯一约束 | 点赞用户的会员 ID |
| likedAt | string | 是 | RFC3339 时间;不能为空 | 点赞发生时间 |
#### Response
- `response.Response{msg}`
#### 规则
- 更新请求禁止传服务端维护字段:`createdAt/updatedAt`
- 更新为实体全量保存,前端必须传完整可编辑实体,避免字段被零值覆盖。
- 更新后的 `commentId + memberUserId` 必须继续满足唯一约束。
### 查询评论点赞记录详情
- Method`GET`
- Path`/book/findBookCommentLikeRecord`
- Handler`BookCommentLikeRecordApi.FindBookCommentLikeRecord`
- Service`BookCommentLikeRecordService.GetBookCommentLikeRecord`
- 审计:否
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 点赞记录 ID |
#### Response `bookRes.BookCommentLikeRecordResponse`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| bookCommentLikeRecord | object | 点赞记录实体 |
| bookCommentLikeRecord.id | uint | 点赞记录 ID |
| bookCommentLikeRecord.createdAt | string | 创建时间 |
| bookCommentLikeRecord.updatedAt | string | 更新时间 |
| bookCommentLikeRecord.commentId | uint | 被点赞评论 ID |
| bookCommentLikeRecord.memberUserId | uint | 点赞用户的会员 ID |
| bookCommentLikeRecord.likedAt | string | 点赞发生时间 |
### 分页查询评论点赞记录列表
- Method`GET`
- Path`/book/getBookCommentLikeRecordList`
- Handler`BookCommentLikeRecordApi.GetBookCommentLikeRecordList`
- Service`BookCommentLikeRecordService.GetBookCommentLikeRecordInfoList`
- 审计:否
#### Request Query `bookReq.BookCommentLikeRecordSearch`
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| page | int | 否 | 默认 `1`,最小 `1` | 页码 |
| pageSize | int | 否 | 默认 `10`,最大 `100` | 每页数量 |
| commentId | uint | 否 | `> 0` | 按被点赞评论 ID 筛选 |
| memberUserId | uint | 否 | `> 0` | 按点赞用户的会员 ID 筛选 |
#### Response `response.PageResult`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| list | array | `book.BookCommentLikeRecord` 列表 |
| list[].id | uint | 点赞记录 ID |
| list[].createdAt | string | 创建时间 |
| list[].updatedAt | string | 更新时间 |
| list[].commentId | uint | 被点赞评论 ID |
| list[].memberUserId | uint | 点赞用户的会员 ID |
| list[].likedAt | string | 点赞发生时间 |
| total | int64 | 总数 |
| page | int | 当前页 |
| pageSize | int | 每页数量 |
#### 规则
- 列表默认排序:`id desc`
## 规则
- 该资源承载用户对评论的有效点赞关系,后台 CRUD 仅维护记录本身。
- `commentId + memberUserId` 是幂等判断边界,必须保持唯一。
- 删除策略为硬删;删除后该点赞关系不再存在。
- 时间字段以接口实际 JSON 序列化格式为准,业务语义为 `liked_at`

View File

@@ -1,29 +1,180 @@
# 书籍收藏记录 admin 接口
# 书籍收藏记录 admin 接口
## 基本信息
- 模块book
- 资源:收藏记录
- 资源:书籍收藏记录
-admin
- 鉴权:挂载 `PrivateGroup`,需要 `JWT + Casbin`
- 操作审计:创建、更新、单删、批量删除写操作启用 `OperationRecord`
- 路由前缀:`/book`
- 鉴权:`PrivateGroup`,需要 `JWT + Casbin`
- 审计:创建、更新、单删、批量删除启用 `OperationRecord`
- 前缀:`/book`
- 实体模型:`book.BookFavoriteRecord`
- 搜索入参:`bookReq.BookFavoriteRecordSearch`
- 详情响应:`bookRes.BookFavoriteRecordResponse`
- 列表项:`book.BookFavoriteRecord`
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
- 删除策略:硬删
## 默认 CRUD
## CRUD
| 动作 | Method | 路径 | API 方法 | Service 方法 |
|:---|:---|:---|:---|:---|
| 创建 | `POST` | `/book/createBookFavoriteRecord` | `CreateBookFavoriteRecord` | `CreateBookFavoriteRecord` |
| 单删 | `DELETE` | `/book/deleteBookFavoriteRecord` | `DeleteBookFavoriteRecord` | `DeleteBookFavoriteRecord` |
| 批量删除 | `DELETE` | `/book/deleteBookFavoriteRecordByIds` | `DeleteBookFavoriteRecordByIds` | `DeleteBookFavoriteRecordByIds` |
| 更新 | `PUT` | `/book/updateBookFavoriteRecord` | `UpdateBookFavoriteRecord` | `UpdateBookFavoriteRecord` |
| 详情 | `GET` | `/book/findBookFavoriteRecord` | `FindBookFavoriteRecord` | `GetBookFavoriteRecord` |
| 分页列表 | `GET` | `/book/getBookFavoriteRecordList` | `GetBookFavoriteRecordList` | `GetBookFavoriteRecordInfoList` |
### 创建书籍收藏记录
## 参数与返回
- Method`POST`
- Path`/book/createBookFavoriteRecord`
- Handler`BookFavoriteRecordApi.CreateBookFavoriteRecord`
- Service`BookFavoriteRecordService.CreateBookFavoriteRecord`
- 审计:是
- 创建、更新:`body` 使用 `book.BookFavoriteRecord`
- 单删、详情:`query id`
- 批量删除:`query ids[]`
- 分页列表:`query` 使用 `bookReq.BookFavoriteRecordSearch`,返回 `response.PageResult`
- 详情返回:`bookRes.BookFavoriteRecordResponse`
#### Request Body `book.BookFavoriteRecord`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| memberUserId | uint | 是 | `> 0`;与 `bookId` 组成唯一约束 | 收藏用户的会员 ID |
| bookId | uint | 是 | `> 0`;与 `memberUserId` 组成唯一约束 | 收藏书籍 ID |
| favoritedAt | string | 否 | RFC3339 时间;不传使用数据库默认 `CURRENT_TIMESTAMP` | 收藏发生时间 |
#### Response
- `response.Response{msg}`
#### 规则
- 创建请求禁止传服务端生成字段:`id/createdAt/updatedAt`
- 同一 `memberUserId + bookId` 只能存在一条收藏记录。
### 删除书籍收藏记录
- Method`DELETE`
- Path`/book/deleteBookFavoriteRecord`
- Handler`BookFavoriteRecordApi.DeleteBookFavoriteRecord`
- Service`BookFavoriteRecordService.DeleteBookFavoriteRecord`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 收藏记录 ID |
#### Response
- `response.Response{msg}`
### 批量删除书籍收藏记录
- Method`DELETE`
- Path`/book/deleteBookFavoriteRecordByIds`
- Handler`BookFavoriteRecordApi.DeleteBookFavoriteRecordByIds`
- Service`BookFavoriteRecordService.DeleteBookFavoriteRecordByIds`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| ids | int[] | 二选一 | 非空,元素均 `> 0` | 收藏记录 ID 数组 |
| ids[] | int[] | 二选一 | 非空,元素均 `> 0` | 收藏记录 ID 数组,兼容数组写法 |
#### Response
- `response.Response{msg}`
#### 规则
- `ids``ids[]` 二选一。
### 更新书籍收藏记录
- Method`PUT`
- Path`/book/updateBookFavoriteRecord`
- Handler`BookFavoriteRecordApi.UpdateBookFavoriteRecord`
- Service`BookFavoriteRecordService.UpdateBookFavoriteRecord`
- 审计:是
#### Request Body `book.BookFavoriteRecord`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | uint | 是 | `> 0` | 收藏记录 ID |
| memberUserId | uint | 是 | `> 0`;与 `bookId` 组成唯一约束 | 收藏用户的会员 ID |
| bookId | uint | 是 | `> 0`;与 `memberUserId` 组成唯一约束 | 收藏书籍 ID |
| favoritedAt | string | 是 | RFC3339 时间 | 收藏发生时间 |
#### Response
- `response.Response{msg}`
#### 规则
- 更新请求禁止传服务端维护字段:`createdAt/updatedAt`
- 更新为实体全量保存,前端应传完整可编辑实体,避免字段被零值覆盖。
- 同一 `memberUserId + bookId` 只能存在一条收藏记录。
### 查询书籍收藏记录详情
- Method`GET`
- Path`/book/findBookFavoriteRecord`
- Handler`BookFavoriteRecordApi.FindBookFavoriteRecord`
- Service`BookFavoriteRecordService.GetBookFavoriteRecord`
- 审计:否
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 收藏记录 ID |
#### Response `bookRes.BookFavoriteRecordResponse`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| bookFavoriteRecord | object | 收藏记录实体 |
| bookFavoriteRecord.id | uint | 收藏记录 ID |
| bookFavoriteRecord.createdAt | string | 创建时间 |
| bookFavoriteRecord.updatedAt | string | 更新时间 |
| bookFavoriteRecord.memberUserId | uint | 收藏用户的会员 ID |
| bookFavoriteRecord.bookId | uint | 收藏书籍 ID |
| bookFavoriteRecord.favoritedAt | string | 收藏发生时间 |
### 分页查询书籍收藏记录列表
- Method`GET`
- Path`/book/getBookFavoriteRecordList`
- Handler`BookFavoriteRecordApi.GetBookFavoriteRecordList`
- Service`BookFavoriteRecordService.GetBookFavoriteRecordInfoList`
- 审计:否
#### Request Query `bookReq.BookFavoriteRecordSearch`
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| page | int | 否 | 默认 `1`,最小 `1` | 页码 |
| pageSize | int | 否 | 默认 `10`,最大 `100` | 每页数量 |
| memberUserId | uint | 否 | `> 0` | 按收藏用户的会员 ID 精确筛选 |
| bookId | uint | 否 | `> 0` | 按收藏书籍 ID 精确筛选 |
#### Response `response.PageResult`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| list | array | `book.BookFavoriteRecord` 列表 |
| list[].id | uint | 收藏记录 ID |
| list[].createdAt | string | 创建时间 |
| list[].updatedAt | string | 更新时间 |
| list[].memberUserId | uint | 收藏用户的会员 ID |
| list[].bookId | uint | 收藏书籍 ID |
| list[].favoritedAt | string | 收藏发生时间 |
| total | int64 | 总数 |
| page | int | 当前页 |
| pageSize | int | 每页数量 |
#### 规则
- 列表默认排序:`id desc`
## 规则
- admin 端书籍收藏记录接口统一挂载到 `PrivateGroup`,需要后台 `JWT + Casbin` 权限。
- 写操作挂载 `middleware.OperationRecord()`;详情和分页列表不挂操作审计。
- `memberUserId + bookId` 唯一约束由数据库索引 `uk_book_favorite_record_member_user_id_book_id` 保证。
- 删除策略为硬删,实体使用 `HardDeleteModel`,不维护 `deletedAt`

View File

@@ -1,29 +1,199 @@
# 书籍阅读记录 admin 接口
# 书籍阅读记录 admin 接口
## 基本信息
- 模块book
- 资源:阅读记录
- 资源:书籍阅读记录
-admin
- 鉴权:挂载 `PrivateGroup`,需要 `JWT + Casbin`
- 操作审计:创建、更新、单删、批量删除写操作启用 `OperationRecord`
- 路由前缀:`/book`
- 鉴权:`PrivateGroup`,需要 `JWT + Casbin`
- 审计:创建、更新、单删、批量删除启用 `OperationRecord`
- 前缀:`/book`
- 实体模型:`book.BookReadRecord`
- 搜索入参:`bookReq.BookReadRecordSearch`
- 详情响应:`bookRes.BookReadRecordResponse`
- 列表项:`book.BookReadRecord`
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
- 删除策略:硬删
## 默认 CRUD
## CRUD
| 动作 | Method | 路径 | API 方法 | Service 方法 |
|:---|:---|:---|:---|:---|
| 创建 | `POST` | `/book/createBookReadRecord` | `CreateBookReadRecord` | `CreateBookReadRecord` |
| 单删 | `DELETE` | `/book/deleteBookReadRecord` | `DeleteBookReadRecord` | `DeleteBookReadRecord` |
| 批量删除 | `DELETE` | `/book/deleteBookReadRecordByIds` | `DeleteBookReadRecordByIds` | `DeleteBookReadRecordByIds` |
| 更新 | `PUT` | `/book/updateBookReadRecord` | `UpdateBookReadRecord` | `UpdateBookReadRecord` |
| 详情 | `GET` | `/book/findBookReadRecord` | `FindBookReadRecord` | `GetBookReadRecord` |
| 分页列表 | `GET` | `/book/getBookReadRecordList` | `GetBookReadRecordList` | `GetBookReadRecordInfoList` |
### 创建书籍阅读记录
## 参数与返回
- Method`POST`
- Path`/book/createBookReadRecord`
- Handler`BookReadRecordApi.CreateBookReadRecord`
- Service`BookReadRecordService.CreateBookReadRecord`
- 审计:是
- 创建、更新:`body` 使用 `book.BookReadRecord`
- 单删、详情:`query id`
- 批量删除:`query ids[]`
- 分页列表:`query` 使用 `bookReq.BookReadRecordSearch`,返回 `response.PageResult`
- 详情返回:`bookRes.BookReadRecordResponse`
#### Request Body `book.BookReadRecord`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| memberUserId | uint | 是 | `> 0` | 阅读用户的会员 ID |
| bookId | uint | 是 | `> 0` | 所属书籍 ID |
| bookTitleSnapshot | string | 是 | 非空,最大 `255` | 阅读书籍标题快照 |
| readProgress | float | 否 | 默认 `0.00`,范围 `0-100` | 阅读进度百分比 |
| chapterId | uint | 是 | `> 0` | 当前续读章节 ID |
| lineIndex | int | 是 | `> 0` | 当前续读文本行下标,正文首行为 `1` |
| lastReadAt | string | 否 | 不传使用数据库默认时间 | 最近一次阅读时间 |
#### Response
- `response.Response{msg}`
#### 规则
- 创建请求禁止传服务端生成字段:`id/createdAt/updatedAt`
- `memberUserId + bookId` 唯一,同一用户同一本书只保留一条最新阅读记录。
- `readProgress/chapterId/lineIndex` 必须通过 Service 校验后写入。
### 删除书籍阅读记录
- Method`DELETE`
- Path`/book/deleteBookReadRecord`
- Handler`BookReadRecordApi.DeleteBookReadRecord`
- Service`BookReadRecordService.DeleteBookReadRecord`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 阅读记录 ID |
#### Response
- `response.Response{msg}`
### 批量删除书籍阅读记录
- Method`DELETE`
- Path`/book/deleteBookReadRecordByIds`
- Handler`BookReadRecordApi.DeleteBookReadRecordByIds`
- Service`BookReadRecordService.DeleteBookReadRecordByIds`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| ids | int[] | 二选一 | 非空,元素均 `> 0` | 阅读记录 ID 数组 |
| ids[] | int[] | 二选一 | 非空,元素均 `> 0` | 阅读记录 ID 数组,兼容数组写法 |
#### Response
- `response.Response{msg}`
#### 规则
- `ids``ids[]` 二选一。
### 更新书籍阅读记录
- Method`PUT`
- Path`/book/updateBookReadRecord`
- Handler`BookReadRecordApi.UpdateBookReadRecord`
- Service`BookReadRecordService.UpdateBookReadRecord`
- 审计:是
#### Request Body `book.BookReadRecord`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | uint | 是 | `> 0` | 阅读记录 ID |
| memberUserId | uint | 是 | `> 0` | 阅读用户的会员 ID |
| bookId | uint | 是 | `> 0` | 所属书籍 ID |
| bookTitleSnapshot | string | 是 | 非空,最大 `255` | 阅读书籍标题快照 |
| readProgress | float | 是 | 范围 `0-100` | 阅读进度百分比 |
| chapterId | uint | 是 | `> 0` | 当前续读章节 ID |
| lineIndex | int | 是 | `> 0` | 当前续读文本行下标,正文首行为 `1` |
| lastReadAt | string | 是 | 有效时间 | 最近一次阅读时间 |
#### Response
- `response.Response{msg}`
#### 规则
- 更新请求禁止传服务端维护字段:`createdAt/updatedAt`
- 更新为实体全量保存,前端应传完整可编辑实体,避免字段被零值覆盖。
- `memberUserId + bookId` 唯一,同一用户同一本书不能更新成重复记录。
- `readProgress/chapterId/lineIndex` 必须通过 Service 校验后写入。
### 查询书籍阅读记录详情
- Method`GET`
- Path`/book/findBookReadRecord`
- Handler`BookReadRecordApi.FindBookReadRecord`
- Service`BookReadRecordService.GetBookReadRecord`
- 审计:否
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 阅读记录 ID |
#### Response `BookReadRecordResponse`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| bookReadRecord | object | 阅读记录实体 |
| bookReadRecord.id | uint | 阅读记录 ID |
| bookReadRecord.createdAt | string | 创建时间 |
| bookReadRecord.updatedAt | string | 更新时间 |
| bookReadRecord.memberUserId | uint | 阅读用户的会员 ID |
| bookReadRecord.bookId | uint | 所属书籍 ID |
| bookReadRecord.bookTitleSnapshot | string | 阅读书籍标题快照 |
| bookReadRecord.readProgress | float | 阅读进度百分比 |
| bookReadRecord.chapterId | uint | 当前续读章节 ID |
| bookReadRecord.lineIndex | int | 当前续读文本行下标,正文首行为 `1` |
| bookReadRecord.lastReadAt | string | 最近一次阅读时间 |
### 分页查询书籍阅读记录列表
- Method`GET`
- Path`/book/getBookReadRecordList`
- Handler`BookReadRecordApi.GetBookReadRecordList`
- Service`BookReadRecordService.GetBookReadRecordInfoList`
- 审计:否
#### Request Query `bookReq.BookReadRecordSearch`
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| page | int | 否 | 默认 `1`,最小 `1` | 页码 |
| pageSize | int | 否 | 默认 `10`,最大 `100` | 每页数量 |
| memberUserId | uint | 否 | `> 0` | 按阅读用户的会员 ID 筛选 |
| bookId | uint | 否 | `> 0` | 按所属书籍 ID 筛选 |
#### Response `response.PageResult`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| list | array | `book.BookReadRecord` 列表 |
| list[].id | uint | 阅读记录 ID |
| list[].createdAt | string | 创建时间 |
| list[].updatedAt | string | 更新时间 |
| list[].memberUserId | uint | 阅读用户的会员 ID |
| list[].bookId | uint | 所属书籍 ID |
| list[].bookTitleSnapshot | string | 阅读书籍标题快照 |
| list[].readProgress | float | 阅读进度百分比 |
| list[].chapterId | uint | 当前续读章节 ID |
| list[].lineIndex | int | 当前续读文本行下标,正文首行为 `1` |
| list[].lastReadAt | string | 最近一次阅读时间 |
| total | int64 | 总数 |
| page | int | 当前页 |
| pageSize | int | 每页数量 |
#### 规则
- 列表默认排序:`last_read_at desc, id desc`
## 规则
- admin 端阅读记录 CRUD 默认挂载到 `PrivateGroup`,统一需要后台 `JWT + Casbin` 权限。
- 写操作启用 `OperationRecord`;读操作不启用操作审计。
- 删除策略为硬删,删除后不保留软删标记。
- `memberUserId + bookId` 是业务唯一键。
- `readProgress` 范围为 `0-100``chapterId` 必须大于 `0``lineIndex` 必须大于 `0`

View File

@@ -1,29 +1,193 @@
# 书籍系列 admin 接口
# 书籍系列 admin 接口
## 基本信息
- 模块book
- 资源:系列
- 资源:书籍系列
-admin
- 鉴权:挂载 `PrivateGroup`,需要 `JWT + Casbin`
- 操作审计:创建、更新、单删、批量删除写操作启用 `OperationRecord`
- 路由前缀:`/book`
- 鉴权:`PrivateGroup`,需要 `JWT + Casbin`
- 审计:创建、更新、单删、批量删除启用 `OperationRecord`
- 前缀:`/book`
- 实体模型:`book.BookSeries`
- 搜索入参:`bookReq.BookSeriesSearch`
- 详情响应:`bookRes.BookSeriesResponse`
- 列表项:`book.BookSeries`
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
- 删除策略:硬删
## 默认 CRUD
## CRUD
| 动作 | Method | 路径 | API 方法 | Service 方法 |
|:---|:---|:---|:---|:---|
| 创建 | `POST` | `/book/createBookSeries` | `CreateBookSeries` | `CreateBookSeries` |
| 单删 | `DELETE` | `/book/deleteBookSeries` | `DeleteBookSeries` | `DeleteBookSeries` |
| 批量删除 | `DELETE` | `/book/deleteBookSeriesByIds` | `DeleteBookSeriesByIds` | `DeleteBookSeriesByIds` |
| 更新 | `PUT` | `/book/updateBookSeries` | `UpdateBookSeries` | `UpdateBookSeries` |
| 详情 | `GET` | `/book/findBookSeries` | `FindBookSeries` | `GetBookSeries` |
| 分页列表 | `GET` | `/book/getBookSeriesList` | `GetBookSeriesList` | `GetBookSeriesInfoList` |
### 创建书籍系列
## 参数与返回
- Method`POST`
- Path`/book/createBookSeries`
- Handler`BookSeriesApi.CreateBookSeries`
- Service`BookSeriesService.CreateBookSeries`
- 审计:是
- 创建、更新:`body` 使用 `book.BookSeries`
- 单删、详情:`query id`
- 批量删除:`query ids[]`
- 分页列表:`query` 使用 `bookReq.BookSeriesSearch`,返回 `response.PageResult`
- 详情返回:`bookRes.BookSeriesResponse`
#### Request Body `book.BookSeries`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| name | string | 是 | 非空,最大长度 `128` | 系列名称 |
| coverUrl | string | 否 | 最大长度 `500` | 系列封面图片 URL |
| intro | string | 否 | 文本 | 系列简介 |
| isEnabled | bool | 否 | 默认 `true` | 系列是否启用 |
#### Response
- `response.Response{msg}`
#### 规则
- 创建请求禁止传服务端生成字段:`id/createdAt/updatedAt`
- `name` 写入 `book_series.name`,数据库要求非空。
- `isEnabled` 未传时按数据库默认值 `true` 落库。
### 删除书籍系列
- Method`DELETE`
- Path`/book/deleteBookSeries`
- Handler`BookSeriesApi.DeleteBookSeries`
- Service`BookSeriesService.DeleteBookSeries`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 系列 ID |
#### Response
- `response.Response{msg}`
#### 规则
- 删除策略为硬删,删除后记录不再保留在 `book_series` 表。
### 批量删除书籍系列
- Method`DELETE`
- Path`/book/deleteBookSeriesByIds`
- Handler`BookSeriesApi.DeleteBookSeriesByIds`
- Service`BookSeriesService.DeleteBookSeriesByIds`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| ids | int[] | 二选一 | 非空,元素均 `> 0` | 系列 ID 数组 |
| ids[] | int[] | 二选一 | 非空,元素均 `> 0` | 系列 ID 数组,兼容数组写法 |
#### Response
- `response.Response{msg}`
#### 规则
- `ids``ids[]` 二选一。
- 删除策略为硬删,删除后记录不再保留在 `book_series` 表。
### 更新书籍系列
- Method`PUT`
- Path`/book/updateBookSeries`
- Handler`BookSeriesApi.UpdateBookSeries`
- Service`BookSeriesService.UpdateBookSeries`
- 审计:是
#### Request Body `book.BookSeries`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | uint | 是 | `> 0` | 系列 ID |
| name | string | 是 | 非空,最大长度 `128` | 系列名称 |
| coverUrl | string | 否 | 最大长度 `500` | 系列封面图片 URL |
| intro | string | 否 | 文本 | 系列简介 |
| isEnabled | bool | 否 | `true/false` | 系列是否启用 |
#### Response
- `response.Response{msg}`
#### 规则
- 更新请求禁止传服务端维护字段:`createdAt/updatedAt`
- 当前 Service 使用实体 `Save`,前端应传完整可编辑实体,避免未传字段被零值覆盖。
- `id` 不能为空;`name` 写入 `book_series.name`,数据库要求非空。
### 查询书籍系列详情
- Method`GET`
- Path`/book/findBookSeries`
- Handler`BookSeriesApi.FindBookSeries`
- Service`BookSeriesService.GetBookSeries`
- 审计:否
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 系列 ID |
#### Response `bookRes.BookSeriesResponse`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| bookSeries | object | 书籍系列实体 |
| bookSeries.id | uint | 系列 ID |
| bookSeries.createdAt | time | 创建时间 |
| bookSeries.updatedAt | time | 更新时间 |
| bookSeries.name | string | 系列名称 |
| bookSeries.coverUrl | string | 系列封面图片 URL |
| bookSeries.intro | string | 系列简介 |
| bookSeries.isEnabled | bool | 系列是否启用 |
### 分页查询书籍系列列表
- Method`GET`
- Path`/book/getBookSeriesList`
- Handler`BookSeriesApi.GetBookSeriesList`
- Service`BookSeriesService.GetBookSeriesInfoList`
- 审计:否
#### Request Query `bookReq.BookSeriesSearch`
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| page | int | 否 | 默认 `1`,最小 `1` | 页码 |
| pageSize | int | 否 | 默认 `10`,最大 `100` | 每页数量 |
| keyword | string | 否 | 模糊匹配 `name` | 关键字 |
| name | string | 否 | 模糊匹配 `name` | 系列名称 |
| isEnabled | bool | 否 | `true/false` | 系列是否启用 |
#### Response `response.PageResult`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| list | array | `book.BookSeries` 列表 |
| list[].id | uint | 系列 ID |
| list[].createdAt | time | 创建时间 |
| list[].updatedAt | time | 更新时间 |
| list[].name | string | 系列名称 |
| list[].coverUrl | string | 系列封面图片 URL |
| list[].intro | string | 系列简介 |
| list[].isEnabled | bool | 系列是否启用 |
| total | int64 | 总数 |
| page | int | 当前页 |
| pageSize | int | 每页数量 |
#### 规则
- 列表默认排序:`id desc`
- `keyword``name` 同时传入时,两个条件按 `AND` 叠加过滤。
## 规则
- admin 端书籍系列 CRUD 挂载在 `PrivateGroup`,统一需要 `JWT + Casbin`
- 写操作挂载 `middleware.OperationRecord()`;读操作不挂操作审计。
- `isEnabled` 当前按实体和 SQL 使用 `bool``true` 表示启用,`false` 表示禁用。
- `book_series.name` 建普通索引,不定义唯一约束;接口不要求系列名称全局唯一。
- `book_series` 为硬删表,当前接口不提供软删除恢复能力。

View File

@@ -5,35 +5,96 @@
- 模块sys_api
- 资源API 管理
-admin
- 鉴权:挂载 `PrivateGroup`,需要 `JWT + Casbin`
- 操作审计:`/api/enterSyncApi` 启用 `OperationRecord`
- 路由前缀:`/api`
- 鉴权:默认 `PrivateGroup`,需要 `JWT + Casbin``/api/freshCasbin``PublicGroup`
- 审计:`getAllApis/getApiList/getApiRoles/freshCasbin` 外,其余接口启用 `OperationRecord`
- 前缀:`/api`
- 模型:`system.SysApi``system.SysIgnoreApi`;搜索:`systemReq.SearchApiParams`;角色绑定:`systemReq.SetApiAuthorities`
- 返回:统一 `response.Response`
## 同步接口
## 接口
| 动作 | Method | 路径 | API 方法 | Service 方法 |
| 动作 | Method | 路径 | 入参 | 返回重点 |
|:---|:---|:---|:---|:---|
| 预览同步差异 | `GET` | `/api/syncApi` | `SyncApi` | `SyncApi` |
| 确认同步路由 | `POST` | `/api/enterSyncApi` | `EnterSyncApi` | `EnterSyncApi` / `SyncApiToDB` |
| 获取 API 关联角色 | `GET` | `/api/getApiRoles` | `GetApiRoles` | `GetAuthoritiesByApi` |
| 覆盖 API 关联角色 | `POST` | `/api/setApiRoles` | `SetApiRoles` | `SetApiAuthorities` |
| 刷新 Casbin 缓存 | `GET` | `/api/freshCasbin` | `FreshCasbin` | `FreshCasbin` |
| 创建 API | `POST` | `/api/createApi` | `system.SysApi` body | 操作结果 |
| 删除 API | `POST` | `/api/deleteApi` | `system.SysApi` body仅需 `ID` | 操作结果 |
| 批量删除 API | `DELETE` | `/api/deleteApisByIds` | `request.IdsReq` body | 操作结果 |
| 更新 API | `POST` | `/api/updateApi` | `system.SysApi` body | 操作结果 |
| API 详情 | `POST` | `/api/getApiById` | `request.GetById` body | `api` |
| API 分页列表 | `POST` | `/api/getApiList` | `systemReq.SearchApiParams` body | `response.PageResult` |
| 全量 API 列表 | `POST` | `/api/getAllApis` | 无 | `apis` |
| API 分组 | `GET` | `/api/getApiGroups` | 无 | `groups/apiGroupMap` |
| 预览同步差异 | `GET` | `/api/syncApi` | 无 | `newApis/deleteApis/ignoreApis` |
| 忽略/取消忽略 API | `POST` | `/api/ignoreApi` | `system.SysIgnoreApi` body | 操作结果 |
| 确认同步路由 | `POST` | `/api/enterSyncApi` | 空 body 或 `systemRes.SysSyncApis` | 操作结果 |
| 获取 API 关联角色 | `GET` | `/api/getApiRoles` | `path/method` query | `uint[]` |
| 覆盖 API 关联角色 | `POST` | `/api/setApiRoles` | `systemReq.SetApiAuthorities` body | 操作结果 |
| 刷新 Casbin 缓存 | `GET` | `/api/freshCasbin` | 无 | 操作结果 |
## 参数与返回
## 请求字段
- `/api/syncApi`:无入参,返回 `newApis``deleteApis``ignoreApis`,只预览差异,不写库。
- `/api/enterSyncApi`:允许空 body 或 `{}`,默认以当前 Gin 路由表 `global.GVA_ROUTERS` 为事实来源同步 `sys_apis`
- `/api/enterSyncApi`:兼容旧请求体 `systemRes.SysSyncApis`,传入 `newApis/deleteApis` 时按传入列表写入或删除。
- `/api/getApiRoles`query 传入 `path``method`,返回该 API 已关联的角色 ID 列表。
- `/api/setApiRoles`body 传入 `path``method``authorityIds`,全量覆盖该 API 的角色权限。
- `/api/freshCasbin`:无入参,刷新 Casbin 缓存;该接口走公开路由但写入 `sys_ignore_apis`,不作为角色权限点维护。
- 返回:统一使用 `response.Response`
### SysApi Body
## 同步规则
| 字段 | 类型 | 必填 | 说明 |
|:---|:---|:---:|:---|
| ID | uint | 更新/删除必填 | API ID来自 `global.GVA_MODEL` 的 JSON 字段 |
| path | string | 创建/更新必填 | API 路径 |
| description | string | 创建/更新必填 | API 中文描述 |
| apiGroup | string | 创建/更新必填 | API 分组 |
| method | string | 创建/更新必填 | HTTP Method |
- 新路由:`sys_apis` 中不存在同一 `path + method` 时自动新增。
- 已有路由:保留原 `description``apiGroup` 等人工维护字段,不自动覆盖。
- 忽略路由:存在于 `sys_ignore_apis``path + method` 不写入 `sys_apis`
- 失效路由:数据库中存在但当前 Gin 路由表不存在时,从 `sys_apis` 删除,并清理对应 Casbin 权限。
- 默认管理员角色 `888` 必须具备 `/api/getApiRoles``/api/setApiRoles` 权限,避免出现“无权分配权限”的系统初始化死锁。
- 公开接口、仅登录接口不应长期作为角色权限点维护;需要通过 `sys_ignore_apis` 排除。
### SearchApiParams Body
| 字段 | 类型 | 必填 | 说明 |
|:---|:---|:---:|:---|
| page/pageSize | int | 是 | 页码/每页数量 |
| path | string | 否 | API 路径筛选 |
| description | string | 否 | API 描述筛选 |
| apiGroup | string | 否 | API 分组筛选 |
| method | string | 否 | HTTP Method 筛选 |
| orderKey | string | 否 | 排序字段 |
| desc | bool | 否 | 是否倒序 |
### SysIgnoreApi Body
| 字段 | 类型 | 必填 | 说明 |
|:---|:---|:---:|:---|
| path | string | 是 | API 路径 |
| method | string | 是 | HTTP Method |
| flag | bool | 是 | `true` 写入忽略;`false` 取消忽略 |
### 其他 Body/Query
| 场景 | 字段 | 类型 | 必填 | 说明 |
|:---|:---|:---|:---:|:---|
| getApiById | id | int | 是 | API ID此处使用 `request.GetById`,字段为小写 `id` |
| deleteApisByIds | ids | int[] | 是 | API ID 数组 |
| enterSyncApi | newApis | system.SysApi[] | 否 | 兼容旧请求体;传入时按列表新增 |
| enterSyncApi | deleteApis | system.SysApi[] | 否 | 兼容旧请求体;传入时按列表删除 |
| getApiRoles | path | string | 是 | API 路径 |
| getApiRoles | method | string | 是 | HTTP Method |
| setApiRoles | path | string | 是 | API 路径 |
| setApiRoles | method | string | 是 | HTTP Method |
| setApiRoles | authorityIds | uint[] | 是 | 全量覆盖后的角色 ID 列表 |
## 响应字段
| 场景 | 字段 | 类型 | 说明 |
|:---|:---|:---|:---|
| getApiById | api | object | `system.SysApi` |
| getApiList | list/total/page/pageSize | mixed | 分页结果 |
| getAllApis | apis | array | `system.SysApi` 列表 |
| getApiGroups | groups | string[] | API 分组列表 |
| getApiGroups | apiGroupMap | object | 路径首段到 API 分组映射 |
| syncApi | newApis/deleteApis/ignoreApis | array | 待新增、待删除、已忽略 API |
| getApiRoles | data | uint[] | 角色 ID 列表 |
## 规则
- `path + method` 唯一;创建重复 API 返回失败。
- `/api/syncApi` 只预览差异,不写库。
- `/api/enterSyncApi` 空 body 时以 `global.GVA_ROUTERS` 为事实来源同步 `sys_apis`
- 同步时新路由按 `path + method` 新增;已有路由保留人工维护的 `description/apiGroup`
- `sys_ignore_apis` 中的 `path + method` 不写入 `sys_apis`
- 当前路由表不存在的旧 API 从 `sys_apis` 删除,并清理对应 Casbin 权限。
- `setApiRoles` 为全量覆盖,成功后刷新 Casbin 缓存。
- 默认管理员角色 `888` 必须具备 `/api/getApiRoles``/api/setApiRoles` 权限。

View File

@@ -1,10 +0,0 @@
# 作者状态
- 模块book
- 字典编码:`book_author_status`
- 字典类型:`固定值域字典`
| Label | Value | Sort | Status | Desc |
|:---|:---|:---|:---|:---|
| 启用 | `enabled` | 10 | true | 作者可正常展示,并可被书籍继续关联 |
| 禁用 | `disabled` | 20 | true | 作者不再对外展示,且不应再被新增书籍关联 |

View File

@@ -0,0 +1,10 @@
# 通用启用状态
- 模块common
- 字典编码:`common_enabled_status`
- 字典类型:`固定值域字典`
| Label | Value | Sort | Status | Desc |
|:---|:---|:---|:---|:---|
| 启用 | `enabled` | 10 | true | 业务对象处于启用状态,可按对应业务规则正常使用 |
| 禁用 | `disabled` | 20 | true | 业务对象处于禁用状态,不应再被新增业务关系使用 |

View File

@@ -7,7 +7,7 @@
-- 模型model/book/book_author.go
-- 迁移接入initialize/gorm_biz.go
-- 删除策略硬删表
-- 状态字典book_author_status
-- 启用状态字典common_enabled_status
-- 职责承载书籍作者主体信息用于作者资料展示书籍作者关联和后台作者管理
CREATE TABLE book_author (
@@ -25,7 +25,7 @@ COMMENT ON COLUMN book_author.id IS '主键';
COMMENT ON COLUMN book_author.created_at IS '创建时间';
COMMENT ON COLUMN book_author.updated_at IS '更新时间';
COMMENT ON COLUMN book_author.name IS '作者名称';
COMMENT ON COLUMN book_author.author_status IS '作者状态字典值对应 book_author_status';
COMMENT ON COLUMN book_author.author_status IS '作者启用状态字典值对应 common_enabled_status';
COMMENT ON COLUMN book_author.intro IS '作者简介';
COMMENT ON COLUMN book_author.cover_url IS '作者封面图片 URL';

View File

@@ -41,7 +41,7 @@
### 书籍作者表
- 名称:作者名称
- 状态:字典 `book_author_status`
- 状态:字典 `common_enabled_status`
- 简介:作者简介
- 封面图片URL作者封面地址

View File

@@ -0,0 +1,240 @@
# doc-api 文档规范
## 适用范围
- 新增、重建、校准 `.ai-specs/doc-api/<端>/<resource>.md` 时必读。
- 目标:即使目标 `doc-api` 为空,也能按本文稳定生成接口 contract 文档。
- `doc-api` 只写端点、`Handler``Service`、鉴权、审计、请求、响应、规则;表结构看 `doc-sql`,值域看 `doc-dict`,代码落点看 `coding-specs`
## 生成输入
按顺序读取,缺失则跳过并从下一项补齐:
1. `AGENTS.md`:确认项目链路、文档索引、鉴权入口。
2. `.ai-specs/coding-specs/module-admin-crud-default.md`:确认 admin 默认 CRUD、命名、Method、Service 映射。
3. `.ai-specs/coding-specs/api-auth-control.md`:确认 `PublicGroup``PrivateGroup``JWT + Casbin` 规则。
4. `.ai-specs/coding-specs/vo-model-request-response.md`确认实体、request、response 落点。
5. 对应 `.ai-specs/doc-sql/<resource>.sql`:确认字段、默认值、唯一约束、索引、删除策略、排序依据。
6. 对应 `.ai-specs/doc-dict/*.md`:确认状态、类型、枚举值域。
7. 源码:`router/<module>``api/v1/<module>``service/<module>``model/<module>``initialize/router_biz.go`
## 文档结构
```md
# <资源中文名> <端> 接口
## 基本信息
- 模块:<module>
- 资源:<资源中文名>
- 端:<admin/app/平台>
- 鉴权:<PrivateGroup/PublicGroup/LoginGroup 描述>
- 审计:<启用 OperationRecord 的端点>
- 前缀:/<abbreviation>
- 实体模型:<model>
- 搜索入参:<Search request可选>
- 详情响应:<Detail response可选>
- 列表项:<ListItem response可选>
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
- 删除策略:<硬删/软删/不支持删除>
## CRUD
<按端点模板展开>
## 规则
- <资源级通用规则>
```
## admin CRUD 模板
默认 admin CRUD 必须按以下 6 个端点展开;有明确例外时,在对应 `doc-api``## 规则` 写明。
### 创建
```md
### 创建<资源>
- Method`POST`
- Path`/<abbreviation>/create<StructName>`
- Handler`<StructName>Api.Create<StructName>`
- Service`<StructName>Service.Create<StructName>`
- 审计:是
#### Request Body `<Entity 或 CreateReq>`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| <field> | <type> | <是/否> | <来自 doc-sql/doc-dict/代码校验> | <字段说明> |
#### Response
- `response.Response{msg}`
#### 规则
- 创建请求禁止传服务端生成字段:`id/createdAt/updatedAt`
- <唯一约束、默认值、引用校验等创建规则>
```
### 单删
```md
### 删除<资源>
- Method`DELETE`
- Path`/<abbreviation>/delete<StructName>`
- Handler`<StructName>Api.Delete<StructName>`
- Service`<StructName>Service.Delete<StructName>`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 主键 ID |
#### Response
- `response.Response{msg}`
```
### 批量删除
```md
### 批量删除<资源>
- Method`DELETE`
- Path`/<abbreviation>/delete<StructName>ByIds`
- Handler`<StructName>Api.Delete<StructName>ByIds`
- Service`<StructName>Service.Delete<StructName>ByIds`
- 审计:是
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| ids | int[] | 二选一 | 非空,元素均 `> 0` | 主键 ID 数组 |
| ids[] | int[] | 二选一 | 非空,元素均 `> 0` | 主键 ID 数组,兼容数组写法 |
#### Response
- `response.Response{msg}`
#### 规则
- `ids``ids[]` 二选一。
```
### 更新
```md
### 更新<资源>
- Method`PUT`
- Path`/<abbreviation>/update<StructName>`
- Handler`<StructName>Api.Update<StructName>`
- Service`<StructName>Service.Update<StructName>`
- 审计:是
#### Request Body `<Entity 或 UpdateReq>`
| 字段 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | uint | 是 | `> 0` | 主键 ID |
| <field> | <type> | <是/否> | <来自 doc-sql/doc-dict/代码校验> | <字段说明> |
#### Response
- `response.Response{msg}`
#### 规则
- 更新请求禁止传服务端维护字段:`createdAt/updatedAt`
- <全量保存/局部更新、唯一约束、引用校验等更新规则>
```
### 详情
```md
### 查询<资源>详情
- Method`GET`
- Path`/<abbreviation>/find<StructName>`
- Handler`<StructName>Api.Find<StructName>`
- Service`<StructName>Service.Get<StructName>`
- 审计:否
#### Request Query
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| id | int | 是 | `> 0` | 主键 ID |
#### Response `<StructName>Response`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| <resource> | object | 详情实体 |
| <resource>.id | uint | 主键 ID |
| <resource>.<field> | <type> | <字段说明> |
```
### 分页列表
```md
### 分页查询<资源>列表
- Method`GET`
- Path`/<abbreviation>/get<StructName>List`
- Handler`<StructName>Api.Get<StructName>List`
- Service`<StructName>Service.Get<StructName>InfoList`
- 审计:否
#### Request Query `<StructName>Search`
| 参数 | 类型 | 必填 | 规则 | 说明 |
|:---|:---|:---:|:---|:---|
| page | int | 否 | 默认 `1`,最小 `1` | 页码 |
| pageSize | int | 否 | 默认 `10`,最大 `100` | 每页数量 |
| <filter> | <type> | 否 | <规则> | <筛选说明> |
#### Response `response.PageResult`
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| list | array | `<ListItem>` 列表 |
| list[].id | uint | 主键 ID |
| list[].<field> | <type> | <字段说明> |
| total | int64 | 总数 |
| page | int | 当前页 |
| pageSize | int | 每页数量 |
#### 规则
- 列表默认排序:`<order by>`
```
## 字段来源
- Body 字段:优先取 `request`;没有独立 `request` 时取实体可编辑字段。
- Query 字段:取 `request/Search``common.GetById``common.IdsReq`
- Detail 响应:取 `response` 包装结构和实体 JSON 字段。
- List 响应:取 `ListItem``Select/Scan` 字段、额外展示字段。
- 字段规则:取 `doc-sql` 非空/默认值/唯一索引/check叠加 `doc-dict` 值域和 `Service` 校验。
- 字段名:使用实际 `json/form` 名称,禁止使用 Go 字段名替代。
- 端点鉴权:默认继承 `## 基本信息`;只有端点鉴权不同才在端点内单独写。
- 写操作响应:统一写 `response.Response{msg}`,不展开 `code/msg` 表格。
- 请求/响应汇总:默认不写 `## 请求字段``## 响应字段`;字段以端点内 `Request/Response` 为准。
## 硬性要求
- `## CRUD` 必须展开端点,禁止只写 CRUD 总表。
- 每个端点必须有自己的 `Request``Response`;无请求参数时保留 `Request` 小节并写 `- 无。`
- 端点特有规则按需写在端点下;通用规则写到文末 `## 规则`
- 请求字段表列固定为:字段/参数、类型、必填、规则、说明。
- 详情/列表响应字段表列固定为:字段、类型、说明;写操作响应简写为 `response.Response{msg}`
- 关键响应字段必须展开,禁止写 `mixed`
- `ids``ids[]` 必须写清二选一。
- 文档规则要求业务校验时,代码必须同步落到 `API``Service`

View File

@@ -16,5 +16,6 @@
| 中文 | 英文 | 说明 |
|:---|:---|:---|
| 通用 | common | 跨业务复用的通用值域与模型 |
| 书籍 | book | 书籍与章节相关业务模块 |
| 设备 | device | 设备管理模块 |

View File

@@ -28,9 +28,17 @@ WHERE NOT EXISTS (
-- book 业务字典初始化补齐 / sys_dictionaries, sys_dictionary_details / 2026-04-26
DELETE FROM sys_dictionary_details d
USING sys_dictionaries dict
WHERE d.sys_dictionary_id = dict.id
AND dict.type = 'book_author_status';
DELETE FROM sys_dictionaries
WHERE type = 'book_author_status';
WITH dict_seed(name, type, status, description) AS (
VALUES
('作者状态', 'book_author_status', true, '作者状态字典'),
('通用启用状态', 'common_enabled_status', true, '通用启用禁用状态字典'),
('书籍评论状态', 'book_comment_status', true, '书籍评论状态字典'),
('书籍完结状态', 'book_completion_status', true, '书籍完结状态字典'),
('书籍时代标签', 'book_era_tag', true, '书籍时代标签字典'),
@@ -48,7 +56,7 @@ WHERE d.type = s.type;
WITH dict_seed(name, type, status, description) AS (
VALUES
('作者状态', 'book_author_status', true, '作者状态字典'),
('通用启用状态', 'common_enabled_status', true, '通用启用禁用状态字典'),
('书籍评论状态', 'book_comment_status', true, '书籍评论状态字典'),
('书籍完结状态', 'book_completion_status', true, '书籍完结状态字典'),
('书籍时代标签', 'book_era_tag', true, '书籍时代标签字典'),
@@ -64,8 +72,8 @@ WHERE NOT EXISTS (
WITH detail_seed(dict_type, label, value, sort, status) AS (
VALUES
('book_author_status', '启用', 'enabled', 10, true),
('book_author_status', '禁用', 'disabled', 20, true),
('common_enabled_status', '启用', 'enabled', 10, true),
('common_enabled_status', '禁用', 'disabled', 20, true),
('book_comment_status', '正常', 'normal', 10, true),
('book_comment_status', '隐藏', 'hidden', 20, true),
('book_completion_status', '完结', 'completed', 10, true),
@@ -100,8 +108,8 @@ WHERE d.sys_dictionary_id = dict.id
WITH detail_seed(dict_type, label, value, sort, status) AS (
VALUES
('book_author_status', '启用', 'enabled', 10, true),
('book_author_status', '禁用', 'disabled', 20, true),
('common_enabled_status', '启用', 'enabled', 10, true),
('common_enabled_status', '禁用', 'disabled', 20, true),
('book_comment_status', '正常', 'normal', 10, true),
('book_comment_status', '隐藏', 'hidden', 20, true),
('book_completion_status', '完结', 'completed', 10, true),

View File

@@ -1,10 +1,10 @@
# AI 开发入口 [!IMPORTANT]
# AI 开发入口 [!IMPORTANT]
- **本文档要求**:本文档为项目级别规范和重要导航,必须严格参考
- **本文档要求**本文档只允许在已有的结构上CURD,不允许增加其他标题区
- **本文档要求**.ai-specs 目录下新增/删除任何文档的时候都应该 在本文档中修改 `## 项目文档`
- **文档要求**:规范型文档是给 AI 的顶层入口文档,不是“解释得更全”就更好,而是要 更短、更硬、更可判定
- **文档要求**:如果我的`需求/要求``规范文档`冲突,你应该及时提醒我,让我决策是修正`需求/规范文档`
- **文档要求**:规范型文档是给 AI 的顶层入口文档,不是“解释得更全”就更好,而是要 更短、更硬、更可判定,推荐文档内容以“硬规则 + 模板”为主
- **文档要求**:如果我的`需求/要求``规范文档`冲突,你应该及时提醒我,让我决策是修正`需求/规范文档`
- **代码优化**:先复用再新增,允许抽公共逻辑,但公共逻辑必须保证边界仍清晰。
- **代码优化**优化代码时必须同时考虑冗余、孤岛代码、代码清晰度、复杂度、边界条件和兼容性不能只追求功能跑通。不要修改ui/ux 视觉效果,除非明确要求。
- **代码默认遵循**:业务流程需要遵循 `主流做法` `工业级正规`
@@ -12,7 +12,7 @@
## 工具使用规则
- **搜索范围限制**Grep/Glob 严禁全盘搜索,绝对禁止扫描 `.gitignore` 忽略的目录,以避免性能卡顿。
- **搜索范围限制**Grep/Glob 严禁全盘搜索,绝对禁止扫描 `server\docs``.worktrees``web\node_modules``web\dist``server\log``server\uploads``rm_file` 等大目录,以避免性能卡顿。
- **读写**:所有文件读取/写入统一使用 UTF-8建议无 BOM
- **读写**PowerShell/脚本读取项目文件必须显式指定 `-Encoding utf8`
- **画图**:优先使用 `Mermaid 图` 不能混入非 Mermaid 语法文本。
@@ -134,6 +134,7 @@ flowchart LR
| `.ai-specs\sys-specs\business-table-spec.md` | 规定新增业务表的 SQL 设计、索引、约束、迁移和兼容要求 | 涉及新增/修改业务表、字段、索引、唯一约束、迁移注册时必读 |
| `.ai-specs\sys-specs\business-dictionary-spec.md` | 规定新增业务字典的定义方式,以及代码枚举与字典值的一一对应关系 | 涉及新增业务状态、类型、级别、来源、模式、分类等值域时必读 |
| `.ai-specs\sys-specs\module-naming-spec.md` | 规定业务模块中文名与英文名的统一登记方式 | 涉及新增/修改业务模块命名、中英文对照、目录命名时必读 |
| `.ai-specs\sys-specs\doc-api-doc-spec.md` | 规定 `doc-api` 从空文档生成接口 contract 的输入源、admin CRUD 模板、字段来源和硬性要求 | 涉及新增/修改 `.ai-specs\doc-api` 接口文档时必读 |
| `.ai-specs\sys-specs\database-upgrade-doc-spec.md` | 规定 `.ai-transition\database-upgrade-doc` 数据库升级 SQL 的版本定位、写入时机和兼容 SQL 要求 | 涉及修改 `doc-sql` 并产生数据库结构变更、维护 `v1.sql`/`v2.sql` 等升级 SQL 时必读 |
### doc-api <admin or app or `平台`>
@@ -169,7 +170,7 @@ flowchart LR
| 路径 | 用途 | 说明 |
|:---|:---|:---|
| `.ai-specs\doc-dict\book_author_status.md` | 定义作者状态的标准值域 | 涉及作者状态的存储、校验、展示和接口出入参时必读 |
| `.ai-specs\doc-dict\common_enabled_status.md` | 定义通用启用/禁用状态的标准值域 | 涉及业务对象启用禁用状态的存储、校验、展示和接口出入参时必读 |
| `.ai-specs\doc-dict\book_comment_status.md` | 定义书籍评论状态的标准值域 | 涉及评论状态存储、评论隐藏和评论出入参展示时必读 |
| `.ai-specs\doc-dict\book_completion_status.md` | 定义书籍完结状态的标准值域 | 涉及书籍完结状态的存储、校验、展示和接口出入参时必读 |
| `.ai-specs\doc-dict\book_era_tag.md` | 定义书籍时代标签的标准值域 | 涉及时代标签存储、筛选聚合和接口出入参时必读 |

View File

@@ -5,9 +5,10 @@ go mod tidy
go run main.go
# 更新api文档
swag init
go install github.com/swaggo/swag/cmd/swag@latest
swag init

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -18,8 +18,6 @@ const (
BookPublishStatusDraft = "draft"
BookPublishStatusOffShelf = "off_shelf"
BookPublishStatusOnShelf = "on_shelf"
BookAuthorStatusEnabled = "enabled"
BookAuthorStatusDisabled = "disabled"
BookCommentStatusNormal = "normal"
BookCommentStatusHidden = "hidden"
)

View File

@@ -3,7 +3,7 @@ package book
type BookAuthor struct {
HardDeleteModel
Name string `json:"name" form:"name" gorm:"type:varchar(128);not null;uniqueIndex:uk_book_author_name;comment:作者名称"`
AuthorStatus string `json:"authorStatus" form:"authorStatus" gorm:"type:varchar(32);not null;default:enabled;index;comment:作者状态字典值,对应 book_author_status"`
AuthorStatus string `json:"authorStatus" form:"authorStatus" gorm:"type:varchar(32);not null;default:enabled;index;comment:作者启用状态字典值,对应 common_enabled_status"`
Intro string `json:"intro" form:"intro" gorm:"type:text;comment:作者简介"`
CoverUrl string `json:"coverUrl" form:"coverUrl" gorm:"type:varchar(500);comment:作者头像或封面 URL"`
}

View File

@@ -8,9 +8,17 @@ type BookResponse struct {
type BookAuthorResponse struct {
BookAuthor book.BookAuthor `json:"bookAuthor"`
}
type BookAuthorListItem struct {
book.BookAuthor `gorm:"embedded"`
AuthorName string `json:"authorName" gorm:"column:author_name"`
}
type BookAuthorRelationResponse struct {
BookAuthorRelation book.BookAuthorRelation `json:"bookAuthorRelation"`
}
type BookAuthorRelationListItem struct {
book.BookAuthorRelation `gorm:"embedded"`
AuthorName string `json:"authorName" gorm:"column:author_name"`
}
type BookChapterResponse struct {
BookChapter book.BookChapter `json:"bookChapter"`
}

View File

@@ -0,0 +1,6 @@
package common
const (
CommonEnabledStatusEnabled = "enabled"
CommonEnabledStatusDisabled = "disabled"
)

View File

@@ -4,6 +4,7 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
bookReq "github.com/flipped-aurora/gin-vue-admin/server/model/book/request"
bookRes "github.com/flipped-aurora/gin-vue-admin/server/model/book/response"
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
)
@@ -36,7 +37,7 @@ func (s *BookAuthorService) GetBookAuthor(id uint) (item book.BookAuthor, err er
return
}
func (s *BookAuthorService) GetBookAuthorInfoList(info bookReq.BookAuthorSearch) (list []book.BookAuthor, total int64, err error) {
func (s *BookAuthorService) GetBookAuthorInfoList(info bookReq.BookAuthorSearch) (list []bookRes.BookAuthorListItem, total int64, err error) {
db := global.GVA_DB.Model(&book.BookAuthor{})
if info.Name != "" {
db = db.Where("name LIKE ?", "%"+info.Name+"%")
@@ -51,6 +52,6 @@ func (s *BookAuthorService) GetBookAuthorInfoList(info bookReq.BookAuthorSearch)
if err != nil {
return
}
err = db.Scopes(paginate(info.PageInfo)).Order("id desc").Find(&list).Error
err = db.Select("book_author.*, book_author.name AS author_name").Scopes(paginate(info.PageInfo)).Order("id desc").Scan(&list).Error
return
}

View File

@@ -0,0 +1,70 @@
package book
import (
"testing"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
bookReq "github.com/flipped-aurora/gin-vue-admin/server/model/book/request"
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
)
func setupBookAuthorListTestDB(t *testing.T) {
t.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(&book.BookAuthor{}, &book.BookAuthorRelation{}))
global.GVA_DB = db
t.Cleanup(func() {
global.GVA_DB = nil
})
}
func TestBookAuthorService_GetBookAuthorInfoListReturnsAuthorName(t *testing.T) {
setupBookAuthorListTestDB(t)
require.NoError(t, global.GVA_DB.Create(&book.BookAuthor{
Name: "鲁迅",
AuthorStatus: "enabled",
}).Error)
list, total, err := (&BookAuthorService{}).GetBookAuthorInfoList(bookReq.BookAuthorSearch{
PageInfo: commonReq.PageInfo{Page: 1, PageSize: 10},
})
require.NoError(t, err)
require.EqualValues(t, 1, total)
require.Len(t, list, 1)
require.Equal(t, "鲁迅", list[0].Name)
require.Equal(t, "鲁迅", list[0].AuthorName)
}
func TestBookAuthorRelationService_GetBookAuthorRelationInfoListReturnsAuthorName(t *testing.T) {
setupBookAuthorListTestDB(t)
author := book.BookAuthor{Name: "沈从文", AuthorStatus: "enabled"}
require.NoError(t, global.GVA_DB.Create(&author).Error)
require.NoError(t, global.GVA_DB.Create(&book.BookAuthorRelation{
BookID: 11,
AuthorID: author.ID,
AuthorSort: 1,
}).Error)
list, total, err := (&BookAuthorRelationService{}).GetBookAuthorRelationInfoList(bookReq.BookAuthorRelationSearch{
PageInfo: commonReq.PageInfo{Page: 1, PageSize: 10},
BookID: uintPtr(11),
})
require.NoError(t, err)
require.EqualValues(t, 1, total)
require.Len(t, list, 1)
require.Equal(t, author.ID, list[0].AuthorID)
require.Equal(t, "沈从文", list[0].AuthorName)
}
func uintPtr(v uint) *uint {
return &v
}

View File

@@ -4,6 +4,7 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
bookReq "github.com/flipped-aurora/gin-vue-admin/server/model/book/request"
bookRes "github.com/flipped-aurora/gin-vue-admin/server/model/book/response"
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
)
@@ -37,18 +38,22 @@ func (s *BookAuthorRelationService) GetBookAuthorRelation(id uint) (item book.Bo
return
}
func (s *BookAuthorRelationService) GetBookAuthorRelationInfoList(info bookReq.BookAuthorRelationSearch) (list []book.BookAuthorRelation, total int64, err error) {
db := global.GVA_DB.Model(&book.BookAuthorRelation{})
func (s *BookAuthorRelationService) GetBookAuthorRelationInfoList(info bookReq.BookAuthorRelationSearch) (list []bookRes.BookAuthorRelationListItem, total int64, err error) {
db := global.GVA_DB.Table("book_author_relation AS bar")
if info.BookID != nil {
db = db.Where("book_id = ?", *info.BookID)
db = db.Where("bar.book_id = ?", *info.BookID)
}
if info.AuthorID != nil {
db = db.Where("author_id = ?", *info.AuthorID)
db = db.Where("bar.author_id = ?", *info.AuthorID)
}
err = db.Count(&total).Error
if err != nil {
return
}
err = db.Scopes(paginate(info.PageInfo)).Order("book_id asc, author_sort asc, id desc").Find(&list).Error
err = db.Select("bar.*, ba.name AS author_name").
Joins("LEFT JOIN book_author AS ba ON ba.id = bar.author_id").
Scopes(paginate(info.PageInfo)).
Order("bar.book_id asc, bar.author_sort asc, bar.id desc").
Scan(&list).Error
return
}

View File

@@ -4,6 +4,7 @@ import (
"errors"
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
commonModel "github.com/flipped-aurora/gin-vue-admin/server/model/common"
)
func validateBook(item book.Book) error {
@@ -59,8 +60,8 @@ var validBookPublishStatuses = map[string]bool{
}
var validBookAuthorStatuses = map[string]bool{
book.BookAuthorStatusEnabled: true,
book.BookAuthorStatusDisabled: true,
commonModel.CommonEnabledStatusEnabled: true,
commonModel.CommonEnabledStatusDisabled: true,
}
var validBookCommentStatuses = map[string]bool{

View File

@@ -5,6 +5,7 @@ import (
"testing"
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
commonModel "github.com/flipped-aurora/gin-vue-admin/server/model/common"
)
func TestValidateBookRejectsOutOfRangeAggregates(t *testing.T) {
@@ -51,6 +52,15 @@ func TestValidateBookAuthorRejectsInvalidStatus(t *testing.T) {
assertValidationErrorContains(t, err, "authorStatus")
}
func TestValidateBookAuthorAcceptsCommonEnabledStatus(t *testing.T) {
if err := validateBookAuthor(book.BookAuthor{AuthorStatus: commonModel.CommonEnabledStatusEnabled}); err != nil {
t.Fatalf("validateBookAuthor enabled error = %v", err)
}
if err := validateBookAuthor(book.BookAuthor{AuthorStatus: commonModel.CommonEnabledStatusDisabled}); err != nil {
t.Fatalf("validateBookAuthor disabled error = %v", err)
}
}
func TestApplyBookAuthorRelationDefaults(t *testing.T) {
item := applyBookAuthorRelationDefaults(book.BookAuthorRelation{})
if item.AuthorSort != 1 {

View File

@@ -50,7 +50,7 @@ func (i *initDict) InitializeData(ctx context.Context) (next context.Context, er
{Name: "数据库浮点型", Type: "float64", Status: &True, Desc: "数据库浮点型"},
{Name: "数据库字符串", Type: "string", Status: &True, Desc: "数据库字符串"},
{Name: "数据库bool类型", Type: "bool", Status: &True, Desc: "数据库bool类型"},
{Name: "作者状态", Type: "book_author_status", Status: &True, Desc: "作者状态字典"},
{Name: "通用启用状态", Type: "common_enabled_status", Status: &True, Desc: "通用启用禁用状态字典"},
{Name: "书籍评论状态", Type: "book_comment_status", Status: &True, Desc: "书籍评论状态字典"},
{Name: "书籍完结状态", Type: "book_completion_status", Status: &True, Desc: "书籍完结状态字典"},
{Name: "书籍时代标签", Type: "book_era_tag", Status: &True, Desc: "书籍时代标签字典"},