当前位置: 首页 > news >正文

Ruby设计-开发日志

Log

1 产品 Product

1.1 创建 Product

创建名为 project 的 rails 应用

rails new project

创建 Product 模型

rails generate scaffold Product title:string description:text image_url:string price:decimal

这会生成一个 migration ,我们需要进一步修改这个迁移,保证价格拥有 8 位有效数字,同时小数点后保留两位。修改迁移文件

class CreateProducts < ActiveRecord::Migration[7.0]def changecreate_table :products do |t|t.string :titlet.text :descriptiont.string :image_urlt.decimal :price, precision: 8, scale: 2t.timestampsendend
end

然后就可以进行 migrate

rake db:migrate

这里的 rake 可以被理解为一个脚本的管理器,db:migrate 是其中的一个脚本。还有一种说法是 rake 类似与 C 中的 make

最终我们对于数据库的修改,都会被记录在 db/schema.rb 中,比如说现在

ActiveRecord::Schema[7.0].define(version: 2022_12_31_030243) docreate_table "products", force: :cascade do |t|t.string "title"t.text "description"t.string "image_url"t.decimal "price", precision: 8, scale: 2t.datetime "created_at", null: falset.datetime "updated_at", null: falseendend

可以看到基本上与 migration 是一致的,原来的 t.timestamps 时间戳变成了 created_atupdated_at 两个属性。此外主键被叫做 product_id ,并没有在这里显示,这应该是一种默认配置。

1.2 本地服务器

我们输入如下命令,就可以在本地启动服务器

rails s

会看到如下字样

=> Booting Puma
=> Rails 7.0.4 application starting in development

其中的 Puma 似乎是一个线程管理器,每个线程都用于处理来自客户端的一个 request

1.3 表单

app/views/products/_form.html.erb 是一个局部渲染文件,用于当做 product 信息的表单,这个表单会在 new.html.erb, edit.html.erb 这两个文件中用到,如下所示

<h1>New product</h1><!-- 这里对表单进行局部渲染,并且传递局部参数 product -->
<%= render "form", product: @product %><br><div><%= link_to "Back to products", products_path %>
</div>

关于局部渲染,有如下知识:https://blog.csdn.net/weixin_30621711/article/details/96260112

表单的具体内容如下

<!-- form_with 是 rails 所带的一种表单形式,类似的还有 form_for -->
<%= form_with(model: product) do |form| %><!-- product 如果存在问题 --><% if product.errors.any? %><div style="color: red"><h2><%= pluralize(product.errors.count, "error") %> prohibited this product from being saved:</h2><ul><% product.errors.each do |error| %><li><%= error.full_message %></li><% end %></ul></div><% end %><!-- product 的 title --><div><!-- title 表项 --><%= form.label :title, style: "display: block" %><!-- title 具体内容 --><%= form.text_field :title %></div><!-- product 的 description --><div><%= form.label :description, style: "display: block" %><!-- 这里将产品描述的行数和列数进行了自定义增大 --><%= form.text_area :description, rows: 10, cols: 60 %></div><!-- product 的 image_url --><div><%= form.label :image_url, style: "display: block" %><%= form.text_field :image_url %></div><!-- product 的 price --><div><%= form.label :price, style: "display: block" %><%= form.text_field :price %></div><!-- 填完表单后提交 --><div><%= form.submit %></div>
<% end %>

需要注意我们将表单的产品描述部分的输入框变大了

<!-- 这里将产品描述的行数和列数进行了自定义增大 -->
<%= form.text_area :description, rows: 10, cols: 60 %>

1.4 seeds

如果数据库不是同一个(一般本地开发多个,云端一个),那么测试数据就成了“个人私有”的,显然是低效的,我们可以给数据库一组“初始值“(也就是种子,seeds),这组初始值我们可以在 db/seeds.rb 中给出,如下所示

Product.delete_allProduct.create(title: 'Programming Ruby 1.9',description:%{<p>Ruby is the fastest growing and most exciting dynamic language out there.</p>},image_url: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.kfzimg.com%2Fsw%2Fkfzimg%2F1575%2F012f2857fe9a2966a5_b.jpg&refer=http%3A%2F%2Fwww.kfzimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1668567051&t=61ed25128b60ad15cbe2c21729511f99',price: 49.50
)

然后运行

rake db:seed

就可以添加这个数据。

1.5 SCSS

​ 当前的 products 页面过于丑陋,可以考虑给所有的产品界面一组样式,这里我们用 scss 实现,在 app/assets/stylesheets/ 中创建 products.scss 并写入下面的内容

