HomePageModuleConfigClass.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. <?php
  2. namespace bizHd\homePageConfig\classes;
  3. use bizHd\base\classes\BaseClass;
  4. use Yii;
  5. /**
  6. * 门店首页模块配置的 MySQL 落库层
  7. * 表 xhHomePageModuleConfig 以 shopId+moduleKey 唯一标识一条配置,是首页配置的唯一权威数据源,
  8. * Redis 仅作为兼容镜像:读取以本类为准,不存在时才回退旧 Redis key 并自愈迁移。
  9. */
  10. class HomePageModuleConfigClass extends BaseClass
  11. {
  12. public static $baseFile = '\bizHd\homePageConfig\models\HomePageModuleConfig';
  13. /**
  14. * 读取某模块配置,不存在返回 null(区别于「已保存的空数组/空对象」)
  15. *
  16. * @param int $shopId
  17. * @param string $moduleKey
  18. * @return array|null
  19. */
  20. public static function getConfigValue($shopId, $moduleKey)
  21. {
  22. $row = self::getByCondition(['shopId' => intval($shopId), 'moduleKey' => strval($moduleKey)]);
  23. if (empty($row) || !isset($row['configValue'])) {
  24. return null;
  25. }
  26. $decoded = json_decode($row['configValue'], true);
  27. return is_array($decoded) ? $decoded : null;
  28. }
  29. /**
  30. * 保存某模块配置:存在则更新,不存在则新建;并发写入命中唯一索引冲突时兜底改为更新
  31. *
  32. * @param int $mainId 仅作参考字段落库,不参与查重
  33. * @param int $shopId
  34. * @param string $moduleKey
  35. * @param array $payload
  36. * @return bool
  37. */
  38. public static function saveConfigValue($mainId, $shopId, $moduleKey, $payload)
  39. {
  40. $mainId = intval($mainId);
  41. $shopId = intval($shopId);
  42. $moduleKey = strval($moduleKey);
  43. $json = json_encode($payload, JSON_UNESCAPED_UNICODE);
  44. $exist = self::getByCondition(['shopId' => $shopId, 'moduleKey' => $moduleKey], true);
  45. if (!empty($exist)) {
  46. self::updateById($exist->id, ['mainId' => $mainId, 'configValue' => $json]);
  47. return true;
  48. }
  49. try {
  50. self::add(['mainId' => $mainId, 'shopId' => $shopId, 'moduleKey' => $moduleKey, 'configValue' => $json]);
  51. } catch (\Exception $e) {
  52. // 并发写入命中唯一索引冲突时兜底改为更新
  53. $exist = self::getByCondition(['shopId' => $shopId, 'moduleKey' => $moduleKey], true);
  54. if (!empty($exist)) {
  55. self::updateById($exist->id, ['mainId' => $mainId, 'configValue' => $json]);
  56. return true;
  57. }
  58. throw $e;
  59. }
  60. return true;
  61. }
  62. /**
  63. * 存量数据自愈迁移:MySQL 无记录时,回退读取旧 Redis key(历史按 mainId 存,新 key 按 shopId 存),
  64. * 命中后立即落库 MySQL 并镜像写入新 Redis key,保证后续请求都能直接从 MySQL 命中
  65. *
  66. * @param int $mainId
  67. * @param int $shopId
  68. * @param string $moduleKey
  69. * @param string $newRedisKey 按 shopId 生成的新 key
  70. * @param string $legacyRedisKey 按 mainId 生成的历史 key
  71. * @return array|null
  72. */
  73. public static function migrateFromRedis($mainId, $shopId, $moduleKey, $newRedisKey, $legacyRedisKey)
  74. {
  75. $raw = Yii::$app->redis->executeCommand('GET', [$newRedisKey]);
  76. if (empty($raw) && $legacyRedisKey !== $newRedisKey) {
  77. $raw = Yii::$app->redis->executeCommand('GET', [$legacyRedisKey]);
  78. }
  79. if (empty($raw)) {
  80. return null;
  81. }
  82. $decoded = json_decode($raw, true);
  83. if (!is_array($decoded)) {
  84. return null;
  85. }
  86. self::saveConfigValue($mainId, $shopId, $moduleKey, $decoded);
  87. try {
  88. Yii::$app->redis->executeCommand('SET', [$newRedisKey, json_encode($decoded, JSON_UNESCAPED_UNICODE)]);
  89. } catch (\Exception $e) {
  90. Yii::error('首页配置迁移写入新Redis key失败: ' . $e->getMessage());
  91. }
  92. return $decoded;
  93. }
  94. }