first commot

This commit is contained in:
2026-01-26 09:29:36 +08:00
commit dbb40c38c4
189 changed files with 20198 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
<?php
declare (strict_types = 1);
namespace app\admin;
use think\App;
use think\exception\ValidateException;
use think\Validate;
use think\facade\Request;
use think\facade\Cache;
use app\admin\controller\OperationLog\OperationLogger;
use app\service\JwtService;
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* Request实例
* @var \think\Request
*/
protected $request;
/**
* 应用实例
* @var \think\App
*/
protected $app;
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [];
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->app = $app;
$this->request = $this->app->request;
// 控制器初始化
$this->initialize();
}
// 初始化
protected function initialize()
{}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, string|array $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
// 支持场景
[$validate, $scene] = explode('.', $validate);
}
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
$v = new $class();
if (!empty($scene)) {
$v->scene($scene);
}
}
$v->message($message);
// 是否批量验证
if ($batch || $this->batchValidate) {
$v->batch(true);
}
return $v->failException(true)->check($data);
}
/**
* 记录操作日志
*
* @param string $module 模块名称(如:文件管理)
* @param string $action 操作名称(如:创建文件分组)
* @param array $responseData 响应数据(可选)
* @param array $userInfo 用户信息(可选,如果不提供则从 Session 获取)
* @return bool
*/
protected function logSuccess(string $module, string $action, array $responseData = [], array $userInfo = []): bool
{
return OperationLogger::success($module, $action, $responseData, $userInfo);
}
/**
* 记录失败日志
*
* @param string $module 模块名称
* @param string $action 操作名称
* @param string $errorMessage 错误信息
* @param array $userInfo 用户信息(可选,如果不提供则从 Session 获取)
* @return bool
*/
protected function logFail(string $module, string $action, string $errorMessage, array $userInfo = []): bool
{
return OperationLogger::fail($module, $action, $errorMessage, $userInfo);
}
/**
* 记录操作日志
*
* @param string $module 操作模块
* @param string $action 操作动作
* @param array $requestData 请求数据(为空则自动获取)
* @param array $responseData 响应数据
* @param int $status 操作状态:1-成功,0-失败
* @param string $errorMessage 错误信息
* @return bool
*/
protected function logOperation(
string $module,
string $action,
array $requestData = [],
array $responseData = [],
int $status = 1,
string $errorMessage = ''
): bool {
return OperationLogger::record(
$module,
$action,
$requestData,
$responseData,
$status,
$errorMessage
);
}
/**
* 获取当前登录管理员信息
*
* @return array 用户信息数组,包含 id、account、name 等字段
*/
protected function getAdminUserInfo(): array
{
return JwtService::getUserFromHeader($this->request->header('Authorization', ''));
}
}
+2
View File
@@ -0,0 +1,2 @@
<?php
// 这是系统自动生成的公共文件
@@ -0,0 +1,197 @@
<?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\Session;
use think\response\Json;
use think\db\exception\DbException;
class ArticleCategoryController extends BaseController
{
/**
* 获取文章分类列表接口
* @return \think\response\Json
*/
public function getAllArticleCategories(string $keyword)
{
try {
//判断keyword是否存在
$keyword = $keyword ?? '';
// 获取文章分类列表
$categories = Db::name('mete_articles_category')
->where('delete_time', null)
->where('status', 1)
->where('name', 'like', '%' . $keyword . '%')
->order('sort', 'desc')
->order('id', 'asc')
->select()
->toArray(); // 转换为数组
return json([
'code' => 200,
'msg' => 'success',
'data' => $categories
]);
} catch (DbException $e) {
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => $e->getTraceAsString()
]);
}
}
/**
* 获取文章分类列表接口
* @return \think\response\Json
*/
public function getArticleCategories()
{
try {
// 获取文章分类列表
$categories = Db::name('mete_articles_category')
->where('delete_time', null)
->where('status', 1)
->field('id,cid,name,image,sort')
->order('sort', 'desc')
->order('id', 'asc')
->select()
->toArray(); // 转换为数组
return json([
'code' => 200,
'msg' => 'success',
'data' => $categories
]);
} catch (DbException $e) {
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => $e->getTraceAsString()
]);
}
}
/**
* 新增文章分类
* @return Json
*/
public function createCategory(): Json
{
try {
$data = request()->only(['cid','name','image','desc','sort','status']);
// 基础校验
if (empty($data['name'])) {
throw new ValidateException('分类名称不能为空');
}
$data['cid'] = isset($data['cid']) ? (int)$data['cid'] : 0;
$data['sort'] = isset($data['sort']) ? (int)$data['sort'] : 0;
$data['status']= isset($data['status']) ? (int)$data['status'] : 1;
$data['create_time'] = date('Y-m-d H:i:s');
$data['update_time'] = $data['create_time'];
$id = Db::name('mete_articles_category')->insertGetId($data);
return json(['code'=>200,'msg'=>'success','data'=>['id'=>$id]]);
} catch (ValidateException $ve) {
// 记录失败日志
$this->logFail('文章分类管理', '新增文章分类', $ve->getMessage());
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
* @return Json
*/
public function editCategory(int $id): Json
{
try {
$data = request()->only(['cid','name','image','desc','sort','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_category')->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
* @return Json
*/
public function deleteCategory(int $id): Json
{
try {
$affected = Db::name('mete_articles_category')
->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['status'] = isset($data['status']) ? (int)$data['status'] : 1;
$data['update_time'] = date('Y-m-d H:i:s');
$affected = Db::name('mete_articles_category')->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()]);
}
}
}
@@ -0,0 +1,605 @@
<?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' => []
]);
}
}
}
+232
View File
@@ -0,0 +1,232 @@
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Session;
use think\response\Json;
use think\db\exception\DbException;
use app\model\Banner;
class BannerController extends BaseController
{
/**
* 获取所有Banner信息
* @return Json
*/
public function getAllBanners()
{
try {
$banners = Banner::where('delete_time', null)
->order('sort', 'asc')
->field('id, title, desc, url, image, sort, create_time, update_time')
->select()
->toArray();
// 记录操作日志
$this->logSuccess('Banner管理', '获取所有Banner', ['data' => $banners]);
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $banners
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('Banner管理', '获取所有Banner', $e->getMessage());
return json([
'code' => 500,
'msg' => '获取失败:' . $e->getMessage(),
'data' => []
]);
}
}
/**
* 创建Banner
* @return Json
*/
public function createBanner()
{
try {
// 获取请求参数
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'title|标题' => 'require|max:255',
'desc|简介' => 'max:65535',
'url|跳转地址' => 'max:65535',
'image|图片地址' => 'max:65535',
'sort|排序号' => 'integer',
]);
// 准备Banner数据
$bannerData = [
'title' => $data['title'],
'desc' => $data['desc'] ?? '',
'url' => $data['url'] ?? '',
'image' => $data['image'] ?? '',
'sort' => $data['sort'] ?? 0,
'create_time' => date('Y-m-d H:i:s'),
];
// 创建Banner
$result = Banner::insertGetId($bannerData);
if ($result === false) {
return json([
'code' => 500,
'msg' => '创建失败'
]);
}
// 记录操作日志
$this->logSuccess('Banner管理', '创建Banner', ['id' => $result]);
return json([
'code' => 200,
'msg' => '创建成功',
'data' => ['id' => $result]
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('Banner管理', '创建Banner', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('Banner管理', '创建Banner', $e->getMessage());
return json([
'code' => 500,
'msg' => '创建失败:' . $e->getMessage()
]);
}
}
/**
* 编辑Banner
* @param int $id Banner ID
* @return Json
*/
public function editBanner(int $id)
{
try {
// 获取请求参数
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'title|标题' => 'require|max:255',
'desc|简介' => 'max:65535',
'url|跳转地址' => 'max:65535',
'image|图片地址' => 'max:65535',
'sort|排序号' => 'integer',
]);
// 检查Banner是否存在
$banner = Banner::where('id', $id)
->where('delete_time', null)
->find();
if (!$banner) {
return json([
'code' => 404,
'msg' => 'Banner不存在'
]);
}
// 准备更新数据
$updateData = [
'title' => $data['title'],
'desc' => $data['desc'] ?? null,
'url' => $data['url'] ?? null,
'image' => $data['image'] ?? null,
'sort' => $data['sort'] ?? 0,
'update_time' => date('Y-m-d H:i:s'),
];
// 执行更新
Banner::where('id', $id)->update($updateData);
// 获取更新后的Banner信息
$updatedBanner = Banner::where('id', $id)->find();
// 记录操作日志
$this->logSuccess('Banner管理', '更新Banner', ['id' => $id]);
return json([
'code' => 200,
'msg' => '更新成功',
'data' => $updatedBanner ? $updatedBanner->toArray() : []
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('Banner管理', '更新Banner', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('Banner管理', '更新Banner', $e->getMessage());
return json([
'code' => 500,
'msg' => '更新失败:' . $e->getMessage()
]);
}
}
/**
* 删除Banner
* @param int $id Banner ID
* @return Json
*/
public function deleteBanner(int $id)
{
try {
// 检查Banner是否存在
$banner = Banner::where('id', $id)
->where('delete_time', null)
->find();
if (!$banner) {
return json([
'code' => 404,
'msg' => 'Banner不存在'
]);
}
// 逻辑删除Banner
$result = Banner::where('id', $id)
->where('delete_time', null)
->update([
'delete_time' => time(),
'update_time' => time()
]);
if ($result === false) {
return json([
'code' => 500,
'msg' => '删除失败'
]);
}
// 记录操作日志
$this->logSuccess('Banner管理', '删除Banner', ['id' => $id]);
return json([
'code' => 200,
'msg' => '删除成功'
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('Banner管理', '删除Banner', $e->getMessage());
return json([
'code' => 500,
'msg' => '删除失败:' . $e->getMessage()
]);
}
}
}
+390
View File
@@ -0,0 +1,390 @@
<?php
namespace app\admin\controller;
use app\admin\BaseController;
use think\facade\Filesystem;
use think\facade\Request;
use think\facade\Db;
use think\Response;
use app\model\FilesCategory;
use app\model\Files;
class FileController extends BaseController
{
// 文件类型映射
protected $fileTypes = [
'image' => 1,
'document' => 2,
'video' => 3,
'audio' => 4
];
// 允许的文件类型
protected $allowedExtensions = [
'image' => ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'],
'document' => ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'],
'video' => ['mp4', 'webm', 'mov'],
'audio' => ['mp3', 'wav', 'ogg']
];
// 获取所有文件
public function getAllFiles()
{
try {
$page = Request::param('page/d', 1);
$pageSize = Request::param('pageSize/d', 10);
$cate = Request::param('cate/d', 0);
$keyword = Request::param('keyword/s', '');
$query = Files::where('delete_time', null);
if ($cate) {
$query->where('cate', $cate);
}
if ($keyword) {
$query->whereLike('name', "%{$keyword}%");
}
$total = $query->count();
$list = $query->page($page, $pageSize)
->order('create_time', 'desc')
->select();
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'list' => $list,
'total' => $total,
'page' => $page,
'pageSize' => $pageSize
]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取文件列表失败: ' . $e->getMessage()
]);
}
}
//获取用户分类
public function getUserCate()
{
try {
$cate = FilesCategory::where('delete_time', null)->field('id,name')->select();
// 获取每个分类下的文件数量
foreach ($cate as &$c) {
$c['total'] = Files::where('cate', $c['id'])->where('delete_time', null)->count();
}
return json([
'code' => 200,
'msg' => 'success',
'data' => $cate
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取用户分类失败: ' . $e->getMessage()
]);
}
}
//新建文件分组
public function createFileCate()
{
try {
$data = Request::param();
$data['create_time'] = date('Y-m-d H:i:s');
$id = FilesCategory::insertGetId($data);
// 记录操作日志
$this->logSuccess('文件管理', '创建文件分组', ['id' => $id]);
return json([
'code' => 200,
'msg' => '新建文件分组成功',
'data' => [
'id' => $id
]
]);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '创建文件分组', $e->getMessage());
return json([
'code' => 500,
'msg' => '新建文件分组失败: ' . $e->getMessage()
]);
}
}
//重命名文件分组
public function renameFileCate($id)
{
try {
$data = Request::param();
$result = FilesCategory::where('id', $id)->update(['name' => $data['name']]);
// 记录操作日志
$this->logSuccess('文件管理', '重命名文件分组', ['id' => $id]);
return json([
'code' => 200,
'msg' => '重命名文件分组成功'
]);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '重命名文件分组', $e->getMessage());
return json([
'code' => 500,
'msg' => '重命名文件分组失败: ' . $e->getMessage()
]);
}
}
//删除文件分组
public function deleteFileCate($id)
{
try {
$result = FilesCategory::where('id', $id)->update(['delete_time' => date('Y-m-d H:i:s')]);
// 记录操作日志
$this->logSuccess('文件管理', '删除文件分组', ['id' => $id]);
return json([
'code' => 200,
'msg' => '删除文件分组成功'
]);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '删除文件分组', $e->getMessage());
return json([
'code' => 500,
'msg' => '删除文件分组失败: ' . $e->getMessage()
]);
}
}
//获取分类文件
public function getCateFiles(int $id)
{
try {
$page = Request::param('page/d', 1);
$pageSize = Request::param('pageSize/d', 10);
$keyword = Request::param('keyword/s', '');
$query = Files::where('cate', $id)->where('delete_time', null);
if ($keyword) {
$query->whereLike('name', "%{$keyword}%");
}
$list = $query->page($page, $pageSize)
->order('create_time', 'desc')
->select();
// 格式化文件信息
foreach ($list as &$file) {
$file['createTime'] = $file['create_time'];
$file['groupId'] = $file['cate'];
$file['url'] = $file['src'];
}
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'list' => $list,
'page' => $page,
'pageSize' => $pageSize,
'categoryId' => $id
]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取分类文件失败: ' . $e->getMessage()
]);
}
}
public function uploadFile()
{
try {
$file = Request::file('file');
if (!$file) {
return json(['code' => 400, 'msg' => '请选择要上传的文件']);
}
// 验证文件大小和类型
$maxSize = 50 * 1024 * 1024; // 50MB
$fileExt = strtolower($file->getOriginalExtension());
if ($file->getSize() > $maxSize) {
return json(['code' => 400, 'msg' => '文件大小不能超过50MB']);
}
// 计算文件MD5
$fileMd5 = md5_file($file->getRealPath());
// 检查是否已存在相同文件
$existFile = Files::where('md5', $fileMd5)->where('delete_time', null)->find();
if ($existFile) {
return json([
'code' => 201,
'msg' => '文件已存在',
'data' => [
'url' => $existFile['src'],
'id' => $existFile['id'],
'name' => $existFile['name']
]
]);
}
// 确定文件类型
$fileType = 2; // 默认为文件
foreach ($this->allowedExtensions as $type => $extensions) {
if (in_array($fileExt, $extensions)) {
$fileType = $this->fileTypes[$type];
break;
}
}
$cate = Request::param('cate/d', 0);
// 生成按日期分类的目录结构
$datePath = date('Y/m/d');
$saveName = $datePath . '/' . uniqid() . '.' . $fileExt;
$fullPath = Filesystem::disk('public')->putFileAs('uploads', $file, $saveName);
$fileUrl = '/storage/' . str_replace('\\', '/', $fullPath);
// 获取当前登录用户ID
$userId = Request::middleware('user_id', '');
// 保存文件信息到数据库
$fileData = [
'name' => $file->getOriginalName(),
'type' => $fileType,
'cate' => $cate,
'size' => $file->getSize(),
'src' => $fileUrl,
'md5' => $fileMd5,
'uploader' => $userId,
'create_time' => date('Y-m-d H:i:s'),
];
$fileId = Files::insertGetId($fileData);
// 记录操作日志
$this->logSuccess('文件管理', '上传文件', ['id' => $fileId]);
return json([
'code' => 200,
'msg' => '上传成功',
'data' => [
'url' => $fileUrl,
'id' => $fileId,
'name' => $fileData['name']
]
]);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '上传文件', $e->getMessage());
return json([
'code' => 500,
'msg' => '上传失败: ' . $e->getMessage()
]);
}
}
// 更新文件信息
public function updateFile($id)
{
try {
$data = Request::only(['name', 'cate']);
if (empty($data)) {
return json(['code' => 400, 'msg' => '无更新数据']);
}
$data['update_time'] = date('Y-m-d H:i:s');
$result = Files::where('id', $id)->where('delete_time', null)->update($data);
if ($result) {
// 记录操作日志
$this->logSuccess('文件管理', '更新文件', ['id' => $id]);
return json(['code' => 200, 'msg' => '更新成功']);
}
return json(['code' => 404, 'msg' => '文件不存在']);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '更新文件', $e->getMessage());
return json(['code' => 500, 'msg' => '更新失败: ' . $e->getMessage()]);
}
}
// 删除文件
public function deleteFile($id)
{
try {
$result = Files::where('id', $id)->update(['delete_time' => date('Y-m-d H:i:s')]);
if ($result) {
// 记录操作日志
$this->logSuccess('文件管理', '删除文件', ['id' => $id]);
return json(['code' => 200, 'msg' => '删除成功']);
}
return json(['code' => 404, 'msg' => '文件不存在']);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '删除文件', $e->getMessage());
return json(['code' => 500, 'msg' => '删除失败: ' . $e->getMessage()]);
}
}
// 下载文件
public function download($id)
{
try {
$file = Files::where('id', $id)->where('delete_time', null)->find();
if (!$file) {
return json(['code' => 404, 'msg' => '文件不存在']);
}
$filePath = public_path() . $file['src'];
if (!file_exists($filePath)) {
return json(['code' => 404, 'msg' => '文件不存在']);
}
return download($filePath, $file['name']);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '下载文件', $e->getMessage());
return json(['code' => 500, 'msg' => '下载失败: ' . $e->getMessage()]);
}
}
// 移动文件
public function moveFile($id)
{
try {
$cate = Request::param('cate/d', 0);
$result = Files::where('id', $id)->update(['cate' => $cate]);
if ($result) {
// 记录操作日志
$this->logSuccess('文件管理', '移动文件', ['id' => $id]);
return json(['code' => 200, 'msg' => '移动成功']);
}
return json(['code' => 404, 'msg' => '文件不存在']);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '移动文件', $e->getMessage());
return json(['code' => 500, 'msg' => '移动失败: ' . $e->getMessage()]);
}
}
}
@@ -0,0 +1,261 @@
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Session;
use think\response\Json;
use think\db\exception\DbException;
use app\model\FrontMenu;
class FrontMenuController extends BaseController
{
/**
* 获取前端导航接口
* @return \think\response\Json
*/
public function getFrontMenus()
{
try {
// 获取所有未删除的菜单
$frontMenus = FrontMenu::where('delete_time', null)
->field('id,pid,title,type,image,path,component_path,sort,desc')
->order('sort', 'desc')
->select()
->toArray(); // 转换为数组
// 使用 buildFrontMenuTree 构建树形结构
$treeFrontMenus = $this->buildFrontMenuTree($frontMenus);
return json([
'code' => 200,
'msg' => 'success',
'data' => $treeFrontMenus
]);
} catch (DbException $e) {
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => $e->getTraceAsString()
]);
}
}
/**
* 创建新菜单
* @return \think\response\Json
*/
public function createFrontMenu()
{
try {
// 获取请求参数
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'title|菜单名称' => 'require|max:50',
'pid|上级菜单ID' => 'integer',
'type|菜单类型' => 'require|in:1,2,3,4',
'sort|排序号' => 'integer',
'path|路由路径' => 'max:200',
'component_path|组件路径' => 'max:200',
'desc|描述' => 'max:500',
]);
// 准备菜单数据
$menuData = [
'pid' => $data['pid'] ?? 0,
'title' => $data['title'],
'type' => $data['type'],
'sort' => $data['sort'] ?? 0,
'path' => $data['path'] ?? '',
'component_path' => $data['component_path'] ?? '',
'image' => $data['image'] ?? '',
'desc' => $data['desc'] ?? '',
'create_time' => date('Y-m-d H:i:s')
];
// 创建菜单
$result = FrontMenu::insertGetId($menuData);
if ($result === false) {
return json([
'code' => 500,
'msg' => 'fail'
]);
}
// 记录操作日志
$this->logSuccess('前端导航管理', '创建前端导航', ['id' => $result]);
return json([
'code' => 200,
'msg' => 'success'
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('前端导航管理', '创建前端导航', $e->getMessage());
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage()
]);
}
}
/**
* 编辑前端导航
* @param int $id 前端导航ID
* @return \think\response\Json
*/
public function editFrontMenu(int $id)
{
try {
// 获取请求参数
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'title|菜单名称' => 'require|max:50',
'pid|上级菜单ID' => 'integer',
'type|菜单类型' => 'require|in:1,2,3,4',
'sort|排序号' => 'integer',
'path|路由路径' => 'max:200',
'component_path|组件路径' => 'max:200',
'desc|描述' => 'max:500',
]);
// 准备更新数据
$updateData = [
'title' => $data['title'],
'pid' => $data['pid'] ?? null,
'type' => $data['type'],
'path' => $data['path'] ?? null,
'component_path' => $data['component_path'] ?? null,
'image' => $data['image'] ?? null,
'desc' => $data['desc'] ?? null,
'sort' => $data['sort'] ?? 0,
'update_time' => date('Y-m-d H:i:s')
];
// 执行更新
FrontMenu::where('id', $id)->update($updateData);
// 获取更新后的菜单信息
$menu = FrontMenu::where('id', $id)->find();
// 记录操作日志
$this->logSuccess('前端导航管理', '更新前端导航', ['id' => $id]);
return json([
'code' => 200,
'msg' => 'success',
'data' => $menu ? $menu->toArray() : []
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('前端导航管理', '更新前端导航', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('前端导航管理', '更新前端导航', $e->getMessage());
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => $e->getTraceAsString()
]);
}
}
public function deleteFrontMenu(int $id)
{
try {
// 检查是否有子前端导航
$hasChildren = FrontMenu::where('pid', $id)
->where('pid', $id)
->where('delete_time', null)
->count() > 0;
if ($hasChildren) {
return json([
'code' => 400,
'msg' => '该前端导航下有子导航,无法删除'
]);
}
// 逻辑删除前端导航
$result = FrontMenu::where('id', $id)
->where('id', $id)
->where('delete_time', null)
->update([
'delete_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s')
]);
if ($result === false) {
return json([
'code' => 500,
'msg' => 'fail'
]);
}
// 记录操作日志
$this->logSuccess('前端导航管理', '删除前端导航', ['id' => $id]);
return json([
'code' => 200,
'msg' => 'success'
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('前端导航管理', '删除前端导航', $e->getMessage());
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage()
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('前端导航管理', '删除前端导航', $e->getMessage());
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage()
]);
}
}
/**
* 构建前端导航树形结构
* @param array $frontMenus 前端导航列表
* @param int $pid 父前端导航ID
* @return array
*/
private function buildFrontMenuTree(array $frontMenus, int $pid = 0): array
{
$tree = [];
foreach ($frontMenus as $frontMenu) {
// 将null的pid视为0处理
$menuPid = is_null($frontMenu['pid']) ? 0 : $frontMenu['pid'];
if ($menuPid == $pid) {
$children = $this->buildFrontMenuTree($frontMenus, $frontMenu['id']);
if (!empty($children)) {
$frontMenu['children'] = $children;
}
$tree[] = $frontMenu;
}
}
// 按 sort 字段降序排序
usort($tree, function ($a, $b) {
$sortA = $a['sort'] ?? 0;
$sortB = $b['sort'] ?? 0;
return $sortB - $sortA;
});
return $tree;
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
declare (strict_types = 1);
namespace app\admin\controller;
use app\admin\BaseController;
class Index extends BaseController
{
public function index()
{
return '后端运行中...';
}
public function hello($name = 'ThinkPHP8')
{
return 'hello,' . $name;
}
}
+191
View File
@@ -0,0 +1,191 @@
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Cache;
use think\response\Json;
use app\model\AdminUser;
use app\service\JwtService;
class LoginController extends BaseController
{
private function generateToken($userInfo): string
{
return JwtService::generateToken($userInfo);
}
private function verifyToken($token): ?array
{
return JwtService::verifyToken($token);
}
/**
* 登录接口
* @return Json
*/
public function login(): Json
{
$data = $this->request->param();
if (isset($data['email'])) {
$data['account'] = $data['email'];
} elseif (isset($data['phone'])) {
$data['account'] = $data['phone'];
}
try {
$this->validate($data, [
'account|账号' => 'require|length:3,32',
'password|密码' => 'require|length:6,32'
]);
} catch (ValidateException $e) {
$this->logFail('登录管理', '登录', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
}
$user = AdminUser::where('account', $data['account'])
->where('status', 1)
->where('delete_time', null)
->find();
if (!$user) {
return json([
'code' => 401,
'msg' => '账号不存在或已禁用'
]);
}
if (md5($data['password']) !== $user['password']) {
return json([
'code' => 401,
'msg' => '密码错误'
]);
}
AdminUser::where('id', $user['id'])->update([
'login_count' => $user['login_count'] + 1,
'last_login_ip' => $this->request->ip()
]);
$userInfo = [
'id' => $user['id'],
'account' => $user['account'],
'name' => $user['name'],
'group_id' => $user['group_id']
];
$token = $this->generateToken($userInfo);
$this->logSuccess('登录管理', '登录', ['id' => $user['id']], $userInfo);
return json([
'code' => 200,
'msg' => '登录成功',
'data' => [
'token' => $token,
'user' => $userInfo
]
]);
}
/**
* 退出登录
* @return Json
*/
public function logout(): Json
{
$authHeader = $this->request->header('Authorization', '');
$userInfo = null;
if (preg_match('/Bearer\s+(.+)/i', $authHeader, $matches)) {
$tokenData = $this->verifyToken($matches[1]);
if ($tokenData && isset($tokenData['user'])) {
$userInfo = (array)$tokenData['user'];
}
}
if ($userInfo && isset($userInfo['id'])) {
$this->logSuccess('登录管理', '退出登录', ['result' => 'success'], $userInfo);
} else {
\app\model\OperationLog::create([
'user_id' => 0,
'user_account' => '',
'user_name' => '未知用户',
'module' => '登录管理',
'action' => '退出登录',
'method' => 'POST',
'url' => $this->request->url(true),
'ip' => $this->request->ip(),
'user_agent' => $this->request->header('user-agent', ''),
'request_data' => null,
'response_data' => json_encode(['result' => 'success'], JSON_UNESCAPED_UNICODE),
'status' => 1,
'error_message' => '',
'execution_time' => 0.0,
]);
}
return json([
'code' => 200,
'msg' => '退出成功'
]);
}
/**
* 获取当前登录用户信息
* @return Json
*/
public function userInfo(): Json
{
$authHeader = $this->request->header('Authorization', '');
if (!preg_match('/Bearer\s+(.+)/i', $authHeader, $matches)) {
return json([
'code' => 401,
'msg' => '未登录'
]);
}
$tokenData = $this->verifyToken($matches[1]);
if (!$tokenData || !isset($tokenData['user'])) {
return json([
'code' => 401,
'msg' => 'Token无效'
]);
}
$user = (array)$tokenData['user'];
$user_id = $user['id'];
$userData = AdminUser::where('id', $user_id)
->where('delete_time', null)
->field('id, account, name, phone, qq, sex, group_id, status, create_time, update_time')
->find();
if (!$userData) {
return json([
'code' => 404,
'msg' => '用户不存在'
]);
}
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $userData->toArray()
]);
}
public function getAdminUserFromToken(): array
{
return JwtService::getUserFromHeader($this->request->header('Authorization', ''));
}
}
+418
View File
@@ -0,0 +1,418 @@
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Session;
use think\response\Json;
use think\db\exception\DbException;
use app\model\SystemMenu;
use app\model\AdminUser;
use app\model\AdminUserGroup;
class MenuController extends BaseController
{
/**
* 获取用户菜单接口
* @return \think\response\Json
*/
public function getMenus(int $id)
{
try {
$user = AdminUser::where('id', $id)
->where('id', $id)
->find();
if (!$user) {
return json([
'code' => 404,
'msg' => '用户不存在',
'data' => null
]);
}
// 获取用户组权限信息
$userGroup = AdminUserGroup::where('id', $user['group_id'])
->find();
// var_dump($userGroup);
if (!$userGroup) {
return json([
'code' => 404,
'msg' => '用户组不存在',
'data' => null
]);
}
// 解析权限数组
$menuIds = [];
if (!empty($userGroup['rights'])) {
$menuIds = is_array($userGroup['rights']) ? $userGroup['rights'] : json_decode($userGroup['rights'], true);
}
// var_dump($menuIds);
// 如果权限为空,返回空数组
if (empty($menuIds)) {
return json([
'code' => 200,
'msg' => 'success',
'data' => []
]);
}
// 获取有权限的菜单
$menus = SystemMenu::where('delete_time', null)
->where('status', 1)
->whereIn('id', $menuIds)
->field('id,pid,title,path,component_path,icon,sort')
->order('sort', 'asc')
->select();
// 将菜单转换为树形结构
$treeMenus = $this->buildMenuTree($menus->toArray());
return json([
'code' => 200,
'msg' => 'success',
'data' => $treeMenus
]);
} catch (DbException $e) {
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => $e->getTraceAsString()
]);
}
}
/**
* 获取所有菜单接口
* @return \think\response\Json
*/
public function getAllMenus()
{
try {
// 获取所有未删除的菜单
$menus = SystemMenu::where('delete_time', null)
->order('sort', 'asc')
->select()
->toArray(); // 转换为数组
// 构建树形结构
$tree = [];
$map = [];
// 首先创建所有菜单的映射
foreach ($menus as $menu) {
$menu['children'] = []; // 初始化子菜单数组
$map[$menu['id']] = $menu;
}
// 构建树形结构
foreach ($menus as $menu) {
$pid = $menu['pid'];
if ($pid == 0) {
// 顶级菜单
$tree[] = &$map[$menu['id']];
} else {
// 子菜单
if (isset($map[$pid])) {
$map[$pid]['children'][] = &$map[$menu['id']];
}
}
}
// 递归函数:对所有层级的菜单按 sort 降序排序
$sortChildren = function (&$items) use (&$sortChildren) {
// 再递归处理每个节点的子菜单
foreach ($items as &$item) {
if (!empty($item['children'])) {
$sortChildren($item['children']);
}
}
};
// 对整个树形结构进行排序(包括顶级菜单)
$sortChildren($tree);
return json([
'code' => 200,
'msg' => 'success',
'data' => $tree
]);
} catch (DbException $e) {
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => $e->getTraceAsString()
]);
}
}
/**
* 更新菜单状态
* @param int $id 菜单ID
* @return \think\response\Json
*/
public function updateMenuStatus(int $id)
{
try {
// 获取请求参数
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'status|菜单状态' => 'require|in:0,1'
]);
// 更新菜单状态
$result = SystemMenu::where('id', $id)
->where('delete_time', null)
->update([
'status' => $data['status'],
'update_time' => time()
]);
if ($result === false) {
return json([
'code' => 500,
'msg' => 'fail'
]);
}
// 记录操作日志
$this->logSuccess('菜单管理', '更新菜单状态', ['id' => $id]);
return json([
'code' => 200,
'msg' => 'success'
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('菜单管理', '更新菜单状态', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('菜单管理', '更新菜单状态', $e->getMessage());
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage()
]);
}
}
/**
* 创建新菜单
* @return \think\response\Json
*/
public function createMenu()
{
try {
// 获取请求参数
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'title|菜单名称' => 'require|max:50',
'type|菜单类型' => 'require|in:1,2,3',
'status|菜单状态' => 'require|in:0,1',
'sort|排序号' => 'integer',
'path|路由路径' => 'max:200',
'icon|菜单图标' => 'max:100',
'permission|权限标识' => 'max:100',
'remark|备注' => 'max:500'
]);
// 准备菜单数据
$menuData = [
'pid' => $data['pid'] ?? 0,
'title' => $data['title'],
'type' => $data['type'],
'status' => $data['status'] ?? 1,
'sort' => $data['sort'] ?? 0,
'path' => $data['path'] ?? '',
'component_path' => $data['component_path'] ?? '',
'icon' => $data['icon'] ?? '',
'permission' => $data['permission'] ?? '',
'remark' => $data['remark'] ?? '',
'creater' => Session::get('name') ?? 'system',
'create_time' => date('Y-m-d H:i:s')
];
// 创建菜单
$result = SystemMenu::insertGetId($menuData);
if ($result) {
// 记录操作日志
$this->logSuccess('菜单管理', '创建菜单', ['id' => $result]);
return json([
'code' => 200,
'msg' => '创建成功',
'data' => ['id' => $result]
]);
} else {
throw new \Exception('创建菜单失败');
}
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('菜单管理', '创建菜单', $e->getMessage());
return json([
'code' => 500,
'msg' => '创建失败:' . $e->getMessage(),
'data' => []
]);
}
}
/**
* 更新菜单信息
* @param int $id 菜单ID
* @return \think\response\Json
*/
public function updateMenu(int $id)
{
try {
// 获取请求参数
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'title|菜单名称' => 'require|max:50',
'pid|上级菜单ID' => 'integer',
'type|菜单类型' => 'require|in:1,2,3',
'status|菜单状态' => 'require|in:0,1',
'sort|排序号' => 'integer',
'path|路由路径' => 'max:200',
'icon|菜单图标' => 'max:100',
'permission|权限标识' => 'max:100',
'remark|备注' => 'max:500'
]);
// 准备更新数据
$updateData = [
'title' => $data['title'],
'pid' => $data['pid'] ?? null,
'type' => $data['type'],
'path' => $data['path'] ?? null,
'component_path' => $data['component_path'] ?? null,
'icon' => $data['icon'] ?? null,
'sort' => $data['sort'] ?? 0,
'status' => $data['status'],
'permission' => $data['permission'] ?? null,
'remark' => $data['remark'] ?? null,
'update_time' => date('Y-m-d H:i:s')
];
// 执行更新
SystemMenu::where('id', $id)->update($updateData);
// 获取更新后的菜单信息
$menu = SystemMenu::where('id', $id)->find();
// 记录操作日志
$this->logSuccess('菜单管理', '更新菜单信息', ['id' => $id]);
return json([
'code' => 200,
'msg' => 'success',
'data' => $menu ? $menu->toArray() : []
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('菜单管理', '更新菜单信息', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('菜单管理', '更新菜单信息', $e->getMessage());
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage()
]);
}
}
/**
* 删除菜单
* @param int $id 菜单ID
* @return \think\response\Json
*/
public function deleteMenu(int $id)
{
try {
// 检查是否有子菜单
$hasChildren = SystemMenu::where('pid', $id)
->where('delete_time', null)
->count() > 0;
if ($hasChildren) {
return json([
'code' => 400,
'msg' => '该菜单下有子菜单,无法删除'
]);
}
// 逻辑删除菜单
$result = SystemMenu::where('id', $id)
->where('delete_time', null)
->update([
'delete_time' => time(),
'update_time' => time()
]);
if ($result === false) {
return json([
'code' => 500,
'msg' => 'fail'
]);
}
// 记录操作日志
$this->logSuccess('菜单管理', '删除菜单', ['id' => $id]);
return json([
'code' => 200,
'msg' => 'success'
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('菜单管理', '删除菜单', $e->getMessage());
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage()
]);
}
}
/**
* 构建菜单树形结构
* @param array $menus 菜单列表
* @param int $pid 父菜单ID
* @return array
*/
private function buildMenuTree(array $menus, int $pid = 0): array
{
$tree = [];
foreach ($menus as $menu) {
// 将null的pid视为0处理
$menuPid = is_null($menu['pid']) ? 0 : $menu['pid'];
if ($menuPid == $pid) {
$children = $this->buildMenuTree($menus, $menu['id']);
if (!empty($children)) {
$menu['children'] = $children;
}
$tree[] = $menu;
}
}
return $tree;
}
}
+268
View File
@@ -0,0 +1,268 @@
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Session;
use think\response\Json;
use think\db\exception\DbException;
use app\model\OnePage;
class OnePageController extends BaseController
{
/**
* 获取所有单页信息
* @return Json
*/
public function getAllOnePages()
{
try {
$onePages = OnePage::where('delete_time', null)
->order('sort', 'asc')
->field('id, title, content, path, sort, status, create_time, update_time')
->select()
->toArray();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $onePages
]);
} catch (DbException $e) {
return json([
'code' => 500,
'msg' => '获取失败:' . $e->getMessage(),
'data' => []
]);
}
}
/**
* 创建单页
* @return Json
*/
public function createOnePage()
{
try {
// 获取请求参数
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'title|标题' => 'require|max:255',
'content|内容' => 'require',
'path|路由' => 'require|max:200',
'sort|排序号' => 'integer',
'status|状态' => 'integer|in:0,1',
]);
// 验证路由格式(必须以 / 开头)
if (!preg_match('/^\/[a-zA-Z0-9\/_-]*$/', $data['path'])) {
return json([
'code' => 400,
'msg' => '路由必须以 / 开头,只能包含字母、数字、下划线、横线和斜线'
]);
}
// 检查路由是否已存在
$exists = OnePage::where('path', $data['path'])
->where('delete_time', null)
->find();
if ($exists) {
return json([
'code' => 400,
'msg' => '路由已存在,请使用其他路由'
]);
}
// 准备单页数据
$onePageData = [
'title' => $data['title'],
'content' => $data['content'],
'path' => $data['path'],
'sort' => $data['sort'] ?? 0,
'status' => $data['status'] ?? 1,
'create_time' => date('Y-m-d H:i:s'),
];
// 创建单页
$result = OnePage::insertGetId($onePageData);
if ($result === false) {
return json([
'code' => 500,
'msg' => '创建失败'
]);
}
// 记录操作日志
$this->logSuccess('单页管理', '创建单页', ['id' => $result]);
return json([
'code' => 200,
'msg' => '创建成功',
'data' => ['id' => $result]
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('单页管理', '创建单页', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('单页管理', '创建单页', $e->getMessage());
return json([
'code' => 500,
'msg' => '创建失败:' . $e->getMessage()
]);
}
}
/**
* 编辑单页
* @param int $id 单页ID
* @return Json
*/
public function editOnePage(int $id)
{
try {
// 获取请求参数
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'title|标题' => 'require|max:255',
'content|内容' => 'require',
'path|路由' => 'require|max:200',
'sort|排序号' => 'integer',
'status|状态' => 'integer|in:0,1',
]);
// 验证路由格式(必须以 / 开头)
if (!preg_match('/^\/[a-zA-Z0-9\/_-]*$/', $data['path'])) {
return json([
'code' => 400,
'msg' => '路由必须以 / 开头,只能包含字母、数字、下划线、横线和斜线'
]);
}
// 检查单页是否存在
$onePage = OnePage::where('id', $id)
->where('delete_time', null)
->find();
if (!$onePage) {
return json([
'code' => 404,
'msg' => '单页不存在'
]);
}
// 检查路由是否被其他单页使用
$exists = OnePage::where('path', $data['path'])
->where('id', '<>', $id)
->where('delete_time', null)
->find();
if ($exists) {
return json([
'code' => 400,
'msg' => '路由已被其他单页使用,请使用其他路由'
]);
}
// 准备更新数据
$updateData = [
'title' => $data['title'],
'content' => $data['content'],
'path' => $data['path'],
'sort' => $data['sort'] ?? 0,
'status' => $data['status'] ?? 1,
'update_time' => date('Y-m-d H:i:s'),
];
// 执行更新
OnePage::where('id', $id)->update($updateData);
// 获取更新后的单页信息
$updatedOnePage = OnePage::where('id', $id)->find();
// 记录操作日志
$this->logSuccess('单页管理', '更新单页', ['id' => $id]);
return json([
'code' => 200,
'msg' => '更新成功',
'data' => $updatedOnePage ? $updatedOnePage->toArray() : []
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('单页管理', '更新单页', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('单页管理', '更新单页', $e->getMessage());
return json([
'code' => 500,
'msg' => '更新失败:' . $e->getMessage()
]);
}
}
/**
* 删除单页
* @param int $id 单页ID
* @return Json
*/
public function deleteOnePage(int $id)
{
try {
// 检查单页是否存在
$onePage = OnePage::where('id', $id)
->where('delete_time', null)
->find();
if (!$onePage) {
return json([
'code' => 404,
'msg' => '单页不存在'
]);
}
// 逻辑删除单页
$result = OnePage::where('id', $id)
->where('delete_time', null)
->update([
'delete_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s')
]);
if ($result === false) {
return json([
'code' => 500,
'msg' => '删除失败'
]);
}
// 记录操作日志
$this->logSuccess('单页管理', '删除单页', ['id' => $id]);
return json([
'code' => 200,
'msg' => '删除成功'
]);
} catch (DbException $e) {
// 记录失败日志
$this->logFail('单页管理', '删除单页', $e->getMessage());
return json([
'code' => 500,
'msg' => '删除失败:' . $e->getMessage()
]);
}
}
}
@@ -0,0 +1,256 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\OperationLog;
use app\admin\BaseController;
use think\facade\Request;
use think\response\Json;
use app\model\OperationLog;
use app\model\AdminUser;
class OperationLogController extends BaseController
{
/**
* 获取操作日志列表
* @return Json
*/
public function getOperationLogs()
{
try {
$page = Request::param('page/d', 1);
$pageSize = Request::param('pageSize/d', 20);
$keyword = Request::param('keyword/s', '');
$module = Request::param('module/s', '');
$action = Request::param('action/s', '');
$status = Request::param('status/s', '');
$startTime = Request::param('startTime/s', '');
$endTime = Request::param('endTime/s', '');
$query = OperationLog::where('delete_time', null);
// 关键词搜索(用户姓名、URL
if ($keyword) {
$query->where(function ($q) use ($keyword) {
$q
->whereOr('user_name', 'like', "%{$keyword}%")
->whereOr('url', 'like', "%{$keyword}%");
});
}
// 模块筛选
if ($module) {
$query->where('module', $module);
}
// 操作动作筛选
if ($action) {
$query->where('action', $action);
}
// 状态筛选
if ($status !== '') {
$query->where('status', $status);
}
// 时间范围筛选
if ($startTime) {
$query->where('create_time', '>=', $startTime);
}
if ($endTime) {
$query->where('create_time', '<=', $endTime);
}
// 获取总数
$total = $query->count();
// 分页查询
$list = $query->order('id', 'desc')
->page($page, $pageSize)
->select()
->toArray();
// 获取所有唯一的 user_id
$userIds = array_unique(array_column($list, 'user_id'));
$userIds = array_filter($userIds); // 过滤掉0和null
// 批量查询用户信息
$users = [];
if (!empty($userIds)) {
$userList = AdminUser::whereIn('id', $userIds)
->where('delete_time', null)
->field('id, name')
->select()
->toArray();
// 转换为以 id 为键的数组,方便查找
foreach ($userList as $user) {
$users[$user['id']] = [
'name' => $user['name']
];
}
}
// 将用户信息合并到日志列表中
foreach ($list as &$item) {
if (isset($users[$item['user_id']])) {
$item['user_name'] = $users[$item['user_id']]['name'];
} else {
$item['user_name'] = $item['user_name'] ?? '';
}
}
unset($item); // 释放引用
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $list,
'total' => $total,
'page' => $page,
'pageSize' => $pageSize
]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取操作日志失败: ' . $e->getMessage()
]);
}
}
/**
* 获取操作日志详情
* @param int $id
* @return Json
*/
public function getOperationLogDetail(int $id)
{
try {
$log = OperationLog::where('id', $id)
->where('delete_time', null)
->find();
if (!$log) {
return json([
'code' => 404,
'msg' => '操作日志不存在'
]);
}
$logData = $log->toArray();
$this->logSuccess('操作日志', '查看操作日志详情', $logData);
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $logData
]);
} catch (\Exception $e) {
$this->logFail('操作日志', '查看操作日志详情', $e->getMessage());
return json([
'code' => 500,
'msg' => '获取操作日志详情失败: ' . $e->getMessage()
]);
}
}
/**
* 删除操作日志
* @param int $id
* @return Json
*/
public function deleteOperationLog(int $id)
{
try {
$log = OperationLog::where('id', $id)
->where('delete_time', null)
->find();
if (!$log) {
return json([
'code' => 404,
'msg' => '操作日志不存在'
]);
}
// 软删除
$log->delete();
return json([
'code' => 200,
'msg' => '删除成功'
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '删除失败: ' . $e->getMessage()
]);
}
}
/**
* 批量删除操作日志
* @return Json
*/
public function batchDeleteOperationLogs()
{
try {
$ids = Request::param('ids/a', []);
if (empty($ids)) {
return json([
'code' => 400,
'msg' => '请选择要删除的操作日志'
]);
}
OperationLog::whereIn('id', $ids)
->where('delete_time', null)
->delete();
return json([
'code' => 200,
'msg' => '批量删除成功'
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '批量删除失败: ' . $e->getMessage()
]);
}
}
/**
* 获取操作统计信息(模块、动作等)
* @return Json
*/
public function getOperationStatistics()
{
try {
// 获取模块列表
$modules = OperationLog::where('delete_time', null)
->group('module')
->column('module');
// 获取动作列表
$actions = OperationLog::where('delete_time', null)
->group('action')
->column('action');
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'modules' => $modules,
'actions' => $actions
]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取统计信息失败: ' . $e->getMessage()
]);
}
}
}
@@ -0,0 +1,96 @@
<?php
namespace app\admin\controller\OperationLog;
use app\model\OperationLog;
use think\facade\Request;
use think\facade\Session;
/**
* 操作日志记录助手类
*/
class OperationLogHelper
{
/**
* 记录操作日志
* @param string $module 操作模块
* @param string $action 操作动作
* @param array $requestData 请求数据
* @param array $responseData 响应数据
* @param int $status 操作状态:1-成功,0-失败
* @param string $errorMessage 错误信息
* @param float $executionTime 执行时间(秒)
* @return bool
*/
public static function log(
string $module,
string $action,
array $requestData = [],
array $responseData = [],
int $status = 1,
string $errorMessage = '',
float $executionTime = 0.0
): bool {
try {
// 获取用户信息
$userInfo = Session::get('user');
$userId = $userInfo['id'] ?? 0;
$userAccount = $userInfo['account'] ?? '';
$userName = $userInfo['name'] ?? '';
// 获取请求信息
$method = Request::method();
$url = Request::url(true);
$ip = Request::ip();
$userAgent = Request::header('user-agent', '');
// 过滤敏感信息(如密码)
$filteredRequestData = self::filterSensitiveData($requestData);
// 记录日志
OperationLog::create([
'user_id' => $userId,
'user_account' => $userAccount,
'user_name' => $userName,
'module' => $module,
'action' => $action,
'method' => $method,
'url' => $url,
'ip' => $ip,
'user_agent' => $userAgent,
'request_data' => !empty($filteredRequestData) ? json_encode($filteredRequestData, JSON_UNESCAPED_UNICODE) : null,
'response_data' => !empty($responseData) ? json_encode($responseData, JSON_UNESCAPED_UNICODE) : null,
'status' => $status,
'error_message' => $errorMessage,
'execution_time' => $executionTime,
]);
return true;
} catch (\Exception $e) {
// 记录日志失败不应该影响主流程,只记录错误
error_log('操作日志记录失败: ' . $e->getMessage());
return false;
}
}
/**
* 过滤敏感数据
* @param array $data
* @return array
*/
private static function filterSensitiveData(array $data): array
{
$sensitiveKeys = ['password', 'pwd', 'token', 'api_key', 'secret'];
foreach ($data as $key => $value) {
if (in_array(strtolower($key), $sensitiveKeys)) {
$data[$key] = '***';
} elseif (is_array($value)) {
$data[$key] = self::filterSensitiveData($value);
}
}
return $data;
}
}
@@ -0,0 +1,97 @@
<?php
namespace app\admin\controller\OperationLog;
use think\facade\Request;
use app\model\OperationLog;
use app\service\JwtService;
/**
* 操作日志记录器(JWT版)
*/
class OperationLogger
{
private static function getUserFromToken(): array
{
return JwtService::getUserFromHeader(Request::header('Authorization', ''));
}
public static function record(
string $module,
string $action,
array $requestData = [],
array $responseData = [],
int $status = 1,
string $errorMessage = '',
array $userInfo = []
): bool {
try {
if (empty($userInfo)) {
$userInfo = self::getUserFromToken();
}
$userId = $userInfo['id'] ?? 0;
$userAccount = $userInfo['account'] ?? '';
$userName = $userInfo['name'] ?? '';
if (empty($requestData)) {
$requestData = Request::param();
}
$method = Request::method();
$url = Request::url(true);
$ip = Request::ip();
$userAgent = Request::header('user-agent', '');
$filteredRequestData = self::filterSensitiveData($requestData);
OperationLog::create([
'user_id' => $userId,
'user_account' => $userAccount,
'user_name' => $userName,
'module' => $module,
'action' => $action,
'method' => $method,
'url' => $url,
'ip' => $ip,
'user_agent' => $userAgent,
'request_data' => !empty($filteredRequestData) ? json_encode($filteredRequestData, JSON_UNESCAPED_UNICODE) : null,
'response_data' => !empty($responseData) ? json_encode($responseData, JSON_UNESCAPED_UNICODE) : null,
'status' => $status,
'error_message' => $errorMessage,
'execution_time' => 0.0,
]);
return true;
} catch (\Exception $e) {
error_log('操作日志记录失败: ' . $e->getMessage());
return false;
}
}
public static function success(string $module, string $action, array $responseData = [], array $userInfo = []): bool
{
return self::record($module, $action, [], $responseData, 1, '', $userInfo);
}
public static function fail(string $module, string $action, string $errorMessage, array $userInfo = []): bool
{
return self::record($module, $action, [], [], 0, $errorMessage, $userInfo);
}
private static function filterSensitiveData(array $data): array
{
$sensitiveKeys = ['password', 'pwd', 'token', 'api_key', 'secret'];
foreach ($data as $key => $value) {
if (in_array(strtolower($key), $sensitiveKeys)) {
$data[$key] = '***';
} elseif (is_array($value)) {
$data[$key] = self::filterSensitiveData($value);
}
}
return $data;
}
}
+249
View File
@@ -0,0 +1,249 @@
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Session;
use think\response\Json;
use app\model\AdminUser;
use app\model\AdminUserGroup;
class RoleController extends BaseController
{
/**
* 获取所有角色列表
* @return Json
*/
public function getAllRoles()
{
$roles = AdminUserGroup::where('delete_time', null)
->order('id', 'asc')
->select();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $roles->toArray()
]);
}
/**
* 获取角色详情
* @param int $id
* @return Json
*/
public function getRoleById(int $id)
{
$role = AdminUserGroup::where('id', $id)
->where('delete_time', null)
->find();
if (!$role) {
return json([
'code' => 404,
'msg' => '角色不存在',
'data' => null
]);
}
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $role->toArray()
]);
}
/**
* 创建角色
* @return Json
*/
public function createRole()
{
try {
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'name|角色名称' => 'require|max:50',
'status|状态' => 'in:0,1',
'rights|权限' => 'array'
]);
// 检查角色名称是否已存在
$exists = AdminUserGroup::where('name', $data['name'])
->where('delete_time', null)
->find();
if ($exists) {
return json([
'code' => 400,
'msg' => '角色名称已存在'
]);
}
// 准备数据
$roleData = [
'name' => $data['name'],
'status' => $data['status'] ?? 1,
'rights' => !empty($data['rights']) ? json_encode($data['rights']) : null,
'create_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s')
];
// 创建角色
$roleModel = new AdminUserGroup();
$roleModel->save($roleData);
// 记录操作日志
$this->logSuccess('角色管理', '创建角色', ['id' => $roleModel->id]);
return json([
'code' => 200,
'msg' => '创建成功',
'data' => $roleModel->toArray()
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('角色管理', '创建角色', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('角色管理', '创建角色', $e->getMessage());
return json([
'code' => 500,
'msg' => '创建失败:' . $e->getMessage()
]);
}
}
/**
* 更新角色
* @param int $id
* @return Json
*/
public function updateRole(int $id)
{
try {
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'name|角色名称' => 'require|max:50',
'status|状态' => 'in:0,1',
'rights|权限' => 'array'
]);
// 查找角色
$role = AdminUserGroup::where('id', $id)
->where('delete_time', null)
->find();
if (!$role) {
return json([
'code' => 404,
'msg' => '角色不存在'
]);
}
// 检查角色名称是否已被其他角色使用
$exists = AdminUserGroup::where('name', $data['name'])
->where('id', '<>', $id)
->where('delete_time', null)
->find();
if ($exists) {
return json([
'code' => 400,
'msg' => '角色名称已存在'
]);
}
// 更新数据
$role->name = $data['name'];
$role->status = $data['status'] ?? 1;
$role->rights = !empty($data['rights']) ? json_encode($data['rights']) : null;
$role->update_time = date('Y-m-d H:i:s');
$role->save();
// 记录操作日志
$this->logSuccess('角色管理', '更新角色', ['id' => $id]);
return json([
'code' => 200,
'msg' => '更新成功',
'data' => $role->toArray()
]);
} catch (ValidateException $e) {
// 记录失败日志
$this->logFail('角色管理', '更新角色', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('角色管理', '更新角色', $e->getMessage());
return json([
'code' => 500,
'msg' => '更新失败:' . $e->getMessage()
]);
}
}
/**
* 删除角色
* @param int $id
* @return Json
*/
public function deleteRole(int $id)
{
try {
// 不允许删除ID为1的超级管理员角色
if ($id == 1) {
return json([
'code' => 400,
'msg' => '不允许删除超级管理员角色'
]);
}
// 查找角色
$role = AdminUserGroup::where('id', $id)
->where('delete_time', null)
->find();
if (!$role) {
return json([
'code' => 404,
'msg' => '角色不存在'
]);
}
// 检查是否有用户正在使用该角色
$userCount = AdminUser::where('group_id', $id)
->where('delete_time', null)
->count();
if ($userCount > 0) {
return json([
'code' => 400,
'msg' => "该角色下还有 {$userCount} 个用户,无法删除"
]);
}
// 软删除
$role->delete();
// 记录操作日志
$this->logSuccess('角色管理', '删除角色', ['id' => $id]);
return json([
'code' => 200,
'msg' => '删除成功'
]);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('角色管理', '删除角色', $e->getMessage());
return json([
'code' => 500,
'msg' => '删除失败:' . $e->getMessage()
]);
}
}
}
@@ -0,0 +1,162 @@
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\response\Json;
use app\model\SystemSiteSettings;
class SiteSettingsController extends BaseController
{
/**
* 获取所有站点设置列表
* @return Json
*/
public function getNormalInfos()
{
$siteSettings = SystemSiteSettings::where('delete_time', null)
->field('id, label, value, sort')
->order('id', 'asc')
->select();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $siteSettings->toArray()
]);
}
/**
* 保存基本信息
* @return Json
*/
public function saveNormalInfos()
{
$params = $this->request->param();
if (!isset($params['data']) || empty($params['data'])) {
return json(['code' => 400, 'msg' => '数据不能为空', 'data' => null]);
}
$data = json_decode($params['data'], true);
if (!is_array($data)) {
return json(['code' => 400, 'msg' => '数据格式错误', 'data' => null]);
}
foreach ($data as $item) {
if (!isset($item['label']) || !isset($item['value'])) {
continue;
}
$label = $item['label'];
$value = $item['value'] ?? '';
$setting = SystemSiteSettings::where('label', $label)->find();
if ($setting) {
$setting->value = $value;
$setting->save();
} else {
SystemSiteSettings::create([
'label' => $label,
'value' => $value,
'create_time' => date('Y-m-d H:i:s')
]);
}
}
$allSettings = SystemSiteSettings::column('value', 'label');
$allSettings['type'] = 'normal';
$labels = array_column($data, 'label');
$userInfo = $this->getAdminUserInfo();
$this->logSuccess('站点设置管理', '保存基本信息', ['labels' => $labels], $userInfo);
return json([
'code' => 200,
'msg' => '保存成功',
'data' => $allSettings
]);
}
/**
* 获取所有站点设置列表
* @return Json
*/
public function getLegalInfos()
{
$siteSettings = SystemSiteSettings::where('delete_time', null)
->field('id, label, value, sort')
->order('id', 'asc')
->select();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $siteSettings->toArray()
]);
}
/**
* 保存法律声明和隐私条款
* @return Json
*/
public function saveLegalInfos()
{
$params = $this->request->param();
if (!isset($params['data']) || empty($params['data'])) {
return json(['code' => 400, 'msg' => '数据不能为空', 'data' => null]);
}
$data = json_decode($params['data'], true);
if (!is_array($data)) {
return json(['code' => 400, 'msg' => '数据格式错误', 'data' => null]);
}
foreach ($data as $item) {
if (!isset($item['label']) || !isset($item['value'])) {
continue;
}
$label = $item['label'];
$value = $item['value'] ?? '';
$setting = SystemSiteSettings::where('label', $label)->find();
if ($setting) {
$setting->value = $value;
$setting->save();
} else {
SystemSiteSettings::create([
'label' => $label,
'value' => $value,
'create_time' => date('Y-m-d H:i:s')
]);
}
}
$allSettings = SystemSiteSettings::column('value', 'label');
$allSettings['type'] = 'legal_notice';
$labels = array_column($data, 'label');
$userId = Session::get('user_id', 0);
$userFromSession = Session::get('user', []);
$userFromCache = Cache::get('userInfo_' . $userId, []);
$account = Session::get('account', '');
$name = Session::get('name', '');
\think\facade\Log::record('SiteSettings Debug - user_id: ' . $userId . ', userFromSession: ' . json_encode($userFromSession) . ', userFromCache: ' . json_encode($userFromCache) . ', account: ' . $account . ', name: ' . $name);
$this->logSuccess('站点设置管理', '保存法律声明和隐私条款', ['labels' => $labels]);
return json([
'code' => 200,
'msg' => '保存成功',
'data' => $allSettings
]);
}
}
@@ -0,0 +1,69 @@
<?php
namespace app\admin\controller;
use app\admin\BaseController;
use think\facade\Request;
use think\Response;
class StorageController extends BaseController
{
/**
* 获取存储文件
* 用于访问 /storage/ 路径下的静态文件
* @param string $path 文件路径
*/
public function index($path = '')
{
// 如果没有传递路径参数,从pathinfo获取
if (empty($path)) {
$pathinfo = Request::pathinfo();
$path = str_replace('storage/', '', $pathinfo);
}
// 防止路径遍历攻击
$path = str_replace('..', '', $path);
$path = ltrim($path, '/');
// 构建文件完整路径
$filePath = public_path() . 'storage/' . $path;
// 检查文件是否存在
if (!file_exists($filePath) || !is_file($filePath)) {
return json([
'code' => 404,
'msg' => '文件不存在'
]);
}
// 获取文件MIME类型
$mimeType = mime_content_type($filePath);
if (!$mimeType) {
// 根据文件扩展名判断MIME类型
$ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
$mimeTypes = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
'mp4' => 'video/mp4',
'mp3' => 'audio/mpeg',
'pdf' => 'application/pdf',
];
$mimeType = $mimeTypes[$ext] ?? 'application/octet-stream';
}
// 读取文件内容
$content = file_get_contents($filePath);
// 返回文件响应
return Response::create($content, 'file', 200)
->header([
'Content-Type' => $mimeType,
'Content-Length' => filesize($filePath),
'Cache-Control' => 'public, max-age=31536000',
]);
}
}
+133
View File
@@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Session;
use think\response\Json;
use app\model\AdminUser;
class UserController extends BaseController
{
/**
* 获取所有用户信息
* @return Json
*/
public function getAllUsers()
{
$users = AdminUser::where('delete_time', null)->field('id, account, name, phone,email, qq, sex, group_id, status, last_login_ip, login_count, create_time, update_time')->select()->toArray();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $users,
'total' => count($users)
]
]);
}
/**
* 获取用户信息
* @return Json
*/
public function getUserInfo(int $id)
{
$user = AdminUser::where('id', $id)
->field('id, account, name, phone,email, qq, sex, group_id, status')
->find();
// 记录操作日志
$this->logSuccess('用户管理', '获取用户信息', ['id' => $id]);
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $user
]);
}
/**
* 修改密码
* @return Json
*/
public function changePassword(int $id, string $password)
{
try {
AdminUser::where('id', $id)->update([
'password' => md5($password),
'update_time' => date('Y-m-d H:i:s')
]);
// 记录操作日志
$this->logSuccess('用户管理', '修改密码', ['id' => $id]);
return json([
'code' => 200,
'msg' => '修改成功'
]);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('用户管理', '修改密码', $e->getMessage());
return json([
'code' => 500,
'msg' => '修改失败'
]);
}
}
/**
* 添加用户
* @return Json
*/
public function addUser()
{
$data = request()->param();
$data['password'] = md5($data['password']);
$data['create_time'] = date('Y-m-d H:i:s');
$data['update_time'] = $data['create_time'];
$data['group_id'] = 2;
$id = AdminUser::insertGetId($data);
// 记录操作日志
$this->logSuccess('用户管理', '添加用户', ['data' => $data]);
return json([
'code' => 200,
'msg' => '添加成功',
'data' => ['id' => $id]
]);
}
/**
* 编辑用户
* @return Json
*/
public function editUser(int $id)
{
$data = request()->param();
$data['update_time'] = date('Y-m-d H:i:s');
AdminUser::where('id', $id)->update($data);
// 记录操作日志
$this->logSuccess('用户管理', '编辑用户', ['data' => $data]);
return json([
'code' => 200,
'msg' => '编辑成功'
]);
}
/**
* 删除用户
* @return Json
*/
public function deleteUser(int $id)
{
AdminUser::where('id', $id)->delete();
// 记录操作日志
$this->logSuccess('用户管理', '删除用户', ['data' => $id]);
return json([
'code' => 200,
'msg' => '删除成功'
]);
}
}
+5
View File
@@ -0,0 +1,5 @@
<?php
// 这是系统自动生成的event定义文件
return [
];
+11
View File
@@ -0,0 +1,11 @@
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class,
];
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace app\admin\middleware;
use think\Response;
use think\Request;
/**
* 自定义跨域中间件
* 解决 AJAX 请求 Cookie 传递问题
*/
class CustomCors
{
public function handle(Request $request, \Closure $next): Response
{
$origin = $request->header('origin', '');
// 允许的域名列表(根据实际情况修改)
$allowedDomains = [
'http://localhost:3000',
'http://localhost:8000',
'http://127.0.0.1:3000',
'http://127.0.0.1:8000',
'http://backapi.yunzer.cn',
'http://localhost:5173',
'http://www.yunzer.cn',
'https://www.yunzer.cn',
'http://yunzer.cn',
'https://yunzer.cn',
];
// 检查是否为允许的域名
if (in_array($origin, $allowedDomains)) {
$header['Access-Control-Allow-Origin'] = $origin;
} else {
// 对于同源请求,允许当前域名
if (!empty($origin)) {
$header['Access-Control-Allow-Origin'] = $origin;
} else {
$header['Access-Control-Allow-Origin'] = '*';
}
}
// 处理 OPTIONS 预检请求,直接返回
if ($request->method() === 'OPTIONS') {
$header['Access-Control-Allow-Credentials'] = 'true';
$header['Access-Control-Max-Age'] = 1800;
$header['Access-Control-Allow-Methods'] = 'GET, POST, PATCH, PUT, DELETE, OPTIONS';
$header['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With';
\think\facade\Log::record('CORS OPTIONS preflight for: ' . $origin);
return response('', 200, $header);
}
$header['Access-Control-Allow-Credentials'] = 'true';
$header['Access-Control-Max-Age'] = 1800;
$header['Access-Control-Allow-Methods'] = 'GET, POST, PATCH, PUT, DELETE, OPTIONS';
$header['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With';
return $next($request)->header($header);
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
use think\facade\Route;
// 首页路由
Route::get('/', 'app\\admin\\controller\\Index@index');
Route::get('index/index', 'app\\admin\\controller\\Index@index');
// 静态文件路由 - 必须在其他路由之前定义,使用通配符匹配所有storage路径
Route::get('storage/:path', 'app\\admin\\controller\\StorageController@index')->pattern(['path' => '.*']);
// 登录路由
Route::post('login', 'app\\admin\\controller\\LoginController@login');
Route::post('logout', 'app\\admin\\controller\\LoginController@logout');
Route::get('user/info', 'app\\admin\\controller\\LoginController@userInfo');
// 菜单路由
Route::get('allmenu', 'app\\admin\\controller\\MenuController@getAllMenus');
Route::get('menu/:id', 'app\\admin\\controller\\MenuController@getMenus');
Route::post('createMenu', 'app\\admin\\controller\\MenuController@createMenu');
Route::put('updateMenu/:id', 'app\\admin\\controller\\MenuController@updateMenu');
Route::delete('deleteMenu/:id', 'app\\admin\\controller\\MenuController@deleteMenu');
Route::patch('menu/status/:id', 'app\\admin\\controller\\MenuController@updateMenuStatus');
// 前端导航路由
Route::get('frontmenus', 'app\\admin\\controller\\FrontMenuController@getFrontMenus');
Route::post('createfrontmenu', 'app\\admin\\controller\\FrontMenuController@createFrontMenu');
Route::post('editfrontmenu/:id', 'app\\admin\\controller\\FrontMenuController@editFrontMenu');
Route::delete('deletefrontmenu/:id', 'app\\admin\\controller\\FrontMenuController@deleteFrontMenu');
// Banner路由
Route::get('allbanners', 'app\\admin\\controller\\BannerController@getAllBanners');
Route::post('createbanner', 'app\\admin\\controller\\BannerController@createBanner');
Route::post('editbanner/:id', 'app\\admin\\controller\\BannerController@editBanner');
Route::delete('deletebanner/:id', 'app\\admin\\controller\\BannerController@deleteBanner');
// 单页路由
Route::get('allonepages', 'app\\admin\\controller\\OnePageController@getAllOnePages');
Route::post('createonepage', 'app\\admin\\controller\\OnePageController@createOnePage');
Route::post('editonepage/:id', 'app\\admin\\controller\\OnePageController@editOnePage');
Route::delete('deleteonepage/:id', 'app\\admin\\controller\\OnePageController@deleteOnePage');
// 文章路由
Route::get('articlesList', 'app\\admin\\controller\\Article\\ArticleController@getArticlesList');
Route::get('allarticles', 'app\\admin\\controller\\Article\\ArticleController@getAllArticles');
Route::get('articles/:id', 'app\\admin\\controller\\Article\\ArticleController@getArticle');
Route::post('createarticle', 'app\\admin\\controller\\Article\\ArticleController@createArticle');
Route::post('editarticle/:id', 'app\\admin\\controller\\Article\\ArticleController@editArticle');
Route::delete('deletearticle/:id', 'app\\admin\\controller\\Article\\ArticleController@deleteArticle');
Route::post('publisharticle/:id', 'app\\admin\\controller\\Article\\ArticleController@publishArticle');
Route::post('unPublisharticle/:id', 'app\\admin\\controller\\Article\\ArticleController@unPublishArticle');
Route::post('articleRecommend/:id', 'app\\admin\\controller\\Article\\ArticleController@articleRecommend');
Route::post('unArticleRecommend/:id', 'app\\admin\\controller\\Article\\ArticleController@unArticleRecommend');
Route::post('articleTop/:id', 'app\\admin\\controller\\Article\\ArticleController@articleTop');
Route::post('unArticleTop/:id', 'app\\admin\\controller\\Article\\ArticleController@unArticleTop');
// 文章分类路由
Route::get('allcategories', 'app\\admin\\controller\\Article\\ArticleCategoryController@getAllArticleCategories');
Route::get('categories', 'app\\admin\\controller\\Article\\ArticleCategoryController@getArticleCategories');
Route::delete('categories/:id', 'app\\admin\\controller\\Article\\ArticleCategoryController@deleteCategory');
Route::post('createCategory', 'app\\admin\\controller\\Article\\ArticleCategoryController@createCategory');
Route::post('editCategory/:id', 'app\\admin\\controller\\Article\\ArticleCategoryController@editCategory');
// 文件路由
Route::get('usercate', 'app\\admin\\controller\\FileController@getUserCate');
Route::get('allfiles', 'app\\admin\\controller\\FileController@getAllFiles');
Route::get('catefiles/:id', 'app\\admin\\controller\\FileController@getCateFiles');
Route::post('uploadfile', 'app\\admin\\controller\\FileController@uploadFile');
Route::post('updatefile/:id', 'app\\admin\\controller\\FileController@updateFile');
Route::delete('deletefile/:id', 'app\\admin\\controller\\FileController@deleteFile');
Route::get('movefile/:id', 'app\\admin\\controller\\FileController@moveFile');
Route::post('createfilecate', 'app\\admin\\controller\\FileController@createFileCate');
Route::post('renamefilecate/:id', 'app\\admin\\controller\\FileController@renameFileCate');
Route::delete('deletefilecate/:id', 'app\\admin\\controller\\FileController@deleteFileCate');
// 用户路由
Route::get('getAllUsers', 'app\\admin\\controller\\UserController@getAllUsers');
Route::get('getUserInfo/:id', 'app\\admin\\controller\\UserController@getUserInfo');
Route::post('addUser', 'app\\admin\\controller\\UserController@addUser');
Route::post('editUser/:id', 'app\\admin\\controller\\UserController@editUser');
Route::delete('deleteUser/:id', 'app\\admin\\controller\\UserController@deleteUser');
Route::post('changePassword', 'app\\admin\\controller\\UserController@changePassword');
// 角色路由
Route::get('allRoles', 'app\\admin\\controller\\RoleController@getAllRoles');
Route::get('roles/:id', 'app\\admin\\controller\\RoleController@getRoleById');
Route::post('roles', 'app\\admin\\controller\\RoleController@createRole');
Route::put('roles/:id', 'app\\admin\\controller\\RoleController@updateRole');
Route::delete('roles/:id', 'app\\admin\\controller\\RoleController@deleteRole');
// 操作日志路由(注意:具体路径要在动态路径之前)
Route::get('operationLogs/statistics', 'app\\admin\\controller\\OperationLog\\OperationLogController@getOperationStatistics');
Route::get('operationLogs/:id', 'app\\admin\\controller\\OperationLog\\OperationLogController@getOperationLogDetail');
Route::get('operationLogs', 'app\\admin\\controller\\OperationLog\\OperationLogController@getOperationLogs');
// 站点设置路由
Route::get('normalInfos', 'app\\admin\\controller\\SiteSettingsController@getNormalInfos');
Route::post('saveNormalInfos', 'app\\admin\\controller\\SiteSettingsController@saveNormalInfos');
Route::get('legalInfos', 'app\\admin\\controller\\SiteSettingsController@getLegalInfos');
Route::post('saveLegalInfos', 'app\\admin\\controller\\SiteSettingsController@saveLegalInfos');
+100
View File
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
// 关键修正:命名空间对应控制器目录
namespace app\index;
use think\App;
use think\exception\ValidateException;
use think\Validate;
use think\Request;
// 在控制器方法中添加
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* Request实例
* @var Request
*/
protected $request;
/**
* 应用实例
* @var App
*/
protected $app;
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [];
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->app = $app;
$this->request = $this->app->request;
// 控制器初始化
$this->initialize();
}
// 初始化
protected function initialize() {}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, string|array $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
// 支持场景
[$validate, $scene] = explode('.', $validate);
}
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
$v = new $class();
if (!empty($scene)) {
$v->scene($scene);
}
}
$v->message($message);
// 是否批量验证
if ($batch || $this->batchValidate) {
$v->batch(true);
}
return $v->failException(true)->check($data);
}
}
+2
View File
@@ -0,0 +1,2 @@
<?php
// 这是系统自动生成的公共文件
@@ -0,0 +1,249 @@
<?php
declare (strict_types = 1);
namespace app\index\controller\Article;
use app\index\BaseController;
use app\model\Articles;
use app\model\ArticlesCategory;
use Symfony\Component\VarDumper\VarDumper;
use think\exception\ValidateException;
use think\facade\Request;
use think\facade\Session;
use think\response\Json;
use think\db\exception\DbException;
class ArticleController extends BaseController
{
/**
* 获取新闻中心顶部4篇文章
* @return Json
*/
public function getNewsCenterTop4(): Json
{
try {
// 1. 查询新闻中心主分类
$newsCenterCategory = ArticlesCategory::where('name', '新闻中心')
->where('delete_time', null)
->find();
if (!$newsCenterCategory) {
return json([
'code' => 200,
'msg' => 'success',
'list' => [],
]);
}
// 2. 获取新闻中心及其子分类的ID列表
$mainCategoryId = $newsCenterCategory['id'];
// 查询子分类
$subCategories = ArticlesCategory::where('cid', $mainCategoryId)
->where('delete_time', null)
->column('id');
// 合并主分类和子分类ID
$categoryIds = array_merge([$mainCategoryId], $subCategories);
if (empty($categoryIds)) {
return json([
'code' => 200,
'msg' => 'success',
'list' => [],
]);
}
// 3. 查询符合条件的文章,按指定优先级排序,取最新4篇
$articles = Articles::published()
->whereIn('cate', $categoryIds)
->order([
'recommend' => 'desc',
'publishdate' => 'desc',
'sort' => 'desc',
'id' => 'desc'
])
->field([
'id',
'title',
'content',
'publishdate',
'DAY(publishdate) AS `date`',
'DATE_FORMAT(publishdate, "%Y-%m") AS `month`',
'image',
'recommend'
])
->limit(4)
->select();
// 4. 处理文章数据,去除HTML标签并生成desc字段
foreach ($articles as &$article) {
// 使用PHP的strip_tags函数去除HTML标签,然后截取前100个字符
$article['desc'] = mb_substr(trim(strip_tags($article['content'])), 0, 100, 'UTF-8');
// 移除原始content字段,减少返回数据大小
unset($article['content']);
}
return json([
'code' => 200,
'msg' => 'success',
'list' => $articles,
]);
} catch (\Exception $e) {
// 打印完整错误信息到日志
$errorMsg = '错误信息:' . $e->getMessage() . ' | 错误行号:' . $e->getLine() . ' | 执行SQL' . Articles::getLastSql();
trace('新闻中心文章查询失败: ' . $errorMsg, 'error');
return json([
'code' => 500,
'msg' => '新闻中心文章查询失败,请稍后重试',
'list' => []
]);
}
}
/**
* 游客文章阅读量函数
* @return Json
*/
public function articleViews(): Json
{
$id = Request::param('id');
if (!$id) {
return json([
'code' => 400,
'msg' => '没有文章id',
'list' => []
]);
}
try {
$article = Articles::where('id', $id)->where('delete_time', null)->find();
if (!$article) {
return json([
'code' => 404,
'msg' => '文章不存在',
'list' => []
]);
}
$article->inc('views');
$article->save();
return json([
'code' => 200,
'msg' => '阅读量更新成功',
'data' => [
'views' => $article->views
]
]);
} catch (\Exception $e) {
trace('阅读量更新失败: ' . $e->getMessage(), 'error');
return json([
'code' => 500,
'msg' => '阅读量更新失败,请稍后重试',
'list' => []
]);
}
}
/**
* 游客文章点赞函数
* @return Json
*/
public function articleLikes(): Json
{
$id = Request::param('id');
if (!$id) {
return json([
'code' => 400,
'msg' => '没有文章id',
'list' => []
]);
}
try {
$article = Articles::where('id', $id)->where('delete_time', null)->find();
if (!$article) {
return json([
'code' => 404,
'msg' => '文章不存在',
'list' => []
]);
}
$article->inc('likes');
$article->save();
return json([
'code' => 200,
'msg' => '点赞成功',
'data' => [
'likes' => $article->likes
]
]);
} catch (\Exception $e) {
trace('点赞失败: ' . $e->getMessage(), 'error');
return json([
'code' => 500,
'msg' => '点赞失败,请稍后重试',
'list' => []
]);
}
}
/**
* 游客文章取消点赞函数
* @return Json
*/
public function articleUnlikes(): Json
{
$id = Request::param('id');
if (!$id) {
return json([
'code' => 400,
'msg' => '没有文章id',
'list' => []
]);
}
try {
$article = Articles::where('id', $id)->where('delete_time', null)->find();
if (!$article) {
return json([
'code' => 404,
'msg' => '文章不存在',
'list' => []
]);
}
if ($article->likes <= 0) {
return json([
'code' => 400,
'msg' => '文章点赞数为0,不能取消点赞',
'list' => []
]);
}
$article->dec('likes');
$article->save();
return json([
'code' => 200,
'msg' => '取消点赞成功',
'data' => [
'likes' => $article->likes
]
]);
} catch (\Exception $e) {
trace('取消点赞失败: ' . $e->getMessage(), 'error');
return json([
'code' => 500,
'msg' => '取消点赞失败,请稍后重试',
'list' => []
]);
}
}
}
+133
View File
@@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace app\index\controller;
use app\model\Banner;
use app\index\BaseController;
use app\model\FrontMenu;
use app\model\OnePage;
use think\db\exception\DbException;
class Index extends BaseController
{
public function index()
{
return '您好!这是一个[index]示例应用';
}
/**
* 获取前端导航接口
* @return \think\response\Json
*/
public function getHeadMenu()
{
try {
// 获取所有未删除的菜单
$frontMenus = FrontMenu::where('delete_time', null)
->field('id,pid,title,type,image,path,component_path,sort,desc')
->order('sort', 'desc')
->select()
->toArray(); // 转换为数组
// 使用 buildFrontMenuTree 构建树形结构
$treeFrontMenus = $this->buildFrontMenuTree($frontMenus);
return json([
'code' => 200,
'msg' => 'success',
'data' => $treeFrontMenus
]);
} catch (DbException $e) {
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => $e->getTraceAsString()
]);
}
}
/**
* 构建前端导航树形结构
* @param array $frontMenus 前端导航列表
* @param int $pid 父前端导航ID
* @return array
*/
private function buildFrontMenuTree(array $frontMenus, int $pid = 0): array
{
$tree = [];
foreach ($frontMenus as $frontMenu) {
// 将null的pid视为0处理
$menuPid = is_null($frontMenu['pid']) ? 0 : $frontMenu['pid'];
if ($menuPid == $pid) {
$children = $this->buildFrontMenuTree($frontMenus, $frontMenu['id']);
if (!empty($children)) {
$frontMenu['children'] = $children;
}
$tree[] = $frontMenu;
}
}
// 按 sort 字段降序排序
usort($tree, function ($a, $b) {
$sortA = $a['sort'] ?? 0;
$sortB = $b['sort'] ?? 0;
return $sortB - $sortA;
});
return $tree;
}
/**
* 根据路径获取单页内容
* @param string $path 路由路径
* @return \think\response\Json
*/
public function getOnePageByPath($path = '')
{
try {
// 从路由参数获取路径
if (empty($path)) {
// 如果没有路由参数,尝试从 pathinfo 获取
$requestPath = $this->request->pathinfo();
$path = str_replace('onepage/', '', $requestPath);
}
// 确保路径以 / 开头
if (empty($path) || $path[0] !== '/') {
$path = '/' . $path;
}
// 解码路径
$path = urldecode($path);
// 查找对应路径的单页
$onePage = OnePage::where('path', $path)
->where('status', 1)
->where('delete_time', null)
->find();
if (!$onePage) {
return json([
'code' => 404,
'msg' => '单页不存在或已禁用',
'data' => null
]);
}
return json([
'code' => 200,
'msg' => 'success',
'data' => $onePage->toArray()
]);
} catch (DbException $e) {
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => null
]);
}
}
}
@@ -0,0 +1,434 @@
<?php
declare(strict_types=1);
namespace app\index\controller;
use app\index\BaseController;
use Symfony\Component\VarDumper\VarDumper;
use think\exception\ValidateException;
use think\facade\Request;
use think\facade\Session;
use think\response\Json;
use think\db\exception\DbException;
use app\model\Articles;
use app\model\ArticlesCategory;
class NewsCenterController extends BaseController
{
/**
* 根据分类获取文章
* @param string $category 分类名称
* @return Json
*/
public function getNewsByCategory(string $category): Json
{
// 查询分类
$categoryInfo = ArticlesCategory::where('name', $category)
->where('delete_time', null)
->find();
if (!$categoryInfo) {
return json([
'code' => 200,
'msg' => 'success',
'list' => [],
]);
}
// 查询文章
$articles = Articles::published()
->where('cate', $categoryInfo['id'])
->order('publishdate', 'desc')
->limit(4)
->select();
return json([
'code' => 200,
'msg' => 'success',
'list' => $articles,
]);
}
/**
* 获取企业新闻
* @return Json
*/
public function getCompanyNews(): Json
{
// 获取分页参数
$page = Request::param('page', 1);
$limit = Request::param('limit', 10);
$page = max(1, intval($page));
$limit = max(1, min(50, intval($limit))); // 限制每页最多50条
// 查询分类
$categoryInfo = ArticlesCategory::where('name', '企业新闻')
->where('delete_time', null)
->find();
if (!$categoryInfo) {
return json([
'code' => 200,
'msg' => 'success',
'list' => [],
'total' => 0,
'page' => $page,
'limit' => $limit,
]);
}
// 查询总数
$total = Articles::where('cate', $categoryInfo['id'])
->where('delete_time', null)
->where('status', 2)
->count();
// 查询文章
$articles = Articles::where('cate', $categoryInfo['id'])
->where('delete_time', null)
->where('status', 2)
->order('top', 'desc')
->order('recommend', 'desc')
->order('sort', 'desc')
->order('publishdate', 'desc')
->page($page, $limit)
->select();
return json([
'code' => 200,
'msg' => 'success',
'list' => $articles,
'total' => $total,
'page' => $page,
'limit' => $limit,
]);
}
/**
* 获取金蝶新闻
* @return Json
*/
public function getKingdeeNews(): Json
{
// 获取分页参数
$page = Request::param('page', 1);
$limit = Request::param('limit', 10);
$page = max(1, intval($page));
$limit = max(1, min(50, intval($limit))); // 限制每页最多50条
// 查询分类
$categoryInfo = ArticlesCategory::where('name', '金蝶新闻')
->where('delete_time', null)
->find();
if (!$categoryInfo) {
return json([
'code' => 200,
'msg' => 'success',
'list' => [],
'total' => 0,
'page' => $page,
'limit' => $limit,
]);
}
// 查询总数
$total = Articles::where('cate', $categoryInfo['id'])
->where('delete_time', null)
->where('status', 2)
->count();
// 查询文章
$articles = Articles::where('cate', $categoryInfo['id'])
->where('delete_time', null)
->where('status', 2)
->order('top', 'desc')
->order('recommend', 'desc')
->order('sort', 'desc')
->order('publishdate', 'desc')
->page($page, $limit)
->select();
return json([
'code' => 200,
'msg' => 'success',
'list' => $articles,
'total' => $total,
'page' => $page,
'limit' => $limit,
]);
}
/**
* 获取技术中心子分类
* @return Json
*/
public function getTechnologyCategories(): Json
{
// 获取"技术中心"主分类
$parentCategory = ArticlesCategory::where('name', '技术中心')
->where('delete_time', null)
->find();
if (!$parentCategory) {
return json([
'code' => 200,
'msg' => 'success',
'data' => []
]);
}
// 查找所有子分类
$subCategories = ArticlesCategory::where('cid', $parentCategory['id'])
->where('delete_time', null)
->field('id,name,desc,sort,image')
->order('sort', 'desc')
->select();
return json([
'code' => 200,
'msg' => 'success',
'data' => $subCategories
]);
}
/**
* 获取技术中心
* @return Json
*/
public function getTechnologyCenter(): Json
{
// 1. 分页参数规范化
$page = max(1, intval(Request::param('page', 1)));
$limit = max(1, min(50, intval(Request::param('limit', 10))));
// 获取分类ID参数(可选)
$categoryId = Request::param('category_id', null);
// 2. 第一步:获取“技术中心”主分类的 id
$parentCategory = ArticlesCategory::where('name', '技术中心')
->where('delete_time', null)
->find();
// print_r($parentCategory['id']);
if (!$parentCategory) {
return $this->emptyResponse($page, $limit);
}
// 3. 第二步:查找 cid 等于主分类 id 的所有子分类 id
$subCategoryQuery = ArticlesCategory::where('cid', $parentCategory['id'])
->where('delete_time', null);
// print_r($subCategoryQuery->select());
// 如果指定了分类ID,则只查询该分类
if ($categoryId) {
$subCategoryQuery->where('id', $categoryId);
}
$subCategoryIds = $subCategoryQuery->column('id');
// print_r($subCategoryIds);
if (empty($subCategoryIds)) {
return $this->emptyResponse($page, $limit);
}
// 5. 第三步:查询 Articles 表,并限定字段
$articleQuery = Articles::whereIn('cate', $subCategoryIds)
->where('delete_time', null)
->where('status', 2);
// 获取总数
$total = (clone $articleQuery)->count();
// 获取列表:限定返回 id, title, desc, publishdate, image, likes, views
$articles = $articleQuery->field('id,title,desc,publishdate,image,likes,views,cate')
->order([
'top' => 'desc',
'recommend' => 'desc',
'sort' => 'desc',
'publishdate' => 'desc'
])
->page($page, $limit)
->select();
// 如果文章没有image,查询对应分类的image
$categoryImages = ArticlesCategory::whereIn('id', $subCategoryIds)->column('image', 'id');
foreach ($articles as &$article) {
if (empty($article['image']) && isset($categoryImages[$article['cate']])) {
$article['image'] = $categoryImages[$article['cate']];
}
}
return json([
'code' => 200,
'msg' => 'success',
'list' => $articles,
'total' => $total,
'page' => $page,
'limit' => $limit,
]);
}
/**
* 统一空返回
*/
private function emptyResponse($page, $limit): Json
{
return json([
'code' => 200,
'msg' => 'success',
'list' => [],
'total' => 0,
'page' => $page,
'limit' => $limit,
]);
}
/**
* 获取金蝶新闻详情
*/
public function getKingdeeNewsDetail(int $id): Json
{
$article = Articles::where('id', $id)
->where('delete_time', null)
->where('status', 2)
->field('id,title,cate,image,desc,author,content,publisher,publishdate,views,likes,is_trans,transurl')
->find();
if (!$article) {
return json(['code' => 404, 'msg' => '文章不存在', 'data' => null]);
}
$cate = (int) $article['cate'];
// 转换为数组再处理
$articleData = $article->toArray();
// $articleData['catename'] = $this->getCategoryName($cate);
// 获取相关文章
$articleData['relatedArticles'] = $this->getRelatedArticles($id, $cate);
// 获取上一篇下一篇
$articleData['nextPreviousArticles'] = $this->getNextPreviousArticles($id, $cate);
// 增加浏览量
Articles::where('id', $id)->inc('views')->update();
return json(['code' => 200, 'msg' => 'success', 'data' => $articleData]);
}
/**
* 获取企业新闻详情
*/
public function getCompanyNewsDetail(int $id): Json
{
$article = Articles::where('id', $id)
->where('delete_time', null)
->where('status', 2)
->find();
if (!$article) {
return json(['code' => 404, 'msg' => '文章不存在', 'data' => null]);
}
$cate = (int) $article['cate'];
// 转换为数组再处理
$articleData = $article->toArray();
$articleData['catename'] = $this->getCategoryName($cate);
// 获取相关文章
$articleData['relatedArticles'] = $this->getRelatedArticles($id, $cate);
// 获取上一篇下一篇
$articleData['nextPreviousArticles'] = $this->getNextPreviousArticles($id, $cate);
// 增加浏览量
Articles::where('id', $id)->inc('views')->update();
return json(['code' => 200, 'msg' => 'success', 'data' => $articleData]);
}
/**
* 获取上一篇下一篇
* @param int $id 文章ID
* @param int $cate 分类ID
* @return array
*/
private function getNextPreviousArticles(int $id, int $cate): array
{
$nextArticle = Articles::where('id', '<', $id)
->where('cate', $cate)
->where('delete_time', null)
->where('status', 2)
->field('id,title')
->find();
$previousArticle = Articles::where('id', '>', $id)
->where('cate', $cate)
->where('delete_time', null)
->where('status', 2)
->field('id,title')
->find();
return [
'code' => 200,
'msg' => 'success',
'next' => $nextArticle,
'previous' => $previousArticle,
];
}
/**
* 通过分类 ID 获取分类名称
* @param int $cateId 分类ID
* @return string
*/
private function getCategoryName(int $id): string
{
$categoryInfo = ArticlesCategory::where('id', $id)
->where('delete_time', null)
->find();
if (!$categoryInfo) {
return '未分类';
}
return $categoryInfo['name'];
}
/**
* 获取相关文章
*/
private function getRelatedArticles(int $id, int $cate): array
{
$articles = Articles::where('id', '<>', $id)
->where('cate', $cate)
->where('delete_time', null)
->where('status', 2)
->order('top', 'desc')
->order('recommend', 'desc')
->order('sort', 'desc')
->order('id', 'desc')
->limit(5)
->select();
foreach ($articles as &$article) {
$article['cate'] = $this->getCategoryName($article['cate']);
}
return [
'code' => 200,
'msg' => 'success',
'list' => $articles,
];
}
}
+5
View File
@@ -0,0 +1,5 @@
<?php
// 这是系统自动生成的event定义文件
return [
];
+12
View File
@@ -0,0 +1,12 @@
<?php
// 全局中间件定义文件
return [
// 全局跨域中间件
\think\middleware\AllowCrossDomain::class,
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class,
];
+27
View File
@@ -0,0 +1,27 @@
<?php
use think\facade\Route;
// 首页路由
Route::get('/', 'app\index\controller\Index@index');
Route::get('index/index', 'app\index\controller\Index@index');
// --- 文章详情路由 ---
Route::get('kingdeenews/detail/:id', 'app\index\controller\NewsCenterController@getKingdeeNewsDetail');
Route::get('companynews/detail/:id', 'app\index\controller\NewsCenterController@getCompanyNewsDetail');
// --- 文章列表路由 ---
Route::get('kingdeenews$', 'app\index\controller\NewsCenterController@getKingdeeNews');
Route::get('companynews$', 'app\index\controller\NewsCenterController@getCompanyNews');
Route::get('technologyCenter$', 'app\index\controller\NewsCenterController@getTechnologyCenter');
Route::get('technologyCategories$', 'app\index\controller\NewsCenterController@getTechnologyCategories');
Route::get('newscentertop4', 'app\index\controller\Article\ArticleController@getNewsCenterTop4');
Route::get('newsbycategory/:category', 'app\index\controller\NewsCenterController@getNewsByCategory');
// --- 文章互动路由 ---
Route::post('articleViews/:id', 'app\index\controller\Article\ArticleController@articleViews');
Route::post('articleLikes/:id', 'app\index\controller\Article\ArticleController@articleLikes');
Route::post('articleUnlikes/:id', 'app\index\controller\Article\ArticleController@articleUnlikes');
// --- 前端导航与单页路由 ---
Route::get('headmenu', 'app\index\controller\Index@getHeadMenu');
Route::rule('onepage/:path', 'app\index\controller\Index@getOnePageByPath', 'GET')->pattern(['path' => '.*']);
+42
View File
@@ -0,0 +1,42 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章分类模型
*/
class AdminUser extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_admin_user';
// 字段类型转换
protected $type = [
'id' => 'integer',
'sex' => 'integer',
'group_id' => 'integer',
'login_count' => 'integer',
'status' => 'integer',
'api_key_status' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+40
View File
@@ -0,0 +1,40 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 用户组模型
*/
class AdminUserGroup extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_admin_user_group';
// 字段类型转换
protected $type = [
'id' => 'integer',
'name' => 'string',
'status' => 'integer',
'rights' => 'string',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+129
View File
@@ -0,0 +1,129 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章模型
*/
class Articles extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_articles';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
// 时间戳字段名
protected $createTime = 'create_time';
protected $updateTime = 'update_time';
protected $deleteTime = 'delete_time';
// 字段类型转换
protected $type = [
'id' => 'integer',
'cate' => 'integer',
'sort' => 'integer',
'status' => 'integer',
'is_trans' => 'integer',
'views' => 'integer',
'likes' => 'integer',
'recommend' => 'integer',
'top' => 'integer',
'publisher' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'publishdate' => 'datetime',
'delete_time' => 'datetime',
];
/**
* 关联文章分类
*/
public function category()
{
return $this->belongsTo('app\index\model\ArticleCategory', 'cate', 'id', [], 'LEFT')->bind([
'cate_name' => 'name',
]);
}
/**
* 关联发布者
*/
public function publisher()
{
return $this->belongsTo('app\index\model\AdminUser', 'publisher', 'id', [], 'LEFT')->bind([
'publisher_name' => 'name',
]);
}
/**
* 获取已发布的文章
*/
public static function published()
{
return self::where('status', 2)->where('delete_time', null);
}
/**
* 获取推荐文章
*/
public static function recommended()
{
return self::published()->where('recommend', 1);
}
/**
* 获取置顶文章
*/
public static function top()
{
return self::published()->where('top', 1);
}
/**
* 按分类获取文章
*/
public static function byCategory($cateId)
{
return self::published()->where('cate', $cateId);
}
/**
* 按关键词搜索文章
*/
public static function search($keyword)
{
return self::published()->where('title', 'like', '%' . $keyword . '%');
}
/**
* 更新阅读量
*/
public function incrementViews()
{
return $this->where('id', $this->id)->inc('views')->update();
}
/**
* 更新点赞量
*/
public function incrementLikes()
{
return $this->where('id', $this->id)->inc('likes')->update();
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章分类模型
*/
class ArticlesCategory extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_articles_category';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
// 时间戳字段名
protected $createTime = 'create_time';
protected $updateTime = 'update_time';
protected $deleteTime = 'delete_time';
// 字段类型转换
protected $type = [
'id' => 'integer',
'sort' => 'integer',
'status' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
/**
* 关联文章
*/
public function articles()
{
return $this->hasMany('app\index\model\Articles', 'cate', 'id');
}
/**
* 获取所有分类
*/
public static function allCategories()
{
return self::where('delete_time', null)->order('sort asc, id asc');
}
/**
* 获取启用的分类
*/
public static function enabled()
{
return self::where('status', 1)->where('delete_time', null);
}
/**
* 获取分类下的文章数量
*/
public function articleCount()
{
return $this->hasMany('app\index\model\Articles', 'cate', 'id')
->where('status', 1)
->where('delete_time', null)
->count();
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章分类模型
*/
class Banner extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_banner';
// 字段类型转换
protected $type = [
'id' => 'integer',
'title' => 'string',
'desc' => 'string',
'url' => 'string',
'image' => 'string',
'sort' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+44
View File
@@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章分类模型
*/
class Files extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_files';
// 字段类型转换
protected $type = [
'id' => 'integer',
'name' => 'string',
'type' => 'integer',
'cate' => 'integer',
'size' => 'integer',
'src' => 'string',
'uploader' => 'integer',
'md5' => 'string',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+38
View File
@@ -0,0 +1,38 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章分类模型
*/
class FilesCategory extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_files_category';
// 字段类型转换
protected $type = [
'id' => 'integer',
'name' => 'string',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+43
View File
@@ -0,0 +1,43 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章分类模型
*/
class FrontMenu extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_front_menu';
// 字段类型转换
protected $type = [
'id' => 'integer',
'pid' => 'integer',
'title' => 'string',
'type' => 'integer',
'path' => 'string',
'component_path' => 'string',
'desc' => 'string',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+41
View File
@@ -0,0 +1,41 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 单页模型
*/
class OnePage extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_onepage';
// 字段类型转换
protected $type = [
'id' => 'integer',
'title' => 'string',
'content' => 'string',
'path' => 'string',
'sort' => 'integer',
'status' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+40
View File
@@ -0,0 +1,40 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 操作日志模型
*/
class OperationLog extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_operation_log';
// 字段类型转换
protected $type = [
'id' => 'integer',
'user_id' => 'integer',
'status' => 'integer',
'execution_time' => 'float',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+48
View File
@@ -0,0 +1,48 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 系统菜单模型
*/
class SystemMenu extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_system_menu';
// 字段类型转换
protected $type = [
'id' => 'integer',
'pid' => 'integer',
'title' => 'string',
'path' => 'string',
'component_path' => 'string',
'icon' => 'string',
'sort' => 'integer',
'status' => 'integer',
'type' => 'integer',
'permission' => 'string',
'remark' => 'string',
'creater' => 'string',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+36
View File
@@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章分类模型
*/
class SystemSiteSettings extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_system_site_settings';
// 字段类型转换
protected $type = [
'id' => 'integer',
'label' => 'string',
'value' => 'string',
'sort' => 'integer',
'remark' => 'string',
];
}
+60
View File
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace app\service;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtService
{
private static string $secret = 'meitian@#!';
private static int $expire = 86400;
public static function generateToken(array $userInfo): string
{
$payload = [
'iss' => 'backapi.yunzer.cn',
'sub' => $userInfo['id'],
'iat' => time(),
'exp' => time() + self::$expire,
'user' => $userInfo
];
return JWT::encode($payload, self::$secret, 'HS256');
}
public static function verifyToken(string $token): ?array
{
try {
$decoded = JWT::decode($token, new Key(self::$secret, 'HS256'));
return (array)$decoded;
} catch (\Exception $e) {
return null;
}
}
public static function getUserFromHeader(string $authHeader): array
{
if (!preg_match('/Bearer\s+(.+)/i', $authHeader, $matches)) {
return ['id' => 0, 'account' => '', 'name' => ''];
}
$tokenData = self::verifyToken($matches[1]);
if (!$tokenData || !isset($tokenData['user'])) {
return ['id' => 0, 'account' => '', 'name' => ''];
}
return (array)$tokenData['user'];
}
public static function getSecret(): string
{
return self::$secret;
}
public static function setSecret(string $secret): void
{
self::$secret = $secret;
}
}