20191221のMySQLに関する記事は13件です。

[Rails]特定のインスタンスに対して前後のレコードを取得したりランダムで取得したりするあれを試してみた

はじめに

こんにちはどうも、pirikaraです。
髪の毛を切りました。

今回はこんなやつを実装しました。
スクリーンショット 2019-12-21 12.36.00.png

特定の投稿(今回はitemの出品)に関して、DBから前後のレコードを取得して表示させたりリンクを飛ばす感じのあれです。
あとDBからランダムにレコードを取得して表示させたりリンク飛ばしたりする感じのあれです。

まずは特定の投稿に関してDBから前後レコードを取得する奴から実装していきます。
Rails標準のAPIでは見つからなかったので、今回はmodelに対してメソッドを書き込んでいきます。

いざ、実装

今回はItemクラスのインスタンスに関して、その前後レコードを取得するメソッドをmodels/item.rbに記述していきます。
前のレコードを取得するメソッドを『previous』
後のレコードを取得するメソッドを『next』
としてmodelに定義していきます。

item.rb
class Item < ApplicationRecord
  #......省略


  def previous
    Item.where("id < ?", self.id).order("id DESC").first
  end

  def next
    Item.where("id > ?", self.id).order("id ASC").first
  end
end

現在のインスタンス(self)のidより大きいか小さいかで前後レコードを判定・取得します。
作成したメソッドをview側で呼び出します。

items/show.haml
#......省略

- if @item.previous.present?
  =link_to item_path(@item.previous.id) do
    = @item.previous.name
- else
- else
  .none
- if @item.next.present?
  =link_to item_path(@item.next.id) do
    = @item.next.name
- else
  .none
#......省略

controller側で@item = Item.find(params[:id])などとし、Itemのshow画面に遷移。
@itemに対して『previous』 『next』メソッドを使用することで、@itemの前後レコードを取得できます。

また、前後レコードがない場合(最新のレコード、もしくは最古のレコードの場合)にリンクを表示しないよう『present?』メソッドによって真偽判定、条件分けしています。

Image from Gyazo

左右感がちょっと気持ち悪いですが、実装できました。(あとで直します。)

変化球

前後レコードではなく、DB内のデータをランダムで取得するあれを実装を試みてみます。
ネットサーフィンしながらいくつか方法を見つけたので試してみます。

1. RAND()関数

MySQLのネイティブ関数RAND()を使用してみます。

items_controller.rb
def show
  #......省略

  @random1 = Item.order("RAND()").limit(1)
  @random2 = Item.order("RAND()").limit(1)

 #......省略
end

左右のリンクがあるので、それぞれ@random1@random2として変数をインスタンス変数を定義してみました。
viewはこちら

items/show.haml
- if @random1.present?
  =link_to item_path(@random1.ids) do
    - @random1.each do |random1|
      = random1.name
- else
  .none
- if @random2.present?
  =link_to item_path(@random2.ids) do
    - @random2.each do |random2|
      = random2.name
- else
  .none

RAND()関数で取得したデータにlimitつけてましたが、配列で取得されるみたいなので『ids』としないとid取れませんでした。
また、nameについてもeach文で取り出してあげないと表示されませんでした悲しい。

そして結果がこちら。
Image from Gyazo
スクリーンショット 2019-12-21 14.32.36.png
......ねこもうさぎも被りました。

このあと何回もページ遷移してみました。
確かにランダムで表示されるようにはなったようですが、左右の値が毎回同じでした。
実際にDBテーブル内に存在する値からランダムに取得してくれるのはありがたいですが、
randam1とrandam2で別々に値を取得してくれるなんてそんな都合よく世界はできていなかったようです。

別の方法を試します。

2. Model.all.sample

モデルから全てのレコードを取得した上で1件抽出する感じ。
ゴリ押ししてみます。

items_controller.rb
def show
  #......省略

  @random1 = Item.all.sample
  @random2 = Item.all.sample

 #......省略
end
items/show.haml
- if @random1.present?
  =link_to item_path(@random1.id) do
    = @random1.name
- else
  .none
- if @random2.present?
  =link_to item_path(@random2.id) do
    = @random2.name
- else
  .none

今回は配列でなくデータ単体を取ってこれたので、idsとかeach文なんてまどろっこしいことをせずに済みました。やったね。
気になる結果は......
Image from Gyazo
で!!!け!!!た!!!
random1とrandom2で別々の値が取ってこれてます。さすがゴリ押し。
これにて実装完了......と思ったその時。見つけてしまいました。

ActiveRecord でランダムなレコードが欲しい(1件、複数件)

『全件取ってランダムに1件取り出すのはダサい』

.........ダサい???

確かに全部とってランダムに1件はメモリやらなんやらに理解の浅い僕でも効率が悪いのは理解できます。
こういう時にindexが便利なのか......?

とにかくダサいのは嫌なので別の方法を試します。(終わりが見えない)

3. Model.offset(rand(Model.count)).limit(1)

記事の方法をパクり...参考にさせていただきました。ありがとうございます。
レコード未満のランダム生成された整数をoffsetでレコード取得の開始位置とし、1個だけデータを取ってくる感じです。

items_controller.rb
def show
  #......省略

  @random1 = Item.offset( rand(Item.count) ).limit(1)
  @random2 = Item.offset( rand(Item.count) ).limit(1)

 #......省略
end
items/show.haml
- if @random1.present?
  =link_to item_path(@random1.ids) do
    - @random1.each do |random1|
      = random1.name
- else
  .none
- if @random2.present?
  =link_to item_path(@random2.ids) do
    - @random2.each do |random2|
      = random2.name
- else
  .none

......each文でないと取り出せませんでした......idsも復活しました......
しかしoffsetで開始を指定している分、Model.all.sampleの時よりはスマートなデータ取得ができている気がします。
さて結果は......
Image from Gyazo
......ランダムなってるやん。
さすがスマート。

『自分のデータは除いて......』の実装はしていないのでGIFでは可愛いうさぎが延々と出現してしまっていますが、
きちんとランダムにリンクが生成されています。

これで終わりにしようと思いましたが、僕は根に持つタイプなのでダサくない方法をもう一つ考えてみます。

4. Model.where('id >= ?', rand(Model.first.id..Model.last.id)).limit(1)

offset、 limitを使った書き方の書き換えとしてwhereに置き換える方法が紹介されていたので、試してみます。
randの引数でランダム数値生成の範囲指定を行いますが、今回は先頭レコードと最終レコードのidを範囲として指定しています。

Rails: データベースのパフォーマンスを損なう3つの書き方(翻訳)

items_controller.rb
def show
  #......省略

  @random1 = Item.where('id >= ?', rand(Item.first.id..Item.last.id)).limit(1)
  @random2 = Item.where('id >= ?', rand(Item.first.id..Item.last.id)).limit(1)

 #......省略
