1. 概述

在 Reddit 发帖有点像掷骰子——有时候一篇内容平平的帖子能爆火,而另一篇精心准备的却无人问津。有没有可能在发布后早期监控帖子表现,一旦发现反响不佳,就立即删除并重新发布

本文是 Spring 构建 Reddit 应用案例 的延续,我们将实现一个实用功能:当帖子在指定时间内未达到预期互动量时,自动删除并重新提交

核心逻辑很简单:允许用户配置两个关键参数——
✅ 在多长时间内(分钟)观察帖子表现
✅ 至少需要多少投票(score)才算“有 traction”
如果未达标,则触发删除 + 重发机制,相当于给帖子“续命”。


2. 申请额外的 Reddit 权限

要实现删除和编辑帖子,我们需要向 Reddit API 申请 edit 权限。

在 OAuth2 配置中,为 Reddit 资源添加 edit scope:

@Bean
public OAuth2ProtectedResourceDetails reddit() {
    AuthorizationCodeResourceDetails details = 
      new AuthorizationCodeResourceDetails();
    details.setScope(Arrays.asList("identity", "read", "submit", "edit"));
    // 其他配置...
    return details;
}

⚠️ 注意:少了 edit 权限,后续删除接口会返回 403。


3. 实体类与数据访问层

3.1 扩展 Post 实体

在原有 Post 实体基础上,新增几个字段用于控制重试逻辑:

@Entity
public class Post {
    // ... 其他字段

    private String redditID;           // Reddit 上的帖子唯一ID(如:abc123)
    private int noOfAttempts;          // 最大重试次数
    private int timeInterval;          // 观察期(单位:分钟)
    private int minScoreRequired;      // 最低达标分数
}

字段说明:

字段 说明
redditID 用于调用 Reddit API 查询分数或删除帖子
noOfAttempts 比如设为 3,表示最多尝试删除重发 3 次
timeInterval 比如设为 60,表示发出去后 1 小时内看表现
minScoreRequired 比如设为 5,表示 1 小时内没到 5 票就重发

3.2 扩展 PostRepository

我们需要能快速查出“正在监控中”的帖子,因此在 Repository 中添加自定义查询方法:

public interface PostRepository extends JpaRepository<Post, Long> {

    // 查找已发送但未超时的帖子(用于定时任务扫描)
    List<Post> findBySubmissionDateBeforeAndIsSent(Date date, boolean sent);

    // 用户个人历史记录
    List<Post> findByUser(User user);

    // 核心:查找已发布到 Reddit 且还有重试机会的帖子
    List<Post> findByRedditIDNotNullAndNoOfAttemptsGreaterThan(int attempts);
}

findByRedditIDNotNullAndNoOfAttemptsGreaterThan(0) 是定时任务的核心查询,确保只处理“可抢救”的帖子。


4. 定时任务:检查并重发

使用 @Scheduled 定义一个每 3 分钟执行一次的任务:

@Scheduled(fixedRate = 3 * 60 * 1000)
public void checkAndReSubmitPosts() {
    List<Post> submitted = 
      postRepository.findByRedditIDNotNullAndNoOfAttemptsGreaterThan(0);
    for (Post post : submitted) {
        checkAndReSubmit(post);
    }
}

4.1 checkAndReSubmit 方法实现

private void checkAndReSubmit(Post post) {
    try {
        checkAndReSubmitInternal(post);
    } catch (final Exception e) {
        logger.error("Error occurred while checking post " + post.toString(), e);
    }
}

private void checkAndReSubmitInternal(Post post) {
    // 判断是否已超过观察期
    if (didIntervalPassed(post.getSubmissionDate(), post.getTimeInterval())) {
        int score = getPostScore(post.getRedditID());
        if (score < post.getMinScoreRequired()) {
            // 未达标:删除 + 重置状态
            deletePost(post.getRedditID());
            resetPost(post);
        } else {
            // 达标:清空重试次数,不再处理
            post.setNoOfAttempts(0);
            postRepository.save(post);
        }
    }
}

// 判断当前时间是否已超过“发布时间 + 观察期”
private boolean didIntervalPassed(Date submissionDate, int intervalInMinutes) {
    long currentTime = new Date().getTime();
    long elapsed = currentTime - submissionDate.getTime();
    long elapsedInMinutes = TimeUnit.MINUTES.convert(elapsed, TimeUnit.MILLISECONDS);
    return elapsedInMinutes > intervalInMinutes;
}

