Skip to content

[ページ管理][ユーザ管理] 数万ユーザでもページ開ける, ダウンロードできるように対応 #2165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions app/Plugins/Manage/PageManage/PageManage.php
Original file line number Diff line number Diff line change
Expand Up @@ -750,12 +750,13 @@ public function role($request, $page_id, $group_id)
->orderBy('group_id', 'asc')
->get();

// グループ参加ユーザ
$users = User::whereIn('id', GroupUser::pluck('user_id')->unique())->get();

foreach ($groups as $group) {
$group->page_roles = $page_roles->where('group_id', $group->id);
$group->group_user_names = $users->whereIn('id', $group->group_user->pluck('user_id'))->pluck('name')->implode(', ');

// 数万ユーザでメモリ不足になるため、DB呼び出し
$group->group_user_names = User::whereIn('id', GroupUser::where('group_id', $group->id)->pluck('user_id'))
->pluck('name')
->implode(', ');
}

// 自分のページから親を遡って取得
Expand Down Expand Up @@ -1050,13 +1051,14 @@ public function roleList($request, $id)
}

// グループの取得
// ※ with('group_user')は、数万ユーザの場合、1Gでもメモリ不足になる。
$groups = Group::orderBy('display_sequence', 'asc')->get();

// グループ参加ユーザ
$users = User::whereIn('id', GroupUser::pluck('user_id')->unique())->get();

foreach ($groups as $group) {
$group->group_user_names = $users->whereIn('id', $group->group_user->pluck('user_id'))->pluck('name')->implode('<br />');
// 数万ユーザでメモリ不足になるため、DB呼び出し
$group->group_user_names = User::whereIn('id', GroupUser::where('group_id', $group->id)->pluck('user_id'))
->pluck('name')
->implode('<br />');
}

// 管理画面プラグインの戻り値の返し方
Expand Down
193 changes: 118 additions & 75 deletions app/Plugins/Manage/UserManage/UserManage.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,41 @@

namespace App\Plugins\Manage\UserManage;

use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;

use App\Enums\CsvCharacterCode;
use App\Enums\EditType;
use App\Enums\Required;
use App\Enums\ShowType;
use App\Enums\UserColumnType;
use App\Enums\UserRegisterNoticeEmbeddedTag;
use App\Enums\UserStatus;
use App\Enums\UseType;
use App\Models\Core\Configs;
use App\Models\Core\Section;
use App\Models\Core\UsersColumns;
use App\Models\Core\UsersColumnsSelects;
use App\Models\Core\UsersColumnsSet;
use App\Models\Core\UsersRoles;
use App\Models\Core\UsersInputCols;
use App\Models\Core\UsersLoginHistories;
use App\Models\Core\UserSection;
use App\Models\Common\Group;
use App\Models\Common\GroupUser;
use App\User;

use App\Traits\ConnectMailTrait;

use App\Plugins\Manage\ManagePluginBase;

use App\Rules\CustomValiUserEmailUnique;
use App\Rules\CustomValiEmails;
use App\Rules\CustomValiCsvExistsName;

use App\Traits\ConnectMailTrait;
use App\User;
use App\Utilities\Csv\CsvUtils;
use App\Utilities\String\StringUtils;

use App\Enums\CsvCharacterCode;
use App\Enums\EditType;
use App\Enums\Required;
use App\Enums\ShowType;
use App\Enums\UserColumnType;
use App\Enums\UserRegisterNoticeEmbeddedTag;
use App\Enums\UserStatus;
use App\Enums\UseType;
use App\Models\Core\Section;
use App\Models\Core\UserSection;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
* ユーザ管理クラス
Expand Down Expand Up @@ -118,15 +113,15 @@ public function declareRole()
}

/**
* データgetで取得
* ユーザquery取得
*/
private function getUsers($request, $users_columns, int $columns_set_id)
private function getUsersQuery($request, $users_columns, int $columns_set_id)
{
return $this->getUsersPaginate($request, null, $users_columns, $columns_set_id, false);
}

