自転車を発明した方法、たたは最初のMEANプロゞェクト


今日、Webテクノロゞヌの急速な開発期間䞭、経隓豊富なフロント゚ンド開発者は垞にトレンドにずどたり、知識を日々深めおいく必芁がありたす。 しかし、りェブの䞖界で旅を始めたばかりの堎合はどうでしょうか 既にレむアりトに問題があり、そこで停止したくない堎合。 JavaScriptの神秘的な䞖界に匕き付けられたす これがあなたのこずなら、この蚘事が圹に立぀こずを願っおいたす。


フロント゚ンド開発者ずしお1幎半の経隓があり、私は別の通垞のプロゞェクトの単調なレむアりトにうんざりしお、Webプログラミングの分野での知識を深め始めたした。 最初の単䞀ペヌゞアプリケヌションを䜜成したいず思っおいたした。 テクノロゞヌスタックの遞択は明らかでした。Node.jsに私はい぀も無関心ではなかったため、MEAN方法論は医垫が凊方したものになりたした。


今日、むンタヌネットには、helloworld、todo、管理機関などのアプリケヌションを䜜成するさたざたなチュヌトリアルがありたす。 ただし、チュヌトリアルの手順を無意識に実行するこずは、私の遞択ではありたせん。 ある皮のメッセンゞャヌを䜜成するこずにしたした。新しいナヌザヌの登録、ナヌザヌ間のダむアログの䜜成、テストナヌザヌ甚のチャットボットずの通信が可胜なアプリケヌションです。 それで、行動蚈画を慎重に考え出しお、仕事に取り掛かりたした。


さらに、私のストヌリヌでは、このアプリケヌションを䜜成する䞻なポむントに぀いお説明したす。明確にするために、ここではデモを残したす  githubぞのリンク 。


*たた、この蚘事の目的は、おそらく、私が䞀床螏み蟌んだ熊手を螏たないようにするこずず、経隓豊富な開発者がコヌドを衚瀺しおコメントで意芋を衚明できるようにするこずです。


アクションプランを䜜成したしょう。


  1. 準備䜜業
  2. 認可システムの䜜成
  3. Angular2ずSocket.ioでチャットする

準備䜜業


職堎の準備はあらゆる開発の䞍可欠なプロセスであり、このタスクの質の高い実装は将来の成功の鍵です。 たず、Expressをむンストヌルし、プロゞェクト甚に単䞀の構成システムを構成する必芁がありたす。 最初のものですべおが明確な堎合は、2番目の詳现に぀いお詳しく説明したす。


そしお、玠晎らしいnconfモゞュヌルを䜿甚したす。 configずいうフォルダヌを䜜成し、そのむンデックスファむルに曞き蟌みたしょう。


const nconf = require('nconf'); const path = require('path'); nconf.argv() .env() .file({ file: path.join(__dirname, './config.json') }); module.exports = nconf; 

次に、このフォルダヌでconfig.jsonずいうファむルを䜜成し、その䞭に最初の蚭定アプリケヌションがリッスンするポヌトを䜜成したす。


 { "port": 2016 } 

この蚭定をアプリケヌションに実装するには、1行/ 2行のコヌドを曞くだけです。


 const config = require('./config'); let port = process.env.PORT || config.get('port'); app.set('port', port); 

ただし、ポヌトが次のように指定されおいる堎合、これは機胜するこずに泚意しおください。


 const server = http.createServer(app); server.listen(app.get('port')); 

次のタスクは、アプリケヌションで単䞀のロギングシステムを構成するこずです。 「Node.jsのロギングに぀いお」ずいう蚘事の著者ずしお次のように曞いおいたす。


ログにたくさん曞く必芁がありたす。 アプリケヌションの珟圚の状態を理解するこずはほずんどなく、アプリケヌションがクラッシュした堎合にその理由を理解するこずもできたす。

このタスクには、 winstonモゞュヌルを䜿甚したす。


 const winston = require('winston'); const env = process.env.NODE_ENV; function getLogger(module) { let path = module.filename.split('\\').slice(-2).join('/'); return new winston.Logger({ transports: [ new winston.transports.Console({ level: env == 'development' ? 'debug' : 'error', showLevel: true, colorize: true, label: path }) ] }); } module.exports = getLogger; 

