refactor: application level prepare for graphql and switch to sqlite

This commit is contained in:
xushanchuan 2021-03-19 15:23:37 +08:00
parent 36e33e7730
commit 01fac42c64
No known key found for this signature in database
GPG Key ID: 44D23C44E00838D6
28 changed files with 746 additions and 416 deletions

3
.gitignore vendored
View File

@ -1,6 +1,7 @@
.gradle .gradle
/build/ /build/
!gradle/wrapper/gradle-wrapper.jar !gradle/wrapper/gradle-wrapper.jar
*.db
### STS ### ### STS ###
.apt_generated .apt_generated
@ -22,4 +23,4 @@ build/
nbbuild/ nbbuild/
dist/ dist/
nbdist/ nbdist/
.nb-gradle/ .nb-gradle/

View File

@ -4,6 +4,7 @@ plugins {
id 'java' id 'java'
id 'idea' id 'idea'
id 'eclipse' id 'eclipse'
id "com.netflix.dgs.codegen" version "4.2.0"
} }
version = '0.0.1-SNAPSHOT' version = '0.0.1-SNAPSHOT'
@ -13,6 +14,7 @@ targetCompatibility = JavaVersion.VERSION_1_8
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
jcenter()
} }
configurations { configurations {
@ -26,11 +28,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-hateoas' implementation 'org.springframework.boot:spring-boot-starter-hateoas'
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3' implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3'
implementation "com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter:latest.release"
implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-core'
implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'joda-time:joda-time:2.10.6' implementation 'joda-time:joda-time:2.10.6'
implementation 'org.xerial:sqlite-jdbc:3.34.0'
runtimeOnly 'com.h2database:h2'
compileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
@ -47,3 +49,8 @@ dependencies {
test { test {
useJUnitPlatform() useJUnitPlatform()
} }
generateJava {
schemaPaths = ["${projectDir}/src/main/resources/schema"] // List of directories containing schema files
packageName = 'io.spring.graphql' // The package name to use to generate sources
}

View File

@ -2,11 +2,12 @@ package io.spring;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
@SpringBootApplication @SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)
public class RealworldApplication { public class RealworldApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(RealworldApplication.class, args); SpringApplication.run(RealworldApplication.class, args);
} }
} }

View File

