1
This commit is contained in:
@@ -5,9 +5,12 @@ namespace app\api\controller;
|
||||
use app\api\controller\Base;
|
||||
use app\common\CitationRelevanceService;
|
||||
use app\common\CrossrefService;
|
||||
use app\common\QueueRedis;
|
||||
use app\common\PubmedService;
|
||||
use think\Validate;
|
||||
use think\Db;
|
||||
use think\Env;
|
||||
use think\Queue;
|
||||
/**
|
||||
* @title 参考文献
|
||||
* @description 相关方法汇总
|
||||
@@ -165,9 +168,9 @@ class References extends Base
|
||||
}
|
||||
|
||||
$apiKey = trim((string)Env::get('citation_chat_api_key', ''));
|
||||
if ($apiKey === '') {
|
||||
return jsonError('Please set env citation_chat_api_key for embedding via chat');
|
||||
}
|
||||
// if ($apiKey === '') {
|
||||
// return jsonError('Please set env citation_chat_api_key for embedding via chat');
|
||||
// }
|
||||
|
||||
$config = [
|
||||
'chat_url' => trim((string)Env::get('citation_chat_url', 'http://chat.taimed.cn/v1/chat/completions')),
|
||||
@@ -204,6 +207,117 @@ class References extends Base
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交参考文献鉴别到队列(异步)
|
||||
* 参数:p_refer_id
|
||||
*/
|
||||
public function checkCitationRelevanceQueue($aParam = [])
|
||||
{
|
||||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||||
$pReferId = intval(isset($aParam['p_refer_id']) ? $aParam['p_refer_id'] : 0);
|
||||
if (!$pReferId) {
|
||||
return jsonError('p_refer_id is required');
|
||||
}
|
||||
|
||||
$redisKey = 'queue_job:app\api\job\CitationRelevanceQueue:' . $pReferId;
|
||||
$queueRedis = QueueRedis::getInstance();
|
||||
$status = $queueRedis->getJobStatus($redisKey);
|
||||
|
||||
// 若已完成,直接返回已完成状态,前端可立刻去拉结果
|
||||
if ($status === 'completed') {
|
||||
return jsonSuccess([
|
||||
'queue_key' => $redisKey,
|
||||
'status' => 'completed',
|
||||
]);
|
||||
}
|
||||
|
||||
// 若正在处理,返回 processing
|
||||
if ($status === 'processing') {
|
||||
return jsonSuccess([
|
||||
'queue_key' => $redisKey,
|
||||
'status' => 'processing',
|
||||
]);
|
||||
}
|
||||
|
||||
// 推送新任务到队列
|
||||
$queueId = Queue::push('app\api\job\CitationRelevanceQueue@fire', [
|
||||
'p_refer_id' => $pReferId,
|
||||
], 'CitationRelevanceQueue');
|
||||
|
||||
if (!$queueId) {
|
||||
return jsonError('queue push failed');
|
||||
}
|
||||
|
||||
return jsonSuccess([
|
||||
'queue_key' => $redisKey,
|
||||
'status' => 'queued',
|
||||
'queue_id' => $queueId,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询获取参考文献鉴别结果
|
||||
* 参数:p_refer_id
|
||||
*/
|
||||
public function getCitationRelevanceResult($aParam = [])
|
||||
{
|
||||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||||
$pReferId = intval(isset($aParam['p_refer_id']) ? $aParam['p_refer_id'] : 0);
|
||||
if (!$pReferId) {
|
||||
return jsonError('p_refer_id is required');
|
||||
}
|
||||
|
||||
$redisKey = 'queue_job:app\api\job\CitationRelevanceQueue:' . $pReferId;
|
||||
$queueRedis = QueueRedis::getInstance();
|
||||
$status = $queueRedis->getJobStatus($redisKey);
|
||||
|
||||
if ($status === null || $status === false) {
|
||||
return jsonSuccess([
|
||||
'status' => 'not_found',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($status === 'processing') {
|
||||
return jsonSuccess([
|
||||
'status' => 'processing',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($status === 'failed') {
|
||||
$raw = $queueRedis->getRedisValue($redisKey . ':result');
|
||||
$data = [];
|
||||
if (is_string($raw) && $raw !== '') {
|
||||
$decoded = json_decode($raw, true);
|
||||
if (is_array($decoded)) {
|
||||
$data = $decoded;
|
||||
}
|
||||
}
|
||||
return jsonSuccess([
|
||||
'status' => 'failed',
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
// completed:从 Redis 取出完整结果返回
|
||||
$raw = $queueRedis->getRedisValue($redisKey . ':result');
|
||||
if (!is_string($raw) || $raw === '') {
|
||||
return jsonSuccess([
|
||||
'status' => 'completed',
|
||||
'data' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
if (!is_array($decoded)) {
|
||||
$decoded = ['status' => 1, 'msg' => 'success', 'data' => []];
|
||||
}
|
||||
|
||||
return jsonSuccess([
|
||||
'status' => 'completed',
|
||||
'data' => $decoded,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 t_article_main 拼接正文,按 [n] 定位句子并取前后各 1 句作为上下文
|
||||
*/
|
||||
@@ -263,6 +377,248 @@ class References extends Base
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 t_production_article_refer 构建:正文引用序号([n] 中的 n)=> p_refer_id
|
||||
* 规则与 Production::convertReferencesToLatex 一致:正文序号 = index + 1
|
||||
*
|
||||
* @param int $pArticleId production 侧 p_article_id
|
||||
* @return array<int,int> 例如 [1 => 101, 2 => 102]
|
||||
*/
|
||||
private function buildCitationNumberToPReferIdMap(int $pArticleId): array
|
||||
{
|
||||
if ($pArticleId <= 0) {
|
||||
return [];
|
||||
}
|
||||
$rows = Db::name('production_article_refer')
|
||||
->where('p_article_id', $pArticleId)
|
||||
->where('state', 0)
|
||||
->field('p_refer_id,index')
|
||||
->order('index asc')
|
||||
->select();
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
$n = intval($row['index']) + 1;
|
||||
if ($n > 0 && !empty($row['p_refer_id'])) {
|
||||
$map[$n] = intval($row['p_refer_id']);
|
||||
}
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析括号内引用串(如 1,2 / 3-5 / 1,3-5),展开为正文引用序号列表(保留顺序,不去重)
|
||||
*
|
||||
* @param string $referencePart 不含 [] 的内层,已规范化为英文逗号与普通连字符
|
||||
* @return int[]
|
||||
*/
|
||||
private function expandCitationBracketInner(string $referencePart): array
|
||||
{
|
||||
$referencePart = trim($referencePart);
|
||||
if ($referencePart === '') {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
$segments = preg_split('/\s*,\s*/', $referencePart);
|
||||
foreach ($segments as $seg) {
|
||||
$seg = trim((string)$seg);
|
||||
if ($seg === '') {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^(\d+)\s*-\s*(\d+)$/', $seg, $m)) {
|
||||
$a = intval($m[1]);
|
||||
$b = intval($m[2]);
|
||||
if ($a > $b) {
|
||||
$t = $a;
|
||||
$a = $b;
|
||||
$b = $t;
|
||||
}
|
||||
for ($i = $a; $i <= $b; $i++) {
|
||||
$out[] = $i;
|
||||
}
|
||||
} else {
|
||||
$out[] = intval($seg);
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将正文 HTML 中的 <blue>[n]</blue>(及 [1,2]、[2-4] 等)替换为 <mycite data-id="p_refer_id"></mycite>
|
||||
* 找不到对应参考文献时保留原 <blue>[…]</blue>,避免丢内容。
|
||||
*
|
||||
* @param string $content article_main.content 等 HTML 片段
|
||||
* @param int $pArticleId t_production_article_refer.p_article_id
|
||||
*/
|
||||
public function rewriteMainContentCitationsToMycite(string $content, int $pArticleId)
|
||||
{
|
||||
$map = $this->buildCitationNumberToPReferIdMap($pArticleId);
|
||||
if ($map === []) {
|
||||
return $content;
|
||||
}
|
||||
return preg_replace_callback(
|
||||
'/(?:<\s*blue[^>]*>)?\[([^\]]+)\](?:<\/\s*blue\s*>)?/iu',
|
||||
function (array $matches) use ($map): string {
|
||||
$inner = trim((string)$matches[1]);
|
||||
if ($inner === '') {
|
||||
return $matches[0];
|
||||
}
|
||||
// 仅处理数字引用,避免误伤 [Fig 1] 等
|
||||
$innerNorm = str_replace([',', '–', '—'], [',', '-', '-'], $inner);
|
||||
if (!preg_match('/^[\d\s,\-]+$/', $innerNorm)) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$nums = $this->expandCitationBracketInner($innerNorm);
|
||||
if ($nums === []) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
foreach ($nums as $n) {
|
||||
if ($n <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (empty($map[$n])) {
|
||||
// 有任意一个序号无法映射到 p_refer_id,则保持原始片段不变,避免丢引用信息
|
||||
return $matches[0];
|
||||
}
|
||||
$ids[] = (string)intval($map[$n]);
|
||||
}
|
||||
if ($ids === []) {
|
||||
return $matches[0];
|
||||
}
|
||||
return '<mycite data-id="' . implode(',', $ids) . '"></mycite>';
|
||||
},
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接口:将 content 中的 blue 引用替换为 mycite(需传 p_article_id)
|
||||
*/
|
||||
public function convertMainCitationsToMycite()
|
||||
{
|
||||
$data = $this->request->post();
|
||||
$rule = new Validate([
|
||||
"am_id"=>"require",
|
||||
"article_id"=>"require"
|
||||
]);
|
||||
if(!$rule->check($data)){
|
||||
return jsonError($rule->getError());
|
||||
}
|
||||
$main_info = $this->article_main_obj->where("am_id",$data['am_id'])->find();
|
||||
$p_info = $this->production_article_obj->where("article_id",$data['article_id'])->where("state",0)->find();
|
||||
if(!$p_info||!$main_info){
|
||||
return jsonError('production_article_id not found');
|
||||
}
|
||||
$pArticleId = $p_info['p_article_id'];
|
||||
$content = $main_info['content'];
|
||||
$out = $this->rewriteMainContentCitationsToMycite($content, $pArticleId);
|
||||
return jsonSuccess(['content' => $out]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量处理并回写 t_article_main.content:
|
||||
* 将正文中的 <blue>[n]</blue> / [1,2] / [2-4] 改写为 <mycite data-id="..."></mycite>
|
||||
*
|
||||
* 参数:
|
||||
* - p_article_id (必填):production 侧文章ID
|
||||
* - type (可选):默认 0(仅文本 main),传空则处理所有 type
|
||||
* - dry_run (可选):1=只预览不落库
|
||||
*/
|
||||
public function convertArticleMainCitationsToMycite($aParam = [])
|
||||
{
|
||||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||||
$pArticleId = intval($aParam['p_article_id'] ?? 0);
|
||||
if ($pArticleId <= 0) {
|
||||
return jsonError('p_article_id is required');
|
||||
}
|
||||
|
||||
// 通过 production_article -> article_id,确保是当前系统存在的文章
|
||||
$aArticle = $this->getArticle(['p_article_id' => $pArticleId]);
|
||||
$iStatus = empty($aArticle['status']) ? 0 : $aArticle['status'];
|
||||
if ($iStatus != 1) {
|
||||
return json_encode($aArticle);
|
||||
}
|
||||
$aArticle = empty($aArticle['data']) ? [] : $aArticle['data'];
|
||||
$articleId = intval($aArticle['article_id'] ?? 0);
|
||||
if ($articleId <= 0) {
|
||||
return jsonError('Article not found');
|
||||
}
|
||||
|
||||
$dryRun = intval($aParam['dry_run'] ?? 0) === 1;
|
||||
$type = isset($aParam['type']) ? $aParam['type'] : 0;
|
||||
|
||||
$query = Db::name('article_main')
|
||||
->where('article_id', $articleId)
|
||||
->whereIn('state', [0, 2])
|
||||
->order('sort asc');
|
||||
if ($type !== '' && $type !== null) {
|
||||
$query->where('type', intval($type));
|
||||
}
|
||||
$mains = $query->field('am_id,content,type,sort')->select();
|
||||
if (empty($mains)) {
|
||||
return jsonError('article_main is empty');
|
||||
}
|
||||
|
||||
$changed = 0;
|
||||
$preview = [];
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
foreach ($mains as $row) {
|
||||
$amId = intval($row['am_id']);
|
||||
$old = (string)($row['content'] ?? '');
|
||||
if ($old === '') {
|
||||
continue;
|
||||
}
|
||||
$new = $this->rewriteMainContentCitationsToMycite($old, $pArticleId);
|
||||
if ($new === $old) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changed++;
|
||||
if (count($preview) < 3) {
|
||||
$preview[] = [
|
||||
'am_id' => $amId,
|
||||
'type' => intval($row['type'] ?? 0),
|
||||
'sort' => intval($row['sort'] ?? 0),
|
||||
'before'=> $old,
|
||||
'after' => $new,
|
||||
];
|
||||
}
|
||||
|
||||
if (!$dryRun) {
|
||||
Db::name('article_main')
|
||||
->where('am_id', $amId)
|
||||
->limit(1)
|
||||
->update([
|
||||
'content' => $new,
|
||||
'update_time' => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
Db::rollback();
|
||||
} else {
|
||||
Db::commit();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return jsonError('convert failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return jsonSuccess([
|
||||
'article_id' => $articleId,
|
||||
'p_article_id' => $pArticleId,
|
||||
'dry_run' => $dryRun ? 1 : 0,
|
||||
'total' => count($mains),
|
||||
'changed' => $changed,
|
||||
'preview' => $preview,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改参考文献的信息
|
||||
* @param p_refer_id 主键ID
|
||||
|
||||
Reference in New Issue
Block a user