.products {table {border-collapse: collapse;}table tr td{padding: 5px;vertical-align: top;}.list_image {width: 60px;height: 70px;}.list_description {width: 60%;dl {margin: 0;}dt {color: #244;font-weight: bold;font-size: larger;}dd {margin: 0;}}.list_actions {font-size: x-small;text-align: right;padding-left: 1em;}.list_line_even {background: #e0f8f8;}.list_line_odd {background: #e2c3e2;}
}

​ 然后就会发现运行不了,这是因为 rails 默认不能处理 scss,需要在 gemfile 中加入如下依赖

# Use Sass to process CSS
gem "sassc-rails"
gem 'bootstrap-sass'

然后命令行运行

bundle install

这是因为

rake是Ruby语言的构建工具,它的配置文件是Rakefile。

gem是Ruby语言的包管理工具,它的配置文件后缀是.gemspec。

bundler是Ruby语言的外部依赖管理工具,它有一个别名叫”bundle”,它的配置文件是Gemfile。

然后在 views/layouts/application.html.erb 中需要进行修改,加上每个类对应不同的 scss

<body class='<%= controller.controller_name %>'><%= yield %>
</body>

views/layouts/application.html.erb 是一个布局页面,会对每一个页面都适用。

最后写一下 index.html.erb 的具体信息

<div id="product_list"><h1>Products</h1><table><% @products.each do |product| %><tr class="<%= cycle('list_line_odd', 'list_line_even') %>"><td><%= image_tag(product.image_url, class: 'list_image') %></td><td class="list_description"><dl><dt><%= product.title %></dt><dd><%= truncate(strip_tags(product.description), :length => 80) %></dd></dl></td><td class="list_actions"><%= link_to 'Show', product %><br/><%= link_to 'Edit', edit_product_path(product) %><br/><%= link_to 'Destroy', product, :confirm => 'Are you sure?', :method => :delete %></td></tr><% end %></table>
</div><br/><%= link_to 'New product', new_product_path %>

1.6 验证

可以在模型层对于模型的属性添加验证,对于 Product 来说有如下验证

class Product < ApplicationRecordvalidates :title, :description, :image_url, presence: truevalidates :price, numericality: {:greater_than_or_equal_to => 0.01}validates :title, uniqueness: truevalidates :image_url, format: {:with => %r{\.(gif|jpg|png)}i,:message => 'must be a URL for GIF, JPG or PNG image.'}
end

验证的格式如下

validate [属性名], [验证内容]

具体的验证内容有

  • presence
  • numericality
  • uniqueness
  • uniqueness

1.7 路由设置

为了更好的展示产品(而不是需要通过 get 路由访问产品列表),我们可以另外再从用户的角度完善一个页面,这需要借助一个一个新的控制器(在后面的开发中,它被定义为“付费购买用户所使用的控制器”),在终端输入

rails generate controller Store index

它的意思是生成一个叫做 Store 的控制器,同时只有一个 index 动作。

然后我们希望当访问根目录的时候,可以访问到 Store#index 对应的界面,所以我们在 router.rb 中加上这句话

root 'store#index', as: 'store_index'

至于这个是怎么来的,可以这样理解,在路由中,标准写法是这样的

get '/test/:id', to: 'test#test', as 'test_test'

这个意思是,用户用 get 的方式访问 test/:id 这个 ulr 的时候,实际访问的是 Test 控制器对应的 test 动作对应的 view 。当我们有了 as 之后,我们可以通过 test_test_path 来指代 xxxx/test/:id ,用 test_test_url 指代 http:/xxxx/test/:id。也就是说path 类方法是对应的路径,不带协议部分。url 生成的带 http。两者差别在此。

这样看上面的 root ,只是某种意义的简写。

我们常见的

resoureces: products

其实就是一堆标准格式的声明,如下所示

HTTP VerbPathActionUsed for
GET/productsindexdisplay a list of all products
GET/products/newnewreturn an HTML form for creating a new product
POST/productscreatecreate a new product
GET/products/:idshowdisplay a specific product
GET/products/:id/editeditreturn an HTML form for editing a product
PATCH/PUT/products/:idupdateupdate a specific product
DELETE/products/:iddestroydelete a specific product

其中 PATCH, PUT, POST 都会被转换成 POST

  • PATCH: 实体中包含一个表,表中说明与该URI所表示的原内容的区别
  • PUT:上传资源
  • DELETE:删除资源

1.8 美化商品目录

store#index 中补充如下代码,表示按字典序展示所有的 Product

class StoreController < ApplicationControllerdef index@products = Product.order(:title)end
end

同时调整相应的视图

<p id="notice"><%= notice %></p><h1>Your Pragmatic Catalog</h1><% @products.each do |product| %><div class="entry"><%= image_tag(product.image_url) %><h3><%= product.title %></h3><%= sanitize(product.description) %><div class="price_line"><span class="price"><%= number_to_currency(product.price) %></span></div></div>
<% end %>

相应的 scss 表

.store {h1 {margin: 0;padding-bottom: 0.5em;font: 150% Sans-Serif;color: #226;border-bottom: 3px dotted #77d;}.entry {overflow: auto;margin-top: 1em;border-bottom: 1px dotted #77d;height: 100px;}img {width: 80px;margin-right: 5px;margin-bottom: 5px;height: 100px;position: absolute;}h3 {font-size: 120%;font-family: sans-serif;margin-left: 100px;margin-top: 0;margin-bottom: 2px;color: #277;}p, div.price_line {margin-left: 100px;margin-top: 0.5em;margin-bottom: 0.8em;}.price {color: #44a;font-weight: bold;margin-right: 3em;}
}

1.9 页面布局

修改 layouts/application.html.erb 加入侧边栏和顶栏

<!DOCTYPE html>
<html>
<head><title>Pragprog Books Online Store</title><meta name="viewport" content="width=device-width,initial-scale=1"><%= csrf_meta_tags %><%= csp_meta_tag %><%= stylesheet_link_tag "application", "data-turbo-track": "reload" %><%= javascript_importmap_tags %>
</head><body class='<%= controller.controller_name %>'>
<div id="banner"><span class="title"><%= @page_title %></span>
</div>
<div id="columns"><div id="side"><ul><li><a href="#">Home</a></li><li><a href="#">Question</a></li><li><a href="#">News</a></li><li><a href="#">Contact</a></li></ul></div><div id="main"><%= yield %></div>
</div>
</body>
</html>

同时修改 scss 文件

body, body > p, body > ol, body > ul, body > td {margin: 8px !important;
}#banner {position: relative;min-height: 40px;background: #9c9;padding: 10px;border-bottom: 2px solid;font: small-caps 40px/40px "Times New Roman", serif;color: #282;text-align: center;img {position: absolute;top: 5px;left: 5px;width: 60px;height: 60px;}
}#notice {color: #000 !important;border: 2px solid red;padding: 1em;margin-bottom: 2em;background-color: #f0f0f0;font: bold smaller sans-serif;
}#notice:empty {display: none;
}#columns {background: #141;display: flex;#main {padding: 1em;background: white;flex: 1;}#side {padding: 1em 2em;background: #141;ul {padding: 0;li {list-style: none;a {color: #bfb;font-size: small;}}}}
}@media all and (max-width: 800px) {#columns {flex-direction: column-reverse;}
}@media all and (max-width: 500px) {#banner {height: 1em;}#banner .title {display: none;}
}

2 购物车 Cart

2.1 Cart 模型

创建购物车

rails generate scaffold Cart
rake db:migrate

可以看到 Cart 基本上没有任何属性,这是因为当前开发的时候我们还不需要它们。

2.2 LineItem 商品模型

我们称在购物车中东西为“商品”,与之对应的还有“产品 Product”,两者的区别是 Product 具有某种静态的属性,没有办法说“两种香皂”,但是很容易形容“两个香皂”。LineItem 依附 Product 存在,同时也依附 Cart

所以我们这样定义它

rails generate scaffold LineItem product:references cart:belongs_to
rake db:migrate

这种定义方式会在模型层自动生成如下代码

class LineItem < ApplicationRecordbelongs_to :productbelongs_to :cart
end

同时我们还需要在 CartProduct 处进一步完善这种关系

class Cart < ApplicationRecordhas_many :line_items, dependent: :destroy
end

其中的 dependent: :destroy 表示当 Cart 销毁的时候,其中的 LineItem 都会被销毁。

# 与 line_item 关系
has_many :line_items
before_destroy :ensure_not_referenced_by_any_line_itemprivate
def ensure_not_referenced_by_any_line_itemunless line_items.empty?errors.add(:base, 'Line Items present')throw :abortend
end

这里说的是,在 Product 被销毁前,必须执行 ensure_not_referenced_by_any_line_item 这个方法,这个方法检测如果没有商品关联,才可以删除,否则报错。

我们可以对这个功能进行测试,有

test "can't delete product in cart" doassert_difference('Product.count', 0) dodelete product_url(products(:two))end
end

2.3 会话

出于一些原因,我们需要在会话中保存 cart_id,用户每添加一个商品,我们需要从会话中把 cart_id 取出来,然后通过标识符在数据库中查找购物车。

我们在 app/controllers/concerns/current_cart.rb 中写入如下代码

module CurrentCartprivate# 用 session 中的 cart_id 去查找 cart,如果没有找到,就创建一个新的 cart,并在 session 中存储 cart_iddef set_cart@cart = Cart.find(session[:cart_id])rescue ActiveRecord::RecordNotFound@cart = Cart.createsession[:cart_id] = @cart.idend
end

app/controllers/concerns/ 这个文件夹中一般来说是一些独立的逻辑模块或者是重复使用的功能模块,这样可以提升代码的可读性以及维护性。

2.4 “加入购物车”

我们可以将某个 Product 加入某个 Cart ,其本质是利用 Product 产生一个 LineItem