// 重置帖子状态,准备重新提交
private void resetPost(Post post) {
    long nextSubmitTime = new Date().getTime();
    nextSubmitTime += TimeUnit.MILLISECONDS.convert(post.getTimeInterval(), TimeUnit.MINUTES);
    
    post.setRedditID(null);                    // 清除旧ID
    post.setSubmissionDate(new Date(nextSubmitTime));
    post.setSent(false);
    post.setSubmissionResponse("Not sent yet");
    postRepository.save(post);                 // 状态持久化
}

⚠️ 踩坑提醒:submissionDate 是原始发布时间,不是当前时间。判断超时必须基于它 + timeInterval


5. 获取 Reddit 帖子当前得分

通过 Reddit 的 api/info 接口查询帖子详情:

private int getPostScore(String redditId) {
    JsonNode node = redditRestTemplate.getForObject(
      "https://oauth.reddit.com/api/info?id=t3_" + redditId, JsonNode.class);
    int score = node.get("data")
                   .get("children")
                   .get(0)
                   .get("data")
                   .get("score")
                   .asInt();
    return score;
}

📌 注意点:

  • 需要 read 权限
  • Reddit 的 full name 规则:帖子 ID 前缀为 t3_,例如原始 ID 是 abc123,完整 ID 就是 t3_abc123

6. 删除 Reddit 帖子

调用 Reddit 的 api/del 接口删除帖子:

private void deletePost(String redditId) {
    MultiValueMap<String, String> param = new LinkedMultiValueMap<>();
    param.add("id", "t3_" + redditId);
    
    redditRestTemplate.postForObject(
      "https://oauth.reddit.com/api/del.json", param, JsonNode.class);
}

✅ 简单粗暴,POST 提交 id=t3_xxx 即可。
⚠️ 注意返回值可能包含错误信息,生产环境建议做错误解析。


7. 控制器层:接收重试配置

RedditController 中接收前端传来的重试策略参数:

@RequestMapping(value = "/schedule", method = RequestMethod.POST)
public String schedule(Model model, 
  @RequestParam Map<String, String> formParams) throws ParseException {
    
    Post post = new Post();
    post.setTitle(formParams.get("title"));
    post.setSubreddit(formParams.get("sr"));
    post.setUrl(formParams.get("url"));
    
    // 接收重试策略
    post.setNoOfAttempts(Integer.parseInt(formParams.get("attempt")));
    post.setTimeInterval(Integer.parseInt(formParams.get("interval")));
    post.setMinScoreRequired(Integer.parseInt(formParams.get("score")));
    
    // 其他业务逻辑...
    return "redirect:/dashboard";
}

8. 前端界面:配置重试规则

在发布表单中加入重试策略选项:

<label class="col-sm-3">重发策略</label>

<label>重试次数</label> 
<select name="attempt">
    <option value="0" selected>不重试</option>
    <option value="2">最多2次</option>
    <option value="3">最多3次</option>
    <option value="4">最多4次</option>
    <option value="5">最多5次</option>
</select>

<label>观察期</label>
<select name="interval">
    <option value="0" selected>不监控</option>
    <option value="45">45分钟</option>
    <option value="60">1小时</option>
    <option value="120">2小时</option>
</select>

<label>最低达标分数</label>
<input type="number" value="0" name="score" required/>

📌 用户体验建议:

  • 默认 attempt=0 表示关闭该功能
  • score 可设为 0,表示“只要有投票就不重发”

9. 总结

我们在这个小项目中又迈出了一步:
✅ 不仅能自动发布到 Reddit
✅ 还能智能监控早期表现,对“冷启动失败”的帖子进行自动删除 + 重发

这相当于给发布系统加了一层“容错机制”,尤其适合那些对曝光敏感的内容运营场景。

💡 扩展思路:

  • 可加入指数退避重试(如第一次等1小时,第二次等2小时)
  • 可结合 subreddit 的活跃时间段智能调度
  • 可记录每次重发后的 score 变化,做数据分析

这个功能虽小,但体现了“自动化 + 数据反馈”的闭环思维,值得在类似内容分发系统中借鉴。


原始标题:Retry a Post to Reddit If It's Not Getting Traction