实现敏感词检测后,失败发生邮箱

This commit is contained in:
2025-11-22 14:03:40 +08:00
parent c2cac51762
commit f3a9926caf
35 changed files with 1233 additions and 43916 deletions

View File

@@ -0,0 +1,181 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
敏感词批量导入脚本
从 sensitive_word_dict.txt 文件读取敏感词并批量插入到数据库
"""
import pymysql
import os
import sys
from datetime import datetime
# 数据库配置
DB_CONFIG = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': '123456',
'database': 'school_news',
'charset': 'utf8mb4'
}
def get_db_connection():
"""获取数据库连接"""
try:
connection = pymysql.connect(**DB_CONFIG)
return connection
except Exception as e:
print(f"数据库连接失败: {e}")
return None
def read_sensitive_words(file_path):
"""读取敏感词文件"""
words = []
try:
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
word = line.strip()
if word and len(word) > 0:
words.append(word)
print(f"成功读取 {len(words)} 个敏感词")
return words
except Exception as e:
print(f"读取敏感词文件失败: {e}")
return []
def batch_insert_words(connection, words, batch_size=1000):
"""批量插入敏感词到数据库"""
cursor = connection.cursor()
try:
# 清空现有的deny类型敏感词可选
print("清理现有的deny类型敏感词...")
cursor.execute("DELETE FROM tb_sensitive_word WHERE type = 'deny'")
# 准备批量插入SQL
insert_sql = "INSERT INTO tb_sensitive_word (word, type) VALUES (%s, %s)"
# 分批插入
total_words = len(words)
inserted_count = 0
for i in range(0, total_words, batch_size):
batch_words = words[i:i + batch_size]
batch_data = [(word, 'deny') for word in batch_words]
try:
cursor.executemany(insert_sql, batch_data)
connection.commit()
inserted_count += len(batch_data)
print(f"已插入 {inserted_count}/{total_words} 个敏感词")
except Exception as e:
print(f"批量插入失败: {e}")
connection.rollback()
break
print(f"批量插入完成,共插入 {inserted_count} 个敏感词")
return inserted_count
except Exception as e:
print(f"批量插入过程中发生错误: {e}")
connection.rollback()
return 0
finally:
cursor.close()
def check_duplicates(connection, words):
"""检查重复的敏感词"""
cursor = connection.cursor()
try:
# 查询已存在的敏感词
cursor.execute("SELECT word FROM tb_sensitive_word WHERE type = 'deny'")
existing_words = set(row[0] for row in cursor.fetchall())
# 过滤重复词
new_words = [word for word in words if word not in existing_words]
duplicate_count = len(words) - len(new_words)
if duplicate_count > 0:
print(f"发现 {duplicate_count} 个重复敏感词,将跳过")
return new_words
except Exception as e:
print(f"检查重复词时发生错误: {e}")
return words
finally:
cursor.close()
def main():
"""主函数"""
print("=" * 50)
print("敏感词批量导入工具")
print("=" * 50)
# 获取脚本所在目录
script_dir = os.path.dirname(os.path.abspath(__file__))
dict_file = os.path.join(script_dir, 'sensitive_word_dict.txt')
# 检查敏感词文件是否存在
if not os.path.exists(dict_file):
print(f"敏感词文件不存在: {dict_file}")
return
# 读取敏感词
print("正在读取敏感词文件...")
words = read_sensitive_words(dict_file)
if not words:
print("没有读取到有效的敏感词")
return
# 连接数据库
print("正在连接数据库...")
connection = get_db_connection()
if not connection:
print("数据库连接失败,程序退出")
return
try:
# 检查重复词(可选,如果不需要可以注释掉)
# print("正在检查重复敏感词...")
# words = check_duplicates(connection, words)
if not words:
print("所有敏感词都已存在,无需导入")
return
# 确认导入
print(f"准备导入 {len(words)} 个敏感词到数据库")
confirm = input("是否继续?(y/N): ").strip().lower()
if confirm != 'y':
print("用户取消导入")
return
# 批量插入
print("开始批量导入敏感词...")
start_time = datetime.now()
inserted_count = batch_insert_words(connection, words)
end_time = datetime.now()
duration = (end_time - start_time).total_seconds()
print("=" * 50)
print(f"导入完成!")
print(f"成功导入: {inserted_count} 个敏感词")
print(f"耗时: {duration:.2f}")
print("=" * 50)
except Exception as e:
print(f"程序执行过程中发生错误: {e}")
finally:
connection.close()
print("数据库连接已关闭")
if __name__ == "__main__":
main()

View File

@@ -15,7 +15,7 @@ CREATE TABLE `tb_resource` (
`view_count` INT(11) DEFAULT 0 COMMENT '浏览次数', `view_count` INT(11) DEFAULT 0 COMMENT '浏览次数',
`like_count` INT(11) DEFAULT 0 COMMENT '点赞次数', `like_count` INT(11) DEFAULT 0 COMMENT '点赞次数',
`collect_count` INT(11) DEFAULT 0 COMMENT '收藏次数', `collect_count` INT(11) DEFAULT 0 COMMENT '收藏次数',
`status` INT(4) DEFAULT 0 COMMENT '状态0草稿 1已发布 2下架', `status` INT(4) DEFAULT 0 COMMENT '状态0草稿 1已发布 2下架 3审核中 4敏感词未通过',
`is_audited` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否已审核', `is_audited` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否已审核',
`is_recommend` TINYINT(1) DEFAULT 0 COMMENT '是否推荐', `is_recommend` TINYINT(1) DEFAULT 0 COMMENT '是否推荐',
`is_banner` TINYINT(1) DEFAULT 0 COMMENT '是否轮播', `is_banner` TINYINT(1) DEFAULT 0 COMMENT '是否轮播',

View File

@@ -3,8 +3,8 @@ use school_news;
DROP TABLE IF EXISTS `tb_sensitive_word`; DROP TABLE IF EXISTS `tb_sensitive_word`;
CREATE TABLE `tb_sensitive_word` ( CREATE TABLE `tb_sensitive_word` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`word` varchar(255) NOT NULL COMMENT '敏感词', `word` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '敏感词',
`type` VARCHAR(10) NOT NULL COMMENT '类型allow\deny' `type` varchar(10) NOT NULL COMMENT '类型allow-允许词deny-禁用词',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `word` (`word`) UNIQUE KEY `uk_word` (`word`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='敏感词表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='敏感词表';

View File

@@ -41,7 +41,8 @@ INSERT INTO `tb_sys_module` (id, module_id, name, code, description, icon, order
('5', 'module_usercenter', '用户中心', 'usercenter', '用户中心模块', 'el-icon-user', 5, 1, '1', now()), ('5', 'module_usercenter', '用户中心', 'usercenter', '用户中心模块', 'el-icon-user', 5, 1, '1', now()),
('6', 'module_file', '文件管理', 'file', '文件管理模块', 'el-icon-folder', 6, 1, '1', now()), ('6', 'module_file', '文件管理', 'file', '文件管理模块', 'el-icon-folder', 6, 1, '1', now()),
('7', 'module_crontab', '定时任务', 'crontab', '定时任务管理模块', 'el-icon-alarm-clock', 7, 1, '1', now()), ('7', 'module_crontab', '定时任务', 'crontab', '定时任务管理模块', 'el-icon-alarm-clock', 7, 1, '1', now()),
('8', 'module_message', '消息通知', 'message', '消息通知管理模块', 'el-icon-message', 8, 1, '1', now()); ('8', 'module_message', '消息通知', 'message', '消息通知管理模块', 'el-icon-message', 8, 1, '1', now()),
('9', 'module_sensitive', '敏感词管理', 'sensitive', '敏感词管理模块', 'el-icon-warning', 9, 1, '1', now());
-- 插入权限数据 -- 插入权限数据
INSERT INTO `tb_sys_permission` (id,permission_id, name, code, description, module_id, creator, create_time) VALUES INSERT INTO `tb_sys_permission` (id,permission_id, name, code, description, module_id, creator, create_time) VALUES
@@ -64,7 +65,8 @@ INSERT INTO `tb_sys_permission` (id,permission_id, name, code, description, modu
('15','perm_crontab_execute', '定时任务执行', 'crontab:execute', '定时任务执行权限', 'module_crontab', '1', now()), ('15','perm_crontab_execute', '定时任务执行', 'crontab:execute', '定时任务执行权限', 'module_crontab', '1', now()),
('16','perm_message_manage', '消息管理', 'message:manage', '消息管理权限(管理端)', 'module_message', '1', now()), ('16','perm_message_manage', '消息管理', 'message:manage', '消息管理权限(管理端)', 'module_message', '1', now()),
('17','perm_message_send', '消息发送', 'message:send', '消息发送权限', 'module_message', '1', now()), ('17','perm_message_send', '消息发送', 'message:send', '消息发送权限', 'module_message', '1', now()),
('18','perm_message_view', '消息查看', 'message:view', '消息查看权限(用户端)', 'module_message', '1', now()); ('18','perm_message_view', '消息查看', 'message:view', '消息查看权限(用户端)', 'module_message', '1', now()),
('19','perm_sensitive_manage', '敏感词管理', 'sensitive:manage', '敏感词管理权限', 'module_sensitive', '1', now());
-- 插入角色-权限关联数据 -- 插入角色-权限关联数据
INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, create_time) VALUES INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, create_time) VALUES
@@ -88,28 +90,30 @@ INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, creat
('17', 'superadmin', 'perm_crontab_execute', '1', now()), ('17', 'superadmin', 'perm_crontab_execute', '1', now()),
('18', 'superadmin', 'perm_message_manage', '1', now()), ('18', 'superadmin', 'perm_message_manage', '1', now()),
('19', 'superadmin', 'perm_message_send', '1', now()), ('19', 'superadmin', 'perm_message_send', '1', now()),
('19.1', 'superadmin', 'perm_message_view', '1', now()), ('20', 'superadmin', 'perm_message_view', '1', now()),
('21', 'superadmin', 'perm_sensitive_manage', '1', now()),
-- 管理员:拥有业务管理权限,但没有系统日志等系统管理权限 -- 管理员:拥有业务管理权限,但没有系统日志等系统管理权限
('20', 'admin', 'perm_default', '1', now()), ('22', 'admin', 'perm_default', '1', now()),
('21', 'admin', 'perm_news_manage', '1', now()), ('23', 'admin', 'perm_news_manage', '1', now()),
('22', 'admin', 'perm_news_article_add', '1', now()), ('24', 'admin', 'perm_news_article_add', '1', now()),
('23', 'admin', 'perm_study_manage', '1', now()), ('25', 'admin', 'perm_study_manage', '1', now()),
('24', 'admin', 'perm_achievement_manage', '1', now()), ('26', 'admin', 'perm_achievement_manage', '1', now()),
('25', 'admin', 'perm_ai_manage', '1', now()), ('27', 'admin', 'perm_ai_manage', '1', now()),
('26', 'admin', 'perm_usercenter_manage', '1', now()), ('28', 'admin', 'perm_usercenter_manage', '1', now()),
('27', 'admin', 'perm_file_manage', '1', now()), ('29', 'admin', 'perm_file_manage', '1', now()),
('28', 'admin', 'perm_message_manage', '1', now()), ('30', 'admin', 'perm_message_manage', '1', now()),
('29', 'admin', 'perm_message_send', '1', now()), ('31', 'admin', 'perm_message_send', '1', now()),
('29.1', 'admin', 'perm_message_view', '1', now()), ('32', 'admin', 'perm_message_view', '1', now()),
('33', 'admin', 'perm_sensitive_manage', '1', now()),
-- 自由角色:拥有用户视图相关的所有权限(前台用户权限) -- 自由角色:拥有用户视图相关的所有权限(前台用户权限)
('30', 'freedom', 'perm_default', '1', now()), ('40', 'freedom', 'perm_default', '1', now()),
('31', 'freedom', 'perm_news_article_add', '1', now()), ('41', 'freedom', 'perm_news_article_add', '1', now()),
('32', 'freedom', 'perm_ai_manage', '1', now()), ('42', 'freedom', 'perm_ai_manage', '1', now()),
('33', 'freedom', 'perm_usercenter_manage', '1', now()), ('43', 'freedom', 'perm_usercenter_manage', '1', now()),
('34', 'freedom', 'perm_file_manage', '1', now()), ('44', 'freedom', 'perm_file_manage', '1', now()),
('35', 'freedom', 'perm_message_view', '1', now()); ('45', 'freedom', 'perm_message_view', '1', now());
-- 插入前端菜单数据 -- 插入前端菜单数据
INSERT INTO `tb_sys_menu` VALUES INSERT INTO `tb_sys_menu` VALUES
@@ -149,6 +153,7 @@ INSERT INTO `tb_sys_menu` VALUES
('4001', 'menu_admin_banner', 'Banner管理', 'menu_admin_content_manage', '/admin/manage/content/banner', 'admin/manage/content/BannerManagementView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('4001', 'menu_admin_banner', 'Banner管理', 'menu_admin_content_manage', '/admin/manage/content/banner', 'admin/manage/content/BannerManagementView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
('4002', 'menu_admin_tag', '标签管理', 'menu_admin_content_manage', '/admin/manage/content/tag', 'admin/manage/content/TagManagementView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('4002', 'menu_admin_tag', '标签管理', 'menu_admin_content_manage', '/admin/manage/content/tag', 'admin/manage/content/TagManagementView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
('4003', 'menu_admin_column', '栏目管理', 'menu_admin_content_manage', '/admin/manage/content/column', 'admin/manage/content/ColumnManagementView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('4003', 'menu_admin_column', '栏目管理', 'menu_admin_content_manage', '/admin/manage/content/column', 'admin/manage/content/ColumnManagementView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
('4004', 'menu_admin_sensitive', '敏感词管理', 'menu_admin_content_manage', '/admin/manage/content/sensitive', 'admin/manage/content/SensitiveManagementView', NULL, 4, 0, 'SidebarLayout', '1', NULL, '2025-11-22 11:30:00', '2025-11-22 11:30:00', NULL, 0),
('5000', 'menu_admin_study_manage', '学习管理', NULL, '', '', 'admin/study.svg', 5, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:46', NULL, 0), ('5000', 'menu_admin_study_manage', '学习管理', NULL, '', '', 'admin/study.svg', 5, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:46', NULL, 0),
('5002', 'menu_admin_task_manage', '任务管理', 'menu_admin_study_manage', '/admin/manage/study/task-manage', 'admin/manage/study/TaskManageView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), ('5002', 'menu_admin_task_manage', '任务管理', 'menu_admin_study_manage', '/admin/manage/study/task-manage', 'admin/manage/study/TaskManageView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
('5003', 'menu_admin_study_records', '学习记录', 'menu_admin_study_manage', '/admin/manage/study/study-records', 'admin/manage/study/StudyRecordsView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), ('5003', 'menu_admin_study_records', '学习记录', 'menu_admin_study_manage', '/admin/manage/study/study-records', 'admin/manage/study/StudyRecordsView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
@@ -212,6 +217,7 @@ INSERT INTO `tb_sys_menu_permission` (id, permission_id, menu_id, creator, creat
('214', 'perm_news_manage', 'menu_admin_banner', '1', now()), ('214', 'perm_news_manage', 'menu_admin_banner', '1', now()),
('215', 'perm_news_manage', 'menu_admin_tag', '1', now()), ('215', 'perm_news_manage', 'menu_admin_tag', '1', now()),
('216', 'perm_news_manage', 'menu_admin_column', '1', now()), ('216', 'perm_news_manage', 'menu_admin_column', '1', now()),
('217', 'perm_sensitive_manage', 'menu_admin_sensitive', '1', now()),
('218', 'perm_study_manage', 'menu_admin_study_manage', '1', now()), ('218', 'perm_study_manage', 'menu_admin_study_manage', '1', now()),
('220', 'perm_study_manage', 'menu_admin_task_manage', '1', now()), ('220', 'perm_study_manage', 'menu_admin_task_manage', '1', now()),
('221', 'perm_study_manage', 'menu_admin_study_records', '1', now()), ('221', 'perm_study_manage', 'menu_admin_study_records', '1', now()),

View File

@@ -91,6 +91,11 @@
<artifactId>message</artifactId> <artifactId>message</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>sensitive</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -18,7 +18,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableScheduling @EnableScheduling
@MapperScan({"org.xyzh.system.mapper", "org.xyzh.file.mapper", "org.xyzh.news.mapper", "org.xyzh.study.mapper", @MapperScan({"org.xyzh.system.mapper", "org.xyzh.file.mapper", "org.xyzh.news.mapper", "org.xyzh.study.mapper",
"org.xyzh.usercenter.mapper", "org.xyzh.ai.mapper", "org.xyzh.achievement.mapper", "org.xyzh.crontab.mapper", "org.xyzh.usercenter.mapper", "org.xyzh.ai.mapper", "org.xyzh.achievement.mapper", "org.xyzh.crontab.mapper",
"org.xyzh.message.mapper"}) "org.xyzh.message.mapper", "org.xyzh.sensitive.mapper"})
public class App { public class App {
public static void main(String[] args) { public static void main(String[] args) {

View File

@@ -186,6 +186,14 @@
<AppenderRef ref="RollingFileError"/> <AppenderRef ref="RollingFileError"/>
<AppenderRef ref="DatabaseAppender"/> <AppenderRef ref="DatabaseAppender"/>
</Logger> </Logger>
<Logger name="org.xyzh.sensitive" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="Filelog"/>
<AppenderRef ref="RollingFileInfo"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
<AppenderRef ref="DatabaseAppender"/>
</Logger>
<root level="info"> <root level="info">
<appender-ref ref="Console"/> <appender-ref ref="Console"/>

View File

@@ -1,5 +1,7 @@
package org.xyzh.api.news.resource; package org.xyzh.api.news.resource;
import java.util.List;
import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.domain.ResultDomain;
/** /**
@@ -14,12 +16,21 @@ public interface ResourceAuditService {
* @param text 待审核文本 * @param text 待审核文本
* @return ResultDomain<Boolean> 审核结果true 表示通过) * @return ResultDomain<Boolean> 审核结果true 表示通过)
*/ */
ResultDomain<Boolean> auditText(String text); ResultDomain<String> auditText(String text);
/** /**
* 根据文件ID进行审核fileId 对应 tb_sys_file.id * 根据文件ID进行审核fileId 对应 tb_sys_file.id
* @param fileId 文件ID * @param fileId 文件ID
* @return ResultDomain<Boolean> 审核结果true 表示通过) * @return ResultDomain<Boolean> 审核结果true 表示通过)
*/ */
ResultDomain<Boolean> auditByFileId(String fileId); ResultDomain<String> auditByFileId(String fileId);
/**
* 发送审核结果消息
* @param receiver 接收者用户ID
* @param target 审核目标(文章、课程名称)
* @param sensitiveWords 检测到的敏感词列表
* @return ResultDomain<Boolean> 发送结果
*/
ResultDomain<Boolean> sendAuditResultMessage(String receiver, String target, List<String> sensitiveWords);
} }

View File

@@ -1,13 +1,18 @@
package org.xyzh.api.sensitive; package org.xyzh.api.sensitive;
import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sensitive.TbSensitive; import org.xyzh.common.dto.sensitive.TbSensitive;
public interface SensitiveService { public interface SensitiveService {
ResultDomain<TbSensitive> page(PageRequest<TbSensitive> pageRequest);
ResultDomain<Boolean> addSensitiveWord(TbSensitive sensitive); ResultDomain<Boolean> addSensitiveWord(TbSensitive sensitive);
ResultDomain<Boolean> changeSensitiveWordType(TbSensitive sensitive);
ResultDomain<Boolean> deleteSensitiveWord(TbSensitive sensitive); ResultDomain<Boolean> deleteSensitiveWord(TbSensitive sensitive);
ResultDomain<Boolean> judgeSensitive(String text); ResultDomain<String> judgeSensitive(String text);
} }

View File

@@ -356,6 +356,38 @@ public class ResultDomain<T> implements Serializable{
this.data = null; this.data = null;
} }
/**
* @description 操作失败
* @param message 返回消息
* @param data 返回数据
* @author yslg
* @since 2025-09-07
*/
public void fail(String message, T data) {
this.code = HttpStatus.BAD_REQUEST.value();
this.message = message;
this.success = false;
this.auth = true;
this.login = true;
this.data = data;
}
/**
* @description 操作失败
* @param message 返回消息
* @param dataList 返回数据列表
* @author yslg
* @since 2025-09-07
*/
public void fail(String message, List<T> dataList) {
this.code = HttpStatus.BAD_REQUEST.value();
this.message = message;
this.success = false;
this.auth = true;
this.login = true;
this.dataList = dataList;
}
/** /**
* @description 操作失败 * @description 操作失败
* @param code 状态码 * @param code 状态码

View File

@@ -29,6 +29,11 @@
<artifactId>api-usercenter</artifactId> <artifactId>api-usercenter</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>api-sensitive</artifactId>
<version>1.0.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.xyzh</groupId> <groupId>org.xyzh</groupId>
<artifactId>system</artifactId> <artifactId>system</artifactId>

View File

@@ -18,7 +18,6 @@ import org.xyzh.common.utils.IDUtils;
import org.xyzh.news.mapper.BannerMapper; import org.xyzh.news.mapper.BannerMapper;
import org.xyzh.api.news.banner.BannerService; import org.xyzh.api.news.banner.BannerService;
import org.xyzh.api.system.permission.ResourcePermissionService; import org.xyzh.api.system.permission.ResourcePermissionService;
import org.xyzh.common.dto.user.TbSysUser;
import org.xyzh.common.vo.UserDeptRoleVO; import org.xyzh.common.vo.UserDeptRoleVO;
import org.xyzh.common.core.enums.ResourceType; import org.xyzh.common.core.enums.ResourceType;
import org.xyzh.system.utils.LoginUtil; import org.xyzh.system.utils.LoginUtil;

View File

@@ -281,14 +281,17 @@ public class NCResourceServiceImpl implements ResourceService {
TbResource resource = resourceVO.getResource(); TbResource resource = resourceVO.getResource();
if(resource.getStatus()==1 && !resource.getIsAudited()){ if(resource.getStatus()==1 && !resource.getIsAudited()){
// 进行审核 // 进行审核
ResultDomain<Boolean> pass =auditService.auditText(resource.getContent()); ResultDomain<String> pass =auditService.auditText(resource.getTitle() + " "+resource.getContent());
if(pass.isSuccess() && pass.getData()){ if(pass.isSuccess()){
resource.setIsAudited(true); resource.setIsAudited(true);
}else {
auditService.sendAuditResultMessage(resource.getCreator(), "文章"+resource.getTitle(), pass.getDataList());
resource.setStatus(4);
} }
} }
// 插入数据库 // 插入数据库
int result = resourceMapper.insertResource(resourceVO.getResource()); int result = resourceMapper.insertResource(resource);
// 插入资源标签 // 插入资源标签
if (resourceVO.getTags() != null && resourceVO.getTags().size() > 0) { if (resourceVO.getTags() != null && resourceVO.getTags().size() > 0) {
List<TbResourceTag> resourceTagList = new ArrayList<>(); List<TbResourceTag> resourceTagList = new ArrayList<>();
@@ -360,10 +363,37 @@ public class NCResourceServiceImpl implements ResourceService {
return resultDomain; return resultDomain;
} }
resource.setIsAudited(existing.getIsAudited()); // 检查内容是否发生变化,如果变化则需要重新审核
if(!existing.getContent().equals(resource.getContent())){ boolean contentChanged = !existing.getContent().equals(resource.getContent());
boolean titleChanged = !existing.getTitle().equals(resource.getTitle());
// 如果内容或标题发生变化,需要重新审核
if (contentChanged || titleChanged) {
resource.setIsAudited(false); resource.setIsAudited(false);
// 如果当前状态是已发布(1),且要发布状态(1),需要先设置为审核中(3)再进行审核
if (resource.getStatus() != null && resource.getStatus() == 1) {
// 先设置为审核中状态
resource.setStatus(3);
// 进行敏感词审核
ResultDomain<String> auditResult = auditService.auditText(resource.getTitle() +" "+resource.getContent());
if (auditResult.isSuccess()) {
// 审核通过,设置为已发布
resource.setStatus(1);
resource.setIsAudited(true);
} else {
// 审核失败,设置为敏感词未通过
resource.setStatus(4);
resource.setIsAudited(false);
auditService.sendAuditResultMessage(existing.getCreator(), "文章:"+resource.getTitle(), auditResult.getDataList());
}
}
} else {
// 内容未变化,保持原有审核状态
resource.setIsAudited(existing.getIsAudited());
} }
Date now = new Date(); Date now = new Date();
// tag先删后增 // tag先删后增
TbResourceTag resourceTag = new TbResourceTag(); TbResourceTag resourceTag = new TbResourceTag();
@@ -376,6 +406,7 @@ public class NCResourceServiceImpl implements ResourceService {
resourceTagMapper.deleteByResourceId(resource.getResourceID()); resourceTagMapper.deleteByResourceId(resource.getResourceID());
resourceTagMapper.batchInsertResourceTags(Arrays.asList(resourceTag)); resourceTagMapper.batchInsertResourceTags(Arrays.asList(resourceTag));
// 更新时间 // 更新时间
resource.setUpdateTime(now); resource.setUpdateTime(now);
@@ -388,7 +419,15 @@ public class NCResourceServiceImpl implements ResourceService {
ResourceVO updatedResourceVO = new ResourceVO(); ResourceVO updatedResourceVO = new ResourceVO();
updatedResourceVO.setResource(updated); updatedResourceVO.setResource(updated);
updatedResourceVO.setTags(resourceVO.getTags()); updatedResourceVO.setTags(resourceVO.getTags());
resultDomain.success("更新资源成功", updatedResourceVO);
// 根据审核结果返回不同消息
if (updated.getStatus() == 4) {
resultDomain.success("更新成功,但内容包含敏感词,请修改后重新发布", updatedResourceVO);
} else if (updated.getStatus() == 3) {
resultDomain.success("更新成功,正在审核中", updatedResourceVO);
} else {
resultDomain.success("更新资源成功", updatedResourceVO);
}
return resultDomain; return resultDomain;
} else { } else {
resultDomain.fail("更新资源失败"); resultDomain.fail("更新资源失败");
@@ -457,22 +496,29 @@ public class NCResourceServiceImpl implements ResourceService {
resultDomain.fail("资源不存在"); resultDomain.fail("资源不存在");
return resultDomain; return resultDomain;
} }
if (status == 1 && !resource.getIsAudited()) { // 更新状态
ResultDomain<Boolean> pass = auditService.auditText(resource.getContent()); resource.setStatus(status);
if (pass.isSuccess() && pass.getData()) { if (status == 1) { // 发布状态下更新都有重新审核
// 审核中状态变更
TbResource resourceStautsChange = new TbResource();
resourceStautsChange.setResourceID(resourceID);
resourceStautsChange.setStatus(3);
resourceMapper.updateResource(resourceStautsChange);
ResultDomain<String> pass = auditService.auditText(resource.getTitle()+" "+resource.getContent());
if (pass.isSuccess()) {
resource.setIsAudited(true); resource.setIsAudited(true);
} else { } else {
// 审核失败,标记状态为3(审核失败) // 审核失败,标记状态为4(审核失败)
resource.setStatus(3); resource.setStatus(4);
resource.setUpdateTime(new Date()); resource.setUpdateTime(new Date());
resourceMapper.updateResource(resource); resourceMapper.updateResource(resource);
auditService.sendAuditResultMessage(resource.getCreator(), "文章"+resource.getTitle(), pass.getDataList());
resultDomain.fail("审核失败"); resultDomain.fail("审核失败");
return resultDomain; return resultDomain;
} }
} }
// 更新状态
resource.setStatus(status);
resource.setUpdateTime(new Date()); resource.setUpdateTime(new Date());
int result = resourceMapper.updateResource(resource); int result = resourceMapper.updateResource(resource);
@@ -512,15 +558,15 @@ public class NCResourceServiceImpl implements ResourceService {
} }
if (!resource.getIsAudited()) { if (!resource.getIsAudited()) {
ResultDomain<Boolean> pass = auditService.auditText(resource.getContent()); ResultDomain<String> pass = auditService.auditText(resource.getTitle()+" "+resource.getContent());
if (pass.isSuccess() && pass.getData()) { if (pass.isSuccess()) {
resource.setIsAudited(true); resource.setIsAudited(true);
} else { } else {
// 审核失败标记状态为3审核失败 // 审核失败标记状态为3审核失败
resource.setStatus(3); resource.setStatus(3);
resource.setUpdateTime(new Date()); resource.setUpdateTime(new Date());
resourceMapper.updateResource(resource); resourceMapper.updateResource(resource);
auditService.sendAuditResultMessage(resource.getCreator(), "文章"+resource.getTitle(), pass.getDataList());
resultDomain.fail("审核失败"); resultDomain.fail("审核失败");
return resultDomain; return resultDomain;
} }

View File

@@ -1,8 +1,18 @@
package org.xyzh.news.service.impl; package org.xyzh.news.service.impl;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.xyzh.api.news.resource.ResourceAuditService; import org.xyzh.api.news.resource.ResourceAuditService;
import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.dto.user.TbSysUser;
import org.xyzh.common.utils.EmailUtils;
import org.xyzh.api.sensitive.SensitiveService;
import org.xyzh.api.system.user.UserService;
/** /**
* @description 资源审核服务实现类 * @description 资源审核服务实现类
@@ -11,24 +21,185 @@ import org.xyzh.common.core.domain.ResultDomain;
@Service @Service
public class ResourceAuditServiceImpl implements ResourceAuditService { public class ResourceAuditServiceImpl implements ResourceAuditService {
private static final Logger logger = LoggerFactory.getLogger(ResourceAuditServiceImpl.class);
@Autowired
private SensitiveService sensitiveService;
@Autowired
private UserService userService;
@Autowired
private EmailUtils emailUtils;
@Override @Override
public ResultDomain<Boolean> auditText(String text) { public ResultDomain<String> auditText(String text) {
ResultDomain<Boolean> result = new ResultDomain<Boolean>(); ResultDomain<String> result = new ResultDomain<>();
ResultDomain<String> sensitiveRet = sensitiveService.judgeSensitive(text);
// TODO: 文本审核逻辑(敏感词、违规词等规则校验) // TODO: 文本审核逻辑(敏感词、违规词等规则校验)
// 示例:直接通过 // 示例:直接通过
result.success("审核通过", Boolean.TRUE); if (sensitiveRet.isSuccess()) {
result.success("审核通过", "");
} else {
result.fail("审核失败", sensitiveRet.getDataList());
}
return result; return result;
} }
@Override @Override
public ResultDomain<Boolean> auditByFileId(String fileId) { public ResultDomain<String> auditByFileId(String fileId) {
ResultDomain<Boolean> result = new ResultDomain<Boolean>(); ResultDomain<String> result = new ResultDomain<>();
// TODO: // TODO:
// 1. 根据 tb_sys_file.id 查询文件信息 // 1. 根据 tb_sys_file.id 查询文件信息
// 2. 读取文件内容,提取文本 // 2. 读取文件内容,提取文本
// 3. 调用 auditText(text) 进行审核 // 3. 调用 auditText(text) 进行审核
// 4. 审核通过后,更新对应业务记录(tb_resource / tb_data_collection_item / tb_course_node) 的 is_audited = 1 // 4. 审核通过后,更新对应业务记录(tb_resource / tb_data_collection_item / tb_course_node) 的 is_audited = 1
result.success("审核通过", Boolean.TRUE); String text = "";
ResultDomain<String> sensitiveRet = sensitiveService.judgeSensitive(text);
if (sensitiveRet.isSuccess()) {
result.success("审核通过", "");
} else {
result.fail("审核失败", sensitiveRet.getDataList());
}
return result; return result;
} }
@Override
public ResultDomain<Boolean> sendAuditResultMessage(String receiver, String target, List<String> sensitiveWords) {
ResultDomain<Boolean> result = new ResultDomain<>();
try {
// 参数验证
if (!StringUtils.hasText(receiver)) {
logger.warn("发送审核结果邮件失败接收者ID为空");
result.fail("接收者ID不能为空");
return result;
}
if (!StringUtils.hasText(target)) {
logger.warn("发送审核结果邮件失败:目标内容名称为空");
result.fail("目标内容名称不能为空");
return result;
}
// 获取用户信息
ResultDomain<TbSysUser> userDomain = userService.getUserById(receiver);
if (!userDomain.isSuccess() || userDomain.getData() == null) {
logger.warn("发送审核结果邮件失败用户不存在用户ID: {}", receiver);
result.fail("用户不存在");
return result;
}
TbSysUser user = userDomain.getData();
if (!StringUtils.hasText(user.getEmail())) {
logger.warn("发送审核结果邮件失败用户邮箱为空用户ID: {}", receiver);
result.fail("用户邮箱为空");
return result;
}
// 构建邮件内容
String emailBody = buildEmailBody(target, sensitiveWords);
String subject = "【校园新闻平台】审核结果通知";
// 发送HTML邮件
boolean sent = emailUtils.sendHtmlEmail(user.getEmail(), subject, emailBody);
if (sent) {
logger.info("审核结果邮件发送成功用户ID: {}, 邮箱: {}, 内容: {}", receiver, user.getEmail(), target);
result.success("发送消息成功", true);
} else {
logger.error("审核结果邮件发送失败用户ID: {}, 邮箱: {}, 内容: {}", receiver, user.getEmail(), target);
result.fail("邮件发送失败");
}
} catch (Exception e) {
logger.error("发送审核结果邮件异常用户ID: {}, 内容: {}, 错误: {}", receiver, target, e.getMessage(), e);
result.fail("发送消息失败:" + e.getMessage());
}
return result;
}
/**
* @description 构建审核失败邮件内容
* @param target 文章、课程名称
* @param sensitiveWords 敏感词列表
* @return 邮件正文内容
* @author yslg
* @since 2025-11-22
*/
private String buildEmailBody(String target, List<String> sensitiveWords) {
StringBuilder emailBody = new StringBuilder();
String currentTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date());
// 构建HTML格式的邮件内容
emailBody.append("<!DOCTYPE html>");
emailBody.append("<html><head><meta charset='UTF-8'>");
emailBody.append("<style>");
emailBody.append("body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }");
emailBody.append(".container { max-width: 600px; margin: 0 auto; padding: 20px; }");
emailBody.append(".header { background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px; }");
emailBody.append(".content { background-color: #fff; padding: 20px; border: 1px solid #dee2e6; border-radius: 8px; }");
emailBody.append(".alert { background-color: #f8d7da; color: #721c24; padding: 15px; border-radius: 4px; margin: 15px 0; }");
emailBody.append(".sensitive-words { background-color: #fff3cd; padding: 15px; border-radius: 4px; margin: 15px 0; }");
emailBody.append(".steps { background-color: #d1ecf1; padding: 15px; border-radius: 4px; margin: 15px 0; }");
emailBody.append(".footer { text-align: center; margin-top: 20px; color: #6c757d; font-size: 12px; }");
emailBody.append("ul { padding-left: 20px; }");
emailBody.append("li { margin: 5px 0; }");
emailBody.append("</style></head><body>");
emailBody.append("<div class='container'>");
// 头部
emailBody.append("<div class='header'>");
emailBody.append("<h2 style='margin: 0; color: #495057;'>📧 校园新闻平台 - 审核结果通知</h2>");
emailBody.append("</div>");
// 主要内容
emailBody.append("<div class='content'>");
emailBody.append("<p>尊敬的用户,您好!</p>");
emailBody.append("<div class='alert'>");
emailBody.append("<strong>⚠️ 审核未通过</strong><br>");
emailBody.append("您提交的内容「<strong>").append(target).append("</strong>」审核未通过。");
emailBody.append("</div>");
emailBody.append("<p><strong>审核失败原因:</strong>内容包含敏感词汇</p>");
// 敏感词列表
if (sensitiveWords != null && !sensitiveWords.isEmpty()) {
emailBody.append("<div class='sensitive-words'>");
emailBody.append("<strong>🔍 检测到的敏感词汇:</strong>");
emailBody.append("<ul>");
for (String word : sensitiveWords) {
emailBody.append("<li><code style='background-color: #f8f9fa; padding: 2px 4px; border-radius: 3px;'>")
.append(word).append("</code></li>");
}
emailBody.append("</ul>");
emailBody.append("</div>");
}
// 处理步骤
emailBody.append("<div class='steps'>");
emailBody.append("<strong>📋 请您按照以下步骤处理:</strong>");
emailBody.append("<ol>");
emailBody.append("<li>检查并修改上述敏感词汇</li>");
emailBody.append("<li>确保内容符合平台发布规范</li>");
emailBody.append("<li>重新提交内容进行审核</li>");
emailBody.append("</ol>");
emailBody.append("</div>");
emailBody.append("<p>如有疑问,请联系平台管理员。</p>");
emailBody.append("</div>");
// 页脚
emailBody.append("<div class='footer'>");
emailBody.append("<p>此邮件为系统自动发送,请勿回复。</p>");
emailBody.append("<p>校园新闻平台 | 发送时间:").append(currentTime).append("</p>");
emailBody.append("</div>");
emailBody.append("</div></body></html>");
return emailBody.toString();
}
} }

View File

@@ -0,0 +1,46 @@
package org.xyzh.sensitive.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.xyzh.api.sensitive.SensitiveService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sensitive.TbSensitive;
@RestController
@RequestMapping("/sensitive")
public class SensitiveController {
@Autowired
private SensitiveService sensitiveService;
@PostMapping("/page")
public ResultDomain<TbSensitive> page(@RequestBody PageRequest<TbSensitive> pageRequest){
return sensitiveService.page(pageRequest);
}
@PostMapping
public ResultDomain<Boolean> addSensitiveWord(@RequestBody TbSensitive tbSensitive){
return sensitiveService.addSensitiveWord(tbSensitive);
}
@DeleteMapping
public ResultDomain<Boolean> deleteSensitiveWord(@RequestBody TbSensitive tbSensitive){
return sensitiveService.deleteSensitiveWord(tbSensitive);
}
@PutMapping
public ResultDomain<Boolean> changeSensitiveWordType(@RequestBody TbSensitive tbSensitive){
return sensitiveService.changeSensitiveWordType(tbSensitive);
}
@PostMapping("/judge")
public ResultDomain<String> judgeSensitive(@RequestBody String text){
return sensitiveService.judgeSensitive(text);
}
}

View File

@@ -3,6 +3,8 @@ package org.xyzh.sensitive.mapper;
import java.util.List; import java.util.List;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.sensitive.TbSensitive; import org.xyzh.common.dto.sensitive.TbSensitive;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -14,5 +16,11 @@ public interface SensitiveMapper extends BaseMapper<TbSensitive> {
public Integer addSensitiveWord(TbSensitive tbSensitive); public Integer addSensitiveWord(TbSensitive tbSensitive);
public Integer changeWordType(TbSensitive tbSensitive);
public Integer deleteSensitiveWord(TbSensitive tbSensitive); public Integer deleteSensitiveWord(TbSensitive tbSensitive);
public List<TbSensitive> selectTbSensitivePage(@Param("filter") TbSensitive filter, @Param("pageParam") PageParam pageParam);
public Integer countByFilter(@Param("filter") TbSensitive filter);
} }

View File

@@ -4,6 +4,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.xyzh.api.sensitive.SensitiveService; import org.xyzh.api.sensitive.SensitiveService;
import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sensitive.TbSensitive; import org.xyzh.common.dto.sensitive.TbSensitive;
import org.xyzh.common.redis.service.RedisService; import org.xyzh.common.redis.service.RedisService;
import org.xyzh.sensitive.constants.SensitiveRedisContants; import org.xyzh.sensitive.constants.SensitiveRedisContants;
@@ -19,12 +21,22 @@ public class SensitiveServiceImpl implements SensitiveService{
@Autowired @Autowired
private SensitiveMapper sensitiveMapper; private SensitiveMapper sensitiveMapper;
@Autowired
private SensitiveWordHelper sensitiveWordHelper;
@Autowired @Autowired
private RedisService redisService; private RedisService redisService;
@Override
public ResultDomain<TbSensitive> page(PageRequest<TbSensitive> pageRequest) {
ResultDomain<TbSensitive> resultDomain = new ResultDomain<>();
List<TbSensitive> tbSensitiveList = sensitiveMapper.selectTbSensitivePage(pageRequest.getFilter(), pageRequest.getPageParam());
int count = sensitiveMapper.countByFilter(pageRequest.getFilter());
PageDomain<TbSensitive> pageDomain = new PageDomain<>();
pageRequest.getPageParam().setTotalElements(count);
pageDomain.setPageParam(pageRequest.getPageParam());
pageDomain.setDataList(tbSensitiveList);
resultDomain.success("查询成功", pageDomain);
return resultDomain;
}
@Override @Override
public ResultDomain<Boolean> addSensitiveWord(TbSensitive tbSensitive) { public ResultDomain<Boolean> addSensitiveWord(TbSensitive tbSensitive) {
ResultDomain<Boolean> resultDomain = new ResultDomain<>(); ResultDomain<Boolean> resultDomain = new ResultDomain<>();
@@ -59,6 +71,44 @@ public class SensitiveServiceImpl implements SensitiveService{
return resultDomain; return resultDomain;
} }
@Override
public ResultDomain<Boolean> changeSensitiveWordType(TbSensitive tbSensitive) {
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
String word = tbSensitive.getWord();
String newType = tbSensitive.getType();
// 目标Redis键
String targetRedisKey = "allow".equals(newType) ? SensitiveRedisContants.SENSITIVE_WORD_ALLOW : SensitiveRedisContants.SENSITIVE_WORD_DENY;
// 检查目标类型中是否已存在该词
if (redisService.sMembers(targetRedisKey).contains(word)) {
resultDomain.fail("该敏感词在目标类型中已存在");
return resultDomain;
}
// 加锁
synchronized (this) {
// 再次检查
if (redisService.sMembers(targetRedisKey).contains(word)) {
resultDomain.fail("该敏感词在目标类型中已存在");
return resultDomain;
}
// 执行数据库更新
int i = sensitiveMapper.changeWordType(tbSensitive);
if (i > 0) {
// 同步到Redis从原类型中删除添加到新类型中
String oldRedisKey = "allow".equals(newType) ? SensitiveRedisContants.SENSITIVE_WORD_DENY : SensitiveRedisContants.SENSITIVE_WORD_ALLOW;
redisService.sRemove(oldRedisKey, word);
redisService.sAdd(targetRedisKey, word);
resultDomain.success("修改敏感词类型成功", true);
} else {
resultDomain.fail("修改敏感词类型失败");
}
}
return resultDomain;
}
@Override @Override
public ResultDomain<Boolean> deleteSensitiveWord(TbSensitive tbSensitive) { public ResultDomain<Boolean> deleteSensitiveWord(TbSensitive tbSensitive) {
ResultDomain<Boolean> resultDomain = new ResultDomain<>(); ResultDomain<Boolean> resultDomain = new ResultDomain<>();
@@ -94,11 +144,15 @@ public class SensitiveServiceImpl implements SensitiveService{
} }
@Override @Override
public ResultDomain<Boolean> judgeSensitive(String text) { public ResultDomain<String> judgeSensitive(String text) {
ResultDomain<Boolean> resultDomain = new ResultDomain<>(); ResultDomain<String> resultDomain = new ResultDomain<>();
List<String> sensitiveWords = SensitiveWordHelper.findAll(text);
boolean containsSensitive = sensitiveWordHelper.contains(text); // boolean containsSensitive = sensitiveWordHelper.contains(text);
resultDomain.success("敏感词检测完成", containsSensitive); if (sensitiveWords.size() > 0) {
resultDomain.fail(text, sensitiveWords);
}else {
resultDomain.success("不包含敏感词", text);
}
return resultDomain; return resultDomain;
} }

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.sensitive.mapper.SensitiveMapper"> <mapper namespace="org.xyzh.sensitive.mapper.SensitiveMapper">
<resultMap id="BaseMap" resultType="org.xyzh.common.dto.sensitive.TbSensitive"> <resultMap id="BaseMap" type="org.xyzh.common.dto.sensitive.TbSensitive">
<id column="id" property="id"/> <id column="id" property="id"/>
<result column="word" property="word"/> <result column="word" property="word"/>
<result column="type" property="type"/> <result column="type" property="type"/>
@@ -14,20 +14,65 @@
<select id="selectAll" resultMap="BaseMap"> <select id="selectAll" resultMap="BaseMap">
SELECT SELECT
<include refid="Base_Column_List"/> <include refid="Base_Column_List"/>
FROM tb_sensitive FROM tb_sensitive_word
</select> </select>
<insert id="addSensitiveWord" parameterType="org.xyzh.common.dto.sensitive.TbSensitive" useGeneratedKeys="true" keyProperty="id" keyColumn="id"> <insert id="addSensitiveWord" parameterType="org.xyzh.common.dto.sensitive.TbSensitive" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
INSERT INTO tb_sensitive (word, type) INSERT INTO tb_sensitive_word (word, type)
VALUES (#{word}, #{type}) VALUES (#{word}, #{type})
</insert> </insert>
<!-- updateSensitiveWord -->
<update id="changeWordType">
UPDATE tb_sensitive_word
SET type = #{type}
WHERE id = #{id}
</update>
<delete id="deleteSensitiveWord" parameterType="org.xyzh.common.dto.sensitive.TbSensitive"> <delete id="deleteSensitiveWord" parameterType="org.xyzh.common.dto.sensitive.TbSensitive">
DELETE FROM tb_sensitive DELETE FROM tb_sensitive_word
WHERE word = #{word} WHERE word = #{word}
<if test="type != null and type != ''"> <if test="type != null and type != ''">
AND type = #{type} AND type = #{type}
</if> </if>
</delete> </delete>
<!-- selectTbSensitivePage -->
<select id="selectTbSensitivePage" resultMap="BaseMap">
SELECT
<include refid="Base_Column_List"/>
FROM tb_sensitive_word
WHERE 1=1
<if test="filter.id != null and filter.id != ''">
AND id = #{filter.id}
</if>
<if test="filter.word != null and filter.word != ''">
AND word LIKE CONCAT('%', #{filter.word}, '%')
</if>
<if test="filter.type != null and filter.type != ''">
AND type = #{filter.type}
</if>
LIMIT #{pageParam.offset}, #{pageParam.pageSize}
</select>
<!-- countByFilter -->
<select id="countByFilter" resultType="int">
SELECT COUNT(*)
FROM tb_sensitive_word
WHERE 1=1
<if test="filter.id != null and filter.id != ''">
AND id = #{filter.id}
</if>
<if test="filter.word != null and filter.word != ''">
AND word LIKE CONCAT('%', #{filter.word}, '%')
</if>
<if test="filter.type != null and filter.type != ''">
AND type = #{filter.type}
</if>
</select>
</mapper> </mapper>

View File

@@ -1,12 +0,0 @@
生日快乐
曹操
幸运
幸运儿
17年前
1条
1梯两户
1比1
年检
幸存
恶搞
游戏机

View File

@@ -1,12 +0,0 @@
fuck
duck
shit
chicken
fowl
sex
sexy
prostitute
whore
harlot
hooker
gender

View File

@@ -429,6 +429,12 @@ public class SCCourseServiceImpl implements SCCourseService {
public ResultDomain<TbCourse> updateCourseStatus(TbCourse course) { public ResultDomain<TbCourse> updateCourseStatus(TbCourse course) {
ResultDomain<TbCourse> resultDomain = new ResultDomain<>(); ResultDomain<TbCourse> resultDomain = new ResultDomain<>();
if(course.getStatus()==1){ // 发布前审核 if(course.getStatus()==1){ // 发布前审核
// 更新为审核中
TbCourse course2 = new TbCourse();
course2.setCourseID(course.getCourseID());
course2.setStatus(3);
course2.setUpdateTime(new Date());
courseMapper.updateCourse(course2);
// 获取所有课程节点 // 获取所有课程节点
List<TbCourseNode> nodeList = courseNodeMapper.selectByCourseId(course.getCourseID()); List<TbCourseNode> nodeList = courseNodeMapper.selectByCourseId(course.getCourseID());
List<TbCourseNode> notPassList = new ArrayList<>(); List<TbCourseNode> notPassList = new ArrayList<>();
@@ -441,39 +447,52 @@ public class SCCourseServiceImpl implements SCCourseService {
ResultDomain<ResourceVO> resourceDomain = resourceService.getResourceById(node.getResourceID()); ResultDomain<ResourceVO> resourceDomain = resourceService.getResourceById(node.getResourceID());
if (resourceDomain.isSuccess()) { if (resourceDomain.isSuccess()) {
if (!resourceDomain.getData().getResource().getIsAudited()) { if (!resourceDomain.getData().getResource().getIsAudited()) {
ResultDomain<Boolean> pass = auditService.auditText(resourceDomain.getData().getResource().getContent()); ResultDomain<String> pass = auditService.auditText(resourceDomain.getData().getResource().getTitle()+" "+resourceDomain.getData().getResource().getContent());
if(!pass.isSuccess() || !pass.getData()){ if(!pass.isSuccess()){
// 审核失败,标记课程状态为3(审核失败) // 审核失败,标记课程状态为4(审核失败)
course.setStatus(3); course.setStatus(4);
course.setUpdateTime(new Date()); course.setUpdateTime(new Date());
courseMapper.updateCourse(course); courseMapper.updateCourse(course);
auditService.sendAuditResultMessage(node.getCreator(), "课程节点:"+node.getName(), pass.getDataList());
resultDomain.fail("课程节点:"+node.getName()+"审核未通过"); resultDomain.fail("课程节点:"+node.getName()+"审核未通过");
return resultDomain; return resultDomain;
}else{
// 更新文章的审核状态
ResourceVO resource = new ResourceVO();
resource.setResource(new TbResource());
resource.getResource().setResourceID(node.getResourceID());
resource.getResource().setIsAudited(true);
resourceService.updateResource(resource);
} }
} }
} }
}else if (type == 2){ }else if (type == 2){
ResultDomain<Boolean> pass = auditService.auditByFileId(node.getResourceID()); ResultDomain<String> pass = auditService.auditByFileId(node.getResourceID());
if(!pass.isSuccess() || !pass.getData()){ if(!pass.isSuccess()){
// 审核失败,标记课程状态为3(审核失败) // 审核失败,标记课程状态为4(审核失败)
course.setStatus(3); course.setStatus(4);
course.setUpdateTime(new Date()); course.setUpdateTime(new Date());
courseMapper.updateCourse(course); courseMapper.updateCourse(course);
auditService.sendAuditResultMessage(node.getCreator(), "课程节点:"+node.getName(), pass.getDataList());
resultDomain.fail("课程节点:"+node.getName()+"审核未通过"); resultDomain.fail("课程节点:"+node.getName()+"审核未通过");
return resultDomain; return resultDomain;
} }
}else{ }else{
ResultDomain<Boolean> pass = auditService.auditText(node.getContent()); ResultDomain<String> pass = auditService.auditText(node.getContent());
if(!pass.isSuccess() || !pass.getData()){ if(!pass.isSuccess()){
// 审核失败标记课程状态为3审核失败 // 审核失败标记课程状态为3审核失败
course.setStatus(3); course.setStatus(3);
course.setUpdateTime(new Date()); course.setUpdateTime(new Date());
courseMapper.updateCourse(course); courseMapper.updateCourse(course);
auditService.sendAuditResultMessage(node.getCreator(), "课程节点:"+node.getName(), pass.getDataList());
resultDomain.fail("课程节点:"+node.getName()+"审核未通过"); resultDomain.fail("课程节点:"+node.getName()+"审核未通过");
return resultDomain; return resultDomain;
}else{
// 更新node审核状态
node.setIsAudited(true);
courseNodeMapper.updateCourseNode(node);
} }
} }
node.setIsAudited(true); node.setIsAudited(true);

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.system.mapper.UserDeptRoleMapper"> <mapper namespace="org.xyzh.system.mapper.UserDeptRoleMapper">
<resultMap id="UserDeptRoleResultMap" type="TbSysUserDeptRole"> <resultMap id="UserDeptRoleResultMap" type="org.xyzh.common.dto.user.TbSysUserDeptRole">
<id column="id" property="id" /> <id column="id" property="id" />
<result column="user_id" property="userID" /> <result column="user_id" property="userID" />
<result column="dept_id" property="deptID" /> <result column="dept_id" property="deptID" />

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.system.mapper.UserInfoMapper"> <mapper namespace="org.xyzh.system.mapper.UserInfoMapper">
<resultMap id="BaseResultMap" type="TbSysUserInfo"> <resultMap id="BaseResultMap" type="org.xyzh.common.dto.user.TbSysUserInfo">
<id column="id" property="id" /> <id column="id" property="id" />
<result column="user_id" property="userID" /> <result column="user_id" property="userID" />
<result column="avatar" property="avatar" /> <result column="avatar" property="avatar" />

View File

@@ -0,0 +1,46 @@
import { api } from '@/apis';
import type { ResultDomain, SensitiveWord, PageParam } from '@/types';
export const sensitiveApi = {
/**
* 获取敏感词列表
* @returns Promise<ResultDomain<SensitiveWord>>
*/
async getSensitivePage(pageParam: PageParam, filter?: SensitiveWord): Promise<ResultDomain<SensitiveWord>> {
const response = await api.post<SensitiveWord>('/sensitive/page', {
pageParam,
filter,
});
return response.data;
},
/**
* 添加敏感词
* @param sensitiveWord 敏感词信息
* @returns Promise<ResultDomain<SensitiveWord>>
*/
async addSensitiveWord(sensitiveWord: SensitiveWord): Promise<ResultDomain<SensitiveWord>> {
const response = await api.post<SensitiveWord>('/sensitive', sensitiveWord);
return response.data;
},
/**
* 修改敏感词类型
* @param sensitiveWord 敏感词对象
* @returns Promise<ResultDomain<boolean>>
*/
async changeSensitiveWordType(sensitiveWord: SensitiveWord): Promise<ResultDomain<boolean>> {
const response = await api.put<boolean>(`/sensitive`, sensitiveWord);
return response.data;
},
/**
* 删除敏感词
* @param sensitiveWord 敏感词对象
* @returns Promise<ResultDomain<boolean>>
*/
async deleteSensitiveWord(sensitiveWord: SensitiveWord): Promise<ResultDomain<boolean>> {
const response = await api.delete<boolean>(`/sensitive`, { data: sensitiveWord });
return response.data;
},
}

View File

@@ -86,8 +86,12 @@ export enum ResourceStatus {
PUBLISHED = 1, PUBLISHED = 1,
/** 下架 */ /** 下架 */
OFFLINE = 2, OFFLINE = 2,
/** 审核中 */
REVIEWING = 3,
/** 敏感词未通过 */
SENSITIVE_FAILED = 4,
/** 审核失败 */ /** 审核失败 */
FAILED = 3 FAILED = 5
} }
/** /**
@@ -96,12 +100,14 @@ export enum ResourceStatus {
export enum CourseStatus { export enum CourseStatus {
/** 未上线 */ /** 未上线 */
OFFLINE = 0, OFFLINE = 0,
/** 已上线 */ /** 已发布 */
ONLINE = 1, PUBLISHED = 1,
/** 下架 */ /** 下架 */
DISABLED = 2, DISABLED = 2,
/** 审核失败 */ /** 审核 */
FAILED = 3 REVIEWING = 3,
/** 敏感词未通过 */
SENSITIVE_FAILED = 4
} }
/** /**

View File

@@ -335,3 +335,12 @@ export interface ResourceStatistics {
/** 总收藏数 */ /** 总收藏数 */
totalCollections?: number; totalCollections?: number;
} }
export interface SensitiveWord {
id: number;
/** 敏感词 */
word: string;
/** 敏感词类型 allow\deny*/
type: string;
}

View File

@@ -587,7 +587,7 @@ async function loadSelectOptions(reset = false) {
// 加载资源列表 // 加载资源列表
result = await resourceApi.getResourcePage( result = await resourceApi.getResourcePage(
selectPageParam.value, selectPageParam.value,
searchKeyword.value ? { keyword: searchKeyword.value } : undefined searchKeyword.value ? { title: searchKeyword.value } : undefined
); );
} else if (currentBanner.value.linkType === 2) { } else if (currentBanner.value.linkType === 2) {
// 加载课程列表 // 加载课程列表

View File

@@ -0,0 +1,377 @@
<template>
<div class="sensitive-management">
<div class="header">
<h2>敏感词管理</h2>
<el-button type="primary" @click="showAddDialog">
<el-icon><Plus /></el-icon>
添加敏感词
</el-button>
</div>
<!-- 搜索过滤区域 -->
<div class="filter-section">
<el-form :model="filterForm" inline>
<el-form-item label="敏感词">
<el-input
v-model="filterForm.word"
placeholder="请输入敏感词"
clearable
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="filterForm.type" class="sensitive-type-selector" placeholder="请选择类型" clearable>
<el-option label="禁用词" value="deny" />
<el-option label="允许词" value="allow" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 数据表格 -->
<div class="table-section">
<el-table
v-loading="loading"
:data="sensitiveList"
style="width: 100%"
border
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="word" label="敏感词" min-width="200" />
<el-table-column prop="type" label="类型" width="120">
<template #default="{ row }">
<el-tag :type="row.type === 'deny' ? 'danger' : 'success'">
{{ row.type === 'deny' ? '禁用词' : '允许词' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
size="small"
@click="handleChangeType(row)"
>
{{ row.type === 'deny' ? '改为允许' : '改为禁用' }}
</el-button>
<el-button
type="danger"
size="small"
@click="handleDelete(row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
class="pagination-container"
v-model:current-page="pageParam.pageNumber"
v-model:page-size="pageParam.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pageParam.totalElements || 0"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 添加敏感词对话框 -->
<el-dialog
v-model="addDialogVisible"
title="添加敏感词"
width="500px"
@close="resetAddForm"
>
<el-form
ref="addFormRef"
:model="addForm"
:rules="addFormRules"
label-width="80px"
>
<el-form-item label="敏感词" prop="word">
<el-input
v-model="addForm.word"
placeholder="请输入敏感词"
maxlength="50"
show-word-limit
/>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-radio-group v-model="addForm.type">
<el-radio label="deny">禁用词</el-radio>
<el-radio label="allow">允许词</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="addDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleAdd" :loading="addLoading">
确定
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from 'vue';
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
import { sensitiveApi } from '@/apis/resource/sensitive';
import type { SensitiveWord, PageParam } from '@/types';
const loading = ref(false);
const addLoading = ref(false);
const addDialogVisible = ref(false);
const addFormRef = ref<FormInstance>();
const pageParam = ref<PageParam>({
pageNumber: 1,
pageSize: 10,
totalElements: 0,
});
const sensitiveList = ref<SensitiveWord[]>([]);
// 过滤表单
const filterForm = reactive<Partial<SensitiveWord>>({
word: '',
type: '',
});
// 添加表单
const addForm = reactive<Partial<SensitiveWord>>({
word: '',
type: 'deny',
});
// 添加表单验证规则
const addFormRules: FormRules = {
word: [
{ required: true, message: '请输入敏感词', trigger: 'blur' },
{ min: 1, max: 50, message: '敏感词长度在 1 到 50 个字符', trigger: 'blur' },
],
type: [
{ required: true, message: '请选择类型', trigger: 'change' },
],
};
// 获取敏感词分页数据
async function getSensitivePage() {
try {
loading.value = true;
const filter: Partial<SensitiveWord> = {};
if (filterForm.word) filter.word = filterForm.word;
if (filterForm.type) filter.type = filterForm.type;
const res = await sensitiveApi.getSensitivePage(pageParam.value, filter as SensitiveWord);
if (res.success) {
pageParam.value.totalElements = res.pageDomain?.pageParam?.totalElements || 0;
sensitiveList.value = res.pageDomain?.dataList || [];
} else {
ElMessage.error(res.message || '获取敏感词列表失败');
}
} catch (error) {
console.error('获取敏感词列表失败:', error);
ElMessage.error('获取敏感词列表失败');
} finally {
loading.value = false;
}
}
// 搜索
function handleSearch() {
pageParam.value.pageNumber = 1;
getSensitivePage();
}
// 重置搜索
function handleReset() {
Object.assign(filterForm, {
word: '',
type: '',
});
pageParam.value.pageNumber = 1;
getSensitivePage();
}
// 分页大小改变
function handleSizeChange(size: number) {
pageParam.value.pageSize = size;
pageParam.value.pageNumber = 1;
getSensitivePage();
}
// 当前页改变
function handleCurrentChange(page: number) {
pageParam.value.pageNumber = page;
getSensitivePage();
}
// 显示添加对话框
function showAddDialog() {
addDialogVisible.value = true;
}
// 重置添加表单
function resetAddForm() {
addFormRef.value?.resetFields();
Object.assign(addForm, {
word: '',
type: 'deny',
});
}
// 添加敏感词
async function handleAdd() {
if (!addFormRef.value) return;
try {
const valid = await addFormRef.value.validate();
if (!valid) return;
addLoading.value = true;
const res = await sensitiveApi.addSensitiveWord(addForm as SensitiveWord);
if (res.success) {
ElMessage.success('添加敏感词成功');
addDialogVisible.value = false;
getSensitivePage();
} else {
ElMessage.error(res.message || '添加敏感词失败');
}
} catch (error) {
console.error('添加敏感词失败:', error);
ElMessage.error('添加敏感词失败');
} finally {
addLoading.value = false;
}
}
// 修改敏感词类型
async function handleChangeType(row: SensitiveWord) {
try {
const newType = row.type === 'deny' ? 'allow' : 'deny';
const typeText = newType === 'deny' ? '禁用词' : '允许词';
await ElMessageBox.confirm(
`确定要将敏感词 "${row.word}" 改为${typeText}吗?`,
'确认修改',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
);
const updateData = { ...row, type: newType };
const res = await sensitiveApi.changeSensitiveWordType(updateData);
if (res.success) {
ElMessage.success('修改敏感词类型成功');
getSensitivePage();
} else {
ElMessage.error(res.message || '修改敏感词类型失败');
}
} catch (error) {
if (error !== 'cancel') {
console.error('修改敏感词类型失败:', error);
ElMessage.error('修改敏感词类型失败');
}
}
}
// 删除敏感词
async function handleDelete(row: SensitiveWord) {
try {
await ElMessageBox.confirm(
`确定要删除敏感词 "${row.word}" 吗?`,
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
);
const res = await sensitiveApi.deleteSensitiveWord(row);
if (res.success) {
ElMessage.success('删除敏感词成功');
getSensitivePage();
} else {
ElMessage.error(res.message || '删除敏感词失败');
}
} catch (error) {
if (error !== 'cancel') {
console.error('删除敏感词失败:', error);
ElMessage.error('删除敏感词失败');
}
}
}
onMounted(() => {
getSensitivePage();
});
</script>
<style lang="scss" scoped>
.sensitive-management {
padding: 20px;
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h2 {
margin: 0;
color: #303133;
}
}
.filter-section {
background: #fff;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.sensitive-type-selector {
width: 100px;
}
}
.table-section {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.pagination-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
}
</style>

View File

@@ -81,7 +81,7 @@ import { useRouter } from 'vue-router';
import { resourceApi, resourceTagApi } from '@/apis/resource' import { resourceApi, resourceTagApi } from '@/apis/resource'
import type { PageParam, ResourceSearchParams, Resource, Tag } from '@/types'; import type { PageParam, ResourceSearchParams, Resource, Tag } from '@/types';
import { ArticleShowView } from '@/views/public/article'; import { ArticleShowView } from '@/views/public/article';
import { ArticleStatus } from '@/types/enums'; import { ResourceStatus } from '@/types/enums';
const router = useRouter(); const router = useRouter();
const searchKeyword = ref(''); const searchKeyword = ref('');
@@ -166,8 +166,8 @@ function editArticle(row: any) {
async function changeArticleStatus(row: Resource) { async function changeArticleStatus(row: Resource) {
try { try {
// status: 0-草稿, 1-已发布, 2-已下架 // status: 0-草稿, 1-已发布, 2-已下架, 3-审核中, 4-敏感词未通过
if (row.status === ArticleStatus.DRAFT || row.status === ArticleStatus.OFFLINE) { if (row.status === ResourceStatus.DRAFT || row.status === ResourceStatus.OFFLINE || row.status === ResourceStatus.SENSITIVE_FAILED) {
// 草稿或下架状态 -> 发布 // 草稿或下架状态 -> 发布
const res = await resourceApi.publishResource(row.resourceID!); const res = await resourceApi.publishResource(row.resourceID!);
if (res.success) { if (res.success) {
@@ -176,7 +176,7 @@ async function changeArticleStatus(row: Resource) {
} else { } else {
ElMessage.error('发布失败'); ElMessage.error('发布失败');
} }
} else if (row.status === ArticleStatus.PUBLISHED) { } else if (row.status === ResourceStatus.PUBLISHED) {
// 已发布状态 -> 下架 // 已发布状态 -> 下架
const res = await resourceApi.unpublishResource(row.resourceID!); const res = await resourceApi.unpublishResource(row.resourceID!);
if (res.success) { if (res.success) {
@@ -206,40 +206,44 @@ function deleteArticle() {
function getStatusType(status: number) { function getStatusType(status: number) {
const typeMap: Record<number, any> = { const typeMap: Record<number, any> = {
[ArticleStatus.DRAFT]: 'info', [ResourceStatus.DRAFT]: 'info',
[ArticleStatus.PUBLISHED]: 'success', [ResourceStatus.PUBLISHED]: 'success',
[ArticleStatus.OFFLINE]: 'warning', [ResourceStatus.OFFLINE]: 'warning',
[ArticleStatus.FAILED]: 'danger' [ResourceStatus.REVIEWING]: 'primary',
[ResourceStatus.SENSITIVE_FAILED]: 'danger'
}; };
return typeMap[status] || 'info'; return typeMap[status] || 'info';
} }
function getStatusText(status: number) { function getStatusText(status: number) {
const textMap: Record<number, string> = { const textMap: Record<number, string> = {
[ArticleStatus.DRAFT]: '草稿', [ResourceStatus.DRAFT]: '草稿',
[ArticleStatus.PUBLISHED]: '已发布', [ResourceStatus.PUBLISHED]: '已发布',
[ArticleStatus.OFFLINE]: '已下架', [ResourceStatus.OFFLINE]: '已下架',
[ArticleStatus.FAILED]: '审核失败' [ResourceStatus.REVIEWING]: '审核中',
[ResourceStatus.SENSITIVE_FAILED]: '敏感词未通过'
}; };
return textMap[status] || '未知'; return textMap[status] || '未知';
} }
function getActionButtonType(status: number) { function getActionButtonType(status: number) {
// 草稿下架状态显示主要按钮(发布), 已发布状态显示警告按钮(下架) // 草稿下架或敏感词未通过状态显示主要按钮(发布), 已发布状态显示警告按钮(下架)
if (status === ArticleStatus.DRAFT || status === ArticleStatus.OFFLINE || status === ArticleStatus.FAILED) { if (status === ResourceStatus.DRAFT || status === ResourceStatus.OFFLINE || status === ResourceStatus.SENSITIVE_FAILED) {
return 'primary'; return 'primary';
} else if (status === ArticleStatus.PUBLISHED) { } else if (status === ResourceStatus.PUBLISHED) {
return 'warning'; return 'warning';
} }
return ''; return '';
} }
function getActionButtonText(status: number) { function getActionButtonText(status: number) {
// 草稿下架状态显示"发布", 已发布状态显示"下架" // 草稿下架或敏感词未通过状态显示"发布", 已发布状态显示"下架", 审核中状态不可操作
if (status === ArticleStatus.DRAFT || status === ArticleStatus.OFFLINE || status === ArticleStatus.FAILED) { if (status === ResourceStatus.DRAFT || status === ResourceStatus.OFFLINE || status === ResourceStatus.SENSITIVE_FAILED) {
return '发布'; return '发布';
} else if (status === ArticleStatus.PUBLISHED) { } else if (status === ResourceStatus.PUBLISHED) {
return '下架'; return '下架';
} else if (status === ResourceStatus.REVIEWING) {
return '审核中';
} }
return '操作'; return '操作';
} }

View File

@@ -223,7 +223,7 @@ async function handlePublish() {
await formRef.value?.validate(); await formRef.value?.validate();
publishing.value = true; publishing.value = true;
// 新建或立即发布时,明确标记为已发布 // 新建或"立即发布"时,明确标记为已发布
// 对新建文章status 没有值,这里设为 1 // 对新建文章status 没有值,这里设为 1
// 对草稿->发布:也会变成 1 // 对草稿->发布:也会变成 1
articleForm.value.resource.status = 1; articleForm.value.resource.status = 1;
@@ -233,18 +233,26 @@ async function handlePublish() {
if (props.collectionItemId) { if (props.collectionItemId) {
await handleConvertFromCollection(); await handleConvertFromCollection();
} else { } else {
const result = await resourceApi.createResource(articleForm.value); let result;
if (isEdit.value) {
// 编辑模式:调用更新接口
result = await resourceApi.updateResource(articleForm.value);
} else {
// 新建模式:调用创建接口
result = await resourceApi.createResource(articleForm.value);
}
if (result.success) { if (result.success) {
const resourceID = result.data?.resource?.resourceID || ''; const resourceID = result.data?.resource?.resourceID || articleForm.value.resource.resourceID || '';
ElMessage.success('发布成功'); ElMessage.success(isEdit.value ? '更新成功' : '发布成功');
emit('publish-success', resourceID); emit('publish-success', resourceID);
} else { } else {
ElMessage.error(result.message || '发布失败'); ElMessage.error(result.message || (isEdit.value ? '更新失败' : '发布失败'));
} }
} }
} catch (error) { } catch (error) {
console.error('发布失败:', error); console.error(isEdit.value ? '更新失败:' : '发布失败:', error);
ElMessage.error('发布失败'); ElMessage.error(isEdit.value ? '更新失败' : '发布失败');
} finally { } finally {
publishing.value = false; publishing.value = false;
} }
@@ -287,13 +295,29 @@ async function handleSaveDraft() {
savingDraft.value = true; savingDraft.value = true;
try { try {
// TODO: 调用API保存草稿 // 设置为草稿状态
console.log('保存草稿:', articleForm); articleForm.value.resource.status = 0;
await new Promise(resolve => setTimeout(resolve, 1000)); let result;
if (isEdit.value) {
// 编辑模式:调用更新接口
result = await resourceApi.updateResource(articleForm.value);
} else {
// 新建模式:调用创建接口
result = await resourceApi.createResource(articleForm.value);
}
ElMessage.success('草稿已保存'); if (result.success) {
emit('save-draft-success'); // 如果是新建模式,需要更新为编辑模式
if (!isEdit.value && result.data?.resource?.resourceID) {
isEdit.value = true;
articleForm.value.resource.resourceID = result.data.resource.resourceID;
}
ElMessage.success('草稿已保存');
emit('save-draft-success');
} else {
ElMessage.error(result.message || '保存失败');
}
} catch (error) { } catch (error) {
console.error('保存失败:', error); console.error('保存失败:', error);
ElMessage.error('保存失败'); ElMessage.error('保存失败');

View File

@@ -17,9 +17,10 @@
<select v-model="searchForm.status" class="form-select"> <select v-model="searchForm.status" class="form-select">
<option :value="undefined">请选择状态</option> <option :value="undefined">请选择状态</option>
<option :value="0">未上线</option> <option :value="0">未上线</option>
<option :value="1">上线</option> <option :value="1">发布</option>
<option :value="2">下架</option> <option :value="2">下架</option>
<option :value="3">审核失败</option> <option :value="3">审核</option>
<option :value="4">敏感词未通过</option>
</select> </select>
</div> </div>
<div class="form-item"> <div class="form-item">
@@ -62,12 +63,13 @@
<el-table-column prop="duration" label="时长(分钟)" width="120" /> <el-table-column prop="duration" label="时长(分钟)" width="120" />
<el-table-column prop="learnCount" label="学习人数" width="100" /> <el-table-column prop="learnCount" label="学习人数" width="100" />
<el-table-column prop="viewCount" label="浏览次数" width="100" /> <el-table-column prop="viewCount" label="浏览次数" width="100" />
<el-table-column label="状态" width="100"> <el-table-column label="状态" width="120">
<template #default="{ row }"> <template #default="{ row }">
<el-tag v-if="row.status === 0" type="info">未上线</el-tag> <el-tag v-if="row.status === 0" type="info">未上线</el-tag>
<el-tag v-else-if="row.status === 1" type="success">上线</el-tag> <el-tag v-else-if="row.status === 1" type="success">发布</el-tag>
<el-tag v-else-if="row.status === 2" type="warning">下架</el-tag> <el-tag v-else-if="row.status === 2" type="warning">下架</el-tag>
<el-tag v-else-if="row.status === 3" type="danger">审核失败</el-tag> <el-tag v-else-if="row.status === 3" type="primary">审核</el-tag>
<el-tag v-else-if="row.status === 4" type="danger">敏感词未通过</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="orderNum" label="排序" width="80" /> <el-table-column prop="orderNum" label="排序" width="80" />
@@ -77,13 +79,13 @@
编辑 编辑
</el-button> </el-button>
<el-button <el-button
v-if="row.status === 0 || row.status === 2 || row.status === 3" v-if="row.status === 0 || row.status === 2 || row.status === 4"
type="success" type="success"
size="small" size="small"
link link
@click="handleUpdateStatus(row, 1)" @click="handleUpdateStatus(row, 1)"
> >
上线 发布
</el-button> </el-button>
<el-button <el-button
v-if="row.status === 1" v-if="row.status === 1"