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

11
.env Normal file
View File

@ -0,0 +1,11 @@
APP_DEBUG = true
DB_TYPE = mysql
DB_HOST = 10.168.1.239
DB_NAME = official_website
DB_USER = official
DB_PASS = meitian@#!
DB_PORT = 3306
DB_CHARSET = utf8
DEFAULT_LANG = zh-cn

11
.example.env Normal file
View File

@ -0,0 +1,11 @@
APP_DEBUG = true
DB_TYPE = mysql
DB_HOST = 127.0.0.1
DB_NAME = test
DB_USER = username
DB_PASS = password
DB_PORT = 3306
DB_CHARSET = utf8
DEFAULT_LANG = zh-cn

42
.travis.yml Normal file
View File

@ -0,0 +1,42 @@
sudo: false
language: php
branches:
only:
- stable
cache:
directories:
- $HOME/.composer/cache
before_install:
- composer self-update
install:
- composer install --no-dev --no-interaction --ignore-platform-reqs
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
- composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
- composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
script:
- php think unit
deploy:
provider: releases
api_key:
secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
file:
- ThinkPHP_Core.zip
- ThinkPHP_Full.zip
skip_cleanup: true
on:
tags: true

32
LICENSE.txt Normal file
View File

@ -0,0 +1,32 @@
ThinkPHP遵循Apache2开源协议发布并提供免费使用。
版权所有Copyright © 2006-2025 by ThinkPHP (http://thinkphp.cn)
All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
Apache Licence是著名的非盈利开源组织Apache采用的协议。
该协议和BSD类似鼓励代码共享和尊重原作者的著作权
允许代码修改,再作为开源或商业软件发布。需要满足
的条件:
1 需要给代码的用户一份Apache Licence
2 如果你修改了代码,需要在被修改的文件中说明;
3 在延伸的代码中(修改和有源代码衍生的代码中)需要
带有原来代码中的协议,商标,专利声明和其他原来作者规
定需要包含的说明;
4 如果再发布的产品中包含一个Notice文件则在Notice文
件中需要带有本协议内容。你可以在Notice中增加自己的
许可但不可以表现为对Apache Licence构成更改。
具体的协议参考http://www.apache.org/licenses/LICENSE-2.0
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

77
README.md Normal file
View File

@ -0,0 +1,77 @@
![](https://www.thinkphp.cn/uploads/images/20230630/300c856765af4d8ae758c503185f8739.png)
ThinkPHP 8
===============
## 特性
* 基于PHP`8.0+`重构
* 升级`PSR`依赖
* 依赖`think-orm`3.0+版本
* 全新的`think-dumper`服务,支持远程调试
* 支持`6.0`/`6.1`无缝升级
> ThinkPHP8的运行环境要求PHP8.0+
现在开始,你可以使用官方提供的[ThinkChat](https://chat.topthink.com/)让你在学习ThinkPHP的旅途中享受私人AI助理服务
![](https://www.topthink.com/uploads/assistant/20230630/4d1a3f0ad2958b49bb8189b7ef824cb0.png)
ThinkPHP生态服务由[顶想云](https://www.topthink.com)TOPThink Cloud提供为生态提供专业的开发者服务和价值之选。
## 文档
[完全开发手册](https://doc.thinkphp.cn)
## 赞助
全新的[赞助计划](https://www.thinkphp.cn/sponsor)可以让你通过我们的网站、手册、欢迎页及GIT仓库获得巨大曝光同时提升企业的品牌声誉也更好保障ThinkPHP的可持续发展。
[![](https://www.thinkphp.cn/sponsor/special.svg)](https://www.thinkphp.cn/sponsor/special)
[![](https://www.thinkphp.cn/sponsor.svg)](https://www.thinkphp.cn/sponsor)
## 安装
~~~
composer create-project topthink/think tp
~~~
启动服务
~~~
cd tp
php think run
~~~
然后就可以在浏览器中访问
~~~
http://localhost:8000
~~~
如果需要更新框架使用
~~~
composer update topthink/framework
~~~
## 命名规范
`ThinkPHP`遵循PSR-2命名规范和PSR-4自动加载规范。
## 参与开发
直接提交PR或者Issue即可
## 版权信息
ThinkPHP遵循Apache2开源协议发布并提供免费使用。
本项目包含的第三方源码和二进制文件之版权信息另行标注。
版权所有Copyright © 2006-2024 by ThinkPHP (http://thinkphp.cn) All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
更多细节参阅 [LICENSE.txt](LICENSE.txt)

100
REDIS_SETUP.md Normal file
View File

@ -0,0 +1,100 @@
# Redis 配置说明
## 1. 安装 Redis 扩展
在项目根目录执行:
```bash
composer require predis/predis
```
或者如果使用 phpredis 扩展(需要 PHP Redis 扩展):
```bash
# 确保已安装 PHP Redis 扩展
# 然后直接使用,无需 composer 安装
```
## 2. 配置环境变量
在项目根目录创建 `.env` 文件(如果不存在),添加以下配置:
```env
# Redis 配置
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
REDIS_PREFIX=
# 缓存配置
CACHE_DRIVER=redis
# Session 配置
SESSION_DRIVER=redis
SESSION_PREFIX=
```
## 3. 配置文件说明
### cache.php
- 默认缓存驱动已设置为 `redis`
- Redis 连接配置已添加
### session.php
- Session 驱动已设置为 `redis`
- 使用 Redis 存储 Session
## 4. 使用说明
### 缓存使用
```php
use think\facade\Cache;
// 设置缓存
Cache::set('key', 'value', 3600);
// 获取缓存
$value = Cache::get('key');
// 删除缓存
Cache::delete('key');
```
### Session 使用
```php
use think\facade\Session;
// 设置 Session
Session::set('key', 'value');
// 获取 Session
$value = Session::get('key');
```
## 5. 验证 Redis 连接
确保 Redis 服务已启动:
```bash
# Windows
redis-server
# Linux/Mac
sudo systemctl start redis
# 或
redis-server
```
测试连接:
```bash
redis-cli ping
# 应该返回 PONG
```
## 6. 注意事项
1. 如果 Redis 未安装或连接失败,可以临时将 `CACHE_DRIVER``SESSION_DRIVER` 改回 `file`
2. Redis 密码如果为空,可以不设置 `REDIS_PASSWORD`
3. `REDIS_PREFIX` 用于区分不同项目的缓存,建议设置

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
app/admin/common.php Normal file
View File

@ -0,0 +1,2 @@
<?php
// 这是系统自动生成的公共文件

View File

@ -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()]);
}
}
}

View File

@ -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' => []
]);
}
}
}

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()
]);
}
}
}

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()]);
}
}
}

