运用外键同步删除与最后活跃时间

This commit is contained in:
fthvgb1 2018-06-02 23:37:19 +08:00
parent 395b1c619a
commit 194c3cc259
10 changed files with 287 additions and 5 deletions

View File

@ -0,0 +1,44 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
class SyncUserActivedAt extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'larabbs:sync-user-actived-at';
/**
* The console command description.
*
* @var string
*/
protected $description = '同步最后活动时间';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
* @param User $user
* @return mixed
*/
public function handle(User $user)
{
$user->syncUserActivedAt();
$this->info('同步成功');
}
}

View File

@ -27,6 +27,7 @@ class Kernel extends ConsoleKernel
// $schedule->command('inspire') // $schedule->command('inspire')
// ->hourly(); // ->hourly();
$schedule->command('larabbs:calculate-active-user')->hourly(); $schedule->command('larabbs:calculate-active-user')->hourly();
$schedule->command('larabbs:sync-user-actived-at')->dailyAt('00:05');
} }
/** /**

View File

@ -2,6 +2,7 @@
namespace App\Http; namespace App\Http;
use App\Http\Middleware\RecordLastActivedTime;
use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel class Kernel extends HttpKernel
@ -10,34 +11,50 @@ class Kernel extends HttpKernel
* The application's global HTTP middleware stack. * The application's global HTTP middleware stack.
* *
* These middleware are run during every request to your application. * These middleware are run during every request to your application.
* 全局中间件,最先调用
* *
* @var array * @var array
*/ */
protected $middleware = [ protected $middleware = [
// 检测是否应用是否进入『维护模式』
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
// 检测请求的数据是否过大
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
// 对提交的请求参数进行 PHP 函数 `trim()` 处理
\App\Http\Middleware\TrimStrings::class, \App\Http\Middleware\TrimStrings::class,
// 将提交请求参数中空子串转换为 null
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
// 修正代理服务器后的服务器参数
\App\Http\Middleware\TrustProxies::class, \App\Http\Middleware\TrustProxies::class,
]; ];
/** /**
* The application's route middleware groups. * The application's route middleware groups.
* *定义中间件组
* @var array * @var array
*/ */
protected $middlewareGroups = [ protected $middlewareGroups = [
// Web 中间件组,应用于 routes/web.php 路由文件
'web' => [ 'web' => [
// Cookie 加密解密
\App\Http\Middleware\EncryptCookies::class, \App\Http\Middleware\EncryptCookies::class,
// 将 Cookie 添加到响应中
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
// 开启会话
\Illuminate\Session\Middleware\StartSession::class, \Illuminate\Session\Middleware\StartSession::class,
// 认证用户,此中间件以后 Auth 类才能生效
// \Illuminate\Session\Middleware\AuthenticateSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class,
// 将系统的错误数据注入到视图变量 $errors 中
\Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class,
// 检验 CSRF ,防止跨站请求伪造的安全威胁
\App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class,
// 处理路由绑定
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
RecordLastActivedTime::class,
], ],
// API 中间件组,应用于 routes/api.php 路由文件
'api' => [ 'api' => [
// 使用别名来调用中间件
'throttle:60,1', 'throttle:60,1',
'bindings', 'bindings',
], ],
@ -45,17 +62,22 @@ class Kernel extends HttpKernel
/** /**
* The application's route middleware. * The application's route middleware.
* * 中间件别名设置,允许你使用别名调用中间件,例如上面的 api 中间件组调用
* These middleware may be assigned to groups or used individually. * These middleware may be assigned to groups or used individually.
* *
* @var array * @var array
*/ */
protected $routeMiddleware = [ protected $routeMiddleware = [
//只有登录用户才能访问,我们在控制器的构造方法中大量使用
'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
// 处理路由绑定
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
// 用户授权功能
'can' => \Illuminate\Auth\Middleware\Authorize::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class,
// 只有游客才能访问,在 register 和 login 请求中使用,只有未登录用户才能访问这些页面
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
// 访问节流,类似于 『1 分钟只能请求 10 次』的需求,一般在 API 中使用
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
]; ];
} }

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
class RecordLastActivedTime
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
if (\Auth::getUser()) {
\Auth::user()->recordLastActivedAt();
}
return $response;
}
}

View File

