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

2019-01-13に作成

image
野球リーグスコア管理システムに関する進捗です。

[PR]参加者募集しています!

使っている技術など

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

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

残りタスクリスト

trello

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

キャップ野球非公式特設サイト進捗(8/27)

「桜咲く佐倉蓋ざんまい」非公式特設サイト
https://sakura-cap.netlify.com/

非・公・式です。(大事なことなので2回言いました)

決勝トーナメントの内訳を前に配置しました


以前はトーナメント表を上に配置していましたが、内訳、トーナメント表の順番に表示するよう変更しました。

試合詳細ページへのリンク

「予選」タブのリーグ戦の組み合わせ表の中に動画があるものだけ下線を引いてリンクを張っています。


初期段階のUIと比べていただくと、今回UIに色々力をいれていることがわかっていただけるかと思います。

試合詳細ページ


スコアと試合動画ツイートを引用しています。

技術的なこと

今回使用しているのは、react-twitter-embedというReactコンポーネントです。

tweetIdにツイートIDが入るよう記述するとスタイルなどもCSSが用意されています。

<TwitterTweetEmbed tweetId={*******} />

サイトのソース

GitHub

技術的なこと

今回はDBやアプリケーションサーバを使わずにnetlifyだけで実装することを目標にしています。
データはjsの中にobjectで埋め込んでいます。
jsonはコメント書けないのが面倒なので。
興味がある方はData.jsを読んでみてください...

キャップ野球全国大会の非公式特設サイトを作った件

桜咲く佐倉蓋ざんまい

非公式特設サイト作りました。

先週末に千葉県佐倉市で開催されたキャップ野球の全国大会の結果をまとめたサイトをCRA(CreateReactApp)でサクッと作りました。
結局手直し含めると2.5人日ぐらいです。

テーマカラーは運営の公式twitterのロゴがこんな感じだったので
(実際は改行されているけれども)そこから採用しました。
カラーピッカーでだいたいの色コードを入れてそこから構成します。
今回はグラデーション使えるようになったので楽しくて多用しています。え、昔マーキーとかいっぱい使いませんでした?(インターネット老人会)

ちなみに、この後さらにUIをいじっています。
どこが変わったかわかるかな?

なぜ作ろうと思ったか

最初は手書きしかないトーナメントをまとめるだけ...のつもりだったのですが、どうせならリーグ戦も表示させたいなーと思い、
よさそうなツールを探したのですが、あまりピンとくるものがなくて自分で書いてしまった次第。最近UI書くの楽しすぎる問題

大会といえば

トーナメント結果と表彰状に個人開発のサービスを採用しています。いつもお世話になっております...!
2.png

トーナメント

おおにしさんの「THE TOURNAMENT」ですよね。

表彰状

鉄板のあんどさんの「WEB表彰」ですよね。

大会概要とか


体裁的にあった方がいいかなーと思ったのですが、
まあ誰も見ませんよね...!

ご意見・ご要望お待ちしています!

快適に楽しく使ってもらえるのが制作者冥利に尽きるので。

終わりに

最近このボードの野球リーグスコア管理システムの進捗があまり出ていません。何だかんだver2で安定しています...w

PWA対応

PWA

経緯

これまで読んだ本や積んでる本の値段の合計がわかるサービス「積読ハウマッチ」が最近リリースされたのですが、

PWA対応してるよ!という売りだったのでふとフロントがCRAのうちのサービスどうだったっけ...と思って確認。
manifest.json初期状態のままだったので「React App」....ダサい。

App Manifest Generatorを使ってみる

項目で設定を選んでアイコンに使う画像ファイルをアップロードするだけ。

項目の解説はこちらが参考になります。
- manifest.jsonでホーム画面へのアプリ追加【これからはじめるPWA】

manifest.json

