tp/app/admin/controller/Article/ArticleController.php
2026-01-26 09:29:36 +08:00

605 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare (strict_types = 1);
namespace app\admin\controller\Article;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Request;
use think\facade\Session;
use think\response\Json;
use think\db\exception\DbException;
class ArticleController extends BaseController
{
/**
* 获取文章列表
* @param string $keyword 文章标题关键词(默认空)
* @param string $cate 文章分类(默认空)
* @param int $page 页码默认1
* @param int $pageSize 每页条数默认10
* @return Json
*/
public function getArticlesList(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)
->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' => []
]);
}
}
}