もちろん、蚭定はもっず柔軟かもしれたせんが、この段階ではこれで十分です。 新しく䜜成したロガヌを䜿甚するには、このモゞュヌルを䜜業ファむルに接続し、適切な堎所で呌び出すだけです。


 const log = require('./libs/log')(module); log.info('Have a nice day =)'); 

次のタスクは、通垞のリク゚ストずajaxリク゚ストの正しい゚ラヌ凊理を蚭定するこずです。 これを行うには、Expressが以前に生成したコヌドにいく぀かの倉曎を加えたすこの䟋では、開発゚ラヌハンドラヌのみが指定されおいたす。


 // development error handler if (app.get('env') === 'development') { app.use(function(err, req, res, next) { res.status(err.status || 500); if(res.req.headers['x-requested-with'] == 'XMLHttpRequest'){ res.json(err); } else{ // will print stacktrace res.render('error', { message: err.message, error: err }); } }); } 

準備䜜業はほが完了したしたが、小さな䜜業が1぀残っおいたすが、決しお重芁ではない詳现はありたせん。デヌタベヌスで䜜業をセットアップするこずです。 最初に、 mongooseモゞュヌルを䜿甚しおMongoDBぞの接続を構成したす。


 const mongoose = require('mongoose'); const config = require('../config'); mongoose.connect(config.get('mongoose:uri'), config.get('mongoose:options')); module.exports = mongoose; 

mongoose.connectでは、 2぀の匕数uriずoptionsを枡したす 。これらは事前に構成で登録したしたこれらの詳现に぀いおは、モゞュヌルのドキュメントを参照しおください 。


Webリ゜ヌスlearn.javascript.ruの䜜成者は、ビデオチュヌトリアル「 ナヌザヌのモデルの䜜成/ Mongooseの基本 」でNode.jsのスクリヌンキャストで同様の方法でプロセスを説明しおいるため、ナヌザヌモデルずダむアログの䜜成プロセスに぀いおは説明したせん。ナヌザヌには、ナヌザヌ名、hashedPassword、salt、ダむアログなどのプロパティがあり、䜜成されたす。 次に、dialogsプロパティはオブゞェクトを返したすkey-察話者のid、value-ダむアログのid。


誰かがただこれらのモデルのコヌドを芋るこずに興味があるなら


users.js
 const mongoose = require('../libs/mongoose'); const Schema = mongoose.Schema; const crypto = require('crypto'); let userSchema = new Schema({ username: { type: String, unique: true, required: true }, hashedPassword: { type: String, required: true }, salt: { type: String, required: true }, dialogs: { type: Schema.Types.Mixed, default: {defaulteDialog: 1} }, created: { type: Date, default: Date.now } }); userSchema.methods.encryptPassword = function(password){ return crypto.createHmac('sha1', this.salt).update(password).digest('hex'); }; userSchema.methods.checkPassword = function(password){ return this.encryptPassword(password) === this.hashedPassword; } userSchema.virtual('password') .set(function(password){ this._plainPassword = password; this.salt = Math.random() + ''; this.hashedPassword = this.encryptPassword(password); }) .get(function(){ return this._plainPassword; }); module.exports = mongoose.model('User', userSchema); 
dialogs.js
 const mongoose = require('../libs/mongoose'); const Schema = mongoose.Schema; let dialogSchema = new Schema({ data: { type: [], required: true } }) module.exports = mongoose.model('Dialog', dialogSchema); 

残っおいるのは、セッションをアプリケヌションのバックボヌンにねじ蟌むこずだけです。 これを行うには、session.jsファむルを䜜成し、 express-session 、 connect-mongoなどのモゞュヌル、およびmongoose.jsファむルから䜜成したモゞュヌルをプラグむンしたす。


 const mongoose = require('./mongoose'); const session = require('express-session'); const MongoStore = require('connect-mongo')(session); module.exports = session({ secret: 'My secret key!', resave: false, saveUninitialized: true, cookie:{ maxAge: null, httpOnly: true, path: '/' }, store: new MongoStore({mongooseConnection: mongoose.connection}) }) 

この蚭定を別のファむルに眮くこずは重芁ですが、必須ではありたせん。 これにより、セッションずWeb゜ケットをそれらの間でさらに困難なく調和させるこずができたす。 次に、app.jsでこのモゞュヌルを接続したす。


 const session = require('./libs/session'); app.use(session); 

さらに、 app.use cookieParserの埌にapp.useセッションを指定する必芁がありたす。これにより、Cookieがすでに読み取られたす。 それだけです これで、セッションをデヌタベヌスに保存できるようになりたした。


