Laravel 5 SaaS单应用多数据库场景下的软链接配置求助
我来帮你解决这个Laravel SaaS的子域名与.env隔离问题,你之前的核心问题在于直接把整个主应用软链到客户目录,导致.env变成了共享的软链,自然修改会同步到主应用。下面是具体的解决方案:
核心思路
让每个客户目录只保留独立的.env文件,其余所有应用文件都通过软链指向主应用,但绝对不能将主应用的.env也软链过来,确保每个客户拥有专属的环境配置文件。
具体步骤
1. 创建客户目录与独立.env文件
首先为客户A创建专属目录,然后基于主应用的模板生成独立的.env文件:
# 创建客户目录 mkdir -p /var/www/clients/clientA # 从主应用复制.env模板并初始化客户配置 cp /var/www/main/.env.example /var/www/clients/clientA/.env # 编辑客户A的.env,配置专属数据库、APP_URL等信息 nano /var/www/clients/clientA/.env
2. 软链主应用文件(排除.env)
进入客户目录,将主应用的所有文件/目录逐个软链过来,但跳过主应用的.env:
cd /var/www/clients/clientA # 遍历主应用的所有文件/目录,软链到客户目录 for item in /var/www/main/*; do # 获取文件/目录名称 item_name=$(basename "$item") # 跳过主应用的.env文件 if [ "$item_name" != ".env" ]; then ln -s "$item" "./$item_name" fi done
这样客户目录下的.env是完全独立的,其他文件(比如app/、public/、artisan等)都是指向主应用的软链,修改客户的.env不会对主应用造成任何影响。
3. 自动化批量创建客户(可选)
如果需要创建大量客户,手动操作太繁琐,可以写一个shell脚本create_client.sh来一键生成:
#!/bin/bash # 检查是否传入客户名称参数 if [ -z "$1" ]; then echo "请传入客户名称,例如:./create_client.sh clientB" exit 1 fi CLIENT_NAME=$1 MAIN_APP="/var/www/main" CLIENT_DIR="/var/www/clients/$CLIENT_NAME" # 创建客户目录 mkdir -p "$CLIENT_DIR" # 复制.env模板并自动替换关键配置 cp "$MAIN_APP/.env.example" "$CLIENT_DIR/.env" sed -i "s/DB_DATABASE=laravel/DB_DATABASE=client_$CLIENT_NAME/g" "$CLIENT_DIR/.env" sed -i "s/APP_URL=http:\/\/localhost/APP_URL=http:\/\/$CLIENT_NAME.application.com/g" "$CLIENT_DIR/.env" # 软链主应用文件 for item in "$MAIN_APP"/*; do item_name=$(basename "$item") if [ "$item_name" != ".env" ]; then ln -s "$item" "$CLIENT_DIR/$item_name" fi done # 设置web服务器权限(适配www-data用户) chown -R www-data:www-data "$CLIENT_DIR" chmod -R 755 "$CLIENT_DIR/storage" "$CLIENT_DIR/bootstrap/cache" echo "客户$CLIENT_NAME创建完成!"
给脚本添加执行权限后,即可一键创建客户:
chmod +x create_client.sh ./create_client.sh clientB
4. 配置Nginx子域名指向客户目录
接下来配置Nginx,让clientA.application.com指向客户目录的public文件夹(该public是软链到主应用的public目录):
server { listen 80; server_name clientA.application.com; root /var/www/clients/clientA/public; # 安全头部配置 add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; index index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } # 静态文件优化 location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } error_page 404 /index.php; location ~ \.php$ { fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # 根据你的PHP版本调整 fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; # 确保Laravel以客户目录为基础路径 fastcgi_param APP_BASE_PATH /var/www/clients/clientA; } # 禁止访问隐藏文件(well-known目录除外) location ~ /\.(?!well-known).* { deny all; } }
配置完成后重启Nginx:
sudo systemctl restart nginx
5. 验证配置有效性
你可以在主应用中添加一个测试路由,验证Laravel是否加载了正确的.env:
// routes/web.php Route::get('/env-test', function () { return [ 'APP_NAME' => env('APP_NAME'), 'DB_DATABASE' => env('DB_DATABASE'), 'Base Path' => base_path() ]; });
访问clientA.application.com/env-test,就能看到客户A的专属配置信息,确认.env加载正常。
注意事项
- 依赖更新:主应用执行
composer install或composer update后,所有客户的vendor目录(软链到主应用)会自动同步更新,无需单独操作。 - 配置缓存:不要在主应用执行
php artisan config:cache,因为缓存会绑定主应用的.env配置。如果需要缓存,要在每个客户目录下单独执行:cd /var/www/clients/clientA && php artisan config:cache。 - Storage目录(可选):如果需要每个客户独立的存储目录,不要软链主应用的
storage,而是在客户目录下创建独立的storage,然后在bootstrap/app.php中修改Laravel的存储路径(需修改主应用代码,根据需求决定)。
内容的提问来源于stack exchange,提问作者Michal
相关产品推荐
相关产品推荐

