文件描述符、打开文件表以及inode

changjixiong 发表了文章 • 0 个评论 • 286 次浏览 • 2018-01-04 22:02 • 来自相关话题

linux系统相关书籍中关于文件描述符及相关内容,通常会出现一张类似这样的图

查看全部

linux系统相关书籍中关于文件描述符及相关内容,通常会出现一张类似这样的图


(filegraph1)


或者这样的图


(filegraph2)


第一个图来自Michael Kerrisk的《Linux/UNIX系统编程手册》,第二个图来自《UNIX环境高级编程》(也就是APUE)。


文中对相关信息做了论述并且配上了上面这样的图,但是我相信很多人看完以后觉得好像懂了,那么请尝试想一想一下几个问题:


1.假如创建了文件a得到了文件描述符fa1,并且正在写入的过程中再次打开文件a得到了文件描述符fa2。这个时候通过fa2对文件重命名,会有什么结果。


2.fa2对文件重命名后,通过fa1获得的文件名是原来的文件名还是修改后的文件名。


3.fa1能否继续写入。


如果以上3个问题完全没有疑惑,说明已经对文件描述符及相关内容掌握的非常清楚,可以不用继续看下去了。如果还有疑问,那么请看下面这段代码


func main() {

fileOldName := "rotate.log"
fileRename := "rotate1.log"
file, _ := os.OpenFile(fileOldName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.FileMode(0644))

fmt.Println("open file", fileOldName)
for i := 0; i < 5; i++ {
file.WriteString(fmt.Sprintf("%v line: %d\n", time.Now(), i))
}

var statOldFile syscall.Stat_t
if err := syscall.Stat(fileOldName, &statOldFile); err != nil {
panic(err)
}

fmt.Println(fileOldName, "statOldFile.Ino:", statOldFile.Ino)

err := os.Rename(fileOldName, fileRename)
if err != nil {
fmt.Println("file rename Error:", err)
}

fmt.Println("rename ", fileOldName, "->", fileRename)

var statRenamedFile syscall.Stat_t
if err := syscall.Stat(fileRename, &statRenamedFile); err != nil {
panic(err)
}

fmt.Println("fileRename", "statRenamedFile.Ino:", statRenamedFile.Ino)

fmt.Println("file.Name:", file.Name())

for i := 5; i < 10; i++ {
file.WriteString(fmt.Sprintf("%v line: %d\n", time.Now(), i))
}

fileOld, err := os.OpenFile(fileOldName, os.O_WRONLY|os.O_APPEND, os.FileMode(0644))

if nil != err {
fmt.Println(fileOldName, " open error:", err)
} else {
fmt.Println("fileOld.Name:", fileOld.Name())
}

}

运行后的输出为


open file rotate.log
rotate.log statOldFile.Ino: 28567907
rename rotate.log -> rotate1.log
fileRename statRenamedFile.Ino: 28567907
file.Name: rotate.log
rotate.log open error: open rotate.log: no such file or directory

rotate1.log文件中有10行记录。


上面的代码首先创建了一个名为rotate.log的文件,然后写入了5行记录,打印出文件的inodeID 28567907,然后将文件重命名为rotate1.log,再次打印文件的inodeID 28567907,继续写入5条记录,打印文件对象的名字,最后再次打开文件rotate.log,提示文件不存在。


现在来分析一下程序的逻辑:



  1. 创建并打开文件rotate.log其inodeID为28567907,file对象中包含了一个文件描述符姑且称为FA1,指向一个文件表项姑且称为FT1,FT1指向一个V节点项。此V节点项的文件就是inodeID为28567907的rotate.log文件,写入了5条记录。

  2. 利用rename将文件rotate.log重命名为rotate1.log。实际就是打开了一个文件描述符FA2,指向文件表FT2,FT2指向inodeID为28567907的V节点项,也就是前文的rotate.log,将V节点中这个文件的文件名属性修改为rotate1.log,文件还是那个文件,因此打印出来的inodeID还是那个inodeID。

  3. file对象再次写入5条记录,FA1没变,FT1没变,V节点中的inode没变,仅仅只是文件名属性变了,因此记录依然写入了原来那个文件,虽然文件名已经变了。

  4. 打印file对象的文件名属性,显示是修改前的名字,因为file对象的name属性在创建时已经赋值,即使V节点中的文件名已经修改,file对象中的name不会变。

  5. 再次打开rotate.log会发现已经打不开了,因为这个文件已经不存在了,毕竟V节点中这个文件现在已经叫rotate1.log了。

  6. rotate1.log中有10行记录,其中5行是改名前写入的,5条是改名后写入的。


通过这个例子,大概能明白文章开头的两个图了吧。


为啥文件名叫rotate.log?因为logrotate就是重命名后再次用原名创建新文件继续写的。


更多golang示例代码见 https://github.com/changjixiong/goNotes

gRPC如何进行文件上传

zsy619 回复了问题 • 4 人关注 • 3 个回复 • 434 次浏览 • 2018-01-04 13:45 • 来自相关话题

Go语言经典笔试题

huazhihao 发表了文章 • 0 个评论 • 498 次浏览 • 2018-01-02 00:40 • 来自相关话题

