队列提取公共方法

This commit is contained in:
chengxl
2025-07-22 11:19:38 +08:00
parent 73cbf94a53
commit 58c4fc27aa
3 changed files with 633 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
<?php
namespace app\common;
use think\Db;
/**
* @title AI审核文章公共方法
* @description 对接OPENAI接口
*/
class Aireview
{
/**
* @title AI审核内容入库
* @param article_id 文章ID
*/
public function addAiReview($aParam = array()){
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
if(empty($iArticleId)){
return ['status' => 2,'msg' => 'Please select the article to be reviewed'];
}
$iJournalId = empty($aParam['journal_id']) ? 0 : $aParam['journal_id'];
if(empty($iJournalId)){
return ['status' => 2,'msg' => 'The journal to which the article belongs cannot be empty'];
}
//返回数组
$aResult = ['status' => 1,'msg' => 'AI review successful'];
//数据库参数
$aFields = ['journal_scope','attribute','contradiction','unreasonable','ethics','academic','conclusion','fund_number','hotspot','submit_direction','references_past_three','references_past_five','references_ratio_JCR1','references_ratio_JCR2','registration_assessment','cite_rate','references_num','article_field'];
foreach ($aParam as $key => $value) {
if(empty($value)){
continue;
}
if(is_array($value)){
if(!empty($value['assessment'])){
$sField = $key.'_'.'assessment';
$sAssessment = empty($value['assessment']) ? '' : $value['assessment'];
if(!empty($sAssessment)){
$sAssessment = is_array($sAssessment) ? json_encode($sAssessment) : htmlspecialchars($value['assessment']);
}
$aInsert[$sField] = $sAssessment;
}
if(!empty($value['explanation'])){
$sField = $key.'_'.'explanation';
$aInsert[$sField] = empty($value['explanation']) ? '' : htmlspecialchars($value['explanation']);
}
}else{
$aInsert[$key] = empty($value) ? '' : htmlspecialchars($value);
}
}
if(empty($aInsert)){
return ['status' => 3,'msg' => 'Data is empty'];
}
//查询文章审核内容-判断新增或修改
$aWhere = ['article_id' => $iArticleId,'journal_id' => $iJournalId];
$aAiReview = Db::table('t_article_ai_review')->field('id')->where($aWhere)->find();
$iLogId = empty($aAiReview['id']) ? 0 : $aAiReview['id'];
//新增
if(empty($iLogId)){
$aInsert['create_time'] = date('Y-m-d H:i:s');
$aInsert['content'] = $iArticleId;
$iLogId = Db::name('article_ai_review')->insertGetId($aInsert);
if(empty($iLogId)){
$aResult = ['status' => 4,'msg' => 'Failed to add AI audit content'];
}
$aResult['data'] = ['id' => $iLogId];
return $aResult;
}
if(!empty($iLogId)){
$aWhere = ['id' => $iLogId];
$aInsert['update_time'] = date('Y-m-d H:i:s');
if(!Db::name('article_ai_review')->where($aWhere)->limit(1)->update($aInsert)){
$aResult = ['status' => 5,'msg' => 'Failed to add AI audit content'];
}
$aAiReview = Db::table('t_article_ai_review')->where($aWhere)->find();
$aResult['data'] = $aAiReview;
return $aResult;
}
return ['status' => 6,'msg' => 'illegal request'];
}
/**
* @title 文章AI审核内容查询
* @param article_id 文章ID
*/
public function get($aParam = []){
//获取参数
$aParam = empty($aParam) ? $this->request->post() : $aParam;
if(empty($aParam['article_id'])){
return ['status' => 2,'msg' => 'Please select an article'];
}
//查询文章审核内容
$aWhere = ['article_id' => $aParam['article_id']];
$aAiReview = Db::table('t_article_ai_review')->where($aWhere)->find();
return ['status' => 1,'msg' => 'Successfully obtained article review content','data' => $aAiReview];
}
}

View File

