文件生成补充
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);
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
artifactId=jog-module-blog
|
||||
groupId=fun.nojava
|
||||
version=1.0.0
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
fun\nojava\module\blog\entity\ArticleTag.class
|
||||
fun\nojava\module\blog\service\ArticleService.class
|
||||
fun\nojava\module\blog\controller\PublicArticleController.class
|
||||
fun\nojava\module\blog\controller\PublicCategoryController.class
|
||||
fun\nojava\module\blog\dto\UpdateArticleDTO.class
|
||||
fun\nojava\module\blog\service\CategoryService.class
|
||||
fun\nojava\module\blog\dto\ArticleListVO.class
|
||||
fun\nojava\module\blog\entity\Category.class
|
||||
fun\nojava\module\blog\dto\CreateArticleDTO.class
|
||||
fun\nojava\module\blog\service\impl\ArticleServiceImpl.class
|
||||
fun\nojava\module\blog\dto\ArticleVO$ArticleVOBuilder.class
|
||||
fun\nojava\module\blog\controller\AdminCategoryController.class
|
||||
fun\nojava\module\blog\service\impl\CategoryServiceImpl.class
|
||||
fun\nojava\module\blog\dto\ArticleVO.class
|
||||
fun\nojava\module\blog\entity\ArticleLike.class
|
||||
fun\nojava\module\blog\controller\AdminArticleController.class
|
||||
fun\nojava\module\blog\controller\ArticleController.class
|
||||
fun\nojava\module\blog\mapper\TagMapper.class
|
||||
fun\nojava\module\blog\mapper\ArticleLikeMapper.class
|
||||
fun\nojava\module\blog\dto\ArticleListVO$ArticleListVOBuilder.class
|
||||
fun\nojava\module\blog\entity\Tag.class
|
||||
fun\nojava\module\blog\mapper\ArticleMapper.class
|
||||
fun\nojava\module\blog\entity\Article.class
|
||||
fun\nojava\module\blog\mapper\CategoryMapper.class
|
||||
fun\nojava\module\blog\mapper\ArticleTagMapper.class
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\controller\AdminArticleController.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\controller\ArticleController.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\controller\CategoryController.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\controller\PublicArticleController.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\dto\ArticleListVO.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\dto\ArticleVO.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\dto\CreateArticleDTO.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\dto\UpdateArticleDTO.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\entity\Article.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\entity\ArticleLike.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\entity\ArticleTag.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\entity\Category.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\entity\Tag.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\mapper\ArticleLikeMapper.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\mapper\ArticleMapper.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\mapper\ArticleTagMapper.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\mapper\CategoryMapper.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\mapper\TagMapper.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\service\ArticleService.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\service\CategoryService.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\service\impl\ArticleServiceImpl.java
|
||||
E:\person\Jog\jog-module-blog\src\main\java\fun\nojava\module\blog\service\impl\CategoryServiceImpl.java
|
||||
Reference in New Issue
Block a user