View File

@ -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;
}
}

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;
}
}

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', ''));
}
}

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;
}
}

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()
]);
}
}
}

View File

@ -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()
]);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

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()
]);
}
}
}

View File

@ -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
]);
}
}

View File

@ -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',
]);
}
}

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
app/admin/event.php Normal file
View File

@ -0,0 +1,5 @@
<?php
// 这是系统自动生成的event定义文件
return [
];

11
app/admin/middleware.php Normal file
View File

@ -0,0 +1,11 @@
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class,
];

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
app/admin/route/app.php Normal file
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');

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
app/index/common.php Normal file
View File

@ -0,0 +1,2 @@
<?php
// 这是系统自动生成的公共文件

View File

@ -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' => []
]);
}
}
}

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
]);
}
}
}

View File

@ -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
app/index/event.php Normal file
View File

@ -0,0 +1,5 @@
<?php
// 这是系统自动生成的event定义文件
return [
];

12
app/index/middleware.php Normal file
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
app/index/route/app.php Normal file
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
app/model/AdminUser.php Normal file
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',
];
}

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
app/model/Articles.php Normal file
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();
}
}

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
app/model/Banner.php Normal file
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
app/model/Files.php Normal file
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',
];
}

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
app/model/FrontMenu.php Normal file
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
app/model/OnePage.php Normal file
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',
];
}

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
app/model/SystemMenu.php Normal file
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',
];
}

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',
];
}

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;
}
}

51
composer.json Normal file
View File

