- 投稿日:2020-09-28T23:41:46+09:00
Qiitaでプログラミング言語トレンド
1. 概要
日本ではプログラミング言語のトレンドはどんな感じか知りたいので、Qiitaの過去データを取得して分析してみましょう。以下の写真は世界でのトレンドです(参考:PYPLサイト)。
2. データ取得
2.1. APIの利用
APIの制限のため、問題がいくつか発生しました:
- 全部投稿を取得するAPIがありません(全部は580000投稿ぐらいがある)。
- APIによる一回の上限は1万投稿です(100ページ * 100投稿/1ページ)。
- 実際は5900投稿を取得すると、403エラーが発生してから、APIをアクセスできない(スパムから保護するため、1時間60リクエストだけ) → あるタグの投稿数は5900投稿以下のためフィルターを適用します(Stocksの数は30以上)。AWSのEC2インスタンスを適用して取得した時、403エラーになると、インスタンスを再起動しました(新しいIPになる)。
- あるタグの投稿数は5900以下なのに、全部投稿を取得できません。2017年09月の時点ぐらいには500エラーが発生しました。 → 最後取得できた投稿の作成日を保存して、新しいリクエストを作ります。
で、以上の問題を解決すると、以下のコードです。
let axios = require("axios") let fs = require("fs") let csvStr = require("csv-stringify/lib/sync") let PAGE_MAX = 55 function parse_response(response, record_num, outcsv) { for (record_idx = 0; record_idx < record_num; record_idx++) { let record = [] id = response["data"][record_idx]["id"] comments_count = response["data"][record_idx]["comments_count"] likes_count = response["data"][record_idx]["likes_count"] reactions_count = response["data"][record_idx]["reactions_count"] tags = response["data"][record_idx]["tags"] tags_str = "" for(tag_num = 0; tag_num < tags.length; tag_num++){ tags_str = tags_str.concat(', ', tags[tag_num]["name"]) } title = response["data"][record_idx]["title"] user_id = response["data"][record_idx]["user"]["id"] created_at = response["data"][record_idx]["created_at"] record.push(id, comments_count, likes_count, reactions_count, tags_str, title, user_id, created_at) outcsv.push(record) } return outcsv } async function getArticle(tag) { let outcsv = [] let columns = ["id", "comments_count", "likes_count", "reactions_count", "tags", "title", "user_id", "created_at"] outcsv.push(columns) let last_date = "2020-09-28" let record_sum = 0 for (page = 1; page <= PAGE_MAX; page++){ let response = await axios.get("https://qiita.com/api/v2/items?page=" + page + "&per_page=100&query=" + encodeURIComponent("stocks:>30") + "+" + encodeURIComponent("tag:" + tag)) record_num = response["data"].length if(record_num != 0){ outcsv = parse_response(response, record_num, outcsv) record_sum = record_sum + record_num last_date = response["data"][record_num-1]["created_at"].split('T')[0] console.log(record_sum + ":" + last_date) } if(record_num < 100){ break } } for (page = 1; page <= PAGE_MAX; page++) { let response = await axios.get("https://qiita.com/api/v2/items?page=" + page + "&per_page=100&query=" + encodeURIComponent("stocks:>30") + "+" + encodeURIComponent("tag:" + tag) + "+" + encodeURIComponent("created:<" + last_date)) record_num = response["data"].length if(record_num != 0){ outcsv = parse_response(response, record_num, outcsv) record_sum = record_sum + record_num console.log(record_sum) } if(record_num < 100){ break } } fs.writeFileSync(tag + ".csv", csvStr(outcsv)) } // それぞれのタグを変更する:C, C++, C#, Go, Java, Javascript, Kotlin, Objective-C, // PHP, R, Ruby, Swift, Typescript getArticle("python")2.2. アウトプットデータ
結果のデータは各投稿のID,コメント数、LGTM数、絵文字リアクション数、タグ、タイトル、筆者のID、投稿の日時です。
言語 データ投稿数 全部投稿数 Python 5337 46000 Javascript 5305 37257 Ruby 2927 29307 PHP 2010 21088 Java 1568 15905 Swift 2577 15317 C# 815 10319 Go 1021 9084 C++ 630 8006 Typescript 701 5891 Kotlin 287 4100 Objective-C 935 4010 R 204 3526 C 261 3305 3. トレンド
以上の結果のデータをTableauに入れてグラフを作成しましょう!
3.1. 全体グラフ
横軸は投稿の作成日です(単位:年間)。
縦軸は各言語のタグの投稿数の割合です(単位:パーセント)。3.2. 結果を分析しよう
3.2.1. Python
投稿数に基づと、2012年のPython言語ユーザーの割合は4%でした。その後、急速な成長により、この率は2014年にPHP、2015年にRubyとSwift、2016年にJavascriptを上回りました。現在、Pythonは一番人気がある言語(35%)になりました。3.2.2. Javascript
2012年、JSの投稿数の割合は22%でしたが、2014年までに徐々に18%減少しました。現在、その割合は26%になり、人気がある言語トップで2位にランクされています。3.2.3. Ruby
2012年、Rubyの投稿数の割合は35%に達し1位になりました。その後、何年にも渡って継続的に減少しています。現在、4%になりました。3.2.4. Typescript
2013年から始まり、近頃の3年間の高い成長により、現在、3位(10%)になりました。3.2.5. Swift/Objective-C
Swiftの発達とともに、Objective-Cは衰退していました(0.24%)。全体として、iOSの言語は2015年,2016年に人気があり、最近、少し減少しました。3.2.6. 他の言語
4. よく投稿した時間、曜日
よく投稿した時間は0時です。特には22時から24時までですよね。ご参考までに!
参考
- 投稿日:2020-09-28T22:26:38+09:00
最短工程でubuntuにnodenvをインストールする
$ uname -a Linux geo-functions 5.4.0-1021-gcp #21-Ubuntu SMP Fri Jul 10 06:53:47 UTC 2020 x86_64 x86_64 x86_64 GNU/Linuxgit clone https://github.com/nodenv/nodenv.git ~/.nodenv git clone git://github.com/nodenv/node-build.git ~/.nodenv/plugins/node-build echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bash_profile echo 'eval "$(nodenv init -)"' >> ~/.bash_profile source ~/.bash_profile nodenv install 12.18.2以上!!!!!
- 投稿日:2020-09-28T20:23:31+09:00
Qiita初心者がまずタグ付けしたらよい言葉とは?
目的
どのタグ(ほぼ言語)が最初にフォローされやすいのかを確認する。
先に登録するものが、Qiitaを使いたいというきっかけになるものなので、
本当に興味があることではないかという推測に基づく。方法
あるグループのユーザリストを取得(プログラム外)し、
APIを使って新しい順にユーザ別にタグを取得。
認証情報がないとAPIをたたける数が少ない。
新しいものの番号を古い順に変換し、登録5つ目までのタグをランキング化(プログラム外)コード
get_qiita_data_tags.js//package require const axios = require("axios"); const fs = require("fs"); const csvStr = require("csv-stringify/lib/sync"); const csvParse = require('csv-parse/lib/sync'); //認証情報の取得 外から渡してる const token = process.argv[2]; //QiitaAPIでデータ取得・csvに出力 async function getArticle(query) { //ユーザリストを取得 const file = './data/input_user_list.csv'; let inputData = fs.readFileSync(file); let user_list = csvParse(inputData); console.log(user_list); //csvに変換する用list let outcsv = []; //csvのヘッダー設定 let columns = ["ユーザ番号","ユーザID","タグ登録順","タグ"]; outcsv.push(columns); //あるリストに存在するユーザ数 var user_num = user_list.length; //検索用パラメータ var PAGE_MAX = 4; var PER_PAGE = 100; for (i = 0;i<user_num;i++){ //あるユーザがフォローをしているタグを取得 var user_id = user_list[i][0]; console.log(user_id); for (page = 1;page<=PAGE_MAX;page++){ //URLを作っておく var url = "https://qiita.com/api/v2/users/"+user_id+"/following_tags?page=" + page + "&per_page=" + PER_PAGE; let response //リクエストが失敗した時の処理 response = await axios.get(url , { headers: { Authorization: `Bearer ${token}`, } } ).catch(err => { return err.response }); if (response.status != 200) { console.log("たぶんAPIエラー") } var tags_num = response.data.length; var sort_num_desc; console.log(tags_num); for (j =0 ; j<tags_num ; j++) { //一レコードの情報格納list let record = []; //欲しい要素 var tag_id = response.data[j].id; sort_num_desc = (page - 1) * PER_PAGE + (j+1); console.log("row: " + sort_num_desc +",user_n: " + i + ",user_id: " + user_id + ",tag_id: " + tag_id); //listに格納 record.push(i,user_id,sort_num_desc,tag_id); outcsv.push(record); } } } // csvとして出力 fs.writeFileSync("./data/following_tag_list.csv", csvStr(outcsv)); } //情報の取得 var query = "node.js"; getArticle(query);結果
そもそもあるグループの前提になっている授業に含まれている言語が多い。
フロント側の技術や、必須になるソースの管理に関しても見る人が多い。
あんまりユーザ数が約40人のグループにしては必須の技術のタグ付け数が少ない。
タグ付けしないのだろうか。
順位 タグ ユーザ数 1 JavaScript 16 2 Node.js 12 3 Python 8 4 GitHub 6 5 Android 5 6 Chrome 4 7 CSS 4 8 HTML 4 9 HTML5 4 10 Git 3
- 投稿日:2020-09-28T19:44:07+09:00
QiitaAPIで2020年投稿のストック数が多い記事のタグ情報を調べてみた
はじめに
ほとんどプログラミング経験のない初心者(VB.Netを少しかじった程度)ですが、最近「モノをつくる」学習の場を与えて貰えたので、自分なりに色々調べて、JavaScriptにチャレンジしてみました。記事の投稿も初で、初めて尽くしで戸惑ってます。。ソースコードがキレイじゃないのもご愛敬。
目的
Node.jsとQiitaAPI(+axios)を使って今の自分に何ができるか考えてみた。
で、今回は、「今年(2020年)に投稿された記事で、ストック数の多い記事のタグってどんなの?」を調べてみました。
(ストック数が多い記事として、今回はストック数が300以上の記事を対象としてみた。)コード
QiitaAPI.js// axiosモジュールの読み込み const axios = require('axios'); axios.defaults.baseURL = 'https://qiita.com/api/v2'; let array1 = []; // タグ名用 let array2 = []; // カウント用 // main関数 async function main(page) { // QiitaAPIからレスポンス受け取り(ストック>=300 + 作成日>=2020-01 の記事一覧) let response = await axios.get( '/items?page=' + page + '&per_page=100&query=stocks:' + encodeURIComponent('>=300') + '+created:'+ encodeURIComponent('>=2020-01') ); // 取得した記事から[tags]要素を取得 for (let i=0; i<response.data.length; i++) { for (let j=0; j<response.data[i].tags.length; j++) { var tagname = response.data[i].tags[j].name; // タグ名取得 // 配列に登録があれば、カウントを+。登録が無いなら配列に追加。 var result = array1.indexOf( tagname ); // 検索(既に配列にあるか) if (result < 0) { array1.push(tagname); array2.push(1); } else { array2[result] += 1; } } } // 結果出力 if (page==5) { for (let i=0; i<array1.length; i++) { console.log(array1[i]+', '+array2[i]); } } } // main関数 実行 let promise = Promise.resolve(); for (let k=1; k<6; k++) { promise = promise.then(() => main(k)); }色々ハマった点
1.QiitaAPIって何がとれるのさ?
QiitaAPIの仕様書を読み込んでみた。(色々取れそうだけど、技術力が追い付かないので断念。)
最初におや?と思ったのは、1回のレスポンスで最大100件、ページ指定も最大100ページ。つまり、1万件まで。
100件以上を取ろうと思うとページを何度か呼び出すしかないのかな。ってことで、main関数をループすることにした。let response = await axios.get( '/items?page=' + page + '&per_page=100&query=stocks:' + encodeURIComponent('>=300') + '+created:'+ encodeURIComponent('>=2020-01') );2.非同期処理ってなんぞや?
ループしようと思ったはいいけど、上手くいかない。
どうも非同期処理で走るからって理由らしい。正直良く分かってないが、以下サイトを参考に改善してみた。
PromiseによるJavaScript非同期処理レシピ集let promise = Promise.resolve(); for (let k=1; k<6; k++) { promise = promise.then(() => main(k)); }結果
結果は、EXCELで集計。
(あまり細かくなってもアレなので、表は上位10件、グラフは記事数7件以上を表示。)
※2020年に投稿された記事で、ストック数が300以上の記事についてタグを集計
タグ 記事数 JavaScript 67 Python 58 初心者 49 機械学習 24 新人プログラマ応援 24 HTML 16 DeepLearning 16 GitHub 16 Vue.js 16 React 16 考察
Qiitaトップのタグ・ランキングとはまた違った結果になった。
上位の「JavaScript」「Python」は、今年も熱い。ストック数も多いということは、興味関心が高く、役立つ情報がまだまだあるってことかなぁ。興味深いのは、タグ・ランキングでは上位の「Ruby」がランク外。
最近の投稿が少ないか、ストック数が伸びないのか、、、??最後に
JavaScriptの学習も兼ねて今回はチャレンジしてみましたが、もっとより良いアプローチがある気がしました。
そもそも、私の技術力の無さを痛感。(書いてませんが色々断念してます。。。)
まずは書く!調べる!やってみる!
トライアンドエラーで今後も素人ながら挑戦していきます。
- 投稿日:2020-09-28T19:44:07+09:00
QiitaAPIで20年投稿のストック数が多い記事のタグ情報を調べてみた
はじめに
ほとんどプログラミング経験のない初心者(VB.Netを少しかじった程度)ですが、最近「モノをつくる」学習の場を与えて貰えたので、自分なりに色々調べて、JavaScriptにチャレンジしてみました。記事の投稿も初で、初めて尽くしで戸惑ってます。。
ソースコードがキレイじゃないのもご愛敬。目的
Node.jsとQiitaAPI(+axios)を使って今の自分に何ができるか考えてみた。
で、今回は、「今年(2020年)に投稿された記事で、ストック数の多い記事のタグってどんなの?」を調べてみました。
(ストック数が多い記事として、今回はストック数が300以上の記事を対象としてみた。)コード
QiitaAPI.js// axiosモジュールの読み込み const axios = require('axios'); axios.defaults.baseURL = 'https://qiita.com/api/v2'; let array1 = []; // タグ名用 let array2 = []; // カウント用 // main関数 async function main(page) { // QiitaAPIからレスポンス受け取り(ストック>=300 + 作成日>=2020-01 の記事一覧) let response = await axios.get( '/items?page=' + page + '&per_page=100&query=stocks:' + encodeURIComponent('>=300') + '+created:'+ encodeURIComponent('>=2020-01') ); // 取得した記事から[tags]要素を取得 for (let i=0; i<response.data.length; i++) { for (let j=0; j<response.data[i].tags.length; j++) { var tagname = response.data[i].tags[j].name; // タグ名取得 // 配列に登録があれば、カウントを+。登録が無いなら配列に追加。 var result = array1.indexOf( tagname ); // 検索(既に配列にあるか) if (result < 0) { array1.push(tagname); array2.push(1); } else { array2[result] += 1; } } } // 結果出力 if (page==5) { for (let i=0; i<array1.length; i++) { console.log(array1[i]+', '+array2[i]); } } } // main関数 実行 let promise = Promise.resolve(); for (let k=1; k<6; k++) { promise = promise.then(() => main(k)); }色々ハマった点
1.QiitaAPIって何がとれるのさ?
QiitaAPIの仕様書を読み込んでみた。(色々取れそうだけど、技術力が追い付かないので断念。)
最初におや?と思ったのは、1回のレスポンスで最大100件、ページ指定も最大100ページ。つまり、1万件まで。
100件以上を取ろうと思うとページを何度か呼び出すしかないのかな。ってことで、main関数をループすることにした。let response = await axios.get( '/items?page=' + page + '&per_page=100&query=stocks:' + encodeURIComponent('>=300') + '+created:'+ encodeURIComponent('>=2020-01') );2.非同期処理ってなんぞや?
ループしようと思ったはいいけど、上手くいかない。
どうも非同期処理で走るからって理由らしい。正直良く分かってないが、以下サイトを参考に改善してみた。
PromiseによるJavaScript非同期処理レシピ集let promise = Promise.resolve(); for (let k=1; k<6; k++) { promise = promise.then(() => main(k)); }結果
結果は、EXCELで集計。
(あまり細かくなってもアレなので、表は上位10件、グラフは記事数7件以上を表示。)
※2020年に投稿された記事で、ストック数が300以上の記事についてタグを集計
タグ 記事数 JavaScript 67 Python 58 初心者 49 機械学習 24 新人プログラマ応援 24 HTML 16 DeepLearning 16 GitHub 16 Vue.js 16 React 16 考察
Qiitaトップのタグ・ランキングとはまた違った結果になった。
上位の「JavaScript」「Python」は、今年も熱い。ストック数も多いということは、興味関心が高く、役立つ情報がまだまだあるってことかなぁ。興味深いのは、タグ・ランキングでは上位の「Ruby」がランク外。
最近の投稿が少ないか、ストック数が伸びないのか、、、??最後に
JavaScriptの学習も兼ねて今回はチャレンジしてみましたが、もっとより良いアプローチがある気がしました。
そもそも、私の技術力の無さを痛感。(書いてませんが色々断念してます。。。)
まずは書く!調べる!やってみる!
トライアンドエラーで今後も素人ながら挑戦していきます。
- 投稿日:2020-09-28T18:57:23+09:00
Qiitaって誰向けなの?レベル別投稿を可視化してみた
はじめに
とある理由からQiitaを利用することとなったが、正直Qiitaの読み方も知らない(たぶんキータ)、どんな人がどういった目的で利用するツールなのか正直何だかわかってない、アカウント作りたてほやほやの初心者が投稿する。
目的
Qiitaのレベル別利用者の分布を可視化し、どのような人にとって便利なツールであるかを考察する。
コード
初級者、中級者、上級者タグをフォローしている人数を取得する
const axios = require("axios"); async function main() { let response = await axios.get( "https://qiita.com/api/v2/tags/%E5%88%9D%E5%BF%83%E8%80%85" ); console.log(response.data.followers_count); } main();実行結果
初級者:4476件
中級者:0件
上級者:0件考察
中級者、上級者タグが存在していなかったため、タグに紐づくフォロー人数では利用者属性を推察することができなかった。
一方で、「初級」と検索した際の記事投稿数は1,836件、「中級」と検索した際の記事投稿数は1,572件、「上級」と検索した際の記事投稿数は1,404件だったたため、検索結果からすると幅広いレベルの利用者向けの技術サイトであると推測できる。最後に
・本来は「投稿記事のタイトルや本文から、記事をレベル分けした分布」や「投稿者のプロフィールをもとにした利用者レベル分析」などといったことも考察してみたかったが、抽出方法がわからず、諦めました..
・Qiitaは、初級者である私にも便利なツールであることがわかった。※実際この記事を投稿するためにいくつかの記事を参考にした。
今後も記事投稿を続ける予定のため、継続して利用しようと思う。
- 投稿日:2020-09-28T18:33:09+09:00
Node.jsでDurable Functionsを使うなら、入出力はObjectにした方が安心かもという話
注: 本記事は2020/09/28時点のものであり
https://www.npmjs.com/package/durable-functions の v1.4.3
https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.DurableTask/ のv2.2.2
https://github.com/Azure/azure-functions-nodejs-worker のv1.2.2
で検証及び再現した事象です。今後の変更及び修正により挙動が変わる場合があるため、バージョンが変わっていた場合各プロダクトのIssueなどを参照してください。
結論だけ知りたい人向け
- アクティビティ関数の入出力バインディングに文字列やbooleanなどの値を渡すと意図しない変換が行われる
- オブジェクトでラップして渡せば基本的には問題ない
とりあえずこれを守っておけばハマることは少ないと思います。
起こる原因
Durable Functionsでは入出力バインディングのデータをJSON文字列としてやり取りしています(ByteArrayを除く)。
ref: Durable Functions のバインド - Azure | Microsoft Docs関数でバインディングが行われた入力を参照する時は呼び出し元の入力が
JSON.stringify
されて、更にJSON.parse
された値が渡ってくる認識して良いでしょう(厳密には異なります)。Durable Functionsなどの実行環境とも言えるAzure Functions NodeJS Worker では渡ってきた値に全てに対して
JSON.parse
を適用します(ByteArrayを除く)。
ref: https://github.com/Azure/azure-functions-nodejs-worker/blob/11303c0dcf2ddcbf876e0cc453dbe3a731b769d9/src/Context.ts#L22………とここまで内部でだいたいどんな事が行われているか記載しましたが、正直自分も処理を追いきれておらず、各文章の末尾に「っぽいことが行われています」が付きそうな具合です。
どういうこととお思いかと思うので、実際の例を挙げてみます。不思議な挙動のバインディング
ここでは公式サンプルのSayHelloを例にとってみます。
https://github.com/Azure/azure-functions-durable-js/blob/32f442cd5fc3fe5d79451bc3daf91d5540f21d1d/samples/E1_HelloSequence/index.js
https://github.com/Azure/azure-functions-durable-js/blob/32f442cd5fc3fe5d79451bc3daf91d5540f21d1d/samples/E1_SayHello/index.jsSayHelloを一部改変して
module.exports = function (context) { context.log(typeof context.bindings.name); context.log(context.bindings.name); return context.bindings.name; };みたいにして、HelloSequenceからSayHelloに渡す値を変えてみましょう。
まず手始めに正常系で
yield context.df.callActivity("E1_SayHello", "Tokyo")を実行してみます。
ログにはstring Tokyoと表示されたと思います。
問題ないですね。では次に数字を渡してみましょう。
yield context.df.callActivity("E1_SayHello", 123)を実行してみます。
ログにはnumber 123これも想定通りですね。
では数字を文字列型として渡してみます。
yield context.df.callActivity("E1_SayHello", '123')を実行してみるとログには
number 123
私は文字列を渡したはずだが…?となりますが、これが上記で述べた渡ってきた値に全てに対して
JSON.parse
を適用します(ByteArrayを除く)。の弊害となります。
これが大きな問題となるのが、Number型で表現できる最大最小の値を超えた時などです。
TwitterなどのAPIではidの値が非常に大きな値となっていて、JavaScriptのNumber型では表現の出来ない値になっています。
そのためid_str
というidをstr型で表現した値も一緒に送信しているのですが、それをDurable Functionsで扱おうとすると上記の挙動でnumber型にparseされ、表現できない範囲の値になり正常な値が扱えなくなるというものです。他にも不思議な挙動はいくつかあり
yield context.df.callActivity("E1_SayHello", 'null')を実行してみるログには
object null文字列を渡しているのに
null
が渡ってきたり(同様に'{}'
を渡すと{}
が返ってきたり)yield context.df.callActivity("E1_SayHello", true)を実行してみるログには
string Trueと、
JSON.stringify(true)
したらtrue
が返ってきているはずでは?と言った挙動が見られます。このことから
JSON.stringify
っぽいことをしているけれども、そうじゃない別の何かが行われているということがわかります。(このあたりで一旦grpcでゴニョゴニョする時に何かが怒ってるのかなぁとあたりは付けているのですが、調査しきれていません)
対処法
扱う対象の値によっていくつか選択肢はありますが、入力値に文字列や数値、bool値、null値をそのまま使うのではなく、
オブジェクトの値として渡す
これで解決します。
例えば
yield context.df.callActivity("E1_SayHello", { idStr: '123' })としてSayHello関数に値を渡して、SayHello関数では
module.exports = function (context) { ... context.log(context.bindings.name.idStr); ... };として受け取ると言った具合です。
オブジェクトはJSON.stringify
っぽいことをされるのではなく、JSON.stringifyが行われるため、数値は数値のまま、文字列は文字列のまま渡されるようになります。かなりエッジケースな話ではありますが、ユーザからの入力値で文字列が来ると思っていたのに何か違うものが渡ってきているぞ、というバグにお悩みの方は上記の方法で対処してみることも考えてみてください。
なお上記現象については
https://github.com/Azure/azure-functions-durable-js/issues/215
にてFB済みです。
今後の対応を期待しましょう。
ByteArrayでやり取りするのも一つの手かもしれませんね
- 投稿日:2020-09-28T13:53:50+09:00
6. Koa2でWebページを作成しよう ~validatorで入力値を検証~
記事一覧
- 第一回 Koa2の環境を構築する
- 第二回 Hello World!!を表示する
- 第三回 koa-routerでGETパラメーターを取得する
- 第四回 koa-bodyでPOSTパラメーターを取得する
- 第五回 アカウントを登録してログインを検証する
概要
今回は、入力値のバリデーションの実装を行います。
また、前回の続きとして進めて行くので、上手くいかない場合は以前の記事をご覧ください。バリデーション
バリデーションは値のフォーマットが正しい形であるかの確認をする処理で、今回は入力必須や文字数制限、メールアドレス形式であるかなどの観点で値の検証を行っていきます。
一例として、ユーザーから値を受け取って処理を行うプログラムでは、ユーザーが必ずしも正しいフォーマットでデータを入力するとは限らないため、フォーマットの確認が必要になります。*バリデーションにはこちらのパッケージを利用します。
https://github.com/skaterdav85/validatorjs以下のコマンドをコンソールで実行してください。
npm install validatorjs
DBの作成
phpMyAdminで以下のSQLを実行してください。
別のツールでDBを操作できる場合は、そちらでも問題ありません。create table koa2_test.validate_user ( user_id int auto_increment primary key, user_name varchar(20) not null, password varchar(60) not null, mail varchar(255) not null );DBの各列で指定されている条件をまとめると、以下のようになります。
- user_id: 主キー制約。自動採番。
- user_name: 最大20文字。NOTNULL。
- password: 最大60文字。NOTNULL。
- mail: 最大255文字。NOTNULL。
また、以下の条件も考慮します。
- user_idは自動採番で、バリデーションの対象とする必要はない
- mailはメールアドレスの形式である事も検証する
- パスワードはハッシュ化前文字列の最大を128文字とする。
よって、必要となる検証項目は以下のようになります。
- user_name: 20文字以下であること。値が空でない事。
- password: 128文字以下であること。値が空でない事。
- mail: 255文字以下であること。値が空でない事。メールアドレスの形式である事。
validatorで入力値の検証
/router/validator-signup.js
を作成し、以下の記述を追加してください。validator-signup.jsconst Router = require('koa-router'); const router = new Router(); const connection = require('../app/db'); const bcrypt = require('bcrypt'); const validator = require('validatorjs'); router.get('/validator-signup', async (ctx) => { await ctx.render('validator-signup'); }); router.post('/validator-signup', async (ctx) => { // POSTパラメータを取得 let userName = ctx.request.body['name']; let password = ctx.request.body['password']; let mail = ctx.request.body['mail']; // 入力値の検証 let validationResult = await validation([userName, password, mail]); // 入力値拒否時、登録処理を行わずにエラーメッセージを表示する if (validationResult.result === false) { await ctx.render('validator-signup', validationResult); } else { // パスワードをハッシュ化 const salt = bcrypt.genSaltSync(); const hashPassword = bcrypt.hashSync(password, salt); // DBにアカウント情報を登録 let signupSQL = 'INSERT INTO validate_user(user_name, password, mail) VALUES (?, ?, ?)'; await connection.query(signupSQL, [userName, hashPassword, mail]); await ctx.render('validator-signup'); } }); module.exports = router;大部分は前回の記事で作成したアカウント登録処理と同じものですが、入力値の検証を行う処理を追加しています。
次に、入力値の検証を行うためのvalidation関数を作成します。validation関数の作成
/router/validator-signup.js
の最後に以下の記述を追加してください。validator-signup.jsasync function validation(data) { // ユーザーの入力値をvalidatorjsへの入力形式である連想配列に変換。 // ここで指定しているkey値がrulesやerrorMessageの条件部分でも利用される。 let requests = { userName: data[0], password: data[1], mail: data[2], } // それぞれの値に対して掛ける制約を指定している。 // required: 文字列が0文字でないことを確認 // max: 最大文字数以下であることを確認 // email: メールの形式であることを確認 let rules = { userName: 'required|max:20', password: 'required|max:128', mail: 'required|max:255|email', }; // '条件.検証項目'の形で、検証項目が条件を満たしていない場合の // エラーメッセージを指定している。 // ここで指定していないエラーについては、 // デフォルトで設定されている英語のメッセージが返される。 let errorMessage = { 'required.userName': 'USER NAMEは必須項目です', 'required.password': 'PASSWORDは必須項目です', 'required.mail': 'MAILは必須項目です', 'max.userName': 'USER NAMEは20文字以下で入力して下さい', 'max.password': 'PASSWORDは128文字以下で入力して下さい', 'max.mail': 'MAILは255文字以下で入力して下さい', 'email.mail': 'MAILはメールアドレスの形式で入力して下さい' } // validator(検証する値, 検証する条件, 拒否時のメッセージ) の形で、 // validatorのインスタンスを生成。 let signupValidator = new validator(requests, rules, errorMessage); let result = { error: {}, result: false }; // 値の検証を実行して、成功時とエラー時で処理を振り分ける。 // checkAsync() の第一引数は成功時の関数を、第二引数は失敗時の関数を指定する。 await signupValidator.checkAsync(() => { // Success時の処理 result.result = true; }, () => { // Error時の処理 result.result = false; // errors.first(‘userName’)では、userNameにエラーが発生している場合、 // 発生したエラーに応じて、validatorインスタンス生成時に // 第三引数で指定したメッセージを取得している。 // 取得した値はresult変数に格納して、関数の最後でreturnしている。 if (signupValidator.errors.first('userName')) { result.error.medicine_name = signupValidator.errors.first('userName'); } if (signupValidator.errors.first('password')) { result.error.hospital_name = signupValidator.errors.first('password'); } if (signupValidator.errors.first('mail')) { result.error.number = signupValidator.errors.first('mail'); } }) return result; }*上記以外の制約についてはこちらのページを参照。
https://github.com/skaterdav85/validatorjs入力フォームを作成
/view/validator-signup.ejs
を作成し、以下の記述を追加してください。validator-signup.ejs<h1>アカウント登録</h1> <% if(typeof error !== 'undefined'){ %> <% for(let key in error) { %> <p style="color: red"><%= error[key] %></p> <% } %> <% } %> <form action="validator-signup" method="post"> USER NAME <input type="text" name="name"><br> PASSWORD <input type="password" name="password"><br> MAIL <input type="email" name="mail"><br> <input type="submit" value="登録"><br> </form>ルーティングを設定
/index.js
に以下の記述を追加してください。index.jsconst validatorSignupRouter = require('./router/validator-signup'); app.use(validatorSignupRouter.routes()); app.use(validatorSignupRouter.allowedMethods());validationの表示結果
最後に
以上でvalidatorjsを利用した入力値の検証は完成です。
次回はセッションを用いたログイン状態の管理を実装してきます。
- 投稿日:2020-09-28T13:44:14+09:00
teratermで2048を遊ぶ
teratermなどのターミナルソフトを使うときは限られています。ネットワークなりサーバーなりいじる人ぐらいしか使いません。
暇なときはあります。ビルド、デプロイ、コンフィグリストア、例を挙げればでるわでるわ。そういう時はターミナルソフトで遊びたくなります。そんなわけで2048をsshで飛ばせるようにしました。
使い方
ローカルで動かす場合
git clone https://github.com/misogihagi/node-ssh-2048.git npm i node 2048そして
localhostにでてきたポートにsshしてくださいデモ
おまけ
実は類似のサービスがあります。
play@ascii.town他にもターミナルで遊べそうなものがあれば教えてください
おまけのおまけ
2048RTA
https://github.com/misogihagi/node-ssh-2048/blob/master/rta.gif?raw=true
- 投稿日:2020-09-28T13:43:12+09:00
npm-scriptsをnpm-run-allで用途や環境毎に書く際の小技
- Web開発の際のタスク処理として、
package.json
内に記述して利用できるnpm-scriptsを利用しています。- またその際に、複数処理の直列化や並列化に便利なnpm-run-allを利用しています。
- 今回はそれを用いた用途や環境毎への記述を、明確かつ柔軟にする方法を記録します。
結果
- 先に結果の記述を示します。以下の通りです。
{ "scripts": { "build" : "run-s build:{sass,ts}", "build:sass" : "sass input.scss output.css", "build:ts" : "tsc main.ts", "watch" : "run-s watch:{sass,ts}", "watch:sass" : "sass --watch input.scss output.css", "watch:ts" : "tsc -w main.ts" } }内容
- 上記の結果例は、主にbuildとwatchを行うタスクです。
- こちらで利用している方法は、処理内容を波括弧で指定するということです。
波括弧での指定
- コロン(:)で用途毎に分けたタスクを一回で実行する際には、以下のようなやり方があります。
{ "scripts": { "build-basic" : "run-s build:sass build:ts", "build-glob" : "run-s build:*", "build-brace" : "run-s build:{sass,ts}", "build:sass" : "sass input.scss output.css", "build:ts" : "tsc main.ts", } }
- これらから私はbuild-braceのような波括弧で指定した方法を利用しています。
- そのメリットは以下の通りです。
- 非重複
run-s build:sass build:ts
のように言葉の重複が無く、今後同一のタスクが増えても、用途の記述だけで済む。- 明示化
run-s build:*
のようなアスタリスク指定と比べて、明示的に記述されているため、処理の内容が理解しやすい。- 順序化
- 波括弧内の順に処理を行ってくれるため、仮に
package.json
内でbuild:sass
とbuild:ts
を逆に記述しても問題無い。
run-s build:*
での指定では、処理順番がpackage.json
内の並び通りに行われるため、逆に記載してしまうとその順になってしまう。まとめ
- 上記のことから、タスクやscript記述においても可読性・明示化・非重複性を再認識しました。
参考
- npm-run-all
- https://www.mitsue.co.jp/knowledge/blog/frontend/202004/14_1408.html
package.json
のスクリプト例に使わせていただきました。
- 投稿日:2020-09-28T12:28:30+09:00
5. Koa2でWebページを作成しよう 〜アカウント登録〜
記事一覧
概要
今回は、アカウント登録、ログイン検証の実装を行います。
当記事ではログインは検証に留め、ログイン状態の保持は後の記事で進めていく予定です。
また、前回の続きとして進めて行くので、上手くいかない場合は以前の記事をご覧ください。アカウントの登録やログインに必要なデータベースはMariaDB(MySQL)を使用します。
インストール
こちらのページからXAMPPをダウンロードし、インストールしてください。
https://www.apachefriends.org/jp/download.htmlインストールが完了したら、MySQL DatabaseとApache Web Serverを起動してください。
ブラウザから以下のURLにアクセスすることで、MariaDBに接続・操作できます。
http://localhost/phpmyadmin/index.phpエラーが発生した場合
preg_match_all(): Allocation of JIT memory failed, PCRE JIT will be disabled. This is likely caused by security restrictions. Either grant PHP permission to allocate executable memory, or set pcre.jit=0
というエラーが表示された場合は、以下の手順を試してください。
XAMPPをインストールしたディレクトリから、/xamppfiles/etc/php.iniを開く。
[Pcre]の文字列で検索して、その次の行に以下の記述を追加。
pcre.jit=0
データベースの作成
今回使用するデータベースを作成します
以下の記述をphpMyAdminのSQLタブのテキストボックスに追加して実行してください。CREATE DATABASE koa2_test; USE koa2_test; CREATE TABLE koa2_test.user ( user_id INT AUTO_INCREMENT PRIMARY KEY, user_name VARCHAR(20) NOT NULL, password VARCHAR(255) NOT NULL );画面左のリストからkoa2_testデータベースをクリックすると、作成したテーブルが確認できます。
Koaでデータベースに接続する処理を記載します。
接続するにあたって、こちらのパッケージを利用します。
https://github.com/sidorares/node-mysql2以下のコマンドをコンソールで実行してください。
npm install mysql2
DB接続にはコネクションプールを作成し、これをすべてのデータベースへのクエリで利用します。
/app/db.js
を作成し、以下の記述を追加してください。db.jsconst mysql = require('mysql2/promise'); const config = require('../config.json'); const connection = mysql.createPool(config.db); module.exports = connection;DB接続を行う外部ファイルでは、当ファイルをrequireで読み込むことでコネクションプールを利用します。
また、設定情報を保存するためのファイルを作成します。
/config.json
を作成し、以下の記述を追加してください。config.json{ "db": { "host": "localhost", "port": 3306, "user": "root", "password": "", "database": "koa2_test" } }設定ファイルを別に用意することで、設定情報を一元管理できたり、Gitにパスワード等の情報が保存されてしまう事を防ぐことができるので、このようにすると良いようです。
アカウント登録
/view/signup.ejs
を作成し、以下の記述を追加してください。signup.ejs<h1>アカウント登録</h1> <form action="signup" method="post"> USER NAME <input type="text" name="name"><br> PASSWORD <input type="password" name="password"><br> <input type="submit" value="登録"><br> </form>
/router/signup.js
を作成し、以下の記述を追加してください。signup.jsconst Router = require('koa-router'); const router = new Router(); const connection = require('../app/db'); router.get('/signup', async (ctx) => { await ctx.render('signup'); }); router.post('/signup', async (ctx) => { // POSTパラメータを取得 let userName = ctx.request.body['name']; let password = ctx.request.body['password']; // DBにアカウント情報を登録 // SQL文中の ? はプレースホルダーというもので、ここに後から値を割り当て(バインド)します。 // ここでは、insert文のuser_nameとpasswordに当たる箇所を ? としており、 // query()関数の第二引数として配列の形で、バインド値を渡しています。 let signupSQL = 'INSERT INTO user (user_name, password) VALUES (?, ?)'; await connection.query(signupSQL, [userName, password]); await ctx.render('signup'); }); module.exports = router;
/index.js
に以下の記述を追加してください。index.jsconst signupRouter = require('./router/signup'); app.use(signupRouter.routes()); app.use(signupRouter.allowedMethods());動作を確認
ブラウザで http://localhost:5000/signup にアクセスすると、アカウント登録ページが表示されます。
http://localhost/phpmyadmin/ で情報がDBに登録されていることが確認できます。パスワードのハッシュ化
現在の状態ではパスワードが平文で登録されてしまっているので、ハッシュ化して登録するように変更します。
パスワードのハッシュ化にはこちらのパッケージを利用します。
https://github.com/kelektiv/node.bcrypt.js以下のコマンドをコンソールで実行してください。
npm install bcrypt
/router/signup.js
を以下のように修正してください。signup.jsconst Router = require('koa-router'); const router = new Router(); const connection = require('../app/db'); const bcrypt = require('bcrypt'); router.get('/signup', async (ctx) => { await ctx.render('signup'); }); router.post('/signup', async (ctx) => { // POSTパラメータを取得 let userName = ctx.request.body['name']; let password = ctx.request.body['password']; //パスワードをハッシュ化 const salt = bcrypt.genSaltSync(); const hashPassword = bcrypt.hashSync(password, salt); // DBにアカウント情報を登録 let signupSQL = 'INSERT INTO user VALUES (0, ?, ?)'; await connection.query(signupSQL, [userName, hashPassword]); await ctx.render('signup'); }); module.exports = router;この状態で再びアカウント登録を実行すると、以下のような文字列がパスワードとして保存されます。
「$2b$10$pO2kkkS0FWpKMgIoCg5QbuQilL7E3nbWF6im47pvSx8…」以上で新規登録は完了です。
ログイン
/view/login.ejs
を作成し、以下の記述を追加してください。login.ejs<h1>ログイン</h1> <form action="login" method="post"> USER NAME <input type="text" name="name" placeholder="userと入力"><br> PASSWORD <input type="password" name="password" placeholder="userと入力"><br> <input type="submit" value="ログイン"><br> </form> <a href="signup">アカウント登録</a> <!-- この部分でログイン結果を表示するようにjs側で処理を書いていきます。 --> <% if(typeof loginResult !== "undefined"){ %> <br> <%= loginResult %> <% } %>
/router/login.js
を作成し、以下の記述を追加してください。login.jsconst Router = require('koa-router'); const router = new Router(); const connection = require('../app/db'); const bcrypt = require('bcrypt') router.get('/login', async (ctx) => { await ctx.render('login'); }); router.post('/login', async (ctx) => { // POSTパラメータを取得 let userName = ctx.request.body['name']; let password = ctx.request.body['password']; // DBからuserNameが一致するデータを検索 let loginSQL = 'SELECT user_id, password FROM user WHERE user_name = ?'; let [queryResult] = await connection.query(loginSQL, [userName]); // 入力されたパスワードとDBに保存されているハッシュ化されたパスワードを比較 let loginResult = 'ログイン失敗'; for (let row of queryResult) { if (bcrypt.compareSync(password, row['password'])) { loginResult = 'ログイン成功'; } } await ctx.render('login', {loginResult: loginResult}); }); module.exports = router;作成したrouterを読み込む処理をindex.jsに追加します。
/index.js
に以下のコードを追加してください。index.jsconst loginRouter = require('./router/login'); app.use(loginRouter.routes()); app.use(loginRouter.allowedMethods());動作を確認
ブラウザでhttp://localhost:5000/login にアクセスすると、ログイン画面が表示されます。
!# 画像を挿入まとめ
以上で新規登録とログインの検証が完了です。
次回は入力値のバリデーションを書いていく予定です。
- 投稿日:2020-09-28T05:25:31+09:00
Symbol testnet bootstrap v0.10.0 自動構築 shellscript
いつもながら "Tera Term" を使用してます
借りたサーバにrootでログイン
vi b"i" を入力 編集モードにする
下記の scriptをコピー ペースト下記をコピペ
#!/bin/bash #new username echo "please input new username" read username #new sshd port echo "please input new sshd port" read sshd #ip echo "please input ip" read ip #friendly_name echo "please input friendly_name" read friendlyname #---------- #usernameset adduser $username gpasswd -a $username sudo #sshdset sed -i -e s/"#Port 22"/"Port 22"/ /etc/ssh/sshd_config sed -i -e s/"Port 22"/"Port $sshd"/ /etc/ssh/sshd_config sed -i -e s/"PermitRootLogin yes"/"PermitRootLogin no"/ /etc/ssh/sshd_config systemctl restart sshd #ufw set apt-get install ufw ufw allow $sshd/tcp #ufw start sudo ufw enable sudo ufw status #docker curl https://get.docker.com | sh usermod -aG docker $username systemctl start docker systemctl enable docker docker --version #docker-compose curl -L https://github.com/docker/compose/releases/download/1.27.3/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose docker-compose --version systemctl restart docker #node js curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - #git apt-get install -y nodejs npm install -g npm node -v npm -v #change userdir cd /home/$username #make workdir mkdir -p symbol-bootstrap cd symbol-bootstrap #install Symbol Bootstrap npm install -g symbol-bootstrap@alpha symbol-bootstrap -v #make configfile symbol-bootstrap config -p testnet -a dual #-------------config ip------------- sed -i -e s/"host ="/"host = $ip"/ target/config/api-node/resources/config-node.properties #--------config friendlyname-------- sed -i -e s/"friendlyName ="/"friendlyName = $friendlyname"/ target/config/api-node/resources/config-node.properties #---config friendlyname at line 53--- vi target/config/api-node/resources/config-node.properties #make docker-compose.yml symbol-bootstrap compose #start symbol-bootstrap run -d symbol-bootstrap run -d symbol-bootstrap run -d symbol-bootstrap run -d symbol-bootstrap run -d"ESC" を押して 編集モード終了
":wq" を入力 "ENTER" で保存終了sh bscript がスタート
username(※①)
sshd port(※②)
IP(※③)
friendlyname(※④)
それぞれ入力後は
user pw(※⑤) の設定(2回入力)
途中で "[Y/n]" は "y"起動直前で vi 起動するので
"i" 押して 編集モードに
53行目の friendlyname(※④)を修正 (黄線の部分を削除 これ消せなかったw)
"ESC" 押して 編集モード終了
":wq" で保存終了
また自動で進みます
最終行の "symbol-bootstrap run -d"は
一回では起動しない事が良くあるので起動出来たかは
http://"自分のノードのIP":3000/node/info
http://"自分のノードのIP":3000/chain/info
で確認起動に失敗していたら
一旦ログアウト
新しく作成した sshd ポート(※②) から user名(※①)とそのパスワード(※⑤)で
ログイン (この時点では port22での root ログインは出来なくなっています)cd symbol-bootstrap symbol-bootstrap run -dこれでも起動しなかったら
もう一回symbol-bootstrap run -dこれを繰り返して下さい
起動出来たかは
http://"自分のノードのIP":3000/node/info
http://"自分のノードのIP":3000/chain/info
で確認nodeを止めるには
"symbol-bootstrap" ディレクトリに入ってsymbol-bootstrap stopこれで停止します
参考資料
Symbolテストネットノードを建ててみた (v0.10.x Hippo on Ubuntu Server 18.04)
https://nemlog.nem.social/blog/49345nemtech/symbol-bootstrap
https://github.com/nemtech/symbol-bootstrap次世代NEMブロックチェーン、Symbolに迫る(9) ~テストネット設定編~
https://note.com/daokasweep/n/n94c30a6f0d55