diff --git a/application/api/controller/Preaccept.php b/application/api/controller/Preaccept.php index 4cabda8..816cbcd 100644 --- a/application/api/controller/Preaccept.php +++ b/application/api/controller/Preaccept.php @@ -1131,6 +1131,9 @@ class Preaccept extends Base return jsonError($rule->getError()); } $am_info = $this->article_main_obj->where("am_id",$data['am_id'])->find(); + $old_content = $am_info['content']; + $new_raw_content = isset($data['content']) ? (string)$data['content'] : ''; + $insert['article_id'] = $am_info['article_id']; $insert['am_id'] = $data['am_id']; $insert['type'] = 0; @@ -1140,11 +1143,21 @@ class Preaccept extends Base $insert['ctime'] = time(); $this->article_main_log_obj->insert($insert); + // 判断是否存在“引用删除”(新 content 相对旧 content 缺少 ) + $hasCitationDeletion = $this->hasMyciteDeletion($old_content, $new_raw_content); + + $update['content'] = $this->formatMain($new_raw_content); + + - $update['content'] = $this->formatMain($data['content']); $update['state'] = 0; $this->article_main_obj->where("am_id",$data['am_id'])->update($update); + + // 若检测到引用删除,则进行全文扫描并标记未被引用条目的 is_used=0(含 table 内容) + if ($hasCitationDeletion) { +// $this->markUnusedReferencesForArticle(intval($am_info['article_id'])); + } // return jsonSuccess([]); //返回更新数据 20260119 start $update = empty($update) ? [] : array_merge($update,['am_id' => empty($data['am_id']) ? 0 : $data['am_id']]); @@ -1155,6 +1168,121 @@ class Preaccept extends Base //返回更新数据 20260119 end } + /** + * 是否发生 删除(new 相对 old 少了任意引用 id) + */ + private function hasMyciteDeletion(string $oldContent, string $newContent): bool + { + $oldIds = $this->extractMyciteIds($oldContent); + if (empty($oldIds)) { + return false; + } + $newIds = $this->extractMyciteIds($newContent); + + // old 有引用,new 为空 => 删除 + if (empty($newIds)) { + return true; + } + $oldSet = array_fill_keys($oldIds, 1); + $newSet = array_fill_keys($newIds, 1); + foreach ($oldSet as $id => $_) { + if (!isset($newSet[$id])) { + return true; + } + } + return false; + } + + /** + * 从文本/HTML 中提取 的 id 列表 + * + * @return int[] + */ + private function extractMyciteIds(string $text): array + { + if ($text === '') return []; + $ids = []; + if (preg_match_all('/<\s*mycite\b[^>]*\bdata-id\s*=\s*(["\'])(.*?)\1[^>]*>/iu', $text, $m)) { + foreach ($m[2] as $raw) { + $raw = trim((string)$raw); + if ($raw === '') continue; + $parts = preg_split('/\s*,\s*/', $raw); + foreach ($parts as $p) { + $p = trim((string)$p); + if ($p === '') continue; + $v = intval($p); + if ($v > 0) $ids[] = $v; + } + } + } + $ids = array_values(array_unique($ids)); + sort($ids); + return $ids; + } + + /** + * 全文扫描(正文 + table),将 production_article_refer 未被引用的条目标记 is_used=0 + * - 被引用的条目 is_used=1 + * + * 注意:依赖 production_article_refer 表存在 is_used 字段(int/tinyint) + */ + private function markUnusedReferencesForArticle(int $articleId) + { + if ($articleId <= 0) return; + + $production = Db::name('production_article') + ->where('article_id', $articleId) + ->where('state', 0) + ->field('p_article_id') + ->find(); + $pArticleId = intval($production['p_article_id'] ?? 0); + if ($pArticleId <= 0) return; + + // 1) 收集已使用的 p_refer_id + $usedIds = []; + + $mains = Db::name('article_main') + ->where('article_id', $articleId) + ->whereIn('state', [0, 2]) + ->field('content') + ->select(); + foreach ($mains as $row) { + $usedIds = array_merge($usedIds, $this->extractMyciteIds((string)($row['content'] ?? ''))); + } + + $tables = Db::name('article_main_table') + ->where('article_id', $articleId) + ->where('state', 0) + ->field('table_data,html_data,title,note') + ->select(); + foreach ($tables as $row) { + $usedIds = array_merge($usedIds, $this->extractMyciteIds((string)($row['table_data'] ?? ''))); + $usedIds = array_merge($usedIds, $this->extractMyciteIds((string)($row['html_data'] ?? ''))); + $usedIds = array_merge($usedIds, $this->extractMyciteIds((string)($row['title'] ?? ''))); + $usedIds = array_merge($usedIds, $this->extractMyciteIds((string)($row['note'] ?? ''))); + } + + $usedIds = array_values(array_unique($usedIds)); + + // 2) 标记:先全置 0,再把用到的置 1 + try { + Db::name('production_article_refer') + ->where('p_article_id', $pArticleId) + ->where('state', 0) + ->update(['is_used' => 0, 'update_time' => time()]); + + if (!empty($usedIds)) { + Db::name('production_article_refer') + ->where('p_article_id', $pArticleId) + ->where('state', 0) + ->whereIn('p_refer_id', $usedIds) + ->update(['is_used' => 1, 'update_time' => time()]); + } + } catch (\Exception $e) { + // 工具方法:不影响主流程,忽略异常(可按需改为记录日志) + } + } + public function getArticleMainsRecycle(){ $data = $this->request->post(); $rule = new Validate([ diff --git a/application/api/controller/References.php b/application/api/controller/References.php index 8371a00..c528456 100644 --- a/application/api/controller/References.php +++ b/application/api/controller/References.php @@ -526,31 +526,40 @@ class References extends Base * - type (可选):默认 0(仅文本 main),传空则处理所有 type * - dry_run (可选):1=只预览不落库 */ - public function convertArticleMainCitationsToMycite($aParam = []) + public function convertArticleMainCitationsToMycite() { - $aParam = empty($aParam) ? $this->request->post() : $aParam; - $pArticleId = intval($aParam['p_article_id'] ?? 0); - if ($pArticleId <= 0) { - return jsonError('p_article_id is required'); - } +// $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'); +// } - // 通过 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); + $aParam = $this->request->post(); + $rule = new Validate([ + "article_id"=>"require" + ]); + if(!$rule->check($aParam)){ + return jsonError($rule->getError()); } - $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; + $type = $aParam['type'] ?? 0; + $p_info = $this->production_article_obj->where('article_id', $aParam['article_id'])->where('state', 0)->find(); + $pArticleId = $p_info['p_article_id']; $query = Db::name('article_main') - ->where('article_id', $articleId) + ->where('article_id', $aParam['article_id']) ->whereIn('state', [0, 2]) ->order('sort asc'); if ($type !== '' && $type !== null) { @@ -593,8 +602,7 @@ class References extends Base ->where('am_id', $amId) ->limit(1) ->update([ - 'content' => $new, - 'update_time' => time(), + 'content' => $new ]); } } @@ -610,7 +618,7 @@ class References extends Base } return jsonSuccess([ - 'article_id' => $articleId, + 'article_id' => $aParam['article_id'], 'p_article_id' => $pArticleId, 'dry_run' => $dryRun ? 1 : 0, 'total' => count($mains), @@ -619,6 +627,97 @@ class References extends Base ]); } + /** + * 批量更新 production_article_refer + * + * 参数: + * - list(必填):数组,每项至少含 p_refer_id,其余为可更新字段 + * - p_article_id(可选):若传则校验每条记录均属该生产文章,防止误改 + * + * 可更新字段(白名单):author,title,joura,dateno,doilink,doi,refer_doi,refer_content,refer_frag, + * refer_type,isbn,index,is_change,is_ai_check,cs,is_ja,article_id + */ + public function batchUpdateRefer($aParam = []) + { + $aParam = empty($aParam) ? $this->request->post() : $aParam; + $list = isset($aParam['list']) ? $aParam['list'] : (isset($aParam['refer_list']) ? $aParam['refer_list'] : null); + if (is_string($list)) { + $list = json_decode($list, true); + } + if (!is_array($list) || $list === []) { + return jsonError('list is required and must be a non-empty array'); + } + + $pArticleIdCheck = isset($aParam['p_article_id']) ? intval($aParam['p_article_id']) : 0; + + $allowed = [ + 'author', 'title', 'joura', 'dateno', 'doilink', 'doi', 'refer_doi', + 'refer_content', 'refer_frag', 'refer_type', 'isbn', 'index', + 'is_change', 'is_ai_check', 'cs', 'is_ja', 'article_id', + ]; + + $ok = 0; + $failed = []; + + Db::startTrans(); + try { + foreach ($list as $idx => $row) { + if (!is_array($row)) { + $failed[] = ['index' => $idx, 'msg' => 'item must be object']; + continue; + } + $pReferId = intval(isset($row['p_refer_id']) ? $row['p_refer_id'] : 0); + if ($pReferId <= 0) { + $failed[] = ['index' => $idx, 'msg' => 'p_refer_id is required']; + continue; + } + + $where = ['p_refer_id' => $pReferId, 'state' => 0]; + $exist = Db::name('production_article_refer')->where($where)->find(); + if (empty($exist)) { + $failed[] = ['p_refer_id' => $pReferId, 'msg' => 'reference not found or state!=0']; + continue; + } + if ($pArticleIdCheck > 0 && intval($exist['p_article_id']) !== $pArticleIdCheck) { + $failed[] = ['p_refer_id' => $pReferId, 'msg' => 'p_article_id mismatch']; + continue; + } + + $update = []; + foreach ($allowed as $field) { + if (array_key_exists($field, $row)) { + $update[$field] = $row[$field]; + } + } + if ($update === []) { + $failed[] = ['p_refer_id' => $pReferId, 'msg' => 'no updatable fields']; + continue; + } + $update['update_time'] = time(); + if (!isset($update['is_change'])) { + $update['is_change'] = 1; + } + + $result = Db::name('production_article_refer')->where($where)->limit(1)->update($update); + if ($result === false) { + $failed[] = ['p_refer_id' => $pReferId, 'msg' => 'update failed']; + continue; + } + $ok++; + } + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + return jsonError('batch update failed: ' . $e->getMessage()); + } + + return jsonSuccess([ + 'updated' => $ok, + 'failed' => $failed, + 'total' => count($list), + ]); + } + /** * 修改参考文献的信息 * @param p_refer_id 主键ID