Compare commits
No commits in common. "1ec4cbe941900f55124acb8284a5459155e6d3ff" and "58e44b5e6129bb682afd469136d721321ad7be84" have entirely different histories.
1ec4cbe941
...
58e44b5e61
@ -89,7 +89,6 @@ class ServerResourceController extends Controller
|
|||||||
$description = (string) ($permission->description ?? '');
|
$description = (string) ($permission->description ?? '');
|
||||||
if (preg_match('/资源ID[::]\s*(\d+)/u', $description, $matches) === 1) {
|
if (preg_match('/资源ID[::]\s*(\d+)/u', $description, $matches) === 1) {
|
||||||
$resourceIds->push((int) $matches[1]);
|
$resourceIds->push((int) $matches[1]);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,12 +316,9 @@ class ServerResourceController extends Controller
|
|||||||
public function useResource(Request $request, int $id): JsonResponse
|
public function useResource(Request $request, int $id): JsonResponse
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'account_name' => ['required', 'string', 'max:255'],
|
'account_name' => ['nullable', 'string', 'max:255'],
|
||||||
'password' => ['required', 'string', 'max:255'],
|
'password' => ['nullable', 'string', 'max:255'],
|
||||||
'protocol' => ['required', 'string', 'max:64'],
|
'protocol' => ['required', 'string', 'max:64'],
|
||||||
], [
|
|
||||||
'account_name.required' => '请输入访问用户名。',
|
|
||||||
'password.required' => '请输入访问密码。',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$resource = ServerResource::query()->with('parent')->findOrFail($id);
|
$resource = ServerResource::query()->with('parent')->findOrFail($id);
|
||||||
@ -460,11 +456,6 @@ class ServerResourceController extends Controller
|
|||||||
], 502);
|
], 502);
|
||||||
}
|
}
|
||||||
|
|
||||||
$tempPassword = null;
|
|
||||||
if ((bool) $resource->allow_copy_temp_password) {
|
|
||||||
$tempPassword = $this->extractSsoTokenFromUrl($ssoUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessLog::query()->create([
|
AccessLog::query()->create([
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'server_resource_id' => $resource->id,
|
'server_resource_id' => $resource->id,
|
||||||
@ -502,42 +493,10 @@ class ServerResourceController extends Controller
|
|||||||
'bastion_account_id' => $bastionAccount->id,
|
'bastion_account_id' => $bastionAccount->id,
|
||||||
'client_type' => (string) data_get($result, 'data.client_type', ''),
|
'client_type' => (string) data_get($result, 'data.client_type', ''),
|
||||||
'response' => $result,
|
'response' => $result,
|
||||||
'allow_copy_temp_password' => (bool) $resource->allow_copy_temp_password,
|
|
||||||
'temp_password' => $tempPassword,
|
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function extractSsoTokenFromUrl(string $ssoUrl): ?string
|
|
||||||
{
|
|
||||||
if (! str_starts_with($ssoUrl, 'sso://')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$encoded = trim(substr($ssoUrl, strlen('sso://')));
|
|
||||||
$encoded = rtrim($encoded, '/');
|
|
||||||
if ($encoded === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$decoded = base64_decode($encoded, true);
|
|
||||||
if ($decoded === false || $decoded === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$payload = json_decode($decoded, true);
|
|
||||||
if (! is_array($payload)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$token = data_get($payload, 'NODE_COMMON.SSOToken');
|
|
||||||
if (! is_string($token) || trim($token) === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return trim($token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function syncDirectPermissionsByPivot(ServerResource $resource, Permission $permission, array $syncData): void
|
private function syncDirectPermissionsByPivot(ServerResource $resource, Permission $permission, array $syncData): void
|
||||||
{
|
{
|
||||||
if (! $resource->parent_id) {
|
if (! $resource->parent_id) {
|
||||||
|
|||||||
@ -11,11 +11,7 @@ use App\Models\UserServerPermission;
|
|||||||
use hg\apidoc\annotation as Apidoc;
|
use hg\apidoc\annotation as Apidoc;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Spatie\Permission\Models\Permission;
|
use Spatie\Permission\Models\Permission;
|
||||||
use Spatie\Permission\Models\Role;
|
|
||||||
|
|
||||||
#[Apidoc\Title('用户管理')]
|
#[Apidoc\Title('用户管理')]
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
@ -24,7 +20,7 @@ class UserController extends Controller
|
|||||||
{
|
{
|
||||||
$this->middleware('auth:api');
|
$this->middleware('auth:api');
|
||||||
$this->middleware('permission:platform.users.view,api')->only(['index', 'show']);
|
$this->middleware('permission:platform.users.view,api')->only(['index', 'show']);
|
||||||
$this->middleware('permission:platform.users.manage,api')->only(['store', 'update', 'destroy', 'syncPermissions', 'import', 'importTemplate']);
|
$this->middleware('permission:platform.users.manage,api')->only(['store', 'update', 'destroy', 'syncPermissions']);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Apidoc\Title('用户列表'), Apidoc\Method('GET'), Apidoc\Url('/users')]
|
#[Apidoc\Title('用户列表'), Apidoc\Method('GET'), Apidoc\Url('/users')]
|
||||||
@ -118,335 +114,6 @@ class UserController extends Controller
|
|||||||
return response()->json(['code' => 0, 'message' => 'ok', 'data' => null]);
|
return response()->json(['code' => 0, 'message' => 'ok', 'data' => null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Apidoc\Title('批量导入用户'), Apidoc\Method('POST'), Apidoc\Url('/users/import')]
|
|
||||||
public function import(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$validated = $request->validate([
|
|
||||||
'file' => ['required', 'file', 'mimes:xlsx'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$file = $validated['file'];
|
|
||||||
$rows = $this->parseImportRows($file->getPathname(), (string) $file->getClientOriginalExtension());
|
|
||||||
if ($rows->isEmpty()) {
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'file' => ['文件中没有可导入的数据行。'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$createdCount = 0;
|
|
||||||
$skippedCount = 0;
|
|
||||||
$errors = [];
|
|
||||||
|
|
||||||
foreach ($rows as $index => $row) {
|
|
||||||
$line = $index + 2;
|
|
||||||
$normalizedRow = [];
|
|
||||||
foreach ($row as $key => $value) {
|
|
||||||
$normalizedRow[$this->normalizeImportHeader((string) $key)] = (string) $value;
|
|
||||||
}
|
|
||||||
$payload = [
|
|
||||||
'nickname' => trim($this->firstMatchedValue($normalizedRow, ['nickname', 'nick_name', 'name', '用户名', '昵称'])),
|
|
||||||
'email' => trim($this->firstMatchedValue($normalizedRow, ['email', 'mail', '邮箱'])),
|
|
||||||
'phone' => trim($this->firstMatchedValue($normalizedRow, ['phone', 'mobile', '手机号', '手机', '电话'])),
|
|
||||||
'password' => trim($this->firstMatchedValue($normalizedRow, ['password', 'passwd', 'pwd', '密码'])),
|
|
||||||
];
|
|
||||||
|
|
||||||
$validator = Validator::make($payload, [
|
|
||||||
'nickname' => ['required', 'string', 'max:255'],
|
|
||||||
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
|
|
||||||
'phone' => ['nullable', 'string', 'max:32', 'unique:users,phone'],
|
|
||||||
'password' => ['required', 'string', 'min:6', 'max:255'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($validator->fails()) {
|
|
||||||
$errors[] = [
|
|
||||||
'line' => $line,
|
|
||||||
'message' => implode(';', $validator->errors()->all()),
|
|
||||||
];
|
|
||||||
$skippedCount++;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = User::query()->create($payload);
|
|
||||||
$roleIds = $this->resolveRoleIds($normalizedRow);
|
|
||||||
if (! empty($roleIds)) {
|
|
||||||
$user->syncRoles($roleIds);
|
|
||||||
}
|
|
||||||
$createdCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->auditLog($request, 'user_import', [
|
|
||||||
'metadata' => [
|
|
||||||
'created_count' => $createdCount,
|
|
||||||
'skipped_count' => $skippedCount,
|
|
||||||
'error_count' => count($errors),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'code' => 0,
|
|
||||||
'message' => 'ok',
|
|
||||||
'data' => [
|
|
||||||
'created_count' => $createdCount,
|
|
||||||
'skipped_count' => $skippedCount,
|
|
||||||
'errors' => $errors,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Apidoc\Title('下载用户导入模板'), Apidoc\Method('GET'), Apidoc\Url('/users/import/template')]
|
|
||||||
public function importTemplate()
|
|
||||||
{
|
|
||||||
if (! class_exists(\ZipArchive::class)) {
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'file' => ['当前 PHP 环境未启用 ZipArchive 扩展,无法生成 xlsx 模板。'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tempPath = tempnam(sys_get_temp_dir(), 'users_import_template_');
|
|
||||||
if ($tempPath === false) {
|
|
||||||
throw ValidationException::withMessages(['file' => ['无法创建临时模板文件。']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$xlsxPath = $tempPath.'.xlsx';
|
|
||||||
@rename($tempPath, $xlsxPath);
|
|
||||||
$this->buildUsersImportTemplateXlsx($xlsxPath);
|
|
||||||
|
|
||||||
return response()->download(
|
|
||||||
$xlsxPath,
|
|
||||||
'users_import_template.xlsx',
|
|
||||||
['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
|
|
||||||
)->deleteFileAfterSend(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function parseImportRows(string $filePath, string $extension): Collection
|
|
||||||
{
|
|
||||||
$ext = strtolower($extension);
|
|
||||||
if ($ext !== 'xlsx') {
|
|
||||||
throw ValidationException::withMessages(['file' => ['仅支持 xlsx 文件导入。']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->parseXlsxRows($filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function parseXlsxRows(string $filePath): Collection
|
|
||||||
{
|
|
||||||
if (! class_exists(\ZipArchive::class)) {
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'file' => ['当前 PHP 环境未启用 ZipArchive 扩展,无法导入 xlsx。请启用 php_zip 扩展。'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$zip = new \ZipArchive;
|
|
||||||
if ($zip->open($filePath) !== true) {
|
|
||||||
throw ValidationException::withMessages(['file' => ['无法解析 xlsx 文件。']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sharedStrings = [];
|
|
||||||
$sharedXml = $zip->getFromName('xl/sharedStrings.xml');
|
|
||||||
if (is_string($sharedXml)) {
|
|
||||||
$sharedDoc = new \SimpleXMLElement($sharedXml);
|
|
||||||
foreach ($sharedDoc->si as $si) {
|
|
||||||
$sharedStrings[] = (string) $si->t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sheetXml = $zip->getFromName('xl/worksheets/sheet1.xml');
|
|
||||||
$zip->close();
|
|
||||||
if (! is_string($sheetXml)) {
|
|
||||||
throw ValidationException::withMessages(['file' => ['xlsx 文件缺少 sheet1。']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sheet = new \SimpleXMLElement($sheetXml);
|
|
||||||
$rowsData = [];
|
|
||||||
foreach ($sheet->sheetData->row as $rowNode) {
|
|
||||||
$rowValues = [];
|
|
||||||
foreach ($rowNode->c as $cell) {
|
|
||||||
$ref = (string) $cell['r'];
|
|
||||||
preg_match('/^[A-Z]+/', $ref, $matches);
|
|
||||||
$column = $matches[0] ?? '';
|
|
||||||
$value = '';
|
|
||||||
if ((string) $cell['t'] === 's') {
|
|
||||||
$index = (int) ($cell->v ?? 0);
|
|
||||||
$value = (string) ($sharedStrings[$index] ?? '');
|
|
||||||
} else {
|
|
||||||
$value = (string) ($cell->v ?? '');
|
|
||||||
}
|
|
||||||
$rowValues[$column] = $value;
|
|
||||||
}
|
|
||||||
$rowsData[] = $rowValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($rowsData) <= 1) {
|
|
||||||
return collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
$headerRow = $rowsData[0];
|
|
||||||
$orderedColumns = array_keys($headerRow);
|
|
||||||
$headers = [];
|
|
||||||
foreach ($orderedColumns as $col) {
|
|
||||||
$headers[] = $this->normalizeImportHeader((string) ($headerRow[$col] ?? ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
$resultRows = collect();
|
|
||||||
for ($i = 1; $i < count($rowsData); $i++) {
|
|
||||||
$row = $rowsData[$i];
|
|
||||||
$assoc = [];
|
|
||||||
$hasValue = false;
|
|
||||||
foreach ($orderedColumns as $idx => $col) {
|
|
||||||
$header = $headers[$idx] ?? '';
|
|
||||||
if ($header === '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$value = (string) ($row[$col] ?? '');
|
|
||||||
if (trim($value) !== '') {
|
|
||||||
$hasValue = true;
|
|
||||||
}
|
|
||||||
$assoc[$header] = $value;
|
|
||||||
}
|
|
||||||
if ($hasValue) {
|
|
||||||
$resultRows->push($assoc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $resultRows;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function resolveRoleIds(array $row): array
|
|
||||||
{
|
|
||||||
$roleIdsRaw = trim((string) ($row['role_ids'] ?? ''));
|
|
||||||
if ($roleIdsRaw !== '') {
|
|
||||||
return collect(explode(',', $roleIdsRaw))
|
|
||||||
->map(fn ($id) => (int) trim($id))
|
|
||||||
->filter(fn (int $id): bool => $id > 0 && Role::query()->where('id', $id)->exists())
|
|
||||||
->values()
|
|
||||||
->all();
|
|
||||||
}
|
|
||||||
|
|
||||||
$roleNamesRaw = trim((string) ($row['roles'] ?? $row['role_names'] ?? ''));
|
|
||||||
if ($roleNamesRaw === '') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$names = collect(explode(',', $roleNamesRaw))
|
|
||||||
->map(fn ($name) => trim($name))
|
|
||||||
->filter()
|
|
||||||
->values()
|
|
||||||
->all();
|
|
||||||
|
|
||||||
if (empty($names)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Role::query()
|
|
||||||
->whereIn('name', $names)
|
|
||||||
->pluck('id')
|
|
||||||
->map(fn (int $id): int => (int) $id)
|
|
||||||
->all();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function normalizeImportHeader(string $header): string
|
|
||||||
{
|
|
||||||
$normalized = trim($this->toUtf8($header));
|
|
||||||
$normalized = preg_replace('/^\xEF\xBB\xBF/u', '', $normalized) ?? $normalized;
|
|
||||||
$normalized = strtolower(str_replace([' ', '-', '.'], '_', trim($normalized)));
|
|
||||||
|
|
||||||
return match ($normalized) {
|
|
||||||
'昵称', '用户名', '姓名', 'name', 'nick_name' => 'nickname',
|
|
||||||
'邮箱', '邮件', 'mail' => 'email',
|
|
||||||
'手机号', '手机', '电话', 'mobile' => 'phone',
|
|
||||||
'密码', 'passwd', 'pwd' => 'password',
|
|
||||||
'角色id', '角色ids', 'role_id', '角色_id' => 'role_ids',
|
|
||||||
'角色', '角色名', 'role_name', 'role_names' => 'roles',
|
|
||||||
default => $normalized,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private function toUtf8(string $value): string
|
|
||||||
{
|
|
||||||
if ($value === '' || mb_check_encoding($value, 'UTF-8')) {
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mb_convert_encoding($value, 'UTF-8', 'UTF-8,GB18030,GBK,Big5,ISO-8859-1');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, string> $row
|
|
||||||
* @param array<int, string> $aliases
|
|
||||||
*/
|
|
||||||
private function firstMatchedValue(array $row, array $aliases): string
|
|
||||||
{
|
|
||||||
foreach ($aliases as $alias) {
|
|
||||||
$key = $this->normalizeImportHeader($alias);
|
|
||||||
if (array_key_exists($key, $row)) {
|
|
||||||
return (string) $row[$key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function buildUsersImportTemplateXlsx(string $xlsxPath): void
|
|
||||||
{
|
|
||||||
$zip = new \ZipArchive;
|
|
||||||
if ($zip->open($xlsxPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
|
||||||
throw ValidationException::withMessages(['file' => ['无法生成 xlsx 模板文件。']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$contentTypes = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
|
||||||
.'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
|
|
||||||
.'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
|
|
||||||
.'<Default Extension="xml" ContentType="application/xml"/>'
|
|
||||||
.'<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
|
|
||||||
.'<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
|
|
||||||
.'</Types>';
|
|
||||||
|
|
||||||
$rels = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
|
||||||
.'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
|
||||||
.'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'
|
|
||||||
.'</Relationships>';
|
|
||||||
|
|
||||||
$workbook = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
|
||||||
.'<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" '
|
|
||||||
.'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
|
|
||||||
.'<sheets><sheet name="Users" sheetId="1" r:id="rId1"/></sheets></workbook>';
|
|
||||||
|
|
||||||
$workbookRels = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
|
||||||
.'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
|
||||||
.'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'
|
|
||||||
.'</Relationships>';
|
|
||||||
|
|
||||||
$rows = [
|
|
||||||
['nickname', 'email', 'phone', 'password', 'role_ids'],
|
|
||||||
['张三', 'zhangsan@example.com', '13800138000', 'Pass@123456', '1'],
|
|
||||||
['李四', 'lisi@example.com', '13900139000', 'Pass@123456', '1,2'],
|
|
||||||
];
|
|
||||||
|
|
||||||
$sheetData = '';
|
|
||||||
foreach ($rows as $rowIndex => $cells) {
|
|
||||||
$r = $rowIndex + 1;
|
|
||||||
$sheetData .= '<row r="'.$r.'">';
|
|
||||||
foreach ($cells as $colIndex => $cellValue) {
|
|
||||||
$column = chr(ord('A') + $colIndex).$r;
|
|
||||||
$escapedValue = htmlspecialchars((string) $cellValue, ENT_XML1 | ENT_QUOTES, 'UTF-8');
|
|
||||||
$sheetData .= '<c r="'.$column.'" t="inlineStr"><is><t>'.$escapedValue.'</t></is></c>';
|
|
||||||
}
|
|
||||||
$sheetData .= '</row>';
|
|
||||||
}
|
|
||||||
$sheet = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
|
||||||
.'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>'
|
|
||||||
.$sheetData
|
|
||||||
.'</sheetData></worksheet>';
|
|
||||||
|
|
||||||
$zip->addFromString('[Content_Types].xml', $contentTypes);
|
|
||||||
$zip->addFromString('_rels/.rels', $rels);
|
|
||||||
$zip->addFromString('xl/workbook.xml', $workbook);
|
|
||||||
$zip->addFromString('xl/_rels/workbook.xml.rels', $workbookRels);
|
|
||||||
$zip->addFromString('xl/worksheets/sheet1.xml', $sheet);
|
|
||||||
$zip->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function syncServerResourcePermissionsByDirectPermissions(User $user, array $permissionIds): void
|
private function syncServerResourcePermissionsByDirectPermissions(User $user, array $permissionIds): void
|
||||||
{
|
{
|
||||||
$resourcePermissions = Permission::query()
|
$resourcePermissions = Permission::query()
|
||||||
|
|||||||
@ -22,7 +22,6 @@ class StoreServerResourceRequest extends FormRequest
|
|||||||
'account_id' => ['nullable', 'integer', 'min:1'],
|
'account_id' => ['nullable', 'integer', 'min:1'],
|
||||||
'protocol' => ['nullable', 'string', 'max:64'],
|
'protocol' => ['nullable', 'string', 'max:64'],
|
||||||
'description' => ['nullable', 'string', 'max:255'],
|
'description' => ['nullable', 'string', 'max:255'],
|
||||||
'allow_copy_temp_password' => ['sometimes', 'boolean'],
|
|
||||||
'is_active' => ['sometimes', 'boolean'],
|
'is_active' => ['sometimes', 'boolean'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ class UpdateServerResourceRequest extends FormRequest
|
|||||||
'account_id' => ['nullable', 'integer', 'min:1'],
|
'account_id' => ['nullable', 'integer', 'min:1'],
|
||||||
'protocol' => ['nullable', 'string', 'max:64'],
|
'protocol' => ['nullable', 'string', 'max:64'],
|
||||||
'description' => ['nullable', 'string', 'max:255'],
|
'description' => ['nullable', 'string', 'max:255'],
|
||||||
'allow_copy_temp_password' => ['sometimes', 'boolean'],
|
|
||||||
'is_active' => ['sometimes', 'boolean'],
|
'is_active' => ['sometimes', 'boolean'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,6 @@ class ServerResource extends Model
|
|||||||
'account_id',
|
'account_id',
|
||||||
'protocols',
|
'protocols',
|
||||||
'description',
|
'description',
|
||||||
'allow_copy_temp_password',
|
|
||||||
'is_active',
|
'is_active',
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -51,7 +50,6 @@ class ServerResource extends Model
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'protocols' => 'array',
|
'protocols' => 'array',
|
||||||
'allow_copy_temp_password' => 'boolean',
|
|
||||||
'is_active' => 'boolean',
|
'is_active' => 'boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::table('server_resources', function (Blueprint $table) {
|
|
||||||
$table->boolean('allow_copy_temp_password')
|
|
||||||
->default(false)
|
|
||||||
->after('protocols')
|
|
||||||
->comment('是否允许用户复制临时密码');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('server_resources', function (Blueprint $table) {
|
|
||||||
$table->dropColumn('allow_copy_temp_password');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Loading…
x
Reference in New Issue
Block a user