refactor(admin): 重构管理模块VO包结构并新增标签管理功能

- 将分类相关VO移至com.hanserwei.admin.model.vo.category包下,用户相关VO移至user包
- 新增标签管理相关VO,包括AddTagReqVO、DeleteTagReqVO、FindTagPageListReqVO、FindTagPageListRspVO、SearchTagReqVO
- 增加AdminTagController,实现标签的增删查和分页查询接口
- 实现AdminTagService及其Impl,完成标签的增删查分页功能
- 新增Tag实体及TagRepository,支持标签数据的持久化及模糊查询
- 优化AdminCategoryServiceImpl分页查询逻辑,提取公共分页查询工具类PageHelper
- 修改CategoryRepository继承JpaSpecificationExecutor,支持动态查询
- 修改TokenAuthenticationFilter,限制JWT认证仅校验/admin路径请求
- 修改Category实体删除注解,调整逻辑删除实现
- 新增数据库脚本,创建t_tag标签表及相关索引和触发器
- 更新ResponseCodeEnum,增加TAG_NOT_EXIST和CATEGORY_NOT_EXIST错误码
- 调整.gitignore,忽略.idea下Apifox相关缓存文件
This commit is contained in:
2025-12-04 23:18:10 +08:00
parent b7afe9496a
commit 304c458436
29 changed files with 641 additions and 127 deletions

View File

@@ -27,7 +27,6 @@ import java.time.Instant;
@Index(name = "idx_create_time", columnList = "create_time")
})
@SQLRestriction("is_deleted = false")
@SQLDelete(sql = "UPDATE t_category SET is_deleted = true WHERE id = ?")
public class Category implements Serializable {
@Serial

View File

@@ -0,0 +1,41 @@
package com.hanserwei.common.domain.dataobject;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.SQLRestriction;
import org.hibernate.annotations.UpdateTimestamp;
import java.io.Serializable;
import java.time.Instant;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "t_tag", indexes = {
@Index(name = "idx_tag_create_time", columnList = "create_time")
})
@SQLRestriction("is_deleted = false")
public class Tag implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 60, unique = true)
private String name;
@CreationTimestamp
@Column(name = "create_time", nullable = false, updatable = false)
private Instant createTime;
@UpdateTimestamp
@Column(name = "update_time", nullable = false)
private Instant updateTime;
@Column(name = "is_deleted", nullable = false)
@Builder.Default
private Boolean isDeleted = false;
}

View File

@@ -5,8 +5,9 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface CategoryRepository extends JpaRepository<Category, Long> {
public interface CategoryRepository extends JpaRepository<Category, Long>, JpaSpecificationExecutor<Category> {
boolean existsCategoryByName(String name);
Page<Category> findAll(Specification<Category> specification, Pageable pageable);

View File

@@ -0,0 +1,25 @@
package com.hanserwei.common.domain.repository;
import com.hanserwei.common.domain.dataobject.Tag;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.Collection;
import java.util.List;
public interface TagRepository extends JpaRepository<Tag, Long>, JpaSpecificationExecutor<Tag> {
Collection<Tag> findByNameIn(List<String> names);
Page<Tag> findAll(Specification<Tag> specification, Pageable pageable);
/**
* 根据标签名称模糊查询
* @param name 标签名称关键词
* @return 标签列表
*/
List<Tag> findByNameContaining(String name);
}

View File

@@ -18,8 +18,9 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
UNAUTHORIZED("20002", "无访问权限,请先登录!"),
FORBIDDEN("20004", "演示账号仅支持查询操作!"),
USER_NOT_EXIST("2005", "有户不存在!"),
CATEGORY_NAME_IS_EXISTED("20005", "该分类已存在,请勿重复添加!")
;
CATEGORY_NAME_IS_EXISTED("20005", "该分类已存在,请勿重复添加!"),
TAG_NOT_EXIST("20006", "标签不存在!"),
CATEGORY_NOT_EXIST("20007", "分类不存在!" );
// 异常码
private final String errorCode;

View File

@@ -0,0 +1,91 @@
package com.hanserwei.common.utils;
import com.hanserwei.common.model.BasePageQuery;
import jakarta.persistence.criteria.Predicate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
/**
* 分页查询工具类
* 用于抽取通用的分页查询逻辑
*/
public class PageHelper {
/**
* 执行带条件的分页查询
*
* @param repository JPA Repository
* @param pageQuery 分页查询参数
* @param name 名称(用于模糊查询)
* @param startDate 开始日期
* @param endDate 结束日期
* @param converter DO 到 VO 的转换函数
* @param <T> 实体类型
* @param <R> 响应VO类型
* @return 分页响应
*/
public static <T, R> PageResponse<R> findPageList(
JpaSpecificationExecutor<T> repository,
BasePageQuery pageQuery,
String name,
LocalDate startDate,
LocalDate endDate,
Function<T, R> converter) {
// 构建分页参数
Pageable pageable = PageRequest.of(
pageQuery.getCurrent().intValue() - 1,
pageQuery.getSize().intValue(),
Sort.by(Sort.Direction.DESC, "createTime")
);
// 构建查询条件
Specification<T> specification = (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
// 名称模糊查询
if (StringUtils.hasText(name)) {
predicates.add(
criteriaBuilder.like(root.get("name"), "%" + name.trim() + "%")
);
}
// 开始日期查询
if (Objects.nonNull(startDate)) {
predicates.add(
criteriaBuilder.greaterThanOrEqualTo(root.get("createTime"), startDate)
);
}
// 结束日期查询
if (Objects.nonNull(endDate)) {
predicates.add(
criteriaBuilder.lessThan(root.get("createTime"), endDate.plusDays(1).atStartOfDay())
);
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
// 执行查询
Page<T> page = repository.findAll(specification, pageable);
// DO 转 VO
List<R> vos = page.getContent().stream()
.map(converter)
.toList();
return PageResponse.success(page, vos);
}
}