WalletLogic.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <?php
  2. namespace addons\HiGo\common\logic;
  3. use addons\HiGo\common\enums\HiGoEnum;
  4. use addons\HiGo\common\models\HigoHibeiLog;
  5. use addons\HiGo\common\models\HigoMall;
  6. use addons\HiGo\common\models\HigoWallet;
  7. use addons\HiGo\common\models\HigoWalletFreezeLog;
  8. use addons\HiGo\common\models\HigoWalletFrozenLog;
  9. use addons\HiGo\common\models\HigoWalletLog;
  10. use common\enums\RabbitMqEnum;
  11. use common\enums\RedisKeyEnum;
  12. use common\enums\StatusEnum;
  13. use common\helpers\LockHelper;
  14. use common\helpers\TaskHelper;
  15. use common\models\common\CapitalLog;
  16. use common\traits\ErrorTrait;
  17. use Exception;
  18. use Yii;
  19. /**
  20. * Class HiZhiLogic
  21. * 嗨值/嗨呗钱包逻辑类
  22. * @Author: mz
  23. * @DateTime: 2022/11/08 16:39
  24. * @Copyright: copyright (c) 2022 广东七件事集团
  25. */
  26. class WalletLogic
  27. {
  28. use ErrorTrait;
  29. /**
  30. * 获取资金类型
  31. * @return array
  32. */
  33. public static function getWalletType()
  34. {
  35. return [
  36. HiGoEnum::WALLET_TYPE_HI_VALUE,
  37. HiGoEnum::WALLET_TYPE_HI_BEI,
  38. ];
  39. }
  40. /**
  41. * 获取资金类型描述
  42. * @param $type
  43. * @return mixed|string
  44. */
  45. public static function getWalletTypeMsg($mall_id, $type)
  46. {
  47. $arr = HiGoEnum::getHigoWalletText($mall_id);
  48. return $arr[$type] ?? '';
  49. }
  50. /**
  51. * 直接操作用户钱包
  52. * @param $task_id
  53. * @param $data
  54. *
  55. * @return bool
  56. */
  57. public static function handle($data, $task_id = 0)
  58. {
  59. if (!empty($data['message_id'])) {
  60. $task_id = $data['message_id'];
  61. unset($data['message_id']);
  62. }
  63. $user_id = $data['user_id'];
  64. $wallet_type = $data['wallet_type'];
  65. $mall_id = $data['mall_id'];
  66. $is_send = $data['is_send'] ?? 1; //是否属于发放
  67. $is_freeze = $data['is_freeze'] ?? false; //是否操作冻结资金
  68. $from_normal_to_freeze = $data['from_normal_to_freeze'] ?? false; //从正常账户 移动到冻结账户
  69. $unfreeze = $data['unfreeze'] ?? false; //是否操作解冻资金
  70. $freeze_log_id = $data['freeze_log_id'] ?? 0; //操作解冻资金的记录ID
  71. $capital_types = self::getWalletType();
  72. if (!in_array($wallet_type, $capital_types)) {
  73. self::$static_error = '操作类型有误';
  74. return false;
  75. }
  76. if(bccomp($data['change_num'],0,10) == 0){
  77. self::$static_error = '操作金额为0';
  78. return false;
  79. }
  80. //lock
  81. $lock_key = RedisKeyEnum::suffix(HiGoEnum::LOCK_HIGO_USER_WALLET, $user_id);
  82. $identification = uniqid();
  83. if (LockHelper::lock($lock_key, $identification, 100, 100)) {
  84. $t = \Yii::$app->db->beginTransaction();
  85. try {
  86. $edit_field = empty($is_freeze) ? $wallet_type : 'freeze_' . $wallet_type;
  87. //$edit_field = $wallet_type;
  88. //修改用户钱包表
  89. $user_wallet_model = HigoWallet::findOne(['mall_id' => $mall_id, 'user_id' => $user_id]);
  90. if (empty($user_wallet_model)) {
  91. $user_wallet_model = HigoWallet::setData([
  92. 'user_id' => $user_id,
  93. 'mall_id' => $mall_id,
  94. 'status' => StatusEnum::ENABLED
  95. ]);
  96. }
  97. $current_num = $user_wallet_model->$edit_field;
  98. $after_num = bcadd($current_num, $data['change_num'], 10);
  99. if (bccomp($after_num, 0, 10) < 0) {
  100. throw new Exception('用户' . self::getWalletTypeMsg($mall_id, $edit_field) . '不足');
  101. }
  102. //操作解冻,且资金是非冻结
  103. if (strpos($edit_field, 'freeze') === false && $data['change_num'] > 0 && $unfreeze) {
  104. $freeze_field = 'freeze_' . $edit_field;
  105. if(bccomp($user_wallet_model->$freeze_field, $data['change_num'], 10) < 0){
  106. throw new Exception('用户冻结' . self::getWalletTypeMsg($mall_id, $edit_field) . '不足');
  107. }
  108. $user_wallet_model->$freeze_field = bcsub($user_wallet_model->$freeze_field,$data['change_num'],10);
  109. //TODO $freeze_log_id
  110. if($freeze_log_id){
  111. HigoWalletFreezeLog::updateAll(['status' => CapitalLog::STATUS_SEND],['id' => $freeze_log_id]);
  112. }
  113. }
  114. $user_wallet_model->$edit_field = $after_num;
  115. if($from_normal_to_freeze && $is_freeze && $data['change_num'] > 0){
  116. //操作冻结账户,1、正常账户,减少对应金额,2、往冻结账户添加金额
  117. $freeze_edit_num = bcmul($data['change_num'],-1,10);
  118. $user_wallet_model->$wallet_type = bcadd($user_wallet_model->$wallet_type, $freeze_edit_num, 10);
  119. }
  120. if (!$user_wallet_model->save()) throw new Exception($user_wallet_model->getErrorMessage());
  121. $setData = [
  122. 'mall_id' => $mall_id,
  123. 'user_id' => $user_id,
  124. 'change_type' => $data['change_num'] > 0 ? CapitalLog::CHANGE_TYPE_ADD : CapitalLog::CHANGE_TYPE_SUB,
  125. 'change_num' => abs($data['change_num']),
  126. 'after_num' => $after_num,
  127. 'desc' => $data['desc'] ?? '',
  128. 'source_table' => $data['source_table'] ?? '',
  129. 'source_table_id' => $data['source_table_id'] ?? 0,
  130. 'from_type' => $data['from_type'] ?? '',
  131. 'wallet_type' => $data['wallet_type'],
  132. 'addon_name' => $data['addon_name'] ?? HiGoEnum::ADDONS_NAME
  133. ];
  134. if($is_freeze){
  135. $log_res = HigoWalletFreezeLog::setData($setData);
  136. }else{
  137. $log_res = HigoWalletLog::setData($setData);
  138. }
  139. if (!$log_res) {
  140. throw new Exception(CapitalLog::getStaticError());
  141. }
  142. $log_res_id = $log_res->id;
  143. //处理商城嗨呗表
  144. $higo_mall_model = HigoMall::findOne(['mall_id' => $mall_id]);
  145. if (empty($higo_mall_model)) {
  146. $higo_mall_model = HigoMall::setData([
  147. 'mall_id' => $mall_id,
  148. 'status' => StatusEnum::ENABLED
  149. ]);
  150. }
  151. if($wallet_type == HiGoEnum::WALLET_TYPE_HI_BEI){
  152. //操作嗨呗
  153. if($is_send){
  154. $higo_mall_model->$wallet_type = bcadd($higo_mall_model->$wallet_type, $data['change_num'], 10);
  155. //添加嗨呗记录
  156. HigoHibeiLog::setData(['mall_id' => $mall_id,'change_type' => CapitalLog::CHANGE_TYPE_ADD,'change_num'=>$data['change_num'],'from_type' => $data['from_type'],'desc' => $data['desc'],'setting' => $data['setting'] ?? 0]);
  157. }
  158. $higo_mall_model->available_hi_bei = bcadd($higo_mall_model->available_hi_bei, $data['change_num'], 10);
  159. }else{
  160. $higo_mall_model->$wallet_type = bcadd($higo_mall_model->$wallet_type, $data['change_num'], 10);
  161. }
  162. if (!$higo_mall_model->save()) throw new Exception($higo_mall_model->getErrorMessage());
  163. $t->commit();
  164. //unlock
  165. LockHelper::uLock($lock_key, $identification);
  166. $task_id && self::setHandleResult($task_id, $log_res_id);
  167. return true;
  168. } catch (Exception $e) {
  169. Yii::error(__CLASS__ . ' handle exception : ' . $e->getMessage() . ',task_id:' . $task_id);
  170. $t->rollBack();
  171. LockHelper::uLock($lock_key, $identification);
  172. self::$static_error = $e->getMessage();
  173. return false;
  174. }
  175. } else {
  176. //抢不到锁
  177. Yii::error(__CLASS__ . ' handle error : 抢不到锁,task_id:' . $task_id);
  178. self::$static_error = '操作太频繁';
  179. return false;
  180. }
  181. }
  182. /**
  183. * 获取处理结果
  184. * @param $task_id
  185. * @return array|bool|null|string
  186. * @throws \yii\db\Exception
  187. */
  188. public static function getHandleResult($task_id)
  189. {
  190. $result_key = HiGoEnum::USER_WALLET_RESUALT_KEY_PREFIX . $task_id;
  191. $redis = \Yii::$app->redis;
  192. return $redis->executeCommand('GET', [$result_key]);
  193. }
  194. /**
  195. * 获取处理结果
  196. * @param $task_id
  197. * @param $result
  198. * @return array|bool|null|string
  199. * @throws \yii\db\Exception
  200. */
  201. public static function setHandleResult($task_id, $result)
  202. {
  203. $result_key = HiGoEnum::USER_WALLET_RESUALT_KEY_PREFIX . $task_id;
  204. $redis = \Yii::$app->redis;
  205. return $redis->executeCommand('SET', [$result_key, $result, 'EX', 86400]);
  206. }
  207. /**
  208. * 异步队列操作用户钱包
  209. * @param array $data
  210. * @param bool $wait_result 是否阻塞,等待结果
  211. * @throws
  212. * @return bool
  213. */
  214. public static function handleInQueue($data, $wait_result = true)
  215. {
  216. if (!in_array($data['wallet_type'], self::getWalletType())) {
  217. throw new Exception('操作类型不合法');
  218. }
  219. // $res = self::handle($data,123);
  220. // $result = self::getHandleResult(123);
  221. // return $result;
  222. $task_id = TaskHelper::pushTaskQueue(
  223. RabbitMqEnum::EXCHANGE_TASK_USER_WALLET,
  224. RabbitMqEnum::USER_WALLET_QUEUE,
  225. $data,
  226. __CLASS__,
  227. 'handle'
  228. );
  229. if (!$task_id) {
  230. throw new Exception('操作失败');
  231. }
  232. $result = true;
  233. //block for result
  234. if ($wait_result) {
  235. $hasWait = 0; //已经等待的时间:毫秒
  236. $max_wait_time = 3 * 1000; //最长等待 3s
  237. while (true) {
  238. $result = self::getHandleResult($task_id);
  239. if ($result) {
  240. break;
  241. }
  242. usleep(30 * 1000); //等待30ms
  243. $hasWait += 30;
  244. if ($hasWait > $max_wait_time) {
  245. //等待超时
  246. $result = false;
  247. break;
  248. }
  249. }
  250. }
  251. //注意:返回false 表示正在等待处理
  252. return $result;
  253. }
  254. /**
  255. * 处理 未处理记录
  256. * @return void
  257. * @throws
  258. */
  259. public static function unfrozen($frozen_log)
  260. {
  261. if($frozen_log['status'] != CapitalLog::STATUS_WAIT_SEND) return ;
  262. $res = self::handleInQueue($frozen_log);
  263. if($res){
  264. HigoWalletFrozenLog::updateAll(['status' => CapitalLog::STATUS_SEND],['id' => $frozen_log['id']]);
  265. }
  266. return true;
  267. }
  268. /**
  269. * 删除未处理记录
  270. * @param $frozen_log
  271. * @return void
  272. */
  273. public static function delfrozen($frozen_log)
  274. {
  275. if($frozen_log['status'] != CapitalLog::STATUS_WAIT_SEND) return ;
  276. HigoWalletFrozenLog::updateAll(['status' => CapitalLog::STATUS_FAIL],['id' => $frozen_log['id']]);
  277. return true;
  278. }
  279. }