From 7380f783ee23b49db294341bcc22a004f24b4a95 Mon Sep 17 00:00:00 2001 From: Hanserwei <2628273921@qq.com> Date: Sun, 30 Nov 2025 22:09:26 +0800 Subject: [PATCH] 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 --- .idea/ApifoxUploaderProjectSetting.xml | 9 +- .idea/data_source_mapping.xml | 1 + .idea/sqldialects.xml | 1 - sql/createTable.sql | 50 +++++++- weblog-module-admin/build.gradle.kts | 1 + .../controller/AdminCategoryController.java | 67 ++++++++++ .../admin/controller/AdminUserController.java | 42 ++++++ .../admin/model/vo/AddCategoryReqVO.java | 23 ++++ .../admin/model/vo/DeleteCategoryReqVO.java | 18 +++ .../model/vo/FindCategoryPageListReqVO.java | 30 +++++ .../model/vo/FindCategoryPageListRspVO.java | 30 +++++ .../admin/model/vo/FindUserInfoRspVO.java | 18 +++ .../vo/UpdateAdminUserPasswordReqVO.java | 26 ++++ .../admin/service/AdminCategoryService.java | 45 +++++++ .../admin/service/AdminUserService.java | 20 +++ .../impl/AdminCategoryServiceImpl.java | 120 ++++++++++++++++++ .../service/impl/AdminUserServiceImpl.java | 51 ++++++++ .../common/config/JacksonConfig.java | 2 + .../common/domain/dataobject/Category.java | 71 +++++++++++ .../common/domain/dataobject/User.java | 6 +- .../domain/repository/CategoryRepository.java | 13 ++ .../domain/repository/UserRepository.java | 10 ++ .../common/enums/ResponseCodeEnum.java | 4 +- .../hanserwei/common/model/BasePageQuery.java | 16 +++ .../common/model/vo/SelectRspVO.java | 22 ++++ .../hanserwei/common/utils/PageResponse.java | 97 ++++++++++++++ .../web/controller/TestController.java | 48 ------- 27 files changed, 788 insertions(+), 53 deletions(-) create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminCategoryController.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminUserController.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/AddCategoryReqVO.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/DeleteCategoryReqVO.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindCategoryPageListReqVO.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindCategoryPageListRspVO.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindUserInfoRspVO.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/UpdateAdminUserPasswordReqVO.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminCategoryService.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminUserService.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminCategoryServiceImpl.java create mode 100644 weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminUserServiceImpl.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Category.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/CategoryRepository.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/model/BasePageQuery.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/model/vo/SelectRspVO.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/utils/PageResponse.java delete mode 100644 weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java diff --git a/.idea/ApifoxUploaderProjectSetting.xml b/.idea/ApifoxUploaderProjectSetting.xml index cbbc398..9fc2e6c 100644 --- a/.idea/ApifoxUploaderProjectSetting.xml +++ b/.idea/ApifoxUploaderProjectSetting.xml @@ -1,6 +1,13 @@ - + \ No newline at end of file diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml index d636af0..73e5cce 100644 --- a/.idea/data_source_mapping.xml +++ b/.idea/data_source_mapping.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index 5ab0b21..9dc955a 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/sql/createTable.sql b/sql/createTable.sql index 30ef788..17d38a8 100644 --- a/sql/createTable.sql +++ b/sql/createTable.sql @@ -40,11 +40,59 @@ CREATE TABLE t_user_role id BIGSERIAL PRIMARY KEY, username VARCHAR(60) NOT NULL, role_name VARCHAR(60) NOT NULL, -- 重命名为 role_name 避免关键字冲突 - create_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP + create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); CREATE INDEX idx_username ON t_user_role (username); COMMENT ON COLUMN t_user_role.role_name IS '角色名称'; +-- 为 t_user_role 表创建触发器 +CREATE TRIGGER set_t_user_role_update_time + BEFORE UPDATE + ON t_user_role + FOR EACH ROW +EXECUTE FUNCTION set_update_time(); +-- ==================================================================================================================== +-- ==================================================================================================================== + +-- ==================================================================================================================== +-- ==================================================================================================================== +CREATE TABLE t_category +( + -- id:对应 MySQL 的 bigint(20) unsigned NOT NULL AUTO_INCREMENT + id BIGSERIAL PRIMARY KEY, + + -- 分类名称:VARCHAR(60) NOT NULL DEFAULT '',同时是 UNIQUE 约束 + "name" VARCHAR(60) NOT NULL DEFAULT '', + + -- 创建时间 + create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + + -- 最后一次更新时间 + update_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + + -- 逻辑删除标志位:tinyint(2) NOT NULL DEFAULT '0',改为 BOOLEAN + is_deleted BOOLEAN NOT NULL DEFAULT FALSE, + + -- UNIQUE KEY uk_name (`name`) + CONSTRAINT uk_name UNIQUE ("name") +); + +-- 添加非唯一索引(对应 MySQL 的 KEY `idx_create_time`) +CREATE INDEX idx_create_time ON t_category (create_time); + +-- 可选:添加注释 +COMMENT ON TABLE t_category IS '文章分类表'; +COMMENT ON COLUMN t_category.id IS '分类id'; +COMMENT ON COLUMN t_category.name IS '分类名称'; +COMMENT ON COLUMN t_category.create_time IS '创建时间'; +COMMENT ON COLUMN t_category.update_time IS '最后一次更新时间'; +COMMENT ON COLUMN t_category.is_deleted IS '逻辑删除标志位:FALSE:未删除 TRUE:已删除'; +-- 为 t_category 表创建触发器 +CREATE TRIGGER set_t_category_update_time + BEFORE UPDATE + ON t_category + FOR EACH ROW +EXECUTE FUNCTION set_update_time(); -- ==================================================================================================================== -- ==================================================================================================================== \ No newline at end of file diff --git a/weblog-module-admin/build.gradle.kts b/weblog-module-admin/build.gradle.kts index 9f6ec8b..841e488 100644 --- a/weblog-module-admin/build.gradle.kts +++ b/weblog-module-admin/build.gradle.kts @@ -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") diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminCategoryController.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminCategoryController.java new file mode 100644 index 0000000..0b57fa7 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminCategoryController.java @@ -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 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> findCategorySelectList() { + return adminCategoryService.findCategorySelectList(); + } + +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminUserController.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminUserController.java new file mode 100644 index 0000000..3494e01 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminUserController.java @@ -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 findUserInfo() { + return adminUserService.findUserInfo(); + } +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/AddCategoryReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/AddCategoryReqVO.java new file mode 100644 index 0000000..6579e32 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/AddCategoryReqVO.java @@ -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; + +} \ No newline at end of file diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/DeleteCategoryReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/DeleteCategoryReqVO.java new file mode 100644 index 0000000..137db3b --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/DeleteCategoryReqVO.java @@ -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; + +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindCategoryPageListReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindCategoryPageListReqVO.java new file mode 100644 index 0000000..f1e7d07 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindCategoryPageListReqVO.java @@ -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; + +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindCategoryPageListRspVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindCategoryPageListRspVO.java new file mode 100644 index 0000000..ec8035f --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindCategoryPageListRspVO.java @@ -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; + +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindUserInfoRspVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindUserInfoRspVO.java new file mode 100644 index 0000000..c4483c5 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindUserInfoRspVO.java @@ -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; + +} \ No newline at end of file diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/UpdateAdminUserPasswordReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/UpdateAdminUserPasswordReqVO.java new file mode 100644 index 0000000..2adfac8 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/UpdateAdminUserPasswordReqVO.java @@ -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; +} \ No newline at end of file diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminCategoryService.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminCategoryService.java new file mode 100644 index 0000000..e8f63b3 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminCategoryService.java @@ -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 findCategoryList(FindCategoryPageListReqVO findCategoryPageListReqVO); + + /** + * 删除分类 + * + * @param deleteCategoryReqVO 删除分类参数 + * @return 删除结果 + */ + Response deleteCategory(DeleteCategoryReqVO deleteCategoryReqVO); + + /** + * 获取文章分类的 Select 列表数据 + * + * @return Select 列表数据 + */ + Response> findCategorySelectList(); + +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminUserService.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminUserService.java new file mode 100644 index 0000000..5774fdc --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminUserService.java @@ -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 findUserInfo(); +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminCategoryServiceImpl.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminCategoryServiceImpl.java new file mode 100644 index 0000000..d16f3e3 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminCategoryServiceImpl.java @@ -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 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 specification = (root, query, criteriaBuilder) -> { + List 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 categoryDOPage = categoryRepository.findAll(specification, pageable); + + List 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> findCategorySelectList() { + List categoryList = categoryRepository.findAll(); + // DO 转 VO + List 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); + } +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminUserServiceImpl.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminUserServiceImpl.java new file mode 100644 index 0000000..162b743 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminUserServiceImpl.java @@ -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 findUserInfo() { + // 获取存储在 ThreadLocal 中的用户信息 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + // 拿到用户名 + String username = authentication.getName(); + return Response.success(new FindUserInfoRspVO(username)); + } +} diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/config/JacksonConfig.java b/weblog-module-common/src/main/java/com/hanserwei/common/config/JacksonConfig.java index 9fc0ee9..f0e9cef 100644 --- a/weblog-module-common/src/main/java/com/hanserwei/common/config/JacksonConfig.java +++ b/weblog-module-common/src/main/java/com/hanserwei/common/config/JacksonConfig.java @@ -47,6 +47,8 @@ public class JacksonConfig { // 设置凡是为 null 的字段,返参中均不返回,请根据项目组约定是否开启 // objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + // 忽略未知属性 + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return objectMapper; } diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Category.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Category.java new file mode 100644 index 0000000..fb3f27a --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Category.java @@ -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; +} \ No newline at end of file diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/User.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/User.java index 2465e0a..1ac93d2 100644 --- a/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/User.java +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/User.java @@ -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 diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/CategoryRepository.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/CategoryRepository.java new file mode 100644 index 0000000..d560343 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/CategoryRepository.java @@ -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 { + boolean existsCategoryByName(String name); + + Page findAll(Specification specification, Pageable pageable); +} diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/UserRepository.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/UserRepository.java index 9042706..23074b4 100644 --- a/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/UserRepository.java +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/UserRepository.java @@ -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 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); } diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java b/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java index d770a39..e239471 100644 --- a/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java +++ b/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java @@ -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", "该分类已存在,请勿重复添加!") ; // 异常码 diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/model/BasePageQuery.java b/weblog-module-common/src/main/java/com/hanserwei/common/model/BasePageQuery.java new file mode 100644 index 0000000..5a83569 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/model/BasePageQuery.java @@ -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; +} + diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/model/vo/SelectRspVO.java b/weblog-module-common/src/main/java/com/hanserwei/common/model/vo/SelectRspVO.java new file mode 100644 index 0000000..a87756d --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/model/vo/SelectRspVO.java @@ -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; +} diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/utils/PageResponse.java b/weblog-module-common/src/main/java/com/hanserwei/common/utils/PageResponse.java new file mode 100644 index 0000000..27cb82b --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/utils/PageResponse.java @@ -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 extends Response> { + + /** + * 总记录数 + */ + 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 响应数据类型 + */ + public static PageResponse success(Page page) { + PageResponse 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 转换为 DTO PageResponse) + * + * @param page Spring Data JPA 提供的实体分页接口 + * @param data 经过转换后的 DTO 列表 + * @param 实体类型 + * @param DTO 类型 + */ + public static PageResponse success(Page page, List data) { + PageResponse 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; + } +} \ No newline at end of file diff --git a/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java b/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java deleted file mode 100644 index f3783c8..0000000 --- a/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.hanserwei.web.controller; - -import com.hanserwei.common.aspect.ApiOperationLog; -import com.hanserwei.common.utils.Response; -import com.hanserwei.web.model.User; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.BindingResult; -import org.springframework.validation.FieldError; -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.RestController; - -import java.util.stream.Collectors; - -@RestController -@Slf4j -public class TestController { - - @PostMapping("/admin/test") - @ApiOperationLog(description = "测试接口") - public ResponseEntitytest(@RequestBody @Validated User user, BindingResult bindingResult) { - // 是否存在校验错误 - if (bindingResult.hasErrors()) { - // 获取校验不通过字段的提示信息 - String errorMsg = bindingResult.getFieldErrors() - .stream() - .map(FieldError::getDefaultMessage) - .collect(Collectors.joining(", ")); - - return ResponseEntity.badRequest().body(errorMsg); - } - - // 返参 - return ResponseEntity.ok("参数没有任何问题"); - } - - @PostMapping("/admin/update") - @ApiOperationLog(description = "测试更新接口") - @PreAuthorize("hasRole('ROLE_ADMIN')") - public Response testUpdate() { - log.info("更新成功..."); - return Response.success(); - } - -} \ No newline at end of file