From 3c35914be0f9be99176d0dd0a5a21469daa415aa Mon Sep 17 00:00:00 2001 From: cody <648753004@qq.com> Date: Wed, 14 Jan 2026 15:03:33 +0800 Subject: [PATCH 1/4] update --- .../Controllers/Mobile/CourseController.php | 110 ++++++++++-------- .../Controllers/Mobile/UserController.php | 16 ++- app/Models/Company.php | 88 ++++++++++++++ 3 files changed, 161 insertions(+), 53 deletions(-) diff --git a/app/Http/Controllers/Mobile/CourseController.php b/app/Http/Controllers/Mobile/CourseController.php index 798be57..f872de5 100755 --- a/app/Http/Controllers/Mobile/CourseController.php +++ b/app/Http/Controllers/Mobile/CourseController.php @@ -9,6 +9,7 @@ use App\Helpers\ResponseCode; use App\Models\AccompanyOrder; use App\Models\AppointmentTotalLog; use App\Models\Calendar; +use App\Models\Company; use App\Models\Config; use App\Models\Course; use App\Models\CourseAppointmentTotal; @@ -124,10 +125,10 @@ class CourseController extends CommonController ]); } ])->withCount([ - 'courseSigns as my_user' => function ($query) { - $query->where('user_id', $this->getUserId()); - } - ])->find($all['course_id']); + 'courseSigns as my_user' => function ($query) { + $query->where('user_id', $this->getUserId()); + } + ])->find($all['course_id']); return $this->success($detail); } @@ -266,6 +267,17 @@ class CourseController extends CommonController return $this->fail([ResponseCode::ERROR_PARAMETER, '以下字段为必填项:' . implode('、', $missingFields)]); } } + // 检测 company_name 字段是否包含特殊符号 + if (isset($all['data']) && is_array($all['data'])) { + foreach ($all['data'] as $item) { + if (isset($item['field']) && $item['field'] === 'company_name' && !empty($item['value'])) { + $validation = Company::validateCompanyName($item['value']); + if (!$validation['valid']) { + return $this->fail([ResponseCode::ERROR_BUSINESS, $validation['message']]); + } + } + } + } $result = CourseSign::create([ 'is_change' => $all['is_change'] ?? 0, 'course_id' => $all['course_id'], @@ -803,54 +815,54 @@ class CourseController extends CommonController $query->where('status', 1); } })->with([ - 'courseSigns' => function ($query) use ($all) { - $query->where('status', 1)->whereHas('course', function ($q) { - $q->where('is_fee', 1); - })->with('course.teacher', 'course.typeDetail') - ->orderByRaw("FIELD(fee_status, 1, 0, 2,3)"); - if (isset($all['course_id'])) { - $query->where('course_id', $all['course_id']); - } - } - ])->where(function ($query) use ($all) { - if ($all['type'] == 1) { - $query->where('is_schoolmate', 1); - } - if (isset($all['name'])) { - $query->where('name', 'like', '%' . $all['name'] . '%'); - } - if (isset($all['company_business'])) { - $query->where('company_business', 'like', '%' . $all['company_business'] . '%'); - } - if (isset($all['company_name'])) { - $query->where('company_name', 'like', '%' . $all['company_name'] . '%'); - } - if (isset($all['company_position'])) { - $query->where('company_position', $all['company_position']); - } - if (isset($all['company_area'])) { - $query->where('company_area', 'like', '%' . $all['company_area'] . '%'); - } - if (isset($all['company_type'])) { - $company_type = explode(',', $all['company_type']); - $query->where(function ($q) use ($company_type) { - foreach ($company_type as $v) { - $q->orWhereRaw('FIND_IN_SET(?, company_type)', [$v]); + 'courseSigns' => function ($query) use ($all) { + $query->where('status', 1)->whereHas('course', function ($q) { + $q->where('is_fee', 1); + })->with('course.teacher', 'course.typeDetail') + ->orderByRaw("FIELD(fee_status, 1, 0, 2,3)"); + if (isset($all['course_id'])) { + $query->where('course_id', $all['course_id']); + } } - }); - } - if (isset($all['company_industry'])) { - $company_industry = explode(',', $all['company_industry']); - $query->where(function ($q) use ($company_industry) { - foreach ($company_industry as $v) { - $q->orWhereRaw('FIND_IN_SET(?, company_industry)', [$v]); + ])->where(function ($query) use ($all) { + if ($all['type'] == 1) { + $query->where('is_schoolmate', 1); + } + if (isset($all['name'])) { + $query->where('name', 'like', '%' . $all['name'] . '%'); + } + if (isset($all['company_business'])) { + $query->where('company_business', 'like', '%' . $all['company_business'] . '%'); + } + if (isset($all['company_name'])) { + $query->where('company_name', 'like', '%' . $all['company_name'] . '%'); + } + if (isset($all['company_position'])) { + $query->where('company_position', $all['company_position']); + } + if (isset($all['company_area'])) { + $query->where('company_area', 'like', '%' . $all['company_area'] . '%'); + } + if (isset($all['company_type'])) { + $company_type = explode(',', $all['company_type']); + $query->where(function ($q) use ($company_type) { + foreach ($company_type as $v) { + $q->orWhereRaw('FIND_IN_SET(?, company_type)', [$v]); + } + }); + } + if (isset($all['company_industry'])) { + $company_industry = explode(',', $all['company_industry']); + $query->where(function ($q) use ($company_industry) { + foreach ($company_industry as $v) { + $q->orWhereRaw('FIND_IN_SET(?, company_industry)', [$v]); + } + }); + } + if (isset($all['letter'])) { + $query->where('letter', $all['letter']); } }); - } - if (isset($all['letter'])) { - $query->where('letter', $all['letter']); - } - }); if (isset($all['type']) && $all['type'] == 2) { $list = $list->orderBy('letter')->paginate(10); } else { diff --git a/app/Http/Controllers/Mobile/UserController.php b/app/Http/Controllers/Mobile/UserController.php index 9250802..31a19b0 100755 --- a/app/Http/Controllers/Mobile/UserController.php +++ b/app/Http/Controllers/Mobile/UserController.php @@ -10,6 +10,7 @@ use App\Helpers\StarterResponseCode; use App\Jobs\SendAppointCar; use App\Jobs\SendCourseCar; use App\Models\Appointment; +use App\Models\Company; use App\Models\Config; use App\Models\CourseContentCheck; use App\Models\CourseSign; @@ -181,6 +182,13 @@ class UserController extends CommonController if (isset($all['name']) && !empty($all['name'])) { $all['letter'] = strtoupper(Pinyin::abbr(mb_substr($all['name'], 0, 1))[0]); } + // 如果上传了company_name,检测是否包含特殊符号 + if (isset($all['company_name']) && !empty($all['company_name'])) { + $validation = Company::validateCompanyName($all['company_name']); + if (!$validation['valid']) { + return $this->fail([ResponseCode::ERROR_BUSINESS, $validation['message']]); + } + } $model->fill($all); $model->save(); // 如果有公司信息,就更新一下公司 @@ -232,10 +240,10 @@ class UserController extends CommonController { $user = User::with('appointments') ->withCount([ - 'appointments as pass_appointments' => function ($query) { - $query->whereIn('status', [0, 1]); - } - ])->with([ + 'appointments as pass_appointments' => function ($query) { + $query->whereIn('status', [0, 1]); + } + ])->with([ 'courseSigns' => function ($query) { $query->whereHas('course')->with('course.typeDetail')->where('status', 1)->where('fee_status', 1); } diff --git a/app/Models/Company.php b/app/Models/Company.php index afaa617..b102b44 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -331,4 +331,92 @@ class Company extends SoftDeletesModel return false; } + /** + * 验证公司名称是否包含特殊符号 + * @param string $companyName 公司名称 + * @return array 返回结果 ['valid' => bool, 'message' => string] + */ + public static function validateCompanyName($companyName) + { + if (empty($companyName)) { + return ['valid' => true, 'message' => '']; + } + + // 定义不允许的特殊符号(包含中英文标点符号,键盘上能打出来的所有标点符号) + $forbiddenChars = [ + // 英文标点符号 + '/', + '\\', + '.', + ',', + ';', + ':', + "'", + '"', + '?', + '!', + '@', + '#', + '$', + '%', + '^', + '&', + '*', + '(', + ')', + '[', + ']', + '{', + '}', + '|', + '`', + '~', + '-', + '_', + '+', + '=', + '<', + '>', + // 中文标点符号 + '。', + ',', + '、', + ';', + ':', + '?', + '!', + '…', + '—', + '·', + '~', + '¥', + '(', + ')', + '【', + '】', + '《', + '》', + '〈', + '〉', + '「', + '」', + '『', + '』', + '〔', + '〕', + ]; + + // 添加中文引号字符(使用十六进制编码避免语法错误) + $chineseQuotes = ["\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\x98", "\xE2\x80\x99"]; // " " ' ' + $forbiddenChars = array_merge($forbiddenChars, $chineseQuotes); + + foreach ($forbiddenChars as $char) { + if (strpos($companyName, $char) !== false) { + return ['valid' => false, 'message' => '公司名称不能包含特殊符号']; + } + } + + return ['valid' => true, 'message' => '']; + } + } From 43dea90e9cd430f6e41f9a4a4497a1d5f104680d Mon Sep 17 00:00:00 2001 From: cody <648753004@qq.com> Date: Wed, 14 Jan 2026 15:13:40 +0800 Subject: [PATCH 2/4] update --- app/Console/Commands/UpdateLetter.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/Console/Commands/UpdateLetter.php b/app/Console/Commands/UpdateLetter.php index 5775fd5..b3a191b 100755 --- a/app/Console/Commands/UpdateLetter.php +++ b/app/Console/Commands/UpdateLetter.php @@ -42,6 +42,16 @@ class UpdateLetter extends Command */ public function handle() { + // 检测所有用户,如果 name 和 username 不一样,就把 username 赋值给 name 保持一致 + $usersToUpdate = User::whereNotNull('username') + ->where(function ($query) { + $query->whereNull('name')->orWhereColumn('name', '!=', 'username'); + })->limit(100)->get(); + foreach ($usersToUpdate as $user) { + $user->name = $user->username; + $user->save(); + } + // 更新用户首字母 $users = User::whereNull('letter') ->where(function ($query) { From 7b07924ba20ac0d9cc4e1849383f95b2c7641fdf Mon Sep 17 00:00:00 2001 From: cody <648753004@qq.com> Date: Wed, 14 Jan 2026 15:26:00 +0800 Subject: [PATCH 3/4] update --- app/Models/Company.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/Company.php b/app/Models/Company.php index b102b44..e289ffc 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -13,9 +13,9 @@ class Company extends SoftDeletesModel public function getIsYhInvestedTextAttribute() { if (empty($this->is_yh_invested)) { - return '否'; + return ''; } - return $this->is_yh_invested == 1 ? '是' : '否'; + return $this->is_yh_invested == 1 ? '被投企业' : ''; } public function users() From d45e8322075d6d1b7152c2e7f3e46386bd855dcd Mon Sep 17 00:00:00 2001 From: cody <648753004@qq.com> Date: Wed, 14 Jan 2026 16:46:32 +0800 Subject: [PATCH 4/4] update --- app/Console/Commands/CheckBirthday.php | 93 +++++++++++++------ app/Models/BirthdayMessage.php | 39 +++++++- ...ail_subject_to_birthday_messages_table.php | 39 ++++++++ 3 files changed, 144 insertions(+), 27 deletions(-) create mode 100644 database/migrations/2026_01_14_164124_add_type_and_email_subject_to_birthday_messages_table.php diff --git a/app/Console/Commands/CheckBirthday.php b/app/Console/Commands/CheckBirthday.php index 7b765f9..265885b 100755 --- a/app/Console/Commands/CheckBirthday.php +++ b/app/Console/Commands/CheckBirthday.php @@ -4,6 +4,7 @@ namespace App\Console\Commands; use App\Models\BirthdayMessage; use App\Models\Config; +use App\Models\EmailRecordUser; use App\Models\User; use App\Repositories\MeetRepository; use Illuminate\Console\Command; @@ -56,42 +57,82 @@ class CheckBirthday extends Command // 发送通知给用户 $smsSign = Config::getValueByKey('sms_sign') ?: ''; - $userSuccessCount = 0; - $userFailCount = 0; + $userSmsSuccessCount = 0; + $userSmsFailCount = 0; + $userEmailSuccessCount = 0; + $userEmailFailCount = 0; foreach ($users as $user) { - // 检查用户是否有手机号 - if (empty($user->mobile)) { - continue; + $username = $user->username ?: '校友'; + $hasMobile = !empty($user->mobile); + $hasEmail = !empty($user->email); + + // 发送短信 + if ($hasMobile) { + // 获取随机短信模板 + $smsMessage = BirthdayMessage::getRandomSmsMessage(); + if ($smsMessage) { + // 替换用户名占位符 + $smsContent = str_replace('{username}', $username, $smsMessage); + + // 添加短信签名 + $smsContent = $smsSign . $smsContent; + + // 直接发送短信 + $result = ymSms($user->mobile, $smsContent); + if ($result) { + $userSmsSuccessCount++; + $this->info("已向用户 {$username}({$user->mobile}) 发送生日祝福短信"); + } else { + $userSmsFailCount++; + $this->error("向用户 {$username}({$user->mobile}) 发送短信失败"); + } + } } - // 获取随机文案 - $message = BirthdayMessage::getRandomMessage(); - if ($message) { - // 替换用户名占位符 - $username = $user->username ?: '校友'; - $content = str_replace('{username}', $username, $message); - - // 添加短信签名 - $content = $smsSign . $content; - - // 直接发送短信 - $result = ymSms($user->mobile, $content); - if ($result) { - $userSuccessCount++; - $this->info("已向用户 {$user->username}({$user->mobile}) 发送生日祝福短信"); + // 发送邮件 + if ($hasEmail) { + // 获取随机邮件模板 + $emailTemplate = BirthdayMessage::getRandomEmailMessage(); + if ($emailTemplate) { + try { + // 准备变量数据 + $varData = [ + 'username' => $username, + ]; + + // 使用模板方法替换邮件标题和内容中的变量 + $emailSubject = EmailRecordUser::template($emailTemplate['subject'], $varData); + $emailContent = EmailRecordUser::template($emailTemplate['content'], $varData); + + // 发送邮件 + EmailRecordUser::email($emailSubject, $emailContent, $user->email); + $userEmailSuccessCount++; + $this->info("已向用户 {$username}({$user->email}) 发送生日祝福邮件"); + } catch (\Exception $e) { + $userEmailFailCount++; + $this->error("向用户 {$username}({$user->email}) 发送邮件失败: " . $e->getMessage()); + } } else { - $userFailCount++; - $this->error("向用户 {$user->username}({$user->mobile}) 发送短信失败"); + $this->warn("未找到可用的邮件模板,跳过向用户 {$username}({$user->email}) 发送邮件"); } } } - if ($userSuccessCount > 0) { - $this->info("共向 {$userSuccessCount} 位用户发送生日祝福短信成功"); + // 输出短信发送统计 + if ($userSmsSuccessCount > 0) { + $this->info("共向 {$userSmsSuccessCount} 位用户发送生日祝福短信成功"); + } + if ($userSmsFailCount > 0) { + $this->error("共 {$userSmsFailCount} 位用户短信发送失败"); + } + + // 输出邮件发送统计 + if ($userEmailSuccessCount > 0) { + $this->info("共向 {$userEmailSuccessCount} 位用户发送生日祝福邮件成功"); } - if ($userFailCount > 0) { - $this->error("共 {$userFailCount} 位用户短信发送失败"); + if ($userEmailFailCount > 0) { + $this->error("共 {$userEmailFailCount} 位用户邮件发送失败"); } // 如果有生日用户,给管理员发送短信 diff --git a/app/Models/BirthdayMessage.php b/app/Models/BirthdayMessage.php index 84e921d..bf639a6 100644 --- a/app/Models/BirthdayMessage.php +++ b/app/Models/BirthdayMessage.php @@ -6,7 +6,7 @@ class BirthdayMessage extends SoftDeletesModel { /** - * 随机获取一条启用的生日祝福文案 + * 随机获取一条启用的生日祝福文案(短信模板) * * @return string|null */ @@ -18,5 +18,42 @@ class BirthdayMessage extends SoftDeletesModel return $message ? $message->content : null; } + + /** + * 随机获取一条启用的短信模板 + * + * @return string|null + */ + public static function getRandomSmsMessage() + { + $message = self::where('status', 1) + ->where('type', 1) + ->inRandomOrder() + ->first(); + + return $message ? $message->content : null; + } + + /** + * 随机获取一条启用的邮件模板 + * + * @return array|null 返回包含 subject 和 content 的数组,如果没有找到则返回 null + */ + public static function getRandomEmailMessage() + { + $message = self::where('status', 1) + ->where('type', 2) + ->inRandomOrder() + ->first(); + + if (!$message) { + return null; + } + + return [ + 'subject' => $message->email_subject ?: '生日快乐', + 'content' => $message->content, + ]; + } } diff --git a/database/migrations/2026_01_14_164124_add_type_and_email_subject_to_birthday_messages_table.php b/database/migrations/2026_01_14_164124_add_type_and_email_subject_to_birthday_messages_table.php new file mode 100644 index 0000000..5a19445 --- /dev/null +++ b/database/migrations/2026_01_14_164124_add_type_and_email_subject_to_birthday_messages_table.php @@ -0,0 +1,39 @@ +tinyInteger('type')->default(1)->after('id')->comment('模板类型:1=短信模板,2=邮件模板'); + // 添加邮件标题字段(仅邮件模板使用) + $table->string('email_subject')->nullable()->after('content')->comment('邮件标题,支持{username}占位符'); + }); + + // 将现有所有记录标记为短信模板(type=1) + DB::table('birthday_messages')->whereNull('type')->update(['type' => 1]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('birthday_messages', function (Blueprint $table) { + $table->dropColumn(['type', 'email_subject']); + }); + } +}; +