@ -21,56 +21,70 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@EnableWebSecurity @EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${spring.h2.console.enabled:false}") @Value("${spring.h2.console.enabled:false}")
private boolean h2ConsoleEnabled; private boolean h2ConsoleEnabled;
@Bean @Bean
public JwtTokenFilter jwtTokenFilter() { public JwtTokenFilter jwtTokenFilter() {
return new JwtTokenFilter(); return new JwtTokenFilter();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
if (h2ConsoleEnabled) {
http.authorizeRequests()
.antMatchers("/h2-console", "/h2-console/**")
.permitAll()
.and()
.headers()
.frameOptions()
.sameOrigin();
} }
@Override http.csrf()
protected void configure(HttpSecurity http) throws Exception { .disable()
.cors()
.and()
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS)
.permitAll()
.antMatchers("/graphiql")
.permitAll()
.antMatchers("/graphql")
.permitAll()
.antMatchers(HttpMethod.GET, "/articles/feed")
.authenticated()
.antMatchers(HttpMethod.POST, "/users", "/users/login")
.permitAll()
.antMatchers(HttpMethod.GET, "/articles/**", "/profiles/**", "/tags")
.permitAll()
.anyRequest()
.authenticated();
if (h2ConsoleEnabled) { http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests() }
.antMatchers("/h2-console", "/h2-console/**").permitAll()
.and()
.headers().frameOptions().sameOrigin();
}
http.csrf().disable() @Bean
.cors() public CorsConfigurationSource corsConfigurationSource() {
.and() final CorsConfiguration configuration = new CorsConfiguration();
.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) configuration.setAllowedOrigins(asList("*"));
.and() configuration.setAllowedMethods(asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"));
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() // setAllowCredentials(true) is important, otherwise:
.authorizeRequests() // The value of the 'Access-Control-Allow-Origin' header in the response must not be the
.antMatchers(HttpMethod.OPTIONS).permitAll() // wildcard '*' when the request's credentials mode is 'include'.
.antMatchers("/graphiql").permitAll() configuration.setAllowCredentials(false);
.antMatchers("/graphql").permitAll() // setAllowedHeaders is important! Without it, OPTIONS preflight request
.antMatchers(HttpMethod.GET, "/articles/feed").authenticated() // will fail with 403 Invalid CORS request
.antMatchers(HttpMethod.POST, "/users", "/users/login").permitAll() configuration.setAllowedHeaders(asList("Authorization", "Cache-Control", "Content-Type"));
.antMatchers(HttpMethod.GET, "/articles/**", "/profiles/**", "/tags").permitAll() final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
.anyRequest().authenticated(); source.registerCorsConfiguration("/**", configuration);
return source;
http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); }
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(asList("*"));
configuration.setAllowedMethods(asList("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
// setAllowCredentials(true) is important, otherwise:
// The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
configuration.setAllowCredentials(false);
// setAllowedHeaders is important! Without it, OPTIONS preflight request
// will fail with 403 Invalid CORS request
configuration.setAllowedHeaders(asList("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
} }

View File

@ -1,5 +1,7 @@
package io.spring.application; package io.spring.application;
import static java.util.stream.Collectors.toList;
import io.spring.application.data.ArticleData; import io.spring.application.data.ArticleData;
import io.spring.application.data.ArticleDataList; import io.spring.application.data.ArticleDataList;
import io.spring.application.data.ArticleFavoriteCount; import io.spring.application.data.ArticleFavoriteCount;
@ -7,126 +9,179 @@ import io.spring.core.user.User;
import io.spring.infrastructure.mybatis.readservice.ArticleFavoritesReadService; import io.spring.infrastructure.mybatis.readservice.ArticleFavoritesReadService;
import io.spring.infrastructure.mybatis.readservice.ArticleReadService; import io.spring.infrastructure.mybatis.readservice.ArticleReadService;
import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService; import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; 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.springframework.beans.factory.annotation.Autowired;
import static java.util.stream.Collectors.toList; import org.springframework.stereotype.Service;
@Service @Service
public class ArticleQueryService { public class ArticleQueryService {
private ArticleReadService articleReadService; private ArticleReadService articleReadService;
private UserRelationshipQueryService userRelationshipQueryService; private UserRelationshipQueryService userRelationshipQueryService;
private ArticleFavoritesReadService articleFavoritesReadService; private ArticleFavoritesReadService articleFavoritesReadService;
@Autowired @Autowired
public ArticleQueryService(ArticleReadService articleReadService, public ArticleQueryService(
UserRelationshipQueryService userRelationshipQueryService, ArticleReadService articleReadService,
ArticleFavoritesReadService articleFavoritesReadService) { UserRelationshipQueryService userRelationshipQueryService,
this.articleReadService = articleReadService; ArticleFavoritesReadService articleFavoritesReadService) {
this.userRelationshipQueryService = userRelationshipQueryService; this.articleReadService = articleReadService;
this.articleFavoritesReadService = articleFavoritesReadService; this.userRelationshipQueryService = userRelationshipQueryService;
this.articleFavoritesReadService = articleFavoritesReadService;
}
public Optional<ArticleData> findById(String id, User user) {
ArticleData articleData = articleReadService.findById(id);
if (articleData == null) {
return Optional.empty();
} else {
if (user != null) {
fillExtraInfo(id, user, articleData);
}
return Optional.of(articleData);
} }
}
public Optional<ArticleData> findById(String id, User user) { public Optional<ArticleData> findBySlug(String slug, User user) {
ArticleData articleData = articleReadService.findById(id); ArticleData articleData = articleReadService.findBySlug(slug);
if (articleData == null) { if (articleData == null) {
return Optional.empty(); return Optional.empty();
} else { } else {
if (user != null) { if (user != null) {
fillExtraInfo(id, user, articleData); fillExtraInfo(articleData.getId(), user, articleData);
} }
return Optional.of(articleData); return Optional.of(articleData);
}
} }
}
public Optional<ArticleData> findBySlug(String slug, User user) { public CursorPager<ArticleData> findRecentArticlesWithCursor(
ArticleData articleData = articleReadService.findBySlug(slug); String tag, String author, String favoritedBy, CursorPageParameter page, User currentUser) {
if (articleData == null) { List<String> articleIds =
return Optional.empty(); articleReadService.findArticlesWithCursor(tag, author, favoritedBy, page);
} else { if (articleIds.size() == 0) {
if (user != null) { return new CursorPager<>(new ArrayList<>(), page.getDirection(), false);
fillExtraInfo(articleData.getId(), user, articleData); } else {
} boolean hasExtra = articleIds.size() > page.getLimit();
return Optional.of(articleData); if (hasExtra) {
} articleIds.remove(page.getLimit());
}
if (!page.isNext()) {
Collections.reverse(articleIds);
}
List<ArticleData> articles = articleReadService.findArticles(articleIds);
fillExtraInfo(articles, currentUser);
return new CursorPager<>(articles, page.getDirection(), hasExtra);
} }
}
public ArticleDataList findRecentArticles(String tag, String author, String favoritedBy, Page page, User currentUser) { public CursorPager<ArticleData> findUserFeedWithCursor(User user, CursorPageParameter page) {
List<String> articleIds = articleReadService.queryArticles(tag, author, favoritedBy, page); List<String> followdUsers = userRelationshipQueryService.followedUsers(user.getId());
int articleCount = articleReadService.countArticle(tag, author, favoritedBy); if (followdUsers.size() == 0) {
if (articleIds.size() == 0) { return new CursorPager<>(new ArrayList<>(), page.getDirection(), false);
return new ArticleDataList(new ArrayList<>(), articleCount); } else {
} else { List<ArticleData> articles =
List<ArticleData> articles = articleReadService.findArticles(articleIds); articleReadService.findArticlesOfAuthorsWithCursor(followdUsers, page);
fillExtraInfo(articles, currentUser); boolean hasExtra = articles.size() > page.getLimit();
return new ArticleDataList(articles, articleCount); if (hasExtra) {
} articles.remove(page.getLimit());
}
if (!page.isNext()) {
Collections.reverse(articles);
}
fillExtraInfo(articles, user);
return new CursorPager<>(articles, page.getDirection(), hasExtra);
} }
}
private void fillExtraInfo(List<ArticleData> articles, User currentUser) { public ArticleDataList findRecentArticles(
setFavoriteCount(articles); String tag, String author, String favoritedBy, Page page, User currentUser) {
if (currentUser != null) { List<String> articleIds = articleReadService.queryArticles(tag, author, favoritedBy, page);
setIsFavorite(articles, currentUser); int articleCount = articleReadService.countArticle(tag, author, favoritedBy);
setIsFollowingAuthor(articles, currentUser); if (articleIds.size() == 0) {
} return new ArticleDataList(new ArrayList<>(), articleCount);
} else {
List<ArticleData> articles = articleReadService.findArticles(articleIds);
fillExtraInfo(articles, currentUser);
return new ArticleDataList(articles, articleCount);
} }
}
private void setIsFollowingAuthor(List<ArticleData> articles, User currentUser) { public ArticleDataList findUserFeed(User user, Page page) {
Set<String> followingAuthors = userRelationshipQueryService.followingAuthors( List<String> followdUsers = userRelationshipQueryService.followedUsers(user.getId());
if (followdUsers.size() == 0) {
return new ArticleDataList(new ArrayList<>(), 0);
} else {
List<ArticleData> articles = articleReadService.findArticlesOfAuthors(followdUsers, page);
fillExtraInfo(articles, user);
int count = articleReadService.countFeedSize(followdUsers);
return new ArticleDataList(articles, count);
}
}
private void fillExtraInfo(List<ArticleData> articles, User currentUser) {
setFavoriteCount(articles);
if (currentUser != null) {
setIsFavorite(articles, currentUser);
setIsFollowingAuthor(articles, currentUser);
}
}
private void setIsFollowingAuthor(List<ArticleData> articles, User currentUser) {
Set<String> followingAuthors =
userRelationshipQueryService.followingAuthors(
currentUser.getId(), currentUser.getId(),
articles.stream().map(articleData1 -> articleData1.getProfileData().getId()).collect(toList())); articles.stream()
articles.forEach(articleData -> { .map(articleData1 -> articleData1.getProfileData().getId())
if (followingAuthors.contains(articleData.getProfileData().getId())) { .collect(toList()));
articleData.getProfileData().setFollowing(true); articles.forEach(
} articleData -> {
if (followingAuthors.contains(articleData.getProfileData().getId())) {
articleData.getProfileData().setFollowing(true);
}
}); });
} }
private void setFavoriteCount(List<ArticleData> articles) { private void setFavoriteCount(List<ArticleData> articles) {
List<ArticleFavoriteCount> favoritesCounts = articleFavoritesReadService.articlesFavoriteCount(articles.stream().map(ArticleData::getId).collect(toList())); List<ArticleFavoriteCount> favoritesCounts =
Map<String, Integer> countMap = new HashMap<>(); articleFavoritesReadService.articlesFavoriteCount(
favoritesCounts.forEach(item -> { articles.stream().map(ArticleData::getId).collect(toList()));
countMap.put(item.getId(), item.getCount()); Map<String, Integer> countMap = new HashMap<>();
favoritesCounts.forEach(
item -> {
countMap.put(item.getId(), item.getCount());
}); });
articles.forEach(articleData -> articleData.setFavoritesCount(countMap.get(articleData.getId()))); articles.forEach(
} articleData -> articleData.setFavoritesCount(countMap.get(articleData.getId())));
}
private void setIsFavorite(List<ArticleData> articles, User currentUser) { private void setIsFavorite(List<ArticleData> articles, User currentUser) {
Set<String> favoritedArticles = articleFavoritesReadService.userFavorites(articles.stream().map(articleData -> articleData.getId()).collect(toList()), currentUser); Set<String> favoritedArticles =
articleFavoritesReadService.userFavorites(
articles.stream().map(articleData -> articleData.getId()).collect(toList()),
currentUser);
articles.forEach(articleData -> { articles.forEach(
if (favoritedArticles.contains(articleData.getId())) { articleData -> {
articleData.setFavorited(true); if (favoritedArticles.contains(articleData.getId())) {
} articleData.setFavorited(true);
}
}); });
} }
private void fillExtraInfo(String id, User user, ArticleData articleData) { private void fillExtraInfo(String id, User user, ArticleData articleData) {
articleData.setFavorited(articleFavoritesReadService.isUserFavorite(user.getId(), id)); articleData.setFavorited(articleFavoritesReadService.isUserFavorite(user.getId(), id));
articleData.setFavoritesCount(articleFavoritesReadService.articleFavoriteCount(id)); articleData.setFavoritesCount(articleFavoritesReadService.articleFavoriteCount(id));
articleData.getProfileData().setFollowing( articleData
.getProfileData()
.setFollowing(
userRelationshipQueryService.isUserFollowing( userRelationshipQueryService.isUserFollowing(
user.getId(), user.getId(), articleData.getProfileData().getId()));
articleData.getProfileData().getId())); }
}
public ArticleDataList findUserFeed(User user, Page page) {
List<String> followdUsers = userRelationshipQueryService.followedUsers(user.getId());
if (followdUsers.size() == 0) {
return new ArticleDataList(new ArrayList<>(), 0);
} else {
List<ArticleData> articles = articleReadService.findArticlesOfAuthors(followdUsers, page);
fillExtraInfo(articles, user);
int count = articleReadService.countFeedSize(followdUsers);
return new ArticleDataList(articles, count);
}
}
} }

View File

@ -1,49 +1,89 @@
package io.spring.application; package io.spring.application;
import io.spring.application.data.CommentData; import io.spring.application.data.CommentData;
import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.infrastructure.mybatis.readservice.CommentReadService; import io.spring.infrastructure.mybatis.readservice.CommentReadService;
import org.springframework.stereotype.Service; import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; 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.springframework.stereotype.Service;
@Service @Service
public class CommentQueryService { public class CommentQueryService {
private CommentReadService commentReadService; private CommentReadService commentReadService;
private UserRelationshipQueryService userRelationshipQueryService; private UserRelationshipQueryService userRelationshipQueryService;
public CommentQueryService(CommentReadService commentReadService, UserRelationshipQueryService userRelationshipQueryService) { public CommentQueryService(
this.commentReadService = commentReadService; CommentReadService commentReadService,
this.userRelationshipQueryService = userRelationshipQueryService; UserRelationshipQueryService userRelationshipQueryService) {
} this.commentReadService = commentReadService;
this.userRelationshipQueryService = userRelationshipQueryService;
}
public Optional<CommentData> findById(String id, User user) { public Optional<CommentData> findById(String id, User user) {
CommentData commentData = commentReadService.findById(id); CommentData commentData = commentReadService.findById(id);
if (commentData == null) { if (commentData == null) {
return Optional.empty(); return Optional.empty();
} else { } else {
commentData.getProfileData().setFollowing( commentData
userRelationshipQueryService.isUserFollowing( .getProfileData()
user.getId(), .setFollowing(
commentData.getProfileData().getId())); userRelationshipQueryService.isUserFollowing(
} user.getId(), commentData.getProfileData().getId()));
return Optional.ofNullable(commentData);
} }
return Optional.ofNullable(commentData);
}
public List<CommentData> findByArticleId(String articleId, User user) { public List<CommentData> findByArticleId(String articleId, User user) {
List<CommentData> comments = commentReadService.findByArticleId(articleId); List<CommentData> comments = commentReadService.findByArticleId(articleId);
if (comments.size() > 0 && user != null) { if (comments.size() > 0 && user != null) {
Set<String> followingAuthors = userRelationshipQueryService.followingAuthors(user.getId(), comments.stream().map(commentData -> commentData.getProfileData().getId()).collect(Collectors.toList())); Set<String> followingAuthors =
comments.forEach(commentData -> { userRelationshipQueryService.followingAuthors(
if (followingAuthors.contains(commentData.getProfileData().getId())) { user.getId(),
commentData.getProfileData().setFollowing(true); comments.stream()
} .map(commentData -> commentData.getProfileData().getId())
}); .collect(Collectors.toList()));
} comments.forEach(
return comments; commentData -> {
if (followingAuthors.contains(commentData.getProfileData().getId())) {
commentData.getProfileData().setFollowing(true);
}
});
} }
return comments;
}
public CursorPager<CommentData> findByArticleIdWithCursor(
String articleId, User user, CursorPageParameter page) {
List<CommentData> comments = commentReadService.findByArticleIdWithCursor(articleId, page);
if (comments.isEmpty()) {
return new CursorPager<>(new ArrayList<>(), page.getDirection(), false);
}
if (user != null) {
Set<String> followingAuthors =
userRelationshipQueryService.followingAuthors(
user.getId(),
comments.stream()
.map(commentData -> commentData.getProfileData().getId())
.collect(Collectors.toList()));
comments.forEach(
commentData -> {
if (followingAuthors.contains(commentData.getProfileData().getId())) {
commentData.getProfileData().setFollowing(true);
}
});
}
boolean hasExtra = comments.size() > page.getLimit();
if (hasExtra) {
comments.remove(page.getLimit());
}
if (!page.isNext()) {
Collections.reverse(comments);
}
return new CursorPager<>(comments, page.getDirection(), hasExtra);
}
} }

View File

@ -0,0 +1,44 @@
package io.spring.application;
import io.spring.application.CursorPager.Direction;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class CursorPageParameter {
private static final int MAX_LIMIT = 1000;
private int limit = 20;
private String cursor;
private Direction direction;
public CursorPageParameter(String cursor, int limit, Direction direction) {
setLimit(limit);
setCursor(cursor);
setDirection(direction);
}
public boolean isNext() {
return direction == Direction.NEXT;
}
public int getQueryLimit() {
return limit + 1;
}
private void setCursor(String cursor) {
if (cursor == null) {
this.cursor = "";
} else {
this.cursor = cursor;
}
}
private void setLimit(int limit) {
if (limit > MAX_LIMIT) {
this.limit = MAX_LIMIT;
} else if (limit > 0) {
this.limit = limit;
}
}
}

View File

@ -0,0 +1,44 @@
package io.spring.application;
import java.util.List;
import lombok.Getter;
@Getter
public class CursorPager<T extends Node> {
private List<T> data;
private boolean next;
private boolean previous;
public CursorPager(List<T> data, Direction direction, boolean hasExtra) {
this.data = data;
if (direction == Direction.NEXT) {
this.previous = false;
this.next = hasExtra;
} else {
this.next = false;
this.previous = hasExtra;
}
}
public boolean hasNext() {
return next;
}
public boolean hasPrevious() {
return previous;
}
public String getStartCursor() {
return data.isEmpty() ? "" : data.get(0).getCursor();
}
public String getEndCursor() {
return data.isEmpty() ? "" : data.get(data.size() - 1).getCursor();
}
public enum Direction {
PREV,
NEXT
}
}

View File

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

View File

@ -1,33 +1,31 @@
package io.spring.application; package io.spring.application;
import lombok.Data; import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@NoArgsConstructor @NoArgsConstructor
@Data @Data
@Getter
public class Page { public class Page {
private static final int MAX_LIMIT = 100; private static final int MAX_LIMIT = 100;
private int offset = 0; private int offset = 0;
private int limit = 20; private int limit = 20;
public Page(int offset, int limit) { public Page(int offset, int limit) {
setOffset(offset); setOffset(offset);
setLimit(limit); setLimit(limit);
} }
private void setOffset(int offset) { private void setOffset(int offset) {
if (offset > 0) { if (offset > 0) {
this.offset = offset; this.offset = offset;
}
} }
}
private void setLimit(int limit) { private void setLimit(int limit) {
if (limit > MAX_LIMIT) { if (limit > MAX_LIMIT) {
this.limit = MAX_LIMIT; this.limit = MAX_LIMIT;
} else if (limit > 0) { } else if (limit > 0) {
this.limit = limit; this.limit = limit;
}
} }
}
} }

View File

@ -1,28 +1,32 @@
package io.spring.application.data; package io.spring.application.data;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import java.util.List;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class ArticleData { public class ArticleData implements io.spring.application.Node {
private String id; private String id;
private String slug; private String slug;
private String title; private String title;
private String description; private String description;
private String body; private String body;
private boolean favorited; private boolean favorited;
private int favoritesCount; private int favoritesCount;
private DateTime createdAt; private DateTime createdAt;
private DateTime updatedAt; private DateTime updatedAt;
private List<String> tagList; private List<String> tagList;
@JsonProperty("author")
private ProfileData profileData;
}
@JsonProperty("author")
private ProfileData profileData;
@Override
public String getCursor() {
return String.valueOf(getUpdatedAt().getMillis());
}
}

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.Node;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -10,13 +11,18 @@ import org.joda.time.DateTime;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class CommentData { public class CommentData implements Node {
private String id; private String id;
private String body; private String body;
@JsonIgnore @JsonIgnore private String articleId;
private String articleId; private DateTime createdAt;
private DateTime createdAt; private DateTime updatedAt;
private DateTime updatedAt;
@JsonProperty("author") @JsonProperty("author")
private ProfileData profileData; private ProfileData profileData;
@Override
public String getCursor() {
return String.valueOf(createdAt.getMillis());
}
} }

View File

@ -1,25 +1,42 @@
package io.spring.infrastructure.mybatis.readservice; package io.spring.infrastructure.mybatis.readservice;
import io.spring.application.CursorPageParameter;
import io.spring.application.Page; import io.spring.application.Page;
import io.spring.application.data.ArticleData; import io.spring.application.data.ArticleData;
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 java.util.List;
@Mapper @Mapper
public interface ArticleReadService { public interface ArticleReadService {
ArticleData findById(@Param("id") String id); ArticleData findById(@Param("id") String id);
ArticleData findBySlug(@Param("slug") String slug); ArticleData findBySlug(@Param("slug") String slug);
List<String> queryArticles(@Param("tag") String tag, @Param("author") String author, @Param("favoritedBy") String favoritedBy, @Param("page") Page page); List<String> queryArticles(
@Param("tag") String tag,
@Param("author") String author,
@Param("favoritedBy") String favoritedBy,
@Param("page") Page page);
int countArticle(@Param("tag") String tag, @Param("author") String author, @Param("favoritedBy") String favoritedBy); int countArticle(
@Param("tag") String tag,
@Param("author") String author,
@Param("favoritedBy") String favoritedBy);
List<ArticleData> findArticles(@Param("articleIds") List<String> articleIds); List<ArticleData> findArticles(@Param("articleIds") List<String> articleIds);
List<ArticleData> findArticlesOfAuthors(@Param("authors") List<String> authors, @Param("page") Page page); List<ArticleData> findArticlesOfAuthors(
@Param("authors") List<String> authors, @Param("page") Page page);
int countFeedSize(@Param("authors") List<String> authors); List<ArticleData> findArticlesOfAuthorsWithCursor(
@Param("authors") List<String> authors, @Param("page") CursorPageParameter page);
int countFeedSize(@Param("authors") List<String> authors);
List<String> findArticlesWithCursor(
@Param("tag") String tag,
@Param("author") String author,
@Param("favoritedBy") String favoritedBy,
@Param("page") CursorPageParameter page);
} }

View File

@ -1,14 +1,17 @@
package io.spring.infrastructure.mybatis.readservice; package io.spring.infrastructure.mybatis.readservice;
import io.spring.application.CursorPageParameter;
import io.spring.application.data.CommentData; import io.spring.application.data.CommentData;
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 java.util.List;
@Mapper @Mapper
public interface CommentReadService { public interface CommentReadService {
CommentData findById(@Param("id") String id); CommentData findById(@Param("id") String id);
List<CommentData> findByArticleId(@Param("articleId") String articleId); List<CommentData> findByArticleId(@Param("articleId") String articleId);
List<CommentData> findByArticleIdWithCursor(
@Param("articleId") String articleId, @Param("page") CursorPageParameter page);
} }

View File

@ -0,0 +1 @@
spring.datasource.url=jdbc:sqlite::memory:

View File

@ -1,3 +1,7 @@
spring.datasource.url=jdbc:sqlite:dev.db
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.datasource.username=
spring.datasource.password=
spring.jackson.deserialization.UNWRAP_ROOT_VALUE=true spring.jackson.deserialization.UNWRAP_ROOT_VALUE=true
image.default=https://static.productionready.io/images/smiley-cyrus.jpg image.default=https://static.productionready.io/images/smiley-cyrus.jpg
jwt.secret=nRvyYC4soFxBdZ-F-5Nnzz5USXstR1YylsTd-mA0aKtI9HUlriGrtkf-TiuDapkLiUCogO3JOK7kwZisrHp6wA jwt.secret=nRvyYC4soFxBdZ-F-5Nnzz5USXstR1YylsTd-mA0aKtI9HUlriGrtkf-TiuDapkLiUCogO3JOK7kwZisrHp6wA
@ -9,5 +13,6 @@ mybatis.configuration.use-generated-keys=true
mybatis.type-handlers-package=io.spring.infrastructure.mybatis mybatis.type-handlers-package=io.spring.infrastructure.mybatis
mybatis.mapper-locations=mapper/*.xml mybatis.mapper-locations=mapper/*.xml
logging.level.io.spring.infrastructure.mybatis.readservice.ArticleReadService=DEBUG logging.level.io.spring.infrastructure.mybatis.readservice.ArticleReadService=DEBUG
logging.level.io.spring.infrastructure.mybatis.mapper=DEBUG
# Uncomment the following line to enable and allow access to the h2-console # Uncomment the following line to enable and allow access to the h2-console
#spring.h2.console.enabled=true #spring.h2.console.enabled=true

View File

@ -24,6 +24,17 @@
left join tags T on T.id = AT.tag_id left join tags T on T.id = AT.tag_id
left join users U on U.id = A.user_id left join users U on U.id = A.user_id
</sql> </sql>
<sql id="selectArticleIds">
select
DISTINCT(A.id) articleId, A.created_at
from
articles A
left join article_tags AT on A.id = AT.article_id
left join tags T on T.id = AT.tag_id
left join article_favorites AF on AF.article_id = A.id
left join users AU on AU.id = A.user_id
left join users AFU on AFU.id = AF.user_id
</sql>
<select id="findById" resultMap="transfer.data.articleData"> <select id="findById" resultMap="transfer.data.articleData">
<include refid="selectArticleData"/> <include refid="selectArticleData"/>
@ -34,15 +45,7 @@
where A.slug = #{slug} where A.slug = #{slug}
</select> </select>
<select id="queryArticles" resultMap="articleId"> <select id="queryArticles" resultMap="articleId">
select <include refid="selectArticleIds" />
DISTINCT(A.id) articleId, A.created_at
from
articles A
left join article_tags AT on A.id = AT.article_id
left join tags T on T.id = AT.tag_id
left join article_favorites AF on AF.article_id = A.id
left join users AU on AU.id = A.user_id
left join users AFU on AFU.id = AF.user_id
<where> <where>
<if test="tag != null"> <if test="tag != null">
T.name = #{tag} T.name = #{tag}
@ -101,6 +104,55 @@
#{id} #{id}
</foreach> </foreach>
</select> </select>
<select id="findArticlesWithCursor" resultType="java.lang.String">
<include refid="selectArticleIds" />
<where>
<if test="tag != null">
T.name = #{tag}
</if>
<if test="author != null">
AND AU.username = #{author}
</if>
<if test="favoritedBy != null">
AND AFU.username = #{favoritedBy}
</if>
<if test='page.cursor != "" and page.direction.name() == "NEXT"'>
AND A.created_at &lt; #{page.cursor}
</if>
<if test='page.cursor != "" and page.direction.name() == "PREV"'>
AND A.created_at > #{page.cursor}
</if>
</where>
<if test='page.direction.name() == "NEXT"'>
order by A.created_at desc
</if>
<if test='page.direction.name() == "PREV"'>
order by A.created_at asc
</if>
limit #{page.queryLimit}
</select>
<select id="findArticlesOfAuthorsWithCursor" resultMap="transfer.data.articleData">
<include refid="selectArticleData"/>
<where>
A.user_id in
<foreach index="index" collection="authors" item="id" open="(" separator="," close=")">
#{id}
</foreach>
<if test='page.cursor != "" and page.direction.name() == "NEXT"'>
AND A.created_at &lt; #{page.cursor}
</if>
<if test='page.cursor != "" and page.direction.name() == "PREV"'>
AND A.created_at > #{page.cursor}
</if>
<if test='page.direction.name() == "NEXT"'>
order by A.created_at desc
</if>
<if test='page.direction.name() == "PREV"'>
order by A.created_at asc
</if>
</where>
limit #{page.queryLimit}
</select>
<resultMap id="articleId" type="string"> <resultMap id="articleId" type="string">
<id javaType="string" column="articleId"/> <id javaType="string" column="articleId"/>

View File

@ -21,4 +21,22 @@
<include refid="selectCommentData"/> <include refid="selectCommentData"/>
where C.article_id = #{articleId} where C.article_id = #{articleId}
</select> </select>
<select id="findByArticleIdWithCursor" resultMap="transfer.data.commentData">
<include refid="selectCommentData"/>
<where>
C.article_id = #{articleId}
<if test='page.cursor != "" and page.direction.name() == "NEXT"'>
AND C.created_at &lt; #{page.cursor}
</if>
<if test='page.cursor != "" and page.direction.name() == "PREV"'>
AND C.created_at > #{page.cursor}
</if>
</where>
<if test='page.direction.name() == "NEXT"'>
order by C.created_at desc
</if>
<if test='page.direction.name() == "PREV"'>
order by C.created_at asc
</if>
</select>
</mapper> </mapper>

View File

@ -6,6 +6,9 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import io.spring.application.ArticleQueryService; 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.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;
@ -16,6 +19,7 @@ import io.spring.core.favorite.ArticleFavoriteRepository;
import io.spring.core.user.FollowRelation; import io.spring.core.user.FollowRelation;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
import io.spring.infrastructure.DbTestBase;
import io.spring.infrastructure.repository.MyBatisArticleFavoriteRepository; import io.spring.infrastructure.repository.MyBatisArticleFavoriteRepository;
import io.spring.infrastructure.repository.MyBatisArticleRepository; import io.spring.infrastructure.repository.MyBatisArticleRepository;
import io.spring.infrastructure.repository.MyBatisUserRepository; import io.spring.infrastructure.repository.MyBatisUserRepository;
@ -24,21 +28,16 @@ import java.util.Optional;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@MybatisTest
@Import({ @Import({
ArticleQueryService.class, ArticleQueryService.class,
MyBatisUserRepository.class, MyBatisUserRepository.class,
MyBatisArticleRepository.class, MyBatisArticleRepository.class,
MyBatisArticleFavoriteRepository.class MyBatisArticleFavoriteRepository.class
}) })
public class ArticleQueryServiceTest { public class ArticleQueryServiceTest extends DbTestBase {
@Autowired private ArticleQueryService queryService; @Autowired private ArticleQueryService queryService;
@Autowired private ArticleRepository articleRepository; @Autowired private ArticleRepository articleRepository;
@ -111,6 +110,40 @@ public class ArticleQueryServiceTest {
assertEquals(nodata.getArticleDatas().size(), 0); assertEquals(nodata.getArticleDatas().size(), 0);
} }
@Test
public void should_get_default_article_list_by_cursor() {
Article anotherArticle =
new Article(
"new article",
"desc",
"body",
Arrays.asList("test"),
user.getId(),
new DateTime().minusHours(1));
articleRepository.save(anotherArticle);
CursorPager<ArticleData> recentArticles =
queryService.findRecentArticlesWithCursor(
null, null, null, new CursorPageParameter("", 20, Direction.NEXT), user);
assertEquals(recentArticles.getData().size(), 2);
assertEquals(recentArticles.getData().get(0).getId(), article.getId());
CursorPager<ArticleData> nodata =
queryService.findRecentArticlesWithCursor(
null,
null,
null,
new CursorPageParameter(recentArticles.getEndCursor(), 20, Direction.NEXT),
user);
assertEquals(nodata.getData().size(), 0);
assertEquals(nodata.getStartCursor(), "");
CursorPager<ArticleData> prevArticles =
queryService.findRecentArticlesWithCursor(
null, null, null, new CursorPageParameter("", 20, Direction.PREV), user);
assertEquals(prevArticles.getData().size(), 2);
}
@Test @Test
public void should_query_article_by_author() { public void should_query_article_by_author() {
User anotherUser = new User("other@email.com", "other", "123", "", ""); User anotherUser = new User("other@email.com", "other", "123", "", "");

View File

@ -12,6 +12,7 @@ import io.spring.core.comment.CommentRepository;
import io.spring.core.user.FollowRelation; import io.spring.core.user.FollowRelation;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
import io.spring.infrastructure.DbTestBase;
import io.spring.infrastructure.repository.MyBatisArticleRepository; import io.spring.infrastructure.repository.MyBatisArticleRepository;
import io.spring.infrastructure.repository.MyBatisCommentRepository; import io.spring.infrastructure.repository.MyBatisCommentRepository;
import io.spring.infrastructure.repository.MyBatisUserRepository; import io.spring.infrastructure.repository.MyBatisUserRepository;
@ -20,21 +21,16 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
@MybatisTest
@RunWith(SpringRunner.class)
@Import({ @Import({
MyBatisCommentRepository.class, MyBatisCommentRepository.class,
MyBatisUserRepository.class, MyBatisUserRepository.class,
CommentQueryService.class, CommentQueryService.class,
MyBatisArticleRepository.class MyBatisArticleRepository.class
}) })
public class CommentQueryServiceTest { public class CommentQueryServiceTest extends DbTestBase {
@Autowired private CommentRepository commentRepository; @Autowired private CommentRepository commentRepository;
@Autowired private UserRepository userRepository; @Autowired private UserRepository userRepository;

View File

@ -1,37 +1,31 @@
package io.spring.application.profile; package io.spring.application.profile;
import static org.junit.Assert.assertTrue;
import io.spring.application.ProfileQueryService; import io.spring.application.ProfileQueryService;
import io.spring.application.data.ProfileData; import io.spring.application.data.ProfileData;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
import io.spring.infrastructure.DbTestBase;
import io.spring.infrastructure.repository.MyBatisUserRepository; import io.spring.infrastructure.repository.MyBatisUserRepository;
import java.util.Optional;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Optional;
import static org.junit.Assert.assertTrue;
@RunWith(SpringRunner.class)
@MybatisTest
@Import({ProfileQueryService.class, MyBatisUserRepository.class}) @Import({ProfileQueryService.class, MyBatisUserRepository.class})
public class ProfileQueryServiceTest { public class ProfileQueryServiceTest extends DbTestBase {
@Autowired @Autowired private ProfileQueryService profileQueryService;
private ProfileQueryService profileQueryService; @Autowired private UserRepository userRepository;
@Autowired
private UserRepository userRepository;
@Test @Test
public void should_fetch_profile_success() { public void should_fetch_profile_success() {
User currentUser = new User("a@test.com", "a", "123", "", ""); User currentUser = new User("a@test.com", "a", "123", "", "");
User profileUser = new User("p@test.com", "p", "123", "", ""); User profileUser = new User("p@test.com", "p", "123", "", "");
userRepository.save(profileUser); userRepository.save(profileUser);
Optional<ProfileData> optional = profileQueryService.findByUsername(profileUser.getUsername(), currentUser); Optional<ProfileData> optional =
assertTrue(optional.isPresent()); profileQueryService.findByUsername(profileUser.getUsername(), currentUser);
} assertTrue(optional.isPresent());
} }
}

View File

@ -5,19 +5,15 @@ import static org.junit.Assert.assertTrue;
import io.spring.application.TagsQueryService; import io.spring.application.TagsQueryService;
import io.spring.core.article.Article; import io.spring.core.article.Article;
import io.spring.core.article.ArticleRepository; import io.spring.core.article.ArticleRepository;
import io.spring.infrastructure.DbTestBase;
import io.spring.infrastructure.repository.MyBatisArticleRepository; import io.spring.infrastructure.repository.MyBatisArticleRepository;
import java.util.Arrays; import java.util.Arrays;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@MybatisTest
@Import({TagsQueryService.class, MyBatisArticleRepository.class}) @Import({TagsQueryService.class, MyBatisArticleRepository.class})
public class TagsQueryServiceTest { public class TagsQueryServiceTest extends DbTestBase {
@Autowired private TagsQueryService tagsQueryService; @Autowired private TagsQueryService tagsQueryService;
@Autowired private ArticleRepository articleRepository; @Autowired private ArticleRepository articleRepository;

View File

@ -0,0 +1,14 @@
package io.spring.infrastructure;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
@ActiveProfiles("test")
@AutoConfigureTestDatabase(replace = Replace.NONE)
@MybatisTest
@RunWith(SpringRunner.class)
public abstract class DbTestBase {}

View File

@ -13,11 +13,13 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class) @ActiveProfiles("test")
@SpringBootTest @SpringBootTest
@AutoConfigureTestDatabase @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@RunWith(SpringRunner.class)
public class ArticleRepositoryTransactionTest { public class ArticleRepositoryTransactionTest {
@Autowired private ArticleRepository articleRepository; @Autowired private ArticleRepository articleRepository;

View File

@ -10,22 +10,18 @@ import io.spring.core.article.ArticleRepository;
import io.spring.core.article.Tag; import io.spring.core.article.Tag;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
import io.spring.infrastructure.DbTestBase;
import io.spring.infrastructure.repository.MyBatisArticleRepository; import io.spring.infrastructure.repository.MyBatisArticleRepository;
import io.spring.infrastructure.repository.MyBatisUserRepository; import io.spring.infrastructure.repository.MyBatisUserRepository;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
@MybatisTest
@RunWith(SpringRunner.class)
@Import({MyBatisArticleRepository.class, MyBatisUserRepository.class}) @Import({MyBatisArticleRepository.class, MyBatisUserRepository.class})
public class MyBatisArticleRepositoryTest { public class MyBatisArticleRepositoryTest extends DbTestBase {
@Autowired private ArticleRepository articleRepository; @Autowired private ArticleRepository articleRepository;
@Autowired private UserRepository userRepository; @Autowired private UserRepository userRepository;

View File

@ -1,34 +1,28 @@
package io.spring.infrastructure.comment; package io.spring.infrastructure.comment;
import io.spring.core.comment.Comment;
import io.spring.core.comment.CommentRepository;
import io.spring.infrastructure.repository.MyBatisCommentRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Optional;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@MybatisTest import io.spring.core.comment.Comment;
@RunWith(SpringRunner.class) import io.spring.core.comment.CommentRepository;
import io.spring.infrastructure.DbTestBase;
import io.spring.infrastructure.repository.MyBatisCommentRepository;
import java.util.Optional;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
@Import({MyBatisCommentRepository.class}) @Import({MyBatisCommentRepository.class})
public class MyBatisCommentRepositoryTest { public class MyBatisCommentRepositoryTest extends DbTestBase {
@Autowired @Autowired private CommentRepository commentRepository;
private CommentRepository commentRepository;
@Test @Test
public void should_create_and_fetch_comment_success() { public void should_create_and_fetch_comment_success() {
Comment comment = new Comment("content", "123", "456"); Comment comment = new Comment("content", "123", "456");
commentRepository.save(comment); commentRepository.save(comment);
Optional<Comment> optional = commentRepository.findById("456", comment.getId()); Optional<Comment> optional = commentRepository.findById("456", comment.getId());
assertTrue(optional.isPresent()); assertTrue(optional.isPresent());
assertEquals(optional.get(), comment); assertEquals(optional.get(), comment);
} }
} }

View File

@ -1,40 +1,36 @@
package io.spring.infrastructure.favorite; package io.spring.infrastructure.favorite;
import io.spring.core.favorite.ArticleFavorite;
import io.spring.core.favorite.ArticleFavoriteRepository;
import io.spring.infrastructure.repository.MyBatisArticleFavoriteRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@RunWith(SpringRunner.class) import io.spring.core.favorite.ArticleFavorite;
@MybatisTest import io.spring.core.favorite.ArticleFavoriteRepository;
import io.spring.infrastructure.DbTestBase;
import io.spring.infrastructure.repository.MyBatisArticleFavoriteRepository;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
@Import({MyBatisArticleFavoriteRepository.class}) @Import({MyBatisArticleFavoriteRepository.class})
public class MyBatisArticleFavoriteRepositoryTest { public class MyBatisArticleFavoriteRepositoryTest extends DbTestBase {
@Autowired @Autowired private ArticleFavoriteRepository articleFavoriteRepository;
private ArticleFavoriteRepository articleFavoriteRepository;
@Autowired @Autowired
private io.spring.infrastructure.mybatis.mapper.ArticleFavoriteMapper articleFavoriteMapper; private io.spring.infrastructure.mybatis.mapper.ArticleFavoriteMapper articleFavoriteMapper;
@Test @Test
public void should_save_and_fetch_articleFavorite_success() { public void should_save_and_fetch_articleFavorite_success() {
ArticleFavorite articleFavorite = new ArticleFavorite("123", "456"); ArticleFavorite articleFavorite = new ArticleFavorite("123", "456");
articleFavoriteRepository.save(articleFavorite); articleFavoriteRepository.save(articleFavorite);
assertNotNull(articleFavoriteMapper.find(articleFavorite.getArticleId(), articleFavorite.getUserId())); assertNotNull(
} articleFavoriteMapper.find(articleFavorite.getArticleId(), articleFavorite.getUserId()));
}
@Test @Test
public void should_remove_favorite_success() { public void should_remove_favorite_success() {
ArticleFavorite articleFavorite = new ArticleFavorite("123", "456"); ArticleFavorite articleFavorite = new ArticleFavorite("123", "456");
articleFavoriteRepository.save(articleFavorite); articleFavoriteRepository.save(articleFavorite);
articleFavoriteRepository.remove(articleFavorite); articleFavoriteRepository.remove(articleFavorite);
assertFalse(articleFavoriteRepository.find("123", "456").isPresent()); assertFalse(articleFavoriteRepository.find("123", "456").isPresent());
} }
} }

View File

@ -1,82 +1,76 @@
package io.spring.infrastructure.user; package io.spring.infrastructure.user;
import io.spring.core.user.FollowRelation;
import io.spring.core.user.User;
import io.spring.core.user.UserRepository;
import io.spring.infrastructure.repository.MyBatisUserRepository;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Optional;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@RunWith(SpringRunner.class) import io.spring.core.user.FollowRelation;
@MybatisTest import io.spring.core.user.User;
import io.spring.core.user.UserRepository;
import io.spring.infrastructure.DbTestBase;
import io.spring.infrastructure.repository.MyBatisUserRepository;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
@Import(MyBatisUserRepository.class) @Import(MyBatisUserRepository.class)
public class MyBatisUserRepositoryTest { public class MyBatisUserRepositoryTest extends DbTestBase {
@Autowired @Autowired private UserRepository userRepository;
private UserRepository userRepository; private User user;
private User user;
@Before @Before
public void setUp() { public void setUp() {
user = new User("aisensiy@163.com", "aisensiy", "123", "", "default"); user = new User("aisensiy@163.com", "aisensiy", "123", "", "default");
} }
@Test @Test
public void should_save_and_fetch_user_success() { public void should_save_and_fetch_user_success() {
userRepository.save(user); userRepository.save(user);
Optional<User> userOptional = userRepository.findByUsername("aisensiy"); Optional<User> userOptional = userRepository.findByUsername("aisensiy");
assertEquals(userOptional.get(), user); assertEquals(userOptional.get(), user);
Optional<User> userOptional2 = userRepository.findByEmail("aisensiy@163.com"); Optional<User> userOptional2 = userRepository.findByEmail("aisensiy@163.com");
assertEquals(userOptional2.get(), user); assertEquals(userOptional2.get(), user);
} }
@Test @Test
public void should_update_user_success() { public void should_update_user_success() {
String newEmail = "newemail@email.com"; String newEmail = "newemail@email.com";
user.update(newEmail, "", "", "", ""); user.update(newEmail, "", "", "", "");
userRepository.save(user); userRepository.save(user);
Optional<User> optional = userRepository.findByUsername(user.getUsername()); Optional<User> optional = userRepository.findByUsername(user.getUsername());
assertTrue(optional.isPresent()); assertTrue(optional.isPresent());
assertEquals(optional.get().getEmail(), newEmail); assertEquals(optional.get().getEmail(), newEmail);
String newUsername = "newUsername"; String newUsername = "newUsername";
user.update("", newUsername, "", "", ""); user.update("", newUsername, "", "", "");
userRepository.save(user); userRepository.save(user);
optional = userRepository.findByEmail(user.getEmail()); optional = userRepository.findByEmail(user.getEmail());
assertTrue(optional.isPresent()); assertTrue(optional.isPresent());
assertEquals(optional.get().getUsername(), newUsername); assertEquals(optional.get().getUsername(), newUsername);
assertEquals(optional.get().getImage(), user.getImage()); assertEquals(optional.get().getImage(), user.getImage());
} }
@Test @Test
public void should_create_new_user_follow_success() { public void should_create_new_user_follow_success() {
User other = new User("other@example.com", "other", "123", "", ""); User other = new User("other@example.com", "other", "123", "", "");
userRepository.save(other); userRepository.save(other);
FollowRelation followRelation = new FollowRelation(user.getId(), other.getId()); FollowRelation followRelation = new FollowRelation(user.getId(), other.getId());
userRepository.saveRelation(followRelation); userRepository.saveRelation(followRelation);
assertTrue(userRepository.findRelation(user.getId(), other.getId()).isPresent()); assertTrue(userRepository.findRelation(user.getId(), other.getId()).isPresent());
} }
@Test @Test
public void should_unfollow_user_success() { public void should_unfollow_user_success() {
User other = new User("other@example.com", "other", "123", "", ""); User other = new User("other@example.com", "other", "123", "", "");
userRepository.save(other); userRepository.save(other);
FollowRelation followRelation = new FollowRelation(user.getId(), other.getId()); FollowRelation followRelation = new FollowRelation(user.getId(), other.getId());
userRepository.saveRelation(followRelation); userRepository.saveRelation(followRelation);
userRepository.removeRelation(followRelation); userRepository.removeRelation(followRelation);
assertFalse(userRepository.findRelation(user.getId(), other.getId()).isPresent()); assertFalse(userRepository.findRelation(user.getId(), other.getId()).isPresent());
} }
} }