$keyword, 'cate' => $cate, 'page' => $page, 'pageSize' => $pageSize ], 'debug'); // 构建查询条件,合并表连接 $query = Db::name('mete_articles')->alias('a') ->where('a.delete_time', null) ->leftJoin('mete_admin_user u', 'a.publisher = u.id') ->leftJoin('mete_articles_category c', 'a.cate = c.id'); // 关键词筛选 if ($keyword) { $query->where('a.title', 'like', '%' . $keyword . '%'); } // 分类筛选 if ($cate) { $query->where('a.cate', $cate); } // 获取总条数 $total = $query->count(); // 执行分页查询,合并order语句 $articles = $query ->field('a.id, a.title, c.name as cate, a.author, a.status, a.views, a.likes, a.publishdate,a.recommend,a.top,a.update_time, u.name as publisher') ->order('a.status, a.top desc, a.recommend desc, a.sort desc, a.id desc') ->page($page, $pageSize) ->select() ->toArray(); return json([ 'code' => 200, 'msg' => 'success', 'data' => [ 'list' => $articles, 'total' => $total, 'page' => $page, 'pageSize' => $pageSize ] ]); } catch (\Exception $e) { // 简化异常处理 trace('文章列表查询失败: ' . $e->getMessage(), 'error'); return json([ 'code' => 500, 'msg' => '文章列表查询失败,请稍后重试', 'data' => ['list' => [], 'total' => 0, 'page' => $page, 'pageSize' => $pageSize] ]); } } /** * 检查标题相似度 * @param string $title 新标题 * @return array|null 相似文章列表,null表示没有相似标题 */ private function checkTitleSimilarity(string $title) { $newTitleLower = strtolower($title); $newTitleLength = strlen($newTitleLower); // 获取所有文章标题 $allTitles = Db::name('mete_articles') ->field('id, title') ->select(); $similarArticles = []; // 计算每个标题的相似度 foreach ($allTitles as $article) { $existingTitleLower = strtolower($article['title']); $existingTitleLength = strlen($existingTitleLower); // 使用Levenshtein距离计算相似度 $distance = levenshtein($newTitleLower, $existingTitleLower); $maxLength = max($newTitleLength, $existingTitleLength); $similarity = $maxLength > 0 ? (1 - $distance / $maxLength) * 100 : 0; // 相似度超过50%才返回 if ($similarity >= 50) { $similarArticles[] = [ 'id' => $article['id'], 'title' => $article['title'], 'similarity' => round($similarity, 2) // 保留两位小数 ]; } } // 按相似度降序排序 usort($similarArticles, function($a, $b) { return $b['similarity'] - $a['similarity']; }); return !empty($similarArticles) ? $similarArticles : null; } public function createArticle(): Json { try { $data = request()->only(['title','cate','image','desc','author','content','sort','status','is_trans','transurl','ignore_similarity']); // 基础校验 if (empty($data['title'])) { throw new ValidateException('文章标题不能为空'); } elseif (empty($data['cate'])) { throw new ValidateException('请选择文章分类'); } elseif (empty($data['author'])) { throw new ValidateException('请填写作者'); } elseif (empty($data['content'])) { throw new ValidateException('请填写内容'); } // 标题相似度检测(仅新增时检测) if (empty($data['ignore_similarity'])) { $similarArticles = $this->checkTitleSimilarity($data['title']); if ($similarArticles) { return json([ 'code' => 409, 'msg' => '检测到相似标题', 'data' => [ 'similar_articles' => $similarArticles ] ]); } } // 获取当前登录用户ID $userId = Request::middleware('user_id', ''); // 移除 ignore_similarity,不保存到数据库 unset($data['ignore_similarity']); // 准备写入数据库的数据 $insertData = [ 'title' => $data['title'], 'cate' => $data['cate'], 'image' => $data['image'] ?? '', 'desc' => $data['desc'] ?? '', 'author' => $data['author'], 'content' => $data['content'], 'sort' => isset($data['sort']) ? (int)$data['sort'] : 0, 'status' => isset($data['status']) ? (int)$data['status'] : 0, 'is_trans' => $data['is_trans'] ?? 0, 'transurl' => $data['is_trans'] == 1 ? ($data['transurl'] ?? '') : '', 'publisher' => $userId, 'create_time' => date('Y-m-d H:i:s'), ]; $id = Db::name('mete_articles')->insertGetId($insertData); // 记录成功日志 $this->logSuccess('文章管理', '创建文章', ['id' => $id]); return json(['code'=>200,'msg'=>'success','data'=>['id'=>$id]]); } catch (ValidateException $ve) { return json(['code'=>422,'msg'=>$ve->getMessage(),'data'=>null]); } catch (\Exception $e) { // 记录失败日志 $this->logFail('文章管理', '创建文章', $e->getMessage()); return json(['code'=>500,'msg'=>'fail:'.$e->getMessage(),'data'=>$e->getTraceAsString()]); } } /** * 获取文章详情 * @param int $id 文章ID * @return Json */ public function getArticle(int $id): Json { try { $article = Db::name('mete_articles') ->where('id', $id) ->field('id,title,cate,image,desc,author,content,is_trans,transurl,views,likes,publisher,create_time,publishdate,update_time') ->find(); if (!$article) { return json(['code' => 404, 'msg' => '文章不存在', 'data' => null]); } return json(['code' => 200, 'msg' => 'success', 'data' => $article]); } catch (\Exception $e) { return json(['code' => 500, 'msg' => 'fail:' . $e->getMessage(), 'data' => $e->getTraceAsString()]); } } /** * 编辑文章 * @param int $id * @return Json */ public function editArticle(int $id): Json { try { $data = request()->only(['title','cate','image','desc','author','content','sort','status','is_trans','transurl']); if (empty($data)) { return json(['code'=>400,'msg'=>'无更新数据','data'=>null]); } $data['update_time'] = date('Y-m-d H:i:s'); $affected = Db::name('mete_articles')->where('id',$id)->update($data); if ($affected) { return json(['code'=>200,'msg'=>'success','data'=>null]); } // 记录成功日志 $this->logSuccess('文章管理', '编辑文章', ['id' => $id]); return json(['code'=>404,'msg'=>'not found','data'=>null]); } catch (\Exception $e) { // 记录失败日志 $this->logFail('文章管理', '编辑文章', $e->getMessage()); return json(['code'=>500,'msg'=>'fail:'.$e->getMessage(),'data'=>$e->getTraceAsString()]); } } /** * 删除文章分类(软删除) * @param int $id * @return Json */ public function deleteArticle(int $id): Json { try { $affected = Db::name('mete_articles') ->where('id', $id) ->update(['delete_time' => date('Y-m-d H:i:s')]); if ($affected) { // 记录成功日志 $this->logSuccess('文章管理', '删除文章', ['id' => $id]); return json(['code' => 200, 'msg' => 'success', 'data' => null]); } return json(['code' => 404, 'msg' => 'not found', 'data' => null]); } catch (\Exception $e) { // 记录失败日志 $this->logFail('文章管理', '删除文章', $e->getMessage()); return json([ 'code' => 500, 'msg' => 'fail:' . $e->getMessage(), 'data' => $e->getTraceAsString() ]); } } /** * 更新分类状态 * @param int $id * @return Json */ public function updateCategoryStatus(int $id): Json { try { $data = request()->only(['status']); if (empty($data)) { return json(['code'=>400,'msg'=>'无更新数据','data'=>null]); } $data['update_time'] = date('Y-m-d H:i:s'); $affected = Db::name('mete_articles')->where('id',$id)->update($data); if ($affected) { // 记录成功日志 $this->logSuccess('文章管理', '更新文章分类状态', ['id' => $id]); return json(['code'=>200,'msg'=>'success','data'=>null]); } return json(['code'=>404,'msg'=>'not found','data'=>null]); } catch (\Exception $e) { // 记录失败日志 $this->logFail('文章管理', '更新文章分类状态', $e->getMessage()); return json(['code'=>500,'msg'=>'fail:'.$e->getMessage(),'data'=>$e->getTraceAsString()]); } } /** * 审核发布文章 * @param int $id 文章ID * @return Json */ public function publishArticle(int $id): Json { try { // 获取请求对象 $request = Request::instance(); $uid = $request->post('uid'); $currentTime = date('Y-m-d H:i:s'); if (!isset($uid) || empty($uid)){ return json(['code'=>400,'msg'=>'用户ID不能为空','data'=>null]); }else{ $user_id = $uid; } $data = [ 'status' => 2, // 已发布状态 'publishdate' => $currentTime, 'update_time' => $currentTime, 'publisher' => $user_id, ]; $affected = Db::name('mete_articles') ->where('id', $id) ->update($data); if ($affected) { // 记录成功日志 $this->logSuccess('文章管理', '审核发布文章', ['id' => $id]); return json(['code' => 200, 'msg' => '发布成功', 'data' => null]); } return json(['code' => 404, 'msg' => '文章不存在或更新失败', 'data' => null]); } catch (\Exception $e) { // 记录失败日志 $this->logFail('文章管理', '审核发布文章', $e->getMessage()); return json([ 'code' => 500, 'msg' => '发布失败:' . $e->getMessage(), 'data' => null ]); } } /** * 下架文章 * @param int $id 文章ID * @return Json */ public function unPublishArticle(int $id): Json { try { $currentTime = date('Y-m-d H:i:s'); $data = [ 'status' => 1, // 已发布状态 'publishdate' => null, 'update_time' => $currentTime ]; $affected = Db::name('mete_articles') ->where('id', $id) ->update($data); if ($affected) { // 记录成功日志 $this->logSuccess('文章管理', '下架文章', ['id' => $id]); return json(['code' => 200, 'msg' => '下架成功', 'data' => null]); } return json(['code' => 404, 'msg' => '文章不存在或更新失败', 'data' => null]); } catch (\Exception $e) { // 记录失败日志 $this->logFail('文章管理', '下架文章', $e->getMessage()); return json([ 'code' => 500, 'msg' => '发布失败:' . $e->getMessage(), 'data' => null ]); } } /** * 文章推荐 * @param int $id 文章ID * @return Json */ public function articleRecommend(int $id): Json { try { $data = [ 'recommend' => 1, // 推荐状态 ]; $affected = Db::name('mete_articles') ->where('id', $id) ->update($data); if ($affected) { // 记录成功日志 $this->logSuccess('文章管理', '文章推荐', ['id' => $id]); return json(['code' => 200, 'msg' => '推荐成功', 'data' => null]); } return json(['code' => 404, 'msg' => '文章不存在或更新失败', 'data' => null]); } catch (\Exception $e) { // 记录失败日志 $this->logFail('文章管理', '文章推荐', $e->getMessage()); return json([ 'code' => 500, 'msg' => '推荐失败:' . $e->getMessage(), 'data' => null ]); } } /** * 文章取消推荐 * @param int $id 文章ID * @return Json */ public function unArticleRecommend(int $id): Json { try { $data = [ 'recommend' => 0, // 取消推荐状态 ]; $affected = Db::name('mete_articles') ->where('id', $id) ->update($data); if ($affected) { return json(['code' => 200, 'msg' => '取消推荐成功', 'data' => null]); } return json(['code' => 404, 'msg' => '文章不存在或更新失败', 'data' => null]); } catch (\Exception $e) { return json([ 'code' => 500, 'msg' => '取消推荐失败:' . $e->getMessage(), 'data' => null ]); } } /** * 文章置顶 * @param int $id 文章ID * @return Json */ public function articleTop(int $id): Json { try { $data = [ 'top' => 1, // 置顶状态 ]; $affected = Db::name('mete_articles') ->where('id', $id) ->update($data); if ($affected) { return json(['code' => 200, 'msg' => '置顶成功', 'data' => null]); } return json(['code' => 404, 'msg' => '文章不存在或更新失败', 'data' => null]); } catch (\Exception $e) { return json([ 'code' => 500, 'msg' => '置顶失败:' . $e->getMessage(), 'data' => null ]); } } /** * 文章取消置顶 * @param int $id 文章ID * @return Json */ public function unArticleTop(int $id): Json { try { $data = [ 'top' => 0, // 取消置顶状态 ]; $affected = Db::name('mete_articles') ->where('id', $id) ->update($data); if ($affected) { return json(['code' => 200, 'msg' => '取消置顶成功', 'data' => null]); } return json(['code' => 404, 'msg' => '文章不存在或更新失败', 'data' => null]); } catch (\Exception $e) { return json([ 'code' => 500, 'msg' => '取消置顶失败:' . $e->getMessage(), 'data' => null ]); } } /** * 获取文章列表接口(带条件筛选和分页) * @param string $keyword 文章标题关键词(默认空) * @param string $cate 文章分类(默认空) * @param int $page 页码(默认1) * @param int $pageSize 每页条数(默认10) * @return Json */ public function getAllArticles(string $keyword = '', string $cate = '', int $page = 1, int $pageSize = 10): Json { try { // 安全处理参数(防止非法参数) $page = max(1, $page); // 页码最小为1 $pageSize = max(1, min(100, $pageSize)); // 每页条数限制1-100条 $keyword = trim($keyword); $cate = trim($cate); // 输出参数值 trace('查询参数:', 'debug'); trace([ 'keyword' => $keyword, 'cate' => $cate, 'page' => $page, 'pageSize' => $pageSize ], 'debug'); // 构建查询条件 $query = Db::name('mete_articles') ->alias('a') ->where('a.delete_time', null); // 关键词筛选(标题模糊匹配) if (!empty($keyword)) { $query->where('a.title', 'like', '%' . $keyword . '%'); } // 分类筛选 if (!empty($cate)) { $query->where('a.cate', $cate); } // 克隆查询对象用于获取总数 $countQuery = clone $query; // 获取总条数(必须在分页前获取) $total = $countQuery->count(); // 执行分页查询 $articles = $query ->alias('a') ->leftJoin('mete_admin_user u', 'a.publisher = u.id') ->leftJoin('mete_articles_category c', 'a.cate = c.id') ->field('a.*, u.name as publisher, c.name as cate') ->order('a.status', 'asc') ->order('a.top', 'desc') ->order('a.recommend', 'desc') ->order('a.sort', 'desc') ->order('a.id', 'desc') ->page($page, $pageSize) ->select() ->toArray(); return json([ 'code' => 200, 'msg' => 'success', 'data' => [ 'list' => $articles, // 分页数据列表 'total' => $total, // 总条数 'page' => $page, // 当前页码 'pageSize' => $pageSize // 每页条数 ] ]); } catch (DbException $e) { // 数据库异常,记录详细日志但返回友好提示 trace('文章查询失败: ' . $e->getMessage() . ' | ' . $e->getTraceAsString(), 'error'); return json([ 'code' => 500, 'msg' => '文章列表查询失败,请稍后重试', 'data' => [] ]); } catch (\Exception $e) { // 其他异常处理 trace('系统异常: ' . $e->getMessage() . ' | ' . $e->getTraceAsString(), 'error'); return json([ 'code' => 500, 'msg' => '服务器内部错误,请稍后重试', 'data' => [] ]); } } }