做好了初始化工作以后,下一步我们就需要用户注册、然后登陆到网站的操作,目前我还没有接入手机号验证的功能,所以暂时先使用用户名的方式去登陆到网站。
Laravel内置了一套非常好用的用户验证助手,所以我决定在此基础上直接修改成为我需要的结构即可。
首先可以参考一下我目前的用户表设计,字段信息与索引信息等:
我创建的表结构相对比较多,而且更接近实际项目中使用到的用户表,可以使用下面的结构做为参考,根据自己的需求增减。
CREATE TABLE `meishi_users` (
`id` mediumint unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`group_id` smallint unsigned NOT NULL DEFAULT '0' COMMENT '用户组ID',
`is_vip` tinyint NOT NULL DEFAULT '0' COMMENT '是否vip',
`username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '密码',
`title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户属性描述',
`sex` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '姓别:0未知,1男,2女',
`mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号码',
`email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '邮箱地址',
`signature` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '个性签名',
`register_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '注册IP',
`last_login_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '最后登陆IP',
`experience` int NOT NULL DEFAULT '0' COMMENT '经验值',
`follow_count` int unsigned NOT NULL DEFAULT '0' COMMENT '关注数',
`fans_count` int unsigned NOT NULL DEFAULT '0' COMMENT '粉丝数',
`liked_count` int unsigned NOT NULL DEFAULT '0' COMMENT '被点赞总数',
`post_count` int unsigned NOT NULL DEFAULT '0' COMMENT '投稿总数',
`comment_count` int unsigned NOT NULL DEFAULT '0' COMMENT '回复总数',
`avatar` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '头像URL',
`avatar_at` datetime DEFAULT NULL COMMENT '头像修改时间',
`created_at` datetime DEFAULT NULL COMMENT '注册时间',
`updated_at` datetime DEFAULT NULL COMMENT '最后更新时间',
`login_at` datetime DEFAULT NULL COMMENT '最后登陆时间',
`last_comment_at` datetime DEFAULT NULL COMMENT '最后评论时间',
`last_post_ate` datetime DEFAULT NULL COMMENT '最后投稿时间',
`identity` char(18) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '身份证号码',
`realname` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '身份证姓名',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '用户状态:0正常,1禁用,2审核,3审核拒绝,4审核忽略',
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`) USING BTREE COMMENT '邮箱不能重复',
UNIQUE KEY `username` (`username`) USING BTREE COMMENT '用户名不可重复',
KEY `groupid` (`group_id`) USING BTREE COMMENT '搜索用户组索引'
) ENGINE=InnoDB AUTO_INCREMENT=1000000 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='用户表';
CREATE TABLE `meishi_user_groups` (
`id` smallint unsigned NOT NULL AUTO_INCREMENT COMMENT '用户组ID',
`name` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户组名',
`color` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户组颜色',
`icon` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户组图标',
`type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '组类型:-1系统,0自定义',
`explower` int unsigned NOT NULL DEFAULT '0' COMMENT '升级本组所需的经验',
`allow_post` tinyint(1) NOT NULL DEFAULT '0' COMMENT '允许投稿',
`allow_comment` tinyint(1) NOT NULL DEFAULT '0' COMMENT '允许评论',
`allow_upload` tinyint(1) NOT NULL DEFAULT '0' COMMENT '允许上传',
`allow_vote` tinyint(1) NOT NULL DEFAULT '1' COMMENT '允许投票',
`is_vip` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否VIP:0否,1是',
`fee` int unsigned NOT NULL DEFAULT '0' COMMENT '收费金额(分)',
`days` smallint unsigned NOT NULL DEFAULT '0' COMMENT '付费获得天数',
`description` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户组说明',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='用户组';
用户组需要与用户表关联关系:
ALTER TABLE `meishi_users` ADD CONSTRAINT `user_group_ship` FOREIGN KEY (`group_id`) REFERENCES `meishi_user_groups`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT;
后期可能还会有部分关联表创建,这里只做简单的演示。
一般情况下我们需要内置一些系统用户,如:管理员、VIP、游客等,限制其使用的权限:
INSERT INTO `meishi_user_groups` (`id`, `name`, `color`, `icon`, `type`, `explower`, `allow_post`, `allow_comment`, `allow_upload`, `allow_vote`, `is_vip`, `fee`, `days`, `description`) VALUES
(1, '超级管理员组', '#000000', '', -1, 0, 1, 1, 1, 1, 0, 0, 0, '拥有系统管理的最高权限用户组'),
(2, '管理员', '#111111', '', -1, 0, 1, 1, 1, 1, 0, 0, 0, '网站普通管理员组'),
(3, '游客组', '#666666', '', -1, 0, 0, 0, 0, 1, 0, 0, 0, '网站普通访客组'),
(4, '禁止访问', '#cccccc', '', -1, 0, 0, 0, 0, 0, 0, 0, 0, '禁止访问网站内容的用户组'),
(5, '禁止发言', '#444444', '', -1, 0, 0, 0, 0, 1, 0, 0, 0, '禁言用户(不能投稿、评论等)'),
(6, '待审核', '#555555', '', -1, 0, 0, 0, 0, 1, 0, 0, 0, '正在等待审核的新注册会员(系统设置是否开启)'),
(7, '网站编辑', '#222222', '', -1, 0, 1, 1, 1, 1, 0, 0, 0, '网站编辑(员工)'),
(88, '贵宾VIP', 'red', '', -1, 0, 1, 1, 1, 1, 1, 3000, 30, '超级VIP用户'),
(99, 'VIP', 'orange', '', -1, 0, 1, 1, 1, 1, 1, 2000, 30, 'VIP用户'),
(100, '初级会员', '', '', 0, 10, 0, 1, 0, 1, 0, 0, 0, '初始等级会员'),
(101, '中级会员', '', '', 0, 200, 0, 0, 0, 1, 0, 0, 0, '中等级会员'),
(102, '高级会员', '', '', 0, 1000, 1, 1, 1, 1, 0, 0, 0, '高等级会员');
COMMIT;
完成用户与用户组的初始化工作后,我们就可以进行下一步,开始使用Laravel操作数据库表,完成会员注册、登陆、退出等工作了。
目前我们只是建立了用户表users,表里的数据目前还是空空如也,所以第一步我们应该尝试注册用户。
待下面的控制器实现后,需要将所需要的所有控制器都导入到此包里哦~
use AppHttpControllersAuthRegisteredUserController; //用户注册
use AppHttpControllersAuthAuthenticatedSessionController; //登陆验证
use IlluminateSupportFacadesRoute; //路由
//路由经过guest中间件
Route::middleware('guest')->group(function () {
//GET 用户注册 视图
Route::get('register', [RegisteredUserController::class, 'create'])->name('register');
//POST接收注册信息
Route::post('register', [RegisteredUserController::class, 'store']);
//GET用户登陆视图
Route::get('login', [AuthenticatedSessionController::class, 'create'])->name('login');
//POST接收登陆信息
Route::post('login', [AuthenticatedSessionController::class, 'store']);
});
//路由经过 auth(验证后) 中间件
Route::middleware('auth')->group(function () {
//POST 用户退出
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout');
});
为了整理路由,我们可以将上面的路由使用一个单独的文件存放:routes/auth.php 然后在routes/web.php引入此路由文件即可:
require __DIR__ . '/auth.php';//验证用户登陆、注册、邮件
注册好路由后,理论上我们就可以通过路由访问每个请求了,目前还没有用户模型与控制器,当然会报相关的错误,所以我们接着先建立用户模型。
快速建立用户模型可以使用下面的命令:
php artisan make:model user #报错Model already exists!
这是因为,刚好Laravel内置了用户模型,所以我不并不需要使用命令再重复新建一次,由于我们的用户表结构与内置是不一样的。我们需要打开:app/Models/User.php 修改里面的内容,以匹配我们自己的用户表。
将原来的内容简单的修改如下:
<?php
namespace AppModels;
use IlluminateContractsAuthMustVerifyEmail;
use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateFoundationAuthUser as Authenticatable;
use IlluminateNotificationsNotifiable;
use LaravelSanctumHasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* 允许通过Model操作的字段
* @var array<int, string>
*/
protected $fillable = [
'username',
'email',
'password',
];
/**
* 序列化时(如:Json),需要隐藏的字段
* @var array<int, string>
*/
protected $hidden = [
'password',
];
}
有点小不同的地方,原来是name的地方,我数据表是username,所以在后面的调用中,所有的name都将改为username。
使用下面的命令批量新建所需要的控制器:
php artisan make:controller Auth/RegisteredUserController #用户注册相关控制器
php artisan make:controller Auth/AuthenticatedSessionController #用户登陆及验证控制器
但现在我们要修改的控制器是:app/Http/Controllers/Auth/RegisteredUserController.php,内容如下:
<?php
namespace AppHttpControllersAuth;
use AppHttpControllersController;
use IlluminateHttpRequest; //接收用户输入
use IlluminateValidationRules; //密码验证规则
use AppModelsUser; //用户模型
use IlluminateSupportFacadesHash; //密码加密
use IlluminateSupportFacadesAuth; //注册成功后自动登陆
use IlluminateAuthEventsRegistered; //事件注册
use AppProvidersRouteServiceProvider; //路由服务提供者:/home跳转
use IlluminateValidationRulesPassword; //密码验证规则
use IlluminateSupportStr; //字符串处理工具
class RegisteredUserController extends Controller
{
//显示注册界面的View
public function create()
{
return view('auth.register');
}
/**
* 处理传入的注册请求。
*
* @param IlluminateHttpRequest $request
* @return IlluminateHttpRedirectResponse
*
* @throws IlluminateValidationValidationException
*/
public function store(Request $request)
{
//请求数据验证
$request->validate([
'username' => ['required', 'string', 'min:5', 'max:64'], //必填,字符串,最小5,最大64
'email' => ['required', 'string', 'email', 'max:128', 'unique:users'], //必填,字符串,Email规则,最大128,users表不可重复
'password' => ['required', 'confirmed', Password::min(6)], //必填,2次确认,密码规则最少6位:https://learnku.com/docs/laravel/9.x/validation/12219#785748
]);
//处理待写入的数据
$user = User::create([
'username' => $request->username,
'email' => Str::lower($request->email),
'password' => Hash::make($request->password),
]);
event(new Registered($user)); //注册事件
Auth::login($user); //注册后自动登陆
return redirect(RouteServiceProvider::HOME); //跳转到注册成功后的页面
}
}
注册成功后,默认会跳转到:RouteServiceProvider::HOME,这个定义可以跳过去查看,我本地是/home所以我要需要为其注册一个路由方便控制:
Route::get('/home', function () {
return view('auth.home');
})->middleware(['auth'])->name('home');
逻辑代码编写完成后,我们需要写一下简单的注册页与登陆成功后的home页。
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>用户注册 - {{ config('app.name', '美食圈') }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<h1>{{ __('Register') }}</h1>
<div class="p-5">
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ route('register') }}">
@csrf
<div class="mb-3">
<label for="inputName" class="form-label">用户名 *</label>
<input type="text" class="form-control" id="inputName" name="username" required value="{{ old('username') }}">
<div id="usernameHelp" class="form-text">请输入您要注册用户名</div>
</div>
<div class="mb-3">
<label for="inputEmail" class="form-label">{{ __('Email') }}</label>
<input type="email" class="form-control" name="email" id="inputEmail" aria-describedby="emailHelp" value="{{ old('email') }}">
<div id="emailHelp" class="form-text">请输入您要注册使用的邮件地址</div>
</div>
<div class="mb-3">
<label for="inputPassword1" class="form-label">{{ __('Password') }} *</label>
<input type="password" class="form-control" name="password" id="inputPassword1" required autocomplete="new-password">
</div>
<div class="mb-3">
<label for="inputPassword2" class="form-label">{{ __('Confirm Password') }} *</label>
<input type="password" class="form-control" name="password_confirmation" required id="inputPassword2">
</div>
<button type="submit" class="btn btn-primary"> {{ __('Register') }}</button>
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>用户中心 - {{ config('app.name', '美食圈') }}</title>
</head>
<body>
<h1>用户中心</h1>
欢迎您:{{ Auth::user()->username }}
<form method="POST" action="{{ route('logout') }}">
@csrf
<a href="{{ route('logout') }}" onclick="event.preventDefault();this.closest('form').submit();">
{{ __('Log Out') }}
</a>
</form>
</body>
</html>
小提示,虽然我们使用闭包方式调用home模板,但依然可以使用Auth::user等方式,查询到当前用户的登陆信息。
上面我们注册后使用了一个自动登陆的方法,现在我们需要先退出登陆,然后再测试重新登陆。
在上面的路由规则中可以看到,我们需要的登陆、退出,使用的是app/Http/Controllers/Auth/AuthenticatedSessionController.php 控制器,所以下面是此控制器的所有内容:
<?php
namespace AppHttpControllersAuth;
use AppHttpControllersController;
use AppHttpRequestsAuthLoginRequest; //验证登陆信息
use IlluminateHttpRequest; //用户输入接收
use AppProvidersRouteServiceProvider; //登陆成功后的路由
use IlluminateSupportFacadesAuth; //退出验证
class AuthenticatedSessionController extends Controller
{
//显示登陆的View
public function create()
{
return view('auth.login');
}
//验证登陆信息
public function store(LoginRequest $request)
{
$request->authenticate(); //身份验证
$request->session()->regenerate(); //生成新的session。
return redirect()->intended(RouteServiceProvider::HOME); //跳转到登陆后的页面
}
//退出登陆
public function destroy(Request $request)
{
Auth::guard('web')->logout(); //登出系统
$request->session()->invalidate(); //刷新并重新生成sessionID
$request->session()->regenerateToken(); //重新生成CSRF令牌值。
return redirect('/'); //跳转到网站首页
}
}
控制器最后一项则是处理退出操作,最后将跳转到网站的首页。
当用户登陆时,我们需要对登陆信息验证,所以上面的控制器里还需要我们手工创建一个请求验证器:
php artisan make:request Auth/LoginRequest #新建一个登陆验证器
此命令会生成:app/Http/Requests/Auth/LoginRequest.php 文件,内容如下:
<?php
namespace AppHttpRequests;
use IlluminateFoundationHttpFormRequest; //继承表单验证
use IlluminateSupportFacadesAuth; //验证器
use IlluminateSupportFacadesRateLimiter; //限流器
use IlluminateSupportStr; //字符串处理器
use IlluminateValidationValidationException; //验证异常
use IlluminateAuthEventsLockout;
class AuthLoginRequest extends FormRequest
{
//确定用户是否被授权发出此请求。
public function authorize()
{
return true;
}
//表单请求的验证规则。
public function rules()
{
return [
'username' => ['required', 'string'],
'password' => ['required', 'string'],
];
}
//校验用户名密码
public function authenticate()
{
$this->ensureIsNotRateLimited(); //检测是否超过限制
if (!Auth::attempt($this->only('username', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey()); //总尝试次数限制:默认60
//抛出一个验证未成功的错误
throw ValidationException::withMessages([
'username' => trans('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey()); //清除限流
}
//检测登陆次数是否已超过了限制
public function ensureIsNotRateLimited()
{
//每分钟不能超过5次
if (!RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this)); //锁定事件
//返回在更多尝试可用之前剩余的秒数
$seconds = RateLimiter::availableIn($this->throttleKey());
//抛出错误信息
throw ValidationException::withMessages([
'username' => trans('auth.throttle', [
'seconds' => $seconds, //多少秒后可以再次尝试登陆
'minutes' => ceil($seconds / 60), //每分钟可以尝试多少次
]),
]);
}
//通过username与IP生成一个用于限流的Key
public function throttleKey()
{
return Str::lower($this->input('username')) . '|' . $this->ip();
}
}
现在程序逻辑部分的代码搞定了,下一步,我们将再新一个简单的登陆视图文件:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>用户登陆 - {{ config('app.name', '美食圈') }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<div class="p-5">
<h1>{{ __('Login') }}</h1>
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ route('login') }}">
@csrf
<div class="mb-3">
<label for="inputName" class="form-label">用户名 *</label>
<input type="text" class="form-control" id="inputName" name="username" required value="{{ old('username') }}">
<div id="usernameHelp" class="form-text">请输入您要登陆的用户名</div>
</div>
<div class="mb-3">
<label for="inputPassword1" class="form-label">{{ __('Password') }} *</label>
<input type="password" class="form-control" name="password" id="inputPassword1" required autocomplete="new-password">
</div>
<div class="mb-3">
<input type="checkbox" class="form-check-input" name="remember" id="remember_me">
<label class="form-check-label" for="remember_me">{{ __('Remember me') }}</label>
</div>
<button type="submit" class="btn btn-primary"> {{ __('Login') }}</button>
</form>
</div>
</body>
</html>
我希望用户每次登陆后,将自动更新数据表里的最后登陆时间、登陆IP等信息,在这里我们可以通过事件监听的方式处理:
php artisan make:event UserLogin #新建一个用户登陆事件
此命令会自动生成文件:app/Events/UserLogin.php。
我们需要接收事件参数并交给下面的监听者处理:
<?php
namespace AppEvents;
use IlluminateBroadcastingInteractsWithSockets;
use IlluminateFoundationEventsDispatchable;
use IlluminateQueueSerializesModels;
class UserLogin
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
//创建一个新的事件实例。
public function __construct($user)
{
$this->user = $user; //传参
}
}
下面我们还需要通过命令创建一个监听者并与事件绑定,一个事件可以由多少监听者绑定:
php artisan make:listener UpdateUserLoginInfo --event=UserLogin #创建一个监听者并与UserLogin绑定
此命令会自动生成文件:app/Listeners/UpdateUserLoginInfo.php,在代码里可以看到与UserLogin的绑定信息:
<?php
namespace AppListeners;
use AppEventsUserLogin; //登陆事件
use CarbonCarbon; //时间处理
class UpdateUserLoginInfo
{
//处理事件
public function handle(UserLogin $event)
{
//更新用户最后登陆时间与IP
try {
$user = $event->user;
$user->login_at = Carbon::now()->toDateTimeString();
$user->last_login_ip = request()->getClientIp();
$user->save();
} catch (Throwable $th) {
report($th);
}
}
}
以上完成后,我们需要在系统里注册事件,打开:app/Providers/EventServiceProvider.php 找到protected $listen 属性,在里面增加:
protected $listen = [
//注册用户登陆事件
UserLogin::class => [
UpdateUserLoginInfo::class, //可以存放多个监听者
],
];
注册好事件以后,我们需要去触发事件才可以,所以我们将在:app/Http/Controllers/Auth/AuthenticatedSessionController.php 的 store 方法里去触发登陆事件,在跳转前增加下面的代码:
event(new UserLogin(Auth::user())); //登陆事件
通过事件处理用户更新登陆时间与IP的方式是Laravel优雅的实现。到此,我们实现了基本的用户登陆、注册等功能。
除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:https://www.55mx.com/post/213
《【Laravel实战】2、用户登陆、注册及相关事件处理》的网友评论(0)