605 lines
21 KiB
PHP
605 lines
21 KiB
PHP
<?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' => []
|
||
]);
|
||
}
|
||
}
|
||
} |