どうやっおAB-DOCをしたすか

この蚘事では、私たちず、Amazon S3ストレヌゞぞのむンタヌフェヌスを提䟛するサヌバヌレスWebアプリケヌションであるAB-DOCに぀いお説明したす。 以前の蚘事でAB-DOCの詳现を読むこずができたす。 次に、AB-DOCの基瀎ずなる技術゜リュヌションに焊点を圓おたす。 開発および展開プロセスをどのように構築したかを説明したす。







写真では、自宅のデスクにいお、90の時間働いおいたす。





私たちには小さなチヌムがあり 、ビゞネス自動化のためのWebアプリケヌションを開発しおいたす。 カスタムプロゞェクトの実行に加えお、2぀のオンラむンサヌビスを䜜成および開発しおいたす。

  1. AB-DOC情報の保管およびシステム化サヌビス
  2. タスクトラッカヌAB-TASKS


チヌム党員がAB-DOCに取り組んでいるわけではありたせんが、私ずIgorBBの 2人だけです 。







それでは、AB-DOCずは䜕ですか



サヌバヌレスアヌキテクチャ



サヌバヌレスアプリケヌションは、珟圚、Web開発の䞖界で匷力なトレンドずなっおいたす。 簡単に蚀えば、これらは完党にクラりドプロバむダヌのサヌビスに基づいお動䜜するアプリケヌションです。 それらの利点は、それらを機胜させるクラりドサヌビスの特性フォヌルトトレランスずスケヌラビリティに由来したす。 さらに、クラりドサヌビスず同様に、それらのコストは負荷に盎接䟝存したす。 負荷がない堎合、コストはれロです。 それは非垞に快適で玠晎らしいです。



しかし、コむンには裏返しがありたす。 サヌバヌレスアプリケヌションの最初の欠点は、耇雑な開発プロセスです。 ぀たり、むンフラストラクチャの初期構成および開発プロセスの構築の段階では、サヌバヌを備えた通垞のWebアプリケヌションよりも倚くの劎力が必芁です。 2番目の欠点は、このようなアプリケヌションは通垞ベンダヌに䟝存しおいるため、別のクラりドプロバむダヌに移行するのは困難な䜜業になる可胜性があるこずです。



Amazon Web Servicesでアプリケヌションを䜜成しおいるため、この蚘事のすべおはこのクラりドプロバむダヌを参照したす。



通垞、サヌバヌレスアプリケヌションにはバック゚ンドがありたす。 バック゚ンドは、サヌビスDynamoDBNOSQL DBMSたたはRDSリレヌショナルDBMSの圢匏のデヌタベヌスです。 バック゚ンドのコヌドはLambda関数ずしお実装され、API Gatewayを介しおアクセスされたす。



AB-DOCにはこれはありたせん。 通垞、バック゚ンドずサヌバヌコヌドはありたせん少なくずもただ。



AB-DOCアヌキテクチャは次のようになりたす







アプリケヌションコヌドHTML、CSS、JavaScriptなどは別のS3バケットにあり、CDN CloudFrontを介しお提䟛されたす。 原則ずしお、バケットでは静的コンテンツをホストする機胜を有効にできるため、S3から盎接提䟛できたす。



CloudFrontが必芁な理由は、コンテンツの読み蟌みを高速化するだけでなく、すべおのリンクをindex.htmlにリダむレクトする方法が必芁だったためでもありたした。 AB-DOCは単䞀ペヌゞアプリケヌションSPAであるため、URLを芁求する堎合、ナヌザヌを読み蟌むためにindex.htmlが必芁です。 さらに、リク゚ストされたURLに基​​づいお、JavaScriptはajaxを䜿甚しおナヌザヌに必芁なコンテンツを読み蟌みたす。 フロント゚ンドフレヌムワヌクは䜿甚しないため、独自の小さなルヌタヌを䜜成したした。



したがっお、すべおのURLをindex.htmlにリダむレクトするには、CloudFrontで404゚ラヌ凊理ルヌルを蚭定したすペヌゞが芋぀かりたせん。







このルヌルのおかげで、CloudFrontは、リ゜ヌスに存圚しないURLのリク゚ストに応じお、200の応答コヌドでindex.htmlを提䟛したす。これは、S3でコヌドをホストするSPAを実装するための魔法です。



ナヌザヌコンテンツもS3の別のバケットでホストされ、CloudFrontを通じおも提䟛されたす。 カスタムコンテンツには、ツリヌ構造Json、ドキュメントコンテンツHTML、ドキュメントに埋め蟌たれた画像、および添付ファむルさたざたな圢匏が含たれたす。



さらに、私の意芋で興味深い個人、アプリケヌションの実装のニュアンスに぀いおお話したす。



認蚌ず承認



コヌドをナヌザヌのブラりザにダりンロヌドした埌、AB-DOCは最初にナヌザヌを認蚌および承認したす。 このために、Cognitoを䜿甚したす。



認蚌はナヌザヌを認蚌するプロセスであり、承認はこのナヌザヌにプログラムで䜜業する特定の暩利を䞎えるプロセスであるこずを思い出させおください。



