lion 2 months ago
commit 9bb3b0de38

@ -44,14 +44,13 @@ class CheckBirthday extends Command
*/
public function handle()
{
// 匹配今天生日格式YYYY-MM-DD 或 MM-DD
$today = date('m-d');
// 仅完整生日YYYY-MM-DD参与提醒避免 YYYY-MM 被自动补成 01 误判
$users = User::where('is_schoolmate', 1)
->where(function ($query) use ($today) {
$query->where('birthday', 'like', '%-' . $today)
->orWhere('birthday', 'like', $today . '%');
})
->whereNotNull('birthday')
->get();
$users = $users->filter(function ($user) {
return User::isBirthdayToday($user->birthday);
})->values();
$birthdayCount = $users->count();

@ -0,0 +1,157 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
class CheckMissingSchoolmateData extends Command
{
/**
* 允许直接认定为校友的课程体系名称
*
* @var string[]
*/
protected array $allowedCourseTypeNames = ['高研班', '攀峰班'];
/**
* 允许直接认定为校友的课程名称
*
* @var string[]
*/
protected array $allowedCourseNames = ['首期技术经理人领航班'];
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'check_missing_schoolmate_data';
/**
* The console command description.
*
* @var string
*/
protected $description = '按当前校友认定口径,检查符合条件但未进入校友库的用户,并输出名单';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$today = date('Y-m-d');
$allowedCourseTypeNames = $this->allowedCourseTypeNames;
$allowedCourseNames = $this->allowedCourseNames;
$missingUsers = User::query()
->where(function ($query) {
$query->whereNull('is_schoolmate')
->orWhere('is_schoolmate', '!=', 1);
})
->whereHas('courseSigns', function ($query) use ($today, $allowedCourseTypeNames, $allowedCourseNames) {
$query->where('status', 1)
->whereHas('course', function ($courseQuery) use ($today, $allowedCourseTypeNames, $allowedCourseNames) {
$courseQuery->where(function ($eligibleQuery) use ($today, $allowedCourseTypeNames, $allowedCourseNames) {
$eligibleQuery->where(function ($autoSchoolmateQuery) use ($today) {
$autoSchoolmateQuery->where('auto_schoolmate', 1)
->whereNotNull('start_date')
->where('start_date', '<=', $today);
})->orWhereHas('typeDetail', function ($typeQuery) use ($allowedCourseTypeNames) {
$typeQuery->whereIn('name', $allowedCourseTypeNames);
})->orWhereIn('name', $allowedCourseNames);
});
});
})
->with([
'courseSigns' => function ($query) use ($today, $allowedCourseTypeNames, $allowedCourseNames) {
$query->where('status', 1)
->whereHas('course', function ($courseQuery) use ($today, $allowedCourseTypeNames, $allowedCourseNames) {
$courseQuery->where(function ($eligibleQuery) use ($today, $allowedCourseTypeNames, $allowedCourseNames) {
$eligibleQuery->where(function ($autoSchoolmateQuery) use ($today) {
$autoSchoolmateQuery->where('auto_schoolmate', 1)
->whereNotNull('start_date')
->where('start_date', '<=', $today);
})->orWhereHas('typeDetail', function ($typeQuery) use ($allowedCourseTypeNames) {
$typeQuery->whereIn('name', $allowedCourseTypeNames);
})->orWhereIn('name', $allowedCourseNames);
});
})
->with('course.typeDetail');
}
])
->orderBy('id')
->get();
$this->info('检测口径:用户当前不是校友,但存在以下任一合格来源,则判定为“应进入校友库但未进入”:');
$this->line('1. 已审核通过,且课程开启自动入校友,且课程已开课');
$this->line('2. 已审核通过,且课程属于高研班体系');
$this->line('3. 已审核通过,且课程属于攀峰班体系');
$this->line('4. 已审核通过,且课程为“首期技术经理人领航班”');
if ($missingUsers->isEmpty()) {
$this->info('检测完成:当前未发现应进入校友库但未进入的用户。');
return self::SUCCESS;
}
$this->warn('检测完成:发现 ' . $missingUsers->count() . ' 位符合条件但未进入校友库的用户。');
$this->newLine();
$rows = [];
foreach ($missingUsers as $user) {
$eligibleCourses = $user->courseSigns
->map(function ($courseSign) use ($allowedCourseTypeNames, $allowedCourseNames, $today) {
if (empty($courseSign->course)) {
return '课程已删除';
}
$course = $courseSign->course;
$courseTypeName = $course->typeDetail->name ?? '空';
$reasons = [];
if ($course->auto_schoolmate == 1 && !empty($course->start_date) && $course->start_date <= $today) {
$reasons[] = '自动入校友';
}
if (in_array($courseTypeName, $allowedCourseTypeNames, true)) {
$reasons[] = '体系白名单';
}
if (in_array($course->name, $allowedCourseNames, true)) {
$reasons[] = '课程白名单';
}
return sprintf(
'%s[课程体系=%s,auto_schoolmate=%s,start_date=%s,命中规则=%s]',
$course->name,
$courseTypeName,
(string) $course->auto_schoolmate,
$course->start_date ?: '空',
empty($reasons) ? '无' : implode('+', $reasons)
);
})
->filter()
->unique()
->values()
->implode('');
$rows[] = [
'用户ID' => $user->id,
'姓名' => $user->name,
'手机号' => $user->mobile,
'当前校友状态' => (string) ($user->is_schoolmate ?? '空'),
'应入校友依据' => $eligibleCourses ?: '无',
'不合格原因' => '符合校友认定条件,但当前未进入校友库',
];
}
$this->table(
['用户ID', '姓名', '手机号', '当前校友状态', '应入校友依据', '不合格原因'],
$rows
);
$this->info('本次仅在命令行输出异常名单,不再生成 txt 文件。');
return self::FAILURE;
}
}

