WashOrderServiceImpl.java 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. package com.kym.service.impl;
  2. import cn.dev33.satoken.stp.StpUtil;
  3. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  4. import com.github.pagehelper.PageHelper;
  5. import com.github.pagehelper.PageInfo;
  6. import com.github.yulichang.toolkit.JoinWrappers;
  7. import com.github.yulichang.wrapper.MPJLambdaWrapper;
  8. import com.kym.common.exception.BusinessException;
  9. import com.kym.common.utils.CommUtil;
  10. import com.kym.common.utils.OrderUtils;
  11. import com.kym.entity.Account;
  12. import com.kym.entity.User;
  13. import com.kym.entity.WashOrder;
  14. import com.kym.entity.WashStation;
  15. import com.kym.entity.awoara.OrderInfo;
  16. import com.kym.entity.common.PageBean;
  17. import com.kym.entity.common.PageParams;
  18. import com.kym.entity.queryParams.DeviceQueryParams;
  19. import com.kym.entity.queryParams.StatQueryParam;
  20. import com.kym.entity.queryParams.WashOrderQueryParams;
  21. import com.kym.entity.vo.StationTrendVo;
  22. import com.kym.entity.vo.WashOrderVo;
  23. import com.kym.mapper.WashOrderMapper;
  24. import com.kym.service.AccountService;
  25. import com.kym.service.OrderSettlementService;
  26. import com.kym.service.UserService;
  27. import com.kym.service.WashOrderService;
  28. import com.kym.service.WashStationService;
  29. import com.kym.service.awoara.AwoaraService;
  30. import com.kym.service.cache.KymCache;
  31. import com.kym.service.mybatisplus.MyBaseServiceImpl;
  32. import lombok.extern.slf4j.Slf4j;
  33. import org.springframework.beans.BeanUtils;
  34. import org.springframework.beans.factory.annotation.Value;
  35. import org.springframework.context.annotation.Lazy;
  36. import org.springframework.stereotype.Service;
  37. import java.time.LocalDate;
  38. import java.time.LocalDateTime;
  39. import java.time.LocalTime;
  40. import java.time.temporal.TemporalAdjusters;
  41. import java.util.List;
  42. import java.util.Map;
  43. import java.util.UUID;
  44. import java.util.stream.Collectors;
  45. /**
  46. * <p>
  47. * 洗车订单表 服务实现类
  48. * </p>
  49. *
  50. * @author skyline
  51. * @since 2024-09-11
  52. */
  53. @Slf4j
  54. @Service
  55. public class WashOrderServiceImpl extends MyBaseServiceImpl<WashOrderMapper, WashOrder> implements WashOrderService {
  56. private final AwoaraService awoaraService;
  57. private final AccountService accountService;
  58. private final WashStationService washStationService;
  59. private final UserService userService;
  60. private final OrderSettlementService orderSettlementService;
  61. @Value("${kym.domain}")
  62. private String DOMAIN;
  63. public WashOrderServiceImpl(AwoaraService awoaraService, AccountService accountService,
  64. @Lazy WashStationService washStationService, UserService userService,
  65. @Lazy OrderSettlementService orderSettlementService) {
  66. this.awoaraService = awoaraService;
  67. this.accountService = accountService;
  68. this.washStationService = washStationService;
  69. this.userService = userService;
  70. this.orderSettlementService = orderSettlementService;
  71. }
  72. /**
  73. * 创建订单(启动洗车机)
  74. *
  75. * @param params
  76. * @return
  77. */
  78. @Override
  79. public String createOrder(DeviceQueryParams params) {
  80. // 校验余额
  81. var account = accountService.getAccountByUserId(StpUtil.getLoginIdAsLong());
  82. if (account.getBalance() < Account.MIN_BALANCE) {
  83. throw new BusinessException("余额不足,请保持余额不低于2元!");
  84. }
  85. // 校验用户是否有未完结的订单
  86. var unfinishedOrder = lambdaQuery()
  87. .eq(WashOrder::getUserId, StpUtil.getLoginIdAsLong())
  88. .and(wrapper -> wrapper
  89. .eq(WashOrder::getOrderStatus, WashOrder.ORDER_STATUS_开机)
  90. .or()
  91. .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_未支付))
  92. .list();
  93. if (!unfinishedOrder.isEmpty()) {
  94. throw new BusinessException("您有未完结的订单!");
  95. }
  96. // 检查设备是否有僵死订单(设备显示忙碌但订单实际已超时)
  97. checkAndResolveStaleOrder(params.getProductKey(), params.getDeviceName());
  98. var memberName = StpUtil.getSession().getString(User.ST_SESSION_KEY_MOBILE);
  99. var orderId = OrderUtils.getOrderNo();
  100. // 请求阿里云lot
  101. var createOrder = awoaraService.createOrder(params.getProductKey(), params.getDeviceName(),
  102. orderId,
  103. memberName,
  104. account.getBalance(),
  105. Account.NO_DISCOUNT,
  106. // 本次开机最大消费金额
  107. account.getBalance());
  108. // 首次消费:无归属站点的用户自动归属到当前设备所在站点
  109. long userId = StpUtil.getLoginIdAsLong();
  110. var user = userService.getById(userId);
  111. var userStationId = user != null ? user.getStationId() : null;
  112. if (CommUtil.isEmptyOrNull(userStationId)) {
  113. userService.lambdaUpdate()
  114. .set(User::getStationId, params.getStationId())
  115. .eq(User::getId, userId)
  116. .update();
  117. StpUtil.getSession().set("stationId", params.getStationId());
  118. KymCache.INSTANCE.putUserId2StationId(Map.of(userId, params.getStationId()));
  119. userStationId = params.getStationId();
  120. }
  121. var washOrder = new WashOrder()
  122. .setUserId(StpUtil.getLoginIdAsLong())
  123. .setStationId(params.getStationId())
  124. .setProductKey(params.getProductKey())
  125. .setDeviceName(params.getDeviceName())
  126. .setShortId(KymCache.INSTANCE.getShortIdByProductKeyAndDeviceName(params.getProductKey(), params.getDeviceName()))
  127. .setOpenType(WashOrder.START_CLOSE_TYPE_网络)
  128. .setOrderId(orderId)
  129. .setOrderIdLocal(createOrder.getOrder_id_local())
  130. .setMemberDiscount(Account.NO_DISCOUNT)
  131. .setPrepayMoney(account.getBalance())
  132. .setStartTime(LocalDateTime.now())
  133. .setOrderStatus(WashOrder.ORDER_STATUS_开机)
  134. .setPayStatus(WashOrder.PAY_STATUS_未支付)
  135. .setIsCross(!params.getStationId().equals(userStationId));
  136. save(washOrder);
  137. return orderId;
  138. }
  139. @Override
  140. public void closeOrder(DeviceQueryParams params) {
  141. var order = lambdaQuery()
  142. .eq(WashOrder::getProductKey, params.getProductKey())
  143. .eq(WashOrder::getDeviceName, params.getDeviceName())
  144. .eq(WashOrder::getOrderStatus, WashOrder.ORDER_STATUS_开机)
  145. .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_未支付)
  146. .one();
  147. if (order == null) {
  148. return;
  149. }
  150. if (order.getUserId() != StpUtil.getLoginIdAsLong()) {
  151. throw new BusinessException("您没有权限关闭该订单!");
  152. }
  153. // 尝试发送关闭命令,失败后重试1次
  154. boolean sent = sendCloseOrderWithRetry(params.getProductKey(), params.getDeviceName(), order.getOrderId(), 1);
  155. if (sent) {
  156. return;
  157. }
  158. // RRpc 失败,主动查询设备订单状态
  159. log.warn("订单 {} 关闭命令发送失败,尝试主动查询设备状态", order.getOrderId());
  160. try {
  161. OrderInfo orderInfo = awoaraService.queryOrder(params.getProductKey(), params.getDeviceName(), order.getOrderId());
  162. if (orderInfo != null && orderInfo.getClose_type() != null && !orderInfo.getClose_type().isEmpty()) {
  163. log.info("订单 {} 设备已关闭,执行结算", order.getOrderId());
  164. orderSettlementService.settleOrder(order, orderInfo);
  165. return;
  166. }
  167. if (orderInfo != null && (orderInfo.getClose_type() == null || orderInfo.getClose_type().isEmpty())) {
  168. // 设备还在运行,尝试强制关闭
  169. log.info("订单 {} 设备仍在运行,尝试强制关闭", order.getOrderId());
  170. try {
  171. awoaraService.forceCloseOrder(params.getProductKey(), params.getDeviceName());
  172. } catch (Exception fe) {
  173. log.error("订单 {} 强制关闭也失败", order.getOrderId(), fe);
  174. }
  175. }
  176. } catch (Exception e) {
  177. log.error("订单 {} 主动查询设备状态也失败", order.getOrderId(), e);
  178. }
  179. throw new BusinessException("关闭订单失败,请稍后重试或联系管理员");
  180. }
  181. @Override
  182. public void forceSettleOrder(String orderId) {
  183. var order = lambdaQuery()
  184. .eq(WashOrder::getOrderId, orderId)
  185. .eq(WashOrder::getOrderStatus, WashOrder.ORDER_STATUS_开机)
  186. .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_未支付)
  187. .one();
  188. if (order == null) {
  189. throw new BusinessException("未找到该订单或订单已完结");
  190. }
  191. // 尝试通过 RRpc 发送关闭命令
  192. sendCloseOrderWithRetry(order.getProductKey(), order.getDeviceName(), order.getOrderId(), 1);
  193. // 查询设备订单状态进行结算
  194. log.info("订单 {} 已发送关闭命令,查询设备订单状态进行结算", order.getOrderId());
  195. try {
  196. OrderInfo orderInfo = awoaraService.queryOrder(order.getProductKey(), order.getDeviceName(), order.getOrderId());
  197. if (orderInfo != null && orderInfo.getClose_type() != null && !orderInfo.getClose_type().isEmpty()) {
  198. log.info("订单 {} 设备已关闭,执行结算", order.getOrderId());
  199. orderSettlementService.settleOrder(order, orderInfo);
  200. return;
  201. }
  202. if (orderInfo != null && (orderInfo.getClose_type() == null || orderInfo.getClose_type().isEmpty())) {
  203. // 设备还在运行,尝试强制关闭
  204. log.info("订单 {} 设备仍在运行,尝试强制关闭", order.getOrderId());
  205. try {
  206. awoaraService.forceCloseOrder(order.getProductKey(), order.getDeviceName());
  207. } catch (Exception fe) {
  208. log.error("订单 {} 强制关闭也失败", order.getOrderId(), fe);
  209. }
  210. }
  211. } catch (Exception e) {
  212. log.error("订单 {} 主动查询设备状态也失败", order.getOrderId(), e);
  213. }
  214. throw new BusinessException("强制结算失败,请稍后重试或联系管理员");
  215. }
  216. private boolean sendCloseOrderWithRetry(String productKey, String deviceName, String orderId, int maxRetries) {
  217. for (int i = 0; i <= maxRetries; i++) {
  218. try {
  219. awoaraService.closeOrder(productKey, deviceName, orderId);
  220. return true;
  221. } catch (Exception e) {
  222. if (i < maxRetries) {
  223. log.warn("订单 {} 关闭命令第{}次失败,准备重试", orderId, i + 1, e);
  224. } else {
  225. log.error("订单 {} 关闭命令{}次均失败", orderId, maxRetries + 1, e);
  226. }
  227. }
  228. }
  229. return false;
  230. }
  231. /**
  232. * 检查设备是否有僵死订单,有则尝试清理
  233. */
  234. private void checkAndResolveStaleOrder(String productKey, String deviceName) {
  235. var staleOrder = lambdaQuery()
  236. .eq(WashOrder::getProductKey, productKey)
  237. .eq(WashOrder::getDeviceName, deviceName)
  238. .eq(WashOrder::getOrderStatus, WashOrder.ORDER_STATUS_开机)
  239. .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_未支付)
  240. .one();
  241. if (staleOrder == null) {
  242. return;
  243. }
  244. log.warn("设备 {}/{} 存在未完结订单 {},尝试查询设备实际状态", productKey, deviceName, staleOrder.getOrderId());
  245. try {
  246. OrderInfo orderInfo = awoaraService.queryOrder(productKey, deviceName, staleOrder.getOrderId());
  247. if (orderInfo != null && orderInfo.getClose_type() != null && !orderInfo.getClose_type().isEmpty()) {
  248. log.info("设备实际已关闭订单 {},执行结算", staleOrder.getOrderId());
  249. orderSettlementService.settleOrder(staleOrder, orderInfo);
  250. }
  251. } catch (Exception e) {
  252. log.warn("查询设备订单状态失败,跳过清理:{}", e.getMessage());
  253. }
  254. }
  255. /**
  256. * 查询订单详情
  257. *
  258. * @param params
  259. * @return
  260. */
  261. @Override
  262. public WashOrder queryOrder(WashOrderQueryParams params) {
  263. // 非实时数据
  264. WashOrder order = lambdaQuery()
  265. .eq(WashOrder::getOrderId, params.getOrderId())
  266. .one();
  267. if (null != params.getUserId()) {
  268. CommUtil.asserts(null != order && order.getUserId().equals(StpUtil.getLoginIdAsLong()),
  269. "订单不存在或您没有权限查看该订单!");
  270. }
  271. return order;
  272. }
  273. /**
  274. * 查询停车减免订单
  275. *
  276. * @param unionid
  277. * @return
  278. */
  279. @Override
  280. public String getParkingDiscounts(String unionid) {
  281. var user = userService.lambdaQuery().eq(User::getUnionid, unionid).one();
  282. CommUtil.asserts(null != user, "用户信息异常:无此用户");
  283. // 查询用户24小时内的订单
  284. var orders = lambdaQuery()
  285. .eq(WashOrder::getUserId, user.getId())
  286. .ge(WashOrder::getStartTime, LocalDateTime.now().minusHours(24))
  287. .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_已支付)
  288. .orderByDesc(WashOrder::getId)
  289. .list();
  290. CommUtil.asserts(CommUtil.isEmptyOrNull(orders) && (orders.stream().mapToInt(WashOrder::getAmount).sum() >= 0),
  291. "抱歉:无停车场洗车记录");
  292. return washStationService.lambdaQuery().eq(WashStation::getStationId, orders.get(0).getStationId()).one().getParkingQrCode();
  293. }
  294. /**
  295. * 当前用户订单列表
  296. *
  297. * @param params
  298. * @return
  299. */
  300. @Override
  301. public PageBean<WashOrderVo> listMyWashOrder(PageParams params) {
  302. PageHelper.startPage(params.getPageNum(), params.getPageSize());
  303. var res = lambdaQuery()
  304. .eq(WashOrder::getUserId, StpUtil.getLoginIdAsLong())
  305. .orderByDesc(WashOrder::getId)
  306. .list();
  307. PageInfo<WashOrder> pages = new PageInfo<>(res);
  308. var voList = pages.getList().stream().map(order -> {
  309. var vo = new WashOrderVo();
  310. BeanUtils.copyProperties(order, vo);
  311. vo.setStationName(KymCache.INSTANCE.getStationNameById(order.getStationId()));
  312. return vo;
  313. }).toList();
  314. PageBean<WashOrderVo> bean = new PageBean<>(voList);
  315. bean.setPages(pages.getPages());
  316. bean.setPageNum(pages.getPageNum());
  317. bean.setPageSize(pages.getPageSize());
  318. bean.setTotal(pages.getTotal());
  319. return bean;
  320. }
  321. //region 管理后台
  322. @Override
  323. public PageBean<WashOrderVo> list(WashOrderQueryParams params) {
  324. PageHelper.startPage(params.getPageNum(), params.getPageSize());
  325. MPJLambdaWrapper<WashOrder> wrapper = JoinWrappers.lambda(WashOrder.class)
  326. .selectAsClass(WashOrder.class, WashOrderVo.class)
  327. .selectAs(User::getMobilePhone, WashOrderVo::getMobilePhone)
  328. .leftJoin(User.class, User::getId, WashOrder::getUserId)
  329. .eq(CommUtil.isNotEmptyAndNull(params.getUserId()), WashOrder::getUserId, params.getUserId())
  330. .eq(CommUtil.isNotEmptyAndNull(params.getMobilePhone()), User::getMobilePhone, params.getMobilePhone())
  331. .eq(CommUtil.isNotEmptyAndNull(params.getOrderStatus()), WashOrder::getOrderStatus, params.getOrderStatus())
  332. .eq(CommUtil.isNotEmptyAndNull(params.getPayStatus()), WashOrder::getPayStatus, params.getPayStatus())
  333. .orderByDesc(WashOrder::getId);
  334. //连表查询 返回自定义ResultType
  335. List<WashOrderVo> list = selectJoinList(WashOrderVo.class, wrapper);
  336. list.forEach(item -> {
  337. item.setStationName(KymCache.INSTANCE.getStationNameById(item.getStationId()));
  338. item.setUserStationId(KymCache.INSTANCE.getUserStationId(item.getUserId()));
  339. item.setUserStationName(KymCache.INSTANCE.getStationNameById(item.getUserStationId()));
  340. });
  341. return new PageBean<>(list);
  342. }
  343. /**
  344. * 获取订单详情
  345. * 根据订单号查询,而不是主键ID
  346. *
  347. * @param orderId 订单号
  348. * @return 订单详情
  349. */
  350. @Override
  351. public WashOrderVo detail(String orderId) {
  352. MPJLambdaWrapper<WashOrder> wrapper = JoinWrappers.lambda(WashOrder.class)
  353. .selectAsClass(WashOrder.class, WashOrderVo.class)
  354. .selectAs(User::getMobilePhone, WashOrderVo::getMobilePhone)
  355. .leftJoin(User.class, User::getId, WashOrder::getUserId)
  356. .eq(WashOrder::getOrderId, orderId);
  357. WashOrderVo washOrderVo = selectJoinOne(WashOrderVo.class, wrapper);
  358. if (washOrderVo != null) {
  359. washOrderVo.setStationName(KymCache.INSTANCE.getStationNameById(washOrderVo.getStationId()));
  360. washOrderVo.setUserStationId(KymCache.INSTANCE.getUserStationId(washOrderVo.getUserId()));
  361. washOrderVo.setUserStationName(KymCache.INSTANCE.getStationNameById(washOrderVo.getUserStationId()));
  362. }
  363. return washOrderVo;
  364. }
  365. /**
  366. * 统计指定日期各站点消费金额
  367. *
  368. * @param statDay
  369. * @return
  370. */
  371. @Override
  372. public Map<String, Integer> sumAmountByDate(LocalDate statDay) {
  373. LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
  374. wrapper.select(WashOrder::getAmount, WashOrder::getStationId);
  375. wrapper.ge(WashOrder::getStartTime, LocalDateTime.of(statDay, LocalTime.MIN));
  376. wrapper.gt(WashOrder::getStartTime, LocalDateTime.of(statDay, LocalTime.MAX));
  377. var list = list(wrapper);
  378. // 按照站点分组统计金额
  379. return list.stream().collect(Collectors.groupingBy(WashOrder::getStationId, Collectors.summingInt(WashOrder::getAmount)));
  380. }
  381. /**
  382. * 统计指定日期当月各站点总消费金额
  383. *
  384. * @param statDay
  385. * @return
  386. */
  387. @Override
  388. public Map<String, Integer> sumMonthAmount(LocalDate statDay) {
  389. var startTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).atTime(LocalTime.MIN);
  390. var endTime = statDay.with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX);
  391. LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
  392. wrapper.select(WashOrder::getAmount, WashOrder::getStationId);
  393. wrapper.ge(WashOrder::getStartTime, startTime);
  394. wrapper.lt(WashOrder::getStartTime, endTime);
  395. var list = list(wrapper);
  396. // 按照站点分组统计金额
  397. return list.stream().collect(Collectors.groupingBy(WashOrder::getStationId, Collectors.summingInt(WashOrder::getAmount)));
  398. }
  399. /**
  400. * 统计指定日期订单数量
  401. *
  402. * @param statDay
  403. * @return
  404. */
  405. @Override
  406. public Map<String, Integer> countDailyOrders(LocalDate statDay) {
  407. LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
  408. wrapper.ge(WashOrder::getStartTime, LocalDateTime.of(statDay, LocalTime.MIN));
  409. wrapper.lt(WashOrder::getStartTime, LocalDateTime.of(statDay, LocalTime.MAX));
  410. // 按照站点分组统计订单数量
  411. return list(wrapper).stream().collect(Collectors.groupingBy(WashOrder::getStationId, Collectors.summingInt(o -> 1)));
  412. }
  413. /**
  414. * 统计指定日期订单数量
  415. *
  416. * @param statDay
  417. * @return
  418. */
  419. @Override
  420. public Map<String, Integer> countMonthOrders(LocalDate statDay) {
  421. var startTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).atTime(LocalTime.MIN);
  422. var endTime = statDay.with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX);
  423. LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
  424. wrapper.select(WashOrder::getStationId);
  425. wrapper.ge(WashOrder::getStartTime, startTime);
  426. wrapper.lt(WashOrder::getStartTime, endTime);
  427. // 按照站点分组统计订单数量
  428. return list(wrapper).stream().collect(Collectors.groupingBy(WashOrder::getStationId, Collectors.summingInt(o -> 1)));
  429. }
  430. @Override
  431. public Map<String, Integer> countDailyActiveUsers(LocalDate statDay) {
  432. LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
  433. wrapper.select(WashOrder::getUserId, WashOrder::getStationId);
  434. wrapper.ge(WashOrder::getStartTime, LocalDateTime.of(statDay, LocalTime.MIN));
  435. wrapper.lt(WashOrder::getStartTime, LocalDateTime.of(statDay, LocalTime.MAX));
  436. wrapper.groupBy(WashOrder::getStationId, WashOrder::getUserId);
  437. return list(wrapper).stream()
  438. .collect(Collectors.groupingBy(WashOrder::getStationId, Collectors.summingInt(o -> 1)));
  439. }
  440. @Override
  441. public Map<String, Integer> countMonthActiveUsers(LocalDate statDay) {
  442. var startTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).atTime(LocalTime.MIN);
  443. var endTime = statDay.with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX);
  444. LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
  445. wrapper.select(WashOrder::getUserId, WashOrder::getStationId);
  446. wrapper.ge(WashOrder::getStartTime, startTime);
  447. wrapper.lt(WashOrder::getStartTime, endTime);
  448. wrapper.groupBy(WashOrder::getStationId, WashOrder::getUserId);
  449. return list(wrapper).stream()
  450. .collect(Collectors.groupingBy(WashOrder::getStationId, Collectors.summingInt(o -> 1)));
  451. }
  452. @Override
  453. public WashOrder getOrderInProgressByUserId(long userId) {
  454. return lambdaQuery()
  455. .eq(WashOrder::getUserId, userId)
  456. .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_未支付)
  457. .one();
  458. }
  459. @Override
  460. public List<StationTrendVo> stationTrend(StatQueryParam params) {
  461. return baseMapper.StationTrend(params);
  462. }
  463. @Override
  464. public String checkParkingCoupon(String mobilePhone) {
  465. CommUtil.asserts(CommUtil.isNotEmptyAndNull(mobilePhone),"查询手机号不能为空");
  466. var user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getMobilePhone, mobilePhone));
  467. if (user == null) {
  468. throw new BusinessException("请输入注册洗车小程序使用的手机号码!");
  469. }
  470. // 优惠券有效期2小时,先查已支付订单
  471. var orderList = lambdaQuery()
  472. .eq(WashOrder::getUserId, user.getId())
  473. .ge(WashOrder::getEndTime, LocalDateTime.now().minusHours(2))
  474. .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_已支付)
  475. .orderByDesc(WashOrder::getId).list();
  476. if (!orderList.isEmpty() && orderList.stream().collect(Collectors.summarizingInt(WashOrder::getAmount)).getSum() >= WashOrder.PARKING_COUPON_MIN_AMOUNT) {
  477. var parkingCouponUrl = KymCache.INSTANCE.getParkingQrCodeUrlByStationId(orderList.get(0).getStationId());
  478. var code = UUID.randomUUID().toString();
  479. KymCache.INSTANCE.setParkingCouponCode(code, parkingCouponUrl, 3600 * 2L);
  480. return DOMAIN + "/api/parking-coupon?code=" + code;
  481. }
  482. // 未找到符合条件的已支付订单,尝试查找僵死订单并主动对账结算
  483. var staleOrders = lambdaQuery()
  484. .eq(WashOrder::getUserId, user.getId())
  485. .ge(WashOrder::getStartTime, LocalDateTime.now().minusHours(2))
  486. .eq(WashOrder::getOrderStatus, WashOrder.ORDER_STATUS_开机)
  487. .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_未支付)
  488. .orderByDesc(WashOrder::getId).list();
  489. if (!staleOrders.isEmpty()) {
  490. boolean reconciled = false;
  491. for (WashOrder staleOrder : staleOrders) {
  492. try {
  493. OrderInfo orderInfo = awoaraService.queryOrder(staleOrder.getProductKey(), staleOrder.getDeviceName(), staleOrder.getOrderId());
  494. if (orderInfo != null && orderInfo.getClose_type() != null && !orderInfo.getClose_type().isEmpty()) {
  495. log.info("停车券查询-订单 {} 设备已关闭,主动结算", staleOrder.getOrderId());
  496. orderSettlementService.settleOrder(staleOrder, orderInfo);
  497. reconciled = true;
  498. }
  499. } catch (Exception e) {
  500. log.warn("停车券查询-订单 {} 查询设备失败:{}", staleOrder.getOrderId(), e.getMessage());
  501. }
  502. }
  503. if (reconciled) {
  504. // 结算后重新查询已支付订单
  505. orderList = lambdaQuery()
  506. .eq(WashOrder::getUserId, user.getId())
  507. .ge(WashOrder::getEndTime, LocalDateTime.now().minusHours(2))
  508. .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_已支付)
  509. .orderByDesc(WashOrder::getId).list();
  510. if (!orderList.isEmpty() && orderList.stream().collect(Collectors.summarizingInt(WashOrder::getAmount)).getSum() >= WashOrder.PARKING_COUPON_MIN_AMOUNT) {
  511. var parkingCouponUrl = KymCache.INSTANCE.getParkingQrCodeUrlByStationId(orderList.get(0).getStationId());
  512. var code = UUID.randomUUID().toString();
  513. KymCache.INSTANCE.setParkingCouponCode(code, parkingCouponUrl, 3600 * 2L);
  514. return DOMAIN + "/api/parking-coupon?code=" + code;
  515. }
  516. }
  517. }
  518. throw new BusinessException("不符合优惠条件:订单完成超过2小时或洗车金额不足6元。");
  519. }
  520. //endregion
  521. }