1. Обзор
В этой части тематического исследования приложения Reddit мы начнем отслеживать историю попыток отправки сообщения и сделаем статусы более описательными и простыми для понимания.
2. Совершенствование структуры почты
Во-первых, давайте начнем с замены старого статуса строки в сущности Post гораздо более полным списком ответов на отправку, отслеживая гораздо больше информации:
public class Post { ... @OneToMany(fetch = FetchType.EAGER, mappedBy = "post") private ListsubmissionsResponse; }
Далее, давайте посмотрим, что мы на самом деле отслеживаем в этой новой сущности ответа на отправку:
@Entity public class SubmissionResponse implements IEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private int attemptNumber; private String content; private Date submissionDate; private Date scoreCheckDate; @JsonIgnore @ManyToOne @JoinColumn(name = "post_id", nullable = false) private Post post; public SubmissionResponse(int attemptNumber, String content, Post post) { super(); this.attemptNumber = attemptNumber; this.content = content; this.submissionDate = new Date(); this.post = post; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Attempt No ").append(attemptNumber).append(" : ").append(content); return builder.toString(); } }
Далее, давайте посмотрим, что мы на самом деле отслеживаем в этой новой сущности ответа на отправку:
- Далее, давайте посмотрим, что мы на самом деле отслеживаем в этой новой сущности ответа на отправку: Далее, давайте посмотрим, что мы на самом деле отслеживаем в этой новой сущности ответа на отправку:
- Далее, давайте посмотрим, что мы на самом деле отслеживаем в этой новой сущности ответа на отправку: Далее, давайте посмотрим, что мы на самом деле отслеживаем в этой новой сущности ответа на отправку:
Далее, давайте посмотрим, что мы на самом деле отслеживаем в этой новой сущности ответа на отправку:
public interface SubmissionResponseRepository extends JpaRepository{ SubmissionResponse findOneByPostAndAttemptNumber(Post post, int attemptNumber); }
3. Служба планирования
Теперь нам нужно начать изменять уровень обслуживания, чтобы отслеживать эту дополнительную информацию.
Сначала мы убедимся, что у нас есть хорошо отформатированные причины успеха или неудачи, по которым сообщение считалось успешным или неудачным:
private final static String SCORE_TEMPLATE = "score %d %s minimum score %d"; private final static String TOTAL_VOTES_TEMPLATE = "total votes %d %s minimum total votes %d"; protected String getFailReason(Post post, PostScores postScores) { StringBuilder builder = new StringBuilder(); builder.append("Failed because "); builder.append(String.format( SCORE_TEMPLATE, postScores.getScore(), "<", post.getMinScoreRequired())); if (post.getMinTotalVotes() > 0) { builder.append(" and "); builder.append(String.format(TOTAL_VOTES_TEMPLATE, postScores.getTotalVotes(), "<", post.getMinTotalVotes())); } if (post.isKeepIfHasComments()) { builder.append(" and has no comments"); } return builder.toString(); } protected String getSuccessReason(Post post, PostScores postScores) { StringBuilder builder = new StringBuilder(); if (postScores.getScore() >= post.getMinScoreRequired()) { builder.append("Succeed because "); builder.append(String.format(SCORE_TEMPLATE, postScores.getScore(), ">=", post.getMinScoreRequired())); return builder.toString(); } if ( (post.getMinTotalVotes() > 0) && (postScores.getTotalVotes() >= post.getMinTotalVotes()) ) { builder.append("Succeed because "); builder.append(String.format(TOTAL_VOTES_TEMPLATE, postScores.getTotalVotes(), ">=", post.getMinTotalVotes())); return builder.toString(); } return "Succeed because has comments"; }
Теперь мы улучшим старую логику и будем отслеживать эту дополнительную историческую информацию :
private void submitPost(...) { ... if (errorNode == null) { post.setSubmissionsResponse(addAttemptResponse(post, "Submitted to Reddit")); ... } else { post.setSubmissionsResponse(addAttemptResponse(post, errorNode.toString())); ... } } private void checkAndReSubmit(Post post) { if (didIntervalPass(...)) { PostScores postScores = getPostScores(post); if (didPostGoalFail(post, postScores)) { ... resetPost(post, getFailReason(post, postScores)); } else { ... updateLastAttemptResponse( post, "Post reached target score successfully " + getSuccessReason(post, postScores)); } } } private void checkAndDeleteInternal(Post post) { if (didIntervalPass(...)) { PostScores postScores = getPostScores(post); if (didPostGoalFail(post, postScores)) { updateLastAttemptResponse(post, "Deleted from reddit, consumed all attempts without reaching score " + getFailReason(post, postScores)); ... } else { updateLastAttemptResponse(post, "Post reached target score successfully " + getSuccessReason(post, postScores)); ... } } } private void resetPost(Post post, String failReason) { ... updateLastAttemptResponse(post, "Deleted from Reddit, to be resubmitted " + failReason); ... }
Обратите внимание, что методы более низкого уровня на самом деле делают:
- add Attempt Response() : создает новую запись Submission Response и добавляет ее в сообщение (вызывается при каждой попытке отправки)
- обновить ответ на последнюю попытку() : обновить ответ на последнюю попытку (вызывается при проверке оценки поста)
4. Запланированный пост DTO
Затем мы изменим DTO, чтобы убедиться, что эта новая информация будет предоставлена клиенту:
public class ScheduledPostDto { ... private String status; private ListdetailedStatus; }
И вот простые Ответы на отправку :
public class SubmissionResponseDto { private int attemptNumber; private String content; private String localSubmissionDate; private String localScoreCheckDate; }
Мы также изменим метод преобразования в нашем Запланированном постконтроллере :
private ScheduledPostDto convertToDto(Post post) { ... Listresponse = post.getSubmissionsResponse(); if ((response != null) && (response.size() > 0)) { postDto.setStatus(response.get(response.size() - 1).toString().substring(0, 30)); List responsedto = post.getSubmissionsResponse().stream(). map(res -> generateResponseDto(res)).collect(Collectors.toList()); postDto.setDetailedStatus(responsedto); } else { postDto.setStatus("Not sent yet"); postDto.setDetailedStatus(Collections.emptyList()); } return postDto; } private SubmissionResponseDto generateResponseDto(SubmissionResponse responseEntity) { SubmissionResponseDto dto = modelMapper.map(responseEntity, SubmissionResponseDto.class); String timezone = userService.getCurrentUser().getPreference().getTimezone(); dto.setLocalSubmissionDate(responseEntity.getSubmissionDate(), timezone); if (responseEntity.getScoreCheckDate() != null) { dto.setLocalScoreCheckDate(responseEntity.getScoreCheckDate(), timezone); } return dto; }
5. Передний Конец
Затем мы изменим наш интерфейс запланированные сообщения.jsp для обработки нашего нового ответа:
Detailed Status
6. Тесты
Наконец, мы проведем простой модульный тест на наших новых методах:
Во-первых, мы проверим реализацию get Success Reason() :
@Test public void whenHasEnoughScore_thenSucceed() { Post post = new Post(); post.setMinScoreRequired(5); PostScores postScores = new PostScores(6, 10, 1); assertTrue(getSuccessReason(post, postScores).contains("Succeed because score")); } @Test public void whenHasEnoughTotalVotes_thenSucceed() { Post post = new Post(); post.setMinScoreRequired(5); post.setMinTotalVotes(8); PostScores postScores = new PostScores(2, 10, 1); assertTrue(getSuccessReason(post, postScores).contains("Succeed because total votes")); } @Test public void givenKeepPostIfHasComments_whenHasComments_thenSucceed() { Post post = new Post(); post.setMinScoreRequired(5); post.setKeepIfHasComments(true); final PostScores postScores = new PostScores(2, 10, 1); assertTrue(getSuccessReason(post, postScores).contains("Succeed because has comments")); }
Далее мы протестируем реализацию getFailReason() :
@Test public void whenNotEnoughScore_thenFail() { Post post = new Post(); post.setMinScoreRequired(5); PostScores postScores = new PostScores(2, 10, 1); assertTrue(getFailReason(post, postScores).contains("Failed because score")); } @Test public void whenNotEnoughTotalVotes_thenFail() { Post post = new Post(); post.setMinScoreRequired(5); post.setMinTotalVotes(15); PostScores postScores = new PostScores(2, 10, 1); String reason = getFailReason(post, postScores); assertTrue(reason.contains("Failed because score")); assertTrue(reason.contains("and total votes")); } @Test public void givenKeepPostIfHasComments_whenNotHasComments_thenFail() { Post post = new Post(); post.setMinScoreRequired(5); post.setKeepIfHasComments(true); final PostScores postScores = new PostScores(2, 10, 0); String reason = getFailReason(post, postScores); assertTrue(reason.contains("Failed because score")); assertTrue(reason.contains("and has no comments")); }
7. Заключение
В этом выпуске мы представили некоторые очень полезные сведения о жизненном цикле сообщения Reddit. Теперь мы можем точно видеть, когда сообщение было отправлено и удалено каждый раз, а также точную причину каждой операции.