Cognitoは3぀のサヌビスで構成されおいたす。



  1. ナヌザヌプヌル
  2. フェデレヌションID
  3. 同期する




Cognitoサヌビスの経隓がなかった堎合、各サヌビスが必芁な理由を理解するこずは困難です。 これを理解する方法を曞いおいたす。



ナヌザヌプヌル



これは、ナヌザヌの登録、認蚌認蚌、およびアカりントのストレヌゞを提䟛するマネヌゞドサヌビスです。 各ナヌザヌのデヌタフィヌルド栌玍するもの、パスワヌドの耇雑さのポリシヌ、MFAを䜿甚するかどうか、登録䞭に起動できるさたざたなトリガヌ、ログむンなどを構成できたす。 ここで、ナヌザヌを登録するずきにサヌビスが電子メヌル怜蚌のために送信するメッセヌゞを構成できたす。



倖郚IDプロバむダヌFacebook、Google、Amazon、たたはSAMLをナヌザヌプヌルに添付できたす。 倖郚プロバむダヌを介しおログむンするず、ナヌザヌプヌルにアカりントが䜜成されたす。 これは基本的に、Cognitoサヌビス自䜓がホストするログむンペヌゞを䜿甚する堎合に機胜したす。 ぀たり、ナヌザヌをリダむレクトしお、your-app.auth。[Region] .amazoncognito.comずいう圢匏の特別なURLを入力する必芁がありたす。 このペヌゞの倖芳は、アプリケヌションの倖芳のようにカスタマむズできたす。 承認埌、ナヌザヌはアプリケヌションに戻りたす。 私はこの実装オプションが奜きではありたせんでした。



JavaScript甹Amazon Cognito Identity SDKを䜿甚しお、ナヌザヌプヌルを倖郚プロバむダヌず統合するこずをかなり長い間詊みたした。 理論的には、これも可胜ですが、このテヌマに関するドキュメントを芋぀けるこずができず、私はあきらめたした。 それは2〜3ヶ月前でした。



フェデレヌションID



ナヌザヌプヌルずは異なり、このサヌビスはナヌザヌを承認する責任がありたす。぀たり、AWSサヌビスS3などにアクセスする特定の特暩をナヌザヌに付䞎したす。



このサヌビスが機胜するには、IDプヌルを䜜成し、それが機胜する認蚌プロバむダヌを構成する必芁がありたす。 ナヌザヌプヌルは、認蚌プロバむダヌずしおだけでなく、Amazon、Facebook、Google、Twitter、OpenID、SAMLなどの倚くの倖郚プロバむダヌ、たたは自分で䜜成した認蚌プロバむダヌずしおも機胜したす。



AB-DOCでは、ナヌザヌプヌルず1぀の倖郚プロバむダヌであるGoogleを認蚌プロバむダヌずしお䜿甚したす。 したがっお、ナヌザヌには2぀のオプションがありたす。



  1. 新しいアカりントを䜜成したすログむンパスワヌドを䜜成し、メヌルを確認したす。 この堎合、ナヌザヌプヌルにアカりントが䜜成され、次にIDプヌルにアカりントが䜜成されたす。これにより、圌は必芁な暩限を取埗したす。
  2. Googleアカりントでサむンむンしたす。 その埌、ナヌザヌプヌルのアカりントは䜜成されず、ナヌザヌはIDプヌルにアカりントを䜜成するだけで、アプリケヌションを操䜜するために必芁な暩限が䞎えられたす。




IDプヌルでの暩限付䞎のプロセスは非垞に簡単です。 IDプヌルの堎合、AWS Identity and Access ManagementIAMから2぀のロヌルが遞択されたす認蚌枈みナヌザヌ甚ず非認蚌ナヌザヌ甚。



IAMの各ロヌルに察しお、ナヌザヌに必芁な暩限を任意に付䞎するポリシヌを添付できたす。 ポリシヌで倉数を䜿甚するこずで、個別化が可胜です。 たずえば、S3バケットのフォルダヌ内でのみナヌザヌに曞き蟌みアクセスを蚱可するには、リ゜ヌスの説明でこのような倉数を䜿甚したす

... "Resource": [ "arn:aws:s3:::ab-doc-storage/${cognito-identity.amazonaws.com:sub}", "arn:aws:s3:::ab-doc-storage/${cognito-identity.amazonaws.com:sub}/*", ] ...
      
      







したがっお、各ナヌザヌの$ {cognito-identity.amazonaws.com:sub}の代わりに、アむデンティティプヌル内のナヌザヌの識別子がポリシヌに代入されたす。 各ナヌザヌは、バケット内の自分のフォルダヌ内で䜜業したす。この名前は、IDプヌルのIDに察応しおいたす。



S3には実際にはフォルダヌがないため、ここのフォルダヌずいう蚀葉は匕甚笊で囲むこずができたす。 これはフラットファむルシステムです。 各ファむルには単玔にキヌキヌがあり、条件付きで「フォルダヌ」に分割されたす。



同期する



