laravel前台和Dcat-admin后台,实现saas的简易方式
2021-07-02 admin laravel 2030
原来使用了stancl/tenancy插件,感觉功能太多,文档过于简单,和dcat admin结合时,比较难实现数据隔离。本文使用laravel自身的路由功能来实现saas的基本功能。
实现原理:在laravel前台和后台路由入口,区分主域名和子域名,然后通过config来切换url、数据库和存储位置,实现基本的隔离。
一、在config/database.php文件中添加一个备用数据库
'mysql_extend' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
二、前台路由,在app/Providers/RouteServiceProvider.php中修改boot方法,添加:
//前台路由,区分主站和子站,并读取主站users表,判断子站是否存在
if(isset($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'] !== 'yzm.dzbfsj.com'){
$url_first = explode('.',$_SERVER['HTTP_HOST'])[0];
config(['app.url' => $url_first.'.yzm.dzbfsj.com']);//全局替换网址
config(['database.connections.mysql.database' => 'db_'.$url_first]);
config(['database.connections.mysql_extend.database' => 'yzm_dzbfsj_com']);//连接主数据库,用于查询网址是否存在
if(DB::connection('mysql_extend')->table('users')->where('name',$url_first)->doesntExist()){
exit('站点不存在,请从主站登录:<a href="https://yzm.dzbfsj.com">点击进入</a>');
}
}
三、后台路由,在app/Admin/routes.php添加:
use Illuminate\Support\Facades\DB;
//后台路由,区分主站和子站,并读取主站users表,判断子站是否存在
if(isset($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'] !== 'yzm.dzbfsj.com'){
$url_first = explode('.',$_SERVER['HTTP_HOST'])[0];
config(['app.url' => $url_first.'.yzm.dzbfsj.com']);//全局替换网址
config(['database.connections.mysql.database' => 'db_'.$url_first]);
config(['database.connections.mysql_extend.database' => 'yzm_dzbfsj_com']);//连接主数据库,用于查询网址是否存在
if(DB::connection('mysql_extend')->table('users')->where('name',$url_first)->doesntExist()){
exit('站点不存在,请从主站登录:<a href="https://yzm.dzbfsj.com">点击进入</a>');
}
}
这样,前后台都能区分主站和子站了,并动态的修改了数据库和url,如果要修改存储位置,也可通过config来修改。
四、主站后台租户管理,可以使用主站的Users表,创建一个通过用户管理,关键代码:
$form->hidden('tenant');
//name是唯一的,对应三级域名,如www;tenant对应是的数据库名,格式为db_name
$form->saving(function (Form $form) {
if ($form->isCreating()) {
$password = $form->password ?? '123456';
$form->password = Hash::make($password);
$form->email_verified_at = now();//默认激活
$db_name = 'db_'.$form->name;
$form->tenant = $db_name;
//创建数据库并导入根目录db.sql
$charset = config('database.connections.mysql.charset');
$collation = config('database.connections.mysql.collation');
$path = database_path('/data.sql');
if(DB::statement("CREATE DATABASE `{$db_name}` CHARACTER SET `$charset` COLLATE `$collation`") && file_exists($path)){
config(['database.connections.mysql_extend.database' => $db_name]);//切换数据库
DB::connection('mysql_extend')->unprepared(file_get_contents($path));
}
}else{
if(!$form->password){
$form->deleteInput('password');//没有输入新密码,不保存
}else{
$form->password = Hash::make($form->password);
}
}
});
$form->deleting(function (Form $form){
$dsc = $form->model()->toArray();//循环删除租户数据库
foreach($dsc as $val){
if($db_name = $val['tenant']){
if(DB::select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$db_name'")){
DB::statement("DROP DATABASE `{$db_name}`");
}
}
}
});
上面充分利用了laravel和数据库切换功能,实现了租户的创建和删除(仅以myql为例,如果是其它数据库,可参照修改)。
五、添加三级域名泛解析
在阿里云域名管理处,添加泛解析:*.yzm解析到服务器ip,然后在宝塔面板,添加主域名和*.主域名,部署ssl证书即可。
六、前台路由
if(isset($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'] == 'yzm.dzbfsj.com'){
//主路由
Auth::routes(['verify' => true]);;
Route::middleware(['verified'])->group(function () {
Route::get('/', [HomeController::class, 'index'])->name('index');
Route::get('/home', [HomeController::class, 'index'])->name('home');
Route::get('/scsj', [HomeController::class, 'scsj'])->name('scsj');
});
}else{
//子路由
Route::domain('{account}.yzm.dzbfsj.com')->group(function () {
Route::get('/', function ($account) {
return redirect('/admin');
});
});
}
经测试前台和后台,功能完全正常。
走过的弯路:
路由入口判断租户是否存在,需要用config切换主副数据库(主副调换)后,通过DB::connection('mysql_extend')的方式读取主站users表来判断。不可直接用User Model,否则读取的是子站的数据库users表;也不可在切换前通过DB来读取,因为config优先执行,放在切换前读取,实际读取的也是切换后的子站数据库。
后期将用来整合第三方登录和支付,以及功能模板的灵活调用。