@ -0,0 +1,51 @@
{
"name": "topthink/think",
"description": "the new thinkphp framework",
"type": "project",
"keywords": [
"framework",
"thinkphp",
"ORM"
],
"homepage": "https://www.thinkphp.cn/",
"license": "Apache-2.0",
"authors": [
{
"name": "liu21st",
"email": "liu21st@gmail.com"
},
{
"name": "yunwuxin",
"email": "448901948@qq.com"
}
],
"require": {
"php": ">=8.0.0",
"topthink/framework": "^8.0",
"topthink/think-orm": "^3.0|^4.0",
"topthink/think-filesystem": "^2.0|^3.0",
"topthink/think-multi-app": "^1.1",
"firebase/php-jwt": "^7.0"
},
"require-dev": {
"topthink/think-dumper": "^1.0",
"topthink/think-trace": "^2.0"
},
"autoload": {
"psr-4": {
"app\\": "app"
},
"psr-0": {
"": "extend/"
}
},
"config": {
"preferred-install": "dist"
},
"scripts": {
"post-autoload-dump": [
"@php think service:discover",
"@php think vendor:publish"
]
}
}

1290
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

35
config/app.php Normal file
View File

@ -0,0 +1,35 @@
<?php
// +----------------------------------------------------------------------
// | 应用设置
// +----------------------------------------------------------------------
return [
// 应用的命名空间
'app_namespace' => 'app',
// 是否启用路由
'with_route' => true,
// 默认应用
'default_app' => 'admin',
// 默认时区
'default_timezone' => 'Asia/Shanghai',
// 自动多应用模式
'auto_multi_app' => true,
// 应用快速访问(当访问的应用不存在时,使用默认应用)
'app_express' => true,
// 控制器后缀
'controller_suffix' => false,
// 应用映射(自动多应用模式有效)
'app_map' => [],
// 域名绑定(自动多应用模式有效)
'domain_bind' => [],
// 禁止URL访问的应用列表自动多应用模式有效
'deny_app_list' => [],
// 异常页面的模板文件
'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl',
// 错误显示信息,非调试模式有效
'error_message' => '页面错误!请稍后再试~',
// 显示错误信息
'show_error_msg' => false,
];

48
config/cache.php Normal file
View File

@ -0,0 +1,48 @@
<?php
// +----------------------------------------------------------------------
// | 缓存设置
// +----------------------------------------------------------------------
return [
// 默认缓存驱动
'default' => 'file',
// 缓存连接方式配置
'stores' => [
'file' => [
// 驱动方式
'type' => 'File',
// 缓存保存目录
'path' => '',
// 缓存前缀
'prefix' => '',
// 缓存有效期 0表示永久缓存
'expire' => 0,
// 缓存标签前缀
'tag_prefix' => 'tag:',
// 序列化机制 例如 ['serialize', 'unserialize']
'serialize' => [],
],
'redis' => [
// 驱动方式
'type' => 'redis',
// 服务器地址
'host' => '10.168.1.231',
// 端口
'port' => 6379,
// 密码
'password' => 'meitian123',
// 数据库索引
'select' => 0,
// 超时时间
'timeout' => 0,
// 缓存前缀
'prefix' => 'cache_',
// 缓存有效期 0表示永久缓存
'expire' => 0,
// 持久化连接
'persistent' => false,
],
],
];

9
config/console.php Normal file
View File

@ -0,0 +1,9 @@
<?php
// +----------------------------------------------------------------------
// | 控制台配置
// +----------------------------------------------------------------------
return [
// 指令定义
'commands' => [
],
];

20
config/cookie.php Normal file
View File

@ -0,0 +1,20 @@
<?php
// +----------------------------------------------------------------------
// | Cookie设置
// +----------------------------------------------------------------------
return [
// cookie 保存时间
'expire' => 0,
// cookie 保存路径
'path' => '/',
// cookie 有效域名
'domain' => '',
// cookie 启用安全传输
'secure' => false,
// httponly设置
'httponly' => false,
// 是否使用 setcookie
'setcookie' => true,
// samesite 设置,支持 'strict' 'lax'
'samesite' => '',
];

63
config/database.php Normal file
View File

