文件生成补充

This commit is contained in:
冯聪聪
2026-05-17 17:44:07 +08:00
parent 43b3928f16
commit db2f9d7f4b
166 changed files with 2651 additions and 3 deletions
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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);
}
@@ -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();
}
}
@@ -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);
}
}
Binary file not shown.
@@ -0,0 +1,3 @@
artifactId=jog-module-blog
groupId=fun.nojava
version=1.0.0
@@ -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
@@ -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