狛ログ

2021年4月30日金曜日

【AWS】API Gatewayで超手軽にMockを作成する。

4月 30, 2021

オフィス狛 技術部のyuckieeeです。

Androidアプリの開発中に動作確認をしようとしたとき、データ取得先のAPIが未完成だったり、特定のレスポンスやエラー(メンテナンス画面にするなど)を返すように制御するのが難しい...
など、もやもやすることがありました。

で、、それならシミュレータとかMockとか呼ばれてるものを自分で用意しちゃえばいいじゃん!?
と、思い立ち、AWSのAPI Gatewayが提供する機能を使用して作成してみました。
これが意外と使える子だったので、今回は一番シンプルに使える「統合Mock」というサービスの使い方を紹介します!

Step1:API GatewayにAPIを登録する

何はともあれ、APIの外枠を作らないことには始まりません。
AWSの管理コンソールからAPI Gateway画面を開き[API作成]をクリックして、APIを作成していきます!

最初に、作成するAPIタイプの選択画面が表示されますので"REST API"の[構築]を選択してください。

次にAPIの作成画面が表示されますので適当にAPI名を指定してください。
この値はアクセスURLに反映はされませんが、変更が出来ないようなので、ご注意ください。
これでAPIの新規作成は完了です。

Step2:リソースとメソッドを作成する

APIの作成が完了するとリソース管理画面に遷移します。
この時点では、下図のとおり何も設定されていませんので、ここから設定を追加していきます。
作成開始前にイメージ共有しておいた方が分かりやすいと思うので、まずはMock化したいAPIの概要を決めておきます。
今回は以下のAPIレスポンスを代替する統合Mockを作ろうと思います。

 GET / https://www.yuckieee.com/book/search


上記APIの下線部がAWS Gatewayが自動発行するパスで置き換わる部分です。
そのため、book以降のパス部分を作成対象として進めていきます。

まずは[アクション]メニューから[リソースの作成]を選択し、上記に沿ってリソースを定義します。
ここで"リソース"と言っているのがアクセスするURLのパスとなります。
リソースは、ディレクトリ 構造での管理となっており、最上位を/(ルート)リソースと呼びます。
このルートリソースに子リソースを追加していく形でリソースを作成していきます。
今回は
/(ルート)リソースにbook子リソースを追加
bookリソースにsearch子リソースを追加
の順でリソースを作成します。

リソースの作成後、レスポンス返却させたいリソース(=今回はsearchリソース)を選択し、[アクション]メニューから[メソッドの作成]を選択します。
[メソッドの作成]クリック後、下図のように作成したいHTTPメソッドが選択可能になるため、今回は"GET"を選択し、右側の確定チェックボタンをクリックします。
確定チェッククリック後、メソッドのセットアップ画面が表示されます。
今回はAWSの統合Mock機能を使用するため、統合タイプから"Mock"を選択し[保存]をクリックします。
これでリソースとメソッドの作成は完了です。

Step3:マッピングテーブルを定義する

統合Mockが作成されると、下図のような定義画面が表示されます。

それぞれ、私視点での概要を軽く説明すると下記のとおりです。

タイプ 説明
メソッド
リクエスト
実際に受信したAPIリクエストに対して、入力値チェックやAPIキー指定などの設定が可能です。
統合
リクエスト
統合Mockエンドポイントに引き渡す値の設定を行います。
統合Mockの場合、ここで設定された値が統合レスポンスに引き渡されます。
統合
レスポンス
統合Mockエンドポイント(統合リクエスト)から受け取った値を元にレスポンスを定義します。ここでHTTPステータスやヘッダ、レスポンスBodyなどを設定します。
メソッド
レスポンス
統合Mockの場合、レスポンスに必要なHTTPステータスをここで設定しておく必要があるようです。

色々触ってみながら試していただくのが良いですが、 最もシンプルに使いたい場合は、統合レスポンスのマッピングテーブルを作成するだけ出来ちゃいます!

ということで、[統合レスポンス]を開きます。
統合レスポンスでは、統合リクエストから受信するHTTPステータスごとにマッピングテンプレートの設定が可能です。 新規作成段階では、統合レスポンスがデフォルトで1つ用意されており、統合リクエストで指定されたHTTPステータスに対応する統合レスポンスが存在しない場合、 この統合レスポンスのマッピングテンプレートがレスポンスされます。
※ただし、API Gateway自体がエラーとなった場合を除きます。

