Skip to content

Commit 7163de4

Browse files
committed
refactor(article): 优化首页最新文章查询逻辑
- 重构 getIndexRecentArticles 方法,分离查询构建逻辑到独立方法 - 添加置顶文章ID集合构建方法,提高查找效率 - 实现分页补偿机制,确保多页间文章数量一致性 - 修复分页时置顶文章重复显示问题 - 更新 AGENTS.md 文档,补充首页两列对齐约束说明
1 parent e8b4b4c commit 7163de4

File tree

2 files changed

+67
-20
lines changed

2 files changed

+67
-20
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
- VIP 管理页样式需注意 `home.css``.form--admin label { flex: 1; }` 会影响布局;配置项行在 `vip-admin.scss` 中需显式改为整行(label/builder 100%)并对 checkbox 使用类型选择器,避免控件被放大。
5454
- 有效期字段:勋章 `expireTime`(毫秒,`0`=永久);会员 `expiresAt`(可回填勋章到期)。
5555
- 首页最新文章链路:`IndexProcessor#loadIndexData` 通过 `ArticleQueryService#getIndexRecentArticles(fetchSize, page)` 组装 classic/mobile 首页“最新文章”;第一页置顶插入与数量行为在该方法维护。
56+
- 首页两列对齐约束:`getIndexRecentArticles` 的第一页会插入全部置顶且不截断;第二页起需按第一页“置顶占位数”补偿 `fetchSize` 与分页偏移,保证两列等高且不丢中间文章。
5657
- 路由总入口:`Router#requestMapping` + 各 Processor `register()`;新增路由先决定使用 `loginCheck` / `apiCheck` / `permission` / `anonymousViewCheck` 哪条链路。
5758
- `LoginCheckMidware#handle`:未登录统一 401(特殊 URI `/gen` 返回空 SVG);支持 `Sessions``apiKey` 两种登录态来源。
5859
- 新接口若需“页面登录态 + apiKey 调用”双兼容,路由层优先挂 `loginCheck::handle`,处理方法再读取 `context.attr(User.USER)`;避免只挂 `permission` 导致拿不到当前用户对象。

