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