MexPriceConfigLogic.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. <?php
  2. namespace App\Module\Mex\Logic;
  3. use App\Module\Mex\Models\MexPriceConfig;
  4. use Illuminate\Support\Facades\Cache;
  5. /**
  6. * 农贸市场价格配置逻辑
  7. *
  8. * 处理价格配置相关的核心业务逻辑
  9. */
  10. class MexPriceConfigLogic
  11. {
  12. /**
  13. * 缓存键前缀
  14. */
  15. private const CACHE_PREFIX = 'mex_price_config:';
  16. /**
  17. * 缓存时间(秒)
  18. */
  19. private const CACHE_TTL = 600; // 10分钟
  20. /**
  21. * 获取商品价格配置
  22. *
  23. * @param int $itemId 商品ID
  24. * @return array|null 价格配置
  25. */
  26. public static function getItemPriceConfig(int $itemId): ?array
  27. {
  28. $cacheKey = self::CACHE_PREFIX . $itemId;
  29. return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($itemId) {
  30. $config = MexPriceConfig::where('item_id', $itemId)->first();
  31. if (!$config) {
  32. return null;
  33. }
  34. return [
  35. 'item_id' => $config->item_id,
  36. 'min_price' => $config->min_price,
  37. 'max_price' => $config->max_price,
  38. 'protection_threshold' => $config->protection_threshold,
  39. 'is_enabled' => $config->is_enabled,
  40. 'price_range' => $config->min_price . ' - ' . $config->max_price,
  41. 'price_range_width' => bcsub($config->max_price, $config->min_price, 5),
  42. 'created_at' => $config->created_at,
  43. 'updated_at' => $config->updated_at,
  44. ];
  45. });
  46. }
  47. /**
  48. * 获取多个商品的价格配置
  49. *
  50. * @param array $itemIds 商品ID数组
  51. * @return array 价格配置列表
  52. */
  53. public static function getItemsPriceConfig(array $itemIds): array
  54. {
  55. $result = [];
  56. foreach ($itemIds as $itemId) {
  57. $config = self::getItemPriceConfig($itemId);
  58. if ($config) {
  59. $result[$itemId] = $config;
  60. }
  61. }
  62. return $result;
  63. }
  64. /**
  65. * 验证卖出价格
  66. *
  67. * @param int $itemId 商品ID
  68. * @param string $price 价格
  69. * @return array 验证结果
  70. */
  71. public static function validateSellPrice(int $itemId, string $price): array
  72. {
  73. $config = self::getItemPriceConfig($itemId);
  74. if (!$config) {
  75. return [
  76. 'valid' => false,
  77. 'message' => '商品未配置价格信息',
  78. 'error_code' => 'CONFIG_NOT_FOUND'
  79. ];
  80. }
  81. if (!$config['is_enabled']) {
  82. return [
  83. 'valid' => false,
  84. 'message' => '商品价格配置已禁用',
  85. 'error_code' => 'CONFIG_DISABLED'
  86. ];
  87. }
  88. // 卖出价格不能低于最低价
  89. if (bccomp($price, $config['min_price'], 5) < 0) {
  90. return [
  91. 'valid' => false,
  92. 'message' => "卖出价格不能低于最低价 {$config['min_price']}",
  93. 'error_code' => 'PRICE_TOO_LOW',
  94. 'min_price' => $config['min_price']
  95. ];
  96. }
  97. // 卖出价格不能高于最高价
  98. if (bccomp($price, $config['max_price'], 5) > 0) {
  99. return [
  100. 'valid' => false,
  101. 'message' => "卖出价格不能高于最高价 {$config['max_price']}",
  102. 'error_code' => 'PRICE_TOO_HIGH',
  103. 'max_price' => $config['max_price']
  104. ];
  105. }
  106. return [
  107. 'valid' => true,
  108. 'message' => '价格验证通过',
  109. 'config' => $config
  110. ];
  111. }
  112. /**
  113. * 验证买入价格
  114. *
  115. * @param int $itemId 商品ID
  116. * @param string $price 价格
  117. * @return array 验证结果
  118. */
  119. public static function validateBuyPrice(int $itemId, string $price): array
  120. {
  121. $config = self::getItemPriceConfig($itemId);
  122. if (!$config) {
  123. return [
  124. 'valid' => false,
  125. 'message' => '商品未配置价格信息',
  126. 'error_code' => 'CONFIG_NOT_FOUND'
  127. ];
  128. }
  129. if (!$config['is_enabled']) {
  130. return [
  131. 'valid' => false,
  132. 'message' => '商品价格配置已禁用',
  133. 'error_code' => 'CONFIG_DISABLED'
  134. ];
  135. }
  136. if (bccomp($price, $config['max_price'], 5) < 0) {
  137. return [
  138. 'valid' => false,
  139. 'message' => "买入价格不能低于最高价 {$config['max_price']}",
  140. 'error_code' => 'PRICE_TOO_LOW',
  141. 'max_price' => $config['max_price']
  142. ];
  143. }
  144. return [
  145. 'valid' => true,
  146. 'message' => '价格验证通过',
  147. 'config' => $config
  148. ];
  149. }
  150. /**
  151. * 验证订单数量
  152. *
  153. * @param int $itemId 商品ID
  154. * @param int $quantity 数量
  155. * @return array 验证结果
  156. */
  157. public static function validateOrderQuantity(int $itemId, int $quantity): array
  158. {
  159. $config = self::getItemPriceConfig($itemId);
  160. if (!$config) {
  161. return [
  162. 'valid' => false,
  163. 'message' => '商品未配置价格信息',
  164. 'error_code' => 'CONFIG_NOT_FOUND'
  165. ];
  166. }
  167. if (!$config['is_enabled']) {
  168. return [
  169. 'valid' => false,
  170. 'message' => '商品价格配置已禁用',
  171. 'error_code' => 'CONFIG_DISABLED'
  172. ];
  173. }
  174. if ($quantity > $config['protection_threshold']) {
  175. return [
  176. 'valid' => false,
  177. 'message' => "订单数量不能超过保护阈值 {$config['protection_threshold']}",
  178. 'error_code' => 'QUANTITY_OVER_THRESHOLD',
  179. 'protection_threshold' => $config['protection_threshold']
  180. ];
  181. }
  182. return [
  183. 'valid' => true,
  184. 'message' => '数量验证通过',
  185. 'config' => $config
  186. ];
  187. }
  188. /**
  189. * 获取所有启用的价格配置
  190. *
  191. * @return array 价格配置列表
  192. */
  193. public static function getEnabledConfigs(): array
  194. {
  195. $cacheKey = self::CACHE_PREFIX . 'enabled_all';
  196. return Cache::remember($cacheKey, self::CACHE_TTL, function () {
  197. $configs = MexPriceConfig::where('is_enabled', true)
  198. ->orderBy('item_id', 'asc')
  199. ->get();
  200. $result = [];
  201. foreach ($configs as $config) {
  202. $result[] = [
  203. 'item_id' => $config->item_id,
  204. 'min_price' => $config->min_price,
  205. 'max_price' => $config->max_price,
  206. 'protection_threshold' => $config->protection_threshold,
  207. 'price_range' => $config->min_price . ' - ' . $config->max_price,
  208. 'price_range_width' => bcsub($config->max_price, $config->min_price, 5),
  209. ];
  210. }
  211. return $result;
  212. });
  213. }
  214. /**
  215. * 清除价格配置缓存
  216. *
  217. * @param int|null $itemId 商品ID,null表示清除所有
  218. * @return bool 操作结果
  219. */
  220. public static function clearCache(?int $itemId = null): bool
  221. {
  222. if ($itemId) {
  223. $cacheKey = self::CACHE_PREFIX . $itemId;
  224. return Cache::forget($cacheKey);
  225. }
  226. // 清除所有相关缓存
  227. Cache::forget(self::CACHE_PREFIX . 'enabled_all');
  228. // 这里可以根据需要清除特定商品的缓存
  229. // 由于无法直接获取所有缓存键,建议在配置更新时调用具体的清除方法
  230. return true;
  231. }
  232. /**
  233. * 挂单阶段参数验证(不验证价格和保护阈值)
  234. * 根据文档要求:挂单阶段无价格验证,所有订单都可以挂单并冻结资金/物品
  235. *
  236. * @param int $itemId 商品ID
  237. * @param string $price 价格(仅做基本格式验证)
  238. * @param int $quantity 数量(不验证保护阈值)
  239. * @param string $orderType 订单类型 (BUY/SELL)
  240. * @return array 验证结果
  241. */
  242. public static function validateOrderParamsForPlacement(int $itemId, string $price, int $quantity, string $orderType): array
  243. {
  244. // 注意:$orderType 参数保留用于接口一致性,挂单阶段不区分买卖类型的验证
  245. // 只验证价格配置是否存在,不验证价格范围
  246. $config = self::getItemPriceConfig($itemId);
  247. if (!$config) {
  248. return [
  249. 'valid' => false,
  250. 'message' => '商品未配置价格信息',
  251. 'error_code' => 'CONFIG_NOT_FOUND'
  252. ];
  253. }
  254. if (!$config['is_enabled']) {
  255. return [
  256. 'valid' => false,
  257. 'message' => '商品价格配置已禁用',
  258. 'error_code' => 'CONFIG_DISABLED'
  259. ];
  260. }
  261. // 基本的价格格式验证(确保是有效数字)
  262. if (!is_numeric($price) || bccomp($price, '0', 5) <= 0) {
  263. return [
  264. 'valid' => false,
  265. 'message' => '价格必须是大于0的数字',
  266. 'error_code' => 'INVALID_PRICE_FORMAT'
  267. ];
  268. }
  269. // 基本的数量验证(确保是正整数)
  270. if (!is_int($quantity) || $quantity <= 0) {
  271. return [
  272. 'valid' => false,
  273. 'message' => '数量必须是大于0的整数',
  274. 'error_code' => 'INVALID_QUANTITY'
  275. ];
  276. }
  277. return [
  278. 'valid' => true,
  279. 'message' => '挂单参数验证通过',
  280. 'config' => $config
  281. ];
  282. }
  283. /**
  284. * 撮合阶段价格和数量验证(用于撮合时的严格验证)
  285. *
  286. * @param int $itemId 商品ID
  287. * @param string $price 价格
  288. * @param int $quantity 数量
  289. * @param string $orderType 订单类型 (BUY/SELL)
  290. * @return array 验证结果
  291. */
  292. public static function validateOrderParamsForMatching(int $itemId, string $price, int $quantity, string $orderType): array
  293. {
  294. // 验证价格
  295. if ($orderType === 'SELL') {
  296. $priceResult = self::validateSellPrice($itemId, $price);
  297. } else {
  298. $priceResult = self::validateBuyPrice($itemId, $price);
  299. }
  300. if (!$priceResult['valid']) {
  301. return $priceResult;
  302. }
  303. // 验证数量(只对买入订单验证保护阈值)
  304. if ($orderType === 'BUY') {
  305. $quantityResult = self::validateOrderQuantity($itemId, $quantity);
  306. if (!$quantityResult['valid']) {
  307. return $quantityResult;
  308. }
  309. }
  310. return [
  311. 'valid' => true,
  312. 'message' => '撮合参数验证通过',
  313. 'config' => $priceResult['config']
  314. ];
  315. }
  316. /**
  317. * 批量验证价格和数量(已废弃,请使用validateOrderParamsForPlacement或validateOrderParamsForMatching)
  318. *
  319. * @deprecated 请根据使用场景选择validateOrderParamsForPlacement(挂单)或validateOrderParamsForMatching(撮合)
  320. * @param int $itemId 商品ID
  321. * @param string $price 价格
  322. * @param int $quantity 数量
  323. * @param string $orderType 订单类型 (BUY/SELL)
  324. * @return array 验证结果
  325. */
  326. public static function validateOrderParams(int $itemId, string $price, int $quantity, string $orderType): array
  327. {
  328. // 为了向后兼容,默认使用挂单验证
  329. return self::validateOrderParamsForPlacement($itemId, $price, $quantity, $orderType);
  330. }
  331. /**
  332. * 获取商品价格建议
  333. *
  334. * @param int $itemId 商品ID
  335. * @return array 价格建议
  336. */
  337. public static function getPriceSuggestion(int $itemId): array
  338. {
  339. $config = self::getItemPriceConfig($itemId);
  340. if (!$config) {
  341. return [
  342. 'has_suggestion' => false,
  343. 'message' => '商品未配置价格信息'
  344. ];
  345. }
  346. // 获取最新成交价格作为参考
  347. $latestPrice = MexTransactionLogic::getLatestPrice($itemId);
  348. return [
  349. 'has_suggestion' => true,
  350. 'min_price' => $config['min_price'],
  351. 'max_price' => $config['max_price'],
  352. 'latest_price' => $latestPrice,
  353. 'sell_suggestion' => $config['min_price'],
  354. 'buy_suggestion' => $config['max_price'],
  355. 'protection_threshold' => $config['protection_threshold'],
  356. ];
  357. }
  358. }