GopherChina 2018 开始了

astaxie 发表了文章 • 1 个评论 • 430 次浏览 • 2017-12-28 23:23 • 来自相关话题

经过半个多月的筹划,GopherChina 2018正式开始对外了,明年我们继续在4.14-4.15期间在上海举办,目前已经确认的国外嘉宾有《Go in Action》作者,Dgraph作者,Go Team的人还在确认中,国内确认的嘉宾有探探后端负责人H... 查看全部

经过半个多月的筹划,GopherChina 2018正式开始对外了,明年我们继续在4.14-4.15期间在上海举办,目前已经确认的国外嘉宾有《Go in Action》作者,Dgraph作者,Go Team的人还在确认中,国内确认的嘉宾有探探后端负责人Henry,容器引擎hyper的CTO 王旭,其他嘉宾正在逐步确认中,目前开始早鸟票时间段,大家抓紧时间抢。


今年我们还很荣幸邀请到了William Kennedy,他作为美国Gopher大会的组织者,同时又是专业的Go培训师,我们今年也打算举行会前一天的workshop。


https://www.bagevent.com/event/1086224

如何从零构建个人博客系统

Rina 发表了文章 • 5 个评论 • 561 次浏览 • 2017-12-25 10:56 • 来自相关话题

简介

这篇文章主要分享博客里涉及的Ruby, Rails,前端CSS,JS,ubuntu系统命令等知识。如果有什么不解的地方可以通过http://liuzhen.me页面下... 查看全部

简介


这篇文章主要分享博客里涉及的Ruby, Rails,前端CSS,JS,ubuntu系统命令等知识。如果有什么不解的地方可以通过http://liuzhen.me页面下方的二维码扫描加我微信。




  • Ruby是一种纯粹的面向对象编程语言。它由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)创建于1993年。




  • Ruby on Rails(官方简称为Rails,亦被简称为RoR),是一个使用Ruby语言写的开源Web应用框架,它是严格按照MVC结构开发的。它努力使自身保持简单,来使实际的应用开发时的代码更少,使用最少的配置。指南: https://ruby-china.github.io/rails-guides/



  • CSS 指层叠样式表, 你在页面看到的展示效果都是通过CSS做出来的,页面的布局,字体大小,颜色,边框,菜单等等. 详情可以查看: http://www.runoob.com/css/css-intro.html

  • JS 是属于网络的脚本语言, 能做的事太多了,像我博客里的相册功能,时间线都是JS做出来的效果。


安装Rails环境


你可以通过搜索 Mac/windows/ubuntu install rails 来找到相关文档,这里提供ubuntu 16.04版本的安装文档: https://gorails.com/setup/ubuntu/16.04 , Mac的安装文档: https://ruby-china.org/wiki/mac-nginx-passenger-rails


创建一个Rails项目


安装好Rails环境之后,你可以创建一个Rails项目了,如果你从来没用过Rails,可以先用15分钟学习一下 Rails入门, 了解Rails MVC结构。


如果你对Rails有一定的了解,可以按照这个模版 https://github.com/80percent/rails-template 提供的操作步骤, 创建一个Rails项目,使用这个模版创建Rails项目的好处是,这个模版相当于一个全家桶,预先添加一个项目经常需要使用的Gem包,发布需要的puma, mina, monit, nginx配置文件,关于这几个东西是什么,有什么用后面会讲到。


启动Rails


$ rails s


访问 localhost:3000 就能看到 hello world 页面了。


创建数据模型


我的博客在设计之初只想要文章,相册,简历这几个功能,这三个功能比较相似,都有标题,内容和可有可无的描述。所以我就用了单表继承,建了个base表。


$ rails g model Base title:string content:text subtitle:string type:string

type字段就是用于单表继承。


执行完这条命令之后,你会看到 db/migrate/xxxx_create_bases.rb 多了一个这样的文件,里面的内容是:


class CreateBases < ActiveRecord::Migration[5.1]
def change
create_table :bases do |t|
t.string :title
t.string :subtitle
t.text :content
t.string :type
t.timestamps
end
end
end

t.timestamps 是时间戳,系统会自动在这个表里面加上 created_at, updated_at 两个字段。


添加完之后,需要把做一下数据迁移,我一开始学rails的时候对 数据迁移 这个词很不理解。其实数据迁移的意思就是,我们现在通过命令创建了个数据表的文件,但是这个文件没有被执行,不执行数据库里就还没有这张表,只有在执行了 rails db:migrate 之后,rails才在数据库里把这张表给加上,这个操作就叫做 数据迁移。


创建完了Base表,现在就要创建文章表了 Article, 我们需要添加一个 app/models/article.rb 文件,写上:


class Article < Base
end

因为Article继承了Base, 所以就拥有了Base的所有字段了。


你可以通过 rails c 从控制台输入 Article.new 可以看到:


irb(main):006:0* Article.new
=> #<Article id: nil, title: nil, subtitle: nil, content: nil, type: "Article", created_at: nil, updated_at: nil>

type字段自动就是Article, 这是Rails的一个特性,单表继承。


输入Article.all, 看到的sql语句实际是从bases里查询type为Article的所以记录。


