【Laravel】バーコード・スキャナーでIBSNを読み取って本の入荷処理をする

さてさて、この間【Laravel + JavaScript】QRコードで自動ログインする機能をつくるという記事を公開したのですが、これはPCについているウェブカメラでQRコードを読み取るというシステムでした。

この記事のウリは、「ウェブカメラさえあればQRコードを読み取ることができるよ」というもので、(時には高価な)機械を買わなくともブラウザだけで実装できるということを知ってほしい気持ちもありました。

一方そんな中、実は「機械をつかった読みとり」の開発もやってみたいという願望があったのも事実で、結論から言うと前回のセキュリティキーを購入する際、同じくバーコードのスキャナーも買ってしまっていました。(こちらは、¥2,180で比較的安かったですが😅)

↓↓↓ これです

というのも、私は以前あるネットカフェ系列が利用している検索システムの開発に参加したことがあるのですが、その際、本の入荷時処理システムの開発をやりたかったのですが、残念ながら別の開発になってしまったので、結果として今更作りたくなっちゃったんですね😂

ということで、今回はバーコード・スキャナー(BEVA バーコードリーダー)を使って本の入荷処理システムをつくってみたいと思います。

ぜひ皆さんのお役に立てると嬉しいです。

開発環境: Laravel 5.8、Vue 2.6、Google Chrome 75

やりたいこと

今回やりたいことの流れは以下のようになります。

  1. バーコード・スキャナーでISBNを読み取る
  2. 読み取ったデータを送信
  3. Yahoo 商品APIから本のタイトルと詳細を取得
  4. データベースにそれらのデータを保存する

という流れになります。

※ ちなみにネットカフェの開発では本のマスターはすでに依頼主がお持ちでした。そちらはネット上からデータ取得していたわけではありません。念のため。

では実際に開発を進めていきましょう!

準備する

今回、本のデータはYahoo 商品APIを使いますので、まずは登録してログインしアプリケーション登録にアクセスして以下の手順を実行してください。

アプリケーションの種類は「サーバーサイド」を選択(おそらく初期状態で選択済みです)

そして、同じページの「アプリケーション名」にお好みの名前、「サイトURL」に利用するサイトのURLを入力。そして、「同意する」を選択して「確認」ボタンをクリックします。

すると、以下のように確認画面になりますので、入力が正しければ「登録」ボタンをクリックしましょう。

登録が完了すると、次のようにIDが表示されますので、この「Client ID」をコピーしておいてください。このID(appid)は後で使います。

では、次からはLaravel側の作業になります。

データベースにテーブルをつくる

では、まず本のデータを保存しておくテーブル「books」をDBに作成しておきましょう。

以下のコマンドを実行してください。(モデル + マイグレーションを一気に作成してくれます)

php artisan make:model Book -m

コマンドを実行したら/database/migrations/****_**_**_******_create_books_table.phpというファイルが作成されているのでこのファイルを開いて以下のようにします。(太字が追加した場所です)

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateBooksTable extends Migration
{
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('isbn');
            $table->string('title');
            $table->string('description');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('books');
    }
}

変更が完了したら、以下のコマンドでマイグレーション実行したら準備は完了です。

php artisan migrate

作成したテーブルはこのようになります。

ルートをつくる

続いて、/routes/web.phpを開いて必要なルートを追加します。

Route::get('barcode_scan/input', 'BarcodeScanController@input');
Route::post('barcode_scan/register', 'BarcodeScanController@register');

上のルートが実際にブラウザに表示するもので、もう一つがAjaxでデータ送信するものです。

コントローラーをつくる

続いて、先ほどルートで指定したコントローラーをつくります。
以下のコマンドを実行してください。

php artisan make:controller BarcodeScanController

すると、/app/Http/Controllers/BarcodeScanController.phpが作成されるので、中身を以下のようにします。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class BarcodeScanController extends Controller
{
    public function input() {

        return view('barcode_scan.input');

    }

    public function register(Request $request) {

        $result = 'not_found';
        $isbn = $request->isbn;
        $appid = '(あなたのYahoo商品APIのID)';
        $category_id = 10002; // 本のカテゴリID
        $api_url = 'https://shopping.yahooapis.jp/ShoppingWebService/V1/json/itemSearch'.
                    '?appid='. $appid .
                    '&category_id='. $category_id.
                    '&isbn='. $isbn;
        $json = file_get_contents($api_url);
        $data = json_decode($json, true);
        $total = intval($data['ResultSet']['totalResultsAvailable']);

        if($total > 0) {

            $item = $data['ResultSet'][0]['Result'][0];
            $exists = \App\Book::where('isbn', $isbn)->exists();

            if(!$exists) {

                $book = new \App\Book();
                $book->isbn = $isbn;
                $book->title = $item['Name'];
                $book->description = $item['Description'];
                $book->save();
                $result = 'complete';

            } else {

                $result = 'already';

            }

        }

        return ['result' => $result];

    }
}

register()の中でやっているのは、Yahoo商品APIにISBN情報をつけてアクセスし、もし本が見つかれば「タイトル」と「詳細」を取得してDBに保存します。

ちなみに、APIから返ってくる情報によって以下の3パターンで返り値を変えています。

  1. 本の登録が完了 ・・・ complete
  2. 本が見つからない ・・・ not_found
  3. 本は見つかったがすでに登録されている ・・・ already

ビューをつくる

では最後にビューの作成です。
/resources/views/barcode_scan/input.blade.phpを作成し中身を以下のようにしてください。

<html>
<body>
    <div id="app">
        <div v-if="processing">
            処理中...
        </div>
        <div v-else>
            <div>
                <input type="text"
                   ref="input"
                   v-model="isbn"
                   @focus="onInputFocus"
                   @blur="onInputBlur"
                   @keydown="onInputKeydown"
                   @keypress.enter="onInputKeypress">
                <button type="button" @click="clearInput">クリアする</button>
            </div>
            <h3 v-if="inputFocusing">バーコードを読み取ってください。</h3>
            <h3 v-else>入力ボックスをクリックしてください</h3>
            <div v-text="message"></div>
            <h1 v-if="isImeJapanese">[エラー] 日本語モードになっています。半角モードへ変更してから実行してください。</h1>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>

        new Vue({
            el: '#app',
            data: {
                isbn: '',
                message: '',
                inputFocusing: false,
                processing: false,
                isImeJapanese: false
            },
            methods: {
                onInputFocus() {

                    this.inputFocusing = true;
                    this.isbn = '';

                },
                onInputBlur() {

                    this.inputFocusing = false;

                },
                onInputKeydown(e) {

                    if(e.which === 229){

                        this.isImeJapanese = true;

                    }

                },
                onInputKeypress(e) {

                    this.isbn = e.target.value;
                    this.processing = true;
                    const url = '/barcode_scan/register';
                    const params = { isbn: this.isbn };
                    axios.post(url, params)
                        .then((response) => {

                            const result = response.data.result;

                            if(result === 'complete') {

                                this.message = '登録が完了しました';

                            } else if(result === 'already') {

                                this.message = '[Warning] すでに登録されています';

                            } else if(result === 'not_found') {

                                this.message = '[Error] データが存在しません';

                            }

                        })
                        .catch((error) => {

                            this.message = '[Error] 登録に失敗しました。';

                        })
                        .then(() => {

                            this.processing = false;
                            this.isbn = '';

                            Vue.nextTick(() => {

                                this.$refs.input.focus();

                            });

                        });

                },
                clearInput() {

                    this.isbn = '';
                    this.$refs.input.focus();
                    this.isImeJapanese = false;

                }
            },
            mounted() {

                this.$refs.input.focus();

            }
        });

    </script>
</body>
</html>

この中で重要な<input>タグの説明は次のとおりです。

BEVAバーコード・スキャナーは、キーボードと同じようにキーを入力され、最後にエンターキーを押してくれます。

そのため、<input>タグには、keypressイベント(今回はEnterのみ起動)を付けてonInputKeypress()を実行し、その中でAjax送信をしています。

また、Ajax送信で返ってくるのは、先ほどコントローラー内で作成した「complete」「not_found」「already」のうちどれかで、この内容によってメッセージを変えて表示しています。

なお、<input>タグにフォーカスが当たっていない場合は入力自体ができませんので、もしフォーカスされていない場合は警告を出すようにしています。

そして最後に、今回一番どうしたものかを思案したのがonInputKeydown()の部分です。なぜなら、先ほども書きましたがバーコード・スキャナーはキーボードと同じ挙動をするので、IME(日本語入力)が有効になっていると最後のEnterキーが文字の確定に使われてしまい、keypressイベントを実行できなくなってしまうからです。

そこで、日本語入力が有効になっているかどうかをe.whichの内容で判断し、状況によってはエラー表示をするようにしています。

テストしてみる

では、実際にテストしてみましょう!

ページを開くと以下のような表示になります。

もしフォーカスが外れるとこのように注意書きを表示します。

では、先にわざと日本語入力をオンにしてバーコードを読み取ってみましょう。

うまくいきました!
では、今度は半角にして私の好きな本のバーコードを読み取った結果が次のとおりです。

こちらもうまくいきました!

※ ただし、本によっては説明文が存在しないものもあるようです。

お疲れ様でした。

おわりに

ということで今回は、USBのバーコード・スキャナーを使って本の入荷処理をやってみました。

今回購入した機械を使ってみてまず感じたのは「精度が高い!」というものです。その昔学生だった頃にアルバイトで棚卸し作業をしたことがあって、その時はなかなか読み取れなかったりもしたんですけど、今やたった3,000円以内で精度の高いバーコード処理ができてすごいなと感じました。(しかも当時のは重いのなんの。今回のは片手でもラクラクだと思います)

ステマになりそうなので、この辺で機械のことだけじゃなくコードでひとつ気になっていることも。

それは記事の中でも書いた「日本語入力」です。

正直言うと、JavaScriptIMEの変更ができると嬉しいんですがこれってセキュリティ上良くないのから実装されてないんでしょうか。

どうやらIEには<input>タグに入力方法をしていする独自のCSSがあるらしいんですが、これ、Chromeにもほしいなと思ったりもします。(というか、久しぶりにIEにはあって他にはない機能にめぐりあいました😂)

今回は以上です。

みなさんも大量のバーコードを読み取る必要がある場合は、私のように実装してみてはいかがでしょうか。

ではでは〜!

この記事が役立ちましたらシェアお願いします😊✨