@ -0,0 +1,177 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
class CheckSchoolmateData extends Command
{
/**
* 允许直接认定为校友的课程体系名称
*
* @var string[]
*/
protected array $allowedCourseTypeNames = ['高研班', '攀峰班'];
/**
* 允许直接认定为校友的课程名称
*
* @var string[]
*/
protected array $allowedCourseNames = ['首期技术经理人领航班'];
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'check_schoolmate_data';
/**
* The console command description.
*
* @var string
*/
protected $description = '按当前校友认定口径校验所有校友数据,并自动修正不合格校友';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$today = date('Y-m-d');
$allowedCourseTypeNames = $this->allowedCourseTypeNames;
$allowedCourseNames = $this->allowedCourseNames;
$fixedUsers = User::query()
->where('is_schoolmate', 1)
->where(function ($query) {
$query->whereNull('mobile')
->orWhere('mobile', '');
})
->orderBy('id')
->get();
$fixedCount = 0;
foreach ($fixedUsers as $user) {
$user->is_schoolmate = 0;
$user->schoolmate_time = null;
if ($user->isDirty()) {
$user->save();
$fixedCount++;
}
}
$invalidUsers = User::query()
->where('is_schoolmate', 1)
->whereDoesntHave('courseSigns', function ($query) use ($today, $allowedCourseTypeNames, $allowedCourseNames) {
$query->where('status', 1)
->whereHas('course', function ($courseQuery) use ($today, $allowedCourseTypeNames, $allowedCourseNames) {
$courseQuery->where(function ($eligibleQuery) use ($today, $allowedCourseTypeNames, $allowedCourseNames) {
$eligibleQuery->where(function ($autoSchoolmateQuery) use ($today) {
$autoSchoolmateQuery->where('auto_schoolmate', 1)
->whereNotNull('start_date')
->where('start_date', '<=', $today);
})->orWhereHas('typeDetail', function ($typeQuery) use ($allowedCourseTypeNames) {
$typeQuery->whereIn('name', $allowedCourseTypeNames);
})->orWhereIn('name', $allowedCourseNames);
});
});
})
->with([
'courseSigns' => function ($query) {
$query->where('status', 1)->with('course.typeDetail');
}
])
->orderBy('id')
->get();
$schoolmateTotal = User::where('is_schoolmate', 1)->count();
$invalidCount = $invalidUsers->count();
$this->info("当前校友总数:{$schoolmateTotal}");
$this->info("已自动修正无手机号但仍是校友的用户数:{$fixedCount}");
$this->info('检测口径:用户当前是校友,但不存在以下任一合格来源,则判定为不符合要求:');
$this->line('1. 已审核通过,且课程开启自动入校友,且课程已开课');
$this->line('2. 已审核通过,且课程属于高研班体系');
$this->line('3. 已审核通过,且课程属于攀峰班体系');
$this->line('4. 已审核通过,且课程为“首期技术经理人领航班”');
if ($invalidUsers->isEmpty()) {
$this->info('检测完成:当前所有校友数据都符合校友认定规则。');
return self::SUCCESS;
}
$this->warn('检测完成:发现 ' . $invalidCount . ' 位校友不符合校友认定规则,以下用户将被自动取消校友身份。');
$this->newLine();
$rows = [];
foreach ($invalidUsers as $user) {
$passedCourses = $user->courseSigns
->map(function ($courseSign) use ($allowedCourseTypeNames, $allowedCourseNames, $today) {
if (empty($courseSign->course)) {
return '课程已删除';
}
$course = $courseSign->course;
$courseTypeName = $course->typeDetail->name ?? '空';
$reasons = [];
if ($course->auto_schoolmate == 1 && !empty($course->start_date) && $course->start_date <= $today) {
$reasons[] = '自动入校友';
}
if (in_array($courseTypeName, $allowedCourseTypeNames, true)) {
$reasons[] = '体系白名单';
}
if (in_array($course->name, $allowedCourseNames, true)) {
$reasons[] = '课程白名单';
}
return sprintf(
'%s[课程体系=%s,auto_schoolmate=%s,start_date=%s,命中规则=%s]',
$course->name,
$courseTypeName,
(string) $course->auto_schoolmate,
$course->start_date ?: '空',
empty($reasons) ? '无' : implode('+', $reasons)
);
})
->filter()
->unique()
->values()
->implode('');
$rows[] = [
'用户ID' => $user->id,
'姓名' => $user->name,
'手机号' => $user->mobile,
'成为校友时间' => $user->schoolmate_time ?: '空',
'已通过课程' => $passedCourses ?: '无',
'不符合原因' => '没有符合自动入校友规则的已通过课程',
];
}
$this->table(
['用户ID', '姓名', '手机号', '成为校友时间', '已通过课程', '不符合原因'],
$rows
);
$updatedCount = 0;
foreach ($invalidUsers as $user) {
$user->is_schoolmate = 0;
$user->schoolmate_time = null;
if ($user->isDirty()) {
$user->save();
$updatedCount++;
}
}
$this->newLine();
$this->info("已自动取消 {$updatedCount} 位异常校友的校友身份。");
$this->info('本次仅在命令行输出处理结果,不再生成 txt 文件。');
return self::SUCCESS;
}
}

