新着記事

矢印キーを押すとスクロールしてしまう時(Excel対処法)


こんにちは、品質管理係の渡辺です。

Excelで作業中に、突然、矢印キーによるセル間のカーソル移動ができなくなり、なぜか表全体が上下にスクロールしまい、困ったことがあります。
作業データに影響はないですが、矢印キーでセル間を移動できないと、微妙に不便でした。
その時の原因は単純にショートカットキーのコピーペーストのコマンドキーを押し間違えて、ScrollLock機能がONになってしまっていたことでした。

ScrollLock機能とは

Excelでは、表ページをスクロールさせたい時にマウスやタッチパネルではなく、キーボードの矢印キーで操作できる機能があります。
これを「ScrollLock機能」といいます。この機能を使用すると、マウスを持たなくても、下矢印キーを押せば、ページを動かせるので、便利です。

ScrollLock機能のNO/OFFの確認

【1】ステータスバーで確認する

Excelの下部にあるステータスバーにScrollLockの表示があれば、ONとなっています。



【2】ステータスバーのメニューから確認する

ステータスバーに表示がない場合は、ステータスバーにマウスを合わせて、右クリックすると、ステータスバーのメニュー設定が表示されます。
その中の「ScrollLock」の項目があるので、右端を確認してください。「オン」の表示であれば、ScrollLock機能がON状態です。


また、左端のチェックは、ステータスバーにScrollLockの状態を表示させるかの設定です。
こちらにチェックがあると、ON状態時は、ステータスバーにScrollLock」と表示されます。
なお、このメニューからScrollLock機能をオン・オフの切り替えはできません。



ScrollLock機能の解除方法

【1】キーボードを使用する

ディスクトップパソコンや機能キーが充実しているキーボードには「Scroll Lock」または「SCR LK」ボタンが備えられているものが多いです。
こちらのボタンを押すとON/OFFに切り替わります。

【2】スクリーンキーボードを使用する

備え付けのキーボードがない場合は、Windows標準の「スクリーンキーボード」を表示する方法もあります。
画面下にある検索バーに「スクリーンキーボード」と検索すれば、アプリのスクロールキーボードがあるので、そちらをクリックして表示させましょう。

画面に表示されたスクリーンキーボードの右側に機能キーがありその中に「ScrLK」キーがあります。
ScrollLock機能がON状態であれば、色が変わっているので、そちらをクリックすると、色が戻り、機能がOFFになります。

【3】ショートカットキーを使用する

こちらは、パソコンのメーカーや外付けキーボードにより、ショートカットキーの組み合わせが異なる場合がありますが、「Fn」とどれかのキーで直ることが多いです。

メーカー ショートカットキー
HP Fn + C
NEC LAVIE Fn + C
DELL Fn + S
ASUS Fn + End
Lenovo Fn + C
Fn + K
Dynabook Fn + F12

まとめ

私の場合は、HPを利用しているので、コピーの時に「ctrl」+「C」を押したつもりが、「Fn」+「C」を押していたようです。
Excel作業をする前に、どのショートカットキーでScroll Lock」機能がON/OFFになるか、一度試してみると良いかもしれませんね。
また、Scroll Lock」機能も、作業する時にとても役立ちました。使い分けができるようになると、さらにExcelの技術が向上するかもしれませんね。

参照サイト
Office Hack 「知ると便利なExcelの画面構成と各部の名称」 https://office-hack.com/excel/screen-structure/

スマートロック、君はどれを選ぶ?


おはこんばんにちは、四斗邊です。
これまで弊社ではさまざまなスマートロックを活用してきました。
そこから最終的にどれを使っているのか、なぜ他ではダメだったのかを今回ご紹介できればと思っています。

ちなみにこれまで弊社で活用してみたスマートロックは以下の通りです。
  • QrioLock(Q-SL1)
  • SESAME4(セサミ)
  • Switch bot
  • bitkey(bitlock pro)
で、結論からいうと現状SESAMI4とbitlock proを弊社では活用しています。
なぜそれに至ったかは、弊社がスマートロックに求めるいくつかポイントがありまして・・・。
  • 登録
    100人規模の登録ができること
  • 管理
    マネージャー権限など管理がしやすいといい。できればゲストとマネージャーの差別化はしてほしい
  • 価格
    コストパフォーマンスがよいこと 1万円切れると高得点
  • セキュリティ
    オートロック機能の有無、(時限性と開閉センサータイプとあるが、開閉センサーが欲しい)
  • 反応速度
    開閉時間がボタンを押したあと5秒以内であること。ボタン押したらすぐ開いて欲しい。
これらのポイントからスマートロックを評価して現状使っているという感じです。
まずは現在使用している二つの機種からご紹介。

SESAME4(セサミ)


安い!スマートロックを手軽に使えて最低限の機能を押さえている、これ買うとスマートロックの沼にハマります。

評価
  • 登録:登録者数はなんと無制限 5
  • 管理:マネージャー権限あります。さらにオーナー権限もあるのでとても管理はしやすい 5
  • 価格: 4,000円!安い! 5
  • セキュリティ、オートロックはあるが、開錠後一定時間が経つと施錠する時限式オートロック、遠隔での操作も可能 3
  • 反応速度:早い!2秒ぐらいで開閉してくれるのは素敵 5
オートロック以外満点です。
この価格でスマホとの連携、リモート操作、管理権限の付与など家庭用としては勿体無い。
ただ安価なので弊社ではすでに三台目になっています。錠に直接当たる部分など劣化は激しい。
個人的にも使っていましたが、唯一の欠点オートロック部分で最終的にはswitch botに変えました。
補足:管理画面はスマホアプリのみです。

bitlock pro(bitkey)


弊社で契約している業務用スマートロック、実際にbitkey本社にお邪魔して筆者が体感してすこぶるよかった。

評価
  • 登録:登録者数は制限がないが、アカウント単位で金額が変わる 5
  • 管理:もちろん、管理しやすい!アプリ上ももちろんだがどちらかというとブラウザ上での管理がしやすい 5
  • 価格: 1台あたり月額5,600円/年額67,200円 うーん、さすが業務用、それでもakerunよりは安かったかな。 3
  • セキュリティ:開閉センサーあり!ネットワークにも接続できて遠隔管理も可能、さらにオートロックする時間帯を選べちゃう 5
  • 反応速度:早い!2秒ぐらいで開閉してくれるのは素敵 5
さすが業務用、痒いところにも手が届くし、弊社は社員カードにfelicaを搭載しているのですが、felica登録などもしやすいです。
金額以外は、死角ないけど、業務用のスマートロックとしてはお値段がお手頃なので、業務用に困っている場合は、bitkeyをお勧めする。



Felica開錠のレスポンスも早い!
補足:管理画面はブラウザ上とスマホアプリで管理できます。
以上のことから、最終的にはsesami4とbitlock proを活用しています。

sesami4安すぎるんだよね・・・。
あの性能で、樋口さん切ってるのすごいわ・・・。

では、後半は実際に購入したり、検討したが、結局使わなかったスマートロックの一覧も評価しています。
そんなおまけ的な項目です。

Qriolock(Q-SL1)


旧型ですが、弊社で一番最初に使っていたスマートロック、アカウント登録も無制限でなかなかよかったです。

評価
  • 登録:登録者数無制限 5
  • 管理:マネージャー権限あるが、マネージャー権限を剥奪できない 3
  • 価格: 17,000円ぐらい 3
  • セキュリティ、オートロックはあるが、開錠後一定時間が経つと施錠する時限式オートロック 2
  • 反応速度:遅い。開閉に5秒超えたらストレス 1
ある程度利用後、SESAMI4に交換しました。
反応速度が遅いのがやはり良くなかったです。それ以外は十分使い方によっては重宝できるスマートロックです。
補足:管理画面はスマホアプリのみです。

Qriolock(Q-SL2)


購入検討まで進んだqrioの後継機種

評価
  • 登録:登録者数はなんと20名・・・。 1
  • 管理:マネージャー権限あります。管理はしやすい 5
  • 価格: 20,000円ぐらい 2
  • セキュリティ:オートロックの開閉センサーがあって非常に優秀ただ、夕方など時間帯などでオートロックして欲しい時間などは決めれない。ただ遠隔操作たまに切れてる 4
  • 反応速度:早い!2秒ぐらいで開閉してくれるのは素敵 5
この機種は個人的にも使っている機種だったのですが弊社では購入見送りとなった機種です。ただ家庭用としてはハイスペックです。
開閉速度も早いし、開閉センサーがあってオートロックしてくれるし、ネットワークに繋げば開閉通知してくれる。
ただ気づいたらqrio hubとの連携が切れてオフラインモードになっていることが多いので、最終的にはbluetoothでの開錠がメインになります。
家庭用としては比較的に優秀なんですが弊社では購入に至らなかった。
登録者数が20名が限界だったのはかなり致命的だった。
最低限のスペック要件として100人規模でも登録できるのが理想です。
法人プランも存在するが、、、コストパフォーマンスは下がってしまって結果選択しませんでした。
重要な部屋などの限定的なスマートロックとして、bitlock proと比較して検討した一台でした。
補足:管理画面はスマホアプリのみです。

Switch bot


これも安い!qrioに比べて安価でかつ、qrioと同等性能、qrioとSwitch botかと言われたらSwitch botを推す。

SESAMI4は入門編、Switch botはもう少し凝ったことがしたい人向けですね。

評価
  • 登録:登録者数はなんと無制限 5
  • 管理:マネージャー権限など有るが、ゲスト権限との大差がないので残念 3
  • 価格: 10,000円!スマートロックとしては安い 4
  • セキュリティ:開閉センサーあり!ネットワークにも接続できて遠隔管理も可能 4
  • 反応速度:早い!2秒ぐらいで開閉してくれるのは素敵 5
管理権限周りがいささか不満だが、業務上以外であればすこぶる優秀、我が家の主力スマートロックです。
業務上、限定的な箇所であれば活かせそうだとは考えていまして、情シス内の重要な場所に設置を検討しています。
またSwitch botは、人感センサーなど他の商品との連携もできるので、スマートプラグなどアプリを統一したい場合などはとてもいいのではないでしょうか?
補足:管理画面はスマホアプリのみです。


おわりに


業務上やご家庭の用途にも異なると思いますが、この記事を見てくれた人が「スマートロック導入してえ!」ってなってくれたら嬉しいです。

サイトページの修正確認はスーパーリロード/ハードリロードで行いましょう

keyboard

こんにちは、品質管理係の渡辺です。
品質管理係は、発見した不具合箇所が改修されたかの再検証もお仕事のひとつとして行っています。
修正の連絡を受けてから、ページにアクセスして確認をしますが、不具合箇所が変わっていない時があります。

そんな時は「キャッシュ」が残っている可能性を考えて「キャッシュクリア」を行います。
大抵は、この操作で、古いデータが削除されるので、改修箇所の再検証が行えます。

さてここで「キャッシュ」とは何か疑問に思いますよね。
簡単に伝えると、アクセスしたサイトページのデータを一時的に保管することで次回同じページにアクセスした時にブラウザが保管データを用いて表示する仕組みのことです。この仕組みにより、ページ表示が高速化され快適に閲覧することが出来るのです。
ブラウザに「キャッシュ」が残った状態の場合は、保管している『キャッシュ』を優先的に表示するため、反映されないことがあるのです。
解決するには再読み込み(リロード)をしてWebページの最新データを取りに行く作業が必要になります。

「通常の再読み込み」
閲覧しているページで、最後までページが表示されない、画像が表示されない等のトラブルの対処法として、ブラウザの更新ボタンやキーボードのF5、ショートカットキーを押して、リロード(再読み込み)させる、という対処は一般的だと思います。
ただし、この通常の再読み込み方法では、更新された画像やキャッシュクリアはされません。


「スーパーリロード」「ハードリロード」
こちらの2つのリロードは同じ作用であり他にも 「強制再読み込み」「フルリロード」「強制リロード」などとも呼ばれます。
スーパーリロードという呼び方は日本の用語のようです。
ある程度のキャッシュを削除して最新データを反映するので、更新の画像やレイアウトなら、こちらの機能で対処ができます。
操作方法は、ショートカットキーで Ctrl+Shift+R または Shift+F5を同時に押すと起動します。

「キャッシュクリア」
しかし最近は、キャッシュの強化が増えています。またcookieにより個人データを保持しているサイトも増えてます。
このようなキャッシュや個人情報データを削除したい場合は、キャッシュ自体を削除する「キャッシュクリア」をすると良いでしょう。
ただし、キャッシュクリアを行うとサイトが初期化されたようになるので、ログイン中のサイトからはログアウトされる、クリック済みのリンクが未クリック状態に戻るなど起こりますので、ご注意ください。
ブラウザの履歴データから削除する方法と、開発ツールを開いて更新ボタンから選択して押す方法があります。後者の方は私も最近その存在を知りました。また、macのsafariでは、メニューバーに開発メニューを表示すれば、ボタンがあるので一度設定してしまえば、使いやすいと思います。

【リロード(再読み込み)の操作方法】

Windows版

通常リロード chrome、edge、firefox 更新ボタン、ctrl+R or F5
スーパーリロード
ハードリロード
chrome、edge、firefox shift +更新ボタン、shift+ctrl+R

chrome、edge ctrl+更新ボタン

画面上で右クリックして開発ツールを開く→更新ボタン上で右クリック→「ハード再読み込み」を押す
キャッシュクリア chrome、edge、firefox ブラウザメニューの履歴データからキャッシュを削除
キャッシュクリア
        +
ハードリロード
chrome、edge 画面上で右クリックして開発ツールを開く→更新ボタン上で右クリック→「キャッシュの消去とハード再読み込み」を押す

mac版
safariは、開発者ツールを有効化すれば、メニューバーにキャッシュクリア、履歴データ削除のボタンが表示されます。

  1. デスクトップ開発マシンで、Safari を開きます。
  2. [Safari] | [環境設定] を選択します。
  3. [詳細] を選択します。
  4. [メニューバーに “開発” メニューを表示] ボックスをオンにします。

通常リロード chrome、edge、firefox 更新ボタン、command+R or F5
スーパーリロード
ハードリロード
chrome、safari shift + command + Rもしくはshift + 更新ボタン

