您的位置:首页 > 编程语言

gitolite 通过测试服务器发布代码到正式服务器一步完成

2016-03-04 11:05 183 查看
gitolite的部署请参考我的上边博客: http://blog.chinaunix.net/uid-30234663-id-5384408.html
gitolite的内置了Git系统支持服务端hook和客户端hook,使用hook可以实现一些git相关的自动化任务,比如类似Github的博客
系统。本文基于Gitolite构建的git服务端,实现服务端hook,Git系统支持服务端hook和客户端hook,使用hook可以实现一些
git相关的自动化任务,比如类似Github的博客系统。本文基于Gitolite构建的git服务端,实现服务端hook,

1:代码部署服务器介绍:

系统版本号
服务器ip地址
服务器的公里特分布
centos 6.5
192.168.0.171
是测试服务器,代码推送到次,在有他到正式服务器
centos 6.5
192.168.0.170
正式服务器
win7
192.168.0.180
开发的代码存放位置

2:gitolite的安装配置解释:

A:最好是192.168.0.171的admin是git用户,在使用git用户把gitolite-admin给clone下来,在把win7系统的
用户秘钥文件给提交上去,作为普用户,在在192.168.0.170上赋予192.168.0.171的git用户的rw权限

3:git的代码发布实例图示



4:开始上干货了,我们来部署代码,并创建代码存放位置,和备份目录:

192.168.0.171
源码位置:/srcWeb/
备份位置:/testWeb/
192.168.0.170 源码位置:/src/web
192.168.0.180
随便代码位置

5:post-recevice钩子函数,是一个在git执行过push完成后的调用执行的一个脚本,

Git挂钩

和其他版本控制系统一样,当某些重要事件发生时,Git 以调用自定义脚本。有两组挂钩:客户端和服务器端。客户端挂钩用于客户端的操作,如提交和合并。服务器端挂钩用于 Git 服务器端的操作,如接收被推送的提交。你可以随意地使用这些挂钩,下面会讲解其中一些。

安装一个挂钩

挂钩都被存储在 Git 目录下的hooks子目录中,即大部分项目中的.git/hooks。
Git
默认会放置一些脚本样本在这个目录中,除了可以作为挂钩使用,这些样本本身是可以独立使用的。所有的样本都是shell脚本,其中一些还包含了Perl的
脚本,不过,任何正确命名的可执行脚本都可以正常使用 — 可以用Ruby或Python,或其他。在Git
1.6版本之后,这些样本名都是以.sample结尾,因此,你必须重新命名。在Git
1.6版本之前,这些样本名都是正确的,但这些样本不是可执行文件。
把一个正确命名且可执行的文件放入 Git 目录下的hooks子目录中,可以激活该挂钩脚本,因此,之后他一直会被 Git 调用。随后会讲解主要的挂钩脚本。

客户端挂钩

有许多客户端挂钩,以下把他们分为:提交工作流挂钩、电子邮件工作流挂钩及其他客户端挂钩。

提交工作流挂钩



4个挂钩被用来处理提交的过程。pre-commit挂钩在键入提交信息前运行,被用来检查即将提交的快照,例如,检查是否有东西被遗漏,确认测试是否运
行,以及检查代码。当从该挂钩返回非零值时,Git 放弃此次提交,但可以用git commit
--no-verify来忽略。该挂钩可以被用来检查代码错误(运行类似lint的程序),检查尾部空白(默认挂钩是这么做的),检查新方法(译注:程序
的函数)的说明。
prepare-commit-msg挂钩在提交信息编辑器显示之前,默认信息被创建之后运行。因此,可以有机会在提交
作者看到默认信息前进行编辑。该挂钩接收一些选项:拥有提交信息的文件路径,提交类型,如果是一次修订的话,提交的SHA-1校验和。该挂钩对通常的提交
来说不是很有用,只在自动产生的默认提交信息的情况下有作用,如提交信息模板、合并、压缩和修订提交等。可以和提交模板配合使用,以编程的方式插入信息。
commit-msg挂钩接收一个参数,此参数是包含最近提交信息的临时文件的路径。如果该挂钩脚本以非零退出,Git 放弃提交,因此,可以用来在提交通过前验证项目状态或提交信息。本章上一小节已经展示了使用该挂钩核对提交信息是否符合特定的模式。
post-commit挂钩在整个提交过程完成后运行,他不会接收任何参数,但可以运行git log -1 HEAD来获得最后的提交信息。总之,该挂钩是作为通知之类使用的。
提交工作流的客户端挂钩脚本可以在任何工作流中使用,他们经常被用来实施某些策略,但值得注意的是,这些脚本在clone期间不会被传送。可以在服
务器端实施策略来拒绝不符合某些策略的推送,但这完全取决于开发者在客户端使用这些脚本的情况。所以,这些脚本对开发者是有用的,由他们自己设置和维护,
而且在任何时候都可以覆盖或修改这些脚本。