所以需要现在商品目录加上这个按钮,最终达到调用 LineItem#create 的目的。

<div class="price_line"><!-- 显示价格 --><span class="price"><%= number_to_currency(product.price) %></span><!-- 加入购物车 --><%= button_to 'Add to Cart', line_items_path(product_id: product) %>
</div>

其中

line_items_path(product_id: product)

就是调用 LineItem 创建方法 create 的意思,同时给它传参 product_id

然后我们来完善 LineItemcreate 方法。

首先在 LintItemController.rb 中引入 CurrentCart 模块,并且在每次的 create 方法前都调用 :set_cart 方法

class LineItemsController < ApplicationControllerinclude CurrentCartbefore_action :set_cart, only: [:create]

因为在 :set_cart 中会对 @cart 赋值,让其为当前对话 session 独有的 cart,所以最终的效果就是 LineItem 创建前就有一个 @cart 属性了。

然后修改 create 方法

# POST /line_items or /line_items.jsondef create# 根据传入的 product_id 查找 productproduct = Product.find(params[:product_id])# build 方法与 new 方法类似,会创建一个与 @cart 和 product 都相关的 @line_item@line_item = @cart.line_items.build(product: product)respond_to do |format|if @line_item.save# 跳转的对象不再是 @line_item,而是它的购物车format.html { redirect_to @line_item.cart, notice: "Line item was successfully created." }format.json { render :show, status: :created, location: @line_item }elseformat.html { render :new, status: :unprocessable_entity }format.json { render json: @line_item.errors, status: :unprocessable_entity }endendend

最后修改 cartshow 页面,让其可以显示里面的 LineItem。

<p id="notice"><%= notice %></p><h2>Your Pragmatic Cart</h2><ul><% @cart.line_items.each do |item| %><li><%= item.product.title %></li><% end %>
</ul>

2.5 加入数量

对于一个商品来说,之前的设计是有问题的,比如说我们买了两个香皂,那么不应该是“香皂,香皂”的显示两遍,而是应该“2 x 香皂”这样的显示,所以对于 LineItem 来说,数量是极其必要的。

所以创建迁移

rails generate migration add_quantity_to_line_items quantity:integer

但是还需要接着修改这个迁移,因为要设置其默认值为 1

class AddQuantityToLineItems < ActiveRecord::Migration[7.0]def change# 默认值是 1add_column :line_items, :quantity, :integer, default: 1end
end

然后需要修改 add_cart 的行为,并不是每次“加入购物车”,都是会产生一个新的 LineItem 的。首先在 app/models/cart.rb 中加入方法

def add_product(product)# 根据 product_id 查找 current_itemcurrent_item = line_items.find_by(product: product.id)if current_item# 查找到了,就数量增加 1current_item.quantity += 1else# 没查找到,就创建 line_itemcurrent_item = line_items.build(product_id: product.id)end# 返回 current_itemcurrent_item
end

然后修改 line_item#create

@line_item = @cart.add_product(product)

同时为了让已有的 LineItem 数据依然显示正确,需要创建一个迁移进行修改

rails generate migration combine_items_in_cart

这个迁移没法按照“约定”自动产生 change,所以需要自己手写 up, down(这两个方法似乎也是某种约定)

class CombineItemsInCart < ActiveRecord::Migration[7.0]def upCart.all.each do |cart|# 把购物车中同一个产品的多个商品替换为单个商品sums = cart.line_items.group(:product_id).sum(:quantity)sums.each do |product_id, quantity|if quantity > 1# 删除同一个产品的多个商品cart.line_items.where(product_id: product_id).delete_all# 替换为单个商品item = cart.line_items.build(product_id: product_id)item.quantity = quantityitem.save!endendendenddef downLineItem.where("quantity>1").each do |line_item|line_item.quantity.times doLineItem.create(cart_id: line_item.cart_id,product_id: line_item.product_id,quantity: 1)endline_item.destroyendend
end

2.6 清空购物车

清空购物车的本质是将当前的购物车删除,所以先加入“清空按钮”在 show.html

<p id="notice"><%= notice %></p><h2>Your Cart</h2><!-- 购物清单 -->
<table><% @cart.line_items.each do |item| %><tr><td><%= item.quantity %> &times; </td><td><%= item.product.title %></td><td class="item_price"><%= number_to_currency(item.total_price) %></td></tr><% end %><!-- 总金额 --><tr class="total_line"><td colspan="2">Total</td><td class="total_cell"><%= number_to_currency(@cart.total_price) %></td></tr>
</table><!-- 清空购物车 -->
<%= button_to 'Empty Cart', @cart, method: :delete, data: {confirm: 'Are you sure?'} %>

并且补充相应的方法即可。

2.7 局部渲染

我们希望在侧边栏也有购物车信息,所以我们考虑利用局部渲染。具体的知识在前面有,所以按照递归的思路,我们需要在 application.html.erb 中加入购物车

<div id="cart"><%= render @cart %>
</div>

这个东西会去渲染 _cart.html.erb 文件,所以需要将它的内容改得和 carts/show.html.erb 一样

<h2>Your Cart</h2><!-- 购物清单 -->
<table><%= render(cart.line_items) %><!-- 总金额 --><tr class="total_line"><td colspan="2">Total</td><td class="total_cell"><%= number_to_currency(cart.total_price) %></td></tr>
</table><!-- 清空购物车 -->
<%= button_to 'Empty Cart', @cart, method: :delete, data: {confirm: 'Are you sure?'} %>

这里面有一个

<%= render(cart.line_items) %>

这是因为对于 line_items 的渲染,在 cart_html.erb 中也出现了。这种局部渲染是一种集合渲染,所以渲染的模板在 _line_item.html.erb 中,修改如下

<tr><td><%= line_item.quantity %> &times; </td><td><%= line_item.product.title %></td><td class="item_price"><%= number_to_currency(line_item.total_price) %></td>
</tr>

最终意识到其实没有必要对 carts/show.html.erb 重复内容,所以将其改为

<p id="notice"><%= notice %></p><%= render @cart %>

因为在侧边栏中现在也有了 cart ,所以同样需要 set_cart ,所以对于 StoreController 中加入如下代码

class StoreController < ApplicationControllerinclude CurrentCartbefore_action :set_cartdef index@products = Product.order(:title)end
end

2.8 Ajax 购物车

现在每次进行 Add Cart 操作,本质都是在渲染整个 application.html.erb 页面,这无疑是低效的,所以考虑只渲染侧边栏的购物车部分。

首先需要在 Add Cart 按钮上添加 remote 参数

<%= button_to 'Add to Cart', line_items_path(product_id: product), remote:true %>

然后在 create 方法上加入神秘的 format.js ,我暂时还理解不了为啥,可以理解为启用了 js 脚本

# POST /line_items or /line_items.jsondef create# 根据传入的 product_id 查找 productproduct = Product.find(params[:product_id])# build 方法与 new 方法类似,会创建一个与 @cart 和 product 都相关的 @line_item@line_item = @cart.add_product(product)# respond_to 是一个方法,其参数为一个 block# 我现在的理解是 respond_to 描述的是服务器对于客户端的反应,或者说,这是浏览器上将执行的步骤# 这里就是先对于 html 进行一个重定向操作,然后调用 js,最后 jsonrespond_to do |format|if @line_item.save# 跳转的对象不再是 @line_item,而是 store 页面format.html { redirect_to store_index_url }# 调用 js 脚本format.jsformat.json { render :show, status: :created, location: @line_item }elseformat.html { render :new, status: :unprocessable_entity }format.json { render json: @line_item.errors, status: :unprocessable_entity }endendend

