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.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -60,7 +61,11 @@ public class ArticleQueryService {
} }
public CursorPager<ArticleData> findRecentArticlesWithCursor( 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 = List<String> articleIds =
articleReadService.findArticlesWithCursor(tag, author, favoritedBy, page); articleReadService.findArticlesWithCursor(tag, author, favoritedBy, page);
if (articleIds.size() == 0) { 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()); List<String> followdUsers = userRelationshipQueryService.followedUsers(user.getId());
if (followdUsers.size() == 0) { if (followdUsers.size() == 0) {
return new CursorPager<>(new ArrayList<>(), page.getDirection(), false); 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.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@ -58,7 +59,7 @@ public class CommentQueryService {
} }
public CursorPager<CommentData> findByArticleIdWithCursor( public CursorPager<CommentData> findByArticleIdWithCursor(
String articleId, User user, CursorPageParameter page) { String articleId, User user, CursorPageParameter<DateTime> page) {
List<CommentData> comments = commentReadService.findByArticleIdWithCursor(articleId, page); List<CommentData> comments = commentReadService.findByArticleIdWithCursor(articleId, page);
if (comments.isEmpty()) { if (comments.isEmpty()) {
return new CursorPager<>(new ArrayList<>(), page.getDirection(), false); return new CursorPager<>(new ArrayList<>(), page.getDirection(), false);

View File

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

View File

@ -29,12 +29,12 @@ public class CursorPager<T extends Node> {
return previous; return previous;
} }
public String getStartCursor() { public PageCursor getStartCursor() {
return data.isEmpty() ? "" : data.get(0).getCursor(); return data.isEmpty() ? null : data.get(0).getCursor();
} }
public String getEndCursor() { public PageCursor getEndCursor() {
return data.isEmpty() ? "" : data.get(data.size() - 1).getCursor(); return data.isEmpty() ? null : data.get(data.size() - 1).getCursor();
} }
public enum Direction { 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; package io.spring.application;
public interface Node { 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; package io.spring.application.data;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.spring.application.DateTimeCursor;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@ -26,7 +27,7 @@ public class ArticleData implements io.spring.application.Node {
private ProfileData profileData; private ProfileData profileData;
@Override @Override
public String getCursor() { public DateTimeCursor getCursor() {
return String.valueOf(getUpdatedAt().getMillis()); 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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.spring.application.DateTimeCursor;
import io.spring.application.Node; import io.spring.application.Node;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@ -22,7 +23,7 @@ public class CommentData implements Node {
private ProfileData profileData; private ProfileData profileData;
@Override @Override
public String getCursor() { public DateTimeCursor getCursor() {
return String.valueOf(createdAt.getMillis()); return new DateTimeCursor(createdAt);
} }
} }

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import io.spring.application.data.CommentData;
import java.util.List; import java.util.List;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.joda.time.DateTime;
@Mapper @Mapper
public interface CommentReadService { public interface CommentReadService {
@ -13,5 +14,5 @@ public interface CommentReadService {
List<CommentData> findByArticleId(@Param("articleId") String articleId); List<CommentData> findByArticleId(@Param("articleId") String articleId);
List<CommentData> findByArticleIdWithCursor( 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"> <if test="favoritedBy != null">
AND AFU.username = #{favoritedBy} AND AFU.username = #{favoritedBy}
</if> </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} AND A.created_at &lt; #{page.cursor}
</if> </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} AND A.created_at > #{page.cursor}
</if> </if>
</where> </where>
@ -138,10 +138,10 @@
<foreach index="index" collection="authors" item="id" open="(" separator="," close=")"> <foreach index="index" collection="authors" item="id" open="(" separator="," close=")">
#{id} #{id}
</foreach> </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} AND A.created_at &lt; #{page.cursor}
</if> </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} AND A.created_at > #{page.cursor}
</if> </if>
<if test='page.direction.name() == "NEXT"'> <if test='page.direction.name() == "NEXT"'>

View File

@ -25,10 +25,10 @@
<include refid="selectCommentData"/> <include refid="selectCommentData"/>
<where> <where>
C.article_id = #{articleId} 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} AND C.created_at &lt; #{page.cursor}
</if> </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} AND C.created_at > #{page.cursor}
</if> </if>
</where> </where>

View File

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