ai写作辅助检测
This commit is contained in:
176
application/common/AiTemplateSentenceService.php
Normal file
176
application/common/AiTemplateSentenceService.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace app\common;
|
||||
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* AI 写作模版句规则匹配(t_ai_template_sentence)
|
||||
*/
|
||||
class AiTemplateSentenceService
|
||||
{
|
||||
/** @var array<int,array>|null */
|
||||
private static $ruleCache;
|
||||
|
||||
/**
|
||||
* 按稿件分节统计模版句出现次数
|
||||
*
|
||||
* @param array<string,string> $sectionTexts 键:abstract/introduction/methods/results/discussion/conclusion
|
||||
* @return array{total_matches:int,matched_rule_count:int,items:array,by_section:array}
|
||||
*/
|
||||
public function countInManuscript(array $sectionTexts): array
|
||||
{
|
||||
$rules = $this->loadActiveRules();
|
||||
if (empty($rules)) {
|
||||
return [
|
||||
'total_matches' => 0,
|
||||
'matched_rule_count' => 0,
|
||||
'items' => [],
|
||||
'by_section' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$normalizedSections = [];
|
||||
foreach ($sectionTexts as $key => $text) {
|
||||
$sectionKey = strtolower(trim((string) $key));
|
||||
if ($sectionKey === '') {
|
||||
continue;
|
||||
}
|
||||
$normalizedSections[$sectionKey] = $this->normalizeText((string) $text);
|
||||
}
|
||||
|
||||
$allText = $this->normalizeText(implode("\n\n", array_filter($normalizedSections)));
|
||||
|
||||
$items = [];
|
||||
$bySection = [];
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
$ruleSection = strtolower(trim((string) ($rule['section'] ?? '')));
|
||||
$pattern = trim((string) ($rule['sentence_pattern'] ?? ''));
|
||||
if ($pattern === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetText = '';
|
||||
if ($ruleSection === 'all') {
|
||||
$targetText = $allText;
|
||||
} elseif (isset($normalizedSections[$ruleSection])) {
|
||||
$targetText = $normalizedSections[$ruleSection];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($targetText === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$count = $this->countPatternMatches($targetText, $pattern);
|
||||
if ($count <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = [
|
||||
'id' => intval($rule['id'] ?? 0),
|
||||
'section' => $ruleSection,
|
||||
'sentence_pattern' => $pattern,
|
||||
'count' => $count,
|
||||
'risk_level' => intval($rule['risk_level'] ?? 0),
|
||||
'weight' => intval($rule['weight'] ?? 0),
|
||||
'remark' => (string) ($rule['remark'] ?? ''),
|
||||
];
|
||||
$items[] = $item;
|
||||
|
||||
if (!isset($bySection[$ruleSection])) {
|
||||
$bySection[$ruleSection] = [
|
||||
'total_matches' => 0,
|
||||
'matched_rule_count' => 0,
|
||||
'items' => [],
|
||||
];
|
||||
}
|
||||
$bySection[$ruleSection]['items'][] = $item;
|
||||
$bySection[$ruleSection]['total_matches'] += $count;
|
||||
$bySection[$ruleSection]['matched_rule_count']++;
|
||||
}
|
||||
|
||||
usort($items, function ($a, $b) {
|
||||
if ($a['count'] !== $b['count']) {
|
||||
return $b['count'] - $a['count'];
|
||||
}
|
||||
if ($a['weight'] !== $b['weight']) {
|
||||
return $b['weight'] - $a['weight'];
|
||||
}
|
||||
return $a['id'] - $b['id'];
|
||||
});
|
||||
|
||||
foreach ($bySection as &$sectionStats) {
|
||||
usort($sectionStats['items'], function ($a, $b) {
|
||||
if ($a['count'] !== $b['count']) {
|
||||
return $b['count'] - $a['count'];
|
||||
}
|
||||
return $b['weight'] - $a['weight'];
|
||||
});
|
||||
}
|
||||
unset($sectionStats);
|
||||
|
||||
$totalMatches = 0;
|
||||
foreach ($items as $item) {
|
||||
$totalMatches += intval($item['count']);
|
||||
}
|
||||
|
||||
return [
|
||||
'total_matches' => $totalMatches,
|
||||
'matched_rule_count' => count($items),
|
||||
'items' => $items,
|
||||
'by_section' => $bySection,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,article_type,version')
|
||||
->order('section asc,id asc')
|
||||
->select();
|
||||
} catch (\Throwable $e) {
|
||||
self::$ruleCache = [];
|
||||
}
|
||||
|
||||
return self::$ruleCache;
|
||||
}
|
||||
|
||||
private function normalizeText(string $text): string
|
||||
{
|
||||
return ManuscriptTextCleanService::clean($text);
|
||||
}
|
||||
|
||||
private function countPatternMatches(string $text, string $pattern): int
|
||||
{
|
||||
if ($text === '' || $pattern === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$regex = '#' . $pattern . '#iu';
|
||||
$error = null;
|
||||
set_error_handler(function ($errno, $errstr) use (&$error) {
|
||||
$error = $errstr;
|
||||
return true;
|
||||
});
|
||||
$count = preg_match_all($regex, $text, $matches);
|
||||
restore_error_handler();
|
||||
|
||||
if ($count === false || $error !== null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return intval($count);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user