irb(main):007:0> Article.all
Article Load (127.0ms) SELECT "bases".* FROM "bases" WHERE "bases"."type" IN ('Article') LIMIT $1 [["LIMIT", 11]]

相册表Photo,简历表ResumeArticle的创建方式相同.


表创建好了,我们就可以创建Controller了,Controller需要区分前端和后端,前端就是提供给用户查询的,后台是提供自己添加,更新,删除操作的。另外后台因为是管理的地方所以不能让所有人都访问,所以需要设置成通过用户名和密码登录。这样别人就无法访问你的后台。


后端设计


为了与前台有所区分,所以需要加一下命名空间: 这里设置成 admin. 先在 config/routes.rb 里添加路由,


Rails.application.routes.draw do
namespace :admin do
root 'dashboard#index', as: 'root'
resources :articles
resources :photos
resource :resume, only: [:edit, :update]
end
end

root 'dashboard#index', as: 'root' 设置后台的root路由。使用 rails routes 命令可以查看具体的路由信息。


控制器


controllers 目录下添加admin目录,这个目录下用于存放所有的后台文件,后台添加一个 app/controllers/admin/base_controller.rb 文件,继承了 ApplicationController, 这么做是为了admin下的所有controller继承 Admin::BaseController 后,用户访问后端链接就会先校验当前用户是否登录,如果没有登录就跳转到登录页面。
代码如下:


class Admin::BaseController < ApplicationController
layout 'admin'
before_action :authenticate_user
def authenticate_user
unless session[:login]
redirect_to new_session_path
end
end
end

因为article, photo, resume这几个功能比较相似,所以我只讲一下article控制器:app/controllers/admin/articles_controller.rb


代码:


class Admin::ArticlesController < Admin::BaseController
def index
@articles = Article.all.order(created_at: 'DESC').page(params[:page])
end
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to admin_articles_path
else
render 'new'
end
end
def edit
@article = Article.find(params[:id])
end
def update
@article = Article.find(params[:id])
if @article.update(article_params)
flash[:notice] = '更新成功'
redirect_to admin_articles_path
else
render 'edit'
end
end
def destroy
@article = Article.find(params[:id])
if @article.destroy
flash[:notice] = '删除成功'
else
flash[:notice] = "删除失败, 原因: #{@article.errors.messages.to_s}"
end
end
private
def article_params
params.require(:article).permit(:title, :subtitle, :content)
end
end

控制台里面很简单,就是增,删,改,查。需要注意的就是redirect_to, render的区别,什么时候要用render, 什么时候用redirect_to.


render 是指直接熏染某个页面.


redirect_to 是指告诉浏览器,让浏览器再重新发送一个指定路由的请求操作。


如:create action 里写到如果保存成功就 redirect_to admin_articles_path, 如果失败就 render 'new'.




  1. 假如保存成功,就会告诉浏览器, 让浏览器再向服务器发送一个admin/articles路由请求,然后进入index action里,查询所有Action记录,再熏染index.html页面,返回给浏览器。



  2. 假如保存失败,就用用户填写的@article信息熏染new.html页面,并用flash里的信息,告诉用户提交失败的原因,如果失败后用 redirect_to new_admin_articles_path,也能跳转到new页面,但是用户提交的信息就没有了。


view


.row
.offset-md-2.col-md-8
= simple_form_for [:admin, @article] do |f|
= f.error_notification
= f.input :title
= f.input :subtitle
= f.text_area :content, id: 'editor_content', class: 'simditor', autofocus: true
= f.submit '提交', class: 'btn btn-primary'
= link_to '取消', admin_articles_path
javascript:
new Simditor({
textarea: $('#editor_content'),
toolbar: ['title', 'bold', 'italic', 'underline', 'strikethrough', 'fontScale', 'color', '|', 'ol', 'ul', '|', 'blockquote', 'code', 'table', 'link', 'image', 'hr', 'indent', 'outdent', 'alignment']
});

这个有点需要讲的是编辑器使用了simditor插件,具体要加哪些信息可以看一下这个文档: http://simditor.tower.im/, 但是要支持上传图片功能需要在admin下添加一条路由: post '/upload', to: 'photos#upload', 在photos controller里添加一个upload action,把上传的图片保存到数据库。


前端设计


前端的controller继承ApplicationController,前端的因为只设计到查询,所以添加路由的时候加上only, 如: resources :articles, only: [:index, :show], 就只添加两条路由,如果不加only默认会创建 7 条路由。


前端功能主要就涉及到css.



css调试步骤:



  1. 右击选择'检查',就能打开控制台,在控制台处,通过点击(2) 处的图标,可以选择页面上任意节点,选择后(3)处会显示这个节点所对应的CSS样式。同样在style处可以通过添加和注释css来对页面样式进行调试。


