Compare commits

...

2 Commits

Author SHA1 Message Date
qingfeng1121
8cc4c1da1d feat: 添加Category_attribute实体类和仓库接口
refactor(security): 限制文章更新仅限AUTHOR角色
修改ArticleController的updateArticle方法权限,移除ADMIN角色访问权限

chore: 更新application.properties中的Redis配置
取消注释Redis相关配置,包括缓存和连接池设置
2025-10-16 16:12:19 +08:00
qingfeng1121
2809837422 feat(分类模块): 实现分类管理功能
新增分类模块相关代码,包括实体类、DTO、Repository、Service和Controller
添加分类数据初始化逻辑和日志记录
实现分类的增删改查及搜索功能
2025-10-12 14:23:42 +08:00
13 changed files with 2507 additions and 5351 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -57,13 +57,13 @@ public class ArticleController {
/**
* 更新现有文章
* 仅限AUTHOR或ADMIN角色用户访问
* 仅限AUTHOR角色用户访问
* @param id 要更新的文章ID
* @param articleDto 包含更新后文章数据的DTO对象
* @return 返回包含更新后文章信息的ResponseMessage对象
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('AUTHOR') or hasRole('ADMIN')")
@PreAuthorize("hasRole('AUTHOR')")
public ResponseMessage<Article> updateArticle(
@PathVariable Integer id,
@Valid @RequestBody ArticleDto articleDto) {

View File

@@ -0,0 +1,97 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.ResponseMessage;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;
import com.qf.myafterprojecy.service.ICategoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 分类控制器类处理分类相关的HTTP请求
* 提供分类的增删改查功能
*/
@RestController
@RequestMapping("/api/categories")
@Validated
public class CategoryController {
private static final Logger log = LoggerFactory.getLogger(CategoryController.class);
@Autowired
private ICategoryService categoryService;
/**
* 根据ID获取分类信息
* @param id 分类ID
* @return 返回分类信息
*/
@GetMapping("/{id}")
public ResponseMessage<Category> getCategoryById(@PathVariable Integer id) {
log.info("接收根据ID获取分类的请求: {}", id);
return categoryService.getCategoryById(id);
}
/**
* 获取所有分类列表
* @return 返回分类列表
*/
@GetMapping
public ResponseMessage<List<Category>> getAllCategories() {
log.info("接收获取所有分类列表的请求");
return categoryService.getAllCategories();
}
/**
* 创建新分类
* @param categoryDto 分类数据传输对象
* @return 返回创建结果
*/
@PostMapping
public ResponseMessage<Category> createCategory(@Valid @RequestBody CategoryDto categoryDto) {
log.info("接收创建分类的请求: {}", categoryDto.getTypename());
return categoryService.saveCategory(categoryDto);
}
/**
* 更新分类信息
* @param id 分类ID
* @param categoryDto 分类数据传输对象
* @return 返回更新结果
*/
@PutMapping("/{id}")
public ResponseMessage<Category> updateCategory(
@PathVariable Integer id,
@Valid @RequestBody CategoryDto categoryDto) {
log.info("接收更新分类的请求: ID={}, 分类名称={}", id, categoryDto.getTypename());
return categoryService.updateCategory(id, categoryDto);
}
/**
* 删除分类
* @param id 分类ID
* @return 返回删除结果
*/
@DeleteMapping("/{id}")
public ResponseMessage<Boolean> deleteCategory(@PathVariable Integer id) {
log.info("接收删除分类的请求: {}", id);
return categoryService.deleteCategory(id);
}
/**
* 根据分类名称搜索分类
* @param typename 分类名称
* @return 返回符合条件的分类列表
*/
@GetMapping("/search")
public ResponseMessage<List<Category>> searchCategoriesByTypename(@RequestParam String typename) {
log.info("接收根据名称搜索分类的请求: {}", typename);
return categoryService.searchCategoriesByTypename(typename);
}
}

View File

@@ -0,0 +1,85 @@
package com.qf.myafterprojecy.init;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.repository.CategoryRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* 分类数据初始化类,用于在应用启动时初始化分类数据
*/
@Component
public class CategoryDataInit implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(CategoryDataInit.class);
@Autowired
private CategoryRepository categoryRepository;
@Override
public void run(ApplicationArguments args) throws Exception {
logger.info("===== 分类数据初始化开始 =====");
// 检查数据库中是否已有分类数据
long count = categoryRepository.count();
logger.info("当前数据库中分类数量: {}", count);
// 如果没有分类数据,添加一些测试数据
if (count == 0) {
logger.info("数据库中没有分类数据,开始添加初始化数据...");
addInitialCategories();
} else {
logger.info("数据库中已存在分类数据,无需初始化");
}
logger.info("===== 分类数据初始化结束 =====");
}
/**
* 添加初始分类数据
*/
private void addInitialCategories() {
List<Category> categories = new ArrayList<>();
// 创建几个常见的文章分类
Category category1 = new Category();
category1.setTypename("技术分享");
category1.setDescription("技术文章、教程、经验分享等");
category1.setCreatedAt(LocalDateTime.now());
category1.setUpdatedAt(LocalDateTime.now());
categories.add(category1);
Category category2 = new Category();
category2.setTypename("生活随笔");
category2.setDescription("日常生活、心情记录、随笔等");
category2.setCreatedAt(LocalDateTime.now());
category2.setUpdatedAt(LocalDateTime.now());
categories.add(category2);
Category category3 = new Category();
category3.setTypename("学习笔记");
category3.setDescription("学习过程中的笔记、总结等");
category3.setCreatedAt(LocalDateTime.now());
category3.setUpdatedAt(LocalDateTime.now());
categories.add(category3);
Category category4 = new Category();
category4.setTypename("行业动态");
category4.setDescription("行业新闻、趋势分析等");
category4.setCreatedAt(LocalDateTime.now());
category4.setUpdatedAt(LocalDateTime.now());
categories.add(category4);
// 保存分类数据到数据库
categoryRepository.saveAll(categories);
logger.info("成功添加 {} 条分类数据", categories.size());
}
}

