感谢思路
https://dev.to/ (其实我很少看这个…只是知道它存在)
下面这俩勉强算是论坛吧。
https://wip.chat/ WIP - Work in Progress
https://getmakerlog.com/ Maker Log
赞啦,以后会写点 Docker Swarm 的 CI/CD 实践文章吗?
求 example .gitlab-ci.yml
捂脸。我目前没有遵循任何标准,就需要什么数据就返回什么。 有空的话打算试试 GraphQL。目前还没空,在折腾 Docker Swarm。
非常感谢详细的回复~ lock_version 没懂是什么我去查查
听过但是还没接触。谢重要的事情说 3 遍。
我之前和美国西雅图的小伙伴一起工作的时候(3 人小团队)是直接西联 West Union 转账。合作了大概 3-4 个月。都是 1000+ 美金,700 美金这样直接打到我银行卡。没有签劳动合同(是的,不太正规,人太少,就是个满随意的外包小团队) ┑( ̄Д  ̄)┍ 只是提供一个案例,可能对你没太大用
没听过这种操作…… 我得去查查
学习了~
嗯原来这样。感谢。 过阵子等我学习(发音:踩坑)完 Docker, Docker Swarm, 实践用在项目里了, 写几篇博客分享一下~ 等 Swarm 稳了说不定再弄 k8s
所以更新一次最快速度是:16 秒构建 + push image20 秒 -40 秒 + docker service update
1 分钟左右。
大概 2 分钟就可以全自动更新一版了。
话说有 rollback 的应急方案吗?还是说干脆代码回滚再 push。而不是用什么回滚机制。
感谢详尽的回复
赞,我也是打算这样做。Gitlab + Swarm。
Drone.io 我在发帖之后又继续试了。觉得构建镜像和推送镜像到 Docker hub 的部分挺简单的。
但是复制 docker-compose.yml 到服务器以及 ssh 进行 Swarm 的更新非常麻烦。 Drone.io 的官方文档又非常精简,没啥有用的内容。
appleboy/scp 和 applyboy/ssh 的文档试了又不行。Drone 插件不算多。 基本每一个插件点进去的顶部都是一个红框说这是 0.8 版本的语法。blahblah。
简言之就是因为文档劝退我了。 说这一堆希望对以后查阅到这篇帖子的人有用。
嗯,感谢~~
PHP 是最好的 ____
嗯这样,谢谢
Gitlab CI 纯粹做测试。测完了发个 http 请求触发 bash 脚本进行部署? vm 虚拟机啥意思?就是云服务商提供的裸机是吧?没用 docker。 感谢回复
试了一下,说下感受:
一进去就要手机号,啥预览内容都没有。如果不是在 Ruby China 看到的话,此时我就卸载了。输手机号-> 输入验证码->输用户名。在一个我使用欲望不是很强烈的情况下,我是懒得走完的。
进去之后没有内容。首页空的。
第二个标签页:“发现”
看了之后给人的感觉这就是个公开版的朋友圈。
我干嘛要发?
结论: 当前这个时间点,我是完全没有任何使用欲望的。会卸载。 在我看来就是个使用麻烦,界面难看的朋友圈而已。
发这个帖之前,在社区里搜了一下。 相关讨论不是很多,就几篇帖子
当新用户出价后
摊手┓( ´∀` )┏
感谢新思路。但不知道具体怎么"检查"。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。感觉不错
当初之所以问这个问题,是因为在 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
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 并返回回去。
文件地址: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`
```ruby
# 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`
```ruby
# 登录
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`
```ruby
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`
```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`
因为代码长,这里只贴关键代码:
```javascript
<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`
```javascript
// 注册登录相关
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`
```javascript
// 登录
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`
```javascript
// 成功登录
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/
好~ 谢谢
多谢各位~~(我有别的事情就把这茬忘了,隔了一天才回来看到各位的回帖,非常感谢) 这些我都看看细节去,最后有结果了回来说~