# 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" } } ```