MexMatchLogic.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  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. // 注意:根据文档要求,Logic层不应该开启事务,事务应该在Service层处理
  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. } catch (\Exception $e) {
  245. return [
  246. 'success' => false,
  247. 'message' => '用户买入物品撮合执行失败:' . $e->getMessage(),
  248. 'matched_orders' => 0,
  249. 'total_amount' => '0.00000',
  250. ];
  251. }
  252. }
  253. /**
  254. * 执行单个商品的用户卖出物品撮合
  255. *
  256. * @param int $itemId 商品ID
  257. * @param int $batchSize 批处理大小
  258. * @return array 撮合结果
  259. */
  260. public static function executeUserSellItemMatchForItem(int $itemId, int $batchSize = 100): array
  261. {
  262. try {
  263. // 注意:根据文档要求,Logic层不应该开启事务,事务应该在Service层处理
  264. // 检查用户卖出物品撮合条件
  265. $conditionCheck = self::checkUserSellItemMatchConditions($itemId);
  266. if (!$conditionCheck['can_match']) {
  267. return [
  268. 'success' => false,
  269. 'message' => $conditionCheck['message'],
  270. 'matched_orders' => 0,
  271. 'total_amount' => '0.00000',
  272. ];
  273. }
  274. $priceConfig = $conditionCheck['price_config'];
  275. // 获取待撮合的用户卖出物品订单
  276. $sellOrders = MexOrder::where('item_id', $itemId)
  277. ->where('order_type', OrderType::SELL)
  278. ->where('status', OrderStatus::PENDING)
  279. ->limit($batchSize)
  280. ->get();
  281. if ($sellOrders->isEmpty()) {
  282. return [
  283. 'success' => true,
  284. 'message' => '没有待撮合的用户卖出物品订单',
  285. 'matched_orders' => 0,
  286. 'total_amount' => '0.00000',
  287. ];
  288. }
  289. $matchedOrders = 0;
  290. $totalAmount = '0.00000';
  291. foreach ($sellOrders as $order) {
  292. // 价格验证:用户卖出物品价格≤最低价
  293. if (bccomp($order->price, $priceConfig->min_price, 5) > 0) {
  294. continue; // 价格不符合条件,跳过此订单
  295. }
  296. // 执行用户卖出物品订单撮合
  297. $matchResult = self::executeUserSellItemOrderMatch($order);
  298. if ($matchResult['success']) {
  299. $matchedOrders++;
  300. $totalAmount = bcadd($totalAmount, $matchResult['total_amount'], 5);
  301. }
  302. }
  303. return [
  304. 'success' => true,
  305. 'message' => "成功撮合 {$matchedOrders} 个用户卖出物品订单",
  306. 'matched_orders' => $matchedOrders,
  307. 'total_amount' => $totalAmount,
  308. ];
  309. } catch (\Exception $e) {
  310. return [
  311. 'success' => false,
  312. 'message' => '用户卖出物品撮合执行失败:' . $e->getMessage(),
  313. 'matched_orders' => 0,
  314. 'total_amount' => '0.00000',
  315. ];
  316. }
  317. }
  318. /**
  319. * 执行单个用户买入物品订单的撮合
  320. *
  321. * @param MexOrder $order 用户买入物品订单
  322. * @param MexWarehouse $warehouse 仓库信息
  323. * @return array 撮合结果
  324. */
  325. private static function executeUserBuyItemOrderMatch(MexOrder $order, MexWarehouse $warehouse): array
  326. {
  327. try {
  328. // 计算成交金额
  329. $totalAmount = bcmul($order->price, $order->quantity, 5);
  330. // 更新订单状态
  331. $order->update([
  332. 'status' => OrderStatus::COMPLETED,
  333. 'completed_quantity' => $order->quantity,
  334. 'completed_amount' => $totalAmount,
  335. 'completed_at' => now(),
  336. ]);
  337. // 更新仓库库存
  338. $warehouse->quantity -= $order->quantity;
  339. $warehouse->total_sell_quantity += $order->quantity;
  340. $warehouse->total_sell_amount = bcadd($warehouse->total_sell_amount, $totalAmount, 5);
  341. $warehouse->last_transaction_at = now();
  342. $warehouse->save();
  343. // 创建成交记录
  344. $transaction = MexTransaction::create([
  345. 'buy_order_id' => $order->id,
  346. 'sell_order_id' => null,
  347. 'buyer_id' => $order->user_id,
  348. 'seller_id' => self::WAREHOUSE_USER_ID, // 仓库账户作为卖方
  349. 'item_id' => $order->item_id,
  350. 'quantity' => $order->quantity,
  351. 'price' => $order->price,
  352. 'total_amount' => $totalAmount,
  353. 'transaction_type' => TransactionType::USER_BUY,
  354. 'is_admin_operation' => false,
  355. ]);
  356. // 执行账户流转逻辑
  357. // 1. 用户冻结资金转入仓库账户
  358. $fundResult = self::transferFrozenFundsToWarehouse($order->user_id, $totalAmount, $order->id);
  359. if (!$fundResult['success']) {
  360. throw new \Exception('资金流转失败:' . $fundResult['message']);
  361. }
  362. // 2. 仓库账户物品转出到用户账户
  363. $itemResult = self::transferItemsFromWarehouseToUser($order->user_id, $order->item_id, $order->quantity, $order->id);
  364. if (!$itemResult['success']) {
  365. throw new \Exception('物品流转失败:' . $itemResult['message']);
  366. }
  367. return [
  368. 'success' => true,
  369. 'message' => '订单撮合成功',
  370. 'order_id' => $order->id,
  371. 'transaction_id' => $transaction->id,
  372. 'total_amount' => $totalAmount,
  373. ];
  374. } catch (\Exception $e) {
  375. return [
  376. 'success' => false,
  377. 'message' => '订单撮合失败:' . $e->getMessage(),
  378. 'order_id' => $order->id,
  379. 'total_amount' => '0.00000',
  380. ];
  381. }
  382. }
  383. /**
  384. * 执行单个用户卖出物品订单的撮合
  385. *
  386. * @param MexOrder $order 用户卖出物品订单
  387. * @return array 撮合结果
  388. */
  389. private static function executeUserSellItemOrderMatch(MexOrder $order): array
  390. {
  391. try {
  392. // 计算成交金额
  393. $totalAmount = bcmul($order->price, $order->quantity, 5);
  394. // 更新订单状态
  395. $order->update([
  396. 'status' => OrderStatus::COMPLETED,
  397. 'completed_quantity' => $order->quantity,
  398. 'completed_amount' => $totalAmount,
  399. 'completed_at' => now(),
  400. ]);
  401. // 更新仓库库存
  402. $warehouse = MexWarehouse::where('item_id', $order->item_id)->first();
  403. if (!$warehouse) {
  404. // 如果仓库记录不存在,创建新记录
  405. $warehouse = MexWarehouse::create([
  406. 'item_id' => $order->item_id,
  407. 'quantity' => $order->quantity,
  408. 'total_buy_amount' => $totalAmount,
  409. 'total_buy_quantity' => $order->quantity,
  410. 'last_transaction_at' => now(),
  411. ]);
  412. } else {
  413. // 更新现有仓库记录
  414. $warehouse->quantity += $order->quantity;
  415. $warehouse->total_buy_quantity += $order->quantity;
  416. $warehouse->total_buy_amount = bcadd($warehouse->total_buy_amount, $totalAmount, 5);
  417. $warehouse->last_transaction_at = now();
  418. $warehouse->save();
  419. }
  420. // 创建成交记录
  421. $transaction = MexTransaction::create([
  422. 'buy_order_id' => null,
  423. 'sell_order_id' => $order->id,
  424. 'buyer_id' => self::WAREHOUSE_USER_ID, // 仓库账户作为买方
  425. 'seller_id' => $order->user_id,
  426. 'item_id' => $order->item_id,
  427. 'quantity' => $order->quantity,
  428. 'price' => $order->price,
  429. 'total_amount' => $totalAmount,
  430. 'transaction_type' => TransactionType::USER_SELL,
  431. 'is_admin_operation' => false,
  432. ]);
  433. // 执行账户流转逻辑
  434. // 1. 用户冻结物品转入仓库账户
  435. $itemResult = self::transferFrozenItemsToWarehouse($order->user_id, $order->item_id, $order->quantity, $order->id);
  436. if (!$itemResult['success']) {
  437. throw new \Exception('物品流转失败:' . $itemResult['message']);
  438. }
  439. // 2. 仓库账户资金转出到用户账户
  440. $fundResult = self::transferFundsFromWarehouseToUser($order->user_id, $totalAmount, $order->id);
  441. if (!$fundResult['success']) {
  442. throw new \Exception('资金流转失败:' . $fundResult['message']);
  443. }
  444. return [
  445. 'success' => true,
  446. 'message' => '用户卖出物品订单撮合成功',
  447. 'order_id' => $order->id,
  448. 'transaction_id' => $transaction->id,
  449. 'total_amount' => $totalAmount,
  450. ];
  451. } catch (\Exception $e) {
  452. return [
  453. 'success' => false,
  454. 'message' => '用户卖出物品订单撮合失败:' . $e->getMessage(),
  455. 'order_id' => $order->id,
  456. 'total_amount' => '0.00000',
  457. ];
  458. }
  459. }
  460. /**
  461. * 检查用户买入物品撮合条件
  462. *
  463. * @param int $itemId 商品ID
  464. * @return array 检查结果
  465. */
  466. public static function checkUserBuyItemMatchConditions(int $itemId): array
  467. {
  468. // 检查价格配置
  469. $priceConfig = MexPriceConfig::where('item_id', $itemId)->where('is_enabled', true)->first();
  470. if (!$priceConfig) {
  471. return [
  472. 'can_match' => false,
  473. 'message' => '商品未配置价格信息或已禁用',
  474. ];
  475. }
  476. // 检查仓库库存
  477. $warehouse = MexWarehouse::where('item_id', $itemId)->first();
  478. if (!$warehouse || $warehouse->quantity <= 0) {
  479. return [
  480. 'can_match' => false,
  481. 'message' => '仓库库存不足',
  482. ];
  483. }
  484. // 检查是否有符合条件的待撮合用户买入物品订单
  485. $pendingBuyOrders = MexOrder::where('item_id', $itemId)
  486. ->where('order_type', OrderType::BUY)
  487. ->where('status', OrderStatus::PENDING)
  488. ->where('price', '>=', $priceConfig->max_price) // 价格≥最高价
  489. ->where('quantity', '<=', $priceConfig->protection_threshold) // 数量≤保护阈值
  490. ->count();
  491. if ($pendingBuyOrders === 0) {
  492. return [
  493. 'can_match' => false,
  494. 'message' => '没有符合条件的待撮合用户买入物品订单',
  495. ];
  496. }
  497. return [
  498. 'can_match' => true,
  499. 'message' => '用户买入物品撮合条件满足',
  500. 'warehouse' => $warehouse,
  501. 'price_config' => $priceConfig,
  502. 'pending_orders' => $pendingBuyOrders,
  503. ];
  504. }
  505. /**
  506. * 检查用户卖出物品撮合条件
  507. *
  508. * @param int $itemId 商品ID
  509. * @return array 检查结果
  510. */
  511. public static function checkUserSellItemMatchConditions(int $itemId): array
  512. {
  513. // 检查价格配置
  514. $priceConfig = MexPriceConfig::where('item_id', $itemId)->where('is_enabled', true)->first();
  515. if (!$priceConfig) {
  516. return [
  517. 'can_match' => false,
  518. 'message' => '商品未配置价格信息或已禁用',
  519. ];
  520. }
  521. // 检查是否有待撮合的用户卖出物品订单
  522. $pendingSellOrders = MexOrder::where('item_id', $itemId)
  523. ->where('order_type', OrderType::SELL)
  524. ->where('status', OrderStatus::PENDING)
  525. ->count();
  526. if ($pendingSellOrders === 0) {
  527. return [
  528. 'can_match' => false,
  529. 'message' => '没有待撮合的用户卖出物品订单',
  530. ];
  531. }
  532. return [
  533. 'can_match' => true,
  534. 'message' => '用户卖出物品撮合条件满足',
  535. 'price_config' => $priceConfig,
  536. 'pending_orders' => $pendingSellOrders,
  537. ];
  538. }
  539. /**
  540. * 获取用户买入物品撮合统计信息
  541. *
  542. * @return array 统计信息
  543. */
  544. public static function getUserBuyItemMatchStats(): array
  545. {
  546. // 获取待撮合用户买入物品订单统计
  547. $pendingStats = MexOrder::where('order_type', OrderType::BUY)
  548. ->where('status', OrderStatus::PENDING)
  549. ->selectRaw('
  550. COUNT(*) as total_pending,
  551. COUNT(DISTINCT item_id) as pending_items,
  552. SUM(quantity) as total_quantity,
  553. SUM(total_amount) as total_amount
  554. ')
  555. ->first();
  556. // 获取今日用户买入物品撮合统计
  557. $todayStats = MexTransaction::where('transaction_type', TransactionType::USER_BUY)
  558. ->whereDate('created_at', today())
  559. ->selectRaw('
  560. COUNT(*) as today_matched,
  561. SUM(quantity) as today_quantity,
  562. SUM(total_amount) as today_amount
  563. ')
  564. ->first();
  565. // 获取有库存的商品数量
  566. $availableItems = MexWarehouse::where('quantity', '>', 0)->count();
  567. return [
  568. 'pending_orders' => $pendingStats->total_pending ?? 0,
  569. 'pending_items' => $pendingStats->pending_items ?? 0,
  570. 'pending_quantity' => $pendingStats->total_quantity ?? 0,
  571. 'pending_amount' => $pendingStats->total_amount ?? '0.00000',
  572. 'today_matched' => $todayStats->today_matched ?? 0,
  573. 'today_quantity' => $todayStats->today_quantity ?? 0,
  574. 'today_amount' => $todayStats->today_amount ?? '0.00000',
  575. 'available_items' => $availableItems,
  576. 'stats_time' => now(),
  577. ];
  578. }
  579. /**
  580. * 获取用户卖出物品撮合统计信息
  581. *
  582. * @return array 统计信息
  583. */
  584. public static function getUserSellItemMatchStats(): array
  585. {
  586. // 获取待撮合用户卖出物品订单统计
  587. $pendingStats = MexOrder::where('order_type', OrderType::SELL)
  588. ->where('status', OrderStatus::PENDING)
  589. ->selectRaw('
  590. COUNT(*) as total_pending,
  591. COUNT(DISTINCT item_id) as pending_items,
  592. SUM(quantity) as total_quantity,
  593. SUM(total_amount) as total_amount
  594. ')
  595. ->first();
  596. // 获取今日用户卖出物品撮合统计
  597. $todayStats = MexTransaction::where('transaction_type', TransactionType::USER_SELL)
  598. ->whereDate('created_at', today())
  599. ->selectRaw('
  600. COUNT(*) as today_matched,
  601. SUM(quantity) as today_quantity,
  602. SUM(total_amount) as today_amount
  603. ')
  604. ->first();
  605. return [
  606. 'pending_orders' => $pendingStats->total_pending ?? 0,
  607. 'pending_items' => $pendingStats->pending_items ?? 0,
  608. 'pending_quantity' => $pendingStats->total_quantity ?? 0,
  609. 'pending_amount' => $pendingStats->total_amount ?? '0.00000',
  610. 'today_matched' => $todayStats->today_matched ?? 0,
  611. 'today_quantity' => $todayStats->today_quantity ?? 0,
  612. 'today_amount' => $todayStats->today_amount ?? '0.00000',
  613. 'stats_time' => now(),
  614. ];
  615. }
  616. // 保留旧方法以兼容现有代码
  617. /**
  618. * 获取撮合统计信息(已废弃,请使用getUserBuyItemMatchStats)
  619. *
  620. * @deprecated 请使用getUserBuyItemMatchStats或getUserSellItemMatchStats
  621. * @return array 统计信息
  622. */
  623. public static function getMatchStats(): array
  624. {
  625. return self::getUserBuyItemMatchStats();
  626. }
  627. /**
  628. * 检查撮合条件(已废弃,请使用checkUserBuyItemMatchConditions)
  629. *
  630. * @deprecated 请使用checkUserBuyItemMatchConditions或checkUserSellItemMatchConditions
  631. * @param int $itemId 商品ID
  632. * @return array 检查结果
  633. */
  634. public static function checkMatchConditions(int $itemId): array
  635. {
  636. return self::checkUserBuyItemMatchConditions($itemId);
  637. }
  638. /**
  639. * 执行撮合任务(已废弃,请使用executeUserBuyItemMatch)
  640. *
  641. * @deprecated 请使用executeUserBuyItemMatch或executeUserSellItemMatch
  642. * @param int|null $itemId 指定商品ID,null表示处理所有商品
  643. * @param int $batchSize 批处理大小
  644. * @return array 撮合结果
  645. */
  646. public static function executeMatch(?int $itemId = null, int $batchSize = 100): array
  647. {
  648. return self::executeUserBuyItemMatch($itemId, $batchSize);
  649. }
  650. /**
  651. * 执行单个商品的撮合(已废弃,请使用executeUserBuyItemMatchForItem)
  652. *
  653. * @deprecated 请使用executeUserBuyItemMatchForItem或executeUserSellItemMatchForItem
  654. * @param int $itemId 商品ID
  655. * @param int $batchSize 批处理大小
  656. * @return array 撮合结果
  657. */
  658. public static function executeItemMatch(int $itemId, int $batchSize = 100): array
  659. {
  660. return self::executeUserBuyItemMatchForItem($itemId, $batchSize);
  661. }
  662. /**
  663. * 将用户冻结资金转入仓库账户
  664. *
  665. * @param int $userId 用户ID
  666. * @param string $amount 金额
  667. * @param int $orderId 订单ID
  668. * @return array 转移结果
  669. */
  670. private static function transferFrozenFundsToWarehouse(int $userId, string $amount, int $orderId): array
  671. {
  672. try {
  673. // TODO: 这里需要根据实际的资金类型进行调整
  674. // 假设使用钻石币种(FUND_TYPE::FUND2)和冻结账户(FUND_TYPE::FUND3)
  675. // 从用户冻结账户转移到仓库账户
  676. $fundService = new FundService($userId, FUND_TYPE::FUND3->value); // 冻结账户
  677. $result = $fundService->trade(
  678. self::WAREHOUSE_USER_ID,
  679. (int)bcmul($amount, '100', 0), // 转换为整数存储
  680. 'MEX_ORDER',
  681. $orderId,
  682. '用户买入物品撮合-资金转移'
  683. );
  684. if (is_string($result)) {
  685. return [
  686. 'success' => false,
  687. 'message' => $result,
  688. ];
  689. }
  690. return [
  691. 'success' => true,
  692. 'message' => '资金转移成功',
  693. 'data' => $result,
  694. ];
  695. } catch (\Exception $e) {
  696. return [
  697. 'success' => false,
  698. 'message' => '资金转移异常:' . $e->getMessage(),
  699. ];
  700. }
  701. }
  702. /**
  703. * 将仓库账户物品转出到用户账户
  704. *
  705. * @param int $userId 用户ID
  706. * @param int $itemId 物品ID
  707. * @param int $quantity 数量
  708. * @param int $orderId 订单ID
  709. * @return array 转移结果
  710. */
  711. private static function transferItemsFromWarehouseToUser(int $userId, int $itemId, int $quantity, int $orderId): array
  712. {
  713. try {
  714. // 添加物品到用户账户
  715. $result = ItemService::addItem($userId, $itemId, $quantity, [
  716. 'source' => 'mex_order',
  717. 'source_id' => $orderId,
  718. 'remark' => '用户买入物品撮合-物品转移',
  719. ]);
  720. if (!$result || !isset($result['success']) || !$result['success']) {
  721. return [
  722. 'success' => false,
  723. 'message' => '物品添加失败:' . ($result['message'] ?? '未知错误'),
  724. ];
  725. }
  726. return [
  727. 'success' => true,
  728. 'message' => '物品转移成功',
  729. 'data' => $result,
  730. ];
  731. } catch (\Exception $e) {
  732. return [
  733. 'success' => false,
  734. 'message' => '物品转移异常:' . $e->getMessage(),
  735. ];
  736. }
  737. }
  738. /**
  739. * 将用户冻结物品转入仓库账户
  740. *
  741. * @param int $userId 用户ID
  742. * @param int $itemId 物品ID
  743. * @param int $quantity 数量
  744. * @param int $orderId 订单ID
  745. * @return array 转移结果
  746. */
  747. private static function transferFrozenItemsToWarehouse(int $userId, int $itemId, int $quantity, int $orderId): array
  748. {
  749. try {
  750. // TODO: 这里需要实现从用户冻结物品转移到仓库的逻辑
  751. // 由于物品冻结功能比较复杂,这里先返回成功,后续完善
  752. // 消耗用户物品(包括冻结的物品)
  753. $result = ItemService::consumeItem($userId, $itemId, null, $quantity, [
  754. 'source' => 'mex_order',
  755. 'source_id' => $orderId,
  756. 'remark' => '用户卖出物品撮合-物品转移',
  757. 'include_frozen' => true, // 包括冻结的物品
  758. ]);
  759. if (!$result || !isset($result['success']) || !$result['success']) {
  760. return [
  761. 'success' => false,
  762. 'message' => '物品消耗失败:' . ($result['message'] ?? '未知错误'),
  763. ];
  764. }
  765. return [
  766. 'success' => true,
  767. 'message' => '物品转移成功',
  768. 'data' => $result,
  769. ];
  770. } catch (\Exception $e) {
  771. return [
  772. 'success' => false,
  773. 'message' => '物品转移异常:' . $e->getMessage(),
  774. ];
  775. }
  776. }
  777. /**
  778. * 将仓库账户资金转出到用户账户
  779. *
  780. * @param int $userId 用户ID
  781. * @param string $amount 金额
  782. * @param int $orderId 订单ID
  783. * @return array 转移结果
  784. */
  785. private static function transferFundsFromWarehouseToUser(int $userId, string $amount, int $orderId): array
  786. {
  787. try {
  788. // TODO: 这里需要根据实际的资金类型进行调整
  789. // 假设使用钻石币种(FUND_TYPE::FUND2)
  790. // 从仓库账户转移到用户账户
  791. $fundService = new FundService(self::WAREHOUSE_USER_ID, FUND_TYPE::FUND2->value); // 仓库账户
  792. $result = $fundService->trade(
  793. $userId,
  794. (int)bcmul($amount, '100', 0), // 转换为整数存储
  795. 'MEX_ORDER',
  796. $orderId,
  797. '用户卖出物品撮合-资金转移'
  798. );
  799. if (is_string($result)) {
  800. return [
  801. 'success' => false,
  802. 'message' => $result,
  803. ];
  804. }
  805. return [
  806. 'success' => true,
  807. 'message' => '资金转移成功',
  808. 'data' => $result,
  809. ];
  810. } catch (\Exception $e) {
  811. return [
  812. 'success' => false,
  813. 'message' => '资金转移异常:' . $e->getMessage(),
  814. ];
  815. }
  816. }
  817. }