end
items/show.haml
- if @random1.present?
  =link_to item_path(@random1.ids) do
    - @random1.each do |random1|
      = random1.name
- else
  .none
- if @random2.present?
  =link_to item_path(@random2.ids) do
    - @random2.each do |random2|
      = random2.name
- else
  .none

こちらもeach文、idsを用いて配列からデータを取り出して出力しています。
結果は......
Image from Gyazo

ランダムになってました。満足。

比較

rails consoleからSQL文とSQL発行の所要時間が確認できるので、
上記4つのものを確認・比較してみます。

1個目
スクリーンショット 2019-12-21 16.06.59.png
2個目
スクリーンショット 2019-12-21 16.06.08.png
3個目
スクリーンショット 2019-12-21 16.07.41.png
4個目
スクリーンショット 2019-12-21 16.08.35.png

......DBにデータが7つしかないので速さは大差ないですね。そりゃそうか。
SQL文は4個目では3回発行されています。あまりよろしくないですね......
スマートな実装としては3個目推奨でしょうか。

という訳でseeds.rbを読み込んでデータを1000件作ってみました。

seeds.rb
1000.times do |index|
  Item.create!(name: "アイテム#{index}",
              seller_id: 1, 
              description: "内容#{index}", 
              category_id: index,
              condition_id: index, 
              prefecture_id: index, 
              sendingmethod_id: index, 
              postageburden_id: index, 
              shippingday_id: index, price: index, 
              profit: index)
end

スクリーンショット 2019-12-21 22.39.10.png
もう一度検索スピードを検証してみます。

1個目
スクリーンショット 2019-12-21 22.41.53.png
2個目
スクリーンショット 2019-12-21 22.43.45.png
3個目
スクリーンショット 2019-12-21 22.45.02.png
4個目
スクリーンショット 2019-12-21 22.46.11.png

SQL発行の所要時間に大きく差が出ました。
1個目、2個目は3個目、4個目に比べて10倍以上の時間がかかってしまっています。
クエリ文が3回発行されてしまっていることを考えると、
今回試した手法の中ではoffsetを用いた3番目の方法がベストプラクティスとなります。

おわりに

今回はSQLについて理解を深めることができました。
Indexを用いた検索でも検証を行ってみたいですね。
大規模なWebアプリケーションになればなるほど『どんな手法で検索をするか』で処理スピードに大きな差が生まれてしまうことを実感できました。

また気が向いたらなんか書きます。

おわり。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerコンテナ(mysql)に接続する

Dockerコンテナ(mysql)にアクセスする

起動中のコンテナを確認する

$docker-compose ps
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                                NAMES
847c99b0929c        nginx:latest                   "nginx -g 'daemon of…"   20 minutes ago      Up 20 minutes       0.0.0.0:8080->80/tcp                 docker_php_nginx_1
5e2039b237df        docker_php_php                 "docker-php-entrypoi…"   20 minutes ago      Up 20 minutes       9000/tcp                             docker_php_php_1
8193d899b38d        phpmyadmin/phpmyadmin:latest   "/docker-entrypoint.…"   20 minutes ago      Up 20 minutes       0.0.0.0:8888->80/tcp                 docker_php_phpmyadmin_1
f4ff8b6b8ac3        mysql:5.7                      "docker-entrypoint.s…"   22 minutes ago      Up 20 minutes       33060/tcp, 0.0.0.0:13306->3306/tcp   docker_php_db_1

ホストPCからコンテナ(mysql)に接続する

CONTAINER IDを指定

$docker exec -it f4ff8b6b8ac3 bash

mysqlに接続

root@f4ff8b6b8ac3:/# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.7.28 MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
+--------------------+
5 rows in set (0.03 sec)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SaaSやサブスクリプションにおける主要なメトリクスをスプレッドシートだけで算出する方法

LAPRAS株式会社でカスタマーサクセスを担当している@yuta_maruyamaです。
自分はその中でもLAPRAS SCOUTという企業の採用担当者向けのSaaSプロダクトのカスタマーサクセスを担当しています。
こちらの記事では、サブスクリプションモデルのサービスにおいて、スプレッドシートだけで主要メトリクス(Charn Rate、MRR等)を算出し、簡単なダッシュボードを作るところまでを解説します。

背景

Zuoraのようなサブスクリプションサービスを支援するようなサブスクリプションサービスが存在するように、サブスクリプションの管理は案外厄介なものです。
自分の場合、カスタマーサクセスとしてChurn Rateの抑制を目指すにあたって、顧客の契約状況の把握とそれに応じた主要メトリクスの算出をする必要がありましたがなかなかそのことについて書かれた文献が見つからないという状況でした。
事業のフェーズ的にも、サブスクリプション管理のためだけに新たにSaaSを導入する気にはならなかったので、シンプルにスプレッドシートで管理する方法をとりました。その方法をご紹介します。

対象

領域

  • SaaSを作っている人
  • サブスクリプションモデルのサービスを作っている人
  • B、Cは問わない

スキルや知識

  • スプレッドシート(もしくはExcel)でif関数、countifs関数が使える
  • サブスクリプションにおけるメトリクスの簡単な理解
    • Recurring Revenue
    • Churn Rate
    • LTV
    • 等々
  • 簡単なDB言語を扱えるとなお良い(今回は使いません)

前提

  • 6ヶ月ごとに自動契約更新をする、月額課金制のBtoBサービスを前提とします
  • 月額は15万円です
  • サブスクリプション更新ごとに新しく契約情報(contract)を作ります

全体観

契約に関する情報を持つcontractに対して、スプレッドシートで用意されている関数を使ってサブスクリプションに関する情報を付け足し、
それを別シートで集計することによって月次のサブスクリプション契約の動向が分かるようにします。
今回は各月における
- 契約している顧客数
- 新規契約した顧客数
- 離脱した顧客数
- MRR(Monthly Recurring Revenue)
- チャーンレート
を求めるまでを目指します。

サンプルのスプレッドシート

実際にメトリクスを算出したスプレッドシートはこちらになります。こちらを参考にしながら下記の説明を読んでみてください。
https://docs.google.com/spreadsheets/d/1dgVjiU-acRXB_tJ18b0tlQnfEcgqfY939QkVVjtgjr0/edit#gid=1999363116
※入っているデータはダミーデータです

contractデータの整備

contract(契約情報)を整えていきます。必要な情報は以下です。

必要な情報

  1. contract_id(A列)
    契約情報のIDです

  2. 顧客ID(B列)
    サブスクリプション契約をしている顧客のIDです

  3. 契約開始日(C列)
    サブスクリプションが始まった日付です。

  4. 契約終了日(D列)

  5. 月額(E列)
    月あたりの売上です。

データの並び替え

スプレッドシート上である計算をするのにデータの並び順が必要になります。
顧客IDで昇順に並び替えた上で、同一の顧客IDの中でさらに契約開始日で昇順に並び替えてください。

