new feature

This commit is contained in:
JaguarJack
2022-12-18 22:44:58 +08:00
parent 47a76af4b0
commit 17f2dc4d3c
20 changed files with 1441 additions and 564 deletions

View File

@@ -32,7 +32,7 @@ class Schemas extends CatchModel
/**
* @var string[]
*/
protected array $mergeCasts = [
protected $casts = [
'is_soft_delete' => Status::class
];

View File

@@ -21,7 +21,7 @@ class Components implements OptionInterface
public function get(): array
{
if ($module = request()->get('module')) {
$components = File::glob(CatchAdmin::getModuleViewsPath($module).'*/*.vue');
$components = File::glob(CatchAdmin::getModuleViewsPath($module).'*'.DIRECTORY_SEPARATOR.'*.vue');
foreach ($components as $component) {
$this->components[] = [

View File

@@ -4,14 +4,16 @@ namespace Modules\Permissions\Middlewares;
use Illuminate\Http\Request;
use Modules\Permissions\Exceptions\PermissionForbidden;
use Modules\Permissions\Models\LogOperate;
use Modules\User\Models\User;
use Symfony\Component\HttpFoundation\Response;
class PermissionGate
{
public function handle(Request $request, \Closure $next)
{
if ($request->isMethod('get')) {
return $next($request);
// return $next($request);
}
/* @var User $user */
@@ -23,4 +25,17 @@ class PermissionGate
return $next($request);
}
/**
* terminate
*
* @param Request $request
* @param Response $response
* @return void
*/
public function terminate(Request $request, Response $response): void
{
app(LogOperate::class)->log($request, $response);
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Modules\Permissions\Models;
use Catch\CatchAdmin;
use Catch\Traits\DB\BaseOperate;
use Catch\Traits\DB\ScopeTrait;
use Catch\Traits\DB\Trans;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
use Modules\Permissions\Exceptions\PermissionForbidden;
use Illuminate\Database\Eloquent\Model;
use Symfony\Component\HttpFoundation\Response;
class LogOperate extends Model
{
use BaseOperate, Trans, ScopeTrait;
protected $table = 'log_operate';
protected $fillable = [
'id',
'module',
'operate',
'route',
'params',
'ip',
'http_method',
'http_method',
'start_at',
'time_taken',
'creator_id',
'created_at',
];
/**
*
* @param Request $request
* @param Response $response
* @return void
*/
public function log(Request $request, Response $response): void
{
if (! $response->isOk() && $response->exception instanceof PermissionForbidden) {
return;
}
$user = Auth::guard(getGuardName())->user();
$userModel = getAuthUserModel();
if (! $user instanceof $userModel) {
return;
}
$user->getAttribute('permissions')->each(function ($permission) use ($user, $request, $response) {
if ($permission->isAction()) {
[$controller, $action] = explode('@', $permission->permission_mark);
if (! CatchAdmin::getModuleControllerNamespace($permission->module).$controller.'Controller@'.$action == Route::currentRouteAction()) {
return;
}
$requestStartAt = app(Kernel::class)->requestStartedAt()->timestamp;
$params = $request->all();
// 如果参数过长则不记录
if (!empty($params)) {
if (strlen($encodeParams = \json_encode($params, JSON_UNESCAPED_UNICODE)) > 5000) {
$params = [];
}
}
$this->storeBy([
'module' => $permission->module,
'operate' => $permission->permission_name,
'route' => $permission->permission_mark,
'creator_id' => $user->id,
'http_method' => $request->method(),
'http_code' => $response->getStatusCode(),
'start_at' => $requestStartAt,
'time_taken' => time() - $requestStartAt,
'ip' => $request->ip(),
'params' => !empty($params) ? $encodeParams : '',
'created_at' => time()
]);
}
});
}
/**
* @return Attribute
*/
protected function timeTaken(): Attribute
{
return new Attribute(
get: fn($value) => $value . 's',
);
}
}

View File

@@ -93,6 +93,16 @@ class PermissionsModel extends Model
return $this->type == MenuType::Action;
}
/**
* is top menu
*
* @return bool
*/
public function isTopMenu(): bool
{
return $this->type == MenuType::Top;
}
/**
* actions
*
@@ -117,12 +127,37 @@ class PermissionsModel extends Model
$model->setAttribute('module', $parentMenu->module);
$model->setAttribute('permission_mark', $parentMenu->permission_mark . '@' . $data['permission_mark']);
$model->setAttribute('route', '');
$model->setAttribute('icon', '');
$model->setAttribute('component', '');
$model->setAttribute('redirect', '');
$model->setAttribute('icon', '');
$model->setAttribute('component', '');
$model->setAttribute('redirect', '');
return $model->setCreatorId()->save();
} else {
return parent::storeBy($data);
}
if ($model->isTopMenu()) {
$data['route'] = '/' . trim($data['route'], '/');
}
return parent::storeBy($data);
}
/**
* update data
*
* @param $id
* @param array $data
* @return mixed
*/
public function updateBy($id, array $data): mixed
{
$model = $this->fill($data);
if ($model->isAction()) {
/* @var PermissionsModel $parentMenu */
$parentMenu = $this->firstBy($model->parent_id, 'id');
$data['permission_mark'] = $parentMenu->permission_mark . '@' . $data['permission_mark'];
}
return parent::updateBy($id, $data);
}
}

View File

@@ -5,6 +5,7 @@ namespace Modules\Permissions\Providers;
use Catch\CatchAdmin;
use Catch\Providers\CatchModuleServiceProvider;
use Modules\Permissions\Middlewares\PermissionGate;
use Modules\Permissions\Models\LogOperate;
class PermissionsServiceProvider extends CatchModuleServiceProvider
{
@@ -34,4 +35,7 @@ class PermissionsServiceProvider extends CatchModuleServiceProvider
// TODO: Implement path() method.
return CatchAdmin::getModuleRoutePath('Permissions');
}
}

View File

@@ -35,6 +35,8 @@ return new class () extends Migration {
$table->updatedAt();
$table->deletedAt();
$table->index(['module', 'permission_mark']);
$table->engine = 'InnoDB';
$table->comment('权限表');
});

View File

@@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('log_operate', function (Blueprint $table) {
$table->increments('id');
$table->string('module', 50)->comment('操作');
$table->string('operate', 50)->comment('操作');
$table->string('route', 50)->comment('路由');
$table->text('params')->comment('参数');
$table->string('ip')->comment('ip 地址');
$table->string('http_method', 10)->comment('http 请求方式');
$table->string('http_code')->comment('http status code');
$table->string('start_at')->comment('请求开始时间');
$table->string('time_taken')->comment('请求消耗时间/s');
$table->creatorId();
$table->createdAt();
$table->engine='InnoDB';
$table->comment('操作日志');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('log_operate');
}
};

View File

@@ -1,5 +1,5 @@
<template>
<el-form :model="formData" label-width="80px" ref="form" v-loading="loading" class="pr-4">
<el-form :model="formData" label-width="85px" ref="form" v-loading="loading" class="pr-4">
<div class="flex flex-row justify-between">
<div>
<el-form-item label="菜单类型" prop="type">
@@ -18,7 +18,7 @@
</el-radio-group>
</el-form-item>
<el-form-item label="菜单名称" prop="permission_name" :rules="[{ required: true, message: '菜单名称必须填写' }]">
<Select v-model="formData.permission_name" name="permission_name" :options="actionMenuNames" v-if="isAction" />
<Select v-model="formData.permission_name" name="permission_name" allow-create :options="actionMenuNames" v-if="isAction" />
<el-input v-model="formData.permission_name" name="permission_name" clearable v-else />
</el-form-item>
<el-form-item label="所属模块" prop="module" :rules="[{ required: true, message: '所属模块必须填写' }]" v-if="!isAction">
@@ -93,9 +93,9 @@
import { useCreate } from '/admin/composables/curd/useCreate'
import { useShow } from '/admin/composables/curd/useShow'
import { useOpen } from '/admin/composables/curd/useOpen'
import { onMounted, ref, watch } from 'vue'
import http from '/admin/support/http'
import { MenuType } from '/admin/enum/app'
const props = defineProps({
primary: String | Number,
@@ -109,11 +109,15 @@ const { open, visible } = useOpen()
const closeSelectIcon = () => {
visible.value = false
}
const defaultSort = 1
const defaultKeepalive = 1
const defaultHidden = 1
// 初始化
formData.value.sort = 1
formData.value.keepalive = 1
formData.value.type = 1
formData.value.hidden = 1
formData.value.sort = defaultSort
formData.value.keepalive = defaultKeepalive
formData.value.type = MenuType.TOP_TYPE
formData.value.hidden = defaultHidden
// 默认目录
const isTop = ref<boolean>(true)
const isMenu = ref<boolean>(false)
@@ -121,7 +125,14 @@ const isAction = ref<boolean>(false)
// 回显示表单
if (props.primary) {
useShow(props.api, props.primary, formData)
const { afterShow } = useShow(props.api, props.primary, formData)
afterShow.value = formData => {
console.log(formData.value.permission_mark)
if (formData.value.permission_mark.indexOf('@') !== -1) {
formData.value.permission_mark = formData.value.permission_mark.split('@')[1]
}
}
}
const emit = defineEmits(['close'])
@@ -130,7 +141,7 @@ onMounted(() => {
http.get(props.api).then(r => {
permissions.value = r.data.data
})
// close dialog
close(() => emit('close'))
// 监听 form data
@@ -138,11 +149,10 @@ onMounted(() => {
formData,
() => {
const type: number = formData.value.type
if (type === 1) {
if (type === MenuType.TOP_TYPE) {
isTop.value = true
isMenu.value = isAction.value = false
} else if (type === 2) {
} else if (type === MenuType.PAGE_TYPE) {
isMenu.value = true
isTop.value = isAction.value = false
} else {
@@ -156,10 +166,10 @@ onMounted(() => {
// 菜单是菜单类型的时,清除模块,那么权限标识&组件也需要清除
const clearModule = () => {
if (formData.value.type === 1 || formData.value.type === 2) {
if (formData.value.type === MenuType.TOP_TYPE || formData.value.type === MenuType.PAGE_TYPE) {
formData.value.component = null
}
if (formData.value.type === 2) {
if (formData.value.type === MenuType.PAGE_TYPE) {
formData.value.permission_mark = null
}
}
@@ -197,6 +207,10 @@ beforeUpdate.value = () => {
}
const getParent = (parentId: any) => {
if (typeof parentId === 'number') {
return parentId
}
return typeof parentId === 'undefined' ? 0 : parentId[parentId.length - 1]
}
</script>

View File

@@ -14,8 +14,8 @@
<el-table-column prop="route" label="菜单路由" />
<el-table-column prop="permission_mark" label="权限标识" width="300">
<template #default="scope">
<div v-if="scope.row.actions.length" class="flex gap gap-1">
<el-tag v-for="action in scope.row.actions" class="cursor-pointer" @click="open(action.id)" closable @close="destroy(api, action.id)">{{ action.permission_name }}</el-tag>
<div v-if="scope.row.actions.length" class="flex grid gap-1 grid-cols-4">
<el-tag v-for="action in scope.row.actions" class="cursor-pointer min-w-fit" @click="open(action.id)" closable @close="destroy(api, action.id)">{{ action.permission_name }}</el-tag>
</div>
</template>
</el-table-column>
@@ -46,6 +46,7 @@ import Create from './form/create.vue'
import { useGetList } from '/admin/composables/curd/useGetList'
import { useDestroy } from '/admin/composables/curd/useDestroy'
import { useOpen } from '/admin/composables/curd/useOpen'
import { MenuType } from '/admin/enum/app'
const api = 'permissions/permissions'

View File

@@ -7,6 +7,7 @@ use Catch\Support\Module\ModuleRepository;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Route;
use Modules\Permissions\Models\PermissionsModel;
trait UserRelations
{
@@ -50,15 +51,21 @@ trait UserRelations
*/
public function withPermissions(): self
{
/* @var \Modules\Permissions\Models\PermissionsModel $permissionsModel */
/* @var PermissionsModel $permissionsModel */
$permissionsModel = app($this->getPermissionsModel());
if ($this->isSuperAdmin()) {
$permissions = $permissionsModel->get();
} else {
$roles = app($this->getRolesModel())->with(['permissions'])->get();
$permissions = Collection::make();
$permissions = [];
app($this->getRolesModel())->with(['permissions'])->get()
->each(function ($role) use (&$permissions){
$permissions = $permissions->concat($role->permissions);
});
$permissions = $permissions->unique();
}
$this->setAttribute('permissions', $permissions->each(fn ($permission) => $permission->setAttribute('hidden', $permission->isHidden())));
@@ -80,14 +87,14 @@ trait UserRelations
}
if ($this->isSuperAdmin()) {
return true;
// return true;
}
$this->withPermissions();
$actions = Collection::make();
$this->permissions->each(function ($permission) use (&$actions) {
$this->getAttribute('permissions')->each(function ($permission) use (&$actions) {
if ($permission->isAction()) {
[$controller, $action] = explode('@', $permission->permission_mark);