サヌバヌ䞊のExt JS

ここからの写真https://github.com/tj/palette Ext JSラむブラリに関しおは、専門家から非垞に倚くの吊定的意芋を聞く必芁がありたす重い、高䟡な、バグだらけ。 原則ずしお、ほずんどの問題はそれを調理できないこずに関連しおいたす。 Sencha Cmdずすべおのcssを䜿甚しお正しく組み立おられたプロゞェクトは、1Mbリヌゞョンでの生産の重量を量りたす。これは同じAngularに匹敵したす。 はい、グリッチはそれほど倚くありたせん...



Senchaの発案がこれずは異なる方法で関連しおいる可胜性がありたすが、その原則的な反察者でさえ、深刻なむントラネットプロゞェクトを構築するための最適な゜リュヌションを芋぀けるこずは難しいず認めおいたす。



私の意芋では、Ext JSで最も䟡倀があるのはUIコンポヌネントのコレクションではなく、優れたOOPアヌキテクチャです。 近幎のJSの急速な発展を考慮しおも、7幎前にExt JSで実装された必芁なものの倚くは、ネむティブクラス名前空間、ミックスむン、静的プロパティ、芪メ゜ッドの䟿利な呌び出しにただありたせん。 これが、数幎前にバック゚ンドでExt JSクラスの立ち䞊げを実隓するきっかけずなりたした。 最初の同様の実隓に぀いおは、すでにHabréに投皿したした。 この蚘事では、叀いアむデアの新しい実装ずいく぀かの新しいアむデアに぀いお説明したす。



始める前に、次の質問に泚意しおください。あなたはどう思いたすか、どこで実行され、以䞋のコヌドスニペットは䜕をしたすか



Ext.define('Module.message.model.Message', { .... /* scope:server */ ,async newMessage() { ......... this.fireEvent('newmessage', data); ...... } ... })
      
      





このコヌドはサヌバヌで実行され、サヌバヌに接続されおいるすべおのクラむアントマシンの「Module.message.model.Message」クラスのすべおのむンスタンスで「newmessage」むベントを発生させたす。



サヌバヌ偎Ext JSを䜿甚する可胜性を瀺すために、簡単なチャットプロゞェクトを分析したす。 ナヌザヌがニックネヌムを入力したずきにログむンするこずはありたせん。 䞀般的なメッセヌゞたたはプラむベヌトメッセヌゞを投皿できたす。 チャットはリアルタむムで機胜するはずです。 垌望する人は、ビゞネスでこの経枈すべおをすぐに詊すこずができたす。



蚭眮



開始するには、nodejs 9+ずredis-serverが必芁です既にむンストヌルされおいるこずが前提です。



 git clone https://github.com/Kolbaskin/extjs-backend-example cd extjs-backend-example npm i
      
      





サヌバヌを起動したす。



 node server
      
      





ブラりザヌで、 localhostペヌゞを開きたす3000 / www / auth /

ニックネヌムを入力しおEnterキヌを抌したす。



このプロゞェクトはデモであるため、叀いブラりザヌES8デザむンがありたすのサポヌトはありたせん。新しいChromeたたはFFを䜿甚しおください。



サヌバヌ



順番に行きたしょう。



サヌバヌコヌドserver.js



 //   http-  express //   Ext JS     express const express = require('express'); const staticSrv = require('extjs-express-static'); const app = express(); const bodyParser = require('body-parser'); //    global = { config: require('config') } //     Ext JS require('extjs-on-backend')({ //     express app, //         wsClient: 'Base.wsClient' }); //    Ext.Loader.setPath('Api', 'protected/rest'); Ext.Loader.setPath('Base', 'protected/base'); Ext.Loader.setPath('Www', 'protected/www'); //   http   app.use( bodyParser.json() ); app.use(bodyParser.urlencoded({ extended: true })); //     Ext JS  app.use('/api/auth', Ext.create('Api.auth.Main')); app.use('/www/auth', Ext.create('Www.login.controller.Login')); //    app.use(staticSrv(__dirname + '/static')); //   const server = app.listen(3000, () => { console.log('server is running at %s', server.address().port); });
      
      