@ -0,0 +1,63 @@
<?php
return [
// 默认使用的数据库连接配置
'default' => env('DB_DRIVER', 'mysql'),
// 自定义时间查询规则
'time_query_rule' => [],
// 自动写入时间戳字段
// true为自动识别类型 false关闭
// 字符串则明确指定时间字段类型 支持 int timestamp datetime date
'auto_timestamp' => true,
// 时间字段取出后的默认时间格式
'datetime_format' => 'Y-m-d H:i:s',
// 时间字段配置 配置格式create_time,update_time
'datetime_field' => '',
// 数据库连接配置信息
'connections' => [
'mysql' => [
// 数据库类型
'type' => env('DB_TYPE', 'mysql'),
// 服务器地址
'hostname' => env('DB_HOST', '127.0.0.1'),
// 数据库名
'database' => env('DB_NAME', ''),
// 用户名
'username' => env('DB_USER', 'root'),
// 密码
'password' => env('DB_PASS', ''),
// 端口
'hostport' => env('DB_PORT', '3306'),
// 数据库连接参数
'params' => [],
// 数据库编码
'charset' => env('DB_CHARSET', 'utf8mb4'),
// 数据库表前缀
'prefix' => env('DB_PREFIX', ''),
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
'deploy' => 0,
// 数据库读写是否分离 主从式有效
'rw_separate' => false,
// 读写分离后 主服务器数量
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// 是否严格检查字段是否存在
'fields_strict' => true,
// 是否需要断线重连
'break_reconnect' => false,
// 监听SQL
'trigger_sql' => env('APP_DEBUG', true),
// 开启字段缓存
'fields_cache' => false,
],
// 更多的数据库配置信息
],
];

24
config/filesystem.php Normal file
View File

@ -0,0 +1,24 @@
<?php
return [
// 默认磁盘
'default' => 'local',
// 磁盘列表
'disks' => [
'local' => [
'type' => 'local',
'root' => app()->getRuntimePath() . 'storage',
],
'public' => [
// 磁盘类型
'type' => 'local',
// 磁盘路径
'root' => app()->getRootPath() . 'public/storage',
// 磁盘路径对应的外部URL路径
'url' => '/storage',
// 可见性
'visibility' => 'public',
],
// 更多的磁盘配置信息
],
];

29
config/lang.php Normal file
View File

@ -0,0 +1,29 @@
<?php
// +----------------------------------------------------------------------
// | 多语言设置
// +----------------------------------------------------------------------
return [
// 默认语言
'default_lang' => env('DEFAULT_LANG', 'zh-cn'),
// 自动侦测浏览器语言
'auto_detect_browser' => true,
// 允许的语言列表
'allow_lang_list' => [],
// 多语言自动侦测变量名
'detect_var' => 'lang',
// 是否使用Cookie记录
'use_cookie' => true,
// 多语言cookie变量
'cookie_var' => 'think_lang',
// 多语言header变量
'header_var' => 'think-lang',
// 扩展语言包
'extend_list' => [],
// Accept-Language转义为对应语言包名称
'accept_language' => [
'zh-hans-cn' => 'zh-cn',
],
// 是否支持语言分组
'allow_group' => false,
];

45
config/log.php Normal file
View File

@ -0,0 +1,45 @@
<?php
// +----------------------------------------------------------------------
// | 日志设置
// +----------------------------------------------------------------------
return [
// 默认日志记录通道
'default' => 'file',
// 日志记录级别
'level' => [],
// 日志类型记录的通道 ['error'=>'email',...]
'type_channel' => [],
// 关闭全局日志写入
'close' => false,
// 全局日志处理 支持闭包
'processor' => null,
// 日志通道列表
'channels' => [
'file' => [
// 日志记录方式
'type' => 'File',
// 日志保存目录
'path' => '',
// 单文件日志写入
'single' => false,
// 独立日志级别
'apart_level' => [],
// 最大日志文件数量
'max_files' => 0,
// 使用JSON格式记录
'json' => false,
// 日志处理
'processor' => null,
// 关闭通道日志写入
'close' => false,
// 日志输出格式化
'format' => '[%s][%s] %s',
// 是否实时写入
'realtime_write' => false,
],
// 其它日志通道配置
],
];

