- 投稿日:2019-03-01T22:16:16+09:00
macOS+Node.js�+OpenCV環境の構築
前提
OpenCVで遊ぶならPython環境が鉄板だと思います。
本稿はそれでもNode.jsからOpenCVを使いたい人向けの記事です。
Node.js+OpenCVの選択肢
Node.jsからOpenCVを使いたい場合、ライブラリとして主な選択肢は次の2つになります。
前者はOpenCV version 2、後者はOpenCV version 3のライブラリになります。
前者は全然メンテされていません。最新バージョンの6.0.0リリースから2年経っています。後者はそこそこメンテされていそうです。
OpenCV3の方が画像まわりのAPIが増えているらしいので、顔認識をしたい場合は後者を使った方が良い可能性があります。
前者の方がネット上の利用例は多そうですが、Pythonに比べたら利用例は圧倒的に少ないので、その意味では五十歩百歩かもしれません。
macOSで使おう
さて、上記ライブラリのうち、前者のライブラリ opencv をmacOS上で使おうと思うとイマイチうまくいきません。ネット上で調べてみても失敗事例が多い気がします。
後者のライブラリ opencv4nodejs は次のようにすれば使えます。
$ brew install cmake opencv@3 $ brew link --force opencv@3 $ export OPENCV4NODEJS_DISABLE_AUTOBUILD=1 $ npm i opencv4nodejs
brew linkするのはカッコ悪いんですがlinkしないと正しく動くバイナリが作れませんでした。動作確認
ビルドがうまくいっているか、動作確認してみましょう。
$ curl http://www.lenna.org/len_std.jpg -o lenna.jpgで有名なLennaさんの画像を用意した上で
lenna.jsconst cv = require('opencv4nodejs'); const img = cv.imread('./lenna.jpg'); const classifier = new cv.CascadeClassifier(cv.HAAR_FRONTALFACE_ALT2); const grayImg = img.bgrToGray(); const result = classifier.detectMultiScale(grayImg); if (!result.objects.length) { throw new Error('failed to detect faces'); } const minDetections = 10; result.objects.forEach((faceRect, i) => { if (result.numDetections[i] < minDetections) { return; } const rect = cv.drawDetection( img, faceRect, { color: new cv.Vec(255, 0, 0), segmentFraction: 4 } ); }); cv.imshowWait('result', img);上記スクリプトを実行すると次のウインドウが現れます。
成功です。やりましたね。
- 投稿日:2019-03-01T19:16:15+09:00
2019年の今 Mithril.js を使うためのTips
最近めっきり話を聞かなくなったJSフレームワーク、 Mithril.js に関する Tips をつらつらと書いていきます。
決して下火になったわけじゃあないんです。Mithril.js とは
いわゆる仮想DOMフレームワークの一つ。メジャーな React や Angular と比べ、軽量・シンプル・高速なことを謳う。
フレームワークの機能としては、React, Vue と比べても決して不足していないうえ、実際のプロダクトにも採用例があり十分ポテンシャルはある。はずです。
前述の通りバズってはいないですが、現在も活発に開発が続けられており、Ver.2のリリースも予定されています。
Mithril 情報を集めるために
重要な点として、Mithril は v0.2 系と v1.x 系で大きくAPIが異なっています。 v1.xの方が正式となり、v2でも大きな変更は入らないようなので、この点だけは覚えておいてください。
早い話が、 Controller あるいは m.props が話題に出る記事は 古いです。これからは参考にしてはいけません。
じゃあどうなったかと言うと、現在はよりシンプルかつ賢くなって、m.props を使わずとも変更が検知されるし、controller を使わずともライフサイクルに応じたフック処理を実行できます。より簡単かつ高速になった純粋な上位互換と言えるものなので、この点でも今から v0.2 系の知識を仕入れる意味はありません。
ここまで分かればあとは公式サイトなり、新し目の記事なりを漁って導入は出来るかと思うので、そのフェーズは他の記事に譲ります
手抜きともいう。以下は使っていく上で気づいたポイントを書き連ねたものです。
vdom.state は使わない
基本的に view に state をもたせるのは99%アンチパターンです。状態に類するものは model のオブジェクトとして分離し、 view 側ではそれを読みに行くだけにしましょう。
例: テーブルの各列コンポーネントに状態をもたせるのではなく、元データにその状態をもたせて、コンポーネントは filter をかけるなどして出し分ける。
arrow function 最強説
arrow function はどんどん活用しましょう。
// 普通に書いてみる const Component = { view: function(vdom) { // ... } } // arrow function で書いてみる const Component = { view: vdom => { // ... } } // 値を返すだけのfunctionはもっと短くできる const Component = { view: v => m("button", { onclick: model.incCount }, "+1"), }これに加えて
mapfilterreduceなどのメソッドや、三項演算子を組み合わせることでよりコンポーネントをスマートに書けます。楽しくかっこよくコーディングしましょう。form の取扱い
結論から言うと、 formは使わずformっぽいスタイルを使って実現します 。 (bootstrap なら
.form-groupが使えます)const FormLikeComponent = { view: v => [ m(".form-group", [ m("input[type=text]", { value: model.textData, oninput: e => model.textData = e.target.value }), m("button", { onclick: model.submit }, "送信"), ]), ], } let model = { submit: e => { m.request({ url: someUrl, data: {text: model.textData}, }) .then( /* some handling */ ); }, }form が使えないとはいえ十分シンプルに書けます。ポイントは、
oninputイベントなどを用いて入力値を補足します。入力値を得るには、イベントオブジェクトのtarget.valueを参照します。ここで得た値は例によって state ではなく model のどこかに持たせるべきです。- form 内の button なりなんなりで、submit 相当のイベントを発火します。そこでは Mithril らしく
m.requestを使います。要は普通のリクエストですね。フォームだからと特別な処理は必要ありません。input 要素を見てもらえれば分かる通り、こんな感じで双方向バインディングな処理が簡単に書けるのが Mithril の素敵ポイントですね。formっぽいスタイルがないときは頑張ってください。
request まわり
request について書きたかったけど力尽きたので一旦ここまでで。
TypeScript まわり
TypeScript について (ry
- 投稿日:2019-03-01T18:53:22+09:00
Nest.jsは素晴らしい
Node.jsとExpressでサーバー側のシステムを開発していたのですが、Nest.jsと言うサーバー側のフルスタックフレームワークがあることを知り、乗り換えることにしました。
メリットとデメリット
どんなものにも、メリットとデメリットがあります。自分にとって「メリット>デメリット」となったら採用する価値が出てきます。
私が感じたNest.jsのメリットとデメリットは以下の通りです。メリット
- Angular風なので、クライアントにAngularを採用するのであれば、同じような考え方で開発することができる
- TypeScriptで開発しやすい
- 予め以下のような構成でシステムを作ることができ、メンテナンス性が向上する(人による実装方法の違いをある程度抑制できる)
- controller
- filter
- guard
- interceptor
- interface
- middleware
- module
- pipe
- provider
- resolver
- service
- テストが容易なコードが書ける
- nest cliでコードのひな形を作成できる(テストコードも作成できる)
- Passport、GraphQL、TypeORMなどをサポートしている
- ドキュメントが充実している(但し、英語)
デメリット
- 日本語の情報が少ない
NestJSのインストール
NestJSはnpmパッケージとして提供されています。インストールは以下のようにします。
npm install @nestjs/cli -g
まずやってみる
nest cliでプロジェクトを作成します。
以下のようにコマンドを起動するといくつかの質問があり、それに答えていくとプロジェクトが作成できます。nest new プロジェクト名
どうでしょう?Angularっぽいでしょ?
フォルダ構成を決める
これから作成するシステムのフォルダ構成を決める必要があります。
よくあるパターンは、エンティティ(モデル)、サービス、コントローラ単位でフォルダを分けます。なんとなく、それがシンプルでいいのかと思ったのですが、nest cliでソースコードを作ると、そういうフォルダ構成にできそうにありません。
例えば、Userサービスを作成するために、以下のコマンドを実行します。nest g service User
すると、srcフォルダにUserと言うフォルダができ、その中にコードが生成されます。
では、フォルダ指定すればいいのかと思い、以下のようにコマンドを実行します。nest g services/User
今度は、srcフォルダにservicesというフォルダができ、さらにUserというフォルダができて、その中にコードが生成されます。
結論
「NestJSは、オブジェクト単位でフォルダを構成する」では、Userに関するコードを作って見ましょう
必要なコードの種類は、module、entity、service、controller、interfaceです。
REST APIではなく、GraphQLを採用する場合は、controllerは必要ありません。Userインターフェースの作成
以下のコマンドで、Userインターフェースのひな形を作成します。
nest g interface User/User
Userフォルダにuser.interface.tsができますので、必要なユーザ属性を追加します。
user.interface.tsexport interface IUser { id: string; name: string; kana: string; email: string; postcode: string; address: string; phone: string; password: string; admin: boolean; createdAt: Date; updatedAt: Date; }Userエンティティの作成
以下のコマンドで、Userエンティティのひな形を作成します。
nest g class User/User
Userフォルダにuser.entity.tsができますので、必要なメンバーを追加します。
下記コードは、ORMにTypeORMを使っていますので、そのためのデコレーターが付いています。user.entity.tsimport 'reflect-metadata'; import { Entity, Column, PrimaryGeneratedColumn, PrimaryColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm'; import { IUser } from './user.interface'; @Entity() export class User implements IUser { @PrimaryGeneratedColumn('uuid') id: string; @Column() name: string; @Column() kana: string; @PrimaryColumn() email: string; @Column() postcode: string; @Column() address: string; @Column() phone: string; @Column() password: string; @Column() admin: boolean; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; }Userサービスの作成
Userオブジェクトに対して様々な操作を行うサービスクラスを作成します。
以下のコマンドでUserサービスのひな形が作成できます。nest g service User
今回は、Eメールアドレス指定でユーザ情報を検索する(findByEMail)と、ユーザを追加する(add)を実装します。
user.service.tsimport { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import * as bcrypt from 'bcrypt'; import { User } from './user.entity'; @Injectable() export class UserService { constructor( @InjectRepository(User) private readonly userRepository: Repository<User> ) {} // 指定されたEメールアドレスのユーザを検索する findByEmail(_email: string): Promise<User> { return new Promise((resolve, reject) => { this.userRepository.findOne({ email: _email }) .then((user: User) => { resolve(user); }); }); } // ユーザを追加する add(user: User): Promise<User> { this.findByEmail(user.email) .then((currentUser: User) => { if (currentUser !== undefined) { return currentUser; } else { return new Promise((resolve, reject) => { if (currentUser === undefined) { // パスワードをハッシュ化する user.password = this.getPasswordHash(user.password); // ユーザ情報を設定する this.userRepository.save<User>(user) .then((result: User) => { resolve(result); }) .catch((err: any) => { reject(err); }); } }); } }) .catch((err: any) => { throw err; }); return undefined; } // パスワードをハッシュ化する private getPasswordHash(_password: String) { const saltRounds: number = 10; const salt: string = bcrypt.genSaltSync(saltRounds); return bcrypt.hashSync(_password, salt); } }Userモジュールを作成
最後にUserの構成を設定するUserモジュールの作成です。
以下のコマンドで、Userモジュールのひな形を作成できます。nest g module User
user.module.tsを以下のように実装します。
このコードもORMにTypeORMを使っていますので、それらしい実装が入っています。user.module.tsimport { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserService } from './user.service'; import { User } from './user.entity'; @Module({ imports: [ TypeOrmModule.forFeature([User]) ], providers: [ UserService ], controllers: [ ], exports: [ UserService ] }) export class UserModule {}Userモジュールの登録
最後に、srcフォルダにあるapp.module.tsに、UserModuleとUserServiceを登録します。
app.module.tsimport { Module } from '@nestjs/common'; import { UserService } from './user/user.service'; import { UserModule } from './user/user.module'; @Module({ imports: [ UserModule ], providers: [ UserService ], controllers: [ ] }) export class AppModule {} }ざっと、これで実装完了です。
- 投稿日:2019-03-01T18:51:28+09:00
VisualStudio CodeでNode.jsを実行するときに.envに定義したものをロードする
Node.jsでアプリを作るときに、プログラム上には埋め込みたくない情報を、.envというファイルを作って管理する事が多いです。
例えば、以下のようなものがあるでしょうか。.env# Postgresql ENV DATABASE_URL=postgres://postgres:password@localhost:5432/db # Session Secret SESSION_SECRET=KNLCKSO+JPOSJPOS # NODE Environment NODE_ENV=development # JWT Secret JWT_SECRET=@ofpowegldmvpvnp今までのやり方
今までは、dotenvパッケージをインストールし、プログラム上に以下のように記述し、.envの内容をロードしていました。
sample.tsimport * as dotenv from 'dotenv'; dotenv.config();これでもいいのですが、いちいちこう記述するのもめんどくさい。
Herokuにデプロイする事を考えると、そもそも必要ないことだし(Herokuでは、コンソールから環境変数を予め定義しますので、.envファイルそのものが必要ありません)これは便利
何か他にいい方法はないものかと模索していましたら見つけました。
VisualStudo Codeに.envをロードする機能があるのです。
.vscodeフォルダにlaunch.jsonというプログラムの実行を定義するファイルがあります。
その定義の中に、envFileを追加し、.envのファイル名を指定すると、実行するときに.envの内容をロードしてくれます。launch.json{ "version": "0.2.0", "configurations": [ // nodeによるnode.jsのデバック設定 { "type": "node", "request": "launch", "name": "node", "program": "${workspaceFolder}\\dist\\server\\server.js", "envFile": "${workspaceFolder}/.env", // ← ココ!! "sourceMaps": true } ], }これでコードを書くことなく、.envに定義したものを、process.env.XXXXXとして使えるようになります。
便利!!
- 投稿日:2019-03-01T18:25:22+09:00
SlackとCloud Functionsで迅速に障害の初動対応をする
突然障害が起きると何をしたらいいか慌ててしまいませんか?
今回それを解決するために、1アクションで障害の初動対応をするslash commandを作りました。概要
障害対応の初動を早めるために
- Slackで/hogeと打つ
- チャンネルが作られる
- チームメンバーがinviteされる
- タスクリストがpostされる
という仕組みを用意しました。
上の例は誰もいないスペースでテストしています。
スペースにメンバーがいるとき、Slack Appのレスポンスはこのようにチャンネル名と、招待したメンバーが出力されます。
環境
- serverless 1.37.1
- Node.js 8
- Google Cloud Functions
- Slack
背景
障害対応のとき
- 突然のアラートに何をすればいいかわからない
- チームの誰が何の対応をしているかわからない
- ベテラン1人しか対応できないので時間がかかる
- チーム外から作業状況が見えない
- 社内Wikiで状況をまとめているがリアルタイム性が低い
というようなことがあると思います。
コンセプト
アラート検知後、以下のようなことを目指します。
- 障害の初動を迅速に行う
- チームメンバーの習熟度に関係なく全員で障害に当たることができる
タスクリストを表示することで突然の障害にも最低限何をすればいいかわかる- 作業状況をオープンにする
パブリックチャンネルを作ることで見たい人が情報を参照できる
slackのリアクションで何をやっているかわかるようにする- 作業状況がリアルタイムにわかるようにする
あくまで初動のための仕組みです。アラートの種類によって調査手段は変わります。
タスクリストはチームごとに適宜更新していく想定です。仕組み
SlackとGoogle Cloud Functionsを使って概要にある仕組みを構築しました。
フレームワークはServerlessを使いました。
- slash commandsでCloud FunctionsのHTTPトリガーを叩きます。
- slash commandは3秒のタイムアウトがあるのでまずはレスポンスを返します。
- 非同期でSlack APIを叩きます。
- Slack APIの結果を取得します。
- API呼び出しの結果をresponse_urlにPOSTします。
チャンネル名のprefix、招待するユーザ、タスクリストはyaml形式の設定ファイルに書きます。
チャンネル名は設定ファイルのprefixと時刻のsuffxが合わせて作られます。
(障害対応を想定して作ったので、チャンネル名を一覧したときわかるように時刻を入れてみました。)ソース
https://github.com/tom-256/emergency-slack
まとめ
メンバーの習熟度にかかわらず、チーム全体で透明性のある障害対応をおこないましょう。
今回の挙げた例はあくまで一例です。
チームに合った障害対応策を考えてみてはいかがでしょうか?参考
https://about.gitlab.com/2017/02/10/postmortem-of-database-outage-of-january-31/
GitLabのデータベースの事件は記憶に新しいでしょう。
対応状況をYouTube配信やGoogleDocsで外部に公開するのは難しいかもしれませんが、
関係部署など適切な範囲でオープンにできるといいですね。
- 投稿日:2019-03-01T00:21:14+09:00
Node.jsを使って、特定のツイートをしている人をフォローする
お馴染みのtwitterモジュールを使います。
フォローのAPIの公式ドキュメントはこちら(POST friendships/create)
使うメソッド
.stream('statuses/filter')で特定ツイートを検索.post('friendships/create')でフォローします。フォローする際はユーザーのIDかスクリーンネームを渡してあげる模様です。
フォロー関連はなかなかサンプルが見つからないですね。
筋トレのハッシュタグでツイートしている人をフォローするコードapp.js'use strict'; const twitter = require('twitter'); const client = new twitter({ consumer_key: '', // consumer keyを記入 consumer_secret : '', // consumer secretを記入 access_token_key : '', // access tokenを記入 access_token_secret : '' // access token secretを記入 }); // async/awaitで表現 const main = async () => { const stream = await client.stream('statuses/filter', {'track':'#筋トレ'}); stream.on('data', async data => { console.log(data.id_str); try { await client.post('friendships/create', {user_id:data.user.id}); console.log(`${data.user.name}さんをフォローしました。`); } catch (error) { console.log(error); } }); } main();最初ミスったエラー
こんな感じで
Cannot find specified user.というエラーメッセージがちょくちょく出てました。[ { code: 108, message: 'Cannot find specified user.' } ]こちらの記事を見て気づきましたが、
client.post('friendships/create', {user_id:data.id});という形で ユーザーのIDじゃなくてツイートのIDを指定してしまっていたのが原因だったみたいです。正しいのは、
client.post('friendships/create', {user_id:data.user.id});という指定ですね。参考




