- 投稿日:2020-05-13T22:59:20+09:00
RaspberryPiで撮った写真をCustom Vision Serviceで画像判定してどの猫がいるかLINEに返す
はじめに
LINEBotからRaspberryPiで写真を撮ってLINEにおくる!で作ったLINEBotにAI機能を追加してどの猫がいるか教えてくれるものを作りました。
概要
LINEBotからRaspberryPiを動かして写真を撮り、Gyazoに送って画像判定で何が映っているかと撮影した写真をLINEに送るものです。
画像判定
画像判定はMicrosoft Custom Vision Serviceを使用。
茶色と白黒とグレーの3匹のねこを学習させ、mixとして色々な猫を追加学習させて、3匹のどの猫に近いかを教えてくれます。
3匹以外の猫を判定するとmixが一番高い値で出ます!(catタグは全ての猫に追加)
できたもの
どのニャンコがいるか教えてくれるようになった!
— 3yaka (@3yaka4) May 13, 2020
ニャンコのプライバシーを守るため色でこたえるw
#ねこIoT #cognitive #customvision #linebot #raspberrypi pic.twitter.com/pILH3mwTq8使い方
RaspberryPiを猫がよくいる場所に置きます。
猫が見たいので LINEBotに「ねこ」と入れます。
(「ねこ」以外のワードには「何が見たいの?」と返してきます。)
写真を撮るのに時間がかかるので、「写真撮ってくるから待っててねー」、とつなぎのワードが入ります。
写真を撮って送るときに Azure Cognitive Vision ServicesのCustom Vision Serviceを使用してどの猫がいるかを画像を分類して、10秒ほどすると猫の種類と写真が送られてきます。環境
MacBook Pro macOS Mojave Visual Studio Code 1.44.0 Azure Cognitive Vision Services Custom Vision Service RaspberryPi 3B Release: 10 Codename: buster Node.js v12.16.3 npm v6.14.4 ngrok Pi camera 洗濯バサミコード
node.js'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); const PORT = process.env.PORT || 3000; const Gyazo = require('gyazo-api'); const gyazoclient = new Gyazo('***'); const config = { channelSecret: '***', channelAccessToken: '***' }; const app = express(); let whichCat; app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない) app.post('/webhook', line.middleware(config), (req, res) => { console.log(req.body.events); Promise .all(req.body.events.map(handleEvent)) .then((result) => res.json(result)); }); const client = new line.Client(config); async function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } let mes = event.message.text; if (event.message.text.match("ねこ")) { await client.replyMessage(event.replyToken, { type: "text", text: '写真を撮ってくるからちょっと待っててね' }); await nyanpi(); return nyancoPic(event.source.userId); } else { return client.replyMessage(event.replyToken, { type: "text", text: '何が見たいのかな?' }); } } const nyanpi = async (userId) => { const PiCamera = require('pi-camera'); const myCamera = new PiCamera({ mode: 'photo', output: `${__dirname}/nyan.jpg`, width: 640, height: 480, nopreview: true, }); await myCamera.snap() await gyazoclient.upload('./nyan.jpg', { title: "my picture", desc: "upload from nodejs" }) } //Custom Vision Service const sendCustomVision = async (imageURL) => { const CUSTOM_VISION_API_ENDPOINT_URL = '***'; // Prediction-Keyの値を Prediction-Key ヘッダーに入れる // JSONで送るので Content-type ヘッダーに application/json 指定 const configCustomVisionAPI = { url: CUSTOM_VISION_API_ENDPOINT_URL, method: 'post', headers: { 'Content-type': 'application/json', 'Prediction-Key': '***' }, data: { url: imageURL } }; // axiosの送信設定 let responseCustomVision; try { // POSTリクエストで送る responseCustomVision = await axios.request(configCustomVisionAPI); console.log("post OK"); // データ送信が成功するとレスポンスが来る console.log(responseCustomVision.data); // 全てのタグにcatが含まれているので、近似値が高いものがcatタグの時は2番目を返す const catTag = responseCustomVision.data; if (catTag.predictions[0].tagName === "cat") { whichCat = catTag.predictions[1].tagName } else { whichCat = catTag.predictions[0].tagName; } } catch (error) { console.log("post Error"); // ダメなときはエラー console.error(error); } } //gyazoから最新の写真URLを取得 const nyancoPic = async (userId) => { const response = await gyazoclient.list() const gyazoimgUrl = response.data[0].url; //画像判定に送る const sendcstmvison = await sendCustomVision(gyazoimgUrl); //タグの名前を猫の見た目に置き換え if (whichCat === "chairo") { whichCat = "ちゃいろ"; } else if (whichCat === "shirokuro") { whichCat = "しろくろ"; } else { whichCat = "グレー"; } return client.pushMessage(userId, [{ type: 'image', originalContentUrl: gyazoimgUrl, previewImageUrl: gyazoimgUrl }, { type: "text", text: `${whichCat}の猫がいるよ` }]); } app.listen(PORT); console.log(`Server running at ${PORT}`);参考サイト
Computer Vision サービスを使用した画像処理 - Learn | Microsoft Docs
感想
AIが簡単に実装できた
グレーがシンガプーラという猫種で他のことめちゃくちゃ顔が同じなのでシンガプーラ判定やってみたい。
ngrokなので早く何かにdeployしたい。
- 投稿日:2020-05-13T22:59:01+09:00
Win10 + node.js + SQL Server 2019 Express で Tedious Request を少し試してみる
Win10 + node.js + SQL Server 2019 Express で Tedious Request を少し試してみる
目的
・Tedious Request
・Package - mssql-tedious-int64
を参考に少し試してみる(select count(*) & select ~ order by)
対象はWin10 + node.js -> Ubuntu 18.04 + SQL Server 2019 Express対象テーブル
データの並びは以下に準拠
郵便番号データの説明
対象データにユニークキーになりそうなデータが無いので
8桁のテキストを追加するCREATE TABLE [dbo].[ZIPCODE]( [SEQ] [nchar](8) NOT NULL, [PREFCODE] [nchar](3) NULL, [KUBUNCODE] [nchar](8) NULL, [POSTAL5] [nchar](5) NULL, [POSTAL] [nchar](8) NULL, [PREFKANA] [nchar](20) NULL, [CITYKANA] [nchar](40) NULL, [ADDRKANA] [nchar](80) NULL, [PREFKANJI] [nchar](20) NULL, [CITYKANJI] [nchar](40) NULL, [ADDRKANJI] [nchar](80) NULL, [FLG1] [int] NULL, [FLG2] [int] NULL, [FLG3] [int] NULL, [FLG4] [int] NULL, [FLG5] [int] NULL, [FLG6] [int] NULL ) ON [PRIMARY]サンプルコード
地元県だと名前感あるので 17ISHIKA.CSV を使う
var Connection = require('tedious').Connection; var Request = require('tedious').Request; var TYPES = require('tedious').TYPES; var connectionConfig = { userName: 'demo', password: 'demo', server: '192.168.5.49', options: { database: 'demo' } }; var connection = new Connection(connectionConfig); connection.on('connect', function(err) { if (err) { console.log(err); console.log('err:connect'); connection.exit; process.exit(0); } console.log("Connected"); itemcount(); console.log("done:truncate"); }); function itemcount() { request = new Request("SELECT COUNT(*) FROM ZIPCODE;", function(err, rowCount) { if (err) { console.log(err); console.log('err:request:itemcount'); connection.exit; process.exit(0); } }); request.on('row', function(columns) { console.log('value: ' + columns[0].value); }); request.on('doneInProc', function (rowCount, more, rows) { console.log('doneInProc:itemcount: '+ rowCount + ' row(s) returned'); }); request.on('requestCompleted', function (err) { console.log("requestCompleted:itemcount"); itemselect() }); connection.execSql(request); } function itemselect() { request = new Request("SELECT * FROM ZIPCODE WHERE SEQ < '00000003' ORDER BY SEQ DESC;", function(err, rowCount) { if (err) { console.log(err); console.log('err:request:itemselect'); connection.exit; process.exit(0); } }); // 1行毎にイベントが発生する request.on('row', function(columns) { console.log('value: ' + columns[0].value); console.log('value: ' + columns.length); // var i = 0; // while (i < columns.length) { // console.log(columns[i++].value); // } }); request.on('doneInProc', function (rowCount, more, returnStatus, rows) { console.log('doneInProc: '+ rowCount + ' row(s) returned'); }); request.on('requestCompleted', function (err) { console.log("requestCompleted:itemselect"); connection.exit; process.exit(0); }); connection.execSql(request); }参考にしたのは以下のサイト
Tedious Request
Package - mssql-tedious-int64
Win10 + node.js から SQL Server 2019 Express に CSVファイルを書き込んでみる
Win10 + node.js から SQL Server 2019 Express に CSVファイルを書き込んでみる 修正版
- 投稿日:2020-05-13T22:10:49+09:00
Slack SDKの公式ドキュメントのコードをそのまま利用したらサーバー障害で地獄を見た
Overview
みんな大好きSlack、私ももれなく処理結果等の通知で使用しています。
今まで何の不具合もなく使っていたはずなのですが、障害発生によってSlack SDKがやらかしてくれたので残しておきます。(今まで発生していたのを見逃していたのかもしれない)最初に言っておきますが、絶対にドキュメント読むことなく公式ドキュメントのコードを使用することは避けてください。
Target reader
- Slack SDKを利用していて、Slack SDKのリトライポリシーを理解していない方。
Prerequisite
- Node.jsのバージョンはGoogle Cloud Function(GCF)に依存し、現時点ではV10系とする。
- Node.jsの例ではあるが、各SDKでも
WebClientを利用していることから恐らく同様と考えられる。- Node.jsのソースコードはesmというパッケージを導入し、import/exportで記述している。
@slack/web-api: "5.8.0"Body
何が起きたのか?
Slackでサーバートラブルが発生しました。
https://status.slack.com//2020-05/147dad376c8946ffその結果簡単に終わる処理が9分動作しても完了せずタイムアウトしました。
更にFunctions自体もリトライでフォローするようになっているため、合計20分程度かけましたが処理はタイムアウトで終了しました。
私の場合は1つのインスタンスが20分動いただけですが、これが100とかだとすると結構いたいです。
また、正常終了が大前提だった場合は焦ることになったでしょう。その時実行していたログが以下です。
間隔をあけながらリトライを繰り返しているようです。
間隔が次第に大きくなっていることから、意図的に作られた再試行だというのが読み取れます。GCFは1回につき最大10分しか実行できないため、9分でタイムアウトさせています。
CloudFunctions.logD 2020-05-13T00:01:01.868207720Z myfunction xxxxxxxx Function execution started myfunction xxxxxxxx A 2020-05-13T00:01:01.936Z myfunction xxxxxxxx start myfunction. A 2020-05-13T00:01:07.279Z myfunction xxxxxxxx [WARN] WebClient:0 http request failed An HTTP protocol error occurred: statusCode = 503 myfunction xxxxxxxx A 2020-05-13T00:01:08.898Z myfunction xxxxxxxx [WARN] WebClient:0 http request failed An HTTP protocol error occurred: statusCode = 503 myfunction xxxxxxxx A 2020-05-13T00:01:12.311Z myfunction xxxxxxxx [WARN] WebClient:0 http request failed An HTTP protocol error occurred: statusCode = 503 myfunction xxxxxxxx A 2020-05-13T00:01:17.444Z myfunction xxxxxxxx [WARN] WebClient:0 http request failed An HTTP protocol error occurred: statusCode = 503 myfunction xxxxxxxx A 2020-05-13T00:01:35.508Z myfunction xxxxxxxx [WARN] WebClient:0 http request failed An HTTP protocol error occurred: statusCode = 503 myfunction xxxxxxxx A 2020-05-13T00:01:56.226Z myfunction xxxxxxxx [WARN] WebClient:0 http request failed An HTTP protocol error occurred: statusCode = 503 myfunction xxxxxxxx A 2020-05-13T00:02:55.448Z myfunction xxxxxxxx [WARN] WebClient:0 http request failed An HTTP protocol error occurred: statusCode = 503 myfunction xxxxxxxx A 2020-05-13T00:04:45.895Z myfunction xxxxxxxx [WARN] WebClient:0 http request failed An HTTP protocol error occurred: statusCode = 503 myfunction xxxxxxxx A 2020-05-13T00:07:47.685Z myfunction xxxxxxxx [WARN] WebClient:0 http request failed An HTTP protocol error occurred: statusCode = 503 myfunction xxxxxxxx D 2020-05-13T00:10:01.933989541Z myfunction xxxxxxxx Function execution took 540067 ms, finished with status: 'timeout' myfunction xxxxxxxxソースコードは以下になります。
Slack関連の処理はほぼ公式ドキュメントそのままです。utils/slack.jsimport { WebClient } from '@slack/web-api'; import { token, conversationId as channel } from '../configs/slack'; const web = new WebClient(token); const postMessageToChat = async (text) => { try { // See: https://api.slack.com/methods/chat.postMessage const res = await web.chat.postMessage({ channel, text }); console.log('Message sent: ', res.ts); } catch (e) { console.error(e); // ignore error // throw e; } }; export { postMessageToChat };index.jsconst requireEsm = require('esm')(module); const { postMessageToChat } = requireEsm('./utils/slack'); exports.myfunction = async (data, context, callback) => { console.log(`start myfunction`); try { await postMessageToChat(`Start myfunction`); // ここがエラーの発生元 // my code await postMessageToChat('End myfunction'); console.log(`end myfunction`); callback(); } catch (e) { console.error(e); callback(e); } };本来ならSlackに通知してすんなり終了する処理ですが、タイムアウトしてしまう悲劇に遭遇しました。
Slackがトラブルなら送信できないのはやむなしですが、どうしてweb.chat.postMessage()は終了しなかったのかが問題です。どうやらリトライポリシーの設定が良くないようだ
issueを出そうと思い、issueをチェックしたりソースをざっと見ていた。
timeoutでは見つからなかったが、retryで検索するとどうもRetryPolicyはあるようだ。
これはpostMessage()でも使えるんじゃないか?と調査したら見えてきた不都合な真実…自動リトライ
https://slack.dev/node-slack-sdk/web-api#automatic-retriesThe WebClient comes with this queuing system out of the box, and its on by default! The client will retry a failed API method call up to 10 times, spaced out over about 30 minutes.
Google翻訳先生曰く
WebClientにはこのキューシステムが標準で付属しており、デフォルトでオンになっています。クライアントは、失敗したAPIメソッド呼び出しを最大10回再試行し、約30分以上間隔を空けます。
最大約30分使ってリトライとかおま
おれも何言っているかよくわからないんだがもう一度言っておく。
web.chat.postMessage()は障害発生時、最大約30分間応答しないということだ。
システムによってはどんどん応答待ちのスレッドが増加してくという怖いことになりかねない
(GCFは同時起動数が最大1000なので、10分間に1000回以上リクエストが来たらあふれそう)
時としておせっかい程始末に負えないものはないというのはこのことか!ドキュメントを更に読むとオプションでこのリトライポリシーを変更可能とある。
const { WebClient, retryPolicies } = require('@slack/web-api'); const web = new WebClient(token, { retryConfig: retryPolicies.fiveRetriesInFiveMinutes, });よく見て頭に入れてほしい。
WebClientにリトライポリシーオプションを含んだ第二引数のないプログラムは間違っているといっても過言ではない。
このコードこそが導入部のサンプルコードとしてあるべきコードだと私は考える。
もちろん、場合によってはメッセージの送信が重要な位置づけになっているため、30分にわたりリトライが適している場合もある。
その場合は恐らくエラーの際にフォローするためのコードが入っているはず。ちなみに
SlackとWebClientのキーワードでQiitaの記事をググってみたが、各言語のサンプルあれど一つもリトライポリシーのオプション指定はなかった。
つまり、相当見落としているとみてもいいだろう。私自身も送信だけなので、公式ドキュメントをコピーして使っていただけだ。
公式ドキュメントを読むということはやっぱり大切だと痛感した。リトライポリシーは以下の種類がある。Slackへの送信が失敗しても問題ないなら迷わず
retryPolicies.rapidRetryPolicyだろう。
気に入るものがないなら自分でカスタマイズできそうなので、リンク先を見るといいと思われる。
retryConfig 説明 retryPolicies.tenRetriesInAboutThirtyMinutes (デフォルト) retryPolicies.fiveRetriesInFiveMinutes 5分間で5回試行 retryPolicies.rapidRetryPolicy テストを高速に実行するために使用 { retries: 0 } 再試行なし(その他のオプション) ちなみに
rapidRetryPolicyは高速というけどどの程度?というのが気になるところ。
これは以下のissueに答えがあって、約10msで10回リトライとあるので、1ms待機後にリトライしていると思われる。
再試行は10回するのに応答速度は再試行なしと比較してもそんなに劣らないと思われる。
https://github.com/slackapi/node-slack-sdk/issues/734try 10 times in about 10ms
Conclusion
今回もまた改めて公式ドキュメントにしっかり目を通すことの大切さを学んだ。
公式ドキュメントの導入部のサンプルコードが時として牙をむくとか想像できなかったこれからレビューの際に見つけたらびしっと指摘できますね!
「再試行時も想定できないあなたはプロじゃない」Have a great day!
- 投稿日:2020-05-13T21:59:34+09:00
【Slack】レガシーテストトークンを使わずに絵文字を一括でエクスポートする
はじめに(対処した問題)
Slackの新しいワークスペースを作成するに伴い,現在使用しているワークスペースから絵文字を一括でエクスポート&インポートしようと企てていました.
参考サイトを基にエクスポートしようとしたら,Slackのトークン作成のところで躓きました.参考にさせていただいたサイト↓
Slackの絵文字(emoji)を一括エクスポート&インポートするというのも,今まで紹介されているエクスポート方法ではSlackのレガシートークンが使われているものが主でした.
しかし,2020/5/5にレガシーテストトークンが作れなくなりました.(すでに作成されているレガシートークンは使用&再生成できますが,新規作成ができないようになっています.)
The creation of legacy test tokens is deprecatedWe're deprecating legacy test tokens and will disallow the creation of new test tokens beginning May 5th, 2020.
2020/5/13現在のSlackで新しいトークンを使用するにはSlackアプリを作成するようにと記述されています.
API トークンの生成と再生成注意 :レガシーテストトークンを新しく作成することはできなくなりました。Slack API を操作するために新しいトークンが必要な場合は、代わりに Slack アプリを作成してください。
今回はレガシートークンで今まで絵文字をエクスポートしていたものを上記に紹介したSlackアプリで代替することで実現していきます.
手順(一括でエクスポートする)
基本的にはレガシートークンを使用してエクスポートする手順と同様のため,先程も紹介させて頂いた記事(Slackの絵文字(emoji)を一括エクスポート&インポートする)に沿って進めていきます.
- Node.jsをインストールする
- 必要なモジュールをインストールする
- エクスポート元のSlackチームのAPIトークンを取得する(←ここが変わる)
- スクリプトファイルを作成する
- スクリプトファイルを実行する
1. Node.jsをインストールする
ターミナルで
node -vを打つことで確認できます.ターミナル> node -vインストールされていればバージョンが表示されます(筆者はv12.16.3).
インストールされてない場合は以下のような記事を参考にインストールしてください.
windows10にNode.jsをインストールする
MacにNode.jsをインストール2. 必要なモジュールをインストールする
今回使用するモジュールをインストールします.
ターミナル> npm install slack-node > npm install request > npm install fs3. エクスポート元のSlackチームのAPIトークンを取得する(←ここが変わる)
ここまでは筆者も順調に進んでいましたが,参考記事にあるようなレガシートークンを発行できないところで詰みました.
そこで今回はSlackアプリを作成することで乗り越えます(他にも良い方法があるかもしれません.)Slack APIにアクセスし,右上の「Your Apps」をクリックする.
「Create New App」をクリックして,「App Name」に適当なアプリ名(例:import-emoji)を入れて,「Development Slack Workspace」にエクスポートしたいワークスペースを選択する(ここでエクスポートしたいチャンネルが出ない場合はログインが必要).
作成したらページが遷移するので「Permission」をクリック.
「Scopes」の「User Token Scopes」の下にある「Add an OAuth Scope」をクリックし,「emoji:read」を選択する("emoji"と入力すると候補で上位に出てくる).
選択すると下図の状態になる.ページの上部に進むと先程までグレーでクリックすることができなかった「Install App to Workspace」が緑色になり,インストール可能となっているため,クリックしてインストールする.
(※ここで無料のワークスペースの場合は10個アプリを連携しているとエラーになってしまうので注意)作成を許可するとトークンが発行されるのでこちらを次の作業のためにコピーして用意しておく.
これでレガシートークンの代わりとなるトークン作成が終わりました.
4. スクリプトファイルを作成する
VSCodeなどのエディターツールで以下のコードをコピー&ペーストする.(ここは下記サイトにある@kureさんや@ne-peerさんの記事にあるコードを拝借致します.)
参考にさせて頂いたサイト
Slackのカスタム絵文字を全てダウンロードする
Slackの絵文字(emoji)を一括エクスポート&インポートするファイル名は自分が認識できれば何でもOKです.(下記のファイル名は例)
slack-emoji-export.jsvar Slack = require('slack-node'); var request = require('request'); var fs = require('fs'); apiToken = "<apitoken>"; // ここにAPIトークンを貼り付ける。 slack = new Slack(apiToken); slack.api("emoji.list", function (err, response) { for(key in response.emoji){ url = response.emoji[key]; //エイリアスは無視 if(url.match(/alias/)){ continue; } // 取得対象の拡張子 extention = url.match(/\.[^\.]+$/); request .get(url) .on('response', function (res) { }) .pipe(fs.createWriteStream('image/' + key + extention)); } });上記コードにある
// ここにAPIトークンを貼り付けるの行にある"<apitoken>"の部分に先程コピーして用意していたトークンを貼り付ける("xoxp-00000~"という形になればOK).5. ディレクトリを作成し,スクリプトファイルを実行する
スクリプトファイルを作成&保存した階層と同階層に
imageというディレクトリを作成する.(これをしないとエラー発生)ディレクトリ作成後,以下のコマンドを入力することでエクスポートが完了する(imageディレクトリにすべての絵文字が保存されます).
【補足】一括インポート
筆者が確認時点(2020/5/13)では,一括インポートは従来どおりの方法で可能なことが確認できています.
従来どおりの方法
Chomeの拡張機能をインストール
Neutral Face Emoji Tools上記ツールをインストール後,
Slackで「ワークスペースをカスタマイズ」の「絵文字」のタブに行くとドラック&ドロップで一括で絵文字をインポートできます.筆者がインポートに使用しようとしたときには上の画像のように拡張ツールのインポート画面が出てこなくて非常に焦りましたが,適当なカスタム絵文字を1つ追加するだけで上記のような画面が出てきます.
おわりに
これで今まで通りSlackのカスタム絵文字を簡単に移行させて使い倒していきましょう.
参考サイト一覧
Slackの絵文字(emoji)を一括エクスポート&インポートする
The creation of legacy test tokens is deprecated
windows10にNode.jsをインストールする
MacにNode.jsをインストール
Slack API
Slackのカスタム絵文字を全てダウンロードするおすすめの記事
- 投稿日:2020-05-13T20:03:17+09:00
【Node.js】node-mysqlをたった3行で操作する
はじめに
Node.jsでmysqlを使おうとした時、大体はnode-mysqlかpromise-mysqlを使うと思うのですが、どちらにしても大変面倒くさいのでnode-mysqlをラップしてmysqlを簡単に操作するためのコードを書いてみました。
結論から言うと、ネストをほぼ発生させずに処理を3行~にまとめる事が出来ました。モジュールにしたら保守とか色々面倒くさそうなのでコードのままここに上げておきます。promiseを返したかったり、足りない部分があった場合は各位で調整してください。
また動作確認はしていますが、下記コードを使用したことによる損害等は一切保証しませんので悪しからず。
「これコピペしても動かないんだけど!」とか言う輩はNode.js向いてないので辞めたほうが良いと思うよ
使い方
test.jsvar test = require("./DbAdapter"); var hoge = new test(); hoge.getData("select * from ?","hoge",function(err,result){console.log(result)});実際に使う時はこんな感じ、データの取得に関しては、ほぼnode-mysqlの使い勝手で行ける。プレースホルダ使わない場合は値の代わりにnullぶち込むだけでOK
test2.jsvar test = require("./DbAdapter"); var hoge = new test(); test.setDataAddSql("insert into hoge value('110');"); test.setDataAddSql("insert into hoge value(?);",120); try{ test.setDataExe(); }catch(e){ //エラー処理 }データを書き込む時は、SQLを追加してから実行メソッドを叩くだけでOKです。まあ失敗した時はエラー返すのでその都合でちょっと長くなりますがSELECT文は3行で行けるので許して
setDataAddSql()は配列に値をぶち込むだけなので、同期処理とか考えなくても良いのは便利ですね。本体の実装
DbAdapter.js (クラス本体)/**DBを操作するクラス。 * newしてメソッドを叩くだけ!!! */ class DbAdapter{ constructor(){ this.sqlArray = new Array(); this.valueArray = new Array(); } /**単一の情報取得クエリを実行します(コミット無し) */ getData(SQL,value = null,callback = function(err,result){}){ var pool = require("./pool").getPool(); var error = null; pool.getConnection(function(err,connection){ if(err){ error = err; } connection.beginTransaction(function(err){ if(err){ error = err; } connection.query(SQL,value,function(err,results,fields){ if(err){ error = err ; } connection.rollback(function(err){ if(err){ error = err; } connection.release(); callback(error,results); }); }) }) }) } /**SQL文をインスタンスに追加します */ setDataAddSql(SQL,value = null){ this.sqlArray.push(SQL); this.valueArray.push(value); } /**追加されたSQL文を実行します */ setDataExe(){ //thisの値とメソッドを取得 var nowSqlArray = this.sqlArray; var nowValueArray = this.valueArray; var recursiveQuery = this._recursiveQuery; //コネクションを実行 var pool = require("./pool").getPool(); pool.getConnection(function(err,connection){ if(err){ throw err; } //トランザクション開始 connection.beginTransaction(function(err){ if(err){ throw err; } recursiveQuery(nowSqlArray,nowValueArray,connection,recursiveQuery); //jsのクラスはメソッドの中から自身への参照を取れないので引数で渡す }) }) } /**connectionの処理をラップしたメソッド。クラス外部からは使用禁止 */ _recursiveQuery(sqlArray,valueArray,connection,self){ var newSqlArray = sqlArray; var newValueArray = valueArray; var myself = self; //エラー制御 if(newSqlArray.length == 0){ console.log("SQLが設定されていません"); connection.rollback(function(err){ if(err){ console.log(err); } connection.release(); }); return; } //クエリを送信 connection.query(newSqlArray.shift(),newValueArray.shift(),function(err,results,fields){ //エラー処理 if(err){ connection.rollback(function(err){ if(err){ console.log(err); } connection.release(); }); throw err; } //SQLを全て実行し終わった時 if(newSqlArray.length == 0){ connection.commit(function(err){ if(err){ console.log(err); connection.rollback(function(err){ if(err){ console.log(err); } connection.release(); }); throw err; } console.log("SQL exe"); return; }) //まだSQLが残っている時(再帰処理) }else{ myself(newSqlArray,newValueArray,connection); } }) } } module.exports = DbAdapter;
………まあこのコードを見たJS使いの諸兄が言いたいことは分からんでも無いですが、元々JavaとかC#とか書いてた民なのでPromiseそんなに好きじゃないんだ。「Promiseを返す」って所までは分かるけど、再帰処理とか値を返す処理とか(そもそもjsでそれを書くべきではない)を書き始めると一気に難読化しない???
という与太はそこら辺にしておいて解説に移りたいと思います。
と言ってもgetData()とかその辺はコード読めば分かりますね。ライブラリの呼び出しをただ単にラップしてるだけです。beginTransactionとか何回も書くのは嫌なので、そういう何やかんやは全てメソッドの中にまとめました。途中で出てくるvar pool = require("./pool").getPool();は別クラスで定義したコネクションプールの呼び出しです。
実装はこんな感じ
pool.jsclass Pool{ constructor(){ var mysql = require("mysql"); this.pool = mysql.createPool({ connectionhoLimit:100, host :"host", user :"hoge", password : "password", database:"huga" }); } getPool(){ return this.pool } } module.exports = new Pool()
特段解説する所は無いですね。よりセキュアな実装を求めるならPASSはベタ書きじゃなくて、より安全な保管場所から拾うべきです。ちなみmodule.exportsしてるのはクラスじゃなくてインスタンスなので、地味にシングルトン風味だったりします。
で話をDbAdapter本体に戻すのですが、書き込み側は配列にSQLを追加するsetDataAddSql()と実行処理のトリガー、再帰処理を含むクエリの実行と言った感じに3つのメソッドで実装しました。使用感としてはJavaのStringBuilderを参考にしたので、一部の方には馴染みが深いと思います。
と言った風に、まあ書いてしまえばそれだけなのですが、この実装にたどり着くまで5回書き直して丸3日かかりました。
意外かもしれませんが、最初はSELECT文発行してデータを受け取る所で詰まりましたね。今思えば何で詰まってたんだろうって感じですが、Node.jsはJavaみたいにスタックでコードを考えると十中八九うまく行かないみたいです。というかどれだけ糖衣錠しようが実際に動かす時はスクリプトチックな挙動をするのでコールスタックなんて幻想ですよ。ちょっとした関数でも値を返すより、その後の処理を値で受け取るのがベター(まあ素直にpromise返せばそれで良い気がするけど……)。Node.js の return と for を信じてはいけない(戒め)
あと詰まった所といえばクラスプロパティのスコープですね。コールバック関数の中でthis.hoge = huga とか書いても値入らないしエラー吐かないのにはビビる。
あとコードの中にコメント書いてるけど、メソッドの中から自分自身をthisで取得しようとするとエラるね。これは引数で渡してご強引に解決しました。
他にも色々詰まった気がするけど、正気を保つためにコーディング中の記憶を全て消去したので、あとは覚えてないです。
おわりに
現在はNode.jsでWebサービスを開発中です。
開発メンバーを募集してるので興味のある方はTwitter@hoge19194545までご連絡ください↑とかやると金関係人間関係その他その他の問題がこじれて開発どころの話では無くなるので募集しません。
1度言ってみたかっただけその代わりと言ってはなんですが、サービス開発してるのは本当なのでツイッター等フォローして下さるとありがたいです。
またどこかでお会いしましょう。ご安全に!
参考資料とか
Node.jsでMySQLを使うメモ
https://qiita.com/PianoScoreJP/items/7ed172cd0e7846641e13
[Node.js][MySQL]Promiseを使ってトランザクションを書きやすくする
https://tech.chakapoko.com/nodejs/mysql/promise.html
Node.jsでシングルトンなクラスモジュール
https://memo.appri.me/programming/nodejs-singleton-class
- 投稿日:2020-05-13T17:49:16+09:00
画像分析サイトを20分で立ち上げる
IBM Cloud を使って、画像分析サイトを瞬時に立ち上げる方法をお伝えします。
デプロイまで15分前後でだいたいあげられてます。この内容を実行するには、IBM Cloudアカウントが必要になります。
ライトアカウント(クレカ登録不要、期限なし)で実行可能です。
→登録はこちらこの手順で作成されるのは、次のサービスです。
- Cloud Foundry Application Node.js
- Service(Continuous Delivery, Visual Recognition)
- Cloud Application(Visual Recognition)
- Toolchain
*ライトアカウントの方は、メモリの制限にかからないか事前に確認してくださいWatson Visual Recognition Basicを立ち上げる
「カタログ」メニューから「ソフトウエア」「Webとアプリケーション」にチェックで対象のサービスを絞り込み、「Watson Visual Recognition Basic」をクリックします。
画面下部の「はじめに」をクリック(または「作成」タブを選択で同じ画面に遷移します)
「作成」をクリックします。
アプリのデプロイ環境を構築立ち上げます
- 「デプロイメント自動化」のパネルから「アプリのデプロイ」をクリックします。
![]()
- デプロイメント自動化のターゲットに「IBM Cloud Foundry」を選択します。
- IBM Cloud API キーの「新規」作成をします。
- 他の入力項目を確認し(初期値可)「次へ」をクリックします。
- 次ページでDevOps Toolchainの項目を確認し(初期値可)「作成」をクリックします。
- コードの生成を待ちます(BUILD・DEPLOY共に緑になれば作成ずみ)
![]()
- Delivery pipeline が表示されたら「名前」のリンクをクリックします。
![]()
- DEPLOY パネルの最終実行の結果の「コンソールの表示」をクリックします。
![]()
- (3)で指定した名前のアプリケーションが表示されるので実行中であることを確認し、「アプリURLにアクセス」をクリックします。
作成したサイトで画像分析を試しましょう
- 左画面下部、青枠の画像を切り替えると、右画面の結果が変わります。
- 左画面下部、ピンク枠をクリックすると、画像のアップロードができ、結果が試せます。
こんな感じ!!
で、どうするのかって突っ込まれそうですが、ここからどうするかはまた改めて考えます。
- 投稿日:2020-05-13T13:48:43+09:00
この解決策→nvm:854: no such file or directory: /Users/suin/.nvm/nvm-exec
- 投稿日:2020-05-13T11:17:31+09:00
Node.jsでDiscordBotを作る
初の記事ですねはい。
今回は、Node.jsを使ってDiscordでBotを作っていこうと思います。インストールするもの、必要なもの
node.js(https://nodejs.org/ja/)
discord.js
VScode(エディタならVScodeである必要はありません)
discordBotのTOKEN
(当たり前だけどDiscordのアカウント)node.jsをインストールする
https://nodejs.org/ja/
ここへ行くとこのような画面になると思います
(2020/5/13現在の表示です。この画像とバージョンが変わっている可能性があります)
今回は、左の「推奨版」でいいかと思います
するとインストーラーがダウンロードされると思うので、実行しましょう
(インストーラーでダウンロードする手順はそこまで難しくないので割愛します。画面に表示されるとおりに進めていけば大丈夫なはずです)discord.jsのインストール
インストールができたら、エディター(私の場合VScode)を起動しましょう
どこでも構わないので適当にファイルを作り、開いてください
そしたら、ターミナルでnpm initと打ちましょう。
なんか色々出てきますが、とりあえずEnterで飛ばしちゃって大丈夫かと思います
最後にIs this OK? (yes)ってのが出るので「yes」と打ちましょう
そうすると、package.jsonというファイルが作られているはずです
それが確認できたら、今度はnpm install discord.js --saveと打ってください
package-lock.jsonというファイルとnode-modulesというフォルダ(中にめちゃくちゃなんかある)ができていれば成功です!
ここまで出来たら今度はBotのアカウントを作ってTOKENを取得します!TOKENの取得
discord Developer Portal(https://discordapp.com/developers/applications/)
へ行きましょう
するとこんな画面になります
そしたら、右上のNew Applicationを押してください。
Bot名を決めて、ここに入れましょう(厳密にいうとBot名ではなくアプリケーション名です。ここに入れた名前はBotを招待するときに表示されます)
こんな画面になるので、左のBotというところを押してください
Add Botを押しましょう。ほんとにBotを追加する??的なのが出るのでyes, Do it !
てんててーん!これでBotアカウントの作成は終わりです!
そしたら、copyというのを押して、TOKENをコピーして取っておいてください。
TOKENの例
NzA5OTM3Njg2ODY5MTgwNTMz.XrtLnQ.4wBNgKLc14aeOtDy3468Xs7STwo
(ここ最近で作ったならNから始まってるかな...?)
ここで注意!
ここでコピーしたTOKENは誰にも見せてはいけません!
他人に取られると、BOTを乗っ取られてしまいます!(ここでちょっと余談。よく間違えがちなのが、Bot名を決めた後にページにある
CLIENT SECRETというところのコピーしてしまい、「TOKEN入れてコードもおかしくないのに動かない!」ということになることがあります。間違えないように!)コードを書いていく
さぁそしたらエディタ-に戻りましょう。
index.jsというファイルを作り、書いていきます。index.jsconst discord = require('discord.js'); const client = new discord.Client(); /*必須*/ client.on('ready', () => { console.log('bot ready!'); }); /*起動に成功したらコンソールに"bot ready!"と表示します*/ client.on('message', message =>{ if(message.content === "こんにちは"){ message.reply("こんにちは!") } /*もし送られたメッセージがこんにちはならこんにちは!と返します*/ }) /*messageが送られたときに反応します*/ client.login('TOKEN'); /*ここにさっきコピーしたTOKENを入れましょう*/このこーどは、「こんにちは」というメッセージが送られたらメンション付きで「おはよう!」と返すコードです
Botをサーバーに招待する
先ほどのbotアカウント管理画面にいきましょう
OAuth2というところを押してください
そうしたら、Botというところにチェックを入れてください
そしたら下になんかでるのでAdministratorを押して、でできたURLをコピーしてください
(このURLは今後別のサーバーにBOTを招待するときにも使えるので取っておくといいかもしれません)
そしたらコピーしたURLに飛びましょう
招待するサーバーを選択してはいを押してください。
権限を与えるとこに確認を求められるので確認し、認証を押しましょう。
私はボットではありませんってやつがでるのでチェックしてください
(指定した画像を押すことを求められる事があります)
これで招待は成功したはずです!
サーバーを見てみましょう
確かにいますね!
そうしたらあとは起動するだけ!
ターミナルでターミナルnode index.jsとうってコンソールにbot ready!と出れば成功です!
無事オンラインになりました!
ちゃんと動いてますね!終わり
お疲れ様でした!
これでbotができました!
今後別の記事で機能を追加方法なども出すつもりです!
長い記事を読んで下さりありがとうございました!(もしかしたら抜けてるところとか間違えてるところがあるかもしれません。もしも間違えがあったらコメントで指摘してください)
参考にしたサイトやページなど
- 投稿日:2020-05-13T10:38:07+09:00
axiosを使用してLiquid APIを叩き、node.jsで実行
初学者ですが自分へのメモの意も含め、誰かのお役に立てたらと思い執筆します。
タイトルの通りaxiosを使用して、APIを叩き、node.jsで実行してBTC/JPYをコンソール上に出力します。
今回は認証不要なPublic APIを使用。
補足情報やアドバイスがあれば、ぜひコメントいただければ幸いです。参考元
Liquid API ドキュメント:https://developers.liquid.com/
https://kennejs.com/entry/2019/01/23/001715
https://qiita.com/shisama/items/61cdcc09dc69fd8d3127環境
macOS Mojave 10.14.6
事前準備
node.jsのインストール
https://qiita.com/oreo3@github/items/622fd6a09d5c1593fee4yarnのインストール
https://classic.yarnpkg.com/ja/docs/install/#mac-stableaxiosのインストール
https://github.com/axios/axios#installingaxiosって何?
https://www.willstyle.co.jp/blog/2751/
公式:https://github.com/axios/axiosソースコード
node.jsconst axios = require("axios").default; let url = "https://api.liquid.com"; let path = "/products/5"; let query = ""; axios .get(url + path + query) //指定した引数へリクエストを送る .then((res) => { const items = res.data; console.log(items.last_traded_price, items.market_bid, items.market_ask); }) .catch((error) => { const { status, statusText } = error.response; console.log(`Error! HTTP Status: ${status} ${statusText}`); });まとめ
APIを叩くことも初めてだったので、最初は"request"と"axios"を2つとも使用してしまっていました。汗
また、「axios.create」などでインスタンスも作成できるようですが、どんな必要性 or 用途で使用するのかもこれから勉強したいと思います。
- 投稿日:2020-05-13T10:38:07+09:00
【axios】axiosを使用してLiquid APIを叩き、node.jsで実行
初学者ですが自分のメモの意も含め、誰かのお役に立てたらと思い執筆します。
タイトルの通りaxiosを使用して、APIを叩き、node.jsで実行してBTC/JPYをコンソール上に出力します。
今回は認証不要なPublic APIを使用。
補足情報やアドバイスがあれば、ぜひコメントいただければ幸いです。参考元
Liquid API ドキュメント:https://developers.liquid.com/
https://kennejs.com/entry/2019/01/23/001715
https://qiita.com/shisama/items/61cdcc09dc69fd8d3127環境
macOS Mojave 10.14.6
事前準備
node.jsのインストール
https://qiita.com/oreo3@github/items/622fd6a09d5c1593fee4yarnのインストール
https://classic.yarnpkg.com/ja/docs/install/#mac-stableaxiosのインストール
https://github.com/axios/axios#installingaxiosって何?
https://www.willstyle.co.jp/blog/2751/
公式:https://github.com/axios/axiosソースコード
sample.jsconst axios = require("axios").default; let url = "https://api.liquid.com"; let path = "/products/5"; let query = ""; axios .get(url + path + query) //指定した引数へリクエストを送る .then((res) => { const items = res.data; console.log(items.last_traded_price, items.market_bid, items.market_ask); }) .catch((error) => { const { status, statusText } = error.response; console.log(`Error! HTTP Status: ${status} ${statusText}`); });まとめ
APIを叩くことも初めてだったので、最初は"request"と"axios"を2つとも使用してしまっていました。汗
また、「axios.create」などでインスタンスも作成できるようですが、どんな必要性 or 用途で使用するのかもこれから勉強したいと思います。
- 投稿日:2020-05-13T10:16:30+09:00
Netlify Functionsのローカル環境をyarn startで起動する設定
Netlify Functionsの開発、検証用としてローカルでの実行環境が用意されている。netlify-lambdaパッケージをローカルにインストールしている場合、以下のコマンドを実行することで
http://localhost:9000/.netlify/functions/ファンクション名にアクセスできるようになる。% npx netlify-lambda serve src/lambda通常は、ファンクションだけではなく、webページとあわせて起動する必要がある。たとえばwebページをVue.jsで開発している場合、webページは以下のコマンドで起動する。
% vue-cli-service serveこれらをpackage.jsonに登録することで、npmあるいはyarnコマンドで実行できるようになる。さきにファンクションの環境を起動して、しばらくしてからwebページを起動しないと、ファンクションが起動できない。startにwebページの起動、prestartにファンクションの起動と3秒の待機(sleep 3)を設定することで、yarn startでwebページとファンクションが起動できるようになる。
package.json... "scripts": { "start": "vue-cli-service serve", "prestart": "npx netlify-lambda serve src/lambda & sleep 3", ...
- 投稿日:2020-05-13T09:30:16+09:00
Promise async function result / return value VS then result / #javascript #node
- 投稿日:2020-05-13T09:30:16+09:00
Promise { <pending> } in Javascript / async await function / return value VS then result #node #javascript
非同期の関数 ( async ) では返り値が
Promiseとなるので、直接返り値を利用できない
then で結果が出たことを待ち受けて、その中で関数の返り値を利用するfunction sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function f() { await sleep(1000); return "XXX"; } const result = f() console.log(result) // Promise { <pending> } f().then(result => { console.log(result) }); // XXXOriginal by Github issue
- 投稿日:2020-05-13T01:20:04+09:00
Build an app using SQL Server 目次
はじめに
今回は、SQL Server を使ったアプリを開発しようということで、Building an app using SQL Server の内容をベースに、SQL Server を使ったアプリ開発関連の記事を一元管理できるように目次ページを作成しました。
※ページは、随時更新予定です。C#
- 投稿日:2020-05-13T00:00:35+09:00
GASでReactを実行するテンプレート
はじめに
最近Google Apps ScriptでWebアプリを作ることが多いです。無料で作れるWebサーバー的用途で使えるので便利なんですよね。ただGoogle Apps Scriptで構築する際に諸々制限も発生します。これを回避したテンプレートの開発環境を公開します。
GASでReactを使う制限と対策
- WebpackでバンドルしたHTMLをUpload出来ない
制限はこれにつきます。普通にGoogle Claspでアップロードすると重すぎてブラウザが固まっておちるのと何とかWebアプリケーションとして公開しても全く動きません。よってWebpackバンドル時に各々node_moduesをバンドルしないようにしてReactがうまく動くような環境にします。
前提
- Google ClaspでGASの開発を既に行なっている方を対象とします
開発環境構成
- Node.js 13.13.0
- Visual Studio Code
- 利用モジュールは以下
{ "name": "web-app-template", "main": "./src/Index.tsx", "license": "MIT", "private": true, "scripts": { "dev": "NODE_ENV=development npx webpack-dev-server --open --hot", "shell": "rm build/app.js & cp ./src/gas/app.js ./build/app.js", "build": "NODE_ENV=development npx webpack && npm run shell && clasp push --force && clasp open", "deploy": "NODE_ENV=production npx webpack && npm run shell && clasp push --force && clasp open" }, "dependencies": { "@types/google-apps-script": "1.0.12", "@material-ui/core": "4.9.10", "@types/react": "16.9.34", "@types/react-dom": "16.9.6", "@types/react-router-dom": "5.1.4", "html-webpack-inline-source-plugin": "1.0.0-beta.2", "html-webpack-plugin": "4.2.0", "react": "16.13.1", "react-dom": "16.13.1", "react-router-dom": "5.1.2", "ts-loader": "6.2.2", "typescript": "3.8.3", "webpack": "4.42.1", "webpack-cli": "3.3.11", "webpack-dev-server": "3.10.3" } }ソースコード
以下Githubにアップロードしました。
web-app-template基本的な使い方
- プロジェクトをダウンロードしたディレクトリでまずはApp Scriptプロジェクトを紐付けましょう。
clasp create --rootDir ./src
- まずは以下コマンドでテストします
npm run devこれにてローカルでWebサーバーが立ち上がり、Web画面のイメージが表示されます。
- ./src/pages内のtsxファイルを編集しましょう
- 編集が完了したら以下コマンドでApp Scriptに反映しましょう
npm run deploy上記を行った後、対象のApp ScriptでWebアプリケーションを公開すれば構築したReactのサイトが表示される筈です。ReactからApp Scriptを呼ぶ時
./src/gas/googleScriptRun.jsに以下のようなスクリプトがあります。
google.script.run.withSuccessHandler(function () { console.log("success"); }).recieveSpreadsheet();これはWebアプリケーション側よりGAS上の関数を実行する記載方法で以下のような構文になります。
google.script.run.withSuccessHandler(__コールバック関数__}).実行するGASの関数名();よって上記google.ScriptRun.jsにてWebアプリ側で呼びたい関数を記載し、./src/gas/app.js側に呼ばれる関数を記載すればWebアプリケーション側よりGAS上の関数を実行することが可能です。
上記、もし何かのお役に立てばお使い頂ければ幸いです。
宜しくお願い致します。以下でも他詳細を記載しています。
Blogger































