diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml index 73e5cce..889b07e 100644 --- a/.idea/data_source_mapping.xml +++ b/.idea/data_source_mapping.xml @@ -1,6 +1,7 @@ + diff --git a/sql/createTable.sql b/sql/createTable.sql index e3b347a..eccb808 100644 --- a/sql/createTable.sql +++ b/sql/createTable.sql @@ -125,7 +125,7 @@ CREATE TABLE t_tag -- 对应 MySQL 的 KEY `idx_create_time` CREATE INDEX idx_tag_create_time ON t_tag (create_time); --- 3. 添加注释 (PostgreSQL 标准方式) +-- 3. 添加注释 (PostgresSQL 标准方式) COMMENT ON TABLE t_tag IS '文章标签表'; COMMENT ON COLUMN t_tag.id IS '标签id'; COMMENT ON COLUMN t_tag.name IS '标签名称'; @@ -133,7 +133,7 @@ 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 强大的过程语言优势) +-- 4. 应用自动更新时间戳触发器 (体现 PostgresSQL 强大的过程语言优势) -- 前提:您之前已经执行过 CREATE FUNCTION set_update_time() ... CREATE TRIGGER set_t_tag_update_time BEFORE UPDATE @@ -146,7 +146,7 @@ EXECUTE FUNCTION set_update_time(); -- ==================================================================================================================== CREATE TABLE t_blog_settings ( - -- id: 使用 BIGSERIAL 自动管理序列 + -- id: 使用 BIG SERIAL 自动管理序列 id BIGSERIAL PRIMARY KEY, -- logo: 图片路径可能很长,使用 TEXT 替代 VARCHAR(120),无性能损耗 @@ -185,4 +185,138 @@ COMMENT ON COLUMN t_blog_settings.csdn_homepage IS 'CSDN 主页访问地址'; COMMENT ON COLUMN t_blog_settings.gitee_homepage IS 'Gitee 主页访问地址'; COMMENT ON COLUMN t_blog_settings.zhihu_homepage IS '知乎主页访问地址'; -- ==================================================================================================================== +-- ==================================================================================================================== +-- ==================================================================================================================== +-- ==================================================================================================================== +CREATE TABLE t_article +( + id BIGSERIAL PRIMARY KEY, + + -- 标题:保持限制,适合作为索引或列表显示 + title VARCHAR(120) NOT NULL DEFAULT '', + + -- 封面:使用 TEXT,不再担心 URL 超长 + cover TEXT NOT NULL DEFAULT '', + + -- 摘要:使用 TEXT,不再受 160 字限制,前端截取即可 + summary TEXT DEFAULT '', + + -- 阅读量:使用 INTEGER 配合 CHECK 约束模拟 unsigned + read_num INTEGER NOT NULL DEFAULT 1 CHECK (read_num >= 0), + + -- 时间与逻辑删除 + create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + is_deleted BOOLEAN NOT NULL DEFAULT FALSE +); + +-- 索引 +CREATE INDEX idx_article_create_time ON t_article (create_time); + +-- 自动更新时间戳触发器 +CREATE TRIGGER set_t_article_update_time + BEFORE UPDATE + ON t_article + FOR EACH ROW +EXECUTE FUNCTION set_update_time(); + +-- 注释 +COMMENT ON TABLE t_article IS '文章表'; +COMMENT ON COLUMN t_article.read_num IS '被阅读次数 (>=0)'; +COMMENT ON COLUMN t_article.cover IS '文章封面'; +-- ==================================================================================================================== +-- ==================================================================================================================== +-- ==================================================================================================================== +-- ==================================================================================================================== +CREATE TABLE t_article_content +( + id BIGSERIAL PRIMARY KEY, + + -- 外键关联字段 + article_id BIGINT NOT NULL, + + -- 正文:PG 的 TEXT 能够容纳海量文字 + content TEXT, + + -- 显式外键约束:确保 article_id 必须存在于 t_article 表中 + -- ON DELETE CASCADE: 如果物理删除了 t_article,对应的内容也会被自动删除 + CONSTRAINT fk_article_content_article_id + FOREIGN KEY (article_id) + REFERENCES t_article (id) + ON DELETE CASCADE +); + +-- 索引 +CREATE INDEX idx_article_content_article_id ON t_article_content (article_id); + +-- 注释 +COMMENT ON TABLE t_article_content IS '文章内容表'; +COMMENT ON COLUMN t_article_content.content IS '教程正文'; +-- ==================================================================================================================== +-- ==================================================================================================================== +-- ==================================================================================================================== +-- ==================================================================================================================== +CREATE TABLE t_article_category_rel +( + id BIGSERIAL PRIMARY KEY, + + -- 文章 ID:添加外键,删除文章时自动删除此关联 + article_id BIGINT NOT NULL, + + -- 分类 ID:添加外键,删除分类时... (通常分类不轻易删,或者策略不同,这里设为级联删除) + category_id BIGINT NOT NULL, + + -- 约束:保证 article_id 唯一 (对应 MySQL 的 UNIQUE KEY) + CONSTRAINT uni_article_category_rel_article_id UNIQUE (article_id), + + -- 外键定义 + CONSTRAINT fk_rel_cat_article + FOREIGN KEY (article_id) + REFERENCES t_article (id) + ON DELETE CASCADE, + + CONSTRAINT fk_rel_cat_category + FOREIGN KEY (category_id) + REFERENCES t_category (id) + ON DELETE CASCADE +); + +-- 索引:category_id 需要索引以便反向查询(查询某分类下有哪些文章) +CREATE INDEX idx_rel_cat_category_id ON t_article_category_rel (category_id); + +-- 注释 +COMMENT ON TABLE t_article_category_rel IS '文章所属分类关联表'; +-- ==================================================================================================================== +-- ==================================================================================================================== +-- ==================================================================================================================== +-- ==================================================================================================================== +CREATE TABLE t_article_tag_rel +( + id BIGSERIAL PRIMARY KEY, + + article_id BIGINT NOT NULL, + tag_id BIGINT NOT NULL, + + -- 外键定义:级联删除 + CONSTRAINT fk_rel_tag_article + FOREIGN KEY (article_id) + REFERENCES t_article (id) + ON DELETE CASCADE, + + CONSTRAINT fk_rel_tag_tag + FOREIGN KEY (tag_id) + REFERENCES t_tag (id) + ON DELETE CASCADE, + + -- ⚡ 优化:防止同一篇文章被打上重复的标签 + CONSTRAINT uk_article_tag_rel_unique_pair UNIQUE (article_id, tag_id) +); + +-- 索引 +CREATE INDEX idx_rel_tag_article_id ON t_article_tag_rel (article_id); +CREATE INDEX idx_rel_tag_tag_id ON t_article_tag_rel (tag_id); + +-- 注释 +COMMENT ON TABLE t_article_tag_rel IS '文章对应标签关联表'; +-- ==================================================================================================================== -- ==================================================================================================================== \ No newline at end of file diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminArticleController.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminArticleController.java new file mode 100644 index 0000000..093e254 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/controller/AdminArticleController.java @@ -0,0 +1,75 @@ +package com.hanserwei.admin.controller; + +import com.hanserwei.admin.model.vo.article.*; +import com.hanserwei.admin.service.AdminArticleService; +import com.hanserwei.common.aspect.ApiOperationLog; +import com.hanserwei.common.utils.PageResponse; +import com.hanserwei.common.utils.Response; +import jakarta.annotation.Resource; +import org.springframework.security.access.prepost.PreAuthorize; +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/article") +public class AdminArticleController { + + @Resource + private AdminArticleService articleService; + + /** + * 发布文章 + */ + @PostMapping("/publish") + @ApiOperationLog(description = "文章发布") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Response publishArticle(@RequestBody @Validated PublishArticleReqVO publishArticleReqVO) { + return articleService.publishArticle(publishArticleReqVO); + } + + /** + * 删除文章 + */ + @PostMapping("/delete") + @ApiOperationLog(description = "文章删除") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Response deleteArticle(@RequestBody @Validated DeleteArticleReqVO deleteArticleReqVO) { + return articleService.deleteArticle(deleteArticleReqVO); + } + + /** + * 查询文章分页数据 + */ + @PostMapping("/list") + @ApiOperationLog(description = "查询文章分页数据") + public PageResponse findArticlePageList(@RequestBody @Validated FindArticlePageListReqVO findArticlePageListReqVO) { + return articleService.findArticlePageList(findArticlePageListReqVO); + } + + /** + * 查询文章详情 + */ + @PostMapping("/detail") + @ApiOperationLog(description = "查询文章详情") + public Response findArticleDetail(@RequestBody @Validated FindArticleDetailReqVO findArticlePageListReqVO) { + return articleService.findArticleDetail(findArticlePageListReqVO); + } + + /** + * 更新文章 + */ + @PostMapping("/update") + @ApiOperationLog(description = "更新文章") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Response updateArticle(@RequestBody @Validated UpdateArticleReqVO updateArticleReqVO) { + return articleService.updateArticle(updateArticleReqVO); + } + + +} \ 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 c080c25..3929188 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,6 @@ package com.hanserwei.admin.controller; -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.model.vo.category.*; import com.hanserwei.admin.service.AdminCategoryService; import com.hanserwei.common.aspect.ApiOperationLog; import com.hanserwei.common.model.vo.SelectRspVO; @@ -11,10 +8,7 @@ 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 org.springframework.web.bind.annotation.*; import java.util.List; @@ -64,4 +58,13 @@ public class AdminCategoryController { return adminCategoryService.findCategorySelectList(); } + /** + * 根据分类ID查询 + */ + @GetMapping("/category/find/{id}") + @ApiOperationLog(description = "根据分类ID查询") + public Response findCategoryById(@PathVariable Long id) { + return adminCategoryService.findCategoryById(id); + } + } 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 index 1767ad3..8666469 100644 --- 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 @@ -1,7 +1,5 @@ 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; @@ -63,5 +61,13 @@ public class AdminTagController { return adminTagService.searchTag(searchTagReqVO); } + /** + * 根据ID列表获取标签 + */ + @PostMapping("/tag/list/ids") + @ApiOperationLog(description = "根据ID列表获取标签") + public Response> findTagsByIds(@RequestBody @Validated FindTagsByIdsReqVO findTagsByIdsReqVO) { + return adminTagService.findTagsByIds(findTagsByIdsReqVO); + } } diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/DeleteArticleReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/DeleteArticleReqVO.java new file mode 100644 index 0000000..f3986af --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/DeleteArticleReqVO.java @@ -0,0 +1,20 @@ +package com.hanserwei.admin.model.vo.article; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DeleteArticleReqVO { + + /** + * 文章ID + */ + @NotNull(message = "文章 ID 不能为空") + private Long id; +} \ No newline at end of file diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticleDetailReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticleDetailReqVO.java new file mode 100644 index 0000000..71ffbae --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticleDetailReqVO.java @@ -0,0 +1,21 @@ +package com.hanserwei.admin.model.vo.article; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FindArticleDetailReqVO { + + /** + * 文章 ID + */ + @NotNull(message = "文章 ID 不能为空") + private Long id; + +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticleDetailRspVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticleDetailRspVO.java new file mode 100644 index 0000000..612f76c --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticleDetailRspVO.java @@ -0,0 +1,51 @@ +package com.hanserwei.admin.model.vo.article; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FindArticleDetailRspVO { + + /** + * 文章 ID + */ + private Long id; + + /** + * 文章标题 + */ + private String title; + + /** + * 文章封面 + */ + private String cover; + + /** + * 文章内容 + */ + private String content; + + /** + * 分类 ID + */ + private Long categoryId; + + /** + * 标签 ID 集合 + */ + private List tagIds; + + /** + * 摘要 + */ + private String summary; + +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticlePageListReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticlePageListReqVO.java new file mode 100644 index 0000000..3c13f8c --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticlePageListReqVO.java @@ -0,0 +1,30 @@ +package com.hanserwei.admin.model.vo.article; + +import com.hanserwei.common.model.BasePageQuery; +import lombok.*; + +import java.time.LocalDate; + +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FindArticlePageListReqVO extends BasePageQuery { + + /** + * 文章标题 + */ + private String title; + + /** + * 发布的起始日期 + */ + private LocalDate startDate; + + /** + * 发布的结束日期 + */ + private LocalDate endDate; + +} \ No newline at end of file diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticlePageListRspVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticlePageListRspVO.java new file mode 100644 index 0000000..0ae4961 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/FindArticlePageListRspVO.java @@ -0,0 +1,36 @@ +package com.hanserwei.admin.model.vo.article; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FindArticlePageListRspVO { + + /** + * 文章 ID + */ + private Long id; + + /** + * 文章标题 + */ + private String title; + + /** + * 文章封面 + */ + private String cover; + + /** + * 发布时间 + */ + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/PublishArticleReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/PublishArticleReqVO.java new file mode 100644 index 0000000..c1fc919 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/PublishArticleReqVO.java @@ -0,0 +1,59 @@ +package com.hanserwei.admin.model.vo.article; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder + +/* + * 发布文章请求参数 + */ +public class PublishArticleReqVO { + + /** + * 文章标题 + */ + @NotBlank(message = "文章标题不能为空") + @Length(min = 1, max = 40, message = "文章标题字数需大于 1 小于 40") + private String title; + + /** + * 文章内容 + */ + @NotBlank(message = "文章内容不能为空") + private String content; + + /** + * 文章封面图片URL + */ + @NotBlank(message = "文章封面不能为空") + private String cover; + + /** + * 文章摘要 + */ + private String summary; + + /** + * 文章分类ID + */ + @NotNull(message = "文章分类不能为空") + private Long categoryId; + + /** + * 文章标签列表 + */ + @NotEmpty(message = "文章标签不能为空") + private List tags; +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/UpdateArticleReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/UpdateArticleReqVO.java new file mode 100644 index 0000000..3aaf6c5 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/article/UpdateArticleReqVO.java @@ -0,0 +1,65 @@ +package com.hanserwei.admin.model.vo.article; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import java.util.List; + + +/** + * 更新文章请求参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class UpdateArticleReqVO { + + /** + * 文章ID + */ + @NotNull(message = "文章 ID 不能为空") + private Long id; + + /** + * 文章标题 + */ + @NotBlank(message = "文章标题不能为空") + @Length(min = 1, max = 40, message = "文章标题字数需大于 1 小于 40") + private String title; + + /** + * 文章内容 + */ + @NotBlank(message = "文章内容不能为空") + private String content; + + /** + * 文章封面图片URL + */ + @NotBlank(message = "文章封面不能为空") + private String cover; + + /** + * 文章摘要 + */ + private String summary; + + /** + * 文章分类ID + */ + @NotNull(message = "文章分类不能为空") + private Long categoryId; + + /** + * 文章标签列表 + */ + @NotEmpty(message = "文章标签不能为空") + private List tags; +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/category/FindCategoryByIdRspVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/category/FindCategoryByIdRspVO.java new file mode 100644 index 0000000..246f77c --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/category/FindCategoryByIdRspVO.java @@ -0,0 +1,21 @@ +package com.hanserwei.admin.model.vo.category; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FindCategoryByIdRspVO { + /** + * 分类 ID + */ + private Long id; + /** + * 分类名称 + */ + private String name; +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagsByIdsReqVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagsByIdsReqVO.java new file mode 100644 index 0000000..a80c497 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagsByIdsReqVO.java @@ -0,0 +1,21 @@ +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 FindTagsByIdsReqVO { + /** + * 标签 ID 集合 + */ + @NotEmpty(message = "标签 ID 集合不能为空") + private List tagIds; +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagsByIdsRspVO.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagsByIdsRspVO.java new file mode 100644 index 0000000..2d5dc04 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/model/vo/tag/FindTagsByIdsRspVO.java @@ -0,0 +1,21 @@ +package com.hanserwei.admin.model.vo.tag; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class FindTagsByIdsRspVO { + /** + * 标签 ID + */ + private Long id; + /** + * 标签名称 + */ + private String name; +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminArticleService.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminArticleService.java new file mode 100644 index 0000000..d26d638 --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/AdminArticleService.java @@ -0,0 +1,47 @@ +package com.hanserwei.admin.service; + +import com.hanserwei.admin.model.vo.article.*; +import com.hanserwei.common.utils.PageResponse; +import com.hanserwei.common.utils.Response; + +public interface AdminArticleService { + /** + * 发布文章 + * + * @param publishArticleReqVO 文章发布参数 + * @return 响应 + */ + Response publishArticle(PublishArticleReqVO publishArticleReqVO); + + /** + * 删除文章 + * + * @param deleteArticleReqVO 删除文章参数 + * @return 响应 + */ + Response deleteArticle(DeleteArticleReqVO deleteArticleReqVO); + + /** + * 查询文章分页数据 + * + * @param findArticlePageListReqVO 查询文章分页请求参数 + * @return 响应 + */ + PageResponse findArticlePageList(FindArticlePageListReqVO findArticlePageListReqVO); + + /** + * 查询文章详情 + * + * @param findArticleDetailReqVO 查询文章详情参数 + * @return 响应 + */ + Response findArticleDetail(FindArticleDetailReqVO findArticleDetailReqVO); + + /** + * 更新文章 + * + * @param updateArticleReqVO 更新文章参数 + * @return 响应 + */ + Response updateArticle(UpdateArticleReqVO updateArticleReqVO); +} \ 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 index 2ac64cf..787aa50 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,6 @@ package com.hanserwei.admin.service; -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.model.vo.category.*; import com.hanserwei.common.model.vo.SelectRspVO; import com.hanserwei.common.utils.PageResponse; import com.hanserwei.common.utils.Response; @@ -40,6 +37,13 @@ public interface AdminCategoryService { * * @return Select 列表数据 */ - Response> findCategorySelectList(); + Response> findCategorySelectList(); + /** + * 根据分类 ID 查询分类 + * + * @param id 分类 ID + * @return 查询结果 + */ + Response findCategoryById(Long id); } 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 index a2aefc3..f382ffa 100644 --- 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 @@ -39,4 +39,12 @@ public interface AdminTagService { * @return 响应结果 */ Response> searchTag(SearchTagReqVO searchTagReqVO); + + /** + * 根据 ID 列表获取标签 + * + * @param findTagsByIdsReqVO 根据 ID 列表获取标签请求参数 + * @return 响应结果 + */ + Response> findTagsByIds(FindTagsByIdsReqVO findTagsByIdsReqVO); } diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminArticleServiceImpl.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminArticleServiceImpl.java new file mode 100644 index 0000000..42e243e --- /dev/null +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/service/impl/AdminArticleServiceImpl.java @@ -0,0 +1,202 @@ +package com.hanserwei.admin.service.impl; + +import com.hanserwei.admin.model.vo.article.*; +import com.hanserwei.admin.service.AdminArticleService; +import com.hanserwei.common.domain.dataobject.*; +import com.hanserwei.common.domain.repository.*; +import com.hanserwei.common.enums.ResponseCodeEnum; +import com.hanserwei.common.exception.BizException; +import com.hanserwei.common.utils.PageHelper; +import com.hanserwei.common.utils.PageResponse; +import com.hanserwei.common.utils.Response; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Optional; + + +@Slf4j +@Service +public class AdminArticleServiceImpl implements AdminArticleService { + + @Resource + private ArticleRepository articleRepository; + @Resource + private ArticleTagRelRepository articleTagRelRepository; + @Resource + private ArticleCategoryRelRepository articleCategoryRelRepository; + @Resource + private ArticleContentRepository articleContentRepository; + @Resource + private CategoryRepository categoryRepository; + @Resource + private TagRepository tagRepository; + + @Override + @Transactional(rollbackFor = Exception.class) + public Response publishArticle(PublishArticleReqVO publishArticleReqVO) { + Article article = new Article(); + BeanUtils.copyProperties(publishArticleReqVO, article); + Article saved = articleRepository.save(article); + // 拿到保存后的文章id + Long articleId = saved.getId(); + ArticleContent articleContent = ArticleContent.builder() + .articleId(articleId) + .content(publishArticleReqVO.getContent()) + .build(); + articleContentRepository.save(articleContent); + // 处理分类 + Long categoryId = publishArticleReqVO.getCategoryId(); + categoryRepository.findById(categoryId) + .ifPresentOrElse(p -> { + ArticleCategoryRel articleCategoryRel = ArticleCategoryRel.builder() + .articleId(articleId) + .categoryId(categoryId) + .build(); + articleCategoryRelRepository.save(articleCategoryRel); + }, () -> { + log.warn("==>文章分类不存在: {}", categoryId); + throw new BizException(ResponseCodeEnum.CATEGORY_NOT_EXISTED); + }); + // 保存标签 + List tags = publishArticleReqVO.getTags(); + insertTags(articleId, tags); + return Response.success(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Response deleteArticle(DeleteArticleReqVO deleteArticleReqVO) { + Long id = deleteArticleReqVO.getId(); + articleRepository.deleteById(id); + articleCategoryRelRepository.deleteByArticleId(id); + articleTagRelRepository.deleteByArticleId(id); + return Response.success(); + } + + @Override + public PageResponse findArticlePageList(FindArticlePageListReqVO findArticlePageListReqVO) { + return PageHelper.findPageList( + articleRepository, + findArticlePageListReqVO, + findArticlePageListReqVO.getTitle(), + "title", + findArticlePageListReqVO.getStartDate(), + findArticlePageListReqVO.getEndDate(), + article -> FindArticlePageListRspVO.builder() + .id(article.getId()) + .title(article.getTitle()) + .cover(article.getCover()) + .createTime(LocalDateTime.ofInstant(article.getCreateTime(), ZoneId.systemDefault())) + .build() + ); + } + + + @Override + public Response findArticleDetail(FindArticleDetailReqVO findArticleDetailReqVO) { + Long id = findArticleDetailReqVO.getId(); + Article article = articleRepository.findById(id) + .orElseThrow(() -> { + log.warn("==>文章不存在: {}", id); + return new BizException(ResponseCodeEnum.ARTICLE_NOT_EXIST); + }); + // 文章正文详情 + ArticleContent articleContent = articleContentRepository.findByArticleId(id); + // 所属分类 + ArticleCategoryRel articleCategoryRel = articleCategoryRelRepository.findByArticleId(id); + // 对应标签集合 + List articleTagRelList = articleTagRelRepository.findByArticleId(id); + List tags = articleTagRelList.stream().map(ArticleTagRel::getTagId).toList(); + // 封装响应结果 + FindArticleDetailRspVO findArticleDetailRspVO = new FindArticleDetailRspVO(); + BeanUtils.copyProperties(article, findArticleDetailRspVO); + findArticleDetailRspVO.setTagIds(tags); + findArticleDetailRspVO.setCategoryId(articleCategoryRel.getCategoryId()); + findArticleDetailRspVO.setContent(articleContent.getContent()); + return Response.success(findArticleDetailRspVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Response updateArticle(UpdateArticleReqVO updateArticleReqVO) { + Long id = updateArticleReqVO.getId(); + Article article = articleRepository.findById(id) + .orElseThrow(() -> { + log.warn("==>欲更新的文章不存在: {}", id); + return new BizException(ResponseCodeEnum.ARTICLE_NOT_EXIST); + }); + BeanUtils.copyProperties(updateArticleReqVO, article); + // 更新文章正文 + ArticleContent articleContent = articleContentRepository.findByArticleId(id); + articleContent.setContent(updateArticleReqVO.getContent()); + articleContentRepository.save(articleContent); + // 更新分类 + Long categoryId = updateArticleReqVO.getCategoryId(); + // 验证该ID的分类是否存在 + categoryRepository.findById(categoryId).orElseThrow(() -> { + log.warn("==>欲更新的文章其分类不存在: {}", categoryId); + return new BizException(ResponseCodeEnum.CATEGORY_NOT_EXISTED); + }); + // 先删除关联的分类记录,再插入新的关联记录 + articleCategoryRelRepository.deleteByArticleId(id); + ArticleCategoryRel articleCategoryRel = ArticleCategoryRel.builder() + .articleId(id) + .categoryId(categoryId) + .build(); + articleCategoryRelRepository.save(articleCategoryRel); + // 删除该文章的标签关联 + articleTagRelRepository.deleteByArticleId(id); + insertTags(id, updateArticleReqVO.getTags()); + return Response.success(); + } + + /** + * 插入标签 + * + * @param articleId 文章ID + * @param tags 标签列表(可能是ID字符串或标签名称) + */ + private void insertTags(Long articleId, List tags) { + if (CollectionUtils.isEmpty(tags)) { + return; + } + + for (String tagStr : tags) { + Long tagId; + + // 判断是否为数字ID + try { + tagId = Long.parseLong(tagStr); + // 验证该ID的标签是否存在 + Optional tagOptional = tagRepository.findById(tagId); + if (tagOptional.isEmpty()) { + log.warn("==>标签ID不存在: {}", tagId); + throw new BizException(ResponseCodeEnum.TAG_NOT_EXISTED); + } + } catch (NumberFormatException e) { + // 不是数字,说明是新标签名称,需要先保存标签 + Tag newTag = Tag.builder() + .name(tagStr) + .build(); + Tag savedTag = tagRepository.save(newTag); + tagId = savedTag.getId(); + log.info("==>新增标签: {}, ID: {}", tagStr, tagId); + } + + // 保存文章-标签关联关系 + ArticleTagRel articleTagRel = ArticleTagRel.builder() + .articleId(articleId) + .tagId(tagId) + .build(); + articleTagRelRepository.save(articleTagRel); + } + } +} 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 7dd8ec1..c46cccc 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,11 +1,10 @@ package com.hanserwei.admin.service.impl; -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.model.vo.category.*; import com.hanserwei.admin.service.AdminCategoryService; +import com.hanserwei.common.domain.dataobject.ArticleCategoryRel; import com.hanserwei.common.domain.dataobject.Category; +import com.hanserwei.common.domain.repository.ArticleCategoryRelRepository; import com.hanserwei.common.domain.repository.CategoryRepository; import com.hanserwei.common.enums.ResponseCodeEnum; import com.hanserwei.common.exception.BizException; @@ -26,6 +25,8 @@ public class AdminCategoryServiceImpl implements AdminCategoryService { @Resource private CategoryRepository categoryRepository; + @Resource + private ArticleCategoryRelRepository articleCategoryRelRepository; @Override public Response addCategory(AddCategoryReqVO addCategoryReqVO) { @@ -60,6 +61,10 @@ public class AdminCategoryServiceImpl implements AdminCategoryService { @Override public Response deleteCategory(DeleteCategoryReqVO deleteCategoryReqVO) { + List articleTagRelList = articleCategoryRelRepository.findByCategoryId((deleteCategoryReqVO.getId())); + if (!CollectionUtils.isEmpty(articleTagRelList)) { + throw new BizException(ResponseCodeEnum.CATEGORY_HAS_ARTICLE); + } return categoryRepository.findById(deleteCategoryReqVO.getId()) .map(tag -> { tag.setIsDeleted(true); @@ -84,4 +89,14 @@ public class AdminCategoryServiceImpl implements AdminCategoryService { } return Response.success(selectRspVOS); } + + @Override + public Response findCategoryById(Long id) { + Category category = categoryRepository.findById(id) + .orElseThrow(() -> new BizException(ResponseCodeEnum.CATEGORY_NOT_EXIST)); + return Response.success(FindCategoryByIdRspVO.builder() + .id(category.getId()) + .name(category.getName()) + .build()); + } } 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 index d7906ea..13b5a32 100644 --- 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 @@ -2,9 +2,12 @@ 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.ArticleTagRel; import com.hanserwei.common.domain.dataobject.Tag; +import com.hanserwei.common.domain.repository.ArticleTagRelRepository; import com.hanserwei.common.domain.repository.TagRepository; 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; @@ -21,6 +24,8 @@ public class AdminTagServiceImpl implements AdminTagService { @Resource private TagRepository tagRepository; + @Resource + private ArticleTagRelRepository articleTagRelRepository; /** @@ -78,6 +83,11 @@ public class AdminTagServiceImpl implements AdminTagService { @Override public Response deleteTag(DeleteTagReqVO deleteTagReqVO) { + // 检查是否有关联的文章 + List articleTagRelsList = articleTagRelRepository.findByTagId(deleteTagReqVO.getId()); + if (!articleTagRelsList.isEmpty()) { + throw new BizException(ResponseCodeEnum.TAG_HAS_ARTICLE); + } return tagRepository.findById(deleteTagReqVO.getId()) .map(tag -> { @@ -92,7 +102,7 @@ public class AdminTagServiceImpl implements AdminTagService { public Response> searchTag(SearchTagReqVO searchTagReqVO) { // 使用模糊查询获取标签列表 List tags = tagRepository.findByNameContaining(searchTagReqVO.getKey()); - + // 将标签转换为下拉列表格式 List vos = tags.stream() .map(tag -> SelectRspVO.builder() @@ -100,7 +110,19 @@ public class AdminTagServiceImpl implements AdminTagService { .value(tag.getId()) .build()) .toList(); - + + return Response.success(vos); + } + + @Override + public Response> findTagsByIds(FindTagsByIdsReqVO findTagsByIdsReqVO) { + List tags = tagRepository.queryAllByIdIn(findTagsByIdsReqVO.getTagIds()); + List vos = tags.stream() + .map(tag -> FindTagsByIdsRspVO.builder() + .id(tag.getId()) + .name(tag.getName()) + .build()) + .toList(); return Response.success(vos); } } diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Article.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Article.java new file mode 100644 index 0000000..45895d4 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Article.java @@ -0,0 +1,60 @@ +package com.hanserwei.common.domain.dataobject; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; +import org.hibernate.annotations.UpdateTimestamp; + +import java.io.Serial; +import java.io.Serializable; +import java.time.Instant; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "t_article", indexes = { + @Index(name = "idx_article_create_time", columnList = "create_time") +}) +@SQLRestriction("is_deleted = false") +@SQLDelete(sql = "update t_article set is_deleted = true where id = ?") +public class Article implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 120) + private String title; + + @Column(nullable = false, columnDefinition = "TEXT") + @Builder.Default + private String cover = ""; + + @Column(columnDefinition = "TEXT") + @Builder.Default + private String summary = ""; + + @Column(name = "read_num", nullable = false) + @Builder.Default + private Integer readNum = 1; + + @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/dataobject/ArticleCategoryRel.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/ArticleCategoryRel.java new file mode 100644 index 0000000..2c74cb4 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/ArticleCategoryRel.java @@ -0,0 +1,39 @@ +package com.hanserwei.common.domain.dataobject; + +import jakarta.persistence.*; +import lombok.*; + +import java.io.Serial; +import java.io.Serializable; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "t_article_category_rel", + indexes = { + @Index(name = "idx_rel_cat_category_id", columnList = "category_id") + }) +public class ArticleCategoryRel implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 文章ID (Unique) + */ + @Column(name = "article_id", nullable = false, unique = true) + private Long articleId; + + /** + * 分类ID + */ + @Column(name = "category_id", nullable = false) + private Long categoryId; +} \ No newline at end of file diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/ArticleContent.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/ArticleContent.java new file mode 100644 index 0000000..0c33fe7 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/ArticleContent.java @@ -0,0 +1,40 @@ +package com.hanserwei.common.domain.dataobject; + +import jakarta.persistence.*; +import lombok.*; + +import java.io.Serial; +import java.io.Serializable; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "t_article_content", indexes = { + @Index(name = "idx_article_content_article_id", columnList = "article_id") +}) +public class ArticleContent implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 关联 Article 的 ID + * 这里只存储 ID,不建立对象关联,以实现轻量级操作 + */ + @Column(name = "article_id", nullable = false) + private Long articleId; + + /** + * 正文内容 + * 映射为 TEXT 类型,支持大文本 + */ + @Column(name = "content", columnDefinition = "TEXT") + private String content; +} \ No newline at end of file diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/ArticleTagRel.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/ArticleTagRel.java new file mode 100644 index 0000000..a8e2236 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/ArticleTagRel.java @@ -0,0 +1,44 @@ +package com.hanserwei.common.domain.dataobject; + +import jakarta.persistence.*; +import lombok.*; + +import java.io.Serial; +import java.io.Serializable; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "t_article_tag_rel", + indexes = { + @Index(name = "idx_rel_tag_article_id", columnList = "article_id"), + @Index(name = "idx_rel_tag_tag_id", columnList = "tag_id") + }, + uniqueConstraints = { + // 对应数据库中的联合唯一约束 + @UniqueConstraint(name = "uk_article_tag_rel_unique_pair", columnNames = {"article_id", "tag_id"}) + }) +public class ArticleTagRel implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 文章ID + */ + @Column(name = "article_id", nullable = false) + private Long articleId; + + /** + * 标签ID + */ + @Column(name = "tag_id", nullable = false) + private Long tagId; +} \ No newline at end of file diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleCategoryRelRepository.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleCategoryRelRepository.java new file mode 100644 index 0000000..b35300c --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleCategoryRelRepository.java @@ -0,0 +1,20 @@ +package com.hanserwei.common.domain.repository; + +import com.hanserwei.common.domain.dataobject.ArticleCategoryRel; + +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 java.util.List; + +public interface ArticleCategoryRelRepository extends JpaRepository { + @Modifying(flushAutomatically = true, clearAutomatically = true) + @Query("delete from ArticleCategoryRel acr where acr.articleId = :articleId") + void deleteByArticleId(@Param("articleId") Long articleId); + + ArticleCategoryRel findByArticleId(Long articleId); + + List findByCategoryId(Long categoryId); +} diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleContentRepository.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleContentRepository.java new file mode 100644 index 0000000..d18af50 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleContentRepository.java @@ -0,0 +1,8 @@ +package com.hanserwei.common.domain.repository; + +import com.hanserwei.common.domain.dataobject.ArticleContent; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ArticleContentRepository extends JpaRepository { + ArticleContent findByArticleId(Long articleId); +} diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleRepository.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleRepository.java new file mode 100644 index 0000000..4761940 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleRepository.java @@ -0,0 +1,8 @@ +package com.hanserwei.common.domain.repository; + +import com.hanserwei.common.domain.dataobject.Article; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface ArticleRepository extends JpaRepository, JpaSpecificationExecutor
{ +} diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleTagRelRepository.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleTagRelRepository.java new file mode 100644 index 0000000..171ebbc --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/ArticleTagRelRepository.java @@ -0,0 +1,19 @@ +package com.hanserwei.common.domain.repository; + +import com.hanserwei.common.domain.dataobject.ArticleTagRel; +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 java.util.List; + +public interface ArticleTagRelRepository extends JpaRepository { + @Modifying(flushAutomatically = true, clearAutomatically = true) + @Query("delete from ArticleTagRel atr where atr.articleId = :articleId") + void deleteByArticleId(@Param("articleId") Long articleId); + + List findByArticleId(Long articleId); + + List findByTagId(Long tagId); +} 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 index cd25fb2..eb4fa8c 100644 --- 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 @@ -22,4 +22,5 @@ public interface TagRepository extends JpaRepository, JpaSpecificatio */ List findByNameContaining(String name); + List queryAllByIdIn(Collection ids); } 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 42d34da..6587739 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 @@ -21,7 +21,12 @@ public enum ResponseCodeEnum implements BaseExceptionInterface { CATEGORY_NAME_IS_EXISTED("20005", "该分类已存在,请勿重复添加!"), TAG_NOT_EXIST("20006", "标签不存在!"), CATEGORY_NOT_EXIST("20007", "分类不存在!"), - FILE_UPLOAD_FAILED("20008", "上传文件失败!"); + FILE_UPLOAD_FAILED("20008", "上传文件失败!"), + CATEGORY_NOT_EXISTED("20009", "提交的分类不存在!"), + TAG_NOT_EXISTED("20010", "标签ID不存在或已删除!"), + ARTICLE_NOT_EXIST("20011", "文章不存在!"), + TAG_HAS_ARTICLE("20012", "该标签有关联文章,无法删除!"), + CATEGORY_HAS_ARTICLE("20013","该分类下有关联文章,无法删除!" ); // 异常码 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 index dec8b5c..4e7cb38 100644 --- 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 @@ -25,20 +25,22 @@ public class PageHelper { /** * 执行带条件的分页查询 * - * @param repository JPA Repository - * @param pageQuery 分页查询参数 - * @param name 名称(用于模糊查询) - * @param startDate 开始日期 - * @param endDate 结束日期 - * @param converter DO 到 VO 的转换函数 - * @param 实体类型 - * @param 响应VO类型 + * @param repository JPA Repository + * @param pageQuery 分页查询参数 + * @param searchText 搜索文本(用于模糊查询) + * @param searchField 搜索字段名称(如 "name"、"title" 等) + * @param startDate 开始日期 + * @param endDate 结束日期 + * @param converter DO 到 VO 的转换函数 + * @param 实体类型 + * @param 响应VO类型 * @return 分页响应 */ public static PageResponse findPageList( JpaSpecificationExecutor repository, BasePageQuery pageQuery, - String name, + String searchText, + String searchField, LocalDate startDate, LocalDate endDate, Function converter) { @@ -54,10 +56,10 @@ public class PageHelper { Specification specification = (root, query, criteriaBuilder) -> { List predicates = new ArrayList<>(); - // 名称模糊查询 - if (StringUtils.hasText(name)) { + // 搜索文本模糊查询 + if (StringUtils.hasText(searchText) && StringUtils.hasText(searchField)) { predicates.add( - criteriaBuilder.like(root.get("name"), "%" + name.trim() + "%") + criteriaBuilder.like(root.get(searchField), "%" + searchText.trim() + "%") ); } @@ -88,4 +90,27 @@ public class PageHelper { return PageResponse.success(page, vos); } + + /** + * 执行带条件的分页查询(使用默认字段名 "name") + * + * @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) { + return findPageList(repository, pageQuery, name, "name", startDate, endDate, converter); + } }