add star && collection.

This commit is contained in:
many2many 2024-05-28 14:58:50 +08:00
parent 169e8604c7
commit b6efd73bf7
11 changed files with 630 additions and 14 deletions

View File

@ -180,7 +180,7 @@ GET /v1/posts?type=1716477250320&style=newest&page=1&page_size=20
### 9 响应示例(错误) ### 9 响应示例(错误)
```json ```json
{ "code": 10001, "message": "验证码无效" } { "code": 10001, "msg": "验证码无效" }
``` ```
### 10 错误响应码参考 ### 10 错误响应码参考

View File

@ -0,0 +1,86 @@
# RESTFul API 文档
## 收藏和取消收藏接口
### 1 接口描述
收藏文章和取消对文章的收藏
### 2 请求URL
`/v1/post/collection`
### 3 请求方式
**POST**
### 4 请求头
| 头字段 | 必填 | 数据类型 | 描述 |
| ------------ | ---- | ------------- | -------------------------------- |
| | | ||
### 1.5 请求体参数
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 | 示例 | 允许空值 |
|----------| ---- |------| ------- |------| ----------- | ----- |
| id | 是 | 长整型 | | 文章id | | |
### 6 请求示例
```http
POST /v1/post/star
Host: {apiAddress}
Content-Type: application/json
{
"id": 123440
}
```
### 7 返回参数说明
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 |
|------| ---- | -------- | -------- | ---------------------------- |
| code | 是 | 整型 | | 错误码200表示成功 |
| msg | 否 | 字符串 | 1-50字符 | 错误信息描述 |
| data | 否 | json | | 具体业务数据 |
data结构说明
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 |
|--------| ---- |---------|-------------------------|-----------|
| status | 是 | boolean | 收藏成功 true, 取消收藏成功 false | |
### 8 响应示例(成功)
```json
{
"code": 0,
"msg": "success",
"data": {
"status": true
}
}
```
### 9 响应示例(错误)
```json
{ "code": 10001, "msg": "验证码无效" }
```
### 10 错误响应码参考
更多响应错误码及含义,请参阅[API响应码表](URL/for/api/responseCode/table)。
### 11 安全性与认证
此API要求调用方在`Authorization`头中携带经过Bearer认证的令牌。
### 12 测试环境
访问测试环境以进行接口调试:
[https://test.apiAddress.com](https://test.apiAddress.com)
### 13 版本管理
本API通过URI路径进行版本控制。请在请求URL中包含`/v1`以使用当前版本。
### 14 更新记录
### 15 联系支持
如需帮助或对API有任何疑问请通过电子邮件与我们联系[support@api.com](mailto:support@api.com)。
### 16 反馈与建议
发现文档问题或有改进建议?请填写[反馈表单](https://forms.api.com/feedback)与我们分享。

View File

@ -0,0 +1,86 @@
# RESTFul API 文档
## 点赞和取消点赞接口
### 1 接口描述
点赞文章和取消对文章的点赞
### 2 请求URL
`/v1/post/star`
### 3 请求方式
**POST**
### 4 请求头
| 头字段 | 必填 | 数据类型 | 描述 |
| ------------ | ---- | ------------- | -------------------------------- |
| | | ||
### 1.5 请求体参数
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 | 示例 | 允许空值 |
|----------| ---- |------| ------- |------| ----------- | ----- |
| id | 是 | 长整型 | | 文章id | | |
### 6 请求示例
```http
POST /v1/post/star
Host: {apiAddress}
Content-Type: application/json
{
"id": 123440
}
```
### 7 返回参数说明
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 |
|------| ---- | -------- | -------- | ---------------------------- |
| code | 是 | 整型 | | 错误码200表示成功 |
| msg | 否 | 字符串 | 1-50字符 | 错误信息描述 |
| data | 否 | json | | 具体业务数据 |
data结构说明
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 |
|--------| ---- |---------|-------------------------|-----------|
| status | 是 | boolean | 点赞成功 true, 取消点赞成功 false | |
### 8 响应示例(成功)
```json
{
"code": 0,
"msg": "success",
"data": {
"status": true
}
}
```
### 9 响应示例(错误)
```json
{ "code": 10001, "msg": "验证码无效" }
```
### 10 错误响应码参考
更多响应错误码及含义,请参阅[API响应码表](URL/for/api/responseCode/table)。
### 11 安全性与认证
此API要求调用方在`Authorization`头中携带经过Bearer认证的令牌。
### 12 测试环境
访问测试环境以进行接口调试:
[https://test.apiAddress.com](https://test.apiAddress.com)
### 13 版本管理
本API通过URI路径进行版本控制。请在请求URL中包含`/v1`以使用当前版本。
### 14 更新记录
### 15 联系支持
如需帮助或对API有任何疑问请通过电子邮件与我们联系[support@api.com](mailto:support@api.com)。
### 16 反馈与建议
发现文档问题或有改进建议?请填写[反馈表单](https://forms.api.com/feedback)与我们分享。

View File

@ -22,7 +22,7 @@
### 1.6 请求示例 ### 1.6 请求示例
```http ```http
POST /api/user/signup POST /v1/auth/register
Host: {apiAddress} Host: {apiAddress}
Content-Type: application/json Content-Type: application/json

View File

@ -0,0 +1,80 @@
# RESTFul API 文档
## 获取当前用户对某个动态的收藏状态
### 1 接口描述
获得当前登录用户对指定动态的收藏状态
### 2 请求URL
`/v1/post/collection`
### 3 请求方式
**GET**
### 4 请求头
| 头字段 | 必填 | 数据类型 | 描述 |
| ------------ | ---- | ------------- | -------------------------------- |
| | | ||
### 1.5 Query参数
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 | 示例 | 允许空值 |
|----------| ---- |------| ------- |------| ----------- | ----- |
| id | 是 | 长整型 | | 文章id | | |
### 6 请求示例
```http
GET /v1/post/collection?id=100
```
### 7 返回参数说明
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 |
|------| ---- | -------- | -------- | ---------------------------- |
| code | 是 | 整型 | | 错误码200表示成功 |
| msg | 否 | 字符串 | 1-50字符 | 错误信息描述 |
| data | 否 | json | | 具体业务数据 |
data结构说明
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 |
|--------| ---- |---------|---------------------|-----------|
| status | 是 | boolean | | 已收藏 true, 未收藏 false|
### 8 响应示例(成功)
```json
{
"code": 0,
"msg": "success",
"data": {
"status": true
}
}
```
### 9 响应示例(错误)
```json
{ "code": 10001, "msg": "验证码无效" }
```
### 10 错误响应码参考
更多响应错误码及含义,请参阅[API响应码表](URL/for/api/responseCode/table)。
### 11 安全性与认证
此API要求调用方在`Authorization`头中携带经过Bearer认证的令牌。
### 12 测试环境
访问测试环境以进行接口调试:
[https://test.apiAddress.com](https://test.apiAddress.com)
### 13 版本管理
本API通过URI路径进行版本控制。请在请求URL中包含`/v1`以使用当前版本。
### 14 更新记录
### 15 联系支持
如需帮助或对API有任何疑问请通过电子邮件与我们联系[support@api.com](mailto:support@api.com)。
### 16 反馈与建议
发现文档问题或有改进建议?请填写[反馈表单](https://forms.api.com/feedback)与我们分享。

View File

@ -0,0 +1,80 @@
# RESTFul API 文档
## 获取当前用户对某个动态的点赞状态
### 1 接口描述
获得当前登录用户对指定动态的点赞状态
### 2 请求URL
`/v1/post/star`
### 3 请求方式
**GET**
### 4 请求头
| 头字段 | 必填 | 数据类型 | 描述 |
| ------------ | ---- | ------------- | -------------------------------- |
| | | ||
### 1.5 Query参数
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 | 示例 | 允许空值 |
|----------| ---- |------| ------- |------| ----------- | ----- |
| id | 是 | 长整型 | | 文章id | | |
### 6 请求示例
```http
GET /v1/post/star?id=100
```
### 7 返回参数说明
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 |
|------| ---- | -------- | -------- | ---------------------------- |
| code | 是 | 整型 | | 错误码200表示成功 |
| msg | 否 | 字符串 | 1-50字符 | 错误信息描述 |
| data | 否 | json | | 具体业务数据 |
data结构说明
| 参数名称 | 必填 | 数据类型 | 约束条件 | 描述 |
|--------| ---- |---------|---------------------|-----------------------|
| status | 是 | boolean | | 已点赞 true, 未点赞 false |
### 8 响应示例(成功)
```json
{
"code": 0,
"msg": "success",
"data": {
"status": true
}
}
```
### 9 响应示例(错误)
```json
{ "code": 10001, "msg": "验证码无效" }
```
### 10 错误响应码参考
更多响应错误码及含义,请参阅[API响应码表](URL/for/api/responseCode/table)。
### 11 安全性与认证
此API要求调用方在`Authorization`头中携带经过Bearer认证的令牌。
### 12 测试环境
访问测试环境以进行接口调试:
[https://test.apiAddress.com](https://test.apiAddress.com)
### 13 版本管理
本API通过URI路径进行版本控制。请在请求URL中包含`/v1`以使用当前版本。
### 14 更新记录
### 15 联系支持
如需帮助或对API有任何疑问请通过电子邮件与我们联系[support@api.com](mailto:support@api.com)。
### 16 反馈与建议
发现文档问题或有改进建议?请填写[反馈表单](https://forms.api.com/feedback)与我们分享。

View File

@ -1,15 +1,215 @@
## 任务名称: 点赞和收藏动态 ## 任务名称: 点赞和收藏动态
### 目标: ### 目标:
- [简要说明此任务旨在达成的具体学习目标或技能提升点。] - 巩固练习: 熟练掌握基于spring boot开发的基本步骤
### 预备知识: ### 预备知识:
- [列出完成该任务前学生应具备的基础知识或先修技能,如特定编程语言基础、框架了解等。]
### 操作步骤: ### 操作步骤:
1. **步骤1**: [详细描述第一步操作,包括使用的工具、命令或技术要点。] #### 1. API文档
2. **步骤2**: [继续描述后续步骤,确保每一步都清晰、可执行。]
... [点赞和取消点赞接口](../api%20doc/点赞和取消点赞接口.md)
[根据需要添加更多步骤]
[获取当前用户对某个动态的点赞状态接口](../api%20doc/获取当前用户对某个动态的点赞状态接口.md)
[收藏和取消收藏接口](../api%20doc/收藏和取消收藏接口.md)
[获取当前用户对某个动态的收藏状态接口](../api%20doc/获取当前用户对某个动态的收藏状态接口.md)
#### 2. 数据模型设计和实现
##### 2.1 实体类
**PostStar**
```java
@Entity
public class PostStar extends BaseAuditingEntity{
@Comment("POST ID")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id", nullable = false)
private Post post;
@Comment("用户ID")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
}
```
**PostCollection**
```java
@Entity
public class PostCollection extends BaseAuditingEntity{
@Comment("POST ID")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id", nullable = false)
private Post post;
@Comment("用户ID")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
}
```
##### 2.2 DTO
**PostStarDTO**
```java
@Value
public class PostStarDto {
Long id;
PostDto post;
UserDto user;
@JsonSerialize(using = MillisecondToSecondSerializer.class)
Long createdOn;
@JsonSerialize(using = MillisecondToSecondSerializer.class)
Long modifiedOn;
}
```
**PostCollectionDTO**
```java
@Value
public class PostCollectionDto {
Long id;
PostDto post;
UserDto user;
@JsonSerialize(using = MillisecondToSecondSerializer.class)
Long createdOn;
@JsonSerialize(using = MillisecondToSecondSerializer.class)
Long modifiedOn;
}
```
##### 2.3 Mapper
**PostStarMapper**
```java
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = MappingConstants.ComponentModel.SPRING)
public interface PostStarMapper {
PostStar toEntity(PostStarDto postStarDto);
PostStarDto toDto(PostStar postStar);
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
PostStar partialUpdate(PostStarDto postStarDto, @MappingTarget PostStar postStar);
}
```
**PostCollectionMapper**
```java
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = MappingConstants.ComponentModel.SPRING)
public interface PostCollectionMapper {
PostCollection toEntity(PostCollectionDto postCollectionDto);
PostCollectionDto toDto(PostCollection postCollection);
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
PostCollection partialUpdate(PostCollectionDto postCollectionDto, @MappingTarget PostCollection postCollection);
}
```
#### 3. JPA接口
**PostStarRepository**
```java
public interface PostStarRepository extends JpaRepository<PostStar, Long> {
Optional<PostStar> findByUserIdAndPostId(Long postId, Long userId);
}
```
**PostCollectionRepository**
```java
public interface PostCollectionRepository extends JpaRepository<PostCollection, Long> {
Optional<PostCollection> findByUserIdAndPostId(Long userId, Long postId);
}
```
#### 4. 业务逻辑
在PostService中添加处理点赞的方法
```java
/**
* 判断当前用户是对指定文章否已点赞
* @param postId
* @return
*/
public boolean currentUserHasStarred(Long postId){
User user = authService.getCurrentUser();
return postStarRepository.findByUserIdAndPostId(user.getId(), postId).isPresent();
}
/**
* 点赞文章
* @param postId 文章ID
* @return 如果点赞成功返回true如果已点过赞则返回false
*/
public boolean toggleStar(long postId){
// 获取当前用户
User user = authService.getCurrentUser();
// 确认文章存在
Post post = postRepository.findById(postId).orElseThrow(()->new ResourceNotFoundException("Post","id="+postId));
// 确认用户是否已点赞
PostStar postStar = postStarRepository.findByUserIdAndPostId(user.getId(),postId).orElse(null);
if(postStar==null){
// 如果未点赞,则创建点赞记录并更新文章点赞数
postStar = new PostStar();
postStar.setPost(post);
postStar.setUser(user);
postStarRepository.save(postStar);
post.setUpvoteCount(post.getUpvoteCount()+1);
postRepository.save(post);
return true;
}
// 如果已点赞,则删除点赞记录并更新文章点赞数
postStarRepository.delete(postStar);
post.setUpvoteCount(post.getUpvoteCount()-1);
postRepository.save(post);
return false;
}
```
对于收藏的处理与点赞类似,不再赘述。
#### 5. RestController
在PostController中添加点赞相关的接口
```java
@GetMapping("/post/star")
public DataResult<Map<String, Boolean>> currentHasStarred(@PathParam("id") Long postId) {
Map<String, Boolean> result = new HashMap<>();
result.put("status", postService.currentUserHasStarred(postId));
return ResultUtil.ok(result);
}
@PostMapping("/post/star")
public DataResult<Map<String, Boolean>> toggleStar(@RequestBody Map<String, Long> payload) {
boolean stared = postService.toggleStar(payload.get("id"));
Map<String, Boolean> result = new HashMap<>();
result.put("status", stared);
return ResultUtil.ok(result);
}
```
收藏的接口与点赞类似,不再赘述。
### 技术/工具需求: ### 技术/工具需求:
- [列出完成任务所需的技术栈、工具、软件版本等。] - [列出完成任务所需的技术栈、工具、软件版本等。]

View File

@ -50,6 +50,14 @@ public class PostController {
result.put("status", postService.currentUserHasStarred(postId)); result.put("status", postService.currentUserHasStarred(postId));
return ResultUtil.ok(result); return ResultUtil.ok(result);
} }
@PostMapping("/post/star")
public DataResult<Map<String, Boolean>> toggleStar(@RequestBody Map<String, Long> payload) {
boolean stared = postService.toggleStar(payload.get("id"));
Map<String, Boolean> result = new HashMap<>();
result.put("status", stared);
return ResultUtil.ok(result);
}
@GetMapping("/post/collection") @GetMapping("/post/collection")
public DataResult<Map<String, Boolean>> currentHasCollected(@PathParam("id") Long postId) { public DataResult<Map<String, Boolean>> currentHasCollected(@PathParam("id") Long postId) {
@ -57,4 +65,13 @@ public class PostController {
result.put("status", postService.currentUserHasCollected(postId)); result.put("status", postService.currentUserHasCollected(postId));
return ResultUtil.ok(result); return ResultUtil.ok(result);
} }
@PostMapping("/post/collection")
public DataResult<Map<String, Boolean>> toggleCollection(@RequestBody Map<String, Long> payload) {
boolean collected = postService.toggleCollection(payload.get("id"));
Map<String, Boolean> result = new HashMap<>();
result.put("status", collected);
return ResultUtil.ok(result);
}
} }

View File

@ -14,7 +14,6 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Index(name = "idx_post_collection_post_id", columnList = "post_id"), @Index(name = "idx_post_collection_post_id", columnList = "post_id"),
@Index(name = "idx_post_collection_user_id", columnList = "user_id") @Index(name = "idx_post_collection_user_id", columnList = "user_id")
}) })
@EntityListeners(AuditingEntityListener.class)
public class PostCollection extends BaseAuditingEntity{ public class PostCollection extends BaseAuditingEntity{
@Comment("POST ID") @Comment("POST ID")

View File

@ -14,7 +14,6 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Index(name = "idx_post_star_post_id", columnList = "post_id"), @Index(name = "idx_post_star_post_id", columnList = "post_id"),
@Index(name = "idx_post_star_user_id", columnList = "user_id") @Index(name = "idx_post_star_user_id", columnList = "user_id")
}) })
@EntityListeners(AuditingEntityListener.class)
public class PostStar extends BaseAuditingEntity{ public class PostStar extends BaseAuditingEntity{
@Comment("POST ID") @Comment("POST ID")

View File

@ -3,10 +3,7 @@ package com.lk.paopao.service;
import com.lk.paopao.dto.PostDto; import com.lk.paopao.dto.PostDto;
import com.lk.paopao.dto.rest.request.PostRequest; import com.lk.paopao.dto.rest.request.PostRequest;
import com.lk.paopao.dto.rest.response.Paged; import com.lk.paopao.dto.rest.response.Paged;
import com.lk.paopao.entity.Post; import com.lk.paopao.entity.*;
import com.lk.paopao.entity.PostContent;
import com.lk.paopao.entity.Tag;
import com.lk.paopao.entity.User;
import com.lk.paopao.exception.ResourceNotFoundException; import com.lk.paopao.exception.ResourceNotFoundException;
import com.lk.paopao.mapper.PostMapper; import com.lk.paopao.mapper.PostMapper;
import com.lk.paopao.mapper.TagMapper; import com.lk.paopao.mapper.TagMapper;
@ -53,6 +50,7 @@ public class PostService {
@Autowired @Autowired
PostCollectionRepository postCollectionRepository; PostCollectionRepository postCollectionRepository;
private static final Logger log = LoggerFactory.getLogger(PostService.class); private static final Logger log = LoggerFactory.getLogger(PostService.class);
/** /**
* 根据文章ID获取文章详情 * 根据文章ID获取文章详情
@ -196,13 +194,84 @@ public class PostService {
return new Paged<>(dtos,posts.getTotalElements(),posts.getNumber()+1,posts.getSize()); return new Paged<>(dtos,posts.getTotalElements(),posts.getNumber()+1,posts.getSize());
} }
/**
* 判断当前用户是对指定文章否已点赞
* @param postId
* @return
*/
public boolean currentUserHasStarred(Long postId){ public boolean currentUserHasStarred(Long postId){
User user = authService.getCurrentUser(); User user = authService.getCurrentUser();
return postStarRepository.findByUserIdAndPostId(user.getId(), postId).isPresent(); return postStarRepository.findByUserIdAndPostId(user.getId(), postId).isPresent();
} }
/**
* 点赞文章
* @param postId 文章ID
* @return 如果点赞成功返回true如果已点过赞则返回false
*/
public boolean toggleStar(long postId){
// 获取当前用户
User user = authService.getCurrentUser();
// 确认文章存在
Post post = postRepository.findById(postId).orElseThrow(()->new ResourceNotFoundException("Post","id="+postId));
// 确认用户是否已点赞
PostStar postStar = postStarRepository.findByUserIdAndPostId(user.getId(),postId).orElse(null);
if(postStar==null){
// 如果未点赞则创建点赞记录并更新文章点赞数
postStar = new PostStar();
postStar.setPost(post);
postStar.setUser(user);
postStarRepository.save(postStar);
post.setUpvoteCount(post.getUpvoteCount()+1);
postRepository.save(post);
return true;
}
// 如果已点赞则删除点赞记录并更新文章点赞数
postStarRepository.delete(postStar);
post.setUpvoteCount(post.getUpvoteCount()-1);
postRepository.save(post);
return false;
}
/**
* 判断当前用户是否收藏了指定文章
* @param postId
* @return
*/
public boolean currentUserHasCollected(Long postId){ public boolean currentUserHasCollected(Long postId){
User user = authService.getCurrentUser(); User user = authService.getCurrentUser();
return postCollectionRepository.findByUserIdAndPostId(user.getId(), postId).isPresent(); return postCollectionRepository.findByUserIdAndPostId(user.getId(), postId).isPresent();
} }
/**
* 切换帖子的收藏状态
* 如果用户还未收藏该帖子则收藏它并增加帖子的收藏计数如果已经收藏则取消收藏并减少帖子的收藏计数
*
* @param postId 帖子的ID
* @return 如果收藏状态切换成功返回true如果之前已经是取消收藏的状态则返回false
*/
public boolean toggleCollection(Long postId){
// 获取当前用户
User user = authService.getCurrentUser();
// 查找帖子如果找不到则抛出异常
Post post = postRepository.findById(postId).orElseThrow(()->new ResourceNotFoundException("Post","id="+postId));
// 查找对应的收藏记录如果不存在则返回null
PostCollection collection = postCollectionRepository.findByUserIdAndPostId(user.getId(), postId).orElse(null);
if(collection==null){
// 创建新的收藏记录并保存
collection = new PostCollection();
collection.setPost(post);
collection.setUser(user);
postCollectionRepository.save(collection);
// 更新帖子的收藏计数
post.setUpvoteCount(post.getCollectionCount()+1);
postRepository.save(post);
return true;
}
// 如果已经存在收藏记录则删除收藏记录并更新帖子的收藏计数
postCollectionRepository.delete(collection);
post.setCollectionCount(post.getCollectionCount()-1);
postRepository.save(post);
return false;
}
} }