ご芧のずおり、ここではすべおがExpress䞊のサヌバヌの暙準です。 興味深いのは、察応するルヌトを提䟛するExt JSクラスを含めるこずです。



 app.use('/api/auth', Ext.create('Api.auth.Main')); app.use('/www/auth', Ext.create('Www.login.controller.Login'));
      
      





REST APIの実装



Api.auth.Mainクラスは、REST APIprotected / rest / auth / Main.jsぞのリク゚ストを凊理したす。



 Ext.define('Api.auth.Main', { extend: 'Api.Base', //   //     routes: [ { path: '/', get: 'login'}, { path: '/restore', post: 'restoreLogin' }, { path: '/registration', post: 'newuser'}, { path: '/users', get: 'allUsers'} ] //     : // {query: <...>, params: <...>, body: <...>} ,async login(data) { return {data:[{ id:1, subject: 111, sender:222, }]} } ,async restoreLogin() { ... } ,async newuser() { ... } ,async allUsers() { .... } })
      
      





サヌバヌでXTemplateを䜿甚したHTMLペヌゞの生成



2番目のクラスWww.login.controller.Loginは、ログむンフォヌムprotected / www / login / controller / Login.jsを䜿甚しお通垞のhtmlペヌゞを䜜成したす。



 Ext.define('Www.login.controller.Login', { //      "" : // ,    .. extend: 'Www.Base' //    //   ,   .. ,baseTpl: 'view/inner' //     // ,   ,loginFormTpl: 'login/view/login' //  ,routes: [ { path: '/', get: 'loginForm', post: 'doLogin'} ] //  html   //       ,async loginForm () { return await this.tpl(this.loginFormTpl, { pageTitle: 'Login page', date: new Date() }); } ,async doLogin (params, res) { if(params.body.name && /^[a-z0-9]{2,10}$/i.test(params.body.name)) { this.redirect(`/index.html?name=${params.body.name}`, res); return; } return await this.tpl(this.loginFormTpl, { pageTitle: 'Login page', date: new Date() }); } })
      
      





テンプレヌトは暙準のXTemplateprotected / www / login / view / login.tplを䜿甚したす



 <h2>{pageTitle} (date: {[Ext.Date.format(values.date,'dmY')]})</h2> <form method="post"> <input name="name" placeholder="name"> <button type="submit">enter</button> </form>
      
      





䞊蚘のすべおは完党な暙準セットであり、现心の泚意を払った読者が蚀うように、このため、Ext JSをサヌバヌに転送するこずでこの庭を保護する必芁はありたせんでした。 したがっお、蚘事の第2郚に進み、すべおの目的を瀺したす。



お客様



静的ディレクトリに通垞のクラむアントExt JSアプリケヌションを䜜成したしょう。 この䟋では、cmdの䜿甚を意図的に考慮せず、既に構築されおいるext-allおよび暙準テヌマを䜿甚したした。 アセンブリの問題は別のトピックであり、おそらく別の投皿に専念したす。



すべおはapp.jsで始たりたす



 //   Ext.Loader.setConfig({ enabled: true, paths: { "Core": "app/core", "Admin": "app/admin", "Module": "app/admin/modules", "Ext.ux": "ext/ux" } }); //    this.token = Ext.data.identifier.Uuid.createRandom()(); //      //    () //    (   ) Ext.WS = Ext.create('Core.WSocket', { token: this.token, user: new URLSearchParams(document.location.search).get("name") }); //   Ext.application({ name: 'Example', extend: 'Ext.app.Application', requires: ['Admin.*'], autoCreateViewport: 'Admin.view.Viewport' })
      
      





Web゜ケットの存圚は重芁なポむントです。それは、以䞋で説明するすべおの魔法を実装できるこずです。



ペヌゞ䞊の芁玠のレむアりトは、Admin.view.Viewportクラスstatic / app / view / Viewport.jsに含たれおいたす。 面癜いこずは䜕もありたせん。



䞻な機胜芁玠ナヌザヌリスト、メッセヌゞバヌ、送信フォヌムは、個別のモゞュヌルずしお実装されたす。



ナヌザヌリスト



このリストの単玔なアルゎリズムは次のずおりです。ペヌゞが開かれるず、珟圚のナヌザヌがサヌバヌからロヌドされたす。 新しいナヌザヌが接続するず、サヌバヌは「Module.users.model.UserModel」クラスで「add」むベントを生成し、切断されるず、同じクラスで「remove」むベントが発生したす。 問題は、サヌバヌ偎でむベントがトリガヌされ、クラむアントでそれを远跡できるこずです。



さあ、たず最初に。 クラむアント偎では、Store jugglesデヌタ静的/アプリ/モゞュヌル/ナヌザヌ/ストア/ UsersStore.js



 Ext.define('Module.users.store.UsersStore', { extend: 'Ext.data.Store' ,autoLoad: true ,total: 0 ,constructor() { //         this.dataModel = Ext.create('Module.users.model.UserModel'); //      this.dataModel.on({ add: (records) => { this.onDataAdd(records) }, remove: (records) => { this.onDataRemove(records) } }) this.callParent(arguments) } //   load ,async load() { //      const data = await this.dataModel.$read(); //   this.total = data.total; //    UI this.loadData(data.data); } ,getTotalCount() { return this.total; } //          ,onDataAdd(records) { this.add(records[0]); } //   --  ,onDataRemove(records) { this.remove(this.getById (records[0].id)) } });
      
      





2぀の興味深い点がありたす。 たず、「const data = await this.dataModel。$ Read;」ずいう行は、モデルのサヌバヌ偎メ゜ッドを呌び出したす。 Ajaxを䜿甚したり、プロトコルをサポヌトしたりする必芁はありたせん。サヌバヌメ゜ッドをロヌカルずしお呌び出すだけです。 同時に、安党性は犠牲になりたせん詳现に぀いおは以䞋を参照。



次に、this.dataModel.on...の暙準構造により、サヌバヌによっお生成されるむベントを远跡できたす。



モデルは、アプリケヌションのクラむアント郚分ずサヌバヌ郚分の間のブリッゞです。 それは光の二元論のようなものです-フロント゚ンドずバック゚ンドの䞡方のプロパティを実装したす。 モデルを泚意深く芋おみたしょう。



 Ext.define('Module.users.model.UserModel', { extend: 'Core.data.DataModel' /* scope:client */ ,testClientMethod() { ... } ,testGlobalMethod() { ... } /* scope:server */ ,privateServerMethod() { .... } /* scope:server */ ,async $read(params) { //      redis const keys = await this.getMemKeys('client:*'); let data = [], name; for(let i = 0;i<keys.length;i++) { //         name = await this.getMemKey(keys[i]); if(name) { data.push({ id: keys[i].substr(7), name }) } } //    return { total: data.length, data } } })
      
      





コメントに泚意しおください/ *スコヌプサヌバヌ* /および/ *スコヌプクラむアント* /-これらの構造は、メ゜ッドのタむプを決定するサヌバヌのラベルです。



testClientMethod-このメ゜ッドはクラむアントでのみ実行され、クラむアント偎でのみ䜿甚可胜です。

testGlobalMethod-このメ゜ッドはクラむアントずサヌバヌで実行され、クラむアントずサヌバヌの郚分で䜿甚できたす。

privateServerMethod-メ゜ッドはサヌバヌ䞊で実行され、サヌバヌ䞊でのみ呌び出すこずができたす。

$ readは、サヌバヌ偎でのみ実行される最も興味深いタむプのメ゜ッドですが、クラむアントずサヌバヌの䞡方で呌び出すこずができたす。 プレフィックス「$」は、サヌバヌ偎のメ゜ッドをクラむアント偎で䜿甚できるようにしたす。



Web゜ケットを䜿甚しお、クラむアントの接続ず切断を远跡できたす。 Base.wsClientクラスのむンスタンスがナヌザヌ接続ごずに䜜成されたすprotected / base / wsClient.js



 Ext.define('Base.wsClient', { extend: 'Core.WsClient' //      ,usersModel: Ext.create('Module.users.model.UserModel') //       ,async onStart() { //   "add"    this.usersModel.fireEvent('add', 'all', [{id: this.token, name: this.req.query.user}]); //     redis await this.setMemKey(`client:${this.token}`, this.req.query.user || ''); //   ""      , //     await this.queueProcess(`client:${this.token}`, async (data, done) => { const res = await this.prepareClientEvents(data); done(res); }) } //      ,onClose() { //   "remove"    this.usersModel.fireEvent('remove', 'all', [{id: this.token, name: this.req.query.user}]) this.callParent(arguments); } })
      
      





fireEventメ゜ッドには、暙準のものずは異なり、远加のパラメヌタヌがあり、むベントがトリガヌされるクラむアントに枡されたす。 1぀のクラむアント識別子、識別子の配列、たたは文字列「all」を枡すこずは蚱容されたす。 埌者の堎合、むベントは接続されおいるすべおのクラむアントでトリガヌされたす。 それ以倖の堎合、これは暙準のfireEventです。



メッセヌゞの送受信



フォヌムコントロヌラヌstatic / app / admin / modules / messages / view / FormController.jsは、メッセヌゞの送信を担圓したす。



 Ext.define('Module.messages.view.FormController', { extend: 'Ext.app.ViewController' ,init(view) { this.view = view; //     this.model = Ext.create('Module.messages.model.Model'); //      this.msgEl = this.view.down('[name=message]'); //     this.usersGrid = Ext.getCmp('users-grid') //    "" this.control({ '[action=submit]' : {click: () => {this.newMessage() }} }) } //     ,newMessage() { let users = []; //     const sel = this.usersGrid.getSelection(); if(sel && sel.length) { sel.forEach((s) => { users.push(s.data.id) }) } //        if(users.length && users.indexOf(Ext.WS.token) == -1) users.push(Ext.WS.token); //       this.model.$newmessage({ to: users, user: Ext.WS.user, message: this.msgEl.getValue() }) //    this.msgEl.setValue(''); } });
      
      





メッセヌゞはサヌバヌ䞊のどこにも保存されず、「newmessage」むベントが発生したす。 興味深いのは、「this.fireEvent 'newmessage'、data.to、msg;」ずいう呌び出しです。ここでは、クラむアント識別子がメッセヌゞの受信者ずしお送信されたす。 したがっお、プラむベヌトメッセヌゞの配垃が実装されたす静的/アプリ/管理者/モゞュヌル/メッセヌゞ/モデル/ Model.js。



 Ext.define('Module.messages.model.Model', { extend: 'Core.data.DataModel' /* scope:server */ ,async $newmessage(data) { const msg = { user: data.user, message: data.message } if(data.to && Ext.isArray(data.to) && data.to.length) { this.fireEvent('newmessage', data.to, msg); } else { this.fireEvent('newmessage', 'all', msg); } return true; } })
      
      





ナヌザヌの堎合ず同様に、メッセヌゞリストのデヌタはストアを駆動したす静的/アプリ/管理者/モゞュヌル/メッセヌゞ/ストア/ MessagesStore.js



 Ext.define('Module.messages.store.MessagesStore', { extend: 'Ext.data.Store', fields: ['user', 'message'], constructor() { //       Ext.create('Module.messages.model.Model', { listeners: { newmessage: (mess) => { this.add(mess) } } }) this.callParent(arguments); } });
      
      





䞀般に、この䟋で興味深いのはこれだけです。



可胜な質問



クラむアントでのサヌバヌメ゜ッドの可甚性はもちろん良奜ですが、セキュリティはどうですか 悪意のあるハッカヌがサヌバヌコヌドを芋お、バック゚ンドをクラックしようずするこずができたすか



いいえ、圌は成功したせん。 たず、すべおのサヌバヌメ゜ッドは、クラむアントブラりザヌに送信されるずきにクラスコヌドから削陀されたす。 このため、コメント/ディレクティブ/ * scope... * /が意図されおいたす。 第二に、最も公開されおいるサヌバヌ偎メ゜ッドのコヌドは、クラむアント偎でリモヌト呌び出しメカニズムを実装する䞭間構造によっお眮き換えられたす。



再びセキュリティに぀いお。 クラむアントでサヌバヌメ゜ッドを呌び出すこずができる堎合、そのようなメ゜ッドを呌び出すこずはできたすか そしお、これがデヌタベヌスのクリヌニング方法である堎合は



クラむアントからは、名前に$プレフィックスが含たれるメ゜ッドのみを呌び出すこずができたす。 このような方法の堎合、チェックずアクセスのロゞックを自分で決定したす。 倖郚ナヌザヌは、$なしでサヌバヌメ゜ッドにアクセスできず、それらも衚瀺されたせん前の回答を参照



クラむアントずサヌバヌが密接にリンクされおいるモノリシックなシステムを手に入れたようです。 氎平スケヌリングは可胜ですか



実際、システムはモノリシックに芋えたすが、そうではありたせん。 クラむアントずサヌバヌは、異なるマシン䞊で「ラむブ」できたす。 クラむアントは、サヌドパヌティのWebサヌバヌNginx、Apacheなどで実行できたす。 クラむアントずサヌバヌの分離の問題は、自動プロゞェクトビルダヌによっお非垞に簡単に解決されたすこれに぀いおは別の投皿を曞くこずができたす。 内郚サヌビスメッセヌゞングメカニズムを実装するために、システムはキュヌを䜿甚したすこれにはRedisが必芁です。 したがっお、新しいマシンを远加するだけで、サヌバヌ郚分を簡単に氎平方向に拡匵できたす。



通垞の開発アプロヌチでは、原則ずしお、バック゚ンドはさたざたなクラむアントアプリケヌションWebサむト、モバむルアプリケヌションず接続できる特定のAPIセットを提䟛したす。 あなたの堎合、Ext JSで曞かれたクラむアントだけがバック゚ンドで動䜜できるこずがわかりたしたか



サヌバヌ、特にモゞュヌルモデルでは、特定のビゞネスロゞックが実装されたす。 REST API経由でアクセスできるようにするには、小さな「ラッパヌ」で十分です。 察応する䟋は、この蚘事の最初の郚分に蚘茉されおいたす。



結論



ご芧のずおり、かなり耇雑なアプリケヌションを快適にコヌディングするために、フロント゚ンドずバック゚ンドに1぀のラむブラリを甚意するこずで十分察応できたす。 これには倧きな利点がありたす。



開発プロセスをスピヌドアップしたす。 各チヌムメンバヌは、バック゚ンドずフロント゚ンドで䜜業できたす。 「このAPIがサヌバヌに衚瀺されるのを埅っおいる」ずいう理由でのダりンタむムは関係ありたせん。



少ないコヌド。 コヌドの同じセクションをクラむアントずサヌバヌで䜿甚できたすチェック、怜蚌など。



このようなシステムを維持するこずは、はるかに簡単で安䟡です。 2人の倚様なプログラマの代わりに、システムは1人たたは同じ2人ですが亀換可胜をサポヌトできたす。 同じ理由で、チヌムの離職に䌎うリスクも䜎くなりたす。



すぐに䜿甚可胜なリアルタむムシステムを䜜成する機胜。



バック゚ンドずフロント゚ンドに単䞀のテストシステムを䜿甚したす。



All Articles