ConfigValueRepository.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2024 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\common\repositories\system\config;
  12. use app\common\dao\system\config\SystemConfigValueDao;
  13. use app\common\repositories\BaseRepository;
  14. use app\common\repositories\store\product\ProductRepository;
  15. use app\common\repositories\system\groupData\GroupDataRepository;
  16. use app\common\repositories\system\groupData\GroupRepository;
  17. use crmeb\jobs\SyncProductTopJob;
  18. use crmeb\services\DownloadImageService;
  19. use crmeb\services\RedisCacheService;
  20. use think\exception\ValidateException;
  21. use think\facade\Cache;
  22. use think\facade\Db;
  23. use think\facade\Queue;
  24. /**
  25. * Class ConfigValueRepository
  26. * @package app\common\repositories\system\config
  27. * @mixin SystemConfigValueDao
  28. */
  29. class ConfigValueRepository extends BaseRepository
  30. {
  31. public $special = [
  32. 'serve_account'
  33. ];
  34. const CONFIG_KEY_PREFIX = 'merchant_sys_config_';
  35. /**
  36. * ConfigValueRepository constructor.
  37. * @param SystemConfigValueDao $dao
  38. */
  39. public function __construct(SystemConfigValueDao $dao)
  40. {
  41. $this->dao = $dao;
  42. }
  43. /**
  44. * 根据指定的键获取配置信息,并为未找到的键设置默认值。
  45. *
  46. * 此方法用于从数据库中检索特定商户的配置信息。它接受一个键的数组和一个商户ID,
  47. * 返回一个包含所请求配置键值对的数组。如果某个键在数据库中不存在,
  48. * 则该键的值将被设置为空字符串。
  49. *
  50. * @param array $keys 需要获取配置值的键的数组。
  51. * @param int $merId 商户的唯一标识ID。
  52. * @return array 返回一个包含配置键值对的数组,对于未找到的键,值为空字符串。
  53. */
  54. public function more(array $keys, int $merId): array
  55. {
  56. // 通过指定的键和商户ID从数据库中获取配置信息
  57. $config = $this->dao->fields($keys, $merId);
  58. // 遍历键数组,为在数据库中未找到的键设置默认值为空字符串
  59. foreach ($keys as $key) {
  60. if (!isset($config[$key])) {
  61. $config[$key] = '';
  62. }
  63. }
  64. // 返回处理后的配置信息数组
  65. return $config;
  66. }
  67. /**
  68. * 根据键和商家ID获取值。
  69. *
  70. * 本函数旨在通过指定的键和商家ID从数据存储中检索相应的值。如果找到值,则返回该值;
  71. * 如果未找到值或检索操作失败,则返回空字符串。
  72. *
  73. * @param string $key 需要检索的键。此键用于唯一标识数据存储中的特定项。
  74. * @param int $merId 商家ID。用于限定检索范围,仅返回与该商家相关的数据。
  75. * @return string 返回与键和商家ID对应的值。如果未找到相应的值,则返回空字符串。
  76. */
  77. public function get(string $key, int $merId)
  78. {
  79. // 通过键和商家ID从数据存储中获取值
  80. $value = $this->dao->value($key, $merId);
  81. // 如果值存在则返回,否则返回空字符串
  82. return $value ?? '';
  83. }
  84. /**
  85. * 保存配置数据
  86. *
  87. * 该方法用于处理配置项的保存操作。它首先确定哪些配置键是有效的,然后处理这些键对应的值,
  88. * 包括类型转换和值的验证,最后保存处理后的数据。
  89. *
  90. * @param int $cid 配置分类ID,用于确定配置项的分类。
  91. * @param array $formData 用户提交的表单数据,包含所有的配置项及其值。
  92. * @param int $merId 商家ID,用于区分不同商家的配置数据(如果适用)。
  93. * @throws ValidateException 如果配置项的值不合法,例如小于0的数字,将抛出验证异常。
  94. */
  95. public function save($cid, array $formData, int $merId)
  96. {
  97. $this->saveBefore($cid, $formData, $merId);
  98. // 获取表单数据的所有键
  99. $keys = array_keys($formData);
  100. // 确定哪些键是有效的配置键,通过与配置仓库中的键进行交集运算
  101. $keys = app()->make(ConfigRepository::class)->intersectionKey($cid, $keys);
  102. // 如果没有有效的键,则直接返回,不进行后续操作
  103. if (!count($keys)) return;
  104. // 遍历有效的键,对每个键对应的值进行处理
  105. foreach ($keys as $key => $info) {
  106. // 如果表单数据中不存在当前键,则从表单数据中移除该键
  107. if (!isset($formData[$key]))
  108. unset($formData[$key]);
  109. else {
  110. // 如果当前配置项的类型为数字,则进行数值验证和类型转换
  111. if ($info['config_type'] == 'number') {
  112. // 如果值为空或小于0,则抛出验证异常
  113. if ($formData[$key] === '' || $formData[$key] < 0)
  114. throw new ValidateException($info['config_name'] . '不能小于0');
  115. // 将值转换为浮点数
  116. $formData[$key] = floatval($formData[$key]);
  117. }
  118. // 对当前配置项进行进一步的处理,例如存储或转换
  119. $this->separate($key,$formData[$key],$merId);
  120. }
  121. }
  122. // 保存处理后的表单数据
  123. $this->setFormData($formData, $merId);
  124. }
  125. public function saveBefore($cid, array $formData, int $merId)
  126. {
  127. return ;
  128. }
  129. /**
  130. * 需要做特殊处理的配置参数
  131. * @param $key
  132. * @author Qinii
  133. * @day 2022/11/17
  134. */
  135. public function separate($key,$value,$merId)
  136. {
  137. switch($key) {
  138. case 'mer_svip_status':
  139. //修改商户的会员状态
  140. app()->make(ProductRepository::class)->getSearch([])->where(['mer_id' => $merId,'product_type' => 0])->update([$key => $value]);
  141. break;
  142. //热卖排行
  143. case 'hot_ranking_switch':
  144. if ($value) {
  145. Queue::push(SyncProductTopJob::class, []);
  146. }
  147. break;
  148. case 'margin_remind_day':
  149. if ($value && floor($value) != $value) throw new ValidateException('时间不可为小数');
  150. break;
  151. case 'svip_switch_status':
  152. if ($value == 1) {
  153. $groupDataRepository = app()->make(GroupDataRepository::class);
  154. $groupRepository = app()->make(GroupRepository::class);
  155. $group_id = $groupRepository->getSearch(['group_key' => 'svip_pay'])->value('group_id');
  156. $where['group_id'] = $group_id;
  157. $where['status'] = 1;
  158. $count = $groupDataRepository->getSearch($where)->field('group_data_id,value,sort,status')->count();
  159. if (!$count)
  160. throw new ValidateException('请先添加会员类型');
  161. }
  162. break;
  163. default:
  164. break;
  165. }
  166. return ;
  167. }
  168. /**
  169. * 设置商家配置数据
  170. * 该方法用于批量更新或插入商家的配置信息。它首先检查每个配置项是否已存在,如果存在,则更新其值;如果不存在,则创建新的配置项。
  171. * 使用数据库事务确保操作的原子性。
  172. *
  173. * @param array $formData 商家配置的数据数组,键值对形式,其中键是配置项的键,值是配置项的值。
  174. * @param int $merId 商家的ID,用于标识商家。
  175. */
  176. public function setFormData(array $formData, int $merId)
  177. {
  178. // 开启数据库事务处理
  179. Db::transaction(function () use ($merId, $formData) {
  180. // 遍历配置数据数组
  181. foreach ($formData as $key => $value) {
  182. // 检查当前配置项是否已存在
  183. if ($this->dao->merExists($key, $merId)) {
  184. // 如果已存在,则更新配置项的值
  185. $this->dao->merUpdate($merId, $key, ['value' => $value]);
  186. } else {
  187. // 如果不存在,则创建新的配置项
  188. $this->dao->create([
  189. 'mer_id' => $merId,
  190. 'value' => $value,
  191. 'config_key' => $key
  192. ]);
  193. }
  194. }
  195. });
  196. // 同步配置信息到其他系统或缓存
  197. $this->syncConfig();
  198. }
  199. /**
  200. * 同步配置到Redis缓存中。
  201. * 该方法从数据库中查询当前的配置信息,然后将这些配置同步到Redis缓存中。
  202. * 同时,它也会清理掉已经不存在于数据库中的旧配置项,以保持缓存的同步和清洁。
  203. * 最后,它会更新一个配置标志,标记配置的同步时间。
  204. */
  205. public function syncConfig()
  206. {
  207. // 从数据库中查询所有的配置项,包括值、配置键和商家ID。
  208. $list = $this->query([])->column('value,config_key,mer_id');
  209. // 实例化Redis缓存服务类。
  210. $make = app()->make(RedisCacheService::class);
  211. // 获取当前Redis中所有以CONFIG_KEY_PREFIX开头的键,这些可能是旧的配置键。
  212. $oldKeys = $make->keys(self::CONFIG_KEY_PREFIX.'*') ?: [];
  213. // 将键名和键值设为相同的值,以便后续操作。
  214. $oldKeys = array_combine($oldKeys, $oldKeys);
  215. // 准备一个数组,用于存储要设置到Redis中的新配置项。
  216. $mset = [];
  217. foreach ($list as $item) {
  218. // 构建新的配置键,并将配置值存储到$mset数组中。
  219. $key = self::CONFIG_KEY_PREFIX . $item['mer_id'] . '_' . $item['config_key'];
  220. $mset[$key] = $item['value'];
  221. // 从旧的键中删除已经存在于新配置中的键,以便后续清理。
  222. unset($oldKeys[$key]);
  223. }
  224. // 更新配置标志,记录配置的同步时间。
  225. $mset[self::CONFIG_KEY_PREFIX.'configFlag'] = time();
  226. // 通过Redis缓存服务类设置新的配置项。
  227. $make->mset($mset);
  228. // 如果还有剩余的旧键,说明这些键已经不存在于新的配置中,需要从Redis中删除。
  229. if (count($oldKeys)) {
  230. $make->handler()->del(...array_values($oldKeys));
  231. }
  232. // 删除API配置的缓存,以便下次请求时重新从Redis获取最新配置。
  233. Cache::delete('get_api_config');
  234. }
  235. /**
  236. * 清除缓存后需要重新增加缓存的配置
  237. * @author Qinii
  238. * @day 2023/10/20
  239. */
  240. public function special()
  241. {
  242. $list = $this->query([])->column('value,config_key,mer_id');
  243. foreach ($list as $item) {
  244. if (in_array($item['config_key'], $this->special)) {
  245. Cache::set($item['config_key'], json_decode($item['value']));
  246. }
  247. }
  248. }
  249. /**
  250. * 根据商家ID和配置名称获取配置信息。
  251. * 该方法支持批量获取配置,如果配置信息存储在Redis中,则直接从Redis中读取;
  252. * 如果Redis中不存在配置信息,则从数据库同步配置到Redis。
  253. *
  254. * @param int $merId 商家ID,用于区分不同商家的配置。
  255. * @param string|array $name 配置名称,可以是单个配置名称的字符串,也可以是多个配置名称的数组。
  256. * @return array|mixed 返回配置值,如果请求的是单个配置,则返回对应的配置值对象;
  257. * 如果请求的是多个配置,则返回一个包含多个配置值的数组。
  258. */
  259. public function getConfig(int $merId, $name)
  260. {
  261. // 创建Redis缓存服务实例
  262. $make = app()->make(RedisCacheService::class);
  263. // 处理配置名称为数组的情况
  264. if (is_array($name)) {
  265. if (!count($name)) {
  266. // 如果配置名称数组为空,则直接返回空数组
  267. return [];
  268. }
  269. $names = $name;
  270. } else {
  271. // 如果配置名称不是数组,则将其转换为数组
  272. $names = [$name];
  273. }
  274. $configFlag = $make->mGet([self::CONFIG_KEY_PREFIX.'configFlag']);
  275. // 从Redis中获取配置标志,用于判断是否需要同步配置到Redis
  276. if (empty($configFlag) || $configFlag[0] == false) {
  277. // 如果Redis中没有配置标志,则调用同步配置方法
  278. $this->syncConfig();
  279. }
  280. // 准备配置键名数组
  281. $keys = [];
  282. foreach ($names as $item) {
  283. $keys[] = self::CONFIG_KEY_PREFIX . $merId . '_' . $item;
  284. }
  285. // 从Redis中批量获取配置值
  286. $values = $make->mGet($keys) ?: [];
  287. if (!is_array($name)) {
  288. // 如果请求的是单个配置,则直接返回对应的配置值
  289. return ($values[0] ?? '') ? json_decode($values[0]) : '';
  290. }
  291. // 处理批量获取配置的情况
  292. $data = [];
  293. if (!count($values)) {
  294. // 如果没有获取到任何配置值,则初始化一个空数组,每个配置对应一个空字符串值
  295. foreach ($names as $v) {
  296. $data[$v] = '';
  297. }
  298. return $data;
  299. }
  300. // 遍历获取到的配置值,将其按照配置名称放入$data数组中
  301. foreach ($values as $i => $value) {
  302. $data[$names[$i]] = $value ? json_decode($value) : '';
  303. }
  304. return $data;
  305. }
  306. }