12
config/middleware.php Normal file
View File

@ -0,0 +1,12 @@
<?php
// 中间件配置
return [
// 别名或分组
'alias' => [
'multi_app' => \think\app\MultiApp::class,
],
// 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
'priority' => [
\think\app\MultiApp::class,
],
];

47
config/route.php Normal file
View File

@ -0,0 +1,47 @@
<?php
// +----------------------------------------------------------------------
// | 路由设置
// +----------------------------------------------------------------------
return [
// pathinfo分隔符
'pathinfo_depr' => '/',
// URL伪静态后缀
'url_html_suffix' => '',
// URL普通方式参数 用于自动生成
'url_common_param' => true,
// 是否开启路由延迟解析
'url_lazy_route' => false,
// 是否强制使用路由
'url_route_must' => false,
// 合并路由规则
'route_rule_merge' => false,
// 路由是否完全匹配
'route_complete_match' => false,
// 访问控制器层名称
'controller_layer' => 'controller',
// 空控制器名
'empty_controller' => 'Error',
// 是否使用控制器后缀
'controller_suffix' => false,
// 默认的路由变量规则
'default_route_pattern' => '[\w\.]+',
// 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
'request_cache_key' => false,
// 请求缓存有效期
'request_cache_expire' => null,
// 全局请求缓存排除规则
'request_cache_except' => [],
// 默认控制器名
'default_controller' => 'Index',
// 默认操作名
'default_action' => 'index',
// 操作方法后缀
'action_suffix' => '',
// 默认JSONP格式返回的处理方法
'default_jsonp_handler' => 'jsonpReturn',
// 默认JSONP处理方法
'var_jsonp_handler' => 'callback',
// 操作方法的参数绑定方式 route get param
'action_bind_param' => 'param',
];

23
config/session.php Normal file
View File

@ -0,0 +1,23 @@
<?php
// +----------------------------------------------------------------------
// | 会话设置
// +----------------------------------------------------------------------
return [
// session name
'name' => 'PHPSESSID',
// SESSION_ID的提交变量,解决flash上传跨域
'var_session_id' => '',
// 驱动方式 支持file cache redis
'type' => 'file',
// 过期时间
'expire' => 1440,
// 前缀
'prefix' => '',
'save_path' => '/www/wwwroot/tp/runtime/admin/session',
// 缓存配置
'cache' => [
'expire' => 1440,
'prefix' => '',
],
];

10
config/trace.php Normal file
View File

@ -0,0 +1,10 @@
<?php
// +----------------------------------------------------------------------
// | Trace设置 开启调试模式后有效
// +----------------------------------------------------------------------
return [
// 内置Html和Console两种方式 支持扩展
'type' => 'Html',
// 读取的日志通道名
'channel' => '',
];

25
config/view.php Normal file
View File

@ -0,0 +1,25 @@
<?php
// +----------------------------------------------------------------------
// | 模板设置
// +----------------------------------------------------------------------
return [
// 模板引擎类型使用Think
'type' => 'Think',
// 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
'auto_rule' => 1,
// 模板目录名
'view_dir_name' => 'view',
// 模板后缀
'view_suffix' => 'html',
// 模板文件名分隔符
'view_depr' => DIRECTORY_SEPARATOR,
// 模板引擎普通标签开始标记
'tpl_begin' => '{',
// 模板引擎普通标签结束标记
'tpl_end' => '}',
// 标签库标签开始标记
'taglib_begin' => '{',
// 标签库标签结束标记
'taglib_end' => '}',
];

10
docs/jwt.md Normal file
View File

@ -0,0 +1,10 @@
use app\service\JwtService;
// 生成 Token
$token = JwtService::generateToken($userInfo);
// 验证 Token
$result = JwtService::verifyToken($token);
// 从 Header 获取用户
$user = JwtService::getUserFromHeader($authHeader);

338
docs/operation_log_usage.md Normal file
View File

