Upgrade to Pro — share decks privately, control downloads, hide ads and more …

如何利用 Rails 在 21 天单枪匹马上线一个产品

8a505779fb988a5667c36c46d18ba667?s=47 yafei lee
September 26, 2016

如何利用 Rails 在 21 天单枪匹马上线一个产品

结合八十二十产品的开发,讲如何从零开发出理想产品的演进过程。
此话题涵盖了 Turborlinks、微信支付、ActionCable 等技术以及八十二十的实践经验。

8a505779fb988a5667c36c46d18ba667?s=128

yafei lee

September 26, 2016
Tweet

Transcript

  1. ই֜ڥአ Rails ࣁ 21 ॠ ܔຖ܃ḘӤᕚӞӻԾߝ ๫Եᷢ For RubyConf China

    2016 2016.09.24
  2. ๫Եᷢ Ⴎࣉ Ruby ၚۖᕟᕢᘏ ᳯ᷌ᥴ٬ᘏ, ޾Ӟӻ፥ᦻጱՈ ؉ᬦ cywin, jiaoluo, 51goda

    ᒵ, ፓڹ೮ᖅڠӱࣁ github: ruby-china: 80% @windy @lyfi2003
  3. ؉ӞӻԾߝಅᵱጱᚆێ Ծߝ௏ᖌ ದ๞ᚆێ ᦡᦇᚆێ ᬩ០ᚆێ

  4. ྯӞӻ Rails ૡᑕ૵᮷ଫᧆํӞ ӻᛔ૩ᳩ๗ᖌಷጱᶱፓ

  5. ٖ਻

  6. ᒫᵭྍ Ծߝᦎᦞ

  7. ك܈ԫ܈فݗ: https://80post.com

  8. ಚᎱ֛ḵ

  9. ֢ᘏᒒ Ӟӻ PC ᒒଫአ ٟ՞ᩇ෈ᒍଚਧհ ಋ๢ಚӞಚړՁ ஙמ๐ۓݩັᧃ

  10. ᧛ᘏᒒ ஙמଫአ ஙמٖඪ՞ଚᴅ᧛ ᦧհ, ݊PCᒒݶྍᴅ᧛ ஙמ๐ۓݩັᧃ

  11. ଘݣᒒ PC ᒒ හഝᕹᦇ ᔮᕹᓕቘ

  12. ᒫӞྍ لݪဳٙ ऒݷ॓ໜ ஙמلռݩ

  13. ᕹᓉොဩ਍ ض؉๋ݢᚆᴥल֕ݢզଚᤈጱԪఘ

  14. ض୏ত? ୮ᆐฎ "ஙמᦊᦤ" ...

  15. ஙמ๐ۓݩ ஙמᦈᴅݩ ஙמ୏නଘݣ ஙמࠟಁݩ ஙמմӱݩ ஙמੜᑕଧݩ ತӧݶ ๋ᕣ, ౯ժԻԧ 1200

    Ո࿆૰!!!
  16. ᑕଧާ ੜᑕଧާ ๋ᕣ, ᬯӻӮኴਖ਼ړԅ

  17. لݪဳٙ ᭌӻ ྲই 80percent vs 37signals( ضਧӻੜፓຽ ) ဳٙࣈஉ᯿ᥝ( ྲইႮࣉڹၹ

    ) വគತӻᶌᨏጱդېلݪ অݷਁ
  18. ऒݷ॓ໜ ੱᰁአ .com ᘒӧฎ .cn ٵ॓অ 20 ॠጱ॓ໜ෸ᳵ ኪᦾ݊෸ള

  19. ᒫԫྍ ದ๞ᭌࣳ

  20. Rails5 قਹ໲

  21. Ruby on Rails 5 turbolinks5 actioncable bootstrap 3 font-awesome figaro

    postgres slim high_voltage carriewave & upyun sidekiq kaminari mina
  22. VS Vue ୏ݎ᭛ଶள( ᫷ڹᒒଫአ ) አಁ֛ḵঅ( turbolinks5 ඪ ൔ )

    ӧտଃ๶ឭݷጱᳯ᷌( SEO, Ḓᶭے᫹᭛ଶᒵ ) ݸᒒኞா෫ᥴᙑ VS React React قਹ໲਍ԟ౮๜ṛ ᭛ଶள, ֛ḵঅ( ᫷ڹᒒଫ አ, turbolinks5ඪൔ ) ݸᒒኞாսᐹӬ౮ᆧ ᫷ڹᒒଫአ turbolinks + Rails5 ᬱսԭڹᒒ໛ຝ
  23. ᒫӣྍ( ದ๞ᓤ ) Ծߝᬽդ୏ݎ

  24. 3.1 ᦡᦇහഝᕮ຅

  25. None
  26. Ⴔศጱ޸ݷฎىᲫ ىᘶىᔮጱ޸ݷԞஉ᯿ᥝ מ௳ӧᥝٞ֟ class User # other codes has_many :reader_orders,

    class_name: 'Order', foreign_key: 'reader_id' has_many :writer_posts, class_name: 'Post', foreign_key: 'writer_id' end
  27. 3.2 Turbolinks5

  28. አಁ֛ḵঅ( ݢྲ SPA ଫአ ) ୏ݎපሲṛ ਍ԟ౮๜֗ սᅩ

  29. ဳ఺ᅩ ඙֢ӧ଍ᒵ෸ىᳮ cache ࣁஙמ6.1զӥᇇ๜ىᳮ᧣አ jssdk ጱᶭᶎ // page1.html - content_for

    :head do meta name="turbolinks-cache-control" content="no-cache" // layouts/application.html.slim = csrf_meta_tags = content_for?(:head) ? yield(:head) : '' // ----------------- // app/views/wechat/writer/readers/index.html.slim = link_to order.post.short_title, reader_post_path(order.post), data: { turbolinks: false }
  30. Turbolinks ᮎԶԪ ๅग़ turbolinks5 ጱᳯ᷌? ᧗ັᴅ:

  31. 3.3 wxpay Ө ActionCable

  32. ᵭ୊᬴ጱඪ՞֛ḵ

  33. wxpay gem & sjr function jsApiCall(){ showSpin(<%= @order.id %>); WeixinJSBridge.invoke(

    'getBrandWCPayRequest', <%= raw @js_pay_hash.to_json %>, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok") { }else if( res.err_msg == "get_brand_wcpay_request:cancel" ){ hideSpin(); $.post("<%= cancel_reader_orders_path(order_id: @order.id) %>"); } } ); } function callpay(){ if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', jsApiCall, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', jsApiCall); document.attachEvent('onWeixinJSBridgeReady', jsApiCall); } }else{ jsApiCall(); } } callpay();
  34. wxpay & actioncable # order action cable( order_channel.rb ) class

    OrderChannel < ApplicationCable::Channel def subscribed stream_from "order_for_#{params[:order_id]}" end end # order.rb class Order def send_cable_notify ActionCable.server.broadcast "order_for_#{self.id}", {} ActionCable.server.broadcast "user_post_for_user_#{self.reader_id}_and_post_#{self.post_id}", { end end // javascripts/pay_helper.js function showSpin(order_id){ window.App.order_channel = window.App.cable.subscriptions. create( {channel: 'OrderChannel', order_id: order_id}, { received: function(){ $('#spin').spin(false); Turbolinks.visit(); } }); $('#spin').css('z-index', 1).spin().css('background-color', 'rgba(0,0,0,0.2)'); }; function hideSpin(){ $('#spin').css('z-index', -1).spin(false).css('background-color', 'transparent'); }
  35. ᵭ୊᬴֛ḵඪ՞᭗Ꭳ ᭦ᬋፗᥡ SJR ӨڹᒒJSᜉঅᯈݳ

  36. 3.4 JS ޾ CSS ೆړ

  37. Layout ೆړ wechat layouts writer layouts admin layouts

  38. JS ೆړ application.js wechat_base.js writer_base.js admin_base.js

  39. CSS ೆړ application.css wechat_base.css writer_base.css

  40. ໐ஞྍṈ ᑏᴻ require_tree . ԅྯӞᐿ᥯ᜋڠୌӞӻ layout ړڦٵ॓ਙժጱ js Ө css,

    ଚوአᕮ຅ ࣁ initializers/assets.rb Ⴒے੒ଫጱ js Ө css
  41. ᕮຎ 300KB -> 80KB, ے᫹᭛ଶ൉܋ 300% ᯈݳ cdn አಁᑏۖᒒࣁ 700ms

    ૢݦ಑୏ଫአ
  42. 3.5 ActionCable ๅग़ ଫአ

  43. አಁࣁኪᚏᒒ಑୏෈ᒍ, ಚᎱᨻԣ, ኪᚏᒒܨ ෸ݶྍᴅ᧛ ᘍᡤጭ୯Ө๚ጭ୯ఘ٭ ௏ᘍӞӻᵱ࿢

  44. ᥴ٬ොໜ Token for user&post Redis Channel Name: user_post_for_user_id_post_id

  45. Channel # user_post_channel.rb class UserPostChannel < ApplicationCable::Channel def subscribed stream_from

    "user_post_for_user_#{params[:user_id]}_and_post_#{params[:post_id]}" end def unsubscribed # Any cleanup needed when channel is unsubscribed end end
  46. Notification # order.rb def send_cable_notify ActionCable.server.broadcast "order_for_#{self.id}", {} ActionCable.server.broadcast "user_post_for_user_#{self.reader_id}_and_post_#{self.post_id}"

    , {} end
  47. # post_token.rb class PostToken redis = Redis.new @hash = Redis::Namespace.new(ENV['TOKEN_NAMESPACE'],

    :redis => redis) @order_token_map = Redis::Namespace.new(ENV['TOKEN_NAMESPACE'] + '_order_token_map', :redis => re @expire_time = 60 * 60 * 24 * 7 * 2 class <<self def generate SecureRandom.uuid.tr('-', '') end def bind_order(order_id, token) @order_token_map.set(order_id, token) @order_token_map.expire(order_id, @expire_time) end def is_bind_order?(order_id) @order_token_map.get(order_id).present? end def order(order_id) token = @order_token_map.get(order_id) if token @hash.set(token, true) @hash.expire(token, @expire_time) # notify ActionCable.server.broadcast "order_token_for_#{token}", {} end end end end Post Token
  48. ᒫࢥྍ ၥᦶӨݎ૲

  49. ၥᦶள᩸๶ Test Mode wxpay sidekiq: async -> sync

  50. Test Mode # routes.rb Rails.application.routes.draw do if ENV['USER_TEST_DEBUG'].present? get '/test'

    => 'visitors#test' get '/test_for_new_user' => 'visitors#test_for_new_user' get '/test_for/:id' => 'visitors#test_for' end end # OrdersController#pay if ENV['USER_TEST_DEBUG'].present? if @order.may_pay? @order.pay! order_token_for_order_id @order.send_cable_notify end render js: 'alert("ၥᦶཛྷୗඪ՞౮ۑ");Turbolinks.visit();' return end
  51. Test Mode( 2 ) # sidekiq worker if ENV['USER_TEST_DEBUG'].present? ServerNotificationWorker.new.perform(title,

    text) else ServerNotificationWorker.perform_async(title, text) end ᛔۖڠୌӨጭ୯አಁ ੱݢᚆݶྍ඙֢ၥᦶ ຄय़ᜓ፜ၥᦶ౮๜
  52. ݎ૲ԯᒒ۸ ASSETS CDN Image CDN Virtual Host

  53. CDN # config/environments/production.rb config.action_controller.asset_host = 'https://p8020cdn.b0.xxyun.com' # app/uploaders/image_uploader.rb class ImageUploader

    < CarrierWave::Uploader::Base storage :xxyun end
  54. ᖌಷᛔ૩ጱ᳒؟౲ཛྷ຃ ຤ԯཛྷ຃ ݎ૲ӞᲫ۸ $ mina deploy

  55. හഝݢᥤ۸ Charts Data Collection Server Notification

  56. Charts # chart gem "chartkick" gem 'groupdate' gem 'linux_fortune'

  57. ෛीአಁහ ෛी෈ᒍහ ၨᥦᰁ / ᦈܔ / ݐၾᦈܔ / ؎؎፡ ྯޮ

    / ྯॠ ᬩ០හഝᥡၥ
  58. ๋ݸӞྍ: ᬩ០ V2EX RubyChina 36kr ᆽ᝜ .... ....

  59. क़ࢱץᅫ UI ᦡᦇӨਭᗦᚆێ Ծߝᦡᦇᚆێ ᬩ០ᚆێ لݪᬩ០ ... ...

  60. ୏ݎᛗӤᕚᬩ០ Ր 21 ॠ 05.16 ~ 06.06

  61. ୮ڹහഝ 2800+ አಁ 150+ ෈ᒍ 3000+ ᲀࠓ᷐

  62. ࣁ Rails5 ᫥᭲Ӥᬩ០֦ጱԾߝ

  63. ኞԾێ௛ᕮ ๋֯ਫ᪢ॺᤰ ᓌ၄ṛපጱ୏ݎቘஷ ኞா୩य़

  64. Ծڊጱپӻ᫪ৼ - ள᭛ଫአ rails5 ጱཛྷ຃ - ӞᤈդᎱ೏ᕷ෯ၨᥦ࢏ ( Native APP

    ཛྷ຃) Rails Template BrowserWarrior Mobx with ReactNative
  65. 80 ਍ᴺ Ӟ੒Ӟ੕૵ګ ᕚӤර਍ 3 ӻ์ http://www.80academy.com AD

  66. ጯړԏك܈ Ⴎࣉ૱ ᗑᕶದ๞ํᴴلݪ ೗ՈӾ ݶ෸൉׀: ڠӱԾߝ MVP ୏ݎ RubyConf ᩩۗࠟ,

    ᩒාᤢํᧇᕡՕᕨ
  67. QA & Thx