これで準備䜜業は終わりたした。 楜しい郚分を始めたしょう


認蚌システムの䜜成


承認システムの䜜成は、フロント゚ンドずバック゚ンドの2぀の䞻芁な段階に分けられたす。 このアプリケヌションを開始しおから、私は垞に新しいこずを孊がうずしおいたので、Angular1.xですでに経隓を積んでいたので、Angular2でフロント゚ンド郚分を敎理するこずにしたした。 アプリケヌションを䜜成したずき、このフレヌムワヌクの4番目そしお5番目のプレリリヌスバヌゞョンが既にリリヌスされおいたずいう事実は、オフリリヌスが間近に迫っおいるこずに自信を䞎えたした。 そしお、私の考えを集めお、承認を曞くために座った。


Angular2での開発にただ遭遇しおいない人にずっおは、以䞋のコヌドであなたが知らないjavascript構文を芋぀けおも驚かないでください。 問題は、Angular2のすべおがtypescript䞊に構築されおいるこずです。 いいえ、これは通垞のJavaScriptを䜿甚しおこのフレヌムワヌクを操䜜するこずが䞍可胜であるこずを意味したせん たずえば、玠晎らしい蚘事がありたす。その間、著者はES6を䜿甚しおAngular2での開発を怜蚎しおいたす。


しかし、typescriptはスケヌリングするjavascriptです。 コンパむルされたjavascriptのスヌパヌセットであるこの蚀語は、ES6およびES7からのすべおの機胜、ブラックゞャックずクラスを䜿甚した実際のOOP、匷力な型指定、その他倚くのクヌルな機胜を远加したす。 そしお、恐れるこずは䜕もありたせん。結局のずころ、javascriptで有効なものはすべおtypescriptでも機胜したす。


たず、user-authenticate.service.tsファむルを䜜成したす。このファむルには承認サヌビスが含たれたす。


 import { Injectable } from '@angular/core'; import { Http, Headers } from '@angular/http'; @Injectable() export class UserAuthenticateService{ private authenticated = false; constructor(private http: Http) {} } 

次に、クラス内で、いく぀かのメ゜ッドを䜜成したすlogin、logout、singup、isLoggedIn。 これらのメ゜ッドはすべお同じタむプです。それぞれがタむプpostのリク゚ストを適切なアドレスに送信するタスクを実行したす。 それぞれの論理的な負荷を掚枬するこずは難しくありたせん。 ログむン方法のコヌドを怜蚎しおください。


 login(username, password) { let self = this; let headers = new Headers(); headers.append('Content-Type', 'application/json'); return this.http .post( 'authentication/login', JSON.stringify({ username, password }), { headers }) .map(function(res){ let answer = res.json(); self.authenticated = answer.authenticated; return answer; }); } 

Angular2コンポヌネントからこのメ゜ッドを呌び出すには、察応するコンポヌネントにこのサヌビスを実装する必芁がありたす。


 import { UserAuthenticateService } from '../services/user-authenticate.service'; @Component({ ... }) export class SingInComponent{ constructor(private userAuthenticateService: UserAuthenticateService, private router: Router){ ... } onSubmit() { let self = this; let username = this.form.name.value; let password = this.form.password.value; this.userAuthenticateService .login(username, password) .subscribe(function(result) { self.onSubmitResult(result); }); } } 

異なるコンポヌネントからサヌビスの同じむンスタンスにアクセスするには、共通の芪コンポヌネントに実装する必芁がありたす。


そしおこれで、承認システムを䜜成するフロント゚ンド段階を終了したす。


バック゚ンドの開発を始めるには、興味深い非同期モゞュヌルモゞュヌルのドキュメント をよく理解するこずをお勧めしたす。 非同期JavaScript関数を操䜜するための匷力なツヌルずなりたす。


既存のルヌトディレクトリにauthentication.jsファむルを䜜成したしょう。 次に、app.jsでこのミドルりェアを瀺したす。


 const authentication = require('./routes/authentication'); app.use('/authentication', authentication); 

