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 = '' . htmlspecialchars($verbatim_texts, ENT_QUOTES, 'UTF-8') . ''; // // 定位错误内容在当前内容中的位置(支持多字节字符,如中文) // $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"]); } }