/**
* データ取得(paginate or get)
* ユーザデータ取得(paginate or query)
*/
private function getUsersPaginate($request, $page, $users_columns, int $columns_set_id, $is_paginate = true)
{
Expand Down Expand Up @@ -372,11 +367,20 @@ private function getUsersPaginate($request, $page, $users_columns, int $columns_
if ($is_paginate) {
// ページャーで取得
$users = $users_query->paginate(10, null, 'page', $page);
// ユーザデータ取得後の追加処理
return $this->getUsersAfter($users);

} else {
// getで取得
$users = $users_query->get();
// query取得
return $users_query;
}
}

/**
* ユーザデータ取得後の追加処理
*/
private function getUsersAfter($users)
{
// ユーザデータからID の配列生成
$user_ids = array();
foreach ($users as $user) {
Expand Down Expand Up @@ -1595,8 +1599,8 @@ public function downloadCsv($request, $id = null, $sub_id = null, $data_output_f
// ユーザーのカラム
$users_columns = UsersTool::getUsersColumns($id);

// User データの取得
$users = $this->getUsers($request, $users_columns, $id);
// ユーザquery取得
$users_query = $this->getUsersQuery($request, $users_columns, $id);

/*
ダウンロード前の配列イメージ。
Expand All @@ -1619,57 +1623,21 @@ public function downloadCsv($request, $id = null, $sub_id = null, $data_output_f
45 =>
]
*/
// 返却用配列
$csv_array = array();

// データ行用の空配列
$copy_base = array();

// 見出し行
$head = array();

// インポートカラムの取得
$import_column = $this->getImportColumn($users_columns);

// 見出し行
foreach ($import_column as $key => $column_name) {
$csv_array[0][$key] = $column_name;
$head[$key] = $column_name;
$copy_base[$key] = '';
}

// $data_output_flag = falseは、CSVフォーマットダウンロード処理
if ($data_output_flag) {
// usersデータ
foreach ($users as $user) {
// ベースをセット
$csv_array[$user->id] = $copy_base;

// 初回で固定項目をセット
$csv_array[$user->id]['id'] = $user->id;
$csv_array[$user->id]['userid'] = $user->userid; // ログインID
$csv_array[$user->id]['name'] = $user->name;

// グループ
$csv_array[$user->id]['group'] = $user->convertLoopValue('group_users', 'name', UsersTool::CHECKBOX_SEPARATOR);

$csv_array[$user->id]['email'] = $user->email;
$csv_array[$user->id]['password'] = ''; // パスワード、中身は空で出力

// 権限
$csv_array[$user->id]['view_user_roles'] = $user->convertLoopValue('view_user_roles', 'role_name', UsersTool::CHECKBOX_SEPARATOR);

// 役割設定
$csv_array[$user->id]['user_original_roles'] = $user->convertLoopValue('user_original_roles', 'value', UsersTool::CHECKBOX_SEPARATOR);

$csv_array[$user->id]['status'] = $user->status;
}

// 追加項目データの取得
$input_cols = UsersTool::getUsersInputCols($users->pluck('id')->all());

// 追加項目データ
foreach ($input_cols as $input_col) {
$csv_array[$input_col->users_id][$input_col->users_columns_id] = UsersTool::getUsersInputColValue($input_col);
}
}

// レスポンス
if (config('connect.USE_USERS_COLUMNS_SET')) {
$columns_set = UsersColumnsSet::findOrNew($id);
Expand All @@ -1682,10 +1650,85 @@ public function downloadCsv($request, $id = null, $sub_id = null, $data_output_f
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
];

// データ
$csv_data = CsvUtils::getResponseCsvData($csv_array, $request->character_code);
$character_code = $request->character_code;

// $data_output_flag = falseは、CSVフォーマットダウンロード処理
if (!$data_output_flag) {
// データ
$csv_array[0] = $head;
$csv_data = CsvUtils::getResponseCsvData($csv_array, $character_code);
return response()->make($csv_data, 200, $headers);
}

// Symfony の StreamedResponse で出力 & chunk でデータ取得することにより
// 大容量の出力に対応
return new StreamedResponse(
function () use ($users_query, $head, $copy_base, $character_code) {
$stream = fopen('php://output', 'w');

// 文字コード変換
if ($character_code == CsvCharacterCode::utf_8) {
mb_convert_variables(CsvCharacterCode::utf_8, CsvCharacterCode::utf_8, $head);
// BOM付きにさせる場合にファイルの先頭に書き込む
fwrite($stream, CsvUtils::bom);
} else {
mb_convert_variables(CsvCharacterCode::sjis_win, CsvCharacterCode::utf_8, $head);
}
fputcsv($stream, $head);

// データの処理
$users_query->chunk(1000, function ($users) use ($stream, $copy_base, $character_code) {

// ユーザデータ取得後の追加処理
$users = $this->getUsersAfter($users);

// 追加項目データの取得
$input_cols = UsersTool::getUsersInputCols($users->pluck('id')->all());

foreach ($users as $user) {
// ベースをセット
$csv_array = $copy_base;

// 初回で固定項目をセット
$csv_array['id'] = $user->id;
$csv_array['userid'] = $user->userid; // ログインID
$csv_array['name'] = $user->name;

// グループ
$csv_array['group'] = $user->convertLoopValue('group_users', 'name', UsersTool::CHECKBOX_SEPARATOR);

return response()->make($csv_data, 200, $headers);
$csv_array['email'] = $user->email;
$csv_array['password'] = ''; // パスワード、中身は空で出力

// 権限
$csv_array['view_user_roles'] = $user->convertLoopValue('view_user_roles', 'role_name', UsersTool::CHECKBOX_SEPARATOR);

// 役割設定
$csv_array['user_original_roles'] = $user->convertLoopValue('user_original_roles', 'value', UsersTool::CHECKBOX_SEPARATOR);

$csv_array['status'] = $user->status;

$input_cols_solo = $input_cols->where('users_id', $user->id);

// 追加項目データ
foreach ($input_cols_solo as $input_col) {
$csv_array[$input_col->users_columns_id] = UsersTool::getUsersInputColValue($input_col);
}

// 文字コード変換
if ($character_code == CsvCharacterCode::utf_8) {
mb_convert_variables(CsvCharacterCode::utf_8, CsvCharacterCode::utf_8, $csv_array);
} else {
mb_convert_variables(CsvCharacterCode::sjis_win, CsvCharacterCode::utf_8, $csv_array);
}
fputcsv($stream, $csv_array);
}
});
fclose($stream);
},
200,
$headers
);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions app/Traits/Migration/MigrationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -1964,6 +1964,8 @@ private function importGroups($redo)
'group_id' => $group->id,
'user_id' => $user_id,
'group_role' => 'general',
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
];
}
// バルクINSERT
Expand Down
4 changes: 2 additions & 2 deletions resources/views/plugins/manage/group/edit.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ function removeUser(user_id, user_name) {
<tr>
<td>{{$group_user->user_name}}</td>
{{-- <td>{{GroupType::getDescription($group_user->group_role)}}</td> --}}
<td>{{$group_user->created_at->format('Y/m/d')}}</td>
<td>{{$group_user->updated_at->format('Y/m/d')}}</td>
<td>{{ optional($group_user->created_at)->format('Y/m/d') }}</td>
<td>{{ optional($group_user->updated_at)->format('Y/m/d') }}</td>
<td>
<button type="button" class="btn btn-danger btn-sm" onclick="removeUser({{$group_user->user_id}}, '{{$group_user->user_name}}');">
<i class="fas fa-trash-alt"></i>
Expand Down