样式这里涉及的东西太多,我不一一讲解,只讲一些我认为值得讲一讲的知识点。如果想学习更多的css样式知识,可以在文章开头处提供的文档查看学习。




  1. 文章的展示对字体,间距,背景,颜色等等都要求很高,如果设计的不好,文章看久了就容易累,而且容易给别人一种不想去看的感觉。如果间距很窄,一大段落全是文字,就给人一种很不舒服的感觉。如果你对这些信息了解不多,不知道把这些值设置成多少比较好,也不要担心,找一个你觉得文字展示效果看起来很舒服的网站,打开他的控制台,看一下这个网站上这些信息设置的值是多少,跟着一样设置就行了。具体的细节可以再另做调整。



  2. 博客的页面底部用的fa字体,在gemfile里添加 font-awesome-sass 后,就能展示出这些字体图标。但是目前的字体中没有支付宝的字体图标,你先不要看代码,想一想,如果是你,你要怎么实现一个跟fa字体相同效果的图标,这个图标带有hover效果,当鼠标放上去的时候背景变成了蓝色。



我的实现方法:


一开始我想的是用一个黑白图片代替,弄完之后我发现hover效果无法实现。于是我就用一个背景透明只有一个支字的图片代替,设置border-radius,background-color和字体达成一致效果,当鼠标放上去的时候就改变background-color: #0085A1;


代码:


footer .fa-alipay {
border-radius: 50%;
margin-bottom: 4px;
background-color: #222529;
width: 41px;
}
footer .fa-alipay:hover {
background-color: #0085A1;
}

调试页面上关于hover,visited, focus, active效果,可以像图片中勾选来查看相应的样式效果。



时间线


时间线是用的一个js库,https://github.com/RyanFitzgerald/vertical-timeline, 具体可以查看文档。值得说一下的是,一开始看到这个时间线的效果是在一个网站看到的。然后我通过页面控制台,看到里面class名称命名很规范,所以感觉是个js库,直接在google搜索 cd-timeline-block 第一个结果就是这个库的信息。除了这种方式,还可以通过控制台的Sources查看assets文件信息,一般都是经常压缩的,但是有些外部库是有注释的,会写上这个是来自哪个库之类的信息。不过最简单快速的办法还是用google搜索来的快一点。如果你搜的class名字没有找到相应的信息,可以换个class名字试试。



另外一个要说的是,这个JS库里的一个js文件main.js, 与turbolink一起加载,没生效,加载的时候就没被执行,然后我就把它用$(document).on 'turbolinks:load', 加载就好了。


相册


博客里我最喜欢的就是这个相册功能了,当初也是看了这个翻书的效果,我才有重写博客的冲动。看到这个js库是在github Trending上, 这上面会推荐github上比较火的项目。这个库的地址: http://www.turnjs.com, 这里面提供了几个demo. 这个turnjs用的 yepnope 加载js,这么加载是因为,有些内容需要在其他文件加载之后去执行。但是有个问题是在生产环境这些js文件都是被转译了的。所以直接在yepnope里面写上文件名,在生产环境上就会找不到对应的文件。对于这个我没有想到特别好的处理办法,就用


$('head').append('<%= javascript_include_tag 'turn.min.js' %>')

来加载文件,然后再执行yepnope({complete: loadApp})。如果你有更好的办法可以交流一下。


其他知识点




  • 效果支持手机端页面需要加上: meta width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no




  • 前端和后端的JS, CSS尽量分开,这样加载速度会快一些



  • 使用的外部库最好重新命名成可读名称, 不然时间长了你就不知道这个库是干什么的了. 比如我用了 timeline 的JS库, 里面有个main.js的文件, 我就把它重命名为 timeline-main.js, 这样不需要再加注释来说明这个文件是做什么的了.


发布


发布需要在服务器上安装好rails后,配置nginx.conf,一般放在/etc/nginx/conf.d/xxx.conf文件.


puma.rbdeploy.rb 配置文件是mina部署的相关配置信息,具体操作了什么可以通过bundle exec mina deploy -v来查看:


$ bundle exec mina deploy -v
-----> Creating a temporary build path
-----> Server: liuzhen.me
-----> Path: /home/ruby/RBlog
$ echo "-----> Branch: master"
-----> Branch: master
-----> Using RVM environment "2.3.1"
-----> Quiet sidekiq (stop accepting new work)
-----> Fetching new git commits
$ (cd "/home/ruby/RBlog/scm" && git fetch "https://github.com/liuzhenangel/RBlog.git" "master:master" --force)
-----> Using git branch 'master'
$ git clone "/home/ruby/RBlog/scm" . --recursive --branch "master"
Cloning into '.'...
done.
-----> Using this git commit
$ git rev-parse HEAD > .mina_git_revision
$ git --no-pager log --format="%aN (%h):%n> %s" -n 1
liuzhenangel (eb06b54):
> update timeline
$ rm -rf .git
-----> Symlinking shared paths
-----> Installing gem dependencies using Bundler
$ bundle install --without development test --path "vendor/bundle" --deployment
-----> DB migrations unchanged; skipping DB migration
-----> Skipping asset precompilation
-----> Cleaning up old releases (keeping 5)
-----> Deploy finished
-----> Building
-----> Moving build to /home/ruby/RBlog/releases/41
-----> Build finished
-----> Launching
-----> Updating the /home/ruby/RBlog/current symlink
-----> Restart Puma -- hard...
-----> Stopping Puma...
-----> Starting Puma...

从这些日志信息可以看出, 首先会根据你配置的服务器的域名和用户名ssh到服务器上,加载ruby环境,从github拉取最新代码,安装gem包,如果css, js, 图片有更新就重新编译压缩js, css, 图片, 执行 db:migrate 数据迁移, 首次发布还会执行rails db:create来创建数据库。然后重启puma.


