app->singleton('snowflake', function () { return (new Snowflake( config('mint.snowflake.data_center_id'), config('mint.snowflake.worker_id') )) ->setStartTimeStamp( strtotime(config('mint.snowflake.start')) * 1000 ) ->setSequenceResolver( new LaravelSequenceResolver( $this->app->get('cache')->store() ) ); }); /* |-------------------------------------------------------------------------- | Romanize Service |-------------------------------------------------------------------------- */ $this->app->singleton(RomanizeService::class); /* |-------------------------------------------------------------------------- | Redis Cache(业务代码专用,门面 App\Facades\RedisCache,连 Redis Cluster) |-------------------------------------------------------------------------- | 只登记这个类,不替换框架的 cache 绑定:RateLimiter、schedule 互斥锁、 | snowflake 序列等框架内部仍走 Laravel Cache(config/cache.php)。 */ $this->app->singleton(RedisCache::class); /* |-------------------------------------------------------------------------- | Pali Serieses Service |-------------------------------------------------------------------------- */ $this->app->singleton(PaliSeriesesService::class); /* |-------------------------------------------------------------------------- | User Service |-------------------------------------------------------------------------- | | 必须是 singleton:它在请求内维护一张 uuid => 摘要的身份映射,同一个用户 | 在一次请求里被解析多次时只查一次库、只签一次头像 URL。每次 make 新实例 | 那张映射就永远是空的。 | */ $this->app->singleton(UserService::class); } /** * Bootstrap any application services. */ public function boot(): void { /* |-------------------------------------------------------------------------- | Laravel 13 Default Production Behaviors |-------------------------------------------------------------------------- */ $this->configureDefaults(); /* |-------------------------------------------------------------------------- | Custom Query Builder Macros |-------------------------------------------------------------------------- */ QueryBuilder::mixin( $this->app->make(QueryBuilderMacro::class) ); /* |-------------------------------------------------------------------------- | View Composers |-------------------------------------------------------------------------- */ View::composer('blog.*', BlogViewComposer::class); View::composer('layouts.blog', BlogViewComposer::class); /* |-------------------------------------------------------------------------- | Rate Limiters |-------------------------------------------------------------------------- | | 找回密码会往外发邮件:按 IP 限住撞库式的批量请求,按邮箱限住对同一个人的 | 邮件轰炸。设新密码的请求不带 email,只按 IP 计。 | */ RateLimiter::for('password-resets', fn (Request $request): array => array_filter([ Limit::perMinute(10)->by('ip:'.$request->ip()), $request->filled('email') ? Limit::perHour(5)->by('email:'.mb_strtolower((string) $request->input('email'))) : null, ])); // 注册验证码同样往外发邮件,口径与找回密码一致 RateLimiter::for('email-certifications', fn (Request $request): array => array_filter([ Limit::perMinute(10)->by('ip:'.$request->ip()), $request->filled('email') ? Limit::perHour(5)->by('email:'.mb_strtolower((string) $request->input('email'))) : null, ])); // 比对验证码、建账号:按 IP 限,挡住对 6 位验证码的穷举(单个码另有 5 次试错上限) RateLimiter::for('sign-up', fn (Request $request): Limit => Limit::perMinute(20)->by('ip:'.$request->ip())); // 登录:按「账号 + IP」挡住对单个账号的猜密码,按 IP 挡住换着账号撞库 RateLimiter::for('sessions', fn (Request $request): array => [ Limit::perMinute(5)->by('login:'.mb_strtolower((string) $request->input('login')).'|'.$request->ip()), Limit::perMinute(30)->by('ip:'.$request->ip()), ]); } /** * Configure default behaviors for production-ready applications. */ protected function configureDefaults(): void { /* |-------------------------------------------------------------------------- | Use Immutable Dates |-------------------------------------------------------------------------- */ Date::use(CarbonImmutable::class); /* |-------------------------------------------------------------------------- | Prevent destructive DB commands in production |-------------------------------------------------------------------------- */ DB::prohibitDestructiveCommands( app()->isProduction(), ); /* |-------------------------------------------------------------------------- | Strong password defaults in production |-------------------------------------------------------------------------- */ Password::defaults( fn (): ?Password => app()->isProduction() ? Password::min(12) ->mixedCase() ->letters() ->numbers() ->symbols() ->uncompromised() : null, ); } }