今回マッピングテンプレートに指定したい内容は以下のとおりです。
[ { "id": "1001", "title": "オフィス狛ものがたり", "Author": "yuckieee" }, { "id": "1002", "title": "狛ちゃん冒険譚", "Author": "yuckieee" } ]

デフォルトの統合レスポンスを順番に展開し、マッピングテンプレートのContent-Type:application/jsonにレスポンスしたい内容を入力します。
入力完了後、右下の[保存]をクリックして保存します。

 👉  統合リクエストと統合レスポンスのマッピングテンプレートの記述方法は
   「おまけ:リクエストによってレスポンス内容を切り替える」の章を参照してください

マッピングテンプレートの設定は以上で完了です。

Step4:動作確認(テスト)を行う

これまでの設定が問題ないか、デプロイまでにテストできる機能がありますので、これを使って動作確認してみます。 リソースの管理画面左にある[テスト⚡️]をクリックしてください。
メソッドのテスト画面が表示されますので、画面中央下部の[⚡️テスト]クリックで、先ほど定義した内容でAPIが呼び出されます。
[⚡️テスト]クリック後、結果が画面右側に表示されます。
この内容が実際にMockにアクセスした際に返却される値と同様の内容となっていますので、この段階でレスポンスに問題がないかを確認します。
もし、Internal Errorなどが返却されている場合は、設定をもう一度見直すか、ログ領域を参照しエラー内容を確認してください。

Step5:デプロイ(外部公開)する

テストで動作に問題がないことを確認したあとは、実際にデプロイしてみます。
[アクション]メニューの[APIのデプロイ]をクリックしてください。
APIのデプロイダイアログが表示されますので、デプロイするステージを新規作成します。
デプロイステージ名は、URLにも組み込まれますので分かりやすい名前にしておきましょう。
今回はdevとしておきます。
問題なくデプロイが完了すると、新規作成したステージのエディター画面に遷移します。 遷移先の画面に呼び出し用のURLが記載されていますので、このURLを使用してMockにアクセスが可能になります。

Step6:実行確認する

最後にPOSTMANを使用してリクエストが可能かを確認してみます。
POSTMANを起動し、該当のURLを指定して実行してみてください。下図のようにマッピングテンプレートに定義した値が想定通り返却されればOKです!
※APIクライアントツールが入っていない場合は、GETであればブラウザ直接指定でも確認可能です!
デプロイが出来ていない場合、下図のようにMissing Authentication Tokenエラーが返ってきますので再度デプロイしてみてください。
これでMockとして使用できるようになりました!
あとは参照元のAndroidアプリ側URLをMockに切り替えてLet's テスト〜♪♬♩

おまけ:リクエストによってレスポンス内容を切り替える

テストをしていると、HTTPステータスやレスポンスBODYの値をリクエストによって切り替えたくなりますよね。私もそうでした!!!!(最終的にはLambdaを使用しましたが)
簡単な切り替えであれば、統合Mock機能でも可能です。幾つか切り替え方法をご紹介しますので参考にしてみてください。

💡 マッピングテンプレート記述言語

マッピングテンプレートではApache Velocity Template Language (VTL)という言語が採用されているようです。 この言語仕様に沿って変数の定義やIF文、FOR文などの記述が可能です。
今回は、これらを使ってリクエストによるレスポンス内容の切り替えを行っていきたいと思います。
なお、ここでは言語自体の説明を省きますので、詳細は公式リファレンスを参照ください。

パターン1:HTTPステータスコードを切り替える

① 統合リクエストのマッピングテンプレート編集
HTTPステータスを切り替えたい場合は、統合リクエストのマッピングテンプレートを編集します。
デフォルトでは{statusCode: 200}が指定されていますが、リクエスト値によって切り替えたい場合は以下の要領で設定します。

👉 GETメソッド(クエリ文字使用)の場合
クエリ文字に"id"というパラメータを指定していたと仮定して記述しています。
例:/book/search?id=1001
#set($id = $input.params("id")) #if($id == "1001") { "statusCode": 200 } #elseif($id == "1002") { "statusCode": 400 } #else { "statusCode": 500 } #end

