# 文章推荐排序(精选顺序可定义)设计 > 日期:2026-09-04 > 状态:已批准(用户确认:不做独立置顶,仅使用精选 + 列表内上移/下移) ## 背景与目标 首页「推荐阅读」与知识中心「书册式推荐」当前展示的内容由 `is_featured=1`(精选)决定,但**顺序完全由 `published_at` 决定**,运营无法控制哪些精选文章排在前面。 目标:让运营在管理端**定义精选文章在首页/知识中心的展示顺序**,从而做到"首页放的内容可定义"。 ## 现状梳理 ### 后端 | 方法 | 位置 | 当前排序逻辑 | |------|------|-------------| | `getFeatured()` | `ArticleService.java:125` | `WHERE status=published AND is_featured=1 ORDER BY published_at DESC LIMIT 50` | | `getPublicList()` | `ArticleService.java:86` | `WHERE status=published ORDER BY is_featured DESC, published_at DESC` | | `toggleFeatured()` | `ArticleService.java:542` | 直接写 `is_featured`,无顺序关联 | | 管理端列表 `getAdminList()` | `ArticleService.java:386` | `ORDER BY created_at DESC` + `SortUtil.applySort` | ### 管理端(cfc-web) - `ArticleManage.vue`:文章管理列表,发布中的行下拉菜单已有「精选/取消精选」 - `api/article.js`:已有 `adminArticleToggleFeatured` - 接口:`POST /api/admin/articles/toggle-featured` ### 小程序端(cfc-frontend) - 首页「推荐阅读」:`getFeaturedArticles({ size: 5 })` → `getFeatured` - 知识中心「书册式推荐」:`getFeaturedArticles({ size: 8 })` → `getFeatured` - 顺序完全由后端返回决定,**前端无需改动** ## 需求确认(用户决策) 1. **不做**独立的置顶(is_top)字段 → 继续使用 `is_featured` 精选标记"要上首页的内容" 2. **新增**排序能力 → 精选文章顺序可由运营控制 3. **操作方式**:管理端文章列表内 **上移/下移** 按钮 ## 设计 ### 1. 数据库(迁移 293) `articles` 表新增一列: ```sql sort_order INT NOT NULL DEFAULT 0 COMMENT '推荐排序值(越小越靠前)' ``` - 迁移位置:`DatabaseInitializer.runMigrations()`,使用 `ensureColumn("articles", "sort_order", "INT NOT NULL DEFAULT 0 COMMENT '推荐排序值(越小越靠前)'")`(幂等) - 同步更新 `schema.sql` 的 `CREATE TABLE articles`(在 `is_featured` 后加 `sort_order` 列) ### 2. 后端 #### 2a. 实体 `Article.java` 新增字段: ```java /** 推荐排序值(越小越靠前,仅精选文章参与排序) */ private Integer sortOrder; ``` #### 2b. `ArticleService.getFeatured()` 排序由 `ORDER BY published_at DESC` 改为: ``` ORDER BY sort_order ASC, published_at DESC ``` 效果:精选文章顺序由运营的 sort_order 控制,未显式设置过 sort_order 的精选(默认 0)排在前面,与新精选的追加逻辑配合避免"新精选抢占首位"。 #### 2c. `ArticleService.getPublicList()` 排序由 `ORDER BY is_featured DESC, published_at DESC` 改为: ``` ORDER BY is_featured DESC, sort_order ASC, published_at DESC ``` 效果:知识中心"全部"列表中精选文章也按运营顺序靠前排布。 #### 2d. `ArticleService.toggleFeatured()` 设为精选(`isFeatured=1`)时,自动将 `sort_order` 置为 `当前最大 sort_order + 1`,使新精选**追加到队列末尾**,不抢占既有顺序。 ```java if (isFeatured == 1) { // 新精选追加到队尾,不抢占已有顺序 Integer maxSort = getMaxSortOrder(); article.setSortOrder((maxSort == null ? 0 : maxSort) + 1); } ``` 辅助方法 `getMaxSortOrder()`:查询 `articles WHERE status='published' AND is_featured=1` 的 `MAX(sort_order)`。 #### 2e. 新增接口 `POST /api/admin/articles/move` 在 `AdminArticleController` 新增: ``` POST /api/admin/articles/move body: { id: Long, direction: 'up' | 'down' } ``` **逻辑(`ArticleService.moveSortOrder(id, direction)`):** 1. 查询"当前已发布且精选"文章列表,按 `sort_order ASC, published_at ASC` 排序(注意:与展示序一致取 ASC,保证交换后顺序与前端一致) 2. 找到目标文章位置 `idx` 3. `direction=up` 且 `idx <= 0` → 返回「已在最前面」;`direction=down` 且 `idx >= len-1` → 返回「已在最后面」 4. 与相邻元素交换 sort_order 5. 交换后对整个列表**重新编号** `sort_order = 1..N`(消除并列与空洞,保证顺序绝对稳定) 6. 批量更新 **实现细节**:交换相邻的 sort_order 可能出现并列——为防止并列排序不稳定,执行重编号(步骤 5)。 **响应**:`Result`,边界返回 `Result.error(400, "已在最前/最后")`。 ### 3. 管理端(cfc-web) #### 3a. `api/article.js` 新增: ```js export function adminArticleMove(data) { return request({ url: '/api/admin/articles/move', method: 'post', data }) } ``` #### 3b. `ArticleManage.vue` - 发布中下拉菜单「精选/取消精选」旁,当 `row.isFeatured === 1` 时增加「上移」「下移」菜单项(或行内小按钮) - 操作列:`row.status === 'published' && row.isFeatured === 1` 时显示上移/下移 - 点击调用 `adminArticleMove({ id, direction })` 成功后刷新列表 - 标题区精选标签旁显示顺序序号:`精选 ①`(用 `sortOrder` 换算:非零序号直接显示,0 显示为队尾追加的未明确序号可不显示或显示 `精选`) **边界**:非精选文章不显示上移/下移;列表排序按 `created_at DESC` 展示,与推荐顺序无关(运营通过按钮逐个调整)。 ### 4. 小程序端(cfc-frontend) 无需改动。首页「推荐阅读」与知识中心书册数据均由 `getFeatured()` 返回,顺序自动受控。 ## 数据流 ``` 运营在管理端点「上移/下移」 → POST /api/admin/articles/move {id, direction} → ArticleService.moveSortOrder() 交换 + 重编号 sort_order=1..N → 小程序 getFeatured() 按 sort_order ASC 返回 → 首页「推荐阅读」/知识中心书册顺序更新 ``` ## 边界与异常处理 | 场景 | 处理 | |------|------| | 目标文章不是精选 | `move` 拒绝:「仅精选文章可调整顺序」 | | 已是最前/最后 | 返回 400 提示「已在最前面/最后面」 | | 文章不存在 | 返回 400「文章不存在」 | | 方向非法 | 返回 400「direction 只能是 up/down」 | | 并发操作 | 事务 + 重编号天然收敛(最后一次操作者胜出) | | 新设精选 | 自动追加队尾,不抢占 | ## 验证 1. 后端:`mvn clean compile` 通过 2. 管理端:`npm run build` 通过 3. 手工验证流程: - 设 2+ 篇文章为精选 → 首页顺序按发布时间的从新到旧(sort_order 追加序) - 管理端点「上移/下移」→ 刷新列表顺位变化 - 首页/知识中心书册顺序与调整后一致 - 边界:第一文章点上移 → 提示;最后文章点下移 → 提示 4. 迁移幂等:重复执行 `runMigrations()` 不报错 ## 文件改动清单 | 文件 | 改动 | |------|------| | `cfc-backend/.../config/DatabaseInitializer.java` | 迁移293 加 sort_order 列 | | `cfc-backend/.../resources/schema.sql` | articles 建表加 sort_order 列 | | `cfc-backend/.../entity/Article.java` | 加 sortOrder 字段 | | `cfc-backend/.../service/ArticleService.java` | getFeatured/getPublicList 排序、toggleFeatured 追加队尾、新增 moveSortOrder + getMaxSortOrder | | `cfc-backend/.../controller/admin/AdminArticleController.java` | 新增 POST /move | | `cfc-web/src/api/article.js` | 新增 adminArticleMove | | `cfc-web/src/views/admin/ArticleManage.vue` | 上移/下移按钮 + 精选序号展示 | | `docs/superpowers/api/API_REFERENCE.md` | 同步记录 /move 接口 |