From ce6e7529051a66cd5d8db6099a8c3d3229c88805 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Fri, 19 Sep 2025 16:51:03 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=97=E5=8A=9B=E6=89=A3=E8=B4=B9=E5=8F=8A?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TokensRecordController.php | 179 ++++++++++++++++++ .../chukebao/model/TokensRecord.php | 16 ++ 2 files changed, 195 insertions(+) create mode 100644 Server/application/chukebao/controller/TokensRecordController.php create mode 100644 Server/application/chukebao/model/TokensRecord.php diff --git a/Server/application/chukebao/controller/TokensRecordController.php b/Server/application/chukebao/controller/TokensRecordController.php new file mode 100644 index 00000000..deb8882d --- /dev/null +++ b/Server/application/chukebao/controller/TokensRecordController.php @@ -0,0 +1,179 @@ +request->param('page', 1); + $limit = $this->request->param('limit', 10); + $type = $this->request->param('type', ''); + $form = $this->request->param('form', ''); + $userId = $this->getUserInfo('id'); + $companyId = $this->getUserInfo('companyId'); + + + $where = [ + ['companyId','=',$companyId], + ['userId' ,'=', $userId] + ]; + + if ($type != '') { + $where[] = ['type','=',$type]; + } + if ($form != '') { + $where[] = ['form','=',$form]; + } + + + $query = TokensRecord::where($where); + + $list = $query->where($where)->page($page,$limit)->order('id desc')->select(); + $total = $query->count(); + + foreach ($list as &$item) { + if (in_array($item['type'],[1])){ + $nickname = Db::table('s2_wechat_friend')->where(['id' => $item['friendIdOrGroupId']])->value('nickname'); + $item['nickname'] = !empty($nickname) ? $nickname : '-'; + } + if (in_array($item['type'],[2,3])){ + $nickname = Db::table('s2_wechat_chatroom')->where(['id' => $item['friendIdOrGroupId']])->value('nickname'); + $item['nickname'] = !empty($nickname) ? $nickname : '-'; + } + } + unset($item); + + return ResponseHelper::success(['list'=>$list,'total'=>$total]); + } + + + public function consumeTokens($data = []) + { + if (empty($data)){ + return ResponseHelper::error('数据缺失'); + } + + $tokens = isset($data['tokens']) ? intval($data['tokens']) : 0; + $type = isset($data['type']) ? intval($data['type']) : 0; + $form = isset($data['form']) ? intval($data['form']) : 0; + $wechatAccountId = isset($data['wechatAccountId']) ? intval($data['wechatAccountId']) : 0; + $friendIdOrGroupId = isset($data['friendIdOrGroupId']) ? intval($data['friendIdOrGroupId']) : 0; + $remarks = isset($data['remarks']) ? $data['remarks'] : ''; + $companyId = isset($data['companyId']) ? intval($data['companyId']) : $this->getUserInfo('companyId'); + $userId = isset($data['userId']) ? intval($data['userId']) : $this->getUserInfo('id'); + + // 验证必要参数 + if ($tokens <= 0) { + return ResponseHelper::error('tokens数量必须大于0'); + } + + if (!in_array($type, [0, 1])) { + return ResponseHelper::error('类型参数错误,0为减少,1为增加'); + } + + if (!in_array($form, [0, 1, 2, 3, 4, 5])) { + return ResponseHelper::error('来源参数错误'); + } + + // 重试机制,最多重试3次 + $maxRetries = 3; + $retryCount = 0; + while ($retryCount < $maxRetries) { + try { + return $this->doConsumeTokens($userId, $companyId, $tokens, $type, $form, $wechatAccountId, $friendIdOrGroupId, $remarks); + } catch (\Exception $e) { + $retryCount++; + if ($retryCount >= $maxRetries) { + return ResponseHelper::error('操作失败,请稍后重试:' . $e->getMessage()); + } + // 短暂延迟后重试 + usleep(100000); // 100ms + } + } + } + + /** + * 执行tokens消费的核心方法 + */ + private function doConsumeTokens($userId, $companyId, $tokens, $type, $form, $wechatAccountId, $friendIdOrGroupId, $remarks) + { + // 开启数据库事务 + Db::startTrans(); + try { + // 使用悲观锁获取用户当前tokens余额,确保并发安全 + $userInfo = Db::name('users') + ->where('id', $userId) + ->where('companyId', $companyId) + ->lock(true) // 悲观锁,防止并发问题 + ->find(); + + if (!$userInfo) { + throw new \Exception('用户不存在'); + } + + $currentTokens = intval($userInfo['tokens']); + + // 计算新的余额 + $newBalance = $type == 1 ? ($currentTokens + $tokens) : ($currentTokens - $tokens); + + // 使用原子更新操作,基于当前值进行更新,防止并发覆盖 + $updateResult = Db::name('users') + ->where('id', $userId) + ->where('companyId', $companyId) + ->where('tokens', $currentTokens) // 确保基于当前值更新 + ->update([ + 'tokens' => $newBalance, + 'updateTime' => time() + ]); + + if (!$updateResult) { + // 如果更新失败,说明tokens值已被其他事务修改,需要重新获取 + throw new \Exception('tokens余额已被其他操作修改,请重试'); + } + + // 记录tokens变动 + $recordData = [ + 'companyId' => $companyId, + 'userId' => $userId, + 'wechatAccountId' => $wechatAccountId, + 'friendIdOrGroupId' => $friendIdOrGroupId, + 'form' => $form, + 'type' => $type, + 'tokens' => $tokens, + 'balanceTokens' => $newBalance, + 'remarks' => $remarks, + 'createTime' => time() + ]; + + $recordId = Db::name('tokens_record')->insertGetId($recordData); + + if (!$recordId) { + throw new \Exception('记录tokens变动失败'); + } + + // 提交事务 + Db::commit(); + + return ResponseHelper::success([ + 'recordId' => $recordId, + 'oldBalance' => $currentTokens, + 'newBalance' => $newBalance, + 'changeAmount' => $type == 1 ? $tokens : -$tokens + ], 'tokens变动记录成功'); + + } catch (\Exception $e) { + // 回滚事务 + Db::rollback(); + throw $e; // 重新抛出异常,让重试机制处理 + } + } + + + +} \ No newline at end of file diff --git a/Server/application/chukebao/model/TokensRecord.php b/Server/application/chukebao/model/TokensRecord.php new file mode 100644 index 00000000..e61c06fb --- /dev/null +++ b/Server/application/chukebao/model/TokensRecord.php @@ -0,0 +1,16 @@ +