为什么要nginx,puma


nginx相当于一个代理,当你在浏览器输入:http://liuzhen.me 的时候,先通过DNS找到这个域名对应的IP,然后通过路由到达IP所在的服务器上,服务器发现用户请求的是 liuzhen.me,然后nginx就根据配置文件里配置的 liuzhen.me 找到对应的项目,这个Rails项目是由puma启动的,然后nginx就把这个事交给puma, puma收到后就根据路由去到对应的controller,然后返回对应的页面给到浏览器。


monit 是什么


没有monit也能发布成功,但是有时候你的puma可能异常关掉了,如果有monit的话,就能时刻监听这个进程是不是启动状态,一旦关闭了,就重新启动。


博客地址: http://liuzhen.me


博客代码: https://github.com/liuzhenangel/RBlog


原文来源: http://liuzhen.me/articles/16

Go Gin 允许跨域访问

xfstart07 发表了文章 • 0 个评论 • 688 次浏览 • 2017-12-19 13:29 • 来自相关话题

上周五的时候,给接口添加了支持跨域访问,所有做一下跨域方面的笔记。

HTTP 访问控制(CORS)

当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 H... 查看全部

上周五的时候,给接口添加了支持跨域访问,所有做一下跨域方面的笔记。


HTTP 访问控制(CORS)


当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。


出于安全原因,浏览器限制从脚本内发起的跨域 HTTP 请求。例如,XMLHttpRequest 和 Fetch API 遵循同源策略。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非使用 CORS 头文件。


跨域资源共享标准:规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。


ps. 上面是 MDN 的文档介绍。


Gin 添加跨域中间件


Gin 是一个Go 开发的 Web 框架,给 Gin 加 CORS 的支持也是非常简单的。


CORS 中间件插件


这是 Gin 官方的 CORS 中间件
https://github.com/gin-contrib/cors


使用 CORS


引入包


import "github.com/gin-contrib/cors"

允许所有源的配置


func main() {
router := gin.Default()
// same as
// config := cors.DefaultConfig()
// config.AllowAllOrigins = true
// router.Use(cors.New(config))
router.Use(cors.Default())
router.Run()
}

自定义源的配置


config := cors.DefaultConfig()
config.AllowOrigins = []string{"http://google.com"}
config.AddAllowOrigins("http://facebook.com")

自定义其他配置:


cors.Config{
AllowOrigins: []string{"https://foo.com"},
AllowMethods: []string{"PUT", "PATCH"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return origin == "https://github.com"
},
MaxAge: 12 * time.Hour,
}

主要的几个配置介绍:



  • AllowOrigins 允许源列表

  • AllowAllOrigins 允许所有源,返回的格式 Access-Control-Allow-Origin: *

  • AllowMethods 允许的方法列表

  • AllowHeaders 允许的头部信息

  • AllowCredentials 允许暴露请求的响应,Access-Control-Allow-Credentials: true


资源


https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS


https://developer.mozilla.org/en-US/docs/Web/HTTP/Server-Side_Access_Control


https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials


https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Authentication


https://github.com/gin-contrib/cors

辞旧迎新之际, 我给博客换了新装

Rina 发表了文章 • 5 个评论 • 346 次浏览 • 2017-12-18 11:09 • 来自相关话题

2015年3月份我用Rails写了第一个比较完整的项目——首个博客系统,现在回过头来看,觉得很多地方都写的比较烂,今年年初的时候我打算重构自己的博客系统,从一开始的想法到完成整件事,经历了长达一年的时间,原因只有一个——懒。

这个博客的诞生... 查看全部

2015年3月份我用Rails写了第一个比较完整的项目——首个博客系统,现在回过头来看,觉得很多地方都写的比较烂,今年年初的时候我打算重构自己的博客系统,从一开始的想法到完成整件事,经历了长达一年的时间,原因只有一个——懒。


这个博客的诞生是因为平时在浏览其他网站及关注一些 js 库时发现一些特别喜欢的东西,所以才给了我做这件事的动力。其实做完这个项目真正花费的时间并不多,比较难的是要让自己愿意来做这件事。


整个项目做下来,对我来说,最难的不是技术实现,而是为博客每个主题找到合适的背景图片,简直就是大海捞针。别人给我推荐了这个 图片库,经过长时间的寻找,终于找到了几张让我比较满意的的图片。这个 工具 不错,有需要的可以收藏一下。


新的博客系统主要功能有:



  • 后端:数据统计,后台文章管理,相册管理,个人简历管理

  • 前端:文章列表,相册展示,时间线等


Demo


项目 Demo 请访问: http://liuzhen.me


项目 github 代码: https://github.com/liuzhenangel/RBlog


核心技术框架



  • Ruby on Rails 5.1.4

  • bootstrap 4

  • font-awesome

  • figaro

  • postgres

  • slim

  • high_voltage

  • carriewave & upyun

  • sidekiq

  • kaminari

  • mina

  • puma

  • lograge

  • simditor

  • turn.js


开发环境准备


第一步, 安装项目依赖


