
本文详解如何在 laravel 中精准实现“每24小时为每个用户生成唯一促销码”的业务逻辑,避免重复创建、误删旧码,并提供高性能、可维护的优化方案。
本文详解如何在 laravel 中精准实现“每24小时为每个用户生成唯一促销码”的业务逻辑,避免重复创建、误删旧码,并提供高性能、可维护的优化方案。
在 Laravel 应用中实现定时促销码(Promocode)自动轮换时,核心挑战在于条件判断的准确性与执行顺序的合理性。原代码存在两个关键缺陷:
- 逻辑错位:在每次遍历用户前,都无差别地查询并删除所有过期 promo($promoCodes = Promocode::where('vk_user_id', '!=', null)->get()),但未按用户维度隔离;
- 缺失守卫条件:删除旧码后,未检查该用户是否已存在有效 promo,导致每次循环均强制创建新码——即使刚生成不到1小时。
✅ 正确实现步骤
1. 批量清理过期码(高效且原子)
使用 Eloquent 的批量删除,一次性清除所有 vk_user_id 非空且创建时间早于 24 小时前的记录:
Promocode::whereNotNull('vk_user_id')
->where('created_at', 'subDay(1))
->delete();
⚠️ 注意:delete() 不会触发模型事件(如 deleting/deleted)。若需监听,请改用 each() + delete(),但会牺牲性能。
2. 按用户粒度判断是否需新建
清理完成后,仅对当前无任何 promo 的用户生成新码:
$userIds = Promo::pluck('user_id');
foreach ($userIds as $id) {
// ✅ 守卫:若该用户已有 promo(说明刚清理完仍存在,即 exists()) {
continue;
}
$permitted_chars = '0123456789abcdefghijklmnopqrstuvwxyz';
$code = substr(str_shuffle($permitted_chars), 0, 8);
Promocode::create([
'name' => $code,
'sum' => '0.25',
'activates' => '100',
'vk_user_id' => $id,
]);
// ✅ 直接使用 $code,无需额外查询数据库
$message = "Your new promo: {$code}";
// VK API 调用(略去重复部分,保持结构清晰)
$url = 'https://api.vk.com/method/messages.send';
$params = [
'message' => $message,
'access_token' => 'token',
'v' => '5.81',
'peer_ids' => $id,
];
file_get_contents($url, false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => http_build_query($params),
],
]));
}
3. 进阶优化:利用 Eloquent 关系减少查询
若已定义 User 模型及其关联,可进一步提升可读性与扩展性:
// 在 User 模型中定义
public function promocodes()
{
return $this->hasMany(Promocode::class);
}
public function latestPromocode()
{
return $this->hasOne(Promocode::class)->latestOfMany();
}
然后在任务中直接筛选「无最新 promo 的用户」:
User::doesntHave('latestPromocode')
->whereIn('id', Promo::pluck('user_id'))
->each(function (User $user) use ($permitted_chars) {
$code = substr(str_shuffle($permitted_chars), 0, 8);
$user->promocodes()->create([
'name' => $code,
'sum' => '0.25',
'activates' => '100',
]);
$message = "Your new promo: {$code}";
// ... VK 发送逻辑
});
? 关键注意事项
- 时间比较务必用 Carbon::now()->subDay(1),而非手动计算小时差(diffInHours 易受时区、闰秒等干扰);
- 避免 N+1 查询:原代码中 $promoText = Promocode::where(...)->first() 属冗余操作,应直接复用生成的 $code;
- 事务安全(可选):若业务强一致性要求高,可在整个流程外包裹 DB::transaction();
- 任务调度建议:将此方法注册为 Artisan 命令,并通过 Laravel Scheduler 每日执行一次,而非依赖前端触发。
通过以上重构,逻辑清晰、性能可控、易于测试与维护,真正实现“一人一码、每日一更”的业务目标。











