chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\service;
|
||||
|
||||
/**
|
||||
* 内容安全服务:敏感词过滤 + 风险评分
|
||||
* 敏感词存放于 wxapp_wxchat_banwords,缺失时使用内置兜底词库
|
||||
*/
|
||||
class SafetyService
|
||||
{
|
||||
private static $instance = null;
|
||||
|
||||
/** @var array|null [['word'=>, 'level'=>, 'replace'=>], ...] */
|
||||
private $words = null;
|
||||
|
||||
|
||||
public static function instance(): self
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载敏感词(带缓存 + 兜底)
|
||||
*/
|
||||
private function loadWords(): array
|
||||
{
|
||||
if ($this->words !== null) {
|
||||
return $this->words;
|
||||
}
|
||||
$this->words = [];
|
||||
try {
|
||||
$rows = \think\facade\Db::name('wxchat_banwords')
|
||||
->where('status', 1)
|
||||
->order('level', 'desc')
|
||||
->select();
|
||||
foreach ($rows as $r) {
|
||||
$this->words[] = [
|
||||
'word' => $r['word'],
|
||||
'level' => (int)$r['level'],
|
||||
'replace' => $r['replace_char'] ?: '**',
|
||||
];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 表可能未创建,忽略,使用兜底词库
|
||||
}
|
||||
// 兜底词库
|
||||
foreach (['fuck', 'shit', '赌博', '色情', '诈骗', '代开发票'] as $w) {
|
||||
$this->words[] = ['word' => $w, 'level' => 1, 'replace' => '**'];
|
||||
}
|
||||
return $this->words;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤敏感词(level=1 替换为 **;level=2 命中则整段清空并返回命中标记)
|
||||
* @return string 过滤后的文本
|
||||
*/
|
||||
public function filter(string $text): string
|
||||
{
|
||||
if ($text === '') {
|
||||
return $text;
|
||||
}
|
||||
foreach ($this->loadWords() as $w) {
|
||||
if ($w['level'] >= 2) {
|
||||
if (mb_stripos($text, $w['word']) !== false) {
|
||||
// 高危词:整段清空(拦截)
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
$text = $this->mbStrReplace($w['word'], $w['replace'], $text);
|
||||
}
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内容安全检测:返回是否命中高危词
|
||||
*/
|
||||
public function check(string $text): array
|
||||
{
|
||||
$hit = [];
|
||||
foreach ($this->loadWords() as $w) {
|
||||
if (mb_stripos($text, $w['word']) !== false) {
|
||||
$hit[] = $w['word'];
|
||||
if ($w['level'] >= 2) {
|
||||
return ['blocked' => true, 'hit' => $hit];
|
||||
}
|
||||
}
|
||||
}
|
||||
return ['blocked' => false, 'hit' => $hit];
|
||||
}
|
||||
|
||||
/**
|
||||
* 多字节安全的字符串替换
|
||||
*/
|
||||
private function mbStrReplace($search, $replace, $subject): string
|
||||
{
|
||||
if ($search === '') {
|
||||
return $subject;
|
||||
}
|
||||
$parts = mb_split(preg_quote($search), $subject);
|
||||
if ($parts === false) {
|
||||
return str_ireplace($search, $replace, $subject);
|
||||
}
|
||||
return implode($replace, $parts);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user