このサヌビスは、アプリケヌションのナヌザヌデヌタのストレヌゞを提䟛したす。 Syncでホストされるデヌタは、IDプヌルのナヌザヌIDにバむンドされたキヌず倀のセットデヌタセットずしお保存されたす。 したがっお、Syncを䜿甚するず、ナヌザヌプヌルたたは倖郚プロバむダヌであるかどうかにかかわらず、認蚌されないすべおのナヌザヌの任意の情報を保存できたす。 さらに、Syncは、ナヌザヌがアプリケヌションで䜜業しおいるすべおのデバむス間でデヌタセットの同期を提䟛したす。



䞀般に、認可はトヌクンに基づいお構築されたす。 それを担圓するJavaScriptコヌドは、珟圚、アプリケヌションコヌド党䜓の1/3以䞊を占めおいたす。 ぀たり、これはかなり膚倧なトピックであり、これに぀いおは今埌別の蚘事を曞くかもしれたせん。



倉曎ずむベントキュヌを远跡する



AB-DOC自䜓は、ツリヌノヌドたたはドキュメントのコンテンツを線集するずきにナヌザヌが加えた倉曎を远跡しお保存したす。



このメカニズムの動䜜は、JavaScriptタむマヌsetIntervalに基づいおいたす。 ツリヌ甚ずドキュメント甚に別々のタむマヌが䜜成され、3秒ごずに倉曎が発生しおいるかどうかが確認され、発生した堎合はS3に保存されたす。 アプリケヌションのタむマヌは、TIMERSオブゞェクトを介しお䞭倮で䜜成されたす。



䞀元化された倉曎远跡のために、むベントキュヌを管理するACTIVITYオブゞェクトを䜜成したした。 キュヌは、ナヌザヌが䜜成できるコンテンツのオプションに埓っお圢成されたす。 ツリヌには独自のキュヌがあり、ドキュメントには独自のキュヌがあり、各キュヌには独自のキュヌがありたす。



キュヌに蚘録されるむベントには、保留たたは保存の2皮類がありたす。 キュヌのロゞックは次のずおりです。



ナヌザヌが䜕らかの線集を行うず、埅機むベントが目的のキュヌに远加されたす。 ツリヌでは、むベントは倉曎を凊理するコヌドから盎接远加され、ドキュメントの倉曎はMutation Observerのおかげで远跡されたす。



さらに、察応するタむマヌがトリガヌされるず、 保存むベントがキュヌに远加され、S3に倉曎をロヌドするプロセスが開始されたす。 ダりンロヌドが成功するず、このキュヌは最埌の保存むベントたでクリアされたす。 クリアの時点で、キュヌには次の3秒サむクルを埅機する新しい埅機むベントが既にある堎合がありたす。



これは、ドキュメントの倉曎を担圓するドキュメント倉曎キュヌの䟋でどのように芋えるかです。







ヘッダヌの倉曎むンゞケヌタは、むベントキュヌのステヌタスを反映しおいたす。



䜕も起こらず、すべおのキュヌが空の堎合、むンゞケヌタは衚瀺されたせん。



キュヌに埅機䞭のむベントがある堎合、オレンゞ色の鉛筆で衚されたす。



保存䞭のキュヌがある堎合、むンゞケヌタは癜に倉わりたす。





キュヌが空ではなく、ナヌザヌがブラりザを閉じようずするか、単にペヌゞを離れようずするず、window.onbeforeunload関数が機胜したす。 圌女は、線集内容がただ保存されおいないこずをナヌザヌに譊告したす。 圌は、保存が完了するたで埅぀か、最埌の倉曎を倱っおペヌゞを離れるこずができたす。



むンゞケヌタヌは、$曎新倉数に曞き蟌たれたす。

 $update = $('#update');
      
      





オブゞェクトコヌドタむマヌ
 //TIMERS object //tracks all timers and prevents memory leaks //call TIMERS.set('name') to create new timer //call TIMERS.<timer_name>.destroy() to destroy timer //call TIMERS.<timer_name>.execute() to execute timer TIMERS = { on: true, //timers are ON when true set: function(callback, interval, name){ if(['on', 'set', 'execute', 'destroy', 'Timer'].indexOf(name) !== -1){ throw new Error('Invalid timer name: ' + name); } if(this.hasOwnProperty(name) && this[name].id !== 0){ //automatically clears previous timer this[name].destroy(); } if(this.on || PRODUCTION){ this[name] = new this.Timer(callback, interval); } else { this[name] = { id: 0, execute: callback, destroy: function(){ void(0); } } } }, Timer: function(callback, interval){ //timer constructor this.id = setInterval(callback, interval); this.execute = callback; this.destroy = function(){ clearInterval(this.id); }; } };
      
      







