ActivityCache.php
20.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
<?php
namespace addons\groupon\library\traits;
use addons\groupon\exception\Exception;
use addons\groupon\library\Redis;
use addons\groupon\model\Activity;
use addons\groupon\model\ActivityGoodsSkuPrice;
use addons\groupon\model\Goods;
use addons\groupon\model\GoodsSkuPrice;
use addons\groupon\model\OrderItem;
use addons\groupon\model\ScoreGoodsSkuPrice;
/**
* 活动 redis 缓存
*/
trait ActivityCache
{
protected $zsetKey = 'zset-activity';
protected $hashPrefix = 'hash-activity:';
protected $hashGoodsPrefix = 'goods-';
protected $hashGrouponPrefix = 'groupon-';
public function hasRedis($is_interrupt = false)
{
$error_msg = '';
try {
$redis = $this->getRedis();
// 检测连接是否正常
$redis->ping();
} catch (\BadFunctionCallException $e) {
// 缺少扩展
$error_msg = $e->getMessage() ? $e->getMessage() : "缺少 redis 扩展";
} catch (\RedisException $e) {
// 连接拒绝
\think\Log::write('redis connection redisException fail: ' . $e->getMessage());
$error_msg = $e->getMessage() ? $e->getMessage() : "redis 连接失败";
} catch (\Exception $e) {
// 异常
\think\Log::write('redis connection fail: ' . $e->getMessage());
$error_msg = $e->getMessage() ? $e->getMessage() : "redis 连接异常";
}
if ($error_msg) {
if ($is_interrupt) {
throw new \Exception($error_msg);
} else {
return false;
}
}
return true;
}
public function getRedis()
{
if (!isset($GLOBALS['SPREDIS'])) {
$GLOBALS['SPREDIS'] = (new Redis())->getRedis();
}
return $GLOBALS['SPREDIS'];
}
/**
* 将活动设置到 redis 中
*
* @param [type] $activity
* @param array $goodsList
* @return void
*/
public function setActivity($activity, $goodsList = [])
{
$redis = $this->getRedis();
// hash 键值
$hashKey = $this->getHashKey($activity['id'], $activity['type']);
// 删除旧的可变数据,需要排除销量 key
if ($redis->EXISTS($hashKey)) {
// 如果 hashKey 存在,删除规格
$hashs = $redis->HGETALL($hashKey);
foreach ($hashs as $hashField => $hashValue) {
// 是商品规格,并且不是销量
if (strpos($hashField, $this->hashGoodsPrefix) !== false && strpos($hashField, '-sale') === false) {
// 商品规格信息,删掉
$redis->HDEL($hashKey, $hashField);
}
}
}
$redis->HMSET(
$hashKey,
[
'id' => $activity['id'],
'title' => $activity['title'],
'type' => $activity['type'],
'richtext_id' => $activity['richtext_id'],
'richtext_title' => $activity['richtext_title'],
'starttime' => $activity['starttime'],
'endtime' => $activity['endtime'],
'rules' => is_array($activity['rules']) ? json_encode($activity['rules']) : $activity['rules'],
'goods_ids' => $activity['goods_ids']
]
);
// 将 hash 键值存入 有序集合,score 为 id
$redis->ZADD($this->zsetKey, $activity['starttime'], $hashKey);
}
/**
* 获取所有活动
*
* @param array $activityTypes 为空将查询所有类型的活动
* @param string $status
* @param string $format_type // 格式化类型,默认clear,清理多余的字段,比如拼团的 团信息
* @return void
*/
public function getActivityList($activityTypes = [], $status = 'all', $format_type = 'normal')
{
$redis = $this->getRedis();
// 获取对应的活动类型的集合
$activityHashList = $this->getActivityHashKeysByType($activityTypes);
$activityList = [];
if (!$activityHashList) { // 没有获取到,返回空数组
return $activityList;
}
foreach ($activityHashList as $activityHashKey) {
// 查询活动状态
if ($status != 'all') {
$activity_status = $this->getActivityStatusByHashKey($activityHashKey);
if ($status != $activity_status) {
continue;
}
}
// 格式化活动
$activity = $this->formatActivityByType($activityHashKey, $format_type);
if ($activity) {
$activityList[] = $activity;
}
}
return $activityList;
}
/**
* 查询商品列表,详情时,获取这个商品对应的满减,满折等活动
*
* @param int $goods_id 特定商品 id
* @param Array $activityTypes 要查询的活动数组
* @param string $type 查单条,还是全部 all|first
* @param int $activity_id
* @return void
*/
public function getGoodsActivityDiscount($goods_id, $activityTypes = [], $type = 'all', $activity_id = 0)
{
// 获取活动的 hash key
$activityHashKey = $this->getActivityHashKeyByGoods($goods_id, $activityTypes, $type, $activity_id);
// 如果存在活动
if ($activityHashKey) {
if (is_array($activityHashKey)) {
$activities = [];
foreach ($activityHashKey as $key => $hashKey) {
$activity = $this->formatActivityByType($hashKey, 'discount');
if ($activity) {
$activities[] = $activity;
}
}
return $activities;
} else {
// 获取活动所有信息
return $activity = $this->formatActivityByType($activityHashKey, 'discount');
}
}
return $type == 'all' ? [] : null;
}
/**
* 通过活动的键值,获取活动完整信息
*
* @param [type] $activityHashKey
* @return array
*/
public function getActivityByHashKey($activityHashKey)
{
$redis = $this->getRedis();
// 取出整条 hash 记录
$activityHash = $redis->HGETALL($activityHashKey);
return $activityHash;
}
// 删除活动缓存
public function delActivity($activity)
{
$redis = $this->getRedis();
$hashKey = $this->getHashKey($activity['id'], $activity['type']);
// 删除 hash
$redis->DEL($hashKey);
// 删除集合
$redis->ZREM($this->zsetKey, $hashKey);
}
/**
* 通过商品获取该商品参与的活动的hash key
*
* @param [type] $goods_id
* @param Array $activityType
* @param string $type 全部还是第一条
* @param integer $activity_id
* @return void
*/
private function getActivityHashKeyByGoods($goods_id, $activityType = [], $type = 'first', $activity_id = 0)
{
$redis = $this->getRedis();
// 获取对应类型的活动集合
$activityHashList = $this->getActivityHashKeysByType($activityType, $activity_id);
$activityHashKeys = [];
if (!$activityHashList) { // 没有获取到,返回空数组
return $activityHashKeys;
}
foreach ($activityHashList as $activityHashKey) {
// 获取活动状态
$activity_status = $this->getActivityStatusByHashKey($activityHashKey);
if ($activity_status != 'ing') { // 判断活动必须进行中
continue;
}
// 判断这条活动是否包含该商品
$goods_ids = array_filter(explode(',', $redis->HGET($activityHashKey, 'goods_ids')));
if (in_array($goods_id, $goods_ids) || empty($goods_ids)) {
$activityHashKeys[] = $activityHashKey;
if ($type == 'first') { // 只取第一条
break;
}
}
}
if ($activity_id && !$activityHashKeys) {
// 查询特定活动,没找到,抛出异常, 活动不存在
throw new Exception('活动不存在');
}
return $type == 'first' ? ($activityHashKeys[0] ?? '') : $activityHashKeys;
}
/**
* 获取活动的状态
*/
private function getActivityStatusByHashKey($activityHashKey)
{
$redis = $this->getRedis();
$starttime = $redis->HGET($activityHashKey, 'starttime');
$endtime = $redis->HGET($activityHashKey, 'endtime');
if ($starttime < time() && $endtime > time()) {
$status = 'ing';
} else if ($starttime > time()) {
$status = 'nostart';
} else if ($endtime < time()) {
$status = 'ended';
}
return $status;
}
/**
* 获取活动类型数组的所有活动hashkeys
*
* @param array|string $activityTypes
* @return array
*/
private function getActivityHashKeysByType($activityTypes = [], $activity_id = 0)
{
$redis = $this->getRedis();
$activityTypes = is_array($activityTypes) ? $activityTypes : [$activityTypes];
$activityTypes = array_values(array_filter($activityTypes)); // 过滤空值
// 获取活动集合
$hashList = $redis->ZRANGE($this->zsetKey, 0, 999999999);
// 优先判断 activity_id,可以唯一确定 活动key, 不需要判断 activityTypes
if ($activity_id) {
$activityHashKeys = [];
foreach ($hashList as $hashKey) {
$suffix = ':' . $activity_id;
// 判断是否是要找的活动id, 截取 hashKey 后面几位,是否为当前要查找的活动 id
if (substr($hashKey, (strlen($hashKey) - strlen($suffix))) == $suffix) {
$activityHashKeys[] = $hashKey;
break;
}
}
return $activityHashKeys;
}
// 判断是否传入了 需要的活动类型,默认取全部活动
if ($activityTypes) {
// 获取对应的活动类型的集合
$activityHashKeys = [];
foreach ($hashList as $hashKey) {
// 循环要查找的活动类型数组
foreach ($activityTypes as $type) {
if (strpos($hashKey, $type) !== false) { // 是要查找的类型
$activityHashKeys[] = $hashKey;
break;
}
}
}
} else {
// 全部活动
$activityHashKeys = $hashList;
}
return $activityHashKeys;
}
// ------------------------格式化活动---------------------
/**
* 格式化活动
*
* @param string array $activityHash 活动 key 或者活动完整信息
* @param string $type 格式化方式
* @param array $data 额外参数
* @return void
*/
public function formatActivityByType($activityHash, $type = 'normal', $data = [])
{
switch ($type) {
case 'normal':
// 正常模式,只移除销量,团信息,保留全部商品规格数据
$activity = $this->getActivityFormatNormal($activityHash, $data);
break;
case 'clear':
// 简洁模式,只保留活动表基本信息
$activity = $this->getActivityFormatClear($activityHash, $data);
break;
case 'goods':
// 按照前端商品方式格式化
$activity = $this->getActivityFormatGoods($activityHash, $data);
break;
case 'discount':
$activity = $this->getActivityFormatDiscount($activityHash, $data);
break;
default:
$activity = $this->getActivityFormatNormal($activityHash, $data);
break;
}
return $activity;
}
/**
* 正常模式,只移除销量, 团信息,保留全部商品规格数据
*
* @param string $activityHashKey
* @param array $data 额外数据,商品 id
* @return void
*/
private function getActivityFormatNormal($activityHashKey, $data = [])
{
// 传入的是活动的key
$activityHash = $this->getActivityByHashKey($activityHashKey);
$activity = [];
foreach ($activityHash as $key => $value) {
// 包含 -sale 全部跳过
if (strpos($key, '-sale') !== false) {
continue;
} else if (strpos($key, $this->hashGrouponPrefix) !== false) {
// 拼团的参团人数,团用户,移除
continue;
} else if ($key == 'rules') {
$activity[$key] = json_decode($value, true);
} else {
// 普通键值
$activity[$key] = $value;
}
}
if ($activity) {
// 处理活动状态
$activity['status_code'] = Activity::getStatusCode($activity);
}
return $activity ?: null;
}
/**
* 简洁模式,只保留活动表基本信息
*
* @param string $activityHashKey
* @param array $data 额外数据,商品 id
* @return void
*/
private function getActivityFormatClear($activityHashKey, $data = [])
{
$activityHash = $this->getActivityByHashKey($activityHashKey);
$activity = [];
foreach ($activityHash as $key => $value) {
// 包含 -sale 全部跳过
if (strpos($key, $this->hashGoodsPrefix) !== false) {
continue;
} else if (strpos($key, $this->hashGrouponPrefix) !== false) {
// 拼团的参团人数,团用户,移除
continue;
} else if ($key == 'rules') {
$activity[$key] = json_decode($value, true);
} else {
// 普通键值
$activity[$key] = $value;
}
}
if ($activity) {
// 处理活动状态
$activity['status_code'] = Activity::getStatusCode($activity);
}
return $activity ?: null;
}
/**
* 获取并按照商品展示格式化活动数据
*
* @param string $activityHashKey hash key
* @param array $data 额外数据,商品 id
* @return array
*/
private function getActivityFormatGoods($activityHashKey, $data = [])
{
$goods_id = $data['goods_id'] ?? 0;
// 传入的是活动的key
$activityHash = $this->getActivityByHashKey($activityHashKey);
$activity = [];
// 商品前缀
$goodsPrefix = $this->hashGoodsPrefix . ($goods_id ? $goods_id . '-' : '');
foreach ($activityHash as $key => $value) {
// 包含 -sale 全部跳过
if (strpos($key, '-sale') !== false) {
continue;
} else if (strpos($key, $goodsPrefix) !== false) {
// 商品规格信息,或者特定商品规格信息
$goods = json_decode($value, true);
// 计算销量库存数据
$goods = $this->calcGoods($goods, $activityHashKey);
// 商品规格项
$activity['activity_goods_sku_price'][] = $goods;
} else if ($goods_id && strpos($key, $this->hashGoodsPrefix) !== false) {
// 需要特定商品时,移除别的非当前商品的数据
continue;
} else if (strpos($key, $this->hashGrouponPrefix) !== false) {
// 拼团的参团人数,团用户,移除
continue;
} else if ($key == 'rules') {
$activity[$key] = json_decode($value, true);
} else {
// 普通键值
$activity[$key] = $value;
}
}
if ($activity) {
// 处理活动状态
$activity['status_code'] = Activity::getStatusCode($activity);
}
return $activity ?: null;
}
/**
* 获取并按照折扣格式展示格式化活动数据
*
* @param string $activityHashKey hash key
* @param array $data 额外数据
* @return void
*/
private function getActivityFormatDiscount($activityHashKey, $data = [])
{
$activityHash = $this->getActivityByHashKey($activityHashKey);
$activity = [];
foreach ($activityHash as $key => $value) {
if ($key == 'rules') {
$rules = json_decode($value, true);
// 存在折扣
if (isset($rules['discounts']) && $rules['discounts']) {
// 处理展示优惠,full 从小到大
$discounts = $rules['discounts'] ?? [];
$discountsKeys = array_column($discounts, null, 'full');
ksort($discountsKeys);
$rules['discounts'] = array_values($discountsKeys); // 优惠按照 full 从小到大排序
}
$activity[$key] = $rules;
} else {
// 普通键值
$activity[$key] = $value;
}
}
if ($activity) {
// 处理活动状态
$activity['status_code'] = Activity::getStatusCode($activity);
}
return $activity ?: null;
}
/**
* 计算每个规格的真实库存、销量
*
* @param [type] $goods
* @param [type] $activityHashKey
* @return void
*/
private function calcGoods($goods, $activityHashKey)
{
$redis = $this->getRedis();
// 销量 key
$saleKey = $this->getHashGoodsKey($goods['goods_id'], $goods['sku_price_id'], true);
// 缓存中的销量
$cacheSale = $redis->HGET($activityHashKey, $saleKey);
$stock = $goods['stock'] - $cacheSale;
$goods['stock'] = $stock > 0 ? $stock : 0;
$goods['sales'] = $cacheSale;
return $goods;
}
// 拼接 hash key
private function getHashKey($activity_id, $activity_type)
{
// 示例 hash-activity:groupon:25
return $this->hashPrefix . $activity_type . ':' . $activity_id;
}
// 拼接 hash 表中 goods 的 key
private function getHashGoodsKey($goods_id, $sku_price_id, $is_sale = false)
{
// 示例 商品规格:goods-25-30 or 商品规格销量:goods-25-30-sale
return $this->hashGoodsPrefix . $goods_id . '-' . $sku_price_id . ($is_sale ? '-sale' : '');
}
// 拼接 hash 表中 groupon 的 key
private function getHashGrouponKey($groupon_id, $goods_id, $type = '')
{
return $this->hashGrouponPrefix . $groupon_id . '-' . $goods_id . ($type ? '-' . $type : '');
}
// 获取 key 集合
public function getKeys($detail, $activity)
{
// 获取 hash key
$activityHashKey = $this->getHashKey($activity['activity_id'], $activity['activity_type']);
$goodsSkuPriceKey = '';
$saleKey = '';
if (isset($detail['goods_sku_price_id']) && $detail['goods_sku_price_id']) {
// 获取 hash 表中商品 sku 的 key
$goodsSkuPriceKey = $this->getHashGoodsKey($detail['goods_id'], $detail['goods_sku_price_id']);
// 获取 hash 表中商品 sku 的 销量的 key
$saleKey = $this->getHashGoodsKey($detail['goods_id'], $detail['goods_sku_price_id'], true);
}
// 需要拼团的字段
$grouponKey = '';
$grouponNumKey = '';
$grouponUserlistKey = '';
if (isset($detail['groupon_id']) && $detail['groupon_id']) {
// 获取 hash 表中团 key
$grouponKey = $this->getHashGrouponKey($detail['groupon_id'], $detail['goods_id']);
// 获取 hash 表中团当前人数 key
$grouponNumKey = $this->getHashGrouponKey($detail['groupon_id'], $detail['goods_id'], 'num');
// 获取 hash 表中团当前人员列表 key
$grouponUserlistKey = $this->getHashGrouponKey($detail['groupon_id'], $detail['goods_id'], 'userlist');
}
return compact('activityHashKey', 'goodsSkuPriceKey', 'saleKey', 'grouponKey', 'grouponNumKey', 'grouponUserlistKey');
}
}