454 lines
17 KiB
Markdown
454 lines
17 KiB
Markdown
# API-10 搜索·通知·举报·反馈(SEARCH + NOTI + REPORT + FEEDBACK + HISTORY)
|
||
|
||
> 共 10 个接口:SEARCH-01~02 + NOTI-01~03 + REPORT-01~02 + FEEDBACK-01 + HISTORY-01~02
|
||
>
|
||
> **注:** 搜索历史由**客户端本地存储**,后端不提供读写接口(原 SEARCH-03/04 已下线)。
|
||
|
||
---
|
||
|
||
## 搜索系统(search)
|
||
|
||
#### SEARCH-01 综合搜索
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | GET /client/api/v1/search |
|
||
| 接口用途 | 按关键词搜索用户 / 帖子 / 标签(对应搜索页 4 个 tab) |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | user, user_profile, post, post_media, tag, user_follow, search_trending |
|
||
|
||
**接口逻辑:**
|
||
1. 接收搜索关键词(前端 300ms 防抖),后端会 trim 前后空格;校验 `type` 合法后,将关键词记录到服务内存聚合器,后台默认每 2 分钟批量写入 `search_trending` 并累加 `search_count`;若待写入搜索 hit 达到 1000 次或不同关键词达到 1000 个,会提前异步 flush(统计写入失败或瞬时超出 4096 个不同关键词聚合上限不影响搜索结果返回)
|
||
2. 按 `type` 分流:
|
||
- `type=users`:在 `user`(普通用户 + 创作者)+ `user_profile` 中模糊匹配 `username / display_name / user_no`;分页,仅返回 active 且未删除用户
|
||
- `type=posts`:在 `post`(status=published, deleted_at IS NULL)中模糊匹配 `title`;排序:匹配度 > 点赞数 > 发布时间;关联 `post_media` 取封面/时长、`user` 取创作者;分页
|
||
- `type=tags`:在 `tag` 中模糊匹配 `name`,附带 `post_count` 冗余字段;按 `post_count` 倒序;分页
|
||
- `type=all`(默认):并行执行上述三段,每段固定 limit(users=3, posts=10, tags=10),忽略 `page/pageSize`
|
||
3. 用户段返回 `isCreator`;创作者用户会补充当前用户视角的 `isFollowing`、`followerCount`、`postCount`,普通用户这些创作者指标返回零值 / false
|
||
4. **搜索历史不写后端**,由客户端本地保存;后端只异步沉淀聚合后的搜索词和次数,不提供个人搜索历史读写
|
||
|
||
**请求参数(Query):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| keyword | string | 是 | 搜索关键词 |
|
||
| type | string | 否 | `all`(默认)/ `users` / `posts` / `tags` |
|
||
| page | number | 否 | 页码,默认 1(`type=all` 时忽略) |
|
||
| pageSize | number | 否 | 每页条数,默认 20(`type=all` 时忽略) |
|
||
|
||
**响应数据:**
|
||
|
||
顶层结构按 `type` 决定返回哪些段;`type=all` 时三段齐全,其他 type 仅返回对应段。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| users | object/null | 用户段(`type=users` 或 `all` 时存在) |
|
||
| users.list | array | 用户列表,包含普通用户和创作者 |
|
||
| users.list[].userId | string | 用户 ID |
|
||
| users.list[].userNo | string | 9 位用户编号 |
|
||
| users.list[].nickname | string | 昵称 |
|
||
| users.list[].avatar | url | 头像 |
|
||
| users.list[].description | string | 简介;创作者优先返回创作者简介,普通用户返回用户简介 |
|
||
| users.list[].isCreator | boolean | 是否创作者 |
|
||
| users.list[].followerCount | number | 粉丝数;普通用户为 0 |
|
||
| users.list[].postCount | number | 内容数;普通用户为 0 |
|
||
| users.list[].isFollowing | boolean | 当前用户是否已关注;普通用户为 false |
|
||
| users.pagination | object | 分页信息 |
|
||
| posts | object/null | 帖子段(`type=posts` 或 `all` 时存在) |
|
||
| posts.list | array | 帖子列表,字段与 CONTENT-02 列表项一致 |
|
||
| posts.list[].postId | string | 帖子 ID |
|
||
| posts.list[].title | string | 标题 |
|
||
| posts.list[].type | string | `post / video` |
|
||
| posts.list[].coverUrl | url | 封面图 |
|
||
| posts.list[].blurCoverUrl | url | 模糊封面(锁定态使用) |
|
||
| posts.list[].duration | number | 时长(秒,视频类) |
|
||
| posts.list[].isLocked | boolean | 是否锁定(需订阅/付费) |
|
||
| posts.list[].visibility | number | 0=免费 1~4=付费档位 |
|
||
| posts.list[].visibilityTierName | string | 档位名称 |
|
||
| posts.list[].likeCount | number | 点赞数 |
|
||
| posts.list[].creator | object | 创作者简要信息(userId/nickname/avatar) |
|
||
| posts.pagination | object | 分页信息 |
|
||
| tags | object/null | 标签段(`type=tags` 或 `all` 时存在) |
|
||
| tags.list | array | 标签列表 |
|
||
| tags.list[].tagName | string | 标签名(不带 #) |
|
||
| tags.list[].postCount | number | 关联内容数 |
|
||
| tags.pagination | object | 分页信息 |
|
||
|
||
---
|
||
|
||
#### SEARCH-02 获取热门搜索词
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | GET /client/api/v1/search/trending |
|
||
| 接口用途 | 获取热门搜索词列表(搜索历史页底部「热门搜索」chips) |
|
||
| 权限要求 | 公开 |
|
||
| 涉及表 | search_trending |
|
||
|
||
**接口逻辑:**
|
||
1. 客户端接口读取服务内存缓存,不每次直查数据库
|
||
2. 服务启动时从 `search_trending` 加载热搜词,之后每 2 分钟自动刷新一次;刷新失败保留旧缓存
|
||
3. 缓存数据来源:`search_trending` 表中 `is_hot=1 AND is_active=1 AND deleted_at IS NULL` 的词
|
||
4. 排序:运营手动词 `is_manual=1` 优先,按 `sort_order ASC`;自动热词按 `search_count DESC`;再按 `updated_at DESC, id ASC` 兜底,取前 10
|
||
5. `search_count` 由搜索接口异步聚合写入,默认每 2 分钟批量落库,待写入搜索 hit 达到 1000 次或不同关键词达到 1000 个时提前 flush;接口缓存仍按 2 分钟刷新节奏展示,搜索量很低时展示延迟可能接近 4 分钟
|
||
|
||
**请求参数:** 无
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| keywords | string[] | 热门搜索词列表(最多 10 个) |
|
||
|
||
---
|
||
|
||
## 通知系统(notification)
|
||
|
||
### relatedId 语义说明
|
||
|
||
| type | relatedType | relatedId 含义 |
|
||
|------|-------------|---------------|
|
||
| like | post | 被点赞的内容 ID(post._id) |
|
||
| comment | post | 被评论的内容 ID(post._id) |
|
||
| mention | post | @ 提及所在的内容 ID(post._id) |
|
||
| creator_publish | post | 创作者新发布内容 ID(post._id) |
|
||
| publish_success | post | UP 主自己发布成功的内容 ID(post._id) |
|
||
| publish_success | media | 视频上传处理完成并进入待审核的媒体 ID(media.id) |
|
||
| system | media | 分片视频异步处理失败的媒体 ID(media.id) |
|
||
| follow | user | 关注者的用户 ID(user._id) |
|
||
| subscription | user | 订阅者的用户 ID(user._id) |
|
||
| purchase | order | 订阅订单 ID(subscription_order._id) |
|
||
| ppv_unlock | order | PPV 消息 ID(ppv_message._id) |
|
||
| system | — | null(系统通知无关联对象) |
|
||
|
||
#### NOTI-01 获取通知列表
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | GET /client/api/v1/notification/list |
|
||
| 接口用途 | 获取通知中心消息 |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | notification, user |
|
||
|
||
**接口逻辑:**
|
||
1. 查询 `notification` 表(user_id 匹配,按 created_at 倒序)
|
||
2. 支持按 category 筛选(interaction/subscription/system)
|
||
3. 相同类型聚合(如"XXX等3人点赞了你的内容")
|
||
4. 关联 `user` 表 获取触发者信息
|
||
|
||
**请求参数(Query):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| category | string | 否 | 分类:all/interaction/subscription/system,默认all |
|
||
| page | number | 否 | 页码,默认1 |
|
||
| pageSize | number | 否 | 每页条数,默认20 |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| list | array | 通知列表 |
|
||
| list[].notificationId | string | 通知ID |
|
||
| list[].category | string | 分类 |
|
||
| list[].type | string | 具体类型:like/comment/mention/creator_publish/publish_success/subscription/follow/purchase/ppv_unlock/system |
|
||
| list[].title | string | 通知标题 |
|
||
| list[].content | string | 通知内容 |
|
||
| list[].sender | object/null | 触发者信息 |
|
||
| list[].sender.userId | string | 触发者ID |
|
||
| list[].sender.nickname | string | 昵称 |
|
||
| list[].sender.avatar | url | 头像 |
|
||
| list[].coverUrl | url | 关联内容封面;当 relatedType=post 且可解析到内容封面时返回 |
|
||
| list[].relatedId | string | 关联对象ID(含义见下表) |
|
||
| list[].relatedType | string | 关联类型:post/user/order/media |
|
||
| list[].isRead | boolean | 是否已读 |
|
||
| list[].createdAt | timestamp | 创建时间 |
|
||
| pagination | object | 分页信息 |
|
||
|
||
---
|
||
|
||
#### NOTI-02 标记已读
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | PUT /client/api/v1/notification/read |
|
||
| 接口用途 | 标记通知为已读(单条/全部) |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | notification |
|
||
|
||
**接口逻辑:**
|
||
1. 如果传了 notificationIds,更新指定通知的 is_read=1
|
||
2. 如果传了 category,更新该分类所有通知的 is_read=1
|
||
3. 如果都不传,更新该用户所有通知的 is_read=1
|
||
|
||
**请求参数(Body):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| notificationIds | string[] | 否 | 通知ID数组(不传表示全部标记) |
|
||
| category | string | 否 | 按分类标记全部已读 |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| readCount | number | 本次标记已读的数量 |
|
||
|
||
---
|
||
|
||
#### NOTI-03 获取未读数
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | GET /client/api/v1/notification/unread-count |
|
||
| 接口用途 | 获取各分类的未读通知数 |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | notification |
|
||
|
||
**接口逻辑:**
|
||
1. 聚合查询 `notification` 表(user_id 匹配,is_read=0),按 category 分组 count
|
||
|
||
**请求参数:** 无
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| total | number | 总未读数 |
|
||
| interaction | number | 互动未读数 |
|
||
| subscription | number | 订阅未读数 |
|
||
| system | number | 系统未读数 |
|
||
|
||
---
|
||
|
||
## 浏览历史(history)
|
||
|
||
#### HISTORY-01 获取浏览历史
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | GET /client/api/v1/history/views |
|
||
| 接口用途 | 获取用户的内容浏览历史 |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | view_history, post, user, media |
|
||
|
||
**接口逻辑:**
|
||
1. 查询 `view_history` 表 WHERE user_id=当前用户,按 last_viewed_at 倒序,分页(读)
|
||
2. 关联查询 `post` 表 获取帖子信息(读)
|
||
3. 关联查询 `user` 表 获取创作者信息(读)
|
||
4. 关联查询 `media` 表 获取封面/缩略图(读)
|
||
5. 返回浏览历史列表
|
||
|
||
**请求参数(Query):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| page | number | 否 | 页码,默认1 |
|
||
| pageSize | number | 否 | 每页条数,默认20 |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| list | array | 浏览记录列表 |
|
||
| list[].postId | string | 帖子ID |
|
||
| list[].title | string | 帖子标题 |
|
||
| list[].coverUrl | url | 封面图URL |
|
||
| list[].type | string | 内容类型:post/video |
|
||
| list[].creator | object | 创作者信息 |
|
||
| list[].creator.userId | string | 创作者ID |
|
||
| list[].creator.nickname | string | 昵称 |
|
||
| list[].creator.avatar | url | 头像 |
|
||
| list[].lastViewedAt | timestamp | 最后浏览时间 |
|
||
| pagination | object | 分页信息 |
|
||
|
||
---
|
||
|
||
#### HISTORY-02 清除浏览历史
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | DELETE /client/api/v1/history/views |
|
||
| 接口用途 | 清除用户的浏览历史 |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | view_history |
|
||
|
||
**接口逻辑:**
|
||
1. 软删除 `view_history` 表 中该用户的所有记录(批量更新 deleted_at=当前时间)
|
||
|
||
**请求参数:** 无
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| success | number | 1=成功 |
|
||
|
||
---
|
||
|
||
## 举报系统(report)
|
||
|
||
#### REPORT-01 举报内容
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | POST /client/api/v1/report/content |
|
||
| 接口用途 | 用户举报不当内容(苹果审核要求) |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | report |
|
||
|
||
**接口逻辑:**
|
||
1. 校验被举报内容存在(读 `post` 表)
|
||
2. 检查是否重复举报(同一用户对同一内容)
|
||
3. 优先读取 `imageMediaIds`,查询 `media` 表校验媒体存在、归属当前用户、已处理完成且为图片(读)
|
||
4. 若未传 `imageMediaIds`,兼容读取旧字段 `images`
|
||
5. 写入 `report` 表(type=content/user,status=1待处理,`images` 保存举报截图 URL 列表);同一用户对同一对象已有待处理举报时,返回原举报记录并更新最新原因、说明和截图
|
||
|
||
**请求参数(Body):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| targetId | string | 是 | 被举报对象ID |
|
||
| targetType | string | 是 | 举报对象类型:content/user |
|
||
| reason | string | 是 | 举报类型:porn/violence/harassment/fraud/other |
|
||
| description | string | 否 | 补充说明 |
|
||
| imageMediaIds | string[] | 否 | 新字段,举报截图媒体 ID 列表;优先于 `images` 生效 |
|
||
| images | string[] | 否 | 旧字段,举报截图 URL 列表(兼容保留,deprecated) |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| reportId | string | 举报记录ID |
|
||
| message | string | "举报已提交,平台将通过机器审核+人工审核处理" |
|
||
|
||
---
|
||
|
||
#### REPORT-02 举报IM消息
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | POST /client/api/v1/report/message |
|
||
| 接口用途 | 用户举报私信中的不当消息(苹果审核要求) |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | report, message |
|
||
|
||
**接口逻辑:**
|
||
1. 校验被举报消息存在且用户是该会话参与者(读 `message` + `conversation`)
|
||
2. 检查是否重复举报
|
||
3. 写入 `report` 表(type=message,status=1待处理);同一用户对同一消息已有待处理举报时,返回原举报记录并更新最新原因、说明
|
||
|
||
**请求参数(Body):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| messageId | string | 是 | 被举报消息ID |
|
||
| reason | string | 是 | 举报类型:porn/violence/harassment/fraud/other |
|
||
| description | string | 否 | 补充说明 |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| reportId | string | 举报记录ID |
|
||
| message | string | "举报已提交" |
|
||
|
||
---
|
||
|
||
## 反馈系统(feedback)
|
||
|
||
#### FEEDBACK-01 提交帮助与反馈
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | POST /client/api/v1/feedback |
|
||
| 接口用途 | 用户在“我的 > 帮助与反馈”页提交问题描述和截图 |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | user_feedback |
|
||
|
||
**接口逻辑:**
|
||
1. 从登录 token 解析当前用户 `userId`
|
||
2. 校验 `description` 非空,去掉首尾空白
|
||
3. 优先读取 `imageMediaIds`,查询 `media` 表校验媒体存在、归属当前用户、已处理完成且为图片(读)
|
||
4. 若未传 `imageMediaIds`,兼容读取旧字段 `images`
|
||
5. 对最终图片列表做空串过滤
|
||
6. 写入 `user_feedback` 表,保存 `user_id / description / images`
|
||
|
||
**请求参数(Body):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| description | string | 是 | 反馈描述 |
|
||
| imageMediaIds | string[] | 否 | 新字段,反馈截图媒体 ID 列表;优先于 `images` 生效 |
|
||
| images | string[] | 否 | 旧字段,反馈截图 URL 列表(兼容保留,deprecated) |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| feedbackId | string | 反馈记录 ID |
|
||
|
||
---
|
||
|
||
## 异常日志(error-log)
|
||
|
||
#### ERRORLOG-01 上报客户端异常日志
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | POST /client/api/v1/uploadlogs |
|
||
| 接口用途 | 客户端在出现白屏、崩溃、接口异常等问题时,上报错误内容和运行环境,供后台排查 |
|
||
| 权限要求 | 公开(可未登录调用);若请求头带有效 Bearer Token,则自动关联当前用户 |
|
||
| 涉及表 | client_error_log |
|
||
|
||
**接口逻辑:**
|
||
1. 对 `deviceId`、`contents`、`appVersion`、`osType`、`stack` 做首尾空白裁剪
|
||
2. 校验 `deviceId`、`contents` 必填;`deviceId` 最大 128 字符,`appVersion`/`osType` 最大 32 字符
|
||
3. 不做服务端去重;每次调用都单独写入一条 `client_error_log`
|
||
4. 若请求头带有效 Bearer Token,则从 token 解析 `userId` 并写入;否则 `user_id` 为空
|
||
5. 返回新写入的日志 ID
|
||
|
||
> **说明:**
|
||
> 1. 请求体不接受 `userId`、`username` 等显式用户字段,用户关联只信登录态 token。
|
||
> 2. `stack` 为可选字段,若客户端有堆栈信息应尽量完整上传。
|
||
|
||
**请求参数(Body):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| deviceId | string | 是 | 设备号,用于后台按设备维度排查 |
|
||
| contents | string | 是 | 异常摘要/错误内容 |
|
||
| appVersion | string | 否 | App 版本号 |
|
||
| osType | string | 否 | 系统类型,如 `ios` / `android` / `h5` |
|
||
| stack | string | 否 | 异常堆栈或补充诊断信息 |
|
||
|
||
**请求示例:**
|
||
|
||
```json
|
||
{
|
||
"deviceId": "qa-uploadlogs-dev-20260420-001",
|
||
"contents": "App launch blank screen on boot",
|
||
"appVersion": "9.9.1-test",
|
||
"osType": "ios",
|
||
"stack": "Error: bootstrap failed\n at App.start (app.js:42)\n at main (index.js:7)"
|
||
}
|
||
```
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| logId | string | 异常日志记录 ID |
|
||
|
||
**响应示例:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"logId": "48faa79e-ca02-4030-a002-c953dabdeea2"
|
||
}
|
||
}
|
||
```
|