This commit is contained in:
u2nyakim 2025-08-22 10:11:22 +08:00
commit a9c2455857
102 changed files with 4186 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
*.log
.env
composer.phar
composer.lock
.DS_Store
Thumbs.db
/.idea
/.vscode
/vendor
/.settings
/.buildpath
/.project

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)

1
app/.htaccess Normal file
View File

@ -0,0 +1 @@
deny from all

18
app/AppService.php Normal file
View File

@ -0,0 +1,18 @@
<?php
declare (strict_types=1);
namespace app;
use app\http\middleware\ContextMiddleware;
use think\Service;
/**
* 应用服务类
*/
class AppService extends Service
{
public function register(): void
{
$this->app->middleware->unshift(ContextMiddleware::class, 'route');
}
}

145
app/BaseController.php Normal file
View File

@ -0,0 +1,145 @@
<?php
declare (strict_types=1);
namespace app;
use app\http\HttpStatus;
use stdClass;
use think\App;
use think\exception\ValidateException;
use think\Paginator;
use think\response\Json;
use think\Validate;
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* Request实例
* @var \app\Request
*/
protected $request;
/**
* 应用实例
* @var \think\App
*/
protected $app;
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [];
readonly protected ?http\HttpAuth $auth;
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->app = $app;
$this->request = $this->app->request;
$this->auth = $this->app->request->getAuth();
// 控制器初始化
$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 $message
* @param mixed $data
* @return Json
*/
protected function writeSuccess(string $message = '', mixed $data = new stdClass()): Json
{
return $this->writeJson(HttpStatus::API_SUCCESS->value, $message ?: HttpStatus::message(HttpStatus::API_SUCCESS), $data);
}
/**
* 接口调用失败
* @param string $message
* @param mixed $data
* @return Json
*/
protected function writeError(string $message = '', mixed $data = new stdClass()): Json
{
return $this->writeJson(HttpStatus::API_ERROR->value, $message ?: HttpStatus::message(HttpStatus::API_ERROR), $data);
}
/**
* 状态码
* @param int $code
* @param string $message
* @param mixed $data
* @return Json
*/
protected function writeJson(int $code = 200, string $message = '', mixed $data = null): Json
{
if($data instanceof Paginator) {
$data = [
'count'=> $data->total(),
'list' => $data->items()
];
}
return json(array(
"code" => $code,
"data" => $data,
"message" => $message,
));
}
}

19
app/BaseModel.php Normal file
View File

@ -0,0 +1,19 @@
<?php
declare (strict_types=1);
namespace app;
use think\Model;
/**
* 模型基础类
*/
abstract class BaseModel extends Model
{
protected function getOptions(): array
{
return [
'convertNameToCamel'=> true
];
}
}

58
app/ExceptionHandle.php Normal file
View File

