Commit 19daf2e5 by wzy

article

parent ba74b150
......@@ -22,6 +22,7 @@
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
......@@ -29,6 +30,11 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
......@@ -55,26 +61,24 @@
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.6</version>
</dependency>
</dependencies>
<build>
......
......@@ -2,7 +2,6 @@ package com.wzy.tacocloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
@SpringBootApplication
public class TacoCloudApplication {
......
package com.wzy.tacocloud.application;
import io.spring.application.data.ArticleData;
import io.spring.application.data.ArticleDataList;
import io.spring.application.data.ArticleFavoriteCount;
import io.spring.core.user.User;
import io.spring.infrastructure.mybatis.readservice.ArticleFavoritesReadService;
import io.spring.infrastructure.mybatis.readservice.ArticleReadService;
import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import static java.util.stream.Collectors.toList;
@Service
public class ArticleQueryService {
private final ArticleReadService articleReadService;
private final UserRelationshipQueryService userRelationshipQueryService;
private final ArticleFavoritesReadService articleFavoritesReadService;
@Autowired
public ArticleQueryService(
ArticleReadService articleReadService,
UserRelationshipQueryService userRelationshipQueryService,
ArticleFavoritesReadService articleFavoritesReadService) {
this.articleReadService = articleReadService;
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> findBySlug(String slug, User user) {
ArticleData articleData = articleReadService.findBySlug(slug);
if (articleData == null) {
return Optional.empty();
} else {
if (user != null) {
fillExtraInfo(articleData.getId(), user, articleData);
}
return Optional.of(articleData);
}
}
public CursorPager<ArticleData> findRecentArticlesWithCursor(
String tag,
String author,
String favoritedBy,
CursorPageParameter<DateTime> page,
User currentUser) {
List<String> articleIds =
articleReadService.findArticlesWithCursor(tag, author, favoritedBy, page);
if (articleIds.size() == 0) {
return new CursorPager<>(new ArrayList<>(), page.getDirection(), false);
} else {
boolean hasExtra = articleIds.size() > page.getLimit();
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 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);
} else {
List<ArticleData> articles =
articleReadService.findArticlesOfAuthorsWithCursor(followdUsers, page);
boolean hasExtra = articles.size() > page.getLimit();
if (hasExtra) {
articles.remove(page.getLimit());
}
if (!page.isNext()) {
Collections.reverse(articles);
}
fillExtraInfo(articles, user);
return new CursorPager<>(articles, page.getDirection(), hasExtra);
}
}
public ArticleDataList findRecentArticles(
String tag, String author, String favoritedBy, Page page, User currentUser) {
List<String> articleIds = articleReadService.queryArticles(tag, author, favoritedBy, page);
int articleCount = articleReadService.countArticle(tag, author, favoritedBy);
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);
}
}
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);
}
}
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(),
articles.stream()
.map(articleData1 -> articleData1.getProfileData().getId())
.collect(toList()));
articles.forEach(
articleData -> {
if (followingAuthors.contains(articleData.getProfileData().getId())) {
articleData.getProfileData().setFollowing(true);
}
});
}
private void setFavoriteCount(List<ArticleData> articles) {
List<ArticleFavoriteCount> favoritesCounts =
articleFavoritesReadService.articlesFavoriteCount(
articles.stream().map(ArticleData::getId).collect(toList()));
Map<String, Integer> countMap = new HashMap<>();
favoritesCounts.forEach(
item -> countMap.put(item.getId(), item.getCount()));
articles.forEach(
articleData -> articleData.setFavoritesCount(countMap.get(articleData.getId())));
}
private void setIsFavorite(List<ArticleData> articles, User currentUser) {
Set<String> favoritedArticles =
articleFavoritesReadService.userFavorites(
articles.stream().map(ArticleData::getId).collect(toList()),
currentUser);
articles.forEach(
articleData -> {
if (favoritedArticles.contains(articleData.getId())) {
articleData.setFavorited(true);
}
});
}
private void fillExtraInfo(String id, User user, ArticleData articleData) {
articleData.setFavorited(articleFavoritesReadService.isUserFavorite(user.getId(), id));
articleData.setFavoritesCount(articleFavoritesReadService.articleFavoriteCount(id));
articleData
.getProfileData()
.setFollowing(
userRelationshipQueryService.isUserFollowing(
user.getId(), articleData.getProfileData().getId()));
}
}
package com.wzy.tacocloud.application.article;
import io.spring.core.article.Article;
import io.spring.core.article.ArticleRepository;
import io.spring.core.user.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
@Service
@Validated
public class ArticleCommandService {
private final ArticleRepository articleRepository;
@Autowired
public ArticleCommandService(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}
public Article createArticle(@Valid NewArticleParam newArticleParam, User creator) {
Article article =
new Article(
newArticleParam.getTitle(),
newArticleParam.getDescription(),
newArticleParam.getBody(),
newArticleParam.getTagList(),
creator.getId());
articleRepository.save(article);
return article;
}
public Article updateArticle(Article article, @Valid UpdateArticleParam updateArticleParam) {
article.update(
updateArticleParam.getTitle(),
updateArticleParam.getDescription(),
updateArticleParam.getBody());
articleRepository.save(article);
return article;
}
}
package com.wzy.tacocloud.application.article;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = DuplicatedArticleValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DuplicatedArticleConstraint {
String message() default "article name exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package com.wzy.tacocloud.application.article;
import io.spring.application.ArticleQueryService;
import io.spring.core.article.Article;
import org.springframework.beans.factory.annotation.Autowired;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
class DuplicatedArticleValidator
implements ConstraintValidator<io.spring.application.article.DuplicatedArticleConstraint, String> {
@Autowired private ArticleQueryService articleQueryService;
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return !articleQueryService.findBySlug(Article.toSlug(value), null).isPresent();
}
}
package com.wzy.tacocloud.application.article;
import com.fasterxml.jackson.annotation.JsonRootName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import java.util.List;
@Getter
@JsonRootName("article")
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class NewArticleParam {
@NotBlank(message = "can't be empty")
@io.spring.application.article.DuplicatedArticleConstraint
private String title;
@NotBlank(message = "can't be empty")
private String description;
@NotBlank(message = "can't be empty")
private String body;
private List<String> tagList;
}
package com.wzy.tacocloud.application.article;
import com.fasterxml.jackson.annotation.JsonRootName;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@JsonRootName("article")
public class UpdateArticleParam {
private final String title = "";
private final String body = "";
private final String description = "";
}
......@@ -6,7 +6,6 @@ import com.wzy.tacocloud.core.user.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
@Service
......
package com.wzy.tacocloud.controller;
import io.spring.api.exception.NoAuthorizationException;
import io.spring.api.exception.ResourceNotFoundException;
import io.spring.application.ArticleQueryService;
import io.spring.application.article.ArticleCommandService;
import io.spring.application.article.UpdateArticleParam;
import io.spring.application.data.ArticleData;
import io.spring.core.article.ArticleRepository;
import io.spring.core.service.AuthorizationService;
import io.spring.core.user.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping(path = "/articles/{slug}")
public class ArticleController {
private final ArticleQueryService articleQueryService;
private final ArticleRepository articleRepository;
private final ArticleCommandService articleCommandService;
@Autowired
public ArticleController(
ArticleQueryService articleQueryService,
ArticleRepository articleRepository,
ArticleCommandService articleCommandService) {
this.articleQueryService = articleQueryService;
this.articleRepository = articleRepository;
this.articleCommandService = articleCommandService;
}
@GetMapping
public ResponseEntity<?> article(
@PathVariable("slug") String slug, @AuthenticationPrincipal User user) {
return articleQueryService
.findBySlug(slug, user)
.map(articleData -> ResponseEntity.ok(articleResponse(articleData)))
.orElseThrow(ResourceNotFoundException::new);
}
@PutMapping
public ResponseEntity<?> updateArticle(
@PathVariable("slug") String slug,
@AuthenticationPrincipal User user,
@Valid @RequestBody UpdateArticleParam updateArticleParam) {
return articleRepository
.findBySlug(slug)
.map(
article -> {
if (!AuthorizationService.canWriteArticle(user, article)) {
throw new NoAuthorizationException();
}
articleCommandService.updateArticle(article, updateArticleParam);
return ResponseEntity.ok(
articleResponse(articleQueryService.findBySlug(slug, user).orElse(null)));
})
.orElseThrow(ResourceNotFoundException::new);
}
@DeleteMapping
public ResponseEntity<?> deleteArticle(
@PathVariable("slug") String slug, @AuthenticationPrincipal User user) {
return articleRepository
.findBySlug(slug)
.map(
article -> {
if (!AuthorizationService.canWriteArticle(user, article)) {
throw new NoAuthorizationException();
}
articleRepository.remove(article);
return ResponseEntity.noContent().build();
})
.orElseThrow(ResourceNotFoundException::new);
}
private Map<String, Object> articleResponse(ArticleData articleData) {
return new HashMap<String, Object>() {
{
put("article", articleData);
}
};
}
}
package com.wzy.tacocloud.controller;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.wzy.tacocloud.application.UserQueryService;
import com.wzy.tacocloud.application.data.UserData;
import com.wzy.tacocloud.application.data.UserWithToken;
import com.wzy.tacocloud.application.user.RegisterParam;
import com.wzy.tacocloud.application.user.UserService;
import com.wzy.tacocloud.controller.exception.InvalidAuthenticationException;
import com.wzy.tacocloud.core.service.JwtService;
import com.wzy.tacocloud.core.user.EncryptService;
import com.wzy.tacocloud.core.user.User;
import com.wzy.tacocloud.application.user.UserService;
import com.wzy.tacocloud.core.user.UserRepository;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import javax.validation.Valid;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@RestController
public class UserController {
// private final UserRepository userRepository;
private final UserRepository userRepository;
private final UserService userService;
private final UserQueryService userQueryService;
private final JwtService jwtService;
private final EncryptService encryptService;
@Autowired
public UserController(UserService userService, UserQueryService userQueryService, JwtService jwtService) {
// this.userRepository = userRepository;
public UserController(UserRepository userRepository, UserService userService,
UserQueryService userQueryService, JwtService jwtService,
EncryptService encryptService) {
this.userRepository = userRepository;
this.userService = userService;
this.userQueryService = userQueryService;
this.jwtService = jwtService;
this.encryptService = encryptService;
}
@RequestMapping(path = "/register", method = POST)
......@@ -39,12 +51,40 @@ public class UserController {
User user = userService.createUser(registerParam);
UserData userData = userQueryService.findById(user.getId()).orElse(null);
assert userData != null;
return ResponseEntity.status(201).body(userResponse(new UserWithToken(userData, jwtService.toToken(user))));
return ResponseEntity.status(201)
.body(userResponse(new UserWithToken(userData, jwtService.toToken(user))));
}
@RequestMapping(path = "/login", method = POST)
public ResponseEntity<?> userLogin(@Valid @RequestBody LoginParam loginParam) {
Optional<User> optional = userRepository.findByUsername(loginParam.getUsername());
if (optional.isPresent() // 存在
&& encryptService.check(loginParam.getPassword(), optional.get().getPassword())) {
UserData userData = userQueryService.findById(optional.get().getId()).orElse(null);
assert userData != null;
return ResponseEntity.ok(
userResponse(new UserWithToken(userData, jwtService.toToken(optional.get()))));
} else {
throw new InvalidAuthenticationException();
}
}
private Map<String, Object> userResponse(UserWithToken userWithToken) {
return new HashMap<String, Object>() {
{put("user", userWithToken);}
{
put("user", userWithToken);
}
};
}
}
@Getter
@JsonRootName("user")
@NoArgsConstructor
class LoginParam {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
\ No newline at end of file
package com.wzy.tacocloud.controller.exception;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
@RestControllerAdvice
public class CustomizeExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({InvalidRequestException.class})
public ResponseEntity<Object> handleInvalidRequest(RuntimeException e, WebRequest request) {
InvalidRequestException ire = (InvalidRequestException) e;
List<FieldErrorResource> errorResources =
ire.getErrors().getFieldErrors().stream()
.map(
fieldError ->
new FieldErrorResource(
fieldError.getObjectName(),
fieldError.getField(),
fieldError.getCode(),
fieldError.getDefaultMessage()))
.collect(Collectors.toList());
ErrorResource error = new ErrorResource(errorResources);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(e, error, headers, UNPROCESSABLE_ENTITY, request);
}
@ExceptionHandler(InvalidAuthenticationException.class)
public ResponseEntity<Object> handleInvalidAuthentication(
InvalidAuthenticationException e, WebRequest request) {
return ResponseEntity.status(UNPROCESSABLE_ENTITY)
.body(
new HashMap<String, Object>() {
{
put("message", e.getMessage());
}
});
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException e,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
List<FieldErrorResource> errorResources =
e.getBindingResult().getFieldErrors().stream()
.map(
fieldError ->
new FieldErrorResource(
fieldError.getObjectName(),
fieldError.getField(),
fieldError.getCode(),
fieldError.getDefaultMessage()))
.collect(Collectors.toList());
return ResponseEntity.status(UNPROCESSABLE_ENTITY).body(new ErrorResource(errorResources));
}
@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(UNPROCESSABLE_ENTITY)
@ResponseBody
public ErrorResource handleConstraintViolation(
ConstraintViolationException ex, WebRequest request) {
List<FieldErrorResource> errors = new ArrayList<>();
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
FieldErrorResource fieldErrorResource =
new FieldErrorResource(
violation.getRootBeanClass().getName(),
getParam(violation.getPropertyPath().toString()),
violation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(),
violation.getMessage());
errors.add(fieldErrorResource);
}
return new ErrorResource(errors);
}
private String getParam(String s) {
String[] splits = s.split("\\.");
if (splits.length == 1) {
return s;
} else {
return String.join(".", Arrays.copyOfRange(splits, 2, splits.length));
}
}
}
package com.wzy.tacocloud.controller.exception;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.List;
@JsonSerialize(using = ErrorResourceSerializer.class)
@JsonIgnoreProperties(ignoreUnknown = true)
@lombok.Getter
@JsonRootName("errors")
public class ErrorResource {
private List<FieldErrorResource> fieldErrors;
public ErrorResource(List<FieldErrorResource> fieldErrorResources) {
this.fieldErrors = fieldErrorResources;
}
}
package com.wzy.tacocloud.controller.exception;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ErrorResourceSerializer extends JsonSerializer<ErrorResource> {
@Override
public void serialize(ErrorResource value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
Map<String, List<String>> json = new HashMap<>();
gen.writeStartObject();
gen.writeObjectFieldStart("errors");
for (FieldErrorResource fieldErrorResource : value.getFieldErrors()) {
if (!json.containsKey(fieldErrorResource.getField())) {
json.put(fieldErrorResource.getField(), new ArrayList<String>());
}
json.get(fieldErrorResource.getField()).add(fieldErrorResource.getMessage());
}
for (Map.Entry<String, List<String>> pair : json.entrySet()) {
gen.writeArrayFieldStart(pair.getKey());
pair.getValue().forEach(content -> {
try {
gen.writeString(content);
} catch (IOException e) {
e.printStackTrace();
}
});
gen.writeEndArray();
}
gen.writeEndObject();
gen.writeEndObject();
}
}
package com.wzy.tacocloud.controller.exception;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
public class FieldErrorResource {
private String resource;
private String field;
private String code;
private String message;
public FieldErrorResource(String resource, String field, String code, String message) {
this.resource = resource;
this.field = field;
this.code = code;
this.message = message;
}
}
package com.wzy.tacocloud.controller.exception;
public class InvalidAuthenticationException extends RuntimeException {
public InvalidAuthenticationException() {
super("用户名或密码错误");
}
}
package com.wzy.tacocloud.controller.exception;
import org.springframework.validation.Errors;
@SuppressWarnings("serial")
public class InvalidRequestException extends RuntimeException {
private final Errors errors;
public InvalidRequestException(Errors errors) {
super("");
this.errors = errors;
}
public Errors getErrors() {
return errors;
}
}
package com.wzy.tacocloud.core.article;
import io.spring.Util;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.joda.time.DateTime;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import static java.util.stream.Collectors.toList;
@Getter
@NoArgsConstructor
@EqualsAndHashCode(of = {"id"})
public class Article {
private String userId;
private String id;
private String slug;
private String title;
private String description;
private String body;
private DateTime createdAt;
private DateTime updatedAt;
public Article(
String title, String description, String body, List<String> tagList, String userId) {
this(title, description, body, tagList, userId, new DateTime());
}
public Article(
String title,
String description,
String body,
List<String> tagList,
String userId,
DateTime createdAt) {
this.id = UUID.randomUUID().toString();
this.slug = toSlug(title);
this.title = title;
this.description = description;
this.body = body;
this.userId = userId;
this.createdAt = createdAt;
this.updatedAt = createdAt;
}
public void update(String title, String description, String body) {
if (!Util.isEmpty(title)) {
this.title = title;
this.slug = toSlug(title);
this.updatedAt = new DateTime();
}
if (!Util.isEmpty(description)) {
this.description = description;
this.updatedAt = new DateTime();
}
if (!Util.isEmpty(body)) {
this.body = body;
this.updatedAt = new DateTime();
}
}
public static String toSlug(String title) {
return title.toLowerCase().replaceAll("[&|\\uFE30-\\uFFA0’”\\s?,.]+", "-");
}
}
package com.wzy.tacocloud.core.article;
import java.util.Optional;
public interface ArticleRepository {
void save(Article article);
Optional<Article> findById(String id);
Optional<Article> findBySlug(String slug);
void remove(Article article);
}
package com.wzy.tacocloud.infrastructure.mybatis.mapper;
import io.spring.core.article.Article;
import io.spring.core.article.Tag;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface ArticleMapper {
void insert(@Param("article") Article article);
Article findById(@Param("id") String id);
void insertArticleTagRelation(@Param("articleId") String articleId, @Param("tagId") String tagId);
Article findBySlug(@Param("slug") String slug);
void update(@Param("article") Article article);
void delete(@Param("id") String id);
}
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/taco_cloud?characterEncoding=utf8&useUnicode=true
username: root
......
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="io.spring.infrastructure.mybatis.mapper.ArticleMapper">
<insert id="insert">
insert into articles(id, slug, title, description, body, user_id, created_at, updated_at)
values(
#{article.id},
#{article.slug},
#{article.title},
#{article.description},
#{article.body},
#{article.userId},
#{article.createdAt},
#{article.updatedAt})
</insert>
<update id="update">
update articles
<set>
<if test="article.title != ''">title = #{article.title},</if>
<if test="article.title != ''">slug = #{article.slug},</if>
<if test="article.description != ''">description = #{article.description},</if>
<if test="article.body != ''">body = #{article.body}</if>
</set>
where id = #{article.id}
</update>
<delete id="delete">
delete from articles where id = #{id}
</delete>
<sql id="selectArticle">
select
A.id articleId,
A.slug articleSlug,
A.title articleTitle,
A.description articleDescription,
A.body articleBody,
A.user_id articleUserId,
A.created_at articleCreatedAt,
A.updated_at articleUpdatedAt
from articles A
</sql>
<select id="findById" resultMap="article">
<include refid="selectArticle"/>
where A.id = #{id}
</select>
<select id="findBySlug" resultMap="article">
<include refid="selectArticle"/>
where A.slug = #{slug}
</select>
<resultMap id="article" type="io.spring.core.article.Article">
<id column="articleId" property="id"/>
<result column="articleUserId" property="userId"/>
<result column="articleTitle" property="title"/>
<result column="articleSlug" property="slug"/>
<result column="articleDescription" property="description"/>
<result column="articleBody" property="body"/>
<result column="articleCreatedAt" property="createdAt"/>
<result column="articleUpdatedAt" property="updatedAt"/>
<collection property="tags" ofType="arraylist" resultMap="tag"/>
</resultMap>
<resultMap id="tag" type="io.spring.core.article.Tag">
<id column="tagId" property="id"/>
<result column="tagName" property="name"/>
</resultMap>
</mapper>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment