野球リーグスコア管理システムの開発

2019-01-13に作成

image
野球リーグスコア管理システムキャップ野球情報局に関する進捗です。

使っている技術など

  • NodeJS
  • ReactJS
  • netlify
  • MySQL
  • materializecss
  • react-bootstrap
  • react-bootstrap-table-next

旧システムについてはこちらの記事をご覧ください。

残りタスクリスト

trello

所有者限定モードのためこのボードには投稿できません ボードとは?

OGPサーバをvercelに移設した

ソース

前回、puppeteer-clusterで同時起動の制御をしたはずだったのだが、
書き方が悪いのか同時アクセスすると暴走してVPSの応答がなくなってしまうので、vercelに分離した。

vercelとは

vercel

herokuと同じようにサーバサイドのプログラムをデプロイできるサービス。
netlifyはフロントエンド特化。

OGPサーバの用途

ルーティングに苦戦

vercelはnow.jsonでルーティングを記述している。
正規表現で各メソッドにreq.query.idを渡す場合の記述の仕方はこんな感じ。

 "routes": [
        { "src": "/cap-baseball/(?<id>[^/]*)", "dest": "/screenshotCap.js?id=$1" },
        { "src": "/(?<id>[^/]*)", "dest": "/screenshot.js?id=$1" }
    ]

puppeteerでスクリーンショット

このあたりは前回の記事で説明しているので不要かなと思う。
cluster使わない書き方に戻してます。

const browser = await puppeteer.launch({
    args: chrome.args,
    executablePath: await chrome.executablePath,
    headless: chrome.headless,
  });
  const page = await browser.newPage();
  await page.goto("https://pawapro-gen.netlify.app/view/" + id);
  await page.setCacheEnabled(false);
  await page.waitForSelector("#main");
  const fullPage = await page.$("#main");
  const fullPageSize = await fullPage.boundingBox();
  const VIEWPORT = { width: 600, height: 450, deviceScaleFactor: 1 };
  await page.setViewport(
    Object.assign({}, VIEWPORT, { height: fullPageSize.height })
  );
  await page.waitFor(5000);

  const elements = await page.$$("#main");
  let img;
  for (const [index, element] of elements.entries()) {
    img = await element.screenshot();
  }
  await page.close();
  await console.log("screenshot done.");
  return img;

キャップ野球情報局v2.0リリースしました。

キャップ野球情報局」というサイトを作っています。

アップデート履歴

ちまちまとマイナーアップデートを重ねていましたが、
5/17からNextJSへ移植するのと同時に、次のメジャーアップデート(v2.0)を7月リリースを目標に作っています。

次バージョン新機能

twitterアカウントを持っていれば誰でもログインできます。

マイページ

ユーザページ編集

別サービス「みんなのSCORE」のデータに対応する形で選手ページを持っています。

選手へのコメント機能

選手間で「この選手はどういう選手です」という他己紹介をする機能です。

チームページ編集

別サービス「みんなのSCORE」のデータに対応する形でチームページを持っています。
チームの紹介のほか、チームのテーマカラーが設定できます。

イベント登録・編集

キャップ野球には、主なイベントとして

  • 大会
  • リーグ
  • 練習会

がありますが、それらの情報を登録・編集することができます。

次バージョンに採用している技術

  • フロント
    • NextJS
    • ReactJS
    • Netlify
    • TypeScript
  • サーバサイド
    • NodeJS
    • MySQL
    • Docker(-compose)
    • TypeScript
  • ミドルウェアなど
    • slack
    • firebase
    • cloudinary

imgurではなくcloudinaryを採用した理由

運営者ギルドでは画像ストレージとしてimgurを薦められていたのですが、imgurとcloudinary両方を実装して、使いやすさの観点からcloudinaryを採用することにしました。

ログイン

imgurはOAuth認証すればログイン状態でアップロードできるのですが、ブラウザでPINが必要など使い勝手と実装に難がある印象です。

匿名アップロード

比較的実装が簡単ですが、imgurでは匿名アップロードした画像をGUIでは管理できません。

工数的にはcloudinaryのログイン状態アップロード≒imgurの匿名アップロードという印象だったので、GUIで全体管理ができるcloudinaryを選びました。用途としては無料枠で足りると思います。

みんなのSCOREがver4αになりました。

NodeJS側

続・Twitterログイン

以下のパッケージを使う。
- passport-twitter
- twitterログインを行うライブラリ
- express-session
- ログイン情報をセッション(メモリ)に格納するライブラリ。
- express-mysql-session
- 揮発するメモリのセッション情報をmysqlに格納するライブラリ。

reactJSからAPI(NodeJS/Express)へはcredentialsを要求するようにする。

データ保全の強化

ユーザが書き換えるテーブルに履歴テーブルを用意した。
これでこのプロジェクトの合計テーブル数は21となった。

ユーザの権限周りの実装

これまで権限レベルが単一だったが、今回権限を制限したユーザを想定しているので権限周りの実装を行った。

ReactJS側

自動入力コンポーネントの移行

スコア入力で大人数の中から選手を選ぶのに自動入力コンポーネントを使用している。

react-autocompleteはどうもモバイルフレンドリーではなかったようで、モバイルでサジェストが見えなくなってしまう欠点があったので使用するコンポーネントを移行した。

SCSS化

他プロジェクトではすでに導入しているSCSSをこのプロジェクトでも導入。最も古参プロジェクトだったのでCRA(react-scripts)のバージョンを上げるなど。

その他

みんなのSCORE、version4開発開始

長らくボードを放置していましたが、
みんなのSCOREのversion4に向けて開発を開始しました。

4/15修正内容

三塁打対応

  • OPS
  • 長打率

の計算を三塁打を加味したものに修正。

セイバーメトリクス

メンテナンスの都合上、セイバーメトリクスの計算をSQLからTypescript側で行うように修正。