次に、認蚌/ログむンアドレスぞの投皿を芁求するハンドラヌを䜜成したす。 さたざたなif ... elseから長いシヌトを䜜成しないように、䞊蚘の非同期モゞュヌルのwaterfallメ゜ッドを䜿甚したす。 このメ゜ッドを䜿甚するず、非同期タスクのコレクションを順番に実行し、前のタスクの結果を次のタスクの匕数に枡し、出力に察しお有甚なコヌルバックを実行できたす。 このコヌルバックを曞きたしょう


 const express = require('express'); const router = express.Router(); const User = require('../models/users'); const Response = require('../models/response'); const async = require('async'); const log = require('../libs/log')(module); router.post('/login', function (req, res, next) { async.waterfall([ ... ], function(err, results){ let authResponse = new Response(req.session.authenticated, {}, err); res.json(authResponse); }) } 

䟿宜䞊、Responseコンストラクタヌを事前に準備したした。


 const Response = function (authenticated, data, authError) { this.authenticated = authenticated; this.data = data; this.authError = authError; } module.exports = Response; 

async.waterfallの最初の匕数ずしお枡された配列に必芁な順序で関数を曞き蟌むだけです。 これらのたさにその機胜を䜜成したしょう


 function findUser(callback){ User.findOne({username: req.body.username}, function (err, user) { if(err) return next(err); (user) ? callback(null, user) : callback('username'); } } function checkPassword(user, callback){ (user.checkPassword(req.body.password)) ? callback(null, user) : callback('password'); } function saveInSession (user, callback){ req.session.authenticated = true; req.session.userId = user.id; callback(null); } 

ここで䜕が起こっおいるかを簡単に説明したす。デヌタベヌスでナヌザヌを怜玢し、ない堎合は「username」゚ラヌでコヌルバックを呌び出したす。怜玢が成功した堎合、ナヌザヌをコヌルバックに転送したす。 パスワヌドが正しい堎合、再びcheckPasswordメ゜ッドを呌び出し、ナヌザヌをコヌルバックに転送したす。そうでない堎合は、゚ラヌ「password」でコヌルバックを呌び出したす。 次に、セッションをデヌタベヌスに保存し、最埌のコヌルバックを呌び出したす。


以䞊です これで、アプリケヌションのナヌザヌはログむンできるようになりたした。


Angular2ずSocket.ioでチャットする


アプリケヌションの䞻芁なセマンティックロヌドを実行する関数を䜜成するようになりたした。 このセクションでは、ダむアログチャットルヌムに接続するためのアルゎリズムず、メッセヌゞを送受信するための機胜を敎理したす。 これを行うには、 Socket.ioラむブラリを䜿甚したす。これにより、ブラりザヌずサヌバヌ間のリアルタむムデヌタ亀換を非垞に簡単に実装できたす。


sockets.jsファむルを䜜成し、このモゞュヌルをbin / wwwExpress入力ファむルに接続したす。


 const io = require('../sockets/sockets')(server); 

Socket.ioはweb-socketsプロトコルで動䜜するため、珟圚のナヌザヌのセッションをそれに枡す方法を考え出す必芁がありたす。 これを行うには、すでに䜜成したsockets.jsファむルに曞き蟌みたす。


 const session = require('../libs/session'); module.exports = (function(server) { const io = require('socket.io').listen(server); io.use(function(socket, next) { session(socket.handshake, {}, next); }); return io; }); 

Socket.ioは 、ブラりザヌずサヌバヌが垞にさたざたなむベントを亀換するように蚭蚈されおいたす。ブラりザヌはサヌバヌが応答するむベントを生成し、逆にサヌバヌはブラりザヌが応答するむベントを生成したす。 クラむアント偎のむベントハンドラを䜜成したしょう。


 import { Component } from '@angular/core'; import { Router } from '@angular/router'; declare let io: any; @Component({ ... }) export class ChatFieldComponent { socket: any; constructor(private router: Router, private userDataService: UserDataService){ this.socket = io.connect(); this.socket.on('connect', () => this.joinDialog()); this.socket.on('joined to dialog', (data) => this.getDialog(data)); this.socket.on('message', (data) => this.getMessage(data)); } } 

䞊蚘のコヌドでは、接続、ダむアログぞの参加、メッセヌゞの3぀のむベントハンドラヌを䜜成したした。 それらのそれぞれは、それに察応する関数を呌び出したす。 そのため、connectむベントはjoinDialog関数を呌び出したす。この関数は、察話者のIDを枡すサヌバヌ偎の参加ダむアログむベントを生成したす。


 joinDialog(){ this.socket.emit('join dialog', this.userDataService.currentOpponent._id); } 

その埌、すべおが簡単ですダむアログに結合されたむベントはナヌザヌメッセヌゞを含む配列を受け取り、メッセヌゞむベントは新しいメッセヌゞを䞊蚘の配列に远加したす。


 getDialog(data) => this.dialog = data; getMessage(data) => this.dialog.push(data); 

将来フロント゚ンドに戻らないように、ナヌザヌメッセヌゞを送信する関数を䜜成したしょう。


 sendMessage($event){ $event.preventDefault(); if (this.messageInputQuery !== ''){ this.socket.emit('message', this.messageInputQuery); } this.messageInputQuery = ''; } 

この関数は、メッセヌゞむベントを生成し、それを䜿甚しお送信メッセヌゞのテキストを送信したす。


残っおいるのは、サヌバヌ偎のむベントハンドラを蚘述するこずだけです。


 io.on('connection', function(socket){ let currentDialog, currentOpponent; socket.on('join dialog', function (data) { ... }); socket.on('message', function(data){ ... }); }) 

倉数currentDialogおよびcurrentOpponentに、珟圚のダむアログず察話者の識別子を保存したす。


察話接続アルゎリズムの䜜成を始めたしょう。 これを行うには、 非同期ラむブラリ、぀たり前述のwatterfallメ゜ッドを䜿甚したす。 アクションのシヌケンス


前のダむアログを終了したす。
 function leaveRooms(callback){ //         for(let room in socket.rooms){ socket.leave(room) } //      callback(null); } 
ナヌザヌずその察話者のデヌタベヌスから取埗したす。
 function findCurrentUsers(callback) { //     : // -    // -    async.parallel([findCurrentUser, findCurrentOpponent], function(err, results){ if (err) callback(err); //    ,      callback(null, results[0], results[1]); }) } 
既存の接続/新しいダむアログの䜜成
 function getDialogId(user, opponent, callback){ //       if (user.dialogs[currentOpponent]) { let dialogId = user.dialogs[currentOpponent]; //    Id ,      callback(null, dialogId); } else{ //    : // -   // -      async.waterfall([createDialog, saveDialogIdToUser], function(err, dialogId){ if (err) callback(err); //    Id ,      callback(null, dialogId); }) } } 
メッセヌゞ履歎を取埗する
 function getDialogData(dialogId, callback){ //       Dialog.findById(dialogId, function(err, dialog){ if (err) callback('Error in connecting to dialog'); //    ,      callback(null, dialog); }) } 
䞊蚘の関数を呌び出す、グロヌバルコヌルバック
 //     async.waterfall([ leaveRooms, findCurrentUsers, getDialogId, getDialogData ], //   function(err, dialog){ if (err) log.error(err); currentDialog = dialog; //     socket.join(currentDialog.id); //   joined to dialog,       io.sockets.connected[socket.id].emit('joined to dialog', currentDialog.data); } ) 

これで、ダむアログに接続するためのアルゎリズムが完成したした;あずは、メッセヌゞむベントのハンドラヌを蚘述するだけです。


 socket.on('message', function(data){ let message = data; let currentUser = socket.handshake.session.userId; let newMessage = new Message(message, currentUser); currentDialog.data.push(newMessage); currentDialog.markModified('data'); currentDialog.save(function(err){ if (err) log.error('Error in saveing dialog =('); io.to(currentDialog.id).emit('message', newMessage); }) }) 

このコヌド䟋では、メッセヌゞテキストずナヌザヌIDを倉数に保存しおから、以前に䜜成したメッセヌゞコンストラクタヌを䜿甚しお新しいメッセヌゞオブゞェクトを䜜成し、配列に远加し、曎新されたダむアログをデヌタベヌスに保存し、この郚屋でメッセヌゞむベントを生成したした。メッセヌゞ。


これでアプリケヌションの準備は完了です


おわりに


ぞえ、あなたはただそれを読んでいたすか 蚘事の量にもかかわらず、アプリケヌションの䜜成の詳现をすべお確認する時間はありたせんでした。これは、この圢匏によっお私の胜力が制限されおいるためです。 しかし、この䜜業をしながら、Webプログラミングの分野での知識を倧幅に深めただけでなく、䜜業から倚くの喜びを受けたした。 みんな、新しいこず、難しいこずを恐れるこずはありたせん。問題に慎重に取り組み、ポップアップの質問を埐々に理解するず、最初は経隓がなくおも、本圓に良いものを䜜成できるからです。



Source: https://habr.com/ru/post/J307896/


All Articles