商品快递模版数据库设计以及实现。 [TOCM] [TOC] # 背景 由于复杂的业务场景,简单的全场满XX包邮,不满XX运费XX的方式以及不在符合业务场景,需要突破为每个商品定制自己的快递模版,计算最终运费的方式。 > 如A商品包邮,B商品江浙沪包邮,其他地区6元,C商品全国10元。 当购买A,B,C商品时,最终订单的实付邮费需要根据公司业务决定,如有的公司采用取最高原则(小件货),而我们公司由于是做原料采购,所以采用累加原则。 附图  # 数据库设计 理清楚期望的需求后,进行数据库设计。 商品表新增一个express_id字段,其中为0代表包邮,不为0则指定了一个快递模版。 接下来设计快递模版表。 ```php public function up() { $table = $this->table('express', array('engine' => 'Innodb', 'comment' => '快递模版')); $table->addColumn('title', 'string', array('limit' => 150, 'comment' => '模版名称')) ->addColumn('type', 'boolean', array('limit' => 4, 'default' => 1, 'comment' => '1为计件方式,2为计重方式,3为体积方式')) ->addTimestamps() ->addSoftDelete() ->create(); } ``` 接下来一个快递模版表可能存在多条指定地区的不同快递方案。 ```php public function up() { $table = $this->table('express_details', array('engine' => 'Innodb', 'comment' => '快递模版详情')); $table->addColumn('express_id', 'integer', array('limit' => 11, 'comment' => '模版ID')) ->addColumn('country', 'string', array('limit' => 150, 'null'=>true, 'comment' => '国家')) ->addColumn('province', 'string', array('limit' => 150, 'null' => true, 'comment' => '省份')) ->addColumn('city', 'string', array('limit' => 150, 'null' => true, 'comment' => '城市')) ->addColumn('area', 'string', array('limit' => 150, 'null' => true, 'comment' => '区县')) ->addColumn('first_price', 'decimal', array('precision' => 10, 'scale' => 2, 'default' => 0, 'comment' => '首费')) ->addColumn('first_num', 'integer', array('limit' => 11, 'comment' => '首件数')) ->addColumn('add_price', 'decimal', array('precision' => 10, 'scale' => 2, 'default' => 0, 'comment' => '续费')) ->addColumn('add_num', 'integer', array('limit' => 11, 'comment' => '续件数')) ->addColumn('is_default', 'boolean', array('limit' => 4, 'default' => 2, 'comment' => '是否默认')) ->addTimestamps() ->addSoftDelete() ->create(); } ``` # 业务匹配 有了数据存储,只要指定摸个商品使用哪套模版,依据用户选择的地址匹配到相应的地区,取得该地区的首件(重/体积)XX元,并根据商品的件(重/体积)数,计算最终商品的运费。 这里封装了一个方案,可传入多个商品的ID,会根据商品的快递模版计算商品的总运费。 > 注意,当地区出现重复时,优先匹配最小单位,如:滨江区4元,杭州市5元,浙江省6元,用户地址在滨江区,优先匹配滨江区的运费。 ```php /** * 根据区域获取快递费用,可传入多个商品。 * 多个商品的快递费用累加。 */ public function getMailPriceByArea($goodIds = "", $area = "") { if (empty($goodIds) || empty($area)) { throw new Exception("参数错误"); } $Product = model('Product'); $Express = model('Express'); $ExpressDetails = model('ExpressDetails'); $areaArray = array_filter(explode("|", $area)); $province = $areaArray[0]; $city = $areaArray[1]; $area = $areaArray[2]; $mailFee = 0; $goodIds = array_filter(explode(",", $goodIds)); foreach ($goodIds as $good) { $pinfo = explode('_', $good); $productId = $pinfo[0]; $num = $pinfo[1]; $info = $Product->where('id', $productId)->find(); //包邮 if ($info['express_id'] == 0) { $mailFee += 0; } else { $expressList = $ExpressDetails->where('express_id', $info['express_id'])->order('is_default asc')->select(); $expressInfo = []; $flag = 0; foreach ($expressList as $subkey => $subval) { //判断是否含有所在地址的模版,如无则取默认费用 $subFee = 0; if ($subval['province'] == $province && empty($subval['city']) && empty($subval['area']) && $flag <= 1) { $flag = 1; $expressInfo = $subval; } if ($subval['city'] == $city && $subval['province'] == $province && empty($subval['area']) && $flag <= 2) { $flag = 2; $expressInfo = $subval; } if ($subval['area'] == $area && $subval['city'] == $city && $subval['province'] == $province && $flag <= 3) { $flag = 3; $expressInfo = $subval; } if ($flag == 0 && $subval['is_default'] == 1) { $expressInfo = $subval; } } $type = $Express->where('id',$info['express_id'])->value('type'); $subFee = $this->calculateProductMailPrice($info, $num, $expressInfo, $type); $mailFee += $subFee; } } return $mailFee; } /** * 依据不同类型获取运费 */ protected function calculateProductMailPrice($productInfo = [], $num = 0, $expressInfo, $type = 1) { $mailFee = 0; if ($num == 0 || empty($expressInfo) || empty($productInfo)) { return 0; } switch ($type) { case 2: //计重 $num = $num * $productInfo['weight']; break; case 3: //体积 $num = $num * $productInfo['volume']; break; } if ($expressInfo['first_num'] >= $num) { $mailFee = $expressInfo['first_price']; } else { //运费 = 首重 + 超重(多出部分+1) $mailFee = $expressInfo['first_price'] + $expressInfo['add_price'] * ceil(($num - $expressInfo['first_num']) / $expressInfo['add_num']); } return $mailFee; } ``` 最终结果: 