保留Reddit帖子提交歷史

REST,Spring
Remote
1
05:53 PM · Dec 01 ,2025

1. 概述

在本篇Reddit應用案例研究中,我們將開始記錄帖子提交嘗試的歷史, 並使狀態更具描述性,更易於理解。

2. 改進 Post 實體

首先,讓我們通過將舊的 String 狀態替換為更完整的一系列提交響應列表,同時跟蹤更多信息。

public class Post { ... @OneToMany(fetch = FetchType.EAGER, mappedBy = \"post\") private List<SubmissionResponse> submissionsResponse; }

接下來,讓我們看看在新的提交響應實體中實際跟蹤的內容:

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("失敗因為 ");
    builder.append(String.format(
      SCORE_TEMPLATE, postScores.getScore(), "<", post.getMinScoreRequired()));

    if (post.getMinTotalVotes() > 0) {
        builder.append("並且 ");
        builder.append(String.format(TOTAL_VOTES_TEMPLATE,
          postScores.getTotalVotes(), "<", post.getMinTotalVotes()));
    }
    if (post.isKeepIfHasComments()) {
        builder.append("並且有無評論");
    }
    return builder.toString();
}

protected String getSuccessReason(Post post, PostScores postScores) {
    StringBuilder builder = new StringBuilder();
    if (postScores.getScore() >= post.getMinScoreRequired()) {
        builder.append("成功因為 ");
        builder.append(String.format(SCORE_TEMPLATE,
          postScores.getScore(), ">=", post.getMinScoreRequired()));
        return builder.toString();
    }
    if (
      (post.getMinTotalVotes() > 0) &&
      (postScores.getTotalVotes() >= post.getMinTotalVotes())
    ) {
        builder.append("成功因為 ");
        builder.append(String.format(TOTAL_VOTES_TEMPLATE,
          postScores.getTotalVotes(), ">=", post.getMinTotalVotes()));
        return builder.toString();
    }
    return "成功因為有評論";
}

現在,我們將改進舊邏輯並跟蹤這些額外歷史信息

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);
    ...
}

請注意較低層方法正在執行的操作:

  • addAttemptResponse():創建新的 SubmissionResponse 記錄並將其添加到帖子(在每次提交嘗試時調用)
  • updateLastAttemptResponse():更新上次嘗試響應(在檢查帖子的分數時調用)

4. 計劃發佈 DTO

接下來,我們將修改 DTO 以確保這些新信息返回給客户端:

public class ScheduledPostDto {
    ...

    private String status;

    private List<SubmissionResponseDto> detailedStatus;
}

以下是簡單的 SubmissionResponseDto

public class SubmissionResponseDto {

    private int attemptNumber;

    private String content;

    private String localSubmissionDate;

    private String localScoreCheckDate;
}

我們還將修改 ScheduledPostRestController 中的轉換方法:

private ScheduledPostDto convertToDto(Post post) {
    ...
    List<SubmissionResponse> response = post.getSubmissionsResponse();
    if ((response != null) && (response.size() > 0)) {
        postDto.setStatus(response.get(response.size() - 1).toString().substring(0, 30));
        List<SubmissionResponseDto> responsedto = 
          post.getSubmissionsResponse().stream().
            map(res -> generateResponseDto(res)).collect(Collectors.toList());
        postDto.setDetailedStatus(responsedto);
    } else {
        postDto.setStatus("尚未發送");
        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. 前端

接下來,我們將修改我們的前端 scheduledPosts.jsp 以處理我們的新響應:


<div class="modal">
    <h4 class="modal-title">詳細狀態</h4>
    <table id="res"></table>
</div>

<script >
var loadedData = [];
var detailedResTable = $('#res').DataTable( {
    "searching":false,
    "paging": false,
    columns: [
        { title: "嘗試次數", data: "attemptNumber" },
        { title: "詳細狀態", data: "content" },
        { title: "提交嘗試時間", data: "localSubmissionDate" },
        { title: "檢查分數時間", data: "localScoreCheckDate" }
 ]
} );
           
$(document).ready(function() {
    $('#myposts').dataTable( {
        ...
        "columnDefs": [
            { "targets": 2, "data": "status",
              "render": function ( data, type, full, meta ) {
                  return data + 
                    ' <a href="#" onclick="showDetailedStatus('+meta.row+' )">更多詳情</a>';
              }
            },
            ....
        ],
        ...
    });
});

function showDetailedStatus(row){
    detailedResTable.clear().rows.add(loadedData[row].detailedStatus).draw();
    $('.modal').modal();
}

</script>

6. 測試

最後,我們將對我們的新方法執行一個簡單的單元測試:

首先,我們將測試 getSuccessReason()的實現:

@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 帖子生命週期的一些非常有用的信息。現在,我們可以清楚地看到帖子提交和刪除的準確時間,以及每次操作的具體原因。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.