👉 POSTメソッド(リクエストBODY使用)の場合
リクエストBODYにidというパラメータを指定していたと仮定して記述しています。
例:{ "id": "1001" }
#set($id = $util.parseJson($input.body).id) #if( $id == "1001") { "statusCode": 200 } #elseif($id == "1002") { "statusCode": 400 } #else { "statusCode": 500 } #end

② メソッドレスポンスの追加
レスポンスのHTTPステータスにバリエーションを持たせたい場合、最初にメソッドレスポンスを追加する必要があります。メソッドレスポンス画面を開き[➕ レスポンスの追加]をクリックし、追加したいHTTPステータスコードを入力し、確定してください。
今回は400と500を追加してみました。
③ 統合レスポンスの追加
続いて、HTTPステータスごとの統合レスポンスを作成します。
デフォルトをHTTPステータス200で使用することにして、新しくHTTPステータス400と500用の統合レスポンスを作成します。統合レスポンス画面を開き[➕ 統合レスポンスの追加]をクリックして下記の要領で統合レスポンスを追加してください。

HTTPステータスの正規表現:
 統合リクエストから引き渡されるStatusCodeのうち、対象とする値を指定します。
 この値は正規表現での指定も可能なため、4\d{2}と書くことでステータスコード400番台全てを対象とすることも出来ます。

メソッドレスポンスのステータス:
 ②で作成したメソッドレスポンスを指定します。
 ※先にメソッドレスポンスを作成したのは、ここで指定させるためでした。

コンテンツの処理:
 デフォルト値であるパススルーのままで!
この段階で一度[保存]をして開き直すと、マッピングテンプレートの追加が可能になりますので、適当なレスポンス内容を設定してみてください。
その後、HTTPステータス500についても同様に設定します。
全ての設定が完了したあとに、再度STEP4~5で紹介した手順を実施いただけば完了です...!

【補足】動作確認時のクエリ文字/リクエストBODYの指定について
リソースに指定されたHTTPメソッドがGETなどクエリ文字指定可能なものである場合、メソッドテスト画面にクエリ文字の入力エリアが表示されます。
同様にPOSTなど、リクエストBODY指定可能なメソッドの場合は、リクエスト本文の入力エリアが表示されます。(注:リクエスト本文は、GETメソッドの場合は非表示となります)

パターン2:レスポンスBODYを切り替える

HTTPステータスは200のままで、リクエスト内容によってレスポンス内容を変えたい!!!
というパターンのご紹介です。
なお、クエリ文字を使用する場合は、パターン1のHTTPステータスでご紹介した$input.params("id")が、統合レスポンスでも使用できるため説明を省略します。

① 統合リクエストのマッピングテンプレート編集
この手順はリクエストBODYを統合レスポンスで使用したいときだけ使用します。

👉 POSTメソッド(リクエストBODY使用)の場合
デフォルトでは、リクエストBODYは統合レスポンスに渡されません。そのため、統合リクエストで明示的にオーバーライドしてあげる必要があります。
なお、statusCodeは必ず統合レスポンスに送る必要があるため、削除しないようご注意ください。
#set($context.requestOverride.path.body = $input.body) { "statusCode": 200 }


② 統合レスポンスのマッピングテンプレート編集
👉 POSTメソッド(リクエストBODY使用)の場合
リクエストBODYにidというパラメータを指定していたと仮定して記述しています。
例:{ "id": "1001" }
#set($body = $context.requestOverride.path.body) #set($id = $util.parseJson($body).id) #if($id == "1001") { "id": "1001", "title": "オフィス狛ものがたり", "Author": "yuckieee" } #elseif($id == "1002") { "id": "1002", "title": "狛ちゃん冒険譚", "Author": "yuckieee" } #else { "message":"該当の書籍情報が存在しません" } #end

なお、この辺りの話はAWS公式のデベロッパーガイドに記述されていますので、こちらも参考にしていただければと思います。
AWS公式:API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス

おまけ:APIGatewayの料金

今回使用したREST APIでは、受信したAPIコールと、転送データ量に対してのみ料金が発生します。