src/main/java/org/b3log/symphony/service/ArticleQueryService.java

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,30 +1320,38 @@ public List<JSONObject> getIndexRecentArticles(int fetchSize, int page) {
13201320
try {
13211321
Stopwatchs.start("Query index recent articles");
13221322
try {
1323-
Query query = new Query().
1324-
setFilter(CompositeFilterOperator.and(
1325-
new PropertyFilter(Article.ARTICLE_TYPE, FilterOperator.NOT_EQUAL, Article.ARTICLE_TYPE_C_DISCUSSION),
1326-
new PropertyFilter(Article.ARTICLE_TYPE, FilterOperator.NOT_EQUAL, Article.ARTICLE_TYPE_C_LONG),
1327-
new PropertyFilter(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID),
1328-
new PropertyFilter(Article.ARTICLE_SHOW_IN_LIST, FilterOperator.NOT_EQUAL, Article.ARTICLE_SHOW_IN_LIST_C_NOT),
1329-
new PropertyFilter(Article.ARTICLE_CREATE_TIME, FilterOperator.GREATER_THAN_OR_EQUAL, String.valueOf(DateUtils.addDays(new Date(), -30).getTime())))).
1330-
setPageCount(1).setPage(page, fetchSize).
1331-
addSort(Article.ARTICLE_LATEST_CMT_TIME, SortDirection.DESCENDING);
1332-
ret = articleRepository.getList(query);
1333-
1323+
final int normalizedPage = Math.max(page, 1);
13341324
final List<JSONObject> stickArticles = getStickArticles();
1335-
if (!stickArticles.isEmpty() && page == 1) {
1336-
final Iterator<JSONObject> i = ret.iterator();
1337-
while (i.hasNext()) {
1338-
final JSONObject article = i.next();
1339-
for (final JSONObject stickArticle : stickArticles) {
1340-
if (article.optString(Keys.OBJECT_ID).equals(stickArticle.optString(Keys.OBJECT_ID))) {
1341-
i.remove();
1325+
final Set<String> stickArticleIds = buildArticleIdSet(stickArticles);
1326+
1327+
if (1 == normalizedPage) {
1328+
final Query query = buildIndexRecentArticlesQuery(1, fetchSize);
1329+
ret = articleRepository.getList(query);
1330+
1331+
if (!stickArticles.isEmpty()) {
1332+
final Iterator<JSONObject> iterator = ret.iterator();
1333+
while (iterator.hasNext()) {
1334+
final JSONObject article = iterator.next();
1335+
if (stickArticleIds.contains(article.optString(Keys.OBJECT_ID))) {
1336+
iterator.remove();
13421337
}
13431338
}
1344-
}
13451339

1346-
ret.addAll(0, stickArticles);
1340+
ret.addAll(0, stickArticles);
1341+
}
1342+
} else {
1343+
final int extraSize = calcIndexRecentExtraSize(fetchSize, stickArticleIds);
1344+
final int actualFetchSize = fetchSize + extraSize;
1345+
final int fromIndex = (normalizedPage - 1) * fetchSize + (normalizedPage - 2) * extraSize;
1346+
final int toIndex = fromIndex + actualFetchSize;
1347+
1348+
final Query query = buildIndexRecentArticlesQuery(1, toIndex);
1349+
final List<JSONObject> queried = articleRepository.getList(query);
1350+
if (queried.size() <= fromIndex) {
1351+
ret = new ArrayList<>();
1352+
} else {
1353+
ret = new ArrayList<>(queried.subList(fromIndex, Math.min(queried.size(), toIndex)));
1354+
}
13471355
}
13481356
} finally {
13491357
Stopwatchs.end();
@@ -1359,6 +1367,44 @@ public List<JSONObject> getIndexRecentArticles(int fetchSize, int page) {
13591367
}
13601368
}
13611369

1370+
private Query buildIndexRecentArticlesQuery(final int page, final int fetchSize) {
1371+
return new Query().
1372+
setFilter(CompositeFilterOperator.and(
1373+
new PropertyFilter(Article.ARTICLE_TYPE, FilterOperator.NOT_EQUAL, Article.ARTICLE_TYPE_C_DISCUSSION),
1374+
new PropertyFilter(Article.ARTICLE_TYPE, FilterOperator.NOT_EQUAL, Article.ARTICLE_TYPE_C_LONG),
1375+
new PropertyFilter(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID),
1376+
new PropertyFilter(Article.ARTICLE_SHOW_IN_LIST, FilterOperator.NOT_EQUAL, Article.ARTICLE_SHOW_IN_LIST_C_NOT),
1377+
new PropertyFilter(Article.ARTICLE_CREATE_TIME, FilterOperator.GREATER_THAN_OR_EQUAL, String.valueOf(DateUtils.addDays(new Date(), -30).getTime())))).
1378+
setPageCount(1).setPage(page, fetchSize).
1379+
addSort(Article.ARTICLE_LATEST_CMT_TIME, SortDirection.DESCENDING);
1380+
}
1381+
1382+
private Set<String> buildArticleIdSet(final List<JSONObject> articles) {
1383+
final Set<String> articleIds = new HashSet<>();
1384+
for (final JSONObject article : articles) {
1385+
articleIds.add(article.optString(Keys.OBJECT_ID));
1386+
}
1387+
1388+
return articleIds;
1389+
}
1390+
1391+
private int calcIndexRecentExtraSize(final int fetchSize, final Set<String> stickArticleIds) throws RepositoryException {
1392+
if (stickArticleIds.isEmpty()) {
1393+
return 0;
1394+
}
1395+
1396+
final Query firstPageQuery = buildIndexRecentArticlesQuery(1, fetchSize);
1397+
final List<JSONObject> firstPageArticles = articleRepository.getList(firstPageQuery);
1398+
int duplicatedCount = 0;
1399+
for (final JSONObject article : firstPageArticles) {
1400+
if (stickArticleIds.contains(article.optString(Keys.OBJECT_ID))) {
1401+
duplicatedCount++;
1402+
}
1403+
}
1404+
1405+
return Math.max(0, stickArticleIds.size() - duplicatedCount);
1406+
}
1407+
13621408
/**
13631409
* Gets the long articles (articleType=6) for index display.
13641410
*

0 commit comments

Comments
 (0)