|
|
|
|
@ -1233,4 +1233,225 @@ class UserController extends BaseController
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 批量头像预览:按课程 + 文件名(姓名)匹配学员,不上传、不更新
|
|
|
|
|
*/
|
|
|
|
|
public function batchHeadimgPreview()
|
|
|
|
|
{
|
|
|
|
|
$all = \request()->all();
|
|
|
|
|
$validator = Validator::make($all, [
|
|
|
|
|
'course_id' => 'required|integer',
|
|
|
|
|
], [
|
|
|
|
|
'course_id.required' => '请选择课程',
|
|
|
|
|
]);
|
|
|
|
|
if ($validator->fails()) {
|
|
|
|
|
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$courseId = (int) $all['course_id'];
|
|
|
|
|
$course = Course::find($courseId);
|
|
|
|
|
if (!$course) {
|
|
|
|
|
return $this->fail([ResponseCode::ERROR_BUSINESS, '课程不存在']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$files = \request()->file('files');
|
|
|
|
|
if (empty($files)) {
|
|
|
|
|
return $this->fail([ResponseCode::ERROR_BUSINESS, '请上传头像图片']);
|
|
|
|
|
}
|
|
|
|
|
if (!is_array($files)) {
|
|
|
|
|
$files = [$files];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$students = $this->getCourseStudents((int) $courseId);
|
|
|
|
|
$list = [];
|
|
|
|
|
$stats = ['ready' => 0, 'unmatched' => 0, 'duplicate' => 0, 'invalid' => 0];
|
|
|
|
|
|
|
|
|
|
foreach ($files as $index => $file) {
|
|
|
|
|
$filename = $file->getClientOriginalName();
|
|
|
|
|
$name = trim(pathinfo($filename, PATHINFO_FILENAME));
|
|
|
|
|
$item = [
|
|
|
|
|
'index' => $index,
|
|
|
|
|
'filename' => $filename,
|
|
|
|
|
'name' => $name,
|
|
|
|
|
'status' => 'unmatched',
|
|
|
|
|
'status_text' => '未匹配',
|
|
|
|
|
'user_id' => null,
|
|
|
|
|
'username' => '',
|
|
|
|
|
'mobile' => '',
|
|
|
|
|
'no' => '',
|
|
|
|
|
'current_headimgurl' => '',
|
|
|
|
|
'candidates' => [],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (!$file->isValid()) {
|
|
|
|
|
$item['status'] = 'invalid';
|
|
|
|
|
$item['status_text'] = '文件无效';
|
|
|
|
|
$stats['invalid']++;
|
|
|
|
|
$list[] = $item;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$ext = strtolower($file->getClientOriginalExtension());
|
|
|
|
|
if (!in_array($ext, ['png', 'gif', 'jpg', 'jpeg', 'bmp', 'svg', 'webp'])) {
|
|
|
|
|
$item['status'] = 'invalid';
|
|
|
|
|
$item['status_text'] = '文件格式不支持';
|
|
|
|
|
$stats['invalid']++;
|
|
|
|
|
$list[] = $item;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($file->getSize() > 500 * 1024) {
|
|
|
|
|
$item['status'] = 'invalid';
|
|
|
|
|
$item['status_text'] = '文件超过500KB';
|
|
|
|
|
$stats['invalid']++;
|
|
|
|
|
$list[] = $item;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($name === '') {
|
|
|
|
|
$item['status'] = 'invalid';
|
|
|
|
|
$item['status_text'] = '文件名无效';
|
|
|
|
|
$stats['invalid']++;
|
|
|
|
|
$list[] = $item;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$matched = $this->matchStudentsByName($students, $name);
|
|
|
|
|
if ($matched->isEmpty()) {
|
|
|
|
|
$stats['unmatched']++;
|
|
|
|
|
} elseif ($matched->count() > 1) {
|
|
|
|
|
$item['status'] = 'duplicate';
|
|
|
|
|
$item['status_text'] = '重名(' . $matched->count() . '人)';
|
|
|
|
|
$item['candidates'] = $matched->map(function ($user) {
|
|
|
|
|
return [
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
'username' => $user->username,
|
|
|
|
|
'mobile' => $user->mobile,
|
|
|
|
|
'no' => $user->no,
|
|
|
|
|
];
|
|
|
|
|
})->values()->all();
|
|
|
|
|
$stats['duplicate']++;
|
|
|
|
|
} else {
|
|
|
|
|
$user = $matched->first();
|
|
|
|
|
$item['status'] = 'ready';
|
|
|
|
|
$item['status_text'] = '可更新';
|
|
|
|
|
$item['user_id'] = $user->id;
|
|
|
|
|
$item['username'] = $user->username;
|
|
|
|
|
$item['mobile'] = $user->mobile;
|
|
|
|
|
$item['no'] = $user->no;
|
|
|
|
|
$item['current_headimgurl'] = $user->headimgurl;
|
|
|
|
|
$stats['ready']++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$list[] = $item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->success([
|
|
|
|
|
'course_id' => $courseId,
|
|
|
|
|
'course_name' => $course->name,
|
|
|
|
|
'list' => $list,
|
|
|
|
|
'stats' => $stats,
|
|
|
|
|
'student_total' => $students->count(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 批量头像导入:仅更新预览中匹配成功且属于该课程的学员
|
|
|
|
|
*/
|
|
|
|
|
public function batchHeadimgImport()
|
|
|
|
|
{
|
|
|
|
|
$all = \request()->all();
|
|
|
|
|
$validator = Validator::make($all, [
|
|
|
|
|
'course_id' => 'required|integer',
|
|
|
|
|
'items' => 'required|array|min:1',
|
|
|
|
|
'items.*.user_id' => 'required|integer',
|
|
|
|
|
'items.*.headimgurl' => 'required|string',
|
|
|
|
|
], [
|
|
|
|
|
'course_id.required' => '请选择课程',
|
|
|
|
|
'items.required' => '导入数据不能为空',
|
|
|
|
|
]);
|
|
|
|
|
if ($validator->fails()) {
|
|
|
|
|
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$courseId = (int) $all['course_id'];
|
|
|
|
|
$course = Course::find($courseId);
|
|
|
|
|
if (!$course) {
|
|
|
|
|
return $this->fail([ResponseCode::ERROR_BUSINESS, '课程不存在']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$courseUserIds = CourseSign::where('course_id', $courseId)->pluck('user_id')->unique()->filter()->values()->all();
|
|
|
|
|
$items = $all['items'];
|
|
|
|
|
$updatedCount = 0;
|
|
|
|
|
$failedRecords = [];
|
|
|
|
|
|
|
|
|
|
DB::beginTransaction();
|
|
|
|
|
try {
|
|
|
|
|
foreach ($items as $index => $item) {
|
|
|
|
|
$userId = (int) ($item['user_id'] ?? 0);
|
|
|
|
|
$headimgurl = trim($item['headimgurl'] ?? '');
|
|
|
|
|
|
|
|
|
|
if (!in_array($userId, $courseUserIds, true)) {
|
|
|
|
|
$failedRecords[] = [
|
|
|
|
|
'index' => $index,
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
'reason' => '该学员不属于所选课程',
|
|
|
|
|
];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user = $this->model->find($userId);
|
|
|
|
|
if (!$user) {
|
|
|
|
|
$failedRecords[] = [
|
|
|
|
|
'index' => $index,
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
'reason' => '学员不存在',
|
|
|
|
|
];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user->headimgurl = $headimgurl;
|
|
|
|
|
$user->save();
|
|
|
|
|
$updatedCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
|
|
|
|
return $this->success([
|
|
|
|
|
'total' => count($items),
|
|
|
|
|
'updated_count' => $updatedCount,
|
|
|
|
|
'failed_count' => count($failedRecords),
|
|
|
|
|
'failed_records' => $failedRecords,
|
|
|
|
|
]);
|
|
|
|
|
} catch (\Exception $exception) {
|
|
|
|
|
DB::rollBack();
|
|
|
|
|
return $this->fail([$exception->getCode(), $exception->getMessage()]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取课程下的学员(去重)
|
|
|
|
|
*/
|
|
|
|
|
protected function getCourseStudents(int $courseId)
|
|
|
|
|
{
|
|
|
|
|
$userIds = CourseSign::where('course_id', $courseId)->pluck('user_id')->unique()->filter();
|
|
|
|
|
if ($userIds->isEmpty()) {
|
|
|
|
|
return collect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return User::whereIn('id', $userIds)->get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 按姓名精确匹配学员
|
|
|
|
|
*/
|
|
|
|
|
protected function matchStudentsByName($students, string $name)
|
|
|
|
|
{
|
|
|
|
|
return $students->filter(function ($user) use ($name) {
|
|
|
|
|
$username = trim((string) ($user->username ?? ''));
|
|
|
|
|
$realName = trim((string) ($user->name ?? ''));
|
|
|
|
|
return $username === $name || $realName === $name;
|
|
|
|
|
})->values();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|