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:
2025-11-30 22:09:26 +08:00
parent 0a126eb520
commit 7380f783ee
27 changed files with 788 additions and 53 deletions

View File

@@ -8,6 +8,7 @@ dependencies {
implementation(project(":weblog-module-common"))
api(project(":weblog-module-jwt"))
implementation("org.springframework.boot:spring-boot-starter-validation")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")

View File

@@ -0,0 +1,67 @@
package com.hanserwei.admin.controller;
import com.hanserwei.admin.model.vo.AddCategoryReqVO;
import com.hanserwei.admin.model.vo.DeleteCategoryReqVO;
import com.hanserwei.admin.model.vo.FindCategoryPageListReqVO;
import com.hanserwei.admin.model.vo.FindCategoryPageListRspVO;
import com.hanserwei.admin.service.AdminCategoryService;
import com.hanserwei.common.aspect.ApiOperationLog;
import com.hanserwei.common.model.vo.SelectRspVO;
import com.hanserwei.common.utils.PageResponse;
import com.hanserwei.common.utils.Response;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 管理端分类控制器
*/
@RestController
@RequestMapping("/admin")
public class AdminCategoryController {
@Resource
private AdminCategoryService adminCategoryService;
/**
* 添加分类
*/
@PostMapping("/category/add")
@ApiOperationLog(description = "添加分类")
public Response<?> addCategory(@RequestBody @Validated AddCategoryReqVO addCategoryReqVO) {
return adminCategoryService.addCategory(addCategoryReqVO);
}
/**
* 分类分页数据获取
*/
@PostMapping("/category/list")
@ApiOperationLog(description = "分类分页数据获取")
public PageResponse<FindCategoryPageListRspVO> findCategoryList(@RequestBody @Validated FindCategoryPageListReqVO findCategoryPageListReqVO) {
return adminCategoryService.findCategoryList(findCategoryPageListReqVO);
}
/**
* 删除分类
*/
@PostMapping("/category/delete")
@ApiOperationLog(description = "删除分类")
public Response<?> deleteCategory(@RequestBody @Validated DeleteCategoryReqVO deleteCategoryReqVO) {
return adminCategoryService.deleteCategory(deleteCategoryReqVO);
}
/**
* 分类下拉列表
*/
@PostMapping("/category/select/list")
@ApiOperationLog(description = "分类 Select 下拉列表数据获取")
public Response<List<SelectRspVO>> findCategorySelectList() {
return adminCategoryService.findCategorySelectList();
}
}

View File

@@ -0,0 +1,42 @@
package com.hanserwei.admin.controller;
import com.hanserwei.admin.model.vo.FindUserInfoRspVO;
import com.hanserwei.admin.model.vo.UpdateAdminUserPasswordReqVO;
import com.hanserwei.admin.service.AdminUserService;
import com.hanserwei.common.aspect.ApiOperationLog;
import com.hanserwei.common.utils.Response;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 管理端用户控制器
*/
@RestController
@RequestMapping("/admin")
public class AdminUserController {
@Resource
private AdminUserService adminUserService;
/**
* 修改用户密码
*/
@PostMapping("/password/update")
@ApiOperationLog(description = "修改用户密码")
public Response<?> updatePassword(@RequestBody @Validated UpdateAdminUserPasswordReqVO updateAdminUserPasswordReqVO) {
return adminUserService.updatePassword(updateAdminUserPasswordReqVO);
}
/**
* 获取用户信息
*/
@PostMapping("/user/info")
@ApiOperationLog(description = "获取用户信息")
public Response<FindUserInfoRspVO> findUserInfo() {
return adminUserService.findUserInfo();
}
}

View File

@@ -0,0 +1,23 @@
package com.hanserwei.admin.model.vo;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AddCategoryReqVO {
/**
* 分类名称
*/
@NotBlank(message = "分类名称不能为空")
@Length(min = 1, max = 10, message = "分类名称字数限制 1 ~ 10 之间")
private String name;
}

View File

@@ -0,0 +1,18 @@
package com.hanserwei.admin.model.vo;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DeleteCategoryReqVO {
@NotNull(message = "分类 ID 不能为空")
private Long id;
}

View File

@@ -0,0 +1,30 @@
package com.hanserwei.admin.model.vo;
import com.hanserwei.common.model.BasePageQuery;
import lombok.*;
import java.time.LocalDate;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FindCategoryPageListReqVO extends BasePageQuery {
/**
* 分类名称
*/
private String name;
/**
* 创建的起始日期
*/
private LocalDate startDate;
/**
* 创建的结束日期
*/
private LocalDate endDate;
}

View File

@@ -0,0 +1,30 @@
package com.hanserwei.admin.model.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FindCategoryPageListRspVO {
/**
* 分类 ID
*/
private Long id;
/**
* 分类名称
*/
private String name;
/**
* 创建时间
*/
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,18 @@
package com.hanserwei.admin.model.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FindUserInfoRspVO {
/**
* 用户名
*/
private String username;
}

View File

@@ -0,0 +1,26 @@
package com.hanserwei.admin.model.vo;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UpdateAdminUserPasswordReqVO {
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
private String username;
/**
* 新密码
*/
@NotBlank(message = "密码不能为空")
private String password;
}

View File

@@ -0,0 +1,45 @@
package com.hanserwei.admin.service;
import com.hanserwei.admin.model.vo.AddCategoryReqVO;
import com.hanserwei.admin.model.vo.DeleteCategoryReqVO;
import com.hanserwei.admin.model.vo.FindCategoryPageListReqVO;
import com.hanserwei.admin.model.vo.FindCategoryPageListRspVO;
import com.hanserwei.common.model.vo.SelectRspVO;
import com.hanserwei.common.utils.PageResponse;
import com.hanserwei.common.utils.Response;
import java.util.List;
public interface AdminCategoryService {
/**
* 添加分类
*
* @param addCategoryReqVO 添加分类请求参数
* @return 添加结果
*/
Response<?> addCategory(AddCategoryReqVO addCategoryReqVO);
/**
* 分类分页数据查询
*
* @param findCategoryPageListReqVO 分页查询分类参数
* @return 查询结果
*/
PageResponse<FindCategoryPageListRspVO> findCategoryList(FindCategoryPageListReqVO findCategoryPageListReqVO);
/**
* 删除分类
*
* @param deleteCategoryReqVO 删除分类参数
* @return 删除结果
*/
Response<?> deleteCategory(DeleteCategoryReqVO deleteCategoryReqVO);
/**
* 获取文章分类的 Select 列表数据
*
* @return Select 列表数据
*/
Response<List<SelectRspVO>> findCategorySelectList();
}

View File

@@ -0,0 +1,20 @@
package com.hanserwei.admin.service;
import com.hanserwei.admin.model.vo.FindUserInfoRspVO;
import com.hanserwei.admin.model.vo.UpdateAdminUserPasswordReqVO;
import com.hanserwei.common.utils.Response;
public interface AdminUserService {
/**
* 修改密码
* @param updateAdminUserPasswordReqVO 修改密码参数
* @return 修改密码结果
*/
Response<?> updatePassword(UpdateAdminUserPasswordReqVO updateAdminUserPasswordReqVO);
/**
* 获取当前登录用户信息
* @return 当前登录用户信息
*/
Response<FindUserInfoRspVO> findUserInfo();
}

View File

@@ -0,0 +1,120 @@
package com.hanserwei.admin.service.impl;
import com.hanserwei.admin.model.vo.AddCategoryReqVO;
import com.hanserwei.admin.model.vo.DeleteCategoryReqVO;
import com.hanserwei.admin.model.vo.FindCategoryPageListReqVO;
import com.hanserwei.admin.model.vo.FindCategoryPageListRspVO;
import com.hanserwei.admin.service.AdminCategoryService;
import com.hanserwei.common.domain.dataobject.Category;
import com.hanserwei.common.domain.repository.CategoryRepository;
import com.hanserwei.common.enums.ResponseCodeEnum;
import com.hanserwei.common.exception.BizException;
import com.hanserwei.common.model.vo.SelectRspVO;
import com.hanserwei.common.utils.PageResponse;
import com.hanserwei.common.utils.Response;
import io.jsonwebtoken.lang.Strings;
import jakarta.annotation.Resource;
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.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
public class AdminCategoryServiceImpl implements AdminCategoryService {
@Resource
private CategoryRepository categoryRepository;
@Override
public Response<?> addCategory(AddCategoryReqVO addCategoryReqVO) {
String categoryName = addCategoryReqVO.getName();
// 先判断是否存在
if (categoryRepository.existsCategoryByName(categoryName)) {
throw new BizException(ResponseCodeEnum.CATEGORY_NAME_IS_EXISTED);
}
// 构造Category对象
Category category = Category.builder()
.name(categoryName)
.build();
categoryRepository.save(category);
return Response.success();
}
@Override
public PageResponse<FindCategoryPageListRspVO> findCategoryList(FindCategoryPageListReqVO findCategoryPageListReqVO) {
Long current = findCategoryPageListReqVO.getCurrent();
Long size = findCategoryPageListReqVO.getSize();
Pageable pageable = PageRequest.of(current.intValue() - 1,
size.intValue(),
Sort.by(Sort.Direction.DESC, "createTime"));
// 构建查询条件
Specification<Category> specification = (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
String name = findCategoryPageListReqVO.getName();
if (Strings.hasText(name)) {
predicates.add(
criteriaBuilder.like(root.get("name"), "%" + name.trim() + "%")
);
}
if (Objects.nonNull(findCategoryPageListReqVO.getStartDate())){
predicates.add(
criteriaBuilder.greaterThanOrEqualTo(root.get("createTime"), findCategoryPageListReqVO.getStartDate())
);
}
if (Objects.nonNull(findCategoryPageListReqVO.getEndDate())) {
predicates.add(
criteriaBuilder.lessThan(root.get("createTime"), findCategoryPageListReqVO.getEndDate().plusDays(1).atStartOfDay())
);
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
Page<Category> categoryDOPage = categoryRepository.findAll(specification, pageable);
List<FindCategoryPageListRspVO> vos = categoryDOPage.getContent().stream()
.map(category -> FindCategoryPageListRspVO.builder()
.id(category.getId())
.name(category.getName())
.createTime(LocalDateTime.ofInstant(category.getCreateTime(), ZoneId.systemDefault()))
.build())
.collect(Collectors.toList());
return PageResponse.success(categoryDOPage, vos);
}
@Override
public Response<?> deleteCategory(DeleteCategoryReqVO deleteCategoryReqVO) {
Long id = deleteCategoryReqVO.getId();
categoryRepository.deleteById(id);
return Response.success();
}
@Override
public Response<List<SelectRspVO>> findCategorySelectList() {
List<Category> categoryList = categoryRepository.findAll();
// DO 转 VO
List<SelectRspVO> selectRspVOS = null;
if (!CollectionUtils.isEmpty(categoryList)){
selectRspVOS = categoryList.stream()
.map(category -> SelectRspVO.builder()
.label(category.getName())
.value(category.getId())
.build())
.toList();
}
return Response.success(selectRspVOS);
}
}

View File

@@ -0,0 +1,51 @@
package com.hanserwei.admin.service.impl;
import com.hanserwei.admin.model.vo.FindUserInfoRspVO;
import com.hanserwei.admin.model.vo.UpdateAdminUserPasswordReqVO;
import com.hanserwei.admin.service.AdminUserService;
import com.hanserwei.common.domain.repository.UserRepository;
import com.hanserwei.common.enums.ResponseCodeEnum;
import com.hanserwei.common.exception.BizException;
import com.hanserwei.common.utils.Response;
import jakarta.annotation.Resource;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class AdminUserServiceImpl implements AdminUserService {
@Resource
private UserRepository userRepository;
@Resource
private PasswordEncoder passwordEncoder;
@Override
public Response<?> updatePassword(UpdateAdminUserPasswordReqVO updateAdminUserPasswordReqVO) {
// 拿到用户名密码
String username = updateAdminUserPasswordReqVO.getUsername();
String password = updateAdminUserPasswordReqVO.getPassword();
// 加密密码
String encodePassword = passwordEncoder.encode(password);
int updatedRows = userRepository.updatePasswordByUsername(username, encodePassword);
if (updatedRows == 0) {
// 如果更新行数为 0说明用户名不存在
throw new BizException(ResponseCodeEnum.USER_NOT_EXIST);
}
return Response.success();
}
@Override
public Response<FindUserInfoRspVO> findUserInfo() {
// 获取存储在 ThreadLocal 中的用户信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 拿到用户名
String username = authentication.getName();
return Response.success(new FindUserInfoRspVO(username));
}
}