文件生成补充
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
||||
package fun.nojava.module.blog.controller;
|
||||
|
||||
import fun.nojava.common.model.ApiResult;
|
||||
import fun.nojava.common.model.PageResult;
|
||||
import fun.nojava.module.blog.dto.ArticleListVO;
|
||||
import fun.nojava.module.blog.service.ArticleService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "管理员-文章管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/article")
|
||||
@RequiredArgsConstructor
|
||||
public class AdminArticleController {
|
||||
|
||||
private final ArticleService articleService;
|
||||
|
||||
@Operation(summary = "管理文章列表")
|
||||
@GetMapping("/list")
|
||||
public ApiResult<PageResult<ArticleListVO>> listArticles(
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "15") int pageSize,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) Long categoryId,
|
||||
@RequestParam(required = false) String keyword) {
|
||||
return ApiResult.success(articleService.listAdminArticles(page, pageSize, status, categoryId, keyword));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除文章(管理员)")
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResult<Void> deleteArticle(@PathVariable Long id) {
|
||||
articleService.adminDeleteArticle(id);
|
||||
return ApiResult.success();
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package fun.nojava.module.blog.controller;
|
||||
|
||||
import fun.nojava.common.model.ApiResult;
|
||||
import fun.nojava.common.model.PageResult;
|
||||
import fun.nojava.module.blog.dto.*;
|
||||
import fun.nojava.module.blog.service.ArticleService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "用户文章接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/article")
|
||||
@RequiredArgsConstructor
|
||||
public class ArticleController {
|
||||
|
||||
private final ArticleService articleService;
|
||||
|
||||
@Operation(summary = "创建文章")
|
||||
@PostMapping
|
||||
public ApiResult<Long> createArticle(
|
||||
@AuthenticationPrincipal Long userId,
|
||||
@Valid @RequestBody CreateArticleDTO dto) {
|
||||
return ApiResult.success(articleService.createArticle(userId, dto));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新文章")
|
||||
@PutMapping("/{id}")
|
||||
public ApiResult<Void> updateArticle(
|
||||
@AuthenticationPrincipal Long userId,
|
||||
@PathVariable Long id,
|
||||
@Valid @RequestBody UpdateArticleDTO dto) {
|
||||
articleService.updateArticle(userId, id, dto);
|
||||
return ApiResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "我的文章列表")
|
||||
@GetMapping("/my")
|
||||
public ApiResult<PageResult<ArticleListVO>> listMyArticles(
|
||||
@AuthenticationPrincipal Long userId,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int pageSize,
|
||||
@RequestParam(required = false) Integer status) {
|
||||
return ApiResult.success(articleService.listUserArticles(userId, page, pageSize, status));
|
||||
}
|
||||
|
||||
@Operation(summary = "移至回收站")
|
||||
@PostMapping("/{id}/recycle")
|
||||
public ApiResult<Void> moveToRecycleBin(
|
||||
@AuthenticationPrincipal Long userId,
|
||||
@PathVariable Long id) {
|
||||
articleService.moveToRecycleBin(userId, id);
|
||||
return ApiResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "从回收站恢复")
|
||||
@PostMapping("/{id}/restore")
|
||||
public ApiResult<Void> restoreArticle(
|
||||
@AuthenticationPrincipal Long userId,
|
||||
@PathVariable Long id) {
|
||||
articleService.restoreArticle(userId, id);
|
||||
return ApiResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除文章")
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResult<Void> deleteArticle(
|
||||
@AuthenticationPrincipal Long userId,
|
||||
@PathVariable Long id) {
|
||||
articleService.deleteArticle(userId, id);
|
||||
return ApiResult.success();
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package fun.nojava.module.blog.controller;
|
||||
|
||||
import fun.nojava.common.model.ApiResult;
|
||||
import fun.nojava.module.blog.entity.Category;
|
||||
import fun.nojava.module.blog.service.CategoryService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "分类接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/public/category")
|
||||
@RequiredArgsConstructor
|
||||
class PublicCategoryController {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
@Operation(summary = "分类列表")
|
||||
@GetMapping("/list")
|
||||
public ApiResult<List<Category>> listCategories() {
|
||||
return ApiResult.success(categoryService.listAllCategories());
|
||||
}
|
||||
}
|
||||
|
||||
@Tag(name = "管理员-分类管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/category")
|
||||
@RequiredArgsConstructor
|
||||
class AdminCategoryController {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
@Operation(summary = "创建分类")
|
||||
@PostMapping
|
||||
public ApiResult<Category> createCategory(
|
||||
@RequestParam String name,
|
||||
@RequestParam(required = false) String description) {
|
||||
return ApiResult.success(categoryService.createCategory(name, description));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新分类")
|
||||
@PutMapping("/{id}")
|
||||
public ApiResult<Void> updateCategory(
|
||||
@PathVariable Long id,
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam(required = false) Boolean enabled) {
|
||||
categoryService.updateCategory(id, name, description, enabled);
|
||||
return ApiResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除分类")
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResult<Void> deleteCategory(@PathVariable Long id) {
|
||||
categoryService.deleteCategory(id);
|
||||
return ApiResult.success();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package fun.nojava.module.blog.controller;
|
||||
|
||||
import fun.nojava.common.model.ApiResult;
|
||||
import fun.nojava.common.model.PageResult;
|
||||
import fun.nojava.module.blog.dto.ArticleListVO;
|
||||
import fun.nojava.module.blog.dto.ArticleVO;
|
||||
import fun.nojava.module.blog.service.ArticleService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "公开文章接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/public/article")
|
||||
@RequiredArgsConstructor
|
||||
public class PublicArticleController {
|
||||
|
||||
private final ArticleService articleService;
|
||||
|
||||
@Operation(summary = "文章列表")
|
||||
@GetMapping("/list")
|
||||
public ApiResult<PageResult<ArticleListVO>> listArticles(
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int pageSize,
|
||||
@RequestParam(required = false) Long categoryId,
|
||||
@RequestParam(required = false) Long tagId,
|
||||
@RequestParam(required = false) String keyword) {
|
||||
return ApiResult.success(articleService.listPublicArticles(page, pageSize, categoryId, tagId, keyword));
|
||||
}
|
||||
|
||||
@Operation(summary = "文章详情")
|
||||
@GetMapping("/{id}")
|
||||
public ApiResult<ArticleVO> getArticle(
|
||||
@PathVariable Long id,
|
||||
@AuthenticationPrincipal Long userId) {
|
||||
articleService.incrementViewCount(id);
|
||||
return ApiResult.success(articleService.getArticleDetail(id, userId));
|
||||
}
|
||||
|
||||
@Operation(summary = "点赞文章")
|
||||
@PostMapping("/{id}/like")
|
||||
public ApiResult<Boolean> toggleLike(
|
||||
@PathVariable Long id,
|
||||
@AuthenticationPrincipal Long userId) {
|
||||
return ApiResult.success(articleService.toggleLike(userId, id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package fun.nojava.module.blog.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "文章列表项")
|
||||
public class ArticleListVO {
|
||||
|
||||
@Schema(description = "文章ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "摘要")
|
||||
private String summary;
|
||||
|
||||
@Schema(description = "封面图")
|
||||
private String coverImage;
|
||||
|
||||
@Schema(description = "浏览量")
|
||||
private Integer viewCount;
|
||||
|
||||
@Schema(description = "点赞数")
|
||||
private Integer likeCount;
|
||||
|
||||
@Schema(description = "评论数")
|
||||
private Integer commentCount;
|
||||
|
||||
@Schema(description = "作者ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "作者昵称")
|
||||
private String authorNickname;
|
||||
|
||||
@Schema(description = "作者头像")
|
||||
private String authorAvatar;
|
||||
|
||||
@Schema(description = "分类名称")
|
||||
private String categoryName;
|
||||
|
||||
@Schema(description = "发布时间")
|
||||
private LocalDateTime publishedAt;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package fun.nojava.module.blog.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ArticleVO {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String title;
|
||||
|
||||
private String summary;
|
||||
|
||||
private String content;
|
||||
|
||||
private String coverImage;
|
||||
|
||||
private Integer viewCount;
|
||||
|
||||
private Integer likeCount;
|
||||
|
||||
private Integer commentCount;
|
||||
|
||||
private Long categoryId;
|
||||
|
||||
private String categoryName;
|
||||
|
||||
private List<String> tags;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String authorNickname;
|
||||
|
||||
private String authorAvatar;
|
||||
|
||||
private LocalDateTime publishedAt;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
private Boolean liked;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package fun.nojava.module.blog.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class CreateArticleDTO {
|
||||
|
||||
@NotBlank(message = "标题不能为空")
|
||||
@Size(max = 200, message = "标题最长200字符")
|
||||
private String title;
|
||||
|
||||
private String summary;
|
||||
|
||||
@NotBlank(message = "内容不能为空")
|
||||
private String content;
|
||||
|
||||
private String coverImage;
|
||||
|
||||
private Long categoryId;
|
||||
|
||||
private List<Long> tagIds;
|
||||
|
||||
private Integer status = 0;
|
||||
|
||||
private Integer visibility = 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package fun.nojava.module.blog.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class UpdateArticleDTO {
|
||||
|
||||
@NotBlank(message = "标题不能为空")
|
||||
@Size(max = 200, message = "标题最长200字符")
|
||||
private String title;
|
||||
|
||||
private String summary;
|
||||
|
||||
@NotBlank(message = "内容不能为空")
|
||||
private String content;
|
||||
|
||||
private String coverImage;
|
||||
|
||||
private Long categoryId;
|
||||
|
||||
private List<Long> tagIds;
|
||||
|
||||
private Integer status;
|
||||
|
||||
private Integer visibility;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package fun.nojava.module.blog.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("article")
|
||||
public class Article {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private Long categoryId;
|
||||
|
||||
private String title;
|
||||
|
||||
private String summary;
|
||||
|
||||
private String content;
|
||||
|
||||
private String coverImage;
|
||||
|
||||
private Integer status;
|
||||
|
||||
private Integer visibility;
|
||||
|
||||
private Integer viewCount;
|
||||
|
||||
private Integer likeCount;
|
||||
|
||||
private Integer commentCount;
|
||||
|
||||
private LocalDateTime publishedAt;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package fun.nojava.module.blog.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("article_like")
|
||||
public class ArticleLike {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Long articleId;
|
||||
|
||||
private Long userId;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package fun.nojava.module.blog.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("article_tag")
|
||||
public class ArticleTag {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Long articleId;
|
||||
|
||||
private Long tagId;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package fun.nojava.module.blog.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("category")
|
||||
public class Category {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private Integer sort;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package fun.nojava.module.blog.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("tag")
|
||||
public class Tag {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package fun.nojava.module.blog.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.nojava.module.blog.entity.ArticleLike;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ArticleLikeMapper extends BaseMapper<ArticleLike> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package fun.nojava.module.blog.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.nojava.module.blog.entity.Article;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ArticleMapper extends BaseMapper<Article> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package fun.nojava.module.blog.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.nojava.module.blog.entity.ArticleTag;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ArticleTagMapper extends BaseMapper<ArticleTag> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package fun.nojava.module.blog.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.nojava.module.blog.entity.Category;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CategoryMapper extends BaseMapper<Category> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package fun.nojava.module.blog.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.nojava.module.blog.entity.Tag;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface TagMapper extends BaseMapper<Tag> {
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package fun.nojava.module.blog.service;
|
||||
|
||||
import fun.nojava.common.model.PageResult;
|
||||
import fun.nojava.module.blog.dto.*;
|
||||
|
||||
public interface ArticleService {
|
||||
|
||||
Long createArticle(Long userId, CreateArticleDTO dto);
|
||||
|
||||
void updateArticle(Long userId, Long articleId, UpdateArticleDTO dto);
|
||||
|
||||
ArticleVO getArticleDetail(Long articleId, Long currentUserId);
|
||||
|
||||
PageResult<ArticleListVO> listPublicArticles(int page, int pageSize, Long categoryId, Long tagId, String keyword);
|
||||
|
||||
PageResult<ArticleListVO> listUserArticles(Long userId, int page, int pageSize, Integer status);
|
||||
|
||||
PageResult<ArticleListVO> listAdminArticles(int page, int pageSize, Integer status, Long categoryId, String keyword);
|
||||
|
||||
void deleteArticle(Long userId, Long articleId);
|
||||
|
||||
void adminDeleteArticle(Long articleId);
|
||||
|
||||
void moveToRecycleBin(Long userId, Long articleId);
|
||||
|
||||
void restoreArticle(Long userId, Long articleId);
|
||||
|
||||
void incrementViewCount(Long articleId);
|
||||
|
||||
boolean toggleLike(Long userId, Long articleId);
|
||||
|
||||
boolean isLiked(Long userId, Long articleId);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package fun.nojava.module.blog.service;
|
||||
|
||||
import fun.nojava.module.blog.entity.Category;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CategoryService {
|
||||
|
||||
List<Category> listAllCategories();
|
||||
|
||||
Category createCategory(String name, String description);
|
||||
|
||||
void updateCategory(Long id, String name, String description, Boolean enabled);
|
||||
|
||||
void deleteCategory(Long id);
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
package fun.nojava.module.blog.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import fun.nojava.common.enums.ArticleStatus;
|
||||
import fun.nojava.common.enums.ArticleVisibility;
|
||||
import fun.nojava.common.exception.BusinessException;
|
||||
import fun.nojava.common.exception.ErrorCode;
|
||||
import fun.nojava.common.model.PageResult;
|
||||
import fun.nojava.module.blog.dto.*;
|
||||
import fun.nojava.module.blog.entity.*;
|
||||
import fun.nojava.module.blog.mapper.*;
|
||||
import fun.nojava.module.blog.service.ArticleService;
|
||||
import fun.nojava.module.system.entity.User;
|
||||
import fun.nojava.module.system.mapper.UserMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ArticleServiceImpl implements ArticleService {
|
||||
|
||||
private final ArticleMapper articleMapper;
|
||||
private final CategoryMapper categoryMapper;
|
||||
private final TagMapper tagMapper;
|
||||
private final ArticleTagMapper articleTagMapper;
|
||||
private final ArticleLikeMapper articleLikeMapper;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long createArticle(Long userId, CreateArticleDTO dto) {
|
||||
Article article = new Article();
|
||||
article.setUserId(userId);
|
||||
article.setCategoryId(dto.getCategoryId());
|
||||
article.setTitle(dto.getTitle());
|
||||
article.setSummary(dto.getSummary());
|
||||
article.setContent(dto.getContent());
|
||||
article.setCoverImage(dto.getCoverImage());
|
||||
article.setStatus(dto.getStatus());
|
||||
article.setVisibility(dto.getVisibility());
|
||||
article.setViewCount(0);
|
||||
article.setLikeCount(0);
|
||||
article.setCommentCount(0);
|
||||
article.setDeleted(0);
|
||||
|
||||
if (dto.getStatus() == ArticleStatus.PUBLISHED.getCode()) {
|
||||
article.setPublishedAt(LocalDateTime.now());
|
||||
}
|
||||
|
||||
articleMapper.insert(article);
|
||||
|
||||
if (dto.getTagIds() != null && !dto.getTagIds().isEmpty()) {
|
||||
for (Long tagId : dto.getTagIds()) {
|
||||
ArticleTag articleTag = new ArticleTag();
|
||||
articleTag.setArticleId(article.getId());
|
||||
articleTag.setTagId(tagId);
|
||||
articleTagMapper.insert(articleTag);
|
||||
}
|
||||
}
|
||||
|
||||
return article.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateArticle(Long userId, Long articleId, UpdateArticleDTO dto) {
|
||||
Article article = getArticleAndCheckOwner(userId, articleId);
|
||||
|
||||
article.setTitle(dto.getTitle());
|
||||
article.setSummary(dto.getSummary());
|
||||
article.setContent(dto.getContent());
|
||||
article.setCoverImage(dto.getCoverImage());
|
||||
article.setCategoryId(dto.getCategoryId());
|
||||
if (dto.getStatus() != null) {
|
||||
article.setStatus(dto.getStatus());
|
||||
if (dto.getStatus() == ArticleStatus.PUBLISHED.getCode() && article.getPublishedAt() == null) {
|
||||
article.setPublishedAt(LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
if (dto.getVisibility() != null) {
|
||||
article.setVisibility(dto.getVisibility());
|
||||
}
|
||||
|
||||
articleMapper.updateById(article);
|
||||
|
||||
if (dto.getTagIds() != null) {
|
||||
articleTagMapper.delete(new LambdaQueryWrapper<ArticleTag>().eq(ArticleTag::getArticleId, articleId));
|
||||
for (Long tagId : dto.getTagIds()) {
|
||||
ArticleTag articleTag = new ArticleTag();
|
||||
articleTag.setArticleId(articleId);
|
||||
articleTag.setTagId(tagId);
|
||||
articleTagMapper.insert(articleTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArticleVO getArticleDetail(Long articleId, Long currentUserId) {
|
||||
Article article = articleMapper.selectById(articleId);
|
||||
if (article == null) {
|
||||
throw new BusinessException(ErrorCode.ARTICLE_NOT_FOUND);
|
||||
}
|
||||
|
||||
return buildArticleVO(article, currentUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ArticleListVO> listPublicArticles(int page, int pageSize, Long categoryId, Long tagId, String keyword) {
|
||||
LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<Article>()
|
||||
.eq(Article::getStatus, ArticleStatus.PUBLISHED.getCode())
|
||||
.eq(Article::getVisibility, ArticleVisibility.PUBLIC.getCode());
|
||||
|
||||
if (categoryId != null) {
|
||||
wrapper.eq(Article::getCategoryId, categoryId);
|
||||
}
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
wrapper.and(w -> w.like(Article::getTitle, keyword).or().like(Article::getSummary, keyword));
|
||||
}
|
||||
if (tagId != null) {
|
||||
List<Long> articleIds = articleTagMapper.selectList(
|
||||
new LambdaQueryWrapper<ArticleTag>().eq(ArticleTag::getTagId, tagId)
|
||||
).stream().map(ArticleTag::getArticleId).toList();
|
||||
if (articleIds.isEmpty()) {
|
||||
return new PageResult<>(Collections.emptyList(), 0, page, pageSize);
|
||||
}
|
||||
wrapper.in(Article::getId, articleIds);
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(Article::getPublishedAt);
|
||||
Page<Article> articlePage = articleMapper.selectPage(new Page<>(page, pageSize), wrapper);
|
||||
|
||||
List<ArticleListVO> records = articlePage.getRecords().stream()
|
||||
.map(this::buildArticleListVO)
|
||||
.toList();
|
||||
|
||||
return new PageResult<>(records, articlePage.getTotal(), page, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ArticleListVO> listUserArticles(Long userId, int page, int pageSize, Integer status) {
|
||||
LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<Article>()
|
||||
.eq(Article::getUserId, userId);
|
||||
if (status != null) {
|
||||
wrapper.eq(Article::getStatus, status);
|
||||
}
|
||||
wrapper.orderByDesc(Article::getCreatedAt);
|
||||
|
||||
Page<Article> articlePage = articleMapper.selectPage(new Page<>(page, pageSize), wrapper);
|
||||
List<ArticleListVO> records = articlePage.getRecords().stream()
|
||||
.map(this::buildArticleListVO)
|
||||
.toList();
|
||||
|
||||
return new PageResult<>(records, articlePage.getTotal(), page, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ArticleListVO> listAdminArticles(int page, int pageSize, Integer status, Long categoryId, String keyword) {
|
||||
LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>();
|
||||
if (status != null) {
|
||||
wrapper.eq(Article::getStatus, status);
|
||||
}
|
||||
if (categoryId != null) {
|
||||
wrapper.eq(Article::getCategoryId, categoryId);
|
||||
}
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
wrapper.and(w -> w.like(Article::getTitle, keyword).or().like(Article::getSummary, keyword));
|
||||
}
|
||||
wrapper.orderByDesc(Article::getCreatedAt);
|
||||
|
||||
Page<Article> articlePage = articleMapper.selectPage(new Page<>(page, pageSize), wrapper);
|
||||
List<ArticleListVO> records = articlePage.getRecords().stream()
|
||||
.map(this::buildArticleListVO)
|
||||
.toList();
|
||||
|
||||
return new PageResult<>(records, articlePage.getTotal(), page, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteArticle(Long userId, Long articleId) {
|
||||
Article article = getArticleAndCheckOwner(userId, articleId);
|
||||
articleMapper.deleteById(articleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void adminDeleteArticle(Long articleId) {
|
||||
Article article = articleMapper.selectById(articleId);
|
||||
if (article == null) {
|
||||
throw new BusinessException(ErrorCode.ARTICLE_NOT_FOUND);
|
||||
}
|
||||
articleMapper.deleteById(articleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void moveToRecycleBin(Long userId, Long articleId) {
|
||||
Article article = getArticleAndCheckOwner(userId, articleId);
|
||||
article.setStatus(ArticleStatus.RECYCLE.getCode());
|
||||
article.setUpdatedAt(LocalDateTime.now());
|
||||
articleMapper.updateById(article);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void restoreArticle(Long userId, Long articleId) {
|
||||
Article article = getArticleAndCheckOwner(userId, articleId);
|
||||
article.setStatus(ArticleStatus.DRAFT.getCode());
|
||||
article.setUpdatedAt(LocalDateTime.now());
|
||||
articleMapper.updateById(article);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementViewCount(Long articleId) {
|
||||
Article article = articleMapper.selectById(articleId);
|
||||
if (article != null) {
|
||||
article.setViewCount(article.getViewCount() + 1);
|
||||
articleMapper.updateById(article);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean toggleLike(Long userId, Long articleId) {
|
||||
ArticleLike existing = articleLikeMapper.selectOne(
|
||||
new LambdaQueryWrapper<ArticleLike>()
|
||||
.eq(ArticleLike::getArticleId, articleId)
|
||||
.eq(ArticleLike::getUserId, userId)
|
||||
);
|
||||
|
||||
Article article = articleMapper.selectById(articleId);
|
||||
if (article == null) {
|
||||
throw new BusinessException(ErrorCode.ARTICLE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (existing != null) {
|
||||
articleLikeMapper.deleteById(existing.getId());
|
||||
article.setLikeCount(article.getLikeCount() - 1);
|
||||
articleMapper.updateById(article);
|
||||
return false;
|
||||
} else {
|
||||
ArticleLike like = new ArticleLike();
|
||||
like.setArticleId(articleId);
|
||||
like.setUserId(userId);
|
||||
articleLikeMapper.insert(like);
|
||||
article.setLikeCount(article.getLikeCount() + 1);
|
||||
articleMapper.updateById(article);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLiked(Long userId, Long articleId) {
|
||||
if (userId == null) return false;
|
||||
return articleLikeMapper.selectCount(
|
||||
new LambdaQueryWrapper<ArticleLike>()
|
||||
.eq(ArticleLike::getArticleId, articleId)
|
||||
.eq(ArticleLike::getUserId, userId)
|
||||
) > 0;
|
||||
}
|
||||
|
||||
private Article getArticleAndCheckOwner(Long userId, Long articleId) {
|
||||
Article article = articleMapper.selectById(articleId);
|
||||
if (article == null) {
|
||||
throw new BusinessException(ErrorCode.ARTICLE_NOT_FOUND);
|
||||
}
|
||||
if (!article.getUserId().equals(userId)) {
|
||||
throw new BusinessException(ErrorCode.PERMISSION_DENIED);
|
||||
}
|
||||
return article;
|
||||
}
|
||||
|
||||
private ArticleVO buildArticleVO(Article article, Long currentUserId) {
|
||||
User author = userMapper.selectById(article.getUserId());
|
||||
Category category = article.getCategoryId() != null ? categoryMapper.selectById(article.getCategoryId()) : null;
|
||||
|
||||
List<String> tags = articleTagMapper.selectList(
|
||||
new LambdaQueryWrapper<ArticleTag>().eq(ArticleTag::getArticleId, article.getId())
|
||||
).stream().map(at -> {
|
||||
Tag tag = tagMapper.selectById(at.getTagId());
|
||||
return tag != null ? tag.getName() : null;
|
||||
}).filter(t -> t != null).toList();
|
||||
|
||||
return ArticleVO.builder()
|
||||
.id(article.getId())
|
||||
.title(article.getTitle())
|
||||
.summary(article.getSummary())
|
||||
.content(article.getContent())
|
||||
.coverImage(article.getCoverImage())
|
||||
.viewCount(article.getViewCount())
|
||||
.likeCount(article.getLikeCount())
|
||||
.commentCount(article.getCommentCount())
|
||||
.categoryId(article.getCategoryId())
|
||||
.categoryName(category != null ? category.getName() : null)
|
||||
.tags(tags)
|
||||
.userId(article.getUserId())
|
||||
.authorNickname(author != null ? author.getNickname() : null)
|
||||
.authorAvatar(author != null ? author.getAvatar() : null)
|
||||
.publishedAt(article.getPublishedAt())
|
||||
.createdAt(article.getCreatedAt())
|
||||
.liked(isLiked(currentUserId, article.getId()))
|
||||
.build();
|
||||
}
|
||||
|
||||
private ArticleListVO buildArticleListVO(Article article) {
|
||||
User author = userMapper.selectById(article.getUserId());
|
||||
Category category = article.getCategoryId() != null ? categoryMapper.selectById(article.getCategoryId()) : null;
|
||||
|
||||
return ArticleListVO.builder()
|
||||
.id(article.getId())
|
||||
.title(article.getTitle())
|
||||
.summary(article.getSummary())
|
||||
.coverImage(article.getCoverImage())
|
||||
.viewCount(article.getViewCount())
|
||||
.likeCount(article.getLikeCount())
|
||||
.commentCount(article.getCommentCount())
|
||||
.userId(article.getUserId())
|
||||
.authorNickname(author != null ? author.getNickname() : null)
|
||||
.authorAvatar(author != null ? author.getAvatar() : null)
|
||||
.categoryName(category != null ? category.getName() : null)
|
||||
.publishedAt(article.getPublishedAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package fun.nojava.module.blog.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import fun.nojava.module.blog.entity.Category;
|
||||
import fun.nojava.module.blog.mapper.CategoryMapper;
|
||||
import fun.nojava.module.blog.service.CategoryService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CategoryServiceImpl implements CategoryService {
|
||||
|
||||
private final CategoryMapper categoryMapper;
|
||||
|
||||
@Override
|
||||
public List<Category> listAllCategories() {
|
||||
return categoryMapper.selectList(
|
||||
new LambdaQueryWrapper<Category>()
|
||||
.eq(Category::getEnabled, true)
|
||||
.orderByAsc(Category::getSort)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category createCategory(String name, String description) {
|
||||
Category category = new Category();
|
||||
category.setName(name);
|
||||
category.setDescription(description);
|
||||
category.setSort(0);
|
||||
category.setEnabled(true);
|
||||
categoryMapper.insert(category);
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCategory(Long id, String name, String description, Boolean enabled) {
|
||||
Category category = categoryMapper.selectById(id);
|
||||
if (category != null) {
|
||||
if (name != null) category.setName(name);
|
||||
if (description != null) category.setDescription(description);
|
||||
if (enabled != null) category.setEnabled(enabled);
|
||||
categoryMapper.updateById(category);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteCategory(Long id) {
|
||||
categoryMapper.deleteById(id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user