【Ruby on Rails实战】3.5 注册功能
注册页面成功显示之后,我们开始实现注册功能~~
1、功能描述
(1)注册时需要填写信息:用户名、邮箱、密码、确认密码、选择角色
(2)用户角色分为普通用户、管理员、超级管理员,后期方便对用户进行权限管理
(3)邮箱要求唯一,注册成功发送欢迎邮件到注册邮箱(之后章节讲解)
(4)密码需要通过SSL进行加密之后,存入到数据库
(5)用户名的长度规定小于等于5个字符、密码和确认密码需要一致
2、编辑controller、view、路由
(1)在路由文件config/routes.rb中添加路由,通过此链接来提交在注册页面填写的信息
# 参考代码,无需粘贴
# get 'login' => 'accounts#login'
post 'create_account' => 'accounts#create_account'
代码解析:
- post 'create_account' => 'accounts#create_account'
下面我们会将signup.html.erb文件中form_for后面的链接改成"/create_account",这样点击注册页面的注册按钮,在注册页面填写的数据会通过/create_account链接提交,浏览器通过该链接在路由文件中的指向,找到accounts_controller.rb文件中的create_account方法,在注册页面填写的数据在create_account方法中处理
(2)编辑views/accounts/signup.html.erb文件,将form_for后面的链接改成"/create_account"。
这样在点击注册按钮后,注册页面上填写的信息会提交到/create_account链接对应的create_account方法
#原代码
<%= form_for @account,url:"#" do |f| %>
#改为
<%= form_for @account,url: "/create_account" do |f| %>
(3)在app/controllers/accounts_controller.rb中添加create_account方法。
我们只添加方法,先不处理数据,下面我们在终端先看一下数据的传递形式。
def create_account
end
再来强调一下网页请求路径:
用户在注册页面填写相关的信息后,点击注册按钮会将数据提交到/create_account链接对应在accounts_controller.rb文件中的create_account方法,在create_account方法中将提交的数据进行处理
PS:之所以多次强调网页请求路径,是因为理解了这个,你才知道为什么我们代码要这样写,才能对项目有一个大体的掌握
3、获取页面提交的数据,在数据库accounts表新建用户
(1)rails s启动项目,在浏览器中打开注册页面http://localhost:3000/signup(注意mac电脑是http://192.168.33.10:3000/signup)随便填写信息,点击注册按钮,回到终端,能看到终端返回的日志中包含以下params数据。
- Parameters: {"utf8"=>"✓", "account"=>{"name"=>"xzn", "email"=>"xzn@stonescloud.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "role"=>"0"}, "commit"=>"注册”}
我们在controller中的create_account方法中操作的就是上面的数据,params是一个二级哈希,里面有两个key分别为utf8、account,其中account又指向了一个第二级的哈希,这个哈希里面有5个key分别为name、email、password、password_confirmation、role,在create_account方法中取出email的值应该像下面这样:
email = params[:account][:email]
(2)回到sublime,编辑我们刚刚在accounts_controller.rb中添加的create_account方法,在方法中取params哈希里面的值,进行if判断、保存到数据库等操作
def create_account
#取出哈希param中的name、email等元素
name = params[:account][:name]
email = params[:account][:email]
password = params[:account][:password]
password_confirmation = params[:account][:password_confirmation]
role = params[:account][:role]
#status为0,代表账号是激活状态;为1,代表账号是未激活状态
#role角色 0普通用户 1管理员 2超级管理员
#角色为管理员的时候(即role等于1),账号要被设为未激活状态(即status等于1)
#其他角色注册账号,状态默认为激活状态
status = 0
if role.to_i == 1
status = 1
end
#从账户表中查找是否有相同的email,有相同的email说明该邮箱已经被注册过了
account = Account.find_by(email:email)
#创建一个Account类的对象
@account = Account.new
#先判断name、email是否为空
if name.blank? || email.blank?
flash.notice = "用户名或者邮箱不能为空"
render :signup
#判断account是否存在,即是否存在相同邮箱的用户
elsif account
flash.notice = "邮箱已注册"
render :signup
#判断名字的长度是否大于5
elsif name.length > 5
flash.notice = "用户名长度不能大于5"
render :signup
#判断密码和确认密码是否一致
elsif password != password_confirmation
flash.notice = "两次密码输入不一致"
render :signup
#上面条件全部不符合,会进入else语句,将填写的信息保存到数据中
else
@account.status = status
@account.name = name
@account.email = email
@account.password = password
@account.role = role
#.save保存成功时,返回true,失败时返回false
boolean = @account.save
#boolean为true时说明account保存成功
if boolean
flash.notice = "注册成功!请登录"
redirect_to :login #注册成功,将页面重定向到登录页面
else
flash.notice = "注册失败!请重新注册"
render :signup
end
end
end
代码解析:
-
account = Account.find_by(email:email)
find_by我们在3.2节讲过,上面的语句相当于Account.where(email:email).first。
Account.where(email:email)返回的结果类型是ActiveRecord::Relation对象集合,而不是Account对象,不能直接使用,需要加first得到Account对象,才能调用Account对象里面的role、status等字段 -
@account = Account.new
先创建一个Account类的对象,命名为@account,@account是一个实例变量,里面保存了Account对象的内存地址,这个对象的name、email等字段我们会在下面赋值。为什么我们要在 if name.blank? || email.blank? 语句的上面定义这条语句呢?因为if语句里面有render :signup会用到@account这个实例变量。具体为什么会用到,在下面第四节讲解 -
flash.notice = "用户名或者邮箱不能为空"
render :signup
3.3章第6节第(4)点,在app/views/layouts/application.html.erb文件中添加的代码,这些代码实现了flash.notice的效果。
flash.notice的用法:
在controller文件中给flash.notice赋值,后面一定要跟render语句或者redirect_to语句。因为flash.notice语句在加载下一个页面(render、redirect_to对应的页面)才生效。
-
render :signup
意思是回到注册页面,相当于代码:
render "signup"
render "signup.html.erb"
render action: :signup
render action: "signup.html.erb"
render template: "accounts/signup"
render template: "accounts/signup.html.erb"
这些情况不需要记住,知道有这种写法就行,以后碰到类似的代码避免疑惑。 -
redirect_to :login
注册成功后,重定向到登录页面,相当于代码:
redirect_to :action => 'login'
4、redirect_to和render的区别及应用
(1)区别
-
redirect_to:浏览器会发送一个新的请求,执行这个请求对应的action(即controller中的实例方法),再渲染action对应的页面。
举例:redirect_to :login的意思是会执行对应controller中的login方法(action),然后再渲染login.html.erb页面 -
render:浏览器不会发送新的请求,不执行action,直接渲染action对应的页面。
举例:render :login的意思是直接渲染login.html.erb页面,不会执行login方法(action)。
(2)我们来看一段create_account方法中的代码,为什么要在if控制结构外面先定义一个@account实例变量呢?
#创建一个Account类的对象
@account = Account.new
if name.blank? || email.blank?
flash.notice = "用户名或者邮箱不能为空"
render :signup
elsif account
flash.notice = "邮箱已注册"
render :signup
elsif name.length > 5
flash.notice = "用户名长度不能大于5"
render :signup
elsif password != password_confirmation
flash.notice = "两次密码输入不一致"
render :signup
else
...
end
代码解析:
需要先看一下render :signup对应的signup.html.erb文件里面,有这样一条语句:
<%= form_for @account,url: "/create_account" do |f| %>
这条语句中的@account实例变量是在action方法signup中定义的,然后再传给signup.html.erb文件。
signup方法如下:
def signup
@account = Account.new
end
根据我们上面讲的「区别」,render不会执行action方法直接渲染页面,所以执行render :signup之后,不会执行controller中signup方法,会直接渲染signup.html.erb页面,所以@account实例变量不会被定义,上面的语句就会报错。
所以我们在执行render :signup语句之前,需要先确保@account被定义了。
(3)什么时候用redirect_to,什么时候用render呢?我们再截取create_account方法中的一段代码来看一下
if boolean
flash.notice = "注册成功!请登录"
redirect_to :login #注册成功,将页面重定向到登录页面
else
flash.notice = "注册失败!请重新注册"
render :signup
end
boolean为true时,说明account在数据库保存成功,为什么这时用redirect_to :login,而不是用render :login呢?
当使用 render ,用户提交表单之后紧接着刷新页面,此时表单就会重复被提交,数据可能会出现重复数据
当使用 redirect_to,用户点击注册按钮提交表单,提交表单之后紧接着刷新页面,此时浏览器的请求链接是服务器重定向到/login链接,而不是/create_account链接。刷新页面时的/create_account链接与重定向/login链接不重复,所以不会出现重复提交数据的情况。
当涉及到表单提交时,数据库保存成功时使用 redirect_to ,保存失败使用 render ,使用render除了避免表单重复提交之外,如果表单提交失败,之前表单填写的信息还会存在,提高用户体验。
5、密码加密保存
SSL信息加密:
就是把明码的输入文件用加密算法转换成加密的文件以实现数据的保密。加密的过程需要用到密钥来加密数据然后再解密。没有了密钥,就无法解开加密的数据。
加密解密方法的内容我们就不详细解释了,大家就知道这是通过密钥加密的技术就可以。之所以将这个文件在lib文件夹下新建,我们再1.3节中提到过,lib文件夹下放一些自定的module、class
(1)创建lib/des.rb文件,复制下面内容
class Des
#定义常量
ALG = 'DES-EDE3-CBC'
#你的密钥,可以自己设定
KEY = "5iLuoPan"
#任意固定长度为8的值
DES_KEY = "XWERDFGS"
#加密方法,之后我们调用Des.des_encode(password)就能对密码进行加密
def self.des_encode(str)
des = OpenSSL::Cipher::Cipher.new(ALG)
des.pkcs5_keyivgen(KEY, DES_KEY)
des.encrypt
cipher = des.update(str)
cipher << des.final
#进行Base64编码,才能保存到数据库
return Base64.encode64(cipher).gsub(/(^\s*)|(\s*$)/, "")
end
#解密方法,登录的时候调用
def self.des_decode(str)
str = Base64.decode64(str)
des = OpenSSL::Cipher::Cipher.new(ALG)
des.pkcs5_keyivgen(KEY, DES_KEY)
des.decrypt
des.update(str) + des.final
end
end
(2)编辑accounts_controller.rb文件,create_account方法中
#原代码
@account.password = password
#改为
@account.password = Des.des_encode(password)
(3)修改config/application.rb配置文件,使程序能加载lib文件夹下的文件,添加下面代码
# 参考代码,无需粘贴
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
config.autoload_paths += %W(#{config.root}/lib)
如果注册时候报错,NameError: uninitialized constant Des 那就是config.autoload_paths += %W(#{config.root}/lib)这条语句没有生效,这个时候退出vagrant,运行vagrant ssh重新登录vagrant就可以了。