pom.xml
@@ -247,6 +247,13 @@ <version>${ruoyi.version}</version> </dependency> <!-- 监管接口 --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-api-jianguan</artifactId> <version>${ruoyi.version}</version> </dependency> </dependencies> </dependencyManagement> ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/model/Site.java
@@ -447,6 +447,12 @@ @TableField("supportOrder") @ApiModelProperty(value = "2.0修改字段-是否支持预约 0不支持1支持") private Integer supportOrder; /** * 高速路服务区编号 */ @TableField("serAreaCode") @ApiModelProperty(value = "高速路服务区编号") private String serAreaCode; // /** // * 换电设备信息 // */ ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/elutong/model/ConnectorInfo.java
New file @@ -0,0 +1,51 @@ package com.ruoyi.integration.api.elutong.model; import lombok.Data; @Data public class ConnectorInfo { /** * 充电设备接口编码 * 充电设备接口编码,同一运营商内唯一 */ private String connectorId; /** * 充电设备接口名称 */ private String connectorName; /** * 充电设备接口类型 * 1:家用插座(模式2); * 2:交流接口插座(模式3,连接方式B ); * 3:交流接口插头(带枪线,模式3,连接方式C); * 4:直流接口枪头(带枪线,模式4); * 5:无线充电座; * 6:其他 */ private Integer connectorType; /** * 额定电压上限 */ private Integer voltageUpperLimits; /** * 额定电压下限 */ private Integer voltageLowerLimits; /** * 额定电流 */ private Integer current; /** * 充电设备总功率 */ private Double power; /** * 车位号 */ private String parkNo; /** * 国家标准 * 1:2011;2:2015 */ private Integer nationalStandard; } ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/elutong/model/ConnectorStatusInfo.java
New file @@ -0,0 +1,30 @@ package com.ruoyi.integration.api.elutong.model; import lombok.Data; @Data public class ConnectorStatusInfo { /** * 充电设备接口编码 * 充电设备接口编码,同一运营商内唯一 */ private String connectorId; /** * 充电设备接口状态 * 0:离网; * 1:空闲; * 2:占用(未充电); * 3:占用(充电中); * 4:占用(预约锁定); * 255:故障 */ private Integer status; /** * 电池剩余电量 */ private Double soc; /** * 估算剩余充电时间(min) */ private Integer remainingTime; } ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/elutong/model/ConnectorStatusReq.java
New file @@ -0,0 +1,36 @@ package com.ruoyi.integration.api.elutong.model; import lombok.Data; import java.util.List; @Data public class ConnectorStatusReq { /** * 充电运营商ID * 使用运营商组织机构代码 */ private String operatorId; /** * 服务区编码 * 如上送具体的服务区信息时,该字段必填,否则可为空 */ private String serAreaCode; /** * 充电站ID * 充电站唯一编码 * 运营商未提供:使用服务区编码+充电站(CD)/换电站(HD)+顺序码 * 顺序码规则:两位字符,取值范围01~99 * 运营商提供:使用运营商提供的充电站编码。同一运营商内唯一 */ private String stationId; /** * 总记录条数 * 符合条件的充电站总数,记录数不超过50 */ private Integer itemSize; /** * 充电设备接口状态列表 */ private List<ConnectorStatusInfo> connectorStatusInfos; } ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/elutong/model/EquipmentInfo.java
New file @@ -0,0 +1,61 @@ package com.ruoyi.integration.api.elutong.model; import lombok.Data; import java.util.List; @Data public class EquipmentInfo { /** * 设备编码 * 设备唯一编码,对同一运营商,保证唯一 */ private String equipmentId; /** * 充电设备名称 * 运营商名称简写+设备名称 */ private String equipmentName; /** * 设备生产商组织机构代码 */ private String manufacturerId; /** * 设备生产商名称 */ private String manufacturerName; /** * 设备型号 */ private String equipmentModel; /** * 设备生产日期 * YYYY-MM-DD */ private String productionDate; /** * 设备类型 * 1:直流设备; * 2:交流设备; * 3:交直流一体设备; * 4:无线设备; * 5:其他 */ private Integer equipmentType; /** * 充电设备经度 */ private Double equipmentLng; /** * 充电设备维度 */ private Double equipmentLat; /** * 充电设备总功率 */ private Double power; /** * 充电设备接口信息列表 */ private List<ConnectorInfo> connectorInfos; } ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/elutong/model/OperatorInfo.java
New file @@ -0,0 +1,32 @@ package com.ruoyi.integration.api.elutong.model; import lombok.Data; @Data public class OperatorInfo { /** * 运营商ID * 组织机构代码 */ private String operatorId; /** * 运营商名称 */ private String operatorName; /** * 运营商客服电话1 */ private String operatorTel1; /** * 运营商客服电话2 */ private String operatorTel2; /** * 运营商注册地址 */ private String operatorRegAddress; /** * 备注 */ private String operatorNote; } ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/elutong/model/StationInfo.java
New file @@ -0,0 +1,145 @@ package com.ruoyi.integration.api.elutong.model; import lombok.Data; import java.math.BigDecimal; import java.util.List; @Data public class StationInfo { /** * 服务区编码 * 由部级管理平台统一管理和分配 */ private String serAreaCode; /** * 运营商ID * 组织机构代码 */ private String operatorId; /** * 充/换电站ID * 充电站唯一编码 * 运营商未提供:使用服务区编码+充电站(CD)/换电站(HD)+顺序码 * 顺序码规则:两位字符,取值范围01~99 * 运营商提供:使用运营商提供的充电站编码。同一运营商内唯一 */ private String stationId; /** * 充电站名称 */ private String stationName; /** * 站点模式 * CD:充电站 * HD:换电站 */ private String stationMode; /** * 充电站省市辖区编码 */ private String areaCode; /** * 服务电话 */ private String serviceTel; /** * 站点类型 * 1:公共; * 50:个人; * 100:公交(专用); * 101:环卫(专用); * 102:物流(专用); * 103:出租车(专用); * 255:其他 */ private Integer stationType; /** * 站点状态 * 0:未知; * 1:建设中; * 5:关闭下线; * 6:维护中; * 50:正常使用 */ private Integer stationStatus; /** * 车位数量 */ private Integer parkNums; /** * 经度 */ private Double stationLng; /** * 纬度 */ private Double stationLat; /** * 站点引导 */ private String siteGuide; /** * 建设场所 * 1:居民区; * 2:公共机构; * 3:企事业单位; * 4:写字楼; * 5:工业园区; * 6:交通枢纽; * 7:大型文体设施; * 8:城市绿地; * 9:大型建筑配建停车场; * 10:路边停车位; * 11:城际高速服务区; * 255:其他 */ private Integer construction; /** * 站点照片 */ private List<String> pictures; /** * 使用车型描述 */ private String matchCars; /** * 营业时间 */ private String busineHours; /** * 充电电费率 */ private BigDecimal electricityFee; /** * 服务费率 */ private BigDecimal serviceFee; /** * 停车费 */ private BigDecimal parkFee; /** * 支付方式 * 1:刷卡 * 2:线上 * 3:现金 * 101:ETC支付 * 说明:其中电子钱包类卡为刷卡,身份鉴权卡、微信/支付宝、APP为线上 */ private String payment; /** * 是否支持预约 * 0:否 * 1:是 */ private Integer supportOrder; /** * 备注 */ private String remark; /** * 充电设备信息列表 */ private List<EquipmentInfo> equipmentInfos; } ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/elutong/model/StationInfoReq.java
New file @@ -0,0 +1,40 @@ package com.ruoyi.integration.api.elutong.model; import lombok.Data; import java.util.List; @Data public class StationInfoReq { /** * 充电运营商ID * 使用运营商组织机构代码 */ private String operatorId; /** * 服务区编码 * 如上送具体的服务区信息时,该字段必填,否则可为空 */ private String serAreaCode; /** * 充电站ID * 充电站唯一编码 * 运营商未提供:使用服务区编码+充电站(CD)/换电站(HD)+顺序码 * 顺序码规则:两位字符,取值范围01~99 * 运营商提供:使用运营商提供的充电站编码。同一运营商内唯一 */ private String stationId; /** * 基础设施运营商信息 */ private OperatorInfo operatorInfo; /** * 总记录条数 * 符合条件的充电站总数,记录数不超过50 */ private Integer itemSize; /** * 充电站信息列表 */ private List<StationInfo> stationInfos; } ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/elutong/model/StationStausInfo.java
New file @@ -0,0 +1,18 @@ package com.ruoyi.integration.api.elutong.model; import lombok.Data; import java.util.List; @Data public class StationStausInfo { /** * 充/换电站ID * 使用服务区编码+充电站(CD)/换电站(HD)+顺序码 */ private String stationId; /** * 充电设备接口状态列表 */ private List<ConnectorStatusInfo> connectorStatusInfos; } ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/elutong/model/StationsStatusReq.java
New file @@ -0,0 +1,36 @@ package com.ruoyi.integration.api.elutong.model; import lombok.Data; import java.util.List; @Data public class StationsStatusReq { /** * 充电运营商ID * 使用运营商组织机构代码 */ private String operatorId; /** * 服务区编码 * 如上送具体的服务区信息时,该字段必填,否则可为空 */ private String serAreaCode; /** * 充电站ID * 充电站唯一编码 * 运营商未提供:使用服务区编码+充电站(CD)/换电站(HD)+顺序码 * 顺序码规则:两位字符,取值范围01~99 * 运营商提供:使用运营商提供的充电站编码。同一运营商内唯一 */ private String stationId; /** * 总记录条数 * 符合条件的充电站总数,记录数不超过50 */ private Integer itemSize; /** * 充电站状态信息列表 */ private List<StationStausInfo> stationStausInfos; } ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/factory/ELuTongClientFallbackFactory.java
New file @@ -0,0 +1,32 @@ package com.ruoyi.integration.api.factory; import com.ruoyi.common.core.domain.R; import com.ruoyi.integration.api.elutong.model.ConnectorStatusReq; import com.ruoyi.integration.api.elutong.model.StationInfoReq; import com.ruoyi.integration.api.elutong.model.StationsStatusReq; import com.ruoyi.integration.api.feignClient.ELuTongClient; import org.springframework.cloud.openfeign.FallbackFactory; public class ELuTongClientFallbackFactory implements FallbackFactory<ELuTongClient> { @Override public ELuTongClient create(Throwable cause) { return new ELuTongClient() { @Override public R pushStationInfo(StationInfoReq stationInfoReq) { return R.fail("推送充电站静态信息失败:" + cause.getMessage()); } @Override public R pushStationsStatus(StationsStatusReq stationsStatusReq) { return R.fail("推送充电站状态信息失败:" + cause.getMessage()); } @Override public R pushConnectorStatus(ConnectorStatusReq connectorStatusReq) { return R.fail("推送设备接口状态信息失败:" + cause.getMessage()); } }; } } ruoyi-api/ruoyi-api-jianguan/src/main/java/com/ruoyi/integration/api/feignClient/ELuTongClient.java
New file @@ -0,0 +1,43 @@ package com.ruoyi.integration.api.feignClient; import com.ruoyi.common.core.constant.ServiceNameConstants; import com.ruoyi.common.core.domain.R; import com.ruoyi.integration.api.elutong.model.ConnectorStatusReq; import com.ruoyi.integration.api.elutong.model.StationInfoReq; import com.ruoyi.integration.api.elutong.model.StationsStatusReq; import com.ruoyi.integration.api.factory.ELuTongClientFallbackFactory; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @FeignClient(contextId = "ELuTongClient", value = ServiceNameConstants.JIANGUAN_SERVICE, fallbackFactory = ELuTongClientFallbackFactory.class) public interface ELuTongClient { /** * 推送充电站静态信息 * @param stationInfoReq * @return */ @PostMapping("/elutong/pushStationInfo") R pushStationInfo(@RequestBody StationInfoReq stationInfoReq); /** * 推送充电站状态信息 * @param stationsStatusReq * @return */ @PostMapping("/elutong/pushStationsStatus") R pushStationsStatus(@RequestBody StationsStatusReq stationsStatusReq); /** * 推送设备接口状态信息 * @param connectorStatusReq * @return */ @PostMapping("/elutong/pushConnectorStatus") R pushConnectorStatus(@RequestBody ConnectorStatusReq connectorStatusReq); } ruoyi-api/ruoyi-api-jianguan/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1 +1,2 @@ com.ruoyi.integration.api.factory.ChargingMessageClientFallbackFactory com.ruoyi.integration.api.factory.ChargingMessageClientFallbackFactory com.ruoyi.integration.api.factory.ELuTongClientFallbackFactory ruoyi-service/ruoyi-chargingPile/pom.xml
@@ -158,6 +158,10 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-api-jianguan</artifactId> </dependency> </dependencies> <build> ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/TChargingPileController.java
@@ -3,9 +3,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.ruoyi.chargingPile.api.model.TChargingGun; import com.ruoyi.chargingPile.api.model.TChargingPile; import com.ruoyi.chargingPile.api.model.TFaultMessage; import com.ruoyi.chargingPile.api.model.*; import com.ruoyi.chargingPile.api.query.TChargingGunQuery; import com.ruoyi.chargingPile.api.vo.TChargingGunVO; import com.ruoyi.chargingPile.api.vo.UpdateChargingPileStatusVo; @@ -15,6 +13,7 @@ import com.ruoyi.chargingPile.dto.GetChargingGunMonitoring; import com.ruoyi.chargingPile.service.*; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.page.PageInfo; import com.ruoyi.chargingPile.api.dto.PageChargingPileListDTO; @@ -30,6 +29,11 @@ import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.common.security.annotation.Logical; import com.ruoyi.common.security.annotation.RequiresPermissions; import com.ruoyi.integration.api.elutong.model.ConnectorStatusInfo; import com.ruoyi.integration.api.elutong.model.ConnectorStatusReq; import com.ruoyi.integration.api.elutong.model.StationStausInfo; import com.ruoyi.integration.api.elutong.model.StationsStatusReq; import com.ruoyi.integration.api.feignClient.ELuTongClient; import com.ruoyi.integration.api.feignClient.TCECClient; import com.ruoyi.order.api.feignClient.ChargingOrderClient; import com.ruoyi.order.api.model.TChargingOrder; @@ -39,6 +43,7 @@ import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*; @@ -60,6 +65,7 @@ * @author xiaochen * @since 2024-08-06 */ @Slf4j @Api(tags = "充电桩") @RestController @RequestMapping("/t-charging-pile") @@ -79,6 +85,14 @@ @Resource private TCECClient tcecClient; @Resource private ELuTongClient eLuTongClient; @Resource private ISiteService siteService; @Autowired @@ -268,6 +282,7 @@ public void run() { //推送状态给三方平台 tcecClient.pushChargingGunStatus(tChargingGun.getFullNumber(), tChargingGun1.getStatus()); pushConnectorStatus(tChargingGun); } }).start(); @@ -296,6 +311,7 @@ public void run() { //推送状态给三方平台 tcecClient.pushChargingGunStatus(tChargingGun.getFullNumber(), tChargingGun1.getStatus()); pushConnectorStatus(tChargingGun); } }).start(); @@ -349,6 +365,7 @@ public void run() { //推送状态给三方平台 tcecClient.pushChargingGunStatus(tChargingGun.getFullNumber(), tChargingGun1.getStatus()); pushConnectorStatus(tChargingGun); } }).start(); }else{ @@ -361,6 +378,7 @@ public void run() { //推送状态给三方平台 tcecClient.pushChargingGunStatus(tChargingGun.getFullNumber(), tChargingGun1.getStatus()); pushConnectorStatus(tChargingGun); } }).start(); } @@ -374,5 +392,60 @@ } } } /** * 推送设备接口状态信息 * @param chargingGun */ private void pushConnectorStatus(TChargingGun chargingGun){ Site site = siteService.getById(chargingGun.getSiteId()); if(StringUtils.isNotEmpty(site.getSerAreaCode())){ ConnectorStatusReq connectorStatusReq = new ConnectorStatusReq(); connectorStatusReq.setOperatorId("91510903906171535D"); connectorStatusReq.setSerAreaCode(site.getSerAreaCode()); connectorStatusReq.setStationId(site.getId().toString()); List<ConnectorStatusInfo> connectorStatusInfos = new ArrayList<>(); connectorStatusInfos.add(buildConnectorStatus(chargingGun)); connectorStatusReq.setItemSize(connectorStatusInfos.size()); connectorStatusReq.setConnectorStatusInfos(connectorStatusInfos); R r = eLuTongClient.pushConnectorStatus(connectorStatusReq); if(200 != r.getCode()){ log.error(r.getMsg()); } } } private ConnectorStatusInfo buildConnectorStatus(TChargingGun chargingGun){ ConnectorStatusInfo connectorStatusInfo = new ConnectorStatusInfo(); connectorStatusInfo.setConnectorId(chargingGun.getId().toString()); switch (chargingGun.getStatus()){ case 1: connectorStatusInfo.setStatus(0); break; case 2: connectorStatusInfo.setStatus(1); break; case 3: connectorStatusInfo.setStatus(2); break; case 4: connectorStatusInfo.setStatus(3); break; case 5: connectorStatusInfo.setStatus(3); break; case 6: connectorStatusInfo.setStatus(4); break; case 7: connectorStatusInfo.setStatus(255); break; } connectorStatusInfo.setSoc(0D); connectorStatusInfo.setRemainingTime(0); return connectorStatusInfo; } } ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/service/impl/SiteServiceImpl.java
@@ -24,6 +24,8 @@ import com.ruoyi.common.core.web.page.PageInfo; import com.ruoyi.common.security.service.TokenService; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.integration.api.elutong.model.*; import com.ruoyi.integration.api.feignClient.ELuTongClient; import com.ruoyi.integration.api.feignClient.IntegrationClient; import com.ruoyi.integration.api.feignClient.TCECClient; import com.ruoyi.other.api.domain.TVip; @@ -41,7 +43,11 @@ import javax.annotation.Resource; import java.math.BigDecimal; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -90,6 +96,8 @@ @Resource private TCECClient tcecClient; @Resource ELuTongClient eLuTongClient; /** @@ -196,9 +204,176 @@ } site.setMark(0); this.save(site); if(StringUtils.isNotEmpty(site.getSerAreaCode())){ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); threadPoolExecutor.execute(()->{ StationInfoReq stationInfoReq = new StationInfoReq(); stationInfoReq.setOperatorId("91510903906171535D"); stationInfoReq.setStationId(site.getId().toString()); OperatorInfo operatorInfo = new OperatorInfo(); operatorInfo.setOperatorId("91510903906171535D"); operatorInfo.setOperatorName("四川明星新能源科技有限公司"); operatorInfo.setOperatorTel1("18683346252"); operatorInfo.setOperatorTel2("13982508784"); operatorInfo.setOperatorRegAddress("遂宁市船山区渠河南路18号"); stationInfoReq.setOperatorInfo(operatorInfo); List<StationInfo> stationInfos = new ArrayList<>(); stationInfos.add(buildStationInfo(site)); stationInfoReq.setItemSize(stationInfos.size()); stationInfoReq.setStationInfos(stationInfos); R r = eLuTongClient.pushStationInfo(stationInfoReq); if(200 != r.getCode()){ System.out.println(r.getMsg()); } }); } return AjaxResult.success(); } private StationInfo buildStationInfo(Site site){ StationInfo stationInfo = new StationInfo(); stationInfo.setSerAreaCode(site.getSerAreaCode()); stationInfo.setOperatorId("91510903906171535D"); stationInfo.setStationId(site.getId().toString()); stationInfo.setStationName(site.getName()); stationInfo.setStationMode("CD"); stationInfo.setAreaCode(site.getDistrictsCode()); stationInfo.setServiceTel(site.getServicePhone()); switch (site.getSiteType()){ case 0: stationInfo.setStationType(255); break; case 1: stationInfo.setStationType(1); break; case 2: stationInfo.setStationType(50); break; case 3: stationInfo.setStationType(100); break; case 4: stationInfo.setStationType(101); break; case 5: stationInfo.setStationType(102); break; case 6: stationInfo.setStationType(103); break; } switch (site.getStatus()){ case 3: stationInfo.setStationStatus(5); break; case 2: stationInfo.setStationStatus(6); break; case 1: stationInfo.setStationStatus(50); break; } stationInfo.setParkNums(site.getParkingSpace()); stationInfo.setStationLng(Double.parseDouble(site.getLon())); stationInfo.setStationLat(Double.parseDouble(site.getLat())); stationInfo.setSiteGuide(site.getGuide()); switch (site.getConstructionSite()){ case 1: stationInfo.setConstruction(1); break; case 2: stationInfo.setConstruction(2); break; case 3: stationInfo.setConstruction(3); break; case 4: stationInfo.setConstruction(4); break; case 5: stationInfo.setConstruction(5); break; case 6: stationInfo.setConstruction(6); break; case 7: stationInfo.setConstruction(7); break; case 8: stationInfo.setConstruction(8); break; case 9: stationInfo.setConstruction(9); break; case 10: stationInfo.setConstruction(10); break; case 11: stationInfo.setConstruction(11); break; case 0: stationInfo.setConstruction(255); break; } if(StringUtils.isNotEmpty(site.getImgUrl())){ stationInfo.setPictures(Arrays.asList(site.getImgUrl().split(","))); } stationInfo.setMatchCars(site.getVehicleDescription()); stationInfo.setBusineHours(site.getStartServiceTime() + " - " + site.getEndServiceTime()); stationInfo.setEquipmentInfos(buildEquipmentInfo(site)); return stationInfo; } private List<EquipmentInfo> buildEquipmentInfo(Site site){ List<TChargingPile> list = chargingPileService.list(new LambdaQueryWrapper<TChargingPile>() .eq(TChargingPile::getSiteId, site.getId()).eq(TChargingPile::getDelFlag, 0)); return new ArrayList<EquipmentInfo>(){{ for (TChargingPile chargingPile : list) { EquipmentInfo equipmentInfo = new EquipmentInfo(); equipmentInfo.setEquipmentId(chargingPile.getId().toString()); equipmentInfo.setEquipmentName(chargingPile.getName()); equipmentInfo.setManufacturerId(chargingPile.getManufacturerCode()); equipmentInfo.setManufacturerName(chargingPile.getManufacturer()); equipmentInfo.setEquipmentModel(chargingPile.getEquipmentType()); if(null != chargingPile.getProductionDate()){ equipmentInfo.setProductionDate(chargingPile.getProductionDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); } equipmentInfo.setEquipmentType(0 == chargingPile.getType() ? 5 : chargingPile.getType()); if(StringUtils.isNotEmpty(chargingPile.getEquipmentLng())){ equipmentInfo.setEquipmentLng(Double.parseDouble(chargingPile.getEquipmentLng())); } if(StringUtils.isNotEmpty(chargingPile.getEquipmentLat())){ equipmentInfo.setEquipmentLat(Double.parseDouble(chargingPile.getEquipmentLat())); } equipmentInfo.setPower(chargingPile.getRatedPower().doubleValue()); equipmentInfo.setConnectorInfos(buildConnectorInfo(chargingPile)); add(equipmentInfo); } }}; } private List<ConnectorInfo> buildConnectorInfo(TChargingPile chargingPile){ List<TChargingGun> list = chargingGunService.list(new LambdaQueryWrapper<TChargingGun>() .eq(TChargingGun::getChargingPileId, chargingPile.getId()).eq(TChargingGun::getDelFlag, 0)); return new ArrayList<ConnectorInfo>(){{ for (TChargingGun chargingGun : list) { ConnectorInfo connectorInfo = new ConnectorInfo(); connectorInfo.setConnectorId(chargingGun.getId().toString()); connectorInfo.setConnectorName(chargingGun.getName()); connectorInfo.setConnectorType(0 == chargingGun.getType() ? 6 : chargingGun.getType()); connectorInfo.setVoltageUpperLimits(chargingGun.getUpperRatedVoltage().intValue()); connectorInfo.setVoltageLowerLimits(chargingGun.getLowerLimitOfRatedVoltage().intValue()); connectorInfo.setCurrent(chargingGun.getRatedCurrent().intValue()); connectorInfo.setPower(chargingGun.getRatedPower().doubleValue()); connectorInfo.setParkNo(chargingGun.getParkingNumber()); connectorInfo.setNationalStandard(Integer.parseInt(chargingGun.getNationalStandard())); add(connectorInfo); } }}; } /** * 编辑站点 @@ -227,7 +402,29 @@ this.updateById(site); tcecClient.superviseNotificationStationInfo(site.getId()); if(StringUtils.isNotEmpty(site.getSerAreaCode())){ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); threadPoolExecutor.execute(()->{ StationInfoReq stationInfoReq = new StationInfoReq(); stationInfoReq.setOperatorId("91510903906171535D"); stationInfoReq.setStationId(site.getId().toString()); OperatorInfo operatorInfo = new OperatorInfo(); operatorInfo.setOperatorId("91510903906171535D"); operatorInfo.setOperatorName("四川明星新能源科技有限公司"); operatorInfo.setOperatorTel1("18683346252"); operatorInfo.setOperatorTel2("13982508784"); operatorInfo.setOperatorRegAddress("遂宁市船山区渠河南路18号"); stationInfoReq.setOperatorInfo(operatorInfo); List<StationInfo> stationInfos = new ArrayList<>(); stationInfos.add(buildStationInfo(site)); stationInfoReq.setItemSize(stationInfos.size()); stationInfoReq.setStationInfos(stationInfos); R r = eLuTongClient.pushStationInfo(stationInfoReq); if(200 != r.getCode()){ System.out.println(r.getMsg()); } }); } return AjaxResult.success(); } ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/util/TaskUtil.java
@@ -1,18 +1,32 @@ package com.ruoyi.chargingPile.util; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; import com.ruoyi.chargingPile.api.model.Site; import com.ruoyi.chargingPile.api.model.TChargingGun; import com.ruoyi.chargingPile.service.ISiteService; import com.ruoyi.chargingPile.service.TChargingGunService; import com.ruoyi.chargingPile.service.TChargingPileService; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.integration.api.elutong.model.*; import com.ruoyi.integration.api.feignClient.ELuTongClient; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.context.WebServerInitializedEvent; import org.springframework.context.ApplicationListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; /** * 定时任务工具类 */ @Slf4j @Component public class TaskUtil implements ApplicationListener<WebServerInitializedEvent> { @@ -20,6 +34,15 @@ private TChargingPileService chargingPileService; private Integer port = null; @Resource private ELuTongClient eLuTongClient; @Resource private ISiteService siteService; @Resource private TChargingGunService chargingGunService; @Override @@ -35,8 +58,78 @@ public void taskMinute(){ if(null != port && port == 5300){ chargingPileService.updateStatus(); pushStationsStatus(); } } /** * 1分钟定时推送设备状态E路通 */ private void pushStationsStatus(){ List<Site> list = siteService.list(new LambdaQueryWrapper<Site>().eq(Site::getDelFlag, 0).isNotNull(Site::getSerAreaCode)); for (Site site : list) { if(StringUtils.isEmpty(site.getSerAreaCode())){ continue; } StationsStatusReq stationsStatusReq = new StationsStatusReq(); stationsStatusReq.setOperatorId("91510903906171535D"); stationsStatusReq.setSerAreaCode(site.getSerAreaCode()); stationsStatusReq.setStationId(site.getId().toString()); List<StationStausInfo> stationStausInfos = new ArrayList<>(); stationStausInfos.add(buildStationStaus(site)); stationsStatusReq.setItemSize(stationStausInfos.size()); stationsStatusReq.setStationStausInfos(stationStausInfos); R r = eLuTongClient.pushStationsStatus(stationsStatusReq); if(200 != r.getCode()){ log.error(r.getMsg()); } } } private StationStausInfo buildStationStaus(Site site){ StationStausInfo stationStausInfo = new StationStausInfo(); stationStausInfo.setStationId(site.getId().toString()); stationStausInfo.setConnectorStatusInfos(buildConnectorStatus(site)); return stationStausInfo; } private List<ConnectorStatusInfo> buildConnectorStatus(Site site){ List<TChargingGun> list = chargingGunService.list(new LambdaQueryWrapper<TChargingGun>().eq(TChargingGun::getDelFlag, 0) .eq(TChargingGun::getSiteId, site.getId())); return new ArrayList<ConnectorStatusInfo>(){{ for (TChargingGun chargingGun : list) { ConnectorStatusInfo connectorStatusInfo = new ConnectorStatusInfo(); connectorStatusInfo.setConnectorId(chargingGun.getId().toString()); switch (chargingGun.getStatus()){ case 1: connectorStatusInfo.setStatus(0); break; case 2: connectorStatusInfo.setStatus(1); break; case 3: connectorStatusInfo.setStatus(2); break; case 4: connectorStatusInfo.setStatus(3); break; case 5: connectorStatusInfo.setStatus(3); break; case 6: connectorStatusInfo.setStatus(4); break; case 7: connectorStatusInfo.setStatus(255); break; } connectorStatusInfo.setSoc(0D); connectorStatusInfo.setRemainingTime(0); add(connectorStatusInfo); } }}; } } ruoyi-service/ruoyi-jianguan/src/main/java/com/ruoyi/jianguan/elutong/config/AppServerConfig.java
New file @@ -0,0 +1,26 @@ package com.ruoyi.jianguan.elutong.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "app.server.config") public class AppServerConfig { //应用ID private String appId; //应用密码 private String appSecret; //应用公钥 private String pubKey; //应用私钥 private String priKey; //AES秘钥 private String aesKey; //接口地址 private String serverUrl; //验签公钥 private String signPub; } ruoyi-service/ruoyi-jianguan/src/main/java/com/ruoyi/jianguan/elutong/config/HttpsClientRequestFactory.java
New file @@ -0,0 +1,130 @@ package com.ruoyi.jianguan.elutong.config; import org.springframework.http.client.SimpleClientHttpRequestFactory; import javax.net.ssl.*; import java.io.IOException; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.Socket; import java.security.cert.X509Certificate; /** * TLS的三个作用: * (1)身份认证 * 通过证书认证来确认对方的身份,防止中间人攻击 * (2)数据私密性 * 使用对称性密钥加密传输的数据,由于密钥只有客户端/服务端有,其他人无法窥探。 * (3)数据完整性 * 使用摘要算法对报文进行计算,收到消息后校验该值防止数据被篡改或丢失。 * <p> * 使用RestTemplate进行HTTPS请求访问: * private static RestTemplate restTemplate = new RestTemplate(new HttpsClientRequestFactory()); */ public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory { @Override protected void prepareConnection(HttpURLConnection connection, String httpMethod) { try { if (!(connection instanceof HttpsURLConnection)) { throw new RuntimeException("An instance of HttpsURLConnection is expected"); } HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory())); httpsConnection.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { return true; } }); super.prepareConnection(httpsConnection, httpMethod); } catch (Exception e) { e.printStackTrace(); } } private static class MyCustomSSLSocketFactory extends SSLSocketFactory { private final SSLSocketFactory delegate; public MyCustomSSLSocketFactory(SSLSocketFactory delegate) { this.delegate = delegate; } // 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。 // 这些默认的服务的最低质量要求保密保护和服务器身份验证 @Override public String[] getDefaultCipherSuites() { return delegate.getDefaultCipherSuites(); } // 返回的密码套件可用于SSL连接启用的名字 @Override public String[] getSupportedCipherSuites() { return delegate.getSupportedCipherSuites(); } @Override public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose) throws IOException { final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose); return overrideProtocol(underlyingSocket); } @Override public Socket createSocket(final String host, final int port) throws IOException { final Socket underlyingSocket = delegate.createSocket(host, port); return overrideProtocol(underlyingSocket); } @Override public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort) throws IOException { final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort); return overrideProtocol(underlyingSocket); } @Override public Socket createSocket(final InetAddress host, final int port) throws IOException { final Socket underlyingSocket = delegate.createSocket(host, port); return overrideProtocol(underlyingSocket); } @Override public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, final int localPort) throws IOException { final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort); return overrideProtocol(underlyingSocket); } private Socket overrideProtocol(final Socket socket) { if (!(socket instanceof SSLSocket)) { throw new RuntimeException("An instance of SSLSocket is expected"); } //((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2"}); ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}); return socket; } } } ruoyi-service/ruoyi-jianguan/src/main/java/com/ruoyi/jianguan/elutong/config/RestConfigBean.java
New file @@ -0,0 +1,20 @@ package com.ruoyi.jianguan.elutong.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class RestConfigBean { // @Bean // public RestTemplate getRestTemplate() { // return new RestTemplate(new HttpsClientRequestFactory()); // } @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } } ruoyi-service/ruoyi-jianguan/src/main/java/com/ruoyi/jianguan/elutong/controller/SampleController.java
New file @@ -0,0 +1,341 @@ package com.ruoyi.jianguan.elutong.controller; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson2.JSON; import com.ruoyi.common.core.domain.R; import com.ruoyi.jianguan.elutong.config.AppServerConfig; import com.ruoyi.jianguan.elutong.core.AESTools; import com.ruoyi.jianguan.elutong.core.CommonRequest; import com.ruoyi.jianguan.elutong.core.CommonResponse; import com.ruoyi.jianguan.elutong.core.SignatureTools; import com.ruoyi.integration.api.elutong.model.ConnectorStatusReq; import com.ruoyi.integration.api.elutong.model.StationInfoReq; import com.ruoyi.integration.api.elutong.model.StationsStatusReq; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @Slf4j @RestController @RequestMapping("/elutong") public class SampleController { @Autowired private AppServerConfig appServerConfig; @Autowired private RestTemplate restTemplate; /** * 签名 * * @param request * @param privateKey * @return */ public static CommonRequest sign(CommonRequest request, String privateKey) { String signStr = SignatureTools.buildSignStr(request); signStr = signStr + "&filename=" + request.getFilename(); log.info("【E路通】signStr : {}", signStr); String sign = SignatureTools.rsa256Sign(signStr, privateKey); request.setSign(sign); return request; } /** * 验签 * * @param response * @return */ public boolean verifySign(CommonResponse response) { String signStr = SignatureTools.buildSignStr(response); log.info("【E路通】signStr : {}", signStr); log.info("【E路通】sign : {}", response.getSign()); return SignatureTools.rsa256Verify(signStr, appServerConfig.getSignPub(), response.getSign()); } @GetMapping("/test") public String test() { Date date = new Date(); DateFormat format = new SimpleDateFormat("yyyyMMddHHmmssSSS"); String filename = "CHARGE_DATA_STATIONINFO_REQ"+"_"+appServerConfig.getAppId()+"_"+format.format(date)+".json"; //String filename = "SAIS_SERVICEAREA_LPAGE_REQ" + "_" + appServerConfig.getAppId() + "_" + format.format(date) + ".json"; CommonRequest req = new CommonRequest(); req.setEncryptType("AES"); req.setSignType("RSA256"); req.setFilename(filename); format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); req.setTimestamp(format.format(date)); String requestJson = "{\n" + "\"operatorId\":\"91530xxxxK3LK15Y\",\n" + "\"operatorInfo\":{\n" + "\"operatorId\":\"91530xxxxK3LK15Y\",\n" + "\"operatorName\":\"xxx产业发展有限公司\",\n" + "\"operatorTel1\":\"0871-61408801\",\n" + "\"operatorTel2\":\"0871-61408801\",\n" + "\"operatorRegAddress\":\"xxx西山区前卫街道前福路231号\",\n" + "\"operatorNote\":null\n" + "},\n" + "\"stationId\":\"G56-111111-220-2-02819-0-CD-01025001\",\n" + "\"serAreaCode\":\"G56-111111-220-2-02819-0\",\n" + "\"itemSize\":1,\n" + "\"stationInfos\":[\n" + "{\n" + "\"serAreaCode\":\"G56-111111-220-2-02819-0\",\n" + "\"stationId\":\"G56-111111-220-2-02819-0-CD-01025001\",\n" + "\"stationMode\":\"CD\",\n" + "\"operatorId\":\"915300016K3LK15Y\",\n" + "\"stationName\":\"黄草xxx车区上行\",\n" + "\"areaCode\":\"510100\",\n" + "\"serviceTel\":\"0871-68408801\",\n" + "\"stationType\":1,\n" + "\"stationStatus\":50,\n" + "\"parkNums\":2,\n" + "\"stationLng\":98.7116251,\n" + "\"stationLat\":24.6117291,\n" + "\"siteGuide\":null,\n" + "\"construction\":11,\n" + "\"pictures\":null,\n" + "\"matchCars\":null,\n" + "\"businessHours\":\"周一至周日00:00-24:00\",\n" + "\"electricityFee\":null,\n" + "\"serviceFee\":null,\n" + "\"parkFee\":null,\n" + "\"payment\":null,\n" + "\"supportOrder\":null,\n" + "\"equipmentInfos\":[\n" + "{\n" + "\"equipmentId\":\"02025001\",\n" + "\"manufacturerId\":\"11\",\n" + "\"manufacturerName\":null,\n" + "\"equipmentModel\":null,\n" + "\"productionDate\":null,\n" + "\"equipmentType\":1,\n" + "\"connectorInfos\":[\n" + "{\n" + "\"connectorId\":\"0202500101\",\n" + "\"connectorName\":null,\n" + "\"connectorType\":1,\n" + "\"voltageUpperLimits\":750,\n" + "\"voltageLowerLimits\":150,\n" + "\"current\":40,\n" + "\"power\":30.0,\n" + "\"parkNo\":null,\n" + "\"nationalStandard\":2\n" + "}\n" + "],\n" + "\"equipmentLng\":null,\n" + "\"equipmentLat\":null,\n" + "\"power\":37.0,\n" + "\"equipmentName\":null\n" + "}\n" + "],\n" + "\"remark\":null\n" + "}\n" + "]\n" + "}\n"; //String requestJson = "{\"pageNum\":1,\"pageSize\":700}"; System.out.println("queryParam : " + requestJson); String bizContentJson = AESTools.encrypt(requestJson, appServerConfig.getAesKey()); req.setBizContent(bizContentJson); System.out.println("encrypt CommonRequest toJson : " + JSONObject.toJSONString(req)); req = sign(req, appServerConfig.getPriKey()); System.out.println("sign : " + req.getSign()); System.out.println("request body : " + JSONObject.toJSONString(req)); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); String bodyStr = JSONObject.toJSONString(req); HttpEntity<String> request = new HttpEntity<String>(bodyStr, headers); ResponseEntity<String> response = restTemplate.postForEntity(appServerConfig.getServerUrl(), request, String.class); String result = ""; if (response.getStatusCodeValue() == 200) { result = response.getBody(); System.out.println("response result : " + result); CommonResponse commonResponse = JSONObject.parseObject(result, CommonResponse.class); if ("200".equals(commonResponse.getStatusCode())) { boolean rsa256Verify = verifySign(commonResponse); System.out.println("verify sign result : " + rsa256Verify); } } return result; } /** * 推送充电站静态信息 * @param stationInfoReq * @return */ @ResponseBody @PostMapping("/pushStationInfo") public R pushStationInfo(@RequestBody StationInfoReq stationInfoReq){ Date date = new Date(); DateFormat format = new SimpleDateFormat("yyyyMMddHHmmssSSS"); String filename = "CHARGE_DATA_STATIONINFO_REQ_" + appServerConfig.getAppId() + "_" + format.format(date) + ".json"; CommonRequest req = new CommonRequest(); req.setEncryptType("AES"); req.setSignType("RSA256"); req.setFilename(filename); format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); req.setTimestamp(format.format(date)); String requestJson = JSON.toJSONString(stationInfoReq); log.info("【E路通-推送充电站静态信息】queryParam : {}", requestJson); String bizContentJson = AESTools.encrypt(requestJson, appServerConfig.getAesKey()); req.setBizContent(bizContentJson); log.info("【E路通-推送充电站静态信息】encrypt CommonRequest toJson : {}", JSONObject.toJSONString(req)); req = sign(req, appServerConfig.getPriKey()); log.info("【E路通-推送充电站静态信息】sign : {}", req.getSign()); log.info("【E路通-推送充电站静态信息】request body : {}", JSONObject.toJSONString(req)); HttpRequest post = HttpUtil.createPost(appServerConfig.getServerUrl()); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); post.header(headers); String bodyStr = JSONObject.toJSONString(req); post.body(bodyStr); HttpResponse execute = post.execute(); String result = ""; if (execute.getStatus() == 200) { result = execute.body(); log.info("【E路通-推送充电站静态信息】response result : {}", result); CommonResponse commonResponse = JSONObject.parseObject(result, CommonResponse.class); if ("200".equals(commonResponse.getStatusCode())) { boolean rsa256Verify = verifySign(commonResponse); log.info("【E路通-推送充电站静态信息】verify sign result : {}", rsa256Verify); return R.ok(); }else{ return R.fail(commonResponse.getErrorMsg()); } } return R.fail("推送接口请求失败"); } /** * 推送充电站状态信息 * @param stationsStatusReq * @return */ @ResponseBody @PostMapping("/pushStationsStatus") public R pushStationsStatus(@RequestBody StationsStatusReq stationsStatusReq){ Date date = new Date(); DateFormat format = new SimpleDateFormat("yyyyMMddHHmmssSSS"); String filename = "CHARGE_DATA_STATIONSTATUS_REQ_" + appServerConfig.getAppId() + "_" + format.format(date) + ".json"; CommonRequest req = new CommonRequest(); req.setEncryptType("AES"); req.setSignType("RSA256"); req.setFilename(filename); format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); req.setTimestamp(format.format(date)); String requestJson = JSON.toJSONString(stationsStatusReq); log.info("【E路通-推送充电站状态信息】queryParam : {}", requestJson); String bizContentJson = AESTools.encrypt(requestJson, appServerConfig.getAesKey()); req.setBizContent(bizContentJson); log.info("【E路通-推送充电站状态信息】encrypt CommonRequest toJson : {}", JSONObject.toJSONString(req)); req = sign(req, appServerConfig.getPriKey()); log.info("【E路通-推送充电站状态信息】sign : {}", req.getSign()); log.info("【E路通-推送充电站状态信息】request body : {}", JSONObject.toJSONString(req)); HttpRequest post = HttpUtil.createPost(appServerConfig.getServerUrl()); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); post.header(headers); String bodyStr = JSONObject.toJSONString(req); post.body(bodyStr); HttpResponse execute = post.execute(); String result = ""; if (execute.getStatus() == 200) { result = execute.body(); log.info("【E路通-推送充电站状态信息】response result : {}", result); CommonResponse commonResponse = JSONObject.parseObject(result, CommonResponse.class); if ("200".equals(commonResponse.getStatusCode())) { boolean rsa256Verify = verifySign(commonResponse); log.info("【E路通-推送充电站状态信息】verify sign result : {}", rsa256Verify); return R.ok(); }else{ return R.fail(commonResponse.getErrorMsg()); } } return R.fail("推送接口请求失败"); } /** * 推送设备接口状态信息 * @param connectorStatusReq * @return */ @ResponseBody @PostMapping("/pushConnectorStatus") public R pushConnectorStatus(@RequestBody ConnectorStatusReq connectorStatusReq){ Date date = new Date(); DateFormat format = new SimpleDateFormat("yyyyMMddHHmmssSSS"); String filename = "CHARGE_NOTIFICATION_CONNECTORSTAUS_REQ_" + appServerConfig.getAppId() + "_" + format.format(date) + ".json"; CommonRequest req = new CommonRequest(); req.setEncryptType("AES"); req.setSignType("RSA256"); req.setFilename(filename); format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); req.setTimestamp(format.format(date)); String requestJson = JSON.toJSONString(connectorStatusReq); log.info("【E路通-推送设备接口状态信息】queryParam : {}", requestJson); String bizContentJson = AESTools.encrypt(requestJson, appServerConfig.getAesKey()); req.setBizContent(bizContentJson); log.info("【E路通-推送设备接口状态信息】encrypt CommonRequest toJson : {}", JSONObject.toJSONString(req)); req = sign(req, appServerConfig.getPriKey()); log.info("【E路通-推送设备接口状态信息】sign : {}", req.getSign()); log.info("【E路通-推送设备接口状态信息】request body : {}", JSONObject.toJSONString(req)); HttpRequest post = HttpUtil.createPost(appServerConfig.getServerUrl()); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); post.header(headers); String bodyStr = JSONObject.toJSONString(req); post.body(bodyStr); HttpResponse execute = post.execute(); String result = ""; if (execute.getStatus() == 200) { result = execute.body(); log.info("【E路通-推送设备接口状态信息】response result : {}", result); CommonResponse commonResponse = JSONObject.parseObject(result, CommonResponse.class); if ("200".equals(commonResponse.getStatusCode())) { boolean rsa256Verify = verifySign(commonResponse); log.info("【E路通-推送设备接口状态信息】verify sign result : {}", rsa256Verify); return R.ok(); }else{ return R.fail(commonResponse.getErrorMsg()); } } return R.fail("推送接口请求失败"); } } ruoyi-service/ruoyi-jianguan/src/main/java/com/ruoyi/jianguan/elutong/core/AESTools.java
New file @@ -0,0 +1,69 @@ package com.ruoyi.jianguan.elutong.core; import org.slf4j.LoggerFactory; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; public class AESTools { private static final String KEY_ALGORITHM = "AES"; private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";// 默认的加密算法 /** * AES 加密操作 * * @param content 待加密内容 * @param password 加密密码 * @return 返回Base64转码后的加密数据 */ public static String encrypt(String content, String password) { try { Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器 byte[] byteContent = content.getBytes("utf-8"); cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(password));// 初始化为加密模式的密码器 byte[] result = cipher.doFinal(byteContent);// 加密 return Base64Tools.encodeToString(result);// 通过Base64转码返回 } catch (Exception ex) { LoggerFactory.getLogger(AESTools.class).error("", ex); throw new RuntimeException("AES加密失败:" + content); } } /** * AES 解密操作 * * @param content * @param password * @return */ public static String decrypt(String content, String password) { try { // 实例化 Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); // 使用密钥初始化,设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, getSecretKey(password)); // 执行操作 byte[] result = cipher.doFinal(Base64Tools.decodeFromString(content)); return new String(result, "utf-8"); } catch (Exception ex) { LoggerFactory.getLogger(AESTools.class).error("", ex); throw new RuntimeException("AES解密失败:" + content); } } /** * 生成加密秘钥 * * @return */ private static SecretKeySpec getSecretKey(final String password) { try { return new SecretKeySpec(password.getBytes("UTF-8"), KEY_ALGORITHM);// 转换为AES专用密钥 } catch (Exception ex) { LoggerFactory.getLogger(AESTools.class).error("", ex); throw new RuntimeException("生成加密秘钥"); } } } ruoyi-service/ruoyi-jianguan/src/main/java/com/ruoyi/jianguan/elutong/core/Base64Tools.java
New file @@ -0,0 +1,111 @@ package com.ruoyi.jianguan.elutong.core; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Base64; public abstract class Base64Tools { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; /** * Base64-encode the given byte array. * * @param src the original byte array * @return the encoded byte array */ public static byte[] encode(byte[] src) { if (src.length == 0) { return src; } return Base64.getEncoder().encode(src); } /** * Base64-decode the given byte array. * * @param src the encoded byte array * @return the original byte array */ public static byte[] decode(byte[] src) { if (src.length == 0) { return src; } return Base64.getDecoder().decode(src); } /** * Base64-encode the given byte array using the RFC 4648 "URL and Filename Safe Alphabet". * * @param src the original byte array * @return the encoded byte array * @since 4.2.4 */ public static byte[] encodeUrlSafe(byte[] src) { if (src.length == 0) { return src; } return Base64.getUrlEncoder().encode(src); } /** * Base64-decode the given byte array using the RFC 4648 "URL and Filename Safe Alphabet". * * @param src the encoded byte array * @return the original byte array * @since 4.2.4 */ public static byte[] decodeUrlSafe(byte[] src) { if (src.length == 0) { return src; } return Base64.getUrlDecoder().decode(src); } /** * Base64-encode the given byte array to a String. * * @param src the original byte array (may be {@code null}) * @return the encoded byte array as a UTF-8 String */ public static String encodeToString(byte[] src) { if (src.length == 0) { return ""; } return new String(encode(src), DEFAULT_CHARSET); } /** * Base64-decode the given byte array from an UTF-8 String. * * @param src the encoded UTF-8 String * @return the original byte array */ public static byte[] decodeFromString(String src) { if (src.isEmpty()) { return new byte[0]; } return decode(src.getBytes(DEFAULT_CHARSET)); } /** * Base64-encode the given byte array to a String using the RFC 4648 "URL and Filename Safe Alphabet". * * @param src the original byte array * @return the encoded byte array as a UTF-8 String */ public static String encodeToUrlSafeString(byte[] src) { return new String(encodeUrlSafe(src), DEFAULT_CHARSET); } /** * Base64-decode the given byte array from an UTF-8 String using the RFC 4648 "URL and Filename Safe Alphabet". * * @param src the encoded UTF-8 String * @return the original byte array */ public static byte[] decodeFromUrlSafeString(String src) { return decodeUrlSafe(src.getBytes(DEFAULT_CHARSET)); } } ruoyi-service/ruoyi-jianguan/src/main/java/com/ruoyi/jianguan/elutong/core/CommonRequest.java
New file @@ -0,0 +1,16 @@ package com.ruoyi.jianguan.elutong.core; import lombok.Getter; import lombok.Setter; @Getter @Setter public class CommonRequest { private String encryptType; private String signType; private String filename; private String bizContent; private String sign; private String timestamp; private String version = "1.0"; } ruoyi-service/ruoyi-jianguan/src/main/java/com/ruoyi/jianguan/elutong/core/CommonResponse.java
New file @@ -0,0 +1,12 @@ package com.ruoyi.jianguan.elutong.core; import lombok.Data; @Data public class CommonResponse { private String bizContent; private String errorMsg; private String sign; private String statusCode; } ruoyi-service/ruoyi-jianguan/src/main/java/com/ruoyi/jianguan/elutong/core/SignAlgorithm.java
New file @@ -0,0 +1,64 @@ package com.ruoyi.jianguan.elutong.core; /** * 签名算法类型 */ public enum SignAlgorithm { // The RSA signature algorithm NONEwithRSA("NONEwithRSA", "RSA"), // The MD2/MD5 with RSA Encryption signature algorithm MD2withRSA("MD2withRSA", "RSA"), MD5withRSA("MD5withRSA", "RSA"), // The signature algorithm with SHA-* and the RSA SHA1withRSA("SHA1withRSA", "RSA"), SHA256withRSA("SHA256withRSA", "RSA"), SHA384withRSA("SHA384withRSA", "RSA"), SHA512withRSA("SHA512withRSA", "RSA"), // The Digital Signature Algorithm NONEwithDSA("NONEwithDSA", "DSA"), // The DSA with SHA-1 signature algorithm SHA1withDSA("SHA1withDSA", "DSA"), // The ECDSA signature algorithms NONEwithECDSA("NONEwithECDSA", "EC"), SHA1withECDSA("SHA1withECDSA", "EC"), SHA256withECDSA("SHA256withECDSA", "EC"), SHA384withECDSA("SHA384withECDSA", "EC"), SHA512withECDSA("SHA512withECDSA", "EC"); private String value; private String type; /** * 构造 * * @param value 算法字符表示,区分大小写 * @param type XXXwithXXX算法的后半部分算法,如果为ECDSA,返回算法为EC */ private SignAlgorithm(String value, String type) { this.value = value; this.type = type; } /** * 获取算法字符串表示,区分大小写 * * @return 算法字符串表示 */ public String getValue() { return this.value; } public String getType() { return type; } public void setType(String type) { this.type = type; } } ruoyi-service/ruoyi-jianguan/src/main/java/com/ruoyi/jianguan/elutong/core/SignatureTools.java
New file @@ -0,0 +1,135 @@ package com.ruoyi.jianguan.elutong.core; import org.apache.commons.lang3.StringUtils; import java.lang.reflect.Field; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; public class SignatureTools { public static final String PUBLIC_KEY = "RSAPublicKey"; public static final String PRIVATE_KEY = "RSAPrivateKey"; private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; /** * sha256WithRsa 加签 * * @param content 待签名的字符串 * @param priKeyBase64 base64编码的私钥 * @return base64编码的签名 */ public static String rsa256Sign(String content, String priKeyBase64) { byte[] signed = sign(SignAlgorithm.SHA256withRSA, content.getBytes(DEFAULT_CHARSET), Base64Tools.decodeFromString(priKeyBase64)); return Base64Tools.encodeToString(signed); } /** * sha256WithRsa 验签 * * @param content 待验签的字符串 * @param pubKeyBase64 base64编码的公钥 * @param signBase64 base64编码签名 * @return 签名是否正确 */ public static boolean rsa256Verify(String content, String pubKeyBase64, String signBase64) { return verify(SignAlgorithm.SHA256withRSA, content.getBytes(DEFAULT_CHARSET), Base64Tools.decodeFromString(signBase64), Base64Tools.decodeFromString(pubKeyBase64)); } public static byte[] sign(SignAlgorithm algorithm, byte[] content, byte[] key) { try { PrivateKey priKey = generatePrivateKey(algorithm, key); Signature signature = Signature.getInstance(algorithm.getValue()); signature.initSign(priKey); signature.update(content); byte[] signed = signature.sign(); return signed; } catch (Exception e) { throw new RuntimeException(e); } } public static boolean verify(SignAlgorithm algorithm, byte[] content, byte[] sign, byte[] key) { try { Signature signature = Signature.getInstance(algorithm.getValue()); PublicKey publicKey = generatePublicKey(algorithm, key); signature.initVerify(publicKey); signature.update(content); return signature.verify(sign); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } public static PrivateKey generatePrivateKey(SignAlgorithm algorithmType, byte[] key) throws InvalidKeySpecException, NoSuchAlgorithmException { return generatePrivateKey(algorithmType, new PKCS8EncodedKeySpec(key)); } public static PrivateKey generatePrivateKey(SignAlgorithm algorithmType, KeySpec keySpec) throws InvalidKeySpecException, NoSuchAlgorithmException { return KeyFactory.getInstance(algorithmType.getType()).generatePrivate(keySpec); } public static PublicKey generatePublicKey(SignAlgorithm algorithm, byte[] key) throws InvalidKeySpecException, NoSuchAlgorithmException { return generatePublicKey(algorithm, new X509EncodedKeySpec(key)); } public static PublicKey generatePublicKey(SignAlgorithm algorithm, KeySpec keySpec) throws InvalidKeySpecException, NoSuchAlgorithmException { return KeyFactory.getInstance(algorithm.getType()).generatePublic(keySpec); } public static String buildSignStr(Object object) { if (object == null) { return null; } Map map = new HashMap(); for (Field field : object.getClass().getDeclaredFields()) { field.setAccessible(true); try { if ("sign".equals(field.getName())) { continue; } if ("filename".equals(field.getName())) { continue; } map.put(field.getName(), field.get(object)); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } TreeMap<String, String> treeMap = new TreeMap<>(map); StringBuffer strBuffer = new StringBuffer(); treeMap.entrySet().forEach(i -> { if (i.getValue() == null) { return; } if (StringUtils.isBlank(i.getValue())) { return; } strBuffer.append(i.getKey()).append("=").append(String.valueOf(i.getValue())).append("&"); }); String signStr = strBuffer.substring(0, strBuffer.length() - 1); return signStr; } }