<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                --- title: 創建帖子 slug: creating-posts date: 0007/01/01 number: 7 points: 5 photoUrl: http://www.flickr.com/photos/markezell/9688179085 photoAuthor: Mark Ezell contents: 學習如何在客戶端提交一個帖子。|實現一個簡單的安全性檢查。|對帖子提交頁面進行限制訪問。|學習在服務端中添加安全性檢查的方法。 paragraphs: 60 --- 我們曾經輕松地通過控制臺去使用 `Posts.insert` 來創建帖子并插入到數據庫。但我們不可能指望用戶去打開控制臺來創建一個新的帖子吧? 所以我們需要在用戶界面上創建一些表單控件,讓用戶在我們的 App 上發布一些新的帖子。 ### 構建新帖子的提交頁面 我們首先為新帖子的提交頁面定義一個路由: ~~~js Router.configure({ layoutTemplate: 'layout', loadingTemplate: 'loading', notFoundTemplate: 'notFound', waitOn: function() { return Meteor.subscribe('posts'); } }); Router.route('/', {name: 'postsList'}); Router.route('/posts/:_id', { name: 'postPage', data: function() { return Posts.findOne(this.params._id); } }); Router.route('/submit', {name: 'postSubmit'}); Router.onBeforeAction('dataNotFound', {only: 'postPage'}); ~~~ <%= caption "lib/router.js" %> <%= highlight "15" %> ### 在頭部(Header)添加一個鏈接 定義了這條路由后,現在我們可以在頭部模板(Header)中添加一個訪問我們提交頁面的鏈接: ~~~html <template name="header"> <nav class="navbar navbar-default" role="navigation"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navigation"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="{{pathFor 'postsList'}}">Microscope</a> </div> <div class="collapse navbar-collapse" id="navigation"> <ul class="nav navbar-nav"> <li><a href="{{pathFor 'postSubmit'}}">Submit Post</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {{> loginButtons}} </ul> </div> </nav> </template> ~~~ <%= caption "client/templates/includes/header.html" %> <%= highlight "13~15" %> 設置了這個路由就意味著如果用戶瀏覽 `/submit` 的 URL 路徑, Meteor 會顯示 `postSubmit` 模板。 下面讓我們來寫這個模板吧: ~~~html <template name="postSubmit"> <form class="main form"> <div class="form-group"> <label class="control-label" for="url">URL</label> <div class="controls"> <input name="url" id="url" type="text" value="" placeholder="Your URL" class="form-control"/> </div> </div> <div class="form-group"> <label class="control-label" for="title">Title</label> <div class="controls"> <input name="title" id="title" type="text" value="" placeholder="Name your post" class="form-control"/> </div> </div> <input type="submit" value="Submit" class="btn btn-primary"/> </form> </template> ~~~ <%= caption "client/templates/posts/post_submit.html" %> 注意:這里有大量的標簽樣式,只不過都來自于 Twitter Bootstrap。只有表單元素是必不可少的,樣式的設置只是讓我們的 App 更好看一點。在瀏覽器中顯示: <%= screenshot "7-1", "帖子提交頁面" %> 這是一個簡單的表單頁面,不需要擔心它的提交事件,因為我們會通過 JavaScript 攔截表單的提交事件并更新數據。(但如果你考慮到一旦禁用了 JavaScript 的話, Meteor App 就會完全失效)。 ### 創建帖子 讓我們將一個事件處理綁定到表單的 `submit` 事件。最好使用 `submit` 事件(而不是按鈕的 `click` 事件),因為這會覆蓋所有可能的提交方式(比如敲擊回車鍵)。 ~~~js Template.postSubmit.events({ 'submit form': function(e) { e.preventDefault(); var post = { url: $(e.target).find('[name=url]').val(), title: $(e.target).find('[name=title]').val() }; post._id = Posts.insert(post); Router.go('postPage', post); } }); ~~~ <%= caption "client/templates/posts/post_submit.js" %> <%= commit "7-1", "添加一個帖子提交頁面并把鏈接放到頭部(Header)。" %> 這個函數使用 [jQuery](http://jquery.com) 去獲取我們表單字段的值,并填充到一個新的帖子對象。我們需要調用 `event` 的 `preventDefault` 方法來確保瀏覽器不會再繼續嘗試提交表單。 最后,我們要跳轉到新的帖子頁面。 `insert()` 方法把這個對象插入到數據庫并返回插入對象的 `_id` 值,路由器的 `go()` 方法將構建一個帖子頁面的 URL 提供我們訪問。 最終的結果是用戶點擊提交時,創建一個帖子,然后用戶瀏覽器將立即跳到帖子創建頁面。 ### 添加一些安全檢查 創建帖子這功能看起來都很好,但我們不想讓隨機瀏覽的游客都可以這樣做:我們希望他們必須登錄。首先可以對登出的用戶隱藏帖子創建頁面的鏈接。不過,沒有登錄的用戶仍然可以在瀏覽器控制臺中創建一個帖子,這是我們不能允許的。 值得慶幸的是數據安全已經集成在 Meteor 的集合中,只是在默認情況下它是關閉的。這樣的設置可以使你在剛開始構建 App 的時候更加輕松。 我們的 App 不再需要這些輔助了,果斷扔掉吧!我們去刪除 `insecure` 包(恢復數據安全): ~~~bash meteor remove insecure ~~~ <%= caption "Terminal 終端" %> 執行以后你會注意到,帖子的提交頁面不可用了。這是因為沒有了 `insecure` 包,從客戶端插入帖子集合已經*不再被允許了*。 我們需要給出一些明確的規則告訴 Meteor ,什么時候才能允許客戶插入帖子,否則我們只能從服務端插入。 ### 允許帖子插入 首先,為了讓我們的提交頁面再次可用,我們先展示如何允許從客戶端插入數據。事實上,我們最終還會用不同的技術去解決這個問題,但是現在,先做一些簡單的處理吧: ~~~js Posts = new Mongo.Collection('posts'); Posts.allow({ insert: function(userId, doc) { // 只允許登錄用戶添加帖子 return !! userId; } }); ~~~ <%= caption "lib/collections/posts.js" %> <%= highlight "3~8" %> <%= commit "7-2", "移除 insecure 包并允許插入帖子" %> `Posts.allow` 是告訴 Meteor:這是一些允許客戶端去修改帖子集合的條件。上面的代碼,等于說“只要客戶擁有 `userId` 就允許去插入帖子”。 這個擁有 `userId` 用戶的修改會傳遞到 `allow` 和 `deny` 的方法(如果沒有用戶登錄就返回 `null`),這個判斷通常都是準確的。因為用戶帳戶是綁定到 Meteor 核心里面的,我們可以依靠 `userId` 去判斷。 我們可以去驗證一下,注銷登錄并創建一個帖子,你在控制臺看到的應該是這樣: <%= screenshot "7-2", "Insert failed: Access denied(插入失敗:拒絕訪問)" %> 然而,我們仍然需要處理一些問題: - 登出后的用戶仍然可以訪問帖子創建頁面。 - 帖子并沒有以任何方式與用戶進行綁定(沒有在服務器上的代碼去執行這個)。 - 允許創建指向相同的 URL 的多個帖子。 讓我們來解決這些問題吧! ### 帖子創建頁面的可訪問性 讓我們首先阻止已登出的用戶看到帖子創建頁面。我們會在路由器中,通過定義一個 **路由 Hook** 。 Hook 在路由過程中進行攔截并可能改變路由器的跳轉。你可以把它當作一個保安,檢查你的憑據才能讓你通過(或者把你帶走)。 我們需要做的是檢查用戶是否登錄,如果他們沒有登錄,呈現出來的是 `accessDenied` 模板而不是 `postSubmit` 模板。 讓我們去修改 router.js 文件: ~~~js Router.configure({ layoutTemplate: 'layout', loadingTemplate: 'loading', notFoundTemplate: 'notFound', waitOn: function() { return Meteor.subscribe('posts'); } }); Router.route('/', {name: 'postsList'}); Router.route('/posts/:_id', { name: 'postPage', data: function() { return Posts.findOne(this.params._id); } }); Router.route('/submit', {name: 'postSubmit'}); var requireLogin = function() { if (! Meteor.user()) { this.render('accessDenied'); } else { this.next(); } } Router.onBeforeAction('dataNotFound', {only: 'postPage'}); Router.onBeforeAction(requireLogin, {only: 'postSubmit'}); ~~~ <%= caption "lib/router.js" %> <%= highlight "17~23,26" %> 我們還要創建拒絕訪問模板: ~~~html <template name="accessDenied"> <div class="access-denied jumbotron"> <h2>Access Denied</h2> <p>You can't get here! Please log in.</p> </div> </template> ~~~ <%= caption "client/templates/includes/access_denied.html" %> <%= commit "7-3", "當沒有登錄的時候拒絕訪問帖子創建頁面" %> 如果你不登錄去訪問 http://localhost:3000/submit/ ,你將會看到: <%= screenshot "7-3", "拒絕訪問模板" %> 路由器 Hooks 的好處是**響應式**。這意味著當用戶登錄的時候,我們不需要考慮回調或者其他類似的方法,它就可以馬上知道。當用戶狀態變為已登錄的時候,路由器的頁面模板立即從 `accessDenied` 變為 `postSubmit`,而我們無需編寫任何代碼來去控制它。 登錄,然后嘗試刷新頁面。你可能注意到,拒絕訪問的頁面會短暫地出現在帖子創建頁面。這是因為在服務器去檢測當前用戶之前,Meteor 會盡可能快的去渲染模板。 為了避免這個問題(這是一種常見的問題,你將會看到更多去處理客戶端和服務器之間錯綜復雜的延遲),我們將短暫顯示一個加載的畫面,騰出足夠時間讓我們去判斷用戶是否有權訪問。 畢竟在這之前,我們不知道用戶是否有正確的登錄憑證,而我們也不能直接顯示 `accessDenied` 或 `postSubmit` 模板。 所以修改 Hook 去使用我們的加載模板,同時判斷 `Meteor.loggingIn()` 是否為真: ~~~js //... var requireLogin = function() { if (! Meteor.user()) { if (Meteor.loggingIn()) { this.render(this.loadingTemplate); } else { this.render('accessDenied'); } } else { this.next(); } } Router.onBeforeAction('dataNotFound', {only: 'postPage'}); Router.onBeforeAction(requireLogin, {only: 'postSubmit'}); ~~~ <%= caption "lib/router.js" %> <%= highlight "5~9" %> <%= commit "7-4", "在等待登錄的時候顯示一個加載畫面" %> ### 隱藏鏈接 當他們注銷登錄之后就隱藏這個鏈接,這是最簡單的方法去防止用戶試圖訪問不被授權的頁面。我們做到這一點很簡單: ~~~html //... <ul class="nav navbar-nav"> {{#if currentUser}}<li><a href="{{pathFor 'postSubmit'}}">Submit Post</a></li>{{/if}} </ul> //... ~~~ <%= caption "client/templates/includes/header.html" %> <%= highlight "3~5" %> <%= commit "7-5", "只在登錄后才顯示創建帖子鏈接" %> `currentUser` 的 Helper 是通過 `accounts` 包提供給我們的,它相當于是 `Meteor.user()` 的調用。因為它是響應式的,鏈接將會在你登入或者登出的的時候出現或者消失。 ### Meteor 內置方法:更好的抽象和安全 我們讓沒有登錄的用戶無法訪問帖子創建頁面,并且不允許這樣的用戶通過使用控制臺去創建帖子。然而,仍有一些更多的事情我們需要考慮: - 帖子的時間戳。 - 確保擁有相同 URL 的帖子不能再次創建。 - 添加帖子的作者的詳細信息(ID 、用戶名等)。 你可能會想我們可以把這些事情放到我們的 `submit` 事件中去處理。但是實際上,我們很快就會遇到一系列的問題。 - 對于時間戳,我們并不是總需要依賴用戶計算機的時間是否正確。 - 客戶并不知道所有發布到該網站的帖子的 URL 。他們只能知道目前看到的帖子(稍后我們將看看這是如何工作),所以沒有辦法在每個客戶端中確保 URL 是否唯一的。 - 最后,雖然我們可以在客戶端添加用戶的詳細信息,但是我們不能確保其準確性,因為可能有人會使用瀏覽器控制臺去進行編輯更改。 因為這些問題,最好是保持我們的事件處理方法里面足夠簡單,如果我們所做的事情超過最基本的插入或更新數據集合,那么可以使用 Meteor 的內置方法 。 Meteor 內置方法是一種服務器端方法提供給客戶端調用。其實我們對它并不陌生--事實上在后臺, `Collection` 的 `insert`、`update` 和 `remove` 都屬于 Meteor 內置方法。下面看看我們如何自己來創建。 讓我們回到 `post_submit.js` 文件,不再是直接插入到 `Posts` 集合,我們將調用一個名為 `postInsert` 的內置方法: ~~~js Template.postSubmit.events({ 'submit form': function(e) { e.preventDefault(); var post = { url: $(e.target).find('[name=url]').val(), title: $(e.target).find('[name=title]').val() }; Meteor.call('postInsert', post, function(error, result) { // 顯示錯誤信息并退出 if (error) return alert(error.reason); Router.go('postPage', {_id: result._id}); }); } }); ~~~ <%= caption "client/templates/posts/post_submit.js" %> <%= highlight "10~16" %> `Meteor.call` 方法通過第一個參數來調用其方法。你可以為調用方法提供參數(在這種情況下,由表單數據來構建的 `post` 對象),最后加上一個回調,它將在服務器的方法完成后執行。 Meteor 方法回調總會有兩個參數,`error` 和 `result`。如果 `error` 參數由于某種原因存在的話,我們會警告用戶(使用 `return` 來終止回調)。如果正常運行的話,我們將用戶轉到剛剛創建好的帖子評論頁面。 ### 安全檢查 我們趁現在這個機會添加 [`audit-argument-checks`](http://docs.meteor.com/#/full/auditargumentchecks) 包來增加一些安全性。 這個包讓你根據預定義的模式檢查任何 JavaScript 對象。對于我們來說,我們使用它來檢查調用方法的用戶是否登陸(通過確認 `Meteor.userId()` 是否是個 `String` 字符串),然后 `postAttributes` 對象是否包含 `title` 和` url` 字符串,來保證我們不會添加任意數據到數據庫中。 首先讓我們定義 `postInsert` 方法,在我們的 `collections/post.js` 文件中。從 `posts.js` 文件中刪除 `allow()` 代碼塊,因為 Meteor 方法會繞過它們。 然后我們 `extend` `postAttributes` 對象另外三個屬性:用戶的 `_id` 和 `username`,還有帖子的 `submitted` 時間戳,在將整個數據插入數據庫之前,并返回給用戶 `_id` 值(換句話說,在 JavaScript 對象里的 原始 caller 方法) ~~~js Posts = new Mongo.Collection('posts'); Meteor.methods({ postInsert: function(postAttributes) { check(Meteor.userId(), String); check(postAttributes, { title: String, url: String }); var user = Meteor.user(); var post = _.extend(postAttributes, { userId: user._id, author: user.username, submitted: new Date() }); var postId = Posts.insert(post); return { _id: postId }; } }); ~~~ <%= caption "lib/collections/posts.js" %> <%= highlight "3~24" %> 注意的是 `_.extend()` 方法來自于 [Underscore](http://underscorejs.org) 庫,作用是將一個對象的屬性傳遞給另一個對象。 <%= commit "7-6", "使用一個內置方法來提交帖子。" %> <% note do %> ### 再見 Allow/Deny 因為 Meteor Methods 是在服務器上執行,所以 Meteor 假設它們是可信任的。這樣的話,Meteor 方法就會繞過任何 allow/deny 回調。 如果你真的想在*服務器端*每次 `insert`、`update` 或 `remove` 之前,運行一些代碼的話,我們建議你查看 [collection-hooks](https://github.com/matb33/meteor-collection-hooks) 代碼包的相關信息。 <% end %> ### 防止重復 我們在完成這個方法之前還要在添加一項檢查。如果之前的帖子擁有了同樣的 URL,我們不會再次添加這個鏈接,反而會引導用戶到已存在的帖子上。 ~~~js Meteor.methods({ postInsert: function(postAttributes) { check(this.userId, String); check(postAttributes, { title: String, url: String }); var postWithSameLink = Posts.findOne({url: postAttributes.url}); if (postWithSameLink) { return { postExists: true, _id: postWithSameLink._id } } var user = Meteor.user(); var post = _.extend(postAttributes, { userId: user._id, author: user.username, submitted: new Date() }); var postId = Posts.insert(post); return { _id: postId }; } }); ~~~ <%= caption "lib/collections/posts.js" %> <%= highlight "9~15" %> 我們在數據庫中搜尋是否存在相同的 URL。如果找到,我們 `return` 返回那帖子的 `_id` 和 `postExists: true` 來讓用戶知道這個特別的情況。 由于我們調用了一個 `return`,方法就會到此停止,而不會執行 `insert` 聲明,因此優雅地防止了任何重復。 剩下的就是用 `postExists` 信息通過我們客戶端的事件 helper 來顯示警告信息: ~~~js Template.postSubmit.events({ 'submit form': function(e) { e.preventDefault(); var post = { url: $(e.target).find('[name=url]').val(), title: $(e.target).find('[name=title]').val() }; Meteor.call('postInsert', post, function(error, result) { // 向用戶顯示錯誤信息并終止 if (error) return alert(error.reason); // 顯示結果,跳轉頁面 if (result.postExists) alert('This link has already been posted(該鏈接已經存在)'); Router.go('postPage', {_id: result._id}); }); } }); ~~~ <%= caption "client/templates/posts/post_submit.js" %> <%= highlight "15~17" %> <%= commit "7-7", "強制帖子 URL 的唯一性。" %> ### 帖子排序 現在我們已經在所有的帖子中添加了日期,確保可以使用這個屬性去進行帖子的分類。這樣,我們就可以使用 Mongo 數據庫的 `sort` 運算方法,根據這個字段去把對象進行排序,并且標識它們是升序還是降序。 ~~~js Template.postsList.helpers({ posts: function() { return Posts.find({}, {sort: {submitted: -1}}); } }); ~~~ <%= caption "client/templates/posts/posts_list.js" %> <%= highlight "3" %> <%= commit "7-8", "帖子通過時間戳進行排序。" %> 花了一點功夫,我們終于有了一個用戶界面,讓用戶安全地在我們的 App 中輸入內容! 但任何一個 App 如果允許用戶去創建內容,同時也需要給他們一個方式來編輯或刪除它。這就是下一章將會說到的。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看