<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>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 8. redis 實現自動輸入完成 #### 1. 介紹 當我們在京東商城的搜索框,輸入想要搜索的內容,比如你想要搜索"熱水瓶",剛輸入一個"熱"字,就會出現一個下拉框,列出了很多以"熱"字開頭的可供選擇的條目,比如"熱水器"、"熱水袋"、”熱水瓶"等,如下圖所示: ![](https://box.kancloud.cn/75b59b1c35bfe2d763211a5af11c88b8_674x474.png) 這種技術就叫做自動輸入完成,當輸入想要搜索的首字符或其中被包含的字符時,就會出現可供選擇的條目,用戶可以選擇其中的條目來完成此次搜索,避免了用戶輸入全部的字符,改善了用戶體驗。這種技術很常用,比如在一個社交網站添加聯系人時,就是可以通過輸入來聯想匹配到聯系人的。 #### 2. 功能分析 要實現這樣的功能,需要前端和后端一起配合,前端部分需要監聽搜入框內容的改變,當內容改變時把內容作為參數傳遞給服務器,服務器再請求后端的數據庫,再把數據返回給客戶端,前端只要把結果渲染出來即可。 這個數據庫我們選擇的是內存數據庫redis,而不用存儲在磁盤的關系型數據庫,畢竟這個功能對實時性要求比較高,頻繁地請求數據庫肯定是用性能高和速度快的redis好。 我們有一個實例網站,是存放文章的,每篇文章都有自己的標簽(tag),整個網站是具有全文檢索功能的,也就是說,可以根據文章的標題、內容、標簽來搜索文章。現在需要在輸入框加一個自動完成的功能,當用戶在輸入框輸入標簽的頭一個或幾個字符的時候,會自動聯想到標簽。比如,有"ruby","postgresql"兩個標簽,當用戶輸入"ru"的時候,會自動出現"ruby"作為可選的項。 首先,被檢索的標簽的名稱事先存放到redis中,每個標簽都是唯一的,所以可選擇redis的集合作為數據庫,但是,有可以出現很多個以"ru"開頭的標簽,所以需要一個權重算法,那就是排序,標簽被使用得頻繁的越排在前面,所以最終是用排序集合(sortedset)來存儲標簽數據。 ``` # rails console > ActsAsTaggableOn::Tag.all [ [ 0] 消息隊列 { :id => 50, :name => "消息隊列", :taggings_count => 5 }, [ 1] ruby on rails { :id => 8, :name => "ruby on rails", :taggings_count => 17 }, [ 2] websocket { :id => 55, :name => "websocket", :taggings_count => 1 }, [ 3] database { :id => 25, :name => "database", :taggings_count => 1 } ] ``` `taggings_count`就是標簽被使用的次數,只要把`tag`的`name`按照`taggings_count`作為排序標準存到redis的`sort set`中,就可以了。下面我們會說如何去存儲這些數據。 #### 3. soulmate使用 [soulmate](https://github.com/seatgeek/soulmate)是一個結合redis實現自動輸入完成的功能強大的gem。 先安裝這個gem。 ``` $ gem install soulmate ``` soulmate要求你的數據存成一種特定格式的json,再把json導出到redis中,格式是類似這樣的。 ``` { "id": 5, "term": "devise", "score": 3, "data": { } } ``` `id`是唯一的標識,`term`就是搜索的條目,`score`就是排序的項,這三項是必須要帶上的,而`data`是可選的,里面可以存自己想要的數據,比如可把tag的描述信息加上。 很簡單,在我們的案例中,把上面的tag的`name`換成`term`,`taggings_count`換成`score`即可。 ##### 3.1 導入tags數據 我寫了一個方法可以將所有的tag導出到一個json文件。 ``` File.open("tags.json","w+") do |f| ActsAsTaggableOn::Tag.find_each do |tag| tag_json = { id: tag.id, term: tag.name, score: tag.taggings_count } f.write("#{tag_json.to_json}\n") end end ``` 輸出的tags.json類似下面這樣: ``` {"id":5,"term":"devise","score":3} {"id":6,"term":"登錄","score":3} {"id":7,"term":"認證","score":3} {"id":8,"term":"ruby on rails","score":17} {"id":9,"term":"rails","score":4} {"id":10,"term":"ruby","score":4} ``` 現在可以先這個tags.json導入到redis數據庫中,soulmate這個gem也提供了相關的命令行工具。 ``` $ soulmate load tag --redis=redis://localhost:6379/0 < tags.json ``` 你會發現redis中增加了很多的數據,我們先不管,等下再來分析那些數據。 ##### 3.2 添加單個tag到redis 每次都用這種導入的方式來在redis增加數據很不方便的,畢竟以后我們隨時要增加tag。你總不可能為了增加一個tag再導一次json吧,這不太科學。所以我們需要在增加或刪除tag的時候自動把數據添加到redis中。 通過查看soulmate的源碼,發現它是提供了相應的方法的。 ``` # https://github.com/seatgeek/soulmate/blob/master/lib/soulmate/loader.rb#L29 def add(item, opts = {}) opts = { :skip_duplicate_check => false }.merge(opts) raise ArgumentError, "Items must specify both an id and a term" unless item["id"] && item["term"] # kill any old items with this id remove("id" => item["id"]) unless opts[:skip_duplicate_check] Soulmate.redis.pipelined do # store the raw data in a separate key to reduce memory usage Soulmate.redis.hset(database, item["id"], MultiJson.encode(item)) phrase = ([item["term"]] + (item["aliases"] || [])).join(' ') prefixes_for_phrase(phrase).each do |p| Soulmate.redis.sadd(base, p) # remember this prefix in a master set Soulmate.redis.zadd("#{base}:#{p}", item["score"], item["id"]) # store the id of this term in the index end end end ``` 由于`add`方法要接一個hash作為參數,所以需要對tag這個model作一些加工。 ``` # config/initializers/tag.rb module ActsAsTaggableOn class Tag < ::ActiveRecord::Base after_create :create_soulmate def to_hash tag_json = { id: self.id, term: self.name, score: self.taggings_count } JSON.parse(tag_json.to_json ) end private def create_soulmate Soulmate::Loader.new("tag").add self.to_hash end end end ``` 我們拿其中一個tag試一下,看看它究竟生成了怎樣的redis數據(如果在開發環境,做這個之前可以用redis的flushdb指令先清除數據)。 ``` $ rails console > tag = ActsAsTaggableOn::Tag.first.to_hash { "id" => 5, "term" => "devise", "score" => 3 } > Soulmate::Loader.new("tag").add tag ``` 在redis生成了如下的數據: ``` $ redis-cli > keys * 1) "soulmate-index:tag:devise" 2) "soulmate-index:tag" 3) "soulmate-index:tag:devis" 4) "soulmate-index:tag:dev" 5) "soulmate-index:tag:de" 6) "soulmate-index:tag:devi" 7) "soulmate-data:tag" ``` 所有的key分為三大類,分別是`"soulmate-index:tag"`、`"soulmate-data:tag"`,剩下的以tag的name為devise的前綴開頭的key。結合add方法的源碼,也可以知道這三種key的類型分別為set, hash, sortedset,可以自己用redis的type命令打印出來。現在打印出這些key的值。 ``` $ redis-cli > smembers soulmate-index:tag 1) "devis" 2) "devi" 3) "dev" 4) "de" 5) "devise" > hgetall soulmate-data:tag 1) "5" 2) "{\"id\":5,\"term\":\"devise\",\"score\":3}" > zrange "soulmate-index:tag:de" 0 -1 WITHSCORES 1) "5" 2) "3" > zrange "soulmate-index:tag:dev" 0 -1 WITHSCORES 1) "5" 2) "3" ``` `soulmate-index:tag`存的是集合(set),將"devise"分成一個個前綴,以后按照這些前綴就能搜出"devise"這個tag。`soulmate-data:tag`存的是一個哈希(hash),健為標簽(tag)的id,值為tag的所有內容,就是那個標簽(ActsAsTaggableOn::Tag) model中`to_hash`方法的內容,如果有存data,它的數據也是會在這里出現。`soulmate-index:tag:de`等存的是排序后的集合,排序的健是`score`,也就是標簽(tag)的`taggings_count`,值為標簽(tag)的`id`。 ##### 3.3 測試效果 現在來實現服務端的邏輯,我們不用自己寫controller端的代碼,soulmate為我們提供好了這一切。 先安裝這個gem。 ``` gem install rack-contrib ``` 然后執行以下這個命令。 ``` $ soulmate-web --foreground --no-launch --redis=redis://localhost:6379/0 ``` 這會開啟一個服務并且監聽在5678端口。 現在我們可以用curl工具或chrome瀏覽器插件postman來測試這個服務。下面是curl工具的使用例子。 ``` $ curl -X GET http://localhost:5678/search --data "types[]=tag&term=de" {"term":"de","results":{"tag":[{"id":5,"term":"devise","score":3}]}} $ curl -X GET http://localhost:5678/search --data "types[]=tag&term=devis" {"term":"devis","results":{"tag":[{"id":5,"term":"devise","score":3}]}} ``` 使用postman請求`http://localhost:5678/search?types[]=tag&term=de`的例子如下: ![](https://box.kancloud.cn/ef55f451bd738a0ebc24e8d47296962c_834x428.png) 現在再來查看redis的數據,會發現多了兩個key。 ``` $ redis-cli > zrange "soulmate-cache:tag:de" 0 -1 WITHSCORES 1) "5" 2) "3" > zrange "soulmate-cache:tag:devis" 0 -1 WITHSCORES 1) "5" 2) "3" ``` #### 4. 頁面上的實現 我們要用soulmate配合前端在rails項目里實現自動輸入完成的功能。 先把soulmate安裝進rails項目里。 ##### 4.1 掛載soulmate服務 把下面的代碼添加到Gemfile文件,然后執行`bundle`命令。 ``` gem 'rack-contrib' gem 'soulmate', :require => 'soulmate/server' ``` 在`config/routes.rb`文件中添加一行路由。 ``` mount Soulmate::Server, :at => "/sm" ``` 在`config/initializers`目錄下添加soulmate.rb文件。 ``` Soulmate.redis = 'redis://127.0.0.1:6379/0' # or you can asign an existing instance of Redis, Redis::Namespace, etc. # Soulmate.redis = $redis ``` 重啟服務器。現在可以這樣來測試。 ``` $ curl -X GET http://localhost:3000/sm/search --data "types[]=tag&term=dev" {"term":"dev","results":{"tag":[{"id":5,"term":"devise","score":3}]}} ``` ##### 4.2 soulmate.js 至于前端部分,我們使用官方推薦的[soulmate.js](https://github.com/mcrowe/soulmate.js)這個庫。 相關的代碼是這樣的。 ``` = form_tag "/articles", method: "get", class: "navbar-form navbar-left", role: "search" .form-group input.form-control id="search" placeholder="Search" type="text" name="term" value="#{params[:search].presence}" autocomplete="off"/ ``` ``` javascript: // Make the input field autosuggest-y. $(document).ready(function() { render = function(term, data, type){ return term; } select = function(term, data, type){ console.log("Selected " + term); } $('#search').soulmate({ url: '/sm/search', types: ['tag'], renderCallback: render, selectCallback: select, minQueryLength: 2, maxResults: 5 }); }); ``` 具體的css和js效果可以自己處理。 #### 5. 源碼解析 現在有個美中不足的地方,就是必須要輸入兩個字符以上才能開始自動輸入完成。而readme文檔沒有相關的解決方法,我們只能從源碼入手。 上面的例子中,"devise"這個單詞會被分解成一個個前綴,比如"de","dev”等。我們來看下中文詞組是如何被分解的。 上面有提到那個`add`方法的源碼,其中調用了`prefixes_for_phrase`這個方法。 ``` # https://github.com/seatgeek/soulmate/blob/ead5d6c2b6d698a5c49294a73a4f7536a5013f01/lib/soulmate/helpers.rb#L3 module Soulmate module Helpers def prefixes_for_phrase(phrase) words = normalize(phrase).split(' ').reject do |w| Soulmate.stop_words.include?(w) end words.map do |w| (Soulmate.min_complete-1..(w.length-1)).map{ |l| w[0..l] } end.flatten.uniq end end end ``` 執行`rails console`進入終端來看一下這個方法是如何分解中文詞組的。 ``` $ rails console > include Soulmate::Helpers Object < BasicObject > prefixes_for_phrase("前端構建與部署工具") [ [0] "前端", [1] "前端構", [2] "前端構建", [3] "前端構建與", [4] "前端構建與部", [5] "前端構建與部署", [6] "前端構建與部署工", [7] "前端構建與部署工具" ] ``` 中文詞組還是能夠正常處理,可是至少要輸入兩個字符。通過分析`prefixes_for_phrase`方法,可以發現關鍵在于`Soulmate.min_complete`這個變量。 通過搜索源碼,發現了定義`Soulmate.min_complete`變量的地方。 ``` # https://github.com/seatgeek/soulmate/blob/ead5d6c2b6d698a5c49294a73a4f7536a5013f01/lib/soulmate/config.rb#L11 require 'uri' require 'redis' module Soulmate module Config attr_writer :min_complete def min_complete @min_complete ||= DEFAULT_MIN_COMPLETE end def redis=(server) ... end end end ``` 還記得上文"掛載soulmate服務"部分提到`config/initializers/soulmate.rb`這個文件嗎,里面就是定義了`Soulmate.redis`這個變量,那同樣的道理,也把`min_complete`定義在里面。 ``` # config/initializers/soulmate.rb Soulmate.redis = 'redis://127.0.0.1:6379/0' Soulmate.min_complete = 1 ``` 再來看結果。 ``` $ redis-cli > include Soulmate::Helpers Object < BasicObject > prefixes_for_phrase("前端構建與部署工具") [ [0] "前", [1] "前端", [2] "前端構", [3] "前端構建", [4] "前端構建與", [5] "前端構建與部", [6] "前端構建與部署", [7] "前端構建與部署工", [8] "前端構建與部署工具" ] ``` 完結。
                  <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>

                              哎呀哎呀视频在线观看