$ bundle install


第二步, 启动服务


$ rails s


第三步, 浏览器访问: http://localhost:3000


结束.


如何发布?


第一步, 配置nginx


先根据项目里的 config/deploy/production.rb, /config/deploy.rb, config/puma.rb, /config/nigix.conf 文件, 修改其中的配置, 然后将 /config/nigix.conf 文件复制到你的服务器上 nginx 所在目录的 /etc/nginx/conf.d 目录下, 命名为 xxx.conf 的文件. 然后重启 nginx.


第二步, 在服务器上初始化


$ mina setup


第三步, 发布


$ mina deploy


学习参考资料


Rails文档: http://edgeguides.rubyonrails.org/api_app.html


使用模板创建Rails项目: https://github.com/80percent/rails-template


ubuntu16.04安装rails: https://gorails.com/setup/ubuntu/16.04


simditor编辑器: http://simditor.tower.im/


startbootstrap-clean-blog前端样式: https://startbootstrap.com/template-overviews/clean-blog/


sb-admin前端样式: https://startbootstrap.com/template-overviews/sb-admin/


turnjs前端样式: http://www.turnjs.com


timeline前端样式: https://github.com/RyanFitzgerald/vertical-timeline


图片库: https://unsplash.com


引荐 Vue.js 项目


项目 Demo 请访问: http://v2ex.liuzhen.me/


项目代码: https://github.com/liuzhenangel/v2ex_frontend


引荐 React.js 项目


项目 Demo 请访问: http://ruby-china.liuzhen.me/


项目代码: https://github.com/liuzhenangel/react-ruby-china

thrift安装

jinheking 发表了文章 • 0 个评论 • 236 次浏览 • 2017-12-15 15:13 • 来自相关话题

不知道怎么在这里贴不过来,就写在开源中国了。

把地址发在这里 https://my.oschina.net/u/3739809...

go汇编入门

lrita 发表了文章 • 0 个评论 • 417 次浏览 • 2017-12-13 00:01 • 来自相关话题

GRABC beego框架的RABC插件

codyi 发表了文章 • 14 个评论 • 458 次浏览 • 2017-12-11 15:05 • 来自相关话题

最近写的一个权限管理插件,欢迎大家来吐槽~~

GRABC

GRABC 是一个beego权限管理插件,插件分为路由、权限、角色。将路由分配给权限,权限授给角色,角色授给用户~~

安装

查看全部
					

最近写的一个权限管理插件,欢迎大家来吐槽~~


GRABC


GRABC 是一个beego权限管理插件,插件分为路由、权限、角色。将路由分配给权限,权限授给角色,角色授给用户~~


安装


go get github.com/codyi/grabc

配置


第一步:在你项目中的数据库中导入rabc.sql,生成对应数据表


第二步:在项目中引入grabc库(可以在项目中的main.go或router.go中引入)


//引入grabc库
import "github.com/codyi/grabc"

引入之后,在引入的router.go或main.go中添加如下配置


func init() {
//将路由注册到grabc,用于反射出对应的网址
grabc.RegisterController(& controllers.SiteController{})
grabc.RegisterController(&controllers.UserController{})
//注册用户系统模型到grabc,用于用户ID和grabc插件绑定
//注意:注册的这个用户模型,需要实现IUserModel中的方法
grabc.RegisterUserModel(&models.User{})
//增加忽律权限检查的页面
grabc.AppendIgnoreRoute("site", "login")
//403页面地址注册到grabc中,用于grabc插件禁止权限的页面跳转
grabc.Http_403("/site/nopermission")
//设置模板,为了让grabc更具有通用性,可以设置模板
//目前设置模板只支持传入模板的内容
grabc.SetLayout(libs.Grabc_layout, nil)
}

添加好上面的配置之后,剩下就是在controller中增加权限判了,个人建议做一个BaseController,然后每个controller都继承这个base,然后在BaseController中的Prepare方法中增加grabc的权限检查~~


//注册当前登录的用户,注意:user需要继承IUserIdentify接口


grabc.RegisterIdentify(user)

if !grabc.CheckAccess(this.controllerName, this.actionName) {
this.redirect(this.URLFor("SiteController.NoPermission"))
}

到此grabc的功能都加完了,是不是很简单~~~


注意:增加完权限判断之后,会发现很多页面都不能访问了,那么就在忽律权限中增加如下配置


grabc.AppendIgnoreRoute("*", "*")

以上配置将会忽律所有的权限检查,这时候需要去/route/index中增加路由,然后添加权限,角色和用户分配,都配置好之后,就可以将grabc.AppendIgnoreRoute("*", "*")代码删掉,然后重启项目~~权限起作用了


接口说明


IUserModel接口

//用于定义用户model
type IUserModel interface {
//用户列表返回可用用户的id和姓名
//参数:pageIndex 分页的页数
//参数:pageCount 每页显示的用户数量
//返回值:userList [用户ID]用户姓名,用户列表展示
//返回值:totalNum 全部的用户数目,用于计算分页的数量
//返回值:err
UserList(pageIndex, pageCount int) (userList map[int]string, totalNum int, err error)
//根据用户ID获取用户姓名
FindNameById(id int) string
}