E-mail工作流挂钩

有3个可用的客户端挂钩用于e-mail工作流。当运行git am命令时,会调用他们,因此,如果你没有在工作流中用到此命令,可以跳过本节。如果你通过e-mail接收由git format-patch产生的补丁,这些挂钩也许对你有用。
首先运行的是applypatch-msg挂钩,他接收一个参数:包含被建议提交信息的临时文件名。如果该脚本非零退出,Git 放弃此补丁。可以使用这个脚本确认提交信息是否被正确格式化,或让脚本编辑信息以达到标准化。

一个在git
am运行期间调用是pre-applypatch挂钩。该挂钩不接收参数,在补丁被运用之后运行,因此,可以被用来在提交前检查快照。你能用此脚本运行测
试,检查工作树。如果有些什么遗漏,或测试没通过,脚本会以非零退出,放弃此次git am的运行,补丁不会被提交。
最后在git am运行期间调用的是post-applypatch挂钩。你可以用他来通知一个小组或获取的补丁的作者,但无法阻止打补丁的过程。

其他客户端挂钩

pre-
rebase挂钩在衍合前运行,脚本以非零退出可以中止衍合的过程。你可以使用这个挂钩来禁止衍合已经推送的提交对象,Git
pre-rebase挂钩样本就是这么做的。该样本假定next是你定义的分支名,因此,你可能要修改样本,把next改成你定义过且稳定的分支名。
在git checkout成功运行后,post-checkout挂钩会被调用。他可以用来为你的项目环境设置合适的工作目录。例如:放入大的二进制文件、自动产生的文档或其他一切你不想纳入版本控制的文件。
最后,在merge命令成功执行后,post-merge挂钩会被调用。他可以用来在 Git 无法跟踪的工作树中恢复数据,诸如权限数据。该挂钩同样能够验证在 Git 控制之外的文件是否存在,因此,当工作树改变时,你想这些文件可以被复制。

服务器端挂钩

除了客户端挂钩,作为系统管理员,你还可以使用两个服务器端的挂钩对项目实施各种类型的策略。这些挂钩脚本可以在提交对象推送到服务器前被调用,也
可以在推送到服务器后被调用。推送到服务器前调用的挂钩可以在任何时候以非零退出,拒绝推送,返回错误消息给客户端,还可以如你所愿设置足够复杂的推送策
略。

pre-receive 和 post-receive

处理来自客户端的推送(push)操作时最先执行的脚本就是 pre-receive 。它从标准输入(stdin)获取被推送引用的列表;如果它退出时的返回值不是0,所有推送内容都不会被接受。利用此挂钩脚本可以实现类似保证最新的索引
中不包含非fast-forward类型的这类效果;抑或检查执行推送操作的用户拥有创建,删除或者推送的权限或者他是否对将要修改的每一个文件都有访问
权限。
post-receive 挂钩在整个过程完结以后运行,可以用来更新其他系统服务或者通知用户。它接受与 pre-receive 相同的标准输入数据。应用实例包括给某邮件列表发信,通知实时整合数据的服务器,或者更新软件项目的问题追踪系统 ——
甚至可以通过分析提交信息来决定某个问题是否应该被开启,修改或者关闭。该脚本无法组织推送进程,不过客户端在它完成运行之前将保持连接状态;所以在用它
作一些消耗时间的操作之前请三思。

update

