diff --git a/.gitignore b/.gitignore index 982e56b..0f90eae 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,5 @@ Desktop.ini # -------------------------------------------------------------------------- application-prod.yml application-dev.yml -.env \ No newline at end of file +.env +/.idea/.cache/.Apifox_Helper/.toolWindow.db diff --git a/sql/createTable.sql b/sql/createTable.sql index 17d38a8..34f9cb2 100644 --- a/sql/createTable.sql +++ b/sql/createTable.sql @@ -38,8 +38,8 @@ COMMENT ON COLUMN t_user.is_deleted IS '逻辑删除:FALSE:未删除 TRUE: CREATE TABLE t_user_role ( id BIGSERIAL PRIMARY KEY, - username VARCHAR(60) NOT NULL, - role_name VARCHAR(60) NOT NULL, -- 重命名为 role_name 避免关键字冲突 + username VARCHAR(60) NOT NULL, + role_name VARCHAR(60) NOT NULL, -- 重命名为 role_name 避免关键字冲突 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); @@ -63,7 +63,7 @@ CREATE TABLE t_category id BIGSERIAL PRIMARY KEY, -- 分类名称:VARCHAR(60) NOT NULL DEFAULT '',同时是 UNIQUE 约束 - "name" VARCHAR(60) NOT NULL DEFAULT '', + "name" VARCHAR(60) NOT NULL DEFAULT '', -- 创建时间 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), @@ -72,7 +72,7 @@ CREATE TABLE t_category update_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), -- 逻辑删除标志位:tinyint(2) NOT NULL DEFAULT '0',改为 BOOLEAN - is_deleted BOOLEAN NOT NULL DEFAULT FALSE, + is_deleted BOOLEAN NOT NULL DEFAULT FALSE, -- UNIQUE KEY uk_name (`name`) CONSTRAINT uk_name UNIQUE ("name") @@ -95,4 +95,50 @@ CREATE TRIGGER set_t_category_update_time FOR EACH ROW EXECUTE FUNCTION set_update_time(); -- ==================================================================================================================== +-- ==================================================================================================================== +-- ==================================================================================================================== +-- ==================================================================================================================== +-- 1. 创建表结构 +CREATE TABLE t_tag +( + -- id: 使用 BIG SERIAL,自动创建序列,性能优异 + id BIGSERIAL PRIMARY KEY, + + -- name: 保持 VARCHAR(60),但在 PG 中 TEXT 和 VARCHAR 性能一样, + -- 这里为了保留原表 "60字符限制" 的业务逻辑,继续使用 VARCHAR(60) + name VARCHAR(60) NOT NULL DEFAULT '', + + -- create_time: 使用带时区的时间戳,更标准严谨 + create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + + -- update_time: 同上 + update_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + + -- is_deleted: 使用原生 BOOLEAN 类型,存储效率高且语义明确 + is_deleted BOOLEAN NOT NULL DEFAULT FALSE, + + -- 约束:显式命名约束,方便后续维护(如报错时能看到具体约束名) + CONSTRAINT uk_tag_name UNIQUE (name) +); + +-- 2. 创建普通索引 +-- 对应 MySQL 的 KEY `idx_create_time` +CREATE INDEX idx_tag_create_time ON t_tag (create_time); + +-- 3. 添加注释 (PostgreSQL 标准方式) +COMMENT ON TABLE t_tag IS '文章标签表'; +COMMENT ON COLUMN t_tag.id IS '标签id'; +COMMENT ON COLUMN t_tag.name IS '标签名称'; +COMMENT ON COLUMN t_tag.create_time IS '创建时间'; +COMMENT ON COLUMN t_tag.update_time IS '最后一次更新时间'; +COMMENT ON COLUMN t_tag.is_deleted IS '逻辑删除标志位:FALSE:未删除 TRUE:已删除'; + +-- 4. 应用自动更新时间戳触发器 (体现 PostgreSQL 强大的过程语言优势) +-- 前提:您之前已经执行过 CREATE FUNCTION set_update_time() ... +CREATE TRIGGER set_t_tag_update_time + BEFORE UPDATE + ON t_tag + FOR EACH ROW +EXECUTE FUNCTION set_update_time(); +-- ==================================================================================================================== -- ==================================================================================================================== \ No newline at end of file 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 index 0b57fa7..c080c25 100644 --- 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 @@ -1,9 +1,9 @@ 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.model.vo.category.AddCategoryReqVO; +import com.hanserwei.admin.model.vo.category.DeleteCategoryReqVO; +import com.hanserwei.admin.model.vo.category.FindCategoryPageListReqVO; +import com.hanserwei.admin.model.vo.category.FindCategoryPageListRspVO; import com.hanserwei.admin.service.AdminCategoryService; import com.hanserwei.common.aspect.ApiOperationLog; import com.hanserwei.common.model.vo.SelectRspVO; diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminTagController.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminTagController.java new file mode 100644 index 0000000..1767ad3 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminTagController.java @@ -0,0 +1,67 @@ +package com.hanserwei.admin.controller; + +import com.hanserwei.admin.model.vo.category.FindCategoryPageListReqVO; +import com.hanserwei.admin.model.vo.category.FindCategoryPageListRspVO; +import com.hanserwei.admin.model.vo.tag.*; +import com.hanserwei.admin.service.AdminTagService; +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 AdminTagController { + + @Resource + private AdminTagService adminTagService; + + /** + * 添加标签 + */ + @PostMapping("/tag/add") + @ApiOperationLog(description = "添加标签") + public Response addTag(@RequestBody @Validated AddTagReqVO addTagReqVO) { + return adminTagService.addTag(addTagReqVO); + } + + /** + * 标签分页数据获取 + */ + @PostMapping("/tag/list") + @ApiOperationLog(description = "标签分页数据获取") + public PageResponse findTagList(@RequestBody @Validated FindTagPageListReqVO findTagPageListReqVO) { + return adminTagService.findTagList(findTagPageListReqVO); + } + + /** + * 删除标签 + */ + @PostMapping("/tag/delete") + @ApiOperationLog(description = "删除标签") + public Response deleteTag(@RequestBody @Validated DeleteTagReqVO deleteTagReqVO) { + return adminTagService.deleteTag(deleteTagReqVO); + } + + /** + * 标签模糊查询 + */ + @PostMapping("/tag/search") + @ApiOperationLog(description = "标签模糊查询") + public Response> searchTag(@RequestBody @Validated SearchTagReqVO searchTagReqVO) { + return adminTagService.searchTag(searchTagReqVO); + } + + +} 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 index 3494e01..ba1ca65 100644 --- 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 @@ -1,7 +1,7 @@ package com.hanserwei.admin.controller; -import com.hanserwei.admin.model.vo.FindUserInfoRspVO; -import com.hanserwei.admin.model.vo.UpdateAdminUserPasswordReqVO; +import com.hanserwei.admin.model.vo.user.FindUserInfoRspVO; +import com.hanserwei.admin.model.vo.user.UpdateAdminUserPasswordReqVO; import com.hanserwei.admin.service.AdminUserService; import com.hanserwei.common.aspect.ApiOperationLog; import com.hanserwei.common.utils.Response; 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/category/AddCategoryReqVO.java similarity index 91% rename from weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/AddCategoryReqVO.java rename to weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/category/AddCategoryReqVO.java index 6579e32..a9908e0 100644 --- 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/category/AddCategoryReqVO.java @@ -1,4 +1,4 @@ -package com.hanserwei.admin.model.vo; +package com.hanserwei.admin.model.vo.category; import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; 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/category/DeleteCategoryReqVO.java similarity index 87% rename from weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/DeleteCategoryReqVO.java rename to weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/category/DeleteCategoryReqVO.java index 137db3b..f4d3986 100644 --- 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/category/DeleteCategoryReqVO.java @@ -1,4 +1,4 @@ -package com.hanserwei.admin.model.vo; +package com.hanserwei.admin.model.vo.category; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; 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/category/FindCategoryPageListReqVO.java similarity index 90% rename from weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindCategoryPageListReqVO.java rename to weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/category/FindCategoryPageListReqVO.java index f1e7d07..db6b9d3 100644 --- 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/category/FindCategoryPageListReqVO.java @@ -1,4 +1,4 @@ -package com.hanserwei.admin.model.vo; +package com.hanserwei.admin.model.vo.category; import com.hanserwei.common.model.BasePageQuery; import lombok.*; 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/category/FindCategoryPageListRspVO.java similarity index 90% rename from weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindCategoryPageListRspVO.java rename to weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/category/FindCategoryPageListRspVO.java index ec8035f..ba918d1 100644 --- 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/category/FindCategoryPageListRspVO.java @@ -1,4 +1,4 @@ -package com.hanserwei.admin.model.vo; +package com.hanserwei.admin.model.vo.category; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/AddTagReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/AddTagReqVO.java new file mode 100644 index 0000000..2025ffe --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/AddTagReqVO.java @@ -0,0 +1,22 @@ +package com.hanserwei.admin.model.vo.tag; + +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class AddTagReqVO { + + /** + * 标签集合 + */ + @NotEmpty(message = "标签集合 不能为空") + private List tags; +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/DeleteTagReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/DeleteTagReqVO.java new file mode 100644 index 0000000..55490b5 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/DeleteTagReqVO.java @@ -0,0 +1,20 @@ +package com.hanserwei.admin.model.vo.tag; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DeleteTagReqVO { + + /** + * 标签 ID + */ + @NotNull(message = "标签 ID 不能为空") + private Long id; +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagPageListReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagPageListReqVO.java new file mode 100644 index 0000000..590277b --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagPageListReqVO.java @@ -0,0 +1,30 @@ +package com.hanserwei.admin.model.vo.tag; + +import com.hanserwei.common.model.BasePageQuery; +import lombok.*; + +import java.time.LocalDate; + +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FindTagPageListReqVO 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/tag/FindTagPageListRspVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagPageListRspVO.java new file mode 100644 index 0000000..79748a8 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagPageListRspVO.java @@ -0,0 +1,30 @@ +package com.hanserwei.admin.model.vo.tag; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FindTagPageListRspVO { + /** + * 标签 ID + */ + private Long id; + + /** + * 标签名称 + */ + private String name; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/SearchTagReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/SearchTagReqVO.java new file mode 100644 index 0000000..ddfdf78 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/SearchTagReqVO.java @@ -0,0 +1,19 @@ +package com.hanserwei.admin.model.vo.tag; + +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SearchTagReqVO { + + @NotEmpty(message = "标签查询关键词不能为空!") + private String key; +} 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/user/FindUserInfoRspVO.java similarity index 85% rename from weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/FindUserInfoRspVO.java rename to weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/user/FindUserInfoRspVO.java index c4483c5..445782b 100644 --- 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/user/FindUserInfoRspVO.java @@ -1,4 +1,4 @@ -package com.hanserwei.admin.model.vo; +package com.hanserwei.admin.model.vo.user; import lombok.AllArgsConstructor; import lombok.Builder; 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/user/UpdateAdminUserPasswordReqVO.java similarity index 91% rename from weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/UpdateAdminUserPasswordReqVO.java rename to weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/user/UpdateAdminUserPasswordReqVO.java index 2adfac8..bdec185 100644 --- 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/user/UpdateAdminUserPasswordReqVO.java @@ -1,4 +1,4 @@ -package com.hanserwei.admin.model.vo; +package com.hanserwei.admin.model.vo.user; import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; 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 index e8f63b3..2ac64cf 100644 --- 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 @@ -1,9 +1,9 @@ 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.admin.model.vo.category.AddCategoryReqVO; +import com.hanserwei.admin.model.vo.category.DeleteCategoryReqVO; +import com.hanserwei.admin.model.vo.category.FindCategoryPageListReqVO; +import com.hanserwei.admin.model.vo.category.FindCategoryPageListRspVO; import com.hanserwei.common.model.vo.SelectRspVO; import com.hanserwei.common.utils.PageResponse; import com.hanserwei.common.utils.Response; diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminTagService.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminTagService.java new file mode 100644 index 0000000..a2aefc3 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminTagService.java @@ -0,0 +1,42 @@ +package com.hanserwei.admin.service; + +import com.hanserwei.admin.model.vo.tag.*; +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 AdminTagService { + /** + * 添加标签 + * + * @param addTagReqVO 添加标签请求参数 + * @return 响应结果 + */ + Response addTag(AddTagReqVO addTagReqVO); + + /** + * 标签分页数据获取 + * + * @param findTagPageListReqVO 标签分页数据获取请求参数 + * @return 响应结果 + */ + PageResponse findTagList(FindTagPageListReqVO findTagPageListReqVO); + + /** + * 删除标签 + * + * @param deleteTagReqVO 删除标签请求参数 + * @return 响应结果 + */ + Response deleteTag(DeleteTagReqVO deleteTagReqVO); + + /** + * 标签下拉列表数据获取 + * + * @param searchTagReqVO 搜索标签请求参数 + * @return 响应结果 + */ + Response> searchTag(SearchTagReqVO searchTagReqVO); +} 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 index 5774fdc..4786250 100644 --- 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 @@ -1,7 +1,7 @@ package com.hanserwei.admin.service; -import com.hanserwei.admin.model.vo.FindUserInfoRspVO; -import com.hanserwei.admin.model.vo.UpdateAdminUserPasswordReqVO; +import com.hanserwei.admin.model.vo.user.FindUserInfoRspVO; +import com.hanserwei.admin.model.vo.user.UpdateAdminUserPasswordReqVO; import com.hanserwei.common.utils.Response; public interface AdminUserService { 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 index d16f3e3..7dd8ec1 100644 --- 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 @@ -1,34 +1,25 @@ 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.model.vo.category.AddCategoryReqVO; +import com.hanserwei.admin.model.vo.category.DeleteCategoryReqVO; +import com.hanserwei.admin.model.vo.category.FindCategoryPageListReqVO; +import com.hanserwei.admin.model.vo.category.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.PageHelper; 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 { @@ -53,53 +44,29 @@ public class AdminCategoryServiceImpl implements AdminCategoryService { @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() + return PageHelper.findPageList( + categoryRepository, + findCategoryPageListReqVO, + findCategoryPageListReqVO.getName(), + findCategoryPageListReqVO.getStartDate(), + findCategoryPageListReqVO.getEndDate(), + 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); + .build() + ); } @Override public Response deleteCategory(DeleteCategoryReqVO deleteCategoryReqVO) { - Long id = deleteCategoryReqVO.getId(); - categoryRepository.deleteById(id); - return Response.success(); + return categoryRepository.findById(deleteCategoryReqVO.getId()) + .map(tag -> { + tag.setIsDeleted(true); + categoryRepository.save(tag); + return Response.success(); + }) + .orElse(Response.fail(ResponseCodeEnum.CATEGORY_NOT_EXIST)); } @Override @@ -107,7 +74,7 @@ public class AdminCategoryServiceImpl implements AdminCategoryService { List categoryList = categoryRepository.findAll(); // DO 转 VO List selectRspVOS = null; - if (!CollectionUtils.isEmpty(categoryList)){ + if (!CollectionUtils.isEmpty(categoryList)) { selectRspVOS = categoryList.stream() .map(category -> SelectRspVO.builder() .label(category.getName()) diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminTagServiceImpl.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminTagServiceImpl.java new file mode 100644 index 0000000..d7906ea --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminTagServiceImpl.java @@ -0,0 +1,106 @@ +package com.hanserwei.admin.service.impl; + +import com.hanserwei.admin.model.vo.tag.*; +import com.hanserwei.admin.service.AdminTagService; +import com.hanserwei.common.domain.dataobject.Tag; +import com.hanserwei.common.domain.repository.TagRepository; +import com.hanserwei.common.enums.ResponseCodeEnum; +import com.hanserwei.common.model.vo.SelectRspVO; +import com.hanserwei.common.utils.PageHelper; +import com.hanserwei.common.utils.PageResponse; +import com.hanserwei.common.utils.Response; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; + +@Service +public class AdminTagServiceImpl implements AdminTagService { + + @Resource + private TagRepository tagRepository; + + + /** + * 添加标签 + * + * @param addTagReqVO 添加标签请求对象 + * @return 响应结果 + */ + @Override + public Response addTag(AddTagReqVO addTagReqVO) { + // 获取标签列表 + List tagList = addTagReqVO.getTags(); + + // 对标签进行清洗:去除空格、过滤空字符串、去重 + List names = tagList.stream() + .map(String::trim) // 去除首尾空格 + .filter(s -> !s.isEmpty()) // 过滤空字符串 + .distinct() // 去重 + .toList(); + + // 查询数据库中已存在的标签名称 + List exists = tagRepository.findByNameIn(names).stream() + .map(Tag::getName) + .toList(); + + // 筛选出需要新建的标签,并构建标签对象 + List toCreate = names.stream() + .filter(n -> !exists.contains(n)) // 过滤掉已存在的标签 + .map(n -> Tag.builder().name(n).build()) // 构建标签对象 + .toList(); + + // 批量保存新标签到数据库 + if (!toCreate.isEmpty()) { + tagRepository.saveAllAndFlush(toCreate); + } + + return Response.success(); + } + + @Override + public PageResponse findTagList(FindTagPageListReqVO findTagPageListReqVO) { + return PageHelper.findPageList( + tagRepository, + findTagPageListReqVO, + findTagPageListReqVO.getName(), + findTagPageListReqVO.getStartDate(), + findTagPageListReqVO.getEndDate(), + tag -> FindTagPageListRspVO.builder() + .id(tag.getId()) + .name(tag.getName()) + .createTime(LocalDateTime.ofInstant(tag.getCreateTime(), ZoneId.systemDefault())) + .build() + ); + } + + @Override + public Response deleteTag(DeleteTagReqVO deleteTagReqVO) { + + return tagRepository.findById(deleteTagReqVO.getId()) + .map(tag -> { + tag.setIsDeleted(true); + tagRepository.save(tag); + return Response.success(); + }) + .orElse(Response.fail(ResponseCodeEnum.TAG_NOT_EXIST)); + } + + @Override + public Response> searchTag(SearchTagReqVO searchTagReqVO) { + // 使用模糊查询获取标签列表 + List tags = tagRepository.findByNameContaining(searchTagReqVO.getKey()); + + // 将标签转换为下拉列表格式 + List vos = tags.stream() + .map(tag -> SelectRspVO.builder() + .label(tag.getName()) + .value(tag.getId()) + .build()) + .toList(); + + return Response.success(vos); + } +} 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 index 162b743..0136283 100644 --- 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 @@ -1,7 +1,7 @@ package com.hanserwei.admin.service.impl; -import com.hanserwei.admin.model.vo.FindUserInfoRspVO; -import com.hanserwei.admin.model.vo.UpdateAdminUserPasswordReqVO; +import com.hanserwei.admin.model.vo.user.FindUserInfoRspVO; +import com.hanserwei.admin.model.vo.user.UpdateAdminUserPasswordReqVO; import com.hanserwei.admin.service.AdminUserService; import com.hanserwei.common.domain.repository.UserRepository; import com.hanserwei.common.enums.ResponseCodeEnum; 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 index fb3f27a..03b7ff5 100644 --- 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 @@ -27,7 +27,6 @@ import java.time.Instant; @Index(name = "idx_create_time", columnList = "create_time") }) @SQLRestriction("is_deleted = false") -@SQLDelete(sql = "UPDATE t_category SET is_deleted = true WHERE id = ?") public class Category implements Serializable { @Serial diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Tag.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Tag.java new file mode 100644 index 0000000..5d1aa92 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Tag.java @@ -0,0 +1,41 @@ +package com.hanserwei.common.domain.dataobject; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.SQLRestriction; +import org.hibernate.annotations.UpdateTimestamp; +import java.io.Serializable; +import java.time.Instant; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "t_tag", indexes = { + @Index(name = "idx_tag_create_time", columnList = "create_time") +}) +@SQLRestriction("is_deleted = false") +public class Tag implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 60, unique = true) + private String name; + + @CreationTimestamp + @Column(name = "create_time", nullable = false, updatable = false) + private Instant createTime; + + @UpdateTimestamp + @Column(name = "update_time", nullable = false) + private Instant updateTime; + + @Column(name = "is_deleted", nullable = false) + @Builder.Default + private Boolean isDeleted = false; +} \ No newline at end of file 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 index d560343..b6da61a 100644 --- 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 @@ -5,8 +5,9 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -public interface CategoryRepository extends JpaRepository { +public interface CategoryRepository extends JpaRepository, JpaSpecificationExecutor { boolean existsCategoryByName(String name); Page findAll(Specification specification, Pageable pageable); diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/TagRepository.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/TagRepository.java new file mode 100644 index 0000000..cd25fb2 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/TagRepository.java @@ -0,0 +1,25 @@ +package com.hanserwei.common.domain.repository; + +import com.hanserwei.common.domain.dataobject.Tag; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +import java.util.Collection; +import java.util.List; + +public interface TagRepository extends JpaRepository, JpaSpecificationExecutor { + Collection findByNameIn(List names); + + Page findAll(Specification specification, Pageable pageable); + + /** + * 根据标签名称模糊查询 + * @param name 标签名称关键词 + * @return 标签列表 + */ + List findByNameContaining(String name); + +} 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 e239471..bc94f56 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 @@ -18,8 +18,9 @@ public enum ResponseCodeEnum implements BaseExceptionInterface { UNAUTHORIZED("20002", "无访问权限,请先登录!"), FORBIDDEN("20004", "演示账号仅支持查询操作!"), USER_NOT_EXIST("2005", "有户不存在!"), - CATEGORY_NAME_IS_EXISTED("20005", "该分类已存在,请勿重复添加!") - ; + CATEGORY_NAME_IS_EXISTED("20005", "该分类已存在,请勿重复添加!"), + TAG_NOT_EXIST("20006", "标签不存在!"), + CATEGORY_NOT_EXIST("20007", "分类不存在!" ); // 异常码 private final String errorCode; diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/utils/PageHelper.java b/weblog-module-common/src/main/java/com/hanserwei/common/utils/PageHelper.java new file mode 100644 index 0000000..dec8b5c --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/utils/PageHelper.java @@ -0,0 +1,91 @@ +package com.hanserwei.common.utils; + +import com.hanserwei.common.model.BasePageQuery; +import jakarta.persistence.criteria.Predicate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +/** + * 分页查询工具类 + * 用于抽取通用的分页查询逻辑 + */ +public class PageHelper { + + /** + * 执行带条件的分页查询 + * + * @param repository JPA Repository + * @param pageQuery 分页查询参数 + * @param name 名称(用于模糊查询) + * @param startDate 开始日期 + * @param endDate 结束日期 + * @param converter DO 到 VO 的转换函数 + * @param 实体类型 + * @param 响应VO类型 + * @return 分页响应 + */ + public static PageResponse findPageList( + JpaSpecificationExecutor repository, + BasePageQuery pageQuery, + String name, + LocalDate startDate, + LocalDate endDate, + Function converter) { + + // 构建分页参数 + Pageable pageable = PageRequest.of( + pageQuery.getCurrent().intValue() - 1, + pageQuery.getSize().intValue(), + Sort.by(Sort.Direction.DESC, "createTime") + ); + + // 构建查询条件 + Specification specification = (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + + // 名称模糊查询 + if (StringUtils.hasText(name)) { + predicates.add( + criteriaBuilder.like(root.get("name"), "%" + name.trim() + "%") + ); + } + + // 开始日期查询 + if (Objects.nonNull(startDate)) { + predicates.add( + criteriaBuilder.greaterThanOrEqualTo(root.get("createTime"), startDate) + ); + } + + // 结束日期查询 + if (Objects.nonNull(endDate)) { + predicates.add( + criteriaBuilder.lessThan(root.get("createTime"), endDate.plusDays(1).atStartOfDay()) + ); + } + + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + + // 执行查询 + Page page = repository.findAll(specification, pageable); + + // DO 转 VO + List vos = page.getContent().stream() + .map(converter) + .toList(); + + return PageResponse.success(page, vos); + } +} diff --git a/weblog-module-jwt/src/main/java/com/hanserwei/jwt/filter/TokenAuthenticationFilter.java b/weblog-module-jwt/src/main/java/com/hanserwei/jwt/filter/TokenAuthenticationFilter.java index bf9d5cc..be8f8b0 100644 --- a/weblog-module-jwt/src/main/java/com/hanserwei/jwt/filter/TokenAuthenticationFilter.java +++ b/weblog-module-jwt/src/main/java/com/hanserwei/jwt/filter/TokenAuthenticationFilter.java @@ -44,58 +44,64 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { // 1. 从请求头中获取 Authorization String header = request.getHeader("Authorization"); - // 2. 校验头格式 (必须以 Bearer 开头) - if (header != null && header.startsWith("Bearer ")) { - String token = StringUtils.substring(header, 7); - log.info("JWT Token: {}", token); - if (StringUtils.isNotBlank(token)) { - try { - // 3. 解析 Token (核心步骤) - // 注意:JwtTokenHelper.parseToken 方法内部已经处理了 JWT 格式校验和过期校验, - // 并将 JJWT 异常转换为了 Spring Security 的 AuthenticationException 抛出。 - Jws claimsJws = jwtTokenHelper.parseToken(token); + String requestURI = request.getRequestURI(); + log.info("Request URI: {}", requestURI); + if (requestURI.startsWith("/admin")) { + // 2. 校验头格式 (必须以 Bearer 开头) + if (header != null && header.startsWith("Bearer ")) { + String token = StringUtils.substring(header, 7); + log.info("JWT Token: {}", token); + if (StringUtils.isNotBlank(token)) { + try { + // 3. 解析 Token (核心步骤) + // 注意:JwtTokenHelper.parseToken 方法内部已经处理了 JWT 格式校验和过期校验, + // 并将 JJWT 异常转换为了 Spring Security 的 AuthenticationException 抛出。 + Jws claimsJws = jwtTokenHelper.parseToken(token); - // 4. 获取用户名 - // JJWT 0.12+ 建议使用 getPayload() 替代 getBody() - // 在 Helper 中生成 Token 时使用的是 .subject(username),所以这里取 Subject - String username = claimsJws.getPayload().getSubject(); + // 4. 获取用户名 + // JJWT 0.12+ 建议使用 getPayload() 替代 getBody() + // 在 Helper 中生成 Token 时使用的是 .subject(username),所以这里取 Subject + String username = claimsJws.getPayload().getSubject(); - // 5. 组装认证信息 (如果当前上下文没有认证信息) - if (StringUtils.isNotBlank(username) && Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) { + // 5. 组装认证信息 (如果当前上下文没有认证信息) + if (StringUtils.isNotBlank(username) && Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) { - // 查询数据库获取用户完整信息 (包含权限) - UserDetails userDetails = userDetailsService.loadUserByUsername(username); + // 查询数据库获取用户完整信息 (包含权限) + UserDetails userDetails = userDetailsService.loadUserByUsername(username); - // 构建 Spring Security 的认证 Token - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( - userDetails, - null, - userDetails.getAuthorities() - ); + // 构建 Spring Security 的认证 Token + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - // 6. 将认证信息存入 SecurityContext - SecurityContextHolder.getContext().setAuthentication(authentication); + // 6. 将认证信息存入 SecurityContext + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (AuthenticationException e) { + // 7. 异常处理 + // 捕获 JwtTokenHelper 抛出的 BadCredentialsException 或 CredentialsExpiredException + // 如果 Token 存在但是无效/过期,直接交给 EntryPoint 处理响应 (通常返回 401) + // 并 return 结束当前过滤器,不再往下执行 + authenticationEntryPoint.commence(request, response, e); + return; + } catch (Exception e) { + // 处理其他未预料到的异常 + log.error("JWT处理过程中发生未知错误", e); + authenticationEntryPoint.commence(request, response, new AuthenticationException("系统内部认证错误") { + }); + return; } - } catch (AuthenticationException e) { - // 7. 异常处理 - // 捕获 JwtTokenHelper 抛出的 BadCredentialsException 或 CredentialsExpiredException - // 如果 Token 存在但是无效/过期,直接交给 EntryPoint 处理响应 (通常返回 401) - // 并 return 结束当前过滤器,不再往下执行 - authenticationEntryPoint.commence(request, response, e); - return; - } catch (Exception e) { - // 处理其他未预料到的异常 - log.error("JWT处理过程中发生未知错误", e); - authenticationEntryPoint.commence(request, response, new AuthenticationException("系统内部认证错误") { - }); - return; } } - } - // 8. 放行请求 (如果没有 Token 或者 Token 校验通过,继续执行下一个过滤器) - filterChain.doFilter(request, response); + // 8. 放行请求 (如果没有 Token 或者 Token 校验通过,继续执行下一个过滤器) + filterChain.doFilter(request, response); + } else { + filterChain.doFilter(request, response); + } } } \ No newline at end of file