@ -281,6 +281,7 @@ class CourseController extends BaseController
* @OA\Parameter(name="url_title", in="query", @OA\Schema(type="string"), description="链接地址"),
* @OA\Parameter(name="is_ganbu", in="query", @OA\Schema(type="integer"), description="是否干部课程-0否1是"),
* @OA\Parameter(name="is_chart", in="query", @OA\Schema(type="integer"), description="是否参与统计-0否1是默认1"),
* @OA\Parameter(name="free_enter_schoolmate", in="query", @OA\Schema(type="integer"), description="免费/公益课程是否计入进入校友库资格-0否1是"),
* @OA\Response(
* response=200,
* description="操作成功"

@ -711,7 +711,38 @@ class UserController extends BaseController
*/
public function import()
{
return parent::import();
$all = \request()->all();
$messages = [
'data.required' => '数据必填',
];
$validator = Validator::make($all, [
'data' => 'required',
], $messages);
if ($validator->fails()) {
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
$records = $all['data'];
DB::beginTransaction();
try {
$tableName = $this->model->getTable();
$existingColumns = (new CustomFormField)->getRowTableFieldsByComment($tableName);
$filteredRecords = array_map(function ($record) use ($existingColumns) {
return array_intersect_key($record, $existingColumns);
}, $records);
$filteredRecords = array_filter($filteredRecords);
foreach ($filteredRecords as $record) {
$this->model->create($record);
}
DB::commit();
return $this->success(['total' => count($records), 'filter_total' => count($filteredRecords)]);
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
}
/**
@ -834,20 +865,46 @@ class UserController extends BaseController
if (empty($idsArray)) {
return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, '编号不能为空']);
}
if (isset($all['is_schoolmate'])) {
if ($all['is_schoolmate'] == 1) {
// 仅当 非校友→校友 时写入 schoolmate_time已是校友的不更新避免覆盖
$this->model->whereIn('id', $idsArray)
->whereRaw('COALESCE(is_schoolmate, 0) != 1')
->update(['is_schoolmate' => 1, 'schoolmate_time' => now()]);
} else {
$this->model->whereIn('id', $idsArray)->update([
'is_schoolmate' => $all['is_schoolmate'],
'schoolmate_time' => null,
]);
DB::beginTransaction();
try {
$updatedCount = 0;
if (isset($all['is_schoolmate'])) {
if ($all['is_schoolmate'] == 1) {
// 改为逐条 save(),确保触发审计日志
$users = $this->model->whereIn('id', $idsArray)
->whereRaw('COALESCE(is_schoolmate, 0) != 1')
->get();
foreach ($users as $user) {
$user->is_schoolmate = 1;
$user->schoolmate_time = now();
if ($user->isDirty()) {
$user->save();
$updatedCount++;
}
}
} else {
// 改为逐条 save(),确保触发审计日志
$users = $this->model->whereIn('id', $idsArray)->get();
foreach ($users as $user) {
$user->is_schoolmate = $all['is_schoolmate'];
$user->schoolmate_time = null;
if ($user->isDirty()) {
$user->save();
$updatedCount++;
}
}
}
}
DB::commit();
return $this->success('批量更新成功,共更新 ' . $updatedCount . ' 条记录');
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
return $this->success('批量更新成功');
}
/**
@ -911,9 +968,20 @@ class UserController extends BaseController
DB::beginTransaction();
try {
$this->model->whereIn('id', $idsArray)->update($data);
$users = $this->model->whereIn('id', $idsArray)->get();
$updatedCount = 0;
foreach ($users as $user) {
// 改为逐条 save(),确保触发审计日志
$user->fill($data);
if ($user->isDirty()) {
$user->save();
$updatedCount++;
}
}
DB::commit();
return $this->success('批量更新成功,共更新 ' . count($idsArray) . ' 条记录');
return $this->success('批量更新成功,共更新 ' . $updatedCount . ' 条记录');
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);

@ -1077,17 +1077,67 @@ class CourseController extends CommonController
if ($validator->fails()) {
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
$monthTs = strtotime($all['month'] . '-01');
if ($monthTs === false) {
return $this->fail([ResponseCode::ERROR_PARAMETER, '月份格式错误']);
}
$monthStart = date('Y-m-01 00:00:00', $monthTs);
$monthEnd = date('Y-m-t 23:59:59', $monthTs);
$list = Calendar::with('course', 'courseContent')
->where(function ($query) use ($all) {
if (isset($all['type'])) {
$query->where('type', $all['type']);
}
})->where('start_time', 'like', '%' . $all['month'] . '%')
})->where(function ($query) use ($monthStart, $monthEnd) {
$query->whereBetween('start_time', [$monthStart, $monthEnd])
->orWhereBetween('end_time', [$monthStart, $monthEnd])
->orWhere(function ($sub) use ($monthStart, $monthEnd) {
$sub->where('start_time', '<=', $monthStart)
->where('end_time', '>=', $monthEnd);
});
})
->where('is_publish', 1)
->orderBy('start_time', 'asc')
->get();
$list->each(function ($calendar) {
$rawStartTime = $calendar->start_time ?? null;
$rawEndTime = $calendar->end_time ?? null;
$calendar->start_time = $this->normalizeCalendarDateTime($rawStartTime, $rawEndTime, true);
$calendar->end_time = $this->normalizeCalendarDateTime($rawStartTime, $rawEndTime, false);
});
return $this->success($list);
}
private function formatDateTimeWithSeconds($value)
{
if (empty($value)) {
return $value;
}
$timestamp = strtotime($value);
if ($timestamp === false) {
return $value;
}
return date('Y-m-d H:i:s', $timestamp);
}
private function normalizeCalendarDateTime($startValue, $endValue, $isStart = true)
{
$start = $this->formatDateTimeWithSeconds($startValue);
$end = $this->formatDateTimeWithSeconds($endValue);
$target = $isStart ? $start : $end;
if (empty($target)) {
return $target;
}
// 跨天事件统一按“日期维度”返回,避免前端以时分秒切段时漏首日
if (!empty($start) && !empty($end) && date('Y-m-d', strtotime($start)) !== date('Y-m-d', strtotime($end))) {
return date('Y-m-d 00:00:00', strtotime($target));
}
return $target;
}
}

@ -143,6 +143,7 @@ class OtherController extends CommonController
if ($cache && !empty($cache->payload)) {
$cache->last_matched_at = now();
$cache->save();
$this->syncCurrentUserCompanyType($cache->payload);
return $this->success($cache->payload);
}
@ -163,9 +164,28 @@ class OtherController extends CommonController
'fetched_at' => now(),
]
);
$this->syncCurrentUserCompanyType($result);
return $this->success($result);
}
protected function syncCurrentUserCompanyType(array $result): void
{
$user = $this->getUser();
if (!$user) {
return;
}
$companyType = implode(',', $result['tagList'] ?? []);
if ($user->company_type === $companyType) {
return;
}
$user->company_type = $companyType;
$user->save();
}
/**
* @OA\Get(
* path="/api/mobile/other/company-list",

@ -275,11 +275,18 @@ class UserController extends CommonController
}
// 是否有资格进入校友库
$enter_schoolmate = User::whereHas('courseSigns', function ($query) {
$query->where('fee_status', 1)->where('status', 1);
$query->where('status', 1)
->where(function ($schoolmateQuery) {
$schoolmateQuery->where('fee_status', 1)
->orWhereHas('course', function ($courseQuery) {
$courseQuery->where('is_fee', 0)
->where('free_enter_schoolmate', 1);
});
});
})->where('id', $this->getUserId())->count();
// 是否生日
$is_birthday = 0;
if (isset($user->birthday) && date('m-d', strtotime($user->birthday)) == date('m-d')) {
if (User::isBirthdayToday($user->birthday ?? null)) {
$is_birthday = 1;
}
return $this->success(compact('user', 'door_appointments', 'course_signs', 'enter_schoolmate', 'is_birthday'));

@ -488,8 +488,6 @@ class Company extends SoftDeletesModel
'^',
'&',
'*',
'(',
')',
'[',
']',
'{',
@ -516,8 +514,6 @@ class Company extends SoftDeletesModel
'·',
'',
'¥',
'',
'',
'【',
'】',
'《',

@ -21,6 +21,7 @@ class Course extends SoftDeletesModel
'show_txl_text',
'show_mobile_text',
'auto_schoolmate_text',
'free_enter_schoolmate_text',
'is_virtual_text'
];
@ -62,6 +63,12 @@ class Course extends SoftDeletesModel
return $array[$this->attributes['auto_schoolmate']];
}
public function getFreeEnterSchoolmateTextAttribute()
{
$array = [0 => '否', 1 => '是'];
return $array[$this->attributes['free_enter_schoolmate'] ?? 0];
}
public function getIsVirtualTextAttribute()
{
$array = [0 => '否', 1 => '是'];
@ -308,4 +315,3 @@ class Course extends SoftDeletesModel
}
}

@ -125,6 +125,97 @@ class User extends Authenticatable implements Auditable
'is_schoolmate' => ['否', '是'],
];
public static function normalizeBirthday($birthday): ?string
{
if ($birthday instanceof DateTimeInterface) {
return Carbon::instance(\DateTime::createFromInterface($birthday))->format('Y-m-d');
}
if ($birthday === null) {
return null;
}
$birthday = trim((string) $birthday);
if ($birthday === '') {
return null;
}
$normalized = str_replace(
['年', '月', '日', '/', '.', '_'],
['-', '-', '', '-', '-', '-'],
$birthday
);
$normalized = preg_replace('/\s+/', '', $normalized);
$normalized = preg_replace('/-+/', '-', $normalized);
$normalized = trim($normalized, '-');
if (preg_match('/^(\d{4})(\d{2})$/', $normalized, $matches)) {
$normalized = $matches[1] . '-' . $matches[2];
} elseif (preg_match('/^(\d{4})(\d{2})(\d{2})$/', $normalized, $matches)) {
$normalized = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
}
if (preg_match('/^(\d{4})-(\d{1,2})$/', $normalized, $matches)) {
$year = (int) $matches[1];
$month = (int) $matches[2];
if ($month >= 1 && $month <= 12) {
return sprintf('%04d-%02d', $year, $month);
}
}
if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $normalized, $matches)) {
$year = (int) $matches[1];
$month = (int) $matches[2];
$day = (int) $matches[3];
if (checkdate($month, $day, $year)) {
return sprintf('%04d-%02d-%02d', $year, $month, $day);
}
}
return null;
}
public static function hasBirthdayFormat(?string $birthday): bool
{
if (empty($birthday)) {
return false;
}
if (preg_match('/^\d{4}-\d{2}$/', $birthday)) {
[, $month] = array_map('intval', explode('-', $birthday));
return $month >= 1 && $month <= 12;
}
return self::hasCompleteBirthday($birthday);
}
public static function hasCompleteBirthday(?string $birthday): bool
{
if (empty($birthday) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $birthday)) {
return false;
}
[$year, $month, $day] = array_map('intval', explode('-', $birthday));
return checkdate($month, $day, $year);
}
public static function isBirthdayToday(?string $birthday): bool
{
if (!self::hasCompleteBirthday($birthday)) {
return false;
}
return substr($birthday, 5, 5) === date('m-d');
}
public function setBirthdayAttribute($value): void
{
$this->attributes['birthday'] = self::normalizeBirthday($value);
}
public function getMobileAttribute($value)
{
// 如果url中包含admin字符串则所有的手机号显示中间4位星号代替
@ -258,13 +349,22 @@ class User extends Authenticatable implements Auditable
// 将课程开课时间转换为 datetime 格式
$schoolmateTime = Carbon::parse($courseStartDate)->format('Y-m-d H:i:s');
// 批量更新:只更新还不是校友的学员;从 非校友→校友 时使用课程开课时间作为 schoolmate_time
return self::whereIn('id', $userIds)
// 改为逐条 save(),确保自动任务也能触发审计日志
$users = self::whereIn('id', $userIds)
->whereRaw('COALESCE(is_schoolmate, 0) != 1')
->update([
'is_schoolmate' => 1,
'schoolmate_time' => $schoolmateTime
]);
->get();
$updatedCount = 0;
foreach ($users as $user) {
$user->is_schoolmate = 1;
$user->schoolmate_time = $schoolmateTime;
if ($user->isDirty()) {
$user->save();
$updatedCount++;
}
}
return $updatedCount;
}
}

@ -195,5 +195,5 @@ return [
|
*/
'console' => false,
'console' => true,
];

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('birthday')->nullable()->comment('生日')->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->date('birthday')->nullable()->comment('生日')->change();
});
}
};

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('courses', function (Blueprint $table) {
$table->boolean('free_enter_schoolmate')
->default(0)
->comment('免费/公益课程是否计入进入校友库资格-0否1是')
->after('auto_schoolmate');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('courses', function (Blueprint $table) {
$table->dropColumn('free_enter_schoolmate');
});
}
};