update 脚本和 pre-receive 脚本十分类似。不同之处在于它会为推送者更新的每一个分支运行一次。假如推送者同时向多个分支推送内容,pre-receive 只运行一次,相比之下 update
则会为每一个更新的分支运行一次。它不会从标准输入读取内容,而是接受三个参数:索引的名字(分支),推送前索引指向的内容的 SHA-1
值,以及用户试图推送内容的 SHA-1 值。如果 update 脚本以退出时返回非零值,只有相应的那一个索引会被拒绝;其余的依然会得到更新。

6:介绍post-receice
我的项目在:/home/git/repositories/pro_order.git/hooks
所以应该在这个项目的hooks目录下面新建一个post-receive文件,应为那些带有.sample是注释掉的文件,不生效
[git@test3 hooks]$ cat post-receive
#!/bin/sh
#
# An example hook script for the "post-receive" event.
#
# The "post-receive" script is run after receive-pack has accepted a pack
# and the repository has been updated. It is passed arguments in through
# stdin in the form
#
# For example:
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
#
# see contrib/hooks/ for a sample, or uncomment the next line and
# rename the file to "post-receive".

#. /usr/share/git-core/contrib/hooks/post-receive-email
PROJECT_NAME=ny_order
SRC_DEPLOY_DIR=/srcWeb/$PROJECT_NAME
TEST_DEPLOY_DIR=/testWeb/$PROJECT_NAME
TIME=`date +%Y%m%d%H%M`
unset GIT_DIR

cd $SRC_DEPLOY_DIR
if [ "`git branch`" = "" ];then
echo "Now,I'm create master branch" > createMasterBranch
git add createMasterBranch
git commit -m "add file createMasterBranch $TIME"
git push origin master

elif [ "`git branch`" != "" ];then
git checkout -f master
git add .
git commit -m "commit the new files which the program generate"
git pull origin master
fi
if [ ! -f controlFile ];then

echo "There is no controlFile file exist in 'The root directory of the
$PROJECT_NAME project',i will do nothing. Please touch the controlFile
file."
exit 404
fi
dos2unix controlFile
_1thLine="`sed -n '1p' controlFile`"
echo $_1thLine

if [ "$_1thLine" = "master" ];then
/bin/sh -x /home/git/hooks/$PROJECT_NAME/push-master.sh

elif [ "$_1thLine" != "master" ] && [ "`echo $_1thLine | grep ":"`" = "" ];then
echo "Not master branch,the branch is $_1thLine"
/bin/sh -x /home/git/hooks/$PROJECT_NAME/push-branch.sh

elif [ "`echo $_1thLine | awk -F ":" '{print $1}'`" = "delete" ];then
echo "delete branch"
/bin/sh -x /home/git/hooks/$PROJECT_NAME/delete-branch.sh

else
echo "The first line you wrote does not match any value"
exit 1
fi

解释:源码存放位置:SRC_DEPLOY_DIR=/srcWeb/pro_order
源码备份位置:TEST_DEPLOY_DIR=/testWeb/pro_order
unset:去除这个git环境

相对应的目录文件图示如下:
[git@test3 hooks]$# cat delete-branch.sh
#!/bin/sh
#
# An example hook script for the "post-receive" event.
#
# The "post-receive" script is run after receive-pack has accepted a pack
# and the repository has been updated. It is passed arguments in through
# stdin in the form
#
# For example:
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
#
# see contrib/hooks/ for a sample, or uncomment the next line and
# rename the file to "post-receive".

#. /usr/share/git-core/contrib/hooks/post-receive-email
SRC_DEPLOY_DIR=/srcWeb/pro _order
TEST_DEPLOY_DIR=/testWeb/pro_order
TIME=`date +%Y%m%d%H%M`
unset GIT_DIR

cd $SRC_DEPLOY_DIR
dos2unix controlFile
checkBranch=`cat controlFile | head -1 | awk -F ":" '{print $2}'`

git branch -D $checkBranch