APIコール料金:
2021/4/13時点で、月間のリクエスト数が3億3,300万件まで100万リクエストにつき4.25USD(アジアパシフィック(東京))のようです。
※なお、キャッシュを利用する場合は、更に料金がかかりますが、テスト・開発で使用する分には必要のない機能かと思います。

データ転送料金:
2021/4/13時点で、10TBまで1GBあたり0.09USDのようです。
なお、対象はAPIGatewayから、インターネット方向のデータ(レスポンスデータ)のみが対象となります。
どちらもリクエスト数、転送データ量が多い場合は、レンジによって割引されます。

ただ、ある程度使い倒したとしても、テスト利用の範囲であれば数百円程度でしょうか。
Mockやシミュレータってどうすりゃいいのか...と、小1時間頭を抱えている工数分で解決できるなら、試してみるのも良さげです!
AWS公式:Amazon API Gateway 料金

まとめ

APIGatewayの統合Mock機能で手軽にMockやシミュレータが作成できるのが分かりました!
次回は、APIGatewayをLambdaやS3と連携して一歩進んだMock作成をご紹介できればと思います!

2021年4月26日月曜日

express-requests-loggerでリクエストログの特定の項目をマスクする方法。

4月 26, 2021

オフィス狛 技術部のJoeです。

担当しているプロジェクトで、Node.js と Express でAPIを作成して、
リクエストのログを出力しているのですが、
セキュリティの観点から、一部リクエストの値をマスクする必要がありました。

express-requests-loggerで、マスクする方法をまとめました。

【バージョン情報】
・node:v12.14.0
・express-requests-logger:v3.0.3

■express-requests-logger のインストール

express-requests-loggerをインストールします。
npm install express-requests-logger

■ログ出力設定

app.jsにexpress-requests-loggerの設定を追加します。

※loggerで指定している「httpLogger」ついては、こちらの記事を参考に設定いただければと思います
Node.js + Express + log4jsで、アクセスログとリクエストログを取る。

const audit = require('express-requests-logger');

// ログ設定
app.use(audit({
  logger: httpLogger,
  request: {
    maskBody: ['password'],
  },
  response: {
    maskBody: ['token'],
  },
}));

上記のように設定しますと、リクエストとレスポンスの内容がログに出力されますが、
リクエストBodyの「password」と、レスポンスBodyの「token」の値が「XXXXX」にマスクされます。

【ログ出力結果】
[2021-04-16T11:04:56.407] [INFO] http - {
  response: {
    status_code: 200,
 (中略)
    body: '{"token":"XXXXX"}'
  },
  request: {
    method: 'POST',
 (中略)
    body: '{"loginId":"test","password":"XXXXX"}'
  },
  'millis-timestamp': 1618538696407,
  'utc-timestamp': '2021-04-16T02:04:56.407Z',
  stage: 'end'
} Inbound Transaction


他にも、項目自体を除外するオプションなどもありますので、一部ご紹介します。
詳細は「express-requests-logger」のホームページをご覧ください。
express-request-logger

【オプション】
■doubleAudit
・true:リクエストを受けたタイミングで出力(リクエスト)、
レスポンスを送信したタイミングで出力(リクエストとレスポンス)します。
・false:レスポンスを送信したタイミングで出力(リクエストとレスポンス)します。

■excludeURLs
配列で指定した文字列が、リクエストURLの一部と一致する場合は、リクエストもレスポンスも出力しません。

【オプション(リクエスト)】
■audit
・true:リクエストを出力します。
・false:リクエストは出力しません。

■excludeBody
配列で指定した文字列と一致する項目は、bodyに出力しません。
※「*」を指定すると、bodyの全ての項目を出力しません。

■maskQuery
配列で指定した文字列と一致する項目は、queryにマスクして出力します。

■excludeHeaders
配列で指定した文字列と一致する項目は、headersに出力しません。
※「*」を指定すると、headersの全ての項目を出力しません。

■maskHeaders
配列で指定した文字列と一致する項目は、headersにマスクして出力します。

■maxBodyLength
bodyに出力される最大文字数を指定できます。