データの加工

上記のデータを元に、サブスクリプション管理をする上で必要な情報を付与していきます。

  1. Subscription Rate
    サブスクリプション契約がその顧客にとって何個目のcontractかを表す列です。
    if関数を用いて、もし上の行と同じ顧客IDであれば上の行のSubscription Rateに1を足して自身のSubscription Rateとする処理を記述します。2行目で例をとると
    =if(B7=B6,F6+1,1)
    になります。
    Subscription計算シート_-_Google_スプレッドシート.png

  2. Cancel Rate
    どのcontractでチャーンしてしまったかを表す列です。ここの値が1となっており、かつ契約終了日が今日の日付より前のcontractに関してはチャーンと判定できます。
    処理としては、該当の顧客のcontract数をcountif関数で算出し、そこからSubscription Rateを引きます。こうすることで一番古いcontractに1の値が入るSubscription Rateと逆に、最新のcontractに1を振ることができます。
    2行目で例をとると
    '=countif($B:$B,B7)+1-F7'
    になります。

  3. 新規判定
    そのcontractがその顧客にとって最初のcontractかを判定します。trueであれば1、falseであれば0を振ります。
    今回の場合、Subscription Rateが1であれば新規とみなせます。
    2行目で例をとると
    =if(F7=1,1,0)
    です。

  4. 更新判定
    そのcontractが契約更新かどうかを判定します。Cancel Rateが1でなければ、次に続くcontractがあるとみなせるので、契約更新と言えます。2行目で例をとると
    =if(G7>1,1,0)
    となります。

  5. 離脱判定
    そのcontractをもって顧客が離脱してしまったかどうかを判定します。Cancel Rateが1で、かつ契約終了日が今日の日付より前の日付(もう決着がついてしまっている)ようであれば離脱判定となります。
    2行目で例をとると
    =if(AND(G7=1,D7<today()),1,0)

集計用シート(Subscription Sheet)

整備したcontractの情報をもとに、各月のパフォーマンスを集計していきます。

月を並べる

算出したい月を手作業で並べていくこともできますが、可能であれば自動で月が塗り替わるようにしたいです。
その際はeomonth関数を使うと便利です。eomonth関数は EOMONTH(開始日, 月数) と記述することができますが、開始日を今日ととり、月数を1ヶ月ごとずらしていくことによって常に今月から過去12ヶ月が自動で並ぶといったような書き方が可能です。
その際、月数をいちいち並べると面倒なので、例えば12ヶ月分の月だけ並べるのであれば、A列に0から12の番号を並べ、それぞれを月数として参照するようにしましょう。
2行目を例にとると
=eomonth(today(),-A2)
といった式になります。

Subscription計算シート_-_Google_スプレッドシート.png

また、このままだと日付まで入ってしまうので表示形式の設定で
yyyy-MM
形式に変更しておきましょう。

※A列は見た目上邪魔なので、非表示にしておきましょう

顧客数と売上の集計

月末顧客数

その月に契約をしていた顧客数を集計します。契約開始日がその月の末日より前で、契約終了日がその月の末日より後であればその月に契約状態であったと言えるので、そういった式にします。
後ほどのチャーンレートの計算で、月初の顧客数と月末の顧客数の違いが重要になってきますので、それぞれ用意します。

さきほどeomonth関数で算出した日付は月末の日付なので、月末顧客数側をいじっていきます。
subscriptionシートのMonthがcontractシート側の契約開始日より後、契約終了日より前の場合はカウントしたいのでそのような式にします。

2行目を例に取ると
=countifs(contract!$C:$C,"<="&B2,contract!$D:$D,">="&B2)
です。

Subscription計算シート_-_Google_スプレッドシート.png

月初顧客数

月初顧客数は前月末の顧客数と一緒になります。
2行目で例にとると
=D3
です。シンプルです。

MRR

その月で生み出す売上を算出します。
簡単に言うと、その月契約状態となっているcontractの月額の総和となるのでそれを求めます。
2行目で例ととると
=sumifs(contract!$E:$E,contract!$C:$C,"<="&B9,contract!$D:$D,">"&B9)
です。
Subscription計算シート_-_Google_スプレッドシート.png

新規、更新、離脱の集計

sumifs関数を使って、その月に新規、更新、離脱が何件あったかを集計します。

新規顧客数

契約開始日が当月の月末より前で前月の月末より後で、新規判定が1の場合、その当月の新規契約とみなせます。
2行目で例を取ると
=sumifs(contract!$H:$H,contract!$C:$C,"<="&B2,contract!$C:$C,">"&B3)
といった式になります。

Subscription計算シート_-_Google_スプレッドシート.png

更新顧客数

契約終了日が当月の月末より前で前月の月末より後、そして更新判定が1の場合、その月の更新とみなせます。
2行目で例を取ると
=sumifs(contract!$I:$I,contract!$D:$D,"<="&B2,contract!$D:$D,">"&B3)
になります。

離脱顧客数

契約終了日が当月の月末より前で前月の月末より後、そして離脱判定が1の場合、その月の離脱とみなせます。
2行目の式は
=sumifs(contract!$J:$J,contract!$D:$D,"<="&B2,contract!$D:$D,">"&B3)
となります。

チャーンレートの集計

チャーンレートは一番一般的な集計の仕方であれば、月初の顧客数/離脱した顧客数となります。
そのレートを求めます。
2行目の式は
=G2/C2
です。

まとめ

今回は手持ちのデータとスプレッドシートで主要なサブスクリプション指標を算出するところまでをやりました。
次回はこれらのデータを用いて、サブスクリプションのコンディションがひと目で分かるダッシュボードをGoogle Data Studioを使って作っていきます。
それではみなさんごきげんよう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初学者向け]mysqlの操作マニュアル

mysqlに関して、操作方法を過去に学習してましたが、完全に忘れてしまってましたので、備忘録としてまとめます!

mysqlへの接続

terminal
#rootの部分はユーザー名を記入。
$ mysql -u root

注意事項

SQL文の終わりには;をつけましょう。

terminal
# ;をつけ忘れた時は->の後に;をつけましょう。
mysql> SHOW DATABASES
     ->;

データベース一覧確認

terminal
#以下コマンドでデータベース一覧が確認できます。
SHOW DATABASES;

データベース作成

terminal
#以下コマンドでデータベースが作成できます。
CREATE DATABASES データベース名;

データベース選択

データベースの操作を行う場合は、そのデータベースを選択する必要があります。

terminal
#以下コマンドでデータベースが選択できます。
USE データベース名;

テーブルの確認

データベース選択後、テーブルの確認をしてみましょう。

terminal
#以下コマンドでデータベースのテーブルの確認ができます。
SHOW TABLES;

テーブルの作成