@ -0,0 +1,20 @@
目前会影响“校友状态”的主要有 3 类操作:
## 1. 后台批量把一批人设为校友
后台支持批量操作,可以一次把多个人加入校友库,也可以一次把多个人从校友库移除。
这种方式的特点:一次会改很多人。过去这类操作没有审计日志,现在追加上了。
## 2. 导入数据时把用户设为校友
如果导入的表格里本身带有“是否校友”这一列,系统在导入时也可能把用户设为校友。
## 3. 系统自动把符合条件的学员加入校友库
系统有一个半小时一次的自动任务,检测课程开启了自动加入校友库设置,并且课程已经开始的审核通过的学员设置成校友。
## 问题排查说明
- 经排查,创业课-企业出海与境外投资法律风险及规划。这里人员被设置成校友来源于后台的批量设置功能。比如沈杰是在2025-11-20 10:56:00被批量设置成校友的一批的还有周炫坊李融。王洋是在2026-01-12 15:33:42被批量设置成校友的同一批的还有杨湾湾王婷。王文岩是在2026-01-23 17:40:02被批量设置成校友的。
- 终身校友的人员进入了校友库是由于3.20日反馈有人员是终身校友,但是没有校友权益的问题时候,我理解错误并且误操作把终身校友课程下人员批量设置成了校友但是没有及时发现导致的。

@ -344,8 +344,7 @@ Route::group(["namespace" => "Mobile", "prefix" => "mobile"], function () {
Route::get('other/banner', [\App\Http\Controllers\Mobile\OtherController::class, "banner"]);
// 公司查询
Route::get('other/company', [\App\Http\Controllers\Mobile\OtherController::class, "company"]);
// 公司详情
Route::get('other/company-detail', [\App\Http\Controllers\Mobile\OtherController::class, "companyDetail"]);
// 公司查询
Route::get('other/company-list', [\App\Http\Controllers\Mobile\OtherController::class, "companyList"]);
// 通知
@ -368,6 +367,8 @@ Route::group(["namespace" => "Mobile", "prefix" => "mobile"], function () {
// 支付回调
Route::any('course/pay_callback', [\App\Http\Controllers\Mobile\CourseController::class, "payCallback"]);
Route::group(['middleware' => ['sanctum.jwt:mobile']], function () {
// 公司详情
Route::get('other/company-detail', [\App\Http\Controllers\Mobile\OtherController::class, "companyDetail"]);
// 其他
Route::post('upload-file', [\App\Http\Controllers\Mobile\UploadController::class, "uploadFile"]);
// 用户信息

Loading…
Cancel
Save