初始化项目
This commit is contained in:
67
app/Exports/DepartmentExport.php
Normal file
67
app/Exports/DepartmentExport.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\Auth\Department;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
|
||||
class DepartmentExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize
|
||||
{
|
||||
protected $departmentIds;
|
||||
|
||||
public function __construct(array $departmentIds = [])
|
||||
{
|
||||
$this->departmentIds = $departmentIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据集合
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
$query = Department::query();
|
||||
|
||||
if (!empty($this->departmentIds)) {
|
||||
$query->whereIn('id', $this->departmentIds);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表头
|
||||
*/
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'ID',
|
||||
'部门名称',
|
||||
'上级部门',
|
||||
'负责人',
|
||||
'联系电话',
|
||||
'排序',
|
||||
'状态',
|
||||
'创建时间',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射数据
|
||||
*/
|
||||
public function map($department): array
|
||||
{
|
||||
return [
|
||||
$department->id,
|
||||
$department->name,
|
||||
$department->parent_id ? Department::find($department->parent_id)?->name : '',
|
||||
$department->leader,
|
||||
$department->phone,
|
||||
$department->sort,
|
||||
$department->status == 1 ? '启用' : '禁用',
|
||||
$department->created_at ? $department->created_at->toDateTimeString() : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
34
app/Exports/GenericExport.php
Normal file
34
app/Exports/GenericExport.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
|
||||
class GenericExport implements FromCollection, WithHeadings
|
||||
{
|
||||
protected $data;
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据集合
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return collect($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表头
|
||||
*/
|
||||
public function headings(): array
|
||||
{
|
||||
// 第一行作为表头
|
||||
return $this->data[0] ?? [];
|
||||
}
|
||||
}
|
||||
71
app/Exports/UserExport.php
Normal file
71
app/Exports/UserExport.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
|
||||
class UserExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize
|
||||
{
|
||||
protected $userIds;
|
||||
|
||||
public function __construct(array $userIds = [])
|
||||
{
|
||||
$this->userIds = $userIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据集合
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
$query = User::with(['department', 'roles']);
|
||||
|
||||
if (!empty($this->userIds)) {
|
||||
$query->whereIn('id', $this->userIds);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表头
|
||||
*/
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'ID',
|
||||
'用户名',
|
||||
'真实姓名',
|
||||
'邮箱',
|
||||
'手机号',
|
||||
'部门',
|
||||
'角色',
|
||||
'状态',
|
||||
'最后登录时间',
|
||||
'创建时间',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射数据
|
||||
*/
|
||||
public function map($user): array
|
||||
{
|
||||
return [
|
||||
$user->id,
|
||||
$user->username,
|
||||
$user->real_name,
|
||||
$user->email,
|
||||
$user->phone,
|
||||
$user->department ? $user->department->name : '',
|
||||
$user->roles->pluck('name')->implode(','),
|
||||
$user->status == 1 ? '启用' : '禁用',
|
||||
$user->last_login_at ? $user->last_login_at->toDateTimeString() : '',
|
||||
$user->created_at ? $user->created_at->toDateTimeString() : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
184
app/Http/Controllers/Auth/Admin/Auth.php
Normal file
184
app/Http/Controllers/Auth/Admin/Auth.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Auth\AuthService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Exception;
|
||||
|
||||
class Auth extends Controller
|
||||
{
|
||||
protected $authService;
|
||||
|
||||
public function __construct(AuthService $authService)
|
||||
{
|
||||
$this->authService = $authService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
$result = $this->authService->login($validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '登录成功',
|
||||
'data' => $result,
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => $e->errors(),
|
||||
], 422);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 500,
|
||||
'message' => '登录失败:' . $e->getMessage(),
|
||||
'data' => null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登出
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
try {
|
||||
$this->authService->logout();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '登出成功',
|
||||
'data' => null,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 500,
|
||||
'message' => '登出失败:' . $e->getMessage(),
|
||||
'data' => null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
public function refresh(Request $request)
|
||||
{
|
||||
try {
|
||||
$result = $this->authService->refresh();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '刷新成功',
|
||||
'data' => $result,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 401,
|
||||
'message' => 'Token无效或已过期',
|
||||
'data' => null,
|
||||
], 401);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
public function me(Request $request)
|
||||
{
|
||||
try {
|
||||
$result = $this->authService->me();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 401,
|
||||
'message' => '未登录或token已过期',
|
||||
'data' => null,
|
||||
], 401);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 找回密码
|
||||
*/
|
||||
public function resetPassword(Request $request)
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string|min:6|confirmed',
|
||||
]);
|
||||
|
||||
$this->authService->resetPassword($validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '密码重置成功',
|
||||
'data' => null,
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => $e->errors(),
|
||||
], 422);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 500,
|
||||
'message' => '密码重置失败:' . $e->getMessage(),
|
||||
'data' => null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
public function changePassword(Request $request)
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'old_password' => 'required|string',
|
||||
'password' => 'required|string|min:6|confirmed',
|
||||
]);
|
||||
|
||||
$this->authService->changePassword($validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '密码修改成功',
|
||||
'data' => null,
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => $e->errors(),
|
||||
], 422);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 500,
|
||||
'message' => '密码修改失败:' . $e->getMessage(),
|
||||
'data' => null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
228
app/Http/Controllers/Auth/Admin/Department.php
Normal file
228
app/Http/Controllers/Auth/Admin/Department.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Auth\DepartmentService;
|
||||
use App\Services\Auth\ImportExportService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Department extends Controller
|
||||
{
|
||||
protected $departmentService;
|
||||
protected $importExportService;
|
||||
|
||||
public function __construct(
|
||||
DepartmentService $departmentService,
|
||||
ImportExportService $importExportService
|
||||
) {
|
||||
$this->departmentService = $departmentService;
|
||||
$this->importExportService = $importExportService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门列表
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$params = $request->all();
|
||||
$result = $this->departmentService->getList($params);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门树
|
||||
*/
|
||||
public function tree()
|
||||
{
|
||||
$result = $this->departmentService->getTree();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => ['tree' => $result],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有部门(不分页)
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
$result = $this->departmentService->getAll();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => ['list' => $result],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门详情
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$result = $this->departmentService->getById($id);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:50',
|
||||
'parent_id' => 'nullable|integer|exists:auth_departments,id',
|
||||
'leader' => 'nullable|string|max:50',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'sort' => 'nullable|integer|min:0',
|
||||
'status' => 'nullable|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$result = $this->departmentService->create($validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '创建成功',
|
||||
'data' => ['id' => $result->id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新部门
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'nullable|string|max:50',
|
||||
'parent_id' => 'nullable|integer|exists:auth_departments,id',
|
||||
'leader' => 'nullable|string|max:50',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'sort' => 'nullable|integer|min:0',
|
||||
'status' => 'nullable|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$result = $this->departmentService->update($id, $validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '更新成功',
|
||||
'data' => ['id' => $result->id],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->departmentService->delete($id);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除部门
|
||||
*/
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
]);
|
||||
|
||||
$count = $this->departmentService->batchDelete($validated['ids']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "成功删除 {$count} 条数据",
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新部门状态
|
||||
*/
|
||||
public function batchUpdateStatus(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
'status' => 'required|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$count = $this->departmentService->batchUpdateStatus($validated['ids'], $validated['status']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "成功更新 {$count} 条数据",
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出部门
|
||||
*/
|
||||
public function export(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'nullable|array',
|
||||
'ids.*' => 'integer',
|
||||
]);
|
||||
|
||||
$filename = $this->importExportService->exportDepartments($validated['ids'] ?? []);
|
||||
|
||||
$filePath = $this->importExportService->getExportFilePath($filename);
|
||||
|
||||
return response()->download($filePath, $filename)->deleteFileAfterSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入部门
|
||||
*/
|
||||
public function import(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'file' => 'required|file|mimes:xlsx,xls',
|
||||
]);
|
||||
|
||||
$file = $request->file('file');
|
||||
$realPath = $file->getRealPath();
|
||||
$filename = $file->getClientOriginalName();
|
||||
|
||||
$result = $this->importExportService->importDepartments($filename, $realPath);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "导入完成,成功 {$result['success_count']} 条,失败 {$result['error_count']} 条",
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载部门导入模板
|
||||
*/
|
||||
public function downloadTemplate()
|
||||
{
|
||||
$filename = $this->importExportService->downloadDepartmentTemplate();
|
||||
$filePath = $this->importExportService->getExportFilePath($filename);
|
||||
|
||||
return response()->download($filePath, $filename)->deleteFileAfterSend();
|
||||
}
|
||||
}
|
||||
182
app/Http/Controllers/Auth/Admin/Permission.php
Normal file
182
app/Http/Controllers/Auth/Admin/Permission.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Auth\PermissionService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Permission extends Controller
|
||||
{
|
||||
protected $permissionService;
|
||||
|
||||
public function __construct(PermissionService $permissionService)
|
||||
{
|
||||
$this->permissionService = $permissionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限列表
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$params = $request->all();
|
||||
$result = $this->permissionService->getList($params);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限树
|
||||
*/
|
||||
public function tree(Request $request)
|
||||
{
|
||||
$params = $request->all();
|
||||
$result = $this->permissionService->getTree($params);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => ['tree' => $result],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单树
|
||||
*/
|
||||
public function menu(Request $request)
|
||||
{
|
||||
$result = $this->permissionService->getMenuTree();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => ['tree' => $result],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限详情
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$result = $this->permissionService->getById($id);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建权限
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:50',
|
||||
'code' => 'required|string|max:100|unique:auth_permissions,code',
|
||||
'type' => 'required|in:menu,api,button',
|
||||
'route' => 'nullable|string|max:200',
|
||||
'component' => 'nullable|string|max:200',
|
||||
'icon' => 'nullable|string|max:50',
|
||||
'parent_id' => 'nullable|integer|exists:auth_permissions,id',
|
||||
'sort' => 'nullable|integer|min:0',
|
||||
'status' => 'nullable|integer|in:0,1',
|
||||
'meta' => 'nullable|array',
|
||||
]);
|
||||
|
||||
$result = $this->permissionService->create($validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '创建成功',
|
||||
'data' => ['id' => $result->id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新权限
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'nullable|string|max:50',
|
||||
'code' => 'nullable|string|max:100|unique:auth_permissions,code,' . $id,
|
||||
'type' => 'nullable|in:menu,api,button',
|
||||
'route' => 'nullable|string|max:200',
|
||||
'component' => 'nullable|string|max:200',
|
||||
'icon' => 'nullable|string|max:50',
|
||||
'parent_id' => 'nullable|integer|exists:auth_permissions,id',
|
||||
'sort' => 'nullable|integer|min:0',
|
||||
'status' => 'nullable|integer|in:0,1',
|
||||
'meta' => 'nullable|array',
|
||||
]);
|
||||
|
||||
$result = $this->permissionService->update($id, $validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '更新成功',
|
||||
'data' => ['id' => $result->id],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除权限
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->permissionService->delete($id);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除权限
|
||||
*/
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
]);
|
||||
|
||||
$count = $this->permissionService->batchDelete($validated['ids']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "成功删除 {$count} 条数据",
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新权限状态
|
||||
*/
|
||||
public function batchUpdateStatus(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
'status' => 'required|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$count = $this->permissionService->batchUpdateStatus($validated['ids'], $validated['status']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "成功更新 {$count} 条数据",
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
}
|
||||
240
app/Http/Controllers/Auth/Admin/Role.php
Normal file
240
app/Http/Controllers/Auth/Admin/Role.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Auth\RoleService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Role extends Controller
|
||||
{
|
||||
protected $roleService;
|
||||
|
||||
public function __construct(RoleService $roleService)
|
||||
{
|
||||
$this->roleService = $roleService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色列表
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$params = $request->all();
|
||||
$result = $this->roleService->getList($params);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有角色(不分页)
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
$result = $this->roleService->getAll();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => ['list' => $result],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色详情
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$result = $this->roleService->getById($id);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建角色
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:50',
|
||||
'code' => 'required|string|max:50|unique:auth_roles,code',
|
||||
'description' => 'nullable|string|max:200',
|
||||
'sort' => 'nullable|integer|min:0',
|
||||
'status' => 'nullable|integer|in:0,1',
|
||||
'permission_ids' => 'nullable|array',
|
||||
'permission_ids.*' => 'integer|exists:auth_permissions,id',
|
||||
]);
|
||||
|
||||
$result = $this->roleService->create($validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '创建成功',
|
||||
'data' => ['id' => $result->id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'nullable|string|max:50',
|
||||
'code' => 'nullable|string|max:50|unique:auth_roles,code,' . $id,
|
||||
'description' => 'nullable|string|max:200',
|
||||
'sort' => 'nullable|integer|min:0',
|
||||
'status' => 'nullable|integer|in:0,1',
|
||||
'permission_ids' => 'nullable|array',
|
||||
'permission_ids.*' => 'integer|exists:auth_permissions,id',
|
||||
]);
|
||||
|
||||
$result = $this->roleService->update($id, $validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '更新成功',
|
||||
'data' => ['id' => $result->id],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->roleService->delete($id);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除角色
|
||||
*/
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
]);
|
||||
|
||||
$count = $this->roleService->batchDelete($validated['ids']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "成功删除 {$count} 条数据",
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新角色状态
|
||||
*/
|
||||
public function batchUpdateStatus(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
'status' => 'required|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$count = $this->roleService->batchUpdateStatus($validated['ids'], $validated['status']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "成功更新 {$count} 条数据",
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配权限
|
||||
*/
|
||||
public function assignPermissions(Request $request, $id)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'permission_ids' => 'required|array',
|
||||
'permission_ids.*' => 'integer|exists:auth_permissions,id',
|
||||
]);
|
||||
|
||||
$this->roleService->assignPermissions($id, $validated['permission_ids']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '权限分配成功',
|
||||
'data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色的权限列表
|
||||
*/
|
||||
public function getPermissions($id)
|
||||
{
|
||||
$result = $this->roleService->getPermissions($id);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => ['tree' => $result],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制角色
|
||||
*/
|
||||
public function copy(Request $request, $id)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:50',
|
||||
'code' => 'required|string|max:50|unique:auth_roles,code',
|
||||
'description' => 'nullable|string|max:200',
|
||||
'sort' => 'nullable|integer|min:0',
|
||||
'status' => 'nullable|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$result = $this->roleService->copy($id, $validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '复制成功',
|
||||
'data' => ['id' => $result->id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量复制角色
|
||||
*/
|
||||
public function batchCopy(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
'name' => 'nullable|string|max:50',
|
||||
'code' => 'nullable|string|max:50',
|
||||
'description' => 'nullable|string|max:200',
|
||||
'sort' => 'nullable|integer|min:0',
|
||||
'status' => 'nullable|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$result = $this->roleService->batchCopy($validated['ids'], $validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "复制完成,成功 {$result['success_count']} 个,失败 {$result['error_count']} 个",
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
}
|
||||
329
app/Http/Controllers/Auth/Admin/User.php
Normal file
329
app/Http/Controllers/Auth/Admin/User.php
Normal file
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Auth\UserService;
|
||||
use App\Services\Auth\UserOnlineService;
|
||||
use App\Services\Auth\ImportExportService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class User extends Controller
|
||||
{
|
||||
protected $userService;
|
||||
protected $userOnlineService;
|
||||
protected $importExportService;
|
||||
|
||||
public function __construct(
|
||||
UserService $userService,
|
||||
UserOnlineService $userOnlineService,
|
||||
ImportExportService $importExportService
|
||||
) {
|
||||
$this->userService = $userService;
|
||||
$this->userOnlineService = $userOnlineService;
|
||||
$this->importExportService = $importExportService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$params = $request->all();
|
||||
$result = $this->userService->getList($params);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
*/
|
||||
public function show(Request $request, $id)
|
||||
{
|
||||
$result = $this->userService->getById($id);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'username' => 'required|string|max:50|unique:auth_users,username',
|
||||
'password' => 'required|string|min:6',
|
||||
'real_name' => 'required|string|max:50',
|
||||
'email' => 'nullable|email|unique:auth_users,email',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'department_id' => 'nullable|integer|exists:auth_departments,id',
|
||||
'role_ids' => 'nullable|array',
|
||||
'role_ids.*' => 'integer|exists:auth_roles,id',
|
||||
'status' => 'nullable|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$result = $this->userService->create($validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '创建成功',
|
||||
'data' => ['id' => $result->id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'username' => 'nullable|string|max:50|unique:auth_users,username,' . $id,
|
||||
'password' => 'nullable|string|min:6',
|
||||
'real_name' => 'nullable|string|max:50',
|
||||
'email' => 'nullable|email|unique:auth_users,email,' . $id,
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'department_id' => 'nullable|integer|exists:auth_departments,id',
|
||||
'role_ids' => 'nullable|array',
|
||||
'role_ids.*' => 'integer|exists:auth_roles,id',
|
||||
'status' => 'nullable|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$result = $this->userService->update($id, $validated);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '更新成功',
|
||||
'data' => ['id' => $result->id],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
public function destroy(Request $request, $id)
|
||||
{
|
||||
$this->userService->delete($id);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除用户
|
||||
*/
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
]);
|
||||
|
||||
$count = $this->userService->batchDelete($validated['ids']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "成功删除 {$count} 条数据",
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新用户状态
|
||||
*/
|
||||
public function batchUpdateStatus(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
'status' => 'required|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$count = $this->userService->batchUpdateStatus($validated['ids'], $validated['status']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "成功更新 {$count} 条数据",
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量分配部门
|
||||
*/
|
||||
public function batchAssignDepartment(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
'department_id' => 'nullable|integer|exists:auth_departments,id',
|
||||
]);
|
||||
|
||||
$count = $this->userService->batchAssignDepartment($validated['ids'], $validated['department_id']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "成功分配 {$count} 条数据",
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量分配角色
|
||||
*/
|
||||
public function batchAssignRoles(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
'role_ids' => 'nullable|array',
|
||||
'role_ids.*' => 'integer|exists:auth_roles,id',
|
||||
]);
|
||||
|
||||
$count = $this->userService->batchAssignRoles($validated['ids'], $validated['role_ids'] ?? []);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "成功分配 {$count} 条数据",
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用户
|
||||
*/
|
||||
public function export(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => 'nullable|array',
|
||||
'ids.*' => 'integer',
|
||||
]);
|
||||
|
||||
$filename = $this->importExportService->exportUsers($validated['ids'] ?? []);
|
||||
|
||||
$filePath = $this->importExportService->getExportFilePath($filename);
|
||||
|
||||
return response()->download($filePath, $filename)->deleteFileAfterSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入用户
|
||||
*/
|
||||
public function import(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'file' => 'required|file|mimes:xlsx,xls',
|
||||
]);
|
||||
|
||||
$file = $request->file('file');
|
||||
$realPath = $file->getRealPath();
|
||||
$filename = $file->getClientOriginalName();
|
||||
|
||||
$result = $this->importExportService->importUsers($filename, $realPath);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => "导入完成,成功 {$result['success_count']} 条,失败 {$result['error_count']} 条",
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载用户导入模板
|
||||
*/
|
||||
public function downloadTemplate()
|
||||
{
|
||||
$filename = $this->importExportService->downloadUserTemplate();
|
||||
$filePath = $this->importExportService->getExportFilePath($filename);
|
||||
|
||||
return response()->download($filePath, $filename)->deleteFileAfterSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线用户数量
|
||||
*/
|
||||
public function getOnlineCount()
|
||||
{
|
||||
$count = $this->userOnlineService->getOnlineCount();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => ['count' => $count],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线用户列表
|
||||
*/
|
||||
public function getOnlineUsers(Request $request)
|
||||
{
|
||||
$limit = $request->get('limit', 100);
|
||||
$users = $this->userOnlineService->getOnlineUsers($limit);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => ['list' => $users],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的所有会话
|
||||
*/
|
||||
public function getUserSessions($userId)
|
||||
{
|
||||
$sessions = $this->userOnlineService->getUserSessions($userId);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => ['sessions' => $sessions],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制用户下线(单个会话)
|
||||
*/
|
||||
public function setUserOffline($userId, Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'token' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if (!empty($validated['token'])) {
|
||||
$this->userOnlineService->setOffline($userId, $validated['token']);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '操作成功',
|
||||
'data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制用户所有设备下线
|
||||
*/
|
||||
public function setUserAllOffline($userId)
|
||||
{
|
||||
$this->userOnlineService->setAllOffline($userId);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '操作成功',
|
||||
'data' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
172
app/Http/Controllers/System/Admin/City.php
Normal file
172
app/Http/Controllers/System/Admin/City.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\System\CityService;
|
||||
|
||||
class City extends Controller
|
||||
{
|
||||
protected $cityService;
|
||||
|
||||
public function __construct(CityService $cityService)
|
||||
{
|
||||
$this->cityService = $cityService;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$result = $this->cityService->getList($request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result
|
||||
]);
|
||||
}
|
||||
|
||||
public function tree()
|
||||
{
|
||||
$tree = $this->cityService->getCachedTree();
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $tree
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
$city = $this->cityService->getById($id);
|
||||
if (!$city) {
|
||||
return response()->json([
|
||||
'code' => 404,
|
||||
'message' => '城市不存在',
|
||||
'data' => null
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $city
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
try {
|
||||
$city = $this->cityService->create($request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '创建成功',
|
||||
'data' => $city
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id)
|
||||
{
|
||||
try {
|
||||
$city = $this->cityService->update($id, $request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '更新成功',
|
||||
'data' => $city
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id)
|
||||
{
|
||||
try {
|
||||
$this->cityService->delete($id);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
$this->cityService->batchDelete($request->input('ids', []));
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function batchUpdateStatus(Request $request)
|
||||
{
|
||||
$this->cityService->batchUpdateStatus(
|
||||
$request->input('ids', []),
|
||||
$request->input('status', true)
|
||||
);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量更新状态成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function children(int $parentId)
|
||||
{
|
||||
$children = $this->cityService->getChildren($parentId);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $children
|
||||
]);
|
||||
}
|
||||
|
||||
public function provinces()
|
||||
{
|
||||
$provinces = $this->cityService->getProvinces();
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $provinces
|
||||
]);
|
||||
}
|
||||
|
||||
public function cities(int $provinceId)
|
||||
{
|
||||
$cities = $this->cityService->getCities($provinceId);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $cities
|
||||
]);
|
||||
}
|
||||
|
||||
public function districts(int $cityId)
|
||||
{
|
||||
$districts = $this->cityService->getDistricts($cityId);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $districts
|
||||
]);
|
||||
}
|
||||
}
|
||||
142
app/Http/Controllers/System/Admin/Config.php
Normal file
142
app/Http/Controllers/System/Admin/Config.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\System\ConfigService;
|
||||
|
||||
class Config extends Controller
|
||||
{
|
||||
protected $configService;
|
||||
|
||||
public function __construct(ConfigService $configService)
|
||||
{
|
||||
$this->configService = $configService;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$result = $this->configService->getList($request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
$config = $this->configService->getById($id);
|
||||
if (!$config) {
|
||||
return response()->json([
|
||||
'code' => 404,
|
||||
'message' => '配置不存在',
|
||||
'data' => null
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $config
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
try {
|
||||
$config = $this->configService->create($request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '创建成功',
|
||||
'data' => $config
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id)
|
||||
{
|
||||
try {
|
||||
$config = $this->configService->update($id, $request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '更新成功',
|
||||
'data' => $config
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id)
|
||||
{
|
||||
try {
|
||||
$this->configService->delete($id);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
$this->configService->batchDelete($request->input('ids', []));
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function batchUpdateStatus(Request $request)
|
||||
{
|
||||
$this->configService->batchUpdateStatus(
|
||||
$request->input('ids', []),
|
||||
$request->input('status', true)
|
||||
);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量更新状态成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function getGroups()
|
||||
{
|
||||
$groups = $this->configService->getGroups();
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $groups
|
||||
]);
|
||||
}
|
||||
|
||||
public function getByGroup(Request $request)
|
||||
{
|
||||
$configs = $this->configService->getByGroup($request->input('group'));
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $configs
|
||||
]);
|
||||
}
|
||||
}
|
||||
211
app/Http/Controllers/System/Admin/Dictionary.php
Normal file
211
app/Http/Controllers/System/Admin/Dictionary.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\System\DictionaryService;
|
||||
|
||||
class Dictionary extends Controller
|
||||
{
|
||||
protected $dictionaryService;
|
||||
|
||||
public function __construct(DictionaryService $dictionaryService)
|
||||
{
|
||||
$this->dictionaryService = $dictionaryService;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$result = $this->dictionaryService->getList($request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result
|
||||
]);
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
$dictionaries = $this->dictionaryService->getAll();
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $dictionaries
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
$dictionary = $this->dictionaryService->getById($id);
|
||||
if (!$dictionary) {
|
||||
return response()->json([
|
||||
'code' => 404,
|
||||
'message' => '字典不存在',
|
||||
'data' => null
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $dictionary
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
try {
|
||||
$dictionary = $this->dictionaryService->create($request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '创建成功',
|
||||
'data' => $dictionary
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id)
|
||||
{
|
||||
try {
|
||||
$dictionary = $this->dictionaryService->update($id, $request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '更新成功',
|
||||
'data' => $dictionary
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id)
|
||||
{
|
||||
try {
|
||||
$this->dictionaryService->delete($id);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
$this->dictionaryService->batchDelete($request->input('ids', []));
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function batchUpdateStatus(Request $request)
|
||||
{
|
||||
$this->dictionaryService->batchUpdateStatus(
|
||||
$request->input('ids', []),
|
||||
$request->input('status', true)
|
||||
);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量更新状态成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function getItemsList(Request $request)
|
||||
{
|
||||
$result = $this->dictionaryService->getItemsList($request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeItem(Request $request)
|
||||
{
|
||||
try {
|
||||
$item = $this->dictionaryService->createItem($request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '创建成功',
|
||||
'data' => $item
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateItem(Request $request, int $id)
|
||||
{
|
||||
try {
|
||||
$item = $this->dictionaryService->updateItem($id, $request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '更新成功',
|
||||
'data' => $item
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroyItem(int $id)
|
||||
{
|
||||
$this->dictionaryService->deleteItem($id);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function batchDeleteItems(Request $request)
|
||||
{
|
||||
$this->dictionaryService->batchDeleteItems($request->input('ids', []));
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function batchUpdateItemsStatus(Request $request)
|
||||
{
|
||||
$this->dictionaryService->batchUpdateItemsStatus(
|
||||
$request->input('ids', []),
|
||||
$request->input('status', true)
|
||||
);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量更新状态成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
}
|
||||
126
app/Http/Controllers/System/Admin/Log.php
Normal file
126
app/Http/Controllers/System/Admin/Log.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\LogRequest;
|
||||
use App\Services\System\LogService;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use App\Exports\GenericExport;
|
||||
|
||||
class Log extends Controller
|
||||
{
|
||||
protected $logService;
|
||||
|
||||
public function __construct(LogService $logService)
|
||||
{
|
||||
$this->logService = $logService;
|
||||
}
|
||||
|
||||
public function index(LogRequest $request)
|
||||
{
|
||||
$result = $this->logService->getList($request->validated());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result
|
||||
]);
|
||||
}
|
||||
|
||||
public function export(LogRequest $request)
|
||||
{
|
||||
$params = $request->validated();
|
||||
$pageSize = $params['page_size'] ?? 10000; // 导出时默认获取更多数据
|
||||
|
||||
// 获取所有符合条件的日志(不分页)
|
||||
$query = $this->logService->getListQuery($params);
|
||||
$logs = $query->limit($pageSize)->get();
|
||||
|
||||
// 准备导出数据
|
||||
$headers = [
|
||||
'ID', '用户名', '模块', '操作', '请求方法', 'URL', 'IP地址',
|
||||
'状态码', '状态', '错误信息', '执行时间(ms)', '创建时间'
|
||||
];
|
||||
|
||||
$data = [];
|
||||
foreach ($logs as $log) {
|
||||
$data[] = [
|
||||
$log->id,
|
||||
$log->username,
|
||||
$log->module,
|
||||
$log->action,
|
||||
$log->method,
|
||||
$log->url,
|
||||
$log->ip,
|
||||
$log->status_code,
|
||||
$log->status === 'success' ? '成功' : '失败',
|
||||
$log->error_message ?? '-',
|
||||
$log->execution_time,
|
||||
$log->created_at->format('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
$filename = '系统操作日志_' . date('YmdHis') . '.xlsx';
|
||||
|
||||
return Excel::download(new GenericExport($headers, $data), $filename);
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
$log = $this->logService->getById($id);
|
||||
if (!$log) {
|
||||
return response()->json([
|
||||
'code' => 404,
|
||||
'message' => '日志不存在',
|
||||
'data' => null
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $log
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$this->logService->delete($id);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
$this->logService->batchDelete($request->input('ids', []));
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function clearLogs(Request $request)
|
||||
{
|
||||
$days = $request->input('days', 30);
|
||||
$this->logService->clearLogs($days);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '清理成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function getStatistics(LogRequest $request)
|
||||
{
|
||||
$statistics = $this->logService->getStatistics($request->validated());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $statistics
|
||||
]);
|
||||
}
|
||||
}
|
||||
152
app/Http/Controllers/System/Admin/Task.php
Normal file
152
app/Http/Controllers/System/Admin/Task.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\System\TaskService;
|
||||
|
||||
class Task extends Controller
|
||||
{
|
||||
protected $taskService;
|
||||
|
||||
public function __construct(TaskService $taskService)
|
||||
{
|
||||
$this->taskService = $taskService;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$result = $this->taskService->getList($request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $result
|
||||
]);
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
$tasks = $this->taskService->getAll();
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $tasks
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
$task = $this->taskService->getById($id);
|
||||
if (!$task) {
|
||||
return response()->json([
|
||||
'code' => 404,
|
||||
'message' => '任务不存在',
|
||||
'data' => null
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $task
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
try {
|
||||
$task = $this->taskService->create($request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '创建成功',
|
||||
'data' => $task
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id)
|
||||
{
|
||||
try {
|
||||
$task = $this->taskService->update($id, $request->all());
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '更新成功',
|
||||
'data' => $task
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 422,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$this->taskService->delete($id);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
$this->taskService->batchDelete($request->input('ids', []));
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function batchUpdateStatus(Request $request)
|
||||
{
|
||||
$this->taskService->batchUpdateStatus(
|
||||
$request->input('ids', []),
|
||||
$request->input('status', true)
|
||||
);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量更新状态成功',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function run(int $id)
|
||||
{
|
||||
try {
|
||||
$result = $this->taskService->run($id);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '执行成功',
|
||||
'data' => $result
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function getStatistics()
|
||||
{
|
||||
$statistics = $this->taskService->getStatistics();
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $statistics
|
||||
]);
|
||||
}
|
||||
}
|
||||
152
app/Http/Controllers/System/Admin/Upload.php
Normal file
152
app/Http/Controllers/System/Admin/Upload.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\System\UploadService;
|
||||
|
||||
class Upload extends Controller
|
||||
{
|
||||
protected $uploadService;
|
||||
|
||||
public function __construct(UploadService $uploadService)
|
||||
{
|
||||
$this->uploadService = $uploadService;
|
||||
}
|
||||
|
||||
public function upload(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'file' => 'required|file|max:10240',
|
||||
]);
|
||||
|
||||
$file = $request->file('file');
|
||||
$directory = $request->input('directory', 'uploads');
|
||||
$options = [
|
||||
'compress' => $request->input('compress', false),
|
||||
'quality' => $request->input('quality', 80),
|
||||
'width' => $request->input('width'),
|
||||
'height' => $request->input('height'),
|
||||
];
|
||||
|
||||
$result = $this->uploadService->upload($file, $directory, $options);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '上传成功',
|
||||
'data' => $result
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function uploadMultiple(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'files' => 'required|array',
|
||||
'files.*' => 'file|max:10240',
|
||||
]);
|
||||
|
||||
$files = $request->file('files');
|
||||
$directory = $request->input('directory', 'uploads');
|
||||
$options = [
|
||||
'compress' => $request->input('compress', false),
|
||||
'quality' => $request->input('quality', 80),
|
||||
'width' => $request->input('width'),
|
||||
'height' => $request->input('height'),
|
||||
];
|
||||
|
||||
$results = $this->uploadService->uploadMultiple($files, $directory, $options);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '上传成功',
|
||||
'data' => $results
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function uploadBase64(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'base64' => 'required|string',
|
||||
]);
|
||||
|
||||
$base64 = $request->input('base64');
|
||||
$directory = $request->input('directory', 'uploads');
|
||||
$fileName = $request->input('file_name');
|
||||
|
||||
$result = $this->uploadService->uploadBase64($base64, $directory, $fileName);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '上传成功',
|
||||
'data' => $result
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'path' => 'required|string',
|
||||
]);
|
||||
|
||||
$path = $request->input('path');
|
||||
$this->uploadService->delete($path);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'paths' => 'required|array',
|
||||
]);
|
||||
|
||||
$paths = $request->input('paths', []);
|
||||
$this->uploadService->deleteMultiple($paths);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '批量删除成功',
|
||||
'data' => null
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
app/Http/Controllers/System/Api/City.php
Normal file
75
app/Http/Controllers/System/Api/City.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\System\CityService;
|
||||
|
||||
class City extends Controller
|
||||
{
|
||||
protected $cityService;
|
||||
|
||||
public function __construct(CityService $cityService)
|
||||
{
|
||||
$this->cityService = $cityService;
|
||||
}
|
||||
|
||||
public function tree()
|
||||
{
|
||||
$tree = $this->cityService->getCachedTree();
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $tree
|
||||
]);
|
||||
}
|
||||
|
||||
public function provinces()
|
||||
{
|
||||
$provinces = $this->cityService->getProvinces();
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $provinces
|
||||
]);
|
||||
}
|
||||
|
||||
public function cities(int $provinceId)
|
||||
{
|
||||
$cities = $this->cityService->getCities($provinceId);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $cities
|
||||
]);
|
||||
}
|
||||
|
||||
public function districts(int $cityId)
|
||||
{
|
||||
$districts = $this->cityService->getDistricts($cityId);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $districts
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
$city = $this->cityService->getById($id);
|
||||
if (!$city) {
|
||||
return response()->json([
|
||||
'code' => 404,
|
||||
'message' => '城市不存在',
|
||||
'data' => null
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $city
|
||||
]);
|
||||
}
|
||||
}
|
||||
51
app/Http/Controllers/System/Api/Config.php
Normal file
51
app/Http/Controllers/System/Api/Config.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\System\ConfigService;
|
||||
|
||||
class Config extends Controller
|
||||
{
|
||||
protected $configService;
|
||||
|
||||
public function __construct(ConfigService $configService)
|
||||
{
|
||||
$this->configService = $configService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$configs = $this->configService->getAllConfig();
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $configs
|
||||
]);
|
||||
}
|
||||
|
||||
public function getByGroup(Request $request)
|
||||
{
|
||||
$configs = $this->configService->getByGroup($request->input('group'));
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $configs
|
||||
]);
|
||||
}
|
||||
|
||||
public function getByKey(Request $request)
|
||||
{
|
||||
$key = $request->input('key');
|
||||
$value = $this->configService->getConfigValue($key);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => [
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
59
app/Http/Controllers/System/Api/Dictionary.php
Normal file
59
app/Http/Controllers/System/Api/Dictionary.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\System\DictionaryService;
|
||||
|
||||
class Dictionary extends Controller
|
||||
{
|
||||
protected $dictionaryService;
|
||||
|
||||
public function __construct(DictionaryService $dictionaryService)
|
||||
{
|
||||
$this->dictionaryService = $dictionaryService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$dictionaries = $this->dictionaryService->getAll();
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $dictionaries
|
||||
]);
|
||||
}
|
||||
|
||||
public function getByCode(Request $request)
|
||||
{
|
||||
$code = $request->input('code');
|
||||
$items = $this->dictionaryService->getItemsByCode($code);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => [
|
||||
'code' => $code,
|
||||
'items' => $items,
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
$dictionary = $this->dictionaryService->getById($id);
|
||||
if (!$dictionary) {
|
||||
return response()->json([
|
||||
'code' => 404,
|
||||
'message' => '字典不存在',
|
||||
'data' => null
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => $dictionary
|
||||
]);
|
||||
}
|
||||
}
|
||||
106
app/Http/Controllers/System/Api/Upload.php
Normal file
106
app/Http/Controllers/System/Api/Upload.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\System\UploadService;
|
||||
|
||||
class Upload extends Controller
|
||||
{
|
||||
protected $uploadService;
|
||||
|
||||
public function __construct(UploadService $uploadService)
|
||||
{
|
||||
$this->uploadService = $uploadService;
|
||||
}
|
||||
|
||||
public function upload(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'file' => 'required|file|max:10240',
|
||||
]);
|
||||
|
||||
$file = $request->file('file');
|
||||
$directory = $request->input('directory', 'uploads');
|
||||
$options = [
|
||||
'compress' => $request->input('compress', false),
|
||||
'quality' => $request->input('quality', 80),
|
||||
'width' => $request->input('width'),
|
||||
'height' => $request->input('height'),
|
||||
];
|
||||
|
||||
$result = $this->uploadService->upload($file, $directory, $options);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '上传成功',
|
||||
'data' => $result
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function uploadMultiple(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'files' => 'required|array',
|
||||
'files.*' => 'file|max:10240',
|
||||
]);
|
||||
|
||||
$files = $request->file('files');
|
||||
$directory = $request->input('directory', 'uploads');
|
||||
$options = [
|
||||
'compress' => $request->input('compress', false),
|
||||
'quality' => $request->input('quality', 80),
|
||||
'width' => $request->input('width'),
|
||||
'height' => $request->input('height'),
|
||||
];
|
||||
|
||||
$results = $this->uploadService->uploadMultiple($files, $directory, $options);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '上传成功',
|
||||
'data' => $results
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function uploadBase64(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'base64' => 'required|string',
|
||||
]);
|
||||
|
||||
$base64 = $request->input('base64');
|
||||
$directory = $request->input('directory', 'uploads');
|
||||
$fileName = $request->input('file_name');
|
||||
|
||||
$result = $this->uploadService->uploadBase64($base64, $directory, $fileName);
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => '上传成功',
|
||||
'data' => $result
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'code' => 400,
|
||||
'message' => $e->getMessage(),
|
||||
'data' => null
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
395
app/Http/Controllers/System/WebSocket.php
Normal file
395
app/Http/Controllers/System/WebSocket.php
Normal file
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\WebSocket\WebSocketService;
|
||||
|
||||
/**
|
||||
* WebSocket Controller
|
||||
*
|
||||
* Provides API endpoints for WebSocket operations
|
||||
*/
|
||||
class WebSocket extends Controller
|
||||
{
|
||||
/**
|
||||
* @var WebSocketService
|
||||
*/
|
||||
protected $webSocketService;
|
||||
|
||||
/**
|
||||
* WebSocket constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->webSocketService = app(WebSocketService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get online user count
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getOnlineCount(): JsonResponse
|
||||
{
|
||||
$count = $this->webSocketService->getOnlineUserCount();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => [
|
||||
'online_count' => $count
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get online user IDs
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getOnlineUsers(): JsonResponse
|
||||
{
|
||||
$userIds = $this->webSocketService->getOnlineUserIds();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => [
|
||||
'user_ids' => $userIds,
|
||||
'count' => count($userIds)
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is online
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function checkOnline(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'user_id' => 'required|integer'
|
||||
]);
|
||||
|
||||
$userId = $request->input('user_id');
|
||||
$isOnline = $this->webSocketService->isUserOnline($userId);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => [
|
||||
'user_id' => $userId,
|
||||
'is_online' => $isOnline
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to a specific user
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function sendToUser(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'user_id' => 'required|integer',
|
||||
'type' => 'required|string',
|
||||
'data' => 'required|array'
|
||||
]);
|
||||
|
||||
$userId = $request->input('user_id');
|
||||
$type = $request->input('type');
|
||||
$data = $request->input('data');
|
||||
|
||||
$message = [
|
||||
'type' => $type,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
$sent = $this->webSocketService->sendToUser($userId, $message);
|
||||
|
||||
return response()->json([
|
||||
'code' => $sent ? 200 : 404,
|
||||
'message' => $sent ? 'Message sent successfully' : 'User is not online',
|
||||
'data' => [
|
||||
'user_id' => $userId,
|
||||
'sent' => $sent
|
||||
]
|
||||
], $sent ? 200 : 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to multiple users
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function sendToUsers(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'user_ids' => 'required|array',
|
||||
'user_ids.*' => 'integer',
|
||||
'type' => 'required|string',
|
||||
'data' => 'required|array'
|
||||
]);
|
||||
|
||||
$userIds = $request->input('user_ids');
|
||||
$type = $request->input('type');
|
||||
$data = $request->input('data');
|
||||
|
||||
$message = [
|
||||
'type' => $type,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
$sentTo = $this->webSocketService->sendToUsers($userIds, $message);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'success',
|
||||
'data' => [
|
||||
'total_users' => count($userIds),
|
||||
'sent_to' => $sentTo,
|
||||
'failed' => count($userIds) - count($sentTo)
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast message to all users
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function broadcast(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'type' => 'required|string',
|
||||
'data' => 'required|array',
|
||||
'exclude_user_id' => 'nullable|integer'
|
||||
]);
|
||||
|
||||
$type = $request->input('type');
|
||||
$data = $request->input('data');
|
||||
$excludeUserId = $request->input('exclude_user_id');
|
||||
|
||||
$message = [
|
||||
'type' => $type,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
$count = $this->webSocketService->broadcast($message, $excludeUserId);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'Broadcast sent successfully',
|
||||
'data' => [
|
||||
'sent_to' => $count,
|
||||
'exclude_user_id' => $excludeUserId
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to a channel
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function sendToChannel(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'channel' => 'required|string',
|
||||
'type' => 'required|string',
|
||||
'data' => 'required|array'
|
||||
]);
|
||||
|
||||
$channel = $request->input('channel');
|
||||
$type = $request->input('type');
|
||||
$data = $request->input('data');
|
||||
|
||||
$message = [
|
||||
'type' => $type,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
$count = $this->webSocketService->sendToChannel($channel, $message);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'Message sent to channel successfully',
|
||||
'data' => [
|
||||
'channel' => $channel,
|
||||
'sent_to' => $count
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send system notification
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function sendNotification(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'message' => 'required|string|max:1000',
|
||||
'type' => 'nullable|string|in:info,success,warning,error',
|
||||
'extra_data' => 'nullable|array'
|
||||
]);
|
||||
|
||||
$title = $request->input('title');
|
||||
$message = $request->input('message');
|
||||
$type = $request->input('type', 'info');
|
||||
$extraData = $request->input('extra_data', []);
|
||||
|
||||
$count = $this->webSocketService->sendSystemNotification($title, $message, $type, $extraData);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'Notification sent successfully',
|
||||
'data' => [
|
||||
'sent_to' => $count
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification to specific users
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function sendNotificationToUsers(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'user_ids' => 'required|array',
|
||||
'user_ids.*' => 'integer',
|
||||
'title' => 'required|string|max:255',
|
||||
'message' => 'required|string|max:1000',
|
||||
'type' => 'nullable|string|in:info,success,warning,error',
|
||||
'extra_data' => 'nullable|array'
|
||||
]);
|
||||
|
||||
$userIds = $request->input('user_ids');
|
||||
$title = $request->input('title');
|
||||
$message = $request->input('message');
|
||||
$type = $request->input('type', 'info');
|
||||
$extraData = $request->input('extra_data', []);
|
||||
|
||||
$sentTo = $this->webSocketService->sendNotificationToUsers($userIds, $title, $message, $type, $extraData);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'Notification sent successfully',
|
||||
'data' => [
|
||||
'total_users' => count($userIds),
|
||||
'sent_to' => $sentTo,
|
||||
'failed' => count($userIds) - count($sentTo)
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push data update
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function pushDataUpdate(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'user_ids' => 'required|array',
|
||||
'user_ids.*' => 'integer',
|
||||
'resource_type' => 'required|string',
|
||||
'action' => 'required|string|in:create,update,delete',
|
||||
'data' => 'required|array'
|
||||
]);
|
||||
|
||||
$userIds = $request->input('user_ids');
|
||||
$resourceType = $request->input('resource_type');
|
||||
$action = $request->input('action');
|
||||
$data = $request->input('data');
|
||||
|
||||
$sentTo = $this->webSocketService->pushDataUpdate($userIds, $resourceType, $action, $data);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'Data update pushed successfully',
|
||||
'data' => [
|
||||
'resource_type' => $resourceType,
|
||||
'action' => $action,
|
||||
'total_users' => count($userIds),
|
||||
'sent_to' => $sentTo,
|
||||
'failed' => count($userIds) - count($sentTo)
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push data update to channel
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function pushDataUpdateToChannel(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'channel' => 'required|string',
|
||||
'resource_type' => 'required|string',
|
||||
'action' => 'required|string|in:create,update,delete',
|
||||
'data' => 'required|array'
|
||||
]);
|
||||
|
||||
$channel = $request->input('channel');
|
||||
$resourceType = $request->input('resource_type');
|
||||
$action = $request->input('action');
|
||||
$data = $request->input('data');
|
||||
|
||||
$count = $this->webSocketService->pushDataUpdateToChannel($channel, $resourceType, $action, $data);
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'message' => 'Data update pushed to channel successfully',
|
||||
'data' => [
|
||||
'channel' => $channel,
|
||||
'resource_type' => $resourceType,
|
||||
'action' => $action,
|
||||
'sent_to' => $count
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect a user from WebSocket
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function disconnectUser(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'user_id' => 'required|integer'
|
||||
]);
|
||||
|
||||
$userId = $request->input('user_id');
|
||||
$disconnected = $this->webSocketService->disconnectUser($userId);
|
||||
|
||||
return response()->json([
|
||||
'code' => $disconnected ? 200 : 404,
|
||||
'message' => $disconnected ? 'User disconnected successfully' : 'User is not online',
|
||||
'data' => [
|
||||
'user_id' => $userId,
|
||||
'disconnected' => $disconnected
|
||||
]
|
||||
], $disconnected ? 200 : 404);
|
||||
}
|
||||
}
|
||||
120
app/Http/Middleware/AuthCheckMiddleware.php
Normal file
120
app/Http/Middleware/AuthCheckMiddleware.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AuthCheckMiddleware
|
||||
{
|
||||
/**
|
||||
* 处理传入请求
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @param string|null $guard 认证守卫名称(默认为 api)
|
||||
* @param string|null $permission 需要检查的权限编码(可选)
|
||||
* @return Response
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, ?string $guard = 'api', ?string $permission = null): Response
|
||||
{
|
||||
// 检查是否已认证
|
||||
if (!Auth::guard($guard)->check()) {
|
||||
return response()->json([
|
||||
'code' => 401,
|
||||
'message' => '未登录或token已过期',
|
||||
'data' => null,
|
||||
], 401);
|
||||
}
|
||||
|
||||
// 获取当前用户
|
||||
$user = Auth::guard($guard)->user();
|
||||
|
||||
// 检查用户状态
|
||||
if (isset($user->status) && $user->status !== 1) {
|
||||
return response()->json([
|
||||
'code' => 403,
|
||||
'message' => '账号已被禁用',
|
||||
'data' => null,
|
||||
], 403);
|
||||
}
|
||||
|
||||
// 如果需要检查权限
|
||||
if ($permission !== null) {
|
||||
if (!$this->checkPermission($user, $permission, $guard)) {
|
||||
return response()->json([
|
||||
'code' => 403,
|
||||
'message' => '无权限访问',
|
||||
'data' => null,
|
||||
], 403);
|
||||
}
|
||||
}
|
||||
|
||||
// 将用户信息添加到请求中
|
||||
$request->merge(['auth_user' => $user]);
|
||||
|
||||
// 更新用户最后活跃时间
|
||||
if (method_exists($user, 'updateLastActiveAt')) {
|
||||
$user->updateLastActiveAt();
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户权限
|
||||
*
|
||||
* @param mixed $user
|
||||
* @param string $permission 权限编码
|
||||
* @param string $guard 认证守卫
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkPermission($user, string $permission, string $guard): bool
|
||||
{
|
||||
// 如果用户有所有权限标识
|
||||
if (method_exists($user, 'hasAllPermissions') && $user->hasAllPermissions()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查用户是否有指定权限
|
||||
if (method_exists($user, 'hasPermission')) {
|
||||
return $user->hasPermission($permission);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查多个权限(满足任意一个即可)
|
||||
*
|
||||
* @param array $permissions 权限编码数组
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkAnyPermission($user, array $permissions): bool
|
||||
{
|
||||
foreach ($permissions as $permission) {
|
||||
if ($this->checkPermission($user, $permission, 'api')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查多个权限(必须全部满足)
|
||||
*
|
||||
* @param array $permissions 权限编码数组
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkAllPermissions($user, array $permissions): bool
|
||||
{
|
||||
foreach ($permissions as $permission) {
|
||||
if (!$this->checkPermission($user, $permission, 'api')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
242
app/Http/Middleware/LogRequestMiddleware.php
Normal file
242
app/Http/Middleware/LogRequestMiddleware.php
Normal file
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log as LaravelLog;
|
||||
use App\Services\System\LogService;
|
||||
use Throwable;
|
||||
|
||||
class LogRequestMiddleware
|
||||
{
|
||||
protected $logService;
|
||||
|
||||
public function __construct(LogService $logService)
|
||||
{
|
||||
$this->logService = $logService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
$endTime = microtime(true);
|
||||
$executionTime = round(($endTime - $startTime) * 1000, 2); // 转换为毫秒
|
||||
|
||||
// 异步记录日志,不影响响应速度
|
||||
$this->logRequest($request, $response, $executionTime);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录请求日志
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Http\Response $response
|
||||
* @param float $executionTime
|
||||
* @return void
|
||||
*/
|
||||
protected function logRequest(Request $request, $response, float $executionTime): void
|
||||
{
|
||||
try {
|
||||
// 获取当前用户信息
|
||||
$user = Auth::guard('admin')->user();
|
||||
$userId = $user ? $user->id : null;
|
||||
$username = $user ? $user->username : 'guest';
|
||||
|
||||
// 解析模块和操作
|
||||
$module = $this->parseModule($request->path());
|
||||
$action = $this->parseAction($request->method(), $request->path());
|
||||
|
||||
// 获取请求参数(排除敏感信息)
|
||||
$params = $this->sanitizeParams($request->all());
|
||||
|
||||
// 获取响应数据
|
||||
$result = null;
|
||||
if ($response->getStatusCode() >= 400) {
|
||||
$result = $response->getContent();
|
||||
}
|
||||
|
||||
// 确定日志状态
|
||||
$status = $response->getStatusCode() < 400 ? 'success' : 'error';
|
||||
$errorMessage = null;
|
||||
if ($status === 'error') {
|
||||
$errorMessage = $this->extractErrorMessage($result);
|
||||
}
|
||||
|
||||
// 构建日志数据
|
||||
$logData = [
|
||||
'user_id' => $userId,
|
||||
'username' => $username,
|
||||
'module' => $module,
|
||||
'action' => $action,
|
||||
'method' => $request->method(),
|
||||
'url' => $request->fullUrl(),
|
||||
'ip' => $this->getClientIp($request),
|
||||
'user_agent' => $request->userAgent(),
|
||||
'params' => $params,
|
||||
'result' => $result,
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'status' => $status,
|
||||
'error_message' => $errorMessage,
|
||||
'execution_time' => $executionTime,
|
||||
];
|
||||
|
||||
// 记录到数据库
|
||||
$this->logService->create($logData);
|
||||
|
||||
// 同时记录到 Laravel 日志(用于错误)
|
||||
if ($status === 'error') {
|
||||
LaravelLog::error('API Request Error', [
|
||||
'url' => $request->fullUrl(),
|
||||
'method' => $request->method(),
|
||||
'user_id' => $userId,
|
||||
'error' => $errorMessage,
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// 记录日志失败不影响业务流程
|
||||
LaravelLog::error('Log request failed', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析模块名称
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
protected function parseModule(string $path): string
|
||||
{
|
||||
$segments = explode('/', trim($path, '/'));
|
||||
|
||||
// 移除前缀(如 admin, api)
|
||||
$prefixes = ['admin', 'api'];
|
||||
if (in_array($segments[0], $prefixes)) {
|
||||
array_shift($segments);
|
||||
}
|
||||
|
||||
return $segments[0] ?? 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析操作名称
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
protected function parseAction(string $method, string $path): string
|
||||
{
|
||||
$segments = explode('/', trim($path, '/'));
|
||||
|
||||
// 获取资源名称
|
||||
$resource = end($segments);
|
||||
|
||||
// 如果资源是 ID,则取前一个作为资源名
|
||||
if (is_numeric($resource)) {
|
||||
$resource = prev($segments);
|
||||
}
|
||||
|
||||
// 根据方法映射操作
|
||||
$actionMap = [
|
||||
'GET' => '查询',
|
||||
'POST' => '创建',
|
||||
'PUT' => '更新',
|
||||
'PATCH' => '更新',
|
||||
'DELETE' => '删除',
|
||||
];
|
||||
|
||||
$action = $actionMap[$method] ?? '操作';
|
||||
|
||||
return $action . ' ' . $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理敏感参数
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
protected function sanitizeParams(array $params): array
|
||||
{
|
||||
$sensitiveKeys = ['password', 'password_confirmation', 'token', 'secret', 'key'];
|
||||
|
||||
array_walk_recursive($params, function (&$value, $key) use ($sensitiveKeys) {
|
||||
if (in_array(strtolower($key), $sensitiveKeys)) {
|
||||
$value = '******';
|
||||
}
|
||||
});
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取错误信息
|
||||
*
|
||||
* @param string|null $content
|
||||
* @return string|null
|
||||
*/
|
||||
protected function extractErrorMessage(?string $content): ?string
|
||||
{
|
||||
if (empty($content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = json_decode($content, true);
|
||||
if (isset($data['message'])) {
|
||||
return $data['message'];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// JSON 解析失败,返回原始内容
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端 IP
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return string
|
||||
*/
|
||||
protected function getClientIp(Request $request): string
|
||||
{
|
||||
$ip = $request->ip();
|
||||
|
||||
// 检查代理头
|
||||
$headers = [
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_CLIENT_IP',
|
||||
'HTTP_X_REAL_IP',
|
||||
'HTTP_CF_CONNECTING_IP',
|
||||
];
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if ($request->hasHeader($header)) {
|
||||
$forwardedIps = explode(',', $request->header($header));
|
||||
$ip = trim($forwardedIps[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
139
app/Http/Requests/LogRequest.php
Normal file
139
app/Http/Requests/LogRequest.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
|
||||
class LogRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if ($this->isMethod('GET')) {
|
||||
// 列表查询参数验证
|
||||
$rules = [
|
||||
'user_id' => 'nullable|integer|exists:auth_users,id',
|
||||
'username' => 'nullable|string|max:50',
|
||||
'module' => 'nullable|string|max:50',
|
||||
'action' => 'nullable|string|max:100',
|
||||
'status' => 'nullable|in:success,error',
|
||||
'start_date' => 'nullable|date',
|
||||
'end_date' => 'nullable|date|after_or_equal:start_date',
|
||||
'ip' => 'nullable|ip',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'page_size' => 'nullable|integer|min:1|max:100',
|
||||
];
|
||||
} elseif ($this->isMethod('POST')) {
|
||||
// 批量删除参数验证
|
||||
if ($this->routeIs('*.batch-delete')) {
|
||||
$rules = [
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'required|integer|exists:system_logs,id',
|
||||
];
|
||||
}
|
||||
// 清理日志参数验证
|
||||
if ($this->routeIs('*.clear')) {
|
||||
$rules = [
|
||||
'days' => 'nullable|integer|min:1|max:365',
|
||||
];
|
||||
}
|
||||
} elseif ($this->isMethod('DELETE')) {
|
||||
// 单个删除参数验证
|
||||
$rules = [
|
||||
'id' => 'required|integer|exists:system_logs,id',
|
||||
];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'user_id.exists' => '用户不存在',
|
||||
'username.max' => '用户名最多50个字符',
|
||||
'module.max' => '模块名最多50个字符',
|
||||
'action.max' => '操作名最多100个字符',
|
||||
'status.in' => '状态值必须是 success 或 error',
|
||||
'start_date.date' => '开始日期格式不正确',
|
||||
'end_date.date' => '结束日期格式不正确',
|
||||
'end_date.after_or_equal' => '结束日期必须大于或等于开始日期',
|
||||
'ip.ip' => 'IP地址格式不正确',
|
||||
'page.integer' => '页码必须是整数',
|
||||
'page.min' => '页码必须大于0',
|
||||
'page_size.integer' => '每页数量必须是整数',
|
||||
'page_size.min' => '每页数量必须大于0',
|
||||
'page_size.max' => '每页数量不能超过100',
|
||||
'ids.required' => '请选择要删除的日志',
|
||||
'ids.array' => '日志ID必须是数组',
|
||||
'ids.*.required' => '日志ID不能为空',
|
||||
'ids.*.integer' => '日志ID必须是整数',
|
||||
'ids.*.exists' => '日志不存在',
|
||||
'days.integer' => '天数必须是整数',
|
||||
'days.min' => '天数必须大于0',
|
||||
'days.max' => '天数不能超过365',
|
||||
'id.required' => '日志ID不能为空',
|
||||
'id.integer' => '日志ID必须是整数',
|
||||
'id.exists' => '日志不存在',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a failed validation attempt.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Validation\Validator $validator
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Http\Exceptions\HttpResponseException
|
||||
*/
|
||||
protected function failedValidation(Validator $validator): void
|
||||
{
|
||||
throw new HttpResponseException(
|
||||
response()->json([
|
||||
'code' => 422,
|
||||
'message' => $validator->errors()->first(),
|
||||
'data' => null,
|
||||
], 422)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// 设置默认值
|
||||
if ($this->isMethod('GET')) {
|
||||
$this->merge([
|
||||
'page' => $this->input('page', 1),
|
||||
'page_size' => $this->input('page_size', 20),
|
||||
'days' => $this->input('days', 30),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
120
app/Imports/DepartmentImport.php
Normal file
120
app/Imports/DepartmentImport.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace App\Imports;
|
||||
|
||||
use App\Models\Auth\Department;
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\ToCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadingRow;
|
||||
use Maatwebsite\Excel\Concerns\WithValidation;
|
||||
|
||||
class DepartmentImport implements ToCollection, WithHeadingRow, WithValidation
|
||||
{
|
||||
protected $successCount = 0;
|
||||
protected $errorCount = 0;
|
||||
protected $errors = [];
|
||||
|
||||
/**
|
||||
* 处理导入数据
|
||||
*/
|
||||
public function collection(Collection $rows)
|
||||
{
|
||||
foreach ($rows as $index => $row) {
|
||||
try {
|
||||
// 跳过空行
|
||||
if (empty($row['部门名称'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查部门名称是否已存在
|
||||
$exists = Department::where('name', $row['部门名称'])->exists();
|
||||
if ($exists) {
|
||||
$this->addError($index + 2, '部门名称已存在: ' . $row['部门名称']);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 查找父级部门
|
||||
$parentId = null;
|
||||
if (!empty($row['上级部门名称'])) {
|
||||
$parent = Department::where('name', $row['上级部门名称'])->first();
|
||||
if (!$parent) {
|
||||
$this->addError($index + 2, '上级部门不存在: ' . $row['上级部门名称']);
|
||||
continue;
|
||||
}
|
||||
$parentId = $parent->id;
|
||||
}
|
||||
|
||||
// 创建部门
|
||||
Department::create([
|
||||
'name' => $row['部门名称'],
|
||||
'parent_id' => $parentId,
|
||||
'leader' => $row['负责人'] ?? null,
|
||||
'phone' => $row['联系电话'] ?? null,
|
||||
'sort' => $row['排序'] ?? 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$this->successCount++;
|
||||
} catch (\Exception $e) {
|
||||
$this->addError($index + 2, $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证规则
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'部门名称' => 'required|string|max:50',
|
||||
'上级部门名称' => 'nullable|string|max:50',
|
||||
'负责人' => 'nullable|string|max:50',
|
||||
'联系电话' => 'nullable|string|max:20',
|
||||
'排序' => 'nullable|integer|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义验证消息
|
||||
*/
|
||||
public function customValidationMessages(): array
|
||||
{
|
||||
return [
|
||||
'部门名称.required' => '部门名称不能为空',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加错误
|
||||
*/
|
||||
protected function addError(int $row, string $message): void
|
||||
{
|
||||
$this->errorCount++;
|
||||
$this->errors[] = "第 {$row} 行: {$message}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成功数量
|
||||
*/
|
||||
public function getSuccessCount(): int
|
||||
{
|
||||
return $this->successCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误数量
|
||||
*/
|
||||
public function getErrorCount(): int
|
||||
{
|
||||
return $this->errorCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
*/
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
147
app/Imports/UserImport.php
Normal file
147
app/Imports/UserImport.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Imports;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Auth\Department;
|
||||
use App\Models\Auth\Role;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Maatwebsite\Excel\Concerns\ToCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadingRow;
|
||||
use Maatwebsite\Excel\Concerns\WithValidation;
|
||||
|
||||
class UserImport implements ToCollection, WithHeadingRow, WithValidation
|
||||
{
|
||||
protected $successCount = 0;
|
||||
protected $errorCount = 0;
|
||||
protected $errors = [];
|
||||
|
||||
/**
|
||||
* 处理导入数据
|
||||
*/
|
||||
public function collection(Collection $rows)
|
||||
{
|
||||
foreach ($rows as $index => $row) {
|
||||
try {
|
||||
// 跳过空行
|
||||
if (empty($row['用户名'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
$exists = User::where('username', $row['用户名'])->exists();
|
||||
if ($exists) {
|
||||
$this->addError($index + 2, '用户名已存在');
|
||||
continue;
|
||||
}
|
||||
|
||||
// 查找部门
|
||||
$departmentId = null;
|
||||
if (!empty($row['部门名称'])) {
|
||||
$department = Department::where('name', $row['部门名称'])->first();
|
||||
if (!$department) {
|
||||
$this->addError($index + 2, '部门不存在: ' . $row['部门名称']);
|
||||
continue;
|
||||
}
|
||||
$departmentId = $department->id;
|
||||
}
|
||||
|
||||
// 查找角色
|
||||
$roleIds = [];
|
||||
if (!empty($row['角色名称(多个用逗号分隔)'])) {
|
||||
$roleNames = array_map('trim', explode(',', $row['角色名称(多个用逗号分隔)']));
|
||||
$roles = Role::whereIn('name', $roleNames)->get();
|
||||
|
||||
if ($roles->count() != count($roleNames)) {
|
||||
$existingNames = $roles->pluck('name')->toArray();
|
||||
$notFound = array_diff($roleNames, $existingNames);
|
||||
$this->addError($index + 2, '角色不存在: ' . implode(', ', $notFound));
|
||||
continue;
|
||||
}
|
||||
$roleIds = $roles->pluck('id')->toArray();
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
$user = User::create([
|
||||
'username' => $row['用户名'],
|
||||
'password' => Hash::make($row['密码']),
|
||||
'real_name' => $row['真实姓名'],
|
||||
'email' => $row['邮箱'] ?? null,
|
||||
'phone' => $row['手机号'] ?? null,
|
||||
'department_id' => $departmentId,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
// 分配角色
|
||||
if (!empty($roleIds)) {
|
||||
$user->roles()->attach($roleIds);
|
||||
}
|
||||
|
||||
$this->successCount++;
|
||||
} catch (\Exception $e) {
|
||||
$this->addError($index + 2, $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证规则
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'用户名' => 'required|string|max:50',
|
||||
'密码' => 'required|string|min:6',
|
||||
'真实姓名' => 'required|string|max:50',
|
||||
'邮箱' => 'nullable|email',
|
||||
'手机号' => 'nullable|string|max:20',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义验证消息
|
||||
*/
|
||||
public function customValidationMessages(): array
|
||||
{
|
||||
return [
|
||||
'用户名.required' => '用户名不能为空',
|
||||
'密码.required' => '密码不能为空',
|
||||
'真实姓名.required' => '真实姓名不能为空',
|
||||
'密码.min' => '密码至少6位',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加错误
|
||||
*/
|
||||
protected function addError(int $row, string $message): void
|
||||
{
|
||||
$this->errorCount++;
|
||||
$this->errors[] = "第 {$row} 行: {$message}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成功数量
|
||||
*/
|
||||
public function getSuccessCount(): int
|
||||
{
|
||||
return $this->successCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误数量
|
||||
*/
|
||||
public function getErrorCount(): int
|
||||
{
|
||||
return $this->errorCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
*/
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
53
app/Models/Auth/Department.php
Normal file
53
app/Models/Auth/Department.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Auth;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Department extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'auth_departments';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'parent_id',
|
||||
'leader',
|
||||
'phone',
|
||||
'sort',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'parent_id' => 'integer',
|
||||
'sort' => 'integer',
|
||||
'status' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取子部门
|
||||
*/
|
||||
public function children(): HasMany
|
||||
{
|
||||
return $this->hasMany(Department::class, 'parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父部门
|
||||
*/
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(Department::class, 'parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门下的用户
|
||||
*/
|
||||
public function users(): HasMany
|
||||
{
|
||||
return $this->hasMany(User::class, 'department_id');
|
||||
}
|
||||
}
|
||||
58
app/Models/Auth/Permission.php
Normal file
58
app/Models/Auth/Permission.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Auth;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Permission extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'auth_permissions';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'code',
|
||||
'type',
|
||||
'parent_id',
|
||||
'route',
|
||||
'component',
|
||||
'meta',
|
||||
'sort',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'parent_id' => 'integer',
|
||||
'meta' => 'array',
|
||||
'sort' => 'integer',
|
||||
'status' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* 关联角色
|
||||
*/
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Role::class, 'auth_role_permission', 'permission_id', 'role_id')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子权限
|
||||
*/
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(Permission::class, 'parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父权限
|
||||
*/
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(Permission::class, 'parent_id');
|
||||
}
|
||||
}
|
||||
45
app/Models/Auth/Role.php
Normal file
45
app/Models/Auth/Role.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Auth;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Role extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'auth_roles';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'code',
|
||||
'description',
|
||||
'sort',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'sort' => 'integer',
|
||||
'status' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* 关联权限
|
||||
*/
|
||||
public function permissions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Permission::class, 'auth_role_permission', 'role_id', 'permission_id')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联用户
|
||||
*/
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'auth_user_role', 'role_id', 'user_id')
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
||||
109
app/Models/Auth/User.php
Normal file
109
app/Models/Auth/User.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Auth;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Tymon\JWTAuth\Contracts\JWTSubject;
|
||||
|
||||
class User extends Authenticatable implements JWTSubject
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'auth_users';
|
||||
|
||||
protected $fillable = [
|
||||
'username',
|
||||
'password',
|
||||
'real_name',
|
||||
'email',
|
||||
'phone',
|
||||
'department_id',
|
||||
'avatar',
|
||||
'status',
|
||||
'last_login_at',
|
||||
'last_login_ip',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'status' => 'integer',
|
||||
'department_id' => 'integer',
|
||||
'last_login_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* 关联部门
|
||||
*/
|
||||
public function department(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Department::class, 'department_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联角色
|
||||
*/
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Role::class, 'auth_user_role', 'user_id', 'role_id')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的所有权限
|
||||
*/
|
||||
public function permissions()
|
||||
{
|
||||
return $this->roles()->with('permissions');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
*/
|
||||
public function hasPermission(string $permissionCode): bool
|
||||
{
|
||||
foreach ($this->roles as $role) {
|
||||
foreach ($role->permissions as $permission) {
|
||||
if ($permission->code === $permissionCode) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
*/
|
||||
public function hasRole(string $roleCode): bool
|
||||
{
|
||||
return $this->roles()->where('code', $roleCode)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 JWT 标识符
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getJWTIdentifier()
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 JWT 自定义声明
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJWTCustomClaims()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
46
app/Models/System/City.php
Normal file
46
app/Models/System/City.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\System;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class City extends Model
|
||||
{
|
||||
protected $table = 'system_cities';
|
||||
|
||||
protected $fillable = [
|
||||
'parent_id',
|
||||
'name',
|
||||
'code',
|
||||
'pinyin',
|
||||
'pinyin_short',
|
||||
'level',
|
||||
'sort',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'parent_id' => 'integer',
|
||||
'level' => 'integer',
|
||||
'sort' => 'integer',
|
||||
'status' => 'boolean',
|
||||
];
|
||||
|
||||
public function children(): HasMany
|
||||
{
|
||||
return $this->hasMany(City::class, 'parent_id')->orderBy('sort');
|
||||
}
|
||||
|
||||
public function activeChildren(): HasMany
|
||||
{
|
||||
return $this->hasMany(City::class, 'parent_id')
|
||||
->where('status', true)
|
||||
->orderBy('sort');
|
||||
}
|
||||
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(City::class, 'parent_id');
|
||||
}
|
||||
}
|
||||
47
app/Models/System/Config.php
Normal file
47
app/Models/System/Config.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\System;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Config extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'system_configs';
|
||||
|
||||
protected $fillable = [
|
||||
'group',
|
||||
'key',
|
||||
'name',
|
||||
'type',
|
||||
'options',
|
||||
'value',
|
||||
'default_value',
|
||||
'description',
|
||||
'validation',
|
||||
'sort',
|
||||
'is_system',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'options' => 'array',
|
||||
'is_system' => 'boolean',
|
||||
'status' => 'boolean',
|
||||
];
|
||||
|
||||
public function getOptionsAttribute($value)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
return json_decode($value, true) ?? [];
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function setOptionsAttribute($value)
|
||||
{
|
||||
$this->attributes['options'] = is_array($value) ? json_encode($value) : $value;
|
||||
}
|
||||
}
|
||||
38
app/Models/System/Dictionary.php
Normal file
38
app/Models/System/Dictionary.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\System;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Dictionary extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'system_dictionaries';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'code',
|
||||
'description',
|
||||
'status',
|
||||
'sort',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'status' => 'boolean',
|
||||
];
|
||||
|
||||
public function items(): HasMany
|
||||
{
|
||||
return $this->hasMany(DictionaryItem::class, 'dictionary_id')->orderBy('sort');
|
||||
}
|
||||
|
||||
public function activeItems(): HasMany
|
||||
{
|
||||
return $this->hasMany(DictionaryItem::class, 'dictionary_id')
|
||||
->where('status', true)
|
||||
->orderBy('sort');
|
||||
}
|
||||
}
|
||||
32
app/Models/System/DictionaryItem.php
Normal file
32
app/Models/System/DictionaryItem.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\System;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class DictionaryItem extends Model
|
||||
{
|
||||
protected $table = 'system_dictionary_items';
|
||||
|
||||
protected $fillable = [
|
||||
'dictionary_id',
|
||||
'label',
|
||||
'value',
|
||||
'color',
|
||||
'description',
|
||||
'is_default',
|
||||
'status',
|
||||
'sort',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_default' => 'boolean',
|
||||
'status' => 'boolean',
|
||||
];
|
||||
|
||||
public function dictionary(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Dictionary::class, 'dictionary_id');
|
||||
}
|
||||
}
|
||||
39
app/Models/System/Log.php
Normal file
39
app/Models/System/Log.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\System;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Log extends Model
|
||||
{
|
||||
protected $table = 'system_logs';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'username',
|
||||
'module',
|
||||
'action',
|
||||
'method',
|
||||
'url',
|
||||
'ip',
|
||||
'user_agent',
|
||||
'params',
|
||||
'result',
|
||||
'status_code',
|
||||
'status',
|
||||
'error_message',
|
||||
'execution_time',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'params' => 'array',
|
||||
'execution_time' => 'integer',
|
||||
'status_code' => 'integer',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Auth\User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
42
app/Models/System/Task.php
Normal file
42
app/Models/System/Task.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\System;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Task extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'system_tasks';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'command',
|
||||
'description',
|
||||
'type',
|
||||
'expression',
|
||||
'timezone',
|
||||
'is_active',
|
||||
'run_in_background',
|
||||
'without_overlapping',
|
||||
'only_one',
|
||||
'last_run_at',
|
||||
'next_run_at',
|
||||
'last_output',
|
||||
'run_count',
|
||||
'failed_count',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
'run_in_background' => 'boolean',
|
||||
'without_overlapping' => 'boolean',
|
||||
'only_one' => 'boolean',
|
||||
'last_run_at' => 'datetime',
|
||||
'next_run_at' => 'datetime',
|
||||
'run_count' => 'integer',
|
||||
'failed_count' => 'integer',
|
||||
];
|
||||
}
|
||||
24
app/Providers/AppServiceProvider.php
Normal file
24
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
248
app/Services/Auth/AuthService.php
Normal file
248
app/Services/Auth/AuthService.php
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use App\Services\Auth\PermissionService;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AuthService
|
||||
{
|
||||
protected $permissionService;
|
||||
|
||||
public function __construct(PermissionService $permissionService)
|
||||
{
|
||||
$this->permissionService = $permissionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
*/
|
||||
public function login(array $credentials): array
|
||||
{
|
||||
$user = User::where('username', $credentials['username'])->first();
|
||||
|
||||
if (!$user || !Hash::check($credentials['password'], $user->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'username' => ['用户名或密码错误'],
|
||||
]);
|
||||
}
|
||||
|
||||
if ($user->status !== 1) {
|
||||
throw ValidationException::withMessages([
|
||||
'username' => ['账号已被禁用'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 更新登录信息
|
||||
$user->update([
|
||||
'last_login_at' => now(),
|
||||
'last_login_ip' => request()->ip(),
|
||||
]);
|
||||
|
||||
// 生成token
|
||||
$token = Auth::guard('admin')->login($user);
|
||||
|
||||
// 生成refresh token
|
||||
$refreshToken = Auth::guard('admin')->refresh();
|
||||
|
||||
// 获取用户菜单
|
||||
$menu = $this->getUserMenu($user);
|
||||
|
||||
// 获取用户权限列表
|
||||
$permissions = $this->getUserPermissions($user);
|
||||
|
||||
return [
|
||||
'token' => $token,
|
||||
'refreshToken' => $refreshToken,
|
||||
'user' => $this->getUserInfo($user),
|
||||
'menu' => $menu,
|
||||
'permissions' => $permissions,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登出
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
Auth::guard('admin')->logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
public function refresh(): array
|
||||
{
|
||||
$newToken = Auth::guard('admin')->refresh();
|
||||
$user = Auth::guard('admin')->user();
|
||||
|
||||
// 生成新的refresh token
|
||||
$newRefreshToken = Auth::guard('admin')->refresh();
|
||||
|
||||
// 获取用户菜单
|
||||
$menu = $this->getUserMenu($user);
|
||||
|
||||
// 获取用户权限列表
|
||||
$permissions = $this->getUserPermissions($user);
|
||||
|
||||
return [
|
||||
'token' => $newToken,
|
||||
'refreshToken' => $newRefreshToken,
|
||||
'user' => $this->getUserInfo($user),
|
||||
'menu' => $menu,
|
||||
'permissions' => $permissions,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
public function me(): array
|
||||
{
|
||||
$user = Auth::guard('admin')->user();
|
||||
return $this->getUserInfo($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 找回密码
|
||||
*/
|
||||
public function resetPassword(array $data): void
|
||||
{
|
||||
$user = User::where('username', $data['username'])
|
||||
->orWhere('email', $data['username'])
|
||||
->orWhere('phone', $data['username'])
|
||||
->first();
|
||||
|
||||
if (!$user) {
|
||||
throw ValidationException::withMessages([
|
||||
'username' => ['用户不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
$user->update([
|
||||
'password' => Hash::make($data['password']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
public function changePassword(array $data): void
|
||||
{
|
||||
$user = Auth::guard('admin')->user();
|
||||
|
||||
if (!Hash::check($data['old_password'], $user->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'old_password' => ['原密码错误'],
|
||||
]);
|
||||
}
|
||||
|
||||
$user->update([
|
||||
'password' => Hash::make($data['password']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息详情
|
||||
*/
|
||||
private function getUserInfo(User $user): array
|
||||
{
|
||||
$user->load(['department', 'roles.permissions']);
|
||||
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'real_name' => $user->real_name,
|
||||
'email' => $user->email,
|
||||
'phone' => $user->phone,
|
||||
'avatar' => $user->avatar,
|
||||
'department' => $user->department ? [
|
||||
'id' => $user->department->id,
|
||||
'name' => $user->department->name,
|
||||
] : null,
|
||||
'roles' => $user->roles->pluck('name')->toArray(),
|
||||
'permissions' => $this->getUserPermissions($user),
|
||||
'status' => $user->status,
|
||||
'last_login_at' => $user->last_login_at ? $user->last_login_at->toDateTimeString() : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户菜单
|
||||
*/
|
||||
private function getUserMenu(User $user): array
|
||||
{
|
||||
// 获取用户的所有权限
|
||||
$permissionIds = [];
|
||||
foreach ($user->roles as $role) {
|
||||
foreach ($role->permissions as $permission) {
|
||||
$permissionIds[] = $permission->id;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询菜单类型的权限
|
||||
$menuPermissions = \App\Models\Auth\Permission::whereIn('id', $permissionIds)
|
||||
->where('type', 'menu')
|
||||
->where('status', 1)
|
||||
->orderBy('sort', 'asc')
|
||||
->get();
|
||||
|
||||
// 构建菜单树
|
||||
return $this->buildMenuTree($menuPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建菜单树
|
||||
*/
|
||||
private function buildMenuTree($permissions, $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($permissions as $permission) {
|
||||
if ($permission->parent_id == $parentId) {
|
||||
$node = [
|
||||
'path' => $permission->route,
|
||||
'name' => $permission->code,
|
||||
'meta' => $permission->meta ? json_decode($permission->meta, true) : [],
|
||||
];
|
||||
|
||||
// 添加组件路径
|
||||
if ($permission->component) {
|
||||
$node['component'] = $permission->component;
|
||||
}
|
||||
|
||||
// 添加重定向
|
||||
if (!empty($node['meta']['redirect'])) {
|
||||
$node['redirect'] = $node['meta']['redirect'];
|
||||
}
|
||||
|
||||
// 递归构建子菜单
|
||||
$children = $this->buildMenuTree($permissions, $permission->id);
|
||||
if (!empty($children)) {
|
||||
$node['children'] = $children;
|
||||
}
|
||||
|
||||
$tree[] = $node;
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户权限列表
|
||||
*/
|
||||
private function getUserPermissions(User $user): array
|
||||
{
|
||||
$permissions = [];
|
||||
foreach ($user->roles as $role) {
|
||||
foreach ($role->permissions as $permission) {
|
||||
if (!in_array($permission->code, $permissions)) {
|
||||
$permissions[] = $permission->code;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $permissions;
|
||||
}
|
||||
}
|
||||
319
app/Services/Auth/DepartmentService.php
Normal file
319
app/Services/Auth/DepartmentService.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\Auth\Department;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class DepartmentService
|
||||
{
|
||||
/**
|
||||
* 获取部门列表
|
||||
*/
|
||||
public function getList(array $params): array
|
||||
{
|
||||
$query = Department::query();
|
||||
|
||||
// 搜索条件
|
||||
if (!empty($params['keyword'])) {
|
||||
$query->where(function ($q) use ($params) {
|
||||
$q->where('name', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('leader', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('phone', 'like', '%' . $params['keyword'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
if (isset($params['parent_id']) && $params['parent_id'] !== '') {
|
||||
$query->where('parent_id', $params['parent_id']);
|
||||
}
|
||||
|
||||
// 排序
|
||||
$orderBy = $params['order_by'] ?? 'sort';
|
||||
$orderDirection = $params['order_direction'] ?? 'asc';
|
||||
$query->orderBy($orderBy, $orderDirection);
|
||||
|
||||
// 分页
|
||||
$page = $params['page'] ?? 1;
|
||||
$pageSize = $params['page_size'] ?? 20;
|
||||
$list = $query->paginate($pageSize, ['*'], 'page', $page);
|
||||
|
||||
return [
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'page' => $page,
|
||||
'page_size' => $pageSize,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门树
|
||||
*/
|
||||
public function getTree(array $params = []): array
|
||||
{
|
||||
$query = Department::query();
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
$departments = $query->orderBy('sort', 'asc')->get();
|
||||
return $this->buildTree($departments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有部门(不分页)
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
$departments = Department::where('status', 1)->orderBy('sort', 'asc')->get();
|
||||
return $departments->map(function ($department) {
|
||||
return [
|
||||
'id' => $department->id,
|
||||
'name' => $department->name,
|
||||
'parent_id' => $department->parent_id,
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门详情
|
||||
*/
|
||||
public function getById(int $id): array
|
||||
{
|
||||
$department = Department::with(['parent', 'children'])->find($id);
|
||||
|
||||
if (!$department) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['部门不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $department->id,
|
||||
'name' => $department->name,
|
||||
'parent_id' => $department->parent_id,
|
||||
'parent' => $department->parent ? [
|
||||
'id' => $department->parent->id,
|
||||
'name' => $department->parent->name,
|
||||
] : null,
|
||||
'leader' => $department->leader,
|
||||
'phone' => $department->phone,
|
||||
'sort' => $department->sort,
|
||||
'status' => $department->status,
|
||||
'children_count' => $department->children()->count(),
|
||||
'users_count' => $department->users()->count(),
|
||||
'created_at' => $department->created_at->toDateTimeString(),
|
||||
'updated_at' => $department->updated_at->toDateTimeString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
public function create(array $data): Department
|
||||
{
|
||||
// 检查部门名称是否已存在
|
||||
$query = Department::where('name', $data['name']);
|
||||
if (!empty($data['parent_id'])) {
|
||||
$query->where('parent_id', $data['parent_id']);
|
||||
} else {
|
||||
$query->where('parent_id', 0);
|
||||
}
|
||||
if ($query->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'name' => ['同级部门名称已存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 如果有父级ID,检查父级是否存在
|
||||
if (!empty($data['parent_id'])) {
|
||||
$parent = Department::find($data['parent_id']);
|
||||
if (!$parent) {
|
||||
throw ValidationException::withMessages([
|
||||
'parent_id' => ['父级部门不存在'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return Department::create([
|
||||
'name' => $data['name'],
|
||||
'parent_id' => $data['parent_id'] ?? 0,
|
||||
'leader' => $data['leader'] ?? null,
|
||||
'phone' => $data['phone'] ?? null,
|
||||
'sort' => $data['sort'] ?? 0,
|
||||
'status' => $data['status'] ?? 1,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新部门
|
||||
*/
|
||||
public function update(int $id, array $data): Department
|
||||
{
|
||||
$department = Department::find($id);
|
||||
|
||||
if (!$department) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['部门不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查部门名称是否已被其他部门使用
|
||||
if (isset($data['name']) && $data['name'] !== $department->name) {
|
||||
$query = Department::where('name', $data['name'])
|
||||
->where('id', '!=', $id);
|
||||
$parentId = isset($data['parent_id']) ? $data['parent_id'] : $department->parent_id;
|
||||
if ($parentId) {
|
||||
$query->where('parent_id', $parentId);
|
||||
} else {
|
||||
$query->where('parent_id', 0);
|
||||
}
|
||||
if ($query->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'name' => ['同级部门名称已存在'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有父级ID,检查父级是否存在
|
||||
if (isset($data['parent_id']) && !empty($data['parent_id'])) {
|
||||
$parent = Department::find($data['parent_id']);
|
||||
if (!$parent) {
|
||||
throw ValidationException::withMessages([
|
||||
'parent_id' => ['父级部门不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 不能将部门设置为自己的子级
|
||||
if ($data['parent_id'] == $id) {
|
||||
throw ValidationException::withMessages([
|
||||
'parent_id' => ['不能将部门设置为自己的子级'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 不能将部门设置为自己的子孙级
|
||||
if ($this->isDescendant($id, $data['parent_id'])) {
|
||||
throw ValidationException::withMessages([
|
||||
'parent_id' => ['不能将部门设置为自己的子孙级'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$updateData = [
|
||||
'name' => $data['name'] ?? $department->name,
|
||||
'parent_id' => $data['parent_id'] ?? $department->parent_id,
|
||||
'leader' => $data['leader'] ?? $department->leader,
|
||||
'phone' => $data['phone'] ?? $department->phone,
|
||||
'sort' => $data['sort'] ?? $department->sort,
|
||||
'status' => $data['status'] ?? $department->status,
|
||||
];
|
||||
|
||||
$department->update($updateData);
|
||||
return $department;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
*/
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$department = Department::find($id);
|
||||
|
||||
if (!$department) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['部门不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查是否有子部门
|
||||
if ($department->children()->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['该部门下还有子部门,无法删除'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查部门下是否有用户
|
||||
if ($department->users()->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['该部门下还有用户,无法删除'],
|
||||
]);
|
||||
}
|
||||
|
||||
$department->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除部门
|
||||
*/
|
||||
public function batchDelete(array $ids): int
|
||||
{
|
||||
// 检查是否有子部门
|
||||
$hasChildren = Department::whereIn('id', $ids)->whereHas('children')->exists();
|
||||
if ($hasChildren) {
|
||||
throw ValidationException::withMessages([
|
||||
'ids' => ['选中的部门中还有子部门,无法删除'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查部门下是否有用户
|
||||
$hasUsers = Department::whereIn('id', $ids)->whereHas('users')->exists();
|
||||
if ($hasUsers) {
|
||||
throw ValidationException::withMessages([
|
||||
'ids' => ['选中的部门中还有用户,无法删除'],
|
||||
]);
|
||||
}
|
||||
|
||||
return Department::whereIn('id', $ids)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新部门状态
|
||||
*/
|
||||
public function batchUpdateStatus(array $ids, int $status): int
|
||||
{
|
||||
return Department::whereIn('id', $ids)->update(['status' => $status]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建部门树
|
||||
*/
|
||||
private function buildTree($departments, $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($departments as $department) {
|
||||
if ($department->parent_id == $parentId) {
|
||||
$node = [
|
||||
'id' => $department->id,
|
||||
'name' => $department->name,
|
||||
'parent_id' => $department->parent_id,
|
||||
'leader' => $department->leader,
|
||||
'phone' => $department->phone,
|
||||
'sort' => $department->sort,
|
||||
'status' => $department->status,
|
||||
'children' => $this->buildTree($departments, $department->id),
|
||||
];
|
||||
$tree[] = $node;
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为子孙部门
|
||||
*/
|
||||
private function isDescendant($id, $childId): bool
|
||||
{
|
||||
if ($id == $childId) {
|
||||
return true;
|
||||
}
|
||||
$child = Department::find($childId);
|
||||
if (!$child || $child->parent_id == 0) {
|
||||
return false;
|
||||
}
|
||||
return $this->isDescendant($id, $child->parent_id);
|
||||
}
|
||||
}
|
||||
201
app/Services/Auth/ImportExportService.php
Normal file
201
app/Services/Auth/ImportExportService.php
Normal file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Auth\Department;
|
||||
use App\Models\Auth\Role;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use App\Exports\UserExport;
|
||||
use App\Exports\DepartmentExport;
|
||||
use App\Imports\UserImport;
|
||||
use App\Imports\DepartmentImport;
|
||||
|
||||
class ImportExportService
|
||||
{
|
||||
/**
|
||||
* 下载用户导入模板
|
||||
*/
|
||||
public function downloadUserTemplate(): string
|
||||
{
|
||||
$filename = 'user_import_template_' . date('YmdHis') . '.xlsx';
|
||||
$path = storage_path('app/exports/' . $filename);
|
||||
|
||||
// 确保目录存在
|
||||
if (!is_dir(dirname($path))) {
|
||||
mkdir(dirname($path), 0755, true);
|
||||
}
|
||||
|
||||
// 使用模板数据创建Excel
|
||||
$templateData = [
|
||||
[
|
||||
'用户名*',
|
||||
'密码*',
|
||||
'真实姓名*',
|
||||
'邮箱',
|
||||
'手机号',
|
||||
'部门名称',
|
||||
'角色名称(多个用逗号分隔)',
|
||||
'备注',
|
||||
],
|
||||
[
|
||||
'test001',
|
||||
'123456',
|
||||
'测试用户001',
|
||||
'test001@example.com',
|
||||
'13800138001',
|
||||
'技术部',
|
||||
'管理员',
|
||||
'示例数据',
|
||||
],
|
||||
];
|
||||
|
||||
Excel::store(new \App\Exports\GenericExport($templateData), 'exports/' . $filename);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载部门导入模板
|
||||
*/
|
||||
public function downloadDepartmentTemplate(): string
|
||||
{
|
||||
$filename = 'department_import_template_' . date('YmdHis') . '.xlsx';
|
||||
|
||||
// 确保目录存在
|
||||
if (!is_dir(storage_path('app/exports'))) {
|
||||
mkdir(storage_path('app/exports'), 0755, true);
|
||||
}
|
||||
|
||||
$templateData = [
|
||||
[
|
||||
'部门名称*',
|
||||
'上级部门名称',
|
||||
'负责人',
|
||||
'联系电话',
|
||||
'排序',
|
||||
'备注',
|
||||
],
|
||||
[
|
||||
'前端开发组',
|
||||
'技术部',
|
||||
'张三',
|
||||
'13800138001',
|
||||
'1',
|
||||
'示例数据',
|
||||
],
|
||||
];
|
||||
|
||||
Excel::store(new \App\Exports\GenericExport($templateData), 'exports/' . $filename);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用户数据
|
||||
*/
|
||||
public function exportUsers(array $userIds = []): string
|
||||
{
|
||||
$filename = 'users_export_' . date('YmdHis') . '.xlsx';
|
||||
|
||||
// 确保目录存在
|
||||
if (!is_dir(storage_path('app/exports'))) {
|
||||
mkdir(storage_path('app/exports'), 0755, true);
|
||||
}
|
||||
|
||||
Excel::store(new UserExport($userIds), 'exports/' . $filename);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出部门数据
|
||||
*/
|
||||
public function exportDepartments(array $departmentIds = []): string
|
||||
{
|
||||
$filename = 'departments_export_' . date('YmdHis') . '.xlsx';
|
||||
|
||||
// 确保目录存在
|
||||
if (!is_dir(storage_path('app/exports'))) {
|
||||
mkdir(storage_path('app/exports'), 0755, true);
|
||||
}
|
||||
|
||||
Excel::store(new DepartmentExport($departmentIds), 'exports/' . $filename);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入用户数据
|
||||
*/
|
||||
public function importUsers(string $filePath, string $realPath): array
|
||||
{
|
||||
if (!file_exists($realPath)) {
|
||||
throw ValidationException::withMessages([
|
||||
'file' => ['文件不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
$import = new UserImport();
|
||||
Excel::import($import, $realPath);
|
||||
|
||||
// 删除临时文件
|
||||
if (file_exists($realPath)) {
|
||||
unlink($realPath);
|
||||
}
|
||||
|
||||
return [
|
||||
'success_count' => $import->getSuccessCount(),
|
||||
'error_count' => $import->getErrorCount(),
|
||||
'errors' => $import->getErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入部门数据
|
||||
*/
|
||||
public function importDepartments(string $filePath, string $realPath): array
|
||||
{
|
||||
if (!file_exists($realPath)) {
|
||||
throw ValidationException::withMessages([
|
||||
'file' => ['文件不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
$import = new DepartmentImport();
|
||||
Excel::import($import, $realPath);
|
||||
|
||||
// 删除临时文件
|
||||
if (file_exists($realPath)) {
|
||||
unlink($realPath);
|
||||
}
|
||||
|
||||
return [
|
||||
'success_count' => $import->getSuccessCount(),
|
||||
'error_count' => $import->getErrorCount(),
|
||||
'errors' => $import->getErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导出文件路径
|
||||
*/
|
||||
public function getExportFilePath(string $filename): string
|
||||
{
|
||||
return storage_path('app/exports/' . $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除导出文件
|
||||
*/
|
||||
public function deleteExportFile(string $filename): bool
|
||||
{
|
||||
$path = $this->getExportFilePath($filename);
|
||||
if (file_exists($path)) {
|
||||
return unlink($path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
256
app/Services/Auth/PermissionCacheService.php
Normal file
256
app/Services/Auth/PermissionCacheService.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Auth\Role;
|
||||
use App\Models\Auth\Permission;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class PermissionCacheService
|
||||
{
|
||||
protected $cachePrefix = 'permission:';
|
||||
protected $cacheMinutes = 60; // 缓存60分钟
|
||||
|
||||
/**
|
||||
* 获取用户的权限列表(带缓存)
|
||||
*/
|
||||
public function getUserPermissions(int $userId): array
|
||||
{
|
||||
$cacheKey = $this->getUserPermissionsCacheKey($userId);
|
||||
|
||||
return Cache::remember($cacheKey, now()->addMinutes($this->cacheMinutes), function() use ($userId) {
|
||||
$user = User::find($userId);
|
||||
if (!$user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$permissions = [];
|
||||
foreach ($user->roles as $role) {
|
||||
foreach ($role->permissions as $permission) {
|
||||
$permissions[$permission->id] = [
|
||||
'id' => $permission->id,
|
||||
'name' => $permission->name,
|
||||
'code' => $permission->code,
|
||||
'type' => $permission->type,
|
||||
'route' => $permission->route,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($permissions);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的权限编码列表(带缓存)
|
||||
*/
|
||||
public function getUserPermissionCodes(int $userId): array
|
||||
{
|
||||
$cacheKey = $this->getUserPermissionCodesCacheKey($userId);
|
||||
|
||||
return Cache::remember($cacheKey, now()->addMinutes($this->cacheMinutes), function() use ($userId) {
|
||||
$permissions = $this->getUserPermissions($userId);
|
||||
return array_column($permissions, 'code');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的菜单树(带缓存)
|
||||
*/
|
||||
public function getUserMenuTree(int $userId): array
|
||||
{
|
||||
$cacheKey = $this->getUserMenuTreeCacheKey($userId);
|
||||
|
||||
return Cache::remember($cacheKey, now()->addMinutes($this->cacheMinutes), function() use ($userId) {
|
||||
$user = User::find($userId);
|
||||
if (!$user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 获取用户的所有权限ID
|
||||
$permissionIds = [];
|
||||
foreach ($user->roles as $role) {
|
||||
foreach ($role->permissions as $permission) {
|
||||
$permissionIds[] = $permission->id;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取菜单类型的权限
|
||||
$permissions = Permission::whereIn('id', $permissionIds)
|
||||
->whereIn('type', ['menu', 'api'])
|
||||
->where('status', 1)
|
||||
->orderBy('sort', 'asc')
|
||||
->get();
|
||||
|
||||
return $this->buildMenuTree($permissions->toArray());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有某个权限(带缓存)
|
||||
*/
|
||||
public function userHasPermission(int $userId, string $permissionCode): bool
|
||||
{
|
||||
$codes = $this->getUserPermissionCodes($userId);
|
||||
return in_array($permissionCode, $codes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色的权限列表(带缓存)
|
||||
*/
|
||||
public function getRolePermissions(int $roleId): array
|
||||
{
|
||||
$cacheKey = $this->getRolePermissionsCacheKey($roleId);
|
||||
|
||||
return Cache::remember($cacheKey, now()->addMinutes($this->cacheMinutes), function() use ($roleId) {
|
||||
$role = Role::find($roleId);
|
||||
if (!$role) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $role->permissions->map(function($permission) {
|
||||
return [
|
||||
'id' => $permission->id,
|
||||
'name' => $permission->name,
|
||||
'code' => $permission->code,
|
||||
'type' => $permission->type,
|
||||
];
|
||||
})->toArray();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除用户权限缓存
|
||||
*/
|
||||
public function clearUserPermissionCache(int $userId): void
|
||||
{
|
||||
Cache::forget($this->getUserPermissionsCacheKey($userId));
|
||||
Cache::forget($this->getUserPermissionCodesCacheKey($userId));
|
||||
Cache::forget($this->getUserMenuTreeCacheKey($userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除角色权限缓存
|
||||
*/
|
||||
public function clearRolePermissionCache(int $roleId): void
|
||||
{
|
||||
Cache::forget($this->getRolePermissionsCacheKey($roleId));
|
||||
|
||||
// 清除所有拥有该角色的用户权限缓存
|
||||
$role = Role::find($roleId);
|
||||
if ($role) {
|
||||
foreach ($role->users as $user) {
|
||||
$this->clearUserPermissionCache($user->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有权限缓存
|
||||
*/
|
||||
public function clearAllPermissionCache(): void
|
||||
{
|
||||
if (Cache::getStore() instanceof \Illuminate\Cache\RedisStore) {
|
||||
$redis = Cache::getStore()->connection();
|
||||
$keys = $redis->keys($this->cachePrefix . '*');
|
||||
if (!empty($keys)) {
|
||||
$redis->del($keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定用户的权限缓存(当用户角色变化时调用)
|
||||
*/
|
||||
public function onUserRolesChanged(int $userId): void
|
||||
{
|
||||
$this->clearUserPermissionCache($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除角色的用户缓存(当角色权限变化时调用)
|
||||
*/
|
||||
public function onRolePermissionsChanged(int $roleId): void
|
||||
{
|
||||
$this->clearRolePermissionCache($roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除权限的所有缓存(当权限本身变化时调用)
|
||||
*/
|
||||
public function onPermissionChanged(int $permissionId): void
|
||||
{
|
||||
// 清除所有缓存,因为权限变化可能影响所有用户
|
||||
$this->clearAllPermissionCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建菜单树
|
||||
*/
|
||||
protected function buildMenuTree(array $permissions, int $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($permissions as $permission) {
|
||||
if ($permission['parent_id'] == $parentId) {
|
||||
$node = [
|
||||
'id' => $permission['id'],
|
||||
'name' => $permission['name'],
|
||||
'code' => $permission['code'],
|
||||
'type' => $permission['type'],
|
||||
'route' => $permission['route'],
|
||||
'component' => $permission['component'],
|
||||
'meta' => json_decode($permission['meta'] ?? '{}', true),
|
||||
'sort' => $permission['sort'],
|
||||
'children' => $this->buildMenuTree($permissions, $permission['id']),
|
||||
];
|
||||
|
||||
// 如果没有子节点,移除children字段
|
||||
if (empty($node['children'])) {
|
||||
unset($node['children']);
|
||||
}
|
||||
|
||||
$tree[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
// 按sort排序
|
||||
usort($tree, function($a, $b) {
|
||||
return $a['sort'] <=> $b['sort'];
|
||||
});
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成用户权限缓存键
|
||||
*/
|
||||
protected function getUserPermissionsCacheKey(int $userId): string
|
||||
{
|
||||
return $this->cachePrefix . 'user:' . $userId . ':permissions';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成用户权限编码缓存键
|
||||
*/
|
||||
protected function getUserPermissionCodesCacheKey(int $userId): string
|
||||
{
|
||||
return $this->cachePrefix . 'user:' . $userId . ':permission_codes';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成用户菜单树缓存键
|
||||
*/
|
||||
protected function getUserMenuTreeCacheKey(int $userId): string
|
||||
{
|
||||
return $this->cachePrefix . 'user:' . $userId . ':menu_tree';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成角色权限缓存键
|
||||
*/
|
||||
protected function getRolePermissionsCacheKey(int $roleId): string
|
||||
{
|
||||
return $this->cachePrefix . 'role:' . $roleId . ':permissions';
|
||||
}
|
||||
}
|
||||
323
app/Services/Auth/PermissionService.php
Normal file
323
app/Services/Auth/PermissionService.php
Normal file
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\Auth\Permission;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class PermissionService
|
||||
{
|
||||
/**
|
||||
* 获取权限列表
|
||||
*/
|
||||
public function getList(array $params): array
|
||||
{
|
||||
$query = Permission::query();
|
||||
|
||||
// 搜索条件
|
||||
if (!empty($params['keyword'])) {
|
||||
$query->where(function ($q) use ($params) {
|
||||
$q->where('name', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('code', 'like', '%' . $params['keyword'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($params['type'])) {
|
||||
$query->where('type', $params['type']);
|
||||
}
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
// 排序
|
||||
$orderBy = $params['order_by'] ?? 'sort';
|
||||
$orderDirection = $params['order_direction'] ?? 'asc';
|
||||
$query->orderBy($orderBy, $orderDirection);
|
||||
|
||||
// 分页
|
||||
$page = $params['page'] ?? 1;
|
||||
$pageSize = $params['page_size'] ?? 20;
|
||||
$list = $query->paginate($pageSize, ['*'], 'page', $page);
|
||||
|
||||
return [
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'page' => $page,
|
||||
'page_size' => $pageSize,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限树
|
||||
*/
|
||||
public function getTree(array $params = []): array
|
||||
{
|
||||
$query = Permission::query();
|
||||
|
||||
if (!empty($params['type'])) {
|
||||
$query->where('type', $params['type']);
|
||||
}
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
$permissions = $query->orderBy('sort', 'asc')->get();
|
||||
return $this->buildTree($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单树(前端使用)
|
||||
*/
|
||||
public function getMenuTree(int $userId = null): array
|
||||
{
|
||||
$query = Permission::whereIn('type', ['menu', 'api'])
|
||||
->where('status', 1);
|
||||
|
||||
if ($userId) {
|
||||
// 获取用户的权限
|
||||
$user = \App\Models\Auth\User::find($userId);
|
||||
if ($user) {
|
||||
$permissionIds = [];
|
||||
foreach ($user->roles as $role) {
|
||||
foreach ($role->permissions as $permission) {
|
||||
$permissionIds[] = $permission->id;
|
||||
}
|
||||
}
|
||||
$query->whereIn('id', $permissionIds);
|
||||
}
|
||||
}
|
||||
|
||||
$permissions = $query->orderBy('sort', 'asc')->get();
|
||||
return $this->buildTree($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限详情
|
||||
*/
|
||||
public function getById(int $id): array
|
||||
{
|
||||
$permission = Permission::with(['parent'])->find($id);
|
||||
|
||||
if (!$permission) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['权限不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $permission->id,
|
||||
'name' => $permission->name,
|
||||
'code' => $permission->code,
|
||||
'type' => $permission->type,
|
||||
'parent_id' => $permission->parent_id,
|
||||
'parent' => $permission->parent ? [
|
||||
'id' => $permission->parent->id,
|
||||
'name' => $permission->parent->name,
|
||||
] : null,
|
||||
'route' => $permission->route,
|
||||
'component' => $permission->component,
|
||||
'meta' => $permission->meta,
|
||||
'sort' => $permission->sort,
|
||||
'status' => $permission->status,
|
||||
'created_at' => $permission->created_at->toDateTimeString(),
|
||||
'updated_at' => $permission->updated_at->toDateTimeString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建权限
|
||||
*/
|
||||
public function create(array $data): Permission
|
||||
{
|
||||
// 检查权限名称是否已存在
|
||||
if (Permission::where('name', $data['name'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'name' => ['权限名称已存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查权限编码是否已存在
|
||||
if (Permission::where('code', $data['code'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'code' => ['权限编码已存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 如果有父级ID,检查父级是否存在
|
||||
if (!empty($data['parent_id'])) {
|
||||
$parent = Permission::find($data['parent_id']);
|
||||
if (!$parent) {
|
||||
throw ValidationException::withMessages([
|
||||
'parent_id' => ['父级权限不存在'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return Permission::create([
|
||||
'name' => $data['name'],
|
||||
'code' => $data['code'],
|
||||
'type' => $data['type'] ?? 'api',
|
||||
'parent_id' => $data['parent_id'] ?? 0,
|
||||
'route' => $data['route'] ?? null,
|
||||
'component' => $data['component'] ?? null,
|
||||
'meta' => $data['meta'] ?? null,
|
||||
'sort' => $data['sort'] ?? 0,
|
||||
'status' => $data['status'] ?? 1,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新权限
|
||||
*/
|
||||
public function update(int $id, array $data): Permission
|
||||
{
|
||||
$permission = Permission::find($id);
|
||||
|
||||
if (!$permission) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['权限不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查权限名称是否已被其他权限使用
|
||||
if (isset($data['name']) && $data['name'] !== $permission->name) {
|
||||
if (Permission::where('name', $data['name'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'name' => ['权限名称已存在'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查权限编码是否已被其他权限使用
|
||||
if (isset($data['code']) && $data['code'] !== $permission->code) {
|
||||
if (Permission::where('code', $data['code'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'code' => ['权限编码已存在'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有父级ID,检查父级是否存在
|
||||
if (isset($data['parent_id']) && !empty($data['parent_id'])) {
|
||||
$parent = Permission::find($data['parent_id']);
|
||||
if (!$parent) {
|
||||
throw ValidationException::withMessages([
|
||||
'parent_id' => ['父级权限不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 不能将权限设置为自己的子级
|
||||
if ($data['parent_id'] == $id) {
|
||||
throw ValidationException::withMessages([
|
||||
'parent_id' => ['不能将权限设置为自己的子级'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$updateData = [
|
||||
'name' => $data['name'] ?? $permission->name,
|
||||
'code' => $data['code'] ?? $permission->code,
|
||||
'type' => $data['type'] ?? $permission->type,
|
||||
'parent_id' => $data['parent_id'] ?? $permission->parent_id,
|
||||
'route' => $data['route'] ?? $permission->route,
|
||||
'component' => $data['component'] ?? $permission->component,
|
||||
'meta' => isset($data['meta']) ? $data['meta'] : $permission->meta,
|
||||
'sort' => $data['sort'] ?? $permission->sort,
|
||||
'status' => $data['status'] ?? $permission->status,
|
||||
];
|
||||
|
||||
$permission->update($updateData);
|
||||
return $permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除权限
|
||||
*/
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$permission = Permission::find($id);
|
||||
|
||||
if (!$permission) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['权限不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查是否有子权限
|
||||
if ($permission->children()->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['该权限下还有子权限,无法删除'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查是否被角色使用
|
||||
if ($permission->roles()->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['该权限已被角色使用,无法删除'],
|
||||
]);
|
||||
}
|
||||
|
||||
$permission->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除权限
|
||||
*/
|
||||
public function batchDelete(array $ids): int
|
||||
{
|
||||
// 检查是否有子权限
|
||||
$hasChildren = Permission::whereIn('id', $ids)->whereHas('children')->exists();
|
||||
if ($hasChildren) {
|
||||
throw ValidationException::withMessages([
|
||||
'ids' => ['选中的权限中还有子权限,无法删除'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查是否被角色使用
|
||||
$hasRoles = Permission::whereIn('id', $ids)->whereHas('roles')->exists();
|
||||
if ($hasRoles) {
|
||||
throw ValidationException::withMessages([
|
||||
'ids' => ['选中的权限中已被角色使用,无法删除'],
|
||||
]);
|
||||
}
|
||||
|
||||
return Permission::whereIn('id', $ids)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新权限状态
|
||||
*/
|
||||
public function batchUpdateStatus(array $ids, int $status): int
|
||||
{
|
||||
return Permission::whereIn('id', $ids)->update(['status' => $status]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建权限树
|
||||
*/
|
||||
private function buildTree($permissions, $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($permissions as $permission) {
|
||||
if ($permission->parent_id == $parentId) {
|
||||
$node = [
|
||||
'id' => $permission->id,
|
||||
'name' => $permission->name,
|
||||
'code' => $permission->code,
|
||||
'type' => $permission->type,
|
||||
'route' => $permission->route,
|
||||
'component' => $permission->component,
|
||||
'meta' => $permission->meta,
|
||||
'sort' => $permission->sort,
|
||||
'status' => $permission->status,
|
||||
'children' => $this->buildTree($permissions, $permission->id),
|
||||
];
|
||||
$tree[] = $node;
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
}
|
||||
430
app/Services/Auth/RoleService.php
Normal file
430
app/Services/Auth/RoleService.php
Normal file
@@ -0,0 +1,430 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\Auth\Role;
|
||||
use App\Models\Auth\Permission;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class RoleService
|
||||
{
|
||||
/**
|
||||
* 获取角色列表
|
||||
*/
|
||||
public function getList(array $params): array
|
||||
{
|
||||
$query = Role::query();
|
||||
|
||||
// 搜索条件
|
||||
if (!empty($params['keyword'])) {
|
||||
$query->where(function ($q) use ($params) {
|
||||
$q->where('name', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('code', 'like', '%' . $params['keyword'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
// 排序
|
||||
$orderBy = $params['order_by'] ?? 'sort';
|
||||
$orderDirection = $params['order_direction'] ?? 'asc';
|
||||
$query->orderBy($orderBy, $orderDirection);
|
||||
|
||||
// 分页
|
||||
$page = $params['page'] ?? 1;
|
||||
$pageSize = $params['page_size'] ?? 20;
|
||||
$list = $query->paginate($pageSize, ['*'], 'page', $page);
|
||||
|
||||
return [
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'page' => $page,
|
||||
'page_size' => $pageSize,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有角色(不分页)
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
$roles = Role::where('status', 1)->orderBy('sort', 'asc')->get();
|
||||
return $roles->map(function ($role) {
|
||||
return [
|
||||
'id' => $role->id,
|
||||
'name' => $role->name,
|
||||
'code' => $role->code,
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色详情
|
||||
*/
|
||||
public function getById(int $id): array
|
||||
{
|
||||
$role = Role::with(['permissions'])->find($id);
|
||||
|
||||
if (!$role) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['角色不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $role->id,
|
||||
'name' => $role->name,
|
||||
'code' => $role->code,
|
||||
'description' => $role->description,
|
||||
'sort' => $role->sort,
|
||||
'status' => $role->status,
|
||||
'permissions' => $role->permissions->pluck('id')->toArray(),
|
||||
'created_at' => $role->created_at->toDateTimeString(),
|
||||
'updated_at' => $role->updated_at->toDateTimeString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建角色
|
||||
*/
|
||||
public function create(array $data): Role
|
||||
{
|
||||
// 检查角色名称是否已存在
|
||||
if (Role::where('name', $data['name'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'name' => ['角色名称已存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查角色编码是否已存在
|
||||
if (Role::where('code', $data['code'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'code' => ['角色编码已存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$role = Role::create([
|
||||
'name' => $data['name'],
|
||||
'code' => $data['code'],
|
||||
'description' => $data['description'] ?? null,
|
||||
'sort' => $data['sort'] ?? 0,
|
||||
'status' => $data['status'] ?? 1,
|
||||
]);
|
||||
|
||||
// 关联权限
|
||||
if (!empty($data['permission_ids'])) {
|
||||
$role->permissions()->attach($data['permission_ids']);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return $role;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色
|
||||
*/
|
||||
public function update(int $id, array $data): Role
|
||||
{
|
||||
$role = Role::find($id);
|
||||
|
||||
if (!$role) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['角色不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查角色名称是否已被其他角色使用
|
||||
if (isset($data['name']) && $data['name'] !== $role->name) {
|
||||
if (Role::where('name', $data['name'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'name' => ['角色名称已存在'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查角色编码是否已被其他角色使用
|
||||
if (isset($data['code']) && $data['code'] !== $role->code) {
|
||||
if (Role::where('code', $data['code'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'code' => ['角色编码已存在'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$updateData = [
|
||||
'name' => $data['name'] ?? $role->name,
|
||||
'code' => $data['code'] ?? $role->code,
|
||||
'description' => $data['description'] ?? $role->description,
|
||||
'sort' => $data['sort'] ?? $role->sort,
|
||||
'status' => $data['status'] ?? $role->status,
|
||||
];
|
||||
|
||||
$role->update($updateData);
|
||||
|
||||
// 更新权限关联
|
||||
if (isset($data['permission_ids'])) {
|
||||
$role->permissions()->sync($data['permission_ids']);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return $role;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
*/
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$role = Role::find($id);
|
||||
|
||||
if (!$role) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['角色不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查角色下是否有用户
|
||||
if ($role->users()->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['该角色下还有用户,无法删除'],
|
||||
]);
|
||||
}
|
||||
|
||||
$role->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除角色
|
||||
*/
|
||||
public function batchDelete(array $ids): int
|
||||
{
|
||||
// 检查角色下是否有用户
|
||||
$hasUsers = Role::whereIn('id', $ids)->whereHas('users')->exists();
|
||||
if ($hasUsers) {
|
||||
throw ValidationException::withMessages([
|
||||
'ids' => ['选中的角色中还有用户,无法删除'],
|
||||
]);
|
||||
}
|
||||
|
||||
return Role::whereIn('id', $ids)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新角色状态
|
||||
*/
|
||||
public function batchUpdateStatus(array $ids, int $status): int
|
||||
{
|
||||
return Role::whereIn('id', $ids)->update(['status' => $status]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配权限
|
||||
*/
|
||||
public function assignPermissions(int $id, array $permissionIds): void
|
||||
{
|
||||
$role = Role::find($id);
|
||||
|
||||
if (!$role) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['角色不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
$role->permissions()->sync($permissionIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色的权限列表
|
||||
*/
|
||||
public function getPermissions(int $id): array
|
||||
{
|
||||
$role = Role::with(['permissions' => function ($query) {
|
||||
$query->orderBy('sort', 'asc');
|
||||
}])->find($id);
|
||||
|
||||
if (!$role) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['角色不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->buildPermissionTree($role->permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制角色
|
||||
*/
|
||||
public function copy(int $id, array $data): Role
|
||||
{
|
||||
$sourceRole = Role::with(['permissions'])->find($id);
|
||||
|
||||
if (!$sourceRole) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['角色不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查新角色名称是否已存在
|
||||
if (Role::where('name', $data['name'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'name' => ['角色名称已存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查新角色编码是否已存在
|
||||
if (Role::where('code', $data['code'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'code' => ['角色编码已存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// 创建新角色
|
||||
$newRole = Role::create([
|
||||
'name' => $data['name'],
|
||||
'code' => $data['code'],
|
||||
'description' => $data['description'] ?? $sourceRole->description,
|
||||
'sort' => $data['sort'] ?? $sourceRole->sort,
|
||||
'status' => $data['status'] ?? 1,
|
||||
]);
|
||||
|
||||
// 复制权限
|
||||
$permissionIds = $sourceRole->permissions->pluck('id')->toArray();
|
||||
if (!empty($permissionIds)) {
|
||||
$newRole->permissions()->attach($permissionIds);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return $newRole;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量复制角色
|
||||
*/
|
||||
public function batchCopy(array $roleIds, array $data): array
|
||||
{
|
||||
$successCount = 0;
|
||||
$errorCount = 0;
|
||||
$errors = [];
|
||||
$newRoles = [];
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach ($roleIds as $index => $roleId) {
|
||||
try {
|
||||
$sourceRole = Role::with(['permissions'])->find($roleId);
|
||||
|
||||
if (!$sourceRole) {
|
||||
$errors[] = "角色ID {$roleId} 不存在";
|
||||
$errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 生成新的名称和编码
|
||||
$newName = $data['name'] ?? ($sourceRole->name . ' (副本)');
|
||||
$newCode = $data['code'] ?? ($sourceRole->code . '_copy_' . time());
|
||||
|
||||
// 检查名称和编码是否已存在
|
||||
$nameSuffix = '';
|
||||
$codeSuffix = '';
|
||||
$counter = 1;
|
||||
|
||||
while (Role::where('name', $newName . $nameSuffix)->exists()) {
|
||||
$nameSuffix = ' (' . $counter . ')';
|
||||
$counter++;
|
||||
}
|
||||
|
||||
$counter = 1;
|
||||
while (Role::where('code', $newCode . $codeSuffix)->exists()) {
|
||||
$codeSuffix = '_' . $counter;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
// 创建新角色
|
||||
$newRole = Role::create([
|
||||
'name' => $newName . $nameSuffix,
|
||||
'code' => $newCode . $codeSuffix,
|
||||
'description' => $data['description'] ?? $sourceRole->description,
|
||||
'sort' => $data['sort'] ?? $sourceRole->sort,
|
||||
'status' => $data['status'] ?? 1,
|
||||
]);
|
||||
|
||||
// 复制权限
|
||||
$permissionIds = $sourceRole->permissions->pluck('id')->toArray();
|
||||
if (!empty($permissionIds)) {
|
||||
$newRole->permissions()->attach($permissionIds);
|
||||
}
|
||||
|
||||
$newRoles[] = [
|
||||
'id' => $newRole->id,
|
||||
'name' => $newRole->name,
|
||||
'code' => $newRole->code,
|
||||
];
|
||||
|
||||
$successCount++;
|
||||
} catch (\Exception $e) {
|
||||
$errors[] = "复制角色ID {$roleId} 失败:" . $e->getMessage();
|
||||
$errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return [
|
||||
'success_count' => $successCount,
|
||||
'error_count' => $errorCount,
|
||||
'errors' => $errors,
|
||||
'new_roles' => $newRoles,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建权限树
|
||||
*/
|
||||
private function buildPermissionTree($permissions, $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($permissions as $permission) {
|
||||
if ($permission->parent_id == $parentId) {
|
||||
$node = [
|
||||
'id' => $permission->id,
|
||||
'name' => $permission->name,
|
||||
'code' => $permission->code,
|
||||
'type' => $permission->type,
|
||||
'route' => $permission->route,
|
||||
'component' => $permission->component,
|
||||
'meta' => $permission->meta,
|
||||
'sort' => $permission->sort,
|
||||
'status' => $permission->status,
|
||||
'children' => $this->buildPermissionTree($permissions, $permission->id),
|
||||
];
|
||||
$tree[] = $node;
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
}
|
||||
208
app/Services/Auth/UserOnlineService.php
Normal file
208
app/Services/Auth/UserOnlineService.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class UserOnlineService
|
||||
{
|
||||
protected $cachePrefix = 'user_online:';
|
||||
protected $expireMinutes = 5;
|
||||
|
||||
/**
|
||||
* 设置用户在线
|
||||
*/
|
||||
public function setOnline(int $userId, string $token): void
|
||||
{
|
||||
$key = $this->getCacheKey($userId, $token);
|
||||
Cache::put($key, [
|
||||
'user_id' => $userId,
|
||||
'token' => $token,
|
||||
'last_active_at' => now()->toDateTimeString(),
|
||||
'ip' => request()->ip(),
|
||||
'user_agent' => request()->userAgent(),
|
||||
], now()->addMinutes($this->expireMinutes));
|
||||
|
||||
// 更新用户的最后在线时间
|
||||
User::where('id', $userId)->update([
|
||||
'last_active_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户在线状态
|
||||
*/
|
||||
public function updateOnline(int $userId, string $token): void
|
||||
{
|
||||
$key = $this->getCacheKey($userId, $token);
|
||||
if (Cache::has($key)) {
|
||||
Cache::put($key, [
|
||||
'user_id' => $userId,
|
||||
'token' => $token,
|
||||
'last_active_at' => now()->toDateTimeString(),
|
||||
'ip' => request()->ip(),
|
||||
'user_agent' => request()->userAgent(),
|
||||
], now()->addMinutes($this->expireMinutes));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户离线
|
||||
*/
|
||||
public function setOffline(int $userId, string $token): void
|
||||
{
|
||||
$key = $this->getCacheKey($userId, $token);
|
||||
Cache::forget($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户所有设备离线
|
||||
*/
|
||||
public function setAllOffline(int $userId): void
|
||||
{
|
||||
$pattern = $this->cachePrefix . $userId . ':*';
|
||||
$keys = Cache::store('redis')->getPrefix() . $pattern;
|
||||
|
||||
// Redis 模式删除
|
||||
if (Cache::getStore() instanceof \Illuminate\Cache\RedisStore) {
|
||||
$redis = Cache::getStore()->connection();
|
||||
$keys = $redis->keys($this->cachePrefix . $userId . ':*');
|
||||
if (!empty($keys)) {
|
||||
$redis->del($keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否在线
|
||||
*/
|
||||
public function isOnline(int $userId): bool
|
||||
{
|
||||
$pattern = $this->cachePrefix . $userId . ':*';
|
||||
|
||||
if (Cache::getStore() instanceof \Illuminate\Cache\RedisStore) {
|
||||
$redis = Cache::getStore()->connection();
|
||||
$keys = $redis->keys($this->cachePrefix . $userId . ':*');
|
||||
return !empty($keys);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户在线信息
|
||||
*/
|
||||
public function getOnlineInfo(int $userId, string $token): ?array
|
||||
{
|
||||
$key = $this->getCacheKey($userId, $token);
|
||||
return Cache::get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有在线会话
|
||||
*/
|
||||
public function getUserSessions(int $userId): array
|
||||
{
|
||||
$sessions = [];
|
||||
|
||||
if (Cache::getStore() instanceof \Illuminate\Cache\RedisStore) {
|
||||
$redis = Cache::getStore()->connection();
|
||||
$keys = $redis->keys($this->cachePrefix . $userId . ':*');
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$session = $redis->get($key);
|
||||
if ($session) {
|
||||
$sessions[] = json_decode($session, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有在线用户数量
|
||||
*/
|
||||
public function getOnlineCount(): int
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
if (Cache::getStore() instanceof \Illuminate\Cache\RedisStore) {
|
||||
$redis = Cache::getStore()->connection();
|
||||
$keys = $redis->keys($this->cachePrefix . '*');
|
||||
// 去重用户ID
|
||||
$userIds = [];
|
||||
foreach ($keys as $key) {
|
||||
preg_match('/user_online:(\d+):/', $key, $matches);
|
||||
if (isset($matches[1])) {
|
||||
$userIds[$matches[1]] = true;
|
||||
}
|
||||
}
|
||||
$count = count($userIds);
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线用户列表
|
||||
*/
|
||||
public function getOnlineUsers(int $limit = 100): array
|
||||
{
|
||||
$onlineUsers = [];
|
||||
|
||||
if (Cache::getStore() instanceof \Illuminate\Cache\RedisStore) {
|
||||
$redis = Cache::getStore()->connection();
|
||||
$keys = $redis->keys($this->cachePrefix . '*');
|
||||
|
||||
$userIds = [];
|
||||
$userSessions = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$session = $redis->get($key);
|
||||
if ($session) {
|
||||
$session = json_decode($session, true);
|
||||
$userId = $session['user_id'];
|
||||
if (!isset($userIds[$userId])) {
|
||||
$userIds[$userId] = $userId;
|
||||
}
|
||||
$userSessions[$userId] = $session;
|
||||
}
|
||||
}
|
||||
|
||||
$userIdList = array_values(array_slice($userIds, 0, $limit));
|
||||
$users = User::whereIn('id', $userIdList)->get();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$onlineUsers[] = [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'real_name' => $user->real_name,
|
||||
'avatar' => $user->avatar,
|
||||
'last_active_at' => $userSessions[$user->id]['last_active_at'] ?? null,
|
||||
'ip' => $userSessions[$user->id]['ip'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $onlineUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期会话
|
||||
*/
|
||||
public function cleanExpiredSessions(): int
|
||||
{
|
||||
// Redis 的 TTL 会自动清理过期键
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缓存键
|
||||
*/
|
||||
protected function getCacheKey(int $userId, string $token): string
|
||||
{
|
||||
return $this->cachePrefix . $userId . ':' . md5($token);
|
||||
}
|
||||
}
|
||||
320
app/Services/Auth/UserService.php
Normal file
320
app/Services/Auth/UserService.php
Normal file
@@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use App\Exports\Auth\UserExport;
|
||||
use App\Imports\Auth\UserImport;
|
||||
use App\Jobs\Auth\UserImportJob;
|
||||
use App\Jobs\Auth\UserExportJob;
|
||||
|
||||
class UserService
|
||||
{
|
||||
/**
|
||||
* 获取用户列表
|
||||
*/
|
||||
public function getList(array $params): array
|
||||
{
|
||||
$query = User::with(['department', 'roles']);
|
||||
|
||||
// 搜索条件
|
||||
if (!empty($params['keyword'])) {
|
||||
$query->where(function ($q) use ($params) {
|
||||
$q->where('username', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('real_name', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('phone', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('email', 'like', '%' . $params['keyword'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($params['department_id'])) {
|
||||
$query->where('department_id', $params['department_id']);
|
||||
}
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
if (!empty($params['role_id'])) {
|
||||
$query->whereHas('roles', function ($q) use ($params) {
|
||||
$q->where('role_id', $params['role_id']);
|
||||
});
|
||||
}
|
||||
|
||||
// 排序
|
||||
$orderBy = $params['order_by'] ?? 'id';
|
||||
$orderDirection = $params['order_direction'] ?? 'desc';
|
||||
$query->orderBy($orderBy, $orderDirection);
|
||||
|
||||
// 分页
|
||||
$page = $params['page'] ?? 1;
|
||||
$pageSize = $params['page_size'] ?? 20;
|
||||
$list = $query->paginate($pageSize, ['*'], 'page', $page);
|
||||
|
||||
return [
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'page' => $page,
|
||||
'page_size' => $pageSize,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
*/
|
||||
public function getById(int $id): array
|
||||
{
|
||||
$user = User::with(['department', 'roles'])->find($id);
|
||||
|
||||
if (!$user) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['用户不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->formatUserInfo($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
*/
|
||||
public function create(array $data): User
|
||||
{
|
||||
// 检查用户名是否已存在
|
||||
if (User::where('username', $data['username'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'username' => ['用户名已存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查手机号是否已存在
|
||||
if (!empty($data['phone']) && User::where('phone', $data['phone'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'phone' => ['手机号已被使用'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if (!empty($data['email']) && User::where('email', $data['email'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => ['邮箱已被使用'],
|
||||
]);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$user = User::create([
|
||||
'username' => $data['username'],
|
||||
'password' => Hash::make($data['password']),
|
||||
'real_name' => $data['real_name'],
|
||||
'email' => $data['email'] ?? null,
|
||||
'phone' => $data['phone'] ?? null,
|
||||
'department_id' => $data['department_id'] ?? null,
|
||||
'avatar' => $data['avatar'] ?? null,
|
||||
'status' => $data['status'] ?? 1,
|
||||
]);
|
||||
|
||||
// 关联角色
|
||||
if (!empty($data['role_ids'])) {
|
||||
$user->roles()->attach($data['role_ids']);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return $user;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*/
|
||||
public function update(int $id, array $data): User
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['用户不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查用户名是否已被其他用户使用
|
||||
if (isset($data['username']) && $data['username'] !== $user->username) {
|
||||
if (User::where('username', $data['username'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'username' => ['用户名已存在'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查手机号是否已被其他用户使用
|
||||
if (isset($data['phone']) && $data['phone'] !== $user->phone) {
|
||||
if (User::where('phone', $data['phone'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'phone' => ['手机号已被使用'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查邮箱是否已被其他用户使用
|
||||
if (isset($data['email']) && $data['email'] !== $user->email) {
|
||||
if (User::where('email', $data['email'])->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => ['邮箱已被使用'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$updateData = [
|
||||
'real_name' => $data['real_name'] ?? $user->real_name,
|
||||
'email' => $data['email'] ?? $user->email,
|
||||
'phone' => $data['phone'] ?? $user->phone,
|
||||
'department_id' => $data['department_id'] ?? $user->department_id,
|
||||
'avatar' => $data['avatar'] ?? $user->avatar,
|
||||
'status' => $data['status'] ?? $user->status,
|
||||
];
|
||||
|
||||
if (isset($data['username'])) {
|
||||
$updateData['username'] = $data['username'];
|
||||
}
|
||||
|
||||
if (isset($data['password'])) {
|
||||
$updateData['password'] = Hash::make($data['password']);
|
||||
}
|
||||
|
||||
$user->update($updateData);
|
||||
|
||||
// 更新角色关联
|
||||
if (isset($data['role_ids'])) {
|
||||
$user->roles()->sync($data['role_ids']);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return $user;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
throw ValidationException::withMessages([
|
||||
'id' => ['用户不存在'],
|
||||
]);
|
||||
}
|
||||
|
||||
$user->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除用户
|
||||
*/
|
||||
public function batchDelete(array $ids): int
|
||||
{
|
||||
return User::whereIn('id', $ids)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新用户状态
|
||||
*/
|
||||
public function batchUpdateStatus(array $ids, int $status): int
|
||||
{
|
||||
return User::whereIn('id', $ids)->update(['status' => $status]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量分配部门
|
||||
*/
|
||||
public function batchAssignDepartment(array $ids, int $departmentId): int
|
||||
{
|
||||
return User::whereIn('id', $ids)->update(['department_id' => $departmentId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量分配角色
|
||||
*/
|
||||
public function batchAssignRoles(array $ids, array $roleIds): void
|
||||
{
|
||||
foreach ($ids as $userId) {
|
||||
$user = User::find($userId);
|
||||
if ($user) {
|
||||
$user->roles()->sync($roleIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用户数据
|
||||
*/
|
||||
public function export(array $params): string
|
||||
{
|
||||
// 异步导出
|
||||
$job = new UserExportJob($params);
|
||||
dispatch($job);
|
||||
|
||||
return '导出任务已提交,请稍后在导出列表中查看';
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入用户数据
|
||||
*/
|
||||
public function import(\Illuminate\Http\UploadedFile $file): array
|
||||
{
|
||||
$path = $file->store('imports');
|
||||
|
||||
// 异步导入
|
||||
$job = new UserImportJob($path);
|
||||
dispatch($job);
|
||||
|
||||
return [
|
||||
'message' => '导入任务已提交,请稍后在导入列表中查看',
|
||||
'file_path' => $path,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化用户信息
|
||||
*/
|
||||
private function formatUserInfo(User $user): array
|
||||
{
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'real_name' => $user->real_name,
|
||||
'email' => $user->email,
|
||||
'phone' => $user->phone,
|
||||
'avatar' => $user->avatar,
|
||||
'department' => $user->department ? [
|
||||
'id' => $user->department->id,
|
||||
'name' => $user->department->name,
|
||||
] : null,
|
||||
'roles' => $user->roles->map(function ($role) {
|
||||
return [
|
||||
'id' => $role->id,
|
||||
'name' => $role->name,
|
||||
'code' => $role->code,
|
||||
];
|
||||
})->toArray(),
|
||||
'status' => $user->status,
|
||||
'last_login_at' => $user->last_login_at ? $user->last_login_at->toDateTimeString() : null,
|
||||
'last_login_ip' => $user->last_login_ip,
|
||||
'created_at' => $user->created_at->toDateTimeString(),
|
||||
'updated_at' => $user->updated_at->toDateTimeString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
198
app/Services/System/CityService.php
Normal file
198
app/Services/System/CityService.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\System;
|
||||
|
||||
use App\Models\System\City;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class CityService
|
||||
{
|
||||
public function getList(array $params): array
|
||||
{
|
||||
$query = City::query();
|
||||
|
||||
if (!empty($params['parent_id'])) {
|
||||
$query->where('parent_id', $params['parent_id']);
|
||||
}
|
||||
|
||||
if (!empty($params['level'])) {
|
||||
$query->where('level', $params['level']);
|
||||
}
|
||||
|
||||
if (!empty($params['keyword'])) {
|
||||
$query->where(function ($q) use ($params) {
|
||||
$q->where('name', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('code', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('pinyin', 'like', '%' . $params['keyword'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
$query->orderBy('sort')->orderBy('id');
|
||||
|
||||
$pageSize = $params['page_size'] ?? 20;
|
||||
$list = $query->paginate($pageSize);
|
||||
|
||||
return [
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'page' => $list->currentPage(),
|
||||
'page_size' => $list->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTree(): array
|
||||
{
|
||||
return $this->buildTree(City::where('status', true)->orderBy('sort')->get());
|
||||
}
|
||||
|
||||
public function getChildren(int $parentId): array
|
||||
{
|
||||
return City::where('parent_id', $parentId)
|
||||
->where('status', true)
|
||||
->orderBy('sort')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getByCode(string $code): ?City
|
||||
{
|
||||
return City::where('code', $code)->first();
|
||||
}
|
||||
|
||||
public function getById(int $id): ?City
|
||||
{
|
||||
return City::find($id);
|
||||
}
|
||||
|
||||
public function getByPinyin(string $pinyin): array
|
||||
{
|
||||
return City::where('pinyin', 'like', '%' . $pinyin . '%')
|
||||
->where('status', true)
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function create(array $data): City
|
||||
{
|
||||
Validator::make($data, [
|
||||
'name' => 'required|string|max:100',
|
||||
'code' => 'required|string|max:50|unique:system_cities,code',
|
||||
'level' => 'required|integer|in:1,2,3',
|
||||
'parent_id' => 'sometimes|exists:system_cities,id',
|
||||
])->validate();
|
||||
|
||||
$city = City::create($data);
|
||||
$this->clearCache();
|
||||
return $city;
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): City
|
||||
{
|
||||
$city = City::findOrFail($id);
|
||||
|
||||
Validator::make($data, [
|
||||
'name' => 'sometimes|required|string|max:100',
|
||||
'code' => 'sometimes|required|string|max:50|unique:system_cities,code,' . $id,
|
||||
'level' => 'sometimes|required|integer|in:1,2,3',
|
||||
'parent_id' => 'sometimes|exists:system_cities,id',
|
||||
])->validate();
|
||||
|
||||
$city->update($data);
|
||||
$this->clearCache();
|
||||
return $city;
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$city = City::findOrFail($id);
|
||||
|
||||
if ($city->children()->exists()) {
|
||||
throw new \Exception('该城市下有子级数据,不能删除');
|
||||
}
|
||||
|
||||
$city->delete();
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function batchDelete(array $ids): bool
|
||||
{
|
||||
City::whereIn('id', $ids)->delete();
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function batchUpdateStatus(array $ids, bool $status): bool
|
||||
{
|
||||
City::whereIn('id', $ids)->update(['status' => $status]);
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function buildTree(array $cities, int $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($cities as $city) {
|
||||
if ($city['parent_id'] == $parentId) {
|
||||
$children = $this->buildTree($cities, $city['id']);
|
||||
if (!empty($children)) {
|
||||
$city['children'] = $children;
|
||||
}
|
||||
$tree[] = $city;
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
private function clearCache(): void
|
||||
{
|
||||
Cache::forget('system:cities:tree');
|
||||
}
|
||||
|
||||
public function getCachedTree(): array
|
||||
{
|
||||
$cacheKey = 'system:cities:tree';
|
||||
$tree = Cache::get($cacheKey);
|
||||
|
||||
if ($tree === null) {
|
||||
$tree = $this->getTree();
|
||||
Cache::put($cacheKey, $tree, 3600);
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
public function getProvinces(): array
|
||||
{
|
||||
return City::where('level', 1)
|
||||
->where('status', true)
|
||||
->orderBy('sort')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getCities(int $provinceId): array
|
||||
{
|
||||
return City::where('parent_id', $provinceId)
|
||||
->where('level', 2)
|
||||
->where('status', true)
|
||||
->orderBy('sort')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getDistricts(int $cityId): array
|
||||
{
|
||||
return City::where('parent_id', $cityId)
|
||||
->where('level', 3)
|
||||
->where('status', true)
|
||||
->orderBy('sort')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
158
app/Services/System/ConfigService.php
Normal file
158
app/Services/System/ConfigService.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\System;
|
||||
|
||||
use App\Models\System\Config;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ConfigService
|
||||
{
|
||||
public function getList(array $params): array
|
||||
{
|
||||
$query = Config::query();
|
||||
|
||||
if (!empty($params['group'])) {
|
||||
$query->where('group', $params['group']);
|
||||
}
|
||||
|
||||
if (!empty($params['keyword'])) {
|
||||
$query->where(function ($q) use ($params) {
|
||||
$q->where('name', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('key', 'like', '%' . $params['keyword'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
$pageSize = $params['page_size'] ?? 20;
|
||||
$list = $query->orderBy('sort')->orderBy('id')->paginate($pageSize);
|
||||
|
||||
return [
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'page' => $list->currentPage(),
|
||||
'page_size' => $list->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getById(int $id): ?Config
|
||||
{
|
||||
return Config::find($id);
|
||||
}
|
||||
|
||||
public function getByKey(string $key): ?Config
|
||||
{
|
||||
return Config::where('key', $key)->first();
|
||||
}
|
||||
|
||||
public function getByGroup(string $group): array
|
||||
{
|
||||
return Config::where('group', $group)
|
||||
->where('status', true)
|
||||
->orderBy('sort')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getAllConfig(): array
|
||||
{
|
||||
$cacheKey = 'system:configs:all';
|
||||
$configs = Cache::get($cacheKey);
|
||||
|
||||
if ($configs === null) {
|
||||
$configs = Config::where('status', true)
|
||||
->orderBy('sort')
|
||||
->get()
|
||||
->keyBy('key')
|
||||
->toArray();
|
||||
Cache::put($cacheKey, $configs, 3600);
|
||||
}
|
||||
|
||||
return $configs;
|
||||
}
|
||||
|
||||
public function getConfigValue(string $key, $default = null)
|
||||
{
|
||||
$config = $this->getByKey($key);
|
||||
if (!$config) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $config->value ?? $config->default_value ?? $default;
|
||||
}
|
||||
|
||||
public function create(array $data): Config
|
||||
{
|
||||
Validator::make($data, [
|
||||
'group' => 'required|string|max:50',
|
||||
'key' => 'required|string|max:100|unique:system_configs,key',
|
||||
'name' => 'required|string|max:100',
|
||||
'type' => 'required|string|in:string,text,number,boolean,select,radio,checkbox,file,json',
|
||||
])->validate();
|
||||
|
||||
$config = Config::create($data);
|
||||
$this->clearCache();
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): Config
|
||||
{
|
||||
$config = Config::findOrFail($id);
|
||||
|
||||
Validator::make($data, [
|
||||
'group' => 'sometimes|required|string|max:50',
|
||||
'key' => 'sometimes|required|string|max:100|unique:system_configs,key,' . $id,
|
||||
'name' => 'sometimes|required|string|max:100',
|
||||
'type' => 'sometimes|required|string|in:string,text,number,boolean,select,radio,checkbox,file,json',
|
||||
])->validate();
|
||||
|
||||
$config->update($data);
|
||||
$this->clearCache();
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$config = Config::findOrFail($id);
|
||||
|
||||
if ($config->is_system) {
|
||||
throw new \Exception('系统配置不能删除');
|
||||
}
|
||||
|
||||
$config->delete();
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function batchDelete(array $ids): bool
|
||||
{
|
||||
$configs = Config::whereIn('id', $ids)->where('is_system', false)->get();
|
||||
Config::whereIn('id', $configs->pluck('id'))->delete();
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function batchUpdateStatus(array $ids, bool $status): bool
|
||||
{
|
||||
Config::whereIn('id', $ids)->update(['status' => $status]);
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function clearCache(): void
|
||||
{
|
||||
Cache::forget('system:configs:all');
|
||||
}
|
||||
|
||||
public function getGroups(): array
|
||||
{
|
||||
return Config::where('status', true)
|
||||
->select('group')
|
||||
->distinct()
|
||||
->pluck('group')
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
210
app/Services/System/DictionaryService.php
Normal file
210
app/Services/System/DictionaryService.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\System;
|
||||
|
||||
use App\Models\System\Dictionary;
|
||||
use App\Models\System\DictionaryItem;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class DictionaryService
|
||||
{
|
||||
public function getList(array $params): array
|
||||
{
|
||||
$query = Dictionary::query();
|
||||
|
||||
if (!empty($params['keyword'])) {
|
||||
$query->where(function ($q) use ($params) {
|
||||
$q->where('name', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('code', 'like', '%' . $params['keyword'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
$pageSize = $params['page_size'] ?? 20;
|
||||
$list = $query->orderBy('sort')->orderBy('id')->paginate($pageSize);
|
||||
|
||||
return [
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'page' => $list->currentPage(),
|
||||
'page_size' => $list->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getAll(): array
|
||||
{
|
||||
return Dictionary::where('status', true)
|
||||
->orderBy('sort')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getById(int $id): ?Dictionary
|
||||
{
|
||||
return Dictionary::with('items')->find($id);
|
||||
}
|
||||
|
||||
public function getByCode(string $code): ?Dictionary
|
||||
{
|
||||
return Dictionary::where('code', $code)->first();
|
||||
}
|
||||
|
||||
public function getItemsByCode(string $code): array
|
||||
{
|
||||
$cacheKey = 'system:dictionary:' . $code;
|
||||
$items = Cache::get($cacheKey);
|
||||
|
||||
if ($items === null) {
|
||||
$dictionary = Dictionary::where('code', $code)->first();
|
||||
if ($dictionary) {
|
||||
$items = DictionaryItem::where('dictionary_id', $dictionary->id)
|
||||
->where('status', true)
|
||||
->orderBy('sort')
|
||||
->get()
|
||||
->toArray();
|
||||
Cache::put($cacheKey, $items, 3600);
|
||||
} else {
|
||||
$items = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function create(array $data): Dictionary
|
||||
{
|
||||
Validator::make($data, [
|
||||
'name' => 'required|string|max:100',
|
||||
'code' => 'required|string|max:50|unique:system_dictionaries,code',
|
||||
])->validate();
|
||||
|
||||
$dictionary = Dictionary::create($data);
|
||||
$this->clearCache();
|
||||
return $dictionary;
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): Dictionary
|
||||
{
|
||||
$dictionary = Dictionary::findOrFail($id);
|
||||
|
||||
Validator::make($data, [
|
||||
'name' => 'sometimes|required|string|max:100',
|
||||
'code' => 'sometimes|required|string|max:50|unique:system_dictionaries,code,' . $id,
|
||||
])->validate();
|
||||
|
||||
$dictionary->update($data);
|
||||
$this->clearCache();
|
||||
return $dictionary;
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$dictionary = Dictionary::findOrFail($id);
|
||||
DictionaryItem::where('dictionary_id', $id)->delete();
|
||||
$dictionary->delete();
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function batchDelete(array $ids): bool
|
||||
{
|
||||
DictionaryItem::whereIn('dictionary_id', $ids)->delete();
|
||||
Dictionary::whereIn('id', $ids)->delete();
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function batchUpdateStatus(array $ids, bool $status): bool
|
||||
{
|
||||
Dictionary::whereIn('id', $ids)->update(['status' => $status]);
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function clearCache(): void
|
||||
{
|
||||
$codes = Dictionary::pluck('code')->toArray();
|
||||
foreach ($codes as $code) {
|
||||
Cache::forget('system:dictionary:' . $code);
|
||||
}
|
||||
}
|
||||
|
||||
public function getItemsList(array $params): array
|
||||
{
|
||||
$query = DictionaryItem::query();
|
||||
|
||||
if (!empty($params['dictionary_id'])) {
|
||||
$query->where('dictionary_id', $params['dictionary_id']);
|
||||
}
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
$query->orderBy('sort')->orderBy('id');
|
||||
|
||||
$pageSize = $params['page_size'] ?? 20;
|
||||
$list = $query->paginate($pageSize);
|
||||
|
||||
return [
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'page' => $list->currentPage(),
|
||||
'page_size' => $list->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
public function createItem(array $data): DictionaryItem
|
||||
{
|
||||
Validator::make($data, [
|
||||
'dictionary_id' => 'required|exists:system_dictionaries,id',
|
||||
'label' => 'required|string|max:100',
|
||||
'value' => 'required|string|max:100',
|
||||
])->validate();
|
||||
|
||||
$item = DictionaryItem::create($data);
|
||||
$this->clearCache();
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function updateItem(int $id, array $data): DictionaryItem
|
||||
{
|
||||
$item = DictionaryItem::findOrFail($id);
|
||||
|
||||
Validator::make($data, [
|
||||
'dictionary_id' => 'sometimes|required|exists:system_dictionaries,id',
|
||||
'label' => 'sometimes|required|string|max:100',
|
||||
'value' => 'sometimes|required|string|max:100',
|
||||
])->validate();
|
||||
|
||||
$item->update($data);
|
||||
$this->clearCache();
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function deleteItem(int $id): bool
|
||||
{
|
||||
$item = DictionaryItem::findOrFail($id);
|
||||
$item->delete();
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function batchDeleteItems(array $ids): bool
|
||||
{
|
||||
DictionaryItem::whereIn('id', $ids)->delete();
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function batchUpdateItemsStatus(array $ids, bool $status): bool
|
||||
{
|
||||
DictionaryItem::whereIn('id', $ids)->update(['status' => $status]);
|
||||
$this->clearCache();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
125
app/Services/System/LogService.php
Normal file
125
app/Services/System/LogService.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\System;
|
||||
|
||||
use App\Models\System\Log;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class LogService
|
||||
{
|
||||
public function create(array $data): Log
|
||||
{
|
||||
return Log::create($data);
|
||||
}
|
||||
|
||||
public function getList(array $params): array
|
||||
{
|
||||
$query = $this->buildQuery($params);
|
||||
|
||||
$query->orderBy('created_at', 'desc');
|
||||
|
||||
$pageSize = $params['page_size'] ?? 20;
|
||||
$list = $query->paginate($pageSize);
|
||||
|
||||
return [
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'page' => $list->currentPage(),
|
||||
'page_size' => $list->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询(用于导出等场景)
|
||||
*
|
||||
* @param array $params
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getListQuery(array $params)
|
||||
{
|
||||
return $this->buildQuery($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建基础查询
|
||||
*
|
||||
* @param array $params
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function buildQuery(array $params)
|
||||
{
|
||||
$query = Log::query()->with('user:id,name,username');
|
||||
|
||||
if (!empty($params['user_id'])) {
|
||||
$query->where('user_id', $params['user_id']);
|
||||
}
|
||||
|
||||
if (!empty($params['username'])) {
|
||||
$query->where('username', 'like', '%' . $params['username'] . '%');
|
||||
}
|
||||
|
||||
if (!empty($params['module'])) {
|
||||
$query->where('module', $params['module']);
|
||||
}
|
||||
|
||||
if (!empty($params['action'])) {
|
||||
$query->where('action', $params['action']);
|
||||
}
|
||||
|
||||
if (!empty($params['status'])) {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
if (!empty($params['start_date']) && !empty($params['end_date'])) {
|
||||
$query->whereBetween('created_at', [$params['start_date'], $params['end_date']]);
|
||||
}
|
||||
|
||||
if (!empty($params['ip'])) {
|
||||
$query->where('ip', 'like', '%' . $params['ip'] . '%');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getById(int $id): ?Log
|
||||
{
|
||||
return Log::with('user')->find($id);
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$log = Log::findOrFail($id);
|
||||
return $log->delete();
|
||||
}
|
||||
|
||||
public function clearLogs(string $days = '30'): bool
|
||||
{
|
||||
Log::where('created_at', '<', now()->subDays($days))->delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function batchDelete(array $ids): bool
|
||||
{
|
||||
Log::whereIn('id', $ids)->delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getStatistics(array $params = []): array
|
||||
{
|
||||
$query = Log::query();
|
||||
|
||||
if (!empty($params['start_date']) && !empty($params['end_date'])) {
|
||||
$query->whereBetween('created_at', [$params['start_date'], $params['end_date']]);
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
$successCount = (clone $query)->where('status', 'success')->count();
|
||||
$errorCount = (clone $query)->where('status', 'error')->count();
|
||||
|
||||
return [
|
||||
'total' => $total,
|
||||
'success' => $successCount,
|
||||
'error' => $errorCount,
|
||||
];
|
||||
}
|
||||
}
|
||||
167
app/Services/System/TaskService.php
Normal file
167
app/Services/System/TaskService.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\System;
|
||||
|
||||
use App\Models\System\Task;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class TaskService
|
||||
{
|
||||
public function getList(array $params): array
|
||||
{
|
||||
$query = Task::query();
|
||||
|
||||
if (!empty($params['keyword'])) {
|
||||
$query->where(function ($q) use ($params) {
|
||||
$q->where('name', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('command', 'like', '%' . $params['keyword'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($params['is_active']) && $params['is_active'] !== '') {
|
||||
$query->where('is_active', $params['is_active']);
|
||||
}
|
||||
|
||||
if (!empty($params['type'])) {
|
||||
$query->where('type', $params['type']);
|
||||
}
|
||||
|
||||
$query->orderBy('id');
|
||||
|
||||
$pageSize = $params['page_size'] ?? 20;
|
||||
$list = $query->paginate($pageSize);
|
||||
|
||||
return [
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'page' => $list->currentPage(),
|
||||
'page_size' => $list->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getAll(): array
|
||||
{
|
||||
return Task::all()->toArray();
|
||||
}
|
||||
|
||||
public function getById(int $id): ?Task
|
||||
{
|
||||
return Task::find($id);
|
||||
}
|
||||
|
||||
public function create(array $data): Task
|
||||
{
|
||||
Validator::make($data, [
|
||||
'name' => 'required|string|max:100',
|
||||
'command' => 'required|string|max:255',
|
||||
'type' => 'required|string|in:command,job,closure',
|
||||
'expression' => 'required|string',
|
||||
])->validate();
|
||||
|
||||
return Task::create($data);
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): Task
|
||||
{
|
||||
$task = Task::findOrFail($id);
|
||||
|
||||
Validator::make($data, [
|
||||
'name' => 'sometimes|required|string|max:100',
|
||||
'command' => 'sometimes|required|string|max:255',
|
||||
'type' => 'sometimes|required|string|in:command,job,closure',
|
||||
'expression' => 'sometimes|required|string',
|
||||
])->validate();
|
||||
|
||||
$task->update($data);
|
||||
return $task;
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$task = Task::findOrFail($id);
|
||||
return $task->delete();
|
||||
}
|
||||
|
||||
public function batchDelete(array $ids): bool
|
||||
{
|
||||
Task::whereIn('id', $ids)->delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function batchUpdateStatus(array $ids, bool $status): bool
|
||||
{
|
||||
Task::whereIn('id', $ids)->update(['is_active' => $status]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function run(int $id): array
|
||||
{
|
||||
$task = Task::findOrFail($id);
|
||||
|
||||
$startTime = microtime(true);
|
||||
$output = '';
|
||||
$status = 'success';
|
||||
$errorMessage = '';
|
||||
|
||||
try {
|
||||
switch ($task->type) {
|
||||
case 'command':
|
||||
$command = $task->command;
|
||||
if ($task->run_in_background) {
|
||||
$command .= ' > /dev/null 2>&1 &';
|
||||
}
|
||||
exec($command, $output, $statusCode);
|
||||
$output = implode("\n", $output);
|
||||
if ($statusCode !== 0) {
|
||||
throw new \Exception('Command failed with status code: ' . $statusCode);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'job':
|
||||
$jobClass = $task->command;
|
||||
if (class_exists($jobClass)) {
|
||||
dispatch(new $jobClass());
|
||||
} else {
|
||||
throw new \Exception('Job class not found: ' . $jobClass);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'closure':
|
||||
throw new \Exception('Closure type tasks cannot be run directly');
|
||||
}
|
||||
|
||||
$task->increment('run_count');
|
||||
} catch (\Exception $e) {
|
||||
$status = 'error';
|
||||
$errorMessage = $e->getMessage();
|
||||
$task->increment('failed_count');
|
||||
}
|
||||
|
||||
$executionTime = round((microtime(true) - $startTime) * 1000);
|
||||
|
||||
$task->update([
|
||||
'last_run_at' => now(),
|
||||
'last_output' => substr($output, 0, 10000),
|
||||
]);
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'output' => $output,
|
||||
'error_message' => $errorMessage,
|
||||
'execution_time' => $executionTime,
|
||||
];
|
||||
}
|
||||
|
||||
public function getStatistics(): array
|
||||
{
|
||||
$total = Task::count();
|
||||
$active = Task::where('is_active', true)->count();
|
||||
$inactive = Task::where('is_active', false)->count();
|
||||
|
||||
return [
|
||||
'total' => $total,
|
||||
'active' => $active,
|
||||
'inactive' => $inactive,
|
||||
];
|
||||
}
|
||||
}
|
||||
160
app/Services/System/UploadService.php
Normal file
160
app/Services/System/UploadService.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\System;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Intervention\Image\Facades\Image;
|
||||
|
||||
class UploadService
|
||||
{
|
||||
protected $disk;
|
||||
protected $allowedImageTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'];
|
||||
protected $allowedFileTypes = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'rar', 'txt'];
|
||||
protected $maxFileSize = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->disk = Storage::disk('public');
|
||||
}
|
||||
|
||||
public function upload(UploadedFile $file, string $directory = 'uploads', array $options = []): array
|
||||
{
|
||||
$extension = strtolower($file->getClientOriginalExtension());
|
||||
|
||||
if (!$this->validateFile($file, $extension)) {
|
||||
throw new \Exception('文件验证失败');
|
||||
}
|
||||
|
||||
$fileName = $this->generateFileName($file, $extension);
|
||||
$filePath = $directory . '/' . date('Ymd') . '/' . $fileName;
|
||||
|
||||
if (in_array($extension, $this->allowedImageTypes) && isset($options['compress'])) {
|
||||
$this->compressImage($file, $filePath, $options);
|
||||
} else {
|
||||
$this->disk->put($filePath, file_get_contents($file));
|
||||
}
|
||||
|
||||
$url = $this->disk->url($filePath);
|
||||
$fullPath = $this->disk->path($filePath);
|
||||
|
||||
return [
|
||||
'url' => $url,
|
||||
'path' => $filePath,
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'size' => $file->getSize(),
|
||||
'mime_type' => $file->getMimeType(),
|
||||
'extension' => $extension,
|
||||
];
|
||||
}
|
||||
|
||||
public function uploadMultiple(array $files, string $directory = 'uploads', array $options = []): array
|
||||
{
|
||||
$results = [];
|
||||
foreach ($files as $file) {
|
||||
if ($file instanceof UploadedFile) {
|
||||
$results[] = $this->upload($file, $directory, $options);
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function uploadBase64(string $base64, string $directory = 'uploads', string $fileName = null): array
|
||||
{
|
||||
if (preg_match('/^data:image\/(\w+);base64,/', $base64, $matches)) {
|
||||
$type = $matches[1];
|
||||
$extension = $type;
|
||||
$data = substr($base64, strpos($base64, ',') + 1);
|
||||
$data = base64_decode($data);
|
||||
|
||||
if (!$data) {
|
||||
throw new \Exception('Base64解码失败');
|
||||
}
|
||||
|
||||
$fileName = $fileName ?: $this->generateUniqueFileName($extension);
|
||||
$filePath = $directory . '/' . date('Ymd') . '/' . $fileName;
|
||||
$this->disk->put($filePath, $data);
|
||||
|
||||
return [
|
||||
'url' => $this->disk->url($filePath),
|
||||
'path' => $filePath,
|
||||
'name' => $fileName,
|
||||
'size' => strlen($data),
|
||||
'mime_type' => 'image/' . $type,
|
||||
'extension' => $extension,
|
||||
];
|
||||
}
|
||||
|
||||
throw new \Exception('无效的Base64图片数据');
|
||||
}
|
||||
|
||||
public function delete(string $path): bool
|
||||
{
|
||||
if ($this->disk->exists($path)) {
|
||||
return $this->disk->delete($path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deleteMultiple(array $paths): bool
|
||||
{
|
||||
foreach ($paths as $path) {
|
||||
$this->delete($path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function validateFile(UploadedFile $file, string $extension): bool
|
||||
{
|
||||
if ($file->getSize() > $this->maxFileSize) {
|
||||
throw new \Exception('文件大小超过限制');
|
||||
}
|
||||
|
||||
$allowedTypes = array_merge($this->allowedImageTypes, $this->allowedFileTypes);
|
||||
if (!in_array($extension, $allowedTypes)) {
|
||||
throw new \Exception('不允许的文件类型');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function generateFileName(UploadedFile $file, string $extension): string
|
||||
{
|
||||
return uniqid() . '_' . Str::random(6) . '.' . $extension;
|
||||
}
|
||||
|
||||
private function generateUniqueFileName(string $extension): string
|
||||
{
|
||||
return uniqid() . '_' . Str::random(6) . '.' . $extension;
|
||||
}
|
||||
|
||||
private function compressImage(UploadedFile $file, string $filePath, array $options): void
|
||||
{
|
||||
$quality = $options['quality'] ?? 80;
|
||||
$width = $options['width'] ?? null;
|
||||
$height = $options['height'] ?? null;
|
||||
|
||||
$image = Image::make($file);
|
||||
|
||||
if ($width || $height) {
|
||||
$image->resize($width, $height, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
});
|
||||
}
|
||||
|
||||
$image->encode(null, $quality);
|
||||
$this->disk->put($filePath, (string) $image);
|
||||
}
|
||||
|
||||
public function getFileUrl(string $path): string
|
||||
{
|
||||
return $this->disk->url($path);
|
||||
}
|
||||
|
||||
public function fileExists(string $path): bool
|
||||
{
|
||||
return $this->disk->exists($path);
|
||||
}
|
||||
}
|
||||
451
app/Services/WebSocket/WebSocketHandler.php
Normal file
451
app/Services/WebSocket/WebSocketHandler.php
Normal file
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\WebSocket;
|
||||
|
||||
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use Swoole\WebSocket\Server;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Services\Auth\UserOnlineService;
|
||||
|
||||
/**
|
||||
* WebSocket Handler
|
||||
*
|
||||
* Handles WebSocket connections, messages, and disconnections
|
||||
*/
|
||||
class WebSocketHandler implements WebSocketHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @var UserOnlineService
|
||||
*/
|
||||
protected $userOnlineService;
|
||||
|
||||
/**
|
||||
* WebSocketHandler constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->userOnlineService = app(UserOnlineService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WebSocket connection open event
|
||||
*
|
||||
* @param Server $server
|
||||
* @param Request $request
|
||||
* @return void
|
||||
*/
|
||||
public function onOpen(Server $server, Request $request): void
|
||||
{
|
||||
try {
|
||||
$fd = $request->fd;
|
||||
$path = $request->server['path_info'] ?? $request->server['request_uri'] ?? '/';
|
||||
|
||||
Log::info('WebSocket connection opened', [
|
||||
'fd' => $fd,
|
||||
'path' => $path,
|
||||
'ip' => $request->server['remote_addr'] ?? 'unknown'
|
||||
]);
|
||||
|
||||
// Extract user ID from query parameters if provided
|
||||
$userId = $request->get['user_id'] ?? null;
|
||||
$token = $request->get['token'] ?? null;
|
||||
|
||||
if ($userId && $token) {
|
||||
// Store user connection mapping
|
||||
$server->wsTable->set('uid:' . $userId, [
|
||||
'value' => $fd,
|
||||
'expiry' => time() + 3600, // 1 hour expiry
|
||||
]);
|
||||
|
||||
$server->wsTable->set('fd:' . $fd, [
|
||||
'value' => $userId,
|
||||
'expiry' => time() + 3600
|
||||
]);
|
||||
|
||||
// Update user online status
|
||||
$this->userOnlineService->updateUserOnlineStatus($userId, $fd, true);
|
||||
|
||||
Log::info('User connected to WebSocket', [
|
||||
'user_id' => $userId,
|
||||
'fd' => $fd
|
||||
]);
|
||||
|
||||
// Send welcome message to client
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'welcome',
|
||||
'data' => [
|
||||
'message' => 'WebSocket connection established',
|
||||
'user_id' => $userId,
|
||||
'timestamp' => time()
|
||||
]
|
||||
]));
|
||||
} else {
|
||||
Log::warning('WebSocket connection without authentication', [
|
||||
'fd' => $fd
|
||||
]);
|
||||
|
||||
// Send error message
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'message' => 'Authentication required. Please provide user_id and token.',
|
||||
'code' => 401
|
||||
]
|
||||
]));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('WebSocket onOpen error', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WebSocket message event
|
||||
*
|
||||
* @param Server $server
|
||||
* @param Frame $frame
|
||||
* @return void
|
||||
*/
|
||||
public function onMessage(Server $server, Frame $frame): void
|
||||
{
|
||||
try {
|
||||
$fd = $frame->fd;
|
||||
$data = $frame->data;
|
||||
|
||||
Log::info('WebSocket message received', [
|
||||
'fd' => $fd,
|
||||
'data' => $data,
|
||||
'opcode' => $frame->opcode
|
||||
]);
|
||||
|
||||
// Parse incoming message
|
||||
$message = json_decode($data, true);
|
||||
|
||||
if (!$message) {
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'message' => 'Invalid JSON format',
|
||||
'code' => 400
|
||||
]
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle different message types
|
||||
$this->handleMessage($server, $fd, $message);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('WebSocket onMessage error', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WebSocket message based on type
|
||||
*
|
||||
* @param Server $server
|
||||
* @param int $fd
|
||||
* @param array $message
|
||||
* @return void
|
||||
*/
|
||||
protected function handleMessage(Server $server, int $fd, array $message): void
|
||||
{
|
||||
$type = $message['type'] ?? 'unknown';
|
||||
$data = $message['data'] ?? [];
|
||||
|
||||
switch ($type) {
|
||||
case 'ping':
|
||||
// Respond to ping with pong
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'pong',
|
||||
'data' => [
|
||||
'timestamp' => time()
|
||||
]
|
||||
]));
|
||||
break;
|
||||
|
||||
case 'heartbeat':
|
||||
// Handle heartbeat
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'heartbeat_ack',
|
||||
'data' => [
|
||||
'timestamp' => time()
|
||||
]
|
||||
]));
|
||||
break;
|
||||
|
||||
case 'chat':
|
||||
// Handle chat message
|
||||
$this->handleChatMessage($server, $fd, $data);
|
||||
break;
|
||||
|
||||
case 'broadcast':
|
||||
// Handle broadcast message (admin only)
|
||||
$this->handleBroadcast($server, $fd, $data);
|
||||
break;
|
||||
|
||||
case 'subscribe':
|
||||
// Handle channel subscription
|
||||
$this->handleSubscribe($server, $fd, $data);
|
||||
break;
|
||||
|
||||
case 'unsubscribe':
|
||||
// Handle channel unsubscription
|
||||
$this->handleUnsubscribe($server, $fd, $data);
|
||||
break;
|
||||
|
||||
default:
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'message' => 'Unknown message type: ' . $type,
|
||||
'code' => 400
|
||||
]
|
||||
]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle chat message
|
||||
*
|
||||
* @param Server $server
|
||||
* @param int $fd
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function handleChatMessage(Server $server, int $fd, array $data): void
|
||||
{
|
||||
$toUserId = $data['to_user_id'] ?? null;
|
||||
$content = $data['content'] ?? '';
|
||||
|
||||
if (!$toUserId || !$content) {
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'message' => 'Missing required fields: to_user_id and content',
|
||||
'code' => 400
|
||||
]
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get target user's connection
|
||||
$targetFd = $server->wsTable->get('uid:' . $toUserId);
|
||||
|
||||
if ($targetFd && $targetFd['value']) {
|
||||
$server->push((int)$targetFd['value'], json_encode([
|
||||
'type' => 'chat',
|
||||
'data' => [
|
||||
'from_user_id' => $server->wsTable->get('fd:' . $fd)['value'] ?? null,
|
||||
'content' => $content,
|
||||
'timestamp' => time()
|
||||
]
|
||||
]));
|
||||
|
||||
// Send delivery receipt to sender
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'message_delivered',
|
||||
'data' => [
|
||||
'to_user_id' => $toUserId,
|
||||
'content' => $content,
|
||||
'timestamp' => time()
|
||||
]
|
||||
]));
|
||||
} else {
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'message' => 'Target user is not online',
|
||||
'code' => 404
|
||||
]
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle broadcast message
|
||||
*
|
||||
* @param Server $server
|
||||
* @param int $fd
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function handleBroadcast(Server $server, int $fd, array $data): void
|
||||
{
|
||||
$message = $data['message'] ?? '';
|
||||
$userId = $server->wsTable->get('fd:' . $fd)['value'] ?? null;
|
||||
|
||||
// TODO: Check if user has admin permission to broadcast
|
||||
// For now, allow any authenticated user
|
||||
|
||||
if (!$message) {
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'message' => 'Message content is required',
|
||||
'code' => 400
|
||||
]
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
// Broadcast to all connected clients except sender
|
||||
$broadcastData = json_encode([
|
||||
'type' => 'broadcast',
|
||||
'data' => [
|
||||
'from_user_id' => $userId,
|
||||
'message' => $message,
|
||||
'timestamp' => time()
|
||||
]
|
||||
]);
|
||||
|
||||
foreach ($server->connections as $connectionFd) {
|
||||
if ($server->isEstablished($connectionFd) && $connectionFd !== $fd) {
|
||||
$server->push($connectionFd, $broadcastData);
|
||||
}
|
||||
}
|
||||
|
||||
// Send confirmation to sender
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'broadcast_sent',
|
||||
'data' => [
|
||||
'message' => $message,
|
||||
'timestamp' => time()
|
||||
]
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle channel subscription
|
||||
*
|
||||
* @param Server $server
|
||||
* @param int $fd
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function handleSubscribe(Server $server, int $fd, array $data): void
|
||||
{
|
||||
$channel = $data['channel'] ?? '';
|
||||
|
||||
if (!$channel) {
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'message' => 'Channel name is required',
|
||||
'code' => 400
|
||||
]
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
// Store subscription in wsTable
|
||||
$server->wsTable->set('channel:' . $channel . ':fd:' . $fd, [
|
||||
'value' => 1,
|
||||
'expiry' => time() + 7200 // 2 hours
|
||||
]);
|
||||
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'subscribed',
|
||||
'data' => [
|
||||
'channel' => $channel,
|
||||
'timestamp' => time()
|
||||
]
|
||||
]));
|
||||
|
||||
Log::info('User subscribed to channel', [
|
||||
'fd' => $fd,
|
||||
'channel' => $channel
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle channel unsubscription
|
||||
*
|
||||
* @param Server $server
|
||||
* @param int $fd
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function handleUnsubscribe(Server $server, int $fd, array $data): void
|
||||
{
|
||||
$channel = $data['channel'] ?? '';
|
||||
|
||||
if (!$channel) {
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'message' => 'Channel name is required',
|
||||
'code' => 400
|
||||
]
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove subscription from wsTable
|
||||
$server->wsTable->del('channel:' . $channel . ':fd:' . $fd);
|
||||
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'unsubscribed',
|
||||
'data' => [
|
||||
'channel' => $channel,
|
||||
'timestamp' => time()
|
||||
]
|
||||
]));
|
||||
|
||||
Log::info('User unsubscribed from channel', [
|
||||
'fd' => $fd,
|
||||
'channel' => $channel
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WebSocket connection close event
|
||||
*
|
||||
* @param Server $server
|
||||
* @param $fd
|
||||
* @param $reactorId
|
||||
* @return void
|
||||
*/
|
||||
public function onClose(Server $server, $fd, $reactorId): void
|
||||
{
|
||||
try {
|
||||
Log::info('WebSocket connection closed', [
|
||||
'fd' => $fd,
|
||||
'reactor_id' => $reactorId
|
||||
]);
|
||||
|
||||
// Get user ID from wsTable
|
||||
$userId = $server->wsTable->get('fd:' . $fd)['value'] ?? null;
|
||||
|
||||
if ($userId) {
|
||||
// Remove user connection mapping
|
||||
$server->wsTable->del('uid:' . $userId);
|
||||
$server->wsTable->del('fd:' . $fd);
|
||||
|
||||
// Update user online status
|
||||
$this->userOnlineService->updateUserOnlineStatus($userId, $fd, false);
|
||||
|
||||
Log::info('User disconnected from WebSocket', [
|
||||
'user_id' => $userId,
|
||||
'fd' => $fd
|
||||
]);
|
||||
}
|
||||
|
||||
// Clean up channel subscriptions
|
||||
// Note: In production, you might want to iterate through all channel keys
|
||||
// and remove the ones associated with this fd
|
||||
} catch (\Exception $e) {
|
||||
Log::error('WebSocket onClose error', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
402
app/Services/WebSocket/WebSocketService.php
Normal file
402
app/Services/WebSocket/WebSocketService.php
Normal file
@@ -0,0 +1,402 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\WebSocket;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Swoole\WebSocket\Server;
|
||||
|
||||
/**
|
||||
* WebSocket Service
|
||||
*
|
||||
* Provides helper functions for WebSocket operations
|
||||
*/
|
||||
class WebSocketService
|
||||
{
|
||||
/**
|
||||
* Get Swoole WebSocket Server instance
|
||||
*
|
||||
* @return Server|null
|
||||
*/
|
||||
public function getServer(): ?Server
|
||||
{
|
||||
return app('swoole.server');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to a specific user
|
||||
*
|
||||
* @param int $userId
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
public function sendToUser(int $userId, array $data): bool
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
Log::warning('WebSocket server not available', ['user_id' => $userId]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$fdInfo = $server->wsTable->get('uid:' . $userId);
|
||||
|
||||
if (!$fdInfo || !$fdInfo['value']) {
|
||||
Log::info('User not connected to WebSocket', ['user_id' => $userId]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$fd = (int)$fdInfo['value'];
|
||||
|
||||
if (!$server->isEstablished($fd)) {
|
||||
Log::info('WebSocket connection not established', ['user_id' => $userId, 'fd' => $fd]);
|
||||
// Clean up stale connection
|
||||
$server->wsTable->del('uid:' . $userId);
|
||||
$server->wsTable->del('fd:' . $fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
$server->push($fd, json_encode($data));
|
||||
|
||||
Log::info('Message sent to user via WebSocket', [
|
||||
'user_id' => $userId,
|
||||
'fd' => $fd,
|
||||
'data' => $data
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to multiple users
|
||||
*
|
||||
* @param array $userIds
|
||||
* @param array $data
|
||||
* @return array Array of user IDs who received the message
|
||||
*/
|
||||
public function sendToUsers(array $userIds, array $data): array
|
||||
{
|
||||
$sentTo = [];
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
if ($this->sendToUser($userId, $data)) {
|
||||
$sentTo[] = $userId;
|
||||
}
|
||||
}
|
||||
|
||||
return $sentTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast message to all connected clients
|
||||
*
|
||||
* @param array $data
|
||||
* @param int|null $excludeUserId User ID to exclude from broadcast
|
||||
* @return int Number of clients the message was sent to
|
||||
*/
|
||||
public function broadcast(array $data, ?int $excludeUserId = null): int
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server) {
|
||||
Log::warning('WebSocket server not available for broadcast');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$message = json_encode($data);
|
||||
$count = 0;
|
||||
|
||||
foreach ($server->connections as $fd) {
|
||||
if (!$server->isEstablished($fd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we should exclude this user
|
||||
if ($excludeUserId) {
|
||||
$fdInfo = $server->wsTable->get('fd:' . $fd);
|
||||
if ($fdInfo && $fdInfo['value'] == $excludeUserId) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$server->push($fd, $message);
|
||||
$count++;
|
||||
}
|
||||
|
||||
Log::info('Broadcast sent via WebSocket', [
|
||||
'data' => $data,
|
||||
'exclude_user_id' => $excludeUserId,
|
||||
'count' => $count
|
||||
]);
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to all subscribers of a channel
|
||||
*
|
||||
* @param string $channel
|
||||
* @param array $data
|
||||
* @return int Number of subscribers who received the message
|
||||
*/
|
||||
public function sendToChannel(string $channel, array $data): int
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
Log::warning('WebSocket server not available for channel broadcast', ['channel' => $channel]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$message = json_encode($data);
|
||||
|
||||
// Iterate through all connections and check if they're subscribed to the channel
|
||||
foreach ($server->connections as $fd) {
|
||||
if (!$server->isEstablished($fd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subscription = $server->wsTable->get('channel:' . $channel . ':fd:' . $fd);
|
||||
|
||||
if ($subscription) {
|
||||
$server->push($fd, $message);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
Log::info('Channel message sent via WebSocket', [
|
||||
'channel' => $channel,
|
||||
'data' => $data,
|
||||
'count' => $count
|
||||
]);
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get online user count
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOnlineUserCount(): int
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Count established connections
|
||||
$count = 0;
|
||||
foreach ($server->connections as $fd) {
|
||||
if ($server->isEstablished($fd)) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is online
|
||||
*
|
||||
* @param int $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function isUserOnline(int $userId): bool
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fdInfo = $server->wsTable->get('uid:' . $userId);
|
||||
|
||||
if (!$fdInfo || !$fdInfo['value']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fd = (int)$fdInfo['value'];
|
||||
|
||||
return $server->isEstablished($fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect a user from WebSocket
|
||||
*
|
||||
* @param int $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function disconnectUser(int $userId): bool
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fdInfo = $server->wsTable->get('uid:' . $userId);
|
||||
|
||||
if (!$fdInfo || !$fdInfo['value']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fd = (int)$fdInfo['value'];
|
||||
|
||||
if ($server->isEstablished($fd)) {
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'disconnect',
|
||||
'data' => [
|
||||
'message' => 'You have been disconnected',
|
||||
'timestamp' => time()
|
||||
]
|
||||
]));
|
||||
|
||||
// Close the connection
|
||||
$server->disconnect($fd);
|
||||
|
||||
// Clean up
|
||||
$server->wsTable->del('uid:' . $userId);
|
||||
$server->wsTable->del('fd:' . $fd);
|
||||
|
||||
Log::info('User disconnected from WebSocket by server', [
|
||||
'user_id' => $userId,
|
||||
'fd' => $fd
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all online user IDs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOnlineUserIds(): array
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$userIds = [];
|
||||
|
||||
foreach ($server->connections as $fd) {
|
||||
if (!$server->isEstablished($fd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fdInfo = $server->wsTable->get('fd:' . $fd);
|
||||
|
||||
if ($fdInfo && $fdInfo['value']) {
|
||||
$userIds[] = (int)$fdInfo['value'];
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send system notification to all online users
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $message
|
||||
* @param string $type
|
||||
* @param array $extraData
|
||||
* @return int
|
||||
*/
|
||||
public function sendSystemNotification(string $title, string $message, string $type = 'info', array $extraData = []): int
|
||||
{
|
||||
$data = [
|
||||
'type' => 'notification',
|
||||
'data' => [
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'type' => $type, // info, success, warning, error
|
||||
'timestamp' => time(),
|
||||
...$extraData
|
||||
]
|
||||
];
|
||||
|
||||
return $this->broadcast($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification to specific users
|
||||
*
|
||||
* @param array $userIds
|
||||
* @param string $title
|
||||
* @param string $message
|
||||
* @param string $type
|
||||
* @param array $extraData
|
||||
* @return array
|
||||
*/
|
||||
public function sendNotificationToUsers(array $userIds, string $title, string $message, string $type = 'info', array $extraData = []): array
|
||||
{
|
||||
$data = [
|
||||
'type' => 'notification',
|
||||
'data' => [
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'type' => $type,
|
||||
'timestamp' => time(),
|
||||
...$extraData
|
||||
]
|
||||
];
|
||||
|
||||
return $this->sendToUsers($userIds, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push data update to specific users
|
||||
*
|
||||
* @param array $userIds
|
||||
* @param string $resourceType
|
||||
* @param string $action
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function pushDataUpdate(array $userIds, string $resourceType, string $action, array $data): array
|
||||
{
|
||||
$message = [
|
||||
'type' => 'data_update',
|
||||
'data' => [
|
||||
'resource_type' => $resourceType, // e.g., 'user', 'order', 'product'
|
||||
'action' => $action, // create, update, delete
|
||||
'data' => $data,
|
||||
'timestamp' => time()
|
||||
]
|
||||
];
|
||||
|
||||
return $this->sendToUsers($userIds, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push data update to a channel
|
||||
*
|
||||
* @param string $channel
|
||||
* @param string $resourceType
|
||||
* @param string $action
|
||||
* @param array $data
|
||||
* @return int
|
||||
*/
|
||||
public function pushDataUpdateToChannel(string $channel, string $resourceType, string $action, array $data): int
|
||||
{
|
||||
$message = [
|
||||
'type' => 'data_update',
|
||||
'data' => [
|
||||
'resource_type' => $resourceType,
|
||||
'action' => $action,
|
||||
'data' => $data,
|
||||
'timestamp' => time()
|
||||
]
|
||||
];
|
||||
|
||||
return $this->sendToChannel($channel, $message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user