修改cms_content表结构和查询逻辑以支持多个分类ID

1. 将category_id字段改为category_ids字符串字段,支持逗号分隔的多个分类ID
2. 修改CmsContent实体类,添加类型转换支持,可接收数组格式的categoryIds
3. 更新CmsContentMapper.xml,修改查询逻辑为AND条件,要求内容包含所有指定分类
4. 修改CmsContentDto,添加categoryIdList字段支持数组格式的分类ID查询
5. 更新create_tables.sql和alter_cms_content.sql文件以反映字段变更
6. 重新生成init_cms_category.sql文件,包含新的分类结构

此修改解决了内容多分类查询的问题,现在可以通过单个或多个分类ID进行查询,且要求返回的内容包含所有指定分类
This commit is contained in:
wangzhiwei 2026-01-28 10:57:28 +08:00
parent 3f59744aab
commit e5f01458a1
7 changed files with 153 additions and 96 deletions

View File

@ -149,7 +149,7 @@ CREATE TABLE `cms_content` (
`title` varchar(255) NOT NULL COMMENT '标题',
`subtitle` varchar(255) DEFAULT NULL COMMENT '副标题',
`content_type` tinyint(1) NOT NULL COMMENT '内容类型1文章2视频3图片',
`category_id` bigint(20) DEFAULT NULL COMMENT '分类ID',
`category_ids` varchar(255) DEFAULT NULL COMMENT '分类ID列表,逗号分隔',
`summary` varchar(500) DEFAULT NULL COMMENT '内容摘要',
`content` longtext DEFAULT NULL COMMENT '内容详情',
`cover_image` varchar(255) DEFAULT NULL COMMENT '封面图片',
@ -175,7 +175,6 @@ CREATE TABLE `cms_content` (
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
`delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 0 未删除1已删除',
PRIMARY KEY (`content_id`),
KEY `idx_category_id` (`category_id`),
KEY `idx_author_id` (`author_id`),
KEY `idx_audit_status` (`audit_status`),
KEY `idx_publish_status` (`publish_status`),

View File

@ -2,10 +2,12 @@ package com.kexue.skills.entity;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.io.Serializable;
import com.kexue.skills.entity.base.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -28,8 +30,8 @@ public class CmsContent extends BaseEntity implements Serializable {
@Schema(description ="是否是官方0否1是")
private Boolean isOfficial;
@Schema(description ="分类ID")
private Long categoryId;
@Schema(description ="分类ID列表,逗号分隔")
private String categoryIds;
@Schema(description ="图标")
private String icon;
@ -59,9 +61,6 @@ public class CmsContent extends BaseEntity implements Serializable {
@Schema(description ="内容类型1文章2视频3图片")
private Integer contentType;
@Schema(description ="父分类ID")
private Long parentCategoryId;
@Schema(description ="内容详情")
private String content;
@ -130,4 +129,24 @@ public class CmsContent extends BaseEntity implements Serializable {
@Schema(description ="副标题")
private String subtitle;
}
// 用于接收前端发送的分类ID数组
@JsonProperty("categoryIds")
public void setCategoryIdsFromArray(List<Long> categoryIdList) {
if (categoryIdList != null && !categoryIdList.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < categoryIdList.size(); i++) {
sb.append(categoryIdList.get(i));
if (i < categoryIdList.size() - 1) {
sb.append(",");
}
}
this.categoryIds = sb.toString();
}
}
// 用于接收前端发送的分类ID字符串
public void setCategoryIds(String categoryIds) {
this.categoryIds = categoryIds;
}
}

View File

@ -3,6 +3,8 @@ package com.kexue.skills.entity.dto;
import com.kexue.skills.entity.base.BaseQueryDto;
import lombok.Data;
import java.util.List;
/**
* (CmsContent)查询DTO类
*
@ -18,9 +20,11 @@ public class CmsContentDto extends BaseQueryDto {
private Integer contentType;
private String categoryIds;
private Long categoryId;
private Long parentCategoryId;
private List<String> categoryIdList;
private Boolean isOfficial;
@ -38,4 +42,4 @@ public class CmsContentDto extends BaseQueryDto {
private Integer deleteFlag;
}
}

View File

@ -46,7 +46,7 @@ sa-token:
# 验证码配置
captcha:
# 是否启用验证码验证
enabled: false
enabled: true
# 验证码有效期(秒)
expire-time: 300
# 验证码长度

View File

@ -7,13 +7,7 @@
<result property="title" column="title" jdbcType="VARCHAR"/>
<result property="subtitle" column="subtitle" jdbcType="VARCHAR"/>
<result property="contentType" column="content_type" jdbcType="INTEGER"/>
<result property="categoryId" column="category_id" jdbcType="BIGINT"/>
<result property="parentCategoryId" column="parent_category_id" jdbcType="BIGINT"/>
<result property="isOfficial" column="is_official" jdbcType="BIT"/>
<result property="shareCount" column="share_count" jdbcType="INTEGER"/>
<result property="fileUrl" column="file_url" jdbcType="VARCHAR"/>
<result property="icon" column="icon" jdbcType="VARCHAR"/>
<result property="background" column="background" jdbcType="VARCHAR"/>
<result property="categoryIds" column="category_ids" jdbcType="VARCHAR"/>
<result property="summary" column="summary" jdbcType="VARCHAR"/>
<result property="content" column="content" jdbcType="LONGVARCHAR"/>
<result property="coverImage" column="cover_image" jdbcType="VARCHAR"/>
@ -29,19 +23,28 @@
<result property="likeCount" column="like_count" jdbcType="INTEGER"/>
<result property="commentCount" column="comment_count" jdbcType="INTEGER"/>
<result property="sort" column="sort" jdbcType="INTEGER"/>
<result property="isPaid" column="is_paid" jdbcType="BIT"/>
<result property="price" column="price" jdbcType="DECIMAL"/>
<result property="requiredPoints" column="required_points" jdbcType="INTEGER"/>
<result property="supportPointsPay" column="support_points_pay" jdbcType="BIT"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
<result property="createBy" column="create_by" jdbcType="VARCHAR"/>
<result property="updateBy" column="update_by" jdbcType="VARCHAR"/>
<result property="deleteFlag" column="delete_flag" jdbcType="INTEGER"/>
<result property="isOfficial" column="is_official" jdbcType="BIT"/>
<result property="shareCount" column="share_count" jdbcType="INTEGER"/>
<result property="fileUrl" column="file_url" jdbcType="VARCHAR"/>
<result property="icon" column="icon" jdbcType="VARCHAR"/>
<result property="background" column="background" jdbcType="VARCHAR"/>
</resultMap>
<!--查询单个-->
<select id="queryById" resultMap="CmsContentMap">
select
content_id, title, subtitle, content_type, category_id, parent_category_id, is_official, share_count, file_url, icon, background, summary, content, cover_image, author_id, author_name,
content_id, title, subtitle, content_type, category_ids, summary, content, cover_image, author_id, author_name,
reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time,
view_count, like_count, comment_count, sort, create_time, update_time, create_by, update_by, delete_flag
view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, create_time, update_time, create_by, update_by, delete_flag
from cms_content
where content_id = #{contentId}
</select>
@ -49,9 +52,9 @@
<!--查询分页列表-->
<select id="getPageList" resultMap="CmsContentMap">
select
content_id, title, subtitle, content_type, category_id, parent_category_id, is_official, share_count, file_url, icon, background, summary, content, cover_image, author_id, author_name,
content_id, title, subtitle, content_type, category_ids, summary, content, cover_image, author_id, author_name,
reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time,
view_count, like_count, comment_count, sort, create_time, update_time, create_by, update_by, delete_flag
view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, create_time, update_time, create_by, update_by, delete_flag
from cms_content
<where>
<if test="contentId != null">
@ -64,13 +67,17 @@
and content_type = #{contentType}
</if>
<if test="categoryId != null">
and category_id = #{categoryId}
and find_in_set(#{categoryId}, category_ids)
</if>
<if test="parentCategoryId != null">
and parent_category_id = #{parentCategoryId}
<if test="categoryIds != null and categoryIds != ''">
<foreach collection="categoryIds.split(',')" item="catId" separator="and" open="and " close="">
find_in_set(#{catId}, category_ids)
</foreach>
</if>
<if test="isOfficial != null">
and is_official = #{isOfficial}
<if test="categoryIdList != null and categoryIdList.size() > 0">
<foreach collection="categoryIdList" item="catId" separator="and" open="and " close="">
find_in_set(#{catId}, category_ids)
</foreach>
</if>
<if test="authorId != null">
and author_id = #{authorId}
@ -97,9 +104,9 @@
<!--查询列表-->
<select id="getList" resultMap="CmsContentMap">
select
content_id, title, subtitle, content_type, category_id, parent_category_id, is_official, share_count, file_url, icon, background, summary, content, cover_image, author_id, author_name,
content_id, title, subtitle, content_type, category_ids, summary, content, cover_image, author_id, author_name,
reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time,
view_count, like_count, comment_count, sort, create_time, update_time, create_by, update_by, delete_flag
view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, create_time, update_time, create_by, update_by, delete_flag
from cms_content
<where>
<if test="contentId != null">
@ -112,7 +119,17 @@
and content_type = #{contentType}
</if>
<if test="categoryId != null">
and category_id = #{categoryId}
and find_in_set(#{categoryId}, category_ids)
</if>
<if test="categoryIds != null and categoryIds != ''">
<foreach collection="categoryIds.split(',')" item="catId" separator="and" open="and " close="">
find_in_set(#{catId}, category_ids)
</foreach>
</if>
<if test="categoryIdList != null and categoryIdList.size() > 0">
<foreach collection="categoryIdList" item="catId" separator="and" open="and " close="">
find_in_set(#{catId}, category_ids)
</foreach>
</if>
<if test="authorId != null">
and author_id = #{authorId}
@ -132,12 +149,12 @@
<!--新增所有列-->
<insert id="insert" keyProperty="contentId" useGeneratedKeys="true">
insert into cms_content(title, subtitle, content_type, category_id, parent_category_id, is_official, share_count, file_url, icon, background, summary, content, cover_image, author_id, author_name,
insert into cms_content(title, subtitle, content_type, category_ids, summary, content, cover_image, author_id, author_name,
reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time,
view_count, like_count, comment_count, sort, create_time, update_time, create_by, update_by, delete_flag)
values (#{title}, #{subtitle}, #{contentType}, #{categoryId}, #{parentCategoryId}, #{isOfficial}, #{shareCount}, #{fileUrl}, #{icon}, #{background}, #{summary}, #{content}, #{coverImage}, #{authorId}, #{authorName},
view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, create_time, update_time, create_by, update_by, delete_flag)
values (#{title}, #{subtitle}, #{contentType}, #{categoryIds}, #{summary}, #{content}, #{coverImage}, #{authorId}, #{authorName},
#{reviewerId}, #{reviewerName}, #{auditStatus}, #{auditComment}, #{publishStatus}, #{publishTime},
#{viewCount}, #{likeCount}, #{commentCount}, #{sort}, #{createTime}, #{updateTime}, #{createBy}, #{updateBy}, #{deleteFlag})
#{viewCount}, #{likeCount}, #{commentCount}, #{sort}, #{isPaid}, #{price}, #{requiredPoints}, #{supportPointsPay}, #{isOfficial}, #{shareCount}, #{fileUrl}, #{icon}, #{background}, #{createTime}, #{updateTime}, #{createBy}, #{updateBy}, #{deleteFlag})
</insert>
<!--通过主键修改数据-->
@ -153,26 +170,8 @@
<if test="contentType != null">
content_type = #{contentType},
</if>
<if test="categoryId != null">
category_id = #{categoryId},
</if>
<if test="parentCategoryId != null">
parent_category_id = #{parentCategoryId},
</if>
<if test="isOfficial != null">
is_official = #{isOfficial},
</if>
<if test="shareCount != null">
share_count = #{shareCount},
</if>
<if test="fileUrl != null">
file_url = #{fileUrl},
</if>
<if test="icon != null">
icon = #{icon},
</if>
<if test="background != null">
background = #{background},
<if test="categoryIds != null and categoryIds != ''">
category_ids = #{categoryIds},
</if>
<if test="summary != null">
summary = #{summary},
@ -219,6 +218,33 @@
<if test="sort != null">
sort = #{sort},
</if>
<if test="isPaid != null">
is_paid = #{isPaid},
</if>
<if test="price != null">
price = #{price},
</if>
<if test="requiredPoints != null">
required_points = #{requiredPoints},
</if>
<if test="supportPointsPay != null">
support_points_pay = #{supportPointsPay},
</if>
<if test="isOfficial != null">
is_official = #{isOfficial},
</if>
<if test="shareCount != null">
share_count = #{shareCount},
</if>
<if test="fileUrl != null">
file_url = #{fileUrl},
</if>
<if test="icon != null">
icon = #{icon},
</if>
<if test="background != null">
background = #{background},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
@ -278,4 +304,4 @@
limit #{limit}
</select>
</mapper>
</mapper>

View File

@ -1,9 +1,14 @@
-- 修改cms_content表添加新字段
-- 修改cms_content表添加新字段并修改分类ID字段
ALTER TABLE cms_content
-- 修改分类ID字段为分类ID列表
MODIFY COLUMN category_id VARCHAR(255) COMMENT '分类ID列表逗号分隔',
-- 添加新字段
ADD COLUMN parent_category_id BIGINT COMMENT '父分类ID',
ADD COLUMN is_official BIT(1) DEFAULT 0 COMMENT '是否是官方0否1是',
ADD COLUMN share_count INT DEFAULT 0 COMMENT '分享数量',
ADD COLUMN file_url VARCHAR(255) COMMENT '文件URL',
ADD COLUMN icon VARCHAR(255) COMMENT '图标',
ADD COLUMN background VARCHAR(255) COMMENT '背景';
-- 重命名字段名
ALTER TABLE cms_content
CHANGE COLUMN category_id category_ids VARCHAR(255) COMMENT '分类ID列表逗号分隔';

View File

@ -1,47 +1,51 @@
-- AI相关分类初始化数据
-- CMS分类初始化数据
-- 先清空现有数据
TRUNCATE TABLE cms_category;
-- 一级分类
INSERT INTO cms_category (category_name, parent_id, level, sort, status, delete_flag, create_time, update_time, create_by, update_by) VALUES
('AI技术', NULL, 1, 1, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI应用', NULL, 1, 2, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI工具', NULL, 1, 3, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI伦理', NULL, 1, 4, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI新闻', NULL, 1, 5, 1, 0, NOW(), NOW(), 'admin', 'admin');
('官方', 0, 1, 1, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('coze', 0, 1, 2, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('用户发布', 0, 1, 3, 1, 0, NOW(), NOW(), 'admin', 'admin');
-- 二级分类 - AI技术
-- 二级分类 - 官方
INSERT INTO cms_category (category_name, parent_id, level, sort, status, delete_flag, create_time, update_time, create_by, update_by) VALUES
('机器学习', 1, 2, 1, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('深度学习', 1, 2, 2, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('自然语言处理', 1, 2, 3, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('计算机视觉', 1, 2, 4, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('语音识别', 1, 2, 5, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('强化学习', 1, 2, 6, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI大模型', 1, 2, 7, 1, 0, NOW(), NOW(), 'admin', 'admin');
('工具', 1, 2, 1, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('编程', 1, 2, 2, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('设计', 1, 2, 3, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('前端', 1, 2, 4, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('测试', 1, 2, 5, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI', 1, 2, 6, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('数据分析', 1, 2, 7, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('写作', 1, 2, 8, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('管理', 1, 2, 9, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('内容', 1, 2, 10, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('自动化', 1, 2, 11, 1, 0, NOW(), NOW(), 'admin', 'admin');
-- 二级分类 - AI应用
-- 二级分类 - coze
INSERT INTO cms_category (category_name, parent_id, level, sort, status, delete_flag, create_time, update_time, create_by, update_by) VALUES
('ChatGPT应用', 2, 2, 1, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI绘画', 2, 2, 2, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI写作', 2, 2, 3, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI编程', 2, 2, 4, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI教育', 2, 2, 5, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI医疗', 2, 2, 6, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI金融', 2, 2, 7, 1, 0, NOW(), NOW(), 'admin', 'admin');
('工具', 2, 2, 1, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('编程', 2, 2, 2, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('设计', 2, 2, 3, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('前端', 2, 2, 4, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('测试', 2, 2, 5, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI', 2, 2, 6, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('数据分析', 2, 2, 7, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('写作', 2, 2, 8, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('管理', 2, 2, 9, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('内容', 2, 2, 10, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('自动化', 2, 2, 11, 1, 0, NOW(), NOW(), 'admin', 'admin');
-- 二级分类 - AI工具
-- 二级分类 - 用户发布
INSERT INTO cms_category (category_name, parent_id, level, sort, status, delete_flag, create_time, update_time, create_by, update_by) VALUES
('AI生成工具', 3, 2, 1, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI数据分析工具', 3, 2, 2, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI开发框架', 3, 2, 3, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI部署工具', 3, 2, 4, 1, 0, NOW(), NOW(), 'admin', 'admin');
-- 二级分类 - AI伦理
INSERT INTO cms_category (category_name, parent_id, level, sort, status, delete_flag, create_time, update_time, create_by, update_by) VALUES
('AI隐私', 4, 2, 1, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI偏见', 4, 2, 2, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI安全', 4, 2, 3, 1, 0, NOW(), NOW(), 'admin', 'admin');
-- 二级分类 - AI新闻
INSERT INTO cms_category (category_name, parent_id, level, sort, status, delete_flag, create_time, update_time, create_by, update_by) VALUES
('AI行业动态', 5, 2, 1, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI企业新闻', 5, 2, 2, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI技术进展', 5, 2, 3, 1, 0, NOW(), NOW(), 'admin', 'admin');
('工具', 3, 2, 1, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('编程', 3, 2, 2, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('设计', 3, 2, 3, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('前端', 3, 2, 4, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('测试', 3, 2, 5, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('AI', 3, 2, 6, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('数据分析', 3, 2, 7, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('写作', 3, 2, 8, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('管理', 3, 2, 9, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('内容', 3, 2, 10, 1, 0, NOW(), NOW(), 'admin', 'admin'),
('自动化', 3, 2, 11, 1, 0, NOW(), NOW(), 'admin', 'admin');