feat: use joda time for cursor process

This commit is contained in:
xushanchuan 2021-03-23 15:54:04 +08:00
parent 7478515067
commit 7b7016e569
No known key found for this signature in database
GPG Key ID: 44D23C44E00838D6
15 changed files with 117 additions and 60 deletions

View File

@ -16,6 +16,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -60,7 +61,11 @@ public class ArticleQueryService {
}
public CursorPager<ArticleData> findRecentArticlesWithCursor(
String tag, String author, String favoritedBy, CursorPageParameter page, User currentUser) {
String tag,
String author,
String favoritedBy,
CursorPageParameter<DateTime> page,
User currentUser) {
List<String> articleIds =
articleReadService.findArticlesWithCursor(tag, author, favoritedBy, page);
if (articleIds.size() == 0) {
@ -81,7 +86,8 @@ public class ArticleQueryService {
}
}
public CursorPager<ArticleData> findUserFeedWithCursor(User user, CursorPageParameter page) {
public CursorPager<ArticleData> findUserFeedWithCursor(
User user, CursorPageParameter<DateTime> page) {
List<String> followdUsers = userRelationshipQueryService.followedUsers(user.getId());
if (followdUsers.size() == 0) {
return new CursorPager<>(new ArrayList<>(), page.getDirection(), false);

View File

@ -10,6 +10,7 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;
@Service
@ -58,7 +59,7 @@ public class CommentQueryService {
}
public CursorPager<CommentData> findByArticleIdWithCursor(
String articleId, User user, CursorPageParameter page) {
String articleId, User user, CursorPageParameter<DateTime> page) {
List<CommentData> comments = commentReadService.findByArticleIdWithCursor(articleId, page);
if (comments.isEmpty()) {
return new CursorPager<>(new ArrayList<>(), page.getDirection(), false);

View File

@ -6,13 +6,13 @@ import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class CursorPageParameter {
public class CursorPageParameter<T> {
private static final int MAX_LIMIT = 1000;
private int limit = 20;
private String cursor;
private T cursor;
private Direction direction;
public CursorPageParameter(String cursor, int limit, Direction direction) {
public CursorPageParameter(T cursor, int limit, Direction direction) {
setLimit(limit);
setCursor(cursor);
setDirection(direction);
@ -26,12 +26,8 @@ public class CursorPageParameter {
return limit + 1;
}
private void setCursor(String cursor) {
if (cursor == null) {
this.cursor = "";
} else {
this.cursor = cursor;
}
private void setCursor(T cursor) {
this.cursor = cursor;
}
private void setLimit(int limit) {

View File

@ -29,12 +29,12 @@ public class CursorPager<T extends Node> {
return previous;
}
public String getStartCursor() {
return data.isEmpty() ? "" : data.get(0).getCursor();
public PageCursor getStartCursor() {
return data.isEmpty() ? null : data.get(0).getCursor();
}
public String getEndCursor() {
return data.isEmpty() ? "" : data.get(data.size() - 1).getCursor();
public PageCursor getEndCursor() {
return data.isEmpty() ? null : data.get(data.size() - 1).getCursor();
}
public enum Direction {

View File

@ -0,0 +1,23 @@
package io.spring.application;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
public class DateTimeCursor extends PageCursor<DateTime> {
public DateTimeCursor(DateTime data) {
super(data);
}
@Override
public String toString() {
return String.valueOf(getData().getMillis());
}
public static DateTime parse(String cursor) {
if (cursor == null) {
return null;
}
return new DateTime().withMillis(Long.parseLong(cursor)).withZone(DateTimeZone.UTC);
}
}

View File

@ -1,5 +1,5 @@
package io.spring.application;
public interface Node {
String getCursor();
PageCursor getCursor();
}

View File

@ -0,0 +1,18 @@
package io.spring.application;
public abstract class PageCursor<T> {
private T data;
public PageCursor(T data) {
this.data = data;
}
public T getData() {
return data;
}
@Override
public String toString() {
return data.toString();
}
}

View File

@ -1,6 +1,7 @@
package io.spring.application.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.spring.application.DateTimeCursor;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -26,7 +27,7 @@ public class ArticleData implements io.spring.application.Node {
private ProfileData profileData;
@Override
public String getCursor() {
return String.valueOf(getUpdatedAt().getMillis());
public DateTimeCursor getCursor() {
return new DateTimeCursor(updatedAt);
}
}

View File

@ -2,6 +2,7 @@ package io.spring.application.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.spring.application.DateTimeCursor;
import io.spring.application.Node;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -22,7 +23,7 @@ public class CommentData implements Node {
private ProfileData profileData;
@Override
public String getCursor() {
return String.valueOf(createdAt.getMillis());
public DateTimeCursor getCursor() {
return new DateTimeCursor(createdAt);
}
}

View File

@ -8,12 +8,12 @@ import graphql.execution.DataFetcherResult;
import graphql.relay.DefaultConnectionCursor;
import graphql.relay.DefaultPageInfo;
import graphql.schema.DataFetchingEnvironment;
import io.spring.Util;
import io.spring.api.exception.ResourceNotFoundException;
import io.spring.application.ArticleQueryService;
import io.spring.application.CursorPageParameter;
import io.spring.application.CursorPager;
import io.spring.application.CursorPager.Direction;
import io.spring.application.DateTimeCursor;
import io.spring.application.data.ArticleData;
import io.spring.application.data.CommentData;
import io.spring.core.user.User;
@ -62,11 +62,13 @@ public class ArticleDatafetcher {
if (first != null) {
articles =
articleQueryService.findUserFeedWithCursor(
current, new CursorPageParameter(after, first, Direction.NEXT));
current,
new CursorPageParameter<>(DateTimeCursor.parse(after), first, Direction.NEXT));
} else {
articles =
articleQueryService.findUserFeedWithCursor(
current, new CursorPageParameter(before, last, Direction.PREV));
current,
new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV));
}
graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles);
ArticlesConnection articlesConnection =
@ -77,7 +79,7 @@ public class ArticleDatafetcher {
.map(
a ->
ArticleEdge.newBuilder()
.cursor(a.getCursor())
.cursor(a.getCursor().toString())
.node(buildArticleResult(a))
.build())
.collect(Collectors.toList()))
@ -110,11 +112,13 @@ public class ArticleDatafetcher {
if (first != null) {
articles =
articleQueryService.findUserFeedWithCursor(
target, new CursorPageParameter(after, first, Direction.NEXT));
target,
new CursorPageParameter<>(DateTimeCursor.parse(after), first, Direction.NEXT));
} else {
articles =
articleQueryService.findUserFeedWithCursor(
target, new CursorPageParameter(before, last, Direction.PREV));
target,
new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV));
}
graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles);
ArticlesConnection articlesConnection =
@ -125,7 +129,7 @@ public class ArticleDatafetcher {
.map(
a ->
ArticleEdge.newBuilder()
.cursor(a.getCursor())
.cursor(a.getCursor().toString())
.node(buildArticleResult(a))
.build())
.collect(Collectors.toList()))
@ -158,7 +162,7 @@ public class ArticleDatafetcher {
null,
null,
profile.getUsername(),
new CursorPageParameter(after, first, Direction.NEXT),
new CursorPageParameter<>(DateTimeCursor.parse(after), first, Direction.NEXT),
current);
} else {
articles =
@ -166,7 +170,7 @@ public class ArticleDatafetcher {
null,
null,
profile.getUsername(),
new CursorPageParameter(before, last, Direction.PREV),
new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV),
current);
}
graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles);
@ -179,7 +183,7 @@ public class ArticleDatafetcher {
.map(
a ->
ArticleEdge.newBuilder()
.cursor(a.getCursor())
.cursor(a.getCursor().toString())
.node(buildArticleResult(a))
.build())
.collect(Collectors.toList()))
@ -212,7 +216,7 @@ public class ArticleDatafetcher {
null,
profile.getUsername(),
null,
new CursorPageParameter(after, first, Direction.NEXT),
new CursorPageParameter<>(DateTimeCursor.parse(after), first, Direction.NEXT),
current);
} else {
articles =
@ -220,7 +224,7 @@ public class ArticleDatafetcher {
null,
profile.getUsername(),
null,
new CursorPageParameter(before, last, Direction.PREV),
new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV),
current);
}
graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles);
@ -232,7 +236,7 @@ public class ArticleDatafetcher {
.map(
a ->
ArticleEdge.newBuilder()
.cursor(a.getCursor())
.cursor(a.getCursor().toString())
.node(buildArticleResult(a))
.build())
.collect(Collectors.toList()))
@ -267,7 +271,7 @@ public class ArticleDatafetcher {
withTag,
authoredBy,
favoritedBy,
new CursorPageParameter(after, first, Direction.NEXT),
new CursorPageParameter<>(DateTimeCursor.parse(after), first, Direction.NEXT),
current);
} else {
articles =
@ -275,7 +279,7 @@ public class ArticleDatafetcher {
withTag,
authoredBy,
favoritedBy,
new CursorPageParameter(before, last, Direction.PREV),
new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV),
current);
}
graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles);
@ -287,7 +291,7 @@ public class ArticleDatafetcher {
.map(
a ->
ArticleEdge.newBuilder()
.cursor(a.getCursor())
.cursor(a.getCursor().toString())
.node(buildArticleResult(a))
.build())
.collect(Collectors.toList()))
@ -360,12 +364,12 @@ public class ArticleDatafetcher {
private DefaultPageInfo buildArticlePageInfo(CursorPager<ArticleData> articles) {
return new DefaultPageInfo(
Util.isEmpty(articles.getStartCursor())
articles.getStartCursor() == null
? null
: new DefaultConnectionCursor(articles.getStartCursor()),
Util.isEmpty(articles.getEndCursor())
: new DefaultConnectionCursor(articles.getStartCursor().toString()),
articles.getEndCursor() == null
? null
: new DefaultConnectionCursor(articles.getEndCursor()),
: new DefaultConnectionCursor(articles.getEndCursor().toString()),
articles.hasPrevious(),
articles.hasNext());
}

View File

@ -7,11 +7,11 @@ import com.netflix.graphql.dgs.InputArgument;
import graphql.execution.DataFetcherResult;
import graphql.relay.DefaultConnectionCursor;
import graphql.relay.DefaultPageInfo;
import io.spring.Util;
import io.spring.application.CommentQueryService;
import io.spring.application.CursorPageParameter;
import io.spring.application.CursorPager;
import io.spring.application.CursorPager.Direction;
import io.spring.application.DateTimeCursor;
import io.spring.application.data.ArticleData;
import io.spring.application.data.CommentData;
import io.spring.core.user.User;
@ -72,11 +72,15 @@ public class CommentDatafetcher {
if (first != null) {
comments =
commentQueryService.findByArticleIdWithCursor(
articleData.getId(), current, new CursorPageParameter(after, first, Direction.NEXT));
articleData.getId(),
current,
new CursorPageParameter<>(DateTimeCursor.parse(after), first, Direction.NEXT));
} else {
comments =
commentQueryService.findByArticleIdWithCursor(
articleData.getId(), current, new CursorPageParameter(before, last, Direction.PREV));
articleData.getId(),
current,
new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV));
}
graphql.relay.PageInfo pageInfo = buildCommentPageInfo(comments);
CommentsConnection result =
@ -87,7 +91,7 @@ public class CommentDatafetcher {
.map(
a ->
CommentEdge.newBuilder()
.cursor(a.getCursor())
.cursor(a.getCursor().toString())
.node(buildCommentResult(a))
.build())
.collect(Collectors.toList()))
@ -101,12 +105,12 @@ public class CommentDatafetcher {
private DefaultPageInfo buildCommentPageInfo(CursorPager<CommentData> comments) {
return new DefaultPageInfo(
Util.isEmpty(comments.getStartCursor())
comments.getStartCursor() == null
? null
: new DefaultConnectionCursor(comments.getStartCursor()),
Util.isEmpty(comments.getEndCursor())
: new DefaultConnectionCursor(comments.getStartCursor().toString()),
comments.getEndCursor() == null
? null
: new DefaultConnectionCursor(comments.getEndCursor()),
: new DefaultConnectionCursor(comments.getEndCursor().toString()),
comments.hasPrevious(),
comments.hasNext());
}

View File

@ -5,6 +5,7 @@ import io.spring.application.data.CommentData;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.joda.time.DateTime;
@Mapper
public interface CommentReadService {
@ -13,5 +14,5 @@ public interface CommentReadService {
List<CommentData> findByArticleId(@Param("articleId") String articleId);
List<CommentData> findByArticleIdWithCursor(
@Param("articleId") String articleId, @Param("page") CursorPageParameter page);
@Param("articleId") String articleId, @Param("page") CursorPageParameter<DateTime> page);
}

View File

@ -116,10 +116,10 @@
<if test="favoritedBy != null">
AND AFU.username = #{favoritedBy}
</if>
<if test='page.cursor != "" and page.direction.name() == "NEXT"'>
<if test='page.cursor != null and page.direction.name() == "NEXT"'>
AND A.created_at &lt; #{page.cursor}
</if>
<if test='page.cursor != "" and page.direction.name() == "PREV"'>
<if test='page.cursor != null and page.direction.name() == "PREV"'>
AND A.created_at > #{page.cursor}
</if>
</where>
@ -138,10 +138,10 @@
<foreach index="index" collection="authors" item="id" open="(" separator="," close=")">
#{id}
</foreach>
<if test='page.cursor != "" and page.direction.name() == "NEXT"'>
<if test='page.cursor != null and page.direction.name() == "NEXT"'>
AND A.created_at &lt; #{page.cursor}
</if>
<if test='page.cursor != "" and page.direction.name() == "PREV"'>
<if test='page.cursor != null and page.direction.name() == "PREV"'>
AND A.created_at > #{page.cursor}
</if>
<if test='page.direction.name() == "NEXT"'>

View File

@ -25,10 +25,10 @@
<include refid="selectCommentData"/>
<where>
C.article_id = #{articleId}
<if test='page.cursor != "" and page.direction.name() == "NEXT"'>
<if test='page.cursor != null and page.direction.name() == "NEXT"'>
AND C.created_at &lt; #{page.cursor}
</if>
<if test='page.cursor != "" and page.direction.name() == "PREV"'>
<if test='page.cursor != null and page.direction.name() == "PREV"'>
AND C.created_at > #{page.cursor}
</if>
</where>

View File

@ -9,6 +9,7 @@ import io.spring.application.ArticleQueryService;
import io.spring.application.CursorPageParameter;
import io.spring.application.CursorPager;
import io.spring.application.CursorPager.Direction;
import io.spring.application.DateTimeCursor;
import io.spring.application.Page;
import io.spring.application.data.ArticleData;
import io.spring.application.data.ArticleDataList;
@ -124,7 +125,7 @@ public class ArticleQueryServiceTest extends DbTestBase {
CursorPager<ArticleData> recentArticles =
queryService.findRecentArticlesWithCursor(
null, null, null, new CursorPageParameter("", 20, Direction.NEXT), user);
null, null, null, new CursorPageParameter<>(null, 20, Direction.NEXT), user);
assertEquals(recentArticles.getData().size(), 2);
assertEquals(recentArticles.getData().get(0).getId(), article.getId());
@ -133,14 +134,15 @@ public class ArticleQueryServiceTest extends DbTestBase {
null,
null,
null,
new CursorPageParameter(recentArticles.getEndCursor(), 20, Direction.NEXT),
new CursorPageParameter<DateTime>(
DateTimeCursor.parse(recentArticles.getEndCursor().toString()), 20, Direction.NEXT),
user);
assertEquals(nodata.getData().size(), 0);
assertEquals(nodata.getStartCursor(), "");
assertEquals(nodata.getStartCursor(), null);
CursorPager<ArticleData> prevArticles =
queryService.findRecentArticlesWithCursor(
null, null, null, new CursorPageParameter("", 20, Direction.PREV), user);
null, null, null, new CursorPageParameter<>(null, 20, Direction.PREV), user);
assertEquals(prevArticles.getData().size(), 2);
}