如何进行架构设计
难点不在于架构设计的好不好,而在于细节是否做得妥当,或者叫做你的架构是否基于最佳实践。
依据
- 用户需求
- 团队配置
- 技术成熟度
用户需求
- 可登陆的增删改查
团队配置
- 会前后端的全栈工程师
技术成熟度
- 假设只会 Rails + Vue / React
什么是前后端分离
这里说的前端和后端是指前端代码和后端代码,不指人。
不分离:传统的后端工程师从数据库、Redis读取数据渲染到 HTML 中,HTML 需要引用 js、css ,但是现代前端代码代码都是打包生成的(style.xxx.css、main.xxx.js),所以无法在 HTML 中提前引用这些 js、css,所以只能用 Rails(插件 webpacker) 读取 webpack 的内容,再反向写到 HTML 中。
分离:前端自己负责 HTML,自己搭建静态服务器给用户访问,如:Ngnix 或 Node等等,好处是 HTML 可以直接用 webpack 的插件,直接把打包后生成的文件写到 HTML 中,数据只通过 AJAX 获取。
注意前后端代码不一定要交给两个人写,可以由一个人写
用户需求
视觉稿
用例图
用例图释义:把使用的例子全部画出来
表设计
有哪些表?
- users / records / tags
- 还有 taggings 表,表示 records 和 tags 的关联
- password_reset_requests 记录所有重置请求
每个表有哪些属性
- 不用一次想好,但命名一定要准确
- 从简单的需求出发,逐渐迭代
users 表
从需求出发
- 注册:邮箱、密码、确认密码
- 数据库需要存密码吗?不需要,只存密文
- 数据库需要存确认密码吗?不需要
- 如何加密?使用最佳实践 has_secure_password
- 密文叫什么?使用最佳实践 password_digest
- 注册之后需要发欢迎邮件吗? 使用 mailer
- 需要强制用户验证邮箱吗? 可以不强制,也可以强制
结论
- users 表含有 email 和 password_digest
开始实现
步骤
- 创建 model
- console 操作 user
- 创建 controller
- 配置 routes
- 配置 mailer
- 创建 mailer
- 使用 HttpClient
- 创建 rspec 测试
- 改代码,测试
- 改代码,测试
- 改代码,测试
约定
- RESTful 接口风格
- post /users 就是注册接口,不能不加 s
- 文件目录
创建数据库
bin/rails db:create
创建 User 表和 Model
bin/rails db:migrate
bin/rails g model User email:string password_digest:string
使用控制台增删改查
bin/rails console
# 创建用户
> u = User.new
> u.email = '1.qq.com'
> u.password_digest = 'xxxxxx'
> u.save
# 查
User.all # 全部
User.all[0] # 第一个 或 User.first、User.second(用英文即可)
不用写任何代码,为什么就可以进行增删改查呢?这才是一个成熟的框架应该内置好的。
上述操作有个问题,我们不能直接赋值 password_digest
- 搜索 rails has_secure_password
- 找到 Gemfile 打开注释
gem 'bcrypt', '~> 3.1.7'
- 然后再 User 上加这句话
has_secure_password
- 安装依赖
bin/bundle install
再次创建
bin/rails console
# 创建用户
> u = User.new
> u.email = '2.qq.com'
> u.password = 'xxxxxx'
> u.password_confirmation = 'xxxxxx'
> u.save
使用 http 请求来创建用户
首先在 routes 里添加
get '/users', to: 'users#index'
get '/users/:id', to: 'users#show'
post '/users', to: 'users#create'
delete '/users/:id', to: 'users#destroy'
patch '/users/:id', to: 'users#update'
这五个增删改查几乎是每个表都要写,既然这么麻烦,于是 rails 就提供了另一个方法,等价于写了上面五句话
resources :users
使用 bin/rails routes
即可查看所有 routes 得以验证
创建 controller
bin/rails g controller users
然后在 controller 定义一个 create 方法
class UsersController < ApplicationController
def create
user = User.new
user.email = params[:email]
user.password = params[:password]
user.password_confirmation = params[:password_confirmation]
user.save
end
end
但是测试时发现没有传 password_confirmation 竟然也能成功保存,所以我们需要加上非空校验
在 user.rb 中加入
class User < ApplicationRecord
has_secure_password
validates_presence_of :email
validates_presence_of :password, :password_confirmation, on: [:create] # 只在创建时校验
end
并加上返回响应
class UsersController < ApplicationController
def create
user = User.new
user.email = params[:email]
user.password = params[:password]
user.password_confirmation = params[:password_confirmation]
if user.save
render json: user, status: 200
else
render json: user.errors, status: 400
end
end
end
当我们什么都没传时,发现 password 重复报错了两遍,可能是 has_secure_password 也做了校验,所以移除我们自己的
class User < ApplicationRecord
has_secure_password
validates_presence_of :email
validates_uniqueness_of :email
validates_presence_of :password_confirmation, on: [:create] # 只在创建时校验
end
再加上更完善的校验
class User < ApplicationRecord
has_secure_password
validates_presence_of :email
validates_presence_of :password_confirmation, on: [:create]
validates_format_of :email, with: /.+@.+/
validates_length_of :password, minimum: 6, on: [:create]
end
校验国际化
- 搜索 rails i18n
- 在
config/locales/
下创建文件zh-CN.yml
- 在
config/initializers/
下创建locale.rb
文件
# config/initializers/locale.rb
# Where the I18n library should search for translation files
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]
# Permitted locales available for the application
I18n.available_locales = [:en, 'zh-CN']
# Set default locale to something other than :en
I18n.default_locale = 'zh-CN'
- 根据报错依次追加内容
zh-CN:
activerecord:
errors:
models:
user:
attributes:
password:
blank: 密码不能为空
too_short: 密码不能少于 %{count} 位
email:
blank: 邮箱不能为空
invalid: 邮箱格式不合法
password_confirmation:
blank: 确认密码不能为空
额外,优化一下上面的这部分代码
class UsersController < ApplicationController
def create
user = User.new
user.email = params[:email]
user.password = params[:password]
user.password_confirmation = params[:password_confirmation]
if user.save
render json: { resource: user }, status: 200
else
render json: { errors: user.errors }, status: 400
end
end
end
第一步
class UsersController < ApplicationController
def create
user = User.new({ email: params[:email], password: params[:password], password_confirmation: params[:password_confirmation] })
if user.save
render json: { resource: user }, status: 200
else
render json: { errors: user.errors }, status: 400
end
end
end
第二步使用 permit 方法
class UsersController < ApplicationController
def create
user = User.new params.permit(:email, :password, :password_confirmation)
if user.save
render json: { resource: user }, status: 200
else
render json: { errors: user.errors }, status: 400
end
end
end
第三步
class UsersController < ApplicationController
def create
user = User.new create_params
user.save
render_resource user
end
def create_params
params.permit(:email, :password, :password_confirmation)
end
def render_resource(resource)
if resource.valid?
render json: {resource: resource}, status: 200
else
render json: {errors: resource.errors}, status: 400
end
end
end
第四步,使用 create 方法,等价于 先 new 再 save
class UsersController < ApplicationController
def create
user = User.create create_params
render_resource user
end
def create_params
params.permit(:email, :password, :password_confirmation)
end
end
# render_resource 方法已提升到 ApplicationController 中
第五步,很明显 user 声明了直接使用
class UsersController < ApplicationController
def create
render_resource User.create create_params
end
def create_params
params.permit(:email, :password, :password_confirmation)
end
end
发送邮件
- bin/rails generate mailer UserMailer
- app/mailers/user_mailer
class UserMailer < ApplicationMailer
def welcome_email(user)
@user = user
@url = 'https://www.baidu.com'
mail(to: @user.email, subject: 'Welcome to My Awesome Site')
end
end
- app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
default from: '529743595@qq.com'
layout 'mailer'
end
- app/views./user_mailer/welcome_email.html.erb
<p>
<%= @user.email %> 您好,
</p>
<p>
欢迎来到 Morney,情记一笔把!
</p>
- config/environments/development.rb
# config.action_mailer.raise_delivery_errors = false
#
# config.action_mailer.perform_caching = false
config.action_mailer.delivery = :smtp
config.action_mailer.raise_delivery_errors = true
config.action_mailer.perform_caching = false
config.action_mailer.smtp_settings = {
address: ENV['smtp_domain'],
port: ENV['smtp_port'],
domain: ENV['smtp_domain'],
user_name: ENV['smtp_username'],
password: ENV['smtp_password'],
authentication: ENV['smtp_authentication'],
enable_starttls_auto: ENV['smtp_enable_starttls_auto'],
}
config.action_mailer.preview_path = "#{Rails.root}/spec/mailers/previews"
- 用 dotenv 创建环境变量,Gemfile
gem 'dotenv-rails'
- config/application.rb
Bundler.require(*Rails.groups)
Dotenv::Railtie.load
- .env
export smtp_username=''
export smtp_password=''
export smtp_domain='smtp.qq.com'
export smtp_port='587'
export smtp_authentication='plain'
export smtp_enable_starttls_auto=true
export mailer_sender=''
- .env.local(并添加至 .gitignore)
export smtp_username='xxxxx'
export smtp_password='xxxxxx'
- app/models/user.rb
class User < ApplicationRecord
has_secure_password
validates_presence_of :email
validates_uniqueness_of :email
validates_presence_of :password_confirmation, on: [:create]
validates_format_of :email, with: /.+@.+/, if: :email
validates_length_of :password, minimum: 6, on: [:create], if: :password
after_create :send_welcome_email
def send_welcome_email
UserMailer.welcome_email(self).deliver_now
end
end
网友评论