@ -2,12 +2,18 @@
namespace App\Models; namespace App\Models;
use App\Models\Traits\ActiveUserHelper; use App\Traits\ActiveUserHelper;
use App\Traits\LastActivedAtHelper;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Spatie\Permission\Traits\HasRoles; use Spatie\Permission\Traits\HasRoles;
/**
* Class User
* @property string avatar
* @package App\Models
*/
class User extends Authenticatable class User extends Authenticatable
{ {
use Notifiable { use Notifiable {
@ -15,6 +21,7 @@ class User extends Authenticatable
} }
use HasRoles; use HasRoles;
use ActiveUserHelper; use ActiveUserHelper;
use LastActivedAtHelper;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -34,11 +41,23 @@ class User extends Authenticatable
'password', 'remember_token', 'password', 'remember_token',
]; ];
/**
* @return string
*/
public function getHeaderAttribute() public function getHeaderAttribute()
{ {
return asset($this->avatar); return asset($this->avatar);
} }
public function getLastActiveATAttribute($value)
{
return $this->getLastActivedAt($value);
}
/**
* @param $path
*/
public function setAvatarAttribute($path) public function setAvatarAttribute($path)
{ {
if (!starts_with($path, 'http')) { if (!starts_with($path, 'http')) {
@ -47,21 +66,34 @@ class User extends Authenticatable
$this->attributes['avatar'] = $path; $this->attributes['avatar'] = $path;
} }
/**
* @param $model
* @return bool
*/
public function isAuthorOf($model) public function isAuthorOf($model)
{ {
return $this->id == $model->user_id; return $this->id == $model->user_id;
} }
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function replies() public function replies()
{ {
return $this->hasMany(Reply::class, 'user_id'); return $this->hasMany(Reply::class, 'user_id');
} }
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function topics() public function topics()
{ {
return $this->hasMany(Topic::class); return $this->hasMany(Topic::class);
} }
/**
* @param $value
*/
public function setPasswordAttribute($value) public function setPasswordAttribute($value)
{ {
if (!isset($value[59])) { if (!isset($value[59])) {
@ -70,6 +102,9 @@ class User extends Authenticatable
$this->attributes['password'] = $value; $this->attributes['password'] = $value;
} }
/**
* @param $instance
*/
public function notify($instance) public function notify($instance)
{ {
if ($this->id == Auth::id()) { if ($this->id == Auth::id()) {

View File

@ -6,7 +6,7 @@
* Time: 17:16 * Time: 17:16
*/ */
namespace App\Models\Traits; namespace App\Traits;
use App\Models\Reply; use App\Models\Reply;

View File

@ -0,0 +1,80 @@
<?php
/**
* Created by PhpStorm.
* User: xing
* Date: 2018/6/2
* Time: 22:29
*/
namespace App\Traits;
use Carbon\Carbon;
use Illuminate\Support\Facades\Redis;
/**
* Trait LastActivedAtHelper
* @property int id
* @package App\Traits
*/
trait LastActivedAtHelper
{
// 缓存相关
protected $hash_prefix = 'larabbs_last_actived_at_';
protected $field_prefix = 'user_';
public function recordLastActivedAt()
{
// 获取今天的日期
$date = Carbon::now()->toDateString();
// Redis 哈希表的命名larabbs_last_actived_at_2017-10-21
$hash = $this->hash_prefix . $date;
// 字段名称user_1
$field = $this->field_prefix . $this->id;
// 当前时间2017-10-21 08:35:15
$now = Carbon::now()->toDateTimeString();
// 数据写入 Redis ,字段已存在会被更新
Redis::hSet($hash, $field, $now);
}
public function syncUserActivedAt()
{
// 获取昨天的日期格式如2017-10-21
$yesterday_date = Carbon::yesterday()->toDateString();
// Redis 哈希表的命名larabbs_last_actived_at_2017-10-21
$hash = $this->hash_prefix . $yesterday_date;
// 从 Redis 中获取所有哈希表里的数据
$dates = Redis::hGetAll($hash);
// 遍历,并同步到数据库中
foreach ($dates as $user_id => $actived_at) {
// 会将 `user_1` 转换为 1
$user_id = str_replace($this->field_prefix, '', $user_id);
// 只有当用户存在时才更新到数据库中
if ($user = $this->find($user_id)) {
$user->last_active_at = $actived_at;
$user->save();
}
}
// 以数据库为中心的存储,既已同步,即可删除
Redis::del($hash);
}
public function getLastActivedAt($value)
{
$date = Carbon::now()->toDateString();
// Redis 哈希表的命名larabbs_last_actived_at_2017-10-21
$hash = $this->hash_prefix . $date;
$field = $this->field_prefix . $this->id;
$date = Redis::hGet($hash, $field) ?: $value;
return $date ? new Carbon($date) : $this->created_at;
}
}

View File

@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddReferences extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('topics', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
Schema::table('replies', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('topic_id')->references('id')->on('topics')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('topics', function (Blueprint $table) {
$table->dropForeign(['user_id']);
});
Schema::table('replies', function (Blueprint $table) {
$table->dropForeign(['user_id', 'topic_id']);
});
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddLastActivedAtToUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->timestamp('last_active_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('last_active_at');
});
}
}

View File

@ -19,6 +19,8 @@
<hr> <hr>
<h4><strong>注册于</strong></h4> <h4><strong>注册于</strong></h4>
<p>{{ $user->created_at->diffForHumans() }}</p> <p>{{ $user->created_at->diffForHumans() }}</p>
<h4><strong>最后活跃</strong></h4>
<p title="{{ $user->last_actived_at }}">{{ $user->last_active_at->diffForHumans() }}</p>
</div> </div>
</div> </div>
</div> </div>