firefox shift + command + R
キャッシュクリア chrom、safari、firefox ブラウザメニューの履歴データからキャッシュを削除
キャッシュクリア
        +
ハードリロード
chrome 画面上で右クリックして開発ツールを開く→更新ボタン上で右クリック→「キャッシュの消去とハード再読み込み」を押す

nginx+FPMの環境のタイムアウトの解決法の備忘録

前回、記事を書いたPHPをnginx+FPMの環境で、実際にwebアプリを作成。
ページが重くなる場合にタイムアウトが発生、その原因が複数の理由のため解決に時間がかかったのでその備忘録。

さきに原因を書くと
  1. phpの実行時間によるタイムアウト
  2. ブラウザとnginxのタイムアウト
  3. nginxとphp-fpmのタイムアウト
と3つの箇所でタイムアウトが発生していた。

まずは最初に表示された
504 Gateway Timeout
を解消する。
phpのタイムアウト時間max_execution_timeを設定して確認。
まだ504エラーが表示されるので、つぎにnginxのsend_timeoutkeepalive_timeoutを設定。
この設定の追加で504エラーは表示されなくなったが、別のエラーがnginxから表示されるようになった。
An error occurrerd. Sorry, the page you are looking for is currently unavailable. Please try again later.

このエラーメッセージがnginxとphp-fpmのタイムアウトだと気づくのに時間がかかった。
これの対応はnginxの設定にfastcgi_connect_timeoutfastcgi_read_timeoutfastcgi_read_timeoutを追加することで解消できた。

最終的にnginx.confphp.iniに下記の設定を追加しています。
nginx.conf
http {
send_timeout 300; # クライアントへの応答のタイムアウト時間
keepalive_timeout 300; # クライアントとの接続をキープする時間

fastcgi_connect_timeout 300; # nginxとphp-fpmの接続を確立するためのタイムアウト時間
fastcgi_send_timeout 300; # nginxからphp-fpmへのリクエスト送信のタイムアウト時間
fastcgi_read_timeout 300; # php-fpmからの応答のタイムアウト時間
}
php.ini
max_execution_time = 600 # phpのスクリプトの実行時間

webサーバーとアプリケーション・サーバーを分けた場合、その間のタイムアウトも気にしなければいけなかった。

PHPをnginx+FPMの環境で動作させる

古いPHPのWebアプリの改修を行うことになりました。
まずはメンテナンス性の確保のためにオレオレフレームワークからLaravelに載せ替える。
また、1台サーバーを使用して動かしていたが、あまり大きなアプリではないので、kubernetesで動作させることにします。
そして合わせて将来的な技術投資としてWebサーバーをApacheからnginx + FastCGI Process Manager(FPM)を試してみることにしました。
その際のdokcerとkubernetesの設定の備忘録になります。

nginxのconfファイルの作成

まずはLaravelとphp-fpm用にしたnginxのconfファイルを作成。
default.conf
server {
    listen       80;
    listen  [::]:80;
    server_name  example.com;

    access_log  /var/log/nginx/host.access.log;
    error_log  /var/log/nginx/host.error.log;

    location / {
        root   /usr/share/nginx/html;
        index  index.php index.html index.htm;
        # リクエストされたファイルが存在しなければ、Laravelのフロントコントローラーに内部リダイレクトさせる
        try_files $uri /index.php?$query_string;
    }

    # 400番台のエラーページの設定
    error_page  404              /404.html;

    # 500番台のエラーページの設定
    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # Apacheの設定なので今回はコメントアウトのまま
    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # FastCGIの設定
    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        # Laravelのルートディレクトリ
        root           /var/www/html/public;
        # nginxからphp-fpmに受け渡すIPアドレスとポート番号の設定
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        # 全てのリクエストをLaravelのフロントコントローラーで実行させる
        fastcgi_param  SCRIPT_FILENAME  $document_root/index.php;
        include        fastcgi_params;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}
ここでの注意点はnginxからphp-fpmに受け渡すIPアドレスとポート番号の設定
ローカル環境で動作させる場合はdokcerのphp-fpmのコンテナを指定してください。
fastcgi_pass   php-fpm:9000;
kubernetesで動作させる場合は1pod内でnginxのコンテナとphp-fpmのコンテナを動作させるため127.0.0.1:9000で受け取ることができます

nginxのDockerfileの作成

Docker Hubから最新のものを使用。
Dockerfile
FROM nginx:1.23.3

COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf

EXPOSE 80 443

STOPSIGNAL SIGQUIT

CMD ["nginx", "-g", "daemon off;"]

php-fpmのDockerfileの作成

こちらもDocker Hubから最新のものを使用。
注意点はnginxがアクセスできるようにLaravelのプロジェクトを配置しているディレクトリの所有ユーザーを変更すること。
アクセスしてくるのはnginxなので所有ユーザーをwww-dataに変更しておかないと404エラーになります。
Dockerfile
FROM php:8.1.16-fpm

#PHPの拡張機能のインストール
RUN apt update \
    && apt install -y libonig-dev libxml2-dev libcurl4-openssl-dev libssl-dev libzip-dev \
    && docker-php-ext-install pdo pdo_mysql mysqli simplexml curl phar zip ftp \
    && pecl install xdebug redis \
    && docker-php-ext-enable xdebug redis

# nginxがアクセスできるように所有ユーザーを変更
COPY project /var/www/html
RUN chown -R www-data:www-data /var/www/html

EXPOSE 9000

ENTRYPOINT ["docker-php-entrypoint"]

CMD ["php-fpm"]

kubernetesの設定の作成

kubernetesに反映させるdeployment、service、ingressを作成します。
deployment.yaml
1pod内で動作させるためcontainersにnginxとphp-fpmのコンテナを記載します。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-deployment
  labels:
    app: example
spec:
  replicas: 1
  selector:
    matchLabels:
      app: example
  template:
    metadata:
      labels:
        app: example
    spec:
      containers:
        - image: php-fpm:latest
          name: example-php-fpm
          ports:
            - containerPort: 9000
              protocol: TCP
        - image: nginx:latest
          name: example-nginx
          ports:
            - containerPort: 80
              protocol: TCP
service.yaml
外からのアクセスはnginxが受けるのでポートは80番。
kind: Service
apiVersion: v1
metadata:
  name: example-service
spec:
  type: NodePort
  selector:
    app: example
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
spec:
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: example-service
                port:
                  number: 80

kubernetesに反映を実行。 これで動作できました。

mysqlのセキュリティ更新で新たなユーザー権限が必要になっていた

久々にmysqldumpを使用した際の話

とある検証のために本番環境の最新のデータを検証環境のデータベースにいれる必要があり、久々にmysqldumpを実行。

まずはテーブル名を指定してdumpファイルを作成する。

mysqldump -uユーザー名 -pパスワード -hホスト スキーマ名 テーブル名 > /tmp/dump.sql 

実行。

 Access denied; you need (at least one of) the PROCESS privilege(s) for this operation 

アクセス拒否、PROCESS権限が必要です。

以前なら問題なく実行できていたユーザーなのに、必要な権限が増えている?

ということで調べてみると、リリースノートのセキュリティ注釈に記載があり