terminal
#以下コマンドでテーブルが作成できます。
CREATE TABLE テーブル名(カラム名 カラムの型,カラム名2 カラム型...);

カラムの型について、以下に記載します。
Railsでは数字を入れる型は"Integer"、文字列を入れる型は"String"となっていましたが、mysqlで数字型や文字列型を定義する際は以下のような型名を使用します。

 
型名 保存できる値
INT 数字
VARCHAR(M) 最大M文字の文字列

テーブル構造の確認

terminal
#以下コマンドでデータベースのテーブル構造の確認ができます。
SHOW columns FROM テーブル名;

カラムの追加

terminal
#以下コマンドでカラムの追加ができます。
#複数の場合は()と,で区切りましょう。
ALTER TABLE テーブル名 ADD (カラム名 カラムの型,カラム名2 カラムの型...);

カラムの修正

terminal
#以下コマンドでカラムの修正ができます。
ALTER TABLE テーブル名 CHANGE 古いカラム名 新しいカラム名 新しいカラムの型;

*カラムの方は修正しない場合も記載が必要です。

カラムの削除

terminal
#以下コマンドでカラムの削除ができます。
ALTER TABLE テーブル名 DROP カラム名;

以上となります。最後までご覧いただき、ありがとうございました!
今後も学習した事項に関してQiitaに投稿していきますので、よろしくお願いします!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Eclipseを使っていてSQLデータのCSVインポートについて困ったことを我なりに解決した。

まず、前提として私はゲームプログラマの勉強しているためこちらの知識はあまりありませんのでご理解のほどよろしくお願いいたします

はじめに

EcipseでSQLを使用して会員情報や情報データを保存した。
その情報データをまとめて更新したりをcsvで出来るようにしたい。
色んなサイトやQiitaで紹介してるが所々省かれたりしていて分からない。

そこを普段Eclipse等を使用しない人がやれるとこまで出来た。

接続のための準備

使用DB:MYSQL

ー今回はEclipseとMYSQLの接続については省く

ここでEclipseに「DBViewer」というプラグインを導入してください
   EclipseでDBをいじるのに便利なプラグイン:DBViewer

MySQL起動

コマンドプロンプトにて

mysql -u root -p --local-infile=1

  ーPWを求められている場合はPWを入力して起動する。

use sampleDB //使用するのを指定する
ここまでは基本として考えておいてください

CSVインポートまでの流れ

Eclipseに導入した、「DBViewer」使用して編集したいテーブルをCSV出力する
csvOutPut.png

編集したCSVは保存してからファイルを右クリックで、プログラマから開く→メモから開く
※ 文字コード設定で ANSI から UTF8に変更する ←ここ重要

ここで確認すること

mysql> status

SQLのステータスを確認するコードを入力

Server characterset:    utf8
Db     characterset:    utf8
Client characterset:    cp932 //⇒utf8だと失敗
Conn.  characterset:    cp932 //⇒utf8だと失敗

上記の結果にならないといけない! カラムデータの日本語は文字化けする。

文字コードを変更する

C:\ProgramData\MySQL\MySQL Server 8.0
の指定されたファイルにある、my.iniを変更しなければならない。

my.ini
[mysqld]
character-set-server=utf8

[mysql]
default-character-set=cp932

参考:https://teratail.com/questions/26559

他サイトに書いてあった、
 skip-character-set-client-handshake の変更はしてはいけない。←僕も騙されましたw

my.iniを書き直した場合は、MYSQLの再起動が必要になる
コントロールパネル→管理ツール→サービス→MYSQL8.0(verは異なる)→左側にある再起動をクリックすればおk

この設定をした後に、上記にあるステータス確認をして合っているか確認

ついにインポート!

LOAD DATA LOCAL INFILE 'C:/Users/Desktop/test.csv' INTO TABLE testTable FIELDS TERMINATED BY ',';

testTableは指定したテーブルを入力。

そしたらデータが入ってるのではないでしょうか。

少し厄介な文字コード等があり時間かかりましたが誰かの役に立てれば幸いです。

時間あってよければ、僕の作品も見てください→ポートフォリオ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLのパスワードポリシーを確認する

はじめに

MySQLのパスワードを変更しようとした際、パスワードポリシーに引っかかって少し戸惑ったので解決策をメモしておく。

MySQLのパスワード変更

MySQLにログイン後、パスワードを変更しようとするとこんなエラーが出た。

パスワード変更
mysql> set password for root@localhost='新パスワード';
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements

パスワードの複雑さを定義しているポリシーを満たしておらず、エラーになっている。
もっと複雑なパスワードにしてください、ということみたい。

ポリシーの確認

このポリシーを確認するため、以下のSQLを実行する。

mysql> show variables like 'validate_password%';
+--------------------------------------+--------+
| Variable_name                        | Value  |
+--------------------------------------+--------+
| validate_password_check_user_name    | OFF    |
| validate_password_dictionary_file    |        |
| validate_password_length             | 8      |
| validate_password_mixed_case_count   | 1      |
| validate_password_number_count       | 1      |
| validate_password_policy             | MEDIUM |
| validate_password_special_char_count | 1      |
+--------------------------------------+--------+
7 rows in set (0.01 sec)

上記のそれぞれの設定項目の意味は、以下の通り

設定項目 設定内容 デフォルト値
validate_password_check_user_name パスワードの一部にユーザー名が使われているか OFF
validate_password_dictionary_file 除外する文字列を定義したファイルを作成 なし
validate_password_length パスワードの長さ 8
validate_password_mixed_case_count パスワードポリシーが MEDIUM またはそれより強い場合の、パスワードに必要な小文字および大文字の最大数 1
validate_password_number_count パスワードポリシーが MEDIUM またはそれより強い場合の、パスワードに必要な数値文字 (数字) の最大数 1
validate_password_policy 数値 0、1、2 または対応する記号値 LOW、MEDIUM、STRONG を使用して指定できる。詳細は下記に示す。 MEDIUM
validate_password_special_char_count パスワードポリシーが MEDIUM またはそれより強い場合の、パスワードに必要な英数字以外の文字の最大数 1

validate_password_policyの内容

ポリシー 実施されるテスト
0 または LOW 長さ
1 または MEDIUM 長さ。数値、小文字、大文字、および特殊文字
2 または STRONG 長さ。数値、小文字、大文字、および特殊文字。辞書ファイル

ポリシーの変更

ポリシーを変更する場合、以下のようなSQLを実行し、ポリシーを定義する変数を設定する。

ポリシーの変更
mysql> set global validate_password_length=6;

上記は、パスワードの長さを6文字に変更している。

参考

MySQL 5.6 リファレンスマニュアル

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLのデフォルトパスワードを確認する

はじめに

MySQL5.7以降では、デフォルトでrootユーザーのパスワードが自動で初期設定されている。
このパスワードの調べ方のメモ。

環境