然后就会调用 views/line_items/create.js.erb 这个文件,将这个文件写入以下内容

$('#cart').html("<%= j render(@cart) %>")

这个脚本描述了将 id = cart 的节点替换成 render(@cart) 的操作。

2.9 突出显示

为了增强美工性,考虑引入 jQuery-ui

jQueryJavaScript 的一个好用的库,里面有常见的 html 操作和一组简单的 UI,我们需要更改 Gemfile 来安装它

# Use jquery as the JavaScript library
gem 'jquery-rails'
gem 'jquery-ui-rails'

然后执行

bundle install

然后新建 app/assets/javascripts/application.js 内容为

//= require jquery
//= require jquery_ujs
//= require jquery-ui/effect.all
//= require_tree .

至于为啥是这个,并不知道为啥。这样之后我们就可以使用 jquery-ui 了。

然后考虑如何对“刚刚点击过”的 LineItem 做一个突出显示,可以考虑在 create 中维护一个 @current_item

format.js { @current_item = @line_item }

然后在 _line_item.html.erb 中,将 current 标出来

<% if line_item == @current_item %><tr id="current_item">
<% else %><tr>
<% end %><td><%= line_item.quantity %> &times;</td><td><%= line_item.product.title %></td><td class="item_price"><%= number_to_currency(line_item.total_price) %></td></tr>

然后在 create.js.erb 中进行渲染

$('#current_item').css({'background-color': '#88ff88'}).animate({'background-color': '#114411'}, 1000)

2.10 辅助方法

我们希望可以在购物车内商品数量为 0 的时候,不显示购物车。

可以利用购物车长度作为判断,这里我们用到了辅助方法(主要是为了让代码更加整洁)

<!-- 购物车 -->
<div id="cart"><% if @cart %><%= hidden_div_if(@cart.line_items.empty?, id: 'cart') do %><%= render @cart %><% end %><% end %>
</div>

辅助方法为在 app/helpers 下的方法,脚手架会自动帮我们建好这些文件。

module ApplicationHelperdef hidden_div_if(condition, attributes={}, &block)# 将 html 中具有 attributes 并满足 condition 条件的 div 设置为 display: noneif conditionattributes["style"] = "display: none"endcontent_tag("div", attributes, &block)end
end

同时我们需要修改 js 文件,使得购物车显示的时候比较平滑

if ($('#cart tr').length === 1) { $('cart').show('blind', 1000) }

3 订单 Order

3.1 Order 模型

订单模型本质上信息全都是收货的信息,订单具体有什么商品,其实并不是由 Order 决定的,而是由 LineItem 决定的。因此建立如下如下模型

rails generate scaffold Order name address:text email phone pay_type:integer

同时给 LineItem 添加外键

rails generate migration add_order_to_line_item order:references 

最后融合迁移

rails db:migrate

然后就会发现融合不了,这是因为对于 LineItem,他有三个外键,分别是 Product, Cart, Order ,在迁移中自动生成的外键,都是不允许为空的,而现在,对于一个 LineItem ,它要么在 Cart 中,要么在 Order 中,所以总会有一个外键为空,所以需要修改多处地方。

首先要修改 LineItem 的两个 migration

# 20230101081559_create_line_items.rb
class CreateLineItems < ActiveRecord::Migration[7.0]def changecreate_table :line_items do |t|t.references :product, null: false, foreign_key: truet.belongs_to :cart, null: true, foreign_key: truet.timestampsendend
end# 20230102121647_add_order_to_line_item.rb
class AddOrderToLineItem < ActiveRecord::Migration[7.0]def changeadd_reference :line_items, :order, null: true , foreign_key: trueend
end

然后还要在 model 中进行数据关系的定义

class LineItem < ApplicationRecordbelongs_to :product# optional 表示外键可以为空belongs_to :cart, optional: truebelongs_to :order, optional: truedef total_priceproduct.price * quantityend
endclass Order < ApplicationRecordenum pay_type: {"Check" => 0,"Credit card" => 1,"Purchase order" => 2}has_many :line_items, dependent: :destroy
end

3.2 生成订单

生成订单的过程是一个将购物车中所有的 LineItem 都放到 Order 中的一个过程,我们可以用一个方法描述这个过程,定义在 model 中。

class Order < ApplicationRecordenum pay_type: {"Check" => 0,"Credit card" => 1,"Purchase order" => 2}has_many :line_items, dependent: :destroyvalidates :name, :address, :email, :phone, presence: truevalidates :pay_type, inclusion: pay_types.keysdef add_line_items_from_cart(cart)cart.line_items.each do |item|item.cart_id = nilline_items << itemendend
end

可以看到还新增了一些字段的验证约束,然后考虑生成表格,首先在 _cart.html.erb 加上生成 Order 的按钮

<!-- 结算购物车 -->
<%= button_to 'Checkout', new_order_path, method: :get %>

这个方法会调用 new 方法,所以我们需要写一下 new.html.erb 这个模板

<div class="project_form"><fieldset><h2>Please Enter Your Details</h2><%= render 'form', order: @order %></fieldset>
</div>

还有与之相关的 _form.html.erb

<%= form_with(model: order) do |form| %><% if order.errors.any? %><div style="color: red"><h2><%= pluralize(order.errors.count, "error") %> prohibited this order from being saved:</h2><ul><% order.errors.each do |error| %><li><%= error.full_message %></li><% end %></ul></div><% end %><div><%= form.label :name, style: "display: block" %><%= form.text_field :name, size: 40 %></div><div><%= form.label :address, style: "display: block" %><%= form.text_area :address, rows: 3, cols: 37 %></div><div><%= form.label :email, style: "display: block" %><%= form.text_field :email, size: 40 %></div><div><%= form.label :phone, style: "display: block" %><%= form.text_field :phone, size: 40 %></div><div><%= form.label :pay_type, style: "display: block" %><%= form.select :pay_type, Order.pay_types.keys, prompt: 'Select a payment method' %></div><div class="actions"><%= form.submit 'Place Order'%></div>
<% end %>

并且美化样式

