feat(article): 实现管理端文章模块的增删改查功能
- 新增文章表、内容表、标签关联及分类关联的数据库设计与实现 - 实现文章发布、删除、分页查询、详情查看及更新接口 - 文章发布时支持分类验证和标签新增与绑定 - 删除操作会级联删除文章关联的分类和标签关系 - 查询详情接口返回文章基本信息、正文内容、分类及标签列表 - 支持根据标签ID列表批量查询标签信息 - 管理分类接口新增根据ID查询分类详情功能 - 删除分类、标签时增加文章关联校验,防止误删 - 统一返回结构,异常时抛出业务异常,规范日志输出 - 统一使用JPA进行数据库操作,保障事务一致性 - 优化查询性能,添加必要的索引及外键约束 - 补充对应请求和响应的VO类,支持参数校验与业务传递
This commit is contained in:
1
.idea/data_source_mapping.xml
generated
1
.idea/data_source_mapping.xml
generated
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="DataSourcePerFileMappings">
|
<component name="DataSourcePerFileMappings">
|
||||||
|
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/bb8330e4-9a89-4978-ad63-ad6402096c16/console.sql" value="bb8330e4-9a89-4978-ad63-ad6402096c16" />
|
||||||
<file url="file://$PROJECT_DIR$/sql/createTable.sql" value="bb8330e4-9a89-4978-ad63-ad6402096c16" />
|
<file url="file://$PROJECT_DIR$/sql/createTable.sql" value="bb8330e4-9a89-4978-ad63-ad6402096c16" />
|
||||||
<file url="file://$PROJECT_DIR$/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Category.java" value="bb8330e4-9a89-4978-ad63-ad6402096c16" />
|
<file url="file://$PROJECT_DIR$/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/Category.java" value="bb8330e4-9a89-4978-ad63-ad6402096c16" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ CREATE TABLE t_tag
|
|||||||
-- 对应 MySQL 的 KEY `idx_create_time`
|
-- 对应 MySQL 的 KEY `idx_create_time`
|
||||||
CREATE INDEX idx_tag_create_time ON t_tag (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 TABLE t_tag IS '文章标签表';
|
||||||
COMMENT ON COLUMN t_tag.id IS '标签id';
|
COMMENT ON COLUMN t_tag.id IS '标签id';
|
||||||
COMMENT ON COLUMN t_tag.name IS '标签名称';
|
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.update_time IS '最后一次更新时间';
|
||||||
COMMENT ON COLUMN t_tag.is_deleted IS '逻辑删除标志位:FALSE:未删除 TRUE:已删除';
|
COMMENT ON COLUMN t_tag.is_deleted IS '逻辑删除标志位:FALSE:未删除 TRUE:已删除';
|
||||||
|
|
||||||
-- 4. 应用自动更新时间戳触发器 (体现 PostgreSQL 强大的过程语言优势)
|
-- 4. 应用自动更新时间戳触发器 (体现 PostgresSQL 强大的过程语言优势)
|
||||||
-- 前提:您之前已经执行过 CREATE FUNCTION set_update_time() ...
|
-- 前提:您之前已经执行过 CREATE FUNCTION set_update_time() ...
|
||||||
CREATE TRIGGER set_t_tag_update_time
|
CREATE TRIGGER set_t_tag_update_time
|
||||||
BEFORE UPDATE
|
BEFORE UPDATE
|
||||||
@@ -146,7 +146,7 @@ EXECUTE FUNCTION set_update_time();
|
|||||||
-- ====================================================================================================================
|
-- ====================================================================================================================
|
||||||
CREATE TABLE t_blog_settings
|
CREATE TABLE t_blog_settings
|
||||||
(
|
(
|
||||||
-- id: 使用 BIGSERIAL 自动管理序列
|
-- id: 使用 BIG SERIAL 自动管理序列
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
|
||||||
-- logo: 图片路径可能很长,使用 TEXT 替代 VARCHAR(120),无性能损耗
|
-- logo: 图片路径可能很长,使用 TEXT 替代 VARCHAR(120),无性能损耗
|
||||||
@@ -186,3 +186,137 @@ COMMENT ON COLUMN t_blog_settings.gitee_homepage IS 'Gitee 主页访问地址';
|
|||||||
COMMENT ON COLUMN t_blog_settings.zhihu_homepage IS '知乎主页访问地址';
|
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 '文章对应标签关联表';
|
||||||
|
-- ====================================================================================================================
|
||||||
|
-- ====================================================================================================================
|
||||||
@@ -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<FindArticlePageListRspVO> findArticlePageList(@RequestBody @Validated FindArticlePageListReqVO findArticlePageListReqVO) {
|
||||||
|
return articleService.findArticlePageList(findArticlePageListReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文章详情
|
||||||
|
*/
|
||||||
|
@PostMapping("/detail")
|
||||||
|
@ApiOperationLog(description = "查询文章详情")
|
||||||
|
public Response<FindArticleDetailRspVO> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
package com.hanserwei.admin.controller;
|
package com.hanserwei.admin.controller;
|
||||||
|
|
||||||
import com.hanserwei.admin.model.vo.category.AddCategoryReqVO;
|
import com.hanserwei.admin.model.vo.category.*;
|
||||||
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.admin.service.AdminCategoryService;
|
||||||
import com.hanserwei.common.aspect.ApiOperationLog;
|
import com.hanserwei.common.aspect.ApiOperationLog;
|
||||||
import com.hanserwei.common.model.vo.SelectRspVO;
|
import com.hanserwei.common.model.vo.SelectRspVO;
|
||||||
@@ -11,10 +8,7 @@ import com.hanserwei.common.utils.PageResponse;
|
|||||||
import com.hanserwei.common.utils.Response;
|
import com.hanserwei.common.utils.Response;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
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;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -64,4 +58,13 @@ public class AdminCategoryController {
|
|||||||
return adminCategoryService.findCategorySelectList();
|
return adminCategoryService.findCategorySelectList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分类ID查询
|
||||||
|
*/
|
||||||
|
@GetMapping("/category/find/{id}")
|
||||||
|
@ApiOperationLog(description = "根据分类ID查询")
|
||||||
|
public Response<FindCategoryByIdRspVO> findCategoryById(@PathVariable Long id) {
|
||||||
|
return adminCategoryService.findCategoryById(id);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.hanserwei.admin.controller;
|
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.model.vo.tag.*;
|
||||||
import com.hanserwei.admin.service.AdminTagService;
|
import com.hanserwei.admin.service.AdminTagService;
|
||||||
import com.hanserwei.common.aspect.ApiOperationLog;
|
import com.hanserwei.common.aspect.ApiOperationLog;
|
||||||
@@ -63,5 +61,13 @@ public class AdminTagController {
|
|||||||
return adminTagService.searchTag(searchTagReqVO);
|
return adminTagService.searchTag(searchTagReqVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID列表获取标签
|
||||||
|
*/
|
||||||
|
@PostMapping("/tag/list/ids")
|
||||||
|
@ApiOperationLog(description = "根据ID列表获取标签")
|
||||||
|
public Response<List<FindTagsByIdsRspVO>> findTagsByIds(@RequestBody @Validated FindTagsByIdsReqVO findTagsByIdsReqVO) {
|
||||||
|
return adminTagService.findTagsByIds(findTagsByIdsReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Long> tagIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 摘要
|
||||||
|
*/
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String> tags;
|
||||||
|
}
|
||||||
@@ -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<String> tags;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<Long> tagIds;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<FindArticlePageListRspVO> findArticlePageList(FindArticlePageListReqVO findArticlePageListReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文章详情
|
||||||
|
*
|
||||||
|
* @param findArticleDetailReqVO 查询文章详情参数
|
||||||
|
* @return 响应
|
||||||
|
*/
|
||||||
|
Response<FindArticleDetailRspVO> findArticleDetail(FindArticleDetailReqVO findArticleDetailReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新文章
|
||||||
|
*
|
||||||
|
* @param updateArticleReqVO 更新文章参数
|
||||||
|
* @return 响应
|
||||||
|
*/
|
||||||
|
Response<?> updateArticle(UpdateArticleReqVO updateArticleReqVO);
|
||||||
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
package com.hanserwei.admin.service;
|
package com.hanserwei.admin.service;
|
||||||
|
|
||||||
import com.hanserwei.admin.model.vo.category.AddCategoryReqVO;
|
import com.hanserwei.admin.model.vo.category.*;
|
||||||
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.model.vo.SelectRspVO;
|
||||||
import com.hanserwei.common.utils.PageResponse;
|
import com.hanserwei.common.utils.PageResponse;
|
||||||
import com.hanserwei.common.utils.Response;
|
import com.hanserwei.common.utils.Response;
|
||||||
@@ -42,4 +39,11 @@ public interface AdminCategoryService {
|
|||||||
*/
|
*/
|
||||||
Response<List<SelectRspVO>> findCategorySelectList();
|
Response<List<SelectRspVO>> findCategorySelectList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分类 ID 查询分类
|
||||||
|
*
|
||||||
|
* @param id 分类 ID
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
Response<FindCategoryByIdRspVO> findCategoryById(Long id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,4 +39,12 @@ public interface AdminTagService {
|
|||||||
* @return 响应结果
|
* @return 响应结果
|
||||||
*/
|
*/
|
||||||
Response<List<SelectRspVO>> searchTag(SearchTagReqVO searchTagReqVO);
|
Response<List<SelectRspVO>> searchTag(SearchTagReqVO searchTagReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 ID 列表获取标签
|
||||||
|
*
|
||||||
|
* @param findTagsByIdsReqVO 根据 ID 列表获取标签请求参数
|
||||||
|
* @return 响应结果
|
||||||
|
*/
|
||||||
|
Response<List<FindTagsByIdsRspVO>> findTagsByIds(FindTagsByIdsReqVO findTagsByIdsReqVO);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<String> 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<FindArticlePageListRspVO> 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<FindArticleDetailRspVO> 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<ArticleTagRel> articleTagRelList = articleTagRelRepository.findByArticleId(id);
|
||||||
|
List<Long> 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<String> tags) {
|
||||||
|
if (CollectionUtils.isEmpty(tags)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String tagStr : tags) {
|
||||||
|
Long tagId;
|
||||||
|
|
||||||
|
// 判断是否为数字ID
|
||||||
|
try {
|
||||||
|
tagId = Long.parseLong(tagStr);
|
||||||
|
// 验证该ID的标签是否存在
|
||||||
|
Optional<Tag> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package com.hanserwei.admin.service.impl;
|
package com.hanserwei.admin.service.impl;
|
||||||
|
|
||||||
import com.hanserwei.admin.model.vo.category.AddCategoryReqVO;
|
import com.hanserwei.admin.model.vo.category.*;
|
||||||
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.admin.service.AdminCategoryService;
|
||||||
|
import com.hanserwei.common.domain.dataobject.ArticleCategoryRel;
|
||||||
import com.hanserwei.common.domain.dataobject.Category;
|
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.domain.repository.CategoryRepository;
|
||||||
import com.hanserwei.common.enums.ResponseCodeEnum;
|
import com.hanserwei.common.enums.ResponseCodeEnum;
|
||||||
import com.hanserwei.common.exception.BizException;
|
import com.hanserwei.common.exception.BizException;
|
||||||
@@ -26,6 +25,8 @@ public class AdminCategoryServiceImpl implements AdminCategoryService {
|
|||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private CategoryRepository categoryRepository;
|
private CategoryRepository categoryRepository;
|
||||||
|
@Resource
|
||||||
|
private ArticleCategoryRelRepository articleCategoryRelRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<?> addCategory(AddCategoryReqVO addCategoryReqVO) {
|
public Response<?> addCategory(AddCategoryReqVO addCategoryReqVO) {
|
||||||
@@ -60,6 +61,10 @@ public class AdminCategoryServiceImpl implements AdminCategoryService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<?> deleteCategory(DeleteCategoryReqVO deleteCategoryReqVO) {
|
public Response<?> deleteCategory(DeleteCategoryReqVO deleteCategoryReqVO) {
|
||||||
|
List<ArticleCategoryRel> articleTagRelList = articleCategoryRelRepository.findByCategoryId((deleteCategoryReqVO.getId()));
|
||||||
|
if (!CollectionUtils.isEmpty(articleTagRelList)) {
|
||||||
|
throw new BizException(ResponseCodeEnum.CATEGORY_HAS_ARTICLE);
|
||||||
|
}
|
||||||
return categoryRepository.findById(deleteCategoryReqVO.getId())
|
return categoryRepository.findById(deleteCategoryReqVO.getId())
|
||||||
.map(tag -> {
|
.map(tag -> {
|
||||||
tag.setIsDeleted(true);
|
tag.setIsDeleted(true);
|
||||||
@@ -84,4 +89,14 @@ public class AdminCategoryServiceImpl implements AdminCategoryService {
|
|||||||
}
|
}
|
||||||
return Response.success(selectRspVOS);
|
return Response.success(selectRspVOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<FindCategoryByIdRspVO> 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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package com.hanserwei.admin.service.impl;
|
|||||||
|
|
||||||
import com.hanserwei.admin.model.vo.tag.*;
|
import com.hanserwei.admin.model.vo.tag.*;
|
||||||
import com.hanserwei.admin.service.AdminTagService;
|
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.dataobject.Tag;
|
||||||
|
import com.hanserwei.common.domain.repository.ArticleTagRelRepository;
|
||||||
import com.hanserwei.common.domain.repository.TagRepository;
|
import com.hanserwei.common.domain.repository.TagRepository;
|
||||||
import com.hanserwei.common.enums.ResponseCodeEnum;
|
import com.hanserwei.common.enums.ResponseCodeEnum;
|
||||||
|
import com.hanserwei.common.exception.BizException;
|
||||||
import com.hanserwei.common.model.vo.SelectRspVO;
|
import com.hanserwei.common.model.vo.SelectRspVO;
|
||||||
import com.hanserwei.common.utils.PageHelper;
|
import com.hanserwei.common.utils.PageHelper;
|
||||||
import com.hanserwei.common.utils.PageResponse;
|
import com.hanserwei.common.utils.PageResponse;
|
||||||
@@ -21,6 +24,8 @@ public class AdminTagServiceImpl implements AdminTagService {
|
|||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private TagRepository tagRepository;
|
private TagRepository tagRepository;
|
||||||
|
@Resource
|
||||||
|
private ArticleTagRelRepository articleTagRelRepository;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,6 +83,11 @@ public class AdminTagServiceImpl implements AdminTagService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<?> deleteTag(DeleteTagReqVO deleteTagReqVO) {
|
public Response<?> deleteTag(DeleteTagReqVO deleteTagReqVO) {
|
||||||
|
// 检查是否有关联的文章
|
||||||
|
List<ArticleTagRel> articleTagRelsList = articleTagRelRepository.findByTagId(deleteTagReqVO.getId());
|
||||||
|
if (!articleTagRelsList.isEmpty()) {
|
||||||
|
throw new BizException(ResponseCodeEnum.TAG_HAS_ARTICLE);
|
||||||
|
}
|
||||||
|
|
||||||
return tagRepository.findById(deleteTagReqVO.getId())
|
return tagRepository.findById(deleteTagReqVO.getId())
|
||||||
.map(tag -> {
|
.map(tag -> {
|
||||||
@@ -103,4 +113,16 @@ public class AdminTagServiceImpl implements AdminTagService {
|
|||||||
|
|
||||||
return Response.success(vos);
|
return Response.success(vos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<List<FindTagsByIdsRspVO>> findTagsByIds(FindTagsByIdsReqVO findTagsByIdsReqVO) {
|
||||||
|
List<Tag> tags = tagRepository.queryAllByIdIn(findTagsByIdsReqVO.getTagIds());
|
||||||
|
List<FindTagsByIdsRspVO> vos = tags.stream()
|
||||||
|
.map(tag -> FindTagsByIdsRspVO.builder()
|
||||||
|
.id(tag.getId())
|
||||||
|
.name(tag.getName())
|
||||||
|
.build())
|
||||||
|
.toList();
|
||||||
|
return Response.success(vos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<ArticleCategoryRel, Long> {
|
||||||
|
@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<ArticleCategoryRel> findByCategoryId(Long categoryId);
|
||||||
|
}
|
||||||
@@ -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, Long> {
|
||||||
|
ArticleContent findByArticleId(Long articleId);
|
||||||
|
}
|
||||||
@@ -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<Article, Long>, JpaSpecificationExecutor<Article> {
|
||||||
|
}
|
||||||
@@ -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<ArticleTagRel, Long> {
|
||||||
|
@Modifying(flushAutomatically = true, clearAutomatically = true)
|
||||||
|
@Query("delete from ArticleTagRel atr where atr.articleId = :articleId")
|
||||||
|
void deleteByArticleId(@Param("articleId") Long articleId);
|
||||||
|
|
||||||
|
List<ArticleTagRel> findByArticleId(Long articleId);
|
||||||
|
|
||||||
|
List<ArticleTagRel> findByTagId(Long tagId);
|
||||||
|
}
|
||||||
@@ -22,4 +22,5 @@ public interface TagRepository extends JpaRepository<Tag, Long>, JpaSpecificatio
|
|||||||
*/
|
*/
|
||||||
List<Tag> findByNameContaining(String name);
|
List<Tag> findByNameContaining(String name);
|
||||||
|
|
||||||
|
List<Tag> queryAllByIdIn(Collection<Long> ids);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
|
|||||||
CATEGORY_NAME_IS_EXISTED("20005", "该分类已存在,请勿重复添加!"),
|
CATEGORY_NAME_IS_EXISTED("20005", "该分类已存在,请勿重复添加!"),
|
||||||
TAG_NOT_EXIST("20006", "标签不存在!"),
|
TAG_NOT_EXIST("20006", "标签不存在!"),
|
||||||
CATEGORY_NOT_EXIST("20007", "分类不存在!"),
|
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;
|
private final String errorCode;
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ public class PageHelper {
|
|||||||
*
|
*
|
||||||
* @param repository JPA Repository
|
* @param repository JPA Repository
|
||||||
* @param pageQuery 分页查询参数
|
* @param pageQuery 分页查询参数
|
||||||
* @param name 名称(用于模糊查询)
|
* @param searchText 搜索文本(用于模糊查询)
|
||||||
|
* @param searchField 搜索字段名称(如 "name"、"title" 等)
|
||||||
* @param startDate 开始日期
|
* @param startDate 开始日期
|
||||||
* @param endDate 结束日期
|
* @param endDate 结束日期
|
||||||
* @param converter DO 到 VO 的转换函数
|
* @param converter DO 到 VO 的转换函数
|
||||||
@@ -38,7 +39,8 @@ public class PageHelper {
|
|||||||
public static <T, R> PageResponse<R> findPageList(
|
public static <T, R> PageResponse<R> findPageList(
|
||||||
JpaSpecificationExecutor<T> repository,
|
JpaSpecificationExecutor<T> repository,
|
||||||
BasePageQuery pageQuery,
|
BasePageQuery pageQuery,
|
||||||
String name,
|
String searchText,
|
||||||
|
String searchField,
|
||||||
LocalDate startDate,
|
LocalDate startDate,
|
||||||
LocalDate endDate,
|
LocalDate endDate,
|
||||||
Function<T, R> converter) {
|
Function<T, R> converter) {
|
||||||
@@ -54,10 +56,10 @@ public class PageHelper {
|
|||||||
Specification<T> specification = (root, query, criteriaBuilder) -> {
|
Specification<T> specification = (root, query, criteriaBuilder) -> {
|
||||||
List<Predicate> predicates = new ArrayList<>();
|
List<Predicate> predicates = new ArrayList<>();
|
||||||
|
|
||||||
// 名称模糊查询
|
// 搜索文本模糊查询
|
||||||
if (StringUtils.hasText(name)) {
|
if (StringUtils.hasText(searchText) && StringUtils.hasText(searchField)) {
|
||||||
predicates.add(
|
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);
|
return PageResponse.success(page, vos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行带条件的分页查询(使用默认字段名 "name")
|
||||||
|
*
|
||||||
|
* @param repository JPA Repository
|
||||||
|
* @param pageQuery 分页查询参数
|
||||||
|
* @param name 名称(用于模糊查询)
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @param converter DO 到 VO 的转换函数
|
||||||
|
* @param <T> 实体类型
|
||||||
|
* @param <R> 响应VO类型
|
||||||
|
* @return 分页响应
|
||||||
|
*/
|
||||||
|
public static <T, R> PageResponse<R> findPageList(
|
||||||
|
JpaSpecificationExecutor<T> repository,
|
||||||
|
BasePageQuery pageQuery,
|
||||||
|
String name,
|
||||||
|
LocalDate startDate,
|
||||||
|
LocalDate endDate,
|
||||||
|
Function<T, R> converter) {
|
||||||
|
return findPageList(repository, pageQuery, name, "name", startDate, endDate, converter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user