https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-31.html
Security Notes Incompatible Change: Access to the INFORMATION_SCHEMA.FILES table now requires the PROCESS privilege. This change affects users of the mysqldump command, which accesses tablespace information in the FILES table, and thus now requires the PROCESS privilege as well. Users who do not need to dump tablespace information can work around this requirement by invoking mysqldump with the --no-tablespaces option. (Bug #30350829) The linked OpenSSL library for MySQL Server has been updated to version 1.1.1g. Issues fixed in the new OpenSSL version are described at https://www.openssl.org/news/cl111.txt and https://www.openssl.org/news/vulnerabilities.html. (Bug #31296697)

PROCESS権限が必要になっていました。

ユーザーに権原を付与する、または

--no-tablespaces

オプションをつけることで回避できるそうです。

今回はオプションで対応しました。

改めて実行。

mysqldump -uユーザー名 -pパスワード -hホスト --no-tablespaces スキーマ名 テーブル名 > /tmp/dump.sql

今度は問題なくダンプ終了

mysql -uユーザー名 -pパスワード -hホスト スキーマ名 < /tmp/dump.sql

ロードする。

Access denied; you need (at least one of) the SUPER privilege(s) for this operation

さらにアクセス拒否が表示される。SUPER権限が必要です?

これについては

SET @@GLOBAL.GTID_PURGED

が原因でした

https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html#option_mysqldump_set-gtid-purged
This option enables control over global transaction ID (GTID) information written to the dump file, by indicating whether to add a SET @@GLOBAL.gtid_purged statement to the output. This option may also cause a statement to be written to the output that disables binary logging while the dump file is being reloaded.

グローバルトランザクションIDについては変更したくないので、これもオプションで対応。

--set-gtid-purged=OFF

を追記して、再度ダンプし直します。

 mysqldump -uユーザー名 -pパスワード -hホスト --no-tablespaces --set-gtid-purged=OFF スキーマ名 テーブル名 > /tmp/dump.sql 

そしてロードする。

mysql -uユーザー名 -pパスワード -hホスト スキーマ名 < /tmp/dump.sql

問題なくできました。これで最新のデータで検証を行うことができそうです。

TORICO が漫画アプリ開発に Flutter を採用した事例の紹介

当社 TORICO は、モバイルアプリの開発に Flutter を採用しています。

この記事では、2023年1月現在の、TORICO での Flutter 開発事例を紹介します。

TORICO が モバイルアプリ開発に Flutter を採用する理由

共通のコードで iOS と Android に両対応できる点と、Dart 言語での開発の体験が良いことが Flutter を採用している理由です。

私の主観が入りますが、Dart 言語については、概ね TypeScript での開発体験と似ていますが、TypeScript が引きずっている JavaScript が持つ特徴的な挙動(this の挙動、null, undefinedの扱い、Date オブジェクト等)が無く、書きやすい言語だと感じます。

Dart はライブラリの開発もしやすく、パッケージマネージャ(pub)も標準で搭載されていますので、有志の型が開発されているライブラリの導入も容易です。

加えて、Flutter は Windows や Mac 等のデスクトップアプリにも対応できます。
開発体験は Electron を使った場合と似ていますが、Electron に比べて生成プログラムのサイズを小さく保てますので、配布に適していると思います。

TORICO では、社内で使う小規模なツールの開発に Flutter を使う場合があります。

TORICO で Flutter を採用しているアプリ

スキマ

スキマ | 全巻無料漫画が32,000冊読み放題!

スキマは漫画を無料で読めるサービスです。

モバイルアプリ版は Flutter で開発しており、iOS Android 両プラットフォームで配信しています。

スキマのサービスは、2015年に開発を開始し、4月28日よりサービスを開始しました。
開発当初は、iOS は Objective-C、Android は Java で開発していましたが、2019年に Flutter に切り替えています。

当初の Flutter のバージョンは 1.3 ぐらいだったと思います。

当時は状態管理やルーティングの有用なライブラリは少なく、FutureBuilder などをそのまま使う場面が多くありました。

Flutter のバージョンアップには積極的に対応し、現在は Flutter 2の最新版で開発を行っています。

2023年1月現在の技術スタックは、 zenn の 0maru の記事を参考にしてください。

約3年間Flutter で開発してきてのあれやこれや

スキマや、後述の漫画全巻ドットコム等で使われている、 Flutter での Twitter のログインを提供するパッケージ twitter_login は、 0maru の開発によるものです。

スキマは 広告SDKを多く扱うため、Swift や Kotlin の開発分量も少なくありませんが、広告 SDK のコントロールは Flutter 上から行えるようになっており、Firebase と連携してリモートで調整することができます。

スキマのモバイルアプリをダウンロード: iOS | Android

漫画全巻ドットコム

漫画全巻ドットコム | コミックセット通販

漫画全巻ドットコムは、漫画の全巻販売に特化したECサイトです。

モバイルアプリも提供しており、 Flutter で開発し、iOS Android 両プラットフォームで配信しています。

アプリのリリースは 2021年7月27日より行っており、リリース初期から Flutter を使っています。

リリース時期の技術スタックは、 zenn の 0maru の記事に書かれていますのでご覧ください。

Flutter でECアプリを新規開発してみて

漫画全巻ドットコムのモバイルアプリをダウンロード: iOS | Android

漫画全巻ドットコム MZ Reader

漫画全巻ドットコムは、紙のコミックの全巻セット販売だけでなく、電子書籍も販売しています。

MZ Reader は、漫画全巻ドットコムで購入した電子書籍を読むためのアプリです。

電子書籍サービスは 2012年11月に開始しており、当時は iOS は Objective-C、Android は Java で開発を行っており、Windows 版も提供していました。

電子書籍ビューアは 2020年10月にリニューアルを行い、フレームワークに Flutter を採用しています。リニューアル時点ではアプリの評価が低かったのですが、体験の向上を目的としたブラッシュアップを行い、アプリの評価を大きく改善することができました。

新入社員が入社後数ヶ月でコミックリーダーアプリのレビュースコアを 1.4 → 4.7 に改善した話

電子書籍のダウンロードには、Dio と Flutter Downloader を場面により使い分けています。

2023年1月現在、Flutter2 でリリースをしています。

MZ Reader をダウンロード: iOS | Android

マンガ展

マンガ展 | まんが作品の原画展・イラスト展やサイン会などのイベント情報掲載

マンガ展は、マンガの作家さんとのイベントを提供するプロジェクトです。

現在、東京の池袋と渋谷(Magnet By Shibuya 109内)、大阪の谷町六丁目、名古屋の栄のオアシス21内、台湾の台北101の近くに店舗を運営しており、イベントを開催しています。

国内の各店舗の地図 | マンガ展台湾の地図 

物品の購入や会場での来場スタンプが獲得できるモバイルアプリを公開しています。

マンガ展のアプリは 2019年1月からリリースしており、Flutter を採用したアプリの中では一番早い時期となります。

Flutter のバージョンは v0.10 あたりから使い始めたように思います。マンガ展アプリのリリース直前で、Flutter v1.0 が公開されたタイミングだったように記憶しています。

現在は Flutter2 対応で Null Safety になり、状態管理は RiverPod を使っています。

マンガ展のモバイルアプリをダウンロード: iOS | Android

Andorid レジ

Sunmi 社の T2s や T2 mini で動作するレジを Flutter で開発しています。

Sunmi 社のレシートプリンタのバインディングライブラリの sunmi_printer_plus  内の、T2 mini の LCD のバインディングを ytyng が開発しています。

T2s は、2つのディスプレイを持つ Android 端末です。presentation_displays パッケージを使うことで、サブディスプレイにも Flutter のウィジェットを表示することができます。

状態管理は Riverpod, hooks_riverpod を使っています。

デスクトップツール

Windows上で動作する、漫画全巻ドットコムの CMS 連携ツールを Flutter で開発しています。

漫画全巻ドットコムでは、一部のキャンペーンページのコンテンツデータをデータベースに保存しています。

コンテンツデータは非エンジニアの運用スタッフが作成するのですが、Web フォーム上での作成は体験が悪いので、Windows 上の VSCode と連携して作成できるツールを Flutter で作っています。

Flutter を採用している理由は、社内の開発者は全員 Mac を使っているため、Windows での使用を要件としたクロスプラットフォーム開発となると使える技術が絞られることと、その中で 実行バイナリのサイズが小さく、別途ランタイムライブラリも必要無く、動作が安定しているためです。

最後に

株式会社TORICO では、Flutter で自社サービスを開発する技術者を募集しています。

業務内容として Flutter 100% ではなく、AWS、MySQL、ElasticSearch(OpenSearch)、Python Django 等サーバサイド開発も扱っていただきます。応募フォームからの連絡をお待ちしています。

https://www.torico-corp.com/recruit/

当社プロダクトにおける、nuxt2→nuxt3 への移行時の記録

2022年11月16日、Nuxt3 が正式リリースされました。

当社でサービスしているプロダクトの1つを、Nuxt2 から Nuxt3 に書き換えましたので、変更した実施内容を書きます。

Nuxt2 から Nuxt3 への変更点

概要としては、 Zennの下記記事が参考になります。

祝・正式リリース!5つのテーマで理解する Nuxt3 の魅力

具体的な変更点や使い方は、今回の記事には記載しません。公式サイトの docs タブにまとめられています。マイグレーションを行う際には、docs 内の記事は一通り読むことをおすすめします。

Introduction · Get Started with Nuxt

移行(マイグレーション)の進め方

既存の Nuxt2プロジェクトを Nuxt3 にマイグレーションする場合、リポジトリをそのまま使うか新しく作るかの選択がありますが、新しく作ることを推奨します。

変更点は膨大になるため、既存のリポジトリをそのまま使うのは現実的ではありませんし、既存リポジトリを流用していく方法では、最初の動作確認をするまでにしなければいけないコード修正の量が膨大です。最小のコード修正で最初の動作確認ができるようにしたほうが、間違った時のコストを少なくできます。

npx nuxi init <project-name>

で新しいプロジェクトを開始し、リポジトリも新しく作ってください。

新しいプロジェクトの作成後は、まずは利用規約ページ、FAQページ、問い合わせページ等、依存リソースの少ないページを1つづつ移行していくと良いと思います。

移行にかかった時間

今回のプロジェクトは、pages が 20, components が 100 ほどの規模でしたが、空き時間にちまちまやって3週間かかりました。集中して時間がとれれば1週間ぐらいでできると思います。

  • ユニットテストはありません。
  • この時間には、スタッフによる品質検証の時間は含まれていません。

移行時の目立った問題

Vue2 で使用していたプラグインが Vue3 に対応していない場合がある

Nuxt ではなく Vue の話になるのですが、いくつかの Vue2 用のライブラリが Vue3 に対応していなため、代替を探したり自作する必要があります。

Nuxt2 から Nuxt3 のマイグレーションで、一番時間がかかったのはこの部分でした。

今回は、以下の変更がありました。

  • hooper → swiper (トップページのヒーローカルーセルで使用)
  • vue-markdown → vue3-markdown-it (MarkDown のレンダリング)
  • vue-multiselect → @vueform/multiselect (Selectウィジェットの代替)

動作に互換性は無いため、使用箇所は新しいライブラリの API に適合できるよう、大きく書き直しています。

以下のライブラリは、バージョンアップで Vue3 にも対応していたため、そのまま使用できました。(プラグインファイルだけ書き直しました)

  • v-calendar

以下のライブラリは、Vue3 に対応しておらず、代替のライブラリも無かったため、一旦機能を無効化しています。
再現するには自作する等の対応が必要になります。

  • vue-highlight-words (検索語句のハイライト)

コード修正箇所

Nuxt2 プロジェクトから Nuxt3 プロジェクトにマイグレーションする際の目立った変更箇所です。

APIプロキシ

当社のプロダクトは、 /api/ 等のリクエストをバックエンドサーバにプロキシを行うようにしています。

(Nuxt3 にビルトインされている、 /server/ ディレクトリを用いるサーバサイド API は使っていません。)

プロダクトによっては、Nuxt に入ってくる前に AWS の アプリケーションロードバランサー や Kubernetes Ingress でルーティングする場合と、一度 Nuxt で受けてから Nuxt のサーバサイドプロセスでプロキシする場合と、両方のパターンがあります。

Mac での開発環境では、Kubernetes Ingress や Nginx は使わないため、必ず Nuxtでのプロキシが必要になります。

Nuxt2 では、nuxt.config.ts の proxy 設定で書いていましたが、Nuxt 3 では本番環境で使うか (つまりビルド成果物に含めるか) どうかで2通りのプロキシ方法があります。

本番環境ではプロキシは必要なく、開発環境でだけ実現だければ良い場合

本番環境では、ALB や Kubernetes Ingress でルーティングが実現できるため必要なく、開発環境でのみプロキシを行いたい場合は、Nitro の devProxy の機能で実現できます。

主に、SSR を行わず、成果物を SSG ( generate ) して動作させる場合はこの方式になると思います。

nitro の標準機能で実現できるため、追加のライブラリは必要がなく、設定ファイルを1つ追加させるだけで可能です。

nitro.config.ts の例
import {defineNitroConfig} from 'nitropack'

function createNitroConfig() {
// 開発環境用のプロキシを設定する
const configFile = process.env.CONFIG_FILE || 'local'

const config = require(`./config/${configFile}.ts`)

if (!config.makeProxyValue) {
console.error(`./config/${configFile}.ts に makeProxyValue がありません`)
}

const proxyPaths = [
'/api/',
'/login/',
'/media/',
...
]

for (const proxyPath of proxyPaths) {
proxySettings[proxyPath] = config.makeProxyValue(proxyPath)
}
return {
devProxy: proxySettings
}
}

export default defineNitroConfig(createNitroConfig())
config/development.ts
// nitro プロキシの設定
export function makeProxyValue(path: string) {
return {
target: `https://api.example.com${path}`,
changeOrigin: true,
hostRewrite: true,
cookieDomainRewrite: true,
headers: {
'X-Forwarded-Host': 'localhost:3006',
'X-Forwarded-Proto': 'http',
'Referer': 'https://api.example.com'
}
}
}

詳しくは、 Configuration | ⚗️ Nitro の devProxy の項目をご覧ください。

本番環境でも開発環境でも同様にプロキシを行う場合

本番環境でも Nuxt のプロセス(コンテナ) でプロキシを行う場合、標準機能では実現できないため nuxt-proxy のライブラリを使います。

SSR を行う場合はこの形になると思います。

nuxt-proxy - npm

npm でインストール後、 nuxt.config.tsdefineNuxtConfigproxy 設定を追加します。

nuxt.config.ts
export default defineNuxtConfig({
  ...
  modules: ['nuxt-proxy'],
  proxy: {
    options: {
    target: ...,
changeOrigin: true,
headers: ...,
pathRewrite: {
'^/some/url/path/': '/api/path/'
...
},
pathFilter: [
'/more/url/path/',
...
]
}
}

環境設定変数の変更

Nuxt3 とは直接的な関係が薄い内容ですが、
当社では、今までのプロジェクトでは環境変数 NODE_ENV に、development, production 以外の設定名も指定していました。
例えば、ステージングサーバでは NODE_ENV=staging といった形での指定がありました。

ただし、ライブラリによっては NODE_ENVdevelopmentproduction のどちらかのみ想定しているもが多く、ビルドする上でも不都合がありましたので、 NODE_ENVdevelopmentproduction 以外の値を入れるのはやめました。

サーバ環境で設定を分岐する場合は、 CONFIG_FILE という新たな環境変数を使用するようにしています。

CONFIG_FILE で設定ファイルを指定し、記載されている設定を Nuxt に伝えるために、環境別の設定ファイルと nuxt.config.ts はこのような指定となっています。

config/devserver.ts
export const runtimeConfig = {
  // public 内で定義したものはクライアントでも使える
public: {
baseUrl: 'https://example.com',
}
}

export const proxyConfig = {
target: 'http://host.docker.internal:8000',
headers: {
'Host': 'example.com',
'X-Forwarded-Host': 'example.com',
'X-Forwarded-Proto': 'https',
'Referer': 'https://example.com'
}
}
nuxt.config.ts
const configBaseName = process ? (process.env.CONFIG_FILE || 'development') : 'development'
const configContent = require(`./config/${configBaseName}.ts`)

export default defineNuxtConfig({
// runtimeConfig は useRuntimeConfig() で参照できる
runtimeConfig: configContent.runtimeConfig,
...
modules: ['nuxt-proxy'],
proxy: {
options: {
target: configContent.proxyConfig.target,
changeOrigin: true,
headers: configContent.proxyConfig.headers,
...
}
}

※ 定義した設定をクライアント側で使うには、nuxt.config.ts の中で runtimeConfig.public に設定を入れ、クライアント側で useRuntimeConfig を実行します。

useRuntimeConfig · Nuxt Composables

vueコンポーネントの変更

script セクションは、すべて script setup に書き換えました。

setup 構文は、Vue3 の一番目立つ変更点だと思います。

Nuxt3の自動インポートの機能もあいまって、今までの Vue コンポーネントの script 部分を 1/2 程度の分量で記述でき、
とても良い開発体験があります。

多くの記事で説明されているため、概要の紹介はここでは書きませんが、ざっくり書いたカウンターのコンポーネントは以下のようになります。

<script lang="ts" setup>
const count = ref<number>(0)

function increment() {
count.value++
}
</script>

<template>
<button @click.prevent="increment">
{{ count }}
</button>
</template>

本当に必要な内容だけをコードで書けばよくなっています。コンポーネント内で、import を書くケースはほとんどありません。

以下の記事が参考になります。

【Vue.js 3.2】`<script setup>` 構文がすごくすごい

page コンポーネントの変数プレースホルダの変更

今まで、 _id.vue と命名していたファイルは、 [id].vue とつける必要があります。

components ディレクトリの自動インポート

components ディレクトリ内に書いた vue コンポーネントは、 import 文無しで使うことができます。

例えば、components/header/HeaderSearchButton.vue という名前のコンポーネントは、 インポート無し<HeaderSearchButton> として使えます。

また、 components/header/SearchButton.vue という名前のコンポーネントも、同じく、 <HeaderSearchButton> として使えます。

ディレクトリ名 + ファイル名 でコンポーネント名になりますが、ファイル名の先頭とディレクトリ名が重複する場合、重複する部分は打ち消されます。

components/global/ ディレクトリは特別で、この中に入れたものは Global を省略して、ファイル名そのままでコンポーネントとして使えます。

今までの当社のプロダクトは、区分が必要無いコンポーネントについては components/common/ ディレクトリに配置することが多かったのですが、components/common/ ディレクトリは components/global/ ディレクトリに変えるようにしています。

Vuex ストア → composables ディレクトリ内の composable 関数

Nuxt2 では、グローバルストアは Vuex + vuex-module-decorators (もしくは nuxt-typed-vuex) を使って書いていました。

Nuxt3 では、Vuex は使わなくなり、かわりに composable を使ってグローバル状態管理を構築します。

composables/ · Nuxt Directory Structure

composables/ 以下のディレクトリにある useXxxxx 関数は、自動インポートの対象になります。

例えば、vuex-module-decorators では、カウンターの Vuex ストアは以下のように書いていました。

Nuxt2: store/counter.ts ( vuex-module-decorators の場合)
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'

@Module({
name: 'counter',
stateFactory: true,
namespaced: true
})
export default class extends VuexModule {
count: number = 0

get doubleCount (): number {
return this.count * 2
}

@Mutation
setCount (value: number) {
this.count = value
}

@Action({ rawError: true })
async increment () {
this.setCount(this.count + 1)
}
}

nuxt-typed-vuex で書いた場合は以下のようになります。

Nuxt2: store/counter.ts ( nuxt-typed-vuex の場合)
import { getterTree, mutationTree, actionTree } from 'typed-vuex'

export const state = () => ({
count: 0 as number
})

export type RootState = ReturnType<typeof state>

export const getters = getterTree(state, {
doubleCount(state): number {
return state.count * 2
}
})

export const mutations = mutationTree(state, {
setCount(state, count: number) {
state.count = count
}
})

export const actions = actionTree(
{ state, mutations },
{
increment({ commit }) {
commit('setCount', state.count + 1)
}
}
)

Nuxt3 composable の場合、composable を使って以下のように書きます。

composables/counter.ts
export const useCounterComposable = () => {
const count = useState<number>('count', () => 0)

const doubleCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}

return {
count,
doubleCount,
increment,
}
}

Vueコンポーネント内で使う際は、このようになります。

composables/counter.vue
<template>
<button @click.prevent="counterComposable.increment" >
{{ counterComposable.count}} * 2
= {{ counterComposable.doubleCount }}
</button>
</template>
<script lang="ts" setup>
const counterComposable = useCounterComposable()
</script>

Vuex Store から composable 関数へのマイグレーションは、完全に 1:1 で行えます。
(ミューテーションは不要になります。)

ファイル名はそのままに、手動で機械的に composable 関数に変換していくと良いでしょう。

注意点

composables/counter.ts の

const count = useState<number>('count', () => 0)

は、

const count = ref<number>(0)

と、ref を使って書いても良さそうに見えます。実際それでも動くのですが、refsetup セクション以外で使うとサーバで状態が共有されメモリリークが発生するため、 setup セクション以外では使えません。

State Management · Get Started with Nuxt

useState の第一引数のキー名は、利用者のアプリ内で一意のものであり、キー名が一致したらどこでコールしても共通のカウント数が取得できます。

const count = useState<number>('count', () => 0)

ただし、使用箇所が分散するとメンテナンスが難しくなるため、composables/ の中だけで扱うと決めるのが良いと思います。

サイトすべてで共通の HTML の head セクション

title タグ、viewport や文字コード等の meta タグ、共通スタイルシートの link タグなどの、サイト内で共通の HTML の head セクションの定義は2通りの方法があります。

nuxt.config.ts の app.head に書く

Nuxt2では、nuxt.config.tshead というセクションに書いていましたが、Nuxt3 以降は app.head に書きます。

Nuxt Configuration Reference · Nuxt #head

基本的な書き方は変わっていません。

app.vue で useHead を使う

一番大枠のVueコンポーネントである、app.vue に、script setup を作り、useHead 関数を使って Head 項目の定義をすることもできます。

nuxt.config.ts で定義すれば良いものなので基本的には使うことは無いと思いますが、昔にこの形式でやってたこともあるので、これでも設定できることを書き残しておきます。

テンプレートフィルタ構文の廃止

テンプレートでの、下記のようなフィルタ構文廃止になり、Vue3 からは使えません。

{{ taxIncluedTotalPrice | addComma }}

そのため、普通の関数に変更する必要があります。

{{ $addComma(taxIncluedTotalPrice) }}

プラグインの書き方の変更

上記、addComma 関数は、プラグイン関数として定義すると、どこからでもインポート無しで使えて便利です。プラグイン関数はこのように書きます。

plugins/filter.ts
export default defineNuxtPlugin((nuxtApp) => {

/**
* 金額をカンマ区切りに表示します。
* $addComma(1000) => 1,000
*/
nuxtApp.provide('addComma', function (val: number) {
if (val === 0) {
return '0'
}

if (!val) {
return ''
}

if (typeof val === 'string') {
val = Number(val)
}

return val.toLocaleString()
})
})

nuxtApp.provide を実行することでで、システムグローバルに $xxxxx という名前で使える関数を定義できます。
テンプレート内でも、script setup 内でも使えます。

app.use する場合

v-calendar のように、Vue3 で app.use でプラグインを登録するライブラリを Nuxt3 で使う場合はこのように書きます。

Vue 3 | V-Calendar

plugins/v-calendar.ts
import {defineNuxtPlugin} from 'nuxt/app'
import VCalendar from 'v-calendar'

// https://vcalendar.io/vue-3.html
export default defineNuxtPlugin((nuxtApp) => {
if(process.client) {
nuxtApp.vueApp.use(VCalendar, {})
}
})

ページを遷移した時に自動的にページの先頭にスクロールアップするプラグイン

ページを移動した時にページの先頭にスクロールアップし、さらにブラウザを戻った場合に前回見ていたスクロール位置をキープする必要がある時、下記ディスカッションで書かれているコードを適用すると良いです。

scrollToTop in v3 ? · Discussion #1661 · nuxt/framework

このディスカッションの、この発言が要件を満たす完璧なコードです。

https://github.com/nuxt/framework/discussions/1661#discussioncomment-3967225

長くて仰々しいですが、使用感は一番良いです。

とはいえ…正直な所、JS のフレームワークを使っている以上、ブラウザの「戻る」の体験は、ページごとに HTML を返すクラシックなスタイルに並ぶまでは至りません。

検索結果のリストを表示して、クリックして詳細表示に遷移し、ブラウザの戻る機能で戻り、その下のものをクリックして…といった当たり前に行う操作において、クラシックなHTMLアプリに近い操作感に達するまでに、書かなければいけないコードや気にしなければいけないことが多すぎます。今後も研究していく課題だと思っています。

データフェッチ周りの改修

当社では、Nuxt2 では axios を使ってデータフェッチを行うこと多かったですが、Nuxt3 からは ohmyfetch 改め ofetch を使います。

リアクティブな状態変化に対応できるのは良いのですが、サーバでフェッチした内容がクライアントにハイドレーションされる時の挙動など慣れが必要となると思います。

また内容をキャッシュする機能があり、場合によっては意図しない所で内容がキャッシュされていまい、利用者が操作しても内容が切り替わらないこともよくあります。

データフェッチ用の関数も、 useAsyncData, useLazyAsyncData, useFetch, useLazyFetch, $fetch と、いくつもあります。

Data Fetching · Get Started with Nuxt

await useFetch('/api/count') と書いた場合、クライアント側とサーバ側で処理が変わります。

これは、私のプロダクトが /server/api/ を作っておらず、外部サーバへプロキシしているためかもしれません。

サーバ側で、 useFetch('/api/count') が発生した場合、リクエストするホスト名が不明なため、リクエストが失敗します。

そのため、サーバ側では、useFetch のオプションの baseUrl でスキーマとホストhttps://example.com を指定するか、リクエスト先のURLの先頭にスキーマとホスト等をつけて、 'https://example.com/api/count' といった完全な形のURL に変換する必要があります。

リクエストヘッダについても、HostAccept 等のヘッダを必要に応じて付与します。サーバサイドプロセスから外部のサーバにリクエストする場合、プロキシではないため nuxt.config.ts の proxy の設定は使われません。少し混乱しやすい箇所だと思いますのでご注意ください。

また、useFetch のキャッシュのキーの生成についても、私の使い方の問題だとは思うのですが、期待通りに一意のキーができてないように思うので、 useFetch のラッパーを作って明示的にキーを作っています。

export async function useFetchAPI<T>(url: string, options: any = {}) {

if (!options.key) {
// キャッシュキーを明示的に作る
options.key = url + JSON.stringify(options)
}
let requestUrl: string = url
if (process.server) {
// サーバリクエストの場合は完全な URL を作ってリクエストする
const runtimeConfig = useRuntimeConfig()
options.baseURL = runtimeConfig.proxy.options.target

// Docker 内から host.docker.internal にアクセスする際に Host ヘッダを付与する
if (runtimeConfig.proxy.options.headers.Host) {
if (!options.headers) {
options.headers = {}
}
options.headers.Host = runtimeConfig.proxy.options.headers.Host
}
}

return await useFetch<T>(
requestUrl, options)
}

$fetch は、 axios の .$get のように、レスポンスの中で必要なコンテンツのみを返してくれる関数ですが、axios の .get や、python の requests, また生の fetch のように、リクエストの結果をレスポンスオブジェクトとして取得し、レスポンスオブジェクトの HTTP ステータスや Content-Type を見て処理を分岐するといったコードは書けません。

ただ、データの Put や Post 等の更新系のリクエストは、$fetch を使わずにもっとプリミティブなレスポンスを扱ってエラーハンドリングをしたいのと、CSRF を防ぐためのトークン送信なども必要になると思いますので、$fetchuseFetch を使わず、 fetch のラッパーを作って使っています。

package.json

Nuxt3 の場合、 package.jsondependencies空のオブジェクトにして、必要な依存関係はすべて devDependencies に書きます
Svelte でもそうで、Vite を使う場合は基本的にこの形になるのだと思います。

package.json
{
"private": true,
"scripts": {
"build": "nuxt build",
"build:devserver": "CONFIG_FILE=devserver nuxt build",
"build:production": "CONFIG_FILE=production nuxt build",
"dev": "PORT=3006 nuxt dev",
"dev:local": "CONFIG_FILE=local PORT=3006 nuxt dev",
"generate": "nuxt generate",
"preview": "PORT=3006 nuxt preview",
"postinstall": "nuxt prepare"
},
"devDependencies": {
"nuxt": "3.0.0",
"sass": "^1.56.1",
"@vueform/multiselect": "^2.5.6",
"nuxt-proxy": "^0.3.4",
"swiper": "^8.4.4",
"v-calendar": "^3.0.0-alpha.8",
"vue3-markdown-it": "^1.0.10"
},
"dependencies": {
}
}

ビルドを行うと、nitro/h3 の Webサーバエンジンも含めたすべての依存ライブラリを output ディレクトリにまとめて書き出します。
output ディレクトリさえあれば他に依存せずアプリを動作させることができるようになっています。

Dockerfile

動作時に外部の依存ライブラリが必要無いということは、 Dockerfile をかなりシンプルに書けます。

Dockerfile
FROM node:16-slim

WORKDIR /app

COPY .output /app/.output

USER nobody
CMD [ "node", ".output/server/index.mjs"]

当社の環境では、ビルドごとに Docker イメージを作っています。
.output ディレクトリを実行環境にコピーできれば、node の docker コンテナにマウントさせて実行させるのも簡単なため、独自の Dockerイメージすら必要無いでしょう。

本番の実行環境の構築が非常に楽になったのは Vite を使うようになってからの大きな利点だと感じます。

Raspberry Pi Pico W で Httpサーバ(microdot)とセンサーによるHTTPリクエスト機能を同時に稼働させる

Raspberry Pi Pico W が発表されました。日本ではまだ未発売ですが、技適は取得されたようですので近いうちに国内販売がされそうです。

試しに、Webサーバ ( Microdot )とWebクライアント(urequest) を uasyncio で並列実行するコードを書きましたので、紹介します。

今回作成したコードや動作している動画は、Github で公開しています。

ytyng/rpi-pico-w-webserver-and-client: Raspberry Pi Pico W webserver and client sample code

Raspberry Pi Pico W とは

コストパフォーマンスが高いマイクロコントローラです。カテゴリとしては Arduino 等に近く、今までの Raspberry Pi のように、Linux OS を動作させるようなマシンではありません。

RP2040 というラズベリーパイ財団が開発したチップのデモボードという位置づけとなります。

実際の商品開発では、Raspberry Pi Pico で製品のR&Dを行い、実際は RP2040 を搭載した製品として生産するという流れとなると思いますが、ホビーや SOHOでは Raspberry Pi Pico をそのまま使うことも多いと思います。

実際、当社でも Raspberry Pi Pico を用いてイベント用の機材を作る場合がありますが、
RP2040 を使ったの製品を作るわけではなく、Raspberry Pi Pico をそのままケースに入れて使います。

MicroPython が動作するため、Python に慣れていれば開発は容易にできます。

商品名に W がつかない今までの機種は、ネットワーク機能はありませんでしたが、今回 Raspberry Pi Pico W となって無線チップが搭載され、コストパフォーマンスと使い勝手が最高の IoT デモボードとなりました。

↑ 左が Raspberry PI Pico, 右 が無線LAN チップが搭載された Raspberry Pi Pico W

考えられる用途

Raspberry Pi Pico W の用途で多く使われると考えられる用途は、

  • 接続させているデバイスのセンシング情報を元に、HTTP リクエストを発生させる
  • HTTP サーバを起動し、外部から HTTP リクエストを受け取って、接続されているデバイスを動作させる

この2つが主なものとなると考えられます。

今回は、この2つを Raspberry Pi Pico W の中で同時に実行する方法を書きます。

一通りのチュートリアル

Raspberry PI の公式ページが提供している PDF が充実います。

https://datasheets.raspberrypi.com/picow/connecting-to-the-internet-with-pico-w.pdf

ただ、PDF なので少し読みにくいのと、Thonny に関しては言及されていないため、Mac や Windows を普段使われている方は、この PDF だけでなく、他のサイトで紹介されているような Thonny を使ったセットアップを行うと良いでしょう。

ファームウェアの準備

https://micropython.org/download/rp2-pico-w/

上記 URL で、Raspberry Pi Pico 用の MicroPython ファームウェアの uf2 ファイルが入手できます。

最新版への直リンクはこちらです。 https://micropython.org/download/rp2-pico-w/rp2-pico-w-latest.uf2

ファームウェアのファイルをダウンロードし、 Pico へコピーしてください。

W 対応でない uf2 ファームウェアは別に存在します。そちらを使った場合、Wi-fi の機能が使えませんのでご注意ください。

コピー方法

Pico の BOOTSEL ボタンを押したまま USB で PC に接続すると、PCが Pico をストレージとして認識します。

ダウンロードした uf2 ファイルを Pico にドラッグアンドドロップでコピーすると、自動的にファームウェアがロードされ、 Pico が再起動します。

Wifi に接続する

一番最初に、Wifi に接続する必要があります。
SSID と パスワードが変数化されていれば、後は簡単なコードで接続が行えます。

接続用の関数を作っておくと便利で、他の方を見ても関数化しているようです。

StackOverflow の話題を見ると、Wifi との接続は main.py の中でやらずに boot.py の中でやったほうがいい、というコメントをいくつか見かけましたが、私は 開発のしやすさから main.py の中で行うようにしています。

Wi-fi に接続するコード

https://github.com/ytyng/rpi-pico-w-webserver-and-client/blob/main/network_utils.py

import rp2
import network
import uasyncio
import secrets


async def prepare_wifi():
"""
Prepare Wi-Fi connection.
https://datasheets.raspberrypi.com/picow/connecting-to-the-internet-with-pico-w.pdf # noqa
"""
# Set country code
rp2.country(secrets.COUNTRY)

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

wlan.connect(secrets.WIFI_SSID, secrets.WIFI_PASSWORD)

for i in range(10):
status = wlan.status()
if wlan.status() < 0 or wlan.status() >= network.STAT_GOT_IP:
break
print(f'Waiting for connection... status={status}')
uasyncio.sleep(1)
else:
raise RuntimeError('Wifi connection timed out.')

# CYW43_LINK_DOWN (0)
# CYW43_LINK_JOIN (1)
# CYW43_LINK_NOIP (2)
# CYW43_LINK_UP (3)
# CYW43_LINK_FAIL (-1)
# CYW43_LINK_NONET (-2)
# CYW43_LINK_BADAUTH (-3)

wlan_status = wlan.status()

if wlan_status != network.STAT_GOT_IP:
raise RuntimeError(
'Wi-Fi connection failed. status={}'.format(wlan_status))

print('Wi-fi ready. ifconfig:', wlan.ifconfig())
return wlan

接続させているデバイスのスイッチ(センサー)情報を元に、HTTP リクエストを発生させる

Raspberry Pi Pico W Wi-Fi Doorbell tutorial (HTTP requests & IFTTT) — PiCockpit | Monitor and Control your Raspberry Pi: free for up to 5 Pis!

こちらの方が開発されているドアベルのコードが参考になります。
Youtube 動画もあってわかりやすいです。

urequests

Python でよく使う、requests ライブラリに変わり、MicroPython では似たような使い勝手の urequests ライブラリを使うことができます。

ネットワーク接続が確立されていれば、あとは urequests.get(...) 等で簡単にリクエストが発行できます。

Thonny の tools -> Manage packages からインストールできます。

HTTP サーバとして稼働させる

ソケットをそのまま使って簡易的な HTTP サーバにする

こちらの記事が参考になりました。大変わかりやすく日本語で説明されているので、Pico の初学者にもおすすめします。

Raspberry Pi Pico W で無線Lチカ

リンクされている、mimoroni 社のコードは、 Pico 用の拡張されたファームウェア

https://github.com/pimoroni/pimoroni-pico/releases

や、 Pico W 用の各種ユーティリティコードがあり、開発の参考になります。

このコードの HTTP サーバの部分は、TCP ソケットをそのまま使い、リクエスト本文の中のパス名と文字列一致して判定してい分岐を行っています。

Raspberry Pi 公式のチュートリアルPDFでもその方式で行っていました。

規模が小さいようであれば十分だと思いますが、HTTP ヘッダーを扱いたい場合や、少し規模を拡張したい場合はこの形では難しいでしょう。

Microdot を起動する

Flask や Bottle、fastApi が動けば良いのですが、現状は動作しません。
代わりに、Microdot という ウェブフレームワークがあり、使い勝手としては Flask や Bottle によく似ていて大変勝手が良いです。

こちらを使って ウェブサーバを起動してみます。

Microdot

Thonny で Tools -> Manage packages からインストールすることができます。

ネットワーク接続が確立したら、

app = Microdot()

@app.get('/')
async def _index(request):
return 'Microdot on Raspberry Pi Pico W'

app.run(port=80)

このような親しみやすいコードで HTTP サーバが起動します。

ウェブサーバとセンサーリクエストを同時に使う

Raspberry Pi Pico は、通常シングルスレッド動作です。(ちなみにCPUはデュアルコアです)

一応、 _threading という疑似スレッドができるライブラリはありますが、処理によっては本体が暴走したり固まることが多く、かなりおすすめしません。
暴走すると、最悪、何度もファームウェアのリセットをするこになり、開発体験は良くありません。

代わりに、 asyncio を使ったコルーチン処理を標準で行うことができ、こちらは安定して動作しますので、 Pico で開発する際は、基本的にメソッドはコルーチンで書くのをおすすめします。

Pico は、待機ループで sleep を使うことも多いですし、コルーチンと相性が良いと感じます。

Microdot も非同期対応の起動ができるものが既に開発されています。

Pico 上の MicroPython でのコルーチンは、通常の Python にビルトインされている asyncio を使うのではなく、
uasyncio というパッケージを使います。

Pico 用の uf2 ファームウェアに含まれていますので、別途新たなインストールは必要ありません。

例えば下記のようなコードで、asyncio が有効な処理を開始することができます。

import uasyncio

async def main():
uasyncio.create_task(any_async_method())
await other_async_method()


if __name__ == '__main__':
    uasyncio.run(main())

Pico の起動後、無線 LAN に接続した後は、スイッチ押下待機のループと、Micorodot の起動を
両方ともコルーチンで書くことで、無理なく並列動作をさせることができます。

実際に動作するコードは Github で公開しています。

rpi-pico-w-webserver-and-client/main.py at main · ytyng/rpi-pico-w-webserver-and-client

メインのコードとしてはこのようになります。

"""
Raspberry Pi Pico Web Server with Microdot and Switch Sample Code.
Pin 14 is used for switch input.
"""
import machine
import urequests
import network_utils
from microdot_asyncio import Microdot
import uasyncio


async def switch_loop():
"""
Switch listener loop
Pin 14 is used for switch input.
When press switch, send request to http web server.
"""
print('start switch_loop')
switch_pin = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)

while True:
current_state = switch_pin.value()
if current_state:
# Change the URL to your own server, IFTTT, Slack, etc.
response = urequests.get('https://example.com/')
print(response.content)
response.close()
await uasyncio.sleep(2)
else:
await uasyncio.sleep(0.1)

async def run_web_server():
"""
Start microdot web server
https://microdot.readthedocs.io/en/latest/index.html
"""
app = Microdot()
led_pin = machine.Pin('LED', machine.Pin.OUT)

@app.get('/')
async def _index(request):
return 'Microdot on Raspberry Pi Pico W'

@app.get('/led/<status>')
async def _led(request, status):
"""
/led/on : LED ON
/led/off : LED OFF
"""
if status == 'on':
led_pin.on()
return 'LED turned on'
elif status == 'off':
led_pin.off()
return 'LED turned off'
return 'Invalid status.'

print('microdot run')
app.run(port=80)


async def main():
wlan = await network_utils.prepare_wifi()
print('LED ON: http://{}/led/on'.format(wlan.ifconfig()[0]))

uasyncio.create_task(switch_loop())
await run_web_server()


if __name__ == '__main__':
uasyncio.run(main())

Svelte で作ったWebコンポーネントを接続国によって出し分ける

概要

11月28日、当社 TORICO の初の海外店舗、マンガ展 台湾 がオープンしました。

漫畫展 台灣

オープンする直前で、「漫画全巻ドットコム、スキマ、マンガ展、ホーリンラブブックスのサイトに、台湾からアクセスされた方に告知を出したい。告知を1回クリックしたらもう出さないようにしたい。」という開発要件が出たので、作ってみました。

接続元の判定と出し分け

今回は、 AWS の CloudFront で、接続元の国名のヘッダを付与する機能を使うことにしました。

CloudFront の入口でリクエストヘッダが付与されますので、CloudFront Functions でそのヘッダを見て動的な処理が行えます。
ヘッダに応じて、対象国であれば、バックエンドのパスを変化させることで、接続国に応じた処理を行えるように設計しました。

サーバの管理はなるべく減らしたいため、今回は完全にサーバレスでサービスを作っています。動的な処理は、CloudFront Function を使いました。

コンポーネントの開発

全サイトに共通のモーダルポップアップ表示を行う必要があります。当社エンジニアのまるさんから、Svelteを進められたので、Svelte で Web コンポーネントを書きました。

完成物

これらのサイトに、台湾からアクセスすると、モーダルポップアップが表示されます。

仕組みとしては、リクエストするスクリプトの内容が、接続国に応じて変化するようになっています。

Svelte で Web コンポーネントを作る

Svelte の概要

Svelte は、Vue のシングルファイルコンポーネントによく似た形で、1ファイルで1コンポーネントをちょうどよく書けます。

CSS はスコープドなものになり、変数は特に意識せずともリアクティブに扱うことができます。

Web コンポーネントとは、JS によって作られた独自の HTML タグをどんなサイトでも使えるようにする技術です。
コンポーネントを定義した JS ファイルをロードすることで、そのサイトで <my-component></my-component> のような独自のタグを使えるようになります。
そのサイトが 素の HTML + jQuery で作られているサービスでも、React や Vue で作られているサイトでも、Google Tag Manager からでも同じようにコンポーネントが使えます。

複数サービスで共通のチャットウィジェットや、広告ウィジェットの開発に適しています。

プロジェクトの開始

Svelte のプロジェクトの開始方法は、チュートリアルがあるのでこの手順で作成できます。

https://svelte.jp/docs#getting-started

npm create vite@latest myapp

対話的に質問されますので、適切に答えます。

Select a framework: Svelte
Select a variant: TypeScript

※ SvelteKit は今回使いませんでした。
単純な Web コンポーネントであれば、特に使わなくても良いと思います。

起動

npm run dev

カウンターが起動します。

Webコンポーネント化する

このカウンターを、Webコンポーネントとしてビルドをできるようにします。

実際の商用コードはカウンターとは違うものですが、今回は例としてカウンターのコンポーネントを使います。

Svelte コンポーネントを Web コンポーネントとして書き出すには、既に良い記事がいくつかあります。

SvelteではじめるWeb Components開発 - Qiita

src/main.ts の内容を下記の1行に修正

export * from './lib/Counter.svelte'

src/lib/Counter.svelte の最上部に1行追加

<svelte:options tag="my-counter" />

ルートディレクトリの index.html の内容を書きに修正

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Svelte + TS</title>
</head>
<body>
<my-counter></my-counter>
<script type="module" src= "/src/main.ts"></script>
</body>
</html>

vite.config.ts を以下のように修正します。

import {defineConfig} from 'vite'
import {svelte} from '@sveltejs/vite-plugin-svelte'

// https://vitejs.dev/config/
export default defineConfig({
build: {
lib: {
entry: './src/main.ts',
name: 'my-counter',
fileName: (format) => `my-counter.${format}.js`,
formats: ['es'],
},
},
plugins: [svelte({
compilerOptions: {
customElement: true,
},
}),]
})

formats については、 ブラウザ用の JS を作るか、サーバ用の JS を作るかでいくつかの形式を指定できます。
デフォルトでは esumd で、umd はサーバサイドでの実行に使うものです。今回は、ブラウザ JS だけあれば良いので、es のみにしています。

独自のコンポーネントを開発する

今回は、開発したプロダクションコードの具体的な内容は割愛します。

開発サーバを実行

npm run dev

ビルド

動作が確認できたら、JS ファイルをビルドします。

npm run build

dist/my-counter.es.js が作成されます。

成果物のテスト

ビルド成果物を確認するには、プロジェクトのルートディレクトリに

<my-counter></my-counter>
<script type="module" src= "dist/my-counter.es.js"></script>

の内容を含む HTML を作り、ダブルクリックで起動すると良いと思います。

npm run preview というコマンドもありますが、dist ディレクトリ以下に index.html が必要で、
dist ディレクトリはビルドするたびに消えてしまうのでコンポーネント開発では使いづらそうです。

実際に他のサービスからコンポーネントを使う際も、上記のようなスクリプトタグで使うことができます。その際、 script タグに type="module" を忘れずに指定します。ビルドしたコードには、ファイル直下に $ といった短い名前の関数が作られます。そのため、type="module" なしでグローバル領域に読み込んだ場合、既存のスクリプトで jQuery 等を使っている場合に誤動作につながります。

なお、type="module" をつけた場合、そのスクリプトは CORS 判定の対象となりますので、サーバ側は access-control-allow-origin の HTTP ヘッダを必ず返す必要があります。

S3の設定

本番環境で使えるようにするにするため、ビルド成果物の JS を、S3 にデプロイして静的サイトホスティングの機能でホスティングします。

バケットの命名の注意

S3 のバケットを作る際は、バケット名をサービスを提供する URL のホスト名と完全に同じにする必要があります

CloudFront で、CloudFront Functions を使ってリクエストヘッダーを操作する場合、S3に伝わる Host ヘッダーは必ずリクエストしている Host ヘッダーとなり、 Functions 内で変更することは制限されておりできないためです。

静的ウェブサイトホスティングの設定

Hosting a static website using Amazon S3 - Amazon Simple Storage Service

S3 の「プロパティ」タブの一番下から設定します。

バケットポリシーの設定

「アクセス許可」タブ内にバケットポリシーの設定欄があります。

パブリックでの読み取りを許可するポリシーにします。

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicRead",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:::world-visitor.torico-corp.com/*"
}
]
}