OSはAmazonLinux2
MySQLのバージョンは5.7

OS
# cat /etc/system-release
Amazon Linux release 2 (Karoo)

# mysql -V
mysql  Ver 14.14 Distrib 5.7.28, for Linux (x86_64) using  EditLine wrapper

MySQLにrootログインしてみる

rootでMySQLに入ろうとすると、Access Diniedとなり弾かれます。

# mysql -u root
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

MySQLのパスワード確認

rootの初期パスワードを確認する。
初期パスワードは /var/log/mysqld.log に記述されているため、以下のコマンドで確認できる。

パスワード確認
# cat /var/log/mysqld.log | grep password
2019-12-13T06:12:17.402781Z 1 [Note] A temporary password is generated for root@localhost: <初期パスワード>

A temporary password is generated for root@localhost: 以降の文字列が初期パスワード。
この文字列をコピペしてログインすると、MySQLにログインできるようになる。

MySQLにログイン
# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 5.7.28

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

初期パスワードの変更

ログインできれば、初期パスワードは変更しておく。

初期パスワード変更
mysql> set password for root@localhost='新パスワード';
Query OK, 0 rows affected (0.00 sec)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ターミナルでSQL文を使ってDB操作

MySQLに接続

mysql -u root

データベースの確認

SHOW DATABASE;

###データベース作成

CREATE DATABASE データベース名;

使用するデータベースの選択

USE データベース名;

テーブル作成

CREATE TABLE テーブル名(カラム名 データ型);#カンマ区切りで複数カラム作成可。

カラムを見る

SHOW columns FROM カラム名;

カラムの追加

ALTER TABLE テーブル名 (カラム名 データ型);#カンマ区切りで複数カラム作成可。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

絶対に二重サブミットを許さない友の会

MDC Advent Calendar 2019 の20日目です。投稿が21日になってすみません。割腹します。
「MDC」がどういう意味なのかよくわからなかったので、Majide Double-submit-ni Curushinderuの略だと信じてこの記事を書いています。間違ってたら教えてください。
おふざけ枠として「API Gatewayで高輪ゲートウェイ作ってみた」とかやろうとしたんですが、
高輪のことをよく知らなかったのでやめました。二重サブミットの話をします。

二重サブミットとわたし

早速ですが。
人生、誰でも一度は二重サブミットと真剣に向き合う時期があると思います。

二重サブミットはその名の通り二重でsubmitをすることですが
「二重登録」とか「二重更新」とか「二重リクエスト」とかの言葉も概念としては同じです。
<input type="submit">を二連打するとか、POSTのリクエストを二連送信するとかの手段はどうでもよく
本質的には「二重で実行されるとシステムとして困る操作を二重ですること」と考えてよいと思います。

困る操作」は、DB更新・セッション更新・ファイル出力・他システム連携のような別レイヤへはみ出す処理や、
その処理に伴って内部で不整合が発生し、適切な画面表示やデータ返却ができないパターンが多いでしょうか。
うっかり操作で簡単に発生し得るので無対策だと痛い目を見るかもしれません。二重徴収とかね。怖いな〜〜〜

以下ではシンプルなWebアプリで二重サブミットを防止する小手先実装に触れますが
OpenAPIのようないつ何者からどれだけ叩かれるかわからないエンドポイントであれば、
べき等にするとかアーキテクチャで対応するとか、「そもそも困らないように作る」ことが肝要かと思います。
リリース前モンキーテストで発覚して、一日で急遽対応しないといけない時だけ参考にしてください。

許さないために

今回はゴリゴリの新技術の話ではなく、きわめて普通のWebアプリで対策する前提で考えますので
テキストボックスがあって、送信ボタンがあって、サーバで受けてDBに突っ込んで、完了画面を返すような
古式ゆかしい登録フォームなんかを想像して。肩肘張らないで。足なんかも崩して。お願いします。

なお、方針としては「させない」対策と「されても耐える」対策に大別され、
前者はフロント側実装、後者はサーバ側実装が多めになります。

させない

させない方です。

ボタンを押せなくする

disabled.mov.gif

formがボタンのクリックイベント発火を受けてsubmitされるので、
押した瞬間にボタンを非活性にして、二回目のクリックをさせないようにしようという発想です。
わかりやすい。

index.js
$(function() {
  $('button').on('click', function() {
     $(this).prop('disabled', true);
     $('form').submit();
  });
});

見た目にもピンと来やすいですし採用しているサイトも多いです。
スピナー(ローディング中のぐるぐる)をボタンに載せるパターンも見かけますね。
ただ回線状況等々によっては画面遷移に失敗してボタン非活性だけ発動みたいなケースもあり
非活性化という強めの処理を行っているだけあって、考慮ポイントもままあることは注意したいところ。

確認ダイアログを出す

confirm.mov.gif

古式ゆかしすぎる。モダンなサイトでは全く見かけないですね。
window.confirm()というなんかすっごいネイティブな機能を使ったものです。
https://developer.mozilla.org/ja/docs/Web/API/Window/confirm

index.js
$(function() {
  $('button').on('click', function() {
    var confirmMessage = '登録しますか?';
    if (window.confirm(confirmMessage)) {
        // OK押下時の挙動
    } else {
        // キャンセル押下時の挙動
    }
  });
});

ボタンを押してもform.submit()は呼ばれず、確認ダイアログが表示されるだけなので
html上のボタン連打をしてもほとんど副作用がないのが嬉しいと思います。
なおかつ確認ダイアログはブラウザ機能で表示しているものなので、
開発者ツールなどで挙動を変えられることがなく、防御としては堅めな気がしています。
ただいかんせん古臭いですし、ワンクリック増えるのでUXとしては一段落ちるかもしれません。

PRGパターンを使う

prg.mov.gif

(※フォームの文字列が消えてるのは消してるんじゃなくリダイレクトで再描画されてるからです!!!)

Post - Redirect - Getの略でPRGです。こちらはサーバ側実装です。
こんなんたまに出ますよね。
スクリーンショット 2019-12-20 23.57.44.png
REST周りの話はそれだけで記事一本分になりそうなので割愛しますが、
POSTリクエストに対して返却された画面でリロードを行うと、
直前のPOSTリクエストが再度サーバに対して送信されてしまいます(フォーム再送信)。

そのため、POSTリクエストを受け付けた後にリダイレクトを行って
GETリクエストに対して画面を返却するようにします。
そうすることでリロードはGETリクエストに対してのみ行われ、POSTリクエストは再送信されなくなります。
(GIF画像でも、POSTの/createに飛んだ後にGETの/indexに転送されているのがわかるかと思います)

サーバでPOSTリクエストを受け取って、リダイレクトして、画面を返却して・・・という順序なので
最初のPOSTリクエストを二連打された日にゃ何もできませんが、後のリロードは防げます。そういうものです。

されても耐える