【オプション(レスポンス)】
レスポンスもリクエストと同様のオプションで、
Body、Headersの項目について、マスクや除外することができます。


少ない設定でマスクすることができますので、機会があればお試しください。

SQL Serverのトランザクションログについて(記録される内容、領域の解放、切り捨て)

4月 26, 2021

オフィス狛 技術部のJoeです。

先日、担当していたシステムで、SQL Serverのトランザクションログが肥大化し、
ストレージが空き容量不足になる事象が発生しました。
その対応にあたり、SQL Serverのトランザクションログについて
調査する機会がありましたので、簡単ですがまとめてみました。

※下記の内容はSQL Server 2017で確認しています

■SQL Serverのトランザクションログには何が記録されるのか?

SQL Serverはデータベースに設定されている、復旧モデルによって異なります。

※復旧モデルは下記で確認できます。
SELECT name, recovery_model_desc FROM sys.databases WHERE name = 'model';

・単純モデル
トランザクション開始からコミットまでが、トランザクションログに記録されます。

・完全モデル
全ての操作(DDL、DML)がトランザクションログに記録されます。
インデックスの操作など、大量のデータが読み込れると、大量のログが出力されます。

・一括ログモデル
完全モデルと似ていますが、一括操作(bcp、BULK INSERT など)の場合に、使用されるログ領域を縮小します。
インデックス操作(CREATE INDEX)も、最小ログ記録の対象(※)になるようです。
※下記の「最小ログ記録が可能な操作」を参照ください
トランザクション ログ (SQL Server)

■トランザクションログの領域は、いつ解放されるのか?領域を使い切るとどうなるのか?

こちらも「復旧モデル」によって異なります。

・単純モデル
チェックポイント(トランザクションログファイルに書き込み完了のタイミング)で解放されます。

トランザクションログは既存の領域を自動的に再利用しますので、ログ領域の管理は基本的に不要です。
領域を再利用するので、トランザクションログをバックアップすることはできません。

・完全モデル、一括ログモデル
トランザクションログのバックアップで解放されます。

トランザクションログファイルは領域を使い切ると、自動拡張の設定をしていれば、
トランザクションログファイルの上限サイズ(※)まで、物理サイズを自動拡張します。
(もちろんストレージの空きが無ければ拡張できません)
※最大、2TBまで設定できます

トランザクションログファイルは一度拡張されると、
自動縮小のオプションを有効にしていない限り、
自動で物理サイズは縮小されませんので、ストレージは解放されません。

ストレージを解放するには、下記でトランザクションログファイルを縮小し、
物理ファイルサイズを減らすことで、ストレージの空き容量を増やすことができます。
use [データベース名];

-- トランザクションログ論理名を確認
SELECT name FROM sys.database_files where type = 1;

-- トランザクションログファイルを縮小
DBCC SHRINKFILE( [トランザクションログ論理名] , [縮小後の容量(MB)]);
※縮小後の容量の単位はMB(100を指定すると100MB)なので、ご注意ください


■トランザクションログを、手動で切り捨てる方法

ストレージの容量不足や、バックアップを取得する時間が無い場合などに、
手動で切り捨てることができます。

※トランザクションログの使用状況は下記コマンドで確認できます。
DBCC SQLPERF('LOGSPACE');

【方法①】トランザクションログの削除
下記で削除できます。
注意点としては、削除前に一度、データベースの完全バックアップを取得している必要があります。
(取得してい場合はエラーになります)
BACKUP LOG [データベース名] TO DISK = N'NUL';

【方法②】データベースの復旧モデルを「単純モデル」に変更する
下記で変更できます。
注意点ですが、例えば完全モデルから単純モデルに変更する場合、
完全モデルのみで使用できる機能を使用していると、単純モデルへ変更できないようです。
※以前、AWS RDSで、マルチAZ「あり (ミラーリング)」が設定されている完全モデルのデータベースは、単純モデルに変更することができませんでした
ALTER DATABASE [データベース名] SET RECOVERY SIMPLE;


以上です。
トランザクションログで何か問題があった際に、お役に立てば幸いです。

2021年4月23日金曜日

2021年追加版、AdobeFontおすすめフォント三選。

4月 23, 2021

こんにちは、オフィス狛 デザイン部のSatoです。