.project_form {fieldset {background: #efe;h2 {color: #dfd;background: #141;font-family: sans-serif;padding: 0.2em 1em;}div {margin-bottom: 0.3em;}}form {label {width: 5em;float: left;text-align: right;padding-top: 0.2em;margin-right: 0.1em;display: block;}select, textarea, input {margin-left: 0.5em;}.submit {margin-left: 4em;}br {display: none;}}
}

Order#create 的过程中,需要清空购物车,所以需要修改一下 create 方法

# POST /orders or /orders.jsondef create@order = Order.new(order_params)@order.add_line_items_from_cart(@cart)respond_to do |format|if @order.save# 清空购物车Cart.destroy(session[:cart_id])session[:cart_id] = nilformat.html { redirect_to store_index_url(@order), notice: "Thank you for your order." }format.json { render :show, status: :created, location: @order }elseformat.html { render :new, status: :unprocessable_entity }format.json { render json: @order.errors, status: :unprocessable_entity }endendend

3.3 订单展示

作为管理端,需要看到所有的订单,所以考虑修改 index.html.erb 这个模板,将其改成表格形式会更漂亮一些,同时加上一些操作和跳转。

<%= form_with(model: order) do |form| %><% if order.errors.any? %><div style="color: red"><h2><%= pluralize(order.errors.count, "error") %> prohibited this order from being saved:</h2><ul><% order.errors.each do |error| %><li><%= error.full_message %></li><% end %></ul></div><% end %><div><%= form.label :name, style: "display: block" %><%= form.text_field :name, size: 40 %></div><div><%= form.label :address, style: "display: block" %><%= form.text_area :address, rows: 3, cols: 37 %></div><div><%= form.label :email, style: "display: block" %><%= form.text_field :email, size: 40 %></div><div><%= form.label :phone, style: "display: block" %><%= form.text_field :phone, size: 40 %></div><div><%= form.label :pay_type, style: "display: block" %><%= form.select :pay_type, Order.pay_types.keys, prompt: 'Select a payment method' %></div><div class="actions"><%= form.submit 'Place Order'%></div>
<% end %>

对于订单的详情展示,可以仿照 _cart.html.erb 书写

<table><%= render(order.line_items) %><!-- 总金额 --><tr class="total_line"><td colspan="2">Total</td><td class="total_cell"><%= number_to_currency(order.total_price) %></td></tr>
</table>

4 用户 User

4.1 User 模型

考虑用户具有用户名,密码,角色三个属性,模型如下

rails generate scaffold User name:string password:digest role:integer
rails db:migrate

对于密码部分,可以借助插件完成“确认密码的功能”

class User < ApplicationRecordhas_secure_password
end

同时在 gemfile 中填入这个插件

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
gem "bcrypt", "~> 3.1.7"

然后使用

bundle install

同时还有关于 role 的设计,目前有两个角色,一个是 Buyer,一个是 Admin,其中 Admin 的权限更高。

class User < ApplicationRecordenum role: {"Buyer" => 0,"Admin" => 1}validates :name, presence: true, uniqueness: truevalidates :role, presence: truevalidates :role, inclusion: roles.keyshas_secure_password
end

注意如果希望 role 作为一个枚举变量,那么这里一定要定义 enum 的名字为 role,不能叫 role_type 或者其他任何的名字,都不会让其具有枚举的效果,这大概就是神秘的 rails 吧。

4.2 控制器与页面

身份验证就是登录相关的功能,这里需要新建两个控制器,sessions 用于为登录和登出提供支持,admin 用于为管理员提供欢迎界面。

其中 sessions 只有两个动作,new, create 对应登入,destroy 对应登出

rails generate controller Sessions new create destroy 

Admin 只有一个动作 index,代表欢迎界面。

rails generate controller Admin index

这也启发我,其实写一个页面就是写一个 controller 和一个 view 而已,这是因为 view 似乎没有办法单独成为一个路由资源。

4.3 登录登出

登入功能就是填一个表单,所以在 new.html.erb 中写入

<div class="project_form"><% if flash[:alert] %><p id='notice'><%= flash[:alert] %></p><% end %><%= form_tag do %><fieldset><legend>Please Log In</legend><div><%= label_tag :name, 'Name:' %><%=text_field_tag :name, params[:name] %></div><div><%= label_tag :password, 'Password:' %><%= password_field_tag :password, params[:password] %></div><div><%= submit_tag 'Login' %></div></fieldset><% end %> %>
</div>

可以看到,因为 Sessions 并没有与模型关联,所以我们并没有用 form_for ,只是一个普通的 form_tag。收集的信息进入了 params

然后我们在 create 中利用 params 中的信息保存到 session

  def create# 这里更加明显,create 会有一个 params,params 的来历就是 new 填的表单user = User.find_by(name: params[:name])# try 对于为 nil 的 user,会直接进入 elseif user.try(:authenticate, params[:password])session[:user_id] = user.idsession[:user_role] = user.role# 如果是 Buyer ,就定向到商店,否则定向到 admin 的欢迎界面if user.role == "Buyer"redirect_to store_index_urlelseredirect_to admin_urlendelseredirect_to login_url, alert: "Invalid user/password combination"endend

同时完成相应界面的跳转,我们在 session 中保存了用户的 id 和权限信息。

退出登录的状态就很简单,就是将 session 中的信息注销掉即可

  def destroysession[:user_id] = nilsession[:user_role] = nilredirect_to store_index_url, notice: "Logged out"end

对于管理界面,可以自己设计一个

<h1>Welcome</h1><div>It's <%= Time.now %>We hava <%= pluralize(@total_orders, 'order') %>
</div>

最后需要修改路由

  get 'admin' => 'admin#index'controller :sessions doget 'login' => :newpost 'login' => :createdelete 'logout' => :destroyend

4.4 访问限制

访问限制可以在 application_controller.rb 中利用 before_action 实现

class ApplicationController < ActionController::Basebefore_action :authorizeprotecteddef authorizeunless User.find_by(id: session[:user_id])redirect_to login_url, notice: "Pleas log in."endend
end

然后在各个控制器,选择是否跳过 authorize 即可

class SessionsController < ApplicationControllerskip_before_action :authorize

这时会发现所有的 test 基本上都瘫痪了,这是因为 test 不会自动登录,所有在 test/test_helper.rb 中写入如下代码即可

ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
class ActionDispatch::IntegrationTestdef login_as(user)post login_url, params: {name: user.name, password: 'secret'}enddef logoutdelete logout_urlenddef setuplogin_as(users(:one))end
endclass ActiveSupport::TestCase# Run tests in parallel with specified workersparallelize(workers: :number_of_processors)# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.fixtures :all# Add more helper methods to be used by all tests here...
end

4.5 权限显示

我们希望对于管理者,可以看到更多的界面,而对于非管理者,则不需要看到这些界面

	  <!-- 管理链接 --><% if current_user and current_user.role == 'Admin' %><ul><li><%= link_to 'Orders', orders_path %></li><li><%= link_to 'Products', products_path %></li><li><%= link_to 'Users', users_path %></li></ul><% end %><!-- 登录登出 --><div class="log"><% if current_user %><%= current_user.name %> Logged in.<br/><%= link_to 'Logout', logout_path, method: :delete %><% else %>Please <%= link_to 'Login', login_url %><% end %></div>

这些需要借助 current_user 这个方法,这个方法定义在 application_controller.rb

helper_method :current_userdef current_user@current_user ||= User.find(session[:user_id]) if session[:user_id]
end

5 收藏夹 Favourite

5.1 Favourite 模型

每个用户都有一个收藏夹,二者是一对一关系,所以没有必要单独做一个实体,可以直接使用 User 模型。但是在实际思考的时候,却应当有收藏夹这个模型,比较方便思考。

5.2 FavorItem 收藏品模型

收藏品模型描述的是 ProductFavourite 之间的“多对多关系”,所以需要这样建立模型

rails generate scaffold FavorItem product:references user:belongs_to
rake db:migrate

对于 User 模型,补充如下代码,也就是当 User 持有一个 favor_items 集合,同时当 User 销毁的时候,会销毁所有的 favor_items

has_many :favor_items, dependent: :destroy

对于 Product 模型,补充如下代码,同样 Product 持有一个 favor_item 集合,同时在销毁前要检验是否可以销毁。

  has_many :favor_itemsbefore_destroy :ensure_not_referenced_by_any_favor_itemdef ensure_not_referenced_by_any_favor_itemunless favor_items.empty?errors.add(:base, 'Line Items present')throw :abortendend

5.3 加入收藏夹

store 界面上,除了有“加入购物车”之外,应当有“加入收藏夹”的功能,可以如此修改 store 界面

<!-- 加入收藏夹 -->
<%= button_to 'Add to Favourite', favor_items_path(product_id: product) %>

可以看到需要修改 favor_itemcreate 方法

  def create# 根据传入的 product_id 查找 productproduct = Product.find(params[:product_id])@favor_item = @user.add_product(product)respond_to do |format|if @favor_item.saveformat.html { redirect_to store_index_url }format.json { render :show, status: :created, location: @favor_item }elseformat.html { render :new, status: :unprocessable_entity }format.json { render json: @favor_item.errors, status: :unprocessable_entity }endendend

可以看到需要获得一个收藏夹的 @user,与 @cart 类似,所以需要一个 set_user 的过程

    def set_user@user = User.find(session[:user_id])rescue ActiveRecord::RecordNotFoundredirect_to store_index_urlend

5.4 收藏夹展示

类似于一个产品目录的子集,可以写 index.html.erb

<p style="color: green"><%= notice %></p><h1>Favourite Collections</h1><div id="favor_items"><table><% @favor_items.each do |favor_item| %><%= render favor_item %><% end %></table>
</div>

可以看到除了外面套了一个表之外,主体是对于每个 favor_item 的渲染,所以需要修改 _favor_item.html.erb

<tr class="<%= cycle('list_line_odd', 'list_line_even') %>"><td><%= image_tag(favor_item.product.image_url, class: 'list_image') %></td><td class="list_description"><dl><dt><%= favor_item.product.title %></dt><dd><%= strip_tags(favor_item.product.description) %></dd></dl></td><td><dl><%= number_to_currency(favor_item.product.price) %></dl><dl><%= button_to 'Add to Cart', line_items_path(product_id: favor_item.product), remote:true %></dl><dl><%= button_to 'Remove', favor_item, :method => :delete %></dl></td>
</tr>

6 促销活动 Activity

6.1 Activity 模型

促销活动只有一个属性就是名字,具体的促销也在这里体现体现,所以应当在终端中输入如下示例

rails generate scaffold Activity name:string disconut:integer
rake db:migrate

6.2 Prompt 促销项模型

促销项都是外键,用于关联 Product 产品和 Activity 活动,形成“活动-产品” 的多对多关系。

rails generate scaffold Prompt  product:references activity:belongs_to
rake db:migrate

同时完善 Product 模型

has_many :prompts, dependent: :destroy

Activity 模型

has_many :prompts, dependent: :destroy

6.3 新建活动

一个活动由本身和它包括的商品组成,在创建的时候,可以先创建好活动,确定活动的名称和折扣力度,然后再向这个活动添加涉及的商品。创建活动这个操作只有管理员可以干,所以我们用一个管理链接指向 activities 的展示页面

<li><%= link_to 'Prompt', activities_path %></li>

然后对于这个页面,我们只是展示其名称和折扣

<p style="color: green"><%= notice %></p><h1>Activities</h1><div id="activities"><% @activities.each do |activity| %><span>Name:<%= activity.name %></span><spac>Discount:<%= number_to_percentage(activity.discount, precision: 0) %></spac><p><%= link_to "Show this activity", activity %></p><% end %>
</div><%= link_to "New activity", new_activity_path %>

在活动中添加商品的操作,可以考虑在具体的活动界面进行添加,也就是 _activity.html.erb 中添加

<div id="<%= dom_id activity %>"><!-- 活动的基本信息 --><div><strong>Name:</strong><%= activity.name %></div><div><strong>Discount:</strong><%= number_to_percentage(activity.discount, precision: 0) %></div><!-- 这里展示了产品列表,可以将产品添加到活动中 --><% Product.all.each do |product| %><div class="entry"><h3><%= product.title %></h3><div class="price_line"><!-- 加入活动 --><%= button_to 'Add to Activity', prompts_path(product_id: product, activity_id: activity)%></div></div><% end %><!-- 已经加入活动的产品 --><% activity.prompts.each do |prompt| %><div><%= prompt.product.title %></div><% end %>
</div>

对于创建一个 prompt,与以往不同,需要传入两个参数,也就是 prompt 的两端 productactivity,也就是这样

<!-- 加入活动 -->
<%= button_to 'Add to Activity', prompts_path(product_id: product, activity_id: activity)%>

所以需要修改 promptcreate 方法

  # POST /prompts or /prompts.jsondef create# 根据传入的 product_id 查找 productproduct = Product.find(params[:product_id])activity = Activity.find(params[:activity_id])@prompt = activity.add_product(product)respond_to do |format|if @prompt.saveformat.html { redirect_to activity_url(activity), notice: "Prompt was successfully created." }format.json { render :show, status: :created, location: @prompt }elseformat.html { render :new, status: :unprocessable_entity }format.json { render json: @prompt.errors, status: :unprocessable_entity }endendend

可以看到进行了两次查找,最终利用两次查找的信息建立了 prompt。和 favor_itemadd_product 一致,对于加入活动的产品,只能加入一次

  def add_product(product)# 根据 product_id 查找 current_itemcurrent_item = prompts.find_by(product: product.id)if current_item# 查找到了,就啥都不干else# 没查找到,就创建 promptcurrent_item = prompts.build(product_id: product.id)end# 返回 current_itemcurrent_itemend

6.4 实现折扣

促销大概需要两个两个机制

  • store 界面上展示折扣
  • 在加入购物车或者订单的时候实际发生折扣

展示折扣这个功能很好实现,只需要在 store 界面中利用 product 检索 prompt 进而检索 activity.discount 即可。

<!-- 显示折扣 -->
<% product.prompts.each do |prompt| %><span> &times; <%= number_to_percentage(prompt.activity.discount, precision: 0) %></span>
<% end %>

注意这里用到了 number_to_percentage 方法,可以将普通整数转换成百分制(除以 100 加百分号)。

发生实际折扣,需要更改 price 的计算方式,原来对于价格的计算,是 Cart 或者 Order 对于其所有的 itemprice 进行求和,line_item 的价格计算,是 Product.pricequantity 的乘积,如下所示

def total_priceproduct.price * quantity
end

可见,只要修改 price 即可,所以在 product 中新写一个方法去获得折扣价格

  def getDiscountPricediscount = priceprompts.each do |prompt|discount *= (prompt.activity.discount / 100.0)enddiscountend

然后修改 total_price

  def total_priceproduct.getDiscountPrice * quantityend

即可。

6.5 展示折扣

对于普通用户来说,没有权限建立活动,但是有权限浏览活动,所以可以实现一个活动界面用于浏览,但是考虑到 index.html.erb 已经用于给管理者新建活动使用了,所以考虑新开设一个界面,在控制器中新定义一个方法

  def show_activity@show_activities = Activity.allend

然后建立对应的 show_activity.html.erb 这个页面,其内容如下

<div class="show_activities"><h1>促销活动</h1><% @show_activities.each do |activity| %><div class="entry"><div class="name"><%= activity.name %></div><div class="discount">折扣力度:<%= number_to_percentage(activity.discount, precision: 0) %></div><div class="items">促销产品:<% activity.prompts.each do |prompt| %><span class="product"><%= prompt.product.title %>,</span><% end %></div></div><% end %>
</div>

其后完善链接即可。

相关文章:

Ruby设计-开发日志

Log 1 产品 Product 1.1 创建 Product 创建名为 project 的 rails 应用 rails new project创建 Product 模型 rails generate scaffold Product title:string description:text image_url:string price:decimal这会生成一个 migration &#xff0c;我们需要进一步修改这个…...

SpringBoot 调用外部接口的三种方式

方式一&#xff1a;使用原始httpClient请求 /** description get方式获取入参&#xff0c;插入数据并发起流程* params documentId* return String*/ RequestMapping("/submit/{documentId}") public String submit1(PathVariable String documentId) throws ParseE…...

C 中的结构体

C 中的结构体 C 数组允许定义可存储相同类型数据项的变量&#xff0c;结构是 C 编程中另一种用户自定义的可用的数据类型&#xff0c;它允许您存储不同类型的数据项。 结构体中的数据成员可以是基本数据类型&#xff08;如 int、float、char 等&#xff09;&#xff0c;也可以…...

nodejs安装教程

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时&#xff0c;可以用于在服务器端运行 JavaScript 代码。以下是 Node.js 的安装教程&#xff1a; 步骤 1&#xff1a;下载 Node.js 访问 Node.js 的官方网站 https://nodejs.org/&#xff0c;进入官方下载页面。 在下载页…...

【华为OD机试】1029 - 整数与IP地址间的转换

文章目录一、题目&#x1f538;题目描述&#x1f538;输入输出&#x1f538;样例1二、代码参考作者&#xff1a;KJ.JK&#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x…...

【FPGA实验1】FPGA点灯工程师养成记

对于FPGA几个与LED相关的实验&#xff08;包括按键点灯、流水灯、呼吸灯等&#xff09;的记录&#xff0c;方便日后查看。这世界上就又多了一个FPGA点灯工程师了&#x1f60f; 成为一个FPGA点灯工程师分三步&#xff1a;一、按键点灯1、按键点灯程序2、硬件实现二、流水灯1、流…...

操作系统论文导读(三):Stack-based scheduling of realtime processes基于堆栈的实时进程调度

目录 一、论文核心思想&#xff1a; 二、基本的相关条件 作业运行的条件&#xff1a; 作业抢占其他作业的条件&#xff1a; 三、基本的相关定义 四、基本的相关调度 五、基本的相关调度 六、堆栈资源共享 七、与PCP的比较 一、论文核心思想&#xff1a; -引入了一个抢占优…...

音频延时测试方法与实现

音频延时测试方法有以下几种 1、使用专业的测试设备&#xff0c;通过专业的音频测试仪器可以准确测量音频延时&#xff0c;如常见声学分析仪、信号发生器、声卡Smaart&#xff08;介绍测试延时方法链接&#xff1a;https://blog.csdn.net/weixin_48408892/article/details/1273…...

在 Python 中管理机密的四种方法

我们生活在一个应用程序用于做任何事情的世界&#xff0c;无论是股票交易还是预订沙龙&#xff0c;但在幕后&#xff0c;连接是使用秘密完成的。必须适当管理机密&#xff0c;例如数据库密码、API 密钥、令牌等&#xff0c;以避免任何泄露。 管理机密的需求对任何组织都至关重…...

全国青少年信息素养大赛Python编程挑战赛初赛试题说明

Python 编程挑战赛初赛采用线上考试比赛形式,分为小学组和初中组。不同组别的考核重难点略有不同,考核内容主要是 Python 基础知识,共 30 题,均为单选题,具体考核如下: 小学组考核内容主要是 Python 基础知识,包括输入输出,变量,条件结构,计次循环和无限循环,海龟库…...

无需魔法打开即用的 AI 工具集锦

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;蚂蚁集团高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《EffectiveJava》独家解析》专栏作者。 热门文章推荐…...

如何进行SEO站内优化,让你的网站更易被搜索引擎收录

我们了解了 SEO 的流程&#xff0c;知道了哪些元素对 SEO 的效果会产生关键影响&#xff0c;接下来&#xff0c;我们就该正式开始动手&#xff0c;打造一个让搜索引擎“爱不释手”的网站。 为了方便理解与记忆&#xff0c;我们将网站划分为几个模块&#xff0c;告诉你优化网站…...

组件内部watch后切换数据报错Error in callback for watcher “xxxx“

报错信息&#xff1a; 报错代码&#xff1a; 百度了一下是因为这里写了箭头函数&#xff0c;导致this指向为父级作用域上下文&#xff0c;不是vue实例导致 修改为&#xff1a; progressData: {handler: function(newValue, oldValue) {this.setChartData(newValue)},deep: …...

VMware ESXi 7.0 U3l macOS Unlocker OEM BIOS (标准版和厂商定制版)

VMware ESXi 7.0 U3l macOS Unlocker & OEM BIOS (标准版和厂商定制版) 提供标准版和 Dell (戴尔)、HPE (慧与)、Lenovo (联想)、Inspur (浪潮)、Cisco (思科) 定制版镜像 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-esxi-7-u3-oem/&#xff0c;查看最新版…...

华为阿里版ChatGPT横空出世,谁的成效更好呢?

“你训练的大模型涌现了吗&#xff1f;”“还没有。好难受。”一时间成为了最近AI赛道玩家的一个爆热梗。 不管承不承认&#xff0c;相信每个玩家都不愿意输掉这场激烈的竞争。自百度成为国内“第一个吃螃蟹的人”后&#xff0c;又有两大中国科技巨头做好了准备——华为和阿里…...

【云原生之Docker实战】使用docker部署kooteam在线团队协作工具

【云原生之Docker实战】使用docker部署kooteam在线团队协作工具 一、kooteam介绍1.kooteam介绍2.kooteam的技术选型二、检查本地docker环境1.检查Docker版本2.检查Docker状态三、下载kooteam镜像四、部署kooteam文档管理系统1.创建安装目录2.创建mysql数据库3.新建kooteam数据库…...

ITSS认证是什么认证,itss资质认证

一、ITSS是什么 ITSS根据英文翻译信息技术服务标准&#xff08;InformationTechnologyServiceStandards&#xff0c;简称ITSS&#xff09;&#xff0c;它既是一套成体系和综合配套的标准库&#xff0c;又是一套选择和提供IT服务的方法学&#xff0c;对企业IT服务而言&#xff0…...

FTP-----局域网内部远程桌面

此文包含详细的图文教程。有疑问评论区留言。博主第一时间解决。 目录 一、被远程桌面的电脑 1.开启远程权限 2.添加账户&#xff0c;有本地账户跳过这步 3.帐号隶属于 远程桌面 4.帐号隶属于 本地用户组 二、本地电脑连接远程桌面 前提条件&#xff1a; 1.两台电脑在…...

Learning C++ No.18【STL No.8】

引言&#xff1a; 北京时间&#xff1a;2023/3/18/21:47&#xff0c;周末&#xff0c;不摆烂&#xff0c;但是欠钱终于还是遭报应了&#xff0c;导致坐牢7小时&#xff08;上午3.5&#xff0c;下午3.5&#xff09;&#xff0c;难受&#xff0c;充分意识到行哥是那么的和蔼可亲…...

pytorch搭建ResNet50实现鸟类识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 &#x1f366; 参考文章地址&#xff1a; 365天深度学习训练营-第J1周&#xff1a;ResNet-50算法实战与解析 &#x1f356; 作者&#xff1a;K同学啊 理论知识储备 深度残差网络ResNet&#xff08;dee…...

Node.js -- npm与包

1.包 Node.js中的第三方模块又叫做包 就像电脑和计算机指的是相同的东西&#xff0c;第三方模块和包指的是同一概念&#xff0c;只不过叫法不同。 包的来源&#xff1a; 包是由第三方或者个人团队开发出来的&#xff0c;免费供个人使用。 国外有一家IT 公司&#xff0c;叫做n…...

二 、Locust自定义用户(场景)

二 、自定义用户&#xff08;场景&#xff09; 一个用户类代表了你系统中的一种用户/场景。当你做一个测试运行时&#xff0c;你指定你想模拟的并发用户的数量&#xff0c;Locust将为每个用户创建一个实例。你可以给这些类/实例添加任何你喜欢的属性&#xff0c;但有一些属性对…...

1~3年的测试工程师薪资陷入了瓶颈期,如何突破自己实现涨薪?

对于技术人员而言&#xff0c;职业规划一般分为两个方向&#xff1a;做技术、做管理。进入软件测试行业的新人都会从最基础的执行开始&#xff0c;然后是基本的功能测试。 随后大家会根据个人职业发展来进一步细化&#xff0c;有的走管理路线&#xff0c;成为主管、经理、项目…...

springboot项目前端ajax 07进阶优化,使用jQuery的ajax

使用官网https://jquery.com/ 在下载那里&#xff0c;选择Download the compressed, production jQuery 3.6.4&#xff08;版本不一样&#xff09;&#xff0c;而后在打开的网页中&#xff0c;选择另存为&#xff0c;就下载好了js文件。 > function doAjax(){ …...

东数西存场景的探索与实践

“东数西算”是通过构建数据中心、云计算、大数据一体化的新型算力网络体系&#xff0c;将东部算力需求有序引导到西部&#xff0c;对优化数据中心建设布局&#xff0c;提升国家整体算力水平&#xff0c;促进绿色发展&#xff0c;扩大有效投资&#xff0c;具有重要意义。 在实…...

[图神经网络]PyTorch简单实现一个GCN

Pytorch自带一个PyG的图神经网络库&#xff0c;和构建卷积神经网络类似。不同于卷积神经网络仅需重构__init__( )和forward( )两个函数&#xff0c;PyTorch必须额外重构propagate( )和message( )函数。 一、环境构建 ①安装torch_geometric包。 pip install torch_geometric …...

Elasticsearch(黑马)

初识elasticsearch ​​. 安装elasticsearch 1.部署单点es 1.1.创建网络 因为我们还需要部署kibana容器&#xff0c;因此需要让es和kibana容器互联。这里先创建一个网络&#xff1a; docker network create es-net 1.2.加载镜像 这里我们采用elasticsearch的7.12.1版本的…...

oracle数据库调整字段类型

oracle数据库更改字段类型比较墨迹&#xff0c;因为如果该字段有值&#xff0c;是不允许直接更改字段类型的。另外oralce不支持在指定的某个字段后面新增一个字段&#xff0c;但是mysql数据可以向指定的字段后面新增一个字段。 mysql向指定字段后面新增一个字段&#xff1a; al…...

面部表情识别2:Pytorch实现表情识别(含表情识别数据集和训练代码)

面部表情识别2&#xff1a;Pytorch实现表情识别(含表情识别数据集和训练代码) 目录 面部表情识别2&#xff1a;Pytorch实现表情识别(含表情识别数据集和训练代码) 1.面部表情识别方法 2.面部表情识别数据集 &#xff08;1&#xff09;表情识别数据集说明 &#xff08;2&…...

赛效:如何在线给图片加水印

学会给图片加水印是一个非常实用的技能&#xff0c;可以让你的图片更具保护性和个性化。说到加水印&#xff0c;很多人不知道怎么操作。其实&#xff0c;给图片加水印非常简单&#xff0c;不用下载任何程序&#xff0c;在线就能完成。今天&#xff0c;我将介绍如何使用改图宝在…...

中国建设银行网站首页企业网银/查淘宝关键词排名软件有哪些

刚刚研究了Kmeans。Kmeans是一种十分简单的聚类算法。可是他十分依赖于用户最初给定的k值。它无法发现随意形状和大小的簇。最适合于发现球状簇。他的时间复杂度为O(tkn)。kmeans算法有两个核心点&#xff1a;计算距离的公式&推断迭代停止的条件。一般距採用欧式距离等能够…...

遵义网上商城/做seo需要哪些知识

IIS负载均衡-Application Request Route详解第五篇&#xff1a;使用ARR来配置试点项目 系列文章链接&#xff1a; IIS负载均衡-Application Request Route详解第一篇&#xff1a; ARR介绍 IIS负载均衡-Application Request Route详解第二篇&#xff1a;创建与配置Server Far…...

微信网站模板/百度移动端模拟点击排名

转行java和Web前端学哪个好&#xff1f;其实二者并没有好与坏之分&#xff0c;选择web前端还是Java主要看自己的兴趣爱好&#xff0c;以及技术倾向&#xff0c;不管是前端还是后端&#xff0c;都是高薪职业&#xff0c;都很有发展前景。 对于Java而言&#xff0c;JavaSE、Javaw…...

dedecms双语网站/友情链接论坛

物联网事业部总裁DipeshPatel于台北国际计算机展的CPX论坛&#xff0c;发表“迈入物联网时代的机会与挑战”&#xff08;Meetingthe Challengesof Scalingthe IoT&#xff09;演说&#xff0c;畅谈物联网的最新技术与发展趋势&#xff0c;和ARM在物联网的发展近况。 ARM物联网事…...

官方网站管理办法/电脑优化

前一篇介绍了怎么从手机中读取图片文件&#xff0c;放入组件GridView实现网格效果的缩略图显示。 今天研究了对GridView中的子项&#xff08;各张小图片&#xff09;进行删除的操作&#xff0c;参考已有软件&#xff0c;长按图片跳出删除确认框。 GridView长按事件为OnItemLong…...

仿做静态网站多少钱/站长工具星空传媒

1.解释Session Session 是客户端与服务器通讯会话技术&#xff0c; 比如浏览器登陆、记录整个浏览会话信息。session存放在服务器&#xff0c;关闭浏览器不会失效。 1.1Session实现原理 客户对向服务器端发送请求后&#xff0c;Session 创建在服务器端&#xff0c;返回Sessi…...