webman后台
This commit is contained in:
206
dafuweng-webman/app/admin/controller/Ajax.php
Normal file
206
dafuweng-webman/app/admin/controller/Ajax.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use ba\Terminal;
|
||||
use ba\TableManager;
|
||||
use support\think\Db;
|
||||
use app\admin\model\AdminLog;
|
||||
use app\common\library\Upload;
|
||||
use app\common\library\upload\WebmanUploadedFile;
|
||||
use app\common\controller\Backend;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class Ajax extends Backend
|
||||
{
|
||||
protected array $noNeedPermission = ['*'];
|
||||
protected array $noNeedLogin = ['terminal'];
|
||||
|
||||
public function upload(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('upload'));
|
||||
$file = $request->file('file');
|
||||
if (!$file) {
|
||||
return $this->error(__('No files were uploaded'));
|
||||
}
|
||||
$file = new WebmanUploadedFile($file);
|
||||
$driver = $request->get('driver', $request->post('driver', 'local'));
|
||||
$topic = $request->get('topic', $request->post('topic', 'default'));
|
||||
try {
|
||||
$upload = new Upload();
|
||||
$attachment = $upload
|
||||
->setFile($file)
|
||||
->setDriver($driver)
|
||||
->setTopic($topic)
|
||||
->upload(null, $this->auth->id);
|
||||
unset($attachment['create_time'], $attachment['quote']);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $this->success(__('File uploaded successfully'), [
|
||||
'file' => $attachment ?? []
|
||||
]);
|
||||
}
|
||||
|
||||
public function area(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
return $this->success('', get_area($request));
|
||||
}
|
||||
|
||||
public function buildSuffixSvg(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$suffix = $request->get('suffix', $request->post('suffix', 'file'));
|
||||
$background = $request->get('background', $request->post('background'));
|
||||
$content = build_suffix_svg((string) $suffix, (string) $background);
|
||||
return response($content, 200, [
|
||||
'Content-Length' => strlen($content),
|
||||
'Content-Type' => 'image/svg+xml'
|
||||
]);
|
||||
}
|
||||
|
||||
public function getDatabaseConnectionList(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$quickSearch = $request->get('quickSearch', '');
|
||||
$connections = config('thinkorm.connections', config('database.connections', []));
|
||||
$desensitization = [];
|
||||
foreach ($connections as $key => $connection) {
|
||||
$connConfig = TableManager::getConnectionConfig($key);
|
||||
$desensitization[] = [
|
||||
'type' => $connConfig['type'] ?? 'mysql',
|
||||
'database' => substr_replace($connConfig['database'] ?? '', '****', 1, strlen($connConfig['database'] ?? '') > 4 ? 2 : 1),
|
||||
'key' => $key,
|
||||
];
|
||||
}
|
||||
if ($quickSearch) {
|
||||
$desensitization = array_values(array_filter($desensitization, function ($item) use ($quickSearch) {
|
||||
return preg_match("/$quickSearch/i", $item['key']);
|
||||
}));
|
||||
}
|
||||
return $this->success('', ['list' => $desensitization]);
|
||||
}
|
||||
|
||||
public function getTablePk(Request $request, ?string $table = null, ?string $connection = null): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$table = $table ?? $request->get('table', $request->post('table'));
|
||||
$connection = $connection ?? $request->get('connection', $request->post('connection'));
|
||||
if (!$table) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$table = TableManager::tableName($table, true, $connection);
|
||||
if (!TableManager::phinxAdapter(false, $connection)->hasTable($table)) {
|
||||
return $this->error(__('Data table does not exist'));
|
||||
}
|
||||
$conn = TableManager::getConnection($connection);
|
||||
$tablePk = Db::connect($conn)->table($table)->getPk();
|
||||
return $this->success('', ['pk' => $tablePk]);
|
||||
}
|
||||
|
||||
public function getTableList(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$quickSearch = $request->get('quickSearch', $request->post('quickSearch', ''));
|
||||
$connection = $request->get('connection', $request->post('connection'));
|
||||
$samePrefix = filter_var($request->get('samePrefix', $request->post('samePrefix', true)), FILTER_VALIDATE_BOOLEAN);
|
||||
$excludeTable = $request->get('excludeTable', $request->post('excludeTable', []));
|
||||
$excludeTable = is_array($excludeTable) ? $excludeTable : [];
|
||||
|
||||
$dbConfig = TableManager::getConnectionConfig($connection);
|
||||
$tables = TableManager::getTableList($connection);
|
||||
if ($quickSearch) {
|
||||
$tables = array_filter($tables, function ($comment) use ($quickSearch) {
|
||||
return preg_match("/$quickSearch/i", $comment);
|
||||
});
|
||||
}
|
||||
$pattern = '/^' . preg_quote($dbConfig['prefix'] ?? '', '/') . '/i';
|
||||
$outTables = [];
|
||||
foreach ($tables as $table => $comment) {
|
||||
if ($samePrefix && !preg_match($pattern, $table)) continue;
|
||||
$tableNoPrefix = preg_replace($pattern, '', $table);
|
||||
if (!in_array($tableNoPrefix, $excludeTable)) {
|
||||
$outTables[] = [
|
||||
'table' => $tableNoPrefix,
|
||||
'comment' => $comment,
|
||||
'connection' => $connection,
|
||||
'prefix' => $dbConfig['prefix'] ?? '',
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->success('', ['list' => $outTables]);
|
||||
}
|
||||
|
||||
public function getTableFieldList(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$table = $request->get('table', $request->post('table'));
|
||||
$clean = filter_var($request->get('clean', $request->post('clean', true)), FILTER_VALIDATE_BOOLEAN);
|
||||
$connection = $request->get('connection', $request->post('connection'));
|
||||
if (!$table) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$conn = TableManager::getConnection($connection);
|
||||
$tablePk = Db::connect($conn)->name($table)->getPk();
|
||||
return $this->success('', [
|
||||
'pk' => $tablePk,
|
||||
'fieldList' => TableManager::getTableColumns($table, $clean, $conn),
|
||||
]);
|
||||
}
|
||||
|
||||
public function changeTerminalConfig(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('Change terminal config'));
|
||||
if (Terminal::changeTerminalConfig()) {
|
||||
return $this->success();
|
||||
}
|
||||
return $this->error(__('Failed to modify the terminal configuration. Please modify the configuration file manually:%s', ['/config/terminal.php']));
|
||||
}
|
||||
|
||||
public function clearCache(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('Clear cache'));
|
||||
$type = $request->post('type');
|
||||
if ($type === 'tp' || $type === 'all') {
|
||||
clear_config_cache();
|
||||
} else {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
event_trigger('cacheClearAfter');
|
||||
return $this->success(__('Cache cleaned~'));
|
||||
}
|
||||
|
||||
public function terminal(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
(new Terminal())->exec();
|
||||
return $this->success();
|
||||
}
|
||||
}
|
||||
22
dafuweng-webman/app/admin/controller/Dashboard.php
Normal file
22
dafuweng-webman/app/admin/controller/Dashboard.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class Dashboard extends Backend
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
return $this->success('', [
|
||||
'remark' => get_route_remark()
|
||||
]);
|
||||
}
|
||||
}
|
||||
132
dafuweng-webman/app/admin/controller/Index.php
Normal file
132
dafuweng-webman/app/admin/controller/Index.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use ba\ClickCaptcha;
|
||||
use app\common\facade\Token;
|
||||
use app\admin\model\AdminLog;
|
||||
use app\common\controller\Backend;
|
||||
use support\validation\Validator;
|
||||
use support\validation\ValidationException;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class Index extends Backend
|
||||
{
|
||||
protected array $noNeedLogin = ['logout', 'login'];
|
||||
protected array $noNeedPermission = ['index'];
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$adminInfo = $this->auth->getInfo();
|
||||
$adminInfo['super'] = $this->auth->isSuperAdmin();
|
||||
unset($adminInfo['token'], $adminInfo['refresh_token']);
|
||||
|
||||
$menus = $this->auth->getMenus();
|
||||
if (!$menus) {
|
||||
return $this->error(__('No background menu, please contact super administrator!'));
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'adminInfo' => $adminInfo,
|
||||
'menus' => $menus,
|
||||
'siteConfig' => [
|
||||
'siteName' => get_sys_config('site_name'),
|
||||
'version' => get_sys_config('version'),
|
||||
'apiUrl' => config('buildadmin.api_url'),
|
||||
'upload' => keys_to_camel_case(get_upload_config($request), ['max_size', 'save_name', 'allowed_suffixes', 'allowed_mime_types']),
|
||||
'cdnUrl' => full_url(),
|
||||
'cdnUrlParams' => config('buildadmin.cdn_url_params'),
|
||||
],
|
||||
'terminal' => [
|
||||
'phpDevelopmentServer' => str_contains($_SERVER['SERVER_SOFTWARE'] ?? '', 'Development Server'),
|
||||
'npmPackageManager' => config('terminal.npm_package_manager'),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function login(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($this->auth->isLogin()) {
|
||||
return $this->success(__('You have already logged in. There is no need to log in again~'), [
|
||||
'type' => $this->auth::LOGGED_IN
|
||||
], $this->auth::LOGIN_RESPONSE_CODE);
|
||||
}
|
||||
|
||||
$captchaSwitch = config('buildadmin.admin_login_captcha');
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$username = $request->post('username');
|
||||
$password = $request->post('password');
|
||||
$keep = $request->post('keep');
|
||||
|
||||
$rules = [
|
||||
'username' => 'required|string|min:3|max:30',
|
||||
'password' => 'required|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/',
|
||||
];
|
||||
$data = ['username' => $username, 'password' => $password];
|
||||
if ($captchaSwitch) {
|
||||
$rules['captchaId'] = 'required|string';
|
||||
$rules['captchaInfo'] = 'required|string';
|
||||
$data['captchaId'] = $request->post('captchaId');
|
||||
$data['captchaInfo'] = $request->post('captchaInfo');
|
||||
}
|
||||
|
||||
try {
|
||||
Validator::make($data, $rules, [
|
||||
'username.required' => __('Username'),
|
||||
'password.required' => __('Password'),
|
||||
'password.regex' => __('Please input correct password'),
|
||||
])->validate();
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
if ($captchaSwitch) {
|
||||
$captchaObj = new ClickCaptcha();
|
||||
if (!$captchaObj->check($data['captchaId'], $data['captchaInfo'])) {
|
||||
return $this->error(__('Captcha error'));
|
||||
}
|
||||
}
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('Login'));
|
||||
|
||||
$res = $this->auth->login($username, $password, (bool) $keep);
|
||||
if ($res === true) {
|
||||
return $this->success(__('Login succeeded!'), [
|
||||
'userInfo' => $this->auth->getInfo()
|
||||
]);
|
||||
}
|
||||
$msg = $this->auth->getError();
|
||||
return $this->error($msg ?: __('Incorrect user name or password!'));
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'captcha' => $captchaSwitch
|
||||
]);
|
||||
}
|
||||
|
||||
public function logout(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$refreshToken = $request->post('refreshToken', '');
|
||||
if ($refreshToken) {
|
||||
Token::delete((string) $refreshToken);
|
||||
}
|
||||
$this->auth->logout();
|
||||
return $this->success();
|
||||
}
|
||||
return $this->error(__('Method not allowed'), [], 0, ['statusCode' => 405]);
|
||||
}
|
||||
}
|
||||
147
dafuweng-webman/app/admin/controller/Module.php
Normal file
147
dafuweng-webman/app/admin/controller/Module.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use ba\Exception as BaException;
|
||||
use app\admin\model\AdminLog;
|
||||
use app\admin\library\module\Server;
|
||||
use app\admin\library\module\Manage;
|
||||
use app\common\controller\Backend;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class Module extends Backend
|
||||
{
|
||||
protected array $noNeedPermission = ['state', 'dependentInstallComplete'];
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
return $this->success('', [
|
||||
'installed' => Server::installedList(root_path() . 'modules' . DIRECTORY_SEPARATOR),
|
||||
'sysVersion' => config('buildadmin.version'),
|
||||
'nuxtVersion' => Server::getNuxtVersion(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function state(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$uid = $request->get('uid', '');
|
||||
if (!$uid) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
return $this->success('', [
|
||||
'state' => Manage::instance($uid)->getInstallState()
|
||||
]);
|
||||
}
|
||||
|
||||
public function install(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('Install module'));
|
||||
$uid = $request->get('uid', $request->post('uid', ''));
|
||||
$update = filter_var($request->get('update', $request->post('update', false)), FILTER_VALIDATE_BOOLEAN);
|
||||
if (!$uid) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$res = [];
|
||||
try {
|
||||
$res = Manage::instance($uid)->install($update);
|
||||
} catch (BaException $e) {
|
||||
return $this->error(__($e->getMessage()), $e->getData(), $e->getCode());
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error(__($e->getMessage()));
|
||||
}
|
||||
return $this->success('', ['data' => $res]);
|
||||
}
|
||||
|
||||
public function dependentInstallComplete(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$uid = $request->get('uid', '');
|
||||
if (!$uid) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
try {
|
||||
Manage::instance($uid)->dependentInstallComplete('all');
|
||||
} catch (BaException $e) {
|
||||
return $this->error(__($e->getMessage()), $e->getData(), $e->getCode());
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error(__($e->getMessage()));
|
||||
}
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
public function changeState(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('Change module state'));
|
||||
$uid = $request->post('uid', '');
|
||||
$state = filter_var($request->post('state', false), FILTER_VALIDATE_BOOLEAN);
|
||||
if (!$uid) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$info = [];
|
||||
try {
|
||||
$info = Manage::instance($uid)->changeState($state);
|
||||
} catch (BaException $e) {
|
||||
return $this->error(__($e->getMessage()), $e->getData(), $e->getCode());
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error(__($e->getMessage()));
|
||||
}
|
||||
return $this->success('', ['info' => $info]);
|
||||
}
|
||||
|
||||
public function uninstall(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('Uninstall module'));
|
||||
$uid = $request->post('uid', '');
|
||||
if (!$uid) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
try {
|
||||
Manage::instance($uid)->uninstall();
|
||||
} catch (BaException $e) {
|
||||
return $this->error(__($e->getMessage()), $e->getData(), $e->getCode());
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error(__($e->getMessage()));
|
||||
}
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
public function upload(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('Upload module'));
|
||||
$file = $request->file('file');
|
||||
if (!$file) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
try {
|
||||
$res = Manage::uploadFromRequest($request);
|
||||
} catch (BaException $e) {
|
||||
return $this->error(__($e->getMessage()), $e->getData(), $e->getCode());
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error(__($e->getMessage()));
|
||||
}
|
||||
return $this->success('', $res);
|
||||
}
|
||||
}
|
||||
285
dafuweng-webman/app/admin/controller/auth/Admin.php
Normal file
285
dafuweng-webman/app/admin/controller/auth/Admin.php
Normal file
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use Throwable;
|
||||
use support\think\Db;
|
||||
use support\validation\Validator;
|
||||
use support\validation\ValidationException;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\Admin as AdminModel;
|
||||
use support\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
class Admin extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array|string $preExcludeFields = ['create_time', 'update_time', 'password', 'salt', 'login_failure', 'last_login_time', 'last_login_ip'];
|
||||
|
||||
protected array|string $quickSearchField = ['username', 'nickname'];
|
||||
|
||||
protected string|int|bool $dataLimit = 'allAuthAndOthers';
|
||||
|
||||
protected string $dataLimitField = 'id';
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new AdminModel();
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') ?? $request->post('select')) {
|
||||
$selectRes = $this->select($request);
|
||||
if ($selectRes !== null) return $selectRes;
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withoutField('login_failure,password,salt')
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
if ($this->modelValidate) {
|
||||
try {
|
||||
$rules = [
|
||||
'username' => 'required|string|regex:/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/|unique:admin,username',
|
||||
'nickname' => 'required|string',
|
||||
'password' => 'required|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/',
|
||||
'email' => 'email|unique:admin,email',
|
||||
'mobile' => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile',
|
||||
'group_arr' => 'required|array',
|
||||
];
|
||||
$messages = [
|
||||
'username.regex' => __('Please input correct username'),
|
||||
'password.regex' => __('Please input correct password'),
|
||||
];
|
||||
Validator::make($data, $rules, $messages)->validate();
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$passwd = $data['password'] ?? '';
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
if (!empty($data['group_arr'])) {
|
||||
$authRes = $this->checkGroupAuth($data['group_arr']);
|
||||
if ($authRes !== null) return $authRes;
|
||||
}
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
$result = $this->model->save($data);
|
||||
if (!empty($data['group_arr'])) {
|
||||
$groupAccess = [];
|
||||
foreach ($data['group_arr'] as $datum) {
|
||||
$groupAccess[] = [
|
||||
'uid' => $this->model->id,
|
||||
'group_id' => $datum,
|
||||
];
|
||||
}
|
||||
Db::name('admin_group_access')->insertAll($groupAccess);
|
||||
}
|
||||
$this->model->commit();
|
||||
|
||||
if (!empty($passwd)) {
|
||||
$this->model->resetPassword($this->model->id, $passwd);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
return $this->success(__('Added successfully'));
|
||||
}
|
||||
return $this->error(__('No rows were added'));
|
||||
}
|
||||
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$pk = $this->model->getPk();
|
||||
$id = $request->get($pk) ?? $request->post($pk);
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds && !in_array($row[$this->dataLimitField], $dataLimitAdminIds)) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
if ($this->modelValidate) {
|
||||
try {
|
||||
$rules = [
|
||||
'username' => 'required|string|regex:/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/|unique:admin,username,' . $id,
|
||||
'nickname' => 'required|string',
|
||||
'password' => 'nullable|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/',
|
||||
'email' => 'email|unique:admin,email,' . $id,
|
||||
'mobile' => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile,' . $id,
|
||||
'group_arr' => 'required|array',
|
||||
];
|
||||
$messages = [
|
||||
'username.regex' => __('Please input correct username'),
|
||||
'password.regex' => __('Please input correct password'),
|
||||
];
|
||||
Validator::make($data, $rules, $messages)->validate();
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->auth->id == $data['id'] && ($data['status'] ?? '') == 'disable') {
|
||||
return $this->error(__('Please use another administrator account to disable the current account!'));
|
||||
}
|
||||
|
||||
if (!empty($data['password'])) {
|
||||
$this->model->resetPassword($row->id, $data['password']);
|
||||
}
|
||||
|
||||
$groupAccess = [];
|
||||
if (!empty($data['group_arr'])) {
|
||||
$checkGroups = [];
|
||||
$rowGroupArr = $row->group_arr ?? [];
|
||||
foreach ($data['group_arr'] as $datum) {
|
||||
if (!in_array($datum, $rowGroupArr)) {
|
||||
$checkGroups[] = $datum;
|
||||
}
|
||||
$groupAccess[] = [
|
||||
'uid' => $id,
|
||||
'group_id' => $datum,
|
||||
];
|
||||
}
|
||||
$authRes = $this->checkGroupAuth($checkGroups);
|
||||
if ($authRes !== null) return $authRes;
|
||||
}
|
||||
|
||||
Db::name('admin_group_access')
|
||||
->where('uid', $id)
|
||||
->delete();
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
$result = $row->save($data);
|
||||
if ($groupAccess) {
|
||||
Db::name('admin_group_access')->insertAll($groupAccess);
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
return $this->success(__('Update successful'));
|
||||
}
|
||||
return $this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
unset($row['salt'], $row['login_failure']);
|
||||
$row['password'] = '';
|
||||
return $this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
public function del(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$where = [];
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds) {
|
||||
$where[] = [$this->dataLimitField, 'in', $dataLimitAdminIds];
|
||||
}
|
||||
|
||||
$ids = $request->get('ids') ?? $request->post('ids') ?? [];
|
||||
$ids = is_array($ids) ? $ids : [];
|
||||
$where[] = [$this->model->getPk(), 'in', $ids];
|
||||
$data = $this->model->where($where)->select();
|
||||
|
||||
$count = 0;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($data as $v) {
|
||||
if ($v->id != $this->auth->id) {
|
||||
$count += $v->delete();
|
||||
Db::name('admin_group_access')
|
||||
->where('uid', $v['id'])
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($count) {
|
||||
return $this->success(__('Deleted successfully'));
|
||||
}
|
||||
return $this->error(__('No rows were deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程下拉(Admin 无自定义,返回 null 走默认列表)
|
||||
*/
|
||||
protected function select(Request $request): ?Response
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private function checkGroupAuth(array $groups): ?Response
|
||||
{
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return null;
|
||||
}
|
||||
$authGroups = $this->auth->getAllAuthGroups('allAuthAndOthers');
|
||||
foreach ($groups as $group) {
|
||||
if (!in_array($group, $authGroups)) {
|
||||
return $this->error(__('You have no permission to add an administrator to this group!'));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
61
dafuweng-webman/app/admin/controller/auth/AdminLog.php
Normal file
61
dafuweng-webman/app/admin/controller/auth/AdminLog.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use Throwable;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\AdminLog as AdminLogModel;
|
||||
use support\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
class AdminLog extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected string|array $preExcludeFields = ['create_time', 'admin_id', 'username'];
|
||||
|
||||
protected string|array $quickSearchField = ['title'];
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new AdminLogModel();
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') ?? $request->post('select')) {
|
||||
$selectRes = $this->select($request);
|
||||
if ($selectRes !== null) return $selectRes;
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
if (!$this->auth->isSuperAdmin()) {
|
||||
$where[] = ['admin_id', '=', $this->auth->id];
|
||||
}
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程下拉(AdminLog 无自定义,返回 null 走默认列表)
|
||||
*/
|
||||
protected function select(Request $request): ?Response
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
344
dafuweng-webman/app/admin/controller/auth/Group.php
Normal file
344
dafuweng-webman/app/admin/controller/auth/Group.php
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use Throwable;
|
||||
use ba\Tree;
|
||||
use support\think\Db;
|
||||
use support\validation\Validator;
|
||||
use support\validation\ValidationException;
|
||||
use app\admin\model\AdminRule;
|
||||
use app\admin\model\AdminGroup;
|
||||
use app\common\controller\Backend;
|
||||
use support\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
class Group extends Backend
|
||||
{
|
||||
protected string $authMethod = 'allAuthAndOthers';
|
||||
|
||||
protected ?object $model = null;
|
||||
|
||||
protected string|array $preExcludeFields = ['create_time', 'update_time'];
|
||||
|
||||
protected string|array $quickSearchField = 'name';
|
||||
|
||||
protected Tree $tree;
|
||||
|
||||
protected array $initValue = [];
|
||||
|
||||
protected string $keyword = '';
|
||||
|
||||
protected bool $assembleTree = true;
|
||||
|
||||
protected array $adminGroups = [];
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new AdminGroup();
|
||||
$this->tree = Tree::instance();
|
||||
|
||||
$isTree = $request->get('isTree') ?? $request->post('isTree') ?? true;
|
||||
$this->initValue = $request->get('initValue') ?? [];
|
||||
$this->initValue = is_array($this->initValue) ? array_filter($this->initValue) : [];
|
||||
$this->keyword = $request->get('quickSearch') ?? $request->post('quickSearch') ?? '';
|
||||
|
||||
$this->assembleTree = $isTree && !$this->initValue;
|
||||
|
||||
$this->adminGroups = Db::name('admin_group_access')->where('uid', $this->auth->id)->column('group_id');
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') ?? $request->post('select')) {
|
||||
return $this->select($request);
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $this->getGroups($request),
|
||||
'group' => $this->adminGroups,
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$rulesRes = $this->handleRules($data);
|
||||
if ($rulesRes instanceof Response) return $rulesRes;
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
try {
|
||||
$rules = [
|
||||
'name' => 'required|string',
|
||||
'rules' => 'required',
|
||||
];
|
||||
$messages = [
|
||||
'rules.required' => __('Please select rules'),
|
||||
];
|
||||
Validator::make($data, $rules, $messages)->validate();
|
||||
} catch (ValidationException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
return $this->success(__('Added successfully'));
|
||||
}
|
||||
return $this->error(__('No rows were added'));
|
||||
}
|
||||
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$pk = $this->model->getPk();
|
||||
$id = $request->get($pk) ?? $request->post($pk);
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$authRes = $this->checkAuth($id);
|
||||
if ($authRes !== null) return $authRes;
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$adminGroup = Db::name('admin_group_access')->where('uid', $this->auth->id)->column('group_id');
|
||||
if (in_array($data['id'], $adminGroup)) {
|
||||
return $this->error(__('You cannot modify your own management group!'));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$rulesRes = $this->handleRules($data);
|
||||
if ($rulesRes instanceof Response) return $rulesRes;
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
try {
|
||||
$rules = [
|
||||
'name' => 'required|string',
|
||||
'rules' => 'required',
|
||||
];
|
||||
$messages = [
|
||||
'rules.required' => __('Please select rules'),
|
||||
];
|
||||
Validator::make($data, $rules, $messages)->validate();
|
||||
} catch (ValidationException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
return $this->success(__('Update successful'));
|
||||
}
|
||||
return $this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
$pidArr = AdminRule::field('pid')
|
||||
->distinct()
|
||||
->where('id', 'in', $row->rules)
|
||||
->select()
|
||||
->toArray();
|
||||
$rules = $row->rules ? explode(',', $row->rules) : [];
|
||||
foreach ($pidArr as $item) {
|
||||
$ruKey = array_search($item['pid'], $rules);
|
||||
if ($ruKey !== false) {
|
||||
unset($rules[$ruKey]);
|
||||
}
|
||||
}
|
||||
$row->rules = array_values($rules);
|
||||
return $this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
public function del(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$ids = $request->get('ids') ?? $request->post('ids') ?? [];
|
||||
$ids = is_array($ids) ? $ids : [];
|
||||
$data = $this->model->where($this->model->getPk(), 'in', $ids)->select();
|
||||
foreach ($data as $v) {
|
||||
$authRes = $this->checkAuth($v->id);
|
||||
if ($authRes !== null) return $authRes;
|
||||
}
|
||||
$subData = $this->model->where('pid', 'in', $ids)->column('pid', 'id');
|
||||
foreach ($subData as $key => $subDatum) {
|
||||
if (!in_array($key, $ids)) {
|
||||
return $this->error(__('Please delete the child element first, or use batch deletion'));
|
||||
}
|
||||
}
|
||||
|
||||
$adminGroup = Db::name('admin_group_access')->where('uid', $this->auth->id)->column('group_id');
|
||||
$count = 0;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($data as $v) {
|
||||
if (!in_array($v['id'], $adminGroup)) {
|
||||
$count += $v->delete();
|
||||
}
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($count) {
|
||||
return $this->success(__('Deleted successfully'));
|
||||
}
|
||||
return $this->error(__('No rows were deleted'));
|
||||
}
|
||||
|
||||
public function select(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$data = $this->getGroups($request, [['status', '=', 1]]);
|
||||
|
||||
if ($this->assembleTree) {
|
||||
$data = $this->tree->assembleTree($this->tree->getTreeArray($data));
|
||||
}
|
||||
return $this->success('', [
|
||||
'options' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|Response
|
||||
*/
|
||||
private function handleRules(array &$data)
|
||||
{
|
||||
if (!empty($data['rules']) && is_array($data['rules'])) {
|
||||
$superAdmin = true;
|
||||
$checkedRules = [];
|
||||
$allRuleIds = AdminRule::column('id');
|
||||
|
||||
foreach ($data['rules'] as $postRuleId) {
|
||||
if (in_array($postRuleId, $allRuleIds)) {
|
||||
$checkedRules[] = $postRuleId;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($allRuleIds as $ruleId) {
|
||||
if (!in_array($ruleId, $checkedRules)) {
|
||||
$superAdmin = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($superAdmin && $this->auth->isSuperAdmin()) {
|
||||
$data['rules'] = '*';
|
||||
} else {
|
||||
$ownedRuleIds = $this->auth->getRuleIds();
|
||||
|
||||
if (!array_diff($ownedRuleIds, $checkedRules)) {
|
||||
return $this->error(__('Role group has all your rights, please contact the upper administrator to add or do not need to add!'));
|
||||
}
|
||||
|
||||
if (array_diff($checkedRules, $ownedRuleIds) && !$this->auth->isSuperAdmin()) {
|
||||
return $this->error(__('The group permission node exceeds the range that can be allocated'));
|
||||
}
|
||||
|
||||
$data['rules'] = implode(',', $checkedRules);
|
||||
}
|
||||
} else {
|
||||
unset($data['rules']);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getGroups(Request $request, array $where = []): array
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$initKey = $request->get('initKey') ?? $pk;
|
||||
|
||||
$absoluteAuth = $request->get('absoluteAuth') ?? false;
|
||||
|
||||
if ($this->keyword) {
|
||||
$keyword = explode(' ', $this->keyword);
|
||||
foreach ($keyword as $item) {
|
||||
$where[] = [$this->quickSearchField, 'like', '%' . $item . '%'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->initValue) {
|
||||
$where[] = [$initKey, 'in', $this->initValue];
|
||||
}
|
||||
|
||||
if (!$this->auth->isSuperAdmin()) {
|
||||
$authGroups = $this->auth->getAllAuthGroups($this->authMethod, $where);
|
||||
if (!$absoluteAuth) {
|
||||
$authGroups = array_merge($this->adminGroups, $authGroups);
|
||||
}
|
||||
$where[] = ['id', 'in', $authGroups];
|
||||
}
|
||||
$data = $this->model->where($where)->select()->toArray();
|
||||
|
||||
foreach ($data as &$datum) {
|
||||
if ($datum['rules']) {
|
||||
if ($datum['rules'] == '*') {
|
||||
$datum['rules'] = __('Super administrator');
|
||||
} else {
|
||||
$rules = explode(',', $datum['rules']);
|
||||
if ($rules) {
|
||||
$rulesFirstTitle = AdminRule::where('id', $rules[0])->value('title');
|
||||
$datum['rules'] = count($rules) == 1 ? $rulesFirstTitle : $rulesFirstTitle . '等 ' . count($rules) . ' 项';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$datum['rules'] = __('No permission');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->assembleTree ? $this->tree->assembleChild($data) : $data;
|
||||
}
|
||||
|
||||
private function checkAuth($groupId): ?Response
|
||||
{
|
||||
$authGroups = $this->auth->getAllAuthGroups($this->authMethod, []);
|
||||
if (!$this->auth->isSuperAdmin() && !in_array($groupId, $authGroups)) {
|
||||
return $this->error(__($this->authMethod == 'allAuth' ? 'You need to have all permissions of this group to operate this group~' : 'You need to have all the permissions of the group and have additional permissions before you can operate the group~'));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
302
dafuweng-webman/app/admin/controller/auth/Rule.php
Normal file
302
dafuweng-webman/app/admin/controller/auth/Rule.php
Normal file
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use Throwable;
|
||||
use ba\Tree;
|
||||
use app\common\library\Menu;
|
||||
use app\admin\model\AdminRule;
|
||||
use app\admin\model\AdminGroup;
|
||||
use app\admin\library\crud\Helper;
|
||||
use app\common\controller\Backend;
|
||||
use support\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
class Rule extends Backend
|
||||
{
|
||||
protected string|array $preExcludeFields = ['create_time', 'update_time'];
|
||||
|
||||
protected string|array $defaultSortField = ['weigh' => 'desc'];
|
||||
|
||||
protected string|array $quickSearchField = 'title';
|
||||
|
||||
protected ?object $model = null;
|
||||
|
||||
protected Tree $tree;
|
||||
|
||||
protected array $initValue = [];
|
||||
|
||||
protected string $keyword = '';
|
||||
|
||||
protected bool $assembleTree = true;
|
||||
|
||||
protected bool $modelValidate = false;
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new AdminRule();
|
||||
$this->tree = Tree::instance();
|
||||
$isTree = $request->get('isTree') ?? $request->post('isTree') ?? true;
|
||||
$this->initValue = $request->get('initValue') ?? [];
|
||||
$this->initValue = is_array($this->initValue) ? array_filter($this->initValue) : [];
|
||||
$this->keyword = $request->get('quickSearch') ?? $request->post('quickSearch') ?? '';
|
||||
$this->assembleTree = $isTree && !$this->initValue;
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') ?? $request->post('select')) {
|
||||
return $this->select($request);
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $this->getMenus($request),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
|
||||
$data[$this->dataLimitField] = $this->auth->id;
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validate)) {
|
||||
$validate = new $validate();
|
||||
if ($this->modelSceneValidate) {
|
||||
$validate->scene('add');
|
||||
}
|
||||
$validate->check($data);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
|
||||
if (!empty($data['pid'])) {
|
||||
$this->autoAssignPermission($this->model->id, (int) $data['pid']);
|
||||
}
|
||||
|
||||
if (($data['type'] ?? '') == 'menu' && !empty($data['buttons'])) {
|
||||
$newButtons = [];
|
||||
foreach ($data['buttons'] as $button) {
|
||||
foreach (Helper::$menuChildren as $menuChild) {
|
||||
if ($menuChild['name'] == '/' . $button) {
|
||||
$menuChild['name'] = $data['name'] . $menuChild['name'];
|
||||
$newButtons[] = $menuChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($newButtons)) {
|
||||
Menu::create($newButtons, $this->model->id, 'ignore');
|
||||
|
||||
$children = AdminRule::where('pid', $this->model->id)->select();
|
||||
foreach ($children as $child) {
|
||||
$this->autoAssignPermission($child['id'], $this->model->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
return $this->success(__('Added successfully'));
|
||||
}
|
||||
return $this->error(__('No rows were added'));
|
||||
}
|
||||
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$id = $request->get($this->model->getPk()) ?? $request->post($this->model->getPk());
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds && !in_array($row[$this->dataLimitField], $dataLimitAdminIds)) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validate)) {
|
||||
$validate = new $validate();
|
||||
if ($this->modelSceneValidate) {
|
||||
$validate->scene('edit');
|
||||
}
|
||||
$validate->check($data);
|
||||
}
|
||||
}
|
||||
if (isset($data['pid']) && $data['pid'] > 0) {
|
||||
$parent = $this->model->where('id', $data['pid'])->find();
|
||||
if ($parent && $parent['pid'] == $row['id']) {
|
||||
$parent->pid = 0;
|
||||
$parent->save();
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
return $this->success(__('Update successful'));
|
||||
}
|
||||
return $this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
public function del(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$ids = $request->get('ids') ?? $request->post('ids') ?? [];
|
||||
$ids = is_array($ids) ? $ids : [];
|
||||
|
||||
$subData = $this->model->where('pid', 'in', $ids)->column('pid', 'id');
|
||||
foreach ($subData as $key => $subDatum) {
|
||||
if (!in_array($key, $ids)) {
|
||||
return $this->error(__('Please delete the child element first, or use batch deletion'));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->delFromTrait($request);
|
||||
}
|
||||
|
||||
public function select(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$data = $this->getMenus($request, [['type', 'in', ['menu_dir', 'menu']], ['status', '=', 1]]);
|
||||
|
||||
if ($this->assembleTree) {
|
||||
$data = $this->tree->assembleTree($this->tree->getTreeArray($data, 'title'));
|
||||
}
|
||||
return $this->success('', [
|
||||
'options' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getMenus(Request $request, array $where = []): array
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$initKey = $request->get('initKey') ?? $pk;
|
||||
|
||||
$ids = $this->auth->getRuleIds();
|
||||
|
||||
if (!in_array('*', $ids)) {
|
||||
$where[] = ['id', 'in', $ids];
|
||||
}
|
||||
|
||||
if ($this->keyword) {
|
||||
$keyword = explode(' ', $this->keyword);
|
||||
foreach ($keyword as $item) {
|
||||
$where[] = [$this->quickSearchField, 'like', '%' . $item . '%'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->initValue) {
|
||||
$where[] = [$initKey, 'in', $this->initValue];
|
||||
}
|
||||
|
||||
$rules = $this->model
|
||||
->where($where)
|
||||
->order($this->queryOrderBuilder())
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $this->assembleTree ? $this->tree->assembleChild($rules) : $rules;
|
||||
}
|
||||
|
||||
private function autoAssignPermission(int $id, int $pid): void
|
||||
{
|
||||
$groups = AdminGroup::where('rules', '<>', '*')->select();
|
||||
foreach ($groups as $group) {
|
||||
/** @var AdminGroup $group */
|
||||
$rules = explode(',', (string) $group->rules);
|
||||
if (in_array($pid, $rules) && !in_array($id, $rules)) {
|
||||
$rules[] = $id;
|
||||
$group->rules = implode(',', $rules);
|
||||
$group->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 trait 的 del 逻辑(因 Rule 重写了 del,需手动调用 trait)
|
||||
*/
|
||||
private function delFromTrait(Request $request): Response
|
||||
{
|
||||
$where = [];
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds) {
|
||||
$where[] = [$this->dataLimitField, 'in', $dataLimitAdminIds];
|
||||
}
|
||||
|
||||
$ids = $request->get('ids') ?? $request->post('ids') ?? [];
|
||||
$ids = is_array($ids) ? $ids : [];
|
||||
$where[] = [$this->model->getPk(), 'in', $ids];
|
||||
$data = $this->model->where($where)->select();
|
||||
|
||||
$count = 0;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($data as $v) {
|
||||
$count += $v->delete();
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($count) {
|
||||
return $this->success(__('Deleted successfully'));
|
||||
}
|
||||
return $this->error(__('No rows were deleted'));
|
||||
}
|
||||
}
|
||||
762
dafuweng-webman/app/admin/controller/crud/Crud.php
Normal file
762
dafuweng-webman/app/admin/controller/crud/Crud.php
Normal file
@@ -0,0 +1,762 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\crud;
|
||||
|
||||
use Throwable;
|
||||
use ba\Exception as BaException;
|
||||
use ba\Filesystem;
|
||||
use ba\TableManager;
|
||||
use app\admin\model\CrudLog;
|
||||
use app\admin\model\AdminLog;
|
||||
use app\admin\model\AdminRule;
|
||||
use app\common\controller\Backend;
|
||||
use app\common\library\Menu;
|
||||
use app\admin\library\crud\Helper;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
use support\think\Db;
|
||||
|
||||
/**
|
||||
* CRUD 代码生成器(Webman 迁移版)
|
||||
*/
|
||||
class Crud extends Backend
|
||||
{
|
||||
protected array $modelData = [];
|
||||
protected array $controllerData = [];
|
||||
protected array $indexVueData = [];
|
||||
protected array $formVueData = [];
|
||||
protected string $webTranslate = '';
|
||||
protected array $langTsData = [];
|
||||
protected array $dtStringToArray = ['checkbox', 'selects', 'remoteSelects', 'city', 'images', 'files'];
|
||||
protected array $noNeedPermission = ['logStart', 'getFileData', 'parseFieldData', 'generateCheck', 'uploadCompleted'];
|
||||
|
||||
protected function initController(Request $request): ?Response
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function generate(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$type = $request->post('type', '');
|
||||
$table = $request->post('table', []);
|
||||
$fields = $request->post('fields', []);
|
||||
|
||||
if (!$table || !$fields || !isset($table['name']) || !$table['name']) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
$crudLogId = 0;
|
||||
try {
|
||||
$crudLogId = Helper::recordCrudStatus([
|
||||
'table' => $table,
|
||||
'fields' => $fields,
|
||||
'status' => 'start',
|
||||
]);
|
||||
|
||||
$tableName = TableManager::tableName($table['name'], false, $table['databaseConnection'] ?? null);
|
||||
|
||||
if ($type == 'create' || ($table['rebuild'] ?? '') == 'Yes') {
|
||||
TableManager::phinxTable($tableName, [], true, $table['databaseConnection'] ?? null)->drop()->save();
|
||||
}
|
||||
|
||||
[$tablePk] = Helper::handleTableDesign($table, $fields);
|
||||
|
||||
$tableComment = mb_substr($table['comment'] ?? '', -1) == '表'
|
||||
? mb_substr($table['comment'], 0, -1) . '管理'
|
||||
: ($table['comment'] ?? '');
|
||||
|
||||
$modelFile = Helper::parseNameData($table['isCommonModel'] ?? false ? 'common' : 'admin', $tableName, 'model', $table['modelFile'] ?? '');
|
||||
$validateFile = Helper::parseNameData($table['isCommonModel'] ?? false ? 'common' : 'admin', $tableName, 'validate', $table['validateFile'] ?? '');
|
||||
$controllerFile = Helper::parseNameData('admin', $tableName, 'controller', $table['controllerFile'] ?? '');
|
||||
$webViewsDir = Helper::parseWebDirNameData($tableName, 'views', $table['webViewsDir'] ?? '');
|
||||
$webLangDir = Helper::parseWebDirNameData($tableName, 'lang', $table['webViewsDir'] ?? '');
|
||||
|
||||
$this->webTranslate = implode('.', $webLangDir['lang']) . '.';
|
||||
|
||||
$quickSearchField = $table['quickSearchField'] ?? [$tablePk];
|
||||
if (!in_array($tablePk, $quickSearchField)) {
|
||||
$quickSearchField[] = $tablePk;
|
||||
}
|
||||
$quickSearchFieldZhCnTitle = [];
|
||||
|
||||
$this->modelData = [
|
||||
'append' => [], 'methods' => [], 'fieldType' => [], 'createTime' => '', 'updateTime' => '',
|
||||
'beforeInsertMixins' => [], 'beforeInsert' => '', 'afterInsert' => '',
|
||||
'connection' => $table['databaseConnection'] ?? '', 'name' => $tableName,
|
||||
'className' => $modelFile['lastName'], 'namespace' => $modelFile['namespace'],
|
||||
'relationMethodList' => [],
|
||||
];
|
||||
$this->controllerData = [
|
||||
'use' => [], 'attr' => [], 'methods' => [], 'filterRule' => '',
|
||||
'className' => $controllerFile['lastName'], 'namespace' => $controllerFile['namespace'],
|
||||
'tableComment' => $tableComment, 'modelName' => $modelFile['lastName'],
|
||||
'modelNamespace' => $modelFile['namespace'],
|
||||
];
|
||||
$this->indexVueData = [
|
||||
'enableDragSort' => false, 'defaultItems' => [],
|
||||
'tableColumn' => [['type' => 'selection', 'align' => 'center', 'operator' => 'false']],
|
||||
'dblClickNotEditColumn' => ['undefined'], 'optButtons' => ['edit', 'delete'],
|
||||
'defaultOrder' => '',
|
||||
];
|
||||
$this->formVueData = ['bigDialog' => false, 'formFields' => [], 'formValidatorRules' => [], 'imports' => []];
|
||||
$this->langTsData = ['en' => [], 'zh-cn' => []];
|
||||
|
||||
$fieldsMap = [];
|
||||
foreach ($fields as $field) {
|
||||
$fieldsMap[$field['name']] = $field['designType'];
|
||||
Helper::analyseField($field);
|
||||
Helper::getDictData($this->langTsData['en'], $field, 'en');
|
||||
Helper::getDictData($this->langTsData['zh-cn'], $field, 'zh-cn');
|
||||
|
||||
if (in_array($field['name'], $quickSearchField)) {
|
||||
$quickSearchFieldZhCnTitle[] = $this->langTsData['zh-cn'][$field['name']] ?? $field['name'];
|
||||
}
|
||||
if (($field['designType'] ?? '') == 'switch') {
|
||||
$this->indexVueData['dblClickNotEditColumn'][] = $field['name'];
|
||||
}
|
||||
|
||||
$columnDict = $this->getColumnDict($field);
|
||||
if (in_array($field['name'], $table['formFields'] ?? [])) {
|
||||
$this->formVueData['formFields'][] = $this->getFormField($field, $columnDict, $table['databaseConnection'] ?? null);
|
||||
}
|
||||
if (in_array($field['name'], $table['columnFields'] ?? [])) {
|
||||
$this->indexVueData['tableColumn'][] = $this->getTableColumn($field, $columnDict);
|
||||
}
|
||||
if (in_array($field['designType'] ?? '', ['remoteSelect', 'remoteSelects'])) {
|
||||
$this->parseJoinData($field, $table);
|
||||
}
|
||||
$this->parseModelMethods($field, $this->modelData);
|
||||
$this->parseSundryData($field, $table);
|
||||
|
||||
if (!in_array($field['name'], $table['formFields'] ?? [])) {
|
||||
$this->controllerData['attr']['preExcludeFields'][] = $field['name'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->langTsData['en']['quick Search Fields'] = implode(',', $quickSearchField);
|
||||
$this->langTsData['zh-cn']['quick Search Fields'] = implode('、', $quickSearchFieldZhCnTitle);
|
||||
$this->controllerData['attr']['quickSearchField'] = $quickSearchField;
|
||||
|
||||
$weighKey = array_search('weigh', $fieldsMap);
|
||||
if ($weighKey !== false) {
|
||||
$this->indexVueData['enableDragSort'] = true;
|
||||
$this->modelData['afterInsert'] = Helper::assembleStub('mixins/model/afterInsert', ['field' => $weighKey]);
|
||||
}
|
||||
|
||||
$this->indexVueData['tableColumn'][] = [
|
||||
'label' => "t('Operate')", 'align' => 'center',
|
||||
'width' => $this->indexVueData['enableDragSort'] ? 140 : 100,
|
||||
'render' => 'buttons', 'buttons' => 'optButtons', 'operator' => 'false',
|
||||
];
|
||||
if ($this->indexVueData['enableDragSort']) {
|
||||
array_unshift($this->indexVueData['optButtons'], 'weigh-sort');
|
||||
}
|
||||
|
||||
Helper::writeWebLangFile($this->langTsData, $webLangDir);
|
||||
Helper::writeModelFile($tablePk, $fieldsMap, $this->modelData, $modelFile);
|
||||
Helper::writeControllerFile($this->controllerData, $controllerFile);
|
||||
|
||||
$validateContent = Helper::assembleStub('mixins/validate/validate', [
|
||||
'namespace' => $validateFile['namespace'],
|
||||
'className' => $validateFile['lastName'],
|
||||
]);
|
||||
Helper::writeFile($validateFile['parseFile'], $validateContent);
|
||||
|
||||
$this->indexVueData['tablePk'] = $tablePk;
|
||||
$this->indexVueData['webTranslate'] = $this->webTranslate;
|
||||
Helper::writeIndexFile($this->indexVueData, $webViewsDir, $controllerFile);
|
||||
Helper::writeFormFile($this->formVueData, $webViewsDir, $fields, $this->webTranslate);
|
||||
|
||||
Helper::createMenu($webViewsDir, $tableComment);
|
||||
|
||||
Helper::recordCrudStatus(['id' => $crudLogId, 'status' => 'success']);
|
||||
} catch (BaException $e) {
|
||||
Helper::recordCrudStatus(['id' => $crudLogId ?: 0, 'status' => 'error']);
|
||||
return $this->error($e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
Helper::recordCrudStatus(['id' => $crudLogId ?: 0, 'status' => 'error']);
|
||||
if (env('app_debug', false)) throw $e;
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
return $this->success('', ['crudLog' => CrudLog::find($crudLogId)]);
|
||||
}
|
||||
|
||||
public function logStart(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$id = $request->post('id');
|
||||
$type = $request->post('type', '');
|
||||
|
||||
if ($type == 'Cloud history') {
|
||||
try {
|
||||
$client = get_ba_client();
|
||||
$response = $client->request('GET', '/api/v6.Crud/info', [
|
||||
'query' => [
|
||||
'id' => $id,
|
||||
'server' => 1,
|
||||
'ba-user-token' => $request->post('token', ''),
|
||||
]
|
||||
]);
|
||||
$content = $response->getBody()->getContents();
|
||||
$statusCode = $response->getStatusCode();
|
||||
if ($content == '' || stripos($content, '<title>系统发生错误</title>') !== false || $statusCode != 200) {
|
||||
return $this->error(__('Failed to load cloud data'));
|
||||
}
|
||||
$json = json_decode($content, true);
|
||||
if (json_last_error() != JSON_ERROR_NONE || !is_array($json)) {
|
||||
return $this->error(__('Failed to load cloud data'));
|
||||
}
|
||||
if (($json['code'] ?? 0) != 1) {
|
||||
return $this->error($json['msg'] ?? __('Failed to load cloud data'));
|
||||
}
|
||||
$info = $json['data']['info'] ?? null;
|
||||
} catch (Throwable $e) {
|
||||
return $this->error(__('Failed to load cloud data'));
|
||||
}
|
||||
} else {
|
||||
$row = CrudLog::find($id);
|
||||
$info = $row ? $row->toArray() : null;
|
||||
}
|
||||
|
||||
if (!$info) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$connection = TableManager::getConnection($info['table']['databaseConnection'] ?? '');
|
||||
$tableName = TableManager::tableName($info['table']['name'], false, $connection);
|
||||
$adapter = TableManager::phinxAdapter(true, $connection);
|
||||
if ($adapter->hasTable($tableName)) {
|
||||
$info['table']['empty'] = Db::connect($connection)->name($tableName)->limit(1)->select()->isEmpty();
|
||||
} else {
|
||||
$info['table']['empty'] = true;
|
||||
}
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('Log start'));
|
||||
|
||||
return $this->success('', [
|
||||
'table' => $info['table'],
|
||||
'fields' => $info['fields'],
|
||||
'sync' => $info['sync'] ?? 0,
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$id = $request->post('id');
|
||||
$row = CrudLog::find($id);
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
$info = $row->toArray();
|
||||
|
||||
$webLangDir = Helper::parseWebDirNameData($info['table']['name'], 'lang', $info['table']['webViewsDir'] ?? '');
|
||||
$files = [
|
||||
$webLangDir['en'] . '.ts',
|
||||
$webLangDir['zh-cn'] . '.ts',
|
||||
($info['table']['webViewsDir'] ?? '') . '/index.vue',
|
||||
($info['table']['webViewsDir'] ?? '') . '/popupForm.vue',
|
||||
$info['table']['controllerFile'] ?? '',
|
||||
$info['table']['modelFile'] ?? '',
|
||||
$info['table']['validateFile'] ?? '',
|
||||
];
|
||||
try {
|
||||
foreach ($files as $file) {
|
||||
$file = Filesystem::fsFit(root_path() . $file);
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
Filesystem::delEmptyDir(dirname($file));
|
||||
}
|
||||
Menu::delete(Helper::getMenuName($webLangDir), true);
|
||||
Helper::recordCrudStatus(['id' => $id, 'status' => 'delete']);
|
||||
} catch (Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $this->success(__('Deleted successfully'));
|
||||
}
|
||||
|
||||
public function getFileData(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$table = $request->get('table');
|
||||
$commonModel = $request->get('commonModel', false);
|
||||
|
||||
if (!$table) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
try {
|
||||
$modelFile = Helper::parseNameData($commonModel ? 'common' : 'admin', $table, 'model');
|
||||
$validateFile = Helper::parseNameData($commonModel ? 'common' : 'admin', $table, 'validate');
|
||||
$controllerFile = Helper::parseNameData('admin', $table, 'controller');
|
||||
$webViewsDir = Helper::parseWebDirNameData($table, 'views');
|
||||
} catch (Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$adminModelFiles = Filesystem::getDirFiles(root_path() . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . 'model' . DIRECTORY_SEPARATOR);
|
||||
$commonModelFiles = Filesystem::getDirFiles(root_path() . 'app' . DIRECTORY_SEPARATOR . 'common' . DIRECTORY_SEPARATOR . 'model' . DIRECTORY_SEPARATOR);
|
||||
$adminControllerFiles = get_controller_list();
|
||||
|
||||
$modelFileList = [];
|
||||
$controllerFiles = [];
|
||||
foreach ($adminModelFiles as $item) {
|
||||
$item = Filesystem::fsFit('app/admin/model/' . $item);
|
||||
$modelFileList[$item] = $item;
|
||||
}
|
||||
foreach ($commonModelFiles as $item) {
|
||||
$item = Filesystem::fsFit('app/common/model/' . $item);
|
||||
$modelFileList[$item] = $item;
|
||||
}
|
||||
|
||||
$outExcludeController = ['Addon.php', 'Ajax.php', 'Dashboard.php', 'Index.php', 'Module.php', 'Terminal.php', 'routine/AdminInfo.php', 'routine/Config.php'];
|
||||
foreach ($adminControllerFiles as $item) {
|
||||
if (!in_array($item, $outExcludeController)) {
|
||||
$item = Filesystem::fsFit('app/admin/controller/' . $item);
|
||||
$controllerFiles[$item] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'modelFile' => $modelFile['rootFileName'],
|
||||
'controllerFile' => $controllerFile['rootFileName'],
|
||||
'validateFile' => $validateFile['rootFileName'],
|
||||
'controllerFileList' => $controllerFiles,
|
||||
'modelFileList' => $modelFileList,
|
||||
'webViewsDir' => $webViewsDir['views'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function checkCrudLog(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$table = $request->get('table');
|
||||
$connection = $request->get('connection', config('thinkorm.default', config('database.default', 'mysql')));
|
||||
|
||||
if (!$table) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
$crudLog = Db::name('crud_log')
|
||||
->where('table_name', $table)
|
||||
->where('connection', $connection)
|
||||
->order('create_time desc')
|
||||
->find();
|
||||
|
||||
$id = ($crudLog && isset($crudLog['status']) && $crudLog['status'] == 'success') ? $crudLog['id'] : 0;
|
||||
|
||||
return $this->success('', ['id' => $id]);
|
||||
}
|
||||
|
||||
public function parseFieldData(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('Parse field data'));
|
||||
$type = $request->post('type');
|
||||
$table = $request->post('table');
|
||||
$connection = TableManager::getConnection($request->post('connection', ''));
|
||||
$table = TableManager::tableName($table, true, $connection);
|
||||
$connectionConfig = TableManager::getConnectionConfig($connection);
|
||||
|
||||
if ($type == 'db') {
|
||||
$sql = 'SELECT * FROM `information_schema`.`tables` WHERE TABLE_SCHEMA = ? AND table_name = ?';
|
||||
$tableInfo = Db::connect($connection)->query($sql, [$connectionConfig['database'], $table]);
|
||||
if (!$tableInfo) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
$adapter = TableManager::phinxAdapter(false, $connection);
|
||||
$empty = $adapter->hasTable($table)
|
||||
? Db::connect($connection)->table($table)->limit(1)->select()->isEmpty()
|
||||
: true;
|
||||
|
||||
return $this->success('', [
|
||||
'columns' => Helper::parseTableColumns($table, false, $connection),
|
||||
'comment' => $tableInfo[0]['TABLE_COMMENT'] ?? '',
|
||||
'empty' => $empty,
|
||||
]);
|
||||
}
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
public function generateCheck(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
AdminLog::instance($request)->setTitle(__('Generate check'));
|
||||
$table = $request->post('table');
|
||||
$connection = $request->post('connection', '');
|
||||
$webViewsDir = $request->post('webViewsDir', '');
|
||||
$controllerFile = $request->post('controllerFile', '');
|
||||
|
||||
if (!$table) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
try {
|
||||
$webViewsDir = Helper::parseWebDirNameData($table, 'views', $webViewsDir);
|
||||
$controllerFile = Helper::parseNameData('admin', $table, 'controller', $controllerFile)['rootFileName'];
|
||||
} catch (Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$tableList = TableManager::getTableList($connection);
|
||||
$tableExist = array_key_exists(TableManager::tableName($table, true, $connection), $tableList);
|
||||
$controllerExist = file_exists(root_path() . $controllerFile);
|
||||
$menuName = Helper::getMenuName($webViewsDir);
|
||||
$menuExist = AdminRule::where('name', $menuName)->value('id');
|
||||
|
||||
if ($controllerExist || $tableExist || $menuExist) {
|
||||
return $this->error('', [
|
||||
'menu' => $menuExist,
|
||||
'table' => $tableExist,
|
||||
'controller' => $controllerExist,
|
||||
], -1);
|
||||
}
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
public function uploadCompleted(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$syncIds = $request->post('syncIds', []);
|
||||
$syncIds = is_array($syncIds) ? $syncIds : [];
|
||||
$cancelSync = $request->post('cancelSync', false);
|
||||
|
||||
$crudLogModel = new CrudLog();
|
||||
|
||||
if ($cancelSync) {
|
||||
$logData = $crudLogModel->where('id', 'in', array_keys($syncIds))->select();
|
||||
foreach ($logData as $logDatum) {
|
||||
if (isset($syncIds[$logDatum->id]) && $logDatum->sync == $syncIds[$logDatum->id]) {
|
||||
$logDatum->sync = 0;
|
||||
$logDatum->save();
|
||||
}
|
||||
}
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
foreach ($syncIds as $key => $syncId) {
|
||||
$row = $crudLogModel->find($key);
|
||||
if ($row) {
|
||||
$row->sync = $syncId;
|
||||
$row->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
private function parseJoinData($field, $table): void
|
||||
{
|
||||
$dictEn = [];
|
||||
$dictZhCn = [];
|
||||
if (empty($field['form']['relation-fields']) || empty($field['form']['remote-table'])) {
|
||||
return;
|
||||
}
|
||||
$columns = Helper::parseTableColumns($field['form']['remote-table'], true, $table['databaseConnection'] ?? null);
|
||||
$relationFields = explode(',', $field['form']['relation-fields']);
|
||||
$tableName = TableManager::tableName($field['form']['remote-table'], false, $table['databaseConnection'] ?? null);
|
||||
$rnPattern = '/(.*)(_ids|_id)$/';
|
||||
$relationName = preg_match($rnPattern, $field['name'])
|
||||
? parse_name(preg_replace($rnPattern, '$1', $field['name']), 1, false)
|
||||
: parse_name($field['name'] . '_table', 1, false);
|
||||
|
||||
if (empty($field['form']['remote-model']) || !file_exists(root_path() . $field['form']['remote-model'])) {
|
||||
$joinModelFile = Helper::parseNameData('admin', $tableName, 'model', $field['form']['remote-model'] ?? '');
|
||||
if (!file_exists(root_path() . $joinModelFile['rootFileName'])) {
|
||||
$joinModelData = [
|
||||
'append' => [], 'methods' => [], 'fieldType' => [], 'createTime' => '', 'updateTime' => '',
|
||||
'beforeInsertMixins' => [], 'beforeInsert' => '', 'afterInsert' => '',
|
||||
'connection' => $table['databaseConnection'] ?? '', 'name' => $tableName,
|
||||
'className' => $joinModelFile['lastName'], 'namespace' => $joinModelFile['namespace'],
|
||||
];
|
||||
$joinTablePk = 'id';
|
||||
$joinFieldsMap = [];
|
||||
foreach ($columns as $column) {
|
||||
$joinFieldsMap[$column['name']] = $column['designType'];
|
||||
$this->parseModelMethods($column, $joinModelData);
|
||||
if ($column['primaryKey']) $joinTablePk = $column['name'];
|
||||
}
|
||||
$weighKey = array_search('weigh', $joinFieldsMap);
|
||||
if ($weighKey !== false) {
|
||||
$joinModelData['afterInsert'] = Helper::assembleStub('mixins/model/afterInsert', ['field' => $weighKey]);
|
||||
}
|
||||
Helper::writeModelFile($joinTablePk, $joinFieldsMap, $joinModelData, $joinModelFile);
|
||||
}
|
||||
$field['form']['remote-model'] = $joinModelFile['rootFileName'];
|
||||
}
|
||||
|
||||
if ($field['designType'] == 'remoteSelect') {
|
||||
$this->controllerData['attr']['withJoinTable'][$relationName] = $relationName;
|
||||
$relationData = [
|
||||
'relationMethod' => $relationName, 'relationMode' => 'belongsTo',
|
||||
'relationPrimaryKey' => $field['form']['remote-pk'] ?? 'id',
|
||||
'relationForeignKey' => $field['name'],
|
||||
'relationClassName' => str_replace(['.php', '/'], ['', '\\'], '\\' . $field['form']['remote-model']) . "::class",
|
||||
];
|
||||
$this->modelData['relationMethodList'][$relationName] = Helper::assembleStub('mixins/model/belongsTo', $relationData);
|
||||
if ($relationFields) {
|
||||
$this->controllerData['relationVisibleFieldList'][$relationData['relationMethod']] = $relationFields;
|
||||
}
|
||||
} elseif ($field['designType'] == 'remoteSelects') {
|
||||
$this->modelData['append'][] = $relationName;
|
||||
$this->modelData['methods'][] = Helper::assembleStub('mixins/model/getters/remoteSelectLabels', [
|
||||
'field' => parse_name($relationName, 1),
|
||||
'className' => str_replace(['.php', '/'], ['', '\\'], '\\' . $field['form']['remote-model']),
|
||||
'primaryKey' => $field['form']['remote-pk'] ?? 'id',
|
||||
'foreignKey' => $field['name'],
|
||||
'labelFieldName' => $field['form']['remote-field'] ?? 'name',
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($relationFields as $relationField) {
|
||||
if (!array_key_exists($relationField, $columns)) continue;
|
||||
$relationFieldPrefix = $relationName . '.';
|
||||
$relationFieldLangPrefix = strtolower($relationName) . '__';
|
||||
Helper::getDictData($dictEn, $columns[$relationField], 'en', $relationFieldLangPrefix);
|
||||
Helper::getDictData($dictZhCn, $columns[$relationField], 'zh-cn', $relationFieldLangPrefix);
|
||||
|
||||
if (($columns[$relationField]['designType'] ?? '') == 'switch') {
|
||||
$this->indexVueData['dblClickNotEditColumn'][] = $field['name'];
|
||||
}
|
||||
|
||||
$columnDict = $this->getColumnDict($columns[$relationField], $relationFieldLangPrefix);
|
||||
$columns[$relationField]['designType'] = $field['designType'];
|
||||
$columns[$relationField]['form'] = ($field['form'] ?? []) + ($columns[$relationField]['form'] ?? []);
|
||||
$columns[$relationField]['table'] = ($field['table'] ?? []) + ($columns[$relationField]['table'] ?? []);
|
||||
|
||||
$remoteAttr = [
|
||||
'pk' => $this->getRemoteSelectPk($field),
|
||||
'field' => $field['form']['remote-field'] ?? 'name',
|
||||
'remoteUrl' => $this->getRemoteSelectUrl($field),
|
||||
];
|
||||
|
||||
if (($columns[$relationField]['table']['comSearchRender'] ?? '') == 'remoteSelect') {
|
||||
$renderColumn = $columns[$relationField];
|
||||
$renderColumn['table']['operator'] = 'false';
|
||||
unset($renderColumn['table']['comSearchRender']);
|
||||
$this->indexVueData['tableColumn'][] = $this->getTableColumn($renderColumn, $columnDict, $relationFieldPrefix, $relationFieldLangPrefix);
|
||||
$columns[$relationField]['table']['show'] = 'false';
|
||||
$columns[$relationField]['table']['label'] = "t('" . $this->webTranslate . $relationFieldLangPrefix . $columns[$relationField]['name'] . "')";
|
||||
$columns[$relationField]['name'] = $field['name'];
|
||||
if ($field['designType'] == 'remoteSelects') {
|
||||
$remoteAttr['multiple'] = 'true';
|
||||
}
|
||||
$columnData = $this->getTableColumn($columns[$relationField], $columnDict, '', $relationFieldLangPrefix);
|
||||
$columnData['comSearchInputAttr'] = array_merge($remoteAttr, $columnData['comSearchInputAttr'] ?? []);
|
||||
} else {
|
||||
$columnData = $this->getTableColumn($columns[$relationField], $columnDict, $relationFieldPrefix, $relationFieldLangPrefix);
|
||||
}
|
||||
$this->indexVueData['tableColumn'][] = $columnData;
|
||||
}
|
||||
$this->langTsData['en'] = array_merge($this->langTsData['en'], $dictEn);
|
||||
$this->langTsData['zh-cn'] = array_merge($this->langTsData['zh-cn'], $dictZhCn);
|
||||
}
|
||||
|
||||
private function parseModelMethods($field, &$modelData): void
|
||||
{
|
||||
if (($field['designType'] ?? '') == 'array') {
|
||||
$modelData['fieldType'][$field['name']] = 'json';
|
||||
} elseif (!in_array($field['name'], ['create_time', 'update_time', 'updatetime', 'createtime'])
|
||||
&& ($field['designType'] ?? '') == 'datetime'
|
||||
&& in_array($field['type'] ?? '', ['int', 'bigint'])) {
|
||||
$modelData['fieldType'][$field['name']] = 'timestamp:Y-m-d H:i:s';
|
||||
}
|
||||
if (($field['designType'] ?? '') == 'spk') {
|
||||
$modelData['beforeInsertMixins']['snowflake'] = Helper::assembleStub('mixins/model/mixins/beforeInsertWithSnowflake', []);
|
||||
}
|
||||
$fieldName = parse_name($field['name'], 1);
|
||||
if (in_array($field['designType'] ?? '', $this->dtStringToArray)) {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/stringToArray', ['field' => $fieldName]);
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/setters/arrayToString', ['field' => $fieldName]);
|
||||
} elseif (($field['designType'] ?? '') == 'array') {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/jsonDecode', ['field' => $fieldName]);
|
||||
} elseif (($field['designType'] ?? '') == 'time') {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/setters/time', ['field' => $fieldName]);
|
||||
} elseif (($field['designType'] ?? '') == 'editor') {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/htmlDecode', ['field' => $fieldName]);
|
||||
} elseif (($field['designType'] ?? '') == 'spk') {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/string', ['field' => $fieldName]);
|
||||
} elseif (in_array($field['type'] ?? '', ['float', 'decimal', 'double'])) {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/float', ['field' => $fieldName]);
|
||||
}
|
||||
if (($field['designType'] ?? '') == 'city') {
|
||||
$modelData['append'][] = $field['name'] . '_text';
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/cityNames', [
|
||||
'field' => $fieldName . 'Text',
|
||||
'originalFieldName' => $field['name'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function parseSundryData($field, $table): void
|
||||
{
|
||||
if (($field['designType'] ?? '') == 'editor') {
|
||||
$this->formVueData['bigDialog'] = true;
|
||||
$this->controllerData['filterRule'] = "\n" . Helper::tab(2) . '$this->request->filter(\'clean_xss\');';
|
||||
}
|
||||
if (!empty($table['defaultSortField']) && !empty($table['defaultSortType'])) {
|
||||
$defaultSortField = "{$table['defaultSortField']},{$table['defaultSortType']}";
|
||||
if ($defaultSortField == 'id,desc') {
|
||||
$this->controllerData['attr']['defaultSortField'] = '';
|
||||
} else {
|
||||
$this->controllerData['attr']['defaultSortField'] = $defaultSortField;
|
||||
$this->indexVueData['defaultOrder'] = Helper::buildDefaultOrder($table['defaultSortField'], $table['defaultSortType']);
|
||||
}
|
||||
}
|
||||
if (($field['originalDesignType'] ?? '') == 'weigh' && $field['name'] != 'weigh') {
|
||||
$this->controllerData['attr']['weighField'] = $field['name'];
|
||||
}
|
||||
}
|
||||
|
||||
private function getFormField($field, $columnDict, ?string $dbConnection = null): array
|
||||
{
|
||||
$formField = [
|
||||
':label' => 't(\'' . $this->webTranslate . $field['name'] . '\')',
|
||||
'type' => $field['designType'],
|
||||
'v-model' => 'baTable.form.items!.' . $field['name'],
|
||||
'prop' => $field['name'],
|
||||
];
|
||||
if ($columnDict || in_array($field['designType'], ['radio', 'checkbox', 'select', 'selects'])) {
|
||||
$formField[':input-attr']['content'] = $columnDict;
|
||||
} elseif ($field['designType'] == 'textarea') {
|
||||
$formField[':input-attr']['rows'] = (int)($field['form']['rows'] ?? 3);
|
||||
$formField['@keyup.enter.stop'] = '';
|
||||
$formField['@keyup.ctrl.enter'] = 'baTable.onSubmit(formRef)';
|
||||
} elseif (in_array($field['designType'], ['remoteSelect', 'remoteSelects'])) {
|
||||
$formField[':input-attr']['pk'] = $this->getRemoteSelectPk($field);
|
||||
$formField[':input-attr']['field'] = $field['form']['remote-field'] ?? 'name';
|
||||
$formField[':input-attr']['remoteUrl'] = $this->getRemoteSelectUrl($field);
|
||||
} elseif ($field['designType'] == 'number') {
|
||||
$formField[':input-attr']['step'] = (int)($field['form']['step'] ?? 1);
|
||||
} elseif ($field['designType'] == 'icon') {
|
||||
$formField[':input-attr']['placement'] = 'top';
|
||||
} elseif ($field['designType'] == 'editor') {
|
||||
$formField['@keyup.enter.stop'] = '';
|
||||
$formField['@keyup.ctrl.enter'] = 'baTable.onSubmit(formRef)';
|
||||
}
|
||||
if (!in_array($field['designType'], ['image', 'images', 'file', 'files', 'switch'])) {
|
||||
if (in_array($field['designType'], ['radio', 'checkbox', 'datetime', 'year', 'date', 'time', 'select', 'selects', 'remoteSelect', 'remoteSelects', 'city', 'icon'])) {
|
||||
$formField[':placeholder'] = "t('Please select field', { field: t('" . $this->webTranslate . $field['name'] . "') })";
|
||||
} else {
|
||||
$formField[':placeholder'] = "t('Please input field', { field: t('" . $this->webTranslate . $field['name'] . "') })";
|
||||
}
|
||||
}
|
||||
if (($field['defaultType'] ?? '') == 'INPUT') {
|
||||
$this->indexVueData['defaultItems'][$field['name']] = $field['default'];
|
||||
}
|
||||
if ($field['designType'] == 'editor') {
|
||||
$this->indexVueData['defaultItems'][$field['name']] = (($field['defaultType'] ?? '') == 'INPUT' && ($field['default'] ?? '')) ? $field['default'] : '';
|
||||
} elseif ($field['designType'] == 'array') {
|
||||
$this->indexVueData['defaultItems'][$field['name']] = "[]";
|
||||
} elseif (($field['defaultType'] ?? '') == 'INPUT' && in_array($field['designType'], $this->dtStringToArray) && str_contains($field['default'] ?? '', ',')) {
|
||||
$this->indexVueData['defaultItems'][$field['name']] = Helper::buildSimpleArray(explode(',', $field['default']));
|
||||
} elseif (($field['defaultType'] ?? '') == 'INPUT' && in_array($field['designType'], ['number', 'float'])) {
|
||||
$this->indexVueData['defaultItems'][$field['name']] = (float)($field['default'] ?? 0);
|
||||
}
|
||||
if (isset($field['default']) && in_array($field['designType'], ['switch', 'number', 'float', 'remoteSelect']) && $field['default'] == 0) {
|
||||
unset($this->indexVueData['defaultItems'][$field['name']]);
|
||||
}
|
||||
return $formField;
|
||||
}
|
||||
|
||||
private function getRemoteSelectPk($field): string
|
||||
{
|
||||
$pk = $field['form']['remote-pk'] ?? 'id';
|
||||
$alias = '';
|
||||
if (!str_contains($pk, '.')) {
|
||||
if (($field['form']['remote-source-config-type'] ?? '') == 'crud' && !empty($field['form']['remote-model'])) {
|
||||
$alias = parse_name(basename(str_replace('\\', '/', $field['form']['remote-model']), '.php'));
|
||||
} else {
|
||||
$alias = $field['form']['remote-primary-table-alias'] ?? '';
|
||||
}
|
||||
}
|
||||
return !empty($alias) ? "$alias.$pk" : $pk;
|
||||
}
|
||||
|
||||
private function getRemoteSelectUrl($field): string
|
||||
{
|
||||
if (($field['form']['remote-source-config-type'] ?? '') == 'crud' && !empty($field['form']['remote-controller'])) {
|
||||
$pathArr = [];
|
||||
$controller = explode(DIRECTORY_SEPARATOR, $field['form']['remote-controller']);
|
||||
$controller = str_replace('.php', '', $controller);
|
||||
$redundantDir = ['app' => 0, 'admin' => 1, 'controller' => 2];
|
||||
foreach ($controller as $key => $item) {
|
||||
if (!array_key_exists($item, $redundantDir) || $key !== $redundantDir[$item]) {
|
||||
$pathArr[] = $item;
|
||||
}
|
||||
}
|
||||
$url = count($pathArr) > 1 ? implode('.', $pathArr) : ($pathArr[0] ?? '');
|
||||
return '/admin/' . $url . '/index';
|
||||
}
|
||||
return $field['form']['remote-url'] ?? '';
|
||||
}
|
||||
|
||||
private function getTableColumn($field, $columnDict, string $fieldNamePrefix = '', string $translationPrefix = ''): array
|
||||
{
|
||||
$column = [
|
||||
'label' => "t('" . $this->webTranslate . $translationPrefix . $field['name'] . "')",
|
||||
'prop' => $fieldNamePrefix . $field['name'] . (($field['designType'] ?? '') == 'city' ? '_text' : ''),
|
||||
'align' => 'center',
|
||||
];
|
||||
if (isset($field['table']['operator']) && $field['table']['operator'] == 'LIKE') {
|
||||
$column['operatorPlaceholder'] = "t('Fuzzy query')";
|
||||
}
|
||||
if (!empty($field['table'])) {
|
||||
$column = array_merge($column, $field['table']);
|
||||
$column['comSearchInputAttr'] = str_attr_to_array($column['comSearchInputAttr'] ?? '');
|
||||
}
|
||||
$columnReplaceValue = ['tag', 'tags', 'switch'];
|
||||
if (!in_array($field['designType'] ?? '', ['remoteSelect', 'remoteSelects'])
|
||||
&& ($columnDict || (isset($field['table']['render']) && in_array($field['table']['render'], $columnReplaceValue)))) {
|
||||
$column['replaceValue'] = $columnDict;
|
||||
}
|
||||
if (isset($column['render']) && $column['render'] == 'none') {
|
||||
unset($column['render']);
|
||||
}
|
||||
return $column;
|
||||
}
|
||||
|
||||
private function getColumnDict($column, string $translationPrefix = ''): array
|
||||
{
|
||||
$dict = [];
|
||||
if (in_array($column['type'] ?? '', ['enum', 'set'])) {
|
||||
$dataType = str_replace(' ', '', $column['dataType'] ?? '');
|
||||
$columnData = substr($dataType, stripos($dataType, '(') + 1, -1);
|
||||
$columnData = explode(',', str_replace(["'", '"'], '', $columnData));
|
||||
foreach ($columnData as $columnDatum) {
|
||||
$dict[$columnDatum] = $column['name'] . ' ' . $columnDatum;
|
||||
}
|
||||
}
|
||||
$dictData = [];
|
||||
Helper::getDictData($dictData, $column, 'zh-cn', $translationPrefix);
|
||||
if ($dictData) {
|
||||
unset($dictData[$translationPrefix . $column['name']]);
|
||||
foreach ($dictData as $key => $item) {
|
||||
$keyName = str_replace($translationPrefix . $column['name'] . ' ', '', $key);
|
||||
$dict[$keyName] = "t('" . $this->webTranslate . $key . "')";
|
||||
}
|
||||
}
|
||||
return $dict;
|
||||
}
|
||||
}
|
||||
53
dafuweng-webman/app/admin/controller/crud/Log.php
Normal file
53
dafuweng-webman/app/admin/controller/crud/Log.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\crud;
|
||||
|
||||
use app\admin\model\CrudLog;
|
||||
use app\common\controller\Backend;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class Log extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array|string $preExcludeFields = ['id', 'create_time'];
|
||||
protected array|string $quickSearchField = ['id', 'table_name', 'comment'];
|
||||
protected array $noNeedPermission = ['index'];
|
||||
|
||||
protected function initController(Request $request): ?Response
|
||||
{
|
||||
$this->model = new CrudLog();
|
||||
|
||||
if (!$this->auth->check('crud/crud/index')) {
|
||||
return $this->error(__('You have no permission'), [], 401);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') || $request->post('select')) {
|
||||
return $this->select($request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable ?? [], $this->withJoinType ?? 'LEFT')
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
90
dafuweng-webman/app/admin/controller/routine/AdminInfo.php
Normal file
90
dafuweng-webman/app/admin/controller/routine/AdminInfo.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\routine;
|
||||
|
||||
use app\admin\model\Admin;
|
||||
use app\common\controller\Backend;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class AdminInfo extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array|string $preExcludeFields = ['username', 'last_login_time', 'password', 'salt', 'status'];
|
||||
protected array $authAllowFields = ['id', 'username', 'nickname', 'avatar', 'email', 'mobile', 'motto', 'last_login_time'];
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->auth->setAllowFields($this->authAllowFields);
|
||||
$this->model = $this->auth->getAdmin();
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$info = $this->auth->getInfo();
|
||||
return $this->success('', ['info' => $info]);
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$pk = $this->model->getPk();
|
||||
$id = $request->post($pk) ?? $request->get($pk);
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
if (!empty($data['avatar'])) {
|
||||
$row->avatar = $data['avatar'];
|
||||
if ($row->save()) {
|
||||
return $this->success(__('Avatar modified successfully!'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->modelValidate) {
|
||||
$validateClass = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validateClass)) {
|
||||
try {
|
||||
$validate = new $validateClass();
|
||||
$validate->scene('info')->check($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['password'])) {
|
||||
$this->model->resetPassword($this->auth->id, $data['password']);
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result !== false ? $this->success(__('Update successful')) : $this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
return $this->success('', ['row' => $row]);
|
||||
}
|
||||
}
|
||||
51
dafuweng-webman/app/admin/controller/routine/Attachment.php
Normal file
51
dafuweng-webman/app/admin/controller/routine/Attachment.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\routine;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\model\Attachment as AttachmentModel;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class Attachment extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array|string $quickSearchField = 'name';
|
||||
protected array $withJoinTable = ['admin', 'user'];
|
||||
protected array|string $defaultSortField = ['last_upload_time' => 'desc'];
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new AttachmentModel();
|
||||
}
|
||||
|
||||
public function del(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$where = [];
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds) {
|
||||
$where[] = [$this->dataLimitField, 'in', $dataLimitAdminIds];
|
||||
}
|
||||
|
||||
$ids = $request->post('ids', $request->get('ids', []));
|
||||
$ids = is_array($ids) ? $ids : [];
|
||||
$where[] = [$this->model->getPk(), 'in', $ids];
|
||||
$data = $this->model->where($where)->select();
|
||||
|
||||
$count = 0;
|
||||
try {
|
||||
foreach ($data as $v) {
|
||||
$count += $v->delete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error(__('%d records and files have been deleted', [$count]) . $e->getMessage());
|
||||
}
|
||||
return $count ? $this->success(__('%d records and files have been deleted', [$count])) : $this->error(__('No rows were deleted'));
|
||||
}
|
||||
}
|
||||
168
dafuweng-webman/app/admin/controller/routine/Config.php
Normal file
168
dafuweng-webman/app/admin/controller/routine/Config.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\routine;
|
||||
|
||||
use ba\Filesystem;
|
||||
use app\common\library\Email;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\Config as ConfigModel;
|
||||
use PHPMailer\PHPMailer\Exception as PHPMailerException;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class Config extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array $filePath = [
|
||||
'appConfig' => 'config/app.php',
|
||||
'webAdminBase' => 'web/src/router/static/adminBase.ts',
|
||||
'backendEntranceStub' => 'app/admin/library/stubs/backendEntrance.stub',
|
||||
];
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new ConfigModel();
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$configGroup = get_sys_config('config_group');
|
||||
$config = $this->model->order('weigh desc')->select()->toArray();
|
||||
|
||||
$list = [];
|
||||
$newConfigGroup = [];
|
||||
if (is_array($configGroup)) {
|
||||
foreach ($configGroup as $item) {
|
||||
$key = $item['key'] ?? $item;
|
||||
$val = $item['value'] ?? $item;
|
||||
$list[$key] = ['name' => $key, 'title' => __($val)];
|
||||
$newConfigGroup[$key] = $list[$key]['title'];
|
||||
}
|
||||
}
|
||||
foreach ($config as $item) {
|
||||
$group = $item['group'] ?? '';
|
||||
if (isset($newConfigGroup[$group])) {
|
||||
$item['title'] = __($item['title'] ?? '');
|
||||
$list[$group]['list'][] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $list,
|
||||
'remark' => get_route_remark(),
|
||||
'configGroup' => $newConfigGroup,
|
||||
'quickEntrance' => get_sys_config('config_quick_entrance'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$all = $this->model->select();
|
||||
if ($request->method() === 'POST') {
|
||||
$this->modelValidate = false;
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$configValue = [];
|
||||
foreach ($all as $item) {
|
||||
if (array_key_exists($item->name, $data)) {
|
||||
$configValue[] = [
|
||||
'id' => $item->id,
|
||||
'type' => $item->getData('type'),
|
||||
'value' => $data[$item->name]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($configValue as $cv) {
|
||||
$this->model->where('id', $cv['id'])->update(['value' => $cv['value']]);
|
||||
}
|
||||
$this->model->commit();
|
||||
$result = true;
|
||||
} catch (\Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result ? $this->success(__('The current page configuration item was updated successfully')) : $this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() !== 'POST') {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validateClass = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validateClass)) {
|
||||
$validate = new $validateClass();
|
||||
$validate->scene('add')->check($data);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$this->model->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result !== false ? $this->success(__('Added successfully')) : $this->error(__('No rows were added'));
|
||||
}
|
||||
|
||||
public function sendTestMail(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$data = $request->post();
|
||||
$mail = new Email();
|
||||
try {
|
||||
$mail->Host = $data['smtp_server'] ?? '';
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $data['smtp_user'] ?? '';
|
||||
$mail->Password = $data['smtp_pass'] ?? '';
|
||||
$mail->SMTPSecure = ($data['smtp_verification'] ?? '') == 'SSL' ? PHPMailer::ENCRYPTION_SMTPS : PHPMailer::ENCRYPTION_STARTTLS;
|
||||
$mail->Port = $data['smtp_port'] ?? 465;
|
||||
$mail->setFrom($data['smtp_sender_mail'] ?? '', $data['smtp_user'] ?? '');
|
||||
$mail->isSMTP();
|
||||
$mail->addAddress($data['testMail'] ?? '');
|
||||
$mail->isHTML();
|
||||
$mail->setSubject(__('This is a test email') . '-' . get_sys_config('site_name'));
|
||||
$mail->Body = __('Congratulations, receiving this email means that your email service has been configured correctly');
|
||||
$mail->send();
|
||||
} catch (PHPMailerException) {
|
||||
return $this->error($mail->ErrorInfo ?? '');
|
||||
}
|
||||
return $this->success(__('Test mail sent successfully~'));
|
||||
}
|
||||
}
|
||||
155
dafuweng-webman/app/admin/controller/security/DataRecycle.php
Normal file
155
dafuweng-webman/app/admin/controller/security/DataRecycle.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\security;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\DataRecycle as DataRecycleModel;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class DataRecycle extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array|string $preExcludeFields = ['update_time', 'create_time'];
|
||||
protected array|string $quickSearchField = 'name';
|
||||
|
||||
protected function initController(Request $request): ?Response
|
||||
{
|
||||
$this->model = new DataRecycleModel();
|
||||
return null;
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') || $request->post('select')) {
|
||||
return $this->select($request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable ?? [], $this->withJoinType ?? 'LEFT')
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
$data = $this->excludeFields($data);
|
||||
$data['controller_as'] = str_ireplace('.php', '', $data['controller'] ?? '');
|
||||
$data['controller_as'] = strtolower(str_ireplace(['\\', '.'], '/', $data['controller_as']));
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validateClass = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validateClass)) {
|
||||
$validate = new $validateClass();
|
||||
$validate->scene('add')->check($data);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$this->model->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result !== false ? $this->success(__('Added successfully')) : $this->error(__('No rows were added'));
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'controllers' => $this->getControllerList(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$pk = $this->model->getPk();
|
||||
$id = $request->post($pk) ?? $request->get($pk);
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
$data = $this->excludeFields($data);
|
||||
$data['controller_as'] = str_ireplace('.php', '', $data['controller'] ?? '');
|
||||
$data['controller_as'] = strtolower(str_ireplace(['\\', '.'], '/', $data['controller_as']));
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validateClass = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validateClass)) {
|
||||
$validate = new $validateClass();
|
||||
$validate->scene('edit')->check(array_merge($data, [$pk => $row[$pk]]));
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result !== false ? $this->success(__('Update successful')) : $this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'row' => $row,
|
||||
'controllers' => $this->getControllerList(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function getControllerList(): array
|
||||
{
|
||||
$outExcludeController = [
|
||||
'Addon.php',
|
||||
'Ajax.php',
|
||||
'Module.php',
|
||||
'Terminal.php',
|
||||
'Dashboard.php',
|
||||
'Index.php',
|
||||
'routine/AdminInfo.php',
|
||||
'user/MoneyLog.php',
|
||||
'user/ScoreLog.php',
|
||||
];
|
||||
$outControllers = [];
|
||||
$controllers = get_controller_list();
|
||||
foreach ($controllers as $key => $controller) {
|
||||
if (!in_array($controller, $outExcludeController)) {
|
||||
$outControllers[$key] = $controller;
|
||||
}
|
||||
}
|
||||
return $outControllers;
|
||||
}
|
||||
}
|
||||
119
dafuweng-webman/app/admin/controller/security/DataRecycleLog.php
Normal file
119
dafuweng-webman/app/admin/controller/security/DataRecycleLog.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\security;
|
||||
|
||||
use ba\TableManager;
|
||||
use support\think\Db;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\DataRecycleLog as DataRecycleLogModel;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class DataRecycleLog extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array|string $preExcludeFields = [];
|
||||
protected array|string $quickSearchField = 'recycle.name';
|
||||
protected array $withJoinTable = ['recycle', 'admin'];
|
||||
|
||||
protected function initController(Request $request): ?Response
|
||||
{
|
||||
$this->model = new DataRecycleLogModel();
|
||||
return null;
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') || $request->post('select')) {
|
||||
return $this->select($request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function restore(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$ids = $request->post('ids', $request->get('ids', []));
|
||||
$ids = is_array($ids) ? $ids : [];
|
||||
$data = $this->model->where('id', 'in', $ids)->select();
|
||||
if (!$data) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($data as $row) {
|
||||
$recycleData = json_decode($row['data'], true);
|
||||
if (is_array($recycleData) && Db::connect(TableManager::getConnection($row->connection))->name($row->data_table)->insert($recycleData)) {
|
||||
$row->delete();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
return $count ? $this->success(__('Restore successful')) : $this->error(__('No rows were restore'));
|
||||
}
|
||||
|
||||
public function info(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$pk = $this->model->getPk();
|
||||
$id = $request->get($pk) ?? $request->post($pk);
|
||||
$row = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->where('data_recycle_log.id', $id)
|
||||
->find();
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
$data = $this->jsonToArray($row['data']);
|
||||
if (is_array($data)) {
|
||||
foreach ($data as $key => $item) {
|
||||
$data[$key] = $this->jsonToArray($item);
|
||||
}
|
||||
}
|
||||
$row['data'] = $data;
|
||||
|
||||
return $this->success('', ['row' => $row]);
|
||||
}
|
||||
|
||||
protected function jsonToArray(mixed $value = ''): mixed
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
$data = json_decode($value, true);
|
||||
if (($data && is_object($data)) || (is_array($data) && !empty($data))) {
|
||||
return $data;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
182
dafuweng-webman/app/admin/controller/security/SensitiveData.php
Normal file
182
dafuweng-webman/app/admin/controller/security/SensitiveData.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\security;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\SensitiveData as SensitiveDataModel;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class SensitiveData extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array|string $preExcludeFields = ['update_time', 'create_time'];
|
||||
protected array|string $quickSearchField = 'controller';
|
||||
|
||||
protected function initController(Request $request): ?Response
|
||||
{
|
||||
$this->model = new SensitiveDataModel();
|
||||
return null;
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') || $request->post('select')) {
|
||||
return $this->select($request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
$items = $res->items();
|
||||
foreach ($items as $item) {
|
||||
if ($item->data_fields) {
|
||||
$fields = [];
|
||||
foreach ($item->data_fields as $key => $field) {
|
||||
$fields[] = $field ?: $key;
|
||||
}
|
||||
$item->data_fields = $fields;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $items,
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() !== 'POST') {
|
||||
return $this->success('', ['controllers' => $this->getControllerList()]);
|
||||
}
|
||||
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$data['controller_as'] = str_ireplace('.php', '', $data['controller'] ?? '');
|
||||
$data['controller_as'] = strtolower(str_ireplace(['\\', '.'], '/', $data['controller_as']));
|
||||
|
||||
if (is_array($data['fields'] ?? null)) {
|
||||
$data['data_fields'] = [];
|
||||
foreach ($data['fields'] as $field) {
|
||||
$data['data_fields'][$field['name']] = $field['value'];
|
||||
}
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validateClass = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validateClass)) {
|
||||
$validate = new $validateClass();
|
||||
$validate->scene('add')->check($data);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$this->model->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result !== false ? $this->success(__('Added successfully')) : $this->error(__('No rows were added'));
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$pk = $this->model->getPk();
|
||||
$id = $request->get($pk) ?? $request->post($pk);
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
if ($request->method() !== 'POST') {
|
||||
return $this->success('', [
|
||||
'row' => $row,
|
||||
'controllers' => $this->getControllerList(),
|
||||
]);
|
||||
}
|
||||
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$data['controller_as'] = str_ireplace('.php', '', $data['controller'] ?? '');
|
||||
$data['controller_as'] = strtolower(str_ireplace(['\\', '.'], '/', $data['controller_as']));
|
||||
|
||||
if (is_array($data['fields'] ?? null)) {
|
||||
$data['data_fields'] = [];
|
||||
foreach ($data['fields'] as $field) {
|
||||
$data['data_fields'][$field['name']] = $field['value'];
|
||||
}
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validateClass = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validateClass)) {
|
||||
$validate = new $validateClass();
|
||||
$validate->scene('edit')->check(array_merge($data, [$pk => $row[$pk]]));
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result !== false ? $this->success(__('Update successful')) : $this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
private function getControllerList(): array
|
||||
{
|
||||
$outExcludeController = [
|
||||
'Addon.php',
|
||||
'Ajax.php',
|
||||
'Dashboard.php',
|
||||
'Index.php',
|
||||
'Module.php',
|
||||
'Terminal.php',
|
||||
'auth/AdminLog.php',
|
||||
'routine/AdminInfo.php',
|
||||
'routine/Config.php',
|
||||
'user/MoneyLog.php',
|
||||
'user/ScoreLog.php',
|
||||
];
|
||||
$outControllers = [];
|
||||
$controllers = get_controller_list();
|
||||
foreach ($controllers as $key => $controller) {
|
||||
if (!in_array($controller, $outExcludeController)) {
|
||||
$outControllers[$key] = $controller;
|
||||
}
|
||||
}
|
||||
return $outControllers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\security;
|
||||
|
||||
use ba\TableManager;
|
||||
use support\think\Db;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\SensitiveDataLog as SensitiveDataLogModel;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class SensitiveDataLog extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array|string $preExcludeFields = [];
|
||||
protected array|string $quickSearchField = 'sensitive.name';
|
||||
protected array $withJoinTable = ['sensitive', 'admin'];
|
||||
|
||||
protected function initController(Request $request): ?Response
|
||||
{
|
||||
$this->model = new SensitiveDataLogModel();
|
||||
return null;
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') || $request->post('select')) {
|
||||
return $this->select($request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
$items = $res->items();
|
||||
foreach ($items as $item) {
|
||||
$item->id_value = $item['primary_key'] . '=' . $item->id_value;
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $items,
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function info(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$pk = $this->model->getPk();
|
||||
$id = $request->get($pk) ?? $request->post($pk);
|
||||
$row = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->where('security_sensitive_data_log.id', $id)
|
||||
->find();
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
return $this->success('', ['row' => $row]);
|
||||
}
|
||||
|
||||
public function rollback(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$ids = $request->post('ids', $request->get('ids', []));
|
||||
$ids = is_array($ids) ? $ids : [];
|
||||
$data = $this->model->where('id', 'in', $ids)->select();
|
||||
if (!$data) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($data as $row) {
|
||||
$conn = Db::connect(TableManager::getConnection($row->connection));
|
||||
if ($conn->name($row->data_table)->where($row->primary_key, $row->id_value)->update([$row->data_field => $row->before])) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $count ? $this->success(__('Rollback successful')) : $this->error(__('No rows were rolled back'));
|
||||
}
|
||||
}
|
||||
129
dafuweng-webman/app/admin/controller/user/Group.php
Normal file
129
dafuweng-webman/app/admin/controller/user/Group.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use Throwable;
|
||||
use app\admin\model\UserRule;
|
||||
use app\admin\model\UserGroup;
|
||||
use app\common\controller\Backend;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class Group extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array|string $preExcludeFields = ['update_time', 'create_time'];
|
||||
protected array|string $quickSearchField = 'name';
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new UserGroup();
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() !== 'POST') {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$data = $this->handleRules($data);
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validateClass = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validateClass)) {
|
||||
$validate = new $validateClass();
|
||||
$validate->scene('add')->check($data);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result !== false ? $this->success(__('Added successfully')) : $this->error(__('No rows were added'));
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$pk = $this->model->getPk();
|
||||
$id = $request->post($pk) ?? $request->get($pk);
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
$data = $this->excludeFields($data);
|
||||
$data = $this->handleRules($data);
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validateClass = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validateClass)) {
|
||||
$validate = new $validateClass();
|
||||
$validate->scene('edit')->check(array_merge($data, [$pk => $row[$pk]]));
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result !== false ? $this->success(__('Update successful')) : $this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
$pidArr = UserRule::field('pid')->distinct(true)->where('id', 'in', $row->rules)->select()->toArray();
|
||||
$rules = $row->rules ? explode(',', $row->rules) : [];
|
||||
foreach ($pidArr as $item) {
|
||||
$ruKey = array_search($item['pid'], $rules);
|
||||
if ($ruKey !== false) {
|
||||
unset($rules[$ruKey]);
|
||||
}
|
||||
}
|
||||
$row->rules = array_values($rules);
|
||||
return $this->success('', ['row' => $row]);
|
||||
}
|
||||
|
||||
private function handleRules(array $data): array
|
||||
{
|
||||
if (isset($data['rules']) && is_array($data['rules']) && $data['rules']) {
|
||||
$rules = UserRule::select();
|
||||
$super = true;
|
||||
foreach ($rules as $rule) {
|
||||
if (!in_array($rule['id'], $data['rules'])) {
|
||||
$super = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$data['rules'] = $super ? '*' : implode(',', $data['rules']);
|
||||
} else {
|
||||
unset($data['rules']);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
42
dafuweng-webman/app/admin/controller/user/MoneyLog.php
Normal file
42
dafuweng-webman/app/admin/controller/user/MoneyLog.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use app\admin\model\User;
|
||||
use app\admin\model\UserMoneyLog;
|
||||
use app\common\controller\Backend;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class MoneyLog extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array $withJoinTable = ['user'];
|
||||
protected array|string $preExcludeFields = ['create_time'];
|
||||
protected array|string $quickSearchField = ['user.username', 'user.nickname'];
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new UserMoneyLog();
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
return $this->_add();
|
||||
}
|
||||
|
||||
$userId = $request->get('userId', $request->post('userId', 0));
|
||||
$user = User::where('id', $userId)->find();
|
||||
if (!$user) {
|
||||
return $this->error(__("The user can't find it"));
|
||||
}
|
||||
return $this->success('', ['user' => $user]);
|
||||
}
|
||||
}
|
||||
97
dafuweng-webman/app/admin/controller/user/Rule.php
Normal file
97
dafuweng-webman/app/admin/controller/user/Rule.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use ba\Tree;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\UserRule;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class Rule extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
protected Tree $tree;
|
||||
protected array $initValue = [];
|
||||
protected bool $assembleTree = true;
|
||||
protected string $keyword = '';
|
||||
|
||||
protected array|string $preExcludeFields = ['create_time', 'update_time'];
|
||||
protected array|string $defaultSortField = ['weigh' => 'desc'];
|
||||
protected array|string $quickSearchField = 'title';
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new UserRule();
|
||||
$this->tree = Tree::instance();
|
||||
$isTree = filter_var($request->get('isTree', $request->post('isTree', true)), FILTER_VALIDATE_BOOLEAN);
|
||||
$this->initValue = $request->get('initValue', $request->post('initValue', []));
|
||||
$this->initValue = is_array($this->initValue) ? array_filter($this->initValue) : [];
|
||||
$this->keyword = $request->get('quickSearch', $request->post('quickSearch', ''));
|
||||
$this->assembleTree = $isTree && !$this->initValue;
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') || $request->post('select')) {
|
||||
return $this->select($request);
|
||||
}
|
||||
|
||||
list($where, , , ) = $this->queryBuilder();
|
||||
$list = $this->model->where($where)->order($this->defaultSortField)->select();
|
||||
|
||||
if ($this->assembleTree) {
|
||||
$list = $this->tree->assembleChild($list->toArray());
|
||||
} else {
|
||||
$list = $list->toArray();
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $list,
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
return $this->_add();
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
return $this->_edit();
|
||||
}
|
||||
|
||||
public function del(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
return $this->_del();
|
||||
}
|
||||
|
||||
public function select(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
list($where, , , ) = $this->queryBuilder();
|
||||
$list = $this->model->where($where)->order($this->defaultSortField)->select();
|
||||
|
||||
if ($this->assembleTree) {
|
||||
$list = $this->tree->assembleChild($list->toArray());
|
||||
} else {
|
||||
$list = $list->toArray();
|
||||
}
|
||||
|
||||
return $this->success('', ['list' => $list]);
|
||||
}
|
||||
}
|
||||
42
dafuweng-webman/app/admin/controller/user/ScoreLog.php
Normal file
42
dafuweng-webman/app/admin/controller/user/ScoreLog.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use app\admin\model\User;
|
||||
use app\admin\model\UserScoreLog;
|
||||
use app\common\controller\Backend;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class ScoreLog extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array $withJoinTable = ['user'];
|
||||
protected array|string $preExcludeFields = ['create_time'];
|
||||
protected array|string $quickSearchField = ['user.username', 'user.nickname'];
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new UserScoreLog();
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
return $this->_add();
|
||||
}
|
||||
|
||||
$userId = $request->get('userId', $request->post('userId', 0));
|
||||
$user = User::where('id', $userId)->find();
|
||||
if (!$user) {
|
||||
return $this->error(__("The user can't find it"));
|
||||
}
|
||||
return $this->success('', ['user' => $user]);
|
||||
}
|
||||
}
|
||||
160
dafuweng-webman/app/admin/controller/user/User.php
Normal file
160
dafuweng-webman/app/admin/controller/user/User.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use Throwable;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\User as UserModel;
|
||||
use Webman\Http\Request;
|
||||
use support\Response;
|
||||
|
||||
class User extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array $withJoinTable = ['userGroup'];
|
||||
protected array|string $preExcludeFields = ['last_login_time', 'login_failure', 'password', 'salt'];
|
||||
protected array|string $quickSearchField = ['username', 'nickname', 'id'];
|
||||
|
||||
protected function initController(Request $request): void
|
||||
{
|
||||
$this->model = new UserModel();
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->get('select') || $request->post('select')) {
|
||||
return $this->select($request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withoutField('password,salt')
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
if ($request->method() !== 'POST') {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$passwd = $data['password'] ?? '';
|
||||
$data = $this->excludeFields($data);
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validateClass = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validateClass)) {
|
||||
$validate = new $validateClass();
|
||||
if ($this->modelSceneValidate) $validate->scene('add');
|
||||
$validate->check($data);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$this->model->commit();
|
||||
if ($passwd) {
|
||||
$this->model->resetPassword($this->model->id, $passwd);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result !== false ? $this->success(__('Added successfully')) : $this->error(__('No rows were added'));
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
$pk = $this->model->getPk();
|
||||
$id = $request->post($pk) ?? $request->get($pk);
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds && !in_array($row[$this->dataLimitField], $dataLimitAdminIds)) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
$data = $request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
if (!empty($data['password'])) {
|
||||
$this->model->resetPassword($row->id, $data['password']);
|
||||
}
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validateClass = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validateClass)) {
|
||||
$validate = new $validateClass();
|
||||
$validate->scene('edit')->check(array_merge($data, [$pk => $row[$pk]]));
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $result !== false ? $this->success(__('Update successful')) : $this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
unset($row['password'], $row['salt']);
|
||||
$row['password'] = '';
|
||||
return $this->success('', ['row' => $row]);
|
||||
}
|
||||
|
||||
public function select(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) return $response;
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withoutField('password,salt')
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
356
dafuweng-webman/app/admin/library/Auth.php
Normal file
356
dafuweng-webman/app/admin/library/Auth.php
Normal file
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\library;
|
||||
|
||||
use Throwable;
|
||||
use ba\Random;
|
||||
use support\think\Db;
|
||||
use app\admin\model\Admin;
|
||||
use app\common\facade\Token;
|
||||
use app\admin\model\AdminGroup;
|
||||
|
||||
/**
|
||||
* 管理员权限类(Webman 迁移版)
|
||||
* @property int $id 管理员ID
|
||||
* @property string $username 管理员用户名
|
||||
* @property string $nickname 管理员昵称
|
||||
* @property string $email 管理员邮箱
|
||||
* @property string $mobile 管理员手机号
|
||||
*/
|
||||
class Auth extends \ba\Auth
|
||||
{
|
||||
public const LOGIN_RESPONSE_CODE = 303;
|
||||
public const NEED_LOGIN = 'need login';
|
||||
public const LOGGED_IN = 'logged in';
|
||||
public const TOKEN_TYPE = 'admin';
|
||||
|
||||
protected bool $loginEd = false;
|
||||
protected string $error = '';
|
||||
protected ?Admin $model = null;
|
||||
protected string $token = '';
|
||||
protected string $refreshToken = '';
|
||||
protected int $keepTime = 86400;
|
||||
protected int $refreshTokenKeepTime = 2592000;
|
||||
protected array $allowFields = ['id', 'username', 'nickname', 'avatar', 'last_login_time'];
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
parent::__construct($config);
|
||||
$this->setKeepTime((int) config('buildadmin.admin_token_keep_time', 86400 * 3));
|
||||
}
|
||||
|
||||
public function __get($name): mixed
|
||||
{
|
||||
return $this->model?->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化(Webman:从 request 获取或创建新实例)
|
||||
*/
|
||||
public static function instance(array $options = []): Auth
|
||||
{
|
||||
$request = function_exists('request') ? request() : null;
|
||||
if ($request !== null && isset($request->adminAuth) && $request->adminAuth instanceof Auth) {
|
||||
return $request->adminAuth;
|
||||
}
|
||||
$auth = new static($options);
|
||||
if ($request !== null) {
|
||||
$request->adminAuth = $auth;
|
||||
}
|
||||
return $auth;
|
||||
}
|
||||
|
||||
public function init(string $token): bool
|
||||
{
|
||||
$tokenData = Token::get($token);
|
||||
if ($tokenData) {
|
||||
Token::tokenExpirationCheck($tokenData);
|
||||
|
||||
$userId = (int) $tokenData['user_id'];
|
||||
if ($tokenData['type'] === self::TOKEN_TYPE && $userId > 0) {
|
||||
$this->model = Admin::where('id', $userId)->find();
|
||||
if (!$this->model) {
|
||||
$this->setError('Account not exist');
|
||||
return false;
|
||||
}
|
||||
if ($this->model['status'] !== 'enable') {
|
||||
$this->setError('Account disabled');
|
||||
return false;
|
||||
}
|
||||
$this->token = $token;
|
||||
$this->loginSuccessful();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$this->setError('Token login failed');
|
||||
$this->reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
public function login(string $username, string $password, bool $keep = false): bool
|
||||
{
|
||||
$this->model = Admin::where('username', $username)->find();
|
||||
if (!$this->model) {
|
||||
$this->setError('Username is incorrect');
|
||||
return false;
|
||||
}
|
||||
if ($this->model->status === 'disable') {
|
||||
$this->setError('Account disabled');
|
||||
return false;
|
||||
}
|
||||
|
||||
$adminLoginRetry = config('buildadmin.admin_login_retry');
|
||||
if ($adminLoginRetry) {
|
||||
$lastLoginTime = $this->model->getData('last_login_time');
|
||||
if ($lastLoginTime) {
|
||||
if ($this->model->login_failure > 0 && time() - $lastLoginTime >= 86400) {
|
||||
$this->model->login_failure = 0;
|
||||
$this->model->save();
|
||||
$this->model = Admin::where('username', $username)->find();
|
||||
}
|
||||
|
||||
if ($this->model->login_failure >= $adminLoginRetry) {
|
||||
$this->setError('Please try again after 1 day');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!verify_password($password, $this->model->password, ['salt' => $this->model->salt ?? ''])) {
|
||||
$this->loginFailed();
|
||||
$this->setError('Password is incorrect');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config('buildadmin.admin_sso')) {
|
||||
Token::clear(self::TOKEN_TYPE, $this->model->id);
|
||||
Token::clear(self::TOKEN_TYPE . '-refresh', $this->model->id);
|
||||
}
|
||||
|
||||
if ($keep) {
|
||||
$this->setRefreshToken($this->refreshTokenKeepTime);
|
||||
}
|
||||
$this->loginSuccessful();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setRefreshToken(int $keepTime = 0): void
|
||||
{
|
||||
$this->refreshToken = Random::uuid();
|
||||
Token::set($this->refreshToken, self::TOKEN_TYPE . '-refresh', $this->model->id, $keepTime);
|
||||
}
|
||||
|
||||
public function loginSuccessful(): bool
|
||||
{
|
||||
if (!$this->model) {
|
||||
return false;
|
||||
}
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
$this->model->login_failure = 0;
|
||||
$this->model->last_login_time = time();
|
||||
$this->model->last_login_ip = function_exists('request') && request() ? request()->getRealIp() : '';
|
||||
$this->model->save();
|
||||
$this->loginEd = true;
|
||||
|
||||
if (!$this->token) {
|
||||
$this->token = Random::uuid();
|
||||
Token::set($this->token, self::TOKEN_TYPE, $this->model->id, $this->keepTime);
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function loginFailed(): bool
|
||||
{
|
||||
if (!$this->model) {
|
||||
return false;
|
||||
}
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
$this->model->login_failure++;
|
||||
$this->model->last_login_time = time();
|
||||
$this->model->last_login_ip = function_exists('request') && request() ? request()->getRealIp() : '';
|
||||
$this->model->save();
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
}
|
||||
return $this->reset();
|
||||
}
|
||||
|
||||
public function logout(): bool
|
||||
{
|
||||
if (!$this->loginEd) {
|
||||
$this->setError('You are not logged in');
|
||||
return false;
|
||||
}
|
||||
return $this->reset();
|
||||
}
|
||||
|
||||
public function isLogin(): bool
|
||||
{
|
||||
return $this->loginEd;
|
||||
}
|
||||
|
||||
public function getAdmin(): Admin
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
public function getToken(): string
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
public function getRefreshToken(): string
|
||||
{
|
||||
return $this->refreshToken;
|
||||
}
|
||||
|
||||
public function getInfo(): array
|
||||
{
|
||||
if (!$this->model) {
|
||||
return [];
|
||||
}
|
||||
$info = $this->model->toArray();
|
||||
$info = array_intersect_key($info, array_flip($this->getAllowFields()));
|
||||
$info['token'] = $this->getToken();
|
||||
$info['refresh_token'] = $this->getRefreshToken();
|
||||
return $info;
|
||||
}
|
||||
|
||||
public function getAllowFields(): array
|
||||
{
|
||||
return $this->allowFields;
|
||||
}
|
||||
|
||||
public function setAllowFields($fields): void
|
||||
{
|
||||
$this->allowFields = $fields;
|
||||
}
|
||||
|
||||
public function setKeepTime(int $keepTime = 0): void
|
||||
{
|
||||
$this->keepTime = $keepTime;
|
||||
}
|
||||
|
||||
public function check(string $name, int $uid = 0, string $relation = 'or', string $mode = 'url'): bool
|
||||
{
|
||||
return parent::check($name, $uid ?: $this->id, $relation, $mode);
|
||||
}
|
||||
|
||||
public function getGroups(int $uid = 0): array
|
||||
{
|
||||
return parent::getGroups($uid ?: $this->id);
|
||||
}
|
||||
|
||||
public function getRuleList(int $uid = 0): array
|
||||
{
|
||||
return parent::getRuleList($uid ?: $this->id);
|
||||
}
|
||||
|
||||
public function getRuleIds(int $uid = 0): array
|
||||
{
|
||||
return parent::getRuleIds($uid ?: $this->id);
|
||||
}
|
||||
|
||||
public function getMenus(int $uid = 0): array
|
||||
{
|
||||
return parent::getMenus($uid ?: $this->id);
|
||||
}
|
||||
|
||||
public function isSuperAdmin(): bool
|
||||
{
|
||||
return in_array('*', $this->getRuleIds());
|
||||
}
|
||||
|
||||
public function getAdminChildGroups(): array
|
||||
{
|
||||
$groupIds = Db::name('admin_group_access')
|
||||
->where('uid', $this->id)
|
||||
->select();
|
||||
$children = [];
|
||||
foreach ($groupIds as $group) {
|
||||
$this->getGroupChildGroups($group['group_id'], $children);
|
||||
}
|
||||
return array_unique($children);
|
||||
}
|
||||
|
||||
public function getGroupChildGroups(int $groupId, array &$children): void
|
||||
{
|
||||
$childrenTemp = AdminGroup::where('pid', $groupId)
|
||||
->where('status', 1)
|
||||
->select();
|
||||
foreach ($childrenTemp as $item) {
|
||||
$children[] = $item['id'];
|
||||
$this->getGroupChildGroups($item['id'], $children);
|
||||
}
|
||||
}
|
||||
|
||||
public function getGroupAdmins(array $groups): array
|
||||
{
|
||||
return Db::name('admin_group_access')
|
||||
->where('group_id', 'in', $groups)
|
||||
->column('uid');
|
||||
}
|
||||
|
||||
public function getAllAuthGroups(string $dataLimit, array $groupQueryWhere = [['status', '=', 1]]): array
|
||||
{
|
||||
$rules = $this->getRuleIds();
|
||||
$allAuthGroups = [];
|
||||
$groups = AdminGroup::where($groupQueryWhere)->select();
|
||||
foreach ($groups as $group) {
|
||||
if ($group['rules'] === '*') {
|
||||
continue;
|
||||
}
|
||||
$groupRules = explode(',', $group['rules']);
|
||||
$all = true;
|
||||
foreach ($groupRules as $groupRule) {
|
||||
if (!in_array($groupRule, $rules)) {
|
||||
$all = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($all) {
|
||||
if ($dataLimit === 'allAuth' || ($dataLimit === 'allAuthAndOthers' && array_diff($rules, $groupRules))) {
|
||||
$allAuthGroups[] = $group['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $allAuthGroups;
|
||||
}
|
||||
|
||||
public function setError($error): Auth
|
||||
{
|
||||
$this->error = $error;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getError(): string
|
||||
{
|
||||
return $this->error ? __($this->error) : '';
|
||||
}
|
||||
|
||||
protected function reset(bool $deleteToken = true): bool
|
||||
{
|
||||
if ($deleteToken && $this->token) {
|
||||
Token::delete($this->token);
|
||||
}
|
||||
|
||||
$this->token = '';
|
||||
$this->loginEd = false;
|
||||
$this->model = null;
|
||||
$this->refreshToken = '';
|
||||
$this->setError('');
|
||||
$this->setKeepTime((int) config('buildadmin.admin_token_keep_time', 86400 * 3));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
924
dafuweng-webman/app/admin/library/crud/Helper.php
Normal file
924
dafuweng-webman/app/admin/library/crud/Helper.php
Normal file
@@ -0,0 +1,924 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\library\crud;
|
||||
|
||||
use Throwable;
|
||||
use ba\Filesystem;
|
||||
use ba\TableManager;
|
||||
use app\common\library\Menu;
|
||||
use app\admin\model\AdminRule;
|
||||
use app\admin\model\CrudLog;
|
||||
use ba\Exception as BaException;
|
||||
use Phinx\Db\Adapter\MysqlAdapter;
|
||||
use Phinx\Db\Adapter\AdapterInterface;
|
||||
use support\think\Db;
|
||||
|
||||
/**
|
||||
* CRUD 代码生成器 Helper(Webman 迁移版)
|
||||
*/
|
||||
class Helper
|
||||
{
|
||||
protected static array $reservedKeywords = [
|
||||
'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone',
|
||||
'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty',
|
||||
'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends',
|
||||
'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once',
|
||||
'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private',
|
||||
'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try',
|
||||
'unset', 'use', 'var', 'while', 'xor', 'yield', 'match', 'readonly', 'fn',
|
||||
];
|
||||
|
||||
protected static array $parseNamePresets = [
|
||||
'controller' => [
|
||||
'user' => ['user', 'user'],
|
||||
'admin' => ['auth', 'admin'],
|
||||
'admin_group' => ['auth', 'group'],
|
||||
'attachment' => ['routine', 'attachment'],
|
||||
'admin_rule' => ['auth', 'rule'],
|
||||
],
|
||||
'model' => [],
|
||||
'validate' => [],
|
||||
];
|
||||
|
||||
public static array $menuChildren = [
|
||||
['type' => 'button', 'title' => '查看', 'name' => '/index', 'status' => 1],
|
||||
['type' => 'button', 'title' => '添加', 'name' => '/add', 'status' => 1],
|
||||
['type' => 'button', 'title' => '编辑', 'name' => '/edit', 'status' => 1],
|
||||
['type' => 'button', 'title' => '删除', 'name' => '/del', 'status' => 1],
|
||||
['type' => 'button', 'title' => '快速排序', 'name' => '/sortable', 'status' => 1],
|
||||
];
|
||||
|
||||
protected static array $inputTypeRule = [
|
||||
['type' => ['tinyint', 'int', 'enum'], 'suffix' => ['switch', 'toggle'], 'value' => 'switch'],
|
||||
['column_type' => ['tinyint(1)', 'char(1)', 'tinyint(1) unsigned'], 'suffix' => ['switch', 'toggle'], 'value' => 'switch'],
|
||||
['type' => ['longtext', 'text', 'mediumtext', 'smalltext', 'tinytext', 'bigtext'], 'suffix' => ['content', 'editor'], 'value' => 'editor'],
|
||||
['type' => ['varchar'], 'suffix' => ['textarea', 'multiline', 'rows'], 'value' => 'textarea'],
|
||||
['suffix' => ['array'], 'value' => 'array'],
|
||||
['type' => ['int'], 'suffix' => ['time', 'datetime'], 'value' => 'timestamp'],
|
||||
['type' => ['datetime', 'timestamp'], 'value' => 'datetime'],
|
||||
['type' => ['date'], 'value' => 'date'],
|
||||
['type' => ['year'], 'value' => 'year'],
|
||||
['type' => ['time'], 'value' => 'time'],
|
||||
['suffix' => ['select', 'list', 'data'], 'value' => 'select'],
|
||||
['suffix' => ['selects', 'multi', 'lists'], 'value' => 'selects'],
|
||||
['suffix' => ['_id'], 'value' => 'remoteSelect'],
|
||||
['suffix' => ['_ids'], 'value' => 'remoteSelects'],
|
||||
['suffix' => ['city'], 'value' => 'city'],
|
||||
['suffix' => ['image', 'avatar'], 'value' => 'image'],
|
||||
['suffix' => ['images', 'avatars'], 'value' => 'images'],
|
||||
['suffix' => ['file'], 'value' => 'file'],
|
||||
['suffix' => ['files'], 'value' => 'files'],
|
||||
['suffix' => ['icon'], 'value' => 'icon'],
|
||||
['column_type' => ['tinyint(1)', 'char(1)', 'tinyint(1) unsigned'], 'suffix' => ['status', 'state', 'type'], 'value' => 'radio'],
|
||||
['suffix' => ['number', 'int', 'num'], 'value' => 'number'],
|
||||
['type' => ['bigint', 'int', 'mediumint', 'smallint', 'tinyint', 'decimal', 'double', 'float'], 'value' => 'number'],
|
||||
['type' => ['longtext', 'text', 'mediumtext', 'smalltext', 'tinytext', 'bigtext'], 'value' => 'textarea'],
|
||||
['type' => ['enum'], 'value' => 'radio'],
|
||||
['type' => ['set'], 'value' => 'checkbox'],
|
||||
['suffix' => ['color'], 'value' => 'color'],
|
||||
];
|
||||
|
||||
protected static array $parseWebDirPresets = [
|
||||
'lang' => [],
|
||||
'views' => [
|
||||
'user' => ['user', 'user'],
|
||||
'admin' => ['auth', 'admin'],
|
||||
'admin_group' => ['auth', 'group'],
|
||||
'attachment' => ['routine', 'attachment'],
|
||||
'admin_rule' => ['auth', 'rule'],
|
||||
],
|
||||
];
|
||||
|
||||
protected static string $createTimeField = 'create_time';
|
||||
protected static string $updateTimeField = 'update_time';
|
||||
|
||||
protected static array $attrType = [
|
||||
'controller' => [
|
||||
'preExcludeFields' => 'array|string',
|
||||
'quickSearchField' => 'string|array',
|
||||
'withJoinTable' => 'array',
|
||||
'defaultSortField' => 'string|array',
|
||||
'weighField' => 'string',
|
||||
],
|
||||
];
|
||||
|
||||
public static function getDictData(array &$dict, array $field, string $lang, string $translationPrefix = ''): array
|
||||
{
|
||||
if (!$field['comment']) return [];
|
||||
$comment = str_replace([',', ':'], [',', ':'], $field['comment']);
|
||||
if (stripos($comment, ':') !== false && stripos($comment, ',') && stripos($comment, '=') !== false) {
|
||||
[$fieldTitle, $item] = explode(':', $comment);
|
||||
$dict[$translationPrefix . $field['name']] = $lang == 'en' ? $field['name'] : $fieldTitle;
|
||||
foreach (explode(',', $item) as $v) {
|
||||
$valArr = explode('=', $v);
|
||||
if (count($valArr) == 2) {
|
||||
[$key, $value] = $valArr;
|
||||
$dict[$translationPrefix . $field['name'] . ' ' . $key] = $lang == 'en' ? $field['name'] . ' ' . $key : $value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$dict[$translationPrefix . $field['name']] = $lang == 'en' ? $field['name'] : $comment;
|
||||
}
|
||||
return $dict;
|
||||
}
|
||||
|
||||
public static function recordCrudStatus(array $data): int
|
||||
{
|
||||
if (isset($data['id'])) {
|
||||
CrudLog::where('id', $data['id'])->update(['status' => $data['status']]);
|
||||
return $data['id'];
|
||||
}
|
||||
|
||||
$connection = $data['table']['databaseConnection'] ?? config('thinkorm.default', config('database.default', 'mysql'));
|
||||
$log = CrudLog::create([
|
||||
'table_name' => $data['table']['name'],
|
||||
'comment' => $data['table']['comment'],
|
||||
'table' => $data['table'],
|
||||
'fields' => $data['fields'],
|
||||
'connection' => $connection,
|
||||
'status' => $data['status'],
|
||||
]);
|
||||
return $log->id;
|
||||
}
|
||||
|
||||
public static function getPhinxFieldType(string $type, array $field): array
|
||||
{
|
||||
if ($type == 'tinyint') {
|
||||
if (
|
||||
(isset($field['dataType']) && $field['dataType'] == 'tinyint(1)') ||
|
||||
($field['default'] == '1' && ($field['defaultType'] ?? '') == 'INPUT')
|
||||
) {
|
||||
$type = 'boolean';
|
||||
}
|
||||
}
|
||||
$phinxFieldTypeMap = [
|
||||
'tinyint' => ['type' => AdapterInterface::PHINX_TYPE_INTEGER, 'limit' => MysqlAdapter::INT_TINY],
|
||||
'smallint' => ['type' => AdapterInterface::PHINX_TYPE_INTEGER, 'limit' => MysqlAdapter::INT_SMALL],
|
||||
'mediumint' => ['type' => AdapterInterface::PHINX_TYPE_INTEGER, 'limit' => MysqlAdapter::INT_MEDIUM],
|
||||
'int' => ['type' => AdapterInterface::PHINX_TYPE_INTEGER, 'limit' => null],
|
||||
'bigint' => ['type' => AdapterInterface::PHINX_TYPE_BIG_INTEGER, 'limit' => null],
|
||||
'boolean' => ['type' => AdapterInterface::PHINX_TYPE_BOOLEAN, 'limit' => null],
|
||||
'varchar' => ['type' => AdapterInterface::PHINX_TYPE_STRING, 'limit' => null],
|
||||
'tinytext' => ['type' => AdapterInterface::PHINX_TYPE_TEXT, 'limit' => MysqlAdapter::TEXT_TINY],
|
||||
'mediumtext' => ['type' => AdapterInterface::PHINX_TYPE_TEXT, 'limit' => MysqlAdapter::TEXT_MEDIUM],
|
||||
'longtext' => ['type' => AdapterInterface::PHINX_TYPE_TEXT, 'limit' => MysqlAdapter::TEXT_LONG],
|
||||
'tinyblob' => ['type' => AdapterInterface::PHINX_TYPE_BLOB, 'limit' => MysqlAdapter::BLOB_TINY],
|
||||
'mediumblob' => ['type' => AdapterInterface::PHINX_TYPE_BLOB, 'limit' => MysqlAdapter::BLOB_MEDIUM],
|
||||
'longblob' => ['type' => AdapterInterface::PHINX_TYPE_BLOB, 'limit' => MysqlAdapter::BLOB_LONG],
|
||||
];
|
||||
return array_key_exists($type, $phinxFieldTypeMap) ? $phinxFieldTypeMap[$type] : ['type' => $type, 'limit' => null];
|
||||
}
|
||||
|
||||
public static function analyseFieldLimit(string $type, array $field): array
|
||||
{
|
||||
$fieldType = ['decimal' => ['decimal', 'double', 'float'], 'values' => ['enum', 'set']];
|
||||
$dataTypeLimit = self::dataTypeLimit($field['dataType'] ?? '');
|
||||
if (in_array($type, $fieldType['decimal'])) {
|
||||
if ($dataTypeLimit) {
|
||||
return ['precision' => $dataTypeLimit[0], 'scale' => $dataTypeLimit[1] ?? 0];
|
||||
}
|
||||
$scale = isset($field['precision']) ? intval($field['precision']) : 0;
|
||||
return ['precision' => $field['length'] ?? 10, 'scale' => $scale];
|
||||
} elseif (in_array($type, $fieldType['values'])) {
|
||||
foreach ($dataTypeLimit as &$item) {
|
||||
$item = str_replace(['"', "'"], '', $item);
|
||||
}
|
||||
return ['values' => $dataTypeLimit];
|
||||
} elseif ($dataTypeLimit && $dataTypeLimit[0]) {
|
||||
return ['limit' => $dataTypeLimit[0]];
|
||||
} elseif (isset($field['length'])) {
|
||||
return ['limit' => $field['length']];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function dataTypeLimit(string $dataType): array
|
||||
{
|
||||
preg_match("/\((.*?)\)/", $dataType, $matches);
|
||||
if (isset($matches[1]) && $matches[1]) {
|
||||
return explode(',', trim($matches[1], ','));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function analyseFieldDefault(array $field): mixed
|
||||
{
|
||||
return match ($field['defaultType'] ?? 'NONE') {
|
||||
'EMPTY STRING' => '',
|
||||
'NULL' => null,
|
||||
default => $field['default'] ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
public static function searchArray($fields, callable $myFunction): array|bool
|
||||
{
|
||||
foreach ($fields as $key => $field) {
|
||||
if (call_user_func($myFunction, $field, $key)) {
|
||||
return $field;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getPhinxFieldData(array $field): array
|
||||
{
|
||||
$conciseType = self::analyseFieldType($field);
|
||||
$phinxTypeData = self::getPhinxFieldType($conciseType, $field);
|
||||
$phinxColumnOptions = self::analyseFieldLimit($conciseType, $field);
|
||||
if ($phinxTypeData['limit'] !== null) {
|
||||
$phinxColumnOptions['limit'] = $phinxTypeData['limit'];
|
||||
}
|
||||
$noDefaultValueFields = [
|
||||
'text', 'blob', 'geometry', 'geometrycollection', 'json', 'linestring', 'longblob', 'longtext', 'mediumblob',
|
||||
'mediumtext', 'multilinestring', 'multipoint', 'multipolygon', 'point', 'polygon', 'tinyblob',
|
||||
];
|
||||
if (($field['defaultType'] ?? '') != 'NONE' && !in_array($conciseType, $noDefaultValueFields)) {
|
||||
$phinxColumnOptions['default'] = self::analyseFieldDefault($field);
|
||||
}
|
||||
$phinxColumnOptions['null'] = (bool)($field['null'] ?? false);
|
||||
$phinxColumnOptions['comment'] = $field['comment'] ?? '';
|
||||
$phinxColumnOptions['signed'] = !($field['unsigned'] ?? false);
|
||||
$phinxColumnOptions['identity'] = $field['autoIncrement'] ?? false;
|
||||
return ['type' => $phinxTypeData['type'], 'options' => $phinxColumnOptions];
|
||||
}
|
||||
|
||||
public static function updateFieldOrder(string $tableName, array $fields, array $designChange, ?string $connection = null): void
|
||||
{
|
||||
if ($designChange) {
|
||||
$table = TableManager::phinxTable($tableName, [], false, $connection);
|
||||
foreach ($designChange as $item) {
|
||||
if (!$item['sync']) continue;
|
||||
if (!empty($item['after'])) {
|
||||
$fieldName = in_array($item['type'], ['add-field', 'change-field-name']) ? $item['newName'] : $item['oldName'];
|
||||
$field = self::searchArray($fields, fn($f) => $f['name'] == $fieldName);
|
||||
if (!$field) continue;
|
||||
$phinxFieldData = self::getPhinxFieldData($field);
|
||||
$phinxFieldData['options']['after'] = $item['after'] == 'FIRST FIELD' ? MysqlAdapter::FIRST : $item['after'];
|
||||
$table->changeColumn($fieldName, $phinxFieldData['type'], $phinxFieldData['options']);
|
||||
}
|
||||
}
|
||||
$table->update();
|
||||
}
|
||||
}
|
||||
|
||||
public static function handleTableDesign(array $table, array $fields): array
|
||||
{
|
||||
$name = TableManager::tableName($table['name'], true, $table['databaseConnection'] ?? null);
|
||||
$comment = $table['comment'] ?? '';
|
||||
$designChange = $table['designChange'] ?? [];
|
||||
$adapter = TableManager::phinxAdapter(false, $table['databaseConnection'] ?? null);
|
||||
|
||||
$pk = self::searchArray($fields, fn($item) => $item['primaryKey'] ?? false);
|
||||
$pk = $pk ? $pk['name'] : '';
|
||||
|
||||
if ($adapter->hasTable($name)) {
|
||||
if ($designChange) {
|
||||
$tableManager = TableManager::phinxTable($name, [], false, $table['databaseConnection'] ?? null);
|
||||
$tableManager->changeComment($comment)->update();
|
||||
$priorityOpt = false;
|
||||
foreach ($designChange as $item) {
|
||||
if (!$item['sync']) continue;
|
||||
if (in_array($item['type'], ['change-field-name', 'del-field']) && !$tableManager->hasColumn($item['oldName'])) {
|
||||
throw new BaException(__($item['type'] . ' fail not exist', [$item['oldName']]));
|
||||
}
|
||||
if ($item['type'] == 'change-field-name') {
|
||||
$priorityOpt = true;
|
||||
$tableManager->renameColumn($item['oldName'], $item['newName']);
|
||||
} elseif ($item['type'] == 'del-field') {
|
||||
$priorityOpt = true;
|
||||
$tableManager->removeColumn($item['oldName']);
|
||||
}
|
||||
}
|
||||
if ($priorityOpt) {
|
||||
$tableManager->update();
|
||||
}
|
||||
foreach ($designChange as $item) {
|
||||
if (!$item['sync']) continue;
|
||||
if ($item['type'] == 'change-field-attr') {
|
||||
if (!$tableManager->hasColumn($item['oldName'])) {
|
||||
throw new BaException(__($item['type'] . ' fail not exist', [$item['oldName']]));
|
||||
}
|
||||
$field = self::searchArray($fields, fn($f) => $f['name'] == $item['oldName']);
|
||||
if ($field) {
|
||||
$phinxFieldData = self::getPhinxFieldData($field);
|
||||
$tableManager->changeColumn($item['oldName'], $phinxFieldData['type'], $phinxFieldData['options']);
|
||||
}
|
||||
} elseif ($item['type'] == 'add-field') {
|
||||
if ($tableManager->hasColumn($item['newName'])) {
|
||||
throw new BaException(__($item['type'] . ' fail exist', [$item['newName']]));
|
||||
}
|
||||
$field = self::searchArray($fields, fn($f) => $f['name'] == $item['newName']);
|
||||
if ($field) {
|
||||
$phinxFieldData = self::getPhinxFieldData($field);
|
||||
$tableManager->addColumn($item['newName'], $phinxFieldData['type'], $phinxFieldData['options']);
|
||||
}
|
||||
}
|
||||
}
|
||||
$tableManager->update();
|
||||
self::updateFieldOrder($name, $fields, $designChange, $table['databaseConnection'] ?? null);
|
||||
}
|
||||
} else {
|
||||
$tableManager = TableManager::phinxTable($name, [
|
||||
'id' => false, 'comment' => $comment, 'row_format' => 'DYNAMIC',
|
||||
'primary_key' => $pk, 'collation' => 'utf8mb4_unicode_ci',
|
||||
], false, $table['databaseConnection'] ?? null);
|
||||
foreach ($fields as $field) {
|
||||
$phinxFieldData = self::getPhinxFieldData($field);
|
||||
$tableManager->addColumn($field['name'], $phinxFieldData['type'], $phinxFieldData['options']);
|
||||
}
|
||||
$tableManager->create();
|
||||
}
|
||||
return [$pk];
|
||||
}
|
||||
|
||||
public static function parseNameData($app, $table, $type, $value = ''): array
|
||||
{
|
||||
$pathArr = [];
|
||||
if ($value) {
|
||||
$value = str_replace('.php', '', $value);
|
||||
$value = str_replace(['.', '/', '\\', '_'], '/', $value);
|
||||
$pathArrTemp = explode('/', $value);
|
||||
$redundantDir = ['app' => 0, $app => 1, $type => 2];
|
||||
foreach ($pathArrTemp as $key => $item) {
|
||||
if (!array_key_exists($item, $redundantDir) || $key !== $redundantDir[$item]) {
|
||||
$pathArr[] = $item;
|
||||
}
|
||||
}
|
||||
} elseif (isset(self::$parseNamePresets[$type]) && array_key_exists($table, self::$parseNamePresets[$type])) {
|
||||
$pathArr = self::$parseNamePresets[$type][$table];
|
||||
} else {
|
||||
$table = str_replace(['.', '/', '\\', '_'], '/', $table);
|
||||
$pathArr = explode('/', $table);
|
||||
}
|
||||
$originalLastName = array_pop($pathArr);
|
||||
$pathArr = array_map('strtolower', $pathArr);
|
||||
$lastName = ucfirst($originalLastName);
|
||||
|
||||
if (in_array(strtolower($originalLastName), self::$reservedKeywords)) {
|
||||
throw new \Exception('Unable to use internal variable:' . $lastName);
|
||||
}
|
||||
|
||||
$appDir = root_path() . 'app' . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR;
|
||||
$namespace = "app\\$app\\$type" . ($pathArr ? '\\' . implode('\\', $pathArr) : '');
|
||||
$parseFile = $appDir . $type . DIRECTORY_SEPARATOR . ($pathArr ? implode(DIRECTORY_SEPARATOR, $pathArr) . DIRECTORY_SEPARATOR : '') . $lastName . '.php';
|
||||
$rootFileName = $namespace . "/$lastName" . '.php';
|
||||
|
||||
return [
|
||||
'lastName' => $lastName,
|
||||
'originalLastName' => $originalLastName,
|
||||
'path' => $pathArr,
|
||||
'namespace' => $namespace,
|
||||
'parseFile' => Filesystem::fsFit($parseFile),
|
||||
'rootFileName' => Filesystem::fsFit($rootFileName),
|
||||
];
|
||||
}
|
||||
|
||||
public static function parseWebDirNameData($table, $type, $value = ''): array
|
||||
{
|
||||
$pathArr = [];
|
||||
if ($value) {
|
||||
$value = str_replace(['.', '/', '\\', '_'], '/', $value);
|
||||
$pathArrTemp = explode('/', $value);
|
||||
$redundantDir = ['web' => 0, 'src' => 1, 'views' => 2, 'lang' => 2, 'backend' => 3, 'pages' => 3, 'en' => 4, 'zh-cn' => 4];
|
||||
foreach ($pathArrTemp as $key => $item) {
|
||||
if (!array_key_exists($item, $redundantDir) || $key !== $redundantDir[$item]) {
|
||||
$pathArr[] = $item;
|
||||
}
|
||||
}
|
||||
} elseif (isset(self::$parseWebDirPresets[$type]) && array_key_exists($table, self::$parseWebDirPresets[$type])) {
|
||||
$pathArr = self::$parseWebDirPresets[$type][$table];
|
||||
} else {
|
||||
$table = str_replace(['.', '/', '\\', '_'], '/', $table);
|
||||
$pathArr = explode('/', $table);
|
||||
}
|
||||
$originalLastName = array_pop($pathArr);
|
||||
$pathArr = array_map('strtolower', $pathArr);
|
||||
$lastName = lcfirst($originalLastName);
|
||||
|
||||
$webDir['path'] = $pathArr;
|
||||
$webDir['lastName'] = $lastName;
|
||||
$webDir['originalLastName'] = $originalLastName;
|
||||
if ($type == 'views') {
|
||||
$webDir['views'] = "web/src/views/backend" . ($pathArr ? '/' . implode('/', $pathArr) : '') . "/$lastName";
|
||||
} elseif ($type == 'lang') {
|
||||
$webDir['lang'] = array_merge($pathArr, [$lastName]);
|
||||
foreach (['en', 'zh-cn'] as $item) {
|
||||
$webDir[$item] = "web/src/lang/backend/$item" . ($pathArr ? '/' . implode('/', $pathArr) : '') . "/$lastName";
|
||||
}
|
||||
}
|
||||
foreach ($webDir as &$item) {
|
||||
if (is_string($item)) $item = Filesystem::fsFit($item);
|
||||
}
|
||||
return $webDir;
|
||||
}
|
||||
|
||||
public static function getMenuName(array $webDir): string
|
||||
{
|
||||
return ($webDir['path'] ? implode('/', $webDir['path']) . '/' : '') . $webDir['originalLastName'];
|
||||
}
|
||||
|
||||
public static function getStubFilePath(string $name): string
|
||||
{
|
||||
return root_path() . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'crud' . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . Filesystem::fsFit($name) . '.stub';
|
||||
}
|
||||
|
||||
public static function arrayToString(array|string $value): string
|
||||
{
|
||||
if (!is_array($value)) return $value;
|
||||
foreach ($value as &$item) {
|
||||
$item = self::arrayToString($item);
|
||||
}
|
||||
return implode(PHP_EOL, $value);
|
||||
}
|
||||
|
||||
public static function assembleStub(string $name, array $data, bool $escape = false): string
|
||||
{
|
||||
foreach ($data as &$datum) {
|
||||
$datum = self::arrayToString($datum);
|
||||
}
|
||||
$search = $replace = [];
|
||||
foreach ($data as $k => $v) {
|
||||
$search[] = "{%$k%}";
|
||||
$replace[] = $v;
|
||||
}
|
||||
$stubPath = self::getStubFilePath($name);
|
||||
$stubContent = file_get_contents($stubPath);
|
||||
$content = str_replace($search, $replace, $stubContent);
|
||||
return $escape ? self::escape($content) : $content;
|
||||
}
|
||||
|
||||
public static function escape(array|string $value): string
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8', false);
|
||||
}
|
||||
|
||||
public static function tab(int $num = 1): string
|
||||
{
|
||||
return str_pad('', 4 * $num);
|
||||
}
|
||||
|
||||
public static function parseTableColumns(string $table, bool $analyseField = false, ?string $connection = null): array
|
||||
{
|
||||
$connection = TableManager::getConnection($connection);
|
||||
$connectionConfig = TableManager::getConnectionConfig($connection);
|
||||
|
||||
$sql = 'SELECT * FROM `information_schema`.`columns` '
|
||||
. 'WHERE TABLE_SCHEMA = ? AND table_name = ? '
|
||||
. 'ORDER BY ORDINAL_POSITION';
|
||||
|
||||
$tableColumn = Db::connect($connection)->query($sql, [$connectionConfig['database'], TableManager::tableName($table, true, $connection)]);
|
||||
|
||||
$columns = [];
|
||||
foreach ($tableColumn as $item) {
|
||||
$isNullAble = $item['IS_NULLABLE'] == 'YES';
|
||||
if (str_contains($item['COLUMN_TYPE'], '(')) {
|
||||
$dataType = substr_replace($item['COLUMN_TYPE'], '', stripos($item['COLUMN_TYPE'], ')') + 1);
|
||||
} else {
|
||||
$dataType = str_replace(' unsigned', '', $item['COLUMN_TYPE']);
|
||||
}
|
||||
|
||||
$default = '';
|
||||
if ($isNullAble && $item['COLUMN_DEFAULT'] === null) {
|
||||
$defaultType = 'NULL';
|
||||
} elseif ($item['COLUMN_DEFAULT'] == '' && in_array($item['DATA_TYPE'], ['varchar', 'char'])) {
|
||||
$defaultType = 'EMPTY STRING';
|
||||
} elseif (!$isNullAble && $item['COLUMN_DEFAULT'] === null) {
|
||||
$defaultType = 'NONE';
|
||||
} else {
|
||||
$defaultType = 'INPUT';
|
||||
$default = $item['COLUMN_DEFAULT'];
|
||||
}
|
||||
|
||||
$column = [
|
||||
'name' => $item['COLUMN_NAME'],
|
||||
'type' => $item['DATA_TYPE'],
|
||||
'dataType' => $dataType,
|
||||
'default' => $default,
|
||||
'defaultType' => $defaultType,
|
||||
'null' => $isNullAble,
|
||||
'primaryKey' => $item['COLUMN_KEY'] == 'PRI',
|
||||
'unsigned' => (bool)stripos($item['COLUMN_TYPE'], 'unsigned'),
|
||||
'autoIncrement' => stripos($item['EXTRA'], 'auto_increment') !== false,
|
||||
'comment' => $item['COLUMN_COMMENT'],
|
||||
'designType' => self::getTableColumnsDataType($item),
|
||||
'table' => [],
|
||||
'form' => [],
|
||||
];
|
||||
if ($analyseField) {
|
||||
self::analyseField($column);
|
||||
} else {
|
||||
self::handleTableColumn($column);
|
||||
}
|
||||
$columns[$item['COLUMN_NAME']] = $column;
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
public static function handleTableColumn(&$column): void
|
||||
{
|
||||
}
|
||||
|
||||
public static function analyseFieldType(array $field): string
|
||||
{
|
||||
$dataType = (isset($field['dataType']) && $field['dataType']) ? $field['dataType'] : $field['type'];
|
||||
if (stripos($dataType, '(') !== false) {
|
||||
$typeName = explode('(', $dataType);
|
||||
return trim($typeName[0]);
|
||||
}
|
||||
return trim($dataType);
|
||||
}
|
||||
|
||||
public static function analyseFieldDataType(array $field): string
|
||||
{
|
||||
if (!empty($field['dataType'])) return $field['dataType'];
|
||||
$conciseType = self::analyseFieldType($field);
|
||||
$limit = self::analyseFieldLimit($conciseType, $field);
|
||||
if (isset($limit['precision'])) {
|
||||
return "$conciseType({$limit['precision']}, {$limit['scale']})";
|
||||
}
|
||||
if (isset($limit['values'])) {
|
||||
return "$conciseType(" . implode(',', $limit['values']) . ")";
|
||||
}
|
||||
return "$conciseType({$limit['limit']})";
|
||||
}
|
||||
|
||||
public static function analyseField(&$field): void
|
||||
{
|
||||
$field['type'] = self::analyseFieldType($field);
|
||||
$field['originalDesignType'] = $field['designType'];
|
||||
|
||||
$designTypeComparison = ['pk' => 'string', 'weigh' => 'number', 'timestamp' => 'datetime', 'float' => 'number'];
|
||||
if (array_key_exists($field['designType'], $designTypeComparison)) {
|
||||
$field['designType'] = $designTypeComparison[$field['designType']];
|
||||
}
|
||||
|
||||
$supportMultipleComparison = ['select', 'image', 'file', 'remoteSelect'];
|
||||
if (in_array($field['designType'], $supportMultipleComparison)) {
|
||||
$multiKey = $field['designType'] == 'remoteSelect' ? 'select-multi' : $field['designType'] . '-multi';
|
||||
if (isset($field['form'][$multiKey]) && $field['form'][$multiKey]) {
|
||||
$field['designType'] = $field['designType'] . 's';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTableColumnsDataType($column): string
|
||||
{
|
||||
if (stripos($column['COLUMN_NAME'], 'id') !== false && stripos($column['EXTRA'], 'auto_increment') !== false) {
|
||||
return 'pk';
|
||||
}
|
||||
if ($column['COLUMN_NAME'] == 'weigh') return 'weigh';
|
||||
if (in_array($column['COLUMN_NAME'], ['createtime', 'updatetime', 'create_time', 'update_time'])) return 'timestamp';
|
||||
|
||||
foreach (self::$inputTypeRule as $item) {
|
||||
$typeBool = !isset($item['type']) || !$item['type'] || in_array($column['DATA_TYPE'], $item['type']);
|
||||
$suffixBool = !isset($item['suffix']) || !$item['suffix'] || self::isMatchSuffix($column['COLUMN_NAME'], $item['suffix']);
|
||||
$columnTypeBool = !isset($item['column_type']) || !$item['column_type'] || in_array($column['COLUMN_TYPE'], $item['column_type']);
|
||||
if ($typeBool && $suffixBool && $columnTypeBool) {
|
||||
return $item['value'];
|
||||
}
|
||||
}
|
||||
return 'string';
|
||||
}
|
||||
|
||||
protected static function isMatchSuffix(string $field, string|array $suffixArr): bool
|
||||
{
|
||||
$suffixArr = is_array($suffixArr) ? $suffixArr : explode(',', $suffixArr);
|
||||
foreach ($suffixArr as $v) {
|
||||
if (preg_match("/$v$/i", $field)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function createMenu($webViewsDir, $tableComment): void
|
||||
{
|
||||
$menuName = self::getMenuName($webViewsDir);
|
||||
if (AdminRule::where('name', $menuName)->value('id')) {
|
||||
return;
|
||||
}
|
||||
$menuChildren = self::$menuChildren;
|
||||
foreach ($menuChildren as &$item) {
|
||||
$item['name'] = $menuName . $item['name'];
|
||||
}
|
||||
$componentPath = str_replace(['\\', 'web/src'], ['/', '/src'], $webViewsDir['views'] . '/' . 'index.vue');
|
||||
|
||||
$menus = [
|
||||
'type' => 'menu',
|
||||
'title' => $tableComment ?: $webViewsDir['originalLastName'],
|
||||
'name' => $menuName,
|
||||
'path' => $menuName,
|
||||
'menu_type' => 'tab',
|
||||
'keepalive' => 1,
|
||||
'component' => $componentPath,
|
||||
'children' => $menuChildren,
|
||||
];
|
||||
$paths = array_reverse($webViewsDir['path']);
|
||||
foreach ($paths as $path) {
|
||||
$menus = [
|
||||
'type' => 'menu_dir',
|
||||
'title' => $path,
|
||||
'name' => $path,
|
||||
'path' => $path,
|
||||
'children' => [$menus],
|
||||
];
|
||||
}
|
||||
Menu::create([$menus], 0, 'ignore');
|
||||
}
|
||||
|
||||
public static function writeWebLangFile($langData, $webLangDir): void
|
||||
{
|
||||
foreach ($langData as $lang => $langDatum) {
|
||||
$langTsContent = '';
|
||||
foreach ($langDatum as $key => $item) {
|
||||
$quote = self::getQuote($item);
|
||||
$keyStr = self::formatObjectKey($key);
|
||||
$langTsContent .= self::tab() . $keyStr . ": $quote$item$quote,\n";
|
||||
}
|
||||
$langTsContent = "export default {\n" . $langTsContent . "}\n";
|
||||
self::writeFile(root_path() . $webLangDir[$lang] . '.ts', $langTsContent);
|
||||
}
|
||||
}
|
||||
|
||||
public static function writeFile($path, $content): bool|int
|
||||
{
|
||||
$path = Filesystem::fsFit($path);
|
||||
if (!is_dir(dirname($path))) {
|
||||
mkdir(dirname($path), 0755, true);
|
||||
}
|
||||
return file_put_contents($path, $content);
|
||||
}
|
||||
|
||||
public static function buildModelAppend($append): string
|
||||
{
|
||||
if (!$append) return '';
|
||||
return "\n" . self::tab() . "// 追加属性\n" . self::tab() . "protected \$append = " . self::buildFormatSimpleArray($append) . ";\n";
|
||||
}
|
||||
|
||||
public static function buildModelFieldType(array $fieldType): string
|
||||
{
|
||||
if (!$fieldType) return '';
|
||||
$maxStrLang = 0;
|
||||
foreach ($fieldType as $key => $item) {
|
||||
$maxStrLang = max(strlen($key), $maxStrLang);
|
||||
}
|
||||
$str = '';
|
||||
foreach ($fieldType as $key => $item) {
|
||||
$str .= self::tab(2) . "'$key'" . str_pad('=>', ($maxStrLang - strlen($key) + 3), ' ', STR_PAD_LEFT) . " '$item',\n";
|
||||
}
|
||||
return "\n" . self::tab() . "// 字段类型转换\n" . self::tab() . "protected \$type = [\n" . rtrim($str, "\n") . "\n" . self::tab() . "];\n";
|
||||
}
|
||||
|
||||
public static function writeModelFile(string $tablePk, array $fieldsMap, array $modelData, array $modelFile): void
|
||||
{
|
||||
if ($modelData['connection'] && $modelData['connection'] != config('thinkorm.default', config('database.default', 'mysql'))) {
|
||||
$modelData['connection'] = "\n" . self::tab() . "// 数据库连接配置标识\n" . self::tab() . 'protected $connection = ' . "'{$modelData['connection']}';\n";
|
||||
} else {
|
||||
$modelData['connection'] = '';
|
||||
}
|
||||
$modelData['pk'] = $tablePk == 'id' ? '' : "\n" . self::tab() . "// 表主键\n" . self::tab() . 'protected $pk = ' . "'$tablePk';\n";
|
||||
$modelData['autoWriteTimestamp'] = array_key_exists(self::$createTimeField, $fieldsMap) || array_key_exists(self::$updateTimeField, $fieldsMap) ? 'true' : 'false';
|
||||
if ($modelData['autoWriteTimestamp'] == 'true') {
|
||||
$modelData['createTime'] = array_key_exists(self::$createTimeField, $fieldsMap) ? '' : "\n" . self::tab() . "protected \$createTime = false;";
|
||||
$modelData['updateTime'] = array_key_exists(self::$updateTimeField, $fieldsMap) ? '' : "\n" . self::tab() . "protected \$updateTime = false;";
|
||||
}
|
||||
$modelMethodList = isset($modelData['relationMethodList']) ? array_merge($modelData['methods'], $modelData['relationMethodList']) : $modelData['methods'];
|
||||
$modelData['methods'] = $modelMethodList ? "\n" . implode("\n", $modelMethodList) : '';
|
||||
$modelData['append'] = self::buildModelAppend($modelData['append'] ?? []);
|
||||
$modelData['fieldType'] = self::buildModelFieldType($modelData['fieldType'] ?? []);
|
||||
|
||||
if (isset($modelData['beforeInsertMixins']['snowflake'])) {
|
||||
$modelData['beforeInsert'] = self::assembleStub('mixins/model/beforeInsert', [
|
||||
'setSnowFlakeIdCode' => $modelData['beforeInsertMixins']['snowflake']
|
||||
]);
|
||||
}
|
||||
if (($modelData['afterInsert'] ?? '') && ($modelData['beforeInsert'] ?? '')) {
|
||||
$modelData['afterInsert'] = "\n" . $modelData['afterInsert'];
|
||||
}
|
||||
|
||||
$modelFileContent = self::assembleStub('mixins/model/model', $modelData);
|
||||
self::writeFile($modelFile['parseFile'], $modelFileContent);
|
||||
}
|
||||
|
||||
public static function writeControllerFile(array $controllerData, array $controllerFile): void
|
||||
{
|
||||
if (isset($controllerData['relationVisibleFieldList']) && $controllerData['relationVisibleFieldList']) {
|
||||
$relationVisibleFields = '->visible([';
|
||||
foreach ($controllerData['relationVisibleFieldList'] as $cKey => $controllerDatum) {
|
||||
$relationVisibleFields .= "'$cKey' => ['" . implode("', '", $controllerDatum) . "'], ";
|
||||
}
|
||||
$relationVisibleFields = rtrim($relationVisibleFields, ', ') . '])';
|
||||
$controllerData['methods']['index'] = self::assembleStub('mixins/controller/index', [
|
||||
'relationVisibleFields' => $relationVisibleFields
|
||||
]);
|
||||
$controllerData['use']['Throwable'] = "\nuse Throwable;";
|
||||
unset($controllerData['relationVisibleFieldList']);
|
||||
}
|
||||
$controllerAttr = '';
|
||||
foreach ($controllerData['attr'] ?? [] as $key => $item) {
|
||||
$attrType = self::$attrType['controller'][$key] ?? '';
|
||||
if (is_array($item)) {
|
||||
$controllerAttr .= "\n" . self::tab() . "protected $attrType \$$key = ['" . implode("', '", $item) . "'];\n";
|
||||
} elseif ($item) {
|
||||
$controllerAttr .= "\n" . self::tab() . "protected $attrType \$$key = '$item';\n";
|
||||
}
|
||||
}
|
||||
$controllerData['attr'] = $controllerAttr;
|
||||
$controllerData['initialize'] = self::assembleStub('mixins/controller/initialize', [
|
||||
'modelNamespace' => $controllerData['modelNamespace'],
|
||||
'modelName' => $controllerData['modelName'],
|
||||
'filterRule' => $controllerData['filterRule'] ?? '',
|
||||
]);
|
||||
$contentFileContent = self::assembleStub('mixins/controller/controller', $controllerData);
|
||||
self::writeFile($controllerFile['parseFile'], $contentFileContent);
|
||||
}
|
||||
|
||||
public static function writeFormFile($formVueData, $webViewsDir, $fields, $webTranslate): void
|
||||
{
|
||||
$fieldHtml = "\n";
|
||||
$formVueData['bigDialog'] = $formVueData['bigDialog'] ? "\n" . self::tab(2) . 'width="70%"' : '';
|
||||
foreach ($formVueData['formFields'] ?? [] as $field) {
|
||||
$fieldHtml .= self::tab(5) . "<FormItem";
|
||||
foreach ($field as $key => $attr) {
|
||||
if (is_array($attr)) {
|
||||
$fieldHtml .= ' ' . $key . '="' . self::getJsonFromArray($attr) . '"';
|
||||
} else {
|
||||
$fieldHtml .= ' ' . $key . '="' . $attr . '"';
|
||||
}
|
||||
}
|
||||
$fieldHtml .= " />\n";
|
||||
}
|
||||
$formVueData['formFields'] = rtrim($fieldHtml, "\n");
|
||||
|
||||
foreach ($fields as $field) {
|
||||
if (isset($field['form']['validator'])) {
|
||||
foreach ($field['form']['validator'] as $item) {
|
||||
$message = isset($field['form']['validatorMsg']) && $field['form']['validatorMsg'] ? ", message: '{$field['form']['validatorMsg']}'" : '';
|
||||
$formVueData['formValidatorRules'][$field['name']][] = "buildValidatorData({ name: '$item', title: t('$webTranslate{$field['name']}')$message })";
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($formVueData['formValidatorRules'] ?? []) {
|
||||
$formVueData['imports'][] = "import { buildValidatorData } from '/@/utils/validate'";
|
||||
}
|
||||
$formVueData['importExpand'] = self::buildImportExpand($formVueData['imports'] ?? []);
|
||||
$formVueData['formItemRules'] = self::buildFormValidatorRules($formVueData['formValidatorRules'] ?? []);
|
||||
$formVueContent = self::assembleStub('html/form', $formVueData);
|
||||
self::writeFile(root_path() . $webViewsDir['views'] . '/' . 'popupForm.vue', $formVueContent);
|
||||
}
|
||||
|
||||
public static function buildImportExpand(array $imports): string
|
||||
{
|
||||
$importExpand = '';
|
||||
foreach ($imports as $import) {
|
||||
$importExpand .= "\n$import";
|
||||
}
|
||||
return $importExpand;
|
||||
}
|
||||
|
||||
public static function buildFormValidatorRules(array $formValidatorRules): string
|
||||
{
|
||||
$rulesHtml = "";
|
||||
foreach ($formValidatorRules as $key => $formItemRule) {
|
||||
$rulesArrHtml = '';
|
||||
foreach ($formItemRule as $item) {
|
||||
$rulesArrHtml .= $item . ', ';
|
||||
}
|
||||
$rulesHtml .= self::tab() . $key . ': [' . rtrim($rulesArrHtml, ', ') . "],\n";
|
||||
}
|
||||
return $rulesHtml ? "\n" . $rulesHtml : '';
|
||||
}
|
||||
|
||||
public static function writeIndexFile($indexVueData, $webViewsDir, $controllerFile): void
|
||||
{
|
||||
$indexVueData['optButtons'] = self::buildSimpleArray($indexVueData['optButtons'] ?? []);
|
||||
$indexVueData['defaultItems'] = self::getJsonFromArray($indexVueData['defaultItems'] ?? []);
|
||||
$indexVueData['tableColumn'] = self::buildTableColumn($indexVueData['tableColumn'] ?? []);
|
||||
$indexVueData['dblClickNotEditColumn'] = self::buildSimpleArray($indexVueData['dblClickNotEditColumn'] ?? ['undefined']);
|
||||
$controllerFile['path'][] = $controllerFile['originalLastName'];
|
||||
$indexVueData['controllerUrl'] = '\'/admin/' . ($controllerFile['path'] ? implode('.', $controllerFile['path']) : '') . '/\'';
|
||||
$indexVueData['componentName'] = ($webViewsDir['path'] ? implode('/', $webViewsDir['path']) . '/' : '') . $webViewsDir['originalLastName'];
|
||||
$indexVueContent = self::assembleStub('html/index', $indexVueData);
|
||||
self::writeFile(root_path() . $webViewsDir['views'] . '/' . 'index.vue', $indexVueContent);
|
||||
}
|
||||
|
||||
public static function buildTableColumn($tableColumnList): string
|
||||
{
|
||||
$columnJson = '';
|
||||
$emptyUnset = ['comSearchInputAttr', 'replaceValue', 'custom'];
|
||||
foreach ($tableColumnList as $column) {
|
||||
foreach ($emptyUnset as $unsetKey) {
|
||||
if (empty($column[$unsetKey])) unset($column[$unsetKey]);
|
||||
}
|
||||
$columnJson .= self::tab(3) . '{';
|
||||
foreach ($column as $key => $item) {
|
||||
$columnJson .= self::buildTableColumnKey($key, $item);
|
||||
}
|
||||
$columnJson = rtrim($columnJson, ',') . " },\n";
|
||||
}
|
||||
return rtrim($columnJson, "\n");
|
||||
}
|
||||
|
||||
public static function buildTableColumnKey($key, $item): string
|
||||
{
|
||||
$key = self::formatObjectKey($key);
|
||||
if (is_array($item)) {
|
||||
$itemJson = ' ' . $key . ': {';
|
||||
foreach ($item as $ik => $iItem) {
|
||||
$itemJson .= self::buildTableColumnKey($ik, $iItem);
|
||||
}
|
||||
$itemJson = rtrim($itemJson, ',') . ' },';
|
||||
} elseif ($item === 'false' || $item === 'true') {
|
||||
$itemJson = ' ' . $key . ': ' . $item . ',';
|
||||
} elseif (in_array($key, ['label', 'width', 'buttons'], true) || str_starts_with((string)$item, "t('") || str_starts_with((string)$item, 't("')) {
|
||||
$itemJson = ' ' . $key . ': ' . $item . ',';
|
||||
} else {
|
||||
$itemJson = ' ' . $key . ': \'' . $item . '\',';
|
||||
}
|
||||
return $itemJson;
|
||||
}
|
||||
|
||||
public static function formatObjectKey(string $keyName): string
|
||||
{
|
||||
if (preg_match("/^[a-zA-Z_][a-zA-Z0-9_]+$/", $keyName)) {
|
||||
return $keyName;
|
||||
}
|
||||
$quote = self::getQuote($keyName);
|
||||
return "$quote$keyName$quote";
|
||||
}
|
||||
|
||||
public static function getQuote(string $value): string
|
||||
{
|
||||
return stripos($value, "'") === false ? "'" : '"';
|
||||
}
|
||||
|
||||
public static function buildFormatSimpleArray($arr, int $tab = 2): string
|
||||
{
|
||||
if (!$arr) return '[]';
|
||||
$str = '[' . PHP_EOL;
|
||||
foreach ($arr as $item) {
|
||||
if ($item == 'undefined' || $item == 'false' || is_numeric($item)) {
|
||||
$str .= self::tab($tab) . $item . ',' . PHP_EOL;
|
||||
} else {
|
||||
$quote = self::getQuote((string)$item);
|
||||
$str .= self::tab($tab) . "$quote$item$quote," . PHP_EOL;
|
||||
}
|
||||
}
|
||||
return $str . self::tab($tab - 1) . ']';
|
||||
}
|
||||
|
||||
public static function buildSimpleArray($arr): string
|
||||
{
|
||||
if (!$arr) return '[]';
|
||||
$str = '';
|
||||
foreach ($arr as $item) {
|
||||
if ($item == 'undefined' || $item == 'false' || is_numeric($item)) {
|
||||
$str .= $item . ', ';
|
||||
} else {
|
||||
$quote = self::getQuote((string)$item);
|
||||
$str .= "$quote$item$quote, ";
|
||||
}
|
||||
}
|
||||
return '[' . rtrim($str, ", ") . ']';
|
||||
}
|
||||
|
||||
public static function buildDefaultOrder(string $field, string $type): string
|
||||
{
|
||||
if ($field && $type) {
|
||||
$defaultOrderStub = self::getJsonFromArray(['prop' => $field, 'order' => $type]);
|
||||
if ($defaultOrderStub) {
|
||||
return "\n" . self::tab(2) . "defaultOrder: " . $defaultOrderStub . ',';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function getJsonFromArray($arr)
|
||||
{
|
||||
if (is_array($arr)) {
|
||||
$jsonStr = '';
|
||||
foreach ($arr as $key => $item) {
|
||||
$keyStr = ' ' . self::formatObjectKey($key) . ': ';
|
||||
if (is_array($item)) {
|
||||
$jsonStr .= $keyStr . self::getJsonFromArray($item) . ',';
|
||||
} elseif ($item === 'false' || $item === 'true') {
|
||||
$jsonStr .= $keyStr . ($item === 'false' ? 'false' : 'true') . ',';
|
||||
} elseif ($item === null) {
|
||||
$jsonStr .= $keyStr . 'null,';
|
||||
} elseif (str_starts_with((string)$item, "t('") || str_starts_with((string)$item, 't("') || $item == '[]' || in_array(gettype($item), ['integer', 'double'])) {
|
||||
$jsonStr .= $keyStr . $item . ',';
|
||||
} elseif (isset($item[0]) && $item[0] == '[' && str_ends_with((string)$item, ']')) {
|
||||
$jsonStr .= $keyStr . $item . ',';
|
||||
} else {
|
||||
$quote = self::getQuote((string)$item);
|
||||
$jsonStr .= $keyStr . "$quote$item$quote,";
|
||||
}
|
||||
}
|
||||
return $jsonStr ? '{' . rtrim($jsonStr, ',') . ' }' : '{}';
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
}
|
||||
63
dafuweng-webman/app/admin/library/crud/stubs/html/form.stub
Normal file
63
dafuweng-webman/app/admin/library/crud/stubs/html/form.stub
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<!-- 对话框表单 -->
|
||||
<!-- 建议使用 Prettier 格式化代码 -->
|
||||
<!-- el-form 内可以混用 el-form-item、FormItem、ba-input 等输入组件 -->
|
||||
<el-dialog
|
||||
class="ba-operate-dialog"
|
||||
:close-on-click-modal="false"
|
||||
:model-value="['Add', 'Edit'].includes(baTable.form.operate!)"
|
||||
@close="baTable.toggleForm"{%bigDialog%}
|
||||
>
|
||||
<template #header>
|
||||
<div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
|
||||
{{ baTable.form.operate ? t(baTable.form.operate) : '' }}
|
||||
</div>
|
||||
</template>
|
||||
<el-scrollbar v-loading="baTable.form.loading" class="ba-table-form-scrollbar">
|
||||
<div
|
||||
class="ba-operate-form"
|
||||
:class="'ba-' + baTable.form.operate + '-form'"
|
||||
:style="config.layout.shrink ? '':'width: calc(100% - ' + baTable.form.labelWidth! / 2 + 'px)'"
|
||||
>
|
||||
<el-form
|
||||
v-if="!baTable.form.loading"
|
||||
ref="formRef"
|
||||
@submit.prevent=""
|
||||
@keyup.enter="baTable.onSubmit(formRef)"
|
||||
:model="baTable.form.items"
|
||||
:label-position="config.layout.shrink ? 'top' : 'right'"
|
||||
:label-width="baTable.form.labelWidth + 'px'"
|
||||
:rules="rules"
|
||||
>{%formFields%}
|
||||
</el-form>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<div :style="'width: calc(100% - ' + baTable.form.labelWidth! / 1.8 + 'px)'">
|
||||
<el-button @click="baTable.toggleForm()">{{ t('Cancel') }}</el-button>
|
||||
<el-button v-blur :loading="baTable.form.submitLoading" @click="baTable.onSubmit(formRef)" type="primary">
|
||||
{{ baTable.form.operateIds && baTable.form.operateIds.length > 1 ? t('Save and edit next item') : t('Save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormItemRule } from 'element-plus'
|
||||
import { inject, reactive, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import FormItem from '/@/components/formItem/index.vue'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
import type baTableClass from '/@/utils/baTable'{%importExpand%}
|
||||
|
||||
const config = useConfig()
|
||||
const formRef = useTemplateRef('formRef')
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({{%formItemRules%}})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
69
dafuweng-webman/app/admin/library/crud/stubs/html/index.stub
Normal file
69
dafuweng-webman/app/admin/library/crud/stubs/html/index.stub
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box">
|
||||
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
|
||||
|
||||
<!-- 表格顶部菜单 -->
|
||||
<!-- 自定义按钮请使用插槽,甚至公共搜索也可以使用具名插槽渲染,参见文档 -->
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('{%webTranslate%}quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<!-- 表格 -->
|
||||
<!-- 表格列有多种自定义渲染方式,比如自定义组件、具名插槽等,参见文档 -->
|
||||
<!-- 要使用 el-table 组件原有的属性,直接加在 Table 标签上即可 -->
|
||||
<Table ref="tableRef"></Table>
|
||||
|
||||
<!-- 表单 -->
|
||||
<PopupForm />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PopupForm from './popupForm.vue'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import { defaultOptButtons } from '/@/components/table'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: '{%componentName%}',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons({%optButtons%})
|
||||
|
||||
/**
|
||||
* baTable 内包含了表格的所有数据且数据具备响应性,然后通过 provide 注入给了后代组件
|
||||
*/
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi({%controllerUrl%}),
|
||||
{
|
||||
pk: '{%tablePk%}',
|
||||
column: [
|
||||
{%tableColumn%}
|
||||
],
|
||||
dblClickNotEditColumn: {%dblClickNotEditColumn%},{%defaultOrder%}
|
||||
},
|
||||
{
|
||||
defaultItems: {%defaultItems%},
|
||||
}
|
||||
)
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
baTable.table.ref = tableRef.value
|
||||
baTable.mount()
|
||||
baTable.getData()?.then(() => {
|
||||
baTable.initSort()
|
||||
baTable.dragSort()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace {%namespace%};
|
||||
{%use%}
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* {%tableComment%}
|
||||
*/
|
||||
class {%className%} extends Backend
|
||||
{
|
||||
/**
|
||||
* {%modelName%}模型对象
|
||||
* @var object
|
||||
* @phpstan-var \{%modelNamespace%}\{%modelName%}
|
||||
*/
|
||||
protected object $model;
|
||||
{%attr%}{%initialize%}
|
||||
{%methods%}
|
||||
|
||||
/**
|
||||
* 若需重写查看、编辑、删除等方法,请复制 @see \app\admin\library\traits\Backend 中对应的方法至此进行重写
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
// 如果是 select 则转发到 select 方法,若未重写该方法,其实还是继续执行 index
|
||||
if ($this->request->param('select')) {
|
||||
$this->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. withJoin 不可使用 alias 方法设置表别名,别名将自动使用关联模型名称(小写下划线命名规则)
|
||||
* 2. 以下的别名设置了主表别名,同时便于拼接查询参数等
|
||||
* 3. paginate 数据集可使用链式操作 each(function($item, $key) {}) 遍历处理
|
||||
*/
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
{%relationVisibleFields%}
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
$this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new \{%modelNamespace%}\{%modelName%}();{%filterRule%}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
protected static function onAfterInsert($model): void
|
||||
{
|
||||
if (is_null($model->{%field%})) {
|
||||
$pk = $model->getPk();
|
||||
if (strlen($model[$pk]) >= 19) {
|
||||
$model->where($pk, $model[$pk])->update(['{%field%}' => $model->count()]);
|
||||
} else {
|
||||
$model->where($pk, $model[$pk])->update(['{%field%}' => $model[$pk]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
protected static function onBeforeInsert($model): void
|
||||
{
|
||||
{%setSnowFlakeIdCode%}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
public function {%relationMethod%}(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->{%relationMode%}({%relationClassName%}, '{%relationForeignKey%}', '{%relationPrimaryKey%}');
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
public function get{%field%}Attr($value, $row): string
|
||||
{
|
||||
if ($row['{%originalFieldName%}'] === '' || $row['{%originalFieldName%}'] === null) return '';
|
||||
$cityNames = \think\facade\Db::name('area')->whereIn('id', $row['{%originalFieldName%}'])->column('name');
|
||||
return $cityNames ? implode(',', $cityNames) : '';
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
public function get{%field%}Attr($value): ?float
|
||||
{
|
||||
return is_null($value) ? null : (float)$value;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
public function get{%field%}Attr($value): string
|
||||
{
|
||||
return !$value ? '' : htmlspecialchars_decode($value);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
public function get{%field%}Attr($value): array
|
||||
{
|
||||
return !$value ? [] : json_decode($value, true);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
public function get{%field%}Attr($value, $row): array
|
||||
{
|
||||
return [
|
||||
'{%labelFieldName%}' => {%className%}::whereIn('{%primaryKey%}', $row['{%foreignKey%}'])->column('{%labelFieldName%}'),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
public function get{%field%}Attr($value): string
|
||||
{
|
||||
return (string)$value;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
public function get{%field%}Attr($value): array
|
||||
{
|
||||
if ($value === '' || $value === null) return [];
|
||||
if (!is_array($value)) {
|
||||
return explode(',', $value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
$pk = $model->getPk();
|
||||
$model->$pk = \app\common\library\SnowFlake::generateParticle();
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace {%namespace%};
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* {%className%}
|
||||
*/
|
||||
class {%className%} extends Model
|
||||
{{%connection%}{%pk%}
|
||||
// 表名
|
||||
protected $name = '{%name%}';
|
||||
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = {%autoWriteTimestamp%};{%createTime%}{%updateTime%}
|
||||
{%append%}{%fieldType%}{%beforeInsert%}{%afterInsert%}{%methods%}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
public function set{%field%}Attr($value): string
|
||||
{
|
||||
return is_array($value) ? implode(',', $value) : $value;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
public function set{%field%}Attr($value): ?string
|
||||
{
|
||||
return $value ? date('H:i:s', strtotime($value)) : $value;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace {%namespace%};
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class {%className%} extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
/**
|
||||
* 验证规则
|
||||
*/
|
||||
protected $rule = [
|
||||
];
|
||||
|
||||
/**
|
||||
* 提示消息
|
||||
*/
|
||||
protected $message = [
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证场景
|
||||
*/
|
||||
protected $scene = [
|
||||
'add' => [],
|
||||
'edit' => [],
|
||||
];
|
||||
|
||||
}
|
||||
900
dafuweng-webman/app/admin/library/module/Manage.php
Normal file
900
dafuweng-webman/app/admin/library/module/Manage.php
Normal file
@@ -0,0 +1,900 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\library\module;
|
||||
|
||||
use Throwable;
|
||||
use ba\Version;
|
||||
use ba\Depends;
|
||||
use ba\Exception;
|
||||
use ba\Filesystem;
|
||||
use FilesystemIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use Webman\Http\Request;
|
||||
|
||||
/**
|
||||
* 模块管理类(Webman 迁移版)
|
||||
*/
|
||||
class Manage
|
||||
{
|
||||
public const UNINSTALLED = 0;
|
||||
public const INSTALLED = 1;
|
||||
public const WAIT_INSTALL = 2;
|
||||
public const CONFLICT_PENDING = 3;
|
||||
public const DEPENDENT_WAIT_INSTALL = 4;
|
||||
public const DIRECTORY_OCCUPIED = 5;
|
||||
public const DISABLE = 6;
|
||||
|
||||
protected static ?Manage $instance = null;
|
||||
|
||||
protected string $installDir;
|
||||
|
||||
protected string $backupsDir;
|
||||
|
||||
protected string $uid;
|
||||
|
||||
protected string $modulesDir;
|
||||
|
||||
public static function instance(string $uid = ''): Manage
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new static($uid);
|
||||
}
|
||||
return self::$instance->setModuleUid($uid);
|
||||
}
|
||||
|
||||
public function __construct(string $uid)
|
||||
{
|
||||
$this->installDir = root_path() . 'modules' . DIRECTORY_SEPARATOR;
|
||||
$this->backupsDir = $this->installDir . 'backups' . DIRECTORY_SEPARATOR;
|
||||
if (!is_dir($this->installDir)) {
|
||||
mkdir($this->installDir, 0755, true);
|
||||
}
|
||||
if (!is_dir($this->backupsDir)) {
|
||||
mkdir($this->backupsDir, 0755, true);
|
||||
}
|
||||
|
||||
if ($uid) {
|
||||
$this->setModuleUid($uid);
|
||||
} else {
|
||||
$this->uid = '';
|
||||
$this->modulesDir = $this->installDir;
|
||||
}
|
||||
}
|
||||
|
||||
public function getInstallState(): int
|
||||
{
|
||||
if (!is_dir($this->modulesDir)) {
|
||||
return self::UNINSTALLED;
|
||||
}
|
||||
$info = $this->getInfo();
|
||||
if ($info && isset($info['state'])) {
|
||||
return $info['state'];
|
||||
}
|
||||
return Filesystem::dirIsEmpty($this->modulesDir) ? self::UNINSTALLED : self::DIRECTORY_OCCUPIED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Webman Request 上传安装(适配 Multipart 上传)
|
||||
* @return array 模块基本信息
|
||||
* @throws Throwable
|
||||
*/
|
||||
public static function uploadFromRequest(Request $request): array
|
||||
{
|
||||
$file = $request->file('file');
|
||||
if (!$file) {
|
||||
throw new Exception('Parameter error');
|
||||
}
|
||||
$token = $request->post('token', $request->get('token', ''));
|
||||
if (!$token) {
|
||||
throw new Exception('Please login to the official website account first');
|
||||
}
|
||||
$uploadDir = root_path() . 'public' . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'upload' . DIRECTORY_SEPARATOR;
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
$saveName = 'temp' . DIRECTORY_SEPARATOR . date('YmdHis') . '_' . ($file->getUploadName() ?? 'module.zip');
|
||||
$savePath = $uploadDir . $saveName;
|
||||
$saveDir = dirname($savePath);
|
||||
if (!is_dir($saveDir)) {
|
||||
mkdir($saveDir, 0755, true);
|
||||
}
|
||||
$file->move($savePath);
|
||||
$relativePath = 'storage/upload/' . str_replace(DIRECTORY_SEPARATOR, '/', $saveName);
|
||||
try {
|
||||
return self::instance('')->doUpload($token, $relativePath);
|
||||
} finally {
|
||||
if (is_file($savePath)) {
|
||||
@unlink($savePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载模块文件
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function download(): string
|
||||
{
|
||||
$req = function_exists('request') ? request() : null;
|
||||
$token = $req ? ($req->post('token', $req->get('token', ''))) : '';
|
||||
$version = $req ? ($req->post('version', $req->get('version', ''))) : '';
|
||||
$orderId = $req ? ($req->post('orderId', $req->get('orderId', 0))) : 0;
|
||||
|
||||
if (!$orderId) {
|
||||
throw new Exception('Order not found');
|
||||
}
|
||||
|
||||
$zipFile = Server::download($this->uid, $this->installDir, [
|
||||
'version' => $version,
|
||||
'orderId' => $orderId,
|
||||
'nuxtVersion' => Server::getNuxtVersion(),
|
||||
'sysVersion' => config('buildadmin.version', ''),
|
||||
'installed' => Server::getInstalledIds($this->installDir),
|
||||
'ba-user-token' => $token,
|
||||
]);
|
||||
|
||||
Filesystem::delDir($this->modulesDir);
|
||||
Filesystem::unzip($zipFile);
|
||||
@unlink($zipFile);
|
||||
|
||||
$this->checkPackage();
|
||||
|
||||
$this->setInfo([
|
||||
'state' => self::WAIT_INSTALL,
|
||||
]);
|
||||
|
||||
return $zipFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传安装(token + 文件相对路径)
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function doUpload(string $token, string $file): array
|
||||
{
|
||||
$file = Filesystem::fsFit(root_path() . 'public' . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $file));
|
||||
if (!is_file($file)) {
|
||||
throw new Exception('Zip file not found');
|
||||
}
|
||||
|
||||
$copyTo = $this->installDir . 'uploadTemp' . date('YmdHis') . '.zip';
|
||||
copy($file, $copyTo);
|
||||
|
||||
$copyToDir = Filesystem::unzip($copyTo);
|
||||
$copyToDir .= DIRECTORY_SEPARATOR;
|
||||
|
||||
@unlink($file);
|
||||
@unlink($copyTo);
|
||||
|
||||
$info = Server::getIni($copyToDir);
|
||||
if (empty($info['uid'])) {
|
||||
Filesystem::delDir($copyToDir);
|
||||
throw new Exception('Basic configuration of the Module is incomplete');
|
||||
}
|
||||
|
||||
$this->setModuleUid($info['uid']);
|
||||
|
||||
$upgrade = false;
|
||||
if (is_dir($this->modulesDir)) {
|
||||
$oldInfo = $this->getInfo();
|
||||
if ($oldInfo && !empty($oldInfo['uid'])) {
|
||||
$versions = explode('.', $oldInfo['version'] ?? '0.0.0');
|
||||
if (isset($versions[2])) {
|
||||
$versions[2]++;
|
||||
}
|
||||
$nextVersion = implode('.', $versions);
|
||||
$upgrade = Version::compare($nextVersion, $info['version'] ?? '');
|
||||
if ($upgrade) {
|
||||
if (!in_array($oldInfo['state'], [self::UNINSTALLED, self::WAIT_INSTALL, self::DISABLE])) {
|
||||
Filesystem::delDir($copyToDir);
|
||||
throw new Exception('Please disable the module before updating');
|
||||
}
|
||||
} else {
|
||||
Filesystem::delDir($copyToDir);
|
||||
throw new Exception('Module already exists');
|
||||
}
|
||||
}
|
||||
|
||||
if (!Filesystem::dirIsEmpty($this->modulesDir) && !$upgrade) {
|
||||
Filesystem::delDir($copyToDir);
|
||||
throw new Exception('The directory required by the module is occupied');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Server::installPreCheck([
|
||||
'uid' => $info['uid'],
|
||||
'version' => $info['version'] ?? '',
|
||||
'sysVersion' => config('buildadmin.version', ''),
|
||||
'nuxtVersion' => Server::getNuxtVersion(),
|
||||
'moduleVersion' => $info['version'] ?? '',
|
||||
'ba-user-token' => $token,
|
||||
'installed' => Server::getInstalledIds($this->installDir),
|
||||
'server' => 1,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
Filesystem::delDir($copyToDir);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$newInfo = ['state' => self::WAIT_INSTALL];
|
||||
if ($upgrade) {
|
||||
$info['update'] = 1;
|
||||
Filesystem::delDir($this->modulesDir);
|
||||
}
|
||||
|
||||
rename($copyToDir, $this->modulesDir);
|
||||
|
||||
$this->checkPackage();
|
||||
|
||||
$this->setInfo($newInfo);
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装模块
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function install(bool $update): array
|
||||
{
|
||||
$state = $this->getInstallState();
|
||||
|
||||
if ($update) {
|
||||
if (!in_array($state, [self::UNINSTALLED, self::WAIT_INSTALL, self::DISABLE])) {
|
||||
throw new Exception('Please disable the module before updating');
|
||||
}
|
||||
if ($state == self::UNINSTALLED || $state != self::WAIT_INSTALL) {
|
||||
$this->download();
|
||||
}
|
||||
} else {
|
||||
if ($state == self::INSTALLED || $state == self::DIRECTORY_OCCUPIED || $state == self::DISABLE) {
|
||||
throw new Exception('Module already exists');
|
||||
}
|
||||
if ($state == self::UNINSTALLED) {
|
||||
$this->download();
|
||||
}
|
||||
}
|
||||
|
||||
Server::importSql($this->modulesDir);
|
||||
|
||||
$info = $this->getInfo();
|
||||
if ($update) {
|
||||
$info['update'] = 1;
|
||||
Server::execEvent($this->uid, 'update');
|
||||
}
|
||||
|
||||
$req = function_exists('request') ? request() : null;
|
||||
$extend = $req ? ($req->post('extend') ?? []) : [];
|
||||
if (!isset($extend['conflictHandle'])) {
|
||||
Server::execEvent($this->uid, 'install');
|
||||
}
|
||||
|
||||
$this->enable('install');
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function uninstall(): void
|
||||
{
|
||||
$info = $this->getInfo();
|
||||
if (($info['state'] ?? 0) != self::DISABLE) {
|
||||
throw new Exception('Please disable the module first', 0, [
|
||||
'uid' => $this->uid,
|
||||
]);
|
||||
}
|
||||
|
||||
Server::execEvent($this->uid, 'uninstall');
|
||||
|
||||
Filesystem::delDir($this->modulesDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改模块状态
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function changeState(bool $state): array
|
||||
{
|
||||
$info = $this->getInfo();
|
||||
if (!$state) {
|
||||
$canDisable = [
|
||||
self::INSTALLED,
|
||||
self::CONFLICT_PENDING,
|
||||
self::DEPENDENT_WAIT_INSTALL,
|
||||
];
|
||||
if (!in_array($info['state'] ?? 0, $canDisable)) {
|
||||
throw new Exception('The current state of the module cannot be set to disabled', 0, [
|
||||
'uid' => $this->uid,
|
||||
'state' => $info['state'] ?? 0,
|
||||
]);
|
||||
}
|
||||
return $this->disable();
|
||||
}
|
||||
|
||||
if (($info['state'] ?? 0) != self::DISABLE) {
|
||||
throw new Exception('The current state of the module cannot be set to enabled', 0, [
|
||||
'uid' => $this->uid,
|
||||
'state' => $info['state'] ?? 0,
|
||||
]);
|
||||
}
|
||||
$this->setInfo([
|
||||
'state' => self::WAIT_INSTALL,
|
||||
]);
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function enable(string $trigger): void
|
||||
{
|
||||
Server::installWebBootstrap($this->uid, $this->modulesDir);
|
||||
Server::createRuntime($this->modulesDir);
|
||||
$this->conflictHandle($trigger);
|
||||
Server::execEvent($this->uid, 'enable');
|
||||
$this->dependUpdateHandle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function disable(): array
|
||||
{
|
||||
$req = function_exists('request') ? request() : null;
|
||||
$update = $req ? filter_var($req->post('update', false), FILTER_VALIDATE_BOOLEAN) : false;
|
||||
$confirmConflict = $req ? filter_var($req->post('confirmConflict', false), FILTER_VALIDATE_BOOLEAN) : false;
|
||||
$dependConflictSolution = $req ? ($req->post('dependConflictSolution') ?? []) : [];
|
||||
|
||||
$info = $this->getInfo();
|
||||
$zipFile = $this->backupsDir . $this->uid . '-install.zip';
|
||||
$zipDir = false;
|
||||
if (is_file($zipFile)) {
|
||||
try {
|
||||
$zipDir = $this->backupsDir . $this->uid . '-install' . DIRECTORY_SEPARATOR;
|
||||
Filesystem::unzip($zipFile, $zipDir);
|
||||
} catch (Exception) {
|
||||
// skip
|
||||
}
|
||||
}
|
||||
|
||||
$conflictFile = Server::getFileList($this->modulesDir, true);
|
||||
$dependConflict = $this->disableDependCheck();
|
||||
if (($conflictFile || !self::isEmptyArray($dependConflict)) && !$confirmConflict) {
|
||||
$dependConflictTemp = [];
|
||||
foreach ($dependConflict as $env => $item) {
|
||||
foreach ($item as $depend => $v) {
|
||||
$dependConflictTemp[] = [
|
||||
'env' => $env,
|
||||
'depend' => $depend,
|
||||
'dependTitle' => $depend . ' ' . $v,
|
||||
'solution' => 'delete',
|
||||
];
|
||||
}
|
||||
}
|
||||
throw new Exception('Module file updated', -1, [
|
||||
'uid' => $this->uid,
|
||||
'conflictFile' => $conflictFile,
|
||||
'dependConflict' => $dependConflictTemp,
|
||||
]);
|
||||
}
|
||||
|
||||
Server::execEvent($this->uid, 'disable', ['update' => $update]);
|
||||
|
||||
$delNpmDepend = false;
|
||||
$delNuxtNpmDepend = false;
|
||||
$delComposerDepend = false;
|
||||
foreach ($dependConflictSolution as $env => $depends) {
|
||||
if (!$depends) continue;
|
||||
if ($env == 'require' || $env == 'require-dev') {
|
||||
$delComposerDepend = true;
|
||||
} elseif ($env == 'dependencies' || $env == 'devDependencies') {
|
||||
$delNpmDepend = true;
|
||||
} elseif ($env == 'nuxtDependencies' || $env == 'nuxtDevDependencies') {
|
||||
$delNuxtNpmDepend = true;
|
||||
}
|
||||
}
|
||||
|
||||
$dependJsonFiles = [
|
||||
'composer' => 'composer.json',
|
||||
'webPackage' => 'web' . DIRECTORY_SEPARATOR . 'package.json',
|
||||
'webNuxtPackage' => 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json',
|
||||
];
|
||||
$dependWaitInstall = [];
|
||||
if ($delComposerDepend) {
|
||||
$conflictFile[] = $dependJsonFiles['composer'];
|
||||
$dependWaitInstall[] = [
|
||||
'pm' => false,
|
||||
'command' => 'composer.update',
|
||||
'type' => 'composer_dependent_wait_install',
|
||||
];
|
||||
}
|
||||
if ($delNpmDepend) {
|
||||
$conflictFile[] = $dependJsonFiles['webPackage'];
|
||||
$dependWaitInstall[] = [
|
||||
'pm' => true,
|
||||
'command' => 'web-install',
|
||||
'type' => 'npm_dependent_wait_install',
|
||||
];
|
||||
}
|
||||
if ($delNuxtNpmDepend) {
|
||||
$conflictFile[] = $dependJsonFiles['webNuxtPackage'];
|
||||
$dependWaitInstall[] = [
|
||||
'pm' => true,
|
||||
'command' => 'nuxt-install',
|
||||
'type' => 'nuxt_npm_dependent_wait_install',
|
||||
];
|
||||
}
|
||||
if ($conflictFile) {
|
||||
$overwriteDir = Server::getOverwriteDir();
|
||||
foreach ($conflictFile as $key => $item) {
|
||||
$paths = explode(DIRECTORY_SEPARATOR, $item);
|
||||
if (in_array($paths[0], $overwriteDir) || in_array($item, $dependJsonFiles)) {
|
||||
$conflictFile[$key] = $item;
|
||||
} else {
|
||||
$conflictFile[$key] = Filesystem::fsFit(str_replace(root_path(), '', $this->modulesDir . $item));
|
||||
}
|
||||
if (!is_file(root_path() . $conflictFile[$key])) {
|
||||
unset($conflictFile[$key]);
|
||||
}
|
||||
}
|
||||
$backupsZip = $this->backupsDir . $this->uid . '-disable-' . date('YmdHis') . '.zip';
|
||||
Filesystem::zip($conflictFile, $backupsZip);
|
||||
}
|
||||
|
||||
$serverDepend = new Depends(root_path() . 'composer.json', 'composer');
|
||||
$webDep = new Depends(root_path() . 'web' . DIRECTORY_SEPARATOR . 'package.json');
|
||||
$webNuxtDep = new Depends(root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json');
|
||||
foreach ($dependConflictSolution as $env => $depends) {
|
||||
if (!$depends) continue;
|
||||
$dev = stripos($env, 'dev') !== false;
|
||||
if ($env == 'require' || $env == 'require-dev') {
|
||||
$serverDepend->removeDepends($depends, $dev);
|
||||
} elseif ($env == 'dependencies' || $env == 'devDependencies') {
|
||||
$webDep->removeDepends($depends, $dev);
|
||||
} elseif ($env == 'nuxtDependencies' || $env == 'nuxtDevDependencies') {
|
||||
$webNuxtDep->removeDepends($depends, $dev);
|
||||
}
|
||||
}
|
||||
|
||||
$composerConfig = Server::getConfig($this->modulesDir, 'composerConfig');
|
||||
if ($composerConfig) {
|
||||
$serverDepend->removeComposerConfig($composerConfig);
|
||||
}
|
||||
|
||||
$protectedFiles = Server::getConfig($this->modulesDir, 'protectedFiles');
|
||||
foreach ($protectedFiles as &$protectedFile) {
|
||||
$protectedFile = Filesystem::fsFit(root_path() . $protectedFile);
|
||||
}
|
||||
$moduleFile = Server::getFileList($this->modulesDir);
|
||||
|
||||
foreach ($moduleFile as &$file) {
|
||||
$moduleFilePath = Filesystem::fsFit($this->modulesDir . $file);
|
||||
$file = Filesystem::fsFit(root_path() . $file);
|
||||
if (!file_exists($file)) continue;
|
||||
if (!file_exists($moduleFilePath)) {
|
||||
if (!is_dir(dirname($moduleFilePath))) {
|
||||
mkdir(dirname($moduleFilePath), 0755, true);
|
||||
}
|
||||
copy($file, $moduleFilePath);
|
||||
}
|
||||
|
||||
if (in_array($file, $protectedFiles)) {
|
||||
continue;
|
||||
}
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
Filesystem::delEmptyDir(dirname($file));
|
||||
}
|
||||
|
||||
if ($zipDir) {
|
||||
$unrecoverableFiles = [
|
||||
Filesystem::fsFit(root_path() . 'composer.json'),
|
||||
Filesystem::fsFit(root_path() . 'web/package.json'),
|
||||
Filesystem::fsFit(root_path() . 'web-nuxt/package.json'),
|
||||
];
|
||||
foreach (
|
||||
new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($zipDir, FilesystemIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
) as $item
|
||||
) {
|
||||
$backupsFile = Filesystem::fsFit(root_path() . str_replace($zipDir, '', $item->getPathname()));
|
||||
|
||||
if (in_array($backupsFile, $moduleFile) && !in_array($backupsFile, $protectedFiles)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($item->isDir()) {
|
||||
if (!is_dir($backupsFile)) {
|
||||
mkdir($backupsFile, 0755, true);
|
||||
}
|
||||
} elseif (!in_array($backupsFile, $unrecoverableFiles)) {
|
||||
copy($item->getPathname(), $backupsFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($zipDir && is_dir($zipDir)) {
|
||||
Filesystem::delDir($zipDir);
|
||||
}
|
||||
|
||||
Server::uninstallWebBootstrap($this->uid);
|
||||
|
||||
$this->setInfo([
|
||||
'state' => self::DISABLE,
|
||||
]);
|
||||
|
||||
if ($update) {
|
||||
throw new Exception('update', -3, [
|
||||
'uid' => $this->uid,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!empty($dependWaitInstall)) {
|
||||
throw new Exception('dependent wait install', -2, [
|
||||
'uid' => $this->uid,
|
||||
'wait_install' => $dependWaitInstall,
|
||||
]);
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理依赖和文件冲突
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function conflictHandle(string $trigger): bool
|
||||
{
|
||||
$info = $this->getInfo();
|
||||
if (!in_array($info['state'] ?? 0, [self::WAIT_INSTALL, self::CONFLICT_PENDING])) {
|
||||
return false;
|
||||
}
|
||||
$fileConflict = Server::getFileList($this->modulesDir, true);
|
||||
$dependConflict = Server::dependConflictCheck($this->modulesDir);
|
||||
$installFiles = Server::getFileList($this->modulesDir);
|
||||
$depends = Server::getDepend($this->modulesDir);
|
||||
|
||||
$coverFiles = [];
|
||||
$discardFiles = [];
|
||||
$serverDep = new Depends(root_path() . 'composer.json', 'composer');
|
||||
$webDep = new Depends(root_path() . 'web' . DIRECTORY_SEPARATOR . 'package.json');
|
||||
$webNuxtDep = new Depends(root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json');
|
||||
|
||||
$req = function_exists('request') ? request() : null;
|
||||
$extend = $req ? ($req->post('extend') ?? []) : [];
|
||||
|
||||
if ($fileConflict || !self::isEmptyArray($dependConflict)) {
|
||||
if (!$extend) {
|
||||
$fileConflictTemp = [];
|
||||
foreach ($fileConflict as $key => $item) {
|
||||
$fileConflictTemp[$key] = [
|
||||
'newFile' => $this->uid . DIRECTORY_SEPARATOR . $item,
|
||||
'oldFile' => $item,
|
||||
'solution' => 'cover',
|
||||
];
|
||||
}
|
||||
$dependConflictTemp = [];
|
||||
foreach ($dependConflict as $env => $item) {
|
||||
$dev = stripos($env, 'dev') !== false;
|
||||
foreach ($item as $depend => $v) {
|
||||
$oldDepend = '';
|
||||
if (in_array($env, ['require', 'require-dev'])) {
|
||||
$oldDepend = $depend . ' ' . $serverDep->hasDepend($depend, $dev);
|
||||
} elseif (in_array($env, ['dependencies', 'devDependencies'])) {
|
||||
$oldDepend = $depend . ' ' . $webDep->hasDepend($depend, $dev);
|
||||
} elseif (in_array($env, ['nuxtDependencies', 'nuxtDevDependencies'])) {
|
||||
$oldDepend = $depend . ' ' . $webNuxtDep->hasDepend($depend, $dev);
|
||||
}
|
||||
$dependConflictTemp[] = [
|
||||
'env' => $env,
|
||||
'newDepend' => $depend . ' ' . $v,
|
||||
'oldDepend' => $oldDepend,
|
||||
'depend' => $depend,
|
||||
'solution' => 'cover',
|
||||
];
|
||||
}
|
||||
}
|
||||
$this->setInfo([
|
||||
'state' => self::CONFLICT_PENDING,
|
||||
]);
|
||||
throw new Exception('Module file conflicts', -1, [
|
||||
'fileConflict' => $fileConflictTemp,
|
||||
'dependConflict' => $dependConflictTemp,
|
||||
'uid' => $this->uid,
|
||||
'state' => self::CONFLICT_PENDING,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($fileConflict && isset($extend['fileConflict'])) {
|
||||
foreach ($installFiles as $ikey => $installFile) {
|
||||
if (isset($extend['fileConflict'][$installFile])) {
|
||||
if ($extend['fileConflict'][$installFile] == 'discard') {
|
||||
$discardFiles[] = $installFile;
|
||||
unset($installFiles[$ikey]);
|
||||
} else {
|
||||
$coverFiles[] = $installFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!self::isEmptyArray($dependConflict) && isset($extend['dependConflict'])) {
|
||||
foreach ($depends as $fKey => $fItem) {
|
||||
foreach ($fItem as $cKey => $cItem) {
|
||||
if (isset($extend['dependConflict'][$fKey][$cKey])) {
|
||||
if ($extend['dependConflict'][$fKey][$cKey] == 'discard') {
|
||||
unset($depends[$fKey][$cKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($depends) {
|
||||
foreach ($depends as $key => $item) {
|
||||
if (!$item) continue;
|
||||
if ($key == 'require' || $key == 'require-dev') {
|
||||
$coverFiles[] = 'composer.json';
|
||||
continue;
|
||||
}
|
||||
if ($key == 'dependencies' || $key == 'devDependencies') {
|
||||
$coverFiles[] = 'web' . DIRECTORY_SEPARATOR . 'package.json';
|
||||
}
|
||||
if ($key == 'nuxtDependencies' || $key == 'nuxtDevDependencies') {
|
||||
$coverFiles[] = 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($coverFiles) {
|
||||
$backupsZip = $trigger == 'install' ? $this->backupsDir . $this->uid . '-install.zip' : $this->backupsDir . $this->uid . '-cover-' . date('YmdHis') . '.zip';
|
||||
Filesystem::zip($coverFiles, $backupsZip);
|
||||
}
|
||||
|
||||
if ($depends) {
|
||||
$npm = false;
|
||||
$composer = false;
|
||||
$nuxtNpm = false;
|
||||
|
||||
$composerConfig = Server::getConfig($this->modulesDir, 'composerConfig');
|
||||
if ($composerConfig) {
|
||||
$serverDep->setComposerConfig($composerConfig);
|
||||
}
|
||||
|
||||
foreach ($depends as $key => $item) {
|
||||
if (!$item) continue;
|
||||
if ($key == 'require') {
|
||||
$composer = true;
|
||||
$serverDep->addDepends($item, false, true);
|
||||
} elseif ($key == 'require-dev') {
|
||||
$composer = true;
|
||||
$serverDep->addDepends($item, true, true);
|
||||
} elseif ($key == 'dependencies') {
|
||||
$npm = true;
|
||||
$webDep->addDepends($item, false, true);
|
||||
} elseif ($key == 'devDependencies') {
|
||||
$npm = true;
|
||||
$webDep->addDepends($item, true, true);
|
||||
} elseif ($key == 'nuxtDependencies') {
|
||||
$nuxtNpm = true;
|
||||
$webNuxtDep->addDepends($item, false, true);
|
||||
} elseif ($key == 'nuxtDevDependencies') {
|
||||
$nuxtNpm = true;
|
||||
$webNuxtDep->addDepends($item, true, true);
|
||||
}
|
||||
}
|
||||
if ($npm) {
|
||||
$info['npm_dependent_wait_install'] = 1;
|
||||
$info['state'] = self::DEPENDENT_WAIT_INSTALL;
|
||||
}
|
||||
if ($composer) {
|
||||
$info['composer_dependent_wait_install'] = 1;
|
||||
$info['state'] = self::DEPENDENT_WAIT_INSTALL;
|
||||
}
|
||||
if ($nuxtNpm) {
|
||||
$info['nuxt_npm_dependent_wait_install'] = 1;
|
||||
$info['state'] = self::DEPENDENT_WAIT_INSTALL;
|
||||
}
|
||||
$info = $info ?? $this->getInfo();
|
||||
if (($info['state'] ?? 0) != self::DEPENDENT_WAIT_INSTALL) {
|
||||
$this->setInfo(['state' => self::INSTALLED]);
|
||||
} else {
|
||||
$this->setInfo([], $info);
|
||||
}
|
||||
} else {
|
||||
$this->setInfo(['state' => self::INSTALLED]);
|
||||
}
|
||||
|
||||
$overwriteDir = Server::getOverwriteDir();
|
||||
foreach ($overwriteDir as $dirItem) {
|
||||
$baseDir = $this->modulesDir . $dirItem;
|
||||
$destDir = root_path() . $dirItem;
|
||||
if (!is_dir($baseDir)) continue;
|
||||
|
||||
foreach (
|
||||
new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($baseDir, FilesystemIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
) as $item
|
||||
) {
|
||||
$destDirItem = Filesystem::fsFit($destDir . DIRECTORY_SEPARATOR . str_replace($baseDir, '', $item->getPathname()));
|
||||
if ($item->isDir()) {
|
||||
Filesystem::mkdir($destDirItem);
|
||||
} elseif (!in_array(str_replace(root_path(), '', $destDirItem), $discardFiles)) {
|
||||
Filesystem::mkdir(dirname($destDirItem));
|
||||
copy($item->getPathname(), $destDirItem);
|
||||
}
|
||||
}
|
||||
if (config('buildadmin.module_pure_install', false)) {
|
||||
Filesystem::delDir($baseDir);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 依赖升级处理
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function dependUpdateHandle(): void
|
||||
{
|
||||
$info = $this->getInfo();
|
||||
if (($info['state'] ?? 0) == self::DEPENDENT_WAIT_INSTALL) {
|
||||
$waitInstall = [];
|
||||
if (isset($info['composer_dependent_wait_install'])) {
|
||||
$waitInstall[] = 'composer_dependent_wait_install';
|
||||
}
|
||||
if (isset($info['npm_dependent_wait_install'])) {
|
||||
$waitInstall[] = 'npm_dependent_wait_install';
|
||||
}
|
||||
if (isset($info['nuxt_npm_dependent_wait_install'])) {
|
||||
$waitInstall[] = 'nuxt_npm_dependent_wait_install';
|
||||
}
|
||||
if ($waitInstall) {
|
||||
throw new Exception('dependent wait install', -2, [
|
||||
'uid' => $this->uid,
|
||||
'state' => self::DEPENDENT_WAIT_INSTALL,
|
||||
'wait_install' => $waitInstall,
|
||||
]);
|
||||
} else {
|
||||
$this->setInfo(['state' => self::INSTALLED]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 依赖安装完成标记
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function dependentInstallComplete(string $type): void
|
||||
{
|
||||
$info = $this->getInfo();
|
||||
if (($info['state'] ?? 0) == self::DEPENDENT_WAIT_INSTALL) {
|
||||
if ($type == 'npm') {
|
||||
unset($info['npm_dependent_wait_install']);
|
||||
}
|
||||
if ($type == 'nuxt_npm') {
|
||||
unset($info['nuxt_npm_dependent_wait_install']);
|
||||
}
|
||||
if ($type == 'composer') {
|
||||
unset($info['composer_dependent_wait_install']);
|
||||
}
|
||||
if ($type == 'all') {
|
||||
unset($info['npm_dependent_wait_install'], $info['composer_dependent_wait_install'], $info['nuxt_npm_dependent_wait_install']);
|
||||
}
|
||||
if (!isset($info['npm_dependent_wait_install']) && !isset($info['composer_dependent_wait_install']) && !isset($info['nuxt_npm_dependent_wait_install'])) {
|
||||
$info['state'] = self::INSTALLED;
|
||||
}
|
||||
$this->setInfo([], $info);
|
||||
}
|
||||
}
|
||||
|
||||
public function disableDependCheck(): array
|
||||
{
|
||||
$depend = Server::getDepend($this->modulesDir);
|
||||
if (!$depend) return [];
|
||||
|
||||
$serverDep = new Depends(root_path() . 'composer.json', 'composer');
|
||||
$webDep = new Depends(root_path() . 'web' . DIRECTORY_SEPARATOR . 'package.json');
|
||||
$webNuxtDep = new Depends(root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json');
|
||||
|
||||
foreach ($depend as $key => $depends) {
|
||||
$dev = stripos($key, 'dev') !== false;
|
||||
if ($key == 'require' || $key == 'require-dev') {
|
||||
foreach ($depends as $dependKey => $dependItem) {
|
||||
if (!$serverDep->hasDepend($dependKey, $dev)) {
|
||||
unset($depends[$dependKey]);
|
||||
}
|
||||
}
|
||||
$depend[$key] = $depends;
|
||||
} elseif ($key == 'dependencies' || $key == 'devDependencies') {
|
||||
foreach ($depends as $dependKey => $dependItem) {
|
||||
if (!$webDep->hasDepend($dependKey, $dev)) {
|
||||
unset($depends[$dependKey]);
|
||||
}
|
||||
}
|
||||
$depend[$key] = $depends;
|
||||
} elseif ($key == 'nuxtDependencies' || $key == 'nuxtDevDependencies') {
|
||||
foreach ($depends as $dependKey => $dependItem) {
|
||||
if (!$webNuxtDep->hasDepend($dependKey, $dev)) {
|
||||
unset($depends[$dependKey]);
|
||||
}
|
||||
}
|
||||
$depend[$key] = $depends;
|
||||
}
|
||||
}
|
||||
return $depend;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查包是否完整
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function checkPackage(): bool
|
||||
{
|
||||
if (!is_dir($this->modulesDir)) {
|
||||
throw new Exception('Module package file does not exist');
|
||||
}
|
||||
$info = $this->getInfo();
|
||||
$infoKeys = ['uid', 'title', 'intro', 'author', 'version', 'state'];
|
||||
foreach ($infoKeys as $value) {
|
||||
if (!array_key_exists($value, $info)) {
|
||||
Filesystem::delDir($this->modulesDir);
|
||||
throw new Exception('Basic configuration of the Module is incomplete');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getInfo(): array
|
||||
{
|
||||
return Server::getIni($this->modulesDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function setInfo(array $kv = [], array $arr = []): bool
|
||||
{
|
||||
if ($kv) {
|
||||
$info = $this->getInfo();
|
||||
foreach ($kv as $k => $v) {
|
||||
$info[$k] = $v;
|
||||
}
|
||||
return Server::setIni($this->modulesDir, $info);
|
||||
}
|
||||
if ($arr) {
|
||||
return Server::setIni($this->modulesDir, $arr);
|
||||
}
|
||||
throw new Exception('Parameter error');
|
||||
}
|
||||
|
||||
public static function isEmptyArray($arr): bool
|
||||
{
|
||||
foreach ($arr as $item) {
|
||||
if (is_array($item)) {
|
||||
if (!self::isEmptyArray($item)) return false;
|
||||
} elseif ($item) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setModuleUid(string $uid): static
|
||||
{
|
||||
$this->uid = $uid;
|
||||
$this->modulesDir = $this->installDir . $uid . DIRECTORY_SEPARATOR;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
551
dafuweng-webman/app/admin/library/module/Server.php
Normal file
551
dafuweng-webman/app/admin/library/module/Server.php
Normal file
@@ -0,0 +1,551 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\library\module;
|
||||
|
||||
use Throwable;
|
||||
use ba\Depends;
|
||||
use ba\Exception;
|
||||
use ba\Filesystem;
|
||||
use support\think\Db;
|
||||
use FilesystemIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use think\db\exception\PDOException;
|
||||
use app\admin\library\crud\Helper;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
|
||||
/**
|
||||
* 模块服务类(Webman 迁移版)
|
||||
*/
|
||||
class Server
|
||||
{
|
||||
private static string $apiBaseUrl = '/api/v7.store/';
|
||||
|
||||
/**
|
||||
* 下载
|
||||
* @throws Throwable
|
||||
*/
|
||||
public static function download(string $uid, string $dir, array $extend = []): string
|
||||
{
|
||||
$tmpFile = $dir . $uid . ".zip";
|
||||
try {
|
||||
$client = get_ba_client();
|
||||
$response = $client->get(self::$apiBaseUrl . 'download', ['query' => array_merge(['uid' => $uid, 'server' => 1], $extend)]);
|
||||
$body = $response->getBody();
|
||||
$content = $body->getContents();
|
||||
if ($content == '' || stripos($content, '<title>系统发生错误</title>') !== false) {
|
||||
throw new Exception('package download failed', 0);
|
||||
}
|
||||
if (str_starts_with($content, '{')) {
|
||||
$json = (array)json_decode($content, true);
|
||||
throw new Exception($json['msg'], $json['code'], $json['data'] ?? []);
|
||||
}
|
||||
} catch (TransferException $e) {
|
||||
throw new Exception('package download failed', 0, ['msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if ($write = fopen($tmpFile, 'w')) {
|
||||
fwrite($write, $content);
|
||||
fclose($write);
|
||||
return $tmpFile;
|
||||
}
|
||||
throw new Exception("No permission to write temporary files");
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装预检
|
||||
* @throws Throwable
|
||||
*/
|
||||
public static function installPreCheck(array $query = []): bool
|
||||
{
|
||||
try {
|
||||
$client = get_ba_client();
|
||||
$response = $client->get(self::$apiBaseUrl . 'preCheck', ['query' => $query]);
|
||||
$body = $response->getBody();
|
||||
$statusCode = $response->getStatusCode();
|
||||
$content = $body->getContents();
|
||||
if ($content == '' || stripos($content, '<title>系统发生错误</title>') !== false || $statusCode != 200) {
|
||||
return true;
|
||||
}
|
||||
if (str_starts_with($content, '{')) {
|
||||
$json = json_decode($content, true);
|
||||
if ($json && $json['code'] == 0) {
|
||||
throw new Exception($json['msg'], $json['code'], $json['data'] ?? []);
|
||||
}
|
||||
}
|
||||
} catch (TransferException $e) {
|
||||
throw new Exception('package check failed', 0, ['msg' => $e->getMessage()]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getConfig(string $dir, $key = ''): array
|
||||
{
|
||||
$configFile = $dir . 'config.json';
|
||||
if (!is_dir($dir) || !is_file($configFile)) {
|
||||
return [];
|
||||
}
|
||||
$configContent = @file_get_contents($configFile);
|
||||
$configContent = json_decode($configContent, true);
|
||||
if (!$configContent) {
|
||||
return [];
|
||||
}
|
||||
if ($key) {
|
||||
return $configContent[$key] ?? [];
|
||||
}
|
||||
return $configContent;
|
||||
}
|
||||
|
||||
public static function getDepend(string $dir, string $key = ''): array
|
||||
{
|
||||
if ($key) {
|
||||
return self::getConfig($dir, $key);
|
||||
}
|
||||
$configContent = self::getConfig($dir);
|
||||
$dependKey = ['require', 'require-dev', 'dependencies', 'devDependencies', 'nuxtDependencies', 'nuxtDevDependencies'];
|
||||
$dependArray = [];
|
||||
foreach ($dependKey as $item) {
|
||||
if (array_key_exists($item, $configContent) && $configContent[$item]) {
|
||||
$dependArray[$item] = $configContent[$item];
|
||||
}
|
||||
}
|
||||
return $dependArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 依赖冲突检查
|
||||
* @throws Throwable
|
||||
*/
|
||||
public static function dependConflictCheck(string $dir): array
|
||||
{
|
||||
$depend = self::getDepend($dir);
|
||||
$serverDep = new Depends(root_path() . 'composer.json', 'composer');
|
||||
$webDep = new Depends(root_path() . 'web' . DIRECTORY_SEPARATOR . 'package.json');
|
||||
$webNuxtDep = new Depends(root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json');
|
||||
$sysDepend = [
|
||||
'require' => $serverDep->getDepends(),
|
||||
'require-dev' => $serverDep->getDepends(true),
|
||||
'dependencies' => $webDep->getDepends(),
|
||||
'devDependencies' => $webDep->getDepends(true),
|
||||
'nuxtDependencies' => $webNuxtDep->getDepends(),
|
||||
'nuxtDevDependencies' => $webNuxtDep->getDepends(true),
|
||||
];
|
||||
|
||||
$conflict = [];
|
||||
foreach ($depend as $key => $item) {
|
||||
$conflict[$key] = array_uintersect_assoc($item, $sysDepend[$key] ?? [], function ($a, $b) {
|
||||
return $a == $b ? -1 : 0;
|
||||
});
|
||||
}
|
||||
return $conflict;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块[冲突]文件列表
|
||||
*/
|
||||
public static function getFileList(string $dir, bool $onlyConflict = false): array
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fileList = [];
|
||||
$overwriteDir = self::getOverwriteDir();
|
||||
$moduleFileList = self::getRuntime($dir, 'files');
|
||||
|
||||
if ($moduleFileList) {
|
||||
if ($onlyConflict) {
|
||||
$excludeFile = ['info.ini'];
|
||||
foreach ($moduleFileList as $file) {
|
||||
$path = Filesystem::fsFit(str_replace($dir, '', $file['path']));
|
||||
$paths = explode(DIRECTORY_SEPARATOR, $path);
|
||||
$overwriteFile = in_array($paths[0], $overwriteDir) ? root_path() . $path : $dir . $path;
|
||||
if (is_file($overwriteFile) && !in_array($path, $excludeFile) && (filesize($overwriteFile) != $file['size'] || md5_file($overwriteFile) != $file['md5'])) {
|
||||
$fileList[] = $path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($overwriteDir as $item) {
|
||||
$baseDir = $dir . $item;
|
||||
foreach ($moduleFileList as $file) {
|
||||
if (!str_starts_with($file['path'], $baseDir)) continue;
|
||||
$fileList[] = Filesystem::fsFit(str_replace($dir, '', $file['path']));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $fileList;
|
||||
}
|
||||
|
||||
foreach ($overwriteDir as $item) {
|
||||
$baseDir = $dir . $item;
|
||||
if (!is_dir($baseDir)) {
|
||||
continue;
|
||||
}
|
||||
$files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($baseDir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
foreach ($files as $file) {
|
||||
if ($file->isFile()) {
|
||||
$filePath = $file->getPathName();
|
||||
$path = str_replace($dir, '', $filePath);
|
||||
$path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
|
||||
|
||||
if ($onlyConflict) {
|
||||
$overwriteFile = root_path() . $path;
|
||||
if (is_file($overwriteFile) && (filesize($overwriteFile) != filesize($filePath) || md5_file($overwriteFile) != md5_file($filePath))) {
|
||||
$fileList[] = $path;
|
||||
}
|
||||
} else {
|
||||
$fileList[] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $fileList;
|
||||
}
|
||||
|
||||
public static function getOverwriteDir(): array
|
||||
{
|
||||
return [
|
||||
'app',
|
||||
'config',
|
||||
'database',
|
||||
'extend',
|
||||
'modules',
|
||||
'public',
|
||||
'vendor',
|
||||
'web',
|
||||
'web-nuxt',
|
||||
];
|
||||
}
|
||||
|
||||
public static function importSql(string $dir): bool
|
||||
{
|
||||
$sqlFile = $dir . 'install.sql';
|
||||
$tempLine = '';
|
||||
$prefix = config('thinkorm.connections.mysql.prefix', config('database.connections.mysql.prefix', ''));
|
||||
if (is_file($sqlFile)) {
|
||||
$lines = file($sqlFile);
|
||||
foreach ($lines as $line) {
|
||||
if (str_starts_with($line, '--') || $line == '' || str_starts_with($line, '/*')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tempLine .= $line;
|
||||
if (str_ends_with(trim($line), ';')) {
|
||||
$tempLine = str_ireplace('__PREFIX__', $prefix, $tempLine);
|
||||
$tempLine = str_ireplace('INSERT INTO ', 'INSERT IGNORE INTO ', $tempLine);
|
||||
try {
|
||||
Db::execute($tempLine);
|
||||
} catch (PDOException) {
|
||||
// ignore
|
||||
}
|
||||
$tempLine = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function installedList(string $dir): array
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return [];
|
||||
}
|
||||
$installedDir = scandir($dir);
|
||||
$installedList = [];
|
||||
foreach ($installedDir as $item) {
|
||||
if ($item === '.' or $item === '..' || is_file($dir . $item)) {
|
||||
continue;
|
||||
}
|
||||
$tempDir = $dir . $item . DIRECTORY_SEPARATOR;
|
||||
if (!is_dir($tempDir)) {
|
||||
continue;
|
||||
}
|
||||
$info = self::getIni($tempDir);
|
||||
if (!isset($info['uid'])) {
|
||||
continue;
|
||||
}
|
||||
$installedList[] = $info;
|
||||
}
|
||||
return $installedList;
|
||||
}
|
||||
|
||||
public static function getInstalledIds(string $dir): array
|
||||
{
|
||||
$installedIds = [];
|
||||
$installed = self::installedList($dir);
|
||||
foreach ($installed as $item) {
|
||||
$installedIds[] = $item['uid'];
|
||||
}
|
||||
return $installedIds;
|
||||
}
|
||||
|
||||
public static function getIni(string $dir): array
|
||||
{
|
||||
$infoFile = $dir . 'info.ini';
|
||||
$info = [];
|
||||
if (is_file($infoFile)) {
|
||||
$info = parse_ini_file($infoFile, true, INI_SCANNER_TYPED) ?: [];
|
||||
if (!$info) return [];
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public static function setIni(string $dir, array $arr): bool
|
||||
{
|
||||
$infoFile = $dir . 'info.ini';
|
||||
$ini = [];
|
||||
foreach ($arr as $key => $val) {
|
||||
if (is_array($val)) {
|
||||
$ini[] = "[$key]";
|
||||
foreach ($val as $ikey => $ival) {
|
||||
$ini[] = "$ikey = $ival";
|
||||
}
|
||||
} else {
|
||||
$ini[] = "$key = $val";
|
||||
}
|
||||
}
|
||||
if (!file_put_contents($infoFile, implode("\n", $ini) . "\n", LOCK_EX)) {
|
||||
throw new Exception("Configuration file has no write permission");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getClass(string $uid, string $type = 'event', ?string $class = null): string
|
||||
{
|
||||
$name = parse_name($uid);
|
||||
if (!is_null($class) && strpos($class, '.')) {
|
||||
$class = explode('.', $class);
|
||||
$class[count($class) - 1] = parse_name(end($class), 1);
|
||||
$class = implode('\\', $class);
|
||||
} else {
|
||||
$class = parse_name(is_null($class) ? $name : $class, 1);
|
||||
}
|
||||
$namespace = match ($type) {
|
||||
'controller' => '\\modules\\' . $name . '\\controller\\' . $class,
|
||||
default => '\\modules\\' . $name . '\\' . $class,
|
||||
};
|
||||
return class_exists($namespace) ? $namespace : '';
|
||||
}
|
||||
|
||||
public static function execEvent(string $uid, string $event, array $params = []): void
|
||||
{
|
||||
$eventClass = self::getClass($uid);
|
||||
if (class_exists($eventClass)) {
|
||||
$handle = new $eventClass();
|
||||
if (method_exists($eventClass, $event)) {
|
||||
$handle->$event($params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function analysisWebBootstrap(string $uid, string $dir): array
|
||||
{
|
||||
$bootstrapFile = $dir . 'webBootstrap.stub';
|
||||
if (!file_exists($bootstrapFile)) return [];
|
||||
$bootstrapContent = file_get_contents($bootstrapFile);
|
||||
$pregArr = [
|
||||
'mainTsImport' => '/#main.ts import code start#([\s\S]*?)#main.ts import code end#/i',
|
||||
'mainTsStart' => '/#main.ts start code start#([\s\S]*?)#main.ts start code end#/i',
|
||||
'appVueImport' => '/#App.vue import code start#([\s\S]*?)#App.vue import code end#/i',
|
||||
'appVueOnMounted' => '/#App.vue onMounted code start#([\s\S]*?)#App.vue onMounted code end#/i',
|
||||
'nuxtAppVueImport' => '/#web-nuxt\/app.vue import code start#([\s\S]*?)#web-nuxt\/app.vue import code end#/i',
|
||||
'nuxtAppVueStart' => '/#web-nuxt\/app.vue start code start#([\s\S]*?)#web-nuxt\/app.vue start code end#/i',
|
||||
];
|
||||
$codeStrArr = [];
|
||||
foreach ($pregArr as $key => $item) {
|
||||
preg_match($item, $bootstrapContent, $matches);
|
||||
if (isset($matches[1]) && $matches[1]) {
|
||||
$mainImportCodeArr = array_filter(preg_split('/\r\n|\r|\n/', $matches[1]));
|
||||
if ($mainImportCodeArr) {
|
||||
$codeStrArr[$key] = "\n";
|
||||
if (count($mainImportCodeArr) == 1) {
|
||||
foreach ($mainImportCodeArr as $codeItem) {
|
||||
$codeStrArr[$key] .= $codeItem . self::buildMarkStr('module-line-mark', $uid, $key);
|
||||
}
|
||||
} else {
|
||||
$codeStrArr[$key] .= self::buildMarkStr('module-multi-line-mark-start', $uid, $key);
|
||||
foreach ($mainImportCodeArr as $codeItem) {
|
||||
$codeStrArr[$key] .= $codeItem . "\n";
|
||||
}
|
||||
$codeStrArr[$key] .= self::buildMarkStr('module-multi-line-mark-end', $uid, $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($matches);
|
||||
}
|
||||
return $codeStrArr;
|
||||
}
|
||||
|
||||
public static function installWebBootstrap(string $uid, string $dir): void
|
||||
{
|
||||
$bootstrapCode = self::analysisWebBootstrap($uid, $dir);
|
||||
if (!$bootstrapCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
$webPath = root_path() . 'web' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR;
|
||||
$webNuxtPath = root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR;
|
||||
$filePaths = [
|
||||
'mainTsImport' => $webPath . 'main.ts',
|
||||
'mainTsStart' => $webPath . 'main.ts',
|
||||
'appVueImport' => $webPath . 'App.vue',
|
||||
'appVueOnMounted' => $webPath . 'App.vue',
|
||||
'nuxtAppVueImport' => $webNuxtPath . 'app.vue',
|
||||
'nuxtAppVueStart' => $webNuxtPath . 'app.vue',
|
||||
];
|
||||
|
||||
$marks = [
|
||||
'mainTsImport' => self::buildMarkStr('import-root-mark'),
|
||||
'mainTsStart' => self::buildMarkStr('start-root-mark'),
|
||||
'appVueImport' => self::buildMarkStr('import-root-mark'),
|
||||
'appVueOnMounted' => self::buildMarkStr('onMounted-root-mark'),
|
||||
'nuxtAppVueImport' => self::buildMarkStr('import-root-mark'),
|
||||
'nuxtAppVueStart' => self::buildMarkStr('start-root-mark'),
|
||||
];
|
||||
|
||||
foreach ($bootstrapCode as $key => $item) {
|
||||
if ($item && isset($marks[$key]) && isset($filePaths[$key]) && is_file($filePaths[$key])) {
|
||||
$content = file_get_contents($filePaths[$key]);
|
||||
$markPos = stripos($content, $marks[$key]);
|
||||
if ($markPos && strripos($content, self::buildMarkStr('module-line-mark', $uid, $key)) === false && strripos($content, self::buildMarkStr('module-multi-line-mark-start', $uid, $key)) === false) {
|
||||
$content = substr_replace($content, $item, $markPos + strlen($marks[$key]), 0);
|
||||
file_put_contents($filePaths[$key], $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function uninstallWebBootstrap(string $uid): void
|
||||
{
|
||||
$webPath = root_path() . 'web' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR;
|
||||
$webNuxtPath = root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR;
|
||||
$filePaths = [
|
||||
'mainTsImport' => $webPath . 'main.ts',
|
||||
'mainTsStart' => $webPath . 'main.ts',
|
||||
'appVueImport' => $webPath . 'App.vue',
|
||||
'appVueOnMounted' => $webPath . 'App.vue',
|
||||
'nuxtAppVueImport' => $webNuxtPath . 'app.vue',
|
||||
'nuxtAppVueStart' => $webNuxtPath . 'app.vue',
|
||||
];
|
||||
|
||||
$marksKey = [
|
||||
'mainTsImport',
|
||||
'mainTsStart',
|
||||
'appVueImport',
|
||||
'appVueOnMounted',
|
||||
'nuxtAppVueImport',
|
||||
'nuxtAppVueStart',
|
||||
];
|
||||
|
||||
foreach ($marksKey as $item) {
|
||||
if (!isset($filePaths[$item]) || !is_file($filePaths[$item])) {
|
||||
continue;
|
||||
}
|
||||
$content = file_get_contents($filePaths[$item]);
|
||||
$moduleLineMark = self::buildMarkStr('module-line-mark', $uid, $item);
|
||||
$moduleMultiLineMarkStart = self::buildMarkStr('module-multi-line-mark-start', $uid, $item);
|
||||
$moduleMultiLineMarkEnd = self::buildMarkStr('module-multi-line-mark-end', $uid, $item);
|
||||
|
||||
$moduleLineMarkPos = strripos($content, $moduleLineMark);
|
||||
if ($moduleLineMarkPos !== false) {
|
||||
$delStartTemp = explode($moduleLineMark, $content);
|
||||
$delStartPos = strripos(rtrim($delStartTemp[0], "\n"), "\n");
|
||||
$delEndPos = stripos($content, "\n", $moduleLineMarkPos);
|
||||
$content = substr_replace($content, '', $delStartPos, $delEndPos - $delStartPos);
|
||||
}
|
||||
|
||||
$moduleMultiLineMarkStartPos = stripos($content, $moduleMultiLineMarkStart);
|
||||
if ($moduleMultiLineMarkStartPos !== false) {
|
||||
$moduleMultiLineMarkStartPos--;
|
||||
$moduleMultiLineMarkEndPos = stripos($content, $moduleMultiLineMarkEnd);
|
||||
$delLang = ($moduleMultiLineMarkEndPos + strlen($moduleMultiLineMarkEnd)) - $moduleMultiLineMarkStartPos;
|
||||
$content = substr_replace($content, '', $moduleMultiLineMarkStartPos, $delLang);
|
||||
}
|
||||
|
||||
if (($moduleLineMarkPos ?? false) !== false || ($moduleMultiLineMarkStartPos ?? false) !== false) {
|
||||
file_put_contents($filePaths[$item], $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function buildMarkStr(string $type, string $uid = '', string $extend = ''): string
|
||||
{
|
||||
$nonTabKeys = ['mti', 'avi', 'navi', 'navs'];
|
||||
$extend = match ($extend) {
|
||||
'mainTsImport' => 'mti',
|
||||
'mainTsStart' => 'mts',
|
||||
'appVueImport' => 'avi',
|
||||
'appVueOnMounted' => 'avo',
|
||||
'nuxtAppVueImport' => 'navi',
|
||||
'nuxtAppVueStart' => 'navs',
|
||||
default => '',
|
||||
};
|
||||
return match ($type) {
|
||||
'import-root-mark' => '// modules import mark, Please do not remove.',
|
||||
'start-root-mark' => '// modules start mark, Please do not remove.',
|
||||
'onMounted-root-mark' => '// Modules onMounted mark, Please do not remove.',
|
||||
'module-line-mark' => ' // Code from module \'' . $uid . "'" . ($extend ? "($extend)" : ''),
|
||||
'module-multi-line-mark-start' => (in_array($extend, $nonTabKeys) ? '' : Helper::tab()) . "// Code from module '$uid' start" . ($extend ? "($extend)" : '') . "\n",
|
||||
'module-multi-line-mark-end' => (in_array($extend, $nonTabKeys) ? '' : Helper::tab()) . "// Code from module '$uid' end",
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
public static function getNuxtVersion(): mixed
|
||||
{
|
||||
$nuxtPackageJsonPath = Filesystem::fsFit(root_path() . 'web-nuxt/package.json');
|
||||
if (is_file($nuxtPackageJsonPath)) {
|
||||
$nuxtPackageJson = file_get_contents($nuxtPackageJsonPath);
|
||||
$nuxtPackageJson = json_decode($nuxtPackageJson, true);
|
||||
if ($nuxtPackageJson && isset($nuxtPackageJson['version'])) {
|
||||
return $nuxtPackageJson['version'];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function createRuntime(string $dir): void
|
||||
{
|
||||
$runtimeFilePath = $dir . '.runtime';
|
||||
$files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
$filePaths = [];
|
||||
foreach ($files as $file) {
|
||||
if (!$file->isDir()) {
|
||||
$pathName = $file->getPathName();
|
||||
if ($pathName == $runtimeFilePath) continue;
|
||||
$filePaths[] = [
|
||||
'path' => Filesystem::fsFit($pathName),
|
||||
'size' => filesize($pathName),
|
||||
'md5' => md5_file($pathName),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($runtimeFilePath, json_encode([
|
||||
'files' => $filePaths,
|
||||
'pure' => config('buildadmin.module_pure_install', false),
|
||||
]));
|
||||
}
|
||||
|
||||
public static function getRuntime(string $dir, string $key = ''): mixed
|
||||
{
|
||||
$runtimeFilePath = $dir . '.runtime';
|
||||
$runtimeContent = @file_get_contents($runtimeFilePath);
|
||||
$runtimeContentArr = json_decode($runtimeContent, true);
|
||||
if (!$runtimeContentArr) return [];
|
||||
|
||||
if ($key) {
|
||||
return $runtimeContentArr[$key] ?? [];
|
||||
}
|
||||
return $runtimeContentArr;
|
||||
}
|
||||
}
|
||||
280
dafuweng-webman/app/admin/library/traits/Backend.php
Normal file
280
dafuweng-webman/app/admin/library/traits/Backend.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\library\traits;
|
||||
|
||||
use Throwable;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* 后台控制器 trait(Webman 迁移版)
|
||||
* 提供 CRUD 方法:index、add、edit、del、sortable
|
||||
* 方法需返回 Response,供 Webman 路由直接返回
|
||||
*/
|
||||
trait Backend
|
||||
{
|
||||
/**
|
||||
* 排除入库字段
|
||||
*/
|
||||
protected function excludeFields(array $params): array
|
||||
{
|
||||
if (!is_array($this->preExcludeFields)) {
|
||||
$this->preExcludeFields = explode(',', (string) $this->preExcludeFields);
|
||||
}
|
||||
|
||||
foreach ($this->preExcludeFields as $field) {
|
||||
if (array_key_exists($field, $params)) {
|
||||
unset($params[$field]);
|
||||
}
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看(内部实现,由 Backend::index(Request) 调用)
|
||||
*/
|
||||
protected function _index(): Response
|
||||
{
|
||||
if ($this->request && $this->request->get('select')) {
|
||||
$this->select();
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->field($this->indexField)
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加(内部实现)
|
||||
*/
|
||||
protected function _add(): Response
|
||||
{
|
||||
if ($this->request && $this->request->method() === 'POST') {
|
||||
$data = $this->request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
|
||||
$data[$this->dataLimitField] = $this->auth->id;
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validate)) {
|
||||
$validate = new $validate();
|
||||
if ($this->modelSceneValidate) {
|
||||
$validate->scene('add');
|
||||
}
|
||||
$validate->check($data);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
return $this->success(__('Added successfully'));
|
||||
}
|
||||
return $this->error(__('No rows were added'));
|
||||
}
|
||||
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑(内部实现)
|
||||
*/
|
||||
protected function _edit(): Response
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$id = $this->request ? ($this->request->post($pk) ?? $this->request->get($pk)) : null;
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds && !in_array($row[$this->dataLimitField], $dataLimitAdminIds)) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
if ($this->request && $this->request->method() === 'POST') {
|
||||
$data = $this->request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
if (class_exists($validate)) {
|
||||
$validate = new $validate();
|
||||
if ($this->modelSceneValidate) {
|
||||
$validate->scene('edit');
|
||||
}
|
||||
$data[$pk] = $row[$pk];
|
||||
$validate->check($data);
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
return $this->success(__('Update successful'));
|
||||
}
|
||||
return $this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除(内部实现)
|
||||
*/
|
||||
protected function _del(): Response
|
||||
{
|
||||
$where = [];
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds) {
|
||||
$where[] = [$this->dataLimitField, 'in', $dataLimitAdminIds];
|
||||
}
|
||||
|
||||
$ids = $this->request ? ($this->request->post('ids') ?? $this->request->get('ids') ?? []) : [];
|
||||
$ids = is_array($ids) ? $ids : [];
|
||||
$where[] = [$this->model->getPk(), 'in', $ids];
|
||||
$data = $this->model->where($where)->select();
|
||||
|
||||
$count = 0;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($data as $v) {
|
||||
$count += $v->delete();
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
if ($count) {
|
||||
return $this->success(__('Deleted successfully'));
|
||||
}
|
||||
return $this->error(__('No rows were deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序 - 增量重排法(内部实现)
|
||||
*/
|
||||
protected function _sortable(): Response
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$move = $this->request ? $this->request->post('move') ?? $this->request->get('move') : null;
|
||||
$target = $this->request ? $this->request->post('target') ?? $this->request->get('target') : null;
|
||||
$order = $this->request ? ($this->request->post('order') ?? $this->request->get('order')) : null;
|
||||
$order = $order ?: $this->defaultSortField;
|
||||
$direction = $this->request ? ($this->request->post('direction') ?? $this->request->get('direction')) : null;
|
||||
|
||||
$dataLimitWhere = [];
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds) {
|
||||
$dataLimitWhere[] = [$this->dataLimitField, 'in', $dataLimitAdminIds];
|
||||
}
|
||||
|
||||
$moveRow = $this->model->where($dataLimitWhere)->find($move);
|
||||
$targetRow = $this->model->where($dataLimitWhere)->find($target);
|
||||
|
||||
if ($move == $target || !$moveRow || !$targetRow || !$direction) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
if ($order && is_string($order)) {
|
||||
$order = explode(',', $order);
|
||||
$order = [$order[0] => $order[1] ?? 'asc'];
|
||||
}
|
||||
if (!is_array($order) || !array_key_exists($this->weighField, $order)) {
|
||||
return $this->error(__('Please use the %s field to sort before operating', [$this->weighField]));
|
||||
}
|
||||
|
||||
$order = $this->queryOrderBuilder();
|
||||
$weigh = $targetRow[$this->weighField];
|
||||
|
||||
$updateMethod = $order[$this->weighField] == 'desc' ? ($direction == 'up' ? 'dec' : 'inc') : ($direction == 'up' ? 'inc' : 'dec');
|
||||
|
||||
$weighRowIds = $this->model
|
||||
->where($dataLimitWhere)
|
||||
->where($this->weighField, $weigh)
|
||||
->order($order)
|
||||
->column($pk);
|
||||
$weighRowsCount = count($weighRowIds);
|
||||
|
||||
$this->model->where($dataLimitWhere)
|
||||
->where($this->weighField, $updateMethod == 'dec' ? '<' : '>', $weigh)
|
||||
->whereNotIn($pk, [$moveRow->$pk])
|
||||
->$updateMethod($this->weighField, $weighRowsCount)
|
||||
->save();
|
||||
|
||||
if ($direction == 'down') {
|
||||
$weighRowIds = array_reverse($weighRowIds);
|
||||
}
|
||||
|
||||
$moveComplete = 0;
|
||||
$weighRowIdsStr = implode(',', $weighRowIds);
|
||||
$weighRows = $this->model->where($dataLimitWhere)
|
||||
->where($pk, 'in', $weighRowIdsStr)
|
||||
->orderRaw("field($pk,$weighRowIdsStr)")
|
||||
->select();
|
||||
|
||||
foreach ($weighRows as $key => $weighRow) {
|
||||
if ($moveRow[$pk] == $weighRow[$pk]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rowWeighVal = $updateMethod == 'dec' ? $weighRow[$this->weighField] - $key : $weighRow[$this->weighField] + $key;
|
||||
|
||||
if ($weighRow[$pk] == $targetRow[$pk]) {
|
||||
$moveComplete = 1;
|
||||
$moveRow[$this->weighField] = $rowWeighVal;
|
||||
$moveRow->save();
|
||||
}
|
||||
|
||||
$rowWeighVal = $updateMethod == 'dec' ? $rowWeighVal - $moveComplete : $rowWeighVal + $moveComplete;
|
||||
$weighRow[$this->weighField] = $rowWeighVal;
|
||||
$weighRow->save();
|
||||
}
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载为 select(远程下拉选择框)数据
|
||||
*/
|
||||
public function select(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
64
dafuweng-webman/app/admin/model/Admin.php
Normal file
64
dafuweng-webman/app/admin/model/Admin.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
use support\think\Db;
|
||||
|
||||
/**
|
||||
* Admin 模型(Webman 迁移版)
|
||||
* @property int $id 管理员ID
|
||||
* @property string $username 管理员用户名
|
||||
* @property string $nickname 管理员昵称
|
||||
* @property string $email 管理员邮箱
|
||||
* @property string $mobile 管理员手机号
|
||||
* @property string $last_login_ip 上次登录IP
|
||||
* @property string $last_login_time 上次登录时间
|
||||
* @property int $login_failure 登录失败次数
|
||||
* @property string $password 密码密文
|
||||
* @property string $salt 密码盐
|
||||
* @property string $status 状态:enable=启用,disable=禁用
|
||||
*/
|
||||
class Admin extends Model
|
||||
{
|
||||
protected string $table = 'admin';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
|
||||
protected array $append = [
|
||||
'group_arr',
|
||||
'group_name_arr',
|
||||
];
|
||||
|
||||
public function getGroupArrAttr($value, $row): array
|
||||
{
|
||||
return Db::name('admin_group_access')
|
||||
->where('uid', $row['id'])
|
||||
->column('group_id');
|
||||
}
|
||||
|
||||
public function getGroupNameArrAttr($value, $row): array
|
||||
{
|
||||
$groupAccess = Db::name('admin_group_access')
|
||||
->where('uid', $row['id'])
|
||||
->column('group_id');
|
||||
return AdminGroup::whereIn('id', $groupAccess)->column('name');
|
||||
}
|
||||
|
||||
public function getAvatarAttr($value): string
|
||||
{
|
||||
return full_url($value ?? '', false, config('buildadmin.default_avatar'));
|
||||
}
|
||||
|
||||
public function setAvatarAttr($value): string
|
||||
{
|
||||
return $value === full_url('', false, config('buildadmin.default_avatar')) ? '' : $value;
|
||||
}
|
||||
|
||||
public function resetPassword(int|string $uid, string $newPassword): int
|
||||
{
|
||||
return $this->where(['id' => $uid])->update(['password' => hash_password($newPassword), 'salt' => '']);
|
||||
}
|
||||
}
|
||||
17
dafuweng-webman/app/admin/model/AdminGroup.php
Normal file
17
dafuweng-webman/app/admin/model/AdminGroup.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
/**
|
||||
* AdminGroup 模型(Webman 迁移版)
|
||||
*/
|
||||
class AdminGroup extends Model
|
||||
{
|
||||
protected string $table = 'admin_group';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
}
|
||||
140
dafuweng-webman/app/admin/model/AdminLog.php
Normal file
140
dafuweng-webman/app/admin/model/AdminLog.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use Throwable;
|
||||
use app\admin\library\Auth;
|
||||
use support\think\Model;
|
||||
use Webman\Http\Request;
|
||||
|
||||
/**
|
||||
* AdminLog 模型(Webman 迁移版)
|
||||
*/
|
||||
class AdminLog extends Model
|
||||
{
|
||||
protected string $table = 'admin_log';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
protected bool $updateTime = false;
|
||||
|
||||
protected string $title = '';
|
||||
protected string|array $data = '';
|
||||
protected array $urlIgnoreRegex = [
|
||||
'/^(.*)\/(select|index|logout)$/i',
|
||||
];
|
||||
protected array $desensitizationRegex = [
|
||||
'/(password|salt|token)/i'
|
||||
];
|
||||
|
||||
public static function instance(?Request $request = null): self
|
||||
{
|
||||
$request = $request ?? (function_exists('request') ? request() : null);
|
||||
if ($request !== null && isset($request->adminLog) && $request->adminLog instanceof self) {
|
||||
return $request->adminLog;
|
||||
}
|
||||
$log = new static();
|
||||
if ($request !== null) {
|
||||
$request->adminLog = $log;
|
||||
}
|
||||
return $log;
|
||||
}
|
||||
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/** 设置日志内容(BuildAdmin 控制器调用) */
|
||||
public function setData(string|array $data): void
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function setUrlIgnoreRegex(array|string $regex = []): void
|
||||
{
|
||||
$this->urlIgnoreRegex = array_merge($this->urlIgnoreRegex, is_array($regex) ? $regex : [$regex]);
|
||||
}
|
||||
|
||||
public function setDesensitizationRegex(array|string $regex = []): void
|
||||
{
|
||||
$this->desensitizationRegex = array_merge($this->desensitizationRegex, is_array($regex) ? $regex : [$regex]);
|
||||
}
|
||||
|
||||
protected function desensitization(array|string $data): array|string
|
||||
{
|
||||
if (!is_array($data) || !$this->desensitizationRegex) {
|
||||
return $data;
|
||||
}
|
||||
foreach ($data as $index => &$item) {
|
||||
foreach ($this->desensitizationRegex as $reg) {
|
||||
if (preg_match($reg, (string) $index)) {
|
||||
$item = '***';
|
||||
} elseif (is_array($item)) {
|
||||
$item = $this->desensitization($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function record(string $title = '', string|array|null $data = null, ?Request $request = null): void
|
||||
{
|
||||
$request = $request ?? (function_exists('request') ? request() : null);
|
||||
if (!$request) {
|
||||
return;
|
||||
}
|
||||
|
||||
$auth = Auth::instance();
|
||||
$adminId = $auth->isLogin() ? $auth->id : 0;
|
||||
$username = $auth->isLogin() ? $auth->username : ($request->get('username') ?? $request->post('username') ?? __('Unknown'));
|
||||
|
||||
$controllerPath = get_controller_path($request) ?? '';
|
||||
$pathParts = explode('/', trim($request->path(), '/'));
|
||||
$action = $pathParts[array_key_last($pathParts)] ?? '';
|
||||
$path = $controllerPath . ($action ? '/' . $action : '');
|
||||
|
||||
foreach ($this->urlIgnoreRegex as $item) {
|
||||
if (preg_match($item, $path)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$data = $data ?: $this->data;
|
||||
if (!$data) {
|
||||
$data = array_merge($request->get(), $request->post());
|
||||
}
|
||||
$data = $this->desensitization($data);
|
||||
$title = $title ?: $this->title;
|
||||
if (!$title && class_exists(\app\admin\model\AdminRule::class)) {
|
||||
$controllerTitle = \app\admin\model\AdminRule::where('name', $controllerPath)->value('title');
|
||||
$pathTitle = \app\admin\model\AdminRule::where('name', $path)->value('title');
|
||||
$title = $pathTitle ?: __('Unknown') . '(' . $action . ')';
|
||||
$title = $controllerTitle ? ($controllerTitle . '-' . $title) : $title;
|
||||
}
|
||||
if (!$title) {
|
||||
$title = __('Unknown');
|
||||
}
|
||||
|
||||
$url = $request->url();
|
||||
$url = strlen($url) > 1500 ? substr($url, 0, 1500) : $url;
|
||||
$useragent = $request->header('user-agent', '');
|
||||
$useragent = strlen($useragent) > 255 ? substr($useragent, 0, 255) : $useragent;
|
||||
|
||||
self::create([
|
||||
'admin_id' => $adminId,
|
||||
'username' => $username,
|
||||
'url' => $url,
|
||||
'title' => $title,
|
||||
'data' => !is_scalar($data) ? json_encode($data, JSON_UNESCAPED_UNICODE) : $data,
|
||||
'ip' => $request->getRealIp(),
|
||||
'useragent' => $useragent,
|
||||
]);
|
||||
}
|
||||
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(Admin::class);
|
||||
}
|
||||
}
|
||||
20
dafuweng-webman/app/admin/model/AdminRule.php
Normal file
20
dafuweng-webman/app/admin/model/AdminRule.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class AdminRule extends Model
|
||||
{
|
||||
protected string $table = 'admin_rule';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
|
||||
public function setComponentAttr($value)
|
||||
{
|
||||
if ($value) $value = str_replace('\\', '/', $value);
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
127
dafuweng-webman/app/admin/model/Config.php
Normal file
127
dafuweng-webman/app/admin/model/Config.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
/**
|
||||
* 系统配置模型(Webman 迁移版)
|
||||
*/
|
||||
class Config extends Model
|
||||
{
|
||||
public static string $cacheTag = 'sys_config';
|
||||
|
||||
protected string $table = 'config';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
|
||||
protected array $append = ['value', 'content', 'extend', 'input_extend'];
|
||||
protected array $jsonDecodeType = ['checkbox', 'array', 'selects'];
|
||||
protected array $needContent = ['radio', 'checkbox', 'select', 'selects'];
|
||||
|
||||
public static function onBeforeInsert(Config $model): void
|
||||
{
|
||||
if (!in_array($model->getData('type'), $model->needContent)) {
|
||||
$model->content = null;
|
||||
} else {
|
||||
$model->content = json_encode(str_attr_to_array($model->getData('content')));
|
||||
}
|
||||
if (is_array($model->rule)) {
|
||||
$model->rule = implode(',', $model->rule);
|
||||
}
|
||||
if ($model->getData('extend') || $model->getData('inputExtend')) {
|
||||
$extend = str_attr_to_array($model->getData('extend'));
|
||||
$inputExtend = str_attr_to_array($model->getData('inputExtend'));
|
||||
if ($inputExtend) {
|
||||
$extend['baInputExtend'] = $inputExtend;
|
||||
}
|
||||
if ($extend) {
|
||||
$model->extend = json_encode($extend);
|
||||
}
|
||||
}
|
||||
$model->allow_del = 1;
|
||||
}
|
||||
|
||||
public static function onAfterWrite(): void
|
||||
{
|
||||
clear_config_cache();
|
||||
}
|
||||
|
||||
public function getValueAttr($value, $row)
|
||||
{
|
||||
if (!isset($row['type']) || $value == '0') return $value;
|
||||
if (in_array($row['type'], $this->jsonDecodeType)) {
|
||||
return empty($value) ? [] : json_decode($value, true);
|
||||
}
|
||||
if ($row['type'] == 'switch') {
|
||||
return (bool) $value;
|
||||
}
|
||||
if ($row['type'] == 'editor') {
|
||||
return !$value ? '' : htmlspecialchars_decode($value);
|
||||
}
|
||||
if (in_array($row['type'], ['city', 'remoteSelects'])) {
|
||||
if (!$value) return [];
|
||||
if (!is_array($value)) return explode(',', $value);
|
||||
return $value;
|
||||
}
|
||||
return $value ?: '';
|
||||
}
|
||||
|
||||
public function setValueAttr(mixed $value, $row): mixed
|
||||
{
|
||||
if (in_array($row['type'], $this->jsonDecodeType)) {
|
||||
return $value ? json_encode($value) : '';
|
||||
}
|
||||
if ($row['type'] == 'switch') {
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
if ($row['type'] == 'time') {
|
||||
return $value ? date('H:i:s', strtotime($value)) : '';
|
||||
}
|
||||
if ($row['type'] == 'city') {
|
||||
if ($value && is_array($value)) {
|
||||
return implode(',', $value);
|
||||
}
|
||||
return $value ?: '';
|
||||
}
|
||||
if (is_array($value)) {
|
||||
return implode(',', $value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getContentAttr($value, $row)
|
||||
{
|
||||
if (!isset($row['type'])) return '';
|
||||
if (in_array($row['type'], $this->needContent)) {
|
||||
$arr = json_decode($value, true);
|
||||
return $arr ?: [];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getExtendAttr($value)
|
||||
{
|
||||
if ($value) {
|
||||
$arr = json_decode($value, true);
|
||||
if ($arr) {
|
||||
unset($arr['baInputExtend']);
|
||||
return $arr;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getInputExtendAttr($value, $row)
|
||||
{
|
||||
if ($row && $row['extend']) {
|
||||
$arr = json_decode($row['extend'], true);
|
||||
if ($arr && isset($arr['baInputExtend'])) {
|
||||
return $arr['baInputExtend'];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
20
dafuweng-webman/app/admin/model/CrudLog.php
Normal file
20
dafuweng-webman/app/admin/model/CrudLog.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class CrudLog extends Model
|
||||
{
|
||||
protected string $table = 'crud_log';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
protected bool $updateTime = false;
|
||||
|
||||
protected array $type = [
|
||||
'table' => 'array',
|
||||
'fields' => 'array',
|
||||
];
|
||||
}
|
||||
14
dafuweng-webman/app/admin/model/DataRecycle.php
Normal file
14
dafuweng-webman/app/admin/model/DataRecycle.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class DataRecycle extends Model
|
||||
{
|
||||
protected string $table = 'security_data_recycle';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
}
|
||||
26
dafuweng-webman/app/admin/model/DataRecycleLog.php
Normal file
26
dafuweng-webman/app/admin/model/DataRecycleLog.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
use think\model\relation\BelongsTo;
|
||||
|
||||
class DataRecycleLog extends Model
|
||||
{
|
||||
protected string $table = 'security_data_recycle_log';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
protected bool $updateTime = false;
|
||||
|
||||
public function recycle(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(DataRecycle::class, 'recycle_id');
|
||||
}
|
||||
|
||||
public function admin(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Admin::class, 'admin_id');
|
||||
}
|
||||
}
|
||||
18
dafuweng-webman/app/admin/model/SensitiveData.php
Normal file
18
dafuweng-webman/app/admin/model/SensitiveData.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class SensitiveData extends Model
|
||||
{
|
||||
protected string $table = 'security_sensitive_data';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
|
||||
protected array $type = [
|
||||
'data_fields' => 'array',
|
||||
];
|
||||
}
|
||||
26
dafuweng-webman/app/admin/model/SensitiveDataLog.php
Normal file
26
dafuweng-webman/app/admin/model/SensitiveDataLog.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
use think\model\relation\BelongsTo;
|
||||
|
||||
class SensitiveDataLog extends Model
|
||||
{
|
||||
protected string $table = 'security_sensitive_data_log';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
protected bool $updateTime = false;
|
||||
|
||||
public function sensitive(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SensitiveData::class, 'sensitive_id');
|
||||
}
|
||||
|
||||
public function admin(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Admin::class, 'admin_id');
|
||||
}
|
||||
}
|
||||
45
dafuweng-webman/app/admin/model/User.php
Normal file
45
dafuweng-webman/app/admin/model/User.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
use think\model\relation\BelongsTo;
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
protected string $table = 'user';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
|
||||
public function getAvatarAttr($value): string
|
||||
{
|
||||
return full_url($value ?? '', false, config('buildadmin.default_avatar'));
|
||||
}
|
||||
|
||||
public function setAvatarAttr($value): string
|
||||
{
|
||||
return $value === full_url('', false, config('buildadmin.default_avatar')) ? '' : $value;
|
||||
}
|
||||
|
||||
public function getMoneyAttr($value): string
|
||||
{
|
||||
return bcdiv((string) $value, '100', 2);
|
||||
}
|
||||
|
||||
public function setMoneyAttr($value): string
|
||||
{
|
||||
return bcmul((string) $value, '100', 2);
|
||||
}
|
||||
|
||||
public function userGroup(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(UserGroup::class, 'group_id');
|
||||
}
|
||||
|
||||
public function resetPassword(int|string $uid, string $newPassword): int
|
||||
{
|
||||
return $this->where(['id' => $uid])->update(['password' => hash_password($newPassword), 'salt' => '']);
|
||||
}
|
||||
}
|
||||
14
dafuweng-webman/app/admin/model/UserGroup.php
Normal file
14
dafuweng-webman/app/admin/model/UserGroup.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class UserGroup extends Model
|
||||
{
|
||||
protected string $table = 'user_group';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
}
|
||||
69
dafuweng-webman/app/admin/model/UserMoneyLog.php
Normal file
69
dafuweng-webman/app/admin/model/UserMoneyLog.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
use think\model\relation\BelongsTo;
|
||||
|
||||
class UserMoneyLog extends Model
|
||||
{
|
||||
protected string $table = 'user_money_log';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
protected bool $updateTime = false;
|
||||
|
||||
public static function onBeforeInsert($model): void
|
||||
{
|
||||
$user = User::where('id', $model->user_id)->lock(true)->find();
|
||||
if (!$user) {
|
||||
throw new \Exception(__("The user can't find it"));
|
||||
}
|
||||
if (!$model->memo) {
|
||||
throw new \Exception(__("Change note cannot be blank"));
|
||||
}
|
||||
$model->before = $user->money;
|
||||
$user->money += $model->money;
|
||||
$user->save();
|
||||
$model->after = $user->money;
|
||||
}
|
||||
|
||||
public static function onBeforeDelete(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMoneyAttr($value): string
|
||||
{
|
||||
return bcdiv((string) $value, '100', 2);
|
||||
}
|
||||
|
||||
public function setMoneyAttr($value): string
|
||||
{
|
||||
return bcmul((string) $value, '100', 2);
|
||||
}
|
||||
|
||||
public function getBeforeAttr($value): string
|
||||
{
|
||||
return bcdiv((string) $value, '100', 2);
|
||||
}
|
||||
|
||||
public function setBeforeAttr($value): string
|
||||
{
|
||||
return bcmul((string) $value, '100', 2);
|
||||
}
|
||||
|
||||
public function getAfterAttr($value): string
|
||||
{
|
||||
return bcdiv((string) $value, '100', 2);
|
||||
}
|
||||
|
||||
public function setAfterAttr($value): string
|
||||
{
|
||||
return bcmul((string) $value, '100', 2);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
26
dafuweng-webman/app/admin/model/UserRule.php
Normal file
26
dafuweng-webman/app/admin/model/UserRule.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class UserRule extends Model
|
||||
{
|
||||
protected string $table = 'user_rule';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
|
||||
protected static function onAfterInsert($model): void
|
||||
{
|
||||
$pk = $model->getPk();
|
||||
$model->where($pk, $model[$pk])->update(['weigh' => $model[$pk]]);
|
||||
}
|
||||
|
||||
public function setComponentAttr($value)
|
||||
{
|
||||
if ($value) $value = str_replace('\\', '/', $value);
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
39
dafuweng-webman/app/admin/model/UserScoreLog.php
Normal file
39
dafuweng-webman/app/admin/model/UserScoreLog.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\think\Model;
|
||||
use think\model\relation\BelongsTo;
|
||||
|
||||
class UserScoreLog extends Model
|
||||
{
|
||||
protected string $table = 'user_score_log';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
protected bool $updateTime = false;
|
||||
|
||||
public static function onBeforeInsert($model): void
|
||||
{
|
||||
$user = User::where('id', $model->user_id)->lock(true)->find();
|
||||
if (!$user) {
|
||||
throw new \Exception(__("The user can't find it"));
|
||||
}
|
||||
if (!$model->memo) {
|
||||
throw new \Exception(__("Change note cannot be blank"));
|
||||
}
|
||||
$model->before = $user->score;
|
||||
$user->score += $model->score;
|
||||
$user->save();
|
||||
$model->after = $user->score;
|
||||
}
|
||||
|
||||
public static function onBeforeDelete(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
58
dafuweng-webman/app/admin/validate/Admin.php
Normal file
58
dafuweng-webman/app/admin/validate/Admin.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Admin extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
protected $rule = [
|
||||
'username' => 'require|regex:^[a-zA-Z][a-zA-Z0-9_]{2,15}$|unique:admin',
|
||||
'nickname' => 'require',
|
||||
'password' => 'require|regex:^(?!.*[&<>"\'\n\r]).{6,32}$',
|
||||
'email' => 'email|unique:admin',
|
||||
'mobile' => 'mobile|unique:admin',
|
||||
'group_arr' => 'require|array',
|
||||
];
|
||||
|
||||
protected $message = [];
|
||||
|
||||
protected $field = [];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['username', 'nickname', 'password', 'email', 'mobile', 'group_arr'],
|
||||
];
|
||||
|
||||
public function sceneInfo(): static
|
||||
{
|
||||
return $this->only(['nickname', 'password', 'email', 'mobile'])
|
||||
->remove('password', 'require');
|
||||
}
|
||||
|
||||
public function sceneEdit(): static
|
||||
{
|
||||
return $this->only(['username', 'nickname', 'password', 'email', 'mobile', 'group_arr'])
|
||||
->remove('password', 'require');
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->field = [
|
||||
'username' => __('Username'),
|
||||
'nickname' => __('Nickname'),
|
||||
'password' => __('Password'),
|
||||
'email' => __('Email'),
|
||||
'mobile' => __('Mobile'),
|
||||
'group_arr' => __('Group Name Arr'),
|
||||
];
|
||||
$this->message = array_merge($this->message, [
|
||||
'username.regex' => __('Please input correct username'),
|
||||
'password.regex' => __('Please input correct password')
|
||||
]);
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
37
dafuweng-webman/app/admin/validate/AdminGroup.php
Normal file
37
dafuweng-webman/app/admin/validate/AdminGroup.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class AdminGroup extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
protected $rule = [
|
||||
'name' => 'require',
|
||||
'rules' => 'require',
|
||||
];
|
||||
|
||||
protected $message = [];
|
||||
|
||||
protected $field = [];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['name', 'rules'],
|
||||
'edit' => ['name', 'rules'],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->field = [
|
||||
'name' => __('name'),
|
||||
];
|
||||
$this->message = [
|
||||
'rules' => __('Please select rules'),
|
||||
];
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
37
dafuweng-webman/app/admin/validate/AdminRule.php
Normal file
37
dafuweng-webman/app/admin/validate/AdminRule.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class AdminRule extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
protected $rule = [
|
||||
'type' => 'require',
|
||||
'title' => 'require',
|
||||
'name' => 'require|unique:admin_rule',
|
||||
];
|
||||
|
||||
protected $message = [];
|
||||
|
||||
protected $field = [];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['type', 'title', 'name'],
|
||||
'edit' => ['type', 'title', 'name'],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->field = [
|
||||
'type' => __('type'),
|
||||
'title' => __('title'),
|
||||
'name' => __('name'),
|
||||
];
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
32
dafuweng-webman/app/admin/validate/Config.php
Normal file
32
dafuweng-webman/app/admin/validate/Config.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Config extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
protected $rule = [
|
||||
'name' => 'require|unique:config',
|
||||
];
|
||||
|
||||
protected $message = [];
|
||||
|
||||
protected $field = [];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['name'],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->field = [
|
||||
'name' => __('Variable name'),
|
||||
];
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
39
dafuweng-webman/app/admin/validate/DataRecycle.php
Normal file
39
dafuweng-webman/app/admin/validate/DataRecycle.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class DataRecycle extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
protected $rule = [
|
||||
'name' => 'require',
|
||||
'controller' => 'require|unique:security_data_recycle',
|
||||
'data_table' => 'require',
|
||||
'primary_key' => 'require',
|
||||
];
|
||||
|
||||
protected $message = [];
|
||||
|
||||
protected $field = [];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['name', 'controller', 'data_table', 'primary_key'],
|
||||
'edit' => ['name', 'controller', 'data_table', 'primary_key'],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->field = [
|
||||
'name' => __('Name'),
|
||||
'controller' => __('Controller'),
|
||||
'data_table' => __('Data Table'),
|
||||
'primary_key' => __('Primary Key'),
|
||||
];
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
41
dafuweng-webman/app/admin/validate/SensitiveData.php
Normal file
41
dafuweng-webman/app/admin/validate/SensitiveData.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class SensitiveData extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
protected $rule = [
|
||||
'name' => 'require',
|
||||
'controller' => 'require|unique:security_sensitive_data',
|
||||
'data_table' => 'require',
|
||||
'primary_key' => 'require',
|
||||
'data_fields' => 'require',
|
||||
];
|
||||
|
||||
protected $message = [];
|
||||
|
||||
protected $field = [];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['name', 'data_fields', 'controller', 'data_table', 'primary_key'],
|
||||
'edit' => ['name', 'data_fields', 'controller', 'data_table', 'primary_key'],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->field = [
|
||||
'name' => __('Name'),
|
||||
'data_fields' => __('Data Fields'),
|
||||
'controller' => __('Controller'),
|
||||
'data_table' => __('Data Table'),
|
||||
'primary_key' => __('Primary Key'),
|
||||
];
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
37
dafuweng-webman/app/admin/validate/UserMoneyLog.php
Normal file
37
dafuweng-webman/app/admin/validate/UserMoneyLog.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class UserMoneyLog extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
protected $rule = [
|
||||
'user_id' => 'require',
|
||||
'money' => 'require',
|
||||
'memo' => 'require',
|
||||
];
|
||||
|
||||
protected $message = [];
|
||||
|
||||
protected $field = [];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['user_id', 'money', 'memo'],
|
||||
'edit' => ['user_id', 'money', 'memo'],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->field = [
|
||||
'user_id' => __('user_id'),
|
||||
'money' => __('money'),
|
||||
'memo' => __('memo'),
|
||||
];
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
37
dafuweng-webman/app/admin/validate/UserScoreLog.php
Normal file
37
dafuweng-webman/app/admin/validate/UserScoreLog.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class UserScoreLog extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
protected $rule = [
|
||||
'user_id' => 'require',
|
||||
'score' => 'require',
|
||||
'memo' => 'require',
|
||||
];
|
||||
|
||||
protected $message = [];
|
||||
|
||||
protected $field = [];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['user_id', 'score', 'memo'],
|
||||
'edit' => ['user_id', 'score', 'memo'],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->field = [
|
||||
'user_id' => __('user_id'),
|
||||
'score' => __('score'),
|
||||
'memo' => __('memo'),
|
||||
];
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user