CORSヘッダーの設定

「アクセス許可」タブ内の一番下に Cross-Origin Resource Sharing (CORS) の設定欄があります。

[
{
"AllowedHeaders": [
"Authorization"
],
"AllowedMethods": [
"GET",
"HEAD"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]

ファイルのデプロイ

ビルド成果物を S3 にアップロードします。

接続国により2つのファイルを出し分ける設計とするため、今回のビルド成果物の他に、空の JS も別のファイル名でデプロイしておきます。

CloudFront の設定

オリジン

先程作成した、S3 の WebホスティングのURLをオリジンとします。

接続国に応じてリクエストヘッダーを付与する設定

CloudFront の「オリジンリクエストポリシー」という機能で、リクエスト国別のヘッダを付与することができます。

CloudFront の ポリシーページの、オリジンリクエストのカスタムポリシーで、「オリジンリクエストポリシーの作成」を選択し、追加するヘッダーを選べます。

Adding the CloudFront HTTP headers - Amazon CloudFront

CloudFront-Viewer-Country を追加すると、ヘッダーの値として2文字の国名コードが追加されます。「JP」等。大文字になります。

リクエストヘッダによってオリジンに要求するファイルのパスを変えるファンクション

付与したリクエストヘッダーを CloudFront Functions で判定し、リクエストパスを変化させます。
今回はこのようなスクリプトになりました。

なお、handle の引数の event の構造はこのページで紹介されています。

CloudFront Functions event structure - Amazon CloudFront

/*
接続元の国によってリクエストパスにプレフィックスをつけるファンクション
*/

/// 国コードを取得
function getCountryCode(event) {
return event.request.headers['cloudfront-viewer-country']
? event.request.headers['cloudfront-viewer-country'].value
: ''
}

/// 国コード(大文字)から判断するディレクトリプレフィックス
/// SG は初回は必要無いが、検証のために追加
function getDirectoryPrefix(countryCode) {
if(['TW', 'SG'].includes(countryCode)) {
return '/tw';
}
return ''
}

/// uri (パス) はルーティング対象か
// 検証用の index.html はルーティングしたくない。
function uriRootingRequired(uri) {
return ['/my-component.es.js'].includes(uri);
}

function handler(event) {
var countryCode = getCountryCode(event);
var directoryPrefix = getDirectoryPrefix(countryCode);
if (directoryPrefix && uriRootingRequired(event.request.uri)) {
event.request.uri = directoryPrefix + event.request.uri;
}
return event.request;
}

Google Tag Manager でロードする

今回作った Svelte のコンポーネントは、 Google Tag Manager からでも使うことができます。

ただし、Google Tag Manager で独自の HTML タグを書くとエラーとなって公開できないため、
document.createElement でタグを作成しています。

また、script タグに type="module" 忘れずに指定します。これをつけないとグローバル変数領域にロードされてしまうため、既存のスクリプトに悪影響があります。

そして、type="module" を入れた場合、なぜか「document.writeをサポートする」にチェックが入ってないとスクリプトが動作しないため、チェックを入れます。

電子書籍の無料ページが変わりました(漫画全巻ドットコム)



こんにちは、品質管理係の渡辺です。
日々、システムのテストをしていると、新機能に出会えていち早く動かすことができて面白いです。

漫画全巻ドットコムは、徐々に「PHP」から「Nuxt.js+Django」への移行している過程で、各ページもリニューアルしています。
そのうちの一つに電子書籍無料ページにある作品の表示方法を改良しました。

以前の陳列方法は無料の巻数全てを表示
漫画全巻ドットコムには、電子書籍無料ページがあります。
以前は、同じ作品が1~3巻が無料の時は、1~3巻の3冊とも並べていました。
以前の陳列方法
そのため、「24時間100巻無料」というキャンペーンがある時は、1ページまるまる同じ作品が並ぶこともありました。
単行本(単巻)の場合はまだ巻数ごとに表紙イラストが違うので楽しめましたが、「黒崎秘書に褒められたい【マイクロ】」のような単話売り無料だった時は、ほぼ同じ表紙イラストがたくさん並ぶことになります。

単話

そうなると、同時期に無料である他の作品が目立たなくなるデメリットがありました。

商品陳列を1作品につき1冊置きにしました
そこで、ユーザーの目により多くの別作品も見つけてもらえるように、まずは同じ作品が複数巻無料であっても最初の1冊だけを並べるようにして、商品を増やすことにしたところ、ページの商品が充実されました。
1ページで見れる商品が増えて、バラエティー豊かなページになりました。
陳列方法は1冊置き

また、同時に表紙イラストの上部に【1~3巻無料】を表記されているので、いま読める無料巻が何冊あるか、すぐに分かるように工夫もしています。



なお、作品によっては単話売りと単巻売りがあります。
その場合は、単話売り、単巻売りが独立した1つずつの商品となっているので別々に表示されます。
単話・

このように、よく同時に無料になる事があるので、並んで表示されている事もしばしばあります。

無料巻から続く巻も分かりやすくなりました
表紙イラストをクリックすると、作品の販売中の巻数がすべて表示されるようになりました。
続巻

この機能が付いたことにより、どのようなキャンペーンでも同じ作品であれば、無料、割引、通常がこのページでいっぺんに表示されます。(無料が優先されます)
これで、花とゆめコミックスの最新刊記念で1巻と前巻が無料キャンペーンなどがある場合でも、すべての巻を枠内でスクロールするだけで、確認することができるようになりました。
このように、連続した巻ではなく、所々選りすぐりの巻を持ちいて、無料・割引とするキャンペーンは、意外と多いのです。
以前は、隣同士の陳列のみだったのでページを跨いでしまった時など、次にもあることを気づかず読めてかった人も、この機能で今度からは、
連続巻ではない無料巻も逃さず読めるようになったことでしょう。

ぜひこれからも、新たな作品の出会いの場として、漫画全巻ドットコムの電子書籍の無料ページをお気軽に閲覧してみてください。

漫画全巻ドットコムTOP → https://www.mangazenkan.com/

PC設定でキーボードのキー配置が換わる(Windows11の設定)

キーボード画像

こんにちは、品質管理係の渡辺です。
今回はキーボードの設定についてお話します。
以前、外部付けキーボードを試すことがありました。
特にキーボードの設定はせずに、いつものようにキーを打つと、あれっ?と違和感がありました。
違和感の正体は、キーの面に記していない文字・記号が画面に表示されることでした。
よくよくキーの配置を観察しながら、打ってみると、画面に表示されたのは、キーの隣や下にあるキーの文字・記号でした。
具体的には「@」のキーを押すと「 [ 」、「 [ 」を押すと「 ] 」、「半角/全角」を押すと「’」が表示されました。
すぐに「ひらがな入力」「半角/全角入力」「ローマ字入力」などの入力方式を切り換えを試しても解決できませんでした。

「英語キーボード」「日本語キーボード」
そこで、ネットで調べたところ、キーボードのキー配置はいろいろな形があり、国内で主流になっているのは「英語キーボード」と「日本語キーボード」の2種でした。

英語キーボード(Windows11タッチキーボード)
英語キーボード
日本語キーボード(Windows11タッチキーボード)
日本語キーボード

画像のようにキーの個数が異なるため、アルファベットは同じ位置ですが、記号の配置は違っていました。
Windowsでは、キーボード設定で、変更もできるので、どちらのキーボードも使用できます。
もし、キーに記されている@マークが表示されない時は、一度設定を見直してみてください。
次は設定の見直しと変更の仕方を述べていきます。

Windows11 のキーボード設定
ここではWindows11の設定方法です。(Windows10以前では設定場所が異なるかもしれません)

1.Windowsアイコンをクリック > 設定(アイコン)をクリック
マウスの右クリック、左クリックで表示が違いますが、どちらからでもOKです。

▽右クリックした時の画面
右クリック
▽左クリックした時の画面


2. サイドメニューの「時刻と言語」(①)を押す > 「地域と言語」(②)を押す
時間と言語

3. 「優先する言語」にある「日本語」の3点アイコンを押す
(無ければ、言語の追加で日本語を選ぶ) 
優先する言語

4. 「言語のオプション」を押す
言語のオプション

5. 「キーボード」の「レイアウトを変更する」を押す
キーボードレイアウト

6. 「ハードウェアキーボードレイアウト変更」画面で選択の中から「日本語キーボード」を選択する
キーボード選択

7. 選択後は、「今すぐ再起動する」を押す
今すぐ再起動


以上で、キーボードの切り替え設定は完了しました。
再起動後、赤枠のキーを打ってみてください。キーに記された記号が画面に表示されれば、設定完了となります。
確認のキー


実は、スクリーンキーボードでも設定を確認できました
後日、知ったのですが、いまがどっちのキーボード設定なのかを確認する方法は、もう一つありました。
それは、パソコンに搭載のスクリーンキーボード(ショートカットは【Windowsロゴ + Ctrl + O】)を表示するだけで確認することができました。

▽英語(US)キーボード設定
スクリーンキーボード(英語キーボード)

▽日本語キーボード設定
スクリーンキーボード(日本語キーボード)

今回のキーボードのお話はこちらで終わりです。






照合順序が違うカラムの外部結合

増築を繰り返しているMysqlデータベースを使用しているとたまにある問題の解決方法

作成されたタイミングの違うテーブルの照合順序があっていないことがあります。
古いテーブルのカラムの照合順序がutf8_unicode_ciで、
最近作られたテーブルのカラムの照合順序がutf8_general_ci
必要データがこのふたつのテーブルにあるため外部結合しなければならない自体になった。

普通にLEFT JOINしようとすると

SELECT t1.*
FROM t1
LEFT JOIN t2
ON t1.code = t2.code

照合順序が違うカラム同士では外部結合できないとエラーが発生します。

Illegal mix of collations (utf8_unicode_ci,IMPLICIT) and (utf8_general_ci,IMPLICIT) for operation '='

これの解決のためにカラムの設定を変えてしまうのは影響範囲が大きいのでできれば避けたい。
そんな場合にはCOLLATE句を使用することで解決できます。

SELECT t1.*
FROM t1
LEFT JOIN t2
ON t1.code COLLATE utf8_general_ci = t2.code

これで問題なく外部結合して結果を取得することができました。

mysqlのドキュメントにはWHERE句、ORDER句、GROUP句などでの使い方は書いてありましたが、外部結合での使い方は書いてなかったので備忘録として残しておきます。

MySQL 5.6 リファレンスマニュアル 10.1.7.2 SQL ステートメントでの COLLATE の使用

【合格体験記】3週間でAWS Certified Solutions Architect - Associate (SAA)を取得した話

エンジニアの高津です。

8/6にCLF、8/26にSAAに合格したのでそれについて書いていこうと思います。

なぜ受けようと思ったのか

会社からの推奨もあって4月に基本情報技術者試験を受けたのですが、

「基本情報くらいノー勉で受からなきゃ話にならん」

といきがってノー勉で受けたところ見事にあと1問のところで午前試験で落ちてしまいました。

運が悪かったと思いつつもあまりにも不甲斐ない結果で終わってしまったので流石に何か他の資格でもとっておこうと思い、色々検討した結果、今まで個人開発等で触ったことがありかつ弊社でも採用しているクラウドサービスであるAWSの資格試験は非常に馴染みが深く障壁が小さいと思い挑戦しようと思いました。

また普段使う機会のないサービスを学んだり体系的な知識を身につけるという面でも今後にとってプラスになるのではないかと思い受験しました。

勉強開始前のスペック

  • 個人開発で基本的なサービス(EC2,S3,Route53,RDS,IAM,CloudFront etc)は触ったことがある
  • 実務では前のインターン先で少し触ったくらい

CLF受験

CLFは巷では「簡単すぎて受ける価値が無い」などと言われていたりもしますが、SAAの勉強をいきなり開始するのは少し腰が重いと思って受けることにしまいした。結果的にCLFを最初に受けたことでCLF合格→SAA合格までのハードルがかなり低く感じて非常に良かったと思っています。

また、合格特典でSAA受験費用が半額になるのも非常に良いです。

勉強方法

AWS認定資格試験テキスト AWS認定 クラウドプラクティショナー

AWS認定資格試験テキスト AWS認定 クラウドプラクティショナー という参考書を1周して脳にある程度インデックスを貼ってからUdemyの模擬試験問題集をひたすら解きました。

このときに学んだことが土台となってSAAの勉強にもかなり生きた実感はあります。

結果としては797点(700点が合格ライン)で100点近く余裕を持って合格できました。

SAA受験

CLFと同様まずは参考書で体系的な知識をインプットしようと思い**AWS認定 ソリューションアーキテクト − アソシエイト教科書という参考書を1周しました。**

CLFの勉強で使用した参考書のSAA版である**AWS認定資格試験テキスト AWS認定ソリューションアーキテクト - アソシエイトよりも図を用いて丁寧に解説されていたので頭に入ってきやすかったです。しかし、これ1冊で合格するのはCLFと同様難しいのでUdemyの過去問をひたすら解きました。**

CLF合格後、通勤の時間で軽く勉強する程度でまったりと勉強していたのですが、8月下旬にSAA02→SAA03に改定されて試験範囲が広くなることを知り慌てて1週間前に申し込んで追い込みました。

勉強する時間が全然確保できなかったので正直厳しいと思いましたがなんとか合格できました。

改定される前に取得できて本当に良かったです。

資格を取得して思ったこと

資格を取得しても何か特別な能力が身についたり給料が急に上がったりはしませんが、AWS全般的に自身がついたと思います。今までは必要にかられて少し勉強する程度だったのでAWSへの苦手意識もあり、そこまで自信はなかったです。しかし、取得後はプライベートでも気楽にAWSを触れるくらいハードルが下がり、会社内で飛び交うAWS関連の会話も難なくついていけるようになりました。車の免許を取得しただけで日頃から運転しないと運転できないペーパードライバーであるようにAWSの資格も取得しただけでは形だけのものになってしまうのでしっかり手を動かしてAWSを触り続けることは非常に重要です。資格を取得するだけではさほど意味は成さないと思いますがAWSを触るきっかけとしては資格取得は悪くないと個人的に思いました。

今後も余裕があればDVAやSOAも取得して4冠を目指して行きたいと思います。

RaycastとDashで爆速でドキュメント検索する(IDEでも可能)

イベント開発の保坂です。

今回はランチャーアプリ「Raycast」とドキュメント検索アプリ「Dash」を組み合わせたら便利すぎたので、そちらの紹介をしたいと思います。
数クリックでできてしまうので是非試してみてください。

1. Raycastをインストールする

最近話題のランチャーアプリ「Raycast」をまだインストールしていない場合はインストールします。

https://www.raycast.com/
Raycastとは? という方はググってみると紹介されている素晴らしい記事がたくさんあるので、そちらを参考にしてみてください。


raycast

私は使い始めたばかりなのですが特に
  • Quicklinks
  • プラグインのAWS
  • プラグインのNotion
  • Calculator(計算機)
をよく使っている気がします。


2. Dashをインストールする

こちらもまだインストールしていない場合はインストール(購入)をします。

これはドキュメントを一元管理することができ、検索したい内容をいちいち、ググって、公式ページから該当箇所を見つけるという大体毎日するであろうタスクから解放してくれるアプリです。


Django Queryset

ググってみるとスニペット用のアプリと紹介されていますが、私は Dash で スニペット機能は使用していません。

Dash は有料アプリで約30$で購入できます。たまにアップグレード版が出て有料アップデートらしいのですが、公式サイトから最新版をダウンロードしてライセンスを使用してアクティベートしようとしたら私のラインセンスはver5で古いと言われ、勝手に新しいライセンスをダウンロードするリンクがメールで送られて来たので最新ver6を使用できています。


3. Raycast と Dash をつなげる


Raycast の extentions からDash のプラグインをダウンロードすることができます。


これで Raycast 上で dash と打ち込みそのプラグインをエンターしてあとは探したい語句を打ち込むだけです。


4. (おまけ)IDE と Dash をつなげる

Dash はIDEのプラグインにもよくあります。私は Jetbrain の IDE を使用しているのですが、マーケットプレイスからDashを入れます。

プラグインの使い方説明に Cmd-Shift-D (Mac)とあるので、エディター上でググりたい単語にマウスカーソルを合わせて、そのコマンドを打てば一発でそちらのドキュメントが開きます。

検索したい時は大抵開発中でエディターと向かい合っていると思うので、Raycastを使うよりさらに早く開けます。初めてやってみると速すぎて驚きます。



Web ページの動作検証のためのボットスクリプトを Windows 上で作る

この文書は、開発経験の無いチームがウェブアプリケーションの動作検証の責任を持つケースで、検証を簡単なプログラムで行うアプローチについての手法を解説しています。

Webアプリケーションの動作検証の際、手動で実行する以外にプログラムで検証すると便利です。開発者であればは検証コードを書くのは簡単ですが、開発経験の無い方はどこから始めたらいいかわからないと思いますので、比較的用意にスクリプトに入門できるように紹介します。OS は Windows を対象としています。

本記事で紹介しているようなプログラムによるリクエストは悪意の有無にかかわらず、不正アクセス禁止法での不正アクセスとみなされたり、電子計算機損壊等業務妨害罪等に問われる可能性があります。実際にリクエストを行う場合は、自社の管理する、許可されたサーバに対してのみ行うようにしてください。

HTTP の リクエスト・レスポンスの仕組みを知る

まずは、ウェブサーバと通信している HTTP のリクエストがどのようなものか知ることが必要です。この知識があいまいなままだとボットのスクリプトは書けません。

まずは、ウェブブラウザに搭載されている開発者ツールを使ってリクエストやレスポンスを観察するのが、良い勉強になります。

ウェブサーバに対して、

  • どの URL に対して
  • どのHTTP メソッドで (GET, POST, HEAD, PUT 等)
  • どのような HTTP ヘッダーで
    • cookie
    • referer
    • user-agent
  • どのようなリクエスト本文(body)で

以上を意識して、ブラウザの行っているリクエストをスクリプトで再現できれば、どのようなクライアントを使おうが、ウェブサーバからはブラウザでのリクエストと同じようにレスポンスが返ってきます。

Chrome のデベロッパーツールの使い方

  1. Google Chrome を起動してください。
  2. Shift + Ctrl + I を押してください。
    右側にデベロッパーツールが表示されます。
  3. デベローパーツールの上部のタブで、Network を選択してください。
  4. ブラウザの URL 欄に適当にページを打ち込み、ページを表示されてください。
  5. リクエスト一覧が表示されますので、適当なリクエストをクリックして選択してください。
  6. Headers にリクエストヘッダ、レスポンスヘッダ
    Payload にリクエスト本文
    Response にレスポンス本文
    が確認できます。
    特に、Headers のリクエストヘッダでどのようなヘッダを送っているか、確認してください。

Headers の中でも、cookie は特に注目してください。cookie の扱いはボット作成の中で最上位に重要な項目です。不安があれば、他のサイトや書籍を参考に学習してください。

また、 cookie の内容は認証情報を含むため、サイトのログインパスワードと同じぐらい重要な秘密情報です。安易にコピーは行わず、また絶対に他者に教えないようにしてください。ブラウザの cookie を格納している場所は安全ですが、他の場所にコピペすると漏洩リスクとなり、ログイン権限を奪われる危険性があります。

HTTPヘッダとクッキーの学習ができるサイト

HTTP ヘッダ、クッキーについての概要は、とほほ先生のサイト、わわわ先生のサイト、あと Wikipedia を読めば理解できます。

HTTPヘッダ

Cookie

ブラウザを手動で操作する以外での HTTP リクエストを行う方法

HTTP リクエストは、結局は特定の文字をサーバに送るだけですので、様々なクライアントで行うことができますが、よく使われるものを紹介します。

Postman

https://www.postman.com/
フリーの Windows クライアントがあります。GUI でリクエストを構築することができますし、スクリプトによる制御処理も書けます。有用なツールですが、最初は機能が多く複雑なため戸惑うかもしれません。

長所
  • GUI で完結する
  • 大量のテストリクエストの管理がしやすい
  • 署名の計算等、複雑な計算を伴う処理も行える。
短所
  • 複雑なため習得が難しい
  • 単純なリクエストを出すだけであれば冗長

curl

mac や linux を使う方には定番のコマンドラインツールです。Windows 10 からは標準でインストールされています。

長所
  • インストール済みなのですぐに使える
  • 単純なリクエストを1発出すだけなら一番適している
短所
  • 計算を伴う順次リクエストには向かない

Selenium や Puppeteer

プログラムで Chrome などのブラウザを実際に起動し、自動操作するための Selenium や Puppeteer といったツールがあります。

ブラウザを起動するため、ページ内で JavaScript を豊富に扱うページも自動操作し、検証することができます。

最近のウェブサイトは、React や Vue といった JavaScript を用いて表現するサイトが多くなってきており、その場合は Postman, curl, 後述するシンプルなスクリプトでは十分な検証ができない場合がありますので、 ページ内の JavaScript の動作検証が必要になる場合実際のブラウザを実行させる以外に無く、自動操作するには Selenium や Puppeteer を使うしかありません。

扱うには高度なプログラミング知識が必要ですので、今回は言及しません。

長所
  • ブラウザでの JavaScript の実行が必要であればこれ一択
  • 表示レイアウトの確認にも使える
短所
  • 実行環境の構築が難しくて、手間がかかる。
  • 他の、単純なリクエストを出す方式に比べると遅い。
  • 様々な要因により安定させて動作させるのは難しい。業務レベルで使うには高い技術が必要。

Rest Client ( .http)

ドットエイチティーティーピーファイルを作り、その記述したリクエストを簡易に何度も再現させることができます。VSCode や JetBrains エディタの機能として扱うことができ、記述が簡単で読みやすいため私は(当社内でも)かなり使います。

長所
  • 習得が容易
  • スクリプトの構文が容易で書きやすく読みやすい
  • スクリプトのチーム内共有が容易
  • レスポンスへの簡易的なテストを行うことができる
短所
  • 計算を伴う複雑な逐次処理はできない

プログラミング言語でスクリプトを組む

Python, PHP, Javascript, Ruby などで、既にある便利な HTTP クライアントライブラリを使ってスクリプトを組む方法です。
今回の記事ではこちらを今回紹介します。

長所
  • 条件分岐を含む複雑な制御処理が得意
  • 複数セッションによる並列リクエストを再現したい場合は一択
  • 完全無人での自動実行が容易
  • 書いたコードの再利用が容易
  • 処理結果の外部ツールへの連携が柔軟に行える
短所
  • 環境構築が手間
  • 単発のリクエストを検証したい場合は RestClient や Curl, Postman と比べても冗長

Windows に Python 実行環境をインストールする

Microsoft Store からの Python のインストール

Microsoft Store で Python パッケージが提供されるようになり、昔と比べて環境構築が楽になりました。

Microsoft が提供している、初心者向けの Python の開発ガイドが良くできています。この流れにそって進めれば問題なく進められますので、こちらも参考にしてください。

Microsoft Store のアプリを開き、 Python を検索して Python 3.10 をインストールします。

インストール後、念の為再起動を行い、その後コマンドプロンプトを起動して python と打ち込んで、 python が起動するか確かめてください。

Python 公式サイトからの Python のインストール

コマンドプロンプトで、 python と打ち込んで python が起動しないようであれば、Microsoft Store からインストールした Python はアンインストールし、 Python の公式サイトから Windows 版のインストーラパッケージをダウンロードしてインストールしてください。

その際、PATH を追加編集するかのオプションが表示されるので、チェックを入れてください。

https://www.python.org/downloads/

requests ライブラリのインストール

Python の インストールが完了したら、ウェブを操作するボットの作成に必須ともいえる、 requests ライブラリをインストールします。

コマンドプロンプトや PowerShell で、

python -m pip install requests

と入力すると、インストールが完了します。

Visual Studio Code のインストール

エディタは Visual Studio Code を使います。

Microsoft Store から Visual Studio Code を検索してインストールしてください。

プロジェクトフォルダの準備

Windows の、ドキュメントフォルダの下にtest-bot フォルダを作ってください。

VSCode を起動し、 File -> Open Folder で test-bot フォルダを開いてください。

フォルダを開いたら、左側のペインで右クリックし、 New File から first_bot.py というファイルを作ってください。

作成後、右下に「インタープリターを選択」と表示されているようであればクリックしてください。Microsoft Store からインストールした Python が、おすすめに表示されているので選択します。

右下に CRLF と表示されている箇所は、改行コードの設定が表示されています。CRLF は一般的ではないため、クリックして LF に変更しておきます。

Python の機能拡張のインストールがおすすめされると思いますので、Python, Pylance の機能拡張をインストールします。

スクリプトを書く

レスポンス本文を表示するだけのスクリプト

import requests
response = requests.get('https://www.torico-corp.com/')
print(response.text)

これは、 https://www.torico-corp.com/ のレスポンス本文を表示するだけの単純なプログラムです。
書いたら、右上の ▶ ボタンを押して、実行させてください。
出力結果がずらっと表示されます。

requests ライブラリについてのドキュメント

レスポンスの経過時間とステータスコードを表示するスクリプト

import requests
response = requests.get('https://www.torico-corp.com/')
print('経過時間 {}ms'.format(response.elapsed.microseconds / 1000))
print('ステータスコード {}'.format(response.status_code))

このスクリプトは、レスポンスの応答時間とステータスコードをコンソールに表示します。

Python に慣れてきたら、結果をファイルに記録するように改修することで、簡易的な負荷監視などに応用できます。

サイトの検索結果ページからを解析するスクリプト

Webサイトの検索ページにリクエストを行い、結果の HTML をパースしてコンソールに表示するスクリプトです。

HTMLをプログラムで扱えるように解析するために、 BeautifuSoup というライブラリをインストールします。

BeautifulSoup のインストール方法

コマンドプロンプトで

python -m pip install beautifulsoup4

でインストールできます。

BeautifulSoup の解説記事

コード

import requests
from bs4 import BeautifulSoup
response = requests.get('https://tech.torico-corp.com/search/?q=docker')
soup = BeautifulSoup(response.content, features='html.parser')

for h2 in soup.find_all('h2'):
a = h2.find('a')
if not a:
continue
print(a.text)
print(a['href'])

上記スクリプトは、TORICO の技術開発ブログを「docker」で検索し、出てきた記事のタイトルとリンクURL を表示しています。

メールアドレスとパスワードでウェブサイトにログインする

最後に、メールアドレスとパスワードでログインをするスクリプトの雛形を記載します。

requests ライブラリは、クッキー管理を行うことのできる Session というしくみがありますので、それを使います。

解説記事

検証するサイトによりますが、多くの場合は、ログイン時に「CSRFトークンの検証」と「Refererヘッダの検証
User-Agent が悪質なボットでないかの検証」を行っていると思いますので、そこを考慮してスクリプトを作れば、ログインが行えるはずです。

下記のような自動ログインのスクリプトは、必ずご自身が権限を持つサーバにのみ行うようにしてください。他者のサーバに行うと罪に問われる可能性があります。

URL 等は架空のものです。

import requests
from bs4 import BeautifulSoup

# session を作る
s = requests.session()
# User-Agent を設定する場合
s.headers['User-Agent'] = 'Tester Python Bot'

# ログインフォームを取得する
response = s.get('https://example.com/login-form/')

# HTTP のステータスコードに異常が無いか確認
response.raise_for_status()

# ログインフォームをパースする
soup = BeautifulSoup(response.content, features='html.parser')

# パースしたログインフォームから CSRF トークンを取得する
csrf_token = soup.find('input', {'name': 'csrftoken'})['value']

# ユーザー名とパスワードをいれてログインフォームを送信する。
response = s.post(
'https://example.com/login-form/',
data={
'email': 'tester@example.com',
'password': 'MY_AWESOME_PASSWORD',
# 先程取得した CSRF トークンを付与
'csrftoken': csrf_token
},
headers={
# Referer を付与
'Referer': 'https://example.com/login-form/',
})

# HTTP のステータスコードに異常が無いか確認
response.raise_for_status()

# ログイン後の URL が正しいものであるか確認
assert response.url == 'https://example.com/mypage/'

# この時点で、セッション s はログイン済みの状態なので、
# マイページ等をリクエストすることが可能
response = s.get('https://example.com/mypage/myprofile/')

そうだ、mysql8.0サーバーを構築してみよう

ども、お久しぶりです。

オンプレのインフラ構築を担当することが多い四斗邊です。

今回は社内の要望でmysql8.0サーバーを社内ネットワーク上に構築する!というのが目標です。

結構ありふれたネタだと思いますが、社内に向けた手順書のような形で残しておきたかったので今回記事しました。

PC構成


  • CPU intel i5-12400 6コア12スレッド 2.5GHz

  • メモリー CT2K32G4SFD832A メモリー32GB 1枚(レイテンシーは特に考えていない)

  • ベアボーンキット DeskMini B660

  • SSD WDS100T3X0E 1TB

これで10万円を切った構成になっています。
安くてそれなりに社内サーバーとしてはそこそこパワーがある構成にしてみました。
一番の特徴はなによりもベアボーンキットなのでコンパクトなのがいい。
個人的にはCPUはAMD派ですが、2022年8月上旬ではintelが安くてちょうどいいかんじでした。
家庭用なので消耗とか気になりますが、4年ぐらいは耐えてくれてると信じたい。

OSソフトウェア構成


OS ubuntu 22.04
DB mysql8.0.30

ubuntu以外も候補はありますが、構築経験があるのでこちらかなと。

mysqlインストール手順


*注意事項
手順の前にsshとかOS周りの設定は一通りしております。今回の手順はmysqlインストールに特化したものとご了承ください。

mysql-serverのインストール

$sudo apt install mysql-server

mysqlにログインする。

$sudo mysql
#ユーザー確認
mysql >select user,host from mysql.user;

インストールしているときに、すでにrootユーザーは作られているので、以下のコマンドでパスワードを改変する。hogeの部分は任意なパスワードに変える。

mysql > USE mysql; 
mysql >ALTER USER 'root'@'localhost' identified BY 'hoge';
一応権限の確認は以下で確認できる
mysql > show grants for 'root'@'localhost';

後々構築した後に気づいた点で、localhostになっているので、リモート時にはrootユーザーに入れないことがあった。

やっぱりここはrootユーザーのhost=%を作るに限る。(セキュアな面ではもうちょっとhost部分は制限したほうがいいかも)

mysql > CREATE USER 'root'@'%' IDENTIFIED BY 'hoge';
mysql > GRANT ALL ON *.* TO 'root'@'%';

これでリモート上でもrootで入れる。

文字のエンコードはutf-8に設定したかったので、my.cnfを以下のようにいじる。参考にしたURL↓

https://qiita.com/is0me/items/12629e3602ebb27c26a4

まずはmy.cnfの場所の特定。どうやら/etc/my.cnfが先で、次に/etc/mysql/my.cnfの順番で読み込まれるようだ。

mysql --help | grep my.cnf 
#以下結果 
order of preference,my.cnf, $MYSQL_TCP_PORT,
/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf

/etc/mysql/my.cnfの中身を見ると以下のような記載がありました。

!includedir /etc/mysql/conf.d/ 
!includedir /etc/mysql/mysql.conf.d/

/etc/mysql/conf.d/のディレクトリに入れると反映されるのでは?ということで、ここにmy.cnfを設置してみる。

保存するその前に、一旦、サーバー上でmysqlが止まっているか確認する。

mysqlが止まっているか以下でわかる。(すでに稼働している場合は周りに影響しないか注意してください。)

$sudo service mysql stop
#以下の内容を入れて/etc/mysql/conf.d/my.cnfに保存する。 
[mysqld]
character-set-server=utf8mb4
[mysql]
default-character-set=utf8mb4
[client]
default-character-set=utf8mb4
#更新したら以下でリスタートする。
service mysql restart

あとは、リモート上でも接続できるようにサーバー上でリッスンを設定する。今回は、mysqlのポート3306をリッスンさせる。

mysqlインストール時にすでにポートはリッスンしているので、bind-addressとmysqlx-bind-addressのアドレスを変更するようです。

/etc/mysql/mysql.conf.d/mysqld.cnf
#bind-address = 127.0.0.1
#mysqlx-bind-address = 127.0.0.1

更新したら以下でリスタートする。

service mysql restart

そうすると、自動的に全てのアドレスから全てのアドレス:3306でリッスンできる。

sudo ss -talpn 
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 151 *:3306 *:* users:(("mysqld",pid=3822,fd=23))
ただ、この設定は少し強引すぎる。

社内サーバー用とかで事前に外部からのアクセスを制限できているなど限定した場面で活用するのが望ましい。というか一応ufwも確認する。

sudo ufw allow 22 #ssh 
sudo ufw allow 3306 #mysql
sudo ufw enable
sudo ufw status numbered
To Action From -- ------ ----
[ 1] 22 ALLOW IN Anywhere
[ 2] 3306 ALLOW IN Anywhere
[ 3] 22 (v6) ALLOW IN Anywhere (v6)
[ 4] 3306 (v6) ALLOW IN Anywhere (v6)

これでmysqlとsshのみ通るようにポートは開けることが確認できた。

ここまでで一通りのmysqlサーバの完成となる。実際に自身のPC上で確認してみてください。

以下のコマンドで入れたら成功です。

(もちろんローカル上にもmysqlは入れてるよね?)

mysql -h XXXX.example.co.jp -u root -p
パスワードを入力:hoge

できましたかね?これであなたもmysqlサーバー構築者だ、おめでとう!

できない人は個別のトラブルシューティングに従って解決してください。

結構最低構成でできたはずなので、時間はかからないのかなと思っています。

みなさん試してみてください。

便利なfabricを使ってみる!

4月1日に新卒で入社したエンジニアの宮副です。
入社してすでに3ヶ月が過ぎました。
今回は、業務の中でもよく使われている「fabric」について書いていきます。

fabricとは

fabricはpythonのライブラリで、コマンドラインで関数を実行できるツールです。
さらに、SSH経由でシェルコマンドを実行することでもできます。
fabricをに定義することで、デプロイ時などで毎回打つ幾つものコマンドを省略することができるのでとても便利です。

実際にやってみる

実際にどういう風に使っているかを書いていきます。
まずはfabric(fabric3)をインストールします。

$pip3 install fabric3

次にfabric.py か fabfile/__initi__.py 内に関数を書くことでコマンドを実行できるようになります。
会社では fabfile/__init__.py 使っています。

def hello():
    print("Hello world!")

と書くと、

$fab hello

これで print() が実行されます。

では、次に少し応用してみます。

from fabric.api import local

def deploy_git():
    local("git add . && git commit -m 'commit message'")
    local("git push")

実行してみましょう。

$fab deploy_git

これでgitコマンドを打たなくても、pushすることができます。

また、

from fabric.api import local

def add():
        local("git add .")

def commit():
        local("git commit -m 'commit message'")

def push():
        local("git push")

def deploy_git():
    add()
        commit()         push()

このように構造化することで便利になります。

それぞれの環境にデプロイするときには、hostを指定してデプロイすることもできます。

from fabric.api import local

#開発環境
def dev():
    env.hosts = ['dev.example.com']

#本番環境
def production():
    env.hosts = ['www.example.com']

def deploy_env():
        #デプロイ処理
$fab dev deploy_env
#開発環境にデプロイ

$fab production deploy_env
#本番環境にデプロイ


__init__.py
に書いてるコマンドを見たいときには、

$fab -l

とするとfabコマンドを一覧で見ることができます。

まとめ

今回は業務のデプロイ作業で使っているfabricについて紹介しました。
入社して3ヶ月経ちましたが、まだまだ分からないことだらけの毎日です。
そんな僕でも、先輩エンジニアの方々に質問すれば丁寧に優しくわかりやすく教えていただけるので楽しく業務をすることができています。

コピペですぐ動く! Gmail をスプレッドシートに書き出す Google Apps スクリプト

あるメーリングリスト(Google グループ) に送信しているメールアドレスの一覧を作りたかったので今回のスクリプトを書きました。

普段 Gmail を使われている場合、Google Apps Script (GAS) を使うことでメールの処理をスクリプトで簡単に自動化することができます。

今回は、Gmail で受信したメールを検索し、検索結果を Google スプレッドシートに書き出す方法を紹介します。

ウェブ上のツールでスクリプトを少し書くだけで実現できます。メールを使う業務の自動化に応用できると思います。

1. Gmail の検索条件を作る

Gmail を開き、検索窓を使って対象のメールを絞り込みます。

to:label: などの演算子を活用して、絞り込んでください。

例えば、 mailinglist@example.com に向けて送信されている、 14日以内に受信したメールを絞り込む場合、

to:mailinglist@example.com newer_than:14d

として検索します。

その他の演算子はこちらに紹介があります。

Gmail で使用できる検索演算子 - Gmail ヘルプ

作成した検索条件は記録しておきます。

2. 書き出し先のスプレッドシートを作る

Google スプレッドシートを開きます。

https://docs.google.com/spreadsheets/

「新しいスプレッドシートを作成」の「空白」を選択します。

作成されたスプレッドシートのURL を確認します。

https://docs.google.com/spreadsheets/d/<ここの部分>/edit#gid=0

上記の、英語の乱数部分がスプレッドシートのIDです。これを記録しておきます。

3. Google Apps Script を作る

https://script.google.com/ を開きます。

「新しいプロジェクト」をクリックします。

「無題のプロジェクト」となっている箇所をクリックし、「メールをスプレッドシートに書き出し」など、適当に変更します。

スクリプトの内容は、下記内容をそのままコピペします。

function exportGmails() {
var book = SpreadsheetApp.openById('<ここにスプレッドシートIDを記入>');

var criteria = '<ここにGmail検索条件を記入>';
var threads = GmailApp.search(criteria);

var sheet = book.getActiveSheet();
sheet.getRange(1, 1).setValue('日付');
sheet.getRange(1, 2).setValue('宛先');
sheet.getRange(1, 3).setValue('From');
sheet.getRange(1, 4).setValue('ReplyTo');
sheet.getRange(1, 5).setValue('件名');

for (var i = 0; i < threads.length; i++) {
messages = threads[i].getMessages();
message = messages[0];
console.log(message.getFrom());
rowNumber = i + 2;
sheet.getRange(rowNumber, 1).setValue(message.getDate());
sheet.getRange(rowNumber, 2).setValue(message.getTo());
sheet.getRange(rowNumber, 3).setValue(message.getFrom());
sheet.getRange(rowNumber, 4).setValue(message.getReplyTo());
sheet.getRange(rowNumber, 5).setValue(message.getSubject());
}
}

<ここにスプレッドシートIDを記入> の箇所を、2で作成した スプレッドシートのURLに含まれるIDに書き換えます。

<ここにGmail検索条件を記入> の箇所を、1で作成した Gmailの検索条件に書き換えます。

保存ボタンを押して、実行を押します。

初回起動時は、「承認が必要です」というダイアログが表示されるので、「権限を確認」をクリック。

個人用の Gmail の場合、「このアプリは Google で確認されていません」となるので、「詳細」をクリックして 「(安全ではないページ)に移動」をクリック。
(企業の Google Apps の場合、この警告は出ません)

許可ダイアログで「許可」をクリック

するするっとコードが実行されて、スプレッドシートに書き込まれます。

スプレッドシートに書き出す項目を変更する場合、コードを修正してください。

コード中に、message.getDate(), message.getTo() などのメソッドでメール内の属性を取得していますが、その他の属性も書き出すことができます。メールオブジェクトのメソッドのリファレンスはここにありますので参考にしてください。

Class GmailMessage | Apps Script | Google Developers

Ubuntu22 に MicroK8s で Kubernetes 環境を構築し、その中で Rancher を起動する

今まで(2021年頃まで) は、社内サーバの Kubernetes 環境を作る際は、 Rancher を Docker で起動し、その中の RKE で Kubernetes クラスタを構築していました。

ところが、それだと OS のアップデートがあったりした時など、年一回ぐらいのペースでトラブルがあり、環境が再構築不能になってしまっていました。

Rancher + RKE で Kubernetes 環境を作っている場合、トラブルの原因を追うのが非常に難しくて、原因まで解明して解決できたことはありません。

今回は、 Kubernetes 環境は Ubuntu の MicroK8s で起動し、その K8s の中で、 Deployment として Rancher を起動するようにしました。

試してみた所、なかなか快適だったため、今後もこのパターンは使っていこうと思います。

OS は Ubutntu 22.04 で、ノードはシングルノード構成です。やはり OS が Ubutnu の場合は MicroK8s が簡単で安定しており、Ingress なども一発で有効化できるため、セットアップは楽でした。

1. MicroK8s の セットアップ

1-1. インストール

sudo snap install microk8s --classic

1-2. ユーザーに権限を付与する

sudo usermod -a -G microk8s ubuntu
sudo chown -f -R ubuntu ~/.kube
newgrp microk8s

1-3. DNS, Ingress の有効化

microk8s enable dns ingress

1-4. ダッシュボードを使う場合

Rancher が起動したらダッシュボードは不要だと思いますが、Rancher 起動前の確認用として重宝します。

microk8s enable dashboard

Kubernetes のノード上で

microk8s kubectl port-forward -n kube-system service/kubernetes-dashboard --address 0.0.0.0 31443:443

してから、https://<your-host>:31443/ を見る

2. kubeconfig の取得

microk8s config

コピペして、 Mac の .kube/config-<Config名> にコピーしておく

3. namespace の作成

※ Macで実行

#!/usr/bin/env zsh

export KUBECONFIG=${HOME}/.kube/config-<Config名>

kubectl create namespace <ネームスペース>

4. secrets の作成

SSL証明書 (wildcard.example.com.key, wildcard.example.com.crt) を mac のディレクトリに用意して、

※ Macで実行

#!/usr/bin/env zsh

export KUBECONFIG=${HOME}/.kube/config-<Config名>

kubectl -n <ネームスペース> create secret tls tls-certificate \
--key <wildcard.example.com.key> \
--cert <wildcard.example.com.crt>

5. Rancher のインストール

サーバに /data/rancher ディレクトリを作っておく

deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-awesome-rancher-deployment
namespace: <ネームスペース>
spec:
replicas: 1
selector:
matchLabels:
app: my-awesome-rancher
template:
metadata:
labels:
app: my-awesome-rancher
spec:
containers:
- name: my-awesome-rancher
image: rancher/rancher:v2.6-head
imagePullPolicy: Always
ports:
- containerPort: 80

volumeMounts:
- name: data-rancher
mountPath: /var/lib/rancher

imagePullSecrets:
- name: ecr-credeintial

volumes:
- name: data-rancher
hostPath:
path: /data/rancher

service.yml

apiVersion: v1
kind: Service
metadata:
  name: my-awesome-rancher-service
namespace: <ネームスペース>
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 80
name: my-awesome-rancher-http
selector:
app: my-awesome-rancher

ingress.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-awesome-rancher-ingress
namespace: <ネームスペース>
spec:
tls:
- hosts:
- my-awesome-rancher.example.com
secretName: tls-certificate
rules:
- host: my-awesome-rancher.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-awesome-rancher-service
port:
number: 80

※ mac で実行

#!/usr/bin/env zsh

export KUBECONFIG=${HOME}/.kube/config-<Config名>

kubectl apply -f deployment.yml
kubectl apply -f service.yml
kubectl apply -f ingress.yml

6. Rancher の ブートストラップパスワードの取得

Rancher を起動すると、「ブートストラップパスワード」が発行され、ログに表示されます。

今回は Kubernetes の Pod として起動しているため、Pod のログを grep します。

 ※ Macで実行

#!/usr/bin/env zsh

export KUBECONFIG=${HOME}/.kube/config-<Config名>

pods=( $(kubectl get pod -n <ネームスペース> | egrep -o "my-awesome-rancher-deployment-[a-zA-Z0-9-]+") )

for pod in "${pods[@]}" ; do
kubectl logs -n <ネームスペース> ${pod} | grep "Bootstrap Password:"
done

7. ブラウザから Rancher へのログイン

my-awesome-rancher.example.com (仮のドメインです) というドメインで Rancher が起動しているはずなので、DNS を設定してからブラウザでアクセスします。

Bootstrap Password の入力を求められるので、先程取得したものを入力します。

ローカルで起動している MicroK8s のシングルノードクラスタを自動的に認識し、管理できるようになります。

付録

kubernetes の secret へ、AWS の EKS のログイントークンを登録する Python スクリプト

当社は、Docker イメージリポジトリは AWS ECR を使っています。
ローカルPCの ~/.aws/credentials の認証情報を元に、 Kubernetes の Secret を作るスクリプトを紹介します。

#!/usr/bin/env python3

import subprocess
import re
import sys

namespace = '<ネームスペース>'
secret_name = 'ecr-credeintial'
aws_region = 'ap-northeast-1'
docker_server = 'https://<AWS-ID>.dkr.ecr.ap-northeast-1.amazonaws.com'


def main():
output = subprocess.check_output([
'/usr/bin/aws', 'ecr', 'get-login',
'--no-include-email', '--region', aws_region,
]).decode()
words = output.split()
username = words[words.index('-u') + 1]
password = words[words.index('-p') + 1]

// 既に作成済みの secret を消す
command = [
// kubectl の実行環境に合わせて調整してください
'microk8s', 'kubectl', '-n', namespace, 'delete', 'secret', secret_name]
subprocess.run(command)

// secret を再登録する
command = [
'microk8s', 'kubectl', '-n', namespace, 'create', 'secret',
'docker-registry', secret_name,
f'--docker-username={username}',
f'--docker-password={password}',
f'--docker-server={docker_server}'
]
subprocess.run(command)


if __name__ == '__main__':
main()
Search