されても耐えます。男の子だから・・・

セッションと画面にトークンを格納する

ここら辺から急にじゃばじゃばしてきます。Spring使います。
登録画面をクライアントに返却する際に、セッションと画面に同じ値を格納しておき、
画面に載せた値はsubmitで送信させ、
POSTリクエストを受け取った直後、二つの値を比較してリクエストの真正性を確認する方法です。
確認して正しいと判断した場合はトークンを削除or上書きし、2回目以降のリクエストが来ても弾くようにします。

Controller.java
    @GetMapping("/index")
    public String index(Model model, SessionDto session, UserForm userForm) {
        String randomStr = RandomString.make(10);
        session.setToken(randomStr);
        userForm.setToken(randomStr);
        // 後略
    }

    @PostMapping("/create")
    public String create(Model model, SessionDto session, @ModelAttribute UserForm userForm) {
        if (!session.getToken().equals(userForm.getToken())) {
            return "/error";
        }
        session.setToken("");
        // 後略
    }

CSRF対策と発想は似ていますが、同一人物でも2回目以降は弾くという点では異なります。
ちなみにみんな大好きSpring SecurityのCSRFトークンは同セッション中は不変です(たぶん)。
なので戻る遷移から再送信されても弾けず、二重サブミット対策にはなりません。

ちなみにこちらの方法ですが・・・
検証中にjsのform.submit()複数回実行で動作確認していたところ
Controllerのメソッド内で画面を返却するまでは、セッションに対するsetが反映されないような雰囲気がありました。
(あくまで雰囲気なので詳しい方補足ください)

要するに、
リクエスト1 : セッションと画面からトークンを取得し比較 -> "token1"が返ってくる
リクエスト1 : セッションのトークンを"token2"で上書き -> この時点でセッション内トークンは"token2"のはず
リクエスト2 : セッションと画面からトークンを取得し比較 -> なぜかここも"token1"が返ってくる
という挙動です。
この時はRedisにセッションを格納していたので、Springboot+Redis特有のやつ?
SpringbootからRedisに書き込まれるタイミングの問題? とか思ってたんですが・・・

トークンをRedisに直接格納する

Springの挙動がよくわからないのでRedisに直接詰めることにしました。
この方法ならredisTemplateを呼び出したタイミングで確実にredisへのアクセスが行われ
データの読み取り・書き込みが即時実行されます。
プロシュート兄貴も「『直』は素早いんだぜ」って言ってたし・・・

    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/index")
    public String index(Model model, SessionDto session, UserForm userForm) {
        String userUniqueKey = session.getUserId() + session.getUserName();
        String randomStr = RandomString.make(10);
        redisTemplate.opsForValue().set(userUniqueKey, randomStr);
        // 後略
    }

    @PostMapping("/create")
    public String create(Model model, SessionDto session, @ModelAttribute UserForm userForm) {
        String userUniqueKey = session.getUserId() + session.getUserName();
        String value = redisTemplate.opsForValue().get(userUniqueKey);
        if (value == null) {
            return "/error";
        }
        redisTemplate.delete(userUniqueKey);
        // 後略
    }

Spring sessionを無視して自分でRedisアクセスを行うことになります。
Springboot+Redisの構成の場合、Controllerのメソッド引数に値を適当に詰めたりして持ち回していれば
特に意識せずとも、Springbootが発行したSessionIdをキーにRedisへのデータ登録が行われます。

ですが直アクセスなので、そのユーザのセッションを一意に特定するようなキーを自分で設定せねばなりません。
DB的にprimaryな値がよいかと思いますが、ここは防ぎたいパターンに応じてチューンしてください。

DBのレコードの存在チェックをする

前項の方法は、「セッションを使って防ぐ」というよりは「インメモリデータベースを使って防ぐ」といった感じでした。
インメモリデータベースで防げるならオンディスクデータベースでも防げそうです。
(そもそもインメモリデータベースを採用していないケースも多いと思いますし)

こちらの場合は「排他テーブル」を作成しておき、以下のような流れで処理することになります。

(1) POSTリクエストを受ける
(2) ユーザを特定する一意な情報で排他テーブルを検索する
(3) 既にレコードが存在した場合はエラーに流す
(4) レコードが存在しない場合はレコードを新規に作成する
(5) 申込内容のDB登録やら他システム連携やらをする
(6) 全ての業務処理が完了した後、排他テーブルのレコードを削除する

トランザクション境界の設定や、(5)でエラーとなった場合の排他レコードの扱いなど
綿密な設計をしないと必要以上にロックが掛かりかねないので、難易度や影響範囲は上がります。

ちなみに、わざわざ排他テーブルを新設せずとも
POSTリクエストを受けてDBへの登録が走るシステムなのであれば、
POSTリクエストを受けた直後に、DBに既に登録があるかをチェックしにいき
問題ない場合だけDBへ申込内容のinsertを行う、という処理順序にすることで
同様の対策を行うことができます。

ただし、POSTリクエストを契機に他システムへの連携等を行って、実行結果を最終的にDBに詰めたい等で

(1) POSTリクエストを受ける
(2) 他システム連携を行う
(3) 申込内容と連携結果のDB登録を行う

という流れになっている場合は、

(1) POSTリクエストを受ける
(2) 申込内容のDB登録を行う
(3) 他システム連携を行う
(4) 申込内容レコードを、他システム連携結果で更新する

「先に空のハコを作っておいて二重登録を防ぎ、後から必要な情報を更新する」という流れにせざるを得なくなります。
こちらは排他テーブルの場合と同様、(3)でエラーとなったときの扱いを密に設計する必要があります。

DBの力を借りる

前項で「排他」という言葉を使いましたが、排他に関してのプロフェッショナルは言わずもがなDBです。
悲観ロックとか楽観ロックとか監獄ロックとかいろいろありますが、
基本思想レベルで真剣に二重登録と向き合っている世界なので、せっかくならそこに相乗りしましょう。楽ちん

select4update.mov.gif

今回はmysqlのselect for updateを使いました。
詳細な挙動は他で語り尽くされているので割愛しますが、
端的に言うと「commitするまで他のプロセスからのクエリ発行を待機させる」ものです。
(for updateが付随しない単なるselectであれば一応通りますが、古いデータが返ります)

この機能を利用して、POSTリクエストを受けた直後に排他を掛け始めて
全ての処理が完了したときにcommit(もしくはDB更新で自然体でcommit)する、という流れになります。

こちらのやり方はとにかく確実です。
長年積み重ねられてきた排他制御の叡智にあやかるのでとにかく守られます。

前項でも触れたロックかかりすぎちゃう問題はこちらにもありますが、
mysqlの行ロックはデフォルト50秒、設定によってセッションごとのロック時間変更もできるようなので
一度しくじったら一生排他、データ修正するまで触れませんゲームクリアさようなら〜ということにはならなさそうです。
https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_lock_wait_timeout