cd $TEST_DEPLOY_DIR
git checkout master
git branch -D $checkBranch
cp -p $SRC_DEPLOY_DIR/.gitignore $TEST_DEPLOY_DIR/ -rf
cp -p $SRC_DEPLOY_DIR/controlFile $TEST_DEPLOY_DIR/ -rf
cp -p $SRC_DEPLOY_DIR/* $TEST_DEPLOY_DIR/ -rf
git add .
git commit -m "modified on master at $TIME"
[git@test3 hooks]$ cat push-branch.sh
#!/bin/sh
#
# An example hook script for the "post-receive" event.
#
# The "post-receive" script is run after receive-pack has accepted a pack
# and the repository has been updated. It is passed arguments in through
# stdin in the form
#
# For example:
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
#
# see contrib/hooks/ for a sample, or uncomment the next line and
# rename the file to "post-receive".

#. /usr/share/git-core/contrib/hooks/post-receive-email
SRC_DEPLOY_DIR=/srcWeb/pro_order
TEST_DEPLOY_DIR=/testWeb/pro_order
TIME=`date +%Y%m%d%H%M`
unset GIT_DIR
cd $SRC_DEPLOY_DIR
git checkout master
cd $TEST_DEPLOY_DIR
git checkout master
cp -p $SRC_DEPLOY_DIR/.gitignore $TEST_DEPLOY_DIR/ -rf
cp -p $SRC_DEPLOY_DIR/controlFile $TEST_DEPLOY_DIR/ -rf
cp -p $SRC_DEPLOY_DIR/* $TEST_DEPLOY_DIR/ -rf
git add .
git commit -m "modified on master at $TIME"

cd $SRC_DEPLOY_DIR
git checkout master
dos2unix controlFile
checkBranch=`cat controlFile | head -1`
echo $checkBranch
git branch $checkBranch
git checkout $checkBranch
git pull origin $checkBranch

cd $TEST_DEPLOY_DIR
git branch $checkBranch
git checkout $checkBranch
cp -p $SRC_DEPLOY_DIR/.gitignore $TEST_DEPLOY_DIR/ -rf
cp -p $SRC_DEPLOY_DIR/controlFile $TEST_DEPLOY_DIR/ -rf
cp -p $SRC_DEPLOY_DIR/* $TEST_DEPLOY_DIR/ -rf
git add .
git commit -m "modified on $checkBranch at $TIME"

[git@test3 hooks]$ cat push-master.sh
#!/bin/sh
#
# An example hook script for the "post-receive" event.
#
# The "post-receive" script is run after receive-pack has accepted a pack
# and the repository has been updated. It is passed arguments in through
# stdin in the form
#
# For example:
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
#
# see contrib/hooks/ for a sample, or uncomment the next line and
# rename the file to "post-receive".

#. /usr/share/git-core/contrib/hooks/post-receive-email
HOOKS=/home/git/hooks/pro_order
function push_master(){
SRC_DEPLOY_DIR=/srcWeb/pro_order
TEST_DEPLOY_DIR=/testWeb/pro_order
TIME=`date +%Y%m%d%H%M`
unset GIT_DIR
cd $TEST_DEPLOY_DIR
if [ "`git branch`" = "" ];then
echo "Now,I'm create master branch" > createMasterBranch
git add createMasterBranch
git commit -m "add file createMasterBranch $TIME"
git push origin master
else
echo "master branch is exist"
fi
git checkout master
cp -p $SRC_DEPLOY_DIR/.gitignore $TEST_DEPLOY_DIR/ -rf
cp -p $SRC_DEPLOY_DIR/controlFile $TEST_DEPLOY_DIR/ -rf
cp -p $SRC_DEPLOY_DIR/onlineConfig $TEST_DEPLOY_DIR/ -rf
for file in `ls ${SRC_DEPLOY_DIR}/* |grep -v userfiles`
do
cp $SRC_DEPLOY_DIR/WebContent/$file $TEST_DEPLOY_DIR/ -rf
done
# cp -p $SRC_DEPLOY_DIR/onlineConfig/hibernate-spring.xml $TEST_DEPLOY_DIR/WEB-INF/classes/spring/hibernate-spring.xml
# cp -p $SRC_DEPLOY_DIR/onlineConfig/resourcesConfig.properties $TEST_DEPLOY_DIR/WEB-INF/classes/resourcesConfig.properties
# cp -p $SRC_DEPLOY_DIR/onlineConfig/shopapplication.properties $TEST_DEPLOY_DIR/WEB-INF/classes/shopapplication.properties
# cp -p $SRC_DEPLOY_DIR/onlineConfig/static-spring.xml $TEST_DEPLOY_DIR/WEB-INF/classes/spring/static-spring.xml
# cp -p $SRC_DEPLOY_DIR/onlineConfig/timer-spring.xml $TEST_DEPLOY_DIR/WEB-INF/classes/spring/timer-spring.xml
# cp -p $SRC_DEPLOY_DIR/onlineConfig/allpay.properties $TEST_DEPLOY_DIR/WEB-INF/classes/

git add .
git commit -m "modified on master at $TIME"
dos2unix controlFile
_2thLine=`sed -n 2p controlFile`
if [ "$_2thLine" = "local" ];then
echo "I was committed on test-server master,no push to online server!"
elif [ "$_2thLine" = "online" ];then

echo "I will push to online master !"
git pull origin master
git push origin master
else
echo "No push to online master"
exit 1
fi
}
push_master 2>&1 | tee $HOOKS/push.error

if [ "`cat $HOOKS/push.error | grep -E 'error|failed|warning'|grep -v
error.jsp|grep -v error.html|grep -v error.htm`" == "" ];then
echo "Congratuations,push success!"
else
cat $HOOKS/push.error | grep -E 'error|failed|warning'|grep -v error.jsp|grep -v error.html|grep -v error.htm
echo "Push failed,please check the error message carefully!"
fi
[root@zsh_test02 ny_orderws]# cat test-branch.sh
#!/bin/sh
#
# An example hook script for the "post-receive" event.
#
# The "post-receive" script is run after receive-pack has accepted a pack
# and the repository has been updated. It is passed arguments in through
# stdin in the form
#
# For example:
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
#
# see contrib/hooks/ for a sample, or uncomment the next line and
# rename the file to "post-receive".

#. /usr/share/git-core/contrib/hooks/post-receive-email
SRC_DEPLOY_DIR=/srcWeb/pro_order
TEST_DEPLOY_DIR=/testWeb/pro_order
TIME=`date +%Y%m%d%H%M`
unset GIT_DIR

cd $TEST_DEPLOY_DIR
git checkout master
cp $SRC_DEPLOY_DIR/.gitignore $TEST_DEPLOY_DIR/ -rf
cp $SRC_DEPLOY_DIR/controlFile $TEST_DEPLOY_DIR/ -rf
cp $SRC_DEPLOY_DIR/* $TEST_DEPLOY_DIR/ -rf
git add .
git commit -m "modified on master at $TIME"

dos2unix controlFile
checkBranch=`cat controlFile | head -1 | awk -F ":" '{print $2}'`
echo $checkBranch

git add .
git commit -m "modified on $checkBranch at $TIME"
git checkout $checkBranch

7: 开始准备环境配置:

[root@test3 ~]$ #mkdir /testWeb/pro_order/
[root@test3 ~]$ #mkdir /srcWeb/pro_order/
[root@test3 ~]$ #chown -R git:git /testWeb/pro_order/ && chown -R git:git /srcWeb/pro_order/
[git@test3 ~]$ #git init /srcWeb/pro_order/
[git@test3 pro_order]$ pwd
/srcWeb/pro_order
[git@test3 pro_order]$ git branch;git remote add origin git@127.0.0.1:pro_order{对应的项目名称}
[git@test3 pro_order]$ git remote -v
origin git@127.0.0.1:pro_order (fetch)
origin git@127.0.0.1:pro_order (push)
[git@test3 pro_order]$ git pull origin master{吧代码pull下来}
[git@test3 pro_order]$ vim controlFile
master
online
none
以后再新增加文件就可以自动备份到testWeb下了,
如果想要吧代码再通过钩子函数把代码发布到正式服务器,就需要定义备份目录了:
[root@test3 ~]$ #mkdir /srcWeb/pro_order/
[root@test3 ~]$ #chown -R git:git /testWeb/pro_order/ && chown -R git:git /srcWeb/pro_order/
[git@test3 pro_order]$ pwd
/testWeb/pro_order
[git@test3 pro_order]$git init; git branch;git remote add origin git@远程服务器的ip地址{192.168.0.170 }:pro_order{对应的项目名称}
[git@test3 pro_order]$ git remote -v
origin git@192.168.0.170 :pro_order (fetch)
origin git@192.168.0.170 :pro_order (push)

现在测试服务器上面的认证做完了;
正式服务器上操作:
搭建gitolite服务——>授予本机的git和192.168.0.170的git用户定义一个@admin = admin(171) git(170)
[git@test ~]$ cat gitolite-admin/conf/gitolite.conf
@admin = admin hanye
repo gitolite-admin
RW+ = @admin

repo testing
RW+ = @all
repo pro_order
RW+ = @admin
再提交代码,在相应的项目下天剑post-recevice文件,
[git@test ~]$ cat repositories/hanye.git/hooks/post-receive
#!/bin/sh
#
# An example hook script for the "post-receive" event.
#
# The "post-receive" script is run after receive-pack has accepted a pack
# and the repository has been updated. It is passed arguments in through
# stdin in the form
#
# For example:
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
#
# see contrib/hooks/ for a sample, or uncomment the next line and
# rename the file to "post-receive".

#. /usr/share/git-core/contrib/hooks/post-receive-email
TEST_DEPLOY_DIR=/data/testWeb/pro_order
unset GIT_DIR

cd $TEST_DEPLOY_DIR
git checkout -f master

if [ "`git branch|grep backup`" != "" ];
then
git branch -D backup
git branch backup
else
git branch backup
echo "Now , the branch backup was created!"
fi
git pull origin master

_3thLine="`sed -n '3p' controlFile`"

if [ "$_3thLine" == "none" ];then
echo "Normal push"

elif [ "$_3thLine" == "restart" ];then

# /home/softinstall/newHome/bin/shutdown.sh
# sleep 5s
# /home/softinstall/newHome/bin/startup.sh
echo "no restart"
if [ "`echo $?`" == "0" ];then
echo "YES, ny_api restart success !"
else
echo "Oh,No,restart Failed !"
fi
elif [ "$_3thLine" == "rollback" ];then
git checkout -f backup

else
echo "The third line you wrote does not match any value"
fi
cp -p $TEST_DEPLOY_DIR/* /data/tomcat/pro_order/ -rf
git checkout -f master

开始做认证:
mkdir: 已创建目录 "/data/tomcat/pro_order"/data/testWeb/pro_order

[root@test pro_order]# pwd
/data/testWeb/pro_order
[root@test pro_order]# git init;git branch
初始化空的 Git 版本库于 /data/testWeb/pro_order/.git/
[root@test pro_order]# git checkout -b backup
切换到一个新分支 'backup'
[root@test pro_order]# git checkout master
error: pathspec 'master' did not match any file(s) known to git.
[root@test pro_order]# vim readme.txt
[root@test pro_order]# git status
位于分支 backup

初始提交

未跟踪的文件:
(使用 "git add ..." 以包含要提交的内容)

readme.txt

提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
[root@test pro_order]# git add .
[root@test pro_order]# git status
位于分支 backup

初始提交

要提交的变更:
(使用 "git rm --cached ..." 撤出暂存区)

新文件: readme.txt

[root@test pro_order]# git remote add origin git@127.0.0.1:hanye
[root@test pro_order]# git commit -m "add file readme"
[backup(根提交) 1c5a4a8] add file readme
1 file changed, 1 insertion(+)
create mode 100644 readme.txt
[root@test pro_order]# git checkout backup
已经位于 'backup'
[root@test pro_order]# git checkout -f master
error: pathspec 'master' did not match any file(s) known to git.
[root@test pro_order]# git checkout -b master
切换到一个新分支 'master'
[root@test pro_order]# git checkout backup
切换到分支 'backup'
[root@test pro_order]# git checkout -
切换到分支 'master'
[root@test pro_order]#
这样就做完了正式服务器的认证,开始通过git上传你的代码吧

参考:http://www.myexception.cn/ruby-rails/1874084.html
参考 : https://github.com/premier815/UGRCA
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息