{
  "short_name": "JCBL-SCORE",
  "name": "日本カラーボール野球連盟スコア管理システム「JCBL-SCORE」",
  "theme_color": "darkcyan",
  "background_color": "#ffffff",
  "display": "standalone",
  "Scope": "/",
  "start_url": "/",
  "icons": [
    {
      "src": "img/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    .....
    {
      "src": "img/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "splash_pages": null
}

対応した結果

7/20進捗

選手登録API実装

まだ画面(UI)の実装はまだですが、選手の新規登録APIは実装完了。
打撃成績などはまだマニュアルでDBに入れてます。

Nodeプロジェクトに統合していたReactプロジェクトを再分離

現在のSEOの資産を活かすのであればこのままnetlifyドメインでもいいかな...と思い始めました。
分けていた方がデプロイが楽なので。

画面イメージ

サンプルデータはいつものように「関東キャップリーグ」さんです。

ブランドロゴに副題をつけました。

チームページ

スタッツ

チーム・選手一覧

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

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

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

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

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

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

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

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

エンドポイント調整

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

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

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

シーズンスタッツ

image

日別試合結果一覧

image

試合結果詳細

image

7/14

SELECT結果の調整

image

これまでは単一のリーグ用のシステムだったため、where句で集計する期間の範囲を与えるだけだったが、リーグIDを渡して「どのリーグのどの期間か」を求めるように修正した。

  • チーム順位
  • 打撃タイトル
  • 投手タイトル

いずれも修正完了。

データ登録APIの実装開始

image

ようやく重い腰を上げてデータ登録機能の実装に着手。

7/7進捗

関東キャップリーグさんにシステム提供の打診中

Excelで集計した成績を写真に撮ってアップされてるので
同じマイナースポーツの誼で、システムを提供させてもらえないかと打診。
確定ではないですが、とりあえず前向きな回答をもらっています。

ver3(複数リーグ対応版)でサンプルデータ投入

若干滞り気味だったver3の開発を進めることに。
ただ、リーグの期間終了が今週末とのことなのでver3投入は間に合わないだろうなー。

  • docker-composeでの動作確認
  • サンプルデータの投入
  • DB設計修正
  • SQL修正

image

やっぱりモチベーションがある方が開発が進みます。

6/16 進捗

  • 既存システム、色々改修する必要のある箇所がある
  • フロント(react)、後でスタイル調整しようと思うが手を加えてしまう
  • サムサニモマケル、アツサニモマケル。

ログイン機能改修

現行システム(ver.2)ではコメントアウトして提供していない機能ですが、作りかけの残骸を改修しています。
とりあえずログインはできるようになった。

リーグ新規登録機能実装中

ドメイン未定←これ重要。

image

フロントに予想以上に時間使ってバックエンド実装できず。

ロゴ作成サービス「canva」使ってみる

canva
image

背景色透過は有料機能みたいです。

6/14 進捗

ver.3 システムの開発

  • 主な機能
    • 複数リーグへの対応
    • DB編集権限の提供

新ルーティングへの対応

/league/リーグ名/season/シーズンIDというルーティングへの対応を進めています。新システムではリーグのURLをユーザが決められるようにする予定です。

image

NodeJSのrouterをこう書いてしまって404エラーしか返ってこなくなって嵌まりました。。。

this.router.get("/:league_name(\\d{1,20})/season/:season_id(\\d{1,3})", this.showStats);

DBを新スキーマへ移行

旧システムのDBのダンプをスキーマを変えた新システムにリストアする方法を色々検討した結果、
一旦dockerにDBを2つ用意して新システムにinsert/selectすることにしました。

docker-composeで新たに設定を追加しました。

environment:
 #タイムゾーンの設定
 - TZ=Asia/Tokyo
 #mysqlサーバの起動時に実行するコマンド
 #文字セットの設定、SQLモードの設定
command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci --sql_mode="STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"

typescript

新スキーマに移行したのに伴い、DBアクセス周りのプログラムの改修が必要になっています。
interface作ったり、追加したテーブル用にservice(DBアクセスクラス)作ったり。

まだselect関係の機能を元に戻すので手一杯ですが、
insert/update関連の機能を早く実装したい。

宣伝

去る6/8に行われた公式戦の動画作りましたのでよろしければご覧ください。
- 第一試合

  • 第二試合

6/9進捗

UI

スコア入力実装

5ケ月前の実装を引き継いでいます。git様様。
行を1つのコンポーネントにまとめて、ボタンで選手人数の変更に対応できるように実装しました。

image
ボタンは右に寄せようと思ってます。
あと数字入力コンポーネントは3桁入力できる幅でいいかなぁ。

Google Domainsの検討

サービス化するにあたって、サービス名の再検討とドメインの取得が必要になりますが、.jpドメインを取得するのでなければ、メール転送サービスがあるGoogle Domainsがよいとのアドバイスをいただきました。

サービス名の検討

上記に関連して、サービスリリースするならサービス名を再検討することにしました。

6/8 進捗

最近設計続きであまり外側からわかる進捗がないのですが、
プロジェクト全体の現在の状況です。

次期システム(Ver3.0)開発中

  • 複数リーグに対応し、ユーザがデータ登録・編集できるようなサービスを目指しています。

reactプロジェクトとNodeJSプロジェクトをドッキング

これまで、reactプロジェクトをnetlifyにデプロイするため、利便性の観点からreact/NodeJS(バックエンド)プロジェクトを分けていましたが統合しました。

次期システム用DBの設計完了

create文(DDL)書き終わったので、現在は現行システム(ver2)のDBをver3システムに移行するSQLを書いているところです。

docker-composeスクリプト

モダンな開発・運用環境ということでdocker-composeを使ってまずDBコンテナを立てています。順を追ってAPI用のNodeコンテナ、WEBサーバ用にnginxコンテナを立てる予定です。

DB再設計(2) & ルーティング設計

DB論理設計

image
XmindでリレーションとかPKの表現っぽいものができますね。
やはりマインドマップツールだけあって構想を練る時には便利。
現行システムは14テーブルですが、ほとんど使っていないテーブルがいくつかあるのと、内容的に重複するテーブルは統合しようかと思っています。

ルーティング設計

image
画面が結構多いので画面遷移図というよりはURLをどうするかの構想を起こしています。いくつか機能面もまとめています。

使いやすいね、Xmind

ちょっと慣れるまでに時間がかかりましたが、慣れてくると自由度が高いソフトですね。
意外と早めに仕様が固まったので早めに着手していけそうです。

DB再設計始めました。

現在、閲覧のみに特化した野球リーグ管理システムを公開していますが、機能的に追加開発するところが段々なくなってきました。

  • 現在1リーグのみに特化したシステムを複数リーグで扱えるように
  • データベースへの登録・更新機能も公開する

以上2点を大きな軸としてサービスリリースに向けて徐々に動き始めました。複数リーグを扱う上でDBの再設計は不可避です。
ということで論理設計からやり直しています。

xmind使えるね!

image

オントロジーエディタ「法造」

image

ER図を作るのにオントロジーエディタを使ってみた

獲得タイトルに順位を表示するようにしました。

獲得タイトルとは

年度ごとに、打率、HR、打点、安打数、防御率、勝利数、セーブ、奪三振数の各タイトルの保持状況を表したもの。

今回実装したUI

テーマ:楽しめる人を増やす
元々の実装では1位(タイトル獲得者)しか表示されず、

  • 2位だったとかそういう情報がわからない
  • 参加しているのに表示されない

では一部の人しか面白くないと思ったので実装を変えることにしました。1-3位はメダル、10位以内入賞は順位を表示するよう変更しました。

image

実装裏話

タイトル集計バッチの実装

集計処理はリプレース前の旧システムで保持していたのですが、
今回の仕様変更に伴いNodeJSで新たに実装しました。

DBの破壊的変更

変更前はevent_typeというカラムを持って拡張性を考慮していましたが、逆にデータが取り出し辛いという欠点がありました。
また、1位の情報のみ保持しており、各タイトル10位までの情報が保持できないという欠点もありました。(定義が煩雑になる)

変更後はいつ誰が 何のタイトルで何位だったかを明確に視認できるようになり、データの可読性が上がりました。

変更前の定義

create table title_holder(
    player_id int(3) not null,
    season_id int(3) not null,
    event_type int(2) not null,
    value double not null,
    lock_flg int(1) not null,
    foreign key (player_id) references player(id),
    foreign key (season_id) references season(id),
    primary key(player_id,season_id,event_type)
);

変更後の定義

create table title_holder(
    player_id int(3) not null,
    season_id int(3) not null,
    average_rank int(2),
    homerun_rank int(2),
    rbi_rank int(2),
    hit_rank int(2),
    era_rank int(2),
    win_rank int(2),
    save_rank int(2),
    strikeout_rank int(2),
    lock_flg int(1) not null,
    foreign key (player_id) references player(id),
    foreign key (season_id) references league(id),
    primary key(player_id,season_id)
);

元々のUI

2/10ごろ完成したもの。
image

最後に

私の成績はそんなに上位ではないのですが、
最高で銅メダル(3位)取れていたのでちょっとうれしくなりました。

5/23進捗

おすすめサービスコンポーネントを作りました

image

実装

あまり難しいことはしてないのでまるっと公開してしまいますが。

  • array: おすすめサービスのインデックスをもつ配列
  • dispArray: 乱数生成した結果を格納する配列
  • num: 0からarray.lengthまでの数字から乱数を生成した結果

おすすめサービスの候補が仮に20個あったとして、
1. arrayにその20件の情報を格納します。
2. 0-19までの乱数を発生させ、該当するインデックスをdispArrayに格納し、arrayから削除します。
3. 残ったarrayに対して再度乱数を発生させます。

あとはReactのJSXを組み立てるだけですね。


export class RecommendServices extends React.Component { render() { if (store.getState().recommend === undefined) { let array = []; let dispArray = []; for (let i = 0; i < recommendConfig.length; i++) { array.push(i); } for (let j = 0; j < 3; j++) { let num = Math.floor(Math.random() * array.length); if(array[num] !== undefined){ dispArray.push(array[num]); } array.splice(num, 1); } console.log("dispArray", dispArray); store.dispatch(setRecommend(dispArray)); console.log(store.getState()); } let dispElm = []; for (let i = 0; i < store.getState().recommend.length; i++) { dispElm.push( <div className="card"> <div className="card-title"> <a href={recommendConfig[store.getState().recommend[i]].href}> <img src={recommendConfig[store.getState().recommend[i]].img} width={200}/> <br/> <div> {recommendConfig[store.getState().recommend[i]].title} &nbsp;<i className="fas fa-external-link-alt 2x" style={{ fontSize: 14 }}/> </div> </a> </div> <div style={{ fontSize: 12 }}> {recommendConfig[store.getState().recommend[i]].comment} </div> </div> ); } console.log(dispElm) return ( <div>{dispElm}</div> ); } }

recommendConfig

export const recommendConfig = [
  {
    title:'Crieit',
    href:'https://crieit.net/boards/baseball-score-management',
    img:'https://storage.googleapis.com/deviita/upload_images/a920719ad1329bd0abcef3505eda1be85c198e94430c7.png',
    comment:'本システムの開発の進捗を掲載させていただいています。'
  },
  {....}
]