mysql
mysql> set innodb_lock_wait_timeout = 30;

ちなみにOracleはクエリごとに設定できるって。

oracle
SQL> SELECT col FROM table WHERE col = 1 FOR UPDATE OF col WAIT 10 ;

なお他の方法とは異なり、二重サブミットされていることをアプリケーションレイヤで検知するのではなく
アプリケーションがおバカで二重サブミットを許容したとしても、DB側で弾くという方法なので
「二重サブミットされたらこういうハンドリングをしたい!」というニーズがある場合はもう一工夫必要になります。

2回目のリクエストはサーバまでは正常に疎通できるものの、GIF画像の通りDB接続で待たされるような挙動となるので
Java的に言うとSQLTimeoutExceptionあたりがthrowされます。
排他による想定通りのタイムアウトなのか、スロークエリやDB側不調によるタイムアウトなのか判別できないため
二重サブミットの防止はできても、二重サブミットの検知は他と比べると難しくなります。

まとめ

「処理を禁止する」という強い制御をかける話なので、どれも多少なりとも副作用があります。
またユーザビリティや処理難易度、アーキテクチャ等を考えると、どれか一案だけ採用して終わりということもないかなと思います。
この世から二重サブミットがなくなる日まで戦い続けるので、他にいい方法があったらぜひ教えてください。
あと記事投稿遅れまして誠に申し訳ございませんでした。。。。。。。。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Laravel 】「Laravel DB Designer 」を便利に使うための注意点「integer」について解説 Vol.4

今回は" Laravel DB Designer "を便利に使うための注意点について書きました(vol4)

「Laravel DB Designer」
もっと最初から詳しく知りたい人は以下リンクからどうぞ
以前のvol3の解説ページへ
以前のvol2の解説ページへ

e.jpg

https://laraveldb.com
※日英対応サイト

主機能

  • ER図作成 → 対応している”型”はvol2記事の下へ
  • ER図作成 → Migrationファイルを生成
  • ER図作成 → チェック用クエリービルダーを自動生成
  • Migrationファイル → ER図を生成(リバース)再設計可能

今回はカラム型:integer,int について

NG例
$table->integer("item_number", 10); 

LaravelのMigrationでは「integerの第二引数」にサイズを指定をするとエラーになる。

1.テーブル&カラムの作成

2.テーブル&カラムの作成

設計上「 INTEGER(6) 」「 INTEGER(3) 」とかのようにサイズを指定は可能です。

ここが本題!!

しかしLaravelのMigrationではintegerにサイズを指定するとエラーになるため、
「 Laravel DB Designer 」ではMigrationファイル生成する際、サイズ指定をER図で設計していても、Migrationでのサイズはスルーするような仕様になっています。

OK例
$table->integer("item_number"); 

エラーを回避するためサイズ指定していても、上記のようなコードが生成されます。
※最新のバージョンではそうなっています。

今回vol4のまとめ

LaravelフレームワークでのDB設計( Laravel DB Designer )は細かく詳細にというよりは、
大胆に、サクッと!!進めて、使用していくほうが楽そうですね!

【 Laravel DB Designer 】
https://laraveldb.com

以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Macに(L)AMP環境を構築してlaravelを動かした時のメモ

LAMP環境とは

Linux + Mysql + Apache + PHP の、webの一般的な構成。今回はLinux以外をインストールした。

参考

https://qiita.com/moroya/items/a0b7881c4b3d809c6f21
http://vdeep.net/homebrew-php-apache-mysql
https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09

Homebrew

基本的に今回は全てhomebrewでインストールした。
入ってない方はこちらから→ https://brew.sh/

PHPのインストール

$ brew install php

Apacheのインストール

$ brew install httpd
$ which apachectl
/usr/local/bin/apachectl

このパスが返ってこれば大丈夫。

Apacheの設定

$ vi /usr/local/etc/httpd/httpd.conf 

ポート番号を指定(80がデフォルトなのでそれに合わせる)

httpd.conf
Listen 80

ホストも合わせる。(コメントアウトされていれば外す)

httpd.conf
ServerName localhost:80 

ドキュメントルートを変更する。

httpd.conf
DocumentRoot "{フォルダパス}"

そのすぐ下も書き換える

httpd.conf
<Directory "{フォルダパス}"> #ドキュメントルートに合わせる。
Options FollowSymLinks
AllowOverride All #追加
Order deny,allow
Deny from all
</Directory>

mod_rewriteが効くようにする。下のコメントアウトを外す。

httpd.conf
LoadModule rewrite_module lib/httpd/modules/mod_rewrite.so

index.phpを実行できるようにする。

httpd.conf
DirectoryIndex index.php index.html

phpを実行できるようにする。
<IfModule mime_module>内に追記。

httpd.conf
AddType application/x-httpd-php .php

phpを使えるようにする。末尾に追記。
httpd.conf
LoadModule php7_module /usr/local/Cellar/php/7.4.1/lib/httpd/modules/libphp7.so

MySQLのインストール

$ brew install mysql

MySQLの設定

MySQLを起動

$ mysql.server start

パスワードを変更

$ mysqladmin -u root password 'パスワード'

セキュリティ設定

$ mysql_secure_installation

Y/nで選択する。1つ目(パスワードチェック)、2つ目(rootのパスワード)以外はY。

VALIDATE PASSWORD COMPONENT can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD component?