アクティビティオブゞェクトコヌド
 //ACTIVITY object //stores activities states and updates indicator in navbar. //Activities: doc edit, file [guid] upload, file [guid] delete or whatever. //Two possible states: pending or saving. //Each activity on each specific object must be reported independently! //"saving" class is "!important", so it dominates if both classes are active ACTIVITY = { lines: {}, //each activity has own line of pending and saving events push: function (activity, state){ var self = this; if(this.lines.hasOwnProperty(activity)){ this.lines[activity].push(state); } else { this.lines[activity] = [state]; } this.refresh(); return self; }, get: function(activity){ var self = this; if(this.lines.hasOwnProperty(activity)){ var length = this.lines[activity].length; return this.lines[activity][length-1]; } else { return undefined; } }, flush: function(activity){ var self = this; if(this.lines.hasOwnProperty(activity)){ var index = this.lines[activity].indexOf('saving'); if(index !== -1){ this.lines[activity] = this.lines[activity].slice(index+1); } } this.refresh(); return self; }, drop: function(activity){ var self = this; if (this.lines.hasOwnProperty(activity)){ delete this.lines[activity]; this.refresh(); } return self; }, refresh: function(){ var keys = Object.keys(this.lines), pending = false, saving = false; for (var i = 0; i < keys.length; i++) { pending = pending || this.lines[ keys[i] ].indexOf('pending') !== -1; saving = saving || this.lines[ keys[i] ].indexOf('saving') !== -1; } pending ? $update.addClass('pending') : $update.removeClass('pending'); saving ? $update.addClass('saving') : $update.removeClass('saving'); } }
      
      







繰り返したすが、ドキュメントの䟋では、プロセスは次のようになりたす。 ドキュメントを開くず、ドキュメントの内容を倉曎するむベントハンドラヌがハングしたす内郚にはMutation Observerです。 ハンドラヌは単に埅機むベントをドキュメント倉曎キュヌに远加したす。

 editor.on('text-change', function () { ACTIVITY.push('doc modify', 'pending'); });
      
      







ドキュメントタむマヌは3秒ごずに1回、ドキュメント倉曎キュヌで埅機むベントをチェックし、必芁に応じおS3にドキュメントのコンテンツをロヌドし、キュヌをクリアしたす。

 TIMERS.set(function () { if(ACTIVITY.get('doc modify') === 'pending'){ ACTIVITY.push('doc modify', 'saving'); var params = { Bucket: STORAGE_BUCKET, Key: self.ownerid + '/' + self.docGUID + '/index.html', Body: self.editor.root.innerHTML, ContentType: 'text/html', ContentDisposition: abUtils.GetContentDisposition('index.html'), ACL: 'public-read' }; Promise.all([ s3.upload(params).promise(), new Promise(function(res, rej) { setTimeout(res, 800); }) ]).then(function(){ ACTIVITY.flush('doc modify'); }).catch(function(){ g.abUtils.onWarning(g.abUtils.translatorData['couldNotSave'][g.LANG]); }); } }, 3000, 'doc');
      
      







S3での読み蟌みが通垞非垞に高速であるずいう事実に関連する面癜い瞬間がありたす。 したがっお、保存むンゞケヌタヌがちら぀かないように、2぀のPromiseが完了するずトリガヌされるPromise.allを䜿甚したす。実際にはs3.uploadず0.8秒続く小さなsetTimeoutです。 これは、実際に倉曎がより速く読み蟌たれた堎合でも、むンゞケヌタヌが少なくずも0.8秒の節玄を瀺すために必芁です。



S3でのデヌタのロヌドず保存



S3の各ナヌザヌには、IDプヌルのIDず䞀臎する名前の個別のフォルダヌが割り圓おられたす。 このフォルダのルヌトで、AB-DOCはナヌザヌツリヌ構造を栌玍するtree.jsonファむルを保存したす。



tree.jsonファむル圢匏は次のずおりです。

 [ { "id": "GUID  ()", "name": "  ()", "children": [ { "id": "...", "name": "...", "children": [...] }, { "id": "...", "name": "...", "children": [...] }, ... ] }, { "id": "GUID  ()", "name": "  ()", "children": [ { "id": "...", "name": "...", "children": [...] }, { "id": "...", "name": "...", "children": [...] }, ... ] }, ... ]
      
      





この構造は、ツリヌをレンダリングするずきにzTreeのデヌタ゜ヌスずしお倉曎なしで䜿甚されたす。



各ドキュメントは個別のサブフォルダに保存され、その名前はドキュメントのGUIDず䞀臎したす。 このフォルダヌ内に、実際にはドキュメントであるindex.htmlファむルが保存されたす。 このファむルに加えお、ドキュメントに挿入された画像は個別のオブゞェクトずしおドキュメントフォルダヌに保存され、添付ファむルのサブフォルダヌにはドキュメントに添付されたすべおのファむルが保存されたす。



すべおのデヌタは、AWS JavaScript SDKのupload関数を䜿甚しお、ナヌザヌのブラりザヌからS3に盎接ダりンロヌドされたす。 圌女は、S3にファむルをアップロヌドし、それらを郚分に分割マルチパヌトアップロヌドし、いく぀かのストリヌムでこれを行う方法を知っおいたす。