@@ -0,0 +1,324 @@
<?php
namespace app\common;
use think\Db;
use app\common\OpenAi;
use app\common\Aireview;
class Article
{
//JAVA接口
protected $sJavaUrl = "http://ts.tmrjournals.com/";
//官网文件地址
protected $sFileUrl = "https://submission.tmrjournals.com/public/";
/**
* 获取文章文件内容
*/
public function getFileContent($aParam = []){
//判断文章ID
$iArticleId = empty($aParam['article_id']) ? [] : $aParam['article_id'];
if(empty($iArticleId)){
return json_encode(['status' => 2,'msg' => 'Please select an article']);
}
//获取文件内容
$aWhere = ['article_id' => $iArticleId,'type_name' => 'manuscirpt'];
$aFile = Db::name('article_file')->field('file_url')->where($aWhere)->order('ctime desc')->limit(1)->find();
if(empty($aFile['file_url'])){
return json_encode(['status' => 2,'msg' => 'No Manuscript']);
}
//接口获取上传文件
$sUrl = $this->sJavaUrl."api/typeset/readDocx";
$aParam['fileRoute'] = $this->sFileUrl.$aFile['file_url'];
$aResult = object_to_array(json_decode(myPost($sUrl,$aParam)));
return json_encode($aResult);
}
/**
* 获取期刊内容
*/
public function getJournalPaperArt($aParam = []){
//判断文章ID
$sIssn = empty($aParam['issn']) ? [] : $aParam['issn'];
if(empty($sIssn)){
return json_encode(['status' => 2,'msg' => 'Please select an article']);
}
//接口获取期刊内容
$sUrl = $this->sTmrUrl."/api/Supplementary/getJournalPaperArt";
$aParam = ['issn' => $sIssn];
$aResult = object_to_array(json_decode(myPost($sUrl,$aParam),true));
return json_encode($aResult);
}
/**
* 更新AI生成内容入库
* @param $messages 内容
* @param $model 模型类型
*/
public function updateAiArticle($aParam = []){
//文章ID
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
//查询内容是否存在
$aWhere = ['is_delete' => 2];
if(empty($iArticleId)){
return json_encode(['status' => 2,'msg' => 'Please select the article to be modified']);
}
//查询文章是否生成AI内容
$aWhere= ['is_delete' => 2,'article_id' => $iArticleId];
$aAiArticle = Db::name('ai_article')->field('ai_article_id')->where($aWhere)->find();
if(empty($aAiArticle)){
return json_encode(['status' => 3,'msg' => 'The article content of WeChat official account has not been generated']);
}
$iAiArticleId = $aAiArticle['ai_article_id'];
//必填参数验证
$aFields = ['article_id','title_english','title_chinese','journal_issn','covered','digest','research_result','content','highlights','discussion','prospect','research_background','discussion_results','research_method','overview','summary','is_generate'];
$sFiled = '';
$aUpdateParam = [];
foreach($aFields as $val){
if(!isset($aParam[$val])){
continue;
}
if(is_array($aParam[$val])){
$aParam[$val] = implode(";",$aParam[$val]);
}
$aUpdateParam[$val] = empty($aParam[$val]) ? '' : addslashes($aParam[$val]);
}
if(empty($aUpdateParam)){
return json_encode(['status' => 1,'msg' => 'No data currently being processed']);
}
//执行入库
$aUpdateParam['update_time'] = time();
$result = Db::name('ai_article')->where('ai_article_id',$iAiArticleId)->limit(1)->update($aUpdateParam);
if($result === false){
return json_encode(['status' => 4,'msg' => 'UPDATEING AI article failed']);
}
return json_encode(['status' => 1,'msg' => 'No data currently being processed']);
}
/**
* 构建文章领域-处理提示词
*/
private function buildFieldPrompt($aSearch = []){
//必填验证
if(empty($aSearch)){
return [];
}
$sSysMessagePrompt = '
你是一位资深的医学期刊学术评审专家负责严谨、客观地评估学术文章。返回格式必须严格遵循以下JSON结构!请根据文章的标题和摘要从目标期刊下所有领域中筛选出符合文章的领域';
$sSysMessagePrompt .= json_encode([
"article_field" => [
"assessment" => [
"major_id" => "领域ID多个,分隔",
"major_name" => "领域名称多个,分隔"
],
"explanation" =>"请详细解释说明.请返回中文解释!"
]
],JSON_UNESCAPED_UNICODE);
//组装问题
$sUserPrompt = '根据文章的标题:{title};摘要:{abstrart}从目标期刊:【{journal_name}】包含的领域【json结构】{journal_major}中筛选出最符合文章领域【小于等于3个】';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
return [
['role' => 'system', 'content' => $sSysMessagePrompt], ['role' => 'user', 'content' => $sUserPrompt]
];
}
/**
* 获取文章领域【AI】
* @param bool $assoc 是否返回关联数组默认true
*/
public function getAiField($aParam = []){
$iArticleId = empty($aParam['article_id']) ? '' : $aParam['article_id'];
if(empty($iArticleId)){
return json_encode(array('status' => 2,'msg' => 'Please select an article' ));
}
//查询文章
$aWhere = ['article_id' => $iArticleId];
$aArticle = json_decode($this->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' ));
}
//获取文章领域
$aArticleField = $this->getArticleField($aWhere);
if(!empty($aArticleField['data'])){
return json_encode(array('status' => 4,'msg' =>'The article has been added to the field' ));
}
//文章标题
$title = empty($aArticle['title']) ? '' : $aArticle['title'];
if(empty($title)){
return json_encode(array('status' => 5,'msg' => 'The title cannot be empty'));
}
//摘要
$abstrart = empty($aArticle['abstrart']) ? '' : $aArticle['abstrart'];
if(empty($abstrart)){
return json_encode(array('status' => 5,'msg' => 'The abstract cannot be empty' ));
}
//查询该期刊下的所有领域
$aWhere = ['journal_id' => $aArticle['journal_id']];
$aResult = $this->getJouarnalMajor($aWhere);
//期刊领域
$aMaJor = empty($aResult['data']['major']) ? [] : $aResult['data']['major'];
//期刊信息
$aJournal = empty($aResult['data']['journal']) ? [] : $aResult['data']['journal'];
if(empty($aJournal) || empty($aMaJor)){
return json_encode($aResult);
}
//变量替换
$aSearch = [];
$title = empty($aArticle['title']) ? '' : '文章标题:'.$aArticle['title'];
$abstrart = empty($aArticle['abstrart']) ? '' : '文章摘要:'.$aArticle['abstrart'];
$aSearch['{title}'] = 'title:'.$title;
$aSearch['{abstrart}'] = 'abstract:'.$abstrart;
$aSearch['{journal_major}'] = empty($aMaJor) ? '' : json_encode($aMaJor);//期刊领域
$aSearch['{journal_name}'] = empty($aJournal['title']) ? '' : $aJournal['title'];
$aMessage = $this->buildFieldPrompt($aSearch);
//请求OPENAI接口-非重要维度一次请求获取答案
if(empty($aMessage)){
return json_encode(['status' => 6,'msg' => 'AI Q&A content not obtained']);
}
//请求OPENAI
$oOpenAi = new OpenAi;
$aParam = ['messages' => $aMessage,'model' => empty($aParam['api_model']) ? 'gpt-4.1' : $aParam['api_model']];
$aResult = json_decode($oOpenAi->curlOpenAIStream($aParam),true);
//处理返回信息
$aData = empty($aResult['data']) ? [] : $aResult['data'];
if(empty($aData)){
return json_encode(['status' => 7,'msg' => empty($aResult['msg']) ? 'OPENAI returns empty content' : $aResult['msg']]);
}
//数据处理
$aData = $oOpenAi->extractAndParse($aData);
if(empty($aData['data'])){
return json_encode($aData);
}
//关联文章领域
$aData = $aData['data'];
$aMaJorId = empty($aData['article_field']['assessment']) ? [] : $aData['article_field']['assessment'];
$sMaJorId = empty($aMaJorId['major_id']) ? '' : $aMaJorId['major_id'];
$aAddResult = $this->addArticleField(['article_field' => $sMaJorId,'article_id' => $iArticleId]);
//插入文章审核记录表
$oAireview = new Aireview;
$aData['article_id'] = $iArticleId;
$aData['journal_id'] = $aArticle['journal_id'];
$aResult = $oAireview->addAiReview($aData);
return json_encode($aAddResult);
}
/**
* @title 文章内容
* @param article_id 文章ID
*/
public function get($aParam = []){
//获取参数
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
if(empty($iArticleId)){
return json_encode(array('status' => 2,'msg' => 'Please select an article' ));
}
//查询文章
$aWhere = ['article_id' => $iArticleId];
$aArticle = Db::name('article')->where($aWhere)->find();
return json_encode(['status' => 1,'msg' => 'success','data' => $aArticle]);
}
/**
* @title 获取文章期刊领域
* @param article_id 文章ID
*/
public function getJouarnalMajor($aParam = []){
//期刊ID
$iJournalId = empty($aParam['journal_id']) ? 0 : $aParam['journal_id'];
if(empty($iJournalId)){
return ['status' => 2,'msg' => 'Please select an journal'];
}
//根据期刊ID查询期刊信息
$aWhere = ['journal_id' => $iJournalId,'state' => 2];
$aJournal = Db::name('journal')->field('title,issn,journal_id')->where('journal_id', $iJournalId)->find();
if(empty($aJournal)){
return ['status' => 3,'msg' => 'This article is not associated with a journal'];
}
$sIssn = empty($aJournal['issn']) ? '' : $aJournal['issn'];
if(empty($sIssn)){
return ['status' => 4,'msg' => 'The issn of the journal is empty'];
}
//查询期刊领域ID
$aWhere = ['journal_issn' => $sIssn,'mtj_state' => 0];
$aMaJorId = Db::name('major_to_journal')->where($aWhere)->column('major_id');
if(empty($aMaJorId)){
return ['status' => 5,'msg' => 'The field of the journal is empty'];
}
//查询领域名称
$aWhere = ['major_state' => 0,'major_id' => ['in',$aMaJorId],'pid' => ['>',0]];
$aMaJor = Db::name('major')->where($aWhere)->column('major_id,major_title');
return ['status' => 5,'msg' => 'success','data' => ['major' => $aMaJor,'journal' => $aJournal]];
}
/**
* 获取文章所属领域
* @param article_id 文章ID
*/
public function getArticleField($aParam = []){
// 文章ID
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
if(empty($iArticleId)){
return ['status' => 2,'msg' => 'Please select a Article'];
}
//查询审稿人领域信息
$aParam['state'] = 0;
$aWhere = ['article_id' => $iArticleId,'state' => 0];
$aArticleField = Db::name('major_to_article')->field('major_id')->where($aWhere)->select();
return ['status' => 1,'msg' => "success",'data' => $aArticleField];
}
/**
* 添加文章所属领域
* @param reviewer_id 审核人ID
* @param
*/
public function addArticleField($aParam = []){
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
if(empty($iArticleId)){
return ['status' => 2,'msg' => 'Please select a article'];
}
$sField = empty($aParam['article_field']) ? '' : $aParam['article_field'];
if(empty($sField)){
return ['status' => 2,'msg' => "Please select the field to which the article belongs"];
}
$aField = is_array($sField) ? $sField : explode(',', trim($sField,','));
if(empty($aField)){
return ['status' => 3,'msg' => "Please select the user's field of expertise"];
}
//领域入库
$aInsertParam = [];
foreach ($aField as $key => $value) {
if(empty($value)){
continue;
}
$aInsertParam[] = ['major_id' => $value,'article_id' => $iArticleId,'ctime' => time(),'state' => 0];
}
if(empty($aInsertParam)){
return ['status' => 4,'msg' => "Please select the user's field of expertise"];
}
$result = Db::name('major_to_article')->insertAll($aInsertParam);
if($result === false){
return ['status' => 5,'msg' => "Failed to add article field"];
}
return ['status' => 1,'msg' => "Successfully added the article field"];
}
}