@ -0,0 +1,58 @@
<?php
namespace app;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\Handle;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\Response;
use Throwable;
/**
* 应用异常处理类
*/
class ExceptionHandle extends Handle
{
/**
* 不需要记录信息(日志)的异常类列表
* @var array
*/
protected $ignoreReport = [
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
DataNotFoundException::class,
ValidateException::class,
];
/**
* 记录异常信息(包括日志或者其它方式记录)
*
* @access public
* @param Throwable $exception
* @return void
*/
public function report(Throwable $exception): void
{
// 使用内置的方式记录异常日志
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @access public
* @param \think\Request $request
* @param Throwable $e
* @return Response
*/
public function render($request, Throwable $e): Response
{
// 添加自定义异常处理机制
// 其他错误交给系统处理
return parent::render($request, $e);
}
}

59
app/Request.php Normal file
View File

@ -0,0 +1,59 @@
<?php
namespace app;
// 应用请求对象类
use app\http\HttpAuth;
use app\http\HttpClient;
use think\helper\Macroable;
/**
* @method static HttpClient getClient() 获取客户端信息
*/
class Request extends \think\Request
{
use Macroable;
/**
* @var string
*/
public string $contextId = '';
/**
* @var HttpAuth
*/
private readonly HttpAuth $auth;
/**
* 设置Auth
* @param HttpAuth $auth
* @return Request
*/
public function setAuth(HttpAuth $auth): self
{
$this->auth = $auth;
return $this;
}
public function getAuth(): ?HttpAuth
{
// var_dump($this->auth);
return $this->auth ?? null;
}
public function getBrowser(): string
{
return get_agent_browser($this->server('HTTP_USER_AGENT'));
}
public function getDevice(): string
{
return get_agent_device($this->server('HTTP_USER_AGENT'));
}
public function getOs(): string
{
return get_agent_os($this->server('HTTP_USER_AGENT'));
}
}

53
app/RequestClient.php Normal file
View File

@ -0,0 +1,53 @@
<?php
namespace app;
use Exception;
use app\validate\auth\ClientValidate;
use think\exception\ValidateException;
/**
* 请求客户端信息
*/
readonly class RequestClient
{
const string CLIENT_KEY = "KOhjymFZ2Ofcwvlp";
public string $name;
public string $id;
public string $version;
public array $data;
private string $sign;
public function __construct(string $clientName, string $clientId, string $clientVersion = '', string $sign = '')
{
$this->name = $clientName; // 客户端名称(PAdmin/IAdmin)
$this->id = $clientId; // 客户端ID(前端生成的唯一标识)
$this->version = $clientVersion; // 客户端版本号
$this->sign = $sign; // 携带签名数据
try {
$this->validate();
} catch (Exception $e) {
throw new ValidateException($e->getMessage());
}
}
private function validate(): void
{
$data = [];
if ($this->sign) {
$encryptedData = base64_decode($this->sign);
$dataStr = openssl_decrypt($encryptedData, 'AES-128-ECB', RequestClient::CLIENT_KEY, OPENSSL_RAW_DATA);
$data = json_decode($dataStr, true);
}
$this->data = $data;
validate(ClientValidate::class)->check([
'name' => $this->name,
'id' => $this->id,
'version' => $this->version,
]);
}
}

221
app/common.php Normal file
View File

@ -0,0 +1,221 @@
<?php
// 应用公共文件
use app\service\DictionaryService;
use Hashids\Hashids;
use think\Collection;
use think\facade\Event;
use think\Model;
/**
* 写入日志
* @param array|string $event
* @param string $message
* @param mixed|null $data
* @return void
*/
function security_log_record(array|string $event, string $message, mixed $data = null): void
{
if (is_string($event)) {
$event = explode('.', $event);
}
if ($data instanceof Model) {
$data = $data->toArray();
}
if ($data instanceof Collection) {
$data = $data->toArray();
}
Event::trigger('sys:securityLogRecord', [$event, $message, $data]);
}
/**
* 获取字典内容
* @param string $name
* @param $default
* @return mixed
*/
function dict_get(string $name, $default = null): mixed
{
return dict()->get($name, $default);
}
/**
* 获取字典服务
* @return DictionaryService
*/
function dict(): DictionaryService
{
return app()->dict;
}
/**
* 将扁平数组列表转换为树形结构
* @param array $items 原始数组
* @param string $idKey ID字段名
* @param string $parentKey 父ID字段名
* @param string $childrenKey 子节点字段名
* @return array 树形结构
*/
function list_build_tree(array $items, string $idKey = 'id', string $parentKey = 'pid', string $childrenKey = 'children'): array
{
// 创建ID映射和结果树
$tree = [];
$map = [];
// 创建ID到数组元素的引用映射
foreach ($items as &$item) {
$map[$item[$idKey]] = &$item;
$item[$childrenKey] = []; // 初始化children
}
// 构建树结构
foreach ($items as &$item) {
$parentId = $item[$parentKey];
if (isset($map[$parentId])) {
// 当前项有父节点添加到父节点的children
$map[$parentId][$childrenKey][] = &$item;
} else {
// 当前项是根节点,添加到树
$tree[] = &$item;
}
}
// 清理引用
unset($item);
return $tree;
}
/**
* @param int $minHashLength
* @return Hashids
*/
function hashids(int $minHashLength = 6): Hashids
{
$salt = (string)config('app.default_salt', '');
return new Hashids($salt, $minHashLength);
}
function snowflake_id(int $machineId = null): int
{
// 时间戳 42字节
$time = floor(microtime(true) * 1000);
// 当前时间 与 开始时间 差值
$time -= 1641862815726;
$base = decbin(1099511627775 + $time);
if ($machineId) {
$machineId = str_pad(decbin($machineId), 10, '0', STR_PAD_LEFT);
}
$random = str_pad(decbin(mt_rand(0, 4095)), 12, '0', STR_PAD_LEFT);
return bindec($base . $machineId . $random);
}
function guid($namespace = ''): string
{
$uid = uniqid("", true);
$data = $namespace;
$data .= $_SERVER['REQUEST_TIME'] ?? '';
$data .= $_SERVER['HTTP_USER_AGENT'] ?? '';
$data .= $_SERVER['LOCAL_ADDR'] ?? '';
$data .= $_SERVER['LOCAL_PORT'] ?? '';
$data .= $_SERVER['REMOTE_ADDR'] ?? '';
$data .= $_SERVER['REMOTE_PORT'] ?? '';
$hash = strtoupper(hash('ripemd128', $uid . md5($data)));
return
substr($hash, 0, 8) .
'-' .
substr($hash, 8, 4) .
'-' .
substr($hash, 12, 4) .
'-' .
substr($hash, 16, 4) .
'-' .
substr($hash, 20, 12);
}
function unique_str(): string
{
return md5(uniqid());
}
function get_agent_browser($userAgent = ''): string
{
if (str_contains($userAgent, 'MicroMessenger')) {
return 'Wechat';
} elseif (str_contains($userAgent, 'QQBrowser')) {
return 'QQ';
} elseif (str_contains($userAgent, 'MSIE') || str_contains($userAgent, 'Trident/7.0;') || str_contains($userAgent, 'rv:11.0')) {
return 'Explorer';
} elseif (str_contains($userAgent, 'Firefox')) {
return 'Firefox';
} elseif (str_contains($userAgent, 'Edg')) {
return 'Edge';
} elseif (str_contains($userAgent, 'Chrome')) {
return 'Chrome';
} elseif (str_contains($userAgent, 'Safari')) {
return 'Safari';
} elseif (str_contains($userAgent, 'Opera Mini') || str_contains($userAgent, 'Opera')) {
return 'Opera';
} else {
return 'Unknown';
}
}
function get_agent_device($agent = ''): string
{
if (stristr($agent, 'iPad')) {
$fb_fs = "iPad";
} else if (preg_match('/Android (([0-9_.]{1,3})+)/i', $agent, $version)) {
$fb_fs = "手机(Android " . $version[1] . ")";
} else if (stristr($agent, 'Linux')) {
$fb_fs = "电脑(Linux)";
} else if (preg_match('/iPhone OS (([0-9_.]{1,3})+)/i', $agent, $version)) {
$fb_fs = "手机(iPhone " . $version[1] . ")";
} else if (preg_match('/Mac OS X (([0-9_.]{1,5})+)/i', $agent, $version)) {
$fb_fs = "电脑(OS X " . $version[1] . ")";
} else if (preg_match('/unix/i', $agent)) {
$fb_fs = "Unix";
} else if (preg_match('/windows/i', $agent)) {
$fb_fs = "电脑(Windows)";
} else {
$fb_fs = "Unknown";
}
return $fb_fs;
}
function get_agent_os($userAgent): string
{
if (preg_match('/windows/i', $userAgent)) {
return 'Windows';
} elseif (preg_match('/iPhone/i', $userAgent)) {
return 'iOS';
} elseif (preg_match('/HarmonyOS/i', $userAgent)) {
return 'Android';
} elseif (preg_match('/Android/i', $userAgent)) {
return 'Android';
} elseif (preg_match('/mac/i', $userAgent)) {
return 'Mac';
} else {
return 'Unknown';
}
}
function agent_is_crawler($userAgent): bool
{
$crawlers = [
'googlebot', 'bingbot', 'slurp', 'baidu', 'duckduckbot',
'yandexbot', 'sogou', 'exabot', 'ia_archiver', 'facebot',
'facebookexternalhit'
];
foreach ($crawlers as $crawler) {
if (str_contains($userAgent, $crawler)) {
return true;
}
}
return false;
}

30
app/controller/Index.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
public function index()
{
// $user = SysUser::with(['roles.menus'=> function($query) {
// $query->where('deleted', 0);
// }])->find("1");
//
// $menus = $user->getMenus();
// dump($user);
// dump($menus);
// dd($user->getMenus(true));
return '';
return '<style>*{ padding: 0; margin: 0; }</style><iframe src="https://www.thinkphp.cn/welcome?version=' . \think\facade\App::version() . '" width="100%" height="100%" frameborder="0" scrolling="auto"></iframe>';
}
public function hello($name = 'ThinkPHP8')
{
return 'hello,' . $name;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace app\controller\admin\auth;
use app\BaseController;
use app\entity\SysUser;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\response\Json;
/**
* 用户Auth控制器
*/
class AuthController extends BaseController
{
/**
* 查询用户信息
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function user(): Json
{
$user = SysUser::findOrFail($this->auth->userId);
$data = $user->append(['authorities', 'roles'])->toArray();
return $this->writeSuccess('', $data);
}
/**
* 退出登录
* @return Json
*/
public function logout(): Json
{
return $this->writeSuccess('退出成功');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace app\controller\admin\auth;
use app\BaseController;
use app\service\admin\LoginService;
use think\exception\ValidateException;
class LoginController extends BaseController
{
public function captcha()
{
$captcha = file_get_contents("https://v2.eleadmin.com/api/captcha");
$captchaData = json_decode($captcha, true);
return $this->writeSuccess('登录成功', [
'base64'=> $captchaData['data']['base64'] ?? '',
'text'=> $captchaData['data']['text'] ?? '',
]);
}
public function index()
{
$username = $this->request->post('username', '');
$password = $this->request->post('password', '');
$remember = $this->request->post('remember', false);
$code = $this->request->post('code', '');
$loginService = new LoginService();
try {
$result = $loginService->login($this->request, $this->request->getClient(), $username, $password, $code, $remember);
} catch (ValidateException $e) {
return $this->writeError($e->getError());
}
return $this->writeSuccess('登录成功', ['user' => $result['user'], 'access_token' => $result['access_token']]);
}
}

View File

@ -0,0 +1,135 @@
<?php
namespace app\controller\admin\system;
use app\BaseController;
use app\entity\SysDictionary;
use app\entity\SysDictionaryData;
use app\service\CurdService;
use think\db\exception\DbException;
use think\response\Json;
class DictionaryController extends BaseController
{
public function lists(): Json
{
$lists = CurdService::getList($this->request, new SysDictionary(), ['sort_number' => 'desc']);
return $this->writeSuccess('success', $lists);
}
public function update()
{
$data = $this->request->put([
'dictId' => 0,
'dictCode' => null,
'dictName' => null,
'sortNumber' => 100,
'comments' => ''
]);
SysDictionary::findOrFail($data['dictId'])->save([
'dict_code' => $data['dictCode'],
'dict_name' => $data['dictName'],
'sort_number' => $data['sortNumber'],
'comments' => $data['comments']
]);
return $this->writeSuccess('修改成功');
}
public function remove(SysDictionary $model)
{
$model->delete();
return $this->writeSuccess('删除成功');
}
public function add()
{
$data = $this->request->post([
'dictCode' => null,
'dictName' => null,
'sortNumber' => 100,
'comments' => ''
]);
$sysDictionary = new SysDictionary();
$sysDictionary->save([
'dict_code' => $data['dictCode'],
'dict_name' => $data['dictName'],
'sort_number' => $data['sortNumber'],
'comments' => $data['comments']
]);
return $this->writeSuccess('添加成功');
}
public function dataAdd()
{
$data = $this->request->post([
'dictId' => null,
'dictDataCode' => null,
'dictDataName' => null,
'sortNumber' => 100,
'comments' => ''
]);
$sysDictionary = new SysDictionaryData();
$sysDictionary->save([
'dict_id' => $data['dictId'],
'dict_data_code' => $data['dictDataCode'],
'dict_data_name' => $data['dictDataName'],
'sort_number' => $data['sortNumber'],
'comments' => $data['comments']
]);
return $this->writeSuccess('添加成功');
}
public function dataUpdate()
{
$data = $this->request->put([
'dictDataId' => 0,
'dictId' => null,
'dictDataCode' => null,
'dictDataName' => null,
'sortNumber' => 100,
'comments' => ''
]);
SysDictionaryData::findOrFail($data['dictDataId'])->save([
'dict_id' => $data['dictId'],
'dict_data_code' => $data['dictDataCode'],
'dict_data_name' => $data['dictDataName'],
'sort_number' => $data['sortNumber'],
'comments' => $data['comments']
]);
return $this->writeSuccess('修改成功');
}
public function dataBatchRemove()
{
$data = $this->request->delete();
SysDictionaryData::destroy($data);
return $this->writeSuccess('删除成功');
}
public function dataLists(): Json
{
$dictCode = $this->request->param('dictCode/s', '');
$data = SysDictionary::dictCodeData($dictCode);
return $this->writeSuccess('success', $data);
}
/**
* 查询字典集列表
* @return Json
* @throws DbException
*/
public function dataPage(): Json
{
$paginate = CurdService::getPaginate($this->request, SysDictionaryData::withSearch(['dictId', 'dictDataName', 'dictDataCode'], [
'dictId' => $this->request->get('dictId/d', 0),
'dictDataName' => $this->request->get('dictDataName/s', ''),
'dictDataCode' => $this->request->get('dictDataCode/s', ''),
]));
return $this->writeSuccess('success', $paginate);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace app\controller\admin\system;
use app\BaseController;
use app\entity\SysDictionary;
use app\entity\SysDictionaryData;
use app\service\CurdService;
use think\db\exception\DbException;
use think\response\Json;
class DictionaryDataController extends BaseController
{
public function add()
{
$data = $this->request->post([
'dictId' => null,
'dictDataCode' => null,
'dictDataName' => null,
'sortNumber' => 100,
'comments' => ''
]);
$sysDictionary = new SysDictionaryData();
$sysDictionary->save([
'dict_id' => $data['dictId'],
'dict_data_code' => $data['dictDataCode'],
'dict_data_name' => $data['dictDataName'],
'sort_number' => $data['sortNumber'],
'comments' => $data['comments']
]);
return $this->writeSuccess('添加成功');
}
public function update()
{
$data = $this->request->put([
'dictDataId' => 0,
'dictId' => null,
'dictDataCode' => null,
'dictDataName' => null,
'sortNumber' => 100,
'comments' => ''
]);
SysDictionaryData::findOrFail($data['dictDataId'])->save([
'dict_id' => $data['dictId'],
'dict_data_code' => $data['dictDataCode'],
'dict_data_name' => $data['dictDataName'],
'sort_number' => $data['sortNumber'],
'comments' => $data['comments']
]);
return $this->writeSuccess('修改成功');
}
public function batchRemove()
{
$data = $this->request->delete();
SysDictionaryData::destroy($data);
return $this->writeSuccess('删除成功');
}
public function lists(): Json
{
$dictCode = $this->request->param('dictCode/s', '');
$data = SysDictionary::dictCodeData($dictCode);
return $this->writeSuccess('success', $data);
}
/**
* 查询字典集列表
* @return Json
* @throws DbException
*/
public function page(): Json
{
$paginate = CurdService::getPaginate($this->request, SysDictionaryData::withSearch(['dictId', 'dictDataName', 'dictDataCode'], [
'dictId' => $this->request->get('dictId/d', 0),
'dictDataName' => $this->request->get('dictDataName/s', ''),
'dictDataCode' => $this->request->get('dictDataCode/s', ''),
]));
return $this->writeSuccess('success', $paginate);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace app\controller\admin\system;
use app\BaseController;
use app\entity\SysFileRecord;
use app\service\CurdService;
use think\db\exception\DbException;
use think\response\Json;
class FileController extends BaseController
{
/**
* 分页查询账号登录记录
* @return Json
* @throws DbException
*/
public function page(): Json
{
$model = SysFileRecord::with(['createUser'])->withSearch(['name','path','createNickname'], [
'name'=> $this->request->param('name/s',''),
'path'=> $this->request->param('path/s',''),
'createNickname'=> $this->request->param('createNickname/s',''),
'createTime' => [
$this->request->get('createTimeStart/s', ''),
$this->request->get('createTimeEnd/s', '')
],
]);
$paginate = CurdService::getPaginate($this->request, $model);
return $this->writeSuccess('ok', $paginate);
}
public function batchRemove()
{
$data = $this->request->delete();
SysFileRecord::destroy($data);
return $this->writeSuccess('删除成功');
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace app\controller\admin\system;
use app\BaseController;
use app\entity\SysLoginRecord;
use app\service\CurdService;
use think\db\exception\DbException;
use think\response\Json;
class LoginRecordController extends BaseController
{
/**
* 分页查询账号登录记录
* @return Json
* @throws DbException
*/
public function page(): Json
{
$model = SysLoginRecord::withSearch(['username', 'nickname', 'loginType', 'createTime'], [
'username' => $this->request->get('username/s', ''),
'nickname' => $this->request->get('nickname/s', ''),
'loginType' => $this->request->get('loginType/d', 0),
'createTime' => [
$this->request->get('createTimeStart/s', ''),
$this->request->get('createTimeEnd/s', '')
],
]);
$paginate = CurdService::getPaginate($this->request, $model);
return $this->writeSuccess('ok', $paginate);
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace app\controller\admin\system;
use app\BaseController;
use app\entity\SysMenu;
use app\service\CurdService;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\response\Json;
class MenuController extends BaseController
{
/**
* 查询菜单列表
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function list(): Json
{
$model = SysMenu::withSearch(['title', 'path', 'authority'], [
'title' => $this->request->get('title/s', ''),
'path' => $this->request->get('path/s', ''),
'authority' => $this->request->get('authority/s', ''),
]);
$lists = CurdService::getList($this->request, $model, ['sort_number' => 'asc']);
return $this->writeSuccess('ok', $lists);
}
/**
* 添加菜单列表
* @return Json
*/
public function add(): Json
{
$data = $this->request->post([
'parentId' => 0,
'title' => '',
'menuType' => '',
'openType' => '',
'icon' => '',
'path' => '',
'component' => '',
'authority' => '',
'sortNumber' => 0,
'hide' => 0,
'meta' => '',
]);
$user = new SysMenu();
$user->save([
'parent_id' => $data['parentId'],
'title' => $data['title'],
'menu_type' => $data['menuType'],
'open_type' => $data['openType'],
'icon' => $data['icon'],
'path' => $data['path'],
'component' => $data['component'],
'authority' => $data['authority'],
'sortNumber' => $data['sortNumber'],
'hide' => $data['hide'],
'meta' => $data['meta'],
]);
return $this->writeSuccess('添加成功');
}
/**
* 删除菜单
* @param SysMenu $model
* @return Json
*/
public function remove(SysMenu $model)
{
$model->delete();
return $this->writeSuccess('删除成功');
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace app\controller\admin\system;
use app\BaseController;
use app\entity\SysOperateRecord;
use app\service\CurdService;
use think\db\exception\DbException;
use think\response\Json;
class OperateRecordController extends BaseController
{
/**
* 分页查询操作日志记录
* @return Json
* @throws DbException
*/
public function page(): Json
{
$model = SysOperateRecord::withSearch([], [
'username' => $this->request->get('username/s', ''),
'nickname' => $this->request->get('nickname/s', ''),
'loginType' => $this->request->get('loginType/d', 0),
'createTime' => [
$this->request->get('createTimeStart/s', ''),
$this->request->get('createTimeEnd/s', '')
],
]);
$paginate = CurdService::getPaginate($this->request, $model);
return $this->writeSuccess('ok', $paginate);
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace app\controller\admin\system;
use app\BaseController;
use app\entity\SysOrganization;
use app\service\CurdService;
use think\response\Json;
class OrganizationController extends BaseController
{
public function lists(): Json
{
$model = SysOrganization::withSearch(['organizationType', 'organizationName'], [
'organizationType' => $this->request->get('organizationType/d', 0),
'organizationName' => $this->request->get('organizationName/s', ''),
])->append(['organizationTypeName']);
$lists = CurdService::getList($this->request, $model);
return $this->writeSuccess('ok', $lists);
}
public function update()
{
$data = $this->request->post([
'parentId' => 0,
'organizationType' => 0,
'sortNumber' => 100,
'organizationName' => '',
'organizationFullName' => '',
'organizationCode' => '',
'comments' => '',
'organizationId' => ''
]);
$sysDictionary = SysOrganization::findOrFail($data['organizationId']);
$sysDictionary->save([
'parent_id' => $data['parentId'],
'sort_number' => $data['sortNumber'],
'organization_type' => $data['organizationType'],
'organization_name' => $data['organizationName'],
'organization_full_name' => $data['organizationFullName'],
'organization_code' => $data['organizationCode'],
'comments' => $data['comments'],
]);
return $this->writeSuccess('更新成功');
}
public function add()
{
$data = $this->request->post([
'parentId' => 0,
'organizationType' => 0,
'sortNumber' => 100,
'organizationName' => '',
'organizationFullName' => '',
'organizationCode' => '',
'comments' => '',
]);
$sysDictionary = new SysOrganization();
$sysDictionary->save([
'parent_id' => $data['parentId'],
'sort_number' => $data['sortNumber'],
'organization_type' => $data['organizationType'],
'organization_name' => $data['organizationName'],
'organization_full_name' => $data['organizationFullName'],
'organization_code' => $data['organizationCode'],
'comments' => $data['comments'],
]);
return $this->writeSuccess('添加成功');
}
public function remove(SysOrganization $model)
{
$model->delete();
return $this->writeSuccess('删除成功');
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace app\controller\admin\system;
use app\BaseController;
use app\entity\SysRequestRecord;
use app\service\CurdService;
use think\db\exception\DbException;
use think\response\Json;
class RequestRecordController extends BaseController
{
/**
* 分页查询请求日志记录
* @return Json
* @throws DbException
*/
public function page(): Json
{
$model = SysRequestRecord::withSearch(['createTime'], [
'createTime' => [
$this->request->get('createTimeStart/s', ''),
$this->request->get('createTimeEnd/s', '')
],
]);
$paginate = CurdService::getPaginate($this->request, $model);
return $this->writeSuccess('ok', $paginate);
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace app\controller\admin\system;
use app\BaseController;
use app\entity\SysMenu;
use app\entity\SysRole;
use app\service\CurdService;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\response\Json;
class RoleController extends BaseController
{
/**
* 查询角色列表
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function list(): Json
{
$lists = CurdService::getList($this->request, new SysRole());
return $this->writeSuccess('ok', $lists);
}
/**
* 查询角色菜单
* @param $role_id
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getMenu($role_id)
{
$role = SysRole::findOrFail($role_id);
$menuIds = $role->menus->column('menu_id');
$menus = SysMenu::select()->each(function (SysMenu $menu) use ($menuIds) {
$menu->checked = in_array($menu['menu_id'], $menuIds);
});
return $this->writeSuccess('ok', $menus);
}
/**
* 更新角色菜单
* @param $role_id
* @return Json
* @throws DataNotFoundException
* @throws ModelNotFoundException
*/
public function updateMenu($role_id)
{
$menus = $this->request->put();
$role = SysRole::findOrFail($role_id);
$role->menus()->attach($menus);
return $this->writeSuccess('操作成功');
}
/**
* 分页查询角色列表
* @return Json
* @throws DbException
*/
public function page(): Json
{
$model = SysRole::withSearch(['roleName', 'roleCode'], [
'roleName' => $this->request->get('roleName/s', ''),
'roleCode' => $this->request->get('roleCode/s', ''),
]);
$paginate = CurdService::getPaginate($this->request, $model);
return $this->writeSuccess('ok', $paginate);
}
/**
* 批量删除角色
* @return Json
*/
public function removeRoles(): Json
{
$data = $this->request->delete();
SysRole::destroy($data);
return $this->writeSuccess('删除成功');
}
}

View File

@ -0,0 +1,170 @@
<?php
namespace app\controller\admin\system;
use app\BaseController;
use app\entity\SysUser;
use app\service\CurdService;
use think\db\exception\{DataNotFoundException, DbException, ModelNotFoundException};
use think\response\Json;
class UserController extends BaseController
{
/**
* 分页获取用户列表
* @return Json
* @throws DbException
*/
public function page(): Json
{
$model = SysUser::with(['roles'])
->withSearch(['organizationId', 'username', 'nickname', 'sex'], [
'organizationId' => $this->request->get('organizationId/d', 0),
'username' => $this->request->get('username/s', ''),
'nickname' => $this->request->get('nickname/s', ''),
'sex' => $this->request->get('sex/d', 0),
])->append(['sexName']);
$paginate = CurdService::getPaginate($this->request, $model);
return $this->writeSuccess('success', $paginate);
}
/**
* 新增用户
* @return Json
*/
public function add(): Json
{
$data = $this->request->post([
'birthday' => null,
'username' => '',
'email' => '',
'nickname' => '',
'introduction' => '',
'organizationId' => 0,
'phone' => '',
'sex' => '',
'status' => 0,
'roles' => [],
]);
$data['roles'] = array_column((array)$data['roles'], 'roleId');
$user = new SysUser();
$user->save([
'birthday' => $data['birthday'],
'username' => $data['username'],
'nickname' => $data['nickname'],
'introduction' => $data['introduction'],
'organization_id' => $data['organizationId'],
'phone' => $data['phone'],
'sex' => $data['sex'],
'status' => $data['status'],
'email' => $data['email'],
'password' => password_hash($data['password'], PASSWORD_DEFAULT),
]);
$user->roles()->saveAll($data['roles']);
return $this->writeSuccess('添加成功');
}
/**
* 更新用户信息
* @return Json
* @throws DataNotFoundException
* @throws ModelNotFoundException
*/
public function update(): Json
{
$data = $this->request->put([
'userId' => 0,
'birthday' => null,
'username' => '',
'email' => '',
'nickname' => '',
'introduction' => '',
'organizationId' => 0,
'phone' => '',
'sex' => '',
'status' => 0,
'roles' => [],
]);
$data['roles'] = array_column((array)$data['roles'], 'roleId');
$user = SysUser::findOrFail($data['userId']);
$updateOk = $user->save([
'birthday' => $data['birthday'],
'username' => $data['username'],
'nickname' => $data['nickname'],
'introduction' => $data['introduction'],
'organization_id' => $data['organizationId'],
'phone' => $data['phone'],
'sex' => $data['sex'],
'status' => $data['status'],
'email' => $data['email'],
]);
if (!$updateOk) {
return $this->writeError('修改失败');
}
$user->roles()->attach($data['roles']);
return $this->writeSuccess('修改成功');
}
/**
* 批量删除用户
* @return Json
*/
public function batchRemove(): Json
{
$data = $this->request->delete();
SysUser::destroy($data);
return $this->writeSuccess('删除成功');
}
/**
* 修改用户状态
* @return Json
* @throws DataNotFoundException
* @throws ModelNotFoundException
*/
public function updateStatus(): Json
{
$userId = $this->request->put('userId/d', 0);
$status = $this->request->put('status/d', 0);
SysUser::findOrfail($userId)->save(['status' => $status]);
return $this->writeSuccess('操作成功');
}
/**
* 修改用户密码
* @return Json
* @throws DataNotFoundException
* @throws ModelNotFoundException
*/
public function updatePassword(): Json
{
$userId = $this->request->put('userId/d', 0);
$password = $this->request->put('password/s', 0);
SysUser::findOrfail($userId)->save(['password' => password_hash($password, PASSWORD_DEFAULT)]);
return $this->writeSuccess('操作成功');
}
/**
* 判断数据是否存在
* @return Json
*/
public function existence(): Json
{
$field = $this->request->get('field/s', '');
$value = $this->request->get('value/s', '');
try {
SysUser::field('user_id')->where([$field => $value])->findOrFail();
} catch (ModelNotFoundException|DataNotFoundException) {
return $this->writeError("用户不存在");
}
return $this->writeSuccess('用户已存在');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace app\entity;
use think\Entity;
/**
* @method static static dictCode(string $dictCode)
*/
class SysDictionary extends Entity
{
public static function dictCodeData($dictCode)
{
return self::dictCode($dictCode)->findOrFail()->dictData->order('sortNumber', 'desc');
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace app\entity;
use think\Entity;
class SysDictionaryData extends Entity
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace app\entity;
use think\Entity;
/**
* 系统文件实体类
* @see \app\model\SysFileRecord
*/
class SysFileRecord extends Entity
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace app\entity;
use think\Entity;
/**
* 系统登录日志实体类
* @see \app\model\SysLoginRecord
*/
class SysLoginRecord extends Entity
{
}

14
app/entity/SysMenu.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace app\entity;
use think\Entity;
/**
* 系统菜单实体类
* @see \app\model\SysMenu
*/
class SysMenu extends Entity
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace app\entity;
use think\Entity;
/**
* 系统操作日志实体类
* @see \app\model\SysOperateRecord
*/
class SysOperateRecord extends Entity
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace app\entity;
use think\Entity;
/**
* 系统机构实体类
* @see \app\model\SysOrganization
*/
class SysOrganization extends Entity
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace app\entity;
use think\Entity;
/**
* 系统请求记录实体类
* @see \app\model\SysRequestRecord
*/
class SysRequestRecord extends Entity
{
}

14
app/entity/SysRole.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace app\entity;
use think\Entity;
/**
* 系统角色实体类
* @see \app\model\SysRole
*/
class SysRole extends Entity
{
}

45
app/entity/SysUser.php Normal file
View File

@ -0,0 +1,45 @@
<?php
namespace app\entity;
use app\model\SysRole;
use Firebase\JWT\JWT;
use Hashids\Hashids;
use think\Collection;
use think\Entity;
/**
* 系统用户实体
* @see \app\model\SysUser
* @property SysRole[]|Collection $roles
* @property array $authorities
*/
class SysUser extends Entity
{
/**
* 获取菜单列表
* @param bool $buildTree
* @return array
*/
public function getMenus(bool $buildTree = false): array
{
return $buildTree ? list_build_tree($this->authorities, 'menuId', 'parentId') : $this->authorities;
}
/**
* 生成AccessToken
* @return string
*/
public function getAccessToken(): string
{
$jwtConfig = (array)config('admin.jwt_config', []);
$jwtKey = (string)config('admin.jwt_key', '');
$hashids = new Hashids($jwtKey, 8);
return JWT::encode([
...$jwtConfig,
'iat' => time(),
'exp' => time() + 3600,
'uid' => $hashids->encode($this->user_id),
], $jwtKey, 'HS256');
}
}

8
app/enum/ClientEnum.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace app\enum;
enum ClientEnum: string
{
case PAdmin = 'PAdmin';// PC版后台
}

10
app/enum/FileEnum.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace app\enum;
enum FileEnum: string
{
case Image = 'image';// 图片
case Video = 'video';// 视频
case Audio = 'audio';// 音频
}

View File

@ -0,0 +1,12 @@
<?php
namespace app\enum;
enum UserLoginEnum: int
{
case USERNAME_ERROR = 11;// 用户名错误
case PASSWORD_ERROR = 12;// 密码错误
case STATUS_DISABLED = 21; // 用户已被禁用
case LOGIN_EXCEPTION = 31; // 登录异常
case LOGIN_SUCCESS = 99; // 登录成功
}

10
app/enum/UserSexEnum.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace app\enum;
enum UserSexEnum: int
{
case Unknown = 0;// 未知
case Man = 1;// 男
case Woman = 2;// 女
}

View File

@ -0,0 +1,9 @@
<?php
namespace app\enum;
enum UserStatusEnum: int
{
case Disable = 0;// 禁用
case Enable = 1;// 启用
}

View File

@ -0,0 +1,9 @@
<?php
namespace app\enum;
enum UserTypeEnum: string
{
case USER = 'user';
case VISITOR = 'visitor';
}

20
app/event.php Normal file
View File

@ -0,0 +1,20 @@
<?php
// 事件定义文件
use app\subscribe\SysUserSubscribe;
return [
'bind' => [
],
'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
],
'subscribe' => [
'sysUser' => SysUserSubscribe::class
],
];

43
app/http/HttpAuth.php Normal file
View File

@ -0,0 +1,43 @@
<?php
namespace app\http;
use app\entity\SysUser;
use app\enum\UserTypeEnum;
use think\helper\Macroable;
/**
* @method SysUser getUser()
*/
class HttpAuth
{
use Macroable;
/**
* 用户ID
* >0 已登录用户
* =0 未登录
* <0 访客
* @var int
*/
public int $userId;
/**
* 用户类型
* @var UserTypeEnum
* USER 标准用户
* VISITOR 未登录访客
*/
public UserTypeEnum $userType;
public function __construct($userId, $userType)
{
$this->userId = $userId;
$this->userType = $userType;
}
public function isLogin(): bool
{
return $this->userId > 0;
}
}

53
app/http/HttpClient.php Normal file
View File

@ -0,0 +1,53 @@
<?php
namespace app\http;
use app\validate\auth\ClientValidate;
use Exception;
use think\exception\ValidateException;
/**
* 请求客户端信息
*/
readonly class HttpClient
{
const string CLIENT_KEY = "KOhjymFZ2Ofcwvlp";
public string $name;
public string $id;
public string $version;
public array $data;
private string $sign;
public function __construct(string $clientName, string $clientId, string $clientVersion = '', string $sign = '')
{
$this->name = $clientName; // 客户端名称(PAdmin/IAdmin)
$this->id = $clientId; // 客户端ID(前端生成的唯一标识)
$this->version = $clientVersion; // 客户端版本号
$this->sign = $sign; // 携带签名数据
try {
$this->validate();
} catch (Exception $e) {
throw new ValidateException($e->getMessage());
}
}
private function validate(): void
{
$data = [];
if ($this->sign) {
$encryptedData = base64_decode($this->sign);
$dataStr = openssl_decrypt($encryptedData, 'AES-128-ECB', static::CLIENT_KEY, OPENSSL_RAW_DATA);
$data = json_decode($dataStr, true);
}
$this->data = $data;
validate(ClientValidate::class)->check([
'name' => $this->name,
'id' => $this->id,
'version' => $this->version,
]);
}
}

24
app/http/HttpStatus.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace app\http;
enum HttpStatus: int
{
case API_SUCCESS = 0;
case API_ERROR = 1;
case UNAUTHORIZED = 401;
case FORBIDDEN = 403;
static function message(self $status): string
{
return match ($status) {
self::API_SUCCESS => '操作成功',
self::API_ERROR => '操作失败',
self::UNAUTHORIZED => '未授权访问',
self::FORBIDDEN => '禁止访问',
};
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace app\http\middleware;
use app\model\SysUser;
use app\Request;
use app\service\admin\LoginService;
use Closure;
use think\exception\ValidateException;
use think\Middleware;
use think\Response;
/**
* 权限校验中间件
*/
class AuthMiddleware extends Middleware
{
/*
* 请求接口白名单列表
*/
protected array $whiteList = [];
public function handle(Request $request, Closure $next)
{
/*
* 权限校验
*/
$authorization = (string)$request->header('authorization', '');
$authorization = str_replace('Bearer ', '', $authorization);
$loginSrv = new LoginService();
if (!in_array($request->pathinfo(), $this->whiteList, true)) {
try{
$auth = $loginSrv->checkUserAccessToken($authorization);
}catch (ValidateException $e){
return json(['code' => 401, 'message' => $e->getMessage()]);
}
} else {
$auth = $loginSrv->getVisitor($request);
return json(['code' => 401, 'message' => '禁止访问']);
}
/*
* 注入获取用户信息的function
*/
$auth::macro('getUser', function () use ($auth) {
return SysUser::cache("sysUserInfo:{$auth->userId}", 180, 'sysUserInfoLists')->findOrFail($auth->userId);
});
return $next($request->setAuth($auth));
}
// /**
// * 结束调度
// * @中间件支持定义请求结束前的回调机制你只需要在中间件类中添加end方法。
// * @param Response $response
// */
// public function end(Response $response)
// {
// // 回调行为
// }
}

View File

@ -0,0 +1,36 @@
<?php
namespace app\http\middleware;
use app\http\HttpClient;
use Closure;
use think\Middleware;
use think\Request;
class ClientMiddleware extends Middleware
{
public function handle(Request $request, Closure $next)
{
$clientName = $request->header('client', '');
if(empty($clientName)) {
return response('设备未授权',200);
}
/*
* 客户端信息
*/
$client = new HttpClient(
$clientName,
$request->header('clientId', ''),
$request->header('clientVersion', ''),
);
/*
* 获取客户端
*/
\app\Request::macro('getClient', function () use ($client) {
return $client;
});
// Next.
return $next($request);
}
}

View File

@ -0,0 +1,135 @@
<?php
namespace app\http\middleware;
use app\entity\SysRequestRecord;
use Closure;
use think\{file\UploadedFile, middleware, Request, Response};
class ContextMiddleware extends middleware
{
public function handle(Request $request, Closure $next): Response
{
/*
* 生成并绑定全局上下文标识ID
*/
$request->contextId = unique_str();
/**
* @var Response $response
*/
$response = $next($request);
$response->header([
'R-Context-Id' => $request->contextId,
]);
return $response;
}
/**
* 结束
* @param Response $response
* @return void
*/
public function end(Response $response): void
{
$request = $this->app->request;
$log = (array)$this->app->config->get('admin.httpLog');
$logOpen = $log['open'] ?? false;
if (!$logOpen) {
return;
}
$request_start_time = $request->time(true);
$request_end_time = microtime(true);
$running_time = ($request_end_time - $request_start_time) * 1000;
$openRecordStartTime = $log['open_start_time'] ?? 0;
$openRecordEndTime = $log['open_end_time'] ?? 0;
if ($openRecordStartTime && $openRecordEndTime) {
if ($request_end_time < $openRecordStartTime || $openRecordEndTime < $request_end_time) {
return;
}
}
$rules = array_merge([
'include_methods' => [],
'include_responses' => [],
'include_codes' => [],
'exclude_codes' => [],
], $log['recordRules'] ?? []);
// 请求类型(GET/POST/PUT/DELETE)
$request_method = $request->method();
if (!in_array($request_method, $rules['include_methods'])) {
return;
}
// 返回类型(JSON/HTML/XML)
$response_type = get_class($response);
if (!in_array($response_type, $rules['include_responses'])) {
return;
}
// 返回状态码(Http Status Code)
$response_code = $response->getCode();
if (in_array($response_type, $rules['exclude_codes'])) {
return;
}
if (count($rules['include_codes']) > 0) {
if (!in_array($response_type, $rules['include_codes'])) {
return;
}
}
$request_start_time = explode('.', $request_start_time);
$request_end_time = explode('.', $request_end_time);
$request_date = date('Y-m-d H:i:s', $request_start_time[0]);
$response_data = $response->getContent();
// 限制response_data记录长度
$response_max = $log['response_max'] ?? 0;
if ($response_max) {
$response_data = mb_substr($response_data, 0, $response_max, 'utf-8');
}
$request_body = $request->param();
try {
// 对文件类型的数据做处理
$request_file = $request->file();
if ($request_file && count($request_file) > 0) {
/**
* @var UploadedFile $file
*/
foreach ($request->file() as $key => $file) {
$request_body[$key] = [
'md5' => $file->md5(),
'name' => $file->getOriginalName(),
'size' => $file->getSize(),
];
}
}
$route = $request->rule()->getRoute();
(new SysRequestRecord)->save([
'context_id' => $request->contextId,
'request_date' => $request_date,
'request_time' => $request_date . '.' . ($request_start_time[1] ?? 0),
'request_method' => $request_method,
'request_end_time' => date('Y-m-d H:i:s', $request_end_time[0]) . '.' . ($request_end_time[1] ?? 0),
'request_headers' => json_encode($request->header(), JSON_UNESCAPED_UNICODE),
'request_body' => json_encode($request_body, JSON_UNESCAPED_UNICODE),
'request_path' => $request->url(),
'request_domain' => $request->domain(),
'response_type' => $response_type,
'response_code' => $response_code,
'response_data' => $response_data,
'running_time' => (int)$running_time,
'running_memory_limit' => ini_get('memory_limit'),
'running_memory_usage' => memory_get_peak_usage(),
'request_ip' => $request->ip(),
'rule_name' => $request->rule()->getName(),
'rule_route' => is_array($route) ? implode('@', $route) : @json_encode($route),
]);
} catch (\Throwable $e) {
$this->app->log->error("[ContextMiddleware::end] 日志写入失败->{$e->getMessage()}");
}
}
}

12
app/middleware.php Normal file
View File

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

14
app/model/SysConfig.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace app\model;
use app\BaseModel;
use think\model\concern\SoftDelete;
class SysConfig extends BaseModel
{
use SoftDelete;
protected $table = "sys_config";
protected $pk = 'config_id';
}

View File

@ -0,0 +1,93 @@
<?php
namespace app\model;
use app\BaseModel;
use app\observer\DictionaryObserver;
use think\Model;
use think\model\Collection;
use think\model\concern\SoftDelete;
use think\model\relation\HasMany;
/**
* 系统字典模型
* @property int $dict_id
* @property string $dict_code
* @property string $dict_name
* @property string $dict_type
* @property int $sort_number
* @property string $comments
* @property int $is_system
* @property int $deleted
* @property string $create_time
* @property string $update_time
* @property string $delete_time
* @property SysDictionaryData[]|Collection $dictData
* @property array $dictDataCodes
*/
class SysDictionary extends BaseModel
{
use SoftDelete;
protected $name = "sys_dictionary";
protected $pk = "dict_id";
protected string $eventObserver = DictionaryObserver::class;
public function dictData(): HasMany
{
return $this->hasMany(SysDictionaryData::class, 'dict_id', 'dict_id');
}
//
// public static function onAfterInsert(Model|SysDictionary $model): void
// {
// security_log_record([SysDictionary::class, 'onAfterInsert'], "添加了新字典集", $model);
// }
//
// public static function onAfterDelete(Model|SysDictionary $model): void
// {
// security_log_record([SysDictionary::class, 'onAfterDelete'], "字典集{$model->dict_name}被删除", $model);
// }
//
// public static function onBeforeUpdate(Model|SysDictionary $model): void
// {
// security_log_record([SysDictionary::class, 'onBeforeUpdate'], "修改了{$model->dict_name}字典集", $model);
// }
public static function getByDictCode(string $dictCode)
{
return self::with(['dictData'])->where('dict_code', $dictCode)->findOrEmpty();
}
public function scopeDictCode($query, $dictCode)
{
$query->with(['dictData'])->where('dict_code', $dictCode);
}
public function getDictDataCodesAttr()
{
return $this->dictData->column('dict_data_code');
}
public function getByDictDataCode(string $dictDataCode, mixed $default = null)
{
$dictDataMap = $this->dictData->column('dict_data_name', 'dict_data_code');
return $dictDataMap[$dictDataCode] ?? $default;
}
public function searchGroupAttr($query, $value): void
{
$query->where('group_tag', '=', $value);
}
public function searchDictNameAttr($query, $value): void
{
$query->where('dict_name', 'like', "%$value%");
}
// public function searchDictCodeAttr($query, $value): void
// {
// $query->where('dict_code', '=', $value);
// }
}

View File

@ -0,0 +1,83 @@
<?php
namespace app\model;
use app\BaseModel;
use app\observer\DictionaryObserver;
use think\db\Query;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 系统字典模型
* @property int $dict_data_id
* @property int $dict_id
* @property string $dict_data_code
* @property string $dict_data_name
* @property int $sort_number
* @property string $comments
* @property int $deleted
* @property string $create_time
* @property string $update_time
* @property string $delete_time
*/
class SysDictionaryData extends BaseModel
{
use SoftDelete;
protected $name = "sys_dictionary_data";
protected $pk = "dict_data_id";
protected string $eventObserver = DictionaryObserver::class;
//
// public static function onBeforeRestore(Model|SysDictionaryData $model)
// {
// $model->set('deleted', 0);
// return true;
// }
//
// public static function onBeforeDelete(Model|SysDictionaryData $model)
// {
// $model->set('deleted', 1);
// return true;
// }
//
// public static function onAfterInsert(Model|SysDictionaryData $model): void
// {
// security_log_record([SysDictionaryData::class, 'onAfterInsert'], "添加了新字典", $model);
// }
//
// public static function onAfterDelete(Model|SysDictionaryData $model): void
// {
// security_log_record([SysDictionaryData::class, 'onAfterDelete'], "字典{$model->dict_data_name}被删除", $model);
// }
//
// public static function onBeforeUpdate(Model|SysDictionaryData $model): void
// {
// security_log_record([SysDictionaryData::class, 'onBeforeUpdate'], "修改了{$model->dict_data_name}字典", $model->getChangedDataDA());
// }
// public function searchDictCodeAttr($query, $value): void
// {
// $query->where('dict_id', function (Query $query) use ($value) {
// return $query->table(SysDictionary::getTable())->where('dict_code', $value)->field('dict_id');
// });
// }
public function searchDictDataNameAttr($query, string $value, array $data = []): void
{
$value != "" && $query->where('dict_data_name', 'like', '%' . $value . '%');
}
public function searchDictDataCodeAttr($query, string $value, array $data = []): void
{
$value != "" && $query->where('dict_data_code', 'like', '%' . $value . '%');
}
public function searchDictIdAttr($query, string|int $value, array $data = []): void
{
(is_numeric($value) && $value > 0) && $query->where('dict_id', $value);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace app\model;
use app\BaseModel;
use think\db\Query;
use think\model\relation\HasOne;
/**
* 系统文件模型
*/
class SysFileRecord extends BaseModel
{
protected $name = "sys_file_record";
protected $pk = "id";
public function createUser(): HasOne
{
return $this->hasOne(SysUser::class, 'user_id', 'create_user_id')->field('user_id,nickname,avatar,username,status');
}
public function searchNameAttr(Query $query, string $value): void
{
$value != "" && $query->where('name', 'like', '%' . $value . '%');
}
public function searchPathAttr(Query $query, string $value): void
{
$value != "" && $query->where('path', 'like', '%' . $value . '%');
}
public function searchCreateNicknameAttr(Query $query, string $value): void
{
$value != "" && $query->whereIn('create_user_id', SysUser::where('username', $value)->column('user_id'));
}
public function searchCreateTimeAttr(Query $query, array $value, array $data): void
{
$value = array_filter($value);
count($value) >= 2 && $query->whereBetweenTime('request_time', $value[0], $value[1]);
}
}

27
app/model/SysFileRule.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace app\model;
use app\adminapi\model\TenantModel;
use app\BaseModel;
/**
* @property string $disk 存储磁盘
* @property string $path_rule 路径规则
* @property string $name_rule 命名规则
* @property string $query Query参数携带
* @property string $permissions
* #1 文件大小限制(单位KB)
* @property int $rule_file_size_limit_min
* @property int $rule_file_size_limit_max
* #2 文件类型限制
* @property string $rule_file_ext
* @property string $rule_file_mime
* #3 图片类型限制
* @property string $rule_image
*/
class SysFileRule extends BaseModel
{
protected $name = "sys_file_rule";
protected $pk = "id";
}

View File

@ -0,0 +1,43 @@
<?php
namespace app\model;
use app\BaseModel;
use think\db\Query;
use think\model\relation\HasOne;
class SysLoginRecord extends BaseModel
{
protected $name = "sys_login_record";
protected $pk = "id";
public function user(): HasOne
{
return $this->hasOne(SysUser::class, 'username', 'username')->field('user_id,username,nickname');
}
public function searchUsernameAttr(Query $query, string $value, array $data): void
{
$value != '' && $query->where('username', $value);
}
public function searchLoginTypeAttr(Query $query, int $value, array $data): void
{
$value > 0 && $query->where('login_type', $value);
}
public function searchNicknameAttr(Query $query, string $value, array $data): void
{
$value != '' && $query->whereIn('username', SysUser::where('username', 'like', "%{$value}%")->column('username'));
}
public function searchCreateTimeAttr(Query $query, array $value, array $data): void
{
$value = array_filter($value);
count($value) >= 2 && $query->whereBetweenTime('create_time', $value[0], $value[1]);
}
}

79
app/model/SysMenu.php Normal file
View File

@ -0,0 +1,79 @@
<?php
namespace app\model;
use app\BaseModel;
use think\Model;
use think\model\concern\SoftDelete;
use think\model\relation\BelongsToMany;
/**
* @property int $menu_id
* @property int $parent_id
* @property string $title
* @property string $path
* @property string $component
* @property int $menu_type
* @property int $sort_number
* @property string $authority
* @property string $icon
* @property int $hide
* @property string $meta
* @property int $deleted
* @property string $create_time
* @property string $update_time
* @property string $delete_time
*/
class SysMenu extends BaseModel
{
use SoftDelete;
protected $name = "sys_menu";
protected $pk = "menu_id";
public function roles(): BelongsToMany
{
return $this->belongsToMany(SysRole::class, SysRoleMenu::class, 'menu_id', 'role_id');
}
// 事件定义
public static function onBeforeRestore(Model|SysMenu $model)
{
$model->set('deleted', 0);
return true;
}
public static function onBeforeDelete(Model|SysMenu $model): bool
{
$model->set('deleted', 1);
return true;
}
public static function onAfterInsert(Model|SysMenu $model): void
{
security_log_record([SysMenu::class, 'onAfterInsert'], "添加了新菜单", $model);
}
public static function onAfterDelete(Model|SysMenu $model): void
{
security_log_record([SysMenu::class, 'onAfterDelete'], "菜单{$model->title}被删除", $model);
}
// 搜索定义
public function searchTitleAttr($query, $value): void
{
$value !== '' && $query->where('title', 'like', '%' . $value . '%');
}
public function searchAuthorityAttr($query, $value): void
{
$value !== '' && $query->where('authority', 'like', '%' . $value . '%');
}
public function searchPathAttr($query, $value): void
{
$value !== '' && $query->where('path', 'like', '%' . $value . '%');
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace app\model;
use app\BaseModel;
use think\db\Query;
use think\model\relation\HasOne;
class SysOperateRecord extends BaseModel
{
protected $name = "sys_operate_record";
protected $pk = "id";
public function operateUser(): HasOne
{
return $this->hasOne(SysUser::class, 'user_id', 'operate_user_id')->field('user_id,nickname,avatar,username,status');
}
public function request(): HasOne
{
return $this->hasOne(SysRequestRecord::class, 'context_id', 'context_id');
}
public function searchCreateTimeAttr(Query $query, array $value, array $data): void
{
$value = array_filter($value);
count($value) >= 2 && $query->whereBetweenTime('create_time', $value[0], $value[1]);
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace app\model;
use app\BaseModel;
use think\db\Query;
use think\Model;
use think\model\concern\SoftDelete;
/**
* @property int $organization_id
* @property int $parent_id
* @property string $organization_name
* @property string $organization_full_name
* @property string $organization_code
* @property string $organization_type
* @property int $leader_id
* @property int $sort_number
* @property string $comments
* @property int $deleted
* @property string $create_time
* @property string $update_time
* @property string $delete_time
*/
class SysOrganization extends BaseModel
{
use SoftDelete;
protected $name = "sys_organization";
protected $pk = "organization_id";
// 事件定义
public static function onAfterInsert(Model|SysOrganization $model): void
{
security_log_record([SysOrganization::class, 'onAfterInsert'], "添加了新机构{$model->organization_full_name}", $model);
}
public static function onAfterDelete(Model|SysOrganization $model): void
{
security_log_record([SysOrganization::class, 'onAfterDelete'], "机构{$model->organization_full_name}被删除", $model);
}
// 搜索定义
public function searchOrganizationNameAttr(Query $query, string $value): void
{
$value != '' && $query->where('organization_name', 'like', '%' . $value . '%');
}
public function searchOrganizationFullNameAttr(Query $query, string $value): void
{
$value != '' && $query->where('organization_full_name', 'like', '%' . $value . '%');
}
public function searchOrganizationCodeAttr(Query $query, string $value): void
{
$value != '' && $query->where('organization_code', 'like', '%' . $value . '%');
}
public function searchOrganizationTypeAttr(Query $query, int $value): void
{
$value > 0 && $query->where('organization_type', $value);
}
public function getOrganizationTypeNameAttr($value, $data): string
{
return dict_get('organization_type.' . $data['organization_type']);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace app\model;
use app\BaseModel;
use think\db\Query;
/**
* 系统接口请求记录模型
*/
class SysRequestRecord extends BaseModel
{
protected $name = "sys_request_record";
protected $pk = "id";
public function searchCreateTimeAttr(Query $query, array $value, array $data): void
{
$value = array_filter($value);
count($value) >= 2 && $query->whereBetweenTime('request_time', $value[0], $value[1]);
}
}

71
app/model/SysRole.php Normal file
View File

@ -0,0 +1,71 @@
<?php
namespace app\model;
use app\BaseModel;
use think\Model;
use think\model\Collection;
use think\model\concern\SoftDelete;
use think\model\relation\BelongsToMany;
/**
* 系统角色模型
* @property int $role_id
* @property string $role_name
* @property string $role_code
* @property string $comments
* @property int $deleted
* @property string $create_time
* @property string $update_time
* @property string $delete_time
* @property SysUser[]|Collection $users 角色存在多个用户
* @property SysMenu[]|Collection $menus 角色存在多个菜单
*/
class SysRole extends BaseModel
{
use SoftDelete;
protected $name = "sys_role";
protected $pk = "role_id";
// 关联定义
public function users(): BelongsToMany
{
return $this->belongsToMany(SysRole::class, SysUserRole::class, 'role_id', 'user_id');
}
public function menus(): BelongsToMany
{
return $this->belongsToMany(SysMenu::class, SysRoleMenu::class, 'menu_id', 'role_id');
}
// 搜索定义
public function searchRoleNameAttr($query, $value): void
{
$value != '' && $query->where('role_name', 'like', '%' . $value . '%');
}
public function searchRoleCodeAttr($query, $value): void
{
$value != '' && $query->where('role_code', 'like', '%' . $value . '%');
}
public function searchCommentsAttr($query, $value): void
{
$value != '' && $query->where('comments', 'like', '%' . $value . '%');
}
// 事件定义
public static function onAfterInsert(Model|SysRole $model): void
{
security_log_record([SysRole::class, 'onAfterInsert'], "添加了新角色", $model);
}
public static function onAfterDelete(SysRole $model): void
{
security_log_record([SysRole::class, 'onAfterDelete'], "角色{$model->role_name}被删除", $model);
}
}

23
app/model/SysRoleMenu.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace app\model;
use think\model\Pivot;
use think\model\relation\HasOne;
class SysRoleMenu extends Pivot
{
protected $name = "sys_role_menu";
protected $pk = "id";
public function role(): HasOne
{
return $this->hasOne(SysRole::class);
}
public function menu(): HasOne
{
return $this->hasOne(SysMenu::class);
}
}

126
app/model/SysUser.php Normal file
View File

@ -0,0 +1,126 @@
<?php
namespace app\model;
use app\BaseModel;
use think\Collection;
use think\db\Query;
use think\model\concern\SoftDelete;
use think\model\relation\BelongsToMany;
/**
* 系统用户模型
* @property int $user_id
* @property string $username
* @property string $password
* @property string $nickname
* @property string $avatar
* @property int $sex
* @property string $birthday
* @property string $phone
* @property string $email
* @property integer $email_verified
* @property string $real_name
* @property string $id_card
* @property string $introduction
* @property int $organization_id
* @property int $status
* @property int $deleted
* @property int $tenant_id
* @property string $create_time
* @property string $update_time
* @property string $delete_time
* @property SysRole[]|Collection $roles
* @property SysMenu[]|Collection $menus
*/
class SysUser extends BaseModel
{
use SoftDelete;
protected $name = "sys_user";
protected $pk = "user_id";
public function roles(): BelongsToMany
{
return $this->belongsToMany(SysRole::class, SysUserRole::class, 'role_id', 'user_id');
}
public function getAuthoritiesAttr(): array
{
$menus = [];
$this->roles->load(['menus'], true);
foreach ($this->roles as $role) {
$menus = array_merge($menus, $role->menus->hidden(['pivot'])->where('deleted', 0)->toArray());
}
$uniqueMenus = [];
foreach ($menus as $menu) {
$uniqueMenus[$menu['menuId']] = $menu;
}
$array = array_values($uniqueMenus);
usort($array, function($a, $b) {
return $a['sortNumber'] <=> $b['sortNumber'];
});
return $array;
}
/**
* Attr@性别
* @param $value
* @param $data
* @return string
*/
public function getSexNameAttr($value, $data): string
{
return (string)dict_get('sex.' . $data['sex'], '未知');
}
public function searchOrganizationIdAttr(Query $query, int $value, array $data): void
{
($value > 0) && $query->where('organization_id', $value);
}
public function searchCreateTimeAttr($query, array $value): void
{
$query->whereBetweenTime('create_time', $value[0], $value[1]);
}
public function searchNicknameAttr(Query $query, string $value): void
{
$value != "" && $query->where('nickname', 'like', '%' . $value . '%');
}
public function searchUsernameAttr(Query $query, string $value): void
{
$value != "" && $query->where('username', 'like', '%' . $value . '%');
}
public function searchSexAttr(Query $query, int $value): void
{
($value > 0) && $query->where('sex', '=', $value);
}
public function searchStatusAttr(Query $query, int $value): void
{
($value > 0) && $query->where('status', '=', $value);
}
public function searchEmailAttr(Query $query, string $value): void
{
$value != "" && $query->where('email', 'like', '%' . $value . '%');
}
public function searchPhoneAttr(Query $query, string $value): void
{
$value != "" && $query->where('phone', 'like', '%' . $value . '%');
}
public function searchIdCardAttr(Query $query, string $value): void
{
$value != "" && $query->where('id_card', 'like', '%' . $value . '%');
}
}

63
app/model/SysUserFile.php Normal file
View File

@ -0,0 +1,63 @@
<?php
namespace app\model;
use app\adminapi\model\TenantModel;
use app\BaseModel;
use think\model\concern\SoftDelete;
use think\model\relation\HasOne;
/***
* 系统用户文件
* @property int $id
* @property int $user_id
* @property string $name
*/
class SysUserFile extends BaseModel
{
use SoftDelete;
protected $name = "sys_user_file";
protected $pk = "id";
public function getThumbnailAttr($thumbnail, $row): string
{
return $row['is_directory'] ? '' : (string)$row['url'];
}
public function getDownloadUrlAttr($downloadUrl, $row): string
{
return $row['is_directory'] ? '' : (string)$row['url'];
}
public function getUrlAttr($url, $row): string
{
return $row['is_directory'] ? '' : (string)$row['url'];
}
public function createUser(): HasOne
{
return $this->hasOne(SysUser::class, 'user_id', 'user_id')->field('user_id,username,nickname,avatar');
}
public function searchNameAttr($query, $value): void
{
$query->whereLike('name', '%' . $value . '%');
}
public function searchContentTypeAttr($query, $value): void
{
$query->whereLike('content_type', $value . '%');
}
public function searchParentIdAttr($query, $value): void
{
$query->where('parent_id', $value);
}
public function searchUserIdAttr($query, $value): void
{
$query->where('user_id', $value);
}
}

40
app/model/SysUserRole.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace app\model;
use think\model\Pivot;
use think\model\relation\HasOne;
/***
* 系统(用户/角色)关联模型
* @property int $id
* @property int $user_id
* @property int $role_id
* @property int $tenant_id
* @property string $create_time
* @property string $update_time
*/
class SysUserRole extends Pivot
{
protected $name = "sys_user_role";
protected $pk = "id";
/**
* 角色
* @return HasOne
*/
public function role(): HasOne
{
return $this->hasOne(SysRole::class);
}
/**
* 用户
* @return HasOne
*/
public function user(): HasOne
{
return $this->hasOne(SysUser::class);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace app\observer;
use app\model\SysDictionary;
use app\model\SysDictionaryData;
class DictionaryObserver
{
/**
* 更新/创建数据事件l
* @param SysDictionary|SysDictionaryData $model
* @return void
*/
public function onAfterWrite(SysDictionary|SysDictionaryData $model)
{
$this->clearCache();
}
/**
* 删除数据事件
* @param SysDictionary|SysDictionaryData $model
* @return void
*/
public function onAfterDelete(SysDictionary|SysDictionaryData $model)
{
$this->clearCache();
}
/**
* 恢复数据事件
* @param SysDictionary|SysDictionaryData $model
* @return void
*/
public function onAfterRestore(SysDictionary|SysDictionaryData $model)
{
$this->clearCache();
}
public function clearCache()
{
dict()->clearCache();
}
}

9
app/provider.php Normal file
View File

@ -0,0 +1,9 @@
<?php
use app\ExceptionHandle;
use app\Request;
// 容器Provider定义文件
return [
'think\Request' => Request::class,
'think\exception\Handle' => ExceptionHandle::class,
];

15
app/service.php Normal file
View File

@ -0,0 +1,15 @@
<?php
use app\AppService;
use app\service\ConfigService;
use app\service\DictionaryService;
use app\service\SecurityService;
// 系统服务定义文件
// 服务在完成全局初始化之后执行
return [
AppService::class,
ConfigService::class,
DictionaryService::class,
SecurityService::class,
];

View File

@ -0,0 +1,31 @@
<?php
namespace app\service;
use app\model\SysConfig;
use think\facade\Config;
use think\Service;
class ConfigService extends Service
{
public function register()
{
Config::hook(function ($name, $default = null) {
if($name == "admin.jwt_key") {
return "123456a";
}
return $default;
// $value = self::getDynamicConfig($name);
// // 对配置参数和值进行处理后返回最终的配置值
// // ...
// return is_null($value) ? $default : $value;
});
}
public static function getDynamicConfig(string $key)
{
// return SysConfig::where('key', $key)
// ->cache(60)
// ->value('value');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace app\service;
use app\BaseModel;
use app\Request;
use think\Collection;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\helper\Str;
use think\Paginator;
use think\Service;
class CurdService extends Service
{
static int $MAX_LIMIT = 1000;
/**
* @param Request $request
* @param BaseModel $model
* @param null $order
* @return Collection
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function getList(Request $request, mixed $model, $order = null): Collection
{
$offset = (int)$request->param('offset', 0);
$limit = (int)$request->param('limit', 30);
$limit = min($limit, self::$MAX_LIMIT);
return $model->order(!is_null($order) ? $order : (string)$model->getPk(), 'desc')
->limit($offset, $limit)
->select();
}
/**
* @param Request $request
* @param BaseModel $model
* @param null $order
* @return Paginator
* @throws DbException
*/
public static function getPaginate(Request $request, mixed $model, $order = null)
{
$page = (int)$request->param('page', 1);
$page = max(1, $page);
$limit = (int)$request->param('limit', 30);
$limit = min($limit, self::$MAX_LIMIT);
// 前端自定义排序条件
$sort = $request->param('sort');
$sort_order = $request->param('order');
if (!empty($sort) && in_array($sort_order, ['desc', 'asc'])) {
$order = [Str::snake($sort) => $sort_order];
}
return $model->order(!is_null($order) ? $order : (string)$model->getPk(), 'desc')
->limit($page, $limit)
->paginate([
'list_rows' => $limit,
'page' => $page,
]);
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace app\service;
use app\entity\SysDictionary;
use think\facade\Cache;
use think\Service;
/**
* 字典服务
*/
class DictionaryService extends Service
{
readonly protected array $sysDictData;
public function register(): void
{
/*
* 注册到全局容器中
*/
$this->app->bind('dict', $this);
/*
* 添加应用事件初始化字典内容
*/
$this->app->event->listen("AppInit", function () {
$this->sysDictData = Cache::remember('sysDict', function () {
$array = [];
SysDictionary::with(['dictData'])->select()->each(function (SysDictionary|\app\model\SysDictionary $dict) use (&$array) {
$array[$dict->dict_code] = $dict->dictData->column('dict_data_name', 'dict_data_code');
});
return $array;
}, 3600 * 4);
});
}
/**
* 获取字典
* @param string $name
* @param $default
* @param $cache
* @return mixed
*/
public function get(string $name, $default = null, $cache = true)
{
if (!$cache) {
return $this->getValue($name, $default);
} else {
/*
* 通过缓存判断减少计算操作(使用内存换取性能)
*/
static $cacheValue = [];
if (!isset($cacheValue[$name]) || $cacheValue[$name]['0'] !== $default) {
$cacheValue[$name] = ['v' => $this->getValue($name, $default), '0' => $default];
}
return $cacheValue[$name]['v'];
}
}
/**
* 获取字典元素
* @param string $name
* @param $default
* @return mixed
*/
private function getValue(string $name, $default = null): mixed
{
$keys = explode('.', $name);
if (count($keys) == 1) {
return $this->sysDictData[$name] ?? (is_array($default) ? $default : []);
}
$code = array_pop($keys);
$key = implode('.', $keys);
if (isset($this->sysDictData[$key])) {
return $this->sysDictData[$key][$code] ?? $default;
}
return $default;
}
public function clearCache()
{
Cache::delete('sysDict');
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace app\service;
use app\model\SysOperateRecord;
use think\Service;
/**
* 安全服务
*/
class SecurityService extends Service
{
protected array $securityLogRecords = [];
public function register(): void
{
/**
* 添加安全日志记录事件
*/
$this->app->event->listen("sys:securityLogRecord", function ($trigger) {
[[$location, $event], $message, $data] = $trigger;
$model = new SysOperateRecord();
$model->setOption('convertNameToCamel', false);
$model->location = $location;
$model->event = $event;
$model->description = $message;
$model->create_time = date('Y-m-d H:i:s');
$model->data = json_encode($data, JSON_UNESCAPED_UNICODE);
$this->securityLogRecords[] = $model->toArray();
});
$this->app->event->listen("HttpEnd", function () {
$auth = $this->app->request->getAuth();
$context_id = $this->app->request->contextId;
$operate_user_id = null;
if ($auth) {
$operate_user_id = $auth->userId;
}
/**
* 保存接口操作记录到日志表
*/
(new SysOperateRecord)->insertAll(array_map(function ($item) use ($operate_user_id, $context_id) {
$item['operate_user_id'] = $operate_user_id;
$item['context_id'] = $context_id;
return $item;
}, $this->securityLogRecords));
});
}
}

View File

@ -0,0 +1,120 @@
<?php
namespace app\service\admin;
use app\http\HttpAuth;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Hashids\Hashids;
use stdClass;
use app\entity\{SysLoginRecord, SysUser};
use UnexpectedValueException;
use app\enum\{UserLoginEnum, UserStatusEnum, UserTypeEnum};
use app\http\HttpClient;
use app\Request;
use app\validate\auth\LoginValidate;
use Exception;
use think\db\exception\{DataNotFoundException, DbException, ModelNotFoundException};
use think\exception\ValidateException;
class LoginService
{
/**
* 账号登录
* @param Request $request
* @param HttpClient $client
* @param string $username
* @param string $password
* @param string $code
* @param string $remember
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function login(Request $request, HttpClient $client, string $username, string $password, string $code, string $remember): array
{
$clientName = $client->name;
validate(LoginValidate::class)
->scene($clientName . 'Login')
->check(compact('username', 'password', 'remember', 'code'));
$user = SysUser::where(['username' => $username])->find();
if (empty($user)) {
$this->saveLoginRecord($request, $username, UserLoginEnum::USERNAME_ERROR, "用户名错误");
throw new ValidateException("用户名或密码错误");
}
if (!password_verify($password, $user->password)) {
$this->saveLoginRecord($request, $username, UserLoginEnum::PASSWORD_ERROR, "密码错误");
throw new ValidateException("用户名或密码错误");
}
if ($user->status == UserStatusEnum::Disable->value) {
$this->saveLoginRecord($request, $username, UserLoginEnum::STATUS_DISABLED, "账号被禁用");
throw new ValidateException("账户不可用");
}
try {
$access_token = $user->getAccessToken();
$user = $user->append(['authorities', 'roles'])->toArray();
$this->saveLoginRecord($request, $username, UserLoginEnum::LOGIN_SUCCESS, "登录成功");
} catch (Exception $e) {
$this->saveLoginRecord($request, $username, UserLoginEnum::LOGIN_EXCEPTION, $e->getMessage());
throw $e;
}
return compact('user', 'access_token');
}
/**
* 保存登录记录
* @param Request $request
* @param string $username
* @param UserLoginEnum $loginType
* @param string $comments
* @return void
*/
private function saveLoginRecord(Request $request, string $username, UserLoginEnum $loginType, string $comments): void
{
$sysLoginRecord = new SysLoginRecord();
$sysLoginRecord->save([
'username' => $username,
'os' => $request->getOs(),
'device' => $request->getDevice(),
'browser' => $request->getBrowser(),
'ip' => $request->ip(),
'comments' => $comments,
'login_type' => $loginType->value,
]);
}
public function checkUserAccessToken(string $token): HttpAuth
{
if (empty($token)) {
throw new ValidateException('凭证错误');
}
try {
$jwtKey = (string)config('admin.jwt_key', '');
$headers = new stdClass();
$data = JWT::decode($token, new Key($jwtKey, 'HS256'), $headers);
} catch (UnexpectedValueException $e) {
if($e->getMessage() == "Expired token") {
throw new ValidateException('凭证过期');
}
throw new ValidateException('校验失败' . $e->getMessage());
}
if ($data->exp <= (time() - 180)) {
// 还要三分钟就要过期
throw new ValidateException('凭证过期');
}
$hashids = new Hashids($jwtKey, 8);
$user_id = $hashids->decode($data->uid)[0] ?? 0;
if (empty($user_id)) {
throw new ValidateException('凭证错误');
}
return new HttpAuth($user_id, UserTypeEnum::USER);
}
public function getVisitor(\think\Request $request): HttpAuth
{
return new HttpAuth(-1, UserTypeEnum::VISITOR);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace app\subscribe;
use app\model\SysUser;
class SysUserSubscribe
{
public function onUserLogin(SysUser $user)
{
// UserLogin事件响应处理
}
public function onUserLogout(SysUser $user)
{
// UserLogout事件响应处理
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace app\validate\auth;
use app\enum\ClientEnum;
use think\Validate;
class ClientValidate extends Validate
{
protected array $rule = [
'name' => ClientEnum::class, // 客户端类型(PAdmin:PC管理端,IAdmin:手机管理端)
'uuid' => 'max:64', // 客户端UUID
'version' => 'max:10', // 客户端版本号
];
protected $message = [
'name.enum' => '客户端类型错误',
'uuid.max' => '客户端UUID错误',
'version.max' => '客户端版本号错误',
];
}

View File

@ -0,0 +1,15 @@
<?php
namespace app\validate\auth;
use app\enum\ClientEnum;
use think\Validate;
class LoginValidate extends Validate
{
protected array $must = ['username', 'password'];
protected array $rule = [
'username' => 'max:20',
'password' => 'min:5|max:64'
];
}

54
composer.json Normal file
View File

@ -0,0 +1,54 @@
{
"name": "topthink/think",
"description": "the new thinkphp framework",
"minimum-stability": "dev",
"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": "^4.0",
"topthink/think-filesystem": "^3.0",
"firebase/php-jwt": "dev-main",
"hashids/hashids": "5.0.x-dev",
"phpmailer/phpmailer": "dev-master",
"topthink/think-helper": "^3.1"
},
"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"
]
}
}

22
config/admin.php Normal file
View File

@ -0,0 +1,22 @@
<?php
use think\response\Json;
return [
'jwt_key' => 'oasdasdasd',
'httpLog' => [
'open' => true,
'open_start_time' => 0,
'open_end_time' => 0,
'recordRules' => [
'include_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'include_responses' => [Json::class],
'include_codes' => [],
'exclude_codes' => [],
]
],
'cls' => [
'secretId' => 'AKIDz6HXnBZ0Pm5UtEXlv5BThrwvsmcM0a5e',
'secretKey' => 'eGoIj6QiYSLBFNTsIdv8GxZOWaWWcn8R'
]
];

30
config/app.php Normal file
View File

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

29
config/cache.php Normal file
View File

@ -0,0 +1,29 @@
<?php
// +----------------------------------------------------------------------
// | 缓存设置
// +----------------------------------------------------------------------
return [
// 默认缓存驱动
'default' => 'file',
// 缓存连接方式配置
'stores' => [
'file' => [
// 驱动方式
'type' => 'File',
// 缓存保存目录
'path' => '',
// 缓存前缀
'prefix' => '',
// 缓存有效期 0表示永久缓存
'expire' => 0,
// 缓存标签前缀
'tag_prefix' => 'tag:',
// 序列化机制 例如 ['serialize', 'unserialize']
'serialize' => [],
],
// 更多的缓存连接
],
];

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,
],
// 其它日志通道配置
],
];

8
config/middleware.php Normal file
View File

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

55
config/route.php Normal file
View File

@ -0,0 +1,55 @@
<?php
// +----------------------------------------------------------------------
// | 路由设置
// +----------------------------------------------------------------------
return [
// pathinfo分隔符
'pathinfo_depr' => '/',
// 是否开启路由延迟解析
'url_lazy_route' => false,
// 是否强制使用路由
'url_route_must' => false,
// 是否区分大小写
'url_case_sensitive' => false,
// 自动扫描子目录分组
'route_auto_group' => false,
// 合并路由规则
'route_rule_merge' => false,
// 路由是否完全匹配
'route_complete_match' => false,
// 去除斜杠
'remove_slash' => false,
// 默认的路由变量规则
'default_route_pattern' => '[\w\.]+',
// URL伪静态后缀
'url_html_suffix' => 'html',
// 访问控制器层名称
'controller_layer' => 'controller',
// 空控制器名
'empty_controller' => 'Error',
// 是否使用控制器后缀
'controller_suffix' => false,
// 默认模块名(开启自动多模块有效)
'default_module' => 'index',
// 默认控制器名
'default_controller' => 'Index',
// 默认操作名
'default_action' => 'index',
// 操作方法后缀
'action_suffix' => '',
// 非路由变量是否使用普通参数方式用于URL生成
'url_common_param' => true,
// 操作方法的参数绑定方式 route get param
'action_bind_param' => 'get',
// 请求缓存规则 true为自动规则
'request_cache_key' => true,
// 请求缓存有效期
'request_cache_expire' => null,
// 全局请求缓存排除规则
'request_cache_except' => [],
// 请求缓存的Tag
'request_cache_tag' => '',
// API版本header变量
'api_version' => 'Api-Version',
];

19
config/session.php Normal file
View File

@ -0,0 +1,19 @@
<?php
// +----------------------------------------------------------------------
// | 会话设置
// +----------------------------------------------------------------------
return [
// session name
'name' => 'PHPSESSID',
// SESSION_ID的提交变量,解决flash上传跨域
'var_session_id' => '',
// 驱动方式 支持file cache
'type' => 'file',
// 存储连接标识 当type使用cache的时候有效
'store' => null,
// 过期时间
'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' => '}',
];

2
extend/.gitignore vendored Normal file
View File

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

8
public/.htaccess Normal file
View File

@ -0,0 +1,8 @@
<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

25
public/index.php Normal file
View File

@ -0,0 +1,25 @@
<?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>
// +----------------------------------------------------------------------
use think\App;
// [ 应用入口文件 ]
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

112
route/api.php Normal file
View File

@ -0,0 +1,112 @@
<?php
use app\controller\admin\system\OrganizationController;
use app\entity\SysMenu;
use app\entity\SysOrganization;
use app\http\middleware\ClientMiddleware;
use app\model\SysDictionary;
use app\controller\admin\{auth,
system\DictionaryController,
system\DictionaryDataController,
system\FileController,
system\LoginRecordController,
system\MenuController,
system\OperateRecordController,
system\RequestRecordController,
system\RoleController,
system\UserController};
use app\http\middleware\AuthMiddleware;
use think\facade\Route;
use think\middleware\AllowCrossDomain;
Route::group("adminapi", function () {
/*
* 用户
*/
Route::get("captcha", [auth\LoginController::class, "captcha"])->name("admin.LoginCaptcha");
Route::post("login", [auth\LoginController::class, "index"])->name("admin.SysUserLogin")->middleware([ClientMiddleware::class]);
Route::group(function () {
Route::group("auth", function () {
Route::post("logout", [auth\AuthController::class, "logout"])->name("admin.SysUserLogout");
Route::get("user", [auth\AuthController::class, "user"])->name("admin.SysUserInfo");
});
/*
* 系统文件管理
*/
Route::get('file/page', [FileController::class, "page"])->name("system.pageFiles");
Route::delete('file/remove/batch', [FileController::class, "batchRemove"])->name("system.batchRemoveFile");
Route::group("system", function () {
Route::get('login-record/page', [LoginRecordController::class, "page"])->name("system.pageLoginRecords");
Route::get('operate-record/page', [OperateRecordController::class, "page"])->name("system.pageOperateRecords");
Route::get('request-record/page', [RequestRecordController::class, "page"])->name("system.pageRequestRecords");
/*
* 用户管理
*/
Route::get("user/page", [UserController::class, "page"])->name("system.pageUsers");
Route::delete("user/batch", [UserController::class, "batchRemove"])->name("system.batchRemoveUser");
Route::post("user", [UserController::class, "add"])->name("system.addUser");
Route::put("user$", [UserController::class, "update"])->name("system.updateUser");
Route::get("user/existence", [UserController::class, "existence"])->name("system.userExistence");
Route::put("user/status", [UserController::class, "updateStatus"])->name("system.updateUserStatus");
Route::put("user/password", [UserController::class, "updatePassword"])->name("system.updateUserPassword");
/*
* 角色管理
*/
Route::get("role$", [RoleController::class, "list"])->name("system.listRoles");
Route::get("role/page", [RoleController::class, "page"])->name("system.pageRoles");
Route::delete("role/batch", [RoleController::class, "removeRoles"])->name("system.removeRoles");
Route::get("role-menu/:role_id", [RoleController::class, "getMenu"])->name("system.getRoleMenu");
Route::put("role-menu/:role_id", [RoleController::class, "updateMenu"])->name("system.updateRoleMenu");
/*
* 菜单管理
*/
Route::get("menu$", [MenuController::class, "list"])->name("system.listMenus");
Route::post("menu", [MenuController::class, "add"])->name("system.addMenu");
Route::delete("menu/:menu_id", [MenuController::class, "remove"])
->model('menu_id', SysMenu::class)
->name("system.removeMenu");
/*
* 机构管理
*/
Route::get("organization", [OrganizationController::class, "lists"])->name("system.getOrganizationList");
Route::post("organization", [OrganizationController::class, "add"])->name("system.addOrganization");
Route::put("organization", [OrganizationController::class, "update"])->name("system.updateOrganization");
Route::delete("organization/:organization_id", [OrganizationController::class, "remove"])
->model('organization_id', SysOrganization::class)
->name("system.removeOrganization");
/*
* 字典管理
*/
Route::get("dictionary$", [DictionaryController::class, "lists"])->name("system.getDictionaryList");
Route::post("dictionary", [DictionaryController::class, "add"])->name("system.addDictionary");
Route::put("dictionary", [DictionaryController::class, "update"])->name("system.updateDictionary");
Route::delete("dictionary/:dict_id", [DictionaryController::class, "remove"])->model('dict_id', SysDictionary::class)->name("system.removeDictionary");
/*
* 字典数据管理
*/
Route::get("dictionary-data$", [DictionaryDataController::class, "lists"])->name("system.getDictionaryData");
Route::get("dictionary-data/page", [DictionaryDataController::class, "page"])->name("system.pageDictionaryData");
Route::post("dictionary-data", [DictionaryDataController::class, "add"])->name("system.addDictionaryData");
Route::put("dictionary-data", [DictionaryDataController::class, "update"])->name("system.updateDictionaryData");
Route::delete("dictionary-data/batch", [DictionaryDataController::class, "batchRemove"])->name("system.batchRemoveDictionaryData");
})->name('系统接口');
})->middleware([ClientMiddleware::class, AuthMiddleware::class]);
})->layer('admin')
->middleware([AllowCrossDomain::class], [
'Access-Control-Allow-Headers' => 'Client, Client-Version, Client-Id, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With'
]);
// AllowCrossDomain 缺一个预检检查.
//if($request->isOptions()) {
// return \response()->header($header)->code(204);
//}

17
route/app.php Normal file
View File

@ -0,0 +1,17 @@
<?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>
// +----------------------------------------------------------------------
use think\facade\Route;
Route::get('think', function () {
return 'hello,ThinkPHP8!';
});
Route::get('hello/:name', 'index/hello');

2
runtime/.gitignore vendored Normal file
View File

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

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