mirror of
https://github.com/vastxie/99AI.git
synced 2026-04-24 19:24:25 +08:00
v4.3.0
This commit is contained in:
244
service/src/modules/badWords/badWords.service.ts
Normal file
244
service/src/modules/badWords/badWords.service.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
import { hideString } from '@/common/utils';
|
||||
import { HttpException, HttpStatus, Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import axios from 'axios';
|
||||
import { In, Like, Repository } from 'typeorm';
|
||||
import { GlobalConfigService } from '../globalConfig/globalConfig.service';
|
||||
import { UserEntity } from '../user/user.entity';
|
||||
import { BadWordsEntity } from './badWords.entity';
|
||||
import { AddBadWordDto } from './dto/addBadWords.dto';
|
||||
import { DelBadWordsDto } from './dto/delBadWords.dto';
|
||||
import { QueryBadWordsDto } from './dto/queryBadWords.dto';
|
||||
import { UpdateBadWordsDto } from './dto/updateBadWords.dto';
|
||||
import { ViolationLogEntity } from './violationLog.entity';
|
||||
|
||||
@Injectable()
|
||||
export class BadWordsService implements OnModuleInit {
|
||||
private badWords: string[];
|
||||
constructor(
|
||||
@InjectRepository(BadWordsEntity)
|
||||
private readonly badWordsEntity: Repository<BadWordsEntity>,
|
||||
@InjectRepository(ViolationLogEntity)
|
||||
private readonly violationLogEntity: Repository<ViolationLogEntity>,
|
||||
@InjectRepository(UserEntity)
|
||||
private readonly userEntity: Repository<UserEntity>,
|
||||
private readonly globalConfigService: GlobalConfigService,
|
||||
) {
|
||||
this.badWords = [];
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
this.loadBadWords();
|
||||
}
|
||||
|
||||
/* 敏感词匹配 */
|
||||
async customSensitiveWords(content, userId) {
|
||||
const triggeredWords = [];
|
||||
|
||||
// 遍历敏感词列表,查找内容中是否包含敏感词
|
||||
for (let i = 0; i < this.badWords.length; i++) {
|
||||
const word = this.badWords[i];
|
||||
if (content.includes(word)) {
|
||||
triggeredWords.push(word); // 如果包含敏感词,将其加入到数组中
|
||||
}
|
||||
}
|
||||
|
||||
if (triggeredWords.length) {
|
||||
// 如果找到敏感词,记录用户提交的违规内容
|
||||
await this.recordUserBadWords(userId, content, triggeredWords, ['自定义'], '自定义检测');
|
||||
}
|
||||
|
||||
// 返回检测到的敏感词列表(如果没有敏感词,返回空数组)
|
||||
return triggeredWords;
|
||||
}
|
||||
|
||||
/* 敏感词检测 先检测百度敏感词 后检测自定义的 */
|
||||
async checkBadWords(content: string, userId: number) {
|
||||
const config = await this.globalConfigService.getSensitiveConfig();
|
||||
/* 如果有则启动配置检测 没有则跳过 */
|
||||
if (config) {
|
||||
await this.checkBadWordsByConfig(content, config, userId);
|
||||
}
|
||||
/* 自定义敏感词检测 */
|
||||
return await this.customSensitiveWords(content, userId);
|
||||
}
|
||||
|
||||
/* 通过配置信息去检测敏感词 */
|
||||
async checkBadWordsByConfig(content: string, config: any, userId) {
|
||||
const { useType } = config;
|
||||
useType === 'baidu' &&
|
||||
(await this.baiduCheckBadWords(content, config.baiduTextAccessToken, userId));
|
||||
}
|
||||
|
||||
/* 提取百度云敏感词违规类型 */
|
||||
extractContent(str) {
|
||||
const pattern = /存在(.*?)不合规/;
|
||||
const match = str.match(pattern);
|
||||
return match ? match[1] : '';
|
||||
}
|
||||
|
||||
/* 通过百度云敏感词检测 */
|
||||
async baiduCheckBadWords(content: string, accessToken: string, userId: number) {
|
||||
if (!accessToken) return;
|
||||
if (!content || content.trim() === '') {
|
||||
Logger.debug('提交的内容为空,跳过百度敏感词检测', 'BadWordsService');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token=${accessToken}}`;
|
||||
const headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: 'application/json',
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await axios.post(url, { text: content }, { headers });
|
||||
const { conclusion, error_code, error_msg, conclusionType, data } = response.data;
|
||||
|
||||
// 如果API返回错误,记录错误并直接返回
|
||||
if (error_code) {
|
||||
Logger.warn(`百度文本检测出现错误、请查看配置信息: ${error_msg}`, 'BadWordsService');
|
||||
return;
|
||||
}
|
||||
|
||||
// conclusion 审核结果,可取值:合规、不合规、疑似、审核失败
|
||||
// conclusionType 1.合规,2.不合规,3.疑似,4.审核失败
|
||||
if (conclusionType !== 1 && data && Array.isArray(data)) {
|
||||
const types = [...new Set(data.map(item => this.extractContent(item.msg)))];
|
||||
await this.recordUserBadWords(userId, content, ['***'], types, '百度云检测');
|
||||
const tips = `您提交的信息中包含${types.join(
|
||||
',',
|
||||
)}的内容、我们已对您的账户进行标记、请合规使用!`;
|
||||
throw new HttpException(tips, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`百度敏感词检测服务异常: ${error.message}`, error.stack, 'BadWordsService');
|
||||
// 出错时不阻止后续流程,继续使用自定义词库检测
|
||||
}
|
||||
}
|
||||
|
||||
/* formarTips */
|
||||
formarTips(wordList) {
|
||||
const categorys = wordList.map(t => t.category);
|
||||
const unSet = [...new Set(categorys)];
|
||||
return `您提交的内容中包含${unSet.join(',')}的信息、我们已对您账号进行标记、请合规使用!`;
|
||||
}
|
||||
|
||||
/* 加载自定义的敏感词 */
|
||||
async loadBadWords() {
|
||||
const data = await this.badWordsEntity.find({
|
||||
where: { status: 1 },
|
||||
select: ['word'],
|
||||
});
|
||||
this.badWords = data.map(t => t.word);
|
||||
}
|
||||
|
||||
/* 查询自定义的敏感词 */
|
||||
async queryBadWords(query: QueryBadWordsDto) {
|
||||
const { page = 1, size = 500, word, status } = query;
|
||||
const where: any = {};
|
||||
[0, 1, '0', '1'].includes(status) && (where.status = status);
|
||||
word && (where.word = Like(`%${word}%`));
|
||||
const [rows, count] = await this.badWordsEntity.findAndCount({
|
||||
where,
|
||||
skip: (page - 1) * size,
|
||||
take: size,
|
||||
order: { id: 'ASC' },
|
||||
});
|
||||
return { rows, count };
|
||||
}
|
||||
|
||||
/* 删除自定义敏感词 */
|
||||
async delBadWords(body: DelBadWordsDto) {
|
||||
const b = await this.badWordsEntity.findOne({ where: { id: body.id } });
|
||||
if (!b) {
|
||||
throw new HttpException('敏感词不存在,请检查您的提交信息', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
const res = await this.badWordsEntity.delete({ id: body.id });
|
||||
if (res.affected > 0) {
|
||||
await this.loadBadWords();
|
||||
return '删除敏感词成功';
|
||||
} else {
|
||||
throw new HttpException('删除敏感词失败', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/* 修改自定义敏感词 */
|
||||
async updateBadWords(body: UpdateBadWordsDto) {
|
||||
const { id, word, status } = body;
|
||||
const b = await this.badWordsEntity.findOne({ where: { word } });
|
||||
if (b) {
|
||||
throw new HttpException('敏感词已经存在了、请勿重复添加', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
const res = await this.badWordsEntity.update({ id }, { word, status });
|
||||
if (res.affected > 0) {
|
||||
await this.loadBadWords();
|
||||
return '更新敏感词成功';
|
||||
} else {
|
||||
throw new HttpException('更新敏感词失败', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
async addBadWord(body: AddBadWordDto) {
|
||||
const { word } = body;
|
||||
const b = await this.badWordsEntity.findOne({ where: { word } });
|
||||
if (b) {
|
||||
throw new HttpException('敏感词已存在,请检查您的提交信息', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
await this.badWordsEntity.save({ word });
|
||||
await this.loadBadWords();
|
||||
return '添加敏感词成功';
|
||||
}
|
||||
|
||||
/* 记录用户违规次数内容 */
|
||||
async recordUserBadWords(userId, content, words, typeCn, typeOriginCn) {
|
||||
const data = {
|
||||
userId,
|
||||
content,
|
||||
words: JSON.stringify(words),
|
||||
typeCn: JSON.stringify(typeCn),
|
||||
typeOriginCn,
|
||||
};
|
||||
try {
|
||||
await this.userEntity
|
||||
.createQueryBuilder()
|
||||
.update(UserEntity)
|
||||
.set({ violationCount: () => 'violationCount + 1' })
|
||||
.where('id = :userId', { userId })
|
||||
.execute();
|
||||
await this.violationLogEntity.save(data);
|
||||
} catch (error) {
|
||||
console.log('error: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
/* 违规记录 */
|
||||
async violation(req, query) {
|
||||
const { role } = req.user;
|
||||
const { page = 1, size = 10, userId, typeOriginCn } = query;
|
||||
const where = {};
|
||||
userId && (where['userId'] = userId);
|
||||
typeOriginCn && (where['typeOriginCn'] = typeOriginCn);
|
||||
const [rows, count] = await this.violationLogEntity.findAndCount({
|
||||
where,
|
||||
skip: (page - 1) * size,
|
||||
take: size,
|
||||
order: { id: 'DESC' },
|
||||
});
|
||||
const userIds = [...new Set(rows.map(t => t.userId))];
|
||||
const usersInfo = await this.userEntity.find({
|
||||
where: { id: In(userIds) },
|
||||
select: ['id', 'avatar', 'username', 'email', 'violationCount', 'status'],
|
||||
});
|
||||
rows.forEach((t: any) => {
|
||||
// 查找用户信息,如果没有找到,返回空对象
|
||||
const user: any = usersInfo.find(u => u.id === t.userId) || {};
|
||||
|
||||
// const user: any = usersInfo.find((u) => u.id === t.userId);
|
||||
role !== 'super' && (user.email = hideString(user.email));
|
||||
t.userInfo = user;
|
||||
});
|
||||
|
||||
return { rows, count };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user