@ -0,0 +1,338 @@
# 操作日志使用说明
## 1. 数据库表创建
执行以下 SQL 创建操作日志表:
```sql
-- 操作日志表
CREATE TABLE IF NOT EXISTS `mete_operation_log` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
`user_account` varchar(50) NOT NULL DEFAULT '' COMMENT '用户账号',
`user_name` varchar(50) NOT NULL DEFAULT '' COMMENT '用户姓名',
`module` varchar(50) NOT NULL DEFAULT '' COMMENT '操作模块(如:用户管理、文件管理等)',
`action` varchar(50) NOT NULL DEFAULT '' COMMENT '操作动作(如:添加、编辑、删除、查询等)',
`method` varchar(10) NOT NULL DEFAULT '' COMMENT '请求方法GET、POST、PUT、DELETE等',
`url` varchar(255) NOT NULL DEFAULT '' COMMENT '请求URL',
`ip` varchar(50) NOT NULL DEFAULT '' COMMENT '操作IP地址',
`user_agent` varchar(500) NOT NULL DEFAULT '' COMMENT '用户代理(浏览器信息)',
`request_data` text COMMENT '请求参数JSON格式',
`response_data` text COMMENT '响应数据JSON格式',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '操作状态1-成功0-失败',
`error_message` text COMMENT '错误信息(失败时记录)',
`execution_time` decimal(10,3) NOT NULL DEFAULT '0.000' COMMENT '执行时间(秒)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_module` (`module`),
KEY `idx_action` (`action`),
KEY `idx_create_time` (`create_time`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表';
```
## 2. 在控制器中记录操作日志
### 方法1使用 BaseController 的便捷方法(推荐)
所有继承 `BaseController` 的控制器都可以使用 `$this->logOperation()` 方法:
```php
class YourController extends BaseController
{
public function yourAction()
{
$startTime = microtime(true);
try {
// 你的业务逻辑
$result = $this->doSomething();
$executionTime = microtime(true) - $startTime;
// 记录成功日志
$this->logOperation(
'模块名称', // 如:用户管理、文件管理等
'操作动作', // 如:添加、编辑、删除、查询等
Request::param(), // 请求参数(可选,默认自动获取)
['id' => $result], // 响应数据
1, // 状态1-成功
'', // 错误信息(成功时为空)
$executionTime // 执行时间(可选)
);
return json(['code' => 200, 'msg' => '操作成功']);
} catch (\Exception $e) {
$executionTime = microtime(true) - $startTime;
// 记录失败日志
$this->logOperation(
'模块名称',
'操作动作',
Request::param(),
[],
0, // 状态0-失败
$e->getMessage(), // 错误信息
$executionTime
);
return json(['code' => 500, 'msg' => $e->getMessage()]);
}
}
}
```
### 方法2直接使用 OperationLogHelper
```php
use app\admin\common\OperationLogHelper;
use think\facade\Request;
class YourController extends BaseController
{
public function yourAction()
{
$startTime = microtime(true);
try {
// 你的业务逻辑
$result = $this->doSomething();
$executionTime = microtime(true) - $startTime;
// 记录成功日志
OperationLogHelper::log(
'模块名称',
'操作动作',
Request::param(),
['result' => 'success'],
1,
'',
$executionTime
);
return json(['code' => 200, 'msg' => '操作成功']);
} catch (\Exception $e) {
$executionTime = microtime(true) - $startTime;
OperationLogHelper::log(
'模块名称',
'操作动作',
Request::param(),
[],
0,
$e->getMessage(),
$executionTime
);
return json(['code' => 500, 'msg' => $e->getMessage()]);
}
}
}
```
## 3. 使用示例
### 示例1用户管理 - 添加用户
```php
public function addUser()
{
$startTime = microtime(true);
try {
$data = Request::param();
// ... 添加用户的业务逻辑
$userId = AdminUser::insertGetId($data);
$executionTime = microtime(true) - $startTime;
// 记录日志
$this->logOperation(
'用户管理',
'添加用户',
$data,
['id' => $userId],
1,
'',
$executionTime
);
return json(['code' => 200, 'msg' => '添加成功']);
} catch (\Exception $e) {
$executionTime = microtime(true) - $startTime;
$this->logOperation(
'用户管理',
'添加用户',
Request::param(),
[],
0,
$e->getMessage(),
$executionTime
);
return json(['code' => 500, 'msg' => $e->getMessage()]);
}
}
```
### 示例2文件管理 - 创建文件分组
```php
public function createFileCate()
{
$startTime = microtime(true);
try {
$data = Request::param();
$data['create_time'] = date('Y-m-d H:i:s');
$id = FilesCategory::insertGetId($data);
$executionTime = microtime(true) - $startTime;
// 记录日志
$this->logOperation(
'文件管理',
'创建文件分组',
$data,
['id' => $id],
1,
'',
$executionTime
);
return json([
'code' => 200,
'msg' => '新建文件分组成功',
'data' => ['id' => $id]
]);
} catch (\Exception $e) {
$executionTime = microtime(true) - $startTime;
$this->logOperation(
'文件管理',
'创建文件分组',
Request::param(),
[],
0,
$e->getMessage(),
$executionTime
);
return json([
'code' => 500,
'msg' => '新建文件分组失败: ' . $e->getMessage()
]);
}
}
```
### 示例3文件管理 - 删除文件
```php
public function deleteFile($id)
{
$startTime = microtime(true);
try {
// ... 删除文件的业务逻辑
Files::where('id', $id)->delete();
$executionTime = microtime(true) - $startTime;
$this->logOperation(
'文件管理',
'删除文件',
['id' => $id],
['result' => 'success'],
1,
'',
$executionTime
);
return json(['code' => 200, 'msg' => '删除成功']);
} catch (\Exception $e) {
$executionTime = microtime(true) - $startTime;
$this->logOperation(
'文件管理',
'删除文件',
['id' => $id],
[],
0,
$e->getMessage(),
$executionTime
);
return json(['code' => 500, 'msg' => $e->getMessage()]);
}
}
```
## 4. 模块和操作命名规范
### 模块名称建议:
- 用户管理
- 角色管理
- 文件管理
- 文章管理
- 菜单管理
- 系统设置
- Banner管理
- 单页管理
- 等等...
### 操作动作建议:
- 添加 / 创建
- 编辑 / 更新
- 删除
- 查询 / 查看
- 登录
- 登出
- 上传
- 下载
- 导出
- 导入
- 重命名
- 移动
- 修改密码
- 等等...
## 5. 注意事项
1. **敏感信息过滤**工具类会自动过滤密码、token 等敏感信息
2. **性能影响**:操作日志记录是异步的,不会影响主业务流程
3. **错误处理**:即使日志记录失败,也不会影响业务逻辑的执行
4. **执行时间**:建议使用 `microtime(true)` 来精确计算执行时间
5. **按需记录**:只在需要记录的操作中添加日志代码,避免过度记录
## 6. 前端查看
操作日志可以在后台管理系统的"系统管理 -> 操作日志"页面查看,支持:
- 关键词搜索
- 模块筛选
- 操作动作筛选
- 状态筛选
- 时间范围筛选
- 查看详情
- 删除和批量删除
## 7. 总结
**使用方式**
- ✅ **推荐使用** `$this->logOperation()` 方法BaseController 提供)
- ✅ **或者使用** `OperationLogHelper::log()` 静态方法
**使用场景**
- 重要的增删改操作
- 需要审计的操作
- 需要追踪问题的操作
**不需要记录**
- 简单的查询操作
- 频繁的操作(如心跳检测)
- 不重要的操作

View File

@ -0,0 +1,211 @@
# 操作日志使用说明(简化版)
## 快速开始
### 最简单的使用方式
```php
// 记录成功日志
$this->logSuccess('文件管理', '创建文件分组', ['id' => $id]);
// 记录失败日志
$this->logFail('文件管理', '创建文件分组', $e->getMessage());
```
就这么简单!只需要两行代码。
## 完整示例
### 示例1创建文件分组
```php
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()
]);
}
}
```
### 示例2删除文件
```php
public function deleteFile($id)
{
try {
Files::where('id', $id)->delete();
// 记录成功日志
$this->logSuccess('文件管理', '删除文件', ['id' => $id]);
return json(['code' => 200, 'msg' => '删除成功']);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '删除文件', $e->getMessage());
return json(['code' => 500, 'msg' => $e->getMessage()]);
}
}
```
### 示例3添加用户
```php
public function addUser()
{
try {
$data = Request::param();
$userId = AdminUser::insertGetId($data);
// 记录成功日志
$this->logSuccess('用户管理', '添加用户', ['id' => $userId]);
return json(['code' => 200, 'msg' => '添加成功']);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('用户管理', '添加用户', $e->getMessage());
return json(['code' => 500, 'msg' => $e->getMessage()]);
}
}
```
## 方法说明
### BaseController 提供的方法
所有继承 `BaseController` 的控制器都可以使用以下方法:
#### 1. `logSuccess()` - 记录成功日志
```php
$this->logSuccess(string $module, string $action, array $responseData = [])
```
**参数:**
- `$module`: 模块名称(如:文件管理、用户管理等)
- `$action`: 操作名称(如:创建文件分组、删除文件等)
- `$responseData`: 响应数据(可选,如:`['id' => 123]`
**示例:**
```php
$this->logSuccess('文件管理', '创建文件分组', ['id' => $id]);
```
#### 2. `logFail()` - 记录失败日志
```php
$this->logFail(string $module, string $action, string $errorMessage)
```
**参数:**
- `$module`: 模块名称
- `$action`: 操作名称
- `$errorMessage`: 错误信息
**示例:**
```php
$this->logFail('文件管理', '创建文件分组', $e->getMessage());
```
#### 3. `logOperation()` - 完整版(需要更多控制时使用)
```php
$this->logOperation(
string $module,
string $action,
array $requestData = [],
array $responseData = [],
int $status = 1,
string $errorMessage = ''
)
```
## 直接使用 OperationLogger 类
如果不继承 BaseController可以直接使用
```php
use app\admin\common\OperationLogger;
// 记录成功
OperationLogger::success('文件管理', '创建文件分组', ['id' => $id]);
// 记录失败
OperationLogger::fail('文件管理', '创建文件分组', $e->getMessage());
// 完整版
OperationLogger::record(
'文件管理',
'创建文件分组',
Request::param(), // 请求数据(可选)
['id' => $id], // 响应数据(可选)
1, // 状态1-成功0-失败
'' // 错误信息(可选)
);
```
## 模块和操作命名建议
### 模块名称:
- 文件管理
- 用户管理
- 角色管理
- 文章管理
- 菜单管理
- Banner管理
- 单页管理
- 系统设置
### 操作名称:
- 创建文件分组
- 重命名文件分组
- 删除文件分组
- 上传文件
- 删除文件
- 移动文件
- 添加用户
- 编辑用户
- 删除用户
- 修改密码
- 等等...
## 注意事项
1. **自动获取**:请求数据会自动获取,无需手动传递
2. **敏感信息过滤**密码、token 等敏感信息会自动过滤
3. **错误处理**:日志记录失败不会影响主业务流程
4. **按需记录**:只在需要记录的操作中添加日志代码
## 总结
**推荐使用方式:**
```php
// 成功时
$this->logSuccess('模块名称', '操作名称', ['响应数据']);
// 失败时
$this->logFail('模块名称', '操作名称', '错误信息');
```
就这么简单!

2
extend/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

14
public/.htaccess Normal file
View File

@ -0,0 +1,14 @@
# Apache/.htaccess CORS 配置
<IfModule mod_rewrite.c>
RewriteEngine On
# 处理 OPTIONS 预检请求
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]
# 添加 CORS 头
Header set Access-Control-Allow-Origin "http://localhost:5173"
Header set Access-Control-Allow-Credentials "true"
Header set Access-Control-Allow-Methods "GET, POST, PATCH, PUT, DELETE, OPTIONS"
Header set Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With"
</IfModule>

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

24
public/index.php Normal file
View File

@ -0,0 +1,24 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// [ 应用入口文件 ]
namespace think;
require __DIR__ . '/../vendor/autoload.php';
// 执行HTTP应用并响应
$http = (new App())->http;
$response = $http->run();
$response->send();
$http->end($response);

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

19
public/router.php Normal file
View File

@ -0,0 +1,19 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// $Id$
if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) {
return false;
} else {
$_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php';
require __DIR__ . "/index.php";
}

2
public/static/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Some files were not shown because too many files have changed in this diff Show More