puzhibing
20 小时以前 c825a6f7e52ce1d61b21c352861c1da082a9fcb6
修改小程序bug
1个文件已添加
5个文件已修改
354 ■■■■■ 已修改文件
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TVinChargingOrderService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TChargingOrderServiceImplVinFallbackTest.java 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TVinChargingOrderAppletServiceTest.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
@@ -1912,6 +1912,18 @@
    })
    public AjaxResult stopCharging(@PathVariable String id) {
        log.info("小程序主动发起停止充电,订单ID:{}", id);
        // VIN 码充电订单路由:VD 订单存于 t_vin_charging_order,不在 t_charging_order;
        // VD 表存在该 ID 订单时优先走 VIN 停充(内部按状态返回准确的“不能重复操作/还未开始充电”),
        // 其余情况回落普通停充。雪花 ID 两表同源生成,撞号概率可忽略
        TVinChargingOrder vinOrder = null;
        try {
            vinOrder = vinChargingOrderService.getById(Long.valueOf(id));
        } catch (NumberFormatException ignored) {
            // 非数字订单ID,按普通停充处理
        }
        if (vinOrder != null) {
            return vinChargingOrderService.stopChargingByApplet(id);
        }
        return chargingOrderService.stopCharging(id);
    }
    
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TVinChargingOrderService.java
@@ -37,6 +37,13 @@
    AjaxResult stopCharging(String chargingOrderId, Integer chargingGunId);
    /**
     * 小程序端停止VIN码充电订单(正在充电界面调用):
     * 复用 getAuthorizedAppletOrder 鉴权(VIN当前归属+下单用户一致),仅状态3允许停止,
     * 置状态4/结束方式1/结束时间并下发平台停机指令
     */
    AjaxResult stopChargingByApplet(String chargingOrderId);
    List<Map<String, Object>> accountingStrategyList(Long chargingOrderId);
    // ===== 设备侧VIN码充电链路(V0.2 VIN码充电) =====
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -56,6 +56,7 @@
import com.ruoyi.order.mapper.TChargingOrderMapper;
import com.ruoyi.order.mapper.TChargingOrderShareRecordMapper;
import com.ruoyi.order.mapper.TSettlementConfirmMapper;
import com.ruoyi.order.mapper.TVinChargingOrderMapper;
import com.ruoyi.order.service.TChargingOrderAccountingStrategyService;
import com.ruoyi.order.service.TChargingOrderRefundService;
import com.ruoyi.order.service.TChargingOrderService;
@@ -251,6 +252,9 @@
    @Resource
    private UploadRealTimeMonitoringDataService uploadRealTimeMonitoringDataService;
    @Resource
    private TVinChargingOrderMapper vinChargingOrderMapper;
    @Resource
    private TCECClient tcecClient;
@@ -2767,7 +2771,19 @@
                .orderByDesc(TChargingOrder::getCreateTime)
                .last(" limit 0, 1"));
        if (null == one || !one.getAppUserId().equals(userId)) {
            // VIN 码充电订单兜底:VD 订单存于 t_vin_charging_order,且后付模式无支付动作,
            // 不按 rechargePaymentStatus 过滤;TVinChargingOrder 继承 TChargingOrder,后续组装原样复用
            TVinChargingOrder vinOrder = vinChargingOrderMapper.selectOne(new LambdaQueryWrapper<TVinChargingOrder>()
                    .eq(TVinChargingOrder::getAppUserId, userId)
                    .eq(TVinChargingOrder::getChargingGunId, id)
                    .in(TVinChargingOrder::getStatus, Arrays.asList(2, 3, 4))
                    .eq(TVinChargingOrder::getDelFlag, 0)
                    .orderByDesc(TVinChargingOrder::getCreateTime)
                    .last(" limit 0, 1"));
            if (null == vinOrder) {
            return null;
            }
            one = vinOrder;
        }
        ChargingDetails chargingDetails = new ChargingDetails();
        chargingDetails.setId(one.getId().toString());
