Yii2 + webserver nginxでWebアプリの角度+マテリアルとRESTを書いたように

プロジェクト自体の背景から始めます。 この考えは偶然に思い浮かびました-私は明らかに、自分のプロジェクトに取り組むことに対する追加の責任を負いませんでした。 そこで私は、評判とお金を公に危険にさらすことで、自分の動機を刺激できるポータルを作成することにしました。



さて、今私は仕事に取りかかります。 トピックは広範ですが、出口で全体像を伝え、プロジェクトが作成される前に浮上したすべての落とし穴を思い出すことができることを願っています。 アプリケーションを角度で記述したい人を助けるために使用したすべての主要な情報源を示します。 はい、実際、誰もが一連の記事でこのトピックに関する質問のほとんどに対する回答を見つけることができます。



私は長い間、ある種の戦闘プロジェクトでmaterial.angularjs.orgをテストするというアイデアを大事にしてきました。 その後、アイデアが浮上しました...すべてが非常にシンプルに見えます-既製のコンポーネントのセット=高速開発、バックエンドのYiiに精通して...サーバー。 ことわざにあるように、おっと...



すべてはnginxの設定から始まりました。 特定のRESTロケーションを除くすべてのリクエストは、index.htmlにリダイレクトする必要があり、angularが機能し始めました。 最初の構成は次のようになりました。



