Table of Contents
System Overview
Core Features
- User Management - CRUD, batch operations
- Role & Permission - RBAC permission control
- Menu Management - Dynamic menu configuration
- Operation Log - Audit trail
- System Config - Parameter configuration
- Data Dictionary - Enum value management
Technical Features
- RBAC permission model
- Operation log recording
- Data permission filtering
- Dynamic menu generation
- Configuration hot reload
Complete Directory Tree
app/
├── controller/
│ └── admin/
│ ├── UserController.php # User Management
│ ├── RoleController.php # Role Management
│ ├── PermissionController.php # Permission Management
│ ├── MenuController.php # Menu Management
│ ├── LogController.php # Log Query
│ ├── ConfigController.php # System Config
│ └── DashboardController.php # Dashboard
│
├── model/
│ └── eloquent/
│ ├── User.php # User Model
│ ├── Role.php # Role Model
│ ├── Permission.php # Permission Model
│ ├── Menu.php # Menu Model
│ ├── OperationLog.php # Operation Log Model
│ └── SystemConfig.php # System Config Model
│
├── service/
│ ├── user/
│ │ ├── CreateUserService.php # Create User
│ │ ├── UpdateUserService.php # Update User
│ │ ├── DeleteUserService.php # Delete User
│ │ └── AssignRoleService.php # Assign Role
│ ├── role/
│ │ ├── CreateRoleService.php # Create Role
│ │ ├── AssignPermissionService.php # Assign Permission
│ │ └── DeleteRoleService.php # Delete Role
│ ├── permission/
│ │ └── CheckPermissionService.php # Permission Check
│ ├── menu/
│ │ ├── BuildMenuTreeService.php # Build Menu Tree
│ │ └── SyncMenuService.php # Sync Menu
│ └── log/
│ └── RecordOperationLogService.php # Record Operation Log
│
├── domain/
│ ├── user/
│ │ ├── entity/
│ │ │ └── User.php # User Entity
│ │ ├── enum/ # Enums
│ │ │ └── UserStatus.php # User Status
│ │ ├── vo/ # Value Objects
│ │ │ ├── Username.php # Username
│ │ │ └── Password.php # Password
│ │ ├── event/
│ │ │ ├── UserCreated.php
│ │ │ ├── UserDeleted.php
│ │ │ └── UserRoleChanged.php
│ │ └── rule/
│ │ └── UserDeletionRule.php # Deletion Rule
│ │
│ ├── role/
│ │ ├── entity/
│ │ │ └── Role.php # Role Entity
│ │ ├── enum/ # Enums
│ │ │ └── RoleType.php # Role Type
│ │ ├── vo/ # Value Objects
│ │ │ └── RoleCode.php # Role Code
│ │ └── rule/
│ │ └── RoleAssignmentRule.php # Role Assignment Rule
│ │
│ ├── permission/
│ │ ├── entity/
│ │ │ └── Permission.php # Permission Entity
│ │ ├── enum/ # Enums
│ │ │ └── PermissionType.php # Permission Type
│ │ ├── vo/ # Value Objects
│ │ │ └── PermissionCode.php # Permission Code
│ │ └── rule/
│ │ └── PermissionCheckRule.php # Permission Check Rule
│ │
│ ├── menu/
│ │ ├── entity/
│ │ │ └── Menu.php # Menu Entity
│ │ ├── vo/ # Value Objects
│ │ │ ├── MenuPath.php # Menu Path
│ │ │ └── MenuIcon.php # Menu Icon
│ │ └── rule/
│ │ └── MenuHierarchyRule.php # Menu Hierarchy Rule
│ │
│ └── log/
│ ├── entity/
│ │ └── OperationLog.php # Operation Log Entity
│ ├── enum/ # Enums
│ │ └── OperationType.php # Operation Type
│ └── vo/ # Value Objects
│ └── IpAddress.php # IP Address
│
├── contract/
│ ├── repository/
│ │ ├── UserRepositoryInterface.php
│ │ ├── RoleRepositoryInterface.php
│ │ ├── PermissionRepositoryInterface.php
│ │ ├── MenuRepositoryInterface.php
│ │ └── OperationLogRepositoryInterface.php
│ └── service/
│ ├── PasswordHasherInterface.php # Password Hasher Interface
│ └── PermissionCheckerInterface.php # Permission Checker Interface
│
├── infrastructure/
│ ├── repository/
│ │ └── eloquent/
│ │ ├── EloquentUserRepository.php
│ │ ├── EloquentRoleRepository.php
│ │ ├── EloquentPermissionRepository.php
│ │ ├── EloquentMenuRepository.php
│ │ └── EloquentOperationLogRepository.php
│ ├── service/
│ │ ├── BcryptPasswordHasher.php # Bcrypt Password Hasher
│ │ └── RbacPermissionChecker.php # RBAC Permission Checker
│ └── cache/
│ ├── RedisPermissionCache.php # Permission Cache
│ └── RedisMenuCache.php # Menu Cache
│
├── middleware/
│ ├── auth/
│ │ ├── AuthenticateMiddleware.php # Authentication Middleware
│ │ └── CheckPermissionMiddleware.php # Permission Check Middleware
│ └── log/
│ └── OperationLogMiddleware.php # Operation Log Middleware
│
├── process/
│ └── task/
│ ├── CleanupOldLogsTask.php # Cleanup Old Logs
│ └── SyncPermissionCacheTask.php # Sync Permission Cache
│
└── support/
├── exception/
│ ├── UserNotFoundException.php
│ ├── PermissionDeniedException.php
│ └── InvalidPasswordException.php
└── helper/
└── permission_helper.phpModule Breakdown
1. User Management Module
Features: User CRUD, status management, role assignment
Core Classes:
domain/user/entity/User.php- User entityservice/user/CreateUserService.php- Create user serviceservice/user/AssignRoleService.php- Role assignment service
2. Role & Permission Module
Features: RBAC permission control, role management, permission assignment
Core Classes:
domain/role/entity/Role.php- Role entitydomain/permission/entity/Permission.php- Permission entityservice/permission/CheckPermissionService.php- Permission check service
3. Menu Management Module
Features: Dynamic menu, menu tree building, permission association
Core Classes:
domain/menu/entity/Menu.php- Menu entityservice/menu/BuildMenuTreeService.php- Menu tree building service
4. Operation Log Module
Features: Operation recording, audit trail, log query
Core Classes:
domain/log/entity/OperationLog.php- Operation log entityservice/log/RecordOperationLogService.php- Log recording service
5. System Config Module
Features: Parameter configuration, configuration hot reload
Core Classes:
domain/config/entity/SystemConfig.php- System config entity
Key Code Examples
1. User Entity
php
<?php
declare(strict_types=1);
namespace app\domain\user\entity;
use app\domain\user\enum\UserStatus;
use app\domain\user\vo\Username;
use app\domain\user\vo\Password;
use app\domain\user\event\UserCreated;
use app\domain\user\event\UserRoleChanged;
use app\domain\user\exception\InvalidUserOperationException;
final class User
{
private array $domainEvents = [];
private function __construct(
private readonly int $id,
private Username $username,
private Password $password,
private string $email,
private UserStatus $status,
private array $roleIds,
private readonly \DateTimeImmutable $createdAt,
private \DateTimeImmutable $updatedAt
) {
}
public static function create(
Username $username,
Password $password,
string $email
): self {
$user = new self(
id: 0,
username: $username,
password: $password,
email: $email,
status: UserStatus::Active,
roleIds: [],
createdAt: new \DateTimeImmutable(),
updatedAt: new \DateTimeImmutable()
);
$user->recordEvent(new UserCreated($user));
return $user;
}
public function assignRoles(array $roleIds): void
{
$this->roleIds = $roleIds;
$this->recordEvent(new UserRoleChanged($this, $roleIds));
}
public function activate(): void
{
if ($this->status->isActive()) {
throw new InvalidUserOperationException('User is already active');
}
$this->status = UserStatus::Active;
}
public function deactivate(): void
{
if ($this->status->isInactive()) {
throw new InvalidUserOperationException('User is already inactive');
}
$this->status = UserStatus::Inactive;
}
public function changePassword(Password $newPassword): void
{
$this->password = $newPassword;
$this->updatedAt = new \DateTimeImmutable();
}
public function hasRole(int $roleId): bool
{
return in_array($roleId, $this->roleIds, true);
}
// Getters
public function id(): int
{
return $this->id;
}
public function username(): Username
{
return $this->username;
}
public function email(): string
{
return $this->email;
}
public function status(): UserStatus
{
return $this->status;
}
public function roleIds(): array
{
return $this->roleIds;
}
private function recordEvent(object $event): void
{
$this->domainEvents[] = $event;
}
public function releaseEvents(): array
{
$events = $this->domainEvents;
$this->domainEvents = [];
return $events;
}
}2. Check Permission Service
php
<?php
declare(strict_types=1);
namespace app\service\permission;
use app\contract\repository\UserRepositoryInterface;
use app\contract\repository\RoleRepositoryInterface;
use app\contract\repository\PermissionRepositoryInterface;
use app\infrastructure\cache\RedisPermissionCache;
final class CheckPermissionService
{
public function __construct(
private readonly UserRepositoryInterface $userRepository,
private readonly RoleRepositoryInterface $roleRepository,
private readonly PermissionRepositoryInterface $permissionRepository,
private readonly RedisPermissionCache $permissionCache
) {
}
/**
* Check if user has specified permission
*/
public function check(int $userId, string $permissionCode): bool
{
// 1. Try to get from cache
$cached = $this->permissionCache->getUserPermissions($userId);
if ($cached !== null) {
return in_array($permissionCode, $cached, true);
}
// 2. Get user
$user = $this->userRepository->findById($userId);
if ($user === null) {
return false;
}
// 3. Get all user roles
$roles = $this->roleRepository->findByIds($user->roleIds());
// 4. Collect all permissions
$permissionIds = [];
foreach ($roles as $role) {
$permissionIds = array_merge($permissionIds, $role->permissionIds());
}
$permissionIds = array_unique($permissionIds);
// 5. Get permission details
$permissions = $this->permissionRepository->findByIds($permissionIds);
$permissionCodes = array_map(
fn ($permission) => $permission->code()->value(),
$permissions
);
// 6. Cache permission list
$this->permissionCache->setUserPermissions($userId, $permissionCodes);
return in_array($permissionCode, $permissionCodes, true);
}
/**
* Check if user has any of the specified permissions
*/
public function checkAny(int $userId, array $permissionCodes): bool
{
foreach ($permissionCodes as $code) {
if ($this->check($userId, $code)) {
return true;
}
}
return false;
}
/**
* Check if user has all specified permissions
*/
public function checkAll(int $userId, array $permissionCodes): bool
{
foreach ($permissionCodes as $code) {
if (!$this->check($userId, $code)) {
return false;
}
}
return true;
}
}3. Build Menu Tree Service
php
<?php
declare(strict_types=1);
namespace app\service\menu;
use app\contract\repository\MenuRepositoryInterface;
use app\service\permission\CheckPermissionService;
use app\infrastructure\cache\RedisMenuCache;
final class BuildMenuTreeService
{
public function __construct(
private readonly MenuRepositoryInterface $menuRepository,
private readonly CheckPermissionService $permissionService,
private readonly RedisMenuCache $menuCache
) {
}
/**
* Build menu tree for user
*/
public function buildForUser(int $userId): array
{
// 1. Try to get from cache
$cached = $this->menuCache->getUserMenuTree($userId);
if ($cached !== null) {
return $cached;
}
// 2. Get all menus
$allMenus = $this->menuRepository->findAll();
// 3. Filter menus user has permission for
$authorizedMenus = array_filter(
$allMenus,
fn ($menu) => $this->isAuthorized($userId, $menu)
);
// 4. Build tree structure
$tree = $this->buildTree($authorizedMenus);
// 5. Cache result
$this->menuCache->setUserMenuTree($userId, $tree);
return $tree;
}
/**
* Check if user is authorized for menu
*/
private function isAuthorized(int $userId, object $menu): bool
{
// If menu has no permission requirement, visible to all
if ($menu->permissionCode() === null) {
return true;
}
return $this->permissionService->check($userId, $menu->permissionCode());
}
/**
* Build tree structure
*/
private function buildTree(array $menus, ?int $parentId = null): array
{
$tree = [];
foreach ($menus as $menu) {
if ($menu->parentId() === $parentId) {
$children = $this->buildTree($menus, $menu->id());
$tree[] = [
'id' => $menu->id(),
'name' => $menu->name(),
'path' => $menu->path()->value(),
'icon' => $menu->icon()->value(),
'sort' => $menu->sort(),
'children' => $children,
];
}
}
// Sort by sort field
usort($tree, fn ($a, $b) => $a['sort'] <=> $b['sort']);
return $tree;
}
}4. Operation Log Middleware
php
<?php
declare(strict_types=1);
namespace app\middleware\log;
use app\service\log\RecordOperationLogService;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
final class OperationLogMiddleware implements MiddlewareInterface
{
public function __construct(
private readonly RecordOperationLogService $logService
) {
}
public function process(Request $request, callable $next): Response
{
$startTime = microtime(true);
// Execute request
$response = $next($request);
// Record log (async)
$this->recordLog($request, $response, $startTime);
return $response;
}
private function recordLog(Request $request, Response $response, float $startTime): void
{
// Only record modification operations
if (!in_array($request->method(), ['POST', 'PUT', 'DELETE', 'PATCH'], true)) {
return;
}
$duration = (int) ((microtime(true) - $startTime) * 1000);
// Record log asynchronously
go(function () use ($request, $response, $duration) {
try {
$this->logService->handle(
userId: $request->user()?->id ?? 0,
method: $request->method(),
path: $request->path(),
params: $request->all(),
ip: $request->getRealIp(),
userAgent: $request->header('user-agent', ''),
statusCode: $response->getStatusCode(),
duration: $duration
);
} catch (\Exception $e) {
logger()->error('Failed to record operation log', [
'error' => $e->getMessage(),
]);
}
});
}
}5. User Controller
php
<?php
declare(strict_types=1);
namespace app\controller\admin;
use app\service\user\CreateUserService;
use app\service\user\UpdateUserService;
use app\service\user\DeleteUserService;
use app\service\user\AssignRoleService;
use app\contract\repository\UserRepositoryInterface;
use support\Request;
use support\Response;
final class UserController
{
public function __construct(
private readonly CreateUserService $createUserService,
private readonly UpdateUserService $updateUserService,
private readonly DeleteUserService $deleteUserService,
private readonly AssignRoleService $assignRoleService,
private readonly UserRepositoryInterface $userRepository
) {
}
/**
* User list
*/
public function index(Request $request): Response
{
$page = (int) $request->get('page', 1);
$pageSize = (int) $request->get('page_size', 20);
$keyword = $request->get('keyword', '');
$result = $this->userRepository->paginate($page, $pageSize, $keyword);
return json([
'success' => true,
'data' => [
'items' => array_map(
fn ($user) => [
'id' => $user->id(),
'username' => $user->username()->value(),
'email' => $user->email(),
'status' => $user->status()->value(),
'created_at' => $user->createdAt()->format('Y-m-d H:i:s'),
],
$result['items']
),
'total' => $result['total'],
'page' => $page,
'page_size' => $pageSize,
],
]);
}
/**
* Create user
*/
public function create(Request $request): Response
{
$validated = $this->validate($request, [
'username' => 'required|string|min:3|max:20',
'password' => 'required|string|min:6',
'email' => 'required|email',
'role_ids' => 'array',
]);
$user = $this->createUserService->handle(
username: $validated['username'],
password: $validated['password'],
email: $validated['email'],
roleIds: $validated['role_ids'] ?? []
);
return json([
'success' => true,
'data' => [
'id' => $user->id(),
'username' => $user->username()->value(),
],
]);
}
/**
* Update user
*/
public function update(Request $request, int $id): Response
{
$validated = $this->validate($request, [
'email' => 'email',
'status' => 'in:active,inactive',
]);
$this->updateUserService->handle($id, $validated);
return json([
'success' => true,
'message' => 'User updated successfully',
]);
}
/**
* Delete user
*/
public function delete(Request $request, int $id): Response
{
$this->deleteUserService->handle($id);
return json([
'success' => true,
'message' => 'User deleted successfully',
]);
}
/**
* Assign roles
*/
public function assignRoles(Request $request, int $id): Response
{
$validated = $this->validate($request, [
'role_ids' => 'required|array',
]);
$this->assignRoleService->handle($id, $validated['role_ids']);
return json([
'success' => true,
'message' => 'Roles assigned successfully',
]);
}
}6. Check Permission Middleware
php
<?php
declare(strict_types=1);
namespace app\middleware\auth;
use app\service\permission\CheckPermissionService;
use app\support\exception\PermissionDeniedException;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
final class CheckPermissionMiddleware implements MiddlewareInterface
{
public function __construct(
private readonly CheckPermissionService $permissionService
) {
}
public function process(Request $request, callable $next): Response
{
// Get required permission from route
$permission = $request->route?->getPermission();
if ($permission === null) {
return $next($request);
}
// Check user permission
$user = $request->user();
if ($user === null) {
throw new PermissionDeniedException('User not authenticated');
}
if (!$this->permissionService->check($user->id, $permission)) {
throw new PermissionDeniedException("Permission denied: {$permission}");
}
return $next($request);
}
}Route Configuration Example
php
<?php
// config/route.php
use app\controller\admin\UserController;
use app\controller\admin\RoleController;
use app\middleware\auth\AuthenticateMiddleware;
use app\middleware\auth\CheckPermissionMiddleware;
use app\middleware\log\OperationLogMiddleware;
Route::group('/admin', function () {
// User Management
Route::get('/users', [UserController::class, 'index'])
->permission('user.list');
Route::post('/users', [UserController::class, 'create'])
->permission('user.create');
Route::put('/users/{id}', [UserController::class, 'update'])
->permission('user.update');
Route::delete('/users/{id}', [UserController::class, 'delete'])
->permission('user.delete');
Route::post('/users/{id}/roles', [UserController::class, 'assignRoles'])
->permission('user.assign_role');
// Role Management
Route::get('/roles', [RoleController::class, 'index'])
->permission('role.list');
Route::post('/roles', [RoleController::class, 'create'])
->permission('role.create');
})->middleware([
AuthenticateMiddleware::class,
CheckPermissionMiddleware::class,
OperationLogMiddleware::class,
]);Dependency Injection Configuration
php
<?php
// config/container.php
use app\contract\repository\UserRepositoryInterface;
use app\contract\repository\RoleRepositoryInterface;
use app\contract\repository\PermissionRepositoryInterface;
use app\contract\service\PasswordHasherInterface;
use app\infrastructure\repository\eloquent\EloquentUserRepository;
use app\infrastructure\repository\eloquent\EloquentRoleRepository;
use app\infrastructure\repository\eloquent\EloquentPermissionRepository;
use app\infrastructure\service\BcryptPasswordHasher;
return [
UserRepositoryInterface::class => EloquentUserRepository::class,
RoleRepositoryInterface::class => EloquentRoleRepository::class,
PermissionRepositoryInterface::class => EloquentPermissionRepository::class,
PasswordHasherInterface::class => BcryptPasswordHasher::class,
];Best Practices
1. RBAC Permission Model
- User -> Role -> Permission
- Support multiple roles
- Permission cache optimization
2. Operation Log
- Record all modification operations
- Async log writing
- Periodic cleanup of old logs
3. Data Permission
- Department data isolation
- Row-level permission control
4. Menu Management
- Dynamic menu generation
- Permission association
- Menu caching
5. Security
- Encrypted password storage
- Login failure limiting
- Session management