This commit is contained in:
chengxl
2025-10-13 16:16:59 +08:00
parent 719ad61cf8
commit c877eba035

View File

@@ -0,0 +1,503 @@
<?php
namespace app\api\controller;
use app\api\controller\Base;
use think\Db;
use app\common\OpenAi;
use app\common\Article;
/**
* @title 文章校对记录
* @description 对接OPENAI接口
*/
class Proofread extends Base
{
/**
* @title AI文章校对
* @param article_id 文章ID
*/
public function proofRead($aParam = []){
//获取参数
$aParam = empty($aParam) ? $this->request->post() : $aParam;
$iArticleId = empty($aParam['article_id']) ? '' : $aParam['article_id'];
if(empty($iArticleId)){
return json_encode(array('status' => 2,'msg' => 'Please select an article' ));
}
//行号
$iAmId = empty($aParam['am_id']) ? 0 : $aParam['am_id'];
//查询文章
$aWhere = ['article_id' => $iArticleId];
$oArticle = new Article;
$aArticle = json_decode($oArticle->get($aWhere),true);
$aArticle = empty($aArticle['data']) ? [] : $aArticle['data'];
if(empty($aArticle)){
return json_encode(array('status' => 3,'msg' => 'No articles requiring review were found' ));
}
if($aArticle['state'] != 6){
return json_encode(array('status' => 4,'msg' => 'The article has not entered the proofreading stage'));
}
//查询是否进行过校对
$aProofReadWhere = ['article_id' => $iArticleId,'state' => 2];
if(!empty($iAmId)){
$aProofReadWhere['am_id'] = $iAmId;
}
$iCount = Db::name('article_proofread')->where($aProofReadWhere)->count();
if(!empty($iCount)){
return json_encode(array('status' => 5,'msg' => 'The article or paragraph has been proofread'));
}
//查询文章内容
$aWhere['type'] = 0;
$aWhere['content'] = ['<>',''];
$aWhere['state'] = 0;
if(!empty($iAmId)){
$aWhere['am_id'] = $iAmId;
}
$aArticleMain = Db::table('t_article_main')->field('am_id,content,type,is_h1,is_h2,is_h3,ami_id,amt_id')->where($aWhere)->select();
if(empty($aArticleMain)){
return json_encode(array('status' => 5,'msg' => 'The content of the article is empty'));
}
//实例化公共方法
$oHelperFunction = new \app\common\HelperFunction;
$oProofReadService = new \app\common\ProofReadService;
//数据处理
$aH = $aTable = [];
foreach ($aArticleMain as $key => $value) {
if(empty($oHelperFunction->filterAllTags($value['content']))){
continue;
}
$aResult = $oProofReadService->proofread($value['content']);
if(empty($aResult)){
continue;
}
$aResult['am_id'] = $value['am_id'];
$aError[] = $aResult;
}
if(empty($aError)){
return json_encode(array('status' => 1,'msg' => 'No errors found'));
}
//数据处理
foreach ($aError as $key => $value) {
if(empty($value['errors'])){
continue;
}
foreach ($value['errors'] as $k => $val) {
$val['am_id'] = $value['am_id'];
$val['article_id'] = $iArticleId;
$val['proof_before'] = empty($value['proof_before']) ? '' : $value['proof_before'];
$val['proof_after'] = empty($value['proof_after']) ? '' : $value['proof_after'];
$aData[] = $val;
}
}
if(empty($aData)){
return json_encode(array('status' => 1,'msg' => 'Data processing failed'));
}
//插入
$result = Db::name('article_proofread')->insertAll($aData);
if(!$result){
return json_encode(array('status' => 6,'msg' => 'No errors found'));
}
//查询参考文献
$aWhere = ['state' => ['in',[0,2]],'article_id' => $iArticleId];
return json_encode(['status' => 1,'msg' => 'success']);
}
/**
* @title 获取每行校对记录
* @param article_id 文章ID
* @param am_id 行号
* @param state 状态1已执行2未执行3删除
*/
public function get($aParam = []){
//获取参数
$aParam = empty($aParam) ? $this->request->post() : $aParam;
//参数验证-文章ID
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
if(empty($iArticleId)){
return json_encode(['status' => 2,'msg' => 'Please select a article']);
}
//行号
$iAmId = empty($aParam['am_id']) ? 0 : $aParam['am_id'];
//查询文章
$aWhere = ['article_id' => $iArticleId];
$aArticle = Db::name('article')->field('journal_id,state')->where($aWhere)->find();
if(empty($aArticle)){
return json_encode(['status' => 3,'msg' => 'The query article does not exist']);
}
if($aArticle['state'] < 5 || $aArticle['state'] == 8){
return json_encode(array('status' => 4,'msg' => 'The article has not entered the proofreading stage'));
}
//查询文章内容
$aWhere['type'] = 0;
$aWhere['content'] = ['<>',''];
$aWhere['state'] = 0;
if(!empty($iAmId)){
$aWhere['am_id'] = $iAmId;
}
$aArticleMain = Db::name('article_main')->field('am_id,content')->where($aWhere)->select();
if(empty($aArticleMain)){
return json_encode(array('status' => 5,'msg' => 'The content of the article is empty'));
}
//查询校对内容
$aAmId = array_column($aArticleMain, 'am_id');
$aWhere = ['am_id' => ['in',$aAmId]];
$iState = empty($aParam['state']) ? 0 : $aParam['state'];
if(!empty($iState)){
$aWhere['state'] = ['in',$iState];
}
$aProofRead = Db::name('article_proofread')->field('id,am_id,verbatim_texts,revised_content,explanation,state')->where($aWhere)->select();
if(empty($aProofRead)){
return json_encode(array('status' => 1,'msg' => 'Proofreading record is empty'));
}
//数据处理
$aData = [];
$aArticleMain = array_column($aArticleMain, 'content','am_id');
foreach ($aProofRead as $key => $value) {
$aData[$value['am_id']][] = $value;
}
// 存储每个文章的最新处理内容
// 样式定义:标红+下划线
// 定义标红+下划线样式
// $style = 'color: red; text-decoration: underline;';
// // 存储最终处理后的文章内容
// $aFinalContent = [];
// // 外层循环:遍历每个文章的错误分组($key为am_id
// foreach ($aData as $amId => $errors) {
// // 获取原始文章内容(若不存在则跳过)
// $sContent = empty($aArticleMain[$amId]) ? '' : $aArticleMain[$amId];
// if (empty($sContent)) {
// continue;
// }
// // 初始化当前内容为原始内容,后续替换将基于此更新
// $currentContent = $sContent;
// // 内层循环:处理当前文章的每条错误记录
// foreach ($errors as $val) {
// // 只处理状态为2的错误
// if ($val['state'] != 2) {
// continue;
// }
// // 获取错误内容(待标红的文本)
// $verbatim_texts = trim($val['verbatim_texts'] ?? '');
// if (empty($verbatim_texts)) {
// continue; // 空错误内容跳过
// }
// // 构建带样式的替换内容用span标签包裹添加CSS样式
// $revised_content = '<span style="' . $style . '">' . htmlspecialchars($verbatim_texts, ENT_QUOTES, 'UTF-8') . '</span>';
// // 定位错误内容在当前内容中的位置(支持多字节字符,如中文)
// $startPos = mb_strpos($currentContent, $verbatim_texts, 0, 'UTF-8');
// if ($startPos === false) {
// continue; // 未找到错误内容,跳过
// }
// // 计算错误内容的字节长度(用于替换)
// $byteLength = strlen($verbatim_texts);
// // 转换字符起始位置为字节位置适配substr_replace的字节处理
// $byteStart = 0;
// for ($i = 0; $i < $startPos; $i++) {
// $byteStart += strlen(mb_substr($currentContent, $i, 1, 'UTF-8'));
// }
// // 执行替换:用带样式的内容替换原始错误内容
// $currentContent = substr_replace($currentContent, $revised_content, $byteStart, $byteLength);
// }
// // 保存当前文章处理后的最终内容
// $aFinalContent[$amId] = $currentContent;
// }
return json_encode(['status' => 1,'msg' => 'success','data' => ['record' => $aData]]);//,'record_style' => $aFinalContent
}
/**
* @title 更新状态
* @param article_id 文章ID
* @param am_id 行号
* @param record_id 记录ID
* @param state 1已执行2未执行3撤销
*/
public function change(){
//获取参数
$aParam = $this->request->post();
//参数验证-文章ID
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
if(empty($iArticleId)){
return json_encode(['status' => 2,'msg' => 'Please select a article']);
}
//参数验证-行号ID
$iAmId = empty($aParam['am_id']) ? 0 : $aParam['am_id'];
if(empty($iAmId)){
return json_encode(['status' => 2,'msg' => 'Please select the proofreading content']);
}
//主键ID
$iId = empty($aParam['record_id']) ? 0 : $aParam['record_id'];
if(empty($iId)){
return json_encode(['status' => 2,'msg' => 'Please select the review record']);
}
//状态
$iState = empty($aParam['state']) ? 0 : intval($aParam['state']);
if(!in_array($iState, [1,2,3])){
return json_encode(['status' => 2,'msg' => 'Illegal review status']);
}
//修改内容
$sContent = empty($aParam['content']) ? '' : $aParam['content'];
if(in_array($iState, [1,2]) && empty($sContent)){
return json_encode(['status' => 2,'msg' => 'The operation content cannot be empty']);
}
//判断校对记录
$aWhere = ['am_id' => $iAmId,'article_id' => $iArticleId,'id' => $iId];
$aProofRead = Db::name('article_proofread')->where($aWhere)->find();
if(empty($aProofRead)){
return json_encode(['status' => 3,'msg' => 'Proofreading record is empty']);
}
if($aProofRead['state'] == 3){
return json_encode(['status' => 4,'msg' => 'Record deleted']);
}
//状态一致
if($iState == $aProofRead['state']){
return json_encode(['status' => 5,'msg' => 'Consistent status without modification']);
}
//判断记录是否执行
if($iState == 3 && $aProofRead['state'] == 1){
return json_encode(['status' => 6,'msg' => 'This record has been executed and cannot be deleted']);
}
$sData = $sUpdateContent = '';
if($iState == 1){ //执行替换操作
$aProofRead['content'] = $sContent;
$aResult = $this->replaceError($aProofRead);
$iStatus = empty($aResult['status']) ? 0 : $aResult['status'];
if($iStatus != 1){
return json_encode($aResult);
}
//获取内容
$sUpdateContent = empty($aResult['data']) ? '' : $aResult['data'];
if(empty($sUpdateContent)){
return json_encode(['status' => 5,'msg' => 'Content processing failed']);
}
// $aDealData = json_decode($this->get(['am_id' => $iAmId,'article_id' => $iArticleId]),true);
// $aDealData = empty($aDealData['data']) ? [] : $aDealData['data'];
// $sData = empty($aDealData['record_style']) ? '' : $aDealData['record_style'];
$sData = $sUpdateContent;
}
if($iState == 2){ //执行替换操作
$aProofRead['content'] = $sContent;
$aResult = $this->removeReplaceError($aProofRead);
$iStatus = empty($aResult['status']) ? 0 : $aResult['status'];
if($iStatus != 1){
return json_encode($aResult);
}
//获取内容
$sUpdateContent = empty($aResult['data']) ? '' : $aResult['data'];
if(empty($sUpdateContent)){
return json_encode(['status' => 5,'msg' => 'Content processing failed']);
}
// $aDealData = json_decode($this->get(['am_id' => $iAmId,'article_id' => $iArticleId]),true);
// $aDealData = empty($aDealData['data']) ? [] : $aDealData['data'];
// $sData = empty($aDealData['record_style']) ? '' : $aDealData['record_style'];
$sData = $sUpdateContent;
}
Db::startTrans();
//更新原始内容
if(!empty($sUpdateContent)){
$aWhere = ['am_id' => $iAmId,'state' => 0,'type' => 0];
$aUpdate = ['content' => $sUpdateContent];
$result_main = Db::name('article_main')->where($aWhere)->limit(1)->update($aUpdate);
}
//判断更新参数
$aUpdate = ['state' => $iState,'update_time' => time()];
//数据库更新
$aWhere = ['id' => $iId];
$result_proofread= Db::name('article_proofread')->where($aWhere)->limit(1)->update($aUpdate);
if(!$result_proofread || !$result_main){
return json_encode(['status' => 7,'msg' => "Update failed"]);
}
Db::commit();
//返回结果
return json_encode(['status' => 1,'msg' => "Update successful",'data' => $sData]);
}
/**
* 替换错误
* @param string $errorId 错误ID
* @return bool 替换是否成功
*/
private function replaceError($aParam = [])
{
if(empty($aParam)){
return ['status' => 2,'msg' => 'The content is empty'];
}
//原始内容
$sContent = empty($aParam['content']) ? '' : $aParam['content'];
//错误内容
$verbatim_texts = empty($aParam['verbatim_texts']) ? '' : $aParam['verbatim_texts'];
//正确内容
$revised_content = empty($aParam['revised_content']) ? 0 : $aParam['revised_content'];
$iLength = strlen($verbatim_texts);
//内容替换
$sContent = $this->replaceByLengthAndKeyword($sContent,$iLength,$verbatim_texts,$revised_content);
return ['status' => 1,'msg' => 'success','data' => $sContent];
}
/**
* 撤回替换错误
* @param string $errorId 错误ID
* @return bool 替换是否成功
*/
private function removeReplaceError($aParam = [])
{
if(empty($aParam)){
return ['status' => 2,'msg' => 'The content is empty'];
}
//原始内容
$sContent = empty($aParam['content']) ? '' : $aParam['content'];
//错误内容
$verbatim_texts = empty($aParam['verbatim_texts']) ? '' : $aParam['verbatim_texts'];
//正确内容
$revised_content = empty($aParam['revised_content']) ? '' : $aParam['revised_content'];
$iLength = strlen($revised_content);
//内容替换
$sContent = $this->replaceByLengthAndKeyword($sContent,$iLength,$revised_content,$verbatim_texts);
return ['status' => 1,'msg' => 'success','data' => $sContent];
}
/**
* 按长度和内容特征快速定位并替换字符串
* @param string $str 原始字符串
* @param int $targetLength 目标子串长度(字节数,多字节字符需注意)
* @param string $keyword 筛选关键词(目标子串需包含此关键词)
* @param string $replace 替换内容
* @param string $encoding 字符编码默认UTF-8处理多字节字符
* @return string 替换后的字符串
*/
private function replaceByLengthAndKeyword($str, $targetLength, $keyword, $replace, $encoding = 'UTF-8') {
// 边界校验:避免无效参数导致错误
if (empty($str) || $targetLength <= 0 || empty($keyword)) {
return '';
}
$strLength = strlen($str);
if ($targetLength > $strLength) {
return ''; // 目标长度超过原始字符串,无需处理
}
$result = $str;
$targets = []; // 存储目标子串的起始位置(字节索引)
// 遍历字符串,提取符合长度且包含关键词的子串位置(一次遍历完成筛选,减少内存占用)
for ($i = 0; $i <= $strLength - $targetLength; $i++) {
// 截取目标长度的子串
$substr = substr($result, $i, $targetLength);
// 检查子串是否包含关键词(多字节安全)
if (mb_strpos($substr, $keyword, 0, $encoding) !== false) {
$targets[] = $i; // 只存起始位置,减少内存占用
}
}
// 若没有匹配目标,直接返回原始字符串
if (empty($targets)) {
return '';
}
// 从后往前替换(避免前面替换导致后面位置偏移)
// 倒序遍历数组,无需额外排序,效率更高
for ($k = count($targets) - 1; $k >= 0; $k--) {
$start = $targets[$k];
// 二次校验:替换前确认子串仍符合条件(防止重复替换或中间修改导致的偏差)
$currentSubstr = substr($result, $start, $targetLength);
if (mb_strpos($currentSubstr, $keyword, 0, $encoding) !== false) {
$result = substr_replace($result, $replace, $start, $targetLength);
}
}
return $result;
}
/**
* @title 更新内容
* @param article_id 文章ID
* @param am_id 行号
* @param record_id 记录ID
* @param revised_content 修改内容
* @param is_update_all 是否更新所有1是2否
*/
public function modify(){
//获取参数
$aParam = $this->request->post();
//参数验证-文章ID
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
if(empty($iArticleId)){
return json_encode(['status' => 2,'msg' => 'Please select a article']);
}
//参数验证-行号ID
$iAmId = empty($aParam['am_id']) ? 0 : $aParam['am_id'];
if(empty($iAmId)){
return json_encode(['status' => 2,'msg' => 'Please select the proofreading content']);
}
//主键ID
$iId = empty($aParam['record_id']) ? 0 : $aParam['record_id'];
if(empty($iId)){
return json_encode(['status' => 2,'msg' => 'Please select the review record']);
}
//修改后的内容
$sRevisedContent = empty($aParam['revised_content']) ? '' : $aParam['revised_content'];
if(empty($sRevisedContent)){
return json_encode(['status' => 2,'msg' => 'Please enter the modification content']);
}
//解释说明
$sExplanation = empty($aParam['explanation']) ? '' : $aParam['explanation'];
//是否更新所有1是2否
$iIsUpdateAll = empty($aParam['is_update_all']) ? 2 : $aParam['is_update_all'];
//查询内容是否存在
$aWhere = ['am_id' => $iAmId,'article_id' => $iArticleId,'id' => $iId];
$aProofRead = Db::name('article_proofread')->field('verbatim_texts,revised_content,explanation,state')->where($aWhere)->find();
if(empty($aProofRead)){
return json_encode(['status' => 3,'msg' => 'Proofreading record is empty']);
}
if($aProofRead['state'] == 3){
return json_encode(['status' => 4,'msg' => 'Record deleted']);
}
if($aProofRead['state'] == 1){
return json_encode(['status' => 5,'msg' => 'Record executed']);
}
//判断更新参数
$aUpdate = ['revised_content' => $sRevisedContent,'update_time' => time()];
if(!empty($sExplanation)){
$aUpdate['explanation'] = $sExplanation;
}
//数据库更新
$aWhere = ['id' => $iId];
if($iIsUpdateAll == 1){
if(empty($aProofRead['verbatim_texts']) || empty($aProofRead['revised_content'])){
return json_encode(['status' => 6,'msg' => 'AI proofreading content is empty']);
}
$aWhere = ['verbatim_texts' => $aProofRead['verbatim_texts'],'revised_content' => $aProofRead['revised_content'],'article_id' => $iArticleId,'state' => 2];
}
$result = Db::name('article_proofread')->where($aWhere)->update($aUpdate);
if(!$result){
return json_encode(['status' => 7,'msg' => "Update failed"]);
}
//返回结果
return json_encode(['status' => 1,'msg' => "Update successful"]);
}
}