// +---------------------------------------------------------------------- declare (strict_types = 1); namespace addon\articles\controller\api; // 注意:本类就叫 Article,模型必须起别名,否则「类名已被占用」致命错误 use addon\articles\model\Article as ArticleModel; use addon\articles\model\ArticleCategory; use addon\articles\model\ArticleComment; use addon\articles\model\ArticleTag; use think\facade\Cache; use ywxapp\controller\ApiBase; use ywxapp\service\AddonService; /** * 文章CMS 对外 API * * 鉴权:继承 ApiBase(默认需登录);列表/详情/分类/标签/搜索放行游客, * 评论必须登录($noNeedLogin 未包含 comment)。 * * URL:/articles/api/articles 等(见 route/app.php) */ class Article extends ApiBase { /** 免登录方法(评论不在其中,必须登录) */ protected $noNeedLogin = ['index', 'read', 'categories', 'tags', 'search']; /** * 文章列表 * 参数:page / cid / tid */ public function index() { $page = (int) input('page/d', 1); $cid = (int) input('cid/d', 0); $tid = (int) input('tid/d', 0); $where = []; if ($cid > 0) { $where['cid'] = $cid; } if ($tid > 0) { $where['tid'] = $tid; } $list = ArticleModel::listArticles($where, $page, $this->pageSize()); $rows = []; foreach ($list->items() as $a) { $rows[] = [ 'id' => $a->id, 'title' => $a->title, 'summary' => $a->summary, 'cover_image' => $a->cover_image, 'cid' => $a->cid, 'category' => $a->category_name ?? '', 'view_count' => (int) $a->view_count, 'comment_count' => (int) $a->comment_count, 'create_at' => $a->create_at, ]; } $this->apiSuccess($rows, 'success', $list->total()); } /** * 文章详情(正文已在入库时做过白名单净化,可安全下发) */ public function read($id = 0) { $id = (int) ($id ?: input('id/d', 0)); $article = ArticleModel::getDetail($id); if (!$article) { $this->apiError('文章不存在或已下线', 404, null, 404); } ArticleModel::where('id', $id)->inc('view_count')->update(); $tags = []; foreach ($article->tags as $t) { $tags[] = ['id' => $t->id, 'name' => $t->name]; } $this->apiSuccess([ 'id' => $article->id, 'title' => $article->title, 'content' => $article->content, 'summary' => $article->summary, 'cover_image' => $article->cover_image, 'author' => $article->author, 'cid' => $article->cid, 'category' => $article->category_name ?? '', 'view_count' => (int) $article->view_count, 'comment_count' => (int) $article->comment_count, 'create_at' => $article->create_at, 'tags' => $tags, ]); } /** * 分类列表 */ public function categories() { $rows = []; foreach (ArticleCategory::allEnable() as $c) { $rows[] = [ 'id' => $c->id, 'name' => $c->name, 'alias' => $c->alias, 'description' => $c->description, ]; } $this->apiSuccess($rows, 'success', count($rows)); } /** * 标签列表 */ public function tags() { $rows = []; foreach (ArticleTag::where('delete_at', 0)->order('id', 'desc')->select() as $t) { $rows[] = ['id' => $t->id, 'name' => $t->name, 'alias' => $t->alias]; } $this->apiSuccess($rows, 'success', count($rows)); } /** * 搜索 */ public function search() { $keyword = trim((string) input('keyword/s', '')); $page = (int) input('page/d', 1); $list = ArticleModel::listArticles(['keyword' => $keyword], $page, $this->pageSize()); $rows = []; foreach ($list->items() as $a) { $rows[] = [ 'id' => $a->id, 'title' => $a->title, 'summary' => $a->summary, 'cid' => $a->cid, 'category' => $a->category_name ?? '', 'create_at' => $a->create_at, ]; } $this->apiSuccess($rows, 'success', $list->total()); } /** * 发表评论(需登录) */ public function comment() { $aid = (int) input('aid/d', 0); $content = trim((string) input('content/s', '')); if ($aid <= 0 || $content === '') { $this->apiError('参数错误'); } $len = mb_strlen($content, 'UTF-8'); if ($len < 2) { $this->apiError('评论内容太短'); } if ($len > 500) { $this->apiError('评论内容不能超过 500 字'); } // 目标文章必须存在且已发布 $article = ArticleModel::where('id', $aid)->where('status', 1)->where('delete_at', 0)->find(); if (!$article) { $this->apiError('文章不存在或未发布'); } $cfg = AddonService::config('articles'); $needAudit = !empty($cfg['comment_audit']); $interval = max(0, (int) ($cfg['comment_interval'] ?? 10)); $uid = (int) $this->uid(); $lockKey = 'articles_comment_lock:' . $uid; if ($interval > 0 && Cache::get($lockKey, 0)) { $this->apiError('评论太频繁,请稍后再试'); } ArticleComment::create([ 'uid' => $uid, 'aid' => $aid, 'pid' => 0, 'content' => $content, 'status' => $needAudit ? 0 : 1, 'ip' => (string) $this->request->ip(), 'user_agent' => mb_substr((string) $this->request->header('user-agent', ''), 0, 255), 'create_at' => time(), 'update_at' => time(), ]); if (!$needAudit) { ArticleModel::where('id', $aid)->inc('comment_count')->update(); } if ($interval > 0) { Cache::set($lockKey, 1, $interval); } $this->apiSuccess(null, $needAudit ? '评论已提交,等待审核' : '评论成功'); } /** * 每页条数(取自插件配置) */ private function pageSize(): int { $cfg = AddonService::config('articles'); $size = (int) ($cfg['page_size'] ?? 10); return $size > 0 ? min($size, 50) : 10; } }