建立您的启动:运行多个域

最终产品图片
您将要创造的

本教程是 Envato Tuts +上的“ 使用PHP构建启动”系列 的一部分。 在本系列文章中,我 将以 我的 Meeting Planner 应用程序作为真实示例 ,指导您完成从概念到现实的启动 在此过程的每一步中,我都会将Meeting Planner代码作为开放源代码示例发布,您可以从中学习。 我还将解决与启动相关的业务问题。

启动第二个Web域

现在,扩大会议策划者的意识和使用是我最大的挑战。 没有大量的投入,就很难使产品变得更好,而没有快速的增长,就很难吸引投资者。

我一直担心,Meeting Planner这个品牌可能会导致人们误解该应用程序的社交用途,例如计划友好的聚会,约会和聚会。

显然,域名的选择和/或购买替代品需要投资的预算限制了名称的选择。 当时会议规划师似乎最好。

最近,我注意到SimplePlanner.io可用,因此我进行了注册,并开始将域与现有的Meeting Planner服务集成在一起。

有两种不同的方法可将域添加到基于Yii2的应用程序中。 在今天的教程中,我将介绍同一代码库上最简单的运行域。

如果您还没有尝试过Meeting Planner,请继续并在Simple Planner安排您的第一次会议 。 我确实参与了下面的评论主题,所以请告诉我您的想法! 您也可以通过Twitter @reifman与联系 。 如果您想为以后的教程提出新功能或主题,我特别感兴趣。

提醒一下,Meeting Planner和Simple Planner的所有代码都是在PHP的Yii2框架中编写的。 如果您想了解有关Yii2的更多信息,请查看我们的平行系列“ 使用Yii2编程”

在开始创业之前,我想谈谈建立创业公司所面临的挑战的一个方面。

初创企业生活

上周末,我一直在努力制作此剧集,它是#StartupLife的“特权”。 我想分享一些有趣的事情。

首先,#StartupLife通常意味着没有时间完成组装宜家家具,只是最大的抽屉:

建立您的启动多个域-我的宜家梳妆台的照片,其中一个内置抽屉,另一个没有内置抽屉

其次,这也意味着要在有趣的地方的咖啡店里工作。俄勒冈州波特兰市的五点烘烤机恰好有一台相邻的泡泡机(或者人们在那里呼吸泡泡):

现在,回到今天的教学重点...

在Yii中实现多个域

Yii2高级模板允许您在一个代码树中运行多个站点。 我用它的前端树来构建Meeting Planner,用它的后端树来构建服务的管理工具套件 。 但是,今天,我将集中精力在现有前端树的顶部启动另一个域,以及与此相关的所有大小复杂性。

在以后的文章中,我将介绍在第三棵或第四棵树上构建站点的方法,例如REST API或与Meeting Planner相关的启动程序(是的,前面有一个令人兴奋的想法)。

我以为启动Simple Planner相当简单(没有双关语),但是最终却花费了几天的时间。

简单的工作是基本的服务器配置以及使用Yii树进行的一些代码处理。 但是,由于Meeting Planner是一项电子邮件密集型服务(会议邀请,确认,公告,更新等),因此,使用创建会议的域(SimplePlanner.io或MeetingPlanner.io)发送这些电子邮件非常重要。

我还希望两个站点之间在视觉上有所区别。

让我们开始吧,我将逐步揭示我遇到的一些复杂性。

基于域地址的配置

当人们通过浏览器请求到达我们的站点时,我们需要使用Yii的集中方式来指示所有已执行的代码应显示哪些服务:会议计划程序或简单计划程序?

在/frontend/config/main.php中,不幸地有一个名为bootstrap的配置(因为它与Bootstrap重叠),它将在框架调用开始时运行代码:

<?php
$config = parse_ini_file('/var/secure/mp.ini', true);

