Flash.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: zhengmingwei
  5. * Date: 2020/2/9
  6. * Time: 6:18 PM
  7. */
  8. namespace addons\unishop\controller;
  9. use addons\unishop\extend\Hashids;
  10. use addons\unishop\extend\Redis;
  11. use addons\unishop\model\Address as AddressModel;
  12. use addons\unishop\model\Area;
  13. use addons\unishop\model\Config;
  14. use addons\unishop\model\DeliveryRule as DeliveryRuleModel;
  15. use addons\unishop\model\Evaluate;
  16. use addons\unishop\model\FlashProduct;
  17. use addons\unishop\model\FlashSale;
  18. use addons\unishop\model\Product;
  19. use think\Db;
  20. use think\Exception;
  21. use think\Hook;
  22. use think\Loader;
  23. /**
  24. * 秒杀
  25. */
  26. class Flash extends Base
  27. {
  28. protected $noNeedLogin = ['index', 'navbar', 'product', 'productDetail'];
  29. public function _initialize()
  30. {
  31. parent::_initialize();
  32. }
  33. /**
  34. * @ApiTitle (首页秒杀信息)
  35. * @ApiSummary (首页秒杀信息)
  36. * @ApiMethod (POST)
  37. * @ApiHeaders (name=cookie, type=string, required=false, description="用户会话的cookie")
  38. * @ApiReturn ({"code":1,"msg":"","data":[]})
  39. *
  40. * @ApiReturnParams (name="title", type="string", description="当前秒杀活动标题")
  41. * @ApiReturnParams (name="introdution", type="string", description="当前秒杀活动简介")
  42. * @ApiReturnParams (name="product.name", type="string", description="商品图片")
  43. * @ApiReturnParams (name="product.title", type="string", description="商品标题")
  44. * @ApiReturnParams (name="product.sales_price", type="string", description="商品价钱")
  45. * @ApiReturnParams (name="product.flash_product_id", type="string", description="商品id")
  46. * @ApiReturnParams (name="flash_id", type="string", description="秒杀id")
  47. * @ApiReturnParams (name="starttime_hour", type="string", description="开始时间的所在小时")
  48. * @ApiReturnParams (name="current", type="bool", description="当前展示的秒杀活动是否正在进行中")
  49. * @ApiReturnParams (name="countdown", type="json", description="下一场秒杀活动的时间等信息")
  50. *
  51. */
  52. public function index()
  53. {
  54. $fid = input('fid',0); // 一级分类Id
  55. $flashSaleModel = new FlashSale();
  56. $hour = strtotime(date('Y-m-d H:00:00'));
  57. $flash = $flashSaleModel
  58. // ->where('starttime', '>=', $hour) //专门加的,防止上一场不结束
  59. ->where('endtime', '>=', $hour)
  60. ->where([
  61. 'switch' => FlashSale::SWITCH_YES,
  62. 'status' => FlashSale::STATUS_NO,
  63. ])
  64. ->with([
  65. 'product' => function ($query) use($fid) {
  66. //$query->with('product')->where(['switch' => FlashProduct::SWITCH_ON]);
  67. $query->alias('fp')->join('unishop_product p', 'fp.product_id = p.id')
  68. ->field('fp.id,fp.flash_id,fp.product_id,fp.sold,fp.number,p.image,p.title,p.sales_price,p.category_id')
  69. ->where([
  70. 'fp.switch' => FlashProduct::SWITCH_ON,
  71. 'p.deletetime' => NULL
  72. ]);
  73. $fid && $query->where(['p.category_id' => $fid]);
  74. }
  75. ])
  76. ->order('starttime ASC')
  77. ->find();
  78. if ($flash) {
  79. $flash = $flash->toArray();
  80. foreach ($flash['product'] as &$product) {
  81. $product['image'] = Config::getImagesFullUrl($product['image']);
  82. $product['sold'] = $product['sold'] > $product['number'] ? $product['number'] : $product['sold'];
  83. $product['bili'] = bcdiv($product['sold'],$product['number'],1).'%';
  84. }
  85. // 寻找下一场的倒计时
  86. $nextFlash = $flashSaleModel
  87. ->where('starttime', '>', $hour)
  88. ->where([
  89. 'switch' => FlashSale::SWITCH_YES,
  90. 'status' => FlashSale::STATUS_NO,
  91. ])
  92. ->order("starttime ASC")
  93. ->cache(10)
  94. ->find();
  95. $flash['next_starttime'] = $nextFlash['starttime'];
  96. $flash['countdown'] = FlashSale::countdown($flash['next_starttime']);
  97. }
  98. $this->success('', $flash);
  99. }
  100. /**
  101. * @ApiTitle (获取秒杀时间段)
  102. * @ApiSummary (获取秒杀时间段)
  103. * @ApiMethod (POST)
  104. * @ApiHeaders (name=cookie, type=string, required=false, description="用户会话的cookie")
  105. * @ApiReturn ({"code":1,"msg":"","data":[]})
  106. *
  107. * @ApiReturnParams (name="title", type="string", description="当前秒杀活动标题")
  108. * @ApiReturnParams (name="introdution", type="string", description="当前秒杀活动简介")、
  109. * @ApiReturnParams (name="flash_id", type="string", description="秒杀id")
  110. * @ApiReturnParams (name="starttime_hour", type="string", description="开始时间的所在小时")
  111. * @ApiReturnParams (name="current", type="bool", description="是否进行中")
  112. *
  113. */
  114. public function navbar()
  115. {
  116. $flashSaleModel = new FlashSale();
  117. $flash = $flashSaleModel
  118. ->where('endtime', '>', time())
  119. ->where([
  120. 'switch' => FlashSale::SWITCH_YES,
  121. 'status' => FlashSale::STATUS_NO
  122. ])
  123. ->field('id,starttime,title,introdution,endtime')
  124. ->order('starttime ASC')
  125. ->cache(2)
  126. ->select();
  127. $this->success('', $flash);
  128. }
  129. /**
  130. * @ApiTitle (获取秒杀的产品列表)
  131. * @ApiSummary (获取秒杀的产品列表)
  132. * @ApiMethod (POST)
  133. * @ApiHeaders (name=cookie, type=string, required=false, description="用户会话的cookie")
  134. * @ApiParams (name="flash_id", type=string, required=true, description="秒杀活动id")
  135. * @ApiParams (name=page, type=integer, required=true, description="第几页")
  136. * @ApiParams (name=pagesize, type=integer, required=true, description="每页展示数量")
  137. * @ApiReturn ({"code":1,"msg":"","data":[]})
  138. *
  139. * @ApiReturnParams (name="number", type="integer", description="秒杀数量")
  140. * @ApiReturnParams (name="introduction", type="string", description="秒杀简介")
  141. * @ApiReturnParams (name="sold", type="integer", description="已售数量")
  142. * @ApiReturnParams (name="product.title", type="integer", description="商品名称")
  143. * @ApiReturnParams (name="product.sales_price", type="integer", description="销售价钱")
  144. * @ApiReturnParams (name="product.market_price", type="integer", description="市场价钱")
  145. * @ApiReturnParams (name="product.image", type="integer", description="商品图片")
  146. * @ApiReturnParams (name="product.product_id", type="integer", description="商品id")
  147. * @ApiReturnParams (name="flash_product_id", type="integer", description="秒杀商品id")
  148. *
  149. */
  150. public function product()
  151. {
  152. $flash_id = $this->request->request('flash_id', 0);
  153. $page = $this->request->request('page', 1);
  154. $pagesize = $this->request->request('pagesize', 15);
  155. $flash_id = Hashids::decodeHex($flash_id);
  156. $productModel = new FlashProduct();
  157. $products = $productModel
  158. ->with('product')
  159. ->where(['flash_id' => $flash_id, 'switch' => FlashProduct::SWITCH_ON])
  160. ->limit(($page - 1) * $pagesize, $pagesize)
  161. ->cache(2)
  162. ->select();
  163. foreach ($products as &$product) {
  164. $product['sold'] = $product['sold'] > $product['number'] ? $product['number'] : $product['sold'];
  165. }
  166. $this->success('', $products);
  167. }
  168. /**
  169. * @ApiTitle (获取秒杀产品)
  170. * @ApiSummary (获取秒杀产品数据)
  171. * @ApiMethod (GET)
  172. * @ApiHeaders (name=cookie, type=string, required=false, description="用户会话的cookie")
  173. * @ApiParams (name="flash_id", type=string, required=true, description="秒杀活动id")
  174. * @ApiParams (name="id", type=string, required=true, description="商品id")
  175. * @ApiReturn ({"code":1,"msg":"","data":{}})
  176. *
  177. * @ApiReturnParams (name="category_id", type="integer", description="分类id")
  178. * @ApiReturnParams (name="title", type="string", description="商品名称")
  179. * @ApiReturnParams (name="image", type="string", description="商品图片")
  180. * @ApiReturnParams (name="images_text", type="array", description="商品图片组")
  181. * @ApiReturnParams (name="desc", type="string", description="商品详情")
  182. * @ApiReturnParams (name="sales", type="integer", description="销量")
  183. * @ApiReturnParams (name="sales_price", type="string", description="销售价钱")
  184. * @ApiReturnParams (name="market_price", type="string", description="市场价钱")
  185. * @ApiReturnParams (name="product_id", type="string", description="商品id")
  186. * @ApiReturnParams (name="stock", type="integer", description="库存")
  187. * @ApiReturnParams (name="look", type="integer", description="观看量")
  188. * @ApiReturnParams (name="use_spec", type="integer", description="是否使用规格")
  189. * @ApiReturnParams (name="server", type="string", description="支持的服务")
  190. * @ApiReturnParams (name="favorite", type="integer", description="是否已收藏")
  191. * @ApiReturnParams (name="evaluate_data", type="json", description="{count:'评论数据',avg:'好评平均值'}")
  192. * @ApiReturnParams (name="coupon", type="array", description="可用优惠券")
  193. * @ApiReturnParams (name="cart_num", type="integer", description="购物车数量")
  194. * @ApiReturnParams (name="spec_list", type="array", description="规格键值数据")
  195. * @ApiReturnParams (name="spec_table_list", type="array", description="规格值数据")
  196. * @ApiReturnParams (name="flash.starttime", type="string", description="秒杀开始时间")
  197. * @ApiReturnParams (name="flash.endtime", type="string", description="秒杀结束时间")
  198. * @ApiReturnParams (name="flash.sold", type="string", description="秒杀商品已售数量")
  199. * @ApiReturnParams (name="flash.number", type="string", description="提供秒杀商品数量")
  200. * @ApiReturnParams (name="flash.text", type="string", description="倒计时时态描述")
  201. * @ApiReturnParams (name="flash.countdown", type="string", description="倒计时")
  202. * @ApiReturnParams (name="flash.countdown.day", type="string", description="天")
  203. * @ApiReturnParams (name="flash.countdown.hour", type="string", description="时")
  204. * @ApiReturnParams (name="flash.countdown.minute", type="string", description="分")
  205. * @ApiReturnParams (name="flash.countdown.second", type="string", description="秒")
  206. *
  207. */
  208. public function productDetail()
  209. {
  210. $productId = $this->request->get('id');
  211. $productId = \addons\unishop\extend\Hashids::decodeHex($productId);
  212. $flashId = $this->request->get('flash_id');
  213. $flashId = \addons\unishop\extend\Hashids::decodeHex($flashId);
  214. try {
  215. $productModel = new Product();
  216. $data = $productModel->where(['id' => $productId])->cache(true, 20, 'flashProduct')->find();
  217. if (!$data) {
  218. $this->error(__('Product not exist'));
  219. }
  220. // 真实浏览量加一
  221. $data->real_look++;
  222. $data->look++;
  223. $data->save();
  224. //服务
  225. $server = explode(',', $data->server);
  226. $configServer = json_decode(Config::getByName('server')['value'],true);
  227. $serverValue = [];
  228. foreach ($server as $k => $v) {
  229. if (isset($configServer[$v])) {
  230. $serverValue[] = $configServer[$v];
  231. }
  232. }
  233. $data->server = count($serverValue) ? implode(' · ', $serverValue) : '';
  234. // 默认没有收藏
  235. $data->favorite = false;
  236. // 评价
  237. $data['evaluate_data'] = (new Evaluate)->where(['product_id' => $productId])
  238. ->field('COUNT(*) as count, IFNULL(CEIL(AVG(rate)/5*100),0) as avg')
  239. ->cache(true, 20, 'flashEvaluate')->find();
  240. $redis = new Redis();
  241. $flash['starttime'] = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'starttime');
  242. $flash['endtime'] = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'endtime');
  243. $flash['sold'] = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'sold');
  244. $flash['number'] = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'number');
  245. $flash['sold'] = $flash['sold'] > $flash['number'] ? $flash['number'] : $flash['sold'];
  246. $flash['text'] = $flash['starttime'] > time() ? '距开始:' : '距结束:';
  247. // 秒杀类型不加载优惠券、促销活动、是否已收藏、评价等等,会影响返回速度
  248. $targetTime = $flash['starttime'] > time() ? $flash['starttime'] : $flash['endtime'];
  249. $flash['countdown'] = FlashSale::countdown($targetTime);
  250. $data['coupon'] = [];
  251. // 秒杀数据
  252. $data['flash'] = $flash;
  253. $data->append(['images_text', 'spec_list', 'spec_table_list'])->toArray();
  254. // 购物车数量
  255. $data['cart_num'] = (new \addons\unishop\model\Cart)->where(['user_id' => $this->auth->id])->count();
  256. } catch (Exception $e) {
  257. $this->error($e->getMessage());
  258. }
  259. $this->success('', $data);
  260. }
  261. /**
  262. * @ApiTitle (创建订单)
  263. * @ApiSummary (创建订单)
  264. * @ApiMethod (POST)
  265. * @ApiHeaders (name=cookie, type=string, required=false, description="用户会话的cookie")
  266. * @ApiHeaders (name=token, type=string, required=true, description="请求的Token")
  267. * @ApiParams (name="flash_id", type=string, required=true, description="秒杀活动id")
  268. * @ApiParams (name="id", type=string, required=true, description="商品id")
  269. * @ApiReturn ({"code":1,"msg":"","data":{}})
  270. *
  271. * @ApiReturnParams (name="product.title", type="string", description="商品名称")
  272. * @ApiReturnParams (name="product.image", type="string", description="商品图片")
  273. * @ApiReturnParams (name="product.sales", type="integer", description="销量")
  274. * @ApiReturnParams (name="product.sales_price", type="string", description="销售价钱")
  275. * @ApiReturnParams (name="product.market_price", type="string", description="市场价钱")
  276. * @ApiReturnParams (name="product.id", type="string", description="商品id")
  277. * @ApiReturnParams (name="product.stock", type="integer", description="库存")
  278. * @ApiReturnParams (name="product.spec", type="integer", description="选中的规格")
  279. * @ApiReturnParams (name="product.number", type="integer", description="购买数量")
  280. *
  281. * @ApiReturnParams (name="address.id", type="integer", description="地址id")
  282. * @ApiReturnParams (name="address.name", type="string", description="收货人名称")
  283. * @ApiReturnParams (name="address.mobile", type="string", description="收货人电话")
  284. * @ApiReturnParams (name="address.address", type="string", description="收货人地址")
  285. * @ApiReturnParams (name="address.province_id", type="integer", description="省份id")
  286. * @ApiReturnParams (name="address.city_id", type="integer", description="城市id")
  287. * @ApiReturnParams (name="address.area_id", type="integer", description="地区id")
  288. * @ApiReturnParams (name="address.is_default", type="integer", description="是否默认")
  289. * @ApiReturnParams (name="address.province.name", type="integer", description="省份")
  290. * @ApiReturnParams (name="address.city.name", type="integer", description="城市")
  291. * @ApiReturnParams (name="address.area.name", type="integer", description="地区")
  292. *
  293. * @ApiReturnParams (name="delivery.id", type="integer", description="货运id")
  294. * @ApiReturnParams (name="delivery.name", type="string", description="货运名称")
  295. * @ApiReturnParams (name="delivery.type", type="string", description="收费类型")
  296. * @ApiReturnParams (name="delivery.min", type="integer", description="至少购买量")
  297. * @ApiReturnParams (name="delivery.first", type="integer", description="首重数量")
  298. * @ApiReturnParams (name="delivery.first_fee", type="string", description="首重价钱")
  299. * @ApiReturnParams (name="delivery.additional", type="integer", description="需重数量")
  300. * @ApiReturnParams (name="delivery.additional_fee", type="string", description="需重价钱")
  301. * @ApiReturnParams (name="flash.sold", type="integer", description="已秒数量")
  302. * @ApiReturnParams (name="flash.sold", type="integer", description="还剩数量")
  303. */
  304. public function createOrder()
  305. {
  306. $productId = $this->request->post('id', 0);
  307. $flashId = $this->request->post('flash_id', 0);
  308. $flashId = \addons\unishop\extend\Hashids::decodeHex($flashId);
  309. $productId = \addons\unishop\extend\Hashids::decodeHex($productId);
  310. try {
  311. $redis = new Redis();
  312. $sold = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'sold');
  313. $number = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'number');
  314. $switch = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'switch');
  315. $starttime = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'starttime');
  316. $endtime = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'endtime');
  317. //判断是否开始或结束
  318. if (time() < $starttime) {
  319. $this->error(__('Activity not started'));
  320. }
  321. if ($endtime < time()) {
  322. $this->error(__('Activity ended'));
  323. }
  324. // 截流
  325. if ($sold >= $number) {
  326. $this->error(__('Item sold out'));
  327. }
  328. if ($switch == FlashSale::SWITCH_NO || $switch == false) {
  329. $this->error(__('Item is off the shelves'));
  330. }
  331. $product = (new Product)->where(['id' => $productId, 'deletetime' => null])->find();
  332. /** 产品基础数据 **/
  333. $spec = $this->request->post('spec', '');
  334. $productData[0] = $product->getDataOnCreateOrder($spec);
  335. if (!$productData) {
  336. $this->error(__('Product not exist'));
  337. }
  338. $productData[0]['image'] = Config::getImagesFullUrl($productData[0]['image']);
  339. $productData[0]['sales_price'] = round($productData[0]['sales_price'], 2);
  340. $productData[0]['market_price'] = round($productData[0]['market_price'], 2);
  341. /** 默认地址 **/
  342. $address = AddressModel::get(['user_id' => $this->auth->id, 'is_default' => AddressModel::IS_DEFAULT_YES]);
  343. if ($address) {
  344. $area = (new Area)->whereIn('id', [$address->province_id, $address->city_id, $address->area_id])->column('name', 'id');
  345. $address = $address->toArray();
  346. $address['province']['name'] = $area[$address['province_id']];
  347. $address['city']['name'] = $area[$address['city_id']];
  348. $address['area']['name'] = $area[$address['area_id']];
  349. }
  350. /** 运费数据 **/
  351. $cityId = $address['city_id'] ? $address['city_id'] : 0;
  352. $delivery = (new DeliveryRuleModel())->getDelivetyByArea($cityId);
  353. $msg = '';
  354. // if ($delivery['status'] == 0) {
  355. // $msg = __('Your receiving address is not within the scope of delivery');
  356. // }
  357. $redis = new Redis();
  358. //$flash['starttime'] = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'starttime');
  359. //$flash['endtime'] = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'endtime');
  360. $flash['sold'] = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'sold');
  361. $flash['number'] = $redis->handler->hGet('flash_sale_' . $flashId . '_' . $productId, 'number');
  362. $flash['sold'] = $flash['sold'] > $flash['number'] ? $flash['number'] : $flash['sold'];
  363. //$flash['text'] = $flash['starttime'] > time() ? '距开始:' : '距结束:';
  364. // 秒杀类型不加载优惠券、促销活动、是否已收藏、评价等等,会影响返回速度
  365. //$targetTime = $flash['starttime'] > time() ? $flash['starttime'] : $flash['endtime'];
  366. //$flash['countdown'] = FlashSale::countdown($targetTime);
  367. } catch (Exception $e) {
  368. $this->error($e->getMessage(), false);
  369. }
  370. $this->success($msg, [
  371. 'product' => $productData,
  372. 'address' => $address,
  373. 'delivery' => $delivery['list'],
  374. 'flash' => $flash
  375. ]);
  376. }
  377. /**
  378. * @ApiTitle (提交订单)
  379. * @ApiSummary (提交订单)
  380. * @ApiMethod (POST)
  381. * @ApiHeaders (name=cookie, type=string, required=false, description="用户会话的cookie")
  382. * @ApiHeaders (name=token, type=string, required=true, description="请求的Token")
  383. * @ApiParams (name="flash_id", type=string, required=true, description="秒杀活动id")
  384. * @ApiParams (name="product_id", type=string, required=true, description="商品id")
  385. * @ApiParams (name="number", type=string, required=true, description="商品数量")
  386. * @ApiParams (name="city_id", type=integer, required=true, description="城市id")
  387. * @ApiParams (name="address_id", type=string, required=true, description="收货地址id")
  388. * @ApiParams (name="delivery_id", type=integer, required=true, description="运费模板id")
  389. * @ApiParams (name="spec", type=string, required=true, description="规格")
  390. * @ApiParams (name="remark", type=string, required=true, description="备注")
  391. * @ApiReturn ({"code":1,"msg":"","data":{}})
  392. *
  393. * @ApiReturnParams (name="order_id", type="string", description="订单编号")
  394. * @ApiReturnParams (name="out_trade_no", type="string", description="商户订单号(支付用)")
  395. *
  396. */
  397. public function submitOrder()
  398. {
  399. $data = $this->request->post();
  400. try {
  401. $validate = Loader::validate('\\addons\\unishop\\validate\\Order');
  402. if (!$validate->check($data, [], 'submitFlash')) {
  403. throw new Exception($validate->getError());
  404. }
  405. Db::startTrans();
  406. // 判断创建订单的条件
  407. if (empty(Hook::get('create_order_before'))) { // 由于自动化测试的时候会注册多个同名行为
  408. Hook::add('create_order_before', 'addons\\unishop\\behavior\\OrderFlash');
  409. }
  410. if (empty(Hook::get('create_order_after'))) {
  411. Hook::add('create_order_after', 'addons\\unishop\\behavior\\OrderFlash');
  412. }
  413. $data['flash_id'] = Hashids::decodeHex($data['flash_id']);
  414. $data['product_id'] = Hashids::decodeHex($data['product_id']);
  415. $orderModel = new \addons\unishop\model\Order();
  416. $result = $orderModel->createOrder($this->auth->id, $data);
  417. Db::commit();
  418. $this->success('', $result);
  419. } catch (Exception $e) {
  420. Db::rollback();
  421. $this->error($e->getMessage(), false);
  422. }
  423. }
  424. }