实现敏感词检测后,失败发生邮箱
This commit is contained in:
181
schoolNewsServ/.bin/mysql/sensitiveData/writeWord.py
Normal file
181
schoolNewsServ/.bin/mysql/sensitiveData/writeWord.py
Normal 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()
|
||||
@@ -15,7 +15,7 @@ CREATE TABLE `tb_resource` (
|
||||
`view_count` INT(11) DEFAULT 0 COMMENT '浏览次数',
|
||||
`like_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_recommend` TINYINT(1) DEFAULT 0 COMMENT '是否推荐',
|
||||
`is_banner` TINYINT(1) DEFAULT 0 COMMENT '是否轮播',
|
||||
|
||||
@@ -3,8 +3,8 @@ use school_news;
|
||||
DROP TABLE IF EXISTS `tb_sensitive_word`;
|
||||
CREATE TABLE `tb_sensitive_word` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`word` varchar(255) NOT NULL COMMENT '敏感词',
|
||||
`type` VARCHAR(10) NOT NULL COMMENT '类型allow\deny'
|
||||
`word` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '敏感词',
|
||||
`type` varchar(10) NOT NULL COMMENT '类型:allow-允许词,deny-禁用词',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `word` (`word`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='敏感词表';
|
||||
UNIQUE KEY `uk_word` (`word`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='敏感词表';
|
||||
@@ -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()),
|
||||
('6', 'module_file', '文件管理', 'file', '文件管理模块', 'el-icon-folder', 6, 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
|
||||
@@ -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()),
|
||||
('16','perm_message_manage', '消息管理', 'message:manage', '消息管理权限(管理端)', '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
|
||||
@@ -88,28 +90,30 @@ INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, creat
|
||||
('17', 'superadmin', 'perm_crontab_execute', '1', now()),
|
||||
('18', 'superadmin', 'perm_message_manage', '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()),
|
||||
('21', 'admin', 'perm_news_manage', '1', now()),
|
||||
('22', 'admin', 'perm_news_article_add', '1', now()),
|
||||
('23', 'admin', 'perm_study_manage', '1', now()),
|
||||
('24', 'admin', 'perm_achievement_manage', '1', now()),
|
||||
('25', 'admin', 'perm_ai_manage', '1', now()),
|
||||
('26', 'admin', 'perm_usercenter_manage', '1', now()),
|
||||
('27', 'admin', 'perm_file_manage', '1', now()),
|
||||
('28', 'admin', 'perm_message_manage', '1', now()),
|
||||
('29', 'admin', 'perm_message_send', '1', now()),
|
||||
('29.1', 'admin', 'perm_message_view', '1', now()),
|
||||
('22', 'admin', 'perm_default', '1', now()),
|
||||
('23', 'admin', 'perm_news_manage', '1', now()),
|
||||
('24', 'admin', 'perm_news_article_add', '1', now()),
|
||||
('25', 'admin', 'perm_study_manage', '1', now()),
|
||||
('26', 'admin', 'perm_achievement_manage', '1', now()),
|
||||
('27', 'admin', 'perm_ai_manage', '1', now()),
|
||||
('28', 'admin', 'perm_usercenter_manage', '1', now()),
|
||||
('29', 'admin', 'perm_file_manage', '1', now()),
|
||||
('30', 'admin', 'perm_message_manage', '1', now()),
|
||||
('31', 'admin', 'perm_message_send', '1', now()),
|
||||
('32', 'admin', 'perm_message_view', '1', now()),
|
||||
('33', 'admin', 'perm_sensitive_manage', '1', now()),
|
||||
|
||||
-- 自由角色:拥有用户视图相关的所有权限(前台用户权限)
|
||||
('30', 'freedom', 'perm_default', '1', now()),
|
||||
('31', 'freedom', 'perm_news_article_add', '1', now()),
|
||||
('32', 'freedom', 'perm_ai_manage', '1', now()),
|
||||
('33', 'freedom', 'perm_usercenter_manage', '1', now()),
|
||||
('34', 'freedom', 'perm_file_manage', '1', now()),
|
||||
('35', 'freedom', 'perm_message_view', '1', now());
|
||||
('40', 'freedom', 'perm_default', '1', now()),
|
||||
('41', 'freedom', 'perm_news_article_add', '1', now()),
|
||||
('42', 'freedom', 'perm_ai_manage', '1', now()),
|
||||
('43', 'freedom', 'perm_usercenter_manage', '1', now()),
|
||||
('44', 'freedom', 'perm_file_manage', '1', now()),
|
||||
('45', 'freedom', 'perm_message_view', '1', now());
|
||||
|
||||
-- 插入前端菜单数据
|
||||
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),
|
||||
('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),
|
||||
('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),
|
||||
('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),
|
||||
@@ -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()),
|
||||
('215', 'perm_news_manage', 'menu_admin_tag', '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()),
|
||||
('220', 'perm_study_manage', 'menu_admin_task_manage', '1', now()),
|
||||
('221', 'perm_study_manage', 'menu_admin_study_records', '1', now()),
|
||||
|
||||
@@ -91,6 +91,11 @@
|
||||
<artifactId>message</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh</groupId>
|
||||
<artifactId>sensitive</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
@EnableScheduling
|
||||
@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.message.mapper"})
|
||||
"org.xyzh.message.mapper", "org.xyzh.sensitive.mapper"})
|
||||
public class App {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -186,6 +186,14 @@
|
||||
<AppenderRef ref="RollingFileError"/>
|
||||
<AppenderRef ref="DatabaseAppender"/>
|
||||
</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">
|
||||
<appender-ref ref="Console"/>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.xyzh.api.news.resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
|
||||
/**
|
||||
@@ -14,12 +16,21 @@ public interface ResourceAuditService {
|
||||
* @param text 待审核文本
|
||||
* @return ResultDomain<Boolean> 审核结果(true 表示通过)
|
||||
*/
|
||||
ResultDomain<Boolean> auditText(String text);
|
||||
ResultDomain<String> auditText(String text);
|
||||
|
||||
/**
|
||||
* 根据文件ID进行审核(fileId 对应 tb_sys_file.id)
|
||||
* @param fileId 文件ID
|
||||
* @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);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package org.xyzh.api.sensitive;
|
||||
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageRequest;
|
||||
import org.xyzh.common.dto.sensitive.TbSensitive;
|
||||
|
||||
public interface SensitiveService {
|
||||
|
||||
ResultDomain<TbSensitive> page(PageRequest<TbSensitive> pageRequest);
|
||||
|
||||
ResultDomain<Boolean> addSensitiveWord(TbSensitive sensitive);
|
||||
|
||||
ResultDomain<Boolean> changeSensitiveWordType(TbSensitive sensitive);
|
||||
|
||||
ResultDomain<Boolean> deleteSensitiveWord(TbSensitive sensitive);
|
||||
|
||||
ResultDomain<Boolean> judgeSensitive(String text);
|
||||
ResultDomain<String> judgeSensitive(String text);
|
||||
}
|
||||
|
||||
@@ -356,6 +356,38 @@ public class ResultDomain<T> implements Serializable{
|
||||
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 操作失败
|
||||
* @param code 状态码
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
<artifactId>api-usercenter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh</groupId>
|
||||
<artifactId>api-sensitive</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh</groupId>
|
||||
<artifactId>system</artifactId>
|
||||
|
||||
@@ -18,7 +18,6 @@ import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.news.mapper.BannerMapper;
|
||||
import org.xyzh.api.news.banner.BannerService;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.core.enums.ResourceType;
|
||||
import org.xyzh.system.utils.LoginUtil;
|
||||
|
||||
@@ -281,14 +281,17 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
TbResource resource = resourceVO.getResource();
|
||||
if(resource.getStatus()==1 && !resource.getIsAudited()){
|
||||
// 进行审核
|
||||
ResultDomain<Boolean> pass =auditService.auditText(resource.getContent());
|
||||
if(pass.isSuccess() && pass.getData()){
|
||||
ResultDomain<String> pass =auditService.auditText(resource.getTitle() + " "+resource.getContent());
|
||||
if(pass.isSuccess()){
|
||||
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) {
|
||||
List<TbResourceTag> resourceTagList = new ArrayList<>();
|
||||
@@ -360,10 +363,37 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
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);
|
||||
|
||||
// 如果当前状态是已发布(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();
|
||||
// tag先删后增
|
||||
TbResourceTag resourceTag = new TbResourceTag();
|
||||
@@ -376,6 +406,7 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
|
||||
resourceTagMapper.deleteByResourceId(resource.getResourceID());
|
||||
resourceTagMapper.batchInsertResourceTags(Arrays.asList(resourceTag));
|
||||
|
||||
// 更新时间
|
||||
resource.setUpdateTime(now);
|
||||
|
||||
@@ -388,7 +419,15 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
ResourceVO updatedResourceVO = new ResourceVO();
|
||||
updatedResourceVO.setResource(updated);
|
||||
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;
|
||||
} else {
|
||||
resultDomain.fail("更新资源失败");
|
||||
@@ -457,22 +496,29 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
resultDomain.fail("资源不存在");
|
||||
return resultDomain;
|
||||
}
|
||||
if (status == 1 && !resource.getIsAudited()) {
|
||||
ResultDomain<Boolean> pass = auditService.auditText(resource.getContent());
|
||||
if (pass.isSuccess() && pass.getData()) {
|
||||
// 更新状态
|
||||
resource.setStatus(status);
|
||||
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);
|
||||
} else {
|
||||
// 审核失败,标记状态为3(审核失败)
|
||||
resource.setStatus(3);
|
||||
// 审核失败,标记状态为4(审核失败)
|
||||
resource.setStatus(4);
|
||||
resource.setUpdateTime(new Date());
|
||||
resourceMapper.updateResource(resource);
|
||||
auditService.sendAuditResultMessage(resource.getCreator(), "文章"+resource.getTitle(), pass.getDataList());
|
||||
|
||||
resultDomain.fail("审核失败");
|
||||
return resultDomain;
|
||||
}
|
||||
}
|
||||
// 更新状态
|
||||
resource.setStatus(status);
|
||||
resource.setUpdateTime(new Date());
|
||||
|
||||
int result = resourceMapper.updateResource(resource);
|
||||
@@ -512,15 +558,15 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
}
|
||||
|
||||
if (!resource.getIsAudited()) {
|
||||
ResultDomain<Boolean> pass = auditService.auditText(resource.getContent());
|
||||
if (pass.isSuccess() && pass.getData()) {
|
||||
ResultDomain<String> pass = auditService.auditText(resource.getTitle()+" "+resource.getContent());
|
||||
if (pass.isSuccess()) {
|
||||
resource.setIsAudited(true);
|
||||
} else {
|
||||
// 审核失败,标记状态为3(审核失败)
|
||||
resource.setStatus(3);
|
||||
resource.setUpdateTime(new Date());
|
||||
resourceMapper.updateResource(resource);
|
||||
|
||||
auditService.sendAuditResultMessage(resource.getCreator(), "文章"+resource.getTitle(), pass.getDataList());
|
||||
resultDomain.fail("审核失败");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
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.util.StringUtils;
|
||||
import org.xyzh.api.news.resource.ResourceAuditService;
|
||||
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 资源审核服务实现类
|
||||
@@ -11,24 +21,185 @@ import org.xyzh.common.core.domain.ResultDomain;
|
||||
@Service
|
||||
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
|
||||
public ResultDomain<Boolean> auditText(String text) {
|
||||
ResultDomain<Boolean> result = new ResultDomain<Boolean>();
|
||||
public ResultDomain<String> auditText(String text) {
|
||||
ResultDomain<String> result = new ResultDomain<>();
|
||||
ResultDomain<String> sensitiveRet = sensitiveService.judgeSensitive(text);
|
||||
// TODO: 文本审核逻辑(敏感词、违规词等规则校验)
|
||||
// 示例:直接通过
|
||||
result.success("审核通过", Boolean.TRUE);
|
||||
if (sensitiveRet.isSuccess()) {
|
||||
result.success("审核通过", "");
|
||||
} else {
|
||||
result.fail("审核失败", sensitiveRet.getDataList());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<Boolean> auditByFileId(String fileId) {
|
||||
ResultDomain<Boolean> result = new ResultDomain<Boolean>();
|
||||
public ResultDomain<String> auditByFileId(String fileId) {
|
||||
ResultDomain<String> result = new ResultDomain<>();
|
||||
// TODO:
|
||||
// 1. 根据 tb_sys_file.id 查询文件信息
|
||||
// 2. 读取文件内容,提取文本
|
||||
// 3. 调用 auditText(text) 进行审核
|
||||
// 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;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package org.xyzh.sensitive.mapper;
|
||||
import java.util.List;
|
||||
|
||||
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 com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
@@ -14,5 +16,11 @@ public interface SensitiveMapper extends BaseMapper<TbSensitive> {
|
||||
|
||||
public Integer addSensitiveWord(TbSensitive tbSensitive);
|
||||
|
||||
public Integer changeWordType(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);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.xyzh.api.sensitive.SensitiveService;
|
||||
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.redis.service.RedisService;
|
||||
import org.xyzh.sensitive.constants.SensitiveRedisContants;
|
||||
@@ -19,12 +21,22 @@ public class SensitiveServiceImpl implements SensitiveService{
|
||||
@Autowired
|
||||
private SensitiveMapper sensitiveMapper;
|
||||
|
||||
@Autowired
|
||||
private SensitiveWordHelper sensitiveWordHelper;
|
||||
|
||||
@Autowired
|
||||
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
|
||||
public ResultDomain<Boolean> addSensitiveWord(TbSensitive tbSensitive) {
|
||||
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
||||
@@ -59,6 +71,44 @@ public class SensitiveServiceImpl implements SensitiveService{
|
||||
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
|
||||
public ResultDomain<Boolean> deleteSensitiveWord(TbSensitive tbSensitive) {
|
||||
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
||||
@@ -94,11 +144,15 @@ public class SensitiveServiceImpl implements SensitiveService{
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<Boolean> judgeSensitive(String text) {
|
||||
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
||||
|
||||
boolean containsSensitive = sensitiveWordHelper.contains(text);
|
||||
resultDomain.success("敏感词检测完成", containsSensitive);
|
||||
public ResultDomain<String> judgeSensitive(String text) {
|
||||
ResultDomain<String> resultDomain = new ResultDomain<>();
|
||||
List<String> sensitiveWords = SensitiveWordHelper.findAll(text);
|
||||
// boolean containsSensitive = sensitiveWordHelper.contains(text);
|
||||
if (sensitiveWords.size() > 0) {
|
||||
resultDomain.fail(text, sensitiveWords);
|
||||
}else {
|
||||
resultDomain.success("不包含敏感词", text);
|
||||
}
|
||||
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?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">
|
||||
<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"/>
|
||||
<result column="word" property="word"/>
|
||||
<result column="type" property="type"/>
|
||||
@@ -14,20 +14,65 @@
|
||||
<select id="selectAll" resultMap="BaseMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_sensitive
|
||||
FROM tb_sensitive_word
|
||||
</select>
|
||||
|
||||
<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})
|
||||
</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 FROM tb_sensitive
|
||||
DELETE FROM tb_sensitive_word
|
||||
WHERE word = #{word}
|
||||
<if test="type != null and type != ''">
|
||||
AND type = #{type}
|
||||
</if>
|
||||
</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>
|
||||
@@ -1,12 +0,0 @@
|
||||
生日快乐
|
||||
曹操
|
||||
幸运
|
||||
幸运儿
|
||||
17年前
|
||||
1条
|
||||
1梯两户
|
||||
1比1
|
||||
年检
|
||||
幸存
|
||||
恶搞
|
||||
游戏机
|
||||
@@ -1,12 +0,0 @@
|
||||
fuck
|
||||
duck
|
||||
shit
|
||||
chicken
|
||||
fowl
|
||||
sex
|
||||
sexy
|
||||
prostitute
|
||||
whore
|
||||
harlot
|
||||
hooker
|
||||
gender
|
||||
File diff suppressed because it is too large
Load Diff
@@ -429,6 +429,12 @@ public class SCCourseServiceImpl implements SCCourseService {
|
||||
public ResultDomain<TbCourse> updateCourseStatus(TbCourse course) {
|
||||
ResultDomain<TbCourse> resultDomain = new ResultDomain<>();
|
||||
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> notPassList = new ArrayList<>();
|
||||
@@ -441,39 +447,52 @@ public class SCCourseServiceImpl implements SCCourseService {
|
||||
ResultDomain<ResourceVO> resourceDomain = resourceService.getResourceById(node.getResourceID());
|
||||
if (resourceDomain.isSuccess()) {
|
||||
if (!resourceDomain.getData().getResource().getIsAudited()) {
|
||||
ResultDomain<Boolean> pass = auditService.auditText(resourceDomain.getData().getResource().getContent());
|
||||
if(!pass.isSuccess() || !pass.getData()){
|
||||
// 审核失败,标记课程状态为3(审核失败)
|
||||
course.setStatus(3);
|
||||
ResultDomain<String> pass = auditService.auditText(resourceDomain.getData().getResource().getTitle()+" "+resourceDomain.getData().getResource().getContent());
|
||||
if(!pass.isSuccess()){
|
||||
// 审核失败,标记课程状态为4(审核失败)
|
||||
course.setStatus(4);
|
||||
course.setUpdateTime(new Date());
|
||||
courseMapper.updateCourse(course);
|
||||
auditService.sendAuditResultMessage(node.getCreator(), "课程节点:"+node.getName(), pass.getDataList());
|
||||
|
||||
resultDomain.fail("课程节点:"+node.getName()+"审核未通过");
|
||||
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){
|
||||
ResultDomain<Boolean> pass = auditService.auditByFileId(node.getResourceID());
|
||||
if(!pass.isSuccess() || !pass.getData()){
|
||||
// 审核失败,标记课程状态为3(审核失败)
|
||||
course.setStatus(3);
|
||||
ResultDomain<String> pass = auditService.auditByFileId(node.getResourceID());
|
||||
if(!pass.isSuccess()){
|
||||
// 审核失败,标记课程状态为4(审核失败)
|
||||
course.setStatus(4);
|
||||
course.setUpdateTime(new Date());
|
||||
courseMapper.updateCourse(course);
|
||||
|
||||
auditService.sendAuditResultMessage(node.getCreator(), "课程节点:"+node.getName(), pass.getDataList());
|
||||
resultDomain.fail("课程节点:"+node.getName()+"审核未通过");
|
||||
return resultDomain;
|
||||
}
|
||||
}else{
|
||||
ResultDomain<Boolean> pass = auditService.auditText(node.getContent());
|
||||
if(!pass.isSuccess() || !pass.getData()){
|
||||
ResultDomain<String> pass = auditService.auditText(node.getContent());
|
||||
if(!pass.isSuccess()){
|
||||
// 审核失败,标记课程状态为3(审核失败)
|
||||
course.setStatus(3);
|
||||
course.setUpdateTime(new Date());
|
||||
courseMapper.updateCourse(course);
|
||||
auditService.sendAuditResultMessage(node.getCreator(), "课程节点:"+node.getName(), pass.getDataList());
|
||||
|
||||
resultDomain.fail("课程节点:"+node.getName()+"审核未通过");
|
||||
return resultDomain;
|
||||
}else{
|
||||
// 更新node审核状态
|
||||
node.setIsAudited(true);
|
||||
courseNodeMapper.updateCourseNode(node);
|
||||
}
|
||||
}
|
||||
node.setIsAudited(true);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?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">
|
||||
<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" />
|
||||
<result column="user_id" property="userID" />
|
||||
<result column="dept_id" property="deptID" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?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">
|
||||
<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" />
|
||||
<result column="user_id" property="userID" />
|
||||
<result column="avatar" property="avatar" />
|
||||
|
||||
46
schoolNewsWeb/src/apis/resource/sensitive.ts
Normal file
46
schoolNewsWeb/src/apis/resource/sensitive.ts
Normal 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;
|
||||
},
|
||||
}
|
||||
@@ -86,8 +86,12 @@ export enum ResourceStatus {
|
||||
PUBLISHED = 1,
|
||||
/** 下架 */
|
||||
OFFLINE = 2,
|
||||
/** 审核中 */
|
||||
REVIEWING = 3,
|
||||
/** 敏感词未通过 */
|
||||
SENSITIVE_FAILED = 4,
|
||||
/** 审核失败 */
|
||||
FAILED = 3
|
||||
FAILED = 5
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,12 +100,14 @@ export enum ResourceStatus {
|
||||
export enum CourseStatus {
|
||||
/** 未上线 */
|
||||
OFFLINE = 0,
|
||||
/** 已上线 */
|
||||
ONLINE = 1,
|
||||
/** 已下架 */
|
||||
/** 已发布 */
|
||||
PUBLISHED = 1,
|
||||
/** 下架 */
|
||||
DISABLED = 2,
|
||||
/** 审核失败 */
|
||||
FAILED = 3
|
||||
/** 审核中 */
|
||||
REVIEWING = 3,
|
||||
/** 敏感词未通过 */
|
||||
SENSITIVE_FAILED = 4
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -335,3 +335,12 @@ export interface ResourceStatistics {
|
||||
/** 总收藏数 */
|
||||
totalCollections?: number;
|
||||
}
|
||||
|
||||
|
||||
export interface SensitiveWord {
|
||||
id: number;
|
||||
/** 敏感词 */
|
||||
word: string;
|
||||
/** 敏感词类型 allow\deny*/
|
||||
type: string;
|
||||
}
|
||||
@@ -587,7 +587,7 @@ async function loadSelectOptions(reset = false) {
|
||||
// 加载资源列表
|
||||
result = await resourceApi.getResourcePage(
|
||||
selectPageParam.value,
|
||||
searchKeyword.value ? { keyword: searchKeyword.value } : undefined
|
||||
searchKeyword.value ? { title: searchKeyword.value } : undefined
|
||||
);
|
||||
} else if (currentBanner.value.linkType === 2) {
|
||||
// 加载课程列表
|
||||
|
||||
@@ -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>
|
||||
@@ -81,7 +81,7 @@ import { useRouter } from 'vue-router';
|
||||
import { resourceApi, resourceTagApi } from '@/apis/resource'
|
||||
import type { PageParam, ResourceSearchParams, Resource, Tag } from '@/types';
|
||||
import { ArticleShowView } from '@/views/public/article';
|
||||
import { ArticleStatus } from '@/types/enums';
|
||||
import { ResourceStatus } from '@/types/enums';
|
||||
|
||||
const router = useRouter();
|
||||
const searchKeyword = ref('');
|
||||
@@ -166,8 +166,8 @@ function editArticle(row: any) {
|
||||
|
||||
async function changeArticleStatus(row: Resource) {
|
||||
try {
|
||||
// status: 0-草稿, 1-已发布, 2-已下架
|
||||
if (row.status === ArticleStatus.DRAFT || row.status === ArticleStatus.OFFLINE) {
|
||||
// status: 0-草稿, 1-已发布, 2-已下架, 3-审核中, 4-敏感词未通过
|
||||
if (row.status === ResourceStatus.DRAFT || row.status === ResourceStatus.OFFLINE || row.status === ResourceStatus.SENSITIVE_FAILED) {
|
||||
// 草稿或下架状态 -> 发布
|
||||
const res = await resourceApi.publishResource(row.resourceID!);
|
||||
if (res.success) {
|
||||
@@ -176,7 +176,7 @@ async function changeArticleStatus(row: Resource) {
|
||||
} else {
|
||||
ElMessage.error('发布失败');
|
||||
}
|
||||
} else if (row.status === ArticleStatus.PUBLISHED) {
|
||||
} else if (row.status === ResourceStatus.PUBLISHED) {
|
||||
// 已发布状态 -> 下架
|
||||
const res = await resourceApi.unpublishResource(row.resourceID!);
|
||||
if (res.success) {
|
||||
@@ -206,40 +206,44 @@ function deleteArticle() {
|
||||
|
||||
function getStatusType(status: number) {
|
||||
const typeMap: Record<number, any> = {
|
||||
[ArticleStatus.DRAFT]: 'info',
|
||||
[ArticleStatus.PUBLISHED]: 'success',
|
||||
[ArticleStatus.OFFLINE]: 'warning',
|
||||
[ArticleStatus.FAILED]: 'danger'
|
||||
[ResourceStatus.DRAFT]: 'info',
|
||||
[ResourceStatus.PUBLISHED]: 'success',
|
||||
[ResourceStatus.OFFLINE]: 'warning',
|
||||
[ResourceStatus.REVIEWING]: 'primary',
|
||||
[ResourceStatus.SENSITIVE_FAILED]: 'danger'
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
}
|
||||
|
||||
function getStatusText(status: number) {
|
||||
const textMap: Record<number, string> = {
|
||||
[ArticleStatus.DRAFT]: '草稿',
|
||||
[ArticleStatus.PUBLISHED]: '已发布',
|
||||
[ArticleStatus.OFFLINE]: '已下架',
|
||||
[ArticleStatus.FAILED]: '审核失败'
|
||||
[ResourceStatus.DRAFT]: '草稿',
|
||||
[ResourceStatus.PUBLISHED]: '已发布',
|
||||
[ResourceStatus.OFFLINE]: '已下架',
|
||||
[ResourceStatus.REVIEWING]: '审核中',
|
||||
[ResourceStatus.SENSITIVE_FAILED]: '敏感词未通过'
|
||||
};
|
||||
return textMap[status] || '未知';
|
||||
}
|
||||
|
||||
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';
|
||||
} else if (status === ArticleStatus.PUBLISHED) {
|
||||
} else if (status === ResourceStatus.PUBLISHED) {
|
||||
return 'warning';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
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 '发布';
|
||||
} else if (status === ArticleStatus.PUBLISHED) {
|
||||
} else if (status === ResourceStatus.PUBLISHED) {
|
||||
return '下架';
|
||||
} else if (status === ResourceStatus.REVIEWING) {
|
||||
return '审核中';
|
||||
}
|
||||
return '操作';
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ async function handlePublish() {
|
||||
await formRef.value?.validate();
|
||||
publishing.value = true;
|
||||
|
||||
// 新建或“立即发布”时,明确标记为已发布
|
||||
// 新建或"立即发布"时,明确标记为已发布
|
||||
// 对新建文章:status 没有值,这里设为 1
|
||||
// 对草稿->发布:也会变成 1
|
||||
articleForm.value.resource.status = 1;
|
||||
@@ -233,18 +233,26 @@ async function handlePublish() {
|
||||
if (props.collectionItemId) {
|
||||
await handleConvertFromCollection();
|
||||
} 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) {
|
||||
const resourceID = result.data?.resource?.resourceID || '';
|
||||
ElMessage.success('发布成功');
|
||||
const resourceID = result.data?.resource?.resourceID || articleForm.value.resource.resourceID || '';
|
||||
ElMessage.success(isEdit.value ? '更新成功' : '发布成功');
|
||||
emit('publish-success', resourceID);
|
||||
} else {
|
||||
ElMessage.error(result.message || '发布失败');
|
||||
ElMessage.error(result.message || (isEdit.value ? '更新失败' : '发布失败'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发布失败:', error);
|
||||
ElMessage.error('发布失败');
|
||||
console.error(isEdit.value ? '更新失败:' : '发布失败:', error);
|
||||
ElMessage.error(isEdit.value ? '更新失败' : '发布失败');
|
||||
} finally {
|
||||
publishing.value = false;
|
||||
}
|
||||
@@ -287,13 +295,29 @@ async function handleSaveDraft() {
|
||||
savingDraft.value = true;
|
||||
|
||||
try {
|
||||
// TODO: 调用API保存草稿
|
||||
console.log('保存草稿:', articleForm);
|
||||
// 设置为草稿状态
|
||||
articleForm.value.resource.status = 0;
|
||||
|
||||
let result;
|
||||
if (isEdit.value) {
|
||||
// 编辑模式:调用更新接口
|
||||
result = await resourceApi.updateResource(articleForm.value);
|
||||
} else {
|
||||
// 新建模式:调用创建接口
|
||||
result = await resourceApi.createResource(articleForm.value);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
ElMessage.success('草稿已保存');
|
||||
emit('save-draft-success');
|
||||
if (result.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) {
|
||||
console.error('保存失败:', error);
|
||||
ElMessage.error('保存失败');
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
<select v-model="searchForm.status" class="form-select">
|
||||
<option :value="undefined">请选择状态</option>
|
||||
<option :value="0">未上线</option>
|
||||
<option :value="1">已上线</option>
|
||||
<option :value="2">已下架</option>
|
||||
<option :value="3">审核失败</option>
|
||||
<option :value="1">已发布</option>
|
||||
<option :value="2">下架</option>
|
||||
<option :value="3">审核中</option>
|
||||
<option :value="4">敏感词未通过</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
@@ -62,12 +63,13 @@
|
||||
<el-table-column prop="duration" label="时长(分钟)" width="120" />
|
||||
<el-table-column prop="learnCount" 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 }">
|
||||
<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 === 2" type="warning">已下架</el-tag>
|
||||
<el-tag v-else-if="row.status === 3" type="danger">审核失败</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 === 3" type="primary">审核中</el-tag>
|
||||
<el-tag v-else-if="row.status === 4" type="danger">敏感词未通过</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderNum" label="排序" width="80" />
|
||||
@@ -77,13 +79,13 @@
|
||||
编辑
|
||||
</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"
|
||||
size="small"
|
||||
link
|
||||
@click="handleUpdateStatus(row, 1)"
|
||||
>
|
||||
上线
|
||||
发布
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 1"
|
||||
|
||||
Reference in New Issue
Block a user