server { charset utf-8; listen 80; server_name truemania.ru; root /path/to/root; access_log /path/to/root/log/access.log; error_log /path/to/root/log/error.log; location / { # Angular app conf root /path/to/root/frontend/web; try_files $uri $uri/ /index.html =404; } location ~* \.php$ { include fastcgi_params; #fastcgi_pass 127.0.0.1:9000; fastcgi_pass unix:/var/run/php5-fpm.sock; try_files $uri =404; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } # avoid processing of calls to non-existing static files by Yii (uncomment if necessary) location ~* \.(css|js|jpg|jpeg|png|gif|bmp|ico|mov|swf|pdf|zip|rar)$ { try_files $uri =404; } location ~* \.(htaccess|htpasswd|svn|git) { deny all; } location /api-location { client_max_body_size 2000M; alias /path/to/root/frontend/web; try_files $uri /frontend/web/index.php?$args; location ~* ^/api-location/(.+\.php)$ { try_files $uri /frontend/web/$1?$args; } } }
      
      





ここでは、すべてのAPIはlocation / api-locationにあります。 角度$ routeProvider構成:



 app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) { $routeProvider. when('/route1', { templateUrl: '/views/route1.html', controller: 'route1Ctrl' }). when('/route2', { templateUrl: '/views/route2.html', controller: 'route2Ctrl' }). when('/route3', { templateUrl: '/views/route3.html', controller: 'route3Ctrl' }). otherwise({ redirectTo: '/route1' }); // use the HTML5 History API $locationProvider.html5Mode({ enabled: true, requireBase: false }); }]);
      
      





しかし、角度のあるサイトはどのようにインデックス付けされますか? 私はすぐに、静力学を別々に与えるべきだと思いました。 少しグーグル、情報が見つかりましたか?_Escaped_fragment。 静的を個別に生成し、次のようなリクエストに与える必要がありました truemania.ru/?_escaped_fragment



truemania.ru/?_escaped_fragment



はインデックス作成の準備ができています。



短い検索で、nginxサーバーについてのみ、角度付きサイトのインデックス作成メカニズムが詳細に説明されている記事に出会いました。 構成にさらにいくつかの場所が追加されました。



 if ($args ~ "_escaped_fragment_=(.*)") { rewrite ^ /snapshot${uri}; } location /snapshot { proxy_pass http://help.truemania.ru/snapshot; proxy_connect_timeout 60s; }
      
      





完成したフラグメントの返却要求が処理される第2レベルドメインを作成します。 リクエストタイプ

 http://truemania.ru/user/50?_escaped_fragment_=
      
      





あなたは得るでしょう

 http://help.truemania.ru/snapshot/user/50
      
      







検索ボットに与える必要があるすべての必要なキャストを作成するだけです。 その際、 schema.orgのマイクロマーキング標準を使用しました 。 セマンティックマークアップの世界に詳しくない人は、 この記事を読むことをお勧めします。



動的サイトマップの作成については、 この記事で詳しく説明しています -読むことをお勧めします。 しかし、Yiiの最初のバージョンのソリューションがここで説明されているのは残念です。 新しいリクエストごとにサイトマップが新たに作成されるため、サーバーに非常に高い負荷がかかる可能性があります。 解決策は、コンソールコントローラーを作成し、crontabを使用して10分間隔でサイトマップを更新することです。 ソースコードを少し変更して、Yii2コン​​ソールに適したソリューションを得ました。



 <?php namespace console\models; use Yii; /** * @author ElisDN <mail@elisdn.ru> * @link http://www.elisdn.ru */ class DSitemap { const ALWAYS = 'always'; const HOURLY = 'hourly'; const DAILY = 'daily'; const WEEKLY = 'weekly'; const MONTHLY = 'monthly'; const YEARLY = 'yearly'; const NEVER = 'never'; protected $items = array(); /** * @param $url * @param string $changeFreq * @param float $priority * @param int $lastMod */ public function addUrl($url, $changeFreq=self::DAILY, $priority = 0.5, $lastMod = 0) { $host = Yii::$app->urlManager->getBaseUrl(); $item = array( 'loc' => $host . $url, 'changefreq' => $changeFreq, 'priority' => $priority ); if ($lastMod) $item['lastmod'] = $this->dateToW3C($lastMod); $this->items[] = $item; } /** * @param \yii\db\ActiveRecord[] $models * @param string $changeFreq * @param float $priority */ public function addModels($models, $changeFreq=self::DAILY, $priority=0.5) { $host = Yii::$app->urlManager->getBaseUrl(); foreach ($models as $model) { $item = array( 'loc' => $host . $model->getUrl(), 'changefreq' => $changeFreq, 'priority' => $priority ); if ($model->hasAttribute('create_date')) $item['lastmod'] = $this->dateToW3C($model->create_date); $this->items[] = $item; } } /** * @return string XML code */ public function render() { $dom = new \DOMDocument('1.0', 'utf-8'); $urlset = $dom->createElement('urlset'); $urlset->setAttribute('xmlns','http://www.sitemaps.org/schemas/sitemap/0.9'); foreach($this->items as $item) { $url = $dom->createElement('url'); foreach ($item as $key=>$value) { $elem = $dom->createElement($key); $elem->appendChild($dom->createTextNode($value)); $url->appendChild($elem); } $urlset->appendChild($url); } $dom->appendChild($urlset); return $dom->saveXML(); } protected function dateToW3C($date) { if (is_int($date)) return date(DATE_W3C, $date); else return date(DATE_W3C, strtotime($date)); } }
      
      





コンソールアクション:



 public function actionGetsitemap() { $sitemap = new DSitemap(); $sitemap->addModels(Model1::find()->active()->all(), DSitemap::HOURLY); $sitemap->addModels(Model2::find()->all(), DSitemap::HOURLY); $sitemap->addModels(Model3::find()->all(), DSitemap::HOURLY); $path = \Yii::getAlias("@frontend/web") . DIRECTORY_SEPARATOR . "sitemap.xml"; return file_put_contents($path, $sitemap->render()); }
      
      





10分ごとに実行するようにcrontabを構成します。



*/10 * * * * /path/to/yii cron/getsitemap >> /path/to/log/command_log/getsitemap.log;









このソリューションは最適で生産性が高いため、かなり最新のデータを取得できます。 必要に応じて、より頻繁またはまれな間隔でサイトマップを再作成できます。



次に、ソーシャルネットワーク内のリンクの美しい出力に関する作業を行いました。 トピックに含まれていない人のために、これはhttp://ogp.me/マークアップ標準です。 ボットがメタを理解していないことに非常に失望しました

-タグ:



 <meta name="fragment" content="!" />
      
      





この段階では、額に対する解決策がまったくなかったため、少し失速しました。 実際の断片がページの背後に隠れていることをボットに理解させたかったのです。 グーグルで、ユーザーエージェントごとにフラグメントを提供することにしました。 対応するサービスのドキュメントを調べて、正規表現を使用して取得できるサンプルユーザーエージェントを取得する必要がありました。



ボットソーシャルネットワークに静的を与えるための私の構成:



 #     user-agent —    ,   if ( $http_user_agent ~* (facebookexternalhit|facebot|twitterbot|tinterest|google.*snippet|vk.com|vkshare) ){ rewrite ^ /snapshot${uri}; }
      
      





当然、スナップショットに開いているグラフレイアウト情報を含めることは残ります。



次に、いくつかの非常に有利な点でwebsocketを使用したかった-これは、ユーザーのオンライン/オフラインステータスなどのタスクを解決するのに最適でした。 もちろん、websocket自体はPHPにとって非常に非標準ですが、既製のソリューションがすぐに見つかりました-http://socketo.me/。



ubuntuのYii2でこれらのソケットを起動する方法を理解するだけです。 実際に、コンソールコントローラーを作成しました。アクションは次のようになります。



 public function actionWebsocketaction() { $server = IoServer::factory( new HttpServer( new WsServer( new UserOnline() ) ), 8099, '127.0.0.1' ); $server->run(); }
      
      





さて、以下ではUserOnlineモデル自体を囲みます。



 <?php namespace console\models; use Yii; use common\modules\core\models\User; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; use yii\web\ServerErrorHttpException; class UserOnline implements MessageComponentInterface { /** *  ,    */ const USER_OFFLINE = 0; const USER_ONLINE = 1; //       resourceId public function onOpen(ConnectionInterface $conn) { echo "New connection! ({$conn->resourceId})\n"; } //   ,     online public function onMessage(ConnectionInterface $from, $username) { $model = UserOnlineConnections::findByUsername($username); if(empty($model)) { $model = new UserOnlineConnections(); //     ,     $model->username = preg_replace('/\\r\\n$/', '', $username); $model->conn_id = $from->resourceId; if(!($model->validate() && $model->save())) throw new ServerErrorHttpException(json_encode($model->getErrors())); } else { $model->conn_id = $from->resourceId; if(!($model->validate() && $model->save())) throw new ServerErrorHttpException(json_encode($model->getErrors())); } echo "New user online $model->username \n"; self::setUserStatus($username, self::USER_ONLINE); } //   —   offline public function onClose(ConnectionInterface $conn) { echo "Close connection! ({$conn->resourceId})\n"; $username = UserOnlineConnections::findByConnId($conn->resourceId)->username; if($username) { //Set status offline echo "User offline $username \n"; self::setUserStatus($username, self::USER_OFFLINE); } } //  —   offline public function onError(ConnectionInterface $conn, \Exception $e) { $username = UserOnlineConnections::findByConnId($conn->resourceId)->username; if($username) { //Set status offline echo "User offline $username \n"; self::setUserStatus($username, self::USER_OFFLINE); echo "An error has occurred: {$e->getMessage()}\n"; $conn->close(); } } /** *     * @param $username * @param $status * @return bool * @throws ServerErrorHttpException */ public function setUserStatus($username, $status) { $model = User::findByUsername($username); if ($model) { $model->online = $status; if(!($model->validate() && $model->save())) throw new ServerErrorHttpException(json_encode($model->getErrors())); return true; } if($status == self::USER_OFFLINE) { UserOnlineConnections::deleteAll( "username=".$username ); } } }
      
      





すべてを実行するだけです。 stdoutでstderrを終了する必要がありましたが、&>は何らかの理由で動作したくありませんでした。 ソリューションにはnohupが付属していました。 ソケットの実行は次のようになりました。



 nohup /path/to/yii ws/useronline >> /path/to/log/command_log/useronline.log;
      
      





また、落下した場合は、このプロセスを再起動する必要があります。 より洗練された解決策、crontabで毎分コマンドを実行する方法は見つかりませんでした。 ポートがビジーの場合、何も起こりません(エラーが発生します)が、ポートが空いている場合、プロセスは再起動されます。



次に、nginxを使用してwebsocetをプロキシする必要があります。 そして、ここで次の行が構成に追加されました。



 upstream useronline { server 127.0.0.1:8099; } map $http_upgrade $connection_upgrade { default upgrade; '' close; } #    server server { #ws proxy location /useronline { proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_pass http://useronline; } }
      
      





これで、Webソケットがws://truemania.ru/useronlineで利用可能になります。



そして、開発プロセス中に(Webサーバー設定から)私が最後に遭遇したのは、httpsプロトコルへの移行です。 問題は、facebookとgoogle +がhttp経由で画像を送信することを望んでおり、プレビューで画像を永続的に表示することを望まなかったことです。 これを行うには、構成を変更する必要がありました。つまり、サーバーがhttp経由でメディアファイルを送信するように強制する必要がありました。



 server { listen 80; server_name truemania.ru; root /path/to/frontend/web; location / { return 301 https://$server_name$request_uri; # enforce https } #   http location ~* \.(css|js|jpg|jpeg|png|gif|bmp|ico|mov|swf|pdf|zip|rar)$ { try_files $uri =404; } } server { charset utf-8; listen 443 ssl; ssl_certificate /path/to/ssl/truemania.crt; ssl_certificate_key /path/to/ssl/truemania.key; }
      
      





また、プロトコルが変更された後、ソケットにアクセスするには、wss://truemania.ru/useronlineにアクセスします。



気に入った場合は、次の記事で、Webアプリケーションとバックエンドがどのように作成したか、許可、許可ユーザーと許可ユーザーの許可、requireJSの使用など、角度に関する興味深いソリューションについて説明します。



All Articles