WashOrderServiceImpl.java 27 KB

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