MexMatchLogic.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. <?php
  2. namespace App\Module\Mex\Logic;
  3. use App\Module\Mex\Models\MexOrder;
  4. use App\Module\Mex\Models\MexWarehouse;
  5. use App\Module\Mex\Models\MexTransaction;
  6. use App\Module\Mex\Models\MexPriceConfig;
  7. use App\Module\Mex\Enums\OrderType;
  8. use App\Module\Mex\Enums\OrderStatus;
  9. use App\Module\Mex\Enums\TransactionType;
  10. use App\Module\Fund\Services\FundService;
  11. use App\Module\Fund\Enums\FUND_TYPE;
  12. use App\Module\GameItems\Services\ItemService;
  13. use App\Module\GameItems\Enums\FREEZE_REASON_TYPE;
  14. use Illuminate\Support\Facades\DB;
  15. use Illuminate\Support\Facades\Log;
  16. /**
  17. * 农贸市场撮合逻辑
  18. *
  19. * 处理撮合相关的核心业务逻辑
  20. * 根据文档要求分离用户买入物品和用户卖出物品的撮合逻辑
  21. */
  22. class MexMatchLogic
  23. {
  24. /**
  25. * 仓库账户ID
  26. */
  27. private const WAREHOUSE_USER_ID = 15;
  28. /**
  29. * 调控账户ID
  30. */
  31. private const CONTROL_USER_ID = 16;
  32. /**
  33. * 执行用户买入物品撮合任务
  34. *
  35. * @param int|null $itemId 指定商品ID,null表示处理所有商品
  36. * @param int $batchSize 批处理大小
  37. * @return array 撮合结果
  38. */
  39. public static function executeUserBuyItemMatch(?int $itemId = null, int $batchSize = 100): array
  40. {
  41. $startTime = microtime(true);
  42. $totalMatched = 0;
  43. $totalAmount = '0.00000';
  44. $processedItems = [];
  45. $errors = [];
  46. try {
  47. if ($itemId) {
  48. // 处理指定商品
  49. $result = self::executeUserBuyItemMatchForItem($itemId, $batchSize);
  50. $processedItems[] = $itemId;
  51. $totalMatched += $result['matched_orders'];
  52. $totalAmount = bcadd($totalAmount, $result['total_amount'], 5);
  53. if (!$result['success']) {
  54. $errors[] = "商品 {$itemId}: " . $result['message'];
  55. }
  56. } else {
  57. // 处理所有有待撮合的用户买入物品订单的商品
  58. $itemIds = MexOrder::where('order_type', OrderType::BUY)
  59. ->where('status', OrderStatus::PENDING)
  60. ->distinct()
  61. ->pluck('item_id')
  62. ->toArray();
  63. foreach ($itemIds as $currentItemId) {
  64. $result = self::executeUserBuyItemMatchForItem($currentItemId, $batchSize);
  65. $processedItems[] = $currentItemId;
  66. $totalMatched += $result['matched_orders'];
  67. $totalAmount = bcadd($totalAmount, $result['total_amount'], 5);
  68. if (!$result['success']) {
  69. $errors[] = "商品 {$currentItemId}: " . $result['message'];
  70. }
  71. }
  72. }
  73. $endTime = microtime(true);
  74. $executionTime = round(($endTime - $startTime) * 1000, 2); // 毫秒
  75. Log::info('Mex用户买入物品撮合任务执行完成', [
  76. 'processed_items' => $processedItems,
  77. 'total_matched' => $totalMatched,
  78. 'total_amount' => $totalAmount,
  79. 'execution_time_ms' => $executionTime,
  80. 'errors' => $errors,
  81. ]);
  82. return [
  83. 'success' => true,
  84. 'message' => '用户买入物品撮合任务执行完成',
  85. 'processed_items' => $processedItems,
  86. 'total_matched' => $totalMatched,
  87. 'total_amount' => $totalAmount,
  88. 'execution_time_ms' => $executionTime,
  89. 'errors' => $errors,
  90. ];
  91. } catch (\Exception $e) {
  92. Log::error('Mex用户买入物品撮合任务执行失败', [
  93. 'error' => $e->getMessage(),
  94. 'trace' => $e->getTraceAsString(),
  95. ]);
  96. return [
  97. 'success' => false,
  98. 'message' => '用户买入物品撮合任务执行失败:' . $e->getMessage(),
  99. 'processed_items' => $processedItems,
  100. 'total_matched' => $totalMatched,
  101. 'total_amount' => $totalAmount,
  102. ];
  103. }
  104. }
  105. /**
  106. * 执行用户卖出物品撮合任务
  107. *
  108. * @param int|null $itemId 指定商品ID,null表示处理所有商品
  109. * @param int $batchSize 批处理大小
  110. * @return array 撮合结果
  111. */
  112. public static function executeUserSellItemMatch(?int $itemId = null, int $batchSize = 100): array
  113. {
  114. $startTime = microtime(true);
  115. $totalMatched = 0;
  116. $totalAmount = '0.00000';
  117. $processedItems = [];
  118. $errors = [];
  119. try {
  120. if ($itemId) {
  121. // 处理指定商品
  122. $result = self::executeUserSellItemMatchForItem($itemId, $batchSize);
  123. $processedItems[] = $itemId;
  124. $totalMatched += $result['matched_orders'];
  125. $totalAmount = bcadd($totalAmount, $result['total_amount'], 5);
  126. if (!$result['success']) {
  127. $errors[] = "商品 {$itemId}: " . $result['message'];
  128. }
  129. } else {
  130. // 处理所有有待撮合的用户卖出物品订单的商品
  131. $itemIds = MexOrder::where('order_type', OrderType::SELL)
  132. ->where('status', OrderStatus::PENDING)
  133. ->distinct()
  134. ->pluck('item_id')
  135. ->toArray();
  136. foreach ($itemIds as $currentItemId) {
  137. $result = self::executeUserSellItemMatchForItem($currentItemId, $batchSize);
  138. $processedItems[] = $currentItemId;
  139. $totalMatched += $result['matched_orders'];
  140. $totalAmount = bcadd($totalAmount, $result['total_amount'], 5);
  141. if (!$result['success']) {
  142. $errors[] = "商品 {$currentItemId}: " . $result['message'];
  143. }
  144. }
  145. }
  146. $endTime = microtime(true);
  147. $executionTime = round(($endTime - $startTime) * 1000, 2); // 毫秒
  148. Log::info('Mex用户卖出物品撮合任务执行完成', [
  149. 'processed_items' => $processedItems,
  150. 'total_matched' => $totalMatched,
  151. 'total_amount' => $totalAmount,
  152. 'execution_time_ms' => $executionTime,
  153. 'errors' => $errors,
  154. ]);
  155. return [
  156. 'success' => true,
  157. 'message' => '用户卖出物品撮合任务执行完成',
  158. 'processed_items' => $processedItems,
  159. 'total_matched' => $totalMatched,
  160. 'total_amount' => $totalAmount,
  161. 'execution_time_ms' => $executionTime,
  162. 'errors' => $errors,
  163. ];
  164. } catch (\Exception $e) {
  165. Log::error('Mex用户卖出物品撮合任务执行失败', [
  166. 'error' => $e->getMessage(),
  167. 'trace' => $e->getTraceAsString(),
  168. ]);
  169. return [
  170. 'success' => false,
  171. 'message' => '用户卖出物品撮合任务执行失败:' . $e->getMessage(),
  172. 'processed_items' => $processedItems,
  173. 'total_matched' => $totalMatched,
  174. 'total_amount' => $totalAmount,
  175. ];
  176. }
  177. }
  178. /**
  179. * 执行单个商品的用户买入物品撮合
  180. *
  181. * @param int $itemId 商品ID
  182. * @param int $batchSize 批处理大小
  183. * @return array 撮合结果
  184. */
  185. public static function executeUserBuyItemMatchForItem(int $itemId, int $batchSize = 100): array
  186. {
  187. try {
  188. return DB::transaction(function () use ($itemId, $batchSize) {
  189. // 检查用户买入物品撮合条件
  190. $conditionCheck = self::checkUserBuyItemMatchConditions($itemId);
  191. if (!$conditionCheck['can_match']) {
  192. return [
  193. 'success' => false,
  194. 'message' => $conditionCheck['message'],
  195. 'matched_orders' => 0,
  196. 'total_amount' => '0.00000',
  197. ];
  198. }
  199. $warehouse = $conditionCheck['warehouse'];
  200. $priceConfig = $conditionCheck['price_config'];
  201. // 获取待撮合的用户买入物品订单(MySQL查询时完成筛选和二级排序)
  202. $buyOrders = MexOrder::where('item_id', $itemId)
  203. ->where('order_type', OrderType::BUY)
  204. ->where('status', OrderStatus::PENDING)
  205. ->where('price', '>=', $priceConfig->max_price) // 价格验证:价格≥最高价
  206. ->where('quantity', '<=', $priceConfig->protection_threshold) // 数量保护:数量≤保护阈值
  207. ->orderBy('price', 'desc') // 价格优先(高价优先)
  208. ->orderBy('created_at', 'asc') // 时间优先(早下单优先)
  209. ->limit($batchSize)
  210. ->get();
  211. if ($buyOrders->isEmpty()) {
  212. return [
  213. 'success' => true,
  214. 'message' => '没有符合条件的用户买入物品订单',
  215. 'matched_orders' => 0,
  216. 'total_amount' => '0.00000',
  217. ];
  218. }
  219. $matchedOrders = 0;
  220. $totalAmount = '0.00000';
  221. $currentStock = $warehouse->quantity;
  222. foreach ($buyOrders as $order) {
  223. // 检查库存是否充足(整单匹配原则)
  224. if ($currentStock < $order->quantity) {
  225. // 库存不足时结束本次撮合处理,避免无效循环
  226. break;
  227. }
  228. // 执行用户买入物品订单撮合
  229. $matchResult = self::executeUserBuyItemOrderMatch($order, $warehouse);
  230. if ($matchResult['success']) {
  231. $matchedOrders++;
  232. $totalAmount = bcadd($totalAmount, $matchResult['total_amount'], 5);
  233. $currentStock -= $order->quantity;
  234. // 更新仓库对象的库存(用于后续订单判断)
  235. $warehouse->quantity = $currentStock;
  236. }
  237. }
  238. return [
  239. 'success' => true,
  240. 'message' => "成功撮合 {$matchedOrders} 个用户买入物品订单",
  241. 'matched_orders' => $matchedOrders,
  242. 'total_amount' => $totalAmount,
  243. ];
  244. });
  245. } catch (\Exception $e) {
  246. return [
  247. 'success' => false,
  248. 'message' => '用户买入物品撮合执行失败:' . $e->getMessage(),
  249. 'matched_orders' => 0,
  250. 'total_amount' => '0.00000',
  251. ];
  252. }
  253. }
  254. /**
  255. * 执行单个商品的用户卖出物品撮合
  256. *
  257. * @param int $itemId 商品ID
  258. * @param int $batchSize 批处理大小
  259. * @return array 撮合结果
  260. */
  261. public static function executeUserSellItemMatchForItem(int $itemId, int $batchSize = 100): array
  262. {
  263. try {
  264. return DB::transaction(function () use ($itemId, $batchSize) {
  265. // 检查用户卖出物品撮合条件
  266. $conditionCheck = self::checkUserSellItemMatchConditions($itemId);
  267. if (!$conditionCheck['can_match']) {
  268. return [
  269. 'success' => false,
  270. 'message' => $conditionCheck['message'],
  271. 'matched_orders' => 0,
  272. 'total_amount' => '0.00000',
  273. ];
  274. }
  275. $priceConfig = $conditionCheck['price_config'];
  276. // 获取待撮合的用户卖出物品订单
  277. $sellOrders = MexOrder::where('item_id', $itemId)
  278. ->where('order_type', OrderType::SELL)
  279. ->where('status', OrderStatus::PENDING)
  280. ->limit($batchSize)
  281. ->get();
  282. if ($sellOrders->isEmpty()) {
  283. return [
  284. 'success' => true,
  285. 'message' => '没有待撮合的用户卖出物品订单',
  286. 'matched_orders' => 0,
  287. 'total_amount' => '0.00000',
  288. ];
  289. }
  290. $matchedOrders = 0;
  291. $totalAmount = '0.00000';
  292. foreach ($sellOrders as $order) {
  293. // 价格验证:用户卖出物品价格≤最低价
  294. if (bccomp($order->price, $priceConfig->min_price, 5) > 0) {
  295. continue; // 价格不符合条件,跳过此订单
  296. }
  297. // 执行用户卖出物品订单撮合
  298. $matchResult = self::executeUserSellItemOrderMatch($order);
  299. if ($matchResult['success']) {
  300. $matchedOrders++;
  301. $totalAmount = bcadd($totalAmount, $matchResult['total_amount'], 5);
  302. }
  303. }
  304. return [
  305. 'success' => true,
  306. 'message' => "成功撮合 {$matchedOrders} 个用户卖出物品订单",
  307. 'matched_orders' => $matchedOrders,
  308. 'total_amount' => $totalAmount,
  309. ];
  310. });
  311. } catch (\Exception $e) {
  312. return [
  313. 'success' => false,
  314. 'message' => '用户卖出物品撮合执行失败:' . $e->getMessage(),
  315. 'matched_orders' => 0,
  316. 'total_amount' => '0.00000',
  317. ];
  318. }
  319. }
  320. /**
  321. * 执行单个用户买入物品订单的撮合
  322. *
  323. * @param MexOrder $order 用户买入物品订单
  324. * @param MexWarehouse $warehouse 仓库信息
  325. * @return array 撮合结果
  326. */
  327. private static function executeUserBuyItemOrderMatch(MexOrder $order, MexWarehouse $warehouse): array
  328. {
  329. try {
  330. // 计算成交金额
  331. $totalAmount = bcmul($order->price, $order->quantity, 5);
  332. // 更新订单状态
  333. $order->update([
  334. 'status' => OrderStatus::COMPLETED,
  335. 'completed_quantity' => $order->quantity,
  336. 'completed_amount' => $totalAmount,
  337. 'completed_at' => now(),
  338. ]);
  339. // 更新仓库库存
  340. $warehouse->quantity -= $order->quantity;
  341. $warehouse->total_sell_quantity += $order->quantity;
  342. $warehouse->total_sell_amount = bcadd($warehouse->total_sell_amount, $totalAmount, 5);
  343. $warehouse->last_transaction_at = now();
  344. $warehouse->save();
  345. // 创建成交记录
  346. $transaction = MexTransaction::create([
  347. 'buy_order_id' => $order->id,
  348. 'sell_order_id' => null,
  349. 'buyer_id' => $order->user_id,
  350. 'seller_id' => self::WAREHOUSE_USER_ID, // 仓库账户作为卖方
  351. 'item_id' => $order->item_id,
  352. 'quantity' => $order->quantity,
  353. 'price' => $order->price,
  354. 'total_amount' => $totalAmount,
  355. 'transaction_type' => TransactionType::USER_BUY,
  356. 'is_admin_operation' => false,
  357. ]);
  358. // 执行账户流转逻辑
  359. // 1. 用户冻结资金转入仓库账户
  360. $fundResult = self::transferFrozenFundsToWarehouse($order->user_id, $totalAmount, $order->id);
  361. if (!$fundResult['success']) {
  362. throw new \Exception('资金流转失败:' . $fundResult['message']);
  363. }
  364. // 2. 仓库账户物品转出到用户账户
  365. $itemResult = self::transferItemsFromWarehouseToUser($order->user_id, $order->item_id, $order->quantity, $order->id);
  366. if (!$itemResult['success']) {
  367. throw new \Exception('物品流转失败:' . $itemResult['message']);
  368. }
  369. return [
  370. 'success' => true,
  371. 'message' => '订单撮合成功',
  372. 'order_id' => $order->id,
  373. 'transaction_id' => $transaction->id,
  374. 'total_amount' => $totalAmount,
  375. ];
  376. } catch (\Exception $e) {
  377. return [
  378. 'success' => false,
  379. 'message' => '订单撮合失败:' . $e->getMessage(),
  380. 'order_id' => $order->id,
  381. 'total_amount' => '0.00000',
  382. ];
  383. }
  384. }
  385. /**
  386. * 执行单个用户卖出物品订单的撮合
  387. *
  388. * @param MexOrder $order 用户卖出物品订单
  389. * @return array 撮合结果
  390. */
  391. private static function executeUserSellItemOrderMatch(MexOrder $order): array
  392. {
  393. try {
  394. // 计算成交金额
  395. $totalAmount = bcmul($order->price, $order->quantity, 5);
  396. // 更新订单状态
  397. $order->update([
  398. 'status' => OrderStatus::COMPLETED,
  399. 'completed_quantity' => $order->quantity,
  400. 'completed_amount' => $totalAmount,
  401. 'completed_at' => now(),
  402. ]);
  403. // 更新仓库库存
  404. $warehouse = MexWarehouse::where('item_id', $order->item_id)->first();
  405. if (!$warehouse) {
  406. // 如果仓库记录不存在,创建新记录
  407. $warehouse = MexWarehouse::create([
  408. 'item_id' => $order->item_id,
  409. 'quantity' => $order->quantity,
  410. 'total_buy_amount' => $totalAmount,
  411. 'total_buy_quantity' => $order->quantity,
  412. 'last_transaction_at' => now(),
  413. ]);
  414. } else {
  415. // 更新现有仓库记录
  416. $warehouse->quantity += $order->quantity;
  417. $warehouse->total_buy_quantity += $order->quantity;
  418. $warehouse->total_buy_amount = bcadd($warehouse->total_buy_amount, $totalAmount, 5);
  419. $warehouse->last_transaction_at = now();
  420. $warehouse->save();
  421. }
  422. // 创建成交记录
  423. $transaction = MexTransaction::create([
  424. 'buy_order_id' => null,
  425. 'sell_order_id' => $order->id,
  426. 'buyer_id' => self::WAREHOUSE_USER_ID, // 仓库账户作为买方
  427. 'seller_id' => $order->user_id,
  428. 'item_id' => $order->item_id,
  429. 'quantity' => $order->quantity,
  430. 'price' => $order->price,
  431. 'total_amount' => $totalAmount,
  432. 'transaction_type' => TransactionType::USER_SELL,
  433. 'is_admin_operation' => false,
  434. ]);
  435. // 执行账户流转逻辑
  436. // 1. 用户冻结物品转入仓库账户
  437. $itemResult = self::transferFrozenItemsToWarehouse($order->user_id, $order->item_id, $order->quantity, $order->id);
  438. if (!$itemResult['success']) {
  439. throw new \Exception('物品流转失败:' . $itemResult['message']);
  440. }
  441. // 2. 仓库账户资金转出到用户账户
  442. $fundResult = self::transferFundsFromWarehouseToUser($order->user_id, $totalAmount, $order->id);
  443. if (!$fundResult['success']) {
  444. throw new \Exception('资金流转失败:' . $fundResult['message']);
  445. }
  446. return [
  447. 'success' => true,
  448. 'message' => '用户卖出物品订单撮合成功',
  449. 'order_id' => $order->id,
  450. 'transaction_id' => $transaction->id,
  451. 'total_amount' => $totalAmount,
  452. ];
  453. } catch (\Exception $e) {
  454. return [
  455. 'success' => false,
  456. 'message' => '用户卖出物品订单撮合失败:' . $e->getMessage(),
  457. 'order_id' => $order->id,
  458. 'total_amount' => '0.00000',
  459. ];
  460. }
  461. }
  462. /**
  463. * 检查用户买入物品撮合条件
  464. *
  465. * @param int $itemId 商品ID
  466. * @return array 检查结果
  467. */
  468. public static function checkUserBuyItemMatchConditions(int $itemId): array
  469. {
  470. // 检查价格配置
  471. $priceConfig = MexPriceConfig::where('item_id', $itemId)->where('is_enabled', true)->first();
  472. if (!$priceConfig) {
  473. return [
  474. 'can_match' => false,
  475. 'message' => '商品未配置价格信息或已禁用',
  476. ];
  477. }
  478. // 检查仓库库存
  479. $warehouse = MexWarehouse::where('item_id', $itemId)->first();
  480. if (!$warehouse || $warehouse->quantity <= 0) {
  481. return [
  482. 'can_match' => false,
  483. 'message' => '仓库库存不足',
  484. ];
  485. }
  486. // 检查是否有符合条件的待撮合用户买入物品订单
  487. $pendingBuyOrders = MexOrder::where('item_id', $itemId)
  488. ->where('order_type', OrderType::BUY)
  489. ->where('status', OrderStatus::PENDING)
  490. ->where('price', '>=', $priceConfig->max_price) // 价格≥最高价
  491. ->where('quantity', '<=', $priceConfig->protection_threshold) // 数量≤保护阈值
  492. ->count();
  493. if ($pendingBuyOrders === 0) {
  494. return [
  495. 'can_match' => false,
  496. 'message' => '没有符合条件的待撮合用户买入物品订单',
  497. ];
  498. }
  499. return [
  500. 'can_match' => true,
  501. 'message' => '用户买入物品撮合条件满足',
  502. 'warehouse' => $warehouse,
  503. 'price_config' => $priceConfig,
  504. 'pending_orders' => $pendingBuyOrders,
  505. ];
  506. }
  507. /**
  508. * 检查用户卖出物品撮合条件
  509. *
  510. * @param int $itemId 商品ID
  511. * @return array 检查结果
  512. */
  513. public static function checkUserSellItemMatchConditions(int $itemId): array
  514. {
  515. // 检查价格配置
  516. $priceConfig = MexPriceConfig::where('item_id', $itemId)->where('is_enabled', true)->first();
  517. if (!$priceConfig) {
  518. return [
  519. 'can_match' => false,
  520. 'message' => '商品未配置价格信息或已禁用',
  521. ];
  522. }
  523. // 检查是否有待撮合的用户卖出物品订单
  524. $pendingSellOrders = MexOrder::where('item_id', $itemId)
  525. ->where('order_type', OrderType::SELL)
  526. ->where('status', OrderStatus::PENDING)
  527. ->count();
  528. if ($pendingSellOrders === 0) {
  529. return [
  530. 'can_match' => false,
  531. 'message' => '没有待撮合的用户卖出物品订单',
  532. ];
  533. }
  534. return [
  535. 'can_match' => true,
  536. 'message' => '用户卖出物品撮合条件满足',
  537. 'price_config' => $priceConfig,
  538. 'pending_orders' => $pendingSellOrders,
  539. ];
  540. }
  541. /**
  542. * 获取用户买入物品撮合统计信息
  543. *
  544. * @return array 统计信息
  545. */
  546. public static function getUserBuyItemMatchStats(): array
  547. {
  548. // 获取待撮合用户买入物品订单统计
  549. $pendingStats = MexOrder::where('order_type', OrderType::BUY)
  550. ->where('status', OrderStatus::PENDING)
  551. ->selectRaw('
  552. COUNT(*) as total_pending,
  553. COUNT(DISTINCT item_id) as pending_items,
  554. SUM(quantity) as total_quantity,
  555. SUM(total_amount) as total_amount
  556. ')
  557. ->first();
  558. // 获取今日用户买入物品撮合统计
  559. $todayStats = MexTransaction::where('transaction_type', TransactionType::USER_BUY)
  560. ->whereDate('created_at', today())
  561. ->selectRaw('
  562. COUNT(*) as today_matched,
  563. SUM(quantity) as today_quantity,
  564. SUM(total_amount) as today_amount
  565. ')
  566. ->first();
  567. // 获取有库存的商品数量
  568. $availableItems = MexWarehouse::where('quantity', '>', 0)->count();
  569. return [
  570. 'pending_orders' => $pendingStats->total_pending ?? 0,
  571. 'pending_items' => $pendingStats->pending_items ?? 0,
  572. 'pending_quantity' => $pendingStats->total_quantity ?? 0,
  573. 'pending_amount' => $pendingStats->total_amount ?? '0.00000',
  574. 'today_matched' => $todayStats->today_matched ?? 0,
  575. 'today_quantity' => $todayStats->today_quantity ?? 0,
  576. 'today_amount' => $todayStats->today_amount ?? '0.00000',
  577. 'available_items' => $availableItems,
  578. 'stats_time' => now(),
  579. ];
  580. }
  581. /**
  582. * 获取用户卖出物品撮合统计信息
  583. *
  584. * @return array 统计信息
  585. */
  586. public static function getUserSellItemMatchStats(): array
  587. {
  588. // 获取待撮合用户卖出物品订单统计
  589. $pendingStats = MexOrder::where('order_type', OrderType::SELL)
  590. ->where('status', OrderStatus::PENDING)
  591. ->selectRaw('
  592. COUNT(*) as total_pending,
  593. COUNT(DISTINCT item_id) as pending_items,
  594. SUM(quantity) as total_quantity,
  595. SUM(total_amount) as total_amount
  596. ')
  597. ->first();
  598. // 获取今日用户卖出物品撮合统计
  599. $todayStats = MexTransaction::where('transaction_type', TransactionType::USER_SELL)
  600. ->whereDate('created_at', today())
  601. ->selectRaw('
  602. COUNT(*) as today_matched,
  603. SUM(quantity) as today_quantity,
  604. SUM(total_amount) as today_amount
  605. ')
  606. ->first();
  607. return [
  608. 'pending_orders' => $pendingStats->total_pending ?? 0,
  609. 'pending_items' => $pendingStats->pending_items ?? 0,
  610. 'pending_quantity' => $pendingStats->total_quantity ?? 0,
  611. 'pending_amount' => $pendingStats->total_amount ?? '0.00000',
  612. 'today_matched' => $todayStats->today_matched ?? 0,
  613. 'today_quantity' => $todayStats->today_quantity ?? 0,
  614. 'today_amount' => $todayStats->today_amount ?? '0.00000',
  615. 'stats_time' => now(),
  616. ];
  617. }
  618. // 保留旧方法以兼容现有代码
  619. /**
  620. * 获取撮合统计信息(已废弃,请使用getUserBuyItemMatchStats)
  621. *
  622. * @deprecated 请使用getUserBuyItemMatchStats或getUserSellItemMatchStats
  623. * @return array 统计信息
  624. */
  625. public static function getMatchStats(): array
  626. {
  627. return self::getUserBuyItemMatchStats();
  628. }
  629. /**
  630. * 检查撮合条件(已废弃,请使用checkUserBuyItemMatchConditions)
  631. *
  632. * @deprecated 请使用checkUserBuyItemMatchConditions或checkUserSellItemMatchConditions
  633. * @param int $itemId 商品ID
  634. * @return array 检查结果
  635. */
  636. public static function checkMatchConditions(int $itemId): array
  637. {
  638. return self::checkUserBuyItemMatchConditions($itemId);
  639. }
  640. /**
  641. * 执行撮合任务(已废弃,请使用executeUserBuyItemMatch)
  642. *
  643. * @deprecated 请使用executeUserBuyItemMatch或executeUserSellItemMatch
  644. * @param int|null $itemId 指定商品ID,null表示处理所有商品
  645. * @param int $batchSize 批处理大小
  646. * @return array 撮合结果
  647. */
  648. public static function executeMatch(?int $itemId = null, int $batchSize = 100): array
  649. {
  650. return self::executeUserBuyItemMatch($itemId, $batchSize);
  651. }
  652. /**
  653. * 执行单个商品的撮合(已废弃,请使用executeUserBuyItemMatchForItem)
  654. *
  655. * @deprecated 请使用executeUserBuyItemMatchForItem或executeUserSellItemMatchForItem
  656. * @param int $itemId 商品ID
  657. * @param int $batchSize 批处理大小
  658. * @return array 撮合结果
  659. */
  660. public static function executeItemMatch(int $itemId, int $batchSize = 100): array
  661. {
  662. return self::executeUserBuyItemMatchForItem($itemId, $batchSize);
  663. }
  664. /**
  665. * 将用户冻结资金转入仓库账户
  666. *
  667. * @param int $userId 用户ID
  668. * @param string $amount 金额
  669. * @param int $orderId 订单ID
  670. * @return array 转移结果
  671. */
  672. private static function transferFrozenFundsToWarehouse(int $userId, string $amount, int $orderId): array
  673. {
  674. try {
  675. // TODO: 这里需要根据实际的资金类型进行调整
  676. // 假设使用钻石币种(FUND_TYPE::FUND2)和冻结账户(FUND_TYPE::FUND3)
  677. // 从用户冻结账户转移到仓库账户
  678. $fundService = new FundService($userId, FUND_TYPE::FUND3->value); // 冻结账户
  679. $result = $fundService->trade(
  680. self::WAREHOUSE_USER_ID,
  681. (int)bcmul($amount, '100', 0), // 转换为整数存储
  682. 'MEX_ORDER',
  683. $orderId,
  684. '用户买入物品撮合-资金转移'
  685. );
  686. if (is_string($result)) {
  687. return [
  688. 'success' => false,
  689. 'message' => $result,
  690. ];
  691. }
  692. return [
  693. 'success' => true,
  694. 'message' => '资金转移成功',
  695. 'data' => $result,
  696. ];
  697. } catch (\Exception $e) {
  698. return [
  699. 'success' => false,
  700. 'message' => '资金转移异常:' . $e->getMessage(),
  701. ];
  702. }
  703. }
  704. /**
  705. * 将仓库账户物品转出到用户账户
  706. *
  707. * @param int $userId 用户ID
  708. * @param int $itemId 物品ID
  709. * @param int $quantity 数量
  710. * @param int $orderId 订单ID
  711. * @return array 转移结果
  712. */
  713. private static function transferItemsFromWarehouseToUser(int $userId, int $itemId, int $quantity, int $orderId): array
  714. {
  715. try {
  716. // 添加物品到用户账户
  717. $result = ItemService::addItem($userId, $itemId, $quantity, [
  718. 'source' => 'mex_order',
  719. 'source_id' => $orderId,
  720. 'remark' => '用户买入物品撮合-物品转移',
  721. ]);
  722. if (!$result || !isset($result['success']) || !$result['success']) {
  723. return [
  724. 'success' => false,
  725. 'message' => '物品添加失败:' . ($result['message'] ?? '未知错误'),
  726. ];
  727. }
  728. return [
  729. 'success' => true,
  730. 'message' => '物品转移成功',
  731. 'data' => $result,
  732. ];
  733. } catch (\Exception $e) {
  734. return [
  735. 'success' => false,
  736. 'message' => '物品转移异常:' . $e->getMessage(),
  737. ];
  738. }
  739. }
  740. /**
  741. * 将用户冻结物品转入仓库账户
  742. *
  743. * @param int $userId 用户ID
  744. * @param int $itemId 物品ID
  745. * @param int $quantity 数量
  746. * @param int $orderId 订单ID
  747. * @return array 转移结果
  748. */
  749. private static function transferFrozenItemsToWarehouse(int $userId, int $itemId, int $quantity, int $orderId): array
  750. {
  751. try {
  752. // TODO: 这里需要实现从用户冻结物品转移到仓库的逻辑
  753. // 由于物品冻结功能比较复杂,这里先返回成功,后续完善
  754. // 消耗用户物品(包括冻结的物品)
  755. $result = ItemService::consumeItem($userId, $itemId, null, $quantity, [
  756. 'source' => 'mex_order',
  757. 'source_id' => $orderId,
  758. 'remark' => '用户卖出物品撮合-物品转移',
  759. 'include_frozen' => true, // 包括冻结的物品
  760. ]);
  761. if (!$result || !isset($result['success']) || !$result['success']) {
  762. return [
  763. 'success' => false,
  764. 'message' => '物品消耗失败:' . ($result['message'] ?? '未知错误'),
  765. ];
  766. }
  767. return [
  768. 'success' => true,
  769. 'message' => '物品转移成功',
  770. 'data' => $result,
  771. ];
  772. } catch (\Exception $e) {
  773. return [
  774. 'success' => false,
  775. 'message' => '物品转移异常:' . $e->getMessage(),
  776. ];
  777. }
  778. }
  779. /**
  780. * 将仓库账户资金转出到用户账户
  781. *
  782. * @param int $userId 用户ID
  783. * @param string $amount 金额
  784. * @param int $orderId 订单ID
  785. * @return array 转移结果
  786. */
  787. private static function transferFundsFromWarehouseToUser(int $userId, string $amount, int $orderId): array
  788. {
  789. try {
  790. // TODO: 这里需要根据实际的资金类型进行调整
  791. // 假设使用钻石币种(FUND_TYPE::FUND2)
  792. // 从仓库账户转移到用户账户
  793. $fundService = new FundService(self::WAREHOUSE_USER_ID, FUND_TYPE::FUND2->value); // 仓库账户
  794. $result = $fundService->trade(
  795. $userId,
  796. (int)bcmul($amount, '100', 0), // 转换为整数存储
  797. 'MEX_ORDER',
  798. $orderId,
  799. '用户卖出物品撮合-资金转移'
  800. );
  801. if (is_string($result)) {
  802. return [
  803. 'success' => false,
  804. 'message' => $result,
  805. ];
  806. }
  807. return [
  808. 'success' => true,
  809. 'message' => '资金转移成功',
  810. 'data' => $result,
  811. ];
  812. } catch (\Exception $e) {
  813. return [
  814. 'success' => false,
  815. 'message' => '资金转移异常:' . $e->getMessage(),
  816. ];
  817. }
  818. }
  819. }