Hi!
こんにちは、英語も活字もダメなのにエンジニアしてる。
どうも僕です。
初投稿ですが
こういうのが苦手で変な日本語や
使っての解釈なので変な所があるかと思いますが
ご愛嬌という事で、許してやってください@w@
また、間違いや不明点に関してはどんどんツッコミをお願いします!
あなたの一言で僕はまた成長できますのでよろしくお願いします>w<
はじめに
マークザッカーバーグの名言
「Done is better than perfect.(完璧を目指すよりも、まずは終わらせろ。)」
をもとに
自分で企画したモノを持ち寄って発表する部活『 つくったもん見せ部 』に参加しており
仕事で使ってる物より
使ってみたい言語や技術を試そうということから
今回のサービスを作ってみました!
※ 今回の企画の為なので、運営し続けるかは不明ですww
何を作ったの?
Nimが使ってみたかったので
Nimで何か出来ないかなという事を考えてた時に
テレビで数字選択式の『 Bingo5 』を買って元が取れるかという企画をしていたので
選択範囲が限られてるので予想する位なら簡単かと思い作ってみました!
予測アルゴリズム
今までの数字を集計した上で
テレビで紹介された41の法則とプラス5の法則を取り入れ
更に、それ以外に独自の法則を取り入れて
特定の数字の割合に修正した上で単純にランダムで割り出す仕組みです
作ったものがこちら↓です。
環境について
開発言語
・Nim *
・TypeScript *
フレームワーク
・Jester *
・Vue.js
ライブラリ & モジュール
・Vue Router
・Vuex
・vuex-module-decorators *
・Vuetify.js *
・axios
・その他諸々...
その他環境
・CentOS7
・Caddy *
・SQLite3 *
* がついてる物は今回、初めてないし本格的に使ったものになります。
Nim
Nimを選んだ理由は
Goを勉強していた時に
調べていたら下記のようなサイトを見つけ至高やイケてるに引かれNimの勉強にきりかえました。
至高の言語、Nimを始めるエンジニアへ - Qiita
イケてないのに人気がある golang vs イケてるのに人気がない Nim
説明は
どんな言語かは上記を読んでね!
わかりやすく説明されてるのでww
今回の使い方
まず、Webサービス化する前に
簡単に使ってみる為にコマンドとして開発。
その後
Webサービスとして開発が容易なフレームワークと出会い
Webサービス化した上でコマンドはそのまま利用にアレンジ。
※ データはいろんなデータが集計されている下記のサイトから収集。
コマンド開発
開発したコンマンドは
・ データの登録
・ 予測発行
・ あたり判定
・ あたり集計
・ あたり集計の取得
どんな感じに書いたか
あたり判定のソースをペタリ。
import
db_sqlite,
marshal,
os,
parseopt,
strutils,
tables
import
./private/helper,
./private/types/heldResult,
./private/types/target
type
Output* = object
position1*: int
position2*: int
position3*: int
position4*: int
position6*: int
position7*: int
position8*: int
position9*: int
Outputs* = object
win1*: seq[int]
win2*: seq[int]
win3*: seq[int]
win4*: seq[int]
win5*: seq[int]
win6*: seq[int]
win7*: seq[int]
target*: Output
template currentSourcePath: string = instantiationInfo(-1, true).filename.parentDir.parentDir
let dbPath: string = currentSourcePath & "SQLiteのファイルパス"
var args: seq[int]
var db: DbConn
for kind, key, value in getOpt():
case kind
of cmdArgument:
args.add parseInt(key)
else: discard
proc getResults(limit: int): seq[seq[string]] =
var query = "SELECT held, position1, position2, position3, position4, position6, position7, position8, position9, ballset FROM result ORDER BY held DESC"
if 0 < limit:
query = query & " LIMIT " & intToStr(limit)
return db.getAllRows(sql query)
proc convertTarget(args: seq[int]): Target =
return Target(
position1: args[0],
position2: args[1],
position3: args[2],
position4: args[3],
position6: args[4],
position7: args[5],
position8: args[6],
position9: args[7])
proc check(target: Target, records: seq[seq[string]], db: DbConn): string =
var heldResult: HeldResult
var held: int
var targetRecord: seq[int]
var winning: int
var output: Output = Output(
position1: target.position1,
position2: target.position2,
position3: target.position3,
position4: target.position4,
position6: target.position6,
position7: target.position7,
position8: target.position8,
position9: target.position9)
var outputs: Outputs = Outputs(
win1: @[],
win2: @[],
win3: @[],
win4: @[],
win5: @[],
win6: @[],
win7: @[],
target: output)
for record in records:
held = parseInt record[0]
targetRecord = @[parseInt(record[1]), parseInt(record[2]), parseInt(record[3]), parseInt(record[4]), parseInt(record[5]), parseInt(record[6]), parseInt(record[7]), parseInt(record[8]), parseInt(record[9])]
heldResult = convertResult(held, targetRecord, db)
winning = target.winning heldResult
case winning:
of 1:
outputs.win1.add held
of 2:
outputs.win2.add held
of 3:
outputs.win3.add held
of 4:
outputs.win4.add held
of 5:
outputs.win5.add held
of 6:
outputs.win6.add held
of 7:
outputs.win7.add held
else: discard
return $$outputs
proc main(): void =
block:
db = open(dbPath, "ユーザ", "パスワード", "データベース")
defer:
db.close()
let records = getResults args[8]
let target = convertTarget args
echo check(target, records, db)
when isMainModule:
main()
RESTサーバ開発
とりあえず有名なフレームワークJester
を
Pythonでいうpip
の様な、Nimのパッケージ管理nimble
でインストールしてインポートしてポン!と、とても簡単!
Jester
はSinatraライクなフレームワークで普段Sinatraを使ってる人は使いやすいのかもね!
RESTサーバのソースをペタリ。
今回は処理が少ない為( メイン処理はコマンドで記載済み )
RESTサーバのソースは、1枚のファイルにまとめてます。
import
asyncdispatch,
asyncnet,
jester,
json,
osproc,
strutils,
times
settings:
port = Port(ポート)
appName = "/api"
routes:
get "/issues/@lots":
let response = %*{"timestamp": $int(epochTime())}
try:
let lots: int = parseInt(@"lots")
if lots <= 0 or 21 <= lots:
raise newException(Exception, "Lots Unacceptable")
else:
response["lots"] = newJInt(lots)
let issueCommand = "発行コマンドのパス " & $response["lots"]
let (output, exitCode) = execCmdEx(issueCommand)
let outputJson = parseJson(output)
response["results"] = outputJson["results"]
resp $response, "application/json"
except:
let e = getCurrentException()
let now: Time = getTime()
let nowStr: string = format(now, "yyyy/MM/dd HH:mm")
echo nowStr & " ----> " & $e.name & " : " & e.msg
let traces = e.getStackTraceEntries()
for trace in traces:
echo $trace.filename & "(line: " & $trace.line & ")" & " - " & $trace.procname
response["message"] = newJString("Bad Request - Parameter Unjust")
resp Http400, $response, "application/json"
post "/check":
let response = %*{"timestamp": $int(epochTime())}
try:
let params = request.formData
let target: int = parseInt(params["target"].body)
if target != 0 and target != 1 and target != 10:
raise newException(Exception, "Target Unacceptable")
else:
let checkCommandFromat: string = "チェックコマンドのパス $1 $2 $3 $4 $5 $6 $7 $8 $9"
let jsons = parseJson(params["json"].body)
if jsons.len <= 0 or 20 < jsons.len:
raise newException(Exception, "JSON Unacceptable")
else:
var results: JsonNode = newJArray()
var checkCommand: string
for json in jsons:
if target == 0:
checkCommand = checkCommandFromat % [$json["col1"], $json["col2"], $json["col3"], $json["col4"], $json["col6"], $json["col7"], $json["col8"], $json["col9"], ""]
else:
checkCommand = checkCommandFromat % [$json["col1"], $json["col2"], $json["col3"], $json["col4"], $json["col6"], $json["col7"], $json["col8"], $json["col9"], $target]
var (output, exitCode) = execCmdEx(checkCommand)
var outputJson = parseJson(output)
results.add outputJson
response["results"] = results
resp $response, "application/json"
except:
let e = getCurrentException()
let now: Time = getTime()
let nowStr: string = format(now, "yyyy/MM/dd HH:mm")
echo nowStr & " ----> " & $e.name & " : " & e.msg
let traces = e.getStackTraceEntries()
for trace in traces:
echo $trace.filename & "(line: " & $trace.line & ")" & " - " & $trace.procname
response["message"] = newJString("Bad Request - Parameter Unjust")
resp Http400, $response, "application/json"
get "/winner":
let response = %*{"timestamp": $int(epochTime())}
let (output, exitCode) = execCmdEx("あたり集計の取得のパス")
let outputJson = parseJson(output)
response["results"] = outputJson
resp $response, "application/json"
error Http400:
let response = %*{"message": "Bad Request", "timestamp": $int(epochTime())}
resp Http400, $response, "application/json"
error Http404:
let response = %*{"message": "Not Found", "timestamp": $int(epochTime())}
resp Http404, $response, "application/json"
error Http500:
let response = %*{"message": "Internal Server Error", "timestamp": $int(epochTime())}
resp Http404, $response, "application/json"
感想
まだまだ、ドキュメントが少なく苦戦しまくった。。。
ただ、コンパイルした結果がバイナリーデータなので
本番公開はアップしてパーミッションを適正にするだけで出来たからデプロイは簡単だったー
また
GoやPythonの様な書き方でコードが少なくかけ
また、結構な省略ができるのでかなり楽!!
しかも、Javaと同じで引数違いの同じ関数名が書けるのは助かるー!@w@
でも
その反面、省略しすぎて少し可読性が下がってしまう時があったり
JavaScriptでも出来る記号が関数名に関してはかなり不明に陥る。。。
上のソースでも使われてる$
や$$
や%*
で何が起きるかってドキュメントやソースをみないと・・・
まぁーチャント読めって話だけど (笑)
かなりハマったのは
Error: type mismatch: got <型> but expected '型'
という型のが間違ってるエラーで
基本、引数や省略して書いた結果代入時のミスで起こる現象だと思うが
僕が開発時に起きたのは
result
という変数名を使うと
変数宣言してその時に正確に型宣言して関数を実行しても上記のエラーが出るという事があってかなり時間を使ってしまった。
型ミスの場合は一度変数名や関数名を疑うのもいいかもしれない・・・
Vue.js + TypeScript
Vue.js + TypeScriptを選んだ理由は
Laravelを使った開発でVue.jsを使って使いやすかったので
他でも使ってみようと思いながら
なんとなく遠ざけてたTypeScriptをこの気なので選びました
今回の使い方
とりあえずデザインは考えるのは面倒いので
VueのUIコンポーネントライブラリのVuetify
でサクッとw
そして
やはり面倒なのでSPAにしてしまおうと
Vue Router
を使ってまたまたサクッとw
状態管理を使う為にVuex
を利用してサクッとと言いたいのですが
TypeScriptで開発の為、vuex-module-decorators
というライブラリを入れると便利と記事があったので右に倣えでインストール!
後は、Ajaxを使うのでAjax様にaxios
でやりとり
vuex + typescriptをvuex-module-decoratorsで無敵になる - Qiita
感想
ほとんど使った事あるものだったので苦労はほとんどなくVuetify
もあまり難しい事なかったんですがー
TypeScriptは初めてだったので、その部分で何個か苦戦した
Javaでいうアノテーションが使えるのは便利と感じた( TypeScriptだとDecorator
っていうみたい )のと型宣言が安心@w@
今後はTypeScriptで書いていこうと思った!
ただ、アノテーションが使えるとは思ってなかったのでvuex-module-decorators
で混乱してしまった
その上で便利だけど呪文化しそうでそれがないと動かないとか何が起きてるのかが分からないってのが少しとりあえず書いとけってのが少し理解薄く今後の課題かなー
Caddy
Goで作られたWebサーバーで選んだ理由は
面白そうの一点張りですが
面白そうな理由は一つはQUIC
という新しいプロトコルと
簡単にLet's Encrypt経由で無料証明書
を発行して設定してくれるみたいで楽そうだったから(てか、自動で発行設定してくれる)
設定ファイル
bingo5.info {
root ルートパス
proxy /api http://0.0.0.0:5555
rewrite {
if {path} not_starts_with /api
regexp .*
to {path} /
}
gzip
log /var/log/caddy/access.log
errors /var/log/caddy/error.log
tls メールアドレス
}
最後のtls
だけでhttpsが使えるなんて本当楽ー!!
感想
基本ソースからコンパイルしてーぽいんですが
環境がCentOS7だったので試しに確認してみたらでけたーなので超簡単でいた!
方法はー
ここに載ってたまま
Install and Configure Caddy on CentOS 7
1. yumのアップデート
# yum update
2. EPELリポジトリ追加
# yum install epel-release
3. Caddyインストール
# yum install caddy
で終わりました@w@
後は
ウェブのルートディレクトリを作って設定ファイルを適正に設定して
サービス登録してぽん!
注意点は
普通はCaddyfile
という設定ファイルなのに対してyumでインストール
すると/etc/caddy/caddy.conf
が大元設定で/etc/caddy/conf.d/*.conf
が個別設定
それと
プラグインがどんだけインストールされてるか分からないけど(もしかして、一切入ってない?)
使いたかったCacheがインストールされてなかったので使えなかったww
総まとめ
複数の言語を同時開発はよくある事だと思うけど
新規で覚える時には正直一つずつをオススメしますねー
今回Nim
とTypeScript
を使う上で
型宣言でint
とnumber
とかlet
の動きの違いとかで混乱してしまったりしたので遊びでも分けてするべき(当たり前のことだけどww)
その上で
Nimは面白く今後も使っていきたいと思ったんですが
つくったもん見せ部でNim使っていくよーっていうと今回の企画をみた人にWeb言語ですか?
と言われ出来る事をうまく説明出来なかったので
今度はWeb以外で何か作るのとGoと比較されがちなのでGoで作ったCaddyのNim版もできたらなーと
それと
TypeScriptは積極的に使ってみようかな
コンパイルは面倒だけどVue.js使うなら結局一緒だし便利なのが盛りだくさんだから
で
Caddyもhttpsが必須な状況で
いちいち証明書設定など面倒なので簡単なサイトにはどんどん使ってみようかなと
あと
yumでインストールした時のプラグイン調査と
Goで作られてるので何かあれば便利ってのがプラグインとして作れるのであれば作ってみたいなー
後、初投稿で不慣れすぎて
かなり時間かかったー
それと
試したかっただけなので
本システムで2等や公開前のバージョンでは3等を当てましたが
あくまでルールに基づいてランダムで発行してるだけなので当選を保証するものではありません。
宝くじの購入は自己責任でお願いいたします。
宣伝
大阪で開催されてるのであれですが興味があれば見せ部に来てねー@w@
作ったもん見せ部