View File

@@ -0,0 +1,68 @@
package com.qf.myafterprojecy.pojo;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
@Entity
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "typeid")
private Integer typeid;
@NotBlank(message = "分类名称不能为空")
@Column(name = "typename")
private String typename;
@Column(name = "description")
private String description;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Getters and Setters
public Integer getTypeid() {
return typeid;
}
public void setTypeid(Integer typeid) {
this.typeid = typeid;
}
public String getTypename() {
return typename;
}
public void setTypename(String typename) {
this.typename = typename;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,4 @@
package com.qf.myafterprojecy.pojo;
public class category_attribute {
}

View File

@@ -0,0 +1,52 @@
package com.qf.myafterprojecy.pojo.dto;
import java.time.LocalDateTime;
public class CategoryDto {
private Integer typeid;
private String typename;
private String description;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getters and Setters
public Integer getTypeid() {
return typeid;
}
public void setTypeid(Integer typeid) {
this.typeid = typeid;
}
public String getTypename() {
return typename;
}
public void setTypename(String typename) {
this.typename = typename;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,25 @@
package com.qf.myafterprojecy.repository;
import com.qf.myafterprojecy.pojo.Category;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CategoryRepository extends JpaRepository<Category, Integer> {
/**
* 根据分类名称查询分类信息
* @param typename 分类名称
* @return 返回符合条件的分类列表
*/
List<Category> findByTypenameContaining(String typename);
/**
* 检查分类名称是否存在
* @param typename 分类名称
* @return 返回是否存在
*/
boolean existsByTypename(String typename);
}

View File

@@ -0,0 +1,4 @@
package com.qf.myafterprojecy.repository;
public interface Category_attribute {
}

View File

@@ -0,0 +1,141 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.ResponseMessage;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;
import com.qf.myafterprojecy.repository.CategoryRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class CategoryService implements ICategoryService {
private static final Logger log = LoggerFactory.getLogger(CategoryService.class);
@Autowired
private CategoryRepository categoryRepository;
@Override
@Transactional(readOnly = true)
public ResponseMessage<Category> getCategoryById(Integer id) {
try {
if (id == null || id <= 0) {
return ResponseMessage.failure("分类ID无效");
}
Category category = categoryRepository.findById(id)
.orElseThrow(() -> new RuntimeException("分类不存在"));
return ResponseMessage.success(category);
} catch (Exception e) {
log.error("获取分类失败: {}", e.getMessage());
return ResponseMessage.failure("获取分类失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Category>> getAllCategories() {
try {
List<Category> categories = categoryRepository.findAll();
return ResponseMessage.success(categories);
} catch (DataAccessException e) {
log.error("获取分类列表失败: {}", e.getMessage());
return ResponseMessage.failure("获取分类列表失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Category> saveCategory(CategoryDto categoryDto) {
try {
// 检查分类名称是否已存在
if (categoryRepository.existsByTypename(categoryDto.getTypename())) {
return ResponseMessage.failure("分类名称已存在");
}
Category category = new Category();
BeanUtils.copyProperties(categoryDto, category);
category.setCreatedAt(LocalDateTime.now());
category.setUpdatedAt(LocalDateTime.now());
Category savedCategory = categoryRepository.save(category);
return ResponseMessage.success(savedCategory);
} catch (DataAccessException e) {
log.error("保存分类失败: {}", e.getMessage());
return ResponseMessage.failure("保存分类失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Category> updateCategory(Integer id, CategoryDto categoryDto) {
try {
if (id == null || id <= 0) {
return ResponseMessage.failure("分类ID无效");
}
Category category = categoryRepository.findById(id)
.orElseThrow(() -> new RuntimeException("分类不存在"));
// 如果修改了分类名称,检查新名称是否已存在
if (!category.getTypename().equals(categoryDto.getTypename()) &&
categoryRepository.existsByTypename(categoryDto.getTypename())) {
return ResponseMessage.failure("分类名称已存在");
}
BeanUtils.copyProperties(categoryDto, category);
category.setUpdatedAt(LocalDateTime.now());
Category updatedCategory = categoryRepository.save(category);
return ResponseMessage.success(updatedCategory);
} catch (Exception e) {
log.error("更新分类失败: {}", e.getMessage());
return ResponseMessage.failure("更新分类失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Boolean> deleteCategory(Integer id) {
try {
if (id == null || id <= 0) {
return ResponseMessage.failure("分类ID无效");
}
if (!categoryRepository.existsById(id)) {
return ResponseMessage.failure("分类不存在");
}
// 注意:实际项目中可能需要先检查是否有文章引用该分类
// 如果有,可能需要先处理文章或者禁止删除
categoryRepository.deleteById(id);
return ResponseMessage.success(true);
} catch (Exception e) {
log.error("删除分类失败: {}", e.getMessage());
return ResponseMessage.failure("删除分类失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Category>> searchCategoriesByTypename(String typename) {
try {
if (typename == null || typename.trim().isEmpty()) {
return ResponseMessage.failure("分类名称不能为空");
}
List<Category> categories = categoryRepository.findByTypenameContaining(typename);
return ResponseMessage.success(categories);
} catch (DataAccessException e) {
log.error("搜索分类失败: {}", e.getMessage());
return ResponseMessage.failure("搜索分类失败");
}
}
}

View File

@@ -0,0 +1,51 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.ResponseMessage;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;
import java.util.List;
public interface ICategoryService {
/**
* 根据ID获取分类信息
* @param id 分类ID
* @return 返回分类信息
*/
ResponseMessage<Category> getCategoryById(Integer id);
/**
* 获取所有分类列表
* @return 返回分类列表
*/
ResponseMessage<List<Category>> getAllCategories();
/**
* 保存新分类
* @param categoryDto 分类数据传输对象
* @return 返回保存结果
*/
ResponseMessage<Category> saveCategory(CategoryDto categoryDto);
/**
* 更新分类信息
* @param id 分类ID
* @param categoryDto 分类数据传输对象
* @return 返回更新结果
*/
ResponseMessage<Category> updateCategory(Integer id, CategoryDto categoryDto);
/**
* 删除分类
* @param id 分类ID
* @return 返回删除结果
*/
ResponseMessage<Boolean> deleteCategory(Integer id);
/**
* 根据分类名称搜索分类
* @param typename 分类名称
* @return 返回符合条件的分类列表
*/
ResponseMessage<List<Category>> searchCategoriesByTypename(String typename);
}

View File

@@ -31,25 +31,25 @@ spring.jpa.properties.hibernate.order_updates=true
# spring.jpa.properties.hibernate.cache.use_second_level_cache=true
# spring.jpa.properties.hibernate.cache.use_query_cache=true
# 缓存配置
# spring.cache.type=redis
# spring.cache.redis.time-to-live=1800000
# spring.cache.redis.key-prefix=CACHE_
# spring.cache.redis.use-key-prefix=true
# spring.cache.redis.cache-null-values=false
缓存配置
spring.cache.type=redis
spring.cache.redis.time-to-live=1800000
spring.cache.redis.key-prefix=CACHE_
spring.cache.redis.use-key-prefix=true
spring.cache.redis.cache-null-values=false
# Redis配置
# spring.redis.host=localhost
# spring.redis.port=6379
# spring.redis.password=123456
# spring.redis.database=0
# spring.redis.timeout=10000ms
# Redis连接池优化配置
#spring.redis.lettuce.pool.max-active=8
#spring.redis.lettuce.pool.max-wait=10000ms
#spring.redis.lettuce.pool.max-idle=8
#spring.redis.lettuce.pool.min-idle=2
#spring.redis.lettuce.shutdown-timeout=100ms
Redis配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=0
spring.redis.timeout=10000ms
Redis连接池优化配置
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=10000ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=2
spring.redis.lettuce.shutdown-timeout=100ms
# 日志配置
logging.level.root=INFO