2021年4月8日に、AdobeFontsに日本語フォントが191も増えたそうです。(4月10日がフォントの日なので、そこに合わせてきたんですね)

既存のものも合わせると、AdobeFontsに日本語のフォントは436フォントもあるらしいです!(使いきれない!)

Adobe Fontsが大幅拡充!日本語フォントラインナップが強化されました

ラインナップを見ていると見覚えがある文字もチラチラ…。

今回はAdobeFontsの個人的推しフォントを3つご紹介しようと思います。



1.ABキリギリス

ABキリギリスは雑誌やお菓子パッケージでよく見るフォントです!

個人的には「え?あのキリギリスがAdobeFontsに!?」と驚きました!嬉しい〜!

ほぼ打ちっぱなしでも、良い感じになりますね

クラフト感や異国感を出したい時に使いたいフォントです。

漢字の収録が少ないですが、それでも素敵なフォントです。

ちなみに「きりぎりす」という、こちらのフォントのバランスを修正+収録文字が追加されたフォントもありますが、AdobeFontには入っていません!「キリギリスじゃ漢字が少ない!」と感じた方はこちらを買ってみてはいかがでしょうか?



2.TA重ね丸ゴ

文字の重なりがレトロでノスタルジーなフォントですね。

Googleで調べた所、アニメの字幕に使われていたらしいですね。

憧れの「スーボ」に似た印象を受ける文字なので一目惚れしていまいました。

平成初期くらいの雑誌や、本の見出しを思い出しませんか?

使い所は難しいですが、かわいい・ポップな印象を出したい時に使えそうですよね!



3.AB好恵の良寛さん

さらさらと小筆で書いたような手書き風フォントです。

女性っぽい字だと勝手に思っていたのですが、良寛という男のお坊さんが書いた臨書を元にしたフォント……との事です。

シンプルで細身なフォントなので筆文字!という迫力が少なくて色々な手書きフォントが必要な時に使えそうですね。

夏のお中元の広告や、ナチュラルなイメージにしたい時に使えそうだと思いました。




番外編.VDL メガ丸

今年追加されたものではないですが、このフォントも紹介させてください(みなさん知ってますかね?)

丸くて抜けてる印象を受ける可愛いフォントなのにきちんと読みやすい!

萌え〜なアニメゲーム、などのかわいい系のサブカルチャーとの相性が良さそうですよね。

SQL Server のデータ移行方法と特徴について。

4月 23, 2021

オフィス狛 技術部のJoeです。

担当したプロジェクトで、SQL Serverのデータを移行する要件がありました。
データの移行方法を調べてみると色々な方法がありましたので、
いくつかの移行方法と、その特徴をまとめてみました。

1.デタッチとアタッチ

【主な手順】
移行元でデタッチ
-> データファイルとトランザクションログファイルを移行先にコピー
-> 移行先でデータファイルをアタッチ

【特徴】
・データファイルとトランザクションログファイル自体を移行します。
 (実質、データベースの移行になります)
・デタッチすると、DBが参照できなくなります。
 (アタッチし直せば、また参照可能になります)
・SQL Serverのバージョンアップにも対応しています。
・デタッチには、いくつかの制限事項があります。(下記の「データベースのデタッチ」を参照)
 https://docs.microsoft.com/ja-jp/sql/relational-databases/databases/database-detach-and-attach-sql-server?view=sql-server-ver15
・後述する移行方法と比較すると、移行に掛かる時間は最も短いです。

2.バックアップとリストア

【主な手順】
移行元でバックアップ
-> バックアップファイルを移行先にコピー
-> 移行先でリストア

【特徴】
・バックアップファイルを作成して移行します。
 (実質、データベースの移行になります)
・DBを停止しなくても移行可能です。
・SQL Serverのバージョンアップにも対応しています。
・リストアの際に、DBとデータファイル名を変更することで、DB名の変更が可能です。
 同じインスタンス内であれば、DB名を変更することで、複製することもできます。

3.ExportとImport

【主な手順】
Data Source設定 -> Destination設定
 ※SQL Server Management Studio の「Import and Export Wizard」を利用した場合です

