在应用laravel5.7框架开发电商系统的过程中,涉及到产品信息的存储问题,当然不能将所有的产品信息存储在一张表中,还需要关于产品信息关联的辅助表,我们来分析一下关于商城的基本功能与数据分析。现在主流商城的基本模式有 B to B与 B to C 两种模式我们以 B to C 为例来分析数据与逻辑:
1.首先是进行数据库设计,我们需要那些数据表:
表名 | 意义 | 关联关系 |
---|---|---|
products | 产品信息表,对应数据模型 Product | |
product_skus | 产品的 SKU 表,对应数据模型 ProductSku | 与products表关联 |
crowdfunding_products | 产品的 众筹 表,对应数据模型 CrowdfundingProduct | 与products表关联 |
2.数据表的字段设计
接下来我们需要整理好 products
表、 product_skus
表和crowdfunding_products
表的字段名称和类型:
products
表:
字段名称 | 描述 | 类型 | 加索引缘由 |
---|---|---|---|
id | 自增长ID | unsigned int | 主键 |
type | 商品类型 | varchar | 无 |
title | 商品名称 | varchar | 无 |
description | 商品详情 | text | 无 |
image | 商品封面图片文件路径 | varchar | 无 |
on_sale | 商品是否正在售卖 | tiny int, default 1 | 无 |
rating | 商品平均评分 | float, default 5 | 无 |
sold_count | 销量 | unsigned int, default 0 | 无 |
review_count | 评价数量 | unsigned int, default 0 | 无 |
price | SKU 最低价格 | decimal | 无 |
商品本身是没有固定的价格的 price
字段是为了方便用户进行搜索与排序的
product_skus
表:
字段名称 | 描述 | 类型 | 加索引缘由 |
---|---|---|---|
id | 自增长ID | unsigned int | 主键 |
title | SKU 名称 | varchar | 无 |
description | SKU 描述 | varchar | 无 |
price | SKU 价格 | decimal | 无 |
stock | 库存 | unsigne int | 无 |
product_id | 所属商品 id | unsigne int | 外键 |
crowdfunding_products
表:
字段名称 | 描述 | 类型 | 加索引缘由 |
---|---|---|---|
id | 自增长ID | unsigned int | 主键 |
product_id | 对应商品表的 ID | unsigned int | 外键 |
target_amount | 众筹目标金额 | decimal | 无 |
total_amount | 当前已筹金额 | decimal | 无 |
user_count | 当前参与众筹用户数 | unsigned int | 无 |
end_at | 众筹结束时间 | datetime | 无 |
status | 当前筹款的状态 | varchar | 无 |
电商项目中与钱相关的有小数点的字段一律使用
decimal
类型,而不是float
和double
,后面两种类型在做小数运算时有可能出现精度丢失的问题,这在电商系统里是绝对不允许出现的。
3.创建相应的模型文件
接下来我们根据整理好的字段创建对应的模型文件:
$ php artisan make:model Models/Product -mf $ php artisan make:model Models/ProductSku -mf $ php artisan make:model Models/CrowdfundingProduct -m
database/migrations/数据表模型文件在这个目录下,根据上面整理好的字段修改数据库迁移文件。
database/migrations/< your_date >_create_products_table.php
public function up() { Schema::create('products', function (Blueprint $table) { $table->increments('id'); $table->string('type')->after('id')->default(\App\Models\Product::TYPE_NORMAL)->index(); $table->string('title'); $table->text('description'); $table->string('image'); $table->boolean('on_sale')->default(true); $table->float('rating')->default(5); $table->unsignedInteger('sold_count')->default(0); $table->unsignedInteger('review_count')->default(0); $table->decimal('price', 10, 2); $table->timestamps(); }); }
\App\Models\Product::TYPE_NORMAL
为引用产品模型中定义的普通商品$table->string('type')->after('id')->default(\App\Models\Product::TYPE_NORMAL)->index();
是定义type字段默认数据是normal,在接下来的模型文件代码中就能够看到这个设置
database/migrations/< your_date >_create_product_skus_table.php
public function up() { Schema::create('product_skus', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->string('description'); $table->decimal('price', 10, 2); $table->unsignedInteger('stock'); $table->unsignedInteger('product_id'); $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade'); $table->timestamps(); }); }
database/migrations/< your_date >_create_crowdfunding_products_table.php
public function up() { Schema::create('crowdfunding_products', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('product_id'); $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade'); $table->decimal('target_amount', 10, 2); $table->decimal('total_amount', 10, 2)->default(0); $table->unsignedInteger('user_count')->default(0); $table->dateTime('end_at'); $table->string('status')->default(\App\Models\CrowdfundingProduct::STATUS_FUNDING); }); }
\App\Models\CrowdfundingProduct::STATUS_FUNDING
在模型中定义的常量请看下面代码
4.接下来我们来定义对应的模型文件:
4.1 app/Models/Product.php
const TYPE_NORMAL = 'normal'; const TYPE_CROWDFUNDING = 'crowdfunding'; public static $typeMap = [ self::TYPE_NORMAL => '普通商品', self::TYPE_CROWDFUNDING => '众筹商品', ]; protected $fillable = [ 'title', 'description', 'image', 'on_sale', 'rating', 'sold_count', 'review_count', 'price', 'type', ]; protected $casts = [ 'on_sale' => 'boolean', // on_sale 是一个布尔类型的字段 ]; // 与商品SKU一对多关联 public function skus() { return $this->hasMany(ProductSku::class); } //与众筹表一对一关联 public function crowdfunding() { return $this->hasOne(CrowdfundingProduct::class); }
4.2 app/Models/ProductSku.php
protected $fillable = ['title', 'description', 'price', 'stock']; //与商品表反向关联 public function product() { return $this->belongsTo(Product::class); }
4.3 app/Models/CrowdfundingProduct.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class CrowdfundingProduct extends Model { // 定义众筹的 3 种状态 const STATUS_FUNDING = 'funding'; const STATUS_SUCCESS = 'success'; const STATUS_FAIL = 'fail'; public static $statusMap = [ self::STATUS_FUNDING => '众筹中', self::STATUS_SUCCESS => '众筹成功', self::STATUS_FAIL => '众筹失败', ]; protected $fillable = ['total_amount', 'target_amount', 'user_count', 'status', 'end_at']; // end_at 会自动转为 Carbon 类型 protected $dates = ['end_at']; // 不需要 created_at 和 updated_at 字段 public $timestamps = false; //与商品表关联 public function product() { return $this->belongsTo(Product::class); } // 定义一个名为 percent 的访问器,返回当前众筹进度 public function getPercentAttribute() { // 已筹金额除以目标金额 $value = $this->attributes['total_amount'] / $this->attributes['target_amount']; return floatval(number_format($value * 100, 2, '.', '')); } }
饭后执行数据迁移命令:
$ php artisan migrate
这样三个关于产品的数据表与模型就是创建完毕了,执行完后会在数据库管理器中发现三个表已经生成。
5.创建控制器
因为我用的是laravel-admin管理后台所以用 admin:make 来创建管理后台的控制器:
$ php artisan admin:make ProductsController --model=App\\Models\\Product $ php artisan admin:make CrowdfundingProductsController --model=App\\Models\\Product
5.1商品SKU数据的交互是在控制器ProductsControlle
中完成的关于产品列表部分也就是$grid()部分就不说了。
接下来着重说一下产品编辑中因为需要添加产品的SKU数据所以需要在$form()中做些改动代码如下
// 直接添加一对多的关联模型 $form->hasMany('skus', 'SKU 列表', function (Form\NestedForm $form) { $form->text('title', 'SKU 名称')->rules('required'); $form->text('description', 'SKU 描述')->rules('required'); $form->text('price', '单价')->rules('required|numeric|min:0.01'); $form->text('stock', '剩余库存')->rules('required|integer|min:0'); }); // 定义事件回调,当模型即将保存时会触发这个回调 $form->saving(function (Form $form) { $form->model()->price = collect($form->input('skus'))->where(Form::REMOVE_FLAG_NAME, 0)->min('price') ?: 0; });
代码解析:
$form->hasMany('skus', 'SKU 列表', /**/)
App\Models\Product
类中定义了skus()
方法来关联 SKU,因此这里我们需要填入skus
,第二个参数是对这个关联关系的描述,第三个参数是一个匿名函数,用来定义关联模型的字段。$form->saving()
SKU
中最低的价格作为商品的价格,然后通过$form->model()->price
存入到商品模型中。collect() 函数是 Laravel 提供的一个辅助函数,可以快速创建一个 Collection 对象。在这里我们把用户提交上来的 SKU 数据放到 Collection 中,利用 Collection 提供的 min() 方法求出所有 SKU 中最小的 price后面的?: 0则是保证当SKU数据为空时price字段被赋值 0。
5.2控制器编辑CrowdfundingProductsController