您将要创造的

本教程是 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中,它们是通过以下方式应用的:

<?phpNavBar::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',],

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


但是封面图像呢? 我为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.io或MeetingPlanner.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的域设置(如下所示):


OAuth登录

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

这是Google:


这是简单的LinkedIn:


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


Mailgun电子邮件

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


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


SSL协议

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

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

<VirtualHost *:80>ServerName simpleplanner.ioServerAlias 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.ioServerAlias 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数据库迁移:

<?phpuse 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最快。

下一步是什么?


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

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

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

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

相关链接

  • 简单计划者或会议计划者
  • 会议计划者的WeFunder页面
  • 使用Yii2编程:入门

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

建立您的启动:运行多个域相关推荐

  1. 老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用 1...

    老李推荐:第5章5节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 获取系统服务引用 上一节我们描述了monkey的命令处理入口函数run是如何调用optionPro ...

  2. 老李推荐: 第8章4节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动AndroidDebugBridge 1...

    老李推荐: 第8章4节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-启动AndroidDebugBridge 上一节我们看到在启动AndroidDebugBri ...

  3. “今朝安全众测平台”正式启动运行

    2021年5月19日,由中国信息产业商会指导,中国信息安全测评中心组织运营的"今朝安全众测平台"正式启动运行,提供网络安全众测服务. "今朝安全众测平台"启动运 ...

  4. 【内网安全-CS】Cobalt Strike启动运行上线方法插件

    前言: 介绍: 博主:网络安全领域狂热爱好者(承诺在CSDN永久无偿分享文章). 殊荣:CSDN网络安全领域优质创作者,2022年双十一业务安全保卫战-某厂第一名,某厂特邀数字业务安全研究员,edus ...

  5. 【架设KMS服务器流程建立服务项目启动】

    架设KMS服务器流程 建立服务项目启动 架设KMS服务器流程--建立服务项目启动 以下操作 root 一.准备Centos服务器CentOS-8.4.2105 1.下载http://mirrors.1 ...

  6. linux启动运行级别上机,linux的启动及其运行级别

    一.启动过程 1.过程:开机→加载BIOS,硬件自检,取得第一个开机装置的代号→读取MBR中的启动引导程序(如grub,lilo)→启动linux内核→由内核运行init进程,根据init配置文件进入 ...

  7. 安装RabbitMq启动运行出现服务无法启动 发生系统错误1067解决方案

    安装RabbitMq启动运行出现服务无法启动 发生系统错误1067解决方案 参考文章: (1)安装RabbitMq启动运行出现服务无法启动 发生系统错误1067解决方案 (2)https://www. ...

  8. 老李推荐:第8章2节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-解析处理命令行参数...

    老李推荐:第8章2节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-解析处理命令行参数 MonkeyRunnerStarter是MonkeyRunner启动时的入 ...

  9. 计算机启动突然断电,电脑启动运行过程主机突然断电怎么办

    有朋友跟小编说"我的电脑开机经常突然断电,断电以后就不能启动了,感觉就不通电了 ",那么电脑启动运行过程主机突然断电怎么办呢?小编为大家分享了解决电脑启动运行过程主机突然断电的方法 ...

最新文章

  1. Duilib开发环境搭建
  2. 非maven项目转成maven后pom.xml解决方法
  3. 树状数组入门(有被精简的树状数组所震撼到)
  4. android 最新功能介绍,Android Studio 常用功能介绍
  5. Google glass GDK - 通过MP3路径获取专辑图片
  6. redis java驱动_java中通过配置文件的方式(Jedis驱动)使用Redis
  7. 分享磨砺营威哥讲解-Android开发过程中内存优化的几点总结
  8. 三菱modbusRTU通讯实例_编程实例 | 台达PLC控制伺服项目接线及程序案例
  9. 互联网进入网盘新时代
  10. 未来的计算机博士就业前景_恐怖博士:电视的未来
  11. 标题python自动化测试培训-UnitTest/PyUnit的用法介绍
  12. 移动硬盘3.5寸和2.5寸的区别
  13. 基于单片机的温湿度监测系统设计(程序)
  14. L2-039 清点代码库 (25 分)(哈希)
  15. vue3+Echart
  16. 软件开发就像歌曲制作,我的岗位相当于乐器伴奏
  17. ssl 客户端无法显示证书
  18. unity之VR模拟消防安全隐患排查综合方案(家庭/校园/商场/地铁/工厂/办公室)
  19. 小冬冬历险记_行为驱动发展历险记
  20. 禁用计算机系统错误汇报,如何关闭电脑发送错误报告的弹窗

热门文章

  1. pt100阻值温度c语言,pt100温度传感器阻值,pt100温度与阻值对照表
  2. 快乐数happy-number
  3. 北京工业大学计算机组成,GitHub - WuSiYu/mips-proj5: 5级流水线MIPS-lite微系统(北工大计组课设)...
  4. Nett源码剖析注册通道2021SC@SDUSC
  5. 千峰教学视频(官方)
  6. dinic 最大流费用流模板
  7. 专 业 学 习 成 果
  8. 假如生活欺骗了你-普希金
  9. C# FileStream和StreamWriter用法
  10. 安装Proteus8.9后出现找不到module或者default文件等问题以及运行仿真后报错显示,例如:Cannot find model file ‘APDS9002A.MDF‘.