$params = array_merge(
    require(__DIR__ . '/../../common/config/params.php'),
    require(__DIR__ . '/../../common/config/params-local.php'),
    require(__DIR__ . '/params.php'),
    require(__DIR__ . '/params-local.php')
);
return [
    'id' => 'mp-frontend',
    'name' => 'Meeting Planner',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log','\common\components\SiteHelper'],
    'controllerNamespace' => 'frontend\controllers',

除了调用上面的日志记录之外 ,我还为其创建了一个名为SiteHelper

因此,SiteHelper具有所有代码,可以根据正在运行的站点来自定义服务。 例如:

private function commonMeetingPlanner() {
      Yii::$app->params['site']['id'] = SiteHelper::SITE_MP;
      Yii::$app->params['site']['domain'] = 'meetingplanner.io';
      Yii::$app->params['site']['url'] = 'https://meetingplanner.io';
      Yii::$app->params['site']['title'] = Yii::t('frontend', 'Meeting Planner');
      Yii::$app->params['site']['mtg'] = Yii::t('frontend', 'Meetings');
      Yii::$app->params['site']['img'] = rand(2,3);
      Yii::$app->params['site']['navbar'] = 'navbar-inverse';
      Yii::$app->params['site']['email_logo'] = 'https://meetingplanner.io/img/email-logo-mp.gif';
      Yii::$app->params['site']['ga'] = 'UA-37244292-18';
    }

    private function commonSimplePlanner() {
      Yii::$app->params['site']['id'] = SiteHelper::SITE_SP;
      Yii::$app->params['site']['domain'] = 'simpleplanner.io';
      Yii::$app->params['site']['url'] = 'https://simpleplanner.io';
      Yii::$app->params['site']['title'] = Yii::t('frontend', 'Simple Planner');
      Yii::$app->params['site']['mtg'] = Yii::t('frontend', 'Meetups');
      Yii::$app->params['site']['img'] = rand(0,1);
      Yii::$app->params['site']['navbar'] = 'navbar-default';
      Yii::$app->params['site']['email_logo'] = 'https://simpleplanner.io/img/email-logo-sp.gif';
      Yii::$app->params['site']['ga'] = 'UA-37244292-21';
    }

这些函数根据请求的服务更改变量,URL和字符串。 这些由SiteHelper::init()函数调用:

<?php
namespace common\components;
use yii;
use yii\helpers\Url;
class SiteHelper extends \yii\base\Component{
  const SITE_MP = 0;
  const SITE_SP = 1;
  const SITE_FD = 2;

    public function init() {
      $baseUrl = Url::home(true);
      if (stristr($baseUrl,'/mp/')!==false) {
        // local mp
        $this->commonMeetingPlanner();
        Yii::$app->params['site']['url'] = 'http://localhost:8888/mp/';
        Yii::$app->params['site']['ga'] = '';
      } else if (stristr($baseUrl,'/sp/')!==false) {
        // local sp
        $this->commonSimplePlanner();
        Yii::$app->params['site']['url'] = 'http://localhost:8888/sp/';
        Yii::$app->params['site']['ga'] = '';
      } else if (stristr($baseUrl,'simple')!==false) {
        // simpleplanner.io
        $this->commonSimplePlanner();
      } else {
        // default meetingplanner.io
        $this->commonMeetingPlanner();
      }
      parent::init();
    }

localhost:8888开发站点进行调用时,上述功能还将覆盖设置。

Meeting Planner是SITE_MP ,Simple Planner是SITE_SPSITE_FD是我的秘密(目前)。

独特的首页外观

现在,我决定通过使用Bootstrap 3.0的两个默认导航栏来快速更改Simple Planner(SP)和Meeting Planner(MP)的外观。

您会在上面的MP中使用Yii::$app->params['site']['navbar'] = 'navbar-default'; SP使用'navbar-inverse';

在/frontend/views/layouts/home.php和main.php中,它们是通过以下方式应用的:

<?php
    NavBar::begin([
    'brandLabel' =>  Yii::$app->params['site']['title'].'&nbsp;<span class="badge">preview</span>', //
    'brandUrl' => Yii::$app->homeUrl,
    'options' => [
        'class' => Yii::$app->params['site']['navbar'].' navbar-fixed-top',
    ],

这将创建两种不同的导航栏配色方案:

建立您的启动多个域-并排查看Simple Planner vs Meeting Planner主页

但是封面图像呢? 我为SP许可了两个有趣的社交图像,为MP许可了两个更严肃的专业图像。

图像会根据激活的服务随机旋转,图像文件名的数字结尾为0、1、2或3,例如/img/home/home-#.jpg。

Yii::$app->params['site']['img'] = rand(2,3);

这是将home.php布局代码应用于所选图像的代码:

<body>
    <?php $this->beginBody() ?>
     <div class="row">
      <div class="col-md-12 bgimage"
        style="background-image:
        url('<?= $urlPrefix ?>/img/home/home-<?= Yii::$app->params['site']['img'] ?>.jpg');">
       <div class="bgimage-inside"></div>

如果刷新SimplePlanner.ioMeetingPlanner.io的主页,则会看到图像振荡。

更新文本,图像和链接

上面SiteHelper中的变量可帮助自定义整个网站的文本标签。 将来,我可以更广泛地做到这一点:

Yii::$app->params['site']['title'] = Yii::t('frontend', 'Meeting Planner');
Yii::$app->params['site']['mtg'] = Yii::t('frontend', 'Meetings');

当MP称为会议时,我可以在全球范围内更改SP以使用更具社交性的Meetups:

Yii::$app->params['site']['title'] = Yii::t('frontend', 'Simple Planner');
Yii::$app->params['site']['mtg'] = Yii::t('frontend', 'Meetups');

配置服务

Meeting Planner使用许多不同的服务来进行计划。 初始化这些耗时最多。

Google Analytics(分析)和Search Console

Google使得在Analytics(分析)中使用多个域变得有些困难,因此我将它们划分为不同的帐户。 SiteHelper设置GA代码:

Yii::$app->params['site']['ga'] = 'UA-37244292-18';

然后,在Home.php和Main.php布局视图中进行设置:

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
  ga('create', '<?php echo Yii::$app->params['site']['ga']; ?>', 'auto');
  ga('send', 'pageview');
</script>

我在注册商处配置了Google Search Console的域设置(如下所示):

建立您的启动多个域-Google Search Console验证TXT记录
OAuth登录

并且,由于我通过OAuth添加了针对Facebook,Google和LinkedIn的社交登录 ,因此我不得不告诉所有这些服务有关SimplerPlanner.io域的信息。 Google和LinkedIn最令人困惑,因为每个查询参数变体都必须向这些天才注册:

这是Google:

建立您的启动多个域-Google Developer凭据URL

这是简单的LinkedIn:

建立您的启动多个域-LinkedIn OAuth配置URL

但是Facebook是最简单且挑剔最少的工具(只需将母乳喂养的图片保持在最低限度 ):

建立您的启动多个域-Facebook开发人员配置URL
Mailgun电子邮件

由于我使用Mailgun传递电子邮件 ,因此我使用它们为Simple Planner创建了一个域帐户:

建立您的启动多个域-Mailgun的域配置要求

这需要在我的域名注册商处进行仔细的更改:

建立您的启动多个域-Mailgun的域注册器DNS设置
SSL协议

我将Meeting Planner及其后端管理服务用于“ 加密 ” https(SSL)。 设置这些对我来说非常顺利。 但是添加SimplePlanner.io是行不通的,最终我不得不为SP创建单独的Apache(.conf)配置文件并自己定制文件。

这是重定向到https的http sp.conf文件:

<VirtualHost *:80>
 ServerName simpleplanner.io
 ServerAlias www.simpleplanner.io
DocumentRoot "/var/www/mp/frontend/web"
<Directory "/var/www/mp/frontend/web">
        AllowOverride All
</Directory>
RewriteEngine on
RewriteCond %{SERVER_NAME} =simpleplanner.io [OR]
RewriteCond %{SERVER_NAME} =www.simpleplanner.io
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
</VirtualHost>

重定向仅不适用于两个服务器名称和两个服务器别名。 这是sp-ssl.conf文件:

<IfModule mod_ssl.c>
<VirtualHost *:443>
 ServerName simpleplanner.io
 ServerAlias www.simpleplanner.io
DocumentRoot "/var/www/mp/frontend/web"
<Directory "/var/www/mp/frontend/web">
        AllowOverride All
</Directory>
RewriteEngine On
RewriteCond %{SERVER_NAME} =www.simpleplanner.io
RewriteRule ^ https://simpleplanner.io%{REQUEST_URI} [END,QSA,R=permanent]
SSLCertificateFile /etc/letsencrypt/live/simpleplanner.io/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/simpleplanner.io/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateChainFile /etc/letsencrypt/live/simpleplanner.io/chain.pem
</VirtualHost>
</IfModule>

这是我最终通过“加密”创建SSL证书的方式:

cd /opt/letsencrypt/
sudo ./letsencrypt-auto --apache
 -d simpleplanner.io -d www.simpleplanner.io -d

让我们加密真棒,但他们还很年轻,尽管他们的脚本不断变得更强大,但偶尔还是会遇到类似这样的问题。

后台电子邮件区分

对我来说,启动Simple Planner所面临的最大挑战是处理后台电子邮件,以便它们来自适当的服务(例如MP或SP),并且它们的所有链接也是如此。

由于我将数据库统一用于SP和MP,因此我也将后台处理留在MP域上进行。 因此,我必须扩展数据库,以便“用户和会议”表保存一个site_id列,以指示创建每个条目的服务。

这是Yii数据库迁移:

<?php
  use yii\db\Schema;
  use yii\db\Migration;
  use frontend\models\Meeting;
  class m161016_204028_extend_user_and_meeting_for_simple extends Migration
  {
    public function up()
    {
      $tableOptions = null;
      if ($this->db->driverName === 'mysql') {
          $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
      }
      $this->addColumn('{{%meeting}}','site_id',Schema::TYPE_SMALLINT.' NOT NULL DEFAULT 0');
      $this->addColumn('{{%user}}','site_id',Schema::TYPE_SMALLINT.' NOT NULL DEFAULT 0');
    }

    public function down()
    {
      $this->dropColumn('{{%meeting}}','site_id');
      $this->dropColumn('{{%user}}','site_id');
    }
  }

我在Meeting模型中使用了beforeSave()方法来始终基于当前服务配置site_id您可能还记得我创建了此方法来始终为每个会议生成一个安全的唯一标识符

public function beforeSave($insert)
{
    if (parent::beforeSave($insert)) {
      if ($insert) {
        $this->identifier = Yii::$app->security->generateRandomString(8);
        $this->site_id = Yii::$app->params['site']['id'];
      }
    }
    return true;
}

我为用户模型创建了一个类似的模型:

public function beforeSave($insert)
{
    if (parent::beforeSave($insert)) {
      if ($insert) {
        $this->site_id = Yii::$app->params['site']['id'];
      }
    }
    return true;
}

在整个服务中,我使用MiscHelpers::buildCommand()方法来构造安全链接,这些链接不需要用户在每次回复电子邮件时都进行登录。

我认为在一个地方自定义此命令以链接到适当的站点域会很容易。 但是,这将要求此常用方法反复在会议表中查询site_id 。 例如,每次会议的每个参与者都会多次调用buildCommand()

出于性能原因,我决定将对此函数的所有调用更改为包括site_id 。 例如,所有这些调用都必须进行如下修改:

foreach ($this->participants as $p) {
    if ($p->status !=Participant::STATUS_DEFAULT) {
      continue;
    }
    // Build the absolute links to the meeting and commands
    $auth_key=\common\models\User::find()->where(['id'=>$p->participant_id])->one()->auth_key;
    $links=[
      'home'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_HOME,0,$p->participant_id,$auth_key,$this->site_id),
      'view'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_VIEW,0,$p->participant_id,$auth_key,$this->site_id),
      'finalize'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_FINALIZE,0,$p->participant_id,$auth_key,$this->site_id),
      'cancel'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_CANCEL,0,$p->participant_id,$auth_key,$this->site_id),
      'decline'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_DECLINE,0,$p->participant_id,$auth_key,$this->site_id),
      'acceptall'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ACCEPT_ALL,0,$p->participant_id,$auth_key,$this->site_id),
      'acceptplaces'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ACCEPT_ALL_PLACES,0,$p->participant_id,$auth_key,$this->site_id),
      'accepttimes'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ACCEPT_ALL_TIMES,0,$p->participant_id,$auth_key,$this->site_id),
      'addplace'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ADD_PLACE,0,$p->participant_id,$auth_key,$this->site_id),
      'addtime'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ADD_TIME,0,$p->participant_id,$auth_key,$this->site_id),
      'addnote'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ADD_NOTE,0,$p->participant_id,$auth_key,$this->site_id),
      'footer_email'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_FOOTER_EMAIL,0,$p->participant_id,$auth_key,$this->site_id),
      'footer_block'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_FOOTER_BLOCK,$this->owner_id,$p->participant_id,$auth_key,$this->site_id),
      'footer_block_all'=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_FOOTER_BLOCK_ALL,0,$p->participant_id,$auth_key,$this->site_id),
    ];

会议模型总是在这些调用之前加载,因此从这里访问site_id最快。

下一步是什么?

建立您的启动多个域-具有社交图像的SimplePlannerio主页

尽管此站点的所有配置和微小的变量更改在一段时间后变得有些烦人,但它展示了Yii Framework的一些非常酷的功能。 如果尚未开始,请尝试在新站点Simple Planner上进行计划

与您的业务伙伴共享会议计划者,与您的朋友和家人共享简单计划者。

有自己的想法吗? 有想法吗? 反馈? 您随时可以直接通过Twitter @reifman与联系 。 在“ 使用PHP构建您的启动”系列中观看即将发布的教程。

基于SEC新的众筹规则的实施,我也越来越接近与WeFunder展开实验。 您可以根据需要在此处关注我们的个人资料 。 我还将在以后的教程中写更多有关此的内容。

相关链接

翻译自: https://code.tutsplus.com/tutorials/building-your-startup-running-multiple-domains--cms-27459

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值