feat(service): 新增分类相关接口并实现功能

- 新增 CategoryService 接口,定义了分类相关操作
- 实现 CategoryServiceImpl 类,完成分类树构建、分类列表获取、分类增删改等功能
- 新增多个分类相关的 DTO 类,用于分类操作的数据传输- 更新 CategoryMapper 接口,调整了部分方法签名
This commit is contained in:
LingandRX 2025-05-08 22:33:45 +08:00
parent 8455307731
commit f08aae0f7b
28 changed files with 698 additions and 41 deletions

View File

@ -12,13 +12,13 @@ public interface CategoryMapper {
int insertBatch(@Param("entities") List<Category> entities); int insertBatch(@Param("entities") List<Category> entities);
List<Category> categoryList(@Param("entities") List<Category> entities); List<Category> categoryList();
Category findById(@Param("id") Integer id); Category findById(@Param("id") Integer id);
Category findByIdBatch(@Param("ids") List<Integer> ids); Category findByIdBatch(@Param("ids") List<Integer> ids);
Category findByIdOrParentId(@Param("id") Integer id, @Param("parentId") Integer parentId); List<Category> findByIdOrParentId(Integer categoryId);
int deleteById(@Param("id") Integer id); int deleteById(@Param("id") Integer id);

View File

@ -1,11 +1,102 @@
package com.example.copykamanotes.mapper; package com.example.copykamanotes.mapper;
import com.example.copykamanotes.model.dto.question.QuestionQueryParam;
import com.example.copykamanotes.model.entity.Question; import com.example.copykamanotes.model.entity.Question;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map; import java.util.Map;
@Mapper @Mapper
public interface QuestionMapper { public interface QuestionMapper {
Map<Integer, Question> getQuestionMapByIds(Integer[] questionIds); /**
* 插入一个问题对象到数据库中
*
* @param question 要插入的问题对象包含问题的相关信息
* @return 插入成功返回1否则返回0
*/
int insert(Question question);
/**
* 根据问题ID查找问题
*
* @param questionId 问题的唯一标识符
* @return 返回找到的问题对象如果没有找到则返回null
*/
Question findById(@Param("questionId") Integer questionId);
/**
* 批量通过问题 ID查找问题
* 此方法允许一次性传入多个问题ID从而批量获取问题信息
*
* @param questionIds 一个问题ID的列表用于指定需要查找的问题
* @return 返回一个Question对象的列表每个对象包含一个问题的详细信息
*/
List<Question> findByIdBatch(@Param("questionIds") List<Integer> questionIds);
/**
* 根据查询参数获取问题列表
*
* @param queryParam 查询参数对象包含多种可能的查询条件
* @param offset 分页查询的起始位置
* @param limit 每页返回的最大记录数
* @return 匹配查询条件的问题列表
*/
List<Question> findByQueryParam(@Param("queryParam") QuestionQueryParam queryParam,
@Param("offset") int offset,
@Param("limit") int limit);
/**
* 根据关键字搜索问题
*
* @param keyword 关键字用于匹配问题标题或内容
* @return 匹配关键字的问题列表
*/
List<Question> findByKeyword(@Param("keyword") String keyword);
/**
* 更新问题
* @param question 问题对象包含要更新的问题信息
* @return 更新成功返回1否则返回0
*/
int update(@Param("question") Question question);
/**
* 更新问题的浏览次数
* @param questionId 问题ID
* @return 更新成功返回1否则返回0
*/
int incrementViewCount(@Param("questionId") Integer questionId);
/**
* 根据查询参数统计问题的数量
*
* @param queryParam 查询参数对象包含多个查询条件
* @return 满足查询条件的问题数量
*/
int countByQueryParam(@Param("queryParam") QuestionQueryParam queryParam);
/**
* 根据问题ID删除问题
*
* @param questionId 需要删除的问题的ID
*/
int deleteById(Integer questionId);
/**
* 根据分类ID删除相关记录
* 此方法旨在删除与给定分类ID 关联的实体
*
* @param categoryId 分类ID用于标识要删除的记录
*/
int deleteByCategoryId(Integer categoryId);
/**
* 批量删除指定分类ID的实体
* 通过分类ID列表来删除实体主要用于批量操作场景
*
* @param categoryIds 分类ID列表用于指定待删除实体的分类
*/
int deleteByCategoryIdBatch(@Param("categoryIds") List<Integer> categoryIds);
} }

View File

@ -0,0 +1,21 @@
package com.example.copykamanotes.model.dto.category;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class CreateCategoryBody {
@NotBlank(message = "name 不能为空")
@NotNull(message = "name 不能为空")
@Length(max = 32, min = 1, message = "name 长度在 1 - 32 之间")
private String name;
@NotNull(message = "parentCategoryId 不能为空")
@Min(value = 0, message = "parentCategoryId 必须为正整数")
private Integer parentCategoryId;
}

View File

@ -0,0 +1,15 @@
package com.example.copykamanotes.model.dto.category;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class UpdateCategoryBody {
@NotBlank(message = "name 不能为空")
@NotNull(message = "name 不能为空")
@Length(max = 32, min = 1, message = "name 长度在 1 - 32 之间")
private String name;
}

View File

@ -0,0 +1,16 @@
package com.example.copykamanotes.model.dto.collection;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
public class CollectionQueryParams {
@NotNull(message = "creatorId 不能为空")
@Min(value = 1, message = "creatorId 必须为正整数")
private Long creatorId;
@Min(value = 1, message = "noteId 必须为正整数")
private Integer noteId;
}

View File

@ -0,0 +1,14 @@
package com.example.copykamanotes.model.dto.collection;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class CreateCollectionBody {
@NotNull(message = "name 不能为空")
@NotBlank(message = "name 不能为空")
private String name;
private String description;
}

View File

@ -0,0 +1,27 @@
package com.example.copykamanotes.model.dto.collection;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@Data
public class UpdateCollectionBody {
@Min(value = 1, message = "noteId 必须为正整数")
private Integer noteId;
private UpdateItem[] collections;
@Data
public static class UpdateItem {
@Min(value = 1, message = "collectionId 必须为正整数")
private Integer collectionId;
// 必须为 create 或者 delete
@NotNull(message = "action 不能为空")
@NotEmpty(message = "action 不能为空")
@Pattern(regexp = "create|delete", message = "action 必须为 create 或者 delete")
private String action;
}
}

View File

@ -0,0 +1,29 @@
package com.example.copykamanotes.model.dto.comment;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 创建评论请求
*/
@Data
public class CreateCommentRequest {
/**
* 笔记ID
*/
@NotNull(message = "笔记ID不能为空")
private Integer noteId;
/**
* 父评论ID
*/
private Integer parentId;
/**
* 评论内容
*/
@NotBlank(message = "评论内容不能为空")
private String content;
}

View File

@ -0,0 +1,17 @@
package com.example.copykamanotes.model.dto.comment;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 更新评论请求
*/
@Data
public class UpdateCommentRequest {
/**
* 评论内容
*/
@NotBlank(message = "评论内容不能为空")
private String content;
}

View File

@ -0,0 +1,53 @@
package com.example.copykamanotes.model.dto.message;
import lombok.Data;
import javax.validation.constraints.Min;
import java.time.LocalDateTime;
/**
* 消息查询参数
*/
@Data
public class MessageQueryParams {
/**
* 消息类型
*/
private String type;
/**
* 是否已读
*/
private Boolean isRead;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* 当前页码从1开始
*/
@Min(value = 1, message = "页码必须大于0")
private Integer page = 1;
/**
* 每页大小
*/
@Min(value = 1, message = "每页大小必须大于0")
private Integer pageSize = 10;
/**
* 排序字段默认创建时间
*/
private String sortField = "created_at";
/**
* 排序方向默认降序
*/
private String sortOrder = "desc";
}

View File

@ -1,12 +0,0 @@
package com.example.copykamanotes.model.dto.note;
import lombok.Data;
import java.time.LocalDate;
@Data
public class NoteHeatMapItem {
private LocalDate data;
private Integer count;
private Integer rank;
}

View File

@ -1,8 +0,0 @@
package com.example.copykamanotes.model.dto.note;
import lombok.Data;
@Data
public class NoteRankListItem {
}

View File

@ -1,10 +0,0 @@
package com.example.copykamanotes.model.dto.note;
import lombok.Data;
@Data
public class Top3Count {
private Integer lastMonthTop3Count;
private Integer thisMonthTop3Count;
}

View File

@ -0,0 +1,14 @@
package com.example.copykamanotes.model.dto.notification;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
public class NotificationDTO {
@NotEmpty(message = "content 不能为空")
@NotNull(message = "content 不能为空")
private String content;
}

View File

@ -0,0 +1,28 @@
package com.example.copykamanotes.model.dto.question;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class CreateQuestionBody {
@NotNull(message = "categoryId 不能为空")
@Min(value = 1, message = "categoryId 必须为正整数")
private Integer categoryId;
@NotNull(message = "title 不能为空")
@NotBlank(message = "title 不能为空")
@Length(max = 255, message = "title 长度不能超过 255")
private String title;
@NotNull(message = "difficulty 不能为空")
@Range(min = 1, max = 3, message = "difficulty 必须为 1, 2, 3")
private Integer difficulty;
@Length(max = 255, message = "examPoint 长度不能超过 255")
private String examPoint;
}

View File

@ -0,0 +1,29 @@
package com.example.copykamanotes.model.dto.question;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@Data
public class QuestionQueryParam {
@Min(value = 1, message = "categoryId 必须为正整数")
private Integer categoryId;
@Pattern(regexp = "^(view|difficulty)$", message = "sort 必须为 view 或 difficulty")
private String sort;
@Pattern(regexp = "^(asc|desc)$", message = "order 必须为 asc 或 desc")
private String order;
@NotNull(message = "page 不能为空")
@Min(value = 1, message = "page 必须为正整数")
private Integer page;
@NotNull(message = "pageSize 不能为空")
@Min(value = 1, message = "pageSize 必须为正整数")
@Max(value = 200, message = "pageSize 不能超过 200")
private Integer pageSize;
}

View File

@ -0,0 +1,15 @@
package com.example.copykamanotes.model.dto.question;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
public class SearchQuestionBody {
@NotNull(message = "keyword 不能为空")
@NotEmpty(message = "keyword 不能为空")
@Length(min = 1, max = 32, message = "keyword 长度在 1 和 32 范围内")
private String keyword;
}

View File

@ -0,0 +1,34 @@
package com.example.copykamanotes.model.dto.question;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class UpdateQuestionBody {
/*
* 问题标题
*/
@NotNull(message = "title 不能为空")
@NotBlank(message = "title 不能为空")
@Length(max = 255, message = "title 长度不能超过 255")
private String title;
/*
* 问题难度
* 1=简单2=中等3=困难
*/
@NotNull(message = "difficulty 不能为空")
@Range(min = 1, max = 3, message = "difficulty 必须为 1, 2, 3")
private Integer difficulty;
/*
* 题目考点
*/
@Length(max = 255, message = "examPoint 长度不能超过 255")
private String examPoint;
}

View File

@ -0,0 +1,26 @@
package com.example.copykamanotes.model.dto.questionList;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
@Data
public class CreateQuestionListBody {
/*
* 题单名称
*/
@Length(max = 32, message = "name 长度不能超过 32")
private String name;
/**
* 题单类型
*/
@Range(min = 1, max = 2, message = "type 必须为 1 或 2")
private Integer type;
/*
* 题单描述
*/
@Length(max = 255, message = "description 长度不能超过 255")
private String description;
}

View File

@ -0,0 +1,27 @@
package com.example.copykamanotes.model.dto.questionList;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
@Data
public class UpdateQuestionListBody {
/*
* 题单名称
*/
@Length(max = 32, message = "name 长度不能超过 32")
private String name;
/**
* 题单类型
*/
@Range(min = 1, max = 2, message = "type 必须为 1 或 2")
private Integer type;
/*
* 题单描述
*/
@Length(max = 255, message = "description 长度不能超过 255")
private String description;
}

View File

@ -0,0 +1,17 @@
package com.example.copykamanotes.model.dto.questionListItem;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
public class CreateQuestionListItemBody {
@NotNull(message = "questionListId 不能为空")
@Min(value = 1, message = "questionListId 必须为正整数")
private Integer questionListId;
@NotNull(message = "questionId 不能为空")
@Min(value = 1, message = "questionId 必须为正整数")
private Integer questionId;
}

View File

@ -0,0 +1,23 @@
package com.example.copykamanotes.model.dto.questionListItem;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
public class QuestionListItemQueryParams {
@NotNull(message = "questionListId 不能为空")
@Min(value = 1, message = "questionListId 必须为正整数")
private Integer questionListId;
@NotNull(message = "page 不能为空")
@Min(value = 1, message = "page 必须为正整数")
private Integer page;
@NotNull(message = "pageSize 不能为空")
@Range(min = 1, max = 100, message = "pageSize 必须为 1 到 100 之间的整数")
private Integer pageSize;
}

View File

@ -0,0 +1,17 @@
package com.example.copykamanotes.model.dto.questionListItem;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
public class SortQuestionListItemBody {
@NotNull(message = "questionListId 不能为空")
@Min(value = 1, message = "questionListId 必须为正整数")
private Integer questionListId;
@NotNull(message = "questionListItemIds 不能为空")
private List<Integer> questionIds;
}

View File

@ -0,0 +1,15 @@
package com.example.copykamanotes.model.enums.questionList;
import lombok.Getter;
@Getter
public enum QuestionListType {
COMMON(1, "普通题单"),
TRAINING_CAMP(2, "训练营题单");
private final Integer type;
private final String desc;
QuestionListType(Integer type, String desc) {
this.type = type;
this.desc = desc;
}
}

View File

@ -1,4 +1,41 @@
package com.example.copykamanotes.service; package com.example.copykamanotes.service;
import com.example.copykamanotes.model.base.ApiResponse;
import com.example.copykamanotes.model.base.EmptyVO;
import com.example.copykamanotes.model.dto.category.CreateCategoryBody;
import com.example.copykamanotes.model.dto.category.UpdateCategoryBody;
import com.example.copykamanotes.model.vo.category.CategoryVO;
import com.example.copykamanotes.model.vo.category.CreateCategoryVO;
import java.util.List;
public interface CategoryService { public interface CategoryService {
/**
* 构建分类树
* @return 分类树
*/
List<CategoryVO> buildCategoryTree();
/**
* 获取所有分类
* @return 所有分类
*/
ApiResponse<List<CategoryVO>> categoryList();
/**
* 删除分类
* @param id 分类id
* @return
*/
ApiResponse<EmptyVO> deleteCategory(Integer id);
/**
* 新增分类
* @param categoryBody 分类
* @return
*/
ApiResponse<CreateCategoryVO> createCategory(CreateCategoryBody categoryBody);
ApiResponse<EmptyVO> updateCategory(Integer id, UpdateCategoryBody updateCategoryBody);
} }

View File

@ -1,13 +1,9 @@
package com.example.copykamanotes.service; package com.example.copykamanotes.service;
import com.example.copykamanotes.mapper.NoteMapper;
import com.example.copykamanotes.model.base.ApiResponse; import com.example.copykamanotes.model.base.ApiResponse;
import com.example.copykamanotes.model.base.EmptyVO; import com.example.copykamanotes.model.base.EmptyVO;
import com.example.copykamanotes.model.dto.note.*; import com.example.copykamanotes.model.dto.note.*;
import com.example.copykamanotes.model.vo.note.CreateNoteVO; import com.example.copykamanotes.model.vo.note.*;
import com.example.copykamanotes.model.vo.note.DownloadNoteVO;
import com.example.copykamanotes.model.vo.note.NoteVO;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List; import java.util.List;

View File

@ -0,0 +1,128 @@
package com.example.copykamanotes.service.impl;
import com.example.copykamanotes.mapper.CategoryMapper;
import com.example.copykamanotes.mapper.QuestionMapper;
import com.example.copykamanotes.model.base.ApiResponse;
import com.example.copykamanotes.model.base.EmptyVO;
import com.example.copykamanotes.model.dto.category.CreateCategoryBody;
import com.example.copykamanotes.model.dto.category.UpdateCategoryBody;
import com.example.copykamanotes.model.entity.Category;
import com.example.copykamanotes.model.vo.category.CategoryVO;
import com.example.copykamanotes.model.vo.category.CreateCategoryVO;
import com.example.copykamanotes.service.CategoryService;
import com.example.copykamanotes.utils.ApiResponseUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private QuestionMapper questionMapper;
@Override
public ApiResponse<EmptyVO> updateCategory(Integer id, UpdateCategoryBody updateCategoryBody) {
Category category = categoryMapper.findById(id);
if (category == null) {
return ApiResponseUtils.error("分类 Id 不存在");
}
category.setName(updateCategoryBody.getName());
try {
categoryMapper.update(category);
return ApiResponseUtils.success("更新分类成功");
} catch (Exception e) {
return ApiResponseUtils.error("更新分类失败");
}
}
@Override
public List<CategoryVO> buildCategoryTree() {
List<Category> categories = categoryMapper.categoryList();
Map<Integer, CategoryVO> categoryMap = new HashMap<>();
categories.forEach(category -> {
if (category.getParentCategoryId() == 0) {
CategoryVO categoryVO = new CategoryVO();
BeanUtils.copyProperties(category, categoryVO);
categoryVO.setChildren(new ArrayList<>());
categoryMap.put(category.getCategoryId(), categoryVO);
} else {
CategoryVO.ChildrenCategoryVO childrenCategoryVO = new CategoryVO.ChildrenCategoryVO();
BeanUtils.copyProperties(category, childrenCategoryVO);
CategoryVO parentCategory = categoryMap.get(category.getParentCategoryId());
if (parentCategory != null) {
parentCategory.getChildren().add(childrenCategoryVO);
}
}
});
return new ArrayList<>(categoryMap.values());
}
@Override
public ApiResponse<List<CategoryVO>> categoryList() {
return ApiResponseUtils.success("获取分类列表成功", buildCategoryTree());
}
@Override
public ApiResponse<EmptyVO> deleteCategory(Integer id) {
List<Category> categories = categoryMapper.findByIdOrParentId(id);
if (categories.isEmpty()) {
return ApiResponseUtils.error("分类 Id 非法");
}
List<Integer> categoryIds = categories.stream().map(Category::getCategoryId).toList();
try {
int deleteCount = categoryMapper.deleteByIdBatch(categoryIds);
if (deleteCount != categories.size()) {
throw new RuntimeException("删除分类失败");
}
questionMapper.deleteByCategoryIdBatch(categoryIds);
return ApiResponseUtils.success("删除分类成功");
} catch (Exception e) {
throw new RuntimeException("删除分类失败");
}
}
@Override
public ApiResponse<CreateCategoryVO> createCategory(CreateCategoryBody createCategoryBody) {
if (createCategoryBody.getParentCategoryId() != 0) {
Category parent = categoryMapper.findById(createCategoryBody.getParentCategoryId());
if (parent == null) {
return ApiResponseUtils.error("父分类 Id 不存在");
}
}
Category category = new Category();
BeanUtils.copyProperties(createCategoryBody, category);
try {
categoryMapper.insert(category);
CreateCategoryVO createCategoryVO = new CreateCategoryVO();
createCategoryVO.setCategoryId(category.getCategoryId());
return ApiResponseUtils.success("创建分类成功", createCategoryVO);
} catch (Exception e) {
return ApiResponseUtils.error("创建分类失败");
}
}
}

View File

@ -8,9 +8,7 @@ import com.example.copykamanotes.model.dto.note.*;
import com.example.copykamanotes.model.entity.Note; import com.example.copykamanotes.model.entity.Note;
import com.example.copykamanotes.model.entity.Question; import com.example.copykamanotes.model.entity.Question;
import com.example.copykamanotes.model.entity.User; import com.example.copykamanotes.model.entity.User;
import com.example.copykamanotes.model.vo.note.CreateNoteVO; import com.example.copykamanotes.model.vo.note.*;
import com.example.copykamanotes.model.vo.note.DownloadNoteVO;
import com.example.copykamanotes.model.vo.note.NoteVO;
import com.example.copykamanotes.scope.RequestScopeData; import com.example.copykamanotes.scope.RequestScopeData;
import com.example.copykamanotes.service.NoteLikeService; import com.example.copykamanotes.service.NoteLikeService;
import com.example.copykamanotes.service.NoteService; import com.example.copykamanotes.service.NoteService;