2026-09-04-article-sort-order-design.md 7.6 KB

文章推荐排序(精选顺序可定义)设计

日期: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 表新增一列:

sort_order INT NOT NULL DEFAULT 0 COMMENT '推荐排序值(越小越靠前)'
  • 迁移位置:DatabaseInitializer.runMigrations(),使用 ensureColumn("articles", "sort_order", "INT NOT NULL DEFAULT 0 COMMENT '推荐排序值(越小越靠前)'")(幂等)
  • 同步更新 schema.sqlCREATE TABLE articles(在 is_featured 后加 sort_order 列)

2. 后端

2a. 实体 Article.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,使新精选追加到队列末尾,不抢占既有顺序。

if (isFeatured == 1) {
    // 新精选追加到队尾,不抢占已有顺序
    Integer maxSort = getMaxSortOrder();
    article.setSortOrder((maxSort == null ? 0 : maxSort) + 1);
}

辅助方法 getMaxSortOrder():查询 articles WHERE status='published' AND is_featured=1MAX(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=upidx <= 0 → 返回「已在最前面」;direction=downidx >= len-1 → 返回「已在最后面」
  4. 与相邻元素交换 sort_order
  5. 交换后对整个列表重新编号 sort_order = 1..N(消除并列与空洞,保证顺序绝对稳定)
  6. 批量更新

实现细节:交换相邻的 sort_order 可能出现并列——为防止并列排序不稳定,执行重编号(步骤 5)。

响应Result<String>,边界返回 Result.error(400, "已在最前/最后")

3. 管理端(cfc-web)

3a. api/article.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 接口