View File

@@ -0,0 +1,212 @@
<?php
namespace app\common;
use think\Db;
class QueueRedis
{
private $redis;
private $config;
private static $instance;
private function __construct()
{
$this->config = \think\Config::get('queue');
$this->connect();
}
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function connect()
{
// 只在首次调用或连接断开时创建连接
if (!$this->redis) {
$this->redis = new \Redis();
// 使用长连接pconnect避免频繁创建连接
$connectMethod = $this->config['persistent'] ? 'pconnect' : 'connect';
$this->redis->$connectMethod(
$this->config['host'] ?? '127.0.0.1',
$this->config['port'] ?? 6379
);
// 始终执行认证(空密码会被 Redis 忽略)
$this->redis->auth($this->config['password'] ?? '');
$this->redis->select($this->config['select'] ?? 0);
}
return $this->redis;
}
// 使用 SET 命令原子操作设置锁
public function setRedisLock($key, $value, $expire)
{
try {
return $this->connect()->set($key, $value, ['nx', 'ex' => $expire]);
} catch (\Exception $e) {
return false;
}
}
// 设置Redis值
public function setRedisValue($key, $value, $expire = null)
{
try {
$redis = $this->connect();
if ($expire) {
return $redis->setex($key, $expire, $value);
} else {
return $redis->set($key, $value);
}
} catch (\Exception $e) {
return false;
}
}
// 获取Redis值
public function getRedisValue($key)
{
try {
return $this->connect()->get($key);
} catch (\Exception $e) {
return null;
}
}
// 安全释放锁(仅当值匹配时删除)
public function releaseRedisLock($key, $value)
{
// 使用Lua脚本确保原子性
$script = <<<LUA
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
LUA; $redis = $this->connect();
$result = $redis->eval($script, [$key, $value], 1);
return $result;
}
// 获取锁剩余时间
public function getLockTtl($key)
{
try {
return $this->connect()->ttl($key);
} catch (\Exception $e) {
return -1;
}
}
// 任务开始时的批量操作
public function startJob($sRedisKey, $sRedisValue, $expire)
{
try {
$redis = $this->connect();
// 先尝试设置锁,成功后再设置状态
if ($redis->set($sRedisKey, $sRedisValue, ['nx', 'ex' => $expire])) {
$redis->set($sRedisKey . ':status', 'processing', $expire);
return true;
}
return false;
} catch (\Exception $e) {
Log::error("Redis批量操作失败: {$e->getMessage()}");
return false;
}
}
// 任务结束时的批量操作
public function finishJob($sRedisKey, $status, $expire)
{
try {
$redis = $this->connect();
// 使用Lua脚本确保原子性
$script = <<<LUA
redis.call('SET', KEYS[1] .. ':status', ARGV[1], 'EX', ARGV[2])
return redis.call('DEL', KEYS[1])
LUA;
return $redis->eval($script, [$sRedisKey, $status, $expire], 1) === 1;
} catch (\Exception $e) {
Log::error("Redis完成任务失败: {$e->getMessage()}");
return false;
}
}
// 记录处理进度
public function recordProcessingStart($key, $totalQuestions)
{
try {
$redis = $this->connect();
$redis->hMSet($key, [
'status' => 'processing',
'total' => $totalQuestions,
'completed' => 0,
'start_time' => time()
]);
$redis->expire($key, 21600); // 6小时过期
return true;
} catch (\Exception $e) {
return false;
}
}
// 更新处理进度
public function updateProcessingProgress($key, $completed)
{
$redis = $this->connect();
// 获取总数
$total = $redis->hGet($key, 'total');
if (!$total) {
return false;
}
// 计算进度
$iProgress = round(($completed / $total) * 100, 2);
// 事务更新多个字段
$redis->hSet($key, 'completed', $completed);
$redis->hSet($key, 'progress', $iProgress);
if ($iProgress >= 100) {
$redis->hSet($key, 'status', 'completed');
$redis->hSet($key, 'end_time', time());
}
return $iProgress;
}
// 保存分块进度
public function saveChunkProgress($key, $chunkIndex, $content)
{
$redis = $this->connect();
$redis->hSet($key, "chunk_{$chunkIndex}", $content);
// 确保设置过期时间(如果已设置则忽略)
$redis->expire($key, 86400);
return true;
}
public function getJobStatus($jobId)
{
try {
return $this->getRedisValue($jobId . ':status');
} catch (\Exception $e) {
return null;
}
}
public function getConnectionStatus()
{
try {
return $this->connect()->ping() === '+PONG';
} catch (\Exception $e) {
return false;
}
}
}
?>