From a1e0a09320776a566b2c73aa85192979ee72bb59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E4=B8=99?= <10604541+sanbing-os@user.noreply.gitee.com> Date: Sat, 27 Sep 2025 18:04:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9F=A5=E8=AF=A2=E5=85=85?= =?UTF-8?q?=E7=94=B5=E6=9E=AA=E7=8A=B6=E6=80=81=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 + .../src/main/resources/app-service.yml | 7 + .../app/service/impl/DefaultGunServiceIT.java | 157 +++++++ .../resources/app-service-test.properties | 2 + .../app/adapter/controller/GunController.java | 54 +++ .../adapter/controller/PileController.java | 38 ++ .../app/adapter/controller/RpcController.java | 196 +++++++++ .../adapter/controller/TestController.java | 59 ++- .../jcpp/app/adapter/dto/RestartPileDTO.java | 32 ++ .../jcpp/app/adapter/dto/SetPricingDTO.java | 33 ++ .../jcpp/app/adapter/dto/StartChargeDTO.java | 61 +++ .../jcpp/app/adapter/dto/StopChargeDTO.java | 31 ++ .../jcpp/app/adapter/dto/TimeSyncDTO.java | 34 ++ .../jcpp/app/adapter/request/RpcRequest.java | 51 +++ .../app/adapter/response/ApiResponse.java | 8 +- .../response/PileWithStatusResponse.java | 5 + .../jcpp/app/dal/config/DalConfig.java | 13 +- .../jcpp/app/dal/mapper/GunMapper.java | 15 +- .../app/dal/repository/GunRepository.java | 17 +- .../repository/impl/GunRepositoryImpl.java | 45 +- .../repository/impl/PileRepositoryImpl.java | 6 +- .../sanbing/jcpp/app/service/GunService.java | 19 +- .../jcpp/app/service/PileProtocolService.java | 22 +- .../sanbing/jcpp/app/service/PileService.java | 5 + .../service/cache/gun/GunCacheEvictEvent.java | 1 + .../app/service/cache/gun/GunCacheKey.java | 20 +- .../app/service/impl/DefaultGunService.java | 31 +- .../impl/DefaultPileProtocolService.java | 75 ++-- .../app/service/impl/DefaultPileService.java | 5 + .../src/main/resources/mapper/GunMapper.xml | 59 ++- .../src/main/resources/mapper/PileMapper.xml | 74 ++-- .../src/main/resources/sql/schema-init.sql | 10 +- jcpp-infrastructure-cache/pom.xml | 1 + jcpp-infrastructure-proto/pom.xml | 1 + .../src/main/proto/downlink.proto | 8 +- .../src/main/proto/uplink.proto | 26 +- jcpp-infrastructure-stats/pom.xml | 1 + .../adapter/DownlinkControllerIT.java | 2 +- .../v340/cmd/LvnengV340RealTimeDataULCmd.java | 4 +- .../cmd/LvnengV340RemoteStopAckULCmd.java | 2 +- .../v340/cmd/LvnengV340RemoteStopDLCmd.java | 2 +- .../cmd/LvnengV340TransactionRecordULCmd.java | 2 +- jcpp-protocol-yunkuaichong/READMD.md | 24 +- .../cmd/YunKuaiChongV150BmsAbortULCmd.java | 2 +- ...YunKuaiChongV150BmsChargingErrorULCmd.java | 2 +- .../YunKuaiChongV150BmsChargingInfoULCmd.java | 2 +- ...iChongV150BmsDemandChargerOutputULCmd.java | 2 +- .../YunKuaiChongV150BmsHandshakeULCmd.java | 2 +- ...uaiChongV150BmsParamConfigReportULCmd.java | 2 +- .../cmd/YunKuaiChongV150LockStatusULCmd.java | 2 +- ...0OfflineCardBalanceUpdateRequestDLCmd.java | 3 +- .../YunKuaiChongV150RealTimeDataULCmd.java | 4 +- .../cmd/YunKuaiChongV150RemoteStartDLCmd.java | 2 +- ...unKuaiChongV150RemoteStartResultULCmd.java | 2 +- .../cmd/YunKuaiChongV150RemoteStopDLCmd.java | 2 +- ...YunKuaiChongV150RemoteStopResultULCmd.java | 2 +- .../YunKuaiChongV150StartChargeAckDLCmd.java | 2 +- .../cmd/YunKuaiChongV150StartChargeULCmd.java | 2 +- ...unKuaiChongV150TransactionRecordULCmd.java | 2 +- ...KuaiChongV160RemoteParallelStartDLCmd.java | 2 +- ...ongV160RemoteParallelStartResultULCmd.java | 2 +- ...unKuaiChongV170TransactionRecordULCmd.java | 2 +- jcpp-web-ui/package-lock.json | 24 +- jcpp-web-ui/package.json | 2 +- jcpp-web-ui/pom.xml | 1 + jcpp-web-ui/public/index.html | 1 + jcpp-web-ui/public/robots.txt | 7 - jcpp-web-ui/src/App.tsx | 8 + jcpp-web-ui/src/components/GunDebug.tsx | 404 ++++++++++++++++++ jcpp-web-ui/src/components/GunManagement.tsx | 154 ++++++- jcpp-web-ui/src/components/PileManagement.tsx | 35 ++ jcpp-web-ui/src/services/gunService.ts | 5 + jcpp-web-ui/src/types/index.ts | 1 + pom.xml | 41 +- 74 files changed, 1727 insertions(+), 259 deletions(-) create mode 100644 jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/service/impl/DefaultGunServiceIT.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/RpcController.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/RestartPileDTO.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/SetPricingDTO.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/StartChargeDTO.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/StopChargeDTO.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/TimeSyncDTO.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/RpcRequest.java create mode 100644 jcpp-web-ui/src/components/GunDebug.tsx diff --git a/.gitignore b/.gitignore index c7b1a41..890fca9 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,7 @@ build/ ### VS Code ### .vscode/ + + +### Cursor ### +.cursor/ \ No newline at end of file diff --git a/jcpp-app-bootstrap/src/main/resources/app-service.yml b/jcpp-app-bootstrap/src/main/resources/app-service.yml index 863af8d..e705d63 100644 --- a/jcpp-app-bootstrap/src/main/resources/app-service.yml +++ b/jcpp-app-bootstrap/src/main/resources/app-service.yml @@ -160,6 +160,13 @@ queue: enabled: "${QUEUE_APP_STATS_ENABLED:true}" print-interval-ms: "${QUEUE_APP_STATS_PRINT_INTERVAL_MS:60000}" timer-top-n: "${QUEUE_APP_STATS_TIMER_TOP_N:5}" +# rpc: +# topic: "${QUEUE_RPC_TOPIC:app_rpc}" +# poll-interval: "${QUEUE_RPC_POLL_INTERVAL_MS:5}" +# pack-processing-timeout: "${QUEUE_RPC_PACK_PROCESSING_TIMEOUT_MS:2000}" +# consumer-per-partition: "${QUEUE_RPC_CONSUMER_PER_PARTITION:true}" +# partitions: "${QUEUE_RPC_PARTITIONS:10}" +# decoder: "${QUEUE_RPC_DECODER:protobuf}" # 应用程序缓存配置 cache: diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/service/impl/DefaultGunServiceIT.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/service/impl/DefaultGunServiceIT.java new file mode 100644 index 0000000..6dec1eb --- /dev/null +++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/service/impl/DefaultGunServiceIT.java @@ -0,0 +1,157 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.impl; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import sanbing.jcpp.AbstractTestBase; +import sanbing.jcpp.app.dal.entity.Gun; +import sanbing.jcpp.app.service.GunService; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * DefaultGunService 集成测试 + * 专门测试 findByPileCodeAndGunNo 方法的数据库连接和查询功能 + * + * @author 九筒 + */ +class DefaultGunServiceIT extends AbstractTestBase { + + @Autowired + private GunService gunService; + + /** + * 测试根据充电桩编码和枪编号查询充电枪 + * 这个测试专门验证之前出现的数据库连接中断问题是否已解决 + */ + @Test + public void testFindByPileCodeAndGunNo_Success() { + log.info("开始测试 findByPileCodeAndGunNo 方法"); + + // 使用测试数据中存在的充电桩编码和枪编号 + String pileCode = "20231212000028"; + String gunNo = "2"; + + try { + // 执行查询 + Gun result = gunService.findByPileCodeAndGunNo(pileCode, gunNo); + + // 验证结果 + if (result != null) { + log.info("查询成功 - 充电枪信息: ID={}, 名称={}, 编码={}, 枪号={}", + result.getId(), result.getGunName(), result.getGunCode(), result.getGunNo()); + + // 验证查询结果的正确性 + assertEquals(gunNo, result.getGunNo(), "枪编号应该匹配"); + assertNotNull(result.getGunCode(), "充电枪编码不应为空"); + assertNotNull(result.getGunName(), "充电枪名称不应为空"); + + log.info("✅ 测试通过:成功查询到充电枪数据"); + } else { + log.warn("⚠️ 查询结果为空,可能是测试数据不存在"); + // 不抛出异常,因为数据为空不代表连接有问题 + } + + } catch (Exception e) { + log.error("❌ 测试失败:查询过程中出现异常", e); + fail("查询充电枪时出现异常: " + e.getMessage()); + } + } + + /** + * 测试查询不存在的充电枪 + * 验证异常处理是否正常 + */ + @Test + public void testFindByPileCodeAndGunNo_NotFound() { + log.info("开始测试查询不存在的充电枪"); + + String pileCode = "NONEXISTENT"; + String gunNo = "999"; + + try { + Gun result = gunService.findByPileCodeAndGunNo(pileCode, gunNo); + + // 查询不存在的数据应该返回 null + assertNull(result, "查询不存在的充电枪应该返回 null"); + log.info("✅ 测试通过:正确处理了不存在的数据查询"); + + } catch (Exception e) { + log.error("❌ 测试失败:查询不存在数据时出现异常", e); + fail("查询不存在数据时不应该抛出异常: " + e.getMessage()); + } + } + + /** + * 测试参数验证 + * 验证空参数的处理 + */ + @Test + public void testFindByPileCodeAndGunNo_InvalidParameters() { + log.info("开始测试参数验证"); + + // 测试空的 pileCode + assertThrows(IllegalArgumentException.class, () -> { + gunService.findByPileCodeAndGunNo(null, "1"); + }, "空的 pileCode 应该抛出 IllegalArgumentException"); + + // 测试空的 gunNo + assertThrows(IllegalArgumentException.class, () -> { + gunService.findByPileCodeAndGunNo("20231212000028", null); + }, "空的 gunNo 应该抛出 IllegalArgumentException"); + + // 测试空字符串 + assertThrows(IllegalArgumentException.class, () -> { + gunService.findByPileCodeAndGunNo("", "1"); + }, "空字符串的 pileCode 应该抛出 IllegalArgumentException"); + + assertThrows(IllegalArgumentException.class, () -> { + gunService.findByPileCodeAndGunNo("20231212000028", ""); + }, "空字符串的 gunNo 应该抛出 IllegalArgumentException"); + + log.info("✅ 测试通过:参数验证正常工作"); + } + + /** + * 压力测试 - 多次连续查询 + * 验证连接池和缓存是否正常工作 + */ + @Test + public void testFindByPileCodeAndGunNo_StressTest() { + log.info("开始压力测试 - 连续查询100次"); + + String pileCode = "20231212000028"; + String gunNo = "2"; + + int successCount = 0; + int totalQueries = 100; + + for (int i = 0; i < totalQueries; i++) { + try { + Gun result = gunService.findByPileCodeAndGunNo(pileCode, gunNo); + successCount++; + + if (i % 20 == 0) { + log.info("已完成 {} 次查询", i + 1); + } + + } catch (Exception e) { + log.error("第 {} 次查询失败", i + 1, e); + } + } + + log.info("压力测试完成:{}/{} 次查询成功", successCount, totalQueries); + + // 至少95%的查询应该成功 + assertTrue(successCount >= totalQueries * 0.95, + String.format("成功率过低:%d/%d (%.2f%%)", successCount, totalQueries, + (double) successCount / totalQueries * 100)); + + log.info("✅ 压力测试通过:连接池和缓存工作正常"); + } +} diff --git a/jcpp-app-bootstrap/src/test/resources/app-service-test.properties b/jcpp-app-bootstrap/src/test/resources/app-service-test.properties index 3964ff8..e829d26 100644 --- a/jcpp-app-bootstrap/src/test/resources/app-service-test.properties +++ b/jcpp-app-bootstrap/src/test/resources/app-service-test.properties @@ -1,3 +1,5 @@ service.protocols.yunkuaichongV150.listener.tcp.bind-port=0 service.protocols.yunkuaichongV160.listener.tcp.bind-port=0 +service.protocols.yunkuaichongV170.listener.tcp.bind-port=0 +service.protocols.lvnengV340.listener.tcp.bind-port=0 service.protocol.rpc.port=0 \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/GunController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/GunController.java index 0a47932..5afea43 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/GunController.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/GunController.java @@ -6,8 +6,10 @@ */ package sanbing.jcpp.app.adapter.controller; +import com.google.common.util.concurrent.ListenableFuture; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import sanbing.jcpp.app.adapter.request.GunCreateRequest; @@ -17,16 +19,22 @@ import sanbing.jcpp.app.adapter.response.ApiResponse; import sanbing.jcpp.app.adapter.response.GunWithStatusResponse; import sanbing.jcpp.app.adapter.response.PageResponse; import sanbing.jcpp.app.dal.entity.Gun; +import sanbing.jcpp.app.data.kv.AttrKeyEnum; +import sanbing.jcpp.app.data.kv.AttributeKvEntry; +import sanbing.jcpp.app.service.AttributeService; import sanbing.jcpp.app.service.GunService; +import java.util.Optional; import java.util.UUID; @RestController @RequestMapping("/api/guns") @RequiredArgsConstructor +@Slf4j public class GunController extends BaseController { private final GunService gunService; + private final AttributeService attributeService; @PostMapping public ResponseEntity> createGun(@Valid @RequestBody GunCreateRequest request) { @@ -58,4 +66,50 @@ public class GunController extends BaseController { PageResponse guns = gunService.queryGunsWithStatus(request); return ResponseEntity.ok(ApiResponse.success("查询成功", guns)); } + + /** + * 根据枪编号获取充电枪运行状态 + */ + @GetMapping("/status/{gunCode}") + public ResponseEntity> getGunStatusByGunCode(@PathVariable String gunCode) { + try { + // 首先根据枪编号查找充电枪 + Gun gun = gunService.findByGunCode(gunCode); + if (gun == null) { + return ResponseEntity.ok(ApiResponse.error("充电枪不存在", null)); + } + + // 通过AttributeService获取充电枪运行状态 + ListenableFuture> attributeFuture = + attributeService.find(gun.getId(), AttrKeyEnum.GUN_RUN_STATUS.getCode()); + + Optional attributeResult = attributeFuture.get(); + String status = null; + if (attributeResult.isPresent()) { + AttributeKvEntry entry = attributeResult.get(); + status = entry.getStrValue().orElse(null); + } + + return ResponseEntity.ok(ApiResponse.success("查询成功", status)); + } catch (Exception e) { + return ResponseEntity.ok(ApiResponse.error("查询失败: " + e.getMessage(), null)); + } + } + + /** + * 根据充电枪编码获取充电枪详细信息 + */ + @GetMapping("/code/{gunCode}") + public ResponseEntity> getGunByCode(@PathVariable String gunCode) { + try { + GunWithStatusResponse gun = gunService.findGunWithStatusByCode(gunCode); + if (gun == null) { + return ResponseEntity.ok(ApiResponse.error("充电枪不存在", null)); + } + return ResponseEntity.ok(ApiResponse.success("查询成功", gun)); + } catch (Exception e) { + return ResponseEntity.ok(ApiResponse.error("查询失败: " + e.getMessage(), null)); + } + } + } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/PileController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/PileController.java index 2f202b4..af14c2c 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/PileController.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/PileController.java @@ -6,8 +6,10 @@ */ package sanbing.jcpp.app.adapter.controller; +import com.google.common.util.concurrent.ListenableFuture; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import sanbing.jcpp.app.adapter.request.PileCreateRequest; @@ -18,18 +20,24 @@ import sanbing.jcpp.app.adapter.response.PageResponse; import sanbing.jcpp.app.adapter.response.PileOptionResponse; import sanbing.jcpp.app.adapter.response.PileWithStatusResponse; import sanbing.jcpp.app.dal.entity.Pile; +import sanbing.jcpp.app.data.kv.AttrKeyEnum; +import sanbing.jcpp.app.data.kv.AttributeKvEntry; import sanbing.jcpp.app.exception.JCPPException; +import sanbing.jcpp.app.service.AttributeService; import sanbing.jcpp.app.service.PileService; import java.util.List; +import java.util.Optional; import java.util.UUID; @RestController @RequestMapping("/api/piles") @RequiredArgsConstructor +@Slf4j public class PileController extends BaseController { private final PileService pileService; + private final AttributeService attributeService; @PostMapping public ResponseEntity> createPile(@Valid @RequestBody PileCreateRequest request) { @@ -70,4 +78,34 @@ public class PileController extends BaseController { List options = pileService.getPileOptions(); return ResponseEntity.ok(ApiResponse.success("查询成功", options)); } + + /** + * 根据桩编号获取充电桩状态 + */ + @GetMapping("/status/{pileCode}") + public ResponseEntity> getPileStatusByPileCode(@PathVariable String pileCode) { + try { + // 首先根据桩编号查找充电桩 + Pile pile = pileService.findByPileCode(pileCode); + if (pile == null) { + return ResponseEntity.ok(ApiResponse.error("充电桩不存在", null)); + } + + // 通过AttributeService获取充电桩状态 + ListenableFuture> attributeFuture = + attributeService.find(pile.getId(), AttrKeyEnum.STATUS.getCode()); + + Optional attributeResult = attributeFuture.get(); + String status = null; + if (attributeResult.isPresent()) { + AttributeKvEntry entry = attributeResult.get(); + status = entry.getStrValue().orElse(null); + } + + return ResponseEntity.ok(ApiResponse.success("查询成功", status)); + } catch (Exception e) { + return ResponseEntity.ok(ApiResponse.error("查询失败: " + e.getMessage(), null)); + } + } + } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/RpcController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/RpcController.java new file mode 100644 index 0000000..cd47e96 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/RpcController.java @@ -0,0 +1,196 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.controller; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import sanbing.jcpp.app.adapter.dto.*; +import sanbing.jcpp.app.adapter.request.RpcRequest; +import sanbing.jcpp.app.adapter.response.ApiResponse; +import sanbing.jcpp.app.service.PileProtocolService; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; +import sanbing.jcpp.proto.gen.DownlinkProto.*; + +/** + * RPC控制器 - 通用化的充电桩下行指令接口 + * + * @author 九筒 + */ +@RestController +@RequestMapping("/api/rpc") +@RequiredArgsConstructor +@Slf4j +public class RpcController extends BaseController { + + private final PileProtocolService pileProtocolService; + + /** + * 单向RPC接口 - 不等待充电桩返回消息 + */ + @PostMapping("/oneway") + public ResponseEntity> onewayRpc(@RequestBody RpcRequest request) { + try { + log.info("收到单向RPC请求: method={}, parameter={}", request.getMethod(), request.getParameter()); + + // 根据method调用对应的服务方法 + executeRpcMethod(request.getMethod(), request.getParameter()); + + return ResponseEntity.ok(ApiResponse.success("指令发送成功", null)); + } catch (Exception e) { + log.error("单向RPC调用失败: method={}, error={}", request.getMethod(), e.getMessage(), e); + return ResponseEntity.ok(ApiResponse.error("指令发送失败: " + e.getMessage(), null)); + } + } + + /** + * 双向RPC接口 - 等待充电桩返回消息(带超时) + * TODO: 实现双向RPC逻辑,包括超时处理 + */ + @PostMapping("/bidirectional") + public ResponseEntity> bidirectionalRpc(@RequestBody RpcRequest request) { + // TODO: 实现双向RPC,需要等待充电桩响应,包含超时时间参数 + return ResponseEntity.ok(ApiResponse.error("双向RPC功能待实现", null)); + } + + /** + * 执行RPC方法 + */ + private void executeRpcMethod(String method, JsonNode parameter) throws Exception { + switch (method) { + case "startCharge": + handleStartCharge(parameter); + break; + case "stopCharge": + handleStopCharge(parameter); + break; + case "restartPile": + handleRestartPile(parameter); + break; + case "setPricing": + handleSetPricing(parameter); + break; + case "setQrcode": + handleSetQrcode(parameter); + break; + case "otaRequest": + handleOtaRequest(parameter); + break; + case "offlineCardBalanceUpdate": + handleOfflineCardBalanceUpdate(parameter); + break; + case "offlineCardSync": + handleOfflineCardSync(parameter); + break; + case "offlineCardClear": + handleOfflineCardClear(parameter); + break; + case "offlineCardQuery": + handleOfflineCardQuery(parameter); + break; + case "timeSync": + handleTimeSync(parameter); + break; + default: + throw new IllegalArgumentException("不支持的RPC方法: " + method); + } + } + + /** + * 处理启动充电指令 + */ + private void handleStartCharge(JsonNode parameter) { + StartChargeDTO startChargeDto = JacksonUtil.fromJson(parameter, StartChargeDTO.class); + pileProtocolService.startCharge(startChargeDto); + } + + /** + * 处理停止充电指令 + */ + private void handleStopCharge(JsonNode parameter) { + StopChargeDTO stopChargeDto = JacksonUtil.fromJson(parameter, StopChargeDTO.class); + pileProtocolService.stopCharge(stopChargeDto); + } + + /** + * 处理重启充电桩指令 + */ + private void handleRestartPile(JsonNode parameter) { + RestartPileDTO restartPileDto = JacksonUtil.fromJson(parameter, RestartPileDTO.class); + pileProtocolService.restartPile(restartPileDto); + } + + /** + * 处理设置计费策略指令 + */ + private void handleSetPricing(JsonNode parameter) { + SetPricingDTO setPricingDto = JacksonUtil.fromJson(parameter, SetPricingDTO.class); + pileProtocolService.setPricing(setPricingDto); + } + + /** + * 处理设置二维码指令 + */ + private void handleSetQrcode(JsonNode parameter) { + SetQrcodeRequest setQrcodeRequest = JacksonUtil.fromJson(parameter, SetQrcodeRequest.class); + pileProtocolService.setQrcode(setQrcodeRequest); + } + + /** + * 处理OTA升级指令 + */ + private void handleOtaRequest(JsonNode parameter) { + OtaRequest otaRequest = JacksonUtil.fromJson(parameter, OtaRequest.class); + pileProtocolService.otaRequest(otaRequest); + } + + /** + * 处理离线卡余额更新指令 + */ + private void handleOfflineCardBalanceUpdate(JsonNode parameter) { + OfflineCardBalanceUpdateRequest request = JacksonUtil.fromJson(parameter, OfflineCardBalanceUpdateRequest.class); + pileProtocolService.offlineCardBalanceUpdateRequest(request); + } + + /** + * 处理离线卡同步指令 + */ + private void handleOfflineCardSync(JsonNode parameter) { + OfflineCardSyncRequest request = JacksonUtil.fromJson(parameter, OfflineCardSyncRequest.class); + pileProtocolService.offlineCardSyncRequest(request); + } + + /** + * 处理离线卡清除指令 + */ + private void handleOfflineCardClear(JsonNode parameter) { + OfflineCardClearRequest request = JacksonUtil.fromJson(parameter, OfflineCardClearRequest.class); + pileProtocolService.offlineCardClearRequest(request); + } + + /** + * 处理离线卡查询指令 + */ + private void handleOfflineCardQuery(JsonNode parameter) { + OfflineCardQueryRequest request = JacksonUtil.fromJson(parameter, OfflineCardQueryRequest.class); + pileProtocolService.offlineCardQueryRequest(request); + } + + /** + * 处理时间同步指令 + */ + private void handleTimeSync(JsonNode parameter) { + TimeSyncDTO timeSyncDto = JacksonUtil.fromJson(parameter, TimeSyncDTO.class); + pileProtocolService.timeSync(timeSyncDto); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/TestController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/TestController.java index 1302fdd..1b29e9e 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/TestController.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/TestController.java @@ -13,6 +13,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import sanbing.jcpp.app.adapter.dto.*; import sanbing.jcpp.app.service.PileProtocolService; import sanbing.jcpp.proto.gen.DownlinkProto.*; @@ -39,8 +40,16 @@ public class TestController extends BaseController { String logicalCardNo = RandomStringUtils.secure().nextNumeric(12); String physicalCardNo = RandomStringUtils.secure().nextNumeric(12); - pileProtocolService.startCharge("20231212000010", "01", new BigDecimal("50"), orderNo, - logicalCardNo, physicalCardNo, null); + StartChargeDTO startChargeDto = new StartChargeDTO(); + startChargeDto.setPileCode("20231212000010"); + startChargeDto.setGunNo("01"); + startChargeDto.setLimitYuan(new BigDecimal("50")); + startChargeDto.setOrderNo(orderNo); + startChargeDto.setLogicalCardNo(logicalCardNo); + startChargeDto.setPhysicalCardNo(physicalCardNo); + startChargeDto.setParallelNo(null); + + pileProtocolService.startCharge(startChargeDto); return ResponseEntity.ok("success"); } @@ -53,8 +62,16 @@ public class TestController extends BaseController { String physicalCardNo = RandomStringUtils.secure().nextNumeric(12); String parallelNo = RandomStringUtils.secure().nextNumeric(6); - pileProtocolService.startCharge("20231212000010", "01", new BigDecimal("100"), - orderNo, logicalCardNo, physicalCardNo, parallelNo); + StartChargeDTO startChargeDto = new StartChargeDTO(); + startChargeDto.setPileCode("20231212000010"); + startChargeDto.setGunNo("01"); + startChargeDto.setLimitYuan(new BigDecimal("100")); + startChargeDto.setOrderNo(orderNo); + startChargeDto.setLogicalCardNo(logicalCardNo); + startChargeDto.setPhysicalCardNo(physicalCardNo); + startChargeDto.setParallelNo(parallelNo); + + pileProtocolService.startCharge(startChargeDto); return ResponseEntity.ok("success"); } @@ -62,7 +79,11 @@ public class TestController extends BaseController { @GetMapping("/stopCharge") public ResponseEntity stopCharge() { - pileProtocolService.stopCharge("20231212000010", "01"); + StopChargeDTO stopChargeDto = new StopChargeDTO(); + stopChargeDto.setPileCode("20231212000010"); + stopChargeDto.setGunNo("01"); + + pileProtocolService.stopCharge(stopChargeDto); return ResponseEntity.ok("success"); } @@ -85,7 +106,11 @@ public class TestController extends BaseController { @GetMapping("/restartPile") public ResponseEntity restartPile() { - pileProtocolService.restartPile("20231212000010", 1); + RestartPileDTO restartPileDto = new RestartPileDTO(); + restartPileDto.setPileCode("20231212000010"); + restartPileDto.setType(1); + + pileProtocolService.restartPile(restartPileDto); return ResponseEntity.ok("success"); } @@ -195,13 +220,16 @@ public class TestController extends BaseController { .setPeakValleyPricing(peakValleyPricing) // 设置峰谷计价配置 .build(); - pileProtocolService.setPricing(pileCode, - SetPricingRequest.newBuilder() + SetPricingDTO setPricingDto = new SetPricingDTO(); + setPricingDto.setPileCode(pileCode); + setPricingDto.setSetPricingRequest(SetPricingRequest.newBuilder() .setPileCode(pileCode) .setPricingId(1000L) .setPricingModel(pricingModel) .build()); + pileProtocolService.setPricing(setPricingDto); + return ResponseEntity.ok("success"); } @@ -274,13 +302,16 @@ public class TestController extends BaseController { .setTimePeriodPricing(timePeriodPricing) // 设置时段计价配置 .build(); - pileProtocolService.setPricing(pileCode, - SetPricingRequest.newBuilder() + SetPricingDTO setPricingDto = new SetPricingDTO(); + setPricingDto.setPileCode(pileCode); + setPricingDto.setSetPricingRequest(SetPricingRequest.newBuilder() .setPileCode(pileCode) .setPricingId(2000L) .setPricingModel(pricingModel) .build()); + pileProtocolService.setPricing(setPricingDto); + return ResponseEntity.ok("Time period pricing test success"); } @@ -309,7 +340,7 @@ public class TestController extends BaseController { pileProtocolService.offlineCardBalanceUpdateRequest(OfflineCardBalanceUpdateRequest.newBuilder() .setCardNo("1000000000123456") .setPileCode("20231212000010") - .setGunCode("01") + .setGunNo("01") .setLimitYuan("1000") .build()); @@ -334,7 +365,11 @@ public class TestController extends BaseController { @GetMapping("/timeSync") public ResponseEntity timeSync() { - pileProtocolService.timeSync("20231212000010", LocalDateTime.now()); + TimeSyncDTO timeSyncDto = new TimeSyncDTO(); + timeSyncDto.setPileCode("20231212000010"); + timeSyncDto.setTime(LocalDateTime.now()); + + pileProtocolService.timeSync(timeSyncDto); return ResponseEntity.ok("success"); } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/RestartPileDTO.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/RestartPileDTO.java new file mode 100644 index 0000000..092cea9 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/RestartPileDTO.java @@ -0,0 +1,32 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 重启充电桩DTO + * + * @author 九筒 + */ +@Data +public class RestartPileDTO { + + /** + * 充电桩编码 + */ + @NotBlank(message = "充电桩编码不能为空") + private String pileCode; + + /** + * 重启类型 + */ + @NotNull(message = "重启类型不能为空") + private Integer type; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/SetPricingDTO.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/SetPricingDTO.java new file mode 100644 index 0000000..85036cf --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/SetPricingDTO.java @@ -0,0 +1,33 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import sanbing.jcpp.proto.gen.DownlinkProto.SetPricingRequest; + +/** + * 设置计费策略DTO + * + * @author 九筒 + */ +@Data +public class SetPricingDTO { + + /** + * 充电桩编码 + */ + @NotBlank(message = "充电桩编码不能为空") + private String pileCode; + + /** + * 计费策略请求 + */ + @NotNull(message = "计费策略请求不能为空") + private SetPricingRequest setPricingRequest; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/StartChargeDTO.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/StartChargeDTO.java new file mode 100644 index 0000000..74dc36b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/StartChargeDTO.java @@ -0,0 +1,61 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 启动充电DTO + * + * @author 九筒 + */ +@Data +public class StartChargeDTO { + + /** + * 充电桩编码 + */ + @NotBlank(message = "充电桩编码不能为空") + private String pileCode; + + /** + * 充电枪编号 + */ + @NotBlank(message = "充电枪编号不能为空") + private String gunNo; + + /** + * 限制金额(元) + */ + @NotNull(message = "限制金额不能为空") + private BigDecimal limitYuan; + + /** + * 订单号 + */ + @NotBlank(message = "订单号不能为空") + private String orderNo; + + /** + * 逻辑卡号 + */ + private String logicalCardNo; + + /** + * 物理卡号 + */ + private String physicalCardNo; + + /** + * 并充序号(当不为空时,自动使用并充启机命令) + */ + private String parallelNo; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/StopChargeDTO.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/StopChargeDTO.java new file mode 100644 index 0000000..5bb9830 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/StopChargeDTO.java @@ -0,0 +1,31 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 停止充电DTO + * + * @author 九筒 + */ +@Data +public class StopChargeDTO { + + /** + * 充电桩编码 + */ + @NotBlank(message = "充电桩编码不能为空") + private String pileCode; + + /** + * 充电枪编号 + */ + @NotBlank(message = "充电枪编号不能为空") + private String gunNo; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/TimeSyncDTO.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/TimeSyncDTO.java new file mode 100644 index 0000000..b1750a5 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/dto/TimeSyncDTO.java @@ -0,0 +1,34 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 时间同步DTO + * + * @author 九筒 + */ +@Data +public class TimeSyncDTO { + + /** + * 充电桩编码 + */ + @NotBlank(message = "充电桩编码不能为空") + private String pileCode; + + /** + * 同步时间 + */ + @NotNull(message = "同步时间不能为空") + private LocalDateTime time; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/RpcRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/RpcRequest.java new file mode 100644 index 0000000..52476b1 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/RpcRequest.java @@ -0,0 +1,51 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * RPC请求参数 + * + * @author 九筒 + */ +@Data +public class RpcRequest { + + /** + * RPC方法名 + * 支持的方法包括: + * - startCharge: 启动充电 + * - stopCharge: 停止充电 + * - restartPile: 重启充电桩 + * - setPricing: 设置计费策略 + * - setQrcode: 设置二维码 + * - otaRequest: OTA升级 + * - offlineCardBalanceUpdate: 离线卡余额更新 + * - offlineCardSync: 离线卡同步 + * - offlineCardClear: 离线卡清除 + * - offlineCardQuery: 离线卡查询 + * - timeSync: 时间同步 + */ + @NotBlank(message = "方法名不能为空") + private String method; + + /** + * 方法参数,JSON格式 + * 不同的方法需要不同的参数结构 + */ + @NotNull(message = "参数不能为空") + private JsonNode parameter; + + /** + * 超时时间(毫秒),仅用于双向RPC + */ + private Long timeoutMs; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ApiResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ApiResponse.java index dcd9237..56c214d 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ApiResponse.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ApiResponse.java @@ -21,7 +21,8 @@ import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor public class ApiResponse { - + + private boolean success; private String errorCode; private String message; private T data; @@ -29,6 +30,7 @@ public class ApiResponse { public static ApiResponse success(T data) { return ApiResponse.builder() + .success(true) .message("操作成功") .data(data) .timestamp(System.currentTimeMillis()) @@ -37,6 +39,7 @@ public class ApiResponse { public static ApiResponse success(String message, T data) { return ApiResponse.builder() + .success(true) .message(message) .data(data) .timestamp(System.currentTimeMillis()) @@ -45,6 +48,7 @@ public class ApiResponse { public static ApiResponse error(String errorCode, String message) { return ApiResponse.builder() + .success(false) .errorCode(errorCode) .message(message) .timestamp(System.currentTimeMillis()) @@ -53,6 +57,7 @@ public class ApiResponse { public static ApiResponse error(ErrorCode errorCode, String message) { return ApiResponse.builder() + .success(false) .errorCode(errorCode.getCode()) .message(message) .timestamp(System.currentTimeMillis()) @@ -61,6 +66,7 @@ public class ApiResponse { public static ApiResponse error(ErrorCode errorCode) { return ApiResponse.builder() + .success(false) .errorCode(errorCode.getCode()) .message(errorCode.getMessage()) .timestamp(System.currentTimeMillis()) diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileWithStatusResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileWithStatusResponse.java index 6a93d8a..e7aed13 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileWithStatusResponse.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileWithStatusResponse.java @@ -108,4 +108,9 @@ public class PileWithStatusResponse { * 最后活跃时间(13位时间戳) */ private Long lastActiveTime; + + /** + * 充电枪数量 + */ + private Long gunCount; } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/DalConfig.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/DalConfig.java index 961463c..403d9d3 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/DalConfig.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/DalConfig.java @@ -22,12 +22,15 @@ import org.springframework.context.annotation.Primary; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.support.TransactionTemplate; import javax.sql.DataSource; @Configuration @EnableAutoConfiguration(exclude = {RedisAutoConfiguration.class}) +@EnableTransactionManagement @MapperScan({"sanbing.jcpp.app.dal.mapper"}) public class DalConfig { @@ -44,13 +47,11 @@ public class DalConfig { return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); } - @Primary @Bean public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } - @Primary @Bean public NamedParameterJdbcTemplate namedParameterJdbcTemplate(@Qualifier("dataSource") DataSource dataSource) { return new NamedParameterJdbcTemplate(dataSource); @@ -58,7 +59,12 @@ public class DalConfig { @Primary @Bean - public TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager) { + public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + public TransactionTemplate transactionTemplate(@Qualifier("transactionManager") PlatformTransactionManager transactionManager) { TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); transactionTemplate.setIsolationLevel(TransactionTemplate.ISOLATION_READ_COMMITTED); transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED); @@ -66,7 +72,6 @@ public class DalConfig { } @Bean - @Primary public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/GunMapper.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/GunMapper.java index c925933..c1723f5 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/GunMapper.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/GunMapper.java @@ -20,11 +20,20 @@ import java.util.UUID; * @author 九筒 */ public interface GunMapper extends BaseMapper { - + + /** - * 根据充电桩编码和充电枪编码查询充电枪 + * 根据充电桩编码和充电枪编号查询充电枪 + * 充电桩上报的是 pile_code + gun_no 的组合,这个组合是唯一的 */ - Gun selectByPileCodeAndGunCode(@Param("pileCode") String pileCode, @Param("gunCode") String gunCode); + Gun selectByPileCodeAndGunNo(@Param("pileCode") String pileCode, @Param("gunNo") String gunNo); + + /** + * 根据枪编号查询充电枪 + */ + Gun selectByGunCode(@Param("gunCode") String gunCode); + + GunWithStatusResponse selectGunWithStatusByCode(@Param("gunCode") String gunCode); /** * 分页查询充电枪及其状态信息 diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/GunRepository.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/GunRepository.java index 0b5003c..52d81b9 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/GunRepository.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/GunRepository.java @@ -6,6 +6,7 @@ */ package sanbing.jcpp.app.dal.repository; +import sanbing.jcpp.app.adapter.response.GunWithStatusResponse; import sanbing.jcpp.app.dal.entity.Gun; import java.util.UUID; @@ -17,14 +18,26 @@ import java.util.UUID; */ public interface GunRepository { + /** - * 根据充电桩编码和充电枪编码查询充电枪 + * 根据充电桩编码和充电枪编号查询充电枪 + * 充电桩上报的是 pile_code + gun_no 的组合,这个组合是唯一的 * * @param pileCode 充电桩编码 + * @param gunNo 充电枪编号 (如: "01", "02") + * @return 充电枪实体,如果不存在返回null + */ + Gun findByPileCodeAndGunNo(String pileCode, String gunNo); + + /** + * 根据枪编号查询充电枪 + * * @param gunCode 充电枪编码 * @return 充电枪实体,如果不存在返回null */ - Gun findByPileCodeAndGunCode(String pileCode, String gunCode); + Gun findByGunCode(String gunCode); + + GunWithStatusResponse findGunWithStatusByCode(String gunCode); /** * 根据充电枪ID查询充电枪 diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/GunRepositoryImpl.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/GunRepositoryImpl.java index 5e581e1..36bcc93 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/GunRepositoryImpl.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/GunRepositoryImpl.java @@ -6,10 +6,11 @@ */ package sanbing.jcpp.app.dal.repository.impl; -import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import org.springframework.transaction.event.TransactionalEventListener; +import sanbing.jcpp.app.adapter.response.GunWithStatusResponse; import sanbing.jcpp.app.dal.entity.Gun; import sanbing.jcpp.app.dal.mapper.GunMapper; import sanbing.jcpp.app.dal.repository.GunRepository; @@ -30,10 +31,10 @@ import static sanbing.jcpp.infrastructure.util.validation.Validator.validateStri */ @Repository @Slf4j +@RequiredArgsConstructor public class GunRepositoryImpl extends CachedVersionedEntityRepository implements GunRepository { - @Resource - GunMapper gunMapper; + private final GunMapper gunMapper; @TransactionalEventListener(classes = GunCacheEvictEvent.class) @Override @@ -45,22 +46,36 @@ public class GunRepositoryImpl extends CachedVersionedEntityRepository "无效的桩编号: " + pileCode); + validateString(gunNo, no -> "无效的枪编号: " + gunNo); + + return cache.get(new GunCacheKey(pileCode, gunNo), + () -> gunMapper.selectByPileCodeAndGunNo(pileCode, gunNo)); + } + + @Override + public Gun findByGunCode(String gunCode) { validateString(gunCode, code -> "无效的枪编号: " + gunCode); - - return cache.get(new GunCacheKey(pileCode, gunCode), - () -> gunMapper.selectByPileCodeAndGunCode(pileCode, gunCode)); + + return cache.get(new GunCacheKey(gunCode), + () -> gunMapper.selectByGunCode(gunCode)); } @Override @@ -70,4 +85,12 @@ public class GunRepositoryImpl extends CachedVersionedEntityRepository gunMapper.selectById(gunId)); } + + @Override + public GunWithStatusResponse findGunWithStatusByCode(String gunCode) { + validateString(gunCode, code -> "无效的枪编号: " + gunCode); + + // 这个方法不使用缓存,因为它包含状态信息,需要实时查询 + return gunMapper.selectGunWithStatusByCode(gunCode); + } } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java index c41b51b..a4d9c2e 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java @@ -6,7 +6,7 @@ */ package sanbing.jcpp.app.dal.repository.impl; -import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import org.springframework.transaction.event.TransactionalEventListener; @@ -26,10 +26,10 @@ import static sanbing.jcpp.infrastructure.util.validation.Validator.validateStri */ @Repository @Slf4j +@RequiredArgsConstructor public class PileRepositoryImpl extends CachedVersionedEntityRepository implements PileRepository { - @Resource - PileMapper pileMapper; + private final PileMapper pileMapper; @TransactionalEventListener(classes = PileCacheEvictEvent.class) @Override diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/GunService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/GunService.java index e9abade..c5c0c11 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/GunService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/GunService.java @@ -42,11 +42,20 @@ public interface GunService { * 分页查询充电枪及状态信息 */ PageResponse queryGunsWithStatus(GunQueryRequest request); - + + /** - * 根据充电桩编码和充电枪编码查询充电枪 + * 根据充电桩编码和充电枪编号查询充电枪 + * 充电桩上报的是 pile_code + gun_no 的组合,这个组合是唯一的 */ - Gun findByPileCodeAndGunCode(String pileCode, String gunCode); + Gun findByPileCodeAndGunNo(String pileCode, String gunNo); + + /** + * 根据枪编号查询充电枪 + */ + Gun findByGunCode(String gunCode); + + GunWithStatusResponse findGunWithStatusByCode(String gunCode); /** * 查询充电枪状态 @@ -69,11 +78,11 @@ public interface GunService { * 处理充电枪状态上报 * * @param pileCode 充电桩编码 - * @param gunCode 充电枪编码 + * @param gunNo 充电枪编号 (如: "01", "02") * @param protoStatus Proto状态 * @param ts 时间戳 * @return 是否需要更新充电桩状态 */ - boolean handleGunRunStatus(String pileCode, String gunCode, GunRunStatus protoStatus, long ts); + boolean handleGunRunStatus(String pileCode, String gunNo, GunRunStatus protoStatus, long ts); } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java index c9df200..3a18ff8 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java @@ -6,17 +6,14 @@ */ package sanbing.jcpp.app.service; +import sanbing.jcpp.app.adapter.dto.*; import sanbing.jcpp.infrastructure.queue.Callback; import sanbing.jcpp.proto.gen.DownlinkProto; import sanbing.jcpp.proto.gen.DownlinkProto.OfflineCardBalanceUpdateRequest; import sanbing.jcpp.proto.gen.DownlinkProto.OfflineCardSyncRequest; import sanbing.jcpp.proto.gen.DownlinkProto.OtaRequest; -import sanbing.jcpp.proto.gen.DownlinkProto.SetPricingRequest; -import sanbing.jcpp.proto.gen.UplinkProto.UplinkQueueMessage; import sanbing.jcpp.proto.gen.DownlinkProto.SetQrcodeRequest; - -import java.math.BigDecimal; -import java.time.LocalDateTime; +import sanbing.jcpp.proto.gen.UplinkProto.UplinkQueueMessage; /** * @author 九筒 @@ -87,23 +84,22 @@ public interface PileProtocolService { * 启动充电(支持卡号和并充序号) * 当 parallelNo 不为空时,自动使用并充启机命令 */ - void startCharge(String pileCode, String gunCode, BigDecimal limitYuan, String orderNo, - String logicalCardNo, String physicalCardNo, String parallelNo); + void startCharge(StartChargeDTO startChargeDto); /** * 停止充电 */ - void stopCharge(String pileCode, String gunCode); + void stopCharge(StopChargeDTO stopChargeDto); /** - * 重启充电 + * 重启充电桩 */ - void restartPile(String pileCode, Integer type); + void restartPile(RestartPileDTO restartPileDto); /** - * 下发计费 + * 下发计费策略 */ - void setPricing(String pileCode, SetPricingRequest setPricingRequest); + void setPricing(SetPricingDTO setPricingDto); /** * 充电桩与 BMS 充电错误上报 @@ -179,7 +175,7 @@ public interface PileProtocolService { /** * 实时同步桩时间 */ - void timeSync(String pileCode, LocalDateTime time); + void timeSync(TimeSyncDTO timeSyncDto); /** * 实时同步桩时间应答 diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileService.java index 5e704d4..fa1632e 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileService.java @@ -32,6 +32,11 @@ public interface PileService { * 根据ID查询充电桩 */ Pile findById(UUID id); + + /** + * 根据桩编号查询充电桩 + */ + Pile findByPileCode(String pileCode); /** * 更新充电桩 diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheEvictEvent.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheEvictEvent.java index 44b6372..d2107f4 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheEvictEvent.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheEvictEvent.java @@ -15,5 +15,6 @@ public class GunCacheEvictEvent { private UUID gunId; private String pileCode; + private String gunNo; private String gunCode; } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheKey.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheKey.java index 188cf9a..19be905 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheKey.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheKey.java @@ -13,27 +13,33 @@ import java.io.Serial; import java.util.UUID; @Builder -public record GunCacheKey(UUID gunId, String pileCode, String gunCode) implements VersionedCacheKey { +public record GunCacheKey(UUID gunId, String pileCode, String gunNo, String gunCode) implements VersionedCacheKey { @Serial private static final long serialVersionUID = 1L; public GunCacheKey(UUID gunId) { - this(gunId, null, null); + this(gunId, null, null, null); } - public GunCacheKey(String pileCode, String gunCode) { - this(null, pileCode, gunCode); + public GunCacheKey(String pileCode, String gunNo) { + this(null, pileCode, gunNo, null); + } + + public GunCacheKey(String gunCode) { + this(null, null, null, gunCode); } @Override public String toString() { if (gunId != null) { return gunId.toString(); - } else if (pileCode != null && gunCode != null) { - return pileCode + ":" + gunCode; + } else if (pileCode != null && gunNo != null) { + return pileCode + ":" + gunNo; + } else if (gunCode != null) { + return "gunCode:" + gunCode; } else { - throw new IllegalStateException("GunCacheKey 必须包含有效的 gunId 或者 pileCode+gunCode 组合"); + throw new IllegalStateException("GunCacheKey 必须包含有效的 gunId、pileCode+gunNo 组合或者 gunCode"); } } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultGunService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultGunService.java index d7c241d..91ab343 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultGunService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultGunService.java @@ -126,9 +126,20 @@ public class DefaultGunService implements GunService { .build(); } + @Override - public Gun findByPileCodeAndGunCode(String pileCode, String gunCode) { - return gunRepository.findByPileCodeAndGunCode(pileCode, gunCode); + public Gun findByPileCodeAndGunNo(String pileCode, String gunNo) { + return gunRepository.findByPileCodeAndGunNo(pileCode, gunNo); + } + + @Override + public Gun findByGunCode(String gunCode) { + return gunRepository.findByGunCode(gunCode); + } + + @Override + public GunWithStatusResponse findGunWithStatusByCode(String gunCode) { + return gunRepository.findGunWithStatusByCode(gunCode); } @Override @@ -167,33 +178,33 @@ public class DefaultGunService implements GunService { } @Override - public boolean handleGunRunStatus(String pileCode, String gunCode, GunRunStatus protoStatus, long ts) { - log.info("处理充电枪状态上报: 桩编码={}, 枪编码={}, 状态={}", pileCode, gunCode, protoStatus); + public boolean handleGunRunStatus(String pileCode, String gunNo, GunRunStatus protoStatus, long ts) { + log.info("处理充电枪状态上报: 桩编码={}, 枪编号={}, 状态={}", pileCode, gunNo, protoStatus); // 将Proto状态转换为数据库枚举 GunRunStatusEnum dbStatus = convertProtoStatusToDbStatus(protoStatus); if (dbStatus != null) { // 获取充电枪信息(使用缓存) - Gun gun = findByPileCodeAndGunCode(pileCode, gunCode); + Gun gun = findByPileCodeAndGunNo(pileCode, gunNo); if (gun != null) { // 检查状态是否真的发生了变化,避免重复保存 String currentStatus = findGunStatus(gun.getId()); if (dbStatus.name().equals(currentStatus)) { - log.debug("充电枪状态未发生变化,跳过更新: 桩编码={}, 枪编码={}, 状态={}", pileCode, gunCode, dbStatus); + log.debug("充电枪状态未发生变化,跳过更新: 桩编码={}, 枪编号={}, 状态={}", pileCode, gunNo, dbStatus); return false; } // 保存充电枪状态到属性表 saveGunStatusChange(gun.getId(), dbStatus.name(), ts); - - log.info("充电枪状态更新成功: 桩编码={}, 枪编码={}, 原状态={}, 新状态={}", - pileCode, gunCode, currentStatus, dbStatus); + + log.info("充电枪状态更新成功: 桩编码={}, 枪编号={}, 原状态={}, 新状态={}", + pileCode, gunNo, currentStatus, dbStatus); // 根据充电枪状态判断是否需要更新充电桩状态 return shouldUpdatePileStatus(dbStatus); } else { - log.warn("未找到充电枪: 桩编码={}, 枪编码={}", pileCode, gunCode); + log.warn("未找到充电枪: 桩编码={}, 枪编号={}", pileCode, gunNo); } } else { log.warn("未知的充电枪状态: {}, 跳过更新", protoStatus); diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java index 36d6d12..ce7a99b 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java @@ -13,6 +13,7 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.stereotype.Service; +import sanbing.jcpp.app.adapter.dto.*; import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum; import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; import sanbing.jcpp.app.dal.entity.Gun; @@ -309,12 +310,12 @@ public class DefaultPileProtocolService implements PileProtocolService { try { GunRunStatusProto gunRunStatusProto = uplinkQueueMessage.getGunRunStatusProto(); String pileCode = gunRunStatusProto.getPileCode(); - String gunCode = gunRunStatusProto.getGunCode(); + String gunNo = gunRunStatusProto.getGunNo(); long ts = uplinkQueueMessage.getTs(); GunRunStatus protoStatus = gunRunStatusProto.getGunRunStatus(); // 委托给 GunService 处理充电枪状态逻辑 - boolean needUpdatePileStatus = gunService.handleGunRunStatus(pileCode, gunCode, protoStatus, ts); + boolean needUpdatePileStatus = gunService.handleGunRunStatus(pileCode, gunNo, protoStatus, ts); // 如果需要,根据充电枪状态更新充电桩状态 if (needUpdatePileStatus) { @@ -401,7 +402,7 @@ public class DefaultPileProtocolService implements PileProtocolService { StartChargeRequest startChargeRequest = uplinkQueueMessage.getStartChargeRequest(); String pileCode = startChargeRequest.getPileCode(); - String gunCode = startChargeRequest.getGunCode(); + String gunNo = startChargeRequest.getGunNo(); // TODO 处理相关业务逻辑 String orderNo = "ORD" + RandomStringUtils.secure().nextNumeric(20); String logicalCardNo = RandomStringUtils.secure().nextNumeric(12); @@ -413,29 +414,35 @@ public class DefaultPileProtocolService implements PileProtocolService { downlinkMessageBuilder.setStartChargeResponse(StartChargeResponse.newBuilder() .setTradeNo(orderNo) .setPileCode(startChargeRequest.getPileCode()) - .setGunCode(startChargeRequest.getGunCode()) + .setGunNo(startChargeRequest.getGunNo()) .setLogicalCardNo(logicalCardNo) .setLimitYuan("50") .setAuthSuccess(true) .setFailReason(FailReason.ACCOUNT_NOT_ALLOWED_ON_PILE.name()) ); - log.info("业务[充电桩启动充电请求应答] 发送下行消息到充电桩: {}, 充电枪: {}", pileCode, gunCode); + log.info("业务[充电桩启动充电请求应答] 发送下行消息到充电桩: {}, 充电枪: {}", pileCode, gunNo); downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder,pileCode); callback.onSuccess(); } @Override - public void startCharge(String pileCode, String gunCode, BigDecimal limitYuan, String orderNo, - String logicalCardNo, String physicalCardNo, String parallelNo) { + public void startCharge(StartChargeDTO startChargeDto) { + String pileCode = startChargeDto.getPileCode(); + String gunNo = startChargeDto.getGunNo(); + BigDecimal limitYuan = startChargeDto.getLimitYuan(); + String orderNo = startChargeDto.getOrderNo(); + String logicalCardNo = startChargeDto.getLogicalCardNo(); + String physicalCardNo = startChargeDto.getPhysicalCardNo(); + String parallelNo = startChargeDto.getParallelNo(); UUID messageId = UUID.randomUUID(); UUID requestId = UUID.randomUUID(); RemoteStartChargingRequest.Builder requestBuilder = RemoteStartChargingRequest.newBuilder() .setPileCode(pileCode) - .setGunCode(gunCode) + .setGunNo(gunNo) .setLimitYuan(limitYuan.toPlainString()) .setTradeNo(orderNo); @@ -464,12 +471,14 @@ public class DefaultPileProtocolService implements PileProtocolService { .setDownlinkCmd(downlinkCmd.name()) .setRemoteStartChargingRequest(requestBuilder.build()); - log.info("业务[远程启动充电] 发送下行消息到充电桩: {}, 充电枪: {}, 订单号: {}", pileCode, gunCode, orderNo); + log.info("业务[远程启动充电] 发送下行消息到充电桩: {}, 充电枪: {}, 订单号: {}", pileCode, gunNo, orderNo); downlinkCallService.sendDownlinkMessage(downlinkRequestMessageBuilder, pileCode); } @Override - public void stopCharge(String pileCode, String gunCode) { + public void stopCharge(StopChargeDTO stopChargeDto) { + String pileCode = stopChargeDto.getPileCode(); + String gunNo = stopChargeDto.getGunNo(); UUID messageId = UUID.randomUUID(); UUID requestId = UUID.randomUUID(); @@ -483,20 +492,21 @@ public class DefaultPileProtocolService implements PileProtocolService { .setDownlinkCmd(DownlinkCmdEnum.REMOTE_STOP_CHARGING.name()) .setRemoteStopChargingRequest(RemoteStopChargingRequest.newBuilder() .setPileCode(pileCode) - .setGunCode(gunCode) + .setGunNo(gunNo) .build()); - log.info("业务[远程停止充电] 发送下行消息到充电桩: {}, 充电枪: {}", pileCode, gunCode); + log.info("业务[远程停止充电] 发送下行消息到充电桩: {}, 充电枪: {}", pileCode, gunNo); downlinkCallService.sendDownlinkMessage(downlinkRequestMessageBuilder, pileCode); } @Override - public void restartPile(String pileCode, Integer type) { + public void restartPile(RestartPileDTO restartPileDto) { + String pileCode = restartPileDto.getPileCode(); + Integer type = restartPileDto.getType(); UUID messageId = UUID.randomUUID(); UUID requestId = UUID.randomUUID(); - DownlinkRequestMessage.Builder downlinkRequestMessageBuilder = DownlinkRequestMessage.newBuilder() .setMessageIdMSB(messageId.getMostSignificantBits()) .setMessageIdLSB(messageId.getLeastSignificantBits()) @@ -514,7 +524,10 @@ public class DefaultPileProtocolService implements PileProtocolService { } @Override - public void setPricing(String pileCode, SetPricingRequest setPricingRequest) { + public void setPricing(SetPricingDTO setPricingDto) { + String pileCode = setPricingDto.getPileCode(); + SetPricingRequest setPricingRequest = setPricingDto.getSetPricingRequest(); + UUID messageId = UUID.randomUUID(); UUID requestId = UUID.randomUUID(); @@ -557,9 +570,9 @@ public class DefaultPileProtocolService implements PileProtocolService { BmsChargingInfoProto bmsCharingInfoProto = uplinkQueueMessage.getBmsChargingInfoProto(); String tradeNo = bmsCharingInfoProto.getTradeNo(); String pileCode = bmsCharingInfoProto.getPileCode(); - String gunCode = bmsCharingInfoProto.getGunCode(); + String gunNo = bmsCharingInfoProto.getGunNo(); String additionalInfo = bmsCharingInfoProto.getAdditionalInfo(); - log.info("BMS充电信息: 交易流水号: {}, 桩编码: {}, 枪号: {}, 附加信息: {}", tradeNo, pileCode, gunCode, additionalInfo); + log.info("BMS充电信息: 交易流水号: {}, 桩编码: {}, 枪号: {}, 附加信息: {}", tradeNo, pileCode, gunNo, additionalInfo); // TODO 处理相关业务逻辑 callback.onSuccess(); } @@ -609,7 +622,7 @@ public class DefaultPileProtocolService implements PileProtocolService { BmsHandshakeProto bmsHandshakeProto = uplinkQueueMessage.getBmsHandshakeProto(); String tradeNo = bmsHandshakeProto.getTradeNo(); String pileCode = bmsHandshakeProto.getPileCode(); - String gunCode = bmsHandshakeProto.getGunCode(); + String gunNo = bmsHandshakeProto.getGunNo(); String carVinCode = bmsHandshakeProto.getCarVinCode(); String bmsProtocolVersion = bmsHandshakeProto.getBmsProtocolVersion(); int bmsBatteryType = bmsHandshakeProto.getBmsBatteryType(); @@ -617,8 +630,8 @@ public class DefaultPileProtocolService implements PileProtocolService { String additionalInfo = bmsHandshakeProto.getAdditionalInfo(); log.info("BMS充电握手信息: 交易流水号: {}, 桩编码: {}, 枪号: {}, 车辆VIN: {}, BMS协议版本: {}, " + - "电池类型: {}, 电池容量: {}Ah, 附加信息: {}", - tradeNo, pileCode, gunCode, carVinCode, bmsProtocolVersion, + "电池类型: {}, 电池容量: {}Ah, 附加信息: {}", + tradeNo, pileCode, gunNo, carVinCode, bmsProtocolVersion, bmsBatteryType, bmsPowerCapacity, additionalInfo); // TODO 处理相关业务逻辑,比如保存握手信息到数据库 @@ -627,7 +640,10 @@ public class DefaultPileProtocolService implements PileProtocolService { } @Override - public void timeSync(String pileCode, LocalDateTime time) { + public void timeSync(TimeSyncDTO timeSyncDto) { + String pileCode = timeSyncDto.getPileCode(); + LocalDateTime time = timeSyncDto.getTime(); + UUID messageId = UUID.randomUUID(); UUID requestId = UUID.randomUUID(); DownlinkRequestMessage.Builder downlinkRequestMessageBuilder = DownlinkRequestMessage.newBuilder() @@ -689,32 +705,33 @@ public class DefaultPileProtocolService implements PileProtocolService { log.info("接收到地锁状态信息 {}", uplinkQueueMessage); GroundLockStatusProto groundLockStatusProto = uplinkQueueMessage.getGroundLockStatusProto(); String pileCode = groundLockStatusProto.getPileCode(); - String gunCode = groundLockStatusProto.getGunCode(); + String gunNo = groundLockStatusProto.getGunNo(); int lockStatus = groundLockStatusProto.getLockStatus(); int parkStatus = groundLockStatusProto.getParkStatus(); int lockBattery = groundLockStatusProto.getLockBattery(); int alarmStatus = groundLockStatusProto.getAlarmStatus(); log.info("地锁状态信息: 桩编码: {}, 枪号: {}, 车位锁状态: {}, 车位状态: {}, 地锁电量: {}%, 报警状态: {}", - pileCode, gunCode, lockStatus, parkStatus, lockBattery, alarmStatus); + pileCode, gunNo, lockStatus, parkStatus, lockBattery, alarmStatus); try { // 获取时间戳 long ts = uplinkQueueMessage.getTs(); // 获取充电枪信息 - Gun gun = gunService.findByPileCodeAndGunCode(pileCode, gunCode); + // 注意:充电桩上报的gunNo实际上是枪编号(gun_no),不是完整的枪编码(gun_code) + Gun gun = gunService.findByPileCodeAndGunNo(pileCode, gunNo); if (gun != null) { // 保存地锁状态到属性表 saveLockStatusToAttributes(gun.getId(), lockStatus, parkStatus, lockBattery, alarmStatus, ts); log.info("地锁和车位状态已保存: 桩编码={}, 枪编码={}, 地锁状态={}, 车位状态={}", - pileCode, gunCode, lockStatus, parkStatus); + pileCode, gunNo, lockStatus, parkStatus); } else { - log.warn("未找到充电枪,无法保存地锁状态: 桩编码={}, 枪编码={}", pileCode, gunCode); + log.warn("未找到充电枪,无法保存地锁状态: 桩编码={}, 枪编码={}", pileCode, gunNo); } } catch (Exception e) { - log.error("保存地锁状态失败: 桩编码={}, 枪编码={}", pileCode, gunCode, e); + log.error("保存地锁状态失败: 桩编码={}, 枪编码={}", pileCode, gunNo, e); callback.onFailure(e); return; } @@ -855,11 +872,11 @@ public class DefaultPileProtocolService implements PileProtocolService { log.info("接收到充电过程BMS需求与充电机输出信息:{}", uplinkQueueMessage); BmsDemandChargerOutputProto bmsDemandChargerOutputProto = uplinkQueueMessage.getBmsDemandChargerOutputProto(); String pileCode = bmsDemandChargerOutputProto.getPileCode(); - String gunCode = bmsDemandChargerOutputProto.getGunCode(); + String gunNo = bmsDemandChargerOutputProto.getGunNo(); String tradeNo = bmsDemandChargerOutputProto.getTradeNo(); String additionalInfo = bmsDemandChargerOutputProto.getAdditionalInfo(); log.info("充电过程BMS需求与充电机输出信息: 桩编码: {}, 枪号: {}, 交易流水号: {}, 附加信息: {}", - pileCode, gunCode, tradeNo, additionalInfo); + pileCode, gunNo, tradeNo, additionalInfo); // TODO 处理相关业务逻辑 callback.onSuccess(); } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileService.java index f5101c5..eccc391 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileService.java @@ -82,6 +82,11 @@ public class DefaultPileService implements PileService { return pileMapper.selectById(id); } + @Override + public Pile findByPileCode(String pileCode) { + return pileRepository.findPileByCode(pileCode); + } + @Override public Pile updatePile(UUID id, PileUpdateRequest request) throws JCPPException { Pile existingPile = findById(id); diff --git a/jcpp-app/src/main/resources/mapper/GunMapper.xml b/jcpp-app/src/main/resources/mapper/GunMapper.xml index d808ced..c09164b 100644 --- a/jcpp-app/src/main/resources/mapper/GunMapper.xml +++ b/jcpp-app/src/main/resources/mapper/GunMapper.xml @@ -98,11 +98,62 @@ - - SELECT g.* FROM t_gun g - INNER JOIN t_pile p ON g.pile_id = p.id - WHERE p.pile_code = #{pileCode} AND g.gun_code = #{gunCode} + INNER JOIN t_pile p ON g.pile_id = p.id + WHERE p.pile_code = #{pileCode} + AND g.gun_no = #{gunNo} + + + + + + + diff --git a/jcpp-app/src/main/resources/mapper/PileMapper.xml b/jcpp-app/src/main/resources/mapper/PileMapper.xml index c7d5652..560d7bf 100644 --- a/jcpp-app/src/main/resources/mapper/PileMapper.xml +++ b/jcpp-app/src/main/resources/mapper/PileMapper.xml @@ -10,7 +10,7 @@ - +