ai写作辅助检测
This commit is contained in:
168
application/common/AiTemplateSentenceRuleDetectService.php
Normal file
168
application/common/AiTemplateSentenceRuleDetectService.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace app\common;
|
||||
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 模板句规则检测(t_ai_template_sentence)
|
||||
*/
|
||||
class AiTemplateSentenceRuleDetectService
|
||||
{
|
||||
/** @var array<int,array>|null */
|
||||
private static $ruleCache;
|
||||
|
||||
/**
|
||||
* @param array<string,string> $sectionTexts 键:abstract/introduction/...
|
||||
* @return array{hits:array,rule_score:int}
|
||||
*/
|
||||
public function detect(array $sectionTexts): array
|
||||
{
|
||||
$rules = $this->loadActiveRules();
|
||||
$hits = [];
|
||||
$ruleScore = 0;
|
||||
|
||||
$normalized = [];
|
||||
foreach ($sectionTexts as $key => $text) {
|
||||
$section = strtolower(trim((string) $key));
|
||||
if ($section === '') {
|
||||
continue;
|
||||
}
|
||||
$normalized[$section] = ManuscriptTextCleanService::clean((string) $text);
|
||||
}
|
||||
$allText = ManuscriptTextCleanService::clean(implode("\n\n", array_filter($normalized)));
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
$section = strtolower(trim((string) ($rule['section'] ?? '')));
|
||||
$pattern = trim((string) ($rule['sentence_pattern'] ?? ''));
|
||||
if ($pattern === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetText = '';
|
||||
if ($section === 'all') {
|
||||
$targetText = $allText;
|
||||
} elseif (isset($normalized[$section])) {
|
||||
$targetText = $normalized[$section];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if ($targetText === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matches = $this->matchPattern($targetText, $pattern);
|
||||
foreach ($matches as $matchedText) {
|
||||
$sentence = $this->extractSentenceContaining($targetText, $matchedText);
|
||||
$hitScore = max(1, intval($rule['weight'] ?? 1)) * max(1, intval($rule['risk_level'] ?? 1));
|
||||
$hits[] = [
|
||||
'section' => $section,
|
||||
'type' => '模板化表达',
|
||||
'sentence' => $sentence !== '' ? $sentence : $matchedText,
|
||||
'pattern' => $pattern,
|
||||
'score' => $hitScore,
|
||||
'remark' => (string) ($rule['remark'] ?? ''),
|
||||
];
|
||||
$ruleScore += $hitScore;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'hits' => $hits,
|
||||
'rule_score' => min(100, $ruleScore),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,array>
|
||||
*/
|
||||
private function loadActiveRules(): array
|
||||
{
|
||||
if (self::$ruleCache !== null) {
|
||||
return self::$ruleCache;
|
||||
}
|
||||
try {
|
||||
self::$ruleCache = Db::name('ai_template_sentence')
|
||||
->where('status', 1)
|
||||
->field('id,sentence_pattern,section,language,risk_level,weight,remark')
|
||||
->order('section asc,id asc')
|
||||
->select();
|
||||
} catch (\Throwable $e) {
|
||||
self::$ruleCache = [];
|
||||
}
|
||||
return self::$ruleCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,string>
|
||||
*/
|
||||
private function matchPattern(string $text, string $pattern): array
|
||||
{
|
||||
// 支持 regex: 前缀;否则优先按正则,失败则按字面量匹配
|
||||
$raw = $pattern;
|
||||
$forceRegex = false;
|
||||
if (stripos($pattern, 'regex:') === 0) {
|
||||
$raw = trim(substr($pattern, 6));
|
||||
$forceRegex = true;
|
||||
}
|
||||
|
||||
$out = $this->runPregMatchAll($text, '#' . $raw . '#iu');
|
||||
if (!empty($out) || $forceRegex) {
|
||||
return $out;
|
||||
}
|
||||
|
||||
// 字面量回退(库内普通句子)
|
||||
return $this->runPregMatchAll($text, '#' . preg_quote($raw, '#') . '#iu');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,string>
|
||||
*/
|
||||
private function runPregMatchAll(string $text, string $regex): array
|
||||
{
|
||||
$error = null;
|
||||
set_error_handler(function ($errno, $errstr) use (&$error) {
|
||||
$error = $errstr;
|
||||
return true;
|
||||
});
|
||||
$ok = preg_match_all($regex, $text, $matches);
|
||||
restore_error_handler();
|
||||
if ($ok === false || $error !== null || empty($matches[0])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($matches[0] as $match) {
|
||||
$match = trim((string) $match);
|
||||
if ($match !== '') {
|
||||
$out[] = $match;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function extractSentenceContaining(string $text, string $needle): string
|
||||
{
|
||||
$pos = mb_stripos($text, $needle);
|
||||
if ($pos === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$before = mb_substr($text, 0, $pos);
|
||||
$after = mb_substr($text, $pos);
|
||||
|
||||
$start = $before;
|
||||
if (preg_match('/.*[.!?。!?]\s*/us', $before, $m)) {
|
||||
$start = substr($before, strlen($m[0]));
|
||||
}
|
||||
|
||||
$sentence = $start . $after;
|
||||
if (preg_match('/^(.+?[.!?。!?])/us', $sentence, $m)) {
|
||||
return trim($m[1]);
|
||||
}
|
||||
if (preg_match('/^.{1,500}/us', $sentence, $m)) {
|
||||
return trim($m[0]);
|
||||
}
|
||||
return trim(mb_substr($sentence, 0, 300));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user