【特徴】
・移行先から直接移行先へデータを移行可能です。
・DBを停止しなくても移行可能です。
・Destinationに「Flat File Destination」を指定すれば、移行元のデータをファイル化することができます。
 ただし、テーブル単位でしかファイルを作成できませんでした。
・「Import and Export Wizard」は機能が多く便利なのですが、逆に使いこなすことが難しいと感じました。
一度、「Import and Export Wizard」でデータ移行を試してみたところ、下記エラーが発生しました。
Error 0xc02020f4: Data Flow Task 1: The column "カラム名" cannot be processed because more than one code page (932 and 1252) are specified for it.
こちらは、Import と Export するテーブルの照合順序(Collation)が異なっていたことが原因でしたが、
メッセージから原因が予測できず、解決するのに少し時間が掛かってしましました。

4.スクリプト作成と実行

【主な手順】
移行元でスクリプト作成
-> スクリプトコピー
-> 移行先でスクリプト実行
 ※SQL Server Management Studio の「Generate and Publish Scripts」を利用した場合です

【特徴】
・スクリプトファイルを作成します。
・DBを停止しなくても移行可能です。
・スクリプトファイルは、スキーマのみ(CREATE DATABASE、CREATE TABLE 等)、
 データのみ(INSERT)や、スキーマとデータ両方など、用途によって作成できます。
・SQLが直接書かれたファイルなので、大量データの場合はファイル容量が大きくなり、
 スクリプトを実行する際も、時間が掛かってしまいます。
・逆に、SQLを直接修正(スキーマの変更や、データの編集)できるので、
 データ量が少ない場合は扱い易いです。


データ移行は、移行時のシステムの稼働状態やデータ量などによって、
適切な移行方法を選定する必要がありますので、上記の特徴が何かお役に立てれば幸いです。

2021年3月30日火曜日

Swift・UIBarButtonItemのtitleを変更する。

3月 30, 2021

こんにちは、オフィス狛 技術部のpinoです。


この度、初めてiOSアプリの開発に携わることができました。

まだまだ勉強中の身ですが、今回は「UIBarbuttonItemのtitleを変更する方法」について紹介したいと思います!


方法

早速ですが、UIBarbuttonItemのtitle変更方法です。

    private func setBarButtonItem() {
        // UIBarbuttonItemのactionを設定
        let button = UIBarButtonItem(title: "タイトル", style: .plain, target: self, action: #selector(self.buttonTapped(_:)))
    }

    @objc func buttonTapped(_ sender: UIBarButtonItem) {
        // 変更後のタイトルを設定
        sender.title = "変更後のタイトル"
    }

タップ時の処理でUIBarButtonItemを引数にとって、
(仮引数名がsenderの場合)sender.title = "hoge"としてあげることでtitleの変更ができます。


...思いのほか、すごく簡単でしたね!
せっかくなので、ここからはこのtitle変更を活かしたものを作ってみたいと思います。


作るもの

以下のようなものを作っていきます。

  1. ・画面真ん中にTextFieldを配置
  2. ・TextField押下でキーボード表示。このキーボードの上にUIToolbarを配置
  3. ・UIToolbar上に並んでいるUIBarButtonItemタップで、キーボードタイプを切り替え

UIBarbuttonItemのtitle変更は、「UIBarButtonItemタップ」で発火するようにしてみます。


準備

Interfaceに「Storyboard」を選択して、プロジェクトを作成します。


画面真ん中にTextFieldを置いて、Keyboard Typeを「Number Pad」にしておきます。


ViewControllerで、TextFieldにUIToolbarを追加します。
ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var sampleTextField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.setToolbar()
    }
    
    private func setToolbar() {

        let toolbar = UIToolbar()
        toolbar.sizeToFit()

        let inputSwitchButton = UIBarButtonItem(title: "英数字入力に切り替え", style: .plain, target: self, action: nil)
        let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)

        toolbar.items = [space, inputSwitchButton]

        self.sampleTextField.inputAccessoryView = toolbar
    }
}


ここまでで、「英数字入力に切り替え」というtitleのUIBarButtonItemが表示されるようになりました。


UIBarbuttonItemのtitle変更