IUserIdentify接口
type IUserIdentify interface {
GetId() int //返回当前登录用户的ID
}

注意


grabc对注册的控制器会进行反射,然后获取每个controller的名称和controller内的公共方法,由于每个controller都继承了beego.Controller,在获取controller下的方法名称时,会将beego.Controller继承的方法也会获取到,所以目前还不能区分出方法名到底是beego和用户自己定义的,所以grabc将beego继承的方法都进行了忽律,如果在route扫描中,没有找到自定义的方法,可以在controller中增加如下方法,进行方法返回~~


func (this *SiteController) RABCMethods() []string {
return []string{"Get", "Post"}
}

grabc的详细例子:github.com/codyi/grabc_example


Image text
Image text
Image text
Image text

关于too many open files的一点深究

changjixiong 发表了文章 • 2 个评论 • 320 次浏览 • 2017-12-10 19:14 • 来自相关话题

今天在gocn上看到一个问题https://gocn.io/question/1391,题主问为啥运行到打开1000多个文件的时候就会报错,提示too many op... 查看全部

今天在gocn上看到一个问题https://gocn.io/question/1391,题主问为啥运行到打开1000多个文件的时候就会报错,提示too many open files。关于这段代码本身的错误,我已经在回帖中回复了,这里不再赘述。这里我主要想对相关问题稍微做一点深入的讨论,毕竟关于文件和文件描述符相关的问题,几乎是每个程序员都会碰到的问题,然而却很少人真的会去搞清楚问题的背后究竟是什么原理。


假设有这样一段代码


func main() {
input := ""
fmt.Scanln(&input)
for i := 0; ; i++ {
_, err := os.OpenFile("data.txt", os.O_RDWR|os.O_CREATE, os.ModePerm)

if err != nil {
fmt.Println("OpenFile i:", i, err)
return
}
}
}

程序运行起来,在键入回车后会输出:


OpenFile i: 1021 open data.txt: too many open files

如果在网上搜索too many open files,会找到无数的文章,里面会提到如何将系统设定的最大打开文件数从1024修改为一个很大的数字。


不过这里会有一个问题,既然最大限制是1024,为啥这里1021就提示错误了?


在回答这个问题之前,再次运行一下上面那段程序,这次不做任何输入,另外开一个终端找到这个程序的进程ID假设是9527,运行


ls /proc/9527/fd

会看到结果 0 1 2,这3个就是大家不能更熟悉的stdin、stdout、stderr,所以程序在初始化以后就已经打开了3个文件,那么在1024的限制了还能打开1021个,i从0开始计数,于是当i计数到1021的时候,就是在尝试打开第1025个文件,所以失败了。这下是不是将标准输入输出与文件的知识关联起来更好理解了?


至于如何将1024的限制修改为更大,这个网上有太多太多的文章,这里就不在赘述。

当go get遇上gitlab

yhf_szb 发表了文章 • 0 个评论 • 400 次浏览 • 2017-11-30 23:15 • 来自相关话题

前言

go get命令可以说是golang开发者最常用的命令了,通过它我们可以轻松获得各种开源仓库中的包,并且比较方便的在不同的开发机快速部署开发环境。

此处应有版本依赖... 查看全部

前言


go get命令可以说是golang开发者最常用的命令了,通过它我们可以轻松获得各种开源仓库中的包,并且比较方便的在不同的开发机快速部署开发环境。



此处应有版本依赖的问题,但听说新版的go会处理。



但作为企业行为,不是所有的代码包都适合放在公开的网站上,而开源的又适用于中小型企业的自建git仓库工具中,gitlab无疑是耀眼的一个,如果配合docker,一键部署简直不要太舒服。


自建仓库的go get


其实golang在设计的时候是可以支持go get获取自建仓库的,详细原理网上很多,不罗嗦,简单讲,当执行go get your-web.com/your-project的时候,go其实会提交一个HTTP GET 到网址https://you-web.com/your-project?go-get=1,此时如果这个网址能在headmeta标签中返回以下格式的内容时,就可以告诉go客户端应该再到哪里获取仓库。



注意,默认情况下go会且仅会从https的网址获取数据!



<html>
<head>
<meta content="szyhf/go-dicache git https://github.com/szyhf/go-dicache" name="go-import">
</head>
</html>

其中meta标签中的name是必填项,内容必须是go-import,而contat的格式为导入路径 VCS类型 仓库路径,例如,上述代码的含义就是从https://github.com/szyhf/go-dicache下载git仓库并放到导入路径为szyhf/go-dicache的$GOPATH中。



至于域名怎么能访问,怎么输出这个meta,相信对于各位童鞋来说肯定是不是什么problem,跳过



更多说明可以看这里go get命令


遇上gitlab


那么对于自建仓库的gitlab,应该怎么实现这个功能呢?其实gitlab很早就支持了go get,例如你的gitlab网站部署在gitlab.hello.com,你要get的项目是gitlab.hello.com/foo/bar,那么直接执行go get gitlab.hello.com/foo/bar就可以了,gitlab会自动在返回的网页中设置合适的meta标签的。


但实际使用的时候,我们知道,很多时候我们之所以用自建的gitlab,是因为这个仓库见不得光,说白了,我们自己git clone的时候还需要输入一下密码,go get显然也绕不过这个问题。


