feat(admin): implement category management functionality
- Added AddCategoryReqVO for category creation with validation - Created AdminCategoryController with endpoints for add, list, delete and select operations - Implemented AdminCategoryService interface and its methods - Added Category entity with JPA annotations and logical delete support - Created CategoryRepository extending JpaRepository with custom query methods - Added SQL table creation script for t_category with indexes and constraints - Implemented PageResponse utility for handling paginated results - Added FindCategoryPageListReqVO and FindCategoryPageListRspVO for pagination - Included DeleteCategoryReqVO for category deletion requests - Updated Jackson configuration to ignore unknown properties - Added base page query model and user info response VO - Fixed typo in response code enum for user not exist error
This commit is contained in:
@@ -47,6 +47,8 @@ public class JacksonConfig {
|
||||
|
||||
// 设置凡是为 null 的字段,返参中均不返回,请根据项目组约定是否开启
|
||||
// objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
// 忽略未知属性
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.hanserwei.common.domain.dataobject;
|
||||
|
||||
import jakarta.persistence.*; // 使用 Jakarta Persistence API (JPA 3.0+)
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import org.hibernate.annotations.*;
|
||||
import lombok.*; // 引入所有 Lombok 注解
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* 文章分类表(t_category 对应实体)
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Entity
|
||||
@Table(name = "t_category",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uk_name", columnNames = {"name"})
|
||||
},
|
||||
indexes = {
|
||||
@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
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 分类ID (BIG SERIAL PRIMARY KEY)
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 分类名称 (VARCHAR(60) NOT NULL DEFAULT '')
|
||||
*/
|
||||
@Column(name = "name", length = 60, nullable = false)
|
||||
private String name = ""; // 对应数据库的 DEFAULT ''
|
||||
|
||||
/**
|
||||
* 创建时间 (TIMESTAMP WITHOUT TIME ZONE NOT NULL)
|
||||
*/
|
||||
@CreationTimestamp
|
||||
@Column(name = "create_time", nullable = false, updatable = false)
|
||||
private Instant createTime;
|
||||
|
||||
/**
|
||||
* 最后一次更新时间 (TIMESTAMP WITHOUT TIME ZONE NOT NULL)
|
||||
* 配合数据库触发器或 ORM 框架自动更新
|
||||
*/
|
||||
@UpdateTimestamp
|
||||
@Column(name = "update_time", nullable = false)
|
||||
private Instant updateTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除标志位:FALSE:未删除 TRUE:已删除 (BOOLEAN NOT NULL DEFAULT FALSE)
|
||||
*/
|
||||
@Builder.Default
|
||||
@Column(name = "is_deleted", nullable = false)
|
||||
private Boolean isDeleted = false;
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.SQLDelete;
|
||||
import org.hibernate.annotations.SQLRestriction;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import java.io.Serial;
|
||||
@@ -18,7 +20,9 @@ import java.time.Instant; // 推荐用于 TIMESTAMP WITH TIME ZONE
|
||||
@Setter
|
||||
@Getter
|
||||
@Builder
|
||||
@Table(name = "t_user") // 对应数据库中的表名
|
||||
@Table(name = "t_user")
|
||||
@SQLRestriction("is_deleted = false")
|
||||
@SQLDelete(sql = "UPDATE t_user SET is_deleted = true WHERE id = ?")
|
||||
public class User implements Serializable {
|
||||
|
||||
@Serial
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.hanserwei.common.domain.repository;
|
||||
|
||||
import com.hanserwei.common.domain.dataobject.Category;
|
||||
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;
|
||||
|
||||
public interface CategoryRepository extends JpaRepository<Category, Long> {
|
||||
boolean existsCategoryByName(String name);
|
||||
|
||||
Page<Category> findAll(Specification<Category> specification, Pageable pageable);
|
||||
}
|
||||
@@ -2,7 +2,17 @@ package com.hanserwei.common.domain.repository;
|
||||
|
||||
import com.hanserwei.common.domain.dataobject.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
User getUsersByUsername(String username);
|
||||
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("UPDATE User u SET u.password = :newPassword WHERE u.username = :username")
|
||||
int updatePasswordByUsername(@Param("username") String username,
|
||||
@Param("newPassword") String newPassword);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
|
||||
LOGIN_FAIL("20000", "登录失败"),
|
||||
USERNAME_OR_PWD_ERROR("20001", "用户名或密码错误"),
|
||||
UNAUTHORIZED("20002", "无访问权限,请先登录!"),
|
||||
FORBIDDEN("20004", "演示账号仅支持查询操作!")
|
||||
FORBIDDEN("20004", "演示账号仅支持查询操作!"),
|
||||
USER_NOT_EXIST("2005", "有户不存在!"),
|
||||
CATEGORY_NAME_IS_EXISTED("20005", "该分类已存在,请勿重复添加!")
|
||||
;
|
||||
|
||||
// 异常码
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.hanserwei.common.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BasePageQuery {
|
||||
/**
|
||||
* 当前页码, 默认第一页
|
||||
*/
|
||||
private Long current = 1L;
|
||||
/**
|
||||
* 每页展示的数据数量,默认每页展示 10 条数据
|
||||
*/
|
||||
private Long size = 10L;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.hanserwei.common.model.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class SelectRspVO {
|
||||
/**
|
||||
* Select 下拉列表的展示文字
|
||||
*/
|
||||
private String label;
|
||||
|
||||
/**
|
||||
* Select 下拉列表的 value 值,如 ID 等
|
||||
*/
|
||||
private Object value;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.hanserwei.common.utils;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class PageResponse<T> extends Response<List<T>> {
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
private long total = 0L;
|
||||
|
||||
/**
|
||||
* 每页显示的记录数,默认每页显示 10 条
|
||||
*/
|
||||
private long size = 10L;
|
||||
|
||||
/**
|
||||
* 当前页码 (JPA Page 从 0 开始, 这里为方便前端, 统一改为从 1 开始)
|
||||
*/
|
||||
private long current;
|
||||
|
||||
/**
|
||||
* 总页数
|
||||
*/
|
||||
private long pages;
|
||||
|
||||
/**
|
||||
* 成功响应
|
||||
*
|
||||
* @param page Spring Data JPA 提供的分页接口
|
||||
* @param <T> 响应数据类型
|
||||
*/
|
||||
public static <T> PageResponse<T> success(Page<T> page) {
|
||||
PageResponse<T> response = new PageResponse<>();
|
||||
response.setSuccess(true);
|
||||
|
||||
if (Objects.nonNull(page)) {
|
||||
// JPA Page 的 getNumber() 是当前页码 (从 0 开始), 我们在返回时通常习惯改为从 1 开始
|
||||
response.setCurrent(page.getNumber() + 1);
|
||||
// JPA Page 的 getSize() 是每页大小
|
||||
response.setSize(page.getSize());
|
||||
// JPA Page 的 getTotalPages() 是总页数
|
||||
response.setPages(page.getTotalPages());
|
||||
// JPA Page 的 getTotalElements() 是总记录数
|
||||
response.setTotal(page.getTotalElements());
|
||||
// JPA Page 的 getContent() 是当前页的数据列表
|
||||
response.setData(page.getContent());
|
||||
} else {
|
||||
// 如果传入的 page 为 null,设置默认值
|
||||
response.setCurrent(1L);
|
||||
response.setSize(10L);
|
||||
response.setPages(0L);
|
||||
response.setTotal(0L);
|
||||
response.setData(null);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// 如果您需要处理分页结果DTO(例如实体转DTO),可以添加一个重载方法:
|
||||
|
||||
/**
|
||||
* 成功响应 (适用于将实体 Page<E> 转换为 DTO PageResponse<D>)
|
||||
*
|
||||
* @param page Spring Data JPA 提供的实体分页接口
|
||||
* @param data 经过转换后的 DTO 列表
|
||||
* @param <E> 实体类型
|
||||
* @param <D> DTO 类型
|
||||
*/
|
||||
public static <E, D> PageResponse<D> success(Page<E> page, List<D> data) {
|
||||
PageResponse<D> response = new PageResponse<>();
|
||||
response.setSuccess(true);
|
||||
|
||||
if (Objects.nonNull(page)) {
|
||||
response.setCurrent(page.getNumber() + 1);
|
||||
response.setSize(page.getSize());
|
||||
response.setPages(page.getTotalPages());
|
||||
response.setTotal(page.getTotalElements());
|
||||
response.setData(data); // 使用传入的 DTO 列表
|
||||
} else {
|
||||
response.setCurrent(1L);
|
||||
response.setSize(10L);
|
||||
response.setPages(0L);
|
||||
response.setTotal(0L);
|
||||
response.setData(data);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user