UIBarButtonItemタップ時の処理を定義します。

ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var sampleTextField: UITextField!
    // 中略
    private func setToolbar() {

        let toolbar = UIToolbar()
        toolbar.sizeToFit()

        let inputSwitchButton = UIBarButtonItem(title: "英数字入力に切り替え", style: .plain, target: self, action: #selector(self.inputSwitchButtonTapped(_:)))  // 🍀変更
        let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)

        toolbar.items = [space, inputSwitchButton]

        self.sampleTextField.inputAccessoryView = toolbar
    }

    // 🍀追加
    @objc func inputSwitchButtonTapped(_ sender: UIBarButtonItem) { 
        sender.title = "数字入力に切り替え"
    }
}

inputSwitchButtonTapped()を定義して、その中でtitleの変更を行うようにしました。

さらに、inputSwitchButtonの#selectorに inputSwitchButtonTapped()を設定することで、
ボタンタップ時にtitleが「数字入力に切り替え」へと変更されるようになります。

ここまでで、実際の動きは以下のようになります。

1回タップした後は、特に変化がありませんね。

今回はtitleと一緒にKeyboard Typeも変更したいので、inputSwitchButtonTapped()を以下のように修正します。

    @objc func inputSwitchButtonTapped(_ sender: UIBarButtonItem) {
        // キーボードが表示されたままだとKeyboard Typeの変更が反映されないのでフォーカスを外す
        self.sampleTextField.resignFirstResponder()
        
        // 🍀以下の分岐を追加
        if self.sampleTextField.keyboardType == .numberPad {
            sender.title = "数字入力に切り替え"
            self.sampleTextField.keyboardType = .namePhonePad
        } else if self.sampleTextField.keyboardType == .namePhonePad {
            sender.title = "英数字入力に切り替え"
            self.sampleTextField.keyboardType = .numberPad
        }
        
        // Keyboard Typeを切り替えた後に再度フォーカスする
        self.sampleTextField.becomeFirstResponder()
    }

(フォーカスのつけ外しについては、コメントの通りです)
現在のKeyboard Typeを判定して、titleとKeyboard Typeが切り替わるようにしました。

完成!

完成形は、以下のようになります。




以上、UIBarbuttonItemのtitleを変更する方法について紹介しました。
参考になるとうれしいです。

2021年3月29日月曜日

windows.openを使用した時に、iOSのSafariにて別タブでファイルを開けない場合の対処法。

3月 29, 2021

オフィス狛 技術部のmmm(むー)です。

タイトルの通りですが、windows.open()を使用した際にiOSのSafariにて別タブでファイルを開くことができなかったため、その対処法を書きます。

業務でしたかったこと
・フロント(Angular)にて、リンクを押下した後PDFを別タブで開く
 1. リンクを押下後、APIを呼ぶ
 2. API(Express)で、S3にあるPDFデータ(Blob)をレスポンスする
 3. フロントにてAPIからのレスポンスを取得した後、別タブを開きPDFを表示する

問題点
・iOS(Safari)だけリンクを押しても何も反応しない
・PCブラウザ(Chrome、 Firefox、 Sarfari)やiOSブラウザ(Chrome)はリンク押下後に別タブでPDFを開くことができる

原因
・Safariでは、非同期処理内でwindows.opon()を実行すると呼び出しをブロックするため
observable.subscribe(downloadData => {
	  const blob = new Blob([downloadData], { type: 'application/pdf' });
	  const fileURL = window.URL.createObjectURL(blob);
    window.open(fileURL, '_blank');
})

解決方法
・非同期処理が始まる前に、windows.open() を実行するように変更する
this.windowReference = window.open();  // <- 🌟変更

observable.subscribe(downloadData => {
	  const blob = new Blob([downloadData], { type: 'application/pdf' });
	  const fileURL = window.URL.createObjectURL(blob);
    this.windowReference.location = fileURL;  // <- 🌟変更
})

その他
・iOSのSafariはデフォルトの設定で、ポップアップブロックがONになっているので、OFFにすることで開けるようになります。
・ただ、別タブで開くか確認するポップアップが表示されますし、デフォルトはONになっています。
設定変更方法:iPhoneの設定 > Safari > ポップアップブロックをOFF

以上となります。
同じようにはまった方の参考になりましたら、幸いです🍭