1. 概述
这是 Spring 构建 Reddit 应用系列 的第二篇文章。本文将实现一个核心功能:通过 Reddit 官方 API,从我们的 Spring 应用中发布一条外部链接。
目标很明确:✅ 实现用户授权 → ✅ 判断是否需要验证码 → ✅ 提交表单 → ✅ 调用 Reddit 接口发布链接。整个流程要简洁、可控,避免踩坑。
2. 必要的 OAuth2 安全配置
要调用 Reddit 的写操作接口(比如发帖),必须先完成 OAuth2 授权,并申请正确的权限范围(scope)。
我们需要注册一个受保护的 OAuth2 资源,关键点是设置 submit
权限,同时加上 identity
以便获取用户身份信息。
@Bean
public OAuth2ProtectedResourceDetails reddit() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("reddit");
details.setClientId("your-client-id-12345"); // 替换为实际 Client ID
details.setClientSecret("your-client-secret-abcde"); // 替换为实际 Secret
details.setAccessTokenUri("https://www.reddit.com/api/v1/access_token");
details.setUserAuthorizationUri("https://www.reddit.com/api/v1/authorize");
details.setTokenName("oauth_token");
details.setScope(Arrays.asList("identity", "submit"));
details.setGrantType("authorization_code");
return details;
}
⚠️ 注意:
submit
scope 是发帖的硬性要求,缺了会 403identity
用于调用/api/v1/me
获取当前用户,调试时很有用- 回调地址(redirect_uri)需在 Reddit 应用配置中提前注册,例如
http://localhost:8080/login
3. 判断是否需要 Captcha
Reddit 对新用户或低 Karma 用户设置了反爬机制:发帖前必须填写验证码(Captcha)。
所以我们不能假设所有用户都能直接发帖,必须先探测:
private String needsCaptcha() {
String result = redditRestTemplate.getForObject(
"https://oauth.reddit.com/api/needs_captcha.json", String.class);
return result;
}
private String getNewCaptcha() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity req = new HttpEntity(headers);
Map<String, String> param = new HashMap<>();
param.put("api_type", "json");
ResponseEntity<String> result = redditRestTemplate.postForEntity(
"https://oauth.reddit.com/api/new_captcha", req, String.class, param);
String[] split = result.getBody().split("\"");
return split[split.length - 2];
}
✅ 解读:
needs_captcha.json
返回"true"
或"false"
字符串- 若需要,则调用
new_captcha
获取一个iden
(验证码 ID),用于后续展示图片和提交验证
⚠️ 踩坑提醒:
返回的 JSON 是原始字符串格式,没有结构化,所以用了 split("\"")
取倒数第二个字段拿 iden
—— 虽然简单粗暴,但 Reddit 的这个接口确实就这么设计的。
4. 提交表单页面
前端页面需要收集必要字段,并根据后端判断动态显示 Captcha。
后端控制器
@RequestMapping("/post")
public String showSubmissionForm(Model model) throws JsonProcessingException, IOException {
String needsCaptchaResult = needsCaptcha();
if (needsCaptchaResult.equalsIgnoreCase("true")) {
String iden = getNewCaptcha();
model.addAttribute("iden", iden);
}
return "submissionForm";
}
逻辑很清晰:如果需要验证码,就把 iden
传给前端用于渲染图片。
前端页面(submissionForm.html)
<form>
<input name="title" placeholder="标题" required/>
<input name="url" type="url" placeholder="链接" required/>
<input name="sr" placeholder="子版块(如 programming)" required/>
<input type="checkbox" name="sendReplies" value="true"/> 接收回复通知
<div th:if="${iden != null}">
<input type="hidden" name="iden" value="${iden}"/>
<input name="captcha" placeholder="请输入验证码" required/>
<img src="http://www.reddit.com/captcha/${iden}" alt="captcha" width="200"/>
</div>
<button type="submit" onclick="submitPost()">发布</button>
</form>
<script>
function submitPost(){
var data = {};
$('form').serializeArray().map(function(x){data[x.name] = x.value;});
$.ajax({
url: "/api/posts",
data: JSON.stringify(data),
type: 'POST',
contentType:'application/json'
}).done(function(data) {
if(data.length < 2){
alert(data[0]); // 显示错误信息
} else {
window.location.href = "/submissionResponse?msg=" +
encodeURIComponent(data[0]) + "&url=" + encodeURIComponent(data[1]);
}
}).fail(function(error) {
alert("请求失败: " + error.responseText);
});
}
</script>
✅ 关键点:
- 使用
th:if
动态渲染 Captcha 区域 - AJAX 提交避免页面刷新,用户体验更平滑
- 错误统一通过
fail
回调处理,防止静默失败
5. 调用 Reddit API 发布链接
真正的发帖逻辑在后端完成,通过 redditRestTemplate
调用 Reddit 的 /api/submit
接口。
REST 接口定义
@Controller
@RequestMapping(value = "/api/posts")
public class RedditPostRestController {
@Autowired
private RedditService service;
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public List<String> submit(@Valid @RequestBody PostDto postDto) {
return service.submitPost(postDto);
}
}
使用 @Valid
自动校验必填字段,失败直接返回 400。
服务层实现
public List<String> submitPost(PostDto postDto) {
MultiValueMap<String, String> params = constructParams(postDto);
JsonNode node = redditTemplate.submitPost(params);
return parseResponse(node);
}
private MultiValueMap<String, String> constructParams(PostDto postDto) {
MultiValueMap<String, String> param = new LinkedMultiValueMap<>();
param.add("title", postDto.getTitle());
param.add("sr", postDto.getSubreddit());
param.add("url", postDto.getUrl());
param.add("iden", postDto.getIden());
param.add("captcha", postDto.getCaptcha());
if (postDto.isSendReplies()) {
param.add("sendReplies", "true");
}
param.add("api_type", "json");
param.add("kind", "link"); // 发布的是链接,不是文本帖
param.add("resubmit", "true"); // 允许重复提交同一链接
param.add("then", "comments"); // 成功后跳转到评论页
return param;
}
✅ 参数说明:
| 参数 | 说明 |
|------|------|
| kind
| link
表示链接帖,self
是自述帖 |
| resubmit
| 防止重复链接被拦截 |
| then
| 控制成功后跳转目标 |
响应解析
Reddit 的返回是嵌套 JSON,需小心处理:
private List<String> parseResponse(JsonNode node) {
String result = "";
JsonNode errorNode = node.get("json").get("errors").get(0);
if (errorNode != null && errorNode.size() > 0) {
for (JsonNode child : errorNode) {
result += child.toString().replaceAll("\"|null", "") + "<br>";
}
return Arrays.asList(result);
} else {
JsonNode urlNode = node.get("json").get("data").get("url");
if (urlNode != null) {
return Arrays.asList("Post submitted successfully", urlNode.asText());
} else {
return Arrays.asList("Error Occurred while parsing Response");
}
}
}
⚠️ 注意:errors
是数组,即使只有一个错误也要遍历。常见错误包括:
BAD_CAPTCHA
:验证码错误RATELIMIT
:发帖太频繁NO_TEXT
:标题或链接为空
6. 数据传输对象(DTO)与响应页面
PostDto
public class PostDto {
@NotNull
private String title;
@NotNull
private String url;
@NotNull
private String subreddit;
private boolean sendReplies;
private String iden;
private String captcha;
// standard getters and setters
}
字段与前端表单一一对应,配合 @Valid
实现基础校验。
提交结果页(submissionResponse.html)
<html>
<body>
<h1 th:text="${msg}">操作结果</h1>
<h2 th:if="${param.containsKey('url')}">
<a th:href="${param.url[0]}" target="_blank">点击查看发布的帖子</a>
</h2>
</body>
</html>
使用 Thymeleaf 解析 URL 并生成跳转链接,简洁明了。
7. 总结
本文完整实现了从 Spring 应用发布链接到 Reddit 的流程:
- ✅ OAuth2 授权配置
- ✅ 动态处理 Captcha
- ✅ 前后端协作表单
- ✅ 调用 Reddit submit 接口
- ✅ 错误统一处理
虽然功能简单,但涵盖了实际集成中常见的坑:权限、验证码、接口返回格式不规范等。
下一步计划:实现“定时发布”功能,结合 Quartz 或 Spring Scheduler,把自动化做到极致。
🔧 完整代码示例 已发布至 GitHub:https://github.com/baeldung/reddit-app
项目基于 Eclipse 构建,导入即可运行,适合快速验证和二次开发。