From 26795c6ce013bb204fa57a8324db7a7c77aea5b8 Mon Sep 17 00:00:00 2001
From: puzhibing <393733352@qq.com>
Date: 星期三, 14 十月 2026 14:07:09 +0800
Subject: [PATCH] 修改bug
---
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TCardChargingOrderController.java | 10 +
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java | 83 +++++++++++++
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TCardChargingOrderService.java | 7 +
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderStopChargingTest.java | 237 +++++++++++++++++++++++++++++++++++++++
4 files changed, 337 insertions(+), 0 deletions(-)
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TCardChargingOrderController.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TCardChargingOrderController.java
index 30c0077..4ecbfc3 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TCardChargingOrderController.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TCardChargingOrderController.java
@@ -5,6 +5,7 @@
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.utils.WebUtils;
+import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
@@ -128,6 +129,15 @@
return R.ok(Collections.singletonMap("list", list));
}
+ @RequiresPermissions("/chargingGun/end_charging")
+ @PutMapping("/stopCharging")
+ @ApiOperation("停止刷卡充电")
+ @Log(title = "刷卡充电订单停止充电", businessType = BusinessType.OTHER)
+ public AjaxResult stopCharging(@RequestParam("chargingOrderId") String chargingOrderId,
+ @RequestParam("chargingGunId") Integer chargingGunId) {
+ return cardChargingOrderService.stopCharging(chargingOrderId, chargingGunId);
+ }
+
@RequiresPermissions("/chargingCardBalanceDetail/list")
@GetMapping("/balance-detail/list")
@ApiOperation("卡片收支明细列表")
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TCardChargingOrderService.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TCardChargingOrderService.java
index fc391b0..b0c2b41 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TCardChargingOrderService.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TCardChargingOrderService.java
@@ -1,5 +1,6 @@
package com.ruoyi.order.service;
+import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.PageInfo;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.common.core.domain.R;
@@ -34,6 +35,12 @@
List<Map<String, Object>> accountingStrategyList(Long chargingOrderId);
+ /**
+ * 管理后台停止刷卡充电:校验订单(数据范围/充电枪/状态),
+ * 条件更新为停止中(4)+结束方式1,再下发平台停止充电命令。
+ */
+ AjaxResult stopCharging(String chargingOrderId, Integer chargingGunId);
+
PageInfo<CardBalanceDetailVO> balanceDetailPage(CardBalanceDetailQuery query);
Map<String, Object> balanceDetailSummary(CardBalanceDetailQuery query);
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java
index 8194c22..ca6c00f 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java
@@ -26,6 +26,7 @@
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.bean.BeanUtils;
+import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.order.api.dto.DeviceStartChargingDTO;
@@ -370,6 +371,65 @@
return baseMapper.accountingStrategyList(chargingOrderId);
}
+ /**
+ * 管理后台停止刷卡充电:与 VIN 码停充口径一致,
+ * 状态推进使用条件更新(仅当前状态为充电中时生效),防止重复点击/设备回调并发。
+ */
+ @Override
+ public AjaxResult stopCharging(String chargingOrderId, Integer chargingGunId) {
+ Long orderId;
+ try {
+ orderId = Long.valueOf(chargingOrderId);
+ } catch (NumberFormatException e) {
+ return AjaxResult.error("刷卡充电订单ID无效");
+ }
+ TCardChargingOrder order = getAuthorizedOrder(orderId);
+ if (order == null) {
+ return AjaxResult.error("刷卡充电订单不存在或无权操作");
+ }
+ if (order.getChargingGunId() == null || !order.getChargingGunId().equals(chargingGunId)) {
+ return AjaxResult.error("充电枪与订单不匹配");
+ }
+ Integer status = order.getStatus();
+ if (status == null || status != ORDER_STATUS_CHARGING) {
+ return AjaxResult.error(status != null && (status == ORDER_STATUS_STOPPING || status == ORDER_STATUS_FINISHED)
+ ? "不能重复操作" : "还未开始充电");
+ }
+ TChargingGun chargingGun;
+ TChargingPile chargingPile;
+ try {
+ R<TChargingGun> gunResult = chargingGunClient.getChargingGunById(chargingGunId);
+ chargingGun = gunResult == null ? null : gunResult.getData();
+ R<TChargingPile> pileResult = order.getChargingPileId() == null ? null
+ : chargingPileClient.getChargingPileById(order.getChargingPileId());
+ chargingPile = pileResult == null ? null : pileResult.getData();
+ } catch (Exception ex) {
+ log.error("[SD停充] 管理后台停充查询设备异常 code={} gunId={}", order.getCode(), chargingGunId, ex);
+ return AjaxResult.error("查询充电设备失败");
+ }
+ if (chargingGun == null || chargingPile == null) {
+ return AjaxResult.error("未找到订单对应的充电设备");
+ }
+ boolean updated = lambdaUpdate()
+ .eq(TCardChargingOrder::getId, order.getId())
+ .eq(TCardChargingOrder::getStatus, ORDER_STATUS_CHARGING)
+ .set(TCardChargingOrder::getStatus, ORDER_STATUS_STOPPING)
+ .set(TCardChargingOrder::getEndMode, 1)
+ .set(TCardChargingOrder::getEndTime, LocalDateTime.now())
+ .update();
+ if (!updated) {
+ return AjaxResult.error("订单状态已变化,请刷新后重试");
+ }
+ order.setStatus(ORDER_STATUS_STOPPING);
+ order.setEndMode(1);
+ PlatformStopCharging command = new PlatformStopCharging();
+ command.setCharging_pile_code(chargingPile.getCode());
+ command.setCharging_gun_code(chargingGun.getCode());
+ sendMessageClient.platformStopCharging(command);
+ log.warn("[SD停充] 管理后台主动停充 code={} gunId={}", order.getCode(), chargingGunId);
+ return AjaxResult.success();
+ }
+
@Override
public PageInfo<CardBalanceDetailVO> balanceDetailPage(CardBalanceDetailQuery query) {
int pageCurr = query.getPageCurr() == null || query.getPageCurr() < 1 ? 1 : query.getPageCurr();
@@ -437,6 +497,29 @@
query.setSiteIds(siteIds);
}
+ /**
+ * 按订单ID查询并校验数据范围(订单所属站点须在当前登录用户授权站点内),与 VIN 码停充校验口径一致
+ */
+ private TCardChargingOrder getAuthorizedOrder(Long chargingOrderId) {
+ if (chargingOrderId == null) {
+ return null;
+ }
+ TCardChargingOrder order = getById(chargingOrderId);
+ if (order == null || order.getSiteId() == null || !getAuthorizedSiteIds().contains(order.getSiteId())) {
+ return null;
+ }
+ return order;
+ }
+
+ private List<Integer> getAuthorizedSiteIds() {
+ Long userId = tokenService.getLoginUser().getUserid();
+ if (userId == null) {
+ return Collections.emptyList();
+ }
+ return Optional.ofNullable(siteClient.getSiteListByUserId(userId).getData())
+ .orElse(Collections.emptyList()).stream().map(GetSiteListDTO::getId).collect(Collectors.toList());
+ }
+
private void setAuthSiteIds(CardBalanceDetailQuery query) {
List<Integer> siteIds = new ArrayList<>();
Long userId = tokenService.getLoginUser().getUserid();
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderStopChargingTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderStopChargingTest.java
new file mode 100644
index 0000000..faabfc1
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderStopChargingTest.java
@@ -0,0 +1,237 @@
+package com.ruoyi.order.service.impl;
+
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.ruoyi.chargingPile.api.dto.GetSiteListDTO;
+import com.ruoyi.chargingPile.api.feignClient.ChargingGunClient;
+import com.ruoyi.chargingPile.api.feignClient.ChargingPileClient;
+import com.ruoyi.chargingPile.api.feignClient.SiteClient;
+import com.ruoyi.chargingPile.api.model.TChargingGun;
+import com.ruoyi.chargingPile.api.model.TChargingPile;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.security.service.TokenService;
+import com.ruoyi.integration.api.feignClient.SendMessageClient;
+import com.ruoyi.integration.api.model.PlatformStopCharging;
+import com.ruoyi.order.api.model.TCardChargingOrder;
+import com.ruoyi.order.mapper.TCardChargingOrderMapper;
+import com.ruoyi.system.api.model.LoginUser;
+import org.apache.ibatis.builder.MapperBuilderAssistant;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * 管理后台刷卡充电订单「停止充电」单测。
+ * 覆盖:数据范围校验、充电枪匹配、状态拦截、条件更新并发保护、停机指令下发。
+ */
+@ExtendWith(MockitoExtension.class)
+class TCardChargingOrderStopChargingTest {
+
+ private static final Long LOGIN_USER_ID = 9L;
+ private static final Integer AUTHORIZED_SITE_ID = 7;
+ private static final Long ORDER_ID = 11L;
+ private static final Integer GUN_ID = 22;
+ private static final Integer PILE_ID = 33;
+
+ @InjectMocks
+ private TCardChargingOrderServiceImpl service;
+ @Mock
+ private TCardChargingOrderMapper mapper;
+ @Mock
+ private TokenService tokenService;
+ @Mock
+ private SiteClient siteClient;
+ @Mock
+ private ChargingGunClient chargingGunClient;
+ @Mock
+ private ChargingPileClient chargingPileClient;
+ @Mock
+ private SendMessageClient sendMessageClient;
+
+ @BeforeEach
+ void setUp() {
+ ReflectionTestUtils.setField(service, "baseMapper", mapper);
+ // lambdaUpdate 解析 getter 引用依赖 MyBatis-Plus 实体元信息缓存,单测环境需显式初始化
+ TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""),
+ TCardChargingOrder.class);
+ // 默认:条件更新成功
+ lenient().when(mapper.update(any(), any())).thenReturn(1);
+ }
+
+ // ===== 一、正常停充 =====
+
+ @Test
+ void stopChargingAdvancesStatusAndSendsCommand() {
+ TCardChargingOrder order = order(3);
+ when(mapper.selectById(ORDER_ID)).thenReturn(order);
+ mockAuthorizedLogin();
+ mockDevice(order);
+
+ AjaxResult result = service.stopCharging(String.valueOf(ORDER_ID), GUN_ID);
+
+ assertEquals(Integer.valueOf(200), result.get("code"));
+ ArgumentCaptor<PlatformStopCharging> captor = ArgumentCaptor.forClass(PlatformStopCharging.class);
+ verify(sendMessageClient).platformStopCharging(captor.capture());
+ assertEquals("PILE-33", captor.getValue().getCharging_pile_code());
+ assertEquals("GUN-22", captor.getValue().getCharging_gun_code());
+ assertEquals(Integer.valueOf(4), order.getStatus());
+ assertEquals(Integer.valueOf(1), order.getEndMode());
+ verify(mapper).update(any(), any());
+ }
+
+ // ===== 二、入参与订单校验 =====
+
+ @Test
+ void rejectsInvalidOrderId() {
+ AjaxResult result = service.stopCharging("abc", GUN_ID);
+
+ assertEquals(Integer.valueOf(500), result.get("code"));
+ assertEquals("刷卡充电订单ID无效", result.get("msg"));
+ verify(sendMessageClient, never()).platformStopCharging(any(PlatformStopCharging.class));
+ }
+
+ @Test
+ void rejectsOrderNotFound() {
+ when(mapper.selectById(ORDER_ID)).thenReturn(null);
+
+ AjaxResult result = service.stopCharging(String.valueOf(ORDER_ID), GUN_ID);
+
+ assertEquals(Integer.valueOf(500), result.get("code"));
+ assertEquals("刷卡充电订单不存在或无权操作", result.get("msg"));
+ verify(sendMessageClient, never()).platformStopCharging(any(PlatformStopCharging.class));
+ }
+
+ @Test
+ void rejectsOrderOutsideAuthorizationScope() {
+ TCardChargingOrder order = order(3);
+ order.setSiteId(AUTHORIZED_SITE_ID + 1);
+ when(mapper.selectById(ORDER_ID)).thenReturn(order);
+ mockAuthorizedLogin();
+
+ AjaxResult result = service.stopCharging(String.valueOf(ORDER_ID), GUN_ID);
+
+ assertEquals(Integer.valueOf(500), result.get("code"));
+ assertEquals("刷卡充电订单不存在或无权操作", result.get("msg"));
+ verify(sendMessageClient, never()).platformStopCharging(any(PlatformStopCharging.class));
+ }
+
+ @Test
+ void rejectsMismatchedGun() {
+ TCardChargingOrder order = order(3);
+ when(mapper.selectById(ORDER_ID)).thenReturn(order);
+ mockAuthorizedLogin();
+
+ AjaxResult result = service.stopCharging(String.valueOf(ORDER_ID), GUN_ID + 1);
+
+ assertEquals(Integer.valueOf(500), result.get("code"));
+ assertEquals("充电枪与订单不匹配", result.get("msg"));
+ verify(sendMessageClient, never()).platformStopCharging(any(PlatformStopCharging.class));
+ }
+
+ // ===== 三、状态拦截 =====
+
+ @Test
+ void rejectsOrderNotStartedCharge() {
+ TCardChargingOrder order = order(2);
+ when(mapper.selectById(ORDER_ID)).thenReturn(order);
+ mockAuthorizedLogin();
+
+ AjaxResult result = service.stopCharging(String.valueOf(ORDER_ID), GUN_ID);
+
+ assertEquals(Integer.valueOf(500), result.get("code"));
+ assertEquals("还未开始充电", result.get("msg"));
+ verify(sendMessageClient, never()).platformStopCharging(any(PlatformStopCharging.class));
+ }
+
+ @Test
+ void rejectsRepeatedStopOperation() {
+ TCardChargingOrder order = order(4);
+ when(mapper.selectById(ORDER_ID)).thenReturn(order);
+ mockAuthorizedLogin();
+
+ AjaxResult result = service.stopCharging(String.valueOf(ORDER_ID), GUN_ID);
+
+ assertEquals(Integer.valueOf(500), result.get("code"));
+ assertEquals("不能重复操作", result.get("msg"));
+ verify(sendMessageClient, never()).platformStopCharging(any(PlatformStopCharging.class));
+ }
+
+ // ===== 四、设备与并发 =====
+
+ @Test
+ void rejectsWhenDeviceMissing() {
+ TCardChargingOrder order = order(3);
+ when(mapper.selectById(ORDER_ID)).thenReturn(order);
+ mockAuthorizedLogin();
+
+ AjaxResult result = service.stopCharging(String.valueOf(ORDER_ID), GUN_ID);
+
+ assertEquals(Integer.valueOf(500), result.get("code"));
+ assertEquals("未找到订单对应的充电设备", result.get("msg"));
+ verify(sendMessageClient, never()).platformStopCharging(any(PlatformStopCharging.class));
+ }
+
+ @Test
+ void rejectsWhenStatusChangedConcurrently() {
+ lenient().when(mapper.update(any(), any())).thenReturn(0);
+ TCardChargingOrder order = order(3);
+ when(mapper.selectById(ORDER_ID)).thenReturn(order);
+ mockAuthorizedLogin();
+ mockDevice(order);
+
+ AjaxResult result = service.stopCharging(String.valueOf(ORDER_ID), GUN_ID);
+
+ assertEquals(Integer.valueOf(500), result.get("code"));
+ assertEquals("订单状态已变化,请刷新后重试", result.get("msg"));
+ verify(sendMessageClient, never()).platformStopCharging(any(PlatformStopCharging.class));
+ }
+
+ // ===== 辅助方法 =====
+
+ private TCardChargingOrder order(Integer status) {
+ TCardChargingOrder order = new TCardChargingOrder();
+ order.setId(ORDER_ID);
+ order.setCode("SD202610140001");
+ order.setSiteId(AUTHORIZED_SITE_ID);
+ order.setChargingGunId(GUN_ID);
+ order.setChargingPileId(PILE_ID);
+ order.setStatus(status);
+ return order;
+ }
+
+ private void mockAuthorizedLogin() {
+ LoginUser loginUser = new LoginUser();
+ loginUser.setUserid(LOGIN_USER_ID);
+ lenient().when(tokenService.getLoginUser()).thenReturn(loginUser);
+ GetSiteListDTO site = new GetSiteListDTO();
+ site.setId(AUTHORIZED_SITE_ID);
+ lenient().when(siteClient.getSiteListByUserId(LOGIN_USER_ID))
+ .thenReturn(R.ok(Collections.singletonList(site)));
+ }
+
+ private void mockDevice(TCardChargingOrder order) {
+ TChargingGun gun = new TChargingGun();
+ gun.setId(order.getChargingGunId());
+ gun.setCode("GUN-" + order.getChargingGunId());
+ lenient().when(chargingGunClient.getChargingGunById(order.getChargingGunId())).thenReturn(R.ok(gun));
+ TChargingPile pile = new TChargingPile();
+ pile.setId(order.getChargingPileId());
+ pile.setCode("PILE-" + order.getChargingPileId());
+ lenient().when(chargingPileClient.getChargingPileById(order.getChargingPileId())).thenReturn(R.ok(pile));
+ }
+}
--
Gitblit v1.7.1