はじめに
クリックするだけ!ランダムに2つの単語を「反応」させて、パワーワードを作りましょう!
モードは
- 形容詞+名詞
- 副詞+動詞
- 名詞+助詞+動詞
の3種類です!
ソースコード
GitHub - Syuparn/LiteralReaction: ランダムに単語を「反応」させて、パワーワードを作ろう
(DockerやVue.jsをはじめて使ったので、汚いところもあると思います…ツッコミ、修正歓迎です)
Webアプリの形で作りましたが、web上では公開していません。
(単語が本当にランダムなので、暴言や不謹慎な表現も生成されるかもしれないからです…)
試したい方は上記リンクからダウンロードしてください。docker-composeを使用しているので、
$ docker-compose -f docker-compose.prod.yml up -d
でコンテナを起動すればlocalhost:8080
からアクセスできます。
webアプリケーションの構成
WebサーバとAPIサーバの2段構成になっています。

WebサーバはUI(html)、APIサーバは単語情報(JSON)をやりとりします。
コンテナもWebサーバとAPIサーバの2つに分けています。
(UIと単語生成を疎結合にして修正しやすくするためです)

version: '3'
services:
apiserver:
build:
context: ./apiserver/
dockerfile: Dockerfile
ports:
- "127.0.0.1:5050:5050" # host:container
expose:
- "5050" # port for other containers (not host)
vue-frontend:
build:
dockerfile: Dockerfile
context: ./vue-frontend
ports:
- "127.0.0.1:8080:80" # host:container
ただし、ユーザーから見ても別々のURLになってしまうと不格好なので、Webサーバの/api/
ではじまるURLにリクエストされた場合はAPIサーバに転送するようにしています。
server {
listen 80;
server_name localhost;
server_tokens off;
location / {
# デフォルトではhtmlを返す
root /usr/share/nginx/html;
try_files $uri $uri/ @dynamic;
}
location /api/ {
# URLが"/api/"ではじまる場合のみ、APIサーバに転送
# URLのうち"/api/"に続く部分のみを取り出す
rewrite /api/(.*) /$1 break;
# docker-composeの機能によって、exposeされた他コンテナのポートはアプリケーション名でアクセス可能
proxy_pass http://apiserver:5050;
# redirectを無効化(既にrewriteでリダイレクトを設定しているため)
proxy_redirect off;
proxy_set_header Host $host;
}
}
単語情報GETの流れは以下の通りです。(例は「形容詞+名詞」の場合)
- クライアントは、単語生成(「反応」)ページを開いたときに、形容詞を
localhost:8080/api/rand/adjective
へGETリクエスト - WebサーバがリクエストをAPIサーバ
http://apiserver:5050/rand/adjective
へ転送 - APIサーバがリクエストに基づきランダムな形容詞1つをJSON形式で(Webサーバに)返す
- Webサーバはレスポンスをクライアントへ転送
- クライアントに形容詞データのJSONが届く
- 名詞についても同様
単語データ生成
「MeCab IPADIC」を使用しました。
MeCabは、文章を単語(正確には形態素、ことばの最小単位)ごとに分かち書きするソフトウェアです。
そして、MeCabが参照する形態素の辞書の1つがIPADICです。
品詞ごとにcsvファイルで書かれていて、各行に各形態素の表記、読み、品詞、活用などが格納されています。
あらっぽい,19,19,6956,形容詞,自立,*,*,形容詞・アウオ段,基本形,あらっぽい,アラッポイ,アラッポイ
あらっぽし,23,23,6956,形容詞,自立,*,*,形容詞・アウオ段,文語基本形,あらっぽい,アラッポシ,アラッポシ
あらっぽから,27,27,6956,形容詞,自立,*,*,形容詞・アウオ段,未然ヌ接続,あらっぽい,アラッポカラ,アラッポカラ
...
このアプリの単語合成モードは
- 形容詞(終止形)+名詞
- 副詞+動詞(終止形)
- 名詞+(助詞)+動詞(終止形)
の3種類です。
この形式で最大限単語を利用するため、(文法的用語には少し不正確ですが)以下のルールで単語を抽出しました。
# 形容詞として利用
# 形容詞終止形
cat $PATH_FROM/Adj.csv | awk -F"," '$10~/^基本形$/ {print $1}' > $PATH_TO/adj.txt
# (「な」を付けると形容詞になる名詞)+「な」
cat $PATH_FROM/Noun.adjv.csv | awk -F"," '{print $1 "な"}' >> $PATH_TO/adj.txt
# (「ない」を付けると形容詞になる名詞)+「ない」
cat $PATH_FROM/Noun.nai.csv | awk -F"," '{print $1 "ない"}' >> $PATH_TO/adj.txt
# 名詞として利用
# 「ナンセンスさ」を出したいので人名、地名、専門用語は除外し、一般名詞だけ使用
# 名詞
cat $PATH_FROM/Noun.csv | awk -F"," '{print $1}' > $PATH_TO/noun.txt
# 「する」を付けると動詞になる名詞
cat $PATH_FROM/Noun.verbal.csv | awk -F"," '{print $1}' >> $PATH_TO/noun.txt
# 「な」を付けると形容詞になる名詞
cat $PATH_FROM/Noun.adverbal.csv | awk -F"," '{print $1}' >> $PATH_TO/noun.txt
# 「ない」を付けると形容詞になる名詞は単体で使うと不自然なので未使用)
# 副詞として使用
# 副詞
cat $PATH_FROM/Adverb.csv | awk -F"," '{print $1}' > $PATH_TO/adverb.txt
# 副詞としても使える名詞(「毎日」等)
cat $PATH_FROM/Noun.adverbal.csv | awk -F"," '{print $1}' >> $PATH_TO/adverb.txt
## 形容詞連用形(小さい「っ」で終わるものは動詞が後続できないので削除)
cat $PATH_FROM/Adj.csv | awk -F"," '$10~/^連用テ接続$/ {print $1}' \
| awk '!/っ$/' >> $PATH_TO/adverb.txt
# 動詞として利用
# 動詞
cat $PATH_FROM/Verb.csv | awk -F"," '$10~/^基本形$/ {print $1}' > $PATH_TO/verb.txt
# (「する」を付けると動詞になる名詞)+「する」
cat $PATH_FROM/Noun.verbal.csv | awk -F"," '{print $1 "する"}' >> $PATH_TO/verb.txt
上記で生成したcsvをSQLiteに流し込んだものを、単語データベースとして使用しました。
sqlite3 $SQL_PATH/$SQL_NAME << EOS
/* create tables */
.read ./db/init.sql
/* let col separator "," */
.separator ','
/* import word data csvs */
.mode csv
.import $CSV_PATH/adj.csv adjectives
.import $CSV_PATH/adverb.csv adverbs
.import $CSV_PATH/noun.csv nouns
.import $CSV_PATH/verb.csv verbs
EOS
drop table if exists adjectives;
create table adjectives (
id integer primary key,
word text
);
drop table if exists adverbs;
create table adverbs (
id integer primary key,
word text
);
drop table if exists nouns;
create table nouns (
id integer primary key,
word text
);
drop table if exists verbs;
create table verbs (
id integer primary key,
word text
);
(個人的)面白かった生成結果