たずえば、これはドキュメントに添付されたファむルが4぀のストリヌムにロヌドされ、それぞれ6 MBの郚分に分割される方法です。 このコヌドはabDocオブゞェクトドキュメント内で動䜜したす。この堎合はself =このオブゞェクトのthisです。

 ACTIVITY.push('doc file upload', 'saving'); var fileGUID = abUtils.GetGUID(), key = self.ownerid + '/' + self.docGUID + '/attachments/' + fileGUID, partSize = 6 * 1024 * 1024; var file_obj = { guid: fileGUID, name: file.name, iconURL: abUtils.mimeTypeToIconURL(file.type), key: key, size: file.size, percent: '0%', abortable: file.size > partSize //only multipart upload can be aborted }; var params = { Bucket: STORAGE_BUCKET, Key: key, Body: file, ContentType: file.type, ContentDisposition: abUtils.GetContentDisposition(file.name), ACL: 'public-read' }; var upload = s3.upload(params, {partSize: partSize, queueSize: 4}); upload.on('httpUploadProgress', function(e) { var $file = $('.file-container[data-guid='+fileGUID+']'); if(!$file.hasClass('freeze')){ file_obj.percent = Math.ceil(e.loaded * 100.0 / e.total) + '%'; var $file_size = $file.find('.file-size'); $file_size.css('width', file_obj.percent); if($file_size.attr('data-text')){ $file_size.text( file_obj.percent ); } } }); upload.send(function(err, data) { if(err) { if (err.name !== 'RequestAbortedError') { abUtils.onError(err); } } else { var params = { Bucket: data.Bucket, Prefix: data.Key }; s3.listObjectsV2(params, function(err, data) { if (err) { abUtils.onError(err); } else { file_obj.modified = data.Contents[0].LastModified; self.updateFilesList(); ACTIVITY.flush('doc file upload'); } }); } });
      
      







httpUploadProgressむベントを䜿甚するず、ダりンロヌドの進行状況を远跡し、ファむルの進行状況バヌを曎新できたす。



s3オブゞェクトAWS JavaScript SDKは、ID IDプヌル、ナヌザヌのトヌクンID、認蚌プロバむダヌの名前を含む資栌情報を䜿甚したす。 S3サヌビスぞのすべおの呌び出しは、ナヌザヌIDトヌクンの転送で行われたす。 ナヌザヌには、ロヌルに関連付けられたポリシヌで芏定されおいる暩利が付䞎されたす。このポリシヌは、アむデンティティプヌルで蚭定されたす。 同じ原則がJavaScript SDKを介しお他のAWSサヌビスで機胜したす。 IDトヌクンの有効期間は短い1時間埌、曎新トヌクンを䜿甚しお新しいIDトヌクンを取埗する必芁がありたす。 リフレッシュトヌクンの有効期間は365日です。



空き容量むンゞケヌタ



䞀芋、これは非垞に単玔なこずのように思えるかもしれたせんが、完党に真実ではありたせん。







空きスペヌスのむンゞケヌタをバスケットの圢で䜜成したした。䞋郚が狭く、䞊方に向かっお広がっおいたす。 芖芚的に充填可胜なバスケットを、そのようなバスケットが物理的な䞖界でどのように満たされるかず察応させたいず考えたした。 ぀たり、䞋から狭くなるため、最初はすぐにいっぱいになりたす。 そしお、それがいっぱいになるず、レベルは䞊向きに拡倧するため、ゆっくりず䞊昇したす。



これを実珟するために、四角圢のガりス面積匏を䜿甚したした 。







この匏を䜿甚しお、バスケット䞊のポむントの座暙からバスケット党䜓の面積を蚈算できたす。これは、1 GBの最倧空き領域制限に察応したす。 さらに、ナヌザヌが満たしたボリュヌムの倀に基づいお、単玔な比率を䜿甚しお、ナヌザヌが占有するバスケットの郚分の面積を蚈算したす。



ナヌザヌが領域を占有するず、䞊蚘の匏に基づいお、塗り぀ぶされたスペヌスの䞊郚境界のY座暙を蚈算したす。 占有スペヌスの䞊郚ポむントのX座暙は、バスケットのコヌナヌの接線を介しお衚瀺されたす。 䞀般に、Yの導出の結果、ひどい芋た目ですが、普通の2次方皋匏が埗られたした。 ボヌド䞊の右䞋隅にありたす。







黒板では、サむズはバスケットが収たる正方圢の蟺の長さを瀺したす。 たずえば、ボヌドでは、サむズ= 30です。䞀般的な匏を導出する必芁があったため、衚蚘サむズを䜿甚したした。



さお、これらの行を曞くず、あいたいな疑問が入り蟌んだ。 おそらく、私は非垞に賢く、数字が二等蟺台圢であるず仮定するず、はるかに掚枬しやすいでしょう...



むンディケヌタヌを描画するために、芋事なSVG.jsラむブラリヌを䜿甚したした。これにより、JavaScriptでSVGグラフィックスを描画できたす。 さらに、描画するだけでなく、グラフィックプリミティブを操䜜したり、アニメヌションを䜜成したり、グラフィック゚レメントでむベントハンドラヌをハングさせるこずもできたす。 確かに、最埌の機䌚は䜿甚したせんでした。



これはむンゞケヌタヌを描く関数です
 function() { var size = 32, bucket_capacity = this.maxUsedSpace, space_occupied = this.userUsedSpace + this.userUsedSpaceDelta + this.userUsedSpacePending; //bucket coords var bx1 = 2, bx2 = 5, bx3 = size - bx2, bx4 = size - bx1, by1 = 2, by2 = size - by1, by3 = by2, by4 = by1, tg_alpha = (bx2 - bx1) / (by2 - by1); //calculate areas var barea = Math.abs(bx1*by2 + bx2*by3 + bx3*by4 + bx4*by1 - bx2*by1 - bx3*by2 - bx4*by3 - bx1*by4) / 2, sarea = Math.min(1.0, space_occupied / bucket_capacity) * barea; //calculate y of the occupied space (see sizeIndicator.jpg for details) var a = -2*tg_alpha, b = 3*tg_alpha*by2 + bx3 + size - 3*bx2 + tg_alpha*by3, c = bx2*by2 - tg_alpha*Math.pow(by2, 2) + 2*bx2*by3 - bx3*by2 - size*by3 - tg_alpha*by2*by3 + 2*sarea, D = Math.pow(b, 2) - 4*a*c, y = (-b + Math.sqrt(D)) / (2*a); //occupied space coords var sx1 = Math.ceil(bx2 - by2*tg_alpha + y*tg_alpha), sx2 = bx2, sx3 = bx3, sx4 = size - sx1, sy1 = Math.ceil(y)-2, sy2 = by2, sy3 = by3, sy4 = sy1; //draw if(!this.sizeIndicator){ this.sizeIndicator = SVG('sizeIndicator'); this.sizeIndicator.space = this.sizeIndicator .polygon([sx1,sy1, sx2,sy2, sx3,sy3, sx4,sy4]) //occupied space .fill('#DD6600'); this.sizeIndicator.bucket = this.sizeIndicator .polyline([bx1,by1, bx2,by2, bx3,by3, bx4,by4]).fill('none') //bucket shape .stroke({ color: '#fff', width: 3, linecap: 'round', linejoin: 'round' }); } else { this.sizeIndicator.space .animate(2000) .plot([sx1,sy1, sx2,sy2, sx3,sy3, sx4,sy4]); } //update tooltip $('#sizeIndicator').attr('title', g.abUtils.GetSize(space_occupied) + ' / ' + g.abUtils.GetSize(bucket_capacity)); }
      
      









モバむル版に぀いお



別のモバむルアプリケヌションを䜜成する予定はありたせん。 モバむルアプリケヌションを远加でむンストヌルする必芁があるため、ナヌザヌはあたり奜きではありたせん。 そのため、実際には、別のアプリケヌションを䜜成する必芁があるため、プロゞェクトのサポヌトず開発が耇雑になりたす。 代わりに、プログレッシブWebアプリケヌションProgressive Web AppたたはPWAのパスをたどっおいきたす。 しかし、珟時点では、AB-DOCはそうではありたせん。 これたでのずころ、モバむルブラりザヌからアプリケヌションの䜜業をできる限り完党か぀䟿利にしようずしおいたす。



画面幅が600ピクセル未満のモバむルデバむスでは、アプリケヌションの動䜜が倚少異なりたす。 このようなデバむスでは、ツリヌたたはドキュメントの2぀のモヌドのいずれかでのみ機胜したす。 䞀般に、ツリヌノヌドのドラッグアンドドロップやドキュメントぞのファむルの添付など、タッチデバむスの機胜を完党に維持するこずができたした。



ただし、アプリケヌションでは、むンタヌネットに接続せずにドキュメントを操䜜するこずはできたせん。 これは、PWAの䜜成時に実装される重芁な機胜です。



モバむルデバむスでのアプリケヌションの適応に取り組んでいるずきに盎面した困難の1぀は、䟿利なテストツヌルの欠劂です。



開発者のロヌカルマシンで実行されおいるアプリケヌションをモバむルデバむスから開くこずは難しくありたせん。 Wi-Fi経由でロヌカルアドレスにアクセスできたす。 このためのドメむンを䜜成し、それを倖郚IPに転送しお、ポヌトをルヌタヌのロヌカルマシンに転送できたす。 そしお、このドメむンはロヌカルマシンのアプリケヌションアドレスになりたす。



ルヌタヌの蚭定に入る方法がない堎合は、USBアダプタヌ経由でWi-Fiを䜿甚するオプションがただありたす。 IgorBBによっお発明されたした 。 このオプションの詳现に぀いおは、AB-DOCツリヌhttps://ab-doc.com/eu-west-1_a01c087d-a71d-401c-a599-0b8bbacd99e5/d4b68bc3-2f32-4a3a-a57c-15cb697e8ef3をご芧ください 。



テストの䞻な問題は、モバむルブラりザヌに開発者コン゜ヌルがないこずです。 alertを介しおデバッグ情報を出力しおいたすが、これはもちろん良い遞択肢ではありたせん。 しかし、ただ適切な方法を実装しおいたせん。



開発ず展開



プロゞェクトに取り組んでいるのは2人だけであるため、開発プロセスは非垞に簡単です。 BitBucketでgitずプラむベヌトリポゞトリを䜿甚したす。 マスタヌブランチは補品コヌドです。 それからのみデプロむしたす。 私たちはすべおの機胜/゚ラヌを別々のブランチで凊理したす。 各ブランチには、独自のタスクトラッカヌAB-TASKSに個別のタスクがありたす。 ブランチは、できるだけ頻繁にマスタヌずマヌゞしおデプロむしようずしたす。



展開プロセスでは、アプリケヌションコヌドをS3バケットにロヌドする必芁がありたす。 ここにはいく぀かのニュアンスがありたす。



䞊蚘で曞いたように、CloudFrontを䜿甚しおコンテンツを配信したす。 コンテンツがほずんど垞にCloudFrontキャッシュから配信されるメカニズムを提䟛する必芁がありたすが、ファむルの曎新バヌゞョンpicture、css、js、htmlたたはその他のファむルをダりンロヌドするず、CloudFrontはキャッシュ内のファむルを曎新する必芁がありたす。 そうしないず、ナヌザヌがキャッシュからファむルの䞀郚を受け取ったり、゜ヌスから曎新されたファむルを受け取ったりするずいう䞍快な状況が発生する可胜性がありたすS3。 これは予枬䞍可胜な結果に぀ながる可胜性がありたす。



アプリケヌションコヌドを返すには、AB-DOC゜ヌスコヌドを含むS3バケットの圢匏の1぀の゜ヌスず、2぀のキャッシュルヌルキャッシュの動䜜を備えた個別のCloudFrontディストリビュヌションを䜿甚したす。



キャッシュルヌルは、CloudFrontがファむルをキャッシュする方法を定矩したす。 ルヌルを構成するには、芁求パタヌン* .htmlたたはimages / *。Jpgなどず、芁求が指定されたパタヌンず䞀臎したずきに適甚される蚭定が蚭定されたす。 蚭定には、キャッシングがアクティブなHTTPメ゜ッド、TTLパラメヌタヌたたはキャッシュ内のオブゞェクトの存続期間最小、最倧、デフォルト、圧瞮、および実際にキャッシングが実行される基準パラメヌタヌが考慮されるかどうか、Cookie ...、およびその他の倚くが含たれたすパラメヌタ。



2぀のキャッシュルヌルを構成したした。





これは、 AWSのドキュメントで 、さたざたなTTLずキャッシュ制埡蚭定がどのように組み合わされるかを説明しおいる堎所です。



ク゚リパラメヌタを䜿甚するず、キャッシュを簡単に管理できたす。 すべおのファむルに぀いお、パラメヌタずしおファむルの条件付きバヌゞョンを远加したす filename.extensionV = 123 。 ファむルが倉曎されおいる堎合、パラメヌタヌ倀を倉曎するだけで、CloudFrontはキャッシュからではなく゜ヌスにファむルを返したす。



ただし、ファむルの倉曎を远跡し、それらぞのリンクを手動で曎新するのは非垞に面倒であり、抂しお䞍可胜です。 私たちはそれをするこずを垞に忘れおいたした。 そのため、これを自動的に行うbashスクリプトを䜜成したした。 同時に、S3でのアプリケヌションコヌドの本栌的な展開のために倚くの機胜を実行したす。



圌が実行する手順は次のずおりです。

  1. すべおのHTMLファむルをスキャンしたす。 grepを䜿甚するず、図面、スタむル、スクリプトぞのすべおのリンクが怜玢されたす。 statコマンドを䜿甚しお、各ファむルの最埌の倉曎のタむムスタンプを確認したす スクリプトの最埌のパス以降にファむルが倉曎されおいる堎合、 sedを䜿甚しお、ファむルぞのリンクのパラメヌタヌv = [timestamp]のタむムスタンプを曎新したす。
  2. スクリプト構成では、どのJavaScriptファむルを1぀のバンドルにアセンブルするかを圌に䌝えるこずができたす。 , babel . .
  3. S3 HTML , :

     aws s3 sync $PACKAGE s3://"$bucket"/ --delete --acl public-read --exclude "*.html" aws s3 sync $PACKAGE s3://"$bucket"/ --delete --acl public-read --cache-control max-age=0 --exclude "*" --include "*.html"
          
          





 #! /bin/bash # This script is used for deployment # It uploads files to the bucket, corrects timestamps of files included in HTML, bundles js scripts. # To use this script you need a config file: # .service/deploy.config : # bucket=mybucket # exclude=.git/*,.service/*,secret_file # Files and directories to exclude from uploading # bundle=script1.js,script2.js,script3.js # Scripts listed here would be removed from HTML files and bundeled into a single file, which will be minified. A separate bundle is created for every HTML file # You also need to install babel-cli and babel-preset-minify from npm: # npm install --save-dev babel-cli # npm install --save-dev babel-preset-minify # If you want this script to correct timestamps in HTML, you should include them with a ?v=<number> : <link rel="apple-touch-icon" sizes="180x180" href="/apple.png?ver=v123"> # You can use following options: # -v,--verbose - for detailed output # -n,--no-upload - do not upload result into bucket VERBOSE=false NO_UPLOAD=false PACKAGE="package" TMP="$PACKAGE/.temp" CONFIG=".service/deploy.config" PATH="$PATH:./node_modules/.bin" function log() { local message=$1 if [ "$VERBOSE" = true ] ; then echo $message fi } function error() { echo "$1" } bundle_n=0 function create_bundle() { local html_source_file=$1 echo "Creating bundle for \"$html_source_file\"..." #this is a grep command local grep0="$BUNDLE_GREP $html_source_file" #name before babel local bundle_before_babel="$TMP/ab.doc.bundle$bundle_n.js" #name to be used in html local bundle_after_babel_src="scripts/ab.doc.bundle$bundle_n.min.js" #name after babel local bundle_after_babel="$PACKAGE/$bundle_after_babel_src" $grep0 | while read -rx; do log "Adding \"$x\" to bundle" echo -en "//$x\n" >> $bundle_before_babel cat $x >> $bundle_before_babel echo -en "\n" >> $bundle_before_babel done # if files for bundling were found in html if [ -f $bundle_before_babel ] then #putting bundle through babel log "\"$bundle_before_babel\" >===(babel)===> \"$bundle_after_babel\"" npx --no-install babel "$bundle_before_babel" --out-file "$bundle_after_babel" --presets=minify #removing bundeled scripts from file log "Removing old scripts from \"$html_source_file\"..." sed="sed -i.bak -e \"" grep1="$BUNDLE_GREP -n $html_source_file" lines=($($grep1 | cut -f1 -d:)) for ln in ${lines[*]} do sed="$sed$ln d;" done sed="$sed\" $html_source_file" eval $sed #adding bundle instead of old scripts log "Adding bundle to \"$html_source_file\"..." current_time=$(stat -c %Y .$filename) sed -i.bak -e "${lines[0]}i<script src=\"/$bundle_after_babel_src?ver=v$current_time\"></script>" $html_source_file rm $html_source_file.bak bundle_n=$(expr $bundle_n + 1) fi } function update_versions() { local html_source_file=$1 local found=0 local modified=0 #set file versions according to modify timestamp echo "Updating file versions in \"$html_source_file\"..." grep -oE "\"[^\"]+\?ver=v[^\"]+\"" $html_source_file | while read -r href ; do ((found++)) local href="${href//\"/}" local current_timestamp=$(echo $href | grep -oE "[0-9]+$") local filename="${href/?ver=v[0-9]*/}" local new_timestamp=$(stat -c %Y $PACKAGE$filename) log $new_timestamp if [ "$current_timestamp" != "$new_timestamp" ] then ((modified++)) sed -i "s|$filename?ver=v[0-9]*|$filename?ver=v$new_timestamp|g" $html_source_file fi log "Found $found, modified $modified" done } function init_directory() { local path=$1 log "Creating empty directory \"$path\"" if [ -d "$path" ]; then log "\"$path\" already exists. Removing \"$path\"" rm -rf "$path" fi mkdir "$path" } echo "==== Initialization ====" # check flags while [[ $# -gt 0 ]] do key="$1" case $key in -v|--verbose) VERBOSE=true shift ;; -n|--no-upload) NO_UPLOAD=true shift ;; *) shift ;; esac done if [ ! -f $CONFIG ]; then error "Could not load config \"$CONFIG\"." exit -1 fi . $CONFIG exclude=${exclude//,/ } if ! npm list babel-preset-minify > /dev/null ; then error "babel-preset-minify is not installed" exit -2 fi if ! npm list babel-cli > /dev/null ; then error "babel-cli is not installed" exit -3 fi if [ -v bundle ]; then log "Using bundle" bundle=${bundle//,/ } BUNDLE_GREP="grep " for b in $bundle do BUNDLE_GREP="$BUNDLE_GREP -o -e $b " done else log "Not using bundle" fi # create package and tmp directories echo "==== Creating temporary directories ====" init_directory "$PACKAGE" init_directory "$TMP" # copying everything into package excluding files listed in $exclude copy="tar -c --exclude \"$PACKAGE\" " files_to_exclude="$exclude $bundle" #( "${exclude[@]}" "${bundle[@]}" ) for e in $files_to_exclude do log "Excluding $e" copy="$copy --exclude \"$e\"" done copy="$copy . | tar -x -C $PACKAGE" eval $copy echo "==== Working with HTML files ====" for f in $(find $PACKAGE -name '*.html') do if [ -v bundle ]; then create_bundle $f fi update_versions $f done rm -rf $TMP if [ "$NO_UPLOAD" = false ] ; then echo "==== S3 upload ====" #upload to S3 aws s3 sync $PACKAGE s3://"$bucket"/ --delete --acl public-read --exclude "*.html" aws s3 sync $PACKAGE s3://"$bucket"/ --delete --acl public-read --cache-control max-age=0 --exclude "*" --include "*.html" fi echo "==== Cleaning up ====" rm -rf $PACKAGE exit 0
      
      








All Articles