Press y|Y for Yes, any other key for No: n
Change the password for root ? ((Press y|Y for Yes, any other key for No) : n 
Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y

つかいかた

Apache

$ apachectl start
$ apachectl stop
$ apachectl restart

Mysql

mysql.server status
mysql.server start
mysql.server reload
mysql.server stop

mysql -u root -p #コンソール

おまけ:nodeのインストール

laravelでjs・sassをコンパイルするために。

nodeをインストールする nodebrew をインストールする。

$ brew install nodebrew
$ mkdir -p ~/.nodebrew/src #行わないと次がエラーになる
$ nodebrew install-binary stable
$ nodebrew ls #インストールされているnodeを表示
$ nodebrew use {使いたいバージョン} #有効化

あとは、 $HOME/.nodebrew/current/bin にパスを通して終わり。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6 マイグレーションファイルの記載ミスでマイグレートしてしまったらrollbackを使おう

目的

  • マイグレーションファイルの記載を間違え、マイグレートしてしまった時の対処法をまとめる

筆者がやらかしたこと

  1. マイグレーションファイルの記載をミスる。
  2. その状態でマイグレートしてしまう。
  3. データベースを確認したところ期待した方になっていない。
  4. 現在のマイグレーションファイルを削除する。
  5. 再度、マイグレーションファイルを作成し正式な記載にをした。
  6. マイグレートを実行した。
  7. データベースが期待したものになっていた。

間違えてマイグレートをしてしまった時はrollebaskをしよう

  • マイグレート後にDBに異変を感じたらすぐに下記のコマンドを実行してマイグレート前の状態に戻す。

     $ rails db:rolleback
    
  • そのあとでマイグレーションファイルの記載を確認、修正を行い再度、マイグレートを行う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自作WebサービスをXserverで公開するときの注意点と学び

1.はじめに

2020年1月から転職活動を始める予定なのですが、ポートフォリオとして作成した自作WebサービスをXserverというレンタルサーバーにアップしました。

その時の注意点や学びを書きますのでご参考になればと思います。

このような方にはぜひ読んでいただきたい内容になっています。
・プログラミングの勉強を始めてWebサイト、Webサービスを作成中の方
・作成したサイト、サービスをこれからサーバーで公開する予定の方
・サーバーで公開しようと思ったけどうまく出来ていない方

逆に
・現役バリバリのエンジニアの方
・すでに問題なく自作サイト、サービスをサーバーで公開済みの方
には正直見る価値はないかなと思います(笑)

ではご覧ください!!

2.自作サービスの内容

いきなりですが、作成したWebサービス兼ポートフォリオについて紹介させてください!

サービス名:KoSoDATE
サービス内容:子育てに関する情報共有サービス
使用言語:
・HTML
・css
・JavaScript(jQuery)
・PHP
・MySQL
製作期間:25日
製作時間:75h
フレームワークなし、フルスクラッチで作成しました。

こちらから見ることができますので、見ていただけたら幸いでございます。
「KoSoDATE」はこちら

3.Xserverへのアップ手順

Xserverへのアップ手順はkana。さん(@kgkgon)の記事でかなり詳しく説明されていてわかりすいので参考にして無事にできました。
kana。さん、とても有益な記事ありがとうございますm(__)m

しかし、私はソースファイルをXserver上にアップロードするときに一気にアップできるsshを上手く使うことができなかったのでWebFTPを使って1つずつアップしました。
(ちょっと面倒でしたが問題なくアップできました)

kana。さんの記事「WebサービスをXserverで公開する方法」はこちら

流れだけ簡単にまとめると以下の通りです。
1.レンタルサーバー(Xserver)を契約
2.独自ドメインを取得(今回はお名前.comを利用しました)
3.DNSサーバの設定(DNS:Domain Name System)
4.データベースをローカルからレンタルサーバに移行
5.ソースファイルをレンタルサーバーにアップロード

僕はここまででサーバー上で見ることができるようになりました。

4.Xserverで公開するときの注意点

無事にXserverで自作サービスを公開することができたのですが、つまづいたことやあらかじめ知っておいた方が良いと思うことがあるので今からあげていきます。

4-1.トップページとして表示するファイル名はindex.○○にする

Webサービスのドメインでアクセスしたときに最初に表示されるページ(トップページ)のファイル名は「index.○○」にしなければ表示されませんので要注意です。
Xserverの公式HPにも書いてます。

私はトップページをtoppage.phpという名前で作成してしまっていたので、
1.toppage.phpindex.phpに変更
2.他ページに入力しているtoppage.phpに遷移する処理のコード(header('Location:toppage.php');)を全てheader('Location:index.php');に変更
このような作業をするはめになりました・・・

なのでこれからWebサイトなりWebサービスを作ろうとしている方はトップページをindex.htmlなりindex.phpにすることをオススメします!!

プログラミングの学習教材では、何か作るときにだいたいindex.htmlが使われてて、「なんでindexなんやろ?」って思っていましたが、この時にその理由がわかりました。

4-2.PHPでDB接続する時のホスト名はMySQL設定のホスト名を入力する

Xserverにデータベースを移行したら、データベース接続設定を編集することになるのですが、ホスト名に注意です。

dbConnect.php
function dbConnect() {
    //DBへの接続準備
    $dsn = 'mysql:dbname=データベース名;host=ホスト名;charset=utf8';
    $user = 'ユーザーネーム';
    $password = 'パスワード';
    $options = array(
        //SQL実行失敗時にはエラーコードのみ設定
        PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
        //デフォルトフェッチモードを連想配列形式に設定
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        //バッファードクエリを使う(一度結果をセットを全て取得し、サーバー負荷を軽減)
        //SELECTで得た結果に対してもrowCountメソッドを使えるようにする
        PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
    );


    //PDOオブジェクトを生成(DBへ接続)
    $dbh = new PDO($dsn, $user, $password, $options);
    return $dbh;
}

入力するホスト名はXserverサーバーパネルのMySQL設定に記載されているホスト名にしてください。
私はサーバー情報に記載されてるホスト名を入力してしまい、何度も接続エラーになってしまいました。
※間違える人いないかもしれませんが、私は間違えましたので記事として残しておきます(笑)
image.png

4-3.プロトコルをhttps化する方法

Xserverで公開した場合、デフォルトではプロトコルはhttpになっていますのでhttps化する方法を紹介します。
こちらもXserverの公式HPに説明があるのでその通りにすれば簡単にできます。

ちなみにhttpとhttpの違いについてはサイバーセキュリティ.comさんの記事で以下の通り記載されております。

HTTPとは「Hyper Text Transfer Protocol(ハイパーテキストトランスファープロトコル)」の事です。ホームページのデータは文字だけでなく画像や動画などがあります。これらのデータをサーバとクライアントの間で通信するときの通信規約(プロトコル)がHTTPです。

HTTPに対してHTTPSには文字列の最後に「S」が付いています。この最後の「S」は「Secure(セキュア)」の「S」です。「Secure」という単語を辞書で調べてみると、
安全な、危険のない、安全で、安定した、心配のない、保証された、確実な、約束された、安心して、心配がなくて(secureの意味・使い方 - 英和辞典 Weblio辞書)
と意味が記載されています。IT業界では「セキュリティがしっかりしている」といった意味で使われている単語です。
HTTPSもHTTPと同じプロトコルの一つですが、HTTPSではSSL(Secure Socket Layer)というプロトコルが使用されています。厳密に言うと現在はSSLではなくTLS(Transport Layer Security)というプロトコルが使われています。SSLという言葉が長い間使われてきたため、現在でもSSLと呼んだり、あるいはSSL/TLSと呼ばれたりする状況になっています。

といった感じで書かれていますが、簡単にいうとhttpsの方がセキュリティがしっかりしているということです。

ユーザー登録をするようなサービスでは個人情報を扱うため漏洩するとかなりの問題となるのでhttps化しておくのが良いと思います。

5.おわりに

本記事では私が自作WebサービスをXserverで公開した時に感じた注意点と学びについて紹介させていただきました。
同じところでつまづいた人の問題の解決に少しでも役に立てれば良いなと思って書きました。

ご覧いただいた方は少しは参考になったでしょうか?

もし参考になった方がいましたら幸いです。
いいね、コメントなどしていただけたら嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む