修正前(SQL)

        CASE
            WHEN ((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is null THEN 0
            WHEN ((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is not null THEN ((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats))
        END as slg,
        CASE
            WHEN ((sum(hit)+sum(four_ball))/sum(tpa))+((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is null THEN 0
            WHEN ((sum(hit)+sum(four_ball))/sum(tpa))+((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is not null THEN ((sum(hit)+sum(four_ball))/sum(tpa))+((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats))
        END as ops,
            (sum(hit)+sum(four_ball))/sum(tpa) as obp,
            ((((sum(hit)+sum(twobase)*1+sum(homerun)*2)+0.26*sum(four_ball)-0.03*sum(strike_out)+3*sum(tpa))*(sum(hit)+sum(four_ball)+2.4*sum(tpa)))/(9*sum(tpa))-0.9*sum(tpa))*27/(sum(at_bats)-sum(hit)) as RC27,
            CASE
            WHEN sum(at_bats)/sum(strike_out) is null THEN 0
            WHEN sum(at_bats)/sum(strike_out) is not null THEN sum(at_bats)/sum(strike_out)
        END as not_strike_out,
        CASE
            WHEN sum(at_bats)/sum(homerun) is null THEN 0
            WHEN sum(at_bats)/sum(homerun) is not null THEN sum(at_bats)/sum(homerun)
        END as avg_homerun,
        CASE
            WHEN sum(rbi)/sum(at_bats) is null THEN 0
            WHEN sum(rbi)/sum(at_bats) is not null THEN sum(rbi)/sum(at_bats)
        END as avgRbi,

修正後

public calcSABR = (players) => {
    for(let i = 0; i < players.length; i++){
      players[i].average = players[i].hit/players[i].at_bats;
      players[i].slg = (players[i].hit + players[i].twobase*1 + players[i].three_base*2 + players[i].homerun*3 )/players[i].at_bats;
      players[i].obp = (players[i].hit + players[i].four_ball)/players[i].tpa;
      players[i].ops = players[i].slg + players[i].obp;
      const rc27b = players[i].slg * players[i].at_bats + 0.26 * players[i].four_ball - 0.03 * players[i].strike_out;
      const rc27a = players[i].slg * players[i].at_bats + players[i].four_ball;
      const rc27c = players[i].at_bats + players[i].four_ball;
      players[i].rc27 = ((rc27a + 2.4*rc27c)*(rc27b + 3*rc27c)/(9*rc27c))-(0.9 + rc27c);
      players[i].not_strike_out = players[i].at_bats/players[i].strike_out;
      players[i].avg_homerun = players[i].at_bats/players[i].homerun;
      players[i].avgRbi = players[i].rbi/players[i].at_bats;
    }
    return players;
  }

残りのversion4 実装予定機能

スコア入力一般開放

  • twitterログイン
    • 先日の蓋々交換で技術調査・実装済み
  • スコア入力画面レスポンシブ対応

要件

チーム管理者は

  • 自分のチームの成績のみ入力できる
  • チームメンバーを追加できる
  • メンバーのサジェストを自チームに限定する

その他バグ修正

リファクタリング

テストコード実装

意見募集

みんなのSCORE、キャップ野球情報局はユーザの皆さんのご意見を募集しています。

チーム成績登録API実装完了

チーム成績登録API実装完了

node-mysql2が数値を文字列として返してくる

おかげでtoFixed()とかコケます。parseFloat(文字列を小数にパースするメソッド)とか間に挟みました。

undef判定はちゃんと書きましょう

undefの判定に! valueって書いてたらvalue0で躓きましたw

テーブルの大文字小文字に注意しましょう

今回からdockerコンテナを使い始めたのですが、小文字のテーブルを大文字でSQL書くと認識しません。やっぱりlinuxですね。

エンドポイント調整

複数リーグ対応のためにUIからAPIに渡すパラメータなどを増やす必要があるので調整。

サンプルデータ投入しながらデバッグ

関東キャップリーグさんの試合データが公開されているので入力していきます。
1カード分探しても見つからなかったのですが。

シーズンスタッツ

image

日別試合結果一覧

image

試合結果詳細

image

2/27進捗

概要

image

昼休み開発

サーバサイドは夜に差し替え。
夜は運営者ギルドでのKoretteさんのレビュー会に参加。

選手ページの打撃成績に出塁率・長打率・OPSを表示するよう修正

  • スマホ版(375px未満)は非表示

選手一覧API

  • 名前を昇順にソートする(全件表示・検索結果)

2/14-15進捗

概要

image

  • 選手一覧(UI)
  • 選手一覧(API)

選手一覧

現在右上メニューバー内から遷移できます。

UI

チームの一覧と所属選手一覧を実装しました。materializeのchipsを使用しています。それから、チーム名・選手名検索ができるフォームの実装をしました。今回は所属チームごとの表示の振り分けをクライアント側で行うようにしました。チームの一覧を最近試合を行った順に並び替えできていないのが課題です。

API

フォームに何も入力されていないときは選手一覧を返すようにして、
入力された場合はAPIに検索結果を取りに行く方式にしています。
フォームがクリアされるとまた選手一覧を取得します。

1/27進捗

全期間通算成績

現在入力している全てのシーズンのデータから通算成績を取得する処理を実装しました。
image
react側は旧システムAPIであらかじめ作っていたのもあって、
これにはそんなに時間がかかりませんでした。

本日から新シーズン始動

本日、2019年度前期シーズン開幕戦でした。
現行のReact/NodeJS/Netlifyのシステムでは初めての本格運用が始まります。

スコア入力

スコアの速報を入力していたのですがサーバサイド側で打率が0のときに打撃十傑から除く処理が抜けていたことに気づいてスコア入力を中断してシステムの修正を行うなど。

昨日の進捗

ReactでOGP対応した

打率分布バブルチャート実装

1/13

打率分布バブルチャートを実装しました。

image

横軸を打席数(四球含む)、縦軸を打率として、
打席数―打率の相関グラフを描画するよう実装しました。
赤枠は規定打席以上ですが、chart.jsでは描画していません。

新APIへの移行作業

image
これまでバックエンドをJavaで書いていましたが、JavaからNodeJSへ移行中です。

  • 打率順位算出バグ修正
  • 空だったチーム順位算出ロジック実装
  • API変更で動作していなかった打撃成績・投球成績の合計行を動作するよう修正
  • 期毎打撃成績内訳実装