意外とフレーズが成立する割合が高く(体感100回に1回くらい?)、他にも以下のような「まともな」フレーズが生成されました。
居場所を失う
酷い蛇行
自我を失う
泥棒を訴える
移り気なロマンチスト
やさしいインターフェース
はまったところ
docker-compose buildできない
docker-compose
はapt install
でインストールすると古いバージョンが入ってしまいます…
そして、古いバージョンのdocker-composeでバージョン3のdocker-compose.yml
を使うとビルドに失敗します。
apt install
ではなくcurl
で最新バージョンをダウンロードしましょう
Install Docker Compose | Docker Documentation
(公式ドキュメントはちゃんと読まねば…反省)
go buildできない
Go1.13からはライブラリ管理の方法が変わったため、最初にgo mod init
をする必要があります。
このコマンドを打つと、モジュールの依存関係ファイルgo.mod
やモジュールが本物か確かめるためのチェックサムgo.sum
が生成されます。
Go 1.13 に向けて知っておきたい Go Modules とそれを取り巻くエコシステム - blog.syfm
その代わり、go.mod
のおかげでライブラリを手動でgo get
する必要がなくなりました!
(go build
時に自動でダウンロードされる)
コンテナ作成中に生成した実行ファイルが消える
volumes
とCOPY
を混同していました。
Dockerfile
中でCOPY
すると、コンテナのビルド時にホストのディレクトリをコピーします。
docker-compose.yml
中でvolumes
を指定すると、コンテナの起動時にコンテナのディレクトリにホストのディレクトリをマウントします。
そのため、ホストのソースをCOPY
してせっかく実行ファイルをビルドしても、同じディレクトリをvolumes
に指定するとホストのディレクトリ(もちろん実行ファイルは無い)に隠されてしまいます…
docker-composeのvolumesで指定したホストのディレクトリがマウントされずハマった
conic-gradientが使えない
単語生成画面の集中線はCSSのconic-gradient
関数を使用する予定でした
…が、この関数はFirefoxでは非対応です。
conic-gradient() - CSS: カスケーディングスタイルシート | MDN
そこで、同名のnpmパッケージを使ってjs側で模様を生成しました。
しかし、このパッケージはES5で書かれているのでVue CLI内部でimport
できません。
結局、
-
index.html
で直接グローバルに読み込む - Vue側では
window.ConicGradient
の形で呼び出す -
npm run build
ではビルドされないので、別途モジュールをstaticにコピーする
という手順を取りました。
<!DOCTYPE html>
<html>
...
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<!-- ここでモジュールを読み込む(prefixfreeはconic-gradientの依存モジュール) -->
<script src="./static/prefixfree.min.js"></script>
<script src="./static/conic-gradient.js"></script>
</body>
</html>
<script>
export default {
name: 'bangBackground',
data: function () {
return {
bangSVG: new window.ConicGradient({
repeating: true,
stops: `#ffffff 0,
#ffffff 2.0%,
#AAAAAA 2.125%,
#AAAAAA 2.375%,
#ffffff 2.5%`
})
}
}
}
</script>
FROM node:lts-alpine as builder
RUN mkdir -p /app
WORKDIR /app
RUN npm install -g http-server
COPY ./package*.json ./
RUN npm install \
# conic-gradientモジュールをstaticにコピー
&& mkdir -p static/ \
&& cp node_modules/conic-gradient/conic-gradient.js static/ \
&& cp node_modules/prefixfree/prefixfree.min.js static/
COPY . .
RUN npm run build
FROM nginx:stable-alpine as product
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx_config/default.conf /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]
参考文献
Go言語
APIサーバ
golangでREST APIをやってみた① - Qiita
SQLiteとの接続
golangでSQLite3を使ってデータベースを操作する方法まとめ | Black Everyday Company
MeCab
IPADICダウンロード方法
Alpine LinuxでMeCab with NEologd - Qiita
Vue.js
SPA(シングルページアプリケーション)のつくり方
【Vue.js】爆速でSPAを作る - Qiita
axiosを使ったJSONのGET/POST
Vue-CLIのプロジェクトでaxiosを使ってAPIや外部リソースからのデータを取得する | 大阪市天王寺区SOHOホームページ制作 | デザインサプライ-DesignSupply.-
コンポーネント内部に要素を入れる(公式)
スロット — Vue.js
nginx
URLの一部分だけ取り出してポートフォワーディング(/api/hoge -> apiserver:5050/hoge
)
Nginx reverse proxy + URL rewrite - Server Fault
Docker,docker-compose
docker-compose基本操作
docker-compose コマンドまとめ - Qiita
複数サーバ(複数コンテナ)を協調させる方法
マイクロサービスほどじゃないけどウェブサービスを分割開発したい人向けDocker設定を集めるスレ - Qiita
vue-cliビルド方法(公式)
Vue.js アプリケーションを Docker 化する — Vue.js
Golangマルチステージビルド方法
(マルチステージビルドのメリット)
Dockerのマルチステージビルドを使う - Qiita
(Go1.7の記事ですが、Go1.13以上の場合Dockerfileでgo get
をする必要はありません)
(go-sqlite3ライブラリを使う場合)
go-sqlite3 が入った状態での Docker のマルチステージビルドを行う - Pistatium note