feat: posts 模块——用户原创文章直接落库#35
Merged
Merged
Conversation
镜像 community/SharedLink 的 JDBC 范式,新增完整的 posts 功能: - schema.sql 追加 posts 表(JSONB tags、slug 唯一约束、feed 索引) - model/Post record + PostStatus / PostVisibility 常量类 - dto/PostRequest + PostView + PostSummaryView(含作者信息冗余) - JdbcPostRepository:JSONB tags 读写、slug 前缀去重查询 - PostService:slug 自动生成(title→kebab-case+去重后缀)、owner 校验 - PostController:7 个端点(创建/更新/删除/mine/feed/详情/转正) - SaTokenConfigure 白名单:GET /api/posts/feed + GET /api/posts/*/*
Contributor
There was a problem hiding this comment.
Pull request overview
该 PR 为后端新增 posts 模块,使站内编辑器产出的原创文章可直接通过 REST API 落库并在 /feed 与详情页公开展示,同时提供“转正”记录入口以衔接后续 GitHub PR 流程。
Changes:
- 在
schema.sql中新增posts表及相关索引(作者维度、feed 维度)。 - 新增
com.involutionhell.backend.posts包:model/dto/repository/service/controller 完整链路与 7 个 API 端点。 - Sa-Token 放行 posts 公开读接口,并补充 docs 文档与开发速查表。
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/resources/schema.sql | 新增 posts 表结构与索引以支持原创文章落库与 feed 查询 |
| src/main/java/com/involutionhell/backend/posts/service/PostService.java | 核心业务逻辑:slug 生成/去重、owner 校验、feed 分页与视图组装 |
| src/main/java/com/involutionhell/backend/posts/repository/PostRepository.java | posts 数据访问接口定义 |
| src/main/java/com/involutionhell/backend/posts/repository/JdbcPostRepository.java | 基于 Spring JDBC 的 posts 表读写实现(含 JSONB tags 处理) |
| src/main/java/com/involutionhell/backend/posts/controller/PostController.java | 暴露 posts 的 7 个 REST 端点并接入 Sa-Token 登录态 |
| src/main/java/com/involutionhell/backend/common/config/SaTokenConfigure.java | 将 posts 公开读接口加入匿名白名单 |
| src/main/java/com/involutionhell/backend/posts/README.md | 新增模块内 README(职责/端点概览) |
| src/main/java/com/involutionhell/backend/posts/model/PostVisibility.java | 可见性常量定义 |
| src/main/java/com/involutionhell/backend/posts/model/PostStatus.java | 状态常量定义 |
| src/main/java/com/involutionhell/backend/posts/model/Post.java | posts 领域对象 record |
| src/main/java/com/involutionhell/backend/posts/dto/PostView.java | 详情视图 DTO(含 contentMd) |
| src/main/java/com/involutionhell/backend/posts/dto/PostSummaryView.java | 列表摘要 DTO(不含 contentMd) |
| src/main/java/com/involutionhell/backend/posts/dto/PostRequest.java | 写请求 DTO |
| docs/posts/README.md | 对外 API 契约与表结构/部署说明文档 |
| docs/dev1.md | 开发端点速查表补充 posts 相关入口 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+135
to
+146
| // slug 处理:前端可传新 slug(改 URL 场景),不传则沿用旧 slug | ||
| String newSlug = (req.slug() != null && !req.slug().isBlank()) | ||
| ? req.slug() | ||
| : existing.slug(); | ||
|
|
||
| // 若 slug 改变,需要检查新 slug 是否与该作者其他文章冲突 | ||
| if (!newSlug.equals(existing.slug())) { | ||
| boolean taken = postRepo.findByAuthorAndSlug(callerId, newSlug).isPresent(); | ||
| if (taken) { | ||
| throw new IllegalArgumentException("slug 已被使用:" + newSlug); | ||
| } | ||
| } |
Comment on lines
+273
to
+279
| /** | ||
| * 按 id 查文章,不存在则抛 404 语义的 IllegalArgumentException。 | ||
| */ | ||
| private Post requirePost(Long postId) { | ||
| return postRepo.findById(postId) | ||
| .orElseThrow(() -> new IllegalArgumentException("文章不存在:" + postId)); | ||
| } |
Comment on lines
+47
to
+53
| @PostMapping | ||
| @SaCheckLogin | ||
| public ResponseEntity<ApiResponse<PostView>> create(@RequestBody PostRequest req) { | ||
| long authorId = StpUtil.getLoginIdAsLong(); | ||
| PostView view = postService.create(authorId, req); | ||
| return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.ok(view)); | ||
| } |
Comment on lines
+59
to
+66
| @PutMapping("/{id}") | ||
| @SaCheckLogin | ||
| public ApiResponse<PostView> update(@PathVariable Long id, | ||
| @RequestBody PostRequest req) { | ||
| long callerId = StpUtil.getLoginIdAsLong(); | ||
| PostView view = postService.update(callerId, id, req); | ||
| return ApiResponse.ok(view); | ||
| } |
Comment on lines
+245
to
+256
| List<Post> posts = postRepo.findFeed(safeLimit, safeOffset); | ||
|
|
||
| // 批量拼作者信息:每篇独立查一次(MVP 量级可接受;后续可加缓存或 JOIN 优化) | ||
| return posts.stream() | ||
| .map(p -> { | ||
| UserAccount author = userAccountRepository.findById(p.authorId()).orElse(null); | ||
| String username = author != null ? author.username() : "unknown"; | ||
| String displayName = author != null ? author.displayName() : ""; | ||
| String avatarUrl = author != null ? author.avatarUrl() : null; | ||
| return PostSummaryView.from(p, username, displayName, avatarUrl); | ||
| }) | ||
| .toList(); |
Comment on lines
+152
to
+156
| log.info("post updated: id={} author={}", postId, callerId); | ||
|
|
||
| Post updated = postRepo.findById(postId) | ||
| .orElseThrow(() -> new IllegalStateException("post not found after update: " + postId)); | ||
| return buildView(updated); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
实现计划 A 节:站内编辑器写完后直接
POST /api/posts落库,不走 Git PR 流程。文章在/feed原创 Tab 和个人主页展示,带"转正"入口可一键跳 GitHub 发 PR 升级为 Fumadocs contributor。变更内容
数据库(
src/main/resources/schema.sql)posts表:author_id FK、slug(作者内唯一)、title、description、tags JSONB、content_md、cover_url、visibility、status、promoted_pr_url、promoted_at、view_count、时间戳idx_posts_author和idx_posts_feed索引SPRING_SQL_INIT_MODE=always启动自动建表,无需手动迁移,幂等新增包
com.involutionhell.backend.postsPostrecord、PostStatus、PostVisibility常量类PostRequest(写请求)、PostView(详情含 contentMd)、PostSummaryView(列表摘要)PostRepository接口 +JdbcPostRepository(裸 JDBC,镜像JdbcSharedLinkRepository,tags JSONB 用Types.OTHER)PostService:slug 自动生成(title→kebab-case+去重后缀)、owner 校验(非作者返回 403)、分页 feedPostController:7 个端点7 个 API 端点
POST/api/postsPUT/api/posts/{id}DELETE/api/posts/{id}GET/api/posts/mineGET/api/posts/feedGET/api/posts/{username}/{slug}POST/api/posts/{id}/promoteSaTokenConfigureGET /api/posts/feed和GET /api/posts/*/*加入公开白名单@SaCheckLogin守卫文档
docs/posts/README.md:完整 API 契约 + 数据库表结构 + 部署说明docs/dev1.md:端点速查表新增 posts 入口验证
本地 curl 全部 7 个端点通过(backend 8081 + PostgreSQL 127.0.0.1:5432):
部署清单(上线时)
feat/posts-module 合并 main 后,重建后端镜像时所有变更一次性上线:
cd /home/ubuntu/involution-hell git pull origin main docker compose build backend docker compose up -d backend重点:新镜像启动时
schema.sql自动建posts表(IF NOT EXISTS幂等),SaTokenConfigure白名单随新镜像生效,线上GET /api/posts/feed和GET /api/posts/*/*匿名访问恢复正常。当前 8080 生产实例是旧镜像(2026-04-21),上线前这两个公开接口会 401,属预期。