而默认情况下,gitlab返回的meta标签中的url是https类型的,而实际上更多时候,我们都是通过ssh的方式实现获取仓库,因此,我们需要对gitlab做一定的改造。


当前笔者使用的gitlab版本是9.3.6,对go get的支持是用过ruby-rails中的Middleware的方式实现的,很传统,如果懂ruby的话可以试试直接改,文件是gitlab/embedded/service/gitlab-rails/lib/gitlab/middleware/go.rb,此处不多说。



主要考虑要改源代码不是很优雅,特别是要处理gitlab的升级的时候。



此处给一个不需要懂ruby的非侵入式方案,因为公司的gitlab是搭配nginx使用的,所以在处理对gitlab的请求的时候,加入以下配置,可以达到一样的效果:


if ($http_user_agent ~* "go") {
return 200 "<!DOCTYPE html><head><meta content='$host$uri git ssh://git@$host:$uri.git' name='go-import'></head></html>";
}

简单解释一下,来自go get的HTTP请求中,User Agent都是以go作为开头的,而且go也不会跟现在任何主流浏览器冲突,所以当发现$http_user_agentgo开头的时候,直接返回一个固定的字符串,字符串中注意仓库路径的拼接要加上ssh://,要不go1.8以下的版本无法识别。



上述是我第一次的方案,go get gitlab.hello.com/foo/bar成功,顺利按照预期工作。



然后工作了一阵子之后忽然又出现了新的问题,subpackage


原因很简单,当我们在go get某个项目时,如果这个项目依赖于gitlab.hello.com/foo/bar/you/hu包,那么go get实际提交的请求会变成https://gitlab.hello.com/foo/bar/you/hu,而实际上并不存在这个仓库,如果按方案1的实现逻辑,会尝试下载git@gitlab.hello.com/foo/bar/you/hu.git


很遗憾,这个仓库并不存在,真正存在的是gitlab.hello.com/foo/bar.git,那么应该怎么处理呢?结合nginx的正则表达式重定位的功能,更新的配置如下:


location ~* ^/[^/]+/[^/]+$ {
if ($http_user_agent ~* '^go.*') {
return 200 "<!DOCTYPE html><head><meta content='$host$uri git ssh://git@$host:$uri.git' name='go-import'></head></html>";
}
proxy_cache off;
proxy_pass http://gitlab-workhorse;
}
location ~* ^/(?<holder>[^/]+)/(?<project>[^/]+)/.*$ {
set $goRedirect 'https://$host/$holder/$project?$args';
if ($http_user_agent ~* '^go.*') {
return 301 $goRedirect;
}
proxy_cache off;
proxy_pass http://gitlab-workhorse;
}

其中proxy_cache off;proxy_pass http://gitlab-workhorse;是gitlab官方文档中给出的设置。



其实很容易理解。



主要来解释一下两个location,首先:


~*表示开始不区分大小写地匹配后边给出的正则。


正则^/[^/]+/[^/]+$是为了匹配形如/foo/bar的路径结构,如果匹配成功,继续检查User-Agent,如果符合go,则按第一个方案返回结果,如果不符合,则按一般的gitlab请求进行处理。


正则^/(?<holder>[^/]+)/(?<project>[^/]+)/.*$是为了匹配形如/foo/bar/you/hu/hu的结构,其中的小括号表示对其第一二个斜杠之间的字符串进行捕捉,并赋值给变量$holder$project,然后判定User-Agent,如果符合go,则将请求重定位给/foo/bar,也就会再交给第一个正则处理,最后获得一致的结果。


显然第二个方案比第一个方案复杂了不少,但也都是很标准的nginx配置逻辑,未必优雅,但还是很实用的。



一般来说小型企业的代码库并不会有很高的访问频率,哪怕proxy稍微慢一点,影响也不大。



Docker


如果使用docker版并使用了docker-compose,配置文件中的选项可以参考如下:


environment:
GITLAB_OMNIBUS_CONFIG: |
nginx['custom_gitlab_server_config'] = "location ~* ^/[^/]+/[^/]+$$ {\n if ($$http_user_agent ~* '^go.*') {\n return 200 \"<!DOCTYPE html><html><head><meta content='$$host$$uri git ssh://git@$$host:$$uri.git' name='go-import'></head></html>\";\n }\n proxy_cache off;\n proxy_pass http://gitlab-workhorse;\n}\nlocation ~* ^/(?<holder>[^/]+)/(?<project>[^/]+)/.*$$ {\n set $$goRedirect 'https://$$host/$$holder/$$project?$$args';\n if ($$http_user_agent ~* '^go.*') {\n return 301 $$goRedirect;\n }\n proxy_cache off;\n proxy_pass http://gitlab-workhorse;\n}"


使用\$\$可以防止参数被当成环境变量使用。



小结


代理的思维方式可以解决很多问题。


公众号广告=。=


原文链接
公众号二维码

【社区福利】使用优惠价格购买《Go Web 编程》

zhaoyun4122 回复了问题 • 6 人关注 • 6 个回复 • 1269 次浏览 • 2017-11-30 16:37 • 来自相关话题