You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

137 lines
4.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Console\Commands;
use App\Models\User;
use App\Models\Venue;
use App\Support\VenueAdminCredentials;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
class BatchCreateVenueAdminAccountsCommand extends Command
{
protected $signature = 'venues:create-admin-accounts
{--output= : 导出 xlsx 绝对或相对路径(默认 storage/app/exports/venue_admins_时间戳.xlsx}
{--dry-run : 仅生成 Excel不向 users / user_venue 写入(预览或离线存档)}';
protected $description = '为每个场馆创建一个场馆管理员账号(姓名=馆名,用户名=拼音首字母缩写,密码按规则),并绑定该场馆;导出含明文密码的 Excel';
public function handle(): int
{
$venues = Venue::query()->orderBy('id')->get(['id', 'name']);
if ($venues->isEmpty()) {
$this->warn('数据库中暂无场馆。');
return self::FAILURE;
}
$this->info("将处理 {$venues->count()} 个场馆。");
$rows = [];
$reservedNames = User::query()->pluck('username')->all();
$reservedSet = array_fill_keys($reservedNames, true);
$batchUsed = [];
foreach ($venues as $venue) {
$name = (string) $venue->name;
$base = VenueAdminCredentials::acronymFromVenueName($name);
$plainPassword = VenueAdminCredentials::passwordFromAcronym($base);
$username = $base;
$suffix = 2;
while (isset($reservedSet[$username]) || isset($batchUsed[$username])) {
$username = $base.$suffix;
$suffix++;
}
$batchUsed[$username] = true;
$rows[] = [
'venue_id' => $venue->id,
'name' => $name,
'username' => $username,
'password_plain' => $plainPassword,
'role' => '场馆管理员',
'venue_name' => $name,
];
}
$defaultDir = storage_path('app/exports');
if (! is_dir($defaultDir)) {
mkdir($defaultDir, 0755, true);
}
$outPath = $this->option('output');
if (! $outPath) {
$outPath = $defaultDir.'/venue_admins_'.now()->format('Ymd_His').'.xlsx';
} elseif (! str_starts_with((string) $outPath, '/')) {
$outPath = base_path($outPath);
}
$dryRun = (bool) $this->option('dry-run');
if ($dryRun) {
$this->warn('当前为 --dry-run只生成 Excel不会写入 users / user_venue。去掉 --dry-run 才会入库。');
} else {
$this->info('即将写入数据库users、user_venue随后生成 Excel…');
DB::transaction(function () use ($rows) {
foreach ($rows as $row) {
$user = User::updateOrCreate(
['username' => $row['username']],
[
'name' => $row['name'],
'email' => null,
'password' => $row['password_plain'],
'role' => 'venue_admin',
'is_active' => true,
]
);
$user->venues()->sync([$row['venue_id']]);
}
});
$this->info('数据库写入完成:已创建/更新 '.count($rows).' 个场馆管理员并完成场馆绑定。');
}
$this->writeXlsx($outPath, $rows);
$this->info("Excel 已生成:{$outPath}");
if ($dryRun) {
$this->warn('本次未写入数据库。若需要入库请执行php artisan venues:create-admin-accounts不要带 --dry-run');
return self::SUCCESS;
}
return self::SUCCESS;
}
/**
* @param array<int, array{venue_id:int, name:string, username:string, password_plain:string, role:string, venue_name:string}> $rows
*/
private function writeXlsx(string $path, array $rows): void
{
$sheetRows = [
['场馆ID', '姓名', '用户名', '密码(明文)', '角色', '绑定场馆'],
];
foreach ($rows as $r) {
$sheetRows[] = [
$r['venue_id'],
$r['name'],
$r['username'],
$r['password_plain'],
$r['role'],
$r['venue_name'],
];
}
$spreadsheet = new Spreadsheet;
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle('场馆管理员账号');
$sheet->fromArray($sheetRows, null, 'A1');
$writer = new Xlsx($spreadsheet);
$writer->save($path);
}
}