@@ -8058,6 +8074,14 @@
    public EndOfChargePageInfo getEndOfChargePageInfo(String id) {
        EndOfChargePageInfo info = new EndOfChargePageInfo();
        TChargingOrder chargingOrder = this.getById(id);
        if (chargingOrder == null) {
            // VIN 码充电订单兜底:VD 订单存于 t_vin_charging_order(继承 TChargingOrder,字段兼容;
            // 结算已写 paymentAmount/startTime/endTime,后续组装原样复用)
            chargingOrder = vinChargingOrderMapper.selectById(id);
        }
        if (chargingOrder == null) {
            throw new RuntimeException("充电订单不存在");
        }
        Long userId = tokenService.getLoginUserApplet().getUserId();
        if (!chargingOrder.getAppUserId().equals(userId)) {
            throw new RuntimeException("权限不足");
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java
@@ -432,6 +432,42 @@
    }
    @Override
    public AjaxResult stopChargingByApplet(String chargingOrderId) {
        Long orderId;
        try {
            orderId = Long.valueOf(chargingOrderId);
        } catch (NumberFormatException e) {
            return AjaxResult.error("VIN码充电订单ID无效");
        }
        TVinChargingOrder order = getAuthorizedAppletOrder(orderId);
        if (order == null) {
            return AjaxResult.error("VIN码充电订单不存在或无权操作");
        }
        Integer status = order.getStatus();
        if (status == null || !status.equals(3)) {
            return AjaxResult.error(status != null && (status.equals(4) || status.equals(5))
                    ? "不能重复操作" : "还未开始充电");
        }
        TChargingGun chargingGun = chargingGunClient.getChargingGunById(order.getChargingGunId()).getData();
        TChargingPile chargingPile = order.getChargingPileId() == null ? null
                : chargingPileClient.getChargingPileById(order.getChargingPileId()).getData();
        if (chargingGun == null || chargingPile == null) {
            return AjaxResult.error("未找到订单对应的充电设备");
        }
        order.setStatus(4);
        order.setEndMode(1);
        order.setEndTime(LocalDateTime.now());
        if (!updateById(order)) {
            return AjaxResult.error("更新VIN码充电订单状态失败");
        }
        PlatformStopCharging command = new PlatformStopCharging();
        command.setCharging_pile_code(chargingPile.getCode());
        command.setCharging_gun_code(chargingGun.getCode());
        sendMessageClient.platformStopCharging(command);
        return AjaxResult.success();
    }
    @Override
    public List<Map<String, Object>> accountingStrategyList(Long chargingOrderId) {
        TVinChargingOrder order = getAuthorizedOrder(chargingOrderId);
        return order == null ? Collections.emptyList() : baseMapper.accountingStrategyList(chargingOrderId);
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TChargingOrderServiceImplVinFallbackTest.java
New file
@@ -0,0 +1,194 @@
package com.ruoyi.order.service.impl;
import com.ruoyi.account.api.feignClient.AppUserIntegralChangeClient;
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.Site;
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.security.service.TokenService;
import com.ruoyi.integration.api.model.UploadRealTimeMonitoringData;
import com.ruoyi.order.api.model.TChargingOrder;
import com.ruoyi.order.api.model.TVinChargingOrder;
import com.ruoyi.order.dto.ChargingDetails;
import com.ruoyi.order.mapper.TChargingOrderMapper;
import com.ruoyi.order.mapper.TVinChargingOrderMapper;
import com.ruoyi.order.util.mongodb.service.UploadRealTimeMonitoringDataService;
import com.ruoyi.order.vo.EndOfChargePageInfo;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.ruoyi.system.api.model.LoginUserApplet;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
/**
 * TChargingOrderServiceImpl 小程序接口对 VIN 码充电订单(t_vin_charging_order)的兜底逻辑测试:
 * getChargingDetails(正在充电详情)、getEndOfChargePageInfo(结束页详情)。
 */
@ExtendWith(MockitoExtension.class)
class TChargingOrderServiceImplVinFallbackTest {
    @InjectMocks
    private TChargingOrderServiceImpl service;
    @Mock
    private TChargingOrderMapper mapper;
    @Mock
    private TVinChargingOrderMapper vinChargingOrderMapper;
    @Mock
    private TokenService tokenService;
    @Mock
    private ChargingGunClient chargingGunClient;
    @Mock
    private ChargingPileClient chargingPileClient;
    @Mock
    private SiteClient siteClient;
    @Mock
    private UploadRealTimeMonitoringDataService uploadRealTimeMonitoringDataService;
    @Mock
    private AppUserIntegralChangeClient appUserIntegralChangeClient;
    /**
     * 纯单测无 Spring 容器,MyBatis-Plus 的 lambda 列缓存需手工初始化,
     * 否则 LambdaQueryWrapper 解析 getter 引用时抛 "can not find lambda cache"
     */
    @BeforeAll
    static void initMybatisPlusTableInfo() {
        MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
        TableInfoHelper.initTableInfo(assistant, TChargingOrder.class);
        TableInfoHelper.initTableInfo(assistant, TVinChargingOrder.class);
    }
    @BeforeEach
    void setUp() {
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
    }
    @Test
    void getChargingDetailsFallsBackToVinOrderAndAssemblesRealTimeData() {
        LoginUserApplet loginUser = new LoginUserApplet();
        loginUser.setUserId(88L);
        when(tokenService.getLoginUserApplet()).thenReturn(loginUser);
        // 普通订单表查不到
        when(mapper.selectOne(any())).thenReturn(null);
        TVinChargingOrder vinOrder = new TVinChargingOrder();
        vinOrder.setId(1001L);
        vinOrder.setAppUserId(88L);
        vinOrder.setChargingGunId(5);
        vinOrder.setStatus(3);
        vinOrder.setCode("VD20261013001");
        vinOrder.setResidualAmount(new BigDecimal("66.60"));
        when(vinChargingOrderMapper.selectOne(any())).thenReturn(vinOrder);
        TChargingGun gun = new TChargingGun();
        gun.setChargingPileId(7);
        when(chargingGunClient.getChargingGunById(5)).thenReturn(R.ok(gun));
        TChargingPile pile = new TChargingPile();
        pile.setName("1号桩");
        pile.setSiteId(9);
        when(chargingPileClient.getChargingPileById(7)).thenReturn(R.ok(pile));
        Site site = new Site();
        site.setName("城东站");
        when(siteClient.getSiteByIds(Arrays.asList(9))).thenReturn(R.ok(Collections.singletonList(site)));
        UploadRealTimeMonitoringData data = new UploadRealTimeMonitoringData();
        data.setOutput_current(new BigDecimal("10"));
        data.setOutput_voltage(new BigDecimal("220"));
        data.setSoc(50);
        data.setTime_remaining(30);
        data.setCharging_degree(new BigDecimal("5.5"));
        data.setCumulative_charging_time(75);
        when(uploadRealTimeMonitoringDataService.getLastDataById("VD20261013001")).thenReturn(data);
        ChargingDetails details = service.getChargingDetails(5);
        assertEquals("1001", details.getId());
        assertEquals("城东站-1号桩", details.getName());
        assertEquals("VD20261013001", details.getCode());
        assertEquals(Integer.valueOf(3), details.getStatus());
        assertEquals(0, new BigDecimal("66.60").compareTo(details.getChargingCost()));
        assertEquals(0, new BigDecimal("10").compareTo(details.getChargeCurrent()));
        assertEquals(0, new BigDecimal("220").compareTo(details.getChargeVoltage()));
        assertEquals(0, new BigDecimal("2.200").compareTo(details.getChargePower()));
        assertEquals(Integer.valueOf(50), details.getCompletionRatio());
        assertEquals(Integer.valueOf(30), details.getRemainingChargeTime());
        assertEquals(0, new BigDecimal("5.5").compareTo(details.getChargedDegrees()));
        assertEquals("01:15", details.getChargedTime());
    }
    @Test
    void getChargingDetailsReturnsNullWhenNeitherNormalNorVinOrderExists() {
        LoginUserApplet loginUser = new LoginUserApplet();
        loginUser.setUserId(88L);
        when(tokenService.getLoginUserApplet()).thenReturn(loginUser);
        when(mapper.selectOne(any())).thenReturn(null);
        when(vinChargingOrderMapper.selectOne(any())).thenReturn(null);
        assertNull(service.getChargingDetails(5));
    }
    @Test
    void getEndOfChargePageInfoFallsBackToVinOrder() {
        LoginUserApplet loginUser = new LoginUserApplet();
        loginUser.setUserId(88L);
        when(tokenService.getLoginUserApplet()).thenReturn(loginUser);
        when(mapper.selectById("1001")).thenReturn(null);
        TVinChargingOrder vinOrder = new TVinChargingOrder();
        vinOrder.setId(1001L);
        vinOrder.setAppUserId(88L);
        vinOrder.setCode("VD20261013001");
        vinOrder.setPaymentAmount(new BigDecimal("18.50"));
        vinOrder.setRechargeAmount(new BigDecimal("50.00"));
        vinOrder.setRefundAmount(new BigDecimal("31.50"));
        vinOrder.setStartTime(LocalDateTime.of(2026, 10, 13, 10, 0));
        vinOrder.setEndTime(LocalDateTime.of(2026, 10, 13, 11, 30));
        when(vinChargingOrderMapper.selectById("1001")).thenReturn(vinOrder);
        when(appUserIntegralChangeClient.getTAppUserIntegralChangeByOrderCode("VD20261013001"))
                .thenReturn(R.ok(null));
        EndOfChargePageInfo info = service.getEndOfChargePageInfo("1001");
        assertEquals("1001", info.getId());
        assertEquals("VD20261013001", info.getCode());
        assertEquals(0, new BigDecimal("18.50").compareTo(info.getPaymentAmount()));
        assertEquals(0, new BigDecimal("50.00").compareTo(info.getRechargeAmount()));
        assertEquals(0, new BigDecimal("31.50").compareTo(info.getRefundAmount()));
        assertEquals(Integer.valueOf(0), info.getIntegral());
        assertEquals("10:00", info.getStartHour());
        assertEquals("11:30", info.getEndHour());
        assertEquals("1小时30分钟", info.getDuration());
    }
    @Test
    void getEndOfChargePageInfoThrowsWhenOrderMissingInBothTables() {
        // 两表均查不到订单时在获取登录用户之前即抛异常,tokenService 无需 stub
        when(mapper.selectById("1001")).thenReturn(null);
        when(vinChargingOrderMapper.selectById("1001")).thenReturn(null);
        RuntimeException exception = null;
        try {
            service.getEndOfChargePageInfo("1001");
        } catch (RuntimeException ex) {
            exception = ex;
        }
        assertNotNull(exception);
        assertEquals("充电订单不存在", exception.getMessage());
    }
}
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TVinChargingOrderAppletServiceTest.java
@@ -2,9 +2,15 @@
import com.ruoyi.account.api.feignClient.VinManagementClient;
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.core.web.page.PageInfo;
import com.ruoyi.integration.api.feignClient.SendMessageClient;
import com.ruoyi.integration.api.model.PlatformStopCharging;
import com.ruoyi.system.api.model.LoginUserApplet;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.order.api.model.TVinChargingOrder;
@@ -25,7 +31,10 @@
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -48,6 +57,10 @@
    private SiteClient siteClient;
    @Mock
    private ChargingGunClient chargingGunClient;
    @Mock
    private ChargingPileClient chargingPileClient;
    @Mock
    private SendMessageClient sendMessageClient;
    @Mock
    private UploadRealTimeMonitoringDataService monitoringDataService;
@@ -149,4 +162,72 @@
        assertTrue(service.appletDetail(1001L).isEmpty());
        verify(mapper, never()).accountingStrategyList(1001L);
    }
    @Test
    void stopChargingByAppletStopsChargingOrderForBoundVinOwner() {
        LoginUserApplet loginUser = new LoginUserApplet();
        loginUser.setUserId(88L);
        when(tokenService.getLoginUserApplet()).thenReturn(loginUser);
        when(vinManagementClient.getVinIdsByAppUserId(88L)).thenReturn(R.ok(Collections.singletonList(11L)));
        TVinChargingOrder order = new TVinChargingOrder();
        order.setId(1001L);
        order.setVinId(11L);
        order.setAppUserId(88L);
        order.setStatus(3);
        order.setChargingGunId(5);
        order.setChargingPileId(7);
        when(mapper.selectById(1001L)).thenReturn(order);
        TChargingGun gun = new TChargingGun();
        gun.setCode("GUN_5");
        when(chargingGunClient.getChargingGunById(5)).thenReturn(R.ok(gun));
        TChargingPile pile = new TChargingPile();
        pile.setCode("PILE_7");
        when(chargingPileClient.getChargingPileById(7)).thenReturn(R.ok(pile));
        when(mapper.updateById(order)).thenReturn(1);
        AjaxResult result = service.stopChargingByApplet("1001");
        assertTrue(result.isSuccess());
        assertEquals(4, order.getStatus());
        assertEquals(1, order.getEndMode());
        assertNotNull(order.getEndTime());
        verify(sendMessageClient).platformStopCharging(any(PlatformStopCharging.class));
    }
    @Test
    void stopChargingByAppletRejectsWhenVinNotBoundToCurrentUser() {
        LoginUserApplet loginUser = new LoginUserApplet();
        loginUser.setUserId(88L);
        when(tokenService.getLoginUserApplet()).thenReturn(loginUser);
        when(vinManagementClient.getVinIdsByAppUserId(88L)).thenReturn(R.ok(Collections.emptyList()));
        TVinChargingOrder order = new TVinChargingOrder();
        order.setId(1001L);
        order.setVinId(11L);
        when(mapper.selectById(1001L)).thenReturn(order);
        AjaxResult result = service.stopChargingByApplet("1001");
        assertFalse(result.isSuccess());
        verify(sendMessageClient, never()).platformStopCharging(any(PlatformStopCharging.class));
        verify(mapper, never()).updateById(any(TVinChargingOrder.class));
    }
    @Test
    void stopChargingByAppletRejectsFinishedOrder() {
        LoginUserApplet loginUser = new LoginUserApplet();
        loginUser.setUserId(88L);
        when(tokenService.getLoginUserApplet()).thenReturn(loginUser);
        when(vinManagementClient.getVinIdsByAppUserId(88L)).thenReturn(R.ok(Collections.singletonList(11L)));
        TVinChargingOrder order = new TVinChargingOrder();
        order.setId(1001L);
        order.setVinId(11L);
        order.setAppUserId(88L);
        order.setStatus(5);
        when(mapper.selectById(1001L)).thenReturn(order);
        AjaxResult result = service.stopChargingByApplet("1001");
        assertFalse(result.isSuccess());
        verify(sendMessageClient, never()).platformStopCharging(any(PlatformStopCharging.class));
    }
}