• 摊手┓( ´∀` )┏

  • 感谢新思路。但不知道具体怎么"检查"。Gemfile 里的确用到了 bootsnap。官方 README 也大概看了下。没有特别提到什么要注意的。

  • 没接触过 kindeditor 编辑器,但假如你要换编辑器的话,可以参考:

    https://github.com/xjh22222228/awesome-web-editor
    https://github.com/JefMari/awesome-wysiwyg
    http://1c7.me/2018-rich-text-wysiwyg-editor-full-list/

    我最近在用 quill。感觉不错

  • 2018-8-3 更新进展

    我碰到了什么问题

    当初之所以问这个问题,是因为在 mina deploy --verbose 部署成功后用 mina puma:phased_restart 重启服务器 (我用了 mina gem + mina-puma gem)

    然后(重点来了)服务器上 puma 会占用 100% CPU 持续2-8分钟才更新成功。
    (我用 htop 命令看的)

    我有两台机子,都在 UCloud ,都是 CPU 2核,内存4G。
    在机器A(测试环境)上完全没问题。phased_restart 只需3秒。一切工作完美。
    在机器B(生产环境)上,不论我怎么查谷歌来试图跟踪问题,就是搞不定(具体细节可参考末尾给出的链接)
    最后决定不折腾这个问题了

    我是怎么解决的

    最后用 nginx load balancer 来指向 2 个 puma 里其中一个,每次要更新了就更新另一个,然后指向那个新的。
    这样来回交替。
    我写了篇博客:http://1c7.me/2018-8-3-solve-rubyonrails-puma-server-phased-restart-100percent-cpu-problem/
    和 Github Issue:https://github.com/puma/puma/issues/1627

    希望这篇回复能帮助以后遇到同样问题的人。

  • 实话实说不会买… 因为不了解 Elixir 和 Phoenix 的"具体"情况,我只是时不时听到这俩词,外加看到和 Rails 相提并论。 我想知道它和 Rails 对比起来的优势是什么缺点是什么,当前的社区现状和发展方向是什么。 不会贸贸然啥都不了解就花钱买书。

    我个人而言会自己先谷歌查一遍,过几篇教程,确定对我有意义才会买。 所以建议楼主不要只写 How(怎么做),也要写几篇 Why(为什么这么做)(为什么用 Elixir 而不是其他工具)

  • 长文预警(贴了很多我项目里的代码)

    我也是前后端完全分离
    Vue 单页应用和 Rails 5 完全分开放 2 个代码库里(2个独立的文件夹) 也用了 Devise

    登录验证:用 JWT

    思路

    前端:

    Vue 发送 POST 登录请求(带上用户名和密码)
    如果用户名和密码对,后端返回 JWT,Vue 获得 JWT 之后就存在 localStorage 里(或者 sessionStorage 或者 cookie 里,看你喜欢咯)
    JWT 里的数据带了生成时间,可以判断并过期。
    需要身份验证就让 Axios 带上请求头 Authorization

    var instance = axios.create({
      baseUrl: 'http://localhost:3000/backend/api/v1'
    })
    instance.defaults.headers.common['Authorization'] = localStorage.getItem('user-token') || '';
    

    文件路径:src/api/user.js

    后端:

    验证用户名密码是否正确,如果正确就生成 JWT 并返回回去。

    Rails 代码:

    文件地址:Gemfile

    gem 'devise-jwt', '~> 0.5.6'
    

    文件地址:/app/utils/jwt_token.rb

    require 'jwt'
    
    module JwtToken
      ALGORITHM = 'HS256'
    
      def self.issue(payload)
        JWT.encode(payload, auth_secret, ALGORITHM)
      end
      def self.decode(token)
        JWT.decode(token, auth_secret, true,{ algorithm: ALGORITHM }).first
      end
      def self.auth_secret
        ENV["AUTH_SECRET"] // 这个秘钥很重要,如果不喜欢用环境变量就 bundle exec rails secret 自己生成一个放这里。
        // 比如 e8ffa64262b6928f397c3890a44778958b37709cc18f2b2bf7ae4d27bbf30e2da1948ea14b0b937d543cdbd45ac651c33d30650088180cef891f3e93f051a62f
      end
    end
    

    文件路径: config/application.rb

    config.autoload_paths += [
      Rails.root.join('app', 'utils'),
    ]
    

    文件路径 app/controllers/api/v1/application_controller.rb

    class Api::V1::ApplicationController < ActionController::Base
      skip_before_action :verify_authenticity_token
      # JWT 验证 (验证 HTTP 请求头里的 Authorization)
      # 部分代码复制自:https://www.sitepoint.com/introduction-to-using-jwt-in-rails/
    
      protected
    
      # 强制验证,会报错
      def authenticate_request!
        unless user_id_in_token?
          render json: { errors: ['Not Authenticated'] }, status: :unauthorized
          return
        end
        @current_user = User.find(auth_token['user_id'])
      rescue JWT::VerificationError, JWT::DecodeError
        render json: { errors: ['Not Authenticated'] }, status: :unauthorized
      end
    
      # 软验证,不会报错
      def current_user
        if user_id_in_token?
          @current_user = User.find(auth_token['user_id'])
        else
          false
        end
      end
    
      private
      def http_token
        @http_token ||= if request.headers['Authorization'].present?
          request.headers['Authorization'].split(' ').last
        end
      end
    
      def auth_token
        @auth_token ||= JwtToken.decode(http_token)
      end
    
      def user_id_in_token?
        http_token && auth_token && auth_token['user_id'].to_i
      end
    end
    

    可以看到这些判断 jwt 的代码都写在 ApplicationController 里了。
    在其他 Controller 里使用方法如下:
    文件路径:app/controllers/api/v1/topic_controller.rb

    class Api::V1::TopicController < Api::V1::ApplicationController
      before_action :authenticate_request!, only: %i[create like unlike comment destroy]
      before_action :current_user, only: %i[show]
      # 省略其他不重要代码,注意看这里的 before_action
    end
    

    文件路径:app/models/user.rb

    # http://blog.jasonheylon.com/2017/12/01/rails-vue-vuex-jwt-token-auth.html
    def token
      JwtToken.issue(user_id: self.id)
    end
    

    文件路径:app/controllers/api/v1/sessions_controller.rb

    # 登录
    def create
      # 验证参数
      unless (params.has_key?(:phone) || params.has_key?(:password))
        render json: { status_code: '1', msg: '参数不足' }
        return
      end
      if params[:phone] == '' ||  params[:password] == ''
        render json: { status_code: '2', msg: '参数错误' }
        return
      end
    
      # 拿到用户
      @user = User.where(phone: params[:phone]).first
      unless @user 
        render json: { status_code: '3', msg: '用户不存在' }
        return
      end
    
      if @user && @user.valid_password?(params[:password])
        render json: @user, serializer: UserSerializer, root: 'user' // 注意这里的 UserSerializer
      else
        render json: { status_code: '4', msg: '用户名或密码错误' }
      end
    end
    

    文件路径:app/serializers/user_serializer.rb

    class UserSerializer < ActiveModel::Serializer
      attributes :id, :token, :login, :portrait_url, :followers_count, :following_count, :admin
      #  注意这里的 :token 调用了 User Model 对应的方法,其他属性这里不重要,可忽略
      def admin
        object.admin?
      end
    
      def portrait_url
        object.portrait
      end
    
    end
    

    Vue 代码

    Vue 这边用了一些库,干脆整个文件全部贴出来供参考。 用了 Axios 和 Vuex 和 Vue-Router 等

    文件路径 package.json

    {
      "name": "x",
      "description": "x",
      "version": "1.0.0",
      "author": "x",
      "license": "MIT",
      "private": true,
      "scripts": {
        "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot --port 8081",
        "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
      },
      "dependencies": {
        "axios": "^0.18.0",
        "bootstrap": "^4.1.1",
        "bootstrap-vue": "^2.0.0-rc.11",
        "jstransformer-verbatim": "^1.1.1",
        "jwt-decode": "^2.2.0",
        "moment": "^2.22.2",
        "qiniu-js": "^2.4.0",
        "tui-editor": "^1.2.3",
        "v-tooltip": "^2.0.0-rc.33",
        "vue": "^2.5.11",
        "vue-i18n": "^8.0.0",
        "vue-markdown": "^2.2.4",
        "vue-router": "^3.0.1",
        "vuelidate": "^0.7.4",
        "vuex": "^3.0.1"
      },
      "browserslist": [
        "> 1%",
        "last 2 versions",
        "not ie <= 8"
      ],
      "devDependencies": {
        "babel-core": "^6.26.0",
        "babel-loader": "^7.1.2",
        "babel-preset-env": "^1.6.0",
        "babel-preset-stage-3": "^6.24.1",
        "clean-webpack-plugin": "^0.1.19",
        "cross-env": "^5.0.5",
        "css-loader": "^0.28.7",
        "file-loader": "^1.1.4",
        "html-webpack-plugin": "^3.2.0",
        "node-sass": "^4.9.0",
        "pug": "^2.0.3",
        "pug-loader": "^2.4.0",
        "raw-loader": "^0.5.1",
        "sass-loader": "^6.0.7",
        "style-loader": "^0.21.0",
        "vue-loader": "^13.0.5",
        "vue-template-compiler": "^2.4.4",
        "webpack": "^3.6.0",
        "webpack-dev-server": "^2.9.1"
      }
    }
    
    

    登录界面是一个弹窗,写成了组件:
    文件路径:/src/components/signup_login.vue
    因为代码长,这里只贴关键代码:

    <template lang='pug'>
    //省略
    </template>
    <script>
      import {
        mapGetters,
        mapActions
      } from 'vuex';
      import {
        required,
        minLength,
      } from 'vuelidate/lib/validators'
      import sessionApi from '../api/session'
    
      export default {
        data: function() {
          return {
            // 省略
          }
        },
        computed: {
          ...mapGetters([
            // 省略
          ])
        },
        methods: {
          ...mapActions([
             // 省略
          ]),
        // 这个 login 是关键方法
          login: function() {
            let data = {
                phone: this.loginPhone,
                password: this.loginPassword
            };
            var self = this;
            sessionApi.signInUser(data).then((result) => { 
              if (result['token']) {
                self.$store.dispatch('logIn', result); // 关键在这里。。登录成功就 dispatch
              }else{
                if(result['status_code'] == '3' || result['status_code'] == '4'){
                  alert('用户名或密码错误');
                }
              }
            }).catch((err) => {
              alert(err);
            })
          },
        }
      }
    </script>
    <style>
    // 省略
    </style>
    

    文件路径:src/api/session.js

    // 注册登录相关
    import axios from 'axios'
    import baseUrl from './base'
    
    // 这里 baseUrl 其实就只是 http://localhost:3000/backend/api/v1
    
    const URLS = {
      SIGN_IN_API_URL: `${baseUrl}/signin`,
    }
    
    export default {
      // 登录  ---- 关键是这里,看这个 signInUser 是上一个代码片段里调用了的
      signInUser (signInData) {
        return axios.post(URLS.SIGN_IN_API_URL, signInData).then((response) => {
          return Promise.resolve(response.data)
        }).catch((error) => {
          return Promise.reject(error)
        })
      },
    }
    

    文件路径:src/store/actions.js

    // 登录
    logIn ({commit}, result) {
      var token = result['token'];
      localStorage.setItem('user-token', token);
      localStorage.setItem('currentUser', JSON.stringify(result));
      axios.defaults.headers.common['Authorization'] = token;
      commit('SET_LOGIN_IN', result);
    },
    

    文件路径:src/store/mutations.js

    // 成功登录
    SET_LOGIN_IN(state, payload) {
      state.currentUser = payload;
      state.jwt_token = payload.token;
      state.isLoggedIn = true;
      state.showPopupLoginIn = false;
    },
    

    最后

    我想前后端的代码我都贴的差不多了,应该不缺什么了。 其实总体就是后端怎么生成和验证 JWT,前端怎么保存和发送 JWT

    参考资料

    https://www.sitepoint.com/introduction-to-using-jwt-in-rails/

  • 好~ 谢谢

  • 多谢各位~~(我有别的事情就把这茬忘了,隔了一天才回来看到各位的回帖,非常感谢) 这些我都看看细节去,最后有结果了回来说~

  • 不清楚什么状况,去看了一眼,全是垃圾信息。
    貌似 Elixir 现在比 Rails 还要小众?所以社区人数少很多

  • 我暂时没试过 Active Storage,没法帮忙。说下我现在的做法,供参考。
    目前我的网站只有图片上传,不需要存其他类型的文件(pdf, txt, mp4 之类的)

    目前用的是七牛。客户端 Javascript 用 Ajax 直传七牛(用了服务器生成的 upload token),
    然后七牛会 callback 到我的服务器。 现在能满足需求,暂时没坑。 (没选"又拍云"或"阿里云 OSS" 没有什么特殊理由,只是之前几个项目用七牛,比较熟,就继续用了)

新浪微博@糖醋